audio_auhal_mac.cc revision f2477e01787aa58f445919b809d89e252beef54f
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
16namespace media {
17
18static 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
26static 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
45AUHALStream::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
73AUHALStream::~AUHALStream() {
74}
75
76bool 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
122void 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
144void 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
167void 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
182void AUHALStream::SetVolume(double volume) {
183  volume_ = static_cast<float>(volume);
184}
185
186void 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.
194OSStatus 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
250void 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.
268OSStatus 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
289double 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
335double 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
358void 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
389bool 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
404bool 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
433bool 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