Shaka Packager SDK
representation.cc
1 // Copyright 2017 Google Inc. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include "packager/mpd/base/representation.h"
8 
9 #include "packager/base/logging.h"
10 #include "packager/mpd/base/mpd_options.h"
11 #include "packager/mpd/base/mpd_utils.h"
12 #include "packager/mpd/base/xml/xml_node.h"
13 
14 namespace shaka {
15 namespace {
16 
17 std::string GetMimeType(const std::string& prefix,
18  MediaInfo::ContainerType container_type) {
19  switch (container_type) {
20  case MediaInfo::CONTAINER_MP4:
21  return prefix + "/mp4";
22  case MediaInfo::CONTAINER_MPEG2_TS:
23  // NOTE: DASH MPD spec uses lowercase but RFC3555 says uppercase.
24  return prefix + "/MP2T";
25  case MediaInfo::CONTAINER_WEBM:
26  return prefix + "/webm";
27  default:
28  break;
29  }
30 
31  // Unsupported container types should be rejected/handled by the caller.
32  LOG(ERROR) << "Unrecognized container type: " << container_type;
33  return std::string();
34 }
35 
36 // Check whether the video info has width and height.
37 // DASH IOP also requires several other fields for video representations, namely
38 // width, height, framerate, and sar.
39 bool HasRequiredVideoFields(const MediaInfo_VideoInfo& video_info) {
40  if (!video_info.has_height() || !video_info.has_width()) {
41  LOG(ERROR)
42  << "Width and height are required fields for generating a valid MPD.";
43  return false;
44  }
45  // These fields are not required for a valid MPD, but required for DASH IOP
46  // compliant MPD. MpdBuilder can keep generating MPDs without these fields.
47  LOG_IF(WARNING, !video_info.has_time_scale())
48  << "Video info does not contain timescale required for "
49  "calculating framerate. @frameRate is required for DASH IOP.";
50  LOG_IF(WARNING, !video_info.has_pixel_width())
51  << "Video info does not contain pixel_width to calculate the sample "
52  "aspect ratio required for DASH IOP.";
53  LOG_IF(WARNING, !video_info.has_pixel_height())
54  << "Video info does not contain pixel_height to calculate the sample "
55  "aspect ratio required for DASH IOP.";
56  return true;
57 }
58 
59 uint32_t GetTimeScale(const MediaInfo& media_info) {
60  if (media_info.has_reference_time_scale()) {
61  return media_info.reference_time_scale();
62  }
63 
64  if (media_info.has_video_info()) {
65  return media_info.video_info().time_scale();
66  }
67 
68  if (media_info.has_audio_info()) {
69  return media_info.audio_info().time_scale();
70  }
71 
72  LOG(WARNING) << "No timescale specified, using 1 as timescale.";
73  return 1;
74 }
75 
76 uint64_t LastSegmentStartTime(const SegmentInfo& segment_info) {
77  return segment_info.start_time + segment_info.duration * segment_info.repeat;
78 }
79 
80 // This is equal to |segment_info| end time
81 uint64_t LastSegmentEndTime(const SegmentInfo& segment_info) {
82  return segment_info.start_time +
83  segment_info.duration * (segment_info.repeat + 1);
84 }
85 
86 uint64_t LatestSegmentStartTime(const std::list<SegmentInfo>& segments) {
87  DCHECK(!segments.empty());
88  const SegmentInfo& latest_segment = segments.back();
89  return LastSegmentStartTime(latest_segment);
90 }
91 
92 // Given |timeshift_limit|, finds out the number of segments that are no longer
93 // valid and should be removed from |segment_info|.
94 int SearchTimedOutRepeatIndex(uint64_t timeshift_limit,
95  const SegmentInfo& segment_info) {
96  DCHECK_LE(timeshift_limit, LastSegmentEndTime(segment_info));
97  if (timeshift_limit < segment_info.start_time)
98  return 0;
99 
100  return (timeshift_limit - segment_info.start_time) / segment_info.duration;
101 }
102 
103 } // namespace
104 
106  const MediaInfo& media_info,
107  const MpdOptions& mpd_options,
108  uint32_t id,
109  std::unique_ptr<RepresentationStateChangeListener> state_change_listener)
110  : media_info_(media_info),
111  id_(id),
112  bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks),
113  mpd_options_(mpd_options),
114  start_number_(1),
115  state_change_listener_(std::move(state_change_listener)),
116  output_suppression_flags_(0) {}
117 
119  const Representation& representation,
120  uint64_t presentation_time_offset,
121  std::unique_ptr<RepresentationStateChangeListener> state_change_listener)
122  : Representation(representation.media_info_,
123  representation.mpd_options_,
124  representation.id_,
125  std::move(state_change_listener)) {
126  mime_type_ = representation.mime_type_;
127  codecs_ = representation.codecs_;
128 
129  start_number_ = representation.start_number_;
130  for (const SegmentInfo& segment_info : representation.segment_infos_)
131  start_number_ += segment_info.repeat + 1;
132 
133  media_info_.set_presentation_time_offset(presentation_time_offset);
134 }
135 
136 Representation::~Representation() {}
137 
139  if (!AtLeastOneTrue(media_info_.has_video_info(),
140  media_info_.has_audio_info(),
141  media_info_.has_text_info())) {
142  // This is an error. Segment information can be in AdaptationSet, Period, or
143  // MPD but the interface does not provide a way to set them.
144  // See 5.3.9.1 ISO 23009-1:2012 for segment info.
145  LOG(ERROR) << "Representation needs one of video, audio, or text.";
146  return false;
147  }
148 
149  if (MoreThanOneTrue(media_info_.has_video_info(),
150  media_info_.has_audio_info(),
151  media_info_.has_text_info())) {
152  LOG(ERROR) << "Only one of VideoInfo, AudioInfo, or TextInfo can be set.";
153  return false;
154  }
155 
156  if (media_info_.container_type() == MediaInfo::CONTAINER_UNKNOWN) {
157  LOG(ERROR) << "'container_type' in MediaInfo cannot be CONTAINER_UNKNOWN.";
158  return false;
159  }
160 
161  if (media_info_.has_video_info()) {
162  mime_type_ = GetVideoMimeType();
163  if (!HasRequiredVideoFields(media_info_.video_info())) {
164  LOG(ERROR) << "Missing required fields to create a video Representation.";
165  return false;
166  }
167  } else if (media_info_.has_audio_info()) {
168  mime_type_ = GetAudioMimeType();
169  } else if (media_info_.has_text_info()) {
170  mime_type_ = GetTextMimeType();
171  }
172 
173  if (mime_type_.empty())
174  return false;
175 
176  codecs_ = GetCodecs(media_info_);
177  return true;
178 }
179 
181  const ContentProtectionElement& content_protection_element) {
182  content_protection_elements_.push_back(content_protection_element);
183  RemoveDuplicateAttributes(&content_protection_elements_.back());
184 }
185 
186 void Representation::UpdateContentProtectionPssh(const std::string& drm_uuid,
187  const std::string& pssh) {
188  UpdateContentProtectionPsshHelper(drm_uuid, pssh,
189  &content_protection_elements_);
190 }
191 
192 void Representation::AddNewSegment(uint64_t start_time,
193  uint64_t duration,
194  uint64_t size) {
195  if (start_time == 0 && duration == 0) {
196  LOG(WARNING) << "Got segment with start_time and duration == 0. Ignoring.";
197  return;
198  }
199 
200  if (state_change_listener_)
201  state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
202  if (IsContiguous(start_time, duration, size)) {
203  ++segment_infos_.back().repeat;
204  } else {
205  SegmentInfo s = {start_time, duration, /* Not repeat. */ 0};
206  segment_infos_.push_back(s);
207  }
208 
209  bandwidth_estimator_.AddBlock(
210  size, static_cast<double>(duration) / media_info_.reference_time_scale());
211 
212  SlideWindow();
213  DCHECK_GE(segment_infos_.size(), 1u);
214 }
215 
216 void Representation::SetSampleDuration(uint32_t sample_duration) {
217  if (media_info_.has_video_info()) {
218  media_info_.mutable_video_info()->set_frame_duration(sample_duration);
219  if (state_change_listener_) {
220  state_change_listener_->OnSetFrameRateForRepresentation(
221  sample_duration, media_info_.video_info().time_scale());
222  }
223  }
224 }
225 
226 const MediaInfo& Representation::GetMediaInfo() const {
227  return media_info_;
228 }
229 
230 // Uses info in |media_info_| and |content_protection_elements_| to create a
231 // "Representation" node.
232 // MPD schema has strict ordering. The following must be done in order.
233 // AddVideoInfo() (possibly adds FramePacking elements), AddAudioInfo() (Adds
234 // AudioChannelConfig elements), AddContentProtectionElements*(), and
235 // AddVODOnlyInfo() (Adds segment info).
236 xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
237  if (!HasRequiredMediaInfoFields()) {
238  LOG(ERROR) << "MediaInfo missing required fields.";
239  return xml::scoped_xml_ptr<xmlNode>();
240  }
241 
242  const uint64_t bandwidth = media_info_.has_bandwidth()
243  ? media_info_.bandwidth()
244  : bandwidth_estimator_.Estimate();
245 
246  DCHECK(!(HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)));
247 
248  xml::RepresentationXmlNode representation;
249  // Mandatory fields for Representation.
250  representation.SetId(id_);
251  representation.SetIntegerAttribute("bandwidth", bandwidth);
252  if (!codecs_.empty())
253  representation.SetStringAttribute("codecs", codecs_);
254  representation.SetStringAttribute("mimeType", mime_type_);
255 
256  const bool has_video_info = media_info_.has_video_info();
257  const bool has_audio_info = media_info_.has_audio_info();
258 
259  if (has_video_info &&
260  !representation.AddVideoInfo(
261  media_info_.video_info(),
262  !(output_suppression_flags_ & kSuppressWidth),
263  !(output_suppression_flags_ & kSuppressHeight),
264  !(output_suppression_flags_ & kSuppressFrameRate))) {
265  LOG(ERROR) << "Failed to add video info to Representation XML.";
266  return xml::scoped_xml_ptr<xmlNode>();
267  }
268 
269  if (has_audio_info &&
270  !representation.AddAudioInfo(media_info_.audio_info())) {
271  LOG(ERROR) << "Failed to add audio info to Representation XML.";
272  return xml::scoped_xml_ptr<xmlNode>();
273  }
274 
275  if (!representation.AddContentProtectionElements(
276  content_protection_elements_)) {
277  return xml::scoped_xml_ptr<xmlNode>();
278  }
279 
280  if (HasVODOnlyFields(media_info_) &&
281  !representation.AddVODOnlyInfo(media_info_)) {
282  LOG(ERROR) << "Failed to add VOD segment info.";
283  return xml::scoped_xml_ptr<xmlNode>();
284  }
285 
286  if (HasLiveOnlyFields(media_info_) &&
287  !representation.AddLiveOnlyInfo(media_info_, segment_infos_,
288  start_number_)) {
289  LOG(ERROR) << "Failed to add Live info.";
290  return xml::scoped_xml_ptr<xmlNode>();
291  }
292  // TODO(rkuroiwa): It is likely that all representations have the exact same
293  // SegmentTemplate. Optimize and propagate the tag up to AdaptationSet level.
294 
295  output_suppression_flags_ = 0;
296  return representation.PassScopedPtr();
297 }
298 
299 void Representation::SuppressOnce(SuppressFlag flag) {
300  output_suppression_flags_ |= flag;
301 }
302 
303 bool Representation::GetEarliestTimestamp(double* timestamp_seconds) const {
304  DCHECK(timestamp_seconds);
305 
306  if (segment_infos_.empty())
307  return false;
308 
309  *timestamp_seconds = static_cast<double>(segment_infos_.begin()->start_time) /
310  GetTimeScale(media_info_);
311  return true;
312 }
313 
315  return media_info_.media_duration_seconds();
316 }
317 
318 bool Representation::HasRequiredMediaInfoFields() const {
319  if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
320  LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields.";
321  return false;
322  }
323 
324  if (!media_info_.has_container_type()) {
325  LOG(ERROR) << "MediaInfo missing required field: container_type.";
326  return false;
327  }
328 
329  if (HasVODOnlyFields(media_info_) && !media_info_.has_bandwidth()) {
330  LOG(ERROR) << "Missing 'bandwidth' field. MediaInfo requires bandwidth for "
331  "static profile for generating a valid MPD.";
332  return false;
333  }
334 
335  VLOG_IF(3, HasLiveOnlyFields(media_info_) && !media_info_.has_bandwidth())
336  << "MediaInfo missing field 'bandwidth'. Using estimated from "
337  "segment size.";
338 
339  return true;
340 }
341 
342 bool Representation::IsContiguous(uint64_t start_time,
343  uint64_t duration,
344  uint64_t size) const {
345  if (segment_infos_.empty())
346  return false;
347 
348  // Contiguous segment.
349  const SegmentInfo& previous = segment_infos_.back();
350  const uint64_t previous_segment_end_time =
351  previous.start_time + previous.duration * (previous.repeat + 1);
352  if (previous_segment_end_time == start_time &&
353  segment_infos_.back().duration == duration) {
354  return true;
355  }
356 
357  // No out of order segments.
358  const uint64_t previous_segment_start_time =
359  previous.start_time + previous.duration * previous.repeat;
360  if (previous_segment_start_time >= start_time) {
361  LOG(ERROR) << "Segments should not be out of order segment. Adding segment "
362  "with start_time == "
363  << start_time << " but the previous segment starts at "
364  << previous_segment_start_time << ".";
365  return false;
366  }
367 
368  // A gap since previous.
369  const uint64_t kRoundingErrorGrace = 5;
370  if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
371  LOG(WARNING) << "Found a gap of size "
372  << (start_time - previous_segment_end_time)
373  << " > kRoundingErrorGrace (" << kRoundingErrorGrace
374  << "). The new segment starts at " << start_time
375  << " but the previous segment ends at "
376  << previous_segment_end_time << ".";
377  return false;
378  }
379 
380  // No overlapping segments.
381  if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
382  LOG(WARNING)
383  << "Segments should not be overlapping. The new segment starts at "
384  << start_time << " but the previous segment ends at "
385  << previous_segment_end_time << ".";
386  return false;
387  }
388 
389  // Within rounding error grace but technically not contiguous in terms of MPD.
390  return false;
391 }
392 
393 void Representation::SlideWindow() {
394  DCHECK(!segment_infos_.empty());
395  if (mpd_options_.mpd_params.time_shift_buffer_depth <= 0.0 ||
396  mpd_options_.mpd_type == MpdType::kStatic)
397  return;
398 
399  const uint32_t time_scale = GetTimeScale(media_info_);
400  DCHECK_GT(time_scale, 0u);
401 
402  uint64_t time_shift_buffer_depth = static_cast<uint64_t>(
403  mpd_options_.mpd_params.time_shift_buffer_depth * time_scale);
404 
405  // The start time of the latest segment is considered the current_play_time,
406  // and this should guarantee that the latest segment will stay in the list.
407  const uint64_t current_play_time = LatestSegmentStartTime(segment_infos_);
408  if (current_play_time <= time_shift_buffer_depth)
409  return;
410 
411  const uint64_t timeshift_limit = current_play_time - time_shift_buffer_depth;
412 
413  // First remove all the SegmentInfos that are completely out of range, by
414  // looking at the very last segment's end time.
415  std::list<SegmentInfo>::iterator first = segment_infos_.begin();
416  std::list<SegmentInfo>::iterator last = first;
417  size_t num_segments_removed = 0;
418  for (; last != segment_infos_.end(); ++last) {
419  const uint64_t last_segment_end_time = LastSegmentEndTime(*last);
420  if (timeshift_limit < last_segment_end_time)
421  break;
422  num_segments_removed += last->repeat + 1;
423  }
424  segment_infos_.erase(first, last);
425  start_number_ += num_segments_removed;
426 
427  // Now some segment in the first SegmentInfo should be left in the list.
428  SegmentInfo* first_segment_info = &segment_infos_.front();
429  DCHECK_LE(timeshift_limit, LastSegmentEndTime(*first_segment_info));
430 
431  // Identify which segments should still be in the SegmentInfo.
432  const int repeat_index =
433  SearchTimedOutRepeatIndex(timeshift_limit, *first_segment_info);
434  CHECK_GE(repeat_index, 0);
435  if (repeat_index == 0)
436  return;
437 
438  first_segment_info->start_time = first_segment_info->start_time +
439  first_segment_info->duration * repeat_index;
440 
441  first_segment_info->repeat = first_segment_info->repeat - repeat_index;
442  start_number_ += repeat_index;
443 }
444 
445 std::string Representation::GetVideoMimeType() const {
446  return GetMimeType("video", media_info_.container_type());
447 }
448 
449 std::string Representation::GetAudioMimeType() const {
450  return GetMimeType("audio", media_info_.container_type());
451 }
452 
453 std::string Representation::GetTextMimeType() const {
454  CHECK(media_info_.has_text_info());
455  if (media_info_.text_info().format() == "ttml") {
456  switch (media_info_.container_type()) {
457  case MediaInfo::CONTAINER_TEXT:
458  return "application/ttml+xml";
459  case MediaInfo::CONTAINER_MP4:
460  return "application/mp4";
461  default:
462  LOG(ERROR) << "Failed to determine MIME type for TTML container: "
463  << media_info_.container_type();
464  return "";
465  }
466  }
467  if (media_info_.text_info().format() == "vtt") {
468  if (media_info_.container_type() == MediaInfo::CONTAINER_TEXT) {
469  return "text/vtt";
470  } else if (media_info_.container_type() == MediaInfo::CONTAINER_MP4) {
471  return "application/mp4";
472  }
473  LOG(ERROR) << "Failed to determine MIME type for VTT container: "
474  << media_info_.container_type();
475  return "";
476  }
477 
478  LOG(ERROR) << "Cannot determine MIME type for format: "
479  << media_info_.text_info().format()
480  << " container: " << media_info_.container_type();
481  return "";
482 }
483 
484 } // namespace shaka
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate)
Definition: xml_node.cc:233
virtual const MediaInfo & GetMediaInfo() const
RepresentationType in MPD.
Definition: xml_node.h:139
virtual void AddNewSegment(uint64_t start_time, uint64_t duration, uint64_t size)
float GetDurationSeconds() const
Representation(const MediaInfo &media_info, const MpdOptions &mpd_options, uint32_t representation_id, std::unique_ptr< RepresentationStateChangeListener > state_change_listener)
virtual void SetSampleDuration(uint32_t sample_duration)
STL namespace.
scoped_xml_ptr< xmlNode > PassScopedPtr()
Definition: xml_node.cc:136
All the methods that are virtual are virtual for mocking.
bool AddVODOnlyInfo(const MediaInfo &media_info)
Definition: xml_node.cc:278
void SetStringAttribute(const char *attribute_name, const std::string &attribute)
Definition: xml_node.cc:104
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, uint32_t start_number)
Definition: xml_node.cc:324
void SetId(uint32_t id)
Definition: xml_node.cc:127
bool GetEarliestTimestamp(double *timestamp_seconds) const
xml::scoped_xml_ptr< xmlNode > GetXml()
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
void SetIntegerAttribute(const char *attribute_name, uint64_t number)
Definition: xml_node.cc:111
Defines Mpd Options.
Definition: mpd_options.h:25
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
double time_shift_buffer_depth
Definition: mpd_params.h:33
void SuppressOnce(SuppressFlag flag)
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info)
Definition: xml_node.cc:270