Implemented name/value pair stream descriptors.

Implemented user-specified bandwidth.

Change-Id: If01f26867d4285bcbc899310d3f1b98b928bed89
This commit is contained in:
Thomas Inskip 2014-06-27 16:07:36 -07:00
parent 50aea3e6f6
commit efe52e62dc
11 changed files with 314 additions and 76 deletions

View File

@ -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] <stream_descriptor> ...\n"
"stream_descriptor may be repeated and consists of a tuplet as follows:\n"
"<input_file>#<stream_selector>,<output_file>[,<segment_template>]\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<std::string> 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<Demuxer> demux(new Demuxer(file_path, NULL));
scoped_ptr<Demuxer> 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<RemuxJob*>& 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;
}

122
app/stream_descriptor.cc Normal file
View File

@ -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

53
app/stream_descriptor.h Normal file
View File

@ -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 <set>
#include <string>
#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<StreamDescriptor, StreamDescriptorCompareFn>
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_

View File

@ -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

View File

@ -9,6 +9,8 @@
#include <string>
#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

View File

@ -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<std::string> 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;

View File

@ -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

View File

@ -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

View File

@ -15,6 +15,7 @@
namespace media {
enum StreamType {
kStreamUnknown = 0,
kStreamAudio,
kStreamVideo,
};

View File

@ -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,

View File

@ -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',
],