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