chromium/chromecast/media/audio/cast_audio_resampler_impl.cc

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

#include <algorithm>
#include <cmath>
#include <memory>
#include <vector>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "chromecast/media/api/cast_audio_resampler.h"
#include "media/base/sinc_resampler.h"

namespace chromecast {
namespace media {

namespace {

constexpr int kRequestFrames = 128;

class CastAudioResamplerImpl : public CastAudioResampler {
 public:
  CastAudioResamplerImpl(int channel_count,
                         int input_sample_rate,
                         int output_sample_rate)
      : channel_count_(channel_count) {
    DCHECK_GT(channel_count_, 0);
    const double io_sample_rate_ratio =
        static_cast<double>(input_sample_rate) / output_sample_rate;
    resamplers_.reserve(channel_count_);
    buffered_input_.channels.reserve(channel_count_);
    for (int c = 0; c < channel_count_; ++c) {
      resamplers_.push_back(std::make_unique<::media::SincResampler>(
          io_sample_rate_ratio, kRequestFrames,
          base::BindRepeating(&CastAudioResamplerImpl::ReadCallback,
                              base::Unretained(this), c)));
      buffered_input_.channels.push_back(
          std::make_unique<float[]>(kRequestFrames));
    }
  }

  ~CastAudioResamplerImpl() override = default;

  CastAudioResamplerImpl(const CastAudioResamplerImpl&) = delete;
  CastAudioResamplerImpl& operator=(const CastAudioResamplerImpl&) = delete;

 private:
  void ResampleOneChunk(std::vector<float>* output_channels) {
    int output_frame_offset = output_channels[0].size();
    int output_frames = resamplers_[0]->ChunkSize();
    for (int c = 0; c < channel_count_; ++c) {
      output_channels[c].resize(output_frame_offset + output_frames);
      resamplers_[c]->Resample(output_frames,
                               output_channels[c].data() + output_frame_offset);
    }
  }

  void ReadCallback(int channel_index, int frames, float* dest) {
    DCHECK_LE(buffered_input_.frames, frames);
    std::copy_n(buffered_input_.channels[channel_index].get(),
                buffered_input_.frames, dest);

    int frames_left = frames - buffered_input_.frames;
    int dest_offset = buffered_input_.frames;
    if (frames_left) {
      CopyCurrentInputTo(channel_index, frames_left, dest + dest_offset);
    }

    if (channel_index == channel_count_ - 1) {
      buffered_input_.frames = 0;
    }
  }

  void CopyCurrentInputTo(int channel_index, int frames_to_copy, float* dest) {
    DCHECK(current_input_.data);
    DCHECK_LE(current_input_.frame_offset + frames_to_copy,
              current_input_.frames);
    std::copy_n(current_input_.data + channel_index * current_input_.frames +
                    current_input_.frame_offset,
                frames_to_copy, dest);
    if (channel_index == channel_count_ - 1) {
      current_input_.frame_offset += frames_to_copy;
    }
  }

  // CastAudioResampler implementation:
  void Resample(const float* input,
                int num_frames,
                std::vector<float>* output_channels) override {
    current_input_.data = input;
    current_input_.frames = num_frames;
    current_input_.frame_offset = 0;

    while (buffered_input_.frames + current_input_.frames -
               current_input_.frame_offset >=
           kRequestFrames) {
      ResampleOneChunk(output_channels);
    }

    int frames_left = current_input_.frames - current_input_.frame_offset;
    DCHECK_LE(buffered_input_.frames + frames_left, kRequestFrames);
    for (int c = 0; c < channel_count_; ++c) {
      CopyCurrentInputTo(
          c, frames_left,
          buffered_input_.channels[c].get() + buffered_input_.frames);
    }
    buffered_input_.frames += frames_left;

    current_input_.data = nullptr;
    current_input_.frames = 0;
    current_input_.frame_offset = 0;
  }

  void Flush(std::vector<float>* output_channels) override {
    // TODO(kmackay) May need some additional flushing to get out data stored in
    // the SincResamplers.
    if (buffered_input_.frames == 0) {
      return;
    }

    for (int c = 0; c < channel_count_; ++c) {
      std::fill_n(buffered_input_.channels[c].get() + buffered_input_.frames,
                  kRequestFrames - buffered_input_.frames, 0);
    }
    buffered_input_.frames = kRequestFrames;
    while (buffered_input_.frames) {
      ResampleOneChunk(output_channels);
    }

    for (int c = 0; c < channel_count_; ++c) {
      resamplers_[c]->Flush();
    }
  }

  int BufferedInputFrames() const override {
    return buffered_input_.frames +
           std::round(resamplers_[0]->BufferedFrames());
  }

  const int channel_count_;

  std::vector<std::unique_ptr<::media::SincResampler>> resamplers_;

  struct InputBuffer {
    std::vector<std::unique_ptr<float[]>> channels;
    int frames = 0;
  } buffered_input_;

  struct InputData {
    const float* data = nullptr;
    int frames = 0;
    int frame_offset = 0;
  } current_input_;
};

}  // namespace

// static
std::unique_ptr<CastAudioResampler> CastAudioResampler::Create(
    int channel_count,
    int input_sample_rate,
    int output_sample_rate) {
  return std::make_unique<CastAudioResamplerImpl>(
      channel_count, input_sample_rate, output_sample_rate);
}

}  // namespace media
}  // namespace chromecast