gstreamer/sys/osxaudio/gstosxcoreaudiocommon.c
Arun Raghavan 691ecebe22 osxaudio: Don't set the format on an initialized AudioUnit
We need to initialize the AudioUnit early to be able to probe the
underlying device, but according to the AudioUnitInitialize() and
AudioUnitUninitialize() documentation, format changes should be done
while the AudioUnit is uninitialized. So we explicitly uninitialize the
AudioUnit during a format change and reinitialize it when we're done.
2015-07-14 17:49:50 +05:30

553 lines
18 KiB
C

/*
* 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]);
}
}