diff --git a/src/modules/interface/module_common_types.h b/src/modules/interface/module_common_types.h index 3df370a47..686493fb0 100644 --- a/src/modules/interface/module_common_types.h +++ b/src/modules/interface/module_common_types.h @@ -37,18 +37,30 @@ struct RTPAudioHeader struct RTPVideoHeaderH263 { + void InitRTPVideoHeaderH263() {}; bool independentlyDecodable; // H.263-1998 if no P bit it's not independently decodable bool bits; // H.263 mode B, Xor the lasy byte of previus packet with the // first byte of this packet }; enum {kNoPictureId = -1}; +enum {kNoTl0PicIdx = -1}; +enum {kNoTemporalIdx = -1}; struct RTPVideoHeaderVP8 { - bool startBit; // Start of partition. - bool stopBit; // Stop of partition. + void InitRTPVideoHeaderVP8() + { + nonReference = false; + pictureId = kNoPictureId; + tl0PicIdx = kNoTl0PicIdx; + temporalIdx = kNoTemporalIdx; + } + + bool nonReference; // Frame is discardable. WebRtc_Word16 pictureId; // Picture ID index, 15 bits; // kNoPictureId if PictureID does not exist. - bool nonReference; // Frame is discardable. + WebRtc_Word16 tl0PicIdx; // TL0PIC_IDX, 8 bits; + // kNoTl0PicIdx means no value provided. + WebRtc_Word8 temporalIdx; // Temporal layer index, or kNoTemporalIdx. }; union RTPVideoTypeHeader { diff --git a/src/modules/rtp_rtcp/source/rtp_format_vp8.cc b/src/modules/rtp_rtcp/source/rtp_format_vp8.cc index ce26dce96..1e8f336b6 100644 --- a/src/modules/rtp_rtcp/source/rtp_format_vp8.cc +++ b/src/modules/rtp_rtcp/source/rtp_format_vp8.cc @@ -35,11 +35,12 @@ RtpFormatVp8::RtpFormatVp8(const WebRtc_UWord8* payload_data, part_ix_(0), beginning_(true), first_fragment_(true), - vp8_header_bytes_(1), + 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) + hdr_info_(hdr_info), + first_partition_in_packet_(0) { part_info_ = fragmentation; } @@ -54,11 +55,12 @@ RtpFormatVp8::RtpFormatVp8(const WebRtc_UWord8* payload_data, part_ix_(0), beginning_(true), first_fragment_(true), - vp8_header_bytes_(1), + 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) + hdr_info_(hdr_info), + first_partition_in_packet_(0) { part_info_.VerifyAndAllocateFragmentationHeader(1); part_info_.fragmentationLength[0] = payload_size; @@ -97,14 +99,22 @@ int RtpFormatVp8::CalcNextSize(int max_payload_len, int remaining_bytes, int RtpFormatVp8::NextPacket(int max_payload_len, WebRtc_UWord8* buffer, int* bytes_to_send, bool* last_packet) { + 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_] + - FirstHeaderExtraLength(); // Add header extra length to payload length. - int rem_payload_len = max_payload_len - vp8_header_bytes_; - const int first_partition_in_packet = 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; while (int next_size = CalcNextSize(rem_payload_len, remaining_in_partition, split_payload)) @@ -141,67 +151,117 @@ int RtpFormatVp8::NextPacket(int max_payload_len, WebRtc_UWord8* buffer, ++part_ix_; // Advance to next partition. } - send_bytes -= FirstHeaderExtraLength(); // Remove the extra length again. + send_bytes -= PayloadDescriptorExtraLength(); // Remove extra length again. assert(send_bytes > 0); - 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, - max_payload_len); + *bytes_to_send = WriteHeaderAndPayload(send_bytes, buffer, max_payload_len); if (*bytes_to_send < 0) { return -1; } + 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; + return first_partition_in_packet_; } int RtpFormatVp8::WriteHeaderAndPayload(int payload_bytes, - bool end_of_fragment, WebRtc_UWord8* buffer, int buffer_length) { - // Write the VP8 payload header. - // 0 1 2 - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | RSV |I|N|FI |B| PictureID (1 or 2 octets) | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // Write the VP8 payload descriptor. + // 0 + // 0 1 2 3 4 5 6 7 8 + // +-+-+-+-+-+-+-+-+-+ + // |X| |N|S| PART_ID | + // +-+-+-+-+-+-+-+-+-+ + // X: |I|L|T| | (mandatory if any of the below are used) + // +-+-+-+-+-+-+-+-+-+ + // I: |PictureID (8/16b)| (optional) + // +-+-+-+-+-+-+-+-+-+ + // L: | TL0PIC_IDX | (optional) + // +-+-+-+-+-+-+-+-+-+ + // T: | TID | | (optional) + // +-+-+-+-+-+-+-+-+-+ - if (payload_bytes < 0) - { - return -1; - } - if (payload_bytes_sent_ + payload_bytes > payload_size_) - { - return -1; - } + assert(payload_bytes > 0); + assert(payload_bytes_sent_ + payload_bytes <= payload_size_); + assert(vp8_fixed_payload_descriptor_bytes_ + PayloadDescriptorExtraLength() + + payload_bytes <= buffer_length); buffer[0] = 0; - if (hdr_info_.nonReference) buffer[0] |= (0x01 << 3); // N - buffer[0] |= (GetFIFlag(end_of_fragment) << 1); // FI - if (beginning_) buffer[0] |= 0x01; // B + 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); - int pic_id_len = WritePictureID(&buffer[vp8_header_bytes_], - buffer_length - vp8_header_bytes_); - if (pic_id_len < 0) return pic_id_len; // error - if (pic_id_len > 0) buffer[0] |= (0x01 << 4); // I + const int extension_length = WriteExtensionFields(buffer, buffer_length); - if (vp8_header_bytes_ + pic_id_len + payload_bytes > buffer_length) - { - return -1; - } - memcpy(&buffer[vp8_header_bytes_ + pic_id_len], + memcpy(&buffer[vp8_fixed_payload_descriptor_bytes_ + extension_length], &payload_data_[payload_bytes_sent_], payload_bytes); - beginning_ = false; // next packet cannot be first packet in frame - // next packet starts new fragment if this ended one - first_fragment_ = end_of_fragment; payload_bytes_sent_ += payload_bytes; // Return total length of written data. - return payload_bytes + vp8_header_bytes_ + pic_id_len; + return payload_bytes + vp8_fixed_payload_descriptor_bytes_ + + extension_length; +} + +int RtpFormatVp8::WriteExtensionFields(WebRtc_UWord8* buffer, int buffer_length) +const +{ + int extension_length = 0; + if (XFieldPresent()) + { + WebRtc_UWord8* x_field = buffer + vp8_fixed_payload_descriptor_bytes_; + *x_field = 0; + extension_length = 1; // One octet for the X field. + if (PictureIdPresent()) + { + if (WritePictureIDFields(x_field, buffer, buffer_length, + &extension_length) < 0) + { + return -1; + } + } + if (TL0PicIdxFieldPresent()) + { + if (WriteTl0PicIdxFields(x_field, buffer, buffer_length, + &extension_length) < 0) + { + return -1; + } + } + if (TIDFieldPresent()) + { + if (WriteTIDFields(x_field, buffer, buffer_length, + &extension_length) < 0) + { + return -1; + } + } + assert(extension_length == PayloadDescriptorExtraLength()); + } + return extension_length; +} + + +int RtpFormatVp8::WritePictureIDFields(WebRtc_UWord8* x_field, + WebRtc_UWord8* buffer, + int buffer_length, + int* extension_length) const +{ + *x_field |= kIBit; + const int pic_id_length = WritePictureID( + buffer + vp8_fixed_payload_descriptor_bytes_ + *extension_length, + buffer_length - vp8_fixed_payload_descriptor_bytes_ + - *extension_length); + if (pic_id_length < 0) return -1; + *extension_length += pic_id_length; + return 0; } int RtpFormatVp8::WritePictureID(WebRtc_UWord8* buffer, int buffer_length) const @@ -209,7 +269,7 @@ int RtpFormatVp8::WritePictureID(WebRtc_UWord8* buffer, int buffer_length) const const WebRtc_UWord16 pic_id = static_cast (hdr_info_.pictureId); int picture_id_len = PictureIdLength(); - if (picture_id_len > buffer_length) return -1; // error + if (picture_id_len > buffer_length) return -1; if (picture_id_len == 2) { buffer[0] = 0x80 | ((pic_id >> 8) & 0x7F); @@ -222,17 +282,51 @@ int RtpFormatVp8::WritePictureID(WebRtc_UWord8* buffer, int buffer_length) const return picture_id_len; } -int RtpFormatVp8::FirstHeaderExtraLength() const +int RtpFormatVp8::WriteTl0PicIdxFields(WebRtc_UWord8* x_field, + WebRtc_UWord8* buffer, + int buffer_length, + int* extension_length) const +{ + if (buffer_length < vp8_fixed_payload_descriptor_bytes_ + *extension_length + + 1) + { + return -1; + } + *x_field |= kLBit; + buffer[vp8_fixed_payload_descriptor_bytes_ + + *extension_length] = hdr_info_.tl0PicIdx; + ++*extension_length; + return 0; +} + +int RtpFormatVp8::WriteTIDFields(WebRtc_UWord8* x_field, + WebRtc_UWord8* buffer, + int buffer_length, + int* extension_length) const +{ + if (buffer_length < vp8_fixed_payload_descriptor_bytes_ + *extension_length + + 1) + { + return -1; + } + *x_field |= kTBit; + buffer[vp8_fixed_payload_descriptor_bytes_ + *extension_length] + = hdr_info_.temporalIdx << 5; + ++*extension_length; + return 0; +} + +int RtpFormatVp8::PayloadDescriptorExtraLength() const { if (!beginning_) { return 0; } - int length = 0; - - length += PictureIdLength(); - - return length; + int length_bytes = PictureIdLength(); + if (TL0PicIdxFieldPresent()) ++length_bytes; + if (TIDFieldPresent()) ++length_bytes; + if (length_bytes > 0) ++length_bytes; // Include the extension field. + return length_bytes; } int RtpFormatVp8::PictureIdLength() const @@ -251,19 +345,19 @@ int RtpFormatVp8::PictureIdLength() const } } -int RtpFormatVp8::GetFIFlag(bool end_of_fragment) const +bool RtpFormatVp8::XFieldPresent() const { - if (first_fragment_ && end_of_fragment) { - return 0x0; - } - if (first_fragment_ && !end_of_fragment) { - return 0x1; - } - if (!first_fragment_ && !end_of_fragment) { - return 0x2; - } - // if (!first_fragment_ && end_of_fragment) - return 0x3; + return (TIDFieldPresent() || TL0PicIdxFieldPresent() || PictureIdPresent()); +} + +bool RtpFormatVp8::TIDFieldPresent() const +{ + return (hdr_info_.temporalIdx != kNoTemporalIdx); +} + +bool RtpFormatVp8::TL0PicIdxFieldPresent() const +{ + return (hdr_info_.tl0PicIdx != kNoTl0PicIdx); } } // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/rtp_format_vp8.h b/src/modules/rtp_rtcp/source/rtp_format_vp8.h index 7ee94db85..48905d90e 100644 --- a/src/modules/rtp_rtcp/source/rtp_format_vp8.h +++ b/src/modules/rtp_rtcp/source/rtp_format_vp8.h @@ -80,6 +80,13 @@ private: static const AggregationMode aggr_modes_[kNumModes]; static const bool balance_modes_[kNumModes]; static const bool separate_first_modes_[kNumModes]; + static const int kXBit = 0x80; + static const int kNBit = 0x20; + static const int kSBit = 0x10; + static const int kPartIdField = 0x0F; + static const int kIBit = 0x80; + static const int kLBit = 0x40; + static const int kTBit = 0x20; // Calculate size of next chunk to send. Returns 0 if none can be sent. int CalcNextSize(int max_payload_len, int remaining_bytes, @@ -89,8 +96,29 @@ private: // 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, bool end_of_fragment, - WebRtc_UWord8* buffer, int buffer_length); + int WriteHeaderAndPayload(int send_bytes, WebRtc_UWord8* buffer, + int buffer_length); + + + // Write the X field and the appropriate extension fields to buffer. + // The function returns the extension length (including X field), or -1 + // on error. + int WriteExtensionFields(WebRtc_UWord8* buffer, int buffer_length) const; + + // Set the I bit in the x_field, and write PictureID to the appropriate + // position in buffer. The function returns 0 on success, -1 otherwise. + int WritePictureIDFields(WebRtc_UWord8* x_field, WebRtc_UWord8* buffer, + int buffer_length, int* extension_length) const; + + // Set the L bit in the x_field, and write Tl0PicIdx to the appropriate + // position in buffer. The function returns 0 on success, -1 otherwise. + int WriteTl0PicIdxFields(WebRtc_UWord8* x_field, WebRtc_UWord8* buffer, + int buffer_length, int* extension_length) const; + + // Set the T bit in the x_field, and write TID to the appropriate + // position in buffer. The function returns 0 on success, -1 otherwise. + int WriteTIDFields(WebRtc_UWord8* x_field, WebRtc_UWord8* buffer, + int buffer_length, int* extension_length) const; // Write the PictureID from codec_specific_info_ to buffer. One or two // bytes are written, depending on magnitude of PictureID. The function @@ -99,13 +127,17 @@ private: // Calculate and return length (octets) of the variable header fields in // the next header (i.e., header length in addition to vp8_header_bytes_). - int FirstHeaderExtraLength() const; + int PayloadDescriptorExtraLength() const; // Calculate and return length (octets) of PictureID field in the next // header. Can be 0, 1, or 2. int PictureIdLength() const; - int GetFIFlag(bool end_of_fragment) const; + // Check whether each of the optional fields will be included in the header. + bool XFieldPresent() const; + bool TIDFieldPresent() const; + bool TL0PicIdxFieldPresent() const; + bool PictureIdPresent() const { return (PictureIdLength() > 0); } const WebRtc_UWord8* payload_data_; const int payload_size_; @@ -114,11 +146,13 @@ private: 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's fixed part + const int vp8_fixed_payload_descriptor_bytes_; // length of VP8 payload + // descriptors's fixed part AggregationMode aggr_mode_; bool balance_; bool separate_first_; const RTPVideoHeaderVP8 hdr_info_; + int first_partition_in_packet_; }; } 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 6b7e8160c..0ce8e6dd3 100644 --- a/src/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc +++ b/src/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc @@ -24,20 +24,26 @@ using webrtc::RTPFragmentationHeader; using webrtc::RtpFormatVp8; using webrtc::RTPVideoHeaderVP8; -const WebRtc_UWord32 kPayloadSize = 30; +const int kPayloadSize = 30; +const int kBufferSize = kPayloadSize + 6; // Add space for payload descriptor. class RtpFormatVp8Test : public ::testing::Test { protected: RtpFormatVp8Test() {}; virtual void SetUp(); virtual void TearDown(); - void CheckHeader(bool first_in_frame, bool frag_start, bool frag_end); + void CheckHeader(bool first_in_frame, bool frag_start, int part_id); + void CheckPictureID(); + void CheckTl0PicIdx(); + void CheckTID(); void CheckPayload(int payload_end); void CheckLast(bool last) const; void CheckPacket(int send_bytes, int expect_bytes, bool last, - bool first_in_frame, bool frag_start, bool frag_end); + bool first_in_frame, bool frag_start); + void CheckPacketZeroPartId(int send_bytes, int expect_bytes, bool last, + bool first_in_frame, bool frag_start); WebRtc_UWord8 payload_data_[kPayloadSize]; - WebRtc_UWord8 buffer_[kPayloadSize]; + WebRtc_UWord8 buffer_[kBufferSize]; WebRtc_UWord8 *data_ptr_; RTPFragmentationHeader* fragmentation_; RTPVideoHeaderVP8 hdr_info_; @@ -60,57 +66,130 @@ void RtpFormatVp8Test::SetUp() { fragmentation_->fragmentationOffset[1] = 10; fragmentation_->fragmentationOffset[2] = 20; - hdr_info_.pictureId = 0; + hdr_info_.pictureId = webrtc::kNoPictureId; hdr_info_.nonReference = false; + hdr_info_.temporalIdx = webrtc::kNoTemporalIdx; + hdr_info_.tl0PicIdx = webrtc::kNoTl0PicIdx; } void RtpFormatVp8Test::TearDown() { delete fragmentation_; } +// First octet tests #define EXPECT_BIT_EQ(x,n,a) EXPECT_EQ((((x)>>n)&0x1), a) #define EXPECT_RSV_ZERO(x) EXPECT_EQ(((x)&0xE0), 0) -//#define EXPECT_BIT_I_EQ(x,a) EXPECT_EQ((((x)&0x10) > 0), (a > 0)) -#define EXPECT_BIT_I_EQ(x,a) EXPECT_BIT_EQ(x, 4, a) +#define EXPECT_BIT_X_EQ(x,a) EXPECT_BIT_EQ(x, 7, a) -#define EXPECT_BIT_N_EQ(x,a) EXPECT_EQ((((x)&0x08) > 0), (a > 0)) +#define EXPECT_BIT_N_EQ(x,a) EXPECT_BIT_EQ(x, 5, a) -#define EXPECT_FI_EQ(x,a) EXPECT_EQ((((x)&0x06) >> 1), a) +#define EXPECT_BIT_S_EQ(x,a) EXPECT_BIT_EQ(x, 4, a) -#define EXPECT_BIT_B_EQ(x,a) EXPECT_EQ((((x)&0x01) > 0), (a > 0)) +#define EXPECT_PART_ID_EQ(x, a) EXPECT_EQ(((x)&0x0F), a) + +// Extension fields tests +#define EXPECT_BIT_I_EQ(x,a) EXPECT_BIT_EQ(x, 7, a) + +#define EXPECT_BIT_L_EQ(x,a) EXPECT_BIT_EQ(x, 6, a) + +#define EXPECT_BIT_T_EQ(x,a) EXPECT_BIT_EQ(x, 5, a) + +#define EXPECT_TID_EQ(x,a) EXPECT_EQ((((x)&0xE0) >> 5), a) void RtpFormatVp8Test::CheckHeader(bool first_in_frame, bool frag_start, - bool frag_end) + int part_id) { payload_start_ = 1; - EXPECT_RSV_ZERO(buffer_[0]); - if (first_in_frame & hdr_info_.pictureId != webrtc::kNoPictureId) + EXPECT_BIT_EQ(buffer_[0], 6, 0); // check reserved bit + + if (first_in_frame && + (hdr_info_.pictureId != webrtc::kNoPictureId || + hdr_info_.temporalIdx != webrtc::kNoTemporalIdx || + hdr_info_.tl0PicIdx != webrtc::kNoTl0PicIdx)) { - EXPECT_BIT_I_EQ(buffer_[0], 1); + EXPECT_BIT_X_EQ(buffer_[0], 1); + ++payload_start_; + CheckPictureID(); + CheckTl0PicIdx(); + CheckTID(); + } + else + { + EXPECT_BIT_X_EQ(buffer_[0], 0); + } + + EXPECT_BIT_N_EQ(buffer_[0], 0); + EXPECT_BIT_S_EQ(buffer_[0], frag_start); + + // Check partition index. + if (part_id < 0) + { + // (Payload data is the same as the partition index.) + EXPECT_EQ(buffer_[0] & 0x0F, buffer_[payload_start_]); + } + else + { + EXPECT_EQ(buffer_[0] & 0x0F, part_id); + } +} + +void RtpFormatVp8Test::CheckPictureID() +{ + if (hdr_info_.pictureId != webrtc::kNoPictureId) + { + EXPECT_BIT_I_EQ(buffer_[1], 1); if (hdr_info_.pictureId > 0x7F) { - EXPECT_BIT_EQ(buffer_[1], 7, 1); - EXPECT_EQ(buffer_[1] & 0x7F, + EXPECT_BIT_EQ(buffer_[payload_start_], 7, 1); + EXPECT_EQ(buffer_[payload_start_] & 0x7F, (hdr_info_.pictureId >> 8) & 0x7F); - EXPECT_EQ(buffer_[2], hdr_info_.pictureId & 0xFF); + EXPECT_EQ(buffer_[payload_start_ + 1], + hdr_info_.pictureId & 0xFF); payload_start_ += 2; } else { - EXPECT_BIT_EQ(buffer_[1], 7, 0); - EXPECT_EQ(buffer_[1] & 0x7F, + EXPECT_BIT_EQ(buffer_[payload_start_], 7, 0); + EXPECT_EQ(buffer_[payload_start_] & 0x7F, (hdr_info_.pictureId) & 0x7F); payload_start_ += 1; } } - EXPECT_BIT_N_EQ(buffer_[0], 0); - WebRtc_UWord8 fi = 0x03; - if (frag_start) fi = fi & 0x01; - if (frag_end) fi = fi & 0x02; - EXPECT_FI_EQ(buffer_[0], fi); - if (first_in_frame) EXPECT_BIT_B_EQ(buffer_[0], 1); + else + { + EXPECT_BIT_I_EQ(buffer_[1], 0); + } +} + +void RtpFormatVp8Test::CheckTl0PicIdx() +{ + if (hdr_info_.tl0PicIdx != webrtc::kNoTl0PicIdx) + { + EXPECT_BIT_L_EQ(buffer_[1], 1); + EXPECT_EQ(buffer_[payload_start_], hdr_info_.tl0PicIdx); + ++payload_start_; + } + else + { + EXPECT_BIT_L_EQ(buffer_[1], 0); + } +} + +void RtpFormatVp8Test::CheckTID() +{ + if (hdr_info_.temporalIdx != webrtc::kNoTemporalIdx) + { + EXPECT_BIT_T_EQ(buffer_[1], 1); + EXPECT_TID_EQ(buffer_[payload_start_], hdr_info_.temporalIdx); + EXPECT_EQ(buffer_[payload_start_] & 0x1F, 0); + ++payload_start_; + } + else + { + EXPECT_BIT_T_EQ(buffer_[1], 0); + } } void RtpFormatVp8Test::CheckPayload(int payload_end) @@ -125,10 +204,22 @@ void RtpFormatVp8Test::CheckLast(bool last) const } void RtpFormatVp8Test::CheckPacket(int send_bytes, int expect_bytes, bool last, - bool first_in_frame, bool frag_start, bool frag_end) + bool first_in_frame, bool frag_start) { EXPECT_EQ(send_bytes, expect_bytes); - CheckHeader(first_in_frame, frag_start, frag_end); + CheckHeader(first_in_frame, frag_start, -1); + CheckPayload(send_bytes); + CheckLast(last); +} + +void RtpFormatVp8Test::CheckPacketZeroPartId(int send_bytes, + int expect_bytes, + bool last, + bool first_in_frame, + bool frag_start) +{ + EXPECT_EQ(send_bytes, expect_bytes); + CheckHeader(first_in_frame, frag_start, 0); CheckPayload(send_bytes); CheckLast(last); } @@ -143,57 +234,51 @@ TEST_F(RtpFormatVp8Test, TestStrictMode) RtpFormatVp8 packetizer = RtpFormatVp8(payload_data_, kPayloadSize, hdr_info_, *fragmentation_, webrtc::kStrict); - // get first packet, expect balanced size = same as second packet - EXPECT_EQ(0, packetizer.NextPacket(8, buffer_, &send_bytes, &last)); - CheckPacket(send_bytes, 7, last, + // get first packet, expect balanced size ~= same as second packet + EXPECT_EQ(0, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); + CheckPacket(send_bytes, 8, last, first_in_frame, - /* frag_start */ true, - /* frag_end */ false); + /* frag_start */ true); first_in_frame = false; // get second packet - EXPECT_EQ(0, packetizer.NextPacket(8, buffer_, &send_bytes, &last)); + EXPECT_EQ(0, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 7, last, first_in_frame, - /* frag_start */ false, - /* frag_end */ true); + /* frag_start */ false); // Second partition // Get first (and only) packet EXPECT_EQ(1, packetizer.NextPacket(20, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 11, last, first_in_frame, - /* frag_start */ true, - /* frag_end */ true); + /* frag_start */ true); // Third partition // Get first packet (of four) EXPECT_EQ(2, packetizer.NextPacket(4, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 4, last, first_in_frame, - /* frag_start */ true, - /* frag_end */ false); + /* frag_start */ true); // Get second packet (of four) EXPECT_EQ(2, packetizer.NextPacket(4, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 3, last, first_in_frame, - /* frag_start */ false, - /* frag_end */ false); + /* frag_start */ false); // Get third packet (of four) EXPECT_EQ(2, packetizer.NextPacket(4, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 4, last, first_in_frame, - /* frag_start */ false, - /* frag_end */ false); + /* frag_start */ false); // Get fourth and last packet EXPECT_EQ(2, packetizer.NextPacket(4, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 3, last, first_in_frame, - /* frag_start */ false, - /* frag_end */ true); + /* frag_start */ false); + } TEST_F(RtpFormatVp8Test, TestAggregateMode) @@ -207,29 +292,34 @@ TEST_F(RtpFormatVp8Test, TestAggregateMode) hdr_info_, *fragmentation_, webrtc::kAggregate); // get first packet - // first half of first partition - EXPECT_EQ(0, packetizer.NextPacket(6, buffer_, &send_bytes, &last)); - CheckPacket(send_bytes, 6, last, + // first part of first partition (balanced fragments are expected) + EXPECT_EQ(0, packetizer.NextPacket(7, buffer_, &send_bytes, &last)); + CheckPacket(send_bytes, 5, last, first_in_frame, - /* frag_start */ true, - /* frag_end */ false); + /* frag_start */ true); first_in_frame = false; // get second packet - // second half of first partition - EXPECT_EQ(0, packetizer.NextPacket(10, buffer_, &send_bytes, &last)); - CheckPacket(send_bytes, 7, last, + // second fragment of first partition + EXPECT_EQ(0, packetizer.NextPacket(7, buffer_, &send_bytes, &last)); + CheckPacket(send_bytes, 5, last, first_in_frame, - /* frag_start */ false, - /* frag_end */ true); + /* frag_start */ false); // get third packet + // third fragment of first partition + EXPECT_EQ(0, packetizer.NextPacket(7, buffer_, &send_bytes, &last)); + CheckPacket(send_bytes, 5, last, + first_in_frame, + /* frag_start */ false); + + // get fourth packet // last two partitions aggregated EXPECT_EQ(1, packetizer.NextPacket(25, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 21, last, first_in_frame, - /* frag_start */ true, - /* frag_end */ true); + /* frag_start */ true); + } TEST_F(RtpFormatVp8Test, TestSloppyMode) @@ -246,8 +336,7 @@ TEST_F(RtpFormatVp8Test, TestSloppyMode) EXPECT_EQ(0, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 9, last, first_in_frame, - /* frag_start */ true, - /* frag_end */ false); + /* frag_start */ true); first_in_frame = false; // get second packet @@ -255,24 +344,22 @@ TEST_F(RtpFormatVp8Test, TestSloppyMode) EXPECT_EQ(0, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 9, last, first_in_frame, - /* frag_start */ false, - /* frag_end */ false); + /* frag_start */ false); // get third packet // fragments of second and third partitions EXPECT_EQ(1, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 9, last, first_in_frame, - /* frag_start */ false, - /* frag_end */ false); + /* frag_start */ false); // get fourth packet // second half of last partition EXPECT_EQ(2, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); CheckPacket(send_bytes, 7, last, first_in_frame, - /* frag_start */ false, - /* frag_end */ true); + /* frag_start */ false); + } // Verify that sloppy mode is forced if fragmentation info is missing. @@ -287,43 +374,39 @@ TEST_F(RtpFormatVp8Test, TestSloppyModeFallback) hdr_info_); // get first packet - EXPECT_EQ(0, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); - CheckPacket(send_bytes, 9, last, - first_in_frame, - /* frag_start */ true, - /* frag_end */ false); + EXPECT_EQ(0, packetizer.NextPacket(10, buffer_, &send_bytes, &last)); + CheckPacketZeroPartId(send_bytes, 10, last, + first_in_frame, + /* frag_start */ true); first_in_frame = false; // get second packet // fragments of first and second partitions - EXPECT_EQ(0, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); - CheckPacket(send_bytes, 9, last, - first_in_frame, - /* frag_start */ false, - /* frag_end */ false); + EXPECT_EQ(0, packetizer.NextPacket(10, buffer_, &send_bytes, &last)); + CheckPacketZeroPartId(send_bytes, 10, last, + first_in_frame, + /* frag_start */ false); // get third packet // fragments of second and third partitions - EXPECT_EQ(0, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); - CheckPacket(send_bytes, 9, last, - first_in_frame, - /* frag_start */ false, - /* frag_end */ false); + EXPECT_EQ(0, packetizer.NextPacket(10, buffer_, &send_bytes, &last)); + CheckPacketZeroPartId(send_bytes, 10, last, + first_in_frame, + /* frag_start */ false); // get fourth packet // second half of last partition - EXPECT_EQ(0, packetizer.NextPacket(9, buffer_, &send_bytes, &last)); - CheckPacket(send_bytes, 9, last, - first_in_frame, - /* frag_start */ false, - /* frag_end */ true); + EXPECT_EQ(0, packetizer.NextPacket(7, buffer_, &send_bytes, &last)); + CheckPacketZeroPartId(send_bytes, 7, last, + first_in_frame, + /* frag_start */ false); + } // Verify that non-reference bit is set. TEST_F(RtpFormatVp8Test, TestNonReferenceBit) { int send_bytes = 0; bool last; - bool first_in_frame = true; hdr_info_.nonReference = true; RtpFormatVp8 packetizer = RtpFormatVp8(payload_data_, kPayloadSize, @@ -340,6 +423,25 @@ TEST_F(RtpFormatVp8Test, TestNonReferenceBit) { EXPECT_BIT_N_EQ(buffer_[0], 1); } +// Verify Tl0PicIdx and TID fields +TEST_F(RtpFormatVp8Test, TestTl0PicIdxAndTID) { + int send_bytes = 0; + bool last; + + hdr_info_.tl0PicIdx = 117; + hdr_info_.temporalIdx = 2; + RtpFormatVp8 packetizer = RtpFormatVp8(payload_data_, kPayloadSize, + hdr_info_, *fragmentation_, webrtc::kAggregate); + + // get first and only packet + EXPECT_EQ(0, packetizer.NextPacket(kBufferSize, buffer_, &send_bytes, + &last)); + bool first_in_frame = true; + CheckPacket(send_bytes, kPayloadSize + 4, last, + first_in_frame, + /* frag_start */ true); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/src/modules/rtp_rtcp/source/rtp_receiver_video.cc b/src/modules/rtp_rtcp/source/rtp_receiver_video.cc index ff71d7b20..9dd19e278 100644 --- a/src/modules/rtp_rtcp/source/rtp_receiver_video.cc +++ b/src/modules/rtp_rtcp/source/rtp_receiver_video.cc @@ -14,7 +14,6 @@ #include "rtp_receiver_video.h" -#include "trace.h" #include "critical_section_wrapper.h" #include "tick_util.h" @@ -466,7 +465,8 @@ RTPReceiverVideo::ReceiveH263Codec(WebRtcRTPHeader* rtpHeader, { ModuleRTPUtility::RTPPayloadParser rtpPayloadParser(kRtpH263Video, payloadData, - payloadDataLength); + payloadDataLength, + _id); ModuleRTPUtility::RTPPayload parsedPacket; const bool success = rtpPayloadParser.Parse(parsedPacket); @@ -491,7 +491,8 @@ RTPReceiverVideo::ReceiveH2631998Codec(WebRtcRTPHeader* rtpHeader, { ModuleRTPUtility::RTPPayloadParser rtpPayloadParser(kRtpH2631998Video, payloadData, - payloadDataLength); + payloadDataLength, + _id); ModuleRTPUtility::RTPPayload parsedPacket; const bool success = rtpPayloadParser.Parse(parsedPacket); @@ -581,7 +582,8 @@ RTPReceiverVideo::ReceiveMPEG4Codec(WebRtcRTPHeader* rtpHeader, { ModuleRTPUtility::RTPPayloadParser rtpPayloadParser(kRtpMpeg4Video, payloadData, - payloadDataLength); + payloadDataLength, + _id); ModuleRTPUtility::RTPPayload parsedPacket; const bool success = rtpPayloadParser.Parse(parsedPacket); @@ -612,7 +614,8 @@ RTPReceiverVideo::ReceiveVp8Codec(WebRtcRTPHeader* rtpHeader, { ModuleRTPUtility::RTPPayloadParser rtpPayloadParser(kRtpVp8Video, payloadData, - payloadDataLength); + payloadDataLength, + _id); ModuleRTPUtility::RTPPayload parsedPacket; const bool success = rtpPayloadParser.Parse(parsedPacket); @@ -631,10 +634,17 @@ RTPReceiverVideo::ReceiveVp8Codec(WebRtcRTPHeader* rtpHeader, } rtpHeader->frameType = (parsedPacket.frameType == ModuleRTPUtility::kIFrame) ? kVideoFrameKey : kVideoFrameDelta; - rtpHeader->type.Video.codecHeader.VP8.startBit = parsedPacket.info.VP8.startFragment; // Start of partition - rtpHeader->type.Video.codecHeader.VP8.stopBit= parsedPacket.info.VP8.stopFragment; // Stop of partition + RTPVideoHeaderVP8 *toHeader = &rtpHeader->type.Video.codecHeader.VP8; + ModuleRTPUtility::RTPPayloadVP8 *fromHeader = &parsedPacket.info.VP8; - rtpHeader->type.Video.isFirstPacket = parsedPacket.info.VP8.beginningOfFrame; + rtpHeader->type.Video.isFirstPacket = fromHeader->beginningOfPartition + && (fromHeader->partitionID == 0); + toHeader->pictureId = fromHeader->hasPictureID ? fromHeader->pictureID : + kNoPictureId; + toHeader->tl0PicIdx = fromHeader->hasTl0PicIdx ? fromHeader->tl0PicIdx : + kNoTl0PicIdx; + toHeader->temporalIdx = fromHeader->hasTID ? fromHeader->tID : + kNoTemporalIdx; if(CallbackOfReceivedPayloadData(parsedPacket.info.VP8.data, parsedPacket.info.VP8.dataLength, diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gyp b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gyp index cfa0258f5..63245c15b 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gyp +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gyp @@ -23,10 +23,25 @@ '.', ], 'sources': [ - 'rtp_format_vp8_unittest.h', 'rtp_format_vp8_unittest.cc', ], }, + + { + 'target_name': 'rtp_utility_test', + 'type': 'executable', + 'dependencies': [ + 'rtp_rtcp.gyp:rtp_rtcp', + '../../../../testing/gtest.gyp:gtest', + '../../../../testing/gtest.gyp:gtest_main', + ], + 'include_dirs': [ + '.', + ], + 'sources': [ + 'rtp_utility_test.cc', + ], + }, ], } diff --git a/src/modules/rtp_rtcp/source/rtp_utility.cc b/src/modules/rtp_rtcp/source/rtp_utility.cc index d3df0f316..957607f04 100644 --- a/src/modules/rtp_rtcp/source/rtp_utility.cc +++ b/src/modules/rtp_rtcp/source/rtp_utility.cc @@ -14,6 +14,8 @@ #include // ceil #include +#include "trace.h" + #if defined(_WIN32) #include // FILETIME #include // timeval @@ -374,12 +376,15 @@ ModuleRTPUtility::RTPPayload::SetType(RtpVideoCodecTypes videoType) } case kRtpVp8Video: { - info.VP8.beginningOfFrame = false; info.VP8.nonReferenceFrame = false; + info.VP8.beginningOfPartition = false; + info.VP8.partitionID = 0; info.VP8.hasPictureID = false; - info.VP8.fragments = false; - info.VP8.startFragment = false; - info.VP8.stopFragment = false; + info.VP8.hasTl0PicIdx = false; + info.VP8.hasTID = false; + info.VP8.pictureID = -1; + info.VP8.tl0PicIdx = -1; + info.VP8.tID = -1; break; } default: @@ -581,10 +586,13 @@ ModuleRTPUtility::RTPHeaderParser::Parse(WebRtcRTPHeader& parsedPacket) const } // RTP payload parser -ModuleRTPUtility::RTPPayloadParser::RTPPayloadParser(const RtpVideoCodecTypes videoType, - const WebRtc_UWord8* payloadData, - const WebRtc_UWord16 payloadDataLength) +ModuleRTPUtility::RTPPayloadParser::RTPPayloadParser( + const RtpVideoCodecTypes videoType, + const WebRtc_UWord8* payloadData, + const WebRtc_UWord16 payloadDataLength, + const WebRtc_Word32 id) : + _id(id), _dataPtr(payloadData), _dataLength(payloadDataLength), _videoType(videoType) @@ -793,63 +801,176 @@ ModuleRTPUtility::RTPPayloadParser::ParseMPEG4( return true; } + +// +// VP8 format: +// +// Payload descriptor +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |X|R|N|S|PartID | (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// X: |I|L|T| RSV-A | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// I: | PictureID | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// L: | TL0PICIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// T: | TID | RSV-B | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// +// Payload header (considered part of the actual payload, sent to decoder) +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |Size0|H| VER |P| +// +-+-+-+-+-+-+-+-+ +// | ... | +// + + + bool ModuleRTPUtility::RTPPayloadParser::ParseVP8(RTPPayload& parsedPacket) const { - parsedPacket.info.VP8.hasPictureID = (_dataPtr[0] & 0x10)?true:false; - parsedPacket.info.VP8.nonReferenceFrame = (_dataPtr[0] & 0x08)?true:false; - parsedPacket.info.VP8.fragments = (_dataPtr[0] & 0x06)?true:false; - parsedPacket.info.VP8.beginningOfFrame = (_dataPtr[0] & 0x01)?true:false; + RTPPayloadVP8 *vp8 = &parsedPacket.info.VP8; + const WebRtc_UWord8 *dataPtr = _dataPtr; + int dataLength = _dataLength; - if(parsedPacket.info.VP8.fragments) + // Parse mandatory first byte of payload descriptor + bool extension = (*dataPtr & 0x80); // X bit + vp8->nonReferenceFrame = (*dataPtr & 0x20); // N bit + vp8->beginningOfPartition = (*dataPtr & 0x10); // S bit + vp8->partitionID = (*dataPtr & 0x0F); // PartID field + + // Advance dataPtr and decrease remaining payload size + dataPtr++; + dataLength--; + + if (extension) { - WebRtc_UWord8 fragments = (_dataPtr[0] >> 1) & 0x03; - if( fragments == 1) - { - parsedPacket.info.VP8.startFragment = true; - parsedPacket.info.VP8.stopFragment = false; - } else if(fragments == 3) - { - parsedPacket.info.VP8.startFragment = false; - parsedPacket.info.VP8.stopFragment = true; - } else - { - parsedPacket.info.VP8.startFragment = false; - parsedPacket.info.VP8.stopFragment = false; - } - } else - { - parsedPacket.info.VP8.startFragment = true; - parsedPacket.info.VP8.stopFragment = true; + const int parsedBytes = ParseVP8Extension(vp8, dataPtr, dataLength); + if (parsedBytes < 0) return false; + dataPtr += parsedBytes; + dataLength -= parsedBytes; } - if(parsedPacket.info.VP8.hasPictureID) - { - WebRtc_UWord8 numBytesPictureId = 1; - while(_dataPtr[numBytesPictureId] & 0x80) - { - numBytesPictureId++; - } - parsedPacket.frameType = (_dataPtr[1+numBytesPictureId] & 0x01) ? kPFrame : kIFrame; // first bit after picture id - - if(!parsedPacket.info.VP8.startFragment) - { - // if not start fragment parse away all picture IDs - parsedPacket.info.VP8.hasPictureID = false; - parsedPacket.info.VP8.data = _dataPtr+numBytesPictureId; - parsedPacket.info.VP8.dataLength = _dataLength-numBytesPictureId; - return true; - } - } else + if (dataLength <= 0) { - parsedPacket.frameType = (_dataPtr[1] & 0x01) ? kPFrame : kIFrame; // first bit after picture id + WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, + "Error parsing VP8 payload descriptor; payload too short"); + return false; } - parsedPacket.info.VP8.data = _dataPtr+1; - parsedPacket.info.VP8.dataLength = _dataLength-1; + + // Read P bit from payload header (only at beginning of first partition) + if (dataLength > 0 && vp8->beginningOfPartition && vp8->partitionID == 0) + { + parsedPacket.frameType = (*dataPtr & 0x01) ? kPFrame : kIFrame; + } + else + { + parsedPacket.frameType = kPFrame; + } + + parsedPacket.info.VP8.data = dataPtr; + parsedPacket.info.VP8.dataLength = dataLength; return true; } +int +ModuleRTPUtility::RTPPayloadParser::ParseVP8Extension( + RTPPayloadVP8 *vp8, + const WebRtc_UWord8 *dataPtr, + int dataLength) const +{ + int parsedBytes = 0; + if (dataLength <= 0) return -1; + // Optional X field is present + vp8->hasPictureID = (*dataPtr & 0x80); // I bit + vp8->hasTl0PicIdx = (*dataPtr & 0x40); // L bit + vp8->hasTID = (*dataPtr & 0x20); // T bit + + // Advance dataPtr and decrease remaining payload size + dataPtr++; + parsedBytes++; + dataLength--; + + if (vp8->hasPictureID) + { + if (ParseVP8PictureID(vp8, &dataPtr, &dataLength, &parsedBytes) != 0) + { + return -1; + } + } + + if (vp8->hasTl0PicIdx) + { + if (ParseVP8Tl0PicIdx(vp8, &dataPtr, &dataLength, &parsedBytes) != 0) + { + return -1; + } + } + + if (vp8->hasTID) + { + if (ParseVP8TID(vp8, &dataPtr, &dataLength, &parsedBytes) != 0) + { + return -1; + } + } + return parsedBytes; +} + +int +ModuleRTPUtility::RTPPayloadParser::ParseVP8PictureID( + RTPPayloadVP8 *vp8, + const WebRtc_UWord8 **dataPtr, + int *dataLength, + int *parsedBytes) const +{ + if (*dataLength <= 0) return -1; + vp8->pictureID = (**dataPtr & 0x7F); + if (**dataPtr & 0x80) + { + (*dataPtr)++; + (*parsedBytes)++; + if (--(*dataLength) <= 0) return -1; + // PictureID is 15 bits + vp8->pictureID = (vp8->pictureID << 8) + **dataPtr; + } + (*dataPtr)++; + (*parsedBytes)++; + (*dataLength)--; + return 0; +} + +int +ModuleRTPUtility::RTPPayloadParser::ParseVP8Tl0PicIdx( + RTPPayloadVP8 *vp8, + const WebRtc_UWord8 **dataPtr, + int *dataLength, + int *parsedBytes) const +{ + if (*dataLength <= 0) return -1; + vp8->tl0PicIdx = **dataPtr; + (*dataPtr)++; + (*parsedBytes)++; + (*dataLength)--; + return 0; +} + +int ModuleRTPUtility::RTPPayloadParser::ParseVP8TID( + RTPPayloadVP8 *vp8, + const WebRtc_UWord8 **dataPtr, + int *dataLength, + int *parsedBytes) const +{ + if (*dataLength <= 0) return -1; + vp8->tID = ((**dataPtr >> 5) & 0x07); + (*dataPtr)++; + (*parsedBytes)++; + (*dataLength)--; + return 0; +} + bool ModuleRTPUtility::RTPPayloadParser::H263PictureStartCode(const WebRtc_UWord8* data, const bool skipFirst2bytes) const { diff --git a/src/modules/rtp_rtcp/source/rtp_utility.h b/src/modules/rtp_rtcp/source/rtp_utility.h index e44bf846f..4481b6369 100644 --- a/src/modules/rtp_rtcp/source/rtp_utility.h +++ b/src/modules/rtp_rtcp/source/rtp_utility.h @@ -139,12 +139,15 @@ namespace ModuleRTPUtility }; struct RTPPayloadVP8 { - bool beginningOfFrame; bool nonReferenceFrame; + bool beginningOfPartition; + int partitionID; bool hasPictureID; - bool fragments; - bool startFragment; - bool stopFragment; + bool hasTl0PicIdx; + bool hasTID; + int pictureID; + int tl0PicIdx; + int tID; const WebRtc_UWord8* data; WebRtc_UWord16 dataLength; @@ -172,7 +175,8 @@ namespace ModuleRTPUtility public: RTPPayloadParser(const RtpVideoCodecTypes payloadType, const WebRtc_UWord8* payloadData, - const WebRtc_UWord16 payloadDataLength); // Length w/o padding. + const WebRtc_UWord16 payloadDataLength, // Length w/o padding. + const WebRtc_Word32 id); ~RTPPayloadParser(); @@ -188,6 +192,25 @@ namespace ModuleRTPUtility bool ParseVP8(RTPPayload& parsedPacket) const; + int ParseVP8Extension(RTPPayloadVP8 *vp8, + const WebRtc_UWord8 *dataPtr, + int dataLength) const; + + int ParseVP8PictureID(RTPPayloadVP8 *vp8, + const WebRtc_UWord8 **dataPtr, + int *dataLength, + int *parsedBytes) const; + + int ParseVP8Tl0PicIdx(RTPPayloadVP8 *vp8, + const WebRtc_UWord8 **dataPtr, + int *dataLength, + int *parsedBytes) const; + + int ParseVP8TID(RTPPayloadVP8 *vp8, + const WebRtc_UWord8 **dataPtr, + int *dataLength, + int *parsedBytes) const; + // H.263 bool H263PictureStartCode(const WebRtc_UWord8* data, const bool skipFirst2bytes = false) const; @@ -199,6 +222,7 @@ namespace ModuleRTPUtility FrameTypes GetH263FrameType(const WebRtc_UWord8* inputVideoBuffer) const; private: + WebRtc_Word32 _id; const WebRtc_UWord8* _dataPtr; const WebRtc_UWord16 _dataLength; const RtpVideoCodecTypes _videoType; diff --git a/src/modules/rtp_rtcp/source/rtp_utility_test.cc b/src/modules/rtp_rtcp/source/rtp_utility_test.cc new file mode 100644 index 000000000..07b7c7cc2 --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_utility_test.cc @@ -0,0 +1,269 @@ +/* + * 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. + */ + + +/* + * This file includes unit tests for the ModuleRTPUtility. + */ + +#include + +#include "typedefs.h" +#include "rtp_utility.h" +#include "rtp_format_vp8.h" + +namespace { + +using webrtc::ModuleRTPUtility::RTPPayloadParser; +using webrtc::ModuleRTPUtility::RTPPayload; +using webrtc::ModuleRTPUtility::RTPPayloadVP8; +using webrtc::RtpVideoCodecTypes; + +// Payload descriptor +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |X|R|N|S|PartID | (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// X: |I|L|T| RSV-A | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// I: | PictureID | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// L: | TL0PICIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// T: | TID | RSV-B | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// +// Payload header +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |Size0|H| VER |P| +// +-+-+-+-+-+-+-+-+ +// | Size1 | +// +-+-+-+-+-+-+-+-+ +// | Size2 | +// +-+-+-+-+-+-+-+-+ +// | Bytes 4..N of | +// | VP8 payload | +// : : +// +-+-+-+-+-+-+-+-+ +// | OPTIONAL RTP | +// | padding | +// : : +// +-+-+-+-+-+-+-+-+ + +void VerifyBasicHeader(const RTPPayloadVP8 &header, bool N, bool S, int PartID) +{ + EXPECT_EQ(N, header.nonReferenceFrame); + EXPECT_EQ(S, header.beginningOfPartition); + EXPECT_EQ(PartID, header.partitionID); +} + +void VerifyExtensions(const RTPPayloadVP8 &header, bool I, bool L, bool T) +{ + EXPECT_EQ(I, header.hasPictureID); + EXPECT_EQ(L, header.hasTl0PicIdx); + EXPECT_EQ(T, header.hasTID); +} + +TEST(ParseVP8Test, BasicHeader) { + WebRtc_UWord8 payload[4] = {0}; + payload[0] = 0x14; // binary 0001 0100; S = 1, PartID = 4 + payload[1] = 0x01; // P frame + + RTPPayloadParser rtpPayloadParser(webrtc::kRtpVp8Video, payload, 4, 0); + + RTPPayload parsedPacket; + ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); + + EXPECT_EQ(webrtc::ModuleRTPUtility::kPFrame, parsedPacket.frameType); + EXPECT_EQ(webrtc::kRtpVp8Video, parsedPacket.type); + + VerifyBasicHeader(parsedPacket.info.VP8, 0 /*N*/, 1 /*S*/, 4 /*PartID*/); + VerifyExtensions(parsedPacket.info.VP8, 0 /*I*/, 0 /*L*/, 0 /*T*/); + + EXPECT_EQ(payload + 1, parsedPacket.info.VP8.data); + EXPECT_EQ(4 - 1, parsedPacket.info.VP8.dataLength); +} + +TEST(ParseVP8Test, PictureID) { + WebRtc_UWord8 payload[10] = {0}; + payload[0] = 0xA0; + payload[1] = 0x80; + payload[2] = 17; + + RTPPayloadParser rtpPayloadParser(webrtc::kRtpVp8Video, payload, 10, 0); + + RTPPayload parsedPacket; + ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); + + EXPECT_EQ(webrtc::ModuleRTPUtility::kPFrame, parsedPacket.frameType); + EXPECT_EQ(webrtc::kRtpVp8Video, parsedPacket.type); + + VerifyBasicHeader(parsedPacket.info.VP8, 1 /*N*/, 0 /*S*/, 0 /*PartID*/); + VerifyExtensions(parsedPacket.info.VP8, 1 /*I*/, 0 /*L*/, 0 /*T*/); + + EXPECT_EQ(17, parsedPacket.info.VP8.pictureID); + + EXPECT_EQ(payload + 3, parsedPacket.info.VP8.data); + EXPECT_EQ(10 - 3, parsedPacket.info.VP8.dataLength); + + + // Re-use payload, but change to long PictureID + payload[2] = 0x80 | 17; + payload[3] = 17; + RTPPayloadParser rtpPayloadParser2(webrtc::kRtpVp8Video, payload, 10, 0); + + ASSERT_TRUE(rtpPayloadParser2.Parse(parsedPacket)); + + VerifyBasicHeader(parsedPacket.info.VP8, 1 /*N*/, 0 /*S*/, 0 /*PartID*/); + VerifyExtensions(parsedPacket.info.VP8, 1 /*I*/, 0 /*L*/, 0 /*T*/); + + EXPECT_EQ((17<<8) + 17, parsedPacket.info.VP8.pictureID); + + EXPECT_EQ(payload + 4, parsedPacket.info.VP8.data); + EXPECT_EQ(10 - 4, parsedPacket.info.VP8.dataLength); +} + +TEST(ParseVP8Test, Tl0PicIdx) { + WebRtc_UWord8 payload[10] = {0}; + payload[0] = 0x90; + payload[1] = 0x40; + payload[2] = 17; + + RTPPayloadParser rtpPayloadParser(webrtc::kRtpVp8Video, payload, 10, 0); + + RTPPayload parsedPacket; + ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); + + EXPECT_EQ(webrtc::ModuleRTPUtility::kIFrame, parsedPacket.frameType); + EXPECT_EQ(webrtc::kRtpVp8Video, parsedPacket.type); + + VerifyBasicHeader(parsedPacket.info.VP8, 0 /*N*/, 1 /*S*/, 0 /*PartID*/); + VerifyExtensions(parsedPacket.info.VP8, 0 /*I*/, 1 /*L*/, 0 /*T*/); + + EXPECT_EQ(17, parsedPacket.info.VP8.tl0PicIdx); + + EXPECT_EQ(payload + 3, parsedPacket.info.VP8.data); + EXPECT_EQ(10 - 3, parsedPacket.info.VP8.dataLength); +} + +TEST(ParseVP8Test, TID) { + WebRtc_UWord8 payload[10] = {0}; + payload[0] = 0x88; + payload[1] = 0x20; + payload[2] = 0x40; + + RTPPayloadParser rtpPayloadParser(webrtc::kRtpVp8Video, payload, 10, 0); + + RTPPayload parsedPacket; + ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); + + EXPECT_EQ(webrtc::ModuleRTPUtility::kPFrame, parsedPacket.frameType); + EXPECT_EQ(webrtc::kRtpVp8Video, parsedPacket.type); + + VerifyBasicHeader(parsedPacket.info.VP8, 0 /*N*/, 0 /*S*/, 8 /*PartID*/); + VerifyExtensions(parsedPacket.info.VP8, 0 /*I*/, 0 /*L*/, 1 /*T*/); + + EXPECT_EQ(2, parsedPacket.info.VP8.tID); + + EXPECT_EQ(payload + 3, parsedPacket.info.VP8.data); + EXPECT_EQ(10 - 3, parsedPacket.info.VP8.dataLength); +} + +TEST(ParseVP8Test, MultipleExtensions) { + WebRtc_UWord8 payload[10] = {0}; + payload[0] = 0x88; + payload[1] = 0x80 | 0x40 | 0x20; + payload[2] = 0x80 | 17; // PictureID, high 7 bits + payload[3] = 17; // PictureID, low 8 bits + payload[4] = 42; // Tl0PicIdx + payload[5] = 0x40; // TID + + RTPPayloadParser rtpPayloadParser(webrtc::kRtpVp8Video, payload, 10, 0); + + RTPPayload parsedPacket; + ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); + + EXPECT_EQ(webrtc::ModuleRTPUtility::kPFrame, parsedPacket.frameType); + EXPECT_EQ(webrtc::kRtpVp8Video, parsedPacket.type); + + VerifyBasicHeader(parsedPacket.info.VP8, 0 /*N*/, 0 /*S*/, 8 /*PartID*/); + VerifyExtensions(parsedPacket.info.VP8, 1 /*I*/, 1 /*L*/, 1 /*T*/); + + EXPECT_EQ((17<<8) + 17, parsedPacket.info.VP8.pictureID); + EXPECT_EQ(42, parsedPacket.info.VP8.tl0PicIdx); + EXPECT_EQ(2, parsedPacket.info.VP8.tID); + + EXPECT_EQ(payload + 6, parsedPacket.info.VP8.data); + EXPECT_EQ(10 - 6, parsedPacket.info.VP8.dataLength); +} + +TEST(ParseVP8Test, TooShortHeader) { + WebRtc_UWord8 payload[4] = {0}; + payload[0] = 0x88; + payload[1] = 0x80 | 0x40 | 0x20; // All extensions are enabled + payload[2] = 0x80 | 17; //... but only 2 bytes PictureID is provided + payload[3] = 17; // PictureID, low 8 bits + + RTPPayloadParser rtpPayloadParser(webrtc::kRtpVp8Video, payload, 4, 0); + + RTPPayload parsedPacket; + EXPECT_FALSE(rtpPayloadParser.Parse(parsedPacket)); +} + +using webrtc::RtpFormatVp8; +using webrtc::RTPVideoHeaderVP8; + +TEST(ParseVP8Test, TestWithPacketizer) { + WebRtc_UWord8 payload[10] = {0}; + WebRtc_UWord8 packet[20] = {0}; + RTPVideoHeaderVP8 inputHeader; + inputHeader.nonReference = true; + inputHeader.pictureId = 300; + inputHeader.temporalIdx = 1; + inputHeader.tl0PicIdx = -1; // disable + RtpFormatVp8 packetizer = RtpFormatVp8(payload, 10, inputHeader); + bool last; + int send_bytes; + ASSERT_EQ(0, packetizer.NextPacket(20, packet, &send_bytes, &last)); + ASSERT_TRUE(last); + + RTPPayloadParser rtpPayloadParser(webrtc::kRtpVp8Video, packet, send_bytes, + 0); + + RTPPayload parsedPacket; + ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); + + EXPECT_EQ(webrtc::ModuleRTPUtility::kIFrame, parsedPacket.frameType); + EXPECT_EQ(webrtc::kRtpVp8Video, parsedPacket.type); + + VerifyBasicHeader(parsedPacket.info.VP8, + inputHeader.nonReference /*N*/, + 1 /*S*/, + 0 /*PartID*/); + VerifyExtensions(parsedPacket.info.VP8, + 1 /*I*/, + 0 /*L*/, + 1 /*T*/); + + EXPECT_EQ(inputHeader.pictureId, parsedPacket.info.VP8.pictureID); + EXPECT_EQ(inputHeader.temporalIdx, parsedPacket.info.VP8.tID); + + EXPECT_EQ(packet + 5, parsedPacket.info.VP8.data); + EXPECT_EQ(send_bytes - 5, parsedPacket.info.VP8.dataLength); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} + +} // namespace diff --git a/src/modules/video_coding/codecs/vp8/main/interface/vp8.h b/src/modules/video_coding/codecs/vp8/main/interface/vp8.h index 3c918db3f..593543765 100644 --- a/src/modules/video_coding/codecs/vp8/main/interface/vp8.h +++ b/src/modules/video_coding/codecs/vp8/main/interface/vp8.h @@ -201,7 +201,7 @@ public: // WEBRTC_VIDEO_CODEC_ERR_PARAMETER virtual WebRtc_Word32 Decode(const EncodedImage& inputImage, bool missingFrames, - const CodecSpecificInfo* /*codecSpecificInfo*/, + const CodecSpecificInfo* codecSpecificInfo, WebRtc_Word64 /*renderTimeMs*/); // Register a decode complete callback object. diff --git a/src/modules/video_coding/codecs/vp8/main/source/vp8.cc b/src/modules/video_coding/codecs/vp8/main/source/vp8.cc index 92e5c7285..a76982a4b 100644 --- a/src/modules/video_coding/codecs/vp8/main/source/vp8.cc +++ b/src/modules/video_coding/codecs/vp8/main/source/vp8.cc @@ -694,7 +694,7 @@ VP8Decoder::InitDecode(const VideoCodec* inst, WebRtc_Word32 VP8Decoder::Decode(const EncodedImage& inputImage, bool missingFrames, - const CodecSpecificInfo* /*codecSpecificInfo*/, + const CodecSpecificInfo* codecSpecificInfo, WebRtc_Word64 /*renderTimeMs*/) { if (!_inited) @@ -727,16 +727,6 @@ VP8Decoder::Decode(const EncodedImage& inputImage, vpx_dec_iter_t _iter = NULL; vpx_image_t* img; - // scan for number of bytes used for picture ID - WebRtc_UWord64 pictureID = inputImage._buffer[0] & 0x7F; - WebRtc_UWord8 numberOfBytes = 1; - if (inputImage._buffer[0] & 0x80) - { - pictureID <<= 8; - pictureID += inputImage._buffer[1]; - ++numberOfBytes; - } - // check for missing frames if (missingFrames) { @@ -749,8 +739,8 @@ VP8Decoder::Decode(const EncodedImage& inputImage, // we remove the picture ID here if (vpx_codec_decode(_decoder, - inputImage._buffer + numberOfBytes, - inputImage._length - numberOfBytes, + inputImage._buffer, + inputImage._length, 0, VPX_DL_REALTIME)) { @@ -761,7 +751,7 @@ VP8Decoder::Decode(const EncodedImage& inputImage, if (inputImage._frameType == kKeyFrame) { // Reduce size due to PictureID that we won't copy. - const WebRtc_UWord32 bytesToCopy = inputImage._length - numberOfBytes; + const WebRtc_UWord32 bytesToCopy = inputImage._length; if (_lastKeyFrame._size < bytesToCopy) { delete [] _lastKeyFrame._buffer; @@ -781,8 +771,7 @@ VP8Decoder::Decode(const EncodedImage& inputImage, _lastKeyFrame._buffer = new WebRtc_UWord8[_lastKeyFrame._size]; } // Copy encoded frame. - memcpy(_lastKeyFrame._buffer, inputImage._buffer + numberOfBytes, - bytesToCopy); + memcpy(_lastKeyFrame._buffer, inputImage._buffer, bytesToCopy); _lastKeyFrame._length = bytesToCopy; } @@ -854,15 +843,22 @@ VP8Decoder::Decode(const EncodedImage& inputImage, // TODO(pw): how do we know it's a golden or alt reference frame? libvpx will // provide an API for now I added it temporarily - if((lastRefUpdates & VP8_GOLD_FRAME) || (lastRefUpdates & VP8_ALTR_FRAME)) + WebRtc_Word16 pictureId = codecSpecificInfo->codecSpecific.VP8.pictureId; + if (codecSpecificInfo && pictureId > -1) { - if (!missingFrames && (inputImage._completeFrame == true)) - //if (!corrupted) // TODO(pw): Can we engage this line intead of the above? + if ((lastRefUpdates & VP8_GOLD_FRAME) + || (lastRefUpdates & VP8_ALTR_FRAME)) { - _decodeCompleteCallback->ReceivedDecodedReferenceFrame(pictureID); + if (!missingFrames && (inputImage._completeFrame == true)) + //if (!corrupted) // TODO(pw): Can we engage this line instead of + // the above? + { + _decodeCompleteCallback->ReceivedDecodedReferenceFrame( + pictureId); + } } + _decodeCompleteCallback->ReceivedDecodedFrame(pictureId); } - _decodeCompleteCallback->ReceivedDecodedFrame(pictureID); #ifdef DEV_PIC_LOSS if (corrupted) diff --git a/src/modules/video_coding/main/source/generic_encoder.cc b/src/modules/video_coding/main/source/generic_encoder.cc index 44d287086..e7f6e45b7 100644 --- a/src/modules/video_coding/main/source/generic_encoder.cc +++ b/src/modules/video_coding/main/source/generic_encoder.cc @@ -248,14 +248,8 @@ void VCMEncodedFrameCallback::CopyCodecSpecific(const CodecSpecificInfo& info, switch (info.codecType) { case kVideoCodecVP8: { - if (info.codecSpecific.VP8.pictureId < 0) - { - (*rtp)->VP8.pictureId = kNoPictureId; - } - else - { - (*rtp)->VP8.pictureId = info.codecSpecific.VP8.pictureId; - } + (*rtp)->VP8.InitRTPVideoHeaderVP8(); + (*rtp)->VP8.pictureId = info.codecSpecific.VP8.pictureId; (*rtp)->VP8.nonReference = info.codecSpecific.VP8.nonReference; return; }