mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00

This is a reland ofe369928e04
Original change's description: > Roll chromium_revision de0d050e64..42d795c24f (908789:908899) > > Change log:de0d050e64..42d795c24f
> Full diff:de0d050e64..42d795c24f
> > Changed dependencies > * src/base:f14f1b7600..6551b66fbf
> * src/build:e360729c13..496f4dc82b
> * src/ios:2965e1969a..fda9d90178
> * src/testing:36299f559a..cb835b4820
> * src/third_party:e99cff4446..aec4ec11c2
> * src/third_party/androidx: 6YnvOFZqQbSfmq9Bknb9CSKuND84c-TqnEATwNlvhqwC..iS9uLbt1ks96lnB9FgzCbsDit0AaQS7PqWyWdVJ3mggC > * src/third_party/depot_tools:0a4dd4181a..cc487710bb
> * src/third_party/perfetto:00e6f338d0..7d0822e5b1
> * src/tools:5219d6859a..667c51bbca
> DEPS diff:de0d050e64..42d795c24f
/DEPS > > Clang version changed llvmorg-13-init-15561-gf98ed74f:llvmorg-14-init-591-g7d9d926a > Details:de0d050e64..42d795c24f
/tools/clang/scripts/update.py > > BUG=None > > Change-Id: Ibc203c4808885594a4316d8ce0e0a82bacebe51b > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/227770 > Commit-Queue: Artem Titov <titovartem@webrtc.org> > Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> > Reviewed-by: Ivo Creusen <ivoc@webrtc.org> > Cr-Commit-Position: refs/heads/master@{#34658} Bug: None Change-Id: Ibc843ef6e4e50d9d62b6b3550d5cde6eaebc02e6 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/227773 Commit-Queue: Artem Titov <titovartem@webrtc.org> Reviewed-by: Ivo Creusen <ivoc@webrtc.org> Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/master@{#34662}
346 lines
12 KiB
C++
346 lines
12 KiB
C++
/*
|
|
* 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 <array>
|
|
#include <map>
|
|
#include <memory>
|
|
#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 "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<PCMFile> 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<PCMFile>();
|
|
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<const int16_t> AudioFrameToView(const AudioFrame& audio_frame) {
|
|
return {audio_frame.data(),
|
|
audio_frame.samples_per_channel() * audio_frame.num_channels()};
|
|
}
|
|
|
|
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:
|
|
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<int, int> 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<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() = 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<int16_t> 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<AudioDecoder::ParseResult> 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<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_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
|