OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/audio/win/audio_low_latency_input_win.h" | 5 #include "media/audio/win/audio_low_latency_input_win.h" |
6 | 6 |
7 #include <windows.h> | |
8 #include <mmsystem.h> | 7 #include <mmsystem.h> |
9 #include <stddef.h> | 8 #include <stddef.h> |
10 #include <stdint.h> | 9 #include <stdint.h> |
| 10 #include <windows.h> |
11 | 11 |
12 #include <memory> | 12 #include <memory> |
13 | 13 |
14 #include "base/environment.h" | 14 #include "base/environment.h" |
15 #include "base/files/file_util.h" | 15 #include "base/files/file_util.h" |
16 #include "base/macros.h" | 16 #include "base/macros.h" |
17 #include "base/message_loop/message_loop.h" | 17 #include "base/message_loop/message_loop.h" |
18 #include "base/path_service.h" | 18 #include "base/path_service.h" |
19 #include "base/run_loop.h" | 19 #include "base/run_loop.h" |
20 #include "base/single_thread_task_runner.h" | 20 #include "base/single_thread_task_runner.h" |
| 21 #include "base/strings/stringprintf.h" |
21 #include "base/test/test_timeouts.h" | 22 #include "base/test/test_timeouts.h" |
22 #include "base/win/scoped_com_initializer.h" | 23 #include "base/win/scoped_com_initializer.h" |
23 #include "media/audio/audio_device_description.h" | 24 #include "media/audio/audio_device_description.h" |
24 #include "media/audio/audio_io.h" | 25 #include "media/audio/audio_io.h" |
25 #include "media/audio/audio_manager.h" | 26 #include "media/audio/audio_manager.h" |
26 #include "media/audio/audio_unittest_util.h" | 27 #include "media/audio/audio_unittest_util.h" |
27 #include "media/audio/win/core_audio_util_win.h" | 28 #include "media/audio/win/core_audio_util_win.h" |
28 #include "media/base/seekable_buffer.h" | 29 #include "media/base/seekable_buffer.h" |
29 #include "testing/gmock/include/gmock/gmock.h" | 30 #include "testing/gmock/include/gmock/gmock.h" |
30 #include "testing/gtest/include/gtest/gtest.h" | 31 #include "testing/gtest/include/gtest/gtest.h" |
(...skipping 26 matching lines...) Expand all Loading... |
57 FakeAudioInputCallback() | 58 FakeAudioInputCallback() |
58 : num_received_audio_frames_(0), | 59 : num_received_audio_frames_(0), |
59 data_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, | 60 data_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
60 base::WaitableEvent::InitialState::NOT_SIGNALED), | 61 base::WaitableEvent::InitialState::NOT_SIGNALED), |
61 error_(false) {} | 62 error_(false) {} |
62 | 63 |
63 bool error() const { return error_; } | 64 bool error() const { return error_; } |
64 int num_received_audio_frames() const { return num_received_audio_frames_; } | 65 int num_received_audio_frames() const { return num_received_audio_frames_; } |
65 | 66 |
66 // Waits until OnData() is called on another thread. | 67 // Waits until OnData() is called on another thread. |
67 void WaitForData() { | 68 void WaitForData() { data_event_.Wait(); } |
68 data_event_.Wait(); | |
69 } | |
70 | 69 |
71 void OnData(AudioInputStream* stream, | 70 void OnData(AudioInputStream* stream, |
72 const AudioBus* src, | 71 const AudioBus* src, |
73 uint32_t hardware_delay_bytes, | 72 uint32_t hardware_delay_bytes, |
74 double volume) override { | 73 double volume) override { |
75 EXPECT_GE(hardware_delay_bytes, 0u); | 74 EXPECT_GE(hardware_delay_bytes, 0u); |
76 EXPECT_LT(hardware_delay_bytes, 0xFFFFu); // Arbitrarily picked. | 75 EXPECT_LT(hardware_delay_bytes, 0xFFFFu); // Arbitrarily picked. |
77 num_received_audio_frames_ += src->frames(); | 76 num_received_audio_frames_ += src->frames(); |
78 data_event_.Signal(); | 77 data_event_.Signal(); |
79 } | 78 } |
80 | 79 |
81 void OnError(AudioInputStream* stream) override { | 80 void OnError(AudioInputStream* stream) override { error_ = true; } |
82 error_ = true; | |
83 } | |
84 | 81 |
85 private: | 82 private: |
86 int num_received_audio_frames_; | 83 int num_received_audio_frames_; |
87 base::WaitableEvent data_event_; | 84 base::WaitableEvent data_event_; |
88 bool error_; | 85 bool error_; |
89 | 86 |
90 DISALLOW_COPY_AND_ASSIGN(FakeAudioInputCallback); | 87 DISALLOW_COPY_AND_ASSIGN(FakeAudioInputCallback); |
91 }; | 88 }; |
92 | 89 |
93 // This audio sink implementation should be used for manual tests only since | 90 // This audio sink implementation should be used for manual tests only since |
94 // the recorded data is stored on a raw binary data file. | 91 // the recorded data is stored on a raw binary data file. |
95 class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback { | 92 class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback { |
96 public: | 93 public: |
97 // Allocate space for ~10 seconds of data @ 48kHz in stereo: | 94 // Allocate space for ~10 seconds of data @ 48kHz in stereo: |
98 // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes. | 95 // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes. |
99 static const size_t kMaxBufferSize = 2 * 2 * 480 * 100 * 10; | 96 static const size_t kMaxBufferSize = 2 * 2 * 480 * 100 * 10; |
100 | 97 |
101 explicit WriteToFileAudioSink(const char* file_name, int bits_per_sample) | 98 explicit WriteToFileAudioSink(const char* file_name, int bits_per_sample) |
102 : bits_per_sample_(bits_per_sample), | 99 : bits_per_sample_(bits_per_sample), |
103 buffer_(0, kMaxBufferSize), | 100 buffer_(0, kMaxBufferSize), |
104 bytes_to_write_(0) { | 101 bytes_to_write_(0) { |
105 base::FilePath file_path; | 102 base::FilePath file_path; |
106 EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_path)); | 103 EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_path)); |
107 file_path = file_path.AppendASCII(file_name); | 104 file_path = file_path.AppendASCII(file_name); |
108 binary_file_ = base::OpenFile(file_path, "wb"); | 105 binary_file_ = base::OpenFile(file_path, "wb"); |
109 DLOG_IF(ERROR, !binary_file_) << "Failed to open binary PCM data file."; | 106 DLOG_IF(ERROR, !binary_file_) << "Failed to open binary PCM data file."; |
110 VLOG(0) << ">> Output file: " << file_path.value() << " has been created."; | 107 VLOG(0) << ">> Output file: " << file_path.value() << " has been created."; |
111 VLOG(0) << "bits_per_sample_:" << bits_per_sample_; | 108 VLOG(0) << ">> bits_per_sample_:" << bits_per_sample_; |
112 } | 109 } |
113 | 110 |
114 ~WriteToFileAudioSink() override { | 111 ~WriteToFileAudioSink() override { |
115 size_t bytes_written = 0; | 112 size_t bytes_written = 0; |
116 while (bytes_written < bytes_to_write_) { | 113 while (bytes_written < bytes_to_write_) { |
117 const uint8_t* chunk; | 114 const uint8_t* chunk; |
118 int chunk_size; | 115 int chunk_size; |
119 | 116 |
120 // Stop writing if no more data is available. | 117 // Stop writing if no more data is available. |
121 if (!buffer_.GetCurrentChunk(&chunk, &chunk_size)) | 118 if (!buffer_.GetCurrentChunk(&chunk, &chunk_size)) |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
170 class AudioInputStreamWrapper { | 167 class AudioInputStreamWrapper { |
171 public: | 168 public: |
172 explicit AudioInputStreamWrapper(AudioManager* audio_manager) | 169 explicit AudioInputStreamWrapper(AudioManager* audio_manager) |
173 : audio_man_(audio_manager) { | 170 : audio_man_(audio_manager) { |
174 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters( | 171 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters( |
175 AudioDeviceDescription::kDefaultDeviceId, false, &default_params_))); | 172 AudioDeviceDescription::kDefaultDeviceId, false, &default_params_))); |
176 EXPECT_EQ(format(), AudioParameters::AUDIO_PCM_LOW_LATENCY); | 173 EXPECT_EQ(format(), AudioParameters::AUDIO_PCM_LOW_LATENCY); |
177 frames_per_buffer_ = default_params_.frames_per_buffer(); | 174 frames_per_buffer_ = default_params_.frames_per_buffer(); |
178 } | 175 } |
179 | 176 |
| 177 AudioInputStreamWrapper(AudioManager* audio_manager, |
| 178 const AudioParameters& default_params) |
| 179 : audio_man_(audio_manager), default_params_(default_params) { |
| 180 EXPECT_EQ(format(), AudioParameters::AUDIO_PCM_LOW_LATENCY); |
| 181 frames_per_buffer_ = default_params_.frames_per_buffer(); |
| 182 } |
| 183 |
180 ~AudioInputStreamWrapper() {} | 184 ~AudioInputStreamWrapper() {} |
181 | 185 |
182 // Creates AudioInputStream object using default parameters. | 186 // Creates AudioInputStream object using default parameters. |
183 AudioInputStream* Create() { | 187 AudioInputStream* Create() { return CreateInputStream(); } |
184 return CreateInputStream(); | |
185 } | |
186 | 188 |
187 // Creates AudioInputStream object using non-default parameters where the | 189 // Creates AudioInputStream object using non-default parameters where the |
188 // frame size is modified. | 190 // frame size is modified. |
189 AudioInputStream* Create(int frames_per_buffer) { | 191 AudioInputStream* Create(int frames_per_buffer) { |
190 frames_per_buffer_ = frames_per_buffer; | 192 frames_per_buffer_ = frames_per_buffer; |
191 return CreateInputStream(); | 193 return CreateInputStream(); |
192 } | 194 } |
193 | 195 |
194 AudioParameters::Format format() const { return default_params_.format(); } | 196 AudioParameters::Format format() const { return default_params_.format(); } |
195 int channels() const { | 197 int channels() const { |
(...skipping 22 matching lines...) Expand all Loading... |
218 // Convenience method which creates a default AudioInputStream object. | 220 // Convenience method which creates a default AudioInputStream object. |
219 static AudioInputStream* CreateDefaultAudioInputStream( | 221 static AudioInputStream* CreateDefaultAudioInputStream( |
220 AudioManager* audio_manager) { | 222 AudioManager* audio_manager) { |
221 AudioInputStreamWrapper aisw(audio_manager); | 223 AudioInputStreamWrapper aisw(audio_manager); |
222 AudioInputStream* ais = aisw.Create(); | 224 AudioInputStream* ais = aisw.Create(); |
223 return ais; | 225 return ais; |
224 } | 226 } |
225 | 227 |
226 class ScopedAudioInputStream { | 228 class ScopedAudioInputStream { |
227 public: | 229 public: |
228 explicit ScopedAudioInputStream(AudioInputStream* stream) | 230 explicit ScopedAudioInputStream(AudioInputStream* stream) : stream_(stream) {} |
229 : stream_(stream) {} | |
230 | 231 |
231 ~ScopedAudioInputStream() { | 232 ~ScopedAudioInputStream() { |
232 if (stream_) | 233 if (stream_) |
233 stream_->Close(); | 234 stream_->Close(); |
234 } | 235 } |
235 | 236 |
236 void Close() { | 237 void Close() { |
237 if (stream_) | 238 if (stream_) |
238 stream_->Close(); | 239 stream_->Close(); |
239 stream_ = NULL; | 240 stream_ = NULL; |
240 } | 241 } |
241 | 242 |
242 AudioInputStream* operator->() { | 243 AudioInputStream* operator->() { return stream_; } |
243 return stream_; | |
244 } | |
245 | 244 |
246 AudioInputStream* get() const { return stream_; } | 245 AudioInputStream* get() const { return stream_; } |
247 | 246 |
248 void Reset(AudioInputStream* new_stream) { | 247 void Reset(AudioInputStream* new_stream) { |
249 Close(); | 248 Close(); |
250 stream_ = new_stream; | 249 stream_ = new_stream; |
251 } | 250 } |
252 | 251 |
253 private: | 252 private: |
254 AudioInputStream* stream_; | 253 AudioInputStream* stream_; |
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
397 // Store current packet size (to be used in the subsequent tests). | 396 // Store current packet size (to be used in the subsequent tests). |
398 int frames_per_buffer_10ms = aisw.frames_per_buffer(); | 397 int frames_per_buffer_10ms = aisw.frames_per_buffer(); |
399 | 398 |
400 ais.Close(); | 399 ais.Close(); |
401 | 400 |
402 // 20 ms packet size. | 401 // 20 ms packet size. |
403 | 402 |
404 count = 0; | 403 count = 0; |
405 ais.Reset(aisw.Create(2 * frames_per_buffer_10ms)); | 404 ais.Reset(aisw.Create(2 * frames_per_buffer_10ms)); |
406 EXPECT_TRUE(ais->Open()); | 405 EXPECT_TRUE(ais->Open()); |
407 bytes_per_packet = aisw.channels() * aisw.frames_per_buffer() * | 406 bytes_per_packet = |
408 (aisw.bits_per_sample() / 8); | 407 aisw.channels() * aisw.frames_per_buffer() * (aisw.bits_per_sample() / 8); |
409 | 408 |
410 { | 409 { |
411 base::RunLoop run_loop; | 410 base::RunLoop run_loop; |
412 EXPECT_CALL(sink, OnData(ais.get(), NotNull(), _, _)) | 411 EXPECT_CALL(sink, OnData(ais.get(), NotNull(), _, _)) |
413 .Times(AtLeast(10)) | 412 .Times(AtLeast(10)) |
414 .WillRepeatedly( | 413 .WillRepeatedly( |
415 CheckCountAndPostQuitTask(&count, 10, message_loop_.task_runner(), | 414 CheckCountAndPostQuitTask(&count, 10, message_loop_.task_runner(), |
416 run_loop.QuitWhenIdleClosure())); | 415 run_loop.QuitWhenIdleClosure())); |
417 ais->Start(&sink); | 416 ais->Start(&sink); |
418 run_loop.Run(); | 417 run_loop.Run(); |
419 ais->Stop(); | 418 ais->Stop(); |
420 ais.Close(); | 419 ais.Close(); |
421 } | 420 } |
422 | 421 |
423 // 5 ms packet size. | 422 // 5 ms packet size. |
424 | 423 |
425 count = 0; | 424 count = 0; |
426 ais.Reset(aisw.Create(frames_per_buffer_10ms / 2)); | 425 ais.Reset(aisw.Create(frames_per_buffer_10ms / 2)); |
427 EXPECT_TRUE(ais->Open()); | 426 EXPECT_TRUE(ais->Open()); |
428 bytes_per_packet = aisw.channels() * aisw.frames_per_buffer() * | 427 bytes_per_packet = |
429 (aisw.bits_per_sample() / 8); | 428 aisw.channels() * aisw.frames_per_buffer() * (aisw.bits_per_sample() / 8); |
430 | 429 |
431 { | 430 { |
432 base::RunLoop run_loop; | 431 base::RunLoop run_loop; |
433 EXPECT_CALL(sink, OnData(ais.get(), NotNull(), _, _)) | 432 EXPECT_CALL(sink, OnData(ais.get(), NotNull(), _, _)) |
434 .Times(AtLeast(10)) | 433 .Times(AtLeast(10)) |
435 .WillRepeatedly( | 434 .WillRepeatedly( |
436 CheckCountAndPostQuitTask(&count, 10, message_loop_.task_runner(), | 435 CheckCountAndPostQuitTask(&count, 10, message_loop_.task_runner(), |
437 run_loop.QuitWhenIdleClosure())); | 436 run_loop.QuitWhenIdleClosure())); |
438 ais->Start(&sink); | 437 ais->Start(&sink); |
439 run_loop.Run(); | 438 run_loop.Run(); |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
476 // By default, GTest will print out YOU HAVE 1 DISABLED TEST. | 475 // By default, GTest will print out YOU HAVE 1 DISABLED TEST. |
477 // To include disabled tests in test execution, just invoke the test program | 476 // To include disabled tests in test execution, just invoke the test program |
478 // with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS | 477 // with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS |
479 // environment variable to a value greater than 0. | 478 // environment variable to a value greater than 0. |
480 TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) { | 479 TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) { |
481 ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get())); | 480 ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get())); |
482 | 481 |
483 // Name of the output PCM file containing captured data. The output file | 482 // Name of the output PCM file containing captured data. The output file |
484 // will be stored in the directory containing 'media_unittests.exe'. | 483 // will be stored in the directory containing 'media_unittests.exe'. |
485 // Example of full name: \src\build\Debug\out_stereo_10sec.pcm. | 484 // Example of full name: \src\build\Debug\out_stereo_10sec.pcm. |
486 const char* file_name = "out_stereo_10sec.pcm"; | 485 const char* file_name = "out_10sec.pcm"; |
487 | 486 |
488 AudioInputStreamWrapper aisw(audio_manager_.get()); | 487 AudioInputStreamWrapper aisw(audio_manager_.get()); |
489 ScopedAudioInputStream ais(aisw.Create()); | 488 ScopedAudioInputStream ais(aisw.Create()); |
490 EXPECT_TRUE(ais->Open()); | 489 ASSERT_TRUE(ais->Open()); |
491 | 490 |
492 VLOG(0) << ">> Sample rate: " << aisw.sample_rate() << " [Hz]"; | 491 VLOG(0) << ">> Sample rate: " << aisw.sample_rate() << " [Hz]"; |
493 WriteToFileAudioSink file_sink(file_name, aisw.bits_per_sample()); | 492 WriteToFileAudioSink file_sink(file_name, aisw.bits_per_sample()); |
494 VLOG(0) << ">> Speak into the default microphone while recording."; | 493 VLOG(0) << ">> Speak into the default microphone while recording."; |
495 ais->Start(&file_sink); | 494 ais->Start(&file_sink); |
496 base::PlatformThread::Sleep(TestTimeouts::action_timeout()); | 495 base::PlatformThread::Sleep(TestTimeouts::action_timeout()); |
497 ais->Stop(); | 496 ais->Stop(); |
498 VLOG(0) << ">> Recording has stopped."; | 497 VLOG(0) << ">> Recording has stopped."; |
499 ais.Close(); | 498 ais.Close(); |
500 } | 499 } |
501 | 500 |
| 501 TEST_F(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamResampleToFile) { |
| 502 ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndInputDevices(audio_manager_.get())); |
| 503 |
| 504 // This is basically the same test as WASAPIAudioInputStreamRecordToFile |
| 505 // except it forces use of a different sample rate than is preferred by |
| 506 // the hardware. This functionality is offered while we still have code |
| 507 // that doesn't ask the lower levels for what the preferred audio parameters |
| 508 // are (and previously depended on the old Wave API to do this automatically). |
| 509 |
| 510 struct TestData { |
| 511 const int rate; |
| 512 const int frames; |
| 513 ChannelLayout layout; |
| 514 } tests[] = { |
| 515 {8000, 80, CHANNEL_LAYOUT_MONO}, |
| 516 {8000, 80, CHANNEL_LAYOUT_STEREO}, |
| 517 {44100, 441, CHANNEL_LAYOUT_MONO}, |
| 518 {44100, 1024, CHANNEL_LAYOUT_STEREO}, |
| 519 }; |
| 520 |
| 521 for (const auto& test : tests) { |
| 522 AudioParameters params; |
| 523 ASSERT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters( |
| 524 AudioDeviceDescription::kDefaultDeviceId, false, ¶ms))); |
| 525 |
| 526 VLOG(0) << ">> Hardware sample rate: " << params.sample_rate() << " [Hz]"; |
| 527 VLOG(0) << ">> Hardware channel layout: " |
| 528 << ChannelLayoutToString(params.channel_layout()); |
| 529 |
| 530 // Pick a somewhat difficult sample rate to convert too. |
| 531 // If the sample rate is 8kHz, 16kHz, 32kHz, 48kHz etc, we convert to |
| 532 // 44.1kHz. |
| 533 // Otherwise (e.g. 44.1kHz, 22.05kHz etc) we convert to 48kHz. |
| 534 const int hw_sample_rate = params.sample_rate(); |
| 535 params.Reset(params.format(), test.layout, test.rate, |
| 536 params.bits_per_sample(), test.frames); |
| 537 |
| 538 std::string file_name(base::StringPrintf( |
| 539 "resampled_10sec_%i_to_%i_%s.pcm", hw_sample_rate, params.sample_rate(), |
| 540 ChannelLayoutToString(params.channel_layout()))); |
| 541 |
| 542 AudioInputStreamWrapper aisw(audio_manager_.get(), params); |
| 543 ScopedAudioInputStream ais(aisw.Create()); |
| 544 ASSERT_TRUE(ais->Open()); |
| 545 |
| 546 VLOG(0) << ">> Resampled rate will be: " << aisw.sample_rate() << " [Hz]"; |
| 547 VLOG(0) << ">> New layout will be: " |
| 548 << ChannelLayoutToString(params.channel_layout()); |
| 549 WriteToFileAudioSink file_sink(file_name.c_str(), aisw.bits_per_sample()); |
| 550 VLOG(0) << ">> Speak into the default microphone while recording."; |
| 551 ais->Start(&file_sink); |
| 552 base::PlatformThread::Sleep(TestTimeouts::action_timeout()); |
| 553 // base::PlatformThread::Sleep(base::TimeDelta::FromMinutes(10)); |
| 554 ais->Stop(); |
| 555 VLOG(0) << ">> Recording has stopped."; |
| 556 ais.Close(); |
| 557 } |
| 558 } |
| 559 |
502 } // namespace media | 560 } // namespace media |
OLD | NEW |