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:
parent
25d26028c1
commit
6cf0a0f400
@ -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<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,
|
||||
std::size_t frame_length, Ranges* frame_ranges) {
|
||||
if (frame == nullptr || frame_length == 0 || frame_ranges == nullptr)
|
||||
|
@ -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<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 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.
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
||||
|
@ -9,11 +9,11 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#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<std::uint16_t>::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<int>(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<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) {
|
||||
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<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;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<std::uint8_t> 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;
|
||||
|
@ -7,6 +7,7 @@
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
#include "m2ts/webm2pes.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#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.
|
||||
*++byte = 0;
|
||||
if (write_pts == true) {
|
||||
if (write_pts == true)
|
||||
*byte |= has_pts.bits << has_pts.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;
|
||||
|
Loading…
Reference in New Issue
Block a user