osxaudiosink: respect the prefered channel layout

In OSX is allowed to configure the default audio output device,
prefered channel layout and speaker positions through the tool
"Audio MIDI Setup".
This commit is contained in:
Josep Torra 2012-06-19 12:15:33 +02:00 committed by Sebastian Dröge
parent c55e492e80
commit d2c6cc3a39
6 changed files with 174 additions and 61 deletions

View file

@ -14,7 +14,7 @@ libgstosxaudio_la_LIBADD = \
$(GST_PLUGINS_BASE_LIBS) \ $(GST_PLUGINS_BASE_LIBS) \
$(GST_BASE_LIBS) \ $(GST_BASE_LIBS) \
$(GST_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 libgstosxaudio_la_LIBTOOLFLAGS = --tag=disable-static
noinst_HEADERS = gstosxaudiosink.h \ noinst_HEADERS = gstosxaudiosink.h \

View file

@ -67,13 +67,14 @@
#endif #endif
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/audio/multichannel.h>
#include <gst/audio/gstaudioiec61937.h>
#include <CoreAudio/CoreAudio.h> #include <CoreAudio/CoreAudio.h>
#include <CoreAudio/AudioHardware.h> #include <CoreAudio/AudioHardware.h>
#include "gstosxaudiosink.h" #include "gstosxaudiosink.h"
#include "gstosxaudioelement.h" #include "gstosxaudioelement.h"
#include <gst/audio/gstaudioiec61937.h>
GST_DEBUG_CATEGORY_STATIC (osx_audiosink_debug); GST_DEBUG_CATEGORY_STATIC (osx_audiosink_debug);
#define GST_CAT_DEFAULT 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, " "width = (int) 32, "
"depth = (int) 32, " "depth = (int) 32, "
"rate = (int) [1, MAX], " "rate = (int) [1, MAX], "
"channels = (int) [1, MAX];" "channels = (int) [1, 9];"
"audio/x-raw-int, " "audio/x-raw-int, "
"endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, " "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, "
"signed = (boolean) { TRUE }, " "signed = (boolean) { TRUE }, "
"width = (int) 32, " "width = (int) 32, "
"depth = (int) 32, " "depth = (int) 32, "
"rate = (int) [1, MAX], " "rate = (int) [1, MAX], "
"channels = (int) [1, MAX];" "channels = (int) [1, 9];"
"audio/x-raw-int, " "audio/x-raw-int, "
"endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, " "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, "
"signed = (boolean) { TRUE }, " "signed = (boolean) { TRUE }, "
"width = (int) 24, " "width = (int) 24, "
"depth = (int) 24, " "depth = (int) 24, "
"rate = (int) [1, MAX], " "rate = (int) [1, MAX], "
"channels = (int) [1, MAX];" "channels = (int) [1, 9];"
"audio/x-raw-int, " "audio/x-raw-int, "
"endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, " "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, "
"signed = (boolean) { TRUE }, " "signed = (boolean) { TRUE }, "
"width = (int) 16, " "width = (int) 16, "
"depth = (int) 16, " "depth = (int) 16, "
"rate = (int) [1, MAX], " "rate = (int) [1, MAX], "
"channels = (int) [1, MAX];" "channels = (int) [1, 9];"
"audio/x-raw-int, " "audio/x-raw-int, "
"endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, " "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, "
"signed = (boolean) { TRUE }, " "signed = (boolean) { TRUE }, "
@ -296,19 +297,8 @@ static GstCaps *
gst_osx_audio_sink_getcaps (GstBaseSink * base) gst_osx_audio_sink_getcaps (GstBaseSink * base)
{ {
GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (base); GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (base);
GstOsxRingBuffer *osxbuf;
GstElementClass *element_class;
GstPadTemplate *pad_template;
GstCaps *caps;
gchar *caps_string = NULL; 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) { if (sink->cached_caps) {
caps_string = gst_caps_to_string (sink->cached_caps); caps_string = gst_caps_to_string (sink->cached_caps);
GST_DEBUG_OBJECT (sink, "using cached caps: %s", caps_string); 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); return gst_caps_ref (sink->cached_caps);
} }
element_class = GST_ELEMENT_GET_CLASS (sink); GST_DEBUG_OBJECT (sink, "using template caps");
pad_template = gst_element_class_get_pad_template (element_class, "sink"); return NULL;
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 static gboolean
@ -383,9 +353,6 @@ gst_osx_audio_sink_acceptcaps (GstPad * pad, GstCaps * caps)
{ {
gboolean framed = FALSE; gboolean framed = FALSE;
if (!osxbuf->is_spdif_capable)
goto done;
st = gst_caps_get_structure (caps, 0); st = gst_caps_get_structure (caps, 0);
gst_structure_get_boolean (st, "framed", &framed); gst_structure_get_boolean (st, "framed", &framed);
@ -397,9 +364,6 @@ gst_osx_audio_sink_acceptcaps (GstPad * pad, GstCaps * caps)
{ {
gboolean parsed = FALSE; gboolean parsed = FALSE;
if (!osxbuf->is_spdif_capable)
goto done;
st = gst_caps_get_structure (caps, 0); st = gst_caps_get_structure (caps, 0);
gst_structure_get_boolean (st, "parsed", &parsed); gst_structure_get_boolean (st, "parsed", &parsed);
@ -566,6 +530,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 static gboolean
gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink) gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink)
{ {
@ -626,9 +712,12 @@ gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink)
if (res && !_audio_device_is_alive (osxsink->device_id)) { if (res && !_audio_device_is_alive (osxsink->device_id)) {
GST_ERROR_OBJECT (osxsink, "Requested device not usable"); GST_ERROR_OBJECT (osxsink, "Requested device not usable");
res = FALSE; res = FALSE;
goto done;
} }
} }
res = gst_osx_audio_sink_allowed_caps (osxsink);
done: done:
g_free (devices); g_free (devices);

View file

@ -71,6 +71,7 @@ struct _GstOsxAudioSink
GstBaseAudioSink sink; GstBaseAudioSink sink;
AudioDeviceID device_id; AudioDeviceID device_id;
AudioUnit audiounit; AudioUnit audiounit;
double volume; double volume;
GstCaps *cached_caps; GstCaps *cached_caps;

View file

@ -347,7 +347,7 @@ _audio_device_get_channel_layout (AudioDeviceID device_id)
{ {
OSStatus status = noErr; OSStatus status = noErr;
UInt32 propertySize = 0; UInt32 propertySize = 0;
AudioChannelLayout *channel_layout = NULL; AudioChannelLayout *layout = NULL;
AudioObjectPropertyAddress channelLayoutAddress = { AudioObjectPropertyAddress channelLayoutAddress = {
kAudioDevicePropertyPreferredChannelLayout, kAudioDevicePropertyPreferredChannelLayout,
@ -359,20 +359,50 @@ _audio_device_get_channel_layout (AudioDeviceID device_id)
status = AudioObjectGetPropertyDataSize (device_id, status = AudioObjectGetPropertyDataSize (device_id,
&channelLayoutAddress, 0, NULL, &propertySize); &channelLayoutAddress, 0, NULL, &propertySize);
if (status != noErr) { if (status != noErr) {
GST_ERROR ("failed to get prefered layout: %" GST_FOURCC_FORMAT,
GST_FOURCC_ARGS (status));
goto beach; goto beach;
} }
/* Get the default channel layout of the device */ /* Get the default channel layout of the device */
channel_layout = (AudioChannelLayout *) g_malloc (propertySize); layout = (AudioChannelLayout *) g_malloc (propertySize);
status = AudioObjectGetPropertyData (device_id, status = AudioObjectGetPropertyData (device_id,
&channelLayoutAddress, 0, NULL, &propertySize, channel_layout); &channelLayoutAddress, 0, NULL, &propertySize, layout);
if (status != noErr) { if (status != noErr) {
g_free (channel_layout); GST_ERROR ("failed to get prefered layout: %" GST_FOURCC_FORMAT,
channel_layout = NULL; 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: beach:
return channel_layout; return layout;
failed:
g_free (layout);
return NULL;
} }
static inline AudioStreamID * static inline AudioStreamID *

View file

@ -131,7 +131,6 @@ gst_osx_ring_buffer_init (GstOsxRingBuffer * ringbuffer,
GstOsxRingBufferClass * g_class) GstOsxRingBufferClass * g_class)
{ {
/* Nothing to do right now */ /* Nothing to do right now */
ringbuffer->is_spdif_capable = FALSE;
ringbuffer->is_passthrough = FALSE; ringbuffer->is_passthrough = FALSE;
ringbuffer->hog_pid = -1; ringbuffer->hog_pid = -1;
ringbuffer->disabled_mixing = FALSE; ringbuffer->disabled_mixing = FALSE;
@ -257,13 +256,6 @@ gst_osx_ring_buffer_open_device (GstRingBuffer * buf)
* thread to handle the notifications. */ * thread to handle the notifications. */
_audio_system_set_runloop (NULL); _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->audiounit = gst_osx_ring_buffer_create_audio_unit (osxbuf,
osxbuf->is_src, osxbuf->device_id); osxbuf->is_src, osxbuf->device_id);

View file

@ -48,6 +48,8 @@
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/audio/gstringbuffer.h> #include <gst/audio/gstringbuffer.h>
#include <CoreAudio/CoreAudio.h> #include <CoreAudio/CoreAudio.h>
#include <AudioToolbox/AudioToolbox.h>
#include "gstosxaudioelement.h" #include "gstosxaudioelement.h"
G_BEGIN_DECLS G_BEGIN_DECLS
@ -74,7 +76,6 @@ struct _GstOsxRingBuffer
{ {
GstRingBuffer object; GstRingBuffer object;
gboolean is_spdif_capable;
gboolean is_src; gboolean is_src;
gboolean is_passthrough; gboolean is_passthrough;
gint stream_idx; gint stream_idx;