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