Option to write timecode using fixed # of bytes

Add an option to write the cluster timecode using exactly 8 bytes
irrespective of the actual number of bytes required to write it as
an ebml integer.

This could be useful for players that want to rewrite the timecode
in-place (e.g. live streaming).

Change-Id: I1a049251f00ab65591d71e169129f5145a8c8863
This commit is contained in:
Vignesh Venkatasubramanian 2016-04-06 12:48:42 -07:00
parent c1991fea81
commit eb50da8e38
7 changed files with 110 additions and 30 deletions

View File

@ -2112,9 +2112,10 @@ bool Tags::ExpandTagsArray() {
// Cluster class
Cluster::Cluster(uint64_t timecode, int64_t cues_pos, uint64_t timecode_scale,
bool write_last_frame_with_duration)
bool write_last_frame_with_duration, bool fixed_size_timecode)
: blocks_added_(0),
finalized_(false),
fixed_size_timecode_(fixed_size_timecode),
header_written_(false),
payload_size_(0),
position_for_cues_(cues_pos),
@ -2386,9 +2387,12 @@ bool Cluster::WriteClusterHeader() {
if (SerializeInt(writer_, kEbmlUnknownValue, 8))
return false;
if (!WriteEbmlElement(writer_, libwebm::kMkvTimecode, timecode()))
if (!WriteEbmlElement(writer_, libwebm::kMkvTimecode, timecode(),
fixed_size_timecode_ ? 8 : 0)) {
return false;
AddPayloadSize(EbmlElementSize(libwebm::kMkvTimecode, timecode()));
}
AddPayloadSize(EbmlElementSize(libwebm::kMkvTimecode, timecode(),
fixed_size_timecode_ ? 8 : 0));
header_written_ = true;
return true;
@ -2720,6 +2724,7 @@ Segment::Segment()
new_cuepoint_(false),
output_cues_(true),
accurate_cluster_duration_(false),
fixed_size_cluster_timecode_(false),
payload_pos_(0),
size_position_(0),
doc_type_version_(kDefaultDocTypeVersion),
@ -3186,6 +3191,10 @@ void Segment::AccurateClusterDuration(bool accurate_cluster_duration) {
accurate_cluster_duration_ = accurate_cluster_duration;
}
void Segment::UseFixedSizeClusterTimecode(bool fixed_size_cluster_timecode) {
fixed_size_cluster_timecode_ = fixed_size_cluster_timecode;
}
bool Segment::SetChunking(bool chunking, const char* filename) {
if (chunk_count_ > 0)
return false;
@ -3490,7 +3499,7 @@ bool Segment::MakeNewCluster(uint64_t frame_timestamp_ns) {
const int64_t offset = MaxOffset();
cluster = new (std::nothrow)
Cluster(cluster_timecode, offset, segment_info_.timecode_scale(),
accurate_cluster_duration_);
accurate_cluster_duration_, fixed_size_cluster_timecode_);
if (!cluster)
return false;
@ -3757,4 +3766,4 @@ bool Segment::WriteFramesLessThan(uint64_t timestamp) {
return true;
}
} // namespace mkvmuxer
} // namespace mkvmuxer

View File

@ -993,7 +993,8 @@ class Cluster {
// position for the cluster within the segment that should be written in
// the cues element. |timecode_scale| is the timecode scale of the segment.
Cluster(uint64_t timecode, int64_t cues_pos, uint64_t timecode_scale,
bool write_last_frame_with_duration = false);
bool write_last_frame_with_duration = false,
bool fixed_size_timecode = false);
~Cluster();
bool Init(IMkvWriter* ptr_writer);
@ -1137,6 +1138,10 @@ class Cluster {
// Flag telling if the cluster has been closed.
bool finalized_;
// Flag indicating whether the cluster's timecode will always be written out
// using 8 bytes.
bool fixed_size_timecode_;
// Flag telling if the cluster's header has been written.
bool header_written_;
@ -1448,6 +1453,9 @@ class Segment {
// Toggles whether to write the last frame in each Cluster with Duration.
void AccurateClusterDuration(bool accurate_cluster_duration);
// Toggles whether to write the Cluster Timecode using exactly 8 bytes.
void UseFixedSizeClusterTimecode(bool fixed_size_cluster_timecode);
// 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
@ -1654,6 +1662,9 @@ class Segment {
// element in it.
bool accurate_cluster_duration_;
// Flag whether or not to write the Cluster Timecode using exactly 8 bytes.
bool fixed_size_cluster_timecode_;
// The size of the EBML header, used to validate the header if
// WriteEbmlHeader() is called more than once.
int32_t ebml_header_size_;
@ -1681,4 +1692,4 @@ class Segment {
} // namespace mkvmuxer
#endif // MKVMUXER_MKVMUXER_H_
#endif // MKVMUXER_MKVMUXER_H_

View File

@ -246,11 +246,15 @@ uint64_t EbmlElementSize(uint64_t type, int64_t value) {
}
uint64_t EbmlElementSize(uint64_t type, uint64_t value) {
return EbmlElementSize(type, value, 0);
}
uint64_t EbmlElementSize(uint64_t type, uint64_t value, uint64_t fixed_size) {
// Size of EBML ID
int32_t ebml_size = GetUIntSize(type);
// Datasize
ebml_size += GetUIntSize(value);
ebml_size += (fixed_size > 0) ? fixed_size : GetUIntSize(value);
// Size of Datasize
ebml_size++;
@ -432,13 +436,23 @@ bool WriteEbmlMasterElement(IMkvWriter* writer, uint64_t type, uint64_t size) {
}
bool WriteEbmlElement(IMkvWriter* writer, uint64_t type, uint64_t value) {
return WriteEbmlElement(writer, type, value, 0);
}
bool WriteEbmlElement(IMkvWriter* writer, uint64_t type, uint64_t value,
uint64_t fixed_size) {
if (!writer)
return false;
if (WriteID(writer, type))
return false;
const uint64_t size = GetUIntSize(value);
uint64_t size = GetUIntSize(value);
if (fixed_size > 0) {
if (size > fixed_size)
return false;
size = fixed_size;
}
if (WriteUInt(writer, size))
return false;

View File

@ -33,6 +33,11 @@ uint64_t EbmlElementSize(uint64_t type, const char* value);
uint64_t EbmlElementSize(uint64_t type, const uint8_t* value, uint64_t size);
uint64_t EbmlDateElementSize(uint64_t type);
// Returns the size in bytes of the element assuming that the element was
// written using |fixed_size| bytes. If |fixed_size| is set to zero, then it
// computes the necessary number of bytes based on |value|.
uint64_t EbmlElementSize(uint64_t type, uint64_t value, uint64_t fixed_size);
// Creates an EBML coded number from |value| and writes it out. The size of
// the coded number is determined by the value of |value|. |value| must not
// be in a coded form. Returns 0 on success.
@ -59,6 +64,13 @@ bool WriteEbmlElement(IMkvWriter* writer, uint64_t type, const uint8_t* value,
uint64_t size);
bool WriteEbmlDateElement(IMkvWriter* writer, uint64_t type, int64_t value);
// Output an Mkv non-master element using fixed size. The element will be
// written out using exactly |fixed_size| bytes. If |fixed_size| is set to zero
// then it computes the necessary number of bytes based on |value|. Returns true
// if the element was written.
bool WriteEbmlElement(IMkvWriter* writer, uint64_t type, uint64_t value,
uint64_t fixed_size);
// Output a Mkv Frame. It decides the correct element to write (Block vs
// SimpleBlock) based on the parameters of the Frame.
uint64_t WriteFrame(IMkvWriter* writer, const Frame* const frame,

View File

@ -52,6 +52,8 @@ void Usage() {
printf(" -copy_tags <int> >0 Copies the tags\n");
printf(" -accurate_cluster_duration <int> ");
printf(">0 Writes the last frame in each cluster with Duration\n");
printf(" -fixed_size_cluster_timecode <int> ");
printf(">0 Writes the cluster timecode using exactly 8 bytes\n");
printf("\n");
printf("Video options:\n");
printf(" -display_width <int> Display width in pixels\n");
@ -163,6 +165,7 @@ int main(int argc, char* argv[]) {
bool copy_tags = false;
const char* chunk_name = NULL;
bool accurate_cluster_duration = false;
bool fixed_size_cluster_timecode = false;
bool output_cues_block_number = true;
@ -221,6 +224,10 @@ int main(int argc, char* argv[]) {
i < argc_check) {
accurate_cluster_duration =
strtol(argv[++i], &end, 10) == 0 ? false : true;
} else if (!strcmp("-fixed_size_cluster_timecode", argv[i]) &&
i < argc_check) {
fixed_size_cluster_timecode =
strtol(argv[++i], &end, 10) == 0 ? false : true;
} else if (!strcmp("-display_width", argv[i]) && i < argc_check) {
display_width = strtol(argv[++i], &end, 10);
} else if (!strcmp("-display_height", argv[i]) && i < argc_check) {
@ -297,6 +304,7 @@ int main(int argc, char* argv[]) {
}
muxer_segment.AccurateClusterDuration(accurate_cluster_duration);
muxer_segment.UseFixedSizeClusterTimecode(fixed_size_cluster_timecode);
if (live_mode)
muxer_segment.set_mode(mkvmuxer::Segment::kLive);

View File

@ -91,7 +91,8 @@ class MuxerTest : public testing::Test {
is_writer_open_ = false;
}
bool SegmentInit(bool output_cues, bool accurate_cluster_duration) {
bool SegmentInit(bool output_cues, bool accurate_cluster_duration,
bool fixed_size_cluster_timecode) {
if (!segment_.Init(writer_.get()))
return false;
SegmentInfo* const info = segment_.GetSegmentInfo();
@ -99,6 +100,7 @@ class MuxerTest : public testing::Test {
info->set_muxing_app(kAppString);
segment_.OutputCues(output_cues);
segment_.AccurateClusterDuration(accurate_cluster_duration);
segment_.UseFixedSizeClusterTimecode(fixed_size_cluster_timecode);
return true;
}
@ -112,7 +114,7 @@ class MuxerTest : public testing::Test {
};
TEST_F(MuxerTest, SegmentInfo) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
SegmentInfo* const info = segment_.GetSegmentInfo();
info->set_timecode_scale(kTimeCodeScale);
info->set_duration(2.345);
@ -129,7 +131,7 @@ TEST_F(MuxerTest, SegmentInfo) {
}
TEST_F(MuxerTest, AddTracks) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
// Add a Video Track
AddVideoTrack();
@ -173,7 +175,7 @@ TEST_F(MuxerTest, AddTracks) {
}
TEST_F(MuxerTest, AddChapters) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
// Add a Chapter
@ -190,7 +192,7 @@ TEST_F(MuxerTest, AddChapters) {
}
TEST_F(MuxerTest, SimpleBlock) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
// Valid Frame
@ -218,7 +220,7 @@ TEST_F(MuxerTest, SimpleBlock) {
}
TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
Frame frame;
@ -250,7 +252,7 @@ TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) {
}
TEST_F(MuxerTest, MetadataBlock) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
Track* const track = segment_.AddTrack(kMetadataTrackNumber);
track->set_uid(kMetadataTrackNumber);
track->set_type(kMetadataTrackType);
@ -281,7 +283,7 @@ TEST_F(MuxerTest, MetadataBlock) {
}
TEST_F(MuxerTest, TrackType) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
Track* const track = segment_.AddTrack(kMetadataTrackNumber);
track->set_uid(kMetadataTrackNumber);
track->set_codec_id(kMetadataCodecId);
@ -301,7 +303,7 @@ TEST_F(MuxerTest, TrackType) {
}
TEST_F(MuxerTest, BlockWithAdditional) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
// Valid Frame
@ -342,7 +344,7 @@ TEST_F(MuxerTest, BlockWithAdditional) {
}
TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
Frame frame;
@ -377,7 +379,7 @@ TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) {
}
TEST_F(MuxerTest, SegmentDurationComputation) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
Frame frame;
@ -405,7 +407,7 @@ TEST_F(MuxerTest, SegmentDurationComputation) {
}
TEST_F(MuxerTest, ForceNewCluster) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
@ -427,7 +429,7 @@ TEST_F(MuxerTest, ForceNewCluster) {
}
TEST_F(MuxerTest, OutputCues) {
EXPECT_TRUE(SegmentInit(true, false));
EXPECT_TRUE(SegmentInit(true, false, false));
AddVideoTrack();
EXPECT_TRUE(
@ -447,7 +449,7 @@ TEST_F(MuxerTest, OutputCues) {
}
TEST_F(MuxerTest, CuesBeforeClusters) {
EXPECT_TRUE(SegmentInit(true, false));
EXPECT_TRUE(SegmentInit(true, false, false));
AddVideoTrack();
EXPECT_TRUE(
@ -480,7 +482,7 @@ TEST_F(MuxerTest, CuesBeforeClusters) {
}
TEST_F(MuxerTest, MaxClusterSize) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
const uint64_t kMaxClusterSize = 20;
segment_.set_max_cluster_size(kMaxClusterSize);
@ -505,7 +507,7 @@ TEST_F(MuxerTest, MaxClusterSize) {
}
TEST_F(MuxerTest, MaxClusterDuration) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddVideoTrack();
const uint64_t kMaxClusterDuration = 4000000;
segment_.set_max_cluster_duration(kMaxClusterDuration);
@ -533,7 +535,7 @@ TEST_F(MuxerTest, MaxClusterDuration) {
TEST_F(MuxerTest, SetCuesTrackNumber) {
const uint64_t kTrackNumber = 10;
EXPECT_TRUE(SegmentInit(true, false));
EXPECT_TRUE(SegmentInit(true, false, false));
const uint64_t vid_track =
segment_.AddVideoTrack(kWidth, kHeight, kTrackNumber);
EXPECT_EQ(kTrackNumber, vid_track);
@ -562,7 +564,7 @@ TEST_F(MuxerTest, SetCuesTrackNumber) {
}
TEST_F(MuxerTest, BlockWithDiscardPadding) {
EXPECT_TRUE(SegmentInit(false, false));
EXPECT_TRUE(SegmentInit(false, false, false));
AddAudioTrack();
int timecode = 1000;
@ -585,7 +587,7 @@ TEST_F(MuxerTest, BlockWithDiscardPadding) {
}
TEST_F(MuxerTest, AccurateClusterDuration) {
EXPECT_TRUE(SegmentInit(false, true));
EXPECT_TRUE(SegmentInit(false, true, false));
AddVideoTrack();
Frame frame;
@ -614,7 +616,7 @@ TEST_F(MuxerTest, AccurateClusterDuration) {
// Tests AccurateClusterDuration flag with the duration of the very last block
// of the file set explicitly.
TEST_F(MuxerTest, AccurateClusterDurationExplicitLastFrameDuration) {
EXPECT_TRUE(SegmentInit(false, true));
EXPECT_TRUE(SegmentInit(false, true, false));
AddVideoTrack();
Frame frame;
@ -645,7 +647,7 @@ TEST_F(MuxerTest, AccurateClusterDurationExplicitLastFrameDuration) {
}
TEST_F(MuxerTest, AccurateClusterDurationTwoTracks) {
EXPECT_TRUE(SegmentInit(false, true));
EXPECT_TRUE(SegmentInit(false, true, false));
AddVideoTrack();
AddAudioTrack();
@ -698,6 +700,30 @@ TEST_F(MuxerTest, AccurateClusterDurationTwoTracks) {
GetTestFilePath("accurate_cluster_duration_two_tracks.webm"), filename_));
}
TEST_F(MuxerTest, UseFixedSizeClusterTimecode) {
EXPECT_TRUE(SegmentInit(false, false, true));
AddVideoTrack();
Frame frame;
frame.Init(dummy_data_, kFrameLength);
frame.set_track_number(kVideoTrackNumber);
frame.set_timestamp(0);
frame.set_is_key(true);
EXPECT_TRUE(segment_.AddGenericFrame(&frame));
segment_.ForceNewClusterOnNextFrame();
frame.set_timestamp(2000000);
EXPECT_TRUE(segment_.AddGenericFrame(&frame));
segment_.ForceNewClusterOnNextFrame();
frame.set_timestamp(4000000);
EXPECT_TRUE(segment_.AddGenericFrame(&frame));
segment_.Finalize();
CloseWriter();
EXPECT_TRUE(CompareFiles(GetTestFilePath("fixed_size_cluster_timecode.webm"),
filename_));
}
} // namespace test
int main(int argc, char* argv[]) {

Binary file not shown.