From 93c4690449a45a9914b4d69e2ea4be07f4e43584 Mon Sep 17 00:00:00 2001 From: Tom Finegan Date: Mon, 1 Feb 2016 14:49:19 -0800 Subject: [PATCH] webm2pes: Add PES packet parsing tests. Add test that parses the first packet output by Webm2Pes. Change-Id: I6d2282e0e87eed759abf2483994fc767f022f0ff --- testing/webm2pes_tests.cc | 239 +++++++++++++++++++++++++++++++++++++- 1 file changed, 236 insertions(+), 3 deletions(-) diff --git a/testing/webm2pes_tests.cc b/testing/webm2pes_tests.cc index 9eab570..02e2dba 100644 --- a/testing/webm2pes_tests.cc +++ b/testing/webm2pes_tests.cc @@ -18,10 +18,81 @@ #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::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 PesFileData; - Webm2PesTests() {} + + 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; + const std::size_t kPesHeaderSize = 6; + const std::size_t kPesOptionalHeaderStartOffset = kPesHeaderSize; + const std::size_t kPesOptionalHeaderSize = 9; + const int kPesOptionalHeaderMarkerValue = 0x2; + const std::size_t kWebm2PesOptHeaderRemainingSize = 6; + const std::size_t kBcmvHeaderSize = 10; + + Webm2PesTests() : read_pos_(0), parse_state_(kParsePesHeader) {} + bool CreateAndLoadTestInput() { libwebm::Webm2Pes converter(input_file_name_, temp_file_name_.name()); EXPECT_TRUE(converter.ConvertToFile()); @@ -33,8 +104,131 @@ class Webm2PesTests : public ::testing::Test { std::fopen(pes_file_name().c_str(), "rb"), libwebm::FILEDeleter()); EXPECT_EQ(std::fread(&pes_file_data_[0], 1, pes_file_size_, file.get()), pes_file_size_); + read_pos_ = 0; + parse_state_ = kParsePesHeader; return true; } + + bool VerifyPacketStartCode() { + // 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; + } + + std::uint8_t ReadStreamId() { return pes_file_data_[read_pos_ + 3]; } + + std::uint16_t ReadPacketLength() { + // 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_); + std::size_t 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(); } @@ -47,14 +241,53 @@ class Webm2PesTests : public ::testing::Test { libwebm::test::GetTestFilePath("bbb_480p_vp9_opus_1second.webm"); std::uint64_t pes_file_size_ = 0; PesFileData pes_file_data_; + std::size_t read_pos_; + ParseState parse_state_; }; TEST_F(Webm2PesTests, CreatePesFile) { - EXPECT_TRUE(CreateAndLoadTestInput()); + ASSERT_TRUE(CreateAndLoadTestInput()); } TEST_F(Webm2PesTests, CanParseFirstPacket) { - EXPECT_TRUE(CreateAndLoadTestInput()); + ASSERT_TRUE(CreateAndLoadTestInput()); + + // + // 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_EQ(kPesOptionalHeaderMarkerValue, header.opt_header.marker); + EXPECT_EQ(0, header.opt_header.scrambling); + EXPECT_EQ(0, header.opt_header.priority); + EXPECT_EQ(0, header.opt_header.data_alignment); + EXPECT_EQ(0, header.opt_header.copyright); + EXPECT_EQ(0, header.opt_header.original); + EXPECT_EQ(1, header.opt_header.has_pts); + 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); + + // Parse the next PES header to confirm correct parsing and consumption of + // payload. + ParsePesHeader(&header); } } // namespace