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