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