osxaudiosink: Add support for SPDIF output
authorJosep Torra <n770galaxy@gmail.com>
Tue, 12 Jun 2012 10:42:31 +0000 (12:42 +0200)
committerSebastian Dröge <sebastian.droege@collabora.co.uk>
Thu, 14 Jun 2012 06:46:34 +0000 (08:46 +0200)
A big refactoring to allow passthrough AC3/DTS over SPDIF.
Several random cleanups and minor fixes.

sys/osxaudio/Makefile.am
sys/osxaudio/gstosxaudiosink.c
sys/osxaudio/gstosxaudiosink.h
sys/osxaudio/gstosxcoreaudio.h [new file with mode: 0644]
sys/osxaudio/gstosxringbuffer.c
sys/osxaudio/gstosxringbuffer.h

index 8c1c3f7..6c168a9 100644 (file)
@@ -20,7 +20,8 @@ libgstosxaudio_la_LIBTOOLFLAGS = --tag=disable-static
 noinst_HEADERS = gstosxaudiosink.h       \
                  gstosxaudioelement.h \
                  gstosxringbuffer.h      \
-                 gstosxaudiosrc.h
+                 gstosxaudiosrc.h     \
+                 gstosxcoreaudio.h
 
 
 
index 639f499..aee25c9 100644 (file)
@@ -2,6 +2,7 @@
  * GStreamer
  * Copyright (C) 2005,2006 Zaheer Abbas Merali <zaheerabbas at merali dot org>
  * Copyright (C) 2007,2008 Pioneers of the Inevitable <songbird@songbirdnest.com>
+ * Copyright (C) 2012 Fluendo S.A. <support@fluendo.com>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
 #include "gstosxaudiosink.h"
 #include "gstosxaudioelement.h"
 
+#include <gst/audio/gstaudioiec61937.h>
+
 GST_DEBUG_CATEGORY_STATIC (osx_audiosink_debug);
 #define GST_CAT_DEFAULT osx_audiosink_debug
 
+#include "gstosxcoreaudio.h"
+
 /* Filter signals and args */
 enum
 {
@@ -126,7 +131,9 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
         "signed = (boolean) { TRUE }, "
         "width = (int) 8, "
         "depth = (int) 8, "
-        "rate = (int) [1, MAX], " "channels = (int) [1, MAX]")
+        "rate = (int) [1, MAX], " "channels = (int) [1, MAX];"
+        "audio/x-ac3, framed = (boolean) true;"
+        "audio/x-dts, framed = (boolean) true")
     );
 
 static void gst_osx_audio_sink_set_property (GObject * object, guint prop_id,
@@ -134,11 +141,17 @@ static void gst_osx_audio_sink_set_property (GObject * object, guint prop_id,
 static void gst_osx_audio_sink_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * pspec);
 
+static gboolean gst_osx_audio_sink_stop (GstBaseSink * base);
+static GstCaps *gst_osx_audio_sink_getcaps (GstBaseSink * base);
+static gboolean gst_osx_audio_sink_acceptcaps (GstPad * pad, GstCaps * caps);
+
+static GstBuffer *gst_osx_audio_sink_sink_payload (GstBaseAudioSink * sink,
+    GstBuffer * buf);
 static GstRingBuffer *gst_osx_audio_sink_create_ringbuffer (GstBaseAudioSink *
     sink);
 static void gst_osx_audio_sink_osxelement_init (gpointer g_iface,
     gpointer iface_data);
-static void gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink);
+static gboolean gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink);
 static void gst_osx_audio_sink_set_volume (GstOsxAudioSink * sink);
 
 static OSStatus gst_osx_audio_sink_io_proc (GstOsxRingBuffer * buf,
@@ -205,8 +218,13 @@ gst_osx_audio_sink_class_init (GstOsxAudioSinkClass * klass)
       g_param_spec_double ("volume", "Volume", "Volume of this stream",
           0, 1.0, 1.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_osx_audio_sink_getcaps);
+  gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_osx_audio_sink_stop);
+
   gstbaseaudiosink_class->create_ringbuffer =
       GST_DEBUG_FUNCPTR (gst_osx_audio_sink_create_ringbuffer);
+  gstbaseaudiosink_class->payload =
+      GST_DEBUG_FUNCPTR (gst_osx_audio_sink_sink_payload);
 }
 
 static void
@@ -215,7 +233,12 @@ gst_osx_audio_sink_init (GstOsxAudioSink * sink, GstOsxAudioSinkClass * gclass)
   GST_DEBUG ("Initialising object");
 
   sink->device_id = kAudioDeviceUnknown;
+  sink->cached_caps = NULL;
+
   sink->volume = DEFAULT_VOLUME;
+
+  gst_pad_set_acceptcaps_function (GST_BASE_SINK (sink)->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_osx_audio_sink_acceptcaps));
 }
 
 static void
@@ -256,6 +279,178 @@ gst_osx_audio_sink_get_property (GObject * object, guint prop_id,
   }
 }
 
+static gboolean
+gst_osx_audio_sink_stop (GstBaseSink * base)
+{
+  GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (base);
+
+  if (sink->cached_caps) {
+    gst_caps_unref (sink->cached_caps);
+    sink->cached_caps = NULL;
+  }
+
+  return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SINK_CLASS, stop, (base), TRUE);
+}
+
+static GstCaps *
+gst_osx_audio_sink_getcaps (GstBaseSink * base)
+{
+  GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (base);
+  GstOsxRingBuffer *osxbuf;
+  GstElementClass *element_class;
+  GstPadTemplate *pad_template;
+  GstCaps *caps;
+  gchar *caps_string = NULL;
+
+  osxbuf = GST_OSX_RING_BUFFER (GST_BASE_AUDIO_SINK (sink)->ringbuffer);
+
+  if (!osxbuf) {
+    GST_DEBUG_OBJECT (sink, "device not open, using template caps");
+    return NULL;                /* base class will get template caps for us */
+  }
+
+  if (sink->cached_caps) {
+    caps_string = gst_caps_to_string (sink->cached_caps);
+    GST_DEBUG_OBJECT (sink, "using cached caps: %s", caps_string);
+    g_free (caps_string);
+    return gst_caps_ref (sink->cached_caps);
+  }
+
+  element_class = GST_ELEMENT_GET_CLASS (sink);
+  pad_template = gst_element_class_get_pad_template (element_class, "sink");
+  g_return_val_if_fail (pad_template != NULL, NULL);
+
+  caps = gst_caps_copy (gst_pad_template_get_caps (pad_template));
+
+  if (caps) {
+    if (!osxbuf->is_spdif_capable) {
+      GstCaps *sub_caps, *orig_caps = caps;
+
+      sub_caps = gst_caps_from_string ("audio/x-ac3;audio/x-dts");
+      caps = gst_caps_subtract (orig_caps, sub_caps);
+      gst_caps_unref (sub_caps);
+      gst_caps_unref (orig_caps);
+    }
+    sink->cached_caps = gst_caps_ref (caps);
+    caps_string = gst_caps_to_string (caps);
+    GST_DEBUG_OBJECT (sink, "cached caps: %s", caps_string);
+    g_free (caps_string);
+  }
+
+  return caps;
+}
+
+static gboolean
+gst_osx_audio_sink_acceptcaps (GstPad * pad, GstCaps * caps)
+{
+  GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (gst_pad_get_parent_element (pad));
+  GstOsxRingBuffer *osxbuf;
+  GstCaps *pad_caps;
+  GstStructure *st;
+  gboolean ret = FALSE;
+  GstRingBufferSpec spec = { 0 };
+  gchar *caps_string = NULL;
+
+  osxbuf = GST_OSX_RING_BUFFER (GST_BASE_AUDIO_SINK (sink)->ringbuffer);
+
+  caps_string = gst_caps_to_string (caps);
+  GST_DEBUG_OBJECT (sink, "acceptcaps called with %s", caps_string);
+  g_free (caps_string);
+
+  pad_caps = gst_pad_get_caps_reffed (pad);
+  if (pad_caps) {
+    gboolean cret = gst_caps_can_intersect (pad_caps, caps);
+    gst_caps_unref (pad_caps);
+    if (!cret)
+      goto done;
+  }
+
+  /* If we've not got fixed caps, creating a stream might fail,
+   * so let's just return from here with default acceptcaps
+   * behaviour */
+  if (!gst_caps_is_fixed (caps))
+    goto done;
+
+  /* parse helper expects this set, so avoid nasty warning
+   * will be set properly later on anyway  */
+  spec.latency_time = GST_SECOND;
+  if (!gst_ring_buffer_parse_caps (&spec, caps))
+    goto done;
+
+  /* Make sure input is framed and can be payloaded */
+  switch (spec.type) {
+    case GST_BUFTYPE_AC3:
+    {
+      gboolean framed = FALSE;
+
+      if (!osxbuf->is_spdif_capable)
+        goto done;
+
+      st = gst_caps_get_structure (caps, 0);
+
+      gst_structure_get_boolean (st, "framed", &framed);
+      if (!framed || gst_audio_iec61937_frame_size (&spec) <= 0)
+        goto done;
+      break;
+    }
+    case GST_BUFTYPE_DTS:
+    {
+      gboolean parsed = FALSE;
+
+      if (!osxbuf->is_spdif_capable)
+        goto done;
+
+      st = gst_caps_get_structure (caps, 0);
+
+      gst_structure_get_boolean (st, "parsed", &parsed);
+      if (!parsed || gst_audio_iec61937_frame_size (&spec) <= 0)
+        goto done;
+      break;
+    }
+    default:
+      break;
+  }
+  ret = TRUE;
+
+done:
+  gst_object_unref (sink);
+  return ret;
+}
+
+static GstBuffer *
+gst_osx_audio_sink_sink_payload (GstBaseAudioSink * sink, GstBuffer * buf)
+{
+  GstOsxAudioSink *osxsink;
+
+  osxsink = GST_OSX_AUDIO_SINK (sink);
+
+  if (RINGBUFFER_IS_SPDIF (sink->ringbuffer->spec.type)) {
+    gint framesize = gst_audio_iec61937_frame_size (&sink->ringbuffer->spec);
+    GstBuffer *out;
+
+    if (framesize <= 0)
+      return NULL;
+
+    out = gst_buffer_new_and_alloc (framesize);
+
+    if (!gst_audio_iec61937_payload (GST_BUFFER_DATA (buf),
+            GST_BUFFER_SIZE (buf), GST_BUFFER_DATA (out),
+            GST_BUFFER_SIZE (out), &sink->ringbuffer->spec)) {
+      gst_buffer_unref (out);
+      return NULL;
+    }
+
+    gst_buffer_copy_metadata (out, buf, GST_BUFFER_COPY_ALL);
+
+    /* Fix endianness */
+    swab ((gchar *) GST_BUFFER_DATA (buf),
+        (gchar *) GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
+    return out;
+  } else {
+    return gst_buffer_ref (buf);
+  }
+}
+
 static GstRingBuffer *
 gst_osx_audio_sink_create_ringbuffer (GstBaseAudioSink * sink)
 {
@@ -264,11 +459,13 @@ gst_osx_audio_sink_create_ringbuffer (GstBaseAudioSink * sink)
 
   osxsink = GST_OSX_AUDIO_SINK (sink);
 
-  gst_osx_audio_sink_select_device (osxsink);
+  if (!gst_osx_audio_sink_select_device (osxsink)) {
+    return NULL;
+  }
 
   GST_DEBUG ("Creating ringbuffer");
   ringbuffer = g_object_new (GST_TYPE_OSX_RING_BUFFER, NULL);
-  GST_DEBUG ("osx sink 0x%p element 0x%p  ioproc 0x%p", osxsink,
+  GST_DEBUG ("osx sink %p element %p  ioproc %p", osxsink,
       GST_OSX_AUDIO_ELEMENT_GET_INTERFACE (osxsink),
       (void *) gst_osx_audio_sink_io_proc);
 
@@ -280,10 +477,10 @@ gst_osx_audio_sink_create_ringbuffer (GstBaseAudioSink * sink)
   return GST_RING_BUFFER (ringbuffer);
 }
 
-/* HALOutput AudioUnit will request fairly arbitrarily-sized chunks of data,
- * not of a fixed size. So, we keep track of where in the current ringbuffer
- * segment we are, and only advance the segment once we've read the whole
- * thing */
+/* HALOutput AudioUnit will request fairly arbitrarily-sized chunks
+ * of data, not of a fixed size. So, we keep track of where in
+ * the current ringbuffer segment we are, and only advance the segment
+ * once we've read the whole thing */
 static OSStatus
 gst_osx_audio_sink_io_proc (GstOsxRingBuffer * buf,
     AudioUnitRenderActionFlags * ioActionFlags,
@@ -293,7 +490,8 @@ gst_osx_audio_sink_io_proc (GstOsxRingBuffer * buf,
   guint8 *readptr;
   gint readseg;
   gint len;
-  gint remaining = bufferList->mBuffers[0].mDataByteSize;
+  gint stream_idx = buf->stream_idx;
+  gint remaining = bufferList->mBuffers[stream_idx].mDataByteSize;
   gint offset = 0;
 
   while (remaining) {
@@ -306,7 +504,7 @@ gst_osx_audio_sink_io_proc (GstOsxRingBuffer * buf,
     if (len > remaining)
       len = remaining;
 
-    memcpy ((char *) bufferList->mBuffers[0].mData + offset,
+    memcpy ((char *) bufferList->mBuffers[stream_idx].mData + offset,
         readptr + buf->segoffset, len);
 
     buf->segoffset += len;
@@ -344,34 +542,95 @@ gst_osx_audio_sink_set_volume (GstOsxAudioSink * sink)
       kAudioUnitScope_Global, 0, (float) sink->volume, 0);
 }
 
-static void
+static inline void
+_dump_channel_layout (AudioChannelLayout * channel_layout)
+{
+  UInt32 i;
+
+  GST_DEBUG ("mChannelLayoutTag: 0x%lx",
+      (unsigned long) channel_layout->mChannelLayoutTag);
+  GST_DEBUG ("mChannelBitmap: 0x%lx",
+      (unsigned long) channel_layout->mChannelBitmap);
+  GST_DEBUG ("mNumberChannelDescriptions: %lu",
+      (unsigned long) channel_layout->mNumberChannelDescriptions);
+  for (i = 0; i < channel_layout->mNumberChannelDescriptions; i++) {
+    AudioChannelDescription *channel_desc =
+        &channel_layout->mChannelDescriptions[i];
+    GST_DEBUG ("  mChannelLabel: 0x%lx mChannelFlags: 0x%lx "
+        "mCoordinates[0]: %f mCoordinates[1]: %f "
+        "mCoordinates[2]: %f",
+        (unsigned long) channel_desc->mChannelLabel,
+        (unsigned long) channel_desc->mChannelFlags,
+        channel_desc->mCoordinates[0], channel_desc->mCoordinates[1],
+        channel_desc->mCoordinates[2]);
+  }
+}
+
+static gboolean
 gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink)
 {
-  OSStatus status;
-  UInt32 propertySize;
+  AudioDeviceID *devices = NULL;
+  AudioDeviceID default_device_id = 0;
+  AudioChannelLayout *channel_layout;
+  gint i, ndevices = 0;
+  gboolean res = FALSE;
 
-  if (osxsink->device_id == kAudioDeviceUnknown) {
-    /* If no specific device has been selected by the user, then pick the
-     * default device */
-    GST_DEBUG_OBJECT (osxsink, "Selecting device for OSXAudioSink");
-    propertySize = sizeof (osxsink->device_id);
-    status =
-        AudioHardwareGetProperty (kAudioHardwarePropertyDefaultOutputDevice,
-        &propertySize, &osxsink->device_id);
-
-    if (status) {
-      GST_WARNING_OBJECT (osxsink,
-          "AudioHardwareGetProperty returned %d", (int) status);
-    } else {
-      GST_DEBUG_OBJECT (osxsink, "AudioHardwareGetProperty returned 0");
+  devices = _audio_system_get_devices (&ndevices);
+
+  if (ndevices < 1) {
+    GST_ERROR_OBJECT (osxsink, "no audio output devices found");
+    goto done;
+  }
+
+  GST_DEBUG_OBJECT (osxsink, "found %d audio device(s)", ndevices);
+
+  for (i = 0; i < ndevices; i++) {
+    gchar *device_name;
+
+    if ((device_name = _audio_device_get_name (devices[i]))) {
+      if (!_audio_device_has_output (devices[i])) {
+        GST_DEBUG_OBJECT (osxsink, "Input Device ID: %u Name: %s",
+            (unsigned) devices[i], device_name);
+      } else {
+        GST_DEBUG_OBJECT (osxsink, "Output Device ID: %u Name: %s",
+            (unsigned) devices[i], device_name);
+
+        channel_layout = _audio_device_get_channel_layout (devices[i]);
+        if (channel_layout) {
+          _dump_channel_layout (channel_layout);
+          g_free (channel_layout);
+        }
+      }
+
+      g_free (device_name);
     }
+  }
+
+  /* Find the ID of the default output device */
+  default_device_id = _audio_system_get_default_output ();
 
-    if (osxsink->device_id == kAudioDeviceUnknown) {
-      GST_WARNING_OBJECT (osxsink,
-          "AudioHardwareGetProperty: device_id is kAudioDeviceUnknown");
+  /* Here we decide if selected device is valid or autoselect
+   * the default one when required */
+  if (osxsink->device_id == kAudioDeviceUnknown) {
+    if (default_device_id != kAudioDeviceUnknown) {
+      osxsink->device_id = default_device_id;
+      res = TRUE;
+    }
+  } else {
+    for (i = 0; i < ndevices; i++) {
+      if (osxsink->device_id == devices[i]) {
+        res = TRUE;
+      }
     }
 
-    GST_DEBUG_OBJECT (osxsink, "AudioHardwareGetProperty: device_id is %lu",
-        (long) osxsink->device_id);
+    if (res && !_audio_device_is_alive (osxsink->device_id)) {
+      GST_ERROR_OBJECT (osxsink, "Requested device not usable");
+      res = FALSE;
+    }
   }
+
+done:
+  g_free (devices);
+
+  return res;
 }
index aac9719..cf94e47 100644 (file)
@@ -2,6 +2,7 @@
  * GStreamer
  * Copyright (C) 2005-2006 Zaheer Abbas Merali <zaheerabbas at merali dot org>
  * Copyright (C) 2007 Pioneers of the Inevitable <songbird@songbirdnest.com>
+ * Copyright (C) 2012 Fluendo S.A. <support@fluendo.com>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -72,9 +73,10 @@ struct _GstOsxAudioSink
   AudioDeviceID device_id;
   AudioUnit audiounit;
   double volume;
+  GstCaps *cached_caps;
 };
 
-struct _GstOsxAudioSinkClass 
+struct _GstOsxAudioSinkClass
 {
   GstBaseAudioSinkClass parent_class;
 };
@@ -84,3 +86,4 @@ GType gst_osx_audio_sink_get_type (void);
 G_END_DECLS
 
 #endif /* __GST_OSXAUDIOSINK_H__ */
+
diff --git a/sys/osxaudio/gstosxcoreaudio.h b/sys/osxaudio/gstosxcoreaudio.h
new file mode 100644 (file)
index 0000000..f1d5123
--- /dev/null
@@ -0,0 +1,576 @@
+/*
+ * GStreamer
+ * Copyright (C) 2012 Fluendo S.A. <support@fluendo.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Alternatively, the contents of this file may be used under the
+ * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
+ * which case the following provisions apply instead of the ones
+ * mentioned above:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * The development of this code was made possible due to the involvement of
+ * Pioneers of the Inevitable, the creators of the Songbird Music player
+ *
+ */
+
+#define CORE_AUDIO_FORMAT "FormatID: %" GST_FOURCC_FORMAT " rate: %f flags: 0x%x BytesPerPacket: %u FramesPerPacket: %u BytesPerFrame: %u ChannelsPerFrame: %u BitsPerChannel: %u"
+#define CORE_AUDIO_FORMAT_ARGS(f) GST_FOURCC_ARGS((f).mFormatID),(f).mSampleRate,(unsigned)(f).mFormatFlags,(unsigned)(f).mBytesPerPacket,(unsigned)(f).mFramesPerPacket,(unsigned)(f).mBytesPerFrame,(unsigned)(f).mChannelsPerFrame,(unsigned)(f).mBitsPerChannel
+
+#define CORE_AUDIO_FORMAT_IS_SPDIF(f) ((f).mFormat.mFormatID == 'IAC3' || (f).mFormat.mFormatID == 'iac3' || (f).mFormat.mFormatID == kAudioFormat60958AC3 || (f).mFormat.mFormatID == kAudioFormatAC3)
+
+static inline gboolean
+_audio_system_set_runloop (CFRunLoopRef runLoop)
+{
+  OSStatus status = noErr;
+
+  gboolean res = FALSE;
+
+  AudioObjectPropertyAddress runloopAddress = {
+    kAudioHardwarePropertyRunLoop,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectSetPropertyData (kAudioObjectSystemObject,
+      &runloopAddress, 0, NULL, sizeof (CFRunLoopRef), &runLoop);
+  if (status == noErr) {
+    res = TRUE;
+  } else {
+    GST_ERROR ("failed to set runloop to %p: %" GST_FOURCC_FORMAT,
+        runLoop, GST_FOURCC_ARGS (status));
+  }
+
+  return res;
+}
+
+static inline AudioDeviceID
+_audio_system_get_default_output (void)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = sizeof (AudioDeviceID);
+  AudioDeviceID device_id = kAudioDeviceUnknown;
+
+  AudioObjectPropertyAddress defaultDeviceAddress = {
+    kAudioHardwarePropertyDefaultOutputDevice,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyData (kAudioObjectSystemObject,
+      &defaultDeviceAddress, 0, NULL, &propertySize, &device_id);
+  if (status != noErr) {
+    GST_ERROR ("failed getting default output device: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+  }
+
+  return device_id;
+}
+
+static inline AudioDeviceID *
+_audio_system_get_devices (gint * ndevices)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = 0;
+  AudioDeviceID *devices = NULL;
+
+  AudioObjectPropertyAddress audioDevicesAddress = {
+    kAudioHardwarePropertyDevices,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyDataSize (kAudioObjectSystemObject,
+      &audioDevicesAddress, 0, NULL, &propertySize);
+  if (status != noErr) {
+    GST_WARNING ("failed getting number of devices: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+    return NULL;
+  }
+
+  *ndevices = propertySize / sizeof (AudioDeviceID);
+
+  devices = (AudioDeviceID *) g_malloc (propertySize);
+  if (devices) {
+    status = AudioObjectGetPropertyData (kAudioObjectSystemObject,
+        &audioDevicesAddress, 0, NULL, &propertySize, devices);
+    if (status != noErr) {
+      GST_WARNING ("failed getting the list of devices: %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+      g_free (devices);
+      *ndevices = 0;
+      return NULL;
+    }
+  }
+  return devices;
+}
+
+static inline gboolean
+_audio_device_is_alive (AudioDeviceID device_id)
+{
+  OSStatus status = noErr;
+  int alive = FALSE;
+  UInt32 propertySize = sizeof (alive);
+
+  AudioObjectPropertyAddress audioDeviceAliveAddress = {
+    kAudioDevicePropertyDeviceIsAlive,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyData (device_id,
+      &audioDeviceAliveAddress, 0, NULL, &propertySize, &alive);
+  if (status != noErr) {
+    alive = FALSE;
+  }
+
+  return alive;
+}
+
+static inline guint
+_audio_device_get_latency (AudioDeviceID device_id)
+{
+  OSStatus status = noErr;
+  UInt32 latency = 0;
+  UInt32 propertySize = sizeof (latency);
+
+  AudioObjectPropertyAddress audioDeviceLatencyAddress = {
+    kAudioDevicePropertyLatency,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyData (device_id,
+      &audioDeviceLatencyAddress, 0, NULL, &propertySize, &latency);
+  if (status != noErr) {
+    GST_ERROR ("failed to get latency: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+    latency = -1;
+  }
+
+  return latency;
+}
+
+static inline pid_t
+_audio_device_get_hog (AudioDeviceID device_id)
+{
+  OSStatus status = noErr;
+  pid_t hog_pid;
+  UInt32 propertySize = sizeof (hog_pid);
+
+  AudioObjectPropertyAddress audioDeviceHogModeAddress = {
+    kAudioDevicePropertyHogMode,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyData (device_id,
+      &audioDeviceHogModeAddress, 0, NULL, &propertySize, &hog_pid);
+  if (status != noErr) {
+    GST_ERROR ("failed to get hog: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+    hog_pid = -1;
+  }
+
+  return hog_pid;
+}
+
+static inline gboolean
+_audio_device_set_hog (AudioDeviceID device_id, pid_t hog_pid)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = sizeof (hog_pid);
+  gboolean res = FALSE;
+
+  AudioObjectPropertyAddress audioDeviceHogModeAddress = {
+    kAudioDevicePropertyHogMode,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectSetPropertyData (device_id,
+      &audioDeviceHogModeAddress, 0, NULL, propertySize, &hog_pid);
+
+  if (status == noErr) {
+    res = TRUE;
+  } else {
+    GST_ERROR ("failed to set hog: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+  }
+
+  return res;
+}
+
+static inline gboolean
+_audio_device_set_mixing (AudioDeviceID device_id, gboolean enable_mix)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = 0, can_mix = enable_mix;
+  Boolean writable = FALSE;
+  gboolean res = FALSE;
+
+  AudioObjectPropertyAddress audioDeviceSupportsMixingAddress = {
+    kAudioDevicePropertySupportsMixing,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  if (AudioObjectHasProperty (device_id, &audioDeviceSupportsMixingAddress)) {
+    /* Set mixable to false if we are allowed to */
+    status = AudioObjectIsPropertySettable (device_id,
+        &audioDeviceSupportsMixingAddress, &writable);
+    if (status) {
+      GST_DEBUG ("AudioObjectIsPropertySettable: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
+    }
+    status = AudioObjectGetPropertyDataSize (device_id,
+        &audioDeviceSupportsMixingAddress, 0, NULL, &propertySize);
+    if (status) {
+      GST_DEBUG ("AudioObjectGetPropertyDataSize: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
+    }
+    status = AudioObjectGetPropertyData (device_id,
+        &audioDeviceSupportsMixingAddress, 0, NULL, &propertySize, &can_mix);
+    if (status) {
+      GST_DEBUG ("AudioObjectGetPropertyData: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
+    }
+
+    if (status == noErr && writable) {
+      can_mix = enable_mix;
+      status = AudioObjectSetPropertyData (device_id,
+          &audioDeviceSupportsMixingAddress, 0, NULL, propertySize, &can_mix);
+      res = TRUE;
+    }
+
+    if (status != noErr) {
+      GST_ERROR ("failed to set mixmode: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
+    }
+  } else {
+    GST_DEBUG ("property not found, mixing coudln't be changed");
+  }
+
+  return res;
+}
+
+static inline gchar *
+_audio_device_get_name (AudioDeviceID device_id)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = 0;
+  gchar *device_name = NULL;
+
+  AudioObjectPropertyAddress deviceNameAddress = {
+    kAudioDevicePropertyDeviceName,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  /* Get the length of the device name */
+  status = AudioObjectGetPropertyDataSize (device_id,
+      &deviceNameAddress, 0, NULL, &propertySize);
+  if (status != noErr) {
+    goto beach;
+  }
+
+  /* Get the name of the device */
+  device_name = (gchar *) g_malloc (propertySize);
+  status = AudioObjectGetPropertyData (device_id,
+      &deviceNameAddress, 0, NULL, &propertySize, device_name);
+  if (status != noErr) {
+    g_free (device_name);
+    device_name = NULL;
+  }
+
+beach:
+  return device_name;
+}
+
+static inline gboolean
+_audio_device_has_output (AudioDeviceID device_id)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize;
+
+  AudioObjectPropertyAddress streamsAddress = {
+    kAudioDevicePropertyStreams,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyDataSize (device_id,
+      &streamsAddress, 0, NULL, &propertySize);
+  if (status != noErr) {
+    return FALSE;
+  }
+  if (propertySize == 0) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static inline AudioChannelLayout *
+_audio_device_get_channel_layout (AudioDeviceID device_id)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = 0;
+  AudioChannelLayout *channel_layout = NULL;
+
+  AudioObjectPropertyAddress channelLayoutAddress = {
+    kAudioDevicePropertyPreferredChannelLayout,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  /* Get the length of the default channel layout structure */
+  status = AudioObjectGetPropertyDataSize (device_id,
+      &channelLayoutAddress, 0, NULL, &propertySize);
+  if (status != noErr) {
+    goto beach;
+  }
+
+  /* Get the default channel layout of the device */
+  channel_layout = (AudioChannelLayout *) g_malloc (propertySize);
+  status = AudioObjectGetPropertyData (device_id,
+      &channelLayoutAddress, 0, NULL, &propertySize, channel_layout);
+  if (status != noErr) {
+    g_free (channel_layout);
+    channel_layout = NULL;
+  }
+
+beach:
+  return channel_layout;
+}
+
+static inline AudioStreamID *
+_audio_device_get_streams (AudioDeviceID device_id, gint * nstreams)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = 0;
+  AudioStreamID *streams = NULL;
+
+  AudioObjectPropertyAddress streamsAddress = {
+    kAudioDevicePropertyStreams,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyDataSize (device_id,
+      &streamsAddress, 0, NULL, &propertySize);
+  if (status != noErr) {
+    GST_WARNING ("failed getting number of streams: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+    return NULL;
+  }
+
+  *nstreams = propertySize / sizeof (AudioStreamID);
+  streams = (AudioStreamID *) g_malloc (propertySize);
+
+  if (streams) {
+    status = AudioObjectGetPropertyData (device_id,
+        &streamsAddress, 0, NULL, &propertySize, streams);
+    if (status != noErr) {
+      GST_WARNING ("failed getting the list of streams: %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+      g_free (streams);
+      *nstreams = 0;
+      return NULL;
+    }
+  }
+
+  return streams;
+}
+
+static inline guint
+_audio_stream_get_latency (AudioStreamID stream_id)
+{
+  OSStatus status = noErr;
+  UInt32 latency;
+  UInt32 propertySize = sizeof (latency);
+
+  AudioObjectPropertyAddress latencyAddress = {
+    kAudioStreamPropertyLatency,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyData (stream_id,
+      &latencyAddress, 0, NULL, &propertySize, &latency);
+  if (status != noErr) {
+    GST_ERROR ("failed to get latency: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+    latency = -1;
+  }
+
+  return latency;
+}
+
+static inline gboolean
+_audio_stream_get_current_format (AudioStreamID stream_id,
+    AudioStreamBasicDescription * format)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = sizeof (AudioStreamBasicDescription);
+
+  AudioObjectPropertyAddress formatAddress = {
+    kAudioStreamPropertyPhysicalFormat,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyData (stream_id,
+      &formatAddress, 0, NULL, &propertySize, format);
+  if (status != noErr) {
+    GST_ERROR ("failed to get current format: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static inline gboolean
+_audio_stream_set_current_format (AudioStreamID stream_id,
+    AudioStreamBasicDescription format)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = sizeof (AudioStreamBasicDescription);
+
+  AudioObjectPropertyAddress formatAddress = {
+    kAudioStreamPropertyPhysicalFormat,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectSetPropertyData (stream_id,
+      &formatAddress, 0, NULL, propertySize, &format);
+  if (status != noErr) {
+    GST_ERROR ("failed to set current format: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static inline AudioStreamRangedDescription *
+_audio_stream_get_formats (AudioStreamID stream_id, gint * nformats)
+{
+  OSStatus status = noErr;
+  UInt32 propertySize = 0;
+  AudioStreamRangedDescription *formats = NULL;
+
+  AudioObjectPropertyAddress formatsAddress = {
+    kAudioStreamPropertyAvailablePhysicalFormats,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  status = AudioObjectGetPropertyDataSize (stream_id,
+      &formatsAddress, 0, NULL, &propertySize);
+  if (status != noErr) {
+    GST_WARNING ("failed getting number of stream formats: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+    return NULL;
+  }
+
+  *nformats = propertySize / sizeof (AudioStreamRangedDescription);
+
+  formats = (AudioStreamRangedDescription *) g_malloc (propertySize);
+  if (formats) {
+    status = AudioObjectGetPropertyData (stream_id,
+        &formatsAddress, 0, NULL, &propertySize, formats);
+    if (status != noErr) {
+      GST_WARNING ("failed getting the list of stream formats: %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+      g_free (formats);
+      *nformats = 0;
+      return NULL;
+    }
+  }
+  return formats;
+}
+
+static inline gboolean
+_audio_stream_is_spdif_avail (AudioStreamID stream_id)
+{
+  AudioStreamRangedDescription *formats;
+  gint i, nformats = 0;
+  gboolean res = FALSE;
+
+  formats = _audio_stream_get_formats (stream_id, &nformats);
+  GST_DEBUG ("found %d stream formats", nformats);
+
+  if (formats) {
+    GST_DEBUG ("formats supported on stream ID: %u",
+        (unsigned) stream_id);
+
+    for (i = 0; i < nformats; i++) {
+      GST_DEBUG ("  " CORE_AUDIO_FORMAT,
+          CORE_AUDIO_FORMAT_ARGS (formats[i].mFormat));
+
+      if (CORE_AUDIO_FORMAT_IS_SPDIF (formats[i])) {
+        res = TRUE;
+      }
+    }
+    g_free (formats);
+  }
+
+  return res;
+}
+
+static inline gboolean
+_audio_device_is_spdif_avail (AudioDeviceID device_id)
+{
+  AudioStreamID *streams = NULL;
+  gint i, nstreams = 0;
+  gboolean res = FALSE;
+
+  streams = _audio_device_get_streams (device_id, &nstreams);
+  GST_DEBUG ("found %d streams", nstreams);
+  if (streams) {
+    for (i = 0; i < nstreams; i++) {
+      if (_audio_stream_is_spdif_avail (streams[i])) {
+        res = TRUE;
+      }
+    }
+
+    g_free (streams);
+  }
+
+  return res;
+}
+
index cb77162..afeb644 100644 (file)
@@ -2,6 +2,7 @@
  * GStreamer
  * Copyright (C) 2006 Zaheer Abbas Merali <zaheerabbas at merali dot org>
  * Copyright (C) 2008 Pioneers of the Inevitable <songbird@songbirdnest.com>
+ * Copyright (C) 2012 Fluendo S.A. <support@fluendo.com>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
 #include "gstosxaudiosink.h"
 #include "gstosxaudiosrc.h"
 
+#include <unistd.h>             /* for getpid() */
+
 GST_DEBUG_CATEGORY_STATIC (osx_audio_debug);
 #define GST_CAT_DEFAULT osx_audio_debug
 
+#include "gstosxcoreaudio.h"
+
 static void gst_osx_ring_buffer_dispose (GObject * object);
 static void gst_osx_ring_buffer_finalize (GObject * object);
 static gboolean gst_osx_ring_buffer_open_device (GstRingBuffer * buf);
@@ -68,13 +73,8 @@ static gboolean gst_osx_ring_buffer_stop (GstRingBuffer * buf);
 static guint gst_osx_ring_buffer_delay (GstRingBuffer * buf);
 static GstRingBufferClass *ring_parent_class = NULL;
 
-static OSStatus gst_osx_ring_buffer_render_notify (GstOsxRingBuffer * osxbuf,
-    AudioUnitRenderActionFlags * ioActionFlags,
-    const AudioTimeStamp * inTimeStamp, unsigned int inBusNumber,
-    unsigned int inNumberFrames, AudioBufferList * ioData);
-
-static AudioBufferList *buffer_list_alloc (int channels, int size);
-static void buffer_list_free (AudioBufferList * list);
+static void gst_osx_ring_buffer_remove_render_callback (GstOsxRingBuffer *
+    osxbuf);
 
 static void
 gst_osx_ring_buffer_do_init (GType type)
@@ -83,8 +83,8 @@ gst_osx_ring_buffer_do_init (GType type)
       "OSX Audio Elements");
 }
 
-GST_BOILERPLATE_FULL (GstOsxRingBuffer, gst_osx_ring_buffer, GstRingBuffer,
-    GST_TYPE_RING_BUFFER, gst_osx_ring_buffer_do_init);
+GST_BOILERPLATE_FULL (GstOsxRingBuffer, gst_osx_ring_buffer,
+    GstRingBuffer, GST_TYPE_RING_BUFFER, gst_osx_ring_buffer_do_init);
 
 static void
 gst_osx_ring_buffer_base_init (gpointer g_class)
@@ -131,6 +131,10 @@ gst_osx_ring_buffer_init (GstOsxRingBuffer * ringbuffer,
     GstOsxRingBufferClass * g_class)
 {
   /* Nothing to do right now */
+  ringbuffer->is_spdif_capable = FALSE;
+  ringbuffer->is_passthrough = FALSE;
+  ringbuffer->hog_pid = -1;
+  ringbuffer->disabled_mixing = FALSE;
 }
 
 static void
@@ -154,15 +158,18 @@ gst_osx_ring_buffer_create_audio_unit (GstOsxRingBuffer * osxbuf,
   OSStatus status;
   AudioUnit unit;
   UInt32 enableIO;
+  AudioStreamBasicDescription asbd_in;
+  UInt32 propertySize;
 
   /* Create a HALOutput AudioUnit.
-   * This is the lowest-level output API that is actually sensibly usable
-   * (the lower level ones require that you do channel-remapping yourself,
-   * and the CoreAudio channel mapping is sufficiently complex that doing
-   * so would be very difficult)
+   * This is the lowest-level output API that is actually sensibly
+   * usable (the lower level ones require that you do
+   * channel-remapping yourself, and the CoreAudio channel mapping
+   * is sufficiently complex that doing so would be very difficult)
    *
-   * Note that for input we request an output unit even though we will do
-   * input with it: http://developer.apple.com/technotes/tn2002/tn2091.html
+   * Note that for input we request an output unit even though
+   * we will do input with it.
+   * http://developer.apple.com/technotes/tn2002/tn2091.html
    */
   desc.componentType = kAudioUnitType_Output;
   desc.componentSubType = kAudioUnitSubType_HALOutput;
@@ -179,7 +186,8 @@ gst_osx_ring_buffer_create_audio_unit (GstOsxRingBuffer * osxbuf,
   status = OpenAComponent (comp, &unit);
 
   if (status) {
-    GST_WARNING_OBJECT (osxbuf, "Couldn't open HALOutput component");
+    GST_ERROR_OBJECT (osxbuf, "Couldn't open HALOutput component %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
     return NULL;
   }
 
@@ -191,8 +199,8 @@ gst_osx_ring_buffer_create_audio_unit (GstOsxRingBuffer * osxbuf,
 
     if (status) {
       CloseComponent (unit);
-      GST_WARNING_OBJECT (osxbuf, "Failed to enable input: %lx",
-          (gulong) status);
+      GST_WARNING_OBJECT (osxbuf, "Failed to enable input: %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
       return NULL;
     }
 
@@ -203,24 +211,37 @@ gst_osx_ring_buffer_create_audio_unit (GstOsxRingBuffer * osxbuf,
 
     if (status) {
       CloseComponent (unit);
-      GST_WARNING_OBJECT (osxbuf, "Failed to disable output: %lx",
-          (gulong) status);
+      GST_WARNING_OBJECT (osxbuf, "Failed to disable output: %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
       return NULL;
     }
   }
 
-  /* Specify which device we're using. */
-  GST_DEBUG_OBJECT (osxbuf, "Setting device to %d", (int) device_id);
-  status = AudioUnitSetProperty (unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0,       /* N/A for global */
-      &device_id, sizeof (AudioDeviceID));
+  GST_DEBUG_OBJECT (osxbuf, "Created HALOutput AudioUnit: %p", unit);
 
-  if (status) {
-    CloseComponent (unit);
-    GST_WARNING_OBJECT (osxbuf, "Failed to set device: %lx", (gulong) status);
-    return NULL;
-  }
+  if (input) {
+    GstOsxAudioSrc *src = GST_OSX_AUDIO_SRC (GST_OBJECT_PARENT (osxbuf));
 
-  GST_DEBUG_OBJECT (osxbuf, "Create HALOutput AudioUnit: %p", unit);
+    propertySize = sizeof (asbd_in);
+    status = AudioUnitGetProperty (unit,
+        kAudioUnitProperty_StreamFormat,
+        kAudioUnitScope_Input, 1, &asbd_in, &propertySize);
+
+    if (status) {
+      CloseComponent (unit);
+      GST_WARNING_OBJECT (osxbuf,
+          "Unable to obtain device properties: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
+      return NULL;
+    }
+
+    src->deviceChannels = asbd_in.mChannelsPerFrame;
+  } else {
+    GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (GST_OBJECT_PARENT (osxbuf));
+
+    /* needed for the sink's volume control */
+    sink->audiounit = unit;
+  }
 
   return unit;
 }
@@ -229,41 +250,25 @@ static gboolean
 gst_osx_ring_buffer_open_device (GstRingBuffer * buf)
 {
   GstOsxRingBuffer *osxbuf;
-  GstOsxAudioSink *sink;
-  GstOsxAudioSrc *src;
-  AudioStreamBasicDescription asbd_in;
-  OSStatus status;
-  UInt32 propertySize;
 
   osxbuf = GST_OSX_RING_BUFFER (buf);
-  sink = NULL;
-  src = NULL;
 
-  osxbuf->audiounit = gst_osx_ring_buffer_create_audio_unit (osxbuf,
-      osxbuf->is_src, osxbuf->device_id);
+  /* The following is needed to instruct HAL to create their own
+   * thread to handle the notifications. */
+  _audio_system_set_runloop (NULL);
 
-  if (osxbuf->is_src) {
-    src = GST_OSX_AUDIO_SRC (GST_OBJECT_PARENT (buf));
+  osxbuf->is_spdif_capable = _audio_device_is_spdif_avail (osxbuf->device_id);
 
-    propertySize = sizeof (asbd_in);
-    status = AudioUnitGetProperty (osxbuf->audiounit,
-        kAudioUnitProperty_StreamFormat,
-        kAudioUnitScope_Input, 1, &asbd_in, &propertySize);
-
-    if (status) {
-      CloseComponent (osxbuf->audiounit);
-      osxbuf->audiounit = NULL;
-      GST_WARNING_OBJECT (osxbuf, "Unable to obtain device properties: %lx",
-          (gulong) status);
-      return FALSE;
-    }
+  if (osxbuf->is_spdif_capable) {
+    GST_DEBUG_OBJECT (osxbuf, "device %u is SPDIF capable",
+        (unsigned) osxbuf->device_id);
+  }
 
-    src->deviceChannels = asbd_in.mChannelsPerFrame;
-  } else {
-    sink = GST_OSX_AUDIO_SINK (GST_OBJECT_PARENT (buf));
+  osxbuf->audiounit = gst_osx_ring_buffer_create_audio_unit (osxbuf,
+      osxbuf->is_src, osxbuf->device_id);
 
-    /* needed for the sink's volume control */
-    sink->audiounit = osxbuf->audiounit;
+  if (!osxbuf->audiounit) {
+    return FALSE;
   }
 
   return TRUE;
@@ -317,65 +322,434 @@ gst_audio_channel_position_to_coreaudio_channel_label (GstAudioChannelPosition
   }
 }
 
+static AudioBufferList *
+buffer_list_alloc (int channels, int size)
+{
+  AudioBufferList *list;
+  int total_size;
+  int n;
+
+  total_size = sizeof (AudioBufferList) + 1 * sizeof (AudioBuffer);
+  list = (AudioBufferList *) g_malloc (total_size);
+
+  list->mNumberBuffers = 1;
+  for (n = 0; n < (int) list->mNumberBuffers; ++n) {
+    list->mBuffers[n].mNumberChannels = channels;
+    list->mBuffers[n].mDataByteSize = size;
+    list->mBuffers[n].mData = g_malloc (size);
+  }
+
+  return list;
+}
+
+static void
+buffer_list_free (AudioBufferList * list)
+{
+  int n;
+
+  for (n = 0; n < (int) list->mNumberBuffers; ++n) {
+    if (list->mBuffers[n].mData)
+      g_free (list->mBuffers[n].mData);
+  }
+
+  g_free (list);
+}
+
+typedef struct
+{
+  GMutex *lock;
+  GCond *cond;
+} PropertyMutex;
+
+static OSStatus
+_audio_stream_format_listener (AudioObjectID inObjectID,
+    UInt32 inNumberAddresses,
+    const AudioObjectPropertyAddress inAddresses[], void *inClientData)
+{
+  OSStatus status = noErr;
+  guint i;
+  PropertyMutex *prop_mutex = inClientData;
+
+  for (i = 0; i < inNumberAddresses; i++) {
+    if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat) {
+      g_mutex_lock (prop_mutex->lock);
+      g_cond_signal (prop_mutex->cond);
+      g_mutex_unlock (prop_mutex->lock);
+      break;
+    }
+  }
+  return (status);
+}
+
 static gboolean
-gst_osx_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec)
+_audio_stream_change_format (AudioStreamID stream_id,
+    AudioStreamBasicDescription format)
+{
+  OSStatus status = noErr;
+  gint i;
+  gboolean ret = FALSE;
+  AudioStreamBasicDescription cformat;
+  PropertyMutex prop_mutex;
+
+  AudioObjectPropertyAddress formatAddress = {
+    kAudioStreamPropertyPhysicalFormat,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  GST_DEBUG ("setting stream format: " CORE_AUDIO_FORMAT,
+      CORE_AUDIO_FORMAT_ARGS (format));
+
+  /* Condition because SetProperty is asynchronous */
+  prop_mutex.lock = g_mutex_new ();
+  prop_mutex.cond = g_cond_new ();
+
+  g_mutex_lock (prop_mutex.lock);
+
+  /* Install the property listener to serialize the operations */
+  status = AudioObjectAddPropertyListener (stream_id, &formatAddress,
+      _audio_stream_format_listener, (void *) &prop_mutex);
+  if (status != noErr) {
+    GST_ERROR ("AudioObjectAddPropertyListener failed: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+    goto done;
+  }
+
+  /* Change the format */
+  if (!_audio_stream_set_current_format (stream_id, format)) {
+    goto done;
+  }
+
+  /* The AudioObjectSetProperty is not only asynchronous
+   * it is also not atomic in its behaviour.
+   * Therefore we check 4 times before we really give up. */
+  for (i = 0; i < 4; i++) {
+    GTimeVal timeout;
+
+    g_get_current_time (&timeout);
+    g_time_val_add (&timeout, 250000);
+
+    if (!g_cond_timed_wait (prop_mutex.cond, prop_mutex.lock, &timeout)) {
+      GST_LOG ("timeout...");
+    }
+
+    if (_audio_stream_get_current_format (stream_id, &cformat)) {
+      GST_DEBUG ("current stream format: " CORE_AUDIO_FORMAT,
+          CORE_AUDIO_FORMAT_ARGS (cformat));
+
+      if (cformat.mSampleRate == format.mSampleRate &&
+          cformat.mFormatID == format.mFormatID &&
+          cformat.mFramesPerPacket == format.mFramesPerPacket) {
+        /* The right format is now active */
+        break;
+      }
+    }
+  }
+
+  if (cformat.mSampleRate != format.mSampleRate ||
+      cformat.mFormatID != format.mFormatID ||
+      cformat.mFramesPerPacket != format.mFramesPerPacket) {
+    goto done;
+  }
+
+  ret = TRUE;
+
+done:
+  /* Removing the property listener */
+  status = AudioObjectRemovePropertyListener (stream_id,
+      &formatAddress, _audio_stream_format_listener, (void *) &prop_mutex);
+  if (status != noErr) {
+    GST_ERROR ("AudioObjectRemovePropertyListener failed: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+  }
+  /* Destroy the lock and condition */
+  g_mutex_unlock (prop_mutex.lock);
+  g_mutex_free (prop_mutex.lock);
+  g_cond_free (prop_mutex.cond);
+
+  return ret;
+}
+
+static OSStatus
+_audio_stream_hardware_changed_listener (AudioObjectID inObjectID,
+    UInt32 inNumberAddresses,
+    const AudioObjectPropertyAddress inAddresses[], void *inClientData)
+{
+  OSStatus status = noErr;
+  guint i;
+  GstOsxRingBuffer *osxbuf = inClientData;
+
+  for (i = 0; i < inNumberAddresses; i++) {
+    if (inAddresses[i].mSelector == kAudioDevicePropertyDeviceHasChanged) {
+      if (!_audio_device_is_spdif_avail (osxbuf->device_id)) {
+        GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (GST_OBJECT_PARENT (osxbuf));
+        GST_ELEMENT_ERROR (sink, RESOURCE, FAILED,
+            ("SPDIF output no longer available"),
+            ("Audio device is reporting that SPDIF output isn't available"));
+      }
+      break;
+    }
+  }
+  return (status);
+}
+
+static gboolean
+gst_osx_ring_buffer_monitorize_spdif (GstOsxRingBuffer * osxbuf)
+{
+  OSStatus status = noErr;
+  gboolean ret = TRUE;
+
+  AudioObjectPropertyAddress propAddress = {
+    kAudioDevicePropertyDeviceHasChanged,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  /* Install the property listener */
+  status = AudioObjectAddPropertyListener (osxbuf->device_id,
+      &propAddress, _audio_stream_hardware_changed_listener, (void *) osxbuf);
+  if (status != noErr) {
+    GST_ERROR ("AudioObjectAddPropertyListener failed: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+    ret = FALSE;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_osx_ring_buffer_unmonitorize_spdif (GstOsxRingBuffer * osxbuf)
+{
+  OSStatus status = noErr;
+  gboolean ret = TRUE;
+
+  AudioObjectPropertyAddress propAddress = {
+    kAudioDevicePropertyDeviceHasChanged,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  /* Remove the property listener */
+  status = AudioObjectRemovePropertyListener (osxbuf->device_id,
+      &propAddress, _audio_stream_hardware_changed_listener, (void *) osxbuf);
+  if (status != noErr) {
+    GST_ERROR ("AudioObjectRemovePropertyListener failed: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+    ret = FALSE;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_osx_ring_buffer_open_spdif (GstOsxRingBuffer * osxbuf)
+{
+  gboolean res = FALSE;
+  pid_t hog_pid, own_pid = getpid ();
+
+  /* We need the device in exclusive and disable the mixing */
+  hog_pid = _audio_device_get_hog (osxbuf->device_id);
+
+  if (hog_pid != -1 && hog_pid != own_pid) {
+    GST_DEBUG_OBJECT (osxbuf,
+        "device is currently in use by another application");
+    goto done;
+  }
+
+  if (_audio_device_set_hog (osxbuf->device_id, own_pid)) {
+    osxbuf->hog_pid = own_pid;
+  }
+
+  if (_audio_device_set_mixing (osxbuf->device_id, FALSE)) {
+    GST_DEBUG_OBJECT (osxbuf, "disabled mixing on the device");
+    osxbuf->disabled_mixing = TRUE;
+  }
+
+  res = TRUE;
+done:
+  return res;
+}
+
+static gboolean
+gst_osx_ring_buffer_close_spdif (GstOsxRingBuffer * osxbuf)
+{
+  pid_t hog_pid;
+
+  gst_osx_ring_buffer_unmonitorize_spdif (osxbuf);
+
+  if (osxbuf->revert_format) {
+    if (!_audio_stream_change_format (osxbuf->stream_id,
+            osxbuf->original_format)) {
+      GST_WARNING ("Format revert failed");
+    }
+    osxbuf->revert_format = FALSE;
+  }
+
+  if (osxbuf->disabled_mixing) {
+    _audio_device_set_mixing (osxbuf->device_id, TRUE);
+    osxbuf->disabled_mixing = FALSE;
+  }
+
+  if (osxbuf->hog_pid != -1) {
+    hog_pid = _audio_device_get_hog (osxbuf->device_id);
+    if (hog_pid == getpid ()) {
+      if (_audio_device_set_hog (osxbuf->device_id, -1)) {
+        osxbuf->hog_pid = -1;
+      }
+    }
+  }
+
+  return TRUE;
+}
+
+static OSStatus
+gst_osx_ring_buffer_io_proc_spdif (AudioDeviceID inDevice,
+    const AudioTimeStamp * inNow,
+    const void *inInputData,
+    const AudioTimeStamp * inTimestamp,
+    AudioBufferList * bufferList,
+    const AudioTimeStamp * inOutputTime, GstOsxRingBuffer * osxbuf)
+{
+  OSStatus status;
+
+  status = osxbuf->element->io_proc (osxbuf, NULL, inTimestamp, 0, 0,
+      bufferList);
+
+  return status;
+}
+
+static gboolean
+gst_osx_ring_buffer_acquire_spdif (GstOsxRingBuffer * osxbuf,
+    AudioStreamBasicDescription format)
+{
+  AudioStreamID *streams = NULL;
+  gint i, j, nstreams = 0;
+  gboolean ret = FALSE;
+
+  if (!gst_osx_ring_buffer_open_spdif (osxbuf))
+    goto done;
+
+  streams = _audio_device_get_streams (osxbuf->device_id, &nstreams);
+
+  for (i = 0; i < nstreams; i++) {
+    AudioStreamRangedDescription *formats = NULL;
+    gint nformats = 0;
+
+    formats = _audio_stream_get_formats (streams[i], &nformats);
+
+    if (formats) {
+      gboolean is_spdif = FALSE;
+
+      /* Check if one of the supported formats is a digital format */
+      for (j = 0; j < nformats; j++) {
+        if (CORE_AUDIO_FORMAT_IS_SPDIF (formats[j])) {
+          is_spdif = TRUE;
+          break;
+        }
+      }
+
+      if (is_spdif) {
+        /* if this stream supports a digital (cac3) format,
+         * then go set it. */
+        gint requested_rate_format = -1;
+        gint current_rate_format = -1;
+        gint backup_rate_format = -1;
+
+        osxbuf->stream_id = streams[i];
+        osxbuf->stream_idx = i;
+
+        if (!osxbuf->revert_format) {
+          if (!_audio_stream_get_current_format (osxbuf->stream_id,
+                  &osxbuf->original_format)) {
+            GST_WARNING ("format could not be saved");
+            g_free (formats);
+            continue;
+          }
+          osxbuf->revert_format = TRUE;
+        }
+
+        for (j = 0; j < nformats; j++) {
+          if (CORE_AUDIO_FORMAT_IS_SPDIF (formats[j])) {
+            GST_LOG ("found stream format: " CORE_AUDIO_FORMAT,
+                CORE_AUDIO_FORMAT_ARGS (formats[j].mFormat));
+
+            if (formats[j].mFormat.mSampleRate == format.mSampleRate) {
+              requested_rate_format = j;
+              break;
+            } else if (formats[j].mFormat.mSampleRate ==
+                osxbuf->original_format.mSampleRate) {
+              current_rate_format = j;
+            } else {
+              if (backup_rate_format < 0 ||
+                  formats[j].mFormat.mSampleRate >
+                  formats[backup_rate_format].mFormat.mSampleRate) {
+                backup_rate_format = j;
+              }
+            }
+          }
+        }
+
+        if (requested_rate_format >= 0) {
+          /* We prefer to output at the rate of the original audio */
+          osxbuf->stream_format = formats[requested_rate_format].mFormat;
+        } else if (current_rate_format >= 0) {
+          /* If not possible, we will try to use the current rate */
+          osxbuf->stream_format = formats[current_rate_format].mFormat;
+        } else {
+          /* And if we have to, any digital format will be just
+           * fine (highest rate possible) */
+          osxbuf->stream_format = formats[backup_rate_format].mFormat;
+        }
+      }
+      g_free (formats);
+    }
+  }
+  g_free (streams);
+
+  GST_DEBUG ("original stream format: " CORE_AUDIO_FORMAT,
+      CORE_AUDIO_FORMAT_ARGS (osxbuf->original_format));
+
+  if (!_audio_stream_change_format (osxbuf->stream_id, osxbuf->stream_format))
+    goto done;
+
+  GST_DEBUG_OBJECT (osxbuf, "osx ring buffer acquired");
+
+  ret = TRUE;
+
+done:
+  return ret;
+}
+
+static gboolean
+gst_osx_ring_buffer_acquire_analog (GstOsxRingBuffer * osxbuf,
+    AudioStreamBasicDescription format, GstCaps * caps)
 {
   /* Configure the output stream and allocate ringbuffer memory */
-  GstOsxRingBuffer *osxbuf;
-  AudioStreamBasicDescription format;
   AudioChannelLayout *layout = NULL;
   OSStatus status;
   UInt32 propertySize;
+  int channels = format.mChannelsPerFrame;
   int layoutSize;
   int element;
   int i;
-  int width, depth;
   AudioUnitScope scope;
   gboolean ret = FALSE;
   GstStructure *structure;
   GstAudioChannelPosition *positions;
   UInt32 frameSize;
 
-  osxbuf = GST_OSX_RING_BUFFER (buf);
-
-  /* Fill out the audio description we're going to be using */
-  format.mFormatID = kAudioFormatLinearPCM;
-  format.mSampleRate = (double) spec->rate;
-  format.mChannelsPerFrame = spec->channels;
-  if (spec->type == GST_BUFTYPE_FLOAT) {
-    format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
-    width = depth = spec->width;
-  } else {
-    format.mFormatFlags = kAudioFormatFlagIsSignedInteger;
-    width = spec->width;
-    depth = spec->depth;
-    if (width == depth) {
-      format.mFormatFlags |= kAudioFormatFlagIsPacked;
-    } else {
-      format.mFormatFlags |= kAudioFormatFlagIsAlignedHigh;
-    }
-    if (spec->bigend) {
-      format.mFormatFlags |= kAudioFormatFlagIsBigEndian;
-    }
-  }
-  format.mBytesPerFrame = spec->channels * (width >> 3);
-  format.mBitsPerChannel = depth;
-  format.mBytesPerPacket = spec->channels * (width >> 3);
-  format.mFramesPerPacket = 1;
-  format.mReserved = 0;
-
   /* Describe channels */
   layoutSize = sizeof (AudioChannelLayout) +
-      spec->channels * sizeof (AudioChannelDescription);
+      channels * sizeof (AudioChannelDescription);
   layout = g_malloc (layoutSize);
 
-  structure = gst_caps_get_structure (spec->caps, 0);
+  structure = gst_caps_get_structure (caps, 0);
   positions = gst_audio_get_channel_positions (structure);
 
   layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
   layout->mChannelBitmap = 0;   /* Not used */
-  layout->mNumberChannelDescriptions = spec->channels;
-  for (i = 0; i < spec->channels; i++) {
+  layout->mNumberChannelDescriptions = channels;
+  for (i = 0; i < channels; i++) {
     if (positions) {
       layout->mChannelDescriptions[i].mChannelLabel =
           gst_audio_channel_position_to_coreaudio_channel_label (positions[i],
@@ -398,45 +772,34 @@ gst_osx_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec)
     positions = NULL;
   }
 
-  GST_LOG_OBJECT (osxbuf, "Format: %x, %f, %u, %x, %d, %d, %d, %d, %d",
-      (unsigned int) format.mFormatID,
-      format.mSampleRate,
-      (unsigned int) format.mChannelsPerFrame,
-      (unsigned int) format.mFormatFlags,
-      (unsigned int) format.mBytesPerFrame,
-      (unsigned int) format.mBitsPerChannel,
-      (unsigned int) format.mBytesPerPacket,
-      (unsigned int) format.mFramesPerPacket, (unsigned int) format.mReserved);
-
   GST_DEBUG_OBJECT (osxbuf, "Setting format for AudioUnit");
 
   scope = osxbuf->is_src ? kAudioUnitScope_Output : kAudioUnitScope_Input;
   element = osxbuf->is_src ? 1 : 0;
 
-  propertySize = sizeof (format);
+  propertySize = sizeof (AudioStreamBasicDescription);
   status = AudioUnitSetProperty (osxbuf->audiounit,
       kAudioUnitProperty_StreamFormat, scope, element, &format, propertySize);
 
   if (status) {
-    GST_WARNING_OBJECT (osxbuf, "Failed to set audio description: %lx",
-        (gulong) status);
+    GST_WARNING_OBJECT (osxbuf,
+        "Failed to set audio description: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
     goto done;
   }
 
-  status = AudioUnitSetProperty (osxbuf->audiounit,
-      kAudioUnitProperty_AudioChannelLayout,
-      scope, element, layout, layoutSize);
-  if (status) {
-    GST_WARNING_OBJECT (osxbuf, "Failed to set output channel layout: %lx",
-        (gulong) status);
-    goto done;
+  if (layoutSize) {
+    status = AudioUnitSetProperty (osxbuf->audiounit,
+        kAudioUnitProperty_AudioChannelLayout,
+        scope, element, layout, layoutSize);
+    if (status) {
+      GST_WARNING_OBJECT (osxbuf,
+          "Failed to set output channel layout: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
+      goto done;
+    }
   }
 
-  spec->segsize =
-      (spec->latency_time * spec->rate / G_USEC_PER_SEC) *
-      spec->bytes_per_sample;
-  spec->segtotal = spec->buffer_time / spec->latency_time;
-
   /* create AudioBufferList needed for recording */
   if (osxbuf->is_src) {
     propertySize = sizeof (frameSize);
@@ -444,33 +807,32 @@ gst_osx_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec)
         &frameSize, &propertySize);
 
     if (status) {
-      GST_WARNING_OBJECT (osxbuf, "Failed to get frame size: %lx",
-          (gulong) status);
+      GST_WARNING_OBJECT (osxbuf, "Failed to get frame size: %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
       goto done;
     }
 
-    osxbuf->recBufferList = buffer_list_alloc (format.mChannelsPerFrame,
+    osxbuf->recBufferList = buffer_list_alloc (channels,
         frameSize * format.mBytesPerFrame);
   }
 
-  buf->data = gst_buffer_new_and_alloc (spec->segtotal * spec->segsize);
-  memset (GST_BUFFER_DATA (buf->data), 0, GST_BUFFER_SIZE (buf->data));
-
-  osxbuf->segoffset = 0;
+  /* Specify which device we're using. */
+  GST_DEBUG_OBJECT (osxbuf, "Bind AudioUnit to device %d",
+      (int) osxbuf->device_id);
+  status = AudioUnitSetProperty (osxbuf->audiounit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0,  /* N/A for global */
+      &osxbuf->device_id, sizeof (AudioDeviceID));
+  if (status) {
+    GST_ERROR_OBJECT (osxbuf, "Failed binding to device: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+    goto audiounit_error;
+  }
 
+  /* Initialize the AudioUnit */
   status = AudioUnitInitialize (osxbuf->audiounit);
   if (status) {
-    gst_buffer_unref (buf->data);
-    buf->data = NULL;
-
-    if (osxbuf->recBufferList) {
-      buffer_list_free (osxbuf->recBufferList);
-      osxbuf->recBufferList = NULL;
-    }
-
-    GST_WARNING_OBJECT (osxbuf,
-        "Failed to initialise AudioUnit: %d", (int) status);
-    goto done;
+    GST_ERROR_OBJECT (osxbuf, "Failed to initialise AudioUnit: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+    goto audiounit_error;
   }
 
   GST_DEBUG_OBJECT (osxbuf, "osx ring buffer acquired");
@@ -480,6 +842,96 @@ gst_osx_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec)
 done:
   g_free (layout);
   return ret;
+
+audiounit_error:
+  if (osxbuf->recBufferList) {
+    buffer_list_free (osxbuf->recBufferList);
+    osxbuf->recBufferList = NULL;
+  }
+  return ret;
+}
+
+static gboolean
+gst_osx_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec)
+{
+  gboolean ret = FALSE;
+  GstOsxRingBuffer *osxbuf;
+  AudioStreamBasicDescription format;
+
+  osxbuf = GST_OSX_RING_BUFFER (buf);
+
+  if (RINGBUFFER_IS_SPDIF (spec->type)) {
+    format.mFormatID = kAudioFormat60958AC3;
+    format.mSampleRate = (double) spec->rate;
+    format.mChannelsPerFrame = 2;
+    format.mFormatFlags = kAudioFormatFlagIsSignedInteger |
+        kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonMixable;
+    format.mBytesPerFrame = 0;
+    format.mBitsPerChannel = 16;
+    format.mBytesPerPacket = 6144;
+    format.mFramesPerPacket = 1536;
+    format.mReserved = 0;
+    spec->segsize = 6144;
+    spec->segtotal = 10;
+    osxbuf->is_passthrough = TRUE;
+  } else {
+    int width, depth;
+    /* Fill out the audio description we're going to be using */
+    format.mFormatID = kAudioFormatLinearPCM;
+    format.mSampleRate = (double) spec->rate;
+    format.mChannelsPerFrame = spec->channels;
+    if (spec->type == GST_BUFTYPE_FLOAT) {
+      format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
+      width = depth = spec->width;
+    } else {
+      format.mFormatFlags = kAudioFormatFlagIsSignedInteger;
+      width = spec->width;
+      depth = spec->depth;
+      if (width == depth) {
+        format.mFormatFlags |= kAudioFormatFlagIsPacked;
+      } else {
+        format.mFormatFlags |= kAudioFormatFlagIsAlignedHigh;
+      }
+      if (spec->bigend) {
+        format.mFormatFlags |= kAudioFormatFlagIsBigEndian;
+      }
+    }
+    format.mBytesPerFrame = spec->channels * (width >> 3);
+    format.mBitsPerChannel = depth;
+    format.mBytesPerPacket = spec->channels * (width >> 3);
+    format.mFramesPerPacket = 1;
+    format.mReserved = 0;
+    spec->segsize =
+        (spec->latency_time * spec->rate / G_USEC_PER_SEC) *
+        spec->bytes_per_sample;
+    spec->segtotal = spec->buffer_time / spec->latency_time;
+    osxbuf->stream_idx = 0;
+    osxbuf->is_passthrough = FALSE;
+  }
+
+  GST_DEBUG_OBJECT (osxbuf, "Format: " CORE_AUDIO_FORMAT,
+      CORE_AUDIO_FORMAT_ARGS (format));
+
+  buf->data = gst_buffer_new_and_alloc (spec->segtotal * spec->segsize);
+  memset (GST_BUFFER_DATA (buf->data), 0, GST_BUFFER_SIZE (buf->data));
+
+  if (osxbuf->is_passthrough) {
+    ret = gst_osx_ring_buffer_acquire_spdif (osxbuf, format);
+    if (ret) {
+      gst_osx_ring_buffer_monitorize_spdif (osxbuf);
+    }
+  } else {
+    ret = gst_osx_ring_buffer_acquire_analog (osxbuf, format, spec->caps);
+  }
+
+  if (!ret) {
+    gst_buffer_unref (buf->data);
+    buf->data = NULL;
+  }
+
+  osxbuf->segoffset = 0;
+
+  return ret;
 }
 
 static gboolean
@@ -502,14 +954,36 @@ gst_osx_ring_buffer_release (GstRingBuffer * buf)
   return TRUE;
 }
 
+static OSStatus
+gst_osx_ring_buffer_render_notify (GstOsxRingBuffer * osxbuf,
+    AudioUnitRenderActionFlags * ioActionFlags,
+    const AudioTimeStamp * inTimeStamp,
+    unsigned int inBusNumber,
+    unsigned int inNumberFrames, AudioBufferList * ioData)
+{
+  /* Before rendering a frame, we get the PreRender notification.
+   * Here, we detach the RenderCallback if we've been paused.
+   *
+   * This is necessary (rather than just directly detaching it) to
+   * work around some thread-safety issues in CoreAudio
+   */
+  if ((*ioActionFlags) & kAudioUnitRenderAction_PreRender) {
+    if (osxbuf->io_proc_needs_deactivation) {
+      gst_osx_ring_buffer_remove_render_callback (osxbuf);
+    }
+  }
+
+  return noErr;
+}
+
 static void
 gst_osx_ring_buffer_remove_render_callback (GstOsxRingBuffer * osxbuf)
 {
   AURenderCallbackStruct input;
   OSStatus status;
 
-  /* Deactivate the render callback by calling SetRenderCallback with a NULL
-   * inputProc.
+  /* Deactivate the render callback by calling SetRenderCallback
+   * with a NULL inputProc.
    */
   input.inputProc = NULL;
   input.inputProcRefCon = NULL;
@@ -518,7 +992,8 @@ gst_osx_ring_buffer_remove_render_callback (GstOsxRingBuffer * osxbuf)
       &input, sizeof (input));
 
   if (status) {
-    GST_WARNING_OBJECT (osxbuf, "Failed to remove render callback");
+    GST_WARNING_OBJECT (osxbuf, "Failed to remove render callback %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
   }
 
   /* Remove the RenderNotify too */
@@ -526,7 +1001,9 @@ gst_osx_ring_buffer_remove_render_callback (GstOsxRingBuffer * osxbuf)
       (AURenderCallback) gst_osx_ring_buffer_render_notify, osxbuf);
 
   if (status) {
-    GST_WARNING_OBJECT (osxbuf, "Failed to remove render notify callback");
+    GST_WARNING_OBJECT (osxbuf,
+        "Failed to remove render notify callback %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
   }
 
   /* We're deactivated.. */
@@ -534,39 +1011,14 @@ gst_osx_ring_buffer_remove_render_callback (GstOsxRingBuffer * osxbuf)
   osxbuf->io_proc_active = FALSE;
 }
 
-static OSStatus
-gst_osx_ring_buffer_render_notify (GstOsxRingBuffer * osxbuf,
-    AudioUnitRenderActionFlags * ioActionFlags,
-    const AudioTimeStamp * inTimeStamp,
-    unsigned int inBusNumber,
-    unsigned int inNumberFrames, AudioBufferList * ioData)
-{
-  /* Before rendering a frame, we get the PreRender notification.
-   * Here, we detach the RenderCallback if we've been paused.
-   *
-   * This is necessary (rather than just directly detaching it) to work
-   * around some thread-safety issues in CoreAudio
-   */
-  if ((*ioActionFlags) & kAudioUnitRenderAction_PreRender) {
-    if (osxbuf->io_proc_needs_deactivation) {
-      gst_osx_ring_buffer_remove_render_callback (osxbuf);
-    }
-  }
-
-  return noErr;
-}
-
 static gboolean
-gst_osx_ring_buffer_start (GstRingBuffer * buf)
+gst_osx_ring_buffer_io_proc_start (GstOsxRingBuffer * osxbuf)
 {
   OSStatus status;
-  GstOsxRingBuffer *osxbuf;
   AURenderCallbackStruct input;
   AudioUnitPropertyID callback_type;
 
-  osxbuf = GST_OSX_RING_BUFFER (buf);
-
-  GST_DEBUG ("osx ring buffer start ioproc: 0x%p device_id %lu",
+  GST_DEBUG ("osx ring buffer start ioproc: %p device_id %lu",
       osxbuf->element->io_proc, (gulong) osxbuf->device_id);
   if (!osxbuf->io_proc_active) {
     callback_type = osxbuf->is_src ?
@@ -580,7 +1032,8 @@ gst_osx_ring_buffer_start (GstRingBuffer * buf)
         &input, sizeof (input));
 
     if (status) {
-      GST_WARNING ("AudioUnitSetProperty returned %d", (int) status);
+      GST_ERROR ("AudioUnitSetProperty failed: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
       return FALSE;
     }
     // ### does it make sense to do this notify stuff for input mode?
@@ -588,7 +1041,8 @@ gst_osx_ring_buffer_start (GstRingBuffer * buf)
         (AURenderCallback) gst_osx_ring_buffer_render_notify, osxbuf);
 
     if (status) {
-      GST_WARNING ("AudioUnitAddRenderNotify returned %d", (int) status);
+      GST_ERROR ("AudioUnitAddRenderNotify failed %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
       return FALSE;
     }
 
@@ -599,107 +1053,197 @@ gst_osx_ring_buffer_start (GstRingBuffer * buf)
 
   status = AudioOutputUnitStart (osxbuf->audiounit);
   if (status) {
-    GST_WARNING ("AudioOutputUnitStart returned %d", (int) status);
+    GST_ERROR ("AudioOutputUnitStart failed: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
     return FALSE;
   }
   return TRUE;
 }
 
-// ###
 static gboolean
-gst_osx_ring_buffer_pause (GstRingBuffer * buf)
+gst_osx_ring_buffer_io_proc_stop (GstOsxRingBuffer * osxbuf)
 {
-  GstOsxRingBuffer *osxbuf = GST_OSX_RING_BUFFER (buf);
+  OSErr status;
 
-  GST_DEBUG ("osx ring buffer pause ioproc: 0x%p device_id %lu",
+  GST_DEBUG ("osx ring buffer stop ioproc: %p device_id %lu",
       osxbuf->element->io_proc, (gulong) osxbuf->device_id);
+
+  status = AudioOutputUnitStop (osxbuf->audiounit);
+  if (status) {
+    GST_WARNING ("AudioOutputUnitStop failed: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+  }
+  // ###: why is it okay to directly remove from here but not from pause() ?
   if (osxbuf->io_proc_active) {
-    /* CoreAudio isn't threadsafe enough to do this here; we must deactivate
-     * the render callback elsewhere. See:
-     *   http://lists.apple.com/archives/Coreaudio-api/2006/Mar/msg00010.html
-     */
-    osxbuf->io_proc_needs_deactivation = TRUE;
+    gst_osx_ring_buffer_remove_render_callback (osxbuf);
   }
   return TRUE;
 }
 
-// ###
+static void
+gst_osx_ring_buffer_remove_render_spdif_callback (GstOsxRingBuffer * osxbuf)
+{
+  OSStatus status;
+
+  /* Deactivate the render callback by calling
+   * AudioDeviceDestroyIOProcID */
+  status = AudioDeviceDestroyIOProcID (osxbuf->device_id, osxbuf->procID);
+  if (status != noErr) {
+    GST_ERROR ("AudioDeviceDestroyIOProcID failed: %"
+        GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+  }
+
+  GST_DEBUG ("osx ring buffer removed ioproc ID: %p device_id %lu",
+      osxbuf->procID, (gulong) osxbuf->device_id);
+
+  /* We're deactivated.. */
+  osxbuf->procID = 0;
+  osxbuf->io_proc_needs_deactivation = FALSE;
+  osxbuf->io_proc_active = FALSE;
+}
+
 static gboolean
-gst_osx_ring_buffer_stop (GstRingBuffer * buf)
+gst_osx_ring_buffer_io_proc_spdif_start (GstOsxRingBuffer * osxbuf)
 {
   OSErr status;
-  GstOsxRingBuffer *osxbuf;
 
-  osxbuf = GST_OSX_RING_BUFFER (buf);
+  GST_DEBUG ("osx ring buffer start ioproc ID: %p device_id %lu",
+      osxbuf->procID, (gulong) osxbuf->device_id);
 
-  GST_DEBUG ("osx ring buffer stop ioproc: 0x%p device_id %lu",
-      osxbuf->element->io_proc, (gulong) osxbuf->device_id);
+  if (!osxbuf->io_proc_active) {
+    /* Add IOProc callback */
+    status = AudioDeviceCreateIOProcID (osxbuf->device_id,
+        (AudioDeviceIOProc) gst_osx_ring_buffer_io_proc_spdif,
+        (void *) osxbuf, &osxbuf->procID);
+    if (status != noErr) {
+      GST_ERROR ("AudioDeviceCreateIOProcID failed: %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+      return FALSE;
+    }
+    osxbuf->io_proc_active = TRUE;
+  }
 
-  status = AudioOutputUnitStop (osxbuf->audiounit);
-  if (status)
-    GST_WARNING ("AudioOutputUnitStop returned %d", (int) status);
+  osxbuf->io_proc_needs_deactivation = FALSE;
+
+  /* Start device */
+  status = AudioDeviceStart (osxbuf->device_id, osxbuf->procID);
+  if (status != noErr) {
+    GST_ERROR ("AudioDeviceStart failed: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+    return FALSE;
+  }
+  return TRUE;
+}
+
+static gboolean
+gst_osx_ring_buffer_io_proc_spdif_stop (GstOsxRingBuffer * osxbuf)
+{
+  OSErr status;
+
+  /* Stop device */
+  status = AudioDeviceStop (osxbuf->device_id, osxbuf->procID);
+  if (status != noErr) {
+    GST_ERROR ("AudioDeviceStop failed: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+  }
+
+  GST_DEBUG ("osx ring buffer stop ioproc ID: %p device_id %lu",
+      osxbuf->procID, (gulong) osxbuf->device_id);
 
-  // ###: why is it okay to directly remove from here but not from pause() ?
   if (osxbuf->io_proc_active) {
-    gst_osx_ring_buffer_remove_render_callback (osxbuf);
+    gst_osx_ring_buffer_remove_render_spdif_callback (osxbuf);
   }
+
+  gst_osx_ring_buffer_close_spdif (osxbuf);
+
   return TRUE;
 }
 
-static guint
-gst_osx_ring_buffer_delay (GstRingBuffer * buf)
+static gboolean
+gst_osx_ring_buffer_start (GstRingBuffer * buf)
 {
-  double latency;
-  UInt32 size = sizeof (double);
   GstOsxRingBuffer *osxbuf;
-  OSStatus status;
-  guint samples;
 
   osxbuf = GST_OSX_RING_BUFFER (buf);
 
-  status = AudioUnitGetProperty (osxbuf->audiounit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0,      /* N/A for global */
-      &latency, &size);
-
-  if (status) {
-    GST_WARNING_OBJECT (buf, "Failed to get latency: %d", (int) status);
-    return 0;
+  if (osxbuf->is_passthrough) {
+    return gst_osx_ring_buffer_io_proc_spdif_start (osxbuf);
+  } else {
+    return gst_osx_ring_buffer_io_proc_start (osxbuf);
   }
+}
 
-  samples = latency * GST_RING_BUFFER (buf)->spec.rate;
-  GST_DEBUG_OBJECT (buf, "Got latency: %f seconds -> %d samples", latency,
-      samples);
-  return samples;
+static gboolean
+gst_osx_ring_buffer_pause (GstRingBuffer * buf)
+{
+  GstOsxRingBuffer *osxbuf = GST_OSX_RING_BUFFER (buf);
+
+  if (osxbuf->is_passthrough) {
+    GST_DEBUG ("osx ring buffer pause ioproc ID: %p device_id %lu",
+        osxbuf->procID, (gulong) osxbuf->device_id);
+
+    if (osxbuf->io_proc_active) {
+      gst_osx_ring_buffer_remove_render_spdif_callback (osxbuf);
+    }
+  } else {
+    GST_DEBUG ("osx ring buffer pause ioproc: %p device_id %lu",
+        osxbuf->element->io_proc, (gulong) osxbuf->device_id);
+    if (osxbuf->io_proc_active) {
+      /* CoreAudio isn't threadsafe enough to do this here;
+       * we must deactivate the render callback elsewhere. See:
+       * http://lists.apple.com/archives/Coreaudio-api/2006/Mar/msg00010.html
+       */
+      osxbuf->io_proc_needs_deactivation = TRUE;
+    }
+  }
+  return TRUE;
 }
 
-static AudioBufferList *
-buffer_list_alloc (int channels, int size)
+
+static gboolean
+gst_osx_ring_buffer_stop (GstRingBuffer * buf)
 {
-  AudioBufferList *list;
-  int total_size;
-  int n;
+  GstOsxRingBuffer *osxbuf;
 
-  total_size = sizeof (AudioBufferList) + 1 * sizeof (AudioBuffer);
-  list = (AudioBufferList *) g_malloc (total_size);
+  osxbuf = GST_OSX_RING_BUFFER (buf);
 
-  list->mNumberBuffers = 1;
-  for (n = 0; n < (int) list->mNumberBuffers; ++n) {
-    list->mBuffers[n].mNumberChannels = channels;
-    list->mBuffers[n].mDataByteSize = size;
-    list->mBuffers[n].mData = g_malloc (size);
+  if (osxbuf->is_passthrough) {
+    gst_osx_ring_buffer_io_proc_spdif_stop (osxbuf);
+  } else {
+    gst_osx_ring_buffer_io_proc_stop (osxbuf);
   }
 
-  return list;
+  return TRUE;
 }
 
-static void
-buffer_list_free (AudioBufferList * list)
+static guint
+gst_osx_ring_buffer_delay (GstRingBuffer * buf)
 {
-  int n;
+  double latency;
+  UInt32 size = sizeof (double);
+  GstOsxRingBuffer *osxbuf;
+  OSStatus status;
+  guint samples;
 
-  for (n = 0; n < (int) list->mNumberBuffers; ++n) {
-    if (list->mBuffers[n].mData)
-      g_free (list->mBuffers[n].mData);
-  }
+  osxbuf = GST_OSX_RING_BUFFER (buf);
 
-  g_free (list);
+  if (osxbuf->is_passthrough) {
+    samples = _audio_device_get_latency (osxbuf->device_id);
+    samples += _audio_stream_get_latency (osxbuf->stream_id);
+    latency = (double) samples / GST_RING_BUFFER (buf)->spec.rate;
+  } else {
+    status = AudioUnitGetProperty (osxbuf->audiounit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0,    /* N/A for global */
+        &latency, &size);
+
+    if (status) {
+      GST_WARNING_OBJECT (buf, "Failed to get latency: %"
+          GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status));
+      return 0;
+    }
+
+    samples = latency * GST_RING_BUFFER (buf)->spec.rate;
+  }
+  GST_DEBUG_OBJECT (buf, "Got latency: %f seconds -> %d samples",
+      latency, samples);
+  return samples;
 }
index 5e6dbe4..6365511 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * GStreamer
  * Copyright (C) 2006 Zaheer Abbas Merali <zaheerabbas at merali dot org>
+ * Copyright (C) 2012 Fluendo S.A. <support@fluendo.com>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -64,6 +65,8 @@ G_BEGIN_DECLS
 #define GST_IS_OSX_RING_BUFFER_CLASS(klass) \
   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSX_RING_BUFFER))
 
+#define RINGBUFFER_IS_SPDIF(t) ((t) == GST_BUFTYPE_AC3 || (t) == GST_BUFTYPE_DTS)
+
 typedef struct _GstOsxRingBuffer GstOsxRingBuffer;
 typedef struct _GstOsxRingBufferClass GstOsxRingBufferClass;
 
@@ -71,15 +74,31 @@ struct _GstOsxRingBuffer
 {
   GstRingBuffer object;
 
+  gboolean is_spdif_capable;
   gboolean is_src;
-  AudioUnit audiounit;
+  gboolean is_passthrough;
+  gint stream_idx;
+
   AudioDeviceID device_id;
   gboolean io_proc_active;
   gboolean io_proc_needs_deactivation;
   guint buffer_len;
   guint segoffset;
-  AudioBufferList * recBufferList;
-  GstOsxAudioElementInterface * element;
+
+  GstOsxAudioElementInterface *element;
+
+  /* For LPCM in/out */
+  AudioUnit audiounit;
+  AudioBufferList *recBufferList;
+
+  /* For SPDIF out */
+  pid_t hog_pid;
+  gboolean disabled_mixing;
+  AudioStreamID stream_id;
+  gboolean revert_format;
+  AudioStreamBasicDescription stream_format;
+  AudioStreamBasicDescription original_format;
+  AudioDeviceIOProcID procID;
 };
 
 struct _GstOsxRingBufferClass
@@ -92,3 +111,4 @@ GType gst_osx_ring_buffer_get_type (void);
 G_END_DECLS
 
 #endif /* __GST_OSX_RING_BUFFER_H__ */
+