diff options
Diffstat (limited to 'sys/osxaudio/gstosxcoreaudiocommon.c')
-rw-r--r-- | sys/osxaudio/gstosxcoreaudiocommon.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/sys/osxaudio/gstosxcoreaudiocommon.c b/sys/osxaudio/gstosxcoreaudiocommon.c new file mode 100644 index 000000000..5200b2ea1 --- /dev/null +++ b/sys/osxaudio/gstosxcoreaudiocommon.c @@ -0,0 +1,431 @@ +/* + * GStreamer + * Copyright (C) 2012-2013 Fluendo S.A. <support@fluendo.com> + * Authors: Josep Torra Vallès <josep@fluendo.com> + * Andoni Morales Alastruey <amorales@fluendo.com> + * + * 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 "gstosxcoreaudiocommon.h" + +void +gst_core_audio_remove_render_callback (GstCoreAudio * core_audio) +{ + AURenderCallbackStruct input; + OSStatus status; + + /* Deactivate the render callback by calling SetRenderCallback + * with a NULL inputProc. + */ + input.inputProc = NULL; + input.inputProcRefCon = NULL; + + status = AudioUnitSetProperty (core_audio->audiounit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, /* N/A for global */ + &input, sizeof (input)); + + if (status) { + GST_WARNING_OBJECT (core_audio->osxbuf, "Failed to remove render callback %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + } + + /* Remove the RenderNotify too */ + status = AudioUnitRemoveRenderNotify (core_audio->audiounit, + (AURenderCallback) gst_core_audio_render_notify, core_audio); + + if (status) { + GST_WARNING_OBJECT (core_audio->osxbuf, + "Failed to remove render notify callback %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (status)); + } + + /* We're deactivated.. */ + core_audio->io_proc_needs_deactivation = FALSE; + core_audio->io_proc_active = FALSE; +} + +OSStatus +gst_core_audio_render_notify (GstCoreAudio * core_audio, + 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 (core_audio->io_proc_needs_deactivation) { + gst_core_audio_remove_render_callback (core_audio); + } + } + + return noErr; +} + +gboolean +gst_core_audio_io_proc_start (GstCoreAudio * core_audio) +{ + OSStatus status; + AURenderCallbackStruct input; + AudioUnitPropertyID callback_type; + + GST_DEBUG_OBJECT (core_audio->osxbuf, + "osx ring buffer start ioproc: %p device_id %lu", + core_audio->element->io_proc, (gulong) core_audio->device_id); + if (!core_audio->io_proc_active) { + callback_type = core_audio->is_src ? + kAudioOutputUnitProperty_SetInputCallback : + kAudioUnitProperty_SetRenderCallback; + + input.inputProc = (AURenderCallback) core_audio->element->io_proc; + input.inputProcRefCon = core_audio->osxbuf; + + status = AudioUnitSetProperty (core_audio->audiounit, callback_type, kAudioUnitScope_Global, 0, /* N/A for global */ + &input, sizeof (input)); + + if (status) { + GST_ERROR_OBJECT (core_audio->osxbuf, + "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 (core_audio->audiounit, + (AURenderCallback) gst_core_audio_render_notify, core_audio); + + if (status) { + GST_ERROR_OBJECT (core_audio->osxbuf, + "AudioUnitAddRenderNotify failed %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + return FALSE; + } + core_audio->io_proc_active = TRUE; + } + + core_audio->io_proc_needs_deactivation = FALSE; + + status = AudioOutputUnitStart (core_audio->audiounit); + if (status) { + GST_ERROR_OBJECT (core_audio->osxbuf, "AudioOutputUnitStart failed: %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + return FALSE; + } + return TRUE; +} + +gboolean +gst_core_audio_io_proc_stop (GstCoreAudio * core_audio) +{ + OSErr status; + + GST_DEBUG_OBJECT (core_audio->osxbuf, + "osx ring buffer stop ioproc: %p device_id %lu", + core_audio->element->io_proc, (gulong) core_audio->device_id); + + status = AudioOutputUnitStop (core_audio->audiounit); + if (status) { + GST_WARNING_OBJECT (core_audio->osxbuf, + "AudioOutputUnitStop failed: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (status)); + } + // ###: why is it okay to directly remove from here but not from pause() ? + if (core_audio->io_proc_active) { + gst_core_audio_remove_render_callback (core_audio); + } + return TRUE; +} + +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; +} + +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); +} + +gboolean +gst_core_audio_bind_device (GstCoreAudio * core_audio) +{ + OSStatus status; + + /* Specify which device we're using. */ + GST_DEBUG_OBJECT (core_audio->osxbuf, "Bind AudioUnit to device %d", + (int) core_audio->device_id); + status = AudioUnitSetProperty (core_audio->audiounit, + kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, + &core_audio->device_id, sizeof (AudioDeviceID)); + if (status) { + GST_ERROR_OBJECT (core_audio->osxbuf, "Failed binding to device: %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + goto audiounit_error; + } + return TRUE; + +audiounit_error: + if (core_audio->recBufferList) { + buffer_list_free (core_audio->recBufferList); + core_audio->recBufferList = NULL; + } + return FALSE; +} + +gboolean +gst_core_audio_set_channels_layout (GstCoreAudio * core_audio, + gint channels, GstCaps * caps) +{ + /* Configure the output stream and allocate ringbuffer memory */ + AudioChannelLayout *layout = NULL; + OSStatus status; + int layoutSize, element, i; + AudioUnitScope scope; + GstStructure *structure; + GstAudioChannelPosition *positions; + + /* 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; + } + + scope = core_audio->is_src ? kAudioUnitScope_Output : kAudioUnitScope_Input; + element = core_audio->is_src ? 1 : 0; + + if (layoutSize) { + status = AudioUnitSetProperty (core_audio->audiounit, + kAudioUnitProperty_AudioChannelLayout, + scope, element, layout, layoutSize); + if (status) { + GST_WARNING_OBJECT (core_audio->osxbuf, + "Failed to set output channel layout: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (status)); + return FALSE; + } + } + + g_free (layout); + return TRUE; +} + +gboolean +gst_core_audio_set_format (GstCoreAudio * core_audio, + AudioStreamBasicDescription format) +{ + /* Configure the output stream and allocate ringbuffer memory */ + OSStatus status; + UInt32 propertySize; + int element; + AudioUnitScope scope; + + GST_DEBUG_OBJECT (core_audio->osxbuf, "Setting format for AudioUnit"); + + scope = core_audio->is_src ? kAudioUnitScope_Output : kAudioUnitScope_Input; + element = core_audio->is_src ? 1 : 0; + + propertySize = sizeof (AudioStreamBasicDescription); + status = AudioUnitSetProperty (core_audio->audiounit, + kAudioUnitProperty_StreamFormat, scope, element, &format, propertySize); + + if (status) { + GST_WARNING_OBJECT (core_audio->osxbuf, + "Failed to set audio description: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (status)); + return FALSE;; + } + + return TRUE; +} + +gboolean +gst_core_audio_open_device (GstCoreAudio * core_audio, OSType sub_type, + const gchar * adesc) +{ + AudioComponentDescription desc; + AudioComponent comp; + OSStatus status; + AudioUnit unit; + UInt32 enableIO; + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = sub_type; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = AudioComponentFindNext (NULL, &desc); + + if (comp == NULL) { + GST_WARNING_OBJECT (core_audio->osxbuf, "Couldn't find %s component", + adesc); + return FALSE; + } + + status = AudioComponentInstanceNew (comp, &unit); + + if (status) { + GST_ERROR_OBJECT (core_audio->osxbuf, "Couldn't open %s component %" + GST_FOURCC_FORMAT, adesc, GST_FOURCC_ARGS (status)); + return FALSE; + } + + if (core_audio->is_src) { + /* enable input */ + enableIO = 1; + status = AudioUnitSetProperty (unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, /* 1 = input element */ + &enableIO, sizeof (enableIO)); + + if (status) { + AudioComponentInstanceDispose (unit); + GST_WARNING_OBJECT (core_audio->osxbuf, "Failed to enable input: %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + return FALSE; + } + + /* disable output */ + enableIO = 0; + status = AudioUnitSetProperty (unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, /* 0 = output element */ + &enableIO, sizeof (enableIO)); + + if (status) { + AudioComponentInstanceDispose (unit); + GST_WARNING_OBJECT (core_audio->osxbuf, "Failed to disable output: %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (status)); + return FALSE; + } + } + + GST_DEBUG_OBJECT (core_audio->osxbuf, "Created %s AudioUnit: %p", adesc, + unit); + core_audio->audiounit = unit; + return TRUE; +} + +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; + } +} + +void +gst_core_audio_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]); + } +} |