Commit 53c06344 authored by Ahmed Fakhry's avatar Ahmed Fakhry Committed by Commit Bot

media: Add API for audio encoding and add PCM and Opus implementations

This is part of the recording service work needed for capture mode.
(See go/simple-recording-service).

This CL defines the base APIs for audio encoders, and ports the PCM
and Opus implementations from blink to //media. A follow-up CL should
be to migrate blink's media recorder to use these new encoders.

BUG=1131275, 1126586
TEST=Added tests at media_unittests --gtest_filter=*AudioEncodersTest*
     Also, Manually tested both encoders in my WIP recording service
     CL, and the output captured webm file contains recorded audio
     that plays back successfully.

Change-Id: Iea132162d319a5b580e6b447e3d5658fbf731de6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2426146
Commit-Queue: Ahmed Fakhry <afakhry@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811552}
parent 2482aae1
......@@ -41,7 +41,6 @@ source_set("audio") {
visibility = [
":wav_audio_handler_fuzzer",
"//media",
"//media/renderers",
# TODO(dalecurtis): CoreAudioUtil::IsCoreAudioSupported() should probably
......@@ -85,6 +84,8 @@ source_set("audio") {
"audio_manager.h",
"audio_manager_base.cc",
"audio_manager_base.h",
"audio_opus_encoder.cc",
"audio_opus_encoder.h",
"audio_output_delegate.cc",
"audio_output_delegate.h",
"audio_output_device.cc",
......@@ -103,6 +104,8 @@ source_set("audio") {
"audio_output_resampler.h",
"audio_output_stream_sink.cc",
"audio_output_stream_sink.h",
"audio_pcm_encoder.cc",
"audio_pcm_encoder.h",
"audio_sink_parameters.cc",
"audio_sink_parameters.h",
"audio_source_diverter.h",
......@@ -144,6 +147,7 @@ source_set("audio") {
"//base",
"//build:chromecast_buildflags",
"//media/base",
"//third_party/opus:opus",
"//url",
]
libs = []
......@@ -269,10 +273,10 @@ source_set("audio") {
]
deps += [ "//chromeos/audio" ]
} else if (is_linux) {
sources += [
"cras/audio_manager_cras.cc",
"cras/audio_manager_cras.h",
]
sources += [
"cras/audio_manager_cras.cc",
"cras/audio_manager_cras.h",
]
}
}
......@@ -379,6 +383,7 @@ source_set("unit_tests") {
"audio_debug_recording_helper_unittest.cc",
"audio_debug_recording_manager_unittest.cc",
"audio_debug_recording_session_impl_unittest.cc",
"audio_encoders_unittest.cc",
"audio_input_device_unittest.cc",
"audio_input_stream_data_interceptor_unittest.cc",
"audio_input_unittest.cc",
......@@ -398,6 +403,7 @@ source_set("unit_tests") {
"//media:test_support",
"//testing/gmock",
"//testing/gtest",
"//third_party/opus:opus",
"//url",
]
......
// Copyright 2020 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 <cstring>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/time/time.h"
#include "media/audio/audio_opus_encoder.h"
#include "media/audio/audio_pcm_encoder.h"
#include "media/audio/simple_sources.h"
#include "media/base/audio_encoder.h"
#include "media/base/audio_parameters.h"
#include "media/base/status.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/opus/src/include/opus.h"
namespace media {
namespace {
constexpr int kAudioSampleRate = 48000;
constexpr base::TimeDelta kBufferDuration =
base::TimeDelta::FromMilliseconds(10);
// This is the preferred opus buffer duration (60 ms), which corresponds to a
// value of 2880 frames per buffer (|kOpusFramesPerBuffer|).
constexpr base::TimeDelta kOpusBufferDuration =
base::TimeDelta::FromMilliseconds(60);
constexpr int kOpusFramesPerBuffer = kOpusBufferDuration.InMicroseconds() *
kAudioSampleRate /
base::Time::kMicrosecondsPerSecond;
struct TestAudioParams {
const media::AudioParameters::Format format;
const media::ChannelLayout channel_layout;
const int sample_rate;
};
constexpr TestAudioParams kTestAudioParams[] = {
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_STEREO, kAudioSampleRate},
// Change to mono:
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
kAudioSampleRate},
// Different sampling rate as well:
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
24000},
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_STEREO, 8000},
// Using a non-default Opus sampling rate (48, 24, 16, 12, or 8 kHz).
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
22050},
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_STEREO, 44100},
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_STEREO, 96000},
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
kAudioSampleRate},
{media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_STEREO, kAudioSampleRate},
};
} // namespace
class AudioEncodersTest : public ::testing::TestWithParam<TestAudioParams> {
public:
AudioEncodersTest()
: input_params_(GetParam().format,
GetParam().channel_layout,
GetParam().sample_rate,
GetParam().sample_rate / 100),
audio_source_(input_params_.channels(),
/*freq=*/440,
input_params_.sample_rate()) {}
AudioEncodersTest(const AudioEncodersTest&) = delete;
AudioEncodersTest& operator=(const AudioEncodersTest&) = delete;
~AudioEncodersTest() override = default;
const AudioParameters& input_params() const { return input_params_; }
const AudioEncoder* encoder() const { return encoder_.get(); }
int encode_callback_count() const { return encode_callback_count_; }
void SetEncoder(std::unique_ptr<AudioEncoder> encoder) {
encoder_ = std::move(encoder);
encode_callback_count_ = 0;
}
// Produces an audio data that corresponds to a |kBufferDuration| and the
// sample rate of the current |input_params_|. The produced data is send to
// |encoder_| to be encoded, and the number of frames generated is returned.
int ProduceAudioAndEncode() {
DCHECK(encoder_);
const int num_frames =
input_params_.sample_rate() * kBufferDuration.InSecondsF();
current_audio_bus_ =
media::AudioBus::Create(input_params_.channels(), num_frames);
const auto capture_time = base::TimeTicks::Now();
audio_source_.OnMoreData(base::TimeDelta(), capture_time, 0,
current_audio_bus_.get());
encoder_->EncodeAudio(*current_audio_bus_, capture_time);
return num_frames;
}
// Used to verify we get no errors.
void OnErrorCallback(Status error) { FAIL() << error.message(); }
// Used as the callback of the PCM encoder.
void VerifyPcmEncoding(EncodedAudioBuffer output) {
DCHECK(current_audio_bus_);
++encode_callback_count_;
// Verify that PCM doesn't change the input; i.e. it's just a pass through.
size_t uncompressed_size = current_audio_bus_->frames() *
current_audio_bus_->channels() * sizeof(float);
ASSERT_EQ(uncompressed_size, output.encoded_data_size);
std::unique_ptr<uint8_t[]> uncompressed_audio_data(
new uint8_t[uncompressed_size]);
current_audio_bus_->ToInterleaved<Float32SampleTypeTraits>(
current_audio_bus_->frames(),
reinterpret_cast<float*>(uncompressed_audio_data.get()));
EXPECT_EQ(std::memcmp(uncompressed_audio_data.get(),
output.encoded_data.get(), uncompressed_size),
0);
}
// Used as the callback of the Opus encoder.
void VerifyOpusEncoding(OpusDecoder* opus_decoder,
EncodedAudioBuffer output) {
DCHECK(current_audio_bus_);
DCHECK(opus_decoder);
++encode_callback_count_;
// Use the provied |opus_decoder| to decode the |encoded_data| and check we
// get the expected number of frames per buffer.
std::vector<float> buffer(kOpusFramesPerBuffer * output.params.channels());
EXPECT_EQ(kOpusFramesPerBuffer,
opus_decode_float(opus_decoder, output.encoded_data.get(),
output.encoded_data_size, buffer.data(),
kOpusFramesPerBuffer, 0));
}
private:
// The input params as initialized from the test's parameter.
const AudioParameters input_params_;
// The audio source used to fill in the data of the |current_audio_bus_|.
media::SineWaveAudioSource audio_source_;
// The encoder the test is verifying.
std::unique_ptr<AudioEncoder> encoder_;
// The audio bus that was most recently generated and sent to the |encoder_|
// by ProduceAudioAndEncode().
std::unique_ptr<media::AudioBus> current_audio_bus_;
// The number of encoder callbacks received.
int encode_callback_count_ = 0;
};
TEST_P(AudioEncodersTest, PcmEncoder) {
SetEncoder(std::make_unique<AudioPcmEncoder>(
input_params(),
base::BindRepeating(&AudioEncodersTest::VerifyPcmEncoding,
base::Unretained(this)),
base::BindRepeating(&AudioEncodersTest::OnErrorCallback,
base::Unretained(this))));
constexpr int kCount = 6;
for (int i = 0; i < kCount; ++i)
ProduceAudioAndEncode();
EXPECT_EQ(kCount, encode_callback_count());
}
TEST_P(AudioEncodersTest, OpusEncoder) {
int error;
OpusDecoder* opus_decoder =
opus_decoder_create(kAudioSampleRate, input_params().channels(), &error);
ASSERT_TRUE(error == OPUS_OK && opus_decoder);
SetEncoder(std::make_unique<AudioOpusEncoder>(
input_params(),
base::BindRepeating(&AudioEncodersTest::VerifyOpusEncoding,
base::Unretained(this), opus_decoder),
base::BindRepeating(&AudioEncodersTest::OnErrorCallback,
base::Unretained(this)),
/*opus_bitrate=*/0));
// The opus encoder encodes in multiple of 60 ms. Wait for the total number of
// frames that will be generated in 60 ms at the input sampling rate.
const int frames_in_60_ms =
kOpusBufferDuration.InSecondsF() * input_params().sample_rate();
int total_frames = 0;
while (total_frames < frames_in_60_ms)
total_frames += ProduceAudioAndEncode();
EXPECT_EQ(1, encode_callback_count());
opus_decoder_destroy(opus_decoder);
opus_decoder = nullptr;
}
INSTANTIATE_TEST_SUITE_P(All,
AudioEncodersTest,
testing::ValuesIn(kTestAudioParams));
} // namespace media
// Copyright 2020 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 "media/audio/audio_opus_encoder.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "media/base/status.h"
#include "media/base/status_codes.h"
namespace media {
namespace {
// Recommended value for opus_encode_float(), according to documentation in
// third_party/opus/src/include/opus.h, so that the Opus encoder does not
// degrade the audio due to memory constraints, and is independent of the
// duration of the encoded buffer.
constexpr int kOpusMaxDataBytes = 4000;
// Opus preferred sampling rate for encoding. This is also the one WebM likes
// to have: https://wiki.xiph.org/MatroskaOpus.
constexpr int kOpusPreferredSamplingRate = 48000;
// For Opus, we try to encode 60ms, the maximum Opus buffer, for quality
// reasons.
constexpr int kOpusPreferredBufferDurationMs = 60;
// The amount of Frames in a 60 ms buffer @ 48000 samples/second.
constexpr int kOpusPreferredFramesPerBuffer =
kOpusPreferredSamplingRate * kOpusPreferredBufferDurationMs /
base::Time::kMillisecondsPerSecond;
// Deletes the libopus encoder instance pointed to by |encoder_ptr|.
inline void OpusEncoderDeleter(OpusEncoder* encoder_ptr) {
opus_encoder_destroy(encoder_ptr);
}
// Adjusts the given input |params| to have a frames-per-buffer value that
// matches that of the FIFO which buffers the input audio before sending it to
// the converter.
AudioParameters AdjustInputParamsForOpus(const AudioParameters& params) {
auto adjusted_params = params;
adjusted_params.set_frames_per_buffer(params.sample_rate() *
kOpusPreferredBufferDurationMs /
base::Time::kMillisecondsPerSecond);
return adjusted_params;
}
// Creates the audio parameters of the converted audio format that Opus prefers,
// which will be used as the input to the libopus encoder.
AudioParameters CreateOpusInputParams(const AudioParameters& input_params) {
// third_party/libopus supports up to 2 channels (see implementation of
// opus_encoder_create()): force |converted_params| to at most those.
// Also, the libopus encoder can accept sample rates of 8, 12, 16, 24, and the
// default preferred 48 kHz. If the input sample rate is anything else, we'll
// use 48 kHz.
const int input_rate = input_params.sample_rate();
const int used_rate = (input_rate == 8000 || input_rate == 12000 ||
input_rate == 16000 || input_rate == 24000)
? input_rate
: kOpusPreferredSamplingRate;
const int frames_per_buffer = used_rate * kOpusPreferredBufferDurationMs /
base::Time::kMillisecondsPerSecond;
const auto converted_params =
AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
GuessChannelLayout(std::min(input_params.channels(), 2)),
used_rate, frames_per_buffer);
DVLOG(1) << "|input_params|:" << input_params.AsHumanReadableString()
<< " -->|converted_params|:"
<< converted_params.AsHumanReadableString();
DCHECK(converted_params.IsValid());
return converted_params;
}
// Creates and returns the libopus encoder instance. Returns nullptr if the
// encoder creation fails.
OwnedOpusEncoder CreateOpusEncoder(
const AudioEncoder::StatusCB& status_callback,
const AudioParameters& params,
int32_t bitrate) {
int opus_result;
OwnedOpusEncoder encoder(
opus_encoder_create(params.sample_rate(), params.channels(),
OPUS_APPLICATION_AUDIO, &opus_result),
OpusEncoderDeleter);
if (opus_result < 0) {
status_callback.Run(Status(
StatusCode::kEncoderInitializationError,
base::StringPrintf(
"Couldn't init Opus encoder: %s, sample rate: %d, channels: %d",
opus_strerror(opus_result), params.sample_rate(),
params.channels())));
}
if (encoder &&
opus_encoder_ctl(encoder.get(), OPUS_SET_BITRATE(bitrate)) != OPUS_OK) {
status_callback.Run(
Status(StatusCode::kEncoderInitializationError,
base::StringPrintf("Failed to set Opus bitrate: %d", bitrate)));
encoder.reset();
}
return encoder;
}
// Tries to encode |in_data|'s |num_samples| into |out_data|. |out_data| is
// always resized to |kOpusMaxDataBytes| but only filled with |*out_size| actual
// encoded data if encoding was successful. Returns true if encoding is
// successful, in which case |*out_size| is guaranteed to be > 1. Returns false
// if an error occurs or the packet does not need to be transmitted.
// |status_callback| will be used to report any errors.
bool DoEncode(const AudioEncoder::StatusCB& status_callback,
OpusEncoder* opus_encoder,
float* in_data,
int num_samples,
std::unique_ptr<uint8_t[]>* out_data,
size_t* out_size) {
DCHECK(opus_encoder);
DCHECK(in_data);
DCHECK(out_data);
DCHECK(out_size);
DCHECK_LE(num_samples, kOpusPreferredFramesPerBuffer);
out_data->reset(new uint8_t[kOpusMaxDataBytes]);
const opus_int32 result = opus_encode_float(
opus_encoder, in_data, num_samples, out_data->get(), kOpusMaxDataBytes);
if (result > 1) {
// TODO(ajose): Investigate improving this. http://crbug.com/547918
*out_size = result;
return true;
}
// If |result| in {0,1}, do nothing; the documentation says that a return
// value of zero or one means the packet does not need to be transmitted.
// Otherwise, we have an error.
if (result < 0) {
status_callback.Run(
Status(StatusCode::kEncoderFailedEncode, opus_strerror(result)));
}
return false;
}
// During this object's lifetime, it will use its |audio_bus_| to provide input
// to its |converter_|.
class ScopedConverterInputProvider : public AudioConverter::InputCallback {
public:
ScopedConverterInputProvider(AudioConverter* converter,
const AudioBus* audio_bus)
: converter_(converter), audio_bus_(audio_bus) {
DCHECK(converter_);
DCHECK(audio_bus_);
converter_->AddInput(this);
}
ScopedConverterInputProvider(const ScopedConverterInputProvider&) = delete;
ScopedConverterInputProvider& operator=(const ScopedConverterInputProvider&) =
delete;
~ScopedConverterInputProvider() override { converter_->RemoveInput(this); }
// AudioConverted::InputCallback:
double ProvideInput(AudioBus* audio_bus, uint32_t frames_delayed) override {
audio_bus_->CopyTo(audio_bus);
return 1.0f;
}
private:
AudioConverter* const converter_;
const AudioBus* const audio_bus_;
};
} // namespace
AudioOpusEncoder::AudioOpusEncoder(const AudioParameters& input_params,
EncodeCB encode_callback,
StatusCB status_callback,
int32_t opus_bitrate)
: AudioEncoder(AdjustInputParamsForOpus(input_params),
std::move(encode_callback),
std::move(status_callback)),
bits_per_second_(opus_bitrate > 0 ? opus_bitrate : OPUS_AUTO),
converted_params_(CreateOpusInputParams(audio_input_params())),
converter_(audio_input_params(),
converted_params_,
/*disable_fifo=*/false),
fifo_(base::BindRepeating(&AudioOpusEncoder::OnFifoOutput,
base::Unretained(this))),
converted_audio_bus_(
AudioBus::Create(converted_params_.channels(),
converted_params_.frames_per_buffer())),
buffer_(converted_params_.channels() *
converted_params_.frames_per_buffer()),
opus_encoder_(CreateOpusEncoder(this->status_callback(),
converted_params_,
bits_per_second_)) {
converter_.PrimeWithSilence();
fifo_.Reset(converter_.GetMaxInputFramesRequested(
converted_params_.frames_per_buffer()));
}
AudioOpusEncoder::~AudioOpusEncoder() = default;
void AudioOpusEncoder::EncodeAudioImpl(const AudioBus& audio_bus,
base::TimeTicks capture_time) {
// Initializing the opus encoder may have failed.
if (!opus_encoder_)
return;
// The |fifo_| won't trigger OnFifoOutput() until we have enough frames
// suitable for the converter.
fifo_.Push(audio_bus);
}
void AudioOpusEncoder::OnFifoOutput(const AudioBus& output_bus,
int frame_delay) {
// Provides input to the converter from |output_bus| within this scope only.
ScopedConverterInputProvider provider(&converter_, &output_bus);
converter_.Convert(converted_audio_bus_.get());
converted_audio_bus_->ToInterleaved<Float32SampleTypeTraits>(
converted_audio_bus_->frames(), buffer_.data());
std::unique_ptr<uint8_t[]> encoded_data;
size_t encoded_data_size;
if (DoEncode(status_callback(), opus_encoder_.get(), buffer_.data(),
converted_params_.frames_per_buffer(), &encoded_data,
&encoded_data_size)) {
DCHECK_GT(encoded_data_size, 1u);
encode_callback().Run(EncodedAudioBuffer(
converted_params_, std::move(encoded_data), encoded_data_size,
ComputeTimestamp(output_bus.frames(), last_capture_time())));
}
}
} // namespace media
// Copyright 2020 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 MEDIA_AUDIO_AUDIO_OPUS_ENCODER_H_
#define MEDIA_AUDIO_AUDIO_OPUS_ENCODER_H_
#include <memory>
#include <vector>
#include "media/base/audio_bus.h"
#include "media/base/audio_converter.h"
#include "media/base/audio_encoder.h"
#include "media/base/audio_push_fifo.h"
#include "third_party/opus/src/include/opus.h"
namespace media {
using OpusEncoderDeleterType = void (*)(OpusEncoder* encoder_ptr);
using OwnedOpusEncoder = std::unique_ptr<OpusEncoder, OpusEncoderDeleterType>;
// Performs Opus encoding of the input audio. The input audio is converted to a
// a format suitable for Opus before it is passed to the libopus encoder
// instance to do the actual encoding.
class MEDIA_EXPORT AudioOpusEncoder : public AudioEncoder {
public:
AudioOpusEncoder(const AudioParameters& input_params,
EncodeCB encode_callback,
StatusCB status_callback,
int32_t opus_bitrate);
AudioOpusEncoder(const AudioOpusEncoder&) = delete;
AudioOpusEncoder& operator=(const AudioOpusEncoder&) = delete;
~AudioOpusEncoder() override;
protected:
// AudioEncoder:
void EncodeAudioImpl(const AudioBus& audio_bus,
base::TimeTicks capture_time) override;
private:
// Called synchronously by |fifo_| once enough audio frames have been
// buffered.
void OnFifoOutput(const AudioBus& output_bus, int frame_delay);
// Target bitrate for Opus. If 0, Opus-provided automatic bitrate is used.
// Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
// variable bitrate up to 102 kbps for 2-channel, 48 kHz audio and a 10 ms
// buffer duration. The Opus library authors may, of course, adjust this in
// later versions.
const int32_t bits_per_second_;
// Output parameters after audio conversion. This may differ from the input
// params in the number of channels, sample rate, and the frames per buffer.
// (See CreateOpusInputParams() in the .cc file for details).
AudioParameters converted_params_;
// Sample rate adapter from the input audio to what OpusEncoder desires.
AudioConverter converter_;
// Buffer for holding the original input audio before it goes to the
// converter.
AudioPushFifo fifo_;
// This is the destination AudioBus where the |converter_| teh audio into.
std::unique_ptr<AudioBus> converted_audio_bus_;
// Buffer for passing AudioBus data from the converter to the encoder.
std::vector<float> buffer_;
// The actual libopus encoder instance. This is nullptr if creating the
// encoder fails.
OwnedOpusEncoder opus_encoder_;
};
} // namespace media
#endif // MEDIA_AUDIO_AUDIO_OPUS_ENCODER_H_
// Copyright 2020 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 "media/audio/audio_pcm_encoder.h"
#include <utility>
namespace media {
AudioPcmEncoder::AudioPcmEncoder(const AudioParameters& input_params,
EncodeCB encode_callback,
StatusCB status_callback)
: AudioEncoder(input_params,
std::move(encode_callback),
std::move(status_callback)) {}
void AudioPcmEncoder::EncodeAudioImpl(const AudioBus& audio_bus,
base::TimeTicks capture_time) {
const size_t size = audio_bus.frames() * audio_bus.channels() * sizeof(float);
std::unique_ptr<uint8_t[]> encoded_data(new uint8_t[size]);
audio_bus.ToInterleaved<Float32SampleTypeTraits>(
audio_bus.frames(), reinterpret_cast<float*>(encoded_data.get()));
encode_callback().Run(
EncodedAudioBuffer(audio_input_params(), std::move(encoded_data), size,
ComputeTimestamp(audio_bus.frames(), capture_time)));
}
} // namespace media
// Copyright 2020 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 MEDIA_AUDIO_AUDIO_PCM_ENCODER_H_
#define MEDIA_AUDIO_AUDIO_PCM_ENCODER_H_
#include "media/base/audio_encoder.h"
namespace media {
// Defines a PCM encoder, which just passes back the raw uncompressed signed
// 16-bit linear audio data.
class MEDIA_EXPORT AudioPcmEncoder : public AudioEncoder {
public:
AudioPcmEncoder(const AudioParameters& input_params,
EncodeCB encode_callback,
StatusCB status_callback);
AudioPcmEncoder(const AudioPcmEncoder&) = delete;
AudioPcmEncoder& operator=(const AudioPcmEncoder&) = delete;
~AudioPcmEncoder() override = default;
protected:
// AudioEncoder:
void EncodeAudioImpl(const AudioBus& audio_bus,
base::TimeTicks capture_time) override;
};
} // namespace media
#endif // MEDIA_AUDIO_AUDIO_PCM_ENCODER_H_
......@@ -57,6 +57,8 @@ source_set("base") {
"audio_decoder_config.h",
"audio_discard_helper.cc",
"audio_discard_helper.h",
"audio_encoder.cc",
"audio_encoder.h",
"audio_fifo.cc",
"audio_fifo.h",
"audio_hash.cc",
......
// Copyright 2020 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 "media/base/audio_encoder.h"
#include "base/logging.h"
#include "base/time/time.h"
#include "media/base/audio_timestamp_helper.h"
namespace media {
// -----------------------------------------------------------------------------
// EncodedAudioBuffer:
EncodedAudioBuffer::EncodedAudioBuffer(const AudioParameters& params,
std::unique_ptr<uint8_t[]> data,
size_t size,
base::TimeTicks timestamp)
: params(params),
encoded_data(std::move(data)),
encoded_data_size(size),
timestamp(timestamp) {}
EncodedAudioBuffer::EncodedAudioBuffer(EncodedAudioBuffer&&) = default;
EncodedAudioBuffer::~EncodedAudioBuffer() = default;
// -----------------------------------------------------------------------------
// AudioEncoder:
AudioEncoder::AudioEncoder(const AudioParameters& input_params,
EncodeCB encode_callback,
StatusCB status_callback)
: audio_input_params_(input_params),
encode_callback_(std::move(encode_callback)),
status_callback_(std::move(status_callback)) {
DCHECK(audio_input_params_.IsValid());
DCHECK(!encode_callback_.is_null());
DCHECK(!status_callback_.is_null());
DETACH_FROM_THREAD(thread_checker_);
}
AudioEncoder::~AudioEncoder() = default;
void AudioEncoder::EncodeAudio(const AudioBus& audio_bus,
base::TimeTicks capture_time) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_EQ(audio_bus.channels(), audio_input_params_.channels());
DCHECK(!capture_time.is_null());
DLOG_IF(ERROR,
!last_capture_time_.is_null() &&
((capture_time - last_capture_time_).InSecondsF() >
1.5f * audio_bus.frames() / audio_input_params().sample_rate()))
<< "Possibly frames were skipped, which may result in inaccuarate "
"timestamp calculation.";
last_capture_time_ = capture_time;
EncodeAudioImpl(audio_bus, capture_time);
}
base::TimeTicks AudioEncoder::ComputeTimestamp(
int num_frames,
base::TimeTicks capture_time) const {
return capture_time - AudioTimestampHelper::FramesToTime(
num_frames, audio_input_params_.sample_rate());
}
} // namespace media
// Copyright 2020 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 MEDIA_BASE_AUDIO_ENCODER_H_
#define MEDIA_BASE_AUDIO_ENCODER_H_
#include <memory>
#include "base/callback.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_parameters.h"
#include "media/base/media_export.h"
#include "media/base/status.h"
namespace media {
// Defines a move-only wrapper to hold the encoded audio data.
struct MEDIA_EXPORT EncodedAudioBuffer {
EncodedAudioBuffer(const AudioParameters& params,
std::unique_ptr<uint8_t[]> data,
size_t size,
base::TimeTicks timestamp);
EncodedAudioBuffer(EncodedAudioBuffer&&);
~EncodedAudioBuffer();
// The audio parameters the encoder used to encode the input audio. They may
// differ from the original parameters given to the encoder initially, as the
// encoder may convert the audio to a format more suitable for encoding.
const AudioParameters params;
// The buffer containing the encoded data.
std::unique_ptr<uint8_t[]> encoded_data;
// The size of the encoded data in the above buffer. Note that this is not
// necessarily equal to the capacity of the buffer. Some encoders allocate a
// bigger buffer and fill it only with |encoded_data_size| data without
// bothering to allocate another shrunk buffer and copy the data in, since the
// number of encoded bytes may not be known in advance.
const size_t encoded_data_size;
// The capture time of the first sample of the current AudioBus.
const base::TimeTicks timestamp;
};
// Defines an interface for audio encoders. Concrete encoders must implement the
// EncodeAudioImpl() function.
class MEDIA_EXPORT AudioEncoder {
public:
// Signature of the callback invoked to provide the encoded audio data. It is
// invoked on the same thread on which EncodeAudio() is called. The utility
// media::BindToCurrentLoop() can be used to create a callback that will be
// invoked on the same thread it is constructed on.
using EncodeCB = base::RepeatingCallback<void(EncodedAudioBuffer output)>;
// Signature of the callback to report errors.
using StatusCB = base::RepeatingCallback<void(Status error)>;
// Constructs the encoder given the audio parameters of the input to this
// encoder, and a callback to trigger to provide the encoded audio data.
// |input_params| must be valid, and |encode_callback| and |status_callback|
// must not be null callbacks. All calls to EncodeAudio() must happen on the
// same thread (usually an encoder thread), but the encoder itself can be
// constructed on any thread.
AudioEncoder(const AudioParameters& input_params,
EncodeCB encode_callback,
StatusCB status_callback);
AudioEncoder(const AudioEncoder&) = delete;
AudioEncoder& operator=(const AudioEncoder&) = delete;
virtual ~AudioEncoder();
const AudioParameters& audio_input_params() const {
return audio_input_params_;
}
// Performs various checks before calling EncodeAudioImpl() which does the
// actual encoding.
void EncodeAudio(const AudioBus& audio_bus, base::TimeTicks capture_time);
protected:
const EncodeCB& encode_callback() const { return encode_callback_; }
const StatusCB& status_callback() const { return status_callback_; }
base::TimeTicks last_capture_time() const { return last_capture_time_; }
virtual void EncodeAudioImpl(const AudioBus& audio_bus,
base::TimeTicks capture_time) = 0;
// Computes the timestamp of an AudioBus which has |num_frames| and was
// captured at |capture_time|. This timestamp is the capture time of the first
// sample in that AudioBus.
base::TimeTicks ComputeTimestamp(int num_frames,
base::TimeTicks capture_time) const;
private:
const AudioParameters audio_input_params_;
const EncodeCB encode_callback_;
const StatusCB status_callback_;
// The capture time of the most recent |audio_bus| delivered to
// EncodeAudio().
base::TimeTicks last_capture_time_;
THREAD_CHECKER(thread_checker_);
};
} // namespace media
#endif // MEDIA_BASE_AUDIO_ENCODER_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