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