blob: 1d65fd1564ec667cd48b436e38ae55f95407ddb2 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/media/renderer_webaudiodevice_impl.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "media/audio/audio_features.h"
#include "media/base/audio_capturer_source.h"
#include "media/base/audio_glitch_info.h"
#include "media/base/limits.h"
#include "media/base/mock_audio_renderer_sink.h"
#include "media/base/output_device_info.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/public/web/modules/media/audio/audio_device_factory.h"
namespace content {
namespace {
using ::media::limits::kMaxWebAudioBufferSize;
using ::media::limits::kMinWebAudioBufferSize;
using ::testing::_;
using ::testing::InSequence;
class MockAudioRendererSink : public media::AudioRendererSink {
public:
explicit MockAudioRendererSink() = default;
void Initialize(const media::AudioParameters& params,
media::AudioRendererSink::RenderCallback* callback) override {
callback_ = callback;
}
MOCK_METHOD(void, Start, (), (override));
MOCK_METHOD(void, Stop, (), (override));
MOCK_METHOD(void, Pause, (), (override));
MOCK_METHOD(void, Play, (), (override));
MOCK_METHOD(void, Flush, (), (override));
MOCK_METHOD(bool, SetVolume, (double volume), (override));
MOCK_METHOD(media::OutputDeviceInfo, GetOutputDeviceInfo, (), (override));
MOCK_METHOD(void,
GetOutputDeviceInfoAsync,
(OutputDeviceInfoCB info_cb),
(override));
MOCK_METHOD(bool, IsOptimizedForHardwareParameters, (), (override));
MOCK_METHOD(bool, CurrentThreadIsRenderingThread, (), (override));
raw_ptr<media::AudioRendererSink::RenderCallback, DanglingUntriaged>
callback_ = nullptr;
private:
~MockAudioRendererSink() override = default;
};
constexpr int kHardwareSampleRate = 44100;
constexpr int kHardwareBufferSize = 128;
const blink::LocalFrameToken kFrameToken;
const media::OutputDeviceInfo kHealthyDevice(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK);
const media::OutputDeviceInfo kErrorDevice(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL);
media::AudioParameters MockGetOutputDeviceParameters(
const blink::LocalFrameToken& frame_token,
const std::string& device_id) {
return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(),
kHardwareSampleRate, kHardwareBufferSize);
}
class RendererWebAudioDeviceImplUnderTest : public RendererWebAudioDeviceImpl {
public:
RendererWebAudioDeviceImplUnderTest(
const blink::WebAudioSinkDescriptor& sink_descriptor,
media::ChannelLayoutConfig layout_config,
const blink::WebAudioLatencyHint& latency_hint,
std::optional<float> context_sample_rate,
media::AudioRendererSink::RenderCallback* callback,
CreateSilentSinkCallback silent_sink_callback)
: RendererWebAudioDeviceImpl(
sink_descriptor,
layout_config,
latency_hint,
context_sample_rate,
callback,
base::BindOnce(&MockGetOutputDeviceParameters),
std::move(silent_sink_callback)) {}
};
class RendererWebAudioDeviceImplConstructorParamTest
: public RendererWebAudioDeviceImpl {
public:
RendererWebAudioDeviceImplConstructorParamTest(
const blink::WebAudioSinkDescriptor& sink_descriptor,
media::ChannelLayoutConfig layout_config,
const blink::WebAudioLatencyHint& latency_hint,
std::optional<float> context_sample_rate,
media::AudioRendererSink::RenderCallback* callback,
CreateSilentSinkCallback silent_sink_callback,
base::RepeatingCallback<
media::AudioParameters(const blink::LocalFrameToken&,
const std::string&)> device_params_cb);
};
RendererWebAudioDeviceImplConstructorParamTest::
RendererWebAudioDeviceImplConstructorParamTest(
const blink::WebAudioSinkDescriptor& sink_descriptor,
media::ChannelLayoutConfig layout_config,
const blink::WebAudioLatencyHint& latency_hint,
std::optional<float> context_sample_rate,
media::AudioRendererSink::RenderCallback* callback,
CreateSilentSinkCallback silent_sink_callback,
base::RepeatingCallback<
media::AudioParameters(const blink::LocalFrameToken&,
const std::string&)> device_params_cb)
: RendererWebAudioDeviceImpl(sink_descriptor,
layout_config,
latency_hint,
context_sample_rate,
callback,
std::move(device_params_cb),
std::move(silent_sink_callback)) {}
} // namespace
class RendererWebAudioDeviceImplTest
: public media::AudioRendererSink::RenderCallback,
public blink::AudioDeviceFactory,
public testing::Test {
public:
MOCK_METHOD(int,
Render,
(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const media::AudioGlitchInfo& glitch_info,
media::AudioBus* dest),
(override));
void OnRenderError() override {}
scoped_refptr<media::AudioRendererSink> CreateMockSilentSink(
const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
return mock_audio_renderer_sink_;
}
protected:
RendererWebAudioDeviceImplTest() {
mock_audio_renderer_sink_ = base::MakeRefCounted<MockAudioRendererSink>();
}
void SetupDevice(blink::WebAudioLatencyHint latencyHint) {
blink::WebAudioSinkDescriptor sink_descriptor(
blink::WebString::FromUTF8(std::string()), kFrameToken);
webaudio_device_ = std::make_unique<RendererWebAudioDeviceImplUnderTest>(
sink_descriptor, media::ChannelLayoutConfig::Mono(), latencyHint,
context_sample_rate_, this,
base::BindRepeating(
&RendererWebAudioDeviceImplTest::CreateMockSilentSink,
// Guaranteed to be valid because |this| owns |webaudio_device_| and
// so will outlive it.
base::Unretained(this)));
webaudio_device_->SetSilentSinkTaskRunnerForTesting(
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
}
void SetupDevice(media::ChannelLayoutConfig layout_config) {
blink::WebAudioSinkDescriptor sink_descriptor(
blink::WebString::FromUTF8(std::string()), kFrameToken);
webaudio_device_ = std::make_unique<RendererWebAudioDeviceImplUnderTest>(
sink_descriptor, layout_config,
blink::WebAudioLatencyHint(
blink::WebAudioLatencyHint::kCategoryInteractive),
context_sample_rate_, this,
base::BindRepeating(
&RendererWebAudioDeviceImplTest::CreateMockSilentSink,
// Guaranteed to be valid because |this| owns |webaudio_device_| and
// so will outlive it.
base::Unretained(this)));
webaudio_device_->SetSilentSinkTaskRunnerForTesting(
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
}
void SetupDevice(blink::WebAudioSinkDescriptor sink_descriptor) {
webaudio_device_ = std::make_unique<RendererWebAudioDeviceImplUnderTest>(
sink_descriptor, media::ChannelLayoutConfig::Mono(),
blink::WebAudioLatencyHint(
blink::WebAudioLatencyHint::kCategoryInteractive),
context_sample_rate_, this,
base::BindRepeating(
&RendererWebAudioDeviceImplTest::CreateMockSilentSink,
// Guaranteed to be valid because |this| owns |webaudio_device_| and
// so will outlive it.
base::Unretained(this)));
webaudio_device_->SetSilentSinkTaskRunnerForTesting(
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
}
scoped_refptr<media::AudioRendererSink> NewAudioRendererSink(
blink::WebAudioDeviceSourceType render_token,
const blink::LocalFrameToken& frame_token,
const media::AudioSinkParameters& params) override {
return mock_audio_renderer_sink_;
}
void TearDown() override { webaudio_device_.reset(); }
std::unique_ptr<RendererWebAudioDeviceImpl> webaudio_device_;
base::test::SingleThreadTaskEnvironment task_environment_;
scoped_refptr<MockAudioRendererSink> mock_audio_renderer_sink_;
std::optional<float> context_sample_rate_;
};
class RendererWebAudioDeviceImplBufferSizeTest : public ::testing::Test {
protected:
static constexpr int kHardwareSampleRate48k = 48000;
static constexpr int kHardwareBufferSize48k = 480;
static constexpr int kHardwareSampleRate44k = 44100;
static constexpr int kHardwareBufferSize44k = 441;
base::test::ScopedFeatureList feature_list_;
};
// When the kWebAudioRemoveAudioDestinationResampler
// feature is disabled, the GetOutputBufferSize method returns the default
// hardware buffer size, regardless of the context sample rate.
TEST_F(RendererWebAudioDeviceImplBufferSizeTest,
InteractiveLatency_FeatureDisabled_UsesDefaultBufferSize) {
feature_list_.InitAndDisableFeature(
features::kWebAudioRemoveAudioDestinationResampler);
blink::WebAudioLatencyHint latency_hint(
blink::WebAudioLatencyHint::kCategoryInteractive);
media::AudioParameters hardware_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), kHardwareSampleRate48k,
kHardwareBufferSize48k);
// When feature is disabled, we ensure the context_sample_rate is using
// default hardware sample rate before calling
// `RendererWebAudioDeviceImpl::GetOutputBufferSize`.
int context_sample_rate = kHardwareSampleRate48k;
int output_buffer_size = RendererWebAudioDeviceImpl::GetOutputBufferSize(
latency_hint, context_sample_rate, hardware_params);
EXPECT_EQ(output_buffer_size, 480);
}
// When the kWebAudioRemoveAudioDestinationResampler
// feature is enabled and the context sample rate matches the hardware sample
// rate, the GetOutputBufferSize method returns the default hardware buffer
// size.
TEST_F(RendererWebAudioDeviceImplBufferSizeTest,
InteractiveLatency_SameSampleRate_ReturnsDefaultBufferSize) {
feature_list_.InitAndEnableFeature(
features::kWebAudioRemoveAudioDestinationResampler);
blink::WebAudioLatencyHint latency_hint(
blink::WebAudioLatencyHint::kCategoryInteractive);
media::AudioParameters hardware_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), kHardwareSampleRate48k,
kHardwareBufferSize48k);
int context_sample_rate = 48000;
int output_buffer_size = RendererWebAudioDeviceImpl::GetOutputBufferSize(
latency_hint, context_sample_rate, hardware_params);
EXPECT_EQ(output_buffer_size, 480);
}
// When the kWebAudioRemoveAudioDestinationResampler
// feature is enabled and the context sample rate is significantly lower than
// the hardware sample rate, the GetOutputBufferSize method returns the minimum
// allowed buffer size (kMinWebAudioBufferSize). This ensures that the scaled
// buffer size is capped at the minimum.
TEST_F(RendererWebAudioDeviceImplBufferSizeTest,
InteractiveLatency_LowSampleRate_CapsAtMinBufferSize) {
feature_list_.InitAndEnableFeature(
features::kWebAudioRemoveAudioDestinationResampler);
blink::WebAudioLatencyHint latency_hint(
blink::WebAudioLatencyHint::kCategoryInteractive);
media::AudioParameters hardware_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), kHardwareSampleRate48k,
kHardwareBufferSize48k);
int context_sample_rate = 8000;
int output_buffer_size = RendererWebAudioDeviceImpl::GetOutputBufferSize(
latency_hint, context_sample_rate, hardware_params);
EXPECT_EQ(output_buffer_size, kMinWebAudioBufferSize);
}
// When the kWebAudioRemoveAudioDestinationResampler
// feature is enabled and the context sample rate is higher than the hardware
// sample rate, the GetOutputBufferSize method correctly scales the buffer size.
TEST_F(RendererWebAudioDeviceImplBufferSizeTest,
InteractiveLatency_HighSampleRate_ScalesBufferSize) {
feature_list_.InitAndEnableFeature(
features::kWebAudioRemoveAudioDestinationResampler);
blink::WebAudioLatencyHint latency_hint(
blink::WebAudioLatencyHint::kCategoryInteractive);
media::AudioParameters hardware_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), kHardwareSampleRate48k,
kHardwareBufferSize48k);
int context_sample_rate = 768000;
int output_buffer_size = RendererWebAudioDeviceImpl::GetOutputBufferSize(
latency_hint, context_sample_rate, hardware_params);
EXPECT_EQ(output_buffer_size, 7680);
}
// When the kWebAudioRemoveAudioDestinationResampler feature is enabled and the
// context sample rate is slightly higher than the hardware sample rate, the
// GetOutputBufferSize method correctly scales the buffer size, demonstrating
// accurate rounding behavior.
TEST_F(RendererWebAudioDeviceImplBufferSizeTest,
InteractiveLatency_CloseSampleRate_ScalesBufferSize) {
feature_list_.InitAndEnableFeature(
features::kWebAudioRemoveAudioDestinationResampler);
blink::WebAudioLatencyHint latency_hint(
blink::WebAudioLatencyHint::kCategoryInteractive);
media::AudioParameters hardware_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), kHardwareSampleRate48k,
kHardwareBufferSize48k);
int context_sample_rate = 48001;
int output_buffer_size = RendererWebAudioDeviceImpl::GetOutputBufferSize(
latency_hint, context_sample_rate, hardware_params);
EXPECT_EQ(output_buffer_size, 481);
}
// When the kWebAudioRemoveAudioDestinationResampler feature is enabled and the
// context sample rate is slightly higher than the hardware sample rate, with
// different hardware parameters, the GetOutputBufferSize method correctly
// scales the buffer size, demonstrating general rounding and scaling behavior
// with various hardware configurations.
TEST_F(RendererWebAudioDeviceImplBufferSizeTest,
InteractiveLatency_CloseSampleRate2_ScalesBufferSize) {
feature_list_.InitAndEnableFeature(
features::kWebAudioRemoveAudioDestinationResampler);
blink::WebAudioLatencyHint latency_hint(
blink::WebAudioLatencyHint::kCategoryInteractive);
media::AudioParameters hardware_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), kHardwareSampleRate44k,
kHardwareBufferSize44k);
int context_sample_rate = 48000;
int output_buffer_size = RendererWebAudioDeviceImpl::GetOutputBufferSize(
latency_hint, context_sample_rate, hardware_params);
EXPECT_EQ(output_buffer_size, 481);
}
// When the kWebAudioRemoveAudioDestinationResampler
// feature is enabled and the context sample rate is extremely high (potentially
// unsupported), the GetOutputBufferSize method caps the scaled buffer size at
// the maximum allowed buffer size (kMaxWebAudioBufferSize).
TEST_F(RendererWebAudioDeviceImplBufferSizeTest,
InteractiveLatency_VeryHighSampleRate_CapsAtMaxBufferSize) {
feature_list_.InitAndEnableFeature(
features::kWebAudioRemoveAudioDestinationResampler);
blink::WebAudioLatencyHint latency_hint(
blink::WebAudioLatencyHint::kCategoryInteractive);
media::AudioParameters hardware_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), kHardwareSampleRate48k,
kHardwareBufferSize48k);
int context_sample_rate = 999000;
int output_buffer_size = RendererWebAudioDeviceImpl::GetOutputBufferSize(
latency_hint, context_sample_rate, hardware_params);
EXPECT_EQ(output_buffer_size, kMaxWebAudioBufferSize);
}
TEST_F(RendererWebAudioDeviceImplTest, ChannelLayout) {
for (int ch = 1; ch < static_cast<int>(media::limits::kMaxChannels); ++ch) {
SCOPED_TRACE(base::StringPrintf("ch == %d", ch));
media::ChannelLayout layout = media::GuessChannelLayout(ch);
if (layout == media::CHANNEL_LAYOUT_UNSUPPORTED) {
layout = media::CHANNEL_LAYOUT_DISCRETE;
}
SetupDevice({layout, ch});
media::AudioParameters sink_params =
webaudio_device_->get_sink_params_for_testing();
EXPECT_TRUE(sink_params.IsValid());
EXPECT_EQ(layout, sink_params.channel_layout());
EXPECT_EQ(ch, sink_params.channels());
}
}
TEST_F(RendererWebAudioDeviceImplTest, NullSink_RenderWorks) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*this, Render).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
// The WebAudioSinkDescriptor constructor with frame token will construct a
// silent sink.
SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
webaudio_device_->Start();
mock_audio_renderer_sink_->callback_->Render(
base::TimeDelta::Min(), base::TimeTicks::Now(), {},
media::AudioBus::Create(1, kHardwareBufferSize).get());
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest, NullSink_PauseResumeWorks) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Pause).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
// The WebAudioSinkDescriptor constructor with frame token will construct a
// silent sink.
SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
webaudio_device_->Start();
webaudio_device_->Pause();
webaudio_device_->Resume();
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest,
NullSink_StartRenderStopStartRenderStopWorks) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*this, Render).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*this, Render).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
// The WebAudioSinkDescriptor constructor with frame token will construct a
// silent sink.
SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
webaudio_device_->Start();
mock_audio_renderer_sink_->callback_->Render(
base::TimeDelta::Min(), base::TimeTicks::Now(), {},
media::AudioBus::Create(1, kHardwareBufferSize).get());
webaudio_device_->Stop();
webaudio_device_->Start();
mock_audio_renderer_sink_->callback_->Render(
base::TimeDelta::Min(), base::TimeTicks::Now(), {},
media::AudioBus::Create(1, kHardwareBufferSize).get());
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest, NullSink_RepeatedStartWorks) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
// The WebAudioSinkDescriptor constructor with frame token will construct a
// silent sink.
SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
webaudio_device_->Start();
webaudio_device_->Start();
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest, NullSink_RepeatedPauseWorks) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Pause).Times(2);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
// The WebAudioSinkDescriptor constructor with frame token will construct a
// silent sink.
SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
webaudio_device_->Start();
webaudio_device_->Pause();
webaudio_device_->Pause();
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest, NullSink_RepeatedResumeWorks) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Pause).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(2);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
// The WebAudioSinkDescriptor constructor with frame token will construct a
// silent sink.
SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
webaudio_device_->Start();
webaudio_device_->Pause();
webaudio_device_->Resume();
webaudio_device_->Resume();
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest, NullSink_RepeatedStopWorks) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
// The WebAudioSinkDescriptor constructor with frame token will construct a
// silent sink.
SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
webaudio_device_->Start();
webaudio_device_->Stop();
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest,
CreateSinkAndGetDeviceStatus_HealthyDevice) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, GetOutputDeviceInfo)
.Times(1)
.WillOnce(testing::Return(kHealthyDevice));
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
SetupDevice(media::ChannelLayoutConfig::Stereo());
// `sink_` should be created after OUTPUT_DEVICE_STATUS_OK status return from
// `CreateAndGetSinkStatus` call.
EXPECT_EQ(webaudio_device_->sink_, nullptr);
media::OutputDeviceStatus status =
webaudio_device_->MaybeCreateSinkAndGetStatus();
EXPECT_NE(webaudio_device_->sink_, nullptr);
// Healthy device should return OUTPUT_DEVICE_STATUS_OK.
EXPECT_EQ(status, media ::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK);
webaudio_device_->Start();
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest,
CreateSinkAndGetDeviceStatus_ErrorDevice) {
{
InSequence s;
EXPECT_CALL(*mock_audio_renderer_sink_, GetOutputDeviceInfo)
.Times(1)
.WillOnce(testing::Return(kErrorDevice));
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(0);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(0);
// Stop() is necessary before destruction per AudioRendererSink contract.
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
SetupDevice(media::ChannelLayoutConfig::Stereo());
// `sink_` should be remain as nullptr after
// OUTPUT_DEVICE_STATUS_ERROR_INTERNAL status return from
// `CreateAndGetSinkStatus` call.
EXPECT_EQ(webaudio_device_->sink_, nullptr);
media::OutputDeviceStatus status =
webaudio_device_->MaybeCreateSinkAndGetStatus();
EXPECT_EQ(webaudio_device_->sink_, nullptr);
// Error device should return OUTPUT_DEVICE_STATUS_ERROR_INTERNAL.
EXPECT_EQ(status,
media ::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL);
}
TEST_F(RendererWebAudioDeviceImplTest,
CreateSinkAndGetDeviceStatus_SilentSink) {
{
InSequence s;
// Silent sink shouldn't invoke `GetOutputDeviceInfo`.
EXPECT_CALL(*mock_audio_renderer_sink_, GetOutputDeviceInfo).Times(0);
EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
}
// The WebAudioSinkDescriptor constructor with frame token will construct a
// silent sink.
SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
// `sink_` should be created after OUTPUT_DEVICE_STATUS_OK status return from
// `CreateAndGetSinkStatus` call.
EXPECT_EQ(webaudio_device_->sink_, nullptr);
media::OutputDeviceStatus status =
webaudio_device_->MaybeCreateSinkAndGetStatus();
EXPECT_NE(webaudio_device_->sink_, nullptr);
// Silent sink should return OUTPUT_DEVICE_STATUS_OK.
EXPECT_EQ(status, media ::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK);
webaudio_device_->Start();
webaudio_device_->Stop();
}
TEST_F(RendererWebAudioDeviceImplTest, ValidDeviceParameters) {
// Setup a scenario where the device parameters callback returns valid
// parameters.
SetupDevice(media::ChannelLayoutConfig::Stereo());
EXPECT_EQ(webaudio_device_->GetOriginalSinkParamsForTesting().sample_rate(),
kHardwareSampleRate);
EXPECT_EQ(
webaudio_device_->GetOriginalSinkParamsForTesting().frames_per_buffer(),
kHardwareBufferSize);
EXPECT_TRUE(webaudio_device_->GetOriginalSinkParamsForTesting().IsValid());
}
TEST_F(RendererWebAudioDeviceImplTest,
HandleInvalidOriginalSinkParamsInConstructor) {
media::AudioParameters default_params;
EXPECT_FALSE(default_params.IsValid());
// Test for handling invalid original sink parameters in constructor.
auto mock_device_params_cb =
base::BindRepeating([](const blink::LocalFrameToken&,
const std::string&) -> media::AudioParameters {
return media::AudioParameters();
});
blink::WebAudioSinkDescriptor sink_descriptor(
blink::WebString::FromUTF8(std::string()), kFrameToken);
RendererWebAudioDeviceImplConstructorParamTest device_under_test(
sink_descriptor, media::ChannelLayoutConfig::Stereo(),
blink::WebAudioLatencyHint(
blink::WebAudioLatencyHint::kCategoryInteractive),
std::nullopt, this,
base::BindRepeating(&RendererWebAudioDeviceImplTest::CreateMockSilentSink,
base::Unretained(this)),
mock_device_params_cb);
const media::AudioParameters& params =
device_under_test.GetOriginalSinkParamsForTesting();
EXPECT_EQ(params.format(), media::AudioParameters::AUDIO_FAKE);
EXPECT_EQ(params.sample_rate(), 48000);
EXPECT_EQ(params.frames_per_buffer(), 480);
EXPECT_TRUE(params.IsValid());
}
class RendererWebAudioDeviceImplLatencyAndSampleRateTest
: public RendererWebAudioDeviceImplTest,
public testing::WithParamInterface<
std::tuple<blink::WebAudioLatencyHint::AudioContextLatencyCategory,
int>> {
protected:
void SetUp() override {
if (std::get<0>(GetParam()) == blink::WebAudioLatencyHint::kCategoryExact) {
// Simulate a 10ms exact latency.
test_latency_hint_ = blink::WebAudioLatencyHint(/*seconds=*/0.01);
} else {
test_latency_hint_ = blink::WebAudioLatencyHint(std::get<0>(GetParam()));
}
int sample_rate = std::get<1>(GetParam());
// sample_rate == 0 means no context_sample_rate is specified.
if (sample_rate != 0) {
context_sample_rate_ = static_cast<float>(sample_rate);
}
feature_list_.InitAndEnableFeature(
features::kWebAudioRemoveAudioDestinationResampler);
}
protected:
blink::WebAudioLatencyHint test_latency_hint_{
blink::WebAudioLatencyHint::kCategoryInteractive};
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_P(RendererWebAudioDeviceImplLatencyAndSampleRateTest,
TestLatencyHintValues) {
int context_sample_rate = context_sample_rate_.value_or(kHardwareSampleRate);
media::AudioParameters hardware_params(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(), kHardwareSampleRate,
kHardwareBufferSize);
int expected_buffer_size = RendererWebAudioDeviceImpl::GetOutputBufferSize(
test_latency_hint_, context_sample_rate, hardware_params);
SetupDevice(test_latency_hint_);
EXPECT_EQ(webaudio_device_->SampleRate(), context_sample_rate);
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), expected_buffer_size);
webaudio_device_->Start();
EXPECT_EQ(webaudio_device_->SampleRate(), context_sample_rate);
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), expected_buffer_size);
webaudio_device_->Stop();
EXPECT_EQ(webaudio_device_->SampleRate(), context_sample_rate);
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), expected_buffer_size);
}
INSTANTIATE_TEST_SUITE_P(
All,
RendererWebAudioDeviceImplLatencyAndSampleRateTest,
testing::Combine(
testing::Values(blink::WebAudioLatencyHint::kCategoryInteractive,
blink::WebAudioLatencyHint::kCategoryBalanced,
blink::WebAudioLatencyHint::kCategoryPlayback,
blink::WebAudioLatencyHint::kCategoryExact),
// User provided sample rate; 0 means no sample rate provided.
testing::Values(0, 16000, 44100, 48000, 96000)));
} // namespace content