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 ca_Render(p_aout
, p_output
, i_size
);
992 #pragma mark initialization
995 WarnConfiguration(audio_output_t
*p_aout
)
997 struct aout_sys_t
*p_sys
= p_aout
->sys
;
998 char *warned_devices
= var_CreateGetNonEmptyString(p_aout
, "auhal-warned-devices");
999 bool dev_is_warned
= false;
1000 unsigned dev_count
= 0;
1002 /* Check if the actual device was already warned */
1005 char *dup
= strdup(warned_devices
);
1009 for (const char *dev
= strtok_r(dup
, ";", &savetpr
);
1010 dev
!= NULL
&& !dev_is_warned
;
1011 dev
= strtok_r(NULL
, ";", &savetpr
))
1014 int devid
= atoi(dev
);
1015 if (devid
>= 0 && (unsigned) devid
== p_sys
->i_selected_dev
)
1017 dev_is_warned
= true;
1025 /* Warn only one time per device */
1028 msg_Warn(p_aout
, "You should configure your speaker layout with "
1029 "Audio Midi Setup in /Applications/Utilities. VLC will "
1030 "output Stereo only.");
1031 vlc_dialog_display_error(p_aout
,
1032 _("Audio device is not configured"), "%s",
1033 _("You should configure your speaker layout with "
1034 "\"Audio Midi Setup\" in /Applications/"
1035 "Utilities. VLC will output Stereo only."));
1037 /* Don't save too many devices */
1038 if (dev_count
>= 10)
1040 char *end
= strrchr(warned_devices
, ';');
1045 /* Add the actual device to the list of warned devices */
1046 char *new_warned_devices
;
1047 if (asprintf(&new_warned_devices
, "%u%s%s", p_sys
->i_selected_dev
,
1048 warned_devices
? ";" : "",
1049 warned_devices
? warned_devices
: "") != -1)
1051 config_PutPsz("auhal-warned-devices", new_warned_devices
);
1052 var_SetString(p_aout
, "auhal-warned-devices", new_warned_devices
);
1053 free(new_warned_devices
);
1056 free(warned_devices
);
1060 * StartAnalog: open and setup a HAL AudioUnit to do PCM audio output
1063 StartAnalog(audio_output_t
*p_aout
, audio_sample_format_t
*fmt
,
1064 mtime_t i_latency_us
)
1066 struct aout_sys_t
*p_sys
= p_aout
->sys
;
1067 OSStatus err
= noErr
;
1068 UInt32 i_param_size
;
1069 AudioChannelLayout
*layout
= NULL
;
1071 if (aout_FormatNbChannels(fmt
) == 0)
1072 return VLC_EGENERIC
;
1074 p_sys
->au_unit
= au_NewOutputInstance(p_aout
, kAudioUnitSubType_HALOutput
);
1075 if (p_sys
->au_unit
== NULL
)
1076 return VLC_EGENERIC
;
1078 p_aout
->current_sink_info
.headphones
= AudioDeviceIsAHeadphone(p_aout
, p_sys
->i_selected_dev
);
1080 /* Set the device we will use for this output unit */
1081 err
= AudioUnitSetProperty(p_sys
->au_unit
,
1082 kAudioOutputUnitProperty_CurrentDevice
,
1083 kAudioUnitScope_Global
, 0,
1084 &p_sys
->i_selected_dev
, sizeof(AudioObjectID
));
1088 ca_LogErr("cannot select audio output device, PCM output failed");
1092 /* Get the channel layout of the device side of the unit (vlc -> unit ->
1094 err
= AudioUnitGetPropertyInfo(p_sys
->au_unit
,
1095 kAudioDevicePropertyPreferredChannelLayout
,
1096 kAudioUnitScope_Output
, 0, &i_param_size
,
1100 layout
= (AudioChannelLayout
*)malloc(i_param_size
);
1105 AudioUnitGetProperty(p_sys
->au_unit
,
1106 kAudioDevicePropertyPreferredChannelLayout
,
1107 kAudioUnitScope_Output
, 0, layout
,
1113 ca_LogWarn("device driver does not support "
1114 "kAudioDevicePropertyPreferredChannelLayout - using stereo");
1116 /* Do the last VLC aout setups */
1117 bool warn_configuration
;
1118 int ret
= au_Initialize(p_aout
, p_sys
->au_unit
, fmt
, layout
, i_latency_us
,
1119 &warn_configuration
);
1120 if (ret
!= VLC_SUCCESS
)
1123 err
= AudioOutputUnitStart(p_sys
->au_unit
);
1126 ca_LogErr("AudioUnitStart failed");
1127 au_Uninitialize(p_aout
, p_sys
->au_unit
);
1131 /* Set volume for output unit */
1132 VolumeSet(p_aout
, p_sys
->f_volume
);
1133 MuteSet(p_aout
, p_sys
->b_mute
);
1137 if (warn_configuration
)
1138 WarnConfiguration(p_aout
);
1142 AudioComponentInstanceDispose(p_sys
->au_unit
);
1144 return VLC_EGENERIC
;
1148 * StartSPDIF: Setup an encoded digital stream (SPDIF) output
1151 StartSPDIF(audio_output_t
* p_aout
, audio_sample_format_t
*fmt
,
1152 mtime_t i_latency_us
)
1154 struct aout_sys_t
*p_sys
= p_aout
->sys
;
1157 /* Check if device supports digital */
1158 if (!AudioDeviceSupportsDigital(p_aout
, p_sys
->i_selected_dev
))
1160 msg_Dbg(p_aout
, "Audio device supports PCM mode only");
1161 return VLC_EGENERIC
;
1164 ret
= AO_GET1PROP(p_sys
->i_selected_dev
, pid_t
, &p_sys
->i_hog_pid
,
1165 kAudioDevicePropertyHogMode
,
1166 kAudioObjectPropertyScopeOutput
);
1167 if (ret
!= VLC_SUCCESS
)
1169 /* This is not a fatal error. Some drivers simply don't support this
1171 p_sys
->i_hog_pid
= -1;
1174 if (p_sys
->i_hog_pid
!= -1 && p_sys
->i_hog_pid
!= getpid())
1176 msg_Err(p_aout
, "Selected audio device is exclusively in use by another"
1178 vlc_dialog_display_error(p_aout
, _("Audio output failed"), "%s",
1179 _("The selected audio output device is exclusively in "
1180 "use by another program."));
1181 return VLC_EGENERIC
;
1184 AudioStreamBasicDescription desired_stream_format
;
1185 memset(&desired_stream_format
, 0, sizeof(desired_stream_format
));
1187 /* Start doing the SPDIF setup proces */
1188 p_sys
->b_digital
= true;
1190 /* Hog the device */
1191 p_sys
->i_hog_pid
= getpid();
1194 * HACK: On 10.6, auhal will trigger the streams changed callback when
1195 * calling below line, directly in the same thread. This call needs to be
1196 * ignored to avoid endless restarting.
1198 p_sys
->b_ignore_streams_changed_callback
= true;
1199 ret
= AO_SETPROP(p_sys
->i_selected_dev
, sizeof(p_sys
->i_hog_pid
),
1200 &p_sys
->i_hog_pid
, kAudioDevicePropertyHogMode
,
1201 kAudioObjectPropertyScopeOutput
);
1202 p_sys
->b_ignore_streams_changed_callback
= false;
1204 if (ret
!= VLC_SUCCESS
)
1207 if (AO_HASPROP(p_sys
->i_selected_dev
, kAudioDevicePropertySupportsMixing
,
1208 kAudioObjectPropertyScopeGlobal
))
1210 /* Set mixable to false if we are allowed to */
1212 bool b_writeable
= AO_ISPROPSETTABLE(p_sys
->i_selected_dev
,
1213 kAudioDevicePropertySupportsMixing
,
1214 kAudioObjectPropertyScopeGlobal
);
1216 ret
= AO_GET1PROP(p_sys
->i_selected_dev
, UInt32
, &b_mix
,
1217 kAudioDevicePropertySupportsMixing
,
1218 kAudioObjectPropertyScopeGlobal
);
1219 if (ret
== VLC_SUCCESS
&& b_writeable
)
1222 ret
= AO_SETPROP(p_sys
->i_selected_dev
, sizeof(UInt32
), &b_mix
,
1223 kAudioDevicePropertySupportsMixing
,
1224 kAudioObjectPropertyScopeGlobal
);
1225 p_sys
->b_changed_mixing
= true;
1228 if (ret
!= VLC_SUCCESS
)
1230 msg_Err(p_aout
, "failed to set mixmode");
1235 /* Get a list of all the streams on this device */
1237 AudioStreamID
*p_streams
;
1238 ret
= AO_GETPROP(p_sys
->i_selected_dev
, AudioStreamID
, &i_streams
,
1239 &p_streams
, kAudioDevicePropertyStreams
,
1240 kAudioObjectPropertyScopeOutput
);
1241 if (ret
!= VLC_SUCCESS
)
1244 for (unsigned i
= 0; i
< i_streams
&& p_sys
->i_stream_index
< 0 ; i
++)
1246 /* Find a stream with a cac3 stream */
1247 bool b_digital
= false;
1249 /* Retrieve all the stream formats supported by each output stream */
1251 AudioStreamRangedDescription
*p_format_list
;
1252 int ret
= AO_GETPROP(p_streams
[i
], AudioStreamRangedDescription
,
1253 &i_formats
, &p_format_list
,
1254 kAudioStreamPropertyAvailablePhysicalFormats
,
1255 kAudioObjectPropertyScopeGlobal
);
1256 if (ret
!= VLC_SUCCESS
)
1259 /* Check if one of the supported formats is a digital format */
1260 for (size_t j
= 0; j
< i_formats
; j
++)
1262 if (IsAudioFormatDigital(p_format_list
[j
].mFormat
.mFormatID
))
1271 /* if this stream supports a digital (cac3) format, then go set it.
1273 int i_requested_rate_format
= -1;
1274 int i_current_rate_format
= -1;
1275 int i_backup_rate_format
= -1;
1277 if (!p_sys
->b_revert
)
1279 /* Retrieve the original format of this stream first if not
1280 * done so already */
1281 AudioStreamBasicDescription current_streamformat
;
1282 int ret
= AO_GET1PROP(p_streams
[i
], AudioStreamBasicDescription
,
1283 ¤t_streamformat
,
1284 kAudioStreamPropertyPhysicalFormat
,
1285 kAudioObjectPropertyScopeGlobal
);
1286 if (ret
!= VLC_SUCCESS
)
1290 * Only the first found format id is accepted. In case of
1291 * another id later on, we still use the already saved one.
1292 * This can happen if the user plugs in a spdif cable while a
1293 * stream is already playing. Then, auhal already misleadingly
1294 * reports an ac3 format here whereas the original format
1295 * should be still pcm.
1297 if (p_sys
->sfmt_revert
.mFormatID
> 0
1298 && p_sys
->sfmt_revert
.mFormatID
!= current_streamformat
.mFormatID
1299 && p_streams
[i
] == p_sys
->i_stream_id
)
1301 msg_Warn(p_aout
, STREAM_FORMAT_MSG("Detected current stream"
1302 " format: ", current_streamformat
));
1303 msg_Warn(p_aout
, "... there is another stream format "
1304 "already stored, the current one is ignored");
1307 p_sys
->sfmt_revert
= current_streamformat
;
1309 p_sys
->b_revert
= true;
1312 p_sys
->i_stream_id
= p_streams
[i
];
1313 p_sys
->i_stream_index
= i
;
1315 for (size_t j
= 0; j
< i_formats
; j
++)
1317 if (IsAudioFormatDigital(p_format_list
[j
].mFormat
.mFormatID
))
1319 if (p_format_list
[j
].mFormat
.mSampleRate
== fmt
->i_rate
)
1321 i_requested_rate_format
= j
;
1324 else if (p_format_list
[j
].mFormat
.mSampleRate
==
1325 p_sys
->sfmt_revert
.mSampleRate
)
1326 i_current_rate_format
= j
;
1329 if (i_backup_rate_format
< 0
1330 || p_format_list
[j
].mFormat
.mSampleRate
>
1331 p_format_list
[i_backup_rate_format
].mFormat
.mSampleRate
)
1332 i_backup_rate_format
= j
;
1338 if (i_requested_rate_format
>= 0)
1340 /* We prefer to output at the samplerate of the original audio */
1341 desired_stream_format
=
1342 p_format_list
[i_requested_rate_format
].mFormat
;
1344 else if (i_current_rate_format
>= 0)
1346 /* If not possible, we will try to use the current samplerate
1348 desired_stream_format
=
1349 p_format_list
[i_current_rate_format
].mFormat
;
1353 /* And if we have to, any digital format will be just fine
1354 * (highest rate possible) */
1355 desired_stream_format
=
1356 p_format_list
[i_backup_rate_format
].mFormat
;
1359 free(p_format_list
);
1363 msg_Dbg(p_aout
, STREAM_FORMAT_MSG("original stream format: ",
1364 p_sys
->sfmt_revert
));
1366 if (!AudioStreamChangeFormat(p_aout
, p_sys
->i_stream_id
, desired_stream_format
))
1368 msg_Err(p_aout
, "failed to change stream format for SPDIF output");
1369 return VLC_EGENERIC
;
1372 /* Set the format flags */
1373 if (desired_stream_format
.mFormatFlags
& kAudioFormatFlagIsBigEndian
)
1374 fmt
->i_format
= VLC_CODEC_SPDIFB
;
1376 fmt
->i_format
= VLC_CODEC_SPDIFL
;
1377 fmt
->i_bytes_per_frame
= AOUT_SPDIF_SIZE
;
1378 fmt
->i_frame_length
= A52_FRAME_NB
;
1379 fmt
->i_rate
= (unsigned int)desired_stream_format
.mSampleRate
;
1380 aout_FormatPrepare(fmt
);
1382 /* Add IOProc callback */
1384 AudioDeviceCreateIOProcID(p_sys
->i_selected_dev
,
1385 RenderCallbackSPDIF
,
1386 p_aout
, &p_sys
->i_procID
);
1389 ca_LogErr("Failed to create Process ID");
1390 return VLC_EGENERIC
;
1393 ret
= ca_Initialize(p_aout
, fmt
, i_latency_us
);
1394 if (ret
!= VLC_SUCCESS
)
1396 AudioDeviceDestroyIOProcID(p_sys
->i_selected_dev
, p_sys
->i_procID
);
1397 return VLC_EGENERIC
;
1401 err
= AudioDeviceStart(p_sys
->i_selected_dev
, p_sys
->i_procID
);
1404 ca_LogErr("Failed to start audio device");
1406 err
= AudioDeviceDestroyIOProcID(p_sys
->i_selected_dev
, p_sys
->i_procID
);
1408 ca_LogErr("Failed to destroy process ID");
1410 return VLC_EGENERIC
;
1413 msg_Dbg(p_aout
, "Using audio device for digital output");
1418 Stop(audio_output_t
*p_aout
)
1420 struct aout_sys_t
*p_sys
= p_aout
->sys
;
1421 OSStatus err
= noErr
;
1423 msg_Dbg(p_aout
, "Stopping the auhal module");
1427 AudioOutputUnitStop(p_sys
->au_unit
);
1428 au_Uninitialize(p_aout
, p_sys
->au_unit
);
1429 AudioComponentInstanceDispose(p_sys
->au_unit
);
1433 assert(p_sys
->b_digital
);
1436 err
= AudioDeviceStop(p_sys
->i_selected_dev
,
1439 ca_LogErr("AudioDeviceStop failed");
1441 /* Remove IOProc callback */
1442 err
= AudioDeviceDestroyIOProcID(p_sys
->i_selected_dev
,
1445 ca_LogErr("Failed to destroy Process ID");
1448 && !AudioStreamChangeFormat(p_aout
, p_sys
->i_stream_id
, p_sys
->sfmt_revert
))
1449 msg_Err(p_aout
, "failed to revert stream format in close");
1451 if (p_sys
->b_changed_mixing
1452 && p_sys
->sfmt_revert
.mFormatID
!= kAudioFormat60958AC3
)
1455 /* Revert mixable to true if we are allowed to */
1457 AO_ISPROPSETTABLE(p_sys
->i_selected_dev
,
1458 kAudioDevicePropertySupportsMixing
,
1459 kAudioObjectPropertyScopeGlobal
);
1460 int ret
= AO_GET1PROP(p_sys
->i_selected_dev
, UInt32
, &b_mix
,
1461 kAudioDevicePropertySupportsMixing
,
1462 kAudioObjectPropertyScopeOutput
);
1463 if (ret
== VLC_SUCCESS
&& b_writeable
)
1465 msg_Dbg(p_aout
, "mixable is: %d", b_mix
);
1467 ret
= AO_SETPROP(p_sys
->i_selected_dev
,
1468 sizeof(UInt32
), &b_mix
,
1469 kAudioDevicePropertySupportsMixing
,
1470 kAudioObjectPropertyScopeOutput
);
1473 if (ret
!= VLC_SUCCESS
)
1474 msg_Err(p_aout
, "failed to re-set mixmode");
1476 ca_Uninitialize(p_aout
);
1478 if (p_sys
->i_hog_pid
== getpid())
1480 p_sys
->i_hog_pid
= -1;
1483 * HACK: On 10.6, auhal will trigger the streams changed callback
1484 * when calling below line, directly in the same thread. This call
1485 * needs to be ignored to avoid endless restarting.
1487 p_sys
->b_ignore_streams_changed_callback
= true;
1488 AO_SETPROP(p_sys
->i_selected_dev
, sizeof(p_sys
->i_hog_pid
),
1489 &p_sys
->i_hog_pid
, kAudioDevicePropertyHogMode
,
1490 kAudioObjectPropertyScopeOutput
);
1491 p_sys
->b_ignore_streams_changed_callback
= false;
1494 p_sys
->b_digital
= false;
1497 /* remove audio device alive callback */
1498 AO_UPDATELISTENER(p_sys
->i_selected_dev
, false, DeviceAliveListener
, p_aout
,
1499 kAudioDevicePropertyDeviceIsAlive
,
1500 kAudioObjectPropertyScopeGlobal
);
1504 Start(audio_output_t
*p_aout
, audio_sample_format_t
*restrict fmt
)
1506 UInt32 i_param_size
= 0;
1507 struct aout_sys_t
*p_sys
= NULL
;
1509 /* Use int here, to match kAudioDevicePropertyDeviceIsAlive
1511 int b_alive
= false;
1513 if (AOUT_FMT_HDMI(fmt
))
1514 return VLC_EGENERIC
;
1516 p_sys
= p_aout
->sys
;
1517 p_sys
->b_digital
= false;
1518 p_sys
->au_unit
= NULL
;
1519 p_sys
->i_stream_index
= -1;
1520 p_sys
->b_revert
= false;
1521 p_sys
->b_changed_mixing
= false;
1523 vlc_mutex_lock(&p_sys
->selected_device_lock
);
1525 if (AOUT_FMT_SPDIF (fmt
))
1527 if (!(p_sys
->i_new_selected_dev
& AOUT_VAR_SPDIF_FLAG
))
1529 vlc_mutex_unlock(&p_sys
->selected_device_lock
);
1530 return VLC_EGENERIC
;
1537 p_sys
->i_selected_dev
= p_sys
->i_new_selected_dev
& ~AOUT_VAR_SPDIF_FLAG
;
1539 aout_FormatPrint(p_aout
, "VLC is looking for:", fmt
);
1541 msg_Dbg(p_aout
, "attempting to use device %i", p_sys
->i_selected_dev
);
1543 if (p_sys
->i_selected_dev
> 0)
1545 /* Check if device is in devices list. Only checking for
1546 * kAudioDevicePropertyDeviceIsAlive is not sufficient, as a former
1547 * airplay device might be already gone, but the device number might be
1548 * still valid. Core Audio even says that this device would be alive.
1549 * Don't ask why, its Core Audio. */
1550 CFIndex count
= CFArrayGetCount(p_sys
->device_list
);
1551 CFNumberRef deviceNumber
=
1552 CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
,
1553 &p_sys
->i_selected_dev
);
1554 if (CFArrayContainsValue(p_sys
->device_list
,
1555 CFRangeMake(0, count
), deviceNumber
))
1557 /* Check if the desired device is alive and usable */
1558 i_param_size
= sizeof(b_alive
);
1559 int ret
= AO_GET1PROP(p_sys
->i_selected_dev
, int, &b_alive
,
1560 kAudioDevicePropertyDeviceIsAlive
,
1561 kAudioObjectPropertyScopeGlobal
);
1562 if (ret
!= VLC_SUCCESS
)
1563 b_alive
= false; /* Be tolerant */
1566 msg_Warn(p_aout
, "selected audio device is not alive, switching"
1567 " to default device");
1572 msg_Warn(p_aout
, "device id %i not found in the current devices "
1573 "list, fallback to default device", p_sys
->i_selected_dev
);
1575 CFRelease(deviceNumber
);
1578 p_sys
->b_selected_dev_is_default
= false;
1579 if (!b_alive
|| p_sys
->i_selected_dev
== 0)
1581 p_sys
->b_selected_dev_is_default
= true;
1583 AudioObjectID defaultDeviceID
= 0;
1584 int ret
= AO_GET1PROP(kAudioObjectSystemObject
, AudioObjectID
,
1586 kAudioHardwarePropertyDefaultOutputDevice
,
1587 kAudioObjectPropertyScopeOutput
);
1588 if (ret
!= VLC_SUCCESS
)
1590 vlc_mutex_unlock(&p_sys
->selected_device_lock
);
1591 return VLC_EGENERIC
;
1594 msg_Dbg(p_aout
, "using default audio device %i", defaultDeviceID
);
1596 p_sys
->i_selected_dev
= defaultDeviceID
;
1598 vlc_mutex_unlock(&p_sys
->selected_device_lock
);
1600 /* add a callback to see if the device dies later on */
1601 AO_UPDATELISTENER(p_sys
->i_selected_dev
, true, DeviceAliveListener
, p_aout
,
1602 kAudioDevicePropertyDeviceIsAlive
,
1603 kAudioObjectPropertyScopeGlobal
);
1605 /* get device latency */
1606 UInt32 i_latency_samples
;
1607 AO_GET1PROP(p_sys
->i_selected_dev
, UInt32
, &i_latency_samples
,
1608 kAudioDevicePropertyLatency
, kAudioObjectPropertyScopeOutput
);
1609 mtime_t i_latency_us
= i_latency_samples
* CLOCK_FREQ
/ fmt
->i_rate
;
1611 /* Check for Digital mode or Analog output mode */
1614 if (StartSPDIF (p_aout
, fmt
, i_latency_us
) == VLC_SUCCESS
)
1616 msg_Dbg(p_aout
, "digital output successfully opened");
1622 if (StartAnalog(p_aout
, fmt
, i_latency_us
) == VLC_SUCCESS
)
1624 msg_Dbg(p_aout
, "analog output successfully opened");
1625 fmt
->channel_type
= AUDIO_CHANNEL_TYPE_BITMAP
;
1630 msg_Err(p_aout
, "opening auhal output failed");
1631 AO_UPDATELISTENER(p_sys
->i_selected_dev
, false, DeviceAliveListener
, p_aout
,
1632 kAudioDevicePropertyDeviceIsAlive
,
1633 kAudioObjectPropertyScopeGlobal
);
1634 return VLC_EGENERIC
;
1637 static void Close(vlc_object_t
*obj
)
1639 audio_output_t
*p_aout
= (audio_output_t
*)obj
;
1640 aout_sys_t
*p_sys
= p_aout
->sys
;
1642 /* remove audio devices callback */
1643 AO_UPDATELISTENER(kAudioObjectSystemObject
, false, DevicesListener
, p_aout
,
1644 kAudioHardwarePropertyDevices
,
1645 kAudioObjectPropertyScopeGlobal
);
1647 /* remove listener to be notified about changes in default audio device */
1648 AO_UPDATELISTENER(kAudioObjectSystemObject
, false,
1649 DefaultDeviceChangedListener
, p_aout
,
1650 kAudioHardwarePropertyDefaultOutputDevice
,
1651 kAudioObjectPropertyScopeGlobal
);
1654 * StreamsChangedListener can rebuild the device list and thus held the
1655 * device_list_lock. To avoid a possible deadlock, an array copy is
1656 * created here. In rare cases, this can lead to missing
1657 * StreamsChangedListener callback deregistration (TODO).
1659 vlc_mutex_lock(&p_sys
->device_list_lock
);
1660 CFArrayRef device_list_cpy
= CFArrayCreateCopy(NULL
, p_sys
->device_list
);
1661 vlc_mutex_unlock(&p_sys
->device_list_lock
);
1663 /* remove streams callbacks */
1664 CFIndex count
= CFArrayGetCount(device_list_cpy
);
1667 for (CFIndex x
= 0; x
< count
; x
++)
1669 AudioDeviceID deviceId
= 0;
1670 CFNumberRef cfn_device_id
=
1671 CFArrayGetValueAtIndex(device_list_cpy
, x
);
1675 CFNumberGetValue(cfn_device_id
, kCFNumberSInt32Type
, &deviceId
);
1676 if (!(deviceId
& AOUT_VAR_SPDIF_FLAG
))
1677 ManageAudioStreamsCallback(p_aout
, deviceId
, false);
1681 CFRelease(device_list_cpy
);
1682 CFRelease(p_sys
->device_list
);
1684 char *psz_device
= aout_DeviceGet(p_aout
);
1685 config_PutPsz("auhal-audio-device", psz_device
);
1688 vlc_mutex_destroy(&p_sys
->selected_device_lock
);
1689 vlc_mutex_destroy(&p_sys
->device_list_lock
);
1695 static int Open(vlc_object_t
*obj
)
1697 audio_output_t
*p_aout
= (audio_output_t
*)obj
;
1698 aout_sys_t
*p_sys
= calloc(1, sizeof (*p_sys
));
1699 if (unlikely(p_sys
== NULL
))
1702 vlc_mutex_init(&p_sys
->device_list_lock
);
1703 vlc_mutex_init(&p_sys
->selected_device_lock
);
1704 p_sys
->b_digital
= false;
1705 p_sys
->b_ignore_streams_changed_callback
= false;
1706 p_sys
->b_selected_dev_is_default
= false;
1707 memset(&p_sys
->sfmt_revert
, 0, sizeof(p_sys
->sfmt_revert
));
1708 p_sys
->i_stream_id
= 0;
1710 p_aout
->sys
= p_sys
;
1711 p_aout
->start
= Start
;
1712 p_aout
->stop
= Stop
;
1713 p_aout
->volume_set
= VolumeSet
;
1714 p_aout
->mute_set
= MuteSet
;
1715 p_aout
->device_select
= SwitchAudioDevice
;
1716 p_sys
->device_list
= CFArrayCreate(kCFAllocatorDefault
, NULL
, 0, NULL
);
1719 * Force an own run loop for callbacks.
1721 * According to rtaudio, this is absolutely necessary since 10.6 to get
1722 * correct notifications. It might fix issues when using the module as a
1723 * library where a proper loop is not setup already.
1725 CFRunLoopRef theRunLoop
= NULL
;
1726 AO_SETPROP(kAudioObjectSystemObject
, sizeof(CFRunLoopRef
),
1727 &theRunLoop
, kAudioHardwarePropertyRunLoop
,
1728 kAudioObjectPropertyScopeGlobal
);
1730 /* Attach a listener so that we are notified of a change in the device
1732 AO_UPDATELISTENER(kAudioObjectSystemObject
, true, DevicesListener
, p_aout
,
1733 kAudioHardwarePropertyDevices
,
1734 kAudioObjectPropertyScopeGlobal
);
1736 /* Attach a listener to be notified about changes in default audio device */
1737 AO_UPDATELISTENER(kAudioObjectSystemObject
, true,
1738 DefaultDeviceChangedListener
, p_aout
,
1739 kAudioHardwarePropertyDefaultOutputDevice
,
1740 kAudioObjectPropertyScopeGlobal
);
1742 char *psz_audio_device
= var_InheritString(p_aout
, "auhal-audio-device");
1743 if (psz_audio_device
!= NULL
)
1745 int dev_id_int
= atoi(psz_audio_device
);
1746 UInt32 dev_id
= dev_id_int
< 0 ? 0 : dev_id_int
;
1747 RebuildDeviceList(p_aout
, &dev_id
);
1748 p_sys
->i_new_selected_dev
= dev_id
;
1749 free(psz_audio_device
);
1753 RebuildDeviceList(p_aout
, NULL
);
1754 p_sys
->i_new_selected_dev
= 0;
1758 sprintf(deviceid
, "%i", p_sys
->i_new_selected_dev
);
1759 aout_DeviceReport(p_aout
, deviceid
);
1761 /* remember the volume */
1762 p_sys
->f_volume
= var_InheritInteger(p_aout
, "auhal-volume")
1763 / (float)AOUT_VOLUME_DEFAULT
;
1764 aout_VolumeReport(p_aout
, p_sys
->f_volume
);
1765 p_sys
->b_mute
= var_InheritBool(p_aout
, "mute");
1766 aout_MuteReport(p_aout
, p_sys
->b_mute
);