/* * 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 /* for getpid */ #include "gstosxaudiosink.h" #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < 120000 #define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster #endif static inline gboolean _audio_system_set_runloop (CFRunLoopRef runLoop) { OSStatus status = noErr; gboolean res = FALSE; AudioObjectPropertyAddress runloopAddress = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; status = AudioObjectSetPropertyData (kAudioObjectSystemObject, &runloopAddress, 0, NULL, sizeof (CFRunLoopRef), &runLoop); if (status == noErr) { res = TRUE; } else { GST_ERROR ("failed to set runloop to %p: %d", runLoop, (int) status); } return res; } static inline AudioDeviceID _audio_system_get_default_device (gboolean output) { OSStatus status = noErr; UInt32 propertySize = sizeof (AudioDeviceID); AudioDeviceID device_id = kAudioDeviceUnknown; AudioObjectPropertySelector prop_selector; prop_selector = output ? kAudioHardwarePropertyDefaultOutputDevice : kAudioHardwarePropertyDefaultInputDevice; AudioObjectPropertyAddress defaultDeviceAddress = { prop_selector, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyData (kAudioObjectSystemObject, &defaultDeviceAddress, 0, NULL, &propertySize, &device_id); if (status != noErr) { GST_ERROR ("failed getting default output device: %d", (int) status); } GST_DEBUG ("Default device id: %u", (unsigned) device_id); return device_id; } static inline AudioDeviceID * _audio_system_get_devices (gint * ndevices) { OSStatus status = noErr; UInt32 propertySize = 0; AudioDeviceID *devices = NULL; AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyDataSize (kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &propertySize); if (status != noErr) { GST_WARNING ("failed getting number of devices: %d", (int) status); return NULL; } *ndevices = propertySize / sizeof (AudioDeviceID); devices = (AudioDeviceID *) g_malloc (propertySize); if (devices) { status = AudioObjectGetPropertyData (kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &propertySize, devices); if (status != noErr) { GST_WARNING ("failed getting the list of devices: %d", (int) status); g_free (devices); *ndevices = 0; return NULL; } } return devices; } static inline gboolean _audio_device_is_alive (AudioDeviceID device_id, gboolean output) { OSStatus status = noErr; int alive = FALSE; UInt32 propertySize = sizeof (alive); AudioObjectPropertyScope prop_scope; prop_scope = output ? kAudioDevicePropertyScopeOutput : kAudioDevicePropertyScopeInput; AudioObjectPropertyAddress audioDeviceAliveAddress = { kAudioDevicePropertyDeviceIsAlive, prop_scope, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyData (device_id, &audioDeviceAliveAddress, 0, NULL, &propertySize, &alive); if (status != noErr) { alive = FALSE; } return alive; } static inline gboolean _audio_device_is_hidden (AudioDeviceID device_id) { OSStatus status = noErr; UInt32 hidden = FALSE; UInt32 property_size = sizeof (hidden); AudioObjectPropertyAddress property_address; property_address.mSelector = kAudioDevicePropertyIsHidden; status = AudioObjectGetPropertyData (device_id, &property_address, 0, NULL, &property_size, &hidden); if (status != noErr) { return FALSE; } return hidden; } static inline guint _audio_device_get_latency (AudioDeviceID device_id) { OSStatus status = noErr; UInt32 latency = 0; UInt32 propertySize = sizeof (latency); AudioObjectPropertyAddress audioDeviceLatencyAddress = { kAudioDevicePropertyLatency, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyData (device_id, &audioDeviceLatencyAddress, 0, NULL, &propertySize, &latency); if (status != noErr) { GST_ERROR ("failed to get latency: %d", (int) status); latency = -1; } return latency; } static inline pid_t _audio_device_get_hog (AudioDeviceID device_id) { OSStatus status = noErr; pid_t hog_pid; UInt32 propertySize = sizeof (hog_pid); AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyData (device_id, &audioDeviceHogModeAddress, 0, NULL, &propertySize, &hog_pid); if (status != noErr) { GST_ERROR ("failed to get hog: %d", (int) status); hog_pid = -1; } return hog_pid; } static inline gboolean _audio_device_set_hog (AudioDeviceID device_id, pid_t hog_pid) { OSStatus status = noErr; UInt32 propertySize = sizeof (hog_pid); gboolean res = FALSE; AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMain }; status = AudioObjectSetPropertyData (device_id, &audioDeviceHogModeAddress, 0, NULL, propertySize, &hog_pid); if (status == noErr) { res = TRUE; } else { GST_ERROR ("failed to set hog: %d", (int) status); } return res; } static inline gboolean _audio_device_set_mixing (AudioDeviceID device_id, gboolean enable_mix) { OSStatus status = noErr; UInt32 propertySize = 0, can_mix = enable_mix; Boolean writable = FALSE; gboolean res = FALSE; AudioObjectPropertyAddress audioDeviceSupportsMixingAddress = { kAudioDevicePropertySupportsMixing, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; if (AudioObjectHasProperty (device_id, &audioDeviceSupportsMixingAddress)) { /* Set mixable to false if we are allowed to */ status = AudioObjectIsPropertySettable (device_id, &audioDeviceSupportsMixingAddress, &writable); if (status) { GST_DEBUG ("AudioObjectIsPropertySettable: %d", (int) status); } status = AudioObjectGetPropertyDataSize (device_id, &audioDeviceSupportsMixingAddress, 0, NULL, &propertySize); if (status) { GST_DEBUG ("AudioObjectGetPropertyDataSize: %d", (int) status); } status = AudioObjectGetPropertyData (device_id, &audioDeviceSupportsMixingAddress, 0, NULL, &propertySize, &can_mix); if (status) { GST_DEBUG ("AudioObjectGetPropertyData: %d", (int) status); } if (status == noErr && writable) { can_mix = enable_mix; status = AudioObjectSetPropertyData (device_id, &audioDeviceSupportsMixingAddress, 0, NULL, propertySize, &can_mix); res = TRUE; } if (status != noErr) { GST_ERROR ("failed to set mixmode: %d", (int) status); } } else { GST_DEBUG ("property not found, mixing coudln't be changed"); } return res; } static inline gchar * _audio_device_get_name (AudioDeviceID device_id, gboolean output) { OSStatus status = noErr; UInt32 propertySize = 0; gchar *device_name = NULL; AudioObjectPropertyScope prop_scope; prop_scope = output ? kAudioDevicePropertyScopeOutput : kAudioDevicePropertyScopeInput; AudioObjectPropertyAddress deviceNameAddress = { kAudioDevicePropertyDeviceName, prop_scope, kAudioObjectPropertyElementMain }; /* Get the length of the device name */ status = AudioObjectGetPropertyDataSize (device_id, &deviceNameAddress, 0, NULL, &propertySize); if (status != noErr) { goto beach; } /* Get the name of the device */ device_name = (gchar *) g_malloc (propertySize); status = AudioObjectGetPropertyData (device_id, &deviceNameAddress, 0, NULL, &propertySize, device_name); if (status != noErr) { g_free (device_name); device_name = NULL; } beach: return device_name; } static inline gboolean _audio_device_has_output (AudioDeviceID device_id) { OSStatus status = noErr; UInt32 propertySize; AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyDataSize (device_id, &streamsAddress, 0, NULL, &propertySize); if (status != noErr) { return FALSE; } if (propertySize == 0) { return FALSE; } return TRUE; } #ifdef GST_CORE_AUDIO_DEBUG static AudioChannelLayout * gst_core_audio_audio_device_get_channel_layout (AudioDeviceID device_id, gboolean output) { OSStatus status = noErr; UInt32 propertySize = 0; AudioChannelLayout *layout = NULL; AudioObjectPropertyScope prop_scope; prop_scope = output ? kAudioDevicePropertyScopeOutput : kAudioDevicePropertyScopeInput; AudioObjectPropertyAddress channelLayoutAddress = { kAudioDevicePropertyPreferredChannelLayout, prop_scope, kAudioObjectPropertyElementMain }; /* Get the length of the default channel layout structure */ status = AudioObjectGetPropertyDataSize (device_id, &channelLayoutAddress, 0, NULL, &propertySize); if (status != noErr) { GST_ERROR ("failed to get preferred layout: %d", (int) status); goto beach; } /* Get the default channel layout of the device */ layout = (AudioChannelLayout *) g_malloc (propertySize); status = AudioObjectGetPropertyData (device_id, &channelLayoutAddress, 0, NULL, &propertySize, layout); if (status != noErr) { GST_ERROR ("failed to get preferred layout: %d", (int) status); goto failed; } if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { /* bitmap defined channellayout */ status = AudioFormatGetProperty (kAudioFormatProperty_ChannelLayoutForBitmap, sizeof (UInt32), &layout->mChannelBitmap, &propertySize, layout); if (status != noErr) { GST_ERROR ("failed to get layout for bitmap: %d", (int) status); goto failed; } } else if (layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions) { /* layouttags defined channellayout */ status = AudioFormatGetProperty (kAudioFormatProperty_ChannelLayoutForTag, sizeof (AudioChannelLayoutTag), &layout->mChannelLayoutTag, &propertySize, layout); if (status != noErr) { GST_ERROR ("failed to get layout for tag: %d", (int) status); goto failed; } } gst_core_audio_dump_channel_layout (layout); beach: return layout; failed: g_free (layout); return NULL; } #endif static inline AudioStreamID * _audio_device_get_streams (AudioDeviceID device_id, gint * nstreams) { OSStatus status = noErr; UInt32 propertySize = 0; AudioStreamID *streams = NULL; AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyDataSize (device_id, &streamsAddress, 0, NULL, &propertySize); if (status != noErr) { GST_WARNING ("failed getting number of streams: %d", (int) status); return NULL; } *nstreams = propertySize / sizeof (AudioStreamID); streams = (AudioStreamID *) g_malloc (propertySize); if (streams) { status = AudioObjectGetPropertyData (device_id, &streamsAddress, 0, NULL, &propertySize, streams); if (status != noErr) { GST_WARNING ("failed getting the list of streams: %d", (int) status); g_free (streams); *nstreams = 0; return NULL; } } return streams; } static inline guint _audio_stream_get_latency (AudioStreamID stream_id) { OSStatus status = noErr; UInt32 latency; UInt32 propertySize = sizeof (latency); AudioObjectPropertyAddress latencyAddress = { kAudioStreamPropertyLatency, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyData (stream_id, &latencyAddress, 0, NULL, &propertySize, &latency); if (status != noErr) { GST_ERROR ("failed to get latency: %d", (int) status); latency = -1; } return latency; } static inline gboolean _audio_stream_get_current_format (AudioStreamID stream_id, AudioStreamBasicDescription * format) { OSStatus status = noErr; UInt32 propertySize = sizeof (AudioStreamBasicDescription); AudioObjectPropertyAddress formatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyData (stream_id, &formatAddress, 0, NULL, &propertySize, format); if (status != noErr) { GST_ERROR ("failed to get current format: %d", (int) status); return FALSE; } return TRUE; } static inline gboolean _audio_stream_set_current_format (AudioStreamID stream_id, AudioStreamBasicDescription format) { OSStatus status = noErr; UInt32 propertySize = sizeof (AudioStreamBasicDescription); AudioObjectPropertyAddress formatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; status = AudioObjectSetPropertyData (stream_id, &formatAddress, 0, NULL, propertySize, &format); if (status != noErr) { GST_ERROR ("failed to set current format: %d", (int) status); return FALSE; } return TRUE; } static inline AudioStreamRangedDescription * _audio_stream_get_formats (AudioStreamID stream_id, gint * nformats) { OSStatus status = noErr; UInt32 propertySize = 0; AudioStreamRangedDescription *formats = NULL; AudioObjectPropertyAddress formatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; status = AudioObjectGetPropertyDataSize (stream_id, &formatsAddress, 0, NULL, &propertySize); if (status != noErr) { GST_WARNING ("failed getting number of stream formats: %d", (int) status); return NULL; } *nformats = propertySize / sizeof (AudioStreamRangedDescription); formats = (AudioStreamRangedDescription *) g_malloc (propertySize); if (formats) { status = AudioObjectGetPropertyData (stream_id, &formatsAddress, 0, NULL, &propertySize, formats); if (status != noErr) { GST_WARNING ("failed getting the list of stream formats: %d", (int) status); g_free (formats); *nformats = 0; return NULL; } } return formats; } static inline gboolean _audio_stream_is_spdif_avail (AudioStreamID stream_id) { AudioStreamRangedDescription *formats; gint i, nformats = 0; gboolean res = FALSE; formats = _audio_stream_get_formats (stream_id, &nformats); GST_DEBUG ("found %d stream formats", nformats); if (formats) { GST_DEBUG ("formats supported on stream ID: %u", (unsigned) stream_id); for (i = 0; i < nformats; i++) { GST_DEBUG (" " CORE_AUDIO_FORMAT, CORE_AUDIO_FORMAT_ARGS (formats[i].mFormat)); if (CORE_AUDIO_FORMAT_IS_SPDIF (formats[i])) { res = TRUE; } } g_free (formats); } return res; } static OSStatus _audio_stream_format_listener (AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) { OSStatus status = noErr; guint i; PropertyMutex *prop_mutex = inClientData; for (i = 0; i < inNumberAddresses; i++) { if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat) { g_mutex_lock (&prop_mutex->lock); g_cond_signal (&prop_mutex->cond); g_mutex_unlock (&prop_mutex->lock); break; } } return (status); } static gboolean _audio_stream_change_format (AudioStreamID stream_id, AudioStreamBasicDescription format) { OSStatus status = noErr; gint i; gboolean ret = FALSE; AudioStreamBasicDescription cformat; PropertyMutex prop_mutex; AudioObjectPropertyAddress formatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; GST_DEBUG ("setting stream format: " CORE_AUDIO_FORMAT, CORE_AUDIO_FORMAT_ARGS (format)); /* Condition because SetProperty is asynchronous */ g_mutex_init (&prop_mutex.lock); g_cond_init (&prop_mutex.cond); g_mutex_lock (&prop_mutex.lock); /* Install the property listener to serialize the operations */ status = AudioObjectAddPropertyListener (stream_id, &formatAddress, _audio_stream_format_listener, (void *) &prop_mutex); if (status != noErr) { GST_ERROR ("AudioObjectAddPropertyListener failed: %d", (int) status); goto done; } /* Change the format */ if (!_audio_stream_set_current_format (stream_id, format)) { goto done; } /* The AudioObjectSetProperty is not only asynchronous * it is also not atomic in its behaviour. * Therefore we check 4 times before we really give up. */ for (i = 0; i < 4; i++) { gint64 timeout; timeout = g_get_monotonic_time () + 250000; if (!g_cond_wait_until (&prop_mutex.cond, &prop_mutex.lock, timeout)) { GST_LOG ("timeout..."); } if (_audio_stream_get_current_format (stream_id, &cformat)) { GST_DEBUG ("current stream format: " CORE_AUDIO_FORMAT, CORE_AUDIO_FORMAT_ARGS (cformat)); if (cformat.mSampleRate == format.mSampleRate && cformat.mFormatID == format.mFormatID && cformat.mFramesPerPacket == format.mFramesPerPacket) { /* The right format is now active */ break; } } } if (cformat.mSampleRate != format.mSampleRate || cformat.mFormatID != format.mFormatID || cformat.mFramesPerPacket != format.mFramesPerPacket) { goto done; } ret = TRUE; done: /* Removing the property listener */ status = AudioObjectRemovePropertyListener (stream_id, &formatAddress, _audio_stream_format_listener, (void *) &prop_mutex); if (status != noErr) { GST_ERROR ("AudioObjectRemovePropertyListener failed: %d", (int) status); } /* Destroy the lock and condition */ g_mutex_unlock (&prop_mutex.lock); g_mutex_clear (&prop_mutex.lock); g_cond_clear (&prop_mutex.cond); return ret; } static OSStatus _audio_stream_hardware_changed_listener (AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) { OSStatus status = noErr; guint i; GstCoreAudio *core_audio = inClientData; for (i = 0; i < inNumberAddresses; i++) { if (inAddresses[i].mSelector == kAudioDevicePropertyDeviceHasChanged) { if (!gst_core_audio_audio_device_is_spdif_avail (core_audio->device_id)) { GstOsxAudioSink *sink = GST_OSX_AUDIO_SINK (GST_OBJECT_PARENT (core_audio->osxbuf)); GST_ELEMENT_ERROR (sink, RESOURCE, FAILED, ("SPDIF output no longer available"), ("Audio device is reporting that SPDIF output isn't available")); } break; } } return (status); } static inline gboolean _monitorize_spdif (GstCoreAudio * core_audio) { OSStatus status = noErr; gboolean ret = TRUE; AudioObjectPropertyAddress propAddress = { kAudioDevicePropertyDeviceHasChanged, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; /* Install the property listener */ status = AudioObjectAddPropertyListener (core_audio->device_id, &propAddress, _audio_stream_hardware_changed_listener, (void *) core_audio); if (status != noErr) { GST_ERROR_OBJECT (core_audio->osxbuf, "AudioObjectAddPropertyListener failed: %d", (int) status); ret = FALSE; } return ret; } static inline gboolean _unmonitorize_spdif (GstCoreAudio * core_audio) { OSStatus status = noErr; gboolean ret = TRUE; AudioObjectPropertyAddress propAddress = { kAudioDevicePropertyDeviceHasChanged, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; /* Remove the property listener */ status = AudioObjectRemovePropertyListener (core_audio->device_id, &propAddress, _audio_stream_hardware_changed_listener, (void *) core_audio); if (status != noErr) { GST_ERROR_OBJECT (core_audio->osxbuf, "AudioObjectRemovePropertyListener failed: %d", (int) status); ret = FALSE; } return ret; } static inline gboolean _open_spdif (GstCoreAudio * core_audio) { gboolean res = FALSE; pid_t hog_pid, own_pid = getpid (); /* We need the device in exclusive and disable the mixing */ hog_pid = _audio_device_get_hog (core_audio->device_id); if (hog_pid != -1 && hog_pid != own_pid) { GST_DEBUG_OBJECT (core_audio, "device is currently in use by another application"); goto done; } if (_audio_device_set_hog (core_audio->device_id, own_pid)) { core_audio->hog_pid = own_pid; } if (_audio_device_set_mixing (core_audio->device_id, FALSE)) { GST_DEBUG_OBJECT (core_audio, "disabled mixing on the device"); core_audio->disabled_mixing = TRUE; } res = TRUE; done: return res; } static inline gboolean _close_spdif (GstCoreAudio * core_audio) { pid_t hog_pid; _unmonitorize_spdif (core_audio); if (core_audio->revert_format) { if (!_audio_stream_change_format (core_audio->stream_id, core_audio->original_format)) { GST_WARNING_OBJECT (core_audio->osxbuf, "Format revert failed"); } core_audio->revert_format = FALSE; } if (core_audio->disabled_mixing) { _audio_device_set_mixing (core_audio->device_id, TRUE); core_audio->disabled_mixing = FALSE; } if (core_audio->hog_pid != -1) { hog_pid = _audio_device_get_hog (core_audio->device_id); if (hog_pid == getpid ()) { if (_audio_device_set_hog (core_audio->device_id, -1)) { core_audio->hog_pid = -1; } } } return TRUE; } static OSStatus _io_proc_spdif (AudioDeviceID inDevice, const AudioTimeStamp * inNow, const void *inInputData, const AudioTimeStamp * inTimestamp, AudioBufferList * bufferList, const AudioTimeStamp * inOutputTime, GstCoreAudio * core_audio) { OSStatus status; status = core_audio->element->io_proc (core_audio->osxbuf, NULL, inTimestamp, 0, 0, bufferList); return status; } static inline gboolean _acquire_spdif (GstCoreAudio * core_audio, AudioStreamBasicDescription format) { AudioStreamID *streams = NULL; gint i, j, nstreams = 0; gboolean ret = FALSE; if (!_open_spdif (core_audio)) goto done; streams = _audio_device_get_streams (core_audio->device_id, &nstreams); for (i = 0; i < nstreams; i++) { AudioStreamRangedDescription *formats = NULL; gint nformats = 0; formats = _audio_stream_get_formats (streams[i], &nformats); if (formats) { gboolean is_spdif = FALSE; /* Check if one of the supported formats is a digital format */ for (j = 0; j < nformats; j++) { if (CORE_AUDIO_FORMAT_IS_SPDIF (formats[j])) { is_spdif = TRUE; break; } } if (is_spdif) { /* if this stream supports a digital (cac3) format, * then go set it. */ gint requested_rate_format = -1; gint current_rate_format = -1; gint backup_rate_format = -1; core_audio->stream_id = streams[i]; core_audio->stream_idx = i; if (!core_audio->revert_format) { if (!_audio_stream_get_current_format (core_audio->stream_id, &core_audio->original_format)) { GST_WARNING_OBJECT (core_audio->osxbuf, "format could not be saved"); g_free (formats); continue; } core_audio->revert_format = TRUE; } for (j = 0; j < nformats; j++) { if (CORE_AUDIO_FORMAT_IS_SPDIF (formats[j])) { GST_LOG_OBJECT (core_audio->osxbuf, "found stream format: " CORE_AUDIO_FORMAT, CORE_AUDIO_FORMAT_ARGS (formats[j].mFormat)); if (formats[j].mFormat.mSampleRate == format.mSampleRate) { requested_rate_format = j; break; } else if (formats[j].mFormat.mSampleRate == core_audio->original_format.mSampleRate) { current_rate_format = j; } else { if (backup_rate_format < 0 || formats[j].mFormat.mSampleRate > formats[backup_rate_format].mFormat.mSampleRate) { backup_rate_format = j; } } } } if (requested_rate_format >= 0) { /* We prefer to output at the rate of the original audio */ core_audio->stream_format = formats[requested_rate_format].mFormat; } else if (current_rate_format >= 0) { /* If not possible, we will try to use the current rate */ core_audio->stream_format = formats[current_rate_format].mFormat; } else { /* And if we have to, any digital format will be just * fine (highest rate possible) */ core_audio->stream_format = formats[backup_rate_format].mFormat; } } g_free (formats); } } g_free (streams); GST_DEBUG_OBJECT (core_audio, "original stream format: " CORE_AUDIO_FORMAT, CORE_AUDIO_FORMAT_ARGS (core_audio->original_format)); if (!_audio_stream_change_format (core_audio->stream_id, core_audio->stream_format)) goto done; ret = TRUE; done: return ret; } static inline void _remove_render_spdif_callback (GstCoreAudio * core_audio) { OSStatus status; /* Deactivate the render callback by calling * AudioDeviceDestroyIOProcID */ status = AudioDeviceDestroyIOProcID (core_audio->device_id, core_audio->procID); if (status != noErr) { GST_ERROR_OBJECT (core_audio->osxbuf, "AudioDeviceDestroyIOProcID failed: %d", (int) status); } GST_DEBUG_OBJECT (core_audio, "osx ring buffer removed ioproc ID: %p device_id %lu", core_audio->procID, (gulong) core_audio->device_id); /* We're deactivated.. */ core_audio->procID = 0; core_audio->io_proc_needs_deactivation = FALSE; core_audio->io_proc_active = FALSE; } static inline gboolean _io_proc_spdif_start (GstCoreAudio * core_audio) { OSErr status; GST_DEBUG_OBJECT (core_audio, "osx ring buffer start ioproc ID: %p device_id %lu", core_audio->procID, (gulong) core_audio->device_id); if (!core_audio->io_proc_active) { /* Add IOProc callback */ status = AudioDeviceCreateIOProcID (core_audio->device_id, (AudioDeviceIOProc) _io_proc_spdif, (void *) core_audio, &core_audio->procID); if (status != noErr) { GST_ERROR_OBJECT (core_audio->osxbuf, ":AudioDeviceCreateIOProcID failed: %d", (int) status); return FALSE; } core_audio->io_proc_active = TRUE; } core_audio->io_proc_needs_deactivation = FALSE; /* Start device */ status = AudioDeviceStart (core_audio->device_id, core_audio->procID); if (status != noErr) { GST_ERROR_OBJECT (core_audio->osxbuf, "AudioDeviceStart failed: %d", (int) status); return FALSE; } return TRUE; } static inline gboolean _io_proc_spdif_stop (GstCoreAudio * core_audio) { OSErr status; /* Stop device */ status = AudioDeviceStop (core_audio->device_id, core_audio->procID); if (status != noErr) { GST_ERROR_OBJECT (core_audio->osxbuf, "AudioDeviceStop failed: %d", (int) status); } GST_DEBUG_OBJECT (core_audio, "osx ring buffer stop ioproc ID: %p device_id %lu", core_audio->procID, (gulong) core_audio->device_id); if (core_audio->io_proc_active) { _remove_render_spdif_callback (core_audio); } _close_spdif (core_audio); return TRUE; } /*********************** * Implementation * **********************/ static gboolean gst_core_audio_open_impl (GstCoreAudio * core_audio) { gboolean ret; /* The following is needed to instruct HAL to create their own * thread to handle the notifications. */ _audio_system_set_runloop (NULL); /* Create a HALOutput AudioUnit. * This is the lowest-level output API that is actually sensibly * usable (the lower level ones require that you do * channel-remapping yourself, and the CoreAudio channel mapping * is sufficiently complex that doing so would be very difficult) * * Note that for input we request an output unit even though * we will do input with it. * http://developer.apple.com/technotes/tn2002/tn2091.html */ ret = gst_core_audio_open_device (core_audio, kAudioUnitSubType_HALOutput, "HALOutput"); if (!ret) { GST_DEBUG ("Could not open device"); goto done; } ret = gst_core_audio_bind_device (core_audio); if (!ret) { GST_DEBUG ("Could not bind device"); goto done; } done: return ret; } static gboolean gst_core_audio_start_processing_impl (GstCoreAudio * core_audio) { if (core_audio->is_passthrough) { return _io_proc_spdif_start (core_audio); } else { return gst_core_audio_io_proc_start (core_audio); } } static gboolean gst_core_audio_pause_processing_impl (GstCoreAudio * core_audio) { if (core_audio->is_passthrough) { GST_DEBUG_OBJECT (core_audio, "osx ring buffer pause ioproc ID: %p device_id %lu", core_audio->procID, (gulong) core_audio->device_id); if (core_audio->io_proc_active) { _remove_render_spdif_callback (core_audio); } } else { GST_DEBUG_OBJECT (core_audio, "osx ring buffer pause ioproc: %p device_id %lu", core_audio->element->io_proc, (gulong) core_audio->device_id); if (core_audio->io_proc_active) { /* CoreAudio isn't threadsafe enough to do this here; * we must deactivate the render callback elsewhere. See: * http://lists.apple.com/archives/Coreaudio-api/2006/Mar/msg00010.html */ core_audio->io_proc_needs_deactivation = TRUE; } } return TRUE; } static gboolean gst_core_audio_stop_processing_impl (GstCoreAudio * core_audio) { if (core_audio->is_passthrough) { _io_proc_spdif_stop (core_audio); } else { gst_core_audio_io_proc_stop (core_audio); } return TRUE; } static gboolean gst_core_audio_get_samples_and_latency_impl (GstCoreAudio * core_audio, gdouble rate, guint * samples, gdouble * latency) { OSStatus status; UInt32 size = sizeof (double); if (core_audio->is_passthrough) { *samples = _audio_device_get_latency (core_audio->device_id); *samples += _audio_stream_get_latency (core_audio->stream_id); *latency = (double) *samples / rate; } else { status = AudioUnitGetProperty (core_audio->audiounit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, /* N/A for global */ latency, &size); if (status) { GST_WARNING_OBJECT (core_audio->osxbuf, "Failed to get latency: %d", (int) status); *samples = 0; return FALSE; } *samples = *latency * rate; } return TRUE; } static gboolean gst_core_audio_initialize_impl (GstCoreAudio * core_audio, AudioStreamBasicDescription format, GstCaps * caps, gboolean is_passthrough, guint32 * frame_size) { gboolean ret = FALSE; OSStatus status; /* Uninitialize the AudioUnit before changing formats */ status = AudioUnitUninitialize (core_audio->audiounit); if (status) { GST_ERROR_OBJECT (core_audio, "Failed to uninitialize AudioUnit: %d", (int) status); return FALSE; } core_audio->is_passthrough = is_passthrough; if (is_passthrough) { if (!_acquire_spdif (core_audio, format)) goto done; _monitorize_spdif (core_audio); } else { OSStatus status; UInt32 propertySize = sizeof (*frame_size); core_audio->stream_idx = 0; if (!gst_core_audio_set_format (core_audio, format)) goto done; if (!gst_core_audio_set_channel_layout (core_audio, format.mChannelsPerFrame, caps)) goto done; // Attempt to configure the requested frame size if smaller than the device's // This will apparently modify the size for all audio devices in the current process so should // be done conservatively if (frame_size != 0) { guint32 cur_frame_size; status = AudioUnitGetProperty (core_audio->audiounit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, /* N/A for global */ &cur_frame_size, &propertySize); if (!status && *frame_size < cur_frame_size) { status = AudioUnitSetProperty (core_audio->audiounit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, /* N/A for global */ frame_size, propertySize); if (status) { GST_WARNING_OBJECT (core_audio->osxbuf, "Failed to set desired frame size of %u: %d", *frame_size, (int) status); } } } status = AudioUnitGetProperty (core_audio->audiounit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, /* N/A for global */ frame_size, &propertySize); if (status) { GST_WARNING_OBJECT (core_audio->osxbuf, "Failed to get frame size: %d", (int) status); goto done; } } ret = TRUE; done: /* Format changed, initialise the AudioUnit again */ status = AudioUnitInitialize (core_audio->audiounit); if (status) { GST_ERROR_OBJECT (core_audio, "Failed to initialize AudioUnit: %d", (int) status); ret = FALSE; } if (ret) { GST_DEBUG_OBJECT (core_audio, "osxbuf ring buffer acquired"); } return ret; } static gboolean gst_core_audio_select_device_impl (GstCoreAudio * core_audio) { AudioDeviceID device_id = core_audio->device_id; gboolean output = !core_audio->is_src; gboolean res = FALSE; /* Find the ID of the default output device */ AudioDeviceID default_device_id = _audio_system_get_default_device (output); /* Here we decide if selected device is valid or autoselect * the default one when required */ if (device_id == kAudioDeviceUnknown) { if (default_device_id != kAudioDeviceUnknown) { device_id = default_device_id; res = TRUE; } else { GST_ERROR ("No device of required type available"); res = FALSE; } } else if (_audio_device_is_hidden (device_id)) { if (_audio_device_is_alive (device_id, output)) { res = TRUE; } else { GST_ERROR ("Requested hidden device not usable"); res = FALSE; } } else { AudioDeviceID *devices = NULL; gint i, ndevices = 0; #ifdef GST_CORE_AUDIO_DEBUG AudioChannelLayout *channel_layout; #endif devices = _audio_system_get_devices (&ndevices); if (ndevices < 1) { GST_ERROR ("no audio output devices found"); g_free (devices); return res; } GST_DEBUG ("found %d audio device(s)", ndevices); #ifdef GST_CORE_AUDIO_DEBUG for (i = 0; i < ndevices; i++) { gchar *device_name; if ((device_name = _audio_device_get_name (devices[i], output))) { if (!_audio_device_has_output (devices[i])) { GST_DEBUG ("Input Device ID: %u Name: %s", (unsigned) devices[i], device_name); } else { GST_DEBUG ("Output Device ID: %u Name: %s", (unsigned) devices[i], device_name); channel_layout = gst_core_audio_audio_device_get_channel_layout (devices[i], output); if (channel_layout) { gst_core_audio_dump_channel_layout (channel_layout); g_free (channel_layout); } } g_free (device_name); } } #endif for (i = 0; i < ndevices; i++) { if (device_id == devices[i]) { res = TRUE; break; } } g_free (devices); if (res && !_audio_device_is_alive (device_id, output)) { GST_ERROR ("Requested device not usable"); res = FALSE; } } if (res) { core_audio->device_id = device_id; core_audio->is_default = (device_id == default_device_id); } return res; } static gboolean gst_core_audio_audio_device_is_spdif_avail_impl (AudioDeviceID device_id) { AudioStreamID *streams = NULL; gint i, nstreams = 0; gboolean res = FALSE; streams = _audio_device_get_streams (device_id, &nstreams); GST_DEBUG ("found %d streams", nstreams); if (streams) { for (i = 0; i < nstreams; i++) { if (_audio_stream_is_spdif_avail (streams[i])) { res = TRUE; } } g_free (streams); } return res; }