Home | History | Annotate | Download | only in mac
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "media/audio/mac/audio_auhal_mac.h"
      6 
      7 #include <CoreServices/CoreServices.h>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/debug/trace_event.h"
     11 #include "base/logging.h"
     12 #include "base/mac/mac_logging.h"
     13 #include "media/audio/mac/audio_manager_mac.h"
     14 #include "media/base/audio_pull_fifo.h"
     15 
     16 namespace media {
     17 
     18 static void ZeroBufferList(AudioBufferList* buffer_list) {
     19   for (size_t i = 0; i < buffer_list->mNumberBuffers; ++i) {
     20     memset(buffer_list->mBuffers[i].mData,
     21            0,
     22            buffer_list->mBuffers[i].mDataByteSize);
     23   }
     24 }
     25 
     26 static void WrapBufferList(AudioBufferList* buffer_list,
     27                            AudioBus* bus,
     28                            int frames) {
     29   DCHECK(buffer_list);
     30   DCHECK(bus);
     31   const int channels = bus->channels();
     32   const int buffer_list_channels = buffer_list->mNumberBuffers;
     33   CHECK_EQ(channels, buffer_list_channels);
     34 
     35   // Copy pointers from AudioBufferList.
     36   for (int i = 0; i < channels; ++i) {
     37     bus->SetChannelData(
     38         i, static_cast<float*>(buffer_list->mBuffers[i].mData));
     39   }
     40 
     41   // Finally set the actual length.
     42   bus->set_frames(frames);
     43 }
     44 
     45 AUHALStream::AUHALStream(
     46     AudioManagerMac* manager,
     47     const AudioParameters& params,
     48     AudioDeviceID device)
     49     : manager_(manager),
     50       params_(params),
     51       input_channels_(params_.input_channels()),
     52       output_channels_(params_.channels()),
     53       number_of_frames_(params_.frames_per_buffer()),
     54       source_(NULL),
     55       device_(device),
     56       audio_unit_(0),
     57       volume_(1),
     58       hardware_latency_frames_(0),
     59       stopped_(false),
     60       input_buffer_list_(NULL),
     61       current_hardware_pending_bytes_(0) {
     62   // We must have a manager.
     63   DCHECK(manager_);
     64 
     65   VLOG(1) << "AUHALStream::AUHALStream()";
     66   VLOG(1) << "Device: " << device;
     67   VLOG(1) << "Input channels: " << input_channels_;
     68   VLOG(1) << "Output channels: " << output_channels_;
     69   VLOG(1) << "Sample rate: " << params_.sample_rate();
     70   VLOG(1) << "Buffer size: " << number_of_frames_;
     71 }
     72 
     73 AUHALStream::~AUHALStream() {
     74 }
     75 
     76 bool AUHALStream::Open() {
     77   // Get the total number of input and output channels that the
     78   // hardware supports.
     79   int device_input_channels;
     80   bool got_input_channels = AudioManagerMac::GetDeviceChannels(
     81       device_,
     82       kAudioDevicePropertyScopeInput,
     83       &device_input_channels);
     84 
     85   int device_output_channels;
     86   bool got_output_channels = AudioManagerMac::GetDeviceChannels(
     87       device_,
     88       kAudioDevicePropertyScopeOutput,
     89       &device_output_channels);
     90 
     91   // Sanity check the requested I/O channels.
     92   if (!got_input_channels ||
     93       input_channels_ < 0 || input_channels_ > device_input_channels) {
     94     LOG(ERROR) << "AudioDevice does not support requested input channels.";
     95     return false;
     96   }
     97 
     98   if (!got_output_channels ||
     99       output_channels_ <= 0 || output_channels_ > device_output_channels) {
    100     LOG(ERROR) << "AudioDevice does not support requested output channels.";
    101     return false;
    102   }
    103 
    104   // The requested sample-rate must match the hardware sample-rate.
    105   int sample_rate = AudioManagerMac::HardwareSampleRateForDevice(device_);
    106 
    107   if (sample_rate != params_.sample_rate()) {
    108     LOG(ERROR) << "Requested sample-rate: " << params_.sample_rate()
    109                << " must match the hardware sample-rate: " << sample_rate;
    110     return false;
    111   }
    112 
    113   CreateIOBusses();
    114 
    115   bool configured = ConfigureAUHAL();
    116   if (configured)
    117     hardware_latency_frames_ = GetHardwareLatency();
    118 
    119   return configured;
    120 }
    121 
    122 void AUHALStream::Close() {
    123   if (input_buffer_list_) {
    124     input_buffer_list_storage_.reset();
    125     input_buffer_list_ = NULL;
    126     input_bus_.reset(NULL);
    127     output_bus_.reset(NULL);
    128   }
    129 
    130   if (audio_unit_) {
    131     OSStatus result = AudioUnitUninitialize(audio_unit_);
    132     OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
    133         << "AudioUnitUninitialize() failed.";
    134     result = AudioComponentInstanceDispose(audio_unit_);
    135     OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
    136         << "AudioComponentInstanceDispose() failed.";
    137   }
    138 
    139   // Inform the audio manager that we have been closed. This will cause our
    140   // destruction.
    141   manager_->ReleaseOutputStream(this);
    142 }
    143 
    144 void AUHALStream::Start(AudioSourceCallback* callback) {
    145   DCHECK(callback);
    146   if (!audio_unit_) {
    147     DLOG(ERROR) << "Open() has not been called successfully";
    148     return;
    149   }
    150 
    151   stopped_ = false;
    152   audio_fifo_.reset();
    153   {
    154     base::AutoLock auto_lock(source_lock_);
    155     source_ = callback;
    156   }
    157 
    158   OSStatus result = AudioOutputUnitStart(audio_unit_);
    159   if (result == noErr)
    160     return;
    161 
    162   Stop();
    163   OSSTATUS_DLOG(ERROR, result) << "AudioOutputUnitStart() failed.";
    164   callback->OnError(this);
    165 }
    166 
    167 void AUHALStream::Stop() {
    168   if (stopped_)
    169     return;
    170 
    171   OSStatus result = AudioOutputUnitStop(audio_unit_);
    172   OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
    173       << "AudioOutputUnitStop() failed.";
    174   if (result != noErr)
    175     source_->OnError(this);
    176 
    177   base::AutoLock auto_lock(source_lock_);
    178   source_ = NULL;
    179   stopped_ = true;
    180 }
    181 
    182 void AUHALStream::SetVolume(double volume) {
    183   volume_ = static_cast<float>(volume);
    184 }
    185 
    186 void AUHALStream::GetVolume(double* volume) {
    187   *volume = volume_;
    188 }
    189 
    190 // Pulls on our provider to get rendered audio stream.
    191 // Note to future hackers of this function: Do not add locks which can
    192 // be contended in the middle of stream processing here (starting and stopping
    193 // the stream are ok) because this is running on a real-time thread.
    194 OSStatus AUHALStream::Render(
    195     AudioUnitRenderActionFlags* flags,
    196     const AudioTimeStamp* output_time_stamp,
    197     UInt32 bus_number,
    198     UInt32 number_of_frames,
    199     AudioBufferList* io_data) {
    200   TRACE_EVENT0("audio", "AUHALStream::Render");
    201 
    202   // If the stream parameters change for any reason, we need to insert a FIFO
    203   // since the OnMoreData() pipeline can't handle frame size changes.  Generally
    204   // this is a temporary situation which can occur after a device change has
    205   // occurred but the AudioManager hasn't received the notification yet.
    206   if (number_of_frames != number_of_frames_) {
    207     // Create a FIFO on the fly to handle any discrepancies in callback rates.
    208     if (!audio_fifo_) {
    209       VLOG(1) << "Audio frame size change detected; adding FIFO to compensate.";
    210       audio_fifo_.reset(new AudioPullFifo(
    211           output_channels_,
    212           number_of_frames_,
    213           base::Bind(&AUHALStream::ProvideInput, base::Unretained(this))));
    214     }
    215 
    216     // Synchronous IO is not supported in this state.
    217     if (input_channels_ > 0)
    218       input_bus_->Zero();
    219   } else {
    220     if (input_channels_ > 0 && input_buffer_list_) {
    221       // Get the input data.  |input_buffer_list_| is wrapped
    222       // to point to the data allocated in |input_bus_|.
    223       OSStatus result = AudioUnitRender(audio_unit_,
    224                                         flags,
    225                                         output_time_stamp,
    226                                         1,
    227                                         number_of_frames,
    228                                         input_buffer_list_);
    229       if (result != noErr)
    230         ZeroBufferList(input_buffer_list_);
    231     }
    232   }
    233 
    234   // Make |output_bus_| wrap the output AudioBufferList.
    235   WrapBufferList(io_data, output_bus_.get(), number_of_frames);
    236 
    237   // Update the playout latency.
    238   const double playout_latency_frames = GetPlayoutLatency(output_time_stamp);
    239   current_hardware_pending_bytes_ = static_cast<uint32>(
    240       (playout_latency_frames + 0.5) * params_.GetBytesPerFrame());
    241 
    242   if (audio_fifo_)
    243     audio_fifo_->Consume(output_bus_.get(), output_bus_->frames());
    244   else
    245     ProvideInput(0, output_bus_.get());
    246 
    247   return noErr;
    248 }
    249 
    250 void AUHALStream::ProvideInput(int frame_delay, AudioBus* dest) {
    251   base::AutoLock auto_lock(source_lock_);
    252   if (!source_) {
    253     dest->Zero();
    254     return;
    255   }
    256 
    257   // Supply the input data and render the output data.
    258   source_->OnMoreIOData(
    259       input_bus_.get(),
    260       dest,
    261       AudioBuffersState(0,
    262                         current_hardware_pending_bytes_ +
    263                             frame_delay * params_.GetBytesPerFrame()));
    264   dest->Scale(volume_);
    265 }
    266 
    267 // AUHAL callback.
    268 OSStatus AUHALStream::InputProc(
    269     void* user_data,
    270     AudioUnitRenderActionFlags* flags,
    271     const AudioTimeStamp* output_time_stamp,
    272     UInt32 bus_number,
    273     UInt32 number_of_frames,
    274     AudioBufferList* io_data) {
    275   // Dispatch to our class method.
    276   AUHALStream* audio_output =
    277       static_cast<AUHALStream*>(user_data);
    278   if (!audio_output)
    279     return -1;
    280 
    281   return audio_output->Render(
    282       flags,
    283       output_time_stamp,
    284       bus_number,
    285       number_of_frames,
    286       io_data);
    287 }
    288 
    289 double AUHALStream::GetHardwareLatency() {
    290   if (!audio_unit_ || device_ == kAudioObjectUnknown) {
    291     DLOG(WARNING) << "AudioUnit is NULL or device ID is unknown";
    292     return 0.0;
    293   }
    294 
    295   // Get audio unit latency.
    296   Float64 audio_unit_latency_sec = 0.0;
    297   UInt32 size = sizeof(audio_unit_latency_sec);
    298   OSStatus result = AudioUnitGetProperty(
    299       audio_unit_,
    300       kAudioUnitProperty_Latency,
    301       kAudioUnitScope_Global,
    302       0,
    303       &audio_unit_latency_sec,
    304       &size);
    305   if (result != noErr) {
    306     OSSTATUS_DLOG(WARNING, result) << "Could not get AudioUnit latency";
    307     return 0.0;
    308   }
    309 
    310   // Get output audio device latency.
    311   static const AudioObjectPropertyAddress property_address = {
    312     kAudioDevicePropertyLatency,
    313     kAudioDevicePropertyScopeOutput,
    314     kAudioObjectPropertyElementMaster
    315   };
    316 
    317   UInt32 device_latency_frames = 0;
    318   size = sizeof(device_latency_frames);
    319   result = AudioObjectGetPropertyData(
    320       device_,
    321       &property_address,
    322       0,
    323       NULL,
    324       &size,
    325       &device_latency_frames);
    326   if (result != noErr) {
    327     OSSTATUS_DLOG(WARNING, result) << "Could not get audio device latency";
    328     return 0.0;
    329   }
    330 
    331   return static_cast<double>((audio_unit_latency_sec *
    332       output_format_.mSampleRate) + device_latency_frames);
    333 }
    334 
    335 double AUHALStream::GetPlayoutLatency(
    336     const AudioTimeStamp* output_time_stamp) {
    337   // Ensure mHostTime is valid.
    338   if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
    339     return 0;
    340 
    341   // Get the delay between the moment getting the callback and the scheduled
    342   // time stamp that tells when the data is going to be played out.
    343   UInt64 output_time_ns = AudioConvertHostTimeToNanos(
    344       output_time_stamp->mHostTime);
    345   UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
    346 
    347   // Prevent overflow leading to huge delay information; occurs regularly on
    348   // the bots, probably less so in the wild.
    349   if (now_ns > output_time_ns)
    350     return 0;
    351 
    352   double delay_frames = static_cast<double>
    353       (1e-9 * (output_time_ns - now_ns) * output_format_.mSampleRate);
    354 
    355   return (delay_frames + hardware_latency_frames_);
    356 }
    357 
    358 void AUHALStream::CreateIOBusses() {
    359   if (input_channels_ > 0) {
    360     // Allocate storage for the AudioBufferList used for the
    361     // input data from the input AudioUnit.
    362     // We allocate enough space for with one AudioBuffer per channel.
    363     size_t buffer_list_size = offsetof(AudioBufferList, mBuffers[0]) +
    364         (sizeof(AudioBuffer) * input_channels_);
    365     input_buffer_list_storage_.reset(new uint8[buffer_list_size]);
    366 
    367     input_buffer_list_ =
    368         reinterpret_cast<AudioBufferList*>(input_buffer_list_storage_.get());
    369     input_buffer_list_->mNumberBuffers = input_channels_;
    370 
    371     // |input_bus_| allocates the storage for the PCM input data.
    372     input_bus_ = AudioBus::Create(input_channels_, number_of_frames_);
    373 
    374     // Make the AudioBufferList point to the memory in |input_bus_|.
    375     UInt32 buffer_size_bytes = input_bus_->frames() * sizeof(Float32);
    376     for (size_t i = 0; i < input_buffer_list_->mNumberBuffers; ++i) {
    377       input_buffer_list_->mBuffers[i].mNumberChannels = 1;
    378       input_buffer_list_->mBuffers[i].mDataByteSize = buffer_size_bytes;
    379       input_buffer_list_->mBuffers[i].mData = input_bus_->channel(i);
    380     }
    381   }
    382 
    383   // The output bus will wrap the AudioBufferList given to us in
    384   // the Render() callback.
    385   DCHECK_GT(output_channels_, 0);
    386   output_bus_ = AudioBus::CreateWrapper(output_channels_);
    387 }
    388 
    389 bool AUHALStream::EnableIO(bool enable, UInt32 scope) {
    390   // See Apple technote for details about the EnableIO property.
    391   // Note that we use bus 1 for input and bus 0 for output:
    392   // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
    393   UInt32 enable_IO = enable ? 1 : 0;
    394   OSStatus result = AudioUnitSetProperty(
    395       audio_unit_,
    396       kAudioOutputUnitProperty_EnableIO,
    397       scope,
    398       (scope == kAudioUnitScope_Input) ? 1 : 0,
    399       &enable_IO,
    400       sizeof(enable_IO));
    401   return (result == noErr);
    402 }
    403 
    404 bool AUHALStream::SetStreamFormat(
    405     AudioStreamBasicDescription* desc,
    406     int channels,
    407     UInt32 scope,
    408     UInt32 element) {
    409   DCHECK(desc);
    410   AudioStreamBasicDescription& format = *desc;
    411 
    412   format.mSampleRate = params_.sample_rate();
    413   format.mFormatID = kAudioFormatLinearPCM;
    414   format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked |
    415       kLinearPCMFormatFlagIsNonInterleaved;
    416   format.mBytesPerPacket = sizeof(Float32);
    417   format.mFramesPerPacket = 1;
    418   format.mBytesPerFrame = sizeof(Float32);
    419   format.mChannelsPerFrame = channels;
    420   format.mBitsPerChannel = 32;
    421   format.mReserved = 0;
    422 
    423   OSStatus result = AudioUnitSetProperty(
    424       audio_unit_,
    425       kAudioUnitProperty_StreamFormat,
    426       scope,
    427       element,
    428       &format,
    429       sizeof(format));
    430   return (result == noErr);
    431 }
    432 
    433 bool AUHALStream::ConfigureAUHAL() {
    434   if (device_ == kAudioObjectUnknown ||
    435       (input_channels_ == 0 && output_channels_ == 0))
    436     return false;
    437 
    438   AudioComponentDescription desc = {
    439       kAudioUnitType_Output,
    440       kAudioUnitSubType_HALOutput,
    441       kAudioUnitManufacturer_Apple,
    442       0,
    443       0
    444   };
    445   AudioComponent comp = AudioComponentFindNext(0, &desc);
    446   if (!comp)
    447     return false;
    448 
    449   OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
    450   if (result != noErr) {
    451     OSSTATUS_DLOG(ERROR, result) << "AudioComponentInstanceNew() failed.";
    452     return false;
    453   }
    454 
    455   // Enable input and output as appropriate.
    456   if (!EnableIO(input_channels_ > 0, kAudioUnitScope_Input))
    457     return false;
    458   if (!EnableIO(output_channels_ > 0, kAudioUnitScope_Output))
    459     return false;
    460 
    461   // Set the device to be used with the AUHAL AudioUnit.
    462   result = AudioUnitSetProperty(
    463       audio_unit_,
    464       kAudioOutputUnitProperty_CurrentDevice,
    465       kAudioUnitScope_Global,
    466       0,
    467       &device_,
    468       sizeof(AudioDeviceID));
    469   if (result != noErr)
    470     return false;
    471 
    472   // Set stream formats.
    473   // See Apple's tech note for details on the peculiar way that
    474   // inputs and outputs are handled in the AUHAL concerning scope and bus
    475   // (element) numbers:
    476   // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
    477 
    478   if (input_channels_ > 0) {
    479     if (!SetStreamFormat(&input_format_,
    480                          input_channels_,
    481                          kAudioUnitScope_Output,
    482                          1))
    483       return false;
    484   }
    485 
    486   if (output_channels_ > 0) {
    487     if (!SetStreamFormat(&output_format_,
    488                          output_channels_,
    489                          kAudioUnitScope_Input,
    490                          0))
    491       return false;
    492   }
    493 
    494   // Set the buffer frame size.
    495   // WARNING: Setting this value changes the frame size for all audio units in
    496   // the current process.  It's imperative that the input and output frame sizes
    497   // be the same as the frames_per_buffer() returned by
    498   // GetDefaultOutputStreamParameters().
    499   // See http://crbug.com/154352 for details.
    500   UInt32 buffer_size = number_of_frames_;
    501   result = AudioUnitSetProperty(
    502       audio_unit_,
    503       kAudioDevicePropertyBufferFrameSize,
    504       kAudioUnitScope_Output,
    505       0,
    506       &buffer_size,
    507       sizeof(buffer_size));
    508   if (result != noErr) {
    509     OSSTATUS_DLOG(ERROR, result)
    510         << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
    511     return false;
    512   }
    513 
    514   // Setup callback.
    515   AURenderCallbackStruct callback;
    516   callback.inputProc = InputProc;
    517   callback.inputProcRefCon = this;
    518   result = AudioUnitSetProperty(
    519       audio_unit_,
    520       kAudioUnitProperty_SetRenderCallback,
    521       kAudioUnitScope_Input,
    522       0,
    523       &callback,
    524       sizeof(callback));
    525   if (result != noErr)
    526     return false;
    527 
    528   result = AudioUnitInitialize(audio_unit_);
    529   if (result != noErr) {
    530     OSSTATUS_DLOG(ERROR, result) << "AudioUnitInitialize() failed.";
    531     return false;
    532   }
    533 
    534   return true;
    535 }
    536 
    537 }  // namespace media
    538