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 <stdint.h>
6
7 #include <memory>
8
9 #include "base/bind.h"
10 #include "base/environment.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/run_loop.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/test/task_environment.h"
15 #include "base/test/test_timeouts.h"
16 #include "base/threading/platform_thread.h"
17 #include "media/audio/audio_device_description.h"
18 #include "media/audio/audio_device_info_accessor_for_tests.h"
19 #include "media/audio/audio_io.h"
20 #include "media/audio/audio_manager_base.h"
21 #include "media/audio/audio_unittest_util.h"
22 #include "media/audio/mac/audio_low_latency_input_mac.h"
23 #include "media/audio/test_audio_thread.h"
24 #include "media/base/seekable_buffer.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27
28 using ::testing::_;
29 using ::testing::AnyNumber;
30 using ::testing::AtLeast;
31 using ::testing::Ge;
32 using ::testing::NotNull;
33
34 namespace media {
35
ACTION_P4(CheckCountAndPostQuitTask,count,limit,task_runner,closure)36 ACTION_P4(CheckCountAndPostQuitTask, count, limit, task_runner, closure) {
37 if (++*count >= limit) {
38 task_runner->PostTask(FROM_HERE, closure);
39 }
40 }
41
42 class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
43 public:
44 MOCK_METHOD3(OnData,
45 void(const AudioBus* src,
46 base::TimeTicks capture_time,
47 double volume));
48 MOCK_METHOD0(OnError, void());
49 };
50
51 // This audio sink implementation should be used for manual tests only since
52 // the recorded data is stored on a raw binary data file.
53 // The last test (WriteToFileAudioSink) - which is disabled by default -
54 // can use this audio sink to store the captured data on a file for offline
55 // analysis.
56 class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback {
57 public:
58 // Allocate space for ~10 seconds of data @ 48kHz in stereo:
59 // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes.
60 static const int kMaxBufferSize = 2 * 2 * 480 * 100 * 10;
61
WriteToFileAudioSink(const char * file_name)62 explicit WriteToFileAudioSink(const char* file_name)
63 : buffer_(0, kMaxBufferSize),
64 file_(fopen(file_name, "wb")),
65 bytes_to_write_(0) {
66 }
67
~WriteToFileAudioSink()68 ~WriteToFileAudioSink() override {
69 int bytes_written = 0;
70 while (bytes_written < bytes_to_write_) {
71 const uint8_t* 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, file_);
80 buffer_.Seek(chunk_size);
81 bytes_written += chunk_size;
82 }
83 fclose(file_);
84 }
85
86 // AudioInputStream::AudioInputCallback implementation.
OnData(const AudioBus * src,base::TimeTicks capture_time,double volume)87 void OnData(const AudioBus* src,
88 base::TimeTicks capture_time,
89 double volume) override {
90 const int num_samples = src->frames() * src->channels();
91 std::unique_ptr<int16_t> interleaved(new int16_t[num_samples]);
92 src->ToInterleaved<SignedInt16SampleTypeTraits>(src->frames(),
93 interleaved.get());
94
95 // Store data data in a temporary buffer to avoid making blocking
96 // fwrite() calls in the audio callback. The complete buffer will be
97 // written to file in the destructor.
98 const int bytes_per_sample = sizeof(*interleaved);
99 const int size = bytes_per_sample * num_samples;
100 if (buffer_.Append((const uint8_t*)interleaved.get(), size)) {
101 bytes_to_write_ += size;
102 }
103 }
104
OnError()105 void OnError() override {}
106
107 private:
108 media::SeekableBuffer buffer_;
109 FILE* file_;
110 int bytes_to_write_;
111 };
112
113 class MacAudioInputTest : public testing::Test {
114 protected:
MacAudioInputTest()115 MacAudioInputTest()
116 : task_environment_(
117 base::test::SingleThreadTaskEnvironment::MainThreadType::UI),
118 audio_manager_(AudioManager::CreateForTesting(
119 std::make_unique<TestAudioThread>())) {
120 // Wait for the AudioManager to finish any initialization on the audio loop.
121 base::RunLoop().RunUntilIdle();
122 }
123
~MacAudioInputTest()124 ~MacAudioInputTest() override { audio_manager_->Shutdown(); }
125
InputDevicesAvailable()126 bool InputDevicesAvailable() {
127 return AudioDeviceInfoAccessorForTests(audio_manager_.get())
128 .HasAudioInputDevices();
129 }
130
131 // Convenience method which creates a default AudioInputStream object using
132 // a 10ms frame size and a sample rate which is set to the hardware sample
133 // rate.
CreateDefaultAudioInputStream()134 AudioInputStream* CreateDefaultAudioInputStream() {
135 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
136 int samples_per_packet = fs / 100;
137 AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
138 AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
139 CHANNEL_LAYOUT_STEREO, fs, samples_per_packet),
140 AudioDeviceDescription::kDefaultDeviceId,
141 base::BindRepeating(&MacAudioInputTest::OnLogMessage,
142 base::Unretained(this)));
143 EXPECT_TRUE(ais);
144 return ais;
145 }
146
147 // Convenience method which creates an AudioInputStream object with a
148 // specified channel layout.
CreateAudioInputStream(ChannelLayout channel_layout)149 AudioInputStream* CreateAudioInputStream(ChannelLayout channel_layout) {
150 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
151 int samples_per_packet = fs / 100;
152 AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
153 AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
154 fs, samples_per_packet),
155 AudioDeviceDescription::kDefaultDeviceId,
156 base::BindRepeating(&MacAudioInputTest::OnLogMessage,
157 base::Unretained(this)));
158 EXPECT_TRUE(ais);
159 return ais;
160 }
161
OnLogMessage(const std::string & message)162 void OnLogMessage(const std::string& message) { log_message_ = message; }
163
164 base::test::SingleThreadTaskEnvironment task_environment_;
165 std::unique_ptr<AudioManager> audio_manager_;
166 std::string log_message_;
167 };
168
169 // Test Create(), Close().
TEST_F(MacAudioInputTest,AUAudioInputStreamCreateAndClose)170 TEST_F(MacAudioInputTest, AUAudioInputStreamCreateAndClose) {
171 ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
172 AudioInputStream* ais = CreateDefaultAudioInputStream();
173 ais->Close();
174 }
175
176 // Test Open(), Close().
TEST_F(MacAudioInputTest,AUAudioInputStreamOpenAndClose)177 TEST_F(MacAudioInputTest, AUAudioInputStreamOpenAndClose) {
178 ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
179 AudioInputStream* ais = CreateDefaultAudioInputStream();
180 EXPECT_TRUE(ais->Open());
181 ais->Close();
182 }
183
184 // Test Open(), Start(), Close().
TEST_F(MacAudioInputTest,AUAudioInputStreamOpenStartAndClose)185 TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartAndClose) {
186 ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
187 AudioInputStream* ais = CreateDefaultAudioInputStream();
188 EXPECT_TRUE(ais->Open());
189 MockAudioInputCallback sink;
190 ais->Start(&sink);
191 ais->Close();
192 }
193
194 // Test Open(), Start(), Stop(), Close().
TEST_F(MacAudioInputTest,AUAudioInputStreamOpenStartStopAndClose)195 TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartStopAndClose) {
196 ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
197 AudioInputStream* ais = CreateDefaultAudioInputStream();
198 EXPECT_TRUE(ais->Open());
199 MockAudioInputCallback sink;
200 ais->Start(&sink);
201 ais->Stop();
202 ais->Close();
203 }
204
205 // Verify that recording starts and stops correctly in mono using mocked sink.
TEST_F(MacAudioInputTest,AUAudioInputStreamVerifyMonoRecording)206 TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) {
207 ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
208
209 int count = 0;
210
211 // Create an audio input stream which records in mono.
212 AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_MONO);
213 EXPECT_TRUE(ais->Open());
214
215 MockAudioInputCallback sink;
216
217 // We use 10ms packets and will run the test until ten packets are received.
218 // All should contain valid packets of the same size and a valid delay
219 // estimate.
220 base::RunLoop run_loop;
221 EXPECT_CALL(sink, OnData(NotNull(), _, _))
222 .Times(AtLeast(10))
223 .WillRepeatedly(CheckCountAndPostQuitTask(
224 &count, 10, task_environment_.GetMainThreadTaskRunner(),
225 run_loop.QuitClosure()));
226 ais->Start(&sink);
227 run_loop.Run();
228 ais->Stop();
229 ais->Close();
230
231 EXPECT_FALSE(log_message_.empty());
232 }
233
234 // Verify that recording starts and stops correctly in mono using mocked sink.
TEST_F(MacAudioInputTest,AUAudioInputStreamVerifyStereoRecording)235 TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) {
236 ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
237
238 int count = 0;
239
240 // Create an audio input stream which records in stereo.
241 AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_STEREO);
242 EXPECT_TRUE(ais->Open());
243
244 MockAudioInputCallback sink;
245
246 // We use 10ms packets and will run the test until ten packets are received.
247 // All should contain valid packets of the same size and a valid delay
248 // estimate.
249 // TODO(henrika): http://crbug.com/154352 forced us to run the capture side
250 // using a native buffer size of 128 audio frames and combine it with a FIFO
251 // to match the requested size by the client. This change might also have
252 // modified the delay estimates since the existing Ge(bytes_per_packet) for
253 // parameter #4 does no longer pass. I am removing this restriction here to
254 // ensure that we can land the patch but will revisit this test again when
255 // more analysis of the delay estimates are done.
256 base::RunLoop run_loop;
257 EXPECT_CALL(sink, OnData(NotNull(), _, _))
258 .Times(AtLeast(10))
259 .WillRepeatedly(CheckCountAndPostQuitTask(
260 &count, 10, task_environment_.GetMainThreadTaskRunner(),
261 run_loop.QuitClosure()));
262 ais->Start(&sink);
263 run_loop.Run();
264 ais->Stop();
265 ais->Close();
266
267 EXPECT_FALSE(log_message_.empty());
268 }
269
270 // This test is intended for manual tests and should only be enabled
271 // when it is required to store the captured data on a local file.
272 // By default, GTest will print out YOU HAVE 1 DISABLED TEST.
273 // To include disabled tests in test execution, just invoke the test program
274 // with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
275 // environment variable to a value greater than 0.
TEST_F(MacAudioInputTest,DISABLED_AUAudioInputStreamRecordToFile)276 TEST_F(MacAudioInputTest, DISABLED_AUAudioInputStreamRecordToFile) {
277 ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
278 const char* file_name = "out_stereo_10sec.pcm";
279
280 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
281 AudioInputStream* ais = CreateDefaultAudioInputStream();
282 EXPECT_TRUE(ais->Open());
283
284 fprintf(stderr, " File name : %s\n", file_name);
285 fprintf(stderr, " Sample rate: %d\n", fs);
286 WriteToFileAudioSink file_sink(file_name);
287 fprintf(stderr, " >> Speak into the mic while recording...\n");
288 ais->Start(&file_sink);
289 base::PlatformThread::Sleep(TestTimeouts::action_timeout());
290 ais->Stop();
291 fprintf(stderr, " >> Recording has stopped.\n");
292 ais->Close();
293 }
294
TEST(MacAudioInputUpmixerTest,Upmix16bit)295 TEST(MacAudioInputUpmixerTest, Upmix16bit) {
296 constexpr int kNumFrames = 512;
297 constexpr int kBytesPerSample = sizeof(int16_t);
298 int16_t mono[kNumFrames];
299 int16_t stereo[kNumFrames * 2];
300
301 // Fill the mono buffer and the first half of the stereo buffer with data
302 for (int i = 0; i != kNumFrames; ++i) {
303 mono[i] = i;
304 stereo[i] = i;
305 }
306
307 AudioBuffer audio_buffer;
308 audio_buffer.mNumberChannels = 2;
309 audio_buffer.mDataByteSize = kNumFrames * kBytesPerSample * 2;
310 audio_buffer.mData = stereo;
311 AUAudioInputStream::UpmixMonoToStereoInPlace(&audio_buffer, kBytesPerSample);
312
313 // Assert that the samples have been distributed properly
314 for (int i = 0; i != kNumFrames; ++i) {
315 ASSERT_EQ(mono[i], stereo[i * 2]);
316 ASSERT_EQ(mono[i], stereo[i * 2 + 1]);
317 }
318 }
319
TEST(MacAudioInputUpmixerTest,Upmix32bit)320 TEST(MacAudioInputUpmixerTest, Upmix32bit) {
321 constexpr int kNumFrames = 512;
322 constexpr int kBytesPerSample = sizeof(int32_t);
323 int32_t mono[kNumFrames];
324 int32_t stereo[kNumFrames * 2];
325
326 // Fill the mono buffer and the first half of the stereo buffer with data
327 for (int i = 0; i != kNumFrames; ++i) {
328 mono[i] = i;
329 stereo[i] = i;
330 }
331
332 AudioBuffer audio_buffer;
333 audio_buffer.mNumberChannels = 2;
334 audio_buffer.mDataByteSize = kNumFrames * kBytesPerSample * 2;
335 audio_buffer.mData = stereo;
336 AUAudioInputStream::UpmixMonoToStereoInPlace(&audio_buffer, kBytesPerSample);
337
338 // Assert that the samples have been distributed properly
339 for (int i = 0; i != kNumFrames; ++i) {
340 ASSERT_EQ(mono[i], stereo[i * 2]);
341 ASSERT_EQ(mono[i], stereo[i * 2 + 1]);
342 }
343 }
344
345 } // namespace media
346