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