From 6cf0a0f400089511be190a0eaec73e7452ece7c6 Mon Sep 17 00:00:00 2001 From: Tom Finegan Date: Fri, 22 Jul 2016 11:09:26 -0700 Subject: [PATCH] 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 --- common/libwebm_util.cc | 6 +- common/libwebm_util.h | 7 +- common/video_frame.h | 3 + m2ts/tests/webm2pes_tests.cc | 2 +- m2ts/vpxpes_parser.cc | 122 +++++++++++++++++++++++++++++------ m2ts/vpxpes_parser.h | 52 +++++++-------- m2ts/webm2pes.cc | 16 +++-- 7 files changed, 150 insertions(+), 58 deletions(-) diff --git a/common/libwebm_util.cc b/common/libwebm_util.cc index ad00091..82b1934 100644 --- a/common/libwebm_util.cc +++ b/common/libwebm_util.cc @@ -13,11 +13,15 @@ namespace libwebm { std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds) { - const double kNanosecondsPerSecond = 1000000000.0; const double pts_seconds = nanoseconds / kNanosecondsPerSecond; return static_cast(pts_seconds * 90000); } +std::int64_t Khz90TicksToNanoseconds(std::int64_t ticks) { + const double seconds = ticks / 90000.0; + return static_cast(seconds * kNanosecondsPerSecond); +} + bool ParseVP9SuperFrameIndex(const std::uint8_t* frame, std::size_t frame_length, Ranges* frame_ranges) { if (frame == nullptr || frame_length == 0 || frame_ranges == nullptr) diff --git a/common/libwebm_util.h b/common/libwebm_util.h index 9594454..14df585 100644 --- a/common/libwebm_util.h +++ b/common/libwebm_util.h @@ -16,6 +16,8 @@ namespace libwebm { +const double kNanosecondsPerSecond = 1000000000.0; + // fclose functor for wrapping FILE in std::unique_ptr. // TODO(tomfinegan): Move this to file_util once c++11 restrictions are // relaxed. @@ -37,11 +39,12 @@ struct Range { const std::size_t offset; const std::size_t length; }; - typedef std::vector 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 Khz90TicksToNanoseconds(std::int64_t khz90_ticks); // Returns true and stores frame offsets and lengths in |frame_ranges| when // |frame| has a valid VP9 super frame index. diff --git a/common/video_frame.h b/common/video_frame.h index 35068f1..3031ef2 100644 --- a/common/video_frame.h +++ b/common/video_frame.h @@ -53,6 +53,9 @@ class VideoFrame { std::int64_t nanosecond_pts() const { return nanosecond_pts_; } Codec codec() const { return codec_; } + // Mutators. + void set_nanosecond_pts(std::int64_t nano_pts) { nanosecond_pts_ = nano_pts; } + private: Buffer buffer_; bool keyframe_ = false; diff --git a/m2ts/tests/webm2pes_tests.cc b/m2ts/tests/webm2pes_tests.cc index da97447..f852510 100644 --- a/m2ts/tests/webm2pes_tests.cc +++ b/m2ts/tests/webm2pes_tests.cc @@ -68,7 +68,7 @@ TEST_F(Webm2PesTests, CreatePesFile) { CreateAndLoadTestInput(); } TEST_F(Webm2PesTests, CanParseFirstPacket) { CreateAndLoadTestInput(); libwebm::VpxPesParser::PesHeader header; - libwebm::VpxPesParser::VpxFrame frame; + libwebm::VideoFrame frame; ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame)); EXPECT_TRUE(VerifyPacketStartCode(header)); diff --git a/m2ts/vpxpes_parser.cc b/m2ts/vpxpes_parser.cc index 8e8da15..cbc0ee7 100644 --- a/m2ts/vpxpes_parser.cc +++ b/m2ts/vpxpes_parser.cc @@ -9,11 +9,11 @@ #include #include +#include #include #include #include "common/file_util.h" -#include "common/libwebm_util.h" namespace libwebm { @@ -30,8 +30,8 @@ bool VpxPesParser::BcmvHeader::operator==(const BcmvHeader& other) const { } bool VpxPesParser::BcmvHeader::Valid() const { - return (length > 0 && length <= std::numeric_limits::max() && - id[0] == 'B' && id[1] == 'C' && id[2] == 'M' && id[3] == 'V'); + return (length > 0 && id[0] == 'B' && id[1] == 'C' && id[2] == 'M' && + id[3] == 'V'); } // 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->copyright = (pes_file_data_[offset] & 0x2) >> 1; header->original = pes_file_data_[offset] & 0x1; - offset++; + header->has_pts = (pes_file_data_[offset] & 0x80) >> 7; header->has_dts = (pes_file_data_[offset] & 0x40) >> 6; header->unused_fields = pes_file_data_[offset] & 0x3f; - offset++; + header->remaining_size = pes_file_data_[offset]; - if (header->remaining_size != kWebm2PesOptHeaderRemainingSize) + if (header->remaining_size != + static_cast(kWebm2PesOptHeaderRemainingSize)) return false; size_t bytes_left = header->remaining_size; + offset++; if (header->has_pts) { // Read PTS markers. Format: @@ -179,14 +181,14 @@ bool VpxPesParser::ParsePesOptionalHeader(PesOptionalHeader* header) { // bottom 15 bits // marker ('1') // TODO(tomfinegan): read/store the timestamp. - offset++; header->pts_dts_flag = (pes_file_data_[offset] & 0x20) >> 4; // 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 + 4] & 1) != 1) { return false; } + offset += 5; bytes_left -= 5; } @@ -214,6 +216,7 @@ bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) { header->id[2] = 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++] << 16; header->length |= pes_file_data_[offset++] << 8; @@ -226,13 +229,14 @@ bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) { if (!header->Valid()) return false; - // TODO(tomfinegan): Verify data instead of jumping to the next packet. - read_pos_ += header->length; parse_state_ = kFindStartCode; + read_pos_ += header->size(); + 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_) 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]; 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; + *offset = origin + i; return true; } } @@ -252,11 +255,53 @@ bool VpxPesParser::FindStartCode(std::size_t origin, std::size_t* offset) { 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 { return static_cast(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) { return false; } @@ -265,6 +310,7 @@ bool VpxPesParser::ParseNextPacket(PesHeader* header, VpxFrame* frame) { if (!FindStartCode(read_pos_, &packet_start_pos)) { return false; } + parse_state_ = kParsePesHeader; read_pos_ = packet_start_pos; if (!ParsePesHeader(header)) { @@ -277,12 +323,46 @@ bool VpxPesParser::ParseNextPacket(PesHeader* header, VpxFrame* frame) { return false; } - // TODO(tomfinegan): Process data payload/store in frame. This requires - // changes to Webm2Pes: - // 1. BCMV header must contain entire frame length, and pes header - // payload size will be what's in the current packet-- parsing code needs - // needs to know how to reassemble frames. - // 2. Random access bit must be set in Adaptation Field for key frames. + // BCMV header length includes the length of the BCMVHeader itself. Adjust: + const std::size_t payload_length = + header->bcmv_header.length - BcmvHeader::size(); + + // Make sure there's enough input data to read the entire frame. + if (read_pos_ + payload_length > pes_file_data_.size()) { + // Need more data. + printf("VpxPesParser: Not enough data. Required: %u Available: %u\n", + static_cast(payload_length), + static_cast(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; } diff --git a/m2ts/vpxpes_parser.h b/m2ts/vpxpes_parser.h index e5263ed..b149693 100644 --- a/m2ts/vpxpes_parser.h +++ b/m2ts/vpxpes_parser.h @@ -12,6 +12,9 @@ #include #include +#include "common/libwebm_util.h" +#include "common/video_frame.h" + namespace libwebm { // Parser for VPx PES. Requires that the _entire_ PES stream can be stored in @@ -29,22 +32,6 @@ class VpxPesParser { kParseBcmvHeader, }; - struct VpxFrame { - enum Codec { - VP8, - VP9, - }; - - Codec codec = VP9; - bool keyframe = false; - - // Frame data. - std::vector data; - - // Raw PES PTS. - std::int64_t pts = 0; - }; - struct PesOptionalHeader { int marker = 0; int scrambling = 0; @@ -73,7 +60,9 @@ class VpxPesParser { bool operator==(const BcmvHeader& other) const; + void Reset(); bool Valid() const; + static std::size_t size() { return 10; } char id[4] = {0}; std::uint32_t length = 0; @@ -90,12 +79,12 @@ class VpxPesParser { // Constants for validating known values from input data. const std::uint8_t kMinVideoStreamId = 0xE0; const std::uint8_t kMaxVideoStreamId = 0xEF; - const int kPesHeaderSize = 6; - const int kPesOptionalHeaderStartOffset = kPesHeaderSize; - const int kPesOptionalHeaderSize = 9; - const int kPesOptionalHeaderMarkerValue = 0x2; - const int kWebm2PesOptHeaderRemainingSize = 6; - const int kBcmvHeaderSize = 10; + const std::size_t kPesHeaderSize = 6; + const std::size_t kPesOptionalHeaderStartOffset = kPesHeaderSize; + const std::size_t kPesOptionalHeaderSize = 9; + const std::size_t kPesOptionalHeaderMarkerValue = 0x2; + const std::size_t kWebm2PesOptHeaderRemainingSize = 6; + const std::size_t kBcmvHeaderSize = 10; VpxPesParser() = default; ~VpxPesParser() = default; @@ -106,8 +95,8 @@ class VpxPesParser { // 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 - // packet is parsed successfully. - bool ParseNextPacket(PesHeader* header, VpxFrame* frame); + // a full frame has been consumed from the PES. + bool ParseNextPacket(PesHeader* header, VideoFrame* frame); // PES Header parsing utility functions. // PES Header structure: @@ -144,10 +133,21 @@ class VpxPesParser { // 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); + 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; - PacketData packet_data_; + PacketData payload_; PesFileData pes_file_data_; std::size_t read_pos_ = 0; ParseState parse_state_ = kFindStartCode; diff --git a/m2ts/webm2pes.cc b/m2ts/webm2pes.cc index c076791..8402ca8 100644 --- a/m2ts/webm2pes.cc +++ b/m2ts/webm2pes.cc @@ -7,6 +7,7 @@ // be found in the AUTHORS file in the root of the source tree. #include "m2ts/webm2pes.h" +#include #include #include #include @@ -102,10 +103,10 @@ bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const { // Second byte of header, fields: has_pts, has_dts, unused fields. *++byte = 0; - if (write_pts == true) { + if (write_pts == true) *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. *++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. - 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) { std::fprintf(stderr, "Webm2Pes: packet header write failed.\n"); return false; @@ -472,7 +474,7 @@ bool Webm2Pes::WritePesPacket(const VideoFrame& frame, } // 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; 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; } - payload_start += bytes_copied; - if (CopyAndEscapeStartCodes(payload_start, extra_bytes_to_copy, + const std::uint8_t* fragment_start = payload_start + bytes_copied; + if (CopyAndEscapeStartCodes(fragment_start, extra_bytes_to_copy, packet_data) == false) { fprintf(stderr, "Webm2Pes: Payload write failed.\n"); return false;