/*
|
* Copyright (c) 2015 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 "testing/gmock/include/gmock/gmock.h"
|
#include "webrtc/base/scoped_ptr.h"
|
#include "webrtc/modules/audio_coding/neteq/tools/neteq_external_decoder_test.h"
|
#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h"
|
|
namespace webrtc {
|
namespace test {
|
|
using ::testing::_;
|
using ::testing::SetArgPointee;
|
using ::testing::Return;
|
|
class MockAudioDecoder final : public AudioDecoder {
|
public:
|
static const int kPacketDuration = 960; // 48 kHz * 20 ms
|
|
explicit MockAudioDecoder(size_t num_channels)
|
: num_channels_(num_channels), fec_enabled_(false) {
|
}
|
~MockAudioDecoder() override { Die(); }
|
MOCK_METHOD0(Die, void());
|
|
MOCK_METHOD0(Reset, void());
|
|
int PacketDuration(const uint8_t* encoded,
|
size_t encoded_len) const override {
|
return kPacketDuration;
|
}
|
|
int PacketDurationRedundant(const uint8_t* encoded,
|
size_t encoded_len) const override {
|
return kPacketDuration;
|
}
|
|
bool PacketHasFec(const uint8_t* encoded, size_t encoded_len) const override {
|
return fec_enabled_;
|
}
|
|
size_t Channels() const override { return num_channels_; }
|
|
void set_fec_enabled(bool enable_fec) { fec_enabled_ = enable_fec; }
|
|
bool fec_enabled() const { return fec_enabled_; }
|
|
protected:
|
// Override the following methods such that no actual payload is needed.
|
int DecodeInternal(const uint8_t* encoded,
|
size_t encoded_len,
|
int /*sample_rate_hz*/,
|
int16_t* decoded,
|
SpeechType* speech_type) override {
|
*speech_type = kSpeech;
|
memset(decoded, 0, sizeof(int16_t) * kPacketDuration * Channels());
|
return kPacketDuration * Channels();
|
}
|
|
int DecodeRedundantInternal(const uint8_t* encoded,
|
size_t encoded_len,
|
int sample_rate_hz,
|
int16_t* decoded,
|
SpeechType* speech_type) override {
|
return DecodeInternal(encoded, encoded_len, sample_rate_hz, decoded,
|
speech_type);
|
}
|
|
private:
|
const size_t num_channels_;
|
bool fec_enabled_;
|
};
|
|
class NetEqNetworkStatsTest : public NetEqExternalDecoderTest {
|
public:
|
static const int kPayloadSizeByte = 30;
|
static const int kFrameSizeMs = 20;
|
static const int kMaxOutputSize = 960; // 10 ms * 48 kHz * 2 channels.
|
|
enum logic {
|
IGNORE,
|
EQUAL,
|
SMALLER_THAN,
|
LARGER_THAN,
|
};
|
|
struct NetEqNetworkStatsCheck {
|
logic current_buffer_size_ms;
|
logic preferred_buffer_size_ms;
|
logic jitter_peaks_found;
|
logic packet_loss_rate;
|
logic packet_discard_rate;
|
logic expand_rate;
|
logic speech_expand_rate;
|
logic preemptive_rate;
|
logic accelerate_rate;
|
logic secondary_decoded_rate;
|
logic clockdrift_ppm;
|
logic added_zero_samples;
|
NetEqNetworkStatistics stats_ref;
|
};
|
|
NetEqNetworkStatsTest(NetEqDecoder codec,
|
MockAudioDecoder* decoder)
|
: NetEqExternalDecoderTest(codec, decoder),
|
external_decoder_(decoder),
|
samples_per_ms_(CodecSampleRateHz(codec) / 1000),
|
frame_size_samples_(kFrameSizeMs * samples_per_ms_),
|
rtp_generator_(new test::RtpGenerator(samples_per_ms_)),
|
last_lost_time_(0),
|
packet_loss_interval_(0xffffffff) {
|
Init();
|
}
|
|
bool Lost(uint32_t send_time) {
|
if (send_time - last_lost_time_ >= packet_loss_interval_) {
|
last_lost_time_ = send_time;
|
return true;
|
}
|
return false;
|
}
|
|
void SetPacketLossRate(double loss_rate) {
|
packet_loss_interval_ = (loss_rate >= 1e-3 ?
|
static_cast<double>(kFrameSizeMs) / loss_rate : 0xffffffff);
|
}
|
|
// |stats_ref|
|
// expects.x = -1, do not care
|
// expects.x = 0, 'x' in current stats should equal 'x' in |stats_ref|
|
// expects.x = 1, 'x' in current stats should < 'x' in |stats_ref|
|
// expects.x = 2, 'x' in current stats should > 'x' in |stats_ref|
|
void CheckNetworkStatistics(NetEqNetworkStatsCheck expects) {
|
NetEqNetworkStatistics stats;
|
neteq()->NetworkStatistics(&stats);
|
|
#define CHECK_NETEQ_NETWORK_STATS(x)\
|
switch (expects.x) {\
|
case EQUAL:\
|
EXPECT_EQ(stats.x, expects.stats_ref.x);\
|
break;\
|
case SMALLER_THAN:\
|
EXPECT_LT(stats.x, expects.stats_ref.x);\
|
break;\
|
case LARGER_THAN:\
|
EXPECT_GT(stats.x, expects.stats_ref.x);\
|
break;\
|
default:\
|
break;\
|
}
|
|
CHECK_NETEQ_NETWORK_STATS(current_buffer_size_ms);
|
CHECK_NETEQ_NETWORK_STATS(preferred_buffer_size_ms);
|
CHECK_NETEQ_NETWORK_STATS(jitter_peaks_found);
|
CHECK_NETEQ_NETWORK_STATS(packet_loss_rate);
|
CHECK_NETEQ_NETWORK_STATS(packet_discard_rate);
|
CHECK_NETEQ_NETWORK_STATS(expand_rate);
|
CHECK_NETEQ_NETWORK_STATS(speech_expand_rate);
|
CHECK_NETEQ_NETWORK_STATS(preemptive_rate);
|
CHECK_NETEQ_NETWORK_STATS(accelerate_rate);
|
CHECK_NETEQ_NETWORK_STATS(secondary_decoded_rate);
|
CHECK_NETEQ_NETWORK_STATS(clockdrift_ppm);
|
CHECK_NETEQ_NETWORK_STATS(added_zero_samples);
|
|
#undef CHECK_NETEQ_NETWORK_STATS
|
|
// Compare with CurrentDelay, which should be identical.
|
EXPECT_EQ(stats.current_buffer_size_ms, neteq()->CurrentDelayMs());
|
}
|
|
void RunTest(int num_loops, NetEqNetworkStatsCheck expects) {
|
NetEqOutputType output_type;
|
uint32_t time_now;
|
uint32_t next_send_time;
|
|
// Initiate |last_lost_time_|.
|
time_now = next_send_time = last_lost_time_ =
|
rtp_generator_->GetRtpHeader(kPayloadType, frame_size_samples_,
|
&rtp_header_);
|
for (int k = 0; k < num_loops; ++k) {
|
// Delay by one frame such that the FEC can come in.
|
while (time_now + kFrameSizeMs >= next_send_time) {
|
next_send_time = rtp_generator_->GetRtpHeader(kPayloadType,
|
frame_size_samples_,
|
&rtp_header_);
|
if (!Lost(next_send_time)) {
|
InsertPacket(rtp_header_, payload_, next_send_time);
|
}
|
}
|
GetOutputAudio(kMaxOutputSize, output_, &output_type);
|
time_now += kOutputLengthMs;
|
}
|
CheckNetworkStatistics(expects);
|
neteq()->FlushBuffers();
|
}
|
|
void DecodeFecTest() {
|
external_decoder_->set_fec_enabled(false);
|
NetEqNetworkStatsCheck expects = {
|
IGNORE, // current_buffer_size_ms
|
IGNORE, // preferred_buffer_size_ms
|
IGNORE, // jitter_peaks_found
|
EQUAL, // packet_loss_rate
|
EQUAL, // packet_discard_rate
|
EQUAL, // expand_rate
|
EQUAL, // voice_expand_rate
|
IGNORE, // preemptive_rate
|
EQUAL, // accelerate_rate
|
EQUAL, // decoded_fec_rate
|
IGNORE, // clockdrift_ppm
|
EQUAL, // added_zero_samples
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
};
|
RunTest(50, expects);
|
|
// Next we introduce packet losses.
|
SetPacketLossRate(0.1);
|
expects.stats_ref.packet_loss_rate = 1337;
|
expects.stats_ref.expand_rate = expects.stats_ref.speech_expand_rate = 1065;
|
RunTest(50, expects);
|
|
// Next we enable FEC.
|
external_decoder_->set_fec_enabled(true);
|
// If FEC fills in the lost packets, no packet loss will be counted.
|
expects.stats_ref.packet_loss_rate = 0;
|
expects.stats_ref.expand_rate = expects.stats_ref.speech_expand_rate = 0;
|
expects.stats_ref.secondary_decoded_rate = 2006;
|
RunTest(50, expects);
|
}
|
|
void NoiseExpansionTest() {
|
NetEqNetworkStatsCheck expects = {
|
IGNORE, // current_buffer_size_ms
|
IGNORE, // preferred_buffer_size_ms
|
IGNORE, // jitter_peaks_found
|
EQUAL, // packet_loss_rate
|
EQUAL, // packet_discard_rate
|
EQUAL, // expand_rate
|
EQUAL, // speech_expand_rate
|
IGNORE, // preemptive_rate
|
EQUAL, // accelerate_rate
|
EQUAL, // decoded_fec_rate
|
IGNORE, // clockdrift_ppm
|
EQUAL, // added_zero_samples
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
};
|
RunTest(50, expects);
|
|
SetPacketLossRate(1);
|
expects.stats_ref.expand_rate = 16384;
|
expects.stats_ref.speech_expand_rate = 5324;
|
RunTest(10, expects); // Lost 10 * 20ms in a row.
|
}
|
|
private:
|
MockAudioDecoder* external_decoder_;
|
const int samples_per_ms_;
|
const size_t frame_size_samples_;
|
rtc::scoped_ptr<test::RtpGenerator> rtp_generator_;
|
WebRtcRTPHeader rtp_header_;
|
uint32_t last_lost_time_;
|
uint32_t packet_loss_interval_;
|
uint8_t payload_[kPayloadSizeByte];
|
int16_t output_[kMaxOutputSize];
|
};
|
|
TEST(NetEqNetworkStatsTest, DecodeFec) {
|
MockAudioDecoder decoder(1);
|
NetEqNetworkStatsTest test(NetEqDecoder::kDecoderOpus, &decoder);
|
test.DecodeFecTest();
|
EXPECT_CALL(decoder, Die()).Times(1);
|
}
|
|
TEST(NetEqNetworkStatsTest, StereoDecodeFec) {
|
MockAudioDecoder decoder(2);
|
NetEqNetworkStatsTest test(NetEqDecoder::kDecoderOpus, &decoder);
|
test.DecodeFecTest();
|
EXPECT_CALL(decoder, Die()).Times(1);
|
}
|
|
TEST(NetEqNetworkStatsTest, NoiseExpansionTest) {
|
MockAudioDecoder decoder(1);
|
NetEqNetworkStatsTest test(NetEqDecoder::kDecoderOpus, &decoder);
|
test.NoiseExpansionTest();
|
EXPECT_CALL(decoder, Die()).Times(1);
|
}
|
|
} // namespace test
|
} // namespace webrtc
|