Commit 168891af authored by Ken MacKay's avatar Ken MacKay Committed by Commit Bot

[Chromecast] Create noncopying audio clock simulator

Changes the effective clock rate by interpolating samples online; does
not require memory allocation. Intended to replace our AudioResampler.

Test: cast_media_unittests
Change-Id: I6563672f936c0d7e9cdf687f42fb42f1bbec1863
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1974600
Commit-Queue: Kenneth MacKay <kmackay@chromium.org>
Reviewed-by: default avatarYuchen Liu <yucliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#726628}
parent 9e2e55c0
...@@ -24,6 +24,12 @@ cast_source_set("audio_io_thread") { ...@@ -24,6 +24,12 @@ cast_source_set("audio_io_thread") {
] ]
} }
cast_source_set("audio_provider") {
sources = [
"audio_provider.h",
]
}
cast_source_set("fader") { cast_source_set("fader") {
sources = [ sources = [
"audio_fader.cc", "audio_fader.cc",
...@@ -45,6 +51,24 @@ cast_source_set("fader") { ...@@ -45,6 +51,24 @@ cast_source_set("fader") {
cflags = [ "-ffast-math" ] cflags = [ "-ffast-math" ]
} }
cast_source_set("audio_clock_simulator") {
sources = [
"audio_clock_simulator.cc",
"audio_clock_simulator.h",
]
deps = [
":audio_provider",
"//base",
"//media",
]
# Use fastest possible float math.
configs -= [ "//build/config/compiler:default_optimization" ]
configs += [ "//build/config/compiler:optimize_speed" ]
cflags = [ "-ffast-math" ]
}
cast_source_set("audio_resampler") { cast_source_set("audio_resampler") {
sources = [ sources = [
"audio_resampler.cc", "audio_resampler.cc",
...@@ -206,11 +230,13 @@ cast_source_set("test_support") { ...@@ -206,11 +230,13 @@ cast_source_set("test_support") {
cast_source_set("unittests") { cast_source_set("unittests") {
testonly = true testonly = true
sources = [ sources = [
"audio_clock_simulator_unittest.cc",
"audio_fader_unittest.cc", "audio_fader_unittest.cc",
"interleaved_channel_mixer_unittest.cc", "interleaved_channel_mixer_unittest.cc",
] ]
deps = [ deps = [
":audio_clock_simulator",
":fader", ":fader",
":interleaved_channel_mixer", ":interleaved_channel_mixer",
"//base", "//base",
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/audio/audio_clock_simulator.h"
#include <algorithm>
#include <cmath>
#include "base/logging.h"
#include "media/base/audio_bus.h"
namespace chromecast {
namespace media {
namespace {
int64_t FramesToTime(int64_t frames, int sample_rate) {
return frames * 1000000 / sample_rate;
}
} // namespace
constexpr int AudioClockSimulator::kInterpolateWindow;
constexpr double AudioClockSimulator::kMaxRate;
constexpr double AudioClockSimulator::kMinRate;
constexpr size_t AudioClockSimulator::kMaxChannels;
AudioClockSimulator::AudioClockSimulator(AudioProvider* provider)
: provider_(provider),
sample_rate_(provider_->sample_rate()),
num_channels_(provider_->num_channels()),
scratch_buffer_(
::media::AudioBus::Create(num_channels_, kInterpolateWindow + 1)) {
DCHECK(provider_);
DCHECK_GT(sample_rate_, 0);
DCHECK_GT(num_channels_, 0u);
DCHECK_LE(num_channels_, kMaxChannels);
scratch_buffer_->Zero();
}
AudioClockSimulator::~AudioClockSimulator() = default;
size_t AudioClockSimulator::num_channels() const {
return num_channels_;
}
int AudioClockSimulator::sample_rate() const {
return sample_rate_;
}
double AudioClockSimulator::SetRate(double rate) {
rate = std::max(kMinRate, std::min(rate, kMaxRate));
clock_rate_ = rate;
input_frames_ = 0;
output_frames_ = 0;
return rate;
}
int AudioClockSimulator::DelayFrames() const {
if (state_ == State::kLengthening && first_frame_filled_) {
return 1;
}
return 0;
}
int AudioClockSimulator::FillFrames(int num_frames,
int64_t playout_timestamp,
float* const* channel_data) {
int filled = 0;
while (filled < num_frames) {
if (state_ == State::kLengthening) {
auto result =
FillDataLengthen(num_frames, playout_timestamp, channel_data, filled);
filled += result.filled;
if (!result.complete) {
break;
}
continue;
}
if (state_ == State::kShortening) {
auto result =
FillDataShorten(num_frames, playout_timestamp, channel_data, filled);
filled += result.filled;
if (!result.complete) {
break;
}
continue;
}
int64_t end_input_frames = input_frames_ + kInterpolateWindow;
int64_t end_output_frames = output_frames_ + filled + kInterpolateWindow;
int64_t desired_output_frames = std::round(end_input_frames / clock_rate_);
if (end_output_frames > desired_output_frames) {
state_ = State::kShortening;
continue;
} else if (end_output_frames < desired_output_frames) {
state_ = State::kLengthening;
continue;
}
DCHECK_EQ(state_, State::kPassthrough);
float* channels[kMaxChannels];
for (size_t c = 0; c < num_channels_; ++c) {
channels[c] = channel_data[c] + filled;
}
int64_t timestamp = playout_timestamp + FramesToTime(filled, sample_rate_);
int desired = std::min(num_frames - filled, kInterpolateWindow);
int provided = provider_->FillFrames(desired, timestamp, channels);
input_frames_ += provided;
filled += provided;
if (provided < desired) {
break;
}
}
output_frames_ += filled;
return filled;
}
AudioClockSimulator::FillResult AudioClockSimulator::FillDataLengthen(
int num_frames,
int64_t playout_timestamp,
float* const* channel_data,
int offset) {
DCHECK_EQ(state_, State::kLengthening);
DCHECK_LT(interpolate_position_, kInterpolateWindow);
DCHECK_GE(interpolate_position_, 0);
if (first_frame_filled_) {
for (size_t c = 0; c < num_channels_; ++c) {
channel_data[c][offset] = scratch_buffer_->channel(c)[0];
}
first_frame_filled_ = false;
state_ = State::kPassthrough; // Finished current interpolation window.
return {true, 1};
}
int frames_for_window = kInterpolateWindow - interpolate_position_;
int desired_fill = std::min((num_frames - offset), frames_for_window);
float* channels[kMaxChannels];
for (size_t c = 0; c < num_channels_; ++c) {
// The first sample from last call is still needed, so fill starting at
// index 1.
channels[c] = scratch_buffer_->channel(c) + 1;
}
int64_t timestamp = playout_timestamp + FramesToTime(offset, sample_rate_);
int provided = provider_->FillFrames(desired_fill, timestamp, channels);
input_frames_ += provided;
InterpolateLonger(provided, channel_data, offset);
return {(provided == desired_fill), provided};
}
void AudioClockSimulator::InterpolateLonger(int num_frames,
float* const* dest,
int offset) {
DCHECK_GT(num_frames, 0);
DCHECK_LE(interpolate_position_ + num_frames, kInterpolateWindow);
// When lengthening, we only set |first_frame_filled_| when we have finished
// the interpolation window (indicating the extra frame is available in
// the start of the scratch buffer).
DCHECK(!first_frame_filled_);
for (size_t c = 0; c < num_channels_; ++c) {
float* source_channel = scratch_buffer_->channel(c);
float* dest_channel = dest[c] + offset;
for (int s = 0; s < num_frames; ++s) {
int interpolate_point = interpolate_position_ + s;
dest_channel[s] =
(source_channel[s] * interpolate_point +
source_channel[s + 1] * (kInterpolateWindow - interpolate_point)) /
kInterpolateWindow;
}
source_channel[0] = source_channel[num_frames];
}
interpolate_position_ += num_frames;
if (interpolate_position_ == kInterpolateWindow) {
interpolate_position_ = 0;
first_frame_filled_ = true;
// Don't set state to passthrough yet, because we still need to consume the
// last frame from the scratch buffer.
}
}
AudioClockSimulator::FillResult AudioClockSimulator::FillDataShorten(
int num_frames,
int64_t playout_timestamp,
float* const* channel_data,
int offset) {
DCHECK_EQ(state_, State::kShortening);
DCHECK_LT(interpolate_position_, kInterpolateWindow);
DCHECK_GE(interpolate_position_, 0);
int frames_for_window = kInterpolateWindow - interpolate_position_;
int desired_fill = std::min((num_frames - offset), frames_for_window);
int fill_offset = 1; // Leave the last frame of the previous step untouched.
if (!first_frame_filled_) {
DCHECK_EQ(interpolate_position_, 0);
// Fill an extra frame for the first step of interpolation.
desired_fill += 1;
fill_offset = 0;
}
float* channels[kMaxChannels];
for (size_t c = 0; c < num_channels_; ++c) {
channels[c] = scratch_buffer_->channel(c) + fill_offset;
}
int64_t timestamp = playout_timestamp + FramesToTime(offset, sample_rate_);
int provided = provider_->FillFrames(desired_fill, timestamp, channels);
if (provided == 0) {
return {false, 0};
}
first_frame_filled_ = true;
input_frames_ += provided;
int output_frames = provided + fill_offset - 1;
InterpolateShorter(output_frames, channel_data, offset);
return {(provided == desired_fill), output_frames};
}
void AudioClockSimulator::InterpolateShorter(int num_frames,
float* const* dest,
int offset) {
DCHECK_GT(num_frames, 0);
DCHECK_LE(interpolate_position_ + num_frames, kInterpolateWindow);
// When shortening, we must ensure that the first frame in the scratch buffer
// is filled with valid data in all cases (including when we start the
// interpolation window).
DCHECK(first_frame_filled_);
for (size_t c = 0; c < num_channels_; ++c) {
float* source_channel = scratch_buffer_->channel(c);
float* dest_channel = dest[c] + offset;
for (int s = 0; s < num_frames; ++s) {
int interpolate_point = interpolate_position_ + s + 1;
dest_channel[s] =
(source_channel[s] * (kInterpolateWindow - interpolate_point) +
source_channel[s + 1] * interpolate_point) /
kInterpolateWindow;
}
source_channel[0] = source_channel[num_frames];
}
interpolate_position_ += num_frames;
if (interpolate_position_ == kInterpolateWindow) {
interpolate_position_ = 0;
first_frame_filled_ = false;
state_ = State::kPassthrough; // Finished current interpolation window.
}
}
} // namespace media
} // namespace chromecast
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_MEDIA_AUDIO_AUDIO_CLOCK_SIMULATOR_H_
#define CHROMECAST_MEDIA_AUDIO_AUDIO_CLOCK_SIMULATOR_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include "chromecast/media/audio/audio_provider.h"
namespace media {
class AudioBus;
} // namespace media
namespace chromecast {
namespace media {
// Simulates a modifiable audio output clock rate by interpolating as needed to
// add or remove single frames.
class AudioClockSimulator : public AudioProvider {
public:
// Number of frames to linearly interpolate over. One interpolation starts,
// it continues until the window is complete; rate changes only take effect
// once the window is complete.
static constexpr int kInterpolateWindow = 1024;
// Maximum/minimum rate that this class supports. If the rate passed to
// SetRate() is outside of these bounds, it is clamped to within the bounds
// and the clamped value is returned. Note that the rate is essentially
// (num_input_frames) / (num_output_frames).
static constexpr double kMaxRate =
(kInterpolateWindow + 1.0) / kInterpolateWindow;
static constexpr double kMinRate =
kInterpolateWindow / (kInterpolateWindow + 1.0);
// Maximum channels that this class supports.
static constexpr size_t kMaxChannels = 8;
explicit AudioClockSimulator(AudioProvider* provider);
~AudioClockSimulator() override;
AudioClockSimulator(const AudioClockSimulator&) = delete;
AudioClockSimulator& operator=(const AudioClockSimulator&) = delete;
// Sets the simulated audio clock rate. The rate is capped internally between
// kMinRate and kMaxRate. Returns the capped effective rate.
double SetRate(double rate);
// Returns the number of frames of additional delay due to audio stored
// internally. Will always return 0 or 1.
int DelayFrames() const;
// AudioProvider implementation:
int FillFrames(int num_frames,
int64_t playout_timestamp,
float* const* channel_data) override;
size_t num_channels() const override;
int sample_rate() const override;
private:
enum class State {
kPassthrough,
kLengthening,
kShortening,
};
struct FillResult {
bool complete;
int filled;
};
FillResult FillDataLengthen(int num_frames,
int64_t playout_timestamp,
float* const* channel_data,
int offset);
FillResult FillDataShorten(int num_frames,
int64_t playout_timestamp,
float* const* channel_data,
int offset);
void InterpolateLonger(int num_frames,
float* const* channel_data,
int offset);
void InterpolateShorter(int num_frames,
float* const* channel_data,
int offset);
AudioProvider* const provider_;
const int sample_rate_;
const size_t num_channels_;
double clock_rate_ = 1.0;
int64_t input_frames_ = 0;
int64_t output_frames_ = 0;
State state_ = State::kPassthrough;
int interpolate_position_ = 0;
bool first_frame_filled_ = false;
// Scratch buffer used to hold the filled original data before it is
// interpolated for output. The first frame (first sample across all channels)
// at index 0 is used to hold the last frame of the previous partial
// interpolation step.
// At the start of the interpolation window:
// * If we are interpolating to make the audio longer, the first frame is
// not used (the weight is 0), so it can be any value.
// * If we are interpolating to make the audio shorted, the first fill
// request to the provider fills in the first frame (so we request one
// extra frame of audio) - this accounts for the frame that is removed due
// to interpolation.
// The |first_frame_filled_| member var tracks whether or not the first frame
// of the |scratch_buffer_| contains valid audio.
std::unique_ptr<::media::AudioBus> scratch_buffer_;
};
} // namespace media
} // namespace chromecast
#endif // CHROMECAST_MEDIA_AUDIO_AUDIO_CLOCK_SIMULATOR_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cmath>
#include <tuple>
#include "base/logging.h"
#include "chromecast/media/audio/audio_clock_simulator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::NiceMock;
namespace chromecast {
namespace media {
namespace {
constexpr int kSampleRate = 48000;
constexpr size_t kDefaultChannels = 1;
constexpr int kBufferSize = 4096;
int64_t FramesToTime(int64_t frames, int sample_rate) {
return frames * 1000000 / sample_rate;
}
class FakeAudioProvider : public AudioProvider {
public:
explicit FakeAudioProvider(size_t num_channels)
: num_channels_(num_channels) {
DCHECK_GT(num_channels_, 0u);
ON_CALL(*this, FillFrames)
.WillByDefault(Invoke(this, &FakeAudioProvider::FillFramesImpl));
}
// AudioProvider implementation:
MOCK_METHOD(int, FillFrames, (int, int64_t, float* const*));
size_t num_channels() const override { return num_channels_; }
int sample_rate() const override { return kSampleRate; }
int FillFramesImpl(int num_frames,
int64_t playout_timestamp,
float* const* channel_data) {
for (int f = 0; f < num_frames; ++f) {
for (size_t c = 0; c < num_channels_; ++c) {
channel_data[c][f] = static_cast<float>(next_ + f);
}
}
next_ += num_frames;
return num_frames;
}
int consumed() { return next_; }
private:
const size_t num_channels_;
int next_ = 0;
};
using TestParams =
std::tuple<int /* input request_size */, double /* clock_rate */>;
} // namespace
class AudioClockSimulatorTest : public testing::TestWithParam<TestParams> {
public:
AudioClockSimulatorTest() = default;
~AudioClockSimulatorTest() override = default;
};
TEST_P(AudioClockSimulatorTest, Fill) {
const TestParams& params = GetParam();
const int request_size = testing::get<0>(params);
const double clock_rate = testing::get<1>(params);
LOG(INFO) << "Request size = " << request_size
<< ", clock rate = " << clock_rate;
NiceMock<FakeAudioProvider> provider(kDefaultChannels);
AudioClockSimulator clock(&provider);
if (request_size > kBufferSize) {
return;
}
EXPECT_EQ(clock.SetRate(clock_rate), clock_rate);
float output[kBufferSize];
std::fill_n(output, kBufferSize, 0);
float* test_data[1] = {output};
int i;
for (i = 0; i + request_size <= kBufferSize; i += request_size) {
test_data[0] = output + i;
int64_t timestamp = FramesToTime(i, kSampleRate);
EXPECT_CALL(provider, FillFrames(_, _, _)).Times(testing::AnyNumber());
// Timestamp for requests to provider should not be before current fill
// timestamp.
EXPECT_CALL(provider, FillFrames(_, testing::Lt(timestamp), _)).Times(0);
int provided = clock.FillFrames(request_size, timestamp, test_data);
EXPECT_EQ(provided, request_size);
int delay = clock.DelayFrames();
EXPECT_GE(delay, 0);
EXPECT_LE(delay, 1);
testing::Mock::VerifyAndClearExpectations(&provider);
}
int leftover = kBufferSize - i;
if (leftover > 0) {
test_data[0] = output + kBufferSize - leftover;
int64_t timestamp = FramesToTime(i, kSampleRate);
EXPECT_CALL(provider, FillFrames(_, _, _)).Times(testing::AnyNumber());
EXPECT_CALL(provider, FillFrames(_, testing::Lt(timestamp), _)).Times(0);
int provided = clock.FillFrames(leftover, timestamp, test_data);
EXPECT_EQ(provided, leftover);
int delay = clock.DelayFrames();
EXPECT_GE(delay, 0);
EXPECT_LE(delay, 1);
}
if (clock_rate < 1.0) {
EXPECT_LE(provider.consumed(), kBufferSize);
if (clock_rate == AudioClockSimulator::kMinRate) {
int windows = kBufferSize / (AudioClockSimulator::kInterpolateWindow + 1);
int extra = kBufferSize % (AudioClockSimulator::kInterpolateWindow + 1);
EXPECT_EQ(provider.consumed(),
windows * AudioClockSimulator::kInterpolateWindow + extra);
}
} else if (clock_rate == 1.0) {
EXPECT_EQ(provider.consumed(), kBufferSize);
} else {
EXPECT_GE(provider.consumed(), kBufferSize);
if (clock_rate == AudioClockSimulator::kMaxRate) {
int windows = kBufferSize / AudioClockSimulator::kInterpolateWindow;
int extra = kBufferSize % AudioClockSimulator::kInterpolateWindow;
EXPECT_EQ(
provider.consumed(),
windows * (AudioClockSimulator::kInterpolateWindow + 1) + extra);
}
}
for (int f = 0; f < kBufferSize - 1; ++f) {
EXPECT_LT(output[f], output[f + 1]);
float diff = output[f + 1] - output[f];
EXPECT_GE(diff, 1.0 - 1.0 / AudioClockSimulator::kInterpolateWindow);
EXPECT_LE(diff, 1.0 + 1.0 / AudioClockSimulator::kInterpolateWindow);
}
}
INSTANTIATE_TEST_SUITE_P(
RequestSizes,
AudioClockSimulatorTest,
testing::Combine(
::testing::Values(1,
2,
100,
AudioClockSimulator::kInterpolateWindow - 1,
AudioClockSimulator::kInterpolateWindow,
AudioClockSimulator::kInterpolateWindow + 1,
AudioClockSimulator::kInterpolateWindow + 100),
::testing::Values(1.0,
AudioClockSimulator::kMinRate,
AudioClockSimulator::kMaxRate,
(1.0 + AudioClockSimulator::kMinRate) / 2,
(1.0 + AudioClockSimulator::kMaxRate) / 2)));
TEST(AudioClockSimulatorTest2, RateChange) {
NiceMock<FakeAudioProvider> provider(kDefaultChannels);
AudioClockSimulator clock(&provider);
float output[kBufferSize];
std::fill_n(output, kBufferSize, 0);
int index = 0;
float* test_data[1] = {output};
// First, some passthrough data.
int consumed = 0;
int requested = 100;
int provided = clock.FillFrames(requested, 0, test_data);
EXPECT_EQ(provided, requested);
EXPECT_EQ(provider.consumed() - consumed, requested);
consumed = provider.consumed();
index += requested;
// Change clock rate. When switching from passthrough, the rate change takes
// effect immediately.
clock.SetRate(AudioClockSimulator::kMinRate);
test_data[0] = output + index;
requested = 100;
provided = clock.FillFrames(requested, 0, test_data);
EXPECT_EQ(provided, requested);
index += requested;
// Change clock rate again. The new rate doesn't take effect until the
// interpolation window is complete.
clock.SetRate(AudioClockSimulator::kMaxRate);
test_data[0] = output + index;
requested = AudioClockSimulator::kInterpolateWindow + 1 - 100;
provided = clock.FillFrames(requested, 0, test_data);
EXPECT_EQ(provided, requested);
// Consume 1 less sample than requested over the entire interpolation window.
EXPECT_EQ(provider.consumed() - consumed,
AudioClockSimulator::kInterpolateWindow);
consumed = provider.consumed();
index += requested;
// Interpolation window should now be complete, start on new clock rate.
test_data[0] = output + index;
requested = 100;
provided = clock.FillFrames(requested, 0, test_data);
EXPECT_EQ(provided, requested);
index += requested;
// Change clock rate again.
clock.SetRate(1.0);
test_data[0] = output + index;
requested = AudioClockSimulator::kInterpolateWindow - 100;
provided = clock.FillFrames(requested, 0, test_data);
EXPECT_EQ(provided, requested);
// Consume 1 more sample than requested over the entire interpolation window.
EXPECT_EQ(provider.consumed() - consumed,
AudioClockSimulator::kInterpolateWindow + 1);
index += requested;
for (int f = 0; f < index - 1; ++f) {
EXPECT_LT(output[f], output[f + 1]);
float diff = output[f + 1] - output[f];
EXPECT_GE(diff, 1.0 - 1.0 / AudioClockSimulator::kInterpolateWindow);
EXPECT_LE(diff, 1.0 + 1.0 / AudioClockSimulator::kInterpolateWindow);
}
}
class AudioClockSimulatorLongRunningTest
: public testing::TestWithParam<double> {
public:
AudioClockSimulatorLongRunningTest() = default;
~AudioClockSimulatorLongRunningTest() override = default;
};
TEST_P(AudioClockSimulatorLongRunningTest, Run) {
double rate = GetParam();
LOG(INFO) << "Rate = " << rate;
NiceMock<FakeAudioProvider> provider(kDefaultChannels);
AudioClockSimulator clock(&provider);
clock.SetRate(rate);
const int kRequestSize = 1000;
const int kIterations = 1000;
float output[kRequestSize];
float* test_data[1] = {output};
for (int i = 0; i < kIterations; ++i) {
int provided = clock.FillFrames(kRequestSize, 0, test_data);
EXPECT_EQ(provided, kRequestSize);
}
int input_frames = provider.consumed();
int output_frames = kRequestSize * kIterations;
EXPECT_GE(input_frames, std::floor(rate * output_frames));
EXPECT_LE(input_frames, std::ceil(rate * output_frames));
}
INSTANTIATE_TEST_SUITE_P(
Rates,
AudioClockSimulatorLongRunningTest,
::testing::Values(1.0,
AudioClockSimulator::kMinRate,
AudioClockSimulator::kMaxRate,
(1.0 + AudioClockSimulator::kMinRate) / 2,
(1.0 + AudioClockSimulator::kMaxRate) / 2));
} // namespace media
} // namespace chromecast
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_MEDIA_AUDIO_AUDIO_PROVIDER_H_
#define CHROMECAST_MEDIA_AUDIO_AUDIO_PROVIDER_H_
#include <cstdint>
namespace chromecast {
namespace media {
// Abstract interface for classes that provide audio data.
class AudioProvider {
public:
// Fills in |channel_data| with up to |num_frames| frames of audio.
// The |playout_timestamp| indicates when the first sample of the filled audio
// is expected to play out. Returns the number of frames actually filled;
// implementations should try to fill as much audio as possible.
virtual int FillFrames(int num_frames,
int64_t playout_timestamp,
float* const* channel_data) = 0;
// Returns the number of audio channels and the sample rate of the provider.
// Used for DCHECKing only; all callers of a provider must use the same
// channel count and sample rate as the provider.
virtual size_t num_channels() const = 0;
virtual int sample_rate() const = 0;
protected:
virtual ~AudioProvider() = default;
};
} // namespace media
} // namespace chromecast
#endif // CHROMECAST_MEDIA_AUDIO_AUDIO_PROVIDER_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment