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;
|
return 0;
|
||||||
}"
|
}"
|
||||||
HAVE_DELETED_MEMBER_FUNCTIONS)
|
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 ()
|
endif ()
|
||||||
|
|
||||||
if (NOT HAVE_UNIQUE_PTR
|
if (NOT HAVE_UNIQUE_PTR
|
||||||
OR NOT HAVE_DEFAULT_MEMBER_VALUES
|
OR NOT HAVE_DEFAULT_MEMBER_VALUES
|
||||||
OR NOT HAVE_DEFAULTED_MEMBER_FUNCTIONS
|
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_TESTS OFF)
|
||||||
set(ENABLE_WEBMTS OFF)
|
set(ENABLE_WEBMTS OFF)
|
||||||
message(WARNING "C++11 feature(s) not supported, tests and webmts disabled.")
|
message(WARNING "C++11 feature(s) not supported, tests and webmts disabled.")
|
||||||
@ -160,10 +183,12 @@ if (ENABLE_WEBMTS)
|
|||||||
add_library(webmts
|
add_library(webmts
|
||||||
"${LIBWEBM_SRC_DIR}/common/libwebm_util.cc"
|
"${LIBWEBM_SRC_DIR}/common/libwebm_util.cc"
|
||||||
"${LIBWEBM_SRC_DIR}/common/libwebm_util.h"
|
"${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.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.
|
# webm2pes section.
|
||||||
add_executable(webm2pes
|
add_executable(webm2pes
|
||||||
|
@ -18,72 +18,13 @@
|
|||||||
|
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/libwebm_util.h"
|
#include "common/libwebm_util.h"
|
||||||
|
#include "m2ts/vpxpes_parser.h"
|
||||||
#include "testing/test_util.h"
|
#include "testing/test_util.h"
|
||||||
|
|
||||||
namespace {
|
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 {
|
class Webm2PesTests : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
typedef std::vector<std::uint8_t> PesFileData;
|
|
||||||
|
|
||||||
enum ParseState {
|
|
||||||
kParsePesHeader,
|
|
||||||
kParsePesOptionalHeader,
|
|
||||||
kParseBcmvHeader,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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;
|
||||||
@ -94,192 +35,55 @@ class Webm2PesTests : public ::testing::Test {
|
|||||||
const int kWebm2PesOptHeaderRemainingSize = 6;
|
const int kWebm2PesOptHeaderRemainingSize = 6;
|
||||||
const int kBcmvHeaderSize = 10;
|
const int kBcmvHeaderSize = 10;
|
||||||
|
|
||||||
Webm2PesTests() : read_pos_(0), parse_state_(kParsePesHeader) {}
|
Webm2PesTests() = default;
|
||||||
|
~Webm2PesTests() = default;
|
||||||
|
|
||||||
void CreateAndLoadTestInput() {
|
void CreateAndLoadTestInput() {
|
||||||
libwebm::Webm2Pes converter(input_file_name_, temp_file_name_.name());
|
libwebm::Webm2Pes converter(input_file_name_, temp_file_name_.name());
|
||||||
ASSERT_TRUE(converter.ConvertToFile());
|
ASSERT_TRUE(converter.ConvertToFile());
|
||||||
pes_file_size_ = libwebm::GetFileSize(pes_file_name());
|
ASSERT_TRUE(parser_.Open(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VerifyPacketStartCode() {
|
bool VerifyPacketStartCode(const libwebm::VpxPesParser::PesHeader& header) {
|
||||||
EXPECT_LT(read_pos_ + 2, pes_file_data_.size());
|
|
||||||
|
|
||||||
// PES packets all start with the byte sequence 0x0 0x0 0x1.
|
// 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 ||
|
if (header.start_code[0] != 0 || header.start_code[1] != 0 ||
|
||||||
pes_file_data_[read_pos_ + 2] != 1) {
|
header.start_code[2] != 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
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(); }
|
const std::string& pes_file_name() const { return temp_file_name_.name(); }
|
||||||
std::uint64_t pes_file_size() const { return pes_file_size_; }
|
libwebm::VpxPesParser* parser() { return &parser_; }
|
||||||
const PesFileData& pes_file_data() const { return pes_file_data_; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const libwebm::TempFileDeleter temp_file_name_;
|
const libwebm::TempFileDeleter temp_file_name_;
|
||||||
const std::string input_file_name_ =
|
const std::string input_file_name_ =
|
||||||
libwebm::test::GetTestFilePath("bbb_480p_vp9_opus_1second.webm");
|
libwebm::test::GetTestFilePath("bbb_480p_vp9_opus_1second.webm");
|
||||||
std::int64_t pes_file_size_ = 0;
|
libwebm::VpxPesParser parser_;
|
||||||
PesFileData pes_file_data_;
|
|
||||||
std::size_t read_pos_;
|
|
||||||
ParseState parse_state_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(Webm2PesTests, CreatePesFile) { CreateAndLoadTestInput(); }
|
TEST_F(Webm2PesTests, CreatePesFile) { CreateAndLoadTestInput(); }
|
||||||
|
|
||||||
TEST_F(Webm2PesTests, CanParseFirstPacket) {
|
TEST_F(Webm2PesTests, CanParseFirstPacket) {
|
||||||
CreateAndLoadTestInput();
|
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 kPesOptionalHeaderLength = 9;
|
||||||
const std::size_t kFirstFrameLength = 83;
|
const std::size_t kFirstFrameLength = 83;
|
||||||
const std::size_t kPesPayloadLength =
|
const std::size_t kPesPayloadLength =
|
||||||
kPesOptionalHeaderLength + kFirstFrameLength;
|
kPesOptionalHeaderLength + kFirstFrameLength;
|
||||||
EXPECT_EQ(kPesPayloadLength, header.packet_length);
|
EXPECT_EQ(kPesPayloadLength, header.packet_length);
|
||||||
|
|
||||||
//
|
EXPECT_GE(header.stream_id, kMinVideoStreamId);
|
||||||
// Parse the PES optional header.
|
EXPECT_LE(header.stream_id, kMaxVideoStreamId);
|
||||||
//
|
|
||||||
ParsePesOptionalHeader(&header.opt_header);
|
|
||||||
|
|
||||||
|
// Test PesOptionalHeader values.
|
||||||
EXPECT_EQ(kPesOptionalHeaderMarkerValue, header.opt_header.marker);
|
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.scrambling);
|
||||||
EXPECT_EQ(0, header.opt_header.priority);
|
EXPECT_EQ(0, header.opt_header.priority);
|
||||||
EXPECT_EQ(0, header.opt_header.data_alignment);
|
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.has_dts);
|
||||||
EXPECT_EQ(0, header.opt_header.unused_fields);
|
EXPECT_EQ(0, header.opt_header.unused_fields);
|
||||||
|
|
||||||
//
|
// Test the BCMV header.
|
||||||
// Parse the BCMV Header
|
const libwebm::VpxPesParser::BcmvHeader kFirstBcmvHeader(kFirstFrameLength);
|
||||||
//
|
EXPECT_TRUE(header.bcmv_header.Valid());
|
||||||
ParseBcmvHeader(&header.bcmv_header);
|
EXPECT_EQ(kFirstBcmvHeader, header.bcmv_header);
|
||||||
EXPECT_EQ(kFirstFrameLength, header.bcmv_header.length);
|
|
||||||
|
|
||||||
// Parse the next PES header to confirm correct parsing and consumption of
|
// Parse the next packet to confirm correct parse and consumption of payload.
|
||||||
// payload.
|
EXPECT_TRUE(parser()->ParseNextPacket(&header, &frame));
|
||||||
ParsePesHeader(&header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // 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