From faf85c227d371abf37fbf5e2966bd7c3fc8a91d4 Mon Sep 17 00:00:00 2001 From: Tom Finegan Date: Thu, 3 Dec 2015 14:42:37 -0800 Subject: [PATCH] webm2pes: Refactor header/optional header writing. - Move class declarations to webm2pes.h. - Add some visual demarcation between the method definitions for each class in webm2pes.cc. - Reorganize the output code to make further development simpler. - Also, clang format noise. Change-Id: Id6d156e1f255cefe30a62784a3eadde6b93ae614 --- CMakeLists.txt | 3 +- webm2pes.cc | 498 ++++++++++++++++++------------------------------- webm2pes.h | 204 ++++++++++++++++++++ 3 files changed, 383 insertions(+), 322 deletions(-) create mode 100644 webm2pes.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e86f6ff..bf99829 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,5 +59,6 @@ target_link_libraries(vttdemux LINK_PUBLIC webm) # webm2pes section. add_executable(webm2pes - "${LIBWEBM_SRC_DIR}/webm2pes.cc") + "${LIBWEBM_SRC_DIR}/webm2pes.cc" + "${LIBWEBM_SRC_DIR}/webm2pes.h") target_link_libraries(webm2pes LINK_PUBLIC webm) diff --git a/webm2pes.cc b/webm2pes.cc index 3550075..bc48e16 100644 --- a/webm2pes.cc +++ b/webm2pes.cc @@ -5,14 +5,7 @@ // 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 -#include -#include -#include - -#include "mkvparser.hpp" -#include "mkvreader.hpp" +#include "webm2pes.h" namespace { void Usage(const char* argv[]) { @@ -20,201 +13,204 @@ void Usage(const char* argv[]) { } bool WriteUint8(std::uint8_t val, std::FILE* fileptr) { - if (fileptr == nullptr) return false; + if (fileptr == nullptr) + return false; return (std::fputc(val, fileptr) == val); } + +std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds) { + const double kNanosecondsPerSecond = 1000000000.0; + const double pts_seconds = nanoseconds / kNanosecondsPerSecond; + return pts_seconds * 90000; +} } // namespace namespace libwebm { -// Stores a value and its size in bits for writing into a PES Optional Header. -// Maximum size is 64 bits. Users may call the Check() method to perform minimal -// validation (size > 0 and <= 64). -struct PesHeaderField { - PesHeaderField(std::uint64_t value, std::uint32_t size_in_bits, - std::uint8_t byte_index, std::uint8_t bits_to_shift) - : bits(value), - num_bits(size_in_bits), - index(byte_index), - shift(bits_to_shift) {} - PesHeaderField() = delete; - PesHeaderField(const PesHeaderField&) = default; - PesHeaderField(PesHeaderField&&) = default; - ~PesHeaderField() = default; - bool Check() const { - return num_bits > 0 && num_bits <= 64 && shift >= 0 && shift < 64; +// +// PesOptionalHeader methods. +// + +void PesOptionalHeader::SetPtsBits(std::int64_t pts_90khz) { + std::uint64_t* pts_bits = &pts.bits; + *pts_bits = 0; + + // PTS is broken up and stored in 40 bits as shown: + // + // PES PTS Only flag + // / Marker Marker Marker + // | / / / + // | | | | + // 7654 321 0 765432107654321 0 765432107654321 0 + // 0010 PTS 32-30 1 PTS 29-15 1 PTS 14-0 1 + const std::uint32_t pts1 = (pts_90khz >> 30) & 0x7; + const std::uint32_t pts2 = (pts_90khz >> 15) & 0x7FFF; + const std::uint32_t pts3 = pts_90khz & 0x7FFF; + + std::uint8_t buffer[5] = {0}; + // PTS only flag. + buffer[0] |= 1 << 5; + // Top 3 bits of PTS and 1 bit marker. + buffer[0] |= pts1 << 1; + // Marker. + buffer[0] |= 1; + + // Next 15 bits of pts and 1 bit marker. + // Top 8 bits of second PTS chunk. + buffer[1] |= (pts2 >> 7) & 0xff; + // bottom 7 bits of second PTS chunk. + buffer[2] |= (pts2 << 1); + // Marker. + buffer[2] |= 1; + + // Last 15 bits of pts and 1 bit marker. + // Top 8 bits of second PTS chunk. + buffer[3] |= (pts3 >> 7) & 0xff; + // bottom 7 bits of second PTS chunk. + buffer[4] |= (pts3 << 1); + // Marker. + buffer[4] |= 1; + + // Write bits into PesHeaderField. + std::memcpy(reinterpret_cast(pts_bits), buffer, 5); +} + +// Writes fields to |file| and returns true. Returns false when write or +// field value validation fails. +bool PesOptionalHeader::Write(std::FILE* file, bool write_pts) const { + if (file == nullptr) { + std::fprintf(stderr, "Webm2Pes: nullptr in opt header writer.\n"); + return false; } - // Value to be stored in the field. - std::uint64_t bits; + std::uint8_t header[9] = {0}; + std::uint8_t* byte = header; - // Number of bits in the value. - const std::uint32_t num_bits; + if (marker.Check() != true || scrambling.Check() != true || + priority.Check() != true || data_alignment.Check() != true || + copyright.Check() != true || original.Check() != true || + has_pts.Check() != true || has_dts.Check() != true || + pts.Check() != true || stuffing_byte.Check() != true) { + std::fprintf(stderr, "Webm2Pes: Invalid PES Optional Header field.\n"); + return false; + } - // Index into the header for the byte in which |bits| will be written. - const std::uint8_t index; + // TODO(tomfinegan): As noted in above, the PesHeaderFields should be an + // array (or some data structure) that can be iterated over. - // Number of bits to shift value before or'ing. - const std::uint8_t shift; -}; + // First byte of header, fields: marker, scrambling, priority, alignment, + // copyright, original. + *byte = 0; + *byte |= marker.bits << marker.shift; + *byte |= scrambling.bits << scrambling.shift; + *byte |= priority.bits << priority.shift; + *byte |= data_alignment.bits << data_alignment.shift; + *byte |= copyright.bits << copyright.shift; + *byte |= original.bits << original.shift; -// Storage for PES Optional Header values. Fields written in order using sizes -// specified. -struct PesOptionalHeader { - // TODO(tomfinegan): The fields could be in an array, which would allow the - // code writing the optional header to iterate over the fields instead of - // having code for dealing with each one. + // Second byte of header, fields: has_pts, has_dts, unused fields. + *++byte = 0; + if (write_pts == true) { + *byte |= has_pts.bits << has_pts.shift; + *byte |= has_dts.bits << has_dts.shift; + } - // 2 bits (marker): 2 ('10') - const PesHeaderField marker = PesHeaderField(2, 2, 0, 6); + // Third byte of header, fields: remaining size of header. + *++byte = remaining_size.bits; // Field is 8 bits wide. - // 2 bits (no scrambling): 0x0 ('00') - const PesHeaderField scrambling = PesHeaderField(0, 2, 0, 4); + int num_stuffing_bytes = (pts.num_bits + 7 / 8) + 1; + if (write_pts == true) { + // Set the PTS value and adjust stuffing byte count accordingly. + *++byte = (pts.bits >> 32) & 0xff; + *++byte = (pts.bits >> 24) & 0xff; + *++byte = (pts.bits >> 16) & 0xff; + *++byte = (pts.bits >> 8) & 0xff; + *++byte = pts.bits & 0xff; + num_stuffing_bytes = 1; + } - // 1 bit (priority): 0x0 ('0') - const PesHeaderField priority = PesHeaderField(0, 1, 0, 3); + // Add the stuffing byte(s). + for (int i = 0; i < num_stuffing_bytes; ++i) + *++byte = stuffing_byte.bits; - // TODO(tomfinegan): The BCMV header could be considered a sync word, and this - // field should be 1 when a sync word follows the packet. Clarify. - // 1 bit (data alignment): 0x0 ('0') - const PesHeaderField data_alignment = PesHeaderField(0, 1, 0, 2); + if (std::fwrite(reinterpret_cast(header), 1, size_in_bytes(), file) != + size_in_bytes()) { + std::fprintf(stderr, "Webm2Pes: unable to write PES opt header to file.\n"); + return false; + } - // 1 bit (copyright): 0x0 ('0') - const PesHeaderField copyright = PesHeaderField(0, 1, 0, 1); + return true; +} - // 1 bit (original/copy): 0x0 ('0') - const PesHeaderField original = PesHeaderField(0, 1, 0, 0); +// +// BCMVHeader methods. +// - // 1 bit (has_pts): 0x1 ('1') - const PesHeaderField has_pts = PesHeaderField(1, 1, 1, 7); - - // 1 bit (has_dts): 0x0 ('0') - const PesHeaderField has_dts = PesHeaderField(0, 1, 1, 6); - - // 6 bits (unused fields): 0x0 ('000000') - const PesHeaderField unused = PesHeaderField(0, 6, 1, 0); - - // 8 bits (size of remaining data in the Header). - const PesHeaderField remaining_size = PesHeaderField(6, 8, 2, 0); - - // 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') - PesHeaderField pts = PesHeaderField(0, 40, 3, 0); - - PesHeaderField stuffing_byte = PesHeaderField(0xFF, 8, 8, 0); - - // PTS omitted in fragments. Size remains unchanged: More stuffing bytes. - bool fragment = false; - - static std::size_t size_in_bytes() { return 9; } -}; - -struct BCMVHeader { - explicit BCMVHeader(std::uint32_t frame_length) : length(frame_length) {} - BCMVHeader() = delete; - BCMVHeader(const BCMVHeader&) = delete; - BCMVHeader(BCMVHeader&&) = delete; - ~BCMVHeader() = default; - // 4 byte 'B' 'C' 'M' 'V' - // 4 byte big-endian length of frame - // 2 bytes 0 padding - const std::uint8_t bcmv[4] = {'B', 'C', 'M', 'V'}; - const std::uint32_t length; - - static std::size_t size() { return 10; } - - // Write the BCMV Header into the FILE stream. - bool Write(std::FILE* fileptr) { - if (fileptr == nullptr) { - std::fprintf(stderr, "Webm2Pes: nullptr for file in BCMV Write.\n"); +bool BCMVHeader::Write(std::FILE* fileptr) const { + if (fileptr == nullptr) { + std::fprintf(stderr, "Webm2Pes: nullptr for file in BCMV Write.\n"); + return false; + } + if (std::fwrite(bcmv, 1, 4, fileptr) != 4) { + std::fprintf(stderr, "Webm2Pes: BCMV write failed.\n"); + } + const std::size_t kRemainingBytes = 6; + const uint8_t buffer[kRemainingBytes] = {(length >> 24) & 0xff, + (length >> 16) & 0xff, + (length >> 8) & 0xff, + length & 0xff, + 0, + 0 /* 2 bytes 0 padding */}; + for (std::int8_t i = 0; i < kRemainingBytes; ++i) { + if (WriteUint8(buffer[i], fileptr) != true) { + std::fprintf(stderr, "Webm2Pes: BCMV remainder write failed.\n"); return false; } - if (std::fwrite(bcmv, 1, 4, fileptr) != 4) { - std::fprintf(stderr, "Webm2Pes: BCMV write failed.\n"); - } - const std::size_t kRemainingBytes = 6; - const uint8_t buffer[kRemainingBytes] = { - (length >> 24) & 0xff, (length >> 16) & 0xff, (length >> 8) & 0xff, - length & 0xff, 0, 0 /* 2 bytes 0 padding */}; - for (std::size_t i = 0; i < kRemainingBytes; ++i) { - if (WriteUint8(buffer[i], fileptr) != true) { - std::fprintf(stderr, "Webm2Pes: BCMV remainder write failed.\n"); - return false; - } - } - return true; } -}; + return true; +} -struct PesHeader { - const std::uint8_t start_code[4] = { - 0x00, 0x00, - 0x01, // 0x000001 is the PES packet start code prefix. - 0xE0}; // 0xE0 is the minimum video stream ID. - std::uint16_t packet_length = 0; // Number of bytes _after_ this field. - PesOptionalHeader optional_header; - std::size_t size() const { - return optional_header.size_in_bytes() + BCMVHeader::size() + - 6 /* start_code + packet_length */ + packet_length; +// +// PesHeader methods. +// + +// Writes out the header to |file|. Calls PesOptionalHeader::Write() to write +// |optional_header| contents. Returns true when successful, false otherwise. +bool PesHeader::Write(std::FILE* file, bool write_pts) const { + if (file == nullptr) { + std::fprintf(stderr, "Webm2Pes: nullptr in header writer.\n"); + return false; } -}; -// Converts the VP9 track of a WebM file to a Packetized Elementary Stream -// suitable for use in a MPEG2TS. -// https://en.wikipedia.org/wiki/Packetized_elementary_stream -// https://en.wikipedia.org/wiki/MPEG_transport_stream -class Webm2Pes { - public: - Webm2Pes(const std::string& input_file, const std::string& output_file); - Webm2Pes() = delete; - Webm2Pes(const Webm2Pes&) = delete; - Webm2Pes(Webm2Pes&&) = delete; - ~Webm2Pes() = default; + // Write |start_code|. + if (std::fwrite(reinterpret_cast(start_code), 1, 4, file) != 4) { + std::fprintf(stderr, "Webm2Pes: cannot write packet start code.\n"); + return false; + } - // Converts the VPx video stream to a PES and returns true. Returns false - // to report failure. - bool Convert(); + // Write |packet_length| as big endian. + std::uint8_t byte = (packet_length >> 8) & 0xff; + if (WriteUint8(byte, file) != true) { + std::fprintf(stderr, "Webm2Pes: cannot write packet length (byte 0).\n"); + return false; + } + byte = packet_length & 0xff; + if (WriteUint8(byte, file) != true) { + std::fprintf(stderr, "Webm2Pes: cannot write packet length (byte 1).\n"); + return false; + } - private: - // fclose functor for wrapping FILE in std::unique_ptr. - struct FILEDeleter { - int operator()(FILE* f) { - if (f != nullptr) return fclose(f); - return 0; - } - }; - typedef std::unique_ptr FilePtr; + // Write the (not really) optional header. + if (optional_header.Write(file, write_pts) != true) { + std::fprintf(stderr, "Webm2Pes: PES optional header write failed."); + return false; + } + return true; +} - bool Write90KHzPtsBitsToPesHeader(std::int64_t pts_90khz, - PesHeader* header) const; - bool WritePesOptionalHeader(const PesHeader& pes_header, std::size_t length, - std::uint8_t* header) const; - bool WritePesPacket(const mkvparser::Block::Frame& vpx_frame, - double timecode_1000nanosecond_ticks); - - const std::string input_file_name_; - const std::string output_file_name_; - std::unique_ptr webm_parser_; - mkvparser::MkvReader webm_reader_; - FilePtr output_file_; - - // Video track num in the WebM file. - int video_track_num_ = 0; - - // Input timecode scale. - std::int64_t timecode_scale_ = 1000000; -}; - -Webm2Pes::Webm2Pes(const std::string& input_file_name, - const std::string& output_file_name) - : input_file_name_(input_file_name), output_file_name_(output_file_name) {} +// +// Webm2Pes methods. +// bool Webm2Pes::Convert() { if (input_file_name_.empty() || output_file_name_.empty()) { @@ -297,7 +293,7 @@ bool Webm2Pes::Convert() { // Write frame out as PES packet(s). const bool pes_status = - WritePesPacket(frame, block->GetTimeCode(cluster)); + WritePesPacket(frame, block->GetTime(cluster)); if (pes_status != true) { std::fprintf(stderr, "Webm2Pes: WritePesPacket failed.\n"); return false; @@ -318,118 +314,8 @@ bool Webm2Pes::Convert() { return true; } -bool Webm2Pes::Write90KHzPtsBitsToPesHeader(std::int64_t pts_90khz, - PesHeader* header) const { - if (header == nullptr) { - std::fprintf(stderr, "Webm2Pes: cannot write PTS to nullptr.\n"); - return false; - } - PesHeaderField* pts_field = &header->optional_header.pts; - std::uint64_t* pts_bits = &pts_field->bits; - *pts_bits = 0; - - // PTS is broken up: - // bits 32-30 - // marker - // bits 29-15 - // marker - // bits 14-0 - // marker - const std::uint32_t pts1 = (pts_90khz >> 30) & 0x7; - const std::uint32_t pts2 = (pts_90khz >> 15) & 0x7FFF; - const std::uint32_t pts3 = pts_90khz & 0x7FFF; - - std::uint8_t buffer[5] = {0}; - // PTS only flag. - buffer[0] |= 1 << 5; - // Top 3 bits of PTS and 1 bit marker. - buffer[0] |= pts1 << 1; - // Marker. - buffer[0] |= 1; - - // Next 15 bits of pts and 1 bit marker. - // Top 8 bits of second PTS chunk. - buffer[1] |= (pts2 >> 8) & 0xff; - // bottom 7 bits of second PTS chunk. - buffer[2] |= (pts2 << 1); - // Marker. - buffer[2] |= 1; - - // Last 15 bits of pts and 1 bit marker. - // Top 8 bits of second PTS chunk. - buffer[3] |= (pts3 >> 8) & 0xff; - // bottom 7 bits of second PTS chunk. - buffer[4] |= (pts3 << 1); - // Marker. - buffer[4] |= 1; - - // Write bits into PesHeaderField. - std::memcpy(reinterpret_cast(pts_bits), buffer, 5); - return true; -} - -bool Webm2Pes::WritePesOptionalHeader(const PesHeader& pes_header, - std::size_t length, - std::uint8_t* header) const { - if (header == nullptr) { - std::fprintf(stderr, "Webm2Pes: nullptr in opt header writer.\n"); - return false; - } - - std::uint8_t* byte = header; - const PesOptionalHeader& poh = pes_header.optional_header; - - if (poh.marker.Check() != true || poh.scrambling.Check() != true || - poh.priority.Check() != true || poh.data_alignment.Check() != true || - poh.copyright.Check() != true || poh.original.Check() != true || - poh.has_pts.Check() != true || poh.has_dts.Check() != true || - poh.pts.Check() != true || poh.stuffing_byte.Check() != true) { - std::fprintf(stderr, "Webm2Pes: Invalid PES Optional Header field.\n"); - return false; - } - - // TODO(tomfinegan): As noted in PesOptionalHeader, the PesHeaderFields - // should be an array that can be iterated over. - - // First byte of header, fields: marker, scrambling, priority, alignment, - // copyright, original. - *byte = 0; - *byte |= poh.marker.bits << poh.marker.shift; - *byte |= poh.scrambling.bits << poh.scrambling.shift; - *byte |= poh.priority.bits << poh.priority.shift; - *byte |= poh.data_alignment.bits << poh.data_alignment.shift; - *byte |= poh.copyright.bits << poh.copyright.shift; - *byte |= poh.original.bits << poh.original.shift; - - // Second byte of header, fields: has_pts, has_dts, unused fields. - *++byte = 0; - *byte |= poh.has_pts.bits << poh.has_pts.shift; - *byte |= poh.has_dts.bits << poh.has_dts.shift; - - // Third byte of header, fields: remaining size of header. - *++byte = poh.remaining_size.bits; // Field is 8 bits wide. - - // Set the PTS value. - *++byte = (poh.pts.bits >> 32) & 0xff; - *++byte = (poh.pts.bits >> 24) & 0xff; - *++byte = (poh.pts.bits >> 16) & 0xff; - *++byte = (poh.pts.bits >> 8) & 0xff; - *++byte = poh.pts.bits & 0xff; - - // Add the stuffing byte; - *++byte = poh.stuffing_byte.bits; - - if (std::fwrite(reinterpret_cast(header), 1, poh.size_in_bytes(), - output_file_.get()) != poh.size_in_bytes()) { - std::fprintf(stderr, "Webm2Pes: unable to write PES opt header to file.\n"); - return false; - } - - return true; -} - bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame, - double timecode_1000nanosecond_ticks) { + double nanosecond_pts) { bool packetize = false; PesHeader header; @@ -442,44 +328,14 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame, packetize = true; } - const double kNanosecondsPerSecond = 1000000000.0; - const double kMillisecondsPerSecond = 1000.0; - const double k1000NanosecondTicksPerSecond = - kNanosecondsPerSecond * kMillisecondsPerSecond; - const double pts_seconds = - timecode_1000nanosecond_ticks / k1000NanosecondTicksPerSecond; - const std::int64_t khz90_pts = pts_seconds * 90000; + const std::int64_t khz90_pts = NanosecondsTo90KhzTicks(nanosecond_pts); + header.optional_header.SetPtsBits(khz90_pts); - if (Write90KHzPtsBitsToPesHeader(khz90_pts, &header) != true) { - std::fprintf(stderr, "Webm2Pes: 90khz pts write failed."); - return false; - } - - std::uint8_t write_buffer[9] = {0}; if (packetize == false) { header.packet_length = header.optional_header.size_in_bytes() + vpx_frame.len; - if (std::fwrite(reinterpret_cast(header.start_code), 1, 4, - output_file_.get()) != 4) { - std::fprintf(stderr, "Webm2Pes: cannot write packet start code.\n"); - return false; - } - - // Big endian length. - std::uint8_t byte = (header.packet_length >> 8) & 0xff; - if (WriteUint8(byte, output_file_.get()) != true) { - std::fprintf(stderr, "Webm2Pes: cannot write packet length (1).\n"); - return false; - } - byte = header.packet_length & 0xff; - if (WriteUint8(byte, output_file_.get()) != true) { - std::fprintf(stderr, "Webm2Pes: cannot write packet length (2).\n"); - return false; - } - - if (WritePesOptionalHeader(header, header.packet_length, - &write_buffer[0]) != true) { - std::fprintf(stderr, "Webm2Pes: PES optional header write failed."); + if (header.Write(output_file_.get(), true) != true) { + std::fprintf(stderr, "Webm2Pes: cannot write PES packet header.\n"); return false; } diff --git a/webm2pes.h b/webm2pes.h new file mode 100644 index 0000000..1ab8cdc --- /dev/null +++ b/webm2pes.h @@ -0,0 +1,204 @@ +// Copyright (c) 2015 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_WEBM2PES_H_ +#define LIBWEBM_WEBM2PES_H_ + +#include +#include +#include +#include +#include + +#include "mkvparser.hpp" +#include "mkvreader.hpp" + +namespace libwebm { + +// Stores a value and its size in bits for writing into a PES Optional Header. +// Maximum size is 64 bits. Users may call the Check() method to perform minimal +// validation (size > 0 and <= 64). +struct PesHeaderField { + PesHeaderField(std::uint64_t value, + std::uint32_t size_in_bits, + std::uint8_t byte_index, + std::uint8_t bits_to_shift) + : bits(value), + num_bits(size_in_bits), + index(byte_index), + shift(bits_to_shift) {} + PesHeaderField() = delete; + PesHeaderField(const PesHeaderField&) = default; + PesHeaderField(PesHeaderField&&) = default; + ~PesHeaderField() = default; + bool Check() const { + return num_bits > 0 && num_bits <= 64 && shift >= 0 && shift < 64; + } + + // Value to be stored in the field. + std::uint64_t bits; + + // Number of bits in the value. + const std::uint32_t num_bits; + + // Index into the header for the byte in which |bits| will be written. + const std::uint8_t index; + + // Number of bits to shift value before or'ing. + const std::uint8_t shift; +}; + +// Storage for PES Optional Header values. Fields written in order using sizes +// specified. +struct PesOptionalHeader { + // TODO(tomfinegan): The fields could be in an array, which would allow the + // code writing the optional header to iterate over the fields instead of + // having code for dealing with each one. + + // 2 bits (marker): 2 ('10') + const PesHeaderField marker = PesHeaderField(2, 2, 0, 6); + + // 2 bits (no scrambling): 0x0 ('00') + const PesHeaderField scrambling = PesHeaderField(0, 2, 0, 4); + + // 1 bit (priority): 0x0 ('0') + const PesHeaderField priority = PesHeaderField(0, 1, 0, 3); + + // TODO(tomfinegan): The BCMV header could be considered a sync word, and this + // field should be 1 when a sync word follows the packet. Clarify. + // 1 bit (data alignment): 0x0 ('0') + const PesHeaderField data_alignment = PesHeaderField(0, 1, 0, 2); + + // 1 bit (copyright): 0x0 ('0') + const PesHeaderField copyright = PesHeaderField(0, 1, 0, 1); + + // 1 bit (original/copy): 0x0 ('0') + const PesHeaderField original = PesHeaderField(0, 1, 0, 0); + + // 1 bit (has_pts): 0x1 ('1') + const PesHeaderField has_pts = PesHeaderField(1, 1, 1, 7); + + // 1 bit (has_dts): 0x0 ('0') + const PesHeaderField has_dts = PesHeaderField(0, 1, 1, 6); + + // 6 bits (unused fields): 0x0 ('000000') + const PesHeaderField unused = PesHeaderField(0, 6, 1, 0); + + // 8 bits (size of remaining data in the Header). + const PesHeaderField remaining_size = PesHeaderField(6, 8, 2, 0); + + // 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') + PesHeaderField pts = PesHeaderField(0, 40, 3, 0); + + PesHeaderField stuffing_byte = PesHeaderField(0xFF, 8, 8, 0); + + // PTS omitted in fragments. Size remains unchanged: More stuffing bytes. + bool fragment = false; + + static std::size_t size_in_bytes() { return 9; } + + // Writes |pts_90khz| to |pts| per format described at its declaration above. + void SetPtsBits(std::int64_t pts_90khz); + + // Writes fields to |file| and returns true. Returns false when write or + // field value validation fails. + bool Write(std::FILE* file, bool write_pts) const; +}; + +// Describes custom 10 byte header that immediately follows the PES Optional +// Header in each PES packet output by Webm2Pes: +// 4 byte 'B' 'C' 'M' 'V' +// 4 byte big-endian length of frame +// 2 bytes 0 padding +struct BCMVHeader { + explicit BCMVHeader(std::uint32_t frame_length) : length(frame_length) {} + BCMVHeader() = delete; + BCMVHeader(const BCMVHeader&) = delete; + BCMVHeader(BCMVHeader&&) = delete; + ~BCMVHeader() = default; + const std::uint8_t bcmv[4] = {'B', 'C', 'M', 'V'}; + const std::uint32_t length; + + static std::size_t size() { return 10; } + + // Write the BCMV Header into the FILE stream. + bool Write(std::FILE* fileptr) const; +}; + +struct PesHeader { + const std::uint8_t start_code[4] = { + 0x00, + 0x00, + 0x01, // 0x000001 is the PES packet start code prefix. + 0xE0}; // 0xE0 is the minimum video stream ID. + std::uint16_t packet_length = 0; // Number of bytes _after_ this field. + PesOptionalHeader optional_header; + std::size_t size() const { + return optional_header.size_in_bytes() + BCMVHeader::size() + + 6 /* start_code + packet_length */ + packet_length; + } + + // Writes out the header to |file|. Calls PesOptionalHeader::Write() to write + // |optional_header| contents. Returns true when successful, false otherwise. + bool Write(std::FILE* file, bool write_pts) const; +}; + +// Converts the VP9 track of a WebM file to a Packetized Elementary Stream +// suitable for use in a MPEG2TS. +// https://en.wikipedia.org/wiki/Packetized_elementary_stream +// https://en.wikipedia.org/wiki/MPEG_transport_stream +class Webm2Pes { + public: + Webm2Pes(const std::string& input_file, const std::string& output_file) + : input_file_name_(input_file), output_file_name_(output_file) {} + + Webm2Pes() = delete; + Webm2Pes(const Webm2Pes&) = delete; + Webm2Pes(Webm2Pes&&) = delete; + ~Webm2Pes() = default; + + // Converts the VPx video stream to a PES and returns true. Returns false + // to report failure. + bool Convert(); + + private: + // fclose functor for wrapping FILE in std::unique_ptr. + struct FILEDeleter { + int operator()(FILE* f) { + if (f != nullptr) + return fclose(f); + return 0; + } + }; + typedef std::unique_ptr FilePtr; + + bool WritePesPacket(const mkvparser::Block::Frame& vpx_frame, + double nanosecond_pts); + + const std::string input_file_name_; + const std::string output_file_name_; + std::unique_ptr webm_parser_; + mkvparser::MkvReader webm_reader_; + FilePtr output_file_; + + // Video track num in the WebM file. + int video_track_num_ = 0; + + // Input timecode scale. + std::int64_t timecode_scale_ = 1000000; +}; +} // namespace libwebm + +#endif // LIBWEBM_WEBM2PES_H_