gstreamer/sys/osxaudio/gstosxcoreaudio.c

668 lines
20 KiB
C
Raw Normal View History

/*
* 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 "gstosxcoreaudio.h"
#include "gstosxcoreaudiocommon.h"
GST_DEBUG_CATEGORY_STATIC (osx_audio_debug);
#define GST_CAT_DEFAULT osx_audio_debug
G_DEFINE_TYPE (GstCoreAudio, gst_core_audio, G_TYPE_OBJECT);
#ifdef HAVE_IOS
#include "gstosxcoreaudioremoteio.c"
#else
#include "gstosxcoreaudiohal.c"
#endif
static void
gst_core_audio_class_init (GstCoreAudioClass * klass)
{
}
static void
gst_core_audio_init (GstCoreAudio * core_audio)
{
core_audio->is_passthrough = FALSE;
core_audio->device_id = kAudioDeviceUnknown;
core_audio->is_src = FALSE;
core_audio->audiounit = NULL;
core_audio->cached_caps = NULL;
core_audio->cached_caps_valid = FALSE;
#ifndef HAVE_IOS
core_audio->hog_pid = -1;
core_audio->disabled_mixing = FALSE;
#endif
}
static gboolean
_is_outer_scope (AudioUnitScope scope, AudioUnitElement element)
{
return
(scope == kAudioUnitScope_Input && element == 1) ||
(scope == kAudioUnitScope_Output && element == 0);
}
static void
_audio_unit_property_listener (void *inRefCon, AudioUnit inUnit,
AudioUnitPropertyID inID, AudioUnitScope inScope,
AudioUnitElement inElement)
{
GstCoreAudio *core_audio;
core_audio = GST_CORE_AUDIO (inRefCon);
g_assert (inUnit == core_audio->audiounit);
switch (inID) {
case kAudioUnitProperty_AudioChannelLayout:
case kAudioUnitProperty_StreamFormat:
if (_is_outer_scope (inScope, inElement)) {
/* We don't push gst_event_new_caps here (for src),
* nor gst_event_new_reconfigure (for sink), since Core Audio continues
* to happily function with the old format, doing conversion/resampling
* as needed.
* This merely "refreshes" our PREFERRED caps. */
/* This function is called either from a Core Audio thread
* or as a result of a Core Audio API (e.g. AudioUnitInitialize)
* from our own thread. In the latter case, osxbuf can be
* already locked (GStreamer's mutex is not recursive).
* For this reason we use a boolean flag instead of nullifying
* cached_caps. */
core_audio->cached_caps_valid = FALSE;
}
break;
}
}
/**************************
* Public API *
*************************/
GstCoreAudio *
gst_core_audio_new (GstObject * osxbuf)
{
GstCoreAudio *core_audio;
core_audio = g_object_new (GST_TYPE_CORE_AUDIO, NULL);
core_audio->osxbuf = osxbuf;
core_audio->cached_caps = NULL;
return core_audio;
}
gboolean
gst_core_audio_close (GstCoreAudio * core_audio)
{
OSStatus status;
/* Uninitialize the AudioUnit */
status = AudioUnitUninitialize (core_audio->audiounit);
if (status) {
GST_ERROR_OBJECT (core_audio, "Failed to uninitialize AudioUnit: %d",
(int) status);
return FALSE;
}
AudioUnitRemovePropertyListenerWithUserData (core_audio->audiounit,
kAudioUnitProperty_AudioChannelLayout, _audio_unit_property_listener,
core_audio);
AudioUnitRemovePropertyListenerWithUserData (core_audio->audiounit,
kAudioUnitProperty_StreamFormat, _audio_unit_property_listener,
core_audio);
/* core_audio->osxbuf is already locked at this point */
core_audio->cached_caps_valid = FALSE;
gst_caps_replace (&core_audio->cached_caps, NULL);
AudioComponentInstanceDispose (core_audio->audiounit);
core_audio->audiounit = NULL;
return TRUE;
}
gboolean
gst_core_audio_open (GstCoreAudio * core_audio)
{
OSStatus status;
/* core_audio->osxbuf is already locked at this point */
core_audio->cached_caps_valid = FALSE;
gst_caps_replace (&core_audio->cached_caps, NULL);
if (!gst_core_audio_open_impl (core_audio))
return FALSE;
/* Add property listener */
status = AudioUnitAddPropertyListener (core_audio->audiounit,
kAudioUnitProperty_AudioChannelLayout, _audio_unit_property_listener,
core_audio);
if (status != noErr) {
GST_ERROR_OBJECT (core_audio, "Failed to add audio channel layout property "
"listener for AudioUnit: %d", (int) status);
}
status = AudioUnitAddPropertyListener (core_audio->audiounit,
kAudioUnitProperty_StreamFormat, _audio_unit_property_listener,
core_audio);
if (status != noErr) {
GST_ERROR_OBJECT (core_audio, "Failed to add stream format property "
"listener for AudioUnit: %d", (int) status);
}
/* Initialize the AudioUnit. We keep the audio unit initialized early so that
* we can probe the underlying device. */
status = AudioUnitInitialize (core_audio->audiounit);
if (status) {
GST_ERROR_OBJECT (core_audio, "Failed to initialize AudioUnit: %d",
(int) status);
return FALSE;
}
return TRUE;
}
gboolean
gst_core_audio_start_processing (GstCoreAudio * core_audio)
{
return gst_core_audio_start_processing_impl (core_audio);
}
gboolean
gst_core_audio_pause_processing (GstCoreAudio * core_audio)
{
return gst_core_audio_pause_processing_impl (core_audio);
}
gboolean
gst_core_audio_stop_processing (GstCoreAudio * core_audio)
{
return gst_core_audio_stop_processing_impl (core_audio);
}
gboolean
gst_core_audio_get_samples_and_latency (GstCoreAudio * core_audio,
gdouble rate, guint * samples, gdouble * latency)
{
return gst_core_audio_get_samples_and_latency_impl (core_audio, rate,
samples, latency);
}
gboolean
gst_core_audio_initialize (GstCoreAudio * core_audio,
AudioStreamBasicDescription format, GstCaps * caps, gboolean is_passthrough)
{
guint32 frame_size;
GST_DEBUG_OBJECT (core_audio,
"Initializing: passthrough:%d caps:%" GST_PTR_FORMAT, is_passthrough,
caps);
if (!gst_core_audio_initialize_impl (core_audio, format, caps,
is_passthrough, &frame_size)) {
return FALSE;
}
if (core_audio->is_src) {
/* create AudioBufferList needed for recording */
core_audio->recBufferSize = frame_size * format.mBytesPerFrame;
core_audio->recBufferList =
buffer_list_alloc (format.mChannelsPerFrame, core_audio->recBufferSize,
/* Currently always TRUE (i.e. interleaved) */
!(format.mFormatFlags & kAudioFormatFlagIsNonInterleaved));
}
return TRUE;
}
void
gst_core_audio_uninitialize (GstCoreAudio * core_audio)
{
buffer_list_free (core_audio->recBufferList);
core_audio->recBufferList = NULL;
}
void
gst_core_audio_set_volume (GstCoreAudio * core_audio, gfloat volume)
{
AudioUnitSetParameter (core_audio->audiounit, kHALOutputParam_Volume,
kAudioUnitScope_Global, 0, (float) volume, 0);
}
gboolean
gst_core_audio_select_device (GstCoreAudio * core_audio)
{
return gst_core_audio_select_device_impl (core_audio);
}
void
gst_core_audio_init_debug (void)
{
GST_DEBUG_CATEGORY_INIT (osx_audio_debug, "osxaudio", 0,
"OSX Audio Elements");
}
gboolean
gst_core_audio_audio_device_is_spdif_avail (AudioDeviceID device_id)
{
return gst_core_audio_audio_device_is_spdif_avail_impl (device_id);
}
/* Does the channel have at least one positioned channel?
* (GStreamer is more strict than Core Audio, in that it requires either
* all channels to be positioned, or all unpositioned.) */
static gboolean
_is_core_audio_layout_positioned (AudioChannelLayout * layout)
{
guint i;
g_assert (layout->mChannelLayoutTag ==
kAudioChannelLayoutTag_UseChannelDescriptions);
for (i = 0; i < layout->mNumberChannelDescriptions; ++i) {
GstAudioChannelPosition p =
gst_core_audio_channel_label_to_gst
(layout->mChannelDescriptions[i].mChannelLabel, i, FALSE);
if (p >= 0) /* not special positition */
return TRUE;
}
return FALSE;
}
static void
_core_audio_parse_channel_descriptions (AudioChannelLayout * layout,
guint * channels, guint64 * channel_mask, GstAudioChannelPosition * pos)
{
gboolean positioned;
guint i;
g_assert (layout->mChannelLayoutTag ==
kAudioChannelLayoutTag_UseChannelDescriptions);
positioned = _is_core_audio_layout_positioned (layout);
*channel_mask = 0;
/* Go over all labels, either taking only positioned or only
* unpositioned channels, up to GST_OSX_AUDIO_MAX_CHANNEL channels.
*
* The resulting 'pos' array will contain either:
* - only regular (>= 0) positions
* - only GST_AUDIO_CHANNEL_POSITION_NONE positions
* in a compact form, skipping over all unsupported positions.
*/
*channels = 0;
for (i = 0; i < layout->mNumberChannelDescriptions; ++i) {
GstAudioChannelPosition p =
gst_core_audio_channel_label_to_gst
(layout->mChannelDescriptions[i].mChannelLabel, i, TRUE);
/* In positioned layouts, skip all unpositioned channels.
* In unpositioned layouts, skip all invalid channels. */
if ((positioned && p >= 0) ||
(!positioned && p == GST_AUDIO_CHANNEL_POSITION_NONE)) {
if (pos)
pos[*channels] = p;
*channel_mask |= G_GUINT64_CONSTANT (1) << p;
++(*channels);
if (*channels == GST_OSX_AUDIO_MAX_CHANNEL)
break; /* not to overflow */
}
}
}
gboolean
gst_core_audio_parse_channel_layout (AudioChannelLayout * layout,
guint * channels, guint64 * channel_mask, GstAudioChannelPosition * pos)
{
g_assert (channels != NULL);
g_assert (channel_mask != NULL);
g_assert (layout != NULL);
if (layout->mChannelLayoutTag !=
kAudioChannelLayoutTag_UseChannelDescriptions) {
GST_ERROR
("Only kAudioChannelLayoutTag_UseChannelDescriptions is supported.");
*channels = 0;
*channel_mask = 0;
return FALSE;
}
switch (layout->mNumberChannelDescriptions) {
case 0:
if (pos)
pos[0] = GST_AUDIO_CHANNEL_POSITION_NONE;
*channels = 0;
*channel_mask = 0;
return TRUE;
case 1:
if (pos)
pos[0] = GST_AUDIO_CHANNEL_POSITION_MONO;
*channels = 1;
*channel_mask = 0;
return TRUE;
case 2:
if (pos) {
pos[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
pos[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
}
*channels = 2;
*channel_mask =
GST_AUDIO_CHANNEL_POSITION_MASK (FRONT_LEFT) |
GST_AUDIO_CHANNEL_POSITION_MASK (FRONT_RIGHT);
return TRUE;
default:
_core_audio_parse_channel_descriptions (layout, channels, channel_mask,
pos);
return TRUE;
}
}
/* Converts an AudioStreamBasicDescription to preferred caps.
*
* These caps will indicate the AU element's canonical format, which won't
* make Core Audio resample nor convert.
*
* NOTE ON MULTI-CHANNEL AUDIO:
*
* If layout is not NULL, resulting caps will only include the subset
* of channels supported by GStreamer. If the Core Audio layout contained
* ANY positioned channels, then ONLY positioned channels will be included
* in the resulting caps. Otherwise, resulting caps will be unpositioned,
* and include only unpositioned channels.
* (Channels with unsupported AudioChannelLabel will be skipped either way.)
*
* Naturally, the number of channels indicated by 'channels' can be lower
* than the AU element's total number of channels.
*/
GstCaps *
gst_core_audio_asbd_to_caps (AudioStreamBasicDescription * asbd,
AudioChannelLayout * layout)
{
GstAudioInfo info;
GstAudioFormat format = GST_AUDIO_FORMAT_UNKNOWN;
guint rate, channels, bps, endianness;
guint64 channel_mask;
gboolean sign, interleaved;
if (asbd->mFormatID != kAudioFormatLinearPCM) {
GST_WARNING ("Only linear PCM is supported");
goto error;
}
if (!(asbd->mFormatFlags & kAudioFormatFlagIsPacked)) {
GST_WARNING ("Only packed formats supported");
goto error;
}
if (asbd->mFormatFlags & kLinearPCMFormatFlagsSampleFractionMask) {
GST_WARNING ("Fixed point audio is unsupported");
goto error;
}
rate = asbd->mSampleRate;
if (rate == kAudioStreamAnyRate) {
GST_WARNING ("No sample rate");
goto error;
}
bps = asbd->mBitsPerChannel;
endianness = asbd->mFormatFlags & kAudioFormatFlagIsBigEndian ?
G_BIG_ENDIAN : G_LITTLE_ENDIAN;
sign = asbd->mFormatID & kAudioFormatFlagIsSignedInteger ? TRUE : FALSE;
interleaved = asbd->mFormatFlags & kAudioFormatFlagIsNonInterleaved ?
TRUE : FALSE;
if (asbd->mFormatFlags & kAudioFormatFlagIsFloat) {
if (bps == 32) {
if (endianness == G_LITTLE_ENDIAN)
format = GST_AUDIO_FORMAT_F32LE;
else
format = GST_AUDIO_FORMAT_F32BE;
} else if (bps == 64) {
if (endianness == G_LITTLE_ENDIAN)
format = GST_AUDIO_FORMAT_F64LE;
else
format = GST_AUDIO_FORMAT_F64BE;
}
} else {
format = gst_audio_format_build_integer (sign, endianness, bps, bps);
}
if (format == GST_AUDIO_FORMAT_UNKNOWN) {
GST_WARNING ("Unsupported sample format");
goto error;
}
if (layout) {
GstAudioChannelPosition pos[GST_OSX_AUDIO_MAX_CHANNEL];
if (!gst_core_audio_parse_channel_layout (layout, &channels, &channel_mask,
pos)) {
GST_WARNING ("Failed to parse channel layout");
goto error;
}
/* The AU can have arbitrary channel order, but we're using GstAudioInfo
* which supports only the GStreamer channel order.
* Also, we're eventually producing caps, which only have channel-mask
* (whose implied order is the GStreamer channel order). */
gst_audio_channel_positions_to_valid_order (pos, channels);
gst_audio_info_set_format (&info, format, rate, channels, pos);
} else {
channels = MIN (asbd->mChannelsPerFrame, GST_OSX_AUDIO_MAX_CHANNEL);
gst_audio_info_set_format (&info, format, rate, channels, NULL);
}
return gst_audio_info_to_caps (&info);
error:
return NULL;
}
static gboolean
_core_audio_get_property (GstCoreAudio * core_audio, gboolean outer,
AudioUnitPropertyID inID, void *inData, UInt32 * inDataSize)
{
OSStatus status;
AudioUnitScope scope;
AudioUnitElement element;
scope = outer ?
CORE_AUDIO_OUTER_SCOPE (core_audio) : CORE_AUDIO_INNER_SCOPE (core_audio);
element = CORE_AUDIO_ELEMENT (core_audio);
status =
AudioUnitGetProperty (core_audio->audiounit, inID, scope, element, inData,
inDataSize);
return status == noErr;
}
static gboolean
_core_audio_get_stream_format (GstCoreAudio * core_audio,
AudioStreamBasicDescription * asbd, gboolean outer)
{
UInt32 size;
size = sizeof (AudioStreamBasicDescription);
return _core_audio_get_property (core_audio, outer,
kAudioUnitProperty_StreamFormat, asbd, &size);
}
AudioChannelLayout *
gst_core_audio_get_channel_layout (GstCoreAudio * core_audio, gboolean outer)
{
UInt32 size;
AudioChannelLayout *layout;
if (core_audio->is_src) {
GST_WARNING_OBJECT (core_audio,
"gst_core_audio_get_channel_layout not supported on source.");
return NULL;
}
if (!_core_audio_get_property (core_audio, outer,
kAudioUnitProperty_AudioChannelLayout, NULL, &size)) {
GST_WARNING_OBJECT (core_audio, "unable to get channel layout");
return NULL;
}
layout = g_malloc (size);
if (!_core_audio_get_property (core_audio, outer,
kAudioUnitProperty_AudioChannelLayout, layout, &size)) {
GST_WARNING_OBJECT (core_audio, "unable to get channel layout");
g_free (layout);
return NULL;
}
return layout;
}
#define STEREO_CHANNEL_MASK \
(GST_AUDIO_CHANNEL_POSITION_MASK (FRONT_LEFT) | \
GST_AUDIO_CHANNEL_POSITION_MASK (FRONT_RIGHT))
GstCaps *
gst_core_audio_probe_caps (GstCoreAudio * core_audio, GstCaps * in_caps)
{
guint i, channels;
gboolean spdif_allowed;
AudioChannelLayout *layout;
AudioStreamBasicDescription outer_asbd;
gboolean got_outer_asbd;
GstCaps *caps = NULL;
guint64 channel_mask;
/* Get the ASBD of the outer scope (i.e. input scope of Input,
* output scope of Output).
* This ASBD indicates the hardware format. */
got_outer_asbd =
_core_audio_get_stream_format (core_audio, &outer_asbd, TRUE);
/* Collect info about the HW capabilites and preferences */
spdif_allowed =
gst_core_audio_audio_device_is_spdif_avail (core_audio->device_id);
if (!core_audio->is_src)
layout = gst_core_audio_get_channel_layout (core_audio, TRUE);
else
layout = NULL; /* no supported for sources */
GST_DEBUG_OBJECT (core_audio, "Selected device ID: %u SPDIF allowed: %d",
(unsigned) core_audio->device_id, spdif_allowed);
if (layout) {
if (!gst_core_audio_parse_channel_layout (layout, &channels, &channel_mask,
NULL)) {
GST_WARNING_OBJECT (core_audio, "Failed to parse channel layout");
channel_mask = 0;
}
/* If available, start with the preferred caps. */
if (got_outer_asbd)
caps = gst_core_audio_asbd_to_caps (&outer_asbd, layout);
g_free (layout);
} else if (got_outer_asbd) {
channels = outer_asbd.mChannelsPerFrame;
channel_mask = 0;
/* If available, start with the preferred caps */
caps = gst_core_audio_asbd_to_caps (&outer_asbd, NULL);
} else {
GST_ERROR_OBJECT (core_audio,
"Unable to get any information about hardware");
return NULL;
}
/* Append the allowed subset based on the template caps */
if (!caps)
caps = gst_caps_new_empty ();
for (i = 0; i < gst_caps_get_size (in_caps); i++) {
GstStructure *in_s;
in_s = gst_caps_get_structure (in_caps, i);
if (gst_structure_has_name (in_s, "audio/x-ac3") ||
gst_structure_has_name (in_s, "audio/x-dts")) {
if (spdif_allowed) {
gst_caps_append_structure (caps, gst_structure_copy (in_s));
}
} else {
GstStructure *out_s;
out_s = gst_structure_copy (in_s);
gst_structure_set (out_s, "channels", G_TYPE_INT, channels, NULL);
if (channel_mask != 0) {
/* positioned layout */
gst_structure_set (out_s,
"channel-mask", GST_TYPE_BITMASK, channel_mask, NULL);
} else {
/* unpositioned layout */
gst_structure_remove_field (out_s, "channel-mask");
}
#ifndef HAVE_IOS
if (core_audio->is_src && got_outer_asbd
&& outer_asbd.mSampleRate != kAudioStreamAnyRate) {
/* According to Core Audio engineer, AUHAL does not support sample rate conversion.
* on sources. Therefore, we fixate the sample rate.
*
* "You definitely cannot do rate conversion as part of getting input from AUHAL.
* That's the most common cause of those "cannot do in current context" errors."
* http://lists.apple.com/archives/coreaudio-api/2006/Sep/msg00088.html
*/
gst_structure_set (out_s, "rate", G_TYPE_INT,
(gint) outer_asbd.mSampleRate, NULL);
}
#endif
/* Special cases for upmixing and downmixing.
* Other than that, the AUs don't upmix or downmix multi-channel audio,
* e.g. if you push 5.1-surround audio to a stereo configuration,
* the left and right channels will be played accordingly,
* and the rest will be dropped. */
if (channels == 1 || (channels == 2 &&
(channel_mask == 0 || channel_mask == STEREO_CHANNEL_MASK))) {
/* If have stereo channels, then also offer mono since CoreAudio
* upmixes it. If mono, then also offer stereo since CoreAudio
* downmixes to it */
gst_structure_set (out_s, "channels", GST_TYPE_INT_RANGE, 1, 2, NULL);
if (channels == 1)
gst_structure_set (out_s, "channel-mask", GST_TYPE_BITMASK,
STEREO_CHANNEL_MASK, NULL);
}
gst_caps_append_structure (caps, out_s);
}
}
return caps;
}