audio_low_latency_input_mac_unittest.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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 "base/basictypes.h"
6#include "base/environment.h"
7#include "base/message_loop/message_loop.h"
8#include "base/run_loop.h"
9#include "base/test/test_timeouts.h"
10#include "base/threading/platform_thread.h"
11#include "media/audio/audio_io.h"
12#include "media/audio/audio_manager_base.h"
13#include "media/audio/mac/audio_low_latency_input_mac.h"
14#include "media/base/seekable_buffer.h"
15#include "testing/gmock/include/gmock/gmock.h"
16#include "testing/gtest/include/gtest/gtest.h"
17
18using ::testing::_;
19using ::testing::AnyNumber;
20using ::testing::AtLeast;
21using ::testing::Ge;
22using ::testing::NotNull;
23
24namespace media {
25
26ACTION_P4(CheckCountAndPostQuitTask, count, limit, loop, closure) {
27  if (++*count >= limit) {
28    loop->PostTask(FROM_HERE, closure);
29  }
30}
31
32class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
33 public:
34  MOCK_METHOD5(OnData, void(AudioInputStream* stream,
35                            const uint8* src, uint32 size,
36                            uint32 hardware_delay_bytes, double volume));
37  MOCK_METHOD1(OnError, void(AudioInputStream* stream));
38};
39
40// This audio sink implementation should be used for manual tests only since
41// the recorded data is stored on a raw binary data file.
42// The last test (WriteToFileAudioSink) - which is disabled by default -
43// can use this audio sink to store the captured data on a file for offline
44// analysis.
45class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback {
46 public:
47  // Allocate space for ~10 seconds of data @ 48kHz in stereo:
48  // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes.
49  static const int kMaxBufferSize = 2 * 2 * 480 * 100 * 10;
50
51  explicit WriteToFileAudioSink(const char* file_name)
52      : buffer_(0, kMaxBufferSize),
53        file_(fopen(file_name, "wb")),
54        bytes_to_write_(0) {
55  }
56
57  virtual ~WriteToFileAudioSink() {
58    int bytes_written = 0;
59    while (bytes_written < bytes_to_write_) {
60      const uint8* chunk;
61      int chunk_size;
62
63      // Stop writing if no more data is available.
64      if (!buffer_.GetCurrentChunk(&chunk, &chunk_size))
65        break;
66
67      // Write recorded data chunk to the file and prepare for next chunk.
68      fwrite(chunk, 1, chunk_size, file_);
69      buffer_.Seek(chunk_size);
70      bytes_written += chunk_size;
71    }
72    fclose(file_);
73  }
74
75  // AudioInputStream::AudioInputCallback implementation.
76  virtual void OnData(AudioInputStream* stream,
77                      const uint8* src, uint32 size,
78                      uint32 hardware_delay_bytes, double volume) OVERRIDE {
79    // Store data data in a temporary buffer to avoid making blocking
80    // fwrite() calls in the audio callback. The complete buffer will be
81    // written to file in the destructor.
82    if (buffer_.Append(src, size)) {
83      bytes_to_write_ += size;
84    }
85  }
86
87  virtual void OnError(AudioInputStream* stream) OVERRIDE {}
88
89 private:
90  media::SeekableBuffer buffer_;
91  FILE* file_;
92  int bytes_to_write_;
93};
94
95class MacAudioInputTest : public testing::Test {
96 protected:
97  MacAudioInputTest()
98      : message_loop_(base::MessageLoop::TYPE_UI),
99        audio_manager_(AudioManager::CreateForTesting()) {
100    // Wait for the AudioManager to finish any initialization on the audio loop.
101    base::RunLoop().RunUntilIdle();
102  }
103
104  virtual ~MacAudioInputTest() {
105    base::RunLoop().RunUntilIdle();
106  }
107
108  // Convenience method which ensures that we are not running on the build
109  // bots and that at least one valid input device can be found.
110  bool CanRunAudioTests() {
111    bool has_input = audio_manager_->HasAudioInputDevices();
112    if (!has_input)
113      LOG(WARNING) << "No input devices detected";
114    return has_input;
115  }
116
117  // Convenience method which creates a default AudioInputStream object using
118  // a 10ms frame size and a sample rate which is set to the hardware sample
119  // rate.
120  AudioInputStream* CreateDefaultAudioInputStream() {
121    int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
122    int samples_per_packet = fs / 100;
123    AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
124        AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
125        CHANNEL_LAYOUT_STEREO, fs, 16, samples_per_packet),
126        AudioManagerBase::kDefaultDeviceId);
127    EXPECT_TRUE(ais);
128    return ais;
129  }
130
131  // Convenience method which creates an AudioInputStream object with a
132  // specified channel layout.
133  AudioInputStream* CreateAudioInputStream(ChannelLayout channel_layout) {
134    int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
135    int samples_per_packet = fs / 100;
136    AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
137        AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
138        channel_layout, fs, 16, samples_per_packet),
139        AudioManagerBase::kDefaultDeviceId);
140    EXPECT_TRUE(ais);
141    return ais;
142  }
143
144  base::MessageLoop message_loop_;
145  scoped_ptr<AudioManager> audio_manager_;
146};
147
148// Test Create(), Close().
149TEST_F(MacAudioInputTest, AUAudioInputStreamCreateAndClose) {
150  if (!CanRunAudioTests())
151    return;
152  AudioInputStream* ais = CreateDefaultAudioInputStream();
153  ais->Close();
154}
155
156// Test Open(), Close().
157TEST_F(MacAudioInputTest, AUAudioInputStreamOpenAndClose) {
158  if (!CanRunAudioTests())
159    return;
160  AudioInputStream* ais = CreateDefaultAudioInputStream();
161  EXPECT_TRUE(ais->Open());
162  ais->Close();
163}
164
165// Test Open(), Start(), Close().
166TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartAndClose) {
167  if (!CanRunAudioTests())
168    return;
169  AudioInputStream* ais = CreateDefaultAudioInputStream();
170  EXPECT_TRUE(ais->Open());
171  MockAudioInputCallback sink;
172  ais->Start(&sink);
173  ais->Close();
174}
175
176// Test Open(), Start(), Stop(), Close().
177TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartStopAndClose) {
178  if (!CanRunAudioTests())
179    return;
180  AudioInputStream* ais = CreateDefaultAudioInputStream();
181  EXPECT_TRUE(ais->Open());
182  MockAudioInputCallback sink;
183  ais->Start(&sink);
184  ais->Stop();
185  ais->Close();
186}
187
188// Test some additional calling sequences.
189TEST_F(MacAudioInputTest, AUAudioInputStreamMiscCallingSequences) {
190  if (!CanRunAudioTests())
191    return;
192  AudioInputStream* ais = CreateDefaultAudioInputStream();
193  AUAudioInputStream* auais = static_cast<AUAudioInputStream*>(ais);
194
195  // Open(), Open() should fail the second time.
196  EXPECT_TRUE(ais->Open());
197  EXPECT_FALSE(ais->Open());
198
199  MockAudioInputCallback sink;
200
201  // Start(), Start() is a valid calling sequence (second call does nothing).
202  ais->Start(&sink);
203  EXPECT_TRUE(auais->started());
204  ais->Start(&sink);
205  EXPECT_TRUE(auais->started());
206
207  // Stop(), Stop() is a valid calling sequence (second call does nothing).
208  ais->Stop();
209  EXPECT_FALSE(auais->started());
210  ais->Stop();
211  EXPECT_FALSE(auais->started());
212
213  ais->Close();
214}
215
216// Verify that recording starts and stops correctly in mono using mocked sink.
217TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) {
218  if (!CanRunAudioTests())
219    return;
220
221  int count = 0;
222
223  // Create an audio input stream which records in mono.
224  AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_MONO);
225  EXPECT_TRUE(ais->Open());
226
227  int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
228  int samples_per_packet = fs / 100;
229  int bits_per_sample = 16;
230  uint32 bytes_per_packet = samples_per_packet * (bits_per_sample / 8);
231
232  MockAudioInputCallback sink;
233
234  // We use 10ms packets and will run the test until ten packets are received.
235  // All should contain valid packets of the same size and a valid delay
236  // estimate.
237  base::RunLoop run_loop;
238  EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, _, _))
239      .Times(AtLeast(10))
240      .WillRepeatedly(CheckCountAndPostQuitTask(
241          &count, 10, &message_loop_, run_loop.QuitClosure()));
242  ais->Start(&sink);
243  run_loop.Run();
244  ais->Stop();
245  ais->Close();
246}
247
248// Verify that recording starts and stops correctly in mono using mocked sink.
249TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) {
250  if (!CanRunAudioTests())
251    return;
252
253  int count = 0;
254
255  // Create an audio input stream which records in stereo.
256  AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_STEREO);
257  EXPECT_TRUE(ais->Open());
258
259  int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
260  int samples_per_packet = fs / 100;
261  int bits_per_sample = 16;
262  uint32 bytes_per_packet = 2 * samples_per_packet * (bits_per_sample / 8);
263
264  MockAudioInputCallback sink;
265
266  // We use 10ms packets and will run the test until ten packets are received.
267  // All should contain valid packets of the same size and a valid delay
268  // estimate.
269  // TODO(henrika): http://crbug.com/154352 forced us to run the capture side
270  // using a native buffer size of 128 audio frames and combine it with a FIFO
271  // to match the requested size by the client. This change might also have
272  // modified the delay estimates since the existing Ge(bytes_per_packet) for
273  // parameter #4 does no longer pass. I am removing this restriction here to
274  // ensure that we can land the patch but will revisit this test again when
275  // more analysis of the delay estimates are done.
276  base::RunLoop run_loop;
277  EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, _, _))
278      .Times(AtLeast(10))
279      .WillRepeatedly(CheckCountAndPostQuitTask(
280          &count, 10, &message_loop_, run_loop.QuitClosure()));
281  ais->Start(&sink);
282  run_loop.Run();
283  ais->Stop();
284  ais->Close();
285}
286
287// This test is intended for manual tests and should only be enabled
288// when it is required to store the captured data on a local file.
289// By default, GTest will print out YOU HAVE 1 DISABLED TEST.
290// To include disabled tests in test execution, just invoke the test program
291// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
292// environment variable to a value greater than 0.
293TEST_F(MacAudioInputTest, DISABLED_AUAudioInputStreamRecordToFile) {
294  if (!CanRunAudioTests())
295    return;
296  const char* file_name = "out_stereo_10sec.pcm";
297
298  int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
299  AudioInputStream* ais = CreateDefaultAudioInputStream();
300  EXPECT_TRUE(ais->Open());
301
302  fprintf(stderr, "               File name  : %s\n", file_name);
303  fprintf(stderr, "               Sample rate: %d\n", fs);
304  WriteToFileAudioSink file_sink(file_name);
305  fprintf(stderr, "               >> Speak into the mic while recording...\n");
306  ais->Start(&file_sink);
307  base::PlatformThread::Sleep(TestTimeouts::action_timeout());
308  ais->Stop();
309  fprintf(stderr, "               >> Recording has stopped.\n");
310  ais->Close();
311}
312
313}  // namespace media
314