/* * 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 #include #include #include #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 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(); 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 AudioFrameToView(const AudioFrame& audio_frame) { return {audio_frame.data(), audio_frame.samples_per_channel() * audio_frame.num_channels()}; } std::unique_ptr 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 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 { 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 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 + 2250); // Max 2250 bps extra. } } // Creates tests for different encoder configurations and implementations. INSTANTIATE_TEST_SUITE_P( IsacApiTest, EncoderTest, ::testing::ValuesIn([] { std::vector 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& 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 { 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& 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 { 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 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 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 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& 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