Extract PES parser from WebM2Pes tests.
Change-Id: I6c81255635a65337bdb701119a3814801864469d
This commit is contained in:
parent
16524e8e3b
commit
c5b76d8d11
@ -85,12 +85,35 @@ if (MSVC OR HAVE_CXX11)
|
||||
return 0;
|
||||
}"
|
||||
HAVE_DELETED_MEMBER_FUNCTIONS)
|
||||
|
||||
# auto&
|
||||
check_cxx_source_compiles("
|
||||
int main(int argc, const char* argv[]) {
|
||||
int a;
|
||||
auto& b = a;
|
||||
(void)b;
|
||||
return 0;
|
||||
}"
|
||||
HAVE_AUTO_REF)
|
||||
|
||||
# ranged for
|
||||
check_cxx_source_compiles("
|
||||
int main(int argc, const char* argv[]) {
|
||||
int a[4];
|
||||
for (int& b : a) {
|
||||
b = 0;
|
||||
}
|
||||
return 0;
|
||||
}"
|
||||
HAVE_RANGED_FOR)
|
||||
endif ()
|
||||
|
||||
if (NOT HAVE_UNIQUE_PTR
|
||||
OR NOT HAVE_DEFAULT_MEMBER_VALUES
|
||||
OR NOT HAVE_DEFAULTED_MEMBER_FUNCTIONS
|
||||
OR NOT HAVE_DELETED_MEMBER_FUNCTIONS)
|
||||
OR NOT HAVE_DELETED_MEMBER_FUNCTIONS
|
||||
OR NOT HAVE_AUTO_REF
|
||||
OR NOT HAVE_RANGED_FOR)
|
||||
set(ENABLE_TESTS OFF)
|
||||
set(ENABLE_WEBMTS OFF)
|
||||
message(WARNING "C++11 feature(s) not supported, tests and webmts disabled.")
|
||||
@ -160,10 +183,12 @@ if (ENABLE_WEBMTS)
|
||||
add_library(webmts
|
||||
"${LIBWEBM_SRC_DIR}/common/libwebm_util.cc"
|
||||
"${LIBWEBM_SRC_DIR}/common/libwebm_util.h"
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/webm2pes.cc"
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/webm2pes.h"
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts.cc"
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts.h")
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts.h"
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/vpxpes_parser.cc"
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/vpxpes_parser.h"
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/webm2pes.cc"
|
||||
"${LIBWEBM_SRC_DIR}/m2ts/webm2pes.h")
|
||||
|
||||
# webm2pes section.
|
||||
add_executable(webm2pes
|
||||
|
@ -18,72 +18,13 @@
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/libwebm_util.h"
|
||||
#include "m2ts/vpxpes_parser.h"
|
||||
#include "testing/test_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct PesOptionalHeader {
|
||||
int marker = 0;
|
||||
int scrambling = 0;
|
||||
int priority = 0;
|
||||
int data_alignment = 0;
|
||||
int copyright = 0;
|
||||
int original = 0;
|
||||
int has_pts = 0;
|
||||
int has_dts = 0;
|
||||
int unused_fields = 0;
|
||||
int remaining_size = 0;
|
||||
int pts_dts_flag = 0;
|
||||
std::uint64_t pts = 0;
|
||||
int stuffing_byte = 0;
|
||||
};
|
||||
|
||||
struct BcmvHeader {
|
||||
BcmvHeader() = default;
|
||||
~BcmvHeader() = default;
|
||||
BcmvHeader(const BcmvHeader&) = delete;
|
||||
BcmvHeader(BcmvHeader&&) = delete;
|
||||
|
||||
// Convenience ctor for quick validation of expected values via operator==
|
||||
// after parsing input.
|
||||
explicit BcmvHeader(std::uint32_t len) : length(len) {
|
||||
id[0] = 'B';
|
||||
id[1] = 'C';
|
||||
id[2] = 'M';
|
||||
id[3] = 'V';
|
||||
}
|
||||
|
||||
bool operator==(const BcmvHeader& other) const {
|
||||
return (other.length == length && other.id[0] == id[0] &&
|
||||
other.id[1] == id[1] && other.id[2] == id[2] &&
|
||||
other.id[3] == id[3]);
|
||||
}
|
||||
|
||||
bool 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');
|
||||
}
|
||||
|
||||
char id[4] = {0};
|
||||
std::uint32_t length = 0;
|
||||
};
|
||||
|
||||
struct PesHeader {
|
||||
std::uint16_t packet_length = 0;
|
||||
PesOptionalHeader opt_header;
|
||||
BcmvHeader bcmv_header;
|
||||
};
|
||||
|
||||
class Webm2PesTests : public ::testing::Test {
|
||||
public:
|
||||
typedef std::vector<std::uint8_t> PesFileData;
|
||||
|
||||
enum ParseState {
|
||||
kParsePesHeader,
|
||||
kParsePesOptionalHeader,
|
||||
kParseBcmvHeader,
|
||||
};
|
||||
|
||||
// Constants for validating known values from input data.
|
||||
const std::uint8_t kMinVideoStreamId = 0xE0;
|
||||
const std::uint8_t kMaxVideoStreamId = 0xEF;
|
||||
@ -94,192 +35,55 @@ class Webm2PesTests : public ::testing::Test {
|
||||
const int kWebm2PesOptHeaderRemainingSize = 6;
|
||||
const int kBcmvHeaderSize = 10;
|
||||
|
||||
Webm2PesTests() : read_pos_(0), parse_state_(kParsePesHeader) {}
|
||||
Webm2PesTests() = default;
|
||||
~Webm2PesTests() = default;
|
||||
|
||||
void CreateAndLoadTestInput() {
|
||||
libwebm::Webm2Pes converter(input_file_name_, temp_file_name_.name());
|
||||
ASSERT_TRUE(converter.ConvertToFile());
|
||||
pes_file_size_ = libwebm::GetFileSize(pes_file_name());
|
||||
ASSERT_GT(pes_file_size_, 0);
|
||||
pes_file_data_.reserve(pes_file_size_);
|
||||
libwebm::FilePtr file = libwebm::FilePtr(
|
||||
std::fopen(pes_file_name().c_str(), "rb"), libwebm::FILEDeleter());
|
||||
|
||||
int byte;
|
||||
while ((byte = fgetc(file.get())) != EOF)
|
||||
pes_file_data_.push_back(static_cast<std::uint8_t>(byte));
|
||||
|
||||
ASSERT_TRUE(feof(file.get()) && !ferror(file.get()));
|
||||
ASSERT_EQ(pes_file_size_, static_cast<std::int64_t>(pes_file_data_.size()));
|
||||
read_pos_ = 0;
|
||||
parse_state_ = kParsePesHeader;
|
||||
ASSERT_TRUE(parser_.Open(pes_file_name()));
|
||||
}
|
||||
|
||||
bool VerifyPacketStartCode() {
|
||||
EXPECT_LT(read_pos_ + 2, pes_file_data_.size());
|
||||
|
||||
bool VerifyPacketStartCode(const libwebm::VpxPesParser::PesHeader& header) {
|
||||
// PES packets all start with the byte sequence 0x0 0x0 0x1.
|
||||
if (pes_file_data_[read_pos_] != 0 || pes_file_data_[read_pos_ + 1] != 0 ||
|
||||
pes_file_data_[read_pos_ + 2] != 1) {
|
||||
if (header.start_code[0] != 0 || header.start_code[1] != 0 ||
|
||||
header.start_code[2] != 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint8_t ReadStreamId() {
|
||||
EXPECT_LT(read_pos_ + 3, pes_file_data_.size());
|
||||
return pes_file_data_[read_pos_ + 3]; }
|
||||
|
||||
std::uint16_t ReadPacketLength() {
|
||||
EXPECT_LT(read_pos_ + 5, pes_file_data_.size());
|
||||
|
||||
// Read and byte swap 16 bit big endian length.
|
||||
return (pes_file_data_[read_pos_ + 4] << 8) | pes_file_data_[read_pos_ + 5];
|
||||
}
|
||||
|
||||
void ParsePesHeader(PesHeader* header) {
|
||||
ASSERT_TRUE(header != nullptr);
|
||||
EXPECT_EQ(kParsePesHeader, parse_state_);
|
||||
EXPECT_TRUE(VerifyPacketStartCode());
|
||||
// PES Video stream IDs start at E0.
|
||||
EXPECT_GE(ReadStreamId(), kMinVideoStreamId);
|
||||
EXPECT_LE(ReadStreamId(), kMaxVideoStreamId);
|
||||
header->packet_length = ReadPacketLength();
|
||||
read_pos_ += kPesHeaderSize;
|
||||
parse_state_ = kParsePesOptionalHeader;
|
||||
}
|
||||
|
||||
// Parses a PES optional header.
|
||||
// https://en.wikipedia.org/wiki/Packetized_elementary_stream
|
||||
// TODO(tomfinegan): Make these masks constants.
|
||||
void ParsePesOptionalHeader(PesOptionalHeader* header) {
|
||||
ASSERT_TRUE(header != nullptr);
|
||||
EXPECT_EQ(kParsePesOptionalHeader, parse_state_);
|
||||
int offset = read_pos_;
|
||||
EXPECT_LT(offset, pes_file_size_);
|
||||
|
||||
header->marker = (pes_file_data_[offset] & 0x80) >> 6;
|
||||
header->scrambling = (pes_file_data_[offset] & 0x30) >> 4;
|
||||
header->priority = (pes_file_data_[offset] & 0x8) >> 3;
|
||||
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];
|
||||
EXPECT_EQ(kWebm2PesOptHeaderRemainingSize, header->remaining_size);
|
||||
|
||||
int bytes_left = header->remaining_size;
|
||||
|
||||
if (header->has_pts) {
|
||||
// Read PTS markers. Format:
|
||||
// PTS: 5 bytes
|
||||
// 4 bits (flag: PTS present, but no DTS): 0x2 ('0010')
|
||||
// 36 bits (90khz PTS):
|
||||
// top 3 bits
|
||||
// marker ('1')
|
||||
// middle 15 bits
|
||||
// marker ('1')
|
||||
// bottom 15 bits
|
||||
// marker ('1')
|
||||
// TODO(tomfinegan): Extract some golden timestamp values and actually
|
||||
// read the timestamp.
|
||||
offset++;
|
||||
header->pts_dts_flag = (pes_file_data_[offset] & 0x20) >> 4;
|
||||
// Check the marker bits.
|
||||
int marker_bit = pes_file_data_[offset] & 1;
|
||||
EXPECT_EQ(1, marker_bit);
|
||||
offset += 2;
|
||||
marker_bit = pes_file_data_[offset] & 1;
|
||||
EXPECT_EQ(1, marker_bit);
|
||||
offset += 2;
|
||||
marker_bit = pes_file_data_[offset] & 1;
|
||||
EXPECT_EQ(1, marker_bit);
|
||||
bytes_left -= 5;
|
||||
offset++;
|
||||
}
|
||||
|
||||
// Validate stuffing byte(s).
|
||||
for (int i = 0; i < bytes_left; ++i)
|
||||
EXPECT_EQ(0xff, pes_file_data_[offset + i]);
|
||||
|
||||
offset += bytes_left;
|
||||
EXPECT_EQ(kPesHeaderSize + kPesOptionalHeaderSize, offset);
|
||||
|
||||
read_pos_ += kPesOptionalHeaderSize;
|
||||
parse_state_ = kParseBcmvHeader;
|
||||
}
|
||||
|
||||
// Parses and validates a BCMV header.
|
||||
void ParseBcmvHeader(BcmvHeader* header) {
|
||||
ASSERT_TRUE(header != nullptr);
|
||||
EXPECT_EQ(kParseBcmvHeader, kParseBcmvHeader);
|
||||
std::size_t offset = read_pos_;
|
||||
header->id[0] = pes_file_data_[offset++];
|
||||
header->id[1] = pes_file_data_[offset++];
|
||||
header->id[2] = pes_file_data_[offset++];
|
||||
header->id[3] = pes_file_data_[offset++];
|
||||
|
||||
header->length |= pes_file_data_[offset++] << 24;
|
||||
header->length |= pes_file_data_[offset++] << 16;
|
||||
header->length |= pes_file_data_[offset++] << 8;
|
||||
header->length |= pes_file_data_[offset++];
|
||||
|
||||
// Length stored in the BCMV header is followed by 2 bytes of 0 padding.
|
||||
EXPECT_EQ(0, pes_file_data_[offset++]);
|
||||
EXPECT_EQ(0, pes_file_data_[offset++]);
|
||||
|
||||
EXPECT_TRUE(header->Valid());
|
||||
|
||||
// TODO(tomfinegan): Verify data instead of jumping to the next packet.
|
||||
read_pos_ += kBcmvHeaderSize + header->length;
|
||||
parse_state_ = kParsePesHeader;
|
||||
}
|
||||
|
||||
~Webm2PesTests() = default;
|
||||
|
||||
const std::string& pes_file_name() const { return temp_file_name_.name(); }
|
||||
std::uint64_t pes_file_size() const { return pes_file_size_; }
|
||||
const PesFileData& pes_file_data() const { return pes_file_data_; }
|
||||
libwebm::VpxPesParser* parser() { return &parser_; }
|
||||
|
||||
private:
|
||||
const libwebm::TempFileDeleter temp_file_name_;
|
||||
const std::string input_file_name_ =
|
||||
libwebm::test::GetTestFilePath("bbb_480p_vp9_opus_1second.webm");
|
||||
std::int64_t pes_file_size_ = 0;
|
||||
PesFileData pes_file_data_;
|
||||
std::size_t read_pos_;
|
||||
ParseState parse_state_;
|
||||
libwebm::VpxPesParser parser_;
|
||||
};
|
||||
|
||||
TEST_F(Webm2PesTests, CreatePesFile) { CreateAndLoadTestInput(); }
|
||||
|
||||
TEST_F(Webm2PesTests, CanParseFirstPacket) {
|
||||
CreateAndLoadTestInput();
|
||||
libwebm::VpxPesParser::PesHeader header;
|
||||
libwebm::VpxPesParser::VpxFrame frame;
|
||||
ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame));
|
||||
EXPECT_TRUE(VerifyPacketStartCode(header));
|
||||
|
||||
//
|
||||
// Parse the PES Header.
|
||||
// This is number of bytes following the final byte in the 6 byte static PES
|
||||
// header. PES optional header is 9 bytes. Payload is 83 bytes.
|
||||
PesHeader header;
|
||||
ParsePesHeader(&header);
|
||||
const std::size_t kPesOptionalHeaderLength = 9;
|
||||
const std::size_t kFirstFrameLength = 83;
|
||||
const std::size_t kPesPayloadLength =
|
||||
kPesOptionalHeaderLength + kFirstFrameLength;
|
||||
EXPECT_EQ(kPesPayloadLength, header.packet_length);
|
||||
|
||||
//
|
||||
// Parse the PES optional header.
|
||||
//
|
||||
ParsePesOptionalHeader(&header.opt_header);
|
||||
EXPECT_GE(header.stream_id, kMinVideoStreamId);
|
||||
EXPECT_LE(header.stream_id, kMaxVideoStreamId);
|
||||
|
||||
// Test PesOptionalHeader values.
|
||||
EXPECT_EQ(kPesOptionalHeaderMarkerValue, header.opt_header.marker);
|
||||
EXPECT_EQ(kWebm2PesOptHeaderRemainingSize, header.opt_header.remaining_size);
|
||||
EXPECT_EQ(0, header.opt_header.scrambling);
|
||||
EXPECT_EQ(0, header.opt_header.priority);
|
||||
EXPECT_EQ(0, header.opt_header.data_alignment);
|
||||
@ -289,15 +93,13 @@ TEST_F(Webm2PesTests, CanParseFirstPacket) {
|
||||
EXPECT_EQ(0, header.opt_header.has_dts);
|
||||
EXPECT_EQ(0, header.opt_header.unused_fields);
|
||||
|
||||
//
|
||||
// Parse the BCMV Header
|
||||
//
|
||||
ParseBcmvHeader(&header.bcmv_header);
|
||||
EXPECT_EQ(kFirstFrameLength, header.bcmv_header.length);
|
||||
// Test the BCMV header.
|
||||
const libwebm::VpxPesParser::BcmvHeader kFirstBcmvHeader(kFirstFrameLength);
|
||||
EXPECT_TRUE(header.bcmv_header.Valid());
|
||||
EXPECT_EQ(kFirstBcmvHeader, header.bcmv_header);
|
||||
|
||||
// Parse the next PES header to confirm correct parsing and consumption of
|
||||
// payload.
|
||||
ParsePesHeader(&header);
|
||||
// Parse the next packet to confirm correct parse and consumption of payload.
|
||||
EXPECT_TRUE(parser()->ParseNextPacket(&header, &frame));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
239
m2ts/vpxpes_parser.cc
Normal file
239
m2ts/vpxpes_parser.cc
Normal file
@ -0,0 +1,239 @@
|
||||
// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
#include "vpxpes_parser.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/libwebm_util.h"
|
||||
|
||||
namespace libwebm {
|
||||
|
||||
VpxPesParser::BcmvHeader::BcmvHeader(std::uint32_t len) : length(len) {
|
||||
id[0] = 'B';
|
||||
id[1] = 'C';
|
||||
id[2] = 'M';
|
||||
id[3] = 'V';
|
||||
}
|
||||
|
||||
bool VpxPesParser::BcmvHeader::operator==(const BcmvHeader& other) const {
|
||||
return (other.length == length && other.id[0] == id[0] &&
|
||||
other.id[1] == id[1] && other.id[2] == id[2] && other.id[3] == id[3]);
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
bool VpxPesParser::Open(const std::string& pes_file) {
|
||||
pes_file_size_ = libwebm::GetFileSize(pes_file);
|
||||
if (pes_file_size_ <= 0)
|
||||
return false;
|
||||
pes_file_data_.reserve(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));
|
||||
|
||||
if (!feof(file.get()) || ferror(file.get()) ||
|
||||
pes_file_size_ != static_cast<std::int64_t>(pes_file_data_.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
read_pos_ = 0;
|
||||
parse_state_ = kParsePesHeader;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::VerifyPacketStartCode() const {
|
||||
if (read_pos_ + 2 > pes_file_data_.size())
|
||||
return false;
|
||||
|
||||
// PES packets all start with the byte sequence 0x0 0x0 0x1.
|
||||
if (pes_file_data_[read_pos_] != 0 || pes_file_data_[read_pos_ + 1] != 0 ||
|
||||
pes_file_data_[read_pos_ + 2] != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::ReadStreamId(std::uint8_t* stream_id) const {
|
||||
if (!stream_id || BytesAvailable() < 4)
|
||||
return false;
|
||||
|
||||
*stream_id = pes_file_data_[read_pos_ + 3];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::ReadPacketLength(std::uint16_t* packet_length) const {
|
||||
if (!packet_length || BytesAvailable() < 6)
|
||||
return false;
|
||||
|
||||
// Read and byte swap 16 bit big endian length.
|
||||
*packet_length =
|
||||
(pes_file_data_[read_pos_ + 4] << 8) | pes_file_data_[read_pos_ + 5];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::ParsePesHeader(PesHeader* header) {
|
||||
if (!header || parse_state_ != kParsePesHeader)
|
||||
return false;
|
||||
|
||||
if (!VerifyPacketStartCode())
|
||||
return false;
|
||||
|
||||
int pos = read_pos_;
|
||||
for (auto& a : header->start_code) {
|
||||
a = pes_file_data_[pos++];
|
||||
}
|
||||
|
||||
// PES Video stream IDs start at E0.
|
||||
if (!ReadStreamId(&header->stream_id))
|
||||
return false;
|
||||
|
||||
if (header->stream_id < kMinVideoStreamId ||
|
||||
header->stream_id > kMaxVideoStreamId)
|
||||
return false;
|
||||
|
||||
if (!ReadPacketLength(&header->packet_length))
|
||||
return false;
|
||||
|
||||
read_pos_ += kPesHeaderSize;
|
||||
parse_state_ = kParsePesOptionalHeader;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(tomfinegan): Make these masks constants.
|
||||
bool VpxPesParser::ParsePesOptionalHeader(PesOptionalHeader* header) {
|
||||
if (!header || parse_state_ != kParsePesOptionalHeader)
|
||||
return false;
|
||||
|
||||
int offset = read_pos_;
|
||||
if (offset >= pes_file_size_)
|
||||
return false;
|
||||
|
||||
header->marker = (pes_file_data_[offset] & 0x80) >> 6;
|
||||
header->scrambling = (pes_file_data_[offset] & 0x30) >> 4;
|
||||
header->priority = (pes_file_data_[offset] & 0x8) >> 3;
|
||||
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)
|
||||
return false;
|
||||
|
||||
int bytes_left = header->remaining_size;
|
||||
|
||||
if (header->has_pts) {
|
||||
// Read PTS markers. Format:
|
||||
// PTS: 5 bytes
|
||||
// 4 bits (flag: PTS present, but no DTS): 0x2 ('0010')
|
||||
// 36 bits (90khz PTS):
|
||||
// top 3 bits
|
||||
// marker ('1')
|
||||
// middle 15 bits
|
||||
// marker ('1')
|
||||
// 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 ||
|
||||
(pes_file_data_[offset + 2] & 1) != 1 ||
|
||||
(pes_file_data_[offset + 2] & 1) != 1) {
|
||||
return false;
|
||||
}
|
||||
offset += 5;
|
||||
bytes_left -= 5;
|
||||
}
|
||||
|
||||
// Validate stuffing byte(s).
|
||||
for (int i = 0; i < bytes_left; ++i) {
|
||||
if (pes_file_data_[offset + i] != 0xff)
|
||||
return false;
|
||||
}
|
||||
|
||||
read_pos_ += kPesOptionalHeaderSize;
|
||||
parse_state_ = kParseBcmvHeader;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses and validates a BCMV header.
|
||||
bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) {
|
||||
if (!header || parse_state_ != kParseBcmvHeader)
|
||||
return false;
|
||||
|
||||
std::size_t offset = read_pos_;
|
||||
header->id[0] = pes_file_data_[offset++];
|
||||
header->id[1] = pes_file_data_[offset++];
|
||||
header->id[2] = pes_file_data_[offset++];
|
||||
header->id[3] = pes_file_data_[offset++];
|
||||
|
||||
header->length |= pes_file_data_[offset++] << 24;
|
||||
header->length |= pes_file_data_[offset++] << 16;
|
||||
header->length |= pes_file_data_[offset++] << 8;
|
||||
header->length |= pes_file_data_[offset++];
|
||||
|
||||
// Length stored in the BCMV header is followed by 2 bytes of 0 padding.
|
||||
if (pes_file_data_[offset++] != 0 || pes_file_data_[offset++] != 0)
|
||||
return false;
|
||||
|
||||
if (!header->Valid())
|
||||
return false;
|
||||
|
||||
// TODO(tomfinegan): Verify data instead of jumping to the next packet.
|
||||
read_pos_ += kBcmvHeaderSize + header->length;
|
||||
parse_state_ = kParsePesHeader;
|
||||
return true;
|
||||
}
|
||||
|
||||
int VpxPesParser::BytesAvailable() const {
|
||||
return pes_file_data_.size() - read_pos_;
|
||||
}
|
||||
|
||||
bool VpxPesParser::ParseNextPacket(PesHeader* header, VpxFrame* frame) {
|
||||
if (!header || !frame) {
|
||||
return false;
|
||||
}
|
||||
if (!ParsePesHeader(header)) {
|
||||
return false;
|
||||
}
|
||||
if (!ParsePesOptionalHeader(&header->opt_header)) {
|
||||
return false;
|
||||
}
|
||||
if (!ParseBcmvHeader(&header->bcmv_header)) {
|
||||
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.
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace libwebm
|
148
m2ts/vpxpes_parser.h
Normal file
148
m2ts/vpxpes_parser.h
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
#ifndef LIBWEBM_M2TS_VPXPES_PARSER_H_
|
||||
#define LIBWEBM_M2TS_VPXPES_PARSER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace libwebm {
|
||||
|
||||
// Parser for VPx PES. Requires that the _entire_ PES stream can be stored in
|
||||
// a std::vector<std::uint8_t> and read into memory when Open() is called.
|
||||
// TODO(tomfinegan): Support incremental parse.
|
||||
class VpxPesParser {
|
||||
public:
|
||||
typedef std::vector<std::uint8_t> PesFileData;
|
||||
|
||||
enum ParseState {
|
||||
kParsePesHeader,
|
||||
kParsePesOptionalHeader,
|
||||
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;
|
||||
int priority = 0;
|
||||
int data_alignment = 0;
|
||||
int copyright = 0;
|
||||
int original = 0;
|
||||
int has_pts = 0;
|
||||
int has_dts = 0;
|
||||
int unused_fields = 0;
|
||||
int remaining_size = 0;
|
||||
int pts_dts_flag = 0;
|
||||
std::uint64_t pts = 0;
|
||||
int stuffing_byte = 0;
|
||||
};
|
||||
|
||||
struct BcmvHeader {
|
||||
BcmvHeader() = default;
|
||||
~BcmvHeader() = default;
|
||||
BcmvHeader(const BcmvHeader&) = delete;
|
||||
BcmvHeader(BcmvHeader&&) = delete;
|
||||
|
||||
// Convenience ctor for quick validation of expected values via operator==
|
||||
// after parsing input.
|
||||
explicit BcmvHeader(std::uint32_t len);
|
||||
|
||||
bool operator==(const BcmvHeader& other) const;
|
||||
|
||||
bool Valid() const;
|
||||
|
||||
char id[4] = {0};
|
||||
std::uint32_t length = 0;
|
||||
};
|
||||
|
||||
struct PesHeader {
|
||||
std::uint8_t start_code[4] = {0};
|
||||
std::uint16_t packet_length = 0;
|
||||
std::uint8_t stream_id = 0;
|
||||
PesOptionalHeader opt_header;
|
||||
BcmvHeader bcmv_header;
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
VpxPesParser() = default;
|
||||
~VpxPesParser() = default;
|
||||
|
||||
// Opens file specified by |pes_file_path| and reads its contents. Returns
|
||||
// true after successful read of input file.
|
||||
bool Open(const std::string& pes_file_path);
|
||||
|
||||
// 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);
|
||||
|
||||
// PES Header parsing utility functions.
|
||||
// PES Header structure:
|
||||
// Start code Stream ID Packet length (16 bits)
|
||||
// / / ____/
|
||||
// | | /
|
||||
// Byte0 Byte1 Byte2 Byte3 Byte4 Byte5
|
||||
// 0 0 1 X Y
|
||||
bool VerifyPacketStartCode() const;
|
||||
bool ReadStreamId(std::uint8_t* stream_id) const;
|
||||
bool ReadPacketLength(std::uint16_t* packet_length) const;
|
||||
|
||||
std::uint64_t pes_file_size() const { return pes_file_size_; }
|
||||
const PesFileData& pes_file_data() const { return pes_file_data_; }
|
||||
|
||||
// Returns number of unparsed bytes remaining.
|
||||
int BytesAvailable() const;
|
||||
|
||||
private:
|
||||
// Parses and verifies the static 6 byte portion that begins every PES packet.
|
||||
bool ParsePesHeader(PesHeader* header);
|
||||
|
||||
// Parses a PES optional header, the optional header following the static
|
||||
// header that begins the VPX PES packet.
|
||||
// https://en.wikipedia.org/wiki/Packetized_elementary_stream
|
||||
bool ParsePesOptionalHeader(PesOptionalHeader* header);
|
||||
|
||||
// Parses and validates the BCMV header. This immediately follows the optional
|
||||
// header.
|
||||
bool ParseBcmvHeader(BcmvHeader* header);
|
||||
|
||||
std::int64_t pes_file_size_ = 0;
|
||||
PesFileData pes_file_data_;
|
||||
std::size_t read_pos_ = 0;
|
||||
ParseState parse_state_ = kParsePesHeader;
|
||||
};
|
||||
|
||||
} // namespace libwebm
|
||||
|
||||
#endif // LIBWEBM_M2TS_VPXPES_PARSER_H_
|
Loading…
x
Reference in New Issue
Block a user