diff options
author | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-14 14:17:49 +0000 |
---|---|---|
committer | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-14 14:17:49 +0000 |
commit | c795e6105d93011bd7b84d0508526f88f2c3aa72 (patch) | |
tree | df7f9b20e5407f3e028fa03afe4afc64d500bfcd /media | |
parent | b4821bb7a533238f2d5ff81c05296475267e55f3 (diff) | |
download | chromium_src-c795e6105d93011bd7b84d0508526f88f2c3aa72.zip chromium_src-c795e6105d93011bd7b84d0508526f88f2c3aa72.tar.gz chromium_src-c795e6105d93011bd7b84d0508526f88f2c3aa72.tar.bz2 |
Adds new full-duplex test which measures and logs the reported audio delays.
BUG=none
TEST=media_unittests --gtest_filter=AudioLowLatencyInputOutputTest*
Review URL: http://codereview.chromium.org/8840004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@114423 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_low_latency_input_output_unittest.cc | 459 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.h | 4 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.cc | 2 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.h | 5 | ||||
-rw-r--r-- | media/media.gyp | 1 |
5 files changed, 468 insertions, 3 deletions
diff --git a/media/audio/audio_low_latency_input_output_unittest.cc b/media/audio/audio_low_latency_input_output_unittest.cc new file mode 100644 index 0000000..88874b0 --- /dev/null +++ b/media/audio/audio_low_latency_input_output_unittest.cc @@ -0,0 +1,459 @@ +// 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 "base/basictypes.h" +#include "base/environment.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/synchronization/lock.h" +#include "base/test/test_timeouts.h" +#include "base/time.h" +#include "base/win/scoped_com_initializer.h" +#include "build/build_config.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_manager_base.h" +#include "media/audio/audio_util.h" +#if defined(OS_LINUX) || defined(OS_OPENBSD) +#include "media/audio/linux/audio_manager_linux.h" +#elif defined(OS_MACOSX) +#include "media/audio/mac/audio_manager_mac.h" +#elif defined(OS_WIN) +#include "media/audio/win/audio_manager_win.h" +#endif +#include "media/base/seekable_buffer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_LINUX) || defined(OS_OPENBSD) +typedef AudioManagerLinux AudioManagerAnyPlatform; +#elif defined(OS_MACOSX) +typedef AudioManagerMac AudioManagerAnyPlatform; +#elif defined(OS_WIN) +typedef AudioManagerWin AudioManagerAnyPlatform; +#endif + +using base::win::ScopedCOMInitializer; + +namespace { +// Limits the number of delay measurements we can store in an array and +// then write to file at end of the WASAPIAudioInputOutputFullDuplex test. +static const size_t kMaxDelayMeasurements = 1000; + +// Name of the output text file. The output file will be stored in the +// directory containing media_unittests.exe. +// Example: \src\build\Debug\audio_delay_values_ms.txt. +// See comments for the WASAPIAudioInputOutputFullDuplex test for more details +// about the file format. +static const char* kDelayValuesFileName = "audio_delay_values_ms.txt"; + +// Contains delay values which are reported during the full-duplex test. +// Total delay = |buffer_delay_ms| + |input_delay_ms| + |output_delay_ms|. +struct AudioDelayState { + AudioDelayState() + : delta_time_ms(0), + buffer_delay_ms(0), + input_delay_ms(0), + output_delay_ms(0) { + } + + // Time in milliseconds since last delay report. Typical value is ~10 [ms]. + int delta_time_ms; + + // Size of internal sync buffer. Typical value is ~0 [ms]. + int buffer_delay_ms; + + // Reported capture/input delay. Typical value is ~10 [ms]. + int input_delay_ms; + + // Reported render/output delay. Typical value is ~40 [ms]. + int output_delay_ms; +}; + +// This class mocks the platform specific audio manager and overrides +// the GetMessageLoop() method to ensure that we can run our tests on +// the main thread instead of the audio thread. +class MockAudioManager : public AudioManagerAnyPlatform { + public: + MockAudioManager() { + Init(); + } + + virtual ~MockAudioManager() {} + + virtual MessageLoop* GetMessageLoop() OVERRIDE { + return MessageLoop::current(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockAudioManager); +}; + +// Test fixture class. +class AudioLowLatencyInputOutputTest : public testing::Test { + protected: + AudioLowLatencyInputOutputTest() + : mock_audio_manager_(new MockAudioManager()) { + } + + virtual ~AudioLowLatencyInputOutputTest() { } + + AudioManager* audio_manager() { + return mock_audio_manager_.get(); + } + + MessageLoopForUI* message_loop() { + return &message_loop_; + } + + // Convenience method which ensures that we are not running on the build + // bots and that at least one valid input and output device can be found. + bool CanRunAudioTests() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + if (env->HasVar("CHROME_HEADLESS")) + return false; + return (audio_manager()->HasAudioInputDevices() && + audio_manager()->HasAudioOutputDevices()); + } + + private: + MessageLoopForUI message_loop_; + scoped_refptr<MockAudioManager> mock_audio_manager_; + + DISALLOW_COPY_AND_ASSIGN(AudioLowLatencyInputOutputTest); +}; + +} // namespace + +// This audio source/sink implementation should be used for manual tests +// only since delay measurements are stored on an output text file. +// All incoming/recorded audio packets are stored in an intermediate media +// buffer which the renderer reads from when it needs audio for playout. +// The total effect is that recorded audio is played out in loop back using +// a sync buffer as temporary storage. +class FullDuplexAudioSinkSource + + : public AudioInputStream::AudioInputCallback, + public AudioOutputStream::AudioSourceCallback { + public: + FullDuplexAudioSinkSource(int sample_rate, + int samples_per_packet, + int channels) + : sample_rate_(sample_rate), + samples_per_packet_(samples_per_packet), + channels_(channels), + input_elements_to_write_(0), + output_elements_to_write_(0), + previous_write_time_(base::Time::Now()) { + // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM). + frame_size_ = (16 / 8) * channels_; + + // Start with the smallest possible buffer size. It will be increased + // dynamically during the test if required. + buffer_.reset( + new media::SeekableBuffer(0, samples_per_packet_ * frame_size_)); + + frames_to_ms_ = static_cast<double>(1000.0 / sample_rate_); + delay_states_.reset(new AudioDelayState[kMaxDelayMeasurements]); + } + + virtual ~FullDuplexAudioSinkSource() { + // Get complete file path to output file in the directory containing + // media_unittests.exe. Example: src/build/Debug/audio_delay_values_ms.txt. + FilePath file_name; + EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_name)); + file_name = file_name.AppendASCII(kDelayValuesFileName); + + FILE* text_file = file_util::OpenFile(file_name, "wt"); + DLOG_IF(ERROR, !text_file) << "Failed to open log file."; + LOG(INFO) << ">> Output file " << file_name.value() << " has been created."; + + // Write the array which contains time-stamps, buffer size and + // audio delays values to a text file. + size_t elements_written = 0; + while (elements_written < + std::min(input_elements_to_write_, output_elements_to_write_)) { + const AudioDelayState state = delay_states_[elements_written]; + fprintf(text_file, "%d %d %d %d\n", + state.delta_time_ms, + state.buffer_delay_ms, + state.input_delay_ms, + state.output_delay_ms); + ++elements_written; + } + + file_util::CloseFile(text_file); + } + + // AudioInputStream::AudioInputCallback. + virtual void OnData(AudioInputStream* stream, + const uint8* src, uint32 size, + uint32 hardware_delay_bytes) OVERRIDE { + base::AutoLock lock(lock_); + + // Update three components in the AudioDelayState for this recorded + // audio packet. + base::Time now_time = base::Time::Now(); + int diff = (now_time - previous_write_time_).InMilliseconds(); + previous_write_time_ = now_time; + if (input_elements_to_write_ < kMaxDelayMeasurements) { + delay_states_[input_elements_to_write_].delta_time_ms = diff; + delay_states_[input_elements_to_write_].buffer_delay_ms = + BytesToMilliseconds(buffer_->forward_bytes()); + delay_states_[input_elements_to_write_].input_delay_ms = + BytesToMilliseconds(hardware_delay_bytes); + ++input_elements_to_write_; + } + + // Store the captured audio packet in a seekable media buffer. + if (!buffer_->Append(src, size)) { + // An attempt to write outside the buffer limits has been made. + // Double the buffer capacity to ensure that we have a buffer large + // enough to handle the current sample test scenario. + buffer_->set_forward_capacity(2 * buffer_->forward_capacity()); + buffer_->Clear(); + } + } + + virtual void OnClose(AudioInputStream* stream) OVERRIDE {} + virtual void OnError(AudioInputStream* stream, int code) OVERRIDE {} + + // AudioOutputStream::AudioSourceCallback. + virtual uint32 OnMoreData(AudioOutputStream* stream, + uint8* dest, uint32 max_size, + AudioBuffersState buffers_state) OVERRIDE { + base::AutoLock lock(lock_); + + // Update one component in the AudioDelayState for the packet + // which is about to be played out. + if (output_elements_to_write_ < kMaxDelayMeasurements) { + int output_delay_bytes = buffers_state.hardware_delay_bytes; +#if defined(OS_WIN) + // Special fix for Windows in combination with Wave where the + // pending bytes field of the audio buffer state is used to + // report the delay. + if (!media::IsWASAPISupported()) { + output_delay_bytes = buffers_state.pending_bytes; + } +#endif + delay_states_[output_elements_to_write_].output_delay_ms = + BytesToMilliseconds(output_delay_bytes); + ++output_elements_to_write_; + } + + // Read the data from the seekable media buffer which contains + // captured data at the same size and sample rate as the output side. + return buffer_->Read(dest, max_size); + } + + virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE {} + virtual void WaitTillDataReady() OVERRIDE {} + + protected: + // Converts from bytes to milliseconds taking the sample rate and size + // of an audio frame into account. + int BytesToMilliseconds(uint32 delay_bytes) const { + return static_cast<int>((delay_bytes / frame_size_) * frames_to_ms_ + 0.5); + } + + private: + base::Lock lock_; + scoped_ptr<media::SeekableBuffer> buffer_; + int sample_rate_; + int samples_per_packet_; + int channels_; + size_t frame_size_; + double frames_to_ms_; + scoped_array<AudioDelayState> delay_states_; + size_t input_elements_to_write_; + size_t output_elements_to_write_; + base::Time previous_write_time_; +}; + +class AudioInputStreamTraits { + public: + typedef AudioInputStream StreamType; + + static int HardwareSampleRate() { + return static_cast<int>(media::GetAudioInputHardwareSampleRate()); + } + + static StreamType* CreateStream(AudioManager* audio_manager, + const AudioParameters& params) { + return audio_manager->MakeAudioInputStream(params, + AudioManagerBase::kDefaultDeviceId); + } +}; + +class AudioOutputStreamTraits { + public: + typedef AudioOutputStream StreamType; + + static int HardwareSampleRate() { + return static_cast<int>(media::GetAudioHardwareSampleRate()); + } + + static StreamType* CreateStream(AudioManager* audio_manager, + const AudioParameters& params) { + return audio_manager->MakeAudioOutputStream(params); + } +}; + +// Traits template holding a trait of StreamType. It encapsulates +// AudioInputStream and AudioOutputStream stream types. +template <typename StreamTraits> +class StreamWrapper { + public: + typedef typename StreamTraits::StreamType StreamType; + + explicit StreamWrapper(AudioManager* audio_manager) + : com_init_(ScopedCOMInitializer::kMTA), + audio_manager_(audio_manager), + format_(AudioParameters::AUDIO_PCM_LOW_LATENCY), + channel_layout_(CHANNEL_LAYOUT_STEREO), + bits_per_sample_(16) { + // Use native/mixing sample rate and N*10ms frame size as default, + // where N is platform dependent. + sample_rate_ = StreamTraits::HardwareSampleRate(); +#if defined(OS_MACOSX) + // 10ms buffer size works well for 44.1, 48, 96 and 192kHz. + samples_per_packet_ = (sample_rate_ / 100); +#elif defined(OS_LINUX) || defined(OS_OPENBSD) + // 10ms buffer size works well for 44.1, 48, 96 and 192kHz. + samples_per_packet_ = (sample_rate_ / 100); +#elif defined(OS_WIN) + if (media::IsWASAPISupported()) { + // WASAPI is supported for Windows Vista and higher. + if (sample_rate_ == 44100) { + // Tests have shown that the shared mode WASAPI implementation + // works bests for a period size of ~10.15873 ms when the sample + // rate is 44.1kHz. + samples_per_packet_ = 448; + } else { + // 10ms buffer size works well for 48, 96 and 192kHz. + samples_per_packet_ = (sample_rate_ / 100); + } + } else { + // Low-latency Wave implementation needs 30ms buffer size to + // ensure glitch-free output audio. + samples_per_packet_ = 3 * (sample_rate_ / 100); + } +#endif + } + + virtual ~StreamWrapper() {} + + // Creates an Audio[Input|Output]Stream stream object using default + // parameters. + StreamType* Create() { + return CreateStream(); + } + + // Creates Audio[Input|Output]Stream object using non-default + // parameters where the frame size is modified. + StreamType* Create(int samples_per_packet) { + samples_per_packet_ = samples_per_packet; + return CreateStream(); + } + + AudioParameters::Format format() const { return format_; } + int channels() const { + return ChannelLayoutToChannelCount(channel_layout_); + } + int bits_per_sample() const { return bits_per_sample_; } + int sample_rate() const { return sample_rate_; } + int samples_per_packet() const { return samples_per_packet_; } + + private: + StreamType* CreateStream() { + StreamType* stream = StreamTraits::CreateStream(audio_manager_, + AudioParameters(format_, channel_layout_, sample_rate_, + bits_per_sample_, samples_per_packet_)); + EXPECT_TRUE(stream); + return stream; + } + + ScopedCOMInitializer com_init_; + AudioManager* audio_manager_; + AudioParameters::Format format_; + ChannelLayout channel_layout_; + int bits_per_sample_; + int sample_rate_; + int samples_per_packet_; +}; + +typedef StreamWrapper<AudioInputStreamTraits> AudioInputStreamWrapper; +typedef StreamWrapper<AudioOutputStreamTraits> AudioOutputStreamWrapper; + +// This test is intended for manual tests and should only be enabled +// when it is required to make a real-time test of audio in full duplex and +// at the same time create a text file which contains measured delay values. +// The file can later be analyzed off line using e.g. MATLAB. +// MATLAB example: +// D=load('audio_delay_values_ms.txt'); +// x=cumsum(D(:,1)); +// plot(x, D(:,2), x, D(:,3), x, D(:,4), x, D(:,2)+D(:,3)+D(:,4)); +// axis([0, max(x), 0, max(D(:,2)+D(:,3)+D(:,4))+10]); +// legend('buffer delay','input delay','output delay','total delay'); +// xlabel('time [msec]') +// ylabel('delay [msec]') +// title('Full-duplex audio delay measurement'); +TEST_F(AudioLowLatencyInputOutputTest, DISABLED_FullDuplexDelayMeasurement) { + if (!CanRunAudioTests()) + return; + + AudioInputStreamWrapper aisw(audio_manager()); + AudioInputStream* ais = aisw.Create(); + EXPECT_TRUE(ais); + + AudioOutputStreamWrapper aosw(audio_manager()); + AudioOutputStream* aos = aosw.Create(); + EXPECT_TRUE(aos); + + // This test only supports identical parameters in both directions. + // TODO(henrika): it is possible to cut delay here by using different + // buffer sizes for input and output. + if (aisw.sample_rate() != aosw.sample_rate() || + aisw.samples_per_packet() != aosw.samples_per_packet() || + aisw.channels()!= aosw.channels() || + aisw.bits_per_sample() != aosw.bits_per_sample()) { + LOG(ERROR) << "This test requires symmetric input and output parameters. " + "Ensure that sample rate and number of channels are identical in " + "both directions"; + aos->Close(); + ais->Close(); + return; + } + + EXPECT_TRUE(ais->Open()); + EXPECT_TRUE(aos->Open()); + + FullDuplexAudioSinkSource full_duplex( + aisw.sample_rate(), aisw.samples_per_packet(), aisw.channels()); + + LOG(INFO) << ">> You should now be able to hear yourself in loopback..."; + DLOG(INFO) << " sample_rate : " << aisw.sample_rate(); + DLOG(INFO) << " samples_per_packet: " << aisw.samples_per_packet(); + DLOG(INFO) << " channels : " << aisw.channels(); + + ais->Start(&full_duplex); + aos->Start(&full_duplex); + + // Wait for approximately 10 seconds. The user shall hear his own voice + // in loop back during this time. At the same time, delay recordings are + // performed and stored in the output text file. + message_loop()->PostDelayedTask(FROM_HERE, + MessageLoop::QuitClosure(), TestTimeouts::action_timeout_ms()); + message_loop()->Run(); + + aos->Stop(); + ais->Stop(); + + // All Close() operations that run on the mocked audio thread, + // should be synchronous and not post additional close tasks to + // mocked the audio thread. Hence, there is no need to call + // message_loop()->RunAllPending() after the Close() methods. + aos->Close(); + ais->Close(); +} diff --git a/media/audio/mac/audio_manager_mac.h b/media/audio/mac/audio_manager_mac.h index c1e1659..2060b66 100644 --- a/media/audio/mac/audio_manager_mac.h +++ b/media/audio/mac/audio_manager_mac.h @@ -34,9 +34,11 @@ class AudioManagerMac : public AudioManagerBase { void ReleaseOutputStream(AudioOutputStream* stream); void ReleaseInputStream(AudioInputStream* stream); - private: + protected: virtual ~AudioManagerMac(); + private: + // Number of currently open output streams. size_t num_output_streams_; diff --git a/media/audio/win/audio_manager_win.cc b/media/audio/win/audio_manager_win.cc index f563d2c..7ee2a76 100644 --- a/media/audio/win/audio_manager_win.cc +++ b/media/audio/win/audio_manager_win.cc @@ -110,6 +110,8 @@ AudioManagerWin::AudioManagerWin() } AudioManagerWin::~AudioManagerWin() { + // All output streams should be released upon termination. + DCHECK_EQ(0, num_output_streams_); } bool AudioManagerWin::HasAudioOutputDevices() { diff --git a/media/audio/win/audio_manager_win.h b/media/audio/win/audio_manager_win.h index 6c519ee..594e535 100644 --- a/media/audio/win/audio_manager_win.h +++ b/media/audio/win/audio_manager_win.h @@ -43,6 +43,9 @@ class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase { // Called internally by the audio stream when it has been closed. void ReleaseInputStream(AudioInputStream* stream); + protected: + virtual ~AudioManagerWin(); + private: enum EnumerationType { kUninitializedEnumeration = 0, @@ -50,8 +53,6 @@ class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase { kWaveEnumeration, }; - virtual ~AudioManagerWin(); - // Allow unit test to modify the utilized enumeration API. friend class AudioInputDeviceTest; diff --git a/media/media.gyp b/media/media.gyp index b20e630..79f6c53 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -580,6 +580,7 @@ 'audio/audio_input_controller_unittest.cc', 'audio/audio_input_device_unittest.cc', 'audio/audio_input_unittest.cc', + 'audio/audio_low_latency_input_output_unittest.cc', 'audio/audio_output_controller_unittest.cc', 'audio/audio_output_proxy_unittest.cc', 'audio/audio_parameters_unittest.cc', |