Merge "Add output of elements to separate files."

This commit is contained in:
Tom Finegan 2012-01-18 13:54:56 -08:00 committed by Gerrit Code Review
commit f7aa8ab33d
4 changed files with 320 additions and 55 deletions

View File

@ -17,6 +17,7 @@
#include <new>
#include "mkvmuxerutil.hpp"
#include "mkvwriter.hpp"
#include "webmids.hpp"
namespace mkvmuxer {
@ -985,18 +986,16 @@ bool Tracks::Write(IMkvWriter* writer) const {
//
// Cluster Class
Cluster::Cluster(uint64 timecode, IMkvWriter* writer)
Cluster::Cluster(uint64 timecode, IMkvWriter* writer, int64 cues_pos)
: blocks_added_(0),
finalized_(false),
header_written_(false),
payload_size_(0),
position_for_cues_(-1),
position_for_cues_(cues_pos),
size_position_(-1),
timecode_(timecode),
writer_(writer) {
// TODO(fgalligan): Create an Init function.
assert(writer_);
position_for_cues_ = writer_->Position();
}
Cluster::~Cluster() {
@ -1057,6 +1056,14 @@ bool Cluster::Finalize() {
return true;
}
uint64 Cluster::Size() const {
const uint64 element_size =
EbmlElementSize(kMkvCluster,
0xFFFFFFFFFFFFFFFFULL,
true) + payload_size_;
return element_size;
}
bool Cluster::WriteClusterHeader() {
assert(!finalized_);
@ -1346,7 +1353,14 @@ void SegmentInfo::set_writing_app(const char* app) {
// Segment Class
Segment::Segment(IMkvWriter* writer)
: cluster_list_(NULL),
: chunk_count_(0),
chunk_name_(NULL),
chunk_writer_cluster_(NULL),
chunk_writer_cues_(NULL),
chunk_writer_header_(NULL),
chunking_(false),
chunking_base_name_(NULL),
cluster_list_(NULL),
cluster_list_capacity_(0),
cluster_list_size_(0),
cues_track_(0),
@ -1364,8 +1378,10 @@ Segment::Segment(IMkvWriter* writer)
output_cues_(true),
payload_pos_(0),
size_position_(0),
writer_(writer) {
assert(writer_);
writer_cluster_(writer),
writer_cues_(writer),
writer_header_(writer) {
assert(writer_cluster_);
// TODO(fgalligan): Create an Init function for Segment.
segment_info_.Init();
@ -1387,6 +1403,22 @@ Segment::~Segment() {
}
delete [] frames_;
}
delete [] chunk_name_;
delete [] chunking_base_name_;
if (chunk_writer_cluster_) {
chunk_writer_cluster_->Close();
delete chunk_writer_cluster_;
}
if (chunk_writer_cues_) {
chunk_writer_cues_->Close();
delete chunk_writer_cues_;
}
if (chunk_writer_header_) {
chunk_writer_header_->Close();
delete chunk_writer_header_;
}
}
bool Segment::Finalize() {
@ -1403,40 +1435,64 @@ bool Segment::Finalize() {
return false;
}
if (chunking_ && chunk_writer_cluster_) {
chunk_writer_cluster_->Close();
chunk_count_++;
}
const double duration =
static_cast<double>(last_timestamp_) / segment_info_.timecode_scale();
segment_info_.set_duration(duration);
if (!segment_info_.Finalize(writer_))
if (!segment_info_.Finalize(writer_header_))
return false;
// TODO(fgalligan): Add support for putting the Cues at the front.
if (!seek_head_.AddSeekEntry(kMkvCues, writer_->Position() - payload_pos_))
if (!seek_head_.AddSeekEntry(kMkvCues, MaxOffset()))
return false;
if (!cues_.Write(writer_))
if (chunking_) {
assert(chunk_writer_cues_);
char* name = NULL;
if (!UpdateChunkName("cues", &name))
return false;
const bool cues_open = chunk_writer_cues_->Open(name);
delete [] name;
if (!cues_open)
return false;
}
if (!cues_.Write(writer_cues_))
return false;
if (!seek_head_.Finalize(writer_))
if (!seek_head_.Finalize(writer_header_))
return false;
if (writer_->Seekable()) {
if (writer_header_->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;
const int64 pos = writer_header_->Position();
const int64 segment_size = MaxOffset();
assert(segment_size > 0);
if (writer_->Position(size_position_))
if (writer_header_->Position(size_position_))
return false;
if (WriteUIntSize(writer_, segment_size, 8))
if (WriteUIntSize(writer_header_, segment_size, 8))
return false;
if (writer_->Position(pos))
if (writer_header_->Position(pos))
return false;
}
if (chunking_) {
// Do not close any writers until the segment size has been written,
// otherwise the size may be off.
assert(chunk_writer_cues_);
chunk_writer_cues_->Close();
assert(chunk_writer_header_);
chunk_writer_header_->Close();
}
}
return true;
@ -1555,6 +1611,30 @@ bool Segment::AddFrame(const uint8* frame,
if (!WriteFramesLessThan(timestamp))
return false;
if (mode_ == kFile) {
if (cluster_list_size_ > 0) {
// Update old cluster's size
Cluster* const old_cluster = cluster_list_[cluster_list_size_-1];
assert(old_cluster);
if (!old_cluster->Finalize())
return false;
}
if (output_cues_)
new_cuepoint_ = true;
}
if (chunking_ && cluster_list_size_ > 0) {
chunk_writer_cluster_->Close();
chunk_count_++;
if (!UpdateChunkName("chk", &chunk_name_))
return false;
if (!chunk_writer_cluster_->Open(chunk_name_))
return false;
}
uint64 audio_timecode = 0;
uint64 timecode = timestamp / segment_info_.timecode_scale();
if (frames_size_ > 0) {
@ -1569,26 +1649,13 @@ bool Segment::AddFrame(const uint8* frame,
// 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_);
cluster_list_[cluster_list_size_] =
new (std::nothrow) Cluster(timecode,
writer_cluster_,
MaxOffset());
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;
}
@ -1626,6 +1693,85 @@ void Segment::OutputCues(bool output_cues) {
output_cues_ = output_cues;
}
bool Segment::SetChunking(bool chunking, const char* filename) {
if (chunk_count_ > 0)
return false;
if (chunking) {
if (!filename)
return false;
// Check if we are being set to what is already set.
if (chunking_ && !strcmp(filename, chunking_base_name_))
return true;
const size_t name_length = strlen(filename) + 1;
char* const temp = new (std::nothrow) char[name_length];
if (!temp)
return false;
#ifdef _MSC_VER
strcpy_s(temp, name_length, filename);
#else
strcpy(temp, filename);
#endif
delete [] chunking_base_name_;
chunking_base_name_ = temp;
if (!UpdateChunkName("chk", &chunk_name_))
return false;
if (!chunk_writer_cluster_) {
chunk_writer_cluster_ = new (std::nothrow) MkvWriter();
if (!chunk_writer_cluster_)
return false;
}
if (!chunk_writer_cues_) {
chunk_writer_cues_ = new (std::nothrow) MkvWriter();
if (!chunk_writer_cues_)
return false;
}
if (!chunk_writer_header_) {
chunk_writer_header_ = new (std::nothrow) MkvWriter();
if (!chunk_writer_header_)
return false;
}
if (!chunk_writer_cluster_->Open(chunk_name_))
return false;
const size_t header_length = strlen(filename) + strlen(".hdr") + 1;
char* const header = new (std::nothrow) char[header_length];
if (!header)
return false;
#ifdef _MSC_VER
strcpy_s(header, header_length - strlen(".hdr"), chunking_base_name_);
strcat_s(header, header_length, ".hdr");
#else
strcpy(header, chunking_base_name_);
strcat(header, ".hdr");
#endif
if (!chunk_writer_header_->Open(header)) {
delete [] header;
return false;
}
writer_cluster_ = chunk_writer_cluster_;
writer_cues_ = chunk_writer_cues_;
writer_header_ = chunk_writer_header_;
delete [] header;
}
chunking_ = chunking;
return true;
}
bool Segment::CuesTrack(uint64 track_number) {
const Track* const track = GetTrackByNumber(track_number);
if (!track)
@ -1641,44 +1787,50 @@ Track* Segment::GetTrackByNumber(uint64 track_number) const {
bool Segment::WriteSegmentHeader() {
// TODO(fgalligan): Support more than one segment.
if (!WriteEbmlHeader(writer_))
if (!WriteEbmlHeader(writer_header_))
return false;
// Write "unknown" (-1) as segment size value. If mode is kFile, Segment
// will write over duration when the file is finalized.
if (SerializeInt(writer_, kMkvSegment, 4))
if (SerializeInt(writer_header_, kMkvSegment, 4))
return false;
// Save for later.
size_position_ = writer_->Position();
size_position_ = writer_header_->Position();
// Write "unknown" (EBML coded -1) as segment size value. We need to write 8
// bytes because if we are going to overwrite the segment size later we do
// not know how big our segment will be.
if (SerializeInt(writer_, kEbmlUnknownValue, 8))
if (SerializeInt(writer_header_, kEbmlUnknownValue, 8))
return false;
payload_pos_ = writer_->Position();
payload_pos_ = writer_header_->Position();
if (mode_ == kFile && writer_->Seekable()) {
if (mode_ == kFile && writer_header_->Seekable()) {
// Set the duration > 0.0 so SegmentInfo will write out the duration. When
// the muxer is done writing we will set the correct duration and have
// SegmentInfo upadte it.
segment_info_.set_duration(1.0);
if (!seek_head_.Write(writer_))
if (!seek_head_.Write(writer_header_))
return false;
}
if (!seek_head_.AddSeekEntry(kMkvInfo, writer_->Position() - payload_pos_))
if (!seek_head_.AddSeekEntry(kMkvInfo, MaxOffset()))
return false;
if (!segment_info_.Write(writer_))
if (!segment_info_.Write(writer_header_))
return false;
if (!seek_head_.AddSeekEntry(kMkvTracks, writer_->Position() - payload_pos_))
if (!seek_head_.AddSeekEntry(kMkvTracks, MaxOffset()))
return false;
if (!tracks_.Write(writer_))
if (!tracks_.Write(writer_header_))
return false;
if (chunking_ && (mode_ == kLive || !writer_header_->Seekable())) {
assert(chunk_writer_header_);
chunk_writer_header_->Close();
}
header_written_ = true;
return true;
@ -1689,8 +1841,7 @@ bool Segment::CheckHeaderInfo() {
if (!WriteSegmentHeader())
return false;
if (!seek_head_.AddSeekEntry(kMkvCluster,
writer_->Position() - payload_pos_))
if (!seek_head_.AddSeekEntry(kMkvCluster, MaxOffset()))
return false;
if (output_cues_ && cues_track_ == 0) {
@ -1727,7 +1878,7 @@ bool Segment::AddCuePoint(uint64 timestamp) {
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_cluster_pos(cluster->position_for_cues());
cue->set_track(cues_track_);
if (!cues_.AddCue(cue))
return false;
@ -1736,6 +1887,53 @@ bool Segment::AddCuePoint(uint64 timestamp) {
return true;
}
bool Segment::UpdateChunkName(const char* ext, char** name) const {
if (!name || !ext)
return false;
char ext_chk[64];
#ifdef _MSC_VER
sprintf_s(ext_chk, 64, "_%06d.%s", chunk_count_, ext);
#else
snprintf(ext_chk, 64, "_%06d.%s", chunk_count_, ext);
#endif
const size_t length = strlen(chunking_base_name_) + strlen(ext_chk) + 1;
char* const str = new (std::nothrow) char[length];
if (!str)
return false;
#ifdef _MSC_VER
strcpy_s(str, length-strlen(ext_chk), chunking_base_name_);
strcat_s(str, length, ext_chk);
#else
strcpy(chunk_name_, chunking_base_name_);
strcat(chunk_name_, ext_chk);
#endif
delete [] *name;
*name = str;
return true;
}
int64 Segment::MaxOffset() {
assert(writer_header_);
int64 offset = writer_header_->Position() - payload_pos_;
if (chunking_) {
for (int32 i = 0; i < cluster_list_size_; ++i) {
Cluster* const cluster = cluster_list_[i];
offset += cluster->Size();
}
if (writer_cues_)
offset += writer_cues_->Position();
}
return offset;
}
bool Segment::QueueFrame(Frame* frame) {
const int32 new_size = frames_size_ + 1;

View File

@ -16,6 +16,8 @@
namespace mkvmuxer {
class MkvWriter;
///////////////////////////////////////////////////////////////
// Interface used by the mkvmuxer to write out the Mkv data.
class IMkvWriter {
@ -436,8 +438,10 @@ class Tracks {
// Cluster element
class Cluster {
public:
// |timecode| is the absolute timecode of the cluster.
Cluster(uint64 timecode, IMkvWriter* writer);
// |timecode| is the absolute timecode of the cluster. |cues_pos| is the
// position for the cluster within the segment that should be written in
// the cues element.
Cluster(uint64 timecode, IMkvWriter* writer, int64 cues_pos);
~Cluster();
// Adds a frame to be output in the file. The frame is written out through
@ -462,6 +466,9 @@ class Cluster {
// cluster's size if |writer_| is seekable. Returns true on success.
bool Finalize();
// Returns the size in bytes for the entire Cluster element.
uint64 Size() const;
int32 blocks_added() const { return blocks_added_; }
uint64 payload_size() const { return payload_size_; }
int64 position_for_cues() const { return position_for_cues_; }
@ -653,6 +660,18 @@ class Segment {
// Toggles whether to output a cues element.
void OutputCues(bool output_cues);
// Sets if the muxer will output files in chunks or not. |chunking| is a
// flag telling whether or not to turn on chunking. |filename| is the base
// filename for the chunk files. The header chunk file will be named
// |filename|.hdr and the data chunks will be named
// |filename|_XXXXXX.chk. Chunking implies that the muxer will be writing
// to files so the muxer will use the default MkvWriter class to control
// what data is written to what files. Returns true on success.
// TODO: Should we change the IMkvWriter Interface to add Open and Close?
// That will force the interface to be dependent on files.
bool SetChunking(bool chunking, const char* filename);
bool chunking() const { return chunking_; }
uint64 cues_track() const { return cues_track_; }
void set_max_cluster_duration(uint64 max_cluster_duration) {
max_cluster_duration_ = max_cluster_duration;
@ -677,6 +696,16 @@ class Segment {
// Cues elements.
bool CheckHeaderInfo();
// Sets |name| according to how many chunks have been written. |ext| is the
// file extension. |name| must be deleted by the calling app. Returns true
// on success.
bool UpdateChunkName(const char* ext, char** name) const;
// Returns the maximum offset within the segment's payload. When chunking
// this function is needed to determine offsets of elements within the
// chunked files. Returns -1 on error.
int64 MaxOffset();
// Adds the frame to our frame array.
bool QueueFrame(Frame* frame);
@ -699,6 +728,31 @@ class Segment {
SegmentInfo segment_info_;
Tracks tracks_;
// Number of chunks written.
int chunk_count_;
// Current chunk filename.
char* chunk_name_;
// Default MkvWriter object created by this class used for writing clusters
// out in separate files.
MkvWriter* chunk_writer_cluster_;
// Default MkvWriter object created by this class used for writing Cues
// element out to a file.
MkvWriter* chunk_writer_cues_;
// Default MkvWriter object created by this class used for writing the
// Matroska header out to a file.
MkvWriter* chunk_writer_header_;
// Flag telling whether or not the muxer is chunking output to multiple
// files.
bool chunking_;
// Base filename for the chunked files.
char* chunking_base_name_;
// List of clusters.
Cluster** cluster_list_;
@ -763,8 +817,10 @@ class Segment {
// The file position of the element's size.
int64 size_position_;
// Pointer to the writer object. Not owned by this class.
IMkvWriter* writer_;
// Pointer to the writer objects. Not owned by this class.
IMkvWriter* writer_cluster_;
IMkvWriter* writer_cues_;
IMkvWriter* writer_header_;
LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Segment);
};

View File

@ -63,7 +63,9 @@ void MkvWriter::Close() {
}
int64 MkvWriter::Position() const {
assert(file_);
if (!file_)
return 0;
#ifdef _MSC_VER
return _ftelli64(file_);
#else

View File

@ -38,6 +38,7 @@ void Usage() {
printf(" -switch_tracks <int> >0 switches tracks in output\n");
printf(" -audio_track_number <int> >0 Changes the audio track number\n");
printf(" -video_track_number <int> >0 Changes the video track number\n");
printf(" -chunking <string> Chunk output\n");
printf("\n");
printf("Video options:\n");
printf(" -display_width <int> Display width in pixels\n");
@ -68,6 +69,8 @@ int main(int argc, char* argv[]) {
bool switch_tracks = false;
int audio_track_number = 0; // 0 tells muxer to decide.
int video_track_number = 0; // 0 tells muxer to decide.
bool chunking = false;
const char* chunk_name = NULL;
bool output_cues_block_number = true;
@ -109,6 +112,9 @@ int main(int argc, char* argv[]) {
audio_track_number = strtol(argv[++i], &end, 10);
} else if (!strcmp("-video_track_number", argv[i])) {
video_track_number = strtol(argv[++i], &end, 10);
} else if (!strcmp("-chunking", argv[i])) {
chunking = true;
chunk_name = argv[++i];
} else if (!strcmp("-display_width", argv[i])) {
display_width = strtol(argv[++i], &end, 10);
} else if (!strcmp("-display_height", argv[i])) {
@ -171,6 +177,9 @@ int main(int argc, char* argv[]) {
else
muxer_segment.set_mode(mkvmuxer::Segment::kFile);
if (chunking)
muxer_segment.SetChunking(true, chunk_name);
if (max_cluster_duration > 0)
muxer_segment.set_max_cluster_duration(max_cluster_duration);
if (max_cluster_size > 0)