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