Home | History | Annotate | Download | only in win
      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