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