Updating to new VP8 rtp format

The VP8 packetizer and tests have been updated to the new
RTP draft (http://tools.ietf.org/html/draft-ietf-payload-vp8-01).
The receive-side parser is also updated, and a new unit test
is implemented for it. Finally, some data traversing work to
get the parsed information into the decoder.
Review URL: http://webrtc-codereview.appspot.com/116011

git-svn-id: http://webrtc.googlecode.com/svn/trunk@482 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
henrik.lundin@webrtc.org
2011-08-29 15:37:12 +00:00
parent 09734086c6
commit 8571af7be6
12 changed files with 922 additions and 251 deletions

View File

@ -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
{

View File

@ -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<WebRtc_UWord16> (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

View File

@ -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_;
};
}

View File

@ -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);

View File

@ -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,

View File

@ -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',
],
},
],
}

View File

@ -14,6 +14,8 @@
#include <cmath> // ceil
#include <cassert>
#include "trace.h"
#if defined(_WIN32)
#include <Windows.h> // FILETIME
#include <WinSock.h> // 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
{

View File

@ -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;

View File

@ -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 <gtest/gtest.h>
#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

View File

@ -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.

View File

@ -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)

View File

@ -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;
}