7#include <packager/mpd/base/representation.h>
18#include <absl/log/check.h>
19#include <absl/log/log.h>
20#include <absl/strings/str_format.h>
22#include <packager/file.h>
23#include <packager/media/base/muxer_util.h>
24#include <packager/mpd/base/media_info.pb.h>
25#include <packager/mpd/base/mpd_options.h>
26#include <packager/mpd/base/mpd_utils.h>
27#include <packager/mpd/base/xml/xml_node.h>
32std::string GetMimeType(
const std::string& prefix,
33 MediaInfo::ContainerType container_type) {
34 switch (container_type) {
35 case MediaInfo::CONTAINER_MP4:
36 return prefix +
"/mp4";
37 case MediaInfo::CONTAINER_MPEG2_TS:
39 return prefix +
"/MP2T";
40 case MediaInfo::CONTAINER_WEBM:
41 return prefix +
"/webm";
47 LOG(ERROR) <<
"Unrecognized container type: " << container_type;
54bool HasRequiredVideoFields(
const MediaInfo_VideoInfo& video_info) {
55 if (!video_info.has_height() || !video_info.has_width()) {
57 <<
"Width and height are required fields for generating a valid MPD.";
62 LOG_IF(WARNING, !video_info.has_time_scale())
63 <<
"Video info does not contain timescale required for "
64 "calculating framerate. @frameRate is required for DASH IOP.";
65 LOG_IF(WARNING, !video_info.has_pixel_width())
66 <<
"Video info does not contain pixel_width to calculate the sample "
67 "aspect ratio required for DASH IOP.";
68 LOG_IF(WARNING, !video_info.has_pixel_height())
69 <<
"Video info does not contain pixel_height to calculate the sample "
70 "aspect ratio required for DASH IOP.";
74int32_t GetTimeScale(
const MediaInfo& media_info) {
75 if (media_info.has_reference_time_scale()) {
76 return media_info.reference_time_scale();
79 if (media_info.has_video_info()) {
80 return media_info.video_info().time_scale();
83 if (media_info.has_audio_info()) {
84 return media_info.audio_info().time_scale();
87 LOG(WARNING) <<
"No timescale specified, using 1 as timescale.";
94 const MediaInfo& media_info,
97 std::unique_ptr<RepresentationStateChangeListener> state_change_listener)
98 : media_info_(media_info),
100 mpd_options_(mpd_options),
101 state_change_listener_(std::move(state_change_listener)),
102 allow_approximate_segment_timeline_(
105 media_info.segment_template().find(
"$Time") == std::string::npos &&
106 mpd_options_.mpd_params.allow_approximate_segment_timeline) {}
110 std::unique_ptr<RepresentationStateChangeListener> state_change_listener)
112 representation.mpd_options_,
114 std::move(state_change_listener)) {
115 mime_type_ = representation.mime_type_;
116 codecs_ = representation.codecs_;
119Representation::~Representation() {}
122 if (!AtLeastOneTrue(media_info_.has_video_info(),
123 media_info_.has_audio_info(),
124 media_info_.has_text_info())) {
128 LOG(ERROR) <<
"Representation needs one of video, audio, or text.";
132 if (MoreThanOneTrue(media_info_.has_video_info(),
133 media_info_.has_audio_info(),
134 media_info_.has_text_info())) {
135 LOG(ERROR) <<
"Only one of VideoInfo, AudioInfo, or TextInfo can be set.";
139 if (media_info_.container_type() == MediaInfo::CONTAINER_UNKNOWN) {
140 LOG(ERROR) <<
"'container_type' in MediaInfo cannot be CONTAINER_UNKNOWN.";
144 if (media_info_.has_video_info()) {
145 mime_type_ = GetVideoMimeType();
146 if (!HasRequiredVideoFields(media_info_.video_info())) {
147 LOG(ERROR) <<
"Missing required fields to create a video Representation.";
150 }
else if (media_info_.has_audio_info()) {
151 mime_type_ = GetAudioMimeType();
152 }
else if (media_info_.has_text_info()) {
153 mime_type_ = GetTextMimeType();
156 if (mime_type_.empty())
159 codecs_ = GetCodecs(media_info_);
160 supplemental_codecs_ = GetSupplementalCodecs(media_info_);
161 supplemental_profiles_ = GetSupplementalProfiles(media_info_);
167 content_protection_elements_.push_back(content_protection_element);
168 RemoveDuplicateAttributes(&content_protection_elements_.back());
172 const std::string& pssh) {
173 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
174 &content_protection_elements_);
180 int64_t segment_number) {
181 if (start_time == 0 && duration == 0) {
182 LOG(WARNING) <<
"Got segment with start_time and duration == 0. Ignoring.";
193 if (state_change_listener_)
194 state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
196 AddSegmentInfo(start_time, duration, segment_number);
203 if (!mpd_options_.mpd_params.low_latency_dash_mode) {
204 current_buffer_depth_ += segment_infos_.back().duration;
206 bandwidth_estimator_.
AddBlock(size,
static_cast<double>(duration) /
207 media_info_.reference_time_scale());
212 if (!mpd_options_.mpd_params.low_latency_dash_mode) {
214 <<
"UpdateCompletedSegment is only applicable to low latency mode.";
218 UpdateSegmentInfo(duration);
220 current_buffer_depth_ += segment_infos_.back().duration;
223 size,
static_cast<double>(duration) / media_info_.reference_time_scale());
229 if (media_info_.has_audio_info() || media_info_.has_video_info())
230 frame_duration_ = frame_duration;
232 if (media_info_.has_video_info()) {
233 media_info_.mutable_video_info()->set_frame_duration(frame_duration);
234 if (state_change_listener_) {
235 state_change_listener_->OnSetFrameRateForRepresentation(
236 frame_duration, media_info_.video_info().time_scale());
242 int64_t sd = mpd_options_.mpd_params.target_segment_duration *
243 media_info_.reference_time_scale();
246 media_info_.set_segment_duration(sd);
260 if (!HasRequiredMediaInfoFields()) {
261 LOG(ERROR) <<
"MediaInfo missing required fields.";
265 const uint64_t bandwidth = media_info_.has_bandwidth()
266 ? media_info_.bandwidth()
267 : bandwidth_estimator_.
Max();
269 DCHECK(!(HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)));
273 if (!representation.
SetId(id_) ||
281 if (!supplemental_codecs_.empty() && !supplemental_profiles_.empty()) {
283 supplemental_codecs_) ||
285 supplemental_profiles_)) {
286 LOG(ERROR) <<
"Failed to add supplemental codecs/profiles to "
287 "Representation XML.";
291 const bool has_video_info = media_info_.has_video_info();
292 const bool has_audio_info = media_info_.has_audio_info();
294 if (has_video_info &&
296 media_info_.video_info(),
297 !(output_suppression_flags_ & kSuppressWidth),
298 !(output_suppression_flags_ & kSuppressHeight),
299 !(output_suppression_flags_ & kSuppressFrameRate))) {
300 LOG(ERROR) <<
"Failed to add video info to Representation XML.";
304 if (has_audio_info &&
305 !representation.
AddAudioInfo(media_info_.audio_info())) {
306 LOG(ERROR) <<
"Failed to add audio info to Representation XML.";
310 if (!representation.AddContentProtectionElements(
311 content_protection_elements_)) {
315 if (HasVODOnlyFields(media_info_) &&
317 media_info_, mpd_options_.mpd_params.use_segment_list,
318 mpd_options_.mpd_params.target_segment_duration)) {
319 LOG(ERROR) <<
"Failed to add VOD info.";
323 if (HasLiveOnlyFields(media_info_) &&
325 media_info_, segment_infos_,
326 mpd_options_.mpd_params.low_latency_dash_mode)) {
327 LOG(ERROR) <<
"Failed to add Live info.";
333 output_suppression_flags_ = 0;
334 return representation;
338 output_suppression_flags_ |= flag;
342 double presentation_time_offset) {
343 int64_t pto = presentation_time_offset * media_info_.reference_time_scale();
346 media_info_.set_presentation_time_offset(pto);
352 const double frame_duration_sec =
353 (double)frame_duration_ / (
double)media_info_.reference_time_scale();
360 mpd_options_.mpd_params.target_segment_duration - frame_duration_sec;
363 media_info_.set_availability_time_offset(ato);
367 double* start_timestamp_seconds,
368 double* end_timestamp_seconds)
const {
369 if (segment_infos_.empty())
372 if (start_timestamp_seconds) {
373 *start_timestamp_seconds =
374 static_cast<double>(segment_infos_.begin()->start_time) /
375 GetTimeScale(media_info_);
377 if (end_timestamp_seconds) {
378 *end_timestamp_seconds =
379 static_cast<double>(segment_infos_.rbegin()->start_time +
380 segment_infos_.rbegin()->duration *
381 (segment_infos_.rbegin()->repeat + 1)) /
382 GetTimeScale(media_info_);
387bool Representation::HasRequiredMediaInfoFields()
const {
388 if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
389 LOG(ERROR) <<
"MediaInfo cannot have both VOD and Live fields.";
393 if (!media_info_.has_container_type()) {
394 LOG(ERROR) <<
"MediaInfo missing required field: container_type.";
401void Representation::AddSegmentInfo(int64_t start_time,
403 int64_t segment_number) {
404 const uint64_t kNoRepeat = 0;
405 const int64_t adjusted_duration = AdjustDuration(duration);
407 if (!segment_infos_.empty()) {
409 const SegmentInfo& previous = segment_infos_.back();
410 const int64_t previous_segment_end_time =
411 previous.start_time + previous.duration * (previous.repeat + 1);
414 if (ApproximiatelyEqual(previous_segment_end_time, start_time)) {
415 const int64_t segment_end_time_for_same_duration =
416 previous_segment_end_time + previous.duration;
417 const int64_t actual_segment_end_time = start_time + duration;
420 if (ApproximiatelyEqual(segment_end_time_for_same_duration,
421 actual_segment_end_time)) {
422 ++segment_infos_.back().repeat;
424 segment_infos_.push_back(
425 {previous_segment_end_time,
426 actual_segment_end_time - previous_segment_end_time, kNoRepeat,
433 const int64_t kRoundingErrorGrace = 5;
434 if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
435 LOG(WARNING) << RepresentationAsString() <<
" Found a gap of size "
436 << (start_time - previous_segment_end_time)
437 <<
" > kRoundingErrorGrace (" << kRoundingErrorGrace
438 <<
"). The new segment starts at " << start_time
439 <<
" but the previous segment ends at "
440 << previous_segment_end_time <<
".";
444 if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
446 << RepresentationAsString()
447 <<
" Segments should not be overlapping. The new segment starts at "
448 << start_time <<
" but the previous segment ends at "
449 << previous_segment_end_time <<
".";
452 segment_infos_.push_back(
453 {start_time, adjusted_duration, kNoRepeat, segment_number});
456void Representation::UpdateSegmentInfo(int64_t duration) {
457 if (!segment_infos_.empty()) {
459 segment_infos_.back().duration = duration;
463bool Representation::ApproximiatelyEqual(int64_t time1, int64_t time2)
const {
464 if (!allow_approximate_segment_timeline_)
465 return time1 == time2;
475 const double kErrorThresholdSeconds = 0.05;
478 const int32_t error_threshold =
479 std::min(frame_duration_,
480 static_cast<int32_t
>(kErrorThresholdSeconds *
481 media_info_.reference_time_scale()));
482 return std::abs(time1 - time2) <= error_threshold;
485int64_t Representation::AdjustDuration(int64_t duration)
const {
486 if (!allow_approximate_segment_timeline_)
488 const int64_t scaled_target_duration =
489 mpd_options_.mpd_params.target_segment_duration *
490 media_info_.reference_time_scale();
491 return ApproximiatelyEqual(scaled_target_duration, duration)
492 ? scaled_target_duration
496void Representation::SlideWindow() {
497 if (mpd_options_.mpd_params.time_shift_buffer_depth <= 0.0 ||
498 mpd_options_.mpd_type == MpdType::kStatic)
501 const int32_t time_scale = GetTimeScale(media_info_);
502 DCHECK_GT(time_scale, 0);
504 const int64_t time_shift_buffer_depth =
static_cast<int64_t
>(
505 mpd_options_.mpd_params.time_shift_buffer_depth * time_scale);
507 if (current_buffer_depth_ <= time_shift_buffer_depth)
510 std::list<SegmentInfo>::iterator first = segment_infos_.begin();
511 std::list<SegmentInfo>::iterator last = first;
512 for (; last != segment_infos_.end(); ++last) {
515 while (last->repeat >= 0 &&
516 current_buffer_depth_ - last->duration >= time_shift_buffer_depth) {
517 current_buffer_depth_ -= last->duration;
518 RemoveOldSegment(&*last);
520 if (last->repeat >= 0)
523 segment_infos_.erase(first, last);
526void Representation::RemoveOldSegment(SegmentInfo* segment_info) {
527 int64_t segment_start_time = segment_info->start_time;
528 segment_info->start_time += segment_info->duration;
529 segment_info->repeat--;
530 int64_t start_number = segment_info->start_segment_number;
531 segment_info->start_segment_number++;
533 if (mpd_options_.mpd_params.preserved_segments_outside_live_window == 0)
536 segments_to_be_removed_.push_back(
537 media::GetSegmentName(media_info_.segment_template(), segment_start_time,
538 start_number, media_info_.bandwidth()));
539 while (segments_to_be_removed_.size() >
540 mpd_options_.mpd_params.preserved_segments_outside_live_window) {
541 const std::string& file_name = segments_to_be_removed_.front();
542 VLOG(2) <<
"Deleting " << file_name;
548 if (!File::Delete(file_name.c_str()) &&
549 File::GetFileSize(file_name.c_str()) >= 0) {
550 LOG(WARNING) <<
"Failed to delete " << file_name <<
"; Will retry later.";
553 segments_to_be_removed_.pop_front();
557std::string Representation::GetVideoMimeType()
const {
558 return GetMimeType(
"video", media_info_.container_type());
561std::string Representation::GetAudioMimeType()
const {
562 return GetMimeType(
"audio", media_info_.container_type());
565std::string Representation::GetTextMimeType()
const {
566 CHECK(media_info_.has_text_info());
567 if (media_info_.text_info().codec() ==
"ttml") {
568 switch (media_info_.container_type()) {
569 case MediaInfo::CONTAINER_TEXT:
570 return "application/ttml+xml";
571 case MediaInfo::CONTAINER_MP4:
572 return "application/mp4";
574 LOG(ERROR) <<
"Failed to determine MIME type for TTML container: "
575 << media_info_.container_type();
579 if (media_info_.text_info().codec() ==
"wvtt") {
580 if (media_info_.container_type() == MediaInfo::CONTAINER_TEXT) {
582 }
else if (media_info_.container_type() == MediaInfo::CONTAINER_MP4) {
583 return "application/mp4";
585 LOG(ERROR) <<
"Failed to determine MIME type for VTT container: "
586 << media_info_.container_type();
590 LOG(ERROR) <<
"Cannot determine MIME type for format: "
591 << media_info_.text_info().codec()
592 <<
" container: " << media_info_.container_type();
596std::string Representation::RepresentationAsString()
const {
597 std::string s = absl::StrFormat(
"Representation (id=%d,", id_);
598 if (media_info_.has_video_info()) {
599 const MediaInfo_VideoInfo& video_info = media_info_.video_info();
600 absl::StrAppendFormat(&s,
"codec='%s',width=%d,height=%d",
601 video_info.codec().c_str(), video_info.width(),
602 video_info.height());
603 }
else if (media_info_.has_audio_info()) {
604 const MediaInfo_AudioInfo& audio_info = media_info_.audio_info();
605 absl::StrAppendFormat(
606 &s,
"codec='%s',frequency=%d,language='%s'", audio_info.codec().c_str(),
607 audio_info.sampling_frequency(), audio_info.language().c_str());
608 }
else if (media_info_.has_text_info()) {
609 const MediaInfo_TextInfo& text_info = media_info_.text_info();
610 absl::StrAppendFormat(&s,
"codec='%s',language='%s'",
611 text_info.codec().c_str(),
612 text_info.language().c_str());
614 absl::StrAppendFormat(&s,
")");
void AddBlock(uint64_t size_in_bytes, double duration)
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
virtual void AddNewSegment(int64_t start_time, int64_t duration, uint64_t size, int64_t segment_number)
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
virtual void UpdateCompletedSegment(int64_t duration, uint64_t size)
void SuppressOnce(SuppressFlag flag)
virtual const MediaInfo & GetMediaInfo() const
void SetAvailabilityTimeOffset()
virtual void SetSampleDuration(int32_t sample_duration)
bool GetStartAndEndTimestamps(double *start_timestamp_seconds, double *end_timestamp_seconds) const
Representation(const MediaInfo &media_info, const MpdOptions &mpd_options, uint32_t representation_id, std::unique_ptr< RepresentationStateChangeListener > state_change_listener)
std::optional< xml::XmlNode > GetXml()
void SetPresentationTimeOffset(double presentation_time_offset)
Set @presentationTimeOffset in SegmentBase / SegmentTemplate.
void SetSegmentDuration()
RepresentationType in MPD.
bool AddVODOnlyInfo(const MediaInfo &media_info, bool use_segment_list, double target_segment_duration)
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, bool low_latency_dash_mode)
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info)
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate)
bool SetStringAttribute(const std::string &attribute_name, const std::string &attribute)
bool SetIntegerAttribute(const std::string &attribute_name, uint64_t number)
All the methods that are virtual are virtual for mocking.