From 7b75369b97e58524234965301034a8785cdfec87 Mon Sep 17 00:00:00 2001 From: Gabe Kopley Date: Mon, 11 Jul 2016 14:40:02 -0700 Subject: [PATCH] Support $DecodeTime$ as filename template variable, work-in-progress --- docs/design.md | 2 +- packager/media/base/muxer_util.cc | 16 +++++++++--- packager/media/base/muxer_util.h | 1 + packager/media/base/muxer_util_unittest.cc | 26 ++++++++++++++++++- packager/media/formats/mp2t/ts_segmenter.cc | 2 +- .../formats/mp4/multi_segment_segmenter.cc | 1 + packager/media/formats/mp4/segmenter.h | 1 + .../formats/webm/multi_segment_segmenter.cc | 4 +-- .../media/formats/webm/segmenter_test_base.cc | 2 +- 9 files changed, 45 insertions(+), 10 deletions(-) diff --git a/docs/design.md b/docs/design.md index 9e85a7d765..abfa54b604 100644 --- a/docs/design.md +++ b/docs/design.md @@ -157,7 +157,7 @@ muxer_options.output_file_name = …; // Specify output segment name pattern for generated segments. It can // furthermore be configured by using a subset of the SegmentTemplate -// identifiers: $RepresentationID$, $Number$, $Bandwidth$ and $Time$. +// identifiers: $RepresentationID$, $Number$, $Bandwidth$, $Time$ and $DecodeTime$. // Optional. muxer_options.segment_template = …; diff --git a/packager/media/base/muxer_util.cc b/packager/media/base/muxer_util.cc index a6145cd7ee..cd115a7854 100644 --- a/packager/media/base/muxer_util.cc +++ b/packager/media/base/muxer_util.cc @@ -46,6 +46,10 @@ bool ValidateSegmentTemplate(const std::string& segment_template) { // ISO/IEC 23009-1:2012 5.3.9.4.4 Template-based Segment URL construction. // Allowed identifiers: $$, $RepresentationID$, $Number$, $Bandwidth$, $Time$. // "$" always appears in pairs, so there should be odd number of splits. + // We also allow $DecodeTime$ in order to help work around this longstanding + // bug in Chrome: + // https://bugs.chromium.org/p/chromium/issues/detail?id=398130#c13 + // cf. https://github.com/google/shaka-packager/issues/113 if (splits.size() % 2 == 0) { LOG(ERROR) << "SegmentTemplate: '$' should appear in pairs."; return false; @@ -72,7 +76,7 @@ bool ValidateSegmentTemplate(const std::string& segment_template) { return false; } else if (identifier == "Number") { has_number = true; - } else if (identifier == "Time") { + } else if (identifier == "Time" || identifier == "DecodeTime") { has_time = true; } else if (identifier == "") { if (format_pos != std::string::npos) { @@ -86,11 +90,11 @@ bool ValidateSegmentTemplate(const std::string& segment_template) { } } if (has_number && has_time) { - LOG(ERROR) << "SegmentTemplate: $Number$ and $Time$ should not co-exist."; + LOG(ERROR) << "SegmentTemplate: $Number$ and $Time$/$DecodeTime$ should not co-exist."; return false; } if (!has_number && !has_time) { - LOG(ERROR) << "SegmentTemplate: $Number$ or $Time$ should exist."; + LOG(ERROR) << "SegmentTemplate: $Number$ or $Time$/$DecodeTime$ should exist."; return false; } // Note: The below check is skipped. @@ -101,6 +105,7 @@ bool ValidateSegmentTemplate(const std::string& segment_template) { std::string GetSegmentName(const std::string& segment_template, uint64_t segment_start_time, + uint64_t segment_start_decode_time, uint32_t segment_index, uint32_t bandwidth) { DCHECK(ValidateSegmentTemplate(segment_template)); @@ -126,7 +131,7 @@ std::string GetSegmentName(const std::string& segment_template, size_t format_pos = splits[i].find('%'); std::string identifier = splits[i].substr(0, format_pos); DCHECK(identifier == "Number" || identifier == "Time" || - identifier == "Bandwidth"); + identifier == "DecodeTime" || identifier == "Bandwidth"); std::string format_tag; if (format_pos != std::string::npos) { @@ -146,6 +151,9 @@ std::string GetSegmentName(const std::string& segment_template, } else if (identifier == "Time") { segment_name += base::StringPrintf(format_tag.c_str(), segment_start_time); + } else if (identifier == "DecodeTime") { + segment_name += + base::StringPrintf(format_tag.c_str(), segment_start_decode_time); } else if (identifier == "Bandwidth") { segment_name += base::StringPrintf(format_tag.c_str(), static_cast(bandwidth)); diff --git a/packager/media/base/muxer_util.h b/packager/media/base/muxer_util.h index a9b59cd598..d84384c00e 100644 --- a/packager/media/base/muxer_util.h +++ b/packager/media/base/muxer_util.h @@ -36,6 +36,7 @@ bool ValidateSegmentTemplate(const std::string& segment_template); /// @return The segment name with identifier substituted. std::string GetSegmentName(const std::string& segment_template, uint64_t segment_start_time, + uint64_t segment_start_decode_time, uint32_t segment_index, uint32_t bandwidth); diff --git a/packager/media/base/muxer_util_unittest.cc b/packager/media/base/muxer_util_unittest.cc index 152396d3d7..aad6b7b4f4 100644 --- a/packager/media/base/muxer_util_unittest.cc +++ b/packager/media/base/muxer_util_unittest.cc @@ -16,6 +16,7 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) { EXPECT_TRUE(ValidateSegmentTemplate("$Number$")); EXPECT_TRUE(ValidateSegmentTemplate("$Time$")); + EXPECT_TRUE(ValidateSegmentTemplate("$DecodeTime$")); EXPECT_TRUE(ValidateSegmentTemplate("$Time$$Time$")); EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$goo")); EXPECT_TRUE(ValidateSegmentTemplate("$Number$_$Number$")); @@ -30,7 +31,7 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) { EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$$$")); EXPECT_TRUE(ValidateSegmentTemplate("$$$Time$$$")); - // Missing $Number$ / $Time$. + // Missing $Number$ / $Time$ / $DecodeTime$. EXPECT_FALSE(ValidateSegmentTemplate("$$")); EXPECT_FALSE(ValidateSegmentTemplate("foo$$goo")); @@ -62,15 +63,18 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) { TEST(MuxerUtilTest, GetSegmentName) { const uint64_t kSegmentStartTime = 180180; + const uint64_t kSegmentStartDecodeTime = 180173; const uint32_t kSegmentIndex = 11; const uint32_t kBandwidth = 1234; EXPECT_EQ("12", GetSegmentName("$Number$", + kSegmentStartTime, kSegmentStartTime, kSegmentIndex, kBandwidth)); EXPECT_EQ("012", GetSegmentName("$Number%03d$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); EXPECT_EQ( @@ -78,17 +82,26 @@ TEST(MuxerUtilTest, GetSegmentName) { GetSegmentName( "$Number%01d$$$foo$$$Number%05d$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); EXPECT_EQ("180180", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentStartDecodeTime, + kSegmentIndex, + kBandwidth)); + EXPECT_EQ("180173", + GetSegmentName("$DecodeTime$", + kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); EXPECT_EQ("foo$_$18018000180180.m4s", GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); @@ -96,11 +109,13 @@ TEST(MuxerUtilTest, GetSegmentName) { EXPECT_EQ("12-1234", GetSegmentName("$Number$-$Bandwidth$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); EXPECT_EQ("012-001234", GetSegmentName("$Number%03d$-$Bandwidth%06d$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); @@ -108,47 +123,56 @@ TEST(MuxerUtilTest, GetSegmentName) { EXPECT_EQ("12", GetSegmentName("$Number%00d$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); EXPECT_EQ("00012", GetSegmentName("$Number%005d$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) { const uint64_t kSegmentStartTime = 0; + const uint64_t kSegmentStartDecodeTime = 0; const uint32_t kSegmentIndex = 0; const uint32_t kBandwidth = 0; EXPECT_EQ("1", GetSegmentName("$Number$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); EXPECT_EQ("001", GetSegmentName("$Number%03d$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); EXPECT_EQ("0", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); EXPECT_EQ("00000000.m4s", GetSegmentName("$Time%08d$.m4s", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameLargeTime) { const uint64_t kSegmentStartTime = 1601599839840ULL; + const uint64_t kSegmentStartDecodeTime = 1337; const uint32_t kSegmentIndex = 8888888; const uint32_t kBandwidth = 444444; EXPECT_EQ("1601599839840", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentStartDecodeTime, kSegmentIndex, kBandwidth)); } diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index 1eb80636ec..96c3ff7a48 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -116,7 +116,7 @@ Status TsSegmenter::OpenNewSegmentIfClosed(uint32_t next_pts) { if (ts_writer_file_opened_) return Status::OK; const std::string segment_name = - GetSegmentName(muxer_options_.segment_template, next_pts, + GetSegmentName(muxer_options_.segment_template, next_pts, next_pts, segment_number_++, muxer_options_.bandwidth); if (!ts_writer_->NewSegment(segment_name)) return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter."); diff --git a/packager/media/formats/mp4/multi_segment_segmenter.cc b/packager/media/formats/mp4/multi_segment_segmenter.cc index b7f146f2b6..3c8ff147bf 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.cc +++ b/packager/media/formats/mp4/multi_segment_segmenter.cc @@ -150,6 +150,7 @@ Status MultiSegmentSegmenter::WriteSegment() { } else { file_name = GetSegmentName(options().segment_template, sidx()->earliest_presentation_time, + moof()->tracks[0].decode_time.decode_time, num_segments_++, options().bandwidth); file = File::Open(file_name.c_str(), "w"); if (file == NULL) { diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index 21471af289..e12e6c9dc7 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -111,6 +111,7 @@ class Segmenter { Movie* moov() { return moov_.get(); } BufferWriter* fragment_buffer() { return fragment_buffer_.get(); } SegmentIndex* sidx() { return sidx_.get(); } + MovieFragment* moof() { return moof_.get() ; } // NOTE delete me! MuxerListener* muxer_listener() { return muxer_listener_; } uint64_t progress_target() { return progress_target_; } diff --git a/packager/media/formats/webm/multi_segment_segmenter.cc b/packager/media/formats/webm/multi_segment_segmenter.cc index d36e01b523..a7d6c4067d 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.cc +++ b/packager/media/formats/webm/multi_segment_segmenter.cc @@ -77,8 +77,8 @@ Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale) { // Create a new file for the new segment. std::string segment_name = - GetSegmentName(options().segment_template, start_timescale, num_segment_, - options().bandwidth); + GetSegmentName(options().segment_template, start_timescale, + start_timescale, num_segment_, options().bandwidth); writer_.reset(new MkvWriter); Status status = writer_->Open(segment_name); if (!status.ok()) diff --git a/packager/media/formats/webm/segmenter_test_base.cc b/packager/media/formats/webm/segmenter_test_base.cc index b79d435c65..e3c711b6ec 100644 --- a/packager/media/formats/webm/segmenter_test_base.cc +++ b/packager/media/formats/webm/segmenter_test_base.cc @@ -101,7 +101,7 @@ std::string SegmentTestBase::OutputFileName() const { } std::string SegmentTestBase::TemplateFileName(int number) const { - return GetSegmentName(segment_template_, 0, number, 0); + return GetSegmentName(segment_template_, 0, 0, number, 0); } SegmentTestBase::ClusterParser::ClusterParser() : in_cluster_(false) {}