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