gstreamer/sys/osxaudio/gstosxcoreaudiocommon.c
Ilya Konstantinov 8ca40fa86f osxaudiosrc: iOS resampling causes stuttering
Fixes stuttering audio when iOS AU is resampling. To make AU resample,
one has to request a rate that differs from AVAudioSession's
sampleRate. The resampling itself is not the culprit, but rather our
API misuse.

AudioUnitRender modifies the mDataByteSize members with the
actual read bytes count. Therefore, they must be reinitialized
before each AudioUnitRender. (The buffers themselves can be
preallocated.)

The "stutter" was caused by one AudioUnitRender making the buffer
too small for other AudioUnitRender invocations, making them fail
with -50 (paramErr). By way of luck, when AU didn't resample, all
AudioUnitRender invocations read the same number of bytes.

(This patch addresses some non-interleaved audio concerns, but
at this moment the elements do not support non-interleaved audio
and non-interleaved is untested.)

https://bugzilla.gnome.org/show_bug.cgi?id=744922
2015-02-24 16:21:11 +05:30

435 lines
13 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;
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;
}
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 = NULL;
guint64 channel_mask;
/* Describe channels */
layoutSize = sizeof (AudioChannelLayout) +
channels * sizeof (AudioChannelDescription);
layout = g_malloc (layoutSize);
structure = gst_caps_get_structure (caps, 0);
if (gst_structure_get (structure, "channel-mask", GST_TYPE_BITMASK,
&channel_mask, NULL)) {
positions = g_new (GstAudioChannelPosition, channels);
gst_audio_channel_positions_from_mask (channels, channel_mask, positions);
}
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: %d", (int) 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: %d", (int) 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 %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_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_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_LFE1:
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]);
}
}