| 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 AudioParameters params; |
| 511 ASSERT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters( |
| 512 AudioDeviceDescription::kDefaultDeviceId, false, ¶ms))); |
| 513 |
| 514 VLOG(0) << ">> Hardware sample rate: " << params.sample_rate() << " [Hz]"; |
| 515 VLOG(0) << ">> Hardware channel layout: " |
| 516 << ChannelLayoutToString(params.channel_layout()); |
| 517 |
| 518 // Pick a somewhat difficult sample rate to convert too. |
| 519 // If the sample rate is 8kHz, 16kHz, 32kHz, 48kHz etc, we convert to 44.1kHz. |
| 520 // Otherwise (e.g. 44.1kHz, 22.05kHz etc) we convert to 48kHz. |
| 521 const int hw_sample_rate = params.sample_rate(); |
| 522 const int new_sample_rate = |
| 523 (params.sample_rate() % 8000) == 0 ? 44100 : 48000; |
| 524 params.Reset(params.format(), params.channel_layout() == CHANNEL_LAYOUT_STEREO |
| 525 ? CHANNEL_LAYOUT_MONO |
| 526 : CHANNEL_LAYOUT_STEREO, |
| 527 new_sample_rate, params.bits_per_sample(), |
| 528 new_sample_rate / 100); |
| 529 |
| 530 std::string file_name(base::StringPrintf( |
| 531 "resampled_10sec_%i_to_%i_%s.pcm", hw_sample_rate, params.sample_rate(), |
| 532 ChannelLayoutToString(params.channel_layout()))); |
| 533 |
| 534 AudioInputStreamWrapper aisw(audio_manager_.get(), params); |
| 535 ScopedAudioInputStream ais(aisw.Create()); |
| 536 ASSERT_TRUE(ais->Open()); |
| 537 |
| 538 VLOG(0) << ">> Resampled rate will be: " << aisw.sample_rate() << " [Hz]"; |
| 539 VLOG(0) << ">> New layout will be: " |
| 540 << ChannelLayoutToString(params.channel_layout()); |
| 541 WriteToFileAudioSink file_sink(file_name.c_str(), aisw.bits_per_sample()); |
| 542 VLOG(0) << ">> Speak into the default microphone while recording."; |
| 543 ais->Start(&file_sink); |
| 544 base::PlatformThread::Sleep(TestTimeouts::action_timeout()); |
| 545 ais->Stop(); |
| 546 VLOG(0) << ">> Recording has stopped."; |
| 547 ais.Close(); |
| 548 } |
| 549 |
| 502 } // namespace media | 550 } // namespace media |
| OLD | NEW |