1 /*****************************************************************************
2 * auhal.c: AUHAL and Coreaudio output plugin
3 *****************************************************************************
4 * Copyright (C) 2005 - 2014 VLC authors and VideoLAN
7 * Authors: Derk-Jan Hartman <hartman at videolan dot org>
8 * Felix Paul Kühne <fkuehne at videolan dot org>
9 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
32 #import <vlc_common.h>
33 #import <vlc_plugin.h>
34 #import <vlc_dialog.h> // dialog_Fatal
35 #import <vlc_aout.h> // aout_*
37 #import <AudioUnit/AudioUnit.h> // AudioUnit
38 #import <CoreAudio/CoreAudio.h> // AudioDeviceID
39 #import <AudioToolbox/AudioFormat.h> // AudioFormatGetProperty
40 #import <CoreServices/CoreServices.h>
42 #import "TPCircularBuffer.h"
45 #pragma mark private declarations
48 # define verify_noerr(a) assert((a) == noErr)
51 #define STREAM_FORMAT_MSG(pre, sfm) \
52 pre "[%f][%4.4s][%u][%u][%u][%u][%u][%u]", \
53 sfm.mSampleRate, (char *)&sfm.mFormatID, \
54 (unsigned int)sfm.mFormatFlags, (unsigned int)sfm.mBytesPerPacket, \
55 (unsigned int)sfm.mFramesPerPacket, (unsigned int)sfm.mBytesPerFrame, \
56 (unsigned int)sfm.mChannelsPerFrame, (unsigned int)sfm.mBitsPerChannel
58 #define AOUT_VAR_SPDIF_FLAG 0xf00000
60 #define AUDIO_BUFFER_SIZE_IN_SECONDS ((AOUT_MAX_ADVANCE_TIME + CLOCK_FREQ) / CLOCK_FREQ)
63 #define AOUT_VOLUME_DEFAULT 256
64 #define AOUT_VOLUME_MAX 512
66 #define VOLUME_TEXT N_("Audio volume")
67 #define VOLUME_LONGTEXT VOLUME_TEXT
69 #define DEVICE_TEXT N_("Last audio device")
70 #define DEVICE_LONGTEXT DEVICE_TEXT
72 /*****************************************************************************
73 * aout_sys_t: private audio output method descriptor
74 *****************************************************************************
75 * This structure is part of the audio output thread descriptor.
76 * It describes the CoreAudio specific properties of an output thread.
77 *****************************************************************************/
80 AudioObjectID i_selected_dev; /* DeviceID of the selected device */
81 AudioObjectID i_new_selected_dev; /* DeviceID of device which will be selected on start */
82 bool b_selected_dev_is_digital;
83 bool b_selected_dev_is_default; /* true if the user selected the default audio device (id 0) */
85 AudioDeviceIOProcID i_procID; /* DeviceID of current device */
86 bool b_digital; /* Are we running in digital mode? */
88 uint8_t chans_to_reorder; /* do we need channel reordering */
89 uint8_t chan_table[AOUT_CHAN_MAX];
91 TPCircularBuffer circular_buffer; /* circular buffer to swap the audio data */
94 AudioComponent au_component; /* The AudioComponent we use */
95 AudioUnit au_unit; /* The AudioUnit we use */
97 /* CoreAudio SPDIF mode specific */
98 pid_t i_hog_pid; /* The keep the pid of our hog status */
99 AudioStreamID i_stream_id; /* The StreamID that has a cac3 streamformat */
100 int i_stream_index; /* The index of i_stream_id in an AudioBufferList */
101 AudioStreamBasicDescription sfmt_revert; /* The original format of the stream */
102 bool b_revert; /* Whether we need to revert the stream format */
103 bool b_changed_mixing; /* Whether we need to set the mixing mode back */
105 int i_rate; /* media sample rate */
106 int i_bytes_per_sample;
108 CFArrayRef device_list;
109 vlc_mutex_t device_list_lock; /* protects access to device_list */
111 vlc_mutex_t selected_device_lock;/* Synchronizes access to i_selected_dev. This is only needed
112 between VLCs audio thread and the core audio callback thread.
113 The value is only changed in Start, further access to this variable
114 within the audio thread (start, stop, close) needs no protection. */
123 bool b_ignore_streams_changed_callback;
125 UInt32 i_device_latency; /* The time the device needs to process the data. In samples. */
129 #pragma mark local prototypes & module descriptor
131 static int Open (vlc_object_t *);
132 static void Close (vlc_object_t *);
133 static int Start (audio_output_t *, audio_sample_format_t *);
134 static int StartAnalog (audio_output_t *, audio_sample_format_t *);
135 static int StartSPDIF (audio_output_t *, audio_sample_format_t *);
136 static void Stop (audio_output_t *);
138 static void RebuildDeviceList (audio_output_t *);
139 static int SwitchAudioDevice (audio_output_t *p_aout, const char *name);
140 static int VolumeSet (audio_output_t *, float);
141 static int MuteSet (audio_output_t *, bool);
143 static void Play (audio_output_t *, block_t *);
144 static void Pause (audio_output_t *, bool, mtime_t);
145 static void Flush (audio_output_t *, bool);
146 static int TimeGet (audio_output_t *, mtime_t *);
147 static OSStatus RenderCallbackAnalog (vlc_object_t *, AudioUnitRenderActionFlags *, const AudioTimeStamp *,
148 UInt32 , UInt32, AudioBufferList *);
150 static OSStatus RenderCallbackSPDIF (AudioDeviceID, const AudioTimeStamp *, const void *, const AudioTimeStamp *,
151 AudioBufferList *, const AudioTimeStamp *, void *);
153 static OSStatus DevicesListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *);
154 static OSStatus DeviceAliveListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *);
155 static OSStatus DefaultDeviceChangedListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *);
156 static OSStatus StreamsChangedListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *);
158 static OSStatus StreamListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *);
160 static int ManageAudioStreamsCallback(audio_output_t *p_aout, AudioDeviceID i_dev_id, bool b_register);
161 static int AudioDeviceHasOutput (AudioDeviceID);
162 static int AudioDeviceSupportsDigital(audio_output_t *, AudioDeviceID);
163 static int AudioStreamSupportsDigital(audio_output_t *, AudioStreamID);
164 static int AudioStreamChangeFormat (audio_output_t *, AudioStreamID, AudioStreamBasicDescription);
168 set_shortname("auhal")
169 set_description(N_("HAL AudioUnit output"))
170 set_capability("audio output", 101)
171 set_category(CAT_AUDIO)
172 set_subcategory(SUBCAT_AUDIO_AOUT)
173 set_callbacks(Open, Close)
174 add_integer("auhal-volume", AOUT_VOLUME_DEFAULT,
175 VOLUME_TEXT, VOLUME_LONGTEXT, true)
176 change_integer_range(0, AOUT_VOLUME_MAX)
177 add_string("auhal-audio-device", "", DEVICE_TEXT, DEVICE_LONGTEXT, true)
178 add_obsolete_integer("macosx-audio-device") /* since 2.1.0 */
182 #pragma mark initialization
184 static int Open(vlc_object_t *obj)
186 audio_output_t *p_aout = (audio_output_t *)obj;
187 aout_sys_t *p_sys = malloc(sizeof (*p_sys));
188 if (unlikely(p_sys == NULL))
191 OSStatus err = noErr;
193 vlc_mutex_init(&p_sys->device_list_lock);
194 vlc_mutex_init(&p_sys->selected_device_lock);
195 vlc_mutex_init(&p_sys->lock);
196 vlc_cond_init(&p_sys->cond);
197 p_sys->b_digital = false;
198 p_sys->b_ignore_streams_changed_callback = false;
199 p_sys->b_selected_dev_is_default = false;
200 p_sys->b_paused = false;
201 memset(&p_sys->sfmt_revert, 0, sizeof(p_sys->sfmt_revert));
202 p_sys->i_stream_id = 0;
205 p_aout->start = Start;
207 p_aout->volume_set = VolumeSet;
208 p_aout->mute_set = MuteSet;
209 p_aout->device_select = SwitchAudioDevice;
210 p_sys->device_list = CFArrayCreate(kCFAllocatorDefault, NULL, 0, NULL);
213 * Force an own run loop for callbacks.
215 * According to rtaudio, this is absolutely necessary since 10.6 to get correct notifications.
216 * It might fix issues when using the module as a library where a proper loop is not setup already.
218 CFRunLoopRef theRunLoop = NULL;
219 AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal,kAudioObjectPropertyElementMaster };
220 err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
222 msg_Err(p_aout, "failed to set the run loop property [%4.4s]", (char *)&err);
225 /* Attach a listener so that we are notified of a change in the device setup */
226 AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
227 err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &audioDevicesAddress, DevicesListener, (void *)p_aout);
229 msg_Err(p_aout, "failed to add listener for audio device configuration [%4.4s]", (char *)&err);
231 /* Attach a listener to be notified about changes in default audio device */
232 AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
233 err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &defaultDeviceAddress, DefaultDeviceChangedListener, (void *)p_aout);
235 msg_Err(p_aout, "failed to add listener for default audio device [%4.4s]", (char *)&err);
237 RebuildDeviceList(p_aout);
239 /* remember the volume */
240 p_sys->f_volume = var_InheritInteger(p_aout, "auhal-volume") / (float)AOUT_VOLUME_DEFAULT;
241 aout_VolumeReport(p_aout, p_sys->f_volume);
242 p_sys->b_mute = var_InheritBool(p_aout, "mute");
243 aout_MuteReport(p_aout, p_sys->b_mute);
245 char *psz_audio_device = var_InheritString(p_aout, "auhal-audio-device");
246 SwitchAudioDevice(p_aout, psz_audio_device);
247 free(psz_audio_device);
252 static void Close(vlc_object_t *obj)
254 audio_output_t *p_aout = (audio_output_t *)obj;
255 aout_sys_t *p_sys = p_aout->sys;
257 OSStatus err = noErr;
259 /* remove audio devices callback */
260 AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
261 err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &audioDevicesAddress, DevicesListener, (void *)p_aout);
263 msg_Err(p_aout, "AudioHardwareRemovePropertyListener failed [%4.4s]", (char *)&err);
265 /* remove listener to be notified about changes in default audio device */
266 AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
267 err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &defaultDeviceAddress, DefaultDeviceChangedListener, (void *)p_aout);
269 msg_Err(p_aout, "failed to remove listener for default audio device [%4.4s]", (char *)&err);
272 * StreamsChangedListener can rebuild the device list and thus held the device_list_lock.
273 * To avoid a possible deadlock, an array copy is created here.
274 * In rare cases, this can lead to missing StreamsChangedListener callback deregistration (TODO).
276 vlc_mutex_lock(&p_sys->device_list_lock);
277 CFArrayRef device_list_cpy = CFArrayCreateCopy(NULL, p_sys->device_list);
278 vlc_mutex_unlock(&p_sys->device_list_lock);
280 /* remove streams callbacks */
281 CFIndex count = CFArrayGetCount(device_list_cpy);
283 for (CFIndex x = 0; x < count; x++) {
284 AudioDeviceID deviceId = 0;
285 CFNumberRef cfn_device_id = CFArrayGetValueAtIndex(device_list_cpy, x);
289 CFNumberGetValue(cfn_device_id, kCFNumberSInt32Type, &deviceId);
290 if (!(deviceId & AOUT_VAR_SPDIF_FLAG)) {
291 ManageAudioStreamsCallback(p_aout, deviceId, false);
296 CFRelease(device_list_cpy);
297 CFRelease(p_sys->device_list);
299 char *psz_device = aout_DeviceGet(p_aout);
300 config_PutPsz(p_aout, "auhal-audio-device", psz_device);
303 vlc_mutex_destroy(&p_sys->selected_device_lock);
304 vlc_mutex_destroy(&p_sys->device_list_lock);
305 vlc_mutex_destroy(&p_sys->lock);
306 vlc_cond_destroy(&p_sys->cond);
311 static int Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt)
313 OSStatus err = noErr;
314 UInt32 i_param_size = 0;
315 struct aout_sys_t *p_sys = NULL;
317 /* Use int here, to match kAudioDevicePropertyDeviceIsAlive
321 bool b_start_digital = false;
324 p_sys->b_digital = false;
325 p_sys->au_component = NULL;
326 p_sys->au_unit = NULL;
327 p_sys->i_hog_pid = -1;
328 p_sys->i_stream_index = -1;
329 p_sys->b_revert = false;
330 p_sys->b_changed_mixing = false;
331 p_sys->i_bytes_per_sample = 0;
332 p_sys->b_paused = false;
333 p_sys->i_device_latency = 0;
335 vlc_mutex_lock(&p_sys->selected_device_lock);
336 p_sys->i_selected_dev = p_sys->i_new_selected_dev;
338 aout_FormatPrint(p_aout, "VLC is looking for:", fmt);
340 msg_Dbg(p_aout, "attempting to use device %i", p_sys->i_selected_dev);
342 AudioObjectPropertyAddress audioDeviceAliveAddress = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
343 if (p_sys->i_selected_dev > 0) {
345 /* Check if device is in devices list. Only checking for kAudioDevicePropertyDeviceIsAlive is not sufficient,
346 * as a former airplay device might be already gone, but the device number might be still valid. Core Audio
347 * even says that this device would be alive. Don't ask why, its Core Audio. */
348 CFIndex count = CFArrayGetCount(p_sys->device_list);
349 CFNumberRef deviceNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &p_sys->i_selected_dev);
350 if (CFArrayContainsValue(p_sys->device_list, CFRangeMake(0, count), deviceNumber)) {
352 /* Check if the desired device is alive and usable */
353 i_param_size = sizeof(b_alive);
354 err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceAliveAddress, 0, NULL, &i_param_size, &b_alive);
356 /* Be tolerant, only give a warning here */
357 msg_Warn(p_aout, "could not check whether device [0x%x] is alive [%4.4s]",
358 (unsigned int)p_sys->i_selected_dev, (char *)&err);
363 msg_Warn(p_aout, "selected audio device is not alive, switching to default device");
366 msg_Warn(p_aout, "device id %i not found in the current devices list, fallback to default device", p_sys->i_selected_dev);
368 CFRelease(deviceNumber);
371 p_sys->b_selected_dev_is_default = false;
372 if (!b_alive || p_sys->i_selected_dev == 0) {
373 p_sys->b_selected_dev_is_default = true;
375 AudioObjectID defaultDeviceID = 0;
376 UInt32 propertySize = sizeof(AudioObjectID);
377 AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
378 propertySize = sizeof(AudioObjectID);
379 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultDeviceAddress, 0, NULL, &propertySize, &defaultDeviceID);
381 msg_Err(p_aout, "could not get default audio device [%4.4s]", (char *)&err);
382 vlc_mutex_unlock(&p_sys->selected_device_lock);
386 msg_Dbg(p_aout, "using default audio device %i", defaultDeviceID);
388 p_sys->i_selected_dev = defaultDeviceID;
389 p_sys->b_selected_dev_is_digital = var_InheritBool(p_aout, "spdif");
391 vlc_mutex_unlock(&p_sys->selected_device_lock);
393 // recheck if device still supports digital
394 b_start_digital = p_sys->b_selected_dev_is_digital;
395 if(!AudioDeviceSupportsDigital(p_aout, p_sys->i_selected_dev))
396 b_start_digital = false;
399 msg_Dbg(p_aout, "Using audio device for digital output");
401 msg_Dbg(p_aout, "Audio device supports PCM mode only");
403 /* add a callback to see if the device dies later on */
404 err = AudioObjectAddPropertyListener(p_sys->i_selected_dev, &audioDeviceAliveAddress, DeviceAliveListener, (void *)p_aout);
406 /* Be tolerant, only give a warning here */
407 msg_Warn(p_aout, "could not set alive check callback on device [0x%x] [%4.4s]",
408 (unsigned int)p_sys->i_selected_dev, (char *)&err);
411 AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode,
412 kAudioDevicePropertyScopeOutput,
413 kAudioObjectPropertyElementMaster };
414 i_param_size = sizeof(p_sys->i_hog_pid);
415 err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, &i_param_size, &p_sys->i_hog_pid);
417 /* This is not a fatal error. Some drivers simply don't support this property */
418 msg_Warn(p_aout, "could not check whether device is hogged [%4.4s]",
420 p_sys->i_hog_pid = -1;
423 if (p_sys->i_hog_pid != -1 && p_sys->i_hog_pid != getpid()) {
424 msg_Err(p_aout, "Selected audio device is exclusively in use by another program.");
425 dialog_Fatal(p_aout, _("Audio output failed"), "%s",
426 _("The selected audio output device is exclusively in "
427 "use by another program."));
431 /* get device latency */
432 AudioObjectPropertyAddress latencyAddress = { kAudioDevicePropertyLatency, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
433 i_param_size = sizeof(p_sys->i_device_latency);
434 err = AudioObjectGetPropertyData(p_sys->i_selected_dev,
439 &p_sys->i_device_latency);
441 msg_Warn(p_aout, "Cannot get device latency [%4.4s]",
444 msg_Dbg(p_aout, "Current device has a latency of %u frames", p_sys->i_device_latency);
447 bool b_success = false;
449 /* Check for Digital mode or Analog output mode */
450 if (AOUT_FMT_SPDIF (fmt) && b_start_digital) {
451 if (StartSPDIF (p_aout, fmt)) {
452 msg_Dbg(p_aout, "digital output successfully opened");
456 if (StartAnalog(p_aout, fmt)) {
457 msg_Dbg(p_aout, "analog output successfully opened");
464 p_aout->flush = Flush;
465 p_aout->time_get = TimeGet;
466 p_aout->pause = Pause;
471 /* If we reach this, this aout has failed */
472 msg_Err(p_aout, "opening auhal output failed");
477 * StartAnalog: open and setup a HAL AudioUnit to do PCM audio output
479 static int StartAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt)
481 struct aout_sys_t *p_sys = p_aout->sys;
482 OSStatus err = noErr;
483 UInt32 i_param_size = 0;
485 AudioComponentDescription desc;
486 AudioStreamBasicDescription DeviceFormat;
487 AudioChannelLayout *layout;
488 AURenderCallbackStruct input;
489 p_aout->sys->chans_to_reorder = 0;
491 SInt32 currentMinorSystemVersion;
492 if(Gestalt(gestaltSystemVersionMinor, ¤tMinorSystemVersion) != noErr)
493 msg_Err(p_aout, "failed to check OSX version");
495 /* Lets go find our Component */
496 desc.componentType = kAudioUnitType_Output;
497 desc.componentSubType = kAudioUnitSubType_HALOutput;
498 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
499 desc.componentFlags = 0;
500 desc.componentFlagsMask = 0;
502 p_sys->au_component = AudioComponentFindNext(NULL, &desc);
503 if (p_sys->au_component == NULL) {
504 msg_Err(p_aout, "cannot find any HAL component, PCM output failed");
508 err = AudioComponentInstanceNew(p_sys->au_component, &p_sys->au_unit);
510 msg_Err(p_aout, "cannot open HAL component, PCM output failed [%4.4s]", (char *)&err);
514 /* Set the device we will use for this output unit */
515 err = AudioUnitSetProperty(p_sys->au_unit,
516 kAudioOutputUnitProperty_CurrentDevice,
517 kAudioUnitScope_Global,
519 &p_sys->i_selected_dev,
520 sizeof(AudioObjectID));
523 msg_Err(p_aout, "cannot select audio output device, PCM output failed [%4.4s]", (char *)&err);
527 /* Get the current format */
528 i_param_size = sizeof(AudioStreamBasicDescription);
530 err = AudioUnitGetProperty(p_sys->au_unit,
531 kAudioUnitProperty_StreamFormat,
532 kAudioUnitScope_Output,
538 msg_Err(p_aout, "failed to detect supported stream formats [%4.4s]", (char *)&err);
541 msg_Dbg(p_aout, STREAM_FORMAT_MSG("current format is: ", DeviceFormat));
543 /* Get the channel layout of the device side of the unit (vlc -> unit -> device) */
544 err = AudioUnitGetPropertyInfo(p_sys->au_unit,
545 kAudioDevicePropertyPreferredChannelLayout,
546 kAudioUnitScope_Output,
552 layout = (AudioChannelLayout *)malloc(i_param_size);
554 verify_noerr(AudioUnitGetProperty(p_sys->au_unit,
555 kAudioDevicePropertyPreferredChannelLayout,
556 kAudioUnitScope_Output,
561 /* We need to "fill out" the ChannelLayout, because there are multiple ways that it can be set */
562 if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) {
563 /* bitmap defined channellayout */
564 verify_noerr(AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
565 sizeof(UInt32), &layout->mChannelBitmap,
568 } else if (layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions)
570 /* layouttags defined channellayout */
571 verify_noerr(AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
572 sizeof(AudioChannelLayoutTag), &layout->mChannelLayoutTag,
577 msg_Dbg(p_aout, "layout of AUHAL has %i channels" , layout->mNumberChannelDescriptions);
579 if (layout->mNumberChannelDescriptions == 0) {
580 msg_Err(p_aout, "insufficient number of output channels");
585 /* Initialize the VLC core channel count */
586 fmt->i_physical_channels = 0;
587 i_original = fmt->i_original_channels & AOUT_CHAN_PHYSMASK;
589 if (i_original == AOUT_CHAN_CENTER || layout->mNumberChannelDescriptions < 2) {
590 /* We only need Mono or cannot output more than 1 channel */
591 fmt->i_physical_channels = AOUT_CHAN_CENTER;
592 } else if (i_original == (AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT) || layout->mNumberChannelDescriptions < 3) {
593 /* We only need Stereo or cannot output more than 2 channels */
594 fmt->i_physical_channels = AOUT_CHANS_STEREO;
597 // maps auhal channels to vlc ones
598 static const unsigned i_auhal_channel_mapping[] = {
599 [kAudioChannelLabel_Left] = AOUT_CHAN_LEFT,
600 [kAudioChannelLabel_Right] = AOUT_CHAN_RIGHT,
601 [kAudioChannelLabel_Center] = AOUT_CHAN_CENTER,
602 [kAudioChannelLabel_LFEScreen] = AOUT_CHAN_LFE,
603 [kAudioChannelLabel_LeftSurround] = AOUT_CHAN_REARLEFT,
604 [kAudioChannelLabel_RightSurround] = AOUT_CHAN_REARRIGHT,
605 [kAudioChannelLabel_RearSurroundLeft] = AOUT_CHAN_MIDDLELEFT, // needs to be swapped with rear
606 [kAudioChannelLabel_RearSurroundRight] = AOUT_CHAN_MIDDLERIGHT,// needs to be swapped with rear
607 [kAudioChannelLabel_CenterSurround] = AOUT_CHAN_REARCENTER
610 /* We want more than stereo and we can do that */
611 for (unsigned int i = 0; i < layout->mNumberChannelDescriptions; i++) {
613 msg_Dbg(p_aout, "this is channel: %d", (int)layout->mChannelDescriptions[i].mChannelLabel);
616 AudioChannelLabel chan = layout->mChannelDescriptions[i].mChannelLabel;
617 if(chan < sizeof(i_auhal_channel_mapping) / sizeof(i_auhal_channel_mapping[0])
618 && i_auhal_channel_mapping[chan] > 0) {
619 fmt->i_physical_channels |= i_auhal_channel_mapping[chan];
621 msg_Dbg(p_aout, "found nonrecognized channel %d at index %d", chan, i);
624 if (fmt->i_physical_channels == 0) {
625 fmt->i_physical_channels = AOUT_CHANS_STEREO;
626 msg_Err(p_aout, "You should configure your speaker layout with Audio Midi Setup in /Applications/Utilities. VLC will output Stereo only.");
627 dialog_Fatal(p_aout, _("Audio device is not configured"), "%s",
628 _("You should configure your speaker layout with "
629 "\"Audio Midi Setup\" in /Applications/"
630 "Utilities. VLC will output Stereo only."));
635 msg_Warn(p_aout, "device driver does not support kAudioDevicePropertyPreferredChannelLayout - using stereo fallback [%4.4s]", (char *)&err);
636 fmt->i_physical_channels = AOUT_CHANS_STEREO;
638 fmt->i_original_channels = fmt->i_physical_channels;
640 msg_Dbg(p_aout, "selected %d physical channels for device output", aout_FormatNbChannels(fmt));
641 msg_Dbg(p_aout, "VLC will output: %s", aout_FormatPrintChannels(fmt));
643 /* Now we set the INPUT layout of the AU */
644 AudioChannelLayout input_layout;
645 memset (&input_layout, 0, sizeof(input_layout));
646 uint32_t chans_out[AOUT_CHAN_MAX];
648 /* Some channel abbreviations used below:
653 * Rs - right surround
654 * Cs - center surround
655 * Rls - rear left surround
656 * Rrs - rear right surround
659 * Lsd - left surround direct
660 * Rsd - right surround direct
664 * Vhl - vertical height left
665 * Vhc - vertical height center
666 * Vhr - vertical height right
667 * Lt - left matrix total. for matrix encoded stereo.
668 * Rt - right matrix total. for matrix encoded stereo. */
670 switch(aout_FormatNbChannels(fmt)) {
672 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
675 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
678 if (fmt->i_physical_channels & AOUT_CHAN_CENTER)
679 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_7; // L R C
680 else if (fmt->i_physical_channels & AOUT_CHAN_LFE)
681 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4; // L R LFE
684 if (fmt->i_physical_channels & (AOUT_CHAN_CENTER | AOUT_CHAN_LFE))
685 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_10; // L R C LFE
686 else if (fmt->i_physical_channels & (AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT))
687 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_3; // L R Ls Rs
688 else if (fmt->i_physical_channels & (AOUT_CHAN_CENTER | AOUT_CHAN_REARCENTER))
689 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_3; // L R C Cs
692 if (fmt->i_physical_channels & (AOUT_CHAN_CENTER))
693 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_19; // L R Ls Rs C
694 else if (fmt->i_physical_channels & (AOUT_CHAN_LFE))
695 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_18; // L R Ls Rs LFE
698 if (fmt->i_physical_channels & (AOUT_CHAN_LFE)) {
699 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_20; // L R Ls Rs C LFE
701 chans_out[0] = AOUT_CHAN_LEFT;
702 chans_out[1] = AOUT_CHAN_RIGHT;
703 chans_out[2] = AOUT_CHAN_REARLEFT;
704 chans_out[3] = AOUT_CHAN_REARRIGHT;
705 chans_out[4] = AOUT_CHAN_CENTER;
706 chans_out[5] = AOUT_CHAN_LFE;
708 p_aout->sys->chans_to_reorder = aout_CheckChannelReorder(NULL, chans_out, fmt->i_physical_channels, p_aout->sys->chan_table);
709 if (p_aout->sys->chans_to_reorder)
710 msg_Dbg(p_aout, "channel reordering needed for 5.1 output");
712 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_AudioUnit_6_0; // L R Ls Rs C Cs
714 chans_out[0] = AOUT_CHAN_LEFT;
715 chans_out[1] = AOUT_CHAN_RIGHT;
716 chans_out[2] = AOUT_CHAN_REARLEFT;
717 chans_out[3] = AOUT_CHAN_REARRIGHT;
718 chans_out[4] = AOUT_CHAN_CENTER;
719 chans_out[5] = AOUT_CHAN_REARCENTER;
721 p_aout->sys->chans_to_reorder = aout_CheckChannelReorder(NULL, chans_out, fmt->i_physical_channels, p_aout->sys->chan_table);
722 if (p_aout->sys->chans_to_reorder)
723 msg_Dbg(p_aout, "channel reordering needed for 6.0 output");
727 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A; // L R C LFE Ls Rs Cs
729 chans_out[0] = AOUT_CHAN_LEFT;
730 chans_out[1] = AOUT_CHAN_RIGHT;
731 chans_out[2] = AOUT_CHAN_CENTER;
732 chans_out[3] = AOUT_CHAN_LFE;
733 chans_out[4] = AOUT_CHAN_REARLEFT;
734 chans_out[5] = AOUT_CHAN_REARRIGHT;
735 chans_out[6] = AOUT_CHAN_REARCENTER;
737 p_aout->sys->chans_to_reorder = aout_CheckChannelReorder(NULL, chans_out, fmt->i_physical_channels, p_aout->sys->chan_table);
738 if (p_aout->sys->chans_to_reorder)
739 msg_Dbg(p_aout, "channel reordering needed for 6.1 output");
743 if (fmt->i_physical_channels & (AOUT_CHAN_LFE) || currentMinorSystemVersion < 7) {
744 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A; // L R C LFE Ls Rs Lc Rc
746 chans_out[0] = AOUT_CHAN_LEFT;
747 chans_out[1] = AOUT_CHAN_RIGHT;
748 chans_out[2] = AOUT_CHAN_CENTER;
749 chans_out[3] = AOUT_CHAN_LFE;
750 chans_out[4] = AOUT_CHAN_MIDDLELEFT;
751 chans_out[5] = AOUT_CHAN_MIDDLERIGHT;
752 chans_out[6] = AOUT_CHAN_REARLEFT;
753 chans_out[7] = AOUT_CHAN_REARRIGHT;
755 if (!(fmt->i_physical_channels & (AOUT_CHAN_LFE)))
756 msg_Warn(p_aout, "8.0 audio output not supported on OS X 10.%i, layout will be incorrect", currentMinorSystemVersion);
758 #ifdef MAC_OS_X_VERSION_10_7
760 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DTS_8_0_B; // Lc C Rc L R Ls Cs Rs
762 chans_out[0] = AOUT_CHAN_MIDDLELEFT;
763 chans_out[1] = AOUT_CHAN_CENTER;
764 chans_out[2] = AOUT_CHAN_MIDDLERIGHT;
765 chans_out[3] = AOUT_CHAN_LEFT;
766 chans_out[4] = AOUT_CHAN_RIGHT;
767 chans_out[5] = AOUT_CHAN_REARLEFT;
768 chans_out[6] = AOUT_CHAN_REARCENTER;
769 chans_out[7] = AOUT_CHAN_REARRIGHT;
772 p_aout->sys->chans_to_reorder = aout_CheckChannelReorder(NULL, chans_out, fmt->i_physical_channels, p_aout->sys->chan_table);
773 if (p_aout->sys->chans_to_reorder)
774 msg_Dbg(p_aout, "channel reordering needed for 7.1 / 8.0 output");
778 if (currentMinorSystemVersion < 7) {
779 msg_Warn(p_aout, "8.1 audio output not supported on OS X 10.%i", currentMinorSystemVersion);
783 #ifdef MAC_OS_X_VERSION_10_7
784 input_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DTS_8_1_B; // Lc C Rc L R Ls Cs Rs LFE
785 chans_out[0] = AOUT_CHAN_MIDDLELEFT;
786 chans_out[1] = AOUT_CHAN_CENTER;
787 chans_out[2] = AOUT_CHAN_MIDDLERIGHT;
788 chans_out[3] = AOUT_CHAN_LEFT;
789 chans_out[4] = AOUT_CHAN_RIGHT;
790 chans_out[5] = AOUT_CHAN_REARLEFT;
791 chans_out[6] = AOUT_CHAN_REARCENTER;
792 chans_out[7] = AOUT_CHAN_REARRIGHT;
793 chans_out[8] = AOUT_CHAN_LFE;
795 p_aout->sys->chans_to_reorder = aout_CheckChannelReorder(NULL, chans_out, fmt->i_physical_channels, p_aout->sys->chan_table);
796 if (p_aout->sys->chans_to_reorder)
797 msg_Dbg(p_aout, "channel reordering needed for 8.1 output");
802 /* Set up the format to be used */
803 DeviceFormat.mSampleRate = fmt->i_rate;
804 DeviceFormat.mFormatID = kAudioFormatLinearPCM;
805 p_sys->i_rate = fmt->i_rate;
807 /* We use float 32 since this is VLC's endorsed format */
808 fmt->i_format = VLC_CODEC_FL32;
809 DeviceFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
810 DeviceFormat.mBitsPerChannel = 32;
811 DeviceFormat.mChannelsPerFrame = aout_FormatNbChannels(fmt);
813 /* Calculate framesizes and stuff */
814 DeviceFormat.mFramesPerPacket = 1;
815 DeviceFormat.mBytesPerFrame = DeviceFormat.mBitsPerChannel * DeviceFormat.mChannelsPerFrame / 8;
816 DeviceFormat.mBytesPerPacket = DeviceFormat.mBytesPerFrame * DeviceFormat.mFramesPerPacket;
818 /* Set the desired format */
819 i_param_size = sizeof(AudioStreamBasicDescription);
820 verify_noerr(AudioUnitSetProperty(p_sys->au_unit,
821 kAudioUnitProperty_StreamFormat,
822 kAudioUnitScope_Input,
827 msg_Dbg(p_aout, STREAM_FORMAT_MSG("we set the AU format: " , DeviceFormat));
829 /* Retrieve actual format */
830 verify_noerr(AudioUnitGetProperty(p_sys->au_unit,
831 kAudioUnitProperty_StreamFormat,
832 kAudioUnitScope_Input,
837 msg_Dbg(p_aout, STREAM_FORMAT_MSG("the actual set AU format is " , DeviceFormat));
839 /* Do the last VLC aout setups */
840 aout_FormatPrepare(fmt);
842 /* set the IOproc callback */
843 input.inputProc = (AURenderCallback) RenderCallbackAnalog;
844 input.inputProcRefCon = p_aout;
846 verify_noerr(AudioUnitSetProperty(p_sys->au_unit,
847 kAudioUnitProperty_SetRenderCallback,
848 kAudioUnitScope_Input,
849 0, &input, sizeof(input)));
851 /* Set the input_layout as the layout VLC will use to feed the AU unit */
852 verify_noerr(AudioUnitSetProperty(p_sys->au_unit,
853 kAudioUnitProperty_AudioChannelLayout,
854 kAudioUnitScope_Input, /* yes, it must be the INPUT scope */
855 0, &input_layout, sizeof(input_layout)));
858 verify_noerr(AudioUnitInitialize(p_sys->au_unit));
860 /* setup circular buffer */
861 TPCircularBufferInit(&p_sys->circular_buffer, AUDIO_BUFFER_SIZE_IN_SECONDS *
862 fmt->i_rate * fmt->i_bytes_per_frame);
864 verify_noerr(AudioOutputUnitStart(p_sys->au_unit));
866 /* Set volume for output unit */
867 VolumeSet(p_aout, p_sys->f_volume);
868 MuteSet(p_aout, p_sys->b_mute);
874 * StartSPDIF: Setup an encoded digital stream (SPDIF) output
876 static int StartSPDIF(audio_output_t * p_aout, audio_sample_format_t *fmt)
878 struct aout_sys_t *p_sys = p_aout->sys;
879 OSStatus err = noErr;
880 UInt32 i_param_size = 0, b_mix = 0;
881 Boolean b_writeable = false;
882 AudioStreamID *p_streams = NULL;
883 unsigned i_streams = 0;
884 AudioStreamBasicDescription desired_stream_format;
885 memset(&desired_stream_format, 0, sizeof(desired_stream_format));
887 /* Start doing the SPDIF setup proces */
888 p_sys->b_digital = true;
891 AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
892 i_param_size = sizeof(p_sys->i_hog_pid);
893 p_sys->i_hog_pid = getpid() ;
896 * HACK: On 10.6, auhal will trigger the streams changed callback when calling below line,
897 * directly in the same thread. This call needs to be ignored to avoid endless restarting.
899 p_sys->b_ignore_streams_changed_callback = true;
900 err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, i_param_size, &p_sys->i_hog_pid);
901 p_sys->b_ignore_streams_changed_callback = false;
904 msg_Err(p_aout, "failed to set hogmode [%4.4s]", (char *)&err);
908 AudioObjectPropertyAddress audioDeviceSupportsMixingAddress = { kAudioDevicePropertySupportsMixing , kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
910 if (AudioObjectHasProperty(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress)) {
911 /* Set mixable to false if we are allowed to */
912 err = AudioObjectIsPropertySettable(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, &b_writeable);
913 err = AudioObjectGetPropertyDataSize(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size);
914 err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size, &b_mix);
916 if (err == noErr && b_writeable) {
918 err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, i_param_size, &b_mix);
919 p_sys->b_changed_mixing = true;
923 msg_Err(p_aout, "failed to set mixmode [%4.4s]", (char *)&err);
928 /* Get a list of all the streams on this device */
929 AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
930 err = AudioObjectGetPropertyDataSize(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size);
932 msg_Err(p_aout, "could not get size of stream description packet [%4.4s]", (char *)&err);
936 i_streams = i_param_size / sizeof(AudioStreamID);
937 p_streams = (AudioStreamID *)malloc(i_param_size);
938 if (p_streams == NULL)
941 err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size, p_streams);
944 msg_Err(p_aout, "could not fetch stream descriptions [%4.4s]", (char *)&err);
949 AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 };
950 for (unsigned i = 0; i < i_streams && p_sys->i_stream_index < 0 ; i++) {
951 /* Find a stream with a cac3 stream */
952 AudioStreamRangedDescription *p_format_list = NULL;
954 bool b_digital = false;
956 /* Retrieve all the stream formats supported by each output stream */
957 err = AudioObjectGetPropertyDataSize(p_streams[i], &physicalFormatsAddress, 0, NULL, &i_param_size);
959 msg_Err(p_aout, "could not get number of streamformats: [%4.4s] (%i)", (char *)&err, (int32_t)err);
963 i_formats = i_param_size / sizeof(AudioStreamRangedDescription);
964 p_format_list = (AudioStreamRangedDescription *)malloc(i_param_size);
965 if (p_format_list == NULL)
968 err = AudioObjectGetPropertyData(p_streams[i], &physicalFormatsAddress, 0, NULL, &i_param_size, p_format_list);
970 msg_Err(p_aout, "could not get the list of streamformats: [%4.4s]", (char *)&err);
975 /* Check if one of the supported formats is a digital format */
976 for (int j = 0; j < i_formats; j++) {
977 if (p_format_list[j].mFormat.mFormatID == 'IAC3' ||
978 p_format_list[j].mFormat.mFormatID == 'iac3' ||
979 p_format_list[j].mFormat.mFormatID == kAudioFormat60958AC3 ||
980 p_format_list[j].mFormat.mFormatID == kAudioFormatAC3) {
987 /* if this stream supports a digital (cac3) format, then go set it. */
988 int i_requested_rate_format = -1;
989 int i_current_rate_format = -1;
990 int i_backup_rate_format = -1;
992 if (!p_sys->b_revert) {
993 AudioObjectPropertyAddress currentPhysicalFormatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
994 /* Retrieve the original format of this stream first if not done so already */
995 AudioStreamBasicDescription current_streamformat;
996 i_param_size = sizeof(current_streamformat);
997 err = AudioObjectGetPropertyData(p_streams[i], ¤tPhysicalFormatAddress, 0, NULL, &i_param_size, ¤t_streamformat);
999 msg_Err(p_aout, "could not retrieve the original streamformat [%4.4s]", (char *)&err);
1004 * Only the first found format id is accepted. In case of another id later on, we still use the
1005 * already saved one. This can happen if the user plugs in a spdif cable while a stream is already
1006 * playing. Then, auhal already misleadingly reports an ac3 format here whereas the original format
1007 * should be still pcm.
1009 if (p_sys->sfmt_revert.mFormatID > 0 && p_sys->sfmt_revert.mFormatID != current_streamformat.mFormatID &&
1010 p_streams[i] == p_sys->i_stream_id) {
1011 msg_Warn(p_aout, STREAM_FORMAT_MSG("Detected current stream format: ", current_streamformat));
1012 msg_Warn(p_aout, "... there is another stream format already stored, the current one is ignored");
1014 p_sys->sfmt_revert = current_streamformat;
1017 p_sys->b_revert = true;
1020 p_sys->i_stream_id = p_streams[i];
1021 p_sys->i_stream_index = i;
1023 for (int j = 0; j < i_formats; j++) {
1024 if (p_format_list[j].mFormat.mFormatID == 'IAC3' ||
1025 p_format_list[j].mFormat.mFormatID == 'iac3' ||
1026 p_format_list[j].mFormat.mFormatID == kAudioFormat60958AC3 ||
1027 p_format_list[j].mFormat.mFormatID == kAudioFormatAC3) {
1028 if (p_format_list[j].mFormat.mSampleRate == fmt->i_rate) {
1029 i_requested_rate_format = j;
1031 } else if (p_format_list[j].mFormat.mSampleRate == p_sys->sfmt_revert.mSampleRate)
1032 i_current_rate_format = j;
1034 if (i_backup_rate_format < 0 || p_format_list[j].mFormat.mSampleRate > p_format_list[i_backup_rate_format].mFormat.mSampleRate)
1035 i_backup_rate_format = j;
1041 if (i_requested_rate_format >= 0) /* We prefer to output at the samplerate of the original audio */
1042 desired_stream_format = p_format_list[i_requested_rate_format].mFormat;
1043 else if (i_current_rate_format >= 0) /* If not possible, we will try to use the current samplerate of the device */
1044 desired_stream_format = p_format_list[i_current_rate_format].mFormat;
1046 desired_stream_format = p_format_list[i_backup_rate_format].mFormat; /* And if we have to, any digital format will be just fine (highest rate possible) */
1048 free(p_format_list);
1052 msg_Dbg(p_aout, STREAM_FORMAT_MSG("original stream format: ", p_sys->sfmt_revert));
1054 if (!AudioStreamChangeFormat(p_aout, p_sys->i_stream_id, desired_stream_format)) {
1055 msg_Err(p_aout, "failed to change stream format for SPDIF output");
1059 /* Set the format flags */
1060 if (desired_stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian)
1061 fmt->i_format = VLC_CODEC_SPDIFB;
1063 fmt->i_format = VLC_CODEC_SPDIFL;
1064 fmt->i_bytes_per_frame = AOUT_SPDIF_SIZE;
1065 fmt->i_frame_length = A52_FRAME_NB;
1066 fmt->i_rate = (unsigned int)desired_stream_format.mSampleRate;
1067 p_sys->i_rate = fmt->i_rate;
1068 aout_FormatPrepare(fmt);
1070 /* Add IOProc callback */
1071 err = AudioDeviceCreateIOProcID(p_sys->i_selected_dev,
1072 (AudioDeviceIOProc)RenderCallbackSPDIF,
1076 msg_Err(p_aout, "Failed to create Process ID [%4.4s]", (char *)&err);
1081 err = AudioDeviceStart(p_sys->i_selected_dev, p_sys->i_procID);
1083 msg_Err(p_aout, "Failed to start audio device [%4.4s]", (char *)&err);
1085 err = AudioDeviceDestroyIOProcID(p_sys->i_selected_dev, p_sys->i_procID);
1087 msg_Err(p_aout, "Failed to destroy process ID [%4.4s]", (char *)&err);
1092 /* setup circular buffer */
1093 TPCircularBufferInit(&p_sys->circular_buffer, 200 * AOUT_SPDIF_SIZE);
1098 static void Stop(audio_output_t *p_aout)
1100 struct aout_sys_t *p_sys = p_aout->sys;
1101 OSStatus err = noErr;
1102 UInt32 i_param_size = 0;
1104 msg_Dbg(p_aout, "Stopping the auhal module");
1106 if (p_sys->au_unit) {
1107 verify_noerr(AudioOutputUnitStop(p_sys->au_unit));
1108 verify_noerr(AudioUnitUninitialize(p_sys->au_unit));
1109 verify_noerr(AudioComponentInstanceDispose(p_sys->au_unit));
1112 if (p_sys->b_digital) {
1114 err = AudioDeviceStop(p_sys->i_selected_dev,
1117 msg_Err(p_aout, "Failed to stop audio device [%4.4s]", (char *)&err);
1119 /* Remove IOProc callback */
1120 err = AudioDeviceDestroyIOProcID(p_sys->i_selected_dev,
1123 msg_Err(p_aout, "Failed to destroy Process ID [%4.4s]", (char *)&err);
1125 if (p_sys->b_revert) {
1126 if (!AudioStreamChangeFormat(p_aout, p_sys->i_stream_id, p_sys->sfmt_revert)) {
1127 msg_Err(p_aout, "failed to revert stream format in close");
1131 if (p_sys->b_changed_mixing && p_sys->sfmt_revert.mFormatID != kAudioFormat60958AC3) {
1133 Boolean b_writeable = false;
1134 i_param_size = sizeof(int);
1135 /* Revert mixable to true if we are allowed to */
1136 AudioObjectPropertyAddress audioDeviceSupportsMixingAddress = { kAudioDevicePropertySupportsMixing , kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
1137 err = AudioObjectIsPropertySettable(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, &b_writeable);
1138 err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size, &b_mix);
1140 if (err == noErr && b_writeable) {
1141 msg_Dbg(p_aout, "mixable is: %d", b_mix);
1143 err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, i_param_size, &b_mix);
1147 msg_Err(p_aout, "failed to re-set mixmode [%4.4s]", (char *)&err);
1151 if (p_sys->i_hog_pid == getpid()) {
1152 p_sys->i_hog_pid = -1;
1153 i_param_size = sizeof(p_sys->i_hog_pid);
1154 AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode,
1155 kAudioDevicePropertyScopeOutput,
1156 kAudioObjectPropertyElementMaster };
1159 * HACK: On 10.6, auhal will trigger the streams changed callback when calling below line,
1160 * directly in the same thread. This call needs to be ignored to avoid endless restarting.
1162 p_sys->b_ignore_streams_changed_callback = true;
1163 err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, i_param_size, &p_sys->i_hog_pid);
1164 p_sys->b_ignore_streams_changed_callback = false;
1166 msg_Err(p_aout, "Failed to release hogmode [%4.4s]", (char *)&err);
1169 /* remove audio device alive callback */
1170 AudioObjectPropertyAddress deviceAliveAddress = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
1171 err = AudioObjectRemovePropertyListener(p_sys->i_selected_dev, &deviceAliveAddress, DeviceAliveListener, (void *)p_aout);
1173 /* Be tolerant, only give a warning here */
1174 msg_Warn(p_aout, "failed to remove audio device life checker [%4.4s]", (char *)&err);
1177 p_sys->i_bytes_per_sample = 0;
1178 p_sys->b_digital = false;
1180 /* clean-up circular buffer */
1181 TPCircularBufferCleanup(&p_sys->circular_buffer);
1185 #pragma mark core interaction
1187 static void ReportDevice(audio_output_t *p_aout, UInt32 i_id, char *name)
1190 sprintf(deviceid, "%i", i_id);
1192 aout_HotplugReport(p_aout, deviceid, name);
1195 static void RebuildDeviceList(audio_output_t * p_aout)
1197 OSStatus err = noErr;
1198 UInt32 propertySize = 0;
1199 AudioObjectID *deviceIDs;
1200 UInt32 numberOfDevices;
1201 CFMutableArrayRef currentListOfDevices;
1203 struct aout_sys_t *p_sys = p_aout->sys;
1205 msg_Dbg(p_aout, "Rebuild device list");
1207 ReportDevice(p_aout, 0, _("System Sound Output Device"));
1209 /* setup local array */
1210 currentListOfDevices = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1212 /* Get number of devices */
1213 AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
1214 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &propertySize);
1216 msg_Err(p_aout, "Could not get number of devices: [%4.4s]", (char *)&err);
1217 CFRelease(currentListOfDevices);
1221 numberOfDevices = propertySize / sizeof(AudioDeviceID);
1223 if (numberOfDevices < 1) {
1224 msg_Err(p_aout, "No audio output devices found.");
1225 CFRelease(currentListOfDevices);
1228 msg_Dbg(p_aout, "found %i audio device(s)", numberOfDevices);
1230 /* Allocate DeviceID array */
1231 deviceIDs = (AudioDeviceID *)calloc(numberOfDevices, sizeof(AudioDeviceID));
1232 if (deviceIDs == NULL) {
1233 CFRelease(currentListOfDevices);
1237 /* Populate DeviceID array */
1238 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &propertySize, deviceIDs);
1240 msg_Err(p_aout, "could not get the device IDs [%4.4s]", (char *)&err);
1241 CFRelease(currentListOfDevices);
1246 AudioObjectPropertyAddress deviceNameAddress = { kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
1248 for (unsigned int i = 0; i < numberOfDevices; i++) {
1249 CFStringRef device_name_ref;
1252 UInt32 i_id = deviceIDs[i];
1254 /* Retrieve the length of the device name */
1255 err = AudioObjectGetPropertyDataSize(deviceIDs[i], &deviceNameAddress, 0, NULL, &propertySize);
1257 msg_Dbg(p_aout, "failed to get name size for device %i", deviceIDs[i]);
1261 /* Retrieve the name of the device */
1262 err = AudioObjectGetPropertyData(deviceIDs[i], &deviceNameAddress, 0, NULL, &propertySize, &device_name_ref);
1264 msg_Dbg(p_aout, "failed to get name for device %i", deviceIDs[i]);
1267 length = CFStringGetLength(device_name_ref);
1269 psz_name = malloc(length);
1271 CFRelease(device_name_ref);
1274 CFStringGetCString(device_name_ref, psz_name, length, kCFStringEncodingUTF8);
1275 CFRelease(device_name_ref);
1277 msg_Dbg(p_aout, "DevID: %i DevName: %s", deviceIDs[i], psz_name);
1279 if (!AudioDeviceHasOutput(deviceIDs[i])) {
1280 msg_Dbg(p_aout, "this '%s' is INPUT only. skipping...", psz_name);
1285 ReportDevice(p_aout, i_id, psz_name);
1286 CFNumberRef deviceNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i_id);
1287 CFArrayAppendValue(currentListOfDevices, deviceNumber);
1288 CFRelease(deviceNumber);
1290 if (AudioDeviceSupportsDigital(p_aout, deviceIDs[i])) {
1291 msg_Dbg(p_aout, "'%s' supports digital output", psz_name);
1292 char *psz_encoded_name = nil;
1293 asprintf(&psz_encoded_name, _("%s (Encoded Output)"), psz_name);
1294 i_id = i_id | AOUT_VAR_SPDIF_FLAG;
1295 ReportDevice(p_aout, i_id, psz_encoded_name);
1296 deviceNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i_id);
1297 CFArrayAppendValue(currentListOfDevices, deviceNumber);
1298 CFRelease(deviceNumber);
1299 free(psz_encoded_name);
1302 // TODO: only register once for each device
1303 ManageAudioStreamsCallback(p_aout, deviceIDs[i], true);
1308 vlc_mutex_lock(&p_sys->device_list_lock);
1310 if (p_sys->device_list)
1311 count = CFArrayGetCount(p_sys->device_list);
1312 CFRange newListSearchRange = CFRangeMake(0, CFArrayGetCount(currentListOfDevices));
1315 msg_Dbg(p_aout, "Looking for removed devices");
1316 CFNumberRef cfn_device_id;
1317 int i_device_id = 0;
1318 for (CFIndex x = 0; x < count; x++) {
1319 if (!CFArrayContainsValue(currentListOfDevices, newListSearchRange, CFArrayGetValueAtIndex(p_sys->device_list, x))) {
1320 cfn_device_id = CFArrayGetValueAtIndex(p_sys->device_list, x);
1321 if (cfn_device_id) {
1322 CFNumberGetValue(cfn_device_id, kCFNumberSInt32Type, &i_device_id);
1323 msg_Dbg(p_aout, "Device ID %i is not found in new array, deleting.", i_device_id);
1325 ReportDevice(p_aout, i_device_id, NULL);
1330 if (p_sys->device_list)
1331 CFRelease(p_sys->device_list);
1332 p_sys->device_list = CFArrayCreateCopy(kCFAllocatorDefault, currentListOfDevices);
1333 CFRelease(currentListOfDevices);
1334 vlc_mutex_unlock(&p_sys->device_list_lock);
1339 static int SwitchAudioDevice(audio_output_t *p_aout, const char *name)
1341 struct aout_sys_t *p_sys = p_aout->sys;
1344 p_sys->i_new_selected_dev = atoi(name);
1346 p_sys->i_new_selected_dev = 0;
1348 bool b_supports_digital = (p_sys->i_new_selected_dev & AOUT_VAR_SPDIF_FLAG);
1349 if (b_supports_digital)
1350 p_sys->b_selected_dev_is_digital = true;
1352 p_sys->b_selected_dev_is_digital = false;
1354 p_sys->i_new_selected_dev = p_sys->i_new_selected_dev & ~AOUT_VAR_SPDIF_FLAG;
1356 aout_DeviceReport(p_aout, name);
1357 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
1362 static int VolumeSet(audio_output_t * p_aout, float volume)
1364 struct aout_sys_t *p_sys = p_aout->sys;
1365 OSStatus ostatus = 0;
1367 if(p_sys->b_digital)
1368 return VLC_EGENERIC;
1370 p_sys->f_volume = volume;
1371 aout_VolumeReport(p_aout, volume);
1373 /* Set volume for output unit */
1374 if(!p_sys->b_mute) {
1375 ostatus = AudioUnitSetParameter(p_sys->au_unit,
1376 kHALOutputParam_Volume,
1377 kAudioUnitScope_Global,
1379 volume * volume * volume,
1383 if (var_InheritBool(p_aout, "volume-save"))
1384 config_PutInt(p_aout, "auhal-volume", lroundf(volume * AOUT_VOLUME_DEFAULT));
1389 static int MuteSet(audio_output_t * p_aout, bool mute)
1391 struct aout_sys_t *p_sys = p_aout->sys;
1394 if(p_sys->b_digital)
1395 return VLC_EGENERIC;
1397 p_sys->b_mute = mute;
1398 aout_MuteReport(p_aout, mute);
1402 volume = p_sys->f_volume;
1404 ostatus = AudioUnitSetParameter(p_sys->au_unit,
1405 kHALOutputParam_Volume,
1406 kAudioUnitScope_Global,
1408 volume * volume * volume,
1415 #pragma mark actual playback
1417 static void Play(audio_output_t * p_aout, block_t * p_block)
1419 struct aout_sys_t *p_sys = p_aout->sys;
1421 if (p_block->i_nb_samples > 0) {
1422 /* Do the channel reordering */
1423 if (p_sys->chans_to_reorder && !p_sys->b_digital) {
1424 aout_ChannelReorder(p_block->p_buffer,
1426 p_sys->chans_to_reorder,
1431 /* move data to buffer */
1432 if (unlikely(!TPCircularBufferProduceBytes(&p_sys->circular_buffer, p_block->p_buffer, p_block->i_buffer)))
1433 msg_Warn(p_aout, "dropped buffer");
1435 if (!p_sys->i_bytes_per_sample)
1436 p_sys->i_bytes_per_sample = p_block->i_buffer / p_block->i_nb_samples;
1439 block_Release(p_block);
1442 static void Pause(audio_output_t *p_aout, bool pause, mtime_t date)
1444 struct aout_sys_t * p_sys = p_aout->sys;
1447 vlc_mutex_lock(&p_sys->lock);
1448 p_sys->b_paused = pause;
1449 vlc_mutex_unlock(&p_sys->lock);
1452 static void Flush(audio_output_t *p_aout, bool wait)
1454 struct aout_sys_t *p_sys = p_aout->sys;
1456 int32_t availableBytes;
1457 vlc_mutex_lock(&p_sys->lock);
1458 TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes);
1461 while (availableBytes > 0) {
1462 vlc_cond_wait(&p_sys->cond, &p_sys->lock);
1463 TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes);
1466 /* flush circular buffer if data is left */
1467 if (availableBytes > 0)
1468 TPCircularBufferClear(&p_aout->sys->circular_buffer);
1471 vlc_mutex_unlock(&p_sys->lock);
1474 static int TimeGet(audio_output_t *p_aout, mtime_t *delay)
1476 struct aout_sys_t * p_sys = p_aout->sys;
1478 if (!p_sys->i_bytes_per_sample)
1481 int32_t availableBytes;
1482 TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes);
1484 *delay = ((availableBytes / p_sys->i_bytes_per_sample) + p_sys->i_device_latency) * CLOCK_FREQ / p_sys->i_rate;
1489 /*****************************************************************************
1490 * RenderCallbackAnalog: This function is called everytime the AudioUnit wants
1491 * us to provide some more audio data.
1492 * Don't print anything during normal playback, calling blocking function from
1493 * this callback is not allowed.
1494 *****************************************************************************/
1495 static OSStatus RenderCallbackAnalog(vlc_object_t *p_obj,
1496 AudioUnitRenderActionFlags *ioActionFlags,
1497 const AudioTimeStamp *inTimeStamp,
1499 UInt32 inNumberFrames,
1500 AudioBufferList *ioData) {
1501 VLC_UNUSED(ioActionFlags);
1502 VLC_UNUSED(inTimeStamp);
1503 VLC_UNUSED(inBusNumber);
1504 VLC_UNUSED(inNumberFrames);
1506 audio_output_t * p_aout = (audio_output_t *)p_obj;
1507 struct aout_sys_t * p_sys = p_aout->sys;
1509 int bytesRequested = ioData->mBuffers[0].mDataByteSize;
1510 Float32 *targetBuffer = (Float32*)ioData->mBuffers[0].mData;
1511 if (unlikely(bytesRequested == 0)) /* cannot be negative */
1514 vlc_mutex_lock(&p_sys->lock);
1515 /* Pull audio from buffer */
1516 int32_t availableBytes;
1517 Float32 *buffer = TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes);
1519 /* check if we have enough data */
1520 if (!availableBytes || p_sys->b_paused) {
1521 /* return an empty buffer so silence is played until we have data */
1522 memset(targetBuffer, 0, bytesRequested);
1524 int32_t bytesToCopy = __MIN(bytesRequested, availableBytes);
1526 memcpy(targetBuffer, buffer, bytesToCopy);
1527 TPCircularBufferConsume(&p_sys->circular_buffer, bytesToCopy);
1528 ioData->mBuffers[0].mDataByteSize = bytesToCopy;
1531 vlc_cond_signal(&p_sys->cond);
1532 vlc_mutex_unlock(&p_sys->lock);
1538 * RenderCallbackSPDIF: callback for SPDIF audio output
1540 static OSStatus RenderCallbackSPDIF(AudioDeviceID inDevice,
1541 const AudioTimeStamp * inNow,
1542 const void * inInputData,
1543 const AudioTimeStamp * inInputTime,
1544 AudioBufferList * outOutputData,
1545 const AudioTimeStamp * inOutputTime,
1546 void * threadGlobals)
1549 VLC_UNUSED(inDevice);
1550 VLC_UNUSED(inInputData);
1551 VLC_UNUSED(inInputTime);
1552 VLC_UNUSED(inOutputTime);
1554 audio_output_t * p_aout = (audio_output_t *)threadGlobals;
1555 struct aout_sys_t * p_sys = p_aout->sys;
1557 int bytesRequested = outOutputData->mBuffers[p_sys->i_stream_index].mDataByteSize;
1558 char *targetBuffer = outOutputData->mBuffers[p_sys->i_stream_index].mData;
1559 if (unlikely(bytesRequested == 0)) /* cannot be negative */
1562 vlc_mutex_lock(&p_sys->lock);
1563 /* Pull audio from buffer */
1564 int32_t availableBytes;
1565 char *buffer = TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes);
1567 /* check if we have enough data */
1568 if (!availableBytes || p_sys->b_paused) {
1569 /* return an empty buffer so silence is played until we have data */
1570 memset(targetBuffer, 0, bytesRequested);
1572 int32_t bytesToCopy = __MIN(bytesRequested, availableBytes);
1574 memcpy(targetBuffer, buffer, bytesToCopy);
1575 TPCircularBufferConsume(&p_sys->circular_buffer, bytesToCopy);
1576 outOutputData->mBuffers[p_sys->i_stream_index].mDataByteSize = bytesToCopy;
1579 vlc_cond_signal(&p_sys->cond);
1580 vlc_mutex_unlock(&p_sys->lock);
1586 #pragma mark Stream / Hardware Listeners
1589 * Callback when device list changed
1591 static OSStatus DevicesListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
1593 VLC_UNUSED(inObjectID);
1594 VLC_UNUSED(inNumberAddresses);
1595 VLC_UNUSED(inAddresses);
1597 audio_output_t *p_aout = (audio_output_t *)inClientData;
1600 aout_sys_t *p_sys = p_aout->sys;
1602 msg_Dbg(p_aout, "audio device configuration changed, resetting cache");
1603 RebuildDeviceList(p_aout);
1605 vlc_mutex_lock(&p_sys->selected_device_lock);
1606 vlc_mutex_lock(&p_sys->device_list_lock);
1607 CFNumberRef selectedDevice = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &p_sys->i_selected_dev);
1608 if(!CFArrayContainsValue(p_sys->device_list, CFRangeMake(0, CFArrayGetCount(p_sys->device_list)), selectedDevice))
1609 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
1610 CFRelease(selectedDevice);
1611 vlc_mutex_unlock(&p_sys->device_list_lock);
1612 vlc_mutex_unlock(&p_sys->selected_device_lock);
1618 * Callback when current device is not alive anymore
1620 static OSStatus DeviceAliveListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
1622 VLC_UNUSED(inObjectID);
1623 VLC_UNUSED(inNumberAddresses);
1624 VLC_UNUSED(inAddresses);
1626 audio_output_t *p_aout = (audio_output_t *)inClientData;
1630 msg_Warn(p_aout, "audio device died, resetting aout");
1631 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
1637 * Callback when default audio device changed
1639 static OSStatus DefaultDeviceChangedListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
1641 VLC_UNUSED(inObjectID);
1642 VLC_UNUSED(inNumberAddresses);
1643 VLC_UNUSED(inAddresses);
1645 audio_output_t *p_aout = (audio_output_t *)inClientData;
1649 aout_sys_t *p_sys = p_aout->sys;
1651 if (!p_aout->sys->b_selected_dev_is_default)
1654 AudioObjectID defaultDeviceID = 0;
1655 UInt32 propertySize = sizeof(AudioObjectID);
1656 AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
1657 propertySize = sizeof(AudioObjectID);
1658 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultDeviceAddress, 0, NULL, &propertySize, &defaultDeviceID);
1660 msg_Err(p_aout, "could not get default audio device [%4.4s]", (char *)&err);
1664 msg_Dbg(p_aout, "default device changed to %i", defaultDeviceID);
1666 /* Default device is changed by the os to allow other apps to play sound while in digital
1667 mode. But this should not affect ourself. */
1668 if (p_aout->sys->b_digital) {
1669 msg_Dbg(p_aout, "ignore, as digital mode is active");
1673 vlc_mutex_lock(&p_sys->selected_device_lock);
1674 /* Also ignore events which announce the same device id */
1675 if(defaultDeviceID != p_aout->sys->i_selected_dev) {
1676 msg_Dbg(p_aout, "default device actually changed, resetting aout");
1677 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
1679 vlc_mutex_unlock(&p_sys->selected_device_lock);
1685 * Callback when physical formats for device change
1687 static OSStatus StreamsChangedListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
1689 OSStatus err = noErr;
1690 UInt32 i_param_size = 0;
1691 AudioStreamID *p_streams = NULL;
1694 VLC_UNUSED(inNumberAddresses);
1695 VLC_UNUSED(inAddresses);
1697 audio_output_t *p_aout = (audio_output_t *)inClientData;
1701 aout_sys_t *p_sys = p_aout->sys;
1702 if(unlikely(p_sys->b_ignore_streams_changed_callback == true))
1705 msg_Dbg(p_aout, "available physical formats for audio device changed");
1706 RebuildDeviceList(p_aout);
1708 vlc_mutex_lock(&p_sys->selected_device_lock);
1709 /* In this case audio has not yet started. Below code will not work and is not needed here. */
1710 if (p_sys->i_selected_dev == 0) {
1711 vlc_mutex_unlock(&p_sys->selected_device_lock);
1716 * check if changed stream id belongs to current device
1718 AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
1719 err = AudioObjectGetPropertyDataSize(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size);
1721 msg_Err(p_aout, "could not get number of streams for device %i [%4.4s]", p_sys->i_selected_dev, (char *)&err);
1722 vlc_mutex_unlock(&p_sys->selected_device_lock);
1723 return VLC_EGENERIC;
1726 i_streams = i_param_size / sizeof(AudioStreamID);
1727 p_streams = (AudioStreamID *)malloc(i_param_size);
1728 if (p_streams == NULL) {
1729 vlc_mutex_unlock(&p_sys->selected_device_lock);
1733 err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size, p_streams);
1735 msg_Err(p_aout, "could not get list of streams [%4.4s]", (char *)&err);
1736 vlc_mutex_unlock(&p_sys->selected_device_lock);
1738 return VLC_EGENERIC;
1740 vlc_mutex_unlock(&p_sys->selected_device_lock);
1742 for (int i = 0; i < i_streams; i++) {
1743 if (p_streams[i] == inObjectID) {
1744 msg_Dbg(p_aout, "Restart aout as this affects current device");
1745 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
1755 * StreamListener: check whether the device's physical format change is complete
1757 static OSStatus StreamListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
1759 OSStatus err = noErr;
1760 struct { vlc_mutex_t lock; vlc_cond_t cond; } * w = inClientData;
1762 VLC_UNUSED(inObjectID);
1764 for (unsigned int i = 0; i < inNumberAddresses; i++) {
1765 if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat) {
1766 int canc = vlc_savecancel();
1767 vlc_mutex_lock(&w->lock);
1768 vlc_cond_signal(&w->cond);
1769 vlc_mutex_unlock(&w->lock);
1770 vlc_restorecancel(canc);
1778 #pragma mark helpers
1780 static int ManageAudioStreamsCallback(audio_output_t *p_aout, AudioDeviceID i_dev_id, bool b_register)
1782 OSStatus err = noErr;
1783 UInt32 i_param_size = 0;
1784 AudioStreamID *p_streams = NULL;
1787 /* Retrieve all the output streams */
1788 AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
1789 err = AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &i_param_size);
1791 msg_Err(p_aout, "could not get number of streams for device id %i [%4.4s]", i_dev_id, (char *)&err);
1792 return VLC_EGENERIC;
1795 i_streams = i_param_size / sizeof(AudioStreamID);
1796 p_streams = (AudioStreamID *)malloc(i_param_size);
1797 if (p_streams == NULL)
1800 err = AudioObjectGetPropertyData(i_dev_id, &streamsAddress, 0, NULL, &i_param_size, p_streams);
1802 msg_Err(p_aout, "could not get list of streams [%4.4s]", (char *)&err);
1804 return VLC_EGENERIC;
1807 for (int i = 0; i < i_streams; i++) {
1808 /* get notified when physical formats change */
1809 AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 };
1812 err = AudioObjectAddPropertyListener(p_streams[i], &physicalFormatsAddress, StreamsChangedListener, (void *)p_aout);
1814 // nope just means that we already have a callback
1815 if (err == kAudioHardwareIllegalOperationError) {
1816 msg_Warn(p_aout, "could not set audio stream formats property callback on stream id %i, callback already set? [%4.4s]", p_streams[i],
1819 msg_Err(p_aout, "could not set audio stream formats property callback on stream id %i [%4.4s]", p_streams[i],
1824 } else { /* unregister callback */
1825 err = AudioObjectRemovePropertyListener(p_streams[i], &physicalFormatsAddress, StreamsChangedListener, (void *)p_aout);
1827 msg_Err(p_aout, "failed to remove audio device property streams callback [%4.4s]", (char *)&err);
1837 * AudioDeviceHasOutput: Checks if the device is actually an output device
1839 static int AudioDeviceHasOutput(AudioDeviceID i_dev_id)
1841 UInt32 dataSize = 0;
1844 AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
1845 status = AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &dataSize);
1847 if (dataSize == 0 || status != noErr)
1854 * AudioDeviceSupportsDigital: Checks if device supports raw bitstreams
1856 static int AudioDeviceSupportsDigital(audio_output_t *p_aout, AudioDeviceID i_dev_id)
1858 OSStatus err = noErr;
1859 UInt32 i_param_size = 0;
1860 AudioStreamID *p_streams = NULL;
1862 bool b_return = false;
1864 /* Retrieve all the output streams */
1865 AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
1866 err = AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &i_param_size);
1868 msg_Err(p_aout, "could not get number of streams [%4.4s] (%i)", (char *)&err, (int32_t)err);
1872 i_streams = i_param_size / sizeof(AudioStreamID);
1873 p_streams = (AudioStreamID *)malloc(i_param_size);
1874 if (p_streams == NULL)
1877 err = AudioObjectGetPropertyData(i_dev_id, &streamsAddress, 0, NULL, &i_param_size, p_streams);
1879 msg_Err(p_aout, "could not get list of streams [%4.4s]", (char *)&err);
1884 for (int i = 0; i < i_streams; i++) {
1885 if (AudioStreamSupportsDigital(p_aout, p_streams[i]))
1894 * AudioStreamSupportsDigital: Checks if audio stream is compatible with raw bitstreams
1896 static int AudioStreamSupportsDigital(audio_output_t *p_aout, AudioStreamID i_stream_id)
1898 OSStatus err = noErr;
1899 UInt32 i_param_size = 0;
1900 AudioStreamRangedDescription *p_format_list = NULL;
1902 bool b_return = false;
1904 /* Retrieve all the stream formats supported by each output stream */
1905 AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 };
1906 err = AudioObjectGetPropertyDataSize(i_stream_id, &physicalFormatsAddress, 0, NULL, &i_param_size);
1908 msg_Err(p_aout, "could not get number of streamformats [%4.4s] (%i)", (char *)&err, (int32_t)err);
1912 i_formats = i_param_size / sizeof(AudioStreamRangedDescription);
1913 msg_Dbg(p_aout, "found %i stream formats for stream id %i", i_formats, i_stream_id);
1915 p_format_list = (AudioStreamRangedDescription *)malloc(i_param_size);
1916 if (p_format_list == NULL)
1919 err = AudioObjectGetPropertyData(i_stream_id, &physicalFormatsAddress, 0, NULL, &i_param_size, p_format_list);
1921 msg_Err(p_aout, "could not get the list of streamformats [%4.4s]", (char *)&err);
1922 free(p_format_list);
1923 p_format_list = NULL;
1927 for (int i = 0; i < i_formats; i++) {
1929 msg_Dbg(p_aout, STREAM_FORMAT_MSG("supported format: ", p_format_list[i].mFormat));
1932 if (p_format_list[i].mFormat.mFormatID == 'IAC3' ||
1933 p_format_list[i].mFormat.mFormatID == 'iac3' ||
1934 p_format_list[i].mFormat.mFormatID == kAudioFormat60958AC3 ||
1935 p_format_list[i].mFormat.mFormatID == kAudioFormatAC3)
1939 free(p_format_list);
1944 * AudioStreamChangeFormat: switch stream format based on the provided description
1946 static int AudioStreamChangeFormat(audio_output_t *p_aout, AudioStreamID i_stream_id, AudioStreamBasicDescription change_format)
1948 OSStatus err = noErr;
1949 int retValue = false;
1951 AudioObjectPropertyAddress physicalFormatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
1953 struct { vlc_mutex_t lock; vlc_cond_t cond; } w;
1955 msg_Dbg(p_aout, STREAM_FORMAT_MSG("setting stream format: ", change_format));
1957 /* Condition because SetProperty is asynchronious */
1958 vlc_cond_init(&w.cond);
1959 vlc_mutex_init(&w.lock);
1960 vlc_mutex_lock(&w.lock);
1962 /* Install the callback */
1963 err = AudioObjectAddPropertyListener(i_stream_id, &physicalFormatAddress, StreamListener, (void *)&w);
1965 msg_Err(p_aout, "AudioObjectAddPropertyListener for kAudioStreamPropertyPhysicalFormat failed [%4.4s]", (char *)&err);
1970 /* change the format */
1971 err = AudioObjectSetPropertyData(i_stream_id, &physicalFormatAddress, 0, NULL, sizeof(AudioStreamBasicDescription),
1974 msg_Err(p_aout, "could not set the stream format [%4.4s]", (char *)&err);
1979 /* The AudioStreamSetProperty is not only asynchronious (requiring the locks)
1980 * it is also not atomic in its behaviour.
1981 * Therefore we check 9 times before we really give up.
1983 AudioStreamBasicDescription actual_format;
1984 UInt32 i_param_size = sizeof(AudioStreamBasicDescription);
1985 for (int i = 0; i < 9; i++) {
1986 /* Callback is not always invoked. So first check if format is already set. */
1988 mtime_t timeout = mdate() + 500000;
1989 if (vlc_cond_timedwait(&w.cond, &w.lock, timeout))
1990 msg_Dbg(p_aout, "reached timeout");
1993 err = AudioObjectGetPropertyData(i_stream_id, &physicalFormatAddress, 0, NULL, &i_param_size, &actual_format);
1995 msg_Dbg(p_aout, STREAM_FORMAT_MSG("actual format in use: ", actual_format));
1996 if (actual_format.mSampleRate == change_format.mSampleRate &&
1997 actual_format.mFormatID == change_format.mFormatID &&
1998 actual_format.mFramesPerPacket == change_format.mFramesPerPacket) {
1999 /* The right format is now active */
2004 /* We need to check again */
2008 vlc_mutex_unlock(&w.lock);
2010 /* Removing the property listener */
2011 err = AudioObjectRemovePropertyListener(i_stream_id, &physicalFormatAddress, StreamListener, (void *)&w);
2013 msg_Err(p_aout, "AudioStreamRemovePropertyListener failed [%4.4s]", (char *)&err);
2017 vlc_mutex_destroy(&w.lock);
2018 vlc_cond_destroy(&w.cond);