ref: 15a75b45304248f746634b43763c496322bf8968
dir: /third_party/libwebm/mkvmuxer/mkvmuxer.cc/
// 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 <stdint.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::unique_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; // AV1 tracks require a CodecPrivate. See // https://github.com/Matroska-Org/matroska-specification/blob/av1-mappin/codec/av1.md // TODO(tomfinegan): Update the above link to the AV1 Matroska mappings to // point to a stable version once it is finalized, or our own WebM mappings // page on webmproject.org should we decide to release them. if (!strcmp(codec_id_, Tracks::kAv1CodecId) && !codec_private_) 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(nullptr); if (r) { if (!CopyChromaticity(r, &r_ptr)) return false; } PrimaryChromaticityPtr g_ptr(nullptr); if (g) { if (!CopyChromaticity(g, &g_ptr)) return false; } PrimaryChromaticityPtr b_ptr(nullptr); if (b) { if (!CopyChromaticity(b, &b_ptr)) return false; } PrimaryChromaticityPtr wp_ptr(nullptr); 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::unique_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_space_(NULL), 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 (colour_space_) { if (!WriteEbmlElement(writer, libwebm::kMkvColourSpace, colour_space_)) 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; } void VideoTrack::set_colour_space(const char* colour_space) { if (colour_space) { delete[] colour_space_; const size_t length = strlen(colour_space) + 1; colour_space_ = new (std::nothrow) char[length]; // NOLINT if (colour_space_) { #ifdef _MSC_VER strcpy_s(colour_space_, length, colour_space); #else strcpy(colour_space_, colour_space); #endif } } } bool VideoTrack::SetColour(const Colour& colour) { std::unique_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::unique_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_space_) size += EbmlElementSize(libwebm::kMkvColourSpace, colour_space_); 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::kAv1CodecId[] = "V_AV1"; const char Tracks::kVp8CodecId[] = "V_VP8"; const char Tracks::kVp9CodecId[] = "V_VP9"; 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_(false), 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); if (!tracks_.AddTrack(track, number)) { delete track; return 0; } 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)) { delete 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); if (!tracks_.AddTrack(track, number)) { delete track; return 0; } 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 (cluster_list_size_ > 0) { const uint64_t timecode_scale = segment_info_.timecode_scale(); const uint64_t frame_timecode = frame->timestamp() / timecode_scale; const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1]; const uint64_t last_cluster_timecode = last_cluster->timecode(); const uint64_t rel_timecode = frame_timecode - last_cluster_timecode; if (rel_timecode > kMaxBlockTimecode) { force_new_cluster_ = true; } } // 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)) { delete new_frame; return false; } if (!QueueFrame(new_frame)) { delete 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 || !new_frame->CopyFrom(*frame)) { delete new_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::kAv1CodecId, Tracks::kVp8CodecId, Tracks::kVp9CodecId, 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