summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Chromium Automerger <chromium-automerger@android>2014-11-04 23:45:06 +0000
committerAndroid Chromium Automerger <chromium-automerger@android>2014-11-04 23:45:06 +0000
commita91b08bea4306fbc634924eab6845c96d18eae62 (patch)
tree1b6082fc976cb3b1187d72bf61897191657df8e4
parent8f2a7fafe87564dceb5db0a03326036e5022ee13 (diff)
parent9a8c28f10f329c5ce91e77057933e60224000627 (diff)
downloadwebrtc-a91b08bea4306fbc634924eab6845c96d18eae62.tar.gz
Merge third_party/webrtc from https://chromium.googlesource.com/external/webrtc/trunk/webrtc.git at 9a8c28f10f329c5ce91e77057933e60224000627
This commit was generated by merge_from_chromium.py. Change-Id: If9e805f5024e1fcdd99127626811f9650e109b1d
-rw-r--r--modules/audio_coding/codecs/g711/audio_encoder_pcm.cc6
-rw-r--r--modules/audio_coding/codecs/g711/g711_interface.c28
-rw-r--r--modules/audio_coding/codecs/g711/include/g711_interface.h25
-rw-r--r--modules/audio_coding/codecs/g711/test/testG711.cc9
-rw-r--r--modules/audio_coding/codecs/pcm16b/include/pcm16b.h3
-rw-r--r--modules/audio_coding/codecs/pcm16b/pcm16b.c6
-rw-r--r--modules/audio_coding/main/acm2/acm_pcma.cc2
-rw-r--r--modules/audio_coding/main/acm2/acm_pcmu.cc2
-rw-r--r--modules/audio_coding/neteq/audio_decoder_impl.cc6
-rw-r--r--modules/audio_coding/neteq/decision_logic_normal.cc10
-rw-r--r--modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h2
-rw-r--r--modules/audio_coding/neteq/mock/mock_packet_buffer.h4
-rw-r--r--modules/audio_coding/neteq/neteq_external_decoder_unittest.cc13
-rw-r--r--modules/audio_coding/neteq/neteq_impl.cc8
-rw-r--r--modules/audio_coding/neteq/neteq_impl_unittest.cc93
-rw-r--r--modules/audio_coding/neteq/packet_buffer.cc12
-rw-r--r--modules/audio_coding/neteq/packet_buffer.h28
-rw-r--r--modules/audio_coding/neteq/packet_buffer_unittest.cc59
-rw-r--r--modules/audio_coding/neteq/test/RTPencode.cc7
-rw-r--r--modules/pacing/include/paced_sender.h40
-rw-r--r--modules/pacing/paced_sender.cc354
-rw-r--r--modules/pacing/paced_sender_unittest.cc124
-rw-r--r--modules/rtp_rtcp/source/rtp_sender.cc36
-rw-r--r--modules/rtp_rtcp/source/rtp_sender.h2
-rw-r--r--video/end_to_end_tests.cc35
-rw-r--r--video/loopback.cc26
-rw-r--r--video/video_receive_stream.cc3
-rw-r--r--video_engine/vie_encoder.cc37
-rw-r--r--video_engine/vie_encoder.h2
-rw-r--r--webrtc_tests.gypi1
30 files changed, 619 insertions, 364 deletions
diff --git a/modules/audio_coding/codecs/g711/audio_encoder_pcm.cc b/modules/audio_coding/codecs/g711/audio_encoder_pcm.cc
index 097e11f1..6454e93c 100644
--- a/modules/audio_coding/codecs/g711/audio_encoder_pcm.cc
+++ b/modules/audio_coding/codecs/g711/audio_encoder_pcm.cc
@@ -82,8 +82,7 @@ bool AudioEncoderPcm::Encode(uint32_t timestamp,
int16_t AudioEncoderPcmA::EncodeCall(const int16_t* audio,
size_t input_len,
uint8_t* encoded) {
- return WebRtcG711_EncodeA(NULL,
- const_cast<int16_t*>(audio),
+ return WebRtcG711_EncodeA(const_cast<int16_t*>(audio),
static_cast<int16_t>(input_len),
reinterpret_cast<int16_t*>(encoded));
}
@@ -91,8 +90,7 @@ int16_t AudioEncoderPcmA::EncodeCall(const int16_t* audio,
int16_t AudioEncoderPcmU::EncodeCall(const int16_t* audio,
size_t input_len,
uint8_t* encoded) {
- return WebRtcG711_EncodeU(NULL,
- const_cast<int16_t*>(audio),
+ return WebRtcG711_EncodeU(const_cast<int16_t*>(audio),
static_cast<int16_t>(input_len),
reinterpret_cast<int16_t*>(encoded));
}
diff --git a/modules/audio_coding/codecs/g711/g711_interface.c b/modules/audio_coding/codecs/g711/g711_interface.c
index 134c1e4e..ec726c51 100644
--- a/modules/audio_coding/codecs/g711/g711_interface.c
+++ b/modules/audio_coding/codecs/g711/g711_interface.c
@@ -12,16 +12,12 @@
#include "g711_interface.h"
#include "webrtc/typedefs.h"
-int16_t WebRtcG711_EncodeA(void* state,
- int16_t* speechIn,
+int16_t WebRtcG711_EncodeA(int16_t* speechIn,
int16_t len,
int16_t* encoded) {
int n;
uint16_t tempVal, tempVal2;
- // Set and discard to avoid getting warnings
- (void)(state = NULL);
-
// Sanity check of input length
if (len < 0) {
return (-1);
@@ -50,16 +46,12 @@ int16_t WebRtcG711_EncodeA(void* state,
return (len);
}
-int16_t WebRtcG711_EncodeU(void* state,
- int16_t* speechIn,
+int16_t WebRtcG711_EncodeU(int16_t* speechIn,
int16_t len,
int16_t* encoded) {
int n;
uint16_t tempVal;
- // Set and discard to avoid getting warnings
- (void)(state = NULL);
-
// Sanity check of input length
if (len < 0) {
return (-1);
@@ -86,17 +78,13 @@ int16_t WebRtcG711_EncodeU(void* state,
return (len);
}
-int16_t WebRtcG711_DecodeA(void* state,
- int16_t* encoded,
+int16_t WebRtcG711_DecodeA(int16_t* encoded,
int16_t len,
int16_t* decoded,
int16_t* speechType) {
int n;
uint16_t tempVal;
- // Set and discard to avoid getting warnings
- (void)(state = NULL);
-
// Sanity check of input length
if (len < 0) {
return (-1);
@@ -123,17 +111,13 @@ int16_t WebRtcG711_DecodeA(void* state,
return (len);
}
-int16_t WebRtcG711_DecodeU(void* state,
- int16_t* encoded,
+int16_t WebRtcG711_DecodeU(int16_t* encoded,
int16_t len,
int16_t* decoded,
int16_t* speechType) {
int n;
uint16_t tempVal;
- // Set and discard to avoid getting warnings
- (void)(state = NULL);
-
// Sanity check of input length
if (len < 0) {
return (-1);
@@ -160,10 +144,8 @@ int16_t WebRtcG711_DecodeU(void* state,
return (len);
}
-int WebRtcG711_DurationEst(void* state,
- const uint8_t* payload,
+int WebRtcG711_DurationEst(const uint8_t* payload,
int payload_length_bytes) {
- (void) state;
(void) payload;
/* G.711 is one byte per sample, so we can just return the number of bytes. */
return payload_length_bytes;
diff --git a/modules/audio_coding/codecs/g711/include/g711_interface.h b/modules/audio_coding/codecs/g711/include/g711_interface.h
index 83357e47..545ca3e8 100644
--- a/modules/audio_coding/codecs/g711/include/g711_interface.h
+++ b/modules/audio_coding/codecs/g711/include/g711_interface.h
@@ -28,8 +28,6 @@ extern "C" {
* Input speech length has be of any length.
*
* Input:
- * - state : Dummy state to make this codec look more like
- * other codecs
* - speechIn : Input speech vector
* - len : Samples in speechIn
*
@@ -40,8 +38,7 @@ extern "C" {
* -1 - Error
*/
-int16_t WebRtcG711_EncodeA(void* state,
- int16_t* speechIn,
+int16_t WebRtcG711_EncodeA(int16_t* speechIn,
int16_t len,
int16_t* encoded);
@@ -52,8 +49,6 @@ int16_t WebRtcG711_EncodeA(void* state,
* Input speech length has be of any length.
*
* Input:
- * - state : Dummy state to make this codec look more like
- * other codecs
* - speechIn : Input speech vector
* - len : Samples in speechIn
*
@@ -64,8 +59,7 @@ int16_t WebRtcG711_EncodeA(void* state,
* -1 - Error
*/
-int16_t WebRtcG711_EncodeU(void* state,
- int16_t* speechIn,
+int16_t WebRtcG711_EncodeU(int16_t* speechIn,
int16_t len,
int16_t* encoded);
@@ -75,8 +69,6 @@ int16_t WebRtcG711_EncodeU(void* state,
* This function decodes a packet G711 A-law frame.
*
* Input:
- * - state : Dummy state to make this codec look more like
- * other codecs
* - encoded : Encoded data
* - len : Bytes in encoded vector
*
@@ -90,8 +82,7 @@ int16_t WebRtcG711_EncodeU(void* state,
* -1 - Error
*/
-int16_t WebRtcG711_DecodeA(void* state,
- int16_t* encoded,
+int16_t WebRtcG711_DecodeA(int16_t* encoded,
int16_t len,
int16_t* decoded,
int16_t* speechType);
@@ -102,8 +93,6 @@ int16_t WebRtcG711_DecodeA(void* state,
* This function decodes a packet G711 U-law frame.
*
* Input:
- * - state : Dummy state to make this codec look more like
- * other codecs
* - encoded : Encoded data
* - len : Bytes in encoded vector
*
@@ -117,8 +106,7 @@ int16_t WebRtcG711_DecodeA(void* state,
* -1 - Error
*/
-int16_t WebRtcG711_DecodeU(void* state,
- int16_t* encoded,
+int16_t WebRtcG711_DecodeU(int16_t* encoded,
int16_t len,
int16_t* decoded,
int16_t* speechType);
@@ -129,8 +117,6 @@ int16_t WebRtcG711_DecodeU(void* state,
* This function estimates the duration of a G711 packet in samples.
*
* Input:
- * - state : Dummy state to make this codec look more like
- * other codecs
* - payload : Encoded data
* - payloadLengthBytes : Bytes in encoded vector
*
@@ -139,8 +125,7 @@ int16_t WebRtcG711_DecodeU(void* state,
* byte per sample.
*/
-int WebRtcG711_DurationEst(void* state,
- const uint8_t* payload,
+int WebRtcG711_DurationEst(const uint8_t* payload,
int payload_length_bytes);
/**********************************************************************
diff --git a/modules/audio_coding/codecs/g711/test/testG711.cc b/modules/audio_coding/codecs/g711/test/testG711.cc
index 95a02469..76950fa3 100644
--- a/modules/audio_coding/codecs/g711/test/testG711.cc
+++ b/modules/audio_coding/codecs/g711/test/testG711.cc
@@ -127,7 +127,7 @@ int main(int argc, char* argv[]) {
/* G.711 encoding */
if (!strcmp(law, "A")) {
/* A-law encoding */
- stream_len = WebRtcG711_EncodeA(NULL, shortdata, framelength, streamdata);
+ stream_len = WebRtcG711_EncodeA(shortdata, framelength, streamdata);
if (argc == 6) {
/* Write bits to file */
if (fwrite(streamdata, sizeof(unsigned char), stream_len, bitp) !=
@@ -135,11 +135,11 @@ int main(int argc, char* argv[]) {
return -1;
}
}
- err = WebRtcG711_DecodeA(NULL, streamdata, stream_len, decoded,
+ err = WebRtcG711_DecodeA(streamdata, stream_len, decoded,
speechType);
} else if (!strcmp(law, "u")) {
/* u-law encoding */
- stream_len = WebRtcG711_EncodeU(NULL, shortdata, framelength, streamdata);
+ stream_len = WebRtcG711_EncodeU(shortdata, framelength, streamdata);
if (argc == 6) {
/* Write bits to file */
if (fwrite(streamdata, sizeof(unsigned char), stream_len, bitp) !=
@@ -147,8 +147,7 @@ int main(int argc, char* argv[]) {
return -1;
}
}
- err = WebRtcG711_DecodeU(NULL, streamdata, stream_len, decoded,
- speechType);
+ err = WebRtcG711_DecodeU(streamdata, stream_len, decoded, speechType);
} else {
printf("Wrong law mode\n");
exit(1);
diff --git a/modules/audio_coding/codecs/pcm16b/include/pcm16b.h b/modules/audio_coding/codecs/pcm16b/include/pcm16b.h
index 9c96b830..86b32fe2 100644
--- a/modules/audio_coding/codecs/pcm16b/include/pcm16b.h
+++ b/modules/audio_coding/codecs/pcm16b/include/pcm16b.h
@@ -73,8 +73,7 @@ int16_t WebRtcPcm16b_Encode(int16_t *speech16b,
* Returned value : Samples in speechOut16b
*/
-int16_t WebRtcPcm16b_DecodeW16(void *inst,
- int16_t *speechIn16b,
+int16_t WebRtcPcm16b_DecodeW16(int16_t *speechIn16b,
int16_t length_bytes,
int16_t *speechOut16b,
int16_t* speechType);
diff --git a/modules/audio_coding/codecs/pcm16b/pcm16b.c b/modules/audio_coding/codecs/pcm16b/pcm16b.c
index af6720f6..2c6bea6a 100644
--- a/modules/audio_coding/codecs/pcm16b/pcm16b.c
+++ b/modules/audio_coding/codecs/pcm16b/pcm16b.c
@@ -61,8 +61,7 @@ int16_t WebRtcPcm16b_Encode(int16_t *speech16b,
/* Decoder with int16_t Input instead of char when the int16_t Encoder is used */
-int16_t WebRtcPcm16b_DecodeW16(void *inst,
- int16_t *speechIn16b,
+int16_t WebRtcPcm16b_DecodeW16(int16_t *speechIn16b,
int16_t length_bytes,
int16_t *speechOut16b,
int16_t* speechType)
@@ -80,9 +79,6 @@ int16_t WebRtcPcm16b_DecodeW16(void *inst,
*speechType=1;
- // Avoid warning.
- (void)(inst = NULL);
-
return length_bytes >> 1;
}
diff --git a/modules/audio_coding/main/acm2/acm_pcma.cc b/modules/audio_coding/main/acm2/acm_pcma.cc
index 548e8fda..41d4d085 100644
--- a/modules/audio_coding/main/acm2/acm_pcma.cc
+++ b/modules/audio_coding/main/acm2/acm_pcma.cc
@@ -27,7 +27,7 @@ ACMPCMA::~ACMPCMA() { return; }
int16_t ACMPCMA::InternalEncode(uint8_t* bitstream,
int16_t* bitstream_len_byte) {
*bitstream_len_byte = WebRtcG711_EncodeA(
- NULL, &in_audio_[in_audio_ix_read_], frame_len_smpl_ * num_channels_,
+ &in_audio_[in_audio_ix_read_], frame_len_smpl_ * num_channels_,
reinterpret_cast<int16_t*>(bitstream));
// Increment the read index this tell the caller that how far
// we have gone forward in reading the audio buffer.
diff --git a/modules/audio_coding/main/acm2/acm_pcmu.cc b/modules/audio_coding/main/acm2/acm_pcmu.cc
index 5c032363..4f16062f 100644
--- a/modules/audio_coding/main/acm2/acm_pcmu.cc
+++ b/modules/audio_coding/main/acm2/acm_pcmu.cc
@@ -27,7 +27,7 @@ ACMPCMU::~ACMPCMU() {}
int16_t ACMPCMU::InternalEncode(uint8_t* bitstream,
int16_t* bitstream_len_byte) {
*bitstream_len_byte = WebRtcG711_EncodeU(
- NULL, &in_audio_[in_audio_ix_read_], frame_len_smpl_ * num_channels_,
+ &in_audio_[in_audio_ix_read_], frame_len_smpl_ * num_channels_,
reinterpret_cast<int16_t*>(bitstream));
// Increment the read index this tell the caller that how far
diff --git a/modules/audio_coding/neteq/audio_decoder_impl.cc b/modules/audio_coding/neteq/audio_decoder_impl.cc
index 0215f36d..07b1b4be 100644
--- a/modules/audio_coding/neteq/audio_decoder_impl.cc
+++ b/modules/audio_coding/neteq/audio_decoder_impl.cc
@@ -45,7 +45,7 @@ int AudioDecoderPcmU::Decode(const uint8_t* encoded, size_t encoded_len,
int16_t* decoded, SpeechType* speech_type) {
int16_t temp_type = 1; // Default is speech.
int16_t ret = WebRtcG711_DecodeU(
- state_, reinterpret_cast<int16_t*>(const_cast<uint8_t*>(encoded)),
+ reinterpret_cast<int16_t*>(const_cast<uint8_t*>(encoded)),
static_cast<int16_t>(encoded_len), decoded, &temp_type);
*speech_type = ConvertSpeechType(temp_type);
return ret;
@@ -62,7 +62,7 @@ int AudioDecoderPcmA::Decode(const uint8_t* encoded, size_t encoded_len,
int16_t* decoded, SpeechType* speech_type) {
int16_t temp_type = 1; // Default is speech.
int16_t ret = WebRtcG711_DecodeA(
- state_, reinterpret_cast<int16_t*>(const_cast<uint8_t*>(encoded)),
+ reinterpret_cast<int16_t*>(const_cast<uint8_t*>(encoded)),
static_cast<int16_t>(encoded_len), decoded, &temp_type);
*speech_type = ConvertSpeechType(temp_type);
return ret;
@@ -82,7 +82,7 @@ int AudioDecoderPcm16B::Decode(const uint8_t* encoded, size_t encoded_len,
int16_t* decoded, SpeechType* speech_type) {
int16_t temp_type = 1; // Default is speech.
int16_t ret = WebRtcPcm16b_DecodeW16(
- state_, reinterpret_cast<int16_t*>(const_cast<uint8_t*>(encoded)),
+ reinterpret_cast<int16_t*>(const_cast<uint8_t*>(encoded)),
static_cast<int16_t>(encoded_len), decoded, &temp_type);
*speech_type = ConvertSpeechType(temp_type);
return ret;
diff --git a/modules/audio_coding/neteq/decision_logic_normal.cc b/modules/audio_coding/neteq/decision_logic_normal.cc
index 9e422041..f2382845 100644
--- a/modules/audio_coding/neteq/decision_logic_normal.cc
+++ b/modules/audio_coding/neteq/decision_logic_normal.cc
@@ -67,19 +67,19 @@ Operations DecisionLogicNormal::GetDecisionSpecialized(
return kNormal;
}
+ const uint32_t five_seconds_samples = 5 * 8000 * fs_mult_;
// Check if the required packet is available.
if (target_timestamp == available_timestamp) {
return ExpectedPacketAvailable(prev_mode, play_dtmf);
- } else if (IsNewerTimestamp(available_timestamp, target_timestamp)) {
+ } else if (!PacketBuffer::IsObsoleteTimestamp(
+ available_timestamp, target_timestamp, five_seconds_samples)) {
return FuturePacketAvailable(sync_buffer, expand, decoder_frame_length,
prev_mode, target_timestamp,
available_timestamp, play_dtmf);
} else {
// This implies that available_timestamp < target_timestamp, which can
- // happen when a new stream or codec is received. Do Expand instead, and
- // wait for a newer packet to arrive, or for the buffer to flush (resulting
- // in a master reset).
- return kExpand;
+ // happen when a new stream or codec is received. Signal for a reset.
+ return kUndefined;
}
}
diff --git a/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h b/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h
index c15fa1ac..400c0b03 100644
--- a/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h
+++ b/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h
@@ -33,7 +33,7 @@ class ExternalPcm16B : public AudioDecoder {
int16_t* decoded, SpeechType* speech_type) {
int16_t temp_type;
int16_t ret = WebRtcPcm16b_DecodeW16(
- state_, reinterpret_cast<int16_t*>(const_cast<uint8_t*>(encoded)),
+ reinterpret_cast<int16_t*>(const_cast<uint8_t*>(encoded)),
static_cast<int16_t>(encoded_len), decoded, &temp_type);
*speech_type = ConvertSpeechType(temp_type);
return ret;
diff --git a/modules/audio_coding/neteq/mock/mock_packet_buffer.h b/modules/audio_coding/neteq/mock/mock_packet_buffer.h
index 74eea6f0..0eb7edc9 100644
--- a/modules/audio_coding/neteq/mock/mock_packet_buffer.h
+++ b/modules/audio_coding/neteq/mock/mock_packet_buffer.h
@@ -44,7 +44,9 @@ class MockPacketBuffer : public PacketBuffer {
Packet*(int* discard_count));
MOCK_METHOD0(DiscardNextPacket,
int());
- MOCK_METHOD1(DiscardOldPackets,
+ MOCK_METHOD2(DiscardOldPackets,
+ int(uint32_t timestamp_limit, uint32_t horizon_samples));
+ MOCK_METHOD1(DiscardAllOldPackets,
int(uint32_t timestamp_limit));
MOCK_CONST_METHOD0(NumPacketsInBuffer,
int());
diff --git a/modules/audio_coding/neteq/neteq_external_decoder_unittest.cc b/modules/audio_coding/neteq/neteq_external_decoder_unittest.cc
index 2e07b490..d41bc543 100644
--- a/modules/audio_coding/neteq/neteq_external_decoder_unittest.cc
+++ b/modules/audio_coding/neteq/neteq_external_decoder_unittest.cc
@@ -308,6 +308,8 @@ class LargeTimestampJumpTest : public NetEqExternalDecoderTest {
case kExpandPhase: {
if (output_type == kOutputPLCtoCNG) {
test_state_ = kFadedExpandPhase;
+ } else if (output_type == kOutputNormal) {
+ test_state_ = kRecovered;
}
break;
}
@@ -337,9 +339,14 @@ class LargeTimestampJumpTest : public NetEqExternalDecoderTest {
}
int NumExpectedDecodeCalls(int num_loops) const OVERRIDE {
- // Some packets won't be decoded because of the buffer being flushed after
- // the timestamp jump.
- return num_loops - (config_.max_packets_in_buffer + 1);
+ // Some packets at the end of the stream won't be decoded. When the jump in
+ // timestamp happens, NetEq will do Expand during one GetAudio call. In the
+ // next call it will decode the packet after the jump, but the net result is
+ // that the delay increased by 1 packet. In another call, a Pre-emptive
+ // Expand operation is performed, leading to delay increase by 1 packet. In
+ // total, the test will end with a 2-packet delay, which results in the 2
+ // last packets not being decoded.
+ return num_loops - 2;
}
TestStates test_state_;
diff --git a/modules/audio_coding/neteq/neteq_impl.cc b/modules/audio_coding/neteq/neteq_impl.cc
index 44faa22a..f3d1a4f6 100644
--- a/modules/audio_coding/neteq/neteq_impl.cc
+++ b/modules/audio_coding/neteq/neteq_impl.cc
@@ -868,6 +868,10 @@ int NetEqImpl::GetDecision(Operations* operation,
assert(sync_buffer_.get());
uint32_t end_timestamp = sync_buffer_->end_timestamp();
+ if (!new_codec_) {
+ const uint32_t five_seconds_samples = 5 * fs_hz_;
+ packet_buffer_->DiscardOldPackets(end_timestamp, five_seconds_samples);
+ }
const RTPHeader* header = packet_buffer_->NextRtpHeader();
if (decision_logic_->CngRfc3389On() || last_mode_ == kModeRfc3389Cng) {
@@ -884,7 +888,7 @@ int NetEqImpl::GetDecision(Operations* operation,
}
// Check buffer again.
if (!new_codec_) {
- packet_buffer_->DiscardOldPackets(end_timestamp);
+ packet_buffer_->DiscardOldPackets(end_timestamp, 5 * fs_hz_);
}
header = packet_buffer_->NextRtpHeader();
}
@@ -1823,7 +1827,7 @@ int NetEqImpl::ExtractPackets(int required_samples, PacketList* packet_list) {
// we could end up in the situation where we never decode anything, since
// all incoming packets are considered too old but the buffer will also
// never be flooded and flushed.
- packet_buffer_->DiscardOldPackets(timestamp_);
+ packet_buffer_->DiscardAllOldPackets(timestamp_);
}
return extracted_samples;
diff --git a/modules/audio_coding/neteq/neteq_impl_unittest.cc b/modules/audio_coding/neteq/neteq_impl_unittest.cc
index b3bd69ba..8047fe20 100644
--- a/modules/audio_coding/neteq/neteq_impl_unittest.cc
+++ b/modules/audio_coding/neteq/neteq_impl_unittest.cc
@@ -32,9 +32,11 @@ using ::testing::Return;
using ::testing::ReturnNull;
using ::testing::_;
using ::testing::SetArgPointee;
+using ::testing::SetArrayArgument;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::WithArg;
+using ::testing::Pointee;
namespace webrtc {
@@ -494,4 +496,95 @@ TEST_F(NetEqImplTest, VerifyTimestampPropagation) {
static_cast<int>(sync_buffer->FutureLength()));
}
+TEST_F(NetEqImplTest, ReorderedPacket) {
+ UseNoMocks();
+ CreateInstance();
+
+ const uint8_t kPayloadType = 17; // Just an arbitrary number.
+ const uint32_t kReceiveTime = 17; // Value doesn't matter for this test.
+ const int kSampleRateHz = 8000;
+ const int kPayloadLengthSamples = 10 * kSampleRateHz / 1000; // 10 ms.
+ const size_t kPayloadLengthBytes = kPayloadLengthSamples;
+ uint8_t payload[kPayloadLengthBytes] = {0};
+ WebRtcRTPHeader rtp_header;
+ rtp_header.header.payloadType = kPayloadType;
+ rtp_header.header.sequenceNumber = 0x1234;
+ rtp_header.header.timestamp = 0x12345678;
+ rtp_header.header.ssrc = 0x87654321;
+
+ // Create a mock decoder object.
+ MockAudioDecoder mock_decoder;
+ EXPECT_CALL(mock_decoder, Init()).WillRepeatedly(Return(0));
+ EXPECT_CALL(mock_decoder, IncomingPacket(_, kPayloadLengthBytes, _, _, _))
+ .WillRepeatedly(Return(0));
+ int16_t dummy_output[kPayloadLengthSamples] = {0};
+ // The below expectation will make the mock decoder write
+ // |kPayloadLengthSamples| zeros to the output array, and mark it as speech.
+ EXPECT_CALL(mock_decoder, Decode(Pointee(0), kPayloadLengthBytes, _, _))
+ .WillOnce(DoAll(SetArrayArgument<2>(dummy_output,
+ dummy_output + kPayloadLengthSamples),
+ SetArgPointee<3>(AudioDecoder::kSpeech),
+ Return(kPayloadLengthSamples)));
+ EXPECT_EQ(NetEq::kOK,
+ neteq_->RegisterExternalDecoder(
+ &mock_decoder, kDecoderPCM16B, kPayloadType));
+
+ // Insert one packet.
+ EXPECT_EQ(NetEq::kOK,
+ neteq_->InsertPacket(
+ rtp_header, payload, kPayloadLengthBytes, kReceiveTime));
+
+ // Pull audio once.
+ const int kMaxOutputSize = 10 * kSampleRateHz / 1000;
+ int16_t output[kMaxOutputSize];
+ int samples_per_channel;
+ int num_channels;
+ NetEqOutputType type;
+ EXPECT_EQ(
+ NetEq::kOK,
+ neteq_->GetAudio(
+ kMaxOutputSize, output, &samples_per_channel, &num_channels, &type));
+ ASSERT_EQ(kMaxOutputSize, samples_per_channel);
+ EXPECT_EQ(1, num_channels);
+ EXPECT_EQ(kOutputNormal, type);
+
+ // Insert two more packets. The first one is out of order, and is already too
+ // old, the second one is the expected next packet.
+ rtp_header.header.sequenceNumber -= 1;
+ rtp_header.header.timestamp -= kPayloadLengthSamples;
+ payload[0] = 1;
+ EXPECT_EQ(NetEq::kOK,
+ neteq_->InsertPacket(
+ rtp_header, payload, kPayloadLengthBytes, kReceiveTime));
+ rtp_header.header.sequenceNumber += 2;
+ rtp_header.header.timestamp += 2 * kPayloadLengthSamples;
+ payload[0] = 2;
+ EXPECT_EQ(NetEq::kOK,
+ neteq_->InsertPacket(
+ rtp_header, payload, kPayloadLengthBytes, kReceiveTime));
+
+ // Expect only the second packet to be decoded (the one with "2" as the first
+ // payload byte).
+ EXPECT_CALL(mock_decoder, Decode(Pointee(2), kPayloadLengthBytes, _, _))
+ .WillOnce(DoAll(SetArrayArgument<2>(dummy_output,
+ dummy_output + kPayloadLengthSamples),
+ SetArgPointee<3>(AudioDecoder::kSpeech),
+ Return(kPayloadLengthSamples)));
+
+ // Pull audio once.
+ EXPECT_EQ(
+ NetEq::kOK,
+ neteq_->GetAudio(
+ kMaxOutputSize, output, &samples_per_channel, &num_channels, &type));
+ ASSERT_EQ(kMaxOutputSize, samples_per_channel);
+ EXPECT_EQ(1, num_channels);
+ EXPECT_EQ(kOutputNormal, type);
+
+ // Now check the packet buffer, and make sure it is empty, since the
+ // out-of-order packet should have been discarded.
+ EXPECT_TRUE(packet_buffer_->Empty());
+
+ EXPECT_CALL(mock_decoder, Die());
+}
+
} // namespace webrtc
diff --git a/modules/audio_coding/neteq/packet_buffer.cc b/modules/audio_coding/neteq/packet_buffer.cc
index 4c484185..816713d8 100644
--- a/modules/audio_coding/neteq/packet_buffer.cc
+++ b/modules/audio_coding/neteq/packet_buffer.cc
@@ -216,12 +216,12 @@ int PacketBuffer::DiscardNextPacket() {
return kOK;
}
-int PacketBuffer::DiscardOldPackets(uint32_t timestamp_limit) {
- while (!Empty() &&
- timestamp_limit != buffer_.front()->header.timestamp &&
- static_cast<uint32_t>(timestamp_limit
- - buffer_.front()->header.timestamp) <
- 0xFFFFFFFF / 2) {
+int PacketBuffer::DiscardOldPackets(uint32_t timestamp_limit,
+ uint32_t horizon_samples) {
+ while (!Empty() && timestamp_limit != buffer_.front()->header.timestamp &&
+ IsObsoleteTimestamp(buffer_.front()->header.timestamp,
+ timestamp_limit,
+ horizon_samples)) {
if (DiscardNextPacket() != kOK) {
assert(false); // Must be ok by design.
}
diff --git a/modules/audio_coding/neteq/packet_buffer.h b/modules/audio_coding/neteq/packet_buffer.h
index 76c4ddd1..b9a16189 100644
--- a/modules/audio_coding/neteq/packet_buffer.h
+++ b/modules/audio_coding/neteq/packet_buffer.h
@@ -95,9 +95,19 @@ class PacketBuffer {
// PacketBuffer::kOK otherwise.
virtual int DiscardNextPacket();
- // Discards all packets that are (strictly) older than |timestamp_limit|.
+ // Discards all packets that are (strictly) older than timestamp_limit,
+ // but newer than timestamp_limit - horizon_samples. Setting horizon_samples
+ // to zero implies that the horizon is set to half the timestamp range. That
+ // is, if a packet is more than 2^31 timestamps into the future compared with
+ // timestamp_limit (including wrap-around), it is considered old.
// Returns number of packets discarded.
- virtual int DiscardOldPackets(uint32_t timestamp_limit);
+ virtual int DiscardOldPackets(uint32_t timestamp_limit,
+ uint32_t horizon_samples);
+
+ // Discards all packets that are (strictly) older than timestamp_limit.
+ virtual int DiscardAllOldPackets(uint32_t timestamp_limit) {
+ return DiscardOldPackets(timestamp_limit, 0);
+ }
// Returns the number of packets in the buffer, including duplicates and
// redundant packets.
@@ -125,6 +135,20 @@ class PacketBuffer {
// in |packet_list|.
static void DeleteAllPackets(PacketList* packet_list);
+ // Static method returning true if |timestamp| is older than |timestamp_limit|
+ // but less than |horizon_samples| behind |timestamp_limit|. For instance,
+ // with timestamp_limit = 100 and horizon_samples = 10, a timestamp in the
+ // range (90, 100) is considered obsolete, and will yield true.
+ // Setting |horizon_samples| to 0 is the same as setting it to 2^31, i.e.,
+ // half the 32-bit timestamp range.
+ static bool IsObsoleteTimestamp(uint32_t timestamp,
+ uint32_t timestamp_limit,
+ uint32_t horizon_samples) {
+ return IsNewerTimestamp(timestamp_limit, timestamp) &&
+ (horizon_samples == 0 ||
+ IsNewerTimestamp(timestamp, timestamp_limit - horizon_samples));
+ }
+
private:
size_t max_number_of_packets_;
PacketList buffer_;
diff --git a/modules/audio_coding/neteq/packet_buffer_unittest.cc b/modules/audio_coding/neteq/packet_buffer_unittest.cc
index 478328cb..dc8b68c3 100644
--- a/modules/audio_coding/neteq/packet_buffer_unittest.cc
+++ b/modules/audio_coding/neteq/packet_buffer_unittest.cc
@@ -391,7 +391,7 @@ TEST(PacketBuffer, Failures) {
EXPECT_EQ(NULL, buffer->NextRtpHeader());
EXPECT_EQ(NULL, buffer->GetNextPacket(NULL));
EXPECT_EQ(PacketBuffer::kBufferEmpty, buffer->DiscardNextPacket());
- EXPECT_EQ(0, buffer->DiscardOldPackets(0)); // 0 packets discarded.
+ EXPECT_EQ(0, buffer->DiscardAllOldPackets(0)); // 0 packets discarded.
// Insert one packet to make the buffer non-empty.
packet = gen.NextPacket(payload_len);
@@ -513,4 +513,61 @@ TEST(PacketBuffer, DeleteAllPackets) {
EXPECT_FALSE(PacketBuffer::DeleteFirstPacket(&list));
}
+namespace {
+void TestIsObsoleteTimestamp(uint32_t limit_timestamp) {
+ // Check with zero horizon, which implies that the horizon is at 2^31, i.e.,
+ // half the timestamp range.
+ static const uint32_t kZeroHorizon = 0;
+ static const uint32_t k2Pow31Minus1 = 0x7FFFFFFF;
+ // Timestamp on the limit is not old.
+ EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp, limit_timestamp, kZeroHorizon));
+ // 1 sample behind is old.
+ EXPECT_TRUE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp - 1, limit_timestamp, kZeroHorizon));
+ // 2^31 - 1 samples behind is old.
+ EXPECT_TRUE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp - k2Pow31Minus1, limit_timestamp, kZeroHorizon));
+ // 1 sample ahead is not old.
+ EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp + 1, limit_timestamp, kZeroHorizon));
+ // 2^31 samples ahead is not old.
+ EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp + (1 << 31), limit_timestamp, kZeroHorizon));
+
+ // Fixed horizon at 10 samples.
+ static const uint32_t kHorizon = 10;
+ // Timestamp on the limit is not old.
+ EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp, limit_timestamp, kHorizon));
+ // 1 sample behind is old.
+ EXPECT_TRUE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp - 1, limit_timestamp, kHorizon));
+ // 9 samples behind is old.
+ EXPECT_TRUE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp - 9, limit_timestamp, kHorizon));
+ // 10 samples behind is not old.
+ EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp - 10, limit_timestamp, kHorizon));
+ // 2^31 - 1 samples behind is not old.
+ EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp - k2Pow31Minus1, limit_timestamp, kHorizon));
+ // 1 sample ahead is not old.
+ EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp + 1, limit_timestamp, kHorizon));
+ // 2^31 samples ahead is not old.
+ EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp(
+ limit_timestamp + (1 << 31), limit_timestamp, kHorizon));
+}
+} // namespace
+
+// Test the IsObsoleteTimestamp method with different limit timestamps.
+TEST(PacketBuffer, IsObsoleteTimestamp) {
+ TestIsObsoleteTimestamp(0);
+ TestIsObsoleteTimestamp(1);
+ TestIsObsoleteTimestamp(0xFFFFFFFF); // -1 in uint32_t.
+ TestIsObsoleteTimestamp(0x80000000); // 2^31.
+ TestIsObsoleteTimestamp(0x80000001); // 2^31 + 1.
+ TestIsObsoleteTimestamp(0x7FFFFFFF); // 2^31 - 1.
+}
} // namespace webrtc
diff --git a/modules/audio_coding/neteq/test/RTPencode.cc b/modules/audio_coding/neteq/test/RTPencode.cc
index ab338a73..b73e70e5 100644
--- a/modules/audio_coding/neteq/test/RTPencode.cc
+++ b/modules/audio_coding/neteq/test/RTPencode.cc
@@ -235,9 +235,6 @@ WebRtcVadInst *VAD_inst[2];
#ifdef CODEC_CELT_32
CELT_encinst_t *CELT32enc_inst[2];
#endif
-#ifdef CODEC_G711
- void *G711state[2]={NULL, NULL};
-#endif
int main(int argc, char* argv[])
@@ -1602,12 +1599,12 @@ int NetEQTest_encode(int coder, int16_t *indata, int frameLen, unsigned char * e
/* Encode with the selected coder type */
if (coder==webrtc::kDecoderPCMu) { /*g711 u-law */
#ifdef CODEC_G711
- cdlen = WebRtcG711_EncodeU(G711state[k], indata, frameLen, (int16_t*) encoded);
+ cdlen = WebRtcG711_EncodeU(indata, frameLen, (int16_t*) encoded);
#endif
}
else if (coder==webrtc::kDecoderPCMa) { /*g711 A-law */
#ifdef CODEC_G711
- cdlen = WebRtcG711_EncodeA(G711state[k], indata, frameLen, (int16_t*) encoded);
+ cdlen = WebRtcG711_EncodeA(indata, frameLen, (int16_t*) encoded);
}
#endif
#ifdef CODEC_PCM16B
diff --git a/modules/pacing/include/paced_sender.h b/modules/pacing/include/paced_sender.h
index d3034466..d7efb8ea 100644
--- a/modules/pacing/include/paced_sender.h
+++ b/modules/pacing/include/paced_sender.h
@@ -27,7 +27,7 @@ class CriticalSectionWrapper;
namespace paced_sender {
class IntervalBudget;
struct Packet;
-class PacketList;
+class PacketQueue;
} // namespace paced_sender
class PacedSender : public Module {
@@ -105,15 +105,15 @@ class PacedSender : public Module {
int bytes,
bool retransmission);
- // Sets the max length of the pacer queue in milliseconds.
- // A negative queue size is interpreted as infinite.
- virtual void set_max_queue_length_ms(int max_queue_length_ms);
-
// Returns the time since the oldest queued packet was enqueued.
virtual int QueueInMs() const;
virtual size_t QueueSizePackets() const;
+ // Returns the number of milliseconds it will take to send the current
+ // packets in the queue, given the current size and bitrate, ignoring prio.
+ virtual int ExpectedQueueTimeMs() const;
+
// Returns the number of milliseconds until the module want a worker thread
// to call Process.
virtual int32_t TimeUntilNextProcess() OVERRIDE;
@@ -125,24 +125,13 @@ class PacedSender : public Module {
virtual bool ProbingExperimentIsEnabled() const;
private:
- // Return true if next packet in line should be transmitted.
- // Return packet list that contains the next packet.
- bool ShouldSendNextPacket(paced_sender::PacketList** packet_list, bool probe)
- EXCLUSIVE_LOCKS_REQUIRED(critsect_);
-
- // Local helper function to GetNextPacket.
- paced_sender::Packet GetNextPacketFromList(paced_sender::PacketList* packets)
- EXCLUSIVE_LOCKS_REQUIRED(critsect_);
-
- bool SendPacketFromList(paced_sender::PacketList* packet_list)
- EXCLUSIVE_LOCKS_REQUIRED(critsect_);
-
// Updates the number of bytes that can be sent for the next time interval.
void UpdateBytesPerInterval(uint32_t delta_time_in_ms)
EXCLUSIVE_LOCKS_REQUIRED(critsect_);
- // Updates the buffers with the number of bytes that we sent.
- void UpdateMediaBytesSent(int num_bytes) EXCLUSIVE_LOCKS_REQUIRED(critsect_);
+ bool SendPacket(const paced_sender::Packet& packet)
+ EXCLUSIVE_LOCKS_REQUIRED(critsect_);
+ void SendPadding(int padding_needed) EXCLUSIVE_LOCKS_REQUIRED(critsect_);
Clock* const clock_;
Callback* const callback_;
@@ -150,7 +139,6 @@ class PacedSender : public Module {
scoped_ptr<CriticalSectionWrapper> critsect_;
bool enabled_ GUARDED_BY(critsect_);
bool paused_ GUARDED_BY(critsect_);
- int max_queue_length_ms_ GUARDED_BY(critsect_);
// This is the media budget, keeping track of how many bits of media
// we can pace out during the current interval.
scoped_ptr<paced_sender::IntervalBudget> media_budget_ GUARDED_BY(critsect_);
@@ -164,17 +152,9 @@ class PacedSender : public Module {
int bitrate_bps_ GUARDED_BY(critsect_);
int64_t time_last_update_us_ GUARDED_BY(critsect_);
- // Only accessed via process thread.
- int64_t time_last_media_send_us_;
- int64_t capture_time_ms_last_queued_ GUARDED_BY(critsect_);
- int64_t capture_time_ms_last_sent_ GUARDED_BY(critsect_);
- scoped_ptr<paced_sender::PacketList> high_priority_packets_
- GUARDED_BY(critsect_);
- scoped_ptr<paced_sender::PacketList> normal_priority_packets_
- GUARDED_BY(critsect_);
- scoped_ptr<paced_sender::PacketList> low_priority_packets_
- GUARDED_BY(critsect_);
+ scoped_ptr<paced_sender::PacketQueue> packets_ GUARDED_BY(critsect_);
+ uint64_t packet_counter_ GUARDED_BY(critsect_);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_PACING_INCLUDE_PACED_SENDER_H_
diff --git a/modules/pacing/paced_sender.cc b/modules/pacing/paced_sender.cc
index 64b3eb1e..a071ffcc 100644
--- a/modules/pacing/paced_sender.cc
+++ b/modules/pacing/paced_sender.cc
@@ -13,6 +13,7 @@
#include <assert.h>
#include <map>
+#include <queue>
#include <set>
#include "webrtc/modules/interface/module_common_types.h"
@@ -31,80 +32,140 @@ const int kMinPacketLimitMs = 5;
// time.
const int kMaxIntervalTimeMs = 30;
-// Max time that the first packet in the queue can sit in the queue if no
-// packets are sent, regardless of buffer state. In practice only in effect at
-// low bitrates (less than 320 kbits/s).
-const int kMaxQueueTimeWithoutSendingUs = 30000;
-
} // namespace
namespace webrtc {
namespace paced_sender {
struct Packet {
- Packet(uint32_t ssrc,
+ Packet(PacedSender::Priority priority,
+ uint32_t ssrc,
uint16_t seq_number,
int64_t capture_time_ms,
int64_t enqueue_time_ms,
int length_in_bytes,
- bool retransmission)
- : ssrc(ssrc),
+ bool retransmission,
+ uint64_t enqueue_order)
+ : priority(priority),
+ ssrc(ssrc),
sequence_number(seq_number),
capture_time_ms(capture_time_ms),
enqueue_time_ms(enqueue_time_ms),
bytes(length_in_bytes),
- retransmission(retransmission) {}
+ retransmission(retransmission),
+ enqueue_order(enqueue_order) {}
+
+ PacedSender::Priority priority;
uint32_t ssrc;
uint16_t sequence_number;
int64_t capture_time_ms;
int64_t enqueue_time_ms;
int bytes;
bool retransmission;
+ uint64_t enqueue_order;
+ std::list<Packet>::iterator this_it;
+};
+
+// Used by priority queue to sort packets.
+struct Comparator {
+ bool operator()(const Packet* first, const Packet* second) {
+ // Highest prio = 0.
+ if (first->priority != second->priority)
+ return first->priority > second->priority;
+
+ // Retransmissions go first.
+ if (second->retransmission && !first->retransmission)
+ return true;
+
+ // Older frames have higher prio.
+ if (first->capture_time_ms != second->capture_time_ms)
+ return first->capture_time_ms > second->capture_time_ms;
+
+ return first->enqueue_order > second->enqueue_order;
+ }
};
-// STL list style class which prevents duplicates in the list.
-class PacketList {
+// Class encapsulating a priority queue with some extensions.
+class PacketQueue {
public:
- PacketList() {};
+ PacketQueue() : bytes_(0) {}
+ virtual ~PacketQueue() {}
+
+ void Push(const Packet& packet) {
+ if (!AddToDupeSet(packet))
+ return;
+
+ // Store packet in list, use pointers in priority queue for cheaper moves.
+ // Packets have a handle to its own iterator in the list, for easy removal
+ // when popping from queue.
+ packet_list_.push_front(packet);
+ std::list<Packet>::iterator it = packet_list_.begin();
+ it->this_it = it; // Handle for direct removal from list.
+ prio_queue_.push(&(*it)); // Pointer into list.
+ bytes_ += packet.bytes;
+ }
- bool empty() const {
- return packet_list_.empty();
+ const Packet& BeginPop() {
+ const Packet& packet = *prio_queue_.top();
+ prio_queue_.pop();
+ return packet;
}
- Packet front() const {
- return packet_list_.front();
+ void CancelPop(const Packet& packet) { prio_queue_.push(&(*packet.this_it)); }
+
+ void FinalizePop(const Packet& packet) {
+ RemoveFromDupeSet(packet);
+ bytes_ -= packet.bytes;
+ packet_list_.erase(packet.this_it);
}
- size_t size() const {
- size_t sum = 0;
- for (std::map<uint32_t, std::set<uint16_t> >::const_iterator it =
- sequence_number_set_.begin();
- it != sequence_number_set_.end();
- ++it) {
- sum += it->second.size();
- }
- return sum;
+ bool Empty() const { return prio_queue_.empty(); }
+
+ size_t SizeInPackets() const { return prio_queue_.size(); }
+
+ uint32_t SizeInBytes() const { return bytes_; }
+
+ int64_t OldestEnqueueTime() const {
+ std::list<Packet>::const_reverse_iterator it = packet_list_.rbegin();
+ if (it == packet_list_.rend())
+ return 0;
+ return it->enqueue_time_ms;
}
- void pop_front() {
- Packet& packet = packet_list_.front();
- uint16_t sequence_number = packet.sequence_number;
- uint32_t ssrc = packet.ssrc;
- packet_list_.pop_front();
- sequence_number_set_[ssrc].erase(sequence_number);
+ private:
+ // Try to add a packet to the set of ssrc/seqno identifiers currently in the
+ // queue. Return true if inserted, false if this is a duplicate.
+ bool AddToDupeSet(const Packet& packet) {
+ SsrcSeqNoMap::iterator it = dupe_map_.find(packet.ssrc);
+ if (it == dupe_map_.end()) {
+ // First for this ssrc, just insert.
+ dupe_map_[packet.ssrc].insert(packet.sequence_number);
+ return true;
+ }
+
+ // Insert returns a pair, where second is a bool set to true if new element.
+ return it->second.insert(packet.sequence_number).second;
}
- void push_back(const Packet& packet) {
- if (sequence_number_set_[packet.ssrc].find(packet.sequence_number) ==
- sequence_number_set_[packet.ssrc].end()) {
- // Don't insert duplicates.
- packet_list_.push_back(packet);
- sequence_number_set_[packet.ssrc].insert(packet.sequence_number);
+ void RemoveFromDupeSet(const Packet& packet) {
+ SsrcSeqNoMap::iterator it = dupe_map_.find(packet.ssrc);
+ assert(it != dupe_map_.end());
+ it->second.erase(packet.sequence_number);
+ if (it->second.empty()) {
+ dupe_map_.erase(it);
}
}
- private:
+ // List of packets, in the order the were enqueued. Since dequeueing may
+ // occur out of order, use list instead of vector.
std::list<Packet> packet_list_;
- std::map<uint32_t, std::set<uint16_t> > sequence_number_set_;
+ // Priority queue of the packets, sorted according to Comparator.
+ // Use pointers into list, to avoid moving whole struct within heap.
+ std::priority_queue<Packet*, std::vector<Packet*>, Comparator> prio_queue_;
+ // Total number of bytes in the queue.
+ uint64_t bytes_;
+ // Map<ssrc, set<seq_no> >, for checking duplicates.
+ typedef std::map<uint32_t, std::set<uint16_t> > SsrcSeqNoMap;
+ SsrcSeqNoMap dupe_map_;
};
class IntervalBudget {
@@ -135,6 +196,8 @@ class IntervalBudget {
int bytes_remaining() const { return bytes_remaining_; }
+ int target_rate_kbps() const { return target_rate_kbps_; }
+
private:
int target_rate_kbps_;
int bytes_remaining_;
@@ -153,18 +216,13 @@ PacedSender::PacedSender(Clock* clock,
critsect_(CriticalSectionWrapper::CreateCriticalSection()),
enabled_(true),
paused_(false),
- max_queue_length_ms_(kDefaultMaxQueueLengthMs),
media_budget_(new paced_sender::IntervalBudget(max_bitrate_kbps)),
padding_budget_(new paced_sender::IntervalBudget(min_bitrate_kbps)),
prober_(new BitrateProber()),
bitrate_bps_(1000 * bitrate_kbps),
time_last_update_us_(clock->TimeInMicroseconds()),
- time_last_media_send_us_(-1),
- capture_time_ms_last_queued_(0),
- capture_time_ms_last_sent_(0),
- high_priority_packets_(new paced_sender::PacketList),
- normal_priority_packets_(new paced_sender::PacketList),
- low_priority_packets_(new paced_sender::PacketList) {
+ packets_(new paced_sender::PacketQueue()),
+ packet_counter_(0) {
UpdateBytesPerInterval(kMinPacketLimitMs);
}
@@ -216,64 +274,33 @@ bool PacedSender::SendPacket(Priority priority, uint32_t ssrc,
if (capture_time_ms < 0) {
capture_time_ms = clock_->TimeInMilliseconds();
}
- if (priority != kHighPriority &&
- capture_time_ms > capture_time_ms_last_queued_) {
- capture_time_ms_last_queued_ = capture_time_ms;
- TRACE_EVENT_ASYNC_BEGIN1("webrtc_rtp", "PacedSend", capture_time_ms,
- "capture_time_ms", capture_time_ms);
- }
- paced_sender::PacketList* packet_list = NULL;
- switch (priority) {
- case kHighPriority:
- packet_list = high_priority_packets_.get();
- break;
- case kNormalPriority:
- packet_list = normal_priority_packets_.get();
- break;
- case kLowPriority:
- packet_list = low_priority_packets_.get();
- break;
- }
- packet_list->push_back(paced_sender::Packet(ssrc,
- sequence_number,
- capture_time_ms,
- clock_->TimeInMilliseconds(),
- bytes,
- retransmission));
+
+ packets_->Push(paced_sender::Packet(
+ priority, ssrc, sequence_number, capture_time_ms,
+ clock_->TimeInMilliseconds(), bytes, retransmission, packet_counter_++));
return false;
}
-void PacedSender::set_max_queue_length_ms(int max_queue_length_ms) {
+int PacedSender::ExpectedQueueTimeMs() const {
CriticalSectionScoped cs(critsect_.get());
- max_queue_length_ms_ = max_queue_length_ms;
+ int target_rate = media_budget_->target_rate_kbps();
+ assert(target_rate > 0);
+ return packets_->SizeInBytes() * 8 / target_rate;
}
-int PacedSender::QueueInMs() const {
+size_t PacedSender::QueueSizePackets() const {
CriticalSectionScoped cs(critsect_.get());
- int64_t now_ms = clock_->TimeInMilliseconds();
- int64_t oldest_packet_enqueue_time = now_ms;
- if (!high_priority_packets_->empty()) {
- oldest_packet_enqueue_time =
- std::min(oldest_packet_enqueue_time,
- high_priority_packets_->front().enqueue_time_ms);
- }
- if (!normal_priority_packets_->empty()) {
- oldest_packet_enqueue_time =
- std::min(oldest_packet_enqueue_time,
- normal_priority_packets_->front().enqueue_time_ms);
- }
- if (!low_priority_packets_->empty()) {
- oldest_packet_enqueue_time =
- std::min(oldest_packet_enqueue_time,
- low_priority_packets_->front().enqueue_time_ms);
- }
- return now_ms - oldest_packet_enqueue_time;
+ return packets_->SizeInPackets();
}
-size_t PacedSender::QueueSizePackets() const {
+int PacedSender::QueueInMs() const {
CriticalSectionScoped cs(critsect_.get());
- return low_priority_packets_->size() + normal_priority_packets_->size() +
- high_priority_packets_->size();
+
+ int64_t oldest_packet = packets_->OldestEnqueueTime();
+ if (oldest_packet == 0)
+ return 0;
+
+ return clock_->TimeInMilliseconds() - oldest_packet;
}
int32_t PacedSender::TimeUntilNextProcess() {
@@ -303,127 +330,66 @@ int32_t PacedSender::Process() {
uint32_t delta_time_ms = std::min(kMaxIntervalTimeMs, elapsed_time_ms);
UpdateBytesPerInterval(delta_time_ms);
}
- paced_sender::PacketList* packet_list;
- while (ShouldSendNextPacket(&packet_list, prober_->IsProbing())) {
- if (!SendPacketFromList(packet_list))
+
+ while (!packets_->Empty()) {
+ if (media_budget_->bytes_remaining() <= 0 && !prober_->IsProbing())
return 0;
- // Send one packet per Process() call when probing, so that we have
- // better control over the delta between packets.
- if (prober_->IsProbing())
+
+ // Since we need to release the lock in order to send, we first pop the
+ // element from the priority queue but keep it in storage, so that we can
+ // reinsert it if send fails.
+ const paced_sender::Packet& packet = packets_->BeginPop();
+ if (SendPacket(packet)) {
+ // Send succeeded, remove it from the queue.
+ packets_->FinalizePop(packet);
+ if (prober_->IsProbing())
+ return 0;
+ } else {
+ // Send failed, put it back into the queue.
+ packets_->CancelPop(packet);
return 0;
+ }
}
- if (high_priority_packets_->empty() && normal_priority_packets_->empty() &&
- low_priority_packets_->empty() &&
- padding_budget_->bytes_remaining() > 0) {
- int padding_needed = padding_budget_->bytes_remaining();
- critsect_->Leave();
- int bytes_sent = callback_->TimeToSendPadding(padding_needed);
- critsect_->Enter();
- media_budget_->UseBudget(bytes_sent);
- padding_budget_->UseBudget(bytes_sent);
+
+ int padding_needed = padding_budget_->bytes_remaining();
+ if (padding_needed > 0) {
+ SendPadding(padding_needed);
}
}
return 0;
}
-bool PacedSender::SendPacketFromList(paced_sender::PacketList* packet_list)
- EXCLUSIVE_LOCKS_REQUIRED(critsect_.get()) {
- paced_sender::Packet packet = GetNextPacketFromList(packet_list);
+bool PacedSender::SendPacket(const paced_sender::Packet& packet) {
critsect_->Leave();
-
const bool success = callback_->TimeToSendPacket(packet.ssrc,
packet.sequence_number,
packet.capture_time_ms,
packet.retransmission);
critsect_->Enter();
- // If packet cannot be sent then keep it in packet list and exit early.
- // There's no need to send more packets.
- if (!success) {
- return false;
- }
- packet_list->pop_front();
- const bool last_packet =
- packet_list->empty() ||
- packet_list->front().capture_time_ms > packet.capture_time_ms;
- if (packet_list != high_priority_packets_.get()) {
- if (packet.capture_time_ms > capture_time_ms_last_sent_) {
- capture_time_ms_last_sent_ = packet.capture_time_ms;
- } else if (packet.capture_time_ms == capture_time_ms_last_sent_ &&
- last_packet) {
- TRACE_EVENT_ASYNC_END0("webrtc_rtp", "PacedSend", packet.capture_time_ms);
- }
+
+ if (success) {
+ // Update media bytes sent.
+ prober_->PacketSent(clock_->TimeInMilliseconds(), packet.bytes);
+ media_budget_->UseBudget(packet.bytes);
+ padding_budget_->UseBudget(packet.bytes);
}
- return true;
-}
-void PacedSender::UpdateBytesPerInterval(uint32_t delta_time_ms) {
- media_budget_->IncreaseBudget(delta_time_ms);
- padding_budget_->IncreaseBudget(delta_time_ms);
+ return success;
}
-bool PacedSender::ShouldSendNextPacket(paced_sender::PacketList** packet_list,
- bool probe) {
- *packet_list = NULL;
- if (!probe && media_budget_->bytes_remaining() <= 0) {
- // All bytes consumed for this interval.
- // Check if we have not sent in a too long time.
- if (clock_->TimeInMicroseconds() - time_last_media_send_us_ >
- kMaxQueueTimeWithoutSendingUs) {
- if (!high_priority_packets_->empty()) {
- *packet_list = high_priority_packets_.get();
- return true;
- }
- if (!normal_priority_packets_->empty()) {
- *packet_list = normal_priority_packets_.get();
- return true;
- }
- }
- // Send any old packets to avoid queuing for too long.
- if (max_queue_length_ms_ >= 0 && QueueInMs() > max_queue_length_ms_) {
- int64_t high_priority_capture_time = -1;
- if (!high_priority_packets_->empty()) {
- high_priority_capture_time =
- high_priority_packets_->front().capture_time_ms;
- *packet_list = high_priority_packets_.get();
- }
- if (!normal_priority_packets_->empty() &&
- (high_priority_capture_time == -1 ||
- high_priority_capture_time >
- normal_priority_packets_->front().capture_time_ms)) {
- *packet_list = normal_priority_packets_.get();
- }
- if (*packet_list)
- return true;
- }
- return false;
- }
- if (!high_priority_packets_->empty()) {
- *packet_list = high_priority_packets_.get();
- return true;
- }
- if (!normal_priority_packets_->empty()) {
- *packet_list = normal_priority_packets_.get();
- return true;
- }
- if (!low_priority_packets_->empty()) {
- *packet_list = low_priority_packets_.get();
- return true;
- }
- return false;
-}
+void PacedSender::SendPadding(int padding_needed) {
+ critsect_->Leave();
+ int bytes_sent = callback_->TimeToSendPadding(padding_needed);
+ critsect_->Enter();
-paced_sender::Packet PacedSender::GetNextPacketFromList(
- paced_sender::PacketList* packets) {
- paced_sender::Packet packet = packets->front();
- UpdateMediaBytesSent(packet.bytes);
- return packet;
+ // Update padding bytes sent.
+ media_budget_->UseBudget(bytes_sent);
+ padding_budget_->UseBudget(bytes_sent);
}
-void PacedSender::UpdateMediaBytesSent(int num_bytes) {
- prober_->PacketSent(clock_->TimeInMilliseconds(), num_bytes);
- time_last_media_send_us_ = clock_->TimeInMicroseconds();
- media_budget_->UseBudget(num_bytes);
- padding_budget_->UseBudget(num_bytes);
+void PacedSender::UpdateBytesPerInterval(uint32_t delta_time_ms) {
+ media_budget_->IncreaseBudget(delta_time_ms);
+ padding_budget_->IncreaseBudget(delta_time_ms);
}
bool PacedSender::ProbingExperimentIsEnabled() const {
diff --git a/modules/pacing/paced_sender_unittest.cc b/modules/pacing/paced_sender_unittest.cc
index f8028a91..34787d16 100644
--- a/modules/pacing/paced_sender_unittest.cc
+++ b/modules/pacing/paced_sender_unittest.cc
@@ -639,34 +639,40 @@ TEST_F(PacedSenderTest, ResendPacket) {
EXPECT_EQ(0, send_bucket_->QueueInMs());
}
-TEST_F(PacedSenderTest, MaxQueueLength) {
+TEST_F(PacedSenderTest, ExpectedQueueTimeMs) {
uint32_t ssrc = 12346;
uint16_t sequence_number = 1234;
- EXPECT_EQ(0, send_bucket_->QueueInMs());
+ const int32_t kNumPackets = 60;
+ const int32_t kPacketSize = 1200;
+ const int32_t kMaxBitrate = kPaceMultiplier * 30;
+ EXPECT_EQ(0, send_bucket_->ExpectedQueueTimeMs());
- send_bucket_->UpdateBitrate(30, kPaceMultiplier * 30, 0);
- for (int i = 0; i < 30; ++i) {
- SendAndExpectPacket(PacedSender::kNormalPriority,
- ssrc,
- sequence_number++,
- clock_.TimeInMilliseconds(),
- 1200,
- false);
+ send_bucket_->UpdateBitrate(30, kMaxBitrate, 0);
+ for (int i = 0; i < kNumPackets; ++i) {
+ SendAndExpectPacket(PacedSender::kNormalPriority, ssrc, sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize, false);
}
- clock_.AdvanceTimeMilliseconds(2001);
- SendAndExpectPacket(PacedSender::kNormalPriority,
- ssrc,
- sequence_number++,
- clock_.TimeInMilliseconds(),
- 1200,
- false);
- EXPECT_EQ(2001, send_bucket_->QueueInMs());
- send_bucket_->Process();
- EXPECT_EQ(0, send_bucket_->QueueInMs());
- clock_.AdvanceTimeMilliseconds(31);
+ // Queue in ms = 1000 * (bytes in queue) / (kbit per second * 1000 / 8)
+ int32_t queue_in_ms = kNumPackets * kPacketSize * 8 / kMaxBitrate;
+ EXPECT_EQ(queue_in_ms, send_bucket_->ExpectedQueueTimeMs());
- send_bucket_->Process();
+ int64_t time_start = clock_.TimeInMilliseconds();
+ while (send_bucket_->QueueSizePackets() > 0) {
+ int time_until_process = send_bucket_->TimeUntilNextProcess();
+ if (time_until_process <= 0) {
+ send_bucket_->Process();
+ } else {
+ clock_.AdvanceTimeMilliseconds(time_until_process);
+ }
+ }
+ int64_t duration = clock_.TimeInMilliseconds() - time_start;
+
+ EXPECT_EQ(0, send_bucket_->ExpectedQueueTimeMs());
+
+ // Allow for aliasing, duration should be in [expected(n - 1), expected(n)].
+ EXPECT_LE(duration, queue_in_ms);
+ EXPECT_GE(duration, queue_in_ms - (kPacketSize * 8 / kMaxBitrate));
}
TEST_F(PacedSenderTest, QueueTimeGrowsOverTime) {
@@ -738,5 +744,79 @@ TEST_F(PacedSenderTest, ProbingWithInitialFrame) {
}
}
}
+
+TEST_F(PacedSenderTest, PriorityInversion) {
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+ const int32_t kPacketSize = 1200;
+
+ EXPECT_FALSE(send_bucket_->SendPacket(
+ PacedSender::kHighPriority, ssrc, sequence_number + 3,
+ clock_.TimeInMilliseconds() + 33, kPacketSize, true));
+
+ EXPECT_FALSE(send_bucket_->SendPacket(
+ PacedSender::kHighPriority, ssrc, sequence_number + 2,
+ clock_.TimeInMilliseconds() + 33, kPacketSize, true));
+
+ EXPECT_FALSE(send_bucket_->SendPacket(
+ PacedSender::kHighPriority, ssrc, sequence_number,
+ clock_.TimeInMilliseconds(), kPacketSize, true));
+
+ EXPECT_FALSE(send_bucket_->SendPacket(
+ PacedSender::kHighPriority, ssrc, sequence_number + 1,
+ clock_.TimeInMilliseconds(), kPacketSize, true));
+
+ // Packets from earlier frames should be sent first.
+ {
+ ::testing::InSequence sequence;
+ EXPECT_CALL(callback_, TimeToSendPacket(ssrc, sequence_number,
+ clock_.TimeInMilliseconds(), true))
+ .WillOnce(Return(true));
+ EXPECT_CALL(callback_, TimeToSendPacket(ssrc, sequence_number + 1,
+ clock_.TimeInMilliseconds(), true))
+ .WillOnce(Return(true));
+ EXPECT_CALL(callback_, TimeToSendPacket(ssrc, sequence_number + 3,
+ clock_.TimeInMilliseconds() + 33,
+ true)).WillOnce(Return(true));
+ EXPECT_CALL(callback_, TimeToSendPacket(ssrc, sequence_number + 2,
+ clock_.TimeInMilliseconds() + 33,
+ true)).WillOnce(Return(true));
+
+ while (send_bucket_->QueueSizePackets() > 0) {
+ int time_until_process = send_bucket_->TimeUntilNextProcess();
+ if (time_until_process <= 0) {
+ send_bucket_->Process();
+ } else {
+ clock_.AdvanceTimeMilliseconds(time_until_process);
+ }
+ }
+ }
+}
+
+TEST_F(PacedSenderTest, PaddingOveruse) {
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+ const int32_t kPacketSize = 1200;
+
+ // Min bitrate 0 => no padding, padding budget will stay at 0.
+ send_bucket_->UpdateBitrate(60, 90, 0);
+ SendAndExpectPacket(PacedSender::kNormalPriority, ssrc, sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize, false);
+ send_bucket_->Process();
+
+ // Add 30kbit padding. When increasing budget, media budget will increase from
+ // negative (overuse) while padding budget will increase form 0.
+ clock_.AdvanceTimeMilliseconds(5);
+ send_bucket_->UpdateBitrate(60, 90, 30);
+
+ EXPECT_FALSE(send_bucket_->SendPacket(
+ PacedSender::kHighPriority, ssrc, sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize, false));
+
+ // Don't send padding if queue is non-empty, even if padding budget > 0.
+ EXPECT_CALL(callback_, TimeToSendPadding(_)).Times(0);
+ send_bucket_->Process();
+}
+
} // namespace test
} // namespace webrtc
diff --git a/modules/rtp_rtcp/source/rtp_sender.cc b/modules/rtp_rtcp/source/rtp_sender.cc
index d5412668..0438b9f7 100644
--- a/modules/rtp_rtcp/source/rtp_sender.cc
+++ b/modules/rtp_rtcp/source/rtp_sender.cc
@@ -50,12 +50,17 @@ RTPSender::RTPSender(const int32_t id,
FrameCountObserver* frame_count_observer,
SendSideDelayObserver* send_side_delay_observer)
: clock_(clock),
+ // TODO(holmer): Remove this conversion when we remove the use of
+ // TickTime.
+ clock_delta_ms_(clock_->TimeInMilliseconds() -
+ TickTime::MillisecondTimestamp()),
bitrate_sent_(clock, this),
id_(id),
audio_configured_(audio),
audio_(NULL),
video_(NULL),
paced_sender_(paced_sender),
+ last_capture_time_ms_sent_(0),
send_critsect_(CriticalSectionWrapper::CreateCriticalSection()),
transport_(transport),
sending_media_(true), // Default to sending media.
@@ -622,15 +627,10 @@ int32_t RTPSender::ReSendPacket(uint16_t packet_id, uint32_t min_resend_time) {
}
// Convert from TickTime to Clock since capture_time_ms is based on
// TickTime.
- // TODO(holmer): Remove this conversion when we remove the use of TickTime.
- int64_t clock_delta_ms = clock_->TimeInMilliseconds() -
- TickTime::MillisecondTimestamp();
- if (!paced_sender_->SendPacket(PacedSender::kHighPriority,
- header.ssrc,
- header.sequenceNumber,
- capture_time_ms + clock_delta_ms,
- length - header.headerLength,
- true)) {
+ int64_t corrected_capture_tims_ms = capture_time_ms + clock_delta_ms_;
+ if (!paced_sender_->SendPacket(
+ PacedSender::kHighPriority, header.ssrc, header.sequenceNumber,
+ corrected_capture_tims_ms, length - header.headerLength, true)) {
// We can't send the packet right now.
// We will be called when it is time.
return length;
@@ -819,6 +819,10 @@ bool RTPSender::PrepareAndSendPacket(uint8_t* buffer,
RtpUtility::RtpHeaderParser rtp_parser(buffer, length);
RTPHeader rtp_header;
rtp_parser.Parse(rtp_header);
+ if (!is_retransmit && rtp_header.markerBit) {
+ TRACE_EVENT_ASYNC_END0("webrtc_rtp", "PacedSend", capture_time_ms);
+ }
+
TRACE_EVENT_INSTANT2("webrtc_rtp", "PrepareAndSendPacket",
"timestamp", rtp_header.timestamp,
"seqnum", rtp_header.sequenceNumber);
@@ -937,12 +941,18 @@ int32_t RTPSender::SendToNetwork(
}
if (paced_sender_ && storage != kDontStore) {
- int64_t clock_delta_ms = clock_->TimeInMilliseconds() -
- TickTime::MillisecondTimestamp();
+ // Correct offset between implementations of millisecond time stamps in
+ // TickTime and Clock.
+ int64_t corrected_time_ms = capture_time_ms + clock_delta_ms_;
if (!paced_sender_->SendPacket(priority, rtp_header.ssrc,
- rtp_header.sequenceNumber,
- capture_time_ms + clock_delta_ms,
+ rtp_header.sequenceNumber, corrected_time_ms,
payload_length, false)) {
+ if (last_capture_time_ms_sent_ == 0 ||
+ corrected_time_ms > last_capture_time_ms_sent_) {
+ last_capture_time_ms_sent_ = corrected_time_ms;
+ TRACE_EVENT_ASYNC_BEGIN1("webrtc_rtp", "PacedSend", corrected_time_ms,
+ "capture_time_ms", corrected_time_ms);
+ }
// We can't send the packet right now.
// We will be called when it is time.
return 0;
diff --git a/modules/rtp_rtcp/source/rtp_sender.h b/modules/rtp_rtcp/source/rtp_sender.h
index b2f2e0c4..780baa1f 100644
--- a/modules/rtp_rtcp/source/rtp_sender.h
+++ b/modules/rtp_rtcp/source/rtp_sender.h
@@ -336,6 +336,7 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer {
bool IsFecPacket(const uint8_t* buffer, const RTPHeader& header) const;
Clock* clock_;
+ int64_t clock_delta_ms_;
Bitrate bitrate_sent_;
int32_t id_;
@@ -344,6 +345,7 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer {
RTPSenderVideo *video_;
PacedSender *paced_sender_;
+ int64_t last_capture_time_ms_sent_;
CriticalSectionWrapper *send_critsect_;
Transport *transport_;
diff --git a/video/end_to_end_tests.cc b/video/end_to_end_tests.cc
index 06cb187c..96249c3c 100644
--- a/video/end_to_end_tests.cc
+++ b/video/end_to_end_tests.cc
@@ -2140,4 +2140,39 @@ TEST_F(EndToEndTest, NewReceiveStreamsRespectNetworkDown) {
DestroyStreams();
}
+
+// TODO(pbos): Remove this regression test when VideoEngine is no longer used as
+// a backend. This is to test that we hand channels back properly.
+TEST_F(EndToEndTest, CanCreateAndDestroyManyVideoStreams) {
+ test::NullTransport transport;
+ scoped_ptr<Call> call(Call::Create(Call::Config(&transport)));
+ test::FakeDecoder fake_decoder;
+ test::FakeEncoder fake_encoder(Clock::GetRealTimeClock());
+ for (size_t i = 0; i < 100; ++i) {
+ VideoSendStream::Config send_config;
+ send_config.encoder_settings.encoder = &fake_encoder;
+ send_config.encoder_settings.payload_name = "FAKE";
+ send_config.encoder_settings.payload_type = 123;
+
+ VideoEncoderConfig encoder_config;
+ encoder_config.streams = test::CreateVideoStreams(1);
+ send_config.rtp.ssrcs.push_back(1);
+ VideoSendStream* send_stream =
+ call->CreateVideoSendStream(send_config, encoder_config);
+ call->DestroyVideoSendStream(send_stream);
+
+ VideoReceiveStream::Config receive_config;
+ receive_config.rtp.remote_ssrc = 1;
+ receive_config.rtp.local_ssrc = kReceiverLocalSsrc;
+ VideoReceiveStream::Decoder decoder;
+ decoder.decoder = &fake_decoder;
+ decoder.payload_type = 123;
+ decoder.payload_name = "FAKE";
+ receive_config.decoders.push_back(decoder);
+ VideoReceiveStream* receive_stream =
+ call->CreateVideoReceiveStream(receive_config);
+ call->DestroyVideoReceiveStream(receive_stream);
+ }
+}
+
} // namespace webrtc
diff --git a/video/loopback.cc b/video/loopback.cc
index 4d3393e7..4b49c31e 100644
--- a/video/loopback.cc
+++ b/video/loopback.cc
@@ -22,13 +22,18 @@
#include "webrtc/test/direct_transport.h"
#include "webrtc/test/encoder_settings.h"
#include "webrtc/test/fake_encoder.h"
+#include "webrtc/test/field_trial.h"
#include "webrtc/test/run_loop.h"
#include "webrtc/test/run_test.h"
+#include "webrtc/test/testsupport/trace_to_stderr.h"
#include "webrtc/test/video_capturer.h"
#include "webrtc/test/video_renderer.h"
#include "webrtc/typedefs.h"
namespace webrtc {
+
+static const int kAbsSendTimeExtensionId = 7;
+
namespace flags {
DEFINE_int32(width, 640, "Video width.");
@@ -82,6 +87,16 @@ DEFINE_int32(std_propagation_delay_ms,
int StdPropagationDelayMs() {
return static_cast<int>(FLAGS_std_propagation_delay_ms);
}
+
+DEFINE_bool(logs, false, "print logs to stderr");
+
+DEFINE_string(
+ force_fieldtrials,
+ "",
+ "Field trials control experimental feature code which can be forced. "
+ "E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/"
+ " will assign the group Enable to field trial WebRTC-FooFeature. Multiple "
+ "trials are separated by \"/\"");
} // namespace flags
static const uint32_t kSendSsrc = 0x654321;
@@ -91,6 +106,10 @@ static const uint32_t kReceiverLocalSsrc = 0x123456;
static const uint8_t kRtxPayloadType = 96;
void Loopback() {
+ scoped_ptr<test::TraceToStderr> trace_to_stderr_;
+ if (webrtc::flags::FLAGS_logs)
+ trace_to_stderr_.reset(new test::TraceToStderr);
+
scoped_ptr<test::VideoRenderer> local_preview(test::VideoRenderer::Create(
"Local Preview", flags::Width(), flags::Height()));
scoped_ptr<test::VideoRenderer> loopback_video(test::VideoRenderer::Create(
@@ -116,6 +135,8 @@ void Loopback() {
send_config.rtp.rtx.ssrcs.push_back(kSendRtxSsrc);
send_config.rtp.rtx.payload_type = kRtxPayloadType;
send_config.rtp.nack.rtp_history_ms = 1000;
+ send_config.rtp.extensions.push_back(
+ RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeExtensionId));
send_config.local_renderer = local_preview.get();
scoped_ptr<VideoEncoder> encoder;
@@ -158,6 +179,8 @@ void Loopback() {
receive_config.rtp.nack.rtp_history_ms = 1000;
receive_config.rtp.rtx[kRtxPayloadType].ssrc = kSendRtxSsrc;
receive_config.rtp.rtx[kRtxPayloadType].payload_type = kRtxPayloadType;
+ receive_config.rtp.extensions.push_back(
+ RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeExtensionId));
receive_config.renderer = loopback_video.get();
VideoReceiveStream::Decoder decoder =
test::CreateMatchingDecoder(send_config.encoder_settings);
@@ -188,7 +211,8 @@ void Loopback() {
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
-
+ webrtc::test::InitFieldTrialsFromString(
+ webrtc::flags::FLAGS_force_fieldtrials);
webrtc::test::RunTest(webrtc::Loopback);
return 0;
}
diff --git a/video/video_receive_stream.cc b/video/video_receive_stream.cc
index b1822402..5b085bb8 100644
--- a/video/video_receive_stream.cc
+++ b/video/video_receive_stream.cc
@@ -222,7 +222,6 @@ VideoReceiveStream::~VideoReceiveStream() {
video_engine_base_->SetVoiceEngine(NULL);
image_process_->Release();
- video_engine_base_->Release();
external_codec_->Release();
codec_->DeregisterDecoderObserver(channel_);
rtp_rtcp_->DeregisterReceiveChannelRtpStatisticsCallback(channel_,
@@ -233,6 +232,8 @@ VideoReceiveStream::~VideoReceiveStream() {
network_->Release();
render_->Release();
rtp_rtcp_->Release();
+ video_engine_base_->DeleteChannel(channel_);
+ video_engine_base_->Release();
}
void VideoReceiveStream::Start() {
diff --git a/video_engine/vie_encoder.cc b/video_engine/vie_encoder.cc
index 9d6da977..3cb0ae70 100644
--- a/video_engine/vie_encoder.cc
+++ b/video_engine/vie_encoder.cc
@@ -470,9 +470,31 @@ bool ViEEncoder::EncoderPaused() const {
std::max(static_cast<int>(target_delay_ms_ * kEncoderPausePacerMargin),
kMinPacingDelayMs);
}
+ if (paced_sender_->ExpectedQueueTimeMs() >
+ PacedSender::kDefaultMaxQueueLengthMs) {
+ // Too much data in pacer queue, drop frame.
+ return true;
+ }
return !network_is_transmitting_;
}
+void ViEEncoder::TraceFrameDropStart() {
+ // Start trace event only on the first frame after encoder is paused.
+ if (!encoder_paused_and_dropped_frame_) {
+ TRACE_EVENT_ASYNC_BEGIN0("webrtc", "EncoderPaused", this);
+ }
+ encoder_paused_and_dropped_frame_ = true;
+ return;
+}
+
+void ViEEncoder::TraceFrameDropEnd() {
+ // End trace event on first frame after encoder resumes, if frame was dropped.
+ if (encoder_paused_and_dropped_frame_) {
+ TRACE_EVENT_ASYNC_END0("webrtc", "EncoderPaused", this);
+ }
+ encoder_paused_and_dropped_frame_ = false;
+}
+
RtpRtcp* ViEEncoder::SendRtpRtcpModule() {
return default_rtp_rtcp_.get();
}
@@ -489,16 +511,10 @@ void ViEEncoder::DeliverFrame(int id,
CriticalSectionScoped cs(data_cs_.get());
time_of_last_incoming_frame_ms_ = TickTime::MillisecondTimestamp();
if (EncoderPaused()) {
- if (!encoder_paused_and_dropped_frame_) {
- TRACE_EVENT_ASYNC_BEGIN0("webrtc", "EncoderPaused", this);
- }
- encoder_paused_and_dropped_frame_ = true;
+ TraceFrameDropStart();
return;
}
- if (encoder_paused_and_dropped_frame_) {
- TRACE_EVENT_ASYNC_END0("webrtc", "EncoderPaused", this);
- }
- encoder_paused_and_dropped_frame_ = false;
+ TraceFrameDropEnd();
}
// Convert render time, in ms, to RTP timestamp.
@@ -702,15 +718,10 @@ void ViEEncoder::SetSenderBufferingMode(int target_delay_ms) {
// Disable external frame-droppers.
vcm_.EnableFrameDropper(false);
vpm_.EnableTemporalDecimation(false);
- // We don't put any limits on the pacer queue when running in buffered mode
- // since the encoder will be paused if the queue grow too large.
- paced_sender_->set_max_queue_length_ms(-1);
} else {
// Real-time mode - enable frame droppers.
vpm_.EnableTemporalDecimation(true);
vcm_.EnableFrameDropper(true);
- paced_sender_->set_max_queue_length_ms(
- PacedSender::kDefaultMaxQueueLengthMs);
}
}
diff --git a/video_engine/vie_encoder.h b/video_engine/vie_encoder.h
index 36f87faa..1e358def 100644
--- a/video_engine/vie_encoder.h
+++ b/video_engine/vie_encoder.h
@@ -192,6 +192,8 @@ class ViEEncoder
int TimeToSendPadding(int bytes);
private:
bool EncoderPaused() const EXCLUSIVE_LOCKS_REQUIRED(data_cs_);
+ void TraceFrameDropStart() EXCLUSIVE_LOCKS_REQUIRED(data_cs_);
+ void TraceFrameDropEnd() EXCLUSIVE_LOCKS_REQUIRED(data_cs_);
void UpdateHistograms();
diff --git a/webrtc_tests.gypi b/webrtc_tests.gypi
index 33e509fa..dc17e707 100644
--- a/webrtc_tests.gypi
+++ b/webrtc_tests.gypi
@@ -59,6 +59,7 @@
'<(webrtc_root)/modules/modules.gyp:video_capture_module_internal_impl',
'<(webrtc_root)/modules/modules.gyp:video_render_module_impl',
'<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers_default',
+ 'test/test.gyp:test_main',
'webrtc',
],
},