/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/platform/audio/audio_destination.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "base/feature_list.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/trace_event.h"
#include "media/audio/audio_features.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_glitch_info.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/sinc_resampler.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_audio_latency_hint.h"
#include "third_party/blink/public/platform/web_audio_sink_descriptor.h"
#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
#include "third_party/blink/renderer/platform/audio/vector_math.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_media.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/text/format.h"

namespace blink {

namespace {

// This FIFO size of 16,384 was chosen based on the UMA data. It's the nearest
// multiple of 128 to 16,354 sample-frames, which represents 100% of the
// histogram from "WebAudio.AudioDestination.HardwareBufferSize".
// Although a buffer this big is atypical, some Android phones with a Bluetooth
// audio device report a large buffer size. This redundancy allows such device
// to play audio via Web Audio API.
constexpr uint32_t kFIFOSize = 128 * 128;

const char* DeviceStateToString(AudioDestination::DeviceState state) {
  switch (state) {
    case AudioDestination::kRunning:
      return "running";
    case AudioDestination::kPaused:
      return "paused";
    case AudioDestination::kStopped:
      return "stopped";
  }
}

bool BypassOutputBuffer() {
  if (RuntimeEnabledFeatures::WebAudioBypassOutputBufferingOptOutEnabled()) {
    return false;
  }
  return RuntimeEnabledFeatures::WebAudioBypassOutputBufferingEnabled();
}

}  // namespace

void AudioDestinationTraits::Destruct(const AudioDestination* destination) {
  if (destination->main_thread_task_runner_ &&
      !destination->main_thread_task_runner_->BelongsToCurrentThread()) {
    destination->main_thread_task_runner_->DeleteSoon(FROM_HERE, destination);
  } else {
    delete destination;
  }
}

scoped_refptr<AudioDestination> AudioDestination::Create(
    AudioIOCallback& callback,
    const WebAudioSinkDescriptor& sink_descriptor,
    unsigned number_of_output_channels,
    const WebAudioLatencyHint& latency_hint,
    std::optional<float> context_sample_rate,
    unsigned render_quantum_frames) {
  TRACE_EVENT0("webaudio", "AudioDestination::Create");
  auto destination = base::AdoptRef(new AudioDestination(
      callback, sink_descriptor, number_of_output_channels, latency_hint,
      context_sample_rate, render_quantum_frames));
  if (destination->IsBusAllocationFailed()) {
    return nullptr;
  }
  return destination;
}

AudioDestination::~AudioDestination() {
  Stop();
}

int AudioDestination::Render(base::TimeDelta delay,
                             base::TimeTicks delay_timestamp,
                             const media::AudioGlitchInfo& glitch_info,
                             media::AudioBus* dest) {
  const base::TimeTicks start_timestamp = base::TimeTicks::Now();
  const uint32_t number_of_frames = dest->frames();

  TRACE_EVENT("webaudio", "AudioDestination::Render", "frames",
              number_of_frames, "playout_delay (ms)", delay.InMillisecondsF(),
              "delay_timestamp (ms)",
              (delay_timestamp - base::TimeTicks()).InMillisecondsF());
  glitch_info.MaybeAddTraceEvent();

  CHECK_EQ(static_cast<size_t>(dest->channels()), number_of_output_channels_);
  CHECK_EQ(number_of_frames, callback_buffer_size_);

  if (!is_latency_metric_collected_ && delay.is_positive()) {
    // With the advanced distribution profile for a Bluetooth device
    // (potentially devices with the largest latency), the known latency is
    // around 100 ~ 150ms. Using a "linear" histogram where all buckets are
    // exactly the same size (2ms).
    base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
        "WebAudio.AudioDestination.HardwareOutputLatency", 0, 200, 100,
        base::HistogramBase::kUmaTargetedHistogramFlag);
    histogram->Add(base::saturated_cast<int32_t>(delay.InMillisecondsF()));
    is_latency_metric_collected_ = true;
  }

  // Note that this method is called by AudioDeviceThread. If FIFO is not ready,
  // or the requested render size is greater than FIFO size return here.
  // (crbug.com/692423)
  if (!fifo_ || fifo_->length() < number_of_frames) {
    TRACE_EVENT_INSTANT(
        "webaudio",
        "AudioDestination::Render - FIFO not ready or the size is too small",
        "fifo length", fifo_ ? fifo_->length() : 0);
    return 0;
  }

  // Associate the destination data array with the output bus.
  for (unsigned i = 0; i < number_of_output_channels_; ++i) {
    output_bus_->SetChannelMemory(i, dest->channel(i).first(number_of_frames));
  }

  absl::Cleanup cleanup_and_report_metrics = [this, start_timestamp] {
    uma_reporter_.AddRenderDuration(/*duration=*/base::TimeTicks::Now() -
                                    start_timestamp);
    for (unsigned i = 0; i < number_of_output_channels_; ++i) {
      output_bus_->SetChannelMemory(i, base::span<float>());
    }
  };

  if (is_output_buffer_bypassed_) {
    // Reset the underrun flag at the start of rendering to ensure we only
    // catch underruns occurring during this active session's lifetime.
    is_state_change_underrun_in_bypass_mode_.store(false,
                                                   std::memory_order_relaxed);
    // Fill the FIFO if necessary.
    const uint32_t frames_available = fifo_->FramesAvailable();
    const uint32_t frames_to_render = number_of_frames > frames_available
                                          ? number_of_frames - frames_available
                                          : 0;
    if (worklet_task_runner_) {
      // Use the dual-thread rendering if the AudioWorklet is activated.
      output_buffer_bypass_wait_event_.Reset();
      const uint32_t current_session_id =
          session_id_.load(std::memory_order_relaxed);
      const bool posted_successfully = PostCrossThreadTask(
          *worklet_task_runner_, FROM_HERE,
          CrossThreadBindOnce(
              &AudioDestination::RequestRenderWait, WrapRefCounted(this),
              number_of_frames, frames_to_render, delay, delay_timestamp,
              glitch_info, /*request_timestamp=*/base::TimeTicks::Now(),
              current_session_id, has_unexpected_fifo_underrun_occurred_));
      has_unexpected_fifo_underrun_occurred_ = false;

      if (posted_successfully) {
        TRACE_EVENT0("webaudio", "AudioDestination::Render waiting");
        base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;
        // In bypass mode with a worklet, the audio callback waits for either:
        //   1) RequestRenderWait() to finish and signal
        //      output_buffer_bypass_wait_event_, or
        //   2) Stop() to signal output_buffer_bypass_stop_event_.
        // The separate stop event avoids a shutdown race with
        // output_buffer_bypass_wait_event_.Reset() in Render().
        //
        // WaitableEvents should generally not be allowed on the real-time
        // audio threads. In particular, no other code executed on the worklet
        // task runner thread should be using WaitableEvent. The below should
        // be the only blocking wait in AudioDestination, and should only be
        // executed when the kWebAudioBypassOutputBuffering flag is enabled,
        // for testing output latency differences when the output buffer is
        // bypassed.
        //
        // As long as the above is true, it is not possible to deadlock or
        // have both threads waiting on each other. There is, however, no
        // guarantee that the task runner will finish within the real-time
        // budget.
        base::WaitableEvent* events[] = {&output_buffer_bypass_wait_event_,
                                         &output_buffer_bypass_stop_event_};
        size_t signaled_index = base::WaitableEvent::WaitMany(events);
        if (signaled_index == 1 ||
            current_session_id !=
                session_id_.load(std::memory_order_acquire)) {
          // The stop event was signaled (e.g., Pause() or Stop() was called
          // on the main thread) or the session changed during wait. We treat
          // this as an expected state change underrun.
          is_state_change_underrun_in_bypass_mode_.store(
              true, std::memory_order_relaxed);
        }
      } else {
        // If posting the task fails, it means the worklet is shutting down
        // or the task runner is invalid. This will cause the audio thread
        // to wake up from WaitMany via the stop event or timeout and find an
        // empty FIFO. We set the underrun flag to true to allow it to bypass
        // the CHECK.
        is_state_change_underrun_in_bypass_mode_.store(
            true, std::memory_order_relaxed);
      }
    } else {
      // Otherwise use the single-thread rendering.
      if (!RequestRender(
              number_of_frames, frames_to_render, delay, delay_timestamp,
              glitch_info,
              /*request_timestamp=*/base::TimeTicks::Now(),
              session_id_.load(std::memory_order_relaxed))) {
        is_state_change_underrun_in_bypass_mode_.store(
            true, std::memory_order_relaxed);
      }
    }

    const uint32_t frames_after_render = fifo_->FramesAvailable();
    if (frames_after_render < number_of_frames) {
      // In bypass mode, we expect to have rendered enough frames. If we didn't,
      // it might be due to transient CPU overload or complex race conditions
      // during rapid hardware suspend/resume cycles (see crbug.com/528653884).
      // We demote this to DCHECK to avoid crashing production users, and
      // recover by zeroing the output and pulling whatever is left.
      DCHECK(is_state_change_underrun_in_bypass_mode_.load(
          std::memory_order_relaxed));
      if (!is_state_change_underrun_in_bypass_mode_.load(
              std::memory_order_relaxed)) {
        has_unexpected_fifo_underrun_occurred_ = true;
      }
      output_bus_->Zero();
      fifo_->Pull(output_bus_.get(), frames_after_render);
      return frames_after_render;
    }

    fifo_->Pull(output_bus_.get(), number_of_frames);
    return number_of_frames;
  }

  // Fill the FIFO.
  if (worklet_task_runner_) {
    // Use the dual-thread rendering if the AudioWorklet is activated.
    auto result = fifo_->PullAndUpdateEarmarkedFrames(output_bus_.get(),
                                                      number_of_frames);
    // The audio that we just pulled from the fifo will be played before the
    // audio that we are about to request, so we add that duration to the
    // delay of the audio we request. Note that it doesn't matter if there was
    // a fifo underrun, the delay will be the same either way.
    delay += media::AudioTimestampHelper::FramesToTime(
        number_of_frames, web_audio_device_->SampleRate());

    media::AudioGlitchInfo combined_glitch_info = glitch_info;
    bool has_fifo_underrun_occurred = false;
    if (result.frames_provided < number_of_frames) {
      media::AudioGlitchInfo underrun{
          // FIFO contains audio at the output device sample rate.
          .duration = media::AudioTimestampHelper::FramesToTime(
              number_of_frames - result.frames_provided,
              web_audio_device_->SampleRate()),
          .count = 1};
      underrun.MaybeAddTraceEvent();
      combined_glitch_info += underrun;
      has_fifo_underrun_occurred = true;
    }

    PostCrossThreadTask(
        *worklet_task_runner_, FROM_HERE,
        CrossThreadBindOnce(IgnoreResult(&AudioDestination::RequestRender),
                            WrapRefCounted(this), number_of_frames,
                            result.frames_to_render, delay, delay_timestamp,
                            combined_glitch_info,
                            /*request_timestamp=*/base::TimeTicks::Now(),
                            session_id_.load(std::memory_order_relaxed),
                            /*has_unexpected_fifo_underrun_occurred=*/false,
                            has_fifo_underrun_occurred));
  } else {
    // Otherwise use the single-thread rendering.
    const size_t frames_to_render =
        fifo_->Pull(output_bus_.get(), number_of_frames);
    // The audio that we just pulled from the fifo will be played before the
    // audio that we are about to request, so we add that duration to the
    // delay of the audio we request.
    delay += media::AudioTimestampHelper::FramesToTime(
        number_of_frames, web_audio_device_->SampleRate());
    RequestRender(number_of_frames, frames_to_render, delay, delay_timestamp,
                  glitch_info, /*request_timestamp=*/base::TimeTicks::Now(),
                  session_id_.load(std::memory_order_relaxed));
  }
  return number_of_frames;
}

void AudioDestination::OnRenderError() {
  DCHECK(IsMainThread());

  callback_->OnRenderError();
}

void AudioDestination::Start() {
  DCHECK(IsMainThread());
  TRACE_EVENT0("webaudio", "AudioDestination::Start");
  SendLogMessage(__func__, "");

  if (device_state_ != DeviceState::kStopped) {
    return;
  }
  output_buffer_bypass_stop_event_.Reset();
  SetDeviceState(DeviceState::kRunning);
  web_audio_device_->Start();
}

void AudioDestination::Stop() {
  DCHECK(IsMainThread());
  TRACE_EVENT0("webaudio", "AudioDestination::Stop");
  SendLogMessage(__func__, "");

  if (device_state_ == DeviceState::kStopped) {
    return;
  }

  // Signal before WebAudioDevice::Stop() so any Render() callback already
  // blocked in WaitMany() can exit promptly. This uses a manual-reset event
  // so the stop wakeup cannot be lost to a concurrent Reset() of
  // output_buffer_bypass_wait_event_.
  output_buffer_bypass_stop_event_.Signal();

  web_audio_device_->Stop();

  // Resetting `worklet_task_runner_` here is safe because
  // AudioDestination::Render() won't be called after WebAudioDevice::Stop()
  // call above.
  worklet_task_runner_ = nullptr;

  SetDeviceState(DeviceState::kStopped);
  output_buffer_bypass_stop_event_.Reset();
}

void AudioDestination::Pause() {
  DCHECK(IsMainThread());
  TRACE_EVENT0("webaudio", "AudioDestination::Pause");
  SendLogMessage(__func__, "");

  if (device_state_ != DeviceState::kRunning) {
    return;
  }
  output_buffer_bypass_stop_event_.Signal();
  web_audio_device_->Pause();
  SetDeviceState(DeviceState::kPaused);
}

void AudioDestination::Resume() {
  DCHECK(IsMainThread());
  TRACE_EVENT0("webaudio", "AudioDestination::Resume");
  SendLogMessage(__func__, "");

  if (device_state_ != DeviceState::kPaused) {
    return;
  }
  output_buffer_bypass_stop_event_.Reset();
  SetDeviceState(DeviceState::kRunning);
  // Signal the wait event to unblock the audio thread if it was waiting on the
  // old session. This prevents hangs if Pause() and Resume() happened quickly.
  output_buffer_bypass_wait_event_.Signal();
  web_audio_device_->Resume();
}

void AudioDestination::SetWorkletTaskRunner(
    scoped_refptr<base::SingleThreadTaskRunner> worklet_task_runner) {
  DCHECK(IsMainThread());
  TRACE_EVENT0("webaudio", "AudioDestination::SetWorkletTaskRunner");

  if (worklet_task_runner_) {
    DCHECK_EQ(worklet_task_runner_, worklet_task_runner);
    return;
  }

  // The dual-thread rendering kicks off, so update the earmark frames
  // accordingly.
  fifo_->SetEarmarkedFrames(callback_buffer_size_);
  worklet_task_runner_ = std::move(worklet_task_runner);

  uma_reporter_.UpdateMetricNameForDualThreadMode();
}

void AudioDestination::StartWithWorkletTaskRunner(
    scoped_refptr<base::SingleThreadTaskRunner> worklet_task_runner) {
  DCHECK(IsMainThread());
  TRACE_EVENT0("webaudio", "AudioDestination::StartWithWorkletTaskRunner");
  SendLogMessage(__func__, "");

  if (device_state_ != DeviceState::kStopped) {
    return;
  }

  SetWorkletTaskRunner(worklet_task_runner);
  SetDeviceState(DeviceState::kRunning);
  web_audio_device_->Start();
}

bool AudioDestination::IsPlaying() const {
  DCHECK(IsMainThread());
  return device_state_ == DeviceState::kRunning;
}

double AudioDestination::SampleRate() const {
  return context_sample_rate_;
}

uint32_t AudioDestination::CallbackBufferSize() const {
  return callback_buffer_size_;
}

int AudioDestination::FramesPerBuffer() const {
  DCHECK(IsMainThread());
  return web_audio_device_->FramesPerBuffer();
}

base::TimeDelta AudioDestination::GetPlatformBufferDuration() const {
  DCHECK(IsMainThread());
  return media::AudioTimestampHelper::FramesToTime(
      web_audio_device_->FramesPerBuffer(), web_audio_device_->SampleRate());
}

uint32_t AudioDestination::MaxChannelCount() const {
  return web_audio_device_->MaxChannelCount();
}

void AudioDestination::SetDetectSilence(bool detect_silence) {
  DCHECK(IsMainThread());
  TRACE_EVENT1("webaudio", "AudioDestination::SetDetectSilence",
               "detect_silence", detect_silence);
  SendLogMessage(
      __func__,
      StrCat({"({detect_silence=",
              String::Number(static_cast<int>(detect_silence)), "})"}));

  web_audio_device_->SetDetectSilence(detect_silence);
}

AudioDestination::AudioDestination(
    AudioIOCallback& callback,
    const WebAudioSinkDescriptor& sink_descriptor,
    unsigned number_of_output_channels,
    const WebAudioLatencyHint& latency_hint,
    std::optional<float> context_sample_rate,
    unsigned render_quantum_frames)
    : web_audio_device_(
          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),
      number_of_output_channels_(number_of_output_channels),
      render_quantum_frames_(render_quantum_frames),
      context_sample_rate_(
          context_sample_rate.has_value()
              ? context_sample_rate.value()
              : (web_audio_device_ ? web_audio_device_->SampleRate() : 0)),
      fifo_(PushPullFIFO::TryCreate(
          number_of_output_channels,
          std::max(kFIFOSize, callback_buffer_size_ + render_quantum_frames),
          render_quantum_frames)),
      output_bus_(AudioBus::Create(number_of_output_channels,
                                   render_quantum_frames,
                                   false)),
      render_bus_(
          AudioBus::TryCreate(number_of_output_channels,
                              render_quantum_frames)),
      callback_(callback),
      uma_reporter_(
          AudioDestinationUmaReporter(latency_hint,
                                      callback_buffer_size_,
                                      web_audio_device_->SampleRate())),
      is_output_buffer_bypassed_(BypassOutputBuffer()),
      main_thread_task_runner_(
          base::SingleThreadTaskRunner::HasCurrentDefault()
              ? base::SingleThreadTaskRunner::GetCurrentDefault()
              : nullptr) {
  CHECK(web_audio_device_);

  // If any of the critical audio buses or FIFOs failed to allocate, exit early.
  // Exiting a C++ constructor early via a bare return is standard; the
  // factory method AudioDestination::Create() will detect this via
  // IsBusAllocationFailed() and return nullptr, freeing this instance.
  if (IsBusAllocationFailed()) {
    return;
  }

  SendLogMessage(__func__,
                 StrCat({"({output_channels=",
                         String::Number(number_of_output_channels), "})"}));
  SendLogMessage(
      __func__,
      StrCat({"=> (FIFO size=", String::Number(fifo_->length()), " bytes)"}));

  SendLogMessage(__func__,
                 StrCat({"=> (device callback buffer size=",
                         String::Number(callback_buffer_size_), " frames)"}));
  SendLogMessage(__func__, Format("=> (device sample rate={:.0f} Hz)",
                                  web_audio_device_->SampleRate()));
  if (is_output_buffer_bypassed_) {
    SendLogMessage(__func__, "Output buffer bypass: yes");
  } else {
    SendLogMessage(__func__, "Output buffer bypass: no");
  }

  TRACE_EVENT1("webaudio", "AudioDestination::AudioDestination",
               "sink information",
               audio_utilities::GetSinkInfoForTracing(
                   sink_descriptor, latency_hint,
                   number_of_output_channels, web_audio_device_->SampleRate(),
                   callback_buffer_size_));

  metric_reporter_.Initialize(
      callback_buffer_size_, web_audio_device_->SampleRate());

  if (!is_output_buffer_bypassed_) {
    // Primes the FIFO for the given callback buffer size. This is to prevent
    // first FIFO pulls from causing "underflow" errors.
    const unsigned priming_render_quanta =
        ceil(callback_buffer_size_ / static_cast<float>(render_quantum_frames));
    for (unsigned i = 0; i < priming_render_quanta; ++i) {
      fifo_->Push(render_bus_.get());
    }
  }

  double scale_factor = 1.0;

  if (!base::FeatureList::IsEnabled(
          ::features::kWebAudioRemoveAudioDestinationResampler) &&
      context_sample_rate_ != web_audio_device_->SampleRate()) {
    scale_factor = context_sample_rate_ / web_audio_device_->SampleRate();
    SendLogMessage(
        __func__,
        Format("=> (resampling from {:.0f} Hz to {:.0f} Hz)",
               context_sample_rate.value(), web_audio_device_->SampleRate()));

    // SincResampler requires at least `kMinRequestSize` input samples to
    // perform interpolation. For smaller render quanta this creates an
    // algorithmic latency floor because the graph must buffer at least
    // `resampler_request_frames` before producing resampled output.
    const size_t resampler_request_frames = audio_utilities::RoundUpToMultiple(
        media::SincResampler::kMinRequestSize, render_quantum_frames);
    const size_t num_quanta = resampler_request_frames / render_quantum_frames;

    if (num_quanta > 1) {
      resampler_render_bus_ = AudioBus::Create(number_of_output_channels,
                                               render_quantum_frames, false);
    }

    resampler_ = std::make_unique<MediaMultiChannelResampler>(
        number_of_output_channels, scale_factor, resampler_request_frames,
        CrossThreadBindRepeating(&AudioDestination::ProvideResamplerInput,
                                 CrossThreadUnretained(this)));
    resampler_bus_ =
        media::AudioBus::CreateWrapper(render_bus_->NumberOfChannels());
    resampler_bus_->set_frames(render_bus_->length());
    for (unsigned int i = 0; i < render_bus_->NumberOfChannels(); ++i) {
      resampler_bus_->SetChannelData(i, render_bus_->Channel(i)->MutableSpan());
    }
  } else {
    SendLogMessage(
        __func__,
        Format("=> (no resampling: context sample rate set to {:.0f} Hz)",
               context_sample_rate_));
  }

  // Record the sizes if we successfully created an output device.
  // Histogram for audioHardwareBufferSize
  base::UmaHistogramSparse(
      "WebAudio.AudioDestination.HardwareBufferSize",
      static_cast<int>(Platform::Current()->AudioHardwareBufferSize()));

  // Histogram for the actual callback size used.  Typically, this is the same
  // as audioHardwareBufferSize, but can be adjusted depending on some
  // heuristics below.
  base::UmaHistogramSparse("WebAudio.AudioDestination.CallbackBufferSize",
                           callback_buffer_size_);

  base::UmaHistogramSparse("WebAudio.AudioContext.HardwareSampleRate",
                           web_audio_device_->SampleRate());

  // Record the selected sample rate and ratio if the sampleRate was given.  The
  // ratio is recorded as a percentage, rounded to the nearest percent.
  if (context_sample_rate.has_value()) {
    // The actual supplied `context_sample_rate` is probably a small set
    // including 44100, 48000, 22050, and 2400 Hz.  Other valid values range
    // from 3000 to 384000 Hz, but are not expected to be used much.
    base::UmaHistogramSparse("WebAudio.AudioContextOptions.sampleRate",
                             context_sample_rate.value());
    // From the expected values above and the common HW sample rates, we expect
    // the most common ratios to be the set 0.5, 44100/48000, and 48000/44100.
    // Other values are possible but seem unlikely.
    base::UmaHistogramSparse("WebAudio.AudioContextOptions.sampleRateRatio",
                             static_cast<int32_t>(100.0 * scale_factor + 0.5));
  }
}

void AudioDestination::SetDeviceState(DeviceState state) {
  DCHECK(IsMainThread());
  base::AutoLock locker(device_state_lock_);

  if (device_state_ != state) {
    device_state_ = state;
    session_id_.fetch_add(1, std::memory_order_release);
  }
}

void AudioDestination::RequestRenderWait(
    size_t frames_requested,
    size_t frames_to_render,
    base::TimeDelta delay,
    base::TimeTicks delay_timestamp,
    const media::AudioGlitchInfo& glitch_info,
    base::TimeTicks request_timestamp,
    uint32_t session_id,
    bool has_unexpected_fifo_underrun_occurred) {
  bool success = false;
  if (session_id == session_id_.load(std::memory_order_acquire)) {
    success = RequestRender(frames_requested, frames_to_render, delay,
                            delay_timestamp, glitch_info, request_timestamp,
                            session_id, has_unexpected_fifo_underrun_occurred);
  }

  // We check the session ID again because the session might have changed
  // concurrently (e.g. Pause() called on the main thread) while
  // RequestRender was executing. If it changed, we must treat this as a
  // state-change underrun and avoid signaling to prevent waking up subsequent
  // sessions early.
  if (session_id != session_id_.load(std::memory_order_acquire)) {
    is_state_change_underrun_in_bypass_mode_.store(true,
                                                   std::memory_order_relaxed);
  } else {
    if (!success) {
      is_state_change_underrun_in_bypass_mode_.store(
          true, std::memory_order_relaxed);
    }
    output_buffer_bypass_wait_event_.Signal();
  }
}

bool AudioDestination::RequestRender(
    size_t frames_requested,
    size_t frames_to_render,
    base::TimeDelta delay,
    base::TimeTicks delay_timestamp,
    const media::AudioGlitchInfo& glitch_info,
    base::TimeTicks request_timestamp,
    uint32_t session_id,
    bool has_unexpected_fifo_underrun_occurred,
    bool has_fifo_underrun_occurred) {
  base::TimeTicks start_timestamp = base::TimeTicks::Now();
  uma_reporter_.AddRequestRenderGapDuration(start_timestamp -
                                            request_timestamp);
  base::AutoTryLock locker(device_state_lock_);

  // The state might be changing by ::Stop() call. If the state is locked, do
  // not touch the below.
  if (!locker.is_acquired()) {
    return false;
  }

  // Discard tasks from previous play/pause or suspend/resume sessions.
  if (session_id != session_id_.load(std::memory_order_relaxed)) {
    return false;
  }

  if (device_state_ != DeviceState::kRunning) {
    return false;
  }

  TRACE_EVENT("webaudio", "AudioDestination::RequestRender", "frames_requested",
              frames_requested, "frames_to_render", frames_to_render,
              "delay_timestamp (ms)",
              (delay_timestamp - base::TimeTicks()).InMillisecondsF(),
              "playout_delay (ms)", delay.InMillisecondsF(), "delay (frames)",
              fifo_->FramesAvailable());


  metric_reporter_.BeginTrace();

  if (frames_elapsed_ == 0) {
    SendLogMessage(__func__, "=> (rendering is now alive)");
  }

  // FIFO contains audio at the output device sample rate.
  base::TimeDelta fifo_delay = media::AudioTimestampHelper::FramesToTime(
      fifo_->FramesAvailable(), web_audio_device_->SampleRate());
  uma_reporter_.AddFifoDelay(fifo_delay);
  if (has_unexpected_fifo_underrun_occurred) {
    uma_reporter_.IncreaseUnexpectedFifoUnderrunCount();
  }
  if (has_fifo_underrun_occurred) {
    uma_reporter_.IncreaseFifoUnderrunCount();
  }

  delay_to_report_ = delay + fifo_delay;
  glitch_info_to_report_.Add(glitch_info);

  output_position_.position =
      frames_elapsed_ / static_cast<double>(web_audio_device_->SampleRate()) -
      delay.InSecondsF();
  output_position_.timestamp =
      (delay_timestamp - base::TimeTicks()).InSecondsF();
  output_position_.hardware_output_latency = delay.InSecondsF();
  const base::TimeTicks callback_request = base::TimeTicks::Now();

  for (size_t pushed_frames = 0; pushed_frames < frames_to_render;
       pushed_frames += render_quantum_frames_) {
    // If platform buffer is more than two times longer than
    // `RenderQuantumFrames` we do not want output position to get stuck so we
    // promote it using the elapsed time from the moment it was initially
    // obtained.
    if (callback_buffer_size_ > render_quantum_frames_ * 2) {
      const double delta =
          (base::TimeTicks::Now() - callback_request).InSecondsF();
      output_position_.position += delta;
      output_position_.timestamp += delta;
    }

    // Some implementations give only rough estimation of `delay` so
    // we might have negative estimation `output_position_` value.
    if (output_position_.position < 0.0) {
      output_position_.position = 0.0;
    }

    // Process WebAudio graph and push the rendered output to FIFO.
    if (resampler_) {
      resampler_->ResampleInternal(render_quantum_frames_,
                                   resampler_bus_.get());
    } else {
      // Process WebAudio graph and push the rendered output to FIFO.
      PullFromCallback(render_bus_.get(), delay_to_report_);
    }

    fifo_->Push(render_bus_.get());
  }

  frames_elapsed_ += frames_requested;

  uma_reporter_.Report();
  metric_reporter_.EndTrace();
  uma_reporter_.AddRequestRenderDuration(/*duration=*/base::TimeTicks::Now() -
                                         start_timestamp);
  return true;
}

void AudioDestination::ProvideResamplerInput(int resampler_frame_delay,
                                             AudioBus* dest) {
  // Resampler delay is audio frames at the context sample rate, before
  // resampling.
  TRACE_EVENT("webaudio", "AudioDestination::ProvideResamplerInput",
              "delay (frames)", resampler_frame_delay);

  if (dest->length() == render_quantum_frames_) {
    auto adjusted_delay =
        delay_to_report_ + media::AudioTimestampHelper::FramesToTime(
                               resampler_frame_delay, context_sample_rate_);
    PullFromCallback(dest, adjusted_delay);
    return;
  }

  const unsigned total_frames = dest->length();
  CHECK_EQ(total_frames % render_quantum_frames_, 0u);
  for (unsigned offset = 0; offset < total_frames;
       offset += render_quantum_frames_) {
    for (unsigned c = 0; c < dest->NumberOfChannels(); ++c) {
      resampler_render_bus_->SetChannelMemory(
          c, dest->Channel(c)->MutableSpan().subspan(offset,
                                                     render_quantum_frames_));
    }
    auto adjusted_delay =
        delay_to_report_ +
        media::AudioTimestampHelper::FramesToTime(
            resampler_frame_delay + offset, context_sample_rate_);
    PullFromCallback(resampler_render_bus_.get(), adjusted_delay);
  }
  // Reset channel pointers to prevent holding dangling references to `dest`.
  for (unsigned c = 0; c < dest->NumberOfChannels(); ++c) {
    resampler_render_bus_->SetChannelMemory(c, base::span<float>());
  }
}

void AudioDestination::PullFromCallback(AudioBus* destination_bus,
                                        base::TimeDelta delay) {
  uma_reporter_.AddTotalPlayoutDelay(delay);
  callback_->Render(destination_bus, render_quantum_frames_, output_position_,
                    metric_reporter_.GetMetric(), delay,
                    glitch_info_to_report_.GetAndReset());
}

media::OutputDeviceStatus AudioDestination::MaybeCreateSinkAndGetStatus() {
  TRACE_EVENT0("webaudio", "AudioDestination::MaybeCreateSinkAndGetStatus");
  return web_audio_device_->MaybeCreateSinkAndGetStatus();
}

size_t AudioDestination::FramesElapsed() const {
  DCHECK(IsMainThread());
  DCHECK(!IsPlaying());
  return frames_elapsed_;
}

void AudioDestination::TransferElapsedFramesFrom(
    const scoped_refptr<AudioDestination> previous_platform_destination) {
  DCHECK(IsMainThread());
  DCHECK(!IsPlaying() && !previous_platform_destination->IsPlaying());
  frames_elapsed_ += previous_platform_destination->FramesElapsed();
}

void AudioDestination::SendLogMessage(const String& function_name,
                                      const String& message) const {
  WebRtcLogMessage(StrCat({"[WA]AD::", function_name, " ", message,
                           " [state=", DeviceStateToString(device_state_), "]"})
                       .Utf8());
}

}  // namespace blink
