input: add an input_item_t arg to input_CreateFilename()
[vlc.git] / modules / audio_output / auhal.c
bloba9bf3af2f009e523413c3ecdbf5463c6d56b11c2
1 /*****************************************************************************
2 * auhal.c: AUHAL and Coreaudio output plugin
3 *****************************************************************************
4 * Copyright (C) 2005 - 2017 VLC authors and VideoLAN
6 * Authors: Derk-Jan Hartman <hartman at videolan dot org>
7 * Felix Paul Kühne <fkuehne at videolan dot org>
8 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 #pragma mark includes
27 #import "coreaudio_common.h"
29 #import <vlc_plugin.h>
30 #import <vlc_dialog.h> // vlc_dialog_display_error
32 #import <CoreAudio/CoreAudio.h> // AudioDeviceID
33 #import <CoreServices/CoreServices.h>
35 #pragma mark -
36 #pragma mark local prototypes & module descriptor
39 #define AOUT_VOLUME_DEFAULT 256
40 #define AOUT_VOLUME_MAX 512
42 #define VOLUME_TEXT N_("Audio volume")
43 #define VOLUME_LONGTEXT VOLUME_TEXT
45 #define DEVICE_TEXT N_("Last audio device")
46 #define DEVICE_LONGTEXT DEVICE_TEXT
48 static int Open (vlc_object_t *);
49 static void Close (vlc_object_t *);
51 vlc_module_begin ()
52 set_shortname("auhal")
53 set_description(N_("HAL AudioUnit output"))
54 set_capability("audio output", 101)
55 set_category(CAT_AUDIO)
56 set_subcategory(SUBCAT_AUDIO_AOUT)
57 set_callbacks(Open, Close)
58 add_integer("auhal-volume", AOUT_VOLUME_DEFAULT,
59 VOLUME_TEXT, VOLUME_LONGTEXT, true)
60 change_integer_range(0, AOUT_VOLUME_MAX)
61 add_string("auhal-audio-device", "", DEVICE_TEXT, DEVICE_LONGTEXT, true)
62 add_string("auhal-warned-devices", "", NULL, NULL, true)
63 add_obsolete_integer("macosx-audio-device") /* since 2.1.0 */
64 vlc_module_end ()
66 #pragma mark -
67 #pragma mark private declarations
69 #define AOUT_VAR_SPDIF_FLAG 0xf00000
71 /*****************************************************************************
72 * aout_sys_t: private audio output method descriptor
73 *****************************************************************************
74 * This structure is part of the audio output thread descriptor.
75 * It describes the CoreAudio specific properties of an output thread.
76 *****************************************************************************/
77 typedef struct
79 struct aout_sys_common c;
81 /* DeviceID of the selected device */
82 AudioObjectID i_selected_dev;
83 /* DeviceID of device which will be selected on start */
84 AudioObjectID i_new_selected_dev;
85 /* true if the user selected the default audio device (id 0) */
86 bool b_selected_dev_is_default;
88 /* DeviceID of current device */
89 AudioDeviceIOProcID i_procID;
90 /* Are we running in digital mode? */
91 bool b_digital;
93 /* AUHAL specific */
94 AudioUnit au_unit;
96 /* CoreAudio SPDIF mode specific */
97 /* Keep the pid of our hog status */
98 pid_t i_hog_pid;
99 /* The StreamID that has a cac3 streamformat */
100 AudioStreamID i_stream_id;
101 /* The index of i_stream_id in an AudioBufferList */
102 int i_stream_index;
103 /* The original format of the stream */
104 AudioStreamBasicDescription sfmt_revert;
105 /* Whether we need to revert the stream format */
106 bool b_revert;
107 /* Whether we need to set the mixing mode back */
108 bool b_changed_mixing;
110 CFArrayRef device_list;
111 /* protects access to device_list */
112 vlc_mutex_t device_list_lock;
114 /* Synchronizes access to i_selected_dev. This is only needed between VLCs
115 * audio thread and the core audio callback thread. The value is only
116 * changed in Start, further access to this variable within the audio
117 * thread (start, stop, close) needs no protection. */
118 vlc_mutex_t selected_device_lock;
120 float f_volume;
121 bool b_mute;
123 bool b_ignore_streams_changed_callback;
124 } aout_sys_t;
126 #pragma mark -
127 #pragma mark helpers
129 static int
130 AoGetProperty(audio_output_t *p_aout, AudioObjectID id,
131 const AudioObjectPropertyAddress *p_address, size_t i_elm_size,
132 ssize_t i_nb_expected_elms, size_t *p_nb_elms, void **pp_out_data,
133 void *p_allocated_out_data)
135 assert(i_elm_size > 0);
137 /* Get data size */
138 UInt32 i_out_size;
139 OSStatus err = AudioObjectGetPropertyDataSize(id, p_address, 0, NULL,
140 &i_out_size);
141 if (err != noErr)
143 msg_Err(p_aout, "AudioObjectGetPropertyDataSize failed, device id: %i, "
144 "prop: [%4.4s], OSStatus: %d", id, (const char *) &p_address[0],
145 (int)err);
146 return VLC_EGENERIC;
149 size_t i_nb_elms = i_out_size / i_elm_size;
150 if (p_nb_elms != NULL)
151 *p_nb_elms = i_nb_elms;
152 /* Check if we get the expected number of elements */
153 if (i_nb_expected_elms != -1 && (size_t)i_nb_expected_elms != i_nb_elms)
155 msg_Err(p_aout, "AoGetProperty error: expected elements don't match");
156 return VLC_EGENERIC;
159 if (pp_out_data == NULL && p_allocated_out_data == NULL)
160 return VLC_SUCCESS;
162 if (i_out_size == 0)
164 if (pp_out_data != NULL)
165 *pp_out_data = NULL;
166 return VLC_SUCCESS;
169 /* Alloc data or use pre-allocated one */
170 void *p_out_data;
171 if (pp_out_data != NULL)
173 assert(p_allocated_out_data == NULL);
175 *pp_out_data = malloc(i_out_size);
176 if (*pp_out_data == NULL)
177 return VLC_ENOMEM;
178 p_out_data = *pp_out_data;
180 else
182 assert(p_allocated_out_data != NULL);
183 p_out_data = p_allocated_out_data;
186 /* Fill data */
187 err = AudioObjectGetPropertyData(id, p_address, 0, NULL, &i_out_size,
188 p_out_data);
189 if (err != noErr)
191 msg_Err(p_aout, "AudioObjectGetPropertyData failed, device id: %i, "
192 "prop: [%4.4s], OSStatus: %d", id, (const char *) &p_address[0],
193 (int) err);
195 if (pp_out_data != NULL)
196 free(*pp_out_data);
197 return VLC_EGENERIC;
199 assert(p_nb_elms == NULL || *p_nb_elms == (i_out_size / i_elm_size));
200 return VLC_SUCCESS;
203 /* Get Audio Object Property data: pp_out_data will be allocated by this MACRO
204 * and need to be freed in case of success. */
205 #define AO_GETPROP(id, type, p_out_nb_elms, pp_out_data, a1, a2) \
206 AoGetProperty(p_aout, (id), \
207 &(AudioObjectPropertyAddress) {(a1), (a2), 0}, sizeof(type), \
208 -1, (p_out_nb_elms), (void **)(pp_out_data), NULL)
210 /* Get 1 Audio Object Property data: pre-allocated by the caller */
211 #define AO_GET1PROP(id, type, p_out_data, a1, a2) \
212 AoGetProperty(p_aout, (id), \
213 &(AudioObjectPropertyAddress) {(a1), (a2), 0}, sizeof(type), \
214 1, NULL, NULL, (p_out_data))
216 static bool
217 AoIsPropertySettable(audio_output_t *p_aout, AudioObjectID id,
218 const AudioObjectPropertyAddress *p_address)
220 Boolean b_settable;
221 OSStatus err = AudioObjectIsPropertySettable(id, p_address, &b_settable);
222 if (err != noErr)
224 msg_Warn(p_aout, "AudioObjectIsPropertySettable failed, device id: %i, "
225 "prop: [%4.4s], OSStatus: %d", id, (const char *)&p_address[0],
226 (int)err);
227 return false;
229 return b_settable;
232 #define AO_ISPROPSETTABLE(id, a1, a2) \
233 AoIsPropertySettable(p_aout, (id), \
234 &(AudioObjectPropertyAddress) { (a1), (a2), 0})
236 #define AO_HASPROP(id, a1, a2) \
237 AudioObjectHasProperty((id), &(AudioObjectPropertyAddress) { (a1), (a2), 0})
239 static int
240 AoSetProperty(audio_output_t *p_aout, AudioObjectID id,
241 const AudioObjectPropertyAddress *p_address, size_t i_data,
242 const void *p_data)
244 OSStatus err =
245 AudioObjectSetPropertyData(id, p_address, 0, NULL, i_data, p_data);
247 if (err != noErr)
249 msg_Err(p_aout, "AudioObjectSetPropertyData failed, device id: %i, "
250 "prop: [%4.4s], OSStatus: %d", id, (const char *)&p_address[0],
251 (int)err);
252 return VLC_EGENERIC;
254 return VLC_SUCCESS;
257 #define AO_SETPROP(id, i_data, p_data, a1, a2) \
258 AoSetProperty(p_aout, (id), \
259 &(AudioObjectPropertyAddress) { (a1), (a2), 0}, \
260 (i_data), (p_data));
262 static int
263 AoUpdateListener(audio_output_t *p_aout, bool add, AudioObjectID id,
264 const AudioObjectPropertyAddress *p_address,
265 AudioObjectPropertyListenerProc listener, void *data)
267 OSStatus err = add ?
268 AudioObjectAddPropertyListener(id, p_address, listener, data) :
269 AudioObjectRemovePropertyListener(id, p_address, listener, data);
271 if (err != noErr)
273 msg_Err(p_aout, "AudioObject%sPropertyListener failed, device id %i, "
274 "prop: [%4.4s], OSStatus: %d", add ? "Add" : "Remove", id,
275 (const char *)&p_address[0], (int)err);
276 return VLC_EGENERIC;
278 return VLC_SUCCESS;
281 #define AO_UPDATELISTENER(id, add, listener, data, a1, a2) \
282 AoUpdateListener(p_aout, add, (id), \
283 &(AudioObjectPropertyAddress) { (a1), (a2), 0}, \
284 (listener), (data))
286 #pragma mark -
287 #pragma mark Stream / Hardware Listeners
289 static bool
290 IsAudioFormatDigital(AudioFormatID id)
292 switch (id)
294 case 'IAC3':
295 case 'iac3':
296 case kAudioFormat60958AC3:
297 case kAudioFormatAC3:
298 case kAudioFormatEnhancedAC3:
299 return true;
300 default:
301 return false;
305 static OSStatus
306 StreamsChangedListener(AudioObjectID, UInt32,
307 const AudioObjectPropertyAddress [], void *);
309 static int
310 ManageAudioStreamsCallback(audio_output_t *p_aout, AudioDeviceID i_dev_id,
311 bool b_register)
313 /* Retrieve all the output streams */
314 size_t i_streams;
315 AudioStreamID *p_streams;
316 int ret = AO_GETPROP(i_dev_id, AudioStreamID, &i_streams, &p_streams,
317 kAudioDevicePropertyStreams,
318 kAudioObjectPropertyScopeOutput);
319 if (ret != VLC_SUCCESS)
320 return ret;
322 for (size_t i = 0; i < i_streams; i++)
324 /* get notified when physical formats change */
325 AO_UPDATELISTENER(p_streams[i], b_register, StreamsChangedListener,
326 p_aout, kAudioStreamPropertyAvailablePhysicalFormats,
327 kAudioObjectPropertyScopeGlobal);
330 free(p_streams);
331 return VLC_SUCCESS;
335 * AudioStreamSupportsDigital: Checks if audio stream is compatible with raw
336 * bitstreams
338 static bool
339 AudioStreamSupportsDigital(audio_output_t *p_aout, AudioStreamID i_stream_id)
341 bool b_return = false;
343 /* Retrieve all the stream formats supported by each output stream */
344 size_t i_formats;
345 AudioStreamRangedDescription *p_format_list;
346 int ret = AO_GETPROP(i_stream_id, AudioStreamRangedDescription, &i_formats,
347 &p_format_list,
348 kAudioStreamPropertyAvailablePhysicalFormats,
349 kAudioObjectPropertyScopeGlobal);
350 if (ret != VLC_SUCCESS)
351 return false;
353 for (size_t i = 0; i < i_formats; i++)
355 #ifndef NDEBUG
356 msg_Dbg(p_aout, STREAM_FORMAT_MSG("supported format: ",
357 p_format_list[i].mFormat));
358 #endif
360 if (IsAudioFormatDigital(p_format_list[i].mFormat.mFormatID))
361 b_return = true;
364 free(p_format_list);
365 return b_return;
369 * AudioDeviceSupportsDigital: Checks if device supports raw bitstreams
371 static bool
372 AudioDeviceSupportsDigital(audio_output_t *p_aout, AudioDeviceID i_dev_id)
374 size_t i_streams;
375 AudioStreamID * p_streams;
376 int ret = AO_GETPROP(i_dev_id, AudioStreamID, &i_streams, &p_streams,
377 kAudioDevicePropertyStreams,
378 kAudioObjectPropertyScopeOutput);
379 if (ret != VLC_SUCCESS)
380 return false;
382 for (size_t i = 0; i < i_streams; i++)
384 if (AudioStreamSupportsDigital(p_aout, p_streams[i]))
386 free(p_streams);
387 return true;
391 free(p_streams);
392 return false;
395 static void
396 ReportDevice(audio_output_t *p_aout, UInt32 i_id, char *name)
398 char deviceid[10];
399 sprintf(deviceid, "%i", i_id);
401 aout_HotplugReport(p_aout, deviceid, name);
405 * AudioDeviceIsAHeadphone: Checks if device is a headphone
408 static bool
409 AudioDeviceIsAHeadphone(audio_output_t *p_aout, AudioDeviceID i_dev_id)
411 UInt32 defaultSize = sizeof(AudioDeviceID);
413 const AudioObjectPropertyAddress defaultAddr = {
414 kAudioHardwarePropertyDefaultOutputDevice,
415 kAudioObjectPropertyScopeGlobal,
416 kAudioObjectPropertyElementMaster
419 AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultAddr, 0, NULL, &defaultSize, &i_dev_id);
421 AudioObjectPropertyAddress property;
422 property.mSelector = kAudioDevicePropertyDataSource;
423 property.mScope = kAudioDevicePropertyScopeOutput;
424 property.mElement = kAudioObjectPropertyElementMaster;
426 UInt32 data;
427 UInt32 size = sizeof(UInt32);
428 AudioObjectGetPropertyData(i_dev_id, &property, 0, NULL, &size, &data);
431 'hdpn' == headphone
432 'ispk' == internal speaker
433 '61pd' == HDMI
434 ' ' == Bluetooth accessory or AirPlay
437 return data == 'hdpn';
441 * AudioDeviceHasOutput: Checks if the device is actually an output device
443 static int
444 AudioDeviceHasOutput(audio_output_t *p_aout, AudioDeviceID i_dev_id)
446 size_t i_streams;
447 int ret = AO_GETPROP(i_dev_id, AudioStreamID, &i_streams, NULL,
448 kAudioDevicePropertyStreams,
449 kAudioObjectPropertyScopeOutput);
450 if (ret != VLC_SUCCESS || i_streams == 0)
451 return FALSE;
453 return TRUE;
456 static void
457 RebuildDeviceList(audio_output_t * p_aout, UInt32 *p_id_exists)
459 aout_sys_t *p_sys = p_aout->sys;
461 msg_Dbg(p_aout, "Rebuild device list");
463 ReportDevice(p_aout, 0, _("System Sound Output Device"));
465 /* Get number of devices */
466 size_t i_devices;
467 AudioDeviceID *p_devices;
468 int ret = AO_GETPROP(kAudioObjectSystemObject, AudioDeviceID, &i_devices,
469 &p_devices, kAudioHardwarePropertyDevices,
470 kAudioObjectPropertyScopeGlobal);
472 if (ret != VLC_SUCCESS || i_devices == 0)
474 msg_Err(p_aout, "No audio output devices found.");
475 return;
477 msg_Dbg(p_aout, "found %zu audio device(s)", i_devices);
479 /* setup local array */
480 CFMutableArrayRef currentListOfDevices =
481 CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
483 UInt32 i_id_exists;
484 if (p_id_exists)
486 i_id_exists = *p_id_exists;
487 *p_id_exists = 0;
489 else
490 i_id_exists = 0;
492 for (size_t i = 0; i < i_devices; i++)
494 CFStringRef device_name_ref;
495 char *psz_name;
496 CFIndex length;
497 UInt32 i_id = p_devices[i];
499 int ret = AO_GET1PROP(i_id, CFStringRef, &device_name_ref,
500 kAudioObjectPropertyName,
501 kAudioObjectPropertyScopeGlobal);
502 if (ret != VLC_SUCCESS)
504 msg_Dbg(p_aout, "failed to get name for device %i", i_id);
505 continue;
508 length = CFStringGetLength(device_name_ref);
509 length++;
510 psz_name = malloc(length);
511 if (!psz_name)
513 CFRelease(device_name_ref);
514 return;
516 CFStringGetCString(device_name_ref, psz_name, length,
517 kCFStringEncodingUTF8);
518 CFRelease(device_name_ref);
520 msg_Dbg(p_aout, "DevID: %i DevName: %s", i_id, psz_name);
522 if (!AudioDeviceHasOutput(p_aout, i_id))
524 msg_Dbg(p_aout, "this '%s' is INPUT only. skipping...", psz_name);
525 free(psz_name);
526 continue;
529 if (p_id_exists && i_id == i_id_exists)
530 *p_id_exists = i_id;
532 ReportDevice(p_aout, i_id, psz_name);
533 CFNumberRef deviceNumber = CFNumberCreate(kCFAllocatorDefault,
534 kCFNumberSInt32Type, &i_id);
535 CFArrayAppendValue(currentListOfDevices, deviceNumber);
536 CFRelease(deviceNumber);
538 if (AudioDeviceSupportsDigital(p_aout, i_id))
540 msg_Dbg(p_aout, "'%s' supports digital output", psz_name);
541 char *psz_encoded_name = nil;
542 asprintf(&psz_encoded_name, _("%s (Encoded Output)"), psz_name);
543 i_id = i_id | AOUT_VAR_SPDIF_FLAG;
544 ReportDevice(p_aout, i_id, psz_encoded_name);
545 deviceNumber = CFNumberCreate(kCFAllocatorDefault,
546 kCFNumberSInt32Type, &i_id);
547 CFArrayAppendValue(currentListOfDevices, deviceNumber);
548 CFRelease(deviceNumber);
549 free(psz_encoded_name);
552 // TODO: only register once for each device
553 ManageAudioStreamsCallback(p_aout, p_devices[i], true);
555 free(psz_name);
558 vlc_mutex_lock(&p_sys->device_list_lock);
559 CFIndex count = 0;
560 if (p_sys->device_list)
561 count = CFArrayGetCount(p_sys->device_list);
562 CFRange newListSearchRange =
563 CFRangeMake(0, CFArrayGetCount(currentListOfDevices));
565 if (count > 0)
567 msg_Dbg(p_aout, "Looking for removed devices");
568 CFNumberRef cfn_device_id;
569 int i_device_id = 0;
570 for (CFIndex x = 0; x < count; x++)
572 if (!CFArrayContainsValue(currentListOfDevices, newListSearchRange,
573 CFArrayGetValueAtIndex(p_sys->device_list,
574 x)))
576 cfn_device_id = CFArrayGetValueAtIndex(p_sys->device_list, x);
577 if (cfn_device_id)
579 CFNumberGetValue(cfn_device_id, kCFNumberSInt32Type,
580 &i_device_id);
581 msg_Dbg(p_aout, "Device ID %i is not found in new array, "
582 "deleting.", i_device_id);
584 ReportDevice(p_aout, i_device_id, NULL);
589 if (p_sys->device_list)
590 CFRelease(p_sys->device_list);
591 p_sys->device_list = CFArrayCreateCopy(kCFAllocatorDefault,
592 currentListOfDevices);
593 CFRelease(currentListOfDevices);
594 vlc_mutex_unlock(&p_sys->device_list_lock);
596 free(p_devices);
600 * Callback when current device is not alive anymore
602 static OSStatus
603 DeviceAliveListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
604 const AudioObjectPropertyAddress inAddresses[],
605 void *inClientData)
607 VLC_UNUSED(inObjectID);
608 VLC_UNUSED(inNumberAddresses);
609 VLC_UNUSED(inAddresses);
611 audio_output_t *p_aout = (audio_output_t *)inClientData;
612 if (!p_aout)
613 return -1;
615 msg_Warn(p_aout, "audio device died, resetting aout");
616 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
618 return noErr;
622 * Callback when default audio device changed
624 static OSStatus
625 DefaultDeviceChangedListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
626 const AudioObjectPropertyAddress inAddresses[],
627 void *inClientData)
629 VLC_UNUSED(inObjectID);
630 VLC_UNUSED(inNumberAddresses);
631 VLC_UNUSED(inAddresses);
633 audio_output_t *p_aout = (audio_output_t *)inClientData;
634 if (!p_aout)
635 return -1;
637 aout_sys_t *p_sys = p_aout->sys;
639 if (!p_sys->b_selected_dev_is_default)
640 return noErr;
642 AudioObjectID defaultDeviceID;
643 int ret = AO_GET1PROP(kAudioObjectSystemObject, AudioObjectID,
644 &defaultDeviceID,
645 kAudioHardwarePropertyDefaultOutputDevice,
646 kAudioObjectPropertyScopeOutput);
647 if (ret != VLC_SUCCESS)
648 return -1;
650 msg_Dbg(p_aout, "default device changed to %i", defaultDeviceID);
652 /* Default device is changed by the os to allow other apps to play sound
653 * while in digital mode. But this should not affect ourself. */
654 if (p_sys->b_digital)
656 msg_Dbg(p_aout, "ignore, as digital mode is active");
657 return noErr;
660 vlc_mutex_lock(&p_sys->selected_device_lock);
661 /* Also ignore events which announce the same device id */
662 if (defaultDeviceID != p_sys->i_selected_dev)
664 msg_Dbg(p_aout, "default device actually changed, resetting aout");
665 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
667 vlc_mutex_unlock(&p_sys->selected_device_lock);
669 return noErr;
673 * Callback when physical formats for device change
675 static OSStatus
676 StreamsChangedListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
677 const AudioObjectPropertyAddress inAddresses[],
678 void *inClientData)
680 VLC_UNUSED(inNumberAddresses);
681 VLC_UNUSED(inAddresses);
683 audio_output_t *p_aout = (audio_output_t *)inClientData;
684 if (!p_aout)
685 return -1;
687 aout_sys_t *p_sys = p_aout->sys;
688 if(unlikely(p_sys->b_ignore_streams_changed_callback == true))
689 return 0;
691 msg_Dbg(p_aout, "available physical formats for audio device changed");
692 RebuildDeviceList(p_aout, NULL);
694 vlc_mutex_lock(&p_sys->selected_device_lock);
695 /* In this case audio has not yet started. Below code will not work and is
696 * not needed here. */
697 if (p_sys->i_selected_dev == 0)
699 vlc_mutex_unlock(&p_sys->selected_device_lock);
700 return 0;
704 * check if changed stream id belongs to current device
706 size_t i_streams;
707 AudioStreamID *p_streams;
708 int ret = AO_GETPROP(p_sys->i_selected_dev, AudioStreamID, &i_streams,
709 &p_streams, kAudioDevicePropertyStreams,
710 kAudioObjectPropertyScopeOutput);
711 if (ret != VLC_SUCCESS)
713 vlc_mutex_unlock(&p_sys->selected_device_lock);
714 return ret;
716 vlc_mutex_unlock(&p_sys->selected_device_lock);
718 for (size_t i = 0; i < i_streams; i++)
720 if (p_streams[i] == inObjectID)
722 msg_Dbg(p_aout, "Restart aout as this affects current device");
723 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
724 break;
727 free(p_streams);
729 return noErr;
733 * Callback when device list changed
735 static OSStatus
736 DevicesListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
737 const AudioObjectPropertyAddress inAddresses[],
738 void *inClientData)
740 VLC_UNUSED(inObjectID);
741 VLC_UNUSED(inNumberAddresses);
742 VLC_UNUSED(inAddresses);
744 audio_output_t *p_aout = (audio_output_t *)inClientData;
745 if (!p_aout)
746 return -1;
747 aout_sys_t *p_sys = p_aout->sys;
749 msg_Dbg(p_aout, "audio device configuration changed, resetting cache");
750 RebuildDeviceList(p_aout, NULL);
752 vlc_mutex_lock(&p_sys->selected_device_lock);
753 vlc_mutex_lock(&p_sys->device_list_lock);
754 CFNumberRef selectedDevice =
755 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
756 &p_sys->i_selected_dev);
757 CFRange range = CFRangeMake(0, CFArrayGetCount(p_sys->device_list));
758 if (!CFArrayContainsValue(p_sys->device_list, range, selectedDevice))
759 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
760 CFRelease(selectedDevice);
761 vlc_mutex_unlock(&p_sys->device_list_lock);
762 vlc_mutex_unlock(&p_sys->selected_device_lock);
764 return noErr;
768 * StreamListener: check whether the device's physical format change is complete
770 static OSStatus
771 StreamListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
772 const AudioObjectPropertyAddress inAddresses[],
773 void *inClientData)
775 OSStatus err = noErr;
776 struct { vlc_mutex_t lock; vlc_cond_t cond; } * w = inClientData;
778 VLC_UNUSED(inObjectID);
780 for (unsigned int i = 0; i < inNumberAddresses; i++)
782 if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat)
784 int canc = vlc_savecancel();
785 vlc_mutex_lock(&w->lock);
786 vlc_cond_signal(&w->cond);
787 vlc_mutex_unlock(&w->lock);
788 vlc_restorecancel(canc);
789 break;
792 return err;
796 * AudioStreamChangeFormat: switch stream format based on the provided
797 * description
799 static int
800 AudioStreamChangeFormat(audio_output_t *p_aout, AudioStreamID i_stream_id,
801 AudioStreamBasicDescription change_format)
803 int retValue = false;
805 struct { vlc_mutex_t lock; vlc_cond_t cond; } w;
807 msg_Dbg(p_aout, STREAM_FORMAT_MSG("setting stream format: ", change_format));
809 /* Condition because SetProperty is asynchronious */
810 vlc_cond_init(&w.cond);
811 vlc_mutex_init(&w.lock);
812 vlc_mutex_lock(&w.lock);
814 /* Install the callback */
815 int ret = AO_UPDATELISTENER(i_stream_id, true, StreamListener, &w,
816 kAudioStreamPropertyPhysicalFormat,
817 kAudioObjectPropertyScopeGlobal);
819 if (ret != VLC_SUCCESS)
821 retValue = false;
822 goto out;
825 /* change the format */
826 ret = AO_SETPROP(i_stream_id, sizeof(AudioStreamBasicDescription),
827 &change_format, kAudioStreamPropertyPhysicalFormat,
828 kAudioObjectPropertyScopeGlobal);
829 if (ret != VLC_SUCCESS)
831 retValue = false;
832 goto out;
835 /* The AudioStreamSetProperty is not only asynchronious (requiring the
836 * locks) it is also not atomic in its behaviour. Therefore we check 9
837 * times before we really give up.
839 for (int i = 0; i < 9; i++)
841 /* Callback is not always invoked. So first check if format is already
842 * set. */
843 if (i > 0)
845 vlc_tick_t timeout = vlc_tick_now() + VLC_TICK_FROM_MS(500);
846 if (vlc_cond_timedwait(&w.cond, &w.lock, timeout))
847 msg_Dbg(p_aout, "reached timeout");
850 AudioStreamBasicDescription actual_format;
851 int ret = AO_GET1PROP(i_stream_id, AudioStreamBasicDescription,
852 &actual_format,
853 kAudioStreamPropertyPhysicalFormat,
854 kAudioObjectPropertyScopeGlobal);
856 if (ret != VLC_SUCCESS)
857 continue;
859 msg_Dbg(p_aout, STREAM_FORMAT_MSG("actual format in use: ",
860 actual_format));
861 if (actual_format.mSampleRate == change_format.mSampleRate &&
862 actual_format.mFormatID == change_format.mFormatID &&
863 actual_format.mFramesPerPacket == change_format.mFramesPerPacket)
865 /* The right format is now active */
866 retValue = true;
867 break;
870 /* We need to check again */
873 out:
874 vlc_mutex_unlock(&w.lock);
876 /* Removing the property listener */
877 ret = AO_UPDATELISTENER(i_stream_id, false, StreamListener, &w,
878 kAudioStreamPropertyPhysicalFormat,
879 kAudioObjectPropertyScopeGlobal);
880 if (ret != VLC_SUCCESS)
881 retValue = false;
883 vlc_mutex_destroy(&w.lock);
884 vlc_cond_destroy(&w.cond);
886 return retValue;
889 #pragma mark -
890 #pragma mark core interaction
892 static int
893 SwitchAudioDevice(audio_output_t *p_aout, const char *name)
895 aout_sys_t *p_sys = p_aout->sys;
897 if (name)
898 p_sys->i_new_selected_dev = atoi(name);
899 else
900 p_sys->i_new_selected_dev = 0;
902 p_sys->i_new_selected_dev = p_sys->i_new_selected_dev;
904 aout_DeviceReport(p_aout, name);
905 aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
907 return 0;
910 static int
911 VolumeSet(audio_output_t * p_aout, float volume)
913 aout_sys_t *p_sys = p_aout->sys;
914 OSStatus ostatus = 0;
916 if (p_sys->b_digital)
917 return VLC_EGENERIC;
919 p_sys->f_volume = volume;
920 aout_VolumeReport(p_aout, volume);
922 /* Set volume for output unit */
923 if (!p_sys->b_mute)
925 ostatus = AudioUnitSetParameter(p_sys->au_unit,
926 kHALOutputParam_Volume,
927 kAudioUnitScope_Global,
929 volume * volume * volume,
933 if (var_InheritBool(p_aout, "volume-save"))
934 config_PutInt("auhal-volume", lroundf(volume * AOUT_VOLUME_DEFAULT));
936 return ostatus;
939 static int
940 MuteSet(audio_output_t * p_aout, bool mute)
942 aout_sys_t *p_sys = p_aout->sys;
944 if(p_sys->b_digital)
945 return VLC_EGENERIC;
947 p_sys->b_mute = mute;
948 aout_MuteReport(p_aout, mute);
950 float volume = .0;
951 if (!mute)
952 volume = p_sys->f_volume;
954 OSStatus err =
955 AudioUnitSetParameter(p_sys->au_unit, kHALOutputParam_Volume,
956 kAudioUnitScope_Global, 0,
957 volume * volume * volume, 0);
959 return err == noErr ? VLC_SUCCESS : VLC_EGENERIC;
962 #pragma mark -
963 #pragma mark actual playback
966 * RenderCallbackSPDIF: callback for SPDIF audio output
968 static OSStatus
969 RenderCallbackSPDIF(AudioDeviceID inDevice, const AudioTimeStamp * inNow,
970 const AudioBufferList * inInputData,
971 const AudioTimeStamp * inInputTime,
972 AudioBufferList * outOutputData,
973 const AudioTimeStamp * inOutputTime, void *p_data)
975 VLC_UNUSED(inNow);
976 VLC_UNUSED(inDevice);
977 VLC_UNUSED(inInputData);
978 VLC_UNUSED(inInputTime);
979 VLC_UNUSED(inOutputTime);
981 audio_output_t * p_aout = p_data;
982 aout_sys_t *p_sys = p_aout->sys;
983 uint8_t *p_output = outOutputData->mBuffers[p_sys->i_stream_index].mData;
984 size_t i_size = outOutputData->mBuffers[p_sys->i_stream_index].mDataByteSize;
986 uint64_t i_host_time = (inNow->mFlags & kAudioTimeStampHostTimeValid)
987 ? inNow->mHostTime : 0;
989 ca_Render(p_aout, 0, i_host_time, p_output, i_size);
991 return noErr;
994 #pragma mark -
995 #pragma mark initialization
997 static void
998 WarnConfiguration(audio_output_t *p_aout)
1000 aout_sys_t *p_sys = p_aout->sys;
1001 char *warned_devices = var_CreateGetNonEmptyString(p_aout, "auhal-warned-devices");
1002 bool dev_is_warned = false;
1003 unsigned dev_count = 0;
1005 /* Check if the actual device was already warned */
1006 if (warned_devices)
1008 char *dup = strdup(warned_devices);
1009 if (dup)
1011 char *savetpr;
1012 for (const char *dev = strtok_r(dup, ";", &savetpr);
1013 dev != NULL && !dev_is_warned;
1014 dev = strtok_r(NULL, ";", &savetpr))
1016 dev_count++;
1017 int devid = atoi(dev);
1018 if (devid >= 0 && (unsigned) devid == p_sys->i_selected_dev)
1020 dev_is_warned = true;
1021 break;
1024 free(dup);
1028 /* Warn only one time per device */
1029 if (!dev_is_warned)
1031 msg_Warn(p_aout, "You should configure your speaker layout with "
1032 "Audio Midi Setup in /Applications/Utilities. VLC will "
1033 "output Stereo only.");
1034 vlc_dialog_display_error(p_aout,
1035 _("Audio device is not configured"), "%s",
1036 _("You should configure your speaker layout with "
1037 "\"Audio Midi Setup\" in /Applications/"
1038 "Utilities. VLC will output Stereo only."));
1040 /* Don't save too many devices */
1041 if (dev_count >= 10)
1043 char *end = strrchr(warned_devices, ';');
1044 if (end)
1045 *end = 0;
1048 /* Add the actual device to the list of warned devices */
1049 char *new_warned_devices;
1050 if (asprintf(&new_warned_devices, "%u%s%s", p_sys->i_selected_dev,
1051 warned_devices ? ";" : "",
1052 warned_devices ? warned_devices : "") != -1)
1054 config_PutPsz("auhal-warned-devices", new_warned_devices);
1055 var_SetString(p_aout, "auhal-warned-devices", new_warned_devices);
1056 free(new_warned_devices);
1059 free(warned_devices);
1063 * StartAnalog: open and setup a HAL AudioUnit to do PCM audio output
1065 static int
1066 StartAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt,
1067 vlc_tick_t i_latency_us)
1069 aout_sys_t *p_sys = p_aout->sys;
1070 OSStatus err = noErr;
1071 UInt32 i_param_size;
1072 AudioChannelLayout *layout = NULL;
1074 if (aout_FormatNbChannels(fmt) == 0)
1075 return VLC_EGENERIC;
1077 p_sys->au_unit = au_NewOutputInstance(p_aout, kAudioUnitSubType_HALOutput);
1078 if (p_sys->au_unit == NULL)
1079 return VLC_EGENERIC;
1081 p_aout->current_sink_info.headphones = AudioDeviceIsAHeadphone(p_aout, p_sys->i_selected_dev);
1083 /* Set the device we will use for this output unit */
1084 err = AudioUnitSetProperty(p_sys->au_unit,
1085 kAudioOutputUnitProperty_CurrentDevice,
1086 kAudioUnitScope_Global, 0,
1087 &p_sys->i_selected_dev, sizeof(AudioObjectID));
1089 if (err != noErr)
1091 ca_LogErr("cannot select audio output device, PCM output failed");
1092 goto error;
1095 /* Get the channel layout of the device side of the unit (vlc -> unit ->
1096 * device) */
1097 err = AudioUnitGetPropertyInfo(p_sys->au_unit,
1098 kAudioDevicePropertyPreferredChannelLayout,
1099 kAudioUnitScope_Output, 0, &i_param_size,
1100 NULL);
1101 if (err == noErr)
1103 layout = (AudioChannelLayout *)malloc(i_param_size);
1104 if (layout == NULL)
1105 goto error;
1107 OSStatus err =
1108 AudioUnitGetProperty(p_sys->au_unit,
1109 kAudioDevicePropertyPreferredChannelLayout,
1110 kAudioUnitScope_Output, 0, layout,
1111 &i_param_size);
1112 if (err != noErr)
1113 goto error;
1115 else
1116 ca_LogWarn("device driver does not support "
1117 "kAudioDevicePropertyPreferredChannelLayout - using stereo");
1119 /* Do the last VLC aout setups */
1120 bool warn_configuration;
1121 int ret = au_Initialize(p_aout, p_sys->au_unit, fmt, layout, i_latency_us,
1122 &warn_configuration);
1123 if (ret != VLC_SUCCESS)
1124 goto error;
1126 err = AudioOutputUnitStart(p_sys->au_unit);
1127 if (err != noErr)
1129 ca_LogErr("AudioUnitStart failed");
1130 au_Uninitialize(p_aout, p_sys->au_unit);
1131 goto error;
1134 /* Set volume for output unit */
1135 VolumeSet(p_aout, p_sys->f_volume);
1136 MuteSet(p_aout, p_sys->b_mute);
1138 free(layout);
1140 if (warn_configuration)
1141 WarnConfiguration(p_aout);
1143 return VLC_SUCCESS;
1144 error:
1145 AudioComponentInstanceDispose(p_sys->au_unit);
1146 free(layout);
1147 return VLC_EGENERIC;
1151 * StartSPDIF: Setup an encoded digital stream (SPDIF) output
1153 static int
1154 StartSPDIF(audio_output_t * p_aout, audio_sample_format_t *fmt,
1155 vlc_tick_t i_latency_us)
1157 aout_sys_t *p_sys = p_aout->sys;
1158 int ret;
1160 /* Check if device supports digital */
1161 if (!AudioDeviceSupportsDigital(p_aout, p_sys->i_selected_dev))
1163 msg_Dbg(p_aout, "Audio device supports PCM mode only");
1164 return VLC_EGENERIC;
1167 ret = AO_GET1PROP(p_sys->i_selected_dev, pid_t, &p_sys->i_hog_pid,
1168 kAudioDevicePropertyHogMode,
1169 kAudioObjectPropertyScopeOutput);
1170 if (ret != VLC_SUCCESS)
1172 /* This is not a fatal error. Some drivers simply don't support this
1173 * property */
1174 p_sys->i_hog_pid = -1;
1177 if (p_sys->i_hog_pid != -1 && p_sys->i_hog_pid != getpid())
1179 msg_Err(p_aout, "Selected audio device is exclusively in use by another"
1180 " program.");
1181 vlc_dialog_display_error(p_aout, _("Audio output failed"), "%s",
1182 _("The selected audio output device is exclusively in "
1183 "use by another program."));
1184 return VLC_EGENERIC;
1187 AudioStreamBasicDescription desired_stream_format;
1188 memset(&desired_stream_format, 0, sizeof(desired_stream_format));
1190 /* Start doing the SPDIF setup proces */
1191 p_sys->b_digital = true;
1193 /* Hog the device */
1194 p_sys->i_hog_pid = getpid();
1197 * HACK: On 10.6, auhal will trigger the streams changed callback when
1198 * calling below line, directly in the same thread. This call needs to be
1199 * ignored to avoid endless restarting.
1201 p_sys->b_ignore_streams_changed_callback = true;
1202 ret = AO_SETPROP(p_sys->i_selected_dev, sizeof(p_sys->i_hog_pid),
1203 &p_sys->i_hog_pid, kAudioDevicePropertyHogMode,
1204 kAudioObjectPropertyScopeOutput);
1205 p_sys->b_ignore_streams_changed_callback = false;
1207 if (ret != VLC_SUCCESS)
1208 return ret;
1210 if (AO_HASPROP(p_sys->i_selected_dev, kAudioDevicePropertySupportsMixing,
1211 kAudioObjectPropertyScopeGlobal))
1213 /* Set mixable to false if we are allowed to */
1214 UInt32 b_mix;
1215 bool b_writeable = AO_ISPROPSETTABLE(p_sys->i_selected_dev,
1216 kAudioDevicePropertySupportsMixing,
1217 kAudioObjectPropertyScopeGlobal);
1219 ret = AO_GET1PROP(p_sys->i_selected_dev, UInt32, &b_mix,
1220 kAudioDevicePropertySupportsMixing,
1221 kAudioObjectPropertyScopeGlobal);
1222 if (ret == VLC_SUCCESS && b_writeable)
1224 b_mix = 0;
1225 ret = AO_SETPROP(p_sys->i_selected_dev, sizeof(UInt32), &b_mix,
1226 kAudioDevicePropertySupportsMixing,
1227 kAudioObjectPropertyScopeGlobal);
1228 p_sys->b_changed_mixing = true;
1231 if (ret != VLC_SUCCESS)
1233 msg_Err(p_aout, "failed to set mixmode");
1234 return ret;
1238 /* Get a list of all the streams on this device */
1239 size_t i_streams;
1240 AudioStreamID *p_streams;
1241 ret = AO_GETPROP(p_sys->i_selected_dev, AudioStreamID, &i_streams,
1242 &p_streams, kAudioDevicePropertyStreams,
1243 kAudioObjectPropertyScopeOutput);
1244 if (ret != VLC_SUCCESS)
1245 return ret;
1247 for (unsigned i = 0; i < i_streams && p_sys->i_stream_index < 0 ; i++)
1249 /* Find a stream with a cac3 stream */
1250 bool b_digital = false;
1252 /* Retrieve all the stream formats supported by each output stream */
1253 size_t i_formats;
1254 AudioStreamRangedDescription *p_format_list;
1255 int ret = AO_GETPROP(p_streams[i], AudioStreamRangedDescription,
1256 &i_formats, &p_format_list,
1257 kAudioStreamPropertyAvailablePhysicalFormats,
1258 kAudioObjectPropertyScopeGlobal);
1259 if (ret != VLC_SUCCESS)
1260 continue;
1262 /* Check if one of the supported formats is a digital format */
1263 for (size_t j = 0; j < i_formats; j++)
1265 if (IsAudioFormatDigital(p_format_list[j].mFormat.mFormatID))
1267 b_digital = true;
1268 break;
1272 if (b_digital)
1274 /* if this stream supports a digital (cac3) format, then go set it.
1275 * */
1276 int i_requested_rate_format = -1;
1277 int i_current_rate_format = -1;
1278 int i_backup_rate_format = -1;
1280 if (!p_sys->b_revert)
1282 /* Retrieve the original format of this stream first if not
1283 * done so already */
1284 AudioStreamBasicDescription current_streamformat;
1285 int ret = AO_GET1PROP(p_streams[i], AudioStreamBasicDescription,
1286 &current_streamformat,
1287 kAudioStreamPropertyPhysicalFormat,
1288 kAudioObjectPropertyScopeGlobal);
1289 if (ret != VLC_SUCCESS)
1290 continue;
1293 * Only the first found format id is accepted. In case of
1294 * another id later on, we still use the already saved one.
1295 * This can happen if the user plugs in a spdif cable while a
1296 * stream is already playing. Then, auhal already misleadingly
1297 * reports an ac3 format here whereas the original format
1298 * should be still pcm.
1300 if (p_sys->sfmt_revert.mFormatID > 0
1301 && p_sys->sfmt_revert.mFormatID != current_streamformat.mFormatID
1302 && p_streams[i] == p_sys->i_stream_id)
1304 msg_Warn(p_aout, STREAM_FORMAT_MSG("Detected current stream"
1305 " format: ", current_streamformat));
1306 msg_Warn(p_aout, "... there is another stream format "
1307 "already stored, the current one is ignored");
1309 else
1310 p_sys->sfmt_revert = current_streamformat;
1312 p_sys->b_revert = true;
1315 p_sys->i_stream_id = p_streams[i];
1316 p_sys->i_stream_index = i;
1318 for (size_t j = 0; j < i_formats; j++)
1320 if (IsAudioFormatDigital(p_format_list[j].mFormat.mFormatID))
1322 if (p_format_list[j].mFormat.mSampleRate == fmt->i_rate)
1324 i_requested_rate_format = j;
1325 break;
1327 else if (p_format_list[j].mFormat.mSampleRate ==
1328 p_sys->sfmt_revert.mSampleRate)
1329 i_current_rate_format = j;
1330 else
1332 if (i_backup_rate_format < 0
1333 || p_format_list[j].mFormat.mSampleRate >
1334 p_format_list[i_backup_rate_format].mFormat.mSampleRate)
1335 i_backup_rate_format = j;
1341 if (i_requested_rate_format >= 0)
1343 /* We prefer to output at the samplerate of the original audio */
1344 desired_stream_format =
1345 p_format_list[i_requested_rate_format].mFormat;
1347 else if (i_current_rate_format >= 0)
1349 /* If not possible, we will try to use the current samplerate
1350 * of the device */
1351 desired_stream_format =
1352 p_format_list[i_current_rate_format].mFormat;
1354 else
1356 /* And if we have to, any digital format will be just fine
1357 * (highest rate possible) */
1358 desired_stream_format =
1359 p_format_list[i_backup_rate_format].mFormat;
1362 free(p_format_list);
1364 free(p_streams);
1366 msg_Dbg(p_aout, STREAM_FORMAT_MSG("original stream format: ",
1367 p_sys->sfmt_revert));
1369 if (!AudioStreamChangeFormat(p_aout, p_sys->i_stream_id, desired_stream_format))
1371 msg_Err(p_aout, "failed to change stream format for SPDIF output");
1372 return VLC_EGENERIC;
1375 /* Set the format flags */
1376 if (desired_stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian)
1377 fmt->i_format = VLC_CODEC_SPDIFB;
1378 else
1379 fmt->i_format = VLC_CODEC_SPDIFL;
1380 fmt->i_bytes_per_frame = AOUT_SPDIF_SIZE;
1381 fmt->i_frame_length = A52_FRAME_NB;
1382 fmt->i_rate = (unsigned int)desired_stream_format.mSampleRate;
1383 aout_FormatPrepare(fmt);
1385 /* Add IOProc callback */
1386 OSStatus err =
1387 AudioDeviceCreateIOProcID(p_sys->i_selected_dev,
1388 RenderCallbackSPDIF,
1389 p_aout, &p_sys->i_procID);
1390 if (err != noErr)
1392 ca_LogErr("Failed to create Process ID");
1393 return VLC_EGENERIC;
1396 ret = ca_Initialize(p_aout, fmt, i_latency_us);
1397 if (ret != VLC_SUCCESS)
1399 AudioDeviceDestroyIOProcID(p_sys->i_selected_dev, p_sys->i_procID);
1400 return VLC_EGENERIC;
1403 /* Start device */
1404 err = AudioDeviceStart(p_sys->i_selected_dev, p_sys->i_procID);
1405 if (err != noErr)
1407 ca_LogErr("Failed to start audio device");
1409 err = AudioDeviceDestroyIOProcID(p_sys->i_selected_dev, p_sys->i_procID);
1410 if (err != noErr)
1411 ca_LogErr("Failed to destroy process ID");
1413 return VLC_EGENERIC;
1416 msg_Dbg(p_aout, "Using audio device for digital output");
1417 return VLC_SUCCESS;
1420 static void
1421 Stop(audio_output_t *p_aout)
1423 aout_sys_t *p_sys = p_aout->sys;
1424 OSStatus err = noErr;
1426 msg_Dbg(p_aout, "Stopping the auhal module");
1428 if (p_sys->au_unit)
1430 AudioOutputUnitStop(p_sys->au_unit);
1431 au_Uninitialize(p_aout, p_sys->au_unit);
1432 AudioComponentInstanceDispose(p_sys->au_unit);
1434 else
1436 assert(p_sys->b_digital);
1438 /* Stop device */
1439 err = AudioDeviceStop(p_sys->i_selected_dev,
1440 p_sys->i_procID);
1441 if (err != noErr)
1442 ca_LogErr("AudioDeviceStop failed");
1444 /* Remove IOProc callback */
1445 err = AudioDeviceDestroyIOProcID(p_sys->i_selected_dev,
1446 p_sys->i_procID);
1447 if (err != noErr)
1448 ca_LogErr("Failed to destroy Process ID");
1450 if (p_sys->b_revert
1451 && !AudioStreamChangeFormat(p_aout, p_sys->i_stream_id, p_sys->sfmt_revert))
1452 msg_Err(p_aout, "failed to revert stream format in close");
1454 if (p_sys->b_changed_mixing
1455 && p_sys->sfmt_revert.mFormatID != kAudioFormat60958AC3)
1457 UInt32 b_mix;
1458 /* Revert mixable to true if we are allowed to */
1459 bool b_writeable =
1460 AO_ISPROPSETTABLE(p_sys->i_selected_dev,
1461 kAudioDevicePropertySupportsMixing,
1462 kAudioObjectPropertyScopeGlobal);
1463 int ret = AO_GET1PROP(p_sys->i_selected_dev, UInt32, &b_mix,
1464 kAudioDevicePropertySupportsMixing,
1465 kAudioObjectPropertyScopeOutput);
1466 if (ret == VLC_SUCCESS && b_writeable)
1468 msg_Dbg(p_aout, "mixable is: %d", b_mix);
1469 b_mix = 1;
1470 ret = AO_SETPROP(p_sys->i_selected_dev,
1471 sizeof(UInt32), &b_mix,
1472 kAudioDevicePropertySupportsMixing,
1473 kAudioObjectPropertyScopeOutput);
1476 if (ret != VLC_SUCCESS)
1477 msg_Err(p_aout, "failed to re-set mixmode");
1479 ca_Uninitialize(p_aout);
1481 if (p_sys->i_hog_pid == getpid())
1483 p_sys->i_hog_pid = -1;
1486 * HACK: On 10.6, auhal will trigger the streams changed callback
1487 * when calling below line, directly in the same thread. This call
1488 * needs to be ignored to avoid endless restarting.
1490 p_sys->b_ignore_streams_changed_callback = true;
1491 AO_SETPROP(p_sys->i_selected_dev, sizeof(p_sys->i_hog_pid),
1492 &p_sys->i_hog_pid, kAudioDevicePropertyHogMode,
1493 kAudioObjectPropertyScopeOutput);
1494 p_sys->b_ignore_streams_changed_callback = false;
1497 p_sys->b_digital = false;
1500 /* remove audio device alive callback */
1501 AO_UPDATELISTENER(p_sys->i_selected_dev, false, DeviceAliveListener, p_aout,
1502 kAudioDevicePropertyDeviceIsAlive,
1503 kAudioObjectPropertyScopeGlobal);
1506 static int
1507 Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt)
1509 UInt32 i_param_size = 0;
1510 aout_sys_t *p_sys = NULL;
1512 /* Use int here, to match kAudioDevicePropertyDeviceIsAlive
1513 * property size */
1514 int b_alive = false;
1516 if (AOUT_FMT_HDMI(fmt))
1517 return VLC_EGENERIC;
1519 p_sys = p_aout->sys;
1520 p_sys->b_digital = false;
1521 p_sys->au_unit = NULL;
1522 p_sys->i_stream_index = -1;
1523 p_sys->b_revert = false;
1524 p_sys->b_changed_mixing = false;
1526 vlc_mutex_lock(&p_sys->selected_device_lock);
1527 bool do_spdif;
1528 if (AOUT_FMT_SPDIF (fmt))
1530 if (!(p_sys->i_new_selected_dev & AOUT_VAR_SPDIF_FLAG))
1532 vlc_mutex_unlock(&p_sys->selected_device_lock);
1533 return VLC_EGENERIC;
1535 do_spdif = true;
1537 else
1538 do_spdif = false;
1540 p_sys->i_selected_dev = p_sys->i_new_selected_dev & ~AOUT_VAR_SPDIF_FLAG;
1542 aout_FormatPrint(p_aout, "VLC is looking for:", fmt);
1544 msg_Dbg(p_aout, "attempting to use device %i", p_sys->i_selected_dev);
1546 if (p_sys->i_selected_dev > 0)
1548 /* Check if device is in devices list. Only checking for
1549 * kAudioDevicePropertyDeviceIsAlive is not sufficient, as a former
1550 * airplay device might be already gone, but the device number might be
1551 * still valid. Core Audio even says that this device would be alive.
1552 * Don't ask why, its Core Audio. */
1553 CFIndex count = CFArrayGetCount(p_sys->device_list);
1554 CFNumberRef deviceNumber =
1555 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
1556 &p_sys->i_selected_dev);
1557 if (CFArrayContainsValue(p_sys->device_list,
1558 CFRangeMake(0, count), deviceNumber))
1560 /* Check if the desired device is alive and usable */
1561 i_param_size = sizeof(b_alive);
1562 int ret = AO_GET1PROP(p_sys->i_selected_dev, int, &b_alive,
1563 kAudioDevicePropertyDeviceIsAlive,
1564 kAudioObjectPropertyScopeGlobal);
1565 if (ret != VLC_SUCCESS)
1566 b_alive = false; /* Be tolerant */
1568 if (!b_alive)
1569 msg_Warn(p_aout, "selected audio device is not alive, switching"
1570 " to default device");
1573 else
1575 msg_Warn(p_aout, "device id %i not found in the current devices "
1576 "list, fallback to default device", p_sys->i_selected_dev);
1578 CFRelease(deviceNumber);
1581 p_sys->b_selected_dev_is_default = false;
1582 if (!b_alive || p_sys->i_selected_dev == 0)
1584 p_sys->b_selected_dev_is_default = true;
1586 AudioObjectID defaultDeviceID = 0;
1587 int ret = AO_GET1PROP(kAudioObjectSystemObject, AudioObjectID,
1588 &defaultDeviceID,
1589 kAudioHardwarePropertyDefaultOutputDevice,
1590 kAudioObjectPropertyScopeOutput);
1591 if (ret != VLC_SUCCESS)
1593 vlc_mutex_unlock(&p_sys->selected_device_lock);
1594 return VLC_EGENERIC;
1596 else
1597 msg_Dbg(p_aout, "using default audio device %i", defaultDeviceID);
1599 p_sys->i_selected_dev = defaultDeviceID;
1601 vlc_mutex_unlock(&p_sys->selected_device_lock);
1603 /* add a callback to see if the device dies later on */
1604 AO_UPDATELISTENER(p_sys->i_selected_dev, true, DeviceAliveListener, p_aout,
1605 kAudioDevicePropertyDeviceIsAlive,
1606 kAudioObjectPropertyScopeGlobal);
1608 /* get device latency */
1609 UInt32 i_latency_samples;
1610 AO_GET1PROP(p_sys->i_selected_dev, UInt32, &i_latency_samples,
1611 kAudioDevicePropertyLatency, kAudioObjectPropertyScopeOutput);
1612 vlc_tick_t i_latency_us = i_latency_samples * CLOCK_FREQ / fmt->i_rate;
1614 /* Check for Digital mode or Analog output mode */
1615 if (do_spdif)
1617 if (StartSPDIF (p_aout, fmt, i_latency_us) == VLC_SUCCESS)
1619 msg_Dbg(p_aout, "digital output successfully opened");
1620 return VLC_SUCCESS;
1623 else
1625 if (StartAnalog(p_aout, fmt, i_latency_us) == VLC_SUCCESS)
1627 msg_Dbg(p_aout, "analog output successfully opened");
1628 fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
1629 return VLC_SUCCESS;
1633 msg_Err(p_aout, "opening auhal output failed");
1634 AO_UPDATELISTENER(p_sys->i_selected_dev, false, DeviceAliveListener, p_aout,
1635 kAudioDevicePropertyDeviceIsAlive,
1636 kAudioObjectPropertyScopeGlobal);
1637 return VLC_EGENERIC;
1640 static void Close(vlc_object_t *obj)
1642 audio_output_t *p_aout = (audio_output_t *)obj;
1643 aout_sys_t *p_sys = p_aout->sys;
1645 /* remove audio devices callback */
1646 AO_UPDATELISTENER(kAudioObjectSystemObject, false, DevicesListener, p_aout,
1647 kAudioHardwarePropertyDevices,
1648 kAudioObjectPropertyScopeGlobal);
1650 /* remove listener to be notified about changes in default audio device */
1651 AO_UPDATELISTENER(kAudioObjectSystemObject, false,
1652 DefaultDeviceChangedListener, p_aout,
1653 kAudioHardwarePropertyDefaultOutputDevice,
1654 kAudioObjectPropertyScopeGlobal);
1657 * StreamsChangedListener can rebuild the device list and thus held the
1658 * device_list_lock. To avoid a possible deadlock, an array copy is
1659 * created here. In rare cases, this can lead to missing
1660 * StreamsChangedListener callback deregistration (TODO).
1662 vlc_mutex_lock(&p_sys->device_list_lock);
1663 CFArrayRef device_list_cpy = CFArrayCreateCopy(NULL, p_sys->device_list);
1664 vlc_mutex_unlock(&p_sys->device_list_lock);
1666 /* remove streams callbacks */
1667 CFIndex count = CFArrayGetCount(device_list_cpy);
1668 if (count > 0)
1670 for (CFIndex x = 0; x < count; x++)
1672 AudioDeviceID deviceId = 0;
1673 CFNumberRef cfn_device_id =
1674 CFArrayGetValueAtIndex(device_list_cpy, x);
1675 if (!cfn_device_id)
1676 continue;
1678 CFNumberGetValue(cfn_device_id, kCFNumberSInt32Type, &deviceId);
1679 if (!(deviceId & AOUT_VAR_SPDIF_FLAG))
1680 ManageAudioStreamsCallback(p_aout, deviceId, false);
1684 CFRelease(device_list_cpy);
1685 CFRelease(p_sys->device_list);
1687 char *psz_device = aout_DeviceGet(p_aout);
1688 config_PutPsz("auhal-audio-device", psz_device);
1689 free(psz_device);
1691 vlc_mutex_destroy(&p_sys->selected_device_lock);
1692 vlc_mutex_destroy(&p_sys->device_list_lock);
1694 ca_Close(p_aout);
1695 free(p_sys);
1698 static int Open(vlc_object_t *obj)
1700 audio_output_t *p_aout = (audio_output_t *)obj;
1701 aout_sys_t *p_sys = calloc(1, sizeof (*p_sys));
1702 if (unlikely(p_sys == NULL))
1703 return VLC_ENOMEM;
1705 vlc_mutex_init(&p_sys->device_list_lock);
1706 vlc_mutex_init(&p_sys->selected_device_lock);
1707 p_sys->b_digital = false;
1708 p_sys->b_ignore_streams_changed_callback = false;
1709 p_sys->b_selected_dev_is_default = false;
1710 memset(&p_sys->sfmt_revert, 0, sizeof(p_sys->sfmt_revert));
1711 p_sys->i_stream_id = 0;
1713 p_aout->sys = p_sys;
1714 p_aout->start = Start;
1715 p_aout->stop = Stop;
1716 p_aout->volume_set = VolumeSet;
1717 p_aout->mute_set = MuteSet;
1718 p_aout->device_select = SwitchAudioDevice;
1719 p_sys->device_list = CFArrayCreate(kCFAllocatorDefault, NULL, 0, NULL);
1722 * Force an own run loop for callbacks.
1724 * According to rtaudio, this is absolutely necessary since 10.6 to get
1725 * correct notifications. It might fix issues when using the module as a
1726 * library where a proper loop is not setup already.
1728 CFRunLoopRef theRunLoop = NULL;
1729 AO_SETPROP(kAudioObjectSystemObject, sizeof(CFRunLoopRef),
1730 &theRunLoop, kAudioHardwarePropertyRunLoop,
1731 kAudioObjectPropertyScopeGlobal);
1733 /* Attach a listener so that we are notified of a change in the device
1734 * setup */
1735 AO_UPDATELISTENER(kAudioObjectSystemObject, true, DevicesListener, p_aout,
1736 kAudioHardwarePropertyDevices,
1737 kAudioObjectPropertyScopeGlobal);
1739 /* Attach a listener to be notified about changes in default audio device */
1740 AO_UPDATELISTENER(kAudioObjectSystemObject, true,
1741 DefaultDeviceChangedListener, p_aout,
1742 kAudioHardwarePropertyDefaultOutputDevice,
1743 kAudioObjectPropertyScopeGlobal);
1745 char *psz_audio_device = var_InheritString(p_aout, "auhal-audio-device");
1746 if (psz_audio_device != NULL)
1748 int dev_id_int = atoi(psz_audio_device);
1749 UInt32 dev_id = dev_id_int < 0 ? 0 : dev_id_int;
1750 RebuildDeviceList(p_aout, &dev_id);
1751 p_sys->i_new_selected_dev = dev_id;
1752 free(psz_audio_device);
1754 else
1756 RebuildDeviceList(p_aout, NULL);
1757 p_sys->i_new_selected_dev = 0;
1760 char deviceid[10];
1761 sprintf(deviceid, "%i", p_sys->i_new_selected_dev);
1762 aout_DeviceReport(p_aout, deviceid);
1764 /* remember the volume */
1765 p_sys->f_volume = var_InheritInteger(p_aout, "auhal-volume")
1766 / (float)AOUT_VOLUME_DEFAULT;
1767 aout_VolumeReport(p_aout, p_sys->f_volume);
1768 p_sys->b_mute = var_InheritBool(p_aout, "mute");
1769 aout_MuteReport(p_aout, p_sys->b_mute);
1771 ca_Open(p_aout);
1772 return VLC_SUCCESS;