/* * GStreamer * Copyright (C) 2012-2013 Fluendo S.A. * Authors: Josep Torra Vallès * Andoni Morales Alastruey * * 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]); } }