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:
parent
c1991fea81
commit
eb50da8e38
@ -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
|
||||
|
@ -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_
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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[]) {
|
||||
|
BIN
testing/testdata/fixed_size_cluster_timecode.webm
vendored
Normal file
BIN
testing/testdata/fixed_size_cluster_timecode.webm
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user