webm2pes: Restore frame fragmentation support.

Cap packet payload at 32 kB. Update test to expect non-zero
packet length.

Change-Id: Ibb68a4ef8d32c049c492ae546c631ef6376e3ffd
This commit is contained in:
Tom Finegan 2016-07-18 10:37:23 -07:00
parent f8bb7149f5
commit 448af971d2
3 changed files with 94 additions and 21 deletions

View File

@ -72,7 +72,11 @@ TEST_F(Webm2PesTests, CanParseFirstPacket) {
ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame));
EXPECT_TRUE(VerifyPacketStartCode(header));
const std::size_t kPesPayloadLength = 0;
// 9 bytes: PES optional header
// 10 bytes: BCMV Header
// 83 bytes: frame
// 102 bytes total in packet length field:
const std::size_t kPesPayloadLength = 102;
EXPECT_EQ(kPesPayloadLength, header.packet_length);
EXPECT_GE(header.stream_id, kMinVideoStreamId);

View File

@ -172,10 +172,18 @@ bool PesHeader::Write(bool write_pts, PacketDataBuffer* buffer) const {
for (int i = 0; i < kStartCodeLength; ++i)
buffer->push_back(start_code[i]);
// Write |packet_length| as big endian.
std::uint8_t byte = (packet_length >> 8) & 0xff;
// The length field here reports number of bytes following the field. The
// length of the optional header must be added to the payload length set by
// the user.
const std::size_t header_length =
packet_length + optional_header.size_in_bytes();
if (header_length > UINT16_MAX)
return false;
// Write |header_length| as big endian.
std::uint8_t byte = (header_length >> 8) & 0xff;
buffer->push_back(byte);
byte = packet_length & 0xff;
byte = header_length & 0xff;
buffer->push_back(byte);
// Write the (not really) optional header.
@ -341,9 +349,6 @@ bool Webm2Pes::InitWebmParser() {
return false;
}
// Store timecode scale.
timecode_scale_ = webm_parser_->GetInfo()->GetTimeCodeScale();
// Make sure there's a video track.
const mkvparser::Tracks* tracks = webm_parser_->GetTracks();
if (tracks == nullptr) {
@ -351,6 +356,9 @@ bool Webm2Pes::InitWebmParser() {
input_file_name_.c_str());
return false;
}
timecode_scale_ = webm_parser_->GetInfo()->GetTimeCodeScale();
for (int track_index = 0;
track_index < static_cast<int>(tracks->GetTracksCount());
++track_index) {
@ -418,14 +426,18 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame,
packet_data_.clear();
bool write_pts = true;
for (const Range& packet_payload_range : frame_ranges) {
header.packet_length = 0;
if (header.Write(write_pts, &packet_data_) != true) {
std::size_t extra_bytes = 0;
if (packet_payload_range.length > kMaxPayloadSize) {
extra_bytes = packet_payload_range.length - kMaxPayloadSize;
}
// First packet of new frame. Always include PTS and BCMV header.
header.packet_length = packet_payload_range.length + BCMVHeader::size();
if (header.Write(true, &packet_data_) != true) {
std::fprintf(stderr, "Webm2Pes: packet header write failed.\n");
return false;
}
write_pts = false;
BCMVHeader bcmv_header(static_cast<uint32_t>(packet_payload_range.length));
if (bcmv_header.Write(&packet_data_) != true) {
@ -437,25 +449,56 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame,
const std::uint8_t* payload_start =
frame_data.get() + packet_payload_range.offset;
if (CopyAndEscapeStartCodes(payload_start,
static_cast<int>(packet_payload_range.length),
&packet_data_) == false) {
const std::size_t bytes_to_copy = packet_payload_range.length - extra_bytes;
if (CopyAndEscapeStartCodes(payload_start, bytes_to_copy, &packet_data_) ==
false) {
fprintf(stderr, "Webm2Pes: Payload write failed.\n");
return false;
}
printf("---wrote payload size=%u\n",
static_cast<unsigned int>(bytes_to_copy));
std::size_t bytes_copied = bytes_to_copy;
while (extra_bytes) {
// Write PES packets for the remaining data, but omit the PTS and BCMV
// header.
const std::size_t extra_bytes_to_copy =
std::min(kMaxPayloadSize, extra_bytes);
extra_bytes -= extra_bytes_to_copy;
header.packet_length = extra_bytes_to_copy;
if (header.Write(false, &packet_data_) != true) {
fprintf(stderr, "Webm2pes: fragment write failed.\n");
return false;
}
payload_start += bytes_copied;
if (CopyAndEscapeStartCodes(payload_start, extra_bytes_to_copy,
&packet_data_) == false) {
fprintf(stderr, "Webm2Pes: Payload write failed.\n");
return false;
}
bytes_copied += extra_bytes_to_copy;
// TODO: DEBUG/REMOVE
printf("----wrote fragment total=%u this_fragment=%u\n",
static_cast<unsigned int>(bytes_copied),
static_cast<unsigned int>(extra_bytes_to_copy));
}
}
return true;
}
bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input,
int raw_input_length,
std::size_t 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) {
for (std::size_t i = 0; i < raw_input_length; ++i) {
const uint8_t byte = raw_input[i];
if (byte == 0) {

View File

@ -18,6 +18,30 @@
#include "mkvparser/mkvparser.h"
#include "mkvparser/mkvreader.h"
// Webm2pes
//
// Webm2pes consumes a WebM file containing a VP8 or VP9 video stream and
// outputs a PES stream suitable for inclusion in a MPEG2 Transport Stream.
//
// In the simplest case the PES stream output by Webm2pes consists of a sequence
// of PES packets with the following structure:
// | PES Header w/PTS | BCMV Header | Payload (VPx frame) |
//
// More typically the output will look like the following due to the PES
// payload size limitations caused by the format of the PES header.
// The PES header contains only 2 bytes of storage for expressing payload size.
// VPx PES streams containing fragmented packets look like this:
//
// | PH PTS | BCMV | Payload fragment 1 | PH | Payload fragment 2 | ...
//
// PH = PES Header
// PH PTS = PES Header with PTS
// BCMV = BCMV Header
//
// Note that start codes are properly escaped by Webm2pes, and start code
// emulation prevention bytes must be stripped from the output stream before
// it can be parsed.
namespace libwebm {
// Stores a value and its size in bits for writing into a PES Optional Header.
@ -149,7 +173,7 @@ struct PesHeader {
std::uint16_t packet_length = 0; // Number of bytes _after_ this field.
PesOptionalHeader optional_header;
std::size_t size() const {
return optional_header.size_in_bytes() + BCMVHeader::size() +
return optional_header.size_in_bytes() +
6 /* start_code + packet_length */ + packet_length;
}
@ -172,6 +196,7 @@ class PacketReceiverInterface {
class Webm2Pes {
public:
enum VideoCodec { VP8, VP9 };
const std::size_t kMaxPayloadSize = 32768;
Webm2Pes(const std::string& input_file, const std::string& output_file)
: input_file_name_(input_file), output_file_name_(output_file) {}
@ -188,9 +213,10 @@ class Webm2Pes {
bool ConvertToFile();
// Converts the VPx video stream to a sequence of PES packets, and calls the
// PacketReceiverInterface::ReceivePacket() once for each PES packet. Returns
// only after full conversion or error. Returns true for success, and false
// when an error occurs.
// PacketReceiverInterface::ReceivePacket() once for each VPx frame. The
// packet sent to the receiver may contain multiple PES packets. Returns only
// after full conversion or error. Returns true for success, and false when
// an error occurs.
bool ConvertToPacketReceiver();
private:
@ -232,7 +258,7 @@ class Webm2Pes {
// 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,
std::size_t raw_input_length,
PacketDataBuffer* packet_buffer);
} // namespace libwebm