(77d1794a) Tester's build January 10th, 2020

This commit is contained in:
juanjp600
2020-01-10 14:42:38 -03:00
parent c02da46ef5
commit e6a08d715b
4529 changed files with 1145046 additions and 218950 deletions
@@ -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;
}