blob: 1f323bf2d6008e2412b9492715ae39c4d18b2c4e [file] [log] [blame]
// 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 <cstring>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/audio/audio_opus_encoder.h"
#include "media/audio/simple_sources.h"
#include "media/base/audio_encoder.h"
#include "media/base/audio_parameters.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/converting_audio_fifo.h"
#include "media/base/status.h"
#include "media/media_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/opus/src/include/opus.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_com_initializer.h"
#include "media/gpu/windows/mf_audio_encoder.h"
// The AAC tests are failing on Arm64. Disable the AAC part of these tests until
// those failures can be fixed. TOOO(https://crbug.com/1424215): FIx tests,
// and/or investigate if AAC support should be turned off in Chrome for Arm64
// Windows, or if these are an issue with the tests.
#if !defined(ARCH_CPU_ARM64)
#define HAS_AAC_ENCODER 1
#endif
#endif // IS_WIN
#if BUILDFLAG(IS_MAC) && BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/filters/mac/audio_toolbox_audio_encoder.h"
#define HAS_AAC_ENCODER 1
#endif
#if HAS_AAC_ENCODER
#include "media/base/audio_decoder.h"
#include "media/base/channel_layout.h"
#include "media/base/decoder_status.h"
#include "media/base/mock_media_log.h"
#include "media/filters/ffmpeg_audio_decoder.h"
#endif
namespace media {
namespace {
constexpr int kAudioSampleRateWithDelay = 647744;
// This is the preferred opus buffer duration (20 ms), which corresponds to a
// value of 960 frames per buffer at a sample rate of 48 khz.
constexpr base::TimeDelta kOpusBufferDuration = base::Milliseconds(20);
#if HAS_AAC_ENCODER
// AAC puts 1024 PCM samples into each AAC frame, which corresponds to a
// duration of 21 and 1/3 milliseconds at a sample rate of 48 khz.
constexpr int kAacFramesPerBuffer = 1024;
#endif // HAS_AAC_ENCODER
struct TestAudioParams {
const AudioCodec codec;
const int channels;
const int sample_rate;
};
constexpr TestAudioParams kTestAudioParamsOpus[] = {
{AudioCodec::kOpus, 2, 48000},
// Change to mono:
{AudioCodec::kOpus, 1, 48000},
// Different sampling rate as well:
{AudioCodec::kOpus, 1, 24000},
{AudioCodec::kOpus, 2, 8000},
// Using a non-default Opus sampling rate (48, 24, 16, 12, or 8 kHz).
{AudioCodec::kOpus, 1, 22050},
{AudioCodec::kOpus, 2, 44100},
{AudioCodec::kOpus, 2, 96000},
{AudioCodec::kOpus, 2, kAudioSampleRateWithDelay},
};
#if HAS_AAC_ENCODER
constexpr TestAudioParams kTestAudioParamsAAC[] = {
{AudioCodec::kAAC, 2, 48000}, {AudioCodec::kAAC, 6, 48000},
{AudioCodec::kAAC, 1, 48000}, {AudioCodec::kAAC, 2, 44100},
{AudioCodec::kAAC, 6, 44100}, {AudioCodec::kAAC, 1, 44100},
};
#endif // HAS_AAC_ENCODER
std::string EncoderStatusCodeToString(EncoderStatus::Codes code) {
switch (code) {
case EncoderStatus::Codes::kOk:
return "kOk";
case EncoderStatus::Codes::kEncoderInitializeNeverCompleted:
return "kEncoderInitializeNeverCompleted";
case EncoderStatus::Codes::kEncoderInitializeTwice:
return "kEncoderInitializeTwice";
case EncoderStatus::Codes::kEncoderFailedEncode:
return "kEncoderFailedEncode";
case EncoderStatus::Codes::kEncoderUnsupportedProfile:
return "kEncoderUnsupportedProfile";
case EncoderStatus::Codes::kEncoderUnsupportedCodec:
return "kEncoderUnsupportedCodec";
case EncoderStatus::Codes::kEncoderUnsupportedConfig:
return "kEncoderUnsupportedConfig";
case EncoderStatus::Codes::kEncoderInitializationError:
return "kEncoderInitializationError";
case EncoderStatus::Codes::kEncoderFailedFlush:
return "kEncoderFailedFlush";
case EncoderStatus::Codes::kEncoderMojoConnectionError:
return "kEncoderMojoConnectionError";
default:
NOTREACHED();
return "default";
}
}
bool TimesAreNear(base::TimeTicks t1,
base::TimeTicks t2,
base::TimeDelta error) {
return (t1 - t2).magnitude() <= error;
}
} // namespace
class AudioEncodersTest : public ::testing::TestWithParam<TestAudioParams> {
public:
AudioEncodersTest()
: audio_source_(GetParam().channels,
/*freq=*/440,
GetParam().sample_rate) {
options_.codec = GetParam().codec;
options_.sample_rate = GetParam().sample_rate;
options_.channels = GetParam().channels;
expected_duration_helper_ =
std::make_unique<AudioTimestampHelper>(options_.sample_rate);
expected_duration_helper_->SetBaseTimestamp(base::Microseconds(0));
}
AudioEncodersTest(const AudioEncodersTest&) = delete;
AudioEncodersTest& operator=(const AudioEncodersTest&) = delete;
~AudioEncodersTest() override = default;
using MaybeDesc = absl::optional<AudioEncoder::CodecDescription>;
AudioEncoder* encoder() const { return encoder_.get(); }
bool EncoderHasDelay() const {
return options_.sample_rate == kAudioSampleRateWithDelay;
}
void SetUp() override {
if (options_.codec == AudioCodec::kOpus) {
encoder_ = std::make_unique<AudioOpusEncoder>();
buffer_duration_ = kOpusBufferDuration;
frames_per_buffer_ = AudioTimestampHelper::TimeToFrames(
buffer_duration_, options_.sample_rate);
} else if (options_.codec == AudioCodec::kAAC) {
#if BUILDFLAG(IS_WIN) && HAS_AAC_ENCODER
EXPECT_TRUE(com_initializer_.Succeeded());
ASSERT_TRUE(base::SequencedTaskRunner::HasCurrentDefault());
encoder_ = std::make_unique<MFAudioEncoder>(
base::SequencedTaskRunner::GetCurrentDefault());
frames_per_buffer_ = kAacFramesPerBuffer;
buffer_duration_ = AudioTimestampHelper::FramesToTime(
frames_per_buffer_, options_.sample_rate);
#elif HAS_AAC_ENCODER && BUILDFLAG(IS_MAC)
encoder_ = std::make_unique<AudioToolboxAudioEncoder>();
frames_per_buffer_ = kAacFramesPerBuffer;
buffer_duration_ = AudioTimestampHelper::FramesToTime(
frames_per_buffer_, options_.sample_rate);
#else
NOTREACHED();
#endif
} else {
NOTREACHED();
}
min_number_input_frames_needed_ = frames_per_buffer_;
}
void InitializeEncoder(
AudioEncoder::OutputCB output_cb = base::NullCallback()) {
if (!output_cb) {
output_cb =
base::BindLambdaForTesting([&](EncodedAudioBuffer output, MaybeDesc) {
observed_output_duration_ += output.duration;
});
}
bool called_done = false;
AudioEncoder::EncoderStatusCB done_cb =
base::BindLambdaForTesting([&](EncoderStatus error) {
if (!error.is_ok()) {
FAIL() << "Error code: " << EncoderStatusCodeToString(error.code())
<< "\nError message: " << error.message();
}
called_done = true;
});
encoder_->Initialize(options_, std::move(output_cb), std::move(done_cb));
RunLoop();
EXPECT_TRUE(called_done);
if (options_.codec == AudioCodec::kOpus) {
min_number_input_frames_needed_ =
reinterpret_cast<AudioOpusEncoder*>(encoder_.get())
->fifo_->min_number_input_frames_needed_for_testing();
}
}
// Produces an audio data with |num_frames| frames. The produced data is sent
// to |encoder_| to be encoded, and the number of frames generated is
// returned.
int ProduceAudioAndEncode(
base::TimeTicks timestamp = base::TimeTicks::Now(),
int num_frames = 0,
AudioEncoder::EncoderStatusCB done_cb = base::NullCallback()) {
DCHECK(encoder_);
if (num_frames == 0)
num_frames = frames_per_buffer_;
auto audio_bus = AudioBus::Create(options_.channels, num_frames);
audio_source_.OnMoreData(base::TimeDelta(), timestamp, {}, audio_bus.get());
DoEncode(std::move(audio_bus), timestamp, std::move(done_cb));
return num_frames;
}
void DoEncode(std::unique_ptr<AudioBus> audio_bus,
base::TimeTicks timestamp,
AudioEncoder::EncoderStatusCB done_cb = base::NullCallback()) {
if (!done_cb) {
pending_callback_results_.emplace_back();
done_cb = base::BindLambdaForTesting([&](EncoderStatus error) {
if (!error.is_ok()) {
FAIL() << "Error code: " << EncoderStatusCodeToString(error.code())
<< "\nError message: " << error.message();
}
pending_callback_results_[pending_callback_count_].status_code =
error.code();
pending_callback_results_[pending_callback_count_].completed = true;
pending_callback_count_++;
});
}
int num_frames = audio_bus->frames();
encoder_->Encode(std::move(audio_bus), timestamp, std::move(done_cb));
expected_output_duration_ +=
expected_duration_helper_->GetFrameDuration(num_frames);
expected_duration_helper_->AddFrames(num_frames);
}
void FlushAndVerifyStatus(
EncoderStatus::Codes status_code = EncoderStatus::Codes::kOk) {
bool flush_done = false;
auto flush_done_cb = base::BindLambdaForTesting([&](EncoderStatus error) {
if (error.code() != status_code) {
FAIL() << "Expected " << EncoderStatusCodeToString(status_code)
<< " but got " << EncoderStatusCodeToString(error.code());
}
flush_done = true;
});
encoder()->Flush(std::move(flush_done_cb));
RunLoop();
EXPECT_TRUE(flush_done);
}
void ValidateDoneCallbacksRun() {
for (auto callback_result : pending_callback_results_) {
EXPECT_TRUE(callback_result.completed);
EXPECT_EQ(callback_result.status_code, EncoderStatus::Codes::kOk);
}
}
// The amount of front padding that the encoder emits.
size_t GetExpectedPadding() {
#if BUILDFLAG(IS_MAC)
if (options_.codec == AudioCodec::kAAC)
return 2112;
#endif
return 0;
}
void ValidateOutputDuration(int64_t flush_count = 1) {
// Since encoders can only output buffers of size `frames_per_buffer_`, the
// number of outputs will be larger than the number of inputs.
int64_t frame_remainder =
frames_per_buffer_ -
(expected_duration_helper_->frame_count() % frames_per_buffer_);
int64_t amount_of_padding = GetExpectedPadding() + frame_remainder;
// Padding is re-emitted after each flush.
amount_of_padding *= flush_count;
int64_t number_of_outputs = std::ceil(
(expected_duration_helper_->frame_count() + amount_of_padding) /
static_cast<double>(frames_per_buffer_));
int64_t duration_of_padding_us =
number_of_outputs * AudioTimestampHelper::FramesToTime(
frames_per_buffer_, options_.sample_rate)
.InMicroseconds();
int64_t acceptable_diff = duration_of_padding_us + 10;
EXPECT_NEAR(expected_output_duration_.InMicroseconds(),
observed_output_duration_.InMicroseconds(), acceptable_diff);
}
void RunLoop() { run_loop_.RunUntilIdle(); }
base::test::TaskEnvironment task_environment_;
base::RunLoop run_loop_;
#if BUILDFLAG(IS_WIN)
::base::win::ScopedCOMInitializer com_initializer_;
#endif // BUILDFLAG(IS_WIN)
// The input params as initialized from the test's parameter.
AudioEncoder::Options options_;
// The audio source used to generate data to give to the |encoder_|.
SineWaveAudioSource audio_source_;
// The encoder the test is verifying.
std::unique_ptr<AudioEncoder> encoder_;
base::TimeDelta buffer_duration_;
int frames_per_buffer_;
int min_number_input_frames_needed_;
std::unique_ptr<AudioTimestampHelper> expected_duration_helper_;
base::TimeDelta expected_output_duration_;
base::TimeDelta observed_output_duration_;
struct CallbackResult {
bool completed = false;
EncoderStatus::Codes status_code;
};
int pending_callback_count_ = 0;
std::vector<CallbackResult> pending_callback_results_;
};
TEST_P(AudioEncodersTest, InitializeTwice) {
InitializeEncoder();
bool called_done = false;
auto done_cb = base::BindLambdaForTesting([&](EncoderStatus error) {
if (error.code() != EncoderStatus::Codes::kEncoderInitializeTwice)
FAIL() << "Expected kEncoderInitializeTwice error but got "
<< EncoderStatusCodeToString(error.code());
called_done = true;
});
encoder_->Initialize(options_, base::DoNothing(), std::move(done_cb));
RunLoop();
EXPECT_TRUE(called_done);
}
TEST_P(AudioEncodersTest, StopCallbackWrapping) {
bool called_done = false;
AudioEncoder::EncoderStatusCB done_cb = base::BindLambdaForTesting(
[&](EncoderStatus error) { called_done = true; });
encoder_->DisablePostedCallbacks();
encoder_->Initialize(options_, base::DoNothing(), std::move(done_cb));
EXPECT_TRUE(called_done);
}
TEST_P(AudioEncodersTest, EncodeWithoutInitialize) {
bool called_done = false;
auto done_cb = base::BindLambdaForTesting([&](EncoderStatus error) {
if (error.code() != EncoderStatus::Codes::kEncoderInitializeNeverCompleted)
FAIL() << "Expected kEncoderInitializeNeverCompleted error but got "
<< EncoderStatusCodeToString(error.code());
called_done = true;
});
auto audio_bus = AudioBus::Create(options_.channels, /*num_frames=*/1);
encoder()->Encode(std::move(audio_bus), base::TimeTicks::Now(),
std::move(done_cb));
RunLoop();
EXPECT_TRUE(called_done);
}
TEST_P(AudioEncodersTest, FlushWithoutInitialize) {
FlushAndVerifyStatus(EncoderStatus::Codes::kEncoderInitializeNeverCompleted);
}
TEST_P(AudioEncodersTest, FlushWithNoInput) {
InitializeEncoder();
FlushAndVerifyStatus();
}
TEST_P(AudioEncodersTest, EncodeAndFlush) {
if (EncoderHasDelay())
return;
InitializeEncoder();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
FlushAndVerifyStatus();
ValidateDoneCallbacksRun();
ValidateOutputDuration();
}
TEST_P(AudioEncodersTest, EncodeAndFlushTwice) {
if (EncoderHasDelay())
return;
InitializeEncoder();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
bool called_flush1 = false;
auto flush_cb = base::BindLambdaForTesting([&](EncoderStatus error) {
if (error.code() != EncoderStatus::Codes::kOk)
FAIL() << "Expected kOk but got "
<< EncoderStatusCodeToString(error.code());
called_flush1 = true;
});
encoder()->Flush(std::move(flush_cb));
ProduceAudioAndEncode();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
bool called_flush2 = false;
flush_cb = base::BindLambdaForTesting([&](EncoderStatus error) {
if (error.code() != EncoderStatus::Codes::kOk)
FAIL() << "Expected kOk but got "
<< EncoderStatusCodeToString(error.code());
called_flush2 = true;
});
RunLoop();
EXPECT_TRUE(called_flush1);
encoder()->Flush(std::move(flush_cb));
RunLoop();
EXPECT_TRUE(called_flush2);
ValidateDoneCallbacksRun();
ValidateOutputDuration(/*flush_count=*/2);
}
// Instead of synchronously calling `Encode`, wait until `done_cb` is invoked
// before we provide more input.
TEST_P(AudioEncodersTest, ProvideInputAfterDoneCb) {
if (EncoderHasDelay())
return;
InitializeEncoder();
bool called_done = false;
auto done_lambda = [&](EncoderStatus error) {
if (error.code() != EncoderStatus::Codes::kOk)
FAIL() << "Expected kOk but got "
<< EncoderStatusCodeToString(error.code());
called_done = true;
};
AudioEncoder::EncoderStatusCB done_cb =
base::BindLambdaForTesting(done_lambda);
ProduceAudioAndEncode(base::TimeTicks(), frames_per_buffer_,
std::move(done_cb));
RunLoop();
EXPECT_TRUE(called_done);
called_done = false;
done_cb = base::BindLambdaForTesting(done_lambda);
ProduceAudioAndEncode(base::TimeTicks(), frames_per_buffer_,
std::move(done_cb));
RunLoop();
EXPECT_TRUE(called_done);
called_done = false;
done_cb = base::BindLambdaForTesting(done_lambda);
ProduceAudioAndEncode(base::TimeTicks(), frames_per_buffer_,
std::move(done_cb));
RunLoop();
EXPECT_TRUE(called_done);
FlushAndVerifyStatus();
ValidateOutputDuration();
}
TEST_P(AudioEncodersTest, ManySmallInputs) {
if (EncoderHasDelay())
return;
InitializeEncoder();
base::TimeTicks timestamp = base::TimeTicks::Now();
int frame_count = frames_per_buffer_ / 10;
for (int i = 0; i < 100; i++)
ProduceAudioAndEncode(timestamp, frame_count);
FlushAndVerifyStatus();
ValidateDoneCallbacksRun();
ValidateOutputDuration();
}
// Check that the encoder's timestamps don't drift from the expected timestamp
// over multiple inputs.
TEST_P(AudioEncodersTest, Timestamps) {
if (EncoderHasDelay())
return;
std::vector<base::TimeTicks> timestamps;
AudioEncoder::OutputCB output_cb =
base::BindLambdaForTesting([&](EncodedAudioBuffer output, MaybeDesc) {
timestamps.push_back(output.timestamp);
});
InitializeEncoder(std::move(output_cb));
constexpr int kCount = 12;
// Try to encode buffers of different durations. The timestamps of each output
// should increase by `buffer_duration_` regardless of the size of the input.
for (base::TimeDelta duration :
{buffer_duration_ * 10, buffer_duration_, buffer_duration_ * 2 / 3}) {
timestamps.clear();
int num_frames =
AudioTimestampHelper::TimeToFrames(duration, options_.sample_rate);
size_t total_frames = num_frames * kCount;
#if HAS_AAC_ENCODER
if (options_.codec == AudioCodec::kAAC &&
total_frames % kAacFramesPerBuffer) {
// We send data in chunks of kAacFramesPerBuffer to the encoder, padding
// it with silence when flushing.
// Round `total_frames` up to the nearest multiple of kAacFramesPerBuffer.
int chunks = (total_frames / kAacFramesPerBuffer) + 1;
total_frames = chunks * kAacFramesPerBuffer;
}
#endif
total_frames += GetExpectedPadding();
base::TimeTicks current_timestamp;
for (int i = 0; i < kCount; ++i) {
ProduceAudioAndEncode(current_timestamp, num_frames);
current_timestamp += duration;
}
FlushAndVerifyStatus();
ValidateDoneCallbacksRun();
// The encoder will have multiple outputs per input if `num_frames` is
// larger than `frames_per_buffer_`, and fewer outputs per input if it is
// smaller.
size_t expected_outputs = total_frames / frames_per_buffer_;
// The encoder might output an extra buffer, due to padding.
EXPECT_TRUE(timestamps.size() == expected_outputs ||
timestamps.size() == expected_outputs + 1);
// We must use an `AudioTimestampHelper` to verify the returned timestamps
// to avoid rounding errors.
AudioTimestampHelper timestamp_tracker(options_.sample_rate);
timestamp_tracker.SetBaseTimestamp(base::Microseconds(0));
for (auto& observed_ts : timestamps) {
base::TimeTicks expected_ts =
timestamp_tracker.GetTimestamp() + base::TimeTicks();
EXPECT_TRUE(TimesAreNear(expected_ts, observed_ts, base::Microseconds(1)))
<< "expected_ts: " << expected_ts << ", observed_ts: " << observed_ts;
timestamp_tracker.AddFrames(frames_per_buffer_);
}
}
}
// Check how the encoder reacts to breaks in continuity of incoming sound.
// Under normal circumstances capture times are expected to be exactly
// a buffer's duration apart, but if they are not, the encoder just ignores
// incoming capture times. In other words the only capture times that matter
// are
// 1. timestamp of the first encoded buffer
// 2. timestamps of buffers coming immediately after Flush() calls.
TEST_P(AudioEncodersTest, TimeContinuityBreak) {
if (EncoderHasDelay())
return;
std::vector<base::TimeTicks> timestamps;
AudioEncoder::OutputCB output_cb =
base::BindLambdaForTesting([&](EncodedAudioBuffer output, MaybeDesc) {
timestamps.push_back(output.timestamp);
});
InitializeEncoder(std::move(output_cb));
// Encode first normal buffer.
base::TimeTicks current_timestamp = base::TimeTicks::Now();
auto ts0 = current_timestamp;
ProduceAudioAndEncode(current_timestamp);
current_timestamp += buffer_duration_;
// Encode another buffer after a large gap, output timestamp should
// disregard the gap.
auto ts1 = current_timestamp;
current_timestamp += base::Microseconds(1500);
ProduceAudioAndEncode(current_timestamp);
current_timestamp += buffer_duration_;
// Another buffer without a gap.
auto ts2 = ts1 + buffer_duration_;
ProduceAudioAndEncode(current_timestamp);
FlushAndVerifyStatus();
ASSERT_LE(3u, timestamps.size());
EXPECT_TRUE(TimesAreNear(ts0, timestamps[0], base::Microseconds(1)));
EXPECT_TRUE(TimesAreNear(ts1, timestamps[1], base::Microseconds(1)));
EXPECT_TRUE(TimesAreNear(ts2, timestamps[2], base::Microseconds(1)));
timestamps.clear();
// Reset output timestamp after Flush(), the encoder should start producing
// timestamps from new base 0.
current_timestamp = base::TimeTicks();
auto ts3 = current_timestamp;
ProduceAudioAndEncode(current_timestamp);
current_timestamp += buffer_duration_;
auto ts4 = current_timestamp;
ProduceAudioAndEncode(current_timestamp);
FlushAndVerifyStatus();
ASSERT_LE(2u, timestamps.size());
EXPECT_TRUE(TimesAreNear(ts3, timestamps[0], base::Microseconds(1)));
EXPECT_TRUE(TimesAreNear(ts4, timestamps[1], base::Microseconds(1)));
ValidateDoneCallbacksRun();
}
INSTANTIATE_TEST_SUITE_P(Opus,
AudioEncodersTest,
testing::ValuesIn(kTestAudioParamsOpus));
#if HAS_AAC_ENCODER
INSTANTIATE_TEST_SUITE_P(AAC,
AudioEncodersTest,
testing::ValuesIn(kTestAudioParamsAAC));
#endif // HAS_AAC_ENCODER
class AudioOpusEncoderTest : public AudioEncodersTest {
public:
AudioOpusEncoderTest() { options_.codec = AudioCodec::kOpus; }
AudioOpusEncoderTest(const AudioOpusEncoderTest&) = delete;
AudioOpusEncoderTest& operator=(const AudioOpusEncoderTest&) = delete;
~AudioOpusEncoderTest() override = default;
void SetUp() override { AudioEncodersTest::SetUp(); }
};
TEST_P(AudioOpusEncoderTest, ExtraData) {
if (EncoderHasDelay())
return;
std::vector<uint8_t> extra;
AudioEncoder::OutputCB output_cb = base::BindLambdaForTesting(
[&](EncodedAudioBuffer output, MaybeDesc desc) {
DCHECK(desc.has_value());
extra = desc.value();
});
InitializeEncoder(std::move(output_cb));
ProduceAudioAndEncode(base::TimeTicks::Now(),
min_number_input_frames_needed_);
RunLoop();
ASSERT_GT(extra.size(), 0u);
EXPECT_EQ(extra[0], 'O');
EXPECT_EQ(extra[1], 'p');
EXPECT_EQ(extra[2], 'u');
EXPECT_EQ(extra[3], 's');
uint16_t* sample_rate_ptr = reinterpret_cast<uint16_t*>(extra.data() + 12);
if (options_.sample_rate < std::numeric_limits<uint16_t>::max())
EXPECT_EQ(*sample_rate_ptr, options_.sample_rate);
else
EXPECT_EQ(*sample_rate_ptr, 48000);
uint8_t* channels_ptr = reinterpret_cast<uint8_t*>(extra.data() + 9);
EXPECT_EQ(*channels_ptr, options_.channels);
uint16_t* skip_ptr = reinterpret_cast<uint16_t*>(extra.data() + 10);
EXPECT_GT(*skip_ptr, 0);
}
TEST_P(AudioOpusEncoderTest, FullCycleEncodeDecode) {
const int kOpusDecoderSampleRate = 48000;
const int kOpusDecoderFramesPerBuffer = AudioTimestampHelper::TimeToFrames(
kOpusBufferDuration, kOpusDecoderSampleRate);
int error;
OpusDecoder* opus_decoder =
opus_decoder_create(kOpusDecoderSampleRate, options_.channels, &error);
ASSERT_TRUE(error == OPUS_OK && opus_decoder);
int encode_callback_count = 0;
std::vector<float> buffer(kOpusDecoderFramesPerBuffer * options_.channels);
auto verify_opus_encoding = [&](EncodedAudioBuffer output, MaybeDesc) {
++encode_callback_count;
// Use the libopus decoder to decode the |encoded_data| and check we
// get the expected number of frames per buffer.
EXPECT_EQ(kOpusDecoderFramesPerBuffer,
opus_decode_float(opus_decoder, output.encoded_data.get(),
output.encoded_data_size, buffer.data(),
kOpusDecoderFramesPerBuffer, 0));
};
InitializeEncoder(base::BindLambdaForTesting(verify_opus_encoding));
base::TimeTicks time;
int total_frames = 0;
// Push data until we have a decoded output.
while (total_frames < min_number_input_frames_needed_) {
total_frames += ProduceAudioAndEncode(time);
time += buffer_duration_;
RunLoop();
}
EXPECT_GE(total_frames, frames_per_buffer_);
EXPECT_EQ(1, encode_callback_count);
// Flush the leftover data in the encoder, due to encoder delay.
FlushAndVerifyStatus();
opus_decoder_destroy(opus_decoder);
opus_decoder = nullptr;
}
// Tests we can configure the AudioOpusEncoder's extra options.
TEST_P(AudioOpusEncoderTest, FullCycleEncodeDecode_OpusOptions) {
// TODO(crbug.com/1378399): Test an OpusOptions::frame_duration which forces
// repacketization.
constexpr media::AudioEncoder::OpusOptions kTestOpusOptions[] = {
// Base case
{.frame_duration = base::Milliseconds(20),
.complexity = 10,
.packet_loss_perc = 0,
.use_in_band_fec = false,
.use_dtx = false},
// Use inband-FEC
{.frame_duration = base::Microseconds(2500),
.complexity = 0,
.packet_loss_perc = 10,
.use_in_band_fec = true,
.use_dtx = false},
// Use DTX
{.frame_duration = base::Milliseconds(60),
.complexity = 5,
.packet_loss_perc = 0,
.use_in_band_fec = false,
.use_dtx = true},
// Use inband-FEC and DTX
{.frame_duration = base::Milliseconds(5),
.complexity = 5,
.packet_loss_perc = 20,
.use_in_band_fec = true,
.use_dtx = true},
};
for (const AudioEncoder::OpusOptions& opus_options : kTestOpusOptions) {
const int kOpusDecoderSampleRate = 48000;
// Override the work done in Setup().
encoder_ = std::make_unique<AudioOpusEncoder>();
options_.opus = opus_options;
buffer_duration_ = opus_options.frame_duration;
frames_per_buffer_ = AudioTimestampHelper::TimeToFrames(
buffer_duration_, options_.sample_rate);
int decoder_frames_per_buffer = AudioTimestampHelper::TimeToFrames(
buffer_duration_, kOpusDecoderSampleRate);
int error;
OpusDecoder* opus_decoder =
opus_decoder_create(kOpusDecoderSampleRate, options_.channels, &error);
ASSERT_TRUE(error == OPUS_OK && opus_decoder);
std::vector<float> buffer(decoder_frames_per_buffer * options_.channels);
auto verify_opus_encoding = [&](EncodedAudioBuffer output, MaybeDesc) {
// Use the libopus decoder to decode the |encoded_data| and check we
// get the expected number of frames per buffer.
EXPECT_EQ(decoder_frames_per_buffer,
opus_decode_float(opus_decoder, output.encoded_data.get(),
output.encoded_data_size, buffer.data(),
decoder_frames_per_buffer, 0));
};
InitializeEncoder(base::BindLambdaForTesting(verify_opus_encoding));
base::TimeTicks time;
int total_frames = 0;
// Push data until we have a decoded output.
while (total_frames < min_number_input_frames_needed_) {
total_frames += ProduceAudioAndEncode(time);
time += buffer_duration_;
RunLoop();
}
EXPECT_GE(total_frames, frames_per_buffer_);
FlushAndVerifyStatus();
opus_decoder_destroy(opus_decoder);
opus_decoder = nullptr;
}
}
TEST_P(AudioOpusEncoderTest, VariableChannelCounts) {
constexpr int kTestToneFrequency = 440;
SineWaveAudioSource sources[] = {
SineWaveAudioSource(1, kTestToneFrequency, options_.sample_rate),
SineWaveAudioSource(2, kTestToneFrequency, options_.sample_rate),
SineWaveAudioSource(3, kTestToneFrequency, options_.sample_rate)};
const int num_frames = options_.sample_rate * buffer_duration_.InSecondsF();
auto generate_audio = [&sources, &num_frames](
int channel_count,
base::TimeTicks current_timestamp) {
auto audio_bus = AudioBus::Create(channel_count, num_frames);
sources[channel_count - 1].OnMoreData(base::TimeDelta(), current_timestamp,
{}, audio_bus.get());
return audio_bus;
};
// Superpermutation of {1, 2, 3}, covering all transitions between upmixing,
// downmixing and not mixing.
const int kChannelCountSequence[] = {1, 2, 3, 1, 2, 2, 1, 3, 2, 1};
// Override |GetParam().channels|, to ensure that we can both upmix and
// downmix.
options_.channels = 2;
auto empty_output_cb =
base::BindLambdaForTesting([&](EncodedAudioBuffer output, MaybeDesc) {});
InitializeEncoder(std::move(empty_output_cb));
base::TimeTicks current_timestamp;
for (const int& ch : kChannelCountSequence) {
// Encode, using a different number of channels each time.
DoEncode(generate_audio(ch, current_timestamp), current_timestamp);
current_timestamp += buffer_duration_;
}
FlushAndVerifyStatus();
}
INSTANTIATE_TEST_SUITE_P(Opus,
AudioOpusEncoderTest,
testing::ValuesIn(kTestAudioParamsOpus));
#if HAS_AAC_ENCODER
class AACAudioEncoderTest : public AudioEncodersTest {
public:
AACAudioEncoderTest() = default;
AACAudioEncoderTest(const AACAudioEncoderTest&) = delete;
AACAudioEncoderTest& operator=(const AACAudioEncoderTest&) = delete;
~AACAudioEncoderTest() override = default;
#if BUILDFLAG(ENABLE_FFMPEG) && BUILDFLAG(USE_PROPRIETARY_CODECS)
void InitializeDecoder() {
decoder_ = std::make_unique<FFmpegAudioDecoder>(
base::SequencedTaskRunner::GetCurrentDefault(), &media_log);
ChannelLayout channel_layout = CHANNEL_LAYOUT_NONE;
switch (options_.channels) {
case 1:
channel_layout = CHANNEL_LAYOUT_MONO;
break;
case 2:
channel_layout = CHANNEL_LAYOUT_STEREO;
break;
case 6:
channel_layout = CHANNEL_LAYOUT_5_1_BACK;
break;
default:
NOTREACHED();
}
AudioDecoderConfig config(AudioCodec::kAAC, SampleFormat::kSampleFormatS16,
channel_layout, options_.sample_rate,
/*extra_data=*/std::vector<uint8_t>(),
EncryptionScheme::kUnencrypted);
auto init_cb = [](DecoderStatus decoder_status) {
EXPECT_EQ(decoder_status, DecoderStatus::Codes::kOk);
};
auto output_cb = [&](scoped_refptr<AudioBuffer> decoded_buffer) {
++decoder_output_callback_count;
EXPECT_EQ(decoded_buffer->frame_count(), frames_per_buffer_);
};
decoder_->Initialize(config, /*cdm_context=*/nullptr,
base::BindLambdaForTesting(init_cb),
base::BindLambdaForTesting(output_cb),
/*waiting_cb=*/base::DoNothing());
}
protected:
MockMediaLog media_log;
std::unique_ptr<FFmpegAudioDecoder> decoder_;
int decoder_output_callback_count = 0;
#endif // BUILDFLAG(ENABLE_FFMPEG) && BUILDFLAG(USE_PROPRIETARY_CODECS)
};
#if BUILDFLAG(IS_WIN)
// `MFAudioEncoder` requires `kMinSamplesForOutput` before `Flush` can be called
// successfully.
TEST_P(AACAudioEncoderTest, FlushWithTooLittleInput) {
InitializeEncoder(base::DoNothing());
ProduceAudioAndEncode();
FlushAndVerifyStatus(EncoderStatus::Codes::kEncoderFailedFlush);
ValidateDoneCallbacksRun();
}
#endif
#if BUILDFLAG(ENABLE_FFMPEG) && BUILDFLAG(USE_PROPRIETARY_CODECS)
TEST_P(AACAudioEncoderTest, FullCycleEncodeDecode) {
InitializeDecoder();
int encode_output_callback_count = 0;
int decode_status_callback_count = 0;
auto encode_output_cb = [&](EncodedAudioBuffer output, MaybeDesc) {
++encode_output_callback_count;
auto decode_cb = [&](DecoderStatus status) {
++decode_status_callback_count;
EXPECT_EQ(status, DecoderStatus::Codes::kOk);
};
scoped_refptr<DecoderBuffer> decoder_buffer = DecoderBuffer::FromArray(
std::move(output.encoded_data), output.encoded_data_size);
decoder_->Decode(decoder_buffer, base::BindLambdaForTesting(decode_cb));
};
InitializeEncoder(base::BindLambdaForTesting(encode_output_cb));
ProduceAudioAndEncode();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
FlushAndVerifyStatus();
int expected_outputs = 3 + std::ceil(GetExpectedPadding() /
static_cast<double>(frames_per_buffer_));
EXPECT_EQ(expected_outputs, encode_output_callback_count);
EXPECT_EQ(expected_outputs, decode_status_callback_count);
EXPECT_EQ(expected_outputs, decoder_output_callback_count);
}
#endif // BUILDFLAG(ENABLE_FFMPEG) && BUILDFLAG(USE_PROPRIETARY_CODECS)
INSTANTIATE_TEST_SUITE_P(AAC,
AACAudioEncoderTest,
testing::ValuesIn(kTestAudioParamsAAC));
#endif // HAS_AAC_ENCODER
} // namespace media