webm2ts: Converts WebM VPx video to a MPEG TS.
Built atop webm2pes, takes the output from webm2pes and packetizes it into MPEG TS. Change-Id: Ia122479ee91a112ad7fe223a571ca6d7ba66d406
This commit is contained in:
parent
453bf44d32
commit
bb8cefd516
@ -11,6 +11,13 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/build/msvc_runtime.cmake")
|
||||
|
||||
set(LIBWEBM_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
# Turn up the warning levels.
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "/W4 ${CMAKE_CXX_FLAGS}")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "-Wall -Wextra ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
|
||||
# Test configuration flags. Defined here for visibility.
|
||||
option(ENABLE_TESTS "Enables tests." OFF)
|
||||
set(GTEST_SRC_DIR "${LIBWEBM_SRC_DIR}/../googletest" CACHE PATH
|
||||
@ -74,7 +81,17 @@ add_executable(webm2pes
|
||||
"${LIBWEBM_SRC_DIR}/webm2pes_main.cc")
|
||||
target_link_libraries(webm2pes LINK_PUBLIC webm)
|
||||
|
||||
# tests section.
|
||||
# webm2ts section.
|
||||
add_executable(webm2ts
|
||||
"${LIBWEBM_SRC_DIR}/common/libwebm_utils.cc"
|
||||
"${LIBWEBM_SRC_DIR}/common/libwebm_utils.h"
|
||||
"${LIBWEBM_SRC_DIR}/vpxpes2ts.cc"
|
||||
"${LIBWEBM_SRC_DIR}/vpxpes2ts.h"
|
||||
"${LIBWEBM_SRC_DIR}/webm2pes.cc"
|
||||
"${LIBWEBM_SRC_DIR}/webm2pes.h"
|
||||
"${LIBWEBM_SRC_DIR}/vpxpes2ts_main.cc")
|
||||
target_link_libraries(webm2ts LINK_PUBLIC webm)
|
||||
|
||||
if (ENABLE_TESTS)
|
||||
add_subdirectory("${GTEST_SRC_DIR}" "${GTEST_BUILD_DIR}")
|
||||
include_directories("${GTEST_SRC_DIR}/googletest/include")
|
||||
@ -101,3 +118,4 @@ if (ENABLE_TESTS)
|
||||
"${LIBWEBM_SRC_DIR}/testing/webm2pes_tests.cc")
|
||||
target_link_libraries(webm2pes_tests LINK_PUBLIC webm gtest)
|
||||
endif (ENABLE_TESTS)
|
||||
|
||||
|
217
vpxpes2ts.cc
Normal file
217
vpxpes2ts.cc
Normal file
@ -0,0 +1,217 @@
|
||||
// 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 "vpxpes2ts.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
namespace libwebm {
|
||||
// TODO(tomfinegan): Dedupe this and PesHeaderField.
|
||||
// Stores a value and its size in bits for writing into a MPEG2 TS Header.
|
||||
// Maximum size is 64 bits. Users may call the Check() method to perform minimal
|
||||
// validation (size > 0 and <= 64).
|
||||
struct TsHeaderField {
|
||||
TsHeaderField(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) {}
|
||||
TsHeaderField() = delete;
|
||||
TsHeaderField(const TsHeaderField&) = default;
|
||||
TsHeaderField(TsHeaderField&&) = default;
|
||||
~TsHeaderField() = 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 left shift value before or'ing. Ignored for whole bytes.
|
||||
const std::uint8_t shift;
|
||||
};
|
||||
|
||||
// Data storage for MPEG2 Transport Stream headers.
|
||||
// https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet
|
||||
struct TsHeader {
|
||||
TsHeader(bool payload_start, bool adaptation_flag, std::uint8_t counter)
|
||||
: is_payload_start(payload_start),
|
||||
has_adaptation(adaptation_flag),
|
||||
counter_value(counter) {}
|
||||
TsHeader() = delete;
|
||||
TsHeader(const TsHeader&) = default;
|
||||
TsHeader(TsHeader&&) = default;
|
||||
~TsHeader() = default;
|
||||
|
||||
void Write(PacketDataBuffer* buffer) const;
|
||||
|
||||
// Indicates the packet is the beginning of a new fragmented payload.
|
||||
const bool is_payload_start;
|
||||
|
||||
// Indicates the packet contains an adaptation field.
|
||||
const bool has_adaptation;
|
||||
|
||||
// The sync byte is the bit pattern of 0x47 (ASCII char 'G').
|
||||
const std::uint8_t kTsHeaderSyncByte = 0x47;
|
||||
const std::uint8_t sync_byte = kTsHeaderSyncByte;
|
||||
|
||||
// Value for |continuity_counter|. Used to detect gaps when demuxing.
|
||||
const std::uint8_t counter_value;
|
||||
|
||||
// Set when FEC is impossible. Always 0.
|
||||
const TsHeaderField transport_error_indicator = TsHeaderField(0, 1, 1, 7);
|
||||
|
||||
// This MPEG2 TS header is the start of a new payload (aka PES packet).
|
||||
const TsHeaderField payload_unit_start_indicator =
|
||||
TsHeaderField(is_payload_start ? 1 : 0, 1, 1, 6);
|
||||
|
||||
// Set when the current packet has a higher priority than other packets with
|
||||
// the same PID. Always 0 for VPX.
|
||||
const TsHeaderField transport_priority = TsHeaderField(0, 1, 1, 5);
|
||||
|
||||
// https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet_Identifier_.28PID.29
|
||||
// 0x0020-0x1FFA May be assigned as needed to Program Map Tables, elementary
|
||||
// streams and other data tables.
|
||||
// Note: Though we hard code to 0x20, this value is actually 13 bits-- the
|
||||
// buffer for the header is always set to 0, so it doesn't matter in practice.
|
||||
const TsHeaderField pid = TsHeaderField(0x20, 8, 2, 0);
|
||||
|
||||
// Indicates scrambling key. Unused; always 0.
|
||||
const TsHeaderField scrambling_control = TsHeaderField(0, 2, 3, 6);
|
||||
|
||||
// Adaptation field flag. Unused; always 0.
|
||||
// TODO(tomfinegan): Not sure this is OK. Might need to add support for
|
||||
// writing the Adaptation Field.
|
||||
const TsHeaderField adaptation_field_flag =
|
||||
TsHeaderField(has_adaptation ? 1 : 0, 1, 3, 5);
|
||||
|
||||
// Payload flag. All output packets created here have payloads. Always 1.
|
||||
const TsHeaderField payload_flag = TsHeaderField(1, 1, 3, 4);
|
||||
|
||||
// Continuity counter. Two bit field that is incremented for every packet.
|
||||
const TsHeaderField continuity_counter =
|
||||
TsHeaderField(counter_value, 4, 3, 3);
|
||||
};
|
||||
|
||||
void TsHeader::Write(PacketDataBuffer* buffer) const {
|
||||
std::uint8_t* byte = &(*buffer)[0];
|
||||
*byte = sync_byte;
|
||||
|
||||
*++byte = 0;
|
||||
*byte |= transport_error_indicator.bits << transport_error_indicator.shift;
|
||||
*byte |= payload_unit_start_indicator.bits
|
||||
<< payload_unit_start_indicator.shift;
|
||||
*byte |= transport_priority.bits << transport_priority.shift;
|
||||
|
||||
*++byte = pid.bits;
|
||||
|
||||
*++byte = 0;
|
||||
*byte |= scrambling_control.bits << scrambling_control.shift;
|
||||
*byte |= adaptation_field_flag.bits << adaptation_field_flag.shift;
|
||||
*byte |= payload_flag.bits << payload_flag.shift;
|
||||
*byte |= continuity_counter.bits; // last 4 bits.
|
||||
}
|
||||
|
||||
bool VpxPes2Ts::ConvertToFile() {
|
||||
output_file_ = FilePtr(fopen(output_file_name_.c_str(), "wb"), FILEDeleter());
|
||||
if (output_file_ == nullptr) {
|
||||
std::fprintf(stderr, "VpxPes2Ts: Cannot open %s for output.\n",
|
||||
output_file_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
pes_converter_.reset(new Webm2Pes(input_file_name_, this));
|
||||
if (pes_converter_ == nullptr) {
|
||||
std::fprintf(stderr, "VpxPes2Ts: Out of memory.\n");
|
||||
return false;
|
||||
}
|
||||
return pes_converter_->ConvertToPacketReceiver();
|
||||
}
|
||||
|
||||
bool VpxPes2Ts::ReceivePacket(const PacketDataBuffer& packet_data) {
|
||||
const int kTsHeaderSize = 4;
|
||||
const int kTsPayloadSize = 184;
|
||||
const int kTsPacketSize = kTsHeaderSize + kTsPayloadSize;
|
||||
int bytes_to_packetize = packet_data.size();
|
||||
std::uint8_t continuity_counter = 0;
|
||||
std::size_t read_pos = 0;
|
||||
|
||||
ts_buffer_.reserve(kTsPacketSize);
|
||||
|
||||
while (bytes_to_packetize > 0) {
|
||||
if (continuity_counter > 0xf)
|
||||
continuity_counter = 0;
|
||||
|
||||
// Calculate payload size (need to know if we'll have to pad with an empty
|
||||
// adaptation field).
|
||||
int payload_size = std::min(bytes_to_packetize, kTsPayloadSize);
|
||||
|
||||
// Write the TS header.
|
||||
const TsHeader header(
|
||||
bytes_to_packetize == static_cast<int>(packet_data.size()),
|
||||
payload_size != kTsPayloadSize, continuity_counter);
|
||||
header.Write(&ts_buffer_);
|
||||
std::size_t write_pos = kTsHeaderSize;
|
||||
|
||||
// (pre)Pad payload with an empty adaptation field. All packets must be
|
||||
// |kTsPacketSize| (188).
|
||||
if (payload_size < kTsPayloadSize) {
|
||||
// We need at least 2 bytes to write an empty adaptation field.
|
||||
if (payload_size == (kTsPayloadSize - 1)) {
|
||||
payload_size--;
|
||||
}
|
||||
|
||||
// Padding adaptation field:
|
||||
// 8 bits: number of adaptation field bytes following this byte.
|
||||
// 8 bits: unused (in this program) flags.
|
||||
// This is followed by a run of 0xff to reach |kTsPayloadSize| (184)
|
||||
// bytes.
|
||||
const int pad_size = kTsPayloadSize - payload_size - 1 - 1;
|
||||
ts_buffer_[write_pos++] = pad_size + 1;
|
||||
ts_buffer_[write_pos++] = 0;
|
||||
|
||||
const std::uint8_t kStuffingByte = 0xff;
|
||||
for (int i = 0; i < pad_size; ++i) {
|
||||
ts_buffer_[write_pos++] = kStuffingByte;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < payload_size; ++i) {
|
||||
ts_buffer_[write_pos++] = packet_data[read_pos++];
|
||||
}
|
||||
|
||||
bytes_to_packetize -= payload_size;
|
||||
continuity_counter++;
|
||||
|
||||
if (write_pos != kTsPacketSize) {
|
||||
fprintf(stderr, "VpxPes2Ts: Invalid packet length.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write contents of |ts_buffer_| to |output_file_|.
|
||||
// TODO(tomfinegan): Writing 188 bytes at a time isn't exactly efficient...
|
||||
// Fix me.
|
||||
if (std::fwrite(&ts_buffer_[0], 1, kTsPacketSize, output_file_.get()) !=
|
||||
kTsPacketSize) {
|
||||
std::fprintf(stderr, "VpxPes2Ts: TS packet write failed.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace libwebm
|
48
vpxpes2ts.h
Normal file
48
vpxpes2ts.h
Normal file
@ -0,0 +1,48 @@
|
||||
// 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_VPXPES2TS_H_
|
||||
#define LIBWEBM_VPXPES2TS_H_
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/libwebm_utils.h"
|
||||
#include "webm2pes.h"
|
||||
|
||||
namespace libwebm {
|
||||
|
||||
class VpxPes2Ts : public PacketReceiverInterface {
|
||||
public:
|
||||
VpxPes2Ts(const std::string& input_file_name,
|
||||
const std::string& output_file_name)
|
||||
: input_file_name_(input_file_name),
|
||||
output_file_name_(output_file_name) {}
|
||||
virtual ~VpxPes2Ts() = default;
|
||||
VpxPes2Ts() = delete;
|
||||
VpxPes2Ts(const VpxPes2Ts&) = delete;
|
||||
VpxPes2Ts(VpxPes2Ts&&) = delete;
|
||||
|
||||
bool ConvertToFile();
|
||||
|
||||
private:
|
||||
bool ReceivePacket(const PacketDataBuffer& packet_data) override;
|
||||
|
||||
const std::string input_file_name_;
|
||||
const std::string output_file_name_;
|
||||
|
||||
FilePtr output_file_;
|
||||
std::unique_ptr<Webm2Pes> pes_converter_;
|
||||
PacketDataBuffer ts_buffer_;
|
||||
};
|
||||
|
||||
} // namespace libwebm
|
||||
|
||||
#endif // LIBWEBM_VPXPES2TS_H_
|
32
vpxpes2ts_main.cc
Normal file
32
vpxpes2ts_main.cc
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 "vpxpes2ts.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
void Usage(const char* argv[]) {
|
||||
printf("Usage: %s <WebM file> <output file>", argv[0]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
if (argc < 3) {
|
||||
Usage(argv);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const std::string input_path = argv[1];
|
||||
const std::string output_path = argv[2];
|
||||
|
||||
libwebm::VpxPes2Ts converter(input_path, output_path);
|
||||
return converter.ConvertToFile() == true ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
147
webm2pes.cc
147
webm2pes.cc
@ -106,13 +106,14 @@ void PesOptionalHeader::SetPtsBits(std::int64_t pts_90khz) {
|
||||
|
||||
// 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) {
|
||||
bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const {
|
||||
if (buffer == nullptr) {
|
||||
std::fprintf(stderr, "Webm2Pes: nullptr in opt header writer.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint8_t header[9] = {0};
|
||||
const std::size_t kHeaderSize = 9;
|
||||
std::uint8_t header[kHeaderSize] = {0};
|
||||
std::uint8_t* byte = header;
|
||||
|
||||
if (marker.Check() != true || scrambling.Check() != true ||
|
||||
@ -163,11 +164,8 @@ bool PesOptionalHeader::Write(std::FILE* file, bool write_pts) const {
|
||||
for (int i = 0; i < num_stuffing_bytes; ++i)
|
||||
*++byte = stuffing_byte.bits;
|
||||
|
||||
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;
|
||||
}
|
||||
for (int i = 0; i < kHeaderSize; ++i)
|
||||
buffer->push_back(header[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -176,28 +174,26 @@ bool PesOptionalHeader::Write(std::FILE* file, bool write_pts) const {
|
||||
// BCMVHeader methods.
|
||||
//
|
||||
|
||||
bool BCMVHeader::Write(std::FILE* fileptr) const {
|
||||
if (fileptr == nullptr) {
|
||||
std::fprintf(stderr, "Webm2Pes: nullptr for file in BCMV Write.\n");
|
||||
bool BCMVHeader::Write(PacketDataBuffer* buffer) const {
|
||||
if (buffer == nullptr) {
|
||||
std::fprintf(stderr, "Webm2Pes: nullptr for buffer 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 kBcmvSize = 4;
|
||||
for (int i = 0; i < kBcmvSize; ++i)
|
||||
buffer->push_back(bcmv[i]);
|
||||
|
||||
const std::size_t kRemainingBytes = 6;
|
||||
const uint8_t buffer[kRemainingBytes] = {
|
||||
const uint8_t bcmv_buffer[kRemainingBytes] = {
|
||||
static_cast<std::uint8_t>((length >> 24) & 0xff),
|
||||
static_cast<std::uint8_t>((length >> 16) & 0xff),
|
||||
static_cast<std::uint8_t>((length >> 8) & 0xff),
|
||||
static_cast<std::uint8_t>(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;
|
||||
}
|
||||
}
|
||||
for (std::int8_t i = 0; i < kRemainingBytes; ++i)
|
||||
buffer->push_back(bcmv_buffer[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -205,34 +201,27 @@ bool BCMVHeader::Write(std::FILE* fileptr) const {
|
||||
// PesHeader methods.
|
||||
//
|
||||
|
||||
// Writes out the header to |file|. Calls PesOptionalHeader::Write() to write
|
||||
// Writes out the header to |buffer|. 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) {
|
||||
bool PesHeader::Write(bool write_pts, PacketDataBuffer* buffer) const {
|
||||
if (buffer == nullptr) {
|
||||
std::fprintf(stderr, "Webm2Pes: nullptr in header writer.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
const std::size_t kStartCodeLength = 4;
|
||||
for (int i = 0; i < kStartCodeLength; ++i)
|
||||
buffer->push_back(start_code[i]);
|
||||
|
||||
// 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;
|
||||
}
|
||||
buffer->push_back(byte);
|
||||
byte = packet_length & 0xff;
|
||||
if (WriteUint8(byte, file) != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: cannot write packet length (byte 1).\n");
|
||||
return false;
|
||||
}
|
||||
buffer->push_back(byte);
|
||||
|
||||
// Write the (not really) optional header.
|
||||
if (optional_header.Write(file, write_pts) != true) {
|
||||
if (optional_header.Write(write_pts, buffer) != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: PES optional header write failed.");
|
||||
return false;
|
||||
}
|
||||
@ -261,6 +250,68 @@ bool Webm2Pes::ConvertToFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Walk clusters in segment.
|
||||
const mkvparser::Cluster* cluster = webm_parser_->GetFirst();
|
||||
while (cluster != nullptr && cluster->EOS() == false) {
|
||||
const mkvparser::BlockEntry* block_entry = nullptr;
|
||||
std::int64_t block_status = cluster->GetFirst(block_entry);
|
||||
if (block_status < 0) {
|
||||
std::fprintf(stderr, "Webm2Pes: Cannot parse first block in %s.\n",
|
||||
input_file_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Walk blocks in cluster.
|
||||
while (block_entry != nullptr && block_entry->EOS() == false) {
|
||||
const mkvparser::Block* block = block_entry->GetBlock();
|
||||
if (block->GetTrackNumber() == video_track_num_) {
|
||||
const int frame_count = block->GetFrameCount();
|
||||
|
||||
// Walk frames in block.
|
||||
for (int frame_num = 0; frame_num < frame_count; ++frame_num) {
|
||||
const mkvparser::Block::Frame& frame = block->GetFrame(frame_num);
|
||||
|
||||
// Write frame out as PES packet(s), storing them in |packet_data_|.
|
||||
const bool pes_status =
|
||||
WritePesPacket(frame, block->GetTime(cluster));
|
||||
if (pes_status != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: WritePesPacket failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write contents of |packet_data_| to |output_file_|.
|
||||
if (std::fwrite(&packet_data_[0], 1, packet_data_.size(),
|
||||
output_file_.get()) != packet_data_.size()) {
|
||||
std::fprintf(stderr, "Webm2Pes: packet payload write failed.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
block_status = cluster->GetNext(block_entry, block_entry);
|
||||
if (block_status < 0) {
|
||||
std::fprintf(stderr, "Webm2Pes: Cannot parse block in %s.\n",
|
||||
input_file_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cluster = webm_parser_->GetNext(cluster);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Webm2Pes::ConvertToPacketReceiver() {
|
||||
if (input_file_name_.empty() || packet_sink_ == nullptr) {
|
||||
std::fprintf(stderr, "Webm2Pes: input file name empty or null sink.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (InitWebmParser() != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: Cannot initialize WebM parser.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Walk clusters in segment.
|
||||
const mkvparser::Cluster* cluster = webm_parser_->GetFirst();
|
||||
while (cluster != nullptr && cluster->EOS() == false) {
|
||||
@ -289,6 +340,10 @@ bool Webm2Pes::ConvertToFile() {
|
||||
std::fprintf(stderr, "Webm2Pes: WritePesPacket failed.\n");
|
||||
return false;
|
||||
}
|
||||
if (packet_sink_->ReceivePacket(packet_data_) != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: ReceivePacket failed.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
block_status = cluster->GetNext(block_entry, block_entry);
|
||||
@ -411,29 +466,29 @@ bool Webm2Pes::WritePesPacket(const mkvparser::Block::Frame& vpx_frame,
|
||||
const std::int64_t khz90_pts = NanosecondsTo90KhzTicks(nanosecond_pts);
|
||||
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;
|
||||
if (header.Write(output_file_.get(), write_pts) != true) {
|
||||
if (header.Write(write_pts, &packet_data_) != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: packet header write failed.\n");
|
||||
return false;
|
||||
}
|
||||
write_pts = false;
|
||||
|
||||
BCMVHeader bcmv_header(packet_payload_range.length);
|
||||
if (bcmv_header.Write(output_file_.get()) != true) {
|
||||
if (bcmv_header.Write(&packet_data_) != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: BCMV write failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the payload.
|
||||
if (std::fwrite(frame_data.get() + packet_payload_range.offset, 1,
|
||||
packet_payload_range.length,
|
||||
output_file_.get()) != packet_payload_range.length) {
|
||||
std::fprintf(stderr, "Webm2Pes: packet payload write failed.\n");
|
||||
return false;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
45
webm2pes.h
45
webm2pes.h
@ -11,6 +11,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mkvparser.hpp"
|
||||
#include "mkvreader.hpp"
|
||||
@ -52,6 +53,9 @@ struct PesHeaderField {
|
||||
const std::uint8_t shift;
|
||||
};
|
||||
|
||||
// Data is stored in buffers before being written to output files.
|
||||
typedef std::vector<std::uint8_t> PacketDataBuffer;
|
||||
|
||||
// Storage for PES Optional Header values. Fields written in order using sizes
|
||||
// specified.
|
||||
struct PesOptionalHeader {
|
||||
@ -112,9 +116,9 @@ struct PesOptionalHeader {
|
||||
// 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
|
||||
// Writes fields to |buffer| and returns true. Returns false when write or
|
||||
// field value validation fails.
|
||||
bool Write(std::FILE* file, bool write_pts) const;
|
||||
bool Write(bool write_pts, PacketDataBuffer* buffer) const;
|
||||
};
|
||||
|
||||
// Describes custom 10 byte header that immediately follows the PES Optional
|
||||
@ -133,14 +137,13 @@ struct BCMVHeader {
|
||||
|
||||
static std::size_t size() { return 10; }
|
||||
|
||||
// Write the BCMV Header into the FILE stream.
|
||||
bool Write(std::FILE* fileptr) const;
|
||||
// Write the BCMV Header into |buffer|.
|
||||
bool Write(PacketDataBuffer* buffer) const;
|
||||
};
|
||||
|
||||
struct PesHeader {
|
||||
const std::uint8_t start_code[4] = {
|
||||
0x00,
|
||||
0x00,
|
||||
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.
|
||||
@ -150,9 +153,16 @@ struct PesHeader {
|
||||
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;
|
||||
// Writes out the header to |buffer|. Calls PesOptionalHeader::Write() to
|
||||
// write |optional_header| contents. Returns true when successful, false
|
||||
// otherwise.
|
||||
bool Write(bool write_pts, PacketDataBuffer* buffer) const;
|
||||
};
|
||||
|
||||
class PacketReceiverInterface {
|
||||
public:
|
||||
virtual ~PacketReceiverInterface() {}
|
||||
virtual bool ReceivePacket(const PacketDataBuffer& packet) = 0;
|
||||
};
|
||||
|
||||
// Converts the VP9 track of a WebM file to a Packetized Elementary Stream
|
||||
@ -165,6 +175,8 @@ class Webm2Pes {
|
||||
|
||||
Webm2Pes(const std::string& input_file, const std::string& output_file)
|
||||
: input_file_name_(input_file), output_file_name_(output_file) {}
|
||||
Webm2Pes(const std::string& input_file, PacketReceiverInterface* packet_sink)
|
||||
: input_file_name_(input_file), packet_sink_(packet_sink) {}
|
||||
|
||||
Webm2Pes() = delete;
|
||||
Webm2Pes(const Webm2Pes&) = delete;
|
||||
@ -175,6 +187,12 @@ class Webm2Pes {
|
||||
// to report failure.
|
||||
bool ConvertToFile();
|
||||
|
||||
// Converts the VPx video stream to a sequence of PES packets, and calls the
|
||||
// PacketReceiverInterface::ReceivePacket() once for each PES packet. Returns
|
||||
// only after full conversion or error. Returns true for success, and false
|
||||
// when an error occurs.
|
||||
bool ConvertToPacketReceiver();
|
||||
|
||||
private:
|
||||
bool InitWebmParser();
|
||||
bool WritePesPacket(const mkvparser::Block::Frame& vpx_frame,
|
||||
@ -194,10 +212,15 @@ class Webm2Pes {
|
||||
|
||||
// Input timecode scale.
|
||||
std::int64_t timecode_scale_ = 1000000;
|
||||
|
||||
// Packet sink; when constructed with a PacketReceiverInterface*, packet and
|
||||
// type of packet are sent to |packet_sink_| instead of written to an output
|
||||
// file.
|
||||
PacketReceiverInterface* packet_sink_ = nullptr;
|
||||
|
||||
PacketDataBuffer packet_data_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace libwebm
|
||||
|
||||
#endif // LIBWEBM_WEBM2PES_H_
|
||||
|
Loading…
x
Reference in New Issue
Block a user