Index: source/libvpx/third_party/libwebm/mkvmuxer.cpp |
=================================================================== |
--- source/libvpx/third_party/libwebm/mkvmuxer.cpp (revision 0) |
+++ source/libvpx/third_party/libwebm/mkvmuxer.cpp (revision 0) |
@@ -0,0 +1,3245 @@ |
+// 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.hpp" |
+ |
+#include <climits> |
+#include <cstdio> |
+#include <cstdlib> |
+#include <cstring> |
+#include <ctime> |
+#include <new> |
+ |
+#include "mkvmuxerutil.hpp" |
+#include "mkvparser.hpp" |
+#include "mkvwriter.hpp" |
+#include "webmids.hpp" |
+ |
+#ifdef _MSC_VER |
+// Disable MSVC warnings that suggest making code non-portable. |
+#pragma warning(disable:4996) |
+#endif |
+ |
+namespace mkvmuxer { |
+ |
+namespace { |
+// 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; |
+} |
+} // namespace |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// IMkvWriter Class |
+ |
+IMkvWriter::IMkvWriter() { |
+} |
+ |
+IMkvWriter::~IMkvWriter() { |
+} |
+ |
+bool WriteEbmlHeader(IMkvWriter* writer) { |
+ // Level 0 |
+ uint64 size = EbmlElementSize(kMkvEBMLVersion, 1ULL); |
+ size += EbmlElementSize(kMkvEBMLReadVersion, 1ULL); |
+ size += EbmlElementSize(kMkvEBMLMaxIDLength, 4ULL); |
+ size += EbmlElementSize(kMkvEBMLMaxSizeLength, 8ULL); |
+ size += EbmlElementSize(kMkvDocType, "webm"); |
+ size += EbmlElementSize(kMkvDocTypeVersion, 2ULL); |
+ size += EbmlElementSize(kMkvDocTypeReadVersion, 2ULL); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvEBML, size)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvEBMLVersion, 1ULL)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvEBMLReadVersion, 1ULL)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvEBMLMaxIDLength, 4ULL)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvEBMLMaxSizeLength, 8ULL)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvDocType, "webm")) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvDocTypeVersion, 2ULL)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvDocTypeReadVersion, 2ULL)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+bool ChunkedCopy(mkvparser::IMkvReader* source, |
+ mkvmuxer::IMkvWriter* dst, |
+ mkvmuxer::int64 start, int64 size) { |
+ // TODO(vigneshv): Check if this is a reasonable value. |
+ const uint32 kBufSize = 2048; |
+ uint8* buf = new uint8[kBufSize]; |
+ int64 offset = start; |
+ while (size > 0) { |
+ const int64 read_len = (size > kBufSize) ? kBufSize : size; |
+ if (source->Read(offset, static_cast<long>(read_len), buf)) |
+ return false; |
+ dst->Write(buf, static_cast<uint32>(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), |
+ frame_(NULL), |
+ is_key_(false), |
+ length_(0), |
+ track_number_(0), |
+ timestamp_(0), |
+ discard_padding_(0) { |
+} |
+ |
+Frame::~Frame() { |
+ delete [] frame_; |
+ delete [] additional_; |
+} |
+ |
+bool Frame::Init(const uint8* frame, uint64 length) { |
+ uint8* const data = |
+ new (std::nothrow) uint8[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* additional, uint64 length, |
+ uint64 add_id) { |
+ uint8* const data = |
+ new (std::nothrow) uint8[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; |
+} |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// 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 size = EbmlElementSize(kMkvCueClusterPosition, cluster_pos_); |
+ size += EbmlElementSize(kMkvCueTrack, track_); |
+ if (output_block_number_ && block_number_ > 1) |
+ size += EbmlElementSize(kMkvCueBlockNumber, block_number_); |
+ const uint64 track_pos_size = EbmlMasterElementSize(kMkvCueTrackPositions, |
+ size) + size; |
+ const uint64 payload_size = EbmlElementSize(kMkvCueTime, time_) + |
+ track_pos_size; |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvCuePoint, payload_size)) |
+ return false; |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, kMkvCueTime, time_)) |
+ return false; |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvCueTrackPositions, size)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvCueTrack, track_)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvCueClusterPosition, cluster_pos_)) |
+ return false; |
+ if (output_block_number_ && block_number_ > 1) |
+ if (!WriteEbmlElement(writer, kMkvCueBlockNumber, block_number_)) |
+ return false; |
+ |
+ const int64 stop_position = writer->Position(); |
+ if (stop_position < 0) |
+ return false; |
+ |
+ if (stop_position - payload_position != static_cast<int64>(payload_size)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+uint64 CuePoint::PayloadSize() const { |
+ uint64 size = EbmlElementSize(kMkvCueClusterPosition, cluster_pos_); |
+ size += EbmlElementSize(kMkvCueTrack, track_); |
+ if (output_block_number_ && block_number_ > 1) |
+ size += EbmlElementSize(kMkvCueBlockNumber, block_number_); |
+ const uint64 track_pos_size = EbmlMasterElementSize(kMkvCueTrackPositions, |
+ size) + size; |
+ const uint64 payload_size = EbmlElementSize(kMkvCueTime, time_) + |
+ track_pos_size; |
+ |
+ return payload_size; |
+} |
+ |
+uint64 CuePoint::Size() const { |
+ const uint64 payload_size = PayloadSize(); |
+ return EbmlMasterElementSize(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 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 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 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 index) const { |
+ if (cue_entries_ == NULL) |
+ return NULL; |
+ |
+ if (index >= cue_entries_size_) |
+ return NULL; |
+ |
+ return cue_entries_[index]; |
+} |
+ |
+uint64 Cues::Size() { |
+ uint64 size = 0; |
+ for (int32 i = 0; i < cue_entries_size_; ++i) |
+ size += GetCueByIndex(i)->Size(); |
+ size += EbmlMasterElementSize(kMkvCues, size); |
+ return size; |
+} |
+ |
+bool Cues::Write(IMkvWriter* writer) const { |
+ if (!writer) |
+ return false; |
+ |
+ uint64 size = 0; |
+ for (int32 i = 0; i < cue_entries_size_; ++i) { |
+ const CuePoint* const cue = GetCueByIndex(i); |
+ |
+ if (!cue) |
+ return false; |
+ |
+ size += cue->Size(); |
+ } |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvCues, size)) |
+ return false; |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ for (int32 i = 0; i < cue_entries_size_; ++i) { |
+ const CuePoint* const cue = GetCueByIndex(i); |
+ |
+ if (!cue->Write(writer)) |
+ return false; |
+ } |
+ |
+ const int64 stop_position = writer->Position(); |
+ if (stop_position < 0) |
+ return false; |
+ |
+ if (stop_position - payload_position != static_cast<int64>(size)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// ContentEncAESSettings Class |
+ |
+ContentEncAESSettings::ContentEncAESSettings() : cipher_mode_(kCTR) {} |
+ |
+uint64 ContentEncAESSettings::Size() const { |
+ const uint64 payload = PayloadSize(); |
+ const uint64 size = |
+ EbmlMasterElementSize(kMkvContentEncAESSettings, payload) + payload; |
+ return size; |
+} |
+ |
+bool ContentEncAESSettings::Write(IMkvWriter* writer) const { |
+ const uint64 payload = PayloadSize(); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvContentEncAESSettings, payload)) |
+ return false; |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, kMkvAESSettingsCipherMode, cipher_mode_)) |
+ return false; |
+ |
+ const int64 stop_position = writer->Position(); |
+ if (stop_position < 0 || |
+ stop_position - payload_position != static_cast<int64>(payload)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+uint64 ContentEncAESSettings::PayloadSize() const { |
+ uint64 size = EbmlElementSize(kMkvAESSettingsCipherMode, 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* id, uint64 length) { |
+ if (!id || length < 1) |
+ return false; |
+ |
+ delete [] enc_key_id_; |
+ |
+ enc_key_id_ = |
+ new (std::nothrow) uint8[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 ContentEncoding::Size() const { |
+ const uint64 encryption_size = EncryptionSize(); |
+ const uint64 encoding_size = EncodingSize(0, encryption_size); |
+ const uint64 encodings_size = EbmlMasterElementSize(kMkvContentEncoding, |
+ encoding_size) + |
+ encoding_size; |
+ |
+ return encodings_size; |
+} |
+ |
+bool ContentEncoding::Write(IMkvWriter* writer) const { |
+ const uint64 encryption_size = EncryptionSize(); |
+ const uint64 encoding_size = EncodingSize(0, encryption_size); |
+ const uint64 size = EbmlMasterElementSize(kMkvContentEncoding, |
+ encoding_size) + |
+ encoding_size; |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvContentEncoding, encoding_size)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvContentEncodingOrder, encoding_order_)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvContentEncodingScope, encoding_scope_)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvContentEncodingType, encoding_type_)) |
+ return false; |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvContentEncryption, encryption_size)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvContentEncAlgo, enc_algo_)) |
+ return false; |
+ if (!WriteEbmlElement(writer, |
+ kMkvContentEncKeyID, |
+ enc_key_id_, |
+ enc_key_id_length_)) |
+ return false; |
+ |
+ if (!enc_aes_settings_.Write(writer)) |
+ return false; |
+ |
+ const int64 stop_position = writer->Position(); |
+ if (stop_position < 0 || |
+ stop_position - payload_position != static_cast<int64>(size)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+uint64 ContentEncoding::EncodingSize(uint64 compresion_size, |
+ uint64 encryption_size) const { |
+ // TODO(fgalligan): Add support for compression settings. |
+ if (compresion_size != 0) |
+ return 0; |
+ |
+ uint64 encoding_size = 0; |
+ |
+ if (encryption_size > 0) { |
+ encoding_size += EbmlMasterElementSize(kMkvContentEncryption, |
+ encryption_size) + |
+ encryption_size; |
+ } |
+ encoding_size += EbmlElementSize(kMkvContentEncodingType, encoding_type_); |
+ encoding_size += EbmlElementSize(kMkvContentEncodingScope, encoding_scope_); |
+ encoding_size += EbmlElementSize(kMkvContentEncodingOrder, encoding_order_); |
+ |
+ return encoding_size; |
+} |
+ |
+uint64 ContentEncoding::EncryptionSize() const { |
+ const uint64 aes_size = enc_aes_settings_.Size(); |
+ |
+ uint64 encryption_size = EbmlElementSize(kMkvContentEncKeyID, |
+ enc_key_id_, |
+ enc_key_id_length_); |
+ encryption_size += EbmlElementSize(kMkvContentEncAlgo, 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), |
+ 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 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 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 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 index) const { |
+ if (content_encoding_entries_ == NULL) |
+ return NULL; |
+ |
+ if (index >= content_encoding_entries_size_) |
+ return NULL; |
+ |
+ return content_encoding_entries_[index]; |
+} |
+ |
+uint64 Track::PayloadSize() const { |
+ uint64 size = EbmlElementSize(kMkvTrackNumber, number_); |
+ size += EbmlElementSize(kMkvTrackUID, uid_); |
+ size += EbmlElementSize(kMkvTrackType, type_); |
+ if (codec_id_) |
+ size += EbmlElementSize(kMkvCodecID, codec_id_); |
+ if (codec_private_) |
+ size += EbmlElementSize(kMkvCodecPrivate, |
+ codec_private_, |
+ codec_private_length_); |
+ if (language_) |
+ size += EbmlElementSize(kMkvLanguage, language_); |
+ if (name_) |
+ size += EbmlElementSize(kMkvName, name_); |
+ if (max_block_additional_id_) |
+ size += EbmlElementSize(kMkvMaxBlockAdditionID, max_block_additional_id_); |
+ if (codec_delay_) |
+ size += EbmlElementSize(kMkvCodecDelay, codec_delay_); |
+ if (seek_pre_roll_) |
+ size += EbmlElementSize(kMkvSeekPreRoll, seek_pre_roll_); |
+ |
+ if (content_encoding_entries_size_ > 0) { |
+ uint64 content_encodings_size = 0; |
+ for (uint32 i = 0; i < content_encoding_entries_size_; ++i) { |
+ ContentEncoding* const encoding = content_encoding_entries_[i]; |
+ content_encodings_size += encoding->Size(); |
+ } |
+ |
+ size += EbmlMasterElementSize(kMkvContentEncodings, |
+ content_encodings_size) + |
+ content_encodings_size; |
+ } |
+ |
+ return size; |
+} |
+ |
+uint64 Track::Size() const { |
+ uint64 size = PayloadSize(); |
+ size += EbmlMasterElementSize(kMkvTrackEntry, size); |
+ return size; |
+} |
+ |
+bool Track::Write(IMkvWriter* writer) const { |
+ if (!writer) |
+ 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 payload_size = PayloadSize(); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvTrackEntry, payload_size)) |
+ return false; |
+ |
+ uint64 size = EbmlElementSize(kMkvTrackNumber, number_); |
+ size += EbmlElementSize(kMkvTrackUID, uid_); |
+ size += EbmlElementSize(kMkvTrackType, type_); |
+ if (codec_id_) |
+ size += EbmlElementSize(kMkvCodecID, codec_id_); |
+ if (codec_private_) |
+ size += EbmlElementSize(kMkvCodecPrivate, |
+ codec_private_, |
+ codec_private_length_); |
+ if (language_) |
+ size += EbmlElementSize(kMkvLanguage, language_); |
+ if (name_) |
+ size += EbmlElementSize(kMkvName, name_); |
+ if (max_block_additional_id_) |
+ size += EbmlElementSize(kMkvMaxBlockAdditionID, max_block_additional_id_); |
+ if (codec_delay_) |
+ size += EbmlElementSize(kMkvCodecDelay, codec_delay_); |
+ if (seek_pre_roll_) |
+ size += EbmlElementSize(kMkvSeekPreRoll, seek_pre_roll_); |
+ |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, kMkvTrackNumber, number_)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvTrackUID, uid_)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvTrackType, type_)) |
+ return false; |
+ if (max_block_additional_id_) { |
+ if (!WriteEbmlElement(writer, |
+ kMkvMaxBlockAdditionID, |
+ max_block_additional_id_)) { |
+ return false; |
+ } |
+ } |
+ if (codec_delay_) { |
+ if (!WriteEbmlElement(writer, kMkvCodecDelay, codec_delay_)) |
+ return false; |
+ } |
+ if (seek_pre_roll_) { |
+ if (!WriteEbmlElement(writer, kMkvSeekPreRoll, seek_pre_roll_)) |
+ return false; |
+ } |
+ if (codec_id_) { |
+ if (!WriteEbmlElement(writer, kMkvCodecID, codec_id_)) |
+ return false; |
+ } |
+ if (codec_private_) { |
+ if (!WriteEbmlElement(writer, |
+ kMkvCodecPrivate, |
+ codec_private_, |
+ codec_private_length_)) |
+ return false; |
+ } |
+ if (language_) { |
+ if (!WriteEbmlElement(writer, kMkvLanguage, language_)) |
+ return false; |
+ } |
+ if (name_) { |
+ if (!WriteEbmlElement(writer, kMkvName, name_)) |
+ return false; |
+ } |
+ |
+ int64 stop_position = writer->Position(); |
+ if (stop_position < 0 || |
+ stop_position - payload_position != static_cast<int64>(size)) |
+ return false; |
+ |
+ if (content_encoding_entries_size_ > 0) { |
+ uint64 content_encodings_size = 0; |
+ for (uint32 i = 0; i < content_encoding_entries_size_; ++i) { |
+ ContentEncoding* const encoding = content_encoding_entries_[i]; |
+ content_encodings_size += encoding->Size(); |
+ } |
+ |
+ if (!WriteEbmlMasterElement(writer, |
+ kMkvContentEncodings, |
+ content_encodings_size)) |
+ return false; |
+ |
+ for (uint32 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* codec_private, uint64 length) { |
+ if (!codec_private || length < 1) |
+ return false; |
+ |
+ delete [] codec_private_; |
+ |
+ codec_private_ = |
+ new (std::nothrow) uint8[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 |
+ } |
+ } |
+} |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// VideoTrack Class |
+ |
+VideoTrack::VideoTrack(unsigned int* seed) |
+ : Track(seed), |
+ display_height_(0), |
+ display_width_(0), |
+ frame_rate_(0.0), |
+ height_(0), |
+ stereo_mode_(0), |
+ alpha_mode_(0), |
+ width_(0) { |
+} |
+ |
+VideoTrack::~VideoTrack() { |
+} |
+ |
+bool VideoTrack::SetStereoMode(uint64 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 alpha_mode) { |
+ if (alpha_mode != kNoAlpha && |
+ alpha_mode != kAlpha) |
+ return false; |
+ |
+ alpha_mode_ = alpha_mode; |
+ return true; |
+} |
+ |
+uint64 VideoTrack::PayloadSize() const { |
+ const uint64 parent_size = Track::PayloadSize(); |
+ |
+ uint64 size = VideoPayloadSize(); |
+ size += EbmlMasterElementSize(kMkvVideo, size); |
+ |
+ return parent_size + size; |
+} |
+ |
+bool VideoTrack::Write(IMkvWriter* writer) const { |
+ if (!Track::Write(writer)) |
+ return false; |
+ |
+ const uint64 size = VideoPayloadSize(); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvVideo, size)) |
+ return false; |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, kMkvPixelWidth, width_)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvPixelHeight, height_)) |
+ return false; |
+ if (display_width_ > 0) |
+ if (!WriteEbmlElement(writer, kMkvDisplayWidth, display_width_)) |
+ return false; |
+ if (display_height_ > 0) |
+ if (!WriteEbmlElement(writer, kMkvDisplayHeight, display_height_)) |
+ return false; |
+ if (stereo_mode_ > kMono) |
+ if (!WriteEbmlElement(writer, kMkvStereoMode, stereo_mode_)) |
+ return false; |
+ if (alpha_mode_ > kNoAlpha) |
+ if (!WriteEbmlElement(writer, kMkvAlphaMode, alpha_mode_)) |
+ return false; |
+ if (frame_rate_ > 0.0) |
+ if (!WriteEbmlElement(writer, |
+ kMkvFrameRate, |
+ static_cast<float>(frame_rate_))) |
+ return false; |
+ |
+ const int64 stop_position = writer->Position(); |
+ if (stop_position < 0 || |
+ stop_position - payload_position != static_cast<int64>(size)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+uint64 VideoTrack::VideoPayloadSize() const { |
+ uint64 size = EbmlElementSize(kMkvPixelWidth, width_); |
+ size += EbmlElementSize(kMkvPixelHeight, height_); |
+ if (display_width_ > 0) |
+ size += EbmlElementSize(kMkvDisplayWidth, display_width_); |
+ if (display_height_ > 0) |
+ size += EbmlElementSize(kMkvDisplayHeight, display_height_); |
+ if (stereo_mode_ > kMono) |
+ size += EbmlElementSize(kMkvStereoMode, stereo_mode_); |
+ if (alpha_mode_ > kNoAlpha) |
+ size += EbmlElementSize(kMkvAlphaMode, alpha_mode_); |
+ if (frame_rate_ > 0.0) |
+ size += EbmlElementSize(kMkvFrameRate, static_cast<float>(frame_rate_)); |
+ |
+ return size; |
+} |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// AudioTrack Class |
+ |
+AudioTrack::AudioTrack(unsigned int* seed) |
+ : Track(seed), |
+ bit_depth_(0), |
+ channels_(1), |
+ sample_rate_(0.0) { |
+} |
+ |
+AudioTrack::~AudioTrack() { |
+} |
+ |
+uint64 AudioTrack::PayloadSize() const { |
+ const uint64 parent_size = Track::PayloadSize(); |
+ |
+ uint64 size = EbmlElementSize(kMkvSamplingFrequency, |
+ static_cast<float>(sample_rate_)); |
+ size += EbmlElementSize(kMkvChannels, channels_); |
+ if (bit_depth_ > 0) |
+ size += EbmlElementSize(kMkvBitDepth, bit_depth_); |
+ size += EbmlMasterElementSize(kMkvAudio, size); |
+ |
+ return parent_size + size; |
+} |
+ |
+bool AudioTrack::Write(IMkvWriter* writer) const { |
+ if (!Track::Write(writer)) |
+ return false; |
+ |
+ // Calculate AudioSettings size. |
+ uint64 size = EbmlElementSize(kMkvSamplingFrequency, |
+ static_cast<float>(sample_rate_)); |
+ size += EbmlElementSize(kMkvChannels, channels_); |
+ if (bit_depth_ > 0) |
+ size += EbmlElementSize(kMkvBitDepth, bit_depth_); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvAudio, size)) |
+ return false; |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, |
+ kMkvSamplingFrequency, |
+ static_cast<float>(sample_rate_))) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvChannels, channels_)) |
+ return false; |
+ if (bit_depth_ > 0) |
+ if (!WriteEbmlElement(writer, kMkvBitDepth, bit_depth_)) |
+ return false; |
+ |
+ const int64 stop_position = writer->Position(); |
+ if (stop_position < 0 || |
+ stop_position - payload_position != static_cast<int64>(size)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// Tracks Class |
+ |
+const char Tracks::kOpusCodecId[] = "A_OPUS"; |
+const char Tracks::kVorbisCodecId[] = "A_VORBIS"; |
+const char Tracks::kVp8CodecId[] = "V_VP8"; |
+const char Tracks::kVp9CodecId[] = "V_VP9"; |
+ |
+ |
+Tracks::Tracks() |
+ : track_entries_(NULL), |
+ track_entries_size_(0) { |
+} |
+ |
+Tracks::~Tracks() { |
+ if (track_entries_) { |
+ for (uint32 i = 0; i < track_entries_size_; ++i) { |
+ Track* const track = track_entries_[i]; |
+ delete track; |
+ } |
+ delete [] track_entries_; |
+ } |
+} |
+ |
+bool Tracks::AddTrack(Track* track, int32 number) { |
+ if (number < 0) |
+ 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 track_num = number; |
+ |
+ if (track_num > 0) { |
+ // Check to make sure a track does not already have |track_num|. |
+ for (uint32 i = 0; i < track_entries_size_; ++i) { |
+ if (track_entries_[i]->number() == track_num) |
+ return false; |
+ } |
+ } |
+ |
+ const uint32 count = track_entries_size_ + 1; |
+ |
+ Track** const track_entries = new (std::nothrow) Track*[count]; // NOLINT |
+ if (!track_entries) |
+ return false; |
+ |
+ for (uint32 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 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 index) const { |
+ if (track_entries_ == NULL) |
+ return NULL; |
+ |
+ if (index >= track_entries_size_) |
+ return NULL; |
+ |
+ return track_entries_[index]; |
+} |
+ |
+Track* Tracks::GetTrackByNumber(uint64 track_number) const { |
+ const int32 count = track_entries_size(); |
+ for (int32 i = 0; i < count; ++i) { |
+ if (track_entries_[i]->number() == track_number) |
+ return track_entries_[i]; |
+ } |
+ |
+ return NULL; |
+} |
+ |
+bool Tracks::TrackIsAudio(uint64 track_number) const { |
+ const Track* const track = GetTrackByNumber(track_number); |
+ |
+ if (track->type() == kAudio) |
+ return true; |
+ |
+ return false; |
+} |
+ |
+bool Tracks::TrackIsVideo(uint64 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 size = 0; |
+ const int32 count = track_entries_size(); |
+ for (int32 i = 0; i < count; ++i) { |
+ const Track* const track = GetTrackByIndex(i); |
+ |
+ if (!track) |
+ return false; |
+ |
+ size += track->Size(); |
+ } |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvTracks, size)) |
+ return false; |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ for (int32 i = 0; i < count; ++i) { |
+ const Track* const track = GetTrackByIndex(i); |
+ if (!track->Write(writer)) |
+ return false; |
+ } |
+ |
+ const int64 stop_position = writer->Position(); |
+ if (stop_position < 0 || |
+ stop_position - payload_position != static_cast<int64>(size)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// Chapter Class |
+ |
+bool Chapter::set_id(const char* id) { |
+ return StrCpy(id, &id_); |
+} |
+ |
+void Chapter::set_time(const Segment& segment, |
+ uint64 start_ns, |
+ uint64 end_ns) { |
+ const SegmentInfo* const info = segment.GetSegmentInfo(); |
+ const uint64 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; |
+ 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 Chapter::WriteAtom(IMkvWriter* writer) const { |
+ uint64 payload_size = |
+ EbmlElementSize(kMkvChapterStringUID, id_) + |
+ EbmlElementSize(kMkvChapterUID, uid_) + |
+ EbmlElementSize(kMkvChapterTimeStart, start_timecode_) + |
+ EbmlElementSize(kMkvChapterTimeEnd, end_timecode_); |
+ |
+ for (int idx = 0; idx < displays_count_; ++idx) { |
+ const Display& d = displays_[idx]; |
+ payload_size += d.WriteDisplay(NULL); |
+ } |
+ |
+ const uint64 atom_size = |
+ EbmlMasterElementSize(kMkvChapterAtom, payload_size) + |
+ payload_size; |
+ |
+ if (writer == NULL) |
+ return atom_size; |
+ |
+ const int64 start = writer->Position(); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvChapterAtom, payload_size)) |
+ return 0; |
+ |
+ if (!WriteEbmlElement(writer, kMkvChapterStringUID, id_)) |
+ return 0; |
+ |
+ if (!WriteEbmlElement(writer, kMkvChapterUID, uid_)) |
+ return 0; |
+ |
+ if (!WriteEbmlElement(writer, kMkvChapterTimeStart, start_timecode_)) |
+ return 0; |
+ |
+ if (!WriteEbmlElement(writer, kMkvChapterTimeEnd, 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 stop = writer->Position(); |
+ |
+ if (stop >= start && uint64(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 Chapter::Display::WriteDisplay(IMkvWriter* writer) const { |
+ uint64 payload_size = EbmlElementSize(kMkvChapString, title_); |
+ |
+ if (language_) |
+ payload_size += EbmlElementSize(kMkvChapLanguage, language_); |
+ |
+ if (country_) |
+ payload_size += EbmlElementSize(kMkvChapCountry, country_); |
+ |
+ const uint64 display_size = |
+ EbmlMasterElementSize(kMkvChapterDisplay, payload_size) + |
+ payload_size; |
+ |
+ if (writer == NULL) |
+ return display_size; |
+ |
+ const int64 start = writer->Position(); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvChapterDisplay, payload_size)) |
+ return 0; |
+ |
+ if (!WriteEbmlElement(writer, kMkvChapString, title_)) |
+ return 0; |
+ |
+ if (language_) { |
+ if (!WriteEbmlElement(writer, kMkvChapLanguage, language_)) |
+ return 0; |
+ } |
+ |
+ if (country_) { |
+ if (!WriteEbmlElement(writer, kMkvChapCountry, country_)) |
+ return 0; |
+ } |
+ |
+ const int64 stop = writer->Position(); |
+ |
+ if (stop >= start && uint64(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 payload_size = WriteEdition(NULL); // return size only |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvChapters, payload_size)) |
+ return false; |
+ |
+ const int64 start = writer->Position(); |
+ |
+ if (WriteEdition(writer) == 0) // error |
+ return false; |
+ |
+ const int64 stop = writer->Position(); |
+ |
+ if (stop >= start && uint64(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 Chapters::WriteEdition(IMkvWriter* writer) const { |
+ uint64 payload_size = 0; |
+ |
+ for (int idx = 0; idx < chapters_count_; ++idx) { |
+ const Chapter& chapter = chapters_[idx]; |
+ payload_size += chapter.WriteAtom(NULL); |
+ } |
+ |
+ const uint64 edition_size = |
+ EbmlMasterElementSize(kMkvEditionEntry, payload_size) + |
+ payload_size; |
+ |
+ if (writer == NULL) // return size only |
+ return edition_size; |
+ |
+ const int64 start = writer->Position(); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvEditionEntry, payload_size)) |
+ return 0; // error |
+ |
+ for (int idx = 0; idx < chapters_count_; ++idx) { |
+ const Chapter& chapter = chapters_[idx]; |
+ |
+ const uint64 chapter_size = chapter.WriteAtom(writer); |
+ if (chapter_size == 0) // error |
+ return 0; |
+ } |
+ |
+ const int64 stop = writer->Position(); |
+ |
+ if (stop >= start && uint64(stop - start) != edition_size) |
+ return 0; |
+ |
+ return edition_size; |
+} |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// Cluster class |
+ |
+Cluster::Cluster(uint64 timecode, int64 cues_pos) |
+ : blocks_added_(0), |
+ finalized_(false), |
+ header_written_(false), |
+ payload_size_(0), |
+ position_for_cues_(cues_pos), |
+ size_position_(-1), |
+ timecode_(timecode), |
+ writer_(NULL) { |
+} |
+ |
+Cluster::~Cluster() { |
+} |
+ |
+bool Cluster::Init(IMkvWriter* ptr_writer) { |
+ if (!ptr_writer) { |
+ return false; |
+ } |
+ writer_ = ptr_writer; |
+ return true; |
+} |
+ |
+bool Cluster::AddFrame(const uint8* frame, |
+ uint64 length, |
+ uint64 track_number, |
+ uint64 abs_timecode, |
+ bool is_key) { |
+ return DoWriteBlock(frame, |
+ length, |
+ track_number, |
+ abs_timecode, |
+ is_key ? 1 : 0, |
+ &WriteSimpleBlock); |
+} |
+ |
+bool Cluster::AddFrameWithAdditional(const uint8* frame, |
+ uint64 length, |
+ const uint8* additional, |
+ uint64 additional_length, |
+ uint64 add_id, |
+ uint64 track_number, |
+ uint64 abs_timecode, |
+ bool is_key) { |
+ return DoWriteBlockWithAdditional(frame, |
+ length, |
+ additional, |
+ additional_length, |
+ add_id, |
+ track_number, |
+ abs_timecode, |
+ is_key ? 1 : 0, |
+ &WriteBlockWithAdditional); |
+} |
+ |
+bool Cluster::AddFrameWithDiscardPadding(const uint8* frame, |
+ uint64 length, |
+ int64 discard_padding, |
+ uint64 track_number, |
+ uint64 abs_timecode, |
+ bool is_key) { |
+ return DoWriteBlockWithDiscardPadding(frame, |
+ length, |
+ discard_padding, |
+ track_number, |
+ abs_timecode, |
+ is_key ? 1 : 0, |
+ &WriteBlockWithDiscardPadding); |
+} |
+ |
+bool Cluster::AddMetadata(const uint8* frame, |
+ uint64 length, |
+ uint64 track_number, |
+ uint64 abs_timecode, |
+ uint64 duration_timecode) { |
+ return DoWriteBlock(frame, |
+ length, |
+ track_number, |
+ abs_timecode, |
+ duration_timecode, |
+ &WriteMetadataBlock); |
+} |
+ |
+void Cluster::AddPayloadSize(uint64 size) { |
+ payload_size_ += size; |
+} |
+ |
+bool Cluster::Finalize() { |
+ if (!writer_ || finalized_ || size_position_ == -1) |
+ return false; |
+ |
+ if (writer_->Seekable()) { |
+ const int64 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 Cluster::Size() const { |
+ const uint64 element_size = |
+ EbmlMasterElementSize(kMkvCluster, |
+ 0xFFFFFFFFFFFFFFFFULL) + payload_size_; |
+ return element_size; |
+} |
+ |
+template <typename Type> |
+bool Cluster::PreWriteBlock(Type* write_function) { |
+ if (write_function == NULL) |
+ return false; |
+ |
+ if (finalized_) |
+ return false; |
+ |
+ if (!header_written_) { |
+ if (!WriteClusterHeader()) |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+void Cluster::PostWriteBlock(uint64 element_size) { |
+ AddPayloadSize(element_size); |
+ ++blocks_added_; |
+} |
+ |
+bool Cluster::IsValidTrackNumber(uint64 track_number) const { |
+ return (track_number > 0 && track_number <= 0x7E); |
+} |
+ |
+int64 Cluster::GetRelativeTimecode(int64 abs_timecode) const { |
+ const int64 cluster_timecode = this->Cluster::timecode(); |
+ const int64 rel_timecode = |
+ static_cast<int64>(abs_timecode) - cluster_timecode; |
+ |
+ if (rel_timecode < 0 || rel_timecode > kMaxBlockTimecode) |
+ return -1; |
+ |
+ return rel_timecode; |
+} |
+ |
+bool Cluster::DoWriteBlock( |
+ const uint8* frame, |
+ uint64 length, |
+ uint64 track_number, |
+ uint64 abs_timecode, |
+ uint64 generic_arg, |
+ WriteBlock write_block) { |
+ if (frame == NULL || length == 0) |
+ return false; |
+ |
+ if (!IsValidTrackNumber(track_number)) |
+ return false; |
+ |
+ const int64 rel_timecode = GetRelativeTimecode(abs_timecode); |
+ if (rel_timecode < 0) |
+ return false; |
+ |
+ if (!PreWriteBlock(write_block)) |
+ return false; |
+ |
+ const uint64 element_size = (*write_block)(writer_, |
+ frame, |
+ length, |
+ track_number, |
+ rel_timecode, |
+ generic_arg); |
+ if (element_size == 0) |
+ return false; |
+ |
+ PostWriteBlock(element_size); |
+ return true; |
+} |
+ |
+bool Cluster::DoWriteBlockWithAdditional( |
+ const uint8* frame, |
+ uint64 length, |
+ const uint8* additional, |
+ uint64 additional_length, |
+ uint64 add_id, |
+ uint64 track_number, |
+ uint64 abs_timecode, |
+ uint64 generic_arg, |
+ WriteBlockAdditional write_block) { |
+ if (frame == NULL || length == 0 || |
+ additional == NULL || additional_length == 0) |
+ return false; |
+ |
+ if (!IsValidTrackNumber(track_number)) |
+ return false; |
+ |
+ const int64 rel_timecode = GetRelativeTimecode(abs_timecode); |
+ if (rel_timecode < 0) |
+ return false; |
+ |
+ if (!PreWriteBlock(write_block)) |
+ return false; |
+ |
+ const uint64 element_size = (*write_block)(writer_, |
+ frame, |
+ length, |
+ additional, |
+ additional_length, |
+ add_id, |
+ track_number, |
+ rel_timecode, |
+ generic_arg); |
+ if (element_size == 0) |
+ return false; |
+ |
+ PostWriteBlock(element_size); |
+ return true; |
+} |
+ |
+bool Cluster::DoWriteBlockWithDiscardPadding( |
+ const uint8* frame, |
+ uint64 length, |
+ int64 discard_padding, |
+ uint64 track_number, |
+ uint64 abs_timecode, |
+ uint64 generic_arg, |
+ WriteBlockDiscardPadding write_block) { |
+ if (frame == NULL || length == 0 || discard_padding <= 0) |
+ return false; |
+ |
+ if (!IsValidTrackNumber(track_number)) |
+ return false; |
+ |
+ const int64 rel_timecode = GetRelativeTimecode(abs_timecode); |
+ if (rel_timecode < 0) |
+ return false; |
+ |
+ if (!PreWriteBlock(write_block)) |
+ return false; |
+ |
+ const uint64 element_size = (*write_block)(writer_, |
+ frame, |
+ length, |
+ discard_padding, |
+ track_number, |
+ rel_timecode, |
+ generic_arg); |
+ if (element_size == 0) |
+ return false; |
+ |
+ PostWriteBlock(element_size); |
+ return true; |
+} |
+ |
+bool Cluster::WriteClusterHeader() { |
+ if (finalized_) |
+ return false; |
+ |
+ if (WriteID(writer_, 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_, kMkvTimecode, timecode())) |
+ return false; |
+ AddPayloadSize(EbmlElementSize(kMkvTimecode, timecode())); |
+ header_written_ = true; |
+ |
+ return true; |
+} |
+ |
+/////////////////////////////////////////////////////////////// |
+// |
+// SeekHead Class |
+ |
+SeekHead::SeekHead() : start_pos_(0ULL) { |
+ for (int32 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 payload_size = 0; |
+ uint64 entry_size[kSeekEntryCount]; |
+ |
+ for (int32 i = 0; i < kSeekEntryCount; ++i) { |
+ if (seek_entry_id_[i] != 0) { |
+ entry_size[i] = EbmlElementSize( |
+ kMkvSeekID, |
+ static_cast<uint64>(seek_entry_id_[i])); |
+ entry_size[i] += EbmlElementSize(kMkvSeekPosition, seek_entry_pos_[i]); |
+ |
+ payload_size += EbmlMasterElementSize(kMkvSeek, entry_size[i]) + |
+ entry_size[i]; |
+ } |
+ } |
+ |
+ // No SeekHead elements |
+ if (payload_size == 0) |
+ return true; |
+ |
+ const int64 pos = writer->Position(); |
+ if (writer->Position(start_pos_)) |
+ return false; |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvSeekHead, payload_size)) |
+ return false; |
+ |
+ for (int32 i = 0; i < kSeekEntryCount; ++i) { |
+ if (seek_entry_id_[i] != 0) { |
+ if (!WriteEbmlMasterElement(writer, kMkvSeek, entry_size[i])) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, |
+ kMkvSeekID, |
+ static_cast<uint64>(seek_entry_id_[i]))) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, kMkvSeekPosition, seek_entry_pos_[i])) |
+ return false; |
+ } |
+ } |
+ |
+ const uint64 total_entry_size = kSeekEntryCount * MaxEntrySize(); |
+ const uint64 total_size = |
+ EbmlMasterElementSize(kMkvSeekHead, |
+ total_entry_size) + total_entry_size; |
+ const int64 size_left = total_size - (writer->Position() - start_pos_); |
+ |
+ const uint64 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 entry_size = kSeekEntryCount * MaxEntrySize(); |
+ const uint64 size = EbmlMasterElementSize(kMkvSeekHead, entry_size); |
+ |
+ start_pos_ = writer->Position(); |
+ |
+ const uint64 bytes_written = WriteVoidElement(writer, size + entry_size); |
+ if (!bytes_written) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+bool SeekHead::AddSeekEntry(uint32 id, uint64 pos) { |
+ for (int32 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 SeekHead::GetId(int index) const { |
+ if (index < 0 || index >= kSeekEntryCount) |
+ return UINT_MAX; |
+ return seek_entry_id_[index]; |
+} |
+ |
+uint64 SeekHead::GetPosition(int index) const { |
+ if (index < 0 || index >= kSeekEntryCount) |
+ return ULLONG_MAX; |
+ return seek_entry_pos_[index]; |
+} |
+ |
+bool SeekHead::SetSeekEntry(int index, uint32 id, uint64 position) { |
+ if (index < 0 || index >= kSeekEntryCount) |
+ return false; |
+ seek_entry_id_[index] = id; |
+ seek_entry_pos_[index] = position; |
+ return true; |
+} |
+ |
+uint64 SeekHead::MaxEntrySize() const { |
+ const uint64 max_entry_payload_size = |
+ EbmlElementSize(kMkvSeekID, 0xffffffffULL) + |
+ EbmlElementSize(kMkvSeekPosition, 0xffffffffffffffffULL); |
+ const uint64 max_entry_size = |
+ EbmlMasterElementSize(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), |
+ duration_pos_(-1) { |
+} |
+ |
+SegmentInfo::~SegmentInfo() { |
+ delete [] muxing_app_; |
+ delete [] writing_app_; |
+} |
+ |
+bool SegmentInfo::Init() { |
+ int32 major; |
+ int32 minor; |
+ int32 build; |
+ int32 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 pos = writer->Position(); |
+ |
+ if (writer->Position(duration_pos_)) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, |
+ 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 size = EbmlElementSize(kMkvTimecodeScale, timecode_scale_); |
+ if (duration_ > 0.0) |
+ size += EbmlElementSize(kMkvDuration, static_cast<float>(duration_)); |
+ size += EbmlElementSize(kMkvMuxingApp, muxing_app_); |
+ size += EbmlElementSize(kMkvWritingApp, writing_app_); |
+ |
+ if (!WriteEbmlMasterElement(writer, kMkvInfo, size)) |
+ return false; |
+ |
+ const int64 payload_position = writer->Position(); |
+ if (payload_position < 0) |
+ return false; |
+ |
+ if (!WriteEbmlElement(writer, kMkvTimecodeScale, timecode_scale_)) |
+ return false; |
+ |
+ if (duration_ > 0.0) { |
+ // Save for later |
+ duration_pos_ = writer->Position(); |
+ |
+ if (!WriteEbmlElement(writer, kMkvDuration, static_cast<float>(duration_))) |
+ return false; |
+ } |
+ |
+ if (!WriteEbmlElement(writer, kMkvMuxingApp, muxing_app_)) |
+ return false; |
+ if (!WriteEbmlElement(writer, kMkvWritingApp, writing_app_)) |
+ return false; |
+ |
+ const int64 stop_position = writer->Position(); |
+ if (stop_position < 0 || |
+ stop_position - payload_position != static_cast<int64>(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), |
+ payload_pos_(0), |
+ size_position_(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 i = 0; i < cluster_list_size_; ++i) { |
+ Cluster* const cluster = cluster_list_[i]; |
+ delete cluster; |
+ } |
+ delete [] cluster_list_; |
+ } |
+ |
+ if (frames_) { |
+ for (int32 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 diff, |
+ int32 index, |
+ uint64* cues_size) { |
+ const uint64 old_cues_size = *cues_size; |
+ CuePoint* const cue_point = cues_.GetCueByIndex(index); |
+ if (cue_point == NULL) |
+ return; |
+ const uint64 old_cue_point_size = cue_point->Size(); |
+ const uint64 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 size of Cues Element |
+ // Let b = Difference in Cue Point's size after this pass |
+ // Let c = Difference in length of Cues Element's size |
+ // (This is computed as CodedSize(a + b) - CodedSize(a) |
+ // Let d = a + b + c. Now d is the new size of the Cues element which is |
+ // passed on to the next recursive call. |
+ const uint64 cue_point_size_diff = cue_point->Size() - old_cue_point_size; |
+ const uint64 cue_size_diff = GetCodedUIntSize(*cues_size + |
+ cue_point_size_diff) - |
+ GetCodedUIntSize(*cues_size); |
+ *cues_size += cue_point_size_diff + cue_size_diff; |
+ diff = *cues_size - old_cues_size; |
+ if (diff > 0) { |
+ for (int32 i = 0; i < cues_.cue_entries_size(); ++i) { |
+ MoveCuesBeforeClustersHelper(diff, i, cues_size); |
+ } |
+ } |
+} |
+ |
+void Segment::MoveCuesBeforeClusters() { |
+ const uint64 current_cue_size = cues_.Size(); |
+ uint64 cue_size = current_cue_size; |
+ for (int32 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 cluster_index = 0; |
+ int32 cues_index = 0; |
+ for (int32 i = 0; i < SeekHead::kSeekEntryCount; ++i) { |
+ if (seek_head_.GetId(i) == kMkvCluster) |
+ cluster_index = i; |
+ if (seek_head_.GetId(i) == kMkvCues) |
+ cues_index = i; |
+ } |
+ seek_head_.SetSeekEntry(cues_index, kMkvCues, |
+ seek_head_.GetPosition(cluster_index)); |
+ seek_head_.SetSeekEntry(cluster_index, 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; |
+ return segment_info_.Init(); |
+} |
+ |
+bool Segment::CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader, |
+ IMkvWriter* writer) { |
+ if (!writer->Seekable() || chunking_) |
+ return false; |
+ const int64 cluster_offset = cluster_list_[0]->size_position() - |
+ GetUIntSize(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 pos = writer->Position(); |
+ const int64 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; |
+ |
+ if (mode_ == kFile) { |
+ if (cluster_list_size_ > 0) { |
+ // Update last cluster's size |
+ Cluster* const old_cluster = cluster_list_[cluster_list_size_-1]; |
+ |
+ if (!old_cluster || !old_cluster->Finalize()) |
+ return false; |
+ } |
+ |
+ if (chunking_ && chunk_writer_cluster_) { |
+ chunk_writer_cluster_->Close(); |
+ chunk_count_++; |
+ } |
+ |
+ const double duration = |
+ (static_cast<double>(last_timestamp_) + last_block_duration_) / |
+ segment_info_.timecode_scale(); |
+ segment_info_.set_duration(duration); |
+ if (!segment_info_.Finalize(writer_header_)) |
+ return false; |
+ |
+ if (output_cues_) |
+ if (!seek_head_.AddSeekEntry(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 pos = writer_header_->Position(); |
+ const int64 segment_size = MaxOffset(); |
+ |
+ if (segment_size < 1) |
+ return false; |
+ |
+ 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 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_); |
+} |
+ |
+uint64 Segment::AddVideoTrack(int32 width, int32 height, int32 number) { |
+ VideoTrack* const track = new (std::nothrow) VideoTrack(&seed_); // NOLINT |
+ if (!track) |
+ return 0; |
+ |
+ track->set_type(Tracks::kVideo); |
+ track->set_codec_id(Tracks::kVp8CodecId); |
+ track->set_width(width); |
+ track->set_height(height); |
+ |
+ tracks_.AddTrack(track, number); |
+ has_video_ = true; |
+ |
+ return track->number(); |
+} |
+ |
+bool Segment::AddCuePoint(uint64 timestamp, uint64 track) { |
+ if (cluster_list_size_ < 1) |
+ return false; |
+ |
+ const Cluster* const cluster = cluster_list_[cluster_list_size_-1]; |
+ if (!cluster) |
+ return false; |
+ |
+ CuePoint* const cue = new (std::nothrow) CuePoint(); // NOLINT |
+ if (!cue) |
+ return false; |
+ |
+ cue->set_time(timestamp / segment_info_.timecode_scale()); |
+ cue->set_block_number(cluster->blocks_added()); |
+ cue->set_cluster_pos(cluster->position_for_cues()); |
+ cue->set_track(track); |
+ if (!cues_.AddCue(cue)) |
+ return false; |
+ |
+ new_cuepoint_ = false; |
+ return true; |
+} |
+ |
+uint64 Segment::AddAudioTrack(int32 sample_rate, |
+ int32 channels, |
+ int32 number) { |
+ AudioTrack* const track = new (std::nothrow) AudioTrack(&seed_); // NOLINT |
+ if (!track) |
+ return 0; |
+ |
+ track->set_type(Tracks::kAudio); |
+ track->set_codec_id(Tracks::kVorbisCodecId); |
+ track->set_sample_rate(sample_rate); |
+ track->set_channels(channels); |
+ |
+ tracks_.AddTrack(track, number); |
+ |
+ return track->number(); |
+} |
+ |
+bool Segment::AddFrame(const uint8* frame, |
+ uint64 length, |
+ uint64 track_number, |
+ uint64 timestamp, |
+ bool is_key) { |
+ if (!frame) |
+ return false; |
+ |
+ if (!CheckHeaderInfo()) |
+ return false; |
+ |
+ // Check for non-monotonically increasing timestamps. |
+ if (timestamp < last_timestamp_) |
+ return false; |
+ |
+ // 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(track_number) && !force_new_cluster_) { |
+ Frame* const new_frame = new (std::nothrow) Frame(); |
+ if (new_frame == NULL || !new_frame->Init(frame, length)) |
+ return false; |
+ new_frame->set_track_number(track_number); |
+ new_frame->set_timestamp(timestamp); |
+ new_frame->set_is_key(is_key); |
+ |
+ if (!QueueFrame(new_frame)) |
+ return false; |
+ |
+ return true; |
+ } |
+ |
+ if (!DoNewClusterProcessing(track_number, timestamp, is_key)) |
+ return false; |
+ |
+ if (cluster_list_size_ < 1) |
+ return false; |
+ |
+ Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; |
+ if (!cluster) |
+ return false; |
+ |
+ const uint64 timecode_scale = segment_info_.timecode_scale(); |
+ const uint64 abs_timecode = timestamp / timecode_scale; |
+ |
+ if (!cluster->AddFrame(frame, |
+ length, |
+ track_number, |
+ abs_timecode, |
+ is_key)) |
+ return false; |
+ |
+ if (new_cuepoint_ && cues_track_ == track_number) { |
+ if (!AddCuePoint(timestamp, cues_track_)) |
+ return false; |
+ } |
+ |
+ if (timestamp > last_timestamp_) |
+ last_timestamp_ = timestamp; |
+ |
+ return true; |
+} |
+ |
+bool Segment::AddFrameWithAdditional(const uint8* frame, |
+ uint64 length, |
+ const uint8* additional, |
+ uint64 additional_length, |
+ uint64 add_id, |
+ uint64 track_number, |
+ uint64 timestamp, |
+ bool is_key) { |
+ if (frame == NULL || additional == NULL) |
+ return false; |
+ |
+ if (!CheckHeaderInfo()) |
+ return false; |
+ |
+ // Check for non-monotonically increasing timestamps. |
+ if (timestamp < last_timestamp_) |
+ return false; |
+ |
+ // 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(track_number) && !force_new_cluster_) { |
+ Frame* const new_frame = new (std::nothrow) Frame(); |
+ if (new_frame == NULL || !new_frame->Init(frame, length)) |
+ return false; |
+ new_frame->set_track_number(track_number); |
+ new_frame->set_timestamp(timestamp); |
+ new_frame->set_is_key(is_key); |
+ |
+ if (!QueueFrame(new_frame)) |
+ return false; |
+ |
+ return true; |
+ } |
+ |
+ if (!DoNewClusterProcessing(track_number, timestamp, is_key)) |
+ return false; |
+ |
+ if (cluster_list_size_ < 1) |
+ return false; |
+ |
+ Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; |
+ if (cluster == NULL) |
+ return false; |
+ |
+ const uint64 timecode_scale = segment_info_.timecode_scale(); |
+ const uint64 abs_timecode = timestamp / timecode_scale; |
+ |
+ if (!cluster->AddFrameWithAdditional(frame, |
+ length, |
+ additional, |
+ additional_length, |
+ add_id, |
+ track_number, |
+ abs_timecode, |
+ is_key)) |
+ return false; |
+ |
+ if (new_cuepoint_ && cues_track_ == track_number) { |
+ if (!AddCuePoint(timestamp, cues_track_)) |
+ return false; |
+ } |
+ |
+ if (timestamp > last_timestamp_) |
+ last_timestamp_ = timestamp; |
+ |
+ return true; |
+} |
+ |
+bool Segment::AddFrameWithDiscardPadding(const uint8* frame, |
+ uint64 length, |
+ int64 discard_padding, |
+ uint64 track_number, |
+ uint64 timestamp, |
+ bool is_key) { |
+ if (frame == NULL || discard_padding <= 0) |
+ return false; |
+ |
+ if (!CheckHeaderInfo()) |
+ return false; |
+ |
+ // Check for non-monotonically increasing timestamps. |
+ if (timestamp < last_timestamp_) |
+ return false; |
+ |
+ // 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(track_number) && !force_new_cluster_) { |
+ Frame* const new_frame = new (std::nothrow) Frame(); |
+ if (new_frame == NULL || !new_frame->Init(frame, length)) |
+ return false; |
+ new_frame->set_track_number(track_number); |
+ new_frame->set_timestamp(timestamp); |
+ new_frame->set_is_key(is_key); |
+ new_frame->set_discard_padding(discard_padding); |
+ |
+ if (!QueueFrame(new_frame)) |
+ return false; |
+ |
+ return true; |
+ } |
+ |
+ if (!DoNewClusterProcessing(track_number, timestamp, is_key)) |
+ return false; |
+ |
+ if (cluster_list_size_ < 1) |
+ return false; |
+ |
+ Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; |
+ if (!cluster) |
+ return false; |
+ |
+ const uint64 timecode_scale = segment_info_.timecode_scale(); |
+ const uint64 abs_timecode = timestamp / timecode_scale; |
+ |
+ if (!cluster->AddFrameWithDiscardPadding(frame, length, |
+ discard_padding, |
+ track_number, |
+ abs_timecode, |
+ is_key)) { |
+ return false; |
+ } |
+ |
+ if (new_cuepoint_ && cues_track_ == track_number) { |
+ if (!AddCuePoint(timestamp, cues_track_)) |
+ return false; |
+ } |
+ |
+ if (timestamp > last_timestamp_) |
+ last_timestamp_ = timestamp; |
+ |
+ return true; |
+} |
+ |
+bool Segment::AddMetadata(const uint8* frame, |
+ uint64 length, |
+ uint64 track_number, |
+ uint64 timestamp_ns, |
+ uint64 duration_ns) { |
+ if (!frame) |
+ return false; |
+ |
+ if (!CheckHeaderInfo()) |
+ return false; |
+ |
+ // Check for non-monotonically increasing timestamps. |
+ if (timestamp_ns < last_timestamp_) |
+ return false; |
+ |
+ if (!DoNewClusterProcessing(track_number, timestamp_ns, true)) |
+ return false; |
+ |
+ if (cluster_list_size_ < 1) |
+ return false; |
+ |
+ Cluster* const cluster = cluster_list_[cluster_list_size_-1]; |
+ |
+ if (!cluster) |
+ return false; |
+ |
+ const uint64 timecode_scale = segment_info_.timecode_scale(); |
+ const uint64 abs_timecode = timestamp_ns / timecode_scale; |
+ const uint64 duration_timecode = duration_ns / timecode_scale; |
+ |
+ if (!cluster->AddMetadata(frame, |
+ length, |
+ track_number, |
+ abs_timecode, |
+ duration_timecode)) |
+ return false; |
+ |
+ if (timestamp_ns > last_timestamp_) |
+ last_timestamp_ = timestamp_ns; |
+ |
+ return true; |
+} |
+ |
+bool Segment::AddGenericFrame(const Frame* frame) { |
+ last_block_duration_ = frame->duration(); |
+ if (!tracks_.TrackIsAudio(frame->track_number()) && |
+ !tracks_.TrackIsVideo(frame->track_number()) && |
+ frame->duration() > 0) { |
+ return AddMetadata(frame->frame(), |
+ frame->length(), |
+ frame->track_number(), |
+ frame->timestamp(), |
+ frame->duration()); |
+ } else if (frame->additional() && frame->additional_length() > 0) { |
+ return AddFrameWithAdditional(frame->frame(), |
+ frame->length(), |
+ frame->additional(), |
+ frame->additional_length(), |
+ frame->add_id(), |
+ frame->track_number(), |
+ frame->timestamp(), |
+ frame->is_key()); |
+ } else if (frame->discard_padding() > 0) { |
+ return AddFrameWithDiscardPadding(frame->frame(), frame->length(), |
+ frame->discard_padding(), |
+ frame->track_number(), |
+ frame->timestamp(), |
+ frame->is_key()); |
+ } else { |
+ return AddFrame(frame->frame(), |
+ frame->length(), |
+ frame->track_number(), |
+ frame->timestamp(), |
+ frame->is_key()); |
+ } |
+} |
+ |
+void Segment::OutputCues(bool output_cues) { |
+ output_cues_ = output_cues; |
+} |
+ |
+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 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 track_number) const { |
+ return tracks_.GetTrackByNumber(track_number); |
+} |
+ |
+bool Segment::WriteSegmentHeader() { |
+ // TODO(fgalligan): Support more than one segment. |
+ if (!WriteEbmlHeader(writer_header_)) |
+ return false; |
+ |
+ // 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_, 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(kMkvInfo, MaxOffset())) |
+ return false; |
+ if (!segment_info_.Write(writer_header_)) |
+ return false; |
+ |
+ if (!seek_head_.AddSeekEntry(kMkvTracks, MaxOffset())) |
+ return false; |
+ if (!tracks_.Write(writer_header_)) |
+ return false; |
+ |
+ if (chapters_.Count() > 0) { |
+ if (!seek_head_.AddSeekEntry(kMkvChapters, MaxOffset())) |
+ return false; |
+ if (!chapters_.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 track_number, |
+ uint64 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 timecode_scale = segment_info_.timecode_scale(); |
+ const uint64 frame_timecode = frame_timestamp_ns / timecode_scale; |
+ |
+ const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1]; |
+ const uint64 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; // error |
+ |
+ // 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 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 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 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 frame_timestamp_ns) { |
+ const int32 new_size = cluster_list_size_ + 1; |
+ |
+ if (new_size > cluster_list_capacity_) { |
+ // Add more clusters. |
+ const int32 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 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 (mode_ == kFile) { |
+ 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()) |
+ 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 timecode_scale = segment_info_.timecode_scale(); |
+ const uint64 frame_timecode = frame_timestamp_ns / timecode_scale; |
+ |
+ uint64 cluster_timecode = frame_timecode; |
+ |
+ if (frames_size_ > 0) { |
+ const Frame* const f = frames_[0]; // earliest queued frame |
+ const uint64 ns = f->timestamp(); |
+ const uint64 tc = ns / timecode_scale; |
+ |
+ if (tc < cluster_timecode) |
+ cluster_timecode = tc; |
+ } |
+ |
+ Cluster*& cluster = cluster_list_[cluster_list_size_]; |
+ const int64 offset = MaxOffset(); |
+ cluster = new (std::nothrow) Cluster(cluster_timecode, offset); // NOLINT |
+ if (!cluster) |
+ return false; |
+ |
+ if (!cluster->Init(writer_cluster_)) |
+ return false; |
+ |
+ cluster_list_size_ = new_size; |
+ return true; |
+} |
+ |
+bool Segment::DoNewClusterProcessing(uint64 track_number, |
+ uint64 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(kMkvCluster, MaxOffset())) |
+ return false; |
+ |
+ if (output_cues_ && cues_track_ == 0) { |
+ // Check for a video track |
+ for (uint32 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; |
+} |
+ |
+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 Segment::MaxOffset() { |
+ if (!writer_header_) |
+ return -1; |
+ |
+ int64 offset = writer_header_->Position() - payload_pos_; |
+ |
+ if (chunking_) { |
+ for (int32 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 new_size = frames_size_ + 1; |
+ |
+ if (new_size > frames_capacity_) { |
+ // Add more frames. |
+ const int32 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 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; |
+ |
+ const uint64 timecode_scale = segment_info_.timecode_scale(); |
+ |
+ for (int32 i = 0; i < frames_size_; ++i) { |
+ Frame*& frame = frames_[i]; |
+ const uint64 frame_timestamp = frame->timestamp(); // ns |
+ const uint64 frame_timecode = frame_timestamp / timecode_scale; |
+ |
+ if (frame->discard_padding() > 0) { |
+ if (!cluster->AddFrameWithDiscardPadding(frame->frame(), |
+ frame->length(), |
+ frame->discard_padding(), |
+ frame->track_number(), |
+ frame_timecode, |
+ frame->is_key())) { |
+ return -1; |
+ } |
+ } else { |
+ if (!cluster->AddFrame(frame->frame(), |
+ frame->length(), |
+ frame->track_number(), |
+ frame_timecode, |
+ frame->is_key())) { |
+ return -1; |
+ } |
+ } |
+ |
+ if (new_cuepoint_ && cues_track_ == frame->track_number()) { |
+ if (!AddCuePoint(frame_timestamp, cues_track_)) |
+ return -1; |
+ } |
+ |
+ if (frame_timestamp > last_timestamp_) |
+ last_timestamp_ = frame_timestamp; |
+ |
+ delete frame; |
+ frame = NULL; |
+ } |
+ |
+ const int result = frames_size_; |
+ frames_size_ = 0; |
+ |
+ return result; |
+} |
+ |
+bool Segment::WriteFramesLessThan(uint64 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; |
+ |
+ const uint64 timecode_scale = segment_info_.timecode_scale(); |
+ int32 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 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]; |
+ const uint64 frame_timestamp = frame_prev->timestamp(); |
+ const uint64 frame_timecode = frame_timestamp / timecode_scale; |
+ const int64 discard_padding = frame_prev->discard_padding(); |
+ |
+ if (discard_padding > 0) { |
+ if (!cluster->AddFrameWithDiscardPadding(frame_prev->frame(), |
+ frame_prev->length(), |
+ discard_padding, |
+ frame_prev->track_number(), |
+ frame_timecode, |
+ frame_prev->is_key())) { |
+ return false; |
+ } |
+ } else { |
+ if (!cluster->AddFrame(frame_prev->frame(), |
+ frame_prev->length(), |
+ frame_prev->track_number(), |
+ frame_timecode, |
+ frame_prev->is_key())) { |
+ return false; |
+ } |
+ } |
+ |
+ if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) { |
+ if (!AddCuePoint(frame_timestamp, cues_track_)) |
+ return false; |
+ } |
+ |
+ ++shift_left; |
+ if (frame_timestamp > last_timestamp_) |
+ last_timestamp_ = frame_timestamp; |
+ |
+ delete frame_prev; |
+ } |
+ |
+ if (shift_left > 0) { |
+ if (shift_left >= frames_size_) |
+ return false; |
+ |
+ const int32 new_frames_size = frames_size_ - shift_left; |
+ for (int32 i = 0; i < new_frames_size; ++i) { |
+ frames_[i] = frames_[i+shift_left]; |
+ } |
+ |
+ frames_size_ = new_frames_size; |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace mkvmuxer |