webm2pes: Add start code emulation prevention.

- Make start codes reliable for VPx in PES.
- Stop setting the PES size field and stop splitting packets when
  larger than UINT16_MAX (always set 0; rely on start codes to find
  packet boundaries).

Change-Id: I402e91c26562e930f61543ca59223b83cc92be29
This commit is contained in:
Tom Finegan 2016-05-27 14:21:04 -07:00
parent 82903f36fa
commit 4b735452bb
5 changed files with 125 additions and 82 deletions

View File

@ -72,10 +72,7 @@ TEST_F(Webm2PesTests, CanParseFirstPacket) {
ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame));
EXPECT_TRUE(VerifyPacketStartCode(header));
const std::size_t kPesOptionalHeaderLength = 9;
const std::size_t kFirstFrameLength = 83;
const std::size_t kPesPayloadLength =
kPesOptionalHeaderLength + kFirstFrameLength;
const std::size_t kPesPayloadLength = 0;
EXPECT_EQ(kPesPayloadLength, header.packet_length);
EXPECT_GE(header.stream_id, kMinVideoStreamId);
@ -94,6 +91,7 @@ TEST_F(Webm2PesTests, CanParseFirstPacket) {
EXPECT_EQ(0, header.opt_header.unused_fields);
// Test the BCMV header.
const std::size_t kFirstFrameLength = 83;
const libwebm::VpxPesParser::BcmvHeader kFirstBcmvHeader(kFirstFrameLength);
EXPECT_TRUE(header.bcmv_header.Valid());
EXPECT_EQ(kFirstBcmvHeader, header.bcmv_header);

View File

@ -34,6 +34,9 @@ bool VpxPesParser::BcmvHeader::Valid() const {
id[0] == 'B' && id[1] == 'C' && id[2] == 'M' && id[3] == 'V');
}
// TODO(tomfinegan): Break Open() into separate functions. One that opens the
// file, and one that reads one packet at a time. As things are files larger
// than the maximum availble memory for the current process cannot be loaded.
bool VpxPesParser::Open(const std::string& pes_file) {
pes_file_size_ = static_cast<size_t>(libwebm::GetFileSize(pes_file));
if (pes_file_size_ <= 0)
@ -41,18 +44,38 @@ bool VpxPesParser::Open(const std::string& pes_file) {
pes_file_data_.reserve(static_cast<size_t>(pes_file_size_));
libwebm::FilePtr file = libwebm::FilePtr(std::fopen(pes_file.c_str(), "rb"),
libwebm::FILEDeleter());
int byte;
while ((byte = fgetc(file.get())) != EOF)
pes_file_data_.push_back(static_cast<std::uint8_t>(byte));
int zero_count = 0;
int bytes_skipped = 0;
bool skip_byte = false;
while ((byte = fgetc(file.get())) != EOF) {
if (zero_count >= 2) {
if (byte == 3) {
skip_byte = true;
zero_count = 0;
} else if (byte == 0) {
++zero_count;
} else {
zero_count = 0;
}
}
if (!skip_byte)
pes_file_data_.push_back(static_cast<std::uint8_t>(byte));
else
++bytes_skipped;
skip_byte = false;
}
if (!feof(file.get()) || ferror(file.get()) ||
pes_file_size_ != pes_file_data_.size()) {
pes_file_size_ != pes_file_data_.size() + bytes_skipped) {
return false;
}
read_pos_ = 0;
parse_state_ = kParsePesHeader;
parse_state_ = kFindStartCode;
return true;
}
@ -205,18 +228,45 @@ bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) {
// TODO(tomfinegan): Verify data instead of jumping to the next packet.
read_pos_ += kBcmvHeaderSize + header->length;
parse_state_ = kParsePesHeader;
parse_state_ = kFindStartCode;
return true;
}
bool VpxPesParser::FindStartCode(std::size_t origin, std::size_t* offset) {
if (read_pos_ + 2 >= pes_file_size_)
return false;
const std::size_t length = pes_file_size_ - origin;
if (length < 3)
return false;
const uint8_t* const data = &pes_file_data_[origin];
for (std::size_t i = 0; i < length - 3; ++i) {
if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) {
*offset = i;
parse_state_ = kParsePesHeader;
return true;
}
}
return false;
}
int VpxPesParser::BytesAvailable() const {
return static_cast<int>(pes_file_data_.size() - read_pos_);
}
bool VpxPesParser::ParseNextPacket(PesHeader* header, VpxFrame* frame) {
if (!header || !frame) {
if (!header || !frame || parse_state_ != kFindStartCode) {
return false;
}
std::size_t packet_start_pos = read_pos_;
if (!FindStartCode(read_pos_, &packet_start_pos)) {
return false;
}
read_pos_ = packet_start_pos;
if (!ParsePesHeader(header)) {
return false;
}

View File

@ -20,8 +20,10 @@ namespace libwebm {
class VpxPesParser {
public:
typedef std::vector<std::uint8_t> PesFileData;
typedef std::vector<std::uint8_t> PacketData;
enum ParseState {
kFindStartCode,
kParsePesHeader,
kParsePesOptionalHeader,
kParseBcmvHeader,
@ -137,10 +139,18 @@ class VpxPesParser {
// header.
bool ParseBcmvHeader(BcmvHeader* header);
// Returns true when a start code is found and sets |offset| to the position
// of the start code relative to |pes_file_data_[read_pos_]|.
// Does not set |offset| value if the end of |pes_file_data_| is reached
// without locating a start code.
// Note: A start code is the byte sequence 0x00 0x00 0x01.
bool FindStartCode(std::size_t origin, std::size_t* offset);
std::size_t pes_file_size_ = 0;
PacketData packet_data_;
PesFileData pes_file_data_;
std::size_t read_pos_ = 0;
ParseState parse_state_ = kParsePesHeader;
ParseState parse_state_ = kFindStartCode;
};
} // namespace libwebm

View File

@ -14,48 +14,6 @@
#include "common/libwebm_util.h"
namespace {
bool GetPacketPayloadRanges(const libwebm::PesHeader& header,
const libwebm::Ranges& frame_ranges,
libwebm::Ranges* packet_payload_ranges) {
// TODO(tomfinegan): The length field in PES is actually number of bytes that
// follow the length field, and does not include the 6 byte fixed portion of
// the header (4 byte start code + 2 bytes for the length). We can fit in 6
// more bytes if we really want to, and avoid packetization when size is very
// close to UINT16_MAX.
if (packet_payload_ranges == nullptr) {
std::fprintf(stderr, "Webm2Pes: nullptr getting payload ranges.\n");
return false;
}
const std::size_t kMaxPacketPayloadSize = UINT16_MAX - header.size();
for (const libwebm::Range& frame_range : frame_ranges) {
if (frame_range.length + header.size() > kMaxPacketPayloadSize) {
// make packet ranges until range.length is exhausted
const std::size_t kBytesToPacketize = frame_range.length;
std::size_t packet_payload_length = 0;
for (std::size_t pos = 0; pos < kBytesToPacketize;
pos += packet_payload_length) {
packet_payload_length =
(frame_range.length - pos < kMaxPacketPayloadSize) ?
frame_range.length - pos :
kMaxPacketPayloadSize;
packet_payload_ranges->push_back(
libwebm::Range(frame_range.offset + pos, packet_payload_length));
}
} else {
// copy range into |packet_ranges|
packet_payload_ranges->push_back(
libwebm::Range(frame_range.offset, frame_range.length));
}
}
return true;
}
} // namespace
namespace libwebm {
//
@ -106,7 +64,7 @@ void PesOptionalHeader::SetPtsBits(std::int64_t pts_90khz) {
std::memcpy(reinterpret_cast<std::uint8_t*>(pts_bits), buffer, 5);
}
// Writes fields to |file| and returns true. Returns false when write or
// Writes fields to |buffer| and returns true. Returns false when write or
// field value validation fails.
bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const {
if (buffer == nullptr) {
@ -166,10 +124,7 @@ bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const {
for (int i = 0; i < num_stuffing_bytes; ++i)
*++byte = stuffing_byte.bits & 0xff;
for (int i = 0; i < kHeaderSize; ++i)
buffer->push_back(header[i]);
return true;
return CopyAndEscapeStartCodes(&header[0], kHeaderSize, buffer);
}
//
@ -193,10 +148,8 @@ bool BCMVHeader::Write(PacketDataBuffer* buffer) const {
static_cast<std::uint8_t>(length & 0xff),
0,
0 /* 2 bytes 0 padding */};
for (int i = 0; i < kRemainingBytes; ++i)
buffer->push_back(bcmv_buffer[i]);
return true;
return CopyAndEscapeStartCodes(bcmv_buffer, kRemainingBytes, buffer);
}
//
@ -445,14 +398,6 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame,
frame_ranges.push_back(Range(0, vpx_frame.len));
}
PesHeader header;
Ranges packet_payload_ranges;
if (GetPacketPayloadRanges(header, frame_ranges, &packet_payload_ranges) !=
true) {
std::fprintf(stderr, "Webm2Pes: Error preparing packet payload ranges!\n");
return false;
}
///
/// TODO: DEBUG/REMOVE
///
@ -462,23 +407,17 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame,
static_cast<unsigned int>(frame_range.offset),
static_cast<unsigned int>(frame_range.length));
}
for (const Range& payload_range : packet_payload_ranges) {
printf("---payload range: off:%u len:%u\n",
static_cast<unsigned int>(payload_range.offset),
static_cast<unsigned int>(payload_range.length));
}
const std::int64_t khz90_pts =
NanosecondsTo90KhzTicks(static_cast<std::int64_t>(nanosecond_pts));
PesHeader header;
header.optional_header.SetPtsBits(khz90_pts);
packet_data_.clear();
bool write_pts = true;
for (const Range& packet_payload_range : packet_payload_ranges) {
header.packet_length =
(header.optional_header.size_in_bytes() + packet_payload_range.length) &
0xffff;
for (const Range& packet_payload_range : frame_ranges) {
header.packet_length = 0;
if (header.Write(write_pts, &packet_data_) != true) {
std::fprintf(stderr, "Webm2Pes: packet header write failed.\n");
return false;
@ -494,10 +433,41 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame,
// Insert the payload at the end of |packet_data_|.
const std::uint8_t* payload_start =
frame_data.get() + packet_payload_range.offset;
packet_data_.insert(packet_data_.end(), payload_start,
payload_start + packet_payload_range.length);
if (CopyAndEscapeStartCodes(payload_start,
static_cast<int>(packet_payload_range.length),
&packet_data_) == false) {
fprintf(stderr, "Webm2Pes: Payload write failed.\n");
return false;
}
}
return true;
}
bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input,
int raw_input_length,
PacketDataBuffer* packet_buffer) {
if (raw_input == nullptr || raw_input_length < 1 || packet_buffer == nullptr)
return false;
int num_zeros = 0;
for (int i = 0; i < raw_input_length; ++i) {
const uint8_t byte = raw_input[i];
if (byte == 0) {
++num_zeros;
} else if (num_zeros >= 2 && (byte == 0x1 || byte == 0x3)) {
packet_buffer->push_back(0x3);
num_zeros = 0;
} else {
num_zeros = 0;
}
packet_buffer->push_back(byte);
}
return true;
}
} // namespace libwebm

View File

@ -135,8 +135,10 @@ struct BCMVHeader {
static std::size_t size() { return 10; }
// Write the BCMV Header into |buffer|.
// Write the BCMV Header into |buffer|. Caller responsible for ensuring
// destination buffer is of size >= BCMVHeader::size().
bool Write(PacketDataBuffer* buffer) const;
bool Write(uint8_t* buffer);
};
struct PesHeader {
@ -219,6 +221,19 @@ class Webm2Pes {
PacketDataBuffer packet_data_;
};
// Copies |raw_input_length| bytes from |raw_input| to |packet_buffer| while
// escaping start codes. Returns true when bytes are successfully copied.
// A start code is the 3 byte sequence 0x00 0x00 0x01. When
// the sequence is encountered, the value 0x03 is inserted. To avoid
// any ambiguity at reassembly time, the same is done for the sequence
// 0x00 0x00 0x03. So, the following transformation occurs for when either
// of the noted sequences is encountered:
//
// 0x00 0x00 0x01 => 0x00 0x00 0x03 0x01
// 0x00 0x00 0x03 => 0x00 0x00 0x03 0x03
bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input,
int raw_input_length,
PacketDataBuffer* packet_buffer);
} // namespace libwebm
#endif // LIBWEBM_M2TS_WEBM2PES_H_