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