X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fauhal.c;h=182d9f7f74f8ca065f6bd5b759ade8b5df92090c;hb=de2d863309bb0a1cc797d6d5d43e38562c7c14fe;hp=620ded4827b6a4f601d2f1cc37e2db21f5cc18f5;hpb=5e59f5f48dac111013b48a0afc22fdc8f4e627f4;p=vlc diff --git a/modules/audio_output/auhal.c b/modules/audio_output/auhal.c index 620ded4827..182d9f7f74 100644 --- a/modules/audio_output/auhal.c +++ b/modules/audio_output/auhal.c @@ -6,6 +6,7 @@ * * Authors: Derk-Jan Hartman * Felix Paul Kühne + * David Fuhrmann * * 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 @@ -22,9 +23,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ -/***************************************************************************** - * Preamble - *****************************************************************************/ +#pragma mark includes #ifdef HAVE_CONFIG_H # import "config.h" @@ -42,6 +41,9 @@ #import "TPCircularBuffer.h" +#pragma mark - +#pragma mark private declarations + #ifndef verify_noerr # define verify_noerr(a) assert((a) == noErr) #endif @@ -55,11 +57,17 @@ #define AOUT_VAR_SPDIF_FLAG 0xf00000 -#define kBufferLength 2048 * 8 * 8 * 4 +#define AUDIO_BUFFER_SIZE_IN_SECONDS (AOUT_MAX_ADVANCE_TIME / CLOCK_FREQ) + #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 /***************************************************************************** * aout_sys_t: private audio output method descriptor @@ -69,18 +77,17 @@ *****************************************************************************/ struct aout_sys_t { - aout_packet_t packet; - AudioDeviceID i_default_dev; /* DeviceID of defaultOutputDevice */ - AudioDeviceID i_selected_dev; /* DeviceID of the selected device */ + AudioObjectID i_selected_dev; /* DeviceID of the selected device */ + AudioObjectID i_new_selected_dev; /* DeviceID of device which will be selected on start */ + bool b_selected_dev_is_digital; + bool b_selected_dev_is_default; /* true if the user selected the default audio device (id 0) */ + AudioDeviceIOProcID i_procID; /* DeviceID of current device */ - UInt32 i_devices; /* Number of CoreAudio Devices */ bool b_digital; /* Are we running in digital mode? */ - mtime_t clock_diff; /* Difference between VLC clock and Device clock */ uint8_t chans_to_reorder; /* do we need channel reordering */ uint8_t chan_table[AOUT_CHAN_MAX]; - UInt32 i_numberOfChannels; TPCircularBuffer circular_buffer; /* circular buffer to swap the audio data */ /* AUHAL specific */ @@ -91,62 +98,65 @@ struct aout_sys_t pid_t i_hog_pid; /* The keep the pid of our hog status */ AudioStreamID i_stream_id; /* The StreamID that has a cac3 streamformat */ int i_stream_index; /* The index of i_stream_id in an AudioBufferList */ - AudioStreamBasicDescription stream_format; /* The format we changed the stream to */ AudioStreamBasicDescription sfmt_revert; /* The original format of the stream */ bool b_revert; /* Whether we need to revert the stream format */ bool b_changed_mixing; /* Whether we need to set the mixing mode back */ - bool b_got_first_sample; /* did the aout core provide something to render? */ int i_rate; /* media sample rate */ - mtime_t i_played_length; /* how much did we play already */ - mtime_t i_last_sample_time; /* last sample time played by the AudioUnit */ + int i_bytes_per_sample; + + CFArrayRef device_list; + + vlc_mutex_t var_lock; /* protects access to device_list and i_selected_dev */ + + float f_volume; + bool b_mute; + bool b_paused; vlc_mutex_t lock; + vlc_cond_t cond; + + bool b_ignore_streams_changed_callback; }; -/***************************************************************************** - * Local prototypes. - *****************************************************************************/ +#pragma mark - +#pragma mark local prototypes & module descriptor + static int Open (vlc_object_t *); -static int OpenAnalog (audio_output_t *, audio_sample_format_t *); -static int OpenSPDIF (audio_output_t *, audio_sample_format_t *); static void Close (vlc_object_t *); +static int Start (audio_output_t *, audio_sample_format_t *); +static int StartAnalog (audio_output_t *, audio_sample_format_t *); +static int StartSPDIF (audio_output_t *, audio_sample_format_t *); +static void Stop (audio_output_t *); -static void PlayAnalog (audio_output_t *, block_t *); -static void FlushAnalog (audio_output_t *, bool); -static void PauseAnalog (audio_output_t *, bool, mtime_t); -static int TimeGetAnalog (audio_output_t *, mtime_t *); - -static void Probe (audio_output_t *); - -static int AudioDeviceHasOutput (AudioDeviceID); -static int AudioDeviceSupportsDigital(audio_output_t *, AudioDeviceID); -static int AudioStreamSupportsDigital(audio_output_t *, AudioStreamID); -static int AudioStreamChangeFormat (audio_output_t *, AudioStreamID, AudioStreamBasicDescription); +static void RebuildDeviceList (audio_output_t *); +static int SwitchAudioDevice (audio_output_t *p_aout, const char *name); +static int VolumeSet (audio_output_t *, float); +static int MuteSet (audio_output_t *, bool); -static OSStatus RenderCallbackAnalog (vlc_object_t *, AudioUnitRenderActionFlags *, const AudioTimeStamp *, unsigned int, unsigned int, AudioBufferList *); +static void Play (audio_output_t *, block_t *); +static void Pause (audio_output_t *, bool, mtime_t); +static void Flush (audio_output_t *, bool); +static int TimeGet (audio_output_t *, mtime_t *); +static OSStatus RenderCallbackAnalog (vlc_object_t *, AudioUnitRenderActionFlags *, const AudioTimeStamp *, + UInt32 , UInt32, AudioBufferList *); static OSStatus RenderCallbackSPDIF (AudioDeviceID, const AudioTimeStamp *, const void *, const AudioTimeStamp *, - AudioBufferList *, const AudioTimeStamp *, void *); -static OSStatus HardwareListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *); -static OSStatus StreamListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *); -static int AudioDeviceCallback (vlc_object_t *, const char *, - vlc_value_t, vlc_value_t, void *); + AudioBufferList *, const AudioTimeStamp *, void *); -static int VolumeSet (audio_output_t *, float); -static int MuteSet (audio_output_t *, bool); +static OSStatus DevicesListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *); +static OSStatus DeviceAliveListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *); +static OSStatus DefaultDeviceChangedListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *); +static OSStatus StreamsChangedListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *); +static OSStatus StreamListener (AudioObjectID, UInt32, const AudioObjectPropertyAddress *, void *); -/***************************************************************************** - * Module descriptor - *****************************************************************************/ -#define ADEV_TEXT N_("Audio Device") -#define ADEV_LONGTEXT N_("Choose a number corresponding to the number of an " \ - "audio device, as listed in your 'Audio Device' menu. This device will " \ - "then be used by default for audio playback.") +static int ManageAudioStreamsCallback(audio_output_t *p_aout, AudioDeviceID i_dev_id, bool b_register); +static int AudioDeviceHasOutput (AudioDeviceID); +static int AudioDeviceSupportsDigital(audio_output_t *, AudioDeviceID); +static int AudioStreamSupportsDigital(audio_output_t *, AudioStreamID); +static int AudioStreamChangeFormat (audio_output_t *, AudioStreamID, AudioStreamBasicDescription); -#define VOLUME_TEXT N_("Audio volume") -#define VOLUME_LONGTEXT VOLUME_TEXT vlc_module_begin () set_shortname("auhal") @@ -155,85 +165,215 @@ vlc_module_begin () set_category(CAT_AUDIO) set_subcategory(SUBCAT_AUDIO_AOUT) set_callbacks(Open, Close) - add_integer("macosx-audio-device", 0, ADEV_TEXT, ADEV_LONGTEXT, false) 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 () -/***************************************************************************** - * Open: open macosx audio output - *****************************************************************************/ +#pragma mark - +#pragma mark initialization + +static int Open(vlc_object_t *obj) +{ + audio_output_t *p_aout = (audio_output_t *)obj; + aout_sys_t *p_sys = malloc(sizeof (*p_sys)); + if (unlikely(p_sys == NULL)) + return VLC_ENOMEM; + + OSStatus err = noErr; + + vlc_mutex_init(&p_sys->var_lock); + vlc_mutex_init(&p_sys->lock); + vlc_cond_init(&p_sys->cond); + p_sys->b_digital = false; + p_sys->b_ignore_streams_changed_callback = false; + p_sys->b_selected_dev_is_default = false; + p_sys->b_paused = false; + + p_aout->sys = p_sys; + p_aout->start = Start; + p_aout->stop = Stop; + p_aout->volume_set = VolumeSet; + p_aout->mute_set = MuteSet; + p_aout->device_select = SwitchAudioDevice; + p_sys->device_list = CFArrayCreate(kCFAllocatorDefault, NULL, 0, NULL); + + /* + * Force an own run loop for callbacks. + * + * According to rtaudio, this is absolutely necessary since 10.6 to get correct notifications. + * It might fix issues when using the module as a library where a proper loop is not setup already. + */ + CFRunLoopRef theRunLoop = NULL; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal,kAudioObjectPropertyElementMaster }; + err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); + if (err != noErr) { + msg_Err(p_aout, "failed to set the run loop property [%4.4s]", (char *)&err); + } + + /* Attach a listener so that we are notified of a change in the device setup */ + AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &audioDevicesAddress, DevicesListener, (void *)p_aout); + if (err != noErr) + msg_Err(p_aout, "failed to add listener for audio device configuration [%4.4s]", (char *)&err); + + /* Attach a listener to be notified about changes in default audio device */ + AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &defaultDeviceAddress, DefaultDeviceChangedListener, (void *)p_aout); + if (err != noErr) + msg_Err(p_aout, "failed to add listener for default audio device [%4.4s]", (char *)&err); + + RebuildDeviceList(p_aout); + + /* remember the volume */ + p_sys->f_volume = var_InheritInteger(p_aout, "auhal-volume") / (float)AOUT_VOLUME_DEFAULT; + aout_VolumeReport(p_aout, p_sys->f_volume); + p_sys->b_mute = var_InheritBool(p_aout, "mute"); + aout_MuteReport(p_aout, p_sys->b_mute); + + char *psz_audio_device = var_InheritString(p_aout, "auhal-audio-device"); + SwitchAudioDevice(p_aout, psz_audio_device); + free(psz_audio_device); + + return VLC_SUCCESS; +} + +static void Close(vlc_object_t *obj) +{ + audio_output_t *p_aout = (audio_output_t *)obj; + aout_sys_t *p_sys = p_aout->sys; + + OSStatus err = noErr; + + /* remove audio devices callback */ + AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &audioDevicesAddress, DevicesListener, (void *)p_aout); + if (err != noErr) + msg_Err(p_aout, "AudioHardwareRemovePropertyListener failed [%4.4s]", (char *)&err); + + /* remove listener to be notified about changes in default audio device */ + AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &defaultDeviceAddress, DefaultDeviceChangedListener, (void *)p_aout); + if (err != noErr) + msg_Err(p_aout, "failed to remove listener for default audio device [%4.4s]", (char *)&err); + + vlc_mutex_lock(&p_sys->var_lock); + /* remove streams callbacks */ + CFIndex count = CFArrayGetCount(p_sys->device_list); + if (count > 0) { + for (CFIndex x = 0; x < count; x++) { + AudioDeviceID deviceId = 0; + CFNumberRef cfn_device_id = CFArrayGetValueAtIndex(p_sys->device_list, x); + if (!cfn_device_id) + continue; + + CFNumberGetValue(cfn_device_id, kCFNumberSInt32Type, &deviceId); + if (!(deviceId & AOUT_VAR_SPDIF_FLAG)) { + ManageAudioStreamsCallback(p_aout, deviceId, false); + } + } + } + + CFRelease(p_sys->device_list); + vlc_mutex_unlock(&p_sys->var_lock); + + char *psz_device = aout_DeviceGet(p_aout); + config_PutPsz(p_aout, "auhal-audio-device", psz_device); + free(psz_device); + + vlc_mutex_destroy(&p_sys->var_lock); + vlc_mutex_destroy(&p_sys->lock); + vlc_cond_destroy(&p_sys->cond); + + free(p_sys); +} + static int Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt) { OSStatus err = noErr; UInt32 i_param_size = 0; struct aout_sys_t *p_sys = NULL; - vlc_value_t val; /* Use int here, to match kAudioDevicePropertyDeviceIsAlive * property size */ int b_alive = false; + bool b_start_digital = false; + p_sys = p_aout->sys; - p_sys->i_default_dev = 0; - p_sys->i_selected_dev = 0; - p_sys->i_devices = 0; p_sys->b_digital = false; p_sys->au_component = NULL; p_sys->au_unit = NULL; - p_sys->clock_diff = (mtime_t) 0; p_sys->i_hog_pid = -1; p_sys->i_stream_id = 0; p_sys->i_stream_index = -1; p_sys->b_revert = false; p_sys->b_changed_mixing = false; + p_sys->i_bytes_per_sample = 0; + p_sys->b_paused = false; + + vlc_mutex_lock(&p_sys->var_lock); + p_sys->i_selected_dev = p_sys->i_new_selected_dev; aout_FormatPrint(p_aout, "VLC is looking for:", fmt); - /* Persistent device variable */ - if (var_Type(p_aout->p_libvlc, "macosx-audio-device") == 0) - var_Create(p_aout->p_libvlc, "macosx-audio-device", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT); + msg_Dbg(p_aout, "attempting to use device %i", p_sys->i_selected_dev); - /* Build a list of devices */ - if (var_Type(p_aout, "audio-device") == 0) - Probe(p_aout); + AudioObjectPropertyAddress audioDeviceAliveAddress = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + if (p_sys->i_selected_dev > 0) { + /* Check if the desired device is alive and usable */ + i_param_size = sizeof(b_alive); + err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceAliveAddress, 0, NULL, &i_param_size, &b_alive); + if (err != noErr) { + /* Be tolerant, only give a warning here */ + msg_Warn(p_aout, "could not check whether device [0x%x] is alive [%4.4s]", + (unsigned int)p_sys->i_selected_dev, (char *)&err); + b_alive = false; + } - /* What device do we want? */ - if (var_Get(p_aout, "audio-device", &val) < 0) { - msg_Err(p_aout, "audio-device var does not exist. device probe failed."); - goto error; + if (!b_alive) + msg_Warn(p_aout, "selected audio device is not alive, switching to default device"); } - p_sys->i_selected_dev = val.i_int & ~AOUT_VAR_SPDIF_FLAG; /* remove SPDIF flag to get the true DeviceID */ - bool b_supports_digital = (val.i_int & AOUT_VAR_SPDIF_FLAG); - if (b_supports_digital) - msg_Dbg(p_aout, "audio device supports digital output"); + p_sys->b_selected_dev_is_default = false; + if (!b_alive || p_sys->i_selected_dev == 0) { + p_sys->b_selected_dev_is_default = true; - /* Check if the desired device is alive and usable */ - i_param_size = sizeof(b_alive); - AudioObjectPropertyAddress audioDeviceAliveAddress = { kAudioDevicePropertyDeviceIsAlive, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceAliveAddress, 0, NULL, &i_param_size, &b_alive); + AudioObjectID defaultDeviceID = 0; + UInt32 propertySize = sizeof(AudioObjectID); + AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; + propertySize = sizeof(AudioObjectID); + err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultDeviceAddress, 0, NULL, &propertySize, &defaultDeviceID); + if (err != noErr) { + msg_Err(p_aout, "could not get default audio device [%4.4s]", (char *)&err); + vlc_mutex_unlock(&p_sys->var_lock); + goto error; + } + else + msg_Dbg(p_aout, "using default audio device %i", defaultDeviceID); - if (err != noErr) { - /* Be tolerant, only give a warning here */ - msg_Warn(p_aout, "could not check whether device [0x%x] is alive: %4.4s", - (unsigned int)p_sys->i_selected_dev, (char *)&err); - b_alive = false; + p_sys->i_selected_dev = defaultDeviceID; + p_sys->b_selected_dev_is_digital = var_InheritBool(p_aout, "spdif"); } + vlc_mutex_unlock(&p_sys->var_lock); - if (!b_alive) { - msg_Warn(p_aout, "selected audio device is not alive, switching to default device"); - p_sys->i_selected_dev = p_sys->i_default_dev; - } + // recheck if device still supports digital + b_start_digital = p_sys->b_selected_dev_is_digital; + if(!AudioDeviceSupportsDigital(p_aout, p_sys->i_selected_dev)) + b_start_digital = false; + + if (b_start_digital) + msg_Dbg(p_aout, "Using audio device for digital output"); + else + msg_Dbg(p_aout, "Audio device supports PCM mode only"); /* add a callback to see if the device dies later on */ - err = AudioObjectAddPropertyListener(p_sys->i_selected_dev, &audioDeviceAliveAddress, HardwareListener, (void *)p_aout); + err = AudioObjectAddPropertyListener(p_sys->i_selected_dev, &audioDeviceAliveAddress, DeviceAliveListener, (void *)p_aout); if (err != noErr) { /* Be tolerant, only give a warning here */ - msg_Warn(p_aout, "could not set alive check callback on device [0x%x]: %4.4s", + msg_Warn(p_aout, "could not set alive check callback on device [0x%x] [%4.4s]", (unsigned int)p_sys->i_selected_dev, (char *)&err); } @@ -244,7 +384,7 @@ static int Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt) err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, &i_param_size, &p_sys->i_hog_pid); if (err != noErr) { /* This is not a fatal error. Some drivers simply don't support this property */ - msg_Warn(p_aout, "could not check whether device is hogged: %4.4s", + msg_Warn(p_aout, "could not check whether device is hogged [%4.4s]", (char *)&err); p_sys->i_hog_pid = -1; } @@ -257,33 +397,39 @@ static int Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt) goto error; } - /* If we change the device we want to use, we should renegotiate the audio chain */ - var_AddCallback(p_aout, "audio-device", AudioDeviceCallback, NULL); + bool b_success = false; /* Check for Digital mode or Analog output mode */ - if (AOUT_FMT_SPDIF (fmt) && b_supports_digital) { - if (OpenSPDIF (p_aout, fmt)) { + if (AOUT_FMT_SPDIF (fmt) && b_start_digital) { + if (StartSPDIF (p_aout, fmt)) { msg_Dbg(p_aout, "digital output successfully opened"); - return VLC_SUCCESS; + b_success = true; } } else { - if (OpenAnalog(p_aout, fmt)) { + if (StartAnalog(p_aout, fmt)) { msg_Dbg(p_aout, "analog output successfully opened"); - return VLC_SUCCESS; + b_success = true; } } + if (b_success) { + p_aout->play = Play; + p_aout->flush = Flush; + p_aout->time_get = TimeGet; + p_aout->pause = Pause; + return VLC_SUCCESS; + } + error: /* If we reach this, this aout has failed */ - msg_Err(p_aout, "opening the auhal output failed"); - var_Destroy(p_aout, "audio-device"); + msg_Err(p_aout, "opening auhal output failed"); return VLC_EGENERIC; } -/***************************************************************************** - * Open: open and setup a HAL AudioUnit to do analog (multichannel) audio output - *****************************************************************************/ -static int OpenAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) +/* + * StartAnalog: open and setup a HAL AudioUnit to do PCM audio output + */ +static int StartAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) { struct aout_sys_t *p_sys = p_aout->sys; OSStatus err = noErr; @@ -292,10 +438,13 @@ static int OpenAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) AudioComponentDescription desc; AudioStreamBasicDescription DeviceFormat; AudioChannelLayout *layout; - AudioChannelLayout new_layout; AURenderCallbackStruct input; p_aout->sys->chans_to_reorder = 0; + SInt32 currentMinorSystemVersion; + if(Gestalt(gestaltSystemVersionMinor, ¤tMinorSystemVersion) != noErr) + msg_Err(p_aout, "failed to check OSX version"); + /* Lets go find our Component */ desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_HALOutput; @@ -305,13 +454,13 @@ static int OpenAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) p_sys->au_component = AudioComponentFindNext(NULL, &desc); if (p_sys->au_component == NULL) { - msg_Warn(p_aout, "we cannot find our HAL component"); + msg_Err(p_aout, "cannot find any HAL component, PCM output failed"); return false; } err = AudioComponentInstanceNew(p_sys->au_component, &p_sys->au_unit); if (err != noErr) { - msg_Warn(p_aout, "we cannot open our HAL component"); + msg_Err(p_aout, "cannot open HAL component, PCM output failed [%4.4s]", (char *)&err); return false; } @@ -321,10 +470,10 @@ static int OpenAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) kAudioUnitScope_Global, 0, &p_sys->i_selected_dev, - sizeof(AudioDeviceID)); + sizeof(AudioObjectID)); if (err != noErr) { - msg_Warn(p_aout, "we cannot select the audio device"); + msg_Err(p_aout, "cannot select audio output device, PCM output failed [%4.4s]", (char *)&err); return false; } @@ -333,14 +482,15 @@ static int OpenAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) err = AudioUnitGetProperty(p_sys->au_unit, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, + kAudioUnitScope_Output, 0, &DeviceFormat, &i_param_size); - if (err != noErr) + if (err != noErr) { + msg_Err(p_aout, "failed to detect supported stream formats [%4.4s]", (char *)&err); return false; - else + } else msg_Dbg(p_aout, STREAM_FORMAT_MSG("current format is: ", DeviceFormat)); /* Get the channel layout of the device side of the unit (vlc -> unit -> device) */ @@ -377,7 +527,12 @@ static int OpenAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) layout)); } - msg_Dbg(p_aout, "layout of AUHAL has %d channels" , (int)layout->mNumberChannelDescriptions); + msg_Dbg(p_aout, "layout of AUHAL has %i channels" , layout->mNumberChannelDescriptions); + + if (layout->mNumberChannelDescriptions == 0) { + msg_Err(p_aout, "insufficient number of output channels"); + return false; + } /* Initialize the VLC core channel count */ fmt->i_physical_channels = 0; @@ -391,132 +546,98 @@ static int OpenAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) fmt->i_physical_channels = AOUT_CHANS_STEREO; } else { /* We want more than stereo and we can do that */ + msg_Dbg(p_aout, "Device is configured with %d channels", layout->mNumberChannelDescriptions); + + // maps auhal channels (1-9) to vlc ones + static const unsigned i_auhal_channel_mapping[] = { + [kAudioChannelLabel_Left] = AOUT_CHAN_LEFT, + [kAudioChannelLabel_Right] = AOUT_CHAN_RIGHT, + [kAudioChannelLabel_Center] = AOUT_CHAN_CENTER, + [kAudioChannelLabel_LFEScreen] = AOUT_CHAN_LFE, + [kAudioChannelLabel_LeftSurround] = AOUT_CHAN_REARLEFT, + [kAudioChannelLabel_RightSurround] = AOUT_CHAN_REARRIGHT, + [kAudioChannelLabel_RearSurroundLeft] = AOUT_CHAN_MIDDLELEFT, // needs to be swapped with rear + [kAudioChannelLabel_RearSurroundRight] = AOUT_CHAN_MIDDLERIGHT,// needs to be swapped with rear + [kAudioChannelLabel_CenterSurround] = AOUT_CHAN_REARCENTER + }; + + + uint32_t chans_out[AOUT_CHAN_MAX]; + memset(&chans_out, 0, sizeof(chans_out)); + + bool b_have_middle_channels = false; + unsigned index = 0; for (unsigned int i = 0; i < layout->mNumberChannelDescriptions; i++) { #ifndef NDEBUG - msg_Dbg(p_aout, "this is channel: %d", (int)layout->mChannelDescriptions[i].mChannelLabel); + msg_Dbg(p_aout, "this is channel: %d, at index %d", (int)layout->mChannelDescriptions[i].mChannelLabel, i); #endif + if(index >= AOUT_CHAN_MAX) + break; + + AudioChannelLabel chan = layout->mChannelDescriptions[i].mChannelLabel; + if(chan < sizeof(i_auhal_channel_mapping) / sizeof(i_auhal_channel_mapping[0]) + && i_auhal_channel_mapping[chan] > 0) { + fmt->i_physical_channels |= i_auhal_channel_mapping[chan]; + chans_out[index++] = i_auhal_channel_mapping[chan]; + + if (chan == kAudioChannelLabel_RearSurroundLeft || + chan == kAudioChannelLabel_RearSurroundRight) + b_have_middle_channels = true; + } else { + msg_Dbg(p_aout, "found nonrecognized channel %d at index %d", chan, i); + } + } - switch(layout->mChannelDescriptions[i].mChannelLabel) { - case kAudioChannelLabel_Left: - fmt->i_physical_channels |= AOUT_CHAN_LEFT; - continue; - case kAudioChannelLabel_Right: - fmt->i_physical_channels |= AOUT_CHAN_RIGHT; - continue; - case kAudioChannelLabel_Center: - fmt->i_physical_channels |= AOUT_CHAN_CENTER; - continue; - case kAudioChannelLabel_LFEScreen: - fmt->i_physical_channels |= AOUT_CHAN_LFE; - continue; - case kAudioChannelLabel_LeftSurround: - fmt->i_physical_channels |= AOUT_CHAN_REARLEFT; - continue; - case kAudioChannelLabel_RightSurround: - fmt->i_physical_channels |= AOUT_CHAN_REARRIGHT; - continue; - case kAudioChannelLabel_RearSurroundLeft: - fmt->i_physical_channels |= AOUT_CHAN_MIDDLELEFT; - continue; - case kAudioChannelLabel_RearSurroundRight: - fmt->i_physical_channels |= AOUT_CHAN_MIDDLERIGHT; - continue; - case kAudioChannelLabel_CenterSurround: - fmt->i_physical_channels |= AOUT_CHAN_REARCENTER; - continue; - default: - msg_Warn(p_aout, "unrecognized channel form provided by driver: %d", (int)layout->mChannelDescriptions[i].mChannelLabel); + /* + * For 7.1, AOUT_CHAN_MIDDLELEFT/RIGHT needs to be swapped with AOUT_CHAN_REARLEFT/RIGHT. + * Auhals kAudioChannelLabel_Left/RightSurround is used as surround for 5.1, but as middle speakers + * for rear 7.1. + */ + if (b_have_middle_channels) { + for (unsigned i = 0; i < index; i++) { + switch (chans_out[i]) { + case AOUT_CHAN_MIDDLELEFT: + chans_out[i] = AOUT_CHAN_REARLEFT; + break; + case AOUT_CHAN_MIDDLERIGHT: + chans_out[i] = AOUT_CHAN_REARRIGHT; + break; + case AOUT_CHAN_REARLEFT: + chans_out[i] = AOUT_CHAN_MIDDLELEFT; + break; + case AOUT_CHAN_REARRIGHT: + chans_out[i] = AOUT_CHAN_MIDDLERIGHT; + break; + } } } + if (fmt->i_physical_channels == 0) { fmt->i_physical_channels = AOUT_CHANS_STEREO; - msg_Err(p_aout, "You should configure your speaker layout with Audio Midi Setup Utility in /Applications/Utilities. Now using Stereo mode."); + msg_Err(p_aout, "You should configure your speaker layout with Audio Midi Setup in /Applications/Utilities. VLC will output Stereo only."); dialog_Fatal(p_aout, _("Audio device is not configured"), "%s", _("You should configure your speaker layout with " - "the \"Audio Midi Setup\" utility in /Applications/" - "Utilities. Stereo mode is being used now.")); + "\"Audio Midi Setup\" in /Applications/" + "Utilities. VLC will output Stereo only.")); + } else { + + p_aout->sys->chans_to_reorder = aout_CheckChannelReorder(NULL, chans_out, fmt->i_physical_channels, p_aout->sys->chan_table); + if (p_aout->sys->chans_to_reorder) + msg_Dbg(p_aout, "channel reordering needed"); + else + msg_Dbg(p_aout, "no channel reordering needed"); } + } free(layout); } else { - msg_Warn(p_aout, "this driver does not support kAudioDevicePropertyPreferredChannelLayout. BAD DRIVER AUTHOR !!!"); + msg_Warn(p_aout, "device driver does not support kAudioDevicePropertyPreferredChannelLayout - using stereo fallback [%4.4s]", (char *)&err); fmt->i_physical_channels = AOUT_CHANS_STEREO; } msg_Dbg(p_aout, "selected %d physical channels for device output", aout_FormatNbChannels(fmt)); msg_Dbg(p_aout, "VLC will output: %s", aout_FormatPrintChannels(fmt)); - p_sys->i_numberOfChannels = aout_FormatNbChannels(fmt); - - memset (&new_layout, 0, sizeof(new_layout)); - uint32_t chans_out[AOUT_CHAN_MAX]; - - switch(aout_FormatNbChannels(fmt)) { - case 1: - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; - break; - case 2: - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; - break; - case 3: - if (fmt->i_physical_channels & AOUT_CHAN_CENTER) - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_7; // L R C - else if (fmt->i_physical_channels & AOUT_CHAN_LFE) - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4; // L R LFE - break; - case 4: - if (fmt->i_physical_channels & (AOUT_CHAN_CENTER | AOUT_CHAN_LFE)) - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_10; // L R C LFE - else if (fmt->i_physical_channels & (AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT)) - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_3; // L R Ls Rs - else if (fmt->i_physical_channels & (AOUT_CHAN_CENTER | AOUT_CHAN_REARCENTER)) - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_3; // L R C Cs - break; - case 5: - if (fmt->i_physical_channels & (AOUT_CHAN_CENTER)) - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_19; // L R Ls Rs C - else if (fmt->i_physical_channels & (AOUT_CHAN_LFE)) - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_18; // L R Ls Rs LFE - break; - case 6: - if (fmt->i_physical_channels & (AOUT_CHAN_LFE)) - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_20; // L R Ls Rs C LFE - else - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_AudioUnit_6_0; // L R Ls Rs C Cs - break; - case 7: - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A; - - chans_out[0] = AOUT_CHAN_LEFT; - chans_out[1] = AOUT_CHAN_RIGHT; - chans_out[2] = AOUT_CHAN_CENTER; - chans_out[3] = AOUT_CHAN_LFE; - chans_out[4] = AOUT_CHAN_REARLEFT; - chans_out[5] = AOUT_CHAN_REARRIGHT; - chans_out[6] = AOUT_CHAN_REARCENTER; - p_aout->sys->chans_to_reorder = aout_CheckChannelReorder(NULL, chans_out, fmt->i_physical_channels, p_aout->sys->chan_table); - if (p_aout->sys->chans_to_reorder) - msg_Dbg(p_aout, "channel reordering needed"); - - break; - case 8: - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A; - - chans_out[0] = AOUT_CHAN_LEFT; - chans_out[1] = AOUT_CHAN_RIGHT; - chans_out[2] = AOUT_CHAN_CENTER; - chans_out[3] = AOUT_CHAN_LFE; - chans_out[4] = AOUT_CHAN_MIDDLELEFT; - chans_out[5] = AOUT_CHAN_MIDDLERIGHT; - chans_out[6] = AOUT_CHAN_REARLEFT; - chans_out[7] = AOUT_CHAN_REARRIGHT; - - p_aout->sys->chans_to_reorder = aout_CheckChannelReorder(NULL, chans_out, fmt->i_physical_channels, p_aout->sys->chan_table); - if (p_aout->sys->chans_to_reorder) - msg_Dbg(p_aout, "channel reordering needed"); - - break; - } /* Set up the format to be used */ DeviceFormat.mSampleRate = fmt->i_rate; @@ -567,52 +688,26 @@ static int OpenAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt) kAudioUnitScope_Input, 0, &input, sizeof(input))); - /* Set the new_layout as the layout VLC will use to feed the AU unit */ - verify_noerr(AudioUnitSetProperty(p_sys->au_unit, - kAudioUnitProperty_AudioChannelLayout, - kAudioUnitScope_Output, - 0, &new_layout, sizeof(new_layout))); - - if (new_layout.mNumberChannelDescriptions > 0) - free(new_layout.mChannelDescriptions); - /* AU initiliaze */ verify_noerr(AudioUnitInitialize(p_sys->au_unit)); - /* Find the difference between device clock and mdate clock */ - p_sys->clock_diff = - (mtime_t) - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) / 1000; - p_sys->clock_diff += mdate(); - /* setup circular buffer */ - msg_Dbg( p_aout, "suggested buffer size: %i, default %i", DeviceFormat.mBytesPerPacket * 4, kBufferLength); - TPCircularBufferInit(&p_sys->circular_buffer, kBufferLength); + TPCircularBufferInit(&p_sys->circular_buffer, AUDIO_BUFFER_SIZE_IN_SECONDS * + fmt->i_rate * fmt->i_bytes_per_frame); - p_sys->b_got_first_sample = false; - p_sys->i_played_length = 0; - p_sys->i_last_sample_time = 0; + verify_noerr(AudioOutputUnitStart(p_sys->au_unit)); /* Set volume for output unit */ - float volume = var_InheritInteger(p_aout, "auhal-volume") / (float)AOUT_VOLUME_DEFAULT; - verify_noerr(AudioUnitSetParameter(p_sys->au_unit, - kHALOutputParam_Volume, - kAudioUnitScope_Global, - 0, - volume * volume * volume, - 0)); - - p_aout->time_get = TimeGetAnalog; - p_aout->play = PlayAnalog; - p_aout->pause = PauseAnalog; - p_aout->flush = FlushAnalog; + VolumeSet(p_aout, p_sys->f_volume); + MuteSet(p_aout, p_sys->b_mute); return true; } -/***************************************************************************** - * Setup a encoded digital stream (SPDIF) - *****************************************************************************/ -static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) +/* + * StartSPDIF: Setup an encoded digital stream (SPDIF) output + */ +static int StartSPDIF(audio_output_t * p_aout, audio_sample_format_t *fmt) { struct aout_sys_t *p_sys = p_aout->sys; OSStatus err = noErr; @@ -620,6 +715,7 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) Boolean b_writeable = false; AudioStreamID *p_streams = NULL; unsigned i_streams = 0; + AudioStreamBasicDescription desired_stream_format; /* Start doing the SPDIF setup proces */ p_sys->b_digital = true; @@ -629,10 +725,16 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) i_param_size = sizeof(p_sys->i_hog_pid); p_sys->i_hog_pid = getpid() ; + /* + * 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. + */ + p_sys->b_ignore_streams_changed_callback = true; err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, i_param_size, &p_sys->i_hog_pid); + p_sys->b_ignore_streams_changed_callback = false; if (err != noErr) { - msg_Err(p_aout, "failed to set hogmode: [%4.4s]", (char *)&err); + msg_Err(p_aout, "failed to set hogmode [%4.4s]", (char *)&err); return false; } @@ -651,7 +753,7 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) } if (err != noErr) { - msg_Err(p_aout, "failed to set mixmode: [%4.4s]", (char *)&err); + msg_Err(p_aout, "failed to set mixmode [%4.4s]", (char *)&err); return false; } } @@ -660,7 +762,7 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; err = AudioObjectGetPropertyDataSize(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size); if (err != noErr) { - msg_Err(p_aout, "could not get number of streams: [%4.4s]", (char *)&err); + msg_Err(p_aout, "could not get size of stream description packet [%4.4s]", (char *)&err); return false; } @@ -672,7 +774,7 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size, p_streams); if (err != noErr) { - msg_Err(p_aout, "could not get number of streams: [%4.4s]", (char *)&err); + msg_Err(p_aout, "could not fetch stream descriptions [%4.4s]", (char *)&err); free(p_streams); return false; } @@ -687,7 +789,7 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) /* Retrieve all the stream formats supported by each output stream */ err = AudioObjectGetPropertyDataSize(p_streams[i], &physicalFormatsAddress, 0, NULL, &i_param_size); if (err != noErr) { - msg_Err(p_aout, "OpenSPDIF: could not get number of streamformats: [%s] (%i)", (char *)&err, (int32_t)err); + msg_Err(p_aout, "could not get number of streamformats: [%4.4s] (%i)", (char *)&err, (int32_t)err); continue; } @@ -729,7 +831,7 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) i_param_size = sizeof(p_sys->sfmt_revert); err = AudioObjectGetPropertyData(p_sys->i_stream_id, ¤tPhysicalFormatAddress, 0, NULL, &i_param_size, &p_sys->sfmt_revert); if (err != noErr) { - msg_Err(p_aout, "could not retrieve the original streamformat: [%4.4s]", (char *)&err); + msg_Err(p_aout, "could not retrieve the original streamformat [%4.4s]", (char *)&err); continue; } p_sys->b_revert = true; @@ -754,38 +856,33 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) } if (i_requested_rate_format >= 0) /* We prefer to output at the samplerate of the original audio */ - p_sys->stream_format = p_format_list[i_requested_rate_format].mFormat; + desired_stream_format = p_format_list[i_requested_rate_format].mFormat; else if (i_current_rate_format >= 0) /* If not possible, we will try to use the current samplerate of the device */ - p_sys->stream_format = p_format_list[i_current_rate_format].mFormat; + desired_stream_format = p_format_list[i_current_rate_format].mFormat; else - p_sys->stream_format = p_format_list[i_backup_rate_format].mFormat; /* And if we have to, any digital format will be just fine (highest rate possible) */ + desired_stream_format = p_format_list[i_backup_rate_format].mFormat; /* And if we have to, any digital format will be just fine (highest rate possible) */ } free(p_format_list); } free(p_streams); - /* get notified when we don't have spdif-output anymore */ - err = AudioObjectAddPropertyListener(p_sys->i_stream_id, &physicalFormatsAddress, HardwareListener, (void *)p_aout); - if (err != noErr) { - msg_Warn(p_aout, "could not set audio device property streams callback on device: %4.4s", - (char *)&err); - } - msg_Dbg(p_aout, STREAM_FORMAT_MSG("original stream format: ", p_sys->sfmt_revert)); - if (!AudioStreamChangeFormat(p_aout, p_sys->i_stream_id, p_sys->stream_format)) + if (!AudioStreamChangeFormat(p_aout, p_sys->i_stream_id, desired_stream_format)) { + msg_Err(p_aout, "failed to change stream format for SPDIF output"); return false; + } /* Set the format flags */ - if (p_sys->stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian) + if (desired_stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian) fmt->i_format = VLC_CODEC_SPDIFB; else fmt->i_format = VLC_CODEC_SPDIFL; fmt->i_bytes_per_frame = AOUT_SPDIF_SIZE; fmt->i_frame_length = A52_FRAME_NB; - fmt->i_rate = (unsigned int)p_sys->stream_format.mSampleRate; + fmt->i_rate = (unsigned int)desired_stream_format.mSampleRate; + p_sys->i_rate = fmt->i_rate; aout_FormatPrepare(fmt); - aout_PacketInit(p_aout, &p_sys->packet, A52_FRAME_NB, fmt); /* Add IOProc callback */ err = AudioDeviceCreateIOProcID(p_sys->i_selected_dev, @@ -793,54 +890,34 @@ static int OpenSPDIF (audio_output_t * p_aout, audio_sample_format_t *fmt) (void *)p_aout, &p_sys->i_procID); if (err != noErr) { - msg_Err(p_aout, "AudioDeviceCreateIOProcID failed: [%4.4s]", (char *)&err); - aout_PacketDestroy (p_aout); + msg_Err(p_aout, "Failed to create Process ID [%4.4s]", (char *)&err); return false; } - /* Check for the difference between the Device clock and mdate */ - p_sys->clock_diff = - (mtime_t) - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) / 1000; - p_sys->clock_diff += mdate(); - /* Start device */ err = AudioDeviceStart(p_sys->i_selected_dev, p_sys->i_procID); if (err != noErr) { - msg_Err(p_aout, "AudioDeviceStart failed: [%4.4s]", (char *)&err); + msg_Err(p_aout, "Failed to start audio device [%4.4s]", (char *)&err); err = AudioDeviceDestroyIOProcID(p_sys->i_selected_dev, p_sys->i_procID); if (err != noErr) - msg_Err(p_aout, "AudioDeviceDestroyIOProcID failed: [%4.4s]", (char *)&err); + msg_Err(p_aout, "Failed to destroy process ID [%4.4s]", (char *)&err); - aout_PacketDestroy (p_aout); return false; } + /* setup circular buffer */ + TPCircularBufferInit(&p_sys->circular_buffer, 200 * AOUT_SPDIF_SIZE); + return true; } - -/***************************************************************************** - * Close: Close HAL AudioUnit - *****************************************************************************/ static void Stop(audio_output_t *p_aout) { struct aout_sys_t *p_sys = p_aout->sys; OSStatus err = noErr; UInt32 i_param_size = 0; - AudioObjectPropertyAddress deviceAliveAddress = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - err = AudioObjectRemovePropertyListener(p_sys->i_selected_dev, &deviceAliveAddress, HardwareListener, (void *)p_aout); - if (err != noErr) - msg_Err(p_aout, "failed to remove audio device life checker: [%4.4s]", (char *)&err); - - if (p_sys->b_digital) { - AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 }; - err = AudioObjectRemovePropertyListener(p_sys->i_stream_id, &physicalFormatsAddress, HardwareListener, (void *)p_aout); - if (err != noErr) - msg_Err(p_aout, "failed to remove audio device property streams callback: [%4.4s]", (char *)&err); - } - if (p_sys->au_unit) { verify_noerr(AudioOutputUnitStop(p_sys->au_unit)); verify_noerr(AudioUnitUninitialize(p_sys->au_unit)); @@ -852,13 +929,13 @@ static void Stop(audio_output_t *p_aout) err = AudioDeviceStop(p_sys->i_selected_dev, p_sys->i_procID); if (err != noErr) - msg_Err(p_aout, "AudioDeviceStop failed: [%4.4s]", (char *)&err); + msg_Err(p_aout, "Failed to stop audio device [%4.4s]", (char *)&err); /* Remove IOProc callback */ err = AudioDeviceDestroyIOProcID(p_sys->i_selected_dev, p_sys->i_procID); if (err != noErr) - msg_Err(p_aout, "AudioDeviceDestroyIOProcID failed: [%4.4s]", (char *)&err); + msg_Err(p_aout, "Failed to destroy Process ID [%4.4s]", (char *)&err); if (p_sys->b_revert) AudioStreamChangeFormat(p_aout, p_sys->i_stream_id, p_sys->sfmt_revert); @@ -866,6 +943,7 @@ static void Stop(audio_output_t *p_aout) if (p_sys->b_changed_mixing && p_sys->sfmt_revert.mFormatID != kAudioFormat60958AC3) { int b_mix; Boolean b_writeable = false; + i_param_size = sizeof(int); /* Revert mixable to true if we are allowed to */ AudioObjectPropertyAddress audioDeviceSupportsMixingAddress = { kAudioDevicePropertySupportsMixing , kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; err = AudioObjectIsPropertySettable(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, &b_writeable); @@ -878,361 +956,265 @@ static void Stop(audio_output_t *p_aout) } if (err != noErr) - msg_Err(p_aout, "failed to set mixmode: [%4.4s]", (char *)&err); + msg_Err(p_aout, "failed to re-set mixmode [%4.4s]", (char *)&err); } } - AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &audioDevicesAddress, HardwareListener, (void *)p_aout); - - if (err != noErr) - msg_Err(p_aout, "AudioHardwareRemovePropertyListener failed: [%4.4s]", (char *)&err); - if (p_sys->i_hog_pid == getpid()) { p_sys->i_hog_pid = -1; i_param_size = sizeof(p_sys->i_hog_pid); AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; + + /* + * 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. + */ + p_sys->b_ignore_streams_changed_callback = true; err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, i_param_size, &p_sys->i_hog_pid); + p_sys->b_ignore_streams_changed_callback = false; if (err != noErr) - msg_Err(p_aout, "Could not release hogmode: [%4.4s]", (char *)&err); + msg_Err(p_aout, "Failed to release hogmode [%4.4s]", (char *)&err); } - var_DelCallback(p_aout, "audio-device", AudioDeviceCallback, NULL); + /* remove audio device alive callback */ + AudioObjectPropertyAddress deviceAliveAddress = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + err = AudioObjectRemovePropertyListener(p_sys->i_selected_dev, &deviceAliveAddress, DeviceAliveListener, (void *)p_aout); + if (err != noErr) { + /* Be tolerant, only give a warning here */ + msg_Warn(p_aout, "failed to remove audio device life checker [%4.4s]", (char *)&err); + } - p_sys->i_played_length = 0; - p_sys->i_last_sample_time = 0; + p_sys->i_bytes_per_sample = 0; + p_sys->b_digital = false; /* clean-up circular buffer */ TPCircularBufferCleanup(&p_sys->circular_buffer); +} + +#pragma mark - +#pragma mark core interaction - if (p_sys->b_digital) - aout_PacketDestroy(p_aout); +static void ReportDevice(audio_output_t *p_aout, UInt32 i_id, char *name) +{ + char deviceid[10]; + sprintf(deviceid, "%i", i_id); + + aout_HotplugReport(p_aout, deviceid, name); } -/***************************************************************************** - * Probe: Check which devices the OS has, and add them to our audio-device menu - *****************************************************************************/ -static void Probe(audio_output_t * p_aout) +static void RebuildDeviceList(audio_output_t * p_aout) { OSStatus err = noErr; - UInt32 i_param_size = 0; - AudioDeviceID devid_def = 0; - AudioDeviceID *p_devices = NULL; - vlc_value_t val, text; + UInt32 propertySize = 0; + AudioObjectID *deviceIDs; + UInt32 numberOfDevices; + CFMutableArrayRef currentListOfDevices; struct aout_sys_t *p_sys = p_aout->sys; + ReportDevice(p_aout, 0, _("System Sound Output Device")); + + /* setup local array */ + currentListOfDevices = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + /* Get number of devices */ AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &i_param_size); + err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &propertySize); if (err != noErr) { - msg_Err(p_aout, "Could not get number of devices: [%s]", (char *)&err); - goto error; + msg_Err(p_aout, "Could not get number of devices: [%4.4s]", (char *)&err); + return; } - p_sys->i_devices = i_param_size / sizeof(AudioDeviceID); + numberOfDevices = propertySize / sizeof(AudioDeviceID); - if (p_sys->i_devices < 1) { - msg_Err(p_aout, "No audio output devices were found."); - goto error; + if (numberOfDevices < 1) { + msg_Err(p_aout, "No audio output devices found."); + return; } - msg_Dbg(p_aout, "found %u audio device(s)", (unsigned)p_sys->i_devices); + msg_Dbg(p_aout, "found %i audio device(s)", numberOfDevices); /* Allocate DeviceID array */ - p_devices = (AudioDeviceID*)malloc(sizeof(AudioDeviceID) * p_sys->i_devices); - if (p_devices == NULL) - goto error; + deviceIDs = (AudioDeviceID *)calloc(numberOfDevices, sizeof(AudioDeviceID)); + if (deviceIDs == NULL) + return; /* Populate DeviceID array */ - err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &i_param_size, p_devices); - if (err != noErr) { - msg_Err(p_aout, "could not get the device IDs: [%s]", (char *)&err); - goto error; - } - - /* Find the ID of the default Device */ - AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - i_param_size = sizeof(AudioDeviceID); - err= AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultDeviceAddress, 0, NULL, &i_param_size, &devid_def); + err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &propertySize, deviceIDs); if (err != noErr) { - msg_Err(p_aout, "could not get default audio device: [%s]", (char *)&err); - goto error; + msg_Err(p_aout, "could not get the device IDs [%4.4s]", (char *)&err); + return; } - p_sys->i_default_dev = devid_def; - var_Create(p_aout, "audio-device", VLC_VAR_INTEGER|VLC_VAR_HASCHOICE); - text.psz_string = (char*)_("Audio Device"); - var_Change(p_aout, "audio-device", VLC_VAR_SETTEXT, &text, NULL); + AudioObjectPropertyAddress deviceNameAddress = { kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - AudioObjectPropertyAddress deviceNameAddress = { kAudioDevicePropertyDeviceName, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - - for (unsigned int i = 0; i < p_sys->i_devices; i++) { + for (unsigned int i = 0; i < numberOfDevices; i++) { + CFStringRef device_name_ref; char *psz_name; - i_param_size = 0; + CFIndex length; + bool b_digital = false; + UInt32 i_id = deviceIDs[i]; /* Retrieve the length of the device name */ - err = AudioObjectGetPropertyDataSize(p_devices[i], &deviceNameAddress, 0, NULL, &i_param_size); - if (err != noErr) - goto error; + err = AudioObjectGetPropertyDataSize(deviceIDs[i], &deviceNameAddress, 0, NULL, &propertySize); + if (err != noErr) { + msg_Dbg(p_aout, "failed to get name size for device %i", deviceIDs[i]); + continue; + } /* Retrieve the name of the device */ - psz_name = (char *)malloc(i_param_size); - err = AudioObjectGetPropertyData(p_devices[i], &deviceNameAddress, 0, NULL, &i_param_size, psz_name); - if (err != noErr) - goto error; + err = AudioObjectGetPropertyData(deviceIDs[i], &deviceNameAddress, 0, NULL, &propertySize, &device_name_ref); + if (err != noErr) { + msg_Dbg(p_aout, "failed to get name for device %i", deviceIDs[i]); + continue; + } + length = CFStringGetLength(device_name_ref); + length++; + psz_name = (char *)malloc(length); + CFStringGetCString(device_name_ref, psz_name, length, kCFStringEncodingUTF8); - msg_Dbg(p_aout, "DevID: %u DevName: %s", (unsigned)p_devices[i], psz_name); + msg_Dbg(p_aout, "DevID: %i DevName: %s", deviceIDs[i], psz_name); - if (!AudioDeviceHasOutput(p_devices[i])) { - msg_Dbg(p_aout, "this device is INPUT only. skipping..."); + if (!AudioDeviceHasOutput(deviceIDs[i])) { + msg_Dbg(p_aout, "this '%s' is INPUT only. skipping...", psz_name); free(psz_name); continue; } - /* Add the menu entries */ - val.i_int = (int)p_devices[i]; - text.psz_string = psz_name; - var_Change(p_aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text); - text.psz_string = NULL; - if (p_sys->i_default_dev == p_devices[i]) { - /* The default device is the selected device normally */ - var_Change(p_aout, "audio-device", VLC_VAR_SETDEFAULT, &val, NULL); - var_Set(p_aout, "audio-device", val); + ReportDevice(p_aout, i_id, psz_name); + CFArrayAppendValue(currentListOfDevices, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i_id)); + + if (AudioDeviceSupportsDigital(p_aout, deviceIDs[i])) { + b_digital = true; + 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); + CFArrayAppendValue(currentListOfDevices, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i_id)); + free(psz_encoded_name); } - if (AudioDeviceSupportsDigital(p_aout, p_devices[i])) { - val.i_int = (int)p_devices[i] | AOUT_VAR_SPDIF_FLAG; - if (asprintf(&text.psz_string, _("%s (Encoded Output)"), psz_name) != -1) { - var_Change(p_aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text); - free(text.psz_string); - if (p_sys->i_default_dev == p_devices[i] && var_InheritBool(p_aout, "spdif")) { - /* We selected to prefer SPDIF output if available - * then this "dummy" entry should be selected */ - var_Change(p_aout, "audio-device", VLC_VAR_SETDEFAULT, &val, NULL); - var_Set(p_aout, "audio-device", val); - } - } - } + // TODO: only register once for each device + ManageAudioStreamsCallback(p_aout, deviceIDs[i], true); + CFRelease(device_name_ref); free(psz_name); } - /* If a device is already "preselected", then use this device */ - var_Get(p_aout->p_libvlc, "macosx-audio-device", &val); - if (val.i_int > 0) { - msg_Dbg(p_aout, "using preselected output device %#"PRIx64, val.i_int); - var_Change(p_aout, "audio-device", VLC_VAR_SETDEFAULT, &val, NULL); - var_Set(p_aout, "audio-device", val); - } - - /* Attach a Listener so that we are notified of a change in the Device setup */ - err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &audioDevicesAddress, HardwareListener, (void *)p_aout); - if (err != noErr) { - msg_Warn(p_aout, "failed to add listener for audio device configuration (%i)", err); - goto error; + vlc_mutex_lock(&p_sys->var_lock); + CFIndex count = 0; + if (p_sys->device_list) + count = CFArrayGetCount(p_sys->device_list); + + if (count > 0) { + CFNumberRef cfn_device_id; + int i_device_id = 0; + for (CFIndex x = 0; x < count; x++) { + if (!CFArrayContainsValue(currentListOfDevices, CFRangeMake(0, count), CFArrayGetValueAtIndex(p_sys->device_list, x))) { + cfn_device_id = CFArrayGetValueAtIndex(p_sys->device_list, x); + + if (cfn_device_id) { + CFNumberGetValue(cfn_device_id, kCFNumberSInt32Type, &i_device_id); + ReportDevice(p_aout, i_device_id, NULL); + } + } + } } + CFRelease(p_sys->device_list); + p_sys->device_list = CFArrayCreateCopy(kCFAllocatorDefault, currentListOfDevices); + CFRelease(currentListOfDevices); + vlc_mutex_unlock(&p_sys->var_lock); - free(p_devices); - return; - -error: - msg_Warn(p_aout, "audio device already in use"); - free(p_devices); - return; + free(deviceIDs); } -/***************************************************************************** - * AudioDeviceHasOutput: Checks if the Device actually provides any outputs at all - *****************************************************************************/ -static int AudioDeviceHasOutput(AudioDeviceID i_dev_id) +static int SwitchAudioDevice(audio_output_t *p_aout, const char *name) { - UInt32 dataSize; + struct aout_sys_t *p_sys = p_aout->sys; - AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - verify_noerr(AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &dataSize)); - if (dataSize == 0) - return FALSE; + if (name) + p_sys->i_new_selected_dev = atoi(name); + else + p_sys->i_new_selected_dev = 0; - return TRUE; + bool b_supports_digital = (p_sys->i_new_selected_dev & AOUT_VAR_SPDIF_FLAG); + if (b_supports_digital) + p_sys->b_selected_dev_is_digital = true; + else + p_sys->b_selected_dev_is_digital = false; + + p_sys->i_new_selected_dev = p_sys->i_new_selected_dev & ~AOUT_VAR_SPDIF_FLAG; + + aout_DeviceReport(p_aout, name); + aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT); + + return 0; } -/***************************************************************************** - * AudioDeviceSupportsDigital: Check i_dev_id for digital stream support. - *****************************************************************************/ -static int AudioDeviceSupportsDigital(audio_output_t *p_aout, AudioDeviceID i_dev_id) +static int VolumeSet(audio_output_t * p_aout, float volume) { - OSStatus err = noErr; - UInt32 i_param_size = 0; - AudioStreamID *p_streams = NULL; - int i_streams = 0; - bool b_return = false; + struct aout_sys_t *p_sys = p_aout->sys; + OSStatus ostatus = 0; - /* Retrieve all the output streams */ - AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - err = AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &i_param_size); - if (err != noErr) { - msg_Err(p_aout, "could not get number of streams: [%s] (%i)", (char *)&err, (int32_t)err); - return false; - } + if(p_sys->b_digital) + return VLC_EGENERIC; - i_streams = i_param_size / sizeof(AudioStreamID); - p_streams = (AudioStreamID *)malloc(i_param_size); - if (p_streams == NULL) - return VLC_ENOMEM; + p_sys->f_volume = volume; + aout_VolumeReport(p_aout, volume); - err = AudioObjectGetPropertyData(i_dev_id, &streamsAddress, 0, NULL, &i_param_size, p_streams); - if (err != noErr) { - msg_Err(p_aout, "could not get list of streams: [%s]", (char *)&err); - return false; + /* Set volume for output unit */ + if(!p_sys->b_mute) { + ostatus = AudioUnitSetParameter(p_sys->au_unit, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, + volume * volume * volume, + 0); } - for (int i = 0; i < i_streams; i++) { - if (AudioStreamSupportsDigital(p_aout, p_streams[i])) - b_return = true; - } + if (var_InheritBool(p_aout, "volume-save")) + config_PutInt(p_aout, "auhal-volume", lroundf(volume * AOUT_VOLUME_DEFAULT)); - free(p_streams); - return b_return; + return ostatus; } -/***************************************************************************** - * AudioStreamSupportsDigital: Check i_stream_id for digital stream support. - *****************************************************************************/ -static int AudioStreamSupportsDigital(audio_output_t *p_aout, AudioStreamID i_stream_id) +static int MuteSet(audio_output_t * p_aout, bool mute) { - OSStatus err = noErr; - UInt32 i_param_size = 0; - AudioStreamRangedDescription *p_format_list = NULL; - int i_formats = 0; - bool b_return = false; - - /* Retrieve all the stream formats supported by each output stream */ - AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 }; - err = AudioObjectGetPropertyDataSize(i_stream_id, &physicalFormatsAddress, 0, NULL, &i_param_size); - if (err != noErr) { - msg_Err(p_aout, "could not get number of streamformats: [%s] (%i)", (char *)&err, (int32_t)err); - return false; - } - - i_formats = i_param_size / sizeof(AudioStreamRangedDescription); - msg_Dbg(p_aout, "found %i stream formats", i_formats); + struct aout_sys_t *p_sys = p_aout->sys; + OSStatus ostatus; - p_format_list = (AudioStreamRangedDescription *)malloc(i_param_size); - if (p_format_list == NULL) - return false; + if(p_sys->b_digital) + return VLC_EGENERIC; - err = AudioObjectGetPropertyData(i_stream_id, &physicalFormatsAddress, 0, NULL, &i_param_size, p_format_list); - if (err != noErr) { - msg_Err(p_aout, "could not get the list of streamformats: [%4.4s]", (char *)&err); - free(p_format_list); - p_format_list = NULL; - return false; - } + p_sys->b_mute = mute; + aout_MuteReport(p_aout, mute); - for (int i = 0; i < i_formats; i++) { -#ifndef NDEBUG - msg_Dbg(p_aout, STREAM_FORMAT_MSG("supported format: ", p_format_list[i].mFormat)); -#endif + float volume = .0; + if (!mute) + volume = p_sys->f_volume; - if (p_format_list[i].mFormat.mFormatID == 'IAC3' || - p_format_list[i].mFormat.mFormatID == 'iac3' || - p_format_list[i].mFormat.mFormatID == kAudioFormat60958AC3 || - p_format_list[i].mFormat.mFormatID == kAudioFormatAC3) - b_return = true; - } + ostatus = AudioUnitSetParameter(p_sys->au_unit, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, + volume * volume * volume, + 0); - free(p_format_list); - return b_return; + return ostatus; } -/***************************************************************************** - * AudioStreamChangeFormat: Change i_stream_id to change_format - *****************************************************************************/ -static int AudioStreamChangeFormat(audio_output_t *p_aout, AudioStreamID i_stream_id, AudioStreamBasicDescription change_format) -{ - OSStatus err = noErr; - UInt32 i_param_size = 0; - - AudioObjectPropertyAddress physicalFormatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; +#pragma mark - +#pragma mark actual playback - struct { vlc_mutex_t lock; vlc_cond_t cond; } w; - - msg_Dbg(p_aout, STREAM_FORMAT_MSG("setting stream format: ", change_format)); - - /* Condition because SetProperty is asynchronious */ - vlc_cond_init(&w.cond); - vlc_mutex_init(&w.lock); - vlc_mutex_lock(&w.lock); - - /* Install the callback */ - err = AudioObjectAddPropertyListener(i_stream_id, &physicalFormatAddress, StreamListener, (void *)&w); - if (err != noErr) { - msg_Err(p_aout, "AudioObjectAddPropertyListener for kAudioStreamPropertyPhysicalFormat failed: [%4.4s]", (char *)&err); - return false; - } - - /* change the format */ - err = AudioObjectSetPropertyData(i_stream_id, &physicalFormatAddress, 0, NULL, sizeof(AudioStreamBasicDescription), - &change_format); - if (err != noErr) { - msg_Err(p_aout, "could not set the stream format: [%4.4s]", (char *)&err); - return false; - } - - /* The AudioStreamSetProperty is not only asynchronious (requiring the locks) - * it is also not atomic in its behaviour. - * Therefore we check 5 times before we really give up. - * FIXME: failing isn't actually implemented yet. */ - for (int i = 0; i < 5; i++) { - AudioStreamBasicDescription actual_format; - mtime_t timeout = mdate() + 500000; - - if (vlc_cond_timedwait(&w.cond, &w.lock, timeout)) - msg_Dbg(p_aout, "reached timeout"); - - i_param_size = sizeof(AudioStreamBasicDescription); - err = AudioObjectGetPropertyData(i_stream_id, &physicalFormatAddress, 0, NULL, &i_param_size, &actual_format); - - msg_Dbg(p_aout, STREAM_FORMAT_MSG("actual format in use: ", actual_format)); - if (actual_format.mSampleRate == change_format.mSampleRate && - actual_format.mFormatID == change_format.mFormatID && - actual_format.mFramesPerPacket == change_format.mFramesPerPacket) { - /* The right format is now active */ - break; - } - /* We need to check again */ - } - - /* Removing the property listener */ - err = AudioObjectRemovePropertyListener(i_stream_id, &physicalFormatAddress, StreamListener, (void *)&w); - if (err != noErr) { - msg_Err(p_aout, "AudioStreamRemovePropertyListener failed: [%4.4s]", (char *)&err); - return false; - } - - /* Destroy the lock and condition */ - vlc_mutex_unlock(&w.lock); - vlc_mutex_destroy(&w.lock); - vlc_cond_destroy(&w.cond); - - return true; -} - -static void PlayAnalog (audio_output_t * p_aout, block_t * p_block) +static void Play(audio_output_t * p_aout, block_t * p_block) { struct aout_sys_t *p_sys = p_aout->sys; if (p_block->i_nb_samples > 0) { - if (!p_sys->b_got_first_sample) { - /* Start the AU */ - verify_noerr(AudioOutputUnitStart(p_sys->au_unit)); - p_sys->b_got_first_sample = true; - } - /* Do the channel reordering */ - if (p_sys->chans_to_reorder) { + if (p_sys->chans_to_reorder && !p_sys->b_digital) { aout_ChannelReorder(p_block->p_buffer, p_block->i_buffer, p_sys->chans_to_reorder, @@ -1240,64 +1222,62 @@ static void PlayAnalog (audio_output_t * p_aout, block_t * p_block) VLC_CODEC_FL32); } - /* Render audio into buffer */ - AudioBufferList bufferList; - bufferList.mNumberBuffers = 1; - bufferList.mBuffers[0].mNumberChannels = p_sys->i_numberOfChannels; - bufferList.mBuffers[0].mData = malloc(p_block->i_nb_samples * sizeof(Float32) * p_sys->i_numberOfChannels); - bufferList.mBuffers[0].mDataByteSize = p_block->i_nb_samples * sizeof(Float32) * p_sys->i_numberOfChannels; - - memcpy(bufferList.mBuffers[0].mData, p_block->p_buffer, p_block->i_buffer); - - /* keep track of the played data */ - p_aout->sys->i_played_length += p_block->i_length; - /* move data to buffer */ - TPCircularBufferProduceBytes(&p_sys->circular_buffer, bufferList.mBuffers[0].mData, bufferList.mBuffers[0].mDataByteSize); + if (unlikely(!TPCircularBufferProduceBytes(&p_sys->circular_buffer, p_block->p_buffer, p_block->i_buffer))) + msg_Warn(p_aout, "dropped buffer"); + + if (!p_sys->i_bytes_per_sample) + p_sys->i_bytes_per_sample = p_block->i_buffer / p_block->i_nb_samples; } block_Release(p_block); } -static void PauseAnalog (audio_output_t *p_aout, bool pause, mtime_t date) +static void Pause(audio_output_t *p_aout, bool pause, mtime_t date) { + struct aout_sys_t * p_sys = p_aout->sys; VLC_UNUSED(date); - if (pause) - AudioOutputUnitStop(p_aout->sys->au_unit); - else - AudioOutputUnitStart(p_aout->sys->au_unit); + vlc_mutex_lock(&p_sys->lock); + p_sys->b_paused = pause; + vlc_mutex_unlock(&p_sys->lock); } -static void FlushAnalog(audio_output_t *p_aout, bool wait) +static void Flush(audio_output_t *p_aout, bool wait) { - struct aout_sys_t * p_sys = p_aout->sys; - VLC_UNUSED(wait); + struct aout_sys_t *p_sys = p_aout->sys; - p_sys->b_got_first_sample = false; + int32_t availableBytes; + vlc_mutex_lock(&p_sys->lock); + TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes); - /* flush circular buffer */ - AudioOutputUnitStop(p_aout->sys->au_unit); - TPCircularBufferClear(&p_aout->sys->circular_buffer); + if (wait) { + while (availableBytes > 0) { + vlc_cond_wait(&p_sys->cond, &p_sys->lock); + TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes); + } + } else { + /* flush circular buffer if data is left */ + if (availableBytes > 0) + TPCircularBufferClear(&p_aout->sys->circular_buffer); + } - p_sys->i_played_length = 0; - p_sys->i_last_sample_time = 0; + vlc_mutex_unlock(&p_sys->lock); } -static int TimeGetAnalog(audio_output_t *p_aout, mtime_t *delay) +static int TimeGet(audio_output_t *p_aout, mtime_t *delay) { struct aout_sys_t * p_sys = p_aout->sys; - vlc_mutex_lock(&p_sys->lock); - mtime_t i_pos = p_sys->i_last_sample_time * CLOCK_FREQ / p_sys->i_rate; - vlc_mutex_unlock(&p_sys->lock); - - if (i_pos > 0) { - *delay = p_aout->sys->i_played_length - i_pos; - return 0; - } - else + if (!p_sys->i_bytes_per_sample) return -1; + + int32_t availableBytes; + TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes); + + *delay = (availableBytes / p_sys->i_bytes_per_sample) * CLOCK_FREQ / p_sys->i_rate; + + return 0; } /***************************************************************************** @@ -1315,38 +1295,42 @@ static OSStatus RenderCallbackAnalog(vlc_object_t *p_obj, VLC_UNUSED(ioActionFlags); VLC_UNUSED(inTimeStamp); VLC_UNUSED(inBusNumber); + VLC_UNUSED(inNumberFrames); audio_output_t * p_aout = (audio_output_t *)p_obj; struct aout_sys_t * p_sys = p_aout->sys; - int bytesToCopy = ioData->mBuffers[0].mDataByteSize; + int bytesRequested = ioData->mBuffers[0].mDataByteSize; Float32 *targetBuffer = (Float32*)ioData->mBuffers[0].mData; + vlc_mutex_lock(&p_sys->lock); /* Pull audio from buffer */ int32_t availableBytes; Float32 *buffer = TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes); /* check if we have enough data */ - if (!availableBytes) { + if (!availableBytes || p_sys->b_paused) { /* return an empty buffer so silence is played until we have data */ - for (UInt32 j = 0; j < inNumberFrames; j++) - targetBuffer[j] = 0.; + memset(targetBuffer, 0, bytesRequested); } else { - memcpy(targetBuffer, buffer, __MIN(bytesToCopy, availableBytes)); - TPCircularBufferConsume(&p_sys->circular_buffer, __MIN(bytesToCopy, availableBytes)); - VLC_UNUSED(inNumberFrames); - vlc_mutex_lock(&p_sys->lock); - p_sys->i_last_sample_time = inTimeStamp->mSampleTime; - vlc_mutex_unlock(&p_sys->lock); + int32_t bytesToCopy = __MIN(bytesRequested, availableBytes); + assert(bytesToCopy > 0); + + memcpy(targetBuffer, buffer, bytesToCopy); + TPCircularBufferConsume(&p_sys->circular_buffer, bytesToCopy); + ioData->mBuffers[0].mDataByteSize = bytesToCopy; } + vlc_cond_signal(&p_sys->cond); + vlc_mutex_unlock(&p_sys->lock); + return noErr; } -/***************************************************************************** +/* * RenderCallbackSPDIF: callback for SPDIF audio output - *****************************************************************************/ -static OSStatus RenderCallbackSPDIF (AudioDeviceID inDevice, + */ +static OSStatus RenderCallbackSPDIF(AudioDeviceID inDevice, const AudioTimeStamp * inNow, const void * inInputData, const AudioTimeStamp * inInputTime, @@ -1354,89 +1338,205 @@ static OSStatus RenderCallbackSPDIF (AudioDeviceID inDevice, const AudioTimeStamp * inOutputTime, void * threadGlobals) { - block_t * p_buffer; - mtime_t current_date; - - audio_output_t * p_aout = (audio_output_t *)threadGlobals; - struct aout_sys_t * p_sys = p_aout->sys; - + VLC_UNUSED(inNow); VLC_UNUSED(inDevice); VLC_UNUSED(inInputData); VLC_UNUSED(inInputTime); + VLC_UNUSED(inOutputTime); - /* Check for the difference between the Device clock and mdate */ - p_sys->clock_diff = - (mtime_t) - AudioConvertHostTimeToNanos(inNow->mHostTime) / 1000; - p_sys->clock_diff += mdate(); + audio_output_t * p_aout = (audio_output_t *)threadGlobals; + struct aout_sys_t * p_sys = p_aout->sys; - current_date = p_sys->clock_diff + - AudioConvertHostTimeToNanos(inOutputTime->mHostTime) / 1000; - //- ((mtime_t) 1000000 / p_aout->format.i_rate * 31); // 31 = Latency in Frames. retrieve somewhere + int bytesRequested = outOutputData->mBuffers[p_sys->i_stream_index].mDataByteSize; + char *targetBuffer = outOutputData->mBuffers[p_sys->i_stream_index].mData; - p_buffer = aout_PacketNext(p_aout, current_date); + vlc_mutex_lock(&p_sys->lock); + /* Pull audio from buffer */ + int32_t availableBytes; + char *buffer = TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes); -#define BUFFER outOutputData->mBuffers[p_sys->i_stream_index] - if (p_buffer != NULL) { - if ((int)BUFFER.mDataByteSize != (int)p_buffer->i_buffer) - msg_Warn(p_aout, "bytesize: %d nb_bytes: %d", (int)BUFFER.mDataByteSize, (int)p_buffer->i_buffer); + /* check if we have enough data */ + if (!availableBytes || p_sys->b_paused) { + /* return an empty buffer so silence is played until we have data */ + memset(targetBuffer, 0, bytesRequested); + } else { + int32_t bytesToCopy = __MIN(bytesRequested, availableBytes); + assert(bytesToCopy > 0); - /* move data into output data buffer */ - memcpy(BUFFER.mData, p_buffer->p_buffer, p_buffer->i_buffer); - block_Release(p_buffer); + memcpy(targetBuffer, buffer, bytesToCopy); + TPCircularBufferConsume(&p_sys->circular_buffer, bytesToCopy); + outOutputData->mBuffers[p_sys->i_stream_index].mDataByteSize = bytesToCopy; } - else - memset(BUFFER.mData, 0, BUFFER.mDataByteSize); -#undef BUFFER + + vlc_cond_signal(&p_sys->cond); + vlc_mutex_unlock(&p_sys->lock); return noErr; } -/***************************************************************************** - * HardwareListener: Warns us of changes in the list of registered devices - *****************************************************************************/ -static OSStatus HardwareListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void*inClientData) +#pragma mark - +#pragma mark Stream / Hardware Listeners + +/* + * Callback when device list changed + */ +static OSStatus DevicesListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) { - OSStatus err = noErr; - audio_output_t *p_aout = (audio_output_t *)inClientData; 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; -#ifndef NDEBUG - for (unsigned int i = 0; i < inNumberAddresses; i++) { - switch (inAddresses[i].mSelector) { - case kAudioHardwarePropertyDevices: - msg_Warn(p_aout, "audio device configuration changed, resetting cache"); - break; + msg_Dbg(p_aout, "audio device configuration changed, resetting cache"); + RebuildDeviceList(p_aout); - case kAudioDevicePropertyDeviceIsAlive: - msg_Warn(p_aout, "audio device died, resetting aout"); - break; + vlc_mutex_lock(&p_sys->var_lock); + if(!CFArrayContainsValue(p_sys->device_list, CFRangeMake(0, CFArrayGetCount(p_sys->device_list)),CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &p_sys->i_selected_dev))) + aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT); + vlc_mutex_unlock(&p_sys->var_lock); - case kAudioStreamPropertyAvailablePhysicalFormats: - msg_Warn(p_aout, "available physical formats for audio device changed, resetting aout"); - break; + return noErr; +} - default: - msg_Warn(p_aout, "device reset for unknown reason (%i)", inAddresses[i].mSelector); - break; - } +/* + * Callback when current device is not alive anymore + */ +static OSStatus DeviceAliveListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) +{ + 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 + */ +static OSStatus DefaultDeviceChangedListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) +{ + VLC_UNUSED(inObjectID); + VLC_UNUSED(inNumberAddresses); + VLC_UNUSED(inAddresses); + + audio_output_t *p_aout = (audio_output_t *)inClientData; + if (!p_aout) + return -1; + + if (!p_aout->sys->b_selected_dev_is_default) + return noErr; + + AudioObjectID defaultDeviceID = 0; + UInt32 propertySize = sizeof(AudioObjectID); + AudioObjectPropertyAddress defaultDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; + propertySize = sizeof(AudioObjectID); + OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultDeviceAddress, 0, NULL, &propertySize, &defaultDeviceID); + if (err != noErr) { + msg_Err(p_aout, "could not get default audio device [%4.4s]", (char *)&err); + return -1; } -#endif - var_TriggerCallback(p_aout, "audio-device"); - var_Destroy(p_aout, "audio-device"); + msg_Dbg(p_aout, "default device changed to %i", defaultDeviceID); - return err; + /* 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) { + msg_Dbg(p_aout, "ignore, as digital mode is active"); + return noErr; + } + + /* Also ignore events which announce the same device id */ + if(defaultDeviceID == p_aout->sys->i_selected_dev) + return noErr; + + msg_Dbg(p_aout, "default device actually changed, resetting aout"); + aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT); + + return noErr; } -/***************************************************************************** - * StreamListener - *****************************************************************************/ -static OSStatus StreamListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void*inClientData) +/* + * Callback when physical formats for device change + */ +static OSStatus StreamsChangedListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) +{ + OSStatus err = noErr; + UInt32 i_param_size = 0; + AudioStreamID *p_streams = NULL; + int i_streams = 0; + + 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(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); + + /* 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) + return 0; + + /* + * check if changed stream id belongs to current device + */ + vlc_mutex_lock(&p_sys->var_lock); + AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; + err = AudioObjectGetPropertyDataSize(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size); + if (err != noErr) { + msg_Err(p_aout, "could not get number of streams for device %i [%4.4s]", p_sys->i_selected_dev, (char *)&err); + vlc_mutex_unlock(&p_sys->var_lock); + return VLC_EGENERIC; + } + + i_streams = i_param_size / sizeof(AudioStreamID); + p_streams = (AudioStreamID *)malloc(i_param_size); + if (p_streams == NULL) { + vlc_mutex_unlock(&p_sys->var_lock); + return VLC_ENOMEM; + } + + err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size, p_streams); + if (err != noErr) { + msg_Err(p_aout, "could not get list of streams [%4.4s]", (char *)&err); + vlc_mutex_unlock(&p_sys->var_lock); + return VLC_EGENERIC; + } + vlc_mutex_unlock(&p_sys->var_lock); + + for (int i = 0; i < i_streams; i++) { + if (p_streams[i] == inObjectID) { + msg_Dbg(p_aout, "Restart aout as this affects current device"); + aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT); + break; + } + } + free(p_streams); + + return noErr; +} + +/* + * StreamListener: check whether the device's physical format change is complete + */ +static OSStatus StreamListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) { OSStatus err = noErr; struct { vlc_mutex_t lock; vlc_cond_t cond; } * w = inClientData; @@ -1454,94 +1554,244 @@ static OSStatus StreamListener(AudioObjectID inObjectID, UInt32 inNumberAddress return err; } -/***************************************************************************** - * AudioDeviceCallback: Callback triggered when the audio-device variable is changed - *****************************************************************************/ -static int AudioDeviceCallback(vlc_object_t *p_this, const char *psz_variable, - vlc_value_t old_val, vlc_value_t new_val, void *param) +#pragma mark - +#pragma mark helpers + +static int ManageAudioStreamsCallback(audio_output_t *p_aout, AudioDeviceID i_dev_id, bool b_register) { - audio_output_t *p_aout = (audio_output_t *)p_this; - var_Set(p_aout->p_libvlc, "macosx-audio-device", new_val); - msg_Dbg(p_aout, "Set Device: %#"PRIx64, new_val.i_int); - return aout_ChannelsRestart(p_this, psz_variable, old_val, new_val, param); -} + OSStatus err = noErr; + UInt32 i_param_size = 0; + AudioStreamID *p_streams = NULL; + int i_streams = 0; + /* Retrieve all the output streams */ + AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; + err = AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &i_param_size); + if (err != noErr) { + msg_Err(p_aout, "could not get number of streams for device id %i [%4.4s]", i_dev_id, (char *)&err); + return VLC_EGENERIC; + } -/***************************************************************************** - * VolumeSet: Implements volume_set(). Update the CoreAudio AU volume immediately. - *****************************************************************************/ -static int VolumeSet(audio_output_t * p_aout, float volume) -{ - struct aout_sys_t *p_sys = p_aout->sys; - OSStatus ostatus; + i_streams = i_param_size / sizeof(AudioStreamID); + p_streams = (AudioStreamID *)malloc(i_param_size); + if (p_streams == NULL) + return VLC_ENOMEM; - aout_VolumeReport(p_aout, volume); + err = AudioObjectGetPropertyData(i_dev_id, &streamsAddress, 0, NULL, &i_param_size, p_streams); + if (err != noErr) { + msg_Err(p_aout, "could not get list of streams [%4.4s]", (char *)&err); + return VLC_EGENERIC; + } - /* Set volume for output unit */ - ostatus = AudioUnitSetParameter(p_sys->au_unit, - kHALOutputParam_Volume, - kAudioUnitScope_Global, - 0, - volume * volume * volume, - 0); + for (int i = 0; i < i_streams; i++) { + /* get notified when physical formats change */ + AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 }; - if (var_InheritBool(p_aout, "volume-save")) - config_PutInt(p_aout, "auhal-volume", lroundf(volume * AOUT_VOLUME_DEFAULT)); + if (b_register) { + err = AudioObjectAddPropertyListener(p_streams[i], &physicalFormatsAddress, StreamsChangedListener, (void *)p_aout); + if (err != noErr) { + // nope just means that we already have a callback + if (err == kAudioHardwareIllegalOperationError) { + msg_Warn(p_aout, "could not set audio stream formats property callback on stream id %i, callback already set? [%4.4s]", p_streams[i], + (char *)&err); + } else { + msg_Err(p_aout, "could not set audio stream formats property callback on stream id %i [%4.4s]", p_streams[i], + (char *)&err); + } + } - return ostatus; + } else { /* unregister callback */ + err = AudioObjectRemovePropertyListener(p_streams[i], &physicalFormatsAddress, StreamsChangedListener, (void *)p_aout); + if (err != noErr) + msg_Err(p_aout, "failed to remove audio device property streams callback [%4.4s]", (char *)&err); + } + + } + + free(p_streams); + return VLC_SUCCESS; } -static int MuteSet(audio_output_t * p_aout, bool mute) +/* + * AudioDeviceHasOutput: Checks if the device is actually an output device + */ +static int AudioDeviceHasOutput(AudioDeviceID i_dev_id) { - struct aout_sys_t *p_sys = p_aout->sys; - OSStatus ostatus; + UInt32 dataSize = 0; + OSStatus status; - aout_MuteReport(p_aout, mute); + AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; + status = AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &dataSize); - float volume = .0; + if (dataSize == 0 || status != noErr) + return FALSE; - if (!mute) - volume = var_InheritInteger(p_aout, "auhal-volume") / (float)AOUT_VOLUME_DEFAULT; + return TRUE; +} - ostatus = AudioUnitSetParameter(p_sys->au_unit, - kHALOutputParam_Volume, - kAudioUnitScope_Global, - 0, - volume * volume * volume, - 0); +/* + * AudioDeviceSupportsDigital: Checks if device supports raw bitstreams + */ +static int AudioDeviceSupportsDigital(audio_output_t *p_aout, AudioDeviceID i_dev_id) +{ + OSStatus err = noErr; + UInt32 i_param_size = 0; + AudioStreamID *p_streams = NULL; + int i_streams = 0; + bool b_return = false; - return ostatus; + /* Retrieve all the output streams */ + AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; + err = AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &i_param_size); + if (err != noErr) { + msg_Err(p_aout, "could not get number of streams [%4.4s] (%i)", (char *)&err, (int32_t)err); + return false; + } + + i_streams = i_param_size / sizeof(AudioStreamID); + p_streams = (AudioStreamID *)malloc(i_param_size); + if (p_streams == NULL) + return VLC_ENOMEM; + + err = AudioObjectGetPropertyData(i_dev_id, &streamsAddress, 0, NULL, &i_param_size, p_streams); + if (err != noErr) { + msg_Err(p_aout, "could not get list of streams [%4.4s]", (char *)&err); + return false; + } + + for (int i = 0; i < i_streams; i++) { + if (AudioStreamSupportsDigital(p_aout, p_streams[i])) + b_return = true; + } + + free(p_streams); + return b_return; } -static int Open(vlc_object_t *obj) +/* + * AudioStreamSupportsDigital: Checks if audio stream is compatible with raw bitstreams + */ +static int AudioStreamSupportsDigital(audio_output_t *p_aout, AudioStreamID i_stream_id) { - audio_output_t *aout = (audio_output_t *)obj; - aout_sys_t *sys = malloc(sizeof (*sys)); + OSStatus err = noErr; + UInt32 i_param_size = 0; + AudioStreamRangedDescription *p_format_list = NULL; + int i_formats = 0; + bool b_return = false; - if (unlikely(sys == NULL)) - return VLC_ENOMEM; + /* Retrieve all the stream formats supported by each output stream */ + AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 }; + err = AudioObjectGetPropertyDataSize(i_stream_id, &physicalFormatsAddress, 0, NULL, &i_param_size); + if (err != noErr) { + msg_Err(p_aout, "could not get number of streamformats [%4.4s] (%i)", (char *)&err, (int32_t)err); + return false; + } - vlc_mutex_init(&sys->lock); + i_formats = i_param_size / sizeof(AudioStreamRangedDescription); + msg_Dbg(p_aout, "found %i stream formats for stream id %i", i_formats, i_stream_id); - aout->sys = sys; - aout->start = Start; - aout->stop = Stop; - aout->volume_set = VolumeSet; - aout->mute_set = MuteSet; + p_format_list = (AudioStreamRangedDescription *)malloc(i_param_size); + if (p_format_list == NULL) + return false; - /* remember the volume */ - aout_VolumeReport(aout, var_InheritInteger(aout, "auhal-volume") / (float)AOUT_VOLUME_DEFAULT); - MuteSet(aout, var_InheritBool(aout, "mute")); + err = AudioObjectGetPropertyData(i_stream_id, &physicalFormatsAddress, 0, NULL, &i_param_size, p_format_list); + if (err != noErr) { + msg_Err(p_aout, "could not get the list of streamformats [%4.4s]", (char *)&err); + free(p_format_list); + p_format_list = NULL; + return false; + } - return VLC_SUCCESS; + for (int i = 0; i < i_formats; i++) { +#ifndef NDEBUG + msg_Dbg(p_aout, STREAM_FORMAT_MSG("supported format: ", p_format_list[i].mFormat)); +#endif + + if (p_format_list[i].mFormat.mFormatID == 'IAC3' || + p_format_list[i].mFormat.mFormatID == 'iac3' || + p_format_list[i].mFormat.mFormatID == kAudioFormat60958AC3 || + p_format_list[i].mFormat.mFormatID == kAudioFormatAC3) + b_return = true; + } + + free(p_format_list); + return b_return; } -static void Close(vlc_object_t *obj) +/* + * AudioStreamChangeFormat: switch stream format based on the provided description + */ +static int AudioStreamChangeFormat(audio_output_t *p_aout, AudioStreamID i_stream_id, AudioStreamBasicDescription change_format) { - audio_output_t *aout = (audio_output_t *)obj; - aout_sys_t *sys = aout->sys; + OSStatus err = noErr; + UInt32 i_param_size = 0; + + int retValue = true; - vlc_mutex_destroy(&sys->lock); + AudioObjectPropertyAddress physicalFormatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + + struct { vlc_mutex_t lock; vlc_cond_t cond; } w; + + msg_Dbg(p_aout, STREAM_FORMAT_MSG("setting stream format: ", change_format)); + + /* Condition because SetProperty is asynchronious */ + vlc_cond_init(&w.cond); + vlc_mutex_init(&w.lock); + vlc_mutex_lock(&w.lock); + + /* Install the callback */ + err = AudioObjectAddPropertyListener(i_stream_id, &physicalFormatAddress, StreamListener, (void *)&w); + if (err != noErr) { + msg_Err(p_aout, "AudioObjectAddPropertyListener for kAudioStreamPropertyPhysicalFormat failed [%4.4s]", (char *)&err); + retValue = false; + goto out; + } + + /* change the format */ + err = AudioObjectSetPropertyData(i_stream_id, &physicalFormatAddress, 0, NULL, sizeof(AudioStreamBasicDescription), + &change_format); + if (err != noErr) { + msg_Err(p_aout, "could not set the stream format [%4.4s]", (char *)&err); + retValue = false; + goto out; + } + + /* The AudioStreamSetProperty is not only asynchronious (requiring the locks) + * it is also not atomic in its behaviour. + * Therefore we check 5 times before we really give up. + * FIXME: failing isn't actually implemented yet. */ + for (int i = 0; i < 5; i++) { + AudioStreamBasicDescription actual_format; + mtime_t timeout = mdate() + 500000; + + if (vlc_cond_timedwait(&w.cond, &w.lock, timeout)) + msg_Dbg(p_aout, "reached timeout"); + + i_param_size = sizeof(AudioStreamBasicDescription); + err = AudioObjectGetPropertyData(i_stream_id, &physicalFormatAddress, 0, NULL, &i_param_size, &actual_format); + + msg_Dbg(p_aout, STREAM_FORMAT_MSG("actual format in use: ", actual_format)); + if (actual_format.mSampleRate == change_format.mSampleRate && + actual_format.mFormatID == change_format.mFormatID && + actual_format.mFramesPerPacket == change_format.mFramesPerPacket) { + /* The right format is now active */ + break; + } + /* We need to check again */ + } + +out: + /* Removing the property listener */ + err = AudioObjectRemovePropertyListener(i_stream_id, &physicalFormatAddress, StreamListener, (void *)&w); + if (err != noErr) { + msg_Err(p_aout, "AudioStreamRemovePropertyListener failed [%4.4s]", (char *)&err); + retValue = false; + } + + /* Destroy the lock and condition */ + vlc_mutex_unlock(&w.lock); + vlc_mutex_destroy(&w.lock); + vlc_cond_destroy(&w.cond); - free(sys); + return retValue; }