WebAudio: Remove AudioDestination resampler when feature enabled
This CL introduces the feature flag WebAudioRemoveAudioDestinationResampler. When enabled, this flag bypasses the resampling step within the AudioDestination node. This allows the AudioService to handle resampling, potentially reducing latency and overhead. Additionally, GetOutputBufferSize has been modified to scale hardware buffer sizes and enforce minimum and maximum buffer size limits. Design Doc: go/webaudio-sample-rate-reconciliation Bug: 374797496 Change-Id: I91c2d3064ded355b09309da11589be4d8ae2f04d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6279011 Commit-Queue: Alvin Ji <alvinji@chromium.org> Reviewed-by: Dave Tapuska <dtapuska@chromium.org> Reviewed-by: Eugene Zemtsov <eugene@chromium.org> Reviewed-by: Michael Wilson <mjwilson@chromium.org> Reviewed-by: Hongchan Choi <hongchan@chromium.org> Cr-Commit-Position: refs/heads/main@{#1437249}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
a9ede4da2f
commit
dd2943e8e6
content/renderer
media
renderer_webaudiodevice_impl.ccrenderer_webaudiodevice_impl.hrenderer_webaudiodevice_impl_unittest.cc
renderer_blink_platform_impl.ccrenderer_blink_platform_impl.hmedia/base
third_party/blink
common
public
renderer
@ -6,6 +6,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@ -52,6 +53,9 @@ namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::media::limits::kMaxWebAudioBufferSize;
|
||||
using ::media::limits::kMinWebAudioBufferSize;
|
||||
|
||||
blink::WebAudioDeviceSourceType GetLatencyHintSourceType(
|
||||
WebAudioLatencyHint::AudioContextLatencyCategory latency_category) {
|
||||
switch (latency_category) {
|
||||
@ -69,35 +73,6 @@ blink::WebAudioDeviceSourceType GetLatencyHintSourceType(
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
int GetOutputBufferSize(const blink::WebAudioLatencyHint& latency_hint,
|
||||
const media::AudioParameters& hardware_params) {
|
||||
const media::AudioParameters::HardwareCapabilities hardware_capabilities =
|
||||
hardware_params.hardware_capabilities().value_or(
|
||||
media::AudioParameters::HardwareCapabilities());
|
||||
|
||||
// Adjust output buffer size according to the latency requirement.
|
||||
switch (latency_hint.Category()) {
|
||||
case WebAudioLatencyHint::kCategoryInteractive:
|
||||
return media::AudioLatency::GetInteractiveBufferSize(
|
||||
hardware_params.frames_per_buffer());
|
||||
case WebAudioLatencyHint::kCategoryBalanced:
|
||||
return media::AudioLatency::GetRtcBufferSize(
|
||||
hardware_params.sample_rate(), hardware_params.frames_per_buffer());
|
||||
case WebAudioLatencyHint::kCategoryPlayback:
|
||||
return media::AudioLatency::GetHighLatencyBufferSize(
|
||||
hardware_params.sample_rate(), hardware_params.frames_per_buffer());
|
||||
case WebAudioLatencyHint::kCategoryExact:
|
||||
return media::AudioLatency::GetExactBufferSize(
|
||||
base::Seconds(latency_hint.Seconds()), hardware_params.sample_rate(),
|
||||
hardware_params.frames_per_buffer(),
|
||||
hardware_capabilities.min_frames_per_buffer,
|
||||
hardware_capabilities.max_frames_per_buffer,
|
||||
media::limits::kMaxWebAudioBufferSize);
|
||||
case WebAudioLatencyHint::kLastValue:
|
||||
NOTREACHED();
|
||||
}
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
media::AudioParameters GetOutputDeviceParameters(
|
||||
const blink::LocalFrameToken& frame_token,
|
||||
@ -119,6 +94,7 @@ std::unique_ptr<RendererWebAudioDeviceImpl> RendererWebAudioDeviceImpl::Create(
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
int number_of_output_channels,
|
||||
const blink::WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback* callback) {
|
||||
// The `number_of_output_channels` does not manifest the actual channel
|
||||
// layout of the audio output device. We use the best guess to the channel
|
||||
@ -134,14 +110,85 @@ std::unique_ptr<RendererWebAudioDeviceImpl> RendererWebAudioDeviceImpl::Create(
|
||||
return std::unique_ptr<RendererWebAudioDeviceImpl>(
|
||||
new RendererWebAudioDeviceImpl(
|
||||
sink_descriptor, {layout, number_of_output_channels}, latency_hint,
|
||||
callback, base::BindOnce(&GetOutputDeviceParameters),
|
||||
context_sample_rate, callback,
|
||||
base::BindOnce(&GetOutputDeviceParameters),
|
||||
base::BindRepeating(&GetNullAudioSink)));
|
||||
}
|
||||
|
||||
int RendererWebAudioDeviceImpl::GetOutputBufferSize(
|
||||
const blink::WebAudioLatencyHint& latency_hint,
|
||||
int resolved_context_sample_rate,
|
||||
const media::AudioParameters& hardware_params) {
|
||||
const media::AudioParameters::HardwareCapabilities hardware_capabilities =
|
||||
hardware_params.hardware_capabilities().value_or(
|
||||
media::AudioParameters::HardwareCapabilities());
|
||||
|
||||
const float scale_factor = static_cast<float>(resolved_context_sample_rate) /
|
||||
hardware_params.sample_rate();
|
||||
|
||||
int min_hardware_buffer_size = hardware_capabilities.min_frames_per_buffer;
|
||||
int max_hardware_buffer_size = hardware_capabilities.max_frames_per_buffer;
|
||||
|
||||
// The hardware may not provide explicit buffer size limits. In such cases,
|
||||
// we fall back to predefined minimum and maximum buffer sizes. Additionally,
|
||||
// hardware-provided limits are defined at the hardware's default sample rate.
|
||||
// We must scale these limits to the context's sample rate, as subsequent
|
||||
// buffer size calculations rely on the context sample rate.
|
||||
int min_buffer_size = kMinWebAudioBufferSize;
|
||||
if (min_hardware_buffer_size != 0) {
|
||||
min_buffer_size = std::max(
|
||||
kMinWebAudioBufferSize,
|
||||
static_cast<int>(std::ceil(min_hardware_buffer_size * scale_factor)));
|
||||
}
|
||||
|
||||
int max_buffer_size = kMaxWebAudioBufferSize;
|
||||
if (max_hardware_buffer_size != 0) {
|
||||
max_buffer_size = std::min(
|
||||
kMaxWebAudioBufferSize,
|
||||
static_cast<int>(std::ceil(max_hardware_buffer_size * scale_factor)));
|
||||
}
|
||||
// Ensure that the `min_buffer_size` does not exceed `max_buffer_size`.
|
||||
// This can occur when a small scale_factor leads to inverted limits after
|
||||
// scaling and clamping.
|
||||
max_buffer_size = std::max(min_buffer_size, max_buffer_size);
|
||||
|
||||
// Scale default buffer size to context rate. buffer size calculations for
|
||||
// each latency hint now use the context rate (instead of hardware rate).
|
||||
// Scaling ensures the calculated buffer size corresponds to the desired
|
||||
// callback interval at the context rate.
|
||||
int scaled_default_buffer_size = static_cast<int>(
|
||||
std::ceil(hardware_params.frames_per_buffer() * scale_factor));
|
||||
|
||||
// Clamp the scaled default buffer size to the valid range.
|
||||
scaled_default_buffer_size =
|
||||
std::clamp(scaled_default_buffer_size, min_buffer_size, max_buffer_size);
|
||||
|
||||
switch (latency_hint.Category()) {
|
||||
case WebAudioLatencyHint::kCategoryInteractive:
|
||||
return media::AudioLatency::GetInteractiveBufferSize(
|
||||
scaled_default_buffer_size);
|
||||
case WebAudioLatencyHint::kCategoryBalanced:
|
||||
return media::AudioLatency::GetRtcBufferSize(resolved_context_sample_rate,
|
||||
scaled_default_buffer_size);
|
||||
case WebAudioLatencyHint::kCategoryPlayback:
|
||||
return media::AudioLatency::GetHighLatencyBufferSize(
|
||||
resolved_context_sample_rate, scaled_default_buffer_size);
|
||||
case WebAudioLatencyHint::kCategoryExact:
|
||||
return media::AudioLatency::GetExactBufferSize(
|
||||
base::Seconds(latency_hint.Seconds()), resolved_context_sample_rate,
|
||||
scaled_default_buffer_size, min_buffer_size, max_buffer_size,
|
||||
kMaxWebAudioBufferSize);
|
||||
case WebAudioLatencyHint::kLastValue:
|
||||
NOTREACHED();
|
||||
}
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
RendererWebAudioDeviceImpl::RendererWebAudioDeviceImpl(
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
media::ChannelLayoutConfig layout_config,
|
||||
const blink::WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback* callback,
|
||||
OutputDeviceParamsCallback device_params_cb,
|
||||
CreateSilentSinkCallback create_silent_sink_cb)
|
||||
@ -196,13 +243,26 @@ RendererWebAudioDeviceImpl::RendererWebAudioDeviceImpl(
|
||||
"%s => (hardware_params=[%s])", __func__,
|
||||
original_sink_params_.AsHumanReadableString().c_str()));
|
||||
|
||||
const int output_buffer_size =
|
||||
GetOutputBufferSize(latency_hint_, original_sink_params_);
|
||||
// If the 'WebAudioRemoveAudioDestinationResampler' feature is enabled and
|
||||
// a context sample rate is provided, use the provided context sample rate.
|
||||
// Otherwise, fall back to the use default hardware sample rate to create
|
||||
// sink.
|
||||
int resolved_context_sample_rate;
|
||||
if (base::FeatureList::IsEnabled(
|
||||
blink::features::kWebAudioRemoveAudioDestinationResampler) &&
|
||||
context_sample_rate.has_value()) {
|
||||
resolved_context_sample_rate = *context_sample_rate;
|
||||
} else {
|
||||
resolved_context_sample_rate = original_sink_params_.sample_rate();
|
||||
}
|
||||
|
||||
const int output_buffer_size = GetOutputBufferSize(
|
||||
latency_hint_, resolved_context_sample_rate, original_sink_params_);
|
||||
|
||||
DCHECK_NE(0, output_buffer_size);
|
||||
|
||||
current_sink_params_.Reset(original_sink_params_.format(), layout_config,
|
||||
original_sink_params_.sample_rate(),
|
||||
output_buffer_size);
|
||||
resolved_context_sample_rate, output_buffer_size);
|
||||
|
||||
// Specify the latency info to be passed to the browser side.
|
||||
current_sink_params_.set_latency_tag(AudioDeviceFactory::GetSourceLatencyType(
|
||||
|
@ -49,7 +49,11 @@ class CONTENT_EXPORT RendererWebAudioDeviceImpl
|
||||
const blink::WebAudioSinkDescriptor& sink_descriptor,
|
||||
int number_of_output_channels,
|
||||
const blink::WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback* webaudio_callback);
|
||||
static int GetOutputBufferSize(const blink::WebAudioLatencyHint& latency_hint,
|
||||
int resolved_context_sample_rate,
|
||||
const media::AudioParameters& hardware_params);
|
||||
|
||||
// blink::WebAudioDevice implementation.
|
||||
void Start() override;
|
||||
@ -104,6 +108,7 @@ class CONTENT_EXPORT RendererWebAudioDeviceImpl
|
||||
const blink::WebAudioSinkDescriptor& sink_descriptor,
|
||||
media::ChannelLayoutConfig layout_config,
|
||||
const blink::WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback* webaudio_callback,
|
||||
OutputDeviceParamsCallback device_params_cb,
|
||||
CreateSilentSinkCallback create_silent_sink_cb);
|
||||
|
@ -10,6 +10,7 @@
|
||||
#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/base/audio_capturer_source.h"
|
||||
@ -19,17 +20,20 @@
|
||||
#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"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::InSequence;
|
||||
|
||||
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;
|
||||
@ -80,12 +84,14 @@ class RendererWebAudioDeviceImplUnderTest : public RendererWebAudioDeviceImpl {
|
||||
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)) {}
|
||||
@ -122,7 +128,8 @@ class RendererWebAudioDeviceImplTest
|
||||
blink::WebAudioSinkDescriptor sink_descriptor(
|
||||
blink::WebString::FromUTF8(std::string()), kFrameToken);
|
||||
webaudio_device_ = std::make_unique<RendererWebAudioDeviceImplUnderTest>(
|
||||
sink_descriptor, media::ChannelLayoutConfig::Mono(), latencyHint, this,
|
||||
sink_descriptor, media::ChannelLayoutConfig::Mono(), latencyHint,
|
||||
context_sample_rate_, this,
|
||||
base::BindRepeating(
|
||||
&RendererWebAudioDeviceImplTest::CreateMockSilentSink,
|
||||
// Guaranteed to be valid because |this| owns |webaudio_device_| and
|
||||
@ -139,7 +146,7 @@ class RendererWebAudioDeviceImplTest
|
||||
sink_descriptor, layout_config,
|
||||
blink::WebAudioLatencyHint(
|
||||
blink::WebAudioLatencyHint::kCategoryInteractive),
|
||||
this,
|
||||
context_sample_rate_, this,
|
||||
base::BindRepeating(
|
||||
&RendererWebAudioDeviceImplTest::CreateMockSilentSink,
|
||||
// Guaranteed to be valid because |this| owns |webaudio_device_| and
|
||||
@ -154,7 +161,7 @@ class RendererWebAudioDeviceImplTest
|
||||
sink_descriptor, media::ChannelLayoutConfig::Mono(),
|
||||
blink::WebAudioLatencyHint(
|
||||
blink::WebAudioLatencyHint::kCategoryInteractive),
|
||||
this,
|
||||
context_sample_rate_, this,
|
||||
base::BindRepeating(
|
||||
&RendererWebAudioDeviceImplTest::CreateMockSilentSink,
|
||||
// Guaranteed to be valid because |this| owns |webaudio_device_| and
|
||||
@ -176,15 +183,176 @@ class RendererWebAudioDeviceImplTest
|
||||
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(
|
||||
blink::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(
|
||||
blink::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(
|
||||
blink::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(
|
||||
blink::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(
|
||||
blink::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(
|
||||
blink::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(
|
||||
blink::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)
|
||||
if (layout == media::CHANNEL_LAYOUT_UNSUPPORTED) {
|
||||
layout = media::CHANNEL_LAYOUT_DISCRETE;
|
||||
}
|
||||
|
||||
SetupDevice({layout, ch});
|
||||
media::AudioParameters sink_params =
|
||||
@ -195,86 +363,6 @@ TEST_F(RendererWebAudioDeviceImplTest, ChannelLayout) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RendererWebAudioDeviceImplTest, TestLatencyHintValues) {
|
||||
blink::WebAudioLatencyHint interactiveLatencyHint(
|
||||
blink::WebAudioLatencyHint::kCategoryInteractive);
|
||||
int interactiveBufferSize =
|
||||
media::AudioLatency::GetInteractiveBufferSize(kHardwareBufferSize);
|
||||
SetupDevice(interactiveLatencyHint);
|
||||
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), interactiveBufferSize);
|
||||
|
||||
webaudio_device_->Start();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), interactiveBufferSize);
|
||||
|
||||
webaudio_device_->Stop();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), interactiveBufferSize);
|
||||
|
||||
webaudio_device_->Start();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), interactiveBufferSize);
|
||||
|
||||
webaudio_device_->Stop();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), interactiveBufferSize);
|
||||
|
||||
blink::WebAudioLatencyHint balancedLatencyHint(
|
||||
blink::WebAudioLatencyHint::kCategoryBalanced);
|
||||
int balancedBufferSize = media::AudioLatency::GetRtcBufferSize(
|
||||
kHardwareSampleRate, kHardwareBufferSize);
|
||||
SetupDevice(balancedLatencyHint);
|
||||
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), balancedBufferSize);
|
||||
|
||||
webaudio_device_->Start();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), balancedBufferSize);
|
||||
|
||||
webaudio_device_->Stop();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), balancedBufferSize);
|
||||
|
||||
webaudio_device_->Start();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), balancedBufferSize);
|
||||
|
||||
webaudio_device_->Stop();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), balancedBufferSize);
|
||||
|
||||
blink::WebAudioLatencyHint playbackLatencyHint(
|
||||
blink::WebAudioLatencyHint::kCategoryPlayback);
|
||||
int playbackBufferSize = media::AudioLatency::GetHighLatencyBufferSize(
|
||||
kHardwareSampleRate, kHardwareBufferSize);
|
||||
SetupDevice(playbackLatencyHint);
|
||||
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), playbackBufferSize);
|
||||
|
||||
webaudio_device_->Start();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), playbackBufferSize);
|
||||
|
||||
webaudio_device_->Stop();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), playbackBufferSize);
|
||||
|
||||
webaudio_device_->Start();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), playbackBufferSize);
|
||||
|
||||
webaudio_device_->Stop();
|
||||
EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
|
||||
EXPECT_EQ(webaudio_device_->FramesPerBuffer(), playbackBufferSize);
|
||||
|
||||
EXPECT_GE(playbackBufferSize, balancedBufferSize);
|
||||
EXPECT_GE(balancedBufferSize, interactiveBufferSize);
|
||||
}
|
||||
|
||||
TEST_F(RendererWebAudioDeviceImplTest, NullSink_RenderWorks) {
|
||||
{
|
||||
InSequence s;
|
||||
@ -504,4 +592,73 @@ TEST_F(RendererWebAudioDeviceImplTest,
|
||||
webaudio_device_->Start();
|
||||
webaudio_device_->Stop();
|
||||
}
|
||||
|
||||
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(
|
||||
blink::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
|
||||
|
@ -512,9 +512,11 @@ std::unique_ptr<WebAudioDevice> RendererBlinkPlatformImpl::CreateAudioDevice(
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
unsigned number_of_output_channels,
|
||||
const blink::WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback* callback) {
|
||||
return RendererWebAudioDeviceImpl::Create(
|
||||
sink_descriptor, number_of_output_channels, latency_hint, callback);
|
||||
sink_descriptor, number_of_output_channels, latency_hint,
|
||||
context_sample_rate, callback);
|
||||
}
|
||||
|
||||
bool RendererBlinkPlatformImpl::DecodeAudioFileData(
|
||||
|
@ -135,6 +135,7 @@ class CONTENT_EXPORT RendererBlinkPlatformImpl : public BlinkPlatformImpl {
|
||||
const blink::WebAudioSinkDescriptor& sink_descriptor,
|
||||
unsigned number_of_output_channels,
|
||||
const blink::WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback* callback) override;
|
||||
bool DecodeAudioFileData(blink::WebAudioBus* destination_bus,
|
||||
const char* audio_file_data,
|
||||
|
@ -78,7 +78,8 @@ inline constexpr int kMinAudioBufferSize = 256;
|
||||
inline constexpr int kMaxAudioBufferSize = 8192;
|
||||
#endif
|
||||
|
||||
// Maximum buffer size supported by Web Audio.
|
||||
// Minimum and maximum buffer size supported by Web Audio.
|
||||
inline constexpr int kMinWebAudioBufferSize = 128;
|
||||
inline constexpr int kMaxWebAudioBufferSize = 8192;
|
||||
|
||||
// Bounds for the number of threads used for software video decoding.
|
||||
|
9
third_party/blink/common/features.cc
vendored
9
third_party/blink/common/features.cc
vendored
@ -2684,6 +2684,15 @@ BASE_FEATURE_PARAM(bool,
|
||||
"latency_exact",
|
||||
true);
|
||||
|
||||
// This feature flag controls whether the WebAudio destination resampler is
|
||||
// bypassed. When enabled, if the WebAudio context's sample rate differs from
|
||||
// the hardware's sample rate, the resampling step that normally occurs within
|
||||
// the WebAudio destination node is skipped. This allows the AudioService to
|
||||
// handle any necessary resampling, potentially reducing latency and overhead.
|
||||
BASE_FEATURE(kWebAudioRemoveAudioDestinationResampler,
|
||||
"WebAudioRemoveAudioDestinationResampler",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
/// Enables cache-aware WebFonts loading. See https://crbug.com/570205.
|
||||
// The feature is disabled on Android for WebView API issue discussed at
|
||||
// https://crbug.com/942440.
|
||||
|
3
third_party/blink/public/common/features.h
vendored
3
third_party/blink/public/common/features.h
vendored
@ -1768,6 +1768,9 @@ BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE_PARAM(
|
||||
bool,
|
||||
kWebAudioBypassOutputBufferingExact);
|
||||
|
||||
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
|
||||
kWebAudioRemoveAudioDestinationResampler);
|
||||
|
||||
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kWebFontsCacheAwareTimeoutAdaption);
|
||||
|
||||
// Combine WebRTC Network and Worker threads. More info at crbug.com/1373439.
|
||||
|
1
third_party/blink/public/platform/platform.h
vendored
1
third_party/blink/public/platform/platform.h
vendored
@ -213,6 +213,7 @@ class BLINK_PLATFORM_EXPORT Platform {
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
unsigned number_of_output_channels,
|
||||
const WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback*) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ class AudioContextAutoplayTestPlatform : public TestingPlatformSupport {
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
unsigned number_of_output_channels,
|
||||
const WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback*) override {
|
||||
return std::make_unique<MockWebAudioDeviceForAutoplayTest>(
|
||||
AudioHardwareSampleRate(), AudioHardwareBufferSize());
|
||||
|
@ -179,6 +179,7 @@ class AudioContextTestPlatform : public TestingPlatformSupport {
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
unsigned number_of_output_channels,
|
||||
const WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback*) override {
|
||||
double buffer_size = 0;
|
||||
const double interactive_size = AudioHardwareBufferSize();
|
||||
@ -205,7 +206,7 @@ class AudioContextTestPlatform : public TestingPlatformSupport {
|
||||
}
|
||||
|
||||
return std::make_unique<MockWebAudioDeviceForAudioContext>(
|
||||
AudioHardwareSampleRate(), buffer_size);
|
||||
context_sample_rate.value_or(AudioHardwareSampleRate()), buffer_size);
|
||||
}
|
||||
|
||||
double AudioHardwareSampleRate() override { return 44100; }
|
||||
|
1
third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler_test.cc
vendored
1
third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler_test.cc
vendored
@ -69,6 +69,7 @@ class AudioContextTestPlatform : public TestingPlatformSupport {
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
unsigned number_of_output_channels,
|
||||
const WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback*) override {
|
||||
return std::make_unique<MockWebAudioDevice>(
|
||||
AudioHardwareSampleRate(), AudioHardwareBufferSize());
|
||||
|
@ -417,6 +417,7 @@ AudioDestination::AudioDestination(
|
||||
Platform::Current()->CreateAudioDevice(sink_descriptor,
|
||||
number_of_output_channels,
|
||||
latency_hint,
|
||||
context_sample_rate,
|
||||
this)),
|
||||
callback_buffer_size_(
|
||||
web_audio_device_ ? web_audio_device_->FramesPerBuffer() : 0),
|
||||
@ -479,7 +480,9 @@ AudioDestination::AudioDestination(
|
||||
|
||||
double scale_factor = 1.0;
|
||||
|
||||
if (context_sample_rate_ != web_audio_device_->SampleRate()) {
|
||||
if (!base::FeatureList::IsEnabled(
|
||||
features::kWebAudioRemoveAudioDestinationResampler) &&
|
||||
context_sample_rate_ != web_audio_device_->SampleRate()) {
|
||||
scale_factor = context_sample_rate_ / web_audio_device_->SampleRate();
|
||||
SendLogMessage(__func__,
|
||||
String::Format("=> (resampling from %0.f Hz to %0.f Hz)",
|
||||
|
@ -152,6 +152,10 @@ class PLATFORM_EXPORT AudioDestination final
|
||||
return fifo_->GetStateForTest();
|
||||
}
|
||||
|
||||
MediaMultiChannelResampler* GetResamplerForTesting() {
|
||||
return resampler_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
explicit AudioDestination(AudioIOCallback&,
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "media/base/audio_glitch_info.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
@ -25,10 +26,20 @@ namespace blink {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::InSequence;
|
||||
|
||||
const LocalFrameToken kFrameToken;
|
||||
|
||||
constexpr float kDefaultHardwareSampleRate = 44100;
|
||||
constexpr int kDefaultHardwareBufferSize = 512;
|
||||
constexpr int kDefaultHardwareOutputChannelNumber = 2;
|
||||
constexpr int kWebAudioRenderQuantum = 128;
|
||||
|
||||
int RoundUpToRenderQuantum(int requested_frames) {
|
||||
return std::ceil(requested_frames /
|
||||
static_cast<double>(kWebAudioRenderQuantum)) *
|
||||
kWebAudioRenderQuantum;
|
||||
}
|
||||
|
||||
class MockWebAudioDevice : public WebAudioDevice {
|
||||
public:
|
||||
explicit MockWebAudioDevice(double sample_rate, int frames_per_buffer)
|
||||
@ -54,15 +65,19 @@ class MockWebAudioDevice : public WebAudioDevice {
|
||||
|
||||
class TestPlatform : public TestingPlatformSupport {
|
||||
public:
|
||||
TestPlatform() {
|
||||
webaudio_device_ = std::make_unique<MockWebAudioDevice>(
|
||||
AudioHardwareSampleRate(), AudioHardwareBufferSize());
|
||||
TestPlatform() = default;
|
||||
~TestPlatform() override = default;
|
||||
|
||||
void CreateMockWebAudioDevice(float context_sample_rate, int buffer_size) {
|
||||
webaudio_device_ =
|
||||
std::make_unique<MockWebAudioDevice>(context_sample_rate, buffer_size);
|
||||
}
|
||||
|
||||
std::unique_ptr<WebAudioDevice> CreateAudioDevice(
|
||||
const WebAudioSinkDescriptor& sink_descriptor,
|
||||
unsigned number_of_output_channels,
|
||||
const WebAudioLatencyHint& latency_hint,
|
||||
std::optional<float> context_sample_rate,
|
||||
media::AudioRendererSink::RenderCallback*) override {
|
||||
CHECK(webaudio_device_ != nullptr)
|
||||
<< "Calling CreateAudioDevice (via AudioDestination::Create) multiple "
|
||||
@ -70,9 +85,15 @@ class TestPlatform : public TestingPlatformSupport {
|
||||
return std::move(webaudio_device_);
|
||||
}
|
||||
|
||||
double AudioHardwareSampleRate() override { return 44100; }
|
||||
size_t AudioHardwareBufferSize() override { return 512; }
|
||||
unsigned AudioHardwareOutputChannels() override { return 2; }
|
||||
double AudioHardwareSampleRate() override {
|
||||
return kDefaultHardwareSampleRate;
|
||||
}
|
||||
size_t AudioHardwareBufferSize() override {
|
||||
return kDefaultHardwareBufferSize;
|
||||
}
|
||||
unsigned AudioHardwareOutputChannels() override {
|
||||
return kDefaultHardwareOutputChannelNumber;
|
||||
}
|
||||
|
||||
const MockWebAudioDevice& web_audio_device() {
|
||||
CHECK(webaudio_device_ != nullptr)
|
||||
@ -109,69 +130,82 @@ class AudioCallback : public AudioIOCallback {
|
||||
class AudioDestinationTest
|
||||
: public ::testing::TestWithParam<std::optional<float>> {
|
||||
public:
|
||||
void CountWASamplesProcessedForRate(std::optional<float> sample_rate) {
|
||||
WebAudioLatencyHint latency_hint(WebAudioLatencyHint::kCategoryInteractive);
|
||||
|
||||
const int channel_count =
|
||||
Platform::Current()->AudioHardwareOutputChannels();
|
||||
const size_t request_frames =
|
||||
Platform::Current()->AudioHardwareBufferSize();
|
||||
|
||||
scoped_refptr<AudioDestination> CreateAudioDestination(
|
||||
std::optional<float> context_sample_rate,
|
||||
WebAudioLatencyHint latency_hint) {
|
||||
// Assume the default audio device. (i.e. the empty string)
|
||||
WebAudioSinkDescriptor sink_descriptor(WebString::FromUTF8(""),
|
||||
kFrameToken);
|
||||
const int channel_count =
|
||||
Platform::Current()->AudioHardwareOutputChannels();
|
||||
|
||||
// TODO(https://crbug.com/988121) Replace 128 with the appropriate
|
||||
// AudioContextRenderSizeHintCategory.
|
||||
constexpr int render_quantum_frames = 128;
|
||||
scoped_refptr<AudioDestination> destination = AudioDestination::Create(
|
||||
callback_, sink_descriptor, channel_count, latency_hint, sample_rate,
|
||||
render_quantum_frames);
|
||||
destination->Start();
|
||||
|
||||
destination->Render(
|
||||
base::TimeDelta::Min(), base::TimeTicks::Now(), {},
|
||||
media::AudioBus::Create(channel_count, request_frames).get());
|
||||
|
||||
// Calculate the expected number of frames to be consumed to produce
|
||||
// |request_frames| frames.
|
||||
int exact_frames_required = request_frames;
|
||||
if (destination->SampleRate() !=
|
||||
Platform::Current()->AudioHardwareSampleRate()) {
|
||||
exact_frames_required =
|
||||
std::ceil(request_frames * destination->SampleRate() /
|
||||
Platform::Current()->AudioHardwareSampleRate());
|
||||
// The internal resampler requires media::SincResampler::KernelSize() / 2
|
||||
// more frames to flush the output. See sinc_resampler.cc for details.
|
||||
exact_frames_required +=
|
||||
media::SincResampler::KernelSizeFromRequestFrames(request_frames) / 2;
|
||||
}
|
||||
const int expected_frames_processed =
|
||||
std::ceil(exact_frames_required /
|
||||
static_cast<double>(render_quantum_frames)) *
|
||||
render_quantum_frames;
|
||||
|
||||
EXPECT_EQ(expected_frames_processed, callback_.frames_processed_);
|
||||
return AudioDestination::Create(callback_, sink_descriptor, channel_count,
|
||||
latency_hint, context_sample_rate,
|
||||
kWebAudioRenderQuantum);
|
||||
}
|
||||
|
||||
protected:
|
||||
base::test::ScopedFeatureList feature_list_;
|
||||
AudioCallback callback_;
|
||||
};
|
||||
|
||||
// This test verifies that resampling occurs correctly when the AudioContext's
|
||||
// sample rate differs from the hardware's sample rate. We explicitly disable
|
||||
// kWebAudioRemoveAudioDestinationResampler to ensure the resampler can be
|
||||
// created.
|
||||
TEST_P(AudioDestinationTest, ResamplingTest) {
|
||||
#if defined(MEMORY_SANITIZER)
|
||||
// TODO(crbug.com/342415791): Fix and re-enable tests with MSan.
|
||||
GTEST_SKIP();
|
||||
#else
|
||||
feature_list_.InitAndDisableFeature(
|
||||
blink::features::kWebAudioRemoveAudioDestinationResampler);
|
||||
ScopedTestingPlatformSupport<TestPlatform> platform;
|
||||
{
|
||||
InSequence s;
|
||||
platform->CreateMockWebAudioDevice(kDefaultHardwareSampleRate,
|
||||
kDefaultHardwareBufferSize);
|
||||
EXPECT_CALL(platform->web_audio_device(), Start).Times(1);
|
||||
EXPECT_CALL(platform->web_audio_device(), Stop).Times(1);
|
||||
|
||||
EXPECT_CALL(platform->web_audio_device(), Start).Times(1);
|
||||
EXPECT_CALL(platform->web_audio_device(), Stop).Times(1);
|
||||
scoped_refptr<AudioDestination> audio_destination = CreateAudioDestination(
|
||||
GetParam(),
|
||||
WebAudioLatencyHint(WebAudioLatencyHint::kCategoryInteractive));
|
||||
|
||||
const int requested_frames = audio_destination->FramesPerBuffer();
|
||||
|
||||
audio_destination->Start();
|
||||
audio_destination->Render(
|
||||
base::TimeDelta::Min(), base::TimeTicks::Now(), {},
|
||||
media::AudioBus::Create(kDefaultHardwareOutputChannelNumber,
|
||||
requested_frames)
|
||||
.get());
|
||||
|
||||
int scaled_requested_frames = requested_frames;
|
||||
|
||||
// Check if resampling was performed and calculate the expected frame count.
|
||||
if (audio_destination->SampleRate() !=
|
||||
Platform::Current()->AudioHardwareSampleRate()) {
|
||||
// Resampler should be created when sample rates differ.
|
||||
EXPECT_NE(audio_destination->GetResamplerForTesting(), nullptr);
|
||||
|
||||
// Calculate the scaled frame count based on the sample rate difference.
|
||||
scaled_requested_frames =
|
||||
std::ceil(requested_frames * audio_destination->SampleRate() /
|
||||
Platform::Current()->AudioHardwareSampleRate());
|
||||
|
||||
// The internal resampler requires media::SincResampler::KernelSize() / 2
|
||||
// more frames to flush the output. See sinc_resampler.cc for details.
|
||||
scaled_requested_frames +=
|
||||
media::SincResampler::KernelSizeFromRequestFrames(requested_frames) / 2;
|
||||
} else {
|
||||
// No resampler should be created when sample rates are the same.
|
||||
EXPECT_EQ(audio_destination->GetResamplerForTesting(), nullptr);
|
||||
}
|
||||
|
||||
CountWASamplesProcessedForRate(GetParam());
|
||||
// Verify that the number of frames processed matches the expected count,
|
||||
// rounded up to the render quantum.
|
||||
const int expected_processed_frames =
|
||||
RoundUpToRenderQuantum(scaled_requested_frames);
|
||||
EXPECT_EQ(expected_processed_frames, callback_.frames_processed_);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -180,26 +214,19 @@ TEST_P(AudioDestinationTest, GlitchAndDelay) {
|
||||
// TODO(crbug.com/342415791): Fix and re-enable tests with MSan.
|
||||
GTEST_SKIP();
|
||||
#else
|
||||
feature_list_.InitAndDisableFeature(
|
||||
blink::features::kWebAudioRemoveAudioDestinationResampler);
|
||||
ScopedTestingPlatformSupport<TestPlatform> platform;
|
||||
{
|
||||
InSequence s;
|
||||
EXPECT_CALL(platform->web_audio_device(), Start).Times(1);
|
||||
EXPECT_CALL(platform->web_audio_device(), Stop).Times(1);
|
||||
}
|
||||
platform->CreateMockWebAudioDevice(kDefaultHardwareSampleRate,
|
||||
kDefaultHardwareBufferSize);
|
||||
|
||||
std::optional<float> sample_rate = GetParam();
|
||||
WebAudioLatencyHint latency_hint(WebAudioLatencyHint::kCategoryInteractive);
|
||||
EXPECT_CALL(platform->web_audio_device(), Start).Times(1);
|
||||
EXPECT_CALL(platform->web_audio_device(), Stop).Times(1);
|
||||
|
||||
const int channel_count = Platform::Current()->AudioHardwareOutputChannels();
|
||||
const size_t request_frames = Platform::Current()->AudioHardwareBufferSize();
|
||||
|
||||
// Assume the default audio device. (i.e. the empty string)
|
||||
WebAudioSinkDescriptor sink_descriptor(WebString::FromUTF8(""), kFrameToken);
|
||||
|
||||
int render_quantum_frames = 128;
|
||||
scoped_refptr<AudioDestination> destination = AudioDestination::Create(
|
||||
callback_, sink_descriptor, channel_count, latency_hint, sample_rate,
|
||||
render_quantum_frames);
|
||||
scoped_refptr<AudioDestination> audio_destination = CreateAudioDestination(
|
||||
GetParam(),
|
||||
WebAudioLatencyHint(WebAudioLatencyHint::kCategoryInteractive));
|
||||
const int requested_frames = audio_destination->FramesPerBuffer();
|
||||
|
||||
const int kRenderCount = 3;
|
||||
|
||||
@ -218,23 +245,22 @@ TEST_P(AudioDestinationTest, GlitchAndDelay) {
|
||||
// When creating the AudioDestination, some silence is added to the fifo to
|
||||
// prevent an underrun on the first callback. This contributes a constant
|
||||
// delay.
|
||||
int priming_frames =
|
||||
ceil(request_frames / static_cast<float>(render_quantum_frames)) *
|
||||
render_quantum_frames;
|
||||
const int priming_frames = RoundUpToRenderQuantum(requested_frames);
|
||||
base::TimeDelta priming_delay = audio_utilities::FramesToTime(
|
||||
priming_frames, Platform::Current()->AudioHardwareSampleRate());
|
||||
|
||||
auto audio_bus = media::AudioBus::Create(channel_count, request_frames);
|
||||
auto audio_bus = media::AudioBus::Create(kDefaultHardwareOutputChannelNumber,
|
||||
requested_frames);
|
||||
|
||||
destination->Start();
|
||||
audio_destination->Start();
|
||||
|
||||
for (int i = 0; i < kRenderCount; ++i) {
|
||||
destination->Render(delays[i], base::TimeTicks::Now(), glitches[i],
|
||||
audio_bus.get());
|
||||
audio_destination->Render(delays[i], base::TimeTicks::Now(), glitches[i],
|
||||
audio_bus.get());
|
||||
|
||||
EXPECT_EQ(callback_.glitch_accumulator_.GetAndReset(), glitches[i]);
|
||||
|
||||
if (destination->SampleRate() !=
|
||||
if (audio_destination->SampleRate() !=
|
||||
Platform::Current()->AudioHardwareSampleRate()) {
|
||||
// Resampler kernel adds a bit of a delay.
|
||||
EXPECT_GE(callback_.last_latency_, delays[i] + priming_delay);
|
||||
@ -245,10 +271,64 @@ TEST_P(AudioDestinationTest, GlitchAndDelay) {
|
||||
}
|
||||
}
|
||||
|
||||
destination->Stop();
|
||||
audio_destination->Stop();
|
||||
#endif
|
||||
}
|
||||
|
||||
// This test verifies that the AudioDestination resampler is correctly removed
|
||||
// when the kWebAudioRemoveAudioDestinationResampler feature is enabled
|
||||
// and WebAudioBypassOutputBuffering is also enabled.
|
||||
TEST_P(AudioDestinationTest, ResamplerIsRemoved) {
|
||||
feature_list_.InitAndEnableFeature(
|
||||
blink::features::kWebAudioRemoveAudioDestinationResampler);
|
||||
|
||||
// Ideally we should have two separate test for WebAudioBypassOutputBuffering
|
||||
// enabled and disabled. The difference here is the
|
||||
// `expected_processed_frames` will be different. In FIFO non-bypass case the
|
||||
// buffer is primed so expected processed frames will need to account for
|
||||
// that. But considering that `WebAudioBypassOutputBuffering` is soon to be
|
||||
// default, we only add the bypass buffer enabled test here.
|
||||
blink::WebRuntimeFeatures::EnableFeatureFromString(
|
||||
"WebAudioBypassOutputBuffering", true);
|
||||
|
||||
float context_sample_rate = GetParam().value_or(kDefaultHardwareSampleRate);
|
||||
|
||||
// Use a non-default buffer size (kFakeScaledSinkBufferSize) to confirm it's
|
||||
// correctly applied in AudioDestination. The specific value of
|
||||
// kFakeScaledSinkBufferSize is unimportant, as the scaling calculations are
|
||||
// verified in RendererWebAudioDeviceImplBufferSizeTest. This test
|
||||
// focuses on ensuring the resampler is not created, allowing us to use the
|
||||
// original request frames (kFakeScaledSinkBufferSize) when calculating
|
||||
// expected_processed_frames.
|
||||
constexpr int kFakeScaledSinkBufferSize = 399;
|
||||
|
||||
ScopedTestingPlatformSupport<TestPlatform> platform;
|
||||
platform->CreateMockWebAudioDevice(context_sample_rate,
|
||||
kFakeScaledSinkBufferSize);
|
||||
EXPECT_CALL(platform->web_audio_device(), Start).Times(1);
|
||||
EXPECT_CALL(platform->web_audio_device(), Stop).Times(1);
|
||||
|
||||
scoped_refptr<AudioDestination> audio_destination = CreateAudioDestination(
|
||||
GetParam(),
|
||||
WebAudioLatencyHint(WebAudioLatencyHint::kCategoryInteractive));
|
||||
|
||||
EXPECT_EQ(nullptr, audio_destination->GetResamplerForTesting());
|
||||
|
||||
audio_destination->Start();
|
||||
audio_destination->Render(
|
||||
base::TimeDelta::Min(), base::TimeTicks::Now(), {},
|
||||
media::AudioBus::Create(
|
||||
Platform::Current()->AudioHardwareOutputChannels(),
|
||||
kFakeScaledSinkBufferSize)
|
||||
.get());
|
||||
|
||||
// Calculate the expected number of processed frames, rounded up to the render
|
||||
// quantum.
|
||||
int expected_processed_frames =
|
||||
RoundUpToRenderQuantum(audio_destination->FramesPerBuffer());
|
||||
EXPECT_EQ(callback_.frames_processed_, expected_processed_frames);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(/* no label */,
|
||||
AudioDestinationTest,
|
||||
::testing::Values(std::optional<float>(),
|
||||
@ -264,32 +344,28 @@ TEST_F(AudioDestinationTest, NoUnderrunsWithOutputBufferBypass) {
|
||||
blink::WebRuntimeFeatures::EnableFeatureFromString(
|
||||
"WebAudioBypassOutputBuffering", true);
|
||||
ScopedTestingPlatformSupport<TestPlatform> platform;
|
||||
platform->CreateMockWebAudioDevice(kDefaultHardwareSampleRate,
|
||||
kDefaultHardwareBufferSize);
|
||||
|
||||
const std::optional<float> sample_rate = 44100;
|
||||
const WebAudioLatencyHint latency_hint(
|
||||
WebAudioLatencyHint::kCategoryInteractive);
|
||||
const int channel_count = Platform::Current()->AudioHardwareOutputChannels();
|
||||
const size_t request_frames = Platform::Current()->AudioHardwareBufferSize();
|
||||
const WebAudioSinkDescriptor sink_descriptor(WebString::FromUTF8(""),
|
||||
kFrameToken);
|
||||
const int render_quantum_frames = 128;
|
||||
const std::optional<float> context_sample_rate = 44100;
|
||||
scoped_refptr<AudioDestination> audio_destination = CreateAudioDestination(
|
||||
context_sample_rate,
|
||||
WebAudioLatencyHint(WebAudioLatencyHint::kCategoryInteractive));
|
||||
|
||||
scoped_refptr<AudioDestination> destination = AudioDestination::Create(
|
||||
callback_, sink_descriptor, channel_count, latency_hint, sample_rate,
|
||||
render_quantum_frames);
|
||||
auto audio_bus = media::AudioBus::Create(
|
||||
Platform::Current()->AudioHardwareOutputChannels(),
|
||||
audio_destination->FramesPerBuffer());
|
||||
|
||||
auto audio_bus = media::AudioBus::Create(channel_count, request_frames);
|
||||
audio_destination->Start();
|
||||
audio_destination->Render(base::Milliseconds(90), base::TimeTicks::Now(),
|
||||
media::AudioGlitchInfo(), audio_bus.get());
|
||||
audio_destination->Stop();
|
||||
audio_destination->Render(base::Milliseconds(90), base::TimeTicks::Now(),
|
||||
media::AudioGlitchInfo(), audio_bus.get());
|
||||
|
||||
destination->Start();
|
||||
destination->Render(base::Milliseconds(90), base::TimeTicks::Now(),
|
||||
media::AudioGlitchInfo(), audio_bus.get());
|
||||
destination->Stop();
|
||||
destination->Render(base::Milliseconds(90), base::TimeTicks::Now(),
|
||||
media::AudioGlitchInfo(), audio_bus.get());
|
||||
|
||||
EXPECT_EQ((destination->GetPushPullFIFOStateForTest()).overflow_count,
|
||||
EXPECT_EQ((audio_destination->GetPushPullFIFOStateForTest()).overflow_count,
|
||||
unsigned{0});
|
||||
EXPECT_EQ((destination->GetPushPullFIFOStateForTest()).underflow_count,
|
||||
EXPECT_EQ((audio_destination->GetPushPullFIFOStateForTest()).underflow_count,
|
||||
unsigned{0});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user