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