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.
5 #include "media/audio/mac/audio_auhal_mac.h"
7 #include <CoreServices/CoreServices.h>
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"
21 static void ZeroBufferList(AudioBufferList* buffer_list) {
22 for (size_t i = 0; i < buffer_list->mNumberBuffers; ++i) {
23 memset(buffer_list->mBuffers[i].mData,
25 buffer_list->mBuffers[i].mDataByteSize);
29 static void WrapBufferList(AudioBufferList* buffer_list,
34 const int channels = bus->channels();
35 const int buffer_list_channels = buffer_list->mNumberBuffers;
36 CHECK_EQ(channels, buffer_list_channels);
38 // Copy pointers from AudioBufferList.
39 for (int i = 0; i < channels; ++i) {
41 i, static_cast<float*>(buffer_list->mBuffers[i].mData));
44 // Finally set the actual length.
45 bus->set_frames(frames);
48 AUHALStream::AUHALStream(
49 AudioManagerMac* manager,
50 const AudioParameters& params,
54 input_channels_(params_.input_channels()),
55 output_channels_(params_.channels()),
56 number_of_frames_(params_.frames_per_buffer()),
61 hardware_latency_frames_(0),
63 input_buffer_list_(NULL),
64 current_hardware_pending_bytes_(0) {
65 // We must have a manager.
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_;
76 AUHALStream::~AUHALStream() {
79 bool AUHALStream::Open() {
80 // Get the total number of input and output channels that the
82 int device_input_channels;
83 bool got_input_channels = AudioManagerMac::GetDeviceChannels(
85 kAudioDevicePropertyScopeInput,
86 &device_input_channels);
88 int device_output_channels;
89 bool got_output_channels = AudioManagerMac::GetDeviceChannels(
91 kAudioDevicePropertyScopeOutput,
92 &device_output_channels);
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.";
101 if (!got_output_channels ||
102 output_channels_ <= 0 || output_channels_ > device_output_channels) {
103 LOG(ERROR) << "AudioDevice does not support requested output channels.";
107 // The requested sample-rate must match the hardware sample-rate.
108 int sample_rate = AudioManagerMac::HardwareSampleRateForDevice(device_);
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;
118 bool configured = ConfigureAUHAL();
120 hardware_latency_frames_ = GetHardwareLatency();
125 void 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);
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.";
142 // Inform the audio manager that we have been closed. This will cause our
144 manager_->ReleaseOutputStream(this);
147 void AUHALStream::Start(AudioSourceCallback* callback) {
150 DLOG(ERROR) << "Open() has not been called successfully";
154 // Check if we should defer Start() for http://crbug.com/160920.
155 if (manager_->ShouldDeferStreamStart()) {
156 // Use a cancellable closure so that if Stop() is called before Start()
157 // actually runs, we can cancel the pending start.
158 deferred_start_cb_.Reset(
159 base::Bind(&AUHALStream::Start, base::Unretained(this), callback));
160 manager_->GetTaskRunner()->PostDelayedTask(
161 FROM_HERE, deferred_start_cb_.callback(), base::TimeDelta::FromSeconds(
162 AudioManagerMac::kStartDelayInSecsForPowerEvents));
169 base::AutoLock auto_lock(source_lock_);
173 OSStatus result = AudioOutputUnitStart(audio_unit_);
178 OSSTATUS_DLOG(ERROR, result) << "AudioOutputUnitStart() failed.";
179 callback->OnError(this);
182 void AUHALStream::Stop() {
183 deferred_start_cb_.Cancel();
187 OSStatus result = AudioOutputUnitStop(audio_unit_);
188 OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
189 << "AudioOutputUnitStop() failed.";
191 source_->OnError(this);
193 base::AutoLock auto_lock(source_lock_);
198 void AUHALStream::SetVolume(double volume) {
199 volume_ = static_cast<float>(volume);
202 void AUHALStream::GetVolume(double* volume) {
206 // Pulls on our provider to get rendered audio stream.
207 // Note to future hackers of this function: Do not add locks which can
208 // be contended in the middle of stream processing here (starting and stopping
209 // the stream are ok) because this is running on a real-time thread.
210 OSStatus AUHALStream::Render(
211 AudioUnitRenderActionFlags* flags,
212 const AudioTimeStamp* output_time_stamp,
214 UInt32 number_of_frames,
215 AudioBufferList* io_data) {
216 TRACE_EVENT0("audio", "AUHALStream::Render");
218 // If the stream parameters change for any reason, we need to insert a FIFO
219 // since the OnMoreData() pipeline can't handle frame size changes.
220 if (number_of_frames != number_of_frames_) {
221 // Create a FIFO on the fly to handle any discrepancies in callback rates.
223 VLOG(1) << "Audio frame size changed from " << number_of_frames_ << " to "
224 << number_of_frames << "; adding FIFO to compensate.";
225 audio_fifo_.reset(new AudioPullFifo(
228 base::Bind(&AUHALStream::ProvideInput, base::Unretained(this))));
231 // Synchronous IO is not supported in this state.
232 if (input_channels_ > 0)
235 if (input_channels_ > 0 && input_buffer_list_) {
236 // Get the input data. |input_buffer_list_| is wrapped
237 // to point to the data allocated in |input_bus_|.
238 OSStatus result = AudioUnitRender(audio_unit_,
245 ZeroBufferList(input_buffer_list_);
249 // Make |output_bus_| wrap the output AudioBufferList.
250 WrapBufferList(io_data, output_bus_.get(), number_of_frames);
252 // Update the playout latency.
253 const double playout_latency_frames = GetPlayoutLatency(output_time_stamp);
254 current_hardware_pending_bytes_ = static_cast<uint32>(
255 (playout_latency_frames + 0.5) * params_.GetBytesPerFrame());
258 audio_fifo_->Consume(output_bus_.get(), output_bus_->frames());
260 ProvideInput(0, output_bus_.get());
265 void AUHALStream::ProvideInput(int frame_delay, AudioBus* dest) {
266 base::AutoLock auto_lock(source_lock_);
272 // Supply the input data and render the output data.
273 source_->OnMoreIOData(
277 current_hardware_pending_bytes_ +
278 frame_delay * params_.GetBytesPerFrame()));
279 dest->Scale(volume_);
283 OSStatus AUHALStream::InputProc(
285 AudioUnitRenderActionFlags* flags,
286 const AudioTimeStamp* output_time_stamp,
288 UInt32 number_of_frames,
289 AudioBufferList* io_data) {
290 // Dispatch to our class method.
291 AUHALStream* audio_output =
292 static_cast<AUHALStream*>(user_data);
296 return audio_output->Render(
304 double AUHALStream::GetHardwareLatency() {
305 if (!audio_unit_ || device_ == kAudioObjectUnknown) {
306 DLOG(WARNING) << "AudioUnit is NULL or device ID is unknown";
310 // Get audio unit latency.
311 Float64 audio_unit_latency_sec = 0.0;
312 UInt32 size = sizeof(audio_unit_latency_sec);
313 OSStatus result = AudioUnitGetProperty(
315 kAudioUnitProperty_Latency,
316 kAudioUnitScope_Global,
318 &audio_unit_latency_sec,
320 if (result != noErr) {
321 OSSTATUS_DLOG(WARNING, result) << "Could not get AudioUnit latency";
325 // Get output audio device latency.
326 static const AudioObjectPropertyAddress property_address = {
327 kAudioDevicePropertyLatency,
328 kAudioDevicePropertyScopeOutput,
329 kAudioObjectPropertyElementMaster
332 UInt32 device_latency_frames = 0;
333 size = sizeof(device_latency_frames);
334 result = AudioObjectGetPropertyData(
340 &device_latency_frames);
341 if (result != noErr) {
342 OSSTATUS_DLOG(WARNING, result) << "Could not get audio device latency";
346 return static_cast<double>((audio_unit_latency_sec *
347 output_format_.mSampleRate) + device_latency_frames);
350 double AUHALStream::GetPlayoutLatency(
351 const AudioTimeStamp* output_time_stamp) {
352 // Ensure mHostTime is valid.
353 if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
356 // Get the delay between the moment getting the callback and the scheduled
357 // time stamp that tells when the data is going to be played out.
358 UInt64 output_time_ns = AudioConvertHostTimeToNanos(
359 output_time_stamp->mHostTime);
360 UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
362 // Prevent overflow leading to huge delay information; occurs regularly on
363 // the bots, probably less so in the wild.
364 if (now_ns > output_time_ns)
367 double delay_frames = static_cast<double>
368 (1e-9 * (output_time_ns - now_ns) * output_format_.mSampleRate);
370 return (delay_frames + hardware_latency_frames_);
373 void AUHALStream::CreateIOBusses() {
374 if (input_channels_ > 0) {
375 // Allocate storage for the AudioBufferList used for the
376 // input data from the input AudioUnit.
377 // We allocate enough space for with one AudioBuffer per channel.
378 size_t buffer_list_size = offsetof(AudioBufferList, mBuffers[0]) +
379 (sizeof(AudioBuffer) * input_channels_);
380 input_buffer_list_storage_.reset(new uint8[buffer_list_size]);
383 reinterpret_cast<AudioBufferList*>(input_buffer_list_storage_.get());
384 input_buffer_list_->mNumberBuffers = input_channels_;
386 // |input_bus_| allocates the storage for the PCM input data.
387 input_bus_ = AudioBus::Create(input_channels_, number_of_frames_);
389 // Make the AudioBufferList point to the memory in |input_bus_|.
390 UInt32 buffer_size_bytes = input_bus_->frames() * sizeof(Float32);
391 for (size_t i = 0; i < input_buffer_list_->mNumberBuffers; ++i) {
392 input_buffer_list_->mBuffers[i].mNumberChannels = 1;
393 input_buffer_list_->mBuffers[i].mDataByteSize = buffer_size_bytes;
394 input_buffer_list_->mBuffers[i].mData = input_bus_->channel(i);
398 // The output bus will wrap the AudioBufferList given to us in
399 // the Render() callback.
400 DCHECK_GT(output_channels_, 0);
401 output_bus_ = AudioBus::CreateWrapper(output_channels_);
404 bool AUHALStream::EnableIO(bool enable, UInt32 scope) {
405 // See Apple technote for details about the EnableIO property.
406 // Note that we use bus 1 for input and bus 0 for output:
407 // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
408 UInt32 enable_IO = enable ? 1 : 0;
409 OSStatus result = AudioUnitSetProperty(
411 kAudioOutputUnitProperty_EnableIO,
413 (scope == kAudioUnitScope_Input) ? 1 : 0,
416 return (result == noErr);
419 bool AUHALStream::SetStreamFormat(
420 AudioStreamBasicDescription* desc,
425 AudioStreamBasicDescription& format = *desc;
427 format.mSampleRate = params_.sample_rate();
428 format.mFormatID = kAudioFormatLinearPCM;
429 format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked |
430 kLinearPCMFormatFlagIsNonInterleaved;
431 format.mBytesPerPacket = sizeof(Float32);
432 format.mFramesPerPacket = 1;
433 format.mBytesPerFrame = sizeof(Float32);
434 format.mChannelsPerFrame = channels;
435 format.mBitsPerChannel = 32;
436 format.mReserved = 0;
438 OSStatus result = AudioUnitSetProperty(
440 kAudioUnitProperty_StreamFormat,
445 return (result == noErr);
448 bool AUHALStream::ConfigureAUHAL() {
449 if (device_ == kAudioObjectUnknown ||
450 (input_channels_ == 0 && output_channels_ == 0))
453 AudioComponentDescription desc = {
454 kAudioUnitType_Output,
455 kAudioUnitSubType_HALOutput,
456 kAudioUnitManufacturer_Apple,
460 AudioComponent comp = AudioComponentFindNext(0, &desc);
464 OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
465 if (result != noErr) {
466 OSSTATUS_DLOG(ERROR, result) << "AudioComponentInstanceNew() failed.";
470 // Enable input and output as appropriate.
471 if (!EnableIO(input_channels_ > 0, kAudioUnitScope_Input))
473 if (!EnableIO(output_channels_ > 0, kAudioUnitScope_Output))
476 // Set the device to be used with the AUHAL AudioUnit.
477 result = AudioUnitSetProperty(
479 kAudioOutputUnitProperty_CurrentDevice,
480 kAudioUnitScope_Global,
483 sizeof(AudioDeviceID));
487 // Set stream formats.
488 // See Apple's tech note for details on the peculiar way that
489 // inputs and outputs are handled in the AUHAL concerning scope and bus
490 // (element) numbers:
491 // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
493 if (input_channels_ > 0) {
494 if (!SetStreamFormat(&input_format_,
496 kAudioUnitScope_Output,
501 if (output_channels_ > 0) {
502 if (!SetStreamFormat(&output_format_,
504 kAudioUnitScope_Input,
509 // Set the buffer frame size.
510 // WARNING: Setting this value changes the frame size for all output audio
511 // units in the current process. As a result, the AURenderCallback must be
512 // able to handle arbitrary buffer sizes and FIFO appropriately.
513 UInt32 buffer_size = 0;
514 UInt32 property_size = sizeof(buffer_size);
515 result = AudioUnitGetProperty(audio_unit_,
516 kAudioDevicePropertyBufferFrameSize,
517 kAudioUnitScope_Output,
521 if (result != noErr) {
522 OSSTATUS_DLOG(ERROR, result)
523 << "AudioUnitGetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
527 // Only set the buffer size if we're the only active stream or the buffer size
528 // is lower than the current buffer size.
529 if (manager_->output_stream_count() == 1 || number_of_frames_ < buffer_size) {
530 buffer_size = number_of_frames_;
531 result = AudioUnitSetProperty(audio_unit_,
532 kAudioDevicePropertyBufferFrameSize,
533 kAudioUnitScope_Output,
536 sizeof(buffer_size));
537 if (result != noErr) {
538 OSSTATUS_DLOG(ERROR, result) << "AudioUnitSetProperty("
539 "kAudioDevicePropertyBufferFrameSize) "
540 "failed. Size: " << number_of_frames_;
546 AURenderCallbackStruct callback;
547 callback.inputProc = InputProc;
548 callback.inputProcRefCon = this;
549 result = AudioUnitSetProperty(
551 kAudioUnitProperty_SetRenderCallback,
552 kAudioUnitScope_Input,
559 result = AudioUnitInitialize(audio_unit_);
560 if (result != noErr) {
561 OSSTATUS_DLOG(ERROR, result) << "AudioUnitInitialize() failed.";