(77d1794a) Tester's build January 10th, 2020
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
// 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 "m2ts/webm2pes.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/libwebm_util.h"
|
||||
#include "m2ts/vpxpes_parser.h"
|
||||
#include "testing/test_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class Webm2PesTests : public ::testing::Test {
|
||||
public:
|
||||
// Constants for validating known values from input data.
|
||||
const std::uint8_t kMinVideoStreamId = 0xE0;
|
||||
const std::uint8_t kMaxVideoStreamId = 0xEF;
|
||||
const int kPesHeaderSize = 6;
|
||||
const int kPesOptionalHeaderStartOffset = kPesHeaderSize;
|
||||
const int kPesOptionalHeaderSize = 9;
|
||||
const int kPesOptionalHeaderMarkerValue = 0x2;
|
||||
const int kWebm2PesOptHeaderRemainingSize = 6;
|
||||
const int kBcmvHeaderSize = 10;
|
||||
|
||||
Webm2PesTests() = default;
|
||||
~Webm2PesTests() = default;
|
||||
|
||||
void CreateAndLoadTestInput() {
|
||||
libwebm::Webm2Pes converter(input_file_name_, temp_file_name_.name());
|
||||
ASSERT_TRUE(converter.ConvertToFile());
|
||||
ASSERT_TRUE(parser_.Open(pes_file_name()));
|
||||
}
|
||||
|
||||
bool VerifyPacketStartCode(const libwebm::VpxPesParser::PesHeader& header) {
|
||||
// PES packets all start with the byte sequence 0x0 0x0 0x1.
|
||||
if (header.start_code[0] != 0 || header.start_code[1] != 0 ||
|
||||
header.start_code[2] != 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string& pes_file_name() const { return temp_file_name_.name(); }
|
||||
libwebm::VpxPesParser* parser() { return &parser_; }
|
||||
|
||||
private:
|
||||
const libwebm::TempFileDeleter temp_file_name_;
|
||||
const std::string input_file_name_ =
|
||||
test::GetTestFilePath("bbb_480p_vp9_opus_1second.webm");
|
||||
libwebm::VpxPesParser parser_;
|
||||
};
|
||||
|
||||
TEST_F(Webm2PesTests, CreatePesFile) { CreateAndLoadTestInput(); }
|
||||
|
||||
TEST_F(Webm2PesTests, CanParseFirstPacket) {
|
||||
CreateAndLoadTestInput();
|
||||
libwebm::VpxPesParser::PesHeader header;
|
||||
libwebm::VideoFrame frame;
|
||||
ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame));
|
||||
EXPECT_TRUE(VerifyPacketStartCode(header));
|
||||
|
||||
// 9 bytes: PES optional header
|
||||
// 10 bytes: BCMV Header
|
||||
// 83 bytes: frame
|
||||
// 102 bytes total in packet length field:
|
||||
const std::size_t kPesPayloadLength = 102;
|
||||
EXPECT_EQ(kPesPayloadLength, header.packet_length);
|
||||
|
||||
EXPECT_GE(header.stream_id, kMinVideoStreamId);
|
||||
EXPECT_LE(header.stream_id, kMaxVideoStreamId);
|
||||
|
||||
// Test PesOptionalHeader values.
|
||||
EXPECT_EQ(kPesOptionalHeaderMarkerValue, header.opt_header.marker);
|
||||
EXPECT_EQ(kWebm2PesOptHeaderRemainingSize, header.opt_header.remaining_size);
|
||||
EXPECT_EQ(0, header.opt_header.scrambling);
|
||||
EXPECT_EQ(0, header.opt_header.priority);
|
||||
EXPECT_EQ(0, header.opt_header.data_alignment);
|
||||
EXPECT_EQ(0, header.opt_header.copyright);
|
||||
EXPECT_EQ(0, header.opt_header.original);
|
||||
EXPECT_EQ(1, header.opt_header.has_pts);
|
||||
EXPECT_EQ(0, header.opt_header.has_dts);
|
||||
EXPECT_EQ(0, header.opt_header.unused_fields);
|
||||
|
||||
// Test the BCMV header.
|
||||
// Note: The length field of the BCMV header includes its own length.
|
||||
const std::size_t kBcmvBaseLength = 10;
|
||||
const std::size_t kFirstFrameLength = 83;
|
||||
const libwebm::VpxPesParser::BcmvHeader kFirstBcmvHeader(kFirstFrameLength +
|
||||
kBcmvBaseLength);
|
||||
EXPECT_TRUE(header.bcmv_header.Valid());
|
||||
EXPECT_EQ(kFirstBcmvHeader, header.bcmv_header);
|
||||
|
||||
// Parse the next packet to confirm correct parse and consumption of payload.
|
||||
EXPECT_TRUE(parser()->ParseNextPacket(&header, &frame));
|
||||
}
|
||||
|
||||
TEST_F(Webm2PesTests, CanMuxLargeBuffers) {
|
||||
const std::size_t kBufferSize = 100 * 1024;
|
||||
const std::int64_t kFakeTimestamp = libwebm::kNanosecondsPerSecond;
|
||||
libwebm::VideoFrame fake_frame(kFakeTimestamp, libwebm::VideoFrame::kVP9);
|
||||
ASSERT_TRUE(fake_frame.Init(kBufferSize));
|
||||
std::memset(fake_frame.buffer().data.get(), 0x80, kBufferSize);
|
||||
ASSERT_TRUE(fake_frame.SetBufferLength(kBufferSize));
|
||||
libwebm::PacketDataBuffer pes_packet_buffer;
|
||||
ASSERT_TRUE(
|
||||
libwebm::Webm2Pes::WritePesPacket(fake_frame, &pes_packet_buffer));
|
||||
|
||||
// TODO(tomfinegan): Change VpxPesParser so it can read from a buffer, and get
|
||||
// rid of this extra step.
|
||||
libwebm::FilePtr pes_file(std::fopen(pes_file_name().c_str(), "wb"),
|
||||
libwebm::FILEDeleter());
|
||||
ASSERT_EQ(pes_packet_buffer.size(),
|
||||
fwrite(&pes_packet_buffer[0], 1, pes_packet_buffer.size(),
|
||||
pes_file.get()));
|
||||
fclose(pes_file.get());
|
||||
pes_file.release();
|
||||
|
||||
libwebm::VpxPesParser parser;
|
||||
ASSERT_TRUE(parser.Open(pes_file_name()));
|
||||
libwebm::VpxPesParser::PesHeader header;
|
||||
libwebm::VideoFrame parsed_frame;
|
||||
ASSERT_TRUE(parser.ParseNextPacket(&header, &parsed_frame));
|
||||
EXPECT_EQ(fake_frame.nanosecond_pts(), parsed_frame.nanosecond_pts());
|
||||
EXPECT_EQ(fake_frame.buffer().length, parsed_frame.buffer().length);
|
||||
EXPECT_EQ(0, std::memcmp(fake_frame.buffer().data.get(),
|
||||
parsed_frame.buffer().data.get(), kBufferSize));
|
||||
}
|
||||
|
||||
TEST_F(Webm2PesTests, ParserConsumesAllInput) {
|
||||
CreateAndLoadTestInput();
|
||||
libwebm::VpxPesParser::PesHeader header;
|
||||
libwebm::VideoFrame frame;
|
||||
while (parser()->ParseNextPacket(&header, &frame) == true) {
|
||||
EXPECT_TRUE(VerifyPacketStartCode(header));
|
||||
}
|
||||
EXPECT_EQ(0, parser()->BytesAvailable());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -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 "m2ts/vpxpes2ts.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
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 int 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 int 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 & 0xff;
|
||||
|
||||
*++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 = static_cast<int>(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_);
|
||||
int 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 (static_cast<int>(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
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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_M2TS_VPXPES2TS_H_
|
||||
#define LIBWEBM_M2TS_VPXPES2TS_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "common/libwebm_util.h"
|
||||
#include "m2ts/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_M2TS_VPXPES2TS_H_
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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 "m2ts/vpxpes2ts.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
// 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 "vpxpes_parser.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "common/file_util.h"
|
||||
|
||||
namespace libwebm {
|
||||
|
||||
VpxPesParser::BcmvHeader::BcmvHeader(std::uint32_t len) : length(len) {
|
||||
id[0] = 'B';
|
||||
id[1] = 'C';
|
||||
id[2] = 'M';
|
||||
id[3] = 'V';
|
||||
}
|
||||
|
||||
bool VpxPesParser::BcmvHeader::operator==(const BcmvHeader& other) const {
|
||||
return (other.length == length && other.id[0] == id[0] &&
|
||||
other.id[1] == id[1] && other.id[2] == id[2] && other.id[3] == id[3]);
|
||||
}
|
||||
|
||||
bool VpxPesParser::BcmvHeader::Valid() const {
|
||||
return (length > 0 && 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<size_t>(libwebm::GetFileSize(pes_file));
|
||||
if (pes_file_size_ <= 0)
|
||||
return false;
|
||||
pes_file_data_.reserve(static_cast<size_t>(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<std::uint8_t>(byte));
|
||||
}
|
||||
|
||||
if (!feof(file.get()) || ferror(file.get()) ||
|
||||
pes_file_size_ != pes_file_data_.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
read_pos_ = 0;
|
||||
parse_state_ = kFindStartCode;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::VerifyPacketStartCode() const {
|
||||
if (read_pos_ + 2 > pes_file_data_.size())
|
||||
return false;
|
||||
|
||||
// PES packets all start with the byte sequence 0x0 0x0 0x1.
|
||||
if (pes_file_data_[read_pos_] != 0 || pes_file_data_[read_pos_ + 1] != 0 ||
|
||||
pes_file_data_[read_pos_ + 2] != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::ReadStreamId(std::uint8_t* stream_id) const {
|
||||
if (!stream_id || BytesAvailable() < 4)
|
||||
return false;
|
||||
|
||||
*stream_id = pes_file_data_[read_pos_ + 3];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::ReadPacketLength(std::uint16_t* packet_length) const {
|
||||
if (!packet_length || BytesAvailable() < 6)
|
||||
return false;
|
||||
|
||||
// Read and byte swap 16 bit big endian length.
|
||||
*packet_length =
|
||||
(pes_file_data_[read_pos_ + 4] << 8) | pes_file_data_[read_pos_ + 5];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::ParsePesHeader(PesHeader* header) {
|
||||
if (!header || parse_state_ != kParsePesHeader)
|
||||
return false;
|
||||
|
||||
if (!VerifyPacketStartCode())
|
||||
return false;
|
||||
|
||||
std::size_t pos = read_pos_;
|
||||
for (auto& a : header->start_code) {
|
||||
a = pes_file_data_[pos++];
|
||||
}
|
||||
|
||||
// PES Video stream IDs start at E0.
|
||||
if (!ReadStreamId(&header->stream_id))
|
||||
return false;
|
||||
|
||||
if (header->stream_id < kMinVideoStreamId ||
|
||||
header->stream_id > kMaxVideoStreamId)
|
||||
return false;
|
||||
|
||||
if (!ReadPacketLength(&header->packet_length))
|
||||
return false;
|
||||
|
||||
read_pos_ += kPesHeaderSize;
|
||||
parse_state_ = kParsePesOptionalHeader;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(tomfinegan): Make these masks constants.
|
||||
bool VpxPesParser::ParsePesOptionalHeader(PesOptionalHeader* header) {
|
||||
if (!header || parse_state_ != kParsePesOptionalHeader ||
|
||||
read_pos_ >= pes_file_size_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t consumed = 0;
|
||||
PacketData poh_buffer;
|
||||
if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_],
|
||||
kPesOptionalHeaderSize,
|
||||
&poh_buffer, &consumed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t offset = 0;
|
||||
header->marker = (poh_buffer[offset] & 0x80) >> 6;
|
||||
header->scrambling = (poh_buffer[offset] & 0x30) >> 4;
|
||||
header->priority = (poh_buffer[offset] & 0x8) >> 3;
|
||||
header->data_alignment = (poh_buffer[offset] & 0xc) >> 2;
|
||||
header->copyright = (poh_buffer[offset] & 0x2) >> 1;
|
||||
header->original = poh_buffer[offset] & 0x1;
|
||||
offset++;
|
||||
|
||||
header->has_pts = (poh_buffer[offset] & 0x80) >> 7;
|
||||
header->has_dts = (poh_buffer[offset] & 0x40) >> 6;
|
||||
header->unused_fields = poh_buffer[offset] & 0x3f;
|
||||
offset++;
|
||||
|
||||
header->remaining_size = poh_buffer[offset];
|
||||
if (header->remaining_size !=
|
||||
static_cast<int>(kWebm2PesOptHeaderRemainingSize))
|
||||
return false;
|
||||
|
||||
size_t bytes_left = header->remaining_size;
|
||||
offset++;
|
||||
|
||||
if (header->has_pts) {
|
||||
// Read PTS markers. Format:
|
||||
// PTS: 5 bytes
|
||||
// 4 bits (flag: PTS present, but no DTS): 0x2 ('0010')
|
||||
// 36 bits (90khz PTS):
|
||||
// top 3 bits
|
||||
// marker ('1')
|
||||
// middle 15 bits
|
||||
// marker ('1')
|
||||
// bottom 15 bits
|
||||
// marker ('1')
|
||||
// TODO(tomfinegan): read/store the timestamp.
|
||||
header->pts_dts_flag = (poh_buffer[offset] & 0x20) >> 4;
|
||||
// Check the marker bits.
|
||||
if ((poh_buffer[offset + 0] & 1) != 1 ||
|
||||
(poh_buffer[offset + 2] & 1) != 1 ||
|
||||
(poh_buffer[offset + 4] & 1) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
header->pts = (poh_buffer[offset] & 0xe) << 29 |
|
||||
((ReadUint16(&poh_buffer[offset + 1]) & ~1) << 14) |
|
||||
(ReadUint16(&poh_buffer[offset + 3]) >> 1);
|
||||
offset += 5;
|
||||
bytes_left -= 5;
|
||||
}
|
||||
|
||||
// Validate stuffing byte(s).
|
||||
for (size_t i = 0; i < bytes_left; ++i) {
|
||||
if (poh_buffer[offset + i] != 0xff)
|
||||
return false;
|
||||
}
|
||||
|
||||
read_pos_ += consumed;
|
||||
parse_state_ = kParseBcmvHeader;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses and validates a BCMV header.
|
||||
bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) {
|
||||
if (!header || parse_state_ != kParseBcmvHeader)
|
||||
return false;
|
||||
|
||||
PacketData bcmv_buffer;
|
||||
std::size_t consumed = 0;
|
||||
if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_],
|
||||
kBcmvHeaderSize, &bcmv_buffer,
|
||||
&consumed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t offset = 0;
|
||||
header->id[0] = bcmv_buffer[offset++];
|
||||
header->id[1] = bcmv_buffer[offset++];
|
||||
header->id[2] = bcmv_buffer[offset++];
|
||||
header->id[3] = bcmv_buffer[offset++];
|
||||
|
||||
header->length = 0;
|
||||
header->length |= bcmv_buffer[offset++] << 24;
|
||||
header->length |= bcmv_buffer[offset++] << 16;
|
||||
header->length |= bcmv_buffer[offset++] << 8;
|
||||
header->length |= bcmv_buffer[offset++];
|
||||
|
||||
// Length stored in the BCMV header is followed by 2 bytes of 0 padding.
|
||||
if (bcmv_buffer[offset++] != 0 || bcmv_buffer[offset++] != 0)
|
||||
return false;
|
||||
|
||||
if (!header->Valid())
|
||||
return false;
|
||||
|
||||
parse_state_ = kFindStartCode;
|
||||
read_pos_ += consumed;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::FindStartCode(std::size_t origin,
|
||||
std::size_t* offset) const {
|
||||
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 = origin + i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VpxPesParser::IsPayloadFragmented(const PesHeader& header) const {
|
||||
return (header.packet_length != 0 &&
|
||||
(header.packet_length - kPesOptionalHeaderSize) !=
|
||||
header.bcmv_header.length);
|
||||
}
|
||||
|
||||
bool VpxPesParser::AccumulateFragmentedPayload(std::size_t pes_packet_length,
|
||||
std::size_t payload_length) {
|
||||
const std::size_t first_fragment_length =
|
||||
pes_packet_length - kPesOptionalHeaderSize - kBcmvHeaderSize;
|
||||
for (std::size_t i = 0; i < first_fragment_length; ++i) {
|
||||
payload_.push_back(pes_file_data_[read_pos_ + i]);
|
||||
}
|
||||
read_pos_ += first_fragment_length;
|
||||
parse_state_ = kFindStartCode;
|
||||
|
||||
while (payload_.size() < payload_length) {
|
||||
PesHeader header;
|
||||
std::size_t packet_start_pos = read_pos_;
|
||||
if (!FindStartCode(read_pos_, &packet_start_pos)) {
|
||||
return false;
|
||||
}
|
||||
parse_state_ = kParsePesHeader;
|
||||
read_pos_ = packet_start_pos;
|
||||
|
||||
if (!ParsePesHeader(&header)) {
|
||||
return false;
|
||||
}
|
||||
if (!ParsePesOptionalHeader(&header.opt_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::size_t fragment_length =
|
||||
header.packet_length - kPesOptionalHeaderSize;
|
||||
std::size_t consumed = 0;
|
||||
if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_],
|
||||
fragment_length, &payload_,
|
||||
&consumed)) {
|
||||
return false;
|
||||
}
|
||||
read_pos_ += consumed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VpxPesParser::RemoveStartCodeEmulationPreventionBytes(
|
||||
const std::uint8_t* raw_data, std::size_t bytes_required,
|
||||
PacketData* processed_data, std::size_t* bytes_consumed) const {
|
||||
if (bytes_required == 0 || !processed_data)
|
||||
return false;
|
||||
|
||||
std::size_t num_zeros = 0;
|
||||
std::size_t bytes_copied = 0;
|
||||
const std::uint8_t* const end_of_input =
|
||||
&pes_file_data_[0] + pes_file_data_.size();
|
||||
std::size_t i;
|
||||
for (i = 0; bytes_copied < bytes_required; ++i) {
|
||||
if (raw_data + i > end_of_input)
|
||||
return false;
|
||||
|
||||
bool skip = false;
|
||||
|
||||
const std::uint8_t byte = raw_data[i];
|
||||
if (byte == 0) {
|
||||
++num_zeros;
|
||||
} else if (byte == 0x3 && num_zeros == 2) {
|
||||
skip = true;
|
||||
num_zeros = 0;
|
||||
} else {
|
||||
num_zeros = 0;
|
||||
}
|
||||
|
||||
if (skip == false) {
|
||||
processed_data->push_back(byte);
|
||||
++bytes_copied;
|
||||
}
|
||||
}
|
||||
*bytes_consumed = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
int VpxPesParser::BytesAvailable() const {
|
||||
return static_cast<int>(pes_file_data_.size() - read_pos_);
|
||||
}
|
||||
|
||||
bool VpxPesParser::ParseNextPacket(PesHeader* header, VideoFrame* frame) {
|
||||
if (!header || !frame || parse_state_ != kFindStartCode ||
|
||||
BytesAvailable() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t packet_start_pos = read_pos_;
|
||||
if (!FindStartCode(read_pos_, &packet_start_pos)) {
|
||||
return false;
|
||||
}
|
||||
parse_state_ = kParsePesHeader;
|
||||
read_pos_ = packet_start_pos;
|
||||
|
||||
if (!ParsePesHeader(header)) {
|
||||
return false;
|
||||
}
|
||||
if (!ParsePesOptionalHeader(&header->opt_header)) {
|
||||
return false;
|
||||
}
|
||||
if (!ParseBcmvHeader(&header->bcmv_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// BCMV header length includes the length of the BCMVHeader itself. Adjust:
|
||||
const std::size_t payload_length =
|
||||
header->bcmv_header.length - BcmvHeader::size();
|
||||
|
||||
// Make sure there's enough input data to read the entire frame.
|
||||
if (read_pos_ + payload_length > pes_file_data_.size()) {
|
||||
// Need more data.
|
||||
printf("VpxPesParser: Not enough data. Required: %u Available: %u\n",
|
||||
static_cast<unsigned int>(payload_length),
|
||||
static_cast<unsigned int>(pes_file_data_.size() - read_pos_));
|
||||
parse_state_ = kFindStartCode;
|
||||
read_pos_ = packet_start_pos;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsPayloadFragmented(*header)) {
|
||||
if (!AccumulateFragmentedPayload(header->packet_length, payload_length)) {
|
||||
fprintf(stderr, "VpxPesParser: Failed parsing fragmented payload!\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
std::size_t consumed = 0;
|
||||
if (!RemoveStartCodeEmulationPreventionBytes(
|
||||
&pes_file_data_[read_pos_], payload_length, &payload_, &consumed)) {
|
||||
return false;
|
||||
}
|
||||
read_pos_ += consumed;
|
||||
}
|
||||
|
||||
if (frame->buffer().capacity < payload_.size()) {
|
||||
if (frame->Init(payload_.size()) == false) {
|
||||
fprintf(stderr, "VpxPesParser: Out of memory.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
frame->set_nanosecond_pts(Khz90TicksToNanoseconds(header->opt_header.pts));
|
||||
std::memcpy(frame->buffer().data.get(), &payload_[0], payload_.size());
|
||||
frame->SetBufferLength(payload_.size());
|
||||
|
||||
payload_.clear();
|
||||
parse_state_ = kFindStartCode;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace libwebm
|
||||
@@ -0,0 +1,177 @@
|
||||
// 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_M2TS_VPXPES_PARSER_H_
|
||||
#define LIBWEBM_M2TS_VPXPES_PARSER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/libwebm_util.h"
|
||||
#include "common/video_frame.h"
|
||||
|
||||
namespace libwebm {
|
||||
|
||||
// Parser for VPx PES. Requires that the _entire_ PES stream can be stored in
|
||||
// a std::vector<std::uint8_t> and read into memory when Open() is called.
|
||||
// TODO(tomfinegan): Support incremental parse.
|
||||
class VpxPesParser {
|
||||
public:
|
||||
typedef std::vector<std::uint8_t> PesFileData;
|
||||
typedef std::vector<std::uint8_t> PacketData;
|
||||
|
||||
enum ParseState {
|
||||
kFindStartCode,
|
||||
kParsePesHeader,
|
||||
kParsePesOptionalHeader,
|
||||
kParseBcmvHeader,
|
||||
};
|
||||
|
||||
struct PesOptionalHeader {
|
||||
int marker = 0;
|
||||
int scrambling = 0;
|
||||
int priority = 0;
|
||||
int data_alignment = 0;
|
||||
int copyright = 0;
|
||||
int original = 0;
|
||||
int has_pts = 0;
|
||||
int has_dts = 0;
|
||||
int unused_fields = 0;
|
||||
int remaining_size = 0;
|
||||
int pts_dts_flag = 0;
|
||||
std::uint64_t pts = 0;
|
||||
int stuffing_byte = 0;
|
||||
};
|
||||
|
||||
struct BcmvHeader {
|
||||
BcmvHeader() = default;
|
||||
~BcmvHeader() = default;
|
||||
BcmvHeader(const BcmvHeader&) = delete;
|
||||
BcmvHeader(BcmvHeader&&) = delete;
|
||||
|
||||
// Convenience ctor for quick validation of expected values via operator==
|
||||
// after parsing input.
|
||||
explicit BcmvHeader(std::uint32_t len);
|
||||
|
||||
bool operator==(const BcmvHeader& other) const;
|
||||
|
||||
void Reset();
|
||||
bool Valid() const;
|
||||
static std::size_t size() { return 10; }
|
||||
|
||||
char id[4] = {0};
|
||||
std::uint32_t length = 0;
|
||||
};
|
||||
|
||||
struct PesHeader {
|
||||
std::uint8_t start_code[4] = {0};
|
||||
std::uint16_t packet_length = 0;
|
||||
std::uint8_t stream_id = 0;
|
||||
PesOptionalHeader opt_header;
|
||||
BcmvHeader bcmv_header;
|
||||
};
|
||||
|
||||
// Constants for validating known values from input data.
|
||||
const std::uint8_t kMinVideoStreamId = 0xE0;
|
||||
const std::uint8_t kMaxVideoStreamId = 0xEF;
|
||||
const std::size_t kPesHeaderSize = 6;
|
||||
const std::size_t kPesOptionalHeaderStartOffset = kPesHeaderSize;
|
||||
const std::size_t kPesOptionalHeaderSize = 9;
|
||||
const std::size_t kPesOptionalHeaderMarkerValue = 0x2;
|
||||
const std::size_t kWebm2PesOptHeaderRemainingSize = 6;
|
||||
const std::size_t kBcmvHeaderSize = 10;
|
||||
|
||||
VpxPesParser() = default;
|
||||
~VpxPesParser() = default;
|
||||
|
||||
// Opens file specified by |pes_file_path| and reads its contents. Returns
|
||||
// true after successful read of input file.
|
||||
bool Open(const std::string& pes_file_path);
|
||||
|
||||
// Parses the next packet in the PES. PES header information is stored in
|
||||
// |header|, and the frame payload is stored in |frame|. Returns true when
|
||||
// a full frame has been consumed from the PES.
|
||||
bool ParseNextPacket(PesHeader* header, VideoFrame* frame);
|
||||
|
||||
// PES Header parsing utility functions.
|
||||
// PES Header structure:
|
||||
// Start code Stream ID Packet length (16 bits)
|
||||
// / / ____/
|
||||
// | | /
|
||||
// Byte0 Byte1 Byte2 Byte3 Byte4 Byte5
|
||||
// 0 0 1 X Y
|
||||
bool VerifyPacketStartCode() const;
|
||||
bool ReadStreamId(std::uint8_t* stream_id) const;
|
||||
bool ReadPacketLength(std::uint16_t* packet_length) const;
|
||||
|
||||
std::uint64_t pes_file_size() const { return pes_file_size_; }
|
||||
const PesFileData& pes_file_data() const { return pes_file_data_; }
|
||||
|
||||
// Returns number of unparsed bytes remaining.
|
||||
int BytesAvailable() const;
|
||||
|
||||
private:
|
||||
// Parses and verifies the static 6 byte portion that begins every PES packet.
|
||||
bool ParsePesHeader(PesHeader* header);
|
||||
|
||||
// Parses a PES optional header, the optional header following the static
|
||||
// header that begins the VPX PES packet.
|
||||
// https://en.wikipedia.org/wiki/Packetized_elementary_stream
|
||||
bool ParsePesOptionalHeader(PesOptionalHeader* header);
|
||||
|
||||
// Parses and validates the BCMV header. This immediately follows the optional
|
||||
// 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) const;
|
||||
|
||||
// Returns true when a PES packet containing a BCMV header contains only a
|
||||
// portion of the frame payload length reported by the BCMV header.
|
||||
bool IsPayloadFragmented(const PesHeader& header) const;
|
||||
|
||||
// Parses PES and PES Optional header while accumulating payload data in
|
||||
// |payload_|.
|
||||
// Returns true once all payload fragments have been stored in |payload_|.
|
||||
// Returns false if unable to accumulate full payload.
|
||||
bool AccumulateFragmentedPayload(std::size_t pes_packet_length,
|
||||
std::size_t payload_length);
|
||||
|
||||
// The byte sequence 0x0 0x0 0x1 is a start code in PES. When PES muxers
|
||||
// encounter 0x0 0x0 0x1 or 0x0 0x0 0x3, an additional 0x3 is inserted into
|
||||
// the PES. The following change occurs:
|
||||
// 0x0 0x0 0x1 => 0x0 0x0 0x3 0x1
|
||||
// 0x0 0x0 0x3 => 0x0 0x0 0x3 0x3
|
||||
// PES demuxers must reverse the change:
|
||||
// 0x0 0x0 0x3 0x1 => 0x0 0x0 0x1
|
||||
// 0x0 0x0 0x3 0x3 => 0x0 0x0 0x3
|
||||
// PES optional header, BCMV header, and payload data must be preprocessed to
|
||||
// avoid potentially invalid data due to the presence of inserted bytes.
|
||||
//
|
||||
// Removes start code emulation prevention bytes while copying data from
|
||||
// |raw_data| to |processed_data|. Returns true when |bytes_required| bytes
|
||||
// have been written to |processed_data|. Reports bytes consumed during the
|
||||
// operation via |bytes_consumed|.
|
||||
bool RemoveStartCodeEmulationPreventionBytes(
|
||||
const std::uint8_t* raw_data, std::size_t bytes_required,
|
||||
PacketData* processed_data, std::size_t* bytes_consumed) const;
|
||||
|
||||
std::size_t pes_file_size_ = 0;
|
||||
PacketData payload_;
|
||||
PesFileData pes_file_data_;
|
||||
std::size_t read_pos_ = 0;
|
||||
ParseState parse_state_ = kFindStartCode;
|
||||
};
|
||||
|
||||
} // namespace libwebm
|
||||
|
||||
#endif // LIBWEBM_M2TS_VPXPES_PARSER_H_
|
||||
@@ -0,0 +1,551 @@
|
||||
// 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.
|
||||
#include "m2ts/webm2pes.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
|
||||
#include "common/libwebm_util.h"
|
||||
|
||||
namespace libwebm {
|
||||
|
||||
const std::size_t Webm2Pes::kMaxPayloadSize = 32768;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ToString(const char* str) {
|
||||
return std::string((str == nullptr) ? "" : str);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
//
|
||||
// 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 |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) {
|
||||
std::fprintf(stderr, "Webm2Pes: nullptr in opt header writer.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const int kHeaderSize = 9;
|
||||
std::uint8_t header[kHeaderSize] = {0};
|
||||
std::uint8_t* byte = header;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO(tomfinegan): As noted in above, the PesHeaderFields should be an
|
||||
// array (or some data structure) that can be iterated over.
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// Third byte of header, fields: remaining size of header.
|
||||
*++byte = remaining_size.bits & 0xff; // Field is 8 bits wide.
|
||||
|
||||
int num_stuffing_bytes =
|
||||
(pts.num_bits + 7) / 8 + 1 /* always 1 stuffing byte */;
|
||||
if (write_pts == true) {
|
||||
// Write the PTS value as big endian and adjust stuffing byte count
|
||||
// accordingly.
|
||||
*++byte = pts.bits & 0xff;
|
||||
*++byte = (pts.bits >> 8) & 0xff;
|
||||
*++byte = (pts.bits >> 16) & 0xff;
|
||||
*++byte = (pts.bits >> 24) & 0xff;
|
||||
*++byte = (pts.bits >> 32) & 0xff;
|
||||
num_stuffing_bytes = 1;
|
||||
}
|
||||
|
||||
// Add the stuffing byte(s).
|
||||
for (int i = 0; i < num_stuffing_bytes; ++i)
|
||||
*++byte = stuffing_byte.bits & 0xff;
|
||||
|
||||
return CopyAndEscapeStartCodes(&header[0], kHeaderSize, buffer);
|
||||
}
|
||||
|
||||
//
|
||||
// BCMVHeader methods.
|
||||
//
|
||||
|
||||
bool BCMVHeader::Write(PacketDataBuffer* buffer) const {
|
||||
if (buffer == nullptr) {
|
||||
std::fprintf(stderr, "Webm2Pes: nullptr for buffer in BCMV Write.\n");
|
||||
return false;
|
||||
}
|
||||
const int kBcmvSize = 4;
|
||||
for (int i = 0; i < kBcmvSize; ++i)
|
||||
buffer->push_back(bcmv[i]);
|
||||
|
||||
// Note: The 4 byte length field must include the size of the BCMV header.
|
||||
const int kRemainingBytes = 6;
|
||||
const uint32_t bcmv_total_length = length + static_cast<uint32_t>(size());
|
||||
const uint8_t bcmv_buffer[kRemainingBytes] = {
|
||||
static_cast<std::uint8_t>((bcmv_total_length >> 24) & 0xff),
|
||||
static_cast<std::uint8_t>((bcmv_total_length >> 16) & 0xff),
|
||||
static_cast<std::uint8_t>((bcmv_total_length >> 8) & 0xff),
|
||||
static_cast<std::uint8_t>(bcmv_total_length & 0xff),
|
||||
0,
|
||||
0 /* 2 bytes 0 padding */};
|
||||
|
||||
return CopyAndEscapeStartCodes(bcmv_buffer, kRemainingBytes, buffer);
|
||||
}
|
||||
|
||||
//
|
||||
// PesHeader methods.
|
||||
//
|
||||
|
||||
// Writes out the header to |buffer|. Calls PesOptionalHeader::Write() to write
|
||||
// |optional_header| contents. Returns true when successful, false otherwise.
|
||||
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|.
|
||||
const int kStartCodeLength = 4;
|
||||
for (int i = 0; i < kStartCodeLength; ++i)
|
||||
buffer->push_back(start_code[i]);
|
||||
|
||||
// The length field here reports number of bytes following the field. The
|
||||
// length of the optional header must be added to the payload length set by
|
||||
// the user.
|
||||
const std::size_t header_length =
|
||||
packet_length + optional_header.size_in_bytes();
|
||||
if (header_length > UINT16_MAX)
|
||||
return false;
|
||||
|
||||
// Write |header_length| as big endian.
|
||||
std::uint8_t byte = (header_length >> 8) & 0xff;
|
||||
buffer->push_back(byte);
|
||||
byte = header_length & 0xff;
|
||||
buffer->push_back(byte);
|
||||
|
||||
// Write the (not really) optional header.
|
||||
if (optional_header.Write(write_pts, buffer) != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: PES optional header write failed.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Webm2Pes methods.
|
||||
//
|
||||
|
||||
bool Webm2Pes::ConvertToFile() {
|
||||
if (input_file_name_.empty() || output_file_name_.empty()) {
|
||||
std::fprintf(stderr, "Webm2Pes: input and/or output file name(s) empty.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
output_file_ = FilePtr(fopen(output_file_name_.c_str(), "wb"), FILEDeleter());
|
||||
if (output_file_ == nullptr) {
|
||||
std::fprintf(stderr, "Webm2Pes: Cannot open %s for output.\n",
|
||||
output_file_name_.c_str());
|
||||
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) {
|
||||
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& mkvparser_frame =
|
||||
block->GetFrame(frame_num);
|
||||
|
||||
// Read the frame.
|
||||
VideoFrame vpx_frame(block->GetTime(cluster), codec_);
|
||||
if (ReadVideoFrame(mkvparser_frame, &vpx_frame) == false) {
|
||||
fprintf(stderr, "Webm2Pes: frame read failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write frame out as PES packet(s).
|
||||
if (WritePesPacket(vpx_frame, &packet_data_) == false) {
|
||||
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;
|
||||
}
|
||||
bytes_written_ += packet_data_.size();
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
std::fflush(output_file_.get());
|
||||
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) {
|
||||
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& mkvparser_frame =
|
||||
block->GetFrame(frame_num);
|
||||
|
||||
// Read the frame.
|
||||
VideoFrame frame(block->GetTime(cluster), codec_);
|
||||
if (ReadVideoFrame(mkvparser_frame, &frame) == false) {
|
||||
fprintf(stderr, "Webm2Pes: frame read failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write frame out as PES packet(s).
|
||||
if (WritePesPacket(frame, &packet_data_) == false) {
|
||||
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;
|
||||
}
|
||||
bytes_written_ += packet_data_.size();
|
||||
}
|
||||
}
|
||||
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::InitWebmParser() {
|
||||
if (webm_reader_.Open(input_file_name_.c_str()) != 0) {
|
||||
std::fprintf(stderr, "Webm2Pes: Cannot open %s as input.\n",
|
||||
input_file_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
using mkvparser::Segment;
|
||||
Segment* webm_parser = nullptr;
|
||||
if (Segment::CreateInstance(&webm_reader_, 0 /* pos */,
|
||||
webm_parser /* Segment*& */) != 0) {
|
||||
std::fprintf(stderr, "Webm2Pes: Cannot create WebM parser.\n");
|
||||
return false;
|
||||
}
|
||||
webm_parser_.reset(webm_parser);
|
||||
|
||||
if (webm_parser_->Load() != 0) {
|
||||
std::fprintf(stderr, "Webm2Pes: Cannot parse %s.\n",
|
||||
input_file_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure there's a video track.
|
||||
const mkvparser::Tracks* tracks = webm_parser_->GetTracks();
|
||||
if (tracks == nullptr) {
|
||||
std::fprintf(stderr, "Webm2Pes: %s has no tracks.\n",
|
||||
input_file_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
timecode_scale_ = webm_parser_->GetInfo()->GetTimeCodeScale();
|
||||
|
||||
for (int track_index = 0;
|
||||
track_index < static_cast<int>(tracks->GetTracksCount());
|
||||
++track_index) {
|
||||
const mkvparser::Track* track = tracks->GetTrackByIndex(track_index);
|
||||
if (track && track->GetType() == mkvparser::Track::kVideo) {
|
||||
const std::string codec_id = ToString(track->GetCodecId());
|
||||
if (codec_id == std::string("V_VP8")) {
|
||||
codec_ = VideoFrame::kVP8;
|
||||
} else if (codec_id == std::string("V_VP9")) {
|
||||
codec_ = VideoFrame::kVP9;
|
||||
} else {
|
||||
fprintf(stderr, "Webm2Pes: Codec must be VP8 or VP9.\n");
|
||||
return false;
|
||||
}
|
||||
video_track_num_ = track_index + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (video_track_num_ < 1) {
|
||||
std::fprintf(stderr, "Webm2Pes: No video track found in %s.\n",
|
||||
input_file_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Webm2Pes::ReadVideoFrame(const mkvparser::Block::Frame& mkvparser_frame,
|
||||
VideoFrame* frame) {
|
||||
if (mkvparser_frame.len < 1 || frame == nullptr)
|
||||
return false;
|
||||
|
||||
const std::size_t mkv_len = static_cast<std::size_t>(mkvparser_frame.len);
|
||||
if (mkv_len > frame->buffer().capacity) {
|
||||
const std::size_t new_size = 2 * mkv_len;
|
||||
if (frame->Init(new_size) == false) {
|
||||
std::fprintf(stderr, "Webm2Pes: Out of memory.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (mkvparser_frame.Read(&webm_reader_, frame->buffer().data.get()) != 0) {
|
||||
std::fprintf(stderr, "Webm2Pes: Error reading VPx frame!\n");
|
||||
return false;
|
||||
}
|
||||
return frame->SetBufferLength(mkv_len);
|
||||
}
|
||||
|
||||
bool Webm2Pes::WritePesPacket(const VideoFrame& frame,
|
||||
PacketDataBuffer* packet_data) {
|
||||
if (frame.buffer().data.get() == nullptr || frame.buffer().length < 1)
|
||||
return false;
|
||||
|
||||
Ranges frame_ranges;
|
||||
if (frame.codec() == VideoFrame::kVP9) {
|
||||
bool error = false;
|
||||
const bool has_superframe_index =
|
||||
ParseVP9SuperFrameIndex(frame.buffer().data.get(),
|
||||
frame.buffer().length, &frame_ranges, &error);
|
||||
if (error) {
|
||||
std::fprintf(stderr, "Webm2Pes: Superframe index parse failed.\n");
|
||||
return false;
|
||||
}
|
||||
if (has_superframe_index == false) {
|
||||
frame_ranges.push_back(Range(0, frame.buffer().length));
|
||||
}
|
||||
} else {
|
||||
frame_ranges.push_back(Range(0, frame.buffer().length));
|
||||
}
|
||||
|
||||
const std::int64_t khz90_pts =
|
||||
NanosecondsTo90KhzTicks(frame.nanosecond_pts());
|
||||
PesHeader header;
|
||||
header.optional_header.SetPtsBits(khz90_pts);
|
||||
|
||||
packet_data->clear();
|
||||
|
||||
for (const Range& packet_payload_range : frame_ranges) {
|
||||
std::size_t extra_bytes = 0;
|
||||
if (packet_payload_range.length > kMaxPayloadSize) {
|
||||
extra_bytes = packet_payload_range.length - kMaxPayloadSize;
|
||||
}
|
||||
if (packet_payload_range.length + packet_payload_range.offset >
|
||||
frame.buffer().length) {
|
||||
std::fprintf(stderr, "Webm2Pes: Invalid frame length.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// First packet of new frame. Always include PTS and BCMV header.
|
||||
header.packet_length =
|
||||
packet_payload_range.length - extra_bytes + BCMVHeader::size();
|
||||
if (header.Write(true, packet_data) != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: packet header write failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
BCMVHeader bcmv_header(static_cast<uint32_t>(packet_payload_range.length));
|
||||
if (bcmv_header.Write(packet_data) != true) {
|
||||
std::fprintf(stderr, "Webm2Pes: BCMV write failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert the payload at the end of |packet_data|.
|
||||
const std::uint8_t* const payload_start =
|
||||
frame.buffer().data.get() + packet_payload_range.offset;
|
||||
|
||||
const std::size_t bytes_to_copy = packet_payload_range.length - extra_bytes;
|
||||
if (CopyAndEscapeStartCodes(payload_start, bytes_to_copy, packet_data) ==
|
||||
false) {
|
||||
fprintf(stderr, "Webm2Pes: Payload write failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t bytes_copied = bytes_to_copy;
|
||||
while (extra_bytes) {
|
||||
// Write PES packets for the remaining data, but omit the PTS and BCMV
|
||||
// header.
|
||||
const std::size_t extra_bytes_to_copy =
|
||||
std::min(kMaxPayloadSize, extra_bytes);
|
||||
extra_bytes -= extra_bytes_to_copy;
|
||||
header.packet_length = extra_bytes_to_copy;
|
||||
if (header.Write(false, packet_data) != true) {
|
||||
fprintf(stderr, "Webm2pes: fragment write failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint8_t* fragment_start = payload_start + bytes_copied;
|
||||
if (CopyAndEscapeStartCodes(fragment_start, extra_bytes_to_copy,
|
||||
packet_data) == false) {
|
||||
fprintf(stderr, "Webm2Pes: Payload write failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes_copied += extra_bytes_to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input,
|
||||
std::size_t raw_input_length,
|
||||
PacketDataBuffer* packet_buffer) {
|
||||
if (raw_input == nullptr || raw_input_length < 1 || packet_buffer == nullptr)
|
||||
return false;
|
||||
|
||||
int num_zeros = 0;
|
||||
for (std::size_t 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
|
||||
@@ -0,0 +1,274 @@
|
||||
// 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_M2TS_WEBM2PES_H_
|
||||
#define LIBWEBM_M2TS_WEBM2PES_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/libwebm_util.h"
|
||||
#include "common/video_frame.h"
|
||||
#include "mkvparser/mkvparser.h"
|
||||
#include "mkvparser/mkvreader.h"
|
||||
|
||||
// Webm2pes
|
||||
//
|
||||
// Webm2pes consumes a WebM file containing a VP8 or VP9 video stream and
|
||||
// outputs a PES stream suitable for inclusion in a MPEG2 Transport Stream.
|
||||
//
|
||||
// In the simplest case the PES stream output by Webm2pes consists of a sequence
|
||||
// of PES packets with the following structure:
|
||||
// | PES Header w/PTS | BCMV Header | Payload (VPx frame) |
|
||||
//
|
||||
// More typically the output will look like the following due to the PES
|
||||
// payload size limitations caused by the format of the PES header.
|
||||
// The PES header contains only 2 bytes of storage for expressing payload size.
|
||||
// VPx PES streams containing fragmented packets look like this:
|
||||
//
|
||||
// | PH PTS | BCMV | Payload fragment 1 | PH | Payload fragment 2 | ...
|
||||
//
|
||||
// PH = PES Header
|
||||
// PH PTS = PES Header with PTS
|
||||
// BCMV = BCMV Header
|
||||
//
|
||||
// Note that start codes are properly escaped by Webm2pes, and start code
|
||||
// emulation prevention bytes must be stripped from the output stream before
|
||||
// it can be parsed.
|
||||
|
||||
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 int 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 int 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 {
|
||||
// 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 |buffer| and returns true. Returns false when write or
|
||||
// field value validation fails.
|
||||
bool Write(bool write_pts, PacketDataBuffer* buffer) 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 |buffer|. Caller responsible for ensuring
|
||||
// destination buffer is of size >= BCMVHeader::size().
|
||||
bool Write(PacketDataBuffer* buffer) const;
|
||||
bool Write(uint8_t* buffer);
|
||||
};
|
||||
|
||||
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() +
|
||||
6 /* start_code + packet_length */ + packet_length;
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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:
|
||||
static const std::size_t kMaxPayloadSize;
|
||||
|
||||
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;
|
||||
Webm2Pes(Webm2Pes&&) = delete;
|
||||
~Webm2Pes() = default;
|
||||
|
||||
// Converts the VPx video stream to a PES file and returns true. Returns false
|
||||
// to report failure.
|
||||
bool ConvertToFile();
|
||||
|
||||
// Converts the VPx video stream to a sequence of PES packets, and calls the
|
||||
// PacketReceiverInterface::ReceivePacket() once for each VPx frame. The
|
||||
// packet sent to the receiver may contain multiple PES packets. Returns only
|
||||
// after full conversion or error. Returns true for success, and false when
|
||||
// an error occurs.
|
||||
bool ConvertToPacketReceiver();
|
||||
|
||||
// Writes |vpx_frame| out as PES packet[s] and stores output in |packet_data|.
|
||||
// Returns true for success, false for failure.
|
||||
static bool WritePesPacket(const VideoFrame& frame,
|
||||
PacketDataBuffer* packet_data);
|
||||
|
||||
uint64_t bytes_written() const { return bytes_written_; }
|
||||
|
||||
private:
|
||||
bool InitWebmParser();
|
||||
bool ReadVideoFrame(const mkvparser::Block::Frame& mkvparser_frame,
|
||||
VideoFrame* frame);
|
||||
|
||||
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;
|
||||
|
||||
// Video codec reported by CodecName from Video TrackEntry.
|
||||
VideoFrame::Codec codec_;
|
||||
|
||||
// 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_;
|
||||
|
||||
std::uint64_t bytes_written_ = 0;
|
||||
};
|
||||
|
||||
// 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,
|
||||
std::size_t raw_input_length,
|
||||
PacketDataBuffer* packet_buffer);
|
||||
} // namespace libwebm
|
||||
|
||||
#endif // LIBWEBM_M2TS_WEBM2PES_H_
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
#include "m2ts/webm2pes.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#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::Webm2Pes converter(input_path, output_path);
|
||||
return converter.ConvertToFile() == true ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Reference in New Issue
Block a user