diff --git a/app/packager_main.cc b/app/packager_main.cc index d022a54fc0..d4ed701c7d 100644 --- a/app/packager_main.cc +++ b/app/packager_main.cc @@ -11,6 +11,7 @@ #include "app/mpd_flags.h" #include "app/muxer_flags.h" #include "app/packager_util.h" +#include "app/stream_descriptor.h" #include "app/widevine_encryption_flags.h" #include "base/logging.h" #include "base/stl_util.h" @@ -31,18 +32,23 @@ namespace { const char kUsage[] = "Packager driver program. Sample Usage:\n" "%s [flags] ...\n" - "stream_descriptor may be repeated and consists of a tuplet as follows:\n" - "#,[,]\n" - " - input_file is a file path or network stream URL.\n" - " - stream_selector is one of 'audio', 'video', or stream number.\n" - " - output_file is the output file (single file) or initialization file" - " path (multiple file)." - " - segment_template is an optional value which specifies the naming" - " pattern for the segment files, and that the stream should be split into" - " multiple files. Its presence should be consistent across streams.\n"; - -typedef std::vector StringVector; - + "stream_descriptor consists of comma separated field_name/value pairs:\n" + "field_name=value,[field_name=value,]...\n" + "Supported field names are as follows:\n" + " - input (in): Required input/source media file path or network stream " + "URL.\n" + " - stream_selector (stream): Required field with value 'audio', 'video', " + "or stream number (zero based).\n" + " - output (out): Required output file (single file) or initialization " + "file path (multiple file).\n" + " - segment_template (segment): Optional value which specifies the " + "naming pattern for the segment files, and that the stream should be " + "split into multiple files. Its presence should be consistent across " + "streams.\n" + " - bandwidth (bw): Optional value which contains a user-specified " + "content bit rate for the stream, in bits/sec. If specified, this value is " + "propagated to the $Bandwidth$ template parameter for segment names. " + "If not specified, its value may be estimated.\n"; } // namespace namespace media { @@ -88,7 +94,7 @@ class RemuxJob : public base::SimpleThread { DISALLOW_COPY_AND_ASSIGN(RemuxJob); }; -bool CreateRemuxJobs(const StringVector& stream_descriptors, +bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, const MuxerOptions& muxer_options, EncryptionKeySource* key_source, MpdNotifier* mpd_notifier, @@ -97,55 +103,40 @@ bool CreateRemuxJobs(const StringVector& stream_descriptors, DCHECK(muxer_listeners); DCHECK(remux_jobs); - // Sort the stream descriptors so that we can group muxers by demux. - StringVector sorted_descriptors(stream_descriptors); - std::sort(sorted_descriptors.begin(), sorted_descriptors.end()); - - std::string previous_file_path; - for (StringVector::const_iterator stream_iter = sorted_descriptors.begin(); - stream_iter != sorted_descriptors.end(); + std::string previous_input; + for (StreamDescriptorList::const_iterator stream_iter = + stream_descriptors.begin(); + stream_iter != stream_descriptors.end(); ++stream_iter) { // Process stream descriptor. - StringVector descriptor; - base::SplitString(*stream_iter, ',', &descriptor); - if ((descriptor.size() < 2) || (descriptor.size() > 3)) { - LOG(ERROR) - << "Malformed stream descriptor (invalid number of components)."; - return false; - } - size_t hash_pos = descriptor[0].find('#'); - if (hash_pos == std::string::npos) { - LOG(ERROR) - << "Malformed stream descriptor (stream selector unspecified)."; - return false; - } MuxerOptions stream_muxer_options(muxer_options); - std::string file_path(descriptor[0].substr(0, hash_pos)); - std::string stream_selector(descriptor[0].substr(hash_pos + 1)); - stream_muxer_options.output_file_name = descriptor[1]; - if (descriptor.size() == 3) { - stream_muxer_options.segment_template = descriptor[2]; - if (!ValidateSegmentTemplate(stream_muxer_options.segment_template)) { + stream_muxer_options.output_file_name = stream_iter->output; + if (!stream_iter->segment_template.empty()) { + if (!ValidateSegmentTemplate(stream_iter->segment_template)) { LOG(ERROR) << "ERROR: segment template with '" - << stream_muxer_options.segment_template << "' is invalid."; + << stream_iter->segment_template << "' is invalid."; return false; } + stream_muxer_options.segment_template = stream_iter->segment_template; } + stream_muxer_options.bandwidth = stream_iter->bandwidth; - if (file_path != previous_file_path) { + if (stream_iter->input != previous_input) { // New remux job needed. Create demux and job thread. - scoped_ptr demux(new Demuxer(file_path, NULL)); + scoped_ptr demux(new Demuxer(stream_iter->input, NULL)); Status status = demux->Initialize(); if (!status.ok()) { LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString(); return false; } if (FLAGS_dump_stream_info) { - printf("\nFile \"%s\":\n", file_path.c_str()); + printf("\nFile \"%s\":\n", stream_iter->input.c_str()); DumpStreamInfo(demux->streams()); + if (stream_iter->output.empty()) + continue; // just need stream info. } remux_jobs->push_back(new RemuxJob(demux.Pass())); - previous_file_path = file_path; + previous_input = stream_iter->input; } DCHECK(!remux_jobs->empty()); @@ -183,7 +174,7 @@ bool CreateRemuxJobs(const StringVector& stream_descriptors, } if (!AddStreamToMuxer(remux_jobs->back()->demuxer()->streams(), - stream_selector, + stream_iter->stream_selector, muxer.get())) return false; remux_jobs->back()->AddMuxer(muxer.Pass()); @@ -222,7 +213,7 @@ Status RunRemuxJobs(const std::vector& remux_jobs) { return status; } -bool RunPackager(const StringVector& stream_descriptors) { +bool RunPackager(const StreamDescriptorList& stream_descriptors) { if (!AssignFlagsFromProfile()) return false; @@ -305,8 +296,12 @@ int main(int argc, char** argv) { LOG(ERROR) << "Could not initialize libcrypto threading."; return 1; } - StringVector stream_descriptors; - for (int i = 1; i < argc; ++i) - stream_descriptors.push_back(argv[i]); + // TODO(tinskip): Make InsertStreamDescriptor a member of + // StreamDescriptorList. + media::StreamDescriptorList stream_descriptors; + for (int i = 1; i < argc; ++i) { + if (!InsertStreamDescriptor(argv[i], &stream_descriptors)) + return 1; + } return media::RunPackager(stream_descriptors) ? 0 : 1; } diff --git a/app/stream_descriptor.cc b/app/stream_descriptor.cc new file mode 100644 index 0000000000..adaf966c15 --- /dev/null +++ b/app/stream_descriptor.cc @@ -0,0 +1,122 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "app/stream_descriptor.h" + +#include "app/packager_util.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" + +namespace media { + +namespace { + +enum FieldType { + kUnknownField = 0, + kStreamSelectorField, + kInputField, + kOutputField, + kSegmentTemplateField, + kBandwidthField, +}; + +struct FieldNameToTypeMapping { + const char* field_name; + FieldType field_type; +}; + +const FieldNameToTypeMapping kFieldNameTypeMappings[] = { + { "stream_selector", kStreamSelectorField }, + { "stream", kStreamSelectorField }, + { "input", kInputField }, + { "in", kInputField }, + { "output", kOutputField }, + { "out", kOutputField }, + { "init_segment", kOutputField }, + { "segment_template", kSegmentTemplateField }, + { "template", kSegmentTemplateField }, + { "bandwidth", kBandwidthField }, + { "bw", kBandwidthField }, + { "bitrate", kBandwidthField }, +}; + +FieldType GetFieldType(const std::string& field_name) { + for (size_t idx = 0; idx < arraysize(kFieldNameTypeMappings); ++idx) { + if (field_name == kFieldNameTypeMappings[idx].field_name) + return kFieldNameTypeMappings[idx].field_type; + } + return kUnknownField; +} + +} // anonymous namespace + +StreamDescriptor::StreamDescriptor() : bandwidth(0) {} + +StreamDescriptor::~StreamDescriptor() {} + +bool InsertStreamDescriptor(const std::string& descriptor_string, + StreamDescriptorList* descriptor_list) { + StreamDescriptor descriptor; + + // Split descriptor string into name/value pairs. + base::StringPairs pairs; + if (!base::SplitStringIntoKeyValuePairs(descriptor_string, + '=', + ',', + &pairs)) { + LOG(ERROR) << "Invalid stream descriptors name/value pairs."; + return false; + } + for (base::StringPairs::const_iterator iter = pairs.begin(); + iter != pairs.end(); ++iter) { + switch (GetFieldType(iter->first)) { + case kStreamSelectorField: + descriptor.stream_selector = iter->second; + break; + case kInputField: + descriptor.input = iter->second; + break; + case kOutputField: + descriptor.output = iter->second; + break; + case kSegmentTemplateField: + descriptor.segment_template = iter->second; + break; + case kBandwidthField: { + unsigned bw; + if (!base::StringToUint(iter->second, &bw)) { + LOG(ERROR) << "Non-numeric bandwidth specified."; + return false; + } + descriptor.bandwidth = bw; + break; + } + default: + LOG(ERROR) << "Unknown field in stream descriptor (\"" << iter->first + << "\")."; + return false; + break; + } + } + // Validate and insert the descriptor + if (descriptor.input.empty()) { + LOG(ERROR) << "Stream input not specified."; + return false; + } + if (!FLAGS_dump_stream_info && descriptor.stream_selector.empty()) { + LOG(ERROR) << "Stream stream_selector not specified."; + return false; + } + if (!FLAGS_dump_stream_info && descriptor.output.empty()) { + LOG(ERROR) << "Stream output not specified."; + return false; + } + descriptor_list->insert(descriptor); + return true; +} + +} // namespace media diff --git a/app/stream_descriptor.h b/app/stream_descriptor.h new file mode 100644 index 0000000000..269a5b6560 --- /dev/null +++ b/app/stream_descriptor.h @@ -0,0 +1,53 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef APP_STREAM_DESCRIPTOR_H_ +#define APP_STREAM_DESCRIPTOR_H_ + +#include +#include + +#include "base/basictypes.h" + +namespace media { + +/// Defines a single input/output stream, it's input source, output destination, +/// stream selector, and optional segment template and user-specified bandwidth. +struct StreamDescriptor { + StreamDescriptor(); + ~StreamDescriptor(); + + std::string stream_selector; + std::string input; + std::string output; + std::string segment_template; + uint32 bandwidth; +}; + +class StreamDescriptorCompareFn { + public: + bool operator()(const StreamDescriptor& a, const StreamDescriptor& b) { + return a.input < b.input; + } +}; + +/// Sorted list of StreamDescriptor. +typedef std::multiset + StreamDescriptorList; + +/// Parses a descriptor string, and inserts into sorted list of stream +/// descriptors. +/// @param descriptor_string contains comma separate name-value pairs describing +/// the stream. +/// @param descriptor_list is a pointer to the sorted descriptor list into +/// which the new descriptor should be inserted. +/// @return true if successful, false otherwise. May print error messages. +bool InsertStreamDescriptor(const std::string& descriptor_string, + StreamDescriptorList* descriptor_list); + +} // namespace media + +#endif // APP_STREAM_DESCRIPTOR_H_ diff --git a/media/base/muxer_options.cc b/media/base/muxer_options.cc index e4e030c35f..ec1530920b 100644 --- a/media/base/muxer_options.cc +++ b/media/base/muxer_options.cc @@ -15,7 +15,8 @@ MuxerOptions::MuxerOptions() segment_sap_aligned(false), fragment_sap_aligned(false), normalize_presentation_timestamp(false), - num_subsegments_per_sidx(0) {} + num_subsegments_per_sidx(0), + bandwidth(0) {} MuxerOptions::~MuxerOptions() {} } // namespace media diff --git a/media/base/muxer_options.h b/media/base/muxer_options.h index 4f8793090d..fd05e37b83 100644 --- a/media/base/muxer_options.h +++ b/media/base/muxer_options.h @@ -9,6 +9,8 @@ #include +#include "base/basictypes.h" + namespace media { /// This structure contains the list of configuration options for Muxer. @@ -62,6 +64,9 @@ struct MuxerOptions { /// Specify temporary directory for intermediate files. std::string temp_dir; + + /// User-specified bandwidth for the stream. zero means "unspecified". + uint32 bandwidth; }; } // namespace media diff --git a/media/base/muxer_util.cc b/media/base/muxer_util.cc index 1931608e0f..10a0c49fb7 100644 --- a/media/base/muxer_util.cc +++ b/media/base/muxer_util.cc @@ -63,10 +63,10 @@ bool ValidateSegmentTemplate(const std::string& segment_template) { return false; } - // TODO(kqyang): Support "RepresentationID" and "Bandwidth". - if (identifier == "RepresentationID" || identifier == "Bandwidth") { - NOTIMPLEMENTED() << "SegmentTemplate: $RepresentationID$ and $Bandwidth$ " - "are not supported yet."; + // TODO(kqyang): Support "RepresentationID". + if (identifier == "RepresentationID") { + NOTIMPLEMENTED() << "SegmentTemplate: $RepresentationID$ is not supported " + "yet."; return false; } else if (identifier == "Number") { has_number = true; @@ -77,7 +77,7 @@ bool ValidateSegmentTemplate(const std::string& segment_template) { LOG(ERROR) << "SegmentTemplate: $$ should not have any format tags."; return false; } - } else { + } else if (identifier != "Bandwidth") { LOG(ERROR) << "SegmentTemplate: '$" << splits[i] << "$' is not a valid identifier."; return false; @@ -99,7 +99,8 @@ bool ValidateSegmentTemplate(const std::string& segment_template) { std::string GetSegmentName(const std::string& segment_template, uint64 segment_start_time, - uint32 segment_index) { + uint32 segment_index, + uint32 bandwidth) { DCHECK(ValidateSegmentTemplate(segment_template)); std::vector splits; @@ -122,7 +123,8 @@ 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"); + DCHECK(identifier == "Number" || identifier == "Time" || + identifier == "Bandwidth"); std::string format_tag; if (format_pos != std::string::npos) { @@ -142,6 +144,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 == "Bandwidth") { + segment_name += + base::StringPrintf(format_tag.c_str(), bandwidth); } } return segment_name; diff --git a/media/base/muxer_util.h b/media/base/muxer_util.h index b15a009242..2a43c3f46f 100644 --- a/media/base/muxer_util.h +++ b/media/base/muxer_util.h @@ -27,10 +27,12 @@ bool ValidateSegmentTemplate(const std::string& segment_template); /// comply with ISO/IEC 23009-1:2012 5.3.9.4.4. /// @param segment_start_time specifies the segment start time. /// @param segment_index specifies the segment index. +/// @param bandwidth represents the bit rate, in bits/sec, of the stream. /// @return The segment name with identifier substituted. std::string GetSegmentName(const std::string& segment_template, uint64 segment_start_time, - uint32 segment_index); + uint32 segment_index, + uint32 bandwidth); } // namespace media diff --git a/media/base/muxer_util_unittest.cc b/media/base/muxer_util_unittest.cc index 712398a064..0b8a98c955 100644 --- a/media/base/muxer_util_unittest.cc +++ b/media/base/muxer_util_unittest.cc @@ -18,6 +18,11 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) { EXPECT_TRUE(ValidateSegmentTemplate("$Time$$Time$")); EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$goo")); EXPECT_TRUE(ValidateSegmentTemplate("$Number$_$Number$")); + EXPECT_TRUE(ValidateSegmentTemplate("$Number$$Bandwidth$")); + EXPECT_TRUE(ValidateSegmentTemplate("$Time$$Bandwidth$")); + + // Bandwidth without Number or Time should not be valid. + EXPECT_FALSE(ValidateSegmentTemplate("$Bandwidth$")); // Escape sequence "$$". EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$__$$loo")); @@ -32,9 +37,8 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) { EXPECT_FALSE(ValidateSegmentTemplate("$Number$$Time$")); EXPECT_FALSE(ValidateSegmentTemplate("foo$Number$_$Time$loo")); - // $RepresentationID$ and $Bandwidth$ not implemented yet. + // $RepresentationID$ not implemented yet. EXPECT_FALSE(ValidateSegmentTemplate("$RepresentationID$__$Time$")); - EXPECT_FALSE(ValidateSegmentTemplate("foo$Bandwidth$$Time$")); // Unknown identifier. EXPECT_FALSE(ValidateSegmentTemplate("$foo$$Time$")); @@ -58,47 +62,94 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) { TEST(MuxerUtilTest, GetSegmentName) { const uint64 kSegmentStartTime = 180180; const uint32 kSegmentIndex = 11; - - EXPECT_EQ("12", GetSegmentName("$Number$", kSegmentStartTime, kSegmentIndex)); + const uint32 kBandwidth = 1234; + EXPECT_EQ("12", GetSegmentName("$Number$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); EXPECT_EQ("012", - GetSegmentName("$Number%03d$", kSegmentStartTime, kSegmentIndex)); + GetSegmentName("$Number%03d$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); EXPECT_EQ( "12$foo$00012", GetSegmentName( - "$Number%01d$$$foo$$$Number%05d$", kSegmentStartTime, kSegmentIndex)); + "$Number%01d$$$foo$$$Number%05d$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); EXPECT_EQ("180180", - GetSegmentName("$Time$", kSegmentStartTime, kSegmentIndex)); + GetSegmentName("$Time$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); EXPECT_EQ("foo$_$18018000180180.m4s", GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s", kSegmentStartTime, - kSegmentIndex)); + kSegmentIndex, + kBandwidth)); + + // Combo values. + EXPECT_EQ("12-1234", + GetSegmentName("$Number$-$Bandwidth$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); + EXPECT_EQ("012-001234", + GetSegmentName("$Number%03d$-$Bandwidth%06d$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); + // Format specifier edge cases. EXPECT_EQ("12", - GetSegmentName("$Number%00d$", kSegmentStartTime, kSegmentIndex)); + GetSegmentName("$Number%00d$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); EXPECT_EQ("00012", - GetSegmentName("$Number%005d$", kSegmentStartTime, kSegmentIndex)); + GetSegmentName("$Number%005d$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) { const uint64 kSegmentStartTime = 0; const uint32 kSegmentIndex = 0; - - EXPECT_EQ("1", GetSegmentName("$Number$", kSegmentStartTime, kSegmentIndex)); + const uint32 kBandwidth = 0; + EXPECT_EQ("1", GetSegmentName("$Number$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); EXPECT_EQ("001", - GetSegmentName("$Number%03d$", kSegmentStartTime, kSegmentIndex)); + GetSegmentName("$Number%03d$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); - EXPECT_EQ("0", GetSegmentName("$Time$", kSegmentStartTime, kSegmentIndex)); + EXPECT_EQ("0", GetSegmentName("$Time$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); EXPECT_EQ("00000000.m4s", - GetSegmentName("$Time%08d$.m4s", kSegmentStartTime, kSegmentIndex)); + GetSegmentName("$Time%08d$.m4s", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameLargeTime) { const uint64 kSegmentStartTime = 1601599839840ULL; const uint32 kSegmentIndex = 8888888; - + const uint32 kBandwidth = 444444; EXPECT_EQ("1601599839840", - GetSegmentName("$Time$", kSegmentStartTime, kSegmentIndex)); + GetSegmentName("$Time$", + kSegmentStartTime, + kSegmentIndex, + kBandwidth)); } } // namespace media diff --git a/media/base/stream_info.h b/media/base/stream_info.h index 770e74d176..e0f81bd4b3 100644 --- a/media/base/stream_info.h +++ b/media/base/stream_info.h @@ -15,6 +15,7 @@ namespace media { enum StreamType { + kStreamUnknown = 0, kStreamAudio, kStreamVideo, }; diff --git a/media/formats/mp4/multi_segment_segmenter.cc b/media/formats/mp4/multi_segment_segmenter.cc index 22d2b3664e..8744a18c4c 100644 --- a/media/formats/mp4/multi_segment_segmenter.cc +++ b/media/formats/mp4/multi_segment_segmenter.cc @@ -148,7 +148,8 @@ Status MultiSegmentSegmenter::WriteSegment() { } else { file = File::Open(GetSegmentName(options().segment_template, sidx()->earliest_presentation_time, - num_segments_++).c_str(), + num_segments_++, + options().bandwidth).c_str(), "w"); if (file == NULL) { return Status(error::FILE_FAILURE, diff --git a/packager.gyp b/packager.gyp index 3d78665ce2..7da0a63a40 100644 --- a/packager.gyp +++ b/packager.gyp @@ -30,6 +30,8 @@ 'app/packager_main.cc', 'app/packager_util.cc', 'app/packager_util.h', + 'app/stream_descriptor.cc', + 'app/stream_descriptor.h', 'app/widevine_encryption_flags.cc', 'app/widevine_encryption_flags.h', ],