• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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