osxaudiosink: respect the prefered channel layout
authorJosep Torra <n770galaxy@gmail.com>
Tue, 19 Jun 2012 10:15:33 +0000 (12:15 +0200)
committerSebastian Dröge <sebastian.droege@collabora.co.uk>
Tue, 19 Jun 2012 11:55:37 +0000 (12:55 +0100)
In OSX is allowed to configure the default audio output device,
prefered channel layout and speaker positions through the tool
"Audio MIDI Setup".

sys/osxaudio/Makefile.am
sys/osxaudio/gstosxaudiosink.c
sys/osxaudio/gstosxaudiosink.h
sys/osxaudio/gstosxcoreaudio.h
sys/osxaudio/gstosxringbuffer.c
sys/osxaudio/gstosxringbuffer.h

index 6c168a9..138e5ae 100644 (file)
@@ -14,7 +14,7 @@ libgstosxaudio_la_LIBADD =                      \
     $(GST_PLUGINS_BASE_LIBS)          \
     $(GST_BASE_LIBS)                  \
     $(GST_LIBS)
-libgstosxaudio_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) -Wl,-framework -Wl,CoreAudio -Wl,-framework -Wl,AudioUnit -Wl,-framework -Wl,CoreServices
+libgstosxaudio_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) -Wl,-framework -Wl,CoreAudio -Wl,-framework -Wl,AudioUnit -Wl,-framework -Wl,AudioToolbox -Wl,-framework -Wl,CoreServices
 libgstosxaudio_la_LIBTOOLFLAGS = --tag=disable-static
 
 noinst_HEADERS = gstosxaudiosink.h       \
index aee25c9..99284d0 100644 (file)
 #endif
 
 #include <gst/gst.h>
+#include <gst/audio/multichannel.h>
+#include <gst/audio/gstaudioiec61937.h>
 #include <CoreAudio/CoreAudio.h>
 #include <CoreAudio/AudioHardware.h>
+
 #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
 
@@ -104,28 +105,28 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
         "width = (int) 32, "
         "depth = (int) 32, "
         "rate = (int) [1, MAX], "
-        "channels = (int) [1, MAX];"
+        "channels = (int) [1, 9];"
         "audio/x-raw-int, "
         "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, "
         "signed = (boolean) { TRUE }, "
         "width = (int) 32, "
         "depth = (int) 32, "
         "rate = (int) [1, MAX], "
-        "channels = (int) [1, MAX];"
+        "channels = (int) [1, 9];"
         "audio/x-raw-int, "
         "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, "
         "signed = (boolean) { TRUE }, "
         "width = (int) 24, "
         "depth = (int) 24, "
         "rate = (int) [1, MAX], "
-        "channels = (int) [1, MAX];"
+        "channels = (int) [1, 9];"
         "audio/x-raw-int, "
         "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, "
         "signed = (boolean) { TRUE }, "
         "width = (int) 16, "
         "depth = (int) 16, "
         "rate = (int) [1, MAX], "
-        "channels = (int) [1, MAX];"
+        "channels = (int) [1, 9];"
         "audio/x-raw-int, "
         "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, "
         "signed = (boolean) { TRUE }, "
@@ -296,19 +297,8 @@ 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);
@@ -316,28 +306,8 @@ gst_osx_audio_sink_getcaps (GstBaseSink * base)
     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;
+  GST_DEBUG_OBJECT (sink, "using template caps");
+  return NULL;
 }
 
 static gboolean
@@ -383,9 +353,6 @@ gst_osx_audio_sink_acceptcaps (GstPad * pad, GstCaps * caps)
     {
       gboolean framed = FALSE;
 
-      if (!osxbuf->is_spdif_capable)
-        goto done;
-
       st = gst_caps_get_structure (caps, 0);
 
       gst_structure_get_boolean (st, "framed", &framed);
@@ -397,9 +364,6 @@ gst_osx_audio_sink_acceptcaps (GstPad * pad, GstCaps * caps)
     {
       gboolean parsed = FALSE;
 
-      if (!osxbuf->is_spdif_capable)
-        goto done;
-
       st = gst_caps_get_structure (caps, 0);
 
       gst_structure_get_boolean (st, "parsed", &parsed);
@@ -567,6 +531,128 @@ _dump_channel_layout (AudioChannelLayout * channel_layout)
 }
 
 static gboolean
+gst_osx_audio_sink_allowed_caps (GstOsxAudioSink * osxsink)
+{
+  gint i, max_channels = 0;
+  gboolean spdif_allowed, use_positions = FALSE;
+  AudioChannelLayout *layout;
+  GstElementClass *element_class;
+  GstPadTemplate *pad_template;
+  GstCaps *caps, *in_caps;
+
+  GstAudioChannelPosition pos[9] = {
+    GST_AUDIO_CHANNEL_POSITION_INVALID,
+    GST_AUDIO_CHANNEL_POSITION_INVALID,
+    GST_AUDIO_CHANNEL_POSITION_INVALID,
+    GST_AUDIO_CHANNEL_POSITION_INVALID,
+    GST_AUDIO_CHANNEL_POSITION_INVALID,
+    GST_AUDIO_CHANNEL_POSITION_INVALID,
+    GST_AUDIO_CHANNEL_POSITION_INVALID,
+    GST_AUDIO_CHANNEL_POSITION_INVALID,
+    GST_AUDIO_CHANNEL_POSITION_INVALID
+  };
+
+  /* First collect info about the HW capabilites and preferences */
+  spdif_allowed = _audio_device_is_spdif_avail (osxsink->device_id);
+  layout = _audio_device_get_channel_layout (osxsink->device_id);
+
+  GST_DEBUG_OBJECT (osxsink, "Selected device ID: %u SPDIF allowed: %d",
+      (unsigned) osxsink->device_id, spdif_allowed);
+
+  if (layout) {
+    _dump_channel_layout (layout);
+    max_channels = layout->mNumberChannelDescriptions;
+  } else {
+    GST_WARNING_OBJECT (osxsink, "This driver does not support "
+        "kAudioDevicePropertyPreferredChannelLayout.");
+    max_channels = 2;
+  }
+
+  if (max_channels > 2) {
+    max_channels = MIN (max_channels, 9);
+    use_positions = TRUE;
+    for (i = 0; i < max_channels; i++) {
+      switch (layout->mChannelDescriptions[i].mChannelLabel) {
+        case kAudioChannelLabel_Left:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
+          break;
+        case kAudioChannelLabel_Right:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
+          break;
+        case kAudioChannelLabel_Center:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER;
+          break;
+        case kAudioChannelLabel_LFEScreen:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_LFE;
+          break;
+        case kAudioChannelLabel_LeftSurround:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_REAR_LEFT;
+          break;
+        case kAudioChannelLabel_RightSurround:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT;
+          break;
+        case kAudioChannelLabel_RearSurroundLeft:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT;
+          break;
+        case kAudioChannelLabel_RearSurroundRight:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT;
+          break;
+        case kAudioChannelLabel_CenterSurround:
+          pos[i] = GST_AUDIO_CHANNEL_POSITION_REAR_CENTER;
+          break;
+        default:
+          GST_WARNING_OBJECT (osxsink, "unrecognized channel: %d",
+              (int) layout->mChannelDescriptions[i].mChannelLabel);
+          use_positions = FALSE;
+          max_channels = 2;
+          break;
+      }
+    }
+  }
+  g_free (layout);
+
+  /* Recover the template caps */
+  element_class = GST_ELEMENT_GET_CLASS (osxsink);
+  pad_template = gst_element_class_get_pad_template (element_class, "sink");
+  in_caps = gst_pad_template_get_caps (pad_template);
+
+  /* Create the allowed subset  */
+  caps = gst_caps_new_empty ();
+  for (i = 0; i < gst_caps_get_size (in_caps); i++) {
+    GstStructure *in_s, *out_s;
+
+    in_s = gst_caps_get_structure (in_caps, i);
+
+    if (gst_structure_has_name (in_s, "audio/x-ac3") ||
+        gst_structure_has_name (in_s, "audio/x-dts")) {
+      if (spdif_allowed) {
+        gst_caps_append_structure (caps, gst_structure_copy (in_s));
+      }
+    } else {
+      if (max_channels > 2 && use_positions) {
+        out_s = gst_structure_copy (in_s);
+        gst_structure_remove_field (out_s, "channels");
+        gst_structure_set (out_s, "channels", G_TYPE_INT, max_channels, NULL);
+        gst_audio_set_channel_positions (out_s, pos);
+        gst_caps_append_structure (caps, out_s);
+      }
+      out_s = gst_structure_copy (in_s);
+      gst_structure_remove_field (out_s, "channels");
+      gst_structure_set (out_s, "channels", GST_TYPE_INT_RANGE, 1, 2, NULL);
+      gst_caps_append_structure (caps, out_s);
+    }
+  }
+
+  if (osxsink->cached_caps) {
+    gst_caps_unref (osxsink->cached_caps);
+  }
+
+  osxsink->cached_caps = caps;
+
+  return TRUE;
+}
+
+static gboolean
 gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink)
 {
   AudioDeviceID *devices = NULL;
@@ -626,9 +712,12 @@ gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink)
     if (res && !_audio_device_is_alive (osxsink->device_id)) {
       GST_ERROR_OBJECT (osxsink, "Requested device not usable");
       res = FALSE;
+      goto done;
     }
   }
 
+  res = gst_osx_audio_sink_allowed_caps (osxsink);
+
 done:
   g_free (devices);
 
index cf94e47..e3aaec3 100644 (file)
@@ -71,6 +71,7 @@ struct _GstOsxAudioSink
   GstBaseAudioSink sink;
 
   AudioDeviceID device_id;
+
   AudioUnit audiounit;
   double volume;
   GstCaps *cached_caps;
index f1d5123..8876115 100644 (file)
@@ -347,7 +347,7 @@ _audio_device_get_channel_layout (AudioDeviceID device_id)
 {
   OSStatus status = noErr;
   UInt32 propertySize = 0;
-  AudioChannelLayout *channel_layout = NULL;
+  AudioChannelLayout *layout = NULL;
 
   AudioObjectPropertyAddress channelLayoutAddress = {
     kAudioDevicePropertyPreferredChannelLayout,
@@ -359,20 +359,50 @@ _audio_device_get_channel_layout (AudioDeviceID device_id)
   status = AudioObjectGetPropertyDataSize (device_id,
       &channelLayoutAddress, 0, NULL, &propertySize);
   if (status != noErr) {
+    GST_ERROR ("failed to get prefered layout: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
     goto beach;
   }
 
   /* Get the default channel layout of the device */
-  channel_layout = (AudioChannelLayout *) g_malloc (propertySize);
+  layout = (AudioChannelLayout *) g_malloc (propertySize);
   status = AudioObjectGetPropertyData (device_id,
-      &channelLayoutAddress, 0, NULL, &propertySize, channel_layout);
+      &channelLayoutAddress, 0, NULL, &propertySize, layout);
   if (status != noErr) {
-    g_free (channel_layout);
-    channel_layout = NULL;
+    GST_ERROR ("failed to get prefered layout: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (status));
+    goto failed;
+  }
+
+  if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) {
+    /* bitmap defined channellayout */
+    status =
+        AudioFormatGetProperty (kAudioFormatProperty_ChannelLayoutForBitmap,
+            sizeof (UInt32), &layout->mChannelBitmap, &propertySize, layout);
+    if (status != noErr) {
+      GST_ERROR ("failed to get layout for bitmap: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
+      goto failed;
+    }
+  } else if (layout->mChannelLayoutTag !=
+      kAudioChannelLayoutTag_UseChannelDescriptions) {
+      /* layouttags defined channellayout */
+      status = AudioFormatGetProperty (kAudioFormatProperty_ChannelLayoutForTag,
+          sizeof(AudioChannelLayoutTag), &layout->mChannelLayoutTag,
+          &propertySize, layout);
+    if (status != noErr) {
+      GST_ERROR ("failed to get layout for tag: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (status));
+      goto failed;
+    }
   }
 
 beach:
-  return channel_layout;
+  return layout;
+
+failed:
+  g_free (layout);
+  return NULL;
 }
 
 static inline AudioStreamID *
index afeb644..f43a055 100644 (file)
@@ -131,7 +131,6 @@ 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;
@@ -257,13 +256,6 @@ gst_osx_ring_buffer_open_device (GstRingBuffer * buf)
    * thread to handle the notifications. */
   _audio_system_set_runloop (NULL);
 
-  osxbuf->is_spdif_capable = _audio_device_is_spdif_avail (osxbuf->device_id);
-
-  if (osxbuf->is_spdif_capable) {
-    GST_DEBUG_OBJECT (osxbuf, "device %u is SPDIF capable",
-        (unsigned) osxbuf->device_id);
-  }
-
   osxbuf->audiounit = gst_osx_ring_buffer_create_audio_unit (osxbuf,
       osxbuf->is_src, osxbuf->device_id);
 
index 6365511..4394fa6 100644 (file)
@@ -48,6 +48,8 @@
 #include <gst/gst.h>
 #include <gst/audio/gstringbuffer.h>
 #include <CoreAudio/CoreAudio.h>
+#include <AudioToolbox/AudioToolbox.h>
+
 #include "gstosxaudioelement.h"
 
 G_BEGIN_DECLS
@@ -74,7 +76,6 @@ struct _GstOsxRingBuffer
 {
   GstRingBuffer object;
 
-  gboolean is_spdif_capable;
   gboolean is_src;
   gboolean is_passthrough;
   gint stream_idx;