audio_low_latency_input_win_unittest.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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/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, base::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
48class FakeAudioInputCallback : public AudioInputStream::AudioInputCallback {
49 public:
50  FakeAudioInputCallback()
51    : closed_(false),
52      error_(false),
53      data_event_(false, false) {
54  }
55
56  const std::vector<uint8>& received_data() const { return received_data_; }
57  bool closed() const { return closed_; }
58  bool error() const { return error_; }
59
60  // Waits until OnData() is called on another thread.
61  void WaitForData() {
62    data_event_.Wait();
63  }
64
65  virtual void OnData(AudioInputStream* stream,
66                      const uint8* src, uint32 size,
67                      uint32 hardware_delay_bytes, double volume) OVERRIDE {
68    received_data_.insert(received_data_.end(), src, src + size);
69    data_event_.Signal();
70  }
71
72  virtual void OnClose(AudioInputStream* stream) OVERRIDE {
73    closed_ = true;
74  }
75
76  virtual void OnError(AudioInputStream* stream) OVERRIDE {
77    error_ = true;
78  }
79
80 private:
81  std::vector<uint8> received_data_;
82  base::WaitableEvent data_event_;
83  bool closed_;
84  bool error_;
85
86  DISALLOW_COPY_AND_ASSIGN(FakeAudioInputCallback);
87};
88
89// This audio sink implementation should be used for manual tests only since
90// the recorded data is stored on a raw binary data file.
91class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback {
92 public:
93  // Allocate space for ~10 seconds of data @ 48kHz in stereo:
94  // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes.
95  static const size_t kMaxBufferSize = 2 * 2 * 480 * 100 * 10;
96
97  explicit WriteToFileAudioSink(const char* file_name)
98      : buffer_(0, kMaxBufferSize),
99        bytes_to_write_(0) {
100    base::FilePath file_path;
101    EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_path));
102    file_path = file_path.AppendASCII(file_name);
103    binary_file_ = base::OpenFile(file_path, "wb");
104    DLOG_IF(ERROR, !binary_file_) << "Failed to open binary PCM data file.";
105    VLOG(0) << ">> Output file: " << file_path.value() << " has been created.";
106  }
107
108  virtual ~WriteToFileAudioSink() {
109    size_t bytes_written = 0;
110    while (bytes_written < bytes_to_write_) {
111      const uint8* chunk;
112      int chunk_size;
113
114      // Stop writing if no more data is available.
115      if (!buffer_.GetCurrentChunk(&chunk, &chunk_size))
116        break;
117
118      // Write recorded data chunk to the file and prepare for next chunk.
119      fwrite(chunk, 1, chunk_size, binary_file_);
120      buffer_.Seek(chunk_size);
121      bytes_written += chunk_size;
122    }
123    base::CloseFile(binary_file_);
124  }
125
126  // AudioInputStream::AudioInputCallback implementation.
127  virtual void OnData(AudioInputStream* stream,
128                      const uint8* src,
129                      uint32 size,
130                      uint32 hardware_delay_bytes,
131                      double volume) {
132    // Store data data in a temporary buffer to avoid making blocking
133    // fwrite() calls in the audio callback. The complete buffer will be
134    // written to file in the destructor.
135    if (buffer_.Append(src, size)) {
136      bytes_to_write_ += size;
137    }
138  }
139
140  virtual void OnClose(AudioInputStream* stream) {}
141  virtual void OnError(AudioInputStream* stream) {}
142
143 private:
144  media::SeekableBuffer buffer_;
145  FILE* binary_file_;
146  size_t bytes_to_write_;
147};
148
149// Convenience method which ensures that we are not running on the build
150// bots and that at least one valid input device can be found. We also
151// verify that we are not running on XP since the low-latency (WASAPI-
152// based) version requires Windows Vista or higher.
153static bool CanRunAudioTests(AudioManager* audio_man) {
154  if (!CoreAudioUtil::IsSupported()) {
155    LOG(WARNING) << "This tests requires Windows Vista or higher.";
156    return false;
157  }
158  // TODO(henrika): note that we use Wave today to query the number of
159  // existing input devices.
160  bool input = audio_man->HasAudioInputDevices();
161  LOG_IF(WARNING, !input) << "No input device detected.";
162  return input;
163}
164
165// Convenience method which creates a default AudioInputStream object but
166// also allows the user to modify the default settings.
167class AudioInputStreamWrapper {
168 public:
169  explicit AudioInputStreamWrapper(AudioManager* audio_manager)
170      : com_init_(ScopedCOMInitializer::kMTA),
171        audio_man_(audio_manager),
172        format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
173        channel_layout_(CHANNEL_LAYOUT_STEREO),
174        bits_per_sample_(16) {
175    // Use native/mixing sample rate and 10ms frame size as default.
176    sample_rate_ = static_cast<int>(
177        WASAPIAudioInputStream::HardwareSampleRate(
178            AudioManagerBase::kDefaultDeviceId));
179    samples_per_packet_ = sample_rate_ / 100;
180  }
181
182  ~AudioInputStreamWrapper() {}
183
184  // Creates AudioInputStream object using default parameters.
185  AudioInputStream* Create() {
186    return CreateInputStream();
187  }
188
189  // Creates AudioInputStream object using non-default parameters where the
190  // frame size is modified.
191  AudioInputStream* Create(int samples_per_packet) {
192    samples_per_packet_ = samples_per_packet;
193    return CreateInputStream();
194  }
195
196  AudioParameters::Format format() const { return format_; }
197  int channels() const {
198    return ChannelLayoutToChannelCount(channel_layout_);
199  }
200  int bits_per_sample() const { return bits_per_sample_; }
201  int sample_rate() const { return sample_rate_; }
202  int samples_per_packet() const { return samples_per_packet_; }
203
204 private:
205  AudioInputStream* CreateInputStream() {
206    AudioInputStream* ais = audio_man_->MakeAudioInputStream(
207        AudioParameters(format_, channel_layout_, sample_rate_,
208                        bits_per_sample_, samples_per_packet_),
209                        AudioManagerBase::kDefaultDeviceId);
210    EXPECT_TRUE(ais);
211    return ais;
212  }
213
214  ScopedCOMInitializer com_init_;
215  AudioManager* audio_man_;
216  AudioParameters::Format format_;
217  ChannelLayout channel_layout_;
218  int bits_per_sample_;
219  int sample_rate_;
220  int samples_per_packet_;
221};
222
223// Convenience method which creates a default AudioInputStream object.
224static AudioInputStream* CreateDefaultAudioInputStream(
225    AudioManager* audio_manager) {
226  AudioInputStreamWrapper aisw(audio_manager);
227  AudioInputStream* ais = aisw.Create();
228  return ais;
229}
230
231class ScopedAudioInputStream {
232 public:
233  explicit ScopedAudioInputStream(AudioInputStream* stream)
234      : stream_(stream) {}
235
236  ~ScopedAudioInputStream() {
237    if (stream_)
238      stream_->Close();
239  }
240
241  void Close() {
242    if (stream_)
243      stream_->Close();
244    stream_ = NULL;
245  }
246
247  AudioInputStream* operator->() {
248    return stream_;
249  }
250
251  AudioInputStream* get() const { return stream_; }
252
253  void Reset(AudioInputStream* new_stream) {
254    Close();
255    stream_ = new_stream;
256  }
257
258 private:
259  AudioInputStream* stream_;
260
261  DISALLOW_COPY_AND_ASSIGN(ScopedAudioInputStream);
262};
263
264// Verify that we can retrieve the current hardware/mixing sample rate
265// for all available input devices.
266TEST(WinAudioInputTest, WASAPIAudioInputStreamHardwareSampleRate) {
267  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
268  if (!CanRunAudioTests(audio_manager.get()))
269    return;
270
271  ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
272
273  // Retrieve a list of all available input devices.
274  media::AudioDeviceNames device_names;
275  audio_manager->GetAudioInputDeviceNames(&device_names);
276
277  // Scan all available input devices and repeat the same test for all of them.
278  for (media::AudioDeviceNames::const_iterator it = device_names.begin();
279       it != device_names.end(); ++it) {
280    // Retrieve the hardware sample rate given a specified audio input device.
281    // TODO(tommi): ensure that we don't have to cast here.
282    int fs = static_cast<int>(WASAPIAudioInputStream::HardwareSampleRate(
283        it->unique_id));
284    EXPECT_GE(fs, 0);
285  }
286}
287
288// Test Create(), Close() calling sequence.
289TEST(WinAudioInputTest, WASAPIAudioInputStreamCreateAndClose) {
290  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
291  if (!CanRunAudioTests(audio_manager.get()))
292    return;
293  ScopedAudioInputStream ais(
294      CreateDefaultAudioInputStream(audio_manager.get()));
295  ais.Close();
296}
297
298// Test Open(), Close() calling sequence.
299TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenAndClose) {
300  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
301  if (!CanRunAudioTests(audio_manager.get()))
302    return;
303  ScopedAudioInputStream ais(
304      CreateDefaultAudioInputStream(audio_manager.get()));
305  EXPECT_TRUE(ais->Open());
306  ais.Close();
307}
308
309// Test Open(), Start(), Close() calling sequence.
310TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) {
311  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
312  if (!CanRunAudioTests(audio_manager.get()))
313    return;
314  ScopedAudioInputStream ais(
315      CreateDefaultAudioInputStream(audio_manager.get()));
316  EXPECT_TRUE(ais->Open());
317  MockAudioInputCallback sink;
318  ais->Start(&sink);
319  EXPECT_CALL(sink, OnClose(ais.get()))
320      .Times(1);
321  ais.Close();
322}
323
324// Test Open(), Start(), Stop(), Close() calling sequence.
325TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) {
326  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
327  if (!CanRunAudioTests(audio_manager.get()))
328    return;
329  ScopedAudioInputStream ais(
330      CreateDefaultAudioInputStream(audio_manager.get()));
331  EXPECT_TRUE(ais->Open());
332  MockAudioInputCallback sink;
333  ais->Start(&sink);
334  ais->Stop();
335  EXPECT_CALL(sink, OnClose(ais.get()))
336      .Times(1);
337  ais.Close();
338}
339
340// Test some additional calling sequences.
341TEST(WinAudioInputTest, WASAPIAudioInputStreamMiscCallingSequences) {
342  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
343  if (!CanRunAudioTests(audio_manager.get()))
344    return;
345  ScopedAudioInputStream ais(
346      CreateDefaultAudioInputStream(audio_manager.get()));
347  WASAPIAudioInputStream* wais =
348      static_cast<WASAPIAudioInputStream*>(ais.get());
349
350  // Open(), Open() should fail the second time.
351  EXPECT_TRUE(ais->Open());
352  EXPECT_FALSE(ais->Open());
353
354  MockAudioInputCallback sink;
355
356  // Start(), Start() is a valid calling sequence (second call does nothing).
357  ais->Start(&sink);
358  EXPECT_TRUE(wais->started());
359  ais->Start(&sink);
360  EXPECT_TRUE(wais->started());
361
362  // Stop(), Stop() is a valid calling sequence (second call does nothing).
363  ais->Stop();
364  EXPECT_FALSE(wais->started());
365  ais->Stop();
366  EXPECT_FALSE(wais->started());
367
368  EXPECT_CALL(sink, OnClose(ais.get()))
369    .Times(1);
370  ais.Close();
371}
372
373TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) {
374  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
375  if (!CanRunAudioTests(audio_manager.get()))
376    return;
377
378  int count = 0;
379  base::MessageLoopForUI loop;
380
381  // 10 ms packet size.
382
383  // Create default WASAPI input stream which records in stereo using
384  // the shared mixing rate. The default buffer size is 10ms.
385  AudioInputStreamWrapper aisw(audio_manager.get());
386  ScopedAudioInputStream ais(aisw.Create());
387  EXPECT_TRUE(ais->Open());
388
389  MockAudioInputCallback sink;
390
391  // Derive the expected size in bytes of each recorded packet.
392  uint32 bytes_per_packet = aisw.channels() * aisw.samples_per_packet() *
393      (aisw.bits_per_sample() / 8);
394
395  // We use 10ms packets and will run the test until ten packets are received.
396  // All should contain valid packets of the same size and a valid delay
397  // estimate.
398  EXPECT_CALL(sink, OnData(
399      ais.get(), NotNull(), bytes_per_packet, Gt(bytes_per_packet), _))
400      .Times(AtLeast(10))
401      .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop));
402  ais->Start(&sink);
403  loop.Run();
404  ais->Stop();
405
406  // Store current packet size (to be used in the subsequent tests).
407  int samples_per_packet_10ms = aisw.samples_per_packet();
408
409  EXPECT_CALL(sink, OnClose(ais.get()))
410      .Times(1);
411  ais.Close();
412
413  // 20 ms packet size.
414
415  count = 0;
416  ais.Reset(aisw.Create(2 * samples_per_packet_10ms));
417  EXPECT_TRUE(ais->Open());
418  bytes_per_packet = aisw.channels() * aisw.samples_per_packet() *
419      (aisw.bits_per_sample() / 8);
420
421  EXPECT_CALL(sink, OnData(
422      ais.get(), NotNull(), bytes_per_packet, Gt(bytes_per_packet), _))
423      .Times(AtLeast(10))
424      .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop));
425  ais->Start(&sink);
426  loop.Run();
427  ais->Stop();
428
429  EXPECT_CALL(sink, OnClose(ais.get()))
430      .Times(1);
431  ais.Close();
432
433  // 5 ms packet size.
434
435  count = 0;
436  ais.Reset(aisw.Create(samples_per_packet_10ms / 2));
437  EXPECT_TRUE(ais->Open());
438  bytes_per_packet = aisw.channels() * aisw.samples_per_packet() *
439    (aisw.bits_per_sample() / 8);
440
441  EXPECT_CALL(sink, OnData(
442      ais.get(), NotNull(), bytes_per_packet, Gt(bytes_per_packet), _))
443      .Times(AtLeast(10))
444      .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop));
445  ais->Start(&sink);
446  loop.Run();
447  ais->Stop();
448
449  EXPECT_CALL(sink, OnClose(ais.get()))
450      .Times(1);
451  ais.Close();
452}
453
454// Test that we can capture loopback stream.
455TEST(WinAudioInputTest, WASAPIAudioInputStreamLoopback) {
456  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
457  if (!audio_manager->HasAudioOutputDevices() || !CoreAudioUtil::IsSupported())
458    return;
459
460  AudioParameters params = audio_manager->GetInputStreamParameters(
461      AudioManagerBase::kLoopbackInputDeviceId);
462
463  AudioParameters output_params =
464      audio_manager->GetOutputStreamParameters(std::string());
465  EXPECT_EQ(params.sample_rate(), output_params.sample_rate());
466  EXPECT_EQ(params.channel_layout(), output_params.channel_layout());
467
468  ScopedAudioInputStream stream(audio_manager->MakeAudioInputStream(
469      params, AudioManagerBase::kLoopbackInputDeviceId));
470  ASSERT_TRUE(stream->Open());
471  FakeAudioInputCallback sink;
472  stream->Start(&sink);
473  ASSERT_FALSE(sink.error());
474
475  sink.WaitForData();
476  stream.Close();
477
478  EXPECT_FALSE(sink.received_data().empty());
479  EXPECT_TRUE(sink.closed());
480  EXPECT_FALSE(sink.error());
481}
482
483// This test is intended for manual tests and should only be enabled
484// when it is required to store the captured data on a local file.
485// By default, GTest will print out YOU HAVE 1 DISABLED TEST.
486// To include disabled tests in test execution, just invoke the test program
487// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
488// environment variable to a value greater than 0.
489TEST(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) {
490  scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting());
491  if (!CanRunAudioTests(audio_manager.get()))
492    return;
493
494  // Name of the output PCM file containing captured data. The output file
495  // will be stored in the directory containing 'media_unittests.exe'.
496  // Example of full name: \src\build\Debug\out_stereo_10sec.pcm.
497  const char* file_name = "out_stereo_10sec.pcm";
498
499  AudioInputStreamWrapper aisw(audio_manager.get());
500  ScopedAudioInputStream ais(aisw.Create());
501  EXPECT_TRUE(ais->Open());
502
503  VLOG(0) << ">> Sample rate: " << aisw.sample_rate() << " [Hz]";
504  WriteToFileAudioSink file_sink(file_name);
505  VLOG(0) << ">> Speak into the default microphone while recording.";
506  ais->Start(&file_sink);
507  base::PlatformThread::Sleep(TestTimeouts::action_timeout());
508  ais->Stop();
509  VLOG(0) << ">> Recording has stopped.";
510  ais.Close();
511}
512
513}  // namespace media
514