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 <stddef.h>
6 #include <stdint.h>
7
8 #include <algorithm>
9 #include <memory>
10
11 #include "base/bind.h"
12 #include "base/environment.h"
13 #include "base/files/file_util.h"
14 #include "base/macros.h"
15 #include "base/path_service.h"
16 #include "base/run_loop.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/synchronization/lock.h"
19 #include "base/test/task_environment.h"
20 #include "base/test/test_timeouts.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "base/time/time.h"
23 #include "build/build_config.h"
24 #include "media/audio/audio_device_description.h"
25 #include "media/audio/audio_device_info_accessor_for_tests.h"
26 #include "media/audio/audio_io.h"
27 #include "media/audio/audio_manager.h"
28 #include "media/audio/audio_unittest_util.h"
29 #include "media/audio/test_audio_thread.h"
30 #include "media/base/seekable_buffer.h"
31 #include "testing/gmock/include/gmock/gmock.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33
34 namespace media {
35
36 namespace {
37
38 // Limits the number of delay measurements we can store in an array and
39 // then write to file at end of the WASAPIAudioInputOutputFullDuplex test.
40 static const size_t kMaxDelayMeasurements = 1000;
41
42 // Name of the output text file. The output file will be stored in the
43 // directory containing media_unittests.exe.
44 // Example: \src\build\Debug\audio_delay_values_ms.txt.
45 // See comments for the WASAPIAudioInputOutputFullDuplex test for more details
46 // about the file format.
47 static const char kDelayValuesFileName[] = "audio_delay_values_ms.txt";
48
49 // Contains delay values which are reported during the full-duplex test.
50 // Total delay = |buffer_delay_ms| + |input_delay_ms| + |output_delay_ms|.
51 struct AudioDelayState {
AudioDelayStatemedia::__anon655ef8940111::AudioDelayState52 AudioDelayState()
53 : delta_time_ms(0),
54 buffer_delay_ms(0),
55 input_delay_ms(0),
56 output_delay_ms(0) {
57 }
58
59 // Time in milliseconds since last delay report. Typical value is ~10 [ms].
60 int delta_time_ms;
61
62 // Size of internal sync buffer. Typical value is ~0 [ms].
63 int buffer_delay_ms;
64
65 // Reported capture/input delay. Typical value is ~10 [ms].
66 int input_delay_ms;
67
68 // Reported render/output delay. Typical value is ~40 [ms].
69 int output_delay_ms;
70 };
71
OnLogMessage(const std::string & message)72 void OnLogMessage(const std::string& message) {}
73
74 // Test fixture class.
75 class AudioLowLatencyInputOutputTest : public testing::Test {
76 protected:
AudioLowLatencyInputOutputTest()77 AudioLowLatencyInputOutputTest() {
78 audio_manager_ =
79 AudioManager::CreateForTesting(std::make_unique<TestAudioThread>());
80 }
81
~AudioLowLatencyInputOutputTest()82 ~AudioLowLatencyInputOutputTest() override { audio_manager_->Shutdown(); }
83
audio_manager()84 AudioManager* audio_manager() { return audio_manager_.get(); }
task_runner()85 scoped_refptr<base::SingleThreadTaskRunner> task_runner() {
86 return task_environment_.GetMainThreadTaskRunner();
87 }
88
89 private:
90 base::test::TaskEnvironment task_environment_{
91 base::test::TaskEnvironment::MainThreadType::UI};
92 std::unique_ptr<AudioManager> audio_manager_;
93
94 DISALLOW_COPY_AND_ASSIGN(AudioLowLatencyInputOutputTest);
95 };
96
97 // This audio source/sink implementation should be used for manual tests
98 // only since delay measurements are stored on an output text file.
99 // All incoming/recorded audio packets are stored in an intermediate media
100 // buffer which the renderer reads from when it needs audio for playout.
101 // The total effect is that recorded audio is played out in loop back using
102 // a sync buffer as temporary storage.
103 class FullDuplexAudioSinkSource
104 : public AudioInputStream::AudioInputCallback,
105 public AudioOutputStream::AudioSourceCallback {
106 public:
FullDuplexAudioSinkSource(int sample_rate,int samples_per_packet,int channels)107 FullDuplexAudioSinkSource(int sample_rate,
108 int samples_per_packet,
109 int channels)
110 : sample_rate_(sample_rate),
111 samples_per_packet_(samples_per_packet),
112 channels_(channels),
113 input_elements_to_write_(0),
114 output_elements_to_write_(0),
115 previous_write_time_(base::TimeTicks::Now()) {
116 // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM).
117 frame_size_ = (16 / 8) * channels_;
118
119 // Start with the smallest possible buffer size. It will be increased
120 // dynamically during the test if required.
121 buffer_.reset(
122 new media::SeekableBuffer(0, samples_per_packet_ * frame_size_));
123
124 frames_to_ms_ = static_cast<double>(1000.0 / sample_rate_);
125 delay_states_.reset(new AudioDelayState[kMaxDelayMeasurements]);
126 }
127
~FullDuplexAudioSinkSource()128 ~FullDuplexAudioSinkSource() override {
129 // Get complete file path to output file in the directory containing
130 // media_unittests.exe. Example: src/build/Debug/audio_delay_values_ms.txt.
131 base::FilePath file_name;
132 EXPECT_TRUE(base::PathService::Get(base::DIR_EXE, &file_name));
133 file_name = file_name.AppendASCII(kDelayValuesFileName);
134
135 FILE* text_file = base::OpenFile(file_name, "wt");
136 DLOG_IF(ERROR, !text_file) << "Failed to open log file.";
137 VLOG(0) << ">> Output file " << file_name.value() << " has been created.";
138
139 // Write the array which contains time-stamps, buffer size and
140 // audio delays values to a text file.
141 size_t elements_written = 0;
142 while (elements_written <
143 std::min(input_elements_to_write_, output_elements_to_write_)) {
144 const AudioDelayState state = delay_states_[elements_written];
145 fprintf(text_file, "%d %d %d %d\n",
146 state.delta_time_ms,
147 state.buffer_delay_ms,
148 state.input_delay_ms,
149 state.output_delay_ms);
150 ++elements_written;
151 }
152
153 base::CloseFile(text_file);
154 }
155
156 // AudioInputStream::AudioInputCallback.
OnError()157 void OnError() override {}
OnData(const AudioBus * src,base::TimeTicks capture_time,double volume)158 void OnData(const AudioBus* src,
159 base::TimeTicks capture_time,
160 double volume) override {
161 base::AutoLock lock(lock_);
162
163 // Update three components in the AudioDelayState for this recorded
164 // audio packet.
165 const base::TimeTicks now_time = base::TimeTicks::Now();
166 const int diff = (now_time - previous_write_time_).InMilliseconds();
167 previous_write_time_ = now_time;
168 if (input_elements_to_write_ < kMaxDelayMeasurements) {
169 delay_states_[input_elements_to_write_].delta_time_ms = diff;
170 delay_states_[input_elements_to_write_].buffer_delay_ms =
171 BytesToMilliseconds(buffer_->forward_bytes());
172 delay_states_[input_elements_to_write_].input_delay_ms =
173 (base::TimeTicks::Now() - capture_time).InMilliseconds();
174 ++input_elements_to_write_;
175 }
176
177 // TODO(henrika): fix this and use AudioFifo instead.
178 // Store the captured audio packet in a seekable media buffer.
179 // if (!buffer_->Append(src, size)) {
180 // An attempt to write outside the buffer limits has been made.
181 // Double the buffer capacity to ensure that we have a buffer large
182 // enough to handle the current sample test scenario.
183 // buffer_->set_forward_capacity(2 * buffer_->forward_capacity());
184 // buffer_->Clear();
185 // }
186 }
187
188 // AudioOutputStream::AudioSourceCallback.
OnError(ErrorType type)189 void OnError(ErrorType type) override {}
OnMoreData(base::TimeDelta delay,base::TimeTicks,int,AudioBus * dest)190 int OnMoreData(base::TimeDelta delay,
191 base::TimeTicks /* delay_timestamp */,
192 int /* prior_frames_skipped */,
193 AudioBus* dest) override {
194 base::AutoLock lock(lock_);
195
196 // Update one component in the AudioDelayState for the packet
197 // which is about to be played out.
198 if (output_elements_to_write_ < kMaxDelayMeasurements) {
199 delay_states_[output_elements_to_write_].output_delay_ms =
200 delay.InMilliseconds();
201 ++output_elements_to_write_;
202 }
203
204 int size;
205 const uint8_t* source;
206 // Read the data from the seekable media buffer which contains
207 // captured data at the same size and sample rate as the output side.
208 if (buffer_->GetCurrentChunk(&source, &size) && size > 0) {
209 EXPECT_EQ(channels_, dest->channels());
210 size = std::min(dest->frames() * frame_size_, size);
211 EXPECT_EQ(static_cast<size_t>(size) % sizeof(*dest->channel(0)), 0U);
212 dest->FromInterleaved(source, size / frame_size_,
213 frame_size_ / channels_);
214 buffer_->Seek(size);
215 return size / frame_size_;
216 }
217
218 return 0;
219 }
220
221 protected:
222 // Converts from bytes to milliseconds taking the sample rate and size
223 // of an audio frame into account.
BytesToMilliseconds(uint32_t delay_bytes) const224 int BytesToMilliseconds(uint32_t delay_bytes) const {
225 return static_cast<int>((delay_bytes / frame_size_) * frames_to_ms_ + 0.5);
226 }
227
228 private:
229 base::Lock lock_;
230 std::unique_ptr<media::SeekableBuffer> buffer_;
231 int sample_rate_;
232 int samples_per_packet_;
233 int channels_;
234 int frame_size_;
235 double frames_to_ms_;
236 std::unique_ptr<AudioDelayState[]> delay_states_;
237 size_t input_elements_to_write_;
238 size_t output_elements_to_write_;
239 base::TimeTicks previous_write_time_;
240 };
241
242 class AudioInputStreamTraits {
243 public:
244 typedef AudioInputStream StreamType;
245
GetDefaultAudioStreamParameters(AudioManager * audio_manager)246 static AudioParameters GetDefaultAudioStreamParameters(
247 AudioManager* audio_manager) {
248 return AudioDeviceInfoAccessorForTests(audio_manager)
249 .GetInputStreamParameters(AudioDeviceDescription::kDefaultDeviceId);
250 }
251
CreateStream(AudioManager * audio_manager,const AudioParameters & params)252 static StreamType* CreateStream(AudioManager* audio_manager,
253 const AudioParameters& params) {
254 return audio_manager->MakeAudioInputStream(
255 params, AudioDeviceDescription::kDefaultDeviceId,
256 base::BindRepeating(&OnLogMessage));
257 }
258 };
259
260 class AudioOutputStreamTraits {
261 public:
262 typedef AudioOutputStream StreamType;
263
GetDefaultAudioStreamParameters(AudioManager * audio_manager)264 static AudioParameters GetDefaultAudioStreamParameters(
265 AudioManager* audio_manager) {
266 return AudioDeviceInfoAccessorForTests(audio_manager)
267 .GetDefaultOutputStreamParameters();
268 }
269
CreateStream(AudioManager * audio_manager,const AudioParameters & params)270 static StreamType* CreateStream(AudioManager* audio_manager,
271 const AudioParameters& params) {
272 return audio_manager->MakeAudioOutputStream(
273 params, std::string(), base::BindRepeating(&OnLogMessage));
274 }
275 };
276
277 // Traits template holding a trait of StreamType. It encapsulates
278 // AudioInputStream and AudioOutputStream stream types.
279 template <typename StreamTraits>
280 class StreamWrapper {
281 public:
282 typedef typename StreamTraits::StreamType StreamType;
283
StreamWrapper(AudioManager * audio_manager)284 explicit StreamWrapper(AudioManager* audio_manager)
285 : audio_manager_(audio_manager),
286 format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
287 #if defined(OS_ANDROID)
288 channel_layout_(CHANNEL_LAYOUT_MONO)
289 #else
290 channel_layout_(CHANNEL_LAYOUT_STEREO)
291 #endif
292 {
293 // Use the preferred sample rate.
294 const AudioParameters& params =
295 StreamTraits::GetDefaultAudioStreamParameters(audio_manager_);
296 sample_rate_ = params.sample_rate();
297
298 // Use the preferred buffer size. Note that the input side uses the same
299 // size as the output side in this implementation.
300 samples_per_packet_ = params.frames_per_buffer();
301 }
302
303 virtual ~StreamWrapper() = default;
304
305 // Creates an Audio[Input|Output]Stream stream object using default
306 // parameters.
Create()307 StreamType* Create() {
308 return CreateStream();
309 }
310
channels() const311 int channels() const {
312 return ChannelLayoutToChannelCount(channel_layout_);
313 }
sample_rate() const314 int sample_rate() const { return sample_rate_; }
samples_per_packet() const315 int samples_per_packet() const { return samples_per_packet_; }
316
317 private:
CreateStream()318 StreamType* CreateStream() {
319 StreamType* stream = StreamTraits::CreateStream(
320 audio_manager_, AudioParameters(format_, channel_layout_, sample_rate_,
321 samples_per_packet_));
322 EXPECT_TRUE(stream);
323 return stream;
324 }
325
326 AudioManager* audio_manager_;
327 AudioParameters::Format format_;
328 ChannelLayout channel_layout_;
329 int sample_rate_;
330 int samples_per_packet_;
331 };
332
333 typedef StreamWrapper<AudioInputStreamTraits> AudioInputStreamWrapper;
334 typedef StreamWrapper<AudioOutputStreamTraits> AudioOutputStreamWrapper;
335
336 // This test is intended for manual tests and should only be enabled
337 // when it is required to make a real-time test of audio in full duplex and
338 // at the same time create a text file which contains measured delay values.
339 // The file can later be analyzed off line using e.g. MATLAB.
340 // MATLAB example:
341 // D=load('audio_delay_values_ms.txt');
342 // x=cumsum(D(:,1));
343 // plot(x, D(:,2), x, D(:,3), x, D(:,4), x, D(:,2)+D(:,3)+D(:,4));
344 // axis([0, max(x), 0, max(D(:,2)+D(:,3)+D(:,4))+10]);
345 // legend('buffer delay','input delay','output delay','total delay');
346 // xlabel('time [msec]')
347 // ylabel('delay [msec]')
348 // title('Full-duplex audio delay measurement');
TEST_F(AudioLowLatencyInputOutputTest,DISABLED_FullDuplexDelayMeasurement)349 TEST_F(AudioLowLatencyInputOutputTest, DISABLED_FullDuplexDelayMeasurement) {
350 AudioDeviceInfoAccessorForTests device_info_accessor(audio_manager());
351 ABORT_AUDIO_TEST_IF_NOT(device_info_accessor.HasAudioInputDevices() &&
352 device_info_accessor.HasAudioOutputDevices());
353
354 AudioInputStreamWrapper aisw(audio_manager());
355 AudioInputStream* ais = aisw.Create();
356 EXPECT_TRUE(ais);
357
358 AudioOutputStreamWrapper aosw(audio_manager());
359 AudioOutputStream* aos = aosw.Create();
360 EXPECT_TRUE(aos);
361
362 // This test only supports identical parameters in both directions.
363 // TODO(henrika): it is possible to cut delay here by using different
364 // buffer sizes for input and output.
365 if (aisw.sample_rate() != aosw.sample_rate() ||
366 aisw.samples_per_packet() != aosw.samples_per_packet() ||
367 aisw.channels() != aosw.channels()) {
368 LOG(ERROR) << "This test requires symmetric input and output parameters. "
369 "Ensure that sample rate and number of channels are identical in "
370 "both directions";
371 aos->Close();
372 ais->Close();
373 return;
374 }
375
376 EXPECT_TRUE(ais->Open());
377 EXPECT_TRUE(aos->Open());
378
379 FullDuplexAudioSinkSource full_duplex(
380 aisw.sample_rate(), aisw.samples_per_packet(), aisw.channels());
381
382 VLOG(0) << ">> You should now be able to hear yourself in loopback...";
383 DVLOG(0) << " sample_rate : " << aisw.sample_rate();
384 DVLOG(0) << " samples_per_packet: " << aisw.samples_per_packet();
385 DVLOG(0) << " channels : " << aisw.channels();
386
387 ais->Start(&full_duplex);
388 aos->Start(&full_duplex);
389
390 // Wait for approximately 10 seconds. The user will hear their own voice
391 // in loop back during this time. At the same time, delay recordings are
392 // performed and stored in the output text file.
393 task_runner()->PostDelayedTask(
394 FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(),
395 TestTimeouts::action_timeout());
396 base::RunLoop().Run();
397
398 aos->Stop();
399 ais->Stop();
400
401 // All Close() operations that run on the mocked audio thread,
402 // should be synchronous and not post additional close tasks to
403 // mocked the audio thread. Hence, there is no need to call
404 // message_loop()->RunUntilIdle() after the Close() methods.
405 aos->Close();
406 ais->Close();
407 }
408
409 } // namespace
410
411 } // namespace media
412