auhal.c 55.3 KB
Newer Older
1
/*****************************************************************************
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
2
 * auhal.c: AUHAL and Coreaudio output plugin
3
 *****************************************************************************
4
 * Copyright (C) 2005 - 2017 VLC authors and VideoLAN
5 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>
9
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
10 11 12
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
13
 * (at your option) any later version.
14
 *
15 16
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
17 18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
19
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
20
 * You should have received a copy of the GNU Lesser General Public License
21 22
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24
 *****************************************************************************/

25
#pragma mark includes
26

27
#import "coreaudio_common.h"
28

29
#import <vlc_plugin.h>
Thomas Guillem's avatar
Thomas Guillem committed
30
#import <vlc_dialog.h>                      // vlc_dialog_display_error
31

32
#import <CoreAudio/CoreAudio.h>             // AudioDeviceID
33
#import <CoreServices/CoreServices.h>
34

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
#pragma mark -
#pragma mark local prototypes & module descriptor


#define AOUT_VOLUME_DEFAULT             256
#define AOUT_VOLUME_MAX                 512

#define VOLUME_TEXT N_("Audio volume")
#define VOLUME_LONGTEXT VOLUME_TEXT

#define DEVICE_TEXT N_("Last audio device")
#define DEVICE_LONGTEXT DEVICE_TEXT

static int      Open                    (vlc_object_t *);
static void     Close                   (vlc_object_t *);

vlc_module_begin ()
    set_shortname("auhal")
    set_description(N_("HAL AudioUnit output"))
    set_capability("audio output", 101)
    set_category(CAT_AUDIO)
    set_subcategory(SUBCAT_AUDIO_AOUT)
    set_callbacks(Open, Close)
    add_integer("auhal-volume", AOUT_VOLUME_DEFAULT,
                VOLUME_TEXT, VOLUME_LONGTEXT, true)
    change_integer_range(0, AOUT_VOLUME_MAX)
    add_string("auhal-audio-device", "", DEVICE_TEXT, DEVICE_LONGTEXT, true)
    add_obsolete_integer("macosx-audio-device") /* since 2.1.0 */
vlc_module_end ()

65 66 67
#pragma mark -
#pragma mark private declarations

68
#define AOUT_VAR_SPDIF_FLAG 0xf00000
69 70 71 72 73 74 75 76 77

/*****************************************************************************
 * aout_sys_t: private audio output method descriptor
 *****************************************************************************
 * This structure is part of the audio output thread descriptor.
 * It describes the CoreAudio specific properties of an output thread.
 *****************************************************************************/
struct aout_sys_t
{
78 79
    struct aout_sys_common c;

Thomas Guillem's avatar
Thomas Guillem committed
80 81 82 83 84 85
    /* DeviceID of the selected device */
    AudioObjectID               i_selected_dev;
    /* DeviceID of device which will be selected on start */
    AudioObjectID               i_new_selected_dev;
    /* true if the user selected the default audio device (id 0) */
    bool                        b_selected_dev_is_default;
86

Thomas Guillem's avatar
Thomas Guillem committed
87 88 89 90
    /* DeviceID of current device */
    AudioDeviceIOProcID         i_procID;
    /* Are we running in digital mode? */
    bool                        b_digital;
91

92
    /* AUHAL specific */
Thomas Guillem's avatar
Thomas Guillem committed
93
    AudioUnit                   au_unit;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
94

95
    /* CoreAudio SPDIF mode specific */
Thomas Guillem's avatar
Thomas Guillem committed
96 97 98 99 100 101 102 103 104 105 106 107 108
    /* Keep the pid of our hog status */
    pid_t                       i_hog_pid;
    /* The StreamID that has a cac3 streamformat */
    AudioStreamID               i_stream_id;
    /* The index of i_stream_id in an AudioBufferList */
    int                         i_stream_index;
    /* The original format of the stream */
    AudioStreamBasicDescription sfmt_revert;
    /* Whether we need to revert the stream format */
    bool                        b_revert;
    /* Whether we need to set the mixing mode back */
    bool                        b_changed_mixing;

109
    CFArrayRef                  device_list;
Thomas Guillem's avatar
Thomas Guillem committed
110 111
    /* protects access to device_list */
    vlc_mutex_t                 device_list_lock;
112

Thomas Guillem's avatar
Thomas Guillem committed
113 114 115 116 117
    /* Synchronizes access to i_selected_dev. This is only needed between VLCs
     * audio thread and the core audio callback thread. The value is only
     * changed in Start, further access to this variable within the audio
     * thread (start, stop, close) needs no protection. */
    vlc_mutex_t                 selected_device_lock;
118

119 120
    float                       f_volume;
    bool                        b_mute;
121 122

    bool                        b_ignore_streams_changed_callback;
123 124
};

125
#pragma mark -
126
#pragma mark helpers
127

128 129 130 131 132 133 134
static int
AoGetProperty(audio_output_t *p_aout, AudioObjectID id,
              const AudioObjectPropertyAddress *p_address, size_t i_elm_size,
              ssize_t i_nb_expected_elms, size_t *p_nb_elms, void **pp_out_data,
              void *p_allocated_out_data)
{
    assert(i_elm_size > 0);
135

136 137 138 139 140 141 142
    /* Get data size */
    UInt32 i_out_size;
    OSStatus err = AudioObjectGetPropertyDataSize(id, p_address, 0, NULL,
                                                  &i_out_size);
    if (err != noErr)
    {
        msg_Err(p_aout, "AudioObjectGetPropertyDataSize failed, device id: %i, "
143 144
                "prop: [%4.4s], OSStatus: %d", id, (const char *) &p_address[0],
                (int)err);
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
        return VLC_EGENERIC;
    }

    size_t i_nb_elms = i_out_size / i_elm_size;
    if (p_nb_elms != NULL)
        *p_nb_elms = i_nb_elms;
    /* Check if we get the expected number of elements */
    if (i_nb_expected_elms != -1 && (size_t)i_nb_expected_elms != i_nb_elms)
    {
        msg_Err(p_aout, "AoGetProperty error: expected elements don't match");
        return VLC_EGENERIC;
    }

    if (pp_out_data == NULL && p_allocated_out_data == NULL)
        return VLC_SUCCESS;

    if (i_out_size == 0)
    {
        if (pp_out_data != NULL)
            *pp_out_data = NULL;
        return VLC_SUCCESS;
    }

    /* Alloc data or use pre-allocated one */
    void *p_out_data;
    if (pp_out_data != NULL)
    {
        assert(p_allocated_out_data == NULL);

        *pp_out_data = malloc(i_out_size);
        if (*pp_out_data == NULL)
            return VLC_ENOMEM;
        p_out_data = *pp_out_data;
    }
    else
    {
        assert(p_allocated_out_data != NULL);
        p_out_data = p_allocated_out_data;
    }

    /* Fill data */
    err = AudioObjectGetPropertyData(id, p_address, 0, NULL, &i_out_size,
                                     p_out_data);
    if (err != noErr)
    {
        msg_Err(p_aout, "AudioObjectGetPropertyData failed, device id: %i, "
191 192
                "prop: [%4.4s], OSStatus: %d", id, (const char *) &p_address[0],
                (int) err);
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

        if (pp_out_data != NULL)
            free(*pp_out_data);
        return VLC_EGENERIC;
    }
    assert(p_nb_elms == NULL || *p_nb_elms == (i_out_size / i_elm_size));
    return VLC_SUCCESS;
}

/* Get Audio Object Property data: pp_out_data will be allocated by this MACRO
 * and need to be freed in case of success. */
#define AO_GETPROP(id, type, p_out_nb_elms, pp_out_data, a1, a2) \
    AoGetProperty(p_aout, (id), \
                  &(AudioObjectPropertyAddress) {(a1), (a2), 0}, sizeof(type), \
                  -1, (p_out_nb_elms), (void **)(pp_out_data), NULL)

/* Get 1 Audio Object Property data: pre-allocated by the caller */
#define AO_GET1PROP(id, type, p_out_data, a1, a2) \
    AoGetProperty(p_aout, (id), \
                  &(AudioObjectPropertyAddress) {(a1), (a2), 0}, sizeof(type), \
                  1, NULL, NULL, (p_out_data))

static bool
AoIsPropertySettable(audio_output_t *p_aout, AudioObjectID id,
                     const AudioObjectPropertyAddress *p_address)
218
{
219 220 221 222 223
    Boolean b_settable;
    OSStatus err = AudioObjectIsPropertySettable(id, p_address, &b_settable);
    if (err != noErr)
    {
        msg_Warn(p_aout, "AudioObjectIsPropertySettable failed, device id: %i, "
224 225
                 "prop: [%4.4s], OSStatus: %d", id, (const char *)&p_address[0],
                 (int)err);
226 227 228 229
        return false;
    }
    return b_settable;
}
230

231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
#define AO_ISPROPSETTABLE(id, a1, a2) \
    AoIsPropertySettable(p_aout, (id), \
                         &(AudioObjectPropertyAddress) { (a1), (a2), 0})

#define AO_HASPROP(id, a1, a2) \
    AudioObjectHasProperty((id), &(AudioObjectPropertyAddress) { (a1), (a2), 0})

static int
AoSetProperty(audio_output_t *p_aout, AudioObjectID id,
              const AudioObjectPropertyAddress *p_address, size_t i_data,
              const void *p_data)
{
    OSStatus err =
        AudioObjectSetPropertyData(id, p_address, 0, NULL, i_data, p_data);

    if (err != noErr)
    {
        msg_Err(p_aout, "AudioObjectSetPropertyData failed, device id: %i, "
249 250
                 "prop: [%4.4s], OSStatus: %d", id, (const char *)&p_address[0],
                 (int)err);
251 252
        return VLC_EGENERIC;
    }
253 254
    return VLC_SUCCESS;
}
255

256 257 258 259 260 261 262 263 264 265 266 267 268
#define AO_SETPROP(id, i_data, p_data, a1, a2) \
    AoSetProperty(p_aout, (id), \
                  &(AudioObjectPropertyAddress) { (a1), (a2), 0}, \
                  (i_data), (p_data));

static int
AoUpdateListener(audio_output_t *p_aout, bool add, AudioObjectID id,
                 const AudioObjectPropertyAddress *p_address,
                 AudioObjectPropertyListenerProc listener, void *data)
{
    OSStatus err = add ?
        AudioObjectAddPropertyListener(id, p_address, listener, data) :
        AudioObjectRemovePropertyListener(id, p_address, listener, data);
269

Thomas Guillem's avatar
Thomas Guillem committed
270 271
    if (err != noErr)
    {
272
        msg_Err(p_aout, "AudioObject%sPropertyListener failed, device id %i, "
273 274
                "prop: [%4.4s], OSStatus: %d", add ? "Add" : "Remove", id,
                (const char *)&p_address[0], (int)err);
275 276
        return VLC_EGENERIC;
    }
277 278
    return VLC_SUCCESS;
}
279

280 281 282 283
#define AO_UPDATELISTENER(id, add, listener, data, a1, a2) \
    AoUpdateListener(p_aout, add, (id), \
                  &(AudioObjectPropertyAddress) { (a1), (a2), 0}, \
                  (listener), (data))
284

285 286 287
#pragma mark -
#pragma mark Stream / Hardware Listeners

288 289 290 291 292 293 294 295 296
static bool
IsAudioFormatDigital(AudioFormatID id)
{
    switch (id)
    {
        case 'IAC3':
        case 'iac3':
        case kAudioFormat60958AC3:
        case kAudioFormatAC3:
297
        case kAudioFormatEnhancedAC3:
298 299 300 301 302 303
            return true;
        default:
            return false;
    }
}

Thomas Guillem's avatar
Thomas Guillem committed
304 305 306
static OSStatus
StreamsChangedListener(AudioObjectID, UInt32,
                       const AudioObjectPropertyAddress [], void *);
307

Thomas Guillem's avatar
Thomas Guillem committed
308 309 310
static int
ManageAudioStreamsCallback(audio_output_t *p_aout, AudioDeviceID i_dev_id,
                           bool b_register)
311 312 313 314 315 316 317 318 319 320
{
    /* Retrieve all the output streams */
    size_t i_streams;
    AudioStreamID *p_streams;
    int ret = AO_GETPROP(i_dev_id, AudioStreamID, &i_streams, &p_streams,
                          kAudioDevicePropertyStreams,
                          kAudioObjectPropertyScopeOutput);
    if (ret != VLC_SUCCESS)
        return ret;

Thomas Guillem's avatar
Thomas Guillem committed
321 322
    for (size_t i = 0; i < i_streams; i++)
    {
323 324 325 326
        /* get notified when physical formats change */
        AO_UPDATELISTENER(p_streams[i], b_register, StreamsChangedListener,
                          p_aout, kAudioStreamPropertyAvailablePhysicalFormats,
                          kAudioObjectPropertyScopeGlobal);
327
    }
328

329 330 331
    free(p_streams);
    return VLC_SUCCESS;
}
332

333
/*
Thomas Guillem's avatar
Thomas Guillem committed
334 335
 * AudioStreamSupportsDigital: Checks if audio stream is compatible with raw
 * bitstreams
336
 */
337
static bool
Thomas Guillem's avatar
Thomas Guillem committed
338
AudioStreamSupportsDigital(audio_output_t *p_aout, AudioStreamID i_stream_id)
339
{
340
    bool b_return = false;
341

342
    /* Retrieve all the stream formats supported by each output stream */
343 344 345 346 347 348 349
    size_t i_formats;
    AudioStreamRangedDescription *p_format_list;
    int ret = AO_GETPROP(i_stream_id, AudioStreamRangedDescription, &i_formats,
                          &p_format_list,
                          kAudioStreamPropertyAvailablePhysicalFormats,
                          kAudioObjectPropertyScopeGlobal);
    if (ret != VLC_SUCCESS)
350
        return false;
351

Thomas Guillem's avatar
Thomas Guillem committed
352 353
    for (size_t i = 0; i < i_formats; i++)
    {
354
#ifndef NDEBUG
Thomas Guillem's avatar
Thomas Guillem committed
355 356
        msg_Dbg(p_aout, STREAM_FORMAT_MSG("supported format: ",
                p_format_list[i].mFormat));
357
#endif
358

359
        if (IsAudioFormatDigital(p_format_list[i].mFormat.mFormatID))
360 361
            b_return = true;
    }
362

363 364 365
    free(p_format_list);
    return b_return;
}
366

367 368 369
/*
 * AudioDeviceSupportsDigital: Checks if device supports raw bitstreams
 */
370
static bool
Thomas Guillem's avatar
Thomas Guillem committed
371
AudioDeviceSupportsDigital(audio_output_t *p_aout, AudioDeviceID i_dev_id)
372
{
373 374 375 376 377 378
    size_t i_streams;
    AudioStreamID * p_streams;
    int ret = AO_GETPROP(i_dev_id, AudioStreamID, &i_streams, &p_streams,
                          kAudioDevicePropertyStreams,
                          kAudioObjectPropertyScopeOutput);
    if (ret != VLC_SUCCESS)
379
        return false;
380 381 382

    for (size_t i = 0; i < i_streams; i++)
    {
383
        if (AudioStreamSupportsDigital(p_aout, p_streams[i]))
384 385 386 387
        {
            free(p_streams);
            return true;
        }
388 389 390
    }

    free(p_streams);
391
    return false;
392 393
}

Thomas Guillem's avatar
Thomas Guillem committed
394 395
static void
ReportDevice(audio_output_t *p_aout, UInt32 i_id, char *name)
396
{
397 398
    char deviceid[10];
    sprintf(deviceid, "%i", i_id);
399

400 401
    aout_HotplugReport(p_aout, deviceid, name);
}
402

403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
/*
 * AudioDeviceIsAHeadphone: Checks if device is a headphone
 */

static bool
AudioDeviceIsAHeadphone(audio_output_t *p_aout, AudioDeviceID i_dev_id)
{
    UInt32 defaultSize = sizeof(AudioDeviceID);

    const AudioObjectPropertyAddress defaultAddr = {
        kAudioHardwarePropertyDefaultOutputDevice,
        kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMaster
    };

    AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultAddr, 0, NULL, &defaultSize, &i_dev_id);

    AudioObjectPropertyAddress property;
    property.mSelector = kAudioDevicePropertyDataSource;
    property.mScope = kAudioDevicePropertyScopeOutput;
    property.mElement = kAudioObjectPropertyElementMaster;

    UInt32 data;
    UInt32 size = sizeof(UInt32);
    AudioObjectGetPropertyData(i_dev_id, &property, 0, NULL, &size, &data);

    /*
     'hdpn' == headphone
     'ispk' == internal speaker
     '61pd' == HDMI
     '    ' == Bluetooth accessory or AirPlay
    */

    return data == 'hdpn';
}

439 440 441
/*
 * AudioDeviceHasOutput: Checks if the device is actually an output device
 */
Thomas Guillem's avatar
Thomas Guillem committed
442 443
static int
AudioDeviceHasOutput(audio_output_t *p_aout, AudioDeviceID i_dev_id)
444
{
445 446 447 448 449
    size_t i_streams;
    int ret = AO_GETPROP(i_dev_id, AudioStreamID, &i_streams, NULL,
                          kAudioDevicePropertyStreams,
                          kAudioObjectPropertyScopeOutput);
    if (ret != VLC_SUCCESS || i_streams == 0)
450
        return FALSE;
451

452
    return TRUE;
453 454
}

Thomas Guillem's avatar
Thomas Guillem committed
455 456
static void
RebuildDeviceList(audio_output_t * p_aout)
457
{
458
    struct aout_sys_t   *p_sys = p_aout->sys;
459

460
    msg_Dbg(p_aout, "Rebuild device list");
461

462
    ReportDevice(p_aout, 0, _("System Sound Output Device"));
Thomas Guillem's avatar
Thomas Guillem committed
463

464
    /* Get number of devices */
465 466 467 468 469 470 471 472
    size_t i_devices;
    AudioDeviceID *p_devices;
    int ret = AO_GETPROP(kAudioObjectSystemObject, AudioDeviceID, &i_devices,
                         &p_devices, kAudioHardwarePropertyDevices,
                         kAudioObjectPropertyScopeGlobal);

    if (ret != VLC_SUCCESS || i_devices == 0)
    {
473 474 475
        msg_Err(p_aout, "No audio output devices found.");
        return;
    }
476
    msg_Dbg(p_aout, "found %zu audio device(s)", i_devices);
477

478 479 480
    /* setup local array */
    CFMutableArrayRef currentListOfDevices =
        CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
481

Thomas Guillem's avatar
Thomas Guillem committed
482 483
    for (size_t i = 0; i < i_devices; i++)
    {
484 485 486
        CFStringRef device_name_ref;
        char *psz_name;
        CFIndex length;
487
        UInt32 i_id = p_devices[i];
488

489 490 491 492 493 494
        int ret = AO_GET1PROP(i_id, CFStringRef, &device_name_ref,
                              kAudioObjectPropertyName,
                              kAudioObjectPropertyScopeGlobal);
        if (ret != VLC_SUCCESS)
        {
            msg_Dbg(p_aout, "failed to get name for device %i", i_id);
495
            continue;
496
        }
497

498 499 500
        length = CFStringGetLength(device_name_ref);
        length++;
        psz_name = malloc(length);
Thomas Guillem's avatar
Thomas Guillem committed
501 502
        if (!psz_name)
        {
503 504 505
            CFRelease(device_name_ref);
            return;
        }
Thomas Guillem's avatar
Thomas Guillem committed
506 507
        CFStringGetCString(device_name_ref, psz_name, length,
                           kCFStringEncodingUTF8);
508
        CFRelease(device_name_ref);
509

510
        msg_Dbg(p_aout, "DevID: %i DevName: %s", i_id, psz_name);
511

Thomas Guillem's avatar
Thomas Guillem committed
512 513
        if (!AudioDeviceHasOutput(p_aout, i_id))
        {
514 515 516 517
            msg_Dbg(p_aout, "this '%s' is INPUT only. skipping...", psz_name);
            free(psz_name);
            continue;
        }
518

519
        ReportDevice(p_aout, i_id, psz_name);
Thomas Guillem's avatar
Thomas Guillem committed
520 521
        CFNumberRef deviceNumber = CFNumberCreate(kCFAllocatorDefault,
                                                  kCFNumberSInt32Type, &i_id);
522 523
        CFArrayAppendValue(currentListOfDevices, deviceNumber);
        CFRelease(deviceNumber);
524

Thomas Guillem's avatar
Thomas Guillem committed
525 526
        if (AudioDeviceSupportsDigital(p_aout, i_id))
        {
527 528 529 530 531
            msg_Dbg(p_aout, "'%s' supports digital output", psz_name);
            char *psz_encoded_name = nil;
            asprintf(&psz_encoded_name, _("%s (Encoded Output)"), psz_name);
            i_id = i_id | AOUT_VAR_SPDIF_FLAG;
            ReportDevice(p_aout, i_id, psz_encoded_name);
Thomas Guillem's avatar
Thomas Guillem committed
532 533
            deviceNumber = CFNumberCreate(kCFAllocatorDefault,
                                          kCFNumberSInt32Type, &i_id);
534 535 536 537
            CFArrayAppendValue(currentListOfDevices, deviceNumber);
            CFRelease(deviceNumber);
            free(psz_encoded_name);
        }
538

539
        // TODO: only register once for each device
540
        ManageAudioStreamsCallback(p_aout, p_devices[i], true);
541

542
        free(psz_name);
543 544
    }

545 546 547 548
    vlc_mutex_lock(&p_sys->device_list_lock);
    CFIndex count = 0;
    if (p_sys->device_list)
        count = CFArrayGetCount(p_sys->device_list);
Thomas Guillem's avatar
Thomas Guillem committed
549 550
    CFRange newListSearchRange =
        CFRangeMake(0, CFArrayGetCount(currentListOfDevices));
551

Thomas Guillem's avatar
Thomas Guillem committed
552 553
    if (count > 0)
    {
554 555 556
        msg_Dbg(p_aout, "Looking for removed devices");
        CFNumberRef cfn_device_id;
        int i_device_id = 0;
Thomas Guillem's avatar
Thomas Guillem committed
557 558 559 560 561 562
        for (CFIndex x = 0; x < count; x++)
        {
            if (!CFArrayContainsValue(currentListOfDevices, newListSearchRange,
                                      CFArrayGetValueAtIndex(p_sys->device_list,
                                      x)))
            {
563
                cfn_device_id = CFArrayGetValueAtIndex(p_sys->device_list, x);
Thomas Guillem's avatar
Thomas Guillem committed
564 565 566 567 568 569
                if (cfn_device_id)
                {
                    CFNumberGetValue(cfn_device_id, kCFNumberSInt32Type,
                                     &i_device_id);
                    msg_Dbg(p_aout, "Device ID %i is not found in new array, "
                            "deleting.", i_device_id);
570 571 572 573 574

                    ReportDevice(p_aout, i_device_id, NULL);
                }
            }
        }
575
    }
576 577
    if (p_sys->device_list)
        CFRelease(p_sys->device_list);
Thomas Guillem's avatar
Thomas Guillem committed
578 579
    p_sys->device_list = CFArrayCreateCopy(kCFAllocatorDefault,
                                           currentListOfDevices);
580 581
    CFRelease(currentListOfDevices);
    vlc_mutex_unlock(&p_sys->device_list_lock);
582

583
    free(p_devices);
584
}
585

586 587 588
/*
 * Callback when current device is not alive anymore
 */
Thomas Guillem's avatar
Thomas Guillem committed
589 590 591 592
static OSStatus
DeviceAliveListener(AudioObjectID inObjectID,  UInt32 inNumberAddresses,
                    const AudioObjectPropertyAddress inAddresses[],
                    void *inClientData)
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
{
    VLC_UNUSED(inObjectID);
    VLC_UNUSED(inNumberAddresses);
    VLC_UNUSED(inAddresses);

    audio_output_t *p_aout = (audio_output_t *)inClientData;
    if (!p_aout)
        return -1;

    msg_Warn(p_aout, "audio device died, resetting aout");
    aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);

    return noErr;
}

/*
 * Callback when default audio device changed
 */
Thomas Guillem's avatar
Thomas Guillem committed
611 612 613 614
static OSStatus
DefaultDeviceChangedListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
                             const AudioObjectPropertyAddress inAddresses[],
                             void *inClientData)
615 616 617 618 619 620 621 622 623 624 625 626 627 628
{
    VLC_UNUSED(inObjectID);
    VLC_UNUSED(inNumberAddresses);
    VLC_UNUSED(inAddresses);

    audio_output_t *p_aout = (audio_output_t *)inClientData;
    if (!p_aout)
        return -1;

    aout_sys_t *p_sys = p_aout->sys;

    if (!p_aout->sys->b_selected_dev_is_default)
        return noErr;

629 630 631 632 633 634
    AudioObjectID defaultDeviceID;
    int ret = AO_GET1PROP(kAudioObjectSystemObject, AudioObjectID,
                          &defaultDeviceID,
                          kAudioHardwarePropertyDefaultOutputDevice,
                          kAudioObjectPropertyScopeOutput);
    if (ret != VLC_SUCCESS)
635
        return -1;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
636

637 638
    msg_Dbg(p_aout, "default device changed to %i", defaultDeviceID);

Thomas Guillem's avatar
Thomas Guillem committed
639 640 641 642
    /* Default device is changed by the os to allow other apps to play sound
     * while in digital mode. But this should not affect ourself. */
    if (p_aout->sys->b_digital)
    {
643 644
        msg_Dbg(p_aout, "ignore, as digital mode is active");
        return noErr;
645 646
    }

647 648
    vlc_mutex_lock(&p_sys->selected_device_lock);
    /* Also ignore events which announce the same device id */
Thomas Guillem's avatar
Thomas Guillem committed
649 650
    if (defaultDeviceID != p_aout->sys->i_selected_dev)
    {
651 652 653 654 655 656
        msg_Dbg(p_aout, "default device actually changed, resetting aout");
        aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
    }
    vlc_mutex_unlock(&p_sys->selected_device_lock);

    return noErr;
657 658
}

659
/*
660
 * Callback when physical formats for device change
661
 */
Thomas Guillem's avatar
Thomas Guillem committed
662 663 664 665
static OSStatus
StreamsChangedListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
                       const AudioObjectPropertyAddress inAddresses[],
                       void *inClientData)
666
{
667 668
    VLC_UNUSED(inNumberAddresses);
    VLC_UNUSED(inAddresses);
669

670 671 672
    audio_output_t *p_aout = (audio_output_t *)inClientData;
    if (!p_aout)
        return -1;
673

674 675 676 677 678 679 680 681
    aout_sys_t *p_sys = p_aout->sys;
    if(unlikely(p_sys->b_ignore_streams_changed_callback == true))
        return 0;

    msg_Dbg(p_aout, "available physical formats for audio device changed");
    RebuildDeviceList(p_aout);

    vlc_mutex_lock(&p_sys->selected_device_lock);
Thomas Guillem's avatar
Thomas Guillem committed
682 683 684 685
    /* In this case audio has not yet started. Below code will not work and is
     * not needed here. */
    if (p_sys->i_selected_dev == 0)
    {
686 687
        vlc_mutex_unlock(&p_sys->selected_device_lock);
        return 0;
688 689
    }

690 691 692
    /*
     * check if changed stream id belongs to current device
     */
693 694 695 696 697
    size_t i_streams;
    AudioStreamID *p_streams;
    int ret = AO_GETPROP(p_sys->i_selected_dev, AudioStreamID, &i_streams,
                         &p_streams, kAudioDevicePropertyStreams,
                         kAudioObjectPropertyScopeOutput);
Thomas Guillem's avatar
Thomas Guillem committed
698 699
    if (ret != VLC_SUCCESS)
    {
700
        vlc_mutex_unlock(&p_sys->selected_device_lock);
701
        return ret;
702
    }
703
    vlc_mutex_unlock(&p_sys->selected_device_lock);
704

Thomas Guillem's avatar
Thomas Guillem committed
705 706 707 708
    for (size_t i = 0; i < i_streams; i++)
    {
        if (p_streams[i] == inObjectID)
        {
709 710 711 712 713 714
            msg_Dbg(p_aout, "Restart aout as this affects current device");
            aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
            break;
        }
    }
    free(p_streams);
715

716 717
    return noErr;
}
718

719 720 721
/*
 * Callback when device list changed
 */
Thomas Guillem's avatar
Thomas Guillem committed
722 723 724 725
static OSStatus
DevicesListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
                const AudioObjectPropertyAddress inAddresses[],
                void *inClientData)
726 727 728 729
{
    VLC_UNUSED(inObjectID);
    VLC_UNUSED(inNumberAddresses);
    VLC_UNUSED(inAddresses);
730

731 732 733 734
    audio_output_t *p_aout = (audio_output_t *)inClientData;
    if (!p_aout)
        return -1;
    aout_sys_t *p_sys = p_aout->sys;
735

736 737
    msg_Dbg(p_aout, "audio device configuration changed, resetting cache");
    RebuildDeviceList(p_aout);
738

739 740
    vlc_mutex_lock(&p_sys->selected_device_lock);
    vlc_mutex_lock(&p_sys->device_list_lock);
Thomas Guillem's avatar
Thomas Guillem committed
741 742 743 744 745
    CFNumberRef selectedDevice =
        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
                       &p_sys->i_selected_dev);
    CFRange range = CFRangeMake(0, CFArrayGetCount(p_sys->device_list));
    if (!CFArrayContainsValue(p_sys->device_list, range, selectedDevice))
746 747 748 749
        aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
    CFRelease(selectedDevice);
    vlc_mutex_unlock(&p_sys->device_list_lock);
    vlc_mutex_unlock(&p_sys->selected_device_lock);
750

751 752
    return noErr;
}
753

754 755 756
/*
 * StreamListener: check whether the device's physical format change is complete
 */
Thomas Guillem's avatar
Thomas Guillem committed
757 758 759 760
static OSStatus
StreamListener(AudioObjectID inObjectID, UInt32 inNumberAddresses,
               const AudioObjectPropertyAddress inAddresses[],
               void *inClientData)
761 762 763
{
    OSStatus err = noErr;
    struct { vlc_mutex_t lock; vlc_cond_t cond; } * w = inClientData;
764

765
    VLC_UNUSED(inObjectID);
766

Thomas Guillem's avatar
Thomas Guillem committed
767 768 769 770
    for (unsigned int i = 0; i < inNumberAddresses; i++)
    {
        if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat)
        {
771 772 773 774 775 776
            int canc = vlc_savecancel();
            vlc_mutex_lock(&w->lock);
            vlc_cond_signal(&w->cond);
            vlc_mutex_unlock(&w->lock);
            vlc_restorecancel(canc);
            break;
777
        }
778
    }
779 780
    return err;
}
781

782
/*
Thomas Guillem's avatar
Thomas Guillem committed
783 784
 * AudioStreamChangeFormat: switch stream format based on the provided
 * description
785
 */
Thomas Guillem's avatar
Thomas Guillem committed
786 787 788
static int
AudioStreamChangeFormat(audio_output_t *p_aout, AudioStreamID i_stream_id,
                        AudioStreamBasicDescription change_format)
789 790
{
    int retValue = false;
791

792
    struct { vlc_mutex_t lock; vlc_cond_t cond; } w;
793

794
    msg_Dbg(p_aout, STREAM_FORMAT_MSG("setting stream format: ", change_format));
795

796 797 798 799
    /* Condition because SetProperty is asynchronious */
    vlc_cond_init(&w.cond);
    vlc_mutex_init(&w.lock);
    vlc_mutex_lock(&w.lock);
800

801
    /* Install the callback */
802 803 804 805
    int ret = AO_UPDATELISTENER(i_stream_id, true, StreamListener, &w,
                                kAudioStreamPropertyPhysicalFormat,
                                kAudioObjectPropertyScopeGlobal);

Thomas Guillem's avatar
Thomas Guillem committed
806 807
    if (ret != VLC_SUCCESS)
    {
808 809 810
        retValue = false;
        goto out;
    }
811

812
    /* change the format */
813 814 815
    ret = AO_SETPROP(i_stream_id, sizeof(AudioStreamBasicDescription),
                     &change_format, kAudioStreamPropertyPhysicalFormat,
                     kAudioObjectPropertyScopeGlobal);
Thomas Guillem's avatar
Thomas Guillem committed
816 817
    if (ret != VLC_SUCCESS)
    {
818 819 820
        retValue = false;
        goto out;
    }
821

Thomas Guillem's avatar
Thomas Guillem committed
822 823 824
    /* The AudioStreamSetProperty is not only asynchronious (requiring the
     * locks) it is also not atomic in its behaviour.  Therefore we check 9
     * times before we really give up.
825
     */
Thomas Guillem's avatar
Thomas Guillem committed
826 827 828 829 830 831
    for (int i = 0; i < 9; i++)
    {
        /* Callback is not always invoked. So first check if format is already
         * set. */
        if (i > 0)
        {
832 833 834 835
            mtime_t timeout = mdate() + 500000;
            if (vlc_cond_timedwait(&w.cond, &w.lock, timeout))
                msg_Dbg(p_aout, "reached timeout");
        }
836

837 838 839 840 841 842 843 844
        AudioStreamBasicDescription actual_format;
        int ret = AO_GET1PROP(i_stream_id, AudioStreamBasicDescription,
                              &actual_format,
                              kAudioStreamPropertyPhysicalFormat,
                              kAudioObjectPropertyScopeGlobal);

        if (ret != VLC_SUCCESS)
            continue;
845

Thomas Guillem's avatar
Thomas Guillem committed
846 847
        msg_Dbg(p_aout, STREAM_FORMAT_MSG("actual format in use: ",
                actual_format));
848 849
        if (actual_format.mSampleRate == change_format.mSampleRate &&
            actual_format.mFormatID == change_format.mFormatID &&
Thomas Guillem's avatar
Thomas Guillem committed
850 851
            actual_format.mFramesPerPacket == change_format.mFramesPerPacket)
        {
852 853
            /* The right format is now active */
            retValue = true;
854
            break;
855
        }
856

857
        /* We need to check again */
858
    }
859

860 861
out:
    vlc_mutex_unlock(&w.lock);
862

863
    /* Removing the property listener */
864 865 866 867
    ret = AO_UPDATELISTENER(i_stream_id, false, StreamListener, &w,
                            kAudioStreamPropertyPhysicalFormat,
                            kAudioObjectPropertyScopeGlobal);
    if (ret != VLC_SUCCESS)
868
        retValue = false;
869

870 871
    vlc_mutex_destroy(&w.lock);
    vlc_cond_destroy(&w.cond);
872

873 874
    return retValue;
}
875

876 877
#pragma mark -
#pragma mark core interaction
878

Thomas Guillem's avatar
Thomas Guillem committed
879 880
static int
SwitchAudioDevice(audio_output_t *p_aout, const char *name)
881 882
{
    struct aout_sys_t *p_sys = p_aout->sys;
883

884 885 886 887
    if (name)
        p_sys->i_new_selected_dev = atoi(name);
    else
        p_sys->i_new_selected_dev = 0;
888

889
    p_sys->i_new_selected_dev = p_sys->i_new_selected_dev & ~AOUT_VAR_SPDIF_FLAG;
890

891 892
    aout_DeviceReport(p_aout, name);
    aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT);
893

894
    return 0;
895 896
}

Thomas Guillem's avatar
Thomas Guillem committed
897 898
static int
VolumeSet(audio_output_t * p_aout, float volume)
899
{
900 901
    struct aout_sys_t *p_sys = p_aout->sys;
    OSStatus ostatus = 0;
902

Thomas Guillem's avatar
Thomas Guillem committed
903
    if (p_sys->b_digital)
904
        return VLC_EGENERIC;
905

906 907
    p_sys->f_volume = volume;
    aout_VolumeReport(p_aout, volume);
908

909
    /* Set volume for output unit */
Thomas Guillem's avatar
Thomas Guillem committed
910 911
    if (!p_sys->b_mute)
    {
912 913 914 915 916 917
        ostatus = AudioUnitSetParameter(p_sys->au_unit,
                                        kHALOutputParam_Volume,
                                        kAudioUnitScope_Global,
                                        0,
                                        volume * volume * volume,
                                        0);
918 919
    }

920
    if (var_InheritBool(p_aout, "volume-save"))
Thomas Guillem's avatar
Thomas Guillem committed
921 922
        config_PutInt(p_aout, "auhal-volume",
                      lroundf(volume * AOUT_VOLUME_DEFAULT));
923

924 925
    return ostatus;
}
926

Thomas Guillem's avatar
Thomas Guillem committed
927 928
static int
MuteSet(audio_output_t * p_aout, bool mute)
929
{
Thomas Guillem's avatar
Thomas Guillem committed
930
    struct aout_sys_t *p_sys = p_aout->sys;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
931

932 933
    if(p_sys->b_digital)
        return VLC_EGENERIC;
934

935 936
    p_sys->b_mute = mute;
    aout_MuteReport(p_aout, mute);
937

938 939 940
    float volume = .0;
    if (!mute)
        volume = p_sys->f_volume;
941

Thomas Guillem's avatar
Thomas Guillem committed
942 943 944 945
    OSStatus err =
        AudioUnitSetParameter(p_sys->au_unit, kHALOutputParam_Volume,
                              kAudioUnitScope_Global, 0,
                              volume * volume * volume, 0);
946

Thomas Guillem's avatar
Thomas Guillem committed
947
    return err == noErr ? VLC_SUCCESS : VLC_EGENERIC;
948
}
949

950 951
#pragma mark -
#pragma mark actual playback
952

953 954 955
/*
 * RenderCallbackSPDIF: callback for SPDIF audio output
 */
Thomas Guillem's avatar
Thomas Guillem committed
956 957
static OSStatus
RenderCallbackSPDIF(AudioDeviceID inDevice, const AudioTimeStamp * inNow,
958
                    const AudioBufferList * inInputData,
Thomas Guillem's avatar
Thomas Guillem committed
959 960
                    const AudioTimeStamp * inInputTime,
                    AudioBufferList * outOutputData,
961
                    const AudioTimeStamp * inOutputTime, void *p_data)
962
{
963 964 965 966 967
    VLC_UNUSED(inNow);
    VLC_UNUSED(inDevice);
    VLC_UNUSED(inInputData);
    VLC_UNUSED(inInputTime);
    VLC_UNUSED(inOutputTime);
968

969
    audio_output_t * p_aout = p_data;
970
    aout_sys_t *p_sys = p_aout->sys;
971 972 973
    uint8_t *p_output = outOutputData->mBuffers[p_sys->i_stream_index].mData;
    size_t i_size = outOutputData->mBuffers[p_sys->i_stream_index].mDataByteSize;

974
    ca_Render(p_aout, p_output, i_size);
975

976 977
    return noErr;
}
978

979 980
#pragma mark -
#pragma mark initialization
981

982 983 984
/*
 * StartAnalog: open and setup a HAL AudioUnit to do PCM audio output
 */
Thomas Guillem's avatar
Thomas Guillem committed
985
static int
986 987
StartAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt,
            mtime_t i_latency_us)
988 989 990
{
    struct aout_sys_t           *p_sys = p_aout->sys;
    OSStatus                    err = noErr;
991 992
    UInt32                      i_param_size;
    AudioChannelLayout          *layout = NULL;
993

994 995 996
    if (aout_FormatNbChannels(fmt) == 0)
        return VLC_EGENERIC;

997 998
    p_sys->au_unit = au_NewOutputInstance(p_aout, kAudioUnitSubType_HALOutput);
    if (p_sys->au_unit == NULL)
999
        return VLC_EGENERIC;
1000

1001 1002
    p_aout->current_sink_info.headphones = AudioDeviceIsAHeadphone(p_aout, p_sys->i_selected_dev);

1003 1004
    /* Set the device we will use for this output unit */
    err = AudioUnitSetProperty(p_sys->au_unit,
Thomas Guillem's avatar
Thomas Guillem committed
1005 1006 1007 1008 1009 1010
                               kAudioOutputUnitProperty_CurrentDevice,
                               kAudioUnitScope_Global, 0,
                               &p_sys->i_selected_dev, sizeof(AudioObjectID));

    if (err != noErr)
    {
1011
        ca_LogErr("cannot select audio output device, PCM output failed");
1012
        goto error;
1013 1014
    }

Thomas Guillem's avatar
Thomas Guillem committed
1015 1016
    /* Get the channel layout of the device side of the unit (vlc -> unit ->
     * device) */
1017 1018
    err = AudioUnitGetPropertyInfo(p_sys->au_unit,
                                   kAudioDevicePropertyPreferredChannelLayout,
Thomas Guillem's avatar
Thomas Guillem committed
1019
                                   kAudioUnitScope_Output, 0, &i_param_size,
1020
                                   NULL);
Thomas Guillem's avatar
Thomas Guillem committed
1021 1022
    if (err == noErr)
    {
1023
        layout = (AudioChannelLayout *)malloc(i_param_size);
1024 1025
        if (layout == NULL)
            goto error;
1026

1027 1028 1029 1030 1031 1032
        OSStatus err =
            AudioUnitGetProperty(p_sys->au_unit,
                                 kAudioDevicePropertyPreferredChannelLayout,
                                 kAudioUnitScope_Output, 0, layout,
                                 &i_param_size);
        if (err != noErr)
1033
            goto error;
Thomas Guillem's avatar
Thomas Guillem committed
1034 1035
    }
    else
1036 1037
        ca_LogWarn("device driver does not support "
                   "kAudioDevicePropertyPreferredChannelLayout - using stereo");
1038

1039
    /* Do the last VLC aout setups */
1040 1041 1042
    bool warn_configuration;
    int ret = au_Initialize(p_aout, p_sys->au_unit, fmt, layout, i_latency_us,
                            &warn_configuration);
1043
    if (ret != VLC_SUCCESS)
1044
        goto error;
1045

1046 1047 1048
    err = AudioOutputUnitStart(p_sys->au_unit);
    if (err != noErr)
    {
1049
        ca_LogErr("AudioUnitStart failed");
1050
        au_Uninitialize(p_aout, p_sys->au_unit);
1051 1052
        goto error;
    }
1053

1054 1055 1056
    /* Set volume for output unit */
    VolumeSet(p_aout, p_sys->f_volume);
    MuteSet(p_aout, p_sys->b_mute);
1057

1058
    free(layout);
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071

    if (warn_configuration)
    {
        msg_Err(p_aout, "You should configure your speaker layout with "
                "Audio Midi Setup in /Applications/Utilities. VLC will "
                "output Stereo only.");
        vlc_dialog_display_error(p_aout,
            _("Audio device is not configured"), "%s",
            _("You should configure your speaker layout with "
            "\"Audio Midi Setup\" in /Applications/"
            "Utilities. VLC will output Stereo only."));
    }

1072
    return VLC_SUCCESS;
1073 1074
error:
    AudioComponentInstanceDispose(p_sys->au_unit);
1075
    free(layout);
1076
    return VLC_EGENERIC;
1077 1078
}

1079 1080 1081
/*
 * StartSPDIF: Setup an encoded digital stream (SPDIF) output
 */
Thomas Guillem's avatar
Thomas Guillem committed
1082
static int
1083 1084
StartSPDIF(audio_output_t * p_aout, audio_sample_format_t *fmt,
           mtime_t i_latency_us)
1085
{
1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
    struct aout_sys_t *p_sys = p_aout->sys;
    int ret;

    /* Check if device supports digital */
    if (!AudioDeviceSupportsDigital(p_aout, p_sys->i_selected_dev))
    {
        msg_Dbg(p_aout, "Audio device supports PCM mode only");
        return VLC_EGENERIC;
    }

    ret = AO_GET1PROP(p_sys->i_selected_dev, pid_t, &p_sys->i_hog_pid,
                      kAudioDevicePropertyHogMode,
                      kAudioObjectPropertyScopeOutput);
    if (ret != VLC_SUCCESS)
    {
        /* This is not a fatal error. Some drivers simply don't support this
         * property */
        p_sys->i_hog_pid = -1;
    }

    if (p_sys->i_hog_pid != -1 && p_sys->i_hog_pid != getpid())
    {
        msg_Err(p_aout, "Selected audio device is exclusively in use by another"
                " program.");
        vlc_dialog_display_error(p_aout, _("Audio output failed"), "%s",
            _("The selected audio output device is exclusively in "
            "use by another program."));
        return VLC_EGENERIC;
    }

1116 1117
    AudioStreamBasicDescription desired_stream_format;
    memset(&desired_stream_format, 0, sizeof(desired_stream_format));
1118

1119 1120
    /* Start doing the SPDIF setup proces */
    p_sys->b_digital = true;
1121

1122
    /* Hog the device */
1123
    p_sys->i_hog_pid = getpid();
1124

1125
    /*
Thomas Guillem's avatar
Thomas Guillem committed
1126 1127 1128
     * HACK: On 10.6, auhal will trigger the streams changed callback when
     * calling below line, directly in the same thread. This call needs to be
     * ignored to avoid endless restarting.
1129 1130
     */
    p_sys->b_ignore_streams_changed_callback = true;
1131 1132 1133
    ret = AO_SETPROP(p_sys->i_selected_dev, sizeof(p_sys->i_hog_pid),
                     &p_sys->i_hog_pid, kAudioDevicePropertyHogMode,
                     kAudioObjectPropertyScopeOutput);
1134
    p_sys->b_ignore_streams_changed_callback = false;
1135

1136
    if (ret != VLC_SUCCESS)
1137
        return ret;
David Fuhrmann's avatar
David Fuhrmann committed
1138

1139
    if (AO_HASPROP(p_sys->i_selected_dev, kAudioDevicePropertySupportsMixing,
Thomas Guillem's avatar
Thomas Guillem committed
1140 1141
                   kAudioObjectPropertyScopeGlobal))
    {