James Zern 68833c7f85 third_party: roll libwebm snapshot
git log --no-merges 32d5ac4..9732ae9
9732ae9 EbmlElementSize: quiet uint64->int32 conv warning
da04eba SetProjectionPrivate: quiet uint64->size_t conv warning
6db32d5 mkvparser,Projection::Parse: fix int->bool conv
3bb0dfa cosmetics: fix a couple lint warnings
0e179d6 update .clang-format
fc5f88d Fix temp files being left on system.
c04a134 Add support for overriding PixelWidth and PixelHeight.
c0160e0 Add support to explicitly set segment duration.
02bc809 Add support to estimate file duration.
c97e3e7 Add support to output sub-sample encryption information.
26f4344 MakeUID: quiet unused param warning in Android builds
d6af52a Change check to fix compile error.
1720020 webm_parser: Add Mesh value for ProjectionType
78f2c5a webm_parser: Use ./ prefix for includes
da62f65 webm_parser: Remove webm/ prefix from public includes
e15e8f2 webm_parser: Update README build instructions
5023f2b mkvmuxer: Fix Colour::Valid()
cf16204 mkvmuxer_tests: Actually test cue points in the cue point test.
93e9fb3 Validate Colour element values.
8036925 mkvparser_tests: Add Projection element test.
f52d38c mkvparser_tests: Add Colour element test.
826436a mkvparser: minor SeekHead::Entry clean up.
24fb44a mkvmuxer_tests: Add Projection element test.
1e0a8ea mkvmuxer_tests: Add Colour element test.
0278616 mkvmuxer: Colour accessors/mutators.
2346f8f Add mkvparser wrapper functions.
54d6b6b webm_info: Add Projection element support.
65fee06 mkvmuxer_sample: Add support for Projection element.
9a3f2b5 mkvparser_sample: Add support for Projection element.
41e814a mkvparser: Add Projection element support.
483a0ff mkvmuxer: Add Projection element support.
676a713 Add support for the Projection element
725f362 mkvmuxer: Fix memory leak when Colour is set multiple times.
fa182de mkvparser_sample: Add output of audio track codec private size.
8f521f2 mkvparser_tests: Add invalid BlockGroup test.
39137d7 Remove docs saying binary elements default to 0
80685d3 Do not skip over unknown elements at the root level
c147504 Fix legacy Makefile.
58711e8 mkvparser_sample: Fix version info string.
837746f mkvparser_tests: Add invalid block test.
207cd80 Disambiguate sample sources and targets.
a112d71 mkvparser_tests: Refactor invalid file loading code.
5dea33e Disambiguate test source and target names.
125049e parser_tests: Add another truncated chapter string test.
1de8d4c parser_tests: Add truncated chapter string test.
ff8c2b6 parser_tests: Move cue validation to test_util.
4b0690f parser_tests: Add invalid lacing test.
9828e39 mkvmuxer: Set default doc type version to 4.
5495a59 webm_parser: Reference more files in CMakeLists.txt.
0c0ecd0 vpxpes_parser: Add start code emulation prevention support.
639a4bc webm2pes: Remove debug printfs().
9a51102 webm2pes: fflush() in the correct conversion function.
dc7f155 webm2pes: Track total bytes written.
d518128 webm_parser: Enable usage of werror.
e1fe762 webm2pes: Add test for mux/demux of large input.
1b24a79 vpxpes_parser: Read and store PTS when present.
6cf0a0f vpxpes_parser: Store frame payloads.
25d2602 webm_parser: Convert style to match the rest of libwebm
24be76d webm2pes: Replace VpxFrame with VideoFrame.
b451c3b Add a basic video frame storage class.
05c90eb libwebm_util: Clarify error text in superframe parser.
e6415af webm2pes: Make WritePesPacket() a public method.
8f840dd webm2pes: Move frame read out of PES packet write method.
448af97 webm2pes: Restore frame fragmentation support.
f8bb714 cmake: Integrate new parsing API and tests.
cb8ce0b Add a new incremental parsing API
900d322 vpxpes_parser/webm2pes: BCMV and PTS fixes.
4b73545 webm2pes: Add start code emulation prevention.
82903f3 Add column tiles and frame parallel to webm_info
5d91edf style_clean_up: Remove unnecessary parentheses
a95aa4b vp9_level_stats: correct total_uncompressed_bits_ calculation
f46566f mkvreader: Fix shorten-64-to-32 warning in 32 bit builds.
76630ca mkvwriter: Fix shorten-64-to-32 warning in 32 bit builds.
a8ffbd4 webm2pes: Fix format specifier warnings.
faf89d4 Add MaxLumaSampleRate grace percent to stats.
d31e6c9 Fix profile 2 in vp9_header_parser.
bd3ab3a Add flag to estimate last frame's duration to stats.
c182ed9 Fix lint issue in hdr_util.h
cc62ecd Add test for Cluster memory leak
196708a Change MaxLumaSampleRate to be based on frame resolution.
cbd676b mkvmuxer: Fix leak when a Cluster isn't finalized
9a235e0 mkvmuxer: Set doctype to matroska when muxing non-WebM codecs.
47f2843 Add parsing support for new features in CodecPrivate.
e3c9576 Add VP9 level output to webm_info.
5cf549f cmake: Log compiler flag at check time.
bbaaf2d Add class to gather VP9 level stats.
8bb68c2 Add file to parse data from VP9 frames.
296429a Add support to parse VP9 profile.
df3412f Add support for setting VP9 profile and level to sample_muxer.
87832d4 mkvmuxer: Fix Segment::Finalize in kLive mode
6df3e56 mkvmuxerutil.hpp: Add using directives for overloaded size utils.
ec47928 mkvmuxerutil: Revert to using mkvmuxertypes.
a1dc4f2 Fix parsing of VP9 level.
4e3d037 Add support to output Colour elements to webm_info.
d3656fd muxer_tests: ignore iwyu re gtest-message.h
e76dd5e Fix file name in mkvmuxertypes shim.
1be5889 Add temporary include shims at old file locations.
039df94 Add TEST_TMPDIR environment variable

Change-Id: I84bc1401b0aad71ad6727b687f1bede9953a7a08
2016-10-18 18:11:36 -07:00

4165 lines
117 KiB
C++

// Copyright (c) 2012 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 "mkvmuxer/mkvmuxer.h"
#include <cfloat>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <memory>
#include <new>
#include <string>
#include <vector>
#include "common/webmids.h"
#include "mkvmuxer/mkvmuxerutil.h"
#include "mkvmuxer/mkvwriter.h"
#include "mkvparser/mkvparser.h"
namespace mkvmuxer {
const float PrimaryChromaticity::kChromaticityMin = 0.0f;
const float PrimaryChromaticity::kChromaticityMax = 1.0f;
const float MasteringMetadata::kMinLuminance = 0.0f;
const float MasteringMetadata::kMinLuminanceMax = 999.99f;
const float MasteringMetadata::kMaxLuminanceMax = 9999.99f;
const float MasteringMetadata::kValueNotPresent = FLT_MAX;
const uint64_t Colour::kValueNotPresent = UINT64_MAX;
namespace {
const char kDocTypeWebm[] = "webm";
const char kDocTypeMatroska[] = "matroska";
// Deallocate the string designated by |dst|, and then copy the |src|
// string to |dst|. The caller owns both the |src| string and the
// |dst| copy (hence the caller is responsible for eventually
// deallocating the strings, either directly, or indirectly via
// StrCpy). Returns true if the source string was successfully copied
// to the destination.
bool StrCpy(const char* src, char** dst_ptr) {
if (dst_ptr == NULL)
return false;
char*& dst = *dst_ptr;
delete[] dst;
dst = NULL;
if (src == NULL)
return true;
const size_t size = strlen(src) + 1;
dst = new (std::nothrow) char[size]; // NOLINT
if (dst == NULL)
return false;
strcpy(dst, src); // NOLINT
return true;
}
typedef std::auto_ptr<PrimaryChromaticity> PrimaryChromaticityPtr;
bool CopyChromaticity(const PrimaryChromaticity* src,
PrimaryChromaticityPtr* dst) {
if (!dst)
return false;
dst->reset(new (std::nothrow) PrimaryChromaticity(src->x(), src->y()));
if (!dst->get())
return false;
return true;
}
} // namespace
///////////////////////////////////////////////////////////////
//
// IMkvWriter Class
IMkvWriter::IMkvWriter() {}
IMkvWriter::~IMkvWriter() {}
bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version,
const char* const doc_type) {
// Level 0
uint64_t size =
EbmlElementSize(libwebm::kMkvEBMLVersion, static_cast<uint64>(1));
size += EbmlElementSize(libwebm::kMkvEBMLReadVersion, static_cast<uint64>(1));
size += EbmlElementSize(libwebm::kMkvEBMLMaxIDLength, static_cast<uint64>(4));
size +=
EbmlElementSize(libwebm::kMkvEBMLMaxSizeLength, static_cast<uint64>(8));
size += EbmlElementSize(libwebm::kMkvDocType, doc_type);
size += EbmlElementSize(libwebm::kMkvDocTypeVersion,
static_cast<uint64>(doc_type_version));
size +=
EbmlElementSize(libwebm::kMkvDocTypeReadVersion, static_cast<uint64>(2));
if (!WriteEbmlMasterElement(writer, libwebm::kMkvEBML, size))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvEBMLVersion,
static_cast<uint64>(1))) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvEBMLReadVersion,
static_cast<uint64>(1))) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvEBMLMaxIDLength,
static_cast<uint64>(4))) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvEBMLMaxSizeLength,
static_cast<uint64>(8))) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvDocType, doc_type))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvDocTypeVersion,
static_cast<uint64>(doc_type_version))) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvDocTypeReadVersion,
static_cast<uint64>(2))) {
return false;
}
return true;
}
bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version) {
return WriteEbmlHeader(writer, doc_type_version, kDocTypeWebm);
}
bool WriteEbmlHeader(IMkvWriter* writer) {
return WriteEbmlHeader(writer, mkvmuxer::Segment::kDefaultDocTypeVersion);
}
bool ChunkedCopy(mkvparser::IMkvReader* source, mkvmuxer::IMkvWriter* dst,
int64_t start, int64_t size) {
// TODO(vigneshv): Check if this is a reasonable value.
const uint32_t kBufSize = 2048;
uint8_t* buf = new uint8_t[kBufSize];
int64_t offset = start;
while (size > 0) {
const int64_t read_len = (size > kBufSize) ? kBufSize : size;
if (source->Read(offset, static_cast<long>(read_len), buf))
return false;
dst->Write(buf, static_cast<uint32_t>(read_len));
offset += read_len;
size -= read_len;
}
delete[] buf;
return true;
}
///////////////////////////////////////////////////////////////
//
// Frame Class
Frame::Frame()
: add_id_(0),
additional_(NULL),
additional_length_(0),
duration_(0),
duration_set_(false),
frame_(NULL),
is_key_(false),
length_(0),
track_number_(0),
timestamp_(0),
discard_padding_(0),
reference_block_timestamp_(0),
reference_block_timestamp_set_(false) {}
Frame::~Frame() {
delete[] frame_;
delete[] additional_;
}
bool Frame::CopyFrom(const Frame& frame) {
delete[] frame_;
frame_ = NULL;
length_ = 0;
if (frame.length() > 0 && frame.frame() != NULL &&
!Init(frame.frame(), frame.length())) {
return false;
}
add_id_ = 0;
delete[] additional_;
additional_ = NULL;
additional_length_ = 0;
if (frame.additional_length() > 0 && frame.additional() != NULL &&
!AddAdditionalData(frame.additional(), frame.additional_length(),
frame.add_id())) {
return false;
}
duration_ = frame.duration();
duration_set_ = frame.duration_set();
is_key_ = frame.is_key();
track_number_ = frame.track_number();
timestamp_ = frame.timestamp();
discard_padding_ = frame.discard_padding();
reference_block_timestamp_ = frame.reference_block_timestamp();
reference_block_timestamp_set_ = frame.reference_block_timestamp_set();
return true;
}
bool Frame::Init(const uint8_t* frame, uint64_t length) {
uint8_t* const data =
new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT
if (!data)
return false;
delete[] frame_;
frame_ = data;
length_ = length;
memcpy(frame_, frame, static_cast<size_t>(length_));
return true;
}
bool Frame::AddAdditionalData(const uint8_t* additional, uint64_t length,
uint64_t add_id) {
uint8_t* const data =
new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT
if (!data)
return false;
delete[] additional_;
additional_ = data;
additional_length_ = length;
add_id_ = add_id;
memcpy(additional_, additional, static_cast<size_t>(additional_length_));
return true;
}
bool Frame::IsValid() const {
if (length_ == 0 || !frame_) {
return false;
}
if ((additional_length_ != 0 && !additional_) ||
(additional_ != NULL && additional_length_ == 0)) {
return false;
}
if (track_number_ == 0 || track_number_ > kMaxTrackNumber) {
return false;
}
if (!CanBeSimpleBlock() && !is_key_ && !reference_block_timestamp_set_) {
return false;
}
return true;
}
bool Frame::CanBeSimpleBlock() const {
return additional_ == NULL && discard_padding_ == 0 && duration_ == 0;
}
void Frame::set_duration(uint64_t duration) {
duration_ = duration;
duration_set_ = true;
}
void Frame::set_reference_block_timestamp(int64_t reference_block_timestamp) {
reference_block_timestamp_ = reference_block_timestamp;
reference_block_timestamp_set_ = true;
}
///////////////////////////////////////////////////////////////
//
// CuePoint Class
CuePoint::CuePoint()
: time_(0),
track_(0),
cluster_pos_(0),
block_number_(1),
output_block_number_(true) {}
CuePoint::~CuePoint() {}
bool CuePoint::Write(IMkvWriter* writer) const {
if (!writer || track_ < 1 || cluster_pos_ < 1)
return false;
uint64_t size = EbmlElementSize(libwebm::kMkvCueClusterPosition,
static_cast<uint64>(cluster_pos_));
size += EbmlElementSize(libwebm::kMkvCueTrack, static_cast<uint64>(track_));
if (output_block_number_ && block_number_ > 1)
size += EbmlElementSize(libwebm::kMkvCueBlockNumber,
static_cast<uint64>(block_number_));
const uint64_t track_pos_size =
EbmlMasterElementSize(libwebm::kMkvCueTrackPositions, size) + size;
const uint64_t payload_size =
EbmlElementSize(libwebm::kMkvCueTime, static_cast<uint64>(time_)) +
track_pos_size;
if (!WriteEbmlMasterElement(writer, libwebm::kMkvCuePoint, payload_size))
return false;
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvCueTime,
static_cast<uint64>(time_))) {
return false;
}
if (!WriteEbmlMasterElement(writer, libwebm::kMkvCueTrackPositions, size))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvCueTrack,
static_cast<uint64>(track_))) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvCueClusterPosition,
static_cast<uint64>(cluster_pos_))) {
return false;
}
if (output_block_number_ && block_number_ > 1) {
if (!WriteEbmlElement(writer, libwebm::kMkvCueBlockNumber,
static_cast<uint64>(block_number_))) {
return false;
}
}
const int64_t stop_position = writer->Position();
if (stop_position < 0)
return false;
if (stop_position - payload_position != static_cast<int64_t>(payload_size))
return false;
return true;
}
uint64_t CuePoint::PayloadSize() const {
uint64_t size = EbmlElementSize(libwebm::kMkvCueClusterPosition,
static_cast<uint64>(cluster_pos_));
size += EbmlElementSize(libwebm::kMkvCueTrack, static_cast<uint64>(track_));
if (output_block_number_ && block_number_ > 1)
size += EbmlElementSize(libwebm::kMkvCueBlockNumber,
static_cast<uint64>(block_number_));
const uint64_t track_pos_size =
EbmlMasterElementSize(libwebm::kMkvCueTrackPositions, size) + size;
const uint64_t payload_size =
EbmlElementSize(libwebm::kMkvCueTime, static_cast<uint64>(time_)) +
track_pos_size;
return payload_size;
}
uint64_t CuePoint::Size() const {
const uint64_t payload_size = PayloadSize();
return EbmlMasterElementSize(libwebm::kMkvCuePoint, payload_size) +
payload_size;
}
///////////////////////////////////////////////////////////////
//
// Cues Class
Cues::Cues()
: cue_entries_capacity_(0),
cue_entries_size_(0),
cue_entries_(NULL),
output_block_number_(true) {}
Cues::~Cues() {
if (cue_entries_) {
for (int32_t i = 0; i < cue_entries_size_; ++i) {
CuePoint* const cue = cue_entries_[i];
delete cue;
}
delete[] cue_entries_;
}
}
bool Cues::AddCue(CuePoint* cue) {
if (!cue)
return false;
if ((cue_entries_size_ + 1) > cue_entries_capacity_) {
// Add more CuePoints.
const int32_t new_capacity =
(!cue_entries_capacity_) ? 2 : cue_entries_capacity_ * 2;
if (new_capacity < 1)
return false;
CuePoint** const cues =
new (std::nothrow) CuePoint*[new_capacity]; // NOLINT
if (!cues)
return false;
for (int32_t i = 0; i < cue_entries_size_; ++i) {
cues[i] = cue_entries_[i];
}
delete[] cue_entries_;
cue_entries_ = cues;
cue_entries_capacity_ = new_capacity;
}
cue->set_output_block_number(output_block_number_);
cue_entries_[cue_entries_size_++] = cue;
return true;
}
CuePoint* Cues::GetCueByIndex(int32_t index) const {
if (cue_entries_ == NULL)
return NULL;
if (index >= cue_entries_size_)
return NULL;
return cue_entries_[index];
}
uint64_t Cues::Size() {
uint64_t size = 0;
for (int32_t i = 0; i < cue_entries_size_; ++i)
size += GetCueByIndex(i)->Size();
size += EbmlMasterElementSize(libwebm::kMkvCues, size);
return size;
}
bool Cues::Write(IMkvWriter* writer) const {
if (!writer)
return false;
uint64_t size = 0;
for (int32_t i = 0; i < cue_entries_size_; ++i) {
const CuePoint* const cue = GetCueByIndex(i);
if (!cue)
return false;
size += cue->Size();
}
if (!WriteEbmlMasterElement(writer, libwebm::kMkvCues, size))
return false;
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
for (int32_t i = 0; i < cue_entries_size_; ++i) {
const CuePoint* const cue = GetCueByIndex(i);
if (!cue->Write(writer))
return false;
}
const int64_t stop_position = writer->Position();
if (stop_position < 0)
return false;
if (stop_position - payload_position != static_cast<int64_t>(size))
return false;
return true;
}
///////////////////////////////////////////////////////////////
//
// ContentEncAESSettings Class
ContentEncAESSettings::ContentEncAESSettings() : cipher_mode_(kCTR) {}
uint64_t ContentEncAESSettings::Size() const {
const uint64_t payload = PayloadSize();
const uint64_t size =
EbmlMasterElementSize(libwebm::kMkvContentEncAESSettings, payload) +
payload;
return size;
}
bool ContentEncAESSettings::Write(IMkvWriter* writer) const {
const uint64_t payload = PayloadSize();
if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncAESSettings,
payload))
return false;
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvAESSettingsCipherMode,
static_cast<uint64>(cipher_mode_))) {
return false;
}
const int64_t stop_position = writer->Position();
if (stop_position < 0 ||
stop_position - payload_position != static_cast<int64_t>(payload))
return false;
return true;
}
uint64_t ContentEncAESSettings::PayloadSize() const {
uint64_t size = EbmlElementSize(libwebm::kMkvAESSettingsCipherMode,
static_cast<uint64>(cipher_mode_));
return size;
}
///////////////////////////////////////////////////////////////
//
// ContentEncoding Class
ContentEncoding::ContentEncoding()
: enc_algo_(5),
enc_key_id_(NULL),
encoding_order_(0),
encoding_scope_(1),
encoding_type_(1),
enc_key_id_length_(0) {}
ContentEncoding::~ContentEncoding() { delete[] enc_key_id_; }
bool ContentEncoding::SetEncryptionID(const uint8_t* id, uint64_t length) {
if (!id || length < 1)
return false;
delete[] enc_key_id_;
enc_key_id_ =
new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT
if (!enc_key_id_)
return false;
memcpy(enc_key_id_, id, static_cast<size_t>(length));
enc_key_id_length_ = length;
return true;
}
uint64_t ContentEncoding::Size() const {
const uint64_t encryption_size = EncryptionSize();
const uint64_t encoding_size = EncodingSize(0, encryption_size);
const uint64_t encodings_size =
EbmlMasterElementSize(libwebm::kMkvContentEncoding, encoding_size) +
encoding_size;
return encodings_size;
}
bool ContentEncoding::Write(IMkvWriter* writer) const {
const uint64_t encryption_size = EncryptionSize();
const uint64_t encoding_size = EncodingSize(0, encryption_size);
const uint64_t size =
EbmlMasterElementSize(libwebm::kMkvContentEncoding, encoding_size) +
encoding_size;
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncoding,
encoding_size))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingOrder,
static_cast<uint64>(encoding_order_)))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingScope,
static_cast<uint64>(encoding_scope_)))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingType,
static_cast<uint64>(encoding_type_)))
return false;
if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncryption,
encryption_size))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvContentEncAlgo,
static_cast<uint64>(enc_algo_))) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvContentEncKeyID, enc_key_id_,
enc_key_id_length_))
return false;
if (!enc_aes_settings_.Write(writer))
return false;
const int64_t stop_position = writer->Position();
if (stop_position < 0 ||
stop_position - payload_position != static_cast<int64_t>(size))
return false;
return true;
}
uint64_t ContentEncoding::EncodingSize(uint64_t compresion_size,
uint64_t encryption_size) const {
// TODO(fgalligan): Add support for compression settings.
if (compresion_size != 0)
return 0;
uint64_t encoding_size = 0;
if (encryption_size > 0) {
encoding_size +=
EbmlMasterElementSize(libwebm::kMkvContentEncryption, encryption_size) +
encryption_size;
}
encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingType,
static_cast<uint64>(encoding_type_));
encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingScope,
static_cast<uint64>(encoding_scope_));
encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingOrder,
static_cast<uint64>(encoding_order_));
return encoding_size;
}
uint64_t ContentEncoding::EncryptionSize() const {
const uint64_t aes_size = enc_aes_settings_.Size();
uint64_t encryption_size = EbmlElementSize(libwebm::kMkvContentEncKeyID,
enc_key_id_, enc_key_id_length_);
encryption_size += EbmlElementSize(libwebm::kMkvContentEncAlgo,
static_cast<uint64>(enc_algo_));
return encryption_size + aes_size;
}
///////////////////////////////////////////////////////////////
//
// Track Class
Track::Track(unsigned int* seed)
: codec_id_(NULL),
codec_private_(NULL),
language_(NULL),
max_block_additional_id_(0),
name_(NULL),
number_(0),
type_(0),
uid_(MakeUID(seed)),
codec_delay_(0),
seek_pre_roll_(0),
default_duration_(0),
codec_private_length_(0),
content_encoding_entries_(NULL),
content_encoding_entries_size_(0) {}
Track::~Track() {
delete[] codec_id_;
delete[] codec_private_;
delete[] language_;
delete[] name_;
if (content_encoding_entries_) {
for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
ContentEncoding* const encoding = content_encoding_entries_[i];
delete encoding;
}
delete[] content_encoding_entries_;
}
}
bool Track::AddContentEncoding() {
const uint32_t count = content_encoding_entries_size_ + 1;
ContentEncoding** const content_encoding_entries =
new (std::nothrow) ContentEncoding*[count]; // NOLINT
if (!content_encoding_entries)
return false;
ContentEncoding* const content_encoding =
new (std::nothrow) ContentEncoding(); // NOLINT
if (!content_encoding) {
delete[] content_encoding_entries;
return false;
}
for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
content_encoding_entries[i] = content_encoding_entries_[i];
}
delete[] content_encoding_entries_;
content_encoding_entries_ = content_encoding_entries;
content_encoding_entries_[content_encoding_entries_size_] = content_encoding;
content_encoding_entries_size_ = count;
return true;
}
ContentEncoding* Track::GetContentEncodingByIndex(uint32_t index) const {
if (content_encoding_entries_ == NULL)
return NULL;
if (index >= content_encoding_entries_size_)
return NULL;
return content_encoding_entries_[index];
}
uint64_t Track::PayloadSize() const {
uint64_t size =
EbmlElementSize(libwebm::kMkvTrackNumber, static_cast<uint64>(number_));
size += EbmlElementSize(libwebm::kMkvTrackUID, static_cast<uint64>(uid_));
size += EbmlElementSize(libwebm::kMkvTrackType, static_cast<uint64>(type_));
if (codec_id_)
size += EbmlElementSize(libwebm::kMkvCodecID, codec_id_);
if (codec_private_)
size += EbmlElementSize(libwebm::kMkvCodecPrivate, codec_private_,
codec_private_length_);
if (language_)
size += EbmlElementSize(libwebm::kMkvLanguage, language_);
if (name_)
size += EbmlElementSize(libwebm::kMkvName, name_);
if (max_block_additional_id_) {
size += EbmlElementSize(libwebm::kMkvMaxBlockAdditionID,
static_cast<uint64>(max_block_additional_id_));
}
if (codec_delay_) {
size += EbmlElementSize(libwebm::kMkvCodecDelay,
static_cast<uint64>(codec_delay_));
}
if (seek_pre_roll_) {
size += EbmlElementSize(libwebm::kMkvSeekPreRoll,
static_cast<uint64>(seek_pre_roll_));
}
if (default_duration_) {
size += EbmlElementSize(libwebm::kMkvDefaultDuration,
static_cast<uint64>(default_duration_));
}
if (content_encoding_entries_size_ > 0) {
uint64_t content_encodings_size = 0;
for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
ContentEncoding* const encoding = content_encoding_entries_[i];
content_encodings_size += encoding->Size();
}
size += EbmlMasterElementSize(libwebm::kMkvContentEncodings,
content_encodings_size) +
content_encodings_size;
}
return size;
}
uint64_t Track::Size() const {
uint64_t size = PayloadSize();
size += EbmlMasterElementSize(libwebm::kMkvTrackEntry, size);
return size;
}
bool Track::Write(IMkvWriter* writer) const {
if (!writer)
return false;
// mandatory elements without a default value.
if (!type_ || !codec_id_)
return false;
// |size| may be bigger than what is written out in this function because
// derived classes may write out more data in the Track element.
const uint64_t payload_size = PayloadSize();
if (!WriteEbmlMasterElement(writer, libwebm::kMkvTrackEntry, payload_size))
return false;
uint64_t size =
EbmlElementSize(libwebm::kMkvTrackNumber, static_cast<uint64>(number_));
size += EbmlElementSize(libwebm::kMkvTrackUID, static_cast<uint64>(uid_));
size += EbmlElementSize(libwebm::kMkvTrackType, static_cast<uint64>(type_));
if (codec_id_)
size += EbmlElementSize(libwebm::kMkvCodecID, codec_id_);
if (codec_private_)
size += EbmlElementSize(libwebm::kMkvCodecPrivate, codec_private_,
static_cast<uint64>(codec_private_length_));
if (language_)
size += EbmlElementSize(libwebm::kMkvLanguage, language_);
if (name_)
size += EbmlElementSize(libwebm::kMkvName, name_);
if (max_block_additional_id_)
size += EbmlElementSize(libwebm::kMkvMaxBlockAdditionID,
static_cast<uint64>(max_block_additional_id_));
if (codec_delay_)
size += EbmlElementSize(libwebm::kMkvCodecDelay,
static_cast<uint64>(codec_delay_));
if (seek_pre_roll_)
size += EbmlElementSize(libwebm::kMkvSeekPreRoll,
static_cast<uint64>(seek_pre_roll_));
if (default_duration_)
size += EbmlElementSize(libwebm::kMkvDefaultDuration,
static_cast<uint64>(default_duration_));
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvTrackNumber,
static_cast<uint64>(number_)))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvTrackUID,
static_cast<uint64>(uid_)))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvTrackType,
static_cast<uint64>(type_)))
return false;
if (max_block_additional_id_) {
if (!WriteEbmlElement(writer, libwebm::kMkvMaxBlockAdditionID,
static_cast<uint64>(max_block_additional_id_))) {
return false;
}
}
if (codec_delay_) {
if (!WriteEbmlElement(writer, libwebm::kMkvCodecDelay,
static_cast<uint64>(codec_delay_)))
return false;
}
if (seek_pre_roll_) {
if (!WriteEbmlElement(writer, libwebm::kMkvSeekPreRoll,
static_cast<uint64>(seek_pre_roll_)))
return false;
}
if (default_duration_) {
if (!WriteEbmlElement(writer, libwebm::kMkvDefaultDuration,
static_cast<uint64>(default_duration_)))
return false;
}
if (codec_id_) {
if (!WriteEbmlElement(writer, libwebm::kMkvCodecID, codec_id_))
return false;
}
if (codec_private_) {
if (!WriteEbmlElement(writer, libwebm::kMkvCodecPrivate, codec_private_,
static_cast<uint64>(codec_private_length_)))
return false;
}
if (language_) {
if (!WriteEbmlElement(writer, libwebm::kMkvLanguage, language_))
return false;
}
if (name_) {
if (!WriteEbmlElement(writer, libwebm::kMkvName, name_))
return false;
}
int64_t stop_position = writer->Position();
if (stop_position < 0 ||
stop_position - payload_position != static_cast<int64_t>(size))
return false;
if (content_encoding_entries_size_ > 0) {
uint64_t content_encodings_size = 0;
for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
ContentEncoding* const encoding = content_encoding_entries_[i];
content_encodings_size += encoding->Size();
}
if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncodings,
content_encodings_size))
return false;
for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
ContentEncoding* const encoding = content_encoding_entries_[i];
if (!encoding->Write(writer))
return false;
}
}
stop_position = writer->Position();
if (stop_position < 0)
return false;
return true;
}
bool Track::SetCodecPrivate(const uint8_t* codec_private, uint64_t length) {
if (!codec_private || length < 1)
return false;
delete[] codec_private_;
codec_private_ =
new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT
if (!codec_private_)
return false;
memcpy(codec_private_, codec_private, static_cast<size_t>(length));
codec_private_length_ = length;
return true;
}
void Track::set_codec_id(const char* codec_id) {
if (codec_id) {
delete[] codec_id_;
const size_t length = strlen(codec_id) + 1;
codec_id_ = new (std::nothrow) char[length]; // NOLINT
if (codec_id_) {
#ifdef _MSC_VER
strcpy_s(codec_id_, length, codec_id);
#else
strcpy(codec_id_, codec_id);
#endif
}
}
}
// TODO(fgalligan): Vet the language parameter.
void Track::set_language(const char* language) {
if (language) {
delete[] language_;
const size_t length = strlen(language) + 1;
language_ = new (std::nothrow) char[length]; // NOLINT
if (language_) {
#ifdef _MSC_VER
strcpy_s(language_, length, language);
#else
strcpy(language_, language);
#endif
}
}
}
void Track::set_name(const char* name) {
if (name) {
delete[] name_;
const size_t length = strlen(name) + 1;
name_ = new (std::nothrow) char[length]; // NOLINT
if (name_) {
#ifdef _MSC_VER
strcpy_s(name_, length, name);
#else
strcpy(name_, name);
#endif
}
}
}
///////////////////////////////////////////////////////////////
//
// Colour and its child elements
uint64_t PrimaryChromaticity::PrimaryChromaticitySize(
libwebm::MkvId x_id, libwebm::MkvId y_id) const {
return EbmlElementSize(x_id, x_) + EbmlElementSize(y_id, y_);
}
bool PrimaryChromaticity::Write(IMkvWriter* writer, libwebm::MkvId x_id,
libwebm::MkvId y_id) const {
if (!Valid()) {
return false;
}
return WriteEbmlElement(writer, x_id, x_) &&
WriteEbmlElement(writer, y_id, y_);
}
bool PrimaryChromaticity::Valid() const {
return (x_ >= kChromaticityMin && x_ <= kChromaticityMax &&
y_ >= kChromaticityMin && y_ <= kChromaticityMax);
}
uint64_t MasteringMetadata::MasteringMetadataSize() const {
uint64_t size = PayloadSize();
if (size > 0)
size += EbmlMasterElementSize(libwebm::kMkvMasteringMetadata, size);
return size;
}
bool MasteringMetadata::Valid() const {
if (luminance_min_ != kValueNotPresent) {
if (luminance_min_ < kMinLuminance || luminance_min_ > kMinLuminanceMax ||
luminance_min_ > luminance_max_) {
return false;
}
}
if (luminance_max_ != kValueNotPresent) {
if (luminance_max_ < kMinLuminance || luminance_max_ > kMaxLuminanceMax ||
luminance_max_ < luminance_min_) {
return false;
}
}
if (r_ && !r_->Valid())
return false;
if (g_ && !g_->Valid())
return false;
if (b_ && !b_->Valid())
return false;
if (white_point_ && !white_point_->Valid())
return false;
return true;
}
bool MasteringMetadata::Write(IMkvWriter* writer) const {
const uint64_t size = PayloadSize();
// Don't write an empty element.
if (size == 0)
return true;
if (!WriteEbmlMasterElement(writer, libwebm::kMkvMasteringMetadata, size))
return false;
if (luminance_max_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvLuminanceMax, luminance_max_)) {
return false;
}
if (luminance_min_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvLuminanceMin, luminance_min_)) {
return false;
}
if (r_ &&
!r_->Write(writer, libwebm::kMkvPrimaryRChromaticityX,
libwebm::kMkvPrimaryRChromaticityY)) {
return false;
}
if (g_ &&
!g_->Write(writer, libwebm::kMkvPrimaryGChromaticityX,
libwebm::kMkvPrimaryGChromaticityY)) {
return false;
}
if (b_ &&
!b_->Write(writer, libwebm::kMkvPrimaryBChromaticityX,
libwebm::kMkvPrimaryBChromaticityY)) {
return false;
}
if (white_point_ &&
!white_point_->Write(writer, libwebm::kMkvWhitePointChromaticityX,
libwebm::kMkvWhitePointChromaticityY)) {
return false;
}
return true;
}
bool MasteringMetadata::SetChromaticity(
const PrimaryChromaticity* r, const PrimaryChromaticity* g,
const PrimaryChromaticity* b, const PrimaryChromaticity* white_point) {
PrimaryChromaticityPtr r_ptr(NULL);
if (r) {
if (!CopyChromaticity(r, &r_ptr))
return false;
}
PrimaryChromaticityPtr g_ptr(NULL);
if (g) {
if (!CopyChromaticity(g, &g_ptr))
return false;
}
PrimaryChromaticityPtr b_ptr(NULL);
if (b) {
if (!CopyChromaticity(b, &b_ptr))
return false;
}
PrimaryChromaticityPtr wp_ptr(NULL);
if (white_point) {
if (!CopyChromaticity(white_point, &wp_ptr))
return false;
}
r_ = r_ptr.release();
g_ = g_ptr.release();
b_ = b_ptr.release();
white_point_ = wp_ptr.release();
return true;
}
uint64_t MasteringMetadata::PayloadSize() const {
uint64_t size = 0;
if (luminance_max_ != kValueNotPresent)
size += EbmlElementSize(libwebm::kMkvLuminanceMax, luminance_max_);
if (luminance_min_ != kValueNotPresent)
size += EbmlElementSize(libwebm::kMkvLuminanceMin, luminance_min_);
if (r_) {
size += r_->PrimaryChromaticitySize(libwebm::kMkvPrimaryRChromaticityX,
libwebm::kMkvPrimaryRChromaticityY);
}
if (g_) {
size += g_->PrimaryChromaticitySize(libwebm::kMkvPrimaryGChromaticityX,
libwebm::kMkvPrimaryGChromaticityY);
}
if (b_) {
size += b_->PrimaryChromaticitySize(libwebm::kMkvPrimaryBChromaticityX,
libwebm::kMkvPrimaryBChromaticityY);
}
if (white_point_) {
size += white_point_->PrimaryChromaticitySize(
libwebm::kMkvWhitePointChromaticityX,
libwebm::kMkvWhitePointChromaticityY);
}
return size;
}
uint64_t Colour::ColourSize() const {
uint64_t size = PayloadSize();
if (size > 0)
size += EbmlMasterElementSize(libwebm::kMkvColour, size);
return size;
}
bool Colour::Valid() const {
if (mastering_metadata_ && !mastering_metadata_->Valid())
return false;
if (matrix_coefficients_ != kValueNotPresent &&
!IsMatrixCoefficientsValueValid(matrix_coefficients_)) {
return false;
}
if (chroma_siting_horz_ != kValueNotPresent &&
!IsChromaSitingHorzValueValid(chroma_siting_horz_)) {
return false;
}
if (chroma_siting_vert_ != kValueNotPresent &&
!IsChromaSitingVertValueValid(chroma_siting_vert_)) {
return false;
}
if (range_ != kValueNotPresent && !IsColourRangeValueValid(range_))
return false;
if (transfer_characteristics_ != kValueNotPresent &&
!IsTransferCharacteristicsValueValid(transfer_characteristics_)) {
return false;
}
if (primaries_ != kValueNotPresent && !IsPrimariesValueValid(primaries_))
return false;
return true;
}
bool Colour::Write(IMkvWriter* writer) const {
const uint64_t size = PayloadSize();
// Don't write an empty element.
if (size == 0)
return true;
// Don't write an invalid element.
if (!Valid())
return false;
if (!WriteEbmlMasterElement(writer, libwebm::kMkvColour, size))
return false;
if (matrix_coefficients_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvMatrixCoefficients,
static_cast<uint64>(matrix_coefficients_))) {
return false;
}
if (bits_per_channel_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvBitsPerChannel,
static_cast<uint64>(bits_per_channel_))) {
return false;
}
if (chroma_subsampling_horz_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvChromaSubsamplingHorz,
static_cast<uint64>(chroma_subsampling_horz_))) {
return false;
}
if (chroma_subsampling_vert_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvChromaSubsamplingVert,
static_cast<uint64>(chroma_subsampling_vert_))) {
return false;
}
if (cb_subsampling_horz_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvCbSubsamplingHorz,
static_cast<uint64>(cb_subsampling_horz_))) {
return false;
}
if (cb_subsampling_vert_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvCbSubsamplingVert,
static_cast<uint64>(cb_subsampling_vert_))) {
return false;
}
if (chroma_siting_horz_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvChromaSitingHorz,
static_cast<uint64>(chroma_siting_horz_))) {
return false;
}
if (chroma_siting_vert_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvChromaSitingVert,
static_cast<uint64>(chroma_siting_vert_))) {
return false;
}
if (range_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvRange,
static_cast<uint64>(range_))) {
return false;
}
if (transfer_characteristics_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvTransferCharacteristics,
static_cast<uint64>(transfer_characteristics_))) {
return false;
}
if (primaries_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvPrimaries,
static_cast<uint64>(primaries_))) {
return false;
}
if (max_cll_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvMaxCLL,
static_cast<uint64>(max_cll_))) {
return false;
}
if (max_fall_ != kValueNotPresent &&
!WriteEbmlElement(writer, libwebm::kMkvMaxFALL,
static_cast<uint64>(max_fall_))) {
return false;
}
if (mastering_metadata_ && !mastering_metadata_->Write(writer))
return false;
return true;
}
bool Colour::SetMasteringMetadata(const MasteringMetadata& mastering_metadata) {
std::auto_ptr<MasteringMetadata> mm_ptr(new MasteringMetadata());
if (!mm_ptr.get())
return false;
mm_ptr->set_luminance_max(mastering_metadata.luminance_max());
mm_ptr->set_luminance_min(mastering_metadata.luminance_min());
if (!mm_ptr->SetChromaticity(mastering_metadata.r(), mastering_metadata.g(),
mastering_metadata.b(),
mastering_metadata.white_point())) {
return false;
}
delete mastering_metadata_;
mastering_metadata_ = mm_ptr.release();
return true;
}
uint64_t Colour::PayloadSize() const {
uint64_t size = 0;
if (matrix_coefficients_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvMatrixCoefficients,
static_cast<uint64>(matrix_coefficients_));
}
if (bits_per_channel_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvBitsPerChannel,
static_cast<uint64>(bits_per_channel_));
}
if (chroma_subsampling_horz_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvChromaSubsamplingHorz,
static_cast<uint64>(chroma_subsampling_horz_));
}
if (chroma_subsampling_vert_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvChromaSubsamplingVert,
static_cast<uint64>(chroma_subsampling_vert_));
}
if (cb_subsampling_horz_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvCbSubsamplingHorz,
static_cast<uint64>(cb_subsampling_horz_));
}
if (cb_subsampling_vert_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvCbSubsamplingVert,
static_cast<uint64>(cb_subsampling_vert_));
}
if (chroma_siting_horz_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvChromaSitingHorz,
static_cast<uint64>(chroma_siting_horz_));
}
if (chroma_siting_vert_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvChromaSitingVert,
static_cast<uint64>(chroma_siting_vert_));
}
if (range_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvRange, static_cast<uint64>(range_));
}
if (transfer_characteristics_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvTransferCharacteristics,
static_cast<uint64>(transfer_characteristics_));
}
if (primaries_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvPrimaries,
static_cast<uint64>(primaries_));
}
if (max_cll_ != kValueNotPresent) {
size += EbmlElementSize(libwebm::kMkvMaxCLL, static_cast<uint64>(max_cll_));
}
if (max_fall_ != kValueNotPresent) {
size +=
EbmlElementSize(libwebm::kMkvMaxFALL, static_cast<uint64>(max_fall_));
}
if (mastering_metadata_)
size += mastering_metadata_->MasteringMetadataSize();
return size;
}
///////////////////////////////////////////////////////////////
//
// Projection element
uint64_t Projection::ProjectionSize() const {
uint64_t size = PayloadSize();
if (size > 0)
size += EbmlMasterElementSize(libwebm::kMkvProjection, size);
return size;
}
bool Projection::Write(IMkvWriter* writer) const {
const uint64_t size = PayloadSize();
// Don't write an empty element.
if (size == 0)
return true;
if (!WriteEbmlMasterElement(writer, libwebm::kMkvProjection, size))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvProjectionType,
static_cast<uint64>(type_))) {
return false;
}
if (private_data_length_ > 0 && private_data_ != NULL &&
!WriteEbmlElement(writer, libwebm::kMkvProjectionPrivate, private_data_,
private_data_length_)) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPoseYaw, pose_yaw_))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPosePitch,
pose_pitch_)) {
return false;
}
if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPoseRoll, pose_roll_)) {
return false;
}
return true;
}
bool Projection::SetProjectionPrivate(const uint8_t* data,
uint64_t data_length) {
if (data == NULL || data_length == 0) {
return false;
}
if (data_length != static_cast<size_t>(data_length)) {
return false;
}
uint8_t* new_private_data =
new (std::nothrow) uint8_t[static_cast<size_t>(data_length)];
if (new_private_data == NULL) {
return false;
}
delete[] private_data_;
private_data_ = new_private_data;
private_data_length_ = data_length;
memcpy(private_data_, data, static_cast<size_t>(data_length));
return true;
}
uint64_t Projection::PayloadSize() const {
uint64_t size =
EbmlElementSize(libwebm::kMkvProjection, static_cast<uint64>(type_));
if (private_data_length_ > 0 && private_data_ != NULL) {
size += EbmlElementSize(libwebm::kMkvProjectionPrivate, private_data_,
private_data_length_);
}
size += EbmlElementSize(libwebm::kMkvProjectionPoseYaw, pose_yaw_);
size += EbmlElementSize(libwebm::kMkvProjectionPosePitch, pose_pitch_);
size += EbmlElementSize(libwebm::kMkvProjectionPoseRoll, pose_roll_);
return size;
}
///////////////////////////////////////////////////////////////
//
// VideoTrack Class
VideoTrack::VideoTrack(unsigned int* seed)
: Track(seed),
display_height_(0),
display_width_(0),
pixel_height_(0),
pixel_width_(0),
crop_left_(0),
crop_right_(0),
crop_top_(0),
crop_bottom_(0),
frame_rate_(0.0),
height_(0),
stereo_mode_(0),
alpha_mode_(0),
width_(0),
colour_(NULL),
projection_(NULL) {}
VideoTrack::~VideoTrack() {
delete colour_;
delete projection_;
}
bool VideoTrack::SetStereoMode(uint64_t stereo_mode) {
if (stereo_mode != kMono && stereo_mode != kSideBySideLeftIsFirst &&
stereo_mode != kTopBottomRightIsFirst &&
stereo_mode != kTopBottomLeftIsFirst &&
stereo_mode != kSideBySideRightIsFirst)
return false;
stereo_mode_ = stereo_mode;
return true;
}
bool VideoTrack::SetAlphaMode(uint64_t alpha_mode) {
if (alpha_mode != kNoAlpha && alpha_mode != kAlpha)
return false;
alpha_mode_ = alpha_mode;
return true;
}
uint64_t VideoTrack::PayloadSize() const {
const uint64_t parent_size = Track::PayloadSize();
uint64_t size = VideoPayloadSize();
size += EbmlMasterElementSize(libwebm::kMkvVideo, size);
return parent_size + size;
}
bool VideoTrack::Write(IMkvWriter* writer) const {
if (!Track::Write(writer))
return false;
const uint64_t size = VideoPayloadSize();
if (!WriteEbmlMasterElement(writer, libwebm::kMkvVideo, size))
return false;
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
if (!WriteEbmlElement(
writer, libwebm::kMkvPixelWidth,
static_cast<uint64>((pixel_width_ > 0) ? pixel_width_ : width_)))
return false;
if (!WriteEbmlElement(
writer, libwebm::kMkvPixelHeight,
static_cast<uint64>((pixel_height_ > 0) ? pixel_height_ : height_)))
return false;
if (display_width_ > 0) {
if (!WriteEbmlElement(writer, libwebm::kMkvDisplayWidth,
static_cast<uint64>(display_width_)))
return false;
}
if (display_height_ > 0) {
if (!WriteEbmlElement(writer, libwebm::kMkvDisplayHeight,
static_cast<uint64>(display_height_)))
return false;
}
if (crop_left_ > 0) {
if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropLeft,
static_cast<uint64>(crop_left_)))
return false;
}
if (crop_right_ > 0) {
if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropRight,
static_cast<uint64>(crop_right_)))
return false;
}
if (crop_top_ > 0) {
if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropTop,
static_cast<uint64>(crop_top_)))
return false;
}
if (crop_bottom_ > 0) {
if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropBottom,
static_cast<uint64>(crop_bottom_)))
return false;
}
if (stereo_mode_ > kMono) {
if (!WriteEbmlElement(writer, libwebm::kMkvStereoMode,
static_cast<uint64>(stereo_mode_)))
return false;
}
if (alpha_mode_ > kNoAlpha) {
if (!WriteEbmlElement(writer, libwebm::kMkvAlphaMode,
static_cast<uint64>(alpha_mode_)))
return false;
}
if (frame_rate_ > 0.0) {
if (!WriteEbmlElement(writer, libwebm::kMkvFrameRate,
static_cast<float>(frame_rate_))) {
return false;
}
}
if (colour_) {
if (!colour_->Write(writer))
return false;
}
if (projection_) {
if (!projection_->Write(writer))
return false;
}
const int64_t stop_position = writer->Position();
if (stop_position < 0 ||
stop_position - payload_position != static_cast<int64_t>(size)) {
return false;
}
return true;
}
bool VideoTrack::SetColour(const Colour& colour) {
std::auto_ptr<Colour> colour_ptr(new Colour());
if (!colour_ptr.get())
return false;
if (colour.mastering_metadata()) {
if (!colour_ptr->SetMasteringMetadata(*colour.mastering_metadata()))
return false;
}
colour_ptr->set_matrix_coefficients(colour.matrix_coefficients());
colour_ptr->set_bits_per_channel(colour.bits_per_channel());
colour_ptr->set_chroma_subsampling_horz(colour.chroma_subsampling_horz());
colour_ptr->set_chroma_subsampling_vert(colour.chroma_subsampling_vert());
colour_ptr->set_cb_subsampling_horz(colour.cb_subsampling_horz());
colour_ptr->set_cb_subsampling_vert(colour.cb_subsampling_vert());
colour_ptr->set_chroma_siting_horz(colour.chroma_siting_horz());
colour_ptr->set_chroma_siting_vert(colour.chroma_siting_vert());
colour_ptr->set_range(colour.range());
colour_ptr->set_transfer_characteristics(colour.transfer_characteristics());
colour_ptr->set_primaries(colour.primaries());
colour_ptr->set_max_cll(colour.max_cll());
colour_ptr->set_max_fall(colour.max_fall());
delete colour_;
colour_ = colour_ptr.release();
return true;
}
bool VideoTrack::SetProjection(const Projection& projection) {
std::auto_ptr<Projection> projection_ptr(new Projection());
if (!projection_ptr.get())
return false;
if (projection.private_data()) {
if (!projection_ptr->SetProjectionPrivate(
projection.private_data(), projection.private_data_length())) {
return false;
}
}
projection_ptr->set_type(projection.type());
projection_ptr->set_pose_yaw(projection.pose_yaw());
projection_ptr->set_pose_pitch(projection.pose_pitch());
projection_ptr->set_pose_roll(projection.pose_roll());
delete projection_;
projection_ = projection_ptr.release();
return true;
}
uint64_t VideoTrack::VideoPayloadSize() const {
uint64_t size = EbmlElementSize(
libwebm::kMkvPixelWidth,
static_cast<uint64>((pixel_width_ > 0) ? pixel_width_ : width_));
size += EbmlElementSize(
libwebm::kMkvPixelHeight,
static_cast<uint64>((pixel_height_ > 0) ? pixel_height_ : height_));
if (display_width_ > 0)
size += EbmlElementSize(libwebm::kMkvDisplayWidth,
static_cast<uint64>(display_width_));
if (display_height_ > 0)
size += EbmlElementSize(libwebm::kMkvDisplayHeight,
static_cast<uint64>(display_height_));
if (crop_left_ > 0)
size += EbmlElementSize(libwebm::kMkvPixelCropLeft,
static_cast<uint64>(crop_left_));
if (crop_right_ > 0)
size += EbmlElementSize(libwebm::kMkvPixelCropRight,
static_cast<uint64>(crop_right_));
if (crop_top_ > 0)
size += EbmlElementSize(libwebm::kMkvPixelCropTop,
static_cast<uint64>(crop_top_));
if (crop_bottom_ > 0)
size += EbmlElementSize(libwebm::kMkvPixelCropBottom,
static_cast<uint64>(crop_bottom_));
if (stereo_mode_ > kMono)
size += EbmlElementSize(libwebm::kMkvStereoMode,
static_cast<uint64>(stereo_mode_));
if (alpha_mode_ > kNoAlpha)
size += EbmlElementSize(libwebm::kMkvAlphaMode,
static_cast<uint64>(alpha_mode_));
if (frame_rate_ > 0.0)
size += EbmlElementSize(libwebm::kMkvFrameRate,
static_cast<float>(frame_rate_));
if (colour_)
size += colour_->ColourSize();
if (projection_)
size += projection_->ProjectionSize();
return size;
}
///////////////////////////////////////////////////////////////
//
// AudioTrack Class
AudioTrack::AudioTrack(unsigned int* seed)
: Track(seed), bit_depth_(0), channels_(1), sample_rate_(0.0) {}
AudioTrack::~AudioTrack() {}
uint64_t AudioTrack::PayloadSize() const {
const uint64_t parent_size = Track::PayloadSize();
uint64_t size = EbmlElementSize(libwebm::kMkvSamplingFrequency,
static_cast<float>(sample_rate_));
size +=
EbmlElementSize(libwebm::kMkvChannels, static_cast<uint64>(channels_));
if (bit_depth_ > 0)
size +=
EbmlElementSize(libwebm::kMkvBitDepth, static_cast<uint64>(bit_depth_));
size += EbmlMasterElementSize(libwebm::kMkvAudio, size);
return parent_size + size;
}
bool AudioTrack::Write(IMkvWriter* writer) const {
if (!Track::Write(writer))
return false;
// Calculate AudioSettings size.
uint64_t size = EbmlElementSize(libwebm::kMkvSamplingFrequency,
static_cast<float>(sample_rate_));
size +=
EbmlElementSize(libwebm::kMkvChannels, static_cast<uint64>(channels_));
if (bit_depth_ > 0)
size +=
EbmlElementSize(libwebm::kMkvBitDepth, static_cast<uint64>(bit_depth_));
if (!WriteEbmlMasterElement(writer, libwebm::kMkvAudio, size))
return false;
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvSamplingFrequency,
static_cast<float>(sample_rate_)))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvChannels,
static_cast<uint64>(channels_)))
return false;
if (bit_depth_ > 0)
if (!WriteEbmlElement(writer, libwebm::kMkvBitDepth,
static_cast<uint64>(bit_depth_)))
return false;
const int64_t stop_position = writer->Position();
if (stop_position < 0 ||
stop_position - payload_position != static_cast<int64_t>(size))
return false;
return true;
}
///////////////////////////////////////////////////////////////
//
// Tracks Class
const char Tracks::kOpusCodecId[] = "A_OPUS";
const char Tracks::kVorbisCodecId[] = "A_VORBIS";
const char Tracks::kVp8CodecId[] = "V_VP8";
const char Tracks::kVp9CodecId[] = "V_VP9";
const char Tracks::kVp10CodecId[] = "V_VP10";
const char Tracks::kWebVttCaptionsId[] = "D_WEBVTT/CAPTIONS";
const char Tracks::kWebVttDescriptionsId[] = "D_WEBVTT/DESCRIPTIONS";
const char Tracks::kWebVttMetadataId[] = "D_WEBVTT/METADATA";
const char Tracks::kWebVttSubtitlesId[] = "D_WEBVTT/SUBTITLES";
Tracks::Tracks()
: track_entries_(NULL), track_entries_size_(0), wrote_tracks_(false) {}
Tracks::~Tracks() {
if (track_entries_) {
for (uint32_t i = 0; i < track_entries_size_; ++i) {
Track* const track = track_entries_[i];
delete track;
}
delete[] track_entries_;
}
}
bool Tracks::AddTrack(Track* track, int32_t number) {
if (number < 0 || wrote_tracks_)
return false;
// This muxer only supports track numbers in the range [1, 126], in
// order to be able (to use Matroska integer representation) to
// serialize the block header (of which the track number is a part)
// for a frame using exactly 4 bytes.
if (number > 0x7E)
return false;
uint32_t track_num = number;
if (track_num > 0) {
// Check to make sure a track does not already have |track_num|.
for (uint32_t i = 0; i < track_entries_size_; ++i) {
if (track_entries_[i]->number() == track_num)
return false;
}
}
const uint32_t count = track_entries_size_ + 1;
Track** const track_entries = new (std::nothrow) Track*[count]; // NOLINT
if (!track_entries)
return false;
for (uint32_t i = 0; i < track_entries_size_; ++i) {
track_entries[i] = track_entries_[i];
}
delete[] track_entries_;
// Find the lowest availible track number > 0.
if (track_num == 0) {
track_num = count;
// Check to make sure a track does not already have |track_num|.
bool exit = false;
do {
exit = true;
for (uint32_t i = 0; i < track_entries_size_; ++i) {
if (track_entries[i]->number() == track_num) {
track_num++;
exit = false;
break;
}
}
} while (!exit);
}
track->set_number(track_num);
track_entries_ = track_entries;
track_entries_[track_entries_size_] = track;
track_entries_size_ = count;
return true;
}
const Track* Tracks::GetTrackByIndex(uint32_t index) const {
if (track_entries_ == NULL)
return NULL;
if (index >= track_entries_size_)
return NULL;
return track_entries_[index];
}
Track* Tracks::GetTrackByNumber(uint64_t track_number) const {
const int32_t count = track_entries_size();
for (int32_t i = 0; i < count; ++i) {
if (track_entries_[i]->number() == track_number)
return track_entries_[i];
}
return NULL;
}
bool Tracks::TrackIsAudio(uint64_t track_number) const {
const Track* const track = GetTrackByNumber(track_number);
if (track->type() == kAudio)
return true;
return false;
}
bool Tracks::TrackIsVideo(uint64_t track_number) const {
const Track* const track = GetTrackByNumber(track_number);
if (track->type() == kVideo)
return true;
return false;
}
bool Tracks::Write(IMkvWriter* writer) const {
uint64_t size = 0;
const int32_t count = track_entries_size();
for (int32_t i = 0; i < count; ++i) {
const Track* const track = GetTrackByIndex(i);
if (!track)
return false;
size += track->Size();
}
if (!WriteEbmlMasterElement(writer, libwebm::kMkvTracks, size))
return false;
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
for (int32_t i = 0; i < count; ++i) {
const Track* const track = GetTrackByIndex(i);
if (!track->Write(writer))
return false;
}
const int64_t stop_position = writer->Position();
if (stop_position < 0 ||
stop_position - payload_position != static_cast<int64_t>(size))
return false;
wrote_tracks_ = true;
return true;
}
///////////////////////////////////////////////////////////////
//
// Chapter Class
bool Chapter::set_id(const char* id) { return StrCpy(id, &id_); }
void Chapter::set_time(const Segment& segment, uint64_t start_ns,
uint64_t end_ns) {
const SegmentInfo* const info = segment.GetSegmentInfo();
const uint64_t timecode_scale = info->timecode_scale();
start_timecode_ = start_ns / timecode_scale;
end_timecode_ = end_ns / timecode_scale;
}
bool Chapter::add_string(const char* title, const char* language,
const char* country) {
if (!ExpandDisplaysArray())
return false;
Display& d = displays_[displays_count_++];
d.Init();
if (!d.set_title(title))
return false;
if (!d.set_language(language))
return false;
if (!d.set_country(country))
return false;
return true;
}
Chapter::Chapter() {
// This ctor only constructs the object. Proper initialization is
// done in Init() (called in Chapters::AddChapter()). The only
// reason we bother implementing this ctor is because we had to
// declare it as private (along with the dtor), in order to prevent
// clients from creating Chapter instances (a privelege we grant
// only to the Chapters class). Doing no initialization here also
// means that creating arrays of chapter objects is more efficient,
// because we only initialize each new chapter object as it becomes
// active on the array.
}
Chapter::~Chapter() {}
void Chapter::Init(unsigned int* seed) {
id_ = NULL;
start_timecode_ = 0;
end_timecode_ = 0;
displays_ = NULL;
displays_size_ = 0;
displays_count_ = 0;
uid_ = MakeUID(seed);
}
void Chapter::ShallowCopy(Chapter* dst) const {
dst->id_ = id_;
dst->start_timecode_ = start_timecode_;
dst->end_timecode_ = end_timecode_;
dst->uid_ = uid_;
dst->displays_ = displays_;
dst->displays_size_ = displays_size_;
dst->displays_count_ = displays_count_;
}
void Chapter::Clear() {
StrCpy(NULL, &id_);
while (displays_count_ > 0) {
Display& d = displays_[--displays_count_];
d.Clear();
}
delete[] displays_;
displays_ = NULL;
displays_size_ = 0;
}
bool Chapter::ExpandDisplaysArray() {
if (displays_size_ > displays_count_)
return true; // nothing to do yet
const int size = (displays_size_ == 0) ? 1 : 2 * displays_size_;
Display* const displays = new (std::nothrow) Display[size]; // NOLINT
if (displays == NULL)
return false;
for (int idx = 0; idx < displays_count_; ++idx) {
displays[idx] = displays_[idx]; // shallow copy
}
delete[] displays_;
displays_ = displays;
displays_size_ = size;
return true;
}
uint64_t Chapter::WriteAtom(IMkvWriter* writer) const {
uint64_t payload_size =
EbmlElementSize(libwebm::kMkvChapterStringUID, id_) +
EbmlElementSize(libwebm::kMkvChapterUID, static_cast<uint64>(uid_)) +
EbmlElementSize(libwebm::kMkvChapterTimeStart,
static_cast<uint64>(start_timecode_)) +
EbmlElementSize(libwebm::kMkvChapterTimeEnd,
static_cast<uint64>(end_timecode_));
for (int idx = 0; idx < displays_count_; ++idx) {
const Display& d = displays_[idx];
payload_size += d.WriteDisplay(NULL);
}
const uint64_t atom_size =
EbmlMasterElementSize(libwebm::kMkvChapterAtom, payload_size) +
payload_size;
if (writer == NULL)
return atom_size;
const int64_t start = writer->Position();
if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapterAtom, payload_size))
return 0;
if (!WriteEbmlElement(writer, libwebm::kMkvChapterStringUID, id_))
return 0;
if (!WriteEbmlElement(writer, libwebm::kMkvChapterUID,
static_cast<uint64>(uid_)))
return 0;
if (!WriteEbmlElement(writer, libwebm::kMkvChapterTimeStart,
static_cast<uint64>(start_timecode_)))
return 0;
if (!WriteEbmlElement(writer, libwebm::kMkvChapterTimeEnd,
static_cast<uint64>(end_timecode_)))
return 0;
for (int idx = 0; idx < displays_count_; ++idx) {
const Display& d = displays_[idx];
if (!d.WriteDisplay(writer))
return 0;
}
const int64_t stop = writer->Position();
if (stop >= start && uint64_t(stop - start) != atom_size)
return 0;
return atom_size;
}
void Chapter::Display::Init() {
title_ = NULL;
language_ = NULL;
country_ = NULL;
}
void Chapter::Display::Clear() {
StrCpy(NULL, &title_);
StrCpy(NULL, &language_);
StrCpy(NULL, &country_);
}
bool Chapter::Display::set_title(const char* title) {
return StrCpy(title, &title_);
}
bool Chapter::Display::set_language(const char* language) {
return StrCpy(language, &language_);
}
bool Chapter::Display::set_country(const char* country) {
return StrCpy(country, &country_);
}
uint64_t Chapter::Display::WriteDisplay(IMkvWriter* writer) const {
uint64_t payload_size = EbmlElementSize(libwebm::kMkvChapString, title_);
if (language_)
payload_size += EbmlElementSize(libwebm::kMkvChapLanguage, language_);
if (country_)
payload_size += EbmlElementSize(libwebm::kMkvChapCountry, country_);
const uint64_t display_size =
EbmlMasterElementSize(libwebm::kMkvChapterDisplay, payload_size) +
payload_size;
if (writer == NULL)
return display_size;
const int64_t start = writer->Position();
if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapterDisplay,
payload_size))
return 0;
if (!WriteEbmlElement(writer, libwebm::kMkvChapString, title_))
return 0;
if (language_) {
if (!WriteEbmlElement(writer, libwebm::kMkvChapLanguage, language_))
return 0;
}
if (country_) {
if (!WriteEbmlElement(writer, libwebm::kMkvChapCountry, country_))
return 0;
}
const int64_t stop = writer->Position();
if (stop >= start && uint64_t(stop - start) != display_size)
return 0;
return display_size;
}
///////////////////////////////////////////////////////////////
//
// Chapters Class
Chapters::Chapters() : chapters_size_(0), chapters_count_(0), chapters_(NULL) {}
Chapters::~Chapters() {
while (chapters_count_ > 0) {
Chapter& chapter = chapters_[--chapters_count_];
chapter.Clear();
}
delete[] chapters_;
chapters_ = NULL;
}
int Chapters::Count() const { return chapters_count_; }
Chapter* Chapters::AddChapter(unsigned int* seed) {
if (!ExpandChaptersArray())
return NULL;
Chapter& chapter = chapters_[chapters_count_++];
chapter.Init(seed);
return &chapter;
}
bool Chapters::Write(IMkvWriter* writer) const {
if (writer == NULL)
return false;
const uint64_t payload_size = WriteEdition(NULL); // return size only
if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapters, payload_size))
return false;
const int64_t start = writer->Position();
if (WriteEdition(writer) == 0) // error
return false;
const int64_t stop = writer->Position();
if (stop >= start && uint64_t(stop - start) != payload_size)
return false;
return true;
}
bool Chapters::ExpandChaptersArray() {
if (chapters_size_ > chapters_count_)
return true; // nothing to do yet
const int size = (chapters_size_ == 0) ? 1 : 2 * chapters_size_;
Chapter* const chapters = new (std::nothrow) Chapter[size]; // NOLINT
if (chapters == NULL)
return false;
for (int idx = 0; idx < chapters_count_; ++idx) {
const Chapter& src = chapters_[idx];
Chapter* const dst = chapters + idx;
src.ShallowCopy(dst);
}
delete[] chapters_;
chapters_ = chapters;
chapters_size_ = size;
return true;
}
uint64_t Chapters::WriteEdition(IMkvWriter* writer) const {
uint64_t payload_size = 0;
for (int idx = 0; idx < chapters_count_; ++idx) {
const Chapter& chapter = chapters_[idx];
payload_size += chapter.WriteAtom(NULL);
}
const uint64_t edition_size =
EbmlMasterElementSize(libwebm::kMkvEditionEntry, payload_size) +
payload_size;
if (writer == NULL) // return size only
return edition_size;
const int64_t start = writer->Position();
if (!WriteEbmlMasterElement(writer, libwebm::kMkvEditionEntry, payload_size))
return 0; // error
for (int idx = 0; idx < chapters_count_; ++idx) {
const Chapter& chapter = chapters_[idx];
const uint64_t chapter_size = chapter.WriteAtom(writer);
if (chapter_size == 0) // error
return 0;
}
const int64_t stop = writer->Position();
if (stop >= start && uint64_t(stop - start) != edition_size)
return 0;
return edition_size;
}
// Tag Class
bool Tag::add_simple_tag(const char* tag_name, const char* tag_string) {
if (!ExpandSimpleTagsArray())
return false;
SimpleTag& st = simple_tags_[simple_tags_count_++];
st.Init();
if (!st.set_tag_name(tag_name))
return false;
if (!st.set_tag_string(tag_string))
return false;
return true;
}
Tag::Tag() {
simple_tags_ = NULL;
simple_tags_size_ = 0;
simple_tags_count_ = 0;
}
Tag::~Tag() {}
void Tag::ShallowCopy(Tag* dst) const {
dst->simple_tags_ = simple_tags_;
dst->simple_tags_size_ = simple_tags_size_;
dst->simple_tags_count_ = simple_tags_count_;
}
void Tag::Clear() {
while (simple_tags_count_ > 0) {
SimpleTag& st = simple_tags_[--simple_tags_count_];
st.Clear();
}
delete[] simple_tags_;
simple_tags_ = NULL;
simple_tags_size_ = 0;
}
bool Tag::ExpandSimpleTagsArray() {
if (simple_tags_size_ > simple_tags_count_)
return true; // nothing to do yet
const int size = (simple_tags_size_ == 0) ? 1 : 2 * simple_tags_size_;
SimpleTag* const simple_tags = new (std::nothrow) SimpleTag[size]; // NOLINT
if (simple_tags == NULL)
return false;
for (int idx = 0; idx < simple_tags_count_; ++idx) {
simple_tags[idx] = simple_tags_[idx]; // shallow copy
}
delete[] simple_tags_;
simple_tags_ = simple_tags;
simple_tags_size_ = size;
return true;
}
uint64_t Tag::Write(IMkvWriter* writer) const {
uint64_t payload_size = 0;
for (int idx = 0; idx < simple_tags_count_; ++idx) {
const SimpleTag& st = simple_tags_[idx];
payload_size += st.Write(NULL);
}
const uint64_t tag_size =
EbmlMasterElementSize(libwebm::kMkvTag, payload_size) + payload_size;
if (writer == NULL)
return tag_size;
const int64_t start = writer->Position();
if (!WriteEbmlMasterElement(writer, libwebm::kMkvTag, payload_size))
return 0;
for (int idx = 0; idx < simple_tags_count_; ++idx) {
const SimpleTag& st = simple_tags_[idx];
if (!st.Write(writer))
return 0;
}
const int64_t stop = writer->Position();
if (stop >= start && uint64_t(stop - start) != tag_size)
return 0;
return tag_size;
}
// Tag::SimpleTag
void Tag::SimpleTag::Init() {
tag_name_ = NULL;
tag_string_ = NULL;
}
void Tag::SimpleTag::Clear() {
StrCpy(NULL, &tag_name_);
StrCpy(NULL, &tag_string_);
}
bool Tag::SimpleTag::set_tag_name(const char* tag_name) {
return StrCpy(tag_name, &tag_name_);
}
bool Tag::SimpleTag::set_tag_string(const char* tag_string) {
return StrCpy(tag_string, &tag_string_);
}
uint64_t Tag::SimpleTag::Write(IMkvWriter* writer) const {
uint64_t payload_size = EbmlElementSize(libwebm::kMkvTagName, tag_name_);
payload_size += EbmlElementSize(libwebm::kMkvTagString, tag_string_);
const uint64_t simple_tag_size =
EbmlMasterElementSize(libwebm::kMkvSimpleTag, payload_size) +
payload_size;
if (writer == NULL)
return simple_tag_size;
const int64_t start = writer->Position();
if (!WriteEbmlMasterElement(writer, libwebm::kMkvSimpleTag, payload_size))
return 0;
if (!WriteEbmlElement(writer, libwebm::kMkvTagName, tag_name_))
return 0;
if (!WriteEbmlElement(writer, libwebm::kMkvTagString, tag_string_))
return 0;
const int64_t stop = writer->Position();
if (stop >= start && uint64_t(stop - start) != simple_tag_size)
return 0;
return simple_tag_size;
}
// Tags Class
Tags::Tags() : tags_size_(0), tags_count_(0), tags_(NULL) {}
Tags::~Tags() {
while (tags_count_ > 0) {
Tag& tag = tags_[--tags_count_];
tag.Clear();
}
delete[] tags_;
tags_ = NULL;
}
int Tags::Count() const { return tags_count_; }
Tag* Tags::AddTag() {
if (!ExpandTagsArray())
return NULL;
Tag& tag = tags_[tags_count_++];
return &tag;
}
bool Tags::Write(IMkvWriter* writer) const {
if (writer == NULL)
return false;
uint64_t payload_size = 0;
for (int idx = 0; idx < tags_count_; ++idx) {
const Tag& tag = tags_[idx];
payload_size += tag.Write(NULL);
}
if (!WriteEbmlMasterElement(writer, libwebm::kMkvTags, payload_size))
return false;
const int64_t start = writer->Position();
for (int idx = 0; idx < tags_count_; ++idx) {
const Tag& tag = tags_[idx];
const uint64_t tag_size = tag.Write(writer);
if (tag_size == 0) // error
return 0;
}
const int64_t stop = writer->Position();
if (stop >= start && uint64_t(stop - start) != payload_size)
return false;
return true;
}
bool Tags::ExpandTagsArray() {
if (tags_size_ > tags_count_)
return true; // nothing to do yet
const int size = (tags_size_ == 0) ? 1 : 2 * tags_size_;
Tag* const tags = new (std::nothrow) Tag[size]; // NOLINT
if (tags == NULL)
return false;
for (int idx = 0; idx < tags_count_; ++idx) {
const Tag& src = tags_[idx];
Tag* const dst = tags + idx;
src.ShallowCopy(dst);
}
delete[] tags_;
tags_ = tags;
tags_size_ = size;
return true;
}
///////////////////////////////////////////////////////////////
//
// Cluster class
Cluster::Cluster(uint64_t timecode, int64_t cues_pos, uint64_t timecode_scale,
bool write_last_frame_with_duration, bool fixed_size_timecode)
: blocks_added_(0),
finalized_(false),
fixed_size_timecode_(fixed_size_timecode),
header_written_(false),
payload_size_(0),
position_for_cues_(cues_pos),
size_position_(-1),
timecode_(timecode),
timecode_scale_(timecode_scale),
write_last_frame_with_duration_(write_last_frame_with_duration),
writer_(NULL) {}
Cluster::~Cluster() {
// Delete any stored frames that are left behind. This will happen if the
// Cluster was not Finalized for whatever reason.
while (!stored_frames_.empty()) {
while (!stored_frames_.begin()->second.empty()) {
delete stored_frames_.begin()->second.front();
stored_frames_.begin()->second.pop_front();
}
stored_frames_.erase(stored_frames_.begin()->first);
}
}
bool Cluster::Init(IMkvWriter* ptr_writer) {
if (!ptr_writer) {
return false;
}
writer_ = ptr_writer;
return true;
}
bool Cluster::AddFrame(const Frame* const frame) {
return QueueOrWriteFrame(frame);
}
bool Cluster::AddFrame(const uint8_t* data, uint64_t length,
uint64_t track_number, uint64_t abs_timecode,
bool is_key) {
Frame frame;
if (!frame.Init(data, length))
return false;
frame.set_track_number(track_number);
frame.set_timestamp(abs_timecode);
frame.set_is_key(is_key);
return QueueOrWriteFrame(&frame);
}
bool Cluster::AddFrameWithAdditional(const uint8_t* data, uint64_t length,
const uint8_t* additional,
uint64_t additional_length,
uint64_t add_id, uint64_t track_number,
uint64_t abs_timecode, bool is_key) {
if (!additional || additional_length == 0) {
return false;
}
Frame frame;
if (!frame.Init(data, length) ||
!frame.AddAdditionalData(additional, additional_length, add_id)) {
return false;
}
frame.set_track_number(track_number);
frame.set_timestamp(abs_timecode);
frame.set_is_key(is_key);
return QueueOrWriteFrame(&frame);
}
bool Cluster::AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length,
int64_t discard_padding,
uint64_t track_number,
uint64_t abs_timecode, bool is_key) {
Frame frame;
if (!frame.Init(data, length))
return false;
frame.set_discard_padding(discard_padding);
frame.set_track_number(track_number);
frame.set_timestamp(abs_timecode);
frame.set_is_key(is_key);
return QueueOrWriteFrame(&frame);
}
bool Cluster::AddMetadata(const uint8_t* data, uint64_t length,
uint64_t track_number, uint64_t abs_timecode,
uint64_t duration_timecode) {
Frame frame;
if (!frame.Init(data, length))
return false;
frame.set_track_number(track_number);
frame.set_timestamp(abs_timecode);
frame.set_duration(duration_timecode);
frame.set_is_key(true); // All metadata blocks are keyframes.
return QueueOrWriteFrame(&frame);
}
void Cluster::AddPayloadSize(uint64_t size) { payload_size_ += size; }
bool Cluster::Finalize() {
return !write_last_frame_with_duration_ && Finalize(false, 0);
}
bool Cluster::Finalize(bool set_last_frame_duration, uint64_t duration) {
if (!writer_ || finalized_)
return false;
if (write_last_frame_with_duration_) {
// Write out held back Frames. This essentially performs a k-way merge
// across all tracks in the increasing order of timestamps.
while (!stored_frames_.empty()) {
Frame* frame = stored_frames_.begin()->second.front();
// Get the next frame to write (frame with least timestamp across all
// tracks).
for (FrameMapIterator frames_iterator = ++stored_frames_.begin();
frames_iterator != stored_frames_.end(); ++frames_iterator) {
if (frames_iterator->second.front()->timestamp() < frame->timestamp()) {
frame = frames_iterator->second.front();
}
}
// Set the duration if it's the last frame for the track.
if (set_last_frame_duration &&
stored_frames_[frame->track_number()].size() == 1 &&
!frame->duration_set()) {
frame->set_duration(duration - frame->timestamp());
if (!frame->is_key() && !frame->reference_block_timestamp_set()) {
frame->set_reference_block_timestamp(
last_block_timestamp_[frame->track_number()]);
}
}
// Write the frame and remove it from |stored_frames_|.
const bool wrote_frame = DoWriteFrame(frame);
stored_frames_[frame->track_number()].pop_front();
if (stored_frames_[frame->track_number()].empty()) {
stored_frames_.erase(frame->track_number());
}
delete frame;
if (!wrote_frame)
return false;
}
}
if (size_position_ == -1)
return false;
if (writer_->Seekable()) {
const int64_t pos = writer_->Position();
if (writer_->Position(size_position_))
return false;
if (WriteUIntSize(writer_, payload_size(), 8))
return false;
if (writer_->Position(pos))
return false;
}
finalized_ = true;
return true;
}
uint64_t Cluster::Size() const {
const uint64_t element_size =
EbmlMasterElementSize(libwebm::kMkvCluster, 0xFFFFFFFFFFFFFFFFULL) +
payload_size_;
return element_size;
}
bool Cluster::PreWriteBlock() {
if (finalized_)
return false;
if (!header_written_) {
if (!WriteClusterHeader())
return false;
}
return true;
}
void Cluster::PostWriteBlock(uint64_t element_size) {
AddPayloadSize(element_size);
++blocks_added_;
}
int64_t Cluster::GetRelativeTimecode(int64_t abs_timecode) const {
const int64_t cluster_timecode = this->Cluster::timecode();
const int64_t rel_timecode =
static_cast<int64_t>(abs_timecode) - cluster_timecode;
if (rel_timecode < 0 || rel_timecode > kMaxBlockTimecode)
return -1;
return rel_timecode;
}
bool Cluster::DoWriteFrame(const Frame* const frame) {
if (!frame || !frame->IsValid())
return false;
if (!PreWriteBlock())
return false;
const uint64_t element_size = WriteFrame(writer_, frame, this);
if (element_size == 0)
return false;
PostWriteBlock(element_size);
last_block_timestamp_[frame->track_number()] = frame->timestamp();
return true;
}
bool Cluster::QueueOrWriteFrame(const Frame* const frame) {
if (!frame || !frame->IsValid())
return false;
// If |write_last_frame_with_duration_| is not set, then write the frame right
// away.
if (!write_last_frame_with_duration_) {
return DoWriteFrame(frame);
}
// Queue the current frame.
uint64_t track_number = frame->track_number();
Frame* const frame_to_store = new Frame();
frame_to_store->CopyFrom(*frame);
stored_frames_[track_number].push_back(frame_to_store);
// Iterate through all queued frames in the current track except the last one
// and write it if it is okay to do so (i.e.) no other track has an held back
// frame with timestamp <= the timestamp of the frame in question.
std::vector<std::list<Frame*>::iterator> frames_to_erase;
for (std::list<Frame *>::iterator
current_track_iterator = stored_frames_[track_number].begin(),
end = --stored_frames_[track_number].end();
current_track_iterator != end; ++current_track_iterator) {
const Frame* const frame_to_write = *current_track_iterator;
bool okay_to_write = true;
for (FrameMapIterator track_iterator = stored_frames_.begin();
track_iterator != stored_frames_.end(); ++track_iterator) {
if (track_iterator->first == track_number) {
continue;
}
if (track_iterator->second.front()->timestamp() <
frame_to_write->timestamp()) {
okay_to_write = false;
break;
}
}
if (okay_to_write) {
const bool wrote_frame = DoWriteFrame(frame_to_write);
delete frame_to_write;
if (!wrote_frame)
return false;
frames_to_erase.push_back(current_track_iterator);
} else {
break;
}
}
for (std::vector<std::list<Frame*>::iterator>::iterator iterator =
frames_to_erase.begin();
iterator != frames_to_erase.end(); ++iterator) {
stored_frames_[track_number].erase(*iterator);
}
return true;
}
bool Cluster::WriteClusterHeader() {
if (finalized_)
return false;
if (WriteID(writer_, libwebm::kMkvCluster))
return false;
// Save for later.
size_position_ = writer_->Position();
// Write "unknown" (EBML coded -1) as cluster size value. We need to write 8
// bytes because we do not know how big our cluster will be.
if (SerializeInt(writer_, kEbmlUnknownValue, 8))
return false;
if (!WriteEbmlElement(writer_, libwebm::kMkvTimecode, timecode(),
fixed_size_timecode_ ? 8 : 0)) {
return false;
}
AddPayloadSize(EbmlElementSize(libwebm::kMkvTimecode, timecode(),
fixed_size_timecode_ ? 8 : 0));
header_written_ = true;
return true;
}
///////////////////////////////////////////////////////////////
//
// SeekHead Class
SeekHead::SeekHead() : start_pos_(0ULL) {
for (int32_t i = 0; i < kSeekEntryCount; ++i) {
seek_entry_id_[i] = 0;
seek_entry_pos_[i] = 0;
}
}
SeekHead::~SeekHead() {}
bool SeekHead::Finalize(IMkvWriter* writer) const {
if (writer->Seekable()) {
if (start_pos_ == -1)
return false;
uint64_t payload_size = 0;
uint64_t entry_size[kSeekEntryCount];
for (int32_t i = 0; i < kSeekEntryCount; ++i) {
if (seek_entry_id_[i] != 0) {
entry_size[i] = EbmlElementSize(libwebm::kMkvSeekID,
static_cast<uint64>(seek_entry_id_[i]));
entry_size[i] += EbmlElementSize(
libwebm::kMkvSeekPosition, static_cast<uint64>(seek_entry_pos_[i]));
payload_size +=
EbmlMasterElementSize(libwebm::kMkvSeek, entry_size[i]) +
entry_size[i];
}
}
// No SeekHead elements
if (payload_size == 0)
return true;
const int64_t pos = writer->Position();
if (writer->Position(start_pos_))
return false;
if (!WriteEbmlMasterElement(writer, libwebm::kMkvSeekHead, payload_size))
return false;
for (int32_t i = 0; i < kSeekEntryCount; ++i) {
if (seek_entry_id_[i] != 0) {
if (!WriteEbmlMasterElement(writer, libwebm::kMkvSeek, entry_size[i]))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvSeekID,
static_cast<uint64>(seek_entry_id_[i])))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvSeekPosition,
static_cast<uint64>(seek_entry_pos_[i])))
return false;
}
}
const uint64_t total_entry_size = kSeekEntryCount * MaxEntrySize();
const uint64_t total_size =
EbmlMasterElementSize(libwebm::kMkvSeekHead, total_entry_size) +
total_entry_size;
const int64_t size_left = total_size - (writer->Position() - start_pos_);
const uint64_t bytes_written = WriteVoidElement(writer, size_left);
if (!bytes_written)
return false;
if (writer->Position(pos))
return false;
}
return true;
}
bool SeekHead::Write(IMkvWriter* writer) {
const uint64_t entry_size = kSeekEntryCount * MaxEntrySize();
const uint64_t size =
EbmlMasterElementSize(libwebm::kMkvSeekHead, entry_size);
start_pos_ = writer->Position();
const uint64_t bytes_written = WriteVoidElement(writer, size + entry_size);
if (!bytes_written)
return false;
return true;
}
bool SeekHead::AddSeekEntry(uint32_t id, uint64_t pos) {
for (int32_t i = 0; i < kSeekEntryCount; ++i) {
if (seek_entry_id_[i] == 0) {
seek_entry_id_[i] = id;
seek_entry_pos_[i] = pos;
return true;
}
}
return false;
}
uint32_t SeekHead::GetId(int index) const {
if (index < 0 || index >= kSeekEntryCount)
return UINT_MAX;
return seek_entry_id_[index];
}
uint64_t SeekHead::GetPosition(int index) const {
if (index < 0 || index >= kSeekEntryCount)
return ULLONG_MAX;
return seek_entry_pos_[index];
}
bool SeekHead::SetSeekEntry(int index, uint32_t id, uint64_t position) {
if (index < 0 || index >= kSeekEntryCount)
return false;
seek_entry_id_[index] = id;
seek_entry_pos_[index] = position;
return true;
}
uint64_t SeekHead::MaxEntrySize() const {
const uint64_t max_entry_payload_size =
EbmlElementSize(libwebm::kMkvSeekID,
static_cast<uint64>(UINT64_C(0xffffffff))) +
EbmlElementSize(libwebm::kMkvSeekPosition,
static_cast<uint64>(UINT64_C(0xffffffffffffffff)));
const uint64_t max_entry_size =
EbmlMasterElementSize(libwebm::kMkvSeek, max_entry_payload_size) +
max_entry_payload_size;
return max_entry_size;
}
///////////////////////////////////////////////////////////////
//
// SegmentInfo Class
SegmentInfo::SegmentInfo()
: duration_(-1.0),
muxing_app_(NULL),
timecode_scale_(1000000ULL),
writing_app_(NULL),
date_utc_(LLONG_MIN),
duration_pos_(-1) {}
SegmentInfo::~SegmentInfo() {
delete[] muxing_app_;
delete[] writing_app_;
}
bool SegmentInfo::Init() {
int32_t major;
int32_t minor;
int32_t build;
int32_t revision;
GetVersion(&major, &minor, &build, &revision);
char temp[256];
#ifdef _MSC_VER
sprintf_s(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major,
minor, build, revision);
#else
snprintf(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major,
minor, build, revision);
#endif
const size_t app_len = strlen(temp) + 1;
delete[] muxing_app_;
muxing_app_ = new (std::nothrow) char[app_len]; // NOLINT
if (!muxing_app_)
return false;
#ifdef _MSC_VER
strcpy_s(muxing_app_, app_len, temp);
#else
strcpy(muxing_app_, temp);
#endif
set_writing_app(temp);
if (!writing_app_)
return false;
return true;
}
bool SegmentInfo::Finalize(IMkvWriter* writer) const {
if (!writer)
return false;
if (duration_ > 0.0) {
if (writer->Seekable()) {
if (duration_pos_ == -1)
return false;
const int64_t pos = writer->Position();
if (writer->Position(duration_pos_))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvDuration,
static_cast<float>(duration_)))
return false;
if (writer->Position(pos))
return false;
}
}
return true;
}
bool SegmentInfo::Write(IMkvWriter* writer) {
if (!writer || !muxing_app_ || !writing_app_)
return false;
uint64_t size = EbmlElementSize(libwebm::kMkvTimecodeScale,
static_cast<uint64>(timecode_scale_));
if (duration_ > 0.0)
size +=
EbmlElementSize(libwebm::kMkvDuration, static_cast<float>(duration_));
if (date_utc_ != LLONG_MIN)
size += EbmlDateElementSize(libwebm::kMkvDateUTC);
size += EbmlElementSize(libwebm::kMkvMuxingApp, muxing_app_);
size += EbmlElementSize(libwebm::kMkvWritingApp, writing_app_);
if (!WriteEbmlMasterElement(writer, libwebm::kMkvInfo, size))
return false;
const int64_t payload_position = writer->Position();
if (payload_position < 0)
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvTimecodeScale,
static_cast<uint64>(timecode_scale_)))
return false;
if (duration_ > 0.0) {
// Save for later
duration_pos_ = writer->Position();
if (!WriteEbmlElement(writer, libwebm::kMkvDuration,
static_cast<float>(duration_)))
return false;
}
if (date_utc_ != LLONG_MIN)
WriteEbmlDateElement(writer, libwebm::kMkvDateUTC, date_utc_);
if (!WriteEbmlElement(writer, libwebm::kMkvMuxingApp, muxing_app_))
return false;
if (!WriteEbmlElement(writer, libwebm::kMkvWritingApp, writing_app_))
return false;
const int64_t stop_position = writer->Position();
if (stop_position < 0 ||
stop_position - payload_position != static_cast<int64_t>(size))
return false;
return true;
}
void SegmentInfo::set_muxing_app(const char* app) {
if (app) {
const size_t length = strlen(app) + 1;
char* temp_str = new (std::nothrow) char[length]; // NOLINT
if (!temp_str)
return;
#ifdef _MSC_VER
strcpy_s(temp_str, length, app);
#else
strcpy(temp_str, app);
#endif
delete[] muxing_app_;
muxing_app_ = temp_str;
}
}
void SegmentInfo::set_writing_app(const char* app) {
if (app) {
const size_t length = strlen(app) + 1;
char* temp_str = new (std::nothrow) char[length]; // NOLINT
if (!temp_str)
return;
#ifdef _MSC_VER
strcpy_s(temp_str, length, app);
#else
strcpy(temp_str, app);
#endif
delete[] writing_app_;
writing_app_ = temp_str;
}
}
///////////////////////////////////////////////////////////////
//
// Segment Class
Segment::Segment()
: chunk_count_(0),
chunk_name_(NULL),
chunk_writer_cluster_(NULL),
chunk_writer_cues_(NULL),
chunk_writer_header_(NULL),
chunking_(false),
chunking_base_name_(NULL),
cluster_list_(NULL),
cluster_list_capacity_(0),
cluster_list_size_(0),
cues_position_(kAfterClusters),
cues_track_(0),
force_new_cluster_(false),
frames_(NULL),
frames_capacity_(0),
frames_size_(0),
has_video_(false),
header_written_(false),
last_block_duration_(0),
last_timestamp_(0),
max_cluster_duration_(kDefaultMaxClusterDuration),
max_cluster_size_(0),
mode_(kFile),
new_cuepoint_(false),
output_cues_(true),
accurate_cluster_duration_(false),
fixed_size_cluster_timecode_(false),
estimate_file_duration_(true),
payload_pos_(0),
size_position_(0),
doc_type_version_(kDefaultDocTypeVersion),
doc_type_version_written_(0),
duration_(0.0),
writer_cluster_(NULL),
writer_cues_(NULL),
writer_header_(NULL) {
const time_t curr_time = time(NULL);
seed_ = static_cast<unsigned int>(curr_time);
#ifdef _WIN32
srand(seed_);
#endif
}
Segment::~Segment() {
if (cluster_list_) {
for (int32_t i = 0; i < cluster_list_size_; ++i) {
Cluster* const cluster = cluster_list_[i];
delete cluster;
}
delete[] cluster_list_;
}
if (frames_) {
for (int32_t i = 0; i < frames_size_; ++i) {
Frame* const frame = frames_[i];
delete frame;
}
delete[] frames_;
}
delete[] chunk_name_;
delete[] chunking_base_name_;
if (chunk_writer_cluster_) {
chunk_writer_cluster_->Close();
delete chunk_writer_cluster_;
}
if (chunk_writer_cues_) {
chunk_writer_cues_->Close();
delete chunk_writer_cues_;
}
if (chunk_writer_header_) {
chunk_writer_header_->Close();
delete chunk_writer_header_;
}
}
void Segment::MoveCuesBeforeClustersHelper(uint64_t diff, int32_t index,
uint64_t* cues_size) {
CuePoint* const cue_point = cues_.GetCueByIndex(index);
if (cue_point == NULL)
return;
const uint64_t old_cue_point_size = cue_point->Size();
const uint64_t cluster_pos = cue_point->cluster_pos() + diff;
cue_point->set_cluster_pos(cluster_pos); // update the new cluster position
// New size of the cue is computed as follows
// Let a = current sum of size of all CuePoints
// Let b = Increase in Cue Point's size due to this iteration
// Let c = Increase in size of Cues Element's length due to this iteration
// (This is computed as CodedSize(a + b) - CodedSize(a))
// Let d = b + c. Now d is the |diff| passed to the next recursive call.
// Let e = a + b. Now e is the |cues_size| passed to the next recursive
// call.
const uint64_t cue_point_size_diff = cue_point->Size() - old_cue_point_size;
const uint64_t cue_size_diff =
GetCodedUIntSize(*cues_size + cue_point_size_diff) -
GetCodedUIntSize(*cues_size);
*cues_size += cue_point_size_diff;
diff = cue_size_diff + cue_point_size_diff;
if (diff > 0) {
for (int32_t i = 0; i < cues_.cue_entries_size(); ++i) {
MoveCuesBeforeClustersHelper(diff, i, cues_size);
}
}
}
void Segment::MoveCuesBeforeClusters() {
const uint64_t current_cue_size = cues_.Size();
uint64_t cue_size = 0;
for (int32_t i = 0; i < cues_.cue_entries_size(); ++i)
cue_size += cues_.GetCueByIndex(i)->Size();
for (int32_t i = 0; i < cues_.cue_entries_size(); ++i)
MoveCuesBeforeClustersHelper(current_cue_size, i, &cue_size);
// Adjust the Seek Entry to reflect the change in position
// of Cluster and Cues
int32_t cluster_index = 0;
int32_t cues_index = 0;
for (int32_t i = 0; i < SeekHead::kSeekEntryCount; ++i) {
if (seek_head_.GetId(i) == libwebm::kMkvCluster)
cluster_index = i;
if (seek_head_.GetId(i) == libwebm::kMkvCues)
cues_index = i;
}
seek_head_.SetSeekEntry(cues_index, libwebm::kMkvCues,
seek_head_.GetPosition(cluster_index));
seek_head_.SetSeekEntry(cluster_index, libwebm::kMkvCluster,
cues_.Size() + seek_head_.GetPosition(cues_index));
}
bool Segment::Init(IMkvWriter* ptr_writer) {
if (!ptr_writer) {
return false;
}
writer_cluster_ = ptr_writer;
writer_cues_ = ptr_writer;
writer_header_ = ptr_writer;
memset(&track_frames_written_, 0,
sizeof(track_frames_written_[0]) * kMaxTrackNumber);
memset(&last_track_timestamp_, 0,
sizeof(last_track_timestamp_[0]) * kMaxTrackNumber);
return segment_info_.Init();
}
bool Segment::CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader,
IMkvWriter* writer) {
if (!writer->Seekable() || chunking_)
return false;
const int64_t cluster_offset =
cluster_list_[0]->size_position() - GetUIntSize(libwebm::kMkvCluster);
// Copy the headers.
if (!ChunkedCopy(reader, writer, 0, cluster_offset))
return false;
// Recompute cue positions and seek entries.
MoveCuesBeforeClusters();
// Write cues and seek entries.
// TODO(vigneshv): As of now, it's safe to call seek_head_.Finalize() for the
// second time with a different writer object. But the name Finalize() doesn't
// indicate something we want to call more than once. So consider renaming it
// to write() or some such.
if (!cues_.Write(writer) || !seek_head_.Finalize(writer))
return false;
// Copy the Clusters.
if (!ChunkedCopy(reader, writer, cluster_offset,
cluster_end_offset_ - cluster_offset))
return false;
// Update the Segment size in case the Cues size has changed.
const int64_t pos = writer->Position();
const int64_t segment_size = writer->Position() - payload_pos_;
if (writer->Position(size_position_) ||
WriteUIntSize(writer, segment_size, 8) || writer->Position(pos))
return false;
return true;
}
bool Segment::Finalize() {
if (WriteFramesAll() < 0)
return false;
// In kLive mode, call Cluster::Finalize only if |accurate_cluster_duration_|
// is set. In all other modes, always call Cluster::Finalize.
if ((mode_ == kLive ? accurate_cluster_duration_ : true) &&
cluster_list_size_ > 0) {
// Update last cluster's size
Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
// For the last frame of the last Cluster, we don't write it as a BlockGroup
// with Duration unless the frame itself has duration set explicitly.
if (!old_cluster || !old_cluster->Finalize(false, 0))
return false;
}
if (mode_ == kFile) {
if (chunking_ && chunk_writer_cluster_) {
chunk_writer_cluster_->Close();
chunk_count_++;
}
double duration =
(static_cast<double>(last_timestamp_) + last_block_duration_) /
segment_info_.timecode_scale();
if (duration_ > 0.0) {
duration = duration_;
} else {
if (last_block_duration_ == 0 && estimate_file_duration_) {
const int num_tracks = static_cast<int>(tracks_.track_entries_size());
for (int i = 0; i < num_tracks; ++i) {
if (track_frames_written_[i] < 2)
continue;
// Estimate the duration for the last block of a Track.
const double nano_per_frame =
static_cast<double>(last_track_timestamp_[i]) /
(track_frames_written_[i] - 1);
const double track_duration =
(last_track_timestamp_[i] + nano_per_frame) /
segment_info_.timecode_scale();
if (track_duration > duration)
duration = track_duration;
}
}
}
segment_info_.set_duration(duration);
if (!segment_info_.Finalize(writer_header_))
return false;
if (output_cues_)
if (!seek_head_.AddSeekEntry(libwebm::kMkvCues, MaxOffset()))
return false;
if (chunking_) {
if (!chunk_writer_cues_)
return false;
char* name = NULL;
if (!UpdateChunkName("cues", &name))
return false;
const bool cues_open = chunk_writer_cues_->Open(name);
delete[] name;
if (!cues_open)
return false;
}
cluster_end_offset_ = writer_cluster_->Position();
// Write the seek headers and cues
if (output_cues_)
if (!cues_.Write(writer_cues_))
return false;
if (!seek_head_.Finalize(writer_header_))
return false;
if (writer_header_->Seekable()) {
if (size_position_ == -1)
return false;
const int64_t segment_size = MaxOffset();
if (segment_size < 1)
return false;
const int64_t pos = writer_header_->Position();
UpdateDocTypeVersion();
if (doc_type_version_ != doc_type_version_written_) {
if (writer_header_->Position(0))
return false;
const char* const doc_type =
DocTypeIsWebm() ? kDocTypeWebm : kDocTypeMatroska;
if (!WriteEbmlHeader(writer_header_, doc_type_version_, doc_type))
return false;
if (writer_header_->Position() != ebml_header_size_)
return false;
doc_type_version_written_ = doc_type_version_;
}
if (writer_header_->Position(size_position_))
return false;
if (WriteUIntSize(writer_header_, segment_size, 8))
return false;
if (writer_header_->Position(pos))
return false;
}
if (chunking_) {
// Do not close any writers until the segment size has been written,
// otherwise the size may be off.
if (!chunk_writer_cues_ || !chunk_writer_header_)
return false;
chunk_writer_cues_->Close();
chunk_writer_header_->Close();
}
}
return true;
}
Track* Segment::AddTrack(int32_t number) {
Track* const track = new (std::nothrow) Track(&seed_); // NOLINT
if (!track)
return NULL;
if (!tracks_.AddTrack(track, number)) {
delete track;
return NULL;
}
return track;
}
Chapter* Segment::AddChapter() { return chapters_.AddChapter(&seed_); }
Tag* Segment::AddTag() { return tags_.AddTag(); }
uint64_t Segment::AddVideoTrack(int32_t width, int32_t height, int32_t number) {
VideoTrack* const track = new (std::nothrow) VideoTrack(&seed_); // NOLINT
if (!track)
return 0;
track->set_type(Tracks::kVideo);
track->set_codec_id(Tracks::kVp8CodecId);
track->set_width(width);
track->set_height(height);
tracks_.AddTrack(track, number);
has_video_ = true;
return track->number();
}
bool Segment::AddCuePoint(uint64_t timestamp, uint64_t track) {
if (cluster_list_size_ < 1)
return false;
const Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
if (!cluster)
return false;
CuePoint* const cue = new (std::nothrow) CuePoint(); // NOLINT
if (!cue)
return false;
cue->set_time(timestamp / segment_info_.timecode_scale());
cue->set_block_number(cluster->blocks_added());
cue->set_cluster_pos(cluster->position_for_cues());
cue->set_track(track);
if (!cues_.AddCue(cue))
return false;
new_cuepoint_ = false;
return true;
}
uint64_t Segment::AddAudioTrack(int32_t sample_rate, int32_t channels,
int32_t number) {
AudioTrack* const track = new (std::nothrow) AudioTrack(&seed_); // NOLINT
if (!track)
return 0;
track->set_type(Tracks::kAudio);
track->set_codec_id(Tracks::kVorbisCodecId);
track->set_sample_rate(sample_rate);
track->set_channels(channels);
tracks_.AddTrack(track, number);
return track->number();
}
bool Segment::AddFrame(const uint8_t* data, uint64_t length,
uint64_t track_number, uint64_t timestamp, bool is_key) {
if (!data)
return false;
Frame frame;
if (!frame.Init(data, length))
return false;
frame.set_track_number(track_number);
frame.set_timestamp(timestamp);
frame.set_is_key(is_key);
return AddGenericFrame(&frame);
}
bool Segment::AddFrameWithAdditional(const uint8_t* data, uint64_t length,
const uint8_t* additional,
uint64_t additional_length,
uint64_t add_id, uint64_t track_number,
uint64_t timestamp, bool is_key) {
if (!data || !additional)
return false;
Frame frame;
if (!frame.Init(data, length) ||
!frame.AddAdditionalData(additional, additional_length, add_id)) {
return false;
}
frame.set_track_number(track_number);
frame.set_timestamp(timestamp);
frame.set_is_key(is_key);
return AddGenericFrame(&frame);
}
bool Segment::AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length,
int64_t discard_padding,
uint64_t track_number,
uint64_t timestamp, bool is_key) {
if (!data)
return false;
Frame frame;
if (!frame.Init(data, length))
return false;
frame.set_discard_padding(discard_padding);
frame.set_track_number(track_number);
frame.set_timestamp(timestamp);
frame.set_is_key(is_key);
return AddGenericFrame(&frame);
}
bool Segment::AddMetadata(const uint8_t* data, uint64_t length,
uint64_t track_number, uint64_t timestamp_ns,
uint64_t duration_ns) {
if (!data)
return false;
Frame frame;
if (!frame.Init(data, length))
return false;
frame.set_track_number(track_number);
frame.set_timestamp(timestamp_ns);
frame.set_duration(duration_ns);
frame.set_is_key(true); // All metadata blocks are keyframes.
return AddGenericFrame(&frame);
}
bool Segment::AddGenericFrame(const Frame* frame) {
if (!frame)
return false;
if (!CheckHeaderInfo())
return false;
// Check for non-monotonically increasing timestamps.
if (frame->timestamp() < last_timestamp_)
return false;
// Check if the track number is valid.
if (!tracks_.GetTrackByNumber(frame->track_number()))
return false;
if (frame->discard_padding() != 0)
doc_type_version_ = 4;
// If the segment has a video track hold onto audio frames to make sure the
// audio that is associated with the start time of a video key-frame is
// muxed into the same cluster.
if (has_video_ && tracks_.TrackIsAudio(frame->track_number()) &&
!force_new_cluster_) {
Frame* const new_frame = new (std::nothrow) Frame();
if (!new_frame || !new_frame->CopyFrom(*frame))
return false;
if (!QueueFrame(new_frame))
return false;
track_frames_written_[frame->track_number() - 1]++;
return true;
}
if (!DoNewClusterProcessing(frame->track_number(), frame->timestamp(),
frame->is_key())) {
return false;
}
if (cluster_list_size_ < 1)
return false;
Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
if (!cluster)
return false;
// If the Frame is not a SimpleBlock, then set the reference_block_timestamp
// if it is not set already.
bool frame_created = false;
if (!frame->CanBeSimpleBlock() && !frame->is_key() &&
!frame->reference_block_timestamp_set()) {
Frame* const new_frame = new (std::nothrow) Frame();
if (!new_frame->CopyFrom(*frame))
return false;
new_frame->set_reference_block_timestamp(
last_track_timestamp_[frame->track_number() - 1]);
frame = new_frame;
frame_created = true;
}
if (!cluster->AddFrame(frame))
return false;
if (new_cuepoint_ && cues_track_ == frame->track_number()) {
if (!AddCuePoint(frame->timestamp(), cues_track_))
return false;
}
last_timestamp_ = frame->timestamp();
last_track_timestamp_[frame->track_number() - 1] = frame->timestamp();
last_block_duration_ = frame->duration();
track_frames_written_[frame->track_number() - 1]++;
if (frame_created)
delete frame;
return true;
}
void Segment::OutputCues(bool output_cues) { output_cues_ = output_cues; }
void Segment::AccurateClusterDuration(bool accurate_cluster_duration) {
accurate_cluster_duration_ = accurate_cluster_duration;
}
void Segment::UseFixedSizeClusterTimecode(bool fixed_size_cluster_timecode) {
fixed_size_cluster_timecode_ = fixed_size_cluster_timecode;
}
bool Segment::SetChunking(bool chunking, const char* filename) {
if (chunk_count_ > 0)
return false;
if (chunking) {
if (!filename)
return false;
// Check if we are being set to what is already set.
if (chunking_ && !strcmp(filename, chunking_base_name_))
return true;
const size_t name_length = strlen(filename) + 1;
char* const temp = new (std::nothrow) char[name_length]; // NOLINT
if (!temp)
return false;
#ifdef _MSC_VER
strcpy_s(temp, name_length, filename);
#else
strcpy(temp, filename);
#endif
delete[] chunking_base_name_;
chunking_base_name_ = temp;
if (!UpdateChunkName("chk", &chunk_name_))
return false;
if (!chunk_writer_cluster_) {
chunk_writer_cluster_ = new (std::nothrow) MkvWriter(); // NOLINT
if (!chunk_writer_cluster_)
return false;
}
if (!chunk_writer_cues_) {
chunk_writer_cues_ = new (std::nothrow) MkvWriter(); // NOLINT
if (!chunk_writer_cues_)
return false;
}
if (!chunk_writer_header_) {
chunk_writer_header_ = new (std::nothrow) MkvWriter(); // NOLINT
if (!chunk_writer_header_)
return false;
}
if (!chunk_writer_cluster_->Open(chunk_name_))
return false;
const size_t header_length = strlen(filename) + strlen(".hdr") + 1;
char* const header = new (std::nothrow) char[header_length]; // NOLINT
if (!header)
return false;
#ifdef _MSC_VER
strcpy_s(header, header_length - strlen(".hdr"), chunking_base_name_);
strcat_s(header, header_length, ".hdr");
#else
strcpy(header, chunking_base_name_);
strcat(header, ".hdr");
#endif
if (!chunk_writer_header_->Open(header)) {
delete[] header;
return false;
}
writer_cluster_ = chunk_writer_cluster_;
writer_cues_ = chunk_writer_cues_;
writer_header_ = chunk_writer_header_;
delete[] header;
}
chunking_ = chunking;
return true;
}
bool Segment::CuesTrack(uint64_t track_number) {
const Track* const track = GetTrackByNumber(track_number);
if (!track)
return false;
cues_track_ = track_number;
return true;
}
void Segment::ForceNewClusterOnNextFrame() { force_new_cluster_ = true; }
Track* Segment::GetTrackByNumber(uint64_t track_number) const {
return tracks_.GetTrackByNumber(track_number);
}
bool Segment::WriteSegmentHeader() {
UpdateDocTypeVersion();
const char* const doc_type =
DocTypeIsWebm() ? kDocTypeWebm : kDocTypeMatroska;
if (!WriteEbmlHeader(writer_header_, doc_type_version_, doc_type))
return false;
doc_type_version_written_ = doc_type_version_;
ebml_header_size_ = static_cast<int32_t>(writer_header_->Position());
// Write "unknown" (-1) as segment size value. If mode is kFile, Segment
// will write over duration when the file is finalized.
if (WriteID(writer_header_, libwebm::kMkvSegment))
return false;
// Save for later.
size_position_ = writer_header_->Position();
// Write "unknown" (EBML coded -1) as segment size value. We need to write 8
// bytes because if we are going to overwrite the segment size later we do
// not know how big our segment will be.
if (SerializeInt(writer_header_, kEbmlUnknownValue, 8))
return false;
payload_pos_ = writer_header_->Position();
if (mode_ == kFile && writer_header_->Seekable()) {
// Set the duration > 0.0 so SegmentInfo will write out the duration. When
// the muxer is done writing we will set the correct duration and have
// SegmentInfo upadte it.
segment_info_.set_duration(1.0);
if (!seek_head_.Write(writer_header_))
return false;
}
if (!seek_head_.AddSeekEntry(libwebm::kMkvInfo, MaxOffset()))
return false;
if (!segment_info_.Write(writer_header_))
return false;
if (!seek_head_.AddSeekEntry(libwebm::kMkvTracks, MaxOffset()))
return false;
if (!tracks_.Write(writer_header_))
return false;
if (chapters_.Count() > 0) {
if (!seek_head_.AddSeekEntry(libwebm::kMkvChapters, MaxOffset()))
return false;
if (!chapters_.Write(writer_header_))
return false;
}
if (tags_.Count() > 0) {
if (!seek_head_.AddSeekEntry(libwebm::kMkvTags, MaxOffset()))
return false;
if (!tags_.Write(writer_header_))
return false;
}
if (chunking_ && (mode_ == kLive || !writer_header_->Seekable())) {
if (!chunk_writer_header_)
return false;
chunk_writer_header_->Close();
}
header_written_ = true;
return true;
}
// Here we are testing whether to create a new cluster, given a frame
// having time frame_timestamp_ns.
//
int Segment::TestFrame(uint64_t track_number, uint64_t frame_timestamp_ns,
bool is_key) const {
if (force_new_cluster_)
return 1;
// If no clusters have been created yet, then create a new cluster
// and write this frame immediately, in the new cluster. This path
// should only be followed once, the first time we attempt to write
// a frame.
if (cluster_list_size_ <= 0)
return 1;
// There exists at least one cluster. We must compare the frame to
// the last cluster, in order to determine whether the frame is
// written to the existing cluster, or that a new cluster should be
// created.
const uint64_t timecode_scale = segment_info_.timecode_scale();
const uint64_t frame_timecode = frame_timestamp_ns / timecode_scale;
const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1];
const uint64_t last_cluster_timecode = last_cluster->timecode();
// For completeness we test for the case when the frame's timecode
// is less than the cluster's timecode. Although in principle that
// is allowed, this muxer doesn't actually write clusters like that,
// so this indicates a bug somewhere in our algorithm.
if (frame_timecode < last_cluster_timecode) // should never happen
return -1;
// If the frame has a timestamp significantly larger than the last
// cluster (in Matroska, cluster-relative timestamps are serialized
// using a 16-bit signed integer), then we cannot write this frame
// to that cluster, and so we must create a new cluster.
const int64_t delta_timecode = frame_timecode - last_cluster_timecode;
if (delta_timecode > kMaxBlockTimecode)
return 2;
// We decide to create a new cluster when we have a video keyframe.
// This will flush queued (audio) frames, and write the keyframe
// immediately, in the newly-created cluster.
if (is_key && tracks_.TrackIsVideo(track_number))
return 1;
// Create a new cluster if we have accumulated too many frames
// already, where "too many" is defined as "the total time of frames
// in the cluster exceeds a threshold".
const uint64_t delta_ns = delta_timecode * timecode_scale;
if (max_cluster_duration_ > 0 && delta_ns >= max_cluster_duration_)
return 1;
// This is similar to the case above, with the difference that a new
// cluster is created when the size of the current cluster exceeds a
// threshold.
const uint64_t cluster_size = last_cluster->payload_size();
if (max_cluster_size_ > 0 && cluster_size >= max_cluster_size_)
return 1;
// There's no need to create a new cluster, so emit this frame now.
return 0;
}
bool Segment::MakeNewCluster(uint64_t frame_timestamp_ns) {
const int32_t new_size = cluster_list_size_ + 1;
if (new_size > cluster_list_capacity_) {
// Add more clusters.
const int32_t new_capacity =
(cluster_list_capacity_ <= 0) ? 1 : cluster_list_capacity_ * 2;
Cluster** const clusters =
new (std::nothrow) Cluster*[new_capacity]; // NOLINT
if (!clusters)
return false;
for (int32_t i = 0; i < cluster_list_size_; ++i) {
clusters[i] = cluster_list_[i];
}
delete[] cluster_list_;
cluster_list_ = clusters;
cluster_list_capacity_ = new_capacity;
}
if (!WriteFramesLessThan(frame_timestamp_ns))
return false;
if (cluster_list_size_ > 0) {
// Update old cluster's size
Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
if (!old_cluster || !old_cluster->Finalize(true, frame_timestamp_ns))
return false;
}
if (output_cues_)
new_cuepoint_ = true;
if (chunking_ && cluster_list_size_ > 0) {
chunk_writer_cluster_->Close();
chunk_count_++;
if (!UpdateChunkName("chk", &chunk_name_))
return false;
if (!chunk_writer_cluster_->Open(chunk_name_))
return false;
}
const uint64_t timecode_scale = segment_info_.timecode_scale();
const uint64_t frame_timecode = frame_timestamp_ns / timecode_scale;
uint64_t cluster_timecode = frame_timecode;
if (frames_size_ > 0) {
const Frame* const f = frames_[0]; // earliest queued frame
const uint64_t ns = f->timestamp();
const uint64_t tc = ns / timecode_scale;
if (tc < cluster_timecode)
cluster_timecode = tc;
}
Cluster*& cluster = cluster_list_[cluster_list_size_];
const int64_t offset = MaxOffset();
cluster = new (std::nothrow)
Cluster(cluster_timecode, offset, segment_info_.timecode_scale(),
accurate_cluster_duration_, fixed_size_cluster_timecode_);
if (!cluster)
return false;
if (!cluster->Init(writer_cluster_))
return false;
cluster_list_size_ = new_size;
return true;
}
bool Segment::DoNewClusterProcessing(uint64_t track_number,
uint64_t frame_timestamp_ns, bool is_key) {
for (;;) {
// Based on the characteristics of the current frame and current
// cluster, decide whether to create a new cluster.
const int result = TestFrame(track_number, frame_timestamp_ns, is_key);
if (result < 0) // error
return false;
// Always set force_new_cluster_ to false after TestFrame.
force_new_cluster_ = false;
// A non-zero result means create a new cluster.
if (result > 0 && !MakeNewCluster(frame_timestamp_ns))
return false;
// Write queued (audio) frames.
const int frame_count = WriteFramesAll();
if (frame_count < 0) // error
return false;
// Write the current frame to the current cluster (if TestFrame
// returns 0) or to a newly created cluster (TestFrame returns 1).
if (result <= 1)
return true;
// TestFrame returned 2, which means there was a large time
// difference between the cluster and the frame itself. Do the
// test again, comparing the frame to the new cluster.
}
}
bool Segment::CheckHeaderInfo() {
if (!header_written_) {
if (!WriteSegmentHeader())
return false;
if (!seek_head_.AddSeekEntry(libwebm::kMkvCluster, MaxOffset()))
return false;
if (output_cues_ && cues_track_ == 0) {
// Check for a video track
for (uint32_t i = 0; i < tracks_.track_entries_size(); ++i) {
const Track* const track = tracks_.GetTrackByIndex(i);
if (!track)
return false;
if (tracks_.TrackIsVideo(track->number())) {
cues_track_ = track->number();
break;
}
}
// Set first track found
if (cues_track_ == 0) {
const Track* const track = tracks_.GetTrackByIndex(0);
if (!track)
return false;
cues_track_ = track->number();
}
}
}
return true;
}
void Segment::UpdateDocTypeVersion() {
for (uint32_t index = 0; index < tracks_.track_entries_size(); ++index) {
const Track* track = tracks_.GetTrackByIndex(index);
if (track == NULL)
break;
if ((track->codec_delay() || track->seek_pre_roll()) &&
doc_type_version_ < 4) {
doc_type_version_ = 4;
break;
}
}
}
bool Segment::UpdateChunkName(const char* ext, char** name) const {
if (!name || !ext)
return false;
char ext_chk[64];
#ifdef _MSC_VER
sprintf_s(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext);
#else
snprintf(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext);
#endif
const size_t length = strlen(chunking_base_name_) + strlen(ext_chk) + 1;
char* const str = new (std::nothrow) char[length]; // NOLINT
if (!str)
return false;
#ifdef _MSC_VER
strcpy_s(str, length - strlen(ext_chk), chunking_base_name_);
strcat_s(str, length, ext_chk);
#else
strcpy(str, chunking_base_name_);
strcat(str, ext_chk);
#endif
delete[] * name;
*name = str;
return true;
}
int64_t Segment::MaxOffset() {
if (!writer_header_)
return -1;
int64_t offset = writer_header_->Position() - payload_pos_;
if (chunking_) {
for (int32_t i = 0; i < cluster_list_size_; ++i) {
Cluster* const cluster = cluster_list_[i];
offset += cluster->Size();
}
if (writer_cues_)
offset += writer_cues_->Position();
}
return offset;
}
bool Segment::QueueFrame(Frame* frame) {
const int32_t new_size = frames_size_ + 1;
if (new_size > frames_capacity_) {
// Add more frames.
const int32_t new_capacity = (!frames_capacity_) ? 2 : frames_capacity_ * 2;
if (new_capacity < 1)
return false;
Frame** const frames = new (std::nothrow) Frame*[new_capacity]; // NOLINT
if (!frames)
return false;
for (int32_t i = 0; i < frames_size_; ++i) {
frames[i] = frames_[i];
}
delete[] frames_;
frames_ = frames;
frames_capacity_ = new_capacity;
}
frames_[frames_size_++] = frame;
return true;
}
int Segment::WriteFramesAll() {
if (frames_ == NULL)
return 0;
if (cluster_list_size_ < 1)
return -1;
Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
if (!cluster)
return -1;
for (int32_t i = 0; i < frames_size_; ++i) {
Frame*& frame = frames_[i];
// TODO(jzern/vigneshv): using Segment::AddGenericFrame here would limit the
// places where |doc_type_version_| needs to be updated.
if (frame->discard_padding() != 0)
doc_type_version_ = 4;
if (!cluster->AddFrame(frame))
return -1;
if (new_cuepoint_ && cues_track_ == frame->track_number()) {
if (!AddCuePoint(frame->timestamp(), cues_track_))
return -1;
}
if (frame->timestamp() > last_timestamp_) {
last_timestamp_ = frame->timestamp();
last_track_timestamp_[frame->track_number() - 1] = frame->timestamp();
}
delete frame;
frame = NULL;
}
const int result = frames_size_;
frames_size_ = 0;
return result;
}
bool Segment::WriteFramesLessThan(uint64_t timestamp) {
// Check |cluster_list_size_| to see if this is the first cluster. If it is
// the first cluster the audio frames that are less than the first video
// timesatmp will be written in a later step.
if (frames_size_ > 0 && cluster_list_size_ > 0) {
if (!frames_)
return false;
Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
if (!cluster)
return false;
int32_t shift_left = 0;
// TODO(fgalligan): Change this to use the durations of frames instead of
// the next frame's start time if the duration is accurate.
for (int32_t i = 1; i < frames_size_; ++i) {
const Frame* const frame_curr = frames_[i];
if (frame_curr->timestamp() > timestamp)
break;
const Frame* const frame_prev = frames_[i - 1];
if (frame_prev->discard_padding() != 0)
doc_type_version_ = 4;
if (!cluster->AddFrame(frame_prev))
return false;
if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) {
if (!AddCuePoint(frame_prev->timestamp(), cues_track_))
return false;
}
++shift_left;
if (frame_prev->timestamp() > last_timestamp_) {
last_timestamp_ = frame_prev->timestamp();
last_track_timestamp_[frame_prev->track_number() - 1] =
frame_prev->timestamp();
}
delete frame_prev;
}
if (shift_left > 0) {
if (shift_left >= frames_size_)
return false;
const int32_t new_frames_size = frames_size_ - shift_left;
for (int32_t i = 0; i < new_frames_size; ++i) {
frames_[i] = frames_[i + shift_left];
}
frames_size_ = new_frames_size;
}
}
return true;
}
bool Segment::DocTypeIsWebm() const {
const int kNumCodecIds = 9;
// TODO(vigneshv): Tweak .clang-format.
const char* kWebmCodecIds[kNumCodecIds] = {
Tracks::kOpusCodecId, Tracks::kVorbisCodecId,
Tracks::kVp8CodecId, Tracks::kVp9CodecId,
Tracks::kVp10CodecId, Tracks::kWebVttCaptionsId,
Tracks::kWebVttDescriptionsId, Tracks::kWebVttMetadataId,
Tracks::kWebVttSubtitlesId};
const int num_tracks = static_cast<int>(tracks_.track_entries_size());
for (int track_index = 0; track_index < num_tracks; ++track_index) {
const Track* const track = tracks_.GetTrackByIndex(track_index);
const std::string codec_id = track->codec_id();
bool id_is_webm = false;
for (int id_index = 0; id_index < kNumCodecIds; ++id_index) {
if (codec_id == kWebmCodecIds[id_index]) {
id_is_webm = true;
break;
}
}
if (!id_is_webm)
return false;
}
return true;
}
} // namespace mkvmuxer