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/audio_util.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 class MockUnifiedSourceCallback 49 : public AudioOutputStream::AudioSourceCallback { 50 public: 51 MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus, 52 AudioBuffersState buffers_state)); 53 MOCK_METHOD3(OnMoreIOData, int(AudioBus* source, 54 AudioBus* dest, 55 AudioBuffersState buffers_state)); 56 MOCK_METHOD1(OnError, void(AudioOutputStream* stream)); 57 }; 58 59 // AudioOutputStream::AudioSourceCallback implementation which enables audio 60 // play-through. It also creates a text file that contains times between two 61 // successive callbacks. Units are in milliseconds. This file can be used for 62 // off-line analysis of the callback sequence. 63 class UnifiedSourceCallback : public AudioOutputStream::AudioSourceCallback { 64 public: 65 explicit UnifiedSourceCallback() 66 : previous_call_time_(base::TimeTicks::Now()), 67 text_file_(NULL), 68 elements_to_write_(0) { 69 delta_times_.reset(new int[kMaxDeltaSamples]); 70 } 71 72 virtual ~UnifiedSourceCallback() { 73 base::FilePath file_name; 74 EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_name)); 75 file_name = file_name.AppendASCII(kDeltaTimeMsFileName); 76 77 EXPECT_TRUE(!text_file_); 78 text_file_ = file_util::OpenFile(file_name, "wt"); 79 DLOG_IF(ERROR, !text_file_) << "Failed to open log file."; 80 LOG(INFO) << ">> Output file " << file_name.value() << " has been created."; 81 82 // Write the array which contains delta times to a text file. 83 size_t elements_written = 0; 84 while (elements_written < elements_to_write_) { 85 fprintf(text_file_, "%d\n", delta_times_[elements_written]); 86 ++elements_written; 87 } 88 file_util::CloseFile(text_file_); 89 } 90 91 virtual int OnMoreData(AudioBus* dest, 92 AudioBuffersState buffers_state) { 93 NOTREACHED(); 94 return 0; 95 }; 96 97 virtual int OnMoreIOData(AudioBus* source, 98 AudioBus* dest, 99 AudioBuffersState buffers_state) { 100 // Store time between this callback and the previous callback. 101 const base::TimeTicks now_time = base::TimeTicks::Now(); 102 const int diff = (now_time - previous_call_time_).InMilliseconds(); 103 previous_call_time_ = now_time; 104 if (elements_to_write_ < kMaxDeltaSamples) { 105 delta_times_[elements_to_write_] = diff; 106 ++elements_to_write_; 107 } 108 109 // Play out the recorded audio samples in loop back. Perform channel mixing 110 // if required using a channel mixer which is created only if needed. 111 if (source->channels() == dest->channels()) { 112 source->CopyTo(dest); 113 } else { 114 // A channel mixer is required for converting audio between two different 115 // channel layouts. 116 if (!channel_mixer_) { 117 // Guessing the channel layout will work OK for this unit test. 118 // Main thing is that the number of channels is correct. 119 ChannelLayout input_layout = GuessChannelLayout(source->channels()); 120 ChannelLayout output_layout = GuessChannelLayout(dest->channels()); 121 channel_mixer_.reset(new ChannelMixer(input_layout, output_layout)); 122 DVLOG(1) << "Remixing channel layout from " << input_layout 123 << " to " << output_layout << "; from " 124 << source->channels() << " channels to " 125 << dest->channels() << " channels."; 126 } 127 if (channel_mixer_) 128 channel_mixer_->Transform(source, dest); 129 } 130 return source->frames(); 131 }; 132 133 virtual void OnError(AudioOutputStream* stream) { 134 NOTREACHED(); 135 } 136 137 private: 138 base::TimeTicks previous_call_time_; 139 scoped_ptr<int[]> delta_times_; 140 FILE* text_file_; 141 size_t elements_to_write_; 142 scoped_ptr<ChannelMixer> channel_mixer_; 143 }; 144 145 // Convenience method which ensures that we fulfill all required conditions 146 // to run unified audio tests on Windows. 147 static bool CanRunUnifiedAudioTests(AudioManager* audio_man) { 148 if (!CoreAudioUtil::IsSupported()) { 149 LOG(WARNING) << "This tests requires Windows Vista or higher."; 150 return false; 151 } 152 153 if (!audio_man->HasAudioOutputDevices()) { 154 LOG(WARNING) << "No output devices detected."; 155 return false; 156 } 157 158 if (!audio_man->HasAudioInputDevices()) { 159 LOG(WARNING) << "No input devices detected."; 160 return false; 161 } 162 163 return true; 164 } 165 166 // Convenience class which simplifies creation of a unified AudioOutputStream 167 // object. 168 class AudioUnifiedStreamWrapper { 169 public: 170 explicit AudioUnifiedStreamWrapper(AudioManager* audio_manager) 171 : com_init_(ScopedCOMInitializer::kMTA), 172 audio_man_(audio_manager) { 173 // We open up both both sides (input and output) using the preferred 174 // set of audio parameters. These parameters corresponds to the mix format 175 // that the audio engine uses internally for processing of shared-mode 176 // output streams. 177 AudioParameters out_params; 178 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters( 179 eRender, eConsole, &out_params))); 180 181 // WebAudio is the only real user of unified audio and it always asks 182 // for stereo. 183 // TODO(henrika): extend support to other input channel layouts as well. 184 const int kInputChannels = 2; 185 186 params_.Reset(out_params.format(), 187 out_params.channel_layout(), 188 out_params.channels(), 189 kInputChannels, 190 out_params.sample_rate(), 191 out_params.bits_per_sample(), 192 out_params.frames_per_buffer()); 193 } 194 195 ~AudioUnifiedStreamWrapper() {} 196 197 // Creates an AudioOutputStream object using default parameters. 198 WASAPIUnifiedStream* Create() { 199 return static_cast<WASAPIUnifiedStream*> (CreateOutputStream()); 200 } 201 202 // Creates an AudioOutputStream object using default parameters but a 203 // specified input device. 204 WASAPIUnifiedStream* Create(const std::string device_id) { 205 return static_cast<WASAPIUnifiedStream*> (CreateOutputStream(device_id)); 206 } 207 208 AudioParameters::Format format() const { return params_.format(); } 209 int channels() const { return params_.channels(); } 210 int bits_per_sample() const { return params_.bits_per_sample(); } 211 int sample_rate() const { return params_.sample_rate(); } 212 int frames_per_buffer() const { return params_.frames_per_buffer(); } 213 int bytes_per_buffer() const { return params_.GetBytesPerBuffer(); } 214 int input_channels() const { return params_.input_channels(); } 215 216 private: 217 AudioOutputStream* CreateOutputStream() { 218 // Get the unique device ID of the default capture device instead of using 219 // AudioManagerBase::kDefaultDeviceId since it provides slightly better 220 // test coverage and will utilize the same code path as if a non default 221 // input device was used. 222 ScopedComPtr<IMMDevice> audio_device = 223 CoreAudioUtil::CreateDefaultDevice(eCapture, eConsole); 224 AudioDeviceName name; 225 EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device, &name))); 226 const std::string& device_id = name.unique_id; 227 EXPECT_TRUE(CoreAudioUtil::DeviceIsDefault(eCapture, eConsole, device_id)); 228 229 // Create the unified audio I/O stream using the default input device. 230 AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(params_, 231 device_id); 232 EXPECT_TRUE(aos); 233 return aos; 234 } 235 236 AudioOutputStream* CreateOutputStream(const std::string& device_id) { 237 // Create the unified audio I/O stream using the specified input device. 238 AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(params_, 239 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::Create()); 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::Create()); 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::Create()); 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::Create()); 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::Create()); 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