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