910e62b5创建于 1月15日历史提交
// 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_bus.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