X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fauhal.c;h=9e3246e5586b647f8502c2a2b7b387a832f5c7f3;hb=dd9a1957a593e5a2a9abf17c2e3c3670f7c2ab27;hp=f7b4eb63179dff87bd8edcbd5cc465e806c5915c;hpb=93a0d5ab5b51d369c20f933f379ad4934daeb7bf;p=vlc diff --git a/modules/audio_output/auhal.c b/modules/audio_output/auhal.c index f7b4eb6317..9e3246e558 100644 --- a/modules/audio_output/auhal.c +++ b/modules/audio_output/auhal.c @@ -1,66 +1,73 @@ /***************************************************************************** * auhal.c: AUHAL and Coreaudio output plugin ***************************************************************************** - * Copyright (C) 2005, 2011 the VideoLAN team + * Copyright (C) 2005 - 2013 VLC authors and VideoLAN * $Id$ * * 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 General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ -/***************************************************************************** - * Preamble - *****************************************************************************/ +#pragma mark includes #ifdef HAVE_CONFIG_H -# include "config.h" +# import "config.h" #endif -#include -#include -#include // dialog_Fatal -#include // aout_* +#import +#import +#import // dialog_Fatal +#import // aout_* + +#import // AudioUnit +#import // AudioDeviceID +#import // AudioFormatGetProperty +#import + +#import "TPCircularBuffer.h" -#include // AudioUnit -#include // AudioDeviceID -#include // AudioFormatGetProperty -#include +#pragma mark - +#pragma mark private declarations #ifndef verify_noerr # define verify_noerr(a) assert((a) == noErr) #endif -#define STREAM_FORMAT_MSG( pre, sfm ) \ +#define STREAM_FORMAT_MSG(pre, sfm) \ pre "[%f][%4.4s][%u][%u][%u][%u][%u][%u]", \ sfm.mSampleRate, (char *)&sfm.mFormatID, \ (unsigned int)sfm.mFormatFlags, (unsigned int)sfm.mBytesPerPacket, \ (unsigned int)sfm.mFramesPerPacket, (unsigned int)sfm.mBytesPerFrame, \ (unsigned int)sfm.mChannelsPerFrame, (unsigned int)sfm.mBitsPerChannel -#define FRAMESIZE 2048 -#define BUFSIZE (FRAMESIZE * 8) * 8 #define AOUT_VAR_SPDIF_FLAG 0xf00000 -/* - * TODO: - * - clean up the debug info - * - be better at changing stream setup or devices setup changes while playing. - * - fix 6.1 and 7.1 - */ +#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 @@ -70,230 +77,326 @@ *****************************************************************************/ struct aout_sys_t { - aout_packet_t packet; - AudioDeviceID i_default_dev; /* DeviceID of defaultOutputDevice */ - AudioDeviceID i_selected_dev; /* DeviceID of the selected device */ - 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 */ + AudioObjectID i_default_dev; /* DeviceID of defaultOutputDevice */ + 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; + AudioDeviceIOProcID i_procID; /* DeviceID of current device */ + 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 */ - Component au_component; /* The Audiocomponent we use */ - AudioUnit au_unit; /* The AudioUnit we use */ - uint8_t p_remainder_buffer[BUFSIZE]; - uint32_t i_read_bytes; - uint32_t i_total_bytes; + AudioComponent au_component; /* The AudioComponent we use */ + AudioUnit au_unit; /* The AudioUnit we use */ /* CoreAudio SPDIF mode specific */ - 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; /* Wether we need to revert the stream format */ - bool b_changed_mixing; /* Wether we need to set the mixing mode back */ + 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 */ + int i_bytes_per_sample; + + CFArrayRef device_list; + + float f_volume; + bool b_mute; + + vlc_mutex_t lock; + vlc_cond_t cond; }; -/***************************************************************************** - * Local prototypes. - *****************************************************************************/ -static int Open ( vlc_object_t * ); -static int OpenAnalog ( audio_output_t * ); -static int OpenSPDIF ( audio_output_t * ); -static void Close ( vlc_object_t * ); +#pragma mark - +#pragma mark local prototypes & module descriptor -static void Probe ( audio_output_t * ); +static int Open (vlc_object_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 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 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 * ); +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 int VolumeSet ( audio_output_t *, float, bool ); +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 RegisterAudioStreamsCallback(audio_output_t *, AudioDeviceID); +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); -/***************************************************************************** - * 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.") 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( "macosx-audio-device", 0, ADEV_TEXT, ADEV_LONGTEXT, false ) + 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 () -/***************************************************************************** - * Open: open macosx audio output - *****************************************************************************/ -static int Open( vlc_object_t * p_this ) +#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->lock); + vlc_cond_init(&p_sys->cond); + p_sys->b_digital = 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); + + /* Attach a Listener so that we are notified of a change in the Device setup */ + AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &audioDevicesAddress, HardwareListener, (void *)p_aout); + if (err != noErr) + msg_Warn(p_aout, "failed to add listener for audio device configuration [%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); + + SwitchAudioDevice(p_aout, config_GetPsz(p_aout, "auhal-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, HardwareListener, (void *)p_aout); + if (err != noErr) + msg_Err(p_aout, "AudioHardwareRemovePropertyListener failed [%4.4s]", (char *)&err); + + + /* remove audio device alive callback */ + 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); + + /* remove audio streams callback */ + if (p_sys->i_stream_id > 0) { + 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); + } + + config_PutPsz(p_aout, "auhal-audio-device", aout_DeviceGet(p_aout)); + + CFRelease(p_sys->device_list); + + 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; - audio_output_t *p_aout = (audio_output_t *)p_this; /* Use int here, to match kAudioDevicePropertyDeviceIsAlive * property size */ int b_alive = false; - /* Allocate structure */ - p_aout->sys = malloc( sizeof( aout_sys_t ) ); - if( p_aout->sys == NULL ) - return VLC_ENOMEM; + 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_read_bytes = 0; - p_sys->i_total_bytes = 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; - memset( p_sys->p_remainder_buffer, 0, sizeof(uint8_t) * BUFSIZE ); + p_sys->i_bytes_per_sample = 0; - p_aout->pf_play = aout_PacketPlay; - p_aout->pf_pause = aout_PacketPause; - p_aout->pf_flush = aout_PacketFlush; + p_sys->i_selected_dev = p_sys->i_new_selected_dev; - aout_FormatPrint( p_aout, "VLC is looking for:", &p_aout->format ); + 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 ); - } - - /* Build a list of devices */ - if( var_Type( p_aout, "audio-device" ) == 0 ) - { - Probe( p_aout ); - } - - /* 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; - } - - 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" ); + msg_Dbg(p_aout, "attempting to use device %i", p_sys->i_selected_dev); /* Check if the desired device is alive and usable */ - /* TODO: add a callback to the device to alert us if the device dies */ - i_param_size = sizeof( b_alive ); + i_param_size = sizeof(b_alive); AudioObjectPropertyAddress audioDeviceAliveAddress = { kAudioDevicePropertyDeviceIsAlive, - kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - err = AudioObjectGetPropertyData( p_sys->i_selected_dev, &audioDeviceAliveAddress, 0, NULL, &i_param_size, &b_alive ); + err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceAliveAddress, 0, NULL, &i_param_size, &b_alive); - if( err != noErr ) - { + 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 ); + 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; } - 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; + if (!b_alive || p_sys->i_selected_dev == 0) { + msg_Warn(p_aout, "selected audio device is not alive, switching to default device"); + + AudioObjectID defaultDeviceID = 0; + UInt32 propertySize = 0; + 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); + goto error; + } + else + msg_Dbg(p_aout, "using default audio device %i", defaultDeviceID); + + p_sys->i_selected_dev = defaultDeviceID; + } + + // 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); + 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]", + (unsigned int)p_sys->i_selected_dev, (char *)&err); } AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - i_param_size = sizeof( p_sys->i_hog_pid ); - err = AudioObjectGetPropertyData( p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, &i_param_size, &p_sys->i_hog_pid ); - if( err != noErr ) - { + i_param_size = sizeof(p_sys->i_hog_pid); + 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", - (char *)&err ); + msg_Warn(p_aout, "could not check whether device is hogged [%4.4s]", + (char *)&err); 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." ); - dialog_Fatal( p_aout, _("Audio output failed"), "%s", + 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."); + dialog_Fatal(p_aout, _("Audio output failed"), "%s", _("The selected audio output device is exclusively in " - "use by another program.") ); + "use by another program.")); 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( &p_aout->format ) && b_supports_digital ) - { - if( OpenSPDIF( p_aout ) ) - { - msg_Dbg( p_aout, "digital output successfully opened" ); - return VLC_SUCCESS; + if (AOUT_FMT_SPDIF (fmt) && b_start_digital) { + if (StartSPDIF (p_aout, fmt)) { + msg_Dbg(p_aout, "digital output successfully opened"); + b_success = true; } - } - else - { - if( OpenAnalog( p_aout ) ) - { - msg_Dbg( p_aout, "analog output successfully opened" ); - return VLC_SUCCESS; + } else { + if (StartAnalog(p_aout, fmt)) { + msg_Dbg(p_aout, "analog output successfully opened"); + 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" ); - free( p_sys ); + 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 ) +/* + * 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; UInt32 i_param_size = 0; int i_original; - ComponentDescription desc; + 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; @@ -302,164 +405,182 @@ static int OpenAnalog( audio_output_t *p_aout ) desc.componentFlags = 0; desc.componentFlagsMask = 0; - p_sys->au_component = FindNextComponent( NULL, &desc ); - if( p_sys->au_component == NULL ) - { - msg_Warn( p_aout, "we cannot find our HAL component" ); + p_sys->au_component = AudioComponentFindNext(NULL, &desc); + if (p_sys->au_component == NULL) { + msg_Err(p_aout, "cannot find any HAL component, PCM output failed"); return false; } - err = OpenAComponent( p_sys->au_component, &p_sys->au_unit ); - if( err != noErr ) - { - msg_Warn( p_aout, "we cannot open our HAL component" ); + err = AudioComponentInstanceNew(p_sys->au_component, &p_sys->au_unit); + if (err != noErr) { + msg_Err(p_aout, "cannot open HAL component, PCM output failed [%4.4s]", (char *)&err); return false; } /* Set the device we will use for this output unit */ - err = AudioUnitSetProperty( p_sys->au_unit, + err = AudioUnitSetProperty(p_sys->au_unit, kAudioOutputUnitProperty_CurrentDevice, 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" ); + if (err != noErr) { + msg_Err(p_aout, "cannot select audio output device, PCM output failed [%4.4s]", (char *)&err); return false; } /* Get the current format */ i_param_size = sizeof(AudioStreamBasicDescription); - err = AudioUnitGetProperty( p_sys->au_unit, + err = AudioUnitGetProperty(p_sys->au_unit, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, + kAudioUnitScope_Output, 0, &DeviceFormat, - &i_param_size ); + &i_param_size); - if( err != noErr ) return false; - else msg_Dbg( p_aout, STREAM_FORMAT_MSG( "current format is: ", DeviceFormat ) ); + if (err != noErr) { + msg_Err(p_aout, "failed to detect supported stream formats [%4.4s]", (char *)&err); + return false; + } 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) */ - err = AudioUnitGetPropertyInfo( p_sys->au_unit, + err = AudioUnitGetPropertyInfo(p_sys->au_unit, kAudioDevicePropertyPreferredChannelLayout, kAudioUnitScope_Output, 0, &i_param_size, - NULL ); + NULL); - if( err == noErr ) - { - layout = (AudioChannelLayout *)malloc( i_param_size); + if (err == noErr) { + layout = (AudioChannelLayout *)malloc(i_param_size); - verify_noerr( AudioUnitGetProperty( p_sys->au_unit, + verify_noerr(AudioUnitGetProperty(p_sys->au_unit, kAudioDevicePropertyPreferredChannelLayout, kAudioUnitScope_Output, 0, layout, - &i_param_size )); + &i_param_size)); /* We need to "fill out" the ChannelLayout, because there are multiple ways that it can be set */ - if( layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) - { + if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { /* bitmap defined channellayout */ - verify_noerr( AudioFormatGetProperty( kAudioFormatProperty_ChannelLayoutForBitmap, - sizeof( UInt32), &layout->mChannelBitmap, + verify_noerr(AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap, + sizeof(UInt32), &layout->mChannelBitmap, &i_param_size, - layout )); - } - else if( layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions ) + layout)); + } else if (layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions) { /* layouttags defined channellayout */ - verify_noerr( AudioFormatGetProperty( kAudioFormatProperty_ChannelLayoutForTag, - sizeof( AudioChannelLayoutTag ), &layout->mChannelLayoutTag, + verify_noerr(AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag, + sizeof(AudioChannelLayoutTag), &layout->mChannelLayoutTag, &i_param_size, - layout )); + 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 */ - p_aout->format.i_physical_channels = 0; - i_original = p_aout->format.i_original_channels & AOUT_CHAN_PHYSMASK; + fmt->i_physical_channels = 0; + i_original = fmt->i_original_channels & AOUT_CHAN_PHYSMASK; - if( i_original == AOUT_CHAN_CENTER || layout->mNumberChannelDescriptions < 2 ) - { + if (i_original == AOUT_CHAN_CENTER || layout->mNumberChannelDescriptions < 2) { /* We only need Mono or cannot output more than 1 channel */ - p_aout->format.i_physical_channels = AOUT_CHAN_CENTER; - } - else if( i_original == (AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT) || layout->mNumberChannelDescriptions < 3 ) - { + fmt->i_physical_channels = AOUT_CHAN_CENTER; + } else if (i_original == (AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT) || layout->mNumberChannelDescriptions < 3) { /* We only need Stereo or cannot output more than 2 channels */ - p_aout->format.i_physical_channels = AOUT_CHAN_RIGHT | AOUT_CHAN_LEFT; - } - else - { + fmt->i_physical_channels = AOUT_CHANS_STEREO; + } else { /* We want more than stereo and we can do that */ - for( unsigned int i = 0; i < layout->mNumberChannelDescriptions; i++ ) - { - msg_Dbg( p_aout, "this is channel: %d", (int)layout->mChannelDescriptions[i].mChannelLabel ); + for (unsigned int i = 0; i < layout->mNumberChannelDescriptions; i++) { +#ifndef NDEBUG + msg_Dbg(p_aout, "this is channel: %d", (int)layout->mChannelDescriptions[i].mChannelLabel); +#endif - switch( layout->mChannelDescriptions[i].mChannelLabel ) - { + switch(layout->mChannelDescriptions[i].mChannelLabel) { case kAudioChannelLabel_Left: - p_aout->format.i_physical_channels |= AOUT_CHAN_LEFT; + fmt->i_physical_channels |= AOUT_CHAN_LEFT; continue; case kAudioChannelLabel_Right: - p_aout->format.i_physical_channels |= AOUT_CHAN_RIGHT; + fmt->i_physical_channels |= AOUT_CHAN_RIGHT; continue; case kAudioChannelLabel_Center: - p_aout->format.i_physical_channels |= AOUT_CHAN_CENTER; + fmt->i_physical_channels |= AOUT_CHAN_CENTER; continue; case kAudioChannelLabel_LFEScreen: - p_aout->format.i_physical_channels |= AOUT_CHAN_LFE; + fmt->i_physical_channels |= AOUT_CHAN_LFE; continue; case kAudioChannelLabel_LeftSurround: - p_aout->format.i_physical_channels |= AOUT_CHAN_REARLEFT; + fmt->i_physical_channels |= AOUT_CHAN_REARLEFT; continue; case kAudioChannelLabel_RightSurround: - p_aout->format.i_physical_channels |= AOUT_CHAN_REARRIGHT; + fmt->i_physical_channels |= AOUT_CHAN_REARRIGHT; continue; case kAudioChannelLabel_RearSurroundLeft: - p_aout->format.i_physical_channels |= AOUT_CHAN_MIDDLELEFT; + fmt->i_physical_channels |= AOUT_CHAN_MIDDLELEFT; continue; case kAudioChannelLabel_RearSurroundRight: - p_aout->format.i_physical_channels |= AOUT_CHAN_MIDDLERIGHT; + fmt->i_physical_channels |= AOUT_CHAN_MIDDLERIGHT; continue; case kAudioChannelLabel_CenterSurround: - p_aout->format.i_physical_channels |= AOUT_CHAN_REARCENTER; + 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 ); + msg_Warn(p_aout, "unrecognized channel form provided by driver: %d", (int)layout->mChannelDescriptions[i].mChannelLabel); } } - if( p_aout->format.i_physical_channels == 0 ) - { - p_aout->format.i_physical_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; - msg_Err( p_aout, "You should configure your speaker layout with Audio Midi Setup Utility in /Applications/Utilities. Now using Stereo mode." ); - dialog_Fatal( p_aout, _("Audio device is not configured"), "%s", + 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 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.")); } } - free( layout ); - } - else - { - msg_Warn( p_aout, "this driver does not support kAudioDevicePropertyPreferredChannelLayout. BAD DRIVER AUTHOR !!!" ); - p_aout->format.i_physical_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; + free(layout); + } else { + 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( &p_aout->format ) ); - msg_Dbg( p_aout, "VLC will output: %s", aout_FormatPrintChannels( &p_aout->format )); + 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)); - switch( aout_FormatNbChannels( &p_aout->format ) ) - { + uint32_t chans_out[AOUT_CHAN_MAX]; + + /* Some channel abbreviations used below: + * L - left + * R - right + * C - center + * Ls - left surround + * Rs - right surround + * Cs - center surround + * Rls - rear left surround + * Rrs - rear right surround + * Lw - left wide + * Rw - right wide + * Lsd - left surround direct + * Rsd - right surround direct + * Lc - left center + * Rc - right center + * Ts - top surround + * Vhl - vertical height left + * Vhc - vertical height center + * Vhr - vertical height right + * Lt - left matrix total. for matrix encoded stereo. + * Rt - right matrix total. for matrix encoded stereo. */ + + switch(aout_FormatNbChannels(fmt)) { case 1: new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; break; @@ -467,68 +588,117 @@ static int OpenAnalog( audio_output_t *p_aout ) new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; break; case 3: - if( p_aout->format.i_physical_channels & AOUT_CHAN_CENTER ) - { + if (fmt->i_physical_channels & AOUT_CHAN_CENTER) new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_7; // L R C - } - else if( p_aout->format.i_physical_channels & AOUT_CHAN_LFE ) - { + else if (fmt->i_physical_channels & AOUT_CHAN_LFE) new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4; // L R LFE - } break; case 4: - if( p_aout->format.i_physical_channels & ( AOUT_CHAN_CENTER | AOUT_CHAN_LFE ) ) - { + if (fmt->i_physical_channels & (AOUT_CHAN_CENTER | AOUT_CHAN_LFE)) new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_10; // L R C LFE - } - else if( p_aout->format.i_physical_channels & ( AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT ) ) - { + else if (fmt->i_physical_channels & (AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT)) new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_3; // L R Ls Rs - } - else if( p_aout->format.i_physical_channels & ( AOUT_CHAN_CENTER | AOUT_CHAN_REARCENTER ) ) - { + 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( p_aout->format.i_physical_channels & ( AOUT_CHAN_CENTER ) ) - { + if (fmt->i_physical_channels & (AOUT_CHAN_CENTER)) new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_19; // L R Ls Rs C - } - else if( p_aout->format.i_physical_channels & ( AOUT_CHAN_LFE ) ) - { + else if (fmt->i_physical_channels & (AOUT_CHAN_LFE)) new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_18; // L R Ls Rs LFE - } break; case 6: - if( p_aout->format.i_physical_channels & ( AOUT_CHAN_LFE ) ) - { + 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: - /* FIXME: This is incorrect. VLC uses the internal ordering: L R Lm Rm Lr Rr C LFE but this is wrong */ new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A; // L R C LFE Ls Rs Cs + + 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 for 6.1 output"); + break; case 8: - /* FIXME: This is incorrect. VLC uses the internal ordering: L R Lm Rm Lr Rr C LFE but this is wrong */ - new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A; // L R C LFE Ls Rs Lc Rc + if (fmt->i_physical_channels & (AOUT_CHAN_LFE) || currentMinorSystemVersion < 7) { + new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A; // L R C LFE Ls Rs Lc Rc + + 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; + + if (!(fmt->i_physical_channels & (AOUT_CHAN_LFE))) + msg_Warn(p_aout, "8.0 audio output not supported on OS X 10.%i, layout will be incorrect", currentMinorSystemVersion); + } +#ifdef MAC_OS_X_VERSION_10_7 + else { + new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DTS_8_0_B; // Lc C Rc L R Ls Cs Rs + + chans_out[0] = AOUT_CHAN_MIDDLELEFT; + chans_out[1] = AOUT_CHAN_CENTER; + chans_out[2] = AOUT_CHAN_MIDDLERIGHT; + chans_out[3] = AOUT_CHAN_LEFT; + chans_out[4] = AOUT_CHAN_RIGHT; + chans_out[5] = AOUT_CHAN_REARLEFT; + chans_out[6] = AOUT_CHAN_REARCENTER; + chans_out[7] = AOUT_CHAN_REARRIGHT; + } +#endif + 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 for 7.1 / 8.0 output"); + + break; + case 9: + if (currentMinorSystemVersion < 7) { + msg_Warn(p_aout, "8.1 audio output not supported on OS X 10.%i", currentMinorSystemVersion); + break; + } + +#ifdef MAC_OS_X_VERSION_10_7 + new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_DTS_8_1_B; // Lc C Rc L R Ls Cs Rs LFE + chans_out[0] = AOUT_CHAN_MIDDLELEFT; + chans_out[1] = AOUT_CHAN_CENTER; + chans_out[2] = AOUT_CHAN_MIDDLERIGHT; + chans_out[3] = AOUT_CHAN_LEFT; + chans_out[4] = AOUT_CHAN_RIGHT; + chans_out[5] = AOUT_CHAN_REARLEFT; + chans_out[6] = AOUT_CHAN_REARCENTER; + chans_out[7] = AOUT_CHAN_REARRIGHT; + chans_out[8] = AOUT_CHAN_LFE; + + 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 for 8.1 output"); +#endif break; } /* Set up the format to be used */ - DeviceFormat.mSampleRate = p_aout->format.i_rate; + DeviceFormat.mSampleRate = fmt->i_rate; DeviceFormat.mFormatID = kAudioFormatLinearPCM; + p_sys->i_rate = fmt->i_rate; - /* We use float 32. It's the best supported format by both VLC and Coreaudio */ - p_aout->format.i_format = VLC_CODEC_FL32; + /* We use float 32 since this is VLC's endorsed format */ + fmt->i_format = VLC_CODEC_FL32; DeviceFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; DeviceFormat.mBitsPerChannel = 32; - DeviceFormat.mChannelsPerFrame = aout_FormatNbChannels( &p_aout->format ); + DeviceFormat.mChannelsPerFrame = aout_FormatNbChannels(fmt); /* Calculate framesizes and stuff */ DeviceFormat.mFramesPerPacket = 1; @@ -537,69 +707,71 @@ static int OpenAnalog( audio_output_t *p_aout ) /* Set the desired format */ i_param_size = sizeof(AudioStreamBasicDescription); - verify_noerr( AudioUnitSetProperty( p_sys->au_unit, + verify_noerr(AudioUnitSetProperty(p_sys->au_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &DeviceFormat, - i_param_size )); + i_param_size)); - msg_Dbg( p_aout, STREAM_FORMAT_MSG( "we set the AU format: " , DeviceFormat ) ); + msg_Dbg(p_aout, STREAM_FORMAT_MSG("we set the AU format: " , DeviceFormat)); /* Retrieve actual format */ - verify_noerr( AudioUnitGetProperty( p_sys->au_unit, + verify_noerr(AudioUnitGetProperty(p_sys->au_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &DeviceFormat, - &i_param_size )); + &i_param_size)); - msg_Dbg( p_aout, STREAM_FORMAT_MSG( "the actual set AU format is " , DeviceFormat ) ); + msg_Dbg(p_aout, STREAM_FORMAT_MSG("the actual set AU format is " , DeviceFormat)); /* Do the last VLC aout setups */ - aout_FormatPrepare( &p_aout->format ); - aout_PacketInit( p_aout, &p_sys->packet, FRAMESIZE ); - aout_VolumeHardInit (p_aout, VolumeSet); + aout_FormatPrepare(fmt); /* set the IOproc callback */ input.inputProc = (AURenderCallback) RenderCallbackAnalog; input.inputProcRefCon = p_aout; - verify_noerr( AudioUnitSetProperty( p_sys->au_unit, + verify_noerr(AudioUnitSetProperty(p_sys->au_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, - 0, &input, sizeof( input ) ) ); - - input.inputProc = (AURenderCallback) RenderCallbackAnalog; - input.inputProcRefCon = p_aout; + 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, + verify_noerr(AudioUnitSetProperty(p_sys->au_unit, kAudioUnitProperty_AudioChannelLayout, - kAudioUnitScope_Input, - 0, &new_layout, sizeof(new_layout) ) ); + kAudioUnitScope_Output, + 0, &new_layout, sizeof(new_layout))); - if( new_layout.mNumberChannelDescriptions > 0 ) - free( new_layout.mChannelDescriptions ); + if (new_layout.mNumberChannelDescriptions > 0) + free(new_layout.mChannelDescriptions); /* AU initiliaze */ - verify_noerr( AudioUnitInitialize(p_sys->au_unit) ); + 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; + AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) / 1000; p_sys->clock_diff += mdate(); - /* Start the AU */ - verify_noerr( AudioOutputUnitStart(p_sys->au_unit) ); + /* setup circular buffer */ + 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; + + /* Set volume for output unit */ + 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 ) +/* + * 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; @@ -613,107 +785,95 @@ static int OpenSPDIF( audio_output_t * p_aout ) /* Hog the device */ AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - i_param_size = sizeof( p_sys->i_hog_pid ); + i_param_size = sizeof(p_sys->i_hog_pid); p_sys->i_hog_pid = getpid() ; - err = AudioObjectSetPropertyData( p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, i_param_size, &p_sys->i_hog_pid ); + err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, i_param_size, &p_sys->i_hog_pid); - if( err != noErr ) - { - msg_Err( p_aout, "failed to set hogmode: [%4.4s]", (char *)&err ); + if (err != noErr) { + msg_Err(p_aout, "failed to set hogmode [%4.4s]", (char *)&err); return false; } AudioObjectPropertyAddress audioDeviceSupportsMixingAddress = { kAudioDevicePropertySupportsMixing , kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - if (AudioObjectHasProperty(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress)) - { + if (AudioObjectHasProperty(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress)) { /* Set mixable to false if we are allowed to */ - err = AudioObjectIsPropertySettable( p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, &b_writeable ); - err = AudioObjectGetPropertyDataSize( p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size ); - err = AudioObjectGetPropertyData( p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size, &b_mix ); + err = AudioObjectIsPropertySettable(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, &b_writeable); + err = AudioObjectGetPropertyDataSize(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size); + err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size, &b_mix); - if( err == noErr && b_writeable ) - { + if (err == noErr && b_writeable) { b_mix = 0; - err = AudioObjectSetPropertyData( p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, i_param_size, &b_mix ); + err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, i_param_size, &b_mix); p_sys->b_changed_mixing = true; } - if( err != noErr ) - { - msg_Err( p_aout, "failed to set mixmode: [%4.4s]", (char *)&err ); + if (err != noErr) { + msg_Err(p_aout, "failed to set mixmode [%4.4s]", (char *)&err); return false; } } /* Get a list of all the streams on this device */ 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 ); + err = AudioObjectGetPropertyDataSize(p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size); + if (err != noErr) { + msg_Err(p_aout, "could not get size of stream description packet [%4.4s]", (char *)&err); return false; } - i_streams = i_param_size / sizeof( AudioStreamID ); - p_streams = (AudioStreamID *)malloc( i_param_size ); - if( p_streams == NULL ) + i_streams = i_param_size / sizeof(AudioStreamID); + p_streams = (AudioStreamID *)malloc(i_param_size); + if (p_streams == NULL) return false; - err = AudioObjectGetPropertyData( p_sys->i_selected_dev, &streamsAddress, 0, NULL, &i_param_size, p_streams ); + 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 ); - free( p_streams ); + if (err != noErr) { + msg_Err(p_aout, "could not fetch stream descriptions [%4.4s]", (char *)&err); + free(p_streams); return false; } AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 }; - for( unsigned i = 0; i < i_streams && p_sys->i_stream_index < 0 ; i++ ) - { + for (unsigned i = 0; i < i_streams && p_sys->i_stream_index < 0 ; i++) { /* Find a stream with a cac3 stream */ AudioStreamRangedDescription *p_format_list = NULL; int i_formats = 0; bool b_digital = false; /* 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 ); + err = AudioObjectGetPropertyDataSize(p_streams[i], &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); continue; } - i_formats = i_param_size / sizeof( AudioStreamRangedDescription ); - p_format_list = (AudioStreamRangedDescription *)malloc( i_param_size ); - if( p_format_list == NULL ) + i_formats = i_param_size / sizeof(AudioStreamRangedDescription); + p_format_list = (AudioStreamRangedDescription *)malloc(i_param_size); + if (p_format_list == NULL) continue; - err = AudioObjectGetPropertyData( p_streams[i], &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 ); + err = AudioObjectGetPropertyData(p_streams[i], &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); continue; } /* Check if one of the supported formats is a digital format */ - for( int j = 0; j < i_formats; j++ ) - { - if( p_format_list[j].mFormat.mFormatID == 'IAC3' || + for (int j = 0; j < i_formats; j++) { + if (p_format_list[j].mFormat.mFormatID == 'IAC3' || p_format_list[j].mFormat.mFormatID == 'iac3' || p_format_list[j].mFormat.mFormatID == kAudioFormat60958AC3 || - p_format_list[j].mFormat.mFormatID == kAudioFormatAC3 ) - { + p_format_list[j].mFormat.mFormatID == kAudioFormatAC3) { b_digital = true; break; } } - if( b_digital ) - { + if (b_digital) { /* if this stream supports a digital (cac3) format, then go set it. */ int i_requested_rate_format = -1; int i_current_rate_format = -1; @@ -722,523 +882,459 @@ static int OpenSPDIF( audio_output_t * p_aout ) p_sys->i_stream_id = p_streams[i]; p_sys->i_stream_index = i; - if( !p_sys->b_revert ) - { + if (!p_sys->b_revert) { AudioObjectPropertyAddress currentPhysicalFormatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; /* Retrieve the original format of this stream first if not done so already */ - 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 ); + 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); continue; } p_sys->b_revert = true; } - for( int j = 0; j < i_formats; j++ ) - { - if( p_format_list[j].mFormat.mFormatID == 'IAC3' || + for (int j = 0; j < i_formats; j++) { + if (p_format_list[j].mFormat.mFormatID == 'IAC3' || p_format_list[j].mFormat.mFormatID == 'iac3' || p_format_list[j].mFormat.mFormatID == kAudioFormat60958AC3 || - p_format_list[j].mFormat.mFormatID == kAudioFormatAC3 ) - { - if( p_format_list[j].mFormat.mSampleRate == p_aout->format.i_rate ) - { + p_format_list[j].mFormat.mFormatID == kAudioFormatAC3) { + if (p_format_list[j].mFormat.mSampleRate == fmt->i_rate) { i_requested_rate_format = j; break; - } - else if( p_format_list[j].mFormat.mSampleRate == p_sys->sfmt_revert.mSampleRate ) - { + } else if (p_format_list[j].mFormat.mSampleRate == p_sys->sfmt_revert.mSampleRate) i_current_rate_format = j; - } - else - { - if( i_backup_rate_format < 0 || p_format_list[j].mFormat.mSampleRate > p_format_list[i_backup_rate_format].mFormat.mSampleRate ) + else { + if (i_backup_rate_format < 0 || p_format_list[j].mFormat.mSampleRate > p_format_list[i_backup_rate_format].mFormat.mSampleRate) i_backup_rate_format = j; } } } - if( i_requested_rate_format >= 0 ) /* We prefer to output at the samplerate of the original audio */ + 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; - else if( i_current_rate_format >= 0 ) /* If not possible, we will try to use the current samplerate of the device */ + 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; - 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) */ + 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) */ } - free( p_format_list ); + free(p_format_list); } - free( p_streams ); + free(p_streams); - msg_Dbg( p_aout, STREAM_FORMAT_MSG( "original stream format: ", p_sys->sfmt_revert ) ); + 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, p_sys->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 ) - p_aout->format.i_format = VLC_CODEC_SPDIFB; + if (p_sys->stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian) + fmt->i_format = VLC_CODEC_SPDIFB; else - p_aout->format.i_format = VLC_CODEC_SPDIFL; - p_aout->format.i_bytes_per_frame = AOUT_SPDIF_SIZE; - p_aout->format.i_frame_length = A52_FRAME_NB; - p_aout->format.i_rate = (unsigned int)p_sys->stream_format.mSampleRate; - aout_FormatPrepare( &p_aout->format ); - aout_PacketInit( p_aout, &p_sys->packet, A52_FRAME_NB ); - aout_VolumeNoneInit( p_aout ); + 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; + p_sys->i_rate = fmt->i_rate; + aout_FormatPrepare(fmt); /* Add IOProc callback */ - err = AudioDeviceCreateIOProcID( p_sys->i_selected_dev, + err = AudioDeviceCreateIOProcID(p_sys->i_selected_dev, (AudioDeviceIOProc)RenderCallbackSPDIF, (void *)p_aout, - &p_sys->i_procID ); - if( err != noErr ) - { - msg_Err( p_aout, "AudioDeviceCreateIOProcID failed: [%4.4s]", (char *)&err ); - aout_PacketDestroy (p_aout); + &p_sys->i_procID); + if (err != noErr) { + 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; + 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 ); - - err = AudioDeviceDestroyIOProcID( p_sys->i_selected_dev, - p_sys->i_procID ); - if( err != noErr ) - { - msg_Err( p_aout, "AudioDeviceDestroyIOProcID failed: [%4.4s]", (char *)&err ); - } - aout_PacketDestroy (p_aout); + err = AudioDeviceStart(p_sys->i_selected_dev, p_sys->i_procID); + if (err != noErr) { + 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, "Failed to destroy process ID [%4.4s]", (char *)&err); + return false; } + /* setup circular buffer */ + TPCircularBufferInit(&p_sys->circular_buffer, 200 * AOUT_SPDIF_SIZE); + return true; } - -/***************************************************************************** - * Close: Close HAL AudioUnit - *****************************************************************************/ -static void Close( vlc_object_t * p_this ) +static void Stop(audio_output_t *p_aout) { - audio_output_t *p_aout = (audio_output_t *)p_this; struct aout_sys_t *p_sys = p_aout->sys; OSStatus err = noErr; UInt32 i_param_size = 0; - if( p_sys->au_unit ) - { - verify_noerr( AudioOutputUnitStop( p_sys->au_unit ) ); - verify_noerr( AudioUnitUninitialize( p_sys->au_unit ) ); - verify_noerr( CloseComponent( p_sys->au_unit ) ); + if (p_sys->au_unit) { + verify_noerr(AudioOutputUnitStop(p_sys->au_unit)); + verify_noerr(AudioUnitUninitialize(p_sys->au_unit)); + verify_noerr(AudioComponentInstanceDispose(p_sys->au_unit)); } - if( p_sys->b_digital ) - { + if (p_sys->b_digital) { /* Stop device */ - err = AudioDeviceStop( p_sys->i_selected_dev, - p_sys->i_procID ); - if( err != noErr ) - { - msg_Err( p_aout, "AudioDeviceStop failed: [%4.4s]", (char *)&err ); - } + err = AudioDeviceStop(p_sys->i_selected_dev, + p_sys->i_procID); + if (err != noErr) + 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 ); - } + err = AudioDeviceDestroyIOProcID(p_sys->i_selected_dev, + p_sys->i_procID); + if (err != noErr) + 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 ); - } + if (p_sys->b_revert) + AudioStreamChangeFormat(p_aout, p_sys->i_stream_id, p_sys->sfmt_revert); - if( p_sys->b_changed_mixing && p_sys->sfmt_revert.mFormatID != kAudioFormat60958AC3 ) - { + if (p_sys->b_changed_mixing && p_sys->sfmt_revert.mFormatID != kAudioFormat60958AC3) { int b_mix; Boolean b_writeable = false; /* Revert mixable to true if we are allowed to */ AudioObjectPropertyAddress audioDeviceSupportsMixingAddress = { kAudioDevicePropertySupportsMixing , kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - err = AudioObjectIsPropertySettable( p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, &b_writeable ); - err = AudioObjectGetPropertyData( p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size, &b_mix ); + err = AudioObjectIsPropertySettable(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, &b_writeable); + err = AudioObjectGetPropertyData(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, &i_param_size, &b_mix); - if( err == noErr && b_writeable ) - { - msg_Dbg( p_aout, "mixable is: %d", b_mix ); + if (err == noErr && b_writeable) { + msg_Dbg(p_aout, "mixable is: %d", b_mix); b_mix = 1; - err = AudioObjectSetPropertyData( p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, i_param_size, &b_mix ); + err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceSupportsMixingAddress, 0, NULL, i_param_size, &b_mix); } - if( err != noErr ) - { - msg_Err( p_aout, "failed to set mixmode: [%4.4s]", (char *)&err ); - } + if (err != noErr) + msg_Err(p_aout, "failed to re-set mixmode [%4.4s]", (char *)&err); } } - AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - err = AudioObjectRemovePropertyListener( kAudioObjectSystemObject, &audioDevicesAddress, HardwareListener, NULL ); - - if( err != noErr ) - { - msg_Err( p_aout, "AudioHardwareRemovePropertyListener failed: [%4.4s]", (char *)&err ); - } - - if( p_sys->i_hog_pid == getpid() ) - { + if (p_sys->i_hog_pid == getpid()) { p_sys->i_hog_pid = -1; - i_param_size = sizeof( p_sys->i_hog_pid ); + i_param_size = sizeof(p_sys->i_hog_pid); AudioObjectPropertyAddress audioDeviceHogModeAddress = { kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - err = AudioObjectSetPropertyData( p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, i_param_size, &p_sys->i_hog_pid ); - if( err != noErr ) msg_Err( p_aout, "Could not release hogmode: [%4.4s]", (char *)&err ); + err = AudioObjectSetPropertyData(p_sys->i_selected_dev, &audioDeviceHogModeAddress, 0, NULL, i_param_size, &p_sys->i_hog_pid); + if (err != noErr) + msg_Err(p_aout, "Failed to release hogmode [%4.4s]", (char *)&err); } - var_DelCallback( p_aout, "audio-device", AudioDeviceCallback, NULL ); + p_sys->i_bytes_per_sample = 0; + p_sys->b_digital = false; - aout_PacketDestroy( p_aout ); - free( p_sys ); + /* clean-up circular buffer */ + TPCircularBufferCleanup(&p_sys->circular_buffer); } -/***************************************************************************** - * Probe: Check which devices the OS has, and add them to our audio-device menu - *****************************************************************************/ -static void Probe( audio_output_t * p_aout ) +#pragma mark - +#pragma mark core interaction + +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); +} + +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; + /* setup local array */ + currentListOfDevices = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + /* Get number of devices */ - AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; - err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &i_param_size); - if( err != noErr ) - { - msg_Err( p_aout, "Could not get number of devices: [%s]", (char *)&err ); - goto error; + AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &propertySize); + if (err != noErr) { + 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; + err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &audioDevicesAddress, 0, NULL, &propertySize, deviceIDs); + if (err != noErr) { + msg_Err(p_aout, "could not get the device IDs [%4.4s]", (char *)&err); + return; } - /* 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 ); - if( err != noErr ) - { - msg_Err( p_aout, "could not get default audio device: [%s]", (char *)&err ); - goto error; - } - p_sys->i_default_dev = devid_def; + AudioObjectPropertyAddress deviceNameAddress = { kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - 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 = { 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 ) 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 ) 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..." ); - free( psz_name ); + 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 + RegisterAudioStreamsCallback(p_aout, deviceIDs[i]); - free( psz_name); + 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 ) - { - var_Change( p_aout, "audio-device", VLC_VAR_SETDEFAULT, &val, NULL ); - var_Set( p_aout, "audio-device", val ); - } + CFIndex count = 0; + if (p_sys->device_list) + count = CFArrayGetCount(p_sys->device_list); - /* 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 ) - goto error; + 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); - free( p_devices ); - return; + 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); -error: - msg_Warn( p_aout, "audio device already in use" ); - free( p_devices ); - return; + ReportDevice(p_aout, 0, _("System Sound Output Device")); + + 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; - /* 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 */ + 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; - } + struct aout_sys_t *p_sys = p_aout->sys; + OSStatus ostatus; - i_formats = i_param_size / sizeof( AudioStreamRangedDescription ); - msg_Dbg( p_aout, "found %i stream formats", i_formats ); + if(p_sys->b_digital) + return VLC_EGENERIC; - p_format_list = (AudioStreamRangedDescription *)malloc( i_param_size ); - if( p_format_list == NULL ) - return false; - - 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++ ) - { - msg_Dbg( p_aout, STREAM_FORMAT_MSG( "supported format: ", p_format_list[i].mFormat ) ); + 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; +#pragma mark - +#pragma mark actual playback - AudioObjectPropertyAddress physicalFormatAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; +static void Play(audio_output_t * p_aout, block_t * p_block) +{ + struct aout_sys_t *p_sys = p_aout->sys; - struct { vlc_mutex_t lock; vlc_cond_t cond; } w; + 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; + } - msg_Dbg( p_aout, STREAM_FORMAT_MSG( "setting stream format: ", change_format ) ); + /* Do the channel reordering */ + 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, + p_sys->chan_table, + VLC_CODEC_FL32); + } - /* Condition because SetProperty is asynchronious */ - vlc_cond_init( &w.cond ); - vlc_mutex_init( &w.lock ); - vlc_mutex_lock( &w.lock ); + /* move data to buffer */ + if (unlikely(!TPCircularBufferProduceBytes(&p_sys->circular_buffer, p_block->p_buffer, p_block->i_buffer))) + msg_Warn(p_aout, "dropped buffer"); - /* 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; + if (!p_sys->i_bytes_per_sample) + p_sys->i_bytes_per_sample = p_block->i_buffer / p_block->i_nb_samples; } - /* 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; - } + block_Release(p_block); +} - /* 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; +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( vlc_cond_timedwait( &w.cond, &w.lock, timeout ) ) - { - msg_Dbg( p_aout, "reached timeout" ); + if (p_aout->sys->b_digital) { + if (pause) + AudioDeviceStop(p_sys->i_selected_dev, p_sys->i_procID); + else + AudioDeviceStart(p_sys->i_selected_dev, p_sys->i_procID); + } else { + if (pause) + AudioOutputUnitStop(p_sys->au_unit); + else + AudioOutputUnitStart(p_sys->au_unit); + } +} + +static void Flush(audio_output_t *p_aout, bool wait) +{ + struct aout_sys_t *p_sys = p_aout->sys; + + if (wait) { + int32_t availableBytes; + vlc_mutex_lock(&p_sys->lock); + TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes); + while (availableBytes > 0) { + vlc_cond_wait(&p_sys->cond, &p_sys->lock); + TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes); } + vlc_mutex_unlock(&p_sys->lock); - i_param_size = sizeof( AudioStreamBasicDescription ); - err = AudioObjectGetPropertyData( i_stream_id, &physicalFormatAddress, 0, NULL, &i_param_size, &actual_format ); + } else { + p_sys->b_got_first_sample = false; - 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 */ + /* flush circular buffer */ + AudioOutputUnitStop(p_aout->sys->au_unit); + TPCircularBufferClear(&p_aout->sys->circular_buffer); } +} - /* 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; - } +static int TimeGet(audio_output_t *p_aout, mtime_t *delay) +{ + struct aout_sys_t * p_sys = p_aout->sys; - /* Destroy the lock and condition */ - vlc_mutex_unlock( &w.lock ); - vlc_mutex_destroy( &w.lock ); - vlc_cond_destroy( &w.cond ); + if (!p_sys->i_bytes_per_sample) + return -1; - return true; + 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; } /***************************************************************************** @@ -1247,230 +1343,380 @@ static int AudioStreamChangeFormat( audio_output_t *p_aout, AudioStreamID i_stre * Don't print anything during normal playback, calling blocking function from * this callback is not allowed. *****************************************************************************/ -static OSStatus RenderCallbackAnalog( vlc_object_t *_p_aout, - AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, - unsigned int inBusNumber, - unsigned int inNumberFrames, - AudioBufferList *ioData ) -{ - AudioTimeStamp host_time; - mtime_t current_date = 0; - uint32_t i_mData_bytes = 0; - - audio_output_t * p_aout = (audio_output_t *)_p_aout; - struct aout_sys_t * p_sys = p_aout->sys; - +static OSStatus RenderCallbackAnalog(vlc_object_t *p_obj, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) { VLC_UNUSED(ioActionFlags); + VLC_UNUSED(inTimeStamp); VLC_UNUSED(inBusNumber); VLC_UNUSED(inNumberFrames); - host_time.mFlags = kAudioTimeStampHostTimeValid; - AudioDeviceTranslateTime( p_sys->i_selected_dev, inTimeStamp, &host_time ); + audio_output_t * p_aout = (audio_output_t *)p_obj; + struct aout_sys_t * p_sys = p_aout->sys; - /* Check for the difference between the Device clock and mdate */ - p_sys->clock_diff = - (mtime_t) - AudioConvertHostTimeToNanos( AudioGetCurrentHostTime() ) / 1000; - p_sys->clock_diff += mdate(); + int bytesRequested = ioData->mBuffers[0].mDataByteSize; + Float32 *targetBuffer = (Float32*)ioData->mBuffers[0].mData; - current_date = p_sys->clock_diff + - AudioConvertHostTimeToNanos( host_time.mHostTime ) / 1000; - //- ((mtime_t) 1000000 / p_aout->format.i_rate * 31 ); // 31 = Latency in Frames. retrieve somewhere + vlc_mutex_lock(&p_sys->lock); + /* Pull audio from buffer */ + int32_t availableBytes; + Float32 *buffer = TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes); - if( ioData == NULL && ioData->mNumberBuffers < 1 ) - { - msg_Err( p_aout, "no iodata or buffers"); - return 0; - } - if( ioData->mNumberBuffers > 1 ) - msg_Err( p_aout, "well this is weird. seems like there is more than one buffer..." ); - - - if( p_sys->i_total_bytes > 0 ) - { - i_mData_bytes = __MIN( p_sys->i_total_bytes - p_sys->i_read_bytes, ioData->mBuffers[0].mDataByteSize ); - vlc_memcpy( ioData->mBuffers[0].mData, - &p_sys->p_remainder_buffer[p_sys->i_read_bytes], - i_mData_bytes ); - p_sys->i_read_bytes += i_mData_bytes; - current_date += (mtime_t) ( (mtime_t) 1000000 / p_aout->format.i_rate ) * - ( i_mData_bytes / 4 / aout_FormatNbChannels( &p_aout->format ) ); // 4 is fl32 specific - - if( p_sys->i_read_bytes >= p_sys->i_total_bytes ) - p_sys->i_read_bytes = p_sys->i_total_bytes = 0; + /* check if we have enough data */ + if (!availableBytes) { + /* return an empty buffer so silence is played until we have data */ + memset(targetBuffer, 0, ioData->mBuffers[0].mDataByteSize); + } else { + int32_t bytesToCopy = __MIN(bytesRequested, availableBytes); + + memcpy(targetBuffer, buffer, bytesToCopy); + TPCircularBufferConsume(&p_sys->circular_buffer, bytesToCopy); + ioData->mBuffers[0].mDataByteSize = bytesToCopy; } - while( i_mData_bytes < ioData->mBuffers[0].mDataByteSize ) - { - /* We don't have enough data yet */ - aout_buffer_t * p_buffer; - p_buffer = aout_PacketNext( p_aout, current_date ); + vlc_cond_signal(&p_sys->cond); + vlc_mutex_unlock(&p_sys->lock); - if( p_buffer != NULL ) - { - uint32_t i_second_mData_bytes = __MIN( p_buffer->i_buffer, ioData->mBuffers[0].mDataByteSize - i_mData_bytes ); - - vlc_memcpy( (uint8_t *)ioData->mBuffers[0].mData + i_mData_bytes, - p_buffer->p_buffer, i_second_mData_bytes ); - i_mData_bytes += i_second_mData_bytes; - - if( i_mData_bytes >= ioData->mBuffers[0].mDataByteSize ) - { - p_sys->i_total_bytes = p_buffer->i_buffer - i_second_mData_bytes; - vlc_memcpy( p_sys->p_remainder_buffer, - &p_buffer->p_buffer[i_second_mData_bytes], - p_sys->i_total_bytes ); - aout_BufferFree( p_buffer ); - break; - } - else - { - /* update current_date */ - current_date += (mtime_t) ( (mtime_t) 1000000 / p_aout->format.i_rate ) * - ( i_second_mData_bytes / 4 / aout_FormatNbChannels( &p_aout->format ) ); // 4 is fl32 specific - } - aout_BufferFree( p_buffer ); - } - else - { - vlc_memset( (uint8_t *)ioData->mBuffers[0].mData +i_mData_bytes, - 0,ioData->mBuffers[0].mDataByteSize - i_mData_bytes ); - i_mData_bytes += ioData->mBuffers[0].mDataByteSize - i_mData_bytes; - } - } - return( noErr ); + 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, AudioBufferList * outOutputData, const AudioTimeStamp * inOutputTime, - void * threadGlobals ) + void * threadGlobals) { - aout_buffer_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) { + /* return an empty buffer so silence is played until we have data */ + memset(targetBuffer, 0, outOutputData->mBuffers[p_sys->i_stream_index].mDataByteSize); + } else { + int32_t bytesToCopy = __MIN(bytesRequested, availableBytes); - /* move data into output data buffer */ - vlc_memcpy( BUFFER.mData, p_buffer->p_buffer, p_buffer->i_buffer ); - aout_BufferFree( p_buffer ); + memcpy(targetBuffer, buffer, bytesToCopy); + TPCircularBufferConsume(&p_sys->circular_buffer, bytesToCopy); + outOutputData->mBuffers[p_sys->i_stream_index].mDataByteSize = bytesToCopy; } - else - { - vlc_memset( BUFFER.mData, 0, BUFFER.mDataByteSize ); - } -#undef BUFFER - return( noErr ); + vlc_cond_signal(&p_sys->cond); + vlc_mutex_unlock(&p_sys->lock); + + return noErr; } -/***************************************************************************** +#pragma mark - +#pragma mark Stream / Hardware Listeners + +/* * HardwareListener: Warns us of changes in the list of registered devices - *****************************************************************************/ -static OSStatus HardwareListener( AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void*inClientData) + */ +static OSStatus HardwareListener(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); - for ( unsigned int i = 0; i < inNumberAddresses; i++ ) - { - if( inAddresses[i].mSelector == kAudioHardwarePropertyDevices ) - { - /* something changed in the list of devices */ - /* We trigger the audio-device's aout_ChannelsRestart callback */ - var_TriggerCallback( p_aout, "audio-device" ); - var_Destroy( p_aout, "audio-device" ); + if (!p_aout) + return -1; + + for (unsigned int i = 0; i < inNumberAddresses; i++) { + switch (inAddresses[i].mSelector) { + case kAudioHardwarePropertyDevices: + msg_Dbg(p_aout, "audio device configuration changed, resetting cache"); + break; + + case kAudioDevicePropertyDeviceIsAlive: + msg_Warn(p_aout, "audio device died, resetting aout"); + break; + + case kAudioStreamPropertyAvailablePhysicalFormats: + msg_Dbg(p_aout, "available physical formats for audio device changed, resetting aout"); + break; + + default: + msg_Warn(p_aout, "device reset for unknown reason (%i)", inAddresses[i].mSelector); + break; } } - return( err ); + RebuildDeviceList(p_aout); + aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT); + + return err; } -/***************************************************************************** - * StreamListener - *****************************************************************************/ -static OSStatus StreamListener( AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void*inClientData) +/* + * StreamListener: check whether the device's physical format changes on-the-fly (unlikely) + */ +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; VLC_UNUSED(inObjectID); - for( unsigned int i = 0; i < inNumberAddresses; i++ ) - { - if( inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat ) - { - vlc_mutex_lock( &w->lock ); - vlc_cond_signal( &w->cond ); - vlc_mutex_unlock( &w->lock ); + for (unsigned int i = 0; i < inNumberAddresses; i++) { + if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat) { + vlc_mutex_lock(&w->lock); + vlc_cond_signal(&w->cond); + vlc_mutex_unlock(&w->lock); break; } } - return( err ); + 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 RegisterAudioStreamsCallback(audio_output_t *p_aout, AudioDeviceID i_dev_id) { - 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 [%4.4s] (%i)", (char *)&err, (int32_t)err); + return VLC_EGENERIC; + } + + 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 VLC_EGENERIC; + } + + for (int i = 0; i < i_streams; i++) { + /* get notified when physical formats change */ + AudioObjectPropertyAddress physicalFormatsAddress = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeGlobal, 0 }; + err = AudioObjectAddPropertyListener(p_streams[i], &physicalFormatsAddress, HardwareListener, (void *)p_aout); + if (err != noErr) { + // nope just means that we already have a callback + if (err == kAudioHardwareIllegalOperationError) { + msg_Dbg(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_Warn(p_aout, "could not set audio stream formats property callback on stream id %i [%4.4s]", p_streams[i], + (char *)&err); + } + } + } + + free(p_streams); + return VLC_SUCCESS; } +/* + * AudioDeviceHasOutput: Checks if the device is actually an output device + */ +static int AudioDeviceHasOutput(AudioDeviceID i_dev_id) +{ + UInt32 dataSize = 0; + OSStatus status; -/***************************************************************************** - * VolumeSet: Implements pf_volume_set(). Update the CoreAudio AU volume immediately. - *****************************************************************************/ -static int VolumeSet( audio_output_t * p_aout, float volume, bool mute ) + AudioObjectPropertyAddress streamsAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; + status = AudioObjectGetPropertyDataSize(i_dev_id, &streamsAddress, 0, NULL, &dataSize); + + if (dataSize == 0 || status != noErr) + return FALSE; + + return TRUE; +} + +/* + * AudioDeviceSupportsDigital: Checks if device supports raw bitstreams + */ +static int AudioDeviceSupportsDigital(audio_output_t *p_aout, AudioDeviceID i_dev_id) { - struct aout_sys_t *p_sys = p_aout->sys; - OSStatus ostatus; + OSStatus err = noErr; + UInt32 i_param_size = 0; + AudioStreamID *p_streams = NULL; + int i_streams = 0; + bool b_return = false; - if( mute ) - volume = 0.0; - else - volume = volume * volume * volume; // cubic mapping from output.c + /* 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; + } - /* Set volume for output unit */ - ostatus = AudioUnitSetParameter( p_sys->au_unit, - kHALOutputParam_Volume, - kAudioUnitScope_Global, - 0, - volume, - 0 ); + i_streams = i_param_size / sizeof(AudioStreamID); + p_streams = (AudioStreamID *)malloc(i_param_size); + if (p_streams == NULL) + return VLC_ENOMEM; - return ostatus; + 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; +} + +/* + * AudioStreamSupportsDigital: Checks if audio stream is compatible with raw bitstreams + */ +static int AudioStreamSupportsDigital(audio_output_t *p_aout, AudioStreamID i_stream_id) +{ + 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 [%4.4s] (%i)", (char *)&err, (int32_t)err); + return false; + } + + i_formats = i_param_size / sizeof(AudioStreamRangedDescription); + msg_Dbg(p_aout, "found %i stream formats for stream id %i", i_formats, i_stream_id); + + p_format_list = (AudioStreamRangedDescription *)malloc(i_param_size); + if (p_format_list == NULL) + return false; + + 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; + } + + 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; +} + +/* + * AudioStreamChangeFormat: switch stream format based on the provided description + */ +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 }; + + 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; }