blob: f510acfd5f3b2bc8be9d476ac1f0c369db731057 [file] [log] [blame]
// 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;
//
// 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) {
if (std::string(track->GetCodecId()) == std::string("V_VP8")) {
codec_ = VideoFrame::kVP8;
} else if (std::string(track->GetCodecId()) == 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