From a3dd40877d1d0f75212d3c6d9b418abf543dc68f Mon Sep 17 00:00:00 2001 From: Frank Galligan Date: Tue, 21 Jun 2011 14:42:55 -0400 Subject: [PATCH] Add support for muxing to libwebm. Squashed commit of the following: 9f73b86 Updated code to Google C++ style guide. 8580901 Added support for linux. fd0b59c Fixed segment size issue. d51d5ef Added more Track and Video options. 0fc73fd Fixed issue where cue point was being written out on wrong track. fcfdd07 Updated sample_muxer to read in a webm and output a webm file. Added some options to sample_muxer. Fixed writing out unknown size. 285c558 Added support for muxer guideline "Audio that matches video key frame should be in the same cluster." Added support for block_number in cue points. Added support for setting max cluster size. f956dec Add support for setting the max duration for all clusters. 92ca53e Add support for Cues element. d6b4cba Added typedefs for unsigned long long, long long, and unsigned char. Reformated some code. 1b1f8b2 Added support for SeekHead element. Added WriteVoidElement function. 8faa187 Add support for CodecPrivate. Add support for updating SegmentInfo duration. Add support for updating Segment size. Added new sample that swicthes the tracks. 1dd3555 Refactored some code. Check to see if track is a video track if starting a new cluster on a key-frame. 1bc8374 Refacotrs code so Cluster is in charge of wirting out its own data. 836cd74 Added basic cluster and simple_block support for writing frames. cc48cc2 Add simple support for Audio tracks. 4efd614 Added support for using the encoded ID value for the elements. fb8b5a6 Initial commit of libwebm muxer. Removed switch_sample project. Fixed initialization order warnings on Linux. Reformatted code to follow Google C++ style guide. Fixed Makefile. Removed tester.cpp. Change-Id: I4857042f804edb834be52787a7d5ccdd578b7275 --- Makefile | 37 +- mkvmuxer.cpp | 1617 ++++++++++++++++++++++++++++++ mkvmuxer.hpp | 689 +++++++++++++ mkvmuxer_2008.vcproj | 199 ++++ mkvmuxertypes.hpp | 29 + mkvmuxerutil.cpp | 375 +++++++ mkvmuxerutil.hpp | 82 ++ mkvparser_2008.sln | 16 + mkvwriter.cpp | 87 ++ mkvwriter.hpp | 48 + sample_muxer/sample_muxer.cpp | 352 +++++++ sample_muxer/sample_muxer.vcproj | 197 ++++ webmids.hpp | 107 ++ 13 files changed, 3821 insertions(+), 14 deletions(-) create mode 100644 mkvmuxer.cpp create mode 100644 mkvmuxer.hpp create mode 100644 mkvmuxer_2008.vcproj create mode 100644 mkvmuxertypes.hpp create mode 100644 mkvmuxerutil.cpp create mode 100644 mkvmuxerutil.hpp create mode 100644 mkvwriter.cpp create mode 100644 mkvwriter.hpp create mode 100644 sample_muxer/sample_muxer.cpp create mode 100644 sample_muxer/sample_muxer.vcproj create mode 100644 webmids.hpp diff --git a/Makefile b/Makefile index 7748f4f..8ed2ea9 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,29 @@ -LIB = libmkvparser.a -OBJECTS = mkvparser.o mkvreader.o sample.o -EXE = sample -CFLAGS = -W -Wall -g +CXX := g++ +CXXFLAGS := -W -Wall -g +LIBS := libmkvparser.a libmkvmuxer.a +PARSEOBJ := mkvparser.o mkvreader.o +MUXEROBJ := mkvmuxer.o mkvmuxerutil.o mkvwriter.o +OBJECTS1 := $(PARSEOBJ) sample.o +OBJECTS2 := $(PARSEOBJ) $(MUXEROBJ) sample_muxer/sample_muxer.o +INCLUDES := -I. +EXES := samplemuxer sample -$(EXE): $(OBJECTS) - $(AR) rcs $(LIB) mkvparser.o mkvreader.o - $(CXX) $(OBJECTS) -L./ -lmkvparser -o $(EXE) +all: $(EXES) -mkvparser.o: mkvparser.cpp - $(CXX) -c $(CFLAGS) mkvparser.cpp -o mkvparser.o +sample: sample.o libmkvparser.a + $(CXX) $^ -o $@ -mkvreader.o: mkvreader.cpp - $(CXX) -c $(CFLAGS) mkvreader.cpp -o mkvreader.o +samplemuxer: sample_muxer/sample_muxer.o $(LIBS) + $(CXX) $^ -o $@ -sample.o: sample.cpp - $(CXX) -c $(CFLAGS) sample.cpp -o sample.o +libmkvparser.a: $(PARSEOBJ) + $(AR) rcs $@ $^ + +libmkvmuxer.a: $(MUXEROBJ) + $(AR) rcs $@ $^ + +%.o: %.cpp + $(CXX) -c $(CXXFLAGS) $(INCLUDES) $< -o $@ clean: - rm -rf $(OBJECTS) $(LIB) $(EXE) Makefile.bak + $(RM) -f $(OBJECTS1) $(OBJECTS2) $(LIBS) $(EXES) Makefile.bak diff --git a/mkvmuxer.cpp b/mkvmuxer.cpp new file mode 100644 index 0000000..383751c --- /dev/null +++ b/mkvmuxer.cpp @@ -0,0 +1,1617 @@ +// Copyright (c) 2011 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 "webmids.hpp" + +namespace mkvmuxer { + +/////////////////////////////////////////////////////////////// +// +// IMkvWriter Class + +IMkvWriter::IMkvWriter() { +} + +IMkvWriter::~IMkvWriter() { +} + +bool WriteEbmlHeader(IMkvWriter* writer) { + // Level 0 + uint64 size = EbmlElementSize(kMkvEBMLVersion, 1ULL, false); + size += EbmlElementSize(kMkvEBMLReadVersion, 1ULL, false); + size += EbmlElementSize(kMkvEBMLMaxIDLength, 4ULL, false); + size += EbmlElementSize(kMkvEBMLMaxSizeLength, 8ULL, false); + size += EbmlElementSize(kMkvDocType, "webm", false); + size += EbmlElementSize(kMkvDocTypeVersion, 2ULL, false); + size += EbmlElementSize(kMkvDocTypeReadVersion, 2ULL, false); + + if (!WriteEbmlMasterElement(writer, kMkvEBML, size)) + return false; + + if (!WriteEbmlElement(writer, kMkvEBMLVersion, 1ULL)) + return false; + if (!WriteEbmlElement(writer, kMkvEBMLReadVersion, 1ULL)) + return false; + if (!WriteEbmlElement(writer, kMkvEBMLMaxIDLength, 4ULL)) + return false; + if (!WriteEbmlElement(writer, kMkvEBMLMaxSizeLength, 8ULL)) + return false; + if (!WriteEbmlElement(writer, kMkvDocType, "webm")) + return false; + if (!WriteEbmlElement(writer, kMkvDocTypeVersion, 2ULL)) + return false; + if (!WriteEbmlElement(writer, kMkvDocTypeReadVersion, 2ULL)) + return false; + + return true; +} + +/////////////////////////////////////////////////////////////// +// +// Frame Class + +Frame::Frame() + : frame_(NULL), + length_(0), + track_number_(0), + timestamp_(0), + is_key_(false) { +} + +Frame::~Frame() { + delete [] frame_; +} + +bool Frame::Init(const uint8* frame, uint64 length) { + uint8* const data = new (std::nothrow) uint8[static_cast(length)]; + if (!data) + return false; + + delete [] frame_; + frame_ = data; + length_ = length; + + memcpy(frame_, frame, static_cast(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 { + assert(writer); + assert(track_ > 0); + assert(cluster_pos_ > 0); + + uint64 size = EbmlElementSize(kMkvCueClusterPosition, cluster_pos_, false); + size += EbmlElementSize(kMkvCueTrack, track_, false); + if (output_block_number_ && block_number_ > 1) + size += EbmlElementSize(kMkvCueBlockNumber, block_number_, false); + const uint64 track_pos_size = EbmlElementSize(kMkvCueTrackPositions, + size, + true) + size; + const uint64 payload_size = EbmlElementSize(kMkvCueTime, time_, false) + + 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; + assert(stop_position - payload_position == static_cast(payload_size)); + + return true; +} + +uint64 CuePoint::PayloadSize() const { + uint64 size = EbmlElementSize(kMkvCueClusterPosition, cluster_pos_, false); + size += EbmlElementSize(kMkvCueTrack, track_, false); + if (output_block_number_ && block_number_ > 1) + size += EbmlElementSize(kMkvCueBlockNumber, block_number_, false); + const uint64 track_pos_size = EbmlElementSize(kMkvCueTrackPositions, + size, + true) + size; + const uint64 payload_size = EbmlElementSize(kMkvCueTime, time_, false) + + track_pos_size; + + return payload_size; +} + +uint64 CuePoint::Size() const { + const uint64 payload_size = PayloadSize(); + return EbmlElementSize(kMkvCuePoint, payload_size, true) + 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) { + assert(cue); + + if ((cue_entries_size_ + 1) > cue_entries_capacity_) { + // Add more CuePoints. + const int32 new_capacity = + (!cue_entries_capacity_) ? 2 : cue_entries_capacity_ * 2; + + assert(new_capacity > 0); + CuePoint** const cues = new (std::nothrow) CuePoint*[new_capacity]; + if (!cues) + return false; + + for (int32 i = 0; i < cue_entries_size_; ++i) { + cues[i] = cue_entries_[i]; + } + + delete [] cue_entries_; + + cue_entries_ = cues; + cue_entries_capacity_ = new_capacity; + } + + cue->set_output_block_number(output_block_number_); + cue_entries_[cue_entries_size_++] = cue; + return true; +} + +const CuePoint* Cues::GetCueByIndex(int32 index) const { + if (cue_entries_ == NULL) + return NULL; + + if (index >= cue_entries_size_) + return NULL; + + return cue_entries_[index]; +} + +bool Cues::Write(IMkvWriter* writer) const { + assert(writer); + + uint64 size = 0; + for (int32 i = 0; i < cue_entries_size_; ++i) { + const CuePoint* const cue = GetCueByIndex(i); + assert(cue); + 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); + assert(cue); + if (!cue->Write(writer)) + return false; + } + + const int64 stop_position = writer->Position(); + if (stop_position < 0) + return false; + assert(stop_position - payload_position == static_cast(size)); + + return true; +} + +/////////////////////////////////////////////////////////////// +// +// Track Class + +Track::Track() + : codec_id_(NULL), + codec_private_(NULL), + language_(NULL), + name_(NULL), + number_(0), + type_(0), + uid_(MakeUID()), + codec_private_length_(0) { +} + +Track::~Track() { + delete [] codec_id_; + delete [] codec_private_; +} + +uint64 Track::PayloadSize() const { + uint64 size = EbmlElementSize(kMkvTrackNumber, number_, false); + size += EbmlElementSize(kMkvTrackUID, uid_, false); + size += EbmlElementSize(kMkvTrackType, type_, false); + if (codec_id_) + size += EbmlElementSize(kMkvCodecID, codec_id_, false); + if (codec_private_) + size += EbmlElementSize(kMkvCodecPrivate, + codec_private_, + codec_private_length_, + false); + if (language_) + size += EbmlElementSize(kMkvLanguage, language_, false); + if (name_) + size += EbmlElementSize(kMkvName, name_, false); + + return size; +} + +uint64 Track::Size() const { + uint64 size = Track::PayloadSize(); + size += EbmlElementSize(kMkvTrackEntry, size, true); + + return size; +} + +bool Track::Write(IMkvWriter* writer) const { + assert(writer); + + // |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_, false); + size += EbmlElementSize(kMkvTrackUID, uid_, false); + size += EbmlElementSize(kMkvTrackType, type_, false); + if (codec_id_) + size += EbmlElementSize(kMkvCodecID, codec_id_, false); + if (codec_private_) + size += EbmlElementSize(kMkvCodecPrivate, + codec_private_, + codec_private_length_, + false); + if (language_) + size += EbmlElementSize(kMkvLanguage, language_, false); + if (name_) + size += EbmlElementSize(kMkvName, name_, false); + + const int64 payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlElement(writer, kMkvTrackNumber, number_)) + return false; + if (!WriteEbmlElement(writer, kMkvTrackUID, uid_)) + return false; + if (!WriteEbmlElement(writer, kMkvTrackType, type_)) + return false; + if (codec_id_) { + if (!WriteEbmlElement(writer, kMkvCodecID, codec_id_)) + return false; + } + if (codec_private_) { + if (!WriteEbmlElement(writer, + kMkvCodecPrivate, + codec_private_, + codec_private_length_)) + return false; + } + if (language_) { + if (!WriteEbmlElement(writer, kMkvLanguage, language_)) + return false; + } + if (name_) { + if (!WriteEbmlElement(writer, kMkvName, name_)) + return false; + } + + const int64 stop_position = writer->Position(); + if (stop_position < 0) + return false; + assert(stop_position - payload_position == static_cast(size)); + + return true; +} + +bool Track::SetCodecPrivate(const uint8* codec_private, uint64 length) { + assert(codec_private); + assert(length > 0); + + delete [] codec_private_; + + codec_private_ = + new (std::nothrow) uint8[static_cast(length)]; + 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) { + assert(codec_id); + + delete [] codec_id_; + + const size_t length = strlen(codec_id) + 1; + codec_id_ = new (std::nothrow) char[length]; + 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) { + assert(language); + + delete [] language_; + + const size_t length = strlen(language) + 1; + language_ = new (std::nothrow) char[length]; + if (language_) { +#ifdef _MSC_VER + strcpy_s(language_, length, language); +#else + strcpy(language_, language); +#endif + } +} + +void Track::set_name(const char* name) { + assert(name); + + delete [] name_; + + const size_t length = strlen(name) + 1; + name_ = new (std::nothrow) char[length]; + if (name_) { +#ifdef _MSC_VER + strcpy_s(name_, length, name); +#else + strcpy(name_, name); +#endif + } +} + +bool Track::is_seeded_ = false; + +uint64 Track::MakeUID() { + if (!is_seeded_) { + srand(static_cast(time(NULL))); + is_seeded_ = true; + } + + uint64 track_uid = 0; + for (int32 i = 0; i < 7; ++i) { // avoid problems with 8-byte values + track_uid <<= 8; + + const int32 nn = rand(); + const int32 n = 0xFF & (nn >> 4); // throw away low-order bits + + track_uid |= n; + } + + return track_uid; +} + +/////////////////////////////////////////////////////////////// +// +// VideoTrack Class + +VideoTrack::VideoTrack() + : display_height_(0), + display_width_(0), + frame_rate_(0.0), + height_(0), + stereo_mode_(0), + width_(0) { +} + +VideoTrack::~VideoTrack() { +} + +bool VideoTrack::SetStereoMode(uint64 stereo_mode) { + if (stereo_mode != kMono && + stereo_mode != kSideBySideLeftIsFirst && + stereo_mode != kTopBottomRightIsFirst && + stereo_mode != kTopBottomLeftIsFirst && + stereo_mode != kSideBySideRightIsFirst) + return false; + + stereo_mode_ = stereo_mode; + return true; +} + +uint64 VideoTrack::PayloadSize() const { + const uint64 parent_size = Track::PayloadSize(); + + uint64 size = VideoPayloadSize(); + size += EbmlElementSize(kMkvVideo, size, true); + + return parent_size + size; +} + +uint64 VideoTrack::Size() const { + const uint64 parent_size = Track::Size(); + + uint64 size = VideoPayloadSize(); + size += EbmlElementSize(kMkvVideo, size, true); + + return parent_size + size; +} + +bool VideoTrack::Write(IMkvWriter* writer) const { + assert(writer); + + if (!Track::Write(writer)) + return false; + + const uint64 size = VideoPayloadSize(); + + if (!WriteEbmlMasterElement(writer, kMkvVideo, size)) + return false; + + const int64 payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlElement(writer, kMkvPixelWidth, width_)) + return false; + if (!WriteEbmlElement(writer, kMkvPixelHeight, height_)) + return false; + if (display_width_ > 0) + if (!WriteEbmlElement(writer, kMkvDisplayWidth, display_width_)) + return false; + if (display_height_ > 0) + if (!WriteEbmlElement(writer, kMkvDisplayHeight, display_height_)) + return false; + if (stereo_mode_ > kMono) + if (!WriteEbmlElement(writer, kMkvStereoMode, stereo_mode_)) + return false; + if (frame_rate_ > 0.0) + if (!WriteEbmlElement(writer, + kMkvFrameRate, + static_cast(frame_rate_))) + return false; + + const int64 stop_position = writer->Position(); + if (stop_position < 0) + return false; + assert(stop_position - payload_position == static_cast(size)); + + return true; +} + +uint64 VideoTrack::VideoPayloadSize() const { + uint64 size = EbmlElementSize(kMkvPixelWidth, width_, false); + size += EbmlElementSize(kMkvPixelHeight, height_, false); + if (display_width_ > 0) + size += EbmlElementSize(kMkvDisplayWidth, display_width_, false); + if (display_height_ > 0) + size += EbmlElementSize(kMkvDisplayHeight, display_height_, false); + if (stereo_mode_ > kMono) + size += EbmlElementSize(kMkvStereoMode, stereo_mode_, false); + if (frame_rate_ > 0.0) + size += EbmlElementSize(kMkvFrameRate, + static_cast(frame_rate_), + false); + + return size; +} + +/////////////////////////////////////////////////////////////// +// +// AudioTrack Class + +AudioTrack::AudioTrack() + : bit_depth_(0), + channels_(1), + sample_rate_(0.0) { +} + +AudioTrack::~AudioTrack() { +} + +uint64 AudioTrack::PayloadSize() const { + const uint64 parent_size = Track::PayloadSize(); + + uint64 size = EbmlElementSize(kMkvSamplingFrequency, + static_cast(sample_rate_), + false); + size += EbmlElementSize(kMkvChannels, channels_, false); + if (bit_depth_ > 0) + size += EbmlElementSize(kMkvBitDepth, bit_depth_, false); + size += EbmlElementSize(kMkvAudio, size, true); + + return parent_size + size; +} + +uint64 AudioTrack::Size() const { + const uint64 parent_size = Track::Size(); + + uint64 size = EbmlElementSize(kMkvSamplingFrequency, + static_cast(sample_rate_), + false); + size += EbmlElementSize(kMkvChannels, channels_, false); + if (bit_depth_ > 0) + size += EbmlElementSize(kMkvBitDepth, bit_depth_, false); + size += EbmlElementSize(kMkvAudio, size, true); + + return parent_size + size; +} + +bool AudioTrack::Write(IMkvWriter* writer) const { + assert(writer); + + if (!Track::Write(writer)) + return false; + + // Calculate AudioSettings size. + uint64 size = EbmlElementSize(kMkvSamplingFrequency, + static_cast(sample_rate_), + false); + size += EbmlElementSize(kMkvChannels, channels_, false); + if (bit_depth_ > 0) + size += EbmlElementSize(kMkvBitDepth, bit_depth_, false); + + 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) + return false; + assert(stop_position - payload_position == static_cast(size)); + + return true; +} + +/////////////////////////////////////////////////////////////// +// +// Tracks Class + +const char* const Tracks::kVp8CodecId = "V_VP8"; +const char* const Tracks::kVorbisCodecId = "A_VORBIS"; + +Tracks::Tracks() + : track_entries_(NULL), + track_entries_size_(0) { +} + +Tracks::~Tracks() { + if (track_entries_) { + for (uint32 i = 0; i < track_entries_size_; ++i) { + Track* const track = track_entries_[i]; + delete track; + } + delete [] track_entries_; + } +} + +bool Tracks::AddTrack(Track* track) { + const uint32 count = track_entries_size_ + 1; + + Track** const track_entries = new (std::nothrow) Track*[count]; + if (!track_entries) + return false; + + for (uint32 i = 0; i < track_entries_size_; ++i) { + track_entries[i] = track_entries_[i]; + } + + delete [] track_entries_; + + track->set_number(count); + + 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 { + assert(writer); + + uint64 size = 0; + const int32 count = track_entries_size(); + for (int32 i = 0; i < count; ++i) { + const Track* const track = GetTrackByIndex(i); + assert(track); + 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) + return false; + assert(stop_position - payload_position == static_cast(size)); + + return true; +} + +/////////////////////////////////////////////////////////////// +// +// Cluster Class + +Cluster::Cluster(uint64 timecode, IMkvWriter* writer) + : blocks_added_(0), + finalized_(false), + header_written_(false), + payload_size_(0), + position_for_cues_(-1), + size_position_(-1), + timecode_(timecode), + writer_(writer) { + // TODO(fgalligan): Create an Init function. + assert(writer_); + position_for_cues_ = writer_->Position(); +} + +Cluster::~Cluster() { +} + +bool Cluster::AddFrame(const uint8* frame, + uint64 length, + uint64 track_number, + short timecode, + bool is_key) { + if (finalized_) + return false; + + if (!header_written_) + if (!WriteClusterHeader()) + return false; + + const uint64 element_size = WriteSimpleBlock(writer_, + frame, + length, + static_cast(track_number), + timecode, + is_key); + if (!element_size) + return false; + + AddPayloadSize(element_size); + blocks_added_++; + + return true; +} + +void Cluster::AddPayloadSize(uint64 size) { + payload_size_ += size; +} + +bool Cluster::Finalize() { + if (finalized_) + return false; + + assert(size_position_ != -1); + + 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; +} + +bool Cluster::WriteClusterHeader() { + assert(!finalized_); + + if (SerializeInt(writer_, kMkvCluster, 4)) + 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(), false)); + 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()) { + assert(start_pos_ != -1); + + 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]), + false); + entry_size[i] += EbmlElementSize(kMkvSeekPosition, + seek_entry_pos_[i], + false); + + payload_size += EbmlElementSize(kMkvSeek, entry_size[i], true) + + 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 = EbmlElementSize(kMkvSeekHead, + total_entry_size, + true) + 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 = EbmlElementSize(kMkvSeekHead, entry_size, true); + + start_pos_ = writer->Position(); + + const uint64 bytes_written = WriteVoidElement(writer, size + entry_size); + if (!bytes_written) + return false; + + return true; +} + +bool SeekHead::AddSeekEntry(uint32 id, uint64 pos) { + for (int32 i = 0; i < kSeekEntryCount; ++i) { + if (seek_entry_id_[i] == 0) { + seek_entry_id_[i] = id; + seek_entry_pos_[i] = pos; + return true; + } + } + return false; +} + +uint64 SeekHead::MaxEntrySize() const { + const uint64 max_entry_payload_size = + EbmlElementSize(kMkvSeekID, 0xffffffffULL, false) + + EbmlElementSize(kMkvSeekPosition, 0xffffffffffffffffULL, false); + const uint64 max_entry_size = + EbmlElementSize(kMkvSeek, max_entry_payload_size, true) + + 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]; + 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 { + assert(writer); + + if (duration_ > 0.0) { + if (writer->Seekable()) { + assert(duration_pos_ != -1); + + 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) { + assert(writer); + + if (!muxing_app_ || !writing_app_) + return false; + + uint64 size = EbmlElementSize(kMkvTimecodeScale, timecode_scale_, false); + if (duration_ > 0.0) + size += EbmlElementSize(kMkvDuration, + static_cast(duration_), + false); + size += EbmlElementSize(kMkvMuxingApp, muxing_app_, false); + size += EbmlElementSize(kMkvWritingApp, writing_app_, false); + + 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) + return false; + assert(stop_position - payload_position == static_cast(size)); + + return true; +} + +void SegmentInfo::set_writing_app(const char* app) { + assert(app); + + delete [] writing_app_; + + const size_t length = strlen(app) + 1; + writing_app_ = new (std::nothrow) char[length]; + if (writing_app_) { +#ifdef _MSC_VER + strcpy_s(writing_app_, length, app); +#else + strcpy(writing_app_, app); +#endif + } +} + +/////////////////////////////////////////////////////////////// +// +// Segment Class + +Segment::Segment(IMkvWriter* writer) + : cluster_list_(NULL), + cluster_list_capacity_(0), + cluster_list_size_(0), + cues_track_(0), + frames_(NULL), + frames_capacity_(0), + frames_size_(0), + has_video_(false), + header_written_(false), + last_timestamp_(0), + max_cluster_duration_(0), + max_cluster_size_(0), + mode_(kFile), + new_cluster_(true), + new_cuepoint_(false), + output_cues_(true), + payload_pos_(0), + size_position_(0), + writer_(writer) { + assert(writer_); + + // TODO(fgalligan): Create an Init function for Segment. + segment_info_.Init(); +} + +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_; + } +} + +bool Segment::Finalize() { + if (!WriteFramesAll()) + 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]; + assert(old_cluster); + + if (!old_cluster->Finalize()) + return false; + } + + const double duration = + static_cast(last_timestamp_) / segment_info_.timecode_scale(); + segment_info_.set_duration(duration); + if (!segment_info_.Finalize(writer_)) + return false; + + // TODO(fgalligan): Add support for putting the Cues at the front. + if (!seek_head_.AddSeekEntry(kMkvCues, writer_->Position() - payload_pos_)) + return false; + + if (!cues_.Write(writer_)) + return false; + + if (!seek_head_.Finalize(writer_)) + return false; + + if (writer_->Seekable()) { + assert(size_position_ != -1); + + const int64 pos = writer_->Position(); + + // -8 for the size of the segment size + const int64 segment_size = pos - size_position_ - 8; + assert(segment_size > 0); + + if (writer_->Position(size_position_)) + return false; + + if (WriteUIntSize(writer_, segment_size, 8)) + return false; + + if (writer_->Position(pos)) + return false; + } + } + + return true; +} + +uint64 Segment::AddVideoTrack(int32 width, int32 height) { + VideoTrack* const vid_track = new (std::nothrow) VideoTrack(); + if (!vid_track) + return 0; + + vid_track->set_type(Tracks::kVideo); + vid_track->set_codec_id(Tracks::kVp8CodecId); + vid_track->set_width(width); + vid_track->set_height(height); + + tracks_.AddTrack(vid_track); + has_video_ = true; + + return vid_track->number(); +} + +uint64 Segment::AddAudioTrack(int32 sample_rate, int32 channels) { + AudioTrack* const aud_track = new (std::nothrow) AudioTrack(); + if (!aud_track) + return 0; + + aud_track->set_type(Tracks::kAudio); + aud_track->set_codec_id(Tracks::kVorbisCodecId); + aud_track->set_sample_rate(sample_rate); + aud_track->set_channels(channels); + + tracks_.AddTrack(aud_track); + + return aud_track->number(); +} + +bool Segment::AddFrame(uint8* frame, + uint64 length, + uint64 track_number, + uint64 timestamp, + bool is_key) { + assert(frame); + + if (!header_written_) { + if (!WriteSegmentHeader()) + return false; + + if (!seek_head_.AddSeekEntry(kMkvCluster, + writer_->Position() - payload_pos_)) + 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); + assert(track); + + 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); + assert(track); + cues_track_ = track->number(); + } + } + } + + // If the segment has a video track hold onto audio frames to make sure the + // audio that is associated with the start time of a video key-frame is + // muxed into the same cluster. + if (has_video_ && tracks_.TrackIsAudio(track_number)) { + Frame* const new_frame = new Frame(); + if (!new_frame->Init(frame, length)) + return false; + new_frame->set_track_number(track_number); + new_frame->set_timestamp(timestamp); + new_frame->set_is_key(is_key); + + if (!QueueFrame(new_frame)) + return false; + + return true; + } + + // Check to see if the muxer needs to start a new cluster. + if (is_key && tracks_.TrackIsVideo(track_number)) { + new_cluster_ = true; + } else if (cluster_list_size_ > 0) { + const Cluster* const cluster = cluster_list_[cluster_list_size_-1]; + assert(cluster); + const uint64 cluster_ts = + cluster->timecode() * segment_info_.timecode_scale(); + + if (max_cluster_duration_ > 0 && + (timestamp - cluster_ts) >= max_cluster_duration_) { + new_cluster_ = true; + } else if (max_cluster_size_ > 0 && cluster_list_size_ > 0) { + if (cluster->payload_size() >= max_cluster_size_) { + new_cluster_ = true; + } + } + } + + if (new_cluster_) { + const int32 new_size = cluster_list_size_ + 1; + + if (new_size > cluster_list_capacity_) { + // Add more clusters. + const int32 new_capacity = + (!cluster_list_capacity_) ? 2 : cluster_list_capacity_ * 2; + + assert(new_capacity > 0); + Cluster** const clusters = new (std::nothrow) Cluster*[new_capacity]; + 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(timestamp)) + return false; + + uint64 audio_timecode = 0; + uint64 timecode = timestamp / segment_info_.timecode_scale(); + if (frames_size_ > 0) { + audio_timecode = + frames_[0]->timestamp() / segment_info_.timecode_scale(); + + // Update the cluster's timecode to match the first audio frame. + if (audio_timecode < timecode) + timecode = audio_timecode; + } + + // TODO(fgalligan): Add checks here to make sure the timestamps passed in + // are valid. + + cluster_list_[cluster_list_size_] = new (std::nothrow) Cluster(timecode, + writer_); + if (!cluster_list_[cluster_list_size_]) + return false; + cluster_list_size_ = new_size; + + if (mode_ == kFile) { + if (cluster_list_size_ > 1) { + // Update old cluster's size + Cluster* const old_cluster = cluster_list_[cluster_list_size_-2]; + assert(old_cluster); + + if (!old_cluster->Finalize()) + return false; + } + + if (output_cues_) + new_cuepoint_ = true; + } + + new_cluster_ = false; + } + + // Write any audio frames left. + if (!WriteFramesAll()) + return false; + + assert(cluster_list_size_ > 0); + Cluster* const cluster = cluster_list_[cluster_list_size_-1]; + assert(cluster); + + int64 block_timecode = timestamp / segment_info_.timecode_scale(); + block_timecode -= static_cast(cluster->timecode()); + assert(block_timecode >= 0); + + if (new_cuepoint_ && cues_track_ == track_number) { + if (!AddCuePoint(timestamp)) + return false; + } + + if (!cluster->AddFrame(frame, + length, + track_number, + static_cast(block_timecode), + is_key)) + return false; + + if (timestamp > last_timestamp_) + last_timestamp_ = timestamp; + + return true; +} + +void Segment::OutputCues(bool output_cues) { + output_cues_ = output_cues; +} + +bool Segment::CuesTrack(uint64 track_number) { + const Track* const track = GetTrackByNumber(track_number); + if (!track) + return false; + + cues_track_ = track_number; + return true; +} + +Track* Segment::GetTrackByNumber(uint64 track_number) const { + return tracks_.GetTrackByNumber(track_number); +} + +bool Segment::WriteSegmentHeader() { + // TODO(fgalligan): Support more than one segment. + if (!WriteEbmlHeader(writer_)) + return false; + + // Write "unknown" (-1) as segment size value. If mode is kFile, Segment + // will write over duration when the file is finalized. + if (SerializeInt(writer_, kMkvSegment, 4)) + return false; + + // Save for later. + size_position_ = writer_->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_, kEbmlUnknownValue, 8)) + return false; + + payload_pos_ = writer_->Position(); + + if (mode_ == kFile && writer_->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_)) + return false; + } + + if (!seek_head_.AddSeekEntry(kMkvInfo, writer_->Position() - payload_pos_)) + return false; + if (!segment_info_.Write(writer_)) + return false; + + if (!seek_head_.AddSeekEntry(kMkvTracks, writer_->Position() - payload_pos_)) + return false; + if (!tracks_.Write(writer_)) + return false; + header_written_ = true; + + return true; +} + +bool Segment::AddCuePoint(uint64 timestamp) { + assert(cluster_list_size_ > 0); + const Cluster* const cluster = cluster_list_[cluster_list_size_-1]; + assert(cluster); + + CuePoint* const cue = new (std::nothrow) CuePoint(); + if (!cue) + return false; + + cue->set_time(timestamp / segment_info_.timecode_scale()); + cue->set_block_number(cluster->blocks_added() + 1); + cue->set_cluster_pos(cluster->position_for_cues() - payload_pos_); + cue->set_track(cues_track_); + if (!cues_.AddCue(cue)) + return false; + + new_cuepoint_ = false; + return true; +} + +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; + assert(new_capacity > 0); + + Frame** const frames = new (std::nothrow) Frame*[new_capacity]; + 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; +} + +bool Segment::WriteFramesAll() { + if (frames_) { + assert(cluster_list_size_ > 0); + Cluster* const cluster = cluster_list_[cluster_list_size_-1]; + assert(cluster); + + for (int32 i = 0; i < frames_size_; ++i) { + Frame* const frame = frames_[i]; + + int64 block_timecode = + frame->timestamp() / segment_info_.timecode_scale(); + block_timecode -= static_cast(cluster->timecode()); + assert(block_timecode >= 0); + + if (new_cuepoint_ && cues_track_ == frame->track_number()) { + if (!AddCuePoint(frame->timestamp())) + return false; + } + + if (!cluster->AddFrame(frame->frame(), + frame->length(), + frame->track_number(), + static_cast(block_timecode), + frame->is_key())) + return false; + + if (frame->timestamp() > last_timestamp_) + last_timestamp_ = frame->timestamp(); + + delete frame; + } + + frames_size_ = 0; + } + + return true; +} + +bool Segment::WriteFramesLessThan(uint64 timestamp) { + if (frames_size_ > 0) { + assert(frames_); + assert(cluster_list_size_ > 0); + Cluster* const cluster = cluster_list_[cluster_list_size_-1]; + assert(cluster); + + 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]; + + int64 block_timecode = + frame_prev->timestamp() / segment_info_.timecode_scale(); + block_timecode -= static_cast(cluster->timecode()); + assert(block_timecode >= 0); + + if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) { + if (!AddCuePoint(frame_prev->timestamp())) + return false; + } + + if (!cluster->AddFrame(frame_prev->frame(), + frame_prev->length(), + frame_prev->track_number(), + static_cast(block_timecode), + frame_prev->is_key())) + return false; + + ++shift_left; + if (frame_prev->timestamp() > last_timestamp_) + last_timestamp_ = frame_prev->timestamp(); + + delete frame_prev; + } + + if (shift_left > 0) { + assert(shift_left < frames_size_); + + 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/mkvmuxer.hpp b/mkvmuxer.hpp new file mode 100644 index 0000000..4a2c76c --- /dev/null +++ b/mkvmuxer.hpp @@ -0,0 +1,689 @@ +// Copyright (c) 2011 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 mkvmuxer { + +/////////////////////////////////////////////////////////////// +// 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; + + 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); + +/////////////////////////////////////////////////////////////// +// 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); + + const uint8* frame() const { return frame_; } + 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_is_key(bool key) { is_key_ = key; } + bool is_key() const { return is_key_; } + + private: + // Pointer to the data. Owned by this class. + uint8* frame_; + + // 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_; + + // Flag telling if the data should set the key flag of a block. + bool is_key_; +}; + +/////////////////////////////////////////////////////////////// +// 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. + const CuePoint* GetCueByIndex(int32 index) const; + + // 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); +}; + +/////////////////////////////////////////////////////////////// +// Track element. +class Track { + public: + Track(); + virtual ~Track(); + + // 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_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_; } + uint64 uid() const { return uid_; } + + uint64 codec_private_length() const { return codec_private_length_; } + + private: + // Returns a random number to be used for the Track UID. + static uint64 MakeUID(); + + // Track element names + char* codec_id_; + uint8* codec_private_; + char* language_; + char* name_; + uint64 number_; + uint64 type_; + const uint64 uid_; + + // Size of the CodecPrivate data in bytes. + uint64 codec_private_length_; + + // Flag telling if the rand call was seeded. + static bool is_seeded_; + + 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 + }; + + VideoTrack(); + virtual ~VideoTrack(); + + // Returns the size in bytes for the payload of the Track element plus the + // video specific elements. + virtual uint64 PayloadSize() const; + + // Returns the size in bytes of the Track element plus the video specific + // elements. + virtual uint64 Size() 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); + + 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_; } + 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 width_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(VideoTrack); +}; + +/////////////////////////////////////////////////////////////// +// Track that has audio specific elements. +class AudioTrack : public Track { + public: + AudioTrack(); + virtual ~AudioTrack(); + + // Returns the size in bytes for the payload of the Track element plus the + // audio specific elements. + virtual uint64 PayloadSize() const; + + // Returns the size in bytes of the Track element plus the audio specific + // elements. + virtual uint64 Size() 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 + }; + // Vorbis and VP8 coded id defined by the Matroska specs. + static const char* const kVorbisCodecId; + static const char* const kVp8CodecId; + + Tracks(); + ~Tracks(); + + // Adds a Track element to the Tracks object. |track| will be owned and + // deleted by the Tracks object. Returns true on success. + bool AddTrack(Track* track); + + // 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); +}; + +/////////////////////////////////////////////////////////////// +// Cluster element +class Cluster { + public: + // |timecode| is the absolute timecode of the cluster. + Cluster(uint64 timecode, IMkvWriter* writer); + ~Cluster(); + + // 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. + // timestamp: Timecode of the frame relative to the cluster timecode. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrame(const uint8* frame, + uint64 length, + uint64 track_number, + short timecode, + bool is_key); + + // 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(); + + 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: + // 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. + 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; + + // 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); + + private: + // We are going to put a cap on the number of Seek Entries. + const static int32 kSeekEntryCount = 4; + + // 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_; } + 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. +class Segment { + public: + enum Mode { + kLive = 0x1, + kFile = 0x2 + }; + + explicit Segment(IMkvWriter* writer); + virtual ~Segment(); + + // Adds an audio track to the segment. Returns the number of the track on + // success, 0 on error. + uint64 AddAudioTrack(int32 sample_rate, int32 channels); + + // 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(uint8* frame, + uint64 length, + uint64 track_number, + uint64 timestamp, + bool is_key); + + // Adds a video track to the segment. Returns the number of the track on + // success, 0 on error. + uint64 AddVideoTrack(int32 width, int32 height); + + // 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); + + // 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. + 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); + + 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_; } + bool output_cues() const { return output_cues_; } + const SegmentInfo* segment_info() const { return &segment_info_; } + + private: + // Adds a cue point to the Cues element. |timestamp| is the time in + // nanoseconds of the cue's time. Returns true on success. + bool AddCuePoint(uint64 timestamp); + + // Adds the frame to our frame array. + bool QueueFrame(Frame* frame); + + // Output all frames that are queued. Returns true on success and if there + // are no frames queued. + bool 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(); + + // WebM elements + Cues cues_; + SeekHead seek_head_; + SegmentInfo segment_info_; + Tracks tracks_; + + // 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_; + + // Track number that is associated with the cues element for this segment. + uint64 cues_track_; + + // 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_; + + // 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 0 + // which signifies that the muxer will decide. + 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 cluster should be started with the next + // frame. + bool new_cluster_; + + // 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 object. Not owned by this class. + IMkvWriter* writer_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Segment); +}; + +} //end namespace mkvmuxer + +#endif //MKVMUXER_HPP diff --git a/mkvmuxer_2008.vcproj b/mkvmuxer_2008.vcproj new file mode 100644 index 0000000..b8054b6 --- /dev/null +++ b/mkvmuxer_2008.vcproj @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mkvmuxertypes.hpp b/mkvmuxertypes.hpp new file mode 100644 index 0000000..810f2fe --- /dev/null +++ b/mkvmuxertypes.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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 int int32; +typedef unsigned int uint32; +typedef long long int64; +typedef unsigned long long uint64; + +} //end namespace mkvmuxer + +#endif // MKVMUXERTYPES_HPP diff --git a/mkvmuxerutil.cpp b/mkvmuxerutil.cpp new file mode 100644 index 0000000..4b6123d --- /dev/null +++ b/mkvmuxerutil.cpp @@ -0,0 +1,375 @@ +// Copyright (c) 2011 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" + +#include +#include +#include +#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 EbmlElementSize(uint64 type, uint64 value, bool master) { + // Size of EBML ID + int32 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += GetUIntSize(value); + + // Size of Datasize + if (!master) + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, float value, bool master) { + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += sizeof(value); + + // Size of Datasize + if (!master) + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, const char* value, bool master) { + assert(value != NULL); + + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += strlen(value); + + // Size of Datasize + if (!master) + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, + const uint8* value, + uint64 size, + bool master) { + assert(value != NULL); + + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += size; + + // Size of Datasize + if (!master) + ebml_size += GetCodedUIntSize(size); + + return ebml_size; +} + +int32 SerializeInt( + IMkvWriter* writer, + int64 value, + int32 size) { + assert(writer); + assert(size >= 0); + assert(size <= 8); + + 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) { + assert(writer); + const uint32& val = reinterpret_cast(f); + + for (int32 i = 1; i <= 4; ++i) { + const int32 byte_count = 4 - i; + const int32 bit_count = byte_count * 8; + + const uint32 bb = val >> bit_count; + const uint8 b = static_cast(bb); + + const int32 status = writer->Write(&b, 1); + + if (status < 0) + return status; + } + + return 0; +} + +int32 WriteUInt(IMkvWriter* writer, uint64 value) { + assert(writer); + int32 size = GetCodedUIntSize(value); + + return WriteUIntSize(writer, value, size); +} + +int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size) { + assert(writer); + assert(size >= 0); + + if (size > 0) { + assert(size <= 8); + + const uint64 bit = 1LL << (size * 7); + assert(value <= (bit - 2)); + + value |= bit; + } else { + size = 1; + int64 bit; + + for (;;) { + bit = 1LL << (size * 7); + const uint64 max = bit - 2; + + if (value <= max) + break; + + ++size; + } + + assert(size <= 8); + value |= bit; + } + + return SerializeInt(writer, value, size); +} + +int32 WriteID(IMkvWriter* writer, uint64 type) { + assert(writer); + const int32 size = GetUIntSize(type); + + return SerializeInt(writer, type, size); +} + +bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 type, uint64 size) { + assert(writer); + + if (WriteID(writer, type)) + return false; + + if (WriteUInt(writer, size)) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value) { + assert(writer); + + 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) { + assert(writer); + + 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) { + assert(writer); + assert(value != NULL); + + 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) { + assert(writer); + assert(value != NULL); + assert(size > 0); + + 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, + char track_number, + short timecode, + bool is_key) { + assert(writer); + assert(data != NULL); + assert(length > 0); + assert(track_number > 0); + assert(timecode >= 0); + + 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, static_cast(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; +} + +uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) { + // Subtract one for the void ID and the coded size. + uint64 void_entry_size = size - 1 - GetCodedUIntSize(size-1); + uint64 void_size = EbmlElementSize(kMkvVoid, void_entry_size, true) + + void_entry_size; + assert(void_size == size); + + 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) + return 0; + assert(stop_position - payload_position == static_cast(void_size)); + + return void_size; +} + +void GetVersion(int32& major, int32& minor, int32& build, int32& revision) { + major = 0; + minor = 0; + build = 0; + revision = 1; +} + +} // namespace mkvmuxer diff --git a/mkvmuxerutil.hpp b/mkvmuxerutil.hpp new file mode 100644 index 0000000..92596d4 --- /dev/null +++ b/mkvmuxerutil.hpp @@ -0,0 +1,82 @@ +// Copyright (c) 2011 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; + +// 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. |master| must be set to true if +// the element is an Mkv master element. +// TODO(fgalligan): Change these functions so they are master element aware. +uint64 EbmlElementSize(uint64 type, uint64 value, bool master); +uint64 EbmlElementSize(uint64 type, float value, bool master); +uint64 EbmlElementSize(uint64 type, const char* value, bool master); +uint64 EbmlElementSize(uint64 type, + const uint8* value, + uint64 size, + bool master); + +// 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); + +// 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. +// timecode: Relative timecode of the Block. +// is_key: Flag telling whether or not this frame is a key frame. +uint64 WriteSimpleBlock(IMkvWriter* writer, + const uint8* data, + uint64 length, + char track_number, + short timecode, + bool 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); + +} //end namespace mkvmuxer + +#endif // MKVMUXERUTIL_HPP diff --git a/mkvparser_2008.sln b/mkvparser_2008.sln index 0f9ac0d..9d4866d 100644 --- a/mkvparser_2008.sln +++ b/mkvparser_2008.sln @@ -8,6 +8,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample", "sample_2008.vcpro {F9128EC6-C008-41AD-B38F-0E70D549D9F4} = {F9128EC6-C008-41AD-B38F-0E70D549D9F4} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mkvmuxer", "mkvmuxer_2008.vcproj", "{7B1F12CA-0724-430B-B61A-1D357C912CBA}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample_muxer", "sample_muxer\sample_muxer.vcproj", "{B407561F-1F5E-4798-B9C2-81AB09CFBC16}" + ProjectSection(ProjectDependencies) = postProject + {F9128EC6-C008-41AD-B38F-0E70D549D9F4} = {F9128EC6-C008-41AD-B38F-0E70D549D9F4} + {7B1F12CA-0724-430B-B61A-1D357C912CBA} = {7B1F12CA-0724-430B-B61A-1D357C912CBA} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -22,6 +30,14 @@ Global {0CB5681F-6065-490C-98C8-05531732ED7E}.Debug|Win32.Build.0 = Debug|Win32 {0CB5681F-6065-490C-98C8-05531732ED7E}.Release|Win32.ActiveCfg = Release|Win32 {0CB5681F-6065-490C-98C8-05531732ED7E}.Release|Win32.Build.0 = Release|Win32 + {7B1F12CA-0724-430B-B61A-1D357C912CBA}.Debug|Win32.ActiveCfg = Debug|Win32 + {7B1F12CA-0724-430B-B61A-1D357C912CBA}.Debug|Win32.Build.0 = Debug|Win32 + {7B1F12CA-0724-430B-B61A-1D357C912CBA}.Release|Win32.ActiveCfg = Release|Win32 + {7B1F12CA-0724-430B-B61A-1D357C912CBA}.Release|Win32.Build.0 = Release|Win32 + {B407561F-1F5E-4798-B9C2-81AB09CFBC16}.Debug|Win32.ActiveCfg = Debug|Win32 + {B407561F-1F5E-4798-B9C2-81AB09CFBC16}.Debug|Win32.Build.0 = Debug|Win32 + {B407561F-1F5E-4798-B9C2-81AB09CFBC16}.Release|Win32.ActiveCfg = Release|Win32 + {B407561F-1F5E-4798-B9C2-81AB09CFBC16}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/mkvwriter.cpp b/mkvwriter.cpp new file mode 100644 index 0000000..b0d3d69 --- /dev/null +++ b/mkvwriter.cpp @@ -0,0 +1,87 @@ +// Copyright (c) 2011 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 +#include + +namespace mkvmuxer { + +MkvWriter::MkvWriter() : file_(NULL) { +} + +MkvWriter::~MkvWriter() { + Close(); +} + +int32 MkvWriter::Write(const void* buffer, uint32 length) { + assert(file_); + + 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_) { + fclose(file_); + file_ = NULL; + } +} + +int64 MkvWriter::Position() const { + assert(file_); +#ifdef _MSC_VER + return _ftelli64(file_); +#else + return ftell(file_); +#endif +} + +int32 MkvWriter::Position(int64 position) { + assert(file_); +#ifdef _MSC_VER + return _fseeki64(file_, position, SEEK_SET); +#else + return fseek(file_, position, SEEK_SET); +#endif +} + +bool MkvWriter::Seekable() const { + return true; +} + +} // namespace mkvmuxer diff --git a/mkvwriter.hpp b/mkvwriter.hpp new file mode 100644 index 0000000..6838f03 --- /dev/null +++ b/mkvwriter.hpp @@ -0,0 +1,48 @@ +// Copyright (c) 2011 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(); + 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); + + // 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_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(MkvWriter); +}; + +} //end namespace mkvmuxer + +#endif // MKVWRITER_HPP diff --git a/sample_muxer/sample_muxer.cpp b/sample_muxer/sample_muxer.cpp new file mode 100644 index 0000000..e8f1b2b --- /dev/null +++ b/sample_muxer/sample_muxer.cpp @@ -0,0 +1,352 @@ +// Copyright (c) 2011 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 +#include + +// libwebm parser includes +#include "mkvreader.hpp" +#include "mkvparser.hpp" + +// libwebm muxer includes +#include "mkvmuxer.hpp" +#include "mkvwriter.hpp" +#include "mkvmuxerutil.hpp" + +namespace { + +void Usage() { + printf("Usage: sample_muxer -i input -o output [options]\n"); + printf("\n"); + printf("Main options:\n"); + printf(" -h | -? show help\n"); + printf(" -video >0 outputs video\n"); + printf(" -audio >0 outputs audio\n"); + printf(" -live >0 puts the muxer into live mode\n"); + printf(" 0 puts the muxer into file mode\n"); + printf(" -output_cues >0 outputs cues element\n"); + printf(" -cues_on_video_track >0 outputs cues on video track\n"); + printf(" 0 outputs cues on audio track\n"); + printf(" -max_cluster_duration in seconds\n"); + printf(" -max_cluster_size in bytes\n"); + printf(" -switch_tracks >0 switches tracks in output\n"); + printf("\n"); + printf("Video options:\n"); + printf(" -display_width Display width in pixels\n"); + printf(" -display_height Display height in pixels\n"); + printf(" -stereo_mode 3D video mode\n"); + printf("\n"); + printf("Cues options:\n"); + printf(" -output_cues_block_number >0 outputs cue block number\n"); +} + +} //end namespace + +int main(int argc, char* argv[]) { + using mkvmuxer::uint64; + + char* input = NULL; + char* output = NULL; + + // Segment variables + bool output_video = true; + bool output_audio = true; + bool live_mode = false; + bool output_cues = true; + bool cues_on_video_track = true; + uint64 max_cluster_duration = 0; + uint64 max_cluster_size = 0; + bool switch_tracks = false; + + bool output_cues_block_number = true; + + uint64 display_width = 0; + uint64 display_height = 0; + uint64 stereo_mode = 0; + + for (int i = 1; i < argc; ++i) { + char* end; + + if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) { + Usage(); + return 0; + } else if (!strcmp("-i", argv[i])) { + input = argv[++i]; + } else if (!strcmp("-o", argv[i])) { + output = argv[++i]; + } else if (!strcmp("-video", argv[i])) { + output_video = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-audio", argv[i])) { + output_audio = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-live", argv[i])) { + live_mode = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-output_cues", argv[i])) { + output_cues = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-cues_on_video_track", argv[i])) { + cues_on_video_track = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-max_cluster_duration", argv[i])) { + const double seconds = strtod(argv[++i], &end); + max_cluster_duration = + static_cast(seconds * 1000000000.0); + } else if (!strcmp("-max_cluster_size", argv[i])) { + max_cluster_size = strtol(argv[++i], &end, 10); + } else if (!strcmp("-switch_tracks", argv[i])) { + switch_tracks = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-display_width", argv[i])) { + display_width = strtol(argv[++i], &end, 10); + } else if (!strcmp("-display_height", argv[i])) { + display_height = strtol(argv[++i], &end, 10); + } else if (!strcmp("-stereo_mode", argv[i])) { + stereo_mode = strtol(argv[++i], &end, 10); + } else if (!strcmp("-output_cues_block_number", argv[i])) { + output_cues_block_number = + strtol(argv[++i], &end, 10) == 0 ? false : true; + } + } + + if (input == NULL || output == NULL) { + Usage(); + return 0; + } + + // Get parser header info + mkvparser::MkvReader reader; + + if (reader.Open(input)) { + printf("\n Filename is invalid or error while opening.\n"); + return -1; + } + + long long pos = 0; + mkvparser::EBMLHeader ebml_header; + ebml_header.Parse(&reader, pos); + + mkvparser::Segment* parser_segment; + long long ret = mkvparser::Segment::CreateInstance(&reader, + pos, + parser_segment); + if (ret) { + printf("\n Segment::CreateInstance() failed."); + return -1; + } + + ret = parser_segment->Load(); + if (ret < 0) { + printf("\n Segment::Load() failed."); + return -1; + } + + const mkvparser::SegmentInfo* const segment_info = parser_segment->GetInfo(); + const long long timeCodeScale = segment_info->GetTimeCodeScale(); + + // Set muxer header info + mkvmuxer::MkvWriter writer; + + if (!writer.Open(output)) { + printf("\n Filename is invalid or error while opening.\n"); + return -1; + } + + // Set Segment element attributes + mkvmuxer::Segment muxer_segment(&writer); + if (live_mode) + muxer_segment.set_mode(mkvmuxer::Segment::kLive); + else + muxer_segment.set_mode(mkvmuxer::Segment::kFile); + + if (max_cluster_duration > 0) + muxer_segment.set_max_cluster_duration(max_cluster_duration); + if (max_cluster_size > 0) + muxer_segment.set_max_cluster_size(max_cluster_size); + muxer_segment.OutputCues(output_cues); + + // Set SegmentInfo element attributes + mkvmuxer::SegmentInfo* info = muxer_segment.GetSegmentInfo(); + info->set_timecode_scale(timeCodeScale); + info->set_writing_app("sample_muxer"); + + // Set Tracks element attributes + enum { kVideoTrack = 1, kAudioTrack = 2 }; + const mkvparser::Tracks* parser_tracks = parser_segment->GetTracks(); + unsigned long i = 0; + uint64 vid_track = 0; // no track added + uint64 aud_track = 0; // no track added + + while (i != parser_tracks->GetTracksCount()) { + int track_num = i++; + if (switch_tracks) + track_num = i % parser_tracks->GetTracksCount(); + + const mkvparser::Track* const parser_track = + parser_tracks->GetTrackByIndex(track_num); + + if (parser_track == NULL) + continue; + + // TODO(fgalligan): Add support for language to parser. + const char* track_name = parser_track->GetNameAsUTF8(); + + const long long track_type = parser_track->GetType(); + + if (track_type == kVideoTrack && output_video) { + // Get the video track from the parser + const mkvparser::VideoTrack* const pVideoTrack = + static_cast(parser_track); + const long long width = pVideoTrack->GetWidth(); + const long long height = pVideoTrack->GetHeight(); + + // Add the video track to the muxer + vid_track = muxer_segment.AddVideoTrack(static_cast(width), + static_cast(height)); + if (!vid_track) { + printf("\n Could not add video track.\n"); + return -1; + } + + mkvmuxer::VideoTrack* video = + static_cast( + muxer_segment.GetTrackByNumber(vid_track)); + if (!video) { + printf("\n Could not get video track.\n"); + return -1; + } + + if (track_name) + video->set_name(track_name); + + if (display_width > 0) + video->set_display_width(display_width); + if (display_height > 0) + video->set_display_height(display_height); + if (stereo_mode > 0) + video->SetStereoMode(stereo_mode); + + const double rate = pVideoTrack->GetFrameRate(); + if (rate > 0.0) { + video->set_frame_rate(rate); + } + } else if (track_type == kAudioTrack && output_audio) { + // Get the audio track from the parser + const mkvparser::AudioTrack* const pAudioTrack = + static_cast(parser_track); + const long long channels = pAudioTrack->GetChannels(); + const double sample_rate = pAudioTrack->GetSamplingRate(); + + // Add the audio track to the muxer + aud_track = muxer_segment.AddAudioTrack(static_cast(sample_rate), + static_cast(channels)); + if (!aud_track) { + printf("\n Could not add audio track.\n"); + return -1; + } + + mkvmuxer::AudioTrack* audio = + static_cast( + muxer_segment.GetTrackByNumber(aud_track)); + if (!audio) { + printf("\n Could not get audio track.\n"); + return -1; + } + + if (track_name) + audio->set_name(track_name); + + size_t private_size; + const unsigned char* private_data = + pAudioTrack->GetCodecPrivate(private_size); + if (private_size > 0) { + if (!audio->SetCodecPrivate(private_data, private_size)) { + printf("\n Could not add audio private data.\n"); + return -1; + } + } + + const long long bit_depth = pAudioTrack->GetBitDepth(); + if (bit_depth > 0) + audio->set_bit_depth(bit_depth); + } + } + + // Set Cues element attributes + mkvmuxer::Cues* cues = muxer_segment.GetCues(); + cues->set_output_block_number(output_cues_block_number); + if (cues_on_video_track) { + if (vid_track) + muxer_segment.CuesTrack(vid_track); + } else { + if (aud_track) + muxer_segment.CuesTrack(aud_track); + } + + // Write clusters + unsigned char* data = NULL; + int data_len = 0; + + const mkvparser::Cluster* cluster = parser_segment->GetFirst(); + + while ((cluster != NULL) && !cluster->EOS()) { + const mkvparser::BlockEntry* block_entry = cluster->GetFirst(); + + while ((block_entry != NULL) && !block_entry->EOS()) { + const mkvparser::Block* const block = block_entry->GetBlock(); + const long long trackNum = block->GetTrackNumber(); + const mkvparser::Track* const parser_track = + parser_tracks->GetTrackByNumber( + static_cast(trackNum)); + const long long track_type = parser_track->GetType(); + + if ((track_type == kAudioTrack && output_audio) || + (track_type == kVideoTrack && output_video)) { + const int frame_count = block->GetFrameCount(); + const long long time_ns = block->GetTime(cluster); + const bool is_key = block->IsKey(); + + for (int i = 0; i < frame_count; ++i) { + const mkvparser::Block::Frame& frame = block->GetFrame(i); + + if (frame.len > data_len) { + delete [] data; + data = new unsigned char[frame.len]; + if (!data) + return -1; + data_len = frame.len; + } + + if (frame.Read(&reader, data)) + return -1; + + uint64 track_num = vid_track; + if (track_type == kAudioTrack) + track_num = aud_track; + + if (!muxer_segment.AddFrame(data, + frame.len, + track_num, + time_ns, + is_key)) { + printf("\n Could not add frame.\n"); + return -1; + } + } + } + + block_entry = cluster->GetNext(block_entry); + } + + cluster = parser_segment->GetNext(cluster); + } + + muxer_segment.Finalize(); + + delete [] data; + delete parser_segment; + return 0; +} + + + diff --git a/sample_muxer/sample_muxer.vcproj b/sample_muxer/sample_muxer.vcproj new file mode 100644 index 0000000..4ef055b --- /dev/null +++ b/sample_muxer/sample_muxer.vcproj @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webmids.hpp b/webmids.hpp new file mode 100644 index 0000000..f65a7bf --- /dev/null +++ b/webmids.hpp @@ -0,0 +1,107 @@ +// Copyright (c) 2011 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, + //Track + kMkvTracks = 0x1654AE6B, + kMkvTrackEntry = 0xAE, + kMkvTrackNumber = 0xD7, + kMkvTrackUID = 0x73C5, + kMkvTrackType = 0x83, + kMkvFlagEnabled = 0xB9, + kMkvFlagDefault = 0x88, + kMkvFlagForced = 0x55AA, + kMkvFlagLacing = 0x9C, + kMkvDefaultDuration = 0x23E383, + kMkvName = 0x536E, + kMkvLanguage = 0x22B59C, + kMkvCodecID = 0x86, + kMkvCodecPrivate = 0x63A2, + kMkvCodecName = 0x258688, + //video + kMkvVideo = 0xE0, + kMkvFlagInterlaced = 0x9A, + kMkvStereoMode = 0x53B8, + 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 + //Cueing Data + kMkvCues = 0x1C53BB6B, + kMkvCuePoint = 0xBB, + kMkvCueTime = 0xB3, + kMkvCueTrackPositions = 0xB7, + kMkvCueTrack = 0xF7, + kMkvCueClusterPosition = 0xF1, + kMkvCueBlockNumber = 0x5378, +}; + +} // end namespace mkvmuxer + +#endif // WEBMIDS_HPP \ No newline at end of file