diff --git a/m2ts/tests/webm2pes_tests.cc b/m2ts/tests/webm2pes_tests.cc index 8949460..37fb01b 100644 --- a/m2ts/tests/webm2pes_tests.cc +++ b/m2ts/tests/webm2pes_tests.cc @@ -72,10 +72,7 @@ TEST_F(Webm2PesTests, CanParseFirstPacket) { ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame)); EXPECT_TRUE(VerifyPacketStartCode(header)); - const std::size_t kPesOptionalHeaderLength = 9; - const std::size_t kFirstFrameLength = 83; - const std::size_t kPesPayloadLength = - kPesOptionalHeaderLength + kFirstFrameLength; + const std::size_t kPesPayloadLength = 0; EXPECT_EQ(kPesPayloadLength, header.packet_length); EXPECT_GE(header.stream_id, kMinVideoStreamId); @@ -94,6 +91,7 @@ TEST_F(Webm2PesTests, CanParseFirstPacket) { EXPECT_EQ(0, header.opt_header.unused_fields); // Test the BCMV header. + const std::size_t kFirstFrameLength = 83; const libwebm::VpxPesParser::BcmvHeader kFirstBcmvHeader(kFirstFrameLength); EXPECT_TRUE(header.bcmv_header.Valid()); EXPECT_EQ(kFirstBcmvHeader, header.bcmv_header); diff --git a/m2ts/vpxpes_parser.cc b/m2ts/vpxpes_parser.cc index 764558d..853f6df 100644 --- a/m2ts/vpxpes_parser.cc +++ b/m2ts/vpxpes_parser.cc @@ -34,6 +34,9 @@ bool VpxPesParser::BcmvHeader::Valid() const { id[0] == 'B' && id[1] == 'C' && id[2] == 'M' && id[3] == 'V'); } +// TODO(tomfinegan): Break Open() into separate functions. One that opens the +// file, and one that reads one packet at a time. As things are files larger +// than the maximum availble memory for the current process cannot be loaded. bool VpxPesParser::Open(const std::string& pes_file) { pes_file_size_ = static_cast(libwebm::GetFileSize(pes_file)); if (pes_file_size_ <= 0) @@ -41,18 +44,38 @@ bool VpxPesParser::Open(const std::string& pes_file) { pes_file_data_.reserve(static_cast(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(byte)); + int zero_count = 0; + int bytes_skipped = 0; + bool skip_byte = false; + + while ((byte = fgetc(file.get())) != EOF) { + if (zero_count >= 2) { + if (byte == 3) { + skip_byte = true; + zero_count = 0; + } else if (byte == 0) { + ++zero_count; + } else { + zero_count = 0; + } + } + + if (!skip_byte) + pes_file_data_.push_back(static_cast(byte)); + else + ++bytes_skipped; + + skip_byte = false; + } if (!feof(file.get()) || ferror(file.get()) || - pes_file_size_ != pes_file_data_.size()) { + pes_file_size_ != pes_file_data_.size() + bytes_skipped) { return false; } read_pos_ = 0; - parse_state_ = kParsePesHeader; + parse_state_ = kFindStartCode; return true; } @@ -205,18 +228,45 @@ bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) { // TODO(tomfinegan): Verify data instead of jumping to the next packet. read_pos_ += kBcmvHeaderSize + header->length; - parse_state_ = kParsePesHeader; + parse_state_ = kFindStartCode; return true; } +bool VpxPesParser::FindStartCode(std::size_t origin, std::size_t* offset) { + if (read_pos_ + 2 >= pes_file_size_) + return false; + + const std::size_t length = pes_file_size_ - origin; + if (length < 3) + return false; + + 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; + return true; + } + } + + return false; +} + int VpxPesParser::BytesAvailable() const { return static_cast(pes_file_data_.size() - read_pos_); } bool VpxPesParser::ParseNextPacket(PesHeader* header, VpxFrame* frame) { - if (!header || !frame) { + if (!header || !frame || parse_state_ != kFindStartCode) { return false; } + + std::size_t packet_start_pos = read_pos_; + if (!FindStartCode(read_pos_, &packet_start_pos)) { + return false; + } + read_pos_ = packet_start_pos; + if (!ParsePesHeader(header)) { return false; } diff --git a/m2ts/vpxpes_parser.h b/m2ts/vpxpes_parser.h index 35bfe38..e5263ed 100644 --- a/m2ts/vpxpes_parser.h +++ b/m2ts/vpxpes_parser.h @@ -20,8 +20,10 @@ namespace libwebm { class VpxPesParser { public: typedef std::vector PesFileData; + typedef std::vector PacketData; enum ParseState { + kFindStartCode, kParsePesHeader, kParsePesOptionalHeader, kParseBcmvHeader, @@ -137,10 +139,18 @@ class VpxPesParser { // header. bool ParseBcmvHeader(BcmvHeader* header); + // Returns true when a start code is found and sets |offset| to the position + // of the start code relative to |pes_file_data_[read_pos_]|. + // 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); + std::size_t pes_file_size_ = 0; + PacketData packet_data_; PesFileData pes_file_data_; std::size_t read_pos_ = 0; - ParseState parse_state_ = kParsePesHeader; + ParseState parse_state_ = kFindStartCode; }; } // namespace libwebm diff --git a/m2ts/webm2pes.cc b/m2ts/webm2pes.cc index ca1935b..02d1838 100644 --- a/m2ts/webm2pes.cc +++ b/m2ts/webm2pes.cc @@ -14,48 +14,6 @@ #include "common/libwebm_util.h" -namespace { - -bool GetPacketPayloadRanges(const libwebm::PesHeader& header, - const libwebm::Ranges& frame_ranges, - libwebm::Ranges* packet_payload_ranges) { - // TODO(tomfinegan): The length field in PES is actually number of bytes that - // follow the length field, and does not include the 6 byte fixed portion of - // the header (4 byte start code + 2 bytes for the length). We can fit in 6 - // more bytes if we really want to, and avoid packetization when size is very - // close to UINT16_MAX. - if (packet_payload_ranges == nullptr) { - std::fprintf(stderr, "Webm2Pes: nullptr getting payload ranges.\n"); - return false; - } - - const std::size_t kMaxPacketPayloadSize = UINT16_MAX - header.size(); - - for (const libwebm::Range& frame_range : frame_ranges) { - if (frame_range.length + header.size() > kMaxPacketPayloadSize) { - // make packet ranges until range.length is exhausted - const std::size_t kBytesToPacketize = frame_range.length; - std::size_t packet_payload_length = 0; - for (std::size_t pos = 0; pos < kBytesToPacketize; - pos += packet_payload_length) { - packet_payload_length = - (frame_range.length - pos < kMaxPacketPayloadSize) ? - frame_range.length - pos : - kMaxPacketPayloadSize; - packet_payload_ranges->push_back( - libwebm::Range(frame_range.offset + pos, packet_payload_length)); - } - } else { - // copy range into |packet_ranges| - packet_payload_ranges->push_back( - libwebm::Range(frame_range.offset, frame_range.length)); - } - } - return true; -} - -} // namespace - namespace libwebm { // @@ -106,7 +64,7 @@ void PesOptionalHeader::SetPtsBits(std::int64_t pts_90khz) { std::memcpy(reinterpret_cast(pts_bits), buffer, 5); } -// Writes fields to |file| and returns true. Returns false when write or +// Writes fields to |buffer| and returns true. Returns false when write or // field value validation fails. bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const { if (buffer == nullptr) { @@ -166,10 +124,7 @@ bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const { for (int i = 0; i < num_stuffing_bytes; ++i) *++byte = stuffing_byte.bits & 0xff; - for (int i = 0; i < kHeaderSize; ++i) - buffer->push_back(header[i]); - - return true; + return CopyAndEscapeStartCodes(&header[0], kHeaderSize, buffer); } // @@ -193,10 +148,8 @@ bool BCMVHeader::Write(PacketDataBuffer* buffer) const { static_cast(length & 0xff), 0, 0 /* 2 bytes 0 padding */}; - for (int i = 0; i < kRemainingBytes; ++i) - buffer->push_back(bcmv_buffer[i]); - return true; + return CopyAndEscapeStartCodes(bcmv_buffer, kRemainingBytes, buffer); } // @@ -445,14 +398,6 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame, frame_ranges.push_back(Range(0, vpx_frame.len)); } - PesHeader header; - Ranges packet_payload_ranges; - if (GetPacketPayloadRanges(header, frame_ranges, &packet_payload_ranges) != - true) { - std::fprintf(stderr, "Webm2Pes: Error preparing packet payload ranges!\n"); - return false; - } - /// /// TODO: DEBUG/REMOVE /// @@ -462,23 +407,17 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame, static_cast(frame_range.offset), static_cast(frame_range.length)); } - for (const Range& payload_range : packet_payload_ranges) { - printf("---payload range: off:%u len:%u\n", - static_cast(payload_range.offset), - static_cast(payload_range.length)); - } const std::int64_t khz90_pts = NanosecondsTo90KhzTicks(static_cast(nanosecond_pts)); + PesHeader header; header.optional_header.SetPtsBits(khz90_pts); packet_data_.clear(); bool write_pts = true; - for (const Range& packet_payload_range : packet_payload_ranges) { - header.packet_length = - (header.optional_header.size_in_bytes() + packet_payload_range.length) & - 0xffff; + for (const Range& packet_payload_range : frame_ranges) { + header.packet_length = 0; if (header.Write(write_pts, &packet_data_) != true) { std::fprintf(stderr, "Webm2Pes: packet header write failed.\n"); return false; @@ -494,10 +433,41 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame, // Insert the payload at the end of |packet_data_|. const std::uint8_t* payload_start = frame_data.get() + packet_payload_range.offset; - packet_data_.insert(packet_data_.end(), payload_start, - payload_start + packet_payload_range.length); + + if (CopyAndEscapeStartCodes(payload_start, + static_cast(packet_payload_range.length), + &packet_data_) == false) { + fprintf(stderr, "Webm2Pes: Payload write failed.\n"); + return false; + } } return true; } + +bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input, + int raw_input_length, + PacketDataBuffer* packet_buffer) { + if (raw_input == nullptr || raw_input_length < 1 || packet_buffer == nullptr) + return false; + + int num_zeros = 0; + for (int i = 0; i < raw_input_length; ++i) { + const uint8_t byte = raw_input[i]; + + if (byte == 0) { + ++num_zeros; + } else if (num_zeros >= 2 && (byte == 0x1 || byte == 0x3)) { + packet_buffer->push_back(0x3); + num_zeros = 0; + } else { + num_zeros = 0; + } + + packet_buffer->push_back(byte); + } + + return true; +} + } // namespace libwebm diff --git a/m2ts/webm2pes.h b/m2ts/webm2pes.h index f111b82..5e67471 100644 --- a/m2ts/webm2pes.h +++ b/m2ts/webm2pes.h @@ -135,8 +135,10 @@ struct BCMVHeader { static std::size_t size() { return 10; } - // Write the BCMV Header into |buffer|. + // Write the BCMV Header into |buffer|. Caller responsible for ensuring + // destination buffer is of size >= BCMVHeader::size(). bool Write(PacketDataBuffer* buffer) const; + bool Write(uint8_t* buffer); }; struct PesHeader { @@ -219,6 +221,19 @@ class Webm2Pes { PacketDataBuffer packet_data_; }; +// Copies |raw_input_length| bytes from |raw_input| to |packet_buffer| while +// escaping start codes. Returns true when bytes are successfully copied. +// A start code is the 3 byte sequence 0x00 0x00 0x01. When +// the sequence is encountered, the value 0x03 is inserted. To avoid +// any ambiguity at reassembly time, the same is done for the sequence +// 0x00 0x00 0x03. So, the following transformation occurs for when either +// of the noted sequences is encountered: +// +// 0x00 0x00 0x01 => 0x00 0x00 0x03 0x01 +// 0x00 0x00 0x03 => 0x00 0x00 0x03 0x03 +bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input, + int raw_input_length, + PacketDataBuffer* packet_buffer); } // namespace libwebm #endif // LIBWEBM_M2TS_WEBM2PES_H_