blob: 18b331507dfa5acc1c5e226321379c814f3bf60e [file] [log] [blame]
// Copyright 2017 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 "third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.h"
#include "base/stl_util.h"
#include "media/base/audio_sample_types.h"
#include "media/base/audio_timestamp_helper.h"
namespace {
enum : int {
// 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.
kOpusMaxDataBytes = 4000,
// Opus preferred sampling rate for encoding. This is also the one WebM likes
// to have: https://wiki.xiph.org/MatroskaOpus.
kOpusPreferredSamplingRate = 48000,
// For Opus, we try to encode 60ms, the maximum Opus buffer, for quality
// reasons.
kOpusPreferredBufferDurationMs = 60,
// Maximum buffer multiplier for the AudioEncoders' AudioFifo. Recording is
// not real time, hence a certain buffering is allowed.
kMaxNumberOfFifoBuffers = 3,
};
// The amount of Frames in a 60 ms buffer @ 48000 samples/second.
const int kOpusPreferredFramesPerBuffer = kOpusPreferredSamplingRate *
kOpusPreferredBufferDurationMs /
base::Time::kMillisecondsPerSecond;
// Tries to encode |data_in|'s |num_samples| into |data_out|.
bool DoEncode(OpusEncoder* opus_encoder,
float* data_in,
int num_samples,
std::string* data_out) {
DCHECK_EQ(kOpusPreferredFramesPerBuffer, num_samples);
data_out->resize(kOpusMaxDataBytes);
const opus_int32 result = opus_encode_float(
opus_encoder, data_in, num_samples,
reinterpret_cast<uint8_t*>(base::data(*data_out)), kOpusMaxDataBytes);
if (result > 1) {
// TODO(ajose): Investigate improving this. http://crbug.com/547918
data_out->resize(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.
DLOG_IF(ERROR, result < 0) << " encode failed: " << opus_strerror(result);
return false;
}
} // anonymous namespace
namespace blink {
AudioTrackOpusEncoder::AudioTrackOpusEncoder(
OnEncodedAudioCB on_encoded_audio_cb,
int32_t bits_per_second,
bool vbr_enabled)
: AudioTrackEncoder(std::move(on_encoded_audio_cb)),
bits_per_second_(bits_per_second),
vbr_enabled_(vbr_enabled),
opus_encoder_(nullptr) {}
AudioTrackOpusEncoder::~AudioTrackOpusEncoder() {
// We don't DCHECK that we're on the encoder thread here, as it should have
// already been deleted at this point.
DestroyExistingOpusEncoder();
}
double AudioTrackOpusEncoder::ProvideInput(media::AudioBus* audio_bus,
uint32_t frames_delayed) {
fifo_->Consume(audio_bus, 0, audio_bus->frames());
return 1.0;
}
void AudioTrackOpusEncoder::OnSetFormat(
const media::AudioParameters& input_params) {
DVLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_THREAD(encoder_thread_checker_);
if (input_params_.Equals(input_params))
return;
DestroyExistingOpusEncoder();
if (!input_params.IsValid()) {
DLOG(ERROR) << "Invalid params: " << input_params.AsHumanReadableString();
return;
}
input_params_ = input_params;
input_params_.set_frames_per_buffer(input_params_.sample_rate() *
kOpusPreferredBufferDurationMs /
base::Time::kMillisecondsPerSecond);
// third_party/libopus supports up to 2 channels (see implementation of
// opus_encoder_create()): force |converted_params_| to at most those.
converted_params_ = media::AudioParameters(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::GuessChannelLayout(std::min(input_params_.channels(), 2)),
kOpusPreferredSamplingRate, kOpusPreferredFramesPerBuffer);
DVLOG(1) << "|input_params_|:" << input_params_.AsHumanReadableString()
<< " -->|converted_params_|:"
<< converted_params_.AsHumanReadableString();
converter_.reset(new media::AudioConverter(input_params_, converted_params_,
false /* disable_fifo */));
converter_->AddInput(this);
converter_->PrimeWithSilence();
fifo_.reset(new media::AudioFifo(
input_params_.channels(),
kMaxNumberOfFifoBuffers * input_params_.frames_per_buffer()));
buffer_.reset(new float[converted_params_.channels() *
converted_params_.frames_per_buffer()]);
// Initialize OpusEncoder.
int opus_result;
opus_encoder_ = opus_encoder_create(converted_params_.sample_rate(),
converted_params_.channels(),
OPUS_APPLICATION_AUDIO, &opus_result);
if (opus_result < 0) {
DLOG(ERROR) << "Couldn't init Opus encoder: " << opus_strerror(opus_result)
<< ", sample rate: " << converted_params_.sample_rate()
<< ", channels: " << converted_params_.channels();
return;
}
// Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
// variable bitrate up to 102kbps 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 opus_int32 bitrate =
(bits_per_second_ > 0) ? bits_per_second_ : OPUS_AUTO;
if (opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(bitrate)) != OPUS_OK) {
DLOG(ERROR) << "Failed to set Opus bitrate: " << bitrate;
return;
}
const opus_int32 vbr_enabled = static_cast<opus_int32>(vbr_enabled_);
if (opus_encoder_ctl(opus_encoder_, OPUS_SET_VBR(vbr_enabled)) != OPUS_OK) {
DLOG(ERROR) << "Failed to set Opus VBR mode: " << vbr_enabled;
return;
}
}
void AudioTrackOpusEncoder::EncodeAudio(
std::unique_ptr<media::AudioBus> input_bus,
base::TimeTicks capture_time) {
DVLOG(3) << __func__ << ", #frames " << input_bus->frames();
DCHECK_CALLED_ON_VALID_THREAD(encoder_thread_checker_);
DCHECK_EQ(input_bus->channels(), input_params_.channels());
DCHECK(!capture_time.is_null());
DCHECK(converter_);
if (!is_initialized() || paused_)
return;
// TODO(mcasas): Consider using a
// base::circular_deque<std::unique_ptr<AudioBus>> instead of an AudioFifo,
// to avoid copying data needlessly since we know the sizes of both input and
// output and they are multiples.
fifo_->Push(input_bus.get());
// Wait to have enough |input_bus|s to guarantee a satisfactory conversion,
// accounting for multiple calls to ProvideInput().
while (fifo_->frames() >= converter_->GetMaxInputFramesRequested(
kOpusPreferredFramesPerBuffer)) {
std::unique_ptr<media::AudioBus> audio_bus = media::AudioBus::Create(
converted_params_.channels(), kOpusPreferredFramesPerBuffer);
converter_->Convert(audio_bus.get());
audio_bus->ToInterleaved<media::Float32SampleTypeTraits>(
audio_bus->frames(), buffer_.get());
std::string encoded_data;
if (DoEncode(opus_encoder_, buffer_.get(), kOpusPreferredFramesPerBuffer,
&encoded_data)) {
const base::TimeTicks capture_time_of_first_sample =
capture_time - media::AudioTimestampHelper::FramesToTime(
input_bus->frames(), input_params_.sample_rate());
on_encoded_audio_cb_.Run(converted_params_, std::move(encoded_data),
capture_time_of_first_sample);
}
}
}
void AudioTrackOpusEncoder::DestroyExistingOpusEncoder() {
// We don't DCHECK that we're on the encoder thread here, as this could be
// called from the dtor (main thread) or from OnSetFormat() (encoder thread).
if (opus_encoder_) {
opus_encoder_destroy(opus_encoder_);
opus_encoder_ = nullptr;
}
}
} // namespace blink