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