blob: 463321a22b0df40d104ffd2581d6d384d3955520 [file] [log] [blame]
// 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 "base/basictypes.h"
#include "base/environment.h"
#include "base/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/synchronization/lock.h"
#include "base/test/test_timeouts.h"
#include "base/time.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"
#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)
#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 "base/win/scoped_com_initializer.h"
#include "media/audio/win/audio_manager_win.h"
#include "media/audio/win/core_audio_util_win.h"
#elif defined(OS_ANDROID)
#include "media/audio/android/audio_manager_android.h"
#endif
namespace media {
#if defined(OS_LINUX) || defined(OS_OPENBSD)
typedef AudioManagerLinux AudioManagerAnyPlatform;
#elif defined(OS_MACOSX)
typedef AudioManagerMac AudioManagerAnyPlatform;
#elif defined(OS_WIN)
typedef AudioManagerWin AudioManagerAnyPlatform;
#elif defined(OS_ANDROID)
typedef AudioManagerAndroid AudioManagerAnyPlatform;
#endif
// 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() {}
virtual ~MockAudioManager() {}
virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE {
return MessageLoop::current()->message_loop_proxy();
}
private:
DISALLOW_COPY_AND_ASSIGN(MockAudioManager);
};
// Test fixture class.
class AudioLowLatencyInputOutputTest : public testing::Test {
protected:
AudioLowLatencyInputOutputTest() {}
virtual ~AudioLowLatencyInputOutputTest() {}
AudioManager* audio_manager() { return &mock_audio_manager_; }
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() {
bool input = audio_manager()->HasAudioInputDevices();
bool output = audio_manager()->HasAudioOutputDevices();
LOG_IF(WARNING, !input) << "No input device detected.";
LOG_IF(WARNING, !output) << "No output device detected.";
return input && output;
}
private:
MessageLoopForUI message_loop_;
MockAudioManager mock_audio_manager_;
DISALLOW_COPY_AND_ASSIGN(AudioLowLatencyInputOutputTest);
};
// 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,
double volume) 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 int OnMoreData(AudioBus* audio_bus,
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 (!CoreAudioUtil::IsSupported()) {
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_;
}
int size;
const uint8* source;
// Read the data from the seekable media buffer which contains
// captured data at the same size and sample rate as the output side.
if (buffer_->GetCurrentChunk(&source, &size) && size > 0) {
EXPECT_EQ(channels_, audio_bus->channels());
size = std::min(audio_bus->frames() * frame_size_, size);
EXPECT_EQ(static_cast<size_t>(size) % sizeof(*audio_bus->channel(0)), 0U);
audio_bus->FromInterleaved(
source, size / frame_size_, frame_size_ / channels_);
buffer_->Seek(size);
return size / frame_size_;
}
return 0;
}
virtual int OnMoreIOData(AudioBus* source,
AudioBus* dest,
AudioBuffersState buffers_state) OVERRIDE {
NOTREACHED();
return 0;
}
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_;
int 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(
AudioManagerBase::kDefaultDeviceId));
}
// TODO(henrika): add support for GetAudioInputHardwareBufferSize in media.
static int HardwareBufferSize() {
return static_cast<int>(media::GetAudioHardwareBufferSize());
}
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 int HardwareBufferSize() {
return static_cast<int>(media::GetAudioHardwareBufferSize());
}
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)
:
#if defined(OS_WIN)
com_init_(base::win::ScopedCOMInitializer::kMTA),
#endif
audio_manager_(audio_manager),
format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
#if defined(OS_ANDROID)
channel_layout_(CHANNEL_LAYOUT_MONO),
#else
channel_layout_(CHANNEL_LAYOUT_STEREO),
#endif
bits_per_sample_(16) {
// Use the preferred sample rate.
sample_rate_ = StreamTraits::HardwareSampleRate();
// Use the preferred buffer size. Note that the input side uses the same
// size as the output side in this implementation.
samples_per_packet_ = StreamTraits::HardwareBufferSize();
}
virtual ~StreamWrapper() {}
// Creates an Audio[Input|Output]Stream stream object using default
// parameters.
StreamType* Create() {
return CreateStream();
}
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;
}
#if defined(OS_WIN)
base::win::ScopedCOMInitializer com_init_;
#endif
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());
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()->RunUntilIdle() after the Close() methods.
aos->Close();
ais->Close();
}
} // namespace media