mkvmuxer: add support for WebVTT chapters
Change-Id: I469ce3bd79a9b50b82e00ac8c63fc3d1db220887
This commit is contained in:
parent
25ee621061
commit
2c5836837e
451
mkvmuxer.cpp
451
mkvmuxer.cpp
@ -21,6 +21,36 @@
|
||||
|
||||
namespace mkvmuxer {
|
||||
|
||||
namespace {
|
||||
// Deallocate the string designated by |dst|, and then copy the |src|
|
||||
// string to |dst|. The caller owns both the |src| string and the
|
||||
// |dst| copy (hence the caller is responsible for eventually
|
||||
// deallocating the strings, either directly, or indirectly via
|
||||
// StrCpy). Returns true if the source string was successfully copied
|
||||
// to the destination.
|
||||
bool StrCpy(const char* src, char** dst_ptr) {
|
||||
if (dst_ptr == NULL)
|
||||
return false;
|
||||
|
||||
char*& dst = *dst_ptr;
|
||||
|
||||
delete [] dst;
|
||||
dst = NULL;
|
||||
|
||||
if (src == NULL)
|
||||
return true;
|
||||
|
||||
const size_t size = strlen(src) + 1;
|
||||
|
||||
dst = new (std::nothrow) char[size]; // NOLINT
|
||||
if (dst == NULL)
|
||||
return false;
|
||||
|
||||
strcpy(dst, src); // NOLINT
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMkvWriter Class
|
||||
@ -427,14 +457,14 @@ uint64 ContentEncoding::EncryptionSize() const {
|
||||
//
|
||||
// Track Class
|
||||
|
||||
Track::Track()
|
||||
Track::Track(unsigned int* seed)
|
||||
: codec_id_(NULL),
|
||||
codec_private_(NULL),
|
||||
language_(NULL),
|
||||
name_(NULL),
|
||||
number_(0),
|
||||
type_(0),
|
||||
uid_(MakeUID()),
|
||||
uid_(MakeUID(seed)),
|
||||
codec_private_length_(0),
|
||||
content_encoding_entries_(NULL),
|
||||
content_encoding_entries_size_(0) {
|
||||
@ -679,33 +709,13 @@ void Track::set_name(const char* name) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Track::is_seeded_ = false;
|
||||
|
||||
uint64 Track::MakeUID() {
|
||||
if (!is_seeded_) {
|
||||
srand(static_cast<uint32>(time(NULL)));
|
||||
is_seeded_ = true;
|
||||
}
|
||||
|
||||
uint64 track_uid = 0;
|
||||
for (int32 i = 0; i < 7; ++i) { // avoid problems with 8-byte values
|
||||
track_uid <<= 8;
|
||||
|
||||
const int32 nn = rand();
|
||||
const int32 n = 0xFF & (nn >> 4); // throw away low-order bits
|
||||
|
||||
track_uid |= n;
|
||||
}
|
||||
|
||||
return track_uid;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
//
|
||||
// VideoTrack Class
|
||||
|
||||
VideoTrack::VideoTrack()
|
||||
: display_height_(0),
|
||||
VideoTrack::VideoTrack(unsigned int* seed)
|
||||
: Track(seed),
|
||||
display_height_(0),
|
||||
display_width_(0),
|
||||
frame_rate_(0.0),
|
||||
height_(0),
|
||||
@ -796,8 +806,9 @@ uint64 VideoTrack::VideoPayloadSize() const {
|
||||
//
|
||||
// AudioTrack Class
|
||||
|
||||
AudioTrack::AudioTrack()
|
||||
: bit_depth_(0),
|
||||
AudioTrack::AudioTrack(unsigned int* seed)
|
||||
: Track(seed),
|
||||
bit_depth_(0),
|
||||
channels_(1),
|
||||
sample_rate_(0.0) {
|
||||
}
|
||||
@ -1008,7 +1019,344 @@ bool Tracks::Write(IMkvWriter* writer) const {
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Cluster Class
|
||||
// Chapter Class
|
||||
|
||||
bool Chapter::set_id(const char* id) {
|
||||
return StrCpy(id, &id_);
|
||||
}
|
||||
|
||||
void Chapter::set_time(const Segment& segment,
|
||||
uint64 start_ns,
|
||||
uint64 end_ns) {
|
||||
const SegmentInfo* const info = segment.GetSegmentInfo();
|
||||
const uint64 timecode_scale = info->timecode_scale();
|
||||
start_timecode_ = start_ns / timecode_scale;
|
||||
end_timecode_ = end_ns / timecode_scale;
|
||||
}
|
||||
|
||||
bool Chapter::add_string(const char* title,
|
||||
const char* language,
|
||||
const char* country) {
|
||||
if (!ExpandDisplaysArray())
|
||||
return false;
|
||||
|
||||
Display& d = displays_[displays_count_++];
|
||||
d.Init();
|
||||
|
||||
if (!d.set_title(title))
|
||||
return false;
|
||||
|
||||
if (!d.set_language(language))
|
||||
return false;
|
||||
|
||||
if (!d.set_country(country))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Chapter::Chapter() {
|
||||
// This ctor only constructs the object. Proper initialization is
|
||||
// done in Init() (called in Chapters::AddChapter()). The only
|
||||
// reason we bother implementing this ctor is because we had to
|
||||
// declare it as private (along with the dtor), in order to prevent
|
||||
// clients from creating Chapter instances (a privelege we grant
|
||||
// only to the Chapters class). Doing no initialization here also
|
||||
// means that creating arrays of chapter objects is more efficient,
|
||||
// because we only initialize each new chapter object as it becomes
|
||||
// active on the array.
|
||||
}
|
||||
|
||||
Chapter::~Chapter() {
|
||||
}
|
||||
|
||||
void Chapter::Init(unsigned int* seed) {
|
||||
id_ = NULL;
|
||||
displays_ = NULL;
|
||||
displays_size_ = 0;
|
||||
displays_count_ = 0;
|
||||
uid_ = MakeUID(seed);
|
||||
}
|
||||
|
||||
void Chapter::ShallowCopy(Chapter* dst) const {
|
||||
dst->id_ = id_;
|
||||
dst->start_timecode_ = start_timecode_;
|
||||
dst->end_timecode_ = end_timecode_;
|
||||
dst->uid_ = uid_;
|
||||
dst->displays_ = displays_;
|
||||
dst->displays_size_ = displays_size_;
|
||||
dst->displays_count_ = displays_count_;
|
||||
}
|
||||
|
||||
void Chapter::Clear() {
|
||||
StrCpy(NULL, &id_);
|
||||
|
||||
while (displays_count_ < 0) {
|
||||
Display& d = displays_[--displays_count_];
|
||||
d.Clear();
|
||||
}
|
||||
|
||||
delete [] displays_;
|
||||
displays_ = NULL;
|
||||
|
||||
displays_size_ = 0;
|
||||
}
|
||||
|
||||
bool Chapter::ExpandDisplaysArray() {
|
||||
if (displays_size_ > displays_count_)
|
||||
return true; // nothing to do yet
|
||||
|
||||
const int size = (displays_size_ == 0) ? 1 : 2 * displays_size_;
|
||||
|
||||
Display* const displays = new (std::nothrow) Display[size]; // NOLINT
|
||||
if (displays == NULL)
|
||||
return false;
|
||||
|
||||
for (int idx = 0; idx < displays_count_; ++idx) {
|
||||
displays[idx] = displays_[idx]; // shallow copy
|
||||
}
|
||||
|
||||
delete [] displays_;
|
||||
|
||||
displays_ = displays;
|
||||
displays_size_ = size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64 Chapter::WriteAtom(IMkvWriter* writer) const {
|
||||
uint64 payload_size =
|
||||
// TODO(matthewjheaney): resolve ID issue
|
||||
EbmlElementSize(kMkvChapterUID, uid_) +
|
||||
EbmlElementSize(kMkvChapterTimeStart, start_timecode_) +
|
||||
EbmlElementSize(kMkvChapterTimeEnd, end_timecode_);
|
||||
|
||||
for (int idx = 0; idx < displays_count_; ++idx) {
|
||||
const Display& d = displays_[idx];
|
||||
payload_size += d.WriteDisplay(NULL);
|
||||
}
|
||||
|
||||
const uint64 atom_size =
|
||||
EbmlMasterElementSize(kMkvChapterAtom, payload_size) +
|
||||
payload_size;
|
||||
|
||||
if (writer == NULL)
|
||||
return atom_size;
|
||||
|
||||
const int64 start = writer->Position();
|
||||
|
||||
if (!WriteEbmlMasterElement(writer, kMkvChapterAtom, payload_size))
|
||||
return 0;
|
||||
|
||||
if (!WriteEbmlElement(writer, kMkvChapterUID, uid_))
|
||||
return 0;
|
||||
|
||||
if (!WriteEbmlElement(writer, kMkvChapterTimeStart, start_timecode_))
|
||||
return 0;
|
||||
|
||||
if (!WriteEbmlElement(writer, kMkvChapterTimeEnd, end_timecode_))
|
||||
return 0;
|
||||
|
||||
for (int idx = 0; idx < displays_count_; ++idx) {
|
||||
const Display& d = displays_[idx];
|
||||
|
||||
if (!d.WriteDisplay(writer))
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int64 stop = writer->Position();
|
||||
|
||||
if (stop >= start && uint64(stop - start) != atom_size)
|
||||
return 0;
|
||||
|
||||
return atom_size;
|
||||
}
|
||||
|
||||
void Chapter::Display::Init() {
|
||||
title_ = NULL;
|
||||
language_ = NULL;
|
||||
country_ = NULL;
|
||||
}
|
||||
|
||||
void Chapter::Display::Clear() {
|
||||
StrCpy(NULL, &title_);
|
||||
StrCpy(NULL, &language_);
|
||||
StrCpy(NULL, &country_);
|
||||
}
|
||||
|
||||
bool Chapter::Display::set_title(const char* title) {
|
||||
return StrCpy(title, &title_);
|
||||
}
|
||||
|
||||
bool Chapter::Display::set_language(const char* language) {
|
||||
return StrCpy(language, &language_);
|
||||
}
|
||||
|
||||
bool Chapter::Display::set_country(const char* country) {
|
||||
return StrCpy(country, &country_);
|
||||
}
|
||||
|
||||
uint64 Chapter::Display::WriteDisplay(IMkvWriter* writer) const {
|
||||
uint64 payload_size = EbmlElementSize(kMkvChapString, title_);
|
||||
|
||||
if (language_)
|
||||
payload_size += EbmlElementSize(kMkvChapLanguage, language_);
|
||||
|
||||
if (country_)
|
||||
payload_size += EbmlElementSize(kMkvChapCountry, country_);
|
||||
|
||||
const uint64 display_size =
|
||||
EbmlMasterElementSize(kMkvChapterDisplay, payload_size) +
|
||||
payload_size;
|
||||
|
||||
if (writer == NULL)
|
||||
return display_size;
|
||||
|
||||
const int64 start = writer->Position();
|
||||
|
||||
if (!WriteEbmlMasterElement(writer, kMkvChapterDisplay, payload_size))
|
||||
return 0;
|
||||
|
||||
if (!WriteEbmlElement(writer, kMkvChapString, title_))
|
||||
return 0;
|
||||
|
||||
if (language_) {
|
||||
if (!WriteEbmlElement(writer, kMkvChapLanguage, language_))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (country_) {
|
||||
if (!WriteEbmlElement(writer, kMkvChapCountry, country_))
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int64 stop = writer->Position();
|
||||
|
||||
if (stop >= start && uint64(stop - start) != display_size)
|
||||
return 0;
|
||||
|
||||
return display_size;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Chapters Class
|
||||
|
||||
Chapters::Chapters()
|
||||
: chapters_size_(0),
|
||||
chapters_count_(0),
|
||||
chapters_(NULL) {
|
||||
}
|
||||
|
||||
Chapters::~Chapters() {
|
||||
while (chapters_count_ > 0) {
|
||||
Chapter& chapter = chapters_[--chapters_count_];
|
||||
chapter.Clear();
|
||||
}
|
||||
|
||||
delete [] chapters_;
|
||||
chapters_ = NULL;
|
||||
}
|
||||
|
||||
int Chapters::Count() const {
|
||||
return chapters_count_;
|
||||
}
|
||||
|
||||
Chapter* Chapters::AddChapter(unsigned int* seed) {
|
||||
if (!ExpandChaptersArray())
|
||||
return NULL;
|
||||
|
||||
Chapter& chapter = chapters_[chapters_count_++];
|
||||
chapter.Init(seed);
|
||||
|
||||
return &chapter;
|
||||
}
|
||||
|
||||
bool Chapters::Write(IMkvWriter* writer) const {
|
||||
if (writer == NULL)
|
||||
return false;
|
||||
|
||||
const uint64 payload_size = WriteEdition(NULL); // return size only
|
||||
|
||||
if (!WriteEbmlMasterElement(writer, kMkvChapters, payload_size))
|
||||
return false;
|
||||
|
||||
const int64 start = writer->Position();
|
||||
|
||||
if (WriteEdition(writer) == 0) // error
|
||||
return false;
|
||||
|
||||
const int64 stop = writer->Position();
|
||||
|
||||
if (stop >= start && uint64(stop - start) != payload_size)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Chapters::ExpandChaptersArray() {
|
||||
if (chapters_size_ > chapters_count_)
|
||||
return true; // nothing to do yet
|
||||
|
||||
const int size = (chapters_size_ == 0) ? 1 : 2 * chapters_size_;
|
||||
|
||||
Chapter* const chapters = new (std::nothrow) Chapter[size]; // NOLINT
|
||||
if (chapters == NULL)
|
||||
return false;
|
||||
|
||||
for (int idx = 0; idx < chapters_count_; ++idx) {
|
||||
const Chapter& src = chapters_[idx];
|
||||
Chapter* const dst = chapters + idx;
|
||||
src.ShallowCopy(dst);
|
||||
}
|
||||
|
||||
delete [] chapters_;
|
||||
|
||||
chapters_ = chapters;
|
||||
chapters_size_ = size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64 Chapters::WriteEdition(IMkvWriter* writer) const {
|
||||
uint64 payload_size = 0;
|
||||
|
||||
for (int idx = 0; idx < chapters_count_; ++idx) {
|
||||
const Chapter& chapter = chapters_[idx];
|
||||
payload_size += chapter.WriteAtom(NULL);
|
||||
}
|
||||
|
||||
const uint64 edition_size =
|
||||
EbmlMasterElementSize(kMkvEditionEntry, payload_size) +
|
||||
payload_size;
|
||||
|
||||
if (writer == NULL) // return size only
|
||||
return edition_size;
|
||||
|
||||
const int64 start = writer->Position();
|
||||
|
||||
if (!WriteEbmlMasterElement(writer, kMkvEditionEntry, payload_size))
|
||||
return 0; // error
|
||||
|
||||
for (int idx = 0; idx < chapters_count_; ++idx) {
|
||||
const Chapter& chapter = chapters_[idx];
|
||||
|
||||
const uint64 chapter_size = chapter.WriteAtom(writer);
|
||||
if (chapter_size == 0) // error
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int64 stop = writer->Position();
|
||||
|
||||
if (stop >= start && uint64(stop - start) != edition_size)
|
||||
return 0;
|
||||
|
||||
return edition_size;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Cluster class
|
||||
|
||||
Cluster::Cluster(uint64 timecode, int64 cues_pos)
|
||||
: blocks_added_(0),
|
||||
@ -1477,6 +1825,8 @@ Segment::Segment()
|
||||
writer_cluster_(NULL),
|
||||
writer_cues_(NULL),
|
||||
writer_header_(NULL) {
|
||||
const time_t curr_time = time(NULL);
|
||||
seed_ = static_cast<unsigned int>(curr_time);
|
||||
}
|
||||
|
||||
Segment::~Segment() {
|
||||
@ -1608,7 +1958,7 @@ bool Segment::Finalize() {
|
||||
}
|
||||
|
||||
Track* Segment::AddTrack(int32 number) {
|
||||
Track* const track = new (std::nothrow) Track; // NOLINT
|
||||
Track* const track = new (std::nothrow) Track(&seed_); // NOLINT
|
||||
|
||||
if (!track)
|
||||
return NULL;
|
||||
@ -1621,20 +1971,24 @@ Track* Segment::AddTrack(int32 number) {
|
||||
return track;
|
||||
}
|
||||
|
||||
Chapter* Segment::AddChapter() {
|
||||
return chapters_.AddChapter(&seed_);
|
||||
}
|
||||
|
||||
uint64 Segment::AddVideoTrack(int32 width, int32 height, int32 number) {
|
||||
VideoTrack* const vid_track = new (std::nothrow) VideoTrack(); // NOLINT
|
||||
if (!vid_track)
|
||||
VideoTrack* const track = new (std::nothrow) VideoTrack(&seed_); // NOLINT
|
||||
if (!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);
|
||||
track->set_type(Tracks::kVideo);
|
||||
track->set_codec_id(Tracks::kVp8CodecId);
|
||||
track->set_width(width);
|
||||
track->set_height(height);
|
||||
|
||||
tracks_.AddTrack(vid_track, number);
|
||||
tracks_.AddTrack(track, number);
|
||||
has_video_ = true;
|
||||
|
||||
return vid_track->number();
|
||||
return track->number();
|
||||
}
|
||||
|
||||
bool Segment::AddCuePoint(uint64 timestamp, uint64 track) {
|
||||
@ -1663,18 +2017,18 @@ bool Segment::AddCuePoint(uint64 timestamp, uint64 track) {
|
||||
uint64 Segment::AddAudioTrack(int32 sample_rate,
|
||||
int32 channels,
|
||||
int32 number) {
|
||||
AudioTrack* const aud_track = new (std::nothrow) AudioTrack(); // NOLINT
|
||||
if (!aud_track)
|
||||
AudioTrack* const track = new (std::nothrow) AudioTrack(&seed_); // NOLINT
|
||||
if (!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);
|
||||
track->set_type(Tracks::kAudio);
|
||||
track->set_codec_id(Tracks::kVorbisCodecId);
|
||||
track->set_sample_rate(sample_rate);
|
||||
track->set_channels(channels);
|
||||
|
||||
tracks_.AddTrack(aud_track, number);
|
||||
tracks_.AddTrack(track, number);
|
||||
|
||||
return aud_track->number();
|
||||
return track->number();
|
||||
}
|
||||
|
||||
bool Segment::AddFrame(const uint8* frame,
|
||||
@ -1920,6 +2274,13 @@ bool Segment::WriteSegmentHeader() {
|
||||
if (!tracks_.Write(writer_header_))
|
||||
return false;
|
||||
|
||||
if (chapters_.Count() > 0) {
|
||||
if (!seek_head_.AddSeekEntry(kMkvChapters, MaxOffset()))
|
||||
return false;
|
||||
if (!chapters_.Write(writer_header_))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chunking_ && (mode_ == kLive || !writer_header_->Seekable())) {
|
||||
if (!chunk_writer_header_)
|
||||
return false;
|
||||
|
192
mkvmuxer.hpp
192
mkvmuxer.hpp
@ -17,6 +17,7 @@
|
||||
namespace mkvmuxer {
|
||||
|
||||
class MkvWriter;
|
||||
class Segment;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Interface used by the mkvmuxer to write out the Mkv data.
|
||||
@ -266,7 +267,8 @@ class ContentEncoding {
|
||||
// Track element.
|
||||
class Track {
|
||||
public:
|
||||
Track();
|
||||
// The |seed| parameter is used to synthesize a UID for the track.
|
||||
explicit Track(unsigned int* seed);
|
||||
virtual ~Track();
|
||||
|
||||
// Adds a ContentEncoding element to the Track. Returns true on success.
|
||||
@ -308,9 +310,6 @@ class Track {
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns a random number to be used for the Track UID.
|
||||
static uint64 MakeUID();
|
||||
|
||||
// Track element names
|
||||
char* codec_id_;
|
||||
uint8* codec_private_;
|
||||
@ -329,9 +328,6 @@ class Track {
|
||||
// Number of ContentEncoding elements added.
|
||||
uint32 content_encoding_entries_size_;
|
||||
|
||||
// Flag telling if the rand call was seeded.
|
||||
static bool is_seeded_;
|
||||
|
||||
LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Track);
|
||||
};
|
||||
|
||||
@ -348,7 +344,8 @@ class VideoTrack : public Track {
|
||||
kSideBySideRightIsFirst = 11
|
||||
};
|
||||
|
||||
VideoTrack();
|
||||
// The |seed| parameter is used to synthesize a UID for the track.
|
||||
explicit VideoTrack(unsigned int* seed);
|
||||
virtual ~VideoTrack();
|
||||
|
||||
// Returns the size in bytes for the payload of the Track element plus the
|
||||
@ -392,7 +389,8 @@ class VideoTrack : public Track {
|
||||
// Track that has audio specific elements.
|
||||
class AudioTrack : public Track {
|
||||
public:
|
||||
AudioTrack();
|
||||
// The |seed| parameter is used to synthesize a UID for the track.
|
||||
explicit AudioTrack(unsigned int* seed);
|
||||
virtual ~AudioTrack();
|
||||
|
||||
// Returns the size in bytes for the payload of the Track element plus the
|
||||
@ -468,6 +466,170 @@ class Tracks {
|
||||
LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tracks);
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Chapter element
|
||||
//
|
||||
class Chapter {
|
||||
public:
|
||||
// Set the identifier for this chapter. (This corresponds to the
|
||||
// Cue Identifier line in WebVTT.)
|
||||
// TODO(matthewjheaney): the actual serialization of this item in
|
||||
// MKV is pending.
|
||||
bool set_id(const char* id);
|
||||
|
||||
// Converts the nanosecond start and stop times of this chapter to
|
||||
// their corresponding timecode values, and stores them that way.
|
||||
void set_time(const Segment& segment,
|
||||
uint64 start_time_ns,
|
||||
uint64 end_time_ns);
|
||||
|
||||
// Add a title string to this chapter, per the semantics described
|
||||
// here:
|
||||
// http://www.matroska.org/technical/specs/index.html
|
||||
//
|
||||
// The title ("chapter string") is a UTF-8 string.
|
||||
//
|
||||
// The language has ISO 639-2 representation, described here:
|
||||
// http://www.loc.gov/standards/iso639-2/englangn.html
|
||||
// http://www.loc.gov/standards/iso639-2/php/English_list.php
|
||||
// If you specify NULL as the language value, this implies
|
||||
// English ("eng").
|
||||
//
|
||||
// The country value corresponds to the codes listed here:
|
||||
// http://www.iana.org/domains/root/db/
|
||||
//
|
||||
// The function returns false if the string could not be allocated.
|
||||
bool add_string(const char* title,
|
||||
const char* language,
|
||||
const char* country);
|
||||
|
||||
private:
|
||||
friend class Chapters;
|
||||
|
||||
// For storage of chapter titles that differ by language.
|
||||
class Display {
|
||||
public:
|
||||
// Establish representation invariant for new Display object.
|
||||
void Init();
|
||||
|
||||
// Reclaim resources, in anticipation of destruction.
|
||||
void Clear();
|
||||
|
||||
// Copies the title to the |title_| member. Returns false on
|
||||
// error.
|
||||
bool set_title(const char* title);
|
||||
|
||||
// Copies the language to the |language_| member. Returns false
|
||||
// on error.
|
||||
bool set_language(const char* language);
|
||||
|
||||
// Copies the country to the |country_| member. Returns false on
|
||||
// error.
|
||||
bool set_country(const char* country);
|
||||
|
||||
// If |writer| is non-NULL, serialize the Display sub-element of
|
||||
// the Atom into the stream. Returns the Display element size on
|
||||
// success, 0 if error.
|
||||
uint64 WriteDisplay(IMkvWriter* writer) const;
|
||||
|
||||
private:
|
||||
char* title_;
|
||||
char* language_;
|
||||
char* country_;
|
||||
};
|
||||
|
||||
Chapter();
|
||||
~Chapter();
|
||||
|
||||
// Establish the representation invariant for a newly-created
|
||||
// Chapter object. The |seed| parameter is used to create the UID
|
||||
// for this chapter atom.
|
||||
void Init(unsigned int* seed);
|
||||
|
||||
// Copies this Chapter object to a different one. This is used when
|
||||
// expanding a plain array of Chapter objects (see Chapters).
|
||||
void ShallowCopy(Chapter* dst) const;
|
||||
|
||||
// Reclaim resources used by this Chapter object, pending its
|
||||
// destruction.
|
||||
void Clear();
|
||||
|
||||
// If there is no storage remaining on the |displays_| array for a
|
||||
// new display object, creates a new, longer array and copies the
|
||||
// existing Display objects to the new array. Returns false if the
|
||||
// array cannot be expanded.
|
||||
bool ExpandDisplaysArray();
|
||||
|
||||
// If |writer| is non-NULL, serialize the Atom sub-element into the
|
||||
// stream. Returns the total size of the element on success, 0 if
|
||||
// error.
|
||||
uint64 WriteAtom(IMkvWriter* writer) const;
|
||||
|
||||
// The string identifier for this chapter (corresponds to WebVTT cue
|
||||
// identifier).
|
||||
char* id_;
|
||||
|
||||
// Start timecode of the chapter.
|
||||
uint64 start_timecode_;
|
||||
|
||||
// Stop timecode of the chapter.
|
||||
uint64 end_timecode_;
|
||||
|
||||
// The binary identifier for this chapter.
|
||||
uint64 uid_;
|
||||
|
||||
// The Atom element can contain multiple Display sub-elements, as
|
||||
// the same logical title can be rendered in different languages.
|
||||
Display* displays_;
|
||||
|
||||
// The physical length (total size) of the |displays_| array.
|
||||
int displays_size_;
|
||||
|
||||
// The logical length (number of active elements) on the |displays_|
|
||||
// array.
|
||||
int displays_count_;
|
||||
|
||||
LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapter);
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Chapters element
|
||||
//
|
||||
class Chapters {
|
||||
public:
|
||||
Chapters();
|
||||
~Chapters();
|
||||
|
||||
Chapter* AddChapter(unsigned int* seed);
|
||||
|
||||
// Returns the number of chapters that have been added.
|
||||
int Count() const;
|
||||
|
||||
// Output the Chapters element to the writer. Returns true on success.
|
||||
bool Write(IMkvWriter* writer) const;
|
||||
|
||||
private:
|
||||
// Expands the chapters_ array if there is not enough space to contain
|
||||
// another chapter object. Returns true on success.
|
||||
bool ExpandChaptersArray();
|
||||
|
||||
// If |writer| is non-NULL, serialize the Edition sub-element of the
|
||||
// Chapters element into the stream. Returns the Edition element
|
||||
// size on success, 0 if error.
|
||||
uint64 WriteEdition(IMkvWriter* writer) const;
|
||||
|
||||
// Total length of the chapters_ array.
|
||||
int chapters_size_;
|
||||
|
||||
// Number of active chapters on the chapters_ array.
|
||||
int chapters_count_;
|
||||
|
||||
// Array for storage of chapter objects.
|
||||
Chapter* chapters_;
|
||||
|
||||
LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapters);
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Cluster element
|
||||
//
|
||||
@ -606,7 +768,7 @@ class SeekHead {
|
||||
|
||||
private:
|
||||
// We are going to put a cap on the number of Seek Entries.
|
||||
const static int32 kSeekEntryCount = 4;
|
||||
const static int32 kSeekEntryCount = 5;
|
||||
|
||||
// Returns the maximum size in bytes of one seek entry.
|
||||
uint64 MaxEntrySize() const;
|
||||
@ -701,6 +863,11 @@ class Segment {
|
||||
// the track number.
|
||||
uint64 AddAudioTrack(int32 sample_rate, int32 channels, int32 number);
|
||||
|
||||
// Adds an empty chapter to the chapters of this segment. Returns
|
||||
// non-NULL on success. After adding the chapter, the caller should
|
||||
// populate its fields via the Chapter member functions.
|
||||
Chapter* AddChapter();
|
||||
|
||||
// Adds a cue point to the Cues element. |timestamp| is the time in
|
||||
// nanoseconds of the cue's time. |track| is the Track of the Cue. Returns
|
||||
// true on success.
|
||||
@ -760,6 +927,7 @@ class Segment {
|
||||
Cues* GetCues() { return &cues_; }
|
||||
|
||||
// Returns the Segment Information object.
|
||||
const SegmentInfo* GetSegmentInfo() const { return &segment_info_; }
|
||||
SegmentInfo* GetSegmentInfo() { return &segment_info_; }
|
||||
|
||||
// Search the Tracks and return the track that matches |track_number|.
|
||||
@ -846,11 +1014,15 @@ class Segment {
|
||||
// was necessary but creation was not successful.
|
||||
bool DoNewClusterProcessing(uint64 track_num, uint64 timestamp_ns, bool key);
|
||||
|
||||
// Seeds the random number generator used to make UIDs.
|
||||
unsigned int seed_;
|
||||
|
||||
// WebM elements
|
||||
Cues cues_;
|
||||
SeekHead seek_head_;
|
||||
SegmentInfo segment_info_;
|
||||
Tracks tracks_;
|
||||
Chapters chapters_;
|
||||
|
||||
// Number of chunks written.
|
||||
int chunk_count_;
|
||||
|
13
webmids.hpp
13
webmids.hpp
@ -113,8 +113,19 @@ enum MkvId {
|
||||
kMkvCueTrack = 0xF7,
|
||||
kMkvCueClusterPosition = 0xF1,
|
||||
kMkvCueBlockNumber = 0x5378,
|
||||
//Chapters
|
||||
kMkvChapters = 0x1043A770,
|
||||
kMkvEditionEntry = 0x45B9,
|
||||
kMkvChapterAtom = 0xB6,
|
||||
kMkvChapterUID = 0x73C4,
|
||||
kMkvChapterTimeStart = 0x91,
|
||||
kMkvChapterTimeEnd = 0x92,
|
||||
kMkvChapterDisplay = 0x80,
|
||||
kMkvChapString = 0x85,
|
||||
kMkvChapLanguage = 0x437C,
|
||||
kMkvChapCountry = 0x437E
|
||||
};
|
||||
|
||||
} // end namespace mkvmuxer
|
||||
|
||||
#endif // WEBMIDS_HPP
|
||||
#endif // WEBMIDS_HPP
|
||||
|
Loading…
x
Reference in New Issue
Block a user