You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4231 lines
119 KiB
4231 lines
119 KiB
// 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(static_cast<uint64_t>(libwebm::kMkvCluster),
|
|
uint64_t{0xFFFFFFFFFFFFFFFFU}) +
|
|
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)) {
|
|
delete frame;
|
|
continue;
|
|
}
|
|
|
|
if (new_cuepoint_ && cues_track_ == frame->track_number()) {
|
|
if (!AddCuePoint(frame->timestamp(), cues_track_)) {
|
|
delete frame;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
delete frame_prev;
|
|
continue;
|
|
}
|
|
|
|
if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) {
|
|
if (!AddCuePoint(frame_prev->timestamp(), cues_track_)) {
|
|
delete frame_prev;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
++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
|