diff --git a/third_party/libwebm/AUTHORS.TXT b/third_party/libwebm/AUTHORS.TXT new file mode 100644 index 000000000..8ab6f794c --- /dev/null +++ b/third_party/libwebm/AUTHORS.TXT @@ -0,0 +1,4 @@ +# Names should be added to this file like so: +# Name or Organization + +Google Inc. diff --git a/third_party/libwebm/LICENSE.TXT b/third_party/libwebm/LICENSE.TXT new file mode 100644 index 000000000..7a6f99547 --- /dev/null +++ b/third_party/libwebm/LICENSE.TXT @@ -0,0 +1,30 @@ +Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/third_party/libwebm/PATENTS.TXT b/third_party/libwebm/PATENTS.TXT new file mode 100644 index 000000000..4414d8385 --- /dev/null +++ b/third_party/libwebm/PATENTS.TXT @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the WebM Project. + +Google hereby grants to you a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer, and otherwise run, modify and propagate the contents of this +implementation of VP8, where such license applies only to those patent +claims, both currently owned by Google and acquired in the future, +licensable by Google that are necessarily infringed by this +implementation of VP8. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of VP8 or any code incorporated within this +implementation of VP8 constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of VP8 +shall terminate as of the date such litigation is filed. diff --git a/third_party/libwebm/README.webm b/third_party/libwebm/README.webm new file mode 100644 index 000000000..b13c8cbc6 --- /dev/null +++ b/third_party/libwebm/README.webm @@ -0,0 +1,7 @@ +URL: https://chromium.googlesource.com/webm/libwebm +Version: 630a0e3c338e1b32bddf513a2dad807908d2976a +License: BSD +License File: LICENSE.txt + +Description: +libwebm is used to handle WebM container I/O. diff --git a/third_party/libwebm/RELEASE.TXT b/third_party/libwebm/RELEASE.TXT new file mode 100644 index 000000000..a7e9f032c --- /dev/null +++ b/third_party/libwebm/RELEASE.TXT @@ -0,0 +1,34 @@ +1.0.0.5 + * Handled case when no duration + * Handled empty clusters + * Handled empty clusters when seeking + * Implemented check lacing bits + +1.0.0.4 + * Made Cues member variables mutables + * Defined against badly-formatted cue points + * Segment::GetCluster returns CuePoint too + * Separated cue-based searches + +1.0.0.3 + * Added Block::GetOffset() to get a frame's offset in a block + * Changed cluster count type from size_t to long + * Parsed SeekHead to find cues + * Allowed seeking beyond end of cluster cache + * Added not to attempt to reparse cues element + * Restructured Segment::LoadCluster + * Marked position of cues without parsing cues element + * Allowed cue points to be loaded incrementally + * Implemented to load lazily cue points as they're searched + * Merged Cues::LoadCuePoint into Cues::Find + * Lazy init cues + * Loaded cue point during find + +1.0.0.2 + * added support for Cues element + * seeking was improved + +1.0.0.1 + * fixed item 141 + * added item 142 + * added this file, RELEASE.TXT, to repository diff --git a/third_party/libwebm/mkvmuxer.cpp b/third_party/libwebm/mkvmuxer.cpp new file mode 100644 index 000000000..8ae0dda31 --- /dev/null +++ b/third_party/libwebm/mkvmuxer.cpp @@ -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 +#include +#include +#include +#include +#include + +#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(read_len), buf)) + return false; + dst->Write(buf, static_cast(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(length)]; // NOLINT + if (!data) + return false; + + delete [] frame_; + frame_ = data; + length_ = length; + + memcpy(frame_, frame, static_cast(length_)); + return true; +} + +bool Frame::AddAdditionalData(const uint8* additional, uint64 length, + uint64 add_id) { + uint8* const data = + new (std::nothrow) uint8[static_cast(length)]; // NOLINT + if (!data) + return false; + + delete [] additional_; + additional_ = data; + additional_length_ = length; + add_id_ = add_id; + + memcpy(additional_, additional, static_cast(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(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(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(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(length)]; // NOLINT + if (!enc_key_id_) + return false; + + memcpy(enc_key_id_, id, static_cast(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(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(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(length)]; // NOLINT + if (!codec_private_) + return false; + + memcpy(codec_private_, codec_private, static_cast(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(frame_rate_))) + return false; + + const int64 stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast(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(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(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(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(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(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(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 +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(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(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(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(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(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(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(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(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(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 diff --git a/third_party/libwebm/mkvmuxer.hpp b/third_party/libwebm/mkvmuxer.hpp new file mode 100644 index 000000000..63a315e1a --- /dev/null +++ b/third_party/libwebm/mkvmuxer.hpp @@ -0,0 +1,1403 @@ +// 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. + +#ifndef MKVMUXER_HPP +#define MKVMUXER_HPP + +#include "mkvmuxertypes.hpp" + +// For a description of the WebM elements see +// http://www.webmproject.org/code/specs/container/. + +namespace mkvparser { + class IMkvReader; +} // end namespace + +namespace mkvmuxer { + +class MkvWriter; +class Segment; + +/////////////////////////////////////////////////////////////// +// Interface used by the mkvmuxer to write out the Mkv data. +class IMkvWriter { + public: + // Writes out |len| bytes of |buf|. Returns 0 on success. + virtual int32 Write(const void* buf, uint32 len) = 0; + + // Returns the offset of the output position from the beginning of the + // output. + virtual int64 Position() const = 0; + + // Set the current File position. Returns 0 on success. + virtual int32 Position(int64 position) = 0; + + // Returns true if the writer is seekable. + virtual bool Seekable() const = 0; + + // Element start notification. Called whenever an element identifier is about + // to be written to the stream. |element_id| is the element identifier, and + // |position| is the location in the WebM stream where the first octet of the + // element identifier will be written. + // Note: the |MkvId| enumeration in webmids.hpp defines element values. + virtual void ElementStartNotify(uint64 element_id, int64 position) = 0; + + protected: + IMkvWriter(); + virtual ~IMkvWriter(); + + private: + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(IMkvWriter); +}; + +// Writes out the EBML header for a WebM file. This function must be called +// before any other libwebm writing functions are called. +bool WriteEbmlHeader(IMkvWriter* writer); + +// Copies in Chunk from source to destination between the given byte positions +bool ChunkedCopy(mkvparser::IMkvReader* source, IMkvWriter* dst, + int64 start, int64 size); + +/////////////////////////////////////////////////////////////// +// Class to hold data the will be written to a block. +class Frame { + public: + Frame(); + ~Frame(); + + // Copies |frame| data into |frame_|. Returns true on success. + bool Init(const uint8* frame, uint64 length); + + // Copies |additional| data into |additional_|. Returns true on success. + bool AddAdditionalData(const uint8* additional, uint64 length, + uint64 add_id); + + uint64 add_id() const { return add_id_; } + const uint8* additional() const { return additional_; } + uint64 additional_length() const { return additional_length_; } + void set_duration(uint64 duration) { duration_ = duration; } + uint64 duration() const { return duration_; } + const uint8* frame() const { return frame_; } + void set_is_key(bool key) { is_key_ = key; } + bool is_key() const { return is_key_; } + uint64 length() const { return length_; } + void set_track_number(uint64 track_number) { track_number_ = track_number; } + uint64 track_number() const { return track_number_; } + void set_timestamp(uint64 timestamp) { timestamp_ = timestamp; } + uint64 timestamp() const { return timestamp_; } + void set_discard_padding(uint64 discard_padding) { + discard_padding_ = discard_padding; + } + uint64 discard_padding() const { return discard_padding_; } + + private: + // Id of the Additional data. + uint64 add_id_; + + // Pointer to additional data. Owned by this class. + uint8* additional_; + + // Length of the additional data. + uint64 additional_length_; + + // Duration of the frame in nanoseconds. + uint64 duration_; + + // Pointer to the data. Owned by this class. + uint8* frame_; + + // Flag telling if the data should set the key flag of a block. + bool is_key_; + + // Length of the data. + uint64 length_; + + // Mkv track number the data is associated with. + uint64 track_number_; + + // Timestamp of the data in nanoseconds. + uint64 timestamp_; + + // Discard padding for the frame. + int64 discard_padding_; +}; + +/////////////////////////////////////////////////////////////// +// Class to hold one cue point in a Cues element. +class CuePoint { + public: + CuePoint(); + ~CuePoint(); + + // Returns the size in bytes for the entire CuePoint element. + uint64 Size() const; + + // Output the CuePoint element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + void set_time(uint64 time) { time_ = time; } + uint64 time() const { return time_; } + void set_track(uint64 track) { track_ = track; } + uint64 track() const { return track_; } + void set_cluster_pos(uint64 cluster_pos) { cluster_pos_ = cluster_pos; } + uint64 cluster_pos() const { return cluster_pos_; } + void set_block_number(uint64 block_number) { block_number_ = block_number; } + uint64 block_number() const { return block_number_; } + void set_output_block_number(bool output_block_number) { + output_block_number_ = output_block_number; + } + bool output_block_number() const { return output_block_number_; } + + private: + // Returns the size in bytes for the payload of the CuePoint element. + uint64 PayloadSize() const; + + // Absolute timecode according to the segment time base. + uint64 time_; + + // The Track element associated with the CuePoint. + uint64 track_; + + // The position of the Cluster containing the Block. + uint64 cluster_pos_; + + // Number of the Block within the Cluster, starting from 1. + uint64 block_number_; + + // If true the muxer will write out the block number for the cue if the + // block number is different than the default of 1. Default is set to true. + bool output_block_number_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(CuePoint); +}; + +/////////////////////////////////////////////////////////////// +// Cues element. +class Cues { + public: + Cues(); + ~Cues(); + + // Adds a cue point to the Cues element. Returns true on success. + bool AddCue(CuePoint* cue); + + // Returns the cue point by index. Returns NULL if there is no cue point + // match. + CuePoint* GetCueByIndex(int32 index) const; + + // Returns the total size of the Cues element + uint64 Size(); + + // Output the Cues element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + int32 cue_entries_size() const { return cue_entries_size_; } + void set_output_block_number(bool output_block_number) { + output_block_number_ = output_block_number; + } + bool output_block_number() const { return output_block_number_; } + + private: + // Number of allocated elements in |cue_entries_|. + int32 cue_entries_capacity_; + + // Number of CuePoints in |cue_entries_|. + int32 cue_entries_size_; + + // CuePoint list. + CuePoint** cue_entries_; + + // If true the muxer will write out the block number for the cue if the + // block number is different than the default of 1. Default is set to true. + bool output_block_number_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cues); +}; + +/////////////////////////////////////////////////////////////// +// ContentEncAESSettings element +class ContentEncAESSettings { + public: + enum { + kCTR = 1 + }; + + ContentEncAESSettings(); + ~ContentEncAESSettings() {} + + // Returns the size in bytes for the ContentEncAESSettings element. + uint64 Size() const; + + // Writes out the ContentEncAESSettings element to |writer|. Returns true on + // success. + bool Write(IMkvWriter* writer) const; + + uint64 cipher_mode() const { return cipher_mode_; } + + private: + // Returns the size in bytes for the payload of the ContentEncAESSettings + // element. + uint64 PayloadSize() const; + + // Sub elements + uint64 cipher_mode_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncAESSettings); +}; + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +// Elements used to describe if the track data has been encrypted or +// compressed with zlib or header stripping. +// Currently only whole frames can be encrypted with AES. This dictates that +// ContentEncodingOrder will be 0, ContentEncodingScope will be 1, +// ContentEncodingType will be 1, and ContentEncAlgo will be 5. +class ContentEncoding { + public: + ContentEncoding(); + ~ContentEncoding(); + + // Sets the content encryption id. Copies |length| bytes from |id| to + // |enc_key_id_|. Returns true on success. + bool SetEncryptionID(const uint8* id, uint64 length); + + // Returns the size in bytes for the ContentEncoding element. + uint64 Size() const; + + // Writes out the ContentEncoding element to |writer|. Returns true on + // success. + bool Write(IMkvWriter* writer) const; + + uint64 enc_algo() const { return enc_algo_; } + uint64 encoding_order() const { return encoding_order_; } + uint64 encoding_scope() const { return encoding_scope_; } + uint64 encoding_type() const { return encoding_type_; } + ContentEncAESSettings* enc_aes_settings() { return &enc_aes_settings_; } + + private: + // Returns the size in bytes for the encoding elements. + uint64 EncodingSize(uint64 compresion_size, uint64 encryption_size) const; + + // Returns the size in bytes for the encryption elements. + uint64 EncryptionSize() const; + + // Track element names + uint64 enc_algo_; + uint8* enc_key_id_; + uint64 encoding_order_; + uint64 encoding_scope_; + uint64 encoding_type_; + + // ContentEncAESSettings element. + ContentEncAESSettings enc_aes_settings_; + + // Size of the ContentEncKeyID data in bytes. + uint64 enc_key_id_length_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding); +}; + +/////////////////////////////////////////////////////////////// +// Track element. +class Track { + public: + // The |seed| parameter is used to synthesize a UID for the track. + explicit Track(unsigned int* seed); + virtual ~Track(); + + // Adds a ContentEncoding element to the Track. Returns true on success. + virtual bool AddContentEncoding(); + + // Returns the ContentEncoding by index. Returns NULL if there is no + // ContentEncoding match. + ContentEncoding* GetContentEncodingByIndex(uint32 index) const; + + // Returns the size in bytes for the payload of the Track element. + virtual uint64 PayloadSize() const; + + // Returns the size in bytes of the Track element. + virtual uint64 Size() const; + + // Output the Track element to the writer. Returns true on success. + virtual bool Write(IMkvWriter* writer) const; + + // Sets the CodecPrivate element of the Track element. Copies |length| + // bytes from |codec_private| to |codec_private_|. Returns true on success. + bool SetCodecPrivate(const uint8* codec_private, uint64 length); + + void set_codec_id(const char* codec_id); + const char* codec_id() const { return codec_id_; } + const uint8* codec_private() const { return codec_private_; } + void set_language(const char* language); + const char* language() const { return language_; } + void set_max_block_additional_id(uint64 max_block_additional_id) { + max_block_additional_id_ = max_block_additional_id; + } + uint64 max_block_additional_id() const { return max_block_additional_id_; } + void set_name(const char* name); + const char* name() const { return name_; } + void set_number(uint64 number) { number_ = number; } + uint64 number() const { return number_; } + void set_type(uint64 type) { type_ = type; } + uint64 type() const { return type_; } + void set_uid(uint64 uid) { uid_ = uid; } + uint64 uid() const { return uid_; } + void set_codec_delay(uint64 codec_delay) { codec_delay_ = codec_delay; } + uint64 codec_delay() const { return codec_delay_; } + void set_seek_pre_roll(uint64 seek_pre_roll) { + seek_pre_roll_ = seek_pre_roll; + } + uint64 seek_pre_roll() const { return seek_pre_roll_; } + + uint64 codec_private_length() const { return codec_private_length_; } + uint32 content_encoding_entries_size() const { + return content_encoding_entries_size_; + } + + private: + // Track element names + char* codec_id_; + uint8* codec_private_; + char* language_; + uint64 max_block_additional_id_; + char* name_; + uint64 number_; + uint64 type_; + uint64 uid_; + uint64 codec_delay_; + uint64 seek_pre_roll_; + + // Size of the CodecPrivate data in bytes. + uint64 codec_private_length_; + + // ContentEncoding element list. + ContentEncoding** content_encoding_entries_; + + // Number of ContentEncoding elements added. + uint32 content_encoding_entries_size_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Track); +}; + +/////////////////////////////////////////////////////////////// +// Track that has video specific elements. +class VideoTrack : public Track { + public: + // Supported modes for stereo 3D. + enum StereoMode { + kMono = 0, + kSideBySideLeftIsFirst = 1, + kTopBottomRightIsFirst = 2, + kTopBottomLeftIsFirst = 3, + kSideBySideRightIsFirst = 11 + }; + + enum AlphaMode { + kNoAlpha = 0, + kAlpha = 1 + }; + + // The |seed| parameter is used to synthesize a UID for the track. + explicit VideoTrack(unsigned int* seed); + virtual ~VideoTrack(); + + // Returns the size in bytes for the payload of the Track element plus the + // video specific elements. + virtual uint64 PayloadSize() const; + + // Output the VideoTrack element to the writer. Returns true on success. + virtual bool Write(IMkvWriter* writer) const; + + // Sets the video's stereo mode. Returns true on success. + bool SetStereoMode(uint64 stereo_mode); + + // Sets the video's alpha mode. Returns true on success. + bool SetAlphaMode(uint64 alpha_mode); + + void set_display_height(uint64 height) { display_height_ = height; } + uint64 display_height() const { return display_height_; } + void set_display_width(uint64 width) { display_width_ = width; } + uint64 display_width() const { return display_width_; } + void set_frame_rate(double frame_rate) { frame_rate_ = frame_rate; } + double frame_rate() const { return frame_rate_; } + void set_height(uint64 height) { height_ = height; } + uint64 height() const { return height_; } + uint64 stereo_mode() { return stereo_mode_; } + uint64 alpha_mode() { return alpha_mode_; } + void set_width(uint64 width) { width_ = width; } + uint64 width() const { return width_; } + + private: + // Returns the size in bytes of the Video element. + uint64 VideoPayloadSize() const; + + // Video track element names. + uint64 display_height_; + uint64 display_width_; + double frame_rate_; + uint64 height_; + uint64 stereo_mode_; + uint64 alpha_mode_; + uint64 width_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(VideoTrack); +}; + +/////////////////////////////////////////////////////////////// +// Track that has audio specific elements. +class AudioTrack : public Track { + public: + // The |seed| parameter is used to synthesize a UID for the track. + explicit AudioTrack(unsigned int* seed); + virtual ~AudioTrack(); + + // Returns the size in bytes for the payload of the Track element plus the + // audio specific elements. + virtual uint64 PayloadSize() const; + + // Output the AudioTrack element to the writer. Returns true on success. + virtual bool Write(IMkvWriter* writer) const; + + void set_bit_depth(uint64 bit_depth) { bit_depth_ = bit_depth; } + uint64 bit_depth() const { return bit_depth_; } + void set_channels(uint64 channels) { channels_ = channels; } + uint64 channels() const { return channels_; } + void set_sample_rate(double sample_rate) { sample_rate_ = sample_rate; } + double sample_rate() const { return sample_rate_; } + + private: + // Audio track element names. + uint64 bit_depth_; + uint64 channels_; + double sample_rate_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(AudioTrack); +}; + +/////////////////////////////////////////////////////////////// +// Tracks element +class Tracks { + public: + // Audio and video type defined by the Matroska specs. + enum { + kVideo = 0x1, + kAudio = 0x2 + }; + // Opus, Vorbis, VP8, and VP9 codec ids defined by the Matroska specs. + static const char kOpusCodecId[]; + static const char kVorbisCodecId[]; + static const char kVp8CodecId[]; + static const char kVp9CodecId[]; + + Tracks(); + ~Tracks(); + + // Adds a Track element to the Tracks object. |track| will be owned and + // deleted by the Tracks object. Returns true on success. |number| is the + // number to use for the track. |number| must be >= 0. If |number| == 0 + // then the muxer will decide on the track number. + bool AddTrack(Track* track, int32 number); + + // Returns the track by index. Returns NULL if there is no track match. + const Track* GetTrackByIndex(uint32 idx) const; + + // Search the Tracks and return the track that matches |tn|. Returns NULL + // if there is no track match. + Track* GetTrackByNumber(uint64 track_number) const; + + // Returns true if the track number is an audio track. + bool TrackIsAudio(uint64 track_number) const; + + // Returns true if the track number is a video track. + bool TrackIsVideo(uint64 track_number) const; + + // Output the Tracks element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + uint32 track_entries_size() const { return track_entries_size_; } + + private: + // Track element list. + Track** track_entries_; + + // Number of Track elements added. + uint32 track_entries_size_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tracks); +}; + +/////////////////////////////////////////////////////////////// +// Chapter element +// +class Chapter { + public: + // Set the identifier for this chapter. (This corresponds to the + // Cue Identifier line in WebVTT.) + // TODO(matthewjheaney): the actual serialization of this item in + // MKV is pending. + bool set_id(const char* id); + + // Converts the nanosecond start and stop times of this chapter to + // their corresponding timecode values, and stores them that way. + void set_time(const Segment& segment, + uint64 start_time_ns, + uint64 end_time_ns); + + // Sets the uid for this chapter. Primarily used to enable + // deterministic output from the muxer. + void set_uid(const uint64 uid) { uid_ = uid; } + + // Add a title string to this chapter, per the semantics described + // here: + // http://www.matroska.org/technical/specs/index.html + // + // The title ("chapter string") is a UTF-8 string. + // + // The language has ISO 639-2 representation, described here: + // http://www.loc.gov/standards/iso639-2/englangn.html + // http://www.loc.gov/standards/iso639-2/php/English_list.php + // If you specify NULL as the language value, this implies + // English ("eng"). + // + // The country value corresponds to the codes listed here: + // http://www.iana.org/domains/root/db/ + // + // The function returns false if the string could not be allocated. + bool add_string(const char* title, + const char* language, + const char* country); + + private: + friend class Chapters; + + // For storage of chapter titles that differ by language. + class Display { + public: + // Establish representation invariant for new Display object. + void Init(); + + // Reclaim resources, in anticipation of destruction. + void Clear(); + + // Copies the title to the |title_| member. Returns false on + // error. + bool set_title(const char* title); + + // Copies the language to the |language_| member. Returns false + // on error. + bool set_language(const char* language); + + // Copies the country to the |country_| member. Returns false on + // error. + bool set_country(const char* country); + + // If |writer| is non-NULL, serialize the Display sub-element of + // the Atom into the stream. Returns the Display element size on + // success, 0 if error. + uint64 WriteDisplay(IMkvWriter* writer) const; + + private: + char* title_; + char* language_; + char* country_; + }; + + Chapter(); + ~Chapter(); + + // Establish the representation invariant for a newly-created + // Chapter object. The |seed| parameter is used to create the UID + // for this chapter atom. + void Init(unsigned int* seed); + + // Copies this Chapter object to a different one. This is used when + // expanding a plain array of Chapter objects (see Chapters). + void ShallowCopy(Chapter* dst) const; + + // Reclaim resources used by this Chapter object, pending its + // destruction. + void Clear(); + + // If there is no storage remaining on the |displays_| array for a + // new display object, creates a new, longer array and copies the + // existing Display objects to the new array. Returns false if the + // array cannot be expanded. + bool ExpandDisplaysArray(); + + // If |writer| is non-NULL, serialize the Atom sub-element into the + // stream. Returns the total size of the element on success, 0 if + // error. + uint64 WriteAtom(IMkvWriter* writer) const; + + // The string identifier for this chapter (corresponds to WebVTT cue + // identifier). + char* id_; + + // Start timecode of the chapter. + uint64 start_timecode_; + + // Stop timecode of the chapter. + uint64 end_timecode_; + + // The binary identifier for this chapter. + uint64 uid_; + + // The Atom element can contain multiple Display sub-elements, as + // the same logical title can be rendered in different languages. + Display* displays_; + + // The physical length (total size) of the |displays_| array. + int displays_size_; + + // The logical length (number of active elements) on the |displays_| + // array. + int displays_count_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapter); +}; + +/////////////////////////////////////////////////////////////// +// Chapters element +// +class Chapters { + public: + Chapters(); + ~Chapters(); + + Chapter* AddChapter(unsigned int* seed); + + // Returns the number of chapters that have been added. + int Count() const; + + // Output the Chapters element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + private: + // Expands the chapters_ array if there is not enough space to contain + // another chapter object. Returns true on success. + bool ExpandChaptersArray(); + + // If |writer| is non-NULL, serialize the Edition sub-element of the + // Chapters element into the stream. Returns the Edition element + // size on success, 0 if error. + uint64 WriteEdition(IMkvWriter* writer) const; + + // Total length of the chapters_ array. + int chapters_size_; + + // Number of active chapters on the chapters_ array. + int chapters_count_; + + // Array for storage of chapter objects. + Chapter* chapters_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapters); +}; + +/////////////////////////////////////////////////////////////// +// Cluster element +// +// Notes: +// |Init| must be called before any other method in this class. +class Cluster { + public: + Cluster(uint64 timecode, int64 cues_pos); + ~Cluster(); + + // |timecode| is the absolute timecode of the cluster. |cues_pos| is the + // position for the cluster within the segment that should be written in + // the cues element. + bool Init(IMkvWriter* ptr_writer); + + // Adds a frame to be output in the file. The frame is written out through + // |writer_| if successful. Returns true on success. + // Inputs: + // frame: Pointer to the data + // length: Length of the data + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // timecode: Absolute (not relative to cluster) timestamp of the + // frame, expressed in timecode units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrame(const uint8* frame, + uint64 length, + uint64 track_number, + uint64 timecode, // timecode units (absolute) + bool is_key); + + // Adds a frame to be output in the file. The frame is written out through + // |writer_| if successful. Returns true on success. + // Inputs: + // frame: Pointer to the data + // length: Length of the data + // additional: Pointer to the additional data + // additional_length: Length of the additional data + // add_id: Value of BlockAddID element + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // abs_timecode: Absolute (not relative to cluster) timestamp of the + // frame, expressed in timecode units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithAdditional(const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 abs_timecode, + bool is_key); + + // Adds a frame to be output in the file. The frame is written out through + // |writer_| if successful. Returns true on success. + // Inputs: + // frame: Pointer to the data. + // length: Length of the data. + // discard_padding: DiscardPadding element value. + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // abs_timecode: Absolute (not relative to cluster) timestamp of the + // frame, expressed in timecode units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithDiscardPadding(const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 abs_timecode, + bool is_key); + + // Writes a frame of metadata to the output medium; returns true on + // success. + // Inputs: + // frame: Pointer to the data + // length: Length of the data + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // timecode: Absolute (not relative to cluster) timestamp of the + // metadata frame, expressed in timecode units. + // duration: Duration of metadata frame, in timecode units. + // + // The metadata frame is written as a block group, with a duration + // sub-element but no reference time sub-elements (indicating that + // it is considered a keyframe, per Matroska semantics). + bool AddMetadata(const uint8* frame, + uint64 length, + uint64 track_number, + uint64 timecode, // timecode units (absolute) + uint64 duration); // timecode units + + // Increments the size of the cluster's data in bytes. + void AddPayloadSize(uint64 size); + + // Closes the cluster so no more data can be written to it. Will update the + // cluster's size if |writer_| is seekable. Returns true on success. + bool Finalize(); + + // Returns the size in bytes for the entire Cluster element. + uint64 Size() const; + + int64 size_position() const { return size_position_; } + int32 blocks_added() const { return blocks_added_; } + uint64 payload_size() const { return payload_size_; } + int64 position_for_cues() const { return position_for_cues_; } + uint64 timecode() const { return timecode_; } + + private: + // Signature that matches either of WriteSimpleBlock or WriteMetadataBlock + // in the muxer utilities package. + typedef uint64 (*WriteBlock)(IMkvWriter* writer, + const uint8* data, + uint64 length, + uint64 track_number, + int64 timecode, + uint64 generic_arg); + + // Signature that matches WriteBlockWithAdditional + // in the muxer utilities package. + typedef uint64 (*WriteBlockAdditional)(IMkvWriter* writer, + const uint8* data, + uint64 length, + const uint8* additional, + uint64 add_id, + uint64 additional_length, + uint64 track_number, + int64 timecode, + uint64 is_key); + + // Signature that matches WriteBlockWithDiscardPadding + // in the muxer utilities package. + typedef uint64 (*WriteBlockDiscardPadding)(IMkvWriter* writer, + const uint8* data, + uint64 length, + int64 discard_padding, + uint64 track_number, + int64 timecode, + uint64 is_key); + + // Utility method that confirms that blocks can still be added, and that the + // cluster header has been written. Used by |DoWriteBlock*|. Returns true + // when successful. + template + bool PreWriteBlock(Type* write_function); + + // Utility method used by the |DoWriteBlock*| methods that handles the book + // keeping required after each block is written. + void PostWriteBlock(uint64 element_size); + + // 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. + bool IsValidTrackNumber(uint64 track_number) const; + + // Given |abs_timecode|, calculates timecode relative to most recent timecode. + // Returns -1 on failure, or a relative timecode. + int64 GetRelativeTimecode(int64 abs_timecode) const; + + // Used to implement AddFrame and AddMetadata. + bool DoWriteBlock(const uint8* frame, + uint64 length, + uint64 track_number, + uint64 absolute_timecode, + uint64 generic_arg, + WriteBlock write_block); + + // Used to implement AddFrameWithAdditional + bool DoWriteBlockWithAdditional(const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 absolute_timecode, + uint64 generic_arg, + WriteBlockAdditional write_block); + + // Used to implement AddFrameWithDiscardPadding + bool DoWriteBlockWithDiscardPadding(const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 absolute_timecode, + uint64 generic_arg, + WriteBlockDiscardPadding write_block); + + // Outputs the Cluster header to |writer_|. Returns true on success. + bool WriteClusterHeader(); + + // Number of blocks added to the cluster. + int32 blocks_added_; + + // Flag telling if the cluster has been closed. + bool finalized_; + + // Flag telling if the cluster's header has been written. + bool header_written_; + + // The size of the cluster elements in bytes. + uint64 payload_size_; + + // The file position used for cue points. + const int64 position_for_cues_; + + // The file position of the cluster's size element. + int64 size_position_; + + // The absolute timecode of the cluster. + const uint64 timecode_; + + // Pointer to the writer object. Not owned by this class. + IMkvWriter* writer_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cluster); +}; + +/////////////////////////////////////////////////////////////// +// SeekHead element +class SeekHead { + public: + SeekHead(); + ~SeekHead(); + + // TODO(fgalligan): Change this to reserve a certain size. Then check how + // big the seek entry to be added is as not every seek entry will be the + // maximum size it could be. + // Adds a seek entry to be written out when the element is finalized. |id| + // must be the coded mkv element id. |pos| is the file position of the + // element. Returns true on success. + bool AddSeekEntry(uint32 id, uint64 pos); + + // Writes out SeekHead and SeekEntry elements. Returns true on success. + bool Finalize(IMkvWriter* writer) const; + + // Returns the id of the Seek Entry at the given index. Returns -1 if index is + // out of range. + uint32 GetId(int index) const; + + // Returns the position of the Seek Entry at the given index. Returns -1 if + // index is out of range. + uint64 GetPosition(int index) const; + + // Sets the Seek Entry id and position at given index. + // Returns true on success. + bool SetSeekEntry(int index, uint32 id, uint64 position); + + // Reserves space by writing out a Void element which will be updated with + // a SeekHead element later. Returns true on success. + bool Write(IMkvWriter* writer); + + // We are going to put a cap on the number of Seek Entries. + const static int32 kSeekEntryCount = 5; + + private: + // Returns the maximum size in bytes of one seek entry. + uint64 MaxEntrySize() const; + + // Seek entry id element list. + uint32 seek_entry_id_[kSeekEntryCount]; + + // Seek entry pos element list. + uint64 seek_entry_pos_[kSeekEntryCount]; + + // The file position of SeekHead element. + int64 start_pos_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SeekHead); +}; + +/////////////////////////////////////////////////////////////// +// Segment Information element +class SegmentInfo { + public: + SegmentInfo(); + ~SegmentInfo(); + + // Will update the duration if |duration_| is > 0.0. Returns true on success. + bool Finalize(IMkvWriter* writer) const; + + // Sets |muxing_app_| and |writing_app_|. + bool Init(); + + // Output the Segment Information element to the writer. Returns true on + // success. + bool Write(IMkvWriter* writer); + + void set_duration(double duration) { duration_ = duration; } + double duration() const { return duration_; } + void set_muxing_app(const char* app); + const char* muxing_app() const { return muxing_app_; } + void set_timecode_scale(uint64 scale) { timecode_scale_ = scale; } + uint64 timecode_scale() const { return timecode_scale_; } + void set_writing_app(const char* app); + const char* writing_app() const { return writing_app_; } + + private: + // Segment Information element names. + // Initially set to -1 to signify that a duration has not been set and should + // not be written out. + double duration_; + // Set to libwebm-%d.%d.%d.%d, major, minor, build, revision. + char* muxing_app_; + uint64 timecode_scale_; + // Initially set to libwebm-%d.%d.%d.%d, major, minor, build, revision. + char* writing_app_; + + // The file position of the duration element. + int64 duration_pos_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SegmentInfo); +}; + +/////////////////////////////////////////////////////////////// +// This class represents the main segment in a WebM file. Currently only +// supports one Segment element. +// +// Notes: +// |Init| must be called before any other method in this class. +class Segment { + public: + enum Mode { + kLive = 0x1, + kFile = 0x2 + }; + + enum CuesPosition { + kAfterClusters = 0x0, // Position Cues after Clusters - Default + kBeforeClusters = 0x1 // Position Cues before Clusters + }; + + const static uint64 kDefaultMaxClusterDuration = 30000000000ULL; + + Segment(); + ~Segment(); + + // Initializes |SegmentInfo| and returns result. Always returns false when + // |ptr_writer| is NULL. + bool Init(IMkvWriter* ptr_writer); + + // Adds a generic track to the segment. Returns the newly-allocated + // track object (which is owned by the segment) on success, NULL on + // error. |number| is the number to use for the track. |number| + // must be >= 0. If |number| == 0 then the muxer will decide on the + // track number. + Track* AddTrack(int32 number); + + // Adds a Vorbis audio track to the segment. Returns the number of the track + // on success, 0 on error. |number| is the number to use for the audio track. + // |number| must be >= 0. If |number| == 0 then the muxer will decide on + // the track number. + uint64 AddAudioTrack(int32 sample_rate, int32 channels, int32 number); + + // Adds an empty chapter to the chapters of this segment. Returns + // non-NULL on success. After adding the chapter, the caller should + // populate its fields via the Chapter member functions. + Chapter* AddChapter(); + + // Adds a cue point to the Cues element. |timestamp| is the time in + // nanoseconds of the cue's time. |track| is the Track of the Cue. This + // function must be called after AddFrame to calculate the correct + // BlockNumber for the CuePoint. Returns true on success. + bool AddCuePoint(uint64 timestamp, uint64 track); + + // Adds a frame to be output in the file. Returns true on success. + // Inputs: + // frame: Pointer to the data + // length: Length of the data + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timestamp: Timestamp of the frame in nanoseconds from 0. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrame(const uint8* frame, + uint64 length, + uint64 track_number, + uint64 timestamp_ns, + bool is_key); + + // Writes a frame of metadata to the output medium; returns true on + // success. + // Inputs: + // frame: Pointer to the data + // length: Length of the data + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timecode: Absolute timestamp of the metadata frame, expressed + // in nanosecond units. + // duration: Duration of metadata frame, in nanosecond units. + // + // The metadata frame is written as a block group, with a duration + // sub-element but no reference time sub-elements (indicating that + // it is considered a keyframe, per Matroska semantics). + bool AddMetadata(const uint8* frame, + uint64 length, + uint64 track_number, + uint64 timestamp_ns, + uint64 duration_ns); + + // Writes a frame with additional data to the output medium; returns true on + // success. + // Inputs: + // frame: Pointer to the data. + // length: Length of the data. + // additional: Pointer to additional data. + // additional_length: Length of additional data. + // add_id: Additional ID which identifies the type of additional data. + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timestamp: Absolute timestamp of the frame, expressed in nanosecond + // units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithAdditional(const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 timestamp, + bool is_key); + + // Writes a frame with DiscardPadding to the output medium; returns true on + // success. + // Inputs: + // frame: Pointer to the data. + // length: Length of the data. + // discard_padding: DiscardPadding element value. + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timestamp: Absolute timestamp of the frame, expressed in nanosecond + // units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithDiscardPadding(const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 timestamp, + bool is_key); + + // Writes a Frame to the output medium. Chooses the correct way of writing + // the frame (Block vs SimpleBlock) based on the parameters passed. + // Inputs: + // frame: frame object + bool AddGenericFrame(const Frame* frame); + + // Adds a VP8 video track to the segment. Returns the number of the track on + // success, 0 on error. |number| is the number to use for the video track. + // |number| must be >= 0. If |number| == 0 then the muxer will decide on + // the track number. + uint64 AddVideoTrack(int32 width, int32 height, int32 number); + + // This function must be called after Finalize() if you need a copy of the + // output with Cues written before the Clusters. It will return false if the + // writer is not seekable of if chunking is set to true. + // Input parameters: + // reader - an IMkvReader object created with the same underlying file of the + // current writer object. Make sure to close the existing writer + // object before creating this so that all the data is properly + // flushed and available for reading. + // writer - an IMkvWriter object pointing to a *different* file than the one + // pointed by the current writer object. This file will contain the + // Cues element before the Clusters. + bool CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader, + IMkvWriter* writer); + + // Sets which track to use for the Cues element. Must have added the track + // before calling this function. Returns true on success. |track_number| is + // returned by the Add track functions. + bool CuesTrack(uint64 track_number); + + // This will force the muxer to create a new Cluster when the next frame is + // added. + void ForceNewClusterOnNextFrame(); + + // Writes out any frames that have not been written out. Finalizes the last + // cluster. May update the size and duration of the segment. May output the + // Cues element. May finalize the SeekHead element. Returns true on success. + bool Finalize(); + + // Returns the Cues object. + Cues* GetCues() { return &cues_; } + + // Returns the Segment Information object. + const SegmentInfo* GetSegmentInfo() const { return &segment_info_; } + SegmentInfo* GetSegmentInfo() { return &segment_info_; } + + // Search the Tracks and return the track that matches |track_number|. + // Returns NULL if there is no track match. + Track* GetTrackByNumber(uint64 track_number) const; + + // Toggles whether to output a cues element. + void OutputCues(bool output_cues); + + // Sets if the muxer will output files in chunks or not. |chunking| is a + // flag telling whether or not to turn on chunking. |filename| is the base + // filename for the chunk files. The header chunk file will be named + // |filename|.hdr and the data chunks will be named + // |filename|_XXXXXX.chk. Chunking implies that the muxer will be writing + // to files so the muxer will use the default MkvWriter class to control + // what data is written to what files. Returns true on success. + // TODO: Should we change the IMkvWriter Interface to add Open and Close? + // That will force the interface to be dependent on files. + bool SetChunking(bool chunking, const char* filename); + + bool chunking() const { return chunking_; } + uint64 cues_track() const { return cues_track_; } + void set_max_cluster_duration(uint64 max_cluster_duration) { + max_cluster_duration_ = max_cluster_duration; + } + uint64 max_cluster_duration() const { return max_cluster_duration_; } + void set_max_cluster_size(uint64 max_cluster_size) { + max_cluster_size_ = max_cluster_size; + } + uint64 max_cluster_size() const { return max_cluster_size_; } + void set_mode(Mode mode) { mode_ = mode; } + Mode mode() const { return mode_; } + CuesPosition cues_position() const { return cues_position_; } + bool output_cues() const { return output_cues_; } + const SegmentInfo* segment_info() const { return &segment_info_; } + + private: + // Checks if header information has been output and initialized. If not it + // will output the Segment element and initialize the SeekHead elment and + // Cues elements. + bool CheckHeaderInfo(); + + // Sets |name| according to how many chunks have been written. |ext| is the + // file extension. |name| must be deleted by the calling app. Returns true + // on success. + bool UpdateChunkName(const char* ext, char** name) const; + + // Returns the maximum offset within the segment's payload. When chunking + // this function is needed to determine offsets of elements within the + // chunked files. Returns -1 on error. + int64 MaxOffset(); + + // Adds the frame to our frame array. + bool QueueFrame(Frame* frame); + + // Output all frames that are queued. Returns -1 on error, otherwise + // it returns the number of frames written. + int WriteFramesAll(); + + // Output all frames that are queued that have an end time that is less + // then |timestamp|. Returns true on success and if there are no frames + // queued. + bool WriteFramesLessThan(uint64 timestamp); + + // Outputs the segment header, Segment Information element, SeekHead element, + // and Tracks element to |writer_|. + bool WriteSegmentHeader(); + + // Given a frame with the specified timestamp (nanosecond units) and + // keyframe status, determine whether a new cluster should be + // created, before writing enqueued frames and the frame itself. The + // function returns one of the following values: + // -1 = error: an out-of-order frame was detected + // 0 = do not create a new cluster, and write frame to the existing cluster + // 1 = create a new cluster, and write frame to that new cluster + // 2 = create a new cluster, and re-run test + int TestFrame(uint64 track_num, uint64 timestamp_ns, bool key) const; + + // Create a new cluster, using the earlier of the first enqueued + // frame, or the indicated time. Returns true on success. + bool MakeNewCluster(uint64 timestamp_ns); + + // Checks whether a new cluster needs to be created, and if so + // creates a new cluster. Returns false if creation of a new cluster + // was necessary but creation was not successful. + bool DoNewClusterProcessing(uint64 track_num, uint64 timestamp_ns, bool key); + + + // Adjusts Cue Point values (to place Cues before Clusters) so that they + // reflect the correct offsets. + void MoveCuesBeforeClusters(); + + // This function recursively computes the correct cluster offsets (this is + // done to move the Cues before Clusters). It recursively updates the change + // in size (which indicates a change in cluster offset) until no sizes change. + // Parameters: + // diff - indicates the difference in size of the Cues element that needs to + // accounted for. + // index - index in the list of Cues which is currently being adjusted. + // cue_size - size of the Cues element. + void MoveCuesBeforeClustersHelper(uint64 diff, int index, uint64* cue_size); + + // Seeds the random number generator used to make UIDs. + unsigned int seed_; + + // WebM elements + Cues cues_; + SeekHead seek_head_; + SegmentInfo segment_info_; + Tracks tracks_; + Chapters chapters_; + + // Number of chunks written. + int chunk_count_; + + // Current chunk filename. + char* chunk_name_; + + // Default MkvWriter object created by this class used for writing clusters + // out in separate files. + MkvWriter* chunk_writer_cluster_; + + // Default MkvWriter object created by this class used for writing Cues + // element out to a file. + MkvWriter* chunk_writer_cues_; + + // Default MkvWriter object created by this class used for writing the + // Matroska header out to a file. + MkvWriter* chunk_writer_header_; + + // Flag telling whether or not the muxer is chunking output to multiple + // files. + bool chunking_; + + // Base filename for the chunked files. + char* chunking_base_name_; + + // File position offset where the Clusters end. + int64 cluster_end_offset_; + + // List of clusters. + Cluster** cluster_list_; + + // Number of cluster pointers allocated in the cluster list. + int32 cluster_list_capacity_; + + // Number of clusters in the cluster list. + int32 cluster_list_size_; + + // Indicates whether Cues should be written before or after Clusters + CuesPosition cues_position_; + + // Track number that is associated with the cues element for this segment. + uint64 cues_track_; + + // Tells the muxer to force a new cluster on the next Block. + bool force_new_cluster_; + + // List of stored audio frames. These variables are used to store frames so + // the muxer can follow the guideline "Audio blocks that contain the video + // key frame's timecode should be in the same cluster as the video key frame + // block." + Frame** frames_; + + // Number of frame pointers allocated in the frame list. + int32 frames_capacity_; + + // Number of frames in the frame list. + int32 frames_size_; + + // Flag telling if a video track has been added to the segment. + bool has_video_; + + // Flag telling if the segment's header has been written. + bool header_written_; + + // Duration of the last block in nanoseconds. + uint64 last_block_duration_; + + // Last timestamp in nanoseconds added to a cluster. + uint64 last_timestamp_; + + // Maximum time in nanoseconds for a cluster duration. This variable is a + // guideline and some clusters may have a longer duration. Default is 30 + // seconds. + uint64 max_cluster_duration_; + + // Maximum size in bytes for a cluster. This variable is a guideline and + // some clusters may have a larger size. Default is 0 which signifies that + // the muxer will decide the size. + uint64 max_cluster_size_; + + // The mode that segment is in. If set to |kLive| the writer must not + // seek backwards. + Mode mode_; + + // Flag telling the muxer that a new cue point should be added. + bool new_cuepoint_; + + // TODO(fgalligan): Should we add support for more than one Cues element? + // Flag whether or not the muxer should output a Cues element. + bool output_cues_; + + // The file position of the segment's payload. + int64 payload_pos_; + + // The file position of the element's size. + int64 size_position_; + + // Pointer to the writer objects. Not owned by this class. + IMkvWriter* writer_cluster_; + IMkvWriter* writer_cues_; + IMkvWriter* writer_header_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Segment); +}; + +} //end namespace mkvmuxer + +#endif //MKVMUXER_HPP diff --git a/third_party/libwebm/mkvmuxertypes.hpp b/third_party/libwebm/mkvmuxertypes.hpp new file mode 100644 index 000000000..2c66fd2ab --- /dev/null +++ b/third_party/libwebm/mkvmuxertypes.hpp @@ -0,0 +1,30 @@ +// 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. + +#ifndef MKVMUXERTYPES_HPP +#define MKVMUXERTYPES_HPP + +// Copied from Chromium basictypes.h +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define LIBWEBM_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +namespace mkvmuxer { + +typedef unsigned char uint8; +typedef short int16; +typedef int int32; +typedef unsigned int uint32; +typedef long long int64; +typedef unsigned long long uint64; + +} //end namespace mkvmuxer + +#endif // MKVMUXERTYPES_HPP diff --git a/third_party/libwebm/mkvmuxerutil.cpp b/third_party/libwebm/mkvmuxerutil.cpp new file mode 100644 index 000000000..96350e9c5 --- /dev/null +++ b/third_party/libwebm/mkvmuxerutil.cpp @@ -0,0 +1,713 @@ +// 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 "mkvmuxerutil.hpp" + +#ifdef __ANDROID__ +#include +#endif + +#include +#include +#include +#ifdef _MSC_VER +#define _CRT_RAND_S +#endif +#include +#include +#include + +#include + +#include "mkvwriter.hpp" +#include "webmids.hpp" + +namespace mkvmuxer { + +int32 GetCodedUIntSize(uint64 value) { + if (value < 0x000000000000007FULL) + return 1; + else if (value < 0x0000000000003FFFULL) + return 2; + else if (value < 0x00000000001FFFFFULL) + return 3; + else if (value < 0x000000000FFFFFFFULL) + return 4; + else if (value < 0x00000007FFFFFFFFULL) + return 5; + else if (value < 0x000003FFFFFFFFFFULL) + return 6; + else if (value < 0x0001FFFFFFFFFFFFULL) + return 7; + return 8; +} + +int32 GetUIntSize(uint64 value) { + if (value < 0x0000000000000100ULL) + return 1; + else if (value < 0x0000000000010000ULL) + return 2; + else if (value < 0x0000000001000000ULL) + return 3; + else if (value < 0x0000000100000000ULL) + return 4; + else if (value < 0x0000010000000000ULL) + return 5; + else if (value < 0x0001000000000000ULL) + return 6; + else if (value < 0x0100000000000000ULL) + return 7; + return 8; +} + +uint64 EbmlMasterElementSize(uint64 type, uint64 value) { + // Size of EBML ID + int32 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += GetCodedUIntSize(value); + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, int64 value) { + return EbmlElementSize(type, static_cast(value)); +} + +uint64 EbmlElementSize(uint64 type, uint64 value) { + // Size of EBML ID + int32 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += GetUIntSize(value); + + // Size of Datasize + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, float /* value */ ) { + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += sizeof(float); + + // Size of Datasize + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, const char* value) { + if (!value) + return 0; + + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += strlen(value); + + // Size of Datasize + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size) { + if (!value) + return 0; + + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += size; + + // Size of Datasize + ebml_size += GetCodedUIntSize(size); + + return ebml_size; +} + +int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size) { + if (!writer || size < 1 || size > 8) + return -1; + + for (int32 i = 1; i <= size; ++i) { + const int32 byte_count = size - i; + const int32 bit_count = byte_count * 8; + + const int64 bb = value >> bit_count; + const uint8 b = static_cast(bb); + + const int32 status = writer->Write(&b, 1); + + if (status < 0) + return status; + } + + return 0; +} + +int32 SerializeFloat(IMkvWriter* writer, float f) { + if (!writer) + return -1; + + assert(sizeof(uint32) == sizeof(float)); + // This union is merely used to avoid a reinterpret_cast from float& to + // uint32& which will result in violation of strict aliasing. + union U32 { + uint32 u32; + float f; + } value; + value.f = f; + + for (int32 i = 1; i <= 4; ++i) { + const int32 byte_count = 4 - i; + const int32 bit_count = byte_count * 8; + + const uint8 byte = static_cast(value.u32 >> bit_count); + + const int32 status = writer->Write(&byte, 1); + + if (status < 0) + return status; + } + + return 0; +} + +int32 WriteUInt(IMkvWriter* writer, uint64 value) { + if (!writer) + return -1; + + int32 size = GetCodedUIntSize(value); + + return WriteUIntSize(writer, value, size); +} + +int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size) { + if (!writer || size < 0 || size > 8) + return -1; + + if (size > 0) { + const uint64 bit = 1LL << (size * 7); + + if (value > (bit - 2)) + return -1; + + value |= bit; + } else { + size = 1; + int64 bit; + + for (;;) { + bit = 1LL << (size * 7); + const uint64 max = bit - 2; + + if (value <= max) + break; + + ++size; + } + + if (size > 8) + return false; + + value |= bit; + } + + return SerializeInt(writer, value, size); +} + +int32 WriteID(IMkvWriter* writer, uint64 type) { + if (!writer) + return -1; + + writer->ElementStartNotify(type, writer->Position()); + + const int32 size = GetUIntSize(type); + + return SerializeInt(writer, type, size); +} + +bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 type, uint64 size) { + if (!writer) + return false; + + if (WriteID(writer, type)) + return false; + + if (WriteUInt(writer, size)) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value) { + if (!writer) + return false; + + if (WriteID(writer, type)) + return false; + + const uint64 size = GetUIntSize(value); + if (WriteUInt(writer, size)) + return false; + + if (SerializeInt(writer, value, static_cast(size))) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value) { + if (!writer) + return false; + + if (WriteID(writer, type)) + return false; + + if (WriteUInt(writer, 4)) + return false; + + if (SerializeFloat(writer, value)) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value) { + if (!writer || !value) + return false; + + if (WriteID(writer, type)) + return false; + + const int32 length = strlen(value); + if (WriteUInt(writer, length)) + return false; + + if (writer->Write(value, length)) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, + uint64 type, + const uint8* value, + uint64 size) { + if (!writer || !value || size < 1) + return false; + + if (WriteID(writer, type)) + return false; + + if (WriteUInt(writer, size)) + return false; + + if (writer->Write(value, static_cast(size))) + return false; + + return true; +} + +uint64 WriteSimpleBlock(IMkvWriter* writer, + const uint8* data, + uint64 length, + uint64 track_number, + int64 timecode, + uint64 is_key) { + if (!writer) + return false; + + if (!data || length < 1) + return false; + + // Here we only permit track number values to be no greater than + // 126, which the largest value we can store having a Matroska + // integer representation of only 1 byte. + + if (track_number < 1 || track_number > 126) + return false; + + // Technically the timestamp for a block can be less than the + // timestamp for the cluster itself (remember that block timestamp + // is a signed, 16-bit integer). However, as a simplification we + // only permit non-negative cluster-relative timestamps for blocks. + + if (timecode < 0 || timecode > kMaxBlockTimecode) + return false; + + if (WriteID(writer, kMkvSimpleBlock)) + return 0; + + const int32 size = static_cast(length) + 4; + if (WriteUInt(writer, size)) + return 0; + + if (WriteUInt(writer, static_cast(track_number))) + return 0; + + if (SerializeInt(writer, timecode, 2)) + return 0; + + uint64 flags = 0; + if (is_key) + flags |= 0x80; + + if (SerializeInt(writer, flags, 1)) + return 0; + + if (writer->Write(data, static_cast(length))) + return 0; + + const uint64 element_size = + GetUIntSize(kMkvSimpleBlock) + GetCodedUIntSize(size) + 4 + length; + + return element_size; +} + +// We must write the metadata (key)frame as a BlockGroup element, +// because we need to specify a duration for the frame. The +// BlockGroup element comprises the frame itself and its duration, +// and is laid out as follows: +// +// BlockGroup tag +// BlockGroup size +// Block tag +// Block size +// (the frame is the block payload) +// Duration tag +// Duration size +// (duration payload) +// +uint64 WriteMetadataBlock(IMkvWriter* writer, + const uint8* data, + uint64 length, + uint64 track_number, + int64 timecode, + uint64 duration) { + // We don't backtrack when writing to the stream, so we must + // pre-compute the BlockGroup size, by summing the sizes of each + // sub-element (the block and the duration). + + // We use a single byte for the track number of the block, which + // means the block header is exactly 4 bytes. + + // TODO(matthewjheaney): use EbmlMasterElementSize and WriteEbmlMasterElement + + const uint64 block_payload_size = 4 + length; + const int32 block_size = GetCodedUIntSize(block_payload_size); + const uint64 block_elem_size = 1 + block_size + block_payload_size; + + const int32 duration_payload_size = GetUIntSize(duration); + const int32 duration_size = GetCodedUIntSize(duration_payload_size); + const uint64 duration_elem_size = 1 + duration_size + duration_payload_size; + + const uint64 blockg_payload_size = block_elem_size + duration_elem_size; + const int32 blockg_size = GetCodedUIntSize(blockg_payload_size); + const uint64 blockg_elem_size = 1 + blockg_size + blockg_payload_size; + + if (WriteID(writer, kMkvBlockGroup)) // 1-byte ID size + return 0; + + if (WriteUInt(writer, blockg_payload_size)) + return 0; + + // Write Block element + + if (WriteID(writer, kMkvBlock)) // 1-byte ID size + return 0; + + if (WriteUInt(writer, block_payload_size)) + return 0; + + // Byte 1 of 4 + + if (WriteUInt(writer, track_number)) + return 0; + + // Bytes 2 & 3 of 4 + + if (SerializeInt(writer, timecode, 2)) + return 0; + + // Byte 4 of 4 + + const uint64 flags = 0; + + if (SerializeInt(writer, flags, 1)) + return 0; + + // Now write the actual frame (of metadata) + + if (writer->Write(data, static_cast(length))) + return 0; + + // Write Duration element + + if (WriteID(writer, kMkvBlockDuration)) // 1-byte ID size + return 0; + + if (WriteUInt(writer, duration_payload_size)) + return 0; + + if (SerializeInt(writer, duration, duration_payload_size)) + return 0; + + // Note that we don't write a reference time as part of the block + // group; no reference time(s) indicates that this block is a + // keyframe. (Unlike the case for a SimpleBlock element, the header + // bits of the Block sub-element of a BlockGroup element do not + // indicate keyframe status. The keyframe status is inferred from + // the absence of reference time sub-elements.) + + return blockg_elem_size; +} + +// Writes a WebM BlockGroup with BlockAdditional data. The structure is as +// follows: +// Indentation shows sub-levels +// BlockGroup +// Block +// Data +// BlockAdditions +// BlockMore +// BlockAddID +// 1 (Denotes Alpha) +// BlockAdditional +// Data +uint64 WriteBlockWithAdditional(IMkvWriter* writer, + const uint8* data, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + int64 timecode, + uint64 is_key) { + if (!data || !additional || length < 1 || additional_length < 1) + return 0; + + const uint64 block_payload_size = 4 + length; + const uint64 block_elem_size = EbmlMasterElementSize(kMkvBlock, + block_payload_size) + + block_payload_size; + const uint64 block_additional_elem_size = EbmlElementSize(kMkvBlockAdditional, + additional, + additional_length); + const uint64 block_addid_elem_size = EbmlElementSize(kMkvBlockAddID, add_id); + + const uint64 block_more_payload_size = block_addid_elem_size + + block_additional_elem_size; + const uint64 block_more_elem_size = EbmlMasterElementSize( + kMkvBlockMore, + block_more_payload_size) + + block_more_payload_size; + const uint64 block_additions_payload_size = block_more_elem_size; + const uint64 block_additions_elem_size = EbmlMasterElementSize( + kMkvBlockAdditions, + block_additions_payload_size) + + block_additions_payload_size; + const uint64 block_group_payload_size = block_elem_size + + block_additions_elem_size; + const uint64 block_group_elem_size = EbmlMasterElementSize( + kMkvBlockGroup, + block_group_payload_size) + + block_group_payload_size; + + if (!WriteEbmlMasterElement(writer, kMkvBlockGroup, + block_group_payload_size)) + return 0; + + if (!WriteEbmlMasterElement(writer, kMkvBlock, block_payload_size)) + return 0; + + if (WriteUInt(writer, track_number)) + return 0; + + if (SerializeInt(writer, timecode, 2)) + return 0; + + uint64 flags = 0; + if (is_key) + flags |= 0x80; + if (SerializeInt(writer, flags, 1)) + return 0; + + if (writer->Write(data, static_cast(length))) + return 0; + + if (!WriteEbmlMasterElement(writer, kMkvBlockAdditions, + block_additions_payload_size)) + return 0; + + if (!WriteEbmlMasterElement(writer, kMkvBlockMore, block_more_payload_size)) + return 0; + + if (!WriteEbmlElement(writer, kMkvBlockAddID, add_id)) + return 0; + + if (!WriteEbmlElement(writer, kMkvBlockAdditional, + additional, additional_length)) + return 0; + + return block_group_elem_size; +} + +// Writes a WebM BlockGroup with DiscardPadding. The structure is as follows: +// Indentation shows sub-levels +// BlockGroup +// Block +// Data +// DiscardPadding +uint64 WriteBlockWithDiscardPadding(IMkvWriter* writer, + const uint8* data, + uint64 length, + int64 discard_padding, + uint64 track_number, + int64 timecode, + uint64 is_key) { + if (!data || length < 1 || discard_padding <= 0) + return 0; + + const uint64 block_payload_size = 4 + length; + const uint64 block_elem_size = EbmlMasterElementSize(kMkvBlock, + block_payload_size) + + block_payload_size; + const uint64 discard_padding_elem_size = EbmlElementSize(kMkvDiscardPadding, + discard_padding); + const uint64 block_group_payload_size = block_elem_size + + discard_padding_elem_size; + const uint64 block_group_elem_size = EbmlMasterElementSize( + kMkvBlockGroup, + block_group_payload_size) + + block_group_payload_size; + + if (!WriteEbmlMasterElement(writer, kMkvBlockGroup, + block_group_payload_size)) + return 0; + + if (!WriteEbmlMasterElement(writer, kMkvBlock, block_payload_size)) + return 0; + + if (WriteUInt(writer, track_number)) + return 0; + + if (SerializeInt(writer, timecode, 2)) + return 0; + + uint64 flags = 0; + if (is_key) + flags |= 0x80; + if (SerializeInt(writer, flags, 1)) + return 0; + + if (writer->Write(data, static_cast(length))) + return 0; + + if (WriteID(writer, kMkvDiscardPadding)) + return 0; + + const uint64 size = GetUIntSize(discard_padding); + if (WriteUInt(writer, size)) + return false; + + if (SerializeInt(writer, discard_padding, static_cast(size))) + return false; + + return block_group_elem_size; +} + +uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) { + if (!writer) + return false; + + // Subtract one for the void ID and the coded size. + uint64 void_entry_size = size - 1 - GetCodedUIntSize(size-1); + uint64 void_size = EbmlMasterElementSize(kMkvVoid, void_entry_size) + + void_entry_size; + + if (void_size != size) + return 0; + + const int64 payload_position = writer->Position(); + if (payload_position < 0) + return 0; + + if (WriteID(writer, kMkvVoid)) + return 0; + + if (WriteUInt(writer, void_entry_size)) + return 0; + + const uint8 value = 0; + for (int32 i = 0; i < static_cast(void_entry_size); ++i) { + if (writer->Write(&value, 1)) + return 0; + } + + const int64 stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast(void_size)) + return 0; + + return void_size; +} + +void GetVersion(int32* major, int32* minor, int32* build, int32* revision) { + *major = 0; + *minor = 2; + *build = 1; + *revision = 0; +} + +} // namespace mkvmuxer + +mkvmuxer::uint64 mkvmuxer::MakeUID(unsigned int* seed) { + uint64 uid = 0; + +#ifdef __MINGW32__ + srand(*seed); +#endif + + for (int i = 0; i < 7; ++i) { // avoid problems with 8-byte values + uid <<= 8; + + // TODO(fgalligan): Move random number generation to platform specific code. +#ifdef _MSC_VER + (void)seed; + unsigned int random_value; + const errno_t e = rand_s(&random_value); + (void)e; + const int32 nn = random_value; +#elif __ANDROID__ + int32 temp_num = 1; + int fd = open("/dev/urandom", O_RDONLY); + if (fd != -1) { + read(fd, &temp_num, sizeof(int32)); + close(fd); + } + const int32 nn = temp_num; +#elif defined __MINGW32__ + const int32 nn = rand(); +#else + const int32 nn = rand_r(seed); +#endif + const int32 n = 0xFF & (nn >> 4); // throw away low-order bits + + uid |= n; + } + + return uid; +} diff --git a/third_party/libwebm/mkvmuxerutil.hpp b/third_party/libwebm/mkvmuxerutil.hpp new file mode 100644 index 000000000..d196ad3ac --- /dev/null +++ b/third_party/libwebm/mkvmuxerutil.hpp @@ -0,0 +1,151 @@ +// 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. + +#ifndef MKVMUXERUTIL_HPP +#define MKVMUXERUTIL_HPP + +#include "mkvmuxertypes.hpp" + +namespace mkvmuxer { + +class IMkvWriter; + +const uint64 kEbmlUnknownValue = 0x01FFFFFFFFFFFFFFULL; +const int64 kMaxBlockTimecode = 0x07FFFLL; + +// Writes out |value| in Big Endian order. Returns 0 on success. +int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size); + +// Returns the size in bytes of the element. +int32 GetUIntSize(uint64 value); +int32 GetCodedUIntSize(uint64 value); +uint64 EbmlMasterElementSize(uint64 type, uint64 value); +uint64 EbmlElementSize(uint64 type, int64 value); +uint64 EbmlElementSize(uint64 type, uint64 value); +uint64 EbmlElementSize(uint64 type, float value); +uint64 EbmlElementSize(uint64 type, const char* value); +uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size); + +// Creates an EBML coded number from |value| and writes it out. The size of +// the coded number is determined by the value of |value|. |value| must not +// be in a coded form. Returns 0 on success. +int32 WriteUInt(IMkvWriter* writer, uint64 value); + +// Creates an EBML coded number from |value| and writes it out. The size of +// the coded number is determined by the value of |size|. |value| must not +// be in a coded form. Returns 0 on success. +int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size); + +// Output an Mkv master element. Returns true if the element was written. +bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 value, uint64 size); + +// Outputs an Mkv ID, calls |IMkvWriter::ElementStartNotify|, and passes the +// ID to |SerializeInt|. Returns 0 on success. +int32 WriteID(IMkvWriter* writer, uint64 type); + +// Output an Mkv non-master element. Returns true if the element was written. +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value); +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value); +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value); +bool WriteEbmlElement(IMkvWriter* writer, + uint64 type, + const uint8* value, + uint64 size); + +// Output an Mkv Simple Block. +// Inputs: +// data: Pointer to the data. +// length: Length of the data. +// track_number: Track to add the data to. Value returned by Add track +// functions. Only values in the range [1, 126] are +// permitted. +// timecode: Relative timecode of the Block. Only values in the +// range [0, 2^15) are permitted. +// is_key: Non-zero value specifies that frame is a key frame. +uint64 WriteSimpleBlock(IMkvWriter* writer, + const uint8* data, + uint64 length, + uint64 track_number, + int64 timecode, + uint64 is_key); + +// Output a metadata keyframe, using a Block Group element. +// Inputs: +// data: Pointer to the (meta)data. +// length: Length of the (meta)data. +// track_number: Track to add the data to. Value returned by Add track +// functions. Only values in the range [1, 126] are +// permitted. +// timecode Timecode of frame, relative to cluster timecode. Only +// values in the range [0, 2^15) are permitted. +// duration_timecode Duration of frame, using timecode units. +uint64 WriteMetadataBlock(IMkvWriter* writer, + const uint8* data, + uint64 length, + uint64 track_number, + int64 timecode, + uint64 duration_timecode); + +// Output an Mkv Block with BlockAdditional data. +// Inputs: +// data: Pointer to the data. +// length: Length of the data. +// additional: Pointer to the additional data +// additional_length: Length of the additional data. +// add_id: Value of BlockAddID element. +// track_number: Track to add the data to. Value returned by Add track +// functions. Only values in the range [1, 126] are +// permitted. +// timecode: Relative timecode of the Block. Only values in the +// range [0, 2^15) are permitted. +// is_key: Non-zero value specifies that frame is a key frame. +uint64 WriteBlockWithAdditional(IMkvWriter* writer, + const uint8* data, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + int64 timecode, + uint64 is_key); + +// Output an Mkv Block with a DiscardPadding element. +// Inputs: +// data: Pointer to the data. +// length: Length of the data. +// discard_padding: DiscardPadding value. +// track_number: Track to add the data to. Value returned by Add track +// functions. Only values in the range [1, 126] are +// permitted. +// timecode: Relative timecode of the Block. Only values in the +// range [0, 2^15) are permitted. +// is_key: Non-zero value specifies that frame is a key frame. +uint64 WriteBlockWithDiscardPadding(IMkvWriter* writer, + const uint8* data, + uint64 length, + int64 discard_padding, + uint64 track_number, + int64 timecode, + uint64 is_key); + +// Output a void element. |size| must be the entire size in bytes that will be +// void. The function will calculate the size of the void header and subtract +// it from |size|. +uint64 WriteVoidElement(IMkvWriter* writer, uint64 size); + +// Returns the version number of the muxer in |major|, |minor|, |build|, +// and |revision|. +void GetVersion(int32* major, int32* minor, int32* build, int32* revision); + +// Returns a random number to be used for UID, using |seed| to seed +// the random-number generator (see POSIX rand_r() for semantics). +uint64 MakeUID(unsigned int* seed); + +} //end namespace mkvmuxer + +#endif // MKVMUXERUTIL_HPP diff --git a/third_party/libwebm/mkvparser.cpp b/third_party/libwebm/mkvparser.cpp new file mode 100644 index 000000000..b41456aba --- /dev/null +++ b/third_party/libwebm/mkvparser.cpp @@ -0,0 +1,9617 @@ +// 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 "mkvparser.hpp" +#include +#include +#include +#include + +#ifdef _MSC_VER +// Disable MSVC warnings that suggest making code non-portable. +#pragma warning(disable:4996) +#endif + +mkvparser::IMkvReader::~IMkvReader() +{ +} + +void mkvparser::GetVersion(int& major, int& minor, int& build, int& revision) +{ + major = 1; + minor = 0; + build = 0; + revision = 27; +} + +long long mkvparser::ReadUInt(IMkvReader* pReader, long long pos, long& len) +{ + assert(pReader); + assert(pos >= 0); + + int status; + +//#ifdef _DEBUG +// long long total, available; +// status = pReader->Length(&total, &available); +// assert(status >= 0); +// assert((total < 0) || (available <= total)); +// assert(pos < available); +// assert((available - pos) >= 1); //assume here max u-int len is 8 +//#endif + + len = 1; + + unsigned char b; + + status = pReader->Read(pos, 1, &b); + + if (status < 0) //error or underflow + return status; + + if (status > 0) //interpreted as "underflow" + return E_BUFFER_NOT_FULL; + + if (b == 0) //we can't handle u-int values larger than 8 bytes + return E_FILE_FORMAT_INVALID; + + unsigned char m = 0x80; + + while (!(b & m)) + { + m >>= 1; + ++len; + } + +//#ifdef _DEBUG +// assert((available - pos) >= len); +//#endif + + long long result = b & (~m); + ++pos; + + for (int i = 1; i < len; ++i) + { + status = pReader->Read(pos, 1, &b); + + if (status < 0) + { + len = 1; + return status; + } + + if (status > 0) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result <<= 8; + result |= b; + + ++pos; + } + + return result; +} + +long long mkvparser::GetUIntLength( + IMkvReader* pReader, + long long pos, + long& len) +{ + assert(pReader); + assert(pos >= 0); + + long long total, available; + + int status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + + len = 1; + + if (pos >= available) + return pos; //too few bytes available + + unsigned char b; + + status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + assert(status == 0); + + if (b == 0) //we can't handle u-int values larger than 8 bytes + return E_FILE_FORMAT_INVALID; + + unsigned char m = 0x80; + + while (!(b & m)) + { + m >>= 1; + ++len; + } + + return 0; //success +} + + +long long mkvparser::UnserializeUInt( + IMkvReader* pReader, + long long pos, + long long size) +{ + assert(pReader); + assert(pos >= 0); + + if ((size <= 0) || (size > 8)) + return E_FILE_FORMAT_INVALID; + + long long result = 0; + + for (long long i = 0; i < size; ++i) + { + unsigned char b; + + const long status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + result <<= 8; + result |= b; + + ++pos; + } + + return result; +} + + +long mkvparser::UnserializeFloat( + IMkvReader* pReader, + long long pos, + long long size_, + double& result) +{ + assert(pReader); + assert(pos >= 0); + + if ((size_ != 4) && (size_ != 8)) + return E_FILE_FORMAT_INVALID; + + const long size = static_cast(size_); + + unsigned char buf[8]; + + const int status = pReader->Read(pos, size, buf); + + if (status < 0) //error + return status; + + if (size == 4) + { + union + { + float f; + unsigned long ff; + }; + + ff = 0; + + for (int i = 0;;) + { + ff |= buf[i]; + + if (++i >= 4) + break; + + ff <<= 8; + } + + result = f; + } + else + { + assert(size == 8); + + union + { + double d; + unsigned long long dd; + }; + + dd = 0; + + for (int i = 0;;) + { + dd |= buf[i]; + + if (++i >= 8) + break; + + dd <<= 8; + } + + result = d; + } + + return 0; +} + + +long mkvparser::UnserializeInt( + IMkvReader* pReader, + long long pos, + long size, + long long& result) +{ + assert(pReader); + assert(pos >= 0); + assert(size > 0); + assert(size <= 8); + + { + signed char b; + + const long status = pReader->Read(pos, 1, (unsigned char*)&b); + + if (status < 0) + return status; + + result = b; + + ++pos; + } + + for (long i = 1; i < size; ++i) + { + unsigned char b; + + const long status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + result <<= 8; + result |= b; + + ++pos; + } + + return 0; //success +} + + +long mkvparser::UnserializeString( + IMkvReader* pReader, + long long pos, + long long size_, + char*& str) +{ + delete[] str; + str = NULL; + + if (size_ >= LONG_MAX) //we need (size+1) chars + return E_FILE_FORMAT_INVALID; + + const long size = static_cast(size_); + + str = new (std::nothrow) char[size+1]; + + if (str == NULL) + return -1; + + unsigned char* const buf = reinterpret_cast(str); + + const long status = pReader->Read(pos, size, buf); + + if (status) + { + delete[] str; + str = NULL; + + return status; + } + + str[size] = '\0'; + + return 0; //success +} + + +long mkvparser::ParseElementHeader( + IMkvReader* pReader, + long long& pos, + long long stop, + long long& id, + long long& size) +{ + if ((stop >= 0) && (pos >= stop)) + return E_FILE_FORMAT_INVALID; + + long len; + + id = ReadUInt(pReader, pos, len); + + if (id < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume id + + if ((stop >= 0) && (pos >= stop)) + return E_FILE_FORMAT_INVALID; + + size = ReadUInt(pReader, pos, len); + + if (size < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume length of size + + //pos now designates payload + + if ((stop >= 0) && ((pos + size) > stop)) + return E_FILE_FORMAT_INVALID; + + return 0; //success +} + + +bool mkvparser::Match( + IMkvReader* pReader, + long long& pos, + unsigned long id_, + long long& val) +{ + assert(pReader); + assert(pos >= 0); + + long long total, available; + + const long status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + if (status < 0) + return false; + + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + if ((unsigned long)id != id_) + return false; + + pos += len; //consume id + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert(size <= 8); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + pos += len; //consume length of size of payload + + val = UnserializeUInt(pReader, pos, size); + assert(val >= 0); + + pos += size; //consume size of payload + + return true; +} + +bool mkvparser::Match( + IMkvReader* pReader, + long long& pos, + unsigned long id_, + unsigned char*& buf, + size_t& buflen) +{ + assert(pReader); + assert(pos >= 0); + + long long total, available; + + long status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + if (status < 0) + return false; + + long len; + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + if ((unsigned long)id != id_) + return false; + + pos += len; //consume id + + const long long size_ = ReadUInt(pReader, pos, len); + assert(size_ >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + pos += len; //consume length of size of payload + assert((pos + size_) <= available); + + const long buflen_ = static_cast(size_); + + buf = new (std::nothrow) unsigned char[buflen_]; + assert(buf); //TODO + + status = pReader->Read(pos, buflen_, buf); + assert(status == 0); //TODO + + buflen = buflen_; + + pos += size_; //consume size of payload + return true; +} + + +namespace mkvparser +{ + +EBMLHeader::EBMLHeader() : + m_docType(NULL) +{ + Init(); +} + +EBMLHeader::~EBMLHeader() +{ + delete[] m_docType; +} + +void EBMLHeader::Init() +{ + m_version = 1; + m_readVersion = 1; + m_maxIdLength = 4; + m_maxSizeLength = 8; + + if (m_docType) + { + delete[] m_docType; + m_docType = NULL; + } + + m_docTypeVersion = 1; + m_docTypeReadVersion = 1; +} + +long long EBMLHeader::Parse( + IMkvReader* pReader, + long long& pos) +{ + assert(pReader); + + long long total, available; + + long status = pReader->Length(&total, &available); + + if (status < 0) //error + return status; + + pos = 0; + long long end = (available >= 1024) ? 1024 : available; + + for (;;) + { + unsigned char b = 0; + + while (pos < end) + { + status = pReader->Read(pos, 1, &b); + + if (status < 0) //error + return status; + + if (b == 0x1A) + break; + + ++pos; + } + + if (b != 0x1A) + { + if (pos >= 1024) + return E_FILE_FORMAT_INVALID; //don't bother looking anymore + + if ((total >= 0) && ((total - available) < 5)) + return E_FILE_FORMAT_INVALID; + + return available + 5; //5 = 4-byte ID + 1st byte of size + } + + if ((total >= 0) && ((total - pos) < 5)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < 5) + return pos + 5; //try again later + + long len; + + const long long result = ReadUInt(pReader, pos, len); + + if (result < 0) //error + return result; + + if (result == 0x0A45DFA3) //EBML Header ID + { + pos += len; //consume ID + break; + } + + ++pos; //throw away just the 0x1A byte, and try again + } + + //pos designates start of size field + + //get length of size field + + long len; + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return result; + + if (result > 0) //need more data + return result; + + assert(len > 0); + assert(len <= 8); + + if ((total >= 0) && ((total - pos) < len)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < len) + return pos + len; //try again later + + //get the EBML header size + + result = ReadUInt(pReader, pos, len); + + if (result < 0) //error + return result; + + pos += len; //consume size field + + //pos now designates start of payload + + if ((total >= 0) && ((total - pos) < result)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < result) + return pos + result; + + end = pos + result; + + Init(); + + while (pos < end) + { + long long id, size; + + status = ParseElementHeader( + pReader, + pos, + end, + id, + size); + + if (status < 0) //error + return status; + + if (size == 0) //weird + return E_FILE_FORMAT_INVALID; + + if (id == 0x0286) //version + { + m_version = UnserializeUInt(pReader, pos, size); + + if (m_version <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x02F7) //read version + { + m_readVersion = UnserializeUInt(pReader, pos, size); + + if (m_readVersion <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x02F2) //max id length + { + m_maxIdLength = UnserializeUInt(pReader, pos, size); + + if (m_maxIdLength <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x02F3) //max size length + { + m_maxSizeLength = UnserializeUInt(pReader, pos, size); + + if (m_maxSizeLength <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0282) //doctype + { + if (m_docType) + return E_FILE_FORMAT_INVALID; + + status = UnserializeString(pReader, pos, size, m_docType); + + if (status) //error + return status; + } + else if (id == 0x0287) //doctype version + { + m_docTypeVersion = UnserializeUInt(pReader, pos, size); + + if (m_docTypeVersion <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0285) //doctype read version + { + m_docTypeReadVersion = UnserializeUInt(pReader, pos, size); + + if (m_docTypeReadVersion <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; + } + + assert(pos == end); + return 0; +} + + +Segment::Segment( + IMkvReader* pReader, + long long elem_start, + //long long elem_size, + long long start, + long long size) : + m_pReader(pReader), + m_element_start(elem_start), + //m_element_size(elem_size), + m_start(start), + m_size(size), + m_pos(start), + m_pUnknownSize(0), + m_pSeekHead(NULL), + m_pInfo(NULL), + m_pTracks(NULL), + m_pCues(NULL), + m_pChapters(NULL), + m_clusters(NULL), + m_clusterCount(0), + m_clusterPreloadCount(0), + m_clusterSize(0) +{ +} + + +Segment::~Segment() +{ + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** i = m_clusters; + Cluster** j = m_clusters + count; + + while (i != j) + { + Cluster* const p = *i++; + assert(p); + + delete p; + } + + delete[] m_clusters; + + delete m_pTracks; + delete m_pInfo; + delete m_pCues; + delete m_pChapters; + delete m_pSeekHead; +} + + +long long Segment::CreateInstance( + IMkvReader* pReader, + long long pos, + Segment*& pSegment) +{ + assert(pReader); + assert(pos >= 0); + + pSegment = NULL; + + long long total, available; + + const long status = pReader->Length(&total, &available); + + if (status < 0) //error + return status; + + if (available < 0) + return -1; + + if ((total >= 0) && (available > total)) + return -1; + + //I would assume that in practice this loop would execute + //exactly once, but we allow for other elements (e.g. Void) + //to immediately follow the EBML header. This is fine for + //the source filter case (since the entire file is available), + //but in the splitter case over a network we should probably + //just give up early. We could for example decide only to + //execute this loop a maximum of, say, 10 times. + //TODO: + //There is an implied "give up early" by only parsing up + //to the available limit. We do do that, but only if the + //total file size is unknown. We could decide to always + //use what's available as our limit (irrespective of whether + //we happen to know the total file length). This would have + //as its sense "parse this much of the file before giving up", + //which a slightly different sense from "try to parse up to + //10 EMBL elements before giving up". + + for (;;) + { + if ((total >= 0) && (pos >= total)) + return E_FILE_FORMAT_INVALID; + + //Read ID + long len; + long long result = GetUIntLength(pReader, pos, len); + + if (result) //error, or too few available bytes + return result; + + if ((total >= 0) && ((pos + len) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long idpos = pos; + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return id; + + pos += len; //consume ID + + //Read Size + + result = GetUIntLength(pReader, pos, len); + + if (result) //error, or too few available bytes + return result; + + if ((total >= 0) && ((pos + len) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return size; + + pos += len; //consume length of size of element + + //Pos now points to start of payload + + //Handle "unknown size" for live streaming of webm files. + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (id == 0x08538067) //Segment ID + { + if (size == unknown_size) + size = -1; + + else if (total < 0) + size = -1; + + else if ((pos + size) > total) + size = -1; + + pSegment = new (std::nothrow) Segment( + pReader, + idpos, + //elem_size + pos, + size); + + if (pSegment == 0) + return -1; //generic error + + return 0; //success + } + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + size) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + size) > available) + return pos + size; + + pos += size; //consume payload + } +} + + +long long Segment::ParseHeaders() +{ + //Outermost (level 0) segment object has been constructed, + //and pos designates start of payload. We need to find the + //inner (level 1) elements. + long long total, available; + + const int status = m_pReader->Length(&total, &available); + + if (status < 0) //error + return status; + + assert((total < 0) || (available <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + assert((segment_stop < 0) || (total < 0) || (segment_stop <= total)); + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + for (;;) + { + if ((total >= 0) && (m_pos >= total)) + break; + + if ((segment_stop >= 0) && (m_pos >= segment_stop)) + break; + + long long pos = m_pos; + const long long element_start = pos; + + if ((pos + 1) > available) + return (pos + 1); + + long len; + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return result; + + if (result > 0) //underflow (weird) + return (pos + 1); + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error + return id; + + if (id == 0x0F43B675) //Cluster ID + break; + + pos += len; //consume ID + + if ((pos + 1) > available) + return (pos + 1); + + //Read Size + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return result; + + if (result > 0) //underflow (weird) + return (pos + 1); + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return size; + + pos += len; //consume length of size of element + + const long long element_size = size + pos - element_start; + + //Pos now points to start of payload + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + //We read EBML elements either in total or nothing at all. + + if ((pos + size) > available) + return pos + size; + + if (id == 0x0549A966) //Segment Info ID + { + if (m_pInfo) + return E_FILE_FORMAT_INVALID; + + m_pInfo = new (std::nothrow) SegmentInfo( + this, + pos, + size, + element_start, + element_size); + + if (m_pInfo == NULL) + return -1; + + const long status = m_pInfo->Parse(); + + if (status) + return status; + } + else if (id == 0x0654AE6B) //Tracks ID + { + if (m_pTracks) + return E_FILE_FORMAT_INVALID; + + m_pTracks = new (std::nothrow) Tracks(this, + pos, + size, + element_start, + element_size); + + if (m_pTracks == NULL) + return -1; + + const long status = m_pTracks->Parse(); + + if (status) + return status; + } + else if (id == 0x0C53BB6B) //Cues ID + { + if (m_pCues == NULL) + { + m_pCues = new (std::nothrow) Cues( + this, + pos, + size, + element_start, + element_size); + + if (m_pCues == NULL) + return -1; + } + } + else if (id == 0x014D9B74) //SeekHead ID + { + if (m_pSeekHead == NULL) + { + m_pSeekHead = new (std::nothrow) SeekHead( + this, + pos, + size, + element_start, + element_size); + + if (m_pSeekHead == NULL) + return -1; + + const long status = m_pSeekHead->Parse(); + + if (status) + return status; + } + } + else if (id == 0x0043A770) //Chapters ID + { + if (m_pChapters == NULL) + { + m_pChapters = new (std::nothrow) Chapters( + this, + pos, + size, + element_start, + element_size); + + if (m_pChapters == NULL) + return -1; + + const long status = m_pChapters->Parse(); + + if (status) + return status; + } + } + + m_pos = pos + size; //consume payload + } + + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + if (m_pInfo == NULL) //TODO: liberalize this behavior + return E_FILE_FORMAT_INVALID; + + if (m_pTracks == NULL) + return E_FILE_FORMAT_INVALID; + + return 0; //success +} + + +long Segment::LoadCluster( + long long& pos, + long& len) +{ + for (;;) + { + const long result = DoLoadCluster(pos, len); + + if (result <= 1) + return result; + } +} + + +long Segment::DoLoadCluster( + long long& pos, + long& len) +{ + if (m_pos < 0) + return DoLoadClusterUnknownSize(pos, len); + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + long long cluster_off = -1; //offset relative to start of segment + long long cluster_size = -1; //size of cluster payload + + for (;;) + { + if ((total >= 0) && (m_pos >= total)) + return 1; //no more clusters + + if ((segment_stop >= 0) && (m_pos >= segment_stop)) + return 1; //no more clusters + + pos = m_pos; + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + pos += len; //consume ID + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume length of size of element + + //pos now points to start of payload + + if (size == 0) //weird + { + m_pos = pos; + continue; + } + + const long long unknown_size = (1LL << (7 * len)) - 1; + +#if 0 //we must handle this to support live webm + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //TODO: allow this +#endif + + if ((segment_stop >= 0) && + (size != unknown_size) && + ((pos + size) > segment_stop)) + { + return E_FILE_FORMAT_INVALID; + } + +#if 0 //commented-out, to support incremental cluster parsing + len = static_cast(size); + + if ((pos + size) > avail) + return E_BUFFER_NOT_FULL; +#endif + + if (id == 0x0C53BB6B) //Cues ID + { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //TODO: liberalize + + if (m_pCues == NULL) + { + const long long element_size = (pos - idpos) + size; + + m_pCues = new Cues(this, + pos, + size, + idpos, + element_size); + assert(m_pCues); //TODO + } + + m_pos = pos + size; //consume payload + continue; + } + + if (id != 0x0F43B675) //Cluster ID + { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //TODO: liberalize + + m_pos = pos + size; //consume payload + continue; + } + + //We have a cluster. + + cluster_off = idpos - m_start; //relative pos + + if (size != unknown_size) + cluster_size = size; + + break; + } + + assert(cluster_off >= 0); //have cluster + + long long pos_; + long len_; + + status = Cluster::HasBlockEntries(this, cluster_off, pos_, len_); + + if (status < 0) //error, or underflow + { + pos = pos_; + len = len_; + + return status; + } + + //status == 0 means "no block entries found" + //status > 0 means "found at least one block entry" + + //TODO: + //The issue here is that the segment increments its own + //pos ptr past the most recent cluster parsed, and then + //starts from there to parse the next cluster. If we + //don't know the size of the current cluster, then we + //must either parse its payload (as we do below), looking + //for the cluster (or cues) ID to terminate the parse. + //This isn't really what we want: rather, we really need + //a way to create the curr cluster object immediately. + //The pity is that cluster::parse can determine its own + //boundary, and we largely duplicate that same logic here. + // + //Maybe we need to get rid of our look-ahead preloading + //in source::parse??? + // + //As we're parsing the blocks in the curr cluster + //(in cluster::parse), we should have some way to signal + //to the segment that we have determined the boundary, + //so it can adjust its own segment::m_pos member. + // + //The problem is that we're asserting in asyncreadinit, + //because we adjust the pos down to the curr seek pos, + //and the resulting adjusted len is > 2GB. I'm suspicious + //that this is even correct, but even if it is, we can't + //be loading that much data in the cache anyway. + + const long idx = m_clusterCount; + + if (m_clusterPreloadCount > 0) + { + assert(idx < m_clusterSize); + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + assert(pCluster->m_index < 0); + + const long long off = pCluster->GetPosition(); + assert(off >= 0); + + if (off == cluster_off) //preloaded already + { + if (status == 0) //no entries found + return E_FILE_FORMAT_INVALID; + + if (cluster_size >= 0) + pos += cluster_size; + else + { + const long long element_size = pCluster->GetElementSize(); + + if (element_size <= 0) + return E_FILE_FORMAT_INVALID; //TODO: handle this case + + pos = pCluster->m_element_start + element_size; + } + + pCluster->m_index = idx; //move from preloaded to loaded + ++m_clusterCount; + --m_clusterPreloadCount; + + m_pos = pos; //consume payload + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 0; //success + } + } + + if (status == 0) //no entries found + { + if (cluster_size < 0) + return E_FILE_FORMAT_INVALID; //TODO: handle this + + pos += cluster_size; + + if ((total >= 0) && (pos >= total)) + { + m_pos = total; + return 1; //no more clusters + } + + if ((segment_stop >= 0) && (pos >= segment_stop)) + { + m_pos = segment_stop; + return 1; //no more clusters + } + + m_pos = pos; + return 2; //try again + } + + //status > 0 means we have an entry + + Cluster* const pCluster = Cluster::Create(this, + idx, + cluster_off); + //element_size); + assert(pCluster); + + AppendCluster(pCluster); + assert(m_clusters); + assert(idx < m_clusterSize); + assert(m_clusters[idx] == pCluster); + + if (cluster_size >= 0) + { + pos += cluster_size; + + m_pos = pos; + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 0; + } + + m_pUnknownSize = pCluster; + m_pos = -pos; + + return 0; //partial success, since we have a new cluster + + //status == 0 means "no block entries found" + + //pos designates start of payload + //m_pos has NOT been adjusted yet (in case we need to come back here) + +#if 0 + + if (cluster_size < 0) //unknown size + { + const long long payload_pos = pos; //absolute pos of cluster payload + + for (;;) //determine cluster size + { + if ((total >= 0) && (pos >= total)) + break; + + if ((segment_stop >= 0) && (pos >= segment_stop)) + break; //no more clusters + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + break; + + if (id == 0x0C53BB6B) //Cues ID + break; + + switch (id) + { + case 0x20: //BlockGroup + case 0x23: //Simple Block + case 0x67: //TimeCode + case 0x2B: //PrevSize + break; + + default: + assert(false); + break; + } + + pos += len; //consume ID (of sub-element) + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume size field of element + + //pos now points to start of sub-element's payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } //determine cluster size + + cluster_size = pos - payload_pos; + assert(cluster_size >= 0); + + pos = payload_pos; //reset and re-parse original cluster + } + + if (m_clusterPreloadCount > 0) + { + assert(idx < m_clusterSize); + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + assert(pCluster->m_index < 0); + + const long long off = pCluster->GetPosition(); + assert(off >= 0); + + if (off == cluster_off) //preloaded already + return E_FILE_FORMAT_INVALID; //subtle + } + + m_pos = pos + cluster_size; //consume payload + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 2; //try to find another cluster + +#endif + +} + + +long Segment::DoLoadClusterUnknownSize( + long long& pos, + long& len) +{ + assert(m_pos < 0); + assert(m_pUnknownSize); + +#if 0 + assert(m_pUnknownSize->GetElementSize() < 0); //TODO: verify this + + const long long element_start = m_pUnknownSize->m_element_start; + + pos = -m_pos; + assert(pos > element_start); + + //We have already consumed the (cluster) ID and size fields. + //We just need to consume the blocks and other sub-elements + //of this cluster, until we discover the boundary. + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + long long element_size = -1; + + for (;;) //determine cluster size + { + if ((total >= 0) && (pos >= total)) + { + element_size = total - element_start; + assert(element_size > 0); + + break; + } + + if ((segment_stop >= 0) && (pos >= segment_stop)) + { + element_size = segment_stop - element_start; + assert(element_size > 0); + + break; + } + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) //Cluster ID or Cues ID + { + element_size = pos - element_start; + assert(element_size > 0); + + break; + } + +#ifdef _DEBUG + switch (id) + { + case 0x20: //BlockGroup + case 0x23: //Simple Block + case 0x67: //TimeCode + case 0x2B: //PrevSize + break; + + default: + assert(false); + break; + } +#endif + + pos += len; //consume ID (of sub-element) + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume size field of element + + //pos now points to start of sub-element's payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } //determine cluster size + + assert(element_size >= 0); + + m_pos = element_start + element_size; + m_pUnknownSize = 0; + + return 2; //continue parsing +#else + const long status = m_pUnknownSize->Parse(pos, len); + + if (status < 0) //error or underflow + return status; + + if (status == 0) //parsed a block + return 2; //continue parsing + + assert(status > 0); //nothing left to parse of this cluster + + const long long start = m_pUnknownSize->m_element_start; + + const long long size = m_pUnknownSize->GetElementSize(); + assert(size >= 0); + + pos = start + size; + m_pos = pos; + + m_pUnknownSize = 0; + + return 2; //continue parsing +#endif +} + + +void Segment::AppendCluster(Cluster* pCluster) +{ + assert(pCluster); + assert(pCluster->m_index >= 0); + + const long count = m_clusterCount + m_clusterPreloadCount; + + long& size = m_clusterSize; + assert(size >= count); + + const long idx = pCluster->m_index; + assert(idx == m_clusterCount); + + if (count >= size) + { + const long n = (size <= 0) ? 2048 : 2*size; + + Cluster** const qq = new Cluster*[n]; + Cluster** q = qq; + + Cluster** p = m_clusters; + Cluster** const pp = p + count; + + while (p != pp) + *q++ = *p++; + + delete[] m_clusters; + + m_clusters = qq; + size = n; + } + + if (m_clusterPreloadCount > 0) + { + assert(m_clusters); + + Cluster** const p = m_clusters + m_clusterCount; + assert(*p); + assert((*p)->m_index < 0); + + Cluster** q = p + m_clusterPreloadCount; + assert(q < (m_clusters + size)); + + for (;;) + { + Cluster** const qq = q - 1; + assert((*qq)->m_index < 0); + + *q = *qq; + q = qq; + + if (q == p) + break; + } + } + + m_clusters[idx] = pCluster; + ++m_clusterCount; +} + + +void Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx) +{ + assert(pCluster); + assert(pCluster->m_index < 0); + assert(idx >= m_clusterCount); + + const long count = m_clusterCount + m_clusterPreloadCount; + + long& size = m_clusterSize; + assert(size >= count); + + if (count >= size) + { + const long n = (size <= 0) ? 2048 : 2*size; + + Cluster** const qq = new Cluster*[n]; + Cluster** q = qq; + + Cluster** p = m_clusters; + Cluster** const pp = p + count; + + while (p != pp) + *q++ = *p++; + + delete[] m_clusters; + + m_clusters = qq; + size = n; + } + + assert(m_clusters); + + Cluster** const p = m_clusters + idx; + + Cluster** q = m_clusters + count; + assert(q >= p); + assert(q < (m_clusters + size)); + + while (q > p) + { + Cluster** const qq = q - 1; + assert((*qq)->m_index < 0); + + *q = *qq; + q = qq; + } + + m_clusters[idx] = pCluster; + ++m_clusterPreloadCount; +} + + +long Segment::Load() +{ + assert(m_clusters == NULL); + assert(m_clusterSize == 0); + assert(m_clusterCount == 0); + //assert(m_size >= 0); + + //Outermost (level 0) segment object has been constructed, + //and pos designates start of payload. We need to find the + //inner (level 1) elements. + + const long long header_status = ParseHeaders(); + + if (header_status < 0) //error + return static_cast(header_status); + + if (header_status > 0) //underflow + return E_BUFFER_NOT_FULL; + + assert(m_pInfo); + assert(m_pTracks); + + for (;;) + { + const int status = LoadCluster(); + + if (status < 0) //error + return status; + + if (status >= 1) //no more clusters + return 0; + } +} + + +SeekHead::SeekHead( + Segment* pSegment, + long long start, + long long size_, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_entries(0), + m_entry_count(0), + m_void_elements(0), + m_void_element_count(0) +{ +} + + +SeekHead::~SeekHead() +{ + delete[] m_entries; + delete[] m_void_elements; +} + + +long SeekHead::Parse() +{ + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; + const long long stop = m_start + m_size; + + //first count the seek head entries + + int entry_count = 0; + int void_element_count = 0; + + while (pos < stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x0DBB) //SeekEntry ID + ++entry_count; + else if (id == 0x6C) //Void ID + ++void_element_count; + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + m_entries = new (std::nothrow) Entry[entry_count]; + + if (m_entries == NULL) + return -1; + + m_void_elements = new (std::nothrow) VoidElement[void_element_count]; + + if (m_void_elements == NULL) + return -1; + + //now parse the entries and void elements + + Entry* pEntry = m_entries; + VoidElement* pVoidElement = m_void_elements; + + pos = m_start; + + while (pos < stop) + { + const long long idpos = pos; + + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x0DBB) //SeekEntry ID + { + if (ParseEntry(pReader, pos, size, pEntry)) + { + Entry& e = *pEntry++; + + e.element_start = idpos; + e.element_size = (pos + size) - idpos; + } + } + else if (id == 0x6C) //Void ID + { + VoidElement& e = *pVoidElement++; + + e.element_start = idpos; + e.element_size = (pos + size) - idpos; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + ptrdiff_t count_ = ptrdiff_t(pEntry - m_entries); + assert(count_ >= 0); + assert(count_ <= entry_count); + + m_entry_count = static_cast(count_); + + count_ = ptrdiff_t(pVoidElement - m_void_elements); + assert(count_ >= 0); + assert(count_ <= void_element_count); + + m_void_element_count = static_cast(count_); + + return 0; +} + + +int SeekHead::GetCount() const +{ + return m_entry_count; +} + +const SeekHead::Entry* SeekHead::GetEntry(int idx) const +{ + if (idx < 0) + return 0; + + if (idx >= m_entry_count) + return 0; + + return m_entries + idx; +} + +int SeekHead::GetVoidElementCount() const +{ + return m_void_element_count; +} + +const SeekHead::VoidElement* SeekHead::GetVoidElement(int idx) const +{ + if (idx < 0) + return 0; + + if (idx >= m_void_element_count) + return 0; + + return m_void_elements + idx; +} + + +#if 0 +void Segment::ParseCues(long long off) +{ + if (m_pCues) + return; + + //odbgstream os; + //os << "Segment::ParseCues (begin)" << endl; + + long long pos = m_start + off; + const long long element_start = pos; + const long long stop = m_start + m_size; + + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); + + const long long idpos = pos; + + const long long id = ReadUInt(m_pReader, idpos, len); + assert(id == 0x0C53BB6B); //Cues ID + + pos += len; //consume ID + assert(pos < stop); + + //Read Size + + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size >= 0); + + pos += len; //consume length of size of element + assert((pos + size) <= stop); + + const long long element_size = size + pos - element_start; + + //Pos now points to start of payload + + m_pCues = new Cues(this, pos, size, element_start, element_size); + assert(m_pCues); //TODO + + //os << "Segment::ParseCues (end)" << endl; +} +#else +long Segment::ParseCues( + long long off, + long long& pos, + long& len) +{ + if (m_pCues) + return 0; //success + + if (off < 0) + return -1; + + long long total, avail; + + const int status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + pos = m_start + off; + + if ((total < 0) || (pos >= total)) + return 1; //don't bother parsing cues + + const long long element_start = pos; + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //underflow (weird) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id != 0x0C53BB6B) //Cues ID + return E_FILE_FORMAT_INVALID; + + pos += len; //consume ID + assert((segment_stop < 0) || (pos <= segment_stop)); + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //underflow (weird) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + if (size == 0) //weird, although technically not illegal + return 1; //done + + pos += len; //consume length of size of element + assert((segment_stop < 0) || (pos <= segment_stop)); + + //Pos now points to start of payload + + const long long element_stop = pos + size; + + if ((segment_stop >= 0) && (element_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && (element_stop > total)) + return 1; //don't bother parsing anymore + + len = static_cast(size); + + if (element_stop > avail) + return E_BUFFER_NOT_FULL; + + const long long element_size = element_stop - element_start; + + m_pCues = new (std::nothrow) Cues( + this, + pos, + size, + element_start, + element_size); + assert(m_pCues); //TODO + + return 0; //success +} +#endif + + +#if 0 +void Segment::ParseSeekEntry( + long long start, + long long size_) +{ + long long pos = start; + + const long long stop = start + size_; + + long len; + + const long long seekIdId = ReadUInt(m_pReader, pos, len); + //seekIdId; + assert(seekIdId == 0x13AB); //SeekID ID + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long seekIdSize = ReadUInt(m_pReader, pos, len); + assert(seekIdSize >= 0); + assert((pos + len) <= stop); + + pos += len; //consume size + + const long long seekId = ReadUInt(m_pReader, pos, len); //payload + assert(seekId >= 0); + assert(len == seekIdSize); + assert((pos + len) <= stop); + + pos += seekIdSize; //consume payload + + const long long seekPosId = ReadUInt(m_pReader, pos, len); + //seekPosId; + assert(seekPosId == 0x13AC); //SeekPos ID + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long seekPosSize = ReadUInt(m_pReader, pos, len); + assert(seekPosSize >= 0); + assert((pos + len) <= stop); + + pos += len; //consume size + assert((pos + seekPosSize) <= stop); + + const long long seekOff = UnserializeUInt(m_pReader, pos, seekPosSize); + assert(seekOff >= 0); + assert(seekOff < m_size); + + pos += seekPosSize; //consume payload + assert(pos == stop); + + const long long seekPos = m_start + seekOff; + assert(seekPos < (m_start + m_size)); + + if (seekId == 0x0C53BB6B) //Cues ID + ParseCues(seekOff); +} +#else +bool SeekHead::ParseEntry( + IMkvReader* pReader, + long long start, + long long size_, + Entry* pEntry) +{ + if (size_ <= 0) + return false; + + long long pos = start; + const long long stop = start + size_; + + long len; + + //parse the container for the level-1 element ID + + const long long seekIdId = ReadUInt(pReader, pos, len); + //seekIdId; + + if (seekIdId != 0x13AB) //SeekID ID + return false; + + if ((pos + len) > stop) + return false; + + pos += len; //consume SeekID id + + const long long seekIdSize = ReadUInt(pReader, pos, len); + + if (seekIdSize <= 0) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; //consume size of field + + if ((pos + seekIdSize) > stop) + return false; + + //Note that the SeekId payload really is serialized + //as a "Matroska integer", not as a plain binary value. + //In fact, Matroska requires that ID values in the + //stream exactly match the binary representation as listed + //in the Matroska specification. + // + //This parser is more liberal, and permits IDs to have + //any width. (This could make the representation in the stream + //different from what's in the spec, but it doesn't matter here, + //since we always normalize "Matroska integer" values.) + + pEntry->id = ReadUInt(pReader, pos, len); //payload + + if (pEntry->id <= 0) + return false; + + if (len != seekIdSize) + return false; + + pos += seekIdSize; //consume SeekID payload + + const long long seekPosId = ReadUInt(pReader, pos, len); + + if (seekPosId != 0x13AC) //SeekPos ID + return false; + + if ((pos + len) > stop) + return false; + + pos += len; //consume id + + const long long seekPosSize = ReadUInt(pReader, pos, len); + + if (seekPosSize <= 0) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; //consume size + + if ((pos + seekPosSize) > stop) + return false; + + pEntry->pos = UnserializeUInt(pReader, pos, seekPosSize); + + if (pEntry->pos < 0) + return false; + + pos += seekPosSize; //consume payload + + if (pos != stop) + return false; + + return true; +} +#endif + + +Cues::Cues( + Segment* pSegment, + long long start_, + long long size_, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(start_), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_cue_points(NULL), + m_count(0), + m_preload_count(0), + m_pos(start_) +{ +} + + +Cues::~Cues() +{ + const long n = m_count + m_preload_count; + + CuePoint** p = m_cue_points; + CuePoint** const q = p + n; + + while (p != q) + { + CuePoint* const pCP = *p++; + assert(pCP); + + delete pCP; + } + + delete[] m_cue_points; +} + + +long Cues::GetCount() const +{ + if (m_cue_points == NULL) + return -1; + + return m_count; //TODO: really ignore preload count? +} + + +bool Cues::DoneParsing() const +{ + const long long stop = m_start + m_size; + return (m_pos >= stop); +} + + +void Cues::Init() const +{ + if (m_cue_points) + return; + + assert(m_count == 0); + assert(m_preload_count == 0); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + const long long stop = m_start + m_size; + long long pos = m_start; + + long cue_points_size = 0; + + while (pos < stop) + { + const long long idpos = pos; + + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; //consume Size field + assert((pos + size) <= stop); + + if (id == 0x3B) //CuePoint ID + PreloadCuePoint(cue_points_size, idpos); + + pos += size; //consume payload + assert(pos <= stop); + } +} + + +void Cues::PreloadCuePoint( + long& cue_points_size, + long long pos) const +{ + assert(m_count == 0); + + if (m_preload_count >= cue_points_size) + { + const long n = (cue_points_size <= 0) ? 2048 : 2*cue_points_size; + + CuePoint** const qq = new CuePoint*[n]; + CuePoint** q = qq; //beginning of target + + CuePoint** p = m_cue_points; //beginning of source + CuePoint** const pp = p + m_preload_count; //end of source + + while (p != pp) + *q++ = *p++; + + delete[] m_cue_points; + + m_cue_points = qq; + cue_points_size = n; + } + + CuePoint* const pCP = new CuePoint(m_preload_count, pos); + m_cue_points[m_preload_count++] = pCP; +} + + +bool Cues::LoadCuePoint() const +{ + //odbgstream os; + //os << "Cues::LoadCuePoint" << endl; + + const long long stop = m_start + m_size; + + if (m_pos >= stop) + return false; //nothing else to do + + Init(); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + while (m_pos < stop) + { + const long long idpos = m_pos; + + long len; + + const long long id = ReadUInt(pReader, m_pos, len); + assert(id >= 0); //TODO + assert((m_pos + len) <= stop); + + m_pos += len; //consume ID + + const long long size = ReadUInt(pReader, m_pos, len); + assert(size >= 0); + assert((m_pos + len) <= stop); + + m_pos += len; //consume Size field + assert((m_pos + size) <= stop); + + if (id != 0x3B) //CuePoint ID + { + m_pos += size; //consume payload + assert(m_pos <= stop); + + continue; + } + + assert(m_preload_count > 0); + + CuePoint* const pCP = m_cue_points[m_count]; + assert(pCP); + assert((pCP->GetTimeCode() >= 0) || (-pCP->GetTimeCode() == idpos)); + if (pCP->GetTimeCode() < 0 && (-pCP->GetTimeCode() != idpos)) + return false; + + pCP->Load(pReader); + ++m_count; + --m_preload_count; + + m_pos += size; //consume payload + assert(m_pos <= stop); + + return true; //yes, we loaded a cue point + } + + //return (m_pos < stop); + return false; //no, we did not load a cue point +} + + +bool Cues::Find( + long long time_ns, + const Track* pTrack, + const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) const +{ + assert(time_ns >= 0); + assert(pTrack); + +#if 0 + LoadCuePoint(); //establish invariant + + assert(m_cue_points); + assert(m_count > 0); + + CuePoint** const ii = m_cue_points; + CuePoint** i = ii; + + CuePoint** const jj = ii + m_count + m_preload_count; + CuePoint** j = jj; + + pCP = *i; + assert(pCP); + + if (time_ns <= pCP->GetTime(m_pSegment)) + { + pTP = pCP->Find(pTrack); + return (pTP != NULL); + } + + IMkvReader* const pReader = m_pSegment->m_pReader; + + while (i < j) + { + //INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + CuePoint** const k = i + (j - i) / 2; + assert(k < jj); + + CuePoint* const pCP = *k; + assert(pCP); + + pCP->Load(pReader); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + assert(i > ii); + + pCP = *--i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) <= time_ns); +#else + if (m_cue_points == NULL) + return false; + + if (m_count == 0) + return false; + + CuePoint** const ii = m_cue_points; + CuePoint** i = ii; + + CuePoint** const jj = ii + m_count; + CuePoint** j = jj; + + pCP = *i; + assert(pCP); + + if (time_ns <= pCP->GetTime(m_pSegment)) + { + pTP = pCP->Find(pTrack); + return (pTP != NULL); + } + + while (i < j) + { + //INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + CuePoint** const k = i + (j - i) / 2; + assert(k < jj); + + CuePoint* const pCP = *k; + assert(pCP); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + assert(i > ii); + + pCP = *--i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) <= time_ns); +#endif + + //TODO: here and elsewhere, it's probably not correct to search + //for the cue point with this time, and then search for a matching + //track. In principle, the matching track could be on some earlier + //cue point, and with our current algorithm, we'd miss it. To make + //this bullet-proof, we'd need to create a secondary structure, + //with a list of cue points that apply to a track, and then search + //that track-based structure for a matching cue point. + + pTP = pCP->Find(pTrack); + return (pTP != NULL); +} + + +#if 0 +bool Cues::FindNext( + long long time_ns, + const Track* pTrack, + const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) const +{ + pCP = 0; + pTP = 0; + + if (m_count == 0) + return false; + + assert(m_cue_points); + + const CuePoint* const* const ii = m_cue_points; + const CuePoint* const* i = ii; + + const CuePoint* const* const jj = ii + m_count; + const CuePoint* const* j = jj; + + while (i < j) + { + //INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + const CuePoint* const* const k = i + (j - i) / 2; + assert(k < jj); + + pCP = *k; + assert(pCP); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + + if (i >= jj) //time_ns is greater than max cue point + return false; + + pCP = *i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) > time_ns); + + pTP = pCP->Find(pTrack); + return (pTP != NULL); +} +#endif + + +const CuePoint* Cues::GetFirst() const +{ + if (m_cue_points == NULL) + return NULL; + + if (m_count == 0) + return NULL; + +#if 0 + LoadCuePoint(); //init cues + + const size_t count = m_count + m_preload_count; + + if (count == 0) //weird + return NULL; +#endif + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[0]; + assert(pCP); + assert(pCP->GetTimeCode() >= 0); + + return pCP; +} + + +const CuePoint* Cues::GetLast() const +{ + if (m_cue_points == NULL) + return NULL; + + if (m_count <= 0) + return NULL; + +#if 0 + LoadCuePoint(); //init cues + + const size_t count = m_count + m_preload_count; + + if (count == 0) //weird + return NULL; + + const size_t index = count - 1; + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[index]; + assert(pCP); + + pCP->Load(m_pSegment->m_pReader); + assert(pCP->GetTimeCode() >= 0); +#else + const long index = m_count - 1; + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[index]; + assert(pCP); + assert(pCP->GetTimeCode() >= 0); +#endif + + return pCP; +} + + +const CuePoint* Cues::GetNext(const CuePoint* pCurr) const +{ + if (pCurr == NULL) + return NULL; + + assert(pCurr->GetTimeCode() >= 0); + assert(m_cue_points); + assert(m_count >= 1); + +#if 0 + const size_t count = m_count + m_preload_count; + + size_t index = pCurr->m_index; + assert(index < count); + + CuePoint* const* const pp = m_cue_points; + assert(pp); + assert(pp[index] == pCurr); + + ++index; + + if (index >= count) + return NULL; + + CuePoint* const pNext = pp[index]; + assert(pNext); + + pNext->Load(m_pSegment->m_pReader); +#else + long index = pCurr->m_index; + assert(index < m_count); + + CuePoint* const* const pp = m_cue_points; + assert(pp); + assert(pp[index] == pCurr); + + ++index; + + if (index >= m_count) + return NULL; + + CuePoint* const pNext = pp[index]; + assert(pNext); + assert(pNext->GetTimeCode() >= 0); +#endif + + return pNext; +} + + +const BlockEntry* Cues::GetBlock( + const CuePoint* pCP, + const CuePoint::TrackPosition* pTP) const +{ + if (pCP == NULL) + return NULL; + + if (pTP == NULL) + return NULL; + + return m_pSegment->GetBlock(*pCP, *pTP); +} + + +const BlockEntry* Segment::GetBlock( + const CuePoint& cp, + const CuePoint::TrackPosition& tp) +{ + Cluster** const ii = m_clusters; + Cluster** i = ii; + + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** const jj = ii + count; + Cluster** j = jj; + + while (i < j) + { + //INVARIANT: + //[ii, i) < pTP->m_pos + //[i, j) ? + //[j, jj) > pTP->m_pos + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pCluster = *k; + assert(pCluster); + + //const long long pos_ = pCluster->m_pos; + //assert(pos_); + //const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); + + const long long pos = pCluster->GetPosition(); + assert(pos >= 0); + + if (pos < tp.m_pos) + i = k + 1; + else if (pos > tp.m_pos) + j = k; + else + return pCluster->GetEntry(cp, tp); + } + + assert(i == j); + //assert(Cluster::HasBlockEntries(this, tp.m_pos)); + + Cluster* const pCluster = Cluster::Create(this, -1, tp.m_pos); //, -1); + assert(pCluster); + + const ptrdiff_t idx = i - m_clusters; + + PreloadCluster(pCluster, idx); + assert(m_clusters); + assert(m_clusterPreloadCount > 0); + assert(m_clusters[idx] == pCluster); + + return pCluster->GetEntry(cp, tp); +} + + +const Cluster* Segment::FindOrPreloadCluster(long long requested_pos) +{ + if (requested_pos < 0) + return 0; + + Cluster** const ii = m_clusters; + Cluster** i = ii; + + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** const jj = ii + count; + Cluster** j = jj; + + while (i < j) + { + //INVARIANT: + //[ii, i) < pTP->m_pos + //[i, j) ? + //[j, jj) > pTP->m_pos + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pCluster = *k; + assert(pCluster); + + //const long long pos_ = pCluster->m_pos; + //assert(pos_); + //const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); + + const long long pos = pCluster->GetPosition(); + assert(pos >= 0); + + if (pos < requested_pos) + i = k + 1; + else if (pos > requested_pos) + j = k; + else + return pCluster; + } + + assert(i == j); + //assert(Cluster::HasBlockEntries(this, tp.m_pos)); + + Cluster* const pCluster = Cluster::Create( + this, + -1, + requested_pos); + //-1); + assert(pCluster); + + const ptrdiff_t idx = i - m_clusters; + + PreloadCluster(pCluster, idx); + assert(m_clusters); + assert(m_clusterPreloadCount > 0); + assert(m_clusters[idx] == pCluster); + + return pCluster; +} + + +CuePoint::CuePoint(long idx, long long pos) : + m_element_start(0), + m_element_size(0), + m_index(idx), + m_timecode(-1 * pos), + m_track_positions(NULL), + m_track_positions_count(0) +{ + assert(pos > 0); +} + + +CuePoint::~CuePoint() +{ + delete[] m_track_positions; +} + + +void CuePoint::Load(IMkvReader* pReader) +{ + //odbgstream os; + //os << "CuePoint::Load(begin): timecode=" << m_timecode << endl; + + if (m_timecode >= 0) //already loaded + return; + + assert(m_track_positions == NULL); + assert(m_track_positions_count == 0); + + long long pos_ = -m_timecode; + const long long element_start = pos_; + + long long stop; + + { + long len; + + const long long id = ReadUInt(pReader, pos_, len); + assert(id == 0x3B); //CuePoint ID + if (id != 0x3B) + return; + + pos_ += len; //consume ID + + const long long size = ReadUInt(pReader, pos_, len); + assert(size >= 0); + + pos_ += len; //consume Size field + //pos_ now points to start of payload + + stop = pos_ + size; + } + + const long long element_size = stop - element_start; + + long long pos = pos_; + + //First count number of track positions + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; //consume Size field + assert((pos + size) <= stop); + + if (id == 0x33) //CueTime ID + m_timecode = UnserializeUInt(pReader, pos, size); + + else if (id == 0x37) //CueTrackPosition(s) ID + ++m_track_positions_count; + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(m_timecode >= 0); + assert(m_track_positions_count > 0); + + //os << "CuePoint::Load(cont'd): idpos=" << idpos + // << " timecode=" << m_timecode + // << endl; + + m_track_positions = new TrackPosition[m_track_positions_count]; + + //Now parse track positions + + TrackPosition* p = m_track_positions; + pos = pos_; + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; //consume Size field + assert((pos + size) <= stop); + + if (id == 0x37) //CueTrackPosition(s) ID + { + TrackPosition& tp = *p++; + tp.Parse(pReader, pos, size); + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(size_t(p - m_track_positions) == m_track_positions_count); + + m_element_start = element_start; + m_element_size = element_size; +} + + + +void CuePoint::TrackPosition::Parse( + IMkvReader* pReader, + long long start_, + long long size_) +{ + const long long stop = start_ + size_; + long long pos = start_; + + m_track = -1; + m_pos = -1; + m_block = 1; //default + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; //consume Size field + assert((pos + size) <= stop); + + if (id == 0x77) //CueTrack ID + m_track = UnserializeUInt(pReader, pos, size); + + else if (id == 0x71) //CueClusterPos ID + m_pos = UnserializeUInt(pReader, pos, size); + + else if (id == 0x1378) //CueBlockNumber + m_block = UnserializeUInt(pReader, pos, size); + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(m_pos >= 0); + assert(m_track > 0); + //assert(m_block > 0); +} + + +const CuePoint::TrackPosition* CuePoint::Find(const Track* pTrack) const +{ + assert(pTrack); + + const long long n = pTrack->GetNumber(); + + const TrackPosition* i = m_track_positions; + const TrackPosition* const j = i + m_track_positions_count; + + while (i != j) + { + const TrackPosition& p = *i++; + + if (p.m_track == n) + return &p; + } + + return NULL; //no matching track number found +} + + +long long CuePoint::GetTimeCode() const +{ + return m_timecode; +} + +long long CuePoint::GetTime(const Segment* pSegment) const +{ + assert(pSegment); + assert(m_timecode >= 0); + + const SegmentInfo* const pInfo = pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long time = scale * m_timecode; + + return time; +} + + +#if 0 +long long Segment::Unparsed() const +{ + if (m_size < 0) + return LLONG_MAX; + + const long long stop = m_start + m_size; + + const long long result = stop - m_pos; + assert(result >= 0); + + return result; +} +#else +bool Segment::DoneParsing() const +{ + if (m_size < 0) + { + long long total, avail; + + const int status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return true; //must assume done + + if (total < 0) + return false; //assume live stream + + return (m_pos >= total); + } + + const long long stop = m_start + m_size; + + return (m_pos >= stop); +} +#endif + + +const Cluster* Segment::GetFirst() const +{ + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + Cluster* const pCluster = m_clusters[0]; + assert(pCluster); + + return pCluster; +} + + +const Cluster* Segment::GetLast() const +{ + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + const long idx = m_clusterCount - 1; + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + + return pCluster; +} + + +unsigned long Segment::GetCount() const +{ + return m_clusterCount; +} + + +const Cluster* Segment::GetNext(const Cluster* pCurr) +{ + assert(pCurr); + assert(pCurr != &m_eos); + assert(m_clusters); + + long idx = pCurr->m_index; + + if (idx >= 0) + { + assert(m_clusterCount > 0); + assert(idx < m_clusterCount); + assert(pCurr == m_clusters[idx]); + + ++idx; + + if (idx >= m_clusterCount) + return &m_eos; //caller will LoadCluster as desired + + Cluster* const pNext = m_clusters[idx]; + assert(pNext); + assert(pNext->m_index >= 0); + assert(pNext->m_index == idx); + + return pNext; + } + + assert(m_clusterPreloadCount > 0); + + long long pos = pCurr->m_element_start; + + assert(m_size >= 0); //TODO + const long long stop = m_start + m_size; //end of segment + + { + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); //TODO + if (result != 0) + return NULL; + + const long long id = ReadUInt(m_pReader, pos, len); + assert(id == 0x0F43B675); //Cluster ID + if (id != 0x0F43B675) + return NULL; + + pos += len; //consume ID + + //Read Size + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); //TODO + assert((pos + len) <= stop); //TODO + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size > 0); //TODO + //assert((pCurr->m_size <= 0) || (pCurr->m_size == size)); + + pos += len; //consume length of size of element + assert((pos + size) <= stop); //TODO + + //Pos now points to start of payload + + pos += size; //consume payload + } + + long long off_next = 0; + + while (pos < stop) + { + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); //TODO + if (result != 0) + return NULL; + + const long long idpos = pos; //pos of next (potential) cluster + + const long long id = ReadUInt(m_pReader, idpos, len); + assert(id > 0); //TODO + + pos += len; //consume ID + + //Read Size + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); //TODO + assert((pos + len) <= stop); //TODO + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size >= 0); //TODO + + pos += len; //consume length of size of element + assert((pos + size) <= stop); //TODO + + //Pos now points to start of payload + + if (size == 0) //weird + continue; + + if (id == 0x0F43B675) //Cluster ID + { + const long long off_next_ = idpos - m_start; + + long long pos_; + long len_; + + const long status = Cluster::HasBlockEntries( + this, + off_next_, + pos_, + len_); + + assert(status >= 0); + + if (status > 0) + { + off_next = off_next_; + break; + } + } + + pos += size; //consume payload + } + + if (off_next <= 0) + return 0; + + Cluster** const ii = m_clusters + m_clusterCount; + Cluster** i = ii; + + Cluster** const jj = ii + m_clusterPreloadCount; + Cluster** j = jj; + + while (i < j) + { + //INVARIANT: + //[0, i) < pos_next + //[i, j) ? + //[j, jj) > pos_next + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pNext = *k; + assert(pNext); + assert(pNext->m_index < 0); + + //const long long pos_ = pNext->m_pos; + //assert(pos_); + //pos = pos_ * ((pos_ < 0) ? -1 : 1); + + pos = pNext->GetPosition(); + + if (pos < off_next) + i = k + 1; + else if (pos > off_next) + j = k; + else + return pNext; + } + + assert(i == j); + + Cluster* const pNext = Cluster::Create(this, + -1, + off_next); + assert(pNext); + + const ptrdiff_t idx_next = i - m_clusters; //insertion position + + PreloadCluster(pNext, idx_next); + assert(m_clusters); + assert(idx_next < m_clusterSize); + assert(m_clusters[idx_next] == pNext); + + return pNext; +} + + +long Segment::ParseNext( + const Cluster* pCurr, + const Cluster*& pResult, + long long& pos, + long& len) +{ + assert(pCurr); + assert(!pCurr->EOS()); + assert(m_clusters); + + pResult = 0; + + if (pCurr->m_index >= 0) //loaded (not merely preloaded) + { + assert(m_clusters[pCurr->m_index] == pCurr); + + const long next_idx = pCurr->m_index + 1; + + if (next_idx < m_clusterCount) + { + pResult = m_clusters[next_idx]; + return 0; //success + } + + //curr cluster is last among loaded + + const long result = LoadCluster(pos, len); + + if (result < 0) //error or underflow + return result; + + if (result > 0) //no more clusters + { + //pResult = &m_eos; + return 1; + } + + pResult = GetLast(); + return 0; //success + } + + assert(m_pos > 0); + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + //interrogate curr cluster + + pos = pCurr->m_element_start; + + if (pCurr->m_element_size >= 0) + pos += pCurr->m_element_size; + else + { + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(m_pReader, pos, len); + + if (id != 0x0F43B675) //weird: not Cluster ID + return -1; + + pos += len; //consume ID + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume size field + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) //TODO: should never happen + return E_FILE_FORMAT_INVALID; //TODO: resolve this + + //assert((pCurr->m_size <= 0) || (pCurr->m_size == size)); + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + //Pos now points to start of payload + + pos += size; //consume payload (that is, the current cluster) + assert((segment_stop < 0) || (pos <= segment_stop)); + + //By consuming the payload, we are assuming that the curr + //cluster isn't interesting. That is, we don't bother checking + //whether the payload of the curr cluster is less than what + //happens to be available (obtained via IMkvReader::Length). + //Presumably the caller has already dispensed with the current + //cluster, and really does want the next cluster. + } + + //pos now points to just beyond the last fully-loaded cluster + + for (;;) + { + const long status = DoParseNext(pResult, pos, len); + + if (status <= 1) + return status; + } +} + + +long Segment::DoParseNext( + const Cluster*& pResult, + long long& pos, + long& len) +{ + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + //Parse next cluster. This is strictly a parsing activity. + //Creation of a new cluster object happens later, after the + //parsing is done. + + long long off_next = 0; + long long cluster_size = -1; + + for (;;) + { + if ((total >= 0) && (pos >= total)) + return 1; //EOF + + if ((segment_stop >= 0) && (pos >= segment_stop)) + return 1; //EOF + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; //absolute + const long long idoff = pos - m_start; //relative + + const long long id = ReadUInt(m_pReader, idpos, len); //absolute + + if (id < 0) //error + return static_cast(id); + + if (id == 0) //weird + return -1; //generic error + + pos += len; //consume ID + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume length of size of element + + //Pos now points to start of payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if ((segment_stop >= 0) && + (size != unknown_size) && + ((pos + size) > segment_stop)) + { + return E_FILE_FORMAT_INVALID; + } + + if (id == 0x0C53BB6B) //Cues ID + { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + const long long element_stop = pos + size; + + if ((segment_stop >= 0) && (element_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + const long long element_start = idpos; + const long long element_size = element_stop - element_start; + + if (m_pCues == NULL) + { + m_pCues = new Cues(this, + pos, + size, + element_start, + element_size); + assert(m_pCues); //TODO + } + + pos += size; //consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + continue; + } + + if (id != 0x0F43B675) //not a Cluster ID + { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + continue; + } + +#if 0 //this is commented-out to support incremental cluster parsing + len = static_cast(size); + + if (element_stop > avail) + return E_BUFFER_NOT_FULL; +#endif + + //We have a cluster. + + off_next = idoff; + + if (size != unknown_size) + cluster_size = size; + + break; + } + + assert(off_next > 0); //have cluster + + //We have parsed the next cluster. + //We have not created a cluster object yet. What we need + //to do now is determine whether it has already be preloaded + //(in which case, an object for this cluster has already been + //created), and if not, create a new cluster object. + + Cluster** const ii = m_clusters + m_clusterCount; + Cluster** i = ii; + + Cluster** const jj = ii + m_clusterPreloadCount; + Cluster** j = jj; + + while (i < j) + { + //INVARIANT: + //[0, i) < pos_next + //[i, j) ? + //[j, jj) > pos_next + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + const Cluster* const pNext = *k; + assert(pNext); + assert(pNext->m_index < 0); + + pos = pNext->GetPosition(); + assert(pos >= 0); + + if (pos < off_next) + i = k + 1; + else if (pos > off_next) + j = k; + else + { + pResult = pNext; + return 0; //success + } + } + + assert(i == j); + + long long pos_; + long len_; + + status = Cluster::HasBlockEntries(this, off_next, pos_, len_); + + if (status < 0) //error or underflow + { + pos = pos_; + len = len_; + + return status; + } + + if (status > 0) //means "found at least one block entry" + { + Cluster* const pNext = Cluster::Create(this, + -1, //preloaded + off_next); + //element_size); + assert(pNext); + + const ptrdiff_t idx_next = i - m_clusters; //insertion position + + PreloadCluster(pNext, idx_next); + assert(m_clusters); + assert(idx_next < m_clusterSize); + assert(m_clusters[idx_next] == pNext); + + pResult = pNext; + return 0; //success + } + + //status == 0 means "no block entries found" + + if (cluster_size < 0) //unknown size + { + const long long payload_pos = pos; //absolute pos of cluster payload + + for (;;) //determine cluster size + { + if ((total >= 0) && (pos >= total)) + break; + + if ((segment_stop >= 0) && (pos >= segment_stop)) + break; //no more clusters + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + break; + + if (id == 0x0C53BB6B) //Cues ID + break; + + pos += len; //consume ID (of sub-element) + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume size field of element + + //pos now points to start of sub-element's payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } //determine cluster size + + cluster_size = pos - payload_pos; + assert(cluster_size >= 0); //TODO: handle cluster_size = 0 + + pos = payload_pos; //reset and re-parse original cluster + } + + pos += cluster_size; //consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + return 2; //try to find a cluster that follows next +} + + +const Cluster* Segment::FindCluster(long long time_ns) const +{ + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + { + Cluster* const pCluster = m_clusters[0]; + assert(pCluster); + assert(pCluster->m_index == 0); + + if (time_ns <= pCluster->GetTime()) + return pCluster; + } + + //Binary search of cluster array + + long i = 0; + long j = m_clusterCount; + + while (i < j) + { + //INVARIANT: + //[0, i) <= time_ns + //[i, j) ? + //[j, m_clusterCount) > time_ns + + const long k = i + (j - i) / 2; + assert(k < m_clusterCount); + + Cluster* const pCluster = m_clusters[k]; + assert(pCluster); + assert(pCluster->m_index == k); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i > 0); + assert(i <= m_clusterCount); + + const long k = i - 1; + + Cluster* const pCluster = m_clusters[k]; + assert(pCluster); + assert(pCluster->m_index == k); + assert(pCluster->GetTime() <= time_ns); + + return pCluster; +} + + +#if 0 +const BlockEntry* Segment::Seek( + long long time_ns, + const Track* pTrack) const +{ + assert(pTrack); + + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return pTrack->GetEOS(); + + Cluster** const i = m_clusters; + assert(i); + + { + Cluster* const pCluster = *i; + assert(pCluster); + assert(pCluster->m_index == 0); //m_clusterCount > 0 + assert(pCluster->m_pSegment == this); + + if (time_ns <= pCluster->GetTime()) + return pCluster->GetEntry(pTrack); + } + + Cluster** const j = i + m_clusterCount; + + if (pTrack->GetType() == 2) //audio + { + //TODO: we could decide to use cues for this, as we do for video. + //But we only use it for video because looking around for a keyframe + //can get expensive. Audio doesn't require anything special so a + //straight cluster search is good enough (we assume). + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + Cluster* const pCluster = *mid; + assert(pCluster); + assert(pCluster->m_index == long(mid - m_clusters)); + assert(pCluster->m_pSegment == this); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + while (lo > i) + { + Cluster* const pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + const BlockEntry* const pBE = pCluster->GetEntry(pTrack); + + if ((pBE != 0) && !pBE->EOS()) + return pBE; + + //landed on empty cluster (no entries) + } + + return pTrack->GetEOS(); //weird + } + + assert(pTrack->GetType() == 1); //video + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + Cluster* const pCluster = *mid; + assert(pCluster); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + Cluster* pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + { + const BlockEntry* const pBE = pCluster->GetEntry(pTrack, time_ns); + + if ((pBE != 0) && !pBE->EOS()) //found a keyframe + return pBE; + } + + const VideoTrack* const pVideo = static_cast(pTrack); + + while (lo != i) + { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + const BlockEntry* const pBlockEntry = pCluster->GetMaxKey(pVideo); + + if ((pBlockEntry != 0) && !pBlockEntry->EOS()) + return pBlockEntry; + } + + //weird: we're on the first cluster, but no keyframe found + //should never happen but we must return something anyway + + return pTrack->GetEOS(); +} +#endif + + +#if 0 +bool Segment::SearchCues( + long long time_ns, + Track* pTrack, + Cluster*& pCluster, + const BlockEntry*& pBlockEntry, + const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) +{ + if (pTrack->GetType() != 1) //not video + return false; //TODO: for now, just handle video stream + + if (m_pCues == NULL) + return false; + + if (!m_pCues->Find(time_ns, pTrack, pCP, pTP)) + return false; //weird + + assert(pCP); + assert(pTP); + assert(pTP->m_track == pTrack->GetNumber()); + + //We have the cue point and track position we want, + //so we now need to search for the cluster having + //the indicated position. + + return GetCluster(pCP, pTP, pCluster, pBlockEntry); +} +#endif + + +const Tracks* Segment::GetTracks() const +{ + return m_pTracks; +} + + +const SegmentInfo* Segment::GetInfo() const +{ + return m_pInfo; +} + + +const Cues* Segment::GetCues() const +{ + return m_pCues; +} + + +const Chapters* Segment::GetChapters() const +{ + return m_pChapters; +} + + +const SeekHead* Segment::GetSeekHead() const +{ + return m_pSeekHead; +} + + +long long Segment::GetDuration() const +{ + assert(m_pInfo); + return m_pInfo->GetDuration(); +} + + +Chapters::Chapters( + Segment* pSegment, + long long payload_start, + long long payload_size, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(payload_start), + m_size(payload_size), + m_element_start(element_start), + m_element_size(element_size), + m_editions(NULL), + m_editions_size(0), + m_editions_count(0) +{ +} + + +Chapters::~Chapters() +{ + while (m_editions_count > 0) + { + Edition& e = m_editions[--m_editions_count]; + e.Clear(); + } +} + + +long Chapters::Parse() +{ + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; // payload start + const long long stop = pos + m_size; // payload stop + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x05B9) // EditionEntry ID + { + status = ParseEdition(pos, size); + + if (status < 0) // error + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + + +int Chapters::GetEditionCount() const +{ + return m_editions_count; +} + + +const Chapters::Edition* Chapters::GetEdition(int idx) const +{ + if (idx < 0) + return NULL; + + if (idx >= m_editions_count) + return NULL; + + return m_editions + idx; +} + + +bool Chapters::ExpandEditionsArray() +{ + if (m_editions_size > m_editions_count) + return true; // nothing else to do + + const int size = (m_editions_size == 0) ? 1 : 2 * m_editions_size; + + Edition* const editions = new (std::nothrow) Edition[size]; + + if (editions == NULL) + return false; + + for (int idx = 0; idx < m_editions_count; ++idx) + { + m_editions[idx].ShallowCopy(editions[idx]); + } + + delete[] m_editions; + m_editions = editions; + + m_editions_size = size; + return true; +} + + +long Chapters::ParseEdition( + long long pos, + long long size) +{ + if (!ExpandEditionsArray()) + return -1; + + Edition& e = m_editions[m_editions_count++]; + e.Init(); + + return e.Parse(m_pSegment->m_pReader, pos, size); +} + + +Chapters::Edition::Edition() +{ +} + + +Chapters::Edition::~Edition() +{ +} + + +int Chapters::Edition::GetAtomCount() const +{ + return m_atoms_count; +} + + +const Chapters::Atom* Chapters::Edition::GetAtom(int index) const +{ + if (index < 0) + return NULL; + + if (index >= m_atoms_count) + return NULL; + + return m_atoms + index; +} + + +void Chapters::Edition::Init() +{ + m_atoms = NULL; + m_atoms_size = 0; + m_atoms_count = 0; +} + + +void Chapters::Edition::ShallowCopy(Edition& rhs) const +{ + rhs.m_atoms = m_atoms; + rhs.m_atoms_size = m_atoms_size; + rhs.m_atoms_count = m_atoms_count; +} + + +void Chapters::Edition::Clear() +{ + while (m_atoms_count > 0) + { + Atom& a = m_atoms[--m_atoms_count]; + a.Clear(); + } + + delete[] m_atoms; + m_atoms = NULL; + + m_atoms_size = 0; +} + + +long Chapters::Edition::Parse( + IMkvReader* pReader, + long long pos, + long long size) +{ + const long long stop = pos + size; + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x36) // Atom ID + { + status = ParseAtom(pReader, pos, size); + + if (status < 0) // error + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + + +long Chapters::Edition::ParseAtom( + IMkvReader* pReader, + long long pos, + long long size) +{ + if (!ExpandAtomsArray()) + return -1; + + Atom& a = m_atoms[m_atoms_count++]; + a.Init(); + + return a.Parse(pReader, pos, size); +} + + +bool Chapters::Edition::ExpandAtomsArray() +{ + if (m_atoms_size > m_atoms_count) + return true; // nothing else to do + + const int size = (m_atoms_size == 0) ? 1 : 2 * m_atoms_size; + + Atom* const atoms = new (std::nothrow) Atom[size]; + + if (atoms == NULL) + return false; + + for (int idx = 0; idx < m_atoms_count; ++idx) + { + m_atoms[idx].ShallowCopy(atoms[idx]); + } + + delete[] m_atoms; + m_atoms = atoms; + + m_atoms_size = size; + return true; +} + + +Chapters::Atom::Atom() +{ +} + + +Chapters::Atom::~Atom() +{ +} + + +unsigned long long Chapters::Atom::GetUID() const +{ + return m_uid; +} + + +const char* Chapters::Atom::GetStringUID() const +{ + return m_string_uid; +} + + +long long Chapters::Atom::GetStartTimecode() const +{ + return m_start_timecode; +} + + +long long Chapters::Atom::GetStopTimecode() const +{ + return m_stop_timecode; +} + + +long long Chapters::Atom::GetStartTime(const Chapters* pChapters) const +{ + return GetTime(pChapters, m_start_timecode); +} + + +long long Chapters::Atom::GetStopTime(const Chapters* pChapters) const +{ + return GetTime(pChapters, m_stop_timecode); +} + + +int Chapters::Atom::GetDisplayCount() const +{ + return m_displays_count; +} + + +const Chapters::Display* Chapters::Atom::GetDisplay(int index) const +{ + if (index < 0) + return NULL; + + if (index >= m_displays_count) + return NULL; + + return m_displays + index; +} + + +void Chapters::Atom::Init() +{ + m_string_uid = NULL; + m_uid = 0; + m_start_timecode = -1; + m_stop_timecode = -1; + + m_displays = NULL; + m_displays_size = 0; + m_displays_count = 0; +} + + +void Chapters::Atom::ShallowCopy(Atom& rhs) const +{ + rhs.m_string_uid = m_string_uid; + rhs.m_uid = m_uid; + rhs.m_start_timecode = m_start_timecode; + rhs.m_stop_timecode = m_stop_timecode; + + rhs.m_displays = m_displays; + rhs.m_displays_size = m_displays_size; + rhs.m_displays_count = m_displays_count; +} + + +void Chapters::Atom::Clear() +{ + delete[] m_string_uid; + m_string_uid = NULL; + + while (m_displays_count > 0) + { + Display& d = m_displays[--m_displays_count]; + d.Clear(); + } + + delete[] m_displays; + m_displays = NULL; + + m_displays_size = 0; +} + + +long Chapters::Atom::Parse( + IMkvReader* pReader, + long long pos, + long long size) +{ + const long long stop = pos + size; + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x00) // Display ID + { + status = ParseDisplay(pReader, pos, size); + + if (status < 0) // error + return status; + } + else if (id == 0x1654) // StringUID ID + { + status = UnserializeString(pReader, pos, size, m_string_uid); + + if (status < 0) // error + return status; + } + else if (id == 0x33C4) // UID ID + { + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast(val); + + m_uid = val; + } + else if (id == 0x11) // TimeStart ID + { + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast(val); + + m_start_timecode = val; + } + else if (id == 0x12) // TimeEnd ID + { + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast(val); + + m_stop_timecode = val; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + + +long long Chapters::Atom::GetTime( + const Chapters* pChapters, + long long timecode) +{ + if (pChapters == NULL) + return -1; + + Segment* const pSegment = pChapters->m_pSegment; + + if (pSegment == NULL) // weird + return -1; + + const SegmentInfo* const pInfo = pSegment->GetInfo(); + + if (pInfo == NULL) + return -1; + + const long long timecode_scale = pInfo->GetTimeCodeScale(); + + if (timecode_scale < 1) // weird + return -1; + + if (timecode < 0) + return -1; + + const long long result = timecode_scale * timecode; + + return result; +} + + +long Chapters::Atom::ParseDisplay( + IMkvReader* pReader, + long long pos, + long long size) +{ + if (!ExpandDisplaysArray()) + return -1; + + Display& d = m_displays[m_displays_count++]; + d.Init(); + + return d.Parse(pReader, pos, size); +} + + +bool Chapters::Atom::ExpandDisplaysArray() +{ + if (m_displays_size > m_displays_count) + return true; // nothing else to do + + const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size; + + Display* const displays = new (std::nothrow) Display[size]; + + if (displays == NULL) + return false; + + for (int idx = 0; idx < m_displays_count; ++idx) + { + m_displays[idx].ShallowCopy(displays[idx]); + } + + delete[] m_displays; + m_displays = displays; + + m_displays_size = size; + return true; +} + + +Chapters::Display::Display() +{ +} + + +Chapters::Display::~Display() +{ +} + + +const char* Chapters::Display::GetString() const +{ + return m_string; +} + + +const char* Chapters::Display::GetLanguage() const +{ + return m_language; +} + + +const char* Chapters::Display::GetCountry() const +{ + return m_country; +} + + +void Chapters::Display::Init() +{ + m_string = NULL; + m_language = NULL; + m_country = NULL; +} + + +void Chapters::Display::ShallowCopy(Display& rhs) const +{ + rhs.m_string = m_string; + rhs.m_language = m_language; + rhs.m_country = m_country; +} + + +void Chapters::Display::Clear() +{ + delete[] m_string; + m_string = NULL; + + delete[] m_language; + m_language = NULL; + + delete[] m_country; + m_country = NULL; +} + + +long Chapters::Display::Parse( + IMkvReader* pReader, + long long pos, + long long size) +{ + const long long stop = pos + size; + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x05) // ChapterString ID + { + status = UnserializeString(pReader, pos, size, m_string); + + if (status) + return status; + } + else if (id == 0x037C) // ChapterLanguage ID + { + status = UnserializeString(pReader, pos, size, m_language); + + if (status) + return status; + } + else if (id == 0x037E) // ChapterCountry ID + { + status = UnserializeString(pReader, pos, size, m_country); + + if (status) + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + + +SegmentInfo::SegmentInfo( + Segment* pSegment, + long long start, + long long size_, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_pMuxingAppAsUTF8(NULL), + m_pWritingAppAsUTF8(NULL), + m_pTitleAsUTF8(NULL) +{ +} + +SegmentInfo::~SegmentInfo() +{ + delete[] m_pMuxingAppAsUTF8; + m_pMuxingAppAsUTF8 = NULL; + + delete[] m_pWritingAppAsUTF8; + m_pWritingAppAsUTF8 = NULL; + + delete[] m_pTitleAsUTF8; + m_pTitleAsUTF8 = NULL; +} + + +long SegmentInfo::Parse() +{ + assert(m_pMuxingAppAsUTF8 == NULL); + assert(m_pWritingAppAsUTF8 == NULL); + assert(m_pTitleAsUTF8 == NULL); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; + const long long stop = m_start + m_size; + + m_timecodeScale = 1000000; + m_duration = -1; + + while (pos < stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x0AD7B1) //Timecode Scale + { + m_timecodeScale = UnserializeUInt(pReader, pos, size); + + if (m_timecodeScale <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0489) //Segment duration + { + const long status = UnserializeFloat( + pReader, + pos, + size, + m_duration); + + if (status < 0) + return status; + + if (m_duration < 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0D80) //MuxingApp + { + const long status = UnserializeString( + pReader, + pos, + size, + m_pMuxingAppAsUTF8); + + if (status) + return status; + } + else if (id == 0x1741) //WritingApp + { + const long status = UnserializeString( + pReader, + pos, + size, + m_pWritingAppAsUTF8); + + if (status) + return status; + } + else if (id == 0x3BA9) //Title + { + const long status = UnserializeString( + pReader, + pos, + size, + m_pTitleAsUTF8); + + if (status) + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; +} + + +long long SegmentInfo::GetTimeCodeScale() const +{ + return m_timecodeScale; +} + + +long long SegmentInfo::GetDuration() const +{ + if (m_duration < 0) + return -1; + + assert(m_timecodeScale >= 1); + + const double dd = double(m_duration) * double(m_timecodeScale); + const long long d = static_cast(dd); + + return d; +} + +const char* SegmentInfo::GetMuxingAppAsUTF8() const +{ + return m_pMuxingAppAsUTF8; +} + + +const char* SegmentInfo::GetWritingAppAsUTF8() const +{ + return m_pWritingAppAsUTF8; +} + +const char* SegmentInfo::GetTitleAsUTF8() const +{ + return m_pTitleAsUTF8; +} + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +ContentEncoding::ContentCompression::ContentCompression() + : algo(0), + settings(NULL), + settings_len(0) { +} + +ContentEncoding::ContentCompression::~ContentCompression() { + delete [] settings; +} + +ContentEncoding::ContentEncryption::ContentEncryption() + : algo(0), + key_id(NULL), + key_id_len(0), + signature(NULL), + signature_len(0), + sig_key_id(NULL), + sig_key_id_len(0), + sig_algo(0), + sig_hash_algo(0) { +} + +ContentEncoding::ContentEncryption::~ContentEncryption() { + delete [] key_id; + delete [] signature; + delete [] sig_key_id; +} + +ContentEncoding::ContentEncoding() + : compression_entries_(NULL), + compression_entries_end_(NULL), + encryption_entries_(NULL), + encryption_entries_end_(NULL), + encoding_order_(0), + encoding_scope_(1), + encoding_type_(0) { +} + +ContentEncoding::~ContentEncoding() { + ContentCompression** comp_i = compression_entries_; + ContentCompression** const comp_j = compression_entries_end_; + + while (comp_i != comp_j) { + ContentCompression* const comp = *comp_i++; + delete comp; + } + + delete [] compression_entries_; + + ContentEncryption** enc_i = encryption_entries_; + ContentEncryption** const enc_j = encryption_entries_end_; + + while (enc_i != enc_j) { + ContentEncryption* const enc = *enc_i++; + delete enc; + } + + delete [] encryption_entries_; +} + + +const ContentEncoding::ContentCompression* +ContentEncoding::GetCompressionByIndex(unsigned long idx) const { + const ptrdiff_t count = compression_entries_end_ - compression_entries_; + assert(count >= 0); + + if (idx >= static_cast(count)) + return NULL; + + return compression_entries_[idx]; +} + +unsigned long ContentEncoding::GetCompressionCount() const { + const ptrdiff_t count = compression_entries_end_ - compression_entries_; + assert(count >= 0); + + return static_cast(count); +} + +const ContentEncoding::ContentEncryption* +ContentEncoding::GetEncryptionByIndex(unsigned long idx) const { + const ptrdiff_t count = encryption_entries_end_ - encryption_entries_; + assert(count >= 0); + + if (idx >= static_cast(count)) + return NULL; + + return encryption_entries_[idx]; +} + +unsigned long ContentEncoding::GetEncryptionCount() const { + const ptrdiff_t count = encryption_entries_end_ - encryption_entries_; + assert(count >= 0); + + return static_cast(count); +} + +long ContentEncoding::ParseContentEncAESSettingsEntry( + long long start, + long long size, + IMkvReader* pReader, + ContentEncAESSettings* aes) { + assert(pReader); + assert(aes); + + long long pos = start; + const long long stop = start + size; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x7E8) { + // AESSettingsCipherMode + aes->cipher_mode = UnserializeUInt(pReader, pos, size); + if (aes->cipher_mode != 1) + return E_FILE_FORMAT_INVALID; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + return 0; +} + +long ContentEncoding::ParseContentEncodingEntry(long long start, + long long size, + IMkvReader* pReader) { + assert(pReader); + + long long pos = start; + const long long stop = start + size; + + // Count ContentCompression and ContentEncryption elements. + int compression_count = 0; + int encryption_count = 0; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x1034) // ContentCompression ID + ++compression_count; + + if (id == 0x1035) // ContentEncryption ID + ++encryption_count; + + pos += size; //consume payload + assert(pos <= stop); + } + + if (compression_count <= 0 && encryption_count <= 0) + return -1; + + if (compression_count > 0) { + compression_entries_ = + new (std::nothrow) ContentCompression*[compression_count]; + if (!compression_entries_) + return -1; + compression_entries_end_ = compression_entries_; + } + + if (encryption_count > 0) { + encryption_entries_ = + new (std::nothrow) ContentEncryption*[encryption_count]; + if (!encryption_entries_) { + delete [] compression_entries_; + return -1; + } + encryption_entries_end_ = encryption_entries_; + } + + pos = start; + while (pos < stop) { + long long id, size; + long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x1031) { + // ContentEncodingOrder + encoding_order_ = UnserializeUInt(pReader, pos, size); + } else if (id == 0x1032) { + // ContentEncodingScope + encoding_scope_ = UnserializeUInt(pReader, pos, size); + if (encoding_scope_ < 1) + return -1; + } else if (id == 0x1033) { + // ContentEncodingType + encoding_type_ = UnserializeUInt(pReader, pos, size); + } else if (id == 0x1034) { + // ContentCompression ID + ContentCompression* const compression = + new (std::nothrow) ContentCompression(); + if (!compression) + return -1; + + status = ParseCompressionEntry(pos, size, pReader, compression); + if (status) { + delete compression; + return status; + } + *compression_entries_end_++ = compression; + } else if (id == 0x1035) { + // ContentEncryption ID + ContentEncryption* const encryption = + new (std::nothrow) ContentEncryption(); + if (!encryption) + return -1; + + status = ParseEncryptionEntry(pos, size, pReader, encryption); + if (status) { + delete encryption; + return status; + } + *encryption_entries_end_++ = encryption; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + +long ContentEncoding::ParseCompressionEntry( + long long start, + long long size, + IMkvReader* pReader, + ContentCompression* compression) { + assert(pReader); + assert(compression); + + long long pos = start; + const long long stop = start + size; + + bool valid = false; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x254) { + // ContentCompAlgo + long long algo = UnserializeUInt(pReader, pos, size); + if (algo < 0) + return E_FILE_FORMAT_INVALID; + compression->algo = algo; + valid = true; + } else if (id == 0x255) { + // ContentCompSettings + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = pReader->Read(pos, buflen, buf); + if (read_status) { + delete [] buf; + return status; + } + + compression->settings = buf; + compression->settings_len = buflen; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + // ContentCompAlgo is mandatory + if (!valid) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +long ContentEncoding::ParseEncryptionEntry( + long long start, + long long size, + IMkvReader* pReader, + ContentEncryption* encryption) { + assert(pReader); + assert(encryption); + + long long pos = start; + const long long stop = start + size; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x7E1) { + // ContentEncAlgo + encryption->algo = UnserializeUInt(pReader, pos, size); + if (encryption->algo != 5) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x7E2) { + // ContentEncKeyID + delete[] encryption->key_id; + encryption->key_id = NULL; + encryption->key_id_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = pReader->Read(pos, buflen, buf); + if (read_status) { + delete [] buf; + return status; + } + + encryption->key_id = buf; + encryption->key_id_len = buflen; + } else if (id == 0x7E3) { + // ContentSignature + delete[] encryption->signature; + encryption->signature = NULL; + encryption->signature_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = pReader->Read(pos, buflen, buf); + if (read_status) { + delete [] buf; + return status; + } + + encryption->signature = buf; + encryption->signature_len = buflen; + } else if (id == 0x7E4) { + // ContentSigKeyID + delete[] encryption->sig_key_id; + encryption->sig_key_id = NULL; + encryption->sig_key_id_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = pReader->Read(pos, buflen, buf); + if (read_status) { + delete [] buf; + return status; + } + + encryption->sig_key_id = buf; + encryption->sig_key_id_len = buflen; + } else if (id == 0x7E5) { + // ContentSigAlgo + encryption->sig_algo = UnserializeUInt(pReader, pos, size); + } else if (id == 0x7E6) { + // ContentSigHashAlgo + encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size); + } else if (id == 0x7E7) { + // ContentEncAESSettings + const long status = ParseContentEncAESSettingsEntry( + pos, + size, + pReader, + &encryption->aes_settings); + if (status) + return status; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + return 0; +} + +Track::Track( + Segment* pSegment, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_element_start(element_start), + m_element_size(element_size), + content_encoding_entries_(NULL), + content_encoding_entries_end_(NULL) +{ +} + +Track::~Track() +{ + Info& info = const_cast(m_info); + info.Clear(); + + ContentEncoding** i = content_encoding_entries_; + ContentEncoding** const j = content_encoding_entries_end_; + + while (i != j) { + ContentEncoding* const encoding = *i++; + delete encoding; + } + + delete [] content_encoding_entries_; +} + +long Track::Create( + Segment* pSegment, + const Info& info, + long long element_start, + long long element_size, + Track*& pResult) +{ + if (pResult) + return -1; + + Track* const pTrack = new (std::nothrow) Track(pSegment, + element_start, + element_size); + + if (pTrack == NULL) + return -1; //generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) // error + { + delete pTrack; + return status; + } + + pResult = pTrack; + return 0; //success +} + +Track::Info::Info(): + uid(0), + defaultDuration(0), + codecDelay(0), + seekPreRoll(0), + nameAsUTF8(NULL), + language(NULL), + codecId(NULL), + codecNameAsUTF8(NULL), + codecPrivate(NULL), + codecPrivateSize(0), + lacing(false) +{ +} + +Track::Info::~Info() +{ + Clear(); +} + +void Track::Info::Clear() +{ + delete[] nameAsUTF8; + nameAsUTF8 = NULL; + + delete[] language; + language = NULL; + + delete[] codecId; + codecId = NULL; + + delete[] codecPrivate; + codecPrivate = NULL; + codecPrivateSize = 0; + + delete[] codecNameAsUTF8; + codecNameAsUTF8 = NULL; +} + +int Track::Info::CopyStr(char* Info::*str, Info& dst_) const +{ + if (str == static_cast(NULL)) + return -1; + + char*& dst = dst_.*str; + + if (dst) //should be NULL already + return -1; + + const char* const src = this->*str; + + if (src == NULL) + return 0; + + const size_t len = strlen(src); + + dst = new (std::nothrow) char[len+1]; + + if (dst == NULL) + return -1; + + strcpy(dst, src); + + return 0; +} + + +int Track::Info::Copy(Info& dst) const +{ + if (&dst == this) + return 0; + + dst.type = type; + dst.number = number; + dst.defaultDuration = defaultDuration; + dst.codecDelay = codecDelay; + dst.seekPreRoll = seekPreRoll; + dst.uid = uid; + dst.lacing = lacing; + dst.settings = settings; + + //We now copy the string member variables from src to dst. + //This involves memory allocation so in principle the operation + //can fail (indeed, that's why we have Info::Copy), so we must + //report this to the caller. An error return from this function + //therefore implies that the copy was only partially successful. + + if (int status = CopyStr(&Info::nameAsUTF8, dst)) + return status; + + if (int status = CopyStr(&Info::language, dst)) + return status; + + if (int status = CopyStr(&Info::codecId, dst)) + return status; + + if (int status = CopyStr(&Info::codecNameAsUTF8, dst)) + return status; + + if (codecPrivateSize > 0) + { + if (codecPrivate == NULL) + return -1; + + if (dst.codecPrivate) + return -1; + + if (dst.codecPrivateSize != 0) + return -1; + + dst.codecPrivate = new (std::nothrow) unsigned char[codecPrivateSize]; + + if (dst.codecPrivate == NULL) + return -1; + + memcpy(dst.codecPrivate, codecPrivate, codecPrivateSize); + dst.codecPrivateSize = codecPrivateSize; + } + + return 0; +} + +const BlockEntry* Track::GetEOS() const +{ + return &m_eos; +} + +long Track::GetType() const +{ + return m_info.type; +} + +long Track::GetNumber() const +{ + return m_info.number; +} + +unsigned long long Track::GetUid() const +{ + return m_info.uid; +} + +const char* Track::GetNameAsUTF8() const +{ + return m_info.nameAsUTF8; +} + +const char* Track::GetLanguage() const +{ + return m_info.language; +} + +const char* Track::GetCodecNameAsUTF8() const +{ + return m_info.codecNameAsUTF8; +} + + +const char* Track::GetCodecId() const +{ + return m_info.codecId; +} + +const unsigned char* Track::GetCodecPrivate(size_t& size) const +{ + size = m_info.codecPrivateSize; + return m_info.codecPrivate; +} + + +bool Track::GetLacing() const +{ + return m_info.lacing; +} + +unsigned long long Track::GetDefaultDuration() const +{ + return m_info.defaultDuration; +} + +unsigned long long Track::GetCodecDelay() const +{ + return m_info.codecDelay; +} + +unsigned long long Track::GetSeekPreRoll() const +{ + return m_info.seekPreRoll; +} + +long Track::GetFirst(const BlockEntry*& pBlockEntry) const +{ + const Cluster* pCluster = m_pSegment->GetFirst(); + + for (int i = 0; ; ) + { + if (pCluster == NULL) + { + pBlockEntry = GetEOS(); + return 1; + } + + if (pCluster->EOS()) + { +#if 0 + if (m_pSegment->Unparsed() <= 0) //all clusters have been loaded + { + pBlockEntry = GetEOS(); + return 1; + } +#else + if (m_pSegment->DoneParsing()) + { + pBlockEntry = GetEOS(); + return 1; + } +#endif + + pBlockEntry = 0; + return E_BUFFER_NOT_FULL; + } + + long status = pCluster->GetFirst(pBlockEntry); + + if (status < 0) //error + return status; + + if (pBlockEntry == 0) //empty cluster + { + pCluster = m_pSegment->GetNext(pCluster); + continue; + } + + for (;;) + { + const Block* const pBlock = pBlockEntry->GetBlock(); + assert(pBlock); + + const long long tn = pBlock->GetTrackNumber(); + + if ((tn == m_info.number) && VetEntry(pBlockEntry)) + return 0; + + const BlockEntry* pNextEntry; + + status = pCluster->GetNext(pBlockEntry, pNextEntry); + + if (status < 0) //error + return status; + + if (pNextEntry == 0) + break; + + pBlockEntry = pNextEntry; + } + + ++i; + + if (i >= 100) + break; + + pCluster = m_pSegment->GetNext(pCluster); + } + + //NOTE: if we get here, it means that we didn't find a block with + //a matching track number. We interpret that as an error (which + //might be too conservative). + + pBlockEntry = GetEOS(); //so we can return a non-NULL value + return 1; +} + + +long Track::GetNext( + const BlockEntry* pCurrEntry, + const BlockEntry*& pNextEntry) const +{ + assert(pCurrEntry); + assert(!pCurrEntry->EOS()); //? + + const Block* const pCurrBlock = pCurrEntry->GetBlock(); + assert(pCurrBlock && pCurrBlock->GetTrackNumber() == m_info.number); + if (!pCurrBlock || pCurrBlock->GetTrackNumber() != m_info.number) + return -1; + + const Cluster* pCluster = pCurrEntry->GetCluster(); + assert(pCluster); + assert(!pCluster->EOS()); + + long status = pCluster->GetNext(pCurrEntry, pNextEntry); + + if (status < 0) //error + return status; + + for (int i = 0; ; ) + { + while (pNextEntry) + { + const Block* const pNextBlock = pNextEntry->GetBlock(); + assert(pNextBlock); + + if (pNextBlock->GetTrackNumber() == m_info.number) + return 0; + + pCurrEntry = pNextEntry; + + status = pCluster->GetNext(pCurrEntry, pNextEntry); + + if (status < 0) //error + return status; + } + + pCluster = m_pSegment->GetNext(pCluster); + + if (pCluster == NULL) + { + pNextEntry = GetEOS(); + return 1; + } + + if (pCluster->EOS()) + { +#if 0 + if (m_pSegment->Unparsed() <= 0) //all clusters have been loaded + { + pNextEntry = GetEOS(); + return 1; + } +#else + if (m_pSegment->DoneParsing()) + { + pNextEntry = GetEOS(); + return 1; + } +#endif + + //TODO: there is a potential O(n^2) problem here: we tell the + //caller to (pre)load another cluster, which he does, but then he + //calls GetNext again, which repeats the same search. This is + //a pathological case, since the only way it can happen is if + //there exists a long sequence of clusters none of which contain a + // block from this track. One way around this problem is for the + //caller to be smarter when he loads another cluster: don't call + //us back until you have a cluster that contains a block from this + //track. (Of course, that's not cheap either, since our caller + //would have to scan the each cluster as it's loaded, so that + //would just push back the problem.) + + pNextEntry = NULL; + return E_BUFFER_NOT_FULL; + } + + status = pCluster->GetFirst(pNextEntry); + + if (status < 0) //error + return status; + + if (pNextEntry == NULL) //empty cluster + continue; + + ++i; + + if (i >= 100) + break; + } + + //NOTE: if we get here, it means that we didn't find a block with + //a matching track number after lots of searching, so we give + //up trying. + + pNextEntry = GetEOS(); //so we can return a non-NULL value + return 1; +} + +bool Track::VetEntry(const BlockEntry* pBlockEntry) const +{ + assert(pBlockEntry); + const Block* const pBlock = pBlockEntry->GetBlock(); + assert(pBlock); + assert(pBlock->GetTrackNumber() == m_info.number); + if (!pBlock || pBlock->GetTrackNumber() != m_info.number) + return false; + + // This function is used during a seek to determine whether the + // frame is a valid seek target. This default function simply + // returns true, which means all frames are valid seek targets. + // It gets overridden by the VideoTrack class, because only video + // keyframes can be used as seek target. + + return true; +} + +long Track::Seek( + long long time_ns, + const BlockEntry*& pResult) const +{ + const long status = GetFirst(pResult); + + if (status < 0) //buffer underflow, etc + return status; + + assert(pResult); + + if (pResult->EOS()) + return 0; + + const Cluster* pCluster = pResult->GetCluster(); + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + + if (time_ns <= pResult->GetBlock()->GetTime(pCluster)) + return 0; + + Cluster** const clusters = m_pSegment->m_clusters; + assert(clusters); + + const long count = m_pSegment->GetCount(); //loaded only, not preloaded + assert(count > 0); + + Cluster** const i = clusters + pCluster->GetIndex(); + assert(i); + assert(*i == pCluster); + assert(pCluster->GetTime() <= time_ns); + + Cluster** const j = clusters + count; + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + pCluster = *mid; + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters)); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + while (lo > i) + { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + pResult = pCluster->GetEntry(this); + + if ((pResult != 0) && !pResult->EOS()) + return 0; + + //landed on empty cluster (no entries) + } + + pResult = GetEOS(); //weird + return 0; +} + +const ContentEncoding* +Track::GetContentEncodingByIndex(unsigned long idx) const { + const ptrdiff_t count = + content_encoding_entries_end_ - content_encoding_entries_; + assert(count >= 0); + + if (idx >= static_cast(count)) + return NULL; + + return content_encoding_entries_[idx]; +} + +unsigned long Track::GetContentEncodingCount() const { + const ptrdiff_t count = + content_encoding_entries_end_ - content_encoding_entries_; + assert(count >= 0); + + return static_cast(count); +} + +long Track::ParseContentEncodingsEntry(long long start, long long size) { + IMkvReader* const pReader = m_pSegment->m_pReader; + assert(pReader); + + long long pos = start; + const long long stop = start + size; + + // Count ContentEncoding elements. + int count = 0; + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + + //pos now designates start of element + if (id == 0x2240) // ContentEncoding ID + ++count; + + pos += size; //consume payload + assert(pos <= stop); + } + + if (count <= 0) + return -1; + + content_encoding_entries_ = new (std::nothrow) ContentEncoding*[count]; + if (!content_encoding_entries_) + return -1; + + content_encoding_entries_end_ = content_encoding_entries_; + + pos = start; + while (pos < stop) { + long long id, size; + long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + //pos now designates start of element + if (id == 0x2240) { // ContentEncoding ID + ContentEncoding* const content_encoding = + new (std::nothrow) ContentEncoding(); + if (!content_encoding) + return -1; + + status = content_encoding->ParseContentEncodingEntry(pos, + size, + pReader); + if (status) { + delete content_encoding; + return status; + } + + *content_encoding_entries_end_++ = content_encoding; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; +} + +Track::EOSBlock::EOSBlock() : + BlockEntry(NULL, LONG_MIN) +{ +} + +BlockEntry::Kind Track::EOSBlock::GetKind() const +{ + return kBlockEOS; +} + + +const Block* Track::EOSBlock::GetBlock() const +{ + return NULL; +} + + +VideoTrack::VideoTrack( + Segment* pSegment, + long long element_start, + long long element_size) : + Track(pSegment, element_start, element_size) +{ +} + + +long VideoTrack::Parse( + Segment* pSegment, + const Info& info, + long long element_start, + long long element_size, + VideoTrack*& pResult) +{ + if (pResult) + return -1; + + if (info.type != Track::kVideo) + return -1; + + long long width = 0; + long long height = 0; + double rate = 0.0; + + IMkvReader* const pReader = pSegment->m_pReader; + + const Settings& s = info.settings; + assert(s.start >= 0); + assert(s.size >= 0); + + long long pos = s.start; + assert(pos >= 0); + + const long long stop = pos + s.size; + + while (pos < stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x30) //pixel width + { + width = UnserializeUInt(pReader, pos, size); + + if (width <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x3A) //pixel height + { + height = UnserializeUInt(pReader, pos, size); + + if (height <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0383E3) //frame rate + { + const long status = UnserializeFloat( + pReader, + pos, + size, + rate); + + if (status < 0) + return status; + + if (rate <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + VideoTrack* const pTrack = new (std::nothrow) VideoTrack(pSegment, + element_start, + element_size); + + if (pTrack == NULL) + return -1; //generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) // error + { + delete pTrack; + return status; + } + + pTrack->m_width = width; + pTrack->m_height = height; + pTrack->m_rate = rate; + + pResult = pTrack; + return 0; //success +} + + +bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const +{ + return Track::VetEntry(pBlockEntry) && pBlockEntry->GetBlock()->IsKey(); +} + +long VideoTrack::Seek( + long long time_ns, + const BlockEntry*& pResult) const +{ + const long status = GetFirst(pResult); + + if (status < 0) //buffer underflow, etc + return status; + + assert(pResult); + + if (pResult->EOS()) + return 0; + + const Cluster* pCluster = pResult->GetCluster(); + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + + if (time_ns <= pResult->GetBlock()->GetTime(pCluster)) + return 0; + + Cluster** const clusters = m_pSegment->m_clusters; + assert(clusters); + + const long count = m_pSegment->GetCount(); //loaded only, not pre-loaded + assert(count > 0); + + Cluster** const i = clusters + pCluster->GetIndex(); + assert(i); + assert(*i == pCluster); + assert(pCluster->GetTime() <= time_ns); + + Cluster** const j = clusters + count; + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + pCluster = *mid; + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters)); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + pResult = pCluster->GetEntry(this, time_ns); + + if ((pResult != 0) && !pResult->EOS()) //found a keyframe + return 0; + + while (lo != i) + { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + +#if 0 + //TODO: + //We need to handle the case when a cluster + //contains multiple keyframes. Simply returning + //the largest keyframe on the cluster isn't + //good enough. + pResult = pCluster->GetMaxKey(this); +#else + pResult = pCluster->GetEntry(this, time_ns); +#endif + + if ((pResult != 0) && !pResult->EOS()) + return 0; + } + + //weird: we're on the first cluster, but no keyframe found + //should never happen but we must return something anyway + + pResult = GetEOS(); + return 0; +} + + +long long VideoTrack::GetWidth() const +{ + return m_width; +} + + +long long VideoTrack::GetHeight() const +{ + return m_height; +} + + +double VideoTrack::GetFrameRate() const +{ + return m_rate; +} + + +AudioTrack::AudioTrack( + Segment* pSegment, + long long element_start, + long long element_size) : + Track(pSegment, element_start, element_size) +{ +} + + +long AudioTrack::Parse( + Segment* pSegment, + const Info& info, + long long element_start, + long long element_size, + AudioTrack*& pResult) +{ + if (pResult) + return -1; + + if (info.type != Track::kAudio) + return -1; + + IMkvReader* const pReader = pSegment->m_pReader; + + const Settings& s = info.settings; + assert(s.start >= 0); + assert(s.size >= 0); + + long long pos = s.start; + assert(pos >= 0); + + const long long stop = pos + s.size; + + double rate = 8000.0; // MKV default + long long channels = 1; + long long bit_depth = 0; + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x35) //Sample Rate + { + status = UnserializeFloat(pReader, pos, size, rate); + + if (status < 0) + return status; + + if (rate <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x1F) //Channel Count + { + channels = UnserializeUInt(pReader, pos, size); + + if (channels <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x2264) //Bit Depth + { + bit_depth = UnserializeUInt(pReader, pos, size); + + if (bit_depth <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + AudioTrack* const pTrack = new (std::nothrow) AudioTrack(pSegment, + element_start, + element_size); + + if (pTrack == NULL) + return -1; //generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) + { + delete pTrack; + return status; + } + + pTrack->m_rate = rate; + pTrack->m_channels = channels; + pTrack->m_bitDepth = bit_depth; + + pResult = pTrack; + return 0; //success +} + + +double AudioTrack::GetSamplingRate() const +{ + return m_rate; +} + + +long long AudioTrack::GetChannels() const +{ + return m_channels; +} + +long long AudioTrack::GetBitDepth() const +{ + return m_bitDepth; +} + +Tracks::Tracks( + Segment* pSegment, + long long start, + long long size_, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_trackEntries(NULL), + m_trackEntriesEnd(NULL) +{ +} + + +long Tracks::Parse() +{ + assert(m_trackEntries == NULL); + assert(m_trackEntriesEnd == NULL); + + const long long stop = m_start + m_size; + IMkvReader* const pReader = m_pSegment->m_pReader; + + int count = 0; + long long pos = m_start; + + while (pos < stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (size == 0) //weird + continue; + + if (id == 0x2E) //TrackEntry ID + ++count; + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + if (count <= 0) + return 0; //success + + m_trackEntries = new (std::nothrow) Track*[count]; + + if (m_trackEntries == NULL) + return -1; + + m_trackEntriesEnd = m_trackEntries; + + pos = m_start; + + while (pos < stop) + { + const long long element_start = pos; + + long long id, payload_size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + payload_size); + + if (status < 0) //error + return status; + + if (payload_size == 0) //weird + continue; + + const long long payload_stop = pos + payload_size; + assert(payload_stop <= stop); //checked in ParseElement + + const long long element_size = payload_stop - element_start; + + if (id == 0x2E) //TrackEntry ID + { + Track*& pTrack = *m_trackEntriesEnd; + pTrack = NULL; + + const long status = ParseTrackEntry( + pos, + payload_size, + element_start, + element_size, + pTrack); + + if (status) + return status; + + if (pTrack) + ++m_trackEntriesEnd; + } + + pos = payload_stop; + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; //success +} + + +unsigned long Tracks::GetTracksCount() const +{ + const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries; + assert(result >= 0); + + return static_cast(result); +} + +long Tracks::ParseTrackEntry( + long long track_start, + long long track_size, + long long element_start, + long long element_size, + Track*& pResult) const +{ + if (pResult) + return -1; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = track_start; + const long long track_stop = track_start + track_size; + + Track::Info info; + + info.type = 0; + info.number = 0; + info.uid = 0; + info.defaultDuration = 0; + + Track::Settings v; + v.start = -1; + v.size = -1; + + Track::Settings a; + a.start = -1; + a.size = -1; + + Track::Settings e; //content_encodings_settings; + e.start = -1; + e.size = -1; + + long long lacing = 1; //default is true + + while (pos < track_stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + track_stop, + id, + size); + + if (status < 0) //error + return status; + + if (size < 0) + return E_FILE_FORMAT_INVALID; + + const long long start = pos; + + if (id == 0x60) // VideoSettings ID + { + v.start = start; + v.size = size; + } + else if (id == 0x61) // AudioSettings ID + { + a.start = start; + a.size = size; + } + else if (id == 0x2D80) // ContentEncodings ID + { + e.start = start; + e.size = size; + } + else if (id == 0x33C5) //Track UID + { + if (size > 8) + return E_FILE_FORMAT_INVALID; + + info.uid = 0; + + long long pos_ = start; + const long long pos_end = start + size; + + while (pos_ != pos_end) + { + unsigned char b; + + const int status = pReader->Read(pos_, 1, &b); + + if (status) + return status; + + info.uid <<= 8; + info.uid |= b; + + ++pos_; + } + } + else if (id == 0x57) //Track Number + { + const long long num = UnserializeUInt(pReader, pos, size); + + if ((num <= 0) || (num > 127)) + return E_FILE_FORMAT_INVALID; + + info.number = static_cast(num); + } + else if (id == 0x03) //Track Type + { + const long long type = UnserializeUInt(pReader, pos, size); + + if ((type <= 0) || (type > 254)) + return E_FILE_FORMAT_INVALID; + + info.type = static_cast(type); + } + else if (id == 0x136E) //Track Name + { + const long status = UnserializeString( + pReader, + pos, + size, + info.nameAsUTF8); + + if (status) + return status; + } + else if (id == 0x02B59C) //Track Language + { + const long status = UnserializeString( + pReader, + pos, + size, + info.language); + + if (status) + return status; + } + else if (id == 0x03E383) //Default Duration + { + const long long duration = UnserializeUInt(pReader, pos, size); + + if (duration < 0) + return E_FILE_FORMAT_INVALID; + + info.defaultDuration = static_cast(duration); + } + else if (id == 0x06) //CodecID + { + const long status = UnserializeString( + pReader, + pos, + size, + info.codecId); + + if (status) + return status; + } + else if (id == 0x1C) //lacing + { + lacing = UnserializeUInt(pReader, pos, size); + + if ((lacing < 0) || (lacing > 1)) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x23A2) //Codec Private + { + delete[] info.codecPrivate; + info.codecPrivate = NULL; + info.codecPrivateSize = 0; + + const size_t buflen = static_cast(size); + + if (buflen) + { + typedef unsigned char* buf_t; + + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + + if (buf == NULL) + return -1; + + const int status = pReader->Read(pos, buflen, buf); + + if (status) + { + delete[] buf; + return status; + } + + info.codecPrivate = buf; + info.codecPrivateSize = buflen; + } + } + else if (id == 0x058688) //Codec Name + { + const long status = UnserializeString( + pReader, + pos, + size, + info.codecNameAsUTF8); + + if (status) + return status; + } + else if (id == 0x16AA) //Codec Delay + { + info.codecDelay = UnserializeUInt(pReader, pos, size); + + } + else if (id == 0x16BB) //Seek Pre Roll + { + info.seekPreRoll = UnserializeUInt(pReader, pos, size); + } + + pos += size; //consume payload + assert(pos <= track_stop); + } + + assert(pos == track_stop); + + if (info.number <= 0) //not specified + return E_FILE_FORMAT_INVALID; + + if (GetTrackByNumber(info.number)) + return E_FILE_FORMAT_INVALID; + + if (info.type <= 0) //not specified + return E_FILE_FORMAT_INVALID; + + info.lacing = (lacing > 0) ? true : false; + + if (info.type == Track::kVideo) + { + if (v.start < 0) + return E_FILE_FORMAT_INVALID; + + if (a.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings = v; + + VideoTrack* pTrack = NULL; + + const long status = VideoTrack::Parse(m_pSegment, + info, + element_start, + element_size, + pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + + if (e.start >= 0) + pResult->ParseContentEncodingsEntry(e.start, e.size); + } + else if (info.type == Track::kAudio) + { + if (a.start < 0) + return E_FILE_FORMAT_INVALID; + + if (v.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings = a; + + AudioTrack* pTrack = NULL; + + const long status = AudioTrack::Parse(m_pSegment, + info, + element_start, + element_size, + pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + + if (e.start >= 0) + pResult->ParseContentEncodingsEntry(e.start, e.size); + } + else + { + // neither video nor audio - probably metadata or subtitles + + if (a.start >= 0) + return E_FILE_FORMAT_INVALID; + + if (v.start >= 0) + return E_FILE_FORMAT_INVALID; + + if (e.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings.start = -1; + info.settings.size = 0; + + Track* pTrack = NULL; + + const long status = Track::Create(m_pSegment, + info, + element_start, + element_size, + pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + } + + return 0; //success +} + + +Tracks::~Tracks() +{ + Track** i = m_trackEntries; + Track** const j = m_trackEntriesEnd; + + while (i != j) + { + Track* const pTrack = *i++; + delete pTrack; + } + + delete[] m_trackEntries; +} + +const Track* Tracks::GetTrackByNumber(long tn) const +{ + if (tn < 0) + return NULL; + + Track** i = m_trackEntries; + Track** const j = m_trackEntriesEnd; + + while (i != j) + { + Track* const pTrack = *i++; + + if (pTrack == NULL) + continue; + + if (tn == pTrack->GetNumber()) + return pTrack; + } + + return NULL; //not found +} + + +const Track* Tracks::GetTrackByIndex(unsigned long idx) const +{ + const ptrdiff_t count = m_trackEntriesEnd - m_trackEntries; + + if (idx >= static_cast(count)) + return NULL; + + return m_trackEntries[idx]; +} + +#if 0 +long long Cluster::Unparsed() const +{ + if (m_timecode < 0) //not even partially loaded + return LLONG_MAX; + + assert(m_pos >= m_element_start); + //assert(m_element_size > m_size); + + const long long element_stop = m_element_start + m_element_size; + assert(m_pos <= element_stop); + + const long long result = element_stop - m_pos; + assert(result >= 0); + + return result; +} +#endif + + +long Cluster::Load(long long& pos, long& len) const +{ + assert(m_pSegment); + assert(m_pos >= m_element_start); + + if (m_timecode >= 0) //at least partially loaded + return 0; + + assert(m_pos == m_element_start); + assert(m_element_size < 0); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + const int status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + assert((total < 0) || (m_pos <= total)); //TODO: verify this + + pos = m_pos; + + long long cluster_size = -1; + + { + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error or underflow + return static_cast(result); + + if (result > 0) //underflow (weird) + return E_BUFFER_NOT_FULL; + + //if ((pos + len) > segment_stop) + // return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id_ = ReadUInt(pReader, pos, len); + + if (id_ < 0) //error + return static_cast(id_); + + if (id_ != 0x0F43B675) //Cluster ID + return E_FILE_FORMAT_INVALID; + + pos += len; //consume id + + //read cluster size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + //if ((pos + len) > segment_stop) + // return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(cluster_size); + + if (size == 0) + return E_FILE_FORMAT_INVALID; //TODO: verify this + + pos += len; //consume length of size of element + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size != unknown_size) + cluster_size = size; + } + + //pos points to start of payload + +#if 0 + len = static_cast(size_); + + if (cluster_stop > avail) + return E_BUFFER_NOT_FULL; +#endif + + long long timecode = -1; + long long new_pos = -1; + bool bBlock = false; + + long long cluster_stop = (cluster_size < 0) ? -1 : pos + cluster_size; + + for (;;) + { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + break; + + //Parse ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + if (id == 0) + return E_FILE_FORMAT_INVALID; + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + break; + + if (id == 0x0C53BB6B) //Cues ID + break; + + pos += len; //consume ID field + + //Parse Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume size field + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + //pos now points to start of payload + + if (size == 0) //weird + continue; + + if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (id == 0x67) //TimeCode ID + { + len = static_cast(size); + + if ((pos + size) > avail) + return E_BUFFER_NOT_FULL; + + timecode = UnserializeUInt(pReader, pos, size); + + if (timecode < 0) //error (or underflow) + return static_cast(timecode); + + new_pos = pos + size; + + if (bBlock) + break; + } + else if (id == 0x20) //BlockGroup ID + { + bBlock = true; + break; + } + else if (id == 0x23) //SimpleBlock ID + { + bBlock = true; + break; + } + + pos += size; //consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } + + assert((cluster_stop < 0) || (pos <= cluster_stop)); + + if (timecode < 0) //no timecode found + return E_FILE_FORMAT_INVALID; + + if (!bBlock) + return E_FILE_FORMAT_INVALID; + + m_pos = new_pos; //designates position just beyond timecode payload + m_timecode = timecode; // m_timecode >= 0 means we're partially loaded + + if (cluster_size >= 0) + m_element_size = cluster_stop - m_element_start; + + return 0; +} + + +long Cluster::Parse(long long& pos, long& len) const +{ + long status = Load(pos, len); + + if (status < 0) + return status; + + assert(m_pos >= m_element_start); + assert(m_timecode >= 0); + //assert(m_size > 0); + //assert(m_element_size > m_size); + + const long long cluster_stop = + (m_element_size < 0) ? -1 : m_element_start + m_element_size; + + if ((cluster_stop >= 0) && (m_pos >= cluster_stop)) + return 1; //nothing else to do + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + pos = m_pos; + + for (;;) + { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + break; + + if ((total >= 0) && (pos >= total)) + { + if (m_element_size < 0) + m_element_size = pos - m_element_start; + + break; + } + + //Parse ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + if (id == 0) //weird + return E_FILE_FORMAT_INVALID; + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) //Cluster or Cues ID + { + if (m_element_size < 0) + m_element_size = pos - m_element_start; + + break; + } + + pos += len; //consume ID field + + //Parse Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume size field + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + //pos now points to start of payload + + if (size == 0) //weird + continue; + + //const long long block_start = pos; + const long long block_stop = pos + size; + + if (cluster_stop >= 0) + { + if (block_stop > cluster_stop) + { + if ((id == 0x20) || (id == 0x23)) + return E_FILE_FORMAT_INVALID; + + pos = cluster_stop; + break; + } + } + else if ((total >= 0) && (block_stop > total)) + { + m_element_size = total - m_element_start; + pos = total; + break; + } + else if (block_stop > avail) + { + len = static_cast(size); + return E_BUFFER_NOT_FULL; + } + + Cluster* const this_ = const_cast(this); + + if (id == 0x20) //BlockGroup + return this_->ParseBlockGroup(size, pos, len); + + if (id == 0x23) //SimpleBlock + return this_->ParseSimpleBlock(size, pos, len); + + pos += size; //consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } + + assert(m_element_size > 0); + + m_pos = pos; + assert((cluster_stop < 0) || (m_pos <= cluster_stop)); + + if (m_entries_count > 0) + { + const long idx = m_entries_count - 1; + + const BlockEntry* const pLast = m_entries[idx]; + assert(pLast); + + const Block* const pBlock = pLast->GetBlock(); + assert(pBlock); + + const long long start = pBlock->m_start; + + if ((total >= 0) && (start > total)) + return -1; //defend against trucated stream + + const long long size = pBlock->m_size; + + const long long stop = start + size; + assert((cluster_stop < 0) || (stop <= cluster_stop)); + + if ((total >= 0) && (stop > total)) + return -1; //defend against trucated stream + } + + return 1; //no more entries +} + + +long Cluster::ParseSimpleBlock( + long long block_size, + long long& pos, + long& len) +{ + const long long block_start = pos; + const long long block_stop = pos + block_size; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + //parse track number + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long track = ReadUInt(pReader, pos, len); + + if (track < 0) //error + return static_cast(track); + + if (track == 0) + return E_FILE_FORMAT_INVALID; + +#if 0 + //TODO(matthewjheaney) + //This turned out to be too conservative. The problem is that + //if we see a track header in the tracks element with an unsupported + //track type, we throw that track header away, so it is not present + //in the track map. But even though we don't understand the track + //header, there are still blocks in the cluster with that track + //number. It was our decision to ignore that track header, so it's + //up to us to deal with blocks associated with that track -- we + //cannot simply report an error since technically there's nothing + //wrong with the file. + // + //For now we go ahead and finish the parse, creating a block entry + //for this block. This is somewhat wasteful, because without a + //track header there's nothing you can do with the block. What + //we really need here is a special return value that indicates to + //the caller that he should ignore this particular block, and + //continue parsing. + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(track); + + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return E_FILE_FORMAT_INVALID; +#endif + + pos += len; //consume track number + + if ((pos + 2) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 2) > avail) + { + len = 2; + return E_BUFFER_NOT_FULL; + } + + pos += 2; //consume timecode + + if ((pos + 1) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + unsigned char flags; + + status = pReader->Read(pos, 1, &flags); + + if (status < 0) //error or underflow + { + len = 1; + return status; + } + + ++pos; //consume flags byte + assert(pos <= avail); + + if (pos >= block_stop) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(flags & 0x06) >> 1; + + if ((lacing != 0) && (block_stop > avail)) + { + len = static_cast(block_stop - pos); + return E_BUFFER_NOT_FULL; + } + + status = CreateBlock(0x23, //simple block id + block_start, block_size, + 0); //DiscardPadding + + if (status != 0) + return status; + + m_pos = block_stop; + + return 0; //success +} + + +long Cluster::ParseBlockGroup( + long long payload_size, + long long& pos, + long& len) +{ + const long long payload_start = pos; + const long long payload_stop = pos + payload_size; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + if ((total >= 0) && (payload_stop > total)) + return E_FILE_FORMAT_INVALID; + + if (payload_stop > avail) + { + len = static_cast(payload_size); + return E_BUFFER_NOT_FULL; + } + + long long discard_padding = 0; + + while (pos < payload_stop) + { + //parse sub-block element ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > payload_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + if (id == 0) //not a value ID + return E_FILE_FORMAT_INVALID; + + pos += len; //consume ID field + + //Parse Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > payload_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume size field + + //pos now points to start of sub-block group payload + + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + if (id == 0x35A2) //DiscardPadding + { + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + status = UnserializeInt(pReader, pos, len, discard_padding); + + if (status < 0) //error + return status; + } + + if (id != 0x21) //sub-part of BlockGroup is not a Block + { + pos += size; //consume sub-part of block group + + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + + continue; + } + + const long long block_stop = pos + size; + + if (block_stop > payload_stop) + return E_FILE_FORMAT_INVALID; + + //parse track number + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long track = ReadUInt(pReader, pos, len); + + if (track < 0) //error + return static_cast(track); + + if (track == 0) + return E_FILE_FORMAT_INVALID; + +#if 0 + //TODO(matthewjheaney) + //This turned out to be too conservative. The problem is that + //if we see a track header in the tracks element with an unsupported + //track type, we throw that track header away, so it is not present + //in the track map. But even though we don't understand the track + //header, there are still blocks in the cluster with that track + //number. It was our decision to ignore that track header, so it's + //up to us to deal with blocks associated with that track -- we + //cannot simply report an error since technically there's nothing + //wrong with the file. + // + //For now we go ahead and finish the parse, creating a block entry + //for this block. This is somewhat wasteful, because without a + //track header there's nothing you can do with the block. What + //we really need here is a special return value that indicates to + //the caller that he should ignore this particular block, and + //continue parsing. + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(track); + + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return E_FILE_FORMAT_INVALID; +#endif + + pos += len; //consume track number + + if ((pos + 2) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 2) > avail) + { + len = 2; + return E_BUFFER_NOT_FULL; + } + + pos += 2; //consume timecode + + if ((pos + 1) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + unsigned char flags; + + status = pReader->Read(pos, 1, &flags); + + if (status < 0) //error or underflow + { + len = 1; + return status; + } + + ++pos; //consume flags byte + assert(pos <= avail); + + if (pos >= block_stop) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(flags & 0x06) >> 1; + + if ((lacing != 0) && (block_stop > avail)) + { + len = static_cast(block_stop - pos); + return E_BUFFER_NOT_FULL; + } + + pos = block_stop; //consume block-part of block group + assert(pos <= payload_stop); + } + + assert(pos == payload_stop); + + status = CreateBlock(0x20, //BlockGroup ID + payload_start, payload_size, + discard_padding); + if (status != 0) + return status; + + m_pos = payload_stop; + + return 0; //success +} + + +long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const +{ + assert(m_pos >= m_element_start); + + pEntry = NULL; + + if (index < 0) + return -1; //generic error + + if (m_entries_count < 0) + return E_BUFFER_NOT_FULL; + + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count <= m_entries_size); + + if (index < m_entries_count) + { + pEntry = m_entries[index]; + assert(pEntry); + + return 1; //found entry + } + + if (m_element_size < 0) //we don't know cluster end yet + return E_BUFFER_NOT_FULL; //underflow + + const long long element_stop = m_element_start + m_element_size; + + if (m_pos >= element_stop) + return 0; //nothing left to parse + + return E_BUFFER_NOT_FULL; //underflow, since more remains to be parsed +} + + +Cluster* Cluster::Create( + Segment* pSegment, + long idx, + long long off) + //long long element_size) +{ + assert(pSegment); + assert(off >= 0); + + const long long element_start = pSegment->m_start + off; + + Cluster* const pCluster = new Cluster(pSegment, + idx, + element_start); + //element_size); + assert(pCluster); + + return pCluster; +} + + +Cluster::Cluster() : + m_pSegment(NULL), + m_element_start(0), + m_index(0), + m_pos(0), + m_element_size(0), + m_timecode(0), + m_entries(NULL), + m_entries_size(0), + m_entries_count(0) //means "no entries" +{ +} + + +Cluster::Cluster( + Segment* pSegment, + long idx, + long long element_start + /* long long element_size */ ) : + m_pSegment(pSegment), + m_element_start(element_start), + m_index(idx), + m_pos(element_start), + m_element_size(-1 /* element_size */ ), + m_timecode(-1), + m_entries(NULL), + m_entries_size(0), + m_entries_count(-1) //means "has not been parsed yet" +{ +} + + +Cluster::~Cluster() +{ + if (m_entries_count <= 0) + return; + + BlockEntry** i = m_entries; + BlockEntry** const j = m_entries + m_entries_count; + + while (i != j) + { + BlockEntry* p = *i++; + assert(p); + + delete p; + } + + delete[] m_entries; +} + + +bool Cluster::EOS() const +{ + return (m_pSegment == NULL); +} + + +long Cluster::GetIndex() const +{ + return m_index; +} + + +long long Cluster::GetPosition() const +{ + const long long pos = m_element_start - m_pSegment->m_start; + assert(pos >= 0); + + return pos; +} + + +long long Cluster::GetElementSize() const +{ + return m_element_size; +} + + +#if 0 +bool Cluster::HasBlockEntries( + const Segment* pSegment, + long long off) //relative to start of segment payload +{ + assert(pSegment); + assert(off >= 0); //relative to segment + + IMkvReader* const pReader = pSegment->m_pReader; + + long long pos = pSegment->m_start + off; //absolute + long long size; + + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + (void)id; + assert(id >= 0); + assert(id == 0x0F43B675); //Cluster ID + + pos += len; //consume id + + size = ReadUInt(pReader, pos, len); + assert(size > 0); + + pos += len; //consume size + + //pos now points to start of payload + } + + const long long stop = pos + size; + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume size + + if (id == 0x20) //BlockGroup ID + return true; + + if (id == 0x23) //SimpleBlock ID + return true; + + pos += size; //consume payload + assert(pos <= stop); + } + + return false; +} +#endif + + +long Cluster::HasBlockEntries( + const Segment* pSegment, + long long off, //relative to start of segment payload + long long& pos, + long& len) +{ + assert(pSegment); + assert(off >= 0); //relative to segment + + IMkvReader* const pReader = pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + pos = pSegment->m_start + off; //absolute + + if ((total >= 0) && (pos >= total)) + return 0; //we don't even have a complete cluster + + const long long segment_stop = + (pSegment->m_size < 0) ? -1 : pSegment->m_start + pSegment->m_size; + + long long cluster_stop = -1; //interpreted later to mean "unknown size" + + { + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //need more data + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + len) > total)) + return 0; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + if (id != 0x0F43B675) //weird: not cluster ID + return -1; //generic error + + pos += len; //consume Cluster ID field + + //read size field + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + len) > total)) + return 0; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + if (size == 0) + return 0; //cluster does not have entries + + pos += len; //consume size field + + //pos now points to start of payload + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size != unknown_size) + { + cluster_stop = pos + size; + assert(cluster_stop >= 0); + + if ((segment_stop >= 0) && (cluster_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && (cluster_stop > total)) + //return E_FILE_FORMAT_INVALID; //too conservative + return 0; //cluster does not have any entries + } + } + + for (;;) + { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + return 0; //no entries detected + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //need more data + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + return 0; //no entries found + + if (id == 0x0C53BB6B) //Cues ID + return 0; //no entries found + + pos += len; //consume id field + + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + return E_FILE_FORMAT_INVALID; + + //read size field + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //underflow + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume size field + + //pos now points to start of payload + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not supported inside cluster + + if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (id == 0x20) //BlockGroup ID + return 1; //have at least one entry + + if (id == 0x23) //SimpleBlock ID + return 1; //have at least one entry + + pos += size; //consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } +} + + +long long Cluster::GetTimeCode() const +{ + long long pos; + long len; + + const long status = Load(pos, len); + + if (status < 0) //error + return status; + + return m_timecode; +} + + +long long Cluster::GetTime() const +{ + const long long tc = GetTimeCode(); + + if (tc < 0) + return tc; + + const SegmentInfo* const pInfo = m_pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long t = m_timecode * scale; + + return t; +} + + +long long Cluster::GetFirstTime() const +{ + const BlockEntry* pEntry; + + const long status = GetFirst(pEntry); + + if (status < 0) //error + return status; + + if (pEntry == NULL) //empty cluster + return GetTime(); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + return pBlock->GetTime(this); +} + + +long long Cluster::GetLastTime() const +{ + const BlockEntry* pEntry; + + const long status = GetLast(pEntry); + + if (status < 0) //error + return status; + + if (pEntry == NULL) //empty cluster + return GetTime(); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + return pBlock->GetTime(this); +} + + +long Cluster::CreateBlock( + long long id, + long long pos, //absolute pos of payload + long long size, + long long discard_padding) +{ + assert((id == 0x20) || (id == 0x23)); //BlockGroup or SimpleBlock + + if (m_entries_count < 0) //haven't parsed anything yet + { + assert(m_entries == NULL); + assert(m_entries_size == 0); + + m_entries_size = 1024; + m_entries = new BlockEntry*[m_entries_size]; + + m_entries_count = 0; + } + else + { + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count <= m_entries_size); + + if (m_entries_count >= m_entries_size) + { + const long entries_size = 2 * m_entries_size; + + BlockEntry** const entries = new BlockEntry*[entries_size]; + assert(entries); + + BlockEntry** src = m_entries; + BlockEntry** const src_end = src + m_entries_count; + + BlockEntry** dst = entries; + + while (src != src_end) + *dst++ = *src++; + + delete[] m_entries; + + m_entries = entries; + m_entries_size = entries_size; + } + } + + if (id == 0x20) //BlockGroup ID + return CreateBlockGroup(pos, size, discard_padding); + else //SimpleBlock ID + return CreateSimpleBlock(pos, size); +} + + +long Cluster::CreateBlockGroup( + long long start_offset, + long long size, + long long discard_padding) +{ + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count >= 0); + assert(m_entries_count < m_entries_size); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = start_offset; + const long long stop = start_offset + size; + + //For WebM files, there is a bias towards previous reference times + //(in order to support alt-ref frames, which refer back to the previous + //keyframe). Normally a 0 value is not possible, but here we tenatively + //allow 0 as the value of a reference frame, with the interpretation + //that this is a "previous" reference time. + + long long prev = 1; //nonce + long long next = 0; //nonce + long long duration = -1; //really, this is unsigned + + long long bpos = -1; + long long bsize = -1; + + while (pos < stop) + { + long len; + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume size + + if (id == 0x21) //Block ID + { + if (bpos < 0) //Block ID + { + bpos = pos; + bsize = size; + } + } + else if (id == 0x1B) //Duration ID + { + assert(size <= 8); + + duration = UnserializeUInt(pReader, pos, size); + assert(duration >= 0); //TODO + } + else if (id == 0x7B) //ReferenceBlock + { + assert(size <= 8); + const long size_ = static_cast(size); + + long long time; + + long status = UnserializeInt(pReader, pos, size_, time); + assert(status == 0); + if (status != 0) + return -1; + + if (time <= 0) //see note above + prev = time; + else //weird + next = time; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + assert(bpos >= 0); + assert(bsize >= 0); + + const long idx = m_entries_count; + + BlockEntry** const ppEntry = m_entries + idx; + BlockEntry*& pEntry = *ppEntry; + + pEntry = new (std::nothrow) BlockGroup( + this, + idx, + bpos, + bsize, + prev, + next, + duration, + discard_padding); + + if (pEntry == NULL) + return -1; //generic error + + BlockGroup* const p = static_cast(pEntry); + + const long status = p->Parse(); + + if (status == 0) //success + { + ++m_entries_count; + return 0; + } + + delete pEntry; + pEntry = 0; + + return status; +} + + + +long Cluster::CreateSimpleBlock( + long long st, + long long sz) +{ + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count >= 0); + assert(m_entries_count < m_entries_size); + + const long idx = m_entries_count; + + BlockEntry** const ppEntry = m_entries + idx; + BlockEntry*& pEntry = *ppEntry; + + pEntry = new (std::nothrow) SimpleBlock(this, idx, st, sz); + + if (pEntry == NULL) + return -1; //generic error + + SimpleBlock* const p = static_cast(pEntry); + + const long status = p->Parse(); + + if (status == 0) + { + ++m_entries_count; + return 0; + } + + delete pEntry; + pEntry = 0; + + return status; +} + + +long Cluster::GetFirst(const BlockEntry*& pFirst) const +{ + if (m_entries_count <= 0) + { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) //error + { + pFirst = NULL; + return status; + } + + if (m_entries_count <= 0) //empty cluster + { + pFirst = NULL; + return 0; + } + } + + assert(m_entries); + + pFirst = m_entries[0]; + assert(pFirst); + + return 0; //success +} + +long Cluster::GetLast(const BlockEntry*& pLast) const +{ + for (;;) + { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) //error + { + pLast = NULL; + return status; + } + + if (status > 0) //no new block + break; + } + + if (m_entries_count <= 0) + { + pLast = NULL; + return 0; + } + + assert(m_entries); + + const long idx = m_entries_count - 1; + + pLast = m_entries[idx]; + assert(pLast); + + return 0; +} + + +long Cluster::GetNext( + const BlockEntry* pCurr, + const BlockEntry*& pNext) const +{ + assert(pCurr); + assert(m_entries); + assert(m_entries_count > 0); + + size_t idx = pCurr->GetIndex(); + assert(idx < size_t(m_entries_count)); + assert(m_entries[idx] == pCurr); + + ++idx; + + if (idx >= size_t(m_entries_count)) + { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) //error + { + pNext = NULL; + return status; + } + + if (status > 0) + { + pNext = NULL; + return 0; + } + + assert(m_entries); + assert(m_entries_count > 0); + assert(idx < size_t(m_entries_count)); + } + + pNext = m_entries[idx]; + assert(pNext); + + return 0; +} + + +long Cluster::GetEntryCount() const +{ + return m_entries_count; +} + + +const BlockEntry* Cluster::GetEntry( + const Track* pTrack, + long long time_ns) const +{ + assert(pTrack); + + if (m_pSegment == NULL) //this is the special EOS cluster + return pTrack->GetEOS(); + +#if 0 + + LoadBlockEntries(); + + if ((m_entries == NULL) || (m_entries_count <= 0)) + return NULL; //return EOS here? + + const BlockEntry* pResult = pTrack->GetEOS(); + + BlockEntry** i = m_entries; + assert(i); + + BlockEntry** const j = i + m_entries_count; + + while (i != j) + { + const BlockEntry* const pEntry = *i++; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) + continue; + + if (pTrack->VetEntry(pEntry)) + { + if (time_ns < 0) //just want first candidate block + return pEntry; + + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + break; + + pResult = pEntry; + } + else if (time_ns >= 0) + { + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + break; + } + } + + return pResult; + +#else + + const BlockEntry* pResult = pTrack->GetEOS(); + + long index = 0; + + for (;;) + { + if (index >= m_entries_count) + { + long long pos; + long len; + + const long status = Parse(pos, len); + assert(status >= 0); + + if (status > 0) //completely parsed, and no more entries + return pResult; + + if (status < 0) //should never happen + return 0; + + assert(m_entries); + assert(index < m_entries_count); + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) + { + ++index; + continue; + } + + if (pTrack->VetEntry(pEntry)) + { + if (time_ns < 0) //just want first candidate block + return pEntry; + + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + return pResult; + + pResult = pEntry; //have a candidate + } + else if (time_ns >= 0) + { + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + return pResult; + } + + ++index; + } + +#endif +} + + +const BlockEntry* +Cluster::GetEntry( + const CuePoint& cp, + const CuePoint::TrackPosition& tp) const +{ + assert(m_pSegment); + +#if 0 + + LoadBlockEntries(); + + if (m_entries == NULL) + return NULL; + + const long long count = m_entries_count; + + if (count <= 0) + return NULL; + + const long long tc = cp.GetTimeCode(); + + if ((tp.m_block > 0) && (tp.m_block <= count)) + { + const size_t block = static_cast(tp.m_block); + const size_t index = block - 1; + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if ((pBlock->GetTrackNumber() == tp.m_track) && + (pBlock->GetTimeCode(this) == tc)) + { + return pEntry; + } + } + + const BlockEntry* const* i = m_entries; + const BlockEntry* const* const j = i + count; + + while (i != j) + { +#ifdef _DEBUG + const ptrdiff_t idx = i - m_entries; + idx; +#endif + + const BlockEntry* const pEntry = *i++; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != tp.m_track) + continue; + + const long long tc_ = pBlock->GetTimeCode(this); + assert(tc_ >= 0); + + if (tc_ < tc) + continue; + + if (tc_ > tc) + return NULL; + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(tp.m_track); + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return NULL; + + const long long type = pTrack->GetType(); + + if (type == 2) //audio + return pEntry; + + if (type != 1) //not video + return NULL; + + if (!pBlock->IsKey()) + return NULL; + + return pEntry; + } + + return NULL; + +#else + + const long long tc = cp.GetTimeCode(); + + if (tp.m_block > 0) + { + const long block = static_cast(tp.m_block); + const long index = block - 1; + + while (index >= m_entries_count) + { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) //TODO: can this happen? + return NULL; + + if (status > 0) //nothing remains to be parsed + return NULL; + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if ((pBlock->GetTrackNumber() == tp.m_track) && + (pBlock->GetTimeCode(this) == tc)) + { + return pEntry; + } + } + + long index = 0; + + for (;;) + { + if (index >= m_entries_count) + { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) //TODO: can this happen? + return NULL; + + if (status > 0) //nothing remains to be parsed + return NULL; + + assert(m_entries); + assert(index < m_entries_count); + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != tp.m_track) + { + ++index; + continue; + } + + const long long tc_ = pBlock->GetTimeCode(this); + + if (tc_ < tc) + { + ++index; + continue; + } + + if (tc_ > tc) + return NULL; + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(tp.m_track); + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return NULL; + + const long long type = pTrack->GetType(); + + if (type == 2) //audio + return pEntry; + + if (type != 1) //not video + return NULL; + + if (!pBlock->IsKey()) + return NULL; + + return pEntry; + } + +#endif + +} + + +#if 0 +const BlockEntry* Cluster::GetMaxKey(const VideoTrack* pTrack) const +{ + assert(pTrack); + + if (m_pSegment == NULL) //EOS + return pTrack->GetEOS(); + + LoadBlockEntries(); + + if ((m_entries == NULL) || (m_entries_count <= 0)) + return pTrack->GetEOS(); + + BlockEntry** i = m_entries + m_entries_count; + BlockEntry** const j = m_entries; + + while (i != j) + { + const BlockEntry* const pEntry = *--i; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) + continue; + + if (pBlock->IsKey()) + return pEntry; + } + + return pTrack->GetEOS(); //no satisfactory block found +} +#endif + + +BlockEntry::BlockEntry(Cluster* p, long idx) : + m_pCluster(p), + m_index(idx) +{ +} + + +BlockEntry::~BlockEntry() +{ +} + + +bool BlockEntry::EOS() const +{ + return (GetKind() == kBlockEOS); +} + + +const Cluster* BlockEntry::GetCluster() const +{ + return m_pCluster; +} + + +long BlockEntry::GetIndex() const +{ + return m_index; +} + + +SimpleBlock::SimpleBlock( + Cluster* pCluster, + long idx, + long long start, + long long size) : + BlockEntry(pCluster, idx), + m_block(start, size, 0) +{ +} + + +long SimpleBlock::Parse() +{ + return m_block.Parse(m_pCluster); +} + + +BlockEntry::Kind SimpleBlock::GetKind() const +{ + return kBlockSimple; +} + + +const Block* SimpleBlock::GetBlock() const +{ + return &m_block; +} + + +BlockGroup::BlockGroup( + Cluster* pCluster, + long idx, + long long block_start, + long long block_size, + long long prev, + long long next, + long long duration, + long long discard_padding) : + BlockEntry(pCluster, idx), + m_block(block_start, block_size, discard_padding), + m_prev(prev), + m_next(next), + m_duration(duration) +{ +} + + +long BlockGroup::Parse() +{ + const long status = m_block.Parse(m_pCluster); + + if (status) + return status; + + m_block.SetKey((m_prev > 0) && (m_next <= 0)); + + return 0; +} + + +#if 0 +void BlockGroup::ParseBlock(long long start, long long size) +{ + IMkvReader* const pReader = m_pCluster->m_pSegment->m_pReader; + + Block* const pBlock = new Block(start, size, pReader); + assert(pBlock); //TODO + + //TODO: the Matroska spec says you have multiple blocks within the + //same block group, with blocks ranked by priority (the flag bits). + + assert(m_pBlock == NULL); + m_pBlock = pBlock; +} +#endif + + +BlockEntry::Kind BlockGroup::GetKind() const +{ + return kBlockGroup; +} + + +const Block* BlockGroup::GetBlock() const +{ + return &m_block; +} + + +long long BlockGroup::GetPrevTimeCode() const +{ + return m_prev; +} + + +long long BlockGroup::GetNextTimeCode() const +{ + return m_next; +} + +long long BlockGroup::GetDurationTimeCode() const +{ + return m_duration; +} + +Block::Block(long long start, long long size_, long long discard_padding) : + m_start(start), + m_size(size_), + m_track(0), + m_timecode(-1), + m_flags(0), + m_frames(NULL), + m_frame_count(-1), + m_discard_padding(discard_padding) +{ +} + + +Block::~Block() +{ + delete[] m_frames; +} + + +long Block::Parse(const Cluster* pCluster) +{ + if (pCluster == NULL) + return -1; + + if (pCluster->m_pSegment == NULL) + return -1; + + assert(m_start >= 0); + assert(m_size >= 0); + assert(m_track <= 0); + assert(m_frames == NULL); + assert(m_frame_count <= 0); + + long long pos = m_start; + const long long stop = m_start + m_size; + + long len; + + IMkvReader* const pReader = pCluster->m_pSegment->m_pReader; + + m_track = ReadUInt(pReader, pos, len); + + if (m_track <= 0) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume track number + + if ((stop - pos) < 2) + return E_FILE_FORMAT_INVALID; + + long status; + long long value; + + status = UnserializeInt(pReader, pos, 2, value); + + if (status) + return E_FILE_FORMAT_INVALID; + + if (value < SHRT_MIN) + return E_FILE_FORMAT_INVALID; + + if (value > SHRT_MAX) + return E_FILE_FORMAT_INVALID; + + m_timecode = static_cast(value); + + pos += 2; + + if ((stop - pos) <= 0) + return E_FILE_FORMAT_INVALID; + + status = pReader->Read(pos, 1, &m_flags); + + if (status) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(m_flags & 0x06) >> 1; + + ++pos; //consume flags byte + + if (lacing == 0) //no lacing + { + if (pos > stop) + return E_FILE_FORMAT_INVALID; + + m_frame_count = 1; + m_frames = new Frame[m_frame_count]; + + Frame& f = m_frames[0]; + f.pos = pos; + + const long long frame_size = stop - pos; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + f.len = static_cast(frame_size); + + return 0; //success + } + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + unsigned char biased_count; + + status = pReader->Read(pos, 1, &biased_count); + + if (status) + return E_FILE_FORMAT_INVALID; + + ++pos; //consume frame count + assert(pos <= stop); + + m_frame_count = int(biased_count) + 1; + + m_frames = new Frame[m_frame_count]; + assert(m_frames); + + if (lacing == 1) //Xiph + { + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + long size = 0; + int frame_count = m_frame_count; + + while (frame_count > 1) + { + long frame_size = 0; + + for (;;) + { + unsigned char val; + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + status = pReader->Read(pos, 1, &val); + + if (status) + return E_FILE_FORMAT_INVALID; + + ++pos; //consume xiph size byte + + frame_size += val; + + if (val < 255) + break; + } + + Frame& f = *pf++; + assert(pf < pf_end); + + f.pos = 0; //patch later + + f.len = frame_size; + size += frame_size; //contribution of this frame + + --frame_count; + } + + assert(pf < pf_end); + assert(pos <= stop); + + { + Frame& f = *pf++; + + if (pf != pf_end) + return E_FILE_FORMAT_INVALID; + + f.pos = 0; //patch later + + const long long total_size = stop - pos; + + if (total_size < size) + return E_FILE_FORMAT_INVALID; + + const long long frame_size = total_size - size; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + f.len = static_cast(frame_size); + } + + pf = m_frames; + while (pf != pf_end) + { + Frame& f = *pf++; + assert((pos + f.len) <= stop); + + f.pos = pos; + pos += f.len; + } + + assert(pos == stop); + } + else if (lacing == 2) //fixed-size lacing + { + const long long total_size = stop - pos; + + if ((total_size % m_frame_count) != 0) + return E_FILE_FORMAT_INVALID; + + const long long frame_size = total_size / m_frame_count; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + while (pf != pf_end) + { + assert((pos + frame_size) <= stop); + + Frame& f = *pf++; + + f.pos = pos; + f.len = static_cast(frame_size); + + pos += frame_size; + } + + assert(pos == stop); + } + else + { + assert(lacing == 3); //EBML lacing + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + long size = 0; + int frame_count = m_frame_count; + + long long frame_size = ReadUInt(pReader, pos, len); + + if (frame_size < 0) + return E_FILE_FORMAT_INVALID; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume length of size of first frame + + if ((pos + frame_size) > stop) + return E_FILE_FORMAT_INVALID; + + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + { + Frame& curr = *pf; + + curr.pos = 0; //patch later + + curr.len = static_cast(frame_size); + size += curr.len; //contribution of this frame + } + + --frame_count; + + while (frame_count > 1) + { + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + + const Frame& prev = *pf++; + assert(prev.len == frame_size); + if (prev.len != frame_size) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + + Frame& curr = *pf; + + curr.pos = 0; //patch later + + const long long delta_size_ = ReadUInt(pReader, pos, len); + + if (delta_size_ < 0) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume length of (delta) size + assert(pos <= stop); + + const int exp = 7*len - 1; + const long long bias = (1LL << exp) - 1LL; + const long long delta_size = delta_size_ - bias; + + frame_size += delta_size; + + if (frame_size < 0) + return E_FILE_FORMAT_INVALID; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + curr.len = static_cast(frame_size); + size += curr.len; //contribution of this frame + + --frame_count; + } + + { + assert(pos <= stop); + assert(pf < pf_end); + + const Frame& prev = *pf++; + assert(prev.len == frame_size); + if (prev.len != frame_size) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + + Frame& curr = *pf++; + assert(pf == pf_end); + + curr.pos = 0; //patch later + + const long long total_size = stop - pos; + + if (total_size < size) + return E_FILE_FORMAT_INVALID; + + frame_size = total_size - size; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + curr.len = static_cast(frame_size); + } + + pf = m_frames; + while (pf != pf_end) + { + Frame& f = *pf++; + assert((pos + f.len) <= stop); + + f.pos = pos; + pos += f.len; + } + + assert(pos == stop); + } + + return 0; //success +} + + +long long Block::GetTimeCode(const Cluster* pCluster) const +{ + if (pCluster == 0) + return m_timecode; + + const long long tc0 = pCluster->GetTimeCode(); + assert(tc0 >= 0); + + const long long tc = tc0 + m_timecode; + + return tc; //unscaled timecode units +} + + +long long Block::GetTime(const Cluster* pCluster) const +{ + assert(pCluster); + + const long long tc = GetTimeCode(pCluster); + + const Segment* const pSegment = pCluster->m_pSegment; + const SegmentInfo* const pInfo = pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long ns = tc * scale; + + return ns; +} + + +long long Block::GetTrackNumber() const +{ + return m_track; +} + + +bool Block::IsKey() const +{ + return ((m_flags & static_cast(1 << 7)) != 0); +} + + +void Block::SetKey(bool bKey) +{ + if (bKey) + m_flags |= static_cast(1 << 7); + else + m_flags &= 0x7F; +} + + +bool Block::IsInvisible() const +{ + return bool(int(m_flags & 0x08) != 0); +} + + +Block::Lacing Block::GetLacing() const +{ + const int value = int(m_flags & 0x06) >> 1; + return static_cast(value); +} + + +int Block::GetFrameCount() const +{ + return m_frame_count; +} + + +const Block::Frame& Block::GetFrame(int idx) const +{ + assert(idx >= 0); + assert(idx < m_frame_count); + + const Frame& f = m_frames[idx]; + assert(f.pos > 0); + assert(f.len > 0); + + return f; +} + + +long Block::Frame::Read(IMkvReader* pReader, unsigned char* buf) const +{ + assert(pReader); + assert(buf); + + const long status = pReader->Read(pos, len, buf); + return status; +} + +long long Block::GetDiscardPadding() const +{ + return m_discard_padding; +} + +} //end namespace mkvparser diff --git a/third_party/libwebm/mkvparser.hpp b/third_party/libwebm/mkvparser.hpp new file mode 100644 index 000000000..7184d267a --- /dev/null +++ b/third_party/libwebm/mkvparser.hpp @@ -0,0 +1,1079 @@ +// 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. + +#ifndef MKVPARSER_HPP +#define MKVPARSER_HPP + +#include +#include +#include + +namespace mkvparser +{ + +const int E_FILE_FORMAT_INVALID = -2; +const int E_BUFFER_NOT_FULL = -3; + +class IMkvReader +{ +public: + virtual int Read(long long pos, long len, unsigned char* buf) = 0; + virtual int Length(long long* total, long long* available) = 0; +protected: + virtual ~IMkvReader(); +}; + +long long GetUIntLength(IMkvReader*, long long, long&); +long long ReadUInt(IMkvReader*, long long, long&); +long long UnserializeUInt(IMkvReader*, long long pos, long long size); + +long UnserializeFloat(IMkvReader*, long long pos, long long size, double&); +long UnserializeInt(IMkvReader*, long long pos, long len, long long& result); + +long UnserializeString( + IMkvReader*, + long long pos, + long long size, + char*& str); + +long ParseElementHeader( + IMkvReader* pReader, + long long& pos, //consume id and size fields + long long stop, //if you know size of element's parent + long long& id, + long long& size); + +bool Match(IMkvReader*, long long&, unsigned long, long long&); +bool Match(IMkvReader*, long long&, unsigned long, unsigned char*&, size_t&); + +void GetVersion(int& major, int& minor, int& build, int& revision); + +struct EBMLHeader +{ + EBMLHeader(); + ~EBMLHeader(); + long long m_version; + long long m_readVersion; + long long m_maxIdLength; + long long m_maxSizeLength; + char* m_docType; + long long m_docTypeVersion; + long long m_docTypeReadVersion; + + long long Parse(IMkvReader*, long long&); + void Init(); +}; + + +class Segment; +class Track; +class Cluster; + +class Block +{ + Block(const Block&); + Block& operator=(const Block&); + +public: + const long long m_start; + const long long m_size; + + Block(long long start, long long size, long long discard_padding); + ~Block(); + + long Parse(const Cluster*); + + long long GetTrackNumber() const; + long long GetTimeCode(const Cluster*) const; //absolute, but not scaled + long long GetTime(const Cluster*) const; //absolute, and scaled (ns) + bool IsKey() const; + void SetKey(bool); + bool IsInvisible() const; + + enum Lacing { kLacingNone, kLacingXiph, kLacingFixed, kLacingEbml }; + Lacing GetLacing() const; + + int GetFrameCount() const; //to index frames: [0, count) + + struct Frame + { + long long pos; //absolute offset + long len; + + long Read(IMkvReader*, unsigned char*) const; + }; + + const Frame& GetFrame(int frame_index) const; + + long long GetDiscardPadding() const; + +private: + long long m_track; //Track::Number() + short m_timecode; //relative to cluster + unsigned char m_flags; + + Frame* m_frames; + int m_frame_count; + +protected: + const long long m_discard_padding; +}; + + +class BlockEntry +{ + BlockEntry(const BlockEntry&); + BlockEntry& operator=(const BlockEntry&); + +protected: + BlockEntry(Cluster*, long index); + +public: + virtual ~BlockEntry(); + + bool EOS() const; + const Cluster* GetCluster() const; + long GetIndex() const; + virtual const Block* GetBlock() const = 0; + + enum Kind { kBlockEOS, kBlockSimple, kBlockGroup }; + virtual Kind GetKind() const = 0; + +protected: + Cluster* const m_pCluster; + const long m_index; + +}; + + +class SimpleBlock : public BlockEntry +{ + SimpleBlock(const SimpleBlock&); + SimpleBlock& operator=(const SimpleBlock&); + +public: + SimpleBlock(Cluster*, long index, long long start, long long size); + long Parse(); + + Kind GetKind() const; + const Block* GetBlock() const; + +protected: + Block m_block; + +}; + + +class BlockGroup : public BlockEntry +{ + BlockGroup(const BlockGroup&); + BlockGroup& operator=(const BlockGroup&); + +public: + BlockGroup( + Cluster*, + long index, + long long block_start, //absolute pos of block's payload + long long block_size, //size of block's payload + long long prev, + long long next, + long long duration, + long long discard_padding); + + long Parse(); + + Kind GetKind() const; + const Block* GetBlock() const; + + long long GetPrevTimeCode() const; //relative to block's time + long long GetNextTimeCode() const; //as above + long long GetDurationTimeCode() const; + +private: + Block m_block; + const long long m_prev; + const long long m_next; + const long long m_duration; +}; + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +// Elements used to describe if the track data has been encrypted or +// compressed with zlib or header stripping. +class ContentEncoding { +public: + enum { + kCTR = 1 + }; + + ContentEncoding(); + ~ContentEncoding(); + + // ContentCompression element names + struct ContentCompression { + ContentCompression(); + ~ContentCompression(); + + unsigned long long algo; + unsigned char* settings; + long long settings_len; + }; + + // ContentEncAESSettings element names + struct ContentEncAESSettings { + ContentEncAESSettings() : cipher_mode(kCTR) {} + ~ContentEncAESSettings() {} + + unsigned long long cipher_mode; + }; + + // ContentEncryption element names + struct ContentEncryption { + ContentEncryption(); + ~ContentEncryption(); + + unsigned long long algo; + unsigned char* key_id; + long long key_id_len; + unsigned char* signature; + long long signature_len; + unsigned char* sig_key_id; + long long sig_key_id_len; + unsigned long long sig_algo; + unsigned long long sig_hash_algo; + + ContentEncAESSettings aes_settings; + }; + + // Returns ContentCompression represented by |idx|. Returns NULL if |idx| + // is out of bounds. + const ContentCompression* GetCompressionByIndex(unsigned long idx) const; + + // Returns number of ContentCompression elements in this ContentEncoding + // element. + unsigned long GetCompressionCount() const; + + // Parses the ContentCompression element from |pReader|. |start| is the + // starting offset of the ContentCompression payload. |size| is the size in + // bytes of the ContentCompression payload. |compression| is where the parsed + // values will be stored. + long ParseCompressionEntry(long long start, + long long size, + IMkvReader* pReader, + ContentCompression* compression); + + // Returns ContentEncryption represented by |idx|. Returns NULL if |idx| + // is out of bounds. + const ContentEncryption* GetEncryptionByIndex(unsigned long idx) const; + + // Returns number of ContentEncryption elements in this ContentEncoding + // element. + unsigned long GetEncryptionCount() const; + + // Parses the ContentEncAESSettings element from |pReader|. |start| is the + // starting offset of the ContentEncAESSettings payload. |size| is the + // size in bytes of the ContentEncAESSettings payload. |encryption| is + // where the parsed values will be stored. + long ParseContentEncAESSettingsEntry(long long start, + long long size, + IMkvReader* pReader, + ContentEncAESSettings* aes); + + // Parses the ContentEncoding element from |pReader|. |start| is the + // starting offset of the ContentEncoding payload. |size| is the size in + // bytes of the ContentEncoding payload. Returns true on success. + long ParseContentEncodingEntry(long long start, + long long size, + IMkvReader* pReader); + + // Parses the ContentEncryption element from |pReader|. |start| is the + // starting offset of the ContentEncryption payload. |size| is the size in + // bytes of the ContentEncryption payload. |encryption| is where the parsed + // values will be stored. + long ParseEncryptionEntry(long long start, + long long size, + IMkvReader* pReader, + ContentEncryption* encryption); + + unsigned long long encoding_order() const { return encoding_order_; } + unsigned long long encoding_scope() const { return encoding_scope_; } + unsigned long long encoding_type() const { return encoding_type_; } + +private: + // Member variables for list of ContentCompression elements. + ContentCompression** compression_entries_; + ContentCompression** compression_entries_end_; + + // Member variables for list of ContentEncryption elements. + ContentEncryption** encryption_entries_; + ContentEncryption** encryption_entries_end_; + + // ContentEncoding element names + unsigned long long encoding_order_; + unsigned long long encoding_scope_; + unsigned long long encoding_type_; + + // LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding); + ContentEncoding(const ContentEncoding&); + ContentEncoding& operator=(const ContentEncoding&); +}; + +class Track +{ + Track(const Track&); + Track& operator=(const Track&); + +public: + class Info; + static long Create( + Segment*, + const Info&, + long long element_start, + long long element_size, + Track*&); + + enum Type { + kVideo = 1, + kAudio = 2, + kSubtitle = 0x11, + kMetadata = 0x21 + }; + + Segment* const m_pSegment; + const long long m_element_start; + const long long m_element_size; + virtual ~Track(); + + long GetType() const; + long GetNumber() const; + unsigned long long GetUid() const; + const char* GetNameAsUTF8() const; + const char* GetLanguage() const; + const char* GetCodecNameAsUTF8() const; + const char* GetCodecId() const; + const unsigned char* GetCodecPrivate(size_t&) const; + bool GetLacing() const; + unsigned long long GetDefaultDuration() const; + unsigned long long GetCodecDelay() const; + unsigned long long GetSeekPreRoll() const; + + const BlockEntry* GetEOS() const; + + struct Settings + { + long long start; + long long size; + }; + + class Info + { + public: + Info(); + ~Info(); + int Copy(Info&) const; + void Clear(); + long type; + long number; + unsigned long long uid; + unsigned long long defaultDuration; + unsigned long long codecDelay; + unsigned long long seekPreRoll; + char* nameAsUTF8; + char* language; + char* codecId; + char* codecNameAsUTF8; + unsigned char* codecPrivate; + size_t codecPrivateSize; + bool lacing; + Settings settings; + + private: + Info(const Info&); + Info& operator=(const Info&); + int CopyStr(char* Info::*str, Info&) const; + }; + + long GetFirst(const BlockEntry*&) const; + long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const; + virtual bool VetEntry(const BlockEntry*) const; + virtual long Seek(long long time_ns, const BlockEntry*&) const; + + const ContentEncoding* GetContentEncodingByIndex(unsigned long idx) const; + unsigned long GetContentEncodingCount() const; + + long ParseContentEncodingsEntry(long long start, long long size); + +protected: + Track( + Segment*, + long long element_start, + long long element_size); + + Info m_info; + + class EOSBlock : public BlockEntry + { + public: + EOSBlock(); + + Kind GetKind() const; + const Block* GetBlock() const; + }; + + EOSBlock m_eos; + +private: + ContentEncoding** content_encoding_entries_; + ContentEncoding** content_encoding_entries_end_; +}; + + +class VideoTrack : public Track +{ + VideoTrack(const VideoTrack&); + VideoTrack& operator=(const VideoTrack&); + + VideoTrack( + Segment*, + long long element_start, + long long element_size); + +public: + static long Parse( + Segment*, + const Info&, + long long element_start, + long long element_size, + VideoTrack*&); + + long long GetWidth() const; + long long GetHeight() const; + double GetFrameRate() const; + + bool VetEntry(const BlockEntry*) const; + long Seek(long long time_ns, const BlockEntry*&) const; + +private: + long long m_width; + long long m_height; + double m_rate; + +}; + + +class AudioTrack : public Track +{ + AudioTrack(const AudioTrack&); + AudioTrack& operator=(const AudioTrack&); + + AudioTrack( + Segment*, + long long element_start, + long long element_size); +public: + static long Parse( + Segment*, + const Info&, + long long element_start, + long long element_size, + AudioTrack*&); + + double GetSamplingRate() const; + long long GetChannels() const; + long long GetBitDepth() const; + +private: + double m_rate; + long long m_channels; + long long m_bitDepth; +}; + + +class Tracks +{ + Tracks(const Tracks&); + Tracks& operator=(const Tracks&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Tracks( + Segment*, + long long start, + long long size, + long long element_start, + long long element_size); + + ~Tracks(); + + long Parse(); + + unsigned long GetTracksCount() const; + + const Track* GetTrackByNumber(long tn) const; + const Track* GetTrackByIndex(unsigned long idx) const; + +private: + Track** m_trackEntries; + Track** m_trackEntriesEnd; + + long ParseTrackEntry( + long long payload_start, + long long payload_size, + long long element_start, + long long element_size, + Track*&) const; + +}; + + +class Chapters +{ + Chapters(const Chapters&); + Chapters& operator=(const Chapters&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Chapters( + Segment*, + long long payload_start, + long long payload_size, + long long element_start, + long long element_size); + + ~Chapters(); + + long Parse(); + + class Atom; + class Edition; + + class Display + { + friend class Atom; + Display(); + Display(const Display&); + ~Display(); + Display& operator=(const Display&); + public: + const char* GetString() const; + const char* GetLanguage() const; + const char* GetCountry() const; + private: + void Init(); + void ShallowCopy(Display&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + char* m_string; + char* m_language; + char* m_country; + }; + + class Atom + { + friend class Edition; + Atom(); + Atom(const Atom&); + ~Atom(); + Atom& operator=(const Atom&); + public: + unsigned long long GetUID() const; + const char* GetStringUID() const; + + long long GetStartTimecode() const; + long long GetStopTimecode() const; + + long long GetStartTime(const Chapters*) const; + long long GetStopTime(const Chapters*) const; + + int GetDisplayCount() const; + const Display* GetDisplay(int index) const; + private: + void Init(); + void ShallowCopy(Atom&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + static long long GetTime(const Chapters*, long long timecode); + + long ParseDisplay(IMkvReader*, long long pos, long long size); + bool ExpandDisplaysArray(); + + char* m_string_uid; + unsigned long long m_uid; + long long m_start_timecode; + long long m_stop_timecode; + + Display* m_displays; + int m_displays_size; + int m_displays_count; + }; + + class Edition + { + friend class Chapters; + Edition(); + Edition(const Edition&); + ~Edition(); + Edition& operator=(const Edition&); + public: + int GetAtomCount() const; + const Atom* GetAtom(int index) const; + private: + void Init(); + void ShallowCopy(Edition&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + long ParseAtom(IMkvReader*, long long pos, long long size); + bool ExpandAtomsArray(); + + Atom* m_atoms; + int m_atoms_size; + int m_atoms_count; + }; + + int GetEditionCount() const; + const Edition* GetEdition(int index) const; + +private: + long ParseEdition(long long pos, long long size); + bool ExpandEditionsArray(); + + Edition* m_editions; + int m_editions_size; + int m_editions_count; + +}; + + +class SegmentInfo +{ + SegmentInfo(const SegmentInfo&); + SegmentInfo& operator=(const SegmentInfo&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + SegmentInfo( + Segment*, + long long start, + long long size, + long long element_start, + long long element_size); + + ~SegmentInfo(); + + long Parse(); + + long long GetTimeCodeScale() const; + long long GetDuration() const; //scaled + const char* GetMuxingAppAsUTF8() const; + const char* GetWritingAppAsUTF8() const; + const char* GetTitleAsUTF8() const; + +private: + long long m_timecodeScale; + double m_duration; + char* m_pMuxingAppAsUTF8; + char* m_pWritingAppAsUTF8; + char* m_pTitleAsUTF8; +}; + + +class SeekHead +{ + SeekHead(const SeekHead&); + SeekHead& operator=(const SeekHead&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + SeekHead( + Segment*, + long long start, + long long size, + long long element_start, + long long element_size); + + ~SeekHead(); + + long Parse(); + + struct Entry + { + //the SeekHead entry payload + long long id; + long long pos; + + //absolute pos of SeekEntry ID + long long element_start; + + //SeekEntry ID size + size size + payload + long long element_size; + }; + + int GetCount() const; + const Entry* GetEntry(int idx) const; + + struct VoidElement + { + //absolute pos of Void ID + long long element_start; + + //ID size + size size + payload size + long long element_size; + }; + + int GetVoidElementCount() const; + const VoidElement* GetVoidElement(int idx) const; + +private: + Entry* m_entries; + int m_entry_count; + + VoidElement* m_void_elements; + int m_void_element_count; + + static bool ParseEntry( + IMkvReader*, + long long pos, //payload + long long size, + Entry*); + +}; + +class Cues; +class CuePoint +{ + friend class Cues; + + CuePoint(long, long long); + ~CuePoint(); + + CuePoint(const CuePoint&); + CuePoint& operator=(const CuePoint&); + +public: + long long m_element_start; + long long m_element_size; + + void Load(IMkvReader*); + + long long GetTimeCode() const; //absolute but unscaled + long long GetTime(const Segment*) const; //absolute and scaled (ns units) + + struct TrackPosition + { + long long m_track; + long long m_pos; //of cluster + long long m_block; + //codec_state //defaults to 0 + //reference = clusters containing req'd referenced blocks + // reftime = timecode of the referenced block + + void Parse(IMkvReader*, long long, long long); + }; + + const TrackPosition* Find(const Track*) const; + +private: + const long m_index; + long long m_timecode; + TrackPosition* m_track_positions; + size_t m_track_positions_count; + +}; + + +class Cues +{ + friend class Segment; + + Cues( + Segment*, + long long start, + long long size, + long long element_start, + long long element_size); + ~Cues(); + + Cues(const Cues&); + Cues& operator=(const Cues&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + bool Find( //lower bound of time_ns + long long time_ns, + const Track*, + const CuePoint*&, + const CuePoint::TrackPosition*&) const; + +#if 0 + bool FindNext( //upper_bound of time_ns + long long time_ns, + const Track*, + const CuePoint*&, + const CuePoint::TrackPosition*&) const; +#endif + + const CuePoint* GetFirst() const; + const CuePoint* GetLast() const; + const CuePoint* GetNext(const CuePoint*) const; + + const BlockEntry* GetBlock( + const CuePoint*, + const CuePoint::TrackPosition*) const; + + bool LoadCuePoint() const; + long GetCount() const; //loaded only + //long GetTotal() const; //loaded + preloaded + bool DoneParsing() const; + +private: + void Init() const; + void PreloadCuePoint(long&, long long) const; + + mutable CuePoint** m_cue_points; + mutable long m_count; + mutable long m_preload_count; + mutable long long m_pos; + +}; + + +class Cluster +{ + friend class Segment; + + Cluster(const Cluster&); + Cluster& operator=(const Cluster&); + +public: + Segment* const m_pSegment; + +public: + static Cluster* Create( + Segment*, + long index, //index in segment + long long off); //offset relative to segment + //long long element_size); + + Cluster(); //EndOfStream + ~Cluster(); + + bool EOS() const; + + long long GetTimeCode() const; //absolute, but not scaled + long long GetTime() const; //absolute, and scaled (nanosecond units) + long long GetFirstTime() const; //time (ns) of first (earliest) block + long long GetLastTime() const; //time (ns) of last (latest) block + + long GetFirst(const BlockEntry*&) const; + long GetLast(const BlockEntry*&) const; + long GetNext(const BlockEntry* curr, const BlockEntry*& next) const; + + const BlockEntry* GetEntry(const Track*, long long ns = -1) const; + const BlockEntry* GetEntry( + const CuePoint&, + const CuePoint::TrackPosition&) const; + //const BlockEntry* GetMaxKey(const VideoTrack*) const; + +// static bool HasBlockEntries(const Segment*, long long); + + static long HasBlockEntries( + const Segment*, + long long idoff, + long long& pos, + long& size); + + long GetEntryCount() const; + + long Load(long long& pos, long& size) const; + + long Parse(long long& pos, long& size) const; + long GetEntry(long index, const mkvparser::BlockEntry*&) const; + +protected: + Cluster( + Segment*, + long index, + long long element_start); + //long long element_size); + +public: + const long long m_element_start; + long long GetPosition() const; //offset relative to segment + + long GetIndex() const; + long long GetElementSize() const; + //long long GetPayloadSize() const; + + //long long Unparsed() const; + +private: + long m_index; + mutable long long m_pos; + //mutable long long m_size; + mutable long long m_element_size; + mutable long long m_timecode; + mutable BlockEntry** m_entries; + mutable long m_entries_size; + mutable long m_entries_count; + + long ParseSimpleBlock(long long, long long&, long&); + long ParseBlockGroup(long long, long long&, long&); + + long CreateBlock(long long id, long long pos, long long size, + long long discard_padding); + long CreateBlockGroup(long long start_offset, long long size, + long long discard_padding); + long CreateSimpleBlock(long long, long long); + +}; + + +class Segment +{ + friend class Cues; + friend class Track; + friend class VideoTrack; + + Segment(const Segment&); + Segment& operator=(const Segment&); + +private: + Segment( + IMkvReader*, + long long elem_start, + //long long elem_size, + long long pos, + long long size); + +public: + IMkvReader* const m_pReader; + const long long m_element_start; + //const long long m_element_size; + const long long m_start; //posn of segment payload + const long long m_size; //size of segment payload + Cluster m_eos; //TODO: make private? + + static long long CreateInstance(IMkvReader*, long long, Segment*&); + ~Segment(); + + long Load(); //loads headers and all clusters + + //for incremental loading + //long long Unparsed() const; + bool DoneParsing() const; + long long ParseHeaders(); //stops when first cluster is found + //long FindNextCluster(long long& pos, long& size) const; + long LoadCluster(long long& pos, long& size); //load one cluster + long LoadCluster(); + + long ParseNext( + const Cluster* pCurr, + const Cluster*& pNext, + long long& pos, + long& size); + +#if 0 + //This pair parses one cluster, but only changes the state of the + //segment object when the cluster is actually added to the index. + long ParseCluster(long long& cluster_pos, long long& new_pos) const; + bool AddCluster(long long cluster_pos, long long new_pos); +#endif + + const SeekHead* GetSeekHead() const; + const Tracks* GetTracks() const; + const SegmentInfo* GetInfo() const; + const Cues* GetCues() const; + const Chapters* GetChapters() const; + + long long GetDuration() const; + + unsigned long GetCount() const; + const Cluster* GetFirst() const; + const Cluster* GetLast() const; + const Cluster* GetNext(const Cluster*); + + const Cluster* FindCluster(long long time_nanoseconds) const; + //const BlockEntry* Seek(long long time_nanoseconds, const Track*) const; + + const Cluster* FindOrPreloadCluster(long long pos); + + long ParseCues( + long long cues_off, //offset relative to start of segment + long long& parse_pos, + long& parse_len); + +private: + + long long m_pos; //absolute file posn; what has been consumed so far + Cluster* m_pUnknownSize; + + SeekHead* m_pSeekHead; + SegmentInfo* m_pInfo; + Tracks* m_pTracks; + Cues* m_pCues; + Chapters* m_pChapters; + Cluster** m_clusters; + long m_clusterCount; //number of entries for which m_index >= 0 + long m_clusterPreloadCount; //number of entries for which m_index < 0 + long m_clusterSize; //array size + + long DoLoadCluster(long long&, long&); + long DoLoadClusterUnknownSize(long long&, long&); + long DoParseNext(const Cluster*&, long long&, long&); + + void AppendCluster(Cluster*); + void PreloadCluster(Cluster*, ptrdiff_t); + + //void ParseSeekHead(long long pos, long long size); + //void ParseSeekEntry(long long pos, long long size); + //void ParseCues(long long); + + const BlockEntry* GetBlock( + const CuePoint&, + const CuePoint::TrackPosition&); + +}; + +} //end namespace mkvparser + +inline long mkvparser::Segment::LoadCluster() +{ + long long pos; + long size; + + return LoadCluster(pos, size); +} + +#endif //MKVPARSER_HPP diff --git a/third_party/libwebm/mkvreader.cpp b/third_party/libwebm/mkvreader.cpp new file mode 100644 index 000000000..cb3567f1a --- /dev/null +++ b/third_party/libwebm/mkvreader.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2010 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 "mkvreader.hpp" + +#include + +namespace mkvparser +{ + +MkvReader::MkvReader() : + m_file(NULL) +{ +} + +MkvReader::~MkvReader() +{ + Close(); +} + +int MkvReader::Open(const char* fileName) +{ + if (fileName == NULL) + return -1; + + if (m_file) + return -1; + +#ifdef _MSC_VER + const errno_t e = fopen_s(&m_file, fileName, "rb"); + + if (e) + return -1; //error +#else + m_file = fopen(fileName, "rb"); + + if (m_file == NULL) + return -1; +#endif + +#ifdef _MSC_VER + int status = _fseeki64(m_file, 0L, SEEK_END); + + if (status) + return -1; //error + + m_length = _ftelli64(m_file); +#else + fseek(m_file, 0L, SEEK_END); + m_length = ftell(m_file); +#endif + assert(m_length >= 0); + +#ifdef _MSC_VER + status = _fseeki64(m_file, 0L, SEEK_SET); + + if (status) + return -1; //error +#else + fseek(m_file, 0L, SEEK_SET); +#endif + + return 0; +} + +void MkvReader::Close() +{ + if (m_file != NULL) + { + fclose(m_file); + m_file = NULL; + } +} + +int MkvReader::Length(long long* total, long long* available) +{ + if (m_file == NULL) + return -1; + + if (total) + *total = m_length; + + if (available) + *available = m_length; + + return 0; +} + +int MkvReader::Read(long long offset, long len, unsigned char* buffer) +{ + if (m_file == NULL) + return -1; + + if (offset < 0) + return -1; + + if (len < 0) + return -1; + + if (len == 0) + return 0; + + if (offset >= m_length) + return -1; + +#ifdef _MSC_VER + const int status = _fseeki64(m_file, offset, SEEK_SET); + + if (status) + return -1; //error +#else + fseek(m_file, offset, SEEK_SET); +#endif + + const size_t size = fread(buffer, 1, len, m_file); + + if (size < size_t(len)) + return -1; //error + + return 0; //success +} + +} //end namespace mkvparser diff --git a/third_party/libwebm/mkvreader.hpp b/third_party/libwebm/mkvreader.hpp new file mode 100644 index 000000000..adcc29f47 --- /dev/null +++ b/third_party/libwebm/mkvreader.hpp @@ -0,0 +1,38 @@ +// Copyright (c) 2010 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. + +#ifndef MKVREADER_HPP +#define MKVREADER_HPP + +#include "mkvparser.hpp" +#include + +namespace mkvparser +{ + +class MkvReader : public IMkvReader +{ + MkvReader(const MkvReader&); + MkvReader& operator=(const MkvReader&); +public: + MkvReader(); + virtual ~MkvReader(); + + int Open(const char*); + void Close(); + + virtual int Read(long long position, long length, unsigned char* buffer); + virtual int Length(long long* total, long long* available); +private: + long long m_length; + FILE* m_file; +}; + +} //end namespace mkvparser + +#endif //MKVREADER_HPP diff --git a/third_party/libwebm/mkvwriter.cpp b/third_party/libwebm/mkvwriter.cpp new file mode 100644 index 000000000..8de89a4b2 --- /dev/null +++ b/third_party/libwebm/mkvwriter.cpp @@ -0,0 +1,97 @@ +// 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 "mkvwriter.hpp" + +#ifdef _MSC_VER +#include // for _SH_DENYWR +#endif + +#include + +namespace mkvmuxer { + +MkvWriter::MkvWriter() : file_(NULL), writer_owns_file_(true) { +} + +MkvWriter::MkvWriter(FILE* fp): file_(fp), writer_owns_file_(false) { +} + +MkvWriter::~MkvWriter() { + Close(); +} + +int32 MkvWriter::Write(const void* buffer, uint32 length) { + if (!file_) + return -1; + + if (length == 0) + return 0; + + if (buffer == NULL) + return -1; + + const size_t bytes_written = fwrite(buffer, 1, length, file_); + + return (bytes_written == length) ? 0 : -1; +} + +bool MkvWriter::Open(const char* filename) { + if (filename == NULL) + return false; + + if (file_) + return false; + +#ifdef _MSC_VER + file_ = _fsopen(filename, "wb", _SH_DENYWR); +#else + file_ = fopen(filename, "wb"); +#endif + if (file_ == NULL) + return false; + return true; +} + +void MkvWriter::Close() { + if (file_ && writer_owns_file_) { + fclose(file_); + } + file_ = NULL; +} + +int64 MkvWriter::Position() const { + if (!file_) + return 0; + +#ifdef _MSC_VER + return _ftelli64(file_); +#else + return ftell(file_); +#endif +} + +int32 MkvWriter::Position(int64 position) { + if (!file_) + return -1; + +#ifdef _MSC_VER + return _fseeki64(file_, position, SEEK_SET); +#else + return fseek(file_, position, SEEK_SET); +#endif +} + +bool MkvWriter::Seekable() const { + return true; +} + +void MkvWriter::ElementStartNotify(uint64, int64) { +} + +} // namespace mkvmuxer diff --git a/third_party/libwebm/mkvwriter.hpp b/third_party/libwebm/mkvwriter.hpp new file mode 100644 index 000000000..524e0f7ea --- /dev/null +++ b/third_party/libwebm/mkvwriter.hpp @@ -0,0 +1,51 @@ +// 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. + +#ifndef MKVWRITER_HPP +#define MKVWRITER_HPP + +#include + +#include "mkvmuxer.hpp" +#include "mkvmuxertypes.hpp" + +namespace mkvmuxer { + +// Default implementation of the IMkvWriter interface on Windows. +class MkvWriter : public IMkvWriter { + public: + MkvWriter(); + MkvWriter(FILE* fp); + virtual ~MkvWriter(); + + // IMkvWriter interface + virtual int64 Position() const; + virtual int32 Position(int64 position); + virtual bool Seekable() const; + virtual int32 Write(const void* buffer, uint32 length); + virtual void ElementStartNotify(uint64 element_id, int64 position); + + // Creates and opens a file for writing. |filename| is the name of the file + // to open. This function will overwrite the contents of |filename|. Returns + // true on success. + bool Open(const char* filename); + + // Closes an opened file. + void Close(); + + private: + // File handle to output file. + FILE* file_; + bool writer_owns_file_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(MkvWriter); +}; + +} //end namespace mkvmuxer + +#endif // MKVWRITER_HPP diff --git a/third_party/libwebm/webmids.hpp b/third_party/libwebm/webmids.hpp new file mode 100644 index 000000000..65fab960f --- /dev/null +++ b/third_party/libwebm/webmids.hpp @@ -0,0 +1,141 @@ +// 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. + +#ifndef WEBMIDS_HPP +#define WEBMIDS_HPP + +namespace mkvmuxer { + +enum MkvId { + kMkvEBML = 0x1A45DFA3, + kMkvEBMLVersion = 0x4286, + kMkvEBMLReadVersion = 0x42F7, + kMkvEBMLMaxIDLength = 0x42F2, + kMkvEBMLMaxSizeLength = 0x42F3, + kMkvDocType = 0x4282, + kMkvDocTypeVersion = 0x4287, + kMkvDocTypeReadVersion = 0x4285, + kMkvVoid = 0xEC, + kMkvSignatureSlot = 0x1B538667, + kMkvSignatureAlgo = 0x7E8A, + kMkvSignatureHash = 0x7E9A, + kMkvSignaturePublicKey = 0x7EA5, + kMkvSignature = 0x7EB5, + kMkvSignatureElements = 0x7E5B, + kMkvSignatureElementList = 0x7E7B, + kMkvSignedElement = 0x6532, + //segment + kMkvSegment = 0x18538067, + //Meta Seek Information + kMkvSeekHead = 0x114D9B74, + kMkvSeek = 0x4DBB, + kMkvSeekID = 0x53AB, + kMkvSeekPosition = 0x53AC, + //Segment Information + kMkvInfo = 0x1549A966, + kMkvTimecodeScale = 0x2AD7B1, + kMkvDuration = 0x4489, + kMkvDateUTC = 0x4461, + kMkvMuxingApp = 0x4D80, + kMkvWritingApp = 0x5741, + //Cluster + kMkvCluster = 0x1F43B675, + kMkvTimecode = 0xE7, + kMkvPrevSize = 0xAB, + kMkvBlockGroup = 0xA0, + kMkvBlock = 0xA1, + kMkvBlockDuration = 0x9B, + kMkvReferenceBlock = 0xFB, + kMkvLaceNumber = 0xCC, + kMkvSimpleBlock = 0xA3, + kMkvBlockAdditions = 0x75A1, + kMkvBlockMore = 0xA6, + kMkvBlockAddID = 0xEE, + kMkvBlockAdditional = 0xA5, + kMkvDiscardPadding = 0x75A2, + //Track + kMkvTracks = 0x1654AE6B, + kMkvTrackEntry = 0xAE, + kMkvTrackNumber = 0xD7, + kMkvTrackUID = 0x73C5, + kMkvTrackType = 0x83, + kMkvFlagEnabled = 0xB9, + kMkvFlagDefault = 0x88, + kMkvFlagForced = 0x55AA, + kMkvFlagLacing = 0x9C, + kMkvDefaultDuration = 0x23E383, + kMkvMaxBlockAdditionID = 0x55EE, + kMkvName = 0x536E, + kMkvLanguage = 0x22B59C, + kMkvCodecID = 0x86, + kMkvCodecPrivate = 0x63A2, + kMkvCodecName = 0x258688, + kMkvCodecDelay = 0x56AA, + kMkvSeekPreRoll = 0x56BB, + //video + kMkvVideo = 0xE0, + kMkvFlagInterlaced = 0x9A, + kMkvStereoMode = 0x53B8, + kMkvAlphaMode = 0x53C0, + kMkvPixelWidth = 0xB0, + kMkvPixelHeight = 0xBA, + kMkvPixelCropBottom = 0x54AA, + kMkvPixelCropTop = 0x54BB, + kMkvPixelCropLeft = 0x54CC, + kMkvPixelCropRight = 0x54DD, + kMkvDisplayWidth = 0x54B0, + kMkvDisplayHeight = 0x54BA, + kMkvDisplayUnit = 0x54B2, + kMkvAspectRatioType = 0x54B3, + kMkvFrameRate = 0x2383E3, + //end video + //audio + kMkvAudio = 0xE1, + kMkvSamplingFrequency = 0xB5, + kMkvOutputSamplingFrequency = 0x78B5, + kMkvChannels = 0x9F, + kMkvBitDepth = 0x6264, + //end audio + //ContentEncodings + kMkvContentEncodings = 0x6D80, + kMkvContentEncoding = 0x6240, + kMkvContentEncodingOrder = 0x5031, + kMkvContentEncodingScope = 0x5032, + kMkvContentEncodingType = 0x5033, + kMkvContentEncryption = 0x5035, + kMkvContentEncAlgo = 0x47E1, + kMkvContentEncKeyID = 0x47E2, + kMkvContentEncAESSettings = 0x47E7, + kMkvAESSettingsCipherMode = 0x47E8, + kMkvAESSettingsCipherInitData = 0x47E9, + //end ContentEncodings + //Cueing Data + kMkvCues = 0x1C53BB6B, + kMkvCuePoint = 0xBB, + kMkvCueTime = 0xB3, + kMkvCueTrackPositions = 0xB7, + kMkvCueTrack = 0xF7, + kMkvCueClusterPosition = 0xF1, + kMkvCueBlockNumber = 0x5378, + //Chapters + kMkvChapters = 0x1043A770, + kMkvEditionEntry = 0x45B9, + kMkvChapterAtom = 0xB6, + kMkvChapterUID = 0x73C4, + kMkvChapterStringUID = 0x5654, + kMkvChapterTimeStart = 0x91, + kMkvChapterTimeEnd = 0x92, + kMkvChapterDisplay = 0x80, + kMkvChapString = 0x85, + kMkvChapLanguage = 0x437C, + kMkvChapCountry = 0x437E +}; + +} // end namespace mkvmuxer + +#endif // WEBMIDS_HPP