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