From dbba1f969f8f117c31bd3af6144dac9bf9bea24d Mon Sep 17 00:00:00 2001 From: "henrik.lundin@webrtc.org" Date: Tue, 20 Dec 2011 15:45:05 +0000 Subject: [PATCH] Packet waiting-time statistics Adding new statistics API to NetEQ, reporting the waiting time for each frame. The output is raw waiting time for the frames that have been decoded since the last statistics report (or maximum 100 frames). The statistics are reset on each query. Implemented functionality in ACM to query NetEQ for the raw waiting times, and process it to produce max, average and median. Updating common_types.h and VoiceEngine tests to include the new metrics. Unit tests are also added for NetEQ and AcmNetEq. Review URL: http://webrtc-codereview.appspot.com/328011 git-svn-id: http://webrtc.googlecode.com/svn/trunk@1251 4adac7df-926f-26a2-2b94-8c16560cd09d --- src/common_types.h | 6 + .../interface/audio_coding_module_typedefs.h | 6 + .../audio_coding/main/source/acm_neteq.cc | 39 +++++- .../main/source/acm_neteq_unittest.cc | 111 ++++++++++++++++++ .../main/source/audio_coding_module.gypi | 15 +++ .../neteq/interface/webrtc_neteq_internal.h | 11 ++ src/modules/audio_coding/neteq/mcu.h | 31 +++++ src/modules/audio_coding/neteq/mcu_reset.c | 30 +++++ .../audio_coding/neteq/packet_buffer.c | 28 ++++- .../audio_coding/neteq/packet_buffer.h | 17 ++- src/modules/audio_coding/neteq/signal_mcu.c | 11 +- src/modules/audio_coding/neteq/webrtc_neteq.c | 18 +++ .../neteq/webrtc_neteq_unittest.cc | 66 ++++++++++- .../main/test/auto_test/voe_standard_test.cc | 6 + 14 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 src/modules/audio_coding/main/source/acm_neteq_unittest.cc diff --git a/src/common_types.h b/src/common_types.h index 5ce4542033..1a521b1eeb 100644 --- a/src/common_types.h +++ b/src/common_types.h @@ -263,6 +263,12 @@ struct NetworkStatistics // NETEQ statistics WebRtc_UWord16 currentPreemptiveRate; // fraction of data removed through acceleration (in Q14) WebRtc_UWord16 currentAccelerateRate; + // average packet waiting time in the jitter buffer (ms) + int meanWaitingTimeMs; + // median packet waiting time in the jitter buffer (ms) + int medianWaitingTimeMs; + // max packet waiting time in the jitter buffer (ms) + int maxWaitingTimeMs; }; typedef struct diff --git a/src/modules/audio_coding/main/interface/audio_coding_module_typedefs.h b/src/modules/audio_coding/main/interface/audio_coding_module_typedefs.h index a501deaf8d..a6ccdd6432 100644 --- a/src/modules/audio_coding/main/interface/audio_coding_module_typedefs.h +++ b/src/modules/audio_coding/main/interface/audio_coding_module_typedefs.h @@ -153,6 +153,9 @@ enum ACMAMRPackingFormat { // pre-emptive expansion (in Q14) // -currentAccelerateRate : fraction of data removed through acceleration // (in Q14) +// -meanWaitingTimeMs : average packet waiting time in the buffer +// -medianWaitingTimeMs : median packet waiting time in the buffer +// -maxWaitingTimeMs : max packet waiting time in the buffer typedef struct { WebRtc_UWord16 currentBufferSize; WebRtc_UWord16 preferredBufferSize; @@ -161,6 +164,9 @@ typedef struct { WebRtc_UWord16 currentExpandRate; WebRtc_UWord16 currentPreemptiveRate; WebRtc_UWord16 currentAccelerateRate; + int meanWaitingTimeMs; + int medianWaitingTimeMs; + int maxWaitingTimeMs; } ACMNetworkStatistics; /////////////////////////////////////////////////////////////////////////// diff --git a/src/modules/audio_coding/main/source/acm_neteq.cc b/src/modules/audio_coding/main/source/acm_neteq.cc index a46c91941b..7c0cfe21a4 100644 --- a/src/modules/audio_coding/main/source/acm_neteq.cc +++ b/src/modules/audio_coding/main/source/acm_neteq.cc @@ -9,7 +9,9 @@ */ -#include // malloc +#include // sort +#include // malloc +#include #include "acm_neteq.h" #include "common_types.h" @@ -461,13 +463,46 @@ ACMNetEQ::NetworkStatistics( statistics->currentPacketLossRate = stats.currentPacketLossRate; statistics->currentPreemptiveRate = stats.currentPreemptiveRate; statistics->preferredBufferSize = stats.preferredBufferSize; - return 0; } else { LogError("getNetworkStatistics", 0); return -1; } + const int kArrayLen = 100; + int waiting_times[kArrayLen]; + int waiting_times_len = WebRtcNetEQ_GetRawFrameWaitingTimes( + _inst[0], kArrayLen, waiting_times); + if (waiting_times_len >= 0) + { + std::vector waiting_times_vec(waiting_times, + waiting_times + waiting_times_len); + sort(waiting_times_vec.begin(), waiting_times_vec.end()); + size_t size = waiting_times_vec.size(); + assert(size == static_cast(waiting_times_len)); + if (size % 2 == 0) + { + statistics->medianWaitingTimeMs = + (waiting_times_vec[size / 2 - 1] + + waiting_times_vec[size / 2]) / 2; + } + else + { + statistics->medianWaitingTimeMs = waiting_times_vec[size / 2]; + } + statistics->maxWaitingTimeMs = waiting_times_vec.back(); + double sum = 0; + for (size_t i = 0; i < size; ++i) { + sum += waiting_times_vec[i]; + } + statistics->meanWaitingTimeMs = static_cast(sum / size); + } + else + { + LogError("getRawFrameWaitingTimes", 0); + return -1; + } + return 0; } WebRtc_Word32 diff --git a/src/modules/audio_coding/main/source/acm_neteq_unittest.cc b/src/modules/audio_coding/main/source/acm_neteq_unittest.cc new file mode 100644 index 0000000000..72f6f71c21 --- /dev/null +++ b/src/modules/audio_coding/main/source/acm_neteq_unittest.cc @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2011 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. + */ + +// This file contains unit tests for ACM's NetEQ wrapper (class ACMNetEQ). + +#include + +#include "gtest/gtest.h" +#include "modules/audio_coding/codecs/pcm16b/include/pcm16b.h" +#include "modules/audio_coding/main/interface/audio_coding_module_typedefs.h" +#include "modules/audio_coding/main/source/acm_codec_database.h" +#include "modules/audio_coding/main/source/acm_neteq.h" +#include "modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" +#include "modules/interface/module_common_types.h" +#include "typedefs.h" // NOLINT(build/include) + +namespace webrtc { + +class AcmNetEqTest : public ::testing::Test { + protected: + static const size_t kMaxPayloadLen = 5760; // 60 ms, 48 kHz, 16 bit samples. + static const int kPcm16WbPayloadType = 94; + AcmNetEqTest() {} + virtual void SetUp(); + virtual void TearDown() {} + + void InsertZeroPacket(uint16_t sequence_number, + uint32_t timestamp, + uint8_t payload_type, + uint32_t ssrc, + bool marker_bit, + size_t len_payload_bytes); + void PullData(int expected_num_samples); + + ACMNetEQ neteq_; +}; + +void AcmNetEqTest::SetUp() { + ASSERT_EQ(0, neteq_.Init()); + ASSERT_EQ(0, neteq_.AllocatePacketBuffer(ACMCodecDB::NetEQDecoders(), + ACMCodecDB::kNumCodecs)); + WebRtcNetEQ_CodecDef codec_def; + SET_CODEC_PAR(codec_def, kDecoderPCM16Bwb, kPcm16WbPayloadType, NULL, 16000); + SET_PCM16B_WB_FUNCTIONS(codec_def); + ASSERT_EQ(0, neteq_.AddCodec(&codec_def, true)); +} + +void AcmNetEqTest::InsertZeroPacket(uint16_t sequence_number, + uint32_t timestamp, + uint8_t payload_type, + uint32_t ssrc, + bool marker_bit, + size_t len_payload_bytes) { + ASSERT_TRUE(len_payload_bytes <= kMaxPayloadLen); + uint16_t payload[kMaxPayloadLen] = {0}; + WebRtcRTPHeader rtp_header; + rtp_header.header.sequenceNumber = sequence_number; + rtp_header.header.timestamp = timestamp; + rtp_header.header.ssrc = ssrc; + rtp_header.header.payloadType = payload_type; + rtp_header.header.markerBit = marker_bit; + rtp_header.type.Audio.channel = 1; + ASSERT_EQ(0, neteq_.RecIn(reinterpret_cast(payload), + len_payload_bytes, rtp_header)); +} + +void AcmNetEqTest::PullData(int expected_num_samples) { + AudioFrame out_frame; + ASSERT_EQ(0, neteq_.RecOut(out_frame)); + ASSERT_EQ(expected_num_samples, out_frame._payloadDataLengthInSamples); +} + +TEST_F(AcmNetEqTest, NetworkStatistics) { + // Use fax mode to avoid time-scaling. This is to simplify the testing of + // packet waiting times in the packet buffer. + neteq_.SetPlayoutMode(fax); + // Insert 31 dummy packets at once. Each packet contains 10 ms 16 kHz audio. + int num_frames = 30; + const int kSamples = 10 * 16; + const int kPayloadBytes = kSamples * 2; + int i, j; + for (i = 0; i < num_frames; ++i) { + InsertZeroPacket(i, i * kSamples, kPcm16WbPayloadType, 0x1234, false, + kPayloadBytes); + } + // Pull out data once. + PullData(kSamples); + // Insert one more packet (to produce different mean and median). + i = num_frames; + InsertZeroPacket(i, i * kSamples, kPcm16WbPayloadType, 0x1234, false, + kPayloadBytes); + // Pull out all data. + for (j = 1; j < num_frames + 1; ++j) { + PullData(kSamples); + } + + ACMNetworkStatistics stats; + ASSERT_EQ(0, neteq_.NetworkStatistics(&stats)); + EXPECT_EQ(300, stats.maxWaitingTimeMs); + EXPECT_EQ(159, stats.meanWaitingTimeMs); + EXPECT_EQ(160, stats.medianWaitingTimeMs); +} + +} // namespace diff --git a/src/modules/audio_coding/main/source/audio_coding_module.gypi b/src/modules/audio_coding/main/source/audio_coding_module.gypi index bc2176d490..d746f11aec 100644 --- a/src/modules/audio_coding/main/source/audio_coding_module.gypi +++ b/src/modules/audio_coding/main/source/audio_coding_module.gypi @@ -134,6 +134,21 @@ }], ], }, + { + 'target_name': 'audio_coding_unittests', + 'type': 'executable', + 'dependencies': [ + 'audio_coding_module', + 'NetEq', + '<(webrtc_root)/common_audio/common_audio.gyp:vad', + '<(webrtc_root)/../testing/gtest.gyp:gtest', + '<(webrtc_root)/../test/test.gyp:test_support_main', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + ], + 'sources': [ + 'acm_neteq_unittest.cc', + ], + }, # audio_coding_unittests ], }], ], diff --git a/src/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h b/src/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h index 1f87e66284..59134d32b6 100644 --- a/src/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h +++ b/src/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h @@ -110,6 +110,17 @@ typedef struct */ int WebRtcNetEQ_GetNetworkStatistics(void *inst, WebRtcNetEQ_NetworkStatistics *stats); +/* + * Get the raw waiting times for decoded frames. The function writes the last + * recorded waiting times (from frame arrival to frame decoding) to the memory + * pointed to by waitingTimeMs. The number of elements written is in the return + * value. No more than maxLength elements are written. Statistics are reset on + * each query. + */ +int WebRtcNetEQ_GetRawFrameWaitingTimes(void *inst, + int max_length, + int* waiting_times_ms); + /***********************************************/ /* Functions for post-decode VAD functionality */ /***********************************************/ diff --git a/src/modules/audio_coding/neteq/mcu.h b/src/modules/audio_coding/neteq/mcu.h index a94661f06d..499684aa36 100644 --- a/src/modules/audio_coding/neteq/mcu.h +++ b/src/modules/audio_coding/neteq/mcu.h @@ -38,6 +38,8 @@ enum TsScaling kTSscalingFourThirds }; +enum { kLenWaitingTimes = 100 }; + typedef struct { @@ -77,6 +79,10 @@ typedef struct WebRtc_UWord32 lostTS; /* Number of timestamps lost */ WebRtc_UWord32 lastReportTS; /* Timestamp elapsed since last report was given */ + int waiting_times[kLenWaitingTimes]; /* Waiting time statistics storage. */ + int len_waiting_times; + int next_waiting_time_index; + WebRtc_UWord32 externalTS; WebRtc_UWord32 internalTS; WebRtc_Word16 TSscalingInitialized; @@ -114,6 +120,31 @@ int WebRtcNetEQ_McuReset(MCUInst_t *inst); */ int WebRtcNetEQ_ResetMcuInCallStats(MCUInst_t *inst); +/**************************************************************************** + * WebRtcNetEQ_ResetWaitingTimeStats(...) + * + * Reset waiting-time statistics. + * + * Input: + * - inst : MCU instance. + * + * Return value : n/a + */ +void WebRtcNetEQ_ResetWaitingTimeStats(MCUInst_t *inst); + +/**************************************************************************** + * WebRtcNetEQ_LogWaitingTime(...) + * + * Log waiting-time to the statistics. + * + * Input: + * - inst : MCU instance. + * - waiting_time : Waiting time in "RecOut calls" (i.e., 1 call = 10 ms). + * + * Return value : n/a + */ +void WebRtcNetEQ_StoreWaitingTime(MCUInst_t *inst, int waiting_time); + /**************************************************************************** * WebRtcNetEQ_ResetMcuJitterStat(...) * diff --git a/src/modules/audio_coding/neteq/mcu_reset.c b/src/modules/audio_coding/neteq/mcu_reset.c index 5fe23ecffa..3aae4ce614 100644 --- a/src/modules/audio_coding/neteq/mcu_reset.c +++ b/src/modules/audio_coding/neteq/mcu_reset.c @@ -14,6 +14,7 @@ #include "mcu.h" +#include #include #include "automode.h" @@ -61,6 +62,8 @@ int WebRtcNetEQ_McuReset(MCUInst_t *inst) WebRtcNetEQ_ResetMcuInCallStats(inst); + WebRtcNetEQ_ResetWaitingTimeStats(inst); + WebRtcNetEQ_ResetMcuJitterStat(inst); WebRtcNetEQ_ResetAutomode(&(inst->BufferStat_inst.Automode_inst), @@ -82,6 +85,33 @@ int WebRtcNetEQ_ResetMcuInCallStats(MCUInst_t *inst) return 0; } +/* + * Reset waiting-time statistics. + */ + +void WebRtcNetEQ_ResetWaitingTimeStats(MCUInst_t *inst) { + memset(inst->waiting_times, 0, + kLenWaitingTimes * sizeof(inst->waiting_times[0])); + inst->len_waiting_times = 0; + inst->next_waiting_time_index = 0; +} + +/* + * Store waiting-time in the statistics. + */ + +void WebRtcNetEQ_StoreWaitingTime(MCUInst_t *inst, int waiting_time) { + assert(inst->next_waiting_time_index < kLenWaitingTimes); + inst->waiting_times[inst->next_waiting_time_index] = waiting_time; + inst->next_waiting_time_index++; + if (inst->next_waiting_time_index >= kLenWaitingTimes) { + inst->next_waiting_time_index = 0; + } + if (inst->len_waiting_times < kLenWaitingTimes) { + inst->len_waiting_times++; + } +} + /* * Reset all MCU-side statistics variables for the post-call statistics. */ diff --git a/src/modules/audio_coding/neteq/packet_buffer.c b/src/modules/audio_coding/neteq/packet_buffer.c index 1307e73b78..0a3969238f 100644 --- a/src/modules/audio_coding/neteq/packet_buffer.c +++ b/src/modules/audio_coding/neteq/packet_buffer.c @@ -78,6 +78,11 @@ int WebRtcNetEQ_PacketBufferInit(PacketBuf_t *bufferInst, int maxNoOfPackets, bufferInst->rcuPlCntr = &pw16_memory[pos]; pos += maxNoOfPackets; /* advance maxNoOfPackets * WebRtc_Word16 */ + bufferInst->waitingTime = (int*) (&pw16_memory[pos]); + /* Advance maxNoOfPackets * sizeof(waitingTime element). */ + pos += maxNoOfPackets * + sizeof(*bufferInst->waitingTime) / sizeof(*pw16_memory); + /* The payload memory starts after the slot arrays */ bufferInst->startPayloadMemory = &pw16_memory[pos]; bufferInst->currentMemoryPos = bufferInst->startPayloadMemory; @@ -301,6 +306,8 @@ int WebRtcNetEQ_PacketBufferInsert(PacketBuf_t *bufferInst, const RTPPacket_t *R bufferInst->seqNumber[bufferInst->insertPosition] = RTPpacket->seqNumber; bufferInst->timeStamp[bufferInst->insertPosition] = RTPpacket->timeStamp; bufferInst->rcuPlCntr[bufferInst->insertPosition] = RTPpacket->rcuPlCntr; + bufferInst->rcuPlCntr[bufferInst->insertPosition] = 0; + bufferInst->waitingTime[bufferInst->insertPosition] = 0; /* Update buffer parameters */ bufferInst->numPacketsInBuffer++; bufferInst->currentMemoryPos += (RTPpacket->payloadLen + 1) >> 1; @@ -326,7 +333,7 @@ int WebRtcNetEQ_PacketBufferInsert(PacketBuf_t *bufferInst, const RTPPacket_t *R int WebRtcNetEQ_PacketBufferExtract(PacketBuf_t *bufferInst, RTPPacket_t *RTPpacket, - int bufferPosition) + int bufferPosition, int *waitingTime) { /* Sanity check */ @@ -364,6 +371,7 @@ int WebRtcNetEQ_PacketBufferExtract(PacketBuf_t *bufferInst, RTPPacket_t *RTPpac RTPpacket->seqNumber = bufferInst->seqNumber[bufferPosition]; RTPpacket->timeStamp = bufferInst->timeStamp[bufferPosition]; RTPpacket->rcuPlCntr = bufferInst->rcuPlCntr[bufferPosition]; + *waitingTime = bufferInst->waitingTime[bufferPosition]; RTPpacket->starts_byte1 = 0; /* payload is 16-bit aligned */ /* Clear the position in the packet buffer */ @@ -371,6 +379,7 @@ int WebRtcNetEQ_PacketBufferExtract(PacketBuf_t *bufferInst, RTPPacket_t *RTPpac bufferInst->payloadLengthBytes[bufferPosition] = 0; bufferInst->seqNumber[bufferPosition] = 0; bufferInst->timeStamp[bufferPosition] = 0; + bufferInst->waitingTime[bufferPosition] = 0; bufferInst->payloadLocation[bufferPosition] = bufferInst->startPayloadMemory; /* Reduce packet counter with one */ @@ -495,6 +504,16 @@ WebRtc_Word32 WebRtcNetEQ_PacketBufferGetSize(const PacketBuf_t *bufferInst) return sizeSamples; } +void WebRtcNetEQ_IncrementWaitingTimes(PacketBuf_t *buffer_inst) { + int i; + /* Loop through all slots in the buffer. */ + for (i = 0; i < buffer_inst->maxInsertPositions; ++i) { + /* Only increment waiting time for the packets with non-zero size. */ + if (buffer_inst->payloadLengthBytes[i] != 0) { + buffer_inst->waitingTime[i]++; + } + } +} int WebRtcNetEQ_GetDefaultCodecSettings(const enum WebRtcNetEQDecoder *codecID, int noOfCodecs, int *maxBytes, int *maxSlots) @@ -690,9 +709,10 @@ int WebRtcNetEQ_GetDefaultCodecSettings(const enum WebRtcNetEQDecoder *codecID, w16_tmp = (sizeof(WebRtc_UWord32) /* timeStamp */ + sizeof(WebRtc_Word16*) /* payloadLocation */ + sizeof(WebRtc_UWord16) /* seqNumber */ - + sizeof(WebRtc_Word16) /* payloadType */ - + sizeof(WebRtc_Word16) /* payloadLengthBytes */ - + sizeof(WebRtc_Word16)); /* rcuPlCntr */ + + sizeof(WebRtc_Word16) /* payloadType */ + + sizeof(WebRtc_Word16) /* payloadLengthBytes */ + + sizeof(WebRtc_Word16) /* rcuPlCntr */ + + sizeof(int)); /* waitingTime */ /* Add the extra size per slot to the memory count */ *maxBytes += w16_tmp * (*maxSlots); diff --git a/src/modules/audio_coding/neteq/packet_buffer.h b/src/modules/audio_coding/neteq/packet_buffer.h index ad9d7be9f6..d2648585f6 100644 --- a/src/modules/audio_coding/neteq/packet_buffer.h +++ b/src/modules/audio_coding/neteq/packet_buffer.h @@ -49,6 +49,8 @@ typedef struct WebRtc_Word16 *payloadLengthBytes; /* Payload length of packet in slot n */ WebRtc_Word16 *rcuPlCntr; /* zero for non-RCU payload, 1 for main payload 2 for redundant payload */ + int *waitingTime; + /* Statistics counter */ WebRtc_UWord16 discardedPackets; /* Number of discarded packets */ @@ -136,7 +138,7 @@ int WebRtcNetEQ_PacketBufferInsert(PacketBuf_t *bufferInst, const RTPPacket_t *R */ int WebRtcNetEQ_PacketBufferExtract(PacketBuf_t *bufferInst, RTPPacket_t *RTPpacket, - int bufferPosition); + int bufferPosition, int *waitingTime); /**************************************************************************** * WebRtcNetEQ_PacketBufferFindLowestTimestamp(...) @@ -181,6 +183,19 @@ int WebRtcNetEQ_PacketBufferFindLowestTimestamp(PacketBuf_t *bufferInst, WebRtc_Word32 WebRtcNetEQ_PacketBufferGetSize(const PacketBuf_t *bufferInst); +/**************************************************************************** + * WebRtcNetEQ_IncrementWaitingTimes(...) + * + * Increment the waiting time for all packets in the buffer by one. + * + * Input: + * - bufferInst : Buffer instance + * + * Return value : n/a + */ + +void WebRtcNetEQ_IncrementWaitingTimes(PacketBuf_t *buffer_inst); + /**************************************************************************** * WebRtcNetEQ_GetDefaultCodecSettings(...) * diff --git a/src/modules/audio_coding/neteq/signal_mcu.c b/src/modules/audio_coding/neteq/signal_mcu.c index 0406b14165..0180f38240 100644 --- a/src/modules/audio_coding/neteq/signal_mcu.c +++ b/src/modules/audio_coding/neteq/signal_mcu.c @@ -63,6 +63,9 @@ int WebRtcNetEQ_SignalMcu(MCUInst_t *inst) /* Increment counter since last statistics report */ inst->lastReportTS += inst->timestampsPerCall; + /* Increment waiting time for all packets. */ + WebRtcNetEQ_IncrementWaitingTimes(&inst->PacketBuffer_inst); + /* Read info from DSP so we now current status */ WEBRTC_SPL_MEMCPY_W8(&dspInfo,inst->pw16_readAddress,sizeof(DSP2MCU_info_t)); @@ -159,13 +162,15 @@ int WebRtcNetEQ_SignalMcu(MCUInst_t *inst) && (WebRtcNetEQ_DbGetPayload(&(inst->codec_DB_inst), (enum WebRtcNetEQDecoder) inst->current_Codec) == payloadType)) { + int waitingTime; temp_pkt.payload = blockPtr + 1; i_res = WebRtcNetEQ_PacketBufferExtract(&inst->PacketBuffer_inst, &temp_pkt, - i_bufferpos); + i_bufferpos, &waitingTime); if (i_res < 0) { /* error returned */ return i_res; } + WebRtcNetEQ_StoreWaitingTime(inst, waitingTime); *blockPtr = temp_pkt.payloadLen; /* set the flag if this is a redundant payload */ if (temp_pkt.rcuPlCntr > 0) @@ -626,17 +631,19 @@ int WebRtcNetEQ_SignalMcu(MCUInst_t *inst) inst->pw16_writeAddress[0] = inst->pw16_writeAddress[0] & 0xFF3F; do { + int waitingTime; inst->timeStamp = uw32_availableTS; /* Write directly to shared memory */ temp_pkt.payload = blockPtr + 1; i_res = WebRtcNetEQ_PacketBufferExtract(&inst->PacketBuffer_inst, &temp_pkt, - i_bufferpos); + i_bufferpos, &waitingTime); if (i_res < 0) { /* error returned */ return i_res; } + WebRtcNetEQ_StoreWaitingTime(inst, waitingTime); #ifdef NETEQ_DELAY_LOGGING temp_var = NETEQ_DELAY_LOGGING_SIGNAL_DECODE; diff --git a/src/modules/audio_coding/neteq/webrtc_neteq.c b/src/modules/audio_coding/neteq/webrtc_neteq.c index bb10d8688d..4a0f98f4db 100644 --- a/src/modules/audio_coding/neteq/webrtc_neteq.c +++ b/src/modules/audio_coding/neteq/webrtc_neteq.c @@ -15,6 +15,7 @@ #include "webrtc_neteq.h" #include "webrtc_neteq_internal.h" +#include #include #include "typedefs.h" @@ -1531,6 +1532,23 @@ int WebRtcNetEQ_GetNetworkStatistics(void *inst, WebRtcNetEQ_NetworkStatistics * return (0); } +int WebRtcNetEQ_GetRawFrameWaitingTimes(void *inst, + int max_length, + int* waiting_times_ms) { + int i = 0; + MainInst_t *main_inst = (MainInst_t*) inst; + if (main_inst == NULL) return -1; + + while ((i < max_length) && (i < main_inst->MCUinst.len_waiting_times)) { + waiting_times_ms[i] = main_inst->MCUinst.waiting_times[i] * + main_inst->DSPinst.millisecondsPerCall; + ++i; + } + assert(i <= kLenWaitingTimes); + WebRtcNetEQ_ResetWaitingTimeStats(&main_inst->MCUinst); + return i; +} + /**************************************************************************** * WebRtcNetEQ_SetVADInstance(...) * diff --git a/src/modules/audio_coding/neteq/webrtc_neteq_unittest.cc b/src/modules/audio_coding/neteq/webrtc_neteq_unittest.cc index 0d5a159cb9..94aa6b98a2 100644 --- a/src/modules/audio_coding/neteq/webrtc_neteq_unittest.cc +++ b/src/modules/audio_coding/neteq/webrtc_neteq_unittest.cc @@ -22,6 +22,7 @@ #include "modules/audio_coding/neteq/interface/webrtc_neteq.h" #include "modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" +#include "modules/audio_coding/neteq/interface/webrtc_neteq_internal.h" #include "modules/audio_coding/neteq/test/NETEQTEST_CodecClass.h" #include "modules/audio_coding/neteq/test/NETEQTEST_NetEQClass.h" #include "modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h" @@ -353,6 +354,69 @@ TEST_F(NetEqDecodingTest, TestNetworkStatistics) { webrtc::test::ResourcePath("neteq_rtcp_stats", "dat"); DecodeAndCheckStats(kInputRtpFile, kNetworkStatRefFile, kRtcpStatRefFile); } -#endif // _WIN32 +#endif // _WIN32 + +TEST_F(NetEqDecodingTest, TestFrameWaitingTimeStatistics) { + // Use fax mode to avoid time-scaling. This is to simplify the testing of + // packet waiting times in the packet buffer. + ASSERT_EQ(0, + WebRtcNetEQ_SetPlayoutMode(neteq_inst_->instance(), kPlayoutFax)); + // Insert 30 dummy packets at once. Each packet contains 10 ms 16 kHz audio. + int num_frames = 30; + const int kSamples = 10 * 16; + const int kPayloadBytes = kSamples * 2; + for (int i = 0; i < num_frames; ++i) { + uint16_t payload[kSamples] = {0}; + WebRtcNetEQ_RTPInfo rtp_info; + rtp_info.sequenceNumber = i; + rtp_info.timeStamp = i * kSamples; + rtp_info.SSRC = 0x1234; // Just an arbitrary SSRC. + rtp_info.payloadType = 94; // PCM16b WB codec. + rtp_info.markerBit = 0; + ASSERT_EQ(0, WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), &rtp_info, + reinterpret_cast(payload), + kPayloadBytes, 0)); + } + // Pull out all data. + for (int i = 0; i < num_frames; ++i) { + ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); + } + const int kVecLen = 110; // More than kLenWaitingTimes in mcu.h. + int waiting_times[kVecLen]; + int len = WebRtcNetEQ_GetRawFrameWaitingTimes(neteq_inst_->instance(), + kVecLen, waiting_times); + EXPECT_EQ(num_frames, len); + // Since all frames are dumped into NetEQ at once, but pulled out with 10 ms + // spacing (per definition), we expect the delay to increase with 10 ms for + // each packet. + for (int i = 0; i < len; ++i) { + EXPECT_EQ((i + 1) * 10, waiting_times[i]); + } + + // Check statistics again and make sure it's been reset. + EXPECT_EQ(0, WebRtcNetEQ_GetRawFrameWaitingTimes(neteq_inst_->instance(), + kVecLen, waiting_times)); + + // Process > 100 frames, and make sure that that we get statistics + // only for 100 frames. Note the new SSRC, causing NetEQ to reset. + num_frames = 110; + for (int i = 0; i < num_frames; ++i) { + uint16_t payload[kSamples] = {0}; + WebRtcNetEQ_RTPInfo rtp_info; + rtp_info.sequenceNumber = i; + rtp_info.timeStamp = i * kSamples; + rtp_info.SSRC = 0x1235; // Just an arbitrary SSRC. + rtp_info.payloadType = 94; // PCM16b WB codec. + rtp_info.markerBit = 0; + ASSERT_EQ(0, WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), &rtp_info, + reinterpret_cast(payload), + kPayloadBytes, 0)); + ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); + } + + len = WebRtcNetEQ_GetRawFrameWaitingTimes(neteq_inst_->instance(), + kVecLen, waiting_times); + EXPECT_EQ(100, len); +} } // namespace diff --git a/src/voice_engine/main/test/auto_test/voe_standard_test.cc b/src/voice_engine/main/test/auto_test/voe_standard_test.cc index 6379731965..abe0660773 100644 --- a/src/voice_engine/main/test/auto_test/voe_standard_test.cc +++ b/src/voice_engine/main/test/auto_test/voe_standard_test.cc @@ -3081,6 +3081,12 @@ TEST_MUSTPASS(voe_codec_->SetSendCodec(0, ci)); nStats.currentPreemptiveRate); TEST_LOG(" preferredBufferSize = %hu \n", nStats.preferredBufferSize); + TEST_LOG(" meanWaitingTimeMs = %i \n", + nStats.meanWaitingTimeMs); + TEST_LOG(" medianWaitingTimeMs = %i \n", + nStats.medianWaitingTimeMs); + TEST_LOG(" maxWaitingTimeMs = %i \n", + nStats.maxWaitingTimeMs); #else TEST_LOG("Skipping NetEQ statistics tests - " "WEBRTC_VOICE_ENGINE_NETEQ_STATS_API not defined \n");