1 /*
2 * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include <array>
12 #include <map>
13 #include <memory>
14 #include <vector>
15
16 #include "absl/strings/string_view.h"
17 #include "api/array_view.h"
18 #include "api/audio_codecs/isac/audio_decoder_isac_fix.h"
19 #include "api/audio_codecs/isac/audio_decoder_isac_float.h"
20 #include "api/audio_codecs/isac/audio_encoder_isac_fix.h"
21 #include "api/audio_codecs/isac/audio_encoder_isac_float.h"
22 #include "modules/audio_coding/test/PCMFile.h"
23 #include "rtc_base/checks.h"
24 #include "rtc_base/strings/string_builder.h"
25 #include "test/gtest.h"
26 #include "test/testsupport/file_utils.h"
27
28 namespace webrtc {
29 namespace {
30
31 constexpr int kPayloadType = 42;
32
33 enum class IsacImpl { kFixed, kFloat };
34
IsacImplToString(IsacImpl impl)35 absl::string_view IsacImplToString(IsacImpl impl) {
36 switch (impl) {
37 case IsacImpl::kFixed:
38 return "fixed";
39 case IsacImpl::kFloat:
40 return "float";
41 }
42 }
43
GetPcmTestFileReader(int sample_rate_hz)44 std::unique_ptr<PCMFile> GetPcmTestFileReader(int sample_rate_hz) {
45 std::string filename;
46 switch (sample_rate_hz) {
47 case 16000:
48 filename = test::ResourcePath("audio_coding/testfile16kHz", "pcm");
49 break;
50 case 32000:
51 filename = test::ResourcePath("audio_coding/testfile32kHz", "pcm");
52 break;
53 default:
54 RTC_NOTREACHED() << "No test file available for " << sample_rate_hz
55 << " Hz.";
56 }
57 auto pcm_file = std::make_unique<PCMFile>();
58 pcm_file->ReadStereo(false);
59 pcm_file->Open(filename, sample_rate_hz, "rb", /*auto_rewind=*/true);
60 pcm_file->FastForward(/*num_10ms_blocks=*/100); // Skip initial silence.
61 RTC_CHECK(!pcm_file->EndOfFile());
62 return pcm_file;
63 }
64
65 // Returns a view to the interleaved samples of an AudioFrame object.
AudioFrameToView(const AudioFrame & audio_frame)66 rtc::ArrayView<const int16_t> AudioFrameToView(const AudioFrame& audio_frame) {
67 return {audio_frame.data(),
68 audio_frame.samples_per_channel() * audio_frame.num_channels()};
69 }
70
CreateEncoder(IsacImpl impl,int sample_rate_hz,int frame_size_ms,int bitrate_bps)71 std::unique_ptr<AudioEncoder> CreateEncoder(IsacImpl impl,
72 int sample_rate_hz,
73 int frame_size_ms,
74 int bitrate_bps) {
75 RTC_CHECK(sample_rate_hz == 16000 || sample_rate_hz == 32000);
76 RTC_CHECK(frame_size_ms == 30 || frame_size_ms == 60);
77 RTC_CHECK_GT(bitrate_bps, 0);
78 switch (impl) {
79 case IsacImpl::kFixed: {
80 AudioEncoderIsacFix::Config config;
81 config.bit_rate = bitrate_bps;
82 config.frame_size_ms = frame_size_ms;
83 RTC_CHECK_EQ(16000, sample_rate_hz);
84 return AudioEncoderIsacFix::MakeAudioEncoder(config, kPayloadType);
85 }
86 case IsacImpl::kFloat: {
87 AudioEncoderIsacFloat::Config config;
88 config.bit_rate = bitrate_bps;
89 config.frame_size_ms = frame_size_ms;
90 config.sample_rate_hz = sample_rate_hz;
91 return AudioEncoderIsacFloat::MakeAudioEncoder(config, kPayloadType);
92 }
93 }
94 }
95
CreateDecoder(IsacImpl impl,int sample_rate_hz)96 std::unique_ptr<AudioDecoder> CreateDecoder(IsacImpl impl, int sample_rate_hz) {
97 RTC_CHECK(sample_rate_hz == 16000 || sample_rate_hz == 32000);
98 switch (impl) {
99 case IsacImpl::kFixed: {
100 webrtc::AudioDecoderIsacFix::Config config;
101 RTC_CHECK_EQ(16000, sample_rate_hz);
102 return webrtc::AudioDecoderIsacFix::MakeAudioDecoder(config);
103 }
104 case IsacImpl::kFloat: {
105 webrtc::AudioDecoderIsacFloat::Config config;
106 config.sample_rate_hz = sample_rate_hz;
107 return webrtc::AudioDecoderIsacFloat::MakeAudioDecoder(config);
108 }
109 }
110 }
111
112 struct EncoderTestParams {
113 IsacImpl impl;
114 int sample_rate_hz;
115 int frame_size_ms;
116 };
117
118 class EncoderTest : public testing::TestWithParam<EncoderTestParams> {
119 protected:
120 EncoderTest() = default;
GetIsacImpl() const121 IsacImpl GetIsacImpl() const { return GetParam().impl; }
GetSampleRateHz() const122 int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
GetFrameSizeMs() const123 int GetFrameSizeMs() const { return GetParam().frame_size_ms; }
124 };
125
TEST_P(EncoderTest,TestConfig)126 TEST_P(EncoderTest, TestConfig) {
127 for (int bitrate_bps : {10000, 21000, 32000}) {
128 SCOPED_TRACE(bitrate_bps);
129 auto encoder = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
130 GetFrameSizeMs(), bitrate_bps);
131 EXPECT_EQ(GetSampleRateHz(), encoder->SampleRateHz());
132 EXPECT_EQ(size_t{1}, encoder->NumChannels());
133 EXPECT_EQ(bitrate_bps, encoder->GetTargetBitrate());
134 }
135 }
136
137 // Encodes an input audio sequence with a low and a high target bitrate and
138 // checks that the number of produces bytes in the first case is less than that
139 // of the second case.
TEST_P(EncoderTest,TestDifferentBitrates)140 TEST_P(EncoderTest, TestDifferentBitrates) {
141 auto pcm_file = GetPcmTestFileReader(GetSampleRateHz());
142 constexpr int kLowBps = 20000;
143 constexpr int kHighBps = 25000;
144 auto encoder_low = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
145 GetFrameSizeMs(), kLowBps);
146 auto encoder_high = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
147 GetFrameSizeMs(), kHighBps);
148 int num_bytes_low = 0;
149 int num_bytes_high = 0;
150 constexpr int kNumFrames = 12;
151 for (int i = 0; i < kNumFrames; ++i) {
152 AudioFrame in;
153 pcm_file->Read10MsData(in);
154 rtc::Buffer low, high;
155 encoder_low->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &low);
156 encoder_high->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &high);
157 num_bytes_low += low.size();
158 num_bytes_high += high.size();
159 }
160 EXPECT_LT(num_bytes_low, num_bytes_high);
161 }
162
163 // Encodes an input audio sequence first with a low, then with a high target
164 // bitrate *using the same encoder* and checks that the number of emitted bytes
165 // in the first case is less than in the second case.
TEST_P(EncoderTest,TestDynamicBitrateChange)166 TEST_P(EncoderTest, TestDynamicBitrateChange) {
167 constexpr int kLowBps = 20000;
168 constexpr int kHighBps = 25000;
169 constexpr int kStartBps = 30000;
170 auto encoder = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
171 GetFrameSizeMs(), kStartBps);
172 std::map<int, int> num_bytes;
173 constexpr int kNumFrames = 200; // 2 seconds.
174 for (int bitrate_bps : {kLowBps, kHighBps}) {
175 auto pcm_file = GetPcmTestFileReader(GetSampleRateHz());
176 encoder->OnReceivedTargetAudioBitrate(bitrate_bps);
177 for (int i = 0; i < kNumFrames; ++i) {
178 AudioFrame in;
179 pcm_file->Read10MsData(in);
180 rtc::Buffer buf;
181 encoder->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &buf);
182 num_bytes[bitrate_bps] += buf.size();
183 }
184 }
185 // kHighBps / kLowBps == 1.25, so require the high-bitrate run to produce at
186 // least 1.2 times the number of bytes.
187 EXPECT_LT(1.2 * num_bytes[kLowBps], num_bytes[kHighBps]);
188 }
189
190 // Checks that, given a target bitrate, the encoder does not overshoot too much.
TEST_P(EncoderTest,DoNotOvershootTargetBitrate)191 TEST_P(EncoderTest, DoNotOvershootTargetBitrate) {
192 for (int bitrate_bps : {10000, 15000, 20000, 26000, 32000}) {
193 SCOPED_TRACE(bitrate_bps);
194 auto pcm_file = GetPcmTestFileReader(GetSampleRateHz());
195 auto e = CreateEncoder(GetIsacImpl(), GetSampleRateHz(), GetFrameSizeMs(),
196 bitrate_bps);
197 int num_bytes = 0;
198 constexpr int kNumFrames = 200; // 2 seconds.
199 for (int i = 0; i < kNumFrames; ++i) {
200 AudioFrame in;
201 pcm_file->Read10MsData(in);
202 rtc::Buffer encoded;
203 e->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &encoded);
204 num_bytes += encoded.size();
205 }
206 // Inverse of the duration of |kNumFrames| 10 ms frames (unit: seconds^-1).
207 constexpr float kAudioDurationInv = 100.f / kNumFrames;
208 const int measured_bitrate_bps = 8 * num_bytes * kAudioDurationInv;
209 EXPECT_LT(measured_bitrate_bps, bitrate_bps + 2000); // Max 2 kbps extra.
210 }
211 }
212
213 // Creates tests for different encoder configurations and implementations.
214 INSTANTIATE_TEST_SUITE_P(
215 IsacApiTest,
216 EncoderTest,
__anonc9d6c6500202null217 ::testing::ValuesIn([] {
218 std::vector<EncoderTestParams> cases;
219 for (IsacImpl impl : {IsacImpl::kFloat, IsacImpl::kFixed}) {
220 for (int frame_size_ms : {30, 60}) {
221 cases.push_back({impl, 16000, frame_size_ms});
222 }
223 }
224 cases.push_back({IsacImpl::kFloat, 32000, 30});
225 return cases;
226 }()),
__anonc9d6c6500302(const ::testing::TestParamInfo<EncoderTestParams>& info) 227 [](const ::testing::TestParamInfo<EncoderTestParams>& info) {
228 rtc::StringBuilder b;
229 const auto& p = info.param;
230 b << IsacImplToString(p.impl) << "_" << p.sample_rate_hz << "_"
231 << p.frame_size_ms;
232 return b.Release();
233 });
234
235 struct DecoderTestParams {
236 IsacImpl impl;
237 int sample_rate_hz;
238 };
239
240 class DecoderTest : public testing::TestWithParam<DecoderTestParams> {
241 protected:
242 DecoderTest() = default;
GetIsacImpl() const243 IsacImpl GetIsacImpl() const { return GetParam().impl; }
GetSampleRateHz() const244 int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
245 };
246
TEST_P(DecoderTest,TestConfig)247 TEST_P(DecoderTest, TestConfig) {
248 auto decoder = CreateDecoder(GetIsacImpl(), GetSampleRateHz());
249 EXPECT_EQ(GetSampleRateHz(), decoder->SampleRateHz());
250 EXPECT_EQ(size_t{1}, decoder->Channels());
251 }
252
253 // Creates tests for different decoder configurations and implementations.
254 INSTANTIATE_TEST_SUITE_P(
255 IsacApiTest,
256 DecoderTest,
257 ::testing::ValuesIn({DecoderTestParams{IsacImpl::kFixed, 16000},
258 DecoderTestParams{IsacImpl::kFloat, 16000},
259 DecoderTestParams{IsacImpl::kFloat, 32000}}),
__anonc9d6c6500402(const ::testing::TestParamInfo<DecoderTestParams>& info) 260 [](const ::testing::TestParamInfo<DecoderTestParams>& info) {
261 const auto& p = info.param;
262 return (rtc::StringBuilder()
263 << IsacImplToString(p.impl) << "_" << p.sample_rate_hz)
264 .Release();
265 });
266
267 struct EncoderDecoderPairTestParams {
268 int sample_rate_hz;
269 int frame_size_ms;
270 IsacImpl encoder_impl;
271 IsacImpl decoder_impl;
272 };
273
274 class EncoderDecoderPairTest
275 : public testing::TestWithParam<EncoderDecoderPairTestParams> {
276 protected:
277 EncoderDecoderPairTest() = default;
GetSampleRateHz() const278 int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
GetEncoderFrameSizeMs() const279 int GetEncoderFrameSizeMs() const { return GetParam().frame_size_ms; }
GetEncoderIsacImpl() const280 IsacImpl GetEncoderIsacImpl() const { return GetParam().encoder_impl; }
GetDecoderIsacImpl() const281 IsacImpl GetDecoderIsacImpl() const { return GetParam().decoder_impl; }
GetEncoderFrameSize() const282 int GetEncoderFrameSize() const {
283 return GetEncoderFrameSizeMs() * GetSampleRateHz() / 1000;
284 }
285 };
286
287 // Checks that the number of encoded and decoded samples match.
TEST_P(EncoderDecoderPairTest,EncodeDecode)288 TEST_P(EncoderDecoderPairTest, EncodeDecode) {
289 auto pcm_file = GetPcmTestFileReader(GetSampleRateHz());
290 auto encoder = CreateEncoder(GetEncoderIsacImpl(), GetSampleRateHz(),
291 GetEncoderFrameSizeMs(), /*bitrate_bps=*/20000);
292 auto decoder = CreateDecoder(GetDecoderIsacImpl(), GetSampleRateHz());
293 const int encoder_frame_size = GetEncoderFrameSize();
294 std::vector<int16_t> out(encoder_frame_size);
295 size_t num_encoded_samples = 0;
296 size_t num_decoded_samples = 0;
297 constexpr int kNumFrames = 12;
298 for (int i = 0; i < kNumFrames; ++i) {
299 AudioFrame in;
300 pcm_file->Read10MsData(in);
301 rtc::Buffer encoded;
302 encoder->Encode(/*rtp_timestamp=*/0, AudioFrameToView(in), &encoded);
303 num_encoded_samples += in.samples_per_channel();
304 if (encoded.empty()) {
305 continue;
306 }
307 // Decode.
308 const std::vector<AudioDecoder::ParseResult> parse_result =
309 decoder->ParsePayload(std::move(encoded), /*timestamp=*/0);
310 EXPECT_EQ(parse_result.size(), size_t{1});
311 auto decode_result = parse_result[0].frame->Decode(out);
312 EXPECT_TRUE(decode_result.has_value());
313 EXPECT_EQ(out.size(), decode_result->num_decoded_samples);
314 num_decoded_samples += decode_result->num_decoded_samples;
315 }
316 EXPECT_EQ(num_encoded_samples, num_decoded_samples);
317 }
318
319 // Creates tests for different encoder frame sizes and different
320 // encoder/decoder implementations.
321 INSTANTIATE_TEST_SUITE_P(
322 IsacApiTest,
323 EncoderDecoderPairTest,
__anonc9d6c6500502null324 ::testing::ValuesIn([] {
325 std::vector<EncoderDecoderPairTestParams> cases;
326 for (int frame_size_ms : {30, 60}) {
327 for (IsacImpl enc : {IsacImpl::kFloat, IsacImpl::kFixed}) {
328 for (IsacImpl dec : {IsacImpl::kFloat, IsacImpl::kFixed}) {
329 cases.push_back({16000, frame_size_ms, enc, dec});
330 }
331 }
332 }
333 cases.push_back({32000, 30, IsacImpl::kFloat, IsacImpl::kFloat});
334 return cases;
335 }()),
__anonc9d6c6500602(const ::testing::TestParamInfo<EncoderDecoderPairTestParams>& info) 336 [](const ::testing::TestParamInfo<EncoderDecoderPairTestParams>& info) {
337 rtc::StringBuilder b;
338 const auto& p = info.param;
339 b << p.sample_rate_hz << "_" << p.frame_size_ms << "_"
340 << IsacImplToString(p.encoder_impl) << "_"
341 << IsacImplToString(p.decoder_impl);
342 return b.Release();
343 });
344
345 } // namespace
346 } // namespace webrtc
347