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 *****************************************************************************/
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>
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
*);
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 */
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 *****************************************************************************/
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? */
96 /* CoreAudio SPDIF mode specific */
97 /* Keep the pid of our hog status */
99 /* The StreamID that has a cac3 streamformat */
100 AudioStreamID i_stream_id
;
101 /* The index of i_stream_id in an AudioBufferList */
103 /* The original format of the stream */
104 AudioStreamBasicDescription sfmt_revert
;
105 /* Whether we need to revert the stream format */
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
;
123 bool b_ignore_streams_changed_callback
;
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);
139 OSStatus err
= AudioObjectGetPropertyDataSize(id
, p_address
, 0, NULL
,
143 msg_Err(p_aout
, "AudioObjectGetPropertyDataSize failed, device id: %i, "
144 "prop: [%4.4s], OSStatus: %d", id
, (const char *) &p_address
[0],
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");
159 if (pp_out_data
== NULL
&& p_allocated_out_data
== NULL
)
164 if (pp_out_data
!= NULL
)
169 /* Alloc data or use pre-allocated one */
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
)
178 p_out_data
= *pp_out_data
;
182 assert(p_allocated_out_data
!= NULL
);
183 p_out_data
= p_allocated_out_data
;
187 err
= AudioObjectGetPropertyData(id
, p_address
, 0, NULL
, &i_out_size
,
191 msg_Err(p_aout
, "AudioObjectGetPropertyData failed, device id: %i, "
192 "prop: [%4.4s], OSStatus: %d", id
, (const char *) &p_address
[0],
195 if (pp_out_data
!= NULL
)
199 assert(p_nb_elms
== NULL
|| *p_nb_elms
== (i_out_size
/ i_elm_size
));
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))
217 AoIsPropertySettable(audio_output_t
*p_aout
, AudioObjectID id
,
218 const AudioObjectPropertyAddress
*p_address
)
221 OSStatus err
= AudioObjectIsPropertySettable(id
, p_address
, &b_settable
);
224 msg_Warn(p_aout
, "AudioObjectIsPropertySettable failed, device id: %i, "
225 "prop: [%4.4s], OSStatus: %d", id
, (const char *)&p_address
[0],
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})
240 AoSetProperty(audio_output_t
*p_aout
, AudioObjectID id
,
241 const AudioObjectPropertyAddress
*p_address
, size_t i_data
,
245 AudioObjectSetPropertyData(id
, p_address
, 0, NULL
, i_data
, p_data
);
249 msg_Err(p_aout
, "AudioObjectSetPropertyData failed, device id: %i, "
250 "prop: [%4.4s], OSStatus: %d", id
, (const char *)&p_address
[0],
257 #define AO_SETPROP(id, i_data, p_data, a1, a2) \
258 AoSetProperty(p_aout, (id), \
259 &(AudioObjectPropertyAddress) { (a1), (a2), 0}, \
263 AoUpdateListener(audio_output_t
*p_aout
, bool add
, AudioObjectID id
,
264 const AudioObjectPropertyAddress
*p_address
,
265 AudioObjectPropertyListenerProc listener
, void *data
)
268 AudioObjectAddPropertyListener(id
, p_address
, listener
, data
) :
269 AudioObjectRemovePropertyListener(id
, p_address
, listener
, data
);
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
);
281 #define AO_UPDATELISTENER(id, add, listener, data, a1, a2) \
282 AoUpdateListener(p_aout, add, (id), \
283 &(AudioObjectPropertyAddress) { (a1), (a2), 0}, \
287 #pragma mark Stream / Hardware Listeners
290 IsAudioFormatDigital(AudioFormatID id
)
296 case kAudioFormat60958AC3
:
297 case kAudioFormatAC3
:
298 case kAudioFormatEnhancedAC3
:
306 StreamsChangedListener(AudioObjectID
, UInt32
,
307 const AudioObjectPropertyAddress
[], void *);
310 ManageAudioStreamsCallback(audio_output_t
*p_aout
, AudioDeviceID i_dev_id
,
313 /* Retrieve all the output 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
)
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
);
335 * AudioStreamSupportsDigital: Checks if audio stream is compatible with raw
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 */
345 AudioStreamRangedDescription
*p_format_list
;
346 int ret
= AO_GETPROP(i_stream_id
, AudioStreamRangedDescription
, &i_formats
,
348 kAudioStreamPropertyAvailablePhysicalFormats
,
349 kAudioObjectPropertyScopeGlobal
);
350 if (ret
!= VLC_SUCCESS
)
353 for (size_t i
= 0; i
< i_formats
; i
++)
356 msg_Dbg(p_aout
, STREAM_FORMAT_MSG("supported format: ",
357 p_format_list
[i
].mFormat
));
360 if (IsAudioFormatDigital(p_format_list
[i
].mFormat
.mFormatID
))
369 * AudioDeviceSupportsDigital: Checks if device supports raw bitstreams
372 AudioDeviceSupportsDigital(audio_output_t
*p_aout
, AudioDeviceID i_dev_id
)
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
)
382 for (size_t i
= 0; i
< i_streams
; i
++)
384 if (AudioStreamSupportsDigital(p_aout
, p_streams
[i
]))
396 ReportDevice(audio_output_t
*p_aout
, UInt32 i_id
, char *name
)
399 sprintf(deviceid
, "%i", i_id
);
401 aout_HotplugReport(p_aout
, deviceid
, name
);
405 * AudioDeviceIsAHeadphone: Checks if device is a headphone
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
;
427 UInt32 size
= sizeof(UInt32
);
428 AudioObjectGetPropertyData(i_dev_id
, &property
, 0, NULL
, &size
, &data
);
432 'ispk' == internal speaker
434 ' ' == Bluetooth accessory or AirPlay
437 return data
== 'hdpn';
441 * AudioDeviceHasOutput: Checks if the device is actually an output device
444 AudioDeviceHasOutput(audio_output_t
*p_aout
, AudioDeviceID i_dev_id
)
447 int ret
= AO_GETPROP(i_dev_id
, AudioStreamID
, &i_streams
, NULL
,
448 kAudioDevicePropertyStreams
,
449 kAudioObjectPropertyScopeOutput
);
450 if (ret
!= VLC_SUCCESS
|| i_streams
== 0)
457 RebuildDeviceList(audio_output_t
* p_aout
, UInt32
*p_id_exists
)
459 struct 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 */
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.");
477 msg_Dbg(p_aout
, "found %zu audio device(s)", i_devices
);
479 /* setup local array */
480 CFMutableArrayRef currentListOfDevices
=
481 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
486 i_id_exists
= *p_id_exists
;
492 for (size_t i
= 0; i
< i_devices
; i
++)
494 CFStringRef device_name_ref
;
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
);
508 length
= CFStringGetLength(device_name_ref
);
510 psz_name
= malloc(length
);
513 CFRelease(device_name_ref
);
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
);
529 if (p_id_exists
&& i_id
== i_id_exists
)
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);
558 vlc_mutex_lock(&p_sys
->device_list_lock
);
560 if (p_sys
->device_list
)
561 count
= CFArrayGetCount(p_sys
->device_list
);
562 CFRange newListSearchRange
=
563 CFRangeMake(0, CFArrayGetCount(currentListOfDevices
));
567 msg_Dbg(p_aout
, "Looking for removed devices");
568 CFNumberRef cfn_device_id
;
570 for (CFIndex x
= 0; x
< count
; x
++)
572 if (!CFArrayContainsValue(currentListOfDevices
, newListSearchRange
,
573 CFArrayGetValueAtIndex(p_sys
->device_list
,
576 cfn_device_id
= CFArrayGetValueAtIndex(p_sys
->device_list
, x
);
579 CFNumberGetValue(cfn_device_id
, kCFNumberSInt32Type
,
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
);
600 * Callback when current device is not alive anymore
603 DeviceAliveListener(AudioObjectID inObjectID
, UInt32 inNumberAddresses
,
604 const AudioObjectPropertyAddress inAddresses
[],
607 VLC_UNUSED(inObjectID
);
608 VLC_UNUSED(inNumberAddresses
);
609 VLC_UNUSED(inAddresses
);
611 audio_output_t
*p_aout
= (audio_output_t
*)inClientData
;
615 msg_Warn(p_aout
, "audio device died, resetting aout");
616 aout_RestartRequest(p_aout
, AOUT_RESTART_OUTPUT
);
622 * Callback when default audio device changed
625 DefaultDeviceChangedListener(AudioObjectID inObjectID
, UInt32 inNumberAddresses
,
626 const AudioObjectPropertyAddress inAddresses
[],
629 VLC_UNUSED(inObjectID
);
630 VLC_UNUSED(inNumberAddresses
);
631 VLC_UNUSED(inAddresses
);
633 audio_output_t
*p_aout
= (audio_output_t
*)inClientData
;
637 aout_sys_t
*p_sys
= p_aout
->sys
;
639 if (!p_aout
->sys
->b_selected_dev_is_default
)
642 AudioObjectID defaultDeviceID
;
643 int ret
= AO_GET1PROP(kAudioObjectSystemObject
, AudioObjectID
,
645 kAudioHardwarePropertyDefaultOutputDevice
,
646 kAudioObjectPropertyScopeOutput
);
647 if (ret
!= VLC_SUCCESS
)
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_aout
->sys
->b_digital
)
656 msg_Dbg(p_aout
, "ignore, as digital mode is active");
660 vlc_mutex_lock(&p_sys
->selected_device_lock
);
661 /* Also ignore events which announce the same device id */
662 if (defaultDeviceID
!= p_aout
->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
);
673 * Callback when physical formats for device change
676 StreamsChangedListener(AudioObjectID inObjectID
, UInt32 inNumberAddresses
,
677 const AudioObjectPropertyAddress inAddresses
[],
680 VLC_UNUSED(inNumberAddresses
);
681 VLC_UNUSED(inAddresses
);
683 audio_output_t
*p_aout
= (audio_output_t
*)inClientData
;
687 aout_sys_t
*p_sys
= p_aout
->sys
;
688 if(unlikely(p_sys
->b_ignore_streams_changed_callback
== true))
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
);
704 * check if changed stream id belongs to current device
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
);
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
);
733 * Callback when device list changed
736 DevicesListener(AudioObjectID inObjectID
, UInt32 inNumberAddresses
,
737 const AudioObjectPropertyAddress inAddresses
[],
740 VLC_UNUSED(inObjectID
);
741 VLC_UNUSED(inNumberAddresses
);
742 VLC_UNUSED(inAddresses
);
744 audio_output_t
*p_aout
= (audio_output_t
*)inClientData
;
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
);
768 * StreamListener: check whether the device's physical format change is complete
771 StreamListener(AudioObjectID inObjectID
, UInt32 inNumberAddresses
,
772 const AudioObjectPropertyAddress inAddresses
[],
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
);
796 * AudioStreamChangeFormat: switch stream format based on the provided
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
)
825 /* change the format */
826 ret
= AO_SETPROP(i_stream_id
, sizeof(AudioStreamBasicDescription
),
827 &change_format
, kAudioStreamPropertyPhysicalFormat
,
828 kAudioObjectPropertyScopeGlobal
);
829 if (ret
!= VLC_SUCCESS
)
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
845 mtime_t timeout
= mdate() + 500000;
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
,
853 kAudioStreamPropertyPhysicalFormat
,
854 kAudioObjectPropertyScopeGlobal
);
856 if (ret
!= VLC_SUCCESS
)
859 msg_Dbg(p_aout
, STREAM_FORMAT_MSG("actual format in use: ",
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 */
870 /* We need to check again */
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
)
883 vlc_mutex_destroy(&w
.lock
);
884 vlc_cond_destroy(&w
.cond
);
890 #pragma mark core interaction
893 SwitchAudioDevice(audio_output_t
*p_aout
, const char *name
)
895 struct aout_sys_t
*p_sys
= p_aout
->sys
;
898 p_sys
->i_new_selected_dev
= atoi(name
);
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
);
911 VolumeSet(audio_output_t
* p_aout
, float volume
)
913 struct aout_sys_t
*p_sys
= p_aout
->sys
;
914 OSStatus ostatus
= 0;
916 if (p_sys
->b_digital
)
919 p_sys
->f_volume
= volume
;
920 aout_VolumeReport(p_aout
, volume
);
922 /* Set volume for output unit */
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
));
940 MuteSet(audio_output_t
* p_aout
, bool mute
)
942 struct aout_sys_t
*p_sys
= p_aout
->sys
;
947 p_sys
->b_mute
= mute
;
948 aout_MuteReport(p_aout
, mute
);
952 volume
= p_sys
->f_volume
;
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
;
963 #pragma mark actual playback
966 * RenderCallbackSPDIF: callback for SPDIF audio output
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
)
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
);
995 #pragma mark initialization
998 WarnConfiguration(audio_output_t
*p_aout
)
1000 struct 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 */
1008 char *dup
= strdup(warned_devices
);
1012 for (const char *dev
= strtok_r(dup
, ";", &savetpr
);
1013 dev
!= NULL
&& !dev_is_warned
;
1014 dev
= strtok_r(NULL
, ";", &savetpr
))
1017 int devid
= atoi(dev
);
1018 if (devid
>= 0 && (unsigned) devid
== p_sys
->i_selected_dev
)
1020 dev_is_warned
= true;
1028 /* Warn only one time per device */
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
, ';');
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
1066 StartAnalog(audio_output_t
*p_aout
, audio_sample_format_t
*fmt
,
1067 mtime_t i_latency_us
)
1069 struct 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
));
1091 ca_LogErr("cannot select audio output device, PCM output failed");
1095 /* Get the channel layout of the device side of the unit (vlc -> unit ->
1097 err
= AudioUnitGetPropertyInfo(p_sys
->au_unit
,
1098 kAudioDevicePropertyPreferredChannelLayout
,
1099 kAudioUnitScope_Output
, 0, &i_param_size
,
1103 layout
= (AudioChannelLayout
*)malloc(i_param_size
);
1108 AudioUnitGetProperty(p_sys
->au_unit
,
1109 kAudioDevicePropertyPreferredChannelLayout
,
1110 kAudioUnitScope_Output
, 0, layout
,
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
)
1126 err
= AudioOutputUnitStart(p_sys
->au_unit
);
1129 ca_LogErr("AudioUnitStart failed");
1130 au_Uninitialize(p_aout
, p_sys
->au_unit
);
1134 /* Set volume for output unit */
1135 VolumeSet(p_aout
, p_sys
->f_volume
);
1136 MuteSet(p_aout
, p_sys
->b_mute
);
1140 if (warn_configuration
)
1141 WarnConfiguration(p_aout
);
1145 AudioComponentInstanceDispose(p_sys
->au_unit
);
1147 return VLC_EGENERIC
;
1151 * StartSPDIF: Setup an encoded digital stream (SPDIF) output
1154 StartSPDIF(audio_output_t
* p_aout
, audio_sample_format_t
*fmt
,
1155 mtime_t i_latency_us
)
1157 struct aout_sys_t
*p_sys
= p_aout
->sys
;
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
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"
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
)
1210 if (AO_HASPROP(p_sys
->i_selected_dev
, kAudioDevicePropertySupportsMixing
,
1211 kAudioObjectPropertyScopeGlobal
))
1213 /* Set mixable to false if we are allowed to */
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
)
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");
1238 /* Get a list of all the streams on this device */
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
)
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 */
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
)
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
))
1274 /* if this stream supports a digital (cac3) format, then go set it.
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 ¤t_streamformat
,
1287 kAudioStreamPropertyPhysicalFormat
,
1288 kAudioObjectPropertyScopeGlobal
);
1289 if (ret
!= VLC_SUCCESS
)
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");
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
;
1327 else if (p_format_list
[j
].mFormat
.mSampleRate
==
1328 p_sys
->sfmt_revert
.mSampleRate
)
1329 i_current_rate_format
= j
;
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
1351 desired_stream_format
=
1352 p_format_list
[i_current_rate_format
].mFormat
;
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
);
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
;
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 */
1387 AudioDeviceCreateIOProcID(p_sys
->i_selected_dev
,
1388 RenderCallbackSPDIF
,
1389 p_aout
, &p_sys
->i_procID
);
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
;
1404 err
= AudioDeviceStart(p_sys
->i_selected_dev
, p_sys
->i_procID
);
1407 ca_LogErr("Failed to start audio device");
1409 err
= AudioDeviceDestroyIOProcID(p_sys
->i_selected_dev
, p_sys
->i_procID
);
1411 ca_LogErr("Failed to destroy process ID");
1413 return VLC_EGENERIC
;
1416 msg_Dbg(p_aout
, "Using audio device for digital output");
1421 Stop(audio_output_t
*p_aout
)
1423 struct aout_sys_t
*p_sys
= p_aout
->sys
;
1424 OSStatus err
= noErr
;
1426 msg_Dbg(p_aout
, "Stopping the auhal module");
1430 AudioOutputUnitStop(p_sys
->au_unit
);
1431 au_Uninitialize(p_aout
, p_sys
->au_unit
);
1432 AudioComponentInstanceDispose(p_sys
->au_unit
);
1436 assert(p_sys
->b_digital
);
1439 err
= AudioDeviceStop(p_sys
->i_selected_dev
,
1442 ca_LogErr("AudioDeviceStop failed");
1444 /* Remove IOProc callback */
1445 err
= AudioDeviceDestroyIOProcID(p_sys
->i_selected_dev
,
1448 ca_LogErr("Failed to destroy Process ID");
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
)
1458 /* Revert mixable to true if we are allowed to */
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
);
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
);
1507 Start(audio_output_t
*p_aout
, audio_sample_format_t
*restrict fmt
)
1509 UInt32 i_param_size
= 0;
1510 struct aout_sys_t
*p_sys
= NULL
;
1512 /* Use int here, to match kAudioDevicePropertyDeviceIsAlive
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
);
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
;
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 */
1569 msg_Warn(p_aout
, "selected audio device is not alive, switching"
1570 " to default device");
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
,
1589 kAudioHardwarePropertyDefaultOutputDevice
,
1590 kAudioObjectPropertyScopeOutput
);
1591 if (ret
!= VLC_SUCCESS
)
1593 vlc_mutex_unlock(&p_sys
->selected_device_lock
);
1594 return VLC_EGENERIC
;
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 mtime_t i_latency_us
= i_latency_samples
* CLOCK_FREQ
/ fmt
->i_rate
;
1614 /* Check for Digital mode or Analog output mode */
1617 if (StartSPDIF (p_aout
, fmt
, i_latency_us
) == VLC_SUCCESS
)
1619 msg_Dbg(p_aout
, "digital output successfully opened");
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
;
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
);
1670 for (CFIndex x
= 0; x
< count
; x
++)
1672 AudioDeviceID deviceId
= 0;
1673 CFNumberRef cfn_device_id
=
1674 CFArrayGetValueAtIndex(device_list_cpy
, x
);
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
);
1691 vlc_mutex_destroy(&p_sys
->selected_device_lock
);
1692 vlc_mutex_destroy(&p_sys
->device_list_lock
);
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
))
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
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
);
1756 RebuildDeviceList(p_aout
, NULL
);
1757 p_sys
->i_new_selected_dev
= 0;
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
);