// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "media/audio/mac/audio_low_latency_output_mac.h" #include #include "base/basictypes.h" #include "base/command_line.h" #include "base/logging.h" #include "base/mac/mac_logging.h" #include "media/audio/mac/audio_manager_mac.h" #include "media/base/media_switches.h" namespace media { static std::ostream& operator<<(std::ostream& os, const AudioStreamBasicDescription& format) { os << "sample rate : " << format.mSampleRate << std::endl << "format ID : " << format.mFormatID << std::endl << "format flags : " << format.mFormatFlags << std::endl << "bytes per packet : " << format.mBytesPerPacket << std::endl << "frames per packet : " << format.mFramesPerPacket << std::endl << "bytes per frame : " << format.mBytesPerFrame << std::endl << "channels per frame: " << format.mChannelsPerFrame << std::endl << "bits per channel : " << format.mBitsPerChannel; return os; } static AudioObjectPropertyAddress kDefaultOutputDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; // Overview of operation: // 1) An object of AUAudioOutputStream is created by the AudioManager // factory: audio_man->MakeAudioStream(). // 2) Next some thread will call Open(), at that point the underlying // default output Audio Unit is created and configured. // 3) Then some thread will call Start(source). // Then the Audio Unit is started which creates its own thread which // periodically will call the source for more data as buffers are being // consumed. // 4) At some point some thread will call Stop(), which we handle by directly // stopping the default output Audio Unit. // 6) The same thread that called stop will call Close() where we cleanup // and notify the audio manager, which likely will destroy this object. AUAudioOutputStream::AUAudioOutputStream( AudioManagerMac* manager, const AudioParameters& params) : manager_(manager), source_(NULL), output_unit_(0), output_device_id_(kAudioObjectUnknown), volume_(1), hardware_latency_frames_(0), stopped_(false), audio_bus_(AudioBus::Create(params)) { // We must have a manager. DCHECK(manager_); // A frame is one sample across all channels. In interleaved audio the per // frame fields identify the set of n |channels|. In uncompressed audio, a // packet is always one frame. format_.mSampleRate = params.sample_rate(); format_.mFormatID = kAudioFormatLinearPCM; format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; format_.mBitsPerChannel = params.bits_per_sample(); format_.mChannelsPerFrame = params.channels(); format_.mFramesPerPacket = 1; format_.mBytesPerPacket = (format_.mBitsPerChannel * params.channels()) / 8; format_.mBytesPerFrame = format_.mBytesPerPacket; format_.mReserved = 0; DVLOG(1) << "Desired ouput format: " << format_; // Calculate the number of sample frames per callback. number_of_frames_ = params.frames_per_buffer(); DVLOG(1) << "Number of frames per callback: " << number_of_frames_; } AUAudioOutputStream::~AUAudioOutputStream() { } bool AUAudioOutputStream::Open() { // Obtain the current input device selected by the user. UInt32 size = sizeof(output_device_id_); OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &kDefaultOutputDeviceAddress, 0, 0, &size, &output_device_id_); if (result != noErr || output_device_id_ == kAudioObjectUnknown) { OSSTATUS_DLOG(ERROR, result) << "Could not get default audio output device."; return false; } // Open and initialize the DefaultOutputUnit. AudioComponent comp; AudioComponentDescription desc; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; comp = AudioComponentFindNext(0, &desc); if (!comp) return false; result = AudioComponentInstanceNew(comp, &output_unit_); if (result != noErr) { OSSTATUS_DLOG(ERROR, result) << "AudioComponentInstanceNew() failed."; return false; } result = AudioUnitInitialize(output_unit_); if (result != noErr) { OSSTATUS_DLOG(ERROR, result) << "AudioUnitInitialize() failed."; return false; } hardware_latency_frames_ = GetHardwareLatency(); return Configure(); } bool AUAudioOutputStream::Configure() { // Set the render callback. AURenderCallbackStruct input; input.inputProc = InputProc; input.inputProcRefCon = this; OSStatus result = AudioUnitSetProperty( output_unit_, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &input, sizeof(input)); if (result != noErr) { OSSTATUS_DLOG(ERROR, result) << "AudioUnitSetProperty(kAudioUnitProperty_SetRenderCallback) failed."; return false; } // Set the stream format. result = AudioUnitSetProperty( output_unit_, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format_, sizeof(format_)); if (result != noErr) { OSSTATUS_DLOG(ERROR, result) << "AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed."; return false; } // Set the buffer frame size. // WARNING: Setting this value changes the frame size for all audio units in // the current process. It's imperative that the input and output frame sizes // be the same as the frames_per_buffer() returned by // GetDefaultOutputStreamParameters. // See http://crbug.com/154352 for details. const AudioParameters hw_params = manager_->GetDefaultOutputStreamParameters(); if (number_of_frames_ != static_cast(hw_params.frames_per_buffer())) { DLOG(ERROR) << "Audio buffer size does not match hardware buffer size."; return false; } UInt32 buffer_size = number_of_frames_; result = AudioUnitSetProperty( output_unit_, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)); if (result != noErr) { OSSTATUS_DLOG(ERROR, result) << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed."; return false; } return true; } void AUAudioOutputStream::Close() { if (output_unit_) AudioComponentInstanceDispose(output_unit_); // Inform the audio manager that we have been closed. This can cause our // destruction. manager_->ReleaseOutputStream(this); } void AUAudioOutputStream::Start(AudioSourceCallback* callback) { DCHECK(callback); if (!output_unit_) { DLOG(ERROR) << "Open() has not been called successfully"; return; } stopped_ = false; { base::AutoLock auto_lock(source_lock_); source_ = callback; } AudioOutputUnitStart(output_unit_); } void AUAudioOutputStream::Stop() { if (stopped_) return; AudioOutputUnitStop(output_unit_); base::AutoLock auto_lock(source_lock_); source_ = NULL; stopped_ = true; } void AUAudioOutputStream::SetVolume(double volume) { if (!output_unit_) return; volume_ = static_cast(volume); // TODO(crogers): set volume property } void AUAudioOutputStream::GetVolume(double* volume) { if (!output_unit_) return; *volume = volume_; } // Pulls on our provider to get rendered audio stream. // Note to future hackers of this function: Do not add locks here because this // is running on a real-time thread (for low-latency). OSStatus AUAudioOutputStream::Render(UInt32 number_of_frames, AudioBufferList* io_data, const AudioTimeStamp* output_time_stamp) { // Update the playout latency. double playout_latency_frames = GetPlayoutLatency(output_time_stamp); AudioBuffer& buffer = io_data->mBuffers[0]; uint8* audio_data = reinterpret_cast(buffer.mData); uint32 hardware_pending_bytes = static_cast ((playout_latency_frames + 0.5) * format_.mBytesPerFrame); // Unfortunately AUAudioInputStream and AUAudioOutputStream share the frame // size set by kAudioDevicePropertyBufferFrameSize above on a per process // basis. What this means is that the |number_of_frames| value may be larger // or smaller than the value set during Configure(). In this case either // audio input or audio output will be broken, so just output silence. // TODO(crogers): Figure out what can trigger a change in |number_of_frames|. // See http://crbug.com/154352 for details. if (number_of_frames != static_cast(audio_bus_->frames())) { memset(audio_data, 0, number_of_frames * format_.mBytesPerFrame); return noErr; } int frames_filled = 0; { // Render() shouldn't be called except between AudioOutputUnitStart() and // AudioOutputUnitStop() calls, but crash reports have shown otherwise: // http://crbug.com/178765. We use |source_lock_| to prevent races and // crashes in Render() when |source_| is cleared. base::AutoLock auto_lock(source_lock_); if (!source_) { memset(audio_data, 0, number_of_frames * format_.mBytesPerFrame); return noErr; } frames_filled = source_->OnMoreData( audio_bus_.get(), AudioBuffersState(0, hardware_pending_bytes)); } // Note: If this ever changes to output raw float the data must be clipped and // sanitized since it may come from an untrusted source such as NaCl. audio_bus_->Scale(volume_); audio_bus_->ToInterleaved( frames_filled, format_.mBitsPerChannel / 8, audio_data); return noErr; } // DefaultOutputUnit callback OSStatus AUAudioOutputStream::InputProc(void* user_data, AudioUnitRenderActionFlags*, const AudioTimeStamp* output_time_stamp, UInt32, UInt32 number_of_frames, AudioBufferList* io_data) { AUAudioOutputStream* audio_output = static_cast(user_data); if (!audio_output) return -1; return audio_output->Render(number_of_frames, io_data, output_time_stamp); } int AUAudioOutputStream::HardwareSampleRate() { // Determine the default output device's sample-rate. AudioDeviceID device_id = kAudioObjectUnknown; UInt32 info_size = sizeof(device_id); OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &kDefaultOutputDeviceAddress, 0, 0, &info_size, &device_id); if (result != noErr || device_id == kAudioObjectUnknown) { OSSTATUS_DLOG(WARNING, result) << "Could not get default audio output device."; return 0; } Float64 nominal_sample_rate; info_size = sizeof(nominal_sample_rate); AudioObjectPropertyAddress nominal_sample_rate_address = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; result = AudioObjectGetPropertyData(device_id, &nominal_sample_rate_address, 0, 0, &info_size, &nominal_sample_rate); if (result != noErr) { OSSTATUS_DLOG(WARNING, result) << "Could not get default sample rate for device: " << device_id; return 0; } return static_cast(nominal_sample_rate); } double AUAudioOutputStream::GetHardwareLatency() { if (!output_unit_ || output_device_id_ == kAudioObjectUnknown) { DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown"; return 0.0; } // Get audio unit latency. Float64 audio_unit_latency_sec = 0.0; UInt32 size = sizeof(audio_unit_latency_sec); OSStatus result = AudioUnitGetProperty(output_unit_, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &audio_unit_latency_sec, &size); if (result != noErr) { OSSTATUS_DLOG(WARNING, result) << "Could not get audio unit latency"; return 0.0; } // Get output audio device latency. AudioObjectPropertyAddress property_address = { kAudioDevicePropertyLatency, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; UInt32 device_latency_frames = 0; size = sizeof(device_latency_frames); result = AudioObjectGetPropertyData(output_device_id_, &property_address, 0, NULL, &size, &device_latency_frames); if (result != noErr) { OSSTATUS_DLOG(WARNING, result) << "Could not get audio unit latency"; return 0.0; } return static_cast((audio_unit_latency_sec * format_.mSampleRate) + device_latency_frames); } double AUAudioOutputStream::GetPlayoutLatency( const AudioTimeStamp* output_time_stamp) { // Ensure mHostTime is valid. if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0) return 0; // Get the delay between the moment getting the callback and the scheduled // time stamp that tells when the data is going to be played out. UInt64 output_time_ns = AudioConvertHostTimeToNanos( output_time_stamp->mHostTime); UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); // Prevent overflow leading to huge delay information; occurs regularly on // the bots, probably less so in the wild. if (now_ns > output_time_ns) return 0; double delay_frames = static_cast (1e-9 * (output_time_ns - now_ns) * format_.mSampleRate); return (delay_frames + hardware_latency_frames_); } } // namespace media