blob: 727c7133b973f7aa86ac79b58303ba85e32af0ba [file] [log] [blame]
// 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 "media/fuchsia/audio/fuchsia_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 "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 media {
constexpr int kSampleRate = 44100;
constexpr ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
constexpr int kNumChannels = 2;
constexpr uint64_t kTestSessionId = 42;
constexpr base::TimeDelta kPeriod = base::Milliseconds(10);
constexpr int kFramesPerPeriod = 441;
class TestRenderer : public AudioRendererSink::RenderCallback {
public:
TestRenderer() = default;
~TestRenderer() override = default;
// AudioRendererSink::Renderer interface.
int Render(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
int prior_frames_skipped,
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 FuchsiaAudioOutputDeviceTest : public testing::Test {
public:
FuchsiaAudioOutputDeviceTest() {
fidl::InterfaceHandle<fuchsia::media::AudioConsumer> audio_consumer;
fake_audio_consumer_ = std::make_unique<FakeAudioConsumer>(
kTestSessionId, audio_consumer.NewRequest());
output_device_ = FuchsiaAudioOutputDevice::Create(
std::move(audio_consumer), base::ThreadTaskRunnerHandle::Get());
}
~FuchsiaAudioOutputDeviceTest() override { output_device_->Stop(); }
protected:
void Initialize() {
output_device_->Initialize(
AudioParameters(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 FuchsiaAudioOutputDevice 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
// FuchsiaAudioOutputDevice 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, FakeAudioConsumer::kMinLeadTime);
EXPECT_LT(lead_time,
FakeAudioConsumer::kMinLeadTime + base::Milliseconds(30));
}
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::IO,
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<FakeAudioConsumer> fake_audio_consumer_;
TestRenderer renderer_;
scoped_refptr<FuchsiaAudioOutputDevice> output_device_;
};
TEST_F(FuchsiaAudioOutputDeviceTest, 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(FuchsiaAudioOutputDeviceTest, StartAndPlay) {
InitializeAndStart();
renderer_.reset_frames_rendered();
// Try advancing time and verify that FuchsiaAudioOutputDevice 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(FuchsiaAudioOutputDeviceTest, Pause) {
InitializeAndStart();
renderer_.reset_frames_rendered();
// Advancing time and verify that FuchsiaAudioOutputDevice 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(FuchsiaAudioOutputDeviceTest, 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. FuchsiaAudioOutputDevice 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(FuchsiaAudioOutputDeviceTest, 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(FuchsiaAudioOutputDeviceTest, 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);
}
} // namespace media