mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-22 08:17:01 +00:00
435 lines
13 KiB
C
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_channel_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]);
|
|
}
|
|
}
|