audio_low_latency_input_win_unittest.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 <windows.h> 6#include <mmsystem.h> 7 8#include "base/basictypes.h" 9#include "base/environment.h" 10#include "base/file_util.h" 11#include "base/memory/scoped_ptr.h" 12#include "base/message_loop.h" 13#include "base/path_service.h" 14#include "base/test/test_timeouts.h" 15#include "base/win/scoped_com_initializer.h" 16#include "media/audio/audio_io.h" 17#include "media/audio/audio_manager_base.h" 18#include "media/audio/win/audio_low_latency_input_win.h" 19#include "media/audio/win/core_audio_util_win.h" 20#include "media/base/seekable_buffer.h" 21#include "testing/gmock/include/gmock/gmock.h" 22#include "testing/gtest/include/gtest/gtest.h" 23 24using base::win::ScopedCOMInitializer; 25using ::testing::_; 26using ::testing::AnyNumber; 27using ::testing::AtLeast; 28using ::testing::Gt; 29using ::testing::NotNull; 30 31namespace media { 32 33ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop) { 34 if (++*count >= limit) { 35 loop->PostTask(FROM_HERE, MessageLoop::QuitClosure()); 36 } 37} 38 39class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { 40 public: 41 MOCK_METHOD5(OnData, void(AudioInputStream* stream, 42 const uint8* src, uint32 size, 43 uint32 hardware_delay_bytes, double volume)); 44 MOCK_METHOD1(OnClose, void(AudioInputStream* stream)); 45 MOCK_METHOD1(OnError, void(AudioInputStream* stream)); 46}; 47 48// This audio sink implementation should be used for manual tests only since 49// the recorded data is stored on a raw binary data file. 50class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback { 51 public: 52 // Allocate space for ~10 seconds of data @ 48kHz in stereo: 53 // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes. 54 static const size_t kMaxBufferSize = 2 * 2 * 480 * 100 * 10; 55 56 explicit WriteToFileAudioSink(const char* file_name) 57 : buffer_(0, kMaxBufferSize), 58 bytes_to_write_(0) { 59 base::FilePath file_path; 60 EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_path)); 61 file_path = file_path.AppendASCII(file_name); 62 binary_file_ = file_util::OpenFile(file_path, "wb"); 63 DLOG_IF(ERROR, !binary_file_) << "Failed to open binary PCM data file."; 64 LOG(INFO) << ">> Output file: " << file_path.value() 65 << " has been created."; 66 } 67 68 virtual ~WriteToFileAudioSink() { 69 size_t bytes_written = 0; 70 while (bytes_written < bytes_to_write_) { 71 const uint8* chunk; 72 int chunk_size; 73 74 // Stop writing if no more data is available. 75 if (!buffer_.GetCurrentChunk(&chunk, &chunk_size)) 76 break; 77 78 // Write recorded data chunk to the file and prepare for next chunk. 79 fwrite(chunk, 1, chunk_size, binary_file_); 80 buffer_.Seek(chunk_size); 81 bytes_written += chunk_size; 82 } 83 file_util::CloseFile(binary_file_); 84 } 85 86 // AudioInputStream::AudioInputCallback implementation. 87 virtual void OnData(AudioInputStream* stream, 88 const uint8* src, 89 uint32 size, 90 uint32 hardware_delay_bytes, 91 double volume) { 92 // Store data data in a temporary buffer to avoid making blocking 93 // fwrite() calls in the audio callback. The complete buffer will be 94 // written to file in the destructor. 95 if (buffer_.Append(src, size)) { 96 bytes_to_write_ += size; 97 } 98 } 99 100 virtual void OnClose(AudioInputStream* stream) {} 101 virtual void OnError(AudioInputStream* stream) {} 102 103 private: 104 media::SeekableBuffer buffer_; 105 FILE* binary_file_; 106 size_t bytes_to_write_; 107}; 108 109// Convenience method which ensures that we are not running on the build 110// bots and that at least one valid input device can be found. We also 111// verify that we are not running on XP since the low-latency (WASAPI- 112// based) version requires Windows Vista or higher. 113static bool CanRunAudioTests(AudioManager* audio_man) { 114 if (!CoreAudioUtil::IsSupported()) { 115 LOG(WARNING) << "This tests requires Windows Vista or higher."; 116 return false; 117 } 118 // TODO(henrika): note that we use Wave today to query the number of 119 // existing input devices. 120 bool input = audio_man->HasAudioInputDevices(); 121 LOG_IF(WARNING, !input) << "No input device detected."; 122 return input; 123} 124 125// Convenience method which creates a default AudioInputStream object but 126// also allows the user to modify the default settings. 127class AudioInputStreamWrapper { 128 public: 129 explicit AudioInputStreamWrapper(AudioManager* audio_manager) 130 : com_init_(ScopedCOMInitializer::kMTA), 131 audio_man_(audio_manager), 132 format_(AudioParameters::AUDIO_PCM_LOW_LATENCY), 133 channel_layout_(CHANNEL_LAYOUT_STEREO), 134 bits_per_sample_(16) { 135 // Use native/mixing sample rate and 10ms frame size as default. 136 sample_rate_ = static_cast<int>( 137 WASAPIAudioInputStream::HardwareSampleRate( 138 AudioManagerBase::kDefaultDeviceId)); 139 samples_per_packet_ = sample_rate_ / 100; 140 } 141 142 ~AudioInputStreamWrapper() {} 143 144 // Creates AudioInputStream object using default parameters. 145 AudioInputStream* Create() { 146 return CreateInputStream(); 147 } 148 149 // Creates AudioInputStream object using non-default parameters where the 150 // frame size is modified. 151 AudioInputStream* Create(int samples_per_packet) { 152 samples_per_packet_ = samples_per_packet; 153 return CreateInputStream(); 154 } 155 156 AudioParameters::Format format() const { return format_; } 157 int channels() const { 158 return ChannelLayoutToChannelCount(channel_layout_); 159 } 160 int bits_per_sample() const { return bits_per_sample_; } 161 int sample_rate() const { return sample_rate_; } 162 int samples_per_packet() const { return samples_per_packet_; } 163 164 private: 165 AudioInputStream* CreateInputStream() { 166 AudioInputStream* ais = audio_man_->MakeAudioInputStream( 167 AudioParameters(format_, channel_layout_, sample_rate_, 168 bits_per_sample_, samples_per_packet_), 169 AudioManagerBase::kDefaultDeviceId); 170 EXPECT_TRUE(ais); 171 return ais; 172 } 173 174 ScopedCOMInitializer com_init_; 175 AudioManager* audio_man_; 176 AudioParameters::Format format_; 177 ChannelLayout channel_layout_; 178 int bits_per_sample_; 179 int sample_rate_; 180 int samples_per_packet_; 181}; 182 183// Convenience method which creates a default AudioInputStream object. 184static AudioInputStream* CreateDefaultAudioInputStream( 185 AudioManager* audio_manager) { 186 AudioInputStreamWrapper aisw(audio_manager); 187 AudioInputStream* ais = aisw.Create(); 188 return ais; 189} 190 191// Verify that we can retrieve the current hardware/mixing sample rate 192// for all available input devices. 193TEST(WinAudioInputTest, WASAPIAudioInputStreamHardwareSampleRate) { 194 scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); 195 if (!CanRunAudioTests(audio_manager.get())) 196 return; 197 198 ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA); 199 200 // Retrieve a list of all available input devices. 201 media::AudioDeviceNames device_names; 202 audio_manager->GetAudioInputDeviceNames(&device_names); 203 204 // Scan all available input devices and repeat the same test for all of them. 205 for (media::AudioDeviceNames::const_iterator it = device_names.begin(); 206 it != device_names.end(); ++it) { 207 // Retrieve the hardware sample rate given a specified audio input device. 208 // TODO(tommi): ensure that we don't have to cast here. 209 int fs = static_cast<int>(WASAPIAudioInputStream::HardwareSampleRate( 210 it->unique_id)); 211 EXPECT_GE(fs, 0); 212 } 213} 214 215// Test Create(), Close() calling sequence. 216TEST(WinAudioInputTest, WASAPIAudioInputStreamCreateAndClose) { 217 scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); 218 if (!CanRunAudioTests(audio_manager.get())) 219 return; 220 AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); 221 ais->Close(); 222} 223 224// Test Open(), Close() calling sequence. 225TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenAndClose) { 226 scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); 227 if (!CanRunAudioTests(audio_manager.get())) 228 return; 229 AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); 230 EXPECT_TRUE(ais->Open()); 231 ais->Close(); 232} 233 234// Test Open(), Start(), Close() calling sequence. 235TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) { 236 scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); 237 if (!CanRunAudioTests(audio_manager.get())) 238 return; 239 AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); 240 EXPECT_TRUE(ais->Open()); 241 MockAudioInputCallback sink; 242 ais->Start(&sink); 243 EXPECT_CALL(sink, OnClose(ais)) 244 .Times(1); 245 ais->Close(); 246} 247 248// Test Open(), Start(), Stop(), Close() calling sequence. 249TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) { 250 scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); 251 if (!CanRunAudioTests(audio_manager.get())) 252 return; 253 AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); 254 EXPECT_TRUE(ais->Open()); 255 MockAudioInputCallback sink; 256 ais->Start(&sink); 257 ais->Stop(); 258 EXPECT_CALL(sink, OnClose(ais)) 259 .Times(1); 260 ais->Close(); 261} 262 263// Test some additional calling sequences. 264TEST(WinAudioInputTest, WASAPIAudioInputStreamMiscCallingSequences) { 265 scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); 266 if (!CanRunAudioTests(audio_manager.get())) 267 return; 268 AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); 269 WASAPIAudioInputStream* wais = static_cast<WASAPIAudioInputStream*>(ais); 270 271 // Open(), Open() should fail the second time. 272 EXPECT_TRUE(ais->Open()); 273 EXPECT_FALSE(ais->Open()); 274 275 MockAudioInputCallback sink; 276 277 // Start(), Start() is a valid calling sequence (second call does nothing). 278 ais->Start(&sink); 279 EXPECT_TRUE(wais->started()); 280 ais->Start(&sink); 281 EXPECT_TRUE(wais->started()); 282 283 // Stop(), Stop() is a valid calling sequence (second call does nothing). 284 ais->Stop(); 285 EXPECT_FALSE(wais->started()); 286 ais->Stop(); 287 EXPECT_FALSE(wais->started()); 288 289 EXPECT_CALL(sink, OnClose(ais)) 290 .Times(1); 291 ais->Close(); 292} 293 294TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) { 295 scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); 296 if (!CanRunAudioTests(audio_manager.get())) 297 return; 298 299 int count = 0; 300 MessageLoopForUI loop; 301 302 // 10 ms packet size. 303 304 // Create default WASAPI input stream which records in stereo using 305 // the shared mixing rate. The default buffer size is 10ms. 306 AudioInputStreamWrapper aisw(audio_manager.get()); 307 AudioInputStream* ais = aisw.Create(); 308 EXPECT_TRUE(ais->Open()); 309 310 MockAudioInputCallback sink; 311 312 // Derive the expected size in bytes of each recorded packet. 313 uint32 bytes_per_packet = aisw.channels() * aisw.samples_per_packet() * 314 (aisw.bits_per_sample() / 8); 315 316 // We use 10ms packets and will run the test until ten packets are received. 317 // All should contain valid packets of the same size and a valid delay 318 // estimate. 319 EXPECT_CALL(sink, OnData( 320 ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) 321 .Times(AtLeast(10)) 322 .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); 323 ais->Start(&sink); 324 loop.Run(); 325 ais->Stop(); 326 327 // Store current packet size (to be used in the subsequent tests). 328 int samples_per_packet_10ms = aisw.samples_per_packet(); 329 330 EXPECT_CALL(sink, OnClose(ais)) 331 .Times(1); 332 ais->Close(); 333 334 // 20 ms packet size. 335 336 count = 0; 337 ais = aisw.Create(2 * samples_per_packet_10ms); 338 EXPECT_TRUE(ais->Open()); 339 bytes_per_packet = aisw.channels() * aisw.samples_per_packet() * 340 (aisw.bits_per_sample() / 8); 341 342 EXPECT_CALL(sink, OnData( 343 ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) 344 .Times(AtLeast(10)) 345 .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); 346 ais->Start(&sink); 347 loop.Run(); 348 ais->Stop(); 349 350 EXPECT_CALL(sink, OnClose(ais)) 351 .Times(1); 352 ais->Close(); 353 354 // 5 ms packet size. 355 356 count = 0; 357 ais = aisw.Create(samples_per_packet_10ms / 2); 358 EXPECT_TRUE(ais->Open()); 359 bytes_per_packet = aisw.channels() * aisw.samples_per_packet() * 360 (aisw.bits_per_sample() / 8); 361 362 EXPECT_CALL(sink, OnData( 363 ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) 364 .Times(AtLeast(10)) 365 .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); 366 ais->Start(&sink); 367 loop.Run(); 368 ais->Stop(); 369 370 EXPECT_CALL(sink, OnClose(ais)) 371 .Times(1); 372 ais->Close(); 373} 374 375// This test is intended for manual tests and should only be enabled 376// when it is required to store the captured data on a local file. 377// By default, GTest will print out YOU HAVE 1 DISABLED TEST. 378// To include disabled tests in test execution, just invoke the test program 379// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS 380// environment variable to a value greater than 0. 381TEST(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) { 382 scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); 383 if (!CanRunAudioTests(audio_manager.get())) 384 return; 385 386 // Name of the output PCM file containing captured data. The output file 387 // will be stored in the directory containing 'media_unittests.exe'. 388 // Example of full name: \src\build\Debug\out_stereo_10sec.pcm. 389 const char* file_name = "out_stereo_10sec.pcm"; 390 391 AudioInputStreamWrapper aisw(audio_manager.get()); 392 AudioInputStream* ais = aisw.Create(); 393 EXPECT_TRUE(ais->Open()); 394 395 LOG(INFO) << ">> Sample rate: " << aisw.sample_rate() << " [Hz]"; 396 WriteToFileAudioSink file_sink(file_name); 397 LOG(INFO) << ">> Speak into the default microphone while recording."; 398 ais->Start(&file_sink); 399 base::PlatformThread::Sleep(TestTimeouts::action_timeout()); 400 ais->Stop(); 401 LOG(INFO) << ">> Recording has stopped."; 402 ais->Close(); 403} 404 405} // namespace media 406