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/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