Adds support for sending redundant payloads over RTX.
TEST=trybots BUG=1812 R=mflodman@webrtc.org, pbos@webrtc.org Review URL: https://webrtc-codereview.appspot.com/4169004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5209 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
9523b55826
commit
7e9315b42e
@ -1,3 +1,3 @@
|
||||
# Tests that are too slow.
|
||||
RampUpTest/*
|
||||
RampUpTest.*
|
||||
CallTest.PlaysOutAudioAndVideoInSync
|
@ -15,6 +15,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/call.h"
|
||||
#include "webrtc/common.h"
|
||||
#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
|
||||
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
|
||||
#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h"
|
||||
@ -148,7 +149,7 @@ namespace internal {
|
||||
TraceDispatcher* global_trace_dispatcher = NULL;
|
||||
} // internal
|
||||
|
||||
Call* Call::Create(const Call::Config& config) {
|
||||
void CreateTraceDispatcher() {
|
||||
if (internal::global_trace_dispatcher == NULL) {
|
||||
TraceDispatcher* dispatcher = new TraceDispatcher();
|
||||
// TODO(pbos): Atomic compare and exchange.
|
||||
@ -158,8 +159,13 @@ Call* Call::Create(const Call::Config& config) {
|
||||
delete dispatcher;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoEngine* video_engine = VideoEngine::Create();
|
||||
Call* Call::Create(const Call::Config& config) {
|
||||
CreateTraceDispatcher();
|
||||
|
||||
VideoEngine* video_engine = config.webrtc_config != NULL ?
|
||||
VideoEngine::Create(*config.webrtc_config) : VideoEngine::Create();
|
||||
assert(video_engine != NULL);
|
||||
|
||||
return new internal::Call(video_engine, config);
|
||||
|
@ -38,12 +38,15 @@ class Call {
|
||||
public:
|
||||
struct Config {
|
||||
explicit Config(newapi::Transport* send_transport)
|
||||
: send_transport(send_transport),
|
||||
: webrtc_config(NULL),
|
||||
send_transport(send_transport),
|
||||
overuse_detection(false),
|
||||
voice_engine(NULL),
|
||||
trace_callback(NULL),
|
||||
trace_filter(kTraceDefault) {}
|
||||
|
||||
webrtc::Config* webrtc_config;
|
||||
|
||||
newapi::Transport* send_transport;
|
||||
bool overuse_detection;
|
||||
|
||||
@ -56,6 +59,9 @@ class Call {
|
||||
|
||||
static Call* Create(const Call::Config& config);
|
||||
|
||||
static Call* Create(const Call::Config& config,
|
||||
const webrtc::Config& webrtc_config);
|
||||
|
||||
virtual std::vector<VideoCodec> GetVideoCodecs() = 0;
|
||||
|
||||
virtual VideoSendStream::Config GetDefaultSendConfig() = 0;
|
||||
|
25
webrtc/experiments.h
Normal file
25
webrtc/experiments.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2013 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_EXPERIMENTS_H_
|
||||
#define WEBRTC_EXPERIMENTS_H_
|
||||
|
||||
namespace webrtc {
|
||||
struct PaddingStrategy {
|
||||
PaddingStrategy()
|
||||
: redundant_payloads(false) {}
|
||||
explicit PaddingStrategy(bool redundant_payloads)
|
||||
: redundant_payloads(redundant_payloads) {}
|
||||
virtual ~PaddingStrategy() {}
|
||||
|
||||
const bool redundant_payloads;
|
||||
};
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_EXPERIMENTS_H_
|
@ -251,7 +251,7 @@ class RtpRtcp : public Module {
|
||||
/*
|
||||
* Turn on/off sending RTX (RFC 4588) on a specific SSRC.
|
||||
*/
|
||||
virtual int32_t SetRTXSendStatus(RtxMode mode, bool set_ssrc,
|
||||
virtual int32_t SetRTXSendStatus(int modes, bool set_ssrc,
|
||||
uint32_t ssrc) = 0;
|
||||
|
||||
// Sets the payload type to use when sending RTX packets. Note that this
|
||||
@ -261,7 +261,7 @@ class RtpRtcp : public Module {
|
||||
/*
|
||||
* Get status of sending RTX (RFC 4588) on a specific SSRC.
|
||||
*/
|
||||
virtual int32_t RTXSendStatus(RtxMode* mode, uint32_t* ssrc,
|
||||
virtual int32_t RTXSendStatus(int* modes, uint32_t* ssrc,
|
||||
int* payloadType) const = 0;
|
||||
|
||||
/*
|
||||
|
@ -129,9 +129,10 @@ enum RetransmissionMode {
|
||||
};
|
||||
|
||||
enum RtxMode {
|
||||
kRtxOff = 0,
|
||||
kRtxRetransmitted = 1, // Apply RTX only to retransmitted packets.
|
||||
kRtxAll = 2 // Apply RTX to all packets (source + retransmissions).
|
||||
kRtxOff = 0x0,
|
||||
kRtxRetransmitted = 0x1, // Only send retransmissions over RTX.
|
||||
kRtxRedundantPayloads = 0x2 // Preventively send redundant payloads
|
||||
// instead of padding.
|
||||
};
|
||||
|
||||
const int kRtxHeaderSize = 2;
|
||||
|
@ -85,9 +85,9 @@ class MockRtpRtcp : public RtpRtcp {
|
||||
MOCK_METHOD1(SetCSRCStatus,
|
||||
int32_t(const bool include));
|
||||
MOCK_METHOD3(SetRTXSendStatus,
|
||||
int32_t(RtxMode mode, bool setSSRC, uint32_t ssrc));
|
||||
int32_t(int modes, bool setSSRC, uint32_t ssrc));
|
||||
MOCK_CONST_METHOD3(RTXSendStatus,
|
||||
int32_t(RtxMode* mode, uint32_t* ssrc, int* payload_type));
|
||||
int32_t(int* modes, uint32_t* ssrc, int* payload_type));
|
||||
MOCK_METHOD1(SetRtxSendPayloadType,
|
||||
void(int));
|
||||
MOCK_METHOD1(SetSendingStatus,
|
||||
|
@ -343,27 +343,3 @@ TEST_F(RtpRtcpRtxNackTest, RtxNack) {
|
||||
EXPECT_EQ(kTestNumberOfRtxPackets, transport_.count_rtx_ssrc_);
|
||||
EXPECT_TRUE(ExpectedPacketsReceived());
|
||||
}
|
||||
|
||||
TEST_F(RtpRtcpRtxNackTest, RTXAllNoLoss) {
|
||||
RunRtxTest(kRtxAll, 0);
|
||||
EXPECT_EQ(kTestSequenceNumber, *(receiver_.sequence_numbers_.begin()));
|
||||
EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1,
|
||||
*(receiver_.sequence_numbers_.rbegin()));
|
||||
// We have transmitted all packets twice, and loss was set to 0.
|
||||
EXPECT_EQ(kTestNumberOfPackets * 2u, receiver_.sequence_numbers_.size());
|
||||
// Half of the packets should be via RTX.
|
||||
EXPECT_EQ(static_cast<int>(kTestNumberOfPackets),
|
||||
transport_.count_rtx_ssrc_);
|
||||
}
|
||||
|
||||
TEST_F(RtpRtcpRtxNackTest, RTXAllWithLoss) {
|
||||
int loss = 10;
|
||||
RunRtxTest(kRtxAll, loss);
|
||||
EXPECT_EQ(kTestSequenceNumber, *(receiver_.sequence_numbers_.begin()));
|
||||
EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1,
|
||||
*(receiver_.sequence_numbers_.rbegin()));
|
||||
// Got everything but lost packets.
|
||||
EXPECT_EQ(2u * (kTestNumberOfPackets - kTestNumberOfPackets / loss),
|
||||
receiver_.sequence_numbers_.size());
|
||||
EXPECT_EQ(static_cast<int>(kTestNumberOfPackets), transport_.count_rtx_ssrc_);
|
||||
}
|
||||
|
@ -11,7 +11,10 @@
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_packet_history.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h> // memset
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h"
|
||||
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
|
||||
@ -19,6 +22,8 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
enum { kMinPacketRequestBytes = 50 };
|
||||
|
||||
RTPPacketHistory::RTPPacketHistory(Clock* clock)
|
||||
: clock_(clock),
|
||||
critsect_(CriticalSectionWrapper::CreateCriticalSection()),
|
||||
@ -55,7 +60,7 @@ void RTPPacketHistory::Allocate(uint16_t number_to_store) {
|
||||
stored_seq_nums_.resize(number_to_store);
|
||||
stored_lengths_.resize(number_to_store);
|
||||
stored_times_.resize(number_to_store);
|
||||
stored_resend_times_.resize(number_to_store);
|
||||
stored_send_times_.resize(number_to_store);
|
||||
stored_types_.resize(number_to_store);
|
||||
}
|
||||
|
||||
@ -74,7 +79,7 @@ void RTPPacketHistory::Free() {
|
||||
stored_seq_nums_.clear();
|
||||
stored_lengths_.clear();
|
||||
stored_times_.clear();
|
||||
stored_resend_times_.clear();
|
||||
stored_send_times_.clear();
|
||||
stored_types_.clear();
|
||||
|
||||
store_ = false;
|
||||
@ -139,9 +144,9 @@ int32_t RTPPacketHistory::PutRTPPacket(const uint8_t* packet,
|
||||
|
||||
stored_seq_nums_[prev_index_] = seq_num;
|
||||
stored_lengths_[prev_index_] = packet_length;
|
||||
stored_times_[prev_index_] =
|
||||
(capture_time_ms > 0) ? capture_time_ms : clock_->TimeInMilliseconds();
|
||||
stored_resend_times_[prev_index_] = 0; // packet not resent
|
||||
stored_times_[prev_index_] = (capture_time_ms > 0) ? capture_time_ms :
|
||||
clock_->TimeInMilliseconds();
|
||||
stored_send_times_[prev_index_] = 0; // Packet not sent.
|
||||
stored_types_[prev_index_] = type;
|
||||
|
||||
++prev_index_;
|
||||
@ -211,12 +216,12 @@ bool RTPPacketHistory::HasRTPPacket(uint16_t sequence_number) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RTPPacketHistory::GetRTPPacket(uint16_t sequence_number,
|
||||
uint32_t min_elapsed_time_ms,
|
||||
uint8_t* packet,
|
||||
uint16_t* packet_length,
|
||||
int64_t* stored_time_ms,
|
||||
StorageType* type) const {
|
||||
bool RTPPacketHistory::GetPacketAndSetSendTime(uint16_t sequence_number,
|
||||
uint32_t min_elapsed_time_ms,
|
||||
bool retransmit,
|
||||
uint8_t* packet,
|
||||
uint16_t* packet_length,
|
||||
int64_t* stored_time_ms) {
|
||||
CriticalSectionScoped cs(critsect_);
|
||||
if (!store_) {
|
||||
return false;
|
||||
@ -237,46 +242,58 @@ bool RTPPacketHistory::GetRTPPacket(uint16_t sequence_number,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length > *packet_length) {
|
||||
if (length > *packet_length) {
|
||||
WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1,
|
||||
"Input buffer too short for packet %u", sequence_number);
|
||||
"Input buffer too short for packet %u", sequence_number);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify elapsed time since last retrieve.
|
||||
int64_t now = clock_->TimeInMilliseconds();
|
||||
if (min_elapsed_time_ms > 0 &&
|
||||
((now - stored_resend_times_.at(index)) < min_elapsed_time_ms)) {
|
||||
((now - stored_send_times_.at(index)) < min_elapsed_time_ms)) {
|
||||
WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1,
|
||||
"Skip getting packet %u, packet recently resent.", sequence_number);
|
||||
*packet_length = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get packet.
|
||||
std::vector<std::vector<uint8_t> >::const_iterator it_found_packet =
|
||||
stored_packets_.begin() + index;
|
||||
std::copy(it_found_packet->begin(), it_found_packet->begin() + length, packet);
|
||||
*packet_length = stored_lengths_.at(index);
|
||||
*stored_time_ms = stored_times_.at(index);
|
||||
*type = stored_types_.at(index);
|
||||
if (length == 0 ||
|
||||
(retransmit && stored_types_.at(index) == kDontRetransmit)) {
|
||||
// No bytes copied since this packet shouldn't be retransmitted or is
|
||||
// of zero size.
|
||||
return false;
|
||||
}
|
||||
stored_send_times_[index] = clock_->TimeInMilliseconds();
|
||||
GetPacket(index, packet, packet_length, stored_time_ms);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RTPPacketHistory::UpdateResendTime(uint16_t sequence_number) {
|
||||
CriticalSectionScoped cs(critsect_);
|
||||
if (!store_) {
|
||||
return;
|
||||
}
|
||||
void RTPPacketHistory::GetPacket(int index,
|
||||
uint8_t* packet,
|
||||
uint16_t* packet_length,
|
||||
int64_t* stored_time_ms) const {
|
||||
// Get packet.
|
||||
uint16_t length = stored_lengths_.at(index);
|
||||
std::vector<std::vector<uint8_t> >::const_iterator it_found_packet =
|
||||
stored_packets_.begin() + index;
|
||||
std::copy(it_found_packet->begin(), it_found_packet->begin() + length,
|
||||
packet);
|
||||
*packet_length = length;
|
||||
*stored_time_ms = stored_times_.at(index);
|
||||
}
|
||||
|
||||
int32_t index = 0;
|
||||
bool found = FindSeqNum(sequence_number, &index);
|
||||
if (!found) {
|
||||
WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1,
|
||||
"Failed to update resend time, seq num: %u.", sequence_number);
|
||||
return;
|
||||
}
|
||||
stored_resend_times_[index] = clock_->TimeInMilliseconds();
|
||||
bool RTPPacketHistory::GetBestFittingPacket(uint8_t* packet,
|
||||
uint16_t* packet_length,
|
||||
int64_t* stored_time_ms) {
|
||||
CriticalSectionScoped cs(critsect_);
|
||||
if (!store_)
|
||||
return false;
|
||||
int index = FindBestFittingPacket(*packet_length);
|
||||
if (index < 0)
|
||||
return false;
|
||||
GetPacket(index, packet, packet_length, stored_time_ms);
|
||||
return true;
|
||||
}
|
||||
|
||||
// private, lock should already be taken
|
||||
@ -313,4 +330,23 @@ bool RTPPacketHistory::FindSeqNum(uint16_t sequence_number,
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int RTPPacketHistory::FindBestFittingPacket(uint16_t size) const {
|
||||
if (size < kMinPacketRequestBytes || stored_lengths_.empty())
|
||||
return -1;
|
||||
int min_diff = -1;
|
||||
size_t best_index = 0;
|
||||
for (size_t i = 0; i < stored_lengths_.size(); ++i) {
|
||||
if (stored_lengths_[i] == 0)
|
||||
continue;
|
||||
int diff = abs(stored_lengths_[i] - size);
|
||||
if (min_diff < 0 || diff < min_diff) {
|
||||
min_diff = diff;
|
||||
best_index = i;
|
||||
}
|
||||
}
|
||||
if (min_diff < 0)
|
||||
return -1;
|
||||
return best_index;
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
@ -59,22 +59,26 @@ class RTPPacketHistory {
|
||||
// copied.
|
||||
// stored_time_ms: returns the time when the packet was stored.
|
||||
// type: returns the storage type set in PutRTPPacket.
|
||||
bool GetRTPPacket(uint16_t sequence_number,
|
||||
uint32_t min_elapsed_time_ms,
|
||||
uint8_t* packet,
|
||||
uint16_t* packet_length,
|
||||
int64_t* stored_time_ms,
|
||||
StorageType* type) const;
|
||||
bool GetPacketAndSetSendTime(uint16_t sequence_number,
|
||||
uint32_t min_elapsed_time_ms,
|
||||
bool retransmit,
|
||||
uint8_t* packet,
|
||||
uint16_t* packet_length,
|
||||
int64_t* stored_time_ms);
|
||||
|
||||
bool GetBestFittingPacket(uint8_t* packet, uint16_t* packet_length,
|
||||
int64_t* stored_time_ms);
|
||||
|
||||
bool HasRTPPacket(uint16_t sequence_number) const;
|
||||
|
||||
void UpdateResendTime(uint16_t sequence_number);
|
||||
|
||||
private:
|
||||
void GetPacket(int index, uint8_t* packet, uint16_t* packet_length,
|
||||
int64_t* stored_time_ms) const;
|
||||
void Allocate(uint16_t number_to_store);
|
||||
void Free();
|
||||
void VerifyAndAllocatePacketLength(uint16_t packet_length);
|
||||
bool FindSeqNum(uint16_t sequence_number, int32_t* index) const;
|
||||
int FindBestFittingPacket(uint16_t size) const;
|
||||
|
||||
private:
|
||||
Clock* clock_;
|
||||
@ -87,7 +91,7 @@ class RTPPacketHistory {
|
||||
std::vector<uint16_t> stored_seq_nums_;
|
||||
std::vector<uint16_t> stored_lengths_;
|
||||
std::vector<int64_t> stored_times_;
|
||||
std::vector<int64_t> stored_resend_times_;
|
||||
std::vector<int64_t> stored_send_times_;
|
||||
std::vector<StorageType> stored_types_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
@ -74,8 +74,8 @@ TEST_F(RtpPacketHistoryTest, NoStoreStatus) {
|
||||
// Packet should not be stored.
|
||||
len = kMaxPacketLength;
|
||||
int64_t time;
|
||||
StorageType type;
|
||||
EXPECT_FALSE(hist_->GetRTPPacket(kSeqNum, 0, packet_, &len, &time, &type));
|
||||
EXPECT_FALSE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_, &len,
|
||||
&time));
|
||||
}
|
||||
|
||||
TEST_F(RtpPacketHistoryTest, DontStore) {
|
||||
@ -89,8 +89,8 @@ TEST_F(RtpPacketHistoryTest, DontStore) {
|
||||
// Packet should not be stored.
|
||||
len = kMaxPacketLength;
|
||||
int64_t time;
|
||||
StorageType type;
|
||||
EXPECT_FALSE(hist_->GetRTPPacket(kSeqNum, 0, packet_, &len, &time, &type));
|
||||
EXPECT_FALSE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_, &len,
|
||||
&time));
|
||||
}
|
||||
|
||||
TEST_F(RtpPacketHistoryTest, PutRtpPacket_TooLargePacketLength) {
|
||||
@ -112,17 +112,16 @@ TEST_F(RtpPacketHistoryTest, GetRtpPacket_TooSmallBuffer) {
|
||||
capture_time_ms, kAllowRetransmission));
|
||||
uint16_t len_out = len - 1;
|
||||
int64_t time;
|
||||
StorageType type;
|
||||
EXPECT_FALSE(hist_->GetRTPPacket(kSeqNum, 0, packet_, &len_out, &time,
|
||||
&type));
|
||||
EXPECT_FALSE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_,
|
||||
&len_out, &time));
|
||||
}
|
||||
|
||||
TEST_F(RtpPacketHistoryTest, GetRtpPacket_NotStored) {
|
||||
hist_->SetStorePacketsStatus(true, 10);
|
||||
uint16_t len = kMaxPacketLength;
|
||||
int64_t time;
|
||||
StorageType type;
|
||||
EXPECT_FALSE(hist_->GetRTPPacket(0, 0, packet_, &len, &time, &type));
|
||||
EXPECT_FALSE(hist_->GetPacketAndSetSendTime(0, 0, false, packet_, &len,
|
||||
&time));
|
||||
}
|
||||
|
||||
TEST_F(RtpPacketHistoryTest, PutRtpPacket) {
|
||||
@ -147,11 +146,9 @@ TEST_F(RtpPacketHistoryTest, GetRtpPacket) {
|
||||
|
||||
uint16_t len_out = kMaxPacketLength;
|
||||
int64_t time;
|
||||
StorageType type;
|
||||
EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 0, packet_out_, &len_out, &time,
|
||||
&type));
|
||||
EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_out_,
|
||||
&len_out, &time));
|
||||
EXPECT_EQ(len, len_out);
|
||||
EXPECT_EQ(kAllowRetransmission, type);
|
||||
EXPECT_EQ(capture_time_ms, time);
|
||||
for (int i = 0; i < len; i++) {
|
||||
EXPECT_EQ(packet_[i], packet_out_[i]);
|
||||
@ -176,11 +173,9 @@ TEST_F(RtpPacketHistoryTest, ReplaceRtpHeader) {
|
||||
|
||||
uint16_t len_out = kMaxPacketLength;
|
||||
int64_t time;
|
||||
StorageType type;
|
||||
EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 0, packet_out_, &len_out, &time,
|
||||
&type));
|
||||
EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_out_,
|
||||
&len_out, &time));
|
||||
EXPECT_EQ(len, len_out);
|
||||
EXPECT_EQ(kAllowRetransmission, type);
|
||||
EXPECT_EQ(capture_time_ms, time);
|
||||
for (int i = 0; i < len; i++) {
|
||||
EXPECT_EQ(packet_[i], packet_out_[i]);
|
||||
@ -207,11 +202,9 @@ TEST_F(RtpPacketHistoryTest, NoCaptureTime) {
|
||||
|
||||
uint16_t len_out = kMaxPacketLength;
|
||||
int64_t time;
|
||||
StorageType type;
|
||||
EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 0, packet_out_, &len_out, &time,
|
||||
&type));
|
||||
EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_out_,
|
||||
&len_out, &time));
|
||||
EXPECT_EQ(len, len_out);
|
||||
EXPECT_EQ(kAllowRetransmission, type);
|
||||
EXPECT_EQ(capture_time_ms, time);
|
||||
for (int i = 0; i < len; i++) {
|
||||
EXPECT_EQ(packet_[i], packet_out_[i]);
|
||||
@ -228,11 +221,9 @@ TEST_F(RtpPacketHistoryTest, DontRetransmit) {
|
||||
|
||||
uint16_t len_out = kMaxPacketLength;
|
||||
int64_t time;
|
||||
StorageType type;
|
||||
EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 0, packet_out_, &len_out, &time,
|
||||
&type));
|
||||
EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_out_,
|
||||
&len_out, &time));
|
||||
EXPECT_EQ(len, len_out);
|
||||
EXPECT_EQ(kDontRetransmit, type);
|
||||
EXPECT_EQ(capture_time_ms, time);
|
||||
}
|
||||
|
||||
@ -244,20 +235,22 @@ TEST_F(RtpPacketHistoryTest, MinResendTime) {
|
||||
EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength,
|
||||
capture_time_ms, kAllowRetransmission));
|
||||
|
||||
hist_->UpdateResendTime(kSeqNum);
|
||||
int64_t time;
|
||||
EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 100, false, packet_, &len,
|
||||
&time));
|
||||
fake_clock_.AdvanceTimeMilliseconds(100);
|
||||
|
||||
// Time has elapsed.
|
||||
len = kMaxPacketLength;
|
||||
StorageType type;
|
||||
int64_t time;
|
||||
EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 100, packet_, &len, &time, &type));
|
||||
EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 100, false, packet_, &len,
|
||||
&time));
|
||||
EXPECT_GT(len, 0);
|
||||
EXPECT_EQ(capture_time_ms, time);
|
||||
|
||||
// Time has not elapsed. Packet should be found, but no bytes copied.
|
||||
len = kMaxPacketLength;
|
||||
EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 101, packet_, &len, &time, &type));
|
||||
EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 101, false, packet_, &len,
|
||||
&time));
|
||||
EXPECT_EQ(0, len);
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
@ -269,15 +269,13 @@ int32_t ModuleRtpRtcpImpl::Process() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ModuleRtpRtcpImpl::SetRTXSendStatus(RtxMode mode, bool set_ssrc,
|
||||
int32_t ModuleRtpRtcpImpl::SetRTXSendStatus(int mode, bool set_ssrc,
|
||||
uint32_t ssrc) {
|
||||
rtp_sender_.SetRTXStatus(mode, set_ssrc, ssrc);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ModuleRtpRtcpImpl::RTXSendStatus(RtxMode* mode, uint32_t* ssrc,
|
||||
int32_t ModuleRtpRtcpImpl::RTXSendStatus(int* mode, uint32_t* ssrc,
|
||||
int* payload_type) const {
|
||||
rtp_sender_.RTXStatus(mode, ssrc, payload_type);
|
||||
return 0;
|
||||
@ -1628,7 +1626,7 @@ int64_t ModuleRtpRtcpImpl::RtcpReportInterval() {
|
||||
void ModuleRtpRtcpImpl::SetRtcpReceiverSsrcs(uint32_t main_ssrc) {
|
||||
std::set<uint32_t> ssrcs;
|
||||
ssrcs.insert(main_ssrc);
|
||||
RtxMode rtx_mode = kRtxOff;
|
||||
int rtx_mode = kRtxOff;
|
||||
uint32_t rtx_ssrc = 0;
|
||||
int rtx_payload_type = 0;
|
||||
rtp_sender_.RTXStatus(&rtx_mode, &rtx_ssrc, &rtx_payload_type);
|
||||
|
@ -95,11 +95,11 @@ class ModuleRtpRtcpImpl : public RtpRtcp {
|
||||
|
||||
virtual uint32_t ByteCountSent() const;
|
||||
|
||||
virtual int32_t SetRTXSendStatus(const RtxMode mode,
|
||||
virtual int32_t SetRTXSendStatus(const int mode,
|
||||
const bool set_ssrc,
|
||||
const uint32_t ssrc) OVERRIDE;
|
||||
|
||||
virtual int32_t RTXSendStatus(RtxMode* mode, uint32_t* ssrc,
|
||||
virtual int32_t RTXSendStatus(int* mode, uint32_t* ssrc,
|
||||
int* payloadType) const OVERRIDE;
|
||||
|
||||
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
#include <stdlib.h> // srand
|
||||
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_packet_history.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_sender_audio.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_sender_video.h"
|
||||
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
|
||||
@ -54,7 +53,7 @@ RTPSender::RTPSender(const int32_t id, const bool audio, Clock *clock,
|
||||
transmission_time_offset_(0), absolute_send_time_(0),
|
||||
// NACK.
|
||||
nack_byte_count_times_(), nack_byte_count_(), nack_bitrate_(clock),
|
||||
packet_history_(new RTPPacketHistory(clock)),
|
||||
packet_history_(clock),
|
||||
// Statistics
|
||||
statistics_crit_(CriticalSectionWrapper::CreateCriticalSection()),
|
||||
packets_sent_(0), payload_bytes_sent_(0), start_time_stamp_forced_(false),
|
||||
@ -97,7 +96,6 @@ RTPSender::~RTPSender() {
|
||||
delete it->second;
|
||||
payload_type_map_.erase(it);
|
||||
}
|
||||
delete packet_history_;
|
||||
delete audio_;
|
||||
delete video_;
|
||||
|
||||
@ -271,7 +269,7 @@ uint16_t RTPSender::MaxPayloadLength() const {
|
||||
|
||||
uint16_t RTPSender::PacketOverHead() const { return packet_over_head_; }
|
||||
|
||||
void RTPSender::SetRTXStatus(RtxMode mode, bool set_ssrc, uint32_t ssrc) {
|
||||
void RTPSender::SetRTXStatus(int mode, bool set_ssrc, uint32_t ssrc) {
|
||||
CriticalSectionScoped cs(send_critsect_);
|
||||
rtx_ = mode;
|
||||
if (rtx_ != kRtxOff) {
|
||||
@ -283,7 +281,7 @@ void RTPSender::SetRTXStatus(RtxMode mode, bool set_ssrc, uint32_t ssrc) {
|
||||
}
|
||||
}
|
||||
|
||||
void RTPSender::RTXStatus(RtxMode* mode, uint32_t* ssrc,
|
||||
void RTPSender::RTXStatus(int* mode, uint32_t* ssrc,
|
||||
int* payload_type) const {
|
||||
CriticalSectionScoped cs(send_critsect_);
|
||||
*mode = rtx_;
|
||||
@ -389,6 +387,28 @@ int32_t RTPSender::SendOutgoingData(
|
||||
}
|
||||
}
|
||||
|
||||
int RTPSender::SendRedundantPayloads(int payload_type, int bytes_to_send) {
|
||||
if (!(rtx_ & kRtxRedundantPayloads))
|
||||
return 0;
|
||||
uint8_t buffer[IP_PACKET_SIZE];
|
||||
int bytes_left = bytes_to_send;
|
||||
while (bytes_left > 0) {
|
||||
uint16_t length = bytes_left;
|
||||
int64_t capture_time_ms;
|
||||
if (!packet_history_.GetBestFittingPacket(buffer, &length,
|
||||
&capture_time_ms)) {
|
||||
break;
|
||||
}
|
||||
if (!PrepareAndSendPacket(buffer, length, capture_time_ms, true))
|
||||
return -1;
|
||||
ModuleRTPUtility::RTPHeaderParser rtp_parser(buffer, length);
|
||||
RTPHeader rtp_header;
|
||||
rtp_parser.Parse(rtp_header);
|
||||
bytes_left -= length - rtp_header.headerLength;
|
||||
}
|
||||
return bytes_to_send - bytes_left;
|
||||
}
|
||||
|
||||
bool RTPSender::SendPaddingAccordingToBitrate(
|
||||
int8_t payload_type, uint32_t capture_timestamp,
|
||||
int64_t capture_time_ms) {
|
||||
@ -509,11 +529,11 @@ int RTPSender::SendPadData(int payload_type, uint32_t timestamp,
|
||||
|
||||
void RTPSender::SetStorePacketsStatus(const bool enable,
|
||||
const uint16_t number_to_store) {
|
||||
packet_history_->SetStorePacketsStatus(enable, number_to_store);
|
||||
packet_history_.SetStorePacketsStatus(enable, number_to_store);
|
||||
}
|
||||
|
||||
bool RTPSender::StorePackets() const {
|
||||
return packet_history_->StorePackets();
|
||||
return packet_history_.StorePackets();
|
||||
}
|
||||
|
||||
int32_t RTPSender::ReSendPacket(uint16_t packet_id, uint32_t min_resend_time) {
|
||||
@ -521,20 +541,12 @@ int32_t RTPSender::ReSendPacket(uint16_t packet_id, uint32_t min_resend_time) {
|
||||
uint8_t data_buffer[IP_PACKET_SIZE];
|
||||
uint8_t *buffer_to_send_ptr = data_buffer;
|
||||
int64_t capture_time_ms;
|
||||
StorageType type;
|
||||
if (!packet_history_->GetRTPPacket(packet_id, min_resend_time, data_buffer,
|
||||
&length, &capture_time_ms, &type)) {
|
||||
if (!packet_history_.GetPacketAndSetSendTime(packet_id, min_resend_time, true,
|
||||
data_buffer, &length,
|
||||
&capture_time_ms)) {
|
||||
// Packet not found.
|
||||
return 0;
|
||||
}
|
||||
if (length == 0 || type == kDontRetransmit) {
|
||||
// No bytes copied (packet recently resent, skip resending) or
|
||||
// packet should not be retransmitted.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Store the time when the packet was last sent or added to pacer.
|
||||
packet_history_->UpdateResendTime(packet_id);
|
||||
|
||||
{
|
||||
// Update send statistics prior to pacer.
|
||||
@ -567,7 +579,7 @@ int32_t RTPSender::ReSendPacket(uint16_t packet_id, uint32_t min_resend_time) {
|
||||
}
|
||||
|
||||
uint8_t data_buffer_rtx[IP_PACKET_SIZE];
|
||||
if (rtx_ != kRtxOff) {
|
||||
if ((rtx_ & kRtxRetransmitted) > 0) {
|
||||
BuildRtxPacket(data_buffer, &length, data_buffer_rtx);
|
||||
buffer_to_send_ptr = data_buffer_rtx;
|
||||
}
|
||||
@ -714,33 +726,39 @@ void RTPSender::UpdateNACKBitRate(const uint32_t bytes,
|
||||
bool RTPSender::TimeToSendPacket(uint16_t sequence_number,
|
||||
int64_t capture_time_ms,
|
||||
bool retransmission) {
|
||||
StorageType type;
|
||||
uint16_t length = IP_PACKET_SIZE;
|
||||
uint8_t data_buffer[IP_PACKET_SIZE];
|
||||
int64_t stored_time_ms;
|
||||
uint8_t *buffer_to_send_ptr = data_buffer;
|
||||
|
||||
if (packet_history_ == NULL) {
|
||||
if (!packet_history_.GetPacketAndSetSendTime(sequence_number,
|
||||
0,
|
||||
retransmission,
|
||||
data_buffer,
|
||||
&length,
|
||||
&stored_time_ms)) {
|
||||
// Packet cannot be found. Allow sending to continue.
|
||||
return true;
|
||||
}
|
||||
if (!packet_history_->GetRTPPacket(sequence_number, 0, data_buffer, &length,
|
||||
&stored_time_ms, &type)) {
|
||||
// Packet cannot be found. Allow sending to continue.
|
||||
return true;
|
||||
}
|
||||
assert(length > 0);
|
||||
return PrepareAndSendPacket(data_buffer, length, capture_time_ms,
|
||||
retransmission && (rtx_ & kRtxRetransmitted) > 0);
|
||||
}
|
||||
|
||||
ModuleRTPUtility::RTPHeaderParser rtp_parser(data_buffer, length);
|
||||
bool RTPSender::PrepareAndSendPacket(uint8_t* buffer,
|
||||
uint16_t length,
|
||||
int64_t capture_time_ms,
|
||||
bool send_over_rtx) {
|
||||
uint8_t *buffer_to_send_ptr = buffer;
|
||||
|
||||
ModuleRTPUtility::RTPHeaderParser rtp_parser(buffer, length);
|
||||
RTPHeader rtp_header;
|
||||
rtp_parser.Parse(rtp_header);
|
||||
TRACE_EVENT_INSTANT2("webrtc_rtp", "RTPSender::TimeToSendPacket",
|
||||
"timestamp", rtp_header.timestamp,
|
||||
"seqnum", sequence_number);
|
||||
"seqnum", rtp_header.sequenceNumber);
|
||||
|
||||
uint8_t data_buffer_rtx[IP_PACKET_SIZE];
|
||||
if (retransmission && rtx_ != kRtxOff) {
|
||||
BuildRtxPacket(data_buffer, &length, data_buffer_rtx);
|
||||
if (send_over_rtx) {
|
||||
BuildRtxPacket(buffer, &length, data_buffer_rtx);
|
||||
buffer_to_send_ptr = data_buffer_rtx;
|
||||
}
|
||||
|
||||
@ -753,9 +771,9 @@ bool RTPSender::TimeToSendPacket(uint16_t sequence_number,
|
||||
UpdateAbsoluteSendTime(buffer_to_send_ptr, length, rtp_header, now_ms);
|
||||
if (updated_transmission_time_offset || updated_abs_send_time) {
|
||||
// Update stored packet in case of receiving a re-transmission request.
|
||||
packet_history_->ReplaceRTPHeader(buffer_to_send_ptr,
|
||||
rtp_header.sequenceNumber,
|
||||
rtp_header.headerLength);
|
||||
packet_history_.ReplaceRTPHeader(buffer_to_send_ptr,
|
||||
rtp_header.sequenceNumber,
|
||||
rtp_header.headerLength);
|
||||
}
|
||||
return SendPacketToNetwork(buffer_to_send_ptr, length);
|
||||
}
|
||||
@ -769,7 +787,8 @@ int RTPSender::TimeToSendPadding(int bytes) {
|
||||
uint32_t timestamp;
|
||||
{
|
||||
CriticalSectionScoped cs(send_critsect_);
|
||||
payload_type = (rtx_ == kRtxOff) ? payload_type_ : payload_type_rtx_;
|
||||
payload_type = ((rtx_ & kRtxRedundantPayloads) > 0) ? payload_type_rtx_ :
|
||||
payload_type_;
|
||||
timestamp = timestamp_;
|
||||
capture_time_ms = capture_time_ms_;
|
||||
if (last_timestamp_time_ms_ > 0) {
|
||||
@ -779,8 +798,14 @@ int RTPSender::TimeToSendPadding(int bytes) {
|
||||
(clock_->TimeInMilliseconds() - last_timestamp_time_ms_);
|
||||
}
|
||||
}
|
||||
return SendPadData(payload_type, timestamp, capture_time_ms, bytes,
|
||||
kDontStore, true, true);
|
||||
int bytes_sent = SendRedundantPayloads(payload_type, bytes);
|
||||
bytes -= bytes_sent;
|
||||
if (bytes > 0) {
|
||||
int padding_sent = SendPadData(payload_type, timestamp, capture_time_ms,
|
||||
bytes, kDontStore, true, true);
|
||||
bytes_sent += padding_sent;
|
||||
}
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
// TODO(pwestin): send in the RTPHeaderParser to avoid parsing it again.
|
||||
@ -807,33 +832,17 @@ int32_t RTPSender::SendToNetwork(
|
||||
rtp_header, now_ms);
|
||||
|
||||
// Used for NACK and to spread out the transmission of packets.
|
||||
if (packet_history_->PutRTPPacket(buffer, rtp_header_length + payload_length,
|
||||
max_payload_length_, capture_time_ms,
|
||||
storage) != 0) {
|
||||
if (packet_history_.PutRTPPacket(buffer, rtp_header_length + payload_length,
|
||||
max_payload_length_, capture_time_ms,
|
||||
storage) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create and send RTX Packet.
|
||||
// TODO(pwesin): This should be moved to its own code path triggered by pacer.
|
||||
bool rtx_sent = false;
|
||||
if (rtx_ == kRtxAll && storage == kAllowRetransmission) {
|
||||
uint16_t length_rtx = payload_length + rtp_header_length;
|
||||
uint8_t data_buffer_rtx[IP_PACKET_SIZE];
|
||||
BuildRtxPacket(buffer, &length_rtx, data_buffer_rtx);
|
||||
if (!SendPacketToNetwork(data_buffer_rtx, length_rtx)) return -1;
|
||||
rtx_sent = true;
|
||||
}
|
||||
{
|
||||
// Update send statistics prior to pacer.
|
||||
CriticalSectionScoped lock(statistics_crit_.get());
|
||||
Bitrate::Update(payload_length + rtp_header_length);
|
||||
++packets_sent_;
|
||||
payload_bytes_sent_ += payload_length;
|
||||
if (rtx_sent) {
|
||||
// The RTX packet.
|
||||
++packets_sent_;
|
||||
payload_bytes_sent_ += payload_length;
|
||||
}
|
||||
}
|
||||
|
||||
if (paced_sender_ && storage != kDontStore) {
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/bitrate.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_header_extension.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_packet_history.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_rtcp_config.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/ssrc_database.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/video_codec_information.h"
|
||||
@ -30,7 +31,6 @@
|
||||
namespace webrtc {
|
||||
|
||||
class CriticalSectionWrapper;
|
||||
class RTPPacketHistory;
|
||||
class RTPSenderAudio;
|
||||
class RTPSenderVideo;
|
||||
|
||||
@ -134,10 +134,6 @@ class RTPSender : public Bitrate, public RTPSenderInterface {
|
||||
VideoCodecInformation *codec_info = NULL,
|
||||
const RTPVideoTypeHeader * rtp_type_hdr = NULL);
|
||||
|
||||
int BuildPaddingPacket(uint8_t* packet, int header_length, int32_t bytes);
|
||||
int SendPadData(int payload_type, uint32_t timestamp, int64_t capture_time_ms,
|
||||
int32_t bytes, StorageType store,
|
||||
bool force_full_size_packets, bool only_pad_after_markerbit);
|
||||
// RTP header extension
|
||||
int32_t SetTransmissionTimeOffset(
|
||||
const int32_t transmission_time_offset);
|
||||
@ -187,9 +183,9 @@ class RTPSender : public Bitrate, public RTPSenderInterface {
|
||||
bool ProcessNACKBitRate(const uint32_t now);
|
||||
|
||||
// RTX.
|
||||
void SetRTXStatus(RtxMode mode, bool set_ssrc, uint32_t ssrc);
|
||||
void SetRTXStatus(int mode, bool set_ssrc, uint32_t ssrc);
|
||||
|
||||
void RTXStatus(RtxMode* mode, uint32_t* ssrc, int* payload_type) const;
|
||||
void RTXStatus(int* mode, uint32_t* ssrc, int* payload_type) const;
|
||||
|
||||
void SetRtxPayloadType(int payloadType);
|
||||
|
||||
@ -276,9 +272,20 @@ class RTPSender : public Bitrate, public RTPSenderInterface {
|
||||
|
||||
void UpdateNACKBitRate(const uint32_t bytes, const uint32_t now);
|
||||
|
||||
bool PrepareAndSendPacket(uint8_t* buffer,
|
||||
uint16_t length,
|
||||
int64_t capture_time_ms,
|
||||
bool send_over_rtx);
|
||||
|
||||
int SendRedundantPayloads(int payload_type, int bytes);
|
||||
|
||||
bool SendPaddingAccordingToBitrate(int8_t payload_type,
|
||||
uint32_t capture_timestamp,
|
||||
int64_t capture_time_ms);
|
||||
int BuildPaddingPacket(uint8_t* packet, int header_length, int32_t bytes);
|
||||
int SendPadData(int payload_type, uint32_t timestamp, int64_t capture_time_ms,
|
||||
int32_t bytes, StorageType store,
|
||||
bool force_full_size_packets, bool only_pad_after_markerbit);
|
||||
|
||||
void BuildRtxPacket(uint8_t* buffer, uint16_t* length,
|
||||
uint8_t* buffer_rtx);
|
||||
@ -312,7 +319,7 @@ class RTPSender : public Bitrate, public RTPSenderInterface {
|
||||
int32_t nack_byte_count_[NACK_BYTECOUNT_SIZE];
|
||||
Bitrate nack_bitrate_;
|
||||
|
||||
RTPPacketHistory *packet_history_;
|
||||
RTPPacketHistory packet_history_;
|
||||
|
||||
// Statistics
|
||||
scoped_ptr<CriticalSectionWrapper> statistics_crit_;
|
||||
@ -336,7 +343,7 @@ class RTPSender : public Bitrate, public RTPSenderInterface {
|
||||
uint8_t num_csrcs_;
|
||||
uint32_t csrcs_[kRtpCsrcSize];
|
||||
bool include_csrcs_;
|
||||
RtxMode rtx_;
|
||||
int rtx_;
|
||||
uint32_t ssrc_rtx_;
|
||||
int payload_type_rtx_;
|
||||
};
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_header_extension.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtp_sender.h"
|
||||
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
||||
#include "webrtc/test/mock_transport.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
@ -113,6 +114,23 @@ class RtpSenderTest : public ::testing::Test {
|
||||
EXPECT_EQ(0, rtp_header.numCSRCs);
|
||||
EXPECT_EQ(0, rtp_header.paddingLength);
|
||||
}
|
||||
|
||||
void SendPacket(int64_t capture_time_ms, int payload_length) {
|
||||
uint32_t timestamp = capture_time_ms * 90;
|
||||
int32_t rtp_length = rtp_sender_->BuildRTPheader(packet_,
|
||||
kPayload,
|
||||
kMarkerBit,
|
||||
timestamp,
|
||||
capture_time_ms);
|
||||
|
||||
// Packet should be stored in a send bucket.
|
||||
EXPECT_EQ(0, rtp_sender_->SendToNetwork(packet_,
|
||||
payload_length,
|
||||
rtp_length,
|
||||
capture_time_ms,
|
||||
kAllowRetransmission,
|
||||
PacedSender::kNormalPriority));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RtpSenderTest, RegisterRtpTransmissionTimeOffsetHeaderExtension) {
|
||||
@ -582,6 +600,69 @@ TEST_F(RtpSenderTest, SendPadding) {
|
||||
EXPECT_EQ(expected_send_time, rtp_header.extension.absoluteSendTime);
|
||||
}
|
||||
|
||||
TEST_F(RtpSenderTest, SendRedundantPayloads) {
|
||||
MockTransport transport;
|
||||
rtp_sender_.reset(new RTPSender(0, false, &fake_clock_, &transport, NULL,
|
||||
&mock_paced_sender_));
|
||||
rtp_sender_->SetSequenceNumber(kSeqNum);
|
||||
// Make all packets go through the pacer.
|
||||
EXPECT_CALL(mock_paced_sender_,
|
||||
SendPacket(PacedSender::kNormalPriority, _, _, _, _, _)).
|
||||
WillRepeatedly(testing::Return(false));
|
||||
|
||||
uint16_t seq_num = kSeqNum;
|
||||
rtp_sender_->SetStorePacketsStatus(true, 10);
|
||||
int rtp_header_len = 12;
|
||||
EXPECT_EQ(0, rtp_sender_->RegisterRtpHeaderExtension(
|
||||
kRtpExtensionAbsoluteSendTime, kAbsoluteSendTimeExtensionId));
|
||||
rtp_header_len += 4; // 4 bytes extension.
|
||||
rtp_header_len += 4; // 4 extra bytes common to all extension headers.
|
||||
|
||||
rtp_sender_->SetRTXStatus(kRtxRetransmitted | kRtxRedundantPayloads, true,
|
||||
1234);
|
||||
|
||||
// Create and set up parser.
|
||||
scoped_ptr<webrtc::RtpHeaderParser> rtp_parser(
|
||||
webrtc::RtpHeaderParser::Create());
|
||||
ASSERT_TRUE(rtp_parser.get() != NULL);
|
||||
rtp_parser->RegisterRtpHeaderExtension(kRtpExtensionTransmissionTimeOffset,
|
||||
kTransmissionTimeOffsetExtensionId);
|
||||
rtp_parser->RegisterRtpHeaderExtension(kRtpExtensionAbsoluteSendTime,
|
||||
kAbsoluteSendTimeExtensionId);
|
||||
rtp_sender_->SetTargetSendBitrate(300000);
|
||||
const size_t kNumPayloadSizes = 10;
|
||||
const int kPayloadSizes[kNumPayloadSizes] = {500, 550, 600, 650, 700, 750,
|
||||
800, 850, 900, 950};
|
||||
// Send 10 packets of increasing size.
|
||||
for (size_t i = 0; i < kNumPayloadSizes; ++i) {
|
||||
int64_t capture_time_ms = fake_clock_.TimeInMilliseconds();
|
||||
EXPECT_CALL(transport, SendPacket(_, _, _))
|
||||
.WillOnce(testing::ReturnArg<2>());
|
||||
SendPacket(capture_time_ms, kPayloadSizes[i]);
|
||||
rtp_sender_->TimeToSendPacket(seq_num++, capture_time_ms, false);
|
||||
fake_clock_.AdvanceTimeMilliseconds(33);
|
||||
}
|
||||
const int kPaddingPayloadSize = 224;
|
||||
// The amount of padding to send it too small to send a payload packet.
|
||||
EXPECT_CALL(transport, SendPacket(_, _, kPaddingPayloadSize + rtp_header_len))
|
||||
.WillOnce(testing::ReturnArg<2>());
|
||||
EXPECT_EQ(kPaddingPayloadSize, rtp_sender_->TimeToSendPadding(49));
|
||||
|
||||
const int kRtxHeaderSize = 2;
|
||||
EXPECT_CALL(transport, SendPacket(_, _, kPayloadSizes[0] +
|
||||
rtp_header_len + kRtxHeaderSize))
|
||||
.WillOnce(testing::ReturnArg<2>());
|
||||
EXPECT_EQ(kPayloadSizes[0], rtp_sender_->TimeToSendPadding(500));
|
||||
|
||||
EXPECT_CALL(transport, SendPacket(_, _, kPayloadSizes[kNumPayloadSizes - 1] +
|
||||
rtp_header_len + kRtxHeaderSize))
|
||||
.WillOnce(testing::ReturnArg<2>());
|
||||
EXPECT_CALL(transport, SendPacket(_, _, kPaddingPayloadSize + rtp_header_len))
|
||||
.WillOnce(testing::ReturnArg<2>());
|
||||
EXPECT_EQ(kPayloadSizes[kNumPayloadSizes - 1] + kPaddingPayloadSize,
|
||||
rtp_sender_->TimeToSendPadding(999));
|
||||
}
|
||||
|
||||
TEST_F(RtpSenderTest, SendGenericVideo) {
|
||||
char payload_name[RTP_PAYLOAD_NAME_SIZE] = "GENERIC";
|
||||
const uint8_t payload_type = 127;
|
||||
|
@ -116,7 +116,7 @@ TEST_F(RtpRtcpAPITest, RTCP) {
|
||||
|
||||
TEST_F(RtpRtcpAPITest, RtxSender) {
|
||||
unsigned int ssrc = 0;
|
||||
RtxMode rtx_mode = kRtxOff;
|
||||
int rtx_mode = kRtxOff;
|
||||
const int kRtxPayloadType = 119;
|
||||
int payload_type = -1;
|
||||
EXPECT_EQ(0, module->SetRTXSendStatus(kRtxRetransmitted, true, 1));
|
||||
|
27
webrtc/test/mock_transport.h
Normal file
27
webrtc/test/mock_transport.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2013 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_TEST_MOCK_TRANSPORT_H_
|
||||
#define WEBRTC_TEST_MOCK_TRANSPORT_H_
|
||||
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "webrtc/transport.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class MockTransport : public webrtc::Transport {
|
||||
public:
|
||||
MOCK_METHOD3(SendPacket,
|
||||
int(int channel, const void* data, int len));
|
||||
MOCK_METHOD3(SendRTCPPacket,
|
||||
int(int channel, const void* data, int len));
|
||||
};
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_TEST_MOCK_TRANSPORT_H_
|
@ -34,6 +34,7 @@
|
||||
'mac/run_tests.mm',
|
||||
'mac/video_renderer_mac.h',
|
||||
'mac/video_renderer_mac.mm',
|
||||
'mock_transport.h',
|
||||
'null_platform_renderer.cc',
|
||||
'null_transport.cc',
|
||||
'null_transport.h',
|
||||
|
@ -10,13 +10,17 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
#include "webrtc/call.h"
|
||||
#include "webrtc/common.h"
|
||||
#include "webrtc/experiments.h"
|
||||
#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
|
||||
#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
|
||||
#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
|
||||
#include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h"
|
||||
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/rtcp_utility.h"
|
||||
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
|
||||
@ -26,18 +30,22 @@
|
||||
#include "webrtc/test/fake_decoder.h"
|
||||
#include "webrtc/test/fake_encoder.h"
|
||||
#include "webrtc/test/frame_generator_capturer.h"
|
||||
#include "webrtc/test/testsupport/perf_test.h"
|
||||
#include "webrtc/video/transport_adapter.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
static const int kTOffsetExtensionId = 7;
|
||||
static const int kMaxPacketSize = 1500;
|
||||
}
|
||||
|
||||
class StreamObserver : public newapi::Transport, public RemoteBitrateObserver {
|
||||
public:
|
||||
typedef std::map<uint32_t, int> BytesSentMap;
|
||||
typedef std::map<uint32_t, uint32_t> SsrcMap;
|
||||
StreamObserver(int num_expected_ssrcs,
|
||||
const SsrcMap& rtx_media_ssrcs,
|
||||
newapi::Transport* feedback_transport,
|
||||
Clock* clock)
|
||||
: critical_section_(CriticalSectionWrapper::CreateCriticalSection()),
|
||||
@ -45,8 +53,17 @@ class StreamObserver : public newapi::Transport, public RemoteBitrateObserver {
|
||||
rtp_parser_(RtpHeaderParser::Create()),
|
||||
feedback_transport_(feedback_transport),
|
||||
receive_stats_(ReceiveStatistics::Create(clock)),
|
||||
payload_registry_(new RTPPayloadRegistry(
|
||||
-1, RTPPayloadStrategy::CreateStrategy(false))),
|
||||
clock_(clock),
|
||||
num_expected_ssrcs_(num_expected_ssrcs) {
|
||||
num_expected_ssrcs_(num_expected_ssrcs),
|
||||
rtx_media_ssrcs_(rtx_media_ssrcs),
|
||||
total_sent_(0),
|
||||
padding_sent_(0),
|
||||
rtx_media_sent_(0),
|
||||
total_packets_sent_(0),
|
||||
padding_packets_sent_(0),
|
||||
rtx_media_packets_sent_(0) {
|
||||
// Ideally we would only have to instantiate an RtcpSender, an
|
||||
// RtpHeaderParser and a RemoteBitrateEstimator here, but due to the current
|
||||
// state of the RTP module we need a full module and receive statistics to
|
||||
@ -66,8 +83,25 @@ class StreamObserver : public newapi::Transport, public RemoteBitrateObserver {
|
||||
virtual void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
|
||||
unsigned int bitrate) {
|
||||
CriticalSectionScoped lock(critical_section_.get());
|
||||
if (ssrcs.size() == num_expected_ssrcs_ && bitrate >= kExpectedBitrateBps)
|
||||
all_ssrcs_sent_->Set();
|
||||
if (ssrcs.size() == num_expected_ssrcs_ && bitrate >= kExpectedBitrateBps) {
|
||||
if (rtx_media_ssrcs_.empty() || rtx_media_sent_ > 0) {
|
||||
const ::testing::TestInfo* const test_info =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
webrtc::test::PrintResult("total-sent", "", test_info->name(),
|
||||
total_sent_, "bytes", false);
|
||||
webrtc::test::PrintResult("padding-sent", "", test_info->name(),
|
||||
padding_sent_, "bytes", false);
|
||||
webrtc::test::PrintResult("rtx-media-sent", "", test_info->name(),
|
||||
rtx_media_sent_, "bytes", false);
|
||||
webrtc::test::PrintResult("total-packets-sent", "", test_info->name(),
|
||||
total_packets_sent_, "packets", false);
|
||||
webrtc::test::PrintResult("padding-packets-sent", "", test_info->name(),
|
||||
padding_packets_sent_, "packets", false);
|
||||
webrtc::test::PrintResult("rtx-packets-sent", "", test_info->name(),
|
||||
rtx_media_packets_sent_, "packets", false);
|
||||
all_ssrcs_sent_->Set();
|
||||
}
|
||||
}
|
||||
rtp_rtcp_->SetREMBData(
|
||||
bitrate, static_cast<uint8_t>(ssrcs.size()), &ssrcs[0]);
|
||||
rtp_rtcp_->Process();
|
||||
@ -78,12 +112,34 @@ class StreamObserver : public newapi::Transport, public RemoteBitrateObserver {
|
||||
RTPHeader header;
|
||||
EXPECT_TRUE(rtp_parser_->Parse(packet, static_cast<int>(length), &header));
|
||||
receive_stats_->IncomingPacket(header, length, false);
|
||||
rtp_rtcp_->SetRemoteSSRC(header.ssrc);
|
||||
payload_registry_->SetIncomingPayloadType(header);
|
||||
remote_bitrate_estimator_->IncomingPacket(
|
||||
clock_->TimeInMilliseconds(), static_cast<int>(length - 12), header);
|
||||
if (remote_bitrate_estimator_->TimeUntilNextProcess() <= 0) {
|
||||
remote_bitrate_estimator_->Process();
|
||||
}
|
||||
total_sent_ += length;
|
||||
padding_sent_ += header.paddingLength;
|
||||
++total_packets_sent_;
|
||||
if (header.paddingLength > 0)
|
||||
++padding_packets_sent_;
|
||||
if (rtx_media_ssrcs_.find(header.ssrc) != rtx_media_ssrcs_.end()) {
|
||||
rtx_media_sent_ += length - header.headerLength - header.paddingLength;
|
||||
if (header.paddingLength == 0)
|
||||
++rtx_media_packets_sent_;
|
||||
uint8_t restored_packet[kMaxPacketSize];
|
||||
uint8_t* restored_packet_ptr = restored_packet;
|
||||
int restored_length = static_cast<int>(length);
|
||||
payload_registry_->RestoreOriginalPacket(
|
||||
&restored_packet_ptr, packet, &restored_length,
|
||||
rtx_media_ssrcs_[header.ssrc],
|
||||
header);
|
||||
length = restored_length;
|
||||
EXPECT_TRUE(rtp_parser_->Parse(restored_packet, static_cast<int>(length),
|
||||
&header));
|
||||
} else {
|
||||
rtp_rtcp_->SetRemoteSSRC(header.ssrc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -102,9 +158,17 @@ class StreamObserver : public newapi::Transport, public RemoteBitrateObserver {
|
||||
scoped_ptr<RtpRtcp> rtp_rtcp_;
|
||||
internal::TransportAdapter feedback_transport_;
|
||||
scoped_ptr<ReceiveStatistics> receive_stats_;
|
||||
scoped_ptr<RTPPayloadRegistry> payload_registry_;
|
||||
scoped_ptr<RemoteBitrateEstimator> remote_bitrate_estimator_;
|
||||
Clock* clock_;
|
||||
const size_t num_expected_ssrcs_;
|
||||
SsrcMap rtx_media_ssrcs_;
|
||||
size_t total_sent_;
|
||||
size_t padding_sent_;
|
||||
size_t rtx_media_sent_;
|
||||
int total_packets_sent_;
|
||||
int padding_packets_sent_;
|
||||
int rtx_media_packets_sent_;
|
||||
};
|
||||
|
||||
class RampUpTest : public ::testing::TestWithParam<bool> {
|
||||
@ -112,52 +176,82 @@ class RampUpTest : public ::testing::TestWithParam<bool> {
|
||||
virtual void SetUp() { reserved_ssrcs_.clear(); }
|
||||
|
||||
protected:
|
||||
void RunRampUpTest(bool pacing, bool rtx) {
|
||||
const size_t kNumberOfStreams = 3;
|
||||
std::vector<uint32_t> ssrcs;
|
||||
for (size_t i = 0; i < kNumberOfStreams; ++i)
|
||||
ssrcs.push_back(static_cast<uint32_t>(i + 1));
|
||||
uint32_t kRtxSsrcs[kNumberOfStreams] = {111, 112, 113};
|
||||
StreamObserver::SsrcMap rtx_ssrc_map;
|
||||
if (rtx) {
|
||||
for (size_t i = 0; i < ssrcs.size(); ++i)
|
||||
rtx_ssrc_map[kRtxSsrcs[i]] = ssrcs[i];
|
||||
}
|
||||
test::DirectTransport receiver_transport;
|
||||
int num_expected_ssrcs = kNumberOfStreams + (rtx ? 1 : 0);
|
||||
StreamObserver stream_observer(
|
||||
num_expected_ssrcs, rtx_ssrc_map, &receiver_transport,
|
||||
Clock::GetRealTimeClock());
|
||||
|
||||
Call::Config call_config(&stream_observer);
|
||||
webrtc::Config webrtc_config;
|
||||
call_config.webrtc_config = &webrtc_config;
|
||||
webrtc_config.Set<PaddingStrategy>(new PaddingStrategy(rtx));
|
||||
scoped_ptr<Call> call(Call::Create(call_config));
|
||||
VideoSendStream::Config send_config = call->GetDefaultSendConfig();
|
||||
|
||||
receiver_transport.SetReceiver(call->Receiver());
|
||||
|
||||
test::FakeEncoder encoder(Clock::GetRealTimeClock());
|
||||
send_config.encoder = &encoder;
|
||||
send_config.internal_source = false;
|
||||
test::FakeEncoder::SetCodecSettings(&send_config.codec, kNumberOfStreams);
|
||||
send_config.codec.plType = 125;
|
||||
send_config.pacing = pacing;
|
||||
send_config.rtp.nack.rtp_history_ms = 1000;
|
||||
send_config.rtp.ssrcs.insert(send_config.rtp.ssrcs.begin(), ssrcs.begin(),
|
||||
ssrcs.end());
|
||||
if (rtx) {
|
||||
send_config.rtp.rtx.rtx_payload_type = 96;
|
||||
send_config.rtp.rtx.ssrcs.insert(send_config.rtp.rtx.ssrcs.begin(),
|
||||
kRtxSsrcs,
|
||||
kRtxSsrcs + kNumberOfStreams);
|
||||
}
|
||||
send_config.rtp.extensions.push_back(
|
||||
RtpExtension(RtpExtension::kTOffset, kTOffsetExtensionId));
|
||||
|
||||
VideoSendStream* send_stream = call->CreateVideoSendStream(send_config);
|
||||
|
||||
scoped_ptr<test::FrameGeneratorCapturer> frame_generator_capturer(
|
||||
test::FrameGeneratorCapturer::Create(send_stream->Input(),
|
||||
send_config.codec.width,
|
||||
send_config.codec.height,
|
||||
30,
|
||||
Clock::GetRealTimeClock()));
|
||||
|
||||
send_stream->StartSending();
|
||||
frame_generator_capturer->Start();
|
||||
|
||||
EXPECT_EQ(kEventSignaled, stream_observer.Wait());
|
||||
|
||||
frame_generator_capturer->Stop();
|
||||
send_stream->StopSending();
|
||||
|
||||
call->DestroyVideoSendStream(send_stream);
|
||||
}
|
||||
std::map<uint32_t, bool> reserved_ssrcs_;
|
||||
};
|
||||
|
||||
TEST_P(RampUpTest, RampUpWithPadding) {
|
||||
static const size_t kNumStreams = 3;
|
||||
test::DirectTransport receiver_transport;
|
||||
StreamObserver stream_observer(
|
||||
kNumStreams, &receiver_transport, Clock::GetRealTimeClock());
|
||||
Call::Config call_config(&stream_observer);
|
||||
scoped_ptr<Call> call(Call::Create(call_config));
|
||||
VideoSendStream::Config send_config = call->GetDefaultSendConfig();
|
||||
|
||||
receiver_transport.SetReceiver(call->Receiver());
|
||||
|
||||
test::FakeEncoder encoder(Clock::GetRealTimeClock());
|
||||
send_config.encoder = &encoder;
|
||||
send_config.internal_source = false;
|
||||
test::FakeEncoder::SetCodecSettings(&send_config.codec, kNumStreams);
|
||||
send_config.codec.plType = 125;
|
||||
send_config.pacing = GetParam();
|
||||
send_config.rtp.extensions.push_back(
|
||||
RtpExtension(RtpExtension::kTOffset, kTOffsetExtensionId));
|
||||
|
||||
for (size_t i = 0; i < kNumStreams; ++i)
|
||||
send_config.rtp.ssrcs.push_back(static_cast<uint32_t>(i + 1));
|
||||
|
||||
VideoSendStream* send_stream = call->CreateVideoSendStream(send_config);
|
||||
|
||||
scoped_ptr<test::FrameGeneratorCapturer> frame_generator_capturer(
|
||||
test::FrameGeneratorCapturer::Create(send_stream->Input(),
|
||||
send_config.codec.width,
|
||||
send_config.codec.height,
|
||||
30,
|
||||
Clock::GetRealTimeClock()));
|
||||
|
||||
send_stream->StartSending();
|
||||
frame_generator_capturer->Start();
|
||||
|
||||
EXPECT_EQ(kEventSignaled, stream_observer.Wait());
|
||||
|
||||
frame_generator_capturer->Stop();
|
||||
send_stream->StopSending();
|
||||
|
||||
call->DestroyVideoSendStream(send_stream);
|
||||
TEST_F(RampUpTest, WithoutPacing) {
|
||||
RunRampUpTest(false, false);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(RampUpTest, RampUpTest, ::testing::Bool());
|
||||
TEST_F(RampUpTest, WithPacing) {
|
||||
RunRampUpTest(true, false);
|
||||
}
|
||||
|
||||
TEST_F(RampUpTest, WithPacingAndRtx) {
|
||||
RunRampUpTest(true, true);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -13,7 +13,9 @@
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/common.h"
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
#include "webrtc/experiments.h"
|
||||
#include "webrtc/modules/pacing/include/paced_sender.h"
|
||||
#include "webrtc/modules/rtp_rtcp/interface/rtp_receiver.h"
|
||||
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
|
||||
@ -102,7 +104,8 @@ ViEChannel::ViEChannel(int32_t channel_id,
|
||||
sender_(sender),
|
||||
nack_history_size_sender_(kSendSidePacketHistorySize),
|
||||
max_nack_reordering_threshold_(kMaxPacketAgeToNack),
|
||||
pre_render_callback_(NULL) {
|
||||
pre_render_callback_(NULL),
|
||||
config_(config) {
|
||||
WEBRTC_TRACE(kTraceMemory, kTraceVideo, ViEId(engine_id, channel_id),
|
||||
"ViEChannel::ViEChannel(channel_id: %d, engine_id: %d)",
|
||||
channel_id, engine_id);
|
||||
@ -965,9 +968,12 @@ int32_t ViEChannel::SetSSRC(const uint32_t SSRC,
|
||||
ViEId(engine_id_, channel_id_),
|
||||
"%s(usage:%d, SSRC: 0x%x, idx:%u)",
|
||||
__FUNCTION__, usage, SSRC, simulcast_idx);
|
||||
int rtx_settings = kRtxRetransmitted;
|
||||
if (config_.Get<PaddingStrategy>().redundant_payloads)
|
||||
rtx_settings |= kRtxRedundantPayloads;
|
||||
if (simulcast_idx == 0) {
|
||||
if (usage == kViEStreamTypeRtx) {
|
||||
return rtp_rtcp_->SetRTXSendStatus(kRtxRetransmitted, true, SSRC);
|
||||
return rtp_rtcp_->SetRTXSendStatus(rtx_settings, true, SSRC);
|
||||
}
|
||||
return rtp_rtcp_->SetSSRC(SSRC);
|
||||
}
|
||||
@ -983,7 +989,7 @@ int32_t ViEChannel::SetSSRC(const uint32_t SSRC,
|
||||
}
|
||||
RtpRtcp* rtp_rtcp_module = *it;
|
||||
if (usage == kViEStreamTypeRtx) {
|
||||
return rtp_rtcp_module->SetRTXSendStatus(kRtxRetransmitted, true, SSRC);
|
||||
return rtp_rtcp_module->SetRTXSendStatus(rtx_settings, true, SSRC);
|
||||
}
|
||||
return rtp_rtcp_module->SetSSRC(SSRC);
|
||||
}
|
||||
|
@ -403,6 +403,7 @@ class ViEChannel
|
||||
int nack_history_size_sender_;
|
||||
int max_nack_reordering_threshold_;
|
||||
I420FrameCallback* pre_render_callback_;
|
||||
const Config& config_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -63,6 +63,7 @@
|
||||
'call.h',
|
||||
'config.cc',
|
||||
'config.h',
|
||||
'experiments.h',
|
||||
'frame_callback.h',
|
||||
'transport.h',
|
||||
'video_receive_stream.h',
|
||||
|
Loading…
x
Reference in New Issue
Block a user