audio_low_latency_input_output_unittest.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/basictypes.h" 6#include "base/environment.h" 7#include "base/file_util.h" 8#include "base/memory/scoped_ptr.h" 9#include "base/message_loop.h" 10#include "base/path_service.h" 11#include "base/synchronization/lock.h" 12#include "base/test/test_timeouts.h" 13#include "base/time.h" 14#include "build/build_config.h" 15#include "media/audio/audio_io.h" 16#include "media/audio/audio_manager_base.h" 17#include "media/audio/audio_util.h" 18#include "media/base/seekable_buffer.h" 19#include "testing/gmock/include/gmock/gmock.h" 20#include "testing/gtest/include/gtest/gtest.h" 21 22#if defined(OS_LINUX) || defined(OS_OPENBSD) 23#include "media/audio/linux/audio_manager_linux.h" 24#elif defined(OS_MACOSX) 25#include "media/audio/mac/audio_manager_mac.h" 26#elif defined(OS_WIN) 27#include "base/win/scoped_com_initializer.h" 28#include "media/audio/win/audio_manager_win.h" 29#elif defined(OS_ANDROID) 30#include "media/audio/android/audio_manager_android.h" 31#endif 32 33namespace media { 34 35#if defined(OS_LINUX) || defined(OS_OPENBSD) 36typedef AudioManagerLinux AudioManagerAnyPlatform; 37#elif defined(OS_MACOSX) 38typedef AudioManagerMac AudioManagerAnyPlatform; 39#elif defined(OS_WIN) 40typedef AudioManagerWin AudioManagerAnyPlatform; 41#elif defined(OS_ANDROID) 42typedef AudioManagerAndroid AudioManagerAnyPlatform; 43#endif 44 45// Limits the number of delay measurements we can store in an array and 46// then write to file at end of the WASAPIAudioInputOutputFullDuplex test. 47static const size_t kMaxDelayMeasurements = 1000; 48 49// Name of the output text file. The output file will be stored in the 50// directory containing media_unittests.exe. 51// Example: \src\build\Debug\audio_delay_values_ms.txt. 52// See comments for the WASAPIAudioInputOutputFullDuplex test for more details 53// about the file format. 54static const char* kDelayValuesFileName = "audio_delay_values_ms.txt"; 55 56// Contains delay values which are reported during the full-duplex test. 57// Total delay = |buffer_delay_ms| + |input_delay_ms| + |output_delay_ms|. 58struct AudioDelayState { 59 AudioDelayState() 60 : delta_time_ms(0), 61 buffer_delay_ms(0), 62 input_delay_ms(0), 63 output_delay_ms(0) { 64 } 65 66 // Time in milliseconds since last delay report. Typical value is ~10 [ms]. 67 int delta_time_ms; 68 69 // Size of internal sync buffer. Typical value is ~0 [ms]. 70 int buffer_delay_ms; 71 72 // Reported capture/input delay. Typical value is ~10 [ms]. 73 int input_delay_ms; 74 75 // Reported render/output delay. Typical value is ~40 [ms]. 76 int output_delay_ms; 77}; 78 79// This class mocks the platform specific audio manager and overrides 80// the GetMessageLoop() method to ensure that we can run our tests on 81// the main thread instead of the audio thread. 82class MockAudioManager : public AudioManagerAnyPlatform { 83 public: 84 MockAudioManager() {} 85 virtual ~MockAudioManager() {} 86 87 virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE { 88 return MessageLoop::current()->message_loop_proxy(); 89 } 90 91 private: 92 DISALLOW_COPY_AND_ASSIGN(MockAudioManager); 93}; 94 95// Test fixture class. 96class AudioLowLatencyInputOutputTest : public testing::Test { 97 protected: 98 AudioLowLatencyInputOutputTest() {} 99 100 virtual ~AudioLowLatencyInputOutputTest() {} 101 102 AudioManager* audio_manager() { return &mock_audio_manager_; } 103 MessageLoopForUI* message_loop() { return &message_loop_; } 104 105 // Convenience method which ensures that we are not running on the build 106 // bots and that at least one valid input and output device can be found. 107 bool CanRunAudioTests() { 108 bool input = audio_manager()->HasAudioInputDevices(); 109 bool output = audio_manager()->HasAudioOutputDevices(); 110 LOG_IF(WARNING, !input) << "No input device detected."; 111 LOG_IF(WARNING, !output) << "No output device detected."; 112 return input && output; 113 } 114 115 private: 116 MessageLoopForUI message_loop_; 117 MockAudioManager mock_audio_manager_; 118 119 DISALLOW_COPY_AND_ASSIGN(AudioLowLatencyInputOutputTest); 120}; 121 122// This audio source/sink implementation should be used for manual tests 123// only since delay measurements are stored on an output text file. 124// All incoming/recorded audio packets are stored in an intermediate media 125// buffer which the renderer reads from when it needs audio for playout. 126// The total effect is that recorded audio is played out in loop back using 127// a sync buffer as temporary storage. 128class FullDuplexAudioSinkSource 129 : public AudioInputStream::AudioInputCallback, 130 public AudioOutputStream::AudioSourceCallback { 131 public: 132 FullDuplexAudioSinkSource(int sample_rate, 133 int samples_per_packet, 134 int channels) 135 : sample_rate_(sample_rate), 136 samples_per_packet_(samples_per_packet), 137 channels_(channels), 138 input_elements_to_write_(0), 139 output_elements_to_write_(0), 140 previous_write_time_(base::Time::Now()) { 141 // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM). 142 frame_size_ = (16 / 8) * channels_; 143 144 // Start with the smallest possible buffer size. It will be increased 145 // dynamically during the test if required. 146 buffer_.reset( 147 new media::SeekableBuffer(0, samples_per_packet_ * frame_size_)); 148 149 frames_to_ms_ = static_cast<double>(1000.0 / sample_rate_); 150 delay_states_.reset(new AudioDelayState[kMaxDelayMeasurements]); 151 } 152 153 virtual ~FullDuplexAudioSinkSource() { 154 // Get complete file path to output file in the directory containing 155 // media_unittests.exe. Example: src/build/Debug/audio_delay_values_ms.txt. 156 FilePath file_name; 157 EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_name)); 158 file_name = file_name.AppendASCII(kDelayValuesFileName); 159 160 FILE* text_file = file_util::OpenFile(file_name, "wt"); 161 DLOG_IF(ERROR, !text_file) << "Failed to open log file."; 162 LOG(INFO) << ">> Output file " << file_name.value() << " has been created."; 163 164 // Write the array which contains time-stamps, buffer size and 165 // audio delays values to a text file. 166 size_t elements_written = 0; 167 while (elements_written < 168 std::min(input_elements_to_write_, output_elements_to_write_)) { 169 const AudioDelayState state = delay_states_[elements_written]; 170 fprintf(text_file, "%d %d %d %d\n", 171 state.delta_time_ms, 172 state.buffer_delay_ms, 173 state.input_delay_ms, 174 state.output_delay_ms); 175 ++elements_written; 176 } 177 178 file_util::CloseFile(text_file); 179 } 180 181 // AudioInputStream::AudioInputCallback. 182 virtual void OnData(AudioInputStream* stream, 183 const uint8* src, uint32 size, 184 uint32 hardware_delay_bytes, 185 double volume) OVERRIDE { 186 base::AutoLock lock(lock_); 187 188 // Update three components in the AudioDelayState for this recorded 189 // audio packet. 190 base::Time now_time = base::Time::Now(); 191 int diff = (now_time - previous_write_time_).InMilliseconds(); 192 previous_write_time_ = now_time; 193 if (input_elements_to_write_ < kMaxDelayMeasurements) { 194 delay_states_[input_elements_to_write_].delta_time_ms = diff; 195 delay_states_[input_elements_to_write_].buffer_delay_ms = 196 BytesToMilliseconds(buffer_->forward_bytes()); 197 delay_states_[input_elements_to_write_].input_delay_ms = 198 BytesToMilliseconds(hardware_delay_bytes); 199 ++input_elements_to_write_; 200 } 201 202 // Store the captured audio packet in a seekable media buffer. 203 if (!buffer_->Append(src, size)) { 204 // An attempt to write outside the buffer limits has been made. 205 // Double the buffer capacity to ensure that we have a buffer large 206 // enough to handle the current sample test scenario. 207 buffer_->set_forward_capacity(2 * buffer_->forward_capacity()); 208 buffer_->Clear(); 209 } 210 } 211 212 virtual void OnClose(AudioInputStream* stream) OVERRIDE {} 213 virtual void OnError(AudioInputStream* stream, int code) OVERRIDE {} 214 215 // AudioOutputStream::AudioSourceCallback. 216 virtual int OnMoreData(AudioBus* audio_bus, 217 AudioBuffersState buffers_state) OVERRIDE { 218 base::AutoLock lock(lock_); 219 220 // Update one component in the AudioDelayState for the packet 221 // which is about to be played out. 222 if (output_elements_to_write_ < kMaxDelayMeasurements) { 223 int output_delay_bytes = buffers_state.hardware_delay_bytes; 224#if defined(OS_WIN) 225 // Special fix for Windows in combination with Wave where the 226 // pending bytes field of the audio buffer state is used to 227 // report the delay. 228 if (!media::IsWASAPISupported()) { 229 output_delay_bytes = buffers_state.pending_bytes; 230 } 231#endif 232 delay_states_[output_elements_to_write_].output_delay_ms = 233 BytesToMilliseconds(output_delay_bytes); 234 ++output_elements_to_write_; 235 } 236 237 int size; 238 const uint8* source; 239 // Read the data from the seekable media buffer which contains 240 // captured data at the same size and sample rate as the output side. 241 if (buffer_->GetCurrentChunk(&source, &size) && size > 0) { 242 EXPECT_EQ(channels_, audio_bus->channels()); 243 size = std::min(audio_bus->frames() * frame_size_, size); 244 EXPECT_EQ(static_cast<size_t>(size) % sizeof(*audio_bus->channel(0)), 0U); 245 audio_bus->FromInterleaved( 246 source, size / frame_size_, frame_size_ / channels_); 247 buffer_->Seek(size); 248 return size / frame_size_; 249 } 250 251 return 0; 252 } 253 254 virtual int OnMoreIOData(AudioBus* source, 255 AudioBus* dest, 256 AudioBuffersState buffers_state) OVERRIDE { 257 NOTREACHED(); 258 return 0; 259 } 260 261 virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE {} 262 virtual void WaitTillDataReady() OVERRIDE {} 263 264 protected: 265 // Converts from bytes to milliseconds taking the sample rate and size 266 // of an audio frame into account. 267 int BytesToMilliseconds(uint32 delay_bytes) const { 268 return static_cast<int>((delay_bytes / frame_size_) * frames_to_ms_ + 0.5); 269 } 270 271 private: 272 base::Lock lock_; 273 scoped_ptr<media::SeekableBuffer> buffer_; 274 int sample_rate_; 275 int samples_per_packet_; 276 int channels_; 277 int frame_size_; 278 double frames_to_ms_; 279 scoped_array<AudioDelayState> delay_states_; 280 size_t input_elements_to_write_; 281 size_t output_elements_to_write_; 282 base::Time previous_write_time_; 283}; 284 285class AudioInputStreamTraits { 286 public: 287 typedef AudioInputStream StreamType; 288 289 static int HardwareSampleRate() { 290 return static_cast<int>(media::GetAudioInputHardwareSampleRate( 291 AudioManagerBase::kDefaultDeviceId)); 292 } 293 294 // TODO(henrika): add support for GetAudioInputHardwareBufferSize in media. 295 static int HardwareBufferSize() { 296 return static_cast<int>(media::GetAudioHardwareBufferSize()); 297 } 298 299 static StreamType* CreateStream(AudioManager* audio_manager, 300 const AudioParameters& params) { 301 return audio_manager->MakeAudioInputStream(params, 302 AudioManagerBase::kDefaultDeviceId); 303 } 304}; 305 306class AudioOutputStreamTraits { 307 public: 308 typedef AudioOutputStream StreamType; 309 310 static int HardwareSampleRate() { 311 return static_cast<int>(media::GetAudioHardwareSampleRate()); 312 } 313 314 static int HardwareBufferSize() { 315 return static_cast<int>(media::GetAudioHardwareBufferSize()); 316 } 317 318 static StreamType* CreateStream(AudioManager* audio_manager, 319 const AudioParameters& params) { 320 return audio_manager->MakeAudioOutputStream(params); 321 } 322}; 323 324// Traits template holding a trait of StreamType. It encapsulates 325// AudioInputStream and AudioOutputStream stream types. 326template <typename StreamTraits> 327class StreamWrapper { 328 public: 329 typedef typename StreamTraits::StreamType StreamType; 330 331 explicit StreamWrapper(AudioManager* audio_manager) 332 : 333#if defined(OS_WIN) 334 com_init_(base::win::ScopedCOMInitializer::kMTA), 335#endif 336 audio_manager_(audio_manager), 337 format_(AudioParameters::AUDIO_PCM_LOW_LATENCY), 338#if defined(OS_ANDROID) 339 channel_layout_(CHANNEL_LAYOUT_MONO), 340#else 341 channel_layout_(CHANNEL_LAYOUT_STEREO), 342#endif 343 bits_per_sample_(16) { 344 // Use the preferred sample rate. 345 sample_rate_ = StreamTraits::HardwareSampleRate(); 346 347 // Use the preferred buffer size. Note that the input side uses the same 348 // size as the output side in this implementation. 349 samples_per_packet_ = StreamTraits::HardwareBufferSize(); 350 } 351 352 virtual ~StreamWrapper() {} 353 354 // Creates an Audio[Input|Output]Stream stream object using default 355 // parameters. 356 StreamType* Create() { 357 return CreateStream(); 358 } 359 360 int channels() const { 361 return ChannelLayoutToChannelCount(channel_layout_); 362 } 363 int bits_per_sample() const { return bits_per_sample_; } 364 int sample_rate() const { return sample_rate_; } 365 int samples_per_packet() const { return samples_per_packet_; } 366 367 private: 368 StreamType* CreateStream() { 369 StreamType* stream = StreamTraits::CreateStream(audio_manager_, 370 AudioParameters(format_, channel_layout_, sample_rate_, 371 bits_per_sample_, samples_per_packet_)); 372 EXPECT_TRUE(stream); 373 return stream; 374 } 375 376#if defined(OS_WIN) 377 base::win::ScopedCOMInitializer com_init_; 378#endif 379 380 AudioManager* audio_manager_; 381 AudioParameters::Format format_; 382 ChannelLayout channel_layout_; 383 int bits_per_sample_; 384 int sample_rate_; 385 int samples_per_packet_; 386}; 387 388typedef StreamWrapper<AudioInputStreamTraits> AudioInputStreamWrapper; 389typedef StreamWrapper<AudioOutputStreamTraits> AudioOutputStreamWrapper; 390 391// This test is intended for manual tests and should only be enabled 392// when it is required to make a real-time test of audio in full duplex and 393// at the same time create a text file which contains measured delay values. 394// The file can later be analyzed off line using e.g. MATLAB. 395// MATLAB example: 396// D=load('audio_delay_values_ms.txt'); 397// x=cumsum(D(:,1)); 398// plot(x, D(:,2), x, D(:,3), x, D(:,4), x, D(:,2)+D(:,3)+D(:,4)); 399// axis([0, max(x), 0, max(D(:,2)+D(:,3)+D(:,4))+10]); 400// legend('buffer delay','input delay','output delay','total delay'); 401// xlabel('time [msec]') 402// ylabel('delay [msec]') 403// title('Full-duplex audio delay measurement'); 404TEST_F(AudioLowLatencyInputOutputTest, DISABLED_FullDuplexDelayMeasurement) { 405 if (!CanRunAudioTests()) 406 return; 407 408 AudioInputStreamWrapper aisw(audio_manager()); 409 AudioInputStream* ais = aisw.Create(); 410 EXPECT_TRUE(ais); 411 412 AudioOutputStreamWrapper aosw(audio_manager()); 413 AudioOutputStream* aos = aosw.Create(); 414 EXPECT_TRUE(aos); 415 416 // This test only supports identical parameters in both directions. 417 // TODO(henrika): it is possible to cut delay here by using different 418 // buffer sizes for input and output. 419 if (aisw.sample_rate() != aosw.sample_rate() || 420 aisw.samples_per_packet() != aosw.samples_per_packet() || 421 aisw.channels()!= aosw.channels() || 422 aisw.bits_per_sample() != aosw.bits_per_sample()) { 423 LOG(ERROR) << "This test requires symmetric input and output parameters. " 424 "Ensure that sample rate and number of channels are identical in " 425 "both directions"; 426 aos->Close(); 427 ais->Close(); 428 return; 429 } 430 431 EXPECT_TRUE(ais->Open()); 432 EXPECT_TRUE(aos->Open()); 433 434 FullDuplexAudioSinkSource full_duplex( 435 aisw.sample_rate(), aisw.samples_per_packet(), aisw.channels()); 436 437 LOG(INFO) << ">> You should now be able to hear yourself in loopback..."; 438 DLOG(INFO) << " sample_rate : " << aisw.sample_rate(); 439 DLOG(INFO) << " samples_per_packet: " << aisw.samples_per_packet(); 440 DLOG(INFO) << " channels : " << aisw.channels(); 441 442 ais->Start(&full_duplex); 443 aos->Start(&full_duplex); 444 445 // Wait for approximately 10 seconds. The user shall hear his own voice 446 // in loop back during this time. At the same time, delay recordings are 447 // performed and stored in the output text file. 448 message_loop()->PostDelayedTask(FROM_HERE, 449 MessageLoop::QuitClosure(), TestTimeouts::action_timeout()); 450 message_loop()->Run(); 451 452 aos->Stop(); 453 ais->Stop(); 454 455 // All Close() operations that run on the mocked audio thread, 456 // should be synchronous and not post additional close tasks to 457 // mocked the audio thread. Hence, there is no need to call 458 // message_loop()->RunAllPending() after the Close() methods. 459 aos->Close(); 460 ais->Close(); 461} 462 463} // namespace media 464