| /* |
| * 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 %d", (int) 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 %d", (int) 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: %d", (int) 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 %d", (int) 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: %d", |
| (int) 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: %d", (int) 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 (UInt32 channels, UInt32 size, gboolean interleaved) |
| { |
| AudioBufferList *list; |
| gsize list_size; |
| UInt32 num_buffers, n; |
| |
| num_buffers = interleaved ? 1 : channels; |
| /* AudioBufferList member mBuffers is variable-length array */ |
| list_size = G_STRUCT_OFFSET (AudioBufferList, mBuffers[num_buffers]); |
| list = (AudioBufferList *) g_malloc (list_size); |
| |
| list->mNumberBuffers = num_buffers; |
| for (n = 0; n < num_buffers; ++n) { |
| /* See http://lists.apple.com/archives/coreaudio-api/2015/Feb/msg00027.html */ |
| list->mBuffers[n].mNumberChannels = interleaved ? channels : 1; |
| /* AudioUnitRender will keep overwriting mDataByteSize */ |
| list->mBuffers[n].mDataByteSize = size; |
| list->mBuffers[n].mData = g_malloc (size); |
| } |
| |
| return list; |
| } |
| |
| void |
| buffer_list_free (AudioBufferList * list) |
| { |
| UInt32 n; |
| |
| if (list == NULL) |
| return; |
| |
| for (n = 0; n < list->mNumberBuffers; ++n) { |
| 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: %d", |
| (int) status); |
| goto audiounit_error; |
| } |
| return TRUE; |
| |
| audiounit_error: |
| if (core_audio->recBufferList) { |
| buffer_list_free (core_audio->recBufferList); |
| core_audio->recBufferList = NULL; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| _core_audio_set_property (GstCoreAudio * core_audio, AudioUnitPropertyID inID, |
| void *inData, UInt32 inDataSize) |
| { |
| OSStatus status; |
| AudioUnitScope scope; |
| AudioUnitElement element; |
| |
| scope = CORE_AUDIO_INNER_SCOPE (core_audio); |
| element = CORE_AUDIO_ELEMENT (core_audio); |
| |
| status = |
| AudioUnitSetProperty (core_audio->audiounit, inID, scope, element, inData, |
| inDataSize); |
| |
| if (status != noErr) { |
| GST_WARNING_OBJECT (core_audio->osxbuf, |
| "Failed to set Audio Unit property: %d", (int) status); |
| return FALSE;; |
| } |
| |
| return TRUE; |
| } |
| |
| /* The AudioUnit must be uninitialized before calling this */ |
| gboolean |
| gst_core_audio_set_channel_layout (GstCoreAudio * core_audio, |
| gint channels, GstCaps * caps) |
| { |
| AudioChannelLayout *layout = NULL; |
| gboolean ret; |
| gsize layoutSize; |
| gint i; |
| GstStructure *structure; |
| GstAudioChannelPosition positions[GST_OSX_AUDIO_MAX_CHANNEL]; |
| guint64 channel_mask; |
| |
| g_return_val_if_fail (channels <= GST_OSX_AUDIO_MAX_CHANNEL, FALSE); |
| |
| /* Determine the channel positions */ |
| structure = gst_caps_get_structure (caps, 0); |
| channel_mask = 0; |
| gst_structure_get (structure, "channel-mask", GST_TYPE_BITMASK, &channel_mask, |
| NULL); |
| |
| if (channel_mask != 0) |
| gst_audio_channel_positions_from_mask (channels, channel_mask, positions); |
| |
| /* AudioChannelLayout member mChannelDescriptions is variable-length array */ |
| layoutSize = |
| G_STRUCT_OFFSET (AudioChannelLayout, mChannelDescriptions[channels]); |
| layout = g_malloc (layoutSize); |
| |
| /* Fill out the AudioChannelLayout */ |
| layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; |
| layout->mChannelBitmap = 0; /* Not used */ |
| layout->mNumberChannelDescriptions = channels; |
| for (i = 0; i < channels; i++) { |
| if (channel_mask != 0) { |
| layout->mChannelDescriptions[i].mChannelLabel = |
| gst_audio_channel_position_to_core_audio (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 = kAudioChannelFlags_AllOff; |
| layout->mChannelDescriptions[i].mCoordinates[0] = 0.f; |
| layout->mChannelDescriptions[i].mCoordinates[1] = 0.f; |
| layout->mChannelDescriptions[i].mCoordinates[2] = 0.f; |
| } |
| |
| /* Sets GStreamer-ordered channel layout on the inner scope. |
| * Reordering between the inner scope and outer scope is handled |
| * by the Audio Unit itself. */ |
| ret = _core_audio_set_property (core_audio, |
| kAudioUnitProperty_AudioChannelLayout, layout, layoutSize); |
| |
| g_free (layout); |
| return ret; |
| } |
| |
| /* The AudioUnit must be uninitialized before calling this */ |
| gboolean |
| gst_core_audio_set_format (GstCoreAudio * core_audio, |
| AudioStreamBasicDescription format) |
| { |
| GST_DEBUG_OBJECT (core_audio->osxbuf, "Setting format for AudioUnit"); |
| |
| return _core_audio_set_property (core_audio, kAudioUnitProperty_StreamFormat, |
| &format, sizeof (AudioStreamBasicDescription)); |
| } |
| |
| 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 %d", |
| adesc, (int) 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: %d", |
| (int) 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: %d", |
| (int) 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_core_audio (GstAudioChannelPosition |
| position, int channel) |
| { |
| switch (position) { |
| 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_LFE1: |
| return kAudioChannelLabel_LFEScreen; |
| case GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER: |
| return kAudioChannelLabel_Center; |
| case GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT: |
| return kAudioChannelLabel_LeftSurroundDirect; |
| case GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT: |
| return kAudioChannelLabel_RightSurroundDirect; |
| case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: |
| return kAudioChannelLabel_LeftCenter; |
| case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: |
| return kAudioChannelLabel_RightCenter; |
| case GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT: |
| return kAudioChannelLabel_TopBackLeft; |
| case GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER: |
| return kAudioChannelLabel_TopBackCenter; |
| case GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT: |
| return kAudioChannelLabel_TopBackRight; |
| case GST_AUDIO_CHANNEL_POSITION_WIDE_LEFT: |
| return kAudioChannelLabel_LeftWide; |
| case GST_AUDIO_CHANNEL_POSITION_WIDE_RIGHT: |
| return kAudioChannelLabel_RightWide; |
| case GST_AUDIO_CHANNEL_POSITION_LFE2: |
| return kAudioChannelLabel_LFE2; |
| case GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT: |
| return kAudioChannelLabel_VerticalHeightLeft; |
| case GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT: |
| return kAudioChannelLabel_VerticalHeightRight; |
| case GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER: |
| return kAudioChannelLabel_VerticalHeightCenter; |
| |
| /* Special position values */ |
| case GST_AUDIO_CHANNEL_POSITION_NONE: |
| return kAudioChannelLabel_Discrete_0 | channel; |
| case GST_AUDIO_CHANNEL_POSITION_MONO: |
| return kAudioChannelLabel_Mono; |
| |
| /* Following positions are unmapped -- |
| * i.e. mapped to kAudioChannelLabel_Unknown: */ |
| case GST_AUDIO_CHANNEL_POSITION_TOP_CENTER: |
| case GST_AUDIO_CHANNEL_POSITION_TOP_SIDE_LEFT: |
| case GST_AUDIO_CHANNEL_POSITION_TOP_SIDE_RIGHT: |
| case GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_CENTER: |
| case GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_LEFT: |
| case GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_RIGHT: |
| case GST_AUDIO_CHANNEL_POSITION_SURROUND_LEFT: |
| case GST_AUDIO_CHANNEL_POSITION_SURROUND_RIGHT: |
| default: |
| return kAudioChannelLabel_Unknown; |
| } |
| } |
| |
| /* Performs a best-effort conversion. 'channel' is used for warnings only. */ |
| GstAudioChannelPosition |
| gst_core_audio_channel_label_to_gst (AudioChannelLabel label, |
| int channel, gboolean warn) |
| { |
| switch (label) { |
| case kAudioChannelLabel_Left: |
| return GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; |
| case kAudioChannelLabel_Right: |
| return GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; |
| case kAudioChannelLabel_Center: |
| return GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER; |
| case kAudioChannelLabel_LFEScreen: |
| return GST_AUDIO_CHANNEL_POSITION_LFE1; |
| case kAudioChannelLabel_LeftSurround: |
| return GST_AUDIO_CHANNEL_POSITION_REAR_LEFT; |
| case kAudioChannelLabel_RightSurround: |
| return GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT; |
| case kAudioChannelLabel_LeftSurroundDirect: |
| return GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT; |
| case kAudioChannelLabel_RightSurroundDirect: |
| return GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT; |
| case kAudioChannelLabel_CenterSurround: |
| return GST_AUDIO_CHANNEL_POSITION_REAR_CENTER; |
| case kAudioChannelLabel_LeftCenter: |
| return GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; |
| case kAudioChannelLabel_RightCenter: |
| return GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; |
| case kAudioChannelLabel_TopBackLeft: |
| return GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT; |
| case kAudioChannelLabel_TopBackCenter: |
| return GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER; |
| case kAudioChannelLabel_TopBackRight: |
| return GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT; |
| case kAudioChannelLabel_LeftWide: |
| return GST_AUDIO_CHANNEL_POSITION_WIDE_LEFT; |
| case kAudioChannelLabel_RightWide: |
| return GST_AUDIO_CHANNEL_POSITION_WIDE_RIGHT; |
| case kAudioChannelLabel_LFE2: |
| return GST_AUDIO_CHANNEL_POSITION_LFE2; |
| case kAudioChannelLabel_VerticalHeightLeft: |
| return GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT; |
| case kAudioChannelLabel_VerticalHeightRight: |
| return GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT; |
| case kAudioChannelLabel_VerticalHeightCenter: |
| return GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER; |
| |
| /* Special position values */ |
| |
| case kAudioChannelLabel_Mono: |
| /* GST_AUDIO_CHANNEL_POSITION_MONO is only for 1-channel layouts */ |
| return GST_AUDIO_CHANNEL_POSITION_INVALID; |
| case kAudioChannelLabel_Discrete: |
| return GST_AUDIO_CHANNEL_POSITION_NONE; |
| |
| /* |
| Following labels are unmapped -- |
| i.e. mapped to GST_AUDIO_CHANNEL_POSITION_INVALID: |
| */ |
| case kAudioChannelLabel_RearSurroundLeft: |
| case kAudioChannelLabel_RearSurroundRight: |
| case kAudioChannelLabel_TopCenterSurround: |
| case kAudioChannelLabel_LeftTotal: |
| case kAudioChannelLabel_RightTotal: |
| case kAudioChannelLabel_HearingImpaired: |
| case kAudioChannelLabel_Narration: |
| case kAudioChannelLabel_DialogCentricMix: |
| case kAudioChannelLabel_CenterSurroundDirect: |
| case kAudioChannelLabel_Haptic: |
| default: |
| if (label >> 16 != 0) { /* kAudioChannelLabel_Discrete_N */ |
| /* no way to store discrete channel order */ |
| if (warn) |
| GST_WARNING |
| ("Core Audio channel %u labeled kAudioChannelLabel_Discrete_%u -- discrete order will be lost", |
| channel, ((unsigned int) label) & 0xFFFF); |
| return GST_AUDIO_CHANNEL_POSITION_NONE; |
| } else { |
| if (warn) |
| GST_WARNING |
| ("Core Audio channel %u has unsupported label %d and will be skipped", |
| channel, (int) label); |
| return GST_AUDIO_CHANNEL_POSITION_INVALID; |
| } |
| } |
| } |
| |
| 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]); |
| } |
| } |