blob: 7684b56bd7836e3e7dfe46b46ff8b7ddebd391ce [file] [log] [blame]
// 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