From 59191412ebfa7e64b74f424657657a33fe70d3be Mon Sep 17 00:00:00 2001 From: Josep Torra Date: Tue, 12 Jun 2012 12:42:31 +0200 Subject: [PATCH] osxaudiosink: Add support for SPDIF output A big refactoring to allow passthrough AC3/DTS over SPDIF. Several random cleanups and minor fixes. --- sys/osxaudio/Makefile.am | 3 +- sys/osxaudio/gstosxaudiosink.c | 333 +++++++- sys/osxaudio/gstosxaudiosink.h | 5 +- sys/osxaudio/gstosxcoreaudio.h | 576 +++++++++++++ sys/osxaudio/gstosxringbuffer.c | 1370 +++++++++++++++++++++---------- sys/osxaudio/gstosxringbuffer.h | 26 +- 6 files changed, 1858 insertions(+), 455 deletions(-) create mode 100644 sys/osxaudio/gstosxcoreaudio.h diff --git a/sys/osxaudio/Makefile.am b/sys/osxaudio/Makefile.am index 8c1c3f749b..6c168a92d2 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 639f49915c..aee25c9e9f 100644 --- a/sys/osxaudio/gstosxaudiosink.c +++ b/sys/osxaudio/gstosxaudiosink.c @@ -2,6 +2,7 @@ * GStreamer * Copyright (C) 2005,2006 Zaheer Abbas Merali * Copyright (C) 2007,2008 Pioneers of the Inevitable + * Copyright (C) 2012 Fluendo S.A. * * 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_DEBUG_CATEGORY_STATIC (osx_audiosink_debug); #define GST_CAT_DEFAULT osx_audiosink_debug +#include "gstosxcoreaudio.h" + /* Filter signals and args */ enum { @@ -126,7 +131,9 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", "signed = (boolean) { TRUE }, " "width = (int) 8, " "depth = (int) 8, " - "rate = (int) [1, MAX], " "channels = (int) [1, MAX]") + "rate = (int) [1, MAX], " "channels = (int) [1, MAX];" + "audio/x-ac3, framed = (boolean) true;" + "audio/x-dts, framed = (boolean) true") ); static void gst_osx_audio_sink_set_property (GObject * object, guint prop_id, @@ -134,11 +141,17 @@ static void gst_osx_audio_sink_set_property (GObject * object, guint prop_id, static void gst_osx_audio_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); +static gboolean gst_osx_audio_sink_stop (GstBaseSink * base); +static GstCaps *gst_osx_audio_sink_getcaps (GstBaseSink * base); +static gboolean gst_osx_audio_sink_acceptcaps (GstPad * pad, GstCaps * caps); + +static GstBuffer *gst_osx_audio_sink_sink_payload (GstBaseAudioSink * sink, + GstBuffer * buf); static GstRingBuffer *gst_osx_audio_sink_create_ringbuffer (GstBaseAudioSink * sink); static void gst_osx_audio_sink_osxelement_init (gpointer g_iface, gpointer iface_data); -static void gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink); +static gboolean gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink); static void gst_osx_audio_sink_set_volume (GstOsxAudioSink * sink); static OSStatus gst_osx_audio_sink_io_proc (GstOsxRingBuffer * buf, @@ -205,8 +218,13 @@ gst_osx_audio_sink_class_init (GstOsxAudioSinkClass * klass) g_param_spec_double ("volume", "Volume", "Volume of this stream", 0, 1.0, 1.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_osx_audio_sink_getcaps); + gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_osx_audio_sink_stop); + gstbaseaudiosink_class->create_ringbuffer = GST_DEBUG_FUNCPTR (gst_osx_audio_sink_create_ringbuffer); + gstbaseaudiosink_class->payload = + GST_DEBUG_FUNCPTR (gst_osx_audio_sink_sink_payload); } static void @@ -215,7 +233,12 @@ gst_osx_audio_sink_init (GstOsxAudioSink * sink, GstOsxAudioSinkClass * gclass) GST_DEBUG ("Initialising object"); sink->device_id = kAudioDeviceUnknown; + sink->cached_caps = NULL; + sink->volume = DEFAULT_VOLUME; + + gst_pad_set_acceptcaps_function (GST_BASE_SINK (sink)->sinkpad, + GST_DEBUG_FUNCPTR (gst_osx_audio_sink_acceptcaps)); } static void @@ -256,6 +279,178 @@ gst_osx_audio_sink_get_property (GObject * object, guint prop_id, } } +static gboolean +gst_osx_audio_sink_stop (GstBaseSink * base) +{ + GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (base); + + if (sink->cached_caps) { + gst_caps_unref (sink->cached_caps); + sink->cached_caps = NULL; + } + + return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SINK_CLASS, stop, (base), TRUE); +} + +static GstCaps * +gst_osx_audio_sink_getcaps (GstBaseSink * base) +{ + GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (base); + GstOsxRingBuffer *osxbuf; + GstElementClass *element_class; + GstPadTemplate *pad_template; + GstCaps *caps; + gchar *caps_string = NULL; + + osxbuf = GST_OSX_RING_BUFFER (GST_BASE_AUDIO_SINK (sink)->ringbuffer); + + if (!osxbuf) { + GST_DEBUG_OBJECT (sink, "device not open, using template caps"); + return NULL; /* base class will get template caps for us */ + } + + if (sink->cached_caps) { + caps_string = gst_caps_to_string (sink->cached_caps); + GST_DEBUG_OBJECT (sink, "using cached caps: %s", caps_string); + g_free (caps_string); + return gst_caps_ref (sink->cached_caps); + } + + element_class = GST_ELEMENT_GET_CLASS (sink); + pad_template = gst_element_class_get_pad_template (element_class, "sink"); + g_return_val_if_fail (pad_template != NULL, NULL); + + caps = gst_caps_copy (gst_pad_template_get_caps (pad_template)); + + if (caps) { + if (!osxbuf->is_spdif_capable) { + GstCaps *sub_caps, *orig_caps = caps; + + sub_caps = gst_caps_from_string ("audio/x-ac3;audio/x-dts"); + caps = gst_caps_subtract (orig_caps, sub_caps); + gst_caps_unref (sub_caps); + gst_caps_unref (orig_caps); + } + sink->cached_caps = gst_caps_ref (caps); + caps_string = gst_caps_to_string (caps); + GST_DEBUG_OBJECT (sink, "cached caps: %s", caps_string); + g_free (caps_string); + } + + return caps; +} + +static gboolean +gst_osx_audio_sink_acceptcaps (GstPad * pad, GstCaps * caps) +{ + GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (gst_pad_get_parent_element (pad)); + GstOsxRingBuffer *osxbuf; + GstCaps *pad_caps; + GstStructure *st; + gboolean ret = FALSE; + GstRingBufferSpec spec = { 0 }; + gchar *caps_string = NULL; + + osxbuf = GST_OSX_RING_BUFFER (GST_BASE_AUDIO_SINK (sink)->ringbuffer); + + caps_string = gst_caps_to_string (caps); + GST_DEBUG_OBJECT (sink, "acceptcaps called with %s", caps_string); + g_free (caps_string); + + pad_caps = gst_pad_get_caps_reffed (pad); + if (pad_caps) { + gboolean cret = gst_caps_can_intersect (pad_caps, caps); + gst_caps_unref (pad_caps); + if (!cret) + goto done; + } + + /* If we've not got fixed caps, creating a stream might fail, + * so let's just return from here with default acceptcaps + * behaviour */ + if (!gst_caps_is_fixed (caps)) + goto done; + + /* parse helper expects this set, so avoid nasty warning + * will be set properly later on anyway */ + spec.latency_time = GST_SECOND; + if (!gst_ring_buffer_parse_caps (&spec, caps)) + goto done; + + /* Make sure input is framed and can be payloaded */ + switch (spec.type) { + case GST_BUFTYPE_AC3: + { + gboolean framed = FALSE; + + if (!osxbuf->is_spdif_capable) + goto done; + + st = gst_caps_get_structure (caps, 0); + + gst_structure_get_boolean (st, "framed", &framed); + if (!framed || gst_audio_iec61937_frame_size (&spec) <= 0) + goto done; + break; + } + case GST_BUFTYPE_DTS: + { + gboolean parsed = FALSE; + + if (!osxbuf->is_spdif_capable) + goto done; + + st = gst_caps_get_structure (caps, 0); + + gst_structure_get_boolean (st, "parsed", &parsed); + if (!parsed || gst_audio_iec61937_frame_size (&spec) <= 0) + goto done; + break; + } + default: + break; + } + ret = TRUE; + +done: + gst_object_unref (sink); + return ret; +} + +static GstBuffer * +gst_osx_audio_sink_sink_payload (GstBaseAudioSink * sink, GstBuffer * buf) +{ + GstOsxAudioSink *osxsink; + + osxsink = GST_OSX_AUDIO_SINK (sink); + + if (RINGBUFFER_IS_SPDIF (sink->ringbuffer->spec.type)) { + gint framesize = gst_audio_iec61937_frame_size (&sink->ringbuffer->spec); + GstBuffer *out; + + if (framesize <= 0) + return NULL; + + out = gst_buffer_new_and_alloc (framesize); + + if (!gst_audio_iec61937_payload (GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf), GST_BUFFER_DATA (out), + GST_BUFFER_SIZE (out), &sink->ringbuffer->spec)) { + gst_buffer_unref (out); + return NULL; + } + + gst_buffer_copy_metadata (out, buf, GST_BUFFER_COPY_ALL); + + /* Fix endianness */ + swab ((gchar *) GST_BUFFER_DATA (buf), + (gchar *) GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + return out; + } else { + return gst_buffer_ref (buf); + } +} + static GstRingBuffer * gst_osx_audio_sink_create_ringbuffer (GstBaseAudioSink * sink) { @@ -264,11 +459,13 @@ gst_osx_audio_sink_create_ringbuffer (GstBaseAudioSink * sink) osxsink = GST_OSX_AUDIO_SINK (sink); - gst_osx_audio_sink_select_device (osxsink); + if (!gst_osx_audio_sink_select_device (osxsink)) { + return NULL; + } GST_DEBUG ("Creating ringbuffer"); ringbuffer = g_object_new (GST_TYPE_OSX_RING_BUFFER, NULL); - GST_DEBUG ("osx sink 0x%p element 0x%p ioproc 0x%p", osxsink, + GST_DEBUG ("osx sink %p element %p ioproc %p", osxsink, GST_OSX_AUDIO_ELEMENT_GET_INTERFACE (osxsink), (void *) gst_osx_audio_sink_io_proc); @@ -280,10 +477,10 @@ gst_osx_audio_sink_create_ringbuffer (GstBaseAudioSink * sink) return GST_RING_BUFFER (ringbuffer); } -/* HALOutput AudioUnit will request fairly arbitrarily-sized chunks of data, - * not of a fixed size. So, we keep track of where in the current ringbuffer - * segment we are, and only advance the segment once we've read the whole - * thing */ +/* HALOutput AudioUnit will request fairly arbitrarily-sized chunks + * of data, not of a fixed size. So, we keep track of where in + * the current ringbuffer segment we are, and only advance the segment + * once we've read the whole thing */ static OSStatus gst_osx_audio_sink_io_proc (GstOsxRingBuffer * buf, AudioUnitRenderActionFlags * ioActionFlags, @@ -293,7 +490,8 @@ gst_osx_audio_sink_io_proc (GstOsxRingBuffer * buf, guint8 *readptr; gint readseg; gint len; - gint remaining = bufferList->mBuffers[0].mDataByteSize; + gint stream_idx = buf->stream_idx; + gint remaining = bufferList->mBuffers[stream_idx].mDataByteSize; gint offset = 0; while (remaining) { @@ -306,7 +504,7 @@ gst_osx_audio_sink_io_proc (GstOsxRingBuffer * buf, if (len > remaining) len = remaining; - memcpy ((char *) bufferList->mBuffers[0].mData + offset, + memcpy ((char *) bufferList->mBuffers[stream_idx].mData + offset, readptr + buf->segoffset, len); buf->segoffset += len; @@ -344,34 +542,95 @@ gst_osx_audio_sink_set_volume (GstOsxAudioSink * sink) kAudioUnitScope_Global, 0, (float) sink->volume, 0); } -static void -gst_osx_audio_sink_select_device (GstOsxAudioSink * osxsink) +static inline void +_dump_channel_layout (AudioChannelLayout * channel_layout) { - OSStatus status; - UInt32 propertySize; + UInt32 i; - 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"); - } - - if (osxsink->device_id == kAudioDeviceUnknown) { - GST_WARNING_OBJECT (osxsink, - "AudioHardwareGetProperty: device_id is kAudioDeviceUnknown"); - } - - GST_DEBUG_OBJECT (osxsink, "AudioHardwareGetProperty: device_id is %lu", - (long) osxsink->device_id); + 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) +{ + AudioDeviceID *devices = NULL; + AudioDeviceID default_device_id = 0; + AudioChannelLayout *channel_layout; + gint i, ndevices = 0; + gboolean res = FALSE; + + 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 (); + + /* 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; + } + } + + 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 aac9719f9d..cf94e474e0 100644 --- a/sys/osxaudio/gstosxaudiosink.h +++ b/sys/osxaudio/gstosxaudiosink.h @@ -2,6 +2,7 @@ * GStreamer * Copyright (C) 2005-2006 Zaheer Abbas Merali * Copyright (C) 2007 Pioneers of the Inevitable + * Copyright (C) 2012 Fluendo S.A. * * 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 0000000000..f1d51239fe --- /dev/null +++ b/sys/osxaudio/gstosxcoreaudio.h @@ -0,0 +1,576 @@ +/* + * GStreamer + * Copyright (C) 2012 Fluendo S.A. + * + * 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 cb77162ccc..afeb64498d 100644 --- a/sys/osxaudio/gstosxringbuffer.c +++ b/sys/osxaudio/gstosxringbuffer.c @@ -2,6 +2,7 @@ * GStreamer * Copyright (C) 2006 Zaheer Abbas Merali * Copyright (C) 2008 Pioneers of the Inevitable + * Copyright (C) 2012 Fluendo S.A. * * 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 /* 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,25 +211,38 @@ 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)); + + 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; } - GST_DEBUG_OBJECT (osxbuf, "Create HALOutput AudioUnit: %p", 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; + + /* The following is needed to instruct HAL to create their own + * 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); - if (osxbuf->is_src) { - src = GST_OSX_AUDIO_SRC (GST_OBJECT_PARENT (buf)); - - 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; - } - - src->deviceChannels = asbd_in.mChannelsPerFrame; - } else { - sink = GST_OSX_AUDIO_SINK (GST_OBJECT_PARENT (buf)); - - /* needed for the sink's volume control */ - sink->audiounit = osxbuf->audiounit; + if (!osxbuf->audiounit) { + return FALSE; } return TRUE; @@ -317,360 +322,6 @@ gst_audio_channel_position_to_coreaudio_channel_label (GstAudioChannelPosition } } -static gboolean -gst_osx_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec) -{ - /* Configure the output stream and allocate ringbuffer memory */ - GstOsxRingBuffer *osxbuf; - AudioStreamBasicDescription format; - AudioChannelLayout *layout = NULL; - OSStatus status; - UInt32 propertySize; - 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); - layout = g_malloc (layoutSize); - - structure = gst_caps_get_structure (spec->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++) { - if (positions) { - layout->mChannelDescriptions[i].mChannelLabel = - gst_audio_channel_position_to_coreaudio_channel_label (positions[i], - i); - } else { - /* Discrete channel numbers are ORed into this */ - layout->mChannelDescriptions[i].mChannelLabel = - kAudioChannelLabel_Discrete_0 | i; - } - - /* Others unused */ - layout->mChannelDescriptions[i].mChannelFlags = 0; - layout->mChannelDescriptions[i].mCoordinates[0] = 0.f; - layout->mChannelDescriptions[i].mCoordinates[1] = 0.f; - layout->mChannelDescriptions[i].mCoordinates[2] = 0.f; - } - - if (positions) { - g_free (positions); - 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); - status = AudioUnitSetProperty (osxbuf->audiounit, - kAudioUnitProperty_StreamFormat, scope, element, &format, propertySize); - - if (status) { - GST_WARNING_OBJECT (osxbuf, "Failed to set audio description: %lx", - (gulong) 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; - } - - 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); - status = AudioUnitGetProperty (osxbuf->audiounit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, /* N/A for global */ - &frameSize, &propertySize); - - if (status) { - GST_WARNING_OBJECT (osxbuf, "Failed to get frame size: %lx", - (gulong) status); - goto done; - } - - osxbuf->recBufferList = buffer_list_alloc (format.mChannelsPerFrame, - 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; - - 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_DEBUG_OBJECT (osxbuf, "osx ring buffer acquired"); - - ret = TRUE; - -done: - g_free (layout); - return ret; -} - -static gboolean -gst_osx_ring_buffer_release (GstRingBuffer * buf) -{ - GstOsxRingBuffer *osxbuf; - - osxbuf = GST_OSX_RING_BUFFER (buf); - - AudioUnitUninitialize (osxbuf->audiounit); - - gst_buffer_unref (buf->data); - buf->data = NULL; - - if (osxbuf->recBufferList) { - buffer_list_free (osxbuf->recBufferList); - osxbuf->recBufferList = NULL; - } - - return TRUE; -} - -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. - */ - input.inputProc = NULL; - input.inputProcRefCon = NULL; - - status = AudioUnitSetProperty (osxbuf->audiounit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, /* N/A for global */ - &input, sizeof (input)); - - if (status) { - GST_WARNING_OBJECT (osxbuf, "Failed to remove render callback"); - } - - /* Remove the RenderNotify too */ - status = AudioUnitRemoveRenderNotify (osxbuf->audiounit, - (AURenderCallback) gst_osx_ring_buffer_render_notify, osxbuf); - - if (status) { - GST_WARNING_OBJECT (osxbuf, "Failed to remove render notify callback"); - } - - /* We're deactivated.. */ - osxbuf->io_proc_needs_deactivation = FALSE; - 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) -{ - 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", - osxbuf->element->io_proc, (gulong) osxbuf->device_id); - if (!osxbuf->io_proc_active) { - callback_type = osxbuf->is_src ? - kAudioOutputUnitProperty_SetInputCallback : - kAudioUnitProperty_SetRenderCallback; - - input.inputProc = (AURenderCallback) osxbuf->element->io_proc; - input.inputProcRefCon = osxbuf; - - status = AudioUnitSetProperty (osxbuf->audiounit, callback_type, kAudioUnitScope_Global, 0, /* N/A for global */ - &input, sizeof (input)); - - if (status) { - GST_WARNING ("AudioUnitSetProperty returned %d", (int) status); - return FALSE; - } - // ### does it make sense to do this notify stuff for input mode? - status = AudioUnitAddRenderNotify (osxbuf->audiounit, - (AURenderCallback) gst_osx_ring_buffer_render_notify, osxbuf); - - if (status) { - GST_WARNING ("AudioUnitAddRenderNotify returned %d", (int) status); - return FALSE; - } - - osxbuf->io_proc_active = TRUE; - } - - osxbuf->io_proc_needs_deactivation = FALSE; - - status = AudioOutputUnitStart (osxbuf->audiounit); - if (status) { - GST_WARNING ("AudioOutputUnitStart returned %d", (int) status); - return FALSE; - } - return TRUE; -} - -// ### -static gboolean -gst_osx_ring_buffer_pause (GstRingBuffer * buf) -{ - GstOsxRingBuffer *osxbuf = GST_OSX_RING_BUFFER (buf); - - GST_DEBUG ("osx ring buffer pause ioproc: 0x%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 gboolean -gst_osx_ring_buffer_stop (GstRingBuffer * buf) -{ - OSErr status; - GstOsxRingBuffer *osxbuf; - - osxbuf = GST_OSX_RING_BUFFER (buf); - - GST_DEBUG ("osx ring buffer stop ioproc: 0x%p device_id %lu", - osxbuf->element->io_proc, (gulong) osxbuf->device_id); - - status = AudioOutputUnitStop (osxbuf->audiounit); - if (status) - GST_WARNING ("AudioOutputUnitStop returned %d", (int) status); - - // ###: 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); - } - return TRUE; -} - -static guint -gst_osx_ring_buffer_delay (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; - } - - samples = latency * GST_RING_BUFFER (buf)->spec.rate; - GST_DEBUG_OBJECT (buf, "Got latency: %f seconds -> %d samples", latency, - samples); - return samples; -} - static AudioBufferList * buffer_list_alloc (int channels, int size) { @@ -703,3 +354,896 @@ buffer_list_free (AudioBufferList * list) 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 +_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 */ + AudioChannelLayout *layout = NULL; + OSStatus status; + UInt32 propertySize; + int channels = format.mChannelsPerFrame; + int layoutSize; + int element; + int i; + AudioUnitScope scope; + gboolean ret = FALSE; + GstStructure *structure; + GstAudioChannelPosition *positions; + UInt32 frameSize; + + /* Describe channels */ + layoutSize = sizeof (AudioChannelLayout) + + channels * sizeof (AudioChannelDescription); + layout = g_malloc (layoutSize); + + 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 = channels; + for (i = 0; i < channels; i++) { + if (positions) { + layout->mChannelDescriptions[i].mChannelLabel = + gst_audio_channel_position_to_coreaudio_channel_label (positions[i], + i); + } else { + /* Discrete channel numbers are ORed into this */ + layout->mChannelDescriptions[i].mChannelLabel = + kAudioChannelLabel_Discrete_0 | i; + } + + /* Others unused */ + layout->mChannelDescriptions[i].mChannelFlags = 0; + layout->mChannelDescriptions[i].mCoordinates[0] = 0.f; + layout->mChannelDescriptions[i].mCoordinates[1] = 0.f; + layout->mChannelDescriptions[i].mCoordinates[2] = 0.f; + } + + if (positions) { + g_free (positions); + positions = NULL; + } + + GST_DEBUG_OBJECT (osxbuf, "Setting format for AudioUnit"); + + scope = osxbuf->is_src ? kAudioUnitScope_Output : kAudioUnitScope_Input; + element = osxbuf->is_src ? 1 : 0; + + propertySize = sizeof (AudioStreamBasicDescription); + status = AudioUnitSetProperty (osxbuf->audiounit, + kAudioUnitProperty_StreamFormat, scope, element, &format, propertySize); + + if (status) { + GST_WARNING_OBJECT (osxbuf, + "Failed to set audio description: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (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; + } + } + + /* create AudioBufferList needed for recording */ + if (osxbuf->is_src) { + propertySize = sizeof (frameSize); + status = AudioUnitGetProperty (osxbuf->audiounit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, /* N/A for global */ + &frameSize, &propertySize); + + if (status) { + GST_WARNING_OBJECT (osxbuf, "Failed to get frame size: %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + goto done; + } + + osxbuf->recBufferList = buffer_list_alloc (channels, + frameSize * format.mBytesPerFrame); + } + + /* 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_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"); + + ret = TRUE; + +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 +gst_osx_ring_buffer_release (GstRingBuffer * buf) +{ + GstOsxRingBuffer *osxbuf; + + osxbuf = GST_OSX_RING_BUFFER (buf); + + AudioUnitUninitialize (osxbuf->audiounit); + + gst_buffer_unref (buf->data); + buf->data = NULL; + + if (osxbuf->recBufferList) { + buffer_list_free (osxbuf->recBufferList); + osxbuf->recBufferList = NULL; + } + + 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. + */ + input.inputProc = NULL; + input.inputProcRefCon = NULL; + + status = AudioUnitSetProperty (osxbuf->audiounit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, /* N/A for global */ + &input, sizeof (input)); + + if (status) { + GST_WARNING_OBJECT (osxbuf, "Failed to remove render callback %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + } + + /* Remove the RenderNotify too */ + status = AudioUnitRemoveRenderNotify (osxbuf->audiounit, + (AURenderCallback) gst_osx_ring_buffer_render_notify, osxbuf); + + if (status) { + GST_WARNING_OBJECT (osxbuf, + "Failed to remove render notify callback %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (status)); + } + + /* We're deactivated.. */ + osxbuf->io_proc_needs_deactivation = FALSE; + osxbuf->io_proc_active = FALSE; +} + +static gboolean +gst_osx_ring_buffer_io_proc_start (GstOsxRingBuffer * osxbuf) +{ + OSStatus status; + AURenderCallbackStruct input; + AudioUnitPropertyID callback_type; + + 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 ? + kAudioOutputUnitProperty_SetInputCallback : + kAudioUnitProperty_SetRenderCallback; + + input.inputProc = (AURenderCallback) osxbuf->element->io_proc; + input.inputProcRefCon = osxbuf; + + status = AudioUnitSetProperty (osxbuf->audiounit, callback_type, kAudioUnitScope_Global, 0, /* N/A for global */ + &input, sizeof (input)); + + if (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? + status = AudioUnitAddRenderNotify (osxbuf->audiounit, + (AURenderCallback) gst_osx_ring_buffer_render_notify, osxbuf); + + if (status) { + GST_ERROR ("AudioUnitAddRenderNotify failed %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + return FALSE; + } + + osxbuf->io_proc_active = TRUE; + } + + osxbuf->io_proc_needs_deactivation = FALSE; + + status = AudioOutputUnitStart (osxbuf->audiounit); + if (status) { + GST_ERROR ("AudioOutputUnitStart failed: %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + return FALSE; + } + return TRUE; +} + +static gboolean +gst_osx_ring_buffer_io_proc_stop (GstOsxRingBuffer * osxbuf) +{ + OSErr status; + + 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) { + 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_io_proc_spdif_start (GstOsxRingBuffer * osxbuf) +{ + OSErr status; + + GST_DEBUG ("osx ring buffer start ioproc ID: %p device_id %lu", + osxbuf->procID, (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; + } + + 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); + + if (osxbuf->io_proc_active) { + gst_osx_ring_buffer_remove_render_spdif_callback (osxbuf); + } + + gst_osx_ring_buffer_close_spdif (osxbuf); + + return TRUE; +} + +static gboolean +gst_osx_ring_buffer_start (GstRingBuffer * buf) +{ + GstOsxRingBuffer *osxbuf; + + osxbuf = GST_OSX_RING_BUFFER (buf); + + if (osxbuf->is_passthrough) { + return gst_osx_ring_buffer_io_proc_spdif_start (osxbuf); + } else { + return gst_osx_ring_buffer_io_proc_start (osxbuf); + } +} + +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 gboolean +gst_osx_ring_buffer_stop (GstRingBuffer * buf) +{ + GstOsxRingBuffer *osxbuf; + + osxbuf = GST_OSX_RING_BUFFER (buf); + + if (osxbuf->is_passthrough) { + gst_osx_ring_buffer_io_proc_spdif_stop (osxbuf); + } else { + gst_osx_ring_buffer_io_proc_stop (osxbuf); + } + + return TRUE; +} + +static guint +gst_osx_ring_buffer_delay (GstRingBuffer * buf) +{ + double latency; + UInt32 size = sizeof (double); + GstOsxRingBuffer *osxbuf; + OSStatus status; + guint samples; + + osxbuf = GST_OSX_RING_BUFFER (buf); + + 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 5e6dbe41c5..6365511cf8 100644 --- a/sys/osxaudio/gstosxringbuffer.h +++ b/sys/osxaudio/gstosxringbuffer.h @@ -1,6 +1,7 @@ /* * GStreamer * Copyright (C) 2006 Zaheer Abbas Merali + * Copyright (C) 2012 Fluendo S.A. * * 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__ */ +