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/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/mac/audio_manager_mac.h"
15 #include "media/base/media_switches.h"
19 static std::ostream& operator<<(std::ostream& os,
20 const AudioStreamBasicDescription& format) {
21 os << "sample rate : " << format.mSampleRate << std::endl
22 << "format ID : " << format.mFormatID << std::endl
23 << "format flags : " << format.mFormatFlags << std::endl
24 << "bytes per packet : " << format.mBytesPerPacket << std::endl
25 << "frames per packet : " << format.mFramesPerPacket << std::endl
26 << "bytes per frame : " << format.mBytesPerFrame << std::endl
27 << "channels per frame: " << format.mChannelsPerFrame << std::endl
28 << "bits per channel : " << format.mBitsPerChannel;
32 static void ZeroBufferList(AudioBufferList* buffer_list) {
33 for (size_t i = 0; i < buffer_list->mNumberBuffers; ++i) {
34 memset(buffer_list->mBuffers[i].mData,
36 buffer_list->mBuffers[i].mDataByteSize);
40 static void WrapBufferList(AudioBufferList* buffer_list,
45 const int channels = bus->channels();
46 const int buffer_list_channels = buffer_list->mNumberBuffers;
47 CHECK_EQ(channels, buffer_list_channels);
49 // Copy pointers from AudioBufferList.
50 for (int i = 0; i < channels; ++i) {
52 i, static_cast<float*>(buffer_list->mBuffers[i].mData));
55 // Finally set the actual length.
56 bus->set_frames(frames);
59 AUHALStream::AUHALStream(
60 AudioManagerMac* manager,
61 const AudioParameters& params,
65 input_channels_(params_.input_channels()),
66 output_channels_(params_.channels()),
67 number_of_frames_(params_.frames_per_buffer()),
72 hardware_latency_frames_(0),
74 notified_for_possible_device_change_(false),
75 input_buffer_list_(NULL) {
76 // We must have a manager.
79 VLOG(1) << "AUHALStream::AUHALStream()";
80 VLOG(1) << "Device: " << device;
81 VLOG(1) << "Input channels: " << input_channels_;
82 VLOG(1) << "Output channels: " << output_channels_;
83 VLOG(1) << "Sample rate: " << params_.sample_rate();
84 VLOG(1) << "Buffer size: " << number_of_frames_;
87 AUHALStream::~AUHALStream() {
90 bool AUHALStream::Open() {
91 // Get the total number of input and output channels that the
93 int device_input_channels;
94 bool got_input_channels = AudioManagerMac::GetDeviceChannels(
96 kAudioDevicePropertyScopeInput,
97 &device_input_channels);
99 int device_output_channels;
100 bool got_output_channels = AudioManagerMac::GetDeviceChannels(
102 kAudioDevicePropertyScopeOutput,
103 &device_output_channels);
105 // Sanity check the requested I/O channels.
106 if (!got_input_channels ||
107 input_channels_ < 0 || input_channels_ > device_input_channels) {
108 LOG(ERROR) << "AudioDevice does not support requested input channels.";
112 if (!got_output_channels ||
113 output_channels_ <= 0 || output_channels_ > device_output_channels) {
114 LOG(ERROR) << "AudioDevice does not support requested output channels.";
118 // The requested sample-rate must match the hardware sample-rate.
119 int sample_rate = AudioManagerMac::HardwareSampleRateForDevice(device_);
121 if (sample_rate != params_.sample_rate()) {
122 LOG(ERROR) << "Requested sample-rate: " << params_.sample_rate()
123 << " must match the hardware sample-rate: " << sample_rate;
129 bool configured = ConfigureAUHAL();
131 hardware_latency_frames_ = GetHardwareLatency();
136 void AUHALStream::Close() {
137 if (input_buffer_list_) {
138 input_buffer_list_storage_.reset();
139 input_buffer_list_ = NULL;
140 input_bus_.reset(NULL);
141 output_bus_.reset(NULL);
145 OSStatus result = AudioUnitUninitialize(audio_unit_);
146 OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
147 << "AudioUnitUninitialize() failed.";
148 result = AudioComponentInstanceDispose(audio_unit_);
149 OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
150 << "AudioComponentInstanceDispose() failed.";
153 // Inform the audio manager that we have been closed. This will cause our
155 manager_->ReleaseOutputStream(this);
158 void AUHALStream::Start(AudioSourceCallback* callback) {
161 DLOG(ERROR) << "Open() has not been called successfully";
166 notified_for_possible_device_change_ = false;
168 base::AutoLock auto_lock(source_lock_);
172 OSStatus result = AudioOutputUnitStart(audio_unit_);
177 OSSTATUS_DLOG(ERROR, result) << "AudioOutputUnitStart() failed.";
178 callback->OnError(this);
181 void AUHALStream::Stop() {
185 OSStatus result = AudioOutputUnitStop(audio_unit_);
186 OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
187 << "AudioOutputUnitStop() failed.";
189 source_->OnError(this);
191 base::AutoLock auto_lock(source_lock_);
196 void AUHALStream::SetVolume(double volume) {
197 volume_ = static_cast<float>(volume);
200 void AUHALStream::GetVolume(double* volume) {
204 // Pulls on our provider to get rendered audio stream.
205 // Note to future hackers of this function: Do not add locks which can
206 // be contended in the middle of stream processing here (starting and stopping
207 // the stream are ok) because this is running on a real-time thread.
208 OSStatus AUHALStream::Render(
209 AudioUnitRenderActionFlags* flags,
210 const AudioTimeStamp* output_time_stamp,
212 UInt32 number_of_frames,
213 AudioBufferList* io_data) {
214 TRACE_EVENT0("audio", "AUHALStream::Render");
216 if (number_of_frames != number_of_frames_) {
217 // This can happen if we've suddenly changed sample-rates.
218 // The stream should be stopping very soon.
220 // Unfortunately AUAudioInputStream and AUHALStream share the frame
221 // size set by kAudioDevicePropertyBufferFrameSize above on a per process
222 // basis. What this means is that the |number_of_frames| value may be
223 // larger or smaller than the value set during ConfigureAUHAL().
224 // In this case either audio input or audio output will be broken,
225 // so just output silence.
226 ZeroBufferList(io_data);
230 if (input_channels_ > 0 && input_buffer_list_) {
231 // Get the input data. |input_buffer_list_| is wrapped
232 // to point to the data allocated in |input_bus_|.
233 OSStatus result = AudioUnitRender(
241 ZeroBufferList(input_buffer_list_);
244 // Make |output_bus_| wrap the output AudioBufferList.
245 WrapBufferList(io_data, output_bus_.get(), number_of_frames);
247 // Update the playout latency.
248 double playout_latency_frames = GetPlayoutLatency(output_time_stamp);
250 uint32 hardware_pending_bytes = static_cast<uint32>
251 ((playout_latency_frames + 0.5) * output_format_.mBytesPerFrame);
254 // Render() shouldn't be called except between AudioOutputUnitStart() and
255 // AudioOutputUnitStop() calls, but crash reports have shown otherwise:
256 // http://crbug.com/178765. We use |source_lock_| to prevent races and
257 // crashes in Render() when |source_| is cleared.
258 base::AutoLock auto_lock(source_lock_);
260 ZeroBufferList(io_data);
264 // Supply the input data and render the output data.
265 source_->OnMoreIOData(
268 AudioBuffersState(0, hardware_pending_bytes));
269 output_bus_->Scale(volume_);
276 OSStatus AUHALStream::InputProc(
278 AudioUnitRenderActionFlags* flags,
279 const AudioTimeStamp* output_time_stamp,
281 UInt32 number_of_frames,
282 AudioBufferList* io_data) {
283 // Dispatch to our class method.
284 AUHALStream* audio_output =
285 static_cast<AUHALStream*>(user_data);
289 return audio_output->Render(
297 double AUHALStream::GetHardwareLatency() {
298 if (!audio_unit_ || device_ == kAudioObjectUnknown) {
299 DLOG(WARNING) << "AudioUnit is NULL or device ID is unknown";
303 // Get audio unit latency.
304 Float64 audio_unit_latency_sec = 0.0;
305 UInt32 size = sizeof(audio_unit_latency_sec);
306 OSStatus result = AudioUnitGetProperty(
308 kAudioUnitProperty_Latency,
309 kAudioUnitScope_Global,
311 &audio_unit_latency_sec,
313 if (result != noErr) {
314 OSSTATUS_DLOG(WARNING, result) << "Could not get AudioUnit latency";
318 // Get output audio device latency.
319 static const AudioObjectPropertyAddress property_address = {
320 kAudioDevicePropertyLatency,
321 kAudioDevicePropertyScopeOutput,
322 kAudioObjectPropertyElementMaster
325 UInt32 device_latency_frames = 0;
326 size = sizeof(device_latency_frames);
327 result = AudioObjectGetPropertyData(
333 &device_latency_frames);
334 if (result != noErr) {
335 OSSTATUS_DLOG(WARNING, result) << "Could not get audio device latency";
339 return static_cast<double>((audio_unit_latency_sec *
340 output_format_.mSampleRate) + device_latency_frames);
343 double AUHALStream::GetPlayoutLatency(
344 const AudioTimeStamp* output_time_stamp) {
345 // Ensure mHostTime is valid.
346 if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
349 // Get the delay between the moment getting the callback and the scheduled
350 // time stamp that tells when the data is going to be played out.
351 UInt64 output_time_ns = AudioConvertHostTimeToNanos(
352 output_time_stamp->mHostTime);
353 UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
355 // Prevent overflow leading to huge delay information; occurs regularly on
356 // the bots, probably less so in the wild.
357 if (now_ns > output_time_ns)
360 double delay_frames = static_cast<double>
361 (1e-9 * (output_time_ns - now_ns) * output_format_.mSampleRate);
363 return (delay_frames + hardware_latency_frames_);
366 void AUHALStream::CreateIOBusses() {
367 if (input_channels_ > 0) {
368 // Allocate storage for the AudioBufferList used for the
369 // input data from the input AudioUnit.
370 // We allocate enough space for with one AudioBuffer per channel.
371 size_t buffer_list_size = offsetof(AudioBufferList, mBuffers[0]) +
372 (sizeof(AudioBuffer) * input_channels_);
373 input_buffer_list_storage_.reset(new uint8[buffer_list_size]);
376 reinterpret_cast<AudioBufferList*>(input_buffer_list_storage_.get());
377 input_buffer_list_->mNumberBuffers = input_channels_;
379 // |input_bus_| allocates the storage for the PCM input data.
380 input_bus_ = AudioBus::Create(input_channels_, number_of_frames_);
382 // Make the AudioBufferList point to the memory in |input_bus_|.
383 UInt32 buffer_size_bytes = input_bus_->frames() * sizeof(Float32);
384 for (size_t i = 0; i < input_buffer_list_->mNumberBuffers; ++i) {
385 input_buffer_list_->mBuffers[i].mNumberChannels = 1;
386 input_buffer_list_->mBuffers[i].mDataByteSize = buffer_size_bytes;
387 input_buffer_list_->mBuffers[i].mData = input_bus_->channel(i);
391 // The output bus will wrap the AudioBufferList given to us in
392 // the Render() callback.
393 DCHECK_GT(output_channels_, 0);
394 output_bus_ = AudioBus::CreateWrapper(output_channels_);
397 bool AUHALStream::EnableIO(bool enable, UInt32 scope) {
398 // See Apple technote for details about the EnableIO property.
399 // Note that we use bus 1 for input and bus 0 for output:
400 // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
401 UInt32 enable_IO = enable ? 1 : 0;
402 OSStatus result = AudioUnitSetProperty(
404 kAudioOutputUnitProperty_EnableIO,
406 (scope == kAudioUnitScope_Input) ? 1 : 0,
409 return (result == noErr);
412 bool AUHALStream::SetStreamFormat(
413 AudioStreamBasicDescription* desc,
418 AudioStreamBasicDescription& format = *desc;
420 format.mSampleRate = params_.sample_rate();
421 format.mFormatID = kAudioFormatLinearPCM;
422 format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked |
423 kLinearPCMFormatFlagIsNonInterleaved;
424 format.mBytesPerPacket = sizeof(Float32);
425 format.mFramesPerPacket = 1;
426 format.mBytesPerFrame = sizeof(Float32);
427 format.mChannelsPerFrame = channels;
428 format.mBitsPerChannel = 32;
429 format.mReserved = 0;
431 OSStatus result = AudioUnitSetProperty(
433 kAudioUnitProperty_StreamFormat,
438 return (result == noErr);
441 bool AUHALStream::ConfigureAUHAL() {
442 if (device_ == kAudioObjectUnknown ||
443 (input_channels_ == 0 && output_channels_ == 0))
446 AudioComponentDescription desc = {
447 kAudioUnitType_Output,
448 kAudioUnitSubType_HALOutput,
449 kAudioUnitManufacturer_Apple,
453 AudioComponent comp = AudioComponentFindNext(0, &desc);
457 OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
458 if (result != noErr) {
459 OSSTATUS_DLOG(ERROR, result) << "AudioComponentInstanceNew() failed.";
463 // Enable input and output as appropriate.
464 if (!EnableIO(input_channels_ > 0, kAudioUnitScope_Input))
466 if (!EnableIO(output_channels_ > 0, kAudioUnitScope_Output))
469 // Set the device to be used with the AUHAL AudioUnit.
470 result = AudioUnitSetProperty(
472 kAudioOutputUnitProperty_CurrentDevice,
473 kAudioUnitScope_Global,
476 sizeof(AudioDeviceID));
480 // Set stream formats.
481 // See Apple's tech note for details on the peculiar way that
482 // inputs and outputs are handled in the AUHAL concerning scope and bus
483 // (element) numbers:
484 // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
486 if (input_channels_ > 0) {
487 if (!SetStreamFormat(&input_format_,
489 kAudioUnitScope_Output,
494 if (output_channels_ > 0) {
495 if (!SetStreamFormat(&output_format_,
497 kAudioUnitScope_Input,
502 // Set the buffer frame size.
503 // WARNING: Setting this value changes the frame size for all audio units in
504 // the current process. It's imperative that the input and output frame sizes
505 // be the same as the frames_per_buffer() returned by
506 // GetDefaultOutputStreamParameters().
507 // See http://crbug.com/154352 for details.
508 UInt32 buffer_size = number_of_frames_;
509 result = AudioUnitSetProperty(
511 kAudioDevicePropertyBufferFrameSize,
512 kAudioUnitScope_Output,
515 sizeof(buffer_size));
516 if (result != noErr) {
517 OSSTATUS_DLOG(ERROR, result)
518 << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
523 AURenderCallbackStruct callback;
524 callback.inputProc = InputProc;
525 callback.inputProcRefCon = this;
526 result = AudioUnitSetProperty(
528 kAudioUnitProperty_SetRenderCallback,
529 kAudioUnitScope_Input,
536 result = AudioUnitInitialize(audio_unit_);
537 if (result != noErr) {
538 OSSTATUS_DLOG(ERROR, result) << "AudioUnitInitialize() failed.";