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