Add support for padding in pacer.
This improves pacer-based padding by making sure it limits padding according to: - Never pad more than 800 kbps. - Padding + media should not go above a given target bitrate. Also adds appropriate unittests to make sure we reach the given targets. BUG=1837 R=mflodman@webrtc.org Review URL: https://webrtc-codereview.appspot.com/1582005 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4168 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
c69ae69d0b
commit
c3cc375499
@ -21,6 +21,11 @@
|
|||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
class CriticalSectionWrapper;
|
class CriticalSectionWrapper;
|
||||||
|
namespace paced_sender {
|
||||||
|
class IntervalBudget;
|
||||||
|
struct Packet;
|
||||||
|
class PacketList;
|
||||||
|
} // namespace paced_sender
|
||||||
|
|
||||||
class PacedSender : public Module {
|
class PacedSender : public Module {
|
||||||
public:
|
public:
|
||||||
@ -40,7 +45,7 @@ class PacedSender : public Module {
|
|||||||
virtual void TimeToSendPacket(uint32_t ssrc, uint16_t sequence_number,
|
virtual void TimeToSendPacket(uint32_t ssrc, uint16_t sequence_number,
|
||||||
int64_t capture_time_ms) = 0;
|
int64_t capture_time_ms) = 0;
|
||||||
// Called when it's a good time to send a padding data.
|
// Called when it's a good time to send a padding data.
|
||||||
virtual void TimeToSendPadding(int bytes) = 0;
|
virtual int TimeToSendPadding(int bytes) = 0;
|
||||||
protected:
|
protected:
|
||||||
virtual ~Callback() {}
|
virtual ~Callback() {}
|
||||||
};
|
};
|
||||||
@ -52,14 +57,19 @@ class PacedSender : public Module {
|
|||||||
// Enable/disable pacing.
|
// Enable/disable pacing.
|
||||||
void SetStatus(bool enable);
|
void SetStatus(bool enable);
|
||||||
|
|
||||||
|
bool Enabled() const;
|
||||||
|
|
||||||
// Temporarily pause all sending.
|
// Temporarily pause all sending.
|
||||||
void Pause();
|
void Pause();
|
||||||
|
|
||||||
// Resume sending packets.
|
// Resume sending packets.
|
||||||
void Resume();
|
void Resume();
|
||||||
|
|
||||||
// Current total estimated bitrate.
|
// Set the pacing target bitrate and the bitrate up to which we are allowed to
|
||||||
void UpdateBitrate(int target_bitrate_kbps);
|
// pad. We will send padding packets to increase the total bitrate until we
|
||||||
|
// reach |pad_up_to_bitrate_kbps|. If the media bitrate is above
|
||||||
|
// |pad_up_to_bitrate_kbps| no padding will be sent.
|
||||||
|
void UpdateBitrate(int target_bitrate_kbps, int pad_up_to_bitrate_kbps);
|
||||||
|
|
||||||
// Returns true if we send the packet now, else it will add the packet
|
// Returns true if we send the packet now, else it will add the packet
|
||||||
// information to the queue and call TimeToSendPacket when it's time to send.
|
// information to the queue and call TimeToSendPacket when it's time to send.
|
||||||
@ -80,42 +90,13 @@ class PacedSender : public Module {
|
|||||||
virtual int32_t Process();
|
virtual int32_t Process();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Packet {
|
|
||||||
Packet(uint32_t ssrc, uint16_t seq_number, int64_t capture_time_ms,
|
|
||||||
int length_in_bytes)
|
|
||||||
: ssrc_(ssrc),
|
|
||||||
sequence_number_(seq_number),
|
|
||||||
capture_time_ms_(capture_time_ms),
|
|
||||||
bytes_(length_in_bytes) {
|
|
||||||
}
|
|
||||||
uint32_t ssrc_;
|
|
||||||
uint16_t sequence_number_;
|
|
||||||
int64_t capture_time_ms_;
|
|
||||||
int bytes_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// STL list style class which prevents duplicates in the list.
|
|
||||||
class PacketList {
|
|
||||||
public:
|
|
||||||
PacketList() {};
|
|
||||||
|
|
||||||
bool empty() const;
|
|
||||||
Packet front() const;
|
|
||||||
void pop_front();
|
|
||||||
void push_back(const Packet& packet);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::list<Packet> packet_list_;
|
|
||||||
std::set<uint16_t> sequence_number_set_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Checks if next packet in line can be transmitted. Returns true on success.
|
// Checks if next packet in line can be transmitted. Returns true on success.
|
||||||
bool GetNextPacket(uint32_t* ssrc, uint16_t* sequence_number,
|
bool GetNextPacket(uint32_t* ssrc, uint16_t* sequence_number,
|
||||||
int64_t* capture_time_ms, Priority* priority,
|
int64_t* capture_time_ms, Priority* priority,
|
||||||
bool* last_packet);
|
bool* last_packet);
|
||||||
|
|
||||||
// Local helper function to GetNextPacket.
|
// Local helper function to GetNextPacket.
|
||||||
void GetNextPacketFromList(PacketList* list,
|
void GetNextPacketFromList(paced_sender::PacketList* packets,
|
||||||
uint32_t* ssrc, uint16_t* sequence_number, int64_t* capture_time_ms,
|
uint32_t* ssrc, uint16_t* sequence_number, int64_t* capture_time_ms,
|
||||||
bool* last_packet);
|
bool* last_packet);
|
||||||
|
|
||||||
@ -123,24 +104,32 @@ class PacedSender : public Module {
|
|||||||
void UpdateBytesPerInterval(uint32_t delta_time_in_ms);
|
void UpdateBytesPerInterval(uint32_t delta_time_in_ms);
|
||||||
|
|
||||||
// Updates the buffers with the number of bytes that we sent.
|
// Updates the buffers with the number of bytes that we sent.
|
||||||
void UpdateState(int num_bytes);
|
void UpdateMediaBytesSent(int num_bytes);
|
||||||
|
|
||||||
Callback* callback_;
|
Callback* callback_;
|
||||||
const float pace_multiplier_;
|
const float pace_multiplier_;
|
||||||
bool enable_;
|
bool enabled_;
|
||||||
bool paused_;
|
bool paused_;
|
||||||
scoped_ptr<CriticalSectionWrapper> critsect_;
|
scoped_ptr<CriticalSectionWrapper> critsect_;
|
||||||
int target_bitrate_kbytes_per_s_;
|
// This is the media budget, keeping track of how many bits of media
|
||||||
int bytes_remaining_interval_;
|
// we can pace out during the current interval.
|
||||||
int padding_bytes_remaining_interval_;
|
scoped_ptr<paced_sender::IntervalBudget> media_budget_;
|
||||||
|
// This is the padding budget, keeping track of how many bits of padding we're
|
||||||
|
// allowed to send out during the current interval.
|
||||||
|
scoped_ptr<paced_sender::IntervalBudget> padding_budget_;
|
||||||
|
// Media and padding share this budget, therefore no padding will be sent if
|
||||||
|
// media uses all of this budget. This is used to avoid padding above a given
|
||||||
|
// bitrate.
|
||||||
|
scoped_ptr<paced_sender::IntervalBudget> pad_up_to_bitrate_budget_;
|
||||||
|
|
||||||
TickTime time_last_update_;
|
TickTime time_last_update_;
|
||||||
TickTime time_last_send_;
|
TickTime time_last_send_;
|
||||||
int64_t capture_time_ms_last_queued_;
|
int64_t capture_time_ms_last_queued_;
|
||||||
int64_t capture_time_ms_last_sent_;
|
int64_t capture_time_ms_last_sent_;
|
||||||
|
|
||||||
PacketList high_priority_packets_;
|
scoped_ptr<paced_sender::PacketList> high_priority_packets_;
|
||||||
PacketList normal_priority_packets_;
|
scoped_ptr<paced_sender::PacketList> normal_priority_packets_;
|
||||||
PacketList low_priority_packets_;
|
scoped_ptr<paced_sender::PacketList> low_priority_packets_;
|
||||||
};
|
};
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
#endif // WEBRTC_MODULES_PACED_SENDER_H_
|
#endif // WEBRTC_MODULES_PACED_SENDER_H_
|
||||||
|
@ -28,47 +28,115 @@ const int kMaxIntervalTimeMs = 30;
|
|||||||
// packets are sent, regardless of buffer state. In practice only in effect at
|
// packets are sent, regardless of buffer state. In practice only in effect at
|
||||||
// low bitrates (less than 320 kbits/s).
|
// low bitrates (less than 320 kbits/s).
|
||||||
const int kMaxQueueTimeWithoutSendingMs = 30;
|
const int kMaxQueueTimeWithoutSendingMs = 30;
|
||||||
|
|
||||||
|
// Max padding bytes per second.
|
||||||
|
const int kMaxPaddingKbps = 800;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
bool PacedSender::PacketList::empty() const {
|
namespace paced_sender {
|
||||||
return packet_list_.empty();
|
struct Packet {
|
||||||
}
|
Packet(uint32_t ssrc, uint16_t seq_number, int64_t capture_time_ms,
|
||||||
|
int length_in_bytes)
|
||||||
PacedSender::Packet PacedSender::PacketList::front() const {
|
: ssrc_(ssrc),
|
||||||
return packet_list_.front();
|
sequence_number_(seq_number),
|
||||||
}
|
capture_time_ms_(capture_time_ms),
|
||||||
|
bytes_(length_in_bytes) {
|
||||||
void PacedSender::PacketList::pop_front() {
|
|
||||||
PacedSender::Packet& packet = packet_list_.front();
|
|
||||||
uint16_t sequence_number = packet.sequence_number_;
|
|
||||||
packet_list_.pop_front();
|
|
||||||
sequence_number_set_.erase(sequence_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PacedSender::PacketList::push_back(const PacedSender::Packet& packet) {
|
|
||||||
if (sequence_number_set_.find(packet.sequence_number_) ==
|
|
||||||
sequence_number_set_.end()) {
|
|
||||||
// Don't insert duplicates.
|
|
||||||
packet_list_.push_back(packet);
|
|
||||||
sequence_number_set_.insert(packet.sequence_number_);
|
|
||||||
}
|
}
|
||||||
}
|
uint32_t ssrc_;
|
||||||
|
uint16_t sequence_number_;
|
||||||
|
int64_t capture_time_ms_;
|
||||||
|
int bytes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// STL list style class which prevents duplicates in the list.
|
||||||
|
class PacketList {
|
||||||
|
public:
|
||||||
|
PacketList() {};
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return packet_list_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Packet front() const {
|
||||||
|
return packet_list_.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_front() {
|
||||||
|
Packet& packet = packet_list_.front();
|
||||||
|
uint16_t sequence_number = packet.sequence_number_;
|
||||||
|
packet_list_.pop_front();
|
||||||
|
sequence_number_set_.erase(sequence_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back(const Packet& packet) {
|
||||||
|
if (sequence_number_set_.find(packet.sequence_number_) ==
|
||||||
|
sequence_number_set_.end()) {
|
||||||
|
// Don't insert duplicates.
|
||||||
|
packet_list_.push_back(packet);
|
||||||
|
sequence_number_set_.insert(packet.sequence_number_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::list<Packet> packet_list_;
|
||||||
|
std::set<uint16_t> sequence_number_set_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IntervalBudget {
|
||||||
|
public:
|
||||||
|
explicit IntervalBudget(int initial_target_rate_kbps)
|
||||||
|
: target_rate_kbps_(initial_target_rate_kbps),
|
||||||
|
bytes_remaining_(0) {}
|
||||||
|
|
||||||
|
void set_target_rate_kbps(int target_rate_kbps) {
|
||||||
|
target_rate_kbps_ = target_rate_kbps;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IncreaseBudget(int delta_time_ms) {
|
||||||
|
int bytes = target_rate_kbps_ * delta_time_ms / 8;
|
||||||
|
if (bytes_remaining_ < 0) {
|
||||||
|
// We overused last interval, compensate this interval.
|
||||||
|
bytes_remaining_ = bytes_remaining_ + bytes;
|
||||||
|
} else {
|
||||||
|
// If we underused last interval we can't use it this interval.
|
||||||
|
bytes_remaining_ = bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UseBudget(int bytes) {
|
||||||
|
bytes_remaining_ = std::max(bytes_remaining_ - bytes,
|
||||||
|
-100 * target_rate_kbps_ / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytes_remaining() const { return bytes_remaining_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int target_rate_kbps_;
|
||||||
|
int bytes_remaining_;
|
||||||
|
};
|
||||||
|
} // namespace paced_sender
|
||||||
|
|
||||||
PacedSender::PacedSender(Callback* callback, int target_bitrate_kbps,
|
PacedSender::PacedSender(Callback* callback, int target_bitrate_kbps,
|
||||||
float pace_multiplier)
|
float pace_multiplier)
|
||||||
: callback_(callback),
|
: callback_(callback),
|
||||||
pace_multiplier_(pace_multiplier),
|
pace_multiplier_(pace_multiplier),
|
||||||
enable_(false),
|
enabled_(false),
|
||||||
paused_(false),
|
paused_(false),
|
||||||
critsect_(CriticalSectionWrapper::CreateCriticalSection()),
|
critsect_(CriticalSectionWrapper::CreateCriticalSection()),
|
||||||
target_bitrate_kbytes_per_s_(target_bitrate_kbps >> 3), // Divide by 8.
|
media_budget_(new paced_sender::IntervalBudget(
|
||||||
bytes_remaining_interval_(0),
|
pace_multiplier_ * target_bitrate_kbps)),
|
||||||
padding_bytes_remaining_interval_(0),
|
padding_budget_(new paced_sender::IntervalBudget(kMaxPaddingKbps)),
|
||||||
|
// No padding until UpdateBitrate is called.
|
||||||
|
pad_up_to_bitrate_budget_(new paced_sender::IntervalBudget(0)),
|
||||||
time_last_update_(TickTime::Now()),
|
time_last_update_(TickTime::Now()),
|
||||||
capture_time_ms_last_queued_(0),
|
capture_time_ms_last_queued_(0),
|
||||||
capture_time_ms_last_sent_(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) {
|
||||||
UpdateBytesPerInterval(kMinPacketLimitMs);
|
UpdateBytesPerInterval(kMinPacketLimitMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,20 +155,27 @@ void PacedSender::Resume() {
|
|||||||
|
|
||||||
void PacedSender::SetStatus(bool enable) {
|
void PacedSender::SetStatus(bool enable) {
|
||||||
CriticalSectionScoped cs(critsect_.get());
|
CriticalSectionScoped cs(critsect_.get());
|
||||||
enable_ = enable;
|
enabled_ = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PacedSender::UpdateBitrate(int target_bitrate_kbps) {
|
bool PacedSender::Enabled() const {
|
||||||
CriticalSectionScoped cs(critsect_.get());
|
CriticalSectionScoped cs(critsect_.get());
|
||||||
target_bitrate_kbytes_per_s_ = target_bitrate_kbps >> 3; // Divide by 8.
|
return enabled_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PacedSender::UpdateBitrate(int target_bitrate_kbps,
|
||||||
|
int pad_up_to_bitrate_kbps) {
|
||||||
|
CriticalSectionScoped cs(critsect_.get());
|
||||||
|
media_budget_->set_target_rate_kbps(pace_multiplier_ * target_bitrate_kbps);
|
||||||
|
pad_up_to_bitrate_budget_->set_target_rate_kbps(pad_up_to_bitrate_kbps);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PacedSender::SendPacket(Priority priority, uint32_t ssrc,
|
bool PacedSender::SendPacket(Priority priority, uint32_t ssrc,
|
||||||
uint16_t sequence_number, int64_t capture_time_ms, int bytes) {
|
uint16_t sequence_number, int64_t capture_time_ms, int bytes) {
|
||||||
CriticalSectionScoped cs(critsect_.get());
|
CriticalSectionScoped cs(critsect_.get());
|
||||||
|
|
||||||
if (!enable_) {
|
if (!enabled_) {
|
||||||
UpdateState(bytes);
|
UpdateMediaBytesSent(bytes);
|
||||||
return true; // We can send now.
|
return true; // We can send now.
|
||||||
}
|
}
|
||||||
if (capture_time_ms < 0) {
|
if (capture_time_ms < 0) {
|
||||||
@ -110,8 +185,10 @@ bool PacedSender::SendPacket(Priority priority, uint32_t ssrc,
|
|||||||
// Queue all packets when we are paused.
|
// Queue all packets when we are paused.
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case kHighPriority:
|
case kHighPriority:
|
||||||
high_priority_packets_.push_back(
|
high_priority_packets_->push_back(paced_sender::Packet(ssrc,
|
||||||
Packet(ssrc, sequence_number, capture_time_ms, bytes));
|
sequence_number,
|
||||||
|
capture_time_ms,
|
||||||
|
bytes));
|
||||||
break;
|
break;
|
||||||
case kNormalPriority:
|
case kNormalPriority:
|
||||||
if (capture_time_ms > capture_time_ms_last_queued_) {
|
if (capture_time_ms > capture_time_ms_last_queued_) {
|
||||||
@ -122,50 +199,31 @@ bool PacedSender::SendPacket(Priority priority, uint32_t ssrc,
|
|||||||
case kLowPriority:
|
case kLowPriority:
|
||||||
// Queue the low priority packets in the normal priority queue when we
|
// Queue the low priority packets in the normal priority queue when we
|
||||||
// are paused to avoid starvation.
|
// are paused to avoid starvation.
|
||||||
normal_priority_packets_.push_back(
|
normal_priority_packets_->push_back(paced_sender::Packet(
|
||||||
Packet(ssrc, sequence_number, capture_time_ms, bytes));
|
ssrc, sequence_number, capture_time_ms, bytes));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
paced_sender::PacketList* packet_list;
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case kHighPriority:
|
case kHighPriority:
|
||||||
if (high_priority_packets_.empty() &&
|
packet_list = high_priority_packets_.get();
|
||||||
bytes_remaining_interval_ > 0) {
|
break;
|
||||||
UpdateState(bytes);
|
|
||||||
return true; // We can send now.
|
|
||||||
}
|
|
||||||
high_priority_packets_.push_back(
|
|
||||||
Packet(ssrc, sequence_number, capture_time_ms, bytes));
|
|
||||||
return false;
|
|
||||||
case kNormalPriority:
|
case kNormalPriority:
|
||||||
if (high_priority_packets_.empty() &&
|
packet_list = normal_priority_packets_.get();
|
||||||
normal_priority_packets_.empty() &&
|
break;
|
||||||
bytes_remaining_interval_ > 0) {
|
|
||||||
UpdateState(bytes);
|
|
||||||
return true; // We can send now.
|
|
||||||
}
|
|
||||||
if (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);
|
|
||||||
}
|
|
||||||
normal_priority_packets_.push_back(
|
|
||||||
Packet(ssrc, sequence_number, capture_time_ms, bytes));
|
|
||||||
return false;
|
|
||||||
case kLowPriority:
|
case kLowPriority:
|
||||||
if (high_priority_packets_.empty() &&
|
packet_list = low_priority_packets_.get();
|
||||||
normal_priority_packets_.empty() &&
|
break;
|
||||||
low_priority_packets_.empty() &&
|
|
||||||
bytes_remaining_interval_ > 0) {
|
|
||||||
UpdateState(bytes);
|
|
||||||
return true; // We can send now.
|
|
||||||
}
|
|
||||||
low_priority_packets_.push_back(
|
|
||||||
Packet(ssrc, sequence_number, capture_time_ms, bytes));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
if (packet_list->empty() &&
|
||||||
|
media_budget_->bytes_remaining() > 0) {
|
||||||
|
UpdateMediaBytesSent(bytes);
|
||||||
|
return true; // We can send now.
|
||||||
|
}
|
||||||
|
packet_list->push_back(paced_sender::Packet(ssrc, sequence_number,
|
||||||
|
capture_time_ms, bytes));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,20 +231,20 @@ int PacedSender::QueueInMs() const {
|
|||||||
CriticalSectionScoped cs(critsect_.get());
|
CriticalSectionScoped cs(critsect_.get());
|
||||||
int64_t now_ms = TickTime::MillisecondTimestamp();
|
int64_t now_ms = TickTime::MillisecondTimestamp();
|
||||||
int64_t oldest_packet_capture_time = now_ms;
|
int64_t oldest_packet_capture_time = now_ms;
|
||||||
if (!high_priority_packets_.empty()) {
|
if (!high_priority_packets_->empty()) {
|
||||||
oldest_packet_capture_time = std::min(
|
oldest_packet_capture_time = std::min(
|
||||||
oldest_packet_capture_time,
|
oldest_packet_capture_time,
|
||||||
high_priority_packets_.front().capture_time_ms_);
|
high_priority_packets_->front().capture_time_ms_);
|
||||||
}
|
}
|
||||||
if (!normal_priority_packets_.empty()) {
|
if (!normal_priority_packets_->empty()) {
|
||||||
oldest_packet_capture_time = std::min(
|
oldest_packet_capture_time = std::min(
|
||||||
oldest_packet_capture_time,
|
oldest_packet_capture_time,
|
||||||
normal_priority_packets_.front().capture_time_ms_);
|
normal_priority_packets_->front().capture_time_ms_);
|
||||||
}
|
}
|
||||||
if (!low_priority_packets_.empty()) {
|
if (!low_priority_packets_->empty()) {
|
||||||
oldest_packet_capture_time = std::min(
|
oldest_packet_capture_time = std::min(
|
||||||
oldest_packet_capture_time,
|
oldest_packet_capture_time,
|
||||||
low_priority_packets_.front().capture_time_ms_);
|
low_priority_packets_->front().capture_time_ms_);
|
||||||
}
|
}
|
||||||
return now_ms - oldest_packet_capture_time;
|
return now_ms - oldest_packet_capture_time;
|
||||||
}
|
}
|
||||||
@ -231,15 +289,20 @@ int32_t PacedSender::Process() {
|
|||||||
callback_->TimeToSendPacket(ssrc, sequence_number, capture_time_ms);
|
callback_->TimeToSendPacket(ssrc, sequence_number, capture_time_ms);
|
||||||
critsect_->Enter();
|
critsect_->Enter();
|
||||||
}
|
}
|
||||||
if (high_priority_packets_.empty() &&
|
if (high_priority_packets_->empty() &&
|
||||||
normal_priority_packets_.empty() &&
|
normal_priority_packets_->empty() &&
|
||||||
low_priority_packets_.empty() &&
|
low_priority_packets_->empty() &&
|
||||||
padding_bytes_remaining_interval_ > 0) {
|
padding_budget_->bytes_remaining() > 0 &&
|
||||||
|
pad_up_to_bitrate_budget_->bytes_remaining() > 0) {
|
||||||
|
int padding_needed = std::min(
|
||||||
|
padding_budget_->bytes_remaining(),
|
||||||
|
pad_up_to_bitrate_budget_->bytes_remaining());
|
||||||
critsect_->Leave();
|
critsect_->Leave();
|
||||||
callback_->TimeToSendPadding(padding_bytes_remaining_interval_);
|
int bytes_sent = callback_->TimeToSendPadding(padding_needed);
|
||||||
critsect_->Enter();
|
critsect_->Enter();
|
||||||
padding_bytes_remaining_interval_ = 0;
|
media_budget_->UseBudget(bytes_sent);
|
||||||
bytes_remaining_interval_ -= padding_bytes_remaining_interval_;
|
padding_budget_->UseBudget(bytes_sent);
|
||||||
|
pad_up_to_bitrate_budget_->UseBudget(bytes_sent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -247,87 +310,74 @@ int32_t PacedSender::Process() {
|
|||||||
|
|
||||||
// MUST have critsect_ when calling.
|
// MUST have critsect_ when calling.
|
||||||
void PacedSender::UpdateBytesPerInterval(uint32_t delta_time_ms) {
|
void PacedSender::UpdateBytesPerInterval(uint32_t delta_time_ms) {
|
||||||
uint32_t bytes_per_interval = target_bitrate_kbytes_per_s_ * delta_time_ms;
|
media_budget_->IncreaseBudget(delta_time_ms);
|
||||||
|
padding_budget_->IncreaseBudget(delta_time_ms);
|
||||||
if (bytes_remaining_interval_ < 0) {
|
pad_up_to_bitrate_budget_->IncreaseBudget(delta_time_ms);
|
||||||
// We overused last interval, compensate this interval.
|
|
||||||
bytes_remaining_interval_ += pace_multiplier_ * bytes_per_interval;
|
|
||||||
} else {
|
|
||||||
// If we underused last interval we can't use it this interval.
|
|
||||||
bytes_remaining_interval_ = pace_multiplier_ * bytes_per_interval;
|
|
||||||
}
|
|
||||||
if (padding_bytes_remaining_interval_ < 0) {
|
|
||||||
// We overused last interval, compensate this interval.
|
|
||||||
padding_bytes_remaining_interval_ += bytes_per_interval;
|
|
||||||
} else {
|
|
||||||
// If we underused last interval we can't use it this interval.
|
|
||||||
padding_bytes_remaining_interval_ = bytes_per_interval;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MUST have critsect_ when calling.
|
// MUST have critsect_ when calling.
|
||||||
bool PacedSender::GetNextPacket(uint32_t* ssrc, uint16_t* sequence_number,
|
bool PacedSender::GetNextPacket(uint32_t* ssrc, uint16_t* sequence_number,
|
||||||
int64_t* capture_time_ms, Priority* priority,
|
int64_t* capture_time_ms, Priority* priority,
|
||||||
bool* last_packet) {
|
bool* last_packet) {
|
||||||
if (bytes_remaining_interval_ <= 0) {
|
if (media_budget_->bytes_remaining() <= 0) {
|
||||||
// All bytes consumed for this interval.
|
// All bytes consumed for this interval.
|
||||||
// Check if we have not sent in a too long time.
|
// Check if we have not sent in a too long time.
|
||||||
if ((TickTime::Now() - time_last_send_).Milliseconds() >
|
if ((TickTime::Now() - time_last_send_).Milliseconds() >
|
||||||
kMaxQueueTimeWithoutSendingMs) {
|
kMaxQueueTimeWithoutSendingMs) {
|
||||||
if (!high_priority_packets_.empty()) {
|
if (!high_priority_packets_->empty()) {
|
||||||
*priority = kHighPriority;
|
*priority = kHighPriority;
|
||||||
GetNextPacketFromList(&high_priority_packets_, ssrc, sequence_number,
|
GetNextPacketFromList(high_priority_packets_.get(), ssrc,
|
||||||
capture_time_ms, last_packet);
|
sequence_number, capture_time_ms, last_packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!normal_priority_packets_.empty()) {
|
if (!normal_priority_packets_->empty()) {
|
||||||
*priority = kNormalPriority;
|
*priority = kNormalPriority;
|
||||||
GetNextPacketFromList(&normal_priority_packets_, ssrc, sequence_number,
|
GetNextPacketFromList(normal_priority_packets_.get(), ssrc,
|
||||||
capture_time_ms, last_packet);
|
sequence_number, capture_time_ms, last_packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!high_priority_packets_.empty()) {
|
if (!high_priority_packets_->empty()) {
|
||||||
*priority = kHighPriority;
|
*priority = kHighPriority;
|
||||||
GetNextPacketFromList(&high_priority_packets_, ssrc, sequence_number,
|
GetNextPacketFromList(high_priority_packets_.get(), ssrc, sequence_number,
|
||||||
capture_time_ms, last_packet);
|
capture_time_ms, last_packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!normal_priority_packets_.empty()) {
|
if (!normal_priority_packets_->empty()) {
|
||||||
*priority = kNormalPriority;
|
*priority = kNormalPriority;
|
||||||
GetNextPacketFromList(&normal_priority_packets_, ssrc, sequence_number,
|
GetNextPacketFromList(normal_priority_packets_.get(), ssrc,
|
||||||
capture_time_ms, last_packet);
|
sequence_number, capture_time_ms, last_packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!low_priority_packets_.empty()) {
|
if (!low_priority_packets_->empty()) {
|
||||||
*priority = kLowPriority;
|
*priority = kLowPriority;
|
||||||
GetNextPacketFromList(&low_priority_packets_, ssrc, sequence_number,
|
GetNextPacketFromList(low_priority_packets_.get(), ssrc, sequence_number,
|
||||||
capture_time_ms, last_packet);
|
capture_time_ms, last_packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PacedSender::GetNextPacketFromList(PacketList* list,
|
void PacedSender::GetNextPacketFromList(paced_sender::PacketList* packets,
|
||||||
uint32_t* ssrc, uint16_t* sequence_number, int64_t* capture_time_ms,
|
uint32_t* ssrc, uint16_t* sequence_number, int64_t* capture_time_ms,
|
||||||
bool* last_packet) {
|
bool* last_packet) {
|
||||||
Packet packet = list->front();
|
paced_sender::Packet packet = packets->front();
|
||||||
UpdateState(packet.bytes_);
|
UpdateMediaBytesSent(packet.bytes_);
|
||||||
*sequence_number = packet.sequence_number_;
|
*sequence_number = packet.sequence_number_;
|
||||||
*ssrc = packet.ssrc_;
|
*ssrc = packet.ssrc_;
|
||||||
*capture_time_ms = packet.capture_time_ms_;
|
*capture_time_ms = packet.capture_time_ms_;
|
||||||
list->pop_front();
|
packets->pop_front();
|
||||||
*last_packet = list->empty() ||
|
*last_packet = packets->empty() ||
|
||||||
list->front().capture_time_ms_ > *capture_time_ms;
|
packets->front().capture_time_ms_ > *capture_time_ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MUST have critsect_ when calling.
|
// MUST have critsect_ when calling.
|
||||||
void PacedSender::UpdateState(int num_bytes) {
|
void PacedSender::UpdateMediaBytesSent(int num_bytes) {
|
||||||
time_last_send_ = TickTime::Now();
|
time_last_send_ = TickTime::Now();
|
||||||
bytes_remaining_interval_ -= num_bytes;
|
media_budget_->UseBudget(num_bytes);
|
||||||
padding_bytes_remaining_interval_ -= num_bytes;
|
pad_up_to_bitrate_budget_->UseBudget(num_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "webrtc/modules/pacing/include/paced_sender.h"
|
#include "webrtc/modules/pacing/include/paced_sender.h"
|
||||||
|
|
||||||
using testing::_;
|
using testing::_;
|
||||||
|
using testing::Return;
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
namespace test {
|
||||||
@ -26,18 +27,41 @@ class MockPacedSenderCallback : public PacedSender::Callback {
|
|||||||
MOCK_METHOD3(TimeToSendPacket,
|
MOCK_METHOD3(TimeToSendPacket,
|
||||||
void(uint32_t ssrc, uint16_t sequence_number, int64_t capture_time_ms));
|
void(uint32_t ssrc, uint16_t sequence_number, int64_t capture_time_ms));
|
||||||
MOCK_METHOD1(TimeToSendPadding,
|
MOCK_METHOD1(TimeToSendPadding,
|
||||||
void(int bytes));
|
int(int bytes));
|
||||||
|
};
|
||||||
|
|
||||||
|
class PacedSenderPadding : public PacedSender::Callback {
|
||||||
|
public:
|
||||||
|
PacedSenderPadding() : padding_sent_(0) {}
|
||||||
|
|
||||||
|
void TimeToSendPacket(uint32_t ssrc, uint16_t sequence_number,
|
||||||
|
int64_t capture_time_ms) {
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeToSendPadding(int bytes) {
|
||||||
|
const int kPaddingPacketSize = 224;
|
||||||
|
int num_packets = (bytes + kPaddingPacketSize - 1) / kPaddingPacketSize;
|
||||||
|
padding_sent_ += kPaddingPacketSize * num_packets;
|
||||||
|
return kPaddingPacketSize * num_packets;
|
||||||
|
}
|
||||||
|
|
||||||
|
int padding_sent() { return padding_sent_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int padding_sent_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PacedSenderTest : public ::testing::Test {
|
class PacedSenderTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
PacedSenderTest() {
|
PacedSenderTest() {
|
||||||
|
srand(0);
|
||||||
TickTime::UseFakeClock(123456);
|
TickTime::UseFakeClock(123456);
|
||||||
// Need to initialize PacedSender after we initialize clock.
|
// Need to initialize PacedSender after we initialize clock.
|
||||||
send_bucket_.reset(new PacedSender(&callback_, kTargetBitrate,
|
send_bucket_.reset(new PacedSender(&callback_, kTargetBitrate,
|
||||||
kPaceMultiplier));
|
kPaceMultiplier));
|
||||||
send_bucket_->SetStatus(true);
|
send_bucket_->SetStatus(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
MockPacedSenderCallback callback_;
|
MockPacedSenderCallback callback_;
|
||||||
scoped_ptr<PacedSender> send_bucket_;
|
scoped_ptr<PacedSender> send_bucket_;
|
||||||
};
|
};
|
||||||
@ -164,6 +188,7 @@ TEST_F(PacedSenderTest, Padding) {
|
|||||||
uint16_t sequence_number = 1234;
|
uint16_t sequence_number = 1234;
|
||||||
int64_t capture_time_ms = 56789;
|
int64_t capture_time_ms = 56789;
|
||||||
|
|
||||||
|
send_bucket_->UpdateBitrate(kTargetBitrate, kTargetBitrate);
|
||||||
// Due to the multiplicative factor we can send 3 packets not 2 packets.
|
// Due to the multiplicative factor we can send 3 packets not 2 packets.
|
||||||
EXPECT_TRUE(send_bucket_->SendPacket(PacedSender::kNormalPriority, ssrc,
|
EXPECT_TRUE(send_bucket_->SendPacket(PacedSender::kNormalPriority, ssrc,
|
||||||
sequence_number++, capture_time_ms, 250));
|
sequence_number++, capture_time_ms, 250));
|
||||||
@ -171,7 +196,8 @@ TEST_F(PacedSenderTest, Padding) {
|
|||||||
sequence_number++, capture_time_ms, 250));
|
sequence_number++, capture_time_ms, 250));
|
||||||
EXPECT_TRUE(send_bucket_->SendPacket(PacedSender::kNormalPriority, ssrc,
|
EXPECT_TRUE(send_bucket_->SendPacket(PacedSender::kNormalPriority, ssrc,
|
||||||
sequence_number++, capture_time_ms, 250));
|
sequence_number++, capture_time_ms, 250));
|
||||||
EXPECT_CALL(callback_, TimeToSendPadding(250)).Times(1);
|
// No padding is expected since we have sent too much already.
|
||||||
|
EXPECT_CALL(callback_, TimeToSendPadding(_)).Times(0);
|
||||||
EXPECT_CALL(callback_,
|
EXPECT_CALL(callback_,
|
||||||
TimeToSendPacket(ssrc, sequence_number, capture_time_ms)).Times(0);
|
TimeToSendPacket(ssrc, sequence_number, capture_time_ms)).Times(0);
|
||||||
EXPECT_EQ(5, send_bucket_->TimeUntilNextProcess());
|
EXPECT_EQ(5, send_bucket_->TimeUntilNextProcess());
|
||||||
@ -179,13 +205,79 @@ TEST_F(PacedSenderTest, Padding) {
|
|||||||
EXPECT_EQ(0, send_bucket_->TimeUntilNextProcess());
|
EXPECT_EQ(0, send_bucket_->TimeUntilNextProcess());
|
||||||
EXPECT_EQ(0, send_bucket_->Process());
|
EXPECT_EQ(0, send_bucket_->Process());
|
||||||
|
|
||||||
EXPECT_CALL(callback_, TimeToSendPadding(500)).Times(1);
|
// 5 milliseconds later we have enough budget to send some padding.
|
||||||
|
EXPECT_CALL(callback_, TimeToSendPadding(250)).Times(1).
|
||||||
|
WillOnce(Return(250));
|
||||||
EXPECT_EQ(5, send_bucket_->TimeUntilNextProcess());
|
EXPECT_EQ(5, send_bucket_->TimeUntilNextProcess());
|
||||||
TickTime::AdvanceFakeClock(5);
|
TickTime::AdvanceFakeClock(5);
|
||||||
EXPECT_EQ(0, send_bucket_->TimeUntilNextProcess());
|
EXPECT_EQ(0, send_bucket_->TimeUntilNextProcess());
|
||||||
EXPECT_EQ(0, send_bucket_->Process());
|
EXPECT_EQ(0, send_bucket_->Process());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PacedSenderTest, VerifyPaddingUpToBitrate) {
|
||||||
|
uint32_t ssrc = 12345;
|
||||||
|
uint16_t sequence_number = 1234;
|
||||||
|
int64_t capture_time_ms = 56789;
|
||||||
|
const int kTimeStep = 5;
|
||||||
|
const int64_t kBitrateWindow = 100;
|
||||||
|
send_bucket_->UpdateBitrate(kTargetBitrate, kTargetBitrate);
|
||||||
|
int64_t start_time = TickTime::MillisecondTimestamp();
|
||||||
|
while (TickTime::MillisecondTimestamp() - start_time < kBitrateWindow) {
|
||||||
|
EXPECT_TRUE(send_bucket_->SendPacket(PacedSender::kNormalPriority, ssrc,
|
||||||
|
sequence_number++, capture_time_ms,
|
||||||
|
250));
|
||||||
|
TickTime::AdvanceFakeClock(kTimeStep);
|
||||||
|
EXPECT_CALL(callback_, TimeToSendPadding(250)).Times(1).
|
||||||
|
WillOnce(Return(250));
|
||||||
|
send_bucket_->Process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PacedSenderTest, VerifyMaxPaddingBitrate) {
|
||||||
|
uint32_t ssrc = 12345;
|
||||||
|
uint16_t sequence_number = 1234;
|
||||||
|
int64_t capture_time_ms = 56789;
|
||||||
|
const int kTimeStep = 5;
|
||||||
|
const int64_t kBitrateWindow = 100;
|
||||||
|
const int kTargetBitrate = 1500;
|
||||||
|
send_bucket_->UpdateBitrate(kTargetBitrate, kTargetBitrate);
|
||||||
|
int64_t start_time = TickTime::MillisecondTimestamp();
|
||||||
|
while (TickTime::MillisecondTimestamp() - start_time < kBitrateWindow) {
|
||||||
|
EXPECT_TRUE(send_bucket_->SendPacket(PacedSender::kNormalPriority, ssrc,
|
||||||
|
sequence_number++, capture_time_ms,
|
||||||
|
250));
|
||||||
|
TickTime::AdvanceFakeClock(kTimeStep);
|
||||||
|
EXPECT_CALL(callback_, TimeToSendPadding(500)).Times(1).
|
||||||
|
WillOnce(Return(250));
|
||||||
|
send_bucket_->Process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PacedSenderTest, VerifyAverageBitrateVaryingMediaPayload) {
|
||||||
|
uint32_t ssrc = 12345;
|
||||||
|
uint16_t sequence_number = 1234;
|
||||||
|
int64_t capture_time_ms = 56789;
|
||||||
|
const int kTimeStep = 5;
|
||||||
|
const int64_t kBitrateWindow = 10000;
|
||||||
|
PacedSenderPadding callback;
|
||||||
|
send_bucket_.reset(new PacedSender(&callback, kTargetBitrate,
|
||||||
|
kPaceMultiplier));
|
||||||
|
send_bucket_->UpdateBitrate(kTargetBitrate, kTargetBitrate);
|
||||||
|
int64_t start_time = TickTime::MillisecondTimestamp();
|
||||||
|
int media_bytes = 0;
|
||||||
|
while (TickTime::MillisecondTimestamp() - start_time < kBitrateWindow) {
|
||||||
|
int media_payload = rand() % 100 + 200; // [200, 300] bytes.
|
||||||
|
EXPECT_TRUE(send_bucket_->SendPacket(PacedSender::kNormalPriority, ssrc,
|
||||||
|
sequence_number++, capture_time_ms,
|
||||||
|
media_payload));
|
||||||
|
media_bytes += media_payload;
|
||||||
|
TickTime::AdvanceFakeClock(kTimeStep);
|
||||||
|
send_bucket_->Process();
|
||||||
|
}
|
||||||
|
EXPECT_NEAR(kTargetBitrate, 8 * (media_bytes + callback.padding_sent()) /
|
||||||
|
kBitrateWindow, 1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(PacedSenderTest, Priority) {
|
TEST_F(PacedSenderTest, Priority) {
|
||||||
uint32_t ssrc_low_priority = 12345;
|
uint32_t ssrc_low_priority = 12345;
|
||||||
uint32_t ssrc = 12346;
|
uint32_t ssrc = 12346;
|
||||||
|
@ -165,7 +165,7 @@ public:
|
|||||||
// < 0, on error.
|
// < 0, on error.
|
||||||
virtual int32_t CodecConfigParameters(uint8_t* buffer, int32_t size) = 0;
|
virtual int32_t CodecConfigParameters(uint8_t* buffer, int32_t size) = 0;
|
||||||
|
|
||||||
// API to get currently configured encoder target bitrate in kbit/s.
|
// API to get currently configured encoder target bitrate in bits/s.
|
||||||
//
|
//
|
||||||
// Return value : 0, on success.
|
// Return value : 0, on success.
|
||||||
// < 0, on error.
|
// < 0, on error.
|
||||||
|
@ -942,8 +942,8 @@ int32_t ViEChannel::EnableKeyFrameRequestCallback(const bool enable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32_t ViEChannel::SetSSRC(const uint32_t SSRC,
|
int32_t ViEChannel::SetSSRC(const uint32_t SSRC,
|
||||||
const StreamType usage,
|
const StreamType usage,
|
||||||
const uint8_t simulcast_idx) {
|
const uint8_t simulcast_idx) {
|
||||||
WEBRTC_TRACE(webrtc::kTraceInfo,
|
WEBRTC_TRACE(webrtc::kTraceInfo,
|
||||||
webrtc::kTraceVideo,
|
webrtc::kTraceVideo,
|
||||||
ViEId(engine_id_, channel_id_),
|
ViEId(engine_id_, channel_id_),
|
||||||
@ -973,7 +973,7 @@ int32_t ViEChannel::SetSSRC(const uint32_t SSRC,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32_t ViEChannel::SetRemoteSSRCType(const StreamType usage,
|
int32_t ViEChannel::SetRemoteSSRCType(const StreamType usage,
|
||||||
const uint32_t SSRC) const {
|
const uint32_t SSRC) const {
|
||||||
WEBRTC_TRACE(webrtc::kTraceInfo,
|
WEBRTC_TRACE(webrtc::kTraceInfo,
|
||||||
webrtc::kTraceVideo,
|
webrtc::kTraceVideo,
|
||||||
ViEId(engine_id_, channel_id_),
|
ViEId(engine_id_, channel_id_),
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "webrtc/video_engine/vie_encoder.h"
|
#include "webrtc/video_engine/vie_encoder.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||||
@ -91,8 +92,9 @@ class ViEPacedSenderCallback : public PacedSender::Callback {
|
|||||||
int64_t capture_time_ms) {
|
int64_t capture_time_ms) {
|
||||||
owner_->TimeToSendPacket(ssrc, sequence_number, capture_time_ms);
|
owner_->TimeToSendPacket(ssrc, sequence_number, capture_time_ms);
|
||||||
}
|
}
|
||||||
virtual void TimeToSendPadding(int /*bytes*/) {
|
virtual int TimeToSendPadding(int bytes) {
|
||||||
// TODO(pwestin): Hook up this.
|
// TODO(pwestin): Hook up this.
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
ViEEncoder* owner_;
|
ViEEncoder* owner_;
|
||||||
@ -1000,7 +1002,7 @@ void ViEEncoder::OnNetworkChanged(const uint32_t bitrate_bps,
|
|||||||
|
|
||||||
vcm_.SetChannelParameters(bitrate_bps, fraction_lost, round_trip_time_ms);
|
vcm_.SetChannelParameters(bitrate_bps, fraction_lost, round_trip_time_ms);
|
||||||
int bitrate_kbps = bitrate_bps / 1000;
|
int bitrate_kbps = bitrate_bps / 1000;
|
||||||
paced_sender_->UpdateBitrate(bitrate_kbps);
|
paced_sender_->UpdateBitrate(bitrate_kbps, 0);
|
||||||
default_rtp_rtcp_->SetTargetSendBitrate(bitrate_bps);
|
default_rtp_rtcp_->SetTargetSendBitrate(bitrate_bps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user