Adding optimized aggrgation to VP8 packetizer
This change introduces a new algorithm for aggregating small partitions into packets. The algorithm is based on a tree-search to find an optimal allocation of the packets, such that the difference in size between packets is minimized. The new method is used when partition aggregation is allowed and balanced packets are requested. Otherwise, the old method is used. The new method is implemented using the new classes Vp8PartitionAggregator and PartitionTreeNode. Both classes have dedicated unit tests. In order to facilitate the new algorithm, the packetizer was redesigned to calculate all packet sizes when the first packet is extracted. The information about all packets is stored in a packet queue structure, which is then popped for each packet extracted. Finally, a bug in the old packetizer algorithm was fixed. The bug caused a +/-1 error in packet sizes when balanced packets were produced. The unit test were updated accordingly. TEST=rtp_rtcp_unittests: PartitionTreeNode.* Vp8PartitionAggregator.* RtpFormatVp8Test.* Review URL: https://webrtc-codereview.appspot.com/345008 git-svn-id: http://webrtc.googlecode.com/svn/trunk@1447 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
1870ac1447
commit
7f2c2a5db2
@ -13,6 +13,9 @@
|
||||
#include <string.h> // memcpy
|
||||
|
||||
#include <cassert> // assert
|
||||
#include <vector>
|
||||
|
||||
#include "modules/rtp_rtcp/source/vp8_partition_aggregator.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
@ -33,17 +36,14 @@ RtpFormatVp8::RtpFormatVp8(const WebRtc_UWord8* payload_data,
|
||||
VP8PacketizerMode mode)
|
||||
: payload_data_(payload_data),
|
||||
payload_size_(static_cast<int>(payload_size)),
|
||||
payload_bytes_sent_(0),
|
||||
part_ix_(0),
|
||||
beginning_(true),
|
||||
first_fragment_(true),
|
||||
vp8_fixed_payload_descriptor_bytes_(1),
|
||||
aggr_mode_(aggr_modes_[mode]),
|
||||
balance_(balance_modes_[mode]),
|
||||
separate_first_(separate_first_modes_[mode]),
|
||||
hdr_info_(hdr_info),
|
||||
first_partition_in_packet_(0),
|
||||
max_payload_len_(max_payload_len) {
|
||||
num_partitions_(fragmentation.fragmentationVectorSize),
|
||||
max_payload_len_(max_payload_len),
|
||||
packets_calculated_(false) {
|
||||
part_info_ = fragmentation;
|
||||
}
|
||||
|
||||
@ -54,22 +54,48 @@ RtpFormatVp8::RtpFormatVp8(const WebRtc_UWord8* payload_data,
|
||||
: payload_data_(payload_data),
|
||||
payload_size_(static_cast<int>(payload_size)),
|
||||
part_info_(),
|
||||
payload_bytes_sent_(0),
|
||||
part_ix_(0),
|
||||
beginning_(true),
|
||||
first_fragment_(true),
|
||||
vp8_fixed_payload_descriptor_bytes_(1),
|
||||
aggr_mode_(aggr_modes_[kSloppy]),
|
||||
balance_(balance_modes_[kSloppy]),
|
||||
separate_first_(separate_first_modes_[kSloppy]),
|
||||
hdr_info_(hdr_info),
|
||||
first_partition_in_packet_(0),
|
||||
max_payload_len_(max_payload_len) {
|
||||
num_partitions_(1),
|
||||
max_payload_len_(max_payload_len),
|
||||
packets_calculated_(false) {
|
||||
part_info_.VerifyAndAllocateFragmentationHeader(1);
|
||||
part_info_.fragmentationLength[0] = payload_size;
|
||||
part_info_.fragmentationOffset[0] = 0;
|
||||
}
|
||||
|
||||
int RtpFormatVp8::NextPacket(WebRtc_UWord8* buffer,
|
||||
int* bytes_to_send,
|
||||
bool* last_packet) {
|
||||
if (!packets_calculated_) {
|
||||
int ret = 0;
|
||||
if (aggr_mode_ == kAggrPartitions && balance_) {
|
||||
ret = GeneratePacketsBalancedAggregates();
|
||||
} else {
|
||||
ret = GeneratePackets();
|
||||
}
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (packets_.empty()) {
|
||||
return -1;
|
||||
}
|
||||
InfoStruct packet_info = packets_.front();
|
||||
packets_.pop();
|
||||
|
||||
*bytes_to_send = WriteHeaderAndPayload(packet_info, buffer, max_payload_len_);
|
||||
if (*bytes_to_send < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*last_packet = packets_.empty();
|
||||
return packet_info.first_partition_ix;
|
||||
}
|
||||
|
||||
int RtpFormatVp8::CalcNextSize(int max_payload_len, int remaining_bytes,
|
||||
bool split_payload) const {
|
||||
if (max_payload_len == 0 || remaining_bytes == 0) {
|
||||
@ -93,73 +119,186 @@ int RtpFormatVp8::CalcNextSize(int max_payload_len, int remaining_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
int RtpFormatVp8::NextPacket(WebRtc_UWord8* buffer,
|
||||
int* bytes_to_send,
|
||||
bool* last_packet) {
|
||||
int RtpFormatVp8::GeneratePackets() {
|
||||
if (max_payload_len_ < vp8_fixed_payload_descriptor_bytes_
|
||||
+ PayloadDescriptorExtraLength() + 1) {
|
||||
// The provided payload length is not long enough for the payload
|
||||
// descriptor and one payload byte. Return an error.
|
||||
return -1;
|
||||
}
|
||||
const int num_partitions = part_info_.fragmentationVectorSize;
|
||||
int send_bytes = 0; // How much data to send in this packet.
|
||||
bool split_payload = true; // Splitting of partitions is initially allowed.
|
||||
int remaining_in_partition = part_info_.fragmentationOffset[part_ix_] -
|
||||
payload_bytes_sent_ + part_info_.fragmentationLength[part_ix_] +
|
||||
PayloadDescriptorExtraLength();
|
||||
int rem_payload_len = max_payload_len_ - vp8_fixed_payload_descriptor_bytes_;
|
||||
first_partition_in_packet_ = part_ix_;
|
||||
if (first_partition_in_packet_ > 8) return -1;
|
||||
int total_bytes_processed = 0;
|
||||
bool start_on_new_fragment = true;
|
||||
bool beginning = true;
|
||||
int part_ix = 0;
|
||||
while (total_bytes_processed < payload_size_) {
|
||||
int packet_bytes = 0; // How much data to send in this packet.
|
||||
bool split_payload = true; // Splitting of partitions is initially allowed.
|
||||
int remaining_in_partition = part_info_.fragmentationOffset[part_ix] -
|
||||
total_bytes_processed + part_info_.fragmentationLength[part_ix];
|
||||
int rem_payload_len = max_payload_len_ -
|
||||
(vp8_fixed_payload_descriptor_bytes_ + PayloadDescriptorExtraLength());
|
||||
int first_partition_in_packet = part_ix;
|
||||
|
||||
while (int next_size = CalcNextSize(rem_payload_len, remaining_in_partition,
|
||||
split_payload)) {
|
||||
send_bytes += next_size;
|
||||
rem_payload_len -= next_size;
|
||||
remaining_in_partition -= next_size;
|
||||
while (int next_size = CalcNextSize(rem_payload_len, remaining_in_partition,
|
||||
split_payload)) {
|
||||
packet_bytes += next_size;
|
||||
rem_payload_len -= next_size;
|
||||
remaining_in_partition -= next_size;
|
||||
|
||||
if (remaining_in_partition == 0 && !(beginning_ && separate_first_)) {
|
||||
// Advance to next partition?
|
||||
// Check that there are more partitions; verify that we are either
|
||||
// allowed to aggregate fragments, or that we are allowed to
|
||||
// aggregate intact partitions and that we started this packet
|
||||
// with an intact partition (indicated by first_fragment_ == true).
|
||||
if (part_ix_ + 1 < num_partitions &&
|
||||
((aggr_mode_ == kAggrFragments) ||
|
||||
(aggr_mode_ == kAggrPartitions && first_fragment_))) {
|
||||
remaining_in_partition
|
||||
= part_info_.fragmentationLength[++part_ix_];
|
||||
// Disallow splitting unless kAggrFragments. In kAggrPartitions,
|
||||
// we can only aggregate intact partitions.
|
||||
split_payload = (aggr_mode_ == kAggrFragments);
|
||||
if (remaining_in_partition == 0 && !(beginning && separate_first_)) {
|
||||
// Advance to next partition?
|
||||
// Check that there are more partitions; verify that we are either
|
||||
// allowed to aggregate fragments, or that we are allowed to
|
||||
// aggregate intact partitions and that we started this packet
|
||||
// with an intact partition (indicated by first_fragment_ == true).
|
||||
if (part_ix + 1 < num_partitions_ &&
|
||||
((aggr_mode_ == kAggrFragments) ||
|
||||
(aggr_mode_ == kAggrPartitions && start_on_new_fragment))) {
|
||||
assert(part_ix < num_partitions_);
|
||||
remaining_in_partition = part_info_.fragmentationLength[++part_ix];
|
||||
// Disallow splitting unless kAggrFragments. In kAggrPartitions,
|
||||
// we can only aggregate intact partitions.
|
||||
split_payload = (aggr_mode_ == kAggrFragments);
|
||||
}
|
||||
} else if (balance_ && remaining_in_partition > 0) {
|
||||
break;
|
||||
}
|
||||
} else if (balance_ && remaining_in_partition > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remaining_in_partition == 0) {
|
||||
++part_ix_; // Advance to next partition.
|
||||
}
|
||||
if (remaining_in_partition == 0) {
|
||||
++part_ix; // Advance to next partition.
|
||||
}
|
||||
assert(packet_bytes > 0);
|
||||
|
||||
send_bytes -= PayloadDescriptorExtraLength(); // Remove extra length again.
|
||||
assert(send_bytes > 0);
|
||||
// Write the payload header and the payload to buffer.
|
||||
*bytes_to_send = WriteHeaderAndPayload(send_bytes, buffer, max_payload_len_);
|
||||
if (*bytes_to_send < 0) {
|
||||
return -1;
|
||||
QueuePacket(total_bytes_processed, packet_bytes, first_partition_in_packet,
|
||||
start_on_new_fragment);
|
||||
total_bytes_processed += packet_bytes;
|
||||
start_on_new_fragment = (remaining_in_partition == 0);
|
||||
beginning = false; // Next packet cannot be first packet in frame.
|
||||
}
|
||||
|
||||
beginning_ = false; // Next packet cannot be first packet in frame.
|
||||
// Next packet starts new fragment if this ended one.
|
||||
first_fragment_ = (remaining_in_partition == 0);
|
||||
*last_packet = (payload_bytes_sent_ >= payload_size_);
|
||||
assert(!*last_packet || (payload_bytes_sent_ == payload_size_));
|
||||
return first_partition_in_packet_;
|
||||
packets_calculated_ = true;
|
||||
assert(total_bytes_processed == payload_size_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RtpFormatVp8::WriteHeaderAndPayload(int payload_bytes,
|
||||
int RtpFormatVp8::GeneratePacketsBalancedAggregates() {
|
||||
if (max_payload_len_ < vp8_fixed_payload_descriptor_bytes_
|
||||
+ PayloadDescriptorExtraLength() + 1) {
|
||||
// The provided payload length is not long enough for the payload
|
||||
// descriptor and one payload byte. Return an error.
|
||||
return -1;
|
||||
}
|
||||
std::vector<int> partition_decision;
|
||||
const int overhead = vp8_fixed_payload_descriptor_bytes_ +
|
||||
PayloadDescriptorExtraLength();
|
||||
const uint32_t max_payload_len = max_payload_len_ - overhead;
|
||||
int min_size, max_size;
|
||||
AggregateSmallPartitions(&partition_decision, &min_size, &max_size);
|
||||
|
||||
int total_bytes_processed = 0;
|
||||
int part_ix = 0;
|
||||
while (part_ix < num_partitions_) {
|
||||
if (partition_decision[part_ix] == -1) {
|
||||
// Split large partitions.
|
||||
int remaining_partition = part_info_.fragmentationLength[part_ix];
|
||||
int num_fragments = Vp8PartitionAggregator::CalcNumberOfFragments(
|
||||
remaining_partition, max_payload_len, overhead, min_size, max_size);
|
||||
const int packet_bytes =
|
||||
(remaining_partition + num_fragments - 1) / num_fragments;
|
||||
for (int n = 0; n < num_fragments; ++n) {
|
||||
const int this_packet_bytes = packet_bytes < remaining_partition ?
|
||||
packet_bytes : remaining_partition;
|
||||
QueuePacket(total_bytes_processed, this_packet_bytes, part_ix,
|
||||
(n == 0));
|
||||
remaining_partition -= this_packet_bytes;
|
||||
total_bytes_processed += this_packet_bytes;
|
||||
if (this_packet_bytes < min_size) {
|
||||
min_size = this_packet_bytes;
|
||||
}
|
||||
if (this_packet_bytes > max_size) {
|
||||
max_size = this_packet_bytes;
|
||||
}
|
||||
}
|
||||
assert(remaining_partition == 0);
|
||||
++part_ix;
|
||||
} else {
|
||||
int this_packet_bytes = 0;
|
||||
const int first_partition_in_packet = part_ix;
|
||||
const int aggregation_index = partition_decision[part_ix];
|
||||
while (static_cast<size_t>(part_ix) < partition_decision.size() &&
|
||||
partition_decision[part_ix] == aggregation_index) {
|
||||
// Collect all partitions that were aggregated into the same packet.
|
||||
this_packet_bytes += part_info_.fragmentationLength[part_ix];
|
||||
++part_ix;
|
||||
}
|
||||
QueuePacket(total_bytes_processed, this_packet_bytes,
|
||||
first_partition_in_packet, true);
|
||||
total_bytes_processed += this_packet_bytes;
|
||||
}
|
||||
}
|
||||
packets_calculated_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RtpFormatVp8::AggregateSmallPartitions(std::vector<int>* partition_vec,
|
||||
int* min_size,
|
||||
int* max_size) {
|
||||
assert(min_size && max_size);
|
||||
*min_size = -1;
|
||||
*max_size = -1;
|
||||
assert(partition_vec);
|
||||
partition_vec->assign(num_partitions_, -1);
|
||||
const int overhead = vp8_fixed_payload_descriptor_bytes_ +
|
||||
PayloadDescriptorExtraLength();
|
||||
const uint32_t max_payload_len = max_payload_len_ - overhead;
|
||||
int first_in_set = 0;
|
||||
int last_in_set = 0;
|
||||
int num_aggregate_packets = 0;
|
||||
// Find sets of partitions smaller than max_payload_len_.
|
||||
while (first_in_set < num_partitions_) {
|
||||
if (part_info_.fragmentationLength[first_in_set] < max_payload_len) {
|
||||
// Found start of a set.
|
||||
last_in_set = first_in_set;
|
||||
while (last_in_set + 1 < num_partitions_ &&
|
||||
part_info_.fragmentationLength[last_in_set + 1] < max_payload_len) {
|
||||
++last_in_set;
|
||||
}
|
||||
// Found end of a set. Run optimized aggregator. It is ok if start == end.
|
||||
Vp8PartitionAggregator aggregator(part_info_, first_in_set,
|
||||
last_in_set);
|
||||
if (*min_size >= 0 && *max_size >= 0) {
|
||||
aggregator.SetPriorMinMax(*min_size, *max_size);
|
||||
}
|
||||
Vp8PartitionAggregator::ConfigVec optimal_config =
|
||||
aggregator.FindOptimalConfiguration(max_payload_len, overhead);
|
||||
aggregator.CalcMinMax(optimal_config, min_size, max_size);
|
||||
for (int i = first_in_set, j = 0; i <= last_in_set; ++i, ++j) {
|
||||
// Transfer configuration for this set of partitions to the joint
|
||||
// partition vector representing all partitions in the frame.
|
||||
(*partition_vec)[i] = num_aggregate_packets + optimal_config[j];
|
||||
}
|
||||
num_aggregate_packets += optimal_config.back() + 1;
|
||||
first_in_set = last_in_set;
|
||||
}
|
||||
++first_in_set;
|
||||
}
|
||||
}
|
||||
|
||||
void RtpFormatVp8::QueuePacket(int start_pos,
|
||||
int packet_size,
|
||||
int first_partition_in_packet,
|
||||
bool start_on_new_fragment) {
|
||||
// Write info to packet info struct and store in packet info queue.
|
||||
InfoStruct packet_info;
|
||||
packet_info.payload_start_pos = start_pos;
|
||||
packet_info.size = packet_size;
|
||||
packet_info.first_partition_ix = first_partition_in_packet;
|
||||
packet_info.first_fragment = start_on_new_fragment;
|
||||
packets_.push(packet_info);
|
||||
}
|
||||
|
||||
int RtpFormatVp8::WriteHeaderAndPayload(const InfoStruct& packet_info,
|
||||
WebRtc_UWord8* buffer,
|
||||
int buffer_length) {
|
||||
int buffer_length) const {
|
||||
// Write the VP8 payload descriptor.
|
||||
// 0
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
@ -175,26 +314,20 @@ int RtpFormatVp8::WriteHeaderAndPayload(int payload_bytes,
|
||||
// T/K: |TID:Y| KEYIDX | (optional)
|
||||
// +-+-+-+-+-+-+-+-+-+
|
||||
|
||||
assert(payload_bytes > 0);
|
||||
assert(payload_bytes_sent_ + payload_bytes <= payload_size_);
|
||||
assert(vp8_fixed_payload_descriptor_bytes_ + PayloadDescriptorExtraLength()
|
||||
+ payload_bytes <= buffer_length);
|
||||
|
||||
assert(packet_info.size > 0);
|
||||
buffer[0] = 0;
|
||||
if (XFieldPresent()) buffer[0] |= kXBit;
|
||||
if (hdr_info_.nonReference) buffer[0] |= kNBit;
|
||||
if (first_fragment_) buffer[0] |= kSBit;
|
||||
buffer[0] |= (first_partition_in_packet_ & kPartIdField);
|
||||
if (XFieldPresent()) buffer[0] |= kXBit;
|
||||
if (hdr_info_.nonReference) buffer[0] |= kNBit;
|
||||
if (packet_info.first_fragment) buffer[0] |= kSBit;
|
||||
buffer[0] |= (packet_info.first_partition_ix & kPartIdField);
|
||||
|
||||
const int extension_length = WriteExtensionFields(buffer, buffer_length);
|
||||
|
||||
memcpy(&buffer[vp8_fixed_payload_descriptor_bytes_ + extension_length],
|
||||
&payload_data_[payload_bytes_sent_], payload_bytes);
|
||||
|
||||
payload_bytes_sent_ += payload_bytes;
|
||||
&payload_data_[packet_info.payload_start_pos], packet_info.size);
|
||||
|
||||
// Return total length of written data.
|
||||
return payload_bytes + vp8_fixed_payload_descriptor_bytes_
|
||||
return packet_info.size + vp8_fixed_payload_descriptor_bytes_
|
||||
+ extension_length;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,11 @@
|
||||
#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_H_
|
||||
#define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_H_
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/interface/module_common_types.h"
|
||||
#include "system_wrappers/interface/constructor_magic.h"
|
||||
#include "typedefs.h" // NOLINT(build/include)
|
||||
|
||||
namespace webrtc {
|
||||
@ -71,6 +75,13 @@ class RtpFormatVp8 {
|
||||
bool* last_packet);
|
||||
|
||||
private:
|
||||
typedef struct {
|
||||
int payload_start_pos;
|
||||
int size;
|
||||
bool first_fragment;
|
||||
int first_partition_ix;
|
||||
} InfoStruct;
|
||||
typedef std::queue<InfoStruct> InfoQueue;
|
||||
enum AggregationMode {
|
||||
kAggrNone = 0, // No aggregation.
|
||||
kAggrPartitions, // Aggregate intact partitions.
|
||||
@ -95,12 +106,38 @@ class RtpFormatVp8 {
|
||||
int CalcNextSize(int max_payload_len, int remaining_bytes,
|
||||
bool split_payload) const;
|
||||
|
||||
// Calculate all packet sizes and load to packet info queue.
|
||||
int GeneratePackets();
|
||||
|
||||
// Calculate all packet sizes using Vp8PartitionAggregator and load to packet
|
||||
// info queue.
|
||||
int GeneratePacketsBalancedAggregates();
|
||||
|
||||
// Helper function to GeneratePacketsBalancedAggregates(). Find all
|
||||
// continuous sets of partitions smaller than the max payload size (not
|
||||
// max_size), and aggregate them into balanced packets. The result is written
|
||||
// to partition_vec, which is of the same length as the number of partitions.
|
||||
// A value of -1 indicates that the partition is too large and must be split.
|
||||
// Aggregates are numbered 0, 1, 2, etc. For each set of small partitions,
|
||||
// the aggregate numbers restart at 0. Output values min_size and max_size
|
||||
// will hold the smallest and largest resulting aggregates (i.e., not counting
|
||||
// those that must be split).
|
||||
void AggregateSmallPartitions(std::vector<int>* partition_vec,
|
||||
int* min_size,
|
||||
int* max_size);
|
||||
|
||||
// Insert packet into packet queue.
|
||||
void QueuePacket(int start_pos,
|
||||
int packet_size,
|
||||
int first_partition_in_packet,
|
||||
bool start_on_new_fragment);
|
||||
|
||||
// Write the payload header and copy the payload to the buffer.
|
||||
// Will copy send_bytes bytes from the current position on the payload data.
|
||||
// last_fragment indicates that this packet ends with the last byte of a
|
||||
// partition.
|
||||
int WriteHeaderAndPayload(int send_bytes, WebRtc_UWord8* buffer,
|
||||
int buffer_length);
|
||||
// The info in packet_info determines which part of the payload is written
|
||||
// and what to write in the header fields.
|
||||
int WriteHeaderAndPayload(const InfoStruct& packet_info,
|
||||
WebRtc_UWord8* buffer,
|
||||
int buffer_length) const;
|
||||
|
||||
|
||||
// Write the X field and the appropriate extension fields to buffer.
|
||||
@ -147,18 +184,18 @@ class RtpFormatVp8 {
|
||||
const WebRtc_UWord8* payload_data_;
|
||||
const int payload_size_;
|
||||
RTPFragmentationHeader part_info_;
|
||||
int payload_bytes_sent_;
|
||||
int part_ix_;
|
||||
bool beginning_; // First partition in this frame.
|
||||
bool first_fragment_; // First fragment of a partition.
|
||||
const int vp8_fixed_payload_descriptor_bytes_; // Length of VP8 payload
|
||||
// descriptors's fixed part.
|
||||
AggregationMode aggr_mode_;
|
||||
bool balance_;
|
||||
bool separate_first_;
|
||||
const AggregationMode aggr_mode_;
|
||||
const bool balance_;
|
||||
const bool separate_first_;
|
||||
const RTPVideoHeaderVP8 hdr_info_;
|
||||
int first_partition_in_packet_;
|
||||
int max_payload_len_;
|
||||
const int num_partitions_;
|
||||
const int max_payload_len_;
|
||||
InfoQueue packets_;
|
||||
bool packets_calculated_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RtpFormatVp8);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -56,15 +56,15 @@ TEST_F(RtpFormatVp8Test, TestStrictMode) {
|
||||
|
||||
hdr_info_.pictureId = 200; // > 0x7F should produce 2-byte PictureID.
|
||||
const int kMaxSize = 13;
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kStrict);
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kStrict);
|
||||
|
||||
// The expected sizes are obtained by running a verified good implementation.
|
||||
const int kExpectedSizes[] = {8, 10, 12, 11, 13, 8, 11};
|
||||
const int kExpectedSizes[] = {9, 9, 12, 11, 11, 11, 10};
|
||||
const int kExpectedPart[] = {0, 0, 1, 2, 2, 2, 2};
|
||||
const bool kExpectedFragStart[] =
|
||||
{true, false, true, true, false, false, false};
|
||||
@ -87,15 +87,15 @@ TEST_F(RtpFormatVp8Test, TestAggregateMode) {
|
||||
|
||||
hdr_info_.pictureId = 20; // <= 0x7F should produce 1-byte PictureID.
|
||||
const int kMaxSize = 25;
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
|
||||
// The expected sizes are obtained by running a verified good implementation.
|
||||
const int kExpectedSizes[] = {22, 23, 24, 23};
|
||||
const int kExpectedSizes[] = {23, 23, 23, 23};
|
||||
const int kExpectedPart[] = {0, 0, 0, 1};
|
||||
const bool kExpectedFragStart[] = {true, false, false, true};
|
||||
const int kExpectedNum = sizeof(kExpectedSizes) / sizeof(kExpectedSizes[0]);
|
||||
@ -110,6 +110,66 @@ TEST_F(RtpFormatVp8Test, TestAggregateMode) {
|
||||
kExpectedFragStart, kExpectedNum);
|
||||
}
|
||||
|
||||
TEST_F(RtpFormatVp8Test, TestAggregateModeManyPartitions1) {
|
||||
const int kSizeVector[] = {1600, 200, 200, 200, 200, 200, 200, 200, 200};
|
||||
const int kNumPartitions = sizeof(kSizeVector) / sizeof(kSizeVector[0]);
|
||||
ASSERT_TRUE(Init(kSizeVector, kNumPartitions));
|
||||
|
||||
hdr_info_.pictureId = 20; // <= 0x7F should produce 1-byte PictureID.
|
||||
const int kMaxSize = 1500;
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
|
||||
// The expected sizes are obtained by running a verified good implementation.
|
||||
const int kExpectedSizes[] = {803, 803, 803, 803};
|
||||
const int kExpectedPart[] = {0, 0, 1, 5};
|
||||
const bool kExpectedFragStart[] = {true, false, true, true};
|
||||
const int kExpectedNum = sizeof(kExpectedSizes) / sizeof(kExpectedSizes[0]);
|
||||
COMPILE_ASSERT(kExpectedNum ==
|
||||
sizeof(kExpectedPart) / sizeof(kExpectedPart[0]),
|
||||
kExpectedPart_wrong_size);
|
||||
COMPILE_ASSERT(kExpectedNum ==
|
||||
sizeof(kExpectedFragStart) / sizeof(kExpectedFragStart[0]),
|
||||
kExpectedFragStart_wrong_size);
|
||||
|
||||
helper_->GetAllPacketsAndCheck(&packetizer, kExpectedSizes, kExpectedPart,
|
||||
kExpectedFragStart, kExpectedNum);
|
||||
}
|
||||
|
||||
TEST_F(RtpFormatVp8Test, TestAggregateModeManyPartitions2) {
|
||||
const int kSizeVector[] = {1599, 200, 200, 200, 1600, 200, 200, 200, 200};
|
||||
const int kNumPartitions = sizeof(kSizeVector) / sizeof(kSizeVector[0]);
|
||||
ASSERT_TRUE(Init(kSizeVector, kNumPartitions));
|
||||
|
||||
hdr_info_.pictureId = 20; // <= 0x7F should produce 1-byte PictureID.
|
||||
const int kMaxSize = 1500;
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
|
||||
// The expected sizes are obtained by running a verified good implementation.
|
||||
const int kExpectedSizes[] = {803, 802, 603, 803, 803, 803};
|
||||
const int kExpectedPart[] = {0, 0, 1, 4, 4, 5};
|
||||
const bool kExpectedFragStart[] = {true, false, true, true, false, true};
|
||||
const int kExpectedNum = sizeof(kExpectedSizes) / sizeof(kExpectedSizes[0]);
|
||||
COMPILE_ASSERT(kExpectedNum ==
|
||||
sizeof(kExpectedPart) / sizeof(kExpectedPart[0]),
|
||||
kExpectedPart_wrong_size);
|
||||
COMPILE_ASSERT(kExpectedNum ==
|
||||
sizeof(kExpectedFragStart) / sizeof(kExpectedFragStart[0]),
|
||||
kExpectedFragStart_wrong_size);
|
||||
|
||||
helper_->GetAllPacketsAndCheck(&packetizer, kExpectedSizes, kExpectedPart,
|
||||
kExpectedFragStart, kExpectedNum);
|
||||
}
|
||||
|
||||
TEST_F(RtpFormatVp8Test, TestSloppyMode) {
|
||||
const int kSizeVector[] = {10, 10, 10};
|
||||
const int kNumPartitions = sizeof(kSizeVector) / sizeof(kSizeVector[0]);
|
||||
@ -117,12 +177,12 @@ TEST_F(RtpFormatVp8Test, TestSloppyMode) {
|
||||
|
||||
hdr_info_.pictureId = kNoPictureId; // No PictureID.
|
||||
const int kMaxSize = 9;
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kSloppy);
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kSloppy);
|
||||
|
||||
// The expected sizes are obtained by running a verified good implementation.
|
||||
const int kExpectedSizes[] = {9, 9, 9, 7};
|
||||
@ -148,10 +208,10 @@ TEST_F(RtpFormatVp8Test, TestSloppyModeFallback) {
|
||||
|
||||
hdr_info_.pictureId = 200; // > 0x7F should produce 2-byte PictureID
|
||||
const int kMaxSize = 12; // Small enough to produce 4 packets.
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize);
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize);
|
||||
|
||||
// Expecting three full packets, and one with the remainder.
|
||||
const int kExpectedSizes[] = {12, 12, 12, 10};
|
||||
@ -179,10 +239,10 @@ TEST_F(RtpFormatVp8Test, TestNonReferenceBit) {
|
||||
|
||||
hdr_info_.nonReference = true;
|
||||
const int kMaxSize = 25; // Small enough to produce two packets.
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize);
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize);
|
||||
|
||||
// Sloppy mode => First packet full; other not.
|
||||
const int kExpectedSizes[] = {25, 7};
|
||||
@ -213,12 +273,12 @@ TEST_F(RtpFormatVp8Test, TestTl0PicIdxAndTID) {
|
||||
hdr_info_.layerSync = true;
|
||||
// kMaxSize is only limited by allocated buffer size.
|
||||
const int kMaxSize = helper_->buffer_size();
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
|
||||
// Expect one single packet of payload_size() + 4 bytes header.
|
||||
const int kExpectedSizes[1] = {helper_->payload_size() + 4};
|
||||
@ -245,12 +305,12 @@ TEST_F(RtpFormatVp8Test, TestKeyIdx) {
|
||||
hdr_info_.keyIdx = 17;
|
||||
// kMaxSize is only limited by allocated buffer size.
|
||||
const int kMaxSize = helper_->buffer_size();
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
|
||||
// Expect one single packet of payload_size() + 3 bytes header.
|
||||
const int kExpectedSizes[1] = {helper_->payload_size() + 3};
|
||||
@ -278,12 +338,12 @@ TEST_F(RtpFormatVp8Test, TestTIDAndKeyIdx) {
|
||||
hdr_info_.keyIdx = 5;
|
||||
// kMaxSize is only limited by allocated buffer size.
|
||||
const int kMaxSize = helper_->buffer_size();
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
RtpFormatVp8 packetizer(helper_->payload_data(),
|
||||
helper_->payload_size(),
|
||||
hdr_info_,
|
||||
kMaxSize,
|
||||
*(helper_->fragmentation()),
|
||||
kAggregate);
|
||||
|
||||
// Expect one single packet of payload_size() + 3 bytes header.
|
||||
const int kExpectedSizes[1] = {helper_->payload_size() + 3};
|
||||
|
@ -86,6 +86,8 @@
|
||||
'rtp_format_vp8.h',
|
||||
'transmission_bucket.cc',
|
||||
'transmission_bucket.h',
|
||||
'vp8_partition_aggregator.cc',
|
||||
'vp8_partition_aggregator.h',
|
||||
# Mocks
|
||||
'../mocks/mock_rtp_rtcp.h',
|
||||
], # source
|
||||
|
@ -31,6 +31,7 @@
|
||||
'rtp_sender_test.cc',
|
||||
'rtcp_sender_test.cc',
|
||||
'transmission_bucket_test.cc',
|
||||
'vp8_partition_aggregator_unittest.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -252,7 +252,7 @@ TEST(ParseVP8Test, TestWithPacketizer) {
|
||||
inputHeader.layerSync = false;
|
||||
inputHeader.tl0PicIdx = kNoTl0PicIdx; // Disable.
|
||||
inputHeader.keyIdx = 31;
|
||||
RtpFormatVp8 packetizer = RtpFormatVp8(payload, 10, inputHeader, 20);
|
||||
RtpFormatVp8 packetizer(payload, 10, inputHeader, 20);
|
||||
bool last;
|
||||
int send_bytes;
|
||||
ASSERT_EQ(0, packetizer.NextPacket(packet, &send_bytes, &last));
|
||||
|
261
src/modules/rtp_rtcp/source/vp8_partition_aggregator.cc
Normal file
261
src/modules/rtp_rtcp/source/vp8_partition_aggregator.cc
Normal file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/rtp_rtcp/source/vp8_partition_aggregator.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h> // NULL
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
PartitionTreeNode::PartitionTreeNode(PartitionTreeNode* parent,
|
||||
const int* size_vector,
|
||||
int num_partitions,
|
||||
int this_size)
|
||||
: parent_(parent),
|
||||
this_size_(this_size),
|
||||
size_vector_(size_vector),
|
||||
num_partitions_(num_partitions),
|
||||
max_parent_size_(0),
|
||||
min_parent_size_(std::numeric_limits<int>::max()),
|
||||
packet_start_(false) {
|
||||
assert(num_partitions >= 0);
|
||||
children_[kLeftChild] = NULL;
|
||||
children_[kRightChild] = NULL;
|
||||
}
|
||||
|
||||
PartitionTreeNode* PartitionTreeNode::CreateRootNode(const int* size_vector,
|
||||
int num_partitions) {
|
||||
PartitionTreeNode* root_node =
|
||||
new PartitionTreeNode(NULL, &size_vector[1], num_partitions - 1,
|
||||
size_vector[0]);
|
||||
root_node->set_packet_start(true);
|
||||
return root_node;
|
||||
}
|
||||
|
||||
PartitionTreeNode::~PartitionTreeNode() {
|
||||
delete children_[kLeftChild];
|
||||
delete children_[kRightChild];
|
||||
}
|
||||
|
||||
int PartitionTreeNode::Cost(int penalty) {
|
||||
assert(penalty >= 0);
|
||||
int cost = 0;
|
||||
if (num_partitions_ == 0) {
|
||||
// This is a solution node.
|
||||
cost = std::max(max_parent_size_, this_size_) -
|
||||
std::min(min_parent_size_, this_size_);
|
||||
} else {
|
||||
cost = std::max(max_parent_size_, this_size_) - min_parent_size_;
|
||||
}
|
||||
return cost + NumPackets() * penalty;
|
||||
}
|
||||
|
||||
bool PartitionTreeNode::CreateChildren(int max_size) {
|
||||
assert(max_size > 0);
|
||||
bool children_created = false;
|
||||
if (num_partitions_ > 0) {
|
||||
if (this_size_ + size_vector_[0] <= max_size) {
|
||||
assert(!children_[kLeftChild]);
|
||||
children_[kLeftChild] =
|
||||
new PartitionTreeNode(this,
|
||||
&size_vector_[1],
|
||||
num_partitions_ - 1,
|
||||
this_size_ + size_vector_[0]);
|
||||
children_[kLeftChild]->set_max_parent_size(max_parent_size_);
|
||||
children_[kLeftChild]->set_min_parent_size(min_parent_size_);
|
||||
// "Left" child is continuation of same packet.
|
||||
children_[kLeftChild]->set_packet_start(false);
|
||||
children_created = true;
|
||||
}
|
||||
if (this_size_ > 0) {
|
||||
assert(!children_[kRightChild]);
|
||||
children_[kRightChild] = new PartitionTreeNode(this,
|
||||
&size_vector_[1],
|
||||
num_partitions_ - 1,
|
||||
size_vector_[0]);
|
||||
children_[kRightChild]->set_max_parent_size(
|
||||
std::max(max_parent_size_, this_size_));
|
||||
children_[kRightChild]->set_min_parent_size(
|
||||
std::min(min_parent_size_, this_size_));
|
||||
// "Right" child starts a new packet.
|
||||
children_[kRightChild]->set_packet_start(true);
|
||||
children_created = true;
|
||||
}
|
||||
}
|
||||
return children_created;
|
||||
}
|
||||
|
||||
int PartitionTreeNode::NumPackets() {
|
||||
if (parent_ == NULL) {
|
||||
// Root node is a "right" child by definition.
|
||||
return 1;
|
||||
}
|
||||
if (parent_->children_[kLeftChild] == this) {
|
||||
// This is a "left" child.
|
||||
return parent_->NumPackets();
|
||||
} else {
|
||||
// This is a "right" child.
|
||||
return 1 + parent_->NumPackets();
|
||||
}
|
||||
}
|
||||
|
||||
PartitionTreeNode* PartitionTreeNode::GetOptimalNode(int max_size,
|
||||
int penalty) {
|
||||
CreateChildren(max_size);
|
||||
PartitionTreeNode* left = children_[kLeftChild];
|
||||
PartitionTreeNode* right = children_[kRightChild];
|
||||
if ((left == NULL) && (right == NULL)) {
|
||||
// This is a solution node; return it.
|
||||
return this;
|
||||
} else if (left == NULL) {
|
||||
// One child empty, return the other.
|
||||
return right->GetOptimalNode(max_size, penalty);
|
||||
} else if (right == NULL) {
|
||||
// One child empty, return the other.
|
||||
return left->GetOptimalNode(max_size, penalty);
|
||||
} else {
|
||||
PartitionTreeNode* first;
|
||||
PartitionTreeNode* second;
|
||||
if (left->Cost(penalty) <= right->Cost(penalty)) {
|
||||
first = left;
|
||||
second = right;
|
||||
} else {
|
||||
first = right;
|
||||
second = left;
|
||||
}
|
||||
first = first->GetOptimalNode(max_size, penalty);
|
||||
if (second->Cost(penalty) <= first->Cost(penalty)) {
|
||||
second = second->GetOptimalNode(max_size, penalty);
|
||||
// Compare cost estimate for "second" with actual cost for "first".
|
||||
if (second->Cost(penalty) < first->Cost(penalty)) {
|
||||
return second;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
}
|
||||
|
||||
Vp8PartitionAggregator::Vp8PartitionAggregator(
|
||||
const RTPFragmentationHeader& fragmentation,
|
||||
int first_partition_idx, int last_partition_idx)
|
||||
: root_(NULL),
|
||||
num_partitions_(last_partition_idx - first_partition_idx + 1),
|
||||
size_vector_(new int[num_partitions_]),
|
||||
largest_partition_size_(0) {
|
||||
assert(first_partition_idx >= 0);
|
||||
assert(last_partition_idx >= first_partition_idx);
|
||||
assert(last_partition_idx < fragmentation.fragmentationVectorSize);
|
||||
for (size_t i = 0; i < num_partitions_; ++i) {
|
||||
size_vector_[i] =
|
||||
fragmentation.fragmentationLength[i + first_partition_idx];
|
||||
largest_partition_size_ = std::max(largest_partition_size_,
|
||||
size_vector_[i]);
|
||||
}
|
||||
root_ = PartitionTreeNode::CreateRootNode(size_vector_, num_partitions_);
|
||||
}
|
||||
|
||||
Vp8PartitionAggregator::~Vp8PartitionAggregator() {
|
||||
delete [] size_vector_;
|
||||
delete root_;
|
||||
}
|
||||
|
||||
void Vp8PartitionAggregator::SetPriorMinMax(int min_size, int max_size) {
|
||||
assert(root_);
|
||||
assert(min_size >= 0);
|
||||
assert(max_size >= min_size);
|
||||
root_->set_min_parent_size(min_size);
|
||||
root_->set_max_parent_size(max_size);
|
||||
}
|
||||
|
||||
Vp8PartitionAggregator::ConfigVec
|
||||
Vp8PartitionAggregator::FindOptimalConfiguration(int max_size, int penalty) {
|
||||
assert(root_);
|
||||
assert(max_size >= largest_partition_size_);
|
||||
PartitionTreeNode* opt = root_->GetOptimalNode(max_size, penalty);
|
||||
ConfigVec config_vector(num_partitions_, 0);
|
||||
PartitionTreeNode* temp_node = opt;
|
||||
int packet_index = opt->NumPackets() - 1;
|
||||
for (int i = num_partitions_ - 1; i >= 0; --i) {
|
||||
assert(packet_index >= 0);
|
||||
assert(temp_node != NULL);
|
||||
config_vector[i] = packet_index;
|
||||
if (temp_node->packet_start()) --packet_index;
|
||||
temp_node = temp_node->parent();
|
||||
}
|
||||
return config_vector;
|
||||
}
|
||||
|
||||
void Vp8PartitionAggregator::CalcMinMax(const ConfigVec& config,
|
||||
int* min_size, int* max_size) const {
|
||||
if (*min_size < 0) {
|
||||
*min_size = std::numeric_limits<int>::max();
|
||||
}
|
||||
if (*max_size < 0) {
|
||||
*max_size = 0;
|
||||
}
|
||||
unsigned int i = 0;
|
||||
while (i < config.size()) {
|
||||
int this_size = 0;
|
||||
unsigned int j = i;
|
||||
while (j < config.size() && config[i] == config[j]) {
|
||||
this_size += size_vector_[j];
|
||||
++j;
|
||||
}
|
||||
i = j;
|
||||
if (this_size < *min_size) {
|
||||
*min_size = this_size;
|
||||
}
|
||||
if (this_size > *max_size) {
|
||||
*max_size = this_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Vp8PartitionAggregator::CalcNumberOfFragments(int large_partition_size,
|
||||
int max_payload_size,
|
||||
int penalty,
|
||||
int min_size,
|
||||
int max_size) {
|
||||
assert(max_size <= max_payload_size);
|
||||
assert(min_size <= max_size);
|
||||
// Divisions with rounding up.
|
||||
const int min_number_of_fragments =
|
||||
(large_partition_size + max_payload_size - 1) / max_payload_size;
|
||||
const int max_number_of_fragments =
|
||||
(large_partition_size + min_size - 1) / min_size;
|
||||
int num_fragments = -1;
|
||||
int best_cost = std::numeric_limits<int>::max();
|
||||
for (int n = min_number_of_fragments; n <= max_number_of_fragments; ++n) {
|
||||
// Round up so that we use the largest fragment.
|
||||
int fragment_size = (large_partition_size + n - 1) / n;
|
||||
int cost = 0;
|
||||
if (fragment_size < min_size) {
|
||||
cost = min_size - fragment_size + n * penalty;
|
||||
} else if (fragment_size > max_size) {
|
||||
cost = fragment_size - max_size + n * penalty;
|
||||
} else {
|
||||
cost = n * penalty;
|
||||
}
|
||||
if (fragment_size <= max_payload_size && cost < best_cost) {
|
||||
num_fragments = n;
|
||||
best_cost = cost;
|
||||
}
|
||||
}
|
||||
assert(num_fragments > 0);
|
||||
assert(large_partition_size / num_fragments + 1 <= max_payload_size);
|
||||
return num_fragments;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
135
src/modules/rtp_rtcp/source/vp8_partition_aggregator.h
Normal file
135
src/modules/rtp_rtcp/source/vp8_partition_aggregator.h
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_VP8_PARTITION_AGGREGATOR_H_
|
||||
#define WEBRTC_MODULES_RTP_RTCP_SOURCE_VP8_PARTITION_AGGREGATOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "modules/interface/module_common_types.h"
|
||||
#include "system_wrappers/interface/constructor_magic.h"
|
||||
#include "typedefs.h" // NOLINT(build/include)
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Class used to solve the VP8 aggregation problem.
|
||||
class PartitionTreeNode {
|
||||
public:
|
||||
// Create a tree node.
|
||||
PartitionTreeNode(PartitionTreeNode* parent,
|
||||
const int* size_vector,
|
||||
int num_partitions,
|
||||
int this_size);
|
||||
|
||||
// Create a root node.
|
||||
static PartitionTreeNode* CreateRootNode(const int* size_vector,
|
||||
int num_partitions);
|
||||
|
||||
~PartitionTreeNode();
|
||||
|
||||
// Calculate the cost for the node. If the node is a solution node, the cost
|
||||
// will be the actual cost associated with that solution. If not, the cost
|
||||
// will be the cost accumulated so far along the current branch (which is a
|
||||
// lower bound for any solution along the branch).
|
||||
int Cost(int penalty);
|
||||
|
||||
// Create the two children for this node.
|
||||
bool CreateChildren(int max_size);
|
||||
|
||||
// Get the number of packets for the configuration that this node represents.
|
||||
int NumPackets();
|
||||
|
||||
// Find the optimal solution given a maximum packet size and a per-packet
|
||||
// penalty. The method will be recursively called while the solver is
|
||||
// probing down the tree of nodes.
|
||||
PartitionTreeNode* GetOptimalNode(int max_size, int penalty);
|
||||
|
||||
// Setters and getters.
|
||||
void set_max_parent_size(int size) { max_parent_size_ = size; }
|
||||
void set_min_parent_size(int size) { min_parent_size_ = size; }
|
||||
PartitionTreeNode* parent() const { return parent_; }
|
||||
PartitionTreeNode* left_child() const { return children_[kLeftChild]; }
|
||||
PartitionTreeNode* right_child() const { return children_[kRightChild]; }
|
||||
int this_size() const { return this_size_; }
|
||||
bool packet_start() const { return packet_start_; }
|
||||
|
||||
private:
|
||||
enum Children {
|
||||
kLeftChild = 0,
|
||||
kRightChild = 1
|
||||
};
|
||||
|
||||
void set_packet_start(bool value) { packet_start_ = value; }
|
||||
|
||||
PartitionTreeNode* parent_;
|
||||
PartitionTreeNode* children_[2];
|
||||
int this_size_;
|
||||
const int* size_vector_;
|
||||
int num_partitions_;
|
||||
int max_parent_size_;
|
||||
int min_parent_size_;
|
||||
bool packet_start_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PartitionTreeNode);
|
||||
};
|
||||
|
||||
// Class that calculates the optimal aggregation of VP8 partitions smaller than
|
||||
// the maximum packet size.
|
||||
class Vp8PartitionAggregator {
|
||||
public:
|
||||
typedef std::vector<int> ConfigVec;
|
||||
|
||||
// Constructor. All partitions in the fragmentation header from index
|
||||
// first_partition_idx to last_partition_idx must be smaller than
|
||||
// maximum packet size to be used in FindOptimalConfiguration.
|
||||
Vp8PartitionAggregator(const RTPFragmentationHeader& fragmentation,
|
||||
int first_partition_idx, int last_partition_idx);
|
||||
|
||||
~Vp8PartitionAggregator();
|
||||
|
||||
// Set the smallest and largest payload sizes produces so far.
|
||||
void SetPriorMinMax(int min_size, int max_size);
|
||||
|
||||
// Find the aggregation of VP8 partitions that produces the smallest cost.
|
||||
// The result is given as a vector of the same length as the number of
|
||||
// partitions given to the constructor (i.e., last_partition_idx -
|
||||
// first_partition_idx + 1), where each element indicates the packet index
|
||||
// for that partition. Thus, the output vector starts at 0 and is increasing
|
||||
// up to the number of packets - 1.
|
||||
ConfigVec FindOptimalConfiguration(int max_size, int penalty);
|
||||
|
||||
// Calculate minimum and maximum packet sizes for a given aggregation config.
|
||||
// The extreme packet sizes of the given aggregation are compared with the
|
||||
// values given in min_size and max_size, and if either of these are exceeded,
|
||||
// the new extreme value will be written to the corresponding variable.
|
||||
void CalcMinMax(const ConfigVec& config, int* min_size, int* max_size) const;
|
||||
|
||||
// Calculate the number of fragments to divide a large partition into.
|
||||
// The large partition is of size large_partition_size. The payload must not
|
||||
// be larger than max_payload_size. Each fragment comes at an overhead cost
|
||||
// of penalty bytes. If the size of the fragments fall outside the range
|
||||
// [min_size, max_size], an extra cost is inflicted.
|
||||
static int CalcNumberOfFragments(int large_partition_size,
|
||||
int max_payload_size,
|
||||
int penalty,
|
||||
int min_size,
|
||||
int max_size);
|
||||
|
||||
private:
|
||||
PartitionTreeNode* root_;
|
||||
size_t num_partitions_;
|
||||
int* size_vector_;
|
||||
int largest_partition_size_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Vp8PartitionAggregator);
|
||||
};
|
||||
} // namespace
|
||||
|
||||
#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_VP8_PARTITION_AGGREGATOR_H_
|
215
src/modules/rtp_rtcp/source/vp8_partition_aggregator_unittest.cc
Normal file
215
src/modules/rtp_rtcp/source/vp8_partition_aggregator_unittest.cc
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <stdlib.h> // NULL
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "modules/rtp_rtcp/source/vp8_partition_aggregator.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST(PartitionTreeNode, CreateAndDelete) {
|
||||
const int kVector[] = {1, 2, 3};
|
||||
const int kNumPartitions = sizeof(kVector) / sizeof(kVector[0]);
|
||||
PartitionTreeNode* node1 =
|
||||
PartitionTreeNode::CreateRootNode(kVector, kNumPartitions);
|
||||
PartitionTreeNode* node2 =
|
||||
new PartitionTreeNode(node1, kVector, kNumPartitions, 17);
|
||||
delete node1;
|
||||
delete node2;
|
||||
}
|
||||
|
||||
TEST(PartitionTreeNode, CreateChildrenAndDelete) {
|
||||
const int kVector[] = {1, 2, 3};
|
||||
const int kNumPartitions = sizeof(kVector) / sizeof(kVector[0]);
|
||||
const int kMaxSize = 10;
|
||||
const int kPenalty = 5;
|
||||
PartitionTreeNode* root =
|
||||
PartitionTreeNode::CreateRootNode(kVector, kNumPartitions);
|
||||
EXPECT_TRUE(root->CreateChildren(kMaxSize));
|
||||
ASSERT_TRUE(NULL != root->left_child());
|
||||
ASSERT_TRUE(NULL != root->right_child());
|
||||
EXPECT_EQ(3, root->left_child()->this_size());
|
||||
EXPECT_EQ(2, root->right_child()->this_size());
|
||||
EXPECT_EQ(11, root->right_child()->Cost(kPenalty));
|
||||
EXPECT_FALSE(root->left_child()->packet_start());
|
||||
EXPECT_TRUE(root->right_child()->packet_start());
|
||||
delete root;
|
||||
}
|
||||
|
||||
TEST(PartitionTreeNode, FindOptimalConfig) {
|
||||
const int kVector[] = {197, 194, 213, 215, 184, 199, 197, 207};
|
||||
const int kNumPartitions = sizeof(kVector) / sizeof(kVector[0]);
|
||||
const int kMaxSize = 1500;
|
||||
const int kPenalty = 1;
|
||||
PartitionTreeNode* root =
|
||||
PartitionTreeNode::CreateRootNode(kVector, kNumPartitions);
|
||||
root->set_max_parent_size(500);
|
||||
root->set_min_parent_size(300);
|
||||
PartitionTreeNode* opt = root->GetOptimalNode(kMaxSize, kPenalty);
|
||||
ASSERT_TRUE(opt != NULL);
|
||||
EXPECT_EQ(4, opt->NumPackets());
|
||||
// Expect optimal sequence to be {1, 0, 1, 0, 1, 0, 1, 0}, which corresponds
|
||||
// to (right)-left-right-left-right-left-right-left, where the root node is
|
||||
// implicitly a "right" node by definition.
|
||||
EXPECT_TRUE(opt->parent()->parent()->parent()->parent()->parent()->
|
||||
parent()->parent()->packet_start());
|
||||
EXPECT_FALSE(opt->parent()->parent()->parent()->parent()->parent()->
|
||||
parent()->packet_start());
|
||||
EXPECT_TRUE(opt->parent()->parent()->parent()->parent()->parent()->
|
||||
packet_start());
|
||||
EXPECT_FALSE(opt->parent()->parent()->parent()->parent()->packet_start());
|
||||
EXPECT_TRUE(opt->parent()->parent()->parent()->packet_start());
|
||||
EXPECT_FALSE(opt->parent()->parent()->packet_start());
|
||||
EXPECT_TRUE(opt->parent()->packet_start());
|
||||
EXPECT_FALSE(opt->packet_start());
|
||||
EXPECT_TRUE(opt == root->left_child()->right_child()->left_child()->
|
||||
right_child()->left_child()->right_child()->left_child());
|
||||
delete root;
|
||||
}
|
||||
|
||||
TEST(PartitionTreeNode, FindOptimalConfigSinglePartition) {
|
||||
const int kVector[] = {17};
|
||||
const int kNumPartitions = sizeof(kVector) / sizeof(kVector[0]);
|
||||
const int kMaxSize = 1500;
|
||||
const int kPenalty = 1;
|
||||
PartitionTreeNode* root =
|
||||
PartitionTreeNode::CreateRootNode(kVector, kNumPartitions);
|
||||
PartitionTreeNode* opt = root->GetOptimalNode(kMaxSize, kPenalty);
|
||||
ASSERT_TRUE(opt != NULL);
|
||||
EXPECT_EQ(1, opt->NumPackets());
|
||||
EXPECT_TRUE(opt == root);
|
||||
delete root;
|
||||
}
|
||||
|
||||
static void VerifyConfiguration(const int* expected_config,
|
||||
size_t expected_config_len,
|
||||
const std::vector<int>& opt_config,
|
||||
const RTPFragmentationHeader& fragmentation) {
|
||||
ASSERT_EQ(expected_config_len, fragmentation.fragmentationVectorSize);
|
||||
EXPECT_EQ(expected_config_len, opt_config.size());
|
||||
for (size_t i = 0; i < expected_config_len; ++i) {
|
||||
EXPECT_EQ(expected_config[i], opt_config[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void VerifyMinMax(const Vp8PartitionAggregator& aggregator,
|
||||
const std::vector<int>& opt_config,
|
||||
int expected_min,
|
||||
int expected_max) {
|
||||
int min_size = -1;
|
||||
int max_size = -1;
|
||||
aggregator.CalcMinMax(opt_config, &min_size, &max_size);
|
||||
EXPECT_EQ(expected_min, min_size);
|
||||
EXPECT_EQ(expected_max, max_size);
|
||||
}
|
||||
|
||||
TEST(Vp8PartitionAggregator, CreateAndDelete) {
|
||||
RTPFragmentationHeader fragmentation;
|
||||
fragmentation.VerifyAndAllocateFragmentationHeader(3);
|
||||
Vp8PartitionAggregator* aggregator =
|
||||
new Vp8PartitionAggregator(fragmentation, 0, 2);
|
||||
delete aggregator;
|
||||
}
|
||||
|
||||
TEST(Vp8PartitionAggregator, FindOptimalConfig) {
|
||||
RTPFragmentationHeader fragmentation;
|
||||
fragmentation.VerifyAndAllocateFragmentationHeader(8);
|
||||
fragmentation.fragmentationLength[0] = 197;
|
||||
fragmentation.fragmentationLength[1] = 194;
|
||||
fragmentation.fragmentationLength[2] = 213;
|
||||
fragmentation.fragmentationLength[3] = 215;
|
||||
fragmentation.fragmentationLength[4] = 184;
|
||||
fragmentation.fragmentationLength[5] = 199;
|
||||
fragmentation.fragmentationLength[6] = 197;
|
||||
fragmentation.fragmentationLength[7] = 207;
|
||||
Vp8PartitionAggregator* aggregator =
|
||||
new Vp8PartitionAggregator(fragmentation, 0, 7);
|
||||
aggregator->SetPriorMinMax(300, 500);
|
||||
int kMaxSize = 1500;
|
||||
int kPenalty = 1;
|
||||
std::vector<int> opt_config = aggregator->FindOptimalConfiguration(kMaxSize,
|
||||
kPenalty);
|
||||
const int kExpectedConfig[] = {0, 0, 1, 1, 2, 2, 3, 3};
|
||||
const size_t kExpectedConfigSize =
|
||||
sizeof(kExpectedConfig) / sizeof(kExpectedConfig[0]);
|
||||
VerifyConfiguration(kExpectedConfig, kExpectedConfigSize, opt_config,
|
||||
fragmentation);
|
||||
VerifyMinMax(*aggregator, opt_config, 383, 428);
|
||||
// Change min and max and run method again. This time, we expect it to leave
|
||||
// the values unchanged.
|
||||
int min_size = 382;
|
||||
int max_size = 429;
|
||||
aggregator->CalcMinMax(opt_config, &min_size, &max_size);
|
||||
EXPECT_EQ(382, min_size);
|
||||
EXPECT_EQ(429, max_size);
|
||||
delete aggregator;
|
||||
}
|
||||
|
||||
TEST(Vp8PartitionAggregator, FindOptimalConfigEqualFragments) {
|
||||
RTPFragmentationHeader fragmentation;
|
||||
fragmentation.VerifyAndAllocateFragmentationHeader(8);
|
||||
fragmentation.fragmentationLength[0] = 200;
|
||||
fragmentation.fragmentationLength[1] = 200;
|
||||
fragmentation.fragmentationLength[2] = 200;
|
||||
fragmentation.fragmentationLength[3] = 200;
|
||||
fragmentation.fragmentationLength[4] = 200;
|
||||
fragmentation.fragmentationLength[5] = 200;
|
||||
fragmentation.fragmentationLength[6] = 200;
|
||||
fragmentation.fragmentationLength[7] = 200;
|
||||
Vp8PartitionAggregator* aggregator =
|
||||
new Vp8PartitionAggregator(fragmentation, 0, 7);
|
||||
int kMaxSize = 1500;
|
||||
int kPenalty = 1;
|
||||
std::vector<int> opt_config = aggregator->FindOptimalConfiguration(kMaxSize,
|
||||
kPenalty);
|
||||
const int kExpectedConfig[] = {0, 0, 0, 0, 1, 1, 1, 1};
|
||||
const size_t kExpectedConfigSize =
|
||||
sizeof(kExpectedConfig) / sizeof(kExpectedConfig[0]);
|
||||
VerifyConfiguration(kExpectedConfig, kExpectedConfigSize, opt_config,
|
||||
fragmentation);
|
||||
VerifyMinMax(*aggregator, opt_config, 800, 800);
|
||||
delete aggregator;
|
||||
}
|
||||
|
||||
TEST(Vp8PartitionAggregator, FindOptimalConfigSinglePartition) {
|
||||
RTPFragmentationHeader fragmentation;
|
||||
fragmentation.VerifyAndAllocateFragmentationHeader(1);
|
||||
fragmentation.fragmentationLength[0] = 17;
|
||||
Vp8PartitionAggregator* aggregator =
|
||||
new Vp8PartitionAggregator(fragmentation, 0, 0);
|
||||
int kMaxSize = 1500;
|
||||
int kPenalty = 1;
|
||||
std::vector<int> opt_config = aggregator->FindOptimalConfiguration(kMaxSize,
|
||||
kPenalty);
|
||||
const int kExpectedConfig[] = {0};
|
||||
const size_t kExpectedConfigSize =
|
||||
sizeof(kExpectedConfig) / sizeof(kExpectedConfig[0]);
|
||||
VerifyConfiguration(kExpectedConfig, kExpectedConfigSize, opt_config,
|
||||
fragmentation);
|
||||
VerifyMinMax(*aggregator, opt_config, 17, 17);
|
||||
delete aggregator;
|
||||
}
|
||||
|
||||
TEST(Vp8PartitionAggregator, TestCalcNumberOfFragments) {
|
||||
const int kMTU = 1500;
|
||||
EXPECT_EQ(2,
|
||||
Vp8PartitionAggregator::CalcNumberOfFragments(
|
||||
1600, kMTU, 1, 300, 900));
|
||||
EXPECT_EQ(3,
|
||||
Vp8PartitionAggregator::CalcNumberOfFragments(
|
||||
1600, kMTU, 1, 300, 798));
|
||||
EXPECT_EQ(2,
|
||||
Vp8PartitionAggregator::CalcNumberOfFragments(
|
||||
1600, kMTU, 1, 900, 1000));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
Loading…
x
Reference in New Issue
Block a user