#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include <cstring>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
#include "base/location.h"
#include "base/run_loop.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_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"
#if !defined(ARCH_CPU_ARM64)
#define HAS_AAC_ENCODER …
#endif
#endif
#if BUILDFLAG(IS_MAC) && BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/filters/mac/audio_toolbox_audio_encoder.h"
#define HAS_AAC_ENCODER …
#endif
#if BUILDFLAG(IS_ANDROID) && BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/gpu/android/ndk_audio_encoder.h"
#define HAS_AAC_ENCODER …
#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 = …;
constexpr base::TimeDelta kOpusBufferDuration = …;
#if HAS_AAC_ENCODER
constexpr int kAacFramesPerBuffer = 1024;
#endif
struct TestAudioParams { … };
constexpr TestAudioParams kTestAudioParamsOpus[] = …;
#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
std::string EncoderStatusCodeToString(EncoderStatus::Codes code) { … }
bool TimesAreNear(base::TimeTicks t1,
base::TimeTicks t2,
base::TimeDelta error) { … }
}
class AudioEncodersTest : public ::testing::TestWithParam<TestAudioParams> { … };
TEST_P(AudioEncodersTest, InitializeTwice) { … }
TEST_P(AudioEncodersTest, StopCallbackWrapping) { … }
TEST_P(AudioEncodersTest, EncodeWithoutInitialize) { … }
TEST_P(AudioEncodersTest, FlushWithoutInitialize) { … }
TEST_P(AudioEncodersTest, FlushWithNoInput) { … }
TEST_P(AudioEncodersTest, EncodeAndFlush) { … }
TEST_P(AudioEncodersTest, EncodeAndFlushTwice) { … }
TEST_P(AudioEncodersTest, ProvideInputAfterDoneCb) { … }
TEST_P(AudioEncodersTest, ManySmallInputs) { … }
TEST_P(AudioEncodersTest, Timestamps) { … }
TEST_P(AudioEncodersTest, TimeContinuityBreak) { … }
INSTANTIATE_TEST_SUITE_P(…);
#if HAS_AAC_ENCODER
INSTANTIATE_TEST_SUITE_P(AAC,
AudioEncodersTest,
testing::ValuesIn(kTestAudioParamsAAC));
#endif
class AudioOpusEncoderTest : public AudioEncodersTest { … };
TEST_P(AudioOpusEncoderTest, ExtraData) { … }
TEST_P(AudioOpusEncoderTest, FullCycleEncodeDecode) { … }
TEST_P(AudioOpusEncoderTest, FullCycleEncodeDecode_BitrateMode) { … }
TEST_P(AudioOpusEncoderTest, FullCycleEncodeDecode_OpusOptions) { … }
TEST_P(AudioOpusEncoderTest, VariableChannelCounts) { … }
INSTANTIATE_TEST_SUITE_P(…);
#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_IN_MIGRATION();
}
AudioDecoderConfig config(AudioCodec::kAAC, SampleFormat::kSampleFormatS16,
channel_layout, options_.sample_rate,
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, nullptr,
base::BindLambdaForTesting(init_cb),
base::BindLambdaForTesting(output_cb),
base::DoNothing());
}
protected:
MockMediaLog media_log;
std::unique_ptr<FFmpegAudioDecoder> decoder_;
int decoder_output_callback_count = 0;
#endif
};
#if BUILDFLAG(IS_WIN)
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));
decoder_->Decode(decoder_buffer, base::BindLambdaForTesting(decode_cb));
};
InitializeEncoder(base::BindLambdaForTesting(encode_output_cb));
ProduceAudioAndEncode();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
FlushAndVerifyStatus();
task_environment_.RunUntilIdle();
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);
}
TEST_P(AACAudioEncoderTest, FullCycleEncodeDecode_BitrateMode) {
constexpr AudioEncoder::BitrateMode kTestAacBitrateMode[] = {
AudioEncoder::BitrateMode::kConstant,
AudioEncoder::BitrateMode::kVariable};
for (const AudioEncoder::BitrateMode& bitrate_mode : kTestAacBitrateMode) {
decoder_output_callback_count = 0;
options_.bitrate_mode = bitrate_mode;
CreateEncoder();
InitializeDecoder();
auto encode_output_cb = [&](EncodedAudioBuffer output, MaybeDesc) {
auto decode_cb = [&](DecoderStatus status) {
EXPECT_EQ(status, DecoderStatus::Codes::kOk);
};
scoped_refptr<DecoderBuffer> decoder_buffer =
DecoderBuffer::FromArray(std::move(output.encoded_data));
decoder_->Decode(decoder_buffer, base::BindLambdaForTesting(decode_cb));
};
InitializeEncoder(base::BindLambdaForTesting(encode_output_cb));
ProduceAudioAndEncode();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
FlushAndVerifyStatus();
task_environment_.RunUntilIdle();
int expected_outputs =
3 + std::ceil(GetExpectedPadding() /
static_cast<double>(frames_per_buffer_));
EXPECT_EQ(expected_outputs, decoder_output_callback_count);
}
}
TEST_P(AACAudioEncoderTest, AacOutputFormat) {
constexpr AudioEncoder::AacOutputFormat kTestAacOutputFormat[] = {
AudioEncoder::AacOutputFormat::AAC, AudioEncoder::AacOutputFormat::ADTS};
for (const auto& output_format : kTestAacOutputFormat) {
options_.aac = {output_format};
CreateEncoder();
bool first_output = true;
const bool needs_description =
output_format == AudioEncoder::AacOutputFormat::AAC;
auto encode_output_cb = [&](EncodedAudioBuffer output,
MaybeDesc codec_description) {
if (first_output) {
first_output = false;
EXPECT_EQ(codec_description.has_value(), needs_description);
} else {
EXPECT_FALSE(codec_description);
}
};
InitializeEncoder(base::BindLambdaForTesting(encode_output_cb));
ProduceAudioAndEncode();
ProduceAudioAndEncode();
ProduceAudioAndEncode();
FlushAndVerifyStatus();
}
}
#endif
INSTANTIATE_TEST_SUITE_P(AAC,
AACAudioEncoderTest,
testing::ValuesIn(kTestAudioParamsAAC));
#endif
}