/* * GStreamer * Copyright (C) 2006 Zaheer Abbas Merali <zaheerabbas at merali dot org> * Copyright (C) 2008 Pioneers of the Inevitable <songbird@songbirdnest.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. */ #include <CoreAudio/CoreAudio.h> #include <gst/gst.h> #include <gst/audio/multichannel.h> #include "gstosxringbuffer.h" #include "gstosxaudiosink.h" #include "gstosxaudiosrc.h" GST_DEBUG_CATEGORY_STATIC (osx_audio_debug); #define GST_CAT_DEFAULT osx_audio_debug 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); static gboolean gst_osx_ring_buffer_close_device (GstRingBuffer * buf); static gboolean gst_osx_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec); static gboolean gst_osx_ring_buffer_release (GstRingBuffer * buf); static gboolean gst_osx_ring_buffer_start (GstRingBuffer * buf); static gboolean gst_osx_ring_buffer_pause (GstRingBuffer * buf); 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_do_init (GType type) { GST_DEBUG_CATEGORY_INIT (osx_audio_debug, "osxaudio", 0, "OSX Audio Elements"); } 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) { /* Nothing to do right now */ } static void gst_osx_ring_buffer_class_init (GstOsxRingBufferClass * klass) { GObjectClass *gobject_class; GstObjectClass *gstobject_class; GstRingBufferClass *gstringbuffer_class; gobject_class = (GObjectClass *) klass; gstobject_class = (GstObjectClass *) klass; gstringbuffer_class = (GstRingBufferClass *) klass; ring_parent_class = g_type_class_peek_parent (klass); gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_dispose); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_finalize); gstringbuffer_class->open_device = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_open_device); gstringbuffer_class->close_device = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_close_device); gstringbuffer_class->acquire = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_acquire); gstringbuffer_class->release = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_release); gstringbuffer_class->start = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_start); gstringbuffer_class->pause = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_pause); gstringbuffer_class->resume = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_start); gstringbuffer_class->stop = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_stop); gstringbuffer_class->delay = GST_DEBUG_FUNCPTR (gst_osx_ring_buffer_delay); GST_DEBUG ("osx ring buffer class init"); } static void gst_osx_ring_buffer_init (GstOsxRingBuffer * ringbuffer, GstOsxRingBufferClass * g_class) { /* Nothing to do right now */ } static void gst_osx_ring_buffer_dispose (GObject * object) { G_OBJECT_CLASS (ring_parent_class)->dispose (object); } static void gst_osx_ring_buffer_finalize (GObject * object) { G_OBJECT_CLASS (ring_parent_class)->finalize (object); } static AudioUnit gst_osx_ring_buffer_create_audio_unit (GstOsxRingBuffer * osxbuf, gboolean input, AudioDeviceID device_id) { ComponentDescription desc; Component comp; OSStatus status; AudioUnit unit; UInt32 enableIO; /* 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) * * 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; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; comp = FindNextComponent (NULL, &desc); if (comp == NULL) { GST_WARNING_OBJECT (osxbuf, "Couldn't find HALOutput component"); return NULL; } status = OpenAComponent (comp, &unit); if (status) { GST_WARNING_OBJECT (osxbuf, "Couldn't open HALOutput component"); return NULL; } if (input) { /* enable input */ enableIO = 1; status = AudioUnitSetProperty (unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, /* 1 = input element */ &enableIO, sizeof (enableIO)); if (status) { CloseComponent (unit); GST_WARNING_OBJECT (osxbuf, "Failed to enable input: %lx", status); return NULL; } /* disable output */ enableIO = 0; status = AudioUnitSetProperty (unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, /* 0 = output element */ &enableIO, sizeof (enableIO)); if (status) { CloseComponent (unit); GST_WARNING_OBJECT (osxbuf, "Failed to disable output: %lx", 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)); if (status) { CloseComponent (unit); GST_WARNING_OBJECT (osxbuf, "Failed to set device: %lx", status); return NULL; } GST_DEBUG_OBJECT (osxbuf, "Create HALOutput AudioUnit: %p", unit); return unit; } 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); 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", 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; } return TRUE; } static gboolean gst_osx_ring_buffer_close_device (GstRingBuffer * buf) { GstOsxRingBuffer *osxbuf; osxbuf = GST_OSX_RING_BUFFER (buf); CloseComponent (osxbuf->audiounit); osxbuf->audiounit = NULL; return TRUE; } static AudioChannelLabel gst_audio_channel_position_to_coreaudio_channel_label (GstAudioChannelPosition position, int channel) { switch (position) { case GST_AUDIO_CHANNEL_POSITION_NONE: return kAudioChannelLabel_Discrete_0 | channel; case GST_AUDIO_CHANNEL_POSITION_FRONT_MONO: return kAudioChannelLabel_Mono; case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT: return kAudioChannelLabel_Left; case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT: return kAudioChannelLabel_Right; case GST_AUDIO_CHANNEL_POSITION_REAR_CENTER: return kAudioChannelLabel_CenterSurround; case GST_AUDIO_CHANNEL_POSITION_REAR_LEFT: return kAudioChannelLabel_LeftSurround; case GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT: return kAudioChannelLabel_RightSurround; case GST_AUDIO_CHANNEL_POSITION_LFE: return kAudioChannelLabel_LFEScreen; case GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER: return kAudioChannelLabel_Center; case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return kAudioChannelLabel_Center; // ??? case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return kAudioChannelLabel_Center; // ??? case GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT: return kAudioChannelLabel_LeftSurroundDirect; case GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT: return kAudioChannelLabel_RightSurroundDirect; default: return kAudioChannelLabel_Unknown; } } 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; 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; format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; format.mBytesPerFrame = spec->channels * sizeof (float); format.mBitsPerChannel = sizeof (float) * 8; format.mBytesPerPacket = spec->channels * sizeof (float); 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", 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", 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", 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, 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, 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, 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) { 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); }