diff --git a/src/modules/rtp_rtcp/source/rtp_format_vp8.cc b/src/modules/rtp_rtcp/source/rtp_format_vp8.cc index 0654a1678c..c81fab994c 100644 --- a/src/modules/rtp_rtcp/source/rtp_format_vp8.cc +++ b/src/modules/rtp_rtcp/source/rtp_format_vp8.cc @@ -13,6 +13,9 @@ #include // memcpy #include // assert +#include + +#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(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(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 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(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* 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; } diff --git a/src/modules/rtp_rtcp/source/rtp_format_vp8.h b/src/modules/rtp_rtcp/source/rtp_format_vp8.h index f736307b1f..31cb0cd6a7 100644 --- a/src/modules/rtp_rtcp/source/rtp_format_vp8.h +++ b/src/modules/rtp_rtcp/source/rtp_format_vp8.h @@ -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 +#include + #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 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* 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 diff --git a/src/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc b/src/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc index 11060e0d57..9497bfa5f9 100644 --- a/src/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc +++ b/src/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc @@ -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}; diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp.gypi b/src/modules/rtp_rtcp/source/rtp_rtcp.gypi index 5bd67d7fb9..48fc05f2ad 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp.gypi +++ b/src/modules/rtp_rtcp/source/rtp_rtcp.gypi @@ -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 diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi index a93ff8b3ed..a450b2ee02 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi @@ -31,6 +31,7 @@ 'rtp_sender_test.cc', 'rtcp_sender_test.cc', 'transmission_bucket_test.cc', + 'vp8_partition_aggregator_unittest.cc', ], }, ], diff --git a/src/modules/rtp_rtcp/source/rtp_utility_test.cc b/src/modules/rtp_rtcp/source/rtp_utility_test.cc index 5446dbb0ec..eabc812980 100644 --- a/src/modules/rtp_rtcp/source/rtp_utility_test.cc +++ b/src/modules/rtp_rtcp/source/rtp_utility_test.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)); diff --git a/src/modules/rtp_rtcp/source/vp8_partition_aggregator.cc b/src/modules/rtp_rtcp/source/vp8_partition_aggregator.cc new file mode 100644 index 0000000000..9fcadad5db --- /dev/null +++ b/src/modules/rtp_rtcp/source/vp8_partition_aggregator.cc @@ -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 +#include // NULL + +#include +#include + +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::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::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::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 + diff --git a/src/modules/rtp_rtcp/source/vp8_partition_aggregator.h b/src/modules/rtp_rtcp/source/vp8_partition_aggregator.h new file mode 100644 index 0000000000..c5d47ded1b --- /dev/null +++ b/src/modules/rtp_rtcp/source/vp8_partition_aggregator.h @@ -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 + +#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 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_ diff --git a/src/modules/rtp_rtcp/source/vp8_partition_aggregator_unittest.cc b/src/modules/rtp_rtcp/source/vp8_partition_aggregator_unittest.cc new file mode 100644 index 0000000000..3c274d1852 --- /dev/null +++ b/src/modules/rtp_rtcp/source/vp8_partition_aggregator_unittest.cc @@ -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 // 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& 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& 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 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 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 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 +