Support $DecodeTime$ as filename template variable, work-in-progress

This commit is contained in:
Gabe Kopley 2016-07-11 14:40:02 -07:00
parent ad3c1768bb
commit 7b75369b97
9 changed files with 45 additions and 10 deletions

View File

@ -157,7 +157,7 @@ muxer_options.output_file_name = …;
// Specify output segment name pattern for generated segments. It can // Specify output segment name pattern for generated segments. It can
// furthermore be configured by using a subset of the SegmentTemplate // furthermore be configured by using a subset of the SegmentTemplate
// identifiers: $RepresentationID$, $Number$, $Bandwidth$ and $Time$. // identifiers: $RepresentationID$, $Number$, $Bandwidth$, $Time$ and $DecodeTime$.
// Optional. // Optional.
muxer_options.segment_template = …; muxer_options.segment_template = …;

View File

@ -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. // ISO/IEC 23009-1:2012 5.3.9.4.4 Template-based Segment URL construction.
// Allowed identifiers: $$, $RepresentationID$, $Number$, $Bandwidth$, $Time$. // Allowed identifiers: $$, $RepresentationID$, $Number$, $Bandwidth$, $Time$.
// "$" always appears in pairs, so there should be odd number of splits. // "$" 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) { if (splits.size() % 2 == 0) {
LOG(ERROR) << "SegmentTemplate: '$' should appear in pairs."; LOG(ERROR) << "SegmentTemplate: '$' should appear in pairs.";
return false; return false;
@ -72,7 +76,7 @@ bool ValidateSegmentTemplate(const std::string& segment_template) {
return false; return false;
} else if (identifier == "Number") { } else if (identifier == "Number") {
has_number = true; has_number = true;
} else if (identifier == "Time") { } else if (identifier == "Time" || identifier == "DecodeTime") {
has_time = true; has_time = true;
} else if (identifier == "") { } else if (identifier == "") {
if (format_pos != std::string::npos) { if (format_pos != std::string::npos) {
@ -86,11 +90,11 @@ bool ValidateSegmentTemplate(const std::string& segment_template) {
} }
} }
if (has_number && has_time) { 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; return false;
} }
if (!has_number && !has_time) { if (!has_number && !has_time) {
LOG(ERROR) << "SegmentTemplate: $Number$ or $Time$ should exist."; LOG(ERROR) << "SegmentTemplate: $Number$ or $Time$/$DecodeTime$ should exist.";
return false; return false;
} }
// Note: The below check is skipped. // 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, std::string GetSegmentName(const std::string& segment_template,
uint64_t segment_start_time, uint64_t segment_start_time,
uint64_t segment_start_decode_time,
uint32_t segment_index, uint32_t segment_index,
uint32_t bandwidth) { uint32_t bandwidth) {
DCHECK(ValidateSegmentTemplate(segment_template)); DCHECK(ValidateSegmentTemplate(segment_template));
@ -126,7 +131,7 @@ std::string GetSegmentName(const std::string& segment_template,
size_t format_pos = splits[i].find('%'); size_t format_pos = splits[i].find('%');
std::string identifier = splits[i].substr(0, format_pos); std::string identifier = splits[i].substr(0, format_pos);
DCHECK(identifier == "Number" || identifier == "Time" || DCHECK(identifier == "Number" || identifier == "Time" ||
identifier == "Bandwidth"); identifier == "DecodeTime" || identifier == "Bandwidth");
std::string format_tag; std::string format_tag;
if (format_pos != std::string::npos) { if (format_pos != std::string::npos) {
@ -146,6 +151,9 @@ std::string GetSegmentName(const std::string& segment_template,
} else if (identifier == "Time") { } else if (identifier == "Time") {
segment_name += segment_name +=
base::StringPrintf(format_tag.c_str(), segment_start_time); 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") { } else if (identifier == "Bandwidth") {
segment_name += base::StringPrintf(format_tag.c_str(), segment_name += base::StringPrintf(format_tag.c_str(),
static_cast<uint64_t>(bandwidth)); static_cast<uint64_t>(bandwidth));

View File

@ -36,6 +36,7 @@ bool ValidateSegmentTemplate(const std::string& segment_template);
/// @return The segment name with identifier substituted. /// @return The segment name with identifier substituted.
std::string GetSegmentName(const std::string& segment_template, std::string GetSegmentName(const std::string& segment_template,
uint64_t segment_start_time, uint64_t segment_start_time,
uint64_t segment_start_decode_time,
uint32_t segment_index, uint32_t segment_index,
uint32_t bandwidth); uint32_t bandwidth);

View File

@ -16,6 +16,7 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) {
EXPECT_TRUE(ValidateSegmentTemplate("$Number$")); EXPECT_TRUE(ValidateSegmentTemplate("$Number$"));
EXPECT_TRUE(ValidateSegmentTemplate("$Time$")); EXPECT_TRUE(ValidateSegmentTemplate("$Time$"));
EXPECT_TRUE(ValidateSegmentTemplate("$DecodeTime$"));
EXPECT_TRUE(ValidateSegmentTemplate("$Time$$Time$")); EXPECT_TRUE(ValidateSegmentTemplate("$Time$$Time$"));
EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$goo")); EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$goo"));
EXPECT_TRUE(ValidateSegmentTemplate("$Number$_$Number$")); EXPECT_TRUE(ValidateSegmentTemplate("$Number$_$Number$"));
@ -30,7 +31,7 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) {
EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$$$")); EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$$$"));
EXPECT_TRUE(ValidateSegmentTemplate("$$$Time$$$")); EXPECT_TRUE(ValidateSegmentTemplate("$$$Time$$$"));
// Missing $Number$ / $Time$. // Missing $Number$ / $Time$ / $DecodeTime$.
EXPECT_FALSE(ValidateSegmentTemplate("$$")); EXPECT_FALSE(ValidateSegmentTemplate("$$"));
EXPECT_FALSE(ValidateSegmentTemplate("foo$$goo")); EXPECT_FALSE(ValidateSegmentTemplate("foo$$goo"));
@ -62,15 +63,18 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) {
TEST(MuxerUtilTest, GetSegmentName) { TEST(MuxerUtilTest, GetSegmentName) {
const uint64_t kSegmentStartTime = 180180; const uint64_t kSegmentStartTime = 180180;
const uint64_t kSegmentStartDecodeTime = 180173;
const uint32_t kSegmentIndex = 11; const uint32_t kSegmentIndex = 11;
const uint32_t kBandwidth = 1234; const uint32_t kBandwidth = 1234;
EXPECT_EQ("12", GetSegmentName("$Number$", EXPECT_EQ("12", GetSegmentName("$Number$",
kSegmentStartTime,
kSegmentStartTime, kSegmentStartTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("012", EXPECT_EQ("012",
GetSegmentName("$Number%03d$", GetSegmentName("$Number%03d$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ( EXPECT_EQ(
@ -78,17 +82,26 @@ TEST(MuxerUtilTest, GetSegmentName) {
GetSegmentName( GetSegmentName(
"$Number%01d$$$foo$$$Number%05d$", "$Number%01d$$$foo$$$Number%05d$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("180180", EXPECT_EQ("180180",
GetSegmentName("$Time$", GetSegmentName("$Time$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("180173",
GetSegmentName("$DecodeTime$",
kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("foo$_$18018000180180.m4s", EXPECT_EQ("foo$_$18018000180180.m4s",
GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s", GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
@ -96,11 +109,13 @@ TEST(MuxerUtilTest, GetSegmentName) {
EXPECT_EQ("12-1234", EXPECT_EQ("12-1234",
GetSegmentName("$Number$-$Bandwidth$", GetSegmentName("$Number$-$Bandwidth$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("012-001234", EXPECT_EQ("012-001234",
GetSegmentName("$Number%03d$-$Bandwidth%06d$", GetSegmentName("$Number%03d$-$Bandwidth%06d$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
@ -108,47 +123,56 @@ TEST(MuxerUtilTest, GetSegmentName) {
EXPECT_EQ("12", EXPECT_EQ("12",
GetSegmentName("$Number%00d$", GetSegmentName("$Number%00d$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("00012", EXPECT_EQ("00012",
GetSegmentName("$Number%005d$", GetSegmentName("$Number%005d$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
} }
TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) { TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) {
const uint64_t kSegmentStartTime = 0; const uint64_t kSegmentStartTime = 0;
const uint64_t kSegmentStartDecodeTime = 0;
const uint32_t kSegmentIndex = 0; const uint32_t kSegmentIndex = 0;
const uint32_t kBandwidth = 0; const uint32_t kBandwidth = 0;
EXPECT_EQ("1", GetSegmentName("$Number$", EXPECT_EQ("1", GetSegmentName("$Number$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("001", EXPECT_EQ("001",
GetSegmentName("$Number%03d$", GetSegmentName("$Number%03d$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("0", GetSegmentName("$Time$", EXPECT_EQ("0", GetSegmentName("$Time$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("00000000.m4s", EXPECT_EQ("00000000.m4s",
GetSegmentName("$Time%08d$.m4s", GetSegmentName("$Time%08d$.m4s",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
} }
TEST(MuxerUtilTest, GetSegmentNameLargeTime) { TEST(MuxerUtilTest, GetSegmentNameLargeTime) {
const uint64_t kSegmentStartTime = 1601599839840ULL; const uint64_t kSegmentStartTime = 1601599839840ULL;
const uint64_t kSegmentStartDecodeTime = 1337;
const uint32_t kSegmentIndex = 8888888; const uint32_t kSegmentIndex = 8888888;
const uint32_t kBandwidth = 444444; const uint32_t kBandwidth = 444444;
EXPECT_EQ("1601599839840", EXPECT_EQ("1601599839840",
GetSegmentName("$Time$", GetSegmentName("$Time$",
kSegmentStartTime, kSegmentStartTime,
kSegmentStartDecodeTime,
kSegmentIndex, kSegmentIndex,
kBandwidth)); kBandwidth));
} }

View File

@ -116,7 +116,7 @@ Status TsSegmenter::OpenNewSegmentIfClosed(uint32_t next_pts) {
if (ts_writer_file_opened_) if (ts_writer_file_opened_)
return Status::OK; return Status::OK;
const std::string segment_name = 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); segment_number_++, muxer_options_.bandwidth);
if (!ts_writer_->NewSegment(segment_name)) if (!ts_writer_->NewSegment(segment_name))
return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter."); return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter.");

View File

@ -150,6 +150,7 @@ Status MultiSegmentSegmenter::WriteSegment() {
} else { } else {
file_name = GetSegmentName(options().segment_template, file_name = GetSegmentName(options().segment_template,
sidx()->earliest_presentation_time, sidx()->earliest_presentation_time,
moof()->tracks[0].decode_time.decode_time,
num_segments_++, options().bandwidth); num_segments_++, options().bandwidth);
file = File::Open(file_name.c_str(), "w"); file = File::Open(file_name.c_str(), "w");
if (file == NULL) { if (file == NULL) {

View File

@ -111,6 +111,7 @@ class Segmenter {
Movie* moov() { return moov_.get(); } Movie* moov() { return moov_.get(); }
BufferWriter* fragment_buffer() { return fragment_buffer_.get(); } BufferWriter* fragment_buffer() { return fragment_buffer_.get(); }
SegmentIndex* sidx() { return sidx_.get(); } SegmentIndex* sidx() { return sidx_.get(); }
MovieFragment* moof() { return moof_.get() ; } // NOTE delete me!
MuxerListener* muxer_listener() { return muxer_listener_; } MuxerListener* muxer_listener() { return muxer_listener_; }
uint64_t progress_target() { return progress_target_; } uint64_t progress_target() { return progress_target_; }

View File

@ -77,8 +77,8 @@ Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale) {
// Create a new file for the new segment. // Create a new file for the new segment.
std::string segment_name = std::string segment_name =
GetSegmentName(options().segment_template, start_timescale, num_segment_, GetSegmentName(options().segment_template, start_timescale,
options().bandwidth); start_timescale, num_segment_, options().bandwidth);
writer_.reset(new MkvWriter); writer_.reset(new MkvWriter);
Status status = writer_->Open(segment_name); Status status = writer_->Open(segment_name);
if (!status.ok()) if (!status.ok())

View File

@ -101,7 +101,7 @@ std::string SegmentTestBase::OutputFileName() const {
} }
std::string SegmentTestBase::TemplateFileName(int number) 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) {} SegmentTestBase::ClusterParser::ClusterParser() : in_cluster_(false) {}