summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorhenrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-14 14:17:49 +0000
committerhenrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-14 14:17:49 +0000
commitc795e6105d93011bd7b84d0508526f88f2c3aa72 (patch)
treedf7f9b20e5407f3e028fa03afe4afc64d500bfcd /media
parentb4821bb7a533238f2d5ff81c05296475267e55f3 (diff)
downloadchromium_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.cc459
-rw-r--r--media/audio/mac/audio_manager_mac.h4
-rw-r--r--media/audio/win/audio_manager_win.cc2
-rw-r--r--media/audio/win/audio_manager_win.h5
-rw-r--r--media/media.gyp1
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',