123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- // Copyright 2020 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "fuchsia_web/webengine/renderer/web_engine_audio_output_device.h"
- #include "base/logging.h"
- #include "base/memory/ref_counted.h"
- #include "base/test/task_environment.h"
- #include "base/threading/thread.h"
- #include "base/threading/thread_task_runner_handle.h"
- #include "base/time/time.h"
- #include "media/base/audio_renderer_sink.h"
- #include "media/base/audio_timestamp_helper.h"
- #include "media/fuchsia/audio/fake_audio_consumer.h"
- #include "testing/gtest/include/gtest/gtest.h"
- namespace {
- constexpr int kSampleRate = 44100;
- constexpr media::ChannelLayout kChannelLayout = media::CHANNEL_LAYOUT_STEREO;
- constexpr int kNumChannels = 2;
- constexpr uint64_t kTestSessionId = 42;
- constexpr base::TimeDelta kPeriod = base::Milliseconds(10);
- constexpr int kFramesPerPeriod = 441;
- } // namespace
- class TestRenderer : public media::AudioRendererSink::RenderCallback {
- public:
- TestRenderer() = default;
- ~TestRenderer() override = default;
- // AudioRendererSink::Renderer interface.
- int Render(base::TimeDelta delay,
- base::TimeTicks delay_timestamp,
- int prior_frames_skipped,
- media::AudioBus* dest) override {
- EXPECT_EQ(dest->channels(), kNumChannels);
- frames_skipped_ += prior_frames_skipped;
- frames_rendered_ += dest->frames();
- EXPECT_GT(delay, base::TimeDelta());
- auto presentation_time = delay_timestamp + delay;
- EXPECT_GT(presentation_time, last_presentation_time_);
- last_presentation_time_ = presentation_time;
- return dest->frames();
- }
- void OnRenderError() override { num_render_errors_++; }
- int frames_rendered() const { return frames_rendered_; }
- void reset_frames_rendered() { frames_rendered_ = 0; }
- int frames_skipped() const { return frames_skipped_; }
- int num_render_errors() const { return num_render_errors_; }
- base::TimeTicks last_presentation_time() const {
- return last_presentation_time_;
- }
- private:
- int frames_rendered_ = 0;
- int frames_skipped_ = 0;
- int num_render_errors_ = 0;
- base::TimeTicks last_presentation_time_;
- };
- class WebEngineAudioOutputDeviceTest : public testing::Test {
- public:
- WebEngineAudioOutputDeviceTest() {
- fidl::InterfaceHandle<fuchsia::media::AudioConsumer> audio_consumer;
- fake_audio_consumer_ = std::make_unique<media::FakeAudioConsumer>(
- kTestSessionId, audio_consumer.NewRequest());
- output_device_ = WebEngineAudioOutputDevice::Create(
- std::move(audio_consumer), base::ThreadTaskRunnerHandle::Get());
- }
- ~WebEngineAudioOutputDeviceTest() override {
- // Stop() must be called before destruction to release resources.
- output_device_->Stop();
- // WebEngineAudioOutputDevice::Stop() posts a task to run
- // StopOnAudioThread() on `task_runner_`. RunUntilIdle() ensures the request
- // to stop is fulfilled.
- task_environment_.RunUntilIdle();
- }
- protected:
- void Initialize() {
- output_device_->Initialize(
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
- kChannelLayout, kSampleRate, kFramesPerPeriod),
- &renderer_);
- task_environment_.RunUntilIdle();
- EXPECT_EQ(renderer_.frames_rendered(), 0);
- }
- void InitializeAndStart() {
- Initialize();
- // As soon as Start() is processed WebEngineAudioOutputDevice is expected to
- // start rendering some samples.
- output_device_->Start();
- task_environment_.RunUntilIdle();
- EXPECT_GT(renderer_.frames_rendered(), 0);
- }
- void CallPumpSamples() {
- output_device_->PumpSamples(base::TimeTicks::Now() +
- base::Milliseconds(200));
- }
- void ValidatePresentationTime() {
- // Verify that the current renderer lead time is in the
- // [min_lead_time, min_lead_time + 30ms] range. 30ms is chosen to allow
- // WebEngineAudioOutputDevice to pre-render slightely ahead of the target
- // time, while keeping latency reasonably low.
- auto lead_time =
- renderer_.last_presentation_time() - base::TimeTicks::Now();
- EXPECT_GT(lead_time, media::FakeAudioConsumer::kMinLeadTime);
- EXPECT_LT(lead_time,
- media::FakeAudioConsumer::kMinLeadTime + base::Milliseconds(30));
- }
- base::test::SingleThreadTaskEnvironment task_environment_{
- base::test::SingleThreadTaskEnvironment::MainThreadType::IO,
- base::test::TaskEnvironment::TimeSource::MOCK_TIME};
- std::unique_ptr<media::FakeAudioConsumer> fake_audio_consumer_;
- TestRenderer renderer_;
- scoped_refptr<WebEngineAudioOutputDevice> output_device_;
- };
- TEST_F(WebEngineAudioOutputDeviceTest, Start) {
- Initialize();
- // Verify that playback doesn't start before Start().
- task_environment_.FastForwardBy(base::Seconds(2));
- EXPECT_EQ(renderer_.frames_rendered(), 0);
- // Rendering should start after Start().
- output_device_->Start();
- task_environment_.RunUntilIdle();
- EXPECT_GT(renderer_.frames_rendered(), 0);
- ValidatePresentationTime();
- }
- TEST_F(WebEngineAudioOutputDeviceTest, StartAndPlay) {
- InitializeAndStart();
- renderer_.reset_frames_rendered();
- // Try advancing time and verify that WebEngineAudioOutputDevice keeps calling
- // Render().
- for (int i = 0; i < 3; ++i) {
- task_environment_.FastForwardBy(kPeriod);
- EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod);
- EXPECT_EQ(renderer_.frames_skipped(), 0);
- renderer_.reset_frames_rendered();
- }
- }
- TEST_F(WebEngineAudioOutputDeviceTest, Pause) {
- InitializeAndStart();
- renderer_.reset_frames_rendered();
- // Advancing time and verify that WebEngineAudioOutputDevice keeps calling
- // Render().
- task_environment_.FastForwardBy(kPeriod);
- EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod);
- EXPECT_EQ(renderer_.frames_skipped(), 0);
- renderer_.reset_frames_rendered();
- // Render() should not be called while paused.
- output_device_->Pause();
- task_environment_.FastForwardBy(base::Seconds(10));
- EXPECT_EQ(renderer_.frames_rendered(), 0);
- // Unpause the stream and verify that Render() is being called now.
- output_device_->Play();
- task_environment_.FastForwardBy(kPeriod);
- EXPECT_GT(renderer_.frames_rendered(), 0);
- EXPECT_EQ(renderer_.frames_skipped(), 0);
- }
- TEST_F(WebEngineAudioOutputDeviceTest, Underflow) {
- InitializeAndStart();
- renderer_.reset_frames_rendered();
- // Missing the timer once should not cause any issues. Timer tasks can't
- // always run at the exact scheduled time. WebEngineAudioOutputDevice should
- // be resilient to small delays.
- task_environment_.AdvanceClock(kPeriod * 2);
- task_environment_.RunUntilIdle();
- EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod * 2);
- EXPECT_EQ(renderer_.frames_skipped(), 0);
- renderer_.reset_frames_rendered();
- // Advance time by 100ms, causing some frames to be skipped.
- task_environment_.AdvanceClock(kPeriod * 10);
- task_environment_.RunUntilIdle();
- EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod * 3);
- EXPECT_EQ(renderer_.frames_skipped(), kFramesPerPeriod * 7);
- renderer_.reset_frames_rendered();
- ValidatePresentationTime();
- }
- TEST_F(WebEngineAudioOutputDeviceTest, Error) {
- InitializeAndStart();
- renderer_.reset_frames_rendered();
- fake_audio_consumer_.reset();
- task_environment_.RunUntilIdle();
- EXPECT_EQ(renderer_.num_render_errors(), 1);
- EXPECT_EQ(renderer_.frames_rendered(), 0);
- }
- TEST_F(WebEngineAudioOutputDeviceTest, Stop) {
- InitializeAndStart();
- renderer_.reset_frames_rendered();
- // Call Stop() and then PumpSamples() immediately after that. The callback
- // should not be called.
- output_device_->Stop();
- CallPumpSamples();
- EXPECT_EQ(renderer_.frames_rendered(), 0);
- }
|