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.
5 #include "base/basictypes.h"
6 #include "base/command_line.h"
7 #include "base/file_util.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/path_service.h"
10 #include "base/test/test_timeouts.h"
11 #include "base/time/time.h"
12 #include "base/win/scoped_com_initializer.h"
13 #include "media/audio/audio_io.h"
14 #include "media/audio/audio_manager.h"
15 #include "media/audio/win/audio_unified_win.h"
16 #include "media/audio/win/core_audio_util_win.h"
17 #include "media/base/channel_mixer.h"
18 #include "media/base/media_switches.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
23 using ::testing::AtLeast
;
24 using ::testing::Between
;
25 using ::testing::DoAll
;
26 using ::testing::NotNull
;
27 using ::testing::Return
;
28 using base::win::ScopedCOMInitializer
;
32 static const size_t kMaxDeltaSamples
= 1000;
33 static const char kDeltaTimeMsFileName
[] = "unified_delta_times_ms.txt";
35 // Verify that the delay estimate in the OnMoreIOData() callback is larger
36 // than an expected minumum value.
37 MATCHER_P(DelayGreaterThan
, value
, "") {
38 return (arg
.hardware_delay_bytes
> value
.hardware_delay_bytes
);
41 // Used to terminate a loop from a different thread than the loop belongs to.
42 // |loop| should be a MessageLoopProxy.
43 ACTION_P(QuitLoop
, loop
) {
44 loop
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
47 class MockUnifiedSourceCallback
48 : public AudioOutputStream::AudioSourceCallback
{
50 MOCK_METHOD2(OnMoreData
, int(AudioBus
* audio_bus
,
51 AudioBuffersState buffers_state
));
52 MOCK_METHOD3(OnMoreIOData
, int(AudioBus
* source
,
54 AudioBuffersState buffers_state
));
55 MOCK_METHOD1(OnError
, void(AudioOutputStream
* stream
));
58 // AudioOutputStream::AudioSourceCallback implementation which enables audio
59 // play-through. It also creates a text file that contains times between two
60 // successive callbacks. Units are in milliseconds. This file can be used for
61 // off-line analysis of the callback sequence.
62 class UnifiedSourceCallback
: public AudioOutputStream::AudioSourceCallback
{
64 explicit UnifiedSourceCallback()
65 : previous_call_time_(base::TimeTicks::Now()),
67 elements_to_write_(0) {
68 delta_times_
.reset(new int[kMaxDeltaSamples
]);
71 virtual ~UnifiedSourceCallback() {
72 base::FilePath file_name
;
73 EXPECT_TRUE(PathService::Get(base::DIR_EXE
, &file_name
));
74 file_name
= file_name
.AppendASCII(kDeltaTimeMsFileName
);
76 EXPECT_TRUE(!text_file_
);
77 text_file_
= file_util::OpenFile(file_name
, "wt");
78 DLOG_IF(ERROR
, !text_file_
) << "Failed to open log file.";
79 LOG(INFO
) << ">> Output file " << file_name
.value() << " has been created.";
81 // Write the array which contains delta times to a text file.
82 size_t elements_written
= 0;
83 while (elements_written
< elements_to_write_
) {
84 fprintf(text_file_
, "%d\n", delta_times_
[elements_written
]);
87 file_util::CloseFile(text_file_
);
90 virtual int OnMoreData(AudioBus
* dest
,
91 AudioBuffersState buffers_state
) {
96 virtual int OnMoreIOData(AudioBus
* source
,
98 AudioBuffersState buffers_state
) {
99 // Store time between this callback and the previous callback.
100 const base::TimeTicks now_time
= base::TimeTicks::Now();
101 const int diff
= (now_time
- previous_call_time_
).InMilliseconds();
102 previous_call_time_
= now_time
;
103 if (elements_to_write_
< kMaxDeltaSamples
) {
104 delta_times_
[elements_to_write_
] = diff
;
105 ++elements_to_write_
;
108 // Play out the recorded audio samples in loop back. Perform channel mixing
109 // if required using a channel mixer which is created only if needed.
110 if (source
->channels() == dest
->channels()) {
111 source
->CopyTo(dest
);
113 // A channel mixer is required for converting audio between two different
115 if (!channel_mixer_
) {
116 // Guessing the channel layout will work OK for this unit test.
117 // Main thing is that the number of channels is correct.
118 ChannelLayout input_layout
= GuessChannelLayout(source
->channels());
119 ChannelLayout output_layout
= GuessChannelLayout(dest
->channels());
120 channel_mixer_
.reset(new ChannelMixer(input_layout
, output_layout
));
121 DVLOG(1) << "Remixing channel layout from " << input_layout
122 << " to " << output_layout
<< "; from "
123 << source
->channels() << " channels to "
124 << dest
->channels() << " channels.";
127 channel_mixer_
->Transform(source
, dest
);
129 return source
->frames();
132 virtual void OnError(AudioOutputStream
* stream
) {
137 base::TimeTicks previous_call_time_
;
138 scoped_ptr
<int[]> delta_times_
;
140 size_t elements_to_write_
;
141 scoped_ptr
<ChannelMixer
> channel_mixer_
;
144 // Convenience method which ensures that we fulfill all required conditions
145 // to run unified audio tests on Windows.
146 static bool CanRunUnifiedAudioTests(AudioManager
* audio_man
) {
147 if (!CoreAudioUtil::IsSupported()) {
148 LOG(WARNING
) << "This tests requires Windows Vista or higher.";
152 if (!audio_man
->HasAudioOutputDevices()) {
153 LOG(WARNING
) << "No output devices detected.";
157 if (!audio_man
->HasAudioInputDevices()) {
158 LOG(WARNING
) << "No input devices detected.";
165 // Convenience class which simplifies creation of a unified AudioOutputStream
167 class AudioUnifiedStreamWrapper
{
169 explicit AudioUnifiedStreamWrapper(AudioManager
* audio_manager
)
170 : com_init_(ScopedCOMInitializer::kMTA
),
171 audio_man_(audio_manager
) {
172 // We open up both both sides (input and output) using the preferred
173 // set of audio parameters. These parameters corresponds to the mix format
174 // that the audio engine uses internally for processing of shared-mode
176 AudioParameters out_params
;
177 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
178 eRender
, eConsole
, &out_params
)));
180 // WebAudio is the only real user of unified audio and it always asks
182 // TODO(henrika): extend support to other input channel layouts as well.
183 const int kInputChannels
= 2;
185 params_
.Reset(out_params
.format(),
186 out_params
.channel_layout(),
187 out_params
.channels(),
189 out_params
.sample_rate(),
190 out_params
.bits_per_sample(),
191 out_params
.frames_per_buffer());
194 ~AudioUnifiedStreamWrapper() {}
196 // Creates an AudioOutputStream object using default parameters.
197 WASAPIUnifiedStream
* Create() {
198 return static_cast<WASAPIUnifiedStream
*>(CreateOutputStream());
201 // Creates an AudioOutputStream object using default parameters but a
202 // specified input device.
203 WASAPIUnifiedStream
* Create(const std::string device_id
) {
204 return static_cast<WASAPIUnifiedStream
*>(CreateOutputStream(device_id
));
207 AudioParameters::Format
format() const { return params_
.format(); }
208 int channels() const { return params_
.channels(); }
209 int bits_per_sample() const { return params_
.bits_per_sample(); }
210 int sample_rate() const { return params_
.sample_rate(); }
211 int frames_per_buffer() const { return params_
.frames_per_buffer(); }
212 int bytes_per_buffer() const { return params_
.GetBytesPerBuffer(); }
213 int input_channels() const { return params_
.input_channels(); }
216 AudioOutputStream
* CreateOutputStream() {
217 // Get the unique device ID of the default capture device instead of using
218 // AudioManagerBase::kDefaultDeviceId since it provides slightly better
219 // test coverage and will utilize the same code path as if a non default
220 // input device was used.
221 ScopedComPtr
<IMMDevice
> audio_device
=
222 CoreAudioUtil::CreateDefaultDevice(eCapture
, eConsole
);
223 AudioDeviceName name
;
224 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device
, &name
)));
225 const std::string
& input_device_id
= name
.unique_id
;
226 EXPECT_TRUE(CoreAudioUtil::DeviceIsDefault(eCapture
, eConsole
,
229 // Create the unified audio I/O stream using the default input device.
230 AudioOutputStream
* aos
= audio_man_
->MakeAudioOutputStream(params_
,
231 "", input_device_id
);
236 AudioOutputStream
* CreateOutputStream(const std::string
& input_device_id
) {
237 // Create the unified audio I/O stream using the specified input device.
238 AudioOutputStream
* aos
= audio_man_
->MakeAudioOutputStream(params_
,
239 "", input_device_id
);
244 ScopedCOMInitializer com_init_
;
245 AudioManager
* audio_man_
;
246 AudioParameters params_
;
249 // Convenience method which creates a default WASAPIUnifiedStream object.
250 static WASAPIUnifiedStream
* CreateDefaultUnifiedStream(
251 AudioManager
* audio_manager
) {
252 AudioUnifiedStreamWrapper
aosw(audio_manager
);
253 return aosw
.Create();
256 // Convenience method which creates a default WASAPIUnifiedStream object but
257 // with a specified audio input device.
258 static WASAPIUnifiedStream
* CreateDefaultUnifiedStream(
259 AudioManager
* audio_manager
, const std::string
& device_id
) {
260 AudioUnifiedStreamWrapper
aosw(audio_manager
);
261 return aosw
.Create(device_id
);
264 // Test Open(), Close() calling sequence.
265 TEST(WASAPIUnifiedStreamTest
, OpenAndClose
) {
266 scoped_ptr
<AudioManager
> audio_manager(AudioManager::Create());
267 if (!CanRunUnifiedAudioTests(audio_manager
.get()))
270 WASAPIUnifiedStream
* wus
= CreateDefaultUnifiedStream(audio_manager
.get());
271 EXPECT_TRUE(wus
->Open());
275 // Test Open(), Close() calling sequence for all available capture devices.
276 TEST(WASAPIUnifiedStreamTest
, OpenAndCloseForAllInputDevices
) {
277 scoped_ptr
<AudioManager
> audio_manager(AudioManager::Create());
278 if (!CanRunUnifiedAudioTests(audio_manager
.get()))
281 AudioDeviceNames device_names
;
282 audio_manager
->GetAudioInputDeviceNames(&device_names
);
283 for (AudioDeviceNames::iterator i
= device_names
.begin();
284 i
!= device_names
.end(); ++i
) {
285 WASAPIUnifiedStream
* wus
= CreateDefaultUnifiedStream(
286 audio_manager
.get(), i
->unique_id
);
287 EXPECT_TRUE(wus
->Open());
292 // Test Open(), Start(), Close() calling sequence.
293 TEST(WASAPIUnifiedStreamTest
, OpenStartAndClose
) {
294 scoped_ptr
<AudioManager
> audio_manager(AudioManager::Create());
295 if (!CanRunUnifiedAudioTests(audio_manager
.get()))
298 MockUnifiedSourceCallback source
;
299 AudioUnifiedStreamWrapper
ausw(audio_manager
.get());
300 WASAPIUnifiedStream
* wus
= ausw
.Create();
302 EXPECT_TRUE(wus
->Open());
303 EXPECT_CALL(source
, OnError(wus
))
305 EXPECT_CALL(source
, OnMoreIOData(NotNull(), NotNull(), _
))
306 .Times(Between(0, 1))
307 .WillOnce(Return(ausw
.frames_per_buffer()));
312 // Verify that IO callbacks starts as they should.
313 TEST(WASAPIUnifiedStreamTest
, StartLoopbackAudio
) {
314 scoped_ptr
<AudioManager
> audio_manager(AudioManager::Create());
315 if (!CanRunUnifiedAudioTests(audio_manager
.get()))
318 base::MessageLoopForUI loop
;
319 MockUnifiedSourceCallback source
;
320 AudioUnifiedStreamWrapper
ausw(audio_manager
.get());
321 WASAPIUnifiedStream
* wus
= ausw
.Create();
323 // Set up expected minimum delay estimation where we use a minium delay
324 // which is equal to the sum of render and capture sizes. We can never
325 // reach a delay lower than this value.
326 AudioBuffersState
min_total_audio_delay(0, 2 * ausw
.bytes_per_buffer());
328 EXPECT_TRUE(wus
->Open());
329 EXPECT_CALL(source
, OnError(wus
))
331 EXPECT_CALL(source
, OnMoreIOData(
332 NotNull(), NotNull(), DelayGreaterThan(min_total_audio_delay
)))
334 .WillOnce(Return(ausw
.frames_per_buffer()))
336 QuitLoop(loop
.message_loop_proxy()),
337 Return(ausw
.frames_per_buffer())));
339 loop
.PostDelayedTask(FROM_HERE
, base::MessageLoop::QuitClosure(),
340 TestTimeouts::action_timeout());
346 // Perform a real-time test in loopback where the recorded audio is echoed
347 // back to the speaker. This test allows the user to verify that the audio
348 // sounds OK. A text file with name |kDeltaTimeMsFileName| is also generated.
349 TEST(WASAPIUnifiedStreamTest
, DISABLED_RealTimePlayThrough
) {
350 scoped_ptr
<AudioManager
> audio_manager(AudioManager::Create());
351 if (!CanRunUnifiedAudioTests(audio_manager
.get()))
354 base::MessageLoopForUI loop
;
355 UnifiedSourceCallback source
;
356 WASAPIUnifiedStream
* wus
= CreateDefaultUnifiedStream(audio_manager
.get());
358 EXPECT_TRUE(wus
->Open());
360 loop
.PostDelayedTask(FROM_HERE
, base::MessageLoop::QuitClosure(),
361 base::TimeDelta::FromMilliseconds(10000));