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");