| // 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 <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <ctime> |
| #include <limits> |
| #include <new> |
| |
| #include "mkvmuxerutil.hpp" |
| #include "mkvwriter.hpp" |
| #include "webmids.hpp" |
| |
| namespace mkvmuxer { |
| |
| /////////////////////////////////////////////////////////////// |
| // |
| // 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; |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // |
| // Frame Class |
| |
| Frame::Frame() |
| : frame_(NULL), |
| length_(0), |
| track_number_(0), |
| timestamp_(0), |
| is_key_(false) { |
| } |
| |
| Frame::~Frame() { |
| delete [] frame_; |
| } |
| |
| 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; |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // |
| // 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; |
| } |
| |
| const CuePoint* Cues::GetCueByIndex(int32 index) const { |
| if (cue_entries_ == NULL) |
| return NULL; |
| |
| if (index >= cue_entries_size_) |
| return NULL; |
| |
| return cue_entries_[index]; |
| } |
| |
| 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() |
| : codec_id_(NULL), |
| codec_private_(NULL), |
| language_(NULL), |
| name_(NULL), |
| number_(0), |
| type_(0), |
| uid_(MakeUID()), |
| 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 (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_); |
| |
| 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 (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 |
| } |
| } |
| } |
| |
| bool Track::is_seeded_ = false; |
| |
| uint64 Track::MakeUID() { |
| if (!is_seeded_) { |
| srand(static_cast<uint32>(time(NULL))); |
| is_seeded_ = true; |
| } |
| |
| uint64 track_uid = 0; |
| for (int32 i = 0; i < 7; ++i) { // avoid problems with 8-byte values |
| track_uid <<= 8; |
| |
| const int32 nn = rand(); |
| const int32 n = 0xFF & (nn >> 4); // throw away low-order bits |
| |
| track_uid |= n; |
| } |
| |
| return track_uid; |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // |
| // VideoTrack Class |
| |
| VideoTrack::VideoTrack() |
| : display_height_(0), |
| display_width_(0), |
| frame_rate_(0.0), |
| height_(0), |
| stereo_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; |
| } |
| |
| 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 (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 (frame_rate_ > 0.0) |
| size += EbmlElementSize(kMkvFrameRate, static_cast<float>(frame_rate_)); |
| |
| return size; |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // |
| // AudioTrack Class |
| |
| AudioTrack::AudioTrack() |
| : 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* const Tracks::kVp8CodecId = "V_VP8"; |
| const char* const Tracks::kVorbisCodecId = "A_VORBIS"; |
| |
| 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; |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // |
| // 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::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; |
| } |
| |
| 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; |
| |
| // To simplify things, we require that there be fewer than 127 |
| // tracks -- this allows us to serialize the track number value for |
| // a stream using a single byte, per the Matroska encoding. |
| |
| if (track_number == 0 || track_number > 0x7E) |
| return false; |
| |
| const int64 cluster_timecode = this->Cluster::timecode(); |
| const int64 rel_timecode = |
| static_cast<int64>(abs_timecode) - cluster_timecode; |
| |
| if (rel_timecode < 0) |
| return false; |
| |
| if (rel_timecode > std::numeric_limits<int16>::max()) |
| return false; |
| |
| if (write_block == NULL) |
| return false; |
| |
| if (finalized_) |
| return false; |
| |
| if (!header_written_) |
| if (!WriteClusterHeader()) |
| return false; |
| |
| const uint64 element_size = (*write_block)(writer_, |
| frame, |
| length, |
| track_number, |
| rel_timecode, |
| generic_arg); |
| |
| if (element_size == 0) |
| return false; |
| |
| AddPayloadSize(element_size); |
| blocks_added_++; |
| |
| 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; |
| } |
| |
| 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_track_(0), |
| frames_(NULL), |
| frames_capacity_(0), |
| frames_size_(0), |
| has_video_(false), |
| header_written_(false), |
| 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) { |
| } |
| |
| 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_; |
| } |
| } |
| |
| 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::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_) / segment_info_.timecode_scale(); |
| segment_info_.set_duration(duration); |
| if (!segment_info_.Finalize(writer_header_)) |
| return false; |
| |
| // TODO(fgalligan): Add support for putting the Cues at the front. |
| 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; |
| } |
| |
| 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; // NOLINT |
| |
| if (!track) |
| return NULL; |
| |
| if (!tracks_.AddTrack(track, number)) { |
| delete track; |
| return NULL; |
| } |
| |
| return track; |
| } |
| |
| uint64 Segment::AddVideoTrack(int32 width, int32 height, int32 number) { |
| VideoTrack* const vid_track = new (std::nothrow) VideoTrack(); // NOLINT |
| if (!vid_track) |
| return 0; |
| |
| vid_track->set_type(Tracks::kVideo); |
| vid_track->set_codec_id(Tracks::kVp8CodecId); |
| vid_track->set_width(width); |
| vid_track->set_height(height); |
| |
| tracks_.AddTrack(vid_track, number); |
| has_video_ = true; |
| |
| return vid_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() + 1); |
| 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 aud_track = new (std::nothrow) AudioTrack(); // NOLINT |
| if (!aud_track) |
| return 0; |
| |
| aud_track->set_type(Tracks::kAudio); |
| aud_track->set_codec_id(Tracks::kVorbisCodecId); |
| aud_track->set_sample_rate(sample_rate); |
| aud_track->set_channels(channels); |
| |
| tracks_.AddTrack(aud_track, number); |
| |
| return aud_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)) { |
| Frame* const new_frame = new Frame(); |
| if (!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::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; |
| } |
| |
| 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; |
| } |
| |
| 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 (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 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 > std::numeric_limits<int16>::max()) |
| 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; |
| |
| // 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 (!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; |
| |
| 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 |