OLD | NEW |
| (Empty) |
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/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/mock_audio_source_callback.h" | |
16 #include "media/audio/win/audio_unified_win.h" | |
17 #include "media/audio/win/core_audio_util_win.h" | |
18 #include "media/base/channel_mixer.h" | |
19 #include "media/base/media_switches.h" | |
20 #include "testing/gmock/include/gmock/gmock.h" | |
21 #include "testing/gtest/include/gtest/gtest.h" | |
22 | |
23 using ::testing::_; | |
24 using ::testing::AtLeast; | |
25 using ::testing::Between; | |
26 using ::testing::DoAll; | |
27 using ::testing::NotNull; | |
28 using ::testing::Return; | |
29 using base::win::ScopedCOMInitializer; | |
30 | |
31 namespace media { | |
32 | |
33 static const size_t kMaxDeltaSamples = 1000; | |
34 static const char kDeltaTimeMsFileName[] = "unified_delta_times_ms.txt"; | |
35 | |
36 // Verify that the delay estimate in the OnMoreIOData() callback is larger | |
37 // than an expected minumum value. | |
38 MATCHER_P(DelayGreaterThan, value, "") { | |
39 return (arg.hardware_delay_bytes > value.hardware_delay_bytes); | |
40 } | |
41 | |
42 // Used to terminate a loop from a different thread than the loop belongs to. | |
43 // |loop| should be a MessageLoopProxy. | |
44 ACTION_P(QuitLoop, loop) { | |
45 loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); | |
46 } | |
47 | |
48 // AudioOutputStream::AudioSourceCallback implementation which enables audio | |
49 // play-through. It also creates a text file that contains times between two | |
50 // successive callbacks. Units are in milliseconds. This file can be used for | |
51 // off-line analysis of the callback sequence. | |
52 class UnifiedSourceCallback : public AudioOutputStream::AudioSourceCallback { | |
53 public: | |
54 explicit UnifiedSourceCallback() | |
55 : previous_call_time_(base::TimeTicks::Now()), | |
56 text_file_(NULL), | |
57 elements_to_write_(0) { | |
58 delta_times_.reset(new int[kMaxDeltaSamples]); | |
59 } | |
60 | |
61 virtual ~UnifiedSourceCallback() { | |
62 base::FilePath file_name; | |
63 EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_name)); | |
64 file_name = file_name.AppendASCII(kDeltaTimeMsFileName); | |
65 | |
66 EXPECT_TRUE(!text_file_); | |
67 text_file_ = base::OpenFile(file_name, "wt"); | |
68 DLOG_IF(ERROR, !text_file_) << "Failed to open log file."; | |
69 VLOG(0) << ">> Output file " << file_name.value() << " has been created."; | |
70 | |
71 // Write the array which contains delta times to a text file. | |
72 size_t elements_written = 0; | |
73 while (elements_written < elements_to_write_) { | |
74 fprintf(text_file_, "%d\n", delta_times_[elements_written]); | |
75 ++elements_written; | |
76 } | |
77 base::CloseFile(text_file_); | |
78 } | |
79 | |
80 virtual int OnMoreData(AudioBus* dest, | |
81 AudioBuffersState buffers_state) { | |
82 NOTREACHED(); | |
83 return 0; | |
84 }; | |
85 | |
86 virtual int OnMoreIOData(AudioBus* source, | |
87 AudioBus* dest, | |
88 AudioBuffersState buffers_state) { | |
89 // Store time between this callback and the previous callback. | |
90 const base::TimeTicks now_time = base::TimeTicks::Now(); | |
91 const int diff = (now_time - previous_call_time_).InMilliseconds(); | |
92 previous_call_time_ = now_time; | |
93 if (elements_to_write_ < kMaxDeltaSamples) { | |
94 delta_times_[elements_to_write_] = diff; | |
95 ++elements_to_write_; | |
96 } | |
97 | |
98 // Play out the recorded audio samples in loop back. Perform channel mixing | |
99 // if required using a channel mixer which is created only if needed. | |
100 if (source->channels() == dest->channels()) { | |
101 source->CopyTo(dest); | |
102 } else { | |
103 // A channel mixer is required for converting audio between two different | |
104 // channel layouts. | |
105 if (!channel_mixer_) { | |
106 // Guessing the channel layout will work OK for this unit test. | |
107 // Main thing is that the number of channels is correct. | |
108 ChannelLayout input_layout = GuessChannelLayout(source->channels()); | |
109 ChannelLayout output_layout = GuessChannelLayout(dest->channels()); | |
110 channel_mixer_.reset(new ChannelMixer(input_layout, output_layout)); | |
111 DVLOG(1) << "Remixing channel layout from " << input_layout | |
112 << " to " << output_layout << "; from " | |
113 << source->channels() << " channels to " | |
114 << dest->channels() << " channels."; | |
115 } | |
116 if (channel_mixer_) | |
117 channel_mixer_->Transform(source, dest); | |
118 } | |
119 return source->frames(); | |
120 }; | |
121 | |
122 virtual void OnError(AudioOutputStream* stream) { | |
123 NOTREACHED(); | |
124 } | |
125 | |
126 private: | |
127 base::TimeTicks previous_call_time_; | |
128 scoped_ptr<int[]> delta_times_; | |
129 FILE* text_file_; | |
130 size_t elements_to_write_; | |
131 scoped_ptr<ChannelMixer> channel_mixer_; | |
132 }; | |
133 | |
134 // Convenience method which ensures that we fulfill all required conditions | |
135 // to run unified audio tests on Windows. | |
136 static bool CanRunUnifiedAudioTests(AudioManager* audio_man) { | |
137 if (!CoreAudioUtil::IsSupported()) { | |
138 LOG(WARNING) << "This tests requires Windows Vista or higher."; | |
139 return false; | |
140 } | |
141 | |
142 if (!audio_man->HasAudioOutputDevices()) { | |
143 LOG(WARNING) << "No output devices detected."; | |
144 return false; | |
145 } | |
146 | |
147 if (!audio_man->HasAudioInputDevices()) { | |
148 LOG(WARNING) << "No input devices detected."; | |
149 return false; | |
150 } | |
151 | |
152 return true; | |
153 } | |
154 | |
155 // Convenience class which simplifies creation of a unified AudioOutputStream | |
156 // object. | |
157 class AudioUnifiedStreamWrapper { | |
158 public: | |
159 explicit AudioUnifiedStreamWrapper(AudioManager* audio_manager) | |
160 : com_init_(ScopedCOMInitializer::kMTA), | |
161 audio_man_(audio_manager) { | |
162 // We open up both both sides (input and output) using the preferred | |
163 // set of audio parameters. These parameters corresponds to the mix format | |
164 // that the audio engine uses internally for processing of shared-mode | |
165 // output streams. | |
166 AudioParameters out_params; | |
167 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters( | |
168 eRender, eConsole, &out_params))); | |
169 | |
170 // WebAudio is the only real user of unified audio and it always asks | |
171 // for stereo. | |
172 // TODO(henrika): extend support to other input channel layouts as well. | |
173 const int kInputChannels = 2; | |
174 | |
175 params_.Reset(out_params.format(), | |
176 out_params.channel_layout(), | |
177 out_params.channels(), | |
178 kInputChannels, | |
179 out_params.sample_rate(), | |
180 out_params.bits_per_sample(), | |
181 out_params.frames_per_buffer()); | |
182 } | |
183 | |
184 ~AudioUnifiedStreamWrapper() {} | |
185 | |
186 // Creates an AudioOutputStream object using default parameters. | |
187 WASAPIUnifiedStream* Create() { | |
188 return static_cast<WASAPIUnifiedStream*>(CreateOutputStream()); | |
189 } | |
190 | |
191 // Creates an AudioOutputStream object using default parameters but a | |
192 // specified input device. | |
193 WASAPIUnifiedStream* Create(const std::string device_id) { | |
194 return static_cast<WASAPIUnifiedStream*>(CreateOutputStream(device_id)); | |
195 } | |
196 | |
197 AudioParameters::Format format() const { return params_.format(); } | |
198 int channels() const { return params_.channels(); } | |
199 int bits_per_sample() const { return params_.bits_per_sample(); } | |
200 int sample_rate() const { return params_.sample_rate(); } | |
201 int frames_per_buffer() const { return params_.frames_per_buffer(); } | |
202 int bytes_per_buffer() const { return params_.GetBytesPerBuffer(); } | |
203 int input_channels() const { return params_.input_channels(); } | |
204 | |
205 private: | |
206 AudioOutputStream* CreateOutputStream() { | |
207 // Get the unique device ID of the default capture device instead of using | |
208 // AudioManagerBase::kDefaultDeviceId since it provides slightly better | |
209 // test coverage and will utilize the same code path as if a non default | |
210 // input device was used. | |
211 ScopedComPtr<IMMDevice> audio_device = | |
212 CoreAudioUtil::CreateDefaultDevice(eCapture, eConsole); | |
213 AudioDeviceName name; | |
214 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device, &name))); | |
215 const std::string& input_device_id = name.unique_id; | |
216 EXPECT_TRUE(CoreAudioUtil::DeviceIsDefault(eCapture, eConsole, | |
217 input_device_id)); | |
218 | |
219 // Create the unified audio I/O stream using the default input device. | |
220 AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(params_, | |
221 "", input_device_id); | |
222 EXPECT_TRUE(aos); | |
223 return aos; | |
224 } | |
225 | |
226 AudioOutputStream* CreateOutputStream(const std::string& input_device_id) { | |
227 // Create the unified audio I/O stream using the specified input device. | |
228 AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(params_, | |
229 "", input_device_id); | |
230 EXPECT_TRUE(aos); | |
231 return aos; | |
232 } | |
233 | |
234 ScopedCOMInitializer com_init_; | |
235 AudioManager* audio_man_; | |
236 AudioParameters params_; | |
237 }; | |
238 | |
239 // Convenience method which creates a default WASAPIUnifiedStream object. | |
240 static WASAPIUnifiedStream* CreateDefaultUnifiedStream( | |
241 AudioManager* audio_manager) { | |
242 AudioUnifiedStreamWrapper aosw(audio_manager); | |
243 return aosw.Create(); | |
244 } | |
245 | |
246 // Convenience method which creates a default WASAPIUnifiedStream object but | |
247 // with a specified audio input device. | |
248 static WASAPIUnifiedStream* CreateDefaultUnifiedStream( | |
249 AudioManager* audio_manager, const std::string& device_id) { | |
250 AudioUnifiedStreamWrapper aosw(audio_manager); | |
251 return aosw.Create(device_id); | |
252 } | |
253 | |
254 // Test Open(), Close() calling sequence. | |
255 TEST(WASAPIUnifiedStreamTest, OpenAndClose) { | |
256 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); | |
257 if (!CanRunUnifiedAudioTests(audio_manager.get())) | |
258 return; | |
259 | |
260 WASAPIUnifiedStream* wus = CreateDefaultUnifiedStream(audio_manager.get()); | |
261 EXPECT_TRUE(wus->Open()); | |
262 wus->Close(); | |
263 } | |
264 | |
265 // Test Open(), Close() calling sequence for all available capture devices. | |
266 TEST(WASAPIUnifiedStreamTest, OpenAndCloseForAllInputDevices) { | |
267 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); | |
268 if (!CanRunUnifiedAudioTests(audio_manager.get())) | |
269 return; | |
270 | |
271 AudioDeviceNames device_names; | |
272 audio_manager->GetAudioInputDeviceNames(&device_names); | |
273 for (AudioDeviceNames::iterator i = device_names.begin(); | |
274 i != device_names.end(); ++i) { | |
275 WASAPIUnifiedStream* wus = CreateDefaultUnifiedStream( | |
276 audio_manager.get(), i->unique_id); | |
277 EXPECT_TRUE(wus->Open()); | |
278 wus->Close(); | |
279 } | |
280 } | |
281 | |
282 // Test Open(), Start(), Close() calling sequence. | |
283 TEST(WASAPIUnifiedStreamTest, OpenStartAndClose) { | |
284 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); | |
285 if (!CanRunUnifiedAudioTests(audio_manager.get())) | |
286 return; | |
287 | |
288 MockAudioSourceCallback source; | |
289 AudioUnifiedStreamWrapper ausw(audio_manager.get()); | |
290 WASAPIUnifiedStream* wus = ausw.Create(); | |
291 | |
292 EXPECT_TRUE(wus->Open()); | |
293 EXPECT_CALL(source, OnError(wus)) | |
294 .Times(0); | |
295 EXPECT_CALL(source, OnMoreIOData(NotNull(), NotNull(), _)) | |
296 .Times(Between(0, 1)) | |
297 .WillOnce(Return(ausw.frames_per_buffer())); | |
298 wus->Start(&source); | |
299 wus->Close(); | |
300 } | |
301 | |
302 // Verify that IO callbacks starts as they should. | |
303 TEST(WASAPIUnifiedStreamTest, StartLoopbackAudio) { | |
304 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); | |
305 if (!CanRunUnifiedAudioTests(audio_manager.get())) | |
306 return; | |
307 | |
308 base::MessageLoopForUI loop; | |
309 MockAudioSourceCallback source; | |
310 AudioUnifiedStreamWrapper ausw(audio_manager.get()); | |
311 WASAPIUnifiedStream* wus = ausw.Create(); | |
312 | |
313 // Set up expected minimum delay estimation where we use a minium delay | |
314 // which is equal to the sum of render and capture sizes. We can never | |
315 // reach a delay lower than this value. | |
316 AudioBuffersState min_total_audio_delay(0, 2 * ausw.bytes_per_buffer()); | |
317 | |
318 EXPECT_TRUE(wus->Open()); | |
319 EXPECT_CALL(source, OnError(wus)) | |
320 .Times(0); | |
321 EXPECT_CALL(source, OnMoreIOData( | |
322 NotNull(), NotNull(), DelayGreaterThan(min_total_audio_delay))) | |
323 .Times(AtLeast(2)) | |
324 .WillOnce(Return(ausw.frames_per_buffer())) | |
325 .WillOnce(DoAll( | |
326 QuitLoop(loop.message_loop_proxy()), | |
327 Return(ausw.frames_per_buffer()))); | |
328 wus->Start(&source); | |
329 loop.PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(), | |
330 TestTimeouts::action_timeout()); | |
331 loop.Run(); | |
332 wus->Stop(); | |
333 wus->Close(); | |
334 } | |
335 | |
336 // Perform a real-time test in loopback where the recorded audio is echoed | |
337 // back to the speaker. This test allows the user to verify that the audio | |
338 // sounds OK. A text file with name |kDeltaTimeMsFileName| is also generated. | |
339 TEST(WASAPIUnifiedStreamTest, DISABLED_RealTimePlayThrough) { | |
340 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); | |
341 if (!CanRunUnifiedAudioTests(audio_manager.get())) | |
342 return; | |
343 | |
344 base::MessageLoopForUI loop; | |
345 UnifiedSourceCallback source; | |
346 WASAPIUnifiedStream* wus = CreateDefaultUnifiedStream(audio_manager.get()); | |
347 | |
348 EXPECT_TRUE(wus->Open()); | |
349 wus->Start(&source); | |
350 loop.PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(), | |
351 base::TimeDelta::FromMilliseconds(10000)); | |
352 loop.Run(); | |
353 wus->Close(); | |
354 } | |
355 | |
356 } // namespace media | |
OLD | NEW |