diff options
author | Josep Torra <n770galaxy@gmail.com> | 2012-06-12 12:42:31 +0200 |
---|---|---|
committer | Sebastian Dröge <sebastian.droege@collabora.co.uk> | 2012-06-14 08:45:57 +0200 |
commit | 6b4c5a7ce0530693d171324809fbf40b76084276 (patch) | |
tree | 7e87e7c65377a793b59a2967d24c49f65a71a64b | |
parent | 1954f392c12d46ca4d60aee8fddb4a9df78763f4 (diff) |
osxaudiosink: Add support for SPDIF output
A big refactoring to allow passthrough AC3/DTS over SPDIF.
Several random cleanups and minor fixes.
-rw-r--r-- | sys/osxaudio/Makefile.am | 3 | ||||
-rw-r--r-- | sys/osxaudio/gstosxaudiosink.c | 323 | ||||
-rw-r--r-- | sys/osxaudio/gstosxaudiosink.h | 5 | ||||
-rw-r--r-- | sys/osxaudio/gstosxcoreaudio.h | 576 | ||||
-rw-r--r-- | sys/osxaudio/gstosxringbuffer.c | 1000 | ||||
-rw-r--r-- | sys/osxaudio/gstosxringbuffer.h | 26 |
6 files changed, 1668 insertions, 265 deletions
diff --git a/sys/osxaudio/Makefile.am b/sys/osxaudio/Makefile.am index cfd0c4d35..c7cb2871e 100644 --- a/sys/osxaudio/Makefile.am +++ b/sys/osxaudio/Makefile.am @@ -20,7 +20,8 @@ libgstosxaudio_la_LIBTOOLFLAGS = --tag=disable-static noinst_HEADERS = gstosxaudiosink.h \ gstosxaudioelement.h \ gstosxringbuffer.h \ - gstosxaudiosrc.h + gstosxaudiosrc.h \ + gstosxcoreaudio.h diff --git a/sys/osxaudio/gstosxaudiosink.c b/sys/osxaudio/gstosxaudiosink.c index fb587a830..562b4efb6 100644 --- a/sys/osxaudio/gstosxaudiosink.c +++ b/sys/osxaudio/gstosxaudiosink.c @@ -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"), @@ -71,9 +72,13 @@ #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, @@ -204,8 +217,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 @@ -214,7 +232,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 @@ -255,6 +278,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) { @@ -263,11 +458,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); @@ -279,10 +476,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, @@ -292,7 +489,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) { @@ -305,7 +503,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; @@ -343,34 +541,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; } diff --git a/sys/osxaudio/gstosxaudiosink.h b/sys/osxaudio/gstosxaudiosink.h index aac9719f9..cf94e474e 100644 --- a/sys/osxaudio/gstosxaudiosink.h +++ b/sys/osxaudio/gstosxaudiosink.h @@ -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 index 000000000..f1d51239f --- /dev/null +++ b/sys/osxaudio/gstosxcoreaudio.h @@ -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; +} + diff --git a/sys/osxaudio/gstosxringbuffer.c b/sys/osxaudio/gstosxringbuffer.c index cb77162cc..afeb64498 100644 --- a/sys/osxaudio/gstosxringbuffer.c +++ b/sys/osxaudio/gstosxringbuffer.c @@ -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"), @@ -50,9 +51,13 @@ #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; } diff --git a/sys/osxaudio/gstosxringbuffer.h b/sys/osxaudio/gstosxringbuffer.h index 5e6dbe41c..6365511cf 100644 --- a/sys/osxaudio/gstosxringbuffer.h +++ b/sys/osxaudio/gstosxringbuffer.h @@ -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__ */ + |