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:
parent
82903f36fa
commit
4b735452bb
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
108
m2ts/webm2pes.cc
108
m2ts/webm2pes.cc
@ -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
|
||||
|
@ -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_
|
||||
|
Loading…
x
Reference in New Issue
Block a user