Adding a LayerSync bit to VP8 RTP header

Updated RtpFormatVp8, ModuleRTPUtility, VP8Encoder and VP8Decoder
to support a new LayerSync ("Y") bit. Note, in VP8Encoder the bit
must be used together with a non-negative value for temporalIdx.
Fixing the plumbing between RTP module and and from VP8 wrapper.
Updating unit tests; all pass.

The new bit is yet to be used by the VP8 wrapper.

Review URL: http://webrtc-codereview.appspot.com/323008

git-svn-id: http://webrtc.googlecode.com/svn/trunk@1169 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
henrik.lundin@webrtc.org 2011-12-13 14:11:06 +00:00
parent 4aae0e489f
commit eda86dc76b
16 changed files with 72 additions and 28 deletions

View file

@ -57,6 +57,7 @@ struct RTPVideoHeaderVP8
pictureId = kNoPictureId; pictureId = kNoPictureId;
tl0PicIdx = kNoTl0PicIdx; tl0PicIdx = kNoTl0PicIdx;
temporalIdx = kNoTemporalIdx; temporalIdx = kNoTemporalIdx;
layerSync = false;
keyIdx = kNoKeyIdx; keyIdx = kNoKeyIdx;
partitionId = 0; partitionId = 0;
beginningOfPartition = false; beginningOfPartition = false;
@ -70,6 +71,8 @@ struct RTPVideoHeaderVP8
WebRtc_Word16 tl0PicIdx; // TL0PIC_IDX, 8 bits; WebRtc_Word16 tl0PicIdx; // TL0PIC_IDX, 8 bits;
// kNoTl0PicIdx means no value provided. // kNoTl0PicIdx means no value provided.
WebRtc_Word8 temporalIdx; // Temporal layer index, or kNoTemporalIdx. WebRtc_Word8 temporalIdx; // Temporal layer index, or kNoTemporalIdx.
bool layerSync; // This frame is a layer sync frame.
// Disabled if temporalIdx == kNoTemporalIdx.
int keyIdx; // 5 bits; kNoKeyIdx means not used. int keyIdx; // 5 bits; kNoKeyIdx means not used.
int partitionId; // VP8 partition ID int partitionId; // VP8 partition ID
bool beginningOfPartition; // True if this packet is the first bool beginningOfPartition; // True if this packet is the first

View file

@ -167,7 +167,7 @@ int RtpFormatVp8::WriteHeaderAndPayload(int payload_bytes,
// +-+-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+-+
// L: | TL0PIC_IDX | (optional) // L: | TL0PIC_IDX | (optional)
// +-+-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+-+
// T/K: | TID | KEYIDX | (optional) // T/K: |TID:Y| KEYIDX | (optional)
// +-+-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+-+
assert(payload_bytes > 0); assert(payload_bytes > 0);
@ -280,7 +280,9 @@ int RtpFormatVp8::WriteTIDAndKeyIdxFields(WebRtc_UWord8* x_field,
*data_field = 0; *data_field = 0;
if (TIDFieldPresent()) { if (TIDFieldPresent()) {
*x_field |= kTBit; *x_field |= kTBit;
*data_field |= hdr_info_.temporalIdx << 5; assert(hdr_info_.temporalIdx >= 0 && hdr_info_.temporalIdx <= 3);
*data_field |= hdr_info_.temporalIdx << 6;
*data_field |= hdr_info_.layerSync ? kYBit : 0;
} }
if (KeyIdxFieldPresent()) { if (KeyIdxFieldPresent()) {
*x_field |= kKBit; *x_field |= kKBit;
@ -314,6 +316,8 @@ bool RtpFormatVp8::XFieldPresent() const {
} }
bool RtpFormatVp8::TIDFieldPresent() const { bool RtpFormatVp8::TIDFieldPresent() const {
assert((hdr_info_.layerSync == false) ||
(hdr_info_.temporalIdx != kNoTemporalIdx));
return (hdr_info_.temporalIdx != kNoTemporalIdx); return (hdr_info_.temporalIdx != kNoTemporalIdx);
} }

View file

@ -86,6 +86,7 @@ class RtpFormatVp8 {
static const int kLBit = 0x40; static const int kLBit = 0x40;
static const int kTBit = 0x20; static const int kTBit = 0x20;
static const int kKBit = 0x10; static const int kKBit = 0x10;
static const int kYBit = 0x20;
// Calculate size of next chunk to send. Returns 0 if none can be sent. // Calculate size of next chunk to send. Returns 0 if none can be sent.
int CalcNextSize(int max_payload_len, int remaining_bytes, int CalcNextSize(int max_payload_len, int remaining_bytes,
@ -114,7 +115,7 @@ class RtpFormatVp8 {
int WriteTl0PicIdxFields(WebRtc_UWord8* x_field, WebRtc_UWord8* buffer, int WriteTl0PicIdxFields(WebRtc_UWord8* x_field, WebRtc_UWord8* buffer,
int buffer_length, int* extension_length) const; int buffer_length, int* extension_length) const;
// Set the T and K bits in the x_field, and write TID and KeyIdx to the // Set the T and K bits in the x_field, and write TID, Y and KeyIdx to the
// appropriate position in buffer. The function returns 0 on success, // appropriate position in buffer. The function returns 0 on success,
// -1 otherwise. // -1 otherwise.
int WriteTIDAndKeyIdxFields(WebRtc_UWord8* x_field, WebRtc_UWord8* buffer, int WriteTIDAndKeyIdxFields(WebRtc_UWord8* x_field, WebRtc_UWord8* buffer,

View file

@ -94,7 +94,9 @@ void RtpFormatVp8Test::TearDown() {
#define EXPECT_BIT_K_EQ(x, a) EXPECT_BIT_EQ(x, 4, a) #define EXPECT_BIT_K_EQ(x, a) EXPECT_BIT_EQ(x, 4, a)
#define EXPECT_TID_EQ(x, a) EXPECT_EQ((((x) & 0xE0) >> 5), a) #define EXPECT_TID_EQ(x, a) EXPECT_EQ((((x) & 0xC0) >> 6), a)
#define EXPECT_BIT_Y_EQ(x, a) EXPECT_BIT_EQ(x, 5, a)
#define EXPECT_KEYIDX_EQ(x, a) EXPECT_EQ(((x) & 0x1F), a) #define EXPECT_KEYIDX_EQ(x, a) EXPECT_EQ(((x) & 0x1F), a)
@ -170,9 +172,11 @@ void RtpFormatVp8Test::CheckTIDAndKeyIdx() {
if (hdr_info_.temporalIdx != kNoTemporalIdx) { if (hdr_info_.temporalIdx != kNoTemporalIdx) {
EXPECT_BIT_T_EQ(buffer_[1], 1); EXPECT_BIT_T_EQ(buffer_[1], 1);
EXPECT_TID_EQ(buffer_[payload_start_], hdr_info_.temporalIdx); EXPECT_TID_EQ(buffer_[payload_start_], hdr_info_.temporalIdx);
EXPECT_BIT_Y_EQ(buffer_[payload_start_], hdr_info_.layerSync);
} else { } else {
EXPECT_BIT_T_EQ(buffer_[1], 0); EXPECT_BIT_T_EQ(buffer_[1], 0);
EXPECT_TID_EQ(buffer_[payload_start_], 0); EXPECT_TID_EQ(buffer_[payload_start_], 0);
EXPECT_BIT_Y_EQ(buffer_[payload_start_], false);
} }
if (hdr_info_.keyIdx != kNoKeyIdx) { if (hdr_info_.keyIdx != kNoKeyIdx) {
EXPECT_BIT_K_EQ(buffer_[1], 1); EXPECT_BIT_K_EQ(buffer_[1], 1);
@ -404,13 +408,14 @@ TEST_F(RtpFormatVp8Test, TestNonReferenceBit) {
EXPECT_BIT_N_EQ(buffer_[0], 1); EXPECT_BIT_N_EQ(buffer_[0], 1);
} }
// Verify Tl0PicIdx and TID fields // Verify Tl0PicIdx and TID fields, and layerSync bit.
TEST_F(RtpFormatVp8Test, TestTl0PicIdxAndTID) { TEST_F(RtpFormatVp8Test, TestTl0PicIdxAndTID) {
int send_bytes = 0; int send_bytes = 0;
bool last; bool last;
hdr_info_.tl0PicIdx = 117; hdr_info_.tl0PicIdx = 117;
hdr_info_.temporalIdx = 2; hdr_info_.temporalIdx = 2;
hdr_info_.layerSync = true;
RtpFormatVp8 packetizer = RtpFormatVp8(payload_data_, kPayloadSize, RtpFormatVp8 packetizer = RtpFormatVp8(payload_data_, kPayloadSize,
hdr_info_, *fragmentation_, hdr_info_, *fragmentation_,
kAggregate); kAggregate);

View file

@ -644,8 +644,13 @@ RTPReceiverVideo::ReceiveVp8Codec(WebRtcRTPHeader* rtpHeader,
kNoPictureId; kNoPictureId;
toHeader->tl0PicIdx = fromHeader->hasTl0PicIdx ? fromHeader->tl0PicIdx : toHeader->tl0PicIdx = fromHeader->hasTl0PicIdx ? fromHeader->tl0PicIdx :
kNoTl0PicIdx; kNoTl0PicIdx;
toHeader->temporalIdx = fromHeader->hasTID ? fromHeader->tID : if (fromHeader->hasTID) {
kNoTemporalIdx; toHeader->temporalIdx = fromHeader->tID;
toHeader->layerSync = fromHeader->layerSync;
} else {
toHeader->temporalIdx = kNoTemporalIdx;
toHeader->layerSync = false;
}
toHeader->keyIdx = fromHeader->hasKeyIdx ? fromHeader->keyIdx : kNoKeyIdx; toHeader->keyIdx = fromHeader->hasKeyIdx ? fromHeader->keyIdx : kNoKeyIdx;
toHeader->frameWidth = fromHeader->frameWidth; toHeader->frameWidth = fromHeader->frameWidth;

View file

@ -434,6 +434,7 @@ ModuleRTPUtility::RTPPayload::SetType(RtpVideoCodecTypes videoType)
info.VP8.pictureID = -1; info.VP8.pictureID = -1;
info.VP8.tl0PicIdx = -1; info.VP8.tl0PicIdx = -1;
info.VP8.tID = -1; info.VP8.tID = -1;
info.VP8.layerSync = false;
info.VP8.frameWidth = 0; info.VP8.frameWidth = 0;
info.VP8.frameHeight = 0; info.VP8.frameHeight = 0;
break; break;
@ -867,7 +868,7 @@ ModuleRTPUtility::RTPPayloadParser::ParseMPEG4(
// +-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+
// L: | TL0PICIDX | (OPTIONAL) // L: | TL0PICIDX | (OPTIONAL)
// +-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+
// T/K: | TID | KEYIDX | (OPTIONAL) // T/K: |TID:Y| KEYIDX | (OPTIONAL)
// +-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+
// //
// Payload header (considered part of the actual payload, sent to decoder) // Payload header (considered part of the actual payload, sent to decoder)
@ -1041,7 +1042,8 @@ int ModuleRTPUtility::RTPPayloadParser::ParseVP8TIDAndKeyIdx(
if (*dataLength <= 0) return -1; if (*dataLength <= 0) return -1;
if (vp8->hasTID) if (vp8->hasTID)
{ {
vp8->tID = ((**dataPtr >> 5) & 0x07); vp8->tID = ((**dataPtr >> 6) & 0x03);
vp8->layerSync = (**dataPtr & 0x20) ? true : false; // Y bit
} }
if (vp8->hasKeyIdx) if (vp8->hasKeyIdx)
{ {

View file

@ -167,6 +167,7 @@ namespace ModuleRTPUtility
int pictureID; int pictureID;
int tl0PicIdx; int tl0PicIdx;
int tID; int tID;
bool layerSync;
int keyIdx; int keyIdx;
int frameWidth; int frameWidth;
int frameHeight; int frameHeight;

View file

@ -35,7 +35,7 @@ using ModuleRTPUtility::RTPPayloadVP8;
// +-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+
// L: | TL0PICIDX | (OPTIONAL) // L: | TL0PICIDX | (OPTIONAL)
// +-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+
// T/K: | TID | KEYIDX | (OPTIONAL) // T/K: |TID:Y| KEYIDX | (OPTIONAL)
// +-+-+-+-+-+-+-+-+ // +-+-+-+-+-+-+-+-+
// //
// Payload header // Payload header
@ -153,11 +153,11 @@ TEST(ParseVP8Test, Tl0PicIdx) {
EXPECT_EQ(13 - 3, parsedPacket.info.VP8.dataLength); EXPECT_EQ(13 - 3, parsedPacket.info.VP8.dataLength);
} }
TEST(ParseVP8Test, TID) { TEST(ParseVP8Test, TIDAndLayerSync) {
WebRtc_UWord8 payload[10] = {0}; WebRtc_UWord8 payload[10] = {0};
payload[0] = 0x88; payload[0] = 0x88;
payload[1] = 0x20; payload[1] = 0x20;
payload[2] = 0x40; payload[2] = 0x80; // TID(2) + LayerSync(false)
RTPPayloadParser rtpPayloadParser(kRtpVp8Video, payload, 10, 0); RTPPayloadParser rtpPayloadParser(kRtpVp8Video, payload, 10, 0);
@ -171,6 +171,7 @@ TEST(ParseVP8Test, TID) {
VerifyExtensions(parsedPacket.info.VP8, 0 /*I*/, 0 /*L*/, 1 /*T*/, 0 /*K*/); VerifyExtensions(parsedPacket.info.VP8, 0 /*I*/, 0 /*L*/, 1 /*T*/, 0 /*K*/);
EXPECT_EQ(2, parsedPacket.info.VP8.tID); EXPECT_EQ(2, parsedPacket.info.VP8.tID);
EXPECT_FALSE(parsedPacket.info.VP8.layerSync);
EXPECT_EQ(payload + 3, parsedPacket.info.VP8.data); EXPECT_EQ(payload + 3, parsedPacket.info.VP8.data);
EXPECT_EQ(10 - 3, parsedPacket.info.VP8.dataLength); EXPECT_EQ(10 - 3, parsedPacket.info.VP8.dataLength);
@ -206,7 +207,7 @@ TEST(ParseVP8Test, MultipleExtensions) {
payload[2] = 0x80 | 17; // PictureID, high 7 bits. payload[2] = 0x80 | 17; // PictureID, high 7 bits.
payload[3] = 17; // PictureID, low 8 bits. payload[3] = 17; // PictureID, low 8 bits.
payload[4] = 42; // Tl0PicIdx. payload[4] = 42; // Tl0PicIdx.
payload[5] = 0x40 | 0x11; // TID + KEYIDX. payload[5] = 0x40 | 0x20 | 0x11; // TID(1) + LayerSync(true) + KEYIDX(17).
RTPPayloadParser rtpPayloadParser(kRtpVp8Video, payload, 10, 0); RTPPayloadParser rtpPayloadParser(kRtpVp8Video, payload, 10, 0);
@ -221,7 +222,7 @@ TEST(ParseVP8Test, MultipleExtensions) {
EXPECT_EQ((17<<8) + 17, parsedPacket.info.VP8.pictureID); EXPECT_EQ((17<<8) + 17, parsedPacket.info.VP8.pictureID);
EXPECT_EQ(42, parsedPacket.info.VP8.tl0PicIdx); EXPECT_EQ(42, parsedPacket.info.VP8.tl0PicIdx);
EXPECT_EQ(2, parsedPacket.info.VP8.tID); EXPECT_EQ(1, parsedPacket.info.VP8.tID);
EXPECT_EQ(17, parsedPacket.info.VP8.keyIdx); EXPECT_EQ(17, parsedPacket.info.VP8.keyIdx);
EXPECT_EQ(payload + 6, parsedPacket.info.VP8.data); EXPECT_EQ(payload + 6, parsedPacket.info.VP8.data);
@ -248,6 +249,7 @@ TEST(ParseVP8Test, TestWithPacketizer) {
inputHeader.nonReference = true; inputHeader.nonReference = true;
inputHeader.pictureId = 300; inputHeader.pictureId = 300;
inputHeader.temporalIdx = 1; inputHeader.temporalIdx = 1;
inputHeader.layerSync = false;
inputHeader.tl0PicIdx = kNoTl0PicIdx; // Disable. inputHeader.tl0PicIdx = kNoTl0PicIdx; // Disable.
inputHeader.keyIdx = 31; inputHeader.keyIdx = 31;
RtpFormatVp8 packetizer = RtpFormatVp8(payload, 10, inputHeader); RtpFormatVp8 packetizer = RtpFormatVp8(payload, 10, inputHeader);
@ -276,6 +278,7 @@ TEST(ParseVP8Test, TestWithPacketizer) {
EXPECT_EQ(inputHeader.pictureId, parsedPacket.info.VP8.pictureID); EXPECT_EQ(inputHeader.pictureId, parsedPacket.info.VP8.pictureID);
EXPECT_EQ(inputHeader.temporalIdx, parsedPacket.info.VP8.tID); EXPECT_EQ(inputHeader.temporalIdx, parsedPacket.info.VP8.tID);
EXPECT_EQ(inputHeader.layerSync, parsedPacket.info.VP8.layerSync);
EXPECT_EQ(inputHeader.keyIdx, parsedPacket.info.VP8.keyIdx); EXPECT_EQ(inputHeader.keyIdx, parsedPacket.info.VP8.keyIdx);
EXPECT_EQ(packet + 5, parsedPacket.info.VP8.data); EXPECT_EQ(packet + 5, parsedPacket.info.VP8.data);

View file

@ -33,6 +33,7 @@ struct CodecSpecificInfoVP8
bool nonReference; bool nonReference;
WebRtc_UWord8 simulcastIdx; WebRtc_UWord8 simulcastIdx;
WebRtc_UWord8 temporalIdx; WebRtc_UWord8 temporalIdx;
bool layerSync;
int tl0PicIdx; // Negative value to skip tl0PicIdx int tl0PicIdx; // Negative value to skip tl0PicIdx
WebRtc_Word8 keyIdx; // negative value to skip keyIdx WebRtc_Word8 keyIdx; // negative value to skip keyIdx
}; };

View file

@ -509,6 +509,7 @@ void VP8Encoder::PopulateCodecSpecific(CodecSpecificInfo* codec_specific,
} else { } else {
#endif #endif
vp8Info->temporalIdx = kNoTemporalIdx; vp8Info->temporalIdx = kNoTemporalIdx;
vp8Info->layerSync = false;
vp8Info->tl0PicIdx = kNoTl0PicIdx; vp8Info->tl0PicIdx = kNoTl0PicIdx;
#if WEBRTC_LIBVPX_VERSION >= 971 #if WEBRTC_LIBVPX_VERSION >= 971
} }

View file

@ -112,6 +112,7 @@ void VCMEncodedFrame::CopyCodecSpecific(const RTPVideoHeader* header)
// This is the first packet for this frame. // This is the first packet for this frame.
_codecSpecificInfo.codecSpecific.VP8.pictureId = -1; _codecSpecificInfo.codecSpecific.VP8.pictureId = -1;
_codecSpecificInfo.codecSpecific.VP8.temporalIdx = 0; _codecSpecificInfo.codecSpecific.VP8.temporalIdx = 0;
_codecSpecificInfo.codecSpecific.VP8.layerSync = false;
_codecSpecificInfo.codecSpecific.VP8.keyIdx = -1; _codecSpecificInfo.codecSpecific.VP8.keyIdx = -1;
_codecSpecificInfo.codecType = kVideoCodecVP8; _codecSpecificInfo.codecType = kVideoCodecVP8;
} }
@ -126,6 +127,8 @@ void VCMEncodedFrame::CopyCodecSpecific(const RTPVideoHeader* header)
{ {
_codecSpecificInfo.codecSpecific.VP8.temporalIdx = _codecSpecificInfo.codecSpecific.VP8.temporalIdx =
header->codecHeader.VP8.temporalIdx; header->codecHeader.VP8.temporalIdx;
_codecSpecificInfo.codecSpecific.VP8.layerSync =
header->codecHeader.VP8.layerSync;
} }
if (header->codecHeader.VP8.keyIdx != kNoKeyIdx) if (header->codecHeader.VP8.keyIdx != kNoKeyIdx)
{ {

View file

@ -76,6 +76,10 @@ int VCMFrameBuffer::TemporalId() const {
return _sessionInfo.TemporalId(); return _sessionInfo.TemporalId();
} }
bool VCMFrameBuffer::LayerSync() const {
return _sessionInfo.LayerSync();
}
int VCMFrameBuffer::Tl0PicId() const { int VCMFrameBuffer::Tl0PicId() const {
return _sessionInfo.Tl0PicId(); return _sessionInfo.Tl0PicId();
} }

View file

@ -58,6 +58,7 @@ public:
int PictureId() const; int PictureId() const;
int TemporalId() const; int TemporalId() const;
bool LayerSync() const;
int Tl0PicId() const; int Tl0PicId() const;
bool NonReference() const; bool NonReference() const;

View file

@ -267,6 +267,8 @@ void VCMEncodedFrameCallback::CopyCodecSpecific(const CodecSpecificInfo& info,
info.codecSpecific.VP8.nonReference; info.codecSpecific.VP8.nonReference;
(*rtp)->codecHeader.VP8.temporalIdx = (*rtp)->codecHeader.VP8.temporalIdx =
info.codecSpecific.VP8.temporalIdx; info.codecSpecific.VP8.temporalIdx;
(*rtp)->codecHeader.VP8.layerSync =
info.codecSpecific.VP8.layerSync;
(*rtp)->codecHeader.VP8.tl0PicIdx = (*rtp)->codecHeader.VP8.tl0PicIdx =
info.codecSpecific.VP8.tl0PicIdx; info.codecSpecific.VP8.tl0PicIdx;
(*rtp)->codecHeader.VP8.keyIdx = (*rtp)->codecHeader.VP8.keyIdx =

View file

@ -59,6 +59,13 @@ int VCMSessionInfo::TemporalId() const {
return packets_.front().codecSpecificHeader.codecHeader.VP8.temporalIdx; return packets_.front().codecSpecificHeader.codecHeader.VP8.temporalIdx;
} }
bool VCMSessionInfo::LayerSync() const {
if (packets_.empty() ||
packets_.front().codecSpecificHeader.codec != kRTPVideoVP8)
return false;
return packets_.front().codecSpecificHeader.codecHeader.VP8.layerSync;
}
int VCMSessionInfo::Tl0PicId() const { int VCMSessionInfo::Tl0PicId() const {
if (packets_.empty() || if (packets_.empty() ||
packets_.front().codecSpecificHeader.codec != kRTPVideoVP8) packets_.front().codecSpecificHeader.codec != kRTPVideoVP8)

View file

@ -63,6 +63,7 @@ class VCMSessionInfo {
int HighSequenceNumber() const; int HighSequenceNumber() const;
int PictureId() const; int PictureId() const;
int TemporalId() const; int TemporalId() const;
bool LayerSync() const;
int Tl0PicId() const; int Tl0PicId() const;
bool NonReference() const; bool NonReference() const;
int PrepareForDecode(uint8_t* frame_buffer); int PrepareForDecode(uint8_t* frame_buffer);