diff options
author | crogers@google.com <crogers@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-29 01:33:48 +0000 |
---|---|---|
committer | crogers@google.com <crogers@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-29 01:33:48 +0000 |
commit | 7aee6c21c1cba5ed857c76f405e1b6b1e4509151 (patch) | |
tree | 203282c9791739ac9a4b58741400c49ccdef1735 /media | |
parent | df6cf1addfa73b6955ef83a641d929f0be9387ab (diff) | |
download | chromium_src-7aee6c21c1cba5ed857c76f405e1b6b1e4509151.zip chromium_src-7aee6c21c1cba5ed857c76f405e1b6b1e4509151.tar.gz chromium_src-7aee6c21c1cba5ed857c76f405e1b6b1e4509151.tar.bz2 |
Add a low-latency AudioOutputStream implementation for Mac OS X.
BUG=none
TEST=none
(tested locally to verify this code plays audio with much lower latency than the current PCMQueueOutAudioOutputStream implementation)
Review URL: http://codereview.chromium.org/6350019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@73058 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_io.h | 5 | ||||
-rw-r--r-- | media/audio/audio_util.h | 17 | ||||
-rw-r--r-- | media/audio/mac/audio_low_latency_output_mac.cc | 245 | ||||
-rw-r--r-- | media/audio/mac/audio_low_latency_output_mac.h | 72 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.cc | 21 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.h | 2 | ||||
-rw-r--r-- | media/media.gyp | 3 |
7 files changed, 353 insertions, 12 deletions
diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h index cadc5d3..787020c 100644 --- a/media/audio/audio_io.h +++ b/media/audio/audio_io.h @@ -74,6 +74,8 @@ class AudioOutputStream { virtual void OnError(AudioOutputStream* stream, int code) = 0; }; + virtual ~AudioOutputStream() {} + // Open the stream. false is returned if the stream cannot be opened. virtual bool Open() = 0; @@ -97,9 +99,6 @@ class AudioOutputStream { // Close the stream. This also generates AudioSourceCallback::OnClose(). // After calling this method, the object should not be used anymore. virtual void Close() = 0; - - protected: - virtual ~AudioOutputStream() {} }; // Models an audio sink receiving recorded audio from the audio driver. diff --git a/media/audio/audio_util.h b/media/audio/audio_util.h index a6ba75f..274c82c 100644 --- a/media/audio/audio_util.h +++ b/media/audio/audio_util.h @@ -74,6 +74,23 @@ void InterleaveFloatToInt16(const std::vector<float*>& source, int16* destination, size_t number_of_frames); +// Reorder PCM from AAC layout to Core Audio 5.1 layout. +// TODO(fbarchard): Switch layout when ffmpeg is updated. +template<class Format> +void SwizzleCoreAudioLayout5_1(Format* b, uint32 filled) { + static const int kNumSurroundChannels = 6; + Format aac[kNumSurroundChannels]; + for (uint32 i = 0; i < filled; i += sizeof(aac), b += kNumSurroundChannels) { + memcpy(aac, b, sizeof(aac)); + b[0] = aac[1]; // L + b[1] = aac[2]; // R + b[2] = aac[0]; // C + b[3] = aac[5]; // LFE + b[4] = aac[3]; // Ls + b[5] = aac[4]; // Rs + } +} + } // namespace media #endif // MEDIA_AUDIO_AUDIO_UTIL_H_ diff --git a/media/audio/mac/audio_low_latency_output_mac.cc b/media/audio/mac/audio_low_latency_output_mac.cc new file mode 100644 index 0000000..2a8e38a --- /dev/null +++ b/media/audio/mac/audio_low_latency_output_mac.cc @@ -0,0 +1,245 @@ +// Copyright (c) 2011 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 "base/basictypes.h" +#include "base/logging.h" +#include "media/audio/audio_util.h" +#include "media/audio/mac/audio_manager_mac.h" + +using media::SwizzleCoreAudioLayout5_1; + +// 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, AudioParameters params) + : manager_(manager), + source_(NULL), + output_unit_(0), + volume_(1) { + // 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; + + // Calculate the number of sample frames per callback. + number_of_frames_ = params.GetPacketSize() / format_.mBytesPerPacket; +} + +AUAudioOutputStream::~AUAudioOutputStream() { +} + +bool AUAudioOutputStream::Open() { + // Open and initialize the DefaultOutputUnit. + Component comp; + ComponentDescription desc; + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + comp = FindNextComponent(0, &desc); + DCHECK(comp); + + OSStatus result = OpenAComponent(comp, &output_unit_); + DCHECK_EQ(result, 0); + if (result) + return false; + + result = AudioUnitInitialize(output_unit_); + + DCHECK_EQ(result, 0); + if (result) + return false; + + 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)); + + DCHECK_EQ(result, 0); + if (result) + return false; + + // Set the stream format. + result = AudioUnitSetProperty( + output_unit_, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &format_, + sizeof(format_)); + DCHECK_EQ(result, 0); + if (result) + return false; + + // Set the buffer frame size. + UInt32 buffer_size = number_of_frames_; + result = AudioUnitSetProperty( + output_unit_, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Output, + 0, + &buffer_size, + sizeof(buffer_size)); + DCHECK_EQ(result, 0); + if (result) + return false; + + return true; +} + +void AUAudioOutputStream::Close() { + if (output_unit_) + CloseComponent(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); + source_ = callback; + + AudioOutputUnitStart(output_unit_); +} + +void AUAudioOutputStream::Stop() { + // We request a synchronous stop, so the next call can take some time. In + // the windows implementation we block here as well. + source_ = NULL; + + AudioOutputUnitStop(output_unit_); +} + +void AUAudioOutputStream::SetVolume(double volume) { + if (!output_unit_) + return; + volume_ = static_cast<float>(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) { + AudioBuffer& buffer = io_data->mBuffers[0]; + uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData); + uint32 filled = source_->OnMoreData( + this, audio_data, buffer.mDataByteSize, AudioBuffersState(0, 0)); + + // Handle channel order for 5.1 audio. + if (format_.mChannelsPerFrame == 6) { + if (format_.mBitsPerChannel == 8) { + SwizzleCoreAudioLayout5_1(reinterpret_cast<uint8*>(audio_data), filled); + } else if (format_.mBitsPerChannel == 16) { + SwizzleCoreAudioLayout5_1(reinterpret_cast<int16*>(audio_data), filled); + } else if (format_.mBitsPerChannel == 32) { + SwizzleCoreAudioLayout5_1(reinterpret_cast<int32*>(audio_data), filled); + } + } + + return noErr; +} + +// DefaultOutputUnit callback +OSStatus AUAudioOutputStream::InputProc(void* user_data, + AudioUnitRenderActionFlags*, + const AudioTimeStamp*, + UInt32, + UInt32 number_of_frames, + AudioBufferList* io_data) { + AUAudioOutputStream* audio_output = + static_cast<AUAudioOutputStream*>(user_data); + DCHECK(audio_output); + if (!audio_output) + return -1; + + return audio_output->Render(number_of_frames, io_data); +} + +double AUAudioOutputStream::HardwareSampleRate() { + // Determine the default output device's sample-rate. + AudioDeviceID device_id = kAudioDeviceUnknown; + UInt32 info_size = sizeof(device_id); + + AudioObjectPropertyAddress default_input_device_address = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &default_input_device_address, + 0, + 0, + &info_size, + &device_id); + DCHECK_EQ(result, 0); + if (result) + return 0.0; // error + + 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); + DCHECK_EQ(result, 0); + if (result) + return 0.0; // error + + return nominal_sample_rate; +} diff --git a/media/audio/mac/audio_low_latency_output_mac.h b/media/audio/mac/audio_low_latency_output_mac.h new file mode 100644 index 0000000..446edaa --- /dev/null +++ b/media/audio/mac/audio_low_latency_output_mac.h @@ -0,0 +1,72 @@ +// Copyright (c) 2011 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. + +#ifndef MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_OUTPUT_MAC_H_ +#define MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_OUTPUT_MAC_H_ + +#include <AudioUnit/AudioUnit.h> + +#include "media/audio/audio_io.h" +#include "media/audio/audio_parameters.h" + +class AudioManagerMac; + +// Implementation of AudioOuputStream for Mac OS X using the +// default output Audio Unit present in OS 10.4 and later. +// The default output Audio Unit is for low-latency audio I/O. +class AUAudioOutputStream : public AudioOutputStream { + public: + // The ctor takes all the usual parameters, plus |manager| which is the + // the audio manager who is creating this object. + AUAudioOutputStream(AudioManagerMac* manager, + AudioParameters params); + // The dtor is typically called by the AudioManager only and it is usually + // triggered by calling AudioOutputStream::Close(). + virtual ~AUAudioOutputStream(); + + // Implementation of AudioOutputStream. + virtual bool Open(); + virtual void Close(); + virtual void Start(AudioSourceCallback* callback); + virtual void Stop(); + virtual void SetVolume(double volume); + virtual void GetVolume(double* volume); + + static double HardwareSampleRate(); + + private: + // DefaultOutputUnit callback. + static OSStatus InputProc(void* user_data, + AudioUnitRenderActionFlags* flags, + const AudioTimeStamp* time_stamp, + UInt32 bus_number, + UInt32 number_of_frames, + AudioBufferList* io_data); + + OSStatus Render(UInt32 number_of_frames, AudioBufferList* io_data); + + // Sets up the stream format for the default output Audio Unit. + bool Configure(); + + // Our creator, the audio manager needs to be notified when we close. + AudioManagerMac* manager_; + + size_t number_of_frames_; + + // Pointer to the object that will provide the audio samples. + AudioSourceCallback* source_; + + // Structure that holds the stream format details such as bitrate. + AudioStreamBasicDescription format_; + + // The default output Audio Unit which talks to the audio hardware. + AudioUnit output_unit_; + + // Volume level from 0 to 1. + float volume_; + + DISALLOW_COPY_AND_ASSIGN(AUAudioOutputStream); +}; + +#endif // MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_OUTPUT_MAC_H_ diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc index 48df6ab..71ce811 100644 --- a/media/audio/mac/audio_manager_mac.cc +++ b/media/audio/mac/audio_manager_mac.cc @@ -8,6 +8,7 @@ #include "media/audio/fake_audio_input_stream.h" #include "media/audio/fake_audio_output_stream.h" #include "media/audio/mac/audio_input_mac.h" +#include "media/audio/mac/audio_low_latency_output_mac.h" #include "media/audio/mac/audio_manager_mac.h" #include "media/audio/mac/audio_output_mac.h" #include "media/base/limits.h" @@ -85,11 +86,8 @@ bool AudioManagerMac::HasAudioInputDevices() { AudioOutputStream* AudioManagerMac::MakeAudioOutputStream( AudioParameters params) { - if (params.format == AudioParameters::AUDIO_MOCK) { - return FakeAudioOutputStream::MakeFakeStream(params); - } else if (params.format != AudioParameters::AUDIO_PCM_LINEAR) { + if (!params.IsValid()) return NULL; - } // Limit the number of audio streams opened. This is to prevent using // excessive resources for a large number of audio streams. More @@ -99,8 +97,16 @@ AudioOutputStream* AudioManagerMac::MakeAudioOutputStream( return NULL; } - num_output_streams_++; - return new PCMQueueOutAudioOutputStream(this, params); + if (params.format == AudioParameters::AUDIO_MOCK) { + return FakeAudioOutputStream::MakeFakeStream(params); + } else if (params.format == AudioParameters::AUDIO_PCM_LINEAR) { + num_output_streams_++; + return new PCMQueueOutAudioOutputStream(this, params); + } else if (params.format == AudioParameters::AUDIO_PCM_LOW_LATENCY) { + num_output_streams_++; + return new AUAudioOutputStream(this, params); + } + return NULL; } AudioInputStream* AudioManagerMac::MakeAudioInputStream( @@ -125,8 +131,7 @@ void AudioManagerMac::UnMuteAll() { } // Called by the stream when it has been released by calling Close(). -void AudioManagerMac::ReleaseOutputStream( - PCMQueueOutAudioOutputStream* stream) { +void AudioManagerMac::ReleaseOutputStream(AudioOutputStream* stream) { DCHECK(stream); num_output_streams_--; delete stream; diff --git a/media/audio/mac/audio_manager_mac.h b/media/audio/mac/audio_manager_mac.h index d2fc3f3..1977bac 100644 --- a/media/audio/mac/audio_manager_mac.h +++ b/media/audio/mac/audio_manager_mac.h @@ -29,7 +29,7 @@ class AudioManagerMac : public AudioManagerBase { // Mac-only method to free the streams created by above facoty methods. // They are called internally by the respective audio stream when it has // been closed. - void ReleaseOutputStream(PCMQueueOutAudioOutputStream* stream); + void ReleaseOutputStream(AudioOutputStream* stream); void ReleaseInputStream(PCMQueueInAudioInputStream* stream); private: diff --git a/media/media.gyp b/media/media.gyp index 660898b..e2cdafd 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -57,6 +57,8 @@ 'audio/openbsd/audio_manager_openbsd.h', 'audio/mac/audio_input_mac.cc', 'audio/mac/audio_input_mac.h', + 'audio/mac/audio_low_latency_output_mac.cc', + 'audio/mac/audio_low_latency_output_mac.h', 'audio/mac/audio_manager_mac.cc', 'audio/mac/audio_manager_mac.h', 'audio/mac/audio_output_mac.cc', @@ -205,6 +207,7 @@ ['OS=="mac"', { 'link_settings': { 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/AudioUnit.framework', '$(SDKROOT)/System/Library/Frameworks/AudioToolbox.framework', '$(SDKROOT)/System/Library/Frameworks/CoreAudio.framework', ], |