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/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" 21 22 using ::testing::_; 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; 29 30 namespace media { 31 32 static const size_t kMaxDeltaSamples = 1000; 33 static const char kDeltaTimeMsFileName[] = "unified_delta_times_ms.txt"; 34 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); 39 } 40 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()); 45 } 46 47 class MockUnifiedSourceCallback 48 : public AudioOutputStream::AudioSourceCallback { 49 public: 50 MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus, 51 AudioBuffersState buffers_state)); 52 MOCK_METHOD3(OnMoreIOData, int(AudioBus* source, 53 AudioBus* dest, 54 AudioBuffersState buffers_state)); 55 MOCK_METHOD1(OnError, void(AudioOutputStream* stream)); 56 }; 57 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 { 63 public: 64 explicit UnifiedSourceCallback() 65 : previous_call_time_(base::TimeTicks::Now()), 66 text_file_(NULL), 67 elements_to_write_(0) { 68 delta_times_.reset(new int[kMaxDeltaSamples]); 69 } 70 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); 75 76 EXPECT_TRUE(!text_file_); 77 text_file_ = base::OpenFile(file_name, "wt"); 78 DLOG_IF(ERROR, !text_file_) << "Failed to open log file."; 79 VLOG(0) << ">> Output file " << file_name.value() << " has been created."; 80 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]); 85 ++elements_written; 86 } 87 base::CloseFile(text_file_); 88 } 89 90 virtual int OnMoreData(AudioBus* dest, 91 AudioBuffersState buffers_state) { 92 NOTREACHED(); 93 return 0; 94 }; 95 96 virtual int OnMoreIOData(AudioBus* source, 97 AudioBus* dest, 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_; 106 } 107 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); 112 } else { 113 // A channel mixer is required for converting audio between two different 114 // channel layouts. 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."; 125 } 126 if (channel_mixer_) 127 channel_mixer_->Transform(source, dest); 128 } 129 return source->frames(); 130 }; 131 132 virtual void OnError(AudioOutputStream* stream) { 133 NOTREACHED(); 134 } 135 136 private: 137 base::TimeTicks previous_call_time_; 138 scoped_ptr<int[]> delta_times_; 139 FILE* text_file_; 140 size_t elements_to_write_; 141 scoped_ptr<ChannelMixer> channel_mixer_; 142 }; 143 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."; 149 return false; 150 } 151 152 if (!audio_man->HasAudioOutputDevices()) { 153 LOG(WARNING) << "No output devices detected."; 154 return false; 155 } 156 157 if (!audio_man->HasAudioInputDevices()) { 158 LOG(WARNING) << "No input devices detected."; 159 return false; 160 } 161 162 return true; 163 } 164 165 // Convenience class which simplifies creation of a unified AudioOutputStream 166 // object. 167 class AudioUnifiedStreamWrapper { 168 public: 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 175 // output streams. 176 AudioParameters out_params; 177 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters( 178 eRender, eConsole, &out_params))); 179 180 // WebAudio is the only real user of unified audio and it always asks 181 // for stereo. 182 // TODO(henrika): extend support to other input channel layouts as well. 183 const int kInputChannels = 2; 184 185 params_.Reset(out_params.format(), 186 out_params.channel_layout(), 187 out_params.channels(), 188 kInputChannels, 189 out_params.sample_rate(), 190 out_params.bits_per_sample(), 191 out_params.frames_per_buffer()); 192 } 193 194 ~AudioUnifiedStreamWrapper() {} 195 196 // Creates an AudioOutputStream object using default parameters. 197 WASAPIUnifiedStream* Create() { 198 return static_cast<WASAPIUnifiedStream*>(CreateOutputStream()); 199 } 200 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)); 205 } 206 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(); } 214 215 private: 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, 227 input_device_id)); 228 229 // Create the unified audio I/O stream using the default input device. 230 AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(params_, 231 "", input_device_id); 232 EXPECT_TRUE(aos); 233 return aos; 234 } 235 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); 240 EXPECT_TRUE(aos); 241 return aos; 242 } 243 244 ScopedCOMInitializer com_init_; 245 AudioManager* audio_man_; 246 AudioParameters params_; 247 }; 248 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(); 254 } 255 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); 262 } 263 264 // Test Open(), Close() calling sequence. 265 TEST(WASAPIUnifiedStreamTest, OpenAndClose) { 266 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); 267 if (!CanRunUnifiedAudioTests(audio_manager.get())) 268 return; 269 270 WASAPIUnifiedStream* wus = CreateDefaultUnifiedStream(audio_manager.get()); 271 EXPECT_TRUE(wus->Open()); 272 wus->Close(); 273 } 274 275 // Test Open(), Close() calling sequence for all available capture devices. 276 TEST(WASAPIUnifiedStreamTest, OpenAndCloseForAllInputDevices) { 277 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); 278 if (!CanRunUnifiedAudioTests(audio_manager.get())) 279 return; 280 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()); 288 wus->Close(); 289 } 290 } 291 292 // Test Open(), Start(), Close() calling sequence. 293 TEST(WASAPIUnifiedStreamTest, OpenStartAndClose) { 294 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); 295 if (!CanRunUnifiedAudioTests(audio_manager.get())) 296 return; 297 298 MockUnifiedSourceCallback source; 299 AudioUnifiedStreamWrapper ausw(audio_manager.get()); 300 WASAPIUnifiedStream* wus = ausw.Create(); 301 302 EXPECT_TRUE(wus->Open()); 303 EXPECT_CALL(source, OnError(wus)) 304 .Times(0); 305 EXPECT_CALL(source, OnMoreIOData(NotNull(), NotNull(), _)) 306 .Times(Between(0, 1)) 307 .WillOnce(Return(ausw.frames_per_buffer())); 308 wus->Start(&source); 309 wus->Close(); 310 } 311 312 // Verify that IO callbacks starts as they should. 313 TEST(WASAPIUnifiedStreamTest, StartLoopbackAudio) { 314 scoped_ptr<AudioManager> audio_manager(AudioManager::CreateForTesting()); 315 if (!CanRunUnifiedAudioTests(audio_manager.get())) 316 return; 317 318 base::MessageLoopForUI loop; 319 MockUnifiedSourceCallback source; 320 AudioUnifiedStreamWrapper ausw(audio_manager.get()); 321 WASAPIUnifiedStream* wus = ausw.Create(); 322 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()); 327 328 EXPECT_TRUE(wus->Open()); 329 EXPECT_CALL(source, OnError(wus)) 330 .Times(0); 331 EXPECT_CALL(source, OnMoreIOData( 332 NotNull(), NotNull(), DelayGreaterThan(min_total_audio_delay))) 333 .Times(AtLeast(2)) 334 .WillOnce(Return(ausw.frames_per_buffer())) 335 .WillOnce(DoAll( 336 QuitLoop(loop.message_loop_proxy()), 337 Return(ausw.frames_per_buffer()))); 338 wus->Start(&source); 339 loop.PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(), 340 TestTimeouts::action_timeout()); 341 loop.Run(); 342 wus->Stop(); 343 wus->Close(); 344 } 345 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::CreateForTesting()); 351 if (!CanRunUnifiedAudioTests(audio_manager.get())) 352 return; 353 354 base::MessageLoopForUI loop; 355 UnifiedSourceCallback source; 356 WASAPIUnifiedStream* wus = CreateDefaultUnifiedStream(audio_manager.get()); 357 358 EXPECT_TRUE(wus->Open()); 359 wus->Start(&source); 360 loop.PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(), 361 base::TimeDelta::FromMilliseconds(10000)); 362 loop.Run(); 363 wus->Close(); 364 } 365 366 } // namespace media 367