iSAC config: target bitrate exposed for fixed impl
It is now possible to set the target bitrate for iSAC for the fixed point implementation. Unit tests added. Bug: webrtc:11360 Change-Id: I60225d4ca1363cdacf18931e7cf412c5aec8d8fe Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/168529 Commit-Queue: Alessio Bazzica <alessiob@webrtc.org> Reviewed-by: Karl Wiberg <kwiberg@webrtc.org> Cr-Commit-Position: refs/heads/master@{#30526}
This commit is contained in:
committed by
Commit Bot
parent
2517a47b01
commit
08b11cafae
@ -12,22 +12,32 @@
|
||||
#include <limits>
|
||||
#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 "rtc_base/random.h"
|
||||
#include "rtc_base/strings/string_builder.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
constexpr int kPayloadType = 42;
|
||||
constexpr int kBitrateBps = 20000;
|
||||
|
||||
enum class IsacImpl { kFixed, kFloat };
|
||||
|
||||
absl::string_view IsacImplToString(IsacImpl impl) {
|
||||
switch (impl) {
|
||||
case IsacImpl::kFixed:
|
||||
return "fixed";
|
||||
case IsacImpl::kFloat:
|
||||
return "float";
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int16_t> GetRandomSamplesVector(size_t size) {
|
||||
constexpr int32_t kMin = std::numeric_limits<int16_t>::min();
|
||||
constexpr int32_t kMax = std::numeric_limits<int16_t>::max();
|
||||
@ -39,67 +49,210 @@ std::vector<int16_t> GetRandomSamplesVector(size_t size) {
|
||||
return v;
|
||||
}
|
||||
|
||||
class IsacApiTest
|
||||
: public testing::TestWithParam<std::tuple<int, int, IsacImpl, IsacImpl>> {
|
||||
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:
|
||||
IsacApiTest() : input_frame_(GetRandomSamplesVector(GetInputFrameLength())) {}
|
||||
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) {
|
||||
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;
|
||||
const auto in = GetRandomSamplesVector(
|
||||
/*size=*/rtc::CheckedDivExact(GetSampleRateHz(), 100));
|
||||
constexpr int kNumFrames = 12;
|
||||
for (int i = 0; i < kNumFrames; ++i) {
|
||||
rtc::Buffer low, high;
|
||||
encoder_low->Encode(/*rtp_timestamp=*/0, in, &low);
|
||||
encoder_high->Encode(/*rtp_timestamp=*/0, in, &high);
|
||||
num_bytes_low += low.size();
|
||||
num_bytes_high += high.size();
|
||||
}
|
||||
EXPECT_LT(num_bytes_low, num_bytes_high);
|
||||
}
|
||||
|
||||
// Checks that the target and the measured bitrates are within tolerance.
|
||||
// TODO(webrtc:11360): Add CBR flag to the config and re-enable test with CBR.
|
||||
TEST_P(EncoderTest, DISABLED_TestBitrateNearTarget) {
|
||||
const auto in = GetRandomSamplesVector(
|
||||
/*size=*/rtc::CheckedDivExact(GetSampleRateHz(), 100)); // 10 ms.
|
||||
for (int bitrate_bps : {10000, 15000, 20000, 26000, 32000}) {
|
||||
SCOPED_TRACE(bitrate_bps);
|
||||
auto e = CreateEncoder(GetIsacImpl(), GetSampleRateHz(), GetFrameSizeMs(),
|
||||
bitrate_bps);
|
||||
int num_bytes = 0;
|
||||
constexpr int kNumFrames = 60;
|
||||
for (int i = 0; i < kNumFrames; ++i) {
|
||||
rtc::Buffer encoded;
|
||||
e->Encode(/*rtp_timestamp=*/0, 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_NEAR(bitrate_bps, measured_bitrate_bps, 1000); // Max 1 kbps.
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
: input_frame_(GetRandomSamplesVector(GetInputFrameSize())) {}
|
||||
rtc::ArrayView<const int16_t> GetInputFrame() { return input_frame_; }
|
||||
int GetSampleRateHz() const { return std::get<0>(GetParam()); }
|
||||
int GetEncoderFrameLenght() const {
|
||||
return GetEncoderFrameLenghtMs() * GetSampleRateHz() / 1000;
|
||||
}
|
||||
std::unique_ptr<AudioEncoder> CreateEncoder() const {
|
||||
switch (GetEncoderIsacImpl()) {
|
||||
case IsacImpl::kFixed: {
|
||||
AudioEncoderIsacFix::Config config;
|
||||
config.frame_size_ms = GetEncoderFrameLenghtMs();
|
||||
RTC_CHECK_EQ(16000, GetSampleRateHz());
|
||||
return AudioEncoderIsacFix::MakeAudioEncoder(config, kPayloadType);
|
||||
}
|
||||
case IsacImpl::kFloat: {
|
||||
AudioEncoderIsacFloat::Config config;
|
||||
config.bit_rate = kBitrateBps;
|
||||
config.frame_size_ms = GetEncoderFrameLenghtMs();
|
||||
config.sample_rate_hz = GetSampleRateHz();
|
||||
return AudioEncoderIsacFloat::MakeAudioEncoder(config, kPayloadType);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::unique_ptr<AudioDecoder> CreateDecoder() const {
|
||||
switch (GetDecoderIsacImpl()) {
|
||||
case IsacImpl::kFixed: {
|
||||
webrtc::AudioDecoderIsacFix::Config config;
|
||||
RTC_CHECK_EQ(16000, GetSampleRateHz());
|
||||
return webrtc::AudioDecoderIsacFix::MakeAudioDecoder(config);
|
||||
}
|
||||
case IsacImpl::kFloat: {
|
||||
webrtc::AudioDecoderIsacFloat::Config config;
|
||||
config.sample_rate_hz = GetSampleRateHz();
|
||||
return webrtc::AudioDecoderIsacFloat::MakeAudioDecoder(config);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<int16_t> input_frame_;
|
||||
int GetInputFrameLength() const {
|
||||
return rtc::CheckedDivExact(std::get<0>(GetParam()), 100); // 10 ms.
|
||||
int GetInputFrameSize() const {
|
||||
return rtc::CheckedDivExact(GetParam().sample_rate_hz, 100); // 10 ms.
|
||||
}
|
||||
int GetEncoderFrameLenghtMs() const {
|
||||
int frame_size_ms = std::get<1>(GetParam());
|
||||
RTC_CHECK(frame_size_ms == 30 || frame_size_ms == 60);
|
||||
return frame_size_ms;
|
||||
}
|
||||
IsacImpl GetEncoderIsacImpl() const { return std::get<2>(GetParam()); }
|
||||
IsacImpl GetDecoderIsacImpl() const { return std::get<3>(GetParam()); }
|
||||
};
|
||||
|
||||
// Checks that the number of encoded and decoded samples match.
|
||||
TEST_P(IsacApiTest, EncodeDecode) {
|
||||
auto encoder = CreateEncoder();
|
||||
auto decoder = CreateDecoder();
|
||||
const int encoder_frame_length = GetEncoderFrameLenght();
|
||||
std::vector<int16_t> out(encoder_frame_length);
|
||||
TEST_P(EncoderDecoderPairTest, EncodeDecode) {
|
||||
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;
|
||||
@ -123,23 +276,31 @@ TEST_P(IsacApiTest, EncodeDecode) {
|
||||
EXPECT_EQ(num_encoded_samples, num_decoded_samples);
|
||||
}
|
||||
|
||||
// Creates tests for different encoder frame lengths and different
|
||||
// Creates tests for different encoder frame sizes and different
|
||||
// encoder/decoder implementations.
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
AllTest,
|
||||
IsacApiTest,
|
||||
EncoderDecoderPairTest,
|
||||
::testing::ValuesIn([] {
|
||||
std::vector<std::tuple<int, int, IsacImpl, IsacImpl>> cases;
|
||||
for (int frame_length_ms : {30, 60}) {
|
||||
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_length_ms, enc, dec});
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user