vpxpes_parser: Store frame payloads.

libwebm_util:
- Add 90khz -> nanosecond conversion.

vpxpes_parser:
- Get rid of VpxPesParser::VpxFrame and use VideoFrame.
- Store/Accumulate (when neccessary) PES payloads in
  VideoFrames.
- Change type of size constants from int to size_t.
- Return offset accounting for origin from FindStartCode().
- Check all PTS marker bits (instead of checking the second
  marker twice).

video_frame:
- Add nanosecond_pts mutator.

webm2pes:
- Write DTS/PTS presence flag correctly when PTS is not
  present.

Change-Id: I10f16cd03bb3a51205a25331527ddceb3769ba03
This commit is contained in:
Tom Finegan 2016-07-22 11:09:26 -07:00
parent 25d26028c1
commit 6cf0a0f400
7 changed files with 150 additions and 58 deletions

View File

@ -13,11 +13,15 @@
namespace libwebm { namespace libwebm {
std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds) { std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds) {
const double kNanosecondsPerSecond = 1000000000.0;
const double pts_seconds = nanoseconds / kNanosecondsPerSecond; const double pts_seconds = nanoseconds / kNanosecondsPerSecond;
return static_cast<std::int64_t>(pts_seconds * 90000); return static_cast<std::int64_t>(pts_seconds * 90000);
} }
std::int64_t Khz90TicksToNanoseconds(std::int64_t ticks) {
const double seconds = ticks / 90000.0;
return static_cast<std::int64_t>(seconds * kNanosecondsPerSecond);
}
bool ParseVP9SuperFrameIndex(const std::uint8_t* frame, bool ParseVP9SuperFrameIndex(const std::uint8_t* frame,
std::size_t frame_length, Ranges* frame_ranges) { std::size_t frame_length, Ranges* frame_ranges) {
if (frame == nullptr || frame_length == 0 || frame_ranges == nullptr) if (frame == nullptr || frame_length == 0 || frame_ranges == nullptr)

View File

@ -16,6 +16,8 @@
namespace libwebm { namespace libwebm {
const double kNanosecondsPerSecond = 1000000000.0;
// fclose functor for wrapping FILE in std::unique_ptr. // fclose functor for wrapping FILE in std::unique_ptr.
// TODO(tomfinegan): Move this to file_util once c++11 restrictions are // TODO(tomfinegan): Move this to file_util once c++11 restrictions are
// relaxed. // relaxed.
@ -37,11 +39,12 @@ struct Range {
const std::size_t offset; const std::size_t offset;
const std::size_t length; const std::size_t length;
}; };
typedef std::vector<Range> Ranges; typedef std::vector<Range> Ranges;
// Converts |nanoseconds| to 90000 Hz clock ticks and returns the value. // Converts |nanoseconds| to 90000 Hz clock ticks and vice versa. Each return
// the converted value.
std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds); std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds);
std::int64_t Khz90TicksToNanoseconds(std::int64_t khz90_ticks);
// Returns true and stores frame offsets and lengths in |frame_ranges| when // Returns true and stores frame offsets and lengths in |frame_ranges| when
// |frame| has a valid VP9 super frame index. // |frame| has a valid VP9 super frame index.

View File

@ -53,6 +53,9 @@ class VideoFrame {
std::int64_t nanosecond_pts() const { return nanosecond_pts_; } std::int64_t nanosecond_pts() const { return nanosecond_pts_; }
Codec codec() const { return codec_; } Codec codec() const { return codec_; }
// Mutators.
void set_nanosecond_pts(std::int64_t nano_pts) { nanosecond_pts_ = nano_pts; }
private: private:
Buffer buffer_; Buffer buffer_;
bool keyframe_ = false; bool keyframe_ = false;

View File

@ -68,7 +68,7 @@ TEST_F(Webm2PesTests, CreatePesFile) { CreateAndLoadTestInput(); }
TEST_F(Webm2PesTests, CanParseFirstPacket) { TEST_F(Webm2PesTests, CanParseFirstPacket) {
CreateAndLoadTestInput(); CreateAndLoadTestInput();
libwebm::VpxPesParser::PesHeader header; libwebm::VpxPesParser::PesHeader header;
libwebm::VpxPesParser::VpxFrame frame; libwebm::VideoFrame frame;
ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame)); ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame));
EXPECT_TRUE(VerifyPacketStartCode(header)); EXPECT_TRUE(VerifyPacketStartCode(header));

View File

@ -9,11 +9,11 @@
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstring>
#include <limits> #include <limits>
#include <vector> #include <vector>
#include "common/file_util.h" #include "common/file_util.h"
#include "common/libwebm_util.h"
namespace libwebm { namespace libwebm {
@ -30,8 +30,8 @@ bool VpxPesParser::BcmvHeader::operator==(const BcmvHeader& other) const {
} }
bool VpxPesParser::BcmvHeader::Valid() const { bool VpxPesParser::BcmvHeader::Valid() const {
return (length > 0 && length <= std::numeric_limits<std::uint16_t>::max() && return (length > 0 && id[0] == 'B' && id[1] == 'C' && id[2] == 'M' &&
id[0] == 'B' && id[1] == 'C' && id[2] == 'M' && id[3] == 'V'); id[3] == 'V');
} }
// TODO(tomfinegan): Break Open() into separate functions. One that opens the // TODO(tomfinegan): Break Open() into separate functions. One that opens the
@ -154,18 +154,20 @@ bool VpxPesParser::ParsePesOptionalHeader(PesOptionalHeader* header) {
header->data_alignment = (pes_file_data_[offset] & 0xc) >> 2; header->data_alignment = (pes_file_data_[offset] & 0xc) >> 2;
header->copyright = (pes_file_data_[offset] & 0x2) >> 1; header->copyright = (pes_file_data_[offset] & 0x2) >> 1;
header->original = pes_file_data_[offset] & 0x1; header->original = pes_file_data_[offset] & 0x1;
offset++; offset++;
header->has_pts = (pes_file_data_[offset] & 0x80) >> 7; header->has_pts = (pes_file_data_[offset] & 0x80) >> 7;
header->has_dts = (pes_file_data_[offset] & 0x40) >> 6; header->has_dts = (pes_file_data_[offset] & 0x40) >> 6;
header->unused_fields = pes_file_data_[offset] & 0x3f; header->unused_fields = pes_file_data_[offset] & 0x3f;
offset++; offset++;
header->remaining_size = pes_file_data_[offset]; header->remaining_size = pes_file_data_[offset];
if (header->remaining_size != kWebm2PesOptHeaderRemainingSize) if (header->remaining_size !=
static_cast<int>(kWebm2PesOptHeaderRemainingSize))
return false; return false;
size_t bytes_left = header->remaining_size; size_t bytes_left = header->remaining_size;
offset++;
if (header->has_pts) { if (header->has_pts) {
// Read PTS markers. Format: // Read PTS markers. Format:
@ -179,14 +181,14 @@ bool VpxPesParser::ParsePesOptionalHeader(PesOptionalHeader* header) {
// bottom 15 bits // bottom 15 bits
// marker ('1') // marker ('1')
// TODO(tomfinegan): read/store the timestamp. // TODO(tomfinegan): read/store the timestamp.
offset++;
header->pts_dts_flag = (pes_file_data_[offset] & 0x20) >> 4; header->pts_dts_flag = (pes_file_data_[offset] & 0x20) >> 4;
// Check the marker bits. // Check the marker bits.
if ((pes_file_data_[offset] & 1) != 1 || if ((pes_file_data_[offset + 0] & 1) != 1 ||
(pes_file_data_[offset + 2] & 1) != 1 || (pes_file_data_[offset + 2] & 1) != 1 ||
(pes_file_data_[offset + 2] & 1) != 1) { (pes_file_data_[offset + 4] & 1) != 1) {
return false; return false;
} }
offset += 5; offset += 5;
bytes_left -= 5; bytes_left -= 5;
} }
@ -214,6 +216,7 @@ bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) {
header->id[2] = pes_file_data_[offset++]; header->id[2] = pes_file_data_[offset++];
header->id[3] = pes_file_data_[offset++]; header->id[3] = pes_file_data_[offset++];
header->length = 0;
header->length |= pes_file_data_[offset++] << 24; header->length |= pes_file_data_[offset++] << 24;
header->length |= pes_file_data_[offset++] << 16; header->length |= pes_file_data_[offset++] << 16;
header->length |= pes_file_data_[offset++] << 8; header->length |= pes_file_data_[offset++] << 8;
@ -226,13 +229,14 @@ bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) {
if (!header->Valid()) if (!header->Valid())
return false; return false;
// TODO(tomfinegan): Verify data instead of jumping to the next packet.
read_pos_ += header->length;
parse_state_ = kFindStartCode; parse_state_ = kFindStartCode;
read_pos_ += header->size();
return true; return true;
} }
bool VpxPesParser::FindStartCode(std::size_t origin, std::size_t* offset) { bool VpxPesParser::FindStartCode(std::size_t origin,
std::size_t* offset) const {
if (read_pos_ + 2 >= pes_file_size_) if (read_pos_ + 2 >= pes_file_size_)
return false; return false;
@ -243,8 +247,7 @@ bool VpxPesParser::FindStartCode(std::size_t origin, std::size_t* offset) {
const uint8_t* const data = &pes_file_data_[origin]; const uint8_t* const data = &pes_file_data_[origin];
for (std::size_t i = 0; i < length - 3; ++i) { for (std::size_t i = 0; i < length - 3; ++i) {
if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) {
*offset = i; *offset = origin + i;
parse_state_ = kParsePesHeader;
return true; return true;
} }
} }
@ -252,11 +255,53 @@ bool VpxPesParser::FindStartCode(std::size_t origin, std::size_t* offset) {
return false; return false;
} }
bool VpxPesParser::IsPayloadFragmented(const PesHeader& header) const {
return (header.packet_length != 0 &&
(header.packet_length - kPesOptionalHeaderSize) !=
header.bcmv_header.length);
}
bool VpxPesParser::AccumulateFragmentedPayload(std::size_t pes_packet_length,
std::size_t payload_length) {
PesHeader fragment_header;
const std::size_t first_fragment_length =
pes_packet_length - kPesOptionalHeaderSize - kBcmvHeaderSize;
for (std::size_t i = 0; i < first_fragment_length; ++i) {
payload_.push_back(pes_file_data_[read_pos_ + i]);
}
read_pos_ += first_fragment_length;
parse_state_ = kFindStartCode;
while (payload_.size() < payload_length) {
PesHeader header;
std::size_t packet_start_pos = read_pos_;
if (!FindStartCode(read_pos_, &packet_start_pos)) {
return false;
}
parse_state_ = kParsePesHeader;
read_pos_ = packet_start_pos;
if (!ParsePesHeader(&header)) {
return false;
}
if (!ParsePesOptionalHeader(&header.opt_header)) {
return false;
}
const std::size_t fragment_length =
header.packet_length - kPesOptionalHeaderSize;
for (std::size_t i = 0; i < fragment_length; ++i) {
payload_.push_back(pes_file_data_[read_pos_ + i]);
}
read_pos_ += fragment_length;
}
return true;
}
int VpxPesParser::BytesAvailable() const { int VpxPesParser::BytesAvailable() const {
return static_cast<int>(pes_file_data_.size() - read_pos_); return static_cast<int>(pes_file_data_.size() - read_pos_);
} }
bool VpxPesParser::ParseNextPacket(PesHeader* header, VpxFrame* frame) { bool VpxPesParser::ParseNextPacket(PesHeader* header, VideoFrame* frame) {
if (!header || !frame || parse_state_ != kFindStartCode) { if (!header || !frame || parse_state_ != kFindStartCode) {
return false; return false;
} }
@ -265,6 +310,7 @@ bool VpxPesParser::ParseNextPacket(PesHeader* header, VpxFrame* frame) {
if (!FindStartCode(read_pos_, &packet_start_pos)) { if (!FindStartCode(read_pos_, &packet_start_pos)) {
return false; return false;
} }
parse_state_ = kParsePesHeader;
read_pos_ = packet_start_pos; read_pos_ = packet_start_pos;
if (!ParsePesHeader(header)) { if (!ParsePesHeader(header)) {
@ -277,12 +323,46 @@ bool VpxPesParser::ParseNextPacket(PesHeader* header, VpxFrame* frame) {
return false; return false;
} }
// TODO(tomfinegan): Process data payload/store in frame. This requires // BCMV header length includes the length of the BCMVHeader itself. Adjust:
// changes to Webm2Pes: const std::size_t payload_length =
// 1. BCMV header must contain entire frame length, and pes header header->bcmv_header.length - BcmvHeader::size();
// payload size will be what's in the current packet-- parsing code needs
// needs to know how to reassemble frames. // Make sure there's enough input data to read the entire frame.
// 2. Random access bit must be set in Adaptation Field for key frames. if (read_pos_ + payload_length > pes_file_data_.size()) {
// Need more data.
printf("VpxPesParser: Not enough data. Required: %u Available: %u\n",
static_cast<unsigned int>(payload_length),
static_cast<unsigned int>(pes_file_data_.size() - read_pos_));
parse_state_ = kFindStartCode;
read_pos_ = packet_start_pos;
return false;
}
if (IsPayloadFragmented(*header)) {
if (!AccumulateFragmentedPayload(header->packet_length, payload_length)) {
fprintf(stderr, "VpxPesParser: Failed parsing fragmented payload!\n");
return false;
}
} else {
for (std::size_t i = 0; i < payload_length; ++i) {
payload_.push_back(pes_file_data_[read_pos_ + i]);
}
read_pos_ += payload_length;
}
if (frame->buffer().capacity < payload_.size()) {
if (frame->Init(payload_.size()) == false) {
fprintf(stderr, "VpxPesParser: Out of memory.\n");
return false;
}
}
frame->set_nanosecond_pts(Khz90TicksToNanoseconds(header->opt_header.pts));
std::memcpy(frame->buffer().data.get(), &payload_[0], payload_.size());
frame->SetBufferLength(payload_.size());
payload_.clear();
parse_state_ = kFindStartCode;
return true; return true;
} }

View File

@ -12,6 +12,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "common/libwebm_util.h"
#include "common/video_frame.h"
namespace libwebm { namespace libwebm {
// Parser for VPx PES. Requires that the _entire_ PES stream can be stored in // Parser for VPx PES. Requires that the _entire_ PES stream can be stored in
@ -29,22 +32,6 @@ class VpxPesParser {
kParseBcmvHeader, kParseBcmvHeader,
}; };
struct VpxFrame {
enum Codec {
VP8,
VP9,
};
Codec codec = VP9;
bool keyframe = false;
// Frame data.
std::vector<std::uint8_t> data;
// Raw PES PTS.
std::int64_t pts = 0;
};
struct PesOptionalHeader { struct PesOptionalHeader {
int marker = 0; int marker = 0;
int scrambling = 0; int scrambling = 0;
@ -73,7 +60,9 @@ class VpxPesParser {
bool operator==(const BcmvHeader& other) const; bool operator==(const BcmvHeader& other) const;
void Reset();
bool Valid() const; bool Valid() const;
static std::size_t size() { return 10; }
char id[4] = {0}; char id[4] = {0};
std::uint32_t length = 0; std::uint32_t length = 0;
@ -90,12 +79,12 @@ class VpxPesParser {
// Constants for validating known values from input data. // Constants for validating known values from input data.
const std::uint8_t kMinVideoStreamId = 0xE0; const std::uint8_t kMinVideoStreamId = 0xE0;
const std::uint8_t kMaxVideoStreamId = 0xEF; const std::uint8_t kMaxVideoStreamId = 0xEF;
const int kPesHeaderSize = 6; const std::size_t kPesHeaderSize = 6;
const int kPesOptionalHeaderStartOffset = kPesHeaderSize; const std::size_t kPesOptionalHeaderStartOffset = kPesHeaderSize;
const int kPesOptionalHeaderSize = 9; const std::size_t kPesOptionalHeaderSize = 9;
const int kPesOptionalHeaderMarkerValue = 0x2; const std::size_t kPesOptionalHeaderMarkerValue = 0x2;
const int kWebm2PesOptHeaderRemainingSize = 6; const std::size_t kWebm2PesOptHeaderRemainingSize = 6;
const int kBcmvHeaderSize = 10; const std::size_t kBcmvHeaderSize = 10;
VpxPesParser() = default; VpxPesParser() = default;
~VpxPesParser() = default; ~VpxPesParser() = default;
@ -106,8 +95,8 @@ class VpxPesParser {
// Parses the next packet in the PES. PES header information is stored in // Parses the next packet in the PES. PES header information is stored in
// |header|, and the frame payload is stored in |frame|. Returns true when // |header|, and the frame payload is stored in |frame|. Returns true when
// packet is parsed successfully. // a full frame has been consumed from the PES.
bool ParseNextPacket(PesHeader* header, VpxFrame* frame); bool ParseNextPacket(PesHeader* header, VideoFrame* frame);
// PES Header parsing utility functions. // PES Header parsing utility functions.
// PES Header structure: // PES Header structure:
@ -144,10 +133,21 @@ class VpxPesParser {
// Does not set |offset| value if the end of |pes_file_data_| is reached // Does not set |offset| value if the end of |pes_file_data_| is reached
// without locating a start code. // without locating a start code.
// Note: A start code is the byte sequence 0x00 0x00 0x01. // Note: A start code is the byte sequence 0x00 0x00 0x01.
bool FindStartCode(std::size_t origin, std::size_t* offset); bool FindStartCode(std::size_t origin, std::size_t* offset) const;
// Returns true when a PES packet containing a BCMV header contains only a
// portion of the frame payload length reported by the BCMV header.
bool IsPayloadFragmented(const PesHeader& header) const;
// Parses PES and PES Optional header while accumulating payload data in
// |payload_|.
// Returns true once all payload fragments have been stored in |payload_|.
// Returns false if unable to accumulate full payload.
bool AccumulateFragmentedPayload(std::size_t pes_packet_length,
std::size_t payload_length);
std::size_t pes_file_size_ = 0; std::size_t pes_file_size_ = 0;
PacketData packet_data_; PacketData payload_;
PesFileData pes_file_data_; PesFileData pes_file_data_;
std::size_t read_pos_ = 0; std::size_t read_pos_ = 0;
ParseState parse_state_ = kFindStartCode; ParseState parse_state_ = kFindStartCode;

View File

@ -7,6 +7,7 @@
// be found in the AUTHORS file in the root of the source tree. // be found in the AUTHORS file in the root of the source tree.
#include "m2ts/webm2pes.h" #include "m2ts/webm2pes.h"
#include <cassert>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <new> #include <new>
@ -102,10 +103,10 @@ bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const {
// Second byte of header, fields: has_pts, has_dts, unused fields. // Second byte of header, fields: has_pts, has_dts, unused fields.
*++byte = 0; *++byte = 0;
if (write_pts == true) { if (write_pts == true)
*byte |= has_pts.bits << has_pts.shift; *byte |= has_pts.bits << has_pts.shift;
*byte |= has_dts.bits << has_dts.shift;
} *byte |= has_dts.bits << has_dts.shift;
// Third byte of header, fields: remaining size of header. // Third byte of header, fields: remaining size of header.
*++byte = remaining_size.bits & 0xff; // Field is 8 bits wide. *++byte = remaining_size.bits & 0xff; // Field is 8 bits wide.
@ -459,7 +460,8 @@ bool Webm2Pes::WritePesPacket(const VideoFrame& frame,
} }
// First packet of new frame. Always include PTS and BCMV header. // First packet of new frame. Always include PTS and BCMV header.
header.packet_length = packet_payload_range.length + BCMVHeader::size(); header.packet_length =
packet_payload_range.length - extra_bytes + BCMVHeader::size();
if (header.Write(true, packet_data) != true) { if (header.Write(true, packet_data) != true) {
std::fprintf(stderr, "Webm2Pes: packet header write failed.\n"); std::fprintf(stderr, "Webm2Pes: packet header write failed.\n");
return false; return false;
@ -472,7 +474,7 @@ bool Webm2Pes::WritePesPacket(const VideoFrame& frame,
} }
// Insert the payload at the end of |packet_data|. // Insert the payload at the end of |packet_data|.
const std::uint8_t* payload_start = const std::uint8_t* const payload_start =
frame.buffer().data.get() + packet_payload_range.offset; frame.buffer().data.get() + packet_payload_range.offset;
const std::size_t bytes_to_copy = packet_payload_range.length - extra_bytes; const std::size_t bytes_to_copy = packet_payload_range.length - extra_bytes;
@ -498,8 +500,8 @@ bool Webm2Pes::WritePesPacket(const VideoFrame& frame,
return false; return false;
} }
payload_start += bytes_copied; const std::uint8_t* fragment_start = payload_start + bytes_copied;
if (CopyAndEscapeStartCodes(payload_start, extra_bytes_to_copy, if (CopyAndEscapeStartCodes(fragment_start, extra_bytes_to_copy,
packet_data) == false) { packet_data) == false) {
fprintf(stderr, "Webm2Pes: Payload write failed.\n"); fprintf(stderr, "Webm2Pes: Payload write failed.\n");
return false; return false;