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:
parent
9b0a820624
commit
4679652d57
2
DEPS
2
DEPS
@ -8,7 +8,7 @@ vars = {
|
|||||||
|
|
||||||
# External resources like video and audio files used for testing purposes.
|
# External resources like video and audio files used for testing purposes.
|
||||||
# Downloaded on demand when needed.
|
# Downloaded on demand when needed.
|
||||||
"webrtc_resources_revision": "6",
|
"webrtc_resources_revision": "7",
|
||||||
}
|
}
|
||||||
|
|
||||||
# NOTE: Prefer revision numbers to tags for svn deps.
|
# NOTE: Prefer revision numbers to tags for svn deps.
|
||||||
|
@ -107,6 +107,19 @@ WebRtc_UWord16 WebRtcNetEQ_BufstatsDecision(BufstatsInst_t *inst, WebRtc_Word16
|
|||||||
{
|
{
|
||||||
/* signed difference between wanted and available TS */
|
/* signed difference between wanted and available TS */
|
||||||
WebRtc_Word32 diffTS = (inst->uw32_CNGplayedTS + targetTS) - availableTS;
|
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))
|
if ((diffTS) < 0 && (prevPlayMode == MODE_RFC3389CNG))
|
||||||
{
|
{
|
||||||
|
@ -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)
|
//NETEQTEST_NetEQVector::NETEQTEST_NetEQVector(int numChannels)
|
||||||
//:
|
//:
|
||||||
//channels(numChannels, new NETEQTEST_NetEQClass())
|
//channels(numChannels, new NETEQTEST_NetEQClass())
|
||||||
|
@ -40,6 +40,7 @@ public:
|
|||||||
int recIn(NETEQTEST_RTPpacket & rtp);
|
int recIn(NETEQTEST_RTPpacket & rtp);
|
||||||
WebRtc_Word16 recOut(WebRtc_Word16 *outData, void *msInfo = NULL, enum WebRtcNetEQOutputType *outputType = NULL);
|
WebRtc_Word16 recOut(WebRtc_Word16 *outData, void *msInfo = NULL, enum WebRtcNetEQOutputType *outputType = NULL);
|
||||||
WebRtc_UWord32 getSpeechTimeStamp();
|
WebRtc_UWord32 getSpeechTimeStamp();
|
||||||
|
WebRtcNetEQOutputType getOutputType();
|
||||||
|
|
||||||
void * instance() { return (_inst); };
|
void * instance() { return (_inst); };
|
||||||
void usePreparseRTP( bool useIt = true ) { _preparseRTP = useIt; };
|
void usePreparseRTP( bool useIt = true ) { _preparseRTP = useIt; };
|
||||||
|
@ -184,8 +184,13 @@ class NetEqDecodingTest : public ::testing::Test {
|
|||||||
const std::string &stat_ref_file,
|
const std::string &stat_ref_file,
|
||||||
const std::string &rtcp_ref_file);
|
const std::string &rtcp_ref_file);
|
||||||
static void PopulateRtpInfo(int frame_index,
|
static void PopulateRtpInfo(int frame_index,
|
||||||
int samples_per_frame,
|
int timestamp,
|
||||||
WebRtcNetEQ_RTPInfo* rtp_info);
|
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_;
|
NETEQTEST_NetEQClass* neteq_inst_;
|
||||||
std::vector<NETEQTEST_Decoder*> dec_;
|
std::vector<NETEQTEST_Decoder*> dec_;
|
||||||
@ -240,7 +245,9 @@ void NetEqDecodingTest::SelectDecoders(WebRtcNetEQDecoder* used_codec) {
|
|||||||
*used_codec++ = kDecoderPCM16Bswb32kHz;
|
*used_codec++ = kDecoderPCM16Bswb32kHz;
|
||||||
dec_.push_back(new decoder_PCM16B_SWB32(95));
|
dec_.push_back(new decoder_PCM16B_SWB32(95));
|
||||||
*used_codec++ = kDecoderCNG;
|
*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() {
|
void NetEqDecodingTest::LoadDecoders() {
|
||||||
@ -337,15 +344,29 @@ void NetEqDecodingTest::DecodeAndCheckStats(const std::string &rtp_file,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NetEqDecodingTest::PopulateRtpInfo(int frame_index,
|
void NetEqDecodingTest::PopulateRtpInfo(int frame_index,
|
||||||
int samples_per_frame,
|
int timestamp,
|
||||||
WebRtcNetEQ_RTPInfo* rtp_info) {
|
WebRtcNetEQ_RTPInfo* rtp_info) {
|
||||||
rtp_info->sequenceNumber = frame_index;
|
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->SSRC = 0x1234; // Just an arbitrary SSRC.
|
||||||
rtp_info->payloadType = 94; // PCM16b WB codec.
|
rtp_info->payloadType = 94; // PCM16b WB codec.
|
||||||
rtp_info->markerBit = 0;
|
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) {
|
TEST_F(NetEqDecodingTest, TestBitExactness) {
|
||||||
const std::string kInputRtpFile = webrtc::test::ProjectRootPath() +
|
const std::string kInputRtpFile = webrtc::test::ProjectRootPath() +
|
||||||
"resources/neteq_universal.rtp";
|
"resources/neteq_universal.rtp";
|
||||||
@ -439,7 +460,7 @@ TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimeNegative) {
|
|||||||
for (int n = 0; n < num_packets; ++n) {
|
for (int n = 0; n < num_packets; ++n) {
|
||||||
uint8_t payload[kPayloadBytes] = {0};
|
uint8_t payload[kPayloadBytes] = {0};
|
||||||
WebRtcNetEQ_RTPInfo rtp_info;
|
WebRtcNetEQ_RTPInfo rtp_info;
|
||||||
PopulateRtpInfo(frame_index, kSamples, &rtp_info);
|
PopulateRtpInfo(frame_index, frame_index * kSamples, &rtp_info);
|
||||||
ASSERT_EQ(0,
|
ASSERT_EQ(0,
|
||||||
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
|
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
|
||||||
&rtp_info,
|
&rtp_info,
|
||||||
@ -470,7 +491,7 @@ TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimePositive) {
|
|||||||
for (int n = 0; n < num_packets; ++n) {
|
for (int n = 0; n < num_packets; ++n) {
|
||||||
uint8_t payload[kPayloadBytes] = {0};
|
uint8_t payload[kPayloadBytes] = {0};
|
||||||
WebRtcNetEQ_RTPInfo rtp_info;
|
WebRtcNetEQ_RTPInfo rtp_info;
|
||||||
PopulateRtpInfo(frame_index, kSamples, &rtp_info);
|
PopulateRtpInfo(frame_index, frame_index * kSamples, &rtp_info);
|
||||||
ASSERT_EQ(0,
|
ASSERT_EQ(0,
|
||||||
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
|
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
|
||||||
&rtp_info,
|
&rtp_info,
|
||||||
@ -489,4 +510,96 @@ TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimePositive) {
|
|||||||
EXPECT_EQ(108352, network_stats.clockDriftPPM);
|
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
|
} // namespace
|
||||||
|
Loading…
x
Reference in New Issue
Block a user