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
This commit is contained in:
@@ -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)
|
||||
|
||||
498
webm2pes.cc
498
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 <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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<std::uint8_t*>(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<void*>(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<const void*>(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<FILE, FILEDeleter> 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<mkvparser::Segment> 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<std::uint8_t*>(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<void*>(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<const void*>(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;
|
||||
}
|
||||
|
||||
|
||||
204
webm2pes.h
Normal file
204
webm2pes.h
Normal file
@@ -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 <cstdio>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<FILE, FILEDeleter> 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<mkvparser::Segment> 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_
|
||||
Reference in New Issue
Block a user