[DASH] Url construction that uses $RepresentationID$.
This commit is contained in:
parent
5b9fd409a5
commit
83cb6a225e
|
@ -35,6 +35,7 @@ std::shared_ptr<Muxer> MuxerFactory::CreateMuxer(
|
||||||
options.output_file_name = stream.output;
|
options.output_file_name = stream.output;
|
||||||
options.segment_template = stream.segment_template;
|
options.segment_template = stream.segment_template;
|
||||||
options.bandwidth = stream.bandwidth;
|
options.bandwidth = stream.bandwidth;
|
||||||
|
options.rep_id = stream.rep_id;
|
||||||
|
|
||||||
std::shared_ptr<Muxer> muxer;
|
std::shared_ptr<Muxer> muxer;
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ enum FieldType {
|
||||||
kDashRolesField,
|
kDashRolesField,
|
||||||
kDashOnlyField,
|
kDashOnlyField,
|
||||||
kHlsOnlyField,
|
kHlsOnlyField,
|
||||||
|
kRepIdField,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FieldNameToTypeMapping {
|
struct FieldNameToTypeMapping {
|
||||||
|
@ -81,6 +82,7 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
||||||
{"role", kDashRolesField},
|
{"role", kDashRolesField},
|
||||||
{"dash_only", kDashOnlyField},
|
{"dash_only", kDashOnlyField},
|
||||||
{"hls_only", kHlsOnlyField},
|
{"hls_only", kHlsOnlyField},
|
||||||
|
{"rep_id", kRepIdField},
|
||||||
};
|
};
|
||||||
|
|
||||||
FieldType GetFieldType(const std::string& field_name) {
|
FieldType GetFieldType(const std::string& field_name) {
|
||||||
|
@ -236,6 +238,9 @@ base::Optional<StreamDescriptor> ParseStreamDescriptor(
|
||||||
}
|
}
|
||||||
descriptor.hls_only = hls_only_value > 0;
|
descriptor.hls_only = hls_only_value > 0;
|
||||||
break;
|
break;
|
||||||
|
case kRepIdField:
|
||||||
|
descriptor.rep_id = iter->second;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG(ERROR) << "Unknown field in stream descriptor (\"" << iter->first
|
LOG(ERROR) << "Unknown field in stream descriptor (\"" << iter->first
|
||||||
<< "\").";
|
<< "\").";
|
||||||
|
|
|
@ -114,7 +114,7 @@ Status Muxer::ReinitializeMuxer(int64_t timestamp) {
|
||||||
// the subclasses.
|
// the subclasses.
|
||||||
options_.output_file_name =
|
options_.output_file_name =
|
||||||
GetSegmentName(output_file_template_, timestamp, output_file_index_++,
|
GetSegmentName(output_file_template_, timestamp, output_file_index_++,
|
||||||
options_.bandwidth);
|
options_.bandwidth, options_.rep_id);
|
||||||
}
|
}
|
||||||
return InitializeMuxer();
|
return InitializeMuxer();
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ struct MuxerOptions {
|
||||||
/// User-specified bit rate for the media stream. If zero, the muxer will
|
/// User-specified bit rate for the media stream. If zero, the muxer will
|
||||||
/// attempt to estimate.
|
/// attempt to estimate.
|
||||||
uint32_t bandwidth = 0;
|
uint32_t bandwidth = 0;
|
||||||
|
|
||||||
|
/// Used as Representation id for template based url construction using
|
||||||
|
/// $RepresentationID$.
|
||||||
|
std::string rep_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -60,6 +60,8 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
|
||||||
|
|
||||||
bool has_number = false;
|
bool has_number = false;
|
||||||
bool has_time = false;
|
bool has_time = false;
|
||||||
|
bool has_representation = false;
|
||||||
|
|
||||||
// Every second substring in split output should be an identifier.
|
// Every second substring in split output should be an identifier.
|
||||||
for (size_t i = 1; i < splits.size(); i += 2) {
|
for (size_t i = 1; i < splits.size(); i += 2) {
|
||||||
// Each identifier may be suffixed, within the enclosing ‘$’ characters,
|
// Each identifier may be suffixed, within the enclosing ‘$’ characters,
|
||||||
|
@ -76,9 +78,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
|
||||||
|
|
||||||
// TODO(kqyang): Support "RepresentationID".
|
// TODO(kqyang): Support "RepresentationID".
|
||||||
if (identifier == "RepresentationID") {
|
if (identifier == "RepresentationID") {
|
||||||
return Status(
|
has_representation = true;
|
||||||
error::UNIMPLEMENTED,
|
|
||||||
"Segment template flag $RepresentationID$ is not supported yet.");
|
|
||||||
} else if (identifier == "Number") {
|
} else if (identifier == "Number") {
|
||||||
has_number = true;
|
has_number = true;
|
||||||
} else if (identifier == "Time") {
|
} else if (identifier == "Time") {
|
||||||
|
@ -98,7 +98,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
|
||||||
error::INVALID_ARGUMENT,
|
error::INVALID_ARGUMENT,
|
||||||
"In segment templates $Number$ and $Time$ should not co-exist.");
|
"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,
|
return Status(error::INVALID_ARGUMENT,
|
||||||
"In segment templates $Number$ or $Time$ should exist.");
|
"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,
|
std::string GetSegmentName(const std::string& segment_template,
|
||||||
uint64_t segment_start_time,
|
uint64_t segment_start_time,
|
||||||
uint32_t segment_index,
|
uint32_t segment_index,
|
||||||
uint32_t bandwidth) {
|
uint32_t bandwidth,
|
||||||
|
std::string rep_id) {
|
||||||
DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template));
|
DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template));
|
||||||
|
|
||||||
std::vector<std::string> splits = base::SplitString(
|
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('%');
|
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");
|
identifier == "Bandwidth" || identifier == "RepresentationID");
|
||||||
|
|
||||||
std::string format_tag;
|
std::string format_tag;
|
||||||
if (format_pos != std::string::npos) {
|
if (format_pos != std::string::npos) {
|
||||||
|
@ -158,6 +159,8 @@ std::string GetSegmentName(const std::string& segment_template,
|
||||||
} else if (identifier == "Bandwidth") {
|
} else if (identifier == "Bandwidth") {
|
||||||
segment_name += base::StringPrintf(format_tag.c_str(),
|
segment_name += base::StringPrintf(format_tag.c_str(),
|
||||||
static_cast<uint64_t>(bandwidth));
|
static_cast<uint64_t>(bandwidth));
|
||||||
|
} else if (identifier == "RepresentationID") {
|
||||||
|
segment_name += rep_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return segment_name;
|
return segment_name;
|
||||||
|
|
|
@ -35,7 +35,8 @@ Status ValidateSegmentTemplate(const std::string& segment_template);
|
||||||
std::string GetSegmentName(const std::string& segment_template,
|
std::string GetSegmentName(const std::string& segment_template,
|
||||||
uint64_t segment_start_time,
|
uint64_t segment_start_time,
|
||||||
uint32_t segment_index,
|
uint32_t segment_index,
|
||||||
uint32_t bandwidth);
|
uint32_t bandwidth,
|
||||||
|
std::string rep_id = "");
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -39,7 +39,7 @@ TEST(MuxerUtilTest, ValidateSegmentTemplate) {
|
||||||
EXPECT_NE(Status::OK, ValidateSegmentTemplate("foo$Number$_$Time$loo"));
|
EXPECT_NE(Status::OK, ValidateSegmentTemplate("foo$Number$_$Time$loo"));
|
||||||
|
|
||||||
// $RepresentationID$ not implemented yet.
|
// $RepresentationID$ not implemented yet.
|
||||||
EXPECT_NE(Status::OK, ValidateSegmentTemplate("$RepresentationID$__$Time$"));
|
EXPECT_EQ(Status::OK, ValidateSegmentTemplate("$RepresentationID$__$Time$"));
|
||||||
|
|
||||||
// Unknown identifier.
|
// Unknown identifier.
|
||||||
EXPECT_NE(Status::OK, ValidateSegmentTemplate("$foo$$Time$"));
|
EXPECT_NE(Status::OK, ValidateSegmentTemplate("$foo$$Time$"));
|
||||||
|
|
|
@ -200,6 +200,9 @@ void SetMediaInfoMuxerOptions(const MuxerOptions& muxer_options,
|
||||||
if (!muxer_options.output_file_name.empty())
|
if (!muxer_options.output_file_name.empty())
|
||||||
media_info->set_init_segment_name(muxer_options.output_file_name);
|
media_info->set_init_segment_name(muxer_options.output_file_name);
|
||||||
media_info->set_segment_template(muxer_options.segment_template);
|
media_info->set_segment_template(muxer_options.segment_template);
|
||||||
|
if (!muxer_options.rep_id.empty()) {
|
||||||
|
media_info->set_rep_id(muxer_options.rep_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,9 +176,9 @@ Status TsSegmenter::FinalizeSegment(uint64_t start_timestamp,
|
||||||
// be false.
|
// be false.
|
||||||
if (!segment_started_)
|
if (!segment_started_)
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
std::string segment_path =
|
std::string segment_path = GetSegmentName(
|
||||||
GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_,
|
muxer_options_.segment_template, segment_start_timestamp_,
|
||||||
segment_number_++, muxer_options_.bandwidth);
|
segment_number_++, muxer_options_.bandwidth, muxer_options_.rep_id);
|
||||||
|
|
||||||
const int64_t file_size = segment_buffer_.Size();
|
const int64_t file_size = segment_buffer_.Size();
|
||||||
std::unique_ptr<File, FileCloser> segment_file;
|
std::unique_ptr<File, FileCloser> segment_file;
|
||||||
|
|
|
@ -110,9 +110,9 @@ Status MultiSegmentSegmenter::WriteSegment() {
|
||||||
options().output_file_name);
|
options().output_file_name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
file_name = GetSegmentName(options().segment_template,
|
file_name = GetSegmentName(
|
||||||
sidx()->earliest_presentation_time,
|
options().segment_template, sidx()->earliest_presentation_time,
|
||||||
num_segments_++, options().bandwidth);
|
num_segments_++, options().bandwidth, options().rep_id);
|
||||||
file.reset(File::Open(file_name.c_str(), "w"));
|
file.reset(File::Open(file_name.c_str(), "w"));
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return Status(error::FILE_FAILURE,
|
return Status(error::FILE_FAILURE,
|
||||||
|
|
|
@ -79,7 +79,8 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id,
|
||||||
options().segment_template.empty()
|
options().segment_template.empty()
|
||||||
? options().output_file_name
|
? options().output_file_name
|
||||||
: GetSegmentName(options().segment_template, segment_timestamp,
|
: 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.
|
// Save |segment_size| as it will be cleared after writing.
|
||||||
const size_t segment_size = segmenter_->segment_buffer()->Size();
|
const size_t segment_size = segmenter_->segment_buffer()->Size();
|
||||||
|
|
|
@ -34,7 +34,7 @@ Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp,
|
||||||
if (!is_subsegment) {
|
if (!is_subsegment) {
|
||||||
std::string segment_name =
|
std::string segment_name =
|
||||||
GetSegmentName(options().segment_template, start_timestamp,
|
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
|
// Close the file, which also does flushing, to make sure the file is
|
||||||
// written before manifest is updated.
|
// written before manifest is updated.
|
||||||
|
@ -91,7 +91,7 @@ Status MultiSegmentSegmenter::NewSegment(uint64_t start_timestamp,
|
||||||
temp_file_name_ =
|
temp_file_name_ =
|
||||||
"memory://" + GetSegmentName(options().segment_template,
|
"memory://" + GetSegmentName(options().segment_template,
|
||||||
start_timestamp, num_segment_,
|
start_timestamp, num_segment_,
|
||||||
options().bandwidth);
|
options().bandwidth, options().rep_id);
|
||||||
|
|
||||||
writer_.reset(new MkvWriter);
|
writer_.reset(new MkvWriter);
|
||||||
Status status = writer_->Open(temp_file_name_);
|
Status status = writer_->Open(temp_file_name_);
|
||||||
|
|
|
@ -90,8 +90,8 @@ Status WebVttTextOutputHandler::OnSegmentInfo(const SegmentInfo& info) {
|
||||||
const uint64_t duration = info.duration;
|
const uint64_t duration = info.duration;
|
||||||
const uint32_t bandwidth = muxer_options_.bandwidth;
|
const uint32_t bandwidth = muxer_options_.bandwidth;
|
||||||
|
|
||||||
const std::string filename =
|
const std::string filename = GetSegmentName(segment_template, start, index,
|
||||||
GetSegmentName(segment_template, start, index, bandwidth);
|
bandwidth, muxer_options_.rep_id);
|
||||||
|
|
||||||
// Write everything to the file before telling the manifest so that the
|
// Write everything to the file before telling the manifest so that the
|
||||||
// file will exist on disk.
|
// file will exist on disk.
|
||||||
|
|
|
@ -336,6 +336,18 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
||||||
representation->SuppressOnce(Representation::kSuppressHeight);
|
representation->SuppressOnce(Representation::kSuppressHeight);
|
||||||
if (suppress_representation_frame_rate)
|
if (suppress_representation_frame_rate)
|
||||||
representation->SuppressOnce(Representation::kSuppressFrameRate);
|
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());
|
xml::scoped_xml_ptr<xmlNode> child(representation->GetXml());
|
||||||
if (!child || !adaptation_set.AddChild(std::move(child)))
|
if (!child || !adaptation_set.AddChild(std::move(child)))
|
||||||
return xml::scoped_xml_ptr<xmlNode>();
|
return xml::scoped_xml_ptr<xmlNode>();
|
||||||
|
@ -409,7 +421,9 @@ void AdaptationSet::UpdateFromMediaInfo(const MediaInfo& media_info) {
|
||||||
|
|
||||||
AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
|
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()) {
|
if (media_info.has_video_info()) {
|
||||||
content_type_ = "video";
|
content_type_ = "video";
|
||||||
} else if (media_info.has_audio_info()) {
|
} else if (media_info.has_audio_info()) {
|
||||||
|
|
|
@ -317,6 +317,10 @@ class AdaptationSet {
|
||||||
// and HD videos in different AdaptationSets can share the same trick play
|
// and HD videos in different AdaptationSets can share the same trick play
|
||||||
// stream.
|
// stream.
|
||||||
std::vector<const AdaptationSet*> trick_play_references_;
|
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
|
} // namespace shaka
|
||||||
|
|
|
@ -184,6 +184,7 @@ message MediaInfo {
|
||||||
// This value is not necessarily the same as the value passed to
|
// This value is not necessarily the same as the value passed to
|
||||||
// MpdNotifier::NotifyNewSegment().
|
// MpdNotifier::NotifyNewSegment().
|
||||||
optional float segment_duration_seconds = 12 [deprecated = true];
|
optional float segment_duration_seconds = 12 [deprecated = true];
|
||||||
|
optional string rep_id = 23;
|
||||||
// END LIVE only.
|
// END LIVE only.
|
||||||
|
|
||||||
// URL fields for the corresponding file_name fields above.
|
// URL fields for the corresponding file_name fields above.
|
||||||
|
|
|
@ -441,11 +441,23 @@ void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
|
||||||
MakePathRelative(media_info->init_segment_name(), mpd_dir));
|
MakePathRelative(media_info->init_segment_name(), mpd_dir));
|
||||||
}
|
}
|
||||||
if (media_info->has_segment_template()) {
|
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(
|
media_info->set_segment_template_url(
|
||||||
MakePathRelative(media_info->segment_template(), mpd_dir));
|
MakePathRelative(media_info->segment_template(), mpd_dir));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -95,7 +95,9 @@ Representation::Representation(
|
||||||
// TODO(kqyang): Need a better check. $Time is legitimate but not a
|
// TODO(kqyang): Need a better check. $Time is legitimate but not a
|
||||||
// template.
|
// template.
|
||||||
media_info.segment_template().find("$Time") == std::string::npos &&
|
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(
|
Representation::Representation(
|
||||||
const Representation& representation,
|
const Representation& representation,
|
||||||
|
@ -212,6 +214,16 @@ const MediaInfo& Representation::GetMediaInfo() const {
|
||||||
return media_info_;
|
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
|
// Uses info in |media_info_| and |content_protection_elements_| to create a
|
||||||
// "Representation" node.
|
// "Representation" node.
|
||||||
// MPD schema has strict ordering. The following must be done in order.
|
// 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;
|
xml::RepresentationXmlNode representation;
|
||||||
// Mandatory fields for Representation.
|
// Mandatory fields for Representation.
|
||||||
|
|
||||||
|
if (rep_id_set) {
|
||||||
|
representation.SetIdString(media_info_.rep_id());
|
||||||
|
} else {
|
||||||
representation.SetId(id_);
|
representation.SetId(id_);
|
||||||
|
}
|
||||||
representation.SetIntegerAttribute("bandwidth", bandwidth);
|
representation.SetIntegerAttribute("bandwidth", bandwidth);
|
||||||
if (!codecs_.empty())
|
if (!codecs_.empty())
|
||||||
representation.SetStringAttribute("codecs", codecs_);
|
representation.SetStringAttribute("codecs", codecs_);
|
||||||
|
@ -269,6 +286,7 @@ xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasLiveOnlyFields(media_info_) &&
|
if (HasLiveOnlyFields(media_info_) &&
|
||||||
|
!(output_suppression_flags_ & kSuppressSegmentTemplate) &&
|
||||||
!representation.AddLiveOnlyInfo(media_info_, segment_infos_,
|
!representation.AddLiveOnlyInfo(media_info_, segment_infos_,
|
||||||
start_number_)) {
|
start_number_)) {
|
||||||
LOG(ERROR) << "Failed to add Live info.";
|
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)
|
if (mpd_options_.mpd_params.preserved_segments_outside_live_window == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
segments_to_be_removed_.push_back(
|
segments_to_be_removed_.push_back(media::GetSegmentName(
|
||||||
media::GetSegmentName(media_info_.segment_template(), segment_start_time,
|
media_info_.segment_template(), segment_start_time, start_number_ - 1,
|
||||||
start_number_ - 1, media_info_.bandwidth()));
|
media_info_.bandwidth(), media_info_.rep_id()));
|
||||||
while (segments_to_be_removed_.size() >
|
while (segments_to_be_removed_.size() >
|
||||||
mpd_options_.mpd_params.preserved_segments_outside_live_window) {
|
mpd_options_.mpd_params.preserved_segments_outside_live_window) {
|
||||||
VLOG(2) << "Deleting " << segments_to_be_removed_.front();
|
VLOG(2) << "Deleting " << segments_to_be_removed_.front();
|
||||||
|
|
|
@ -57,6 +57,7 @@ class Representation {
|
||||||
kSuppressWidth = 1,
|
kSuppressWidth = 1,
|
||||||
kSuppressHeight = 2,
|
kSuppressHeight = 2,
|
||||||
kSuppressFrameRate = 4,
|
kSuppressFrameRate = 4,
|
||||||
|
kSuppressSegmentTemplate = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~Representation();
|
virtual ~Representation();
|
||||||
|
@ -118,6 +119,9 @@ class Representation {
|
||||||
/// @return Copy of <Representation>.
|
/// @return Copy of <Representation>.
|
||||||
xml::scoped_xml_ptr<xmlNode> GetXml();
|
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
|
/// By calling this methods, the next time GetXml() is
|
||||||
/// called, the corresponding attributes will not be set.
|
/// called, the corresponding attributes will not be set.
|
||||||
/// For example, if SuppressOnce(kSuppressWidth) is called, then GetXml() will
|
/// 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
|
// Segments with duration difference less than one frame duration are
|
||||||
// considered to have the same duration.
|
// considered to have the same duration.
|
||||||
uint32_t frame_duration_ = 0;
|
uint32_t frame_duration_ = 0;
|
||||||
|
|
||||||
|
// When set to true, adds $RepresentationID$ in SegmentTemplate.
|
||||||
|
bool rep_id_set = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -197,6 +197,10 @@ void XmlNode::SetId(uint32_t id) {
|
||||||
SetIntegerAttribute("id", id);
|
SetIntegerAttribute("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void XmlNode::SetIdString(std::string id) {
|
||||||
|
SetStringAttribute("id", id);
|
||||||
|
}
|
||||||
|
|
||||||
void XmlNode::SetContent(const std::string& content) {
|
void XmlNode::SetContent(const std::string& content) {
|
||||||
DCHECK(node_);
|
DCHECK(node_);
|
||||||
xmlNodeSetContent(node_.get(), BAD_CAST content.c_str());
|
xmlNodeSetContent(node_.get(), BAD_CAST content.c_str());
|
||||||
|
@ -404,6 +408,77 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
|
||||||
return true;
|
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(
|
bool RepresentationXmlNode::AddLiveOnlyInfo(
|
||||||
const MediaInfo& media_info,
|
const MediaInfo& media_info,
|
||||||
const std::list<SegmentInfo>& segment_infos,
|
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_value = base::UintToString(ec3_channel_mpeg_value);
|
||||||
audio_channel_config_scheme = "urn:mpeg:mpegB:cicp:ChannelConfiguration";
|
audio_channel_config_scheme = "urn:mpeg:mpegB:cicp:ChannelConfiguration";
|
||||||
}
|
}
|
||||||
bool ret = AddDescriptor("AudioChannelConfiguration",
|
bool ret =
|
||||||
audio_channel_config_scheme,
|
AddDescriptor("AudioChannelConfiguration", audio_channel_config_scheme,
|
||||||
audio_channel_config_value);
|
audio_channel_config_value);
|
||||||
// Dolby Digital Plus JOC descriptor. Spec: ETSI TS 103 420 v1.2.1
|
// Dolby Digital Plus JOC descriptor. Spec: ETSI TS 103 420 v1.2.1
|
||||||
// Backwards-compatible object audio carriage using Enhanced AC-3 Standard
|
// Backwards-compatible object audio carriage using Enhanced AC-3 Standard
|
||||||
|
|
|
@ -66,6 +66,10 @@ class XmlNode {
|
||||||
/// @param id is the ID for this element.
|
/// @param id is the ID for this element.
|
||||||
void SetId(uint32_t id);
|
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.
|
/// Set the contents of an XML element using a string.
|
||||||
/// This cannot set child elements because <> will become < and &rt;
|
/// This cannot set child elements because <> will become < and &rt;
|
||||||
/// This should be used to set the text for the element, e.g. setting
|
/// 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,
|
const std::list<SegmentInfo>& segment_infos,
|
||||||
uint32_t start_number);
|
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:
|
private:
|
||||||
// Add AudioChannelConfiguration element. Note that it is a required element
|
// Add AudioChannelConfiguration element. Note that it is a required element
|
||||||
// for audio Representations.
|
// for audio Representations.
|
||||||
|
|
|
@ -316,7 +316,8 @@ Status ValidateParams(const PackagingParams& packaging_params,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!descriptor.output.empty()) {
|
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(
|
return Status(
|
||||||
error::INVALID_ARGUMENT,
|
error::INVALID_ARGUMENT,
|
||||||
"Seeing duplicated outputs '" + descriptor.output +
|
"Seeing duplicated outputs '" + descriptor.output +
|
||||||
|
@ -325,7 +326,9 @@ Status ValidateParams(const PackagingParams& packaging_params,
|
||||||
outputs.insert(descriptor.output);
|
outputs.insert(descriptor.output);
|
||||||
}
|
}
|
||||||
if (!descriptor.segment_template.empty()) {
|
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()) {
|
segment_templates.end()) {
|
||||||
return Status(error::INVALID_ARGUMENT,
|
return Status(error::INVALID_ARGUMENT,
|
||||||
"Seeing duplicated segment templates '" +
|
"Seeing duplicated segment templates '" +
|
||||||
|
|
|
@ -133,6 +133,8 @@ struct StreamDescriptor {
|
||||||
bool dash_only = false;
|
bool dash_only = false;
|
||||||
/// Set to true to indicate that the stream is for hls only.
|
/// Set to true to indicate that the stream is for hls only.
|
||||||
bool hls_only = false;
|
bool hls_only = false;
|
||||||
|
|
||||||
|
std::string rep_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SHAKA_EXPORT Packager {
|
class SHAKA_EXPORT Packager {
|
||||||
|
@ -144,8 +146,7 @@ class SHAKA_EXPORT Packager {
|
||||||
/// @param packaging_params contains the packaging parameters.
|
/// @param packaging_params contains the packaging parameters.
|
||||||
/// @param stream_descriptors a list of stream descriptors.
|
/// @param stream_descriptors a list of stream descriptors.
|
||||||
/// @return OK on success, an appropriate error code on failure.
|
/// @return OK on success, an appropriate error code on failure.
|
||||||
Status Initialize(
|
Status Initialize(const PackagingParams& packaging_params,
|
||||||
const PackagingParams& packaging_params,
|
|
||||||
const std::vector<StreamDescriptor>& stream_descriptors);
|
const std::vector<StreamDescriptor>& stream_descriptors);
|
||||||
|
|
||||||
/// Run the pipeline to completion (or failed / been cancelled). Note
|
/// Run the pipeline to completion (or failed / been cancelled). Note
|
||||||
|
|
Loading…
Reference in New Issue