Adding decoded_fec_rate to NetEQ Network Statistics.

A statistic is introduced to reflect the actual benefits of Opus FEC. It shows what percentage of samples in the rendered audio come from FEC data.

BUG=3867
R=henrik.lundin@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/34969004

Cr-Commit-Position: refs/heads/master@{#8384}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8384 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
minyue@webrtc.org 2015-02-17 10:17:09 +00:00
parent 290cb56dca
commit 2c1bcf2cb4
12 changed files with 636 additions and 226 deletions

View File

@ -38,6 +38,8 @@ struct NetEqNetworkStatistics {
// expansion (in Q14).
uint16_t accelerate_rate; // Fraction of data removed through acceleration
// (in Q14).
uint16_t secondary_decoded_rate; // Fraction of data coming from secondary
// decoding (in Q14).
int32_t clockdrift_ppm; // Average clock-drift in parts-per-million
// (positive or negative).
int added_zero_samples; // Number of zero samples added in "off" mode.

View File

@ -80,8 +80,6 @@ class MockExternalPcm16B : public ExternalPcm16B {
uint32_t arrival_timestamp));
MOCK_METHOD0(ErrorCode,
int());
MOCK_CONST_METHOD0(codec_type,
NetEqDecoder());
private:
ExternalPcm16B real_;

View File

@ -10,221 +10,237 @@
// Test to verify correct operation for externally created decoders.
#include <string>
#include <list>
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/modules/audio_coding/neteq/interface/neteq.h"
#include "webrtc/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h"
#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h"
#include "webrtc/modules/audio_coding/neteq/tools/neteq_external_decoder_test.h"
#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/gtest_disable.h"
namespace webrtc {
using ::testing::_;
using ::testing::Return;
// This test encodes a few packets of PCM16b 32 kHz data and inserts it into two
// different NetEq instances. The first instance uses the internal version of
// the decoder object, while the second one uses an externally created decoder
// object (ExternalPcm16B wrapped in MockExternalPcm16B, both defined above).
// The test verifies that the output from both instances match.
class NetEqExternalDecoderTest : public ::testing::Test {
class NetEqExternalDecoderUnitTest : public test::NetEqExternalDecoderTest {
protected:
static const int kTimeStepMs = 10;
static const int kMaxBlockSize = 480; // 10 ms @ 48 kHz.
static const uint8_t kPayloadType = 95;
static const int kSampleRateHz = 32000;
static const int kFrameSizeMs = 10; // Frame size of Pcm16B.
NetEqExternalDecoderTest()
: sample_rate_hz_(kSampleRateHz),
samples_per_ms_(sample_rate_hz_ / 1000),
frame_size_ms_(10),
frame_size_samples_(frame_size_ms_ * samples_per_ms_),
output_size_samples_(frame_size_ms_ * samples_per_ms_),
external_decoder_(new MockExternalPcm16B),
NetEqExternalDecoderUnitTest(NetEqDecoder codec,
MockExternalPcm16B* 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_)),
input_(new int16_t[frame_size_samples_]),
// Payload should be no larger than input.
encoded_(new uint8_t[2 * frame_size_samples_]),
payload_size_bytes_(0),
last_send_time_(0),
last_arrival_time_(0) {
config_.sample_rate_hz = sample_rate_hz_;
neteq_external_ = NetEq::Create(config_);
neteq_ = NetEq::Create(config_);
input_ = new int16_t[frame_size_samples_];
encoded_ = new uint8_t[2 * frame_size_samples_];
}
~NetEqExternalDecoderTest() {
delete neteq_external_;
delete neteq_;
// We will now delete the decoder ourselves, so expecting Die to be called.
EXPECT_CALL(*external_decoder_, Die()).Times(1);
delete [] input_;
delete [] encoded_;
}
virtual void SetUp() {
const std::string file_name =
webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm");
input_file_.reset(new test::InputAudioFile(file_name));
assert(sample_rate_hz_ == 32000);
NetEqDecoder decoder = kDecoderPCM16Bswb32kHz;
// Init() will trigger external_decoder_->Init().
EXPECT_CALL(*external_decoder_, Init());
// NetEq is not allowed to delete the external decoder (hence Times(0)).
EXPECT_CALL(*external_decoder_, Die()).Times(0);
ASSERT_EQ(NetEq::kOK,
neteq_external_->RegisterExternalDecoder(
external_decoder_.get(), decoder, kPayloadType));
ASSERT_EQ(NetEq::kOK,
neteq_->RegisterPayloadType(decoder, kPayloadType));
Init();
const std::string file_name =
webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm");
input_file_.reset(new test::InputAudioFile(file_name));
}
virtual void TearDown() {}
virtual ~NetEqExternalDecoderUnitTest() {
delete [] input_;
delete [] encoded_;
// ~NetEqExternalDecoderTest() will delete |external_decoder_|, so expecting
// Die() to be called.
EXPECT_CALL(*external_decoder_, Die()).Times(1);
}
int GetNewPackets() {
// Method to draw kFrameSizeMs audio and verify the output.
// Use gTest methods. e.g. ASSERT_EQ() inside to trigger errors.
virtual void GetAndVerifyOutput() = 0;
// Method to get the number of calls to the Decode() method of the external
// decoder.
virtual int NumExpectedDecodeCalls(int num_loops) = 0;
// Method to generate packets and return the send time of the packet.
int GetNewPacket() {
if (!input_file_->Read(frame_size_samples_, input_)) {
return -1;
}
payload_size_bytes_ = WebRtcPcm16b_Encode(input_, frame_size_samples_,
encoded_);
if (frame_size_samples_ * 2 != payload_size_bytes_) {
return -1;
}
encoded_);;
int next_send_time = rtp_generator_->GetRtpHeader(
kPayloadType, frame_size_samples_, &rtp_header_);
return next_send_time;
}
virtual void VerifyOutput(size_t num_samples) const {
for (size_t i = 0; i < num_samples; ++i) {
ASSERT_EQ(output_[i], output_external_[i]) <<
"Diff in sample " << i << ".";
}
}
// Method to decide packet losses.
virtual bool Lost() { return false; }
virtual int GetArrivalTime(int send_time) {
// Method to calculate packet arrival time.
int GetArrivalTime(int send_time) {
int arrival_time = last_arrival_time_ + (send_time - last_send_time_);
last_send_time_ = send_time;
last_arrival_time_ = arrival_time;
return arrival_time;
}
virtual bool Lost() { return false; }
virtual void InsertPackets(int next_arrival_time) {
// Insert packet in regular instance.
ASSERT_EQ(
NetEq::kOK,
neteq_->InsertPacket(
rtp_header_, encoded_, payload_size_bytes_, next_arrival_time));
// Insert packet in external decoder instance.
EXPECT_CALL(*external_decoder_,
IncomingPacket(_,
payload_size_bytes_,
rtp_header_.header.sequenceNumber,
rtp_header_.header.timestamp,
next_arrival_time));
ASSERT_EQ(
NetEq::kOK,
neteq_external_->InsertPacket(
rtp_header_, encoded_, payload_size_bytes_, next_arrival_time));
}
virtual void GetOutputAudio() {
NetEqOutputType output_type;
// Get audio from regular instance.
int samples_per_channel;
int num_channels;
EXPECT_EQ(NetEq::kOK,
neteq_->GetAudio(kMaxBlockSize,
output_,
&samples_per_channel,
&num_channels,
&output_type));
EXPECT_EQ(1, num_channels);
EXPECT_EQ(output_size_samples_, samples_per_channel);
// Get audio from external decoder instance.
ASSERT_EQ(NetEq::kOK,
neteq_external_->GetAudio(kMaxBlockSize,
output_external_,
&samples_per_channel,
&num_channels,
&output_type));
EXPECT_EQ(1, num_channels);
EXPECT_EQ(output_size_samples_, samples_per_channel);
}
virtual int NumExpectedDecodeCalls(int num_loops) const { return num_loops; }
void RunTest(int num_loops) {
// Get next input packets (mono and multi-channel).
int next_send_time;
int next_arrival_time;
uint32_t next_send_time;
uint32_t next_arrival_time;
do {
next_send_time = GetNewPackets();
ASSERT_NE(-1, next_send_time);
next_send_time = GetNewPacket();
next_arrival_time = GetArrivalTime(next_send_time);
} while (Lost()); // If lost, immediately read the next packet.
EXPECT_CALL(*external_decoder_, Decode(_, payload_size_bytes_, _, _))
.Times(NumExpectedDecodeCalls(num_loops));
int time_now = 0;
uint32_t time_now = 0;
for (int k = 0; k < num_loops; ++k) {
while (time_now >= next_arrival_time) {
InsertPackets(next_arrival_time);
InsertPacket(rtp_header_, encoded_, payload_size_bytes_,
next_arrival_time);
// Get next input packet.
do {
next_send_time = GetNewPackets();
ASSERT_NE(-1, next_send_time);
next_send_time = GetNewPacket();
next_arrival_time = GetArrivalTime(next_send_time);
} while (Lost()); // If lost, immediately read the next packet.
}
GetOutputAudio();
std::ostringstream ss;
ss << "Lap number " << k << ".";
SCOPED_TRACE(ss.str()); // Print out the parameter values on failure.
// Compare mono and multi-channel.
ASSERT_NO_FATAL_FAILURE(VerifyOutput(output_size_samples_));
ASSERT_NO_FATAL_FAILURE(GetAndVerifyOutput());
time_now += kTimeStepMs;
time_now += kOutputLengthMs;
}
}
NetEq::Config config_;
int sample_rate_hz_;
int samples_per_ms_;
const int frame_size_ms_;
size_t frame_size_samples_;
int output_size_samples_;
NetEq* neteq_external_;
NetEq* neteq_;
void InsertPacket(WebRtcRTPHeader rtp_header, const uint8_t* payload,
size_t payload_size_bytes,
uint32_t receive_timestamp) override {
EXPECT_CALL(*external_decoder_,
IncomingPacket(_,
payload_size_bytes,
rtp_header.header.sequenceNumber,
rtp_header.header.timestamp,
receive_timestamp));
NetEqExternalDecoderTest::InsertPacket(rtp_header, payload,
payload_size_bytes,
receive_timestamp);
}
MockExternalPcm16B* external_decoder() { return external_decoder_.get(); }
void ResetRtpGenerator(test::RtpGenerator* rtp_generator) {
rtp_generator_.reset(rtp_generator);
}
int samples_per_ms() const { return samples_per_ms_; }
private:
scoped_ptr<MockExternalPcm16B> external_decoder_;
int samples_per_ms_;
size_t frame_size_samples_;
scoped_ptr<test::RtpGenerator> rtp_generator_;
int16_t* input_;
uint8_t* encoded_;
int16_t output_[kMaxBlockSize];
int16_t output_external_[kMaxBlockSize];
WebRtcRTPHeader rtp_header_;
size_t payload_size_bytes_;
int last_send_time_;
int last_arrival_time_;
uint32_t last_send_time_;
uint32_t last_arrival_time_;
scoped_ptr<test::InputAudioFile> input_file_;
WebRtcRTPHeader rtp_header_;
};
TEST_F(NetEqExternalDecoderTest, RunTest) {
// This test encodes a few packets of PCM16b 32 kHz data and inserts it into two
// different NetEq instances. The first instance uses the internal version of
// the decoder object, while the second one uses an externally created decoder
// object (ExternalPcm16B wrapped in MockExternalPcm16B, both defined above).
// The test verifies that the output from both instances match.
class NetEqExternalVsInternalDecoderTest : public NetEqExternalDecoderUnitTest,
public ::testing::Test {
protected:
static const int kMaxBlockSize = 480; // 10 ms @ 48 kHz.
NetEqExternalVsInternalDecoderTest()
: NetEqExternalDecoderUnitTest(kDecoderPCM16Bswb32kHz,
new MockExternalPcm16B),
sample_rate_hz_(CodecSampleRateHz(kDecoderPCM16Bswb32kHz)) {
NetEq::Config config;
config.sample_rate_hz = CodecSampleRateHz(kDecoderPCM16Bswb32kHz);
neteq_internal_.reset(NetEq::Create(config));
}
void SetUp() override {
ASSERT_EQ(NetEq::kOK,
neteq_internal_->RegisterPayloadType(kDecoderPCM16Bswb32kHz,
kPayloadType));
}
void GetAndVerifyOutput() override {
NetEqOutputType output_type;
int samples_per_channel;
int num_channels;
// Get audio from internal decoder instance.
EXPECT_EQ(NetEq::kOK,
neteq_internal_->GetAudio(kMaxBlockSize,
output_internal_,
&samples_per_channel,
&num_channels,
&output_type));
EXPECT_EQ(1, num_channels);
EXPECT_EQ(kOutputLengthMs * sample_rate_hz_ / 1000, samples_per_channel);
// Get audio from external decoder instance.
samples_per_channel = GetOutputAudio(kMaxBlockSize, output_, &output_type);
for (int i = 0; i < samples_per_channel; ++i) {
ASSERT_EQ(output_[i], output_internal_[i]) <<
"Diff in sample " << i << ".";
}
}
void InsertPacket(WebRtcRTPHeader rtp_header, const uint8_t* payload,
size_t payload_size_bytes,
uint32_t receive_timestamp) override {
// Insert packet in internal decoder.
ASSERT_EQ(
NetEq::kOK,
neteq_internal_->InsertPacket(
rtp_header, payload, payload_size_bytes, receive_timestamp));
// Insert packet in external decoder instance.
NetEqExternalDecoderUnitTest::InsertPacket(rtp_header, payload,
payload_size_bytes,
receive_timestamp);
}
int NumExpectedDecodeCalls(int num_loops) override { return num_loops; }
private:
int sample_rate_hz_;
scoped_ptr<NetEq> neteq_internal_;
int16_t output_internal_[kMaxBlockSize];
int16_t output_[kMaxBlockSize];
};
TEST_F(NetEqExternalVsInternalDecoderTest, RunTest) {
RunTest(100); // Run 100 laps @ 10 ms each in the test loop.
}
class LargeTimestampJumpTest : public NetEqExternalDecoderTest {
class LargeTimestampJumpTest : public NetEqExternalDecoderUnitTest,
public ::testing::Test {
protected:
static const int kMaxBlockSize = 480; // 10 ms @ 48 kHz.
enum TestStates {
kInitialPhase,
kNormalPhase,
@ -234,60 +250,11 @@ class LargeTimestampJumpTest : public NetEqExternalDecoderTest {
};
LargeTimestampJumpTest()
: NetEqExternalDecoderTest(), test_state_(kInitialPhase) {
sample_rate_hz_ = 8000;
samples_per_ms_ = sample_rate_hz_ / 1000;
frame_size_samples_ = frame_size_ms_ * samples_per_ms_;
output_size_samples_ = frame_size_ms_ * samples_per_ms_;
EXPECT_CALL(*external_decoder_, Die()).Times(1);
external_decoder_.reset(new MockExternalPcm16B);
}
void SetUp() OVERRIDE {
const std::string file_name =
webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm");
input_file_.reset(new test::InputAudioFile(file_name));
assert(sample_rate_hz_ == 8000);
NetEqDecoder decoder = kDecoderPCM16B;
EXPECT_CALL(*external_decoder_, Init());
EXPECT_CALL(*external_decoder_, HasDecodePlc())
: NetEqExternalDecoderUnitTest(kDecoderPCM16B,
new MockExternalPcm16B),
test_state_(kInitialPhase) {
EXPECT_CALL(*external_decoder(), HasDecodePlc())
.WillRepeatedly(Return(false));
// NetEq is not allowed to delete the external decoder (hence Times(0)).
EXPECT_CALL(*external_decoder_, Die()).Times(0);
ASSERT_EQ(NetEq::kOK,
neteq_external_->RegisterExternalDecoder(
external_decoder_.get(), decoder, kPayloadType));
ASSERT_EQ(NetEq::kOK, neteq_->RegisterPayloadType(decoder, kPayloadType));
}
void InsertPackets(int next_arrival_time) OVERRIDE {
// Insert packet in external decoder instance.
EXPECT_CALL(*external_decoder_,
IncomingPacket(_,
payload_size_bytes_,
rtp_header_.header.sequenceNumber,
rtp_header_.header.timestamp,
next_arrival_time));
ASSERT_EQ(
NetEq::kOK,
neteq_external_->InsertPacket(
rtp_header_, encoded_, payload_size_bytes_, next_arrival_time));
}
void GetOutputAudio() OVERRIDE {
NetEqOutputType output_type;
int samples_per_channel;
int num_channels;
// Get audio from external decoder instance.
ASSERT_EQ(NetEq::kOK,
neteq_external_->GetAudio(kMaxBlockSize,
output_external_,
&samples_per_channel,
&num_channels,
&output_type));
EXPECT_EQ(1, num_channels);
EXPECT_EQ(output_size_samples_, samples_per_channel);
UpdateState(output_type);
}
virtual void UpdateState(NetEqOutputType output_type) {
@ -324,20 +291,26 @@ class LargeTimestampJumpTest : public NetEqExternalDecoderTest {
}
}
void VerifyOutput(size_t num_samples) const OVERRIDE {
void GetAndVerifyOutput() override {
int num_samples;
NetEqOutputType output_type;
num_samples = GetOutputAudio(kMaxBlockSize, output_, &output_type);
UpdateState(output_type);
if (test_state_ == kExpandPhase || test_state_ == kFadedExpandPhase) {
// Don't verify the output in this phase of the test.
return;
}
for (size_t i = 0; i < num_samples; ++i) {
if (output_external_[i] != 0)
for (int i = 0; i < num_samples; ++i) {
if (output_[i] != 0)
return;
}
EXPECT_TRUE(false)
<< "Expected at least one non-zero sample in each output block.";
}
int NumExpectedDecodeCalls(int num_loops) const OVERRIDE {
int NumExpectedDecodeCalls(int num_loops) override {
// Some packets at the end of the stream won't be decoded. When the jump in
// timestamp happens, NetEq will do Expand during one GetAudio call. In the
// next call it will decode the packet after the jump, but the net result is
@ -349,6 +322,9 @@ class LargeTimestampJumpTest : public NetEqExternalDecoderTest {
}
TestStates test_state_;
private:
int16_t output_[kMaxBlockSize];
};
TEST_F(LargeTimestampJumpTest, JumpLongerThanHalfRange) {
@ -365,11 +341,11 @@ TEST_F(LargeTimestampJumpTest, JumpLongerThanHalfRange) {
static_cast<uint32_t>(kJumpToTimestamp - kJumpFromTimestamp) > 0x7FFFFFFF,
"jump should be larger than half range");
// Replace the default RTP generator with one that jumps in timestamp.
rtp_generator_.reset(new test::TimestampJumpRtpGenerator(samples_per_ms_,
kStartSeqeunceNumber,
kStartTimestamp,
kJumpFromTimestamp,
kJumpToTimestamp));
ResetRtpGenerator(new test::TimestampJumpRtpGenerator(samples_per_ms(),
kStartSeqeunceNumber,
kStartTimestamp,
kJumpFromTimestamp,
kJumpToTimestamp));
RunTest(130); // Run 130 laps @ 10 ms each in the test loop.
EXPECT_EQ(kRecovered, test_state_);
@ -389,11 +365,11 @@ TEST_F(LargeTimestampJumpTest, JumpLongerThanHalfRangeAndWrap) {
static_cast<uint32_t>(kJumpToTimestamp - kJumpFromTimestamp) > 0x7FFFFFFF,
"jump should be larger than half range");
// Replace the default RTP generator with one that jumps in timestamp.
rtp_generator_.reset(new test::TimestampJumpRtpGenerator(samples_per_ms_,
kStartSeqeunceNumber,
kStartTimestamp,
kJumpFromTimestamp,
kJumpToTimestamp));
ResetRtpGenerator(new test::TimestampJumpRtpGenerator(samples_per_ms(),
kStartSeqeunceNumber,
kStartTimestamp,
kJumpFromTimestamp,
kJumpToTimestamp));
RunTest(130); // Run 130 laps @ 10 ms each in the test loop.
EXPECT_EQ(kRecovered, test_state_);
@ -428,7 +404,7 @@ class ShortTimestampJumpTest : public LargeTimestampJumpTest {
}
}
int NumExpectedDecodeCalls(int num_loops) const OVERRIDE {
int NumExpectedDecodeCalls(int num_loops) override {
// Some packets won't be decoded because of the timestamp jump.
return num_loops - 2;
}
@ -448,11 +424,11 @@ TEST_F(ShortTimestampJumpTest, JumpShorterThanHalfRange) {
static_cast<uint32_t>(kJumpToTimestamp - kJumpFromTimestamp) < 0x7FFFFFFF,
"jump should be smaller than half range");
// Replace the default RTP generator with one that jumps in timestamp.
rtp_generator_.reset(new test::TimestampJumpRtpGenerator(samples_per_ms_,
kStartSeqeunceNumber,
kStartTimestamp,
kJumpFromTimestamp,
kJumpToTimestamp));
ResetRtpGenerator(new test::TimestampJumpRtpGenerator(samples_per_ms(),
kStartSeqeunceNumber,
kStartTimestamp,
kJumpFromTimestamp,
kJumpToTimestamp));
RunTest(130); // Run 130 laps @ 10 ms each in the test loop.
EXPECT_EQ(kRecovered, test_state_);
@ -472,11 +448,11 @@ TEST_F(ShortTimestampJumpTest, JumpShorterThanHalfRangeAndWrap) {
static_cast<uint32_t>(kJumpToTimestamp - kJumpFromTimestamp) < 0x7FFFFFFF,
"jump should be smaller than half range");
// Replace the default RTP generator with one that jumps in timestamp.
rtp_generator_.reset(new test::TimestampJumpRtpGenerator(samples_per_ms_,
kStartSeqeunceNumber,
kStartTimestamp,
kJumpFromTimestamp,
kJumpToTimestamp));
ResetRtpGenerator(new test::TimestampJumpRtpGenerator(samples_per_ms(),
kStartSeqeunceNumber,
kStartTimestamp,
kJumpFromTimestamp,
kJumpToTimestamp));
RunTest(130); // Run 130 laps @ 10 ms each in the test loop.
EXPECT_EQ(kRecovered, test_state_);

View File

@ -1799,10 +1799,14 @@ int NetEqImpl::ExtractPackets(int required_samples, PacketList* packet_list) {
if (packet->sync_packet) {
packet_duration = decoder_frame_length_;
} else {
packet_duration = packet->primary ?
decoder->PacketDuration(packet->payload, packet->payload_length) :
decoder->PacketDurationRedundant(packet->payload,
packet->payload_length);
if (packet->primary) {
packet_duration = decoder->PacketDuration(packet->payload,
packet->payload_length);
} else {
packet_duration = decoder->
PacketDurationRedundant(packet->payload, packet->payload_length);
stats_.SecondaryDecodedSamples(packet_duration);
}
}
} else {
LOG_FERR1(LS_WARNING, GetDecoder,

View File

@ -0,0 +1,258 @@
/*
* 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/modules/audio_coding/neteq/audio_decoder_impl.h"
#include "webrtc/modules/audio_coding/neteq/tools/neteq_external_decoder_test.h"
#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
namespace webrtc {
namespace test {
using ::testing::_;
using ::testing::SetArgPointee;
using ::testing::Return;
class MockAudioDecoderOpus : public AudioDecoderOpus {
public:
static const int kPacketDuration = 960; // 48 kHz * 20 ms
explicit MockAudioDecoderOpus(int num_channels)
: AudioDecoderOpus(num_channels),
fec_enabled_(false) {
}
virtual ~MockAudioDecoderOpus() { Die(); }
MOCK_METHOD0(Die, void());
MOCK_METHOD0(Init, int());
// Override the following methods such that no actual payload is needed.
int Decode(const uint8_t* encoded, size_t encoded_len, int16_t* decoded,
SpeechType* speech_type) override {
*speech_type = kSpeech;
memset(decoded, 0, sizeof(int16_t) * kPacketDuration * channels_);
return kPacketDuration * channels_;
}
int DecodeRedundant(const uint8_t* encoded, size_t encoded_len,
int16_t* decoded, SpeechType* speech_type) override {
return Decode(encoded, encoded_len, decoded, speech_type);
}
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_;
}
void set_fec_enabled(bool enable_fec) { fec_enabled_ = enable_fec; }
bool fec_enabled() const { return fec_enabled_; }
private:
bool fec_enabled_;
};
class NetEqNetworkStatsTest : public NetEqExternalDecoderTest {
public:
static const int kPayloadSizeByte = 30;
static const int kFrameSizeMs = 20; // frame size of Opus
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 preemptive_rate;
logic accelerate_rate;
logic secondary_decoded_rate;
logic clockdrift_ppm;
logic added_zero_samples;
NetEqNetworkStatistics stats_ref;
};
NetEqNetworkStatsTest(NetEqDecoder codec,
MockAudioDecoderOpus* 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(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
}
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_, kPayloadSizeByte,
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
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}
};
RunTest(50, expects);
// Next we introduce packet losses.
SetPacketLossRate(0.1);
expects.stats_ref.packet_loss_rate = 1337;
expects.stats_ref.expand_rate = 1065;
RunTest(50, expects);
// Next we enable Opus 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 = 0;
expects.stats_ref.secondary_decoded_rate = 2006;
RunTest(50, expects);
}
private:
MockAudioDecoderOpus* external_decoder_;
const int samples_per_ms_;
const size_t frame_size_samples_;
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, OpusDecodeFec) {
MockAudioDecoderOpus decoder(1);
EXPECT_CALL(decoder, Init());
NetEqNetworkStatsTest test(kDecoderOpus, &decoder);
test.DecodeFecTest();
EXPECT_CALL(decoder, Die()).Times(1);
}
TEST(NetEqNetworkStatsTest, StereoOpusDecodeFec) {
MockAudioDecoderOpus decoder(2);
EXPECT_CALL(decoder, Init());
NetEqNetworkStatsTest test(kDecoderOpus, &decoder);
test.DecodeFecTest();
EXPECT_CALL(decoder, Die()).Times(1);
}
} // namespace test
} // namespace webrtc

View File

@ -164,6 +164,8 @@
'<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
],
'sources': [
'tools/neteq_external_decoder_test.cc',
'tools/neteq_external_decoder_test.h',
'tools/neteq_performance_test.cc',
'tools/neteq_performance_test.h',
'tools/neteq_quality_test.cc',

View File

@ -137,13 +137,42 @@ void RefFiles::WriteToFile(const NetEqNetworkStatistics& stats) {
void RefFiles::ReadFromFileAndCompare(
const NetEqNetworkStatistics& stats) {
// TODO(minyue): Update resource/audio_coding/neteq_network_stats.dat and
// resource/audio_coding/neteq_network_stats_win32.dat.
struct NetEqNetworkStatisticsOld {
uint16_t current_buffer_size_ms; // Current jitter buffer size in ms.
uint16_t preferred_buffer_size_ms; // Target buffer size in ms.
uint16_t jitter_peaks_found; // 1 if adding extra delay due to peaky
// jitter; 0 otherwise.
uint16_t packet_loss_rate; // Loss rate (network + late) in Q14.
uint16_t packet_discard_rate; // Late loss rate in Q14.
uint16_t expand_rate; // Fraction (of original stream) of synthesized
// speech inserted through expansion (in Q14).
uint16_t preemptive_rate; // Fraction of data inserted through pre-emptive
// expansion (in Q14).
uint16_t accelerate_rate; // Fraction of data removed through acceleration
// (in Q14).
int32_t clockdrift_ppm; // Average clock-drift in parts-per-million
// (positive or negative).
int added_zero_samples; // Number of zero samples added in "off" mode.
};
if (input_fp_) {
// Read from ref file.
size_t stat_size = sizeof(NetEqNetworkStatistics);
NetEqNetworkStatistics ref_stats;
size_t stat_size = sizeof(NetEqNetworkStatisticsOld);
NetEqNetworkStatisticsOld ref_stats;
ASSERT_EQ(1u, fread(&ref_stats, stat_size, 1, input_fp_));
// Compare
ASSERT_EQ(0, memcmp(&stats, &ref_stats, stat_size));
ASSERT_EQ(stats.current_buffer_size_ms, ref_stats.current_buffer_size_ms);
ASSERT_EQ(stats.preferred_buffer_size_ms,
ref_stats.preferred_buffer_size_ms);
ASSERT_EQ(stats.jitter_peaks_found, ref_stats.jitter_peaks_found);
ASSERT_EQ(stats.packet_loss_rate, ref_stats.packet_loss_rate);
ASSERT_EQ(stats.packet_discard_rate, ref_stats.packet_discard_rate);
ASSERT_EQ(stats.preemptive_rate, ref_stats.preemptive_rate);
ASSERT_EQ(stats.accelerate_rate, ref_stats.accelerate_rate);
ASSERT_EQ(stats.clockdrift_ppm, ref_stats.clockdrift_ppm);
ASSERT_EQ(stats.added_zero_samples, ref_stats.added_zero_samples);
ASSERT_EQ(stats.secondary_decoded_rate, 0);
}
}

View File

@ -28,7 +28,8 @@ StatisticsCalculator::StatisticsCalculator()
lost_timestamps_(0),
timestamps_since_last_report_(0),
len_waiting_times_(0),
next_waiting_time_index_(0) {
next_waiting_time_index_(0),
secondary_decoded_samples_(0) {
memset(waiting_times_, 0, kLenWaitingTimes * sizeof(waiting_times_[0]));
}
@ -38,6 +39,7 @@ void StatisticsCalculator::Reset() {
added_zero_samples_ = 0;
expanded_voice_samples_ = 0;
expanded_noise_samples_ = 0;
secondary_decoded_samples_ = 0;
}
void StatisticsCalculator::ResetMcu() {
@ -90,6 +92,10 @@ void StatisticsCalculator::IncreaseCounter(int num_samples, int fs_hz) {
}
}
void StatisticsCalculator::SecondaryDecodedSamples(int num_samples) {
secondary_decoded_samples_ += num_samples;
}
void StatisticsCalculator::StoreWaitingTime(int waiting_time_ms) {
assert(next_waiting_time_index_ < kLenWaitingTimes);
waiting_times_[next_waiting_time_index_] = waiting_time_ms;
@ -140,6 +146,10 @@ void StatisticsCalculator::GetNetworkStatistics(
CalculateQ14Ratio(expanded_voice_samples_ + expanded_noise_samples_,
timestamps_since_last_report_);
stats->secondary_decoded_rate =
CalculateQ14Ratio(secondary_decoded_samples_,
timestamps_since_last_report_);
// Reset counters.
ResetMcu();
Reset();

View File

@ -70,6 +70,9 @@ class StatisticsCalculator {
// Stores new packet waiting time in waiting time statistics.
void StoreWaitingTime(int waiting_time_ms);
// Reports that |num_samples| samples were decoded from secondary packets.
void SecondaryDecodedSamples(int num_samples);
// Returns the current network statistics in |stats|. The current sample rate
// is |fs_hz|, the total number of samples in packet buffer and sync buffer
// yet to play out is |num_samples_in_buffers|, and the number of samples per
@ -101,6 +104,7 @@ class StatisticsCalculator {
int waiting_times_[kLenWaitingTimes]; // Used as a circular buffer.
int len_waiting_times_;
int next_waiting_time_index_;
uint32_t secondary_decoded_samples_;
DISALLOW_COPY_AND_ASSIGN(StatisticsCalculator);
};

View File

@ -0,0 +1,64 @@
/*
* 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 "webrtc/modules/audio_coding/neteq/tools/neteq_external_decoder_test.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace webrtc {
namespace test {
NetEqExternalDecoderTest::NetEqExternalDecoderTest(NetEqDecoder codec,
AudioDecoder* decoder)
: codec_(codec),
decoder_(decoder),
sample_rate_hz_(CodecSampleRateHz(codec_)),
channels_(static_cast<int>(decoder_->channels())) {
NetEq::Config config;
config.sample_rate_hz = sample_rate_hz_;
neteq_.reset(NetEq::Create(config));
printf("%d\n", channels_);
}
void NetEqExternalDecoderTest::Init() {
ASSERT_EQ(NetEq::kOK,
neteq_->RegisterExternalDecoder(decoder_, codec_, kPayloadType));
}
void NetEqExternalDecoderTest::InsertPacket(WebRtcRTPHeader rtp_header,
const uint8_t* payload,
size_t payload_size_bytes,
uint32_t receive_timestamp) {
ASSERT_EQ(
NetEq::kOK,
neteq_->InsertPacket(
rtp_header, payload, payload_size_bytes, receive_timestamp));
}
int NetEqExternalDecoderTest::GetOutputAudio(size_t max_length,
int16_t* output,
NetEqOutputType* output_type) {
// Get audio from regular instance.
int samples_per_channel;
int num_channels;
EXPECT_EQ(NetEq::kOK,
neteq_->GetAudio(max_length,
output,
&samples_per_channel,
&num_channels,
output_type));
EXPECT_EQ(channels_, num_channels);
EXPECT_EQ(kOutputLengthMs * sample_rate_hz_ / 1000, samples_per_channel);
return samples_per_channel;
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,61 @@
/*
* 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.
*/
#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_EXTERNAL_DECODER_TEST_H_
#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_EXTERNAL_DECODER_TEST_H_
#include "webrtc/modules/audio_coding/codecs/audio_decoder.h"
#include "webrtc/modules/audio_coding/neteq/interface/neteq.h"
#include "webrtc/modules/interface/module_common_types.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
namespace webrtc {
namespace test {
// This test class provides a way run NetEQ with an external decoder.
class NetEqExternalDecoderTest {
protected:
static const uint8_t kPayloadType = 95;
static const int kOutputLengthMs = 10;
// The external decoder |decoder| is suppose to be of type |codec|.
NetEqExternalDecoderTest(NetEqDecoder codec, AudioDecoder* decoder);
virtual ~NetEqExternalDecoderTest() { }
// In Init(), we register the external decoder.
void Init();
// Inserts a new packet with |rtp_header| and |payload| of
// |payload_size_bytes| bytes. The |receive_timestamp| is an indication
// of the time when the packet was received, and should be measured with
// the same tick rate as the RTP timestamp of the current payload.
virtual void InsertPacket(WebRtcRTPHeader rtp_header, const uint8_t* payload,
size_t payload_size_bytes,
uint32_t receive_timestamp);
// Get 10 ms of audio data. The data is written to |output|, which can hold
// (at least) |max_length| elements. Returns number of samples.
int GetOutputAudio(size_t max_length, int16_t* output,
NetEqOutputType* output_type);
NetEq* neteq() { return neteq_.get(); }
private:
NetEqDecoder codec_;
AudioDecoder* decoder_;
int sample_rate_hz_;
int channels_;
scoped_ptr<NetEq> neteq_;
};
} // namespace test
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_EXTERNAL_DECODER_TEST_H_

View File

@ -64,6 +64,7 @@
'iSACFix',
'media_file',
'neteq',
'neteq_test_support',
'neteq_unittest_tools',
'paced_sender',
'PCM16B', # Needed by NetEq tests.
@ -125,6 +126,7 @@
'audio_coding/neteq/merge_unittest.cc',
'audio_coding/neteq/neteq_external_decoder_unittest.cc',
'audio_coding/neteq/neteq_impl_unittest.cc',
'audio_coding/neteq/neteq_network_stats_unittest.cc',
'audio_coding/neteq/neteq_stereo_unittest.cc',
'audio_coding/neteq/neteq_unittest.cc',
'audio_coding/neteq/normal_unittest.cc',