[DASH] Url construction that uses $RepresentationID$.

This commit is contained in:
sr90 2020-09-25 18:57:47 -07:00
parent 5b9fd409a5
commit 83cb6a225e
23 changed files with 203 additions and 38 deletions

View File

@ -35,6 +35,7 @@ std::shared_ptr<Muxer> MuxerFactory::CreateMuxer(
options.output_file_name = stream.output;
options.segment_template = stream.segment_template;
options.bandwidth = stream.bandwidth;
options.rep_id = stream.rep_id;
std::shared_ptr<Muxer> muxer;

View File

@ -35,6 +35,7 @@ enum FieldType {
kDashRolesField,
kDashOnlyField,
kHlsOnlyField,
kRepIdField,
};
struct FieldNameToTypeMapping {
@ -81,6 +82,7 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
{"role", kDashRolesField},
{"dash_only", kDashOnlyField},
{"hls_only", kHlsOnlyField},
{"rep_id", kRepIdField},
};
FieldType GetFieldType(const std::string& field_name) {
@ -236,6 +238,9 @@ base::Optional<StreamDescriptor> ParseStreamDescriptor(
}
descriptor.hls_only = hls_only_value > 0;
break;
case kRepIdField:
descriptor.rep_id = iter->second;
break;
default:
LOG(ERROR) << "Unknown field in stream descriptor (\"" << iter->first
<< "\").";

View File

@ -114,7 +114,7 @@ Status Muxer::ReinitializeMuxer(int64_t timestamp) {
// the subclasses.
options_.output_file_name =
GetSegmentName(output_file_template_, timestamp, output_file_index_++,
options_.bandwidth);
options_.bandwidth, options_.rep_id);
}
return InitializeMuxer();
}

View File

@ -45,6 +45,10 @@ struct MuxerOptions {
/// User-specified bit rate for the media stream. If zero, the muxer will
/// attempt to estimate.
uint32_t bandwidth = 0;
/// Used as Representation id for template based url construction using
/// $RepresentationID$.
std::string rep_id;
};
} // namespace media

View File

@ -60,6 +60,8 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
bool has_number = false;
bool has_time = false;
bool has_representation = false;
// Every second substring in split output should be an identifier.
for (size_t i = 1; i < splits.size(); i += 2) {
// Each identifier may be suffixed, within the enclosing $ characters,
@ -76,9 +78,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
// TODO(kqyang): Support "RepresentationID".
if (identifier == "RepresentationID") {
return Status(
error::UNIMPLEMENTED,
"Segment template flag $RepresentationID$ is not supported yet.");
has_representation = true;
} else if (identifier == "Number") {
has_number = true;
} else if (identifier == "Time") {
@ -98,7 +98,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
error::INVALID_ARGUMENT,
"In segment templates $Number$ and $Time$ should not co-exist.");
}
if (!has_number && !has_time) {
if (!has_number && !has_time && !has_representation) {
return Status(error::INVALID_ARGUMENT,
"In segment templates $Number$ or $Time$ should exist.");
}
@ -111,7 +111,8 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
std::string GetSegmentName(const std::string& segment_template,
uint64_t segment_start_time,
uint32_t segment_index,
uint32_t bandwidth) {
uint32_t bandwidth,
std::string rep_id) {
DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template));
std::vector<std::string> splits = base::SplitString(
@ -135,7 +136,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 == "Bandwidth" || identifier == "RepresentationID");
std::string format_tag;
if (format_pos != std::string::npos) {
@ -158,6 +159,8 @@ std::string GetSegmentName(const std::string& segment_template,
} else if (identifier == "Bandwidth") {
segment_name += base::StringPrintf(format_tag.c_str(),
static_cast<uint64_t>(bandwidth));
} else if (identifier == "RepresentationID") {
segment_name += rep_id;
}
}
return segment_name;

View File

@ -35,7 +35,8 @@ Status ValidateSegmentTemplate(const std::string& segment_template);
std::string GetSegmentName(const std::string& segment_template,
uint64_t segment_start_time,
uint32_t segment_index,
uint32_t bandwidth);
uint32_t bandwidth,
std::string rep_id = "");
} // namespace media
} // namespace shaka

View File

@ -39,7 +39,7 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) {
EXPECT_NE(Status::OK, ValidateSegmentTemplate("foo$Number$_$Time$loo"));
// $RepresentationID$ not implemented yet.
EXPECT_NE(Status::OK, ValidateSegmentTemplate("$RepresentationID$__$Time$"));
EXPECT_EQ(Status::OK, ValidateSegmentTemplate("$RepresentationID$__$Time$"));
// Unknown identifier.
EXPECT_NE(Status::OK, ValidateSegmentTemplate("$foo$$Time$"));

View File

@ -200,6 +200,9 @@ void SetMediaInfoMuxerOptions(const MuxerOptions& muxer_options,
if (!muxer_options.output_file_name.empty())
media_info->set_init_segment_name(muxer_options.output_file_name);
media_info->set_segment_template(muxer_options.segment_template);
if (!muxer_options.rep_id.empty()) {
media_info->set_rep_id(muxer_options.rep_id);
}
}
}

View File

@ -176,9 +176,9 @@ Status TsSegmenter::FinalizeSegment(uint64_t start_timestamp,
// be false.
if (!segment_started_)
return Status::OK;
std::string segment_path =
GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_,
segment_number_++, muxer_options_.bandwidth);
std::string segment_path = GetSegmentName(
muxer_options_.segment_template, segment_start_timestamp_,
segment_number_++, muxer_options_.bandwidth, muxer_options_.rep_id);
const int64_t file_size = segment_buffer_.Size();
std::unique_ptr<File, FileCloser> segment_file;

View File

@ -110,9 +110,9 @@ Status MultiSegmentSegmenter::WriteSegment() {
options().output_file_name);
}
} else {
file_name = GetSegmentName(options().segment_template,
sidx()->earliest_presentation_time,
num_segments_++, options().bandwidth);
file_name = GetSegmentName(
options().segment_template, sidx()->earliest_presentation_time,
num_segments_++, options().bandwidth, options().rep_id);
file.reset(File::Open(file_name.c_str(), "w"));
if (!file) {
return Status(error::FILE_FAILURE,

View File

@ -79,7 +79,8 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id,
options().segment_template.empty()
? options().output_file_name
: GetSegmentName(options().segment_template, segment_timestamp,
segment_number_++, options().bandwidth);
segment_number_++, options().bandwidth,
options().rep_id);
// Save |segment_size| as it will be cleared after writing.
const size_t segment_size = segmenter_->segment_buffer()->Size();

View File

@ -34,7 +34,7 @@ Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp,
if (!is_subsegment) {
std::string segment_name =
GetSegmentName(options().segment_template, start_timestamp,
num_segment_, options().bandwidth);
num_segment_, options().bandwidth, options().rep_id);
// Close the file, which also does flushing, to make sure the file is
// written before manifest is updated.
@ -91,7 +91,7 @@ Status MultiSegmentSegmenter::NewSegment(uint64_t start_timestamp,
temp_file_name_ =
"memory://" + GetSegmentName(options().segment_template,
start_timestamp, num_segment_,
options().bandwidth);
options().bandwidth, options().rep_id);
writer_.reset(new MkvWriter);
Status status = writer_->Open(temp_file_name_);

View File

@ -90,8 +90,8 @@ Status WebVttTextOutputHandler::OnSegmentInfo(const SegmentInfo& info) {
const uint64_t duration = info.duration;
const uint32_t bandwidth = muxer_options_.bandwidth;
const std::string filename =
GetSegmentName(segment_template, start, index, bandwidth);
const std::string filename = GetSegmentName(segment_template, start, index,
bandwidth, muxer_options_.rep_id);
// Write everything to the file before telling the manifest so that the
// file will exist on disk.

View File

@ -336,6 +336,18 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
representation->SuppressOnce(Representation::kSuppressHeight);
if (suppress_representation_frame_rate)
representation->SuppressOnce(Representation::kSuppressFrameRate);
if (include_segment_template_in_adaptation_set)
representation->SuppressOnce(Representation::kSuppressSegmentTemplate);
if (!adaptation_set.GetRawPtr()->children &&
include_segment_template_in_adaptation_set) {
xml::scoped_xml_ptr<xmlNode> live_child(
representation->GetLiveOnlyInfo());
if (!live_child || !adaptation_set.AddChild(std::move(live_child)))
return xml::scoped_xml_ptr<xmlNode>();
}
xml::scoped_xml_ptr<xmlNode> child(representation->GetXml());
if (!child || !adaptation_set.AddChild(std::move(child)))
return xml::scoped_xml_ptr<xmlNode>();
@ -409,7 +421,9 @@ void AdaptationSet::UpdateFromMediaInfo(const MediaInfo& media_info) {
AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
}
if (media_info.has_rep_id()) {
include_segment_template_in_adaptation_set = true;
}
if (media_info.has_video_info()) {
content_type_ = "video";
} else if (media_info.has_audio_info()) {

View File

@ -317,6 +317,10 @@ class AdaptationSet {
// and HD videos in different AdaptationSets can share the same trick play
// stream.
std::vector<const AdaptationSet*> trick_play_references_;
// Set to true if SegmentTemplate needs to be added to AdaptationSet,
// instead of Representation.
bool include_segment_template_in_adaptation_set = false;
};
} // namespace shaka

View File

@ -184,6 +184,7 @@ message MediaInfo {
// This value is not necessarily the same as the value passed to
// MpdNotifier::NotifyNewSegment().
optional float segment_duration_seconds = 12 [deprecated = true];
optional string rep_id = 23;
// END LIVE only.
// URL fields for the corresponding file_name fields above.

View File

@ -441,11 +441,23 @@ void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
MakePathRelative(media_info->init_segment_name(), mpd_dir));
}
if (media_info->has_segment_template()) {
if (media_info->has_rep_id()) {
if (media_info->segment_template().find("$RepresentationID") !=
std::string::npos) {
std::string temp = media_info->segment_template();
temp.replace(
media_info->segment_template().find("$RepresentationID"), 18,
media_info->rep_id());
media_info->set_segment_template_url(
MakePathRelative(temp, mpd_dir));
}
} else {
media_info->set_segment_template_url(
MakePathRelative(media_info->segment_template(), mpd_dir));
}
}
}
}
}
} // namespace shaka

View File

@ -95,7 +95,9 @@ Representation::Representation(
// TODO(kqyang): Need a better check. $Time is legitimate but not a
// template.
media_info.segment_template().find("$Time") == std::string::npos &&
mpd_options_.mpd_params.allow_approximate_segment_timeline) {}
mpd_options_.mpd_params.allow_approximate_segment_timeline),
rep_id_set((media_info.segment_template().find("$RepresentationID$") !=
std::string::npos)) {}
Representation::Representation(
const Representation& representation,
@ -212,6 +214,16 @@ const MediaInfo& Representation::GetMediaInfo() const {
return media_info_;
}
xml::scoped_xml_ptr<xmlNode> Representation::GetLiveOnlyInfo() {
xml::RepresentationXmlNode representation;
if (HasLiveOnlyFields(media_info_)) {
return representation.GetLiveOnlyInfo(media_info_, segment_infos_,
start_number_);
}
return xml::scoped_xml_ptr<xmlNode>();
}
// Uses info in |media_info_| and |content_protection_elements_| to create a
// "Representation" node.
// MPD schema has strict ordering. The following must be done in order.
@ -232,7 +244,12 @@ xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
xml::RepresentationXmlNode representation;
// Mandatory fields for Representation.
if (rep_id_set) {
representation.SetIdString(media_info_.rep_id());
} else {
representation.SetId(id_);
}
representation.SetIntegerAttribute("bandwidth", bandwidth);
if (!codecs_.empty())
representation.SetStringAttribute("codecs", codecs_);
@ -269,6 +286,7 @@ xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
}
if (HasLiveOnlyFields(media_info_) &&
!(output_suppression_flags_ & kSuppressSegmentTemplate) &&
!representation.AddLiveOnlyInfo(media_info_, segment_infos_,
start_number_)) {
LOG(ERROR) << "Failed to add Live info.";
@ -452,9 +470,9 @@ void Representation::RemoveOldSegment(SegmentInfo* segment_info) {
if (mpd_options_.mpd_params.preserved_segments_outside_live_window == 0)
return;
segments_to_be_removed_.push_back(
media::GetSegmentName(media_info_.segment_template(), segment_start_time,
start_number_ - 1, media_info_.bandwidth()));
segments_to_be_removed_.push_back(media::GetSegmentName(
media_info_.segment_template(), segment_start_time, start_number_ - 1,
media_info_.bandwidth(), media_info_.rep_id()));
while (segments_to_be_removed_.size() >
mpd_options_.mpd_params.preserved_segments_outside_live_window) {
VLOG(2) << "Deleting " << segments_to_be_removed_.front();

View File

@ -57,6 +57,7 @@ class Representation {
kSuppressWidth = 1,
kSuppressHeight = 2,
kSuppressFrameRate = 4,
kSuppressSegmentTemplate = 8,
};
virtual ~Representation();
@ -118,6 +119,9 @@ class Representation {
/// @return Copy of <Representation>.
xml::scoped_xml_ptr<xmlNode> GetXml();
/// @return SegmentTemplate xmlNode if live information is present.
xml::scoped_xml_ptr<xmlNode> GetLiveOnlyInfo();
/// By calling this methods, the next time GetXml() is
/// called, the corresponding attributes will not be set.
/// For example, if SuppressOnce(kSuppressWidth) is called, then GetXml() will
@ -244,6 +248,9 @@ class Representation {
// Segments with duration difference less than one frame duration are
// considered to have the same duration.
uint32_t frame_duration_ = 0;
// When set to true, adds $RepresentationID$ in SegmentTemplate.
bool rep_id_set = false;
};
} // namespace shaka

View File

@ -197,6 +197,10 @@ void XmlNode::SetId(uint32_t id) {
SetIntegerAttribute("id", id);
}
void XmlNode::SetIdString(std::string id) {
SetStringAttribute("id", id);
}
void XmlNode::SetContent(const std::string& content) {
DCHECK(node_);
xmlNodeSetContent(node_.get(), BAD_CAST content.c_str());
@ -404,6 +408,77 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
return true;
}
scoped_xml_ptr<xmlNode> RepresentationXmlNode::GetLiveOnlyInfo(
const MediaInfo& media_info,
const std::list<SegmentInfo>& segment_infos,
uint32_t start_number) {
XmlNode segment_template("SegmentTemplate");
if (media_info.has_reference_time_scale()) {
segment_template.SetIntegerAttribute("timescale",
media_info.reference_time_scale());
}
if (media_info.has_presentation_time_offset()) {
segment_template.SetIntegerAttribute("presentationTimeOffset",
media_info.presentation_time_offset());
}
if (media_info.has_init_segment_url()) {
if (media_info.has_rep_id() &&
media_info.init_segment_url().find(media_info.rep_id()) !=
std::string::npos) {
std::string temp = media_info.init_segment_url();
temp.replace(media_info.init_segment_url().find(media_info.rep_id()),
media_info.rep_id().length(), "$RepresentationID$");
segment_template.SetStringAttribute("initialization", temp);
} else {
segment_template.SetStringAttribute("initialization",
media_info.init_segment_url());
}
}
if (media_info.has_segment_template_url()) {
if (media_info.has_rep_id() &&
media_info.segment_template_url().find(media_info.rep_id()) !=
std::string::npos) {
std::string temp = media_info.segment_template_url();
temp.replace(media_info.segment_template_url().find(media_info.rep_id()),
media_info.rep_id().length(), "$RepresentationID$");
segment_template.SetStringAttribute("media", temp);
} else {
segment_template.SetStringAttribute("media",
media_info.segment_template_url());
}
segment_template.SetIntegerAttribute("startNumber", start_number);
}
if (!segment_infos.empty()) {
// Don't use SegmentTimeline if all segments except the last one are of
// the same duration.
if (IsTimelineConstantDuration(segment_infos, start_number)) {
segment_template.SetIntegerAttribute("duration",
segment_infos.front().duration);
if (FLAGS_dash_add_last_segment_number_when_needed) {
uint32_t last_segment_number = start_number - 1;
for (const auto& segment_info_element : segment_infos)
last_segment_number += segment_info_element.repeat + 1;
AddSupplementalProperty(
"http://dashif.org/guidelines/last-segment-number",
std::to_string(last_segment_number));
}
} else {
XmlNode segment_timeline("SegmentTimeline");
CHECK(PopulateSegmentTimeline(segment_infos, &segment_timeline));
CHECK(segment_template.AddChild(segment_timeline.PassScopedPtr()));
}
}
return segment_template.PassScopedPtr();
}
bool RepresentationXmlNode::AddLiveOnlyInfo(
const MediaInfo& media_info,
const std::list<SegmentInfo>& segment_infos,
@ -483,8 +558,8 @@ bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
audio_channel_config_value = base::UintToString(ec3_channel_mpeg_value);
audio_channel_config_scheme = "urn:mpeg:mpegB:cicp:ChannelConfiguration";
}
bool ret = AddDescriptor("AudioChannelConfiguration",
audio_channel_config_scheme,
bool ret =
AddDescriptor("AudioChannelConfiguration", audio_channel_config_scheme,
audio_channel_config_value);
// Dolby Digital Plus JOC descriptor. Spec: ETSI TS 103 420 v1.2.1
// Backwards-compatible object audio carriage using Enhanced AC-3 Standard

View File

@ -66,6 +66,10 @@ class XmlNode {
/// @param id is the ID for this element.
void SetId(uint32_t id);
/// Sets 'id=rep_id' attribute.
/// @param id is the ID for this element.
void SetIdString(std::string id);
/// Set the contents of an XML element using a string.
/// This cannot set child elements because <> will become &lt; and &rt;
/// This should be used to set the text for the element, e.g. setting
@ -188,6 +192,14 @@ class RepresentationXmlNode : public RepresentationBaseXmlNode {
const std::list<SegmentInfo>& segment_infos,
uint32_t start_number);
/// @param segment_infos is a set of SegmentInfos. This method assumes that
/// SegmentInfos are sorted by its start time.
/// @return SegmentTemplate node.
scoped_xml_ptr<xmlNode> GetLiveOnlyInfo(
const MediaInfo& media_info,
const std::list<SegmentInfo>& segment_infos,
uint32_t start_number);
private:
// Add AudioChannelConfiguration element. Note that it is a required element
// for audio Representations.

View File

@ -316,7 +316,8 @@ Status ValidateParams(const PackagingParams& packaging_params,
}
if (!descriptor.output.empty()) {
if (outputs.find(descriptor.output) != outputs.end()) {
if (descriptor.output.find("$RepresentationID$") == std::string::npos &&
outputs.find(descriptor.output) != outputs.end()) {
return Status(
error::INVALID_ARGUMENT,
"Seeing duplicated outputs '" + descriptor.output +
@ -325,7 +326,9 @@ Status ValidateParams(const PackagingParams& packaging_params,
outputs.insert(descriptor.output);
}
if (!descriptor.segment_template.empty()) {
if (segment_templates.find(descriptor.segment_template) !=
if (descriptor.segment_template.find("$RepresentationID$") ==
std::string::npos &&
segment_templates.find(descriptor.segment_template) !=
segment_templates.end()) {
return Status(error::INVALID_ARGUMENT,
"Seeing duplicated segment templates '" +

View File

@ -133,6 +133,8 @@ struct StreamDescriptor {
bool dash_only = false;
/// Set to true to indicate that the stream is for hls only.
bool hls_only = false;
std::string rep_id;
};
class SHAKA_EXPORT Packager {
@ -144,8 +146,7 @@ class SHAKA_EXPORT Packager {
/// @param packaging_params contains the packaging parameters.
/// @param stream_descriptors a list of stream descriptors.
/// @return OK on success, an appropriate error code on failure.
Status Initialize(
const PackagingParams& packaging_params,
Status Initialize(const PackagingParams& packaging_params,
const std::vector<StreamDescriptor>& stream_descriptors);
/// Run the pipeline to completion (or failed / been cancelled). Note