Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(204)

Side by Side Diff: media/audio/win/audio_low_latency_output_win_unittest.cc

Issue 8440002: Low-latency AudioOutputStream implementation based on WASAPI for Windows. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Modified buffer handling and improved unit test Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2011 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/memory/scoped_ptr.h"
11 #include "base/message_loop.h"
12 #include "base/test/test_timeouts.h"
13 #include "base/time.h"
14 #include "base/win/scoped_com_initializer.h"
15 #include "media/audio/audio_io.h"
16 #include "media/audio/audio_manager.h"
17 #include "media/audio/win/audio_low_latency_output_win.h"
18 #include "media/base/seekable_buffer.h"
19 #include "media/base/test_data_util.h"
20 #include "testing/gmock_mutant.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23
24 using ::testing::_;
25 using ::testing::AnyNumber;
26 using ::testing::Between;
27 using ::testing::CreateFunctor;
28 using ::testing::DoAll;
29 using ::testing::Gt;
30 using ::testing::InvokeWithoutArgs;
31 using ::testing::NotNull;
32 using ::testing::Return;
33 using base::win::ScopedCOMInitializer;
34
35 namespace media {
36
37 static const char kSpeechFile_16b_s_48k[] = "speech_16b_stereo_48kHz.raw";
38 static const char kSpeechFile_16b_s_44k[] = "speech_16b_stereo_44kHz.raw";
39 static const size_t kFileDurationMs = 20000;
40
41 static const size_t kMaxDeltaSamples = 1000;
42 static const char* kDeltaTimeMsFileName = "delta_times_ms.txt";
43
44 MATCHER_P(HasValidDelay, value, "") {
45 // It is difficult to come up with a perfect test condition for the delay
46 // estimation. For now, verify that the produced output delay is always
47 // larger than the selected buffer size.
48 return arg.hardware_delay_bytes > value.hardware_delay_bytes;
49 }
50
51 class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
52 public:
53 MOCK_METHOD4(OnMoreData, uint32(AudioOutputStream* stream,
54 uint8* dest,
55 uint32 max_size,
56 AudioBuffersState buffers_state));
57 MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
58 };
59
60 // This audio source implementation should be used for manual tests only since
61 // it takes about 20 seconds to play out a file.
62 class ReadFromFileAudioSource : public AudioOutputStream::AudioSourceCallback {
63 public:
64 explicit ReadFromFileAudioSource(const std::string& name)
65 : pos_(0),
66 previous_call_time_(base::Time::Now()),
67 text_file_(fopen(kDeltaTimeMsFileName, "wt")),
Paweł Hajdan Jr. 2011/11/04 11:32:11 nit: Why not use base/file_util? Up to you, but I
tommi (sloooow) - chröme 2011/11/07 11:47:03 +1
henrika (OOO until Aug 14) 2011/11/07 14:09:45 Now uses base/file_util and base/path_service.
68 elements_to_write_(0) {
69 // Reads a test file from media/test/data directory and stores it in
70 // a scoped_array.
71 ReadTestDataFile(name, &file_, &file_size_);
72 file_size_ = file_size_;
73
74 // Creates an array that will store delta times between callbacks.
75 // The content of this array will be written to a text file at
76 // destruction and can then be used for off-line analysis of the exact
77 // timing of callbacks.
78 delta_times_.reset(new int[kMaxDeltaSamples]);
79 }
80
81 virtual ~ReadFromFileAudioSource() {
82 // Write the array which contains delta times to a text file.
83 size_t elements_written = 0;
84 while (elements_written < elements_to_write_) {
85 fprintf(text_file_, "%d\n", delta_times_[elements_written]);
86 elements_written++;
tommi (sloooow) - chröme 2011/11/07 11:47:03 nit: ++elements_written
henrika (OOO until Aug 14) 2011/11/07 14:09:45 Big difference ;-) Done.
87 }
88 fclose(text_file_);
89 }
90
91 // AudioOutputStream::AudioSourceCallback implementation.
92 virtual uint32 OnMoreData(AudioOutputStream* stream,
93 uint8* dest,
94 uint32 max_size,
95 AudioBuffersState buffers_state) {
96 // Store time difference between two successive callbacks in an array.
97 // These values will be written to a file in the destructor.
98 int diff = (base::Time::Now() - previous_call_time_).InMilliseconds();
99 previous_call_time_ = base::Time::Now();
100 if (elements_to_write_ < kMaxDeltaSamples) {
101 delta_times_[elements_to_write_] = diff;
102 elements_to_write_++;
tommi (sloooow) - chröme 2011/11/07 11:47:03 nit: and here
henrika (OOO until Aug 14) 2011/11/07 14:09:45 Done.
103 }
104
105 // Use samples read from a data file and fill up the audio buffer
106 // provided to us in the callback.
107 if (pos_ + static_cast<int>(max_size) > file_size_)
108 max_size = file_size_ - pos_;
109 if (max_size) {
110 memcpy(dest, &file_[pos_], max_size);
111 pos_ += max_size;
112 }
113 return max_size;
114 }
115
116 virtual void OnError(AudioOutputStream* stream, int code) {}
117
118 int file_size() { return file_size_; }
119
120 private:
121 scoped_array<uint8> file_;
122 scoped_array<int> delta_times_;
123 int file_size_;
124 int pos_;
125 base::Time previous_call_time_;
126 FILE* text_file_;
127 size_t elements_to_write_;
128 };
129
130 // Convenience method which ensures that we are not running on the build
131 // bots and that at least one valid output device can be found.
132 static bool CanRunAudioTests() {
133 scoped_ptr<base::Environment> env(base::Environment::Create());
134 if (env->HasVar("CHROME_HEADLESS"))
135 return false;
136 AudioManager* audio_man = AudioManager::GetAudioManager();
137 if (NULL == audio_man)
138 return false;
139 // TODO(henrika): note that we use Wave today to query the number of
140 // existing output devices.
141 return audio_man->HasAudioOutputDevices();
142 }
143
144 // Convenience method which creates a default AudioOutputStream object but
145 // also allows the user to modify the default settings.
146 class AudioOutputStreamWrapper {
147 public:
148 AudioOutputStreamWrapper()
149 : com_init_(ScopedCOMInitializer::kMTA),
150 audio_man_(AudioManager::GetAudioManager()),
151 format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
152 channel_layout_(CHANNEL_LAYOUT_STEREO),
153 bits_per_sample_(16) {
154 // Use native/mixing sample rate and 10ms frame size as default.
155 sample_rate_ = static_cast<int>(
156 WASAPIAudioOutputStream::HardwareSampleRate(eConsole));
157 samples_per_packet_ = sample_rate_ / 100;
158 DCHECK(sample_rate_);
159 }
160
161 ~AudioOutputStreamWrapper() {}
162
163 // Creates AudioOutputStream object using default parameters.
164 AudioOutputStream* Create() {
165 return CreateOutputStream();
166 }
167
168 // Creates AudioOutputStream object using non-default parameters where the
169 // frame size is modified.
170 AudioOutputStream* Create(int samples_per_packet) {
171 samples_per_packet_ = samples_per_packet;
172 return CreateOutputStream();
173 }
174
175 // Creates AudioOutputStream object using non-default parameters where the
176 // channel layout is modified.
177 AudioOutputStream* Create(ChannelLayout channel_layout) {
178 channel_layout_ = channel_layout;
179 return CreateOutputStream();
180 }
181
182 AudioParameters::Format format() const { return format_; }
183 int channels() const { return ChannelLayoutToChannelCount(channel_layout_); }
184 int bits_per_sample() const { return bits_per_sample_; }
185 int sample_rate() const { return sample_rate_; }
186 int samples_per_packet() const { return samples_per_packet_; }
187
188 private:
189 AudioOutputStream* CreateOutputStream() {
190 AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(
191 AudioParameters(format_, channel_layout_, sample_rate_,
192 bits_per_sample_, samples_per_packet_));
193 EXPECT_TRUE(aos);
194 return aos;
195 }
196
197 ScopedCOMInitializer com_init_;
198 AudioManager* audio_man_;
199 AudioParameters::Format format_;
200 ChannelLayout channel_layout_;
201 int bits_per_sample_;
202 int sample_rate_;
203 int samples_per_packet_;
204 };
205
206 // Convenience method which creates a default AudioOutputStream object.
207 static AudioOutputStream* CreateDefaultAudioOutputStream() {
208 AudioOutputStreamWrapper aosw;
209 AudioOutputStream* aos = aosw.Create();
210 return aos;
211 }
212
213 static void QuitMessageLoop(base::MessageLoopProxy* proxy) {
214 proxy->PostTask(FROM_HERE, new MessageLoop::QuitTask());
215 }
216
217 // Verify that we can retrieve the current hardware/mixing sample rate
218 // for all supported device roles. The ERole enumeration defines constants
219 // that indicate the role that the system/user has assigned to an audio
220 // endpoint device.
221 // TODO(henrika): modify this test when we support full device enumeration.
222 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestHardwareSampleRate) {
223 if (!CanRunAudioTests())
224 return;
225
226 ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
227
228 // Default device intended for games, system notification sounds,
229 // and voice commands.
230 int fs = static_cast<int>(
231 WASAPIAudioOutputStream::HardwareSampleRate(eConsole));
232 EXPECT_GE(fs, 0);
233
234 // Default communication device intended for e.g. VoIP communication.
235 fs = static_cast<int>(
236 WASAPIAudioOutputStream::HardwareSampleRate(eCommunications));
237 EXPECT_GE(fs, 0);
238
239 // Multimedia device for music, movies and live music recording.
240 fs = static_cast<int>(
241 WASAPIAudioOutputStream::HardwareSampleRate(eMultimedia));
242 EXPECT_GE(fs, 0);
243 }
244
245 // Test Create(), Close() calling sequence.
246 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestCreateAndClose) {
247 if (!CanRunAudioTests())
248 return;
249 AudioOutputStream* aos = CreateDefaultAudioOutputStream();
250 aos->Close();
251 }
252
253 // Test Open(), Close() calling sequence.
254 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestOpenAndClose) {
255 if (!CanRunAudioTests())
256 return;
257 AudioOutputStream* aos = CreateDefaultAudioOutputStream();
258 EXPECT_TRUE(aos->Open());
259 aos->Close();
260 }
261
262 // Test Open(), Start(), Close() calling sequence.
263 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestOpenStartAndClose) {
264 if (!CanRunAudioTests())
265 return;
266 AudioOutputStream* aos = CreateDefaultAudioOutputStream();
267 EXPECT_TRUE(aos->Open());
268 MockAudioSourceCallback source;
269 EXPECT_CALL(source, OnError(aos, _))
270 .Times(0);
271 aos->Start(&source);
272 aos->Close();
273 }
274
275 // Test Open(), Start(), Stop(), Close() calling sequence.
276 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestOpenStartStopAndClose) {
277 if (!CanRunAudioTests())
278 return;
279 AudioOutputStream* aos = CreateDefaultAudioOutputStream();
280 EXPECT_TRUE(aos->Open());
281 MockAudioSourceCallback source;
282 EXPECT_CALL(source, OnError(aos, _))
283 .Times(0);
284 aos->Start(&source);
285 aos->Stop();
286 aos->Close();
287 }
288
289 // Test SetVolume(), GetVolume()
290 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestVolume) {
291 if (!CanRunAudioTests())
292 return;
293 AudioOutputStream* aos = CreateDefaultAudioOutputStream();
294
295 // Initial volume should be full volume (1.0).
296 double volume = 0.0;
297 aos->GetVolume(&volume);
298 EXPECT_TRUE(volume == 1.0);
tommi (sloooow) - chröme 2011/11/07 11:47:03 EXPECT_EQ (below too)
henrika (OOO until Aug 14) 2011/11/07 14:09:45 Done.
299
300 // Verify some valid volume settings.
301 aos->SetVolume(0.0);
302 aos->GetVolume(&volume);
303 EXPECT_TRUE(volume == 0.0);
304
305 aos->SetVolume(0.5);
306 aos->GetVolume(&volume);
307 EXPECT_TRUE(volume == 0.5);
308
309 aos->SetVolume(1.0);
310 aos->GetVolume(&volume);
311 EXPECT_TRUE(volume == 1.0);
312
313 // Ensure that invalid volume setting have no effect.
314 aos->SetVolume(1.5);
315 aos->GetVolume(&volume);
316 EXPECT_TRUE(volume == 1.0);
317
318 aos->SetVolume(-0.5);
319 aos->GetVolume(&volume);
320 EXPECT_TRUE(volume == 1.0);
321
322 aos->Close();
323 }
324
325 // Test some additional calling sequences.
326 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestMiscCallingSequences) {
327 if (!CanRunAudioTests())
328 return;
329 AudioOutputStream* aos = CreateDefaultAudioOutputStream();
330 WASAPIAudioOutputStream* waos = static_cast<WASAPIAudioOutputStream*>(aos);
331
332 // Open(), Open() should fail the second time.
333 EXPECT_TRUE(aos->Open());
334 EXPECT_FALSE(aos->Open());
tommi (sloooow) - chröme 2011/11/07 11:47:03 It is a bit confusing that Open fails the second t
henrika (OOO until Aug 14) 2011/11/07 14:09:45 Good point. Modified. Second call to Open() now re
335
336 MockAudioSourceCallback source;
337
338 // Start(), Start() is a valid calling sequence (second call does nothing).
339 aos->Start(&source);
340 EXPECT_TRUE(waos->started());
341 aos->Start(&source);
342 EXPECT_TRUE(waos->started());
343
344 // Stop(), Stop() is a valid calling sequence (second call does nothing).
345 aos->Stop();
346 EXPECT_FALSE(waos->started());
347 aos->Stop();
348 EXPECT_FALSE(waos->started());
349
350 aos->Close();
351 }
352
353 // Use default packet size (10ms) and verify that rendering starts.
354 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestPacketSizeInMilliseconds) {
355 if (!CanRunAudioTests())
356 return;
357
358 MessageLoopForUI loop;
359 scoped_refptr<base::MessageLoopProxy> proxy(loop.message_loop_proxy());
360
361 MockAudioSourceCallback source;
362
363 // Create default WASAPI output stream which plays out in stereo using
364 // the shared mixing rate. The default buffer size is 10ms.
365 AudioOutputStreamWrapper aosw;
366 AudioOutputStream* aos = aosw.Create();
367 EXPECT_TRUE(aos->Open());
368
369 // Derive the expected size in bytes of each packet.
370 uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() *
371 (aosw.bits_per_sample() / 8);
372
373 // Set up expected minimum delay estimation.
374 AudioBuffersState state(0, bytes_per_packet);
375
376 // Wait for the first callback and verify its parameters.
377 EXPECT_CALL(source, OnMoreData(aos, NotNull(), bytes_per_packet,
378 HasValidDelay(state)))
379 .WillOnce(
380 DoAll(
381 InvokeWithoutArgs(
382 CreateFunctor(&QuitMessageLoop, proxy.get())),
383 Return(bytes_per_packet)));
384
385 aos->Start(&source);
386 loop.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(),
387 TestTimeouts::action_timeout_ms());
388 loop.Run();
389 aos->Stop();
390 aos->Close();
391 }
392
393 // Use a fixed packets size (independent of sample rate) and verify
394 // that rendering starts.
395 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestPacketSizeInSamples) {
396 if (!CanRunAudioTests())
397 return;
398
399 MessageLoopForUI loop;
400 scoped_refptr<base::MessageLoopProxy> proxy(loop.message_loop_proxy());
401
402 MockAudioSourceCallback source;
403
404 // Create default WASAPI output stream which plays out in stereo using
405 // the shared mixing rate. The buffer size is set to 1024 samples.
406 AudioOutputStreamWrapper aosw;
407 AudioOutputStream* aos = aosw.Create(1024);
408 EXPECT_TRUE(aos->Open());
409
410 // Derive the expected size in bytes of each packet.
411 uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() *
412 (aosw.bits_per_sample() / 8);
413
414 // Set up expected minimum delay estimation.
415 AudioBuffersState state(0, bytes_per_packet);
416
417 // Wait for the first callback and verify its parameters.
418 EXPECT_CALL(source, OnMoreData(aos, NotNull(), bytes_per_packet,
419 HasValidDelay(state)))
420 .WillOnce(
421 DoAll(
422 InvokeWithoutArgs(
423 CreateFunctor(&QuitMessageLoop, proxy.get())),
424 Return(bytes_per_packet)));
425
426 aos->Start(&source);
427 loop.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(),
428 TestTimeouts::action_timeout_ms());
429 loop.Run();
430 aos->Stop();
431 aos->Close();
432 }
433
434 TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestMono) {
435 if (!CanRunAudioTests())
436 return;
437
438 MessageLoopForUI loop;
439 scoped_refptr<base::MessageLoopProxy> proxy(loop.message_loop_proxy());
440
441 MockAudioSourceCallback source;
442
443 // Create default WASAPI output stream which plays out in *mono* using
444 // the shared mixing rate. The default buffer size is 10ms.
445 AudioOutputStreamWrapper aosw;
446 AudioOutputStream* aos = aosw.Create(CHANNEL_LAYOUT_MONO);
447 EXPECT_TRUE(aos->Open());
448
449 // Derive the expected size in bytes of each packet.
450 uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() *
451 (aosw.bits_per_sample() / 8);
452
453 // Set up expected minimum delay estimation.
454 AudioBuffersState state(0, bytes_per_packet);
455
456 EXPECT_CALL(source, OnMoreData(aos, NotNull(), bytes_per_packet,
457 HasValidDelay(state)))
458 .WillOnce(
459 DoAll(
460 InvokeWithoutArgs(
461 CreateFunctor(&QuitMessageLoop, proxy.get())),
462 Return(bytes_per_packet)));
463
464 aos->Start(&source);
465 loop.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(),
466 TestTimeouts::action_timeout_ms());
467 loop.Run();
468 aos->Stop();
469 aos->Close();
470 }
471
472 // This test is intended for manual tests and should only be enabled
473 // when it is required to store the captured data on a local file.
474 // By default, GTest will print out YOU HAVE 1 DISABLED TEST.
475 // To include disabled tests in test execution, just invoke the test program
476 // with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
477 // environment variable to a value greater than 0.
478 // The test files are approximately 20 seconds long.
479 TEST(WinAudioOutputTest, DISABLED_WASAPIAudioOutputStreamReadFromFile) {
tommi (sloooow) - chröme 2011/11/07 11:47:03 Is it necessary to have the test disabled since it
henrika (OOO until Aug 14) 2011/11/07 14:09:45 Please advice. I figured that it was not OK to add
tommi (sloooow) - chröme 2011/11/07 17:37:17 Nope, you're right, 20 seconds is too long. But d
480 if (!CanRunAudioTests())
481 return;
482
483 AudioOutputStreamWrapper aosw;
484 AudioOutputStream* aos = aosw.Create();
485 EXPECT_TRUE(aos->Open());
486
487 std::string file_name;
488 if (aosw.sample_rate() == 48000) {
489 file_name = kSpeechFile_16b_s_48k;
490 } else if (aosw.sample_rate() == 44100) {
491 file_name = kSpeechFile_16b_s_44k;
492 } else if (aosw.sample_rate() == 96000) {
493 // Use 48kHz file at 96kHz as well. Will sound as Donald Duck.
494 file_name = kSpeechFile_16b_s_48k;
495 } else {
496 fprintf(stderr, "This test supports 44.1, 48kHz and 96kHz only.\n");
Paweł Hajdan Jr. 2011/11/04 11:32:11 Don't you want the test to fail then? FAIL() << "
henrika (OOO until Aug 14) 2011/11/07 14:09:45 Fixed. Thanks.
497 return;
498 }
499 ReadFromFileAudioSource file_source(file_name);
500 int file_duration_ms = kFileDurationMs;
501
502 fprintf(stderr, " File name : %s\n", file_name.c_str());
Paweł Hajdan Jr. 2011/11/04 11:32:11 nit: I think you should generally use LOG in favor
tommi (sloooow) - chröme 2011/11/07 11:47:03 +1
henrika (OOO until Aug 14) 2011/11/07 14:09:45 Done.
503 fprintf(stderr, " Sample rate: %d\n", aosw.sample_rate());
504 fprintf(stderr, " File size : %d\n", file_source.file_size());
505 fprintf(stderr, " >> Listen to the file while playing...\n");
506
507 aos->Start(&file_source);
508 base::PlatformThread::Sleep(file_duration_ms);
509 aos->Stop();
510
511 fprintf(stderr, " >> File playout has stopped.\n");
512 aos->Close();
513 }
514
515 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698