mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-07-28 13:04:07 +08:00
test VP9 RTP parsing through decoder
This commit is contained in:
@@ -46,11 +46,12 @@ struct RTPPayloadVP9 {
|
||||
bool hasGof = false;
|
||||
int numberOfFramesInGof = -1;
|
||||
std::vector<VP9ResolutionLayer> resolutions;
|
||||
int parse(unsigned char* data, int dataLength);
|
||||
|
||||
int parse(const unsigned char *data, int dataLength);
|
||||
bool keyFrame() const { return beginningOfLayerFrame && !interPicturePrediction; }
|
||||
std::string dump() const {
|
||||
std::string dump() const {
|
||||
char line[64] = {0};
|
||||
snprintf(line, sizeof(line), "%c%c%c%c%c%c%c- %d %d, %d %d",
|
||||
snprintf(line, sizeof(line), "%c%c%c%c%c%c%c- %d %d, %d %d",
|
||||
hasPictureID ? 'I' : ' ',
|
||||
interPicturePrediction ? 'P' : ' ',
|
||||
hasLayerIndices ? 'L' : ' ',
|
||||
@@ -107,102 +108,107 @@ struct RTPPayloadVP9 {
|
||||
#define kBBit 0x08
|
||||
#define kEBit 0x04
|
||||
#define kVBit 0x02
|
||||
int RTPPayloadVP9::parse(unsigned char *data, int dataLength) {
|
||||
const unsigned char* dataPtr = data;
|
||||
const unsigned char* dataEnd = data + dataLength;
|
||||
|
||||
#define VP9_CHECK_BOUNDS(n) do { if (dataPtr + (n) > dataEnd) return -1; } while (0)
|
||||
int RTPPayloadVP9::parse(const unsigned char *data, int dataLength) {
|
||||
if (!data || dataLength <= 0) {
|
||||
return -1;
|
||||
}
|
||||
const unsigned char *dataPtr = data;
|
||||
size_t remaining = static_cast<size_t>(dataLength);
|
||||
auto readByte = [&]() -> int {
|
||||
if (remaining < 1) {
|
||||
return -1;
|
||||
}
|
||||
--remaining;
|
||||
return *dataPtr++;
|
||||
};
|
||||
auto skipBytes = [&](size_t count) -> bool {
|
||||
if (remaining < count) {
|
||||
return false;
|
||||
}
|
||||
remaining -= count;
|
||||
dataPtr += count;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Parse mandatory first byte of payload descriptor
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
this->hasPictureID = (*dataPtr & kIBit); // I bit
|
||||
this->interPicturePrediction = (*dataPtr & kPBit); // P bit
|
||||
this->hasLayerIndices = (*dataPtr & kLBit); // L bit
|
||||
this->flexibleMode = (*dataPtr & kFBit); // F bit
|
||||
this->beginningOfLayerFrame = (*dataPtr & kBBit); // B bit
|
||||
this->endingOfLayerFrame = (*dataPtr & kEBit); // E bit
|
||||
this->hasScalabilityStructure = (*dataPtr & kVBit); // V bit
|
||||
dataPtr++;
|
||||
int byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
this->hasPictureID = (byte & kIBit); // I bit
|
||||
this->interPicturePrediction = (byte & kPBit); // P bit
|
||||
this->hasLayerIndices = (byte & kLBit); // L bit
|
||||
this->flexibleMode = (byte & kFBit); // F bit
|
||||
this->beginningOfLayerFrame = (byte & kBBit); // B bit
|
||||
this->endingOfLayerFrame = (byte & kEBit); // E bit
|
||||
this->hasScalabilityStructure = (byte & kVBit); // V bit
|
||||
|
||||
if (this->hasPictureID) {
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
this->largePictureID = (*dataPtr & 0x80); // M bit
|
||||
this->pictureID = (*dataPtr & 0x7F);
|
||||
byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
this->largePictureID = (byte & 0x80); // M bit
|
||||
this->pictureID = (byte & 0x7F);
|
||||
if (this->largePictureID) {
|
||||
dataPtr++;
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
this->pictureID = ntohs((this->pictureID << 16) + (*dataPtr & 0xFF));
|
||||
byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
this->pictureID = (this->pictureID << 8) | byte;
|
||||
}
|
||||
dataPtr++;
|
||||
}
|
||||
|
||||
if (this->hasLayerIndices) {
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
this->temporalID = (*dataPtr & 0xE0) >> 5; // T bits
|
||||
this->isSwitchingUp = (*dataPtr & 0x10); // U bit
|
||||
this->spatialID = (*dataPtr & 0x0E) >> 1; // S bits
|
||||
this->isInterLayeredDepUsed = (*dataPtr & 0x01); // D bit
|
||||
if (this->flexibleMode) { // marked in webrtc code
|
||||
do {
|
||||
dataPtr++;
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
this->referenceIdx = (*dataPtr & 0xFE) >> 1;
|
||||
this->additionalReferenceIdx = (*dataPtr & 0x01); // D bit
|
||||
} while (this->additionalReferenceIdx);
|
||||
} else {
|
||||
dataPtr++;
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
this->tl0PicIdx = (*dataPtr & 0xFF);
|
||||
byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
this->temporalID = (byte & 0xE0) >> 5; // T bits
|
||||
this->isSwitchingUp = (byte & 0x10); // U bit
|
||||
this->spatialID = (byte & 0x0E) >> 1; // S bits
|
||||
this->isInterLayeredDepUsed = (byte & 0x01); // D bit
|
||||
if (!this->flexibleMode) {
|
||||
byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
this->tl0PicIdx = byte;
|
||||
}
|
||||
dataPtr++;
|
||||
}
|
||||
|
||||
if (this->flexibleMode && this->interPicturePrediction) {
|
||||
/* Skip reference indices */
|
||||
uint8_t nbit;
|
||||
// The VP9 payload descriptor permits at most three reference indices.
|
||||
bool nbit;
|
||||
int referenceCount = 0;
|
||||
do {
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
uint8_t p_diff = (*dataPtr & 0xFE) >> 1;
|
||||
nbit = (*dataPtr & 0x01);
|
||||
dataPtr++;
|
||||
if (++referenceCount > 3) return -1;
|
||||
byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
this->referenceIdx = (byte & 0xFE) >> 1;
|
||||
nbit = (byte & 0x01);
|
||||
this->additionalReferenceIdx = nbit;
|
||||
} while (nbit);
|
||||
}
|
||||
if (this->hasScalabilityStructure) {
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
this->spatialLayers = (*dataPtr & 0xE0) >> 5; // N_S bits
|
||||
this->hasResolution = (*dataPtr & 0x10); // Y bit
|
||||
this->hasGof = (*dataPtr & 0x08); // G bit
|
||||
dataPtr++;
|
||||
byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
this->spatialLayers = (byte & 0xE0) >> 5; // N_S bits
|
||||
this->hasResolution = (byte & 0x10); // Y bit
|
||||
this->hasGof = (byte & 0x08); // G bit
|
||||
if (this->hasResolution) {
|
||||
for (int i = 0; i <= this->spatialLayers; i++) {
|
||||
VP9_CHECK_BOUNDS(4);
|
||||
if (remaining < 4) return -1;
|
||||
int width = (dataPtr[0] << 8) + dataPtr[1];
|
||||
dataPtr += 2;
|
||||
int height = (dataPtr[0] << 8) + dataPtr[1];
|
||||
dataPtr += 2;
|
||||
// InfoL << "got vp9 " << width << "x" << height;
|
||||
int height = (dataPtr[2] << 8) + dataPtr[3];
|
||||
this->resolutions.push_back({ width, height });
|
||||
skipBytes(4);
|
||||
}
|
||||
}
|
||||
if (this->hasGof) {
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
this->numberOfFramesInGof = *dataPtr & 0xFF; // N_G bits
|
||||
dataPtr++;
|
||||
byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
this->numberOfFramesInGof = byte; // N_G bits
|
||||
for (int frame_index = 0; frame_index < this->numberOfFramesInGof; frame_index++) {
|
||||
// TODO(javierc): Read these values if needed
|
||||
VP9_CHECK_BOUNDS(1);
|
||||
int reference_indices = (*dataPtr & 0x0C) >> 2; // R bits
|
||||
dataPtr++;
|
||||
VP9_CHECK_BOUNDS(reference_indices);
|
||||
for (int reference_index = 0; reference_index < reference_indices; reference_index++) {
|
||||
dataPtr++;
|
||||
}
|
||||
byte = readByte();
|
||||
if (byte < 0) return -1;
|
||||
int reference_indices = (byte & 0x0C) >> 2; // R bits
|
||||
if (!skipBytes(reference_indices)) return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef VP9_CHECK_BOUNDS
|
||||
|
||||
if (remaining == 0) return -1;
|
||||
return dataPtr - data;
|
||||
}
|
||||
|
||||
@@ -239,7 +245,7 @@ bool VP9RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtp) {
|
||||
|
||||
RTPPayloadVP9 info;
|
||||
int offset = info.parse(payload, payload_size);
|
||||
if (offset < 0) {
|
||||
if (offset < 0 || static_cast<size_t>(offset) >= payload_size) {
|
||||
WarnL << "VP9 RTP payload parse failed, seq:" << seq;
|
||||
return false;
|
||||
}
|
||||
|
||||
98
tests/test_vp9_rtp.cpp
Normal file
98
tests/test_vp9_rtp.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ext-codec/VP9Rtp.h"
|
||||
|
||||
using namespace mediakit;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ParseCase {
|
||||
const char *name;
|
||||
std::vector<unsigned char> packet;
|
||||
int expected_offset;
|
||||
};
|
||||
|
||||
void require(bool condition, const std::string &message) {
|
||||
if (!condition) {
|
||||
std::cerr << message << std::endl;
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void runCase(const ParseCase &test) {
|
||||
VP9RtpDecoder decoder;
|
||||
std::vector<std::string> frames;
|
||||
decoder.addDelegate([&](const Frame::Ptr &frame) {
|
||||
frames.emplace_back(frame->data(), frame->size());
|
||||
return true;
|
||||
});
|
||||
|
||||
auto packet = test.packet;
|
||||
packet[0] |= 0x0C; // B/E: make a successful descriptor dispatch one complete frame.
|
||||
RtpInfo rtp_info(0x12345678, 1400, 90000, 98, 0, 0);
|
||||
auto rtp = rtp_info.makeRtp(TrackVideo, packet.data(), packet.size(), true, 100);
|
||||
require(rtp != nullptr, std::string(test.name) + ": failed to create RTP packet");
|
||||
decoder.inputRtp(rtp, false);
|
||||
|
||||
const bool should_output = test.expected_offset >= 0;
|
||||
require(frames.size() == (should_output ? 1U : 0U),
|
||||
std::string(test.name) + ": unexpected decoded frame count " + std::to_string(frames.size()));
|
||||
if (should_output) {
|
||||
require(frames[0].size() == packet.size() - static_cast<size_t>(test.expected_offset),
|
||||
std::string(test.name) + ": decoded frame has an unexpected size");
|
||||
require(frames[0] == std::string(reinterpret_cast<const char *>(packet.data() + test.expected_offset),
|
||||
packet.size() - static_cast<size_t>(test.expected_offset)),
|
||||
std::string(test.name) + ": decoded frame payload does not match RTP frame payload");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
// The last byte in every successful case is frame payload. These cases cover
|
||||
// all L/F/P combinations and make incorrect descriptor consumption visible.
|
||||
const std::vector<ParseCase> combinations = {
|
||||
{ "L0 F0 P0", { 0x00, 0xAA }, 1 },
|
||||
{ "L0 F0 P1", { 0x40, 0xAA }, 1 },
|
||||
{ "L0 F1 P0", { 0x10, 0xAA }, 1 },
|
||||
{ "L0 F1 P1", { 0x50, 0x02, 0xAA }, 2 },
|
||||
{ "L1 F0 P0", { 0x20, 0x00, 0x33, 0xAA }, 3 },
|
||||
{ "L1 F0 P1", { 0x60, 0x00, 0x33, 0xAA }, 3 },
|
||||
{ "L1 F1 P0 does not consume P_DIFF", { 0x30, 0x00, 0xAA }, 2 },
|
||||
{ "L1 F1 P1 consumes P_DIFF once", { 0x70, 0x00, 0x02, 0xAA }, 3 },
|
||||
};
|
||||
for (const auto &test : combinations) {
|
||||
runCase(test);
|
||||
}
|
||||
|
||||
runCase({ "three references", { 0x50, 0x03, 0x05, 0x06, 0xAA }, 4 });
|
||||
runCase({ "truncated reference chain", { 0x50, 0x03 }, -1 });
|
||||
runCase({ "more than three references", { 0x50, 0x03, 0x05, 0x07, 0x08, 0xAA }, -1 });
|
||||
runCase({ "short picture ID", { 0x80, 0x12, 0xAA }, 2 });
|
||||
runCase({ "large picture ID", { 0x80, 0x81, 0x23, 0xAA }, 3 });
|
||||
runCase({ "truncated large picture ID", { 0x80, 0x81 }, -1 });
|
||||
runCase({ "resolution", { 0x02, 0x10, 0x07, 0x80, 0x04, 0x38, 0xAA }, 6 });
|
||||
runCase({ "truncated resolution", { 0x02, 0x10, 0x07, 0x80, 0x04 }, -1 });
|
||||
|
||||
// V=1, G=1, N_G=2. Each GOF frame byte is followed immediately by
|
||||
// exactly the number of reference indices it declares.
|
||||
runCase({ "GOF exact boundary", { 0x02, 0x08, 0x02, 0x04, 0x11, 0x08, 0x22, 0x33, 0xAA }, 8 });
|
||||
runCase({ "GOF truncated references", { 0x02, 0x08, 0x01, 0x08, 0x22 }, -1 });
|
||||
|
||||
runCase({ "descriptor only", { 0x00 }, -1 });
|
||||
runCase({ "layer descriptor only", { 0x30, 0x00 }, -1 });
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user