chromium/chromecast/media/audio/playback_rate_shifter.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromecast/media/audio/playback_rate_shifter.h"

#include <algorithm>
#include <cmath>
#include <utility>

#include "base/check.h"
#include "base/time/time.h"
#include "media/base/audio_buffer.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_parameters.h"
#include "media/filters/audio_renderer_algorithm.h"

namespace chromecast {
namespace media {

namespace {
constexpr int kMaxChannels = 32;
constexpr double kPlaybackRateEpsilon = 0.005;
constexpr int kOutputBufferSize = 4096;
}  // namespace

PlaybackRateShifter::PlaybackRateShifter(AudioProvider* provider,
                                         ::media::ChannelLayout channel_layout,
                                         int num_channels,
                                         int sample_rate,
                                         int request_size)
    : provider_(provider),
      channel_layout_(channel_layout),
      num_channels_(num_channels),
      sample_rate_(sample_rate),
      request_size_(request_size),
      audio_buffer_pool_(
          base::MakeRefCounted<::media::AudioBufferMemoryPool>()) {
  DCHECK(provider_);
}

PlaybackRateShifter::~PlaybackRateShifter() = default;

double PlaybackRateShifter::BufferedFrames() const {
  if (rate_shifter_) {
    return rate_shifter_->DelayInFrames(playback_rate_);
  }
  return 0;
}

void PlaybackRateShifter::SetPlaybackRate(double rate) {
  if (std::fabs(rate - 1.0) < kPlaybackRateEpsilon) {
    rate = 1.0;
  }

  if (rate == playback_rate_) {
    return;
  }

  if (rate != 1.0) {
    if (!rate_shifter_) {
      rate_shifter_ =
          std::make_unique<::media::AudioRendererAlgorithm>(&media_log_);
      ::media::AudioParameters parameters(
          ::media::AudioParameters::AUDIO_PCM_LINEAR,
          {channel_layout_, static_cast<int>(num_channels_)}, sample_rate_,
          request_size_);
      rate_shifter_->Initialize(parameters, false /* is_encrypted */);
    }

    if (!rate_shifter_output_) {
      rate_shifter_output_ =
          ::media::AudioBus::Create(num_channels_, kOutputBufferSize);
    }
  }

  playback_rate_ = rate;
}

int PlaybackRateShifter::FillFrames(int num_frames,
                                    int64_t playout_timestamp,
                                    float* const* channel_data) {
  if (!rate_shifter_ ||
      (playback_rate_ == 1.0 && rate_shifter_->BufferedFrames() == 0)) {
    return provider_->FillFrames(num_frames, playout_timestamp, channel_data);
  }

  if (playback_rate_ == 1.0) {
    return DrainBufferedData(num_frames, playout_timestamp, channel_data);
  }

  int total_filled = 0;
  while (total_filled < num_frames) {
    int amount = std::min(num_frames - total_filled, kOutputBufferSize);
    int filled = rate_shifter_->FillBuffer(rate_shifter_output_.get(), 0,
                                           amount, playback_rate_);

    for (size_t c = 0; c < num_channels_; ++c) {
      std::copy_n(rate_shifter_output_->channel(c), amount,
                  channel_data[c] + total_filled);
    }
    total_filled += filled;

    if (filled != amount) {
      // Get more data and queue it in the rate shifter.
      auto buffer = ::media::AudioBuffer::CreateBuffer(
          ::media::SampleFormat::kSampleFormatPlanarF32, channel_layout_,
          num_channels_, sample_rate_, request_size_, audio_buffer_pool_);
      int new_fill = provider_->FillFrames(
          request_size_,
          playout_timestamp +
              FramesToMicroseconds(total_filled + BufferedFrames()),
          const_cast<float**>(
              reinterpret_cast<float* const*>(buffer->channel_data().data())));
      if (new_fill == 0) {
        break;
      }
      buffer->TrimEnd(request_size_ - new_fill);
      rate_shifter_->EnqueueBuffer(std::move(buffer));
    }
  }
  return total_filled;
}

int PlaybackRateShifter::DrainBufferedData(int num_frames,
                                           int64_t playout_timestamp,
                                           float* const* channel_data) {
  // Drain buffered data from rate shifter.
  DCHECK_EQ(playback_rate_, 1.0);

  int filled = 0;
  while (filled < num_frames) {
    int amount = std::min(num_frames - filled, kOutputBufferSize);
    int to_copy = rate_shifter_->FillBuffer(rate_shifter_output_.get(), 0,
                                            amount, playback_rate_);
    for (size_t c = 0; c < num_channels_; ++c) {
      std::copy_n(rate_shifter_output_->channel(c), to_copy,
                  channel_data[c] + filled);
    }
    filled += to_copy;

    if (to_copy < amount) {
      break;
    }
  }

  if (filled < num_frames) {
    // Now there is no data buffered in the rate shifter.
    float* fill_channel_data[kMaxChannels];
    for (size_t c = 0; c < num_channels_; ++c) {
      fill_channel_data[c] = channel_data[c] + filled;
    }
    int64_t timestamp = playout_timestamp + FramesToMicroseconds(filled);
    filled += provider_->FillFrames(num_frames - filled, timestamp,
                                    fill_channel_data);
  }
  return filled;
}

size_t PlaybackRateShifter::num_channels() const {
  return num_channels_;
}

int PlaybackRateShifter::sample_rate() const {
  return sample_rate_;
}

int64_t PlaybackRateShifter::FramesToMicroseconds(double frames) {
  return frames * base::Time::kMicrosecondsPerSecond / sample_rate_;
}

}  // namespace media
}  // namespace chromecast