Implemented a fix for Issue 88.

NetEQ now checks for too early CNG packets, and modifies the CNG
sample counter to jump forward in time if needed to combat clock
drift.

Adding a new unittest to reproduce and solve the issue. The
unittest LongCngWithClockDrift verifies that the buffer delay
before and after a long CNG period is almost constant. The test
introduces a clock drift of 25 ms/s.

BUG=http://code.google.com/p/webrtc/issues/detail?id=88
TEST=neteq_unittests NetEqDecodingTest.LongCngWithClockDrift

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@1547 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
henrik.lundin@webrtc.org 2012-01-25 16:37:41 +00:00
parent 9b0a820624
commit 4679652d57
5 changed files with 146 additions and 7 deletions

2
DEPS
View File

@ -8,7 +8,7 @@ vars = {
# External resources like video and audio files used for testing purposes.
# Downloaded on demand when needed.
"webrtc_resources_revision": "6",
"webrtc_resources_revision": "7",
}
# NOTE: Prefer revision numbers to tags for svn deps.

View File

@ -107,6 +107,19 @@ WebRtc_UWord16 WebRtcNetEQ_BufstatsDecision(BufstatsInst_t *inst, WebRtc_Word16
{
/* signed difference between wanted and available TS */
WebRtc_Word32 diffTS = (inst->uw32_CNGplayedTS + targetTS) - availableTS;
int32_t optimal_level_samp = (inst->Automode_inst.optBufLevel *
inst->Automode_inst.packetSpeechLenSamp) >> 8;
int32_t excess_waiting_time_samp = -diffTS - optimal_level_samp;
if (excess_waiting_time_samp > optimal_level_samp / 2)
{
/* The waiting time for this packet will be longer than 1.5
* times the wanted buffer delay. Advance the clock by to cut
* waiting time down to the optimal.
*/
inst->uw32_CNGplayedTS += excess_waiting_time_samp;
diffTS += excess_waiting_time_samp;
}
if ((diffTS) < 0 && (prevPlayMode == MODE_RFC3389CNG))
{

View File

@ -345,6 +345,18 @@ WebRtc_UWord32 NETEQTEST_NetEQClass::getSpeechTimeStamp()
}
WebRtcNetEQOutputType NETEQTEST_NetEQClass::getOutputType() {
WebRtcNetEQOutputType type;
int err = WebRtcNetEQ_GetSpeechOutputType(_inst, &type);
if (err)
{
printError();
type = kOutputNormal;
}
return (type);
}
//NETEQTEST_NetEQVector::NETEQTEST_NetEQVector(int numChannels)
//:
//channels(numChannels, new NETEQTEST_NetEQClass())

View File

@ -40,6 +40,7 @@ public:
int recIn(NETEQTEST_RTPpacket & rtp);
WebRtc_Word16 recOut(WebRtc_Word16 *outData, void *msInfo = NULL, enum WebRtcNetEQOutputType *outputType = NULL);
WebRtc_UWord32 getSpeechTimeStamp();
WebRtcNetEQOutputType getOutputType();
void * instance() { return (_inst); };
void usePreparseRTP( bool useIt = true ) { _preparseRTP = useIt; };

View File

@ -184,8 +184,13 @@ class NetEqDecodingTest : public ::testing::Test {
const std::string &stat_ref_file,
const std::string &rtcp_ref_file);
static void PopulateRtpInfo(int frame_index,
int samples_per_frame,
int timestamp,
WebRtcNetEQ_RTPInfo* rtp_info);
static void PopulateCng(int frame_index,
int timestamp,
WebRtcNetEQ_RTPInfo* rtp_info,
uint8_t* payload,
int* payload_len);
NETEQTEST_NetEQClass* neteq_inst_;
std::vector<NETEQTEST_Decoder*> dec_;
@ -240,7 +245,9 @@ void NetEqDecodingTest::SelectDecoders(WebRtcNetEQDecoder* used_codec) {
*used_codec++ = kDecoderPCM16Bswb32kHz;
dec_.push_back(new decoder_PCM16B_SWB32(95));
*used_codec++ = kDecoderCNG;
dec_.push_back(new decoder_CNG(13));
dec_.push_back(new decoder_CNG(13, 8000));
*used_codec++ = kDecoderCNG;
dec_.push_back(new decoder_CNG(98, 16000));
}
void NetEqDecodingTest::LoadDecoders() {
@ -337,15 +344,29 @@ void NetEqDecodingTest::DecodeAndCheckStats(const std::string &rtp_file,
}
void NetEqDecodingTest::PopulateRtpInfo(int frame_index,
int samples_per_frame,
int timestamp,
WebRtcNetEQ_RTPInfo* rtp_info) {
rtp_info->sequenceNumber = frame_index;
rtp_info->timeStamp = frame_index * samples_per_frame;
rtp_info->timeStamp = timestamp;
rtp_info->SSRC = 0x1234; // Just an arbitrary SSRC.
rtp_info->payloadType = 94; // PCM16b WB codec.
rtp_info->markerBit = 0;
}
void NetEqDecodingTest::PopulateCng(int frame_index,
int timestamp,
WebRtcNetEQ_RTPInfo* rtp_info,
uint8_t* payload,
int* payload_len) {
rtp_info->sequenceNumber = frame_index;
rtp_info->timeStamp = timestamp;
rtp_info->SSRC = 0x1234; // Just an arbitrary SSRC.
rtp_info->payloadType = 98; // WB CNG.
rtp_info->markerBit = 0;
payload[0] = 64; // Noise level -64 dBov, quite arbitrarily chosen.
*payload_len = 1; // Only noise level, no spectral parameters.
}
TEST_F(NetEqDecodingTest, TestBitExactness) {
const std::string kInputRtpFile = webrtc::test::ProjectRootPath() +
"resources/neteq_universal.rtp";
@ -439,7 +460,7 @@ TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimeNegative) {
for (int n = 0; n < num_packets; ++n) {
uint8_t payload[kPayloadBytes] = {0};
WebRtcNetEQ_RTPInfo rtp_info;
PopulateRtpInfo(frame_index, kSamples, &rtp_info);
PopulateRtpInfo(frame_index, frame_index * kSamples, &rtp_info);
ASSERT_EQ(0,
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
&rtp_info,
@ -470,7 +491,7 @@ TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimePositive) {
for (int n = 0; n < num_packets; ++n) {
uint8_t payload[kPayloadBytes] = {0};
WebRtcNetEQ_RTPInfo rtp_info;
PopulateRtpInfo(frame_index, kSamples, &rtp_info);
PopulateRtpInfo(frame_index, frame_index * kSamples, &rtp_info);
ASSERT_EQ(0,
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
&rtp_info,
@ -489,4 +510,96 @@ TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimePositive) {
EXPECT_EQ(108352, network_stats.clockDriftPPM);
}
TEST_F(NetEqDecodingTest, LongCngWithClockDrift) {
uint16_t seq_no = 0;
uint32_t timestamp = 0;
const int kFrameSizeMs = 30;
const int kSamples = kFrameSizeMs * 16;
const int kPayloadBytes = kSamples * 2;
// Apply a clock drift of -25 ms / s (sender faster than receiver).
const double kDriftFactor = 1000.0 / (1000.0 + 25.0);
double next_input_time_ms = 0.0;
double t_ms;
// Insert speech for 5 seconds.
const int kSpeechDurationMs = 5000;
for (t_ms = 0; t_ms < kSpeechDurationMs; t_ms += 10) {
// Each turn in this for loop is 10 ms.
while (next_input_time_ms <= t_ms) {
// Insert one 30 ms speech frame.
uint8_t payload[kPayloadBytes] = {0};
WebRtcNetEQ_RTPInfo rtp_info;
PopulateRtpInfo(seq_no, timestamp, &rtp_info);
ASSERT_EQ(0,
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
&rtp_info,
payload,
kPayloadBytes, 0));
++seq_no;
timestamp += kSamples;
next_input_time_ms += static_cast<double>(kFrameSizeMs) * kDriftFactor;
}
// Pull out data once.
ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_));
}
EXPECT_EQ(kOutputNormal, neteq_inst_->getOutputType());
int32_t delay_before = timestamp - neteq_inst_->getSpeechTimeStamp();
// Insert CNG for 1 minute (= 60000 ms).
const int kCngPeriodMs = 100;
const int kCngPeriodSamples = kCngPeriodMs * 16; // Period in 16 kHz samples.
const int kCngDurationMs = 60000;
for (; t_ms < kSpeechDurationMs + kCngDurationMs; t_ms += 10) {
// Each turn in this for loop is 10 ms.
while (next_input_time_ms <= t_ms) {
// Insert one CNG frame each 100 ms.
uint8_t payload[kPayloadBytes];
int payload_len;
WebRtcNetEQ_RTPInfo rtp_info;
PopulateCng(seq_no, timestamp, &rtp_info, payload, &payload_len);
ASSERT_EQ(0,
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
&rtp_info,
payload,
payload_len, 0));
++seq_no;
timestamp += kCngPeriodSamples;
next_input_time_ms += static_cast<double>(kCngPeriodMs) * kDriftFactor;
}
// Pull out data once.
ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_));
}
EXPECT_EQ(kOutputCNG, neteq_inst_->getOutputType());
// Insert speech again until output type is speech.
while (neteq_inst_->getOutputType() != kOutputNormal) {
// Each turn in this for loop is 10 ms.
while (next_input_time_ms <= t_ms) {
// Insert one 30 ms speech frame.
uint8_t payload[kPayloadBytes] = {0};
WebRtcNetEQ_RTPInfo rtp_info;
PopulateRtpInfo(seq_no, timestamp, &rtp_info);
ASSERT_EQ(0,
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
&rtp_info,
payload,
kPayloadBytes, 0));
++seq_no;
timestamp += kSamples;
next_input_time_ms += static_cast<double>(kFrameSizeMs) * kDriftFactor;
}
// Pull out data once.
ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_));
// Increase clock.
t_ms += 10;
}
int32_t delay_after = timestamp - neteq_inst_->getSpeechTimeStamp();
// Compare delay before and after, and make sure it differs less than 20 ms.
EXPECT_LE(delay_after, delay_before + 20 * 16);
EXPECT_GE(delay_after, delay_before - 20 * 16);
}
} // namespace