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