This is to compare NetEq with various codecs under a shared packet loss pattern.
TEST=passed_all_trybots R=henrik.lundin@webrtc.org, tina.legrand@webrtc.org Review URL: https://webrtc-codereview.appspot.com/15529004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@6536 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
d5075bdbb5
commit
6568e97d10
@ -202,6 +202,22 @@
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
'target_name': 'neteq_isac_quality_test',
|
||||
'type': 'executable',
|
||||
'dependencies': [
|
||||
'neteq',
|
||||
'neteq_test_support',
|
||||
'iSACFix',
|
||||
'<(DEPTH)/testing/gtest.gyp:gtest',
|
||||
'<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
|
||||
'<(webrtc_root)/test/test.gyp:test_support_main',
|
||||
],
|
||||
'sources': [
|
||||
'test/neteq_isac_quality_test.cc',
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
'target_name': 'neteq_test_tools',
|
||||
# Collection of useful functions used in other tests.
|
||||
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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/codecs/isac/fix/interface/isacfix.h"
|
||||
#include "webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h"
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
|
||||
using google::RegisterFlagValidator;
|
||||
using google::ParseCommandLineFlags;
|
||||
using std::string;
|
||||
using testing::InitGoogleTest;
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
static const int kIsacBlockDurationMs = 30;
|
||||
static const int kIsacInputSamplingKhz = 16;
|
||||
static const int kIsacOutputSamplingKhz = 16;
|
||||
|
||||
// Define switch for input file name.
|
||||
static bool ValidateInFilename(const char* flagname, const string& value) {
|
||||
FILE* fid = fopen(value.c_str(), "rb");
|
||||
if (fid != NULL) {
|
||||
fclose(fid);
|
||||
return true;
|
||||
}
|
||||
printf("Invalid input filename.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_string(in_filename,
|
||||
ResourcePath("audio_coding/speech_mono_16kHz", "pcm"),
|
||||
"Filename for input audio (should be 16 kHz sampled mono).");
|
||||
|
||||
static const bool in_filename_dummy =
|
||||
RegisterFlagValidator(&FLAGS_in_filename, &ValidateInFilename);
|
||||
|
||||
// Define switch for output file name.
|
||||
static bool ValidateOutFilename(const char* flagname, const string& value) {
|
||||
FILE* fid = fopen(value.c_str(), "wb");
|
||||
if (fid != NULL) {
|
||||
fclose(fid);
|
||||
return true;
|
||||
}
|
||||
printf("Invalid output filename.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_string(out_filename, OutputPath() + "neteq4_isac_quality_test.pcm",
|
||||
"Name of output audio file.");
|
||||
|
||||
static const bool out_filename_dummy =
|
||||
RegisterFlagValidator(&FLAGS_out_filename, &ValidateOutFilename);
|
||||
|
||||
// Define switch for bir rate.
|
||||
static bool ValidateBitRate(const char* flagname, int32_t value) {
|
||||
if (value >= 10 && value <= 32)
|
||||
return true;
|
||||
printf("Invalid bit rate, should be between 10 and 32 kbps.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(bit_rate_kbps, 32, "Target bit rate (kbps).");
|
||||
|
||||
static const bool bit_rate_dummy =
|
||||
RegisterFlagValidator(&FLAGS_bit_rate_kbps, &ValidateBitRate);
|
||||
|
||||
// Define switch for runtime.
|
||||
static bool ValidateRuntime(const char* flagname, int32_t value) {
|
||||
if (value > 0)
|
||||
return true;
|
||||
printf("Invalid runtime, should be greater than 0.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(runtime_ms, 10000, "Simulated runtime (milliseconds).");
|
||||
|
||||
static const bool runtime_dummy =
|
||||
RegisterFlagValidator(&FLAGS_runtime_ms, &ValidateRuntime);
|
||||
|
||||
class NetEqIsacQualityTest : public NetEqQualityTest {
|
||||
protected:
|
||||
NetEqIsacQualityTest();
|
||||
virtual void SetUp() OVERRIDE;
|
||||
virtual void TearDown() OVERRIDE;
|
||||
virtual int EncodeBlock(int16_t* in_data, int block_size_samples,
|
||||
uint8_t* payload, int max_bytes);
|
||||
private:
|
||||
ISACFIX_MainStruct* isac_encoder_;
|
||||
int bit_rate_kbps_;
|
||||
};
|
||||
|
||||
NetEqIsacQualityTest::NetEqIsacQualityTest()
|
||||
: NetEqQualityTest(kIsacBlockDurationMs, kIsacInputSamplingKhz,
|
||||
kIsacOutputSamplingKhz,
|
||||
kDecoderISAC,
|
||||
1,
|
||||
FLAGS_in_filename,
|
||||
FLAGS_out_filename),
|
||||
isac_encoder_(NULL),
|
||||
bit_rate_kbps_(FLAGS_bit_rate_kbps) {
|
||||
}
|
||||
|
||||
void NetEqIsacQualityTest::SetUp() {
|
||||
// Create encoder memory.
|
||||
WebRtcIsacfix_Create(&isac_encoder_);
|
||||
ASSERT_TRUE(isac_encoder_ != NULL);
|
||||
EXPECT_EQ(0, WebRtcIsacfix_EncoderInit(isac_encoder_, 1));
|
||||
// Set bitrate and block length.
|
||||
EXPECT_EQ(0, WebRtcIsacfix_Control(isac_encoder_, bit_rate_kbps_ * 1000,
|
||||
kIsacBlockDurationMs));
|
||||
NetEqQualityTest::SetUp();
|
||||
}
|
||||
|
||||
void NetEqIsacQualityTest::TearDown() {
|
||||
// Free memory.
|
||||
EXPECT_EQ(0, WebRtcIsacfix_Free(isac_encoder_));
|
||||
NetEqQualityTest::TearDown();
|
||||
}
|
||||
|
||||
int NetEqIsacQualityTest::EncodeBlock(int16_t* in_data,
|
||||
int block_size_samples,
|
||||
uint8_t* payload, int max_bytes) {
|
||||
// ISAC takes 10 ms for every call.
|
||||
const int subblocks = kIsacBlockDurationMs / 10;
|
||||
const int subblock_length = 10 * kIsacInputSamplingKhz;
|
||||
int value = 0;
|
||||
|
||||
int pointer = 0;
|
||||
for (int idx = 0; idx < subblocks; idx++, pointer += subblock_length) {
|
||||
// The Isac encoder does not perform encoding (and returns 0) until it
|
||||
// receives a sequence of sub-blocks that amount to the frame duration.
|
||||
EXPECT_EQ(0, value);
|
||||
value = WebRtcIsacfix_Encode(isac_encoder_, &in_data[pointer],
|
||||
reinterpret_cast<int16_t*>(payload));
|
||||
}
|
||||
EXPECT_GT(value, 0);
|
||||
return value;
|
||||
}
|
||||
|
||||
TEST_F(NetEqIsacQualityTest, Test) {
|
||||
Simulate(FLAGS_runtime_ms);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
@ -8,7 +8,6 @@
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h"
|
||||
#include "webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h"
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
@ -25,6 +24,7 @@ static const int kOpusBlockDurationMs = 20;
|
||||
static const int kOpusInputSamplingKhz = 48;
|
||||
static const int kOpusOutputSamplingKhz = 32;
|
||||
|
||||
// Define switch for input file name.
|
||||
static bool ValidateInFilename(const char* flagname, const string& value) {
|
||||
FILE* fid = fopen(value.c_str(), "rb");
|
||||
if (fid != NULL) {
|
||||
@ -34,12 +34,15 @@ static bool ValidateInFilename(const char* flagname, const string& value) {
|
||||
printf("Invalid input filename.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_string(in_filename,
|
||||
ResourcePath("audio_coding/speech_mono_32_48kHz", "pcm"),
|
||||
"Filename for input audio (should be 48 kHz sampled raw data).");
|
||||
|
||||
static const bool in_filename_dummy =
|
||||
RegisterFlagValidator(&FLAGS_in_filename, &ValidateInFilename);
|
||||
|
||||
// Define switch for output file name.
|
||||
static bool ValidateOutFilename(const char* flagname, const string& value) {
|
||||
FILE* fid = fopen(value.c_str(), "wb");
|
||||
if (fid != NULL) {
|
||||
@ -49,50 +52,60 @@ static bool ValidateOutFilename(const char* flagname, const string& value) {
|
||||
printf("Invalid output filename.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_string(out_filename, OutputPath() + "neteq4_opus_fec_quality_test.pcm",
|
||||
"Name of output audio file.");
|
||||
|
||||
static const bool out_filename_dummy =
|
||||
RegisterFlagValidator(&FLAGS_out_filename, &ValidateOutFilename);
|
||||
|
||||
// Define switch for channels.
|
||||
static bool ValidateChannels(const char* flagname, int32_t value) {
|
||||
if (value == 1 || value == 2)
|
||||
return true;
|
||||
printf("Invalid number of channels, should be either 1 or 2.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(channels, 1, "Number of channels in input audio.");
|
||||
|
||||
static const bool channels_dummy =
|
||||
RegisterFlagValidator(&FLAGS_channels, &ValidateChannels);
|
||||
|
||||
// Define switch for bit rate.
|
||||
static bool ValidateBitRate(const char* flagname, int32_t value) {
|
||||
if (value >= 6 && value <= 510)
|
||||
return true;
|
||||
printf("Invalid bit rate, should be between 6 and 510 kbps.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(bit_rate_kbps, 32, "Target bit rate (kbps).");
|
||||
|
||||
static const bool bit_rate_dummy =
|
||||
RegisterFlagValidator(&FLAGS_bit_rate_kbps, &ValidateBitRate);
|
||||
|
||||
// Define switch for reported packet loss rate.
|
||||
static bool ValidatePacketLossRate(const char* flagname, int32_t value) {
|
||||
if (value >= 0 && value <= 100)
|
||||
return true;
|
||||
printf("Invalid packet loss percentile, should be between 0 and 100.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(reported_loss_rate, 10, "Reported percentile of packet loss.");
|
||||
|
||||
static const bool reported_loss_rate_dummy =
|
||||
RegisterFlagValidator(&FLAGS_reported_loss_rate, &ValidatePacketLossRate);
|
||||
DEFINE_int32(actual_loss_rate, 0, "Actual percentile of packet loss.");
|
||||
static const bool actual_loss_rate_dummy =
|
||||
RegisterFlagValidator(&FLAGS_actual_loss_rate, &ValidatePacketLossRate);
|
||||
|
||||
// Define switch for runtime.
|
||||
static bool ValidateRuntime(const char* flagname, int32_t value) {
|
||||
if (value > 0)
|
||||
return true;
|
||||
printf("Invalid runtime, should be greater than 0.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(runtime_ms, 10000, "Simulated runtime (milliseconds).");
|
||||
static const bool runtime_dummy =
|
||||
RegisterFlagValidator(&FLAGS_runtime_ms, &ValidateRuntime);
|
||||
@ -106,28 +119,26 @@ class NetEqOpusFecQualityTest : public NetEqQualityTest {
|
||||
virtual void TearDown() OVERRIDE;
|
||||
virtual int EncodeBlock(int16_t* in_data, int block_size_samples,
|
||||
uint8_t* payload, int max_bytes);
|
||||
virtual bool PacketLost(int packet_input_time_ms);
|
||||
private:
|
||||
WebRtcOpusEncInst* opus_encoder_;
|
||||
int channels_;
|
||||
int bit_rate_kbps_;
|
||||
bool fec_;
|
||||
int target_loss_rate_;
|
||||
int actual_loss_rate_;
|
||||
};
|
||||
|
||||
NetEqOpusFecQualityTest::NetEqOpusFecQualityTest()
|
||||
: NetEqQualityTest(kOpusBlockDurationMs, kOpusInputSamplingKhz,
|
||||
kOpusOutputSamplingKhz,
|
||||
(FLAGS_channels == 1) ? kDecoderOpus : kDecoderOpus_2ch,
|
||||
FLAGS_channels, 0.0f, FLAGS_in_filename,
|
||||
FLAGS_channels,
|
||||
FLAGS_in_filename,
|
||||
FLAGS_out_filename),
|
||||
opus_encoder_(NULL),
|
||||
channels_(FLAGS_channels),
|
||||
bit_rate_kbps_(FLAGS_bit_rate_kbps),
|
||||
fec_(FLAGS_fec),
|
||||
target_loss_rate_(FLAGS_reported_loss_rate),
|
||||
actual_loss_rate_(FLAGS_actual_loss_rate) {
|
||||
target_loss_rate_(FLAGS_reported_loss_rate) {
|
||||
}
|
||||
|
||||
void NetEqOpusFecQualityTest::SetUp() {
|
||||
@ -138,9 +149,9 @@ void NetEqOpusFecQualityTest::SetUp() {
|
||||
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, bit_rate_kbps_ * 1000));
|
||||
if (fec_) {
|
||||
EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_encoder_));
|
||||
EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_,
|
||||
target_loss_rate_));
|
||||
}
|
||||
EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_,
|
||||
target_loss_rate_));
|
||||
NetEqQualityTest::SetUp();
|
||||
}
|
||||
|
||||
@ -160,16 +171,6 @@ int NetEqOpusFecQualityTest::EncodeBlock(int16_t* in_data,
|
||||
return value;
|
||||
}
|
||||
|
||||
bool NetEqOpusFecQualityTest::PacketLost(int packet_input_time_ms) {
|
||||
static int packets = 0, lost_packets = 0;
|
||||
packets++;
|
||||
if (lost_packets * 100 < actual_loss_rate_ * packets) {
|
||||
lost_packets++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_F(NetEqOpusFecQualityTest, Test) {
|
||||
Simulate(FLAGS_runtime_ms);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include "webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h"
|
||||
|
||||
@ -16,18 +17,116 @@ namespace test {
|
||||
|
||||
const uint8_t kPayloadType = 95;
|
||||
const int kOutputSizeMs = 10;
|
||||
const int kInitSeed = 0x12345678;
|
||||
const int kPacketLossTimeUnitMs = 10;
|
||||
|
||||
// Define switch for packet loss rate.
|
||||
static bool ValidatePacketLossRate(const char* /* flag_name */, int32_t value) {
|
||||
if (value >= 0 && value <= 100)
|
||||
return true;
|
||||
printf("Invalid packet loss percentile, should be between 0 and 100.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(packet_loss_rate, 10, "Percentile of packet loss.");
|
||||
|
||||
static const bool packet_loss_rate_dummy =
|
||||
RegisterFlagValidator(&FLAGS_packet_loss_rate, &ValidatePacketLossRate);
|
||||
|
||||
// Define switch for random loss mode.
|
||||
static bool ValidateRandomLossMode(const char* /* flag_name */, int32_t value) {
|
||||
if (value >= 0 && value <= 2)
|
||||
return true;
|
||||
printf("Invalid random packet loss mode, should be between 0 and 2.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(random_loss_mode, 1,
|
||||
"Random loss mode: 0--no loss, 1--uniform loss, 2--Gilbert Elliot loss.");
|
||||
static const bool random_loss_mode_dummy =
|
||||
RegisterFlagValidator(&FLAGS_random_loss_mode, &ValidateRandomLossMode);
|
||||
|
||||
// Define switch for burst length.
|
||||
static bool ValidateBurstLength(const char* /* flag_name */, int32_t value) {
|
||||
if (value >= kPacketLossTimeUnitMs)
|
||||
return true;
|
||||
printf("Invalid burst length, should be greater than %d ms.",
|
||||
kPacketLossTimeUnitMs);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_int32(burst_length, 30,
|
||||
"Burst length in milliseconds, only valid for Gilbert Elliot loss.");
|
||||
|
||||
static const bool burst_length_dummy =
|
||||
RegisterFlagValidator(&FLAGS_burst_length, &ValidateBurstLength);
|
||||
|
||||
// Define switch for drift factor.
|
||||
static bool ValidateDriftFactor(const char* /* flag_name */, double value) {
|
||||
if (value > -0.1)
|
||||
return true;
|
||||
printf("Invalid drift factor, should be greater than -0.1.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_double(drift_factor, 0.0, "Time drift factor.");
|
||||
|
||||
static const bool drift_factor_dummy =
|
||||
RegisterFlagValidator(&FLAGS_drift_factor, &ValidateDriftFactor);
|
||||
|
||||
// ProbTrans00Solver() is to calculate the transition probability from no-loss
|
||||
// state to itself in a modified Gilbert Elliot packet loss model. The result is
|
||||
// to achieve the target packet loss rate |loss_rate|, when a packet is not
|
||||
// lost only if all |units| drawings within the duration of the packet result in
|
||||
// no-loss.
|
||||
static double ProbTrans00Solver(int units, double loss_rate,
|
||||
double prob_trans_10) {
|
||||
if (units == 1)
|
||||
return prob_trans_10 / (1.0f - loss_rate) - prob_trans_10;
|
||||
// 0 == prob_trans_00 ^ (units - 1) + (1 - loss_rate) / prob_trans_10 *
|
||||
// prob_trans_00 - (1 - loss_rate) * (1 + 1 / prob_trans_10).
|
||||
// There is a unique solution between 0.0 and 1.0, due to the monotonicity and
|
||||
// an opposite sign at 0.0 and 1.0.
|
||||
// For simplicity, we reformulate the equation as
|
||||
// f(x) = x ^ (units - 1) + a x + b.
|
||||
// Its derivative is
|
||||
// f'(x) = (units - 1) x ^ (units - 2) + a.
|
||||
// The derivative is strictly greater than 0 when x is between 0 and 1.
|
||||
// We use Newton's method to solve the equation, iteration is
|
||||
// x(k+1) = x(k) - f(x) / f'(x);
|
||||
const double kPrecision = 0.001f;
|
||||
const int kIterations = 100;
|
||||
const double a = (1.0f - loss_rate) / prob_trans_10;
|
||||
const double b = (loss_rate - 1.0f) * (1.0f + 1.0f / prob_trans_10);
|
||||
double x = 0.0f; // Starting point;
|
||||
double f = b;
|
||||
double f_p;
|
||||
int iter = 0;
|
||||
while ((f >= kPrecision || f <= -kPrecision) && iter < kIterations) {
|
||||
f_p = (units - 1.0f) * pow(x, units - 2) + a;
|
||||
x -= f / f_p;
|
||||
if (x > 1.0f) {
|
||||
x = 1.0f;
|
||||
} else if (x < 0.0f) {
|
||||
x = 0.0f;
|
||||
}
|
||||
f = pow(x, units - 1) + a * x + b;
|
||||
iter ++;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
NetEqQualityTest::NetEqQualityTest(int block_duration_ms,
|
||||
int in_sampling_khz,
|
||||
int out_sampling_khz,
|
||||
enum NetEqDecoder decoder_type,
|
||||
int channels,
|
||||
double drift_factor,
|
||||
std::string in_filename,
|
||||
std::string out_filename)
|
||||
: decoded_time_ms_(0),
|
||||
decodable_time_ms_(0),
|
||||
drift_factor_(drift_factor),
|
||||
drift_factor_(FLAGS_drift_factor),
|
||||
packet_loss_rate_(FLAGS_packet_loss_rate),
|
||||
block_duration_ms_(block_duration_ms),
|
||||
in_sampling_khz_(in_sampling_khz),
|
||||
out_sampling_khz_(out_sampling_khz),
|
||||
@ -35,14 +134,17 @@ NetEqQualityTest::NetEqQualityTest(int block_duration_ms,
|
||||
channels_(channels),
|
||||
in_filename_(in_filename),
|
||||
out_filename_(out_filename),
|
||||
log_filename_(out_filename + ".log"),
|
||||
in_size_samples_(in_sampling_khz_ * block_duration_ms_),
|
||||
out_size_samples_(out_sampling_khz_ * kOutputSizeMs),
|
||||
payload_size_bytes_(0),
|
||||
max_payload_bytes_(0),
|
||||
in_file_(new InputAudioFile(in_filename_)),
|
||||
out_file_(NULL),
|
||||
log_file_(NULL),
|
||||
rtp_generator_(new RtpGenerator(in_sampling_khz_, 0, 0,
|
||||
decodable_time_ms_)) {
|
||||
decodable_time_ms_)),
|
||||
total_payload_size_bytes_(0) {
|
||||
NetEq::Config config;
|
||||
config.sample_rate_hz = out_sampling_khz_ * 1000;
|
||||
neteq_.reset(NetEq::Create(config));
|
||||
@ -52,27 +154,136 @@ NetEqQualityTest::NetEqQualityTest(int block_duration_ms,
|
||||
out_data_.reset(new int16_t[out_size_samples_ * channels_]);
|
||||
}
|
||||
|
||||
bool NoLoss::Lost() {
|
||||
return false;
|
||||
}
|
||||
|
||||
UniformLoss::UniformLoss(int loss_rate)
|
||||
: loss_rate_(loss_rate) {
|
||||
}
|
||||
|
||||
bool UniformLoss::Lost() {
|
||||
int drop_this = rand();
|
||||
return (drop_this < loss_rate_ * RAND_MAX);
|
||||
}
|
||||
|
||||
GilbertElliotLoss::GilbertElliotLoss(double prob_trans_11, double prob_trans_01)
|
||||
: prob_trans_11_(prob_trans_11),
|
||||
prob_trans_01_(prob_trans_01),
|
||||
lost_last_(false),
|
||||
uniform_loss_model_(new UniformLoss(0)) {
|
||||
}
|
||||
|
||||
bool GilbertElliotLoss::Lost() {
|
||||
// Simulate bursty channel (Gilbert model).
|
||||
// (1st order) Markov chain model with memory of the previous/last
|
||||
// packet state (lost or received).
|
||||
if (lost_last_) {
|
||||
// Previous packet was not received.
|
||||
uniform_loss_model_->set_loss_rate(prob_trans_11_);
|
||||
return lost_last_ = uniform_loss_model_->Lost();
|
||||
} else {
|
||||
uniform_loss_model_->set_loss_rate(prob_trans_01_);
|
||||
return lost_last_ = uniform_loss_model_->Lost();
|
||||
}
|
||||
}
|
||||
|
||||
void NetEqQualityTest::SetUp() {
|
||||
out_file_ = fopen(out_filename_.c_str(), "wb");
|
||||
log_file_ = fopen(log_filename_.c_str(), "wt");
|
||||
ASSERT_TRUE(out_file_ != NULL);
|
||||
ASSERT_EQ(0, neteq_->RegisterPayloadType(decoder_type_, kPayloadType));
|
||||
rtp_generator_->set_drift_factor(drift_factor_);
|
||||
|
||||
int units = block_duration_ms_ / kPacketLossTimeUnitMs;
|
||||
switch (FLAGS_random_loss_mode) {
|
||||
case 1: {
|
||||
// |unit_loss_rate| is the packet loss rate for each unit time interval
|
||||
// (kPacketLossTimeUnitMs). Since a packet loss event is generated if any
|
||||
// of |block_duration_ms_ / kPacketLossTimeUnitMs| unit time intervals of
|
||||
// a full packet duration is drawn with a loss, |unit_loss_rate| fulfills
|
||||
// (1 - unit_loss_rate) ^ (block_duration_ms_ / kPacketLossTimeUnitMs) ==
|
||||
// 1 - packet_loss_rate.
|
||||
// |unit_loss_rate| is usually small. To increase its resolution, we
|
||||
// magnify it by |RAND_MAX|.
|
||||
double unit_loss_rate = (1.0f - pow(1.0f - 0.01f * packet_loss_rate_,
|
||||
1.0f / units));
|
||||
loss_model_.reset(new UniformLoss(unit_loss_rate));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// |FLAGS_burst_length| should be integer times of kPacketLossTimeUnitMs.
|
||||
ASSERT_EQ(0, FLAGS_burst_length % kPacketLossTimeUnitMs);
|
||||
|
||||
// We do not allow 100 percent packet loss in Gilbert Elliot model, which
|
||||
// makes no sense.
|
||||
ASSERT_GT(100, packet_loss_rate_);
|
||||
|
||||
// To guarantee the overall packet loss rate, transition probabilities
|
||||
// need to satisfy:
|
||||
// pi_0 * (1 - prob_trans_01_) ^ units +
|
||||
// pi_1 * prob_trans_10_ ^ (units - 1) == 1 - loss_rate
|
||||
// pi_0 = prob_trans_10 / (prob_trans_10 + prob_trans_01_)
|
||||
// is the stationary state probability of no-loss
|
||||
// pi_1 = prob_trans_01_ / (prob_trans_10 + prob_trans_01_)
|
||||
// is the stationary state probability of loss
|
||||
// After a derivation prob_trans_00 should satisfy:
|
||||
// prob_trans_00 ^ (units - 1) = (loss_rate - 1) / prob_trans_10 *
|
||||
// prob_trans_00 + (1 - loss_rate) * (1 + 1 / prob_trans_10).
|
||||
double loss_rate = 0.01f * packet_loss_rate_;
|
||||
double prob_trans_10 = 1.0f * kPacketLossTimeUnitMs / FLAGS_burst_length;
|
||||
double prob_trans_00 = ProbTrans00Solver(units, loss_rate, prob_trans_10);
|
||||
loss_model_.reset(new GilbertElliotLoss(1.0f - prob_trans_10,
|
||||
1.0f - prob_trans_00));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
loss_model_.reset(new NoLoss);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that the packet loss profile is same for all derived tests.
|
||||
srand(kInitSeed);
|
||||
}
|
||||
|
||||
void NetEqQualityTest::TearDown() {
|
||||
fclose(out_file_);
|
||||
}
|
||||
|
||||
bool NetEqQualityTest::PacketLost() {
|
||||
int cycles = block_duration_ms_ / kPacketLossTimeUnitMs;
|
||||
|
||||
// The loop is to make sure that codecs with different block lengths share the
|
||||
// same packet loss profile.
|
||||
bool lost = false;
|
||||
for (int idx = 0; idx < cycles; idx ++) {
|
||||
if (loss_model_->Lost()) {
|
||||
// The packet will be lost if any of the drawings indicates a loss, but
|
||||
// the loop has to go on to make sure that codecs with different block
|
||||
// lengths keep the same pace.
|
||||
lost = true;
|
||||
}
|
||||
}
|
||||
return lost;
|
||||
}
|
||||
|
||||
int NetEqQualityTest::Transmit() {
|
||||
int packet_input_time_ms =
|
||||
rtp_generator_->GetRtpHeader(kPayloadType, in_size_samples_,
|
||||
&rtp_header_);
|
||||
if (!PacketLost(packet_input_time_ms) && payload_size_bytes_ > 0) {
|
||||
int ret = neteq_->InsertPacket(rtp_header_, &payload_[0],
|
||||
payload_size_bytes_,
|
||||
packet_input_time_ms * in_sampling_khz_);
|
||||
if (ret != NetEq::kOK)
|
||||
return -1;
|
||||
if (payload_size_bytes_ > 0) {
|
||||
fprintf(log_file_, "Packet at %d ms", packet_input_time_ms);
|
||||
if (!PacketLost()) {
|
||||
int ret = neteq_->InsertPacket(rtp_header_, &payload_[0],
|
||||
payload_size_bytes_,
|
||||
packet_input_time_ms * in_sampling_khz_);
|
||||
if (ret != NetEq::kOK)
|
||||
return -1;
|
||||
fprintf(log_file_, " OK.\n");
|
||||
} else {
|
||||
fprintf(log_file_, " Lost.\n");
|
||||
}
|
||||
}
|
||||
return packet_input_time_ms;
|
||||
}
|
||||
@ -97,11 +308,13 @@ void NetEqQualityTest::Simulate(int end_time_ms) {
|
||||
int audio_size_samples;
|
||||
|
||||
while (decoded_time_ms_ < end_time_ms) {
|
||||
while (decodable_time_ms_ - kOutputSizeMs < decoded_time_ms_) {
|
||||
// Assume 10 packets in packets buffer.
|
||||
while (decodable_time_ms_ - 10 * block_duration_ms_ < decoded_time_ms_) {
|
||||
ASSERT_TRUE(in_file_->Read(in_size_samples_ * channels_, &in_data_[0]));
|
||||
payload_size_bytes_ = EncodeBlock(&in_data_[0],
|
||||
in_size_samples_, &payload_[0],
|
||||
max_payload_bytes_);
|
||||
total_payload_size_bytes_ += payload_size_bytes_;
|
||||
decodable_time_ms_ = Transmit() + block_duration_ms_;
|
||||
}
|
||||
audio_size_samples = DecodeBlock();
|
||||
@ -109,6 +322,7 @@ void NetEqQualityTest::Simulate(int end_time_ms) {
|
||||
decoded_time_ms_ += audio_size_samples / out_sampling_khz_;
|
||||
}
|
||||
}
|
||||
fprintf(log_file_, "%f", 8.0f * total_payload_size_bytes_ / end_time_ms);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
@ -11,6 +11,7 @@
|
||||
#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_QUALITY_TEST_H_
|
||||
#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_QUALITY_TEST_H_
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <string>
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "webrtc/modules/audio_coding/neteq/interface/neteq.h"
|
||||
@ -19,9 +20,44 @@
|
||||
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
using google::RegisterFlagValidator;
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
class LossModel {
|
||||
public:
|
||||
virtual ~LossModel() {};
|
||||
virtual bool Lost() = 0;
|
||||
};
|
||||
|
||||
class NoLoss : public LossModel {
|
||||
public:
|
||||
virtual bool Lost() OVERRIDE;
|
||||
};
|
||||
|
||||
class UniformLoss : public LossModel {
|
||||
public:
|
||||
UniformLoss(int loss_rate);
|
||||
virtual bool Lost() OVERRIDE;
|
||||
void set_loss_rate(double loss_rate) { loss_rate_ = loss_rate; }
|
||||
private:
|
||||
double loss_rate_;
|
||||
};
|
||||
|
||||
class GilbertElliotLoss : public LossModel {
|
||||
public:
|
||||
GilbertElliotLoss(double prob_trans_11, double prob_trans_01);
|
||||
virtual bool Lost() OVERRIDE;
|
||||
private:
|
||||
// Prob. of losing current packet, when previous packet is lost.
|
||||
double prob_trans_11_;
|
||||
// Prob. of losing current packet, when previous packet is not lost.
|
||||
double prob_trans_01_;
|
||||
bool lost_last_;
|
||||
scoped_ptr<UniformLoss> uniform_loss_model_;
|
||||
};
|
||||
|
||||
class NetEqQualityTest : public ::testing::Test {
|
||||
protected:
|
||||
NetEqQualityTest(int block_duration_ms,
|
||||
@ -29,7 +65,6 @@ class NetEqQualityTest : public ::testing::Test {
|
||||
int out_sampling_khz,
|
||||
enum NetEqDecoder decoder_type,
|
||||
int channels,
|
||||
double drift_factor,
|
||||
std::string in_filename,
|
||||
std::string out_filename);
|
||||
virtual void SetUp() OVERRIDE;
|
||||
@ -43,9 +78,9 @@ class NetEqQualityTest : public ::testing::Test {
|
||||
virtual int EncodeBlock(int16_t* in_data, int block_size_samples,
|
||||
uint8_t* payload, int max_bytes) = 0;
|
||||
|
||||
// PacketLoss(...) determines weather a packet sent at an indicated time gets
|
||||
// PacketLost(...) determines weather a packet sent at an indicated time gets
|
||||
// lost or not.
|
||||
virtual bool PacketLost(int packet_input_time_ms) { return false; }
|
||||
bool PacketLost();
|
||||
|
||||
// DecodeBlock() decodes a block of audio using the payload stored in
|
||||
// |payload_| with the length of |payload_size_bytes_| (bytes). The decoded
|
||||
@ -65,6 +100,7 @@ class NetEqQualityTest : public ::testing::Test {
|
||||
int decoded_time_ms_;
|
||||
int decodable_time_ms_;
|
||||
double drift_factor_;
|
||||
int packet_loss_rate_;
|
||||
const int block_duration_ms_;
|
||||
const int in_sampling_khz_;
|
||||
const int out_sampling_khz_;
|
||||
@ -72,6 +108,7 @@ class NetEqQualityTest : public ::testing::Test {
|
||||
const int channels_;
|
||||
const std::string in_filename_;
|
||||
const std::string out_filename_;
|
||||
const std::string log_filename_;
|
||||
|
||||
// Number of samples per channel in a frame.
|
||||
const int in_size_samples_;
|
||||
@ -84,14 +121,18 @@ class NetEqQualityTest : public ::testing::Test {
|
||||
|
||||
scoped_ptr<InputAudioFile> in_file_;
|
||||
FILE* out_file_;
|
||||
FILE* log_file_;
|
||||
|
||||
scoped_ptr<RtpGenerator> rtp_generator_;
|
||||
scoped_ptr<NetEq> neteq_;
|
||||
scoped_ptr<LossModel> loss_model_;
|
||||
|
||||
scoped_ptr<int16_t[]> in_data_;
|
||||
scoped_ptr<uint8_t[]> payload_;
|
||||
scoped_ptr<int16_t[]> out_data_;
|
||||
WebRtcRTPHeader rtp_header_;
|
||||
|
||||
long total_payload_size_bytes_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
|
Loading…
Reference in New Issue
Block a user