summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosep Torra <n770galaxy@gmail.com>2012-06-12 12:42:31 +0200
committerSebastian Dröge <sebastian.droege@collabora.co.uk>2012-06-14 08:45:57 +0200
commit6b4c5a7ce0530693d171324809fbf40b76084276 (patch)
tree7e87e7c65377a793b59a2967d24c49f65a71a64b
parent1954f392c12d46ca4d60aee8fddb4a9df78763f4 (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.am3
-rw-r--r--sys/osxaudio/gstosxaudiosink.c323
-rw-r--r--sys/osxaudio/gstosxaudiosink.h5
-rw-r--r--sys/osxaudio/gstosxcoreaudio.h576
-rw-r--r--sys/osxaudio/gstosxringbuffer.c1000
-rw-r--r--sys/osxaudio/gstosxringbuffer.h26
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__ */
+