From 0c32a8d65eb2ad7eb46b4e13db72f04e47bc5d6a Mon Sep 17 00:00:00 2001 From: "hlundin@google.com" Date: Wed, 15 Jun 2011 07:43:28 +0000 Subject: [PATCH] VP8 RTP packetizer rewrite Rewriting the RTP packetizer for VP8 to accommodate more functionality. This CL does not change the formatting other than that the kStrict mode now produces equal-sized fragments. Review URL: http://webrtc-codereview.appspot.com/33006 git-svn-id: http://webrtc.googlecode.com/svn/trunk@80 4adac7df-926f-26a2-2b94-8c16560cd09d --- modules/rtp_rtcp/source/rtp_format_vp8.cc | 186 ++++++++---------- modules/rtp_rtcp/source/rtp_format_vp8.h | 32 ++- modules/rtp_rtcp/source/rtp_sender_video.cc | 2 +- .../test/test_rtp_format_vp8/unit_test.cc | 53 +++-- 4 files changed, 144 insertions(+), 129 deletions(-) diff --git a/modules/rtp_rtcp/source/rtp_format_vp8.cc b/modules/rtp_rtcp/source/rtp_format_vp8.cc index 06b6dfb851..c7cb0bf29c 100644 --- a/modules/rtp_rtcp/source/rtp_format_vp8.cc +++ b/modules/rtp_rtcp/source/rtp_format_vp8.cc @@ -11,147 +11,133 @@ #include "rtp_format_vp8.h" #include // assert +#include // ceil, round #include // memcpy namespace webrtc { +// Define how the VP8PacketizerModes are implemented. +// Modes are: kStrict, kAggregate, kSloppy. +const RtpFormatVp8::AggregationMode RtpFormatVp8::aggr_modes_[kNumModes] = + { kAggrNone, kAggrPartitions, kAggrFragments }; +const bool RtpFormatVp8::bal_modes_[kNumModes] = + { true, false, false }; +const bool RtpFormatVp8::sep_first_modes_[kNumModes] = + { true, false, false }; + RtpFormatVp8::RtpFormatVp8(const WebRtc_UWord8* payload_data, WebRtc_UWord32 payload_size, - const RTPFragmentationHeader* fragmentation, + const RTPFragmentationHeader& fragmentation, VP8PacketizerMode mode) : payload_data_(payload_data), payload_size_(payload_size), payload_bytes_sent_(0), - mode_(mode), + part_ix_(0), beginning_(true), first_fragment_(true), - vp8_header_bytes_(1) + vp8_header_bytes_(1), + aggr_mode_(aggr_modes_[mode]), + balance_(bal_modes_[mode]), + separate_first_(sep_first_modes_[mode]) { - if (fragmentation == NULL) - { - // Cannot do kStrict or kAggregate without fragmentation info. - // Change to kSloppy. - mode_ = kSloppy; - } - else - { - frag_info_ = *fragmentation; - } + part_info_ = fragmentation; } RtpFormatVp8::RtpFormatVp8(const WebRtc_UWord8* payload_data, WebRtc_UWord32 payload_size) : payload_data_(payload_data), payload_size_(payload_size), - frag_info_(), + part_info_(), payload_bytes_sent_(0), - mode_(kSloppy), + part_ix_(0), beginning_(true), first_fragment_(true), - vp8_header_bytes_(1) -{} - -int RtpFormatVp8::GetFragIdx() + vp8_header_bytes_(1), + aggr_mode_(aggr_modes_[kSloppy]), + balance_(bal_modes_[kSloppy]), + separate_first_(sep_first_modes_[kSloppy]) { - // Which fragment are we in? - int frag_ix = 0; - while ((frag_ix + 1 < frag_info_.fragmentationVectorSize) && - (payload_bytes_sent_ >= frag_info_.fragmentationOffset[frag_ix + 1])) + part_info_.VerifyAndAllocateFragmentationHeader(1); + part_info_.fragmentationLength[0] = payload_size_; + part_info_.fragmentationOffset[0] = 0; +} + +int RtpFormatVp8::CalcNextSize(int max_payload_len, int remaining_bytes, + bool split_payload) const +{ + if (max_payload_len == 0 || remaining_bytes == 0) { - ++frag_ix; + return 0; + } + if (!split_payload) + { + return max_payload_len >= remaining_bytes ? remaining_bytes : 0; + } + + if (balance_) + { + // Balance payload sizes to produce (almost) equal size + // fragments. + // Number of fragments for remaining_bytes: + int num_frags = ceil( + static_cast(remaining_bytes) / max_payload_len); + // Number of bytes in this fragment: + return static_cast(round( + static_cast(remaining_bytes) / num_frags)); + } + else + { + return max_payload_len >= remaining_bytes ? remaining_bytes + : max_payload_len; } - return frag_ix; } int RtpFormatVp8::NextPacket(int max_payload_len, WebRtc_UWord8* buffer, int* bytes_to_send, bool* last_packet) { - // Convenience variables - const int num_fragments = frag_info_.fragmentationVectorSize; - int frag_ix = GetFragIdx(); //TODO (hlundin): Store frag_ix as a member? + const int num_partitions = part_info_.fragmentationVectorSize; int send_bytes = 0; // How much data to send in this packet. - bool end_of_fragment = false; + 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_]; + int rem_payload_len = max_payload_len - vp8_header_bytes_; - switch (mode_) + while (int next_size = CalcNextSize(rem_payload_len, remaining_in_partition, + split_payload)) { - case kAggregate: + send_bytes += next_size; + rem_payload_len -= next_size; + remaining_in_partition -= next_size; + + if (remaining_in_partition == 0 && !(beginning_ && separate_first_)) { - // Check if we are at the beginning of a new partition. - if (first_fragment_) + // 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_))) { - // Check if this fragment fits in one packet. - if (frag_info_.fragmentationLength[frag_ix] + vp8_header_bytes_ - <= max_payload_len) - { - // Pack as many whole partitions we can into this packet; - // don't fragment. - while ((frag_ix < num_fragments) && - (send_bytes + vp8_header_bytes_ - + frag_info_.fragmentationLength[frag_ix] - <= max_payload_len)) - { - send_bytes += frag_info_.fragmentationLength[frag_ix]; - ++frag_ix; - } - - // This packet ends on a complete fragment. - end_of_fragment = true; - break; // Jump out of case statement. - } + 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); } - - // Either we are not starting this packet with a new partition, - // or the partition is too large for a packet. - // Move on to "case kStrict". - // NOTE: break intentionally omitted! } - - case kStrict: // Can also continue to here from kAggregate. + else if (balance_ && remaining_in_partition > 0) { - // Find out how much is left to send in the current partition. - const int remaining_bytes = frag_info_.fragmentationOffset[frag_ix] - - payload_bytes_sent_ + frag_info_.fragmentationLength[frag_ix]; - assert(remaining_bytes > 0); - assert(remaining_bytes <= frag_info_.fragmentationLength[frag_ix]); - - if (remaining_bytes + vp8_header_bytes_ > max_payload_len) - { - // send one full packet - send_bytes = max_payload_len - vp8_header_bytes_; - } - else - { - // last packet from this partition - send_bytes = remaining_bytes; - end_of_fragment = true; - } break; } - - case kSloppy: - { - // Send a full packet, or what is left of the payload. - const int remaining_bytes = payload_size_ - payload_bytes_sent_; - - if (remaining_bytes + vp8_header_bytes_ > max_payload_len) - { - send_bytes = max_payload_len - vp8_header_bytes_; - end_of_fragment = false; - } - else - { - send_bytes = remaining_bytes; - end_of_fragment = true; - } - break; - } - - default: - // Should not end up here - assert(false); - return -1; + } + if (remaining_in_partition == 0) + { + ++part_ix_; // Advance to next partition. } + const bool end_of_fragment = (remaining_in_partition == 0); // Write the payload header and the payload to buffer. *bytes_to_send = WriteHeaderAndPayload(send_bytes, end_of_fragment, buffer); if (*bytes_to_send < 0) @@ -159,7 +145,7 @@ int RtpFormatVp8::NextPacket(int max_payload_len, WebRtc_UWord8* buffer, return -1; } - *last_packet = payload_bytes_sent_ >= payload_size_; + *last_packet = (payload_bytes_sent_ >= payload_size_); assert(!*last_packet || (payload_bytes_sent_ == payload_size_)); return 0; } diff --git a/modules/rtp_rtcp/source/rtp_format_vp8.h b/modules/rtp_rtcp/source/rtp_format_vp8.h index c8464a5d93..4f2e193c06 100644 --- a/modules/rtp_rtcp/source/rtp_format_vp8.h +++ b/modules/rtp_rtcp/source/rtp_format_vp8.h @@ -33,9 +33,10 @@ namespace webrtc enum VP8PacketizerMode { - kStrict = 0, // split partitions if too large; never aggregate partitions - kAggregate, // split partitions if too large; aggregate whole partitions - kSloppy, // split entire payload without considering partition boundaries + kStrict = 0, // split partitions if too large; never aggregate, balance size + kAggregate, // split partitions if too large; aggregate whole partitions + kSloppy, // split entire payload without considering partition limits + kNumModes, }; // Packetizer for VP8. @@ -46,7 +47,7 @@ public: // The payload_data must be exactly one encoded VP8 frame. RtpFormatVp8(const WebRtc_UWord8* payload_data, WebRtc_UWord32 payload_size, - const RTPFragmentationHeader* fragmentation, + const RTPFragmentationHeader& fragmentation, VP8PacketizerMode mode); // Initialize without fragmentation info. Mode kSloppy will be used. @@ -65,8 +66,20 @@ public: int* bytes_to_send, bool* last_packet); private: - // Determine from which fragment the next byte to send will be taken. - int GetFragIdx(); + enum AggregationMode + { + kAggrNone = 0, // no aggregation + kAggrPartitions, // aggregate intact partitions + kAggrFragments // aggregate intact and fragmented partitions + }; + + static const AggregationMode aggr_modes_[kNumModes]; + static const bool bal_modes_[kNumModes]; + static const bool sep_first_modes_[kNumModes]; + + // Calculate size of next chunk to send. Returns 0 if none can be sent. + int CalcNextSize(int max_payload_len, int remaining_bytes, + bool split_payload) const; // Write the payload header and copy the payload to the buffer. // Will copy send_bytes bytes from the current position on the payload data. @@ -77,12 +90,15 @@ private: const WebRtc_UWord8* payload_data_; const WebRtc_UWord32 payload_size_; - RTPFragmentationHeader frag_info_; + RTPFragmentationHeader part_info_; int payload_bytes_sent_; - VP8PacketizerMode mode_; + int part_ix_; bool beginning_; // first partition in this frame bool first_fragment_; // first fragment of a partition const int vp8_header_bytes_; // length of VP8 payload header + AggregationMode aggr_mode_; + bool balance_; + bool separate_first_; }; } diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc index 5de67cdafa..4f03f85c26 100644 --- a/modules/rtp_rtcp/source/rtp_sender_video.cc +++ b/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -1106,7 +1106,7 @@ RTPSenderVideo::SendVP8(const FrameType frameType, WebRtc_UWord16 maxPayloadLengthVP8 = _rtpSender.MaxPayloadLength() - FECPacketOverhead() - rtpHeaderLength; - RtpFormatVp8 packetizer(data, payloadBytesToSend, fragmentation, kStrict); + RtpFormatVp8 packetizer(data, payloadBytesToSend, *fragmentation, kStrict); bool last = false; while (!last) diff --git a/modules/rtp_rtcp/test/test_rtp_format_vp8/unit_test.cc b/modules/rtp_rtcp/test/test_rtp_format_vp8/unit_test.cc index 7dae82bf78..c740ccaee3 100644 --- a/modules/rtp_rtcp/test/test_rtp_format_vp8/unit_test.cc +++ b/modules/rtp_rtcp/test/test_rtp_format_vp8/unit_test.cc @@ -75,18 +75,18 @@ TEST_F(RtpFormatVp8Test, TestStrictMode) bool last; RtpFormatVp8 packetizer = RtpFormatVp8(payload_data, kPayloadSize, - fragmentation, webrtc::kStrict); + *fragmentation, webrtc::kStrict); - // get first packet + // get first packet, expect balanced size = same as second packet EXPECT_EQ(0, packetizer.NextPacket(8, buffer, &send_bytes, &last)); EXPECT_FALSE(last); - EXPECT_EQ(send_bytes,8); + EXPECT_EQ(send_bytes,6); EXPECT_RSV_ZERO(buffer[0]); EXPECT_BIT_I_EQ(buffer[0], 1); EXPECT_BIT_N_EQ(buffer[0], 0); EXPECT_FI_EQ(buffer[0], 0x01); EXPECT_BIT_B_EQ(buffer[0], 1); - for (int i = 1; i < 8; i++) + for (int i = 1; i < 6; i++) { EXPECT_EQ(buffer[i], 0); } @@ -94,13 +94,13 @@ TEST_F(RtpFormatVp8Test, TestStrictMode) // get second packet EXPECT_EQ(0, packetizer.NextPacket(8, buffer, &send_bytes, &last)); EXPECT_FALSE(last); - EXPECT_EQ(send_bytes,4); // 3 remaining from partition, 1 header + EXPECT_EQ(send_bytes,6); // 5 remaining from partition, 1 header EXPECT_RSV_ZERO(buffer[0]); EXPECT_BIT_I_EQ(buffer[0], 0); EXPECT_BIT_N_EQ(buffer[0], 0); EXPECT_FI_EQ(buffer[0], 0x02); EXPECT_BIT_B_EQ(buffer[0], 0); - for (int i = 1; i < 4; i++) + for (int i = 1; i < 6; i++) { EXPECT_EQ(buffer[i], 0); } @@ -121,36 +121,50 @@ TEST_F(RtpFormatVp8Test, TestStrictMode) } // Third partition - // Get first packet (of three) - EXPECT_EQ(0, packetizer.NextPacket(5, buffer, &send_bytes, &last)); + // Get first packet (of four) + EXPECT_EQ(0, packetizer.NextPacket(4, buffer, &send_bytes, &last)); EXPECT_FALSE(last); - EXPECT_EQ(send_bytes,5); + EXPECT_EQ(send_bytes,4); EXPECT_RSV_ZERO(buffer[0]); EXPECT_BIT_I_EQ(buffer[0], 0); EXPECT_BIT_N_EQ(buffer[0], 0); EXPECT_FI_EQ(buffer[0], 0x01); // first fragment EXPECT_BIT_B_EQ(buffer[0], 0); - for (int i = 1; i < 5; i++) + for (int i = 1; i < 4; i++) { EXPECT_EQ(buffer[i], 2); } - // Get second packet (of three) - EXPECT_EQ(0, packetizer.NextPacket(5, buffer, &send_bytes, &last)); + // Get second packet (of four) + EXPECT_EQ(0, packetizer.NextPacket(4, buffer, &send_bytes, &last)); EXPECT_FALSE(last); - EXPECT_EQ(send_bytes,5); + EXPECT_EQ(send_bytes,3); EXPECT_RSV_ZERO(buffer[0]); EXPECT_BIT_I_EQ(buffer[0], 0); EXPECT_BIT_N_EQ(buffer[0], 0); EXPECT_FI_EQ(buffer[0], 0x03); // middle fragment EXPECT_BIT_B_EQ(buffer[0], 0); - for (int i = 1; i < 5; i++) + for (int i = 1; i < 3; i++) { EXPECT_EQ(buffer[i], 2); } - // Get third and last packet - EXPECT_EQ(0, packetizer.NextPacket(5, buffer, &send_bytes, &last)); + // Get third packet (of four) + EXPECT_EQ(0, packetizer.NextPacket(4, buffer, &send_bytes, &last)); + EXPECT_FALSE(last); + EXPECT_EQ(send_bytes,4); + EXPECT_RSV_ZERO(buffer[0]); + EXPECT_BIT_I_EQ(buffer[0], 0); + EXPECT_BIT_N_EQ(buffer[0], 0); + EXPECT_FI_EQ(buffer[0], 0x03); // middle fragment + EXPECT_BIT_B_EQ(buffer[0], 0); + for (int i = 1; i < 4; i++) + { + EXPECT_EQ(buffer[i], 2); + } + + // Get fourth and last packet + EXPECT_EQ(0, packetizer.NextPacket(4, buffer, &send_bytes, &last)); EXPECT_TRUE(last); // last packet in frame EXPECT_EQ(send_bytes,3); // 2 bytes payload left, 1 header EXPECT_RSV_ZERO(buffer[0]); @@ -172,7 +186,7 @@ TEST_F(RtpFormatVp8Test, TestAggregateMode) bool last; RtpFormatVp8 packetizer = RtpFormatVp8(payload_data, kPayloadSize, - fragmentation, webrtc::kAggregate); + *fragmentation, webrtc::kAggregate); // get first packet // first half of first partition @@ -232,7 +246,7 @@ TEST_F(RtpFormatVp8Test, TestSloppyMode) bool last; RtpFormatVp8 packetizer = RtpFormatVp8(payload_data, kPayloadSize, - fragmentation, webrtc::kSloppy); + *fragmentation, webrtc::kSloppy); // get first packet EXPECT_EQ(0, packetizer.NextPacket(9, buffer, &send_bytes, &last)); @@ -310,8 +324,7 @@ TEST_F(RtpFormatVp8Test, TestSloppyModeFallback) int send_bytes = 0; bool last; - RtpFormatVp8 packetizer = RtpFormatVp8(payload_data, kPayloadSize, - NULL /*fragInfo*/, webrtc::kStrict); // should be changed to kSloppy + RtpFormatVp8 packetizer = RtpFormatVp8(payload_data, kPayloadSize); // get first packet EXPECT_EQ(0, packetizer.NextPacket(9, buffer, &send_bytes, &last));