Implemented name/value pair stream descriptors.
Implemented user-specified bandwidth. Change-Id: If01f26867d4285bcbc899310d3f1b98b928bed89
This commit is contained in:
parent
50aea3e6f6
commit
efe52e62dc
|
@ -11,6 +11,7 @@
|
||||||
#include "app/mpd_flags.h"
|
#include "app/mpd_flags.h"
|
||||||
#include "app/muxer_flags.h"
|
#include "app/muxer_flags.h"
|
||||||
#include "app/packager_util.h"
|
#include "app/packager_util.h"
|
||||||
|
#include "app/stream_descriptor.h"
|
||||||
#include "app/widevine_encryption_flags.h"
|
#include "app/widevine_encryption_flags.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/stl_util.h"
|
#include "base/stl_util.h"
|
||||||
|
@ -31,18 +32,23 @@ namespace {
|
||||||
const char kUsage[] =
|
const char kUsage[] =
|
||||||
"Packager driver program. Sample Usage:\n"
|
"Packager driver program. Sample Usage:\n"
|
||||||
"%s [flags] <stream_descriptor> ...\n"
|
"%s [flags] <stream_descriptor> ...\n"
|
||||||
"stream_descriptor may be repeated and consists of a tuplet as follows:\n"
|
"stream_descriptor consists of comma separated field_name/value pairs:\n"
|
||||||
"<input_file>#<stream_selector>,<output_file>[,<segment_template>]\n"
|
"field_name=value,[field_name=value,]...\n"
|
||||||
" - input_file is a file path or network stream URL.\n"
|
"Supported field names are as follows:\n"
|
||||||
" - stream_selector is one of 'audio', 'video', or stream number.\n"
|
" - input (in): Required input/source media file path or network stream "
|
||||||
" - output_file is the output file (single file) or initialization file"
|
"URL.\n"
|
||||||
" path (multiple file)."
|
" - stream_selector (stream): Required field with value 'audio', 'video', "
|
||||||
" - segment_template is an optional value which specifies the naming"
|
"or stream number (zero based).\n"
|
||||||
" pattern for the segment files, and that the stream should be split into"
|
" - output (out): Required output file (single file) or initialization "
|
||||||
" multiple files. Its presence should be consistent across streams.\n";
|
"file path (multiple file).\n"
|
||||||
|
" - segment_template (segment): Optional value which specifies the "
|
||||||
typedef std::vector<std::string> StringVector;
|
"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
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
@ -88,7 +94,7 @@ class RemuxJob : public base::SimpleThread {
|
||||||
DISALLOW_COPY_AND_ASSIGN(RemuxJob);
|
DISALLOW_COPY_AND_ASSIGN(RemuxJob);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool CreateRemuxJobs(const StringVector& stream_descriptors,
|
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
const MuxerOptions& muxer_options,
|
const MuxerOptions& muxer_options,
|
||||||
EncryptionKeySource* key_source,
|
EncryptionKeySource* key_source,
|
||||||
MpdNotifier* mpd_notifier,
|
MpdNotifier* mpd_notifier,
|
||||||
|
@ -97,55 +103,40 @@ bool CreateRemuxJobs(const StringVector& stream_descriptors,
|
||||||
DCHECK(muxer_listeners);
|
DCHECK(muxer_listeners);
|
||||||
DCHECK(remux_jobs);
|
DCHECK(remux_jobs);
|
||||||
|
|
||||||
// Sort the stream descriptors so that we can group muxers by demux.
|
std::string previous_input;
|
||||||
StringVector sorted_descriptors(stream_descriptors);
|
for (StreamDescriptorList::const_iterator stream_iter =
|
||||||
std::sort(sorted_descriptors.begin(), sorted_descriptors.end());
|
stream_descriptors.begin();
|
||||||
|
stream_iter != stream_descriptors.end();
|
||||||
std::string previous_file_path;
|
|
||||||
for (StringVector::const_iterator stream_iter = sorted_descriptors.begin();
|
|
||||||
stream_iter != sorted_descriptors.end();
|
|
||||||
++stream_iter) {
|
++stream_iter) {
|
||||||
// Process stream descriptor.
|
// 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);
|
MuxerOptions stream_muxer_options(muxer_options);
|
||||||
std::string file_path(descriptor[0].substr(0, hash_pos));
|
stream_muxer_options.output_file_name = stream_iter->output;
|
||||||
std::string stream_selector(descriptor[0].substr(hash_pos + 1));
|
if (!stream_iter->segment_template.empty()) {
|
||||||
stream_muxer_options.output_file_name = descriptor[1];
|
if (!ValidateSegmentTemplate(stream_iter->segment_template)) {
|
||||||
if (descriptor.size() == 3) {
|
|
||||||
stream_muxer_options.segment_template = descriptor[2];
|
|
||||||
if (!ValidateSegmentTemplate(stream_muxer_options.segment_template)) {
|
|
||||||
LOG(ERROR) << "ERROR: segment template with '"
|
LOG(ERROR) << "ERROR: segment template with '"
|
||||||
<< stream_muxer_options.segment_template << "' is invalid.";
|
<< stream_iter->segment_template << "' is invalid.";
|
||||||
return false;
|
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.
|
// 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();
|
Status status = demux->Initialize();
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString();
|
LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (FLAGS_dump_stream_info) {
|
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());
|
DumpStreamInfo(demux->streams());
|
||||||
|
if (stream_iter->output.empty())
|
||||||
|
continue; // just need stream info.
|
||||||
}
|
}
|
||||||
remux_jobs->push_back(new RemuxJob(demux.Pass()));
|
remux_jobs->push_back(new RemuxJob(demux.Pass()));
|
||||||
previous_file_path = file_path;
|
previous_input = stream_iter->input;
|
||||||
}
|
}
|
||||||
DCHECK(!remux_jobs->empty());
|
DCHECK(!remux_jobs->empty());
|
||||||
|
|
||||||
|
@ -183,7 +174,7 @@ bool CreateRemuxJobs(const StringVector& stream_descriptors,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AddStreamToMuxer(remux_jobs->back()->demuxer()->streams(),
|
if (!AddStreamToMuxer(remux_jobs->back()->demuxer()->streams(),
|
||||||
stream_selector,
|
stream_iter->stream_selector,
|
||||||
muxer.get()))
|
muxer.get()))
|
||||||
return false;
|
return false;
|
||||||
remux_jobs->back()->AddMuxer(muxer.Pass());
|
remux_jobs->back()->AddMuxer(muxer.Pass());
|
||||||
|
@ -222,7 +213,7 @@ Status RunRemuxJobs(const std::vector<RemuxJob*>& remux_jobs) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RunPackager(const StringVector& stream_descriptors) {
|
bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
||||||
if (!AssignFlagsFromProfile())
|
if (!AssignFlagsFromProfile())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -305,8 +296,12 @@ int main(int argc, char** argv) {
|
||||||
LOG(ERROR) << "Could not initialize libcrypto threading.";
|
LOG(ERROR) << "Could not initialize libcrypto threading.";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
StringVector stream_descriptors;
|
// TODO(tinskip): Make InsertStreamDescriptor a member of
|
||||||
for (int i = 1; i < argc; ++i)
|
// StreamDescriptorList.
|
||||||
stream_descriptors.push_back(argv[i]);
|
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;
|
return media::RunPackager(stream_descriptors) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -15,7 +15,8 @@ MuxerOptions::MuxerOptions()
|
||||||
segment_sap_aligned(false),
|
segment_sap_aligned(false),
|
||||||
fragment_sap_aligned(false),
|
fragment_sap_aligned(false),
|
||||||
normalize_presentation_timestamp(false),
|
normalize_presentation_timestamp(false),
|
||||||
num_subsegments_per_sidx(0) {}
|
num_subsegments_per_sidx(0),
|
||||||
|
bandwidth(0) {}
|
||||||
MuxerOptions::~MuxerOptions() {}
|
MuxerOptions::~MuxerOptions() {}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "base/basictypes.h"
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
/// This structure contains the list of configuration options for Muxer.
|
/// This structure contains the list of configuration options for Muxer.
|
||||||
|
@ -62,6 +64,9 @@ struct MuxerOptions {
|
||||||
|
|
||||||
/// Specify temporary directory for intermediate files.
|
/// Specify temporary directory for intermediate files.
|
||||||
std::string temp_dir;
|
std::string temp_dir;
|
||||||
|
|
||||||
|
/// User-specified bandwidth for the stream. zero means "unspecified".
|
||||||
|
uint32 bandwidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -63,10 +63,10 @@ bool ValidateSegmentTemplate(const std::string& segment_template) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kqyang): Support "RepresentationID" and "Bandwidth".
|
// TODO(kqyang): Support "RepresentationID".
|
||||||
if (identifier == "RepresentationID" || identifier == "Bandwidth") {
|
if (identifier == "RepresentationID") {
|
||||||
NOTIMPLEMENTED() << "SegmentTemplate: $RepresentationID$ and $Bandwidth$ "
|
NOTIMPLEMENTED() << "SegmentTemplate: $RepresentationID$ is not supported "
|
||||||
"are not supported yet.";
|
"yet.";
|
||||||
return false;
|
return false;
|
||||||
} else if (identifier == "Number") {
|
} else if (identifier == "Number") {
|
||||||
has_number = true;
|
has_number = true;
|
||||||
|
@ -77,7 +77,7 @@ bool ValidateSegmentTemplate(const std::string& segment_template) {
|
||||||
LOG(ERROR) << "SegmentTemplate: $$ should not have any format tags.";
|
LOG(ERROR) << "SegmentTemplate: $$ should not have any format tags.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (identifier != "Bandwidth") {
|
||||||
LOG(ERROR) << "SegmentTemplate: '$" << splits[i]
|
LOG(ERROR) << "SegmentTemplate: '$" << splits[i]
|
||||||
<< "$' is not a valid identifier.";
|
<< "$' is not a valid identifier.";
|
||||||
return false;
|
return false;
|
||||||
|
@ -99,7 +99,8 @@ bool ValidateSegmentTemplate(const std::string& segment_template) {
|
||||||
|
|
||||||
std::string GetSegmentName(const std::string& segment_template,
|
std::string GetSegmentName(const std::string& segment_template,
|
||||||
uint64 segment_start_time,
|
uint64 segment_start_time,
|
||||||
uint32 segment_index) {
|
uint32 segment_index,
|
||||||
|
uint32 bandwidth) {
|
||||||
DCHECK(ValidateSegmentTemplate(segment_template));
|
DCHECK(ValidateSegmentTemplate(segment_template));
|
||||||
|
|
||||||
std::vector<std::string> splits;
|
std::vector<std::string> splits;
|
||||||
|
@ -122,7 +123,8 @@ 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");
|
||||||
|
|
||||||
std::string format_tag;
|
std::string format_tag;
|
||||||
if (format_pos != std::string::npos) {
|
if (format_pos != std::string::npos) {
|
||||||
|
@ -142,6 +144,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 == "Bandwidth") {
|
||||||
|
segment_name +=
|
||||||
|
base::StringPrintf(format_tag.c_str(), bandwidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return segment_name;
|
return segment_name;
|
||||||
|
|
|
@ -27,10 +27,12 @@ bool ValidateSegmentTemplate(const std::string& segment_template);
|
||||||
/// comply with ISO/IEC 23009-1:2012 5.3.9.4.4.
|
/// comply with ISO/IEC 23009-1:2012 5.3.9.4.4.
|
||||||
/// @param segment_start_time specifies the segment start time.
|
/// @param segment_start_time specifies the segment start time.
|
||||||
/// @param segment_index specifies the segment index.
|
/// @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.
|
/// @return The segment name with identifier substituted.
|
||||||
std::string GetSegmentName(const std::string& segment_template,
|
std::string GetSegmentName(const std::string& segment_template,
|
||||||
uint64 segment_start_time,
|
uint64 segment_start_time,
|
||||||
uint32 segment_index);
|
uint32 segment_index,
|
||||||
|
uint32 bandwidth);
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,11 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) {
|
||||||
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$"));
|
||||||
|
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 "$$".
|
// Escape sequence "$$".
|
||||||
EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$__$$loo"));
|
EXPECT_TRUE(ValidateSegmentTemplate("foo$Time$__$$loo"));
|
||||||
|
@ -32,9 +37,8 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) {
|
||||||
EXPECT_FALSE(ValidateSegmentTemplate("$Number$$Time$"));
|
EXPECT_FALSE(ValidateSegmentTemplate("$Number$$Time$"));
|
||||||
EXPECT_FALSE(ValidateSegmentTemplate("foo$Number$_$Time$loo"));
|
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("$RepresentationID$__$Time$"));
|
||||||
EXPECT_FALSE(ValidateSegmentTemplate("foo$Bandwidth$$Time$"));
|
|
||||||
|
|
||||||
// Unknown identifier.
|
// Unknown identifier.
|
||||||
EXPECT_FALSE(ValidateSegmentTemplate("$foo$$Time$"));
|
EXPECT_FALSE(ValidateSegmentTemplate("$foo$$Time$"));
|
||||||
|
@ -58,47 +62,94 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) {
|
||||||
TEST(MuxerUtilTest, GetSegmentName) {
|
TEST(MuxerUtilTest, GetSegmentName) {
|
||||||
const uint64 kSegmentStartTime = 180180;
|
const uint64 kSegmentStartTime = 180180;
|
||||||
const uint32 kSegmentIndex = 11;
|
const uint32 kSegmentIndex = 11;
|
||||||
|
const uint32 kBandwidth = 1234;
|
||||||
EXPECT_EQ("12", GetSegmentName("$Number$", kSegmentStartTime, kSegmentIndex));
|
EXPECT_EQ("12", GetSegmentName("$Number$",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
kBandwidth));
|
||||||
EXPECT_EQ("012",
|
EXPECT_EQ("012",
|
||||||
GetSegmentName("$Number%03d$", kSegmentStartTime, kSegmentIndex));
|
GetSegmentName("$Number%03d$",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
kBandwidth));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
"12$foo$00012",
|
"12$foo$00012",
|
||||||
GetSegmentName(
|
GetSegmentName(
|
||||||
"$Number%01d$$$foo$$$Number%05d$", kSegmentStartTime, kSegmentIndex));
|
"$Number%01d$$$foo$$$Number%05d$",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
kBandwidth));
|
||||||
|
|
||||||
EXPECT_EQ("180180",
|
EXPECT_EQ("180180",
|
||||||
GetSegmentName("$Time$", kSegmentStartTime, kSegmentIndex));
|
GetSegmentName("$Time$",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
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,
|
||||||
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.
|
// Format specifier edge cases.
|
||||||
EXPECT_EQ("12",
|
EXPECT_EQ("12",
|
||||||
GetSegmentName("$Number%00d$", kSegmentStartTime, kSegmentIndex));
|
GetSegmentName("$Number%00d$",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
kBandwidth));
|
||||||
EXPECT_EQ("00012",
|
EXPECT_EQ("00012",
|
||||||
GetSegmentName("$Number%005d$", kSegmentStartTime, kSegmentIndex));
|
GetSegmentName("$Number%005d$",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
kBandwidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) {
|
TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) {
|
||||||
const uint64 kSegmentStartTime = 0;
|
const uint64 kSegmentStartTime = 0;
|
||||||
const uint32 kSegmentIndex = 0;
|
const uint32 kSegmentIndex = 0;
|
||||||
|
const uint32 kBandwidth = 0;
|
||||||
EXPECT_EQ("1", GetSegmentName("$Number$", kSegmentStartTime, kSegmentIndex));
|
EXPECT_EQ("1", GetSegmentName("$Number$",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
kBandwidth));
|
||||||
EXPECT_EQ("001",
|
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",
|
EXPECT_EQ("00000000.m4s",
|
||||||
GetSegmentName("$Time%08d$.m4s", kSegmentStartTime, kSegmentIndex));
|
GetSegmentName("$Time%08d$.m4s",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
kBandwidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MuxerUtilTest, GetSegmentNameLargeTime) {
|
TEST(MuxerUtilTest, GetSegmentNameLargeTime) {
|
||||||
const uint64 kSegmentStartTime = 1601599839840ULL;
|
const uint64 kSegmentStartTime = 1601599839840ULL;
|
||||||
const uint32 kSegmentIndex = 8888888;
|
const uint32 kSegmentIndex = 8888888;
|
||||||
|
const uint32 kBandwidth = 444444;
|
||||||
EXPECT_EQ("1601599839840",
|
EXPECT_EQ("1601599839840",
|
||||||
GetSegmentName("$Time$", kSegmentStartTime, kSegmentIndex));
|
GetSegmentName("$Time$",
|
||||||
|
kSegmentStartTime,
|
||||||
|
kSegmentIndex,
|
||||||
|
kBandwidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
enum StreamType {
|
enum StreamType {
|
||||||
|
kStreamUnknown = 0,
|
||||||
kStreamAudio,
|
kStreamAudio,
|
||||||
kStreamVideo,
|
kStreamVideo,
|
||||||
};
|
};
|
||||||
|
|
|
@ -148,7 +148,8 @@ Status MultiSegmentSegmenter::WriteSegment() {
|
||||||
} else {
|
} else {
|
||||||
file = File::Open(GetSegmentName(options().segment_template,
|
file = File::Open(GetSegmentName(options().segment_template,
|
||||||
sidx()->earliest_presentation_time,
|
sidx()->earliest_presentation_time,
|
||||||
num_segments_++).c_str(),
|
num_segments_++,
|
||||||
|
options().bandwidth).c_str(),
|
||||||
"w");
|
"w");
|
||||||
if (file == NULL) {
|
if (file == NULL) {
|
||||||
return Status(error::FILE_FAILURE,
|
return Status(error::FILE_FAILURE,
|
||||||
|
|
|
@ -30,6 +30,8 @@
|
||||||
'app/packager_main.cc',
|
'app/packager_main.cc',
|
||||||
'app/packager_util.cc',
|
'app/packager_util.cc',
|
||||||
'app/packager_util.h',
|
'app/packager_util.h',
|
||||||
|
'app/stream_descriptor.cc',
|
||||||
|
'app/stream_descriptor.h',
|
||||||
'app/widevine_encryption_flags.cc',
|
'app/widevine_encryption_flags.cc',
|
||||||
'app/widevine_encryption_flags.h',
|
'app/widevine_encryption_flags.h',
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue