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