blob: a2e1e088e6e1222dfafd8cfd0f172ffe6e99c3c8 [file] [log] [blame]
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <array>
#include <map>
#include <memory>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "api/audio_codecs/isac/audio_decoder_isac_fix.h"
#include "api/audio_codecs/isac/audio_decoder_isac_float.h"
#include "api/audio_codecs/isac/audio_encoder_isac_fix.h"
#include "api/audio_codecs/isac/audio_encoder_isac_float.h"
#include "modules/audio_coding/test/PCMFile.h"
#include "rtc_base/checks.h"
#include "rtc_base/strings/string_builder.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
namespace webrtc {
namespace {
constexpr int kPayloadType = 42;
enum class IsacImpl { kFixed, kFloat };
absl::string_view IsacImplToString(IsacImpl impl) {
switch (impl) {
case IsacImpl::kFixed:
return "fixed";
case IsacImpl::kFloat:
return "float";
}
}
std::unique_ptr<PCMFile> GetPcmTestFileReader(int sample_rate_hz) {
std::string filename;
switch (sample_rate_hz) {
case 16000:
filename = test::ResourcePath("audio_coding/testfile16kHz", "pcm");
break;
case 32000:
filename = test::ResourcePath("audio_coding/testfile32kHz", "pcm");
break;
default:
RTC_NOTREACHED() << "No test file available for " << sample_rate_hz
<< " Hz.";
}
auto pcm_file = std::make_unique<PCMFile>();
pcm_file->ReadStereo(false);
pcm_file->Open(filename, sample_rate_hz, "rb", /*auto_rewind=*/true);
pcm_file->FastForward(/*num_10ms_blocks=*/100); // Skip initial silence.
RTC_CHECK(!pcm_file->EndOfFile());
return pcm_file;
}
// Returns a view to the interleaved samples of an AudioFrame object.
rtc::ArrayView<const int16_t> AudioFrameToView(const AudioFrame& audio_frame) {
return {audio_frame.data(),
audio_frame.samples_per_channel() * audio_frame.num_channels()};
}
std::unique_ptr<AudioEncoder> CreateEncoder(IsacImpl impl,
int sample_rate_hz,
int frame_size_ms,
int bitrate_bps) {
RTC_CHECK(sample_rate_hz == 16000 || sample_rate_hz == 32000);
RTC_CHECK(frame_size_ms == 30 || frame_size_ms == 60);
RTC_CHECK_GT(bitrate_bps, 0);
switch (impl) {
case IsacImpl::kFixed: {
AudioEncoderIsacFix::Config config;
config.bit_rate = bitrate_bps;
config.frame_size_ms = frame_size_ms;
RTC_CHECK_EQ(16000, sample_rate_hz);
return AudioEncoderIsacFix::MakeAudioEncoder(config, kPayloadType);
}
case IsacImpl::kFloat: {
AudioEncoderIsacFloat::Config config;
config.bit_rate = bitrate_bps;
config.frame_size_ms = frame_size_ms;
config.sample_rate_hz = sample_rate_hz;
return AudioEncoderIsacFloat::MakeAudioEncoder(config, kPayloadType);
}
}
}
std::unique_ptr<AudioDecoder> CreateDecoder(IsacImpl impl, int sample_rate_hz) {
RTC_CHECK(sample_rate_hz == 16000 || sample_rate_hz == 32000);
switch (impl) {
case IsacImpl::kFixed: {
webrtc::AudioDecoderIsacFix::Config config;
RTC_CHECK_EQ(16000, sample_rate_hz);
return webrtc::AudioDecoderIsacFix::MakeAudioDecoder(config);
}
case IsacImpl::kFloat: {
webrtc::AudioDecoderIsacFloat::Config config;
config.sample_rate_hz = sample_rate_hz;
return webrtc::AudioDecoderIsacFloat::MakeAudioDecoder(config);
}
}
}
struct EncoderTestParams {
IsacImpl impl;
int sample_rate_hz;
int frame_size_ms;
};
class EncoderTest : public testing::TestWithParam<EncoderTestParams> {
protected:
EncoderTest() = default;
IsacImpl GetIsacImpl() const { return GetParam().impl; }
int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
int GetFrameSizeMs() const { return GetParam().frame_size_ms; }
};
TEST_P(EncoderTest, TestConfig) {
for (int bitrate_bps : {10000, 21000, 32000}) {
SCOPED_TRACE(bitrate_bps);
auto encoder = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
GetFrameSizeMs(), bitrate_bps);
EXPECT_EQ(GetSampleRateHz(), encoder->SampleRateHz());
EXPECT_EQ(size_t{1}, encoder->NumChannels());
EXPECT_EQ(bitrate_bps, encoder->GetTargetBitrate());
}
}
// Encodes an input audio sequence with a low and a high target bitrate and
// checks that the number of produces bytes in the first case is less than that
// of the second case.
TEST_P(EncoderTest, TestDifferentBitrates) {
auto pcm_file = GetPcmTestFileReader(GetSampleRateHz());
constexpr int kLowBps = 20000;
constexpr int kHighBps = 25000;
auto encoder_low = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
GetFrameSizeMs(), kLowBps);
auto encoder_high = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
GetFrameSizeMs(), kHighBps);
int num_bytes_low = 0;
int num_bytes_high = 0;
constexpr int kNumFrames = 12;
for (int i = 0; i < kNumFrames; ++i) {
AudioFrame in;
pcm_file->Read10MsData(in);
rtc::Buffer low, high;
encoder_low->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &low);
encoder_high->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &high);
num_bytes_low += low.size();
num_bytes_high += high.size();
}
EXPECT_LT(num_bytes_low, num_bytes_high);
}
// Encodes an input audio sequence first with a low, then with a high target
// bitrate *using the same encoder* and checks that the number of emitted bytes
// in the first case is less than in the second case.
TEST_P(EncoderTest, TestDynamicBitrateChange) {
constexpr int kLowBps = 20000;
constexpr int kHighBps = 25000;
constexpr int kStartBps = 30000;
auto encoder = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
GetFrameSizeMs(), kStartBps);
std::map<int, int> num_bytes;
constexpr int kNumFrames = 200; // 2 seconds.
for (int bitrate_bps : {kLowBps, kHighBps}) {
auto pcm_file = GetPcmTestFileReader(GetSampleRateHz());
encoder->OnReceivedTargetAudioBitrate(bitrate_bps);
for (int i = 0; i < kNumFrames; ++i) {
AudioFrame in;
pcm_file->Read10MsData(in);
rtc::Buffer buf;
encoder->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &buf);
num_bytes[bitrate_bps] += buf.size();
}
}
// kHighBps / kLowBps == 1.25, so require the high-bitrate run to produce at
// least 1.2 times the number of bytes.
EXPECT_LT(1.2 * num_bytes[kLowBps], num_bytes[kHighBps]);
}
// Checks that, given a target bitrate, the encoder does not overshoot too much.
TEST_P(EncoderTest, DoNotOvershootTargetBitrate) {
for (int bitrate_bps : {10000, 15000, 20000, 26000, 32000}) {
SCOPED_TRACE(bitrate_bps);
auto pcm_file = GetPcmTestFileReader(GetSampleRateHz());
auto e = CreateEncoder(GetIsacImpl(), GetSampleRateHz(), GetFrameSizeMs(),
bitrate_bps);
int num_bytes = 0;
constexpr int kNumFrames = 200; // 2 seconds.
for (int i = 0; i < kNumFrames; ++i) {
AudioFrame in;
pcm_file->Read10MsData(in);
rtc::Buffer encoded;
e->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &encoded);
num_bytes += encoded.size();
}
// Inverse of the duration of |kNumFrames| 10 ms frames (unit: seconds^-1).
constexpr float kAudioDurationInv = 100.f / kNumFrames;
const int measured_bitrate_bps = 8 * num_bytes * kAudioDurationInv;
EXPECT_LT(measured_bitrate_bps, bitrate_bps + 2000); // Max 2 kbps extra.
}
}
// Creates tests for different encoder configurations and implementations.
INSTANTIATE_TEST_SUITE_P(
IsacApiTest,
EncoderTest,
::testing::ValuesIn([] {
std::vector<EncoderTestParams> cases;
for (IsacImpl impl : {IsacImpl::kFloat, IsacImpl::kFixed}) {
for (int frame_size_ms : {30, 60}) {
cases.push_back({impl, 16000, frame_size_ms});
}
}
cases.push_back({IsacImpl::kFloat, 32000, 30});
return cases;
}()),
[](const ::testing::TestParamInfo<EncoderTestParams>& info) {
rtc::StringBuilder b;
const auto& p = info.param;
b << IsacImplToString(p.impl) << "_" << p.sample_rate_hz << "_"
<< p.frame_size_ms;
return b.Release();
});
struct DecoderTestParams {
IsacImpl impl;
int sample_rate_hz;
};
class DecoderTest : public testing::TestWithParam<DecoderTestParams> {
protected:
DecoderTest() = default;
IsacImpl GetIsacImpl() const { return GetParam().impl; }
int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
};
TEST_P(DecoderTest, TestConfig) {
auto decoder = CreateDecoder(GetIsacImpl(), GetSampleRateHz());
EXPECT_EQ(GetSampleRateHz(), decoder->SampleRateHz());
EXPECT_EQ(size_t{1}, decoder->Channels());
}
// Creates tests for different decoder configurations and implementations.
INSTANTIATE_TEST_SUITE_P(
IsacApiTest,
DecoderTest,
::testing::ValuesIn({DecoderTestParams{IsacImpl::kFixed, 16000},
DecoderTestParams{IsacImpl::kFloat, 16000},
DecoderTestParams{IsacImpl::kFloat, 32000}}),
[](const ::testing::TestParamInfo<DecoderTestParams>& info) {
const auto& p = info.param;
return (rtc::StringBuilder()
<< IsacImplToString(p.impl) << "_" << p.sample_rate_hz)
.Release();
});
struct EncoderDecoderPairTestParams {
int sample_rate_hz;
int frame_size_ms;
IsacImpl encoder_impl;
IsacImpl decoder_impl;
};
class EncoderDecoderPairTest
: public testing::TestWithParam<EncoderDecoderPairTestParams> {
protected:
EncoderDecoderPairTest() = default;
int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
int GetEncoderFrameSizeMs() const { return GetParam().frame_size_ms; }
IsacImpl GetEncoderIsacImpl() const { return GetParam().encoder_impl; }
IsacImpl GetDecoderIsacImpl() const { return GetParam().decoder_impl; }
int GetEncoderFrameSize() const {
return GetEncoderFrameSizeMs() * GetSampleRateHz() / 1000;
}
};
// Checks that the number of encoded and decoded samples match.
TEST_P(EncoderDecoderPairTest, EncodeDecode) {
auto pcm_file = GetPcmTestFileReader(GetSampleRateHz());
auto encoder = CreateEncoder(GetEncoderIsacImpl(), GetSampleRateHz(),
GetEncoderFrameSizeMs(), /*bitrate_bps=*/20000);
auto decoder = CreateDecoder(GetDecoderIsacImpl(), GetSampleRateHz());
const int encoder_frame_size = GetEncoderFrameSize();
std::vector<int16_t> out(encoder_frame_size);
size_t num_encoded_samples = 0;
size_t num_decoded_samples = 0;
constexpr int kNumFrames = 12;
for (int i = 0; i < kNumFrames; ++i) {
AudioFrame in;
pcm_file->Read10MsData(in);
rtc::Buffer encoded;
encoder->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &encoded);
num_encoded_samples += in.samples_per_channel();
if (encoded.empty()) {
continue;
}
// Decode.
const std::vector<AudioDecoder::ParseResult> parse_result =
decoder->ParsePayload(std::move(encoded), /*timestamp=*/0);
EXPECT_EQ(parse_result.size(), size_t{1});
auto decode_result = parse_result[0].frame->Decode(out);
EXPECT_TRUE(decode_result.has_value());
EXPECT_EQ(out.size(), decode_result->num_decoded_samples);
num_decoded_samples += decode_result->num_decoded_samples;
}
EXPECT_EQ(num_encoded_samples, num_decoded_samples);
}
// Creates tests for different encoder frame sizes and different
// encoder/decoder implementations.
INSTANTIATE_TEST_SUITE_P(
IsacApiTest,
EncoderDecoderPairTest,
::testing::ValuesIn([] {
std::vector<EncoderDecoderPairTestParams> cases;
for (int frame_size_ms : {30, 60}) {
for (IsacImpl enc : {IsacImpl::kFloat, IsacImpl::kFixed}) {
for (IsacImpl dec : {IsacImpl::kFloat, IsacImpl::kFixed}) {
cases.push_back({16000, frame_size_ms, enc, dec});
}
}
}
cases.push_back({32000, 30, IsacImpl::kFloat, IsacImpl::kFloat});
return cases;
}()),
[](const ::testing::TestParamInfo<EncoderDecoderPairTestParams>& info) {
rtc::StringBuilder b;
const auto& p = info.param;
b << p.sample_rate_hz << "_" << p.frame_size_ms << "_"
<< IsacImplToString(p.encoder_impl) << "_"
<< IsacImplToString(p.decoder_impl);
return b.Release();
});
} // namespace
} // namespace webrtc