From 83cb6a225eddbb8a6e9951f123eda7edf5458545 Mon Sep 17 00:00:00 2001 From: sr90 Date: Fri, 25 Sep 2020 18:57:47 -0700 Subject: [PATCH] [DASH] Url construction that uses $RepresentationID$. --- packager/app/muxer_factory.cc | 1 + packager/app/stream_descriptor.cc | 5 ++ packager/media/base/muxer.cc | 2 +- packager/media/base/muxer_options.h | 4 + packager/media/base/muxer_util.cc | 15 ++-- packager/media/base/muxer_util.h | 3 +- packager/media/base/muxer_util_unittest.cc | 2 +- .../media/event/muxer_listener_internal.cc | 3 + packager/media/formats/mp2t/ts_segmenter.cc | 6 +- .../formats/mp4/multi_segment_segmenter.cc | 6 +- .../packed_audio/packed_audio_writer.cc | 3 +- .../formats/webm/multi_segment_segmenter.cc | 4 +- .../webvtt/webvtt_text_output_handler.cc | 4 +- packager/mpd/base/adaptation_set.cc | 16 +++- packager/mpd/base/adaptation_set.h | 4 + packager/mpd/base/media_info.proto | 1 + packager/mpd/base/mpd_builder.cc | 16 +++- packager/mpd/base/representation.cc | 28 +++++-- packager/mpd/base/representation.h | 7 ++ packager/mpd/base/xml/xml_node.cc | 83 ++++++++++++++++++- packager/mpd/base/xml/xml_node.h | 12 +++ packager/packager.cc | 9 +- packager/packager.h | 7 +- 23 files changed, 203 insertions(+), 38 deletions(-) diff --git a/packager/app/muxer_factory.cc b/packager/app/muxer_factory.cc index 159889c5ca..269dcba881 100644 --- a/packager/app/muxer_factory.cc +++ b/packager/app/muxer_factory.cc @@ -35,6 +35,7 @@ std::shared_ptr 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; diff --git a/packager/app/stream_descriptor.cc b/packager/app/stream_descriptor.cc index aa2208f9a5..7b3b168289 100644 --- a/packager/app/stream_descriptor.cc +++ b/packager/app/stream_descriptor.cc @@ -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 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 << "\")."; diff --git a/packager/media/base/muxer.cc b/packager/media/base/muxer.cc index cf8212d75f..0a95c7723e 100644 --- a/packager/media/base/muxer.cc +++ b/packager/media/base/muxer.cc @@ -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(); } diff --git a/packager/media/base/muxer_options.h b/packager/media/base/muxer_options.h index b69d2f794b..a6e5ef2f54 100644 --- a/packager/media/base/muxer_options.h +++ b/packager/media/base/muxer_options.h @@ -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 diff --git a/packager/media/base/muxer_util.cc b/packager/media/base/muxer_util.cc index 86571cb062..9e3605f1a3 100644 --- a/packager/media/base/muxer_util.cc +++ b/packager/media/base/muxer_util.cc @@ -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 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(bandwidth)); + } else if (identifier == "RepresentationID") { + segment_name += rep_id; } } return segment_name; diff --git a/packager/media/base/muxer_util.h b/packager/media/base/muxer_util.h index 43aa0177a2..6b944c0221 100644 --- a/packager/media/base/muxer_util.h +++ b/packager/media/base/muxer_util.h @@ -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 diff --git a/packager/media/base/muxer_util_unittest.cc b/packager/media/base/muxer_util_unittest.cc index 7e0c14616f..fa9c9b4bc9 100644 --- a/packager/media/base/muxer_util_unittest.cc +++ b/packager/media/base/muxer_util_unittest.cc @@ -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$")); diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index 013d8a2a58..44c38eb0c9 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -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); + } } } diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index 4bedb1e32b..e5e089b554 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -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 segment_file; diff --git a/packager/media/formats/mp4/multi_segment_segmenter.cc b/packager/media/formats/mp4/multi_segment_segmenter.cc index cf12e4c170..c845fba311 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.cc +++ b/packager/media/formats/mp4/multi_segment_segmenter.cc @@ -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, diff --git a/packager/media/formats/packed_audio/packed_audio_writer.cc b/packager/media/formats/packed_audio/packed_audio_writer.cc index 29f796c56e..9b1fdfee3f 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer.cc @@ -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(); diff --git a/packager/media/formats/webm/multi_segment_segmenter.cc b/packager/media/formats/webm/multi_segment_segmenter.cc index 5cd46dddae..8ea825d37d 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.cc +++ b/packager/media/formats/webm/multi_segment_segmenter.cc @@ -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_); diff --git a/packager/media/formats/webvtt/webvtt_text_output_handler.cc b/packager/media/formats/webvtt/webvtt_text_output_handler.cc index 3043209114..5312d0030f 100644 --- a/packager/media/formats/webvtt/webvtt_text_output_handler.cc +++ b/packager/media/formats/webvtt/webvtt_text_output_handler.cc @@ -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. diff --git a/packager/mpd/base/adaptation_set.cc b/packager/mpd/base/adaptation_set.cc index d0a0581309..668f1bece3 100644 --- a/packager/mpd/base/adaptation_set.cc +++ b/packager/mpd/base/adaptation_set.cc @@ -336,6 +336,18 @@ xml::scoped_xml_ptr 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 live_child( + representation->GetLiveOnlyInfo()); + if (!live_child || !adaptation_set.AddChild(std::move(live_child))) + return xml::scoped_xml_ptr(); + } + xml::scoped_xml_ptr child(representation->GetXml()); if (!child || !adaptation_set.AddChild(std::move(child))) return xml::scoped_xml_ptr(); @@ -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()) { diff --git a/packager/mpd/base/adaptation_set.h b/packager/mpd/base/adaptation_set.h index a8fc70605e..1a158f2d77 100644 --- a/packager/mpd/base/adaptation_set.h +++ b/packager/mpd/base/adaptation_set.h @@ -317,6 +317,10 @@ class AdaptationSet { // and HD videos in different AdaptationSets can share the same trick play // stream. std::vector 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 diff --git a/packager/mpd/base/media_info.proto b/packager/mpd/base/media_info.proto index 5e2f8619ff..0a6ab4c1a1 100644 --- a/packager/mpd/base/media_info.proto +++ b/packager/mpd/base/media_info.proto @@ -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. diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index f515586563..1474104bbd 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -441,8 +441,20 @@ void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path, MakePathRelative(media_info->init_segment_name(), mpd_dir)); } if (media_info->has_segment_template()) { - media_info->set_segment_template_url( - MakePathRelative(media_info->segment_template(), mpd_dir)); + 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)); + } } } } diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index b1b46c8c2e..1b7a73fc65 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -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 Representation::GetLiveOnlyInfo() { + xml::RepresentationXmlNode representation; + + if (HasLiveOnlyFields(media_info_)) { + return representation.GetLiveOnlyInfo(media_info_, segment_infos_, + start_number_); + } + return xml::scoped_xml_ptr(); +} + // 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 Representation::GetXml() { xml::RepresentationXmlNode representation; // Mandatory fields for Representation. - representation.SetId(id_); + + 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 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(); diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h index 813d43edc5..2cceceb591 100644 --- a/packager/mpd/base/representation.h +++ b/packager/mpd/base/representation.h @@ -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 . xml::scoped_xml_ptr GetXml(); + /// @return SegmentTemplate xmlNode if live information is present. + xml::scoped_xml_ptr 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 diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc index 3c4f781fd3..a07bf10215 100644 --- a/packager/mpd/base/xml/xml_node.cc +++ b/packager/mpd/base/xml/xml_node.cc @@ -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 RepresentationXmlNode::GetLiveOnlyInfo( + const MediaInfo& media_info, + const std::list& 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& segment_infos, @@ -483,15 +558,15 @@ 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, - audio_channel_config_value); + 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 // D.2.2. if (codec_data.ec3_joc_complexity() != 0) { std::string ec3_joc_complexity = - base::UintToString(codec_data.ec3_joc_complexity()); + base::UintToString(codec_data.ec3_joc_complexity()); ret &= AddDescriptor("SupplementalProperty", "tag:dolby.com,2018:dash:EC3_ExtensionType:2018", "JOC"); diff --git a/packager/mpd/base/xml/xml_node.h b/packager/mpd/base/xml/xml_node.h index 976730d4d3..2bfd3ee71d 100644 --- a/packager/mpd/base/xml/xml_node.h +++ b/packager/mpd/base/xml/xml_node.h @@ -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 < 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& 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 GetLiveOnlyInfo( + const MediaInfo& media_info, + const std::list& segment_infos, + uint32_t start_number); + private: // Add AudioChannelConfiguration element. Note that it is a required element // for audio Representations. diff --git a/packager/packager.cc b/packager/packager.cc index b46d6e7f57..a7e15fad68 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -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,8 +326,10 @@ Status ValidateParams(const PackagingParams& packaging_params, outputs.insert(descriptor.output); } if (!descriptor.segment_template.empty()) { - if (segment_templates.find(descriptor.segment_template) != - segment_templates.end()) { + 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 '" + descriptor.segment_template + diff --git a/packager/packager.h b/packager/packager.h index 19d041ebd9..876aef5638 100644 --- a/packager/packager.h +++ b/packager/packager.h @@ -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,9 +146,8 @@ 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, - const std::vector& stream_descriptors); + Status Initialize(const PackagingParams& packaging_params, + const std::vector& stream_descriptors); /// Run the pipeline to completion (or failed / been cancelled). Note /// that it blocks until completion.