Shaka Packager SDK
adaptation_set.cc
1 // Copyright 2017 Google LLC. 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/adaptation_set.h>
8 
9 #include <cmath>
10 
11 #include <absl/log/check.h>
12 #include <absl/log/log.h>
13 #include <absl/strings/numbers.h>
14 #include <absl/strings/str_format.h>
15 
16 #include <packager/macros/classes.h>
17 #include <packager/macros/logging.h>
18 #include <packager/mpd/base/media_info.pb.h>
19 #include <packager/mpd/base/mpd_options.h>
20 #include <packager/mpd/base/mpd_utils.h>
21 #include <packager/mpd/base/representation.h>
22 #include <packager/mpd/base/xml/xml_node.h>
23 
24 namespace shaka {
25 namespace {
26 
27 AdaptationSet::Role MediaInfoTextTypeToRole(
28  MediaInfo::TextInfo::TextType type) {
29  switch (type) {
30  case MediaInfo::TextInfo::UNKNOWN:
31  LOG(WARNING) << "Unknown text type, assuming subtitle.";
32  return AdaptationSet::kRoleSubtitle;
33  case MediaInfo::TextInfo::CAPTION:
34  return AdaptationSet::kRoleCaption;
35  case MediaInfo::TextInfo::SUBTITLE:
36  return AdaptationSet::kRoleSubtitle;
37  default:
38  NOTIMPLEMENTED() << "Unknown MediaInfo TextType: " << type
39  << " assuming subtitle.";
40  return AdaptationSet::kRoleSubtitle;
41  }
42 }
43 
44 std::string RoleToText(AdaptationSet::Role role) {
45  // Using switch so that the compiler can detect whether there is a case that's
46  // not being handled.
47  switch (role) {
48  case AdaptationSet::kRoleCaption:
49  return "caption";
50  case AdaptationSet::kRoleSubtitle:
51  return "subtitle";
52  case AdaptationSet::kRoleMain:
53  return "main";
54  case AdaptationSet::kRoleAlternate:
55  return "alternate";
56  case AdaptationSet::kRoleSupplementary:
57  return "supplementary";
58  case AdaptationSet::kRoleCommentary:
59  return "commentary";
60  case AdaptationSet::kRoleDub:
61  return "dub";
62  case AdaptationSet::kRoleDescription:
63  return "description";
64  case AdaptationSet::kRoleSign:
65  return "sign";
66  case AdaptationSet::kRoleMetadata:
67  return "metadata";
68  case AdaptationSet::kRoleEnhancedAudioIntelligibility:
69  return "enhanced-audio-intelligibility";
70  case AdaptationSet::kRoleEmergency:
71  return "emergency";
72  case AdaptationSet::kRoleForcedSubtitle:
73  return "forced-subtitle";
74  case AdaptationSet::kRoleEasyreader:
75  return "easyreader";
76  case AdaptationSet::kRoleKaraoke:
77  return "karaoke";
78  default:
79  return "unknown";
80  }
81 }
82 
83 // Returns the picture aspect ratio string e.g. "16:9", "4:3".
84 // "Reducing the quotient to minimal form" does not work well in practice as
85 // there may be some rounding performed in the input, e.g. the resolution of
86 // 480p is 854:480 for 16:9 aspect ratio, can only be reduced to 427:240.
87 // The algorithm finds out the pair of integers, num and den, where num / den is
88 // the closest ratio to scaled_width / scaled_height, by looping den through
89 // common values.
90 std::string GetPictureAspectRatio(uint32_t width,
91  uint32_t height,
92  uint32_t pixel_width,
93  uint32_t pixel_height) {
94  const uint32_t scaled_width = pixel_width * width;
95  const uint32_t scaled_height = pixel_height * height;
96  const double par = static_cast<double>(scaled_width) / scaled_height;
97 
98  // Typical aspect ratios have par_y less than or equal to 19:
99  // https://en.wikipedia.org/wiki/List_of_common_resolutions
100  const uint32_t kLargestPossibleParY = 19;
101 
102  uint32_t par_num = 0;
103  uint32_t par_den = 0;
104  double min_error = 1.0;
105  for (uint32_t den = 1; den <= kLargestPossibleParY; ++den) {
106  uint32_t num = par * den + 0.5;
107  double error = fabs(par - static_cast<double>(num) / den);
108  if (error < min_error) {
109  min_error = error;
110  par_num = num;
111  par_den = den;
112  if (error == 0)
113  break;
114  }
115  }
116  VLOG(2) << "width*pix_width : height*pixel_height (" << scaled_width << ":"
117  << scaled_height << ") reduced to " << par_num << ":" << par_den
118  << " with error " << min_error << ".";
119 
120  return absl::StrFormat("%d:%d", par_num, par_den);
121 }
122 
123 // Adds an entry to picture_aspect_ratio if the size of picture_aspect_ratio is
124 // less than 2 and video_info has both pixel width and pixel height.
125 void AddPictureAspectRatio(const MediaInfo::VideoInfo& video_info,
126  std::set<std::string>* picture_aspect_ratio) {
127  // If there are more than one entries in picture_aspect_ratio, the @par
128  // attribute cannot be set, so skip.
129  if (picture_aspect_ratio->size() > 1)
130  return;
131 
132  if (video_info.width() == 0 || video_info.height() == 0 ||
133  video_info.pixel_width() == 0 || video_info.pixel_height() == 0) {
134  // If there is even one Representation without a @sar attribute, @par cannot
135  // be calculated.
136  // Just populate the set with at least 2 bogus strings so that further call
137  // to this function will bail out immediately.
138  picture_aspect_ratio->insert("bogus");
139  picture_aspect_ratio->insert("entries");
140  return;
141  }
142 
143  const std::string par = GetPictureAspectRatio(
144  video_info.width(), video_info.height(), video_info.pixel_width(),
145  video_info.pixel_height());
146  DVLOG(1) << "Setting par as: " << par
147  << " for video with width: " << video_info.width()
148  << " height: " << video_info.height()
149  << " pixel_width: " << video_info.pixel_width() << " pixel_height; "
150  << video_info.pixel_height();
151  picture_aspect_ratio->insert(par);
152 }
153 
154 class RepresentationStateChangeListenerImpl
155  : public RepresentationStateChangeListener {
156  public:
157  // |adaptation_set| is not owned by this class.
158  RepresentationStateChangeListenerImpl(uint32_t representation_id,
159  AdaptationSet* adaptation_set)
160  : representation_id_(representation_id), adaptation_set_(adaptation_set) {
161  DCHECK(adaptation_set_);
162  }
163  ~RepresentationStateChangeListenerImpl() override {}
164 
165  // RepresentationStateChangeListener implementation.
166  void OnNewSegmentForRepresentation(int64_t start_time,
167  int64_t duration) override {
168  adaptation_set_->OnNewSegmentForRepresentation(representation_id_,
169  start_time, duration);
170  }
171 
172  void OnSetFrameRateForRepresentation(int32_t frame_duration,
173  int32_t timescale) override {
174  adaptation_set_->OnSetFrameRateForRepresentation(representation_id_,
175  frame_duration, timescale);
176  }
177 
178  private:
179  const uint32_t representation_id_;
180  AdaptationSet* const adaptation_set_;
181 
182  DISALLOW_COPY_AND_ASSIGN(RepresentationStateChangeListenerImpl);
183 };
184 
185 } // namespace
186 
187 AdaptationSet::AdaptationSet(const std::string& language,
188  const MpdOptions& mpd_options,
189  uint32_t* counter)
190  : representation_counter_(counter),
191  language_(language),
192  mpd_options_(mpd_options),
193  protected_content_(nullptr) {
194  DCHECK(counter);
195 }
196 
197 AdaptationSet::~AdaptationSet() {
198  delete protected_content_;
199 }
200 
201 void AdaptationSet::set_protected_content(const MediaInfo& media_info) {
202  DCHECK(!protected_content_);
203  protected_content_ =
204  new MediaInfo::ProtectedContent(media_info.protected_content());
205 }
206 
207 // The easiest way to check whether two protobufs are equal, is to compare the
208 // serialized version.
209 bool ProtectedContentEq(
210  const MediaInfo::ProtectedContent& content_protection1,
211  const MediaInfo::ProtectedContent& content_protection2) {
212  return content_protection1.SerializeAsString() ==
213  content_protection2.SerializeAsString();
214 }
215 
217  const MediaInfo& media_info,
218  bool content_protection_in_adaptation_set) {
219  if (codec_ != GetBaseCodec(media_info))
220  return false;
221 
222  if (!content_protection_in_adaptation_set)
223  return true;
224 
225  if (!protected_content_)
226  return !media_info.has_protected_content();
227 
228  if (!media_info.has_protected_content())
229  return false;
230 
231  return ProtectedContentEq(*protected_content_,
232  media_info.protected_content());
233 }
234 
235 std::set<std::string> GetUUIDs(
236  const MediaInfo::ProtectedContent* protected_content) {
237  std::set<std::string> uuids;
238  for (const auto& entry : protected_content->content_protection_entry())
239  uuids.insert(entry.uuid());
240  return uuids;
241 }
242 
244  const AdaptationSet& adaptation_set) {
245 
246  // adaptation sets are switchable if both are not protected
247  if (!protected_content_ && !adaptation_set.protected_content()) {
248  return true;
249  }
250 
251  // or if both are protected and have the same UUID
252  if (protected_content_ && adaptation_set.protected_content()) {
253  return GetUUIDs(protected_content_) ==
254  GetUUIDs(adaptation_set.protected_content());
255  }
256 
257  return false;
258 }
259 
260 Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) {
261  const uint32_t representation_id = media_info.has_index()
262  ? media_info.index()
263  : (*representation_counter_)++;
264 
265  // Note that AdaptationSet outlive Representation, so this object
266  // will die before AdaptationSet.
267  std::unique_ptr<RepresentationStateChangeListener> listener(
268  new RepresentationStateChangeListenerImpl(representation_id, this));
269  std::unique_ptr<Representation> new_representation(new Representation(
270  media_info, mpd_options_, representation_id, std::move(listener)));
271 
272  if (!new_representation->Init()) {
273  LOG(ERROR) << "Failed to initialize Representation.";
274  return NULL;
275  }
276  UpdateFromMediaInfo(media_info);
277  Representation* representation_ptr = new_representation.get();
278  representation_map_[representation_ptr->id()] = std::move(new_representation);
279  return representation_ptr;
280 }
281 
283  const Representation& representation) {
284  // Note that AdaptationSet outlive Representation, so this object
285  // will die before AdaptationSet.
286  std::unique_ptr<RepresentationStateChangeListener> listener(
287  new RepresentationStateChangeListenerImpl(representation.id(), this));
288  std::unique_ptr<Representation> new_representation(
289  new Representation(representation, std::move(listener)));
290 
291  UpdateFromMediaInfo(new_representation->GetMediaInfo());
292  Representation* representation_ptr = new_representation.get();
293  representation_map_[representation_ptr->id()] = std::move(new_representation);
294  return representation_ptr;
295 }
296 
298  const ContentProtectionElement& content_protection_element) {
299  content_protection_elements_.push_back(content_protection_element);
300  RemoveDuplicateAttributes(&content_protection_elements_.back());
301 }
302 
303 void AdaptationSet::UpdateContentProtectionPssh(const std::string& drm_uuid,
304  const std::string& pssh) {
305  UpdateContentProtectionPsshHelper(drm_uuid, pssh,
306  &content_protection_elements_);
307 }
308 
309 void AdaptationSet::AddAccessibility(const std::string& scheme,
310  const std::string& value) {
311  accessibilities_.push_back(Accessibility{scheme, value});
312 }
313 
314 void AdaptationSet::AddRole(Role role) {
315  roles_.insert(role);
316 }
317 
318 // Creates a copy of <AdaptationSet> xml element, iterate thru all the
319 // <Representation> (child) elements and add them to the copy.
320 // Set all the attributes first and then add the children elements so that flags
321 // can be passed to Representation to avoid setting redundant attributes. For
322 // example, if AdaptationSet@width is set, then Representation@width is
323 // redundant and should not be set.
324 std::optional<xml::XmlNode> AdaptationSet::GetXml() {
325  xml::AdaptationSetXmlNode adaptation_set;
326 
327  bool suppress_representation_width = false;
328  bool suppress_representation_height = false;
329  bool suppress_representation_frame_rate = false;
330 
331  if (id_ && !adaptation_set.SetId(id_.value()))
332  return std::nullopt;
333  if (!adaptation_set.SetStringAttribute("contentType", content_type_))
334  return std::nullopt;
335  if (!language_.empty() && language_ != "und" &&
336  !adaptation_set.SetStringAttribute("lang", language_)) {
337  return std::nullopt;
338  }
339 
340  // Note that std::{set,map} are ordered, so the last element is the max value.
341  if (video_widths_.size() == 1) {
342  suppress_representation_width = true;
343  if (!adaptation_set.SetIntegerAttribute("width", *video_widths_.begin()))
344  return std::nullopt;
345  } else if (video_widths_.size() > 1) {
346  if (!adaptation_set.SetIntegerAttribute("maxWidth",
347  *video_widths_.rbegin())) {
348  return std::nullopt;
349  }
350  }
351 
352  if (video_heights_.size() == 1) {
353  suppress_representation_height = true;
354  if (!adaptation_set.SetIntegerAttribute("height", *video_heights_.begin()))
355  return std::nullopt;
356  } else if (video_heights_.size() > 1) {
357  if (!adaptation_set.SetIntegerAttribute("maxHeight",
358  *video_heights_.rbegin())) {
359  return std::nullopt;
360  }
361  }
362 
363  if (subsegment_start_with_sap_) {
364  if (!adaptation_set.SetIntegerAttribute("subsegmentStartsWithSAP",
365  subsegment_start_with_sap_))
366  return std::nullopt;
367  } else if (start_with_sap_) {
368  if (!adaptation_set.SetIntegerAttribute("startWithSAP", start_with_sap_))
369  return std::nullopt;
370  }
371 
372  if (video_frame_rates_.size() == 1) {
373  suppress_representation_frame_rate = true;
374  if (!adaptation_set.SetStringAttribute(
375  "frameRate", video_frame_rates_.begin()->second)) {
376  return std::nullopt;
377  }
378  } else if (video_frame_rates_.size() > 1) {
379  if (!adaptation_set.SetStringAttribute(
380  "maxFrameRate", video_frame_rates_.rbegin()->second)) {
381  return std::nullopt;
382  }
383  }
384 
385  // https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf - 4.2.5.1
386  if (IsVideo() && matrix_coefficients_ > 0 &&
387  !adaptation_set.AddSupplementalProperty(
388  "urn:mpeg:mpegB:cicp:MatrixCoefficients",
389  std::to_string(matrix_coefficients_))) {
390  return std::nullopt;
391  }
392 
393  // https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf - 4.2.5.1
394  if (IsVideo() && color_primaries_ > 0 &&
395  !adaptation_set.AddSupplementalProperty(
396  "urn:mpeg:mpegB:cicp:ColourPrimaries",
397  std::to_string(color_primaries_))) {
398  return std::nullopt;
399  }
400 
401  // https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf - 4.2.5.1
402  if (IsVideo() && transfer_characteristics_ > 0 &&
403  !adaptation_set.AddSupplementalProperty(
404  "urn:mpeg:mpegB:cicp:TransferCharacteristics",
405  std::to_string(transfer_characteristics_))) {
406  return std::nullopt;
407  }
408 
409  // Note: must be checked before checking segments_aligned_ (below). So that
410  // segments_aligned_ is set before checking below.
411  if (mpd_options_.mpd_type == MpdType::kStatic) {
412  CheckStaticSegmentAlignment();
413  }
414 
415  if (segments_aligned_ == kSegmentAlignmentTrue) {
416  if (!adaptation_set.SetStringAttribute(
417  mpd_options_.dash_profile == DashProfile::kOnDemand
418  ? "subsegmentAlignment"
419  : "segmentAlignment",
420  "true")) {
421  return std::nullopt;
422  }
423  }
424 
425  if (picture_aspect_ratio_.size() == 1 &&
426  !adaptation_set.SetStringAttribute("par",
427  *picture_aspect_ratio_.begin())) {
428  return std::nullopt;
429  }
430 
431  if (!adaptation_set.AddContentProtectionElements(
432  content_protection_elements_)) {
433  return std::nullopt;
434  }
435 
436  std::string trick_play_reference_ids;
437  for (const AdaptationSet* tp_adaptation_set : trick_play_references_) {
438  // Should be a whitespace-separated list, see DASH-IOP 3.2.9.
439  if (!trick_play_reference_ids.empty())
440  trick_play_reference_ids += ' ';
441  CHECK(tp_adaptation_set->has_id());
442  trick_play_reference_ids += std::to_string(tp_adaptation_set->id());
443  }
444  if (!trick_play_reference_ids.empty() &&
445  !adaptation_set.AddEssentialProperty(
446  "http://dashif.org/guidelines/trickmode", trick_play_reference_ids)) {
447  return std::nullopt;
448  }
449 
450  std::string switching_ids;
451  for (const AdaptationSet* s_adaptation_set : switchable_adaptation_sets_) {
452  // Should be a comma-separated list, see DASH-IOP 3.8.
453  if (!switching_ids.empty())
454  switching_ids += ',';
455  CHECK(s_adaptation_set->has_id());
456  switching_ids += std::to_string(s_adaptation_set->id());
457  }
458  if (!switching_ids.empty() &&
459  !adaptation_set.AddSupplementalProperty(
460  "urn:mpeg:dash:adaptation-set-switching:2016", switching_ids)) {
461  return std::nullopt;
462  }
463 
464  for (const AdaptationSet::Accessibility& accessibility : accessibilities_) {
465  if (!adaptation_set.AddAccessibilityElement(accessibility.scheme,
466  accessibility.value)) {
467  return std::nullopt;
468  }
469  }
470 
471  for (AdaptationSet::Role role : roles_) {
472  if (!adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011",
473  RoleToText(role))) {
474  return std::nullopt;
475  }
476  }
477 
478  if (!label_.empty() && !adaptation_set.AddLabelElement(label_))
479  return std::nullopt;
480 
481  for (const auto& representation_pair : representation_map_) {
482  const auto& representation = representation_pair.second;
483  if (suppress_representation_width)
484  representation->SuppressOnce(Representation::kSuppressWidth);
485  if (suppress_representation_height)
486  representation->SuppressOnce(Representation::kSuppressHeight);
487  if (suppress_representation_frame_rate)
488  representation->SuppressOnce(Representation::kSuppressFrameRate);
489  auto child = representation->GetXml();
490  if (!child || !adaptation_set.AddChild(std::move(*child)))
491  return std::nullopt;
492  }
493 
494  return adaptation_set;
495 }
496 
497 void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) {
498  segments_aligned_ =
499  segment_alignment ? kSegmentAlignmentTrue : kSegmentAlignmentFalse;
500  force_set_segment_alignment_ = true;
501 }
502 
504  const AdaptationSet* adaptation_set) {
505  switchable_adaptation_sets_.push_back(adaptation_set);
506 }
507 
509  subsegment_start_with_sap_ = sap_value;
510 }
511 
512 void AdaptationSet::ForceStartwithSAP(uint32_t sap_value) {
513  start_with_sap_ = sap_value;
514 }
515 
516 // For dynamic MPD, storing all start_time and duration will out-of-memory
517 // because there's no way of knowing when it will end. Static MPD
518 // subsegmentAlignment check is *not* done here because it is possible that some
519 // Representations might not have been added yet (e.g. a thread is assigned per
520 // muxer so one might run faster than others). To be clear, for dynamic MPD, all
521 // Representations should be added before a segment is added.
522 void AdaptationSet::OnNewSegmentForRepresentation(uint32_t representation_id,
523  int64_t start_time,
524  int64_t duration) {
525  if (mpd_options_.mpd_type == MpdType::kDynamic) {
526  CheckDynamicSegmentAlignment(representation_id, start_time, duration);
527  } else {
528  representation_segment_start_times_[representation_id].push_back(
529  start_time);
530  }
531 }
532 
533 void AdaptationSet::OnSetFrameRateForRepresentation(uint32_t representation_id,
534  int32_t frame_duration,
535  int32_t timescale) {
536  RecordFrameRate(frame_duration, timescale);
537 }
538 
540  trick_play_references_.push_back(adaptation_set);
541 }
542 
543 const std::list<Representation*> AdaptationSet::GetRepresentations() const {
544  std::list<Representation*> representations;
545  for (const auto& representation_pair : representation_map_) {
546  representations.push_back(representation_pair.second.get());
547  }
548  return representations;
549 }
550 
552  return content_type_ == "video";
553 }
554 
555 void AdaptationSet::UpdateFromMediaInfo(const MediaInfo& media_info) {
556  // For videos, record the width, height, and the frame rate to calculate the
557  // max {width,height,framerate} required for DASH IOP.
558  if (media_info.has_video_info()) {
559  const MediaInfo::VideoInfo& video_info = media_info.video_info();
560  DCHECK(video_info.has_width());
561  DCHECK(video_info.has_height());
562  video_widths_.insert(video_info.width());
563  video_heights_.insert(video_info.height());
564 
565  if (video_info.has_time_scale() && video_info.has_frame_duration())
566  RecordFrameRate(video_info.frame_duration(), video_info.time_scale());
567 
568  AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
569  }
570 
571  // the command-line index for this AdaptationSet will be the
572  // minimum of the Representations in the set
573  if (media_info.has_index()) {
574  if (index_.has_value()) {
575  index_ = std::min(index_.value(), media_info.index());
576  } else {
577  index_ = media_info.index();
578  }
579  }
580 
581  if (media_info.has_dash_label())
582  label_ = media_info.dash_label();
583 
584  if (media_info.has_video_info()) {
585  content_type_ = "video";
586  } else if (media_info.has_audio_info()) {
587  content_type_ = "audio";
588  } else if (media_info.has_text_info()) {
589  content_type_ = "text";
590 
591  if (media_info.text_info().has_type() &&
592  (media_info.text_info().type() != MediaInfo::TextInfo::UNKNOWN)) {
593  roles_.insert(MediaInfoTextTypeToRole(media_info.text_info().type()));
594  }
595  }
596 }
597 
598 // This implementation assumes that each representations' segments' are
599 // contiguous.
600 // Also assumes that all Representations are added before this is called.
601 // This checks whether the first elements of the lists in
602 // representation_segment_start_times_ are aligned.
603 // For example, suppose this method was just called with args rep_id=2
604 // start_time=1.
605 // 1 -> [1, 100, 200]
606 // 2 -> [1]
607 // The timestamps of the first elements match, so this flags
608 // segments_aligned_=true.
609 // Also since the first segment start times match, the first element of all the
610 // lists are removed, so the map of lists becomes:
611 // 1 -> [100, 200]
612 // 2 -> []
613 // Note that there could be false positives.
614 // e.g. just got rep_id=3 start_time=1 duration=300, and the duration of the
615 // whole AdaptationSet is 300.
616 // 1 -> [1, 100, 200]
617 // 2 -> [1, 90, 100]
618 // 3 -> [1]
619 // They are not aligned but this will be marked as aligned.
620 // But since this is unlikely to happen in the packager (and to save
621 // computation), this isn't handled at the moment.
622 void AdaptationSet::CheckDynamicSegmentAlignment(uint32_t representation_id,
623  int64_t start_time,
624  int64_t /* duration */) {
625  if (segments_aligned_ == kSegmentAlignmentFalse ||
626  force_set_segment_alignment_) {
627  return;
628  }
629 
630  std::list<int64_t>& current_representation_start_times =
631  representation_segment_start_times_[representation_id];
632  current_representation_start_times.push_back(start_time);
633  // There's no way to detemine whether the segments are aligned if some
634  // representations do not have any segments.
635  if (representation_segment_start_times_.size() != representation_map_.size())
636  return;
637 
638  DCHECK(!current_representation_start_times.empty());
639  const int64_t expected_start_time =
640  current_representation_start_times.front();
641  for (const auto& key_value : representation_segment_start_times_) {
642  const std::list<int64_t>& representation_start_time = key_value.second;
643  // If there are no entries in a list, then there is no way for the
644  // segment alignment status to change.
645  // Note that it can be empty because entries get deleted below.
646  if (representation_start_time.empty())
647  return;
648 
649  if (expected_start_time != representation_start_time.front()) {
650  VLOG(1) << "Seeing Misaligned segments with different start_times: "
651  << expected_start_time << " vs "
652  << representation_start_time.front();
653  // Flag as false and clear the start times data, no need to keep it
654  // around.
655  segments_aligned_ = kSegmentAlignmentFalse;
656  representation_segment_start_times_.clear();
657  return;
658  }
659  }
660  segments_aligned_ = kSegmentAlignmentTrue;
661 
662  for (auto& key_value : representation_segment_start_times_) {
663  std::list<int64_t>& representation_start_time = key_value.second;
664  representation_start_time.pop_front();
665  }
666 }
667 
668 // Make sure all segements start times match for all Representations.
669 // This assumes that the segments are contiguous.
670 void AdaptationSet::CheckStaticSegmentAlignment() {
671  if (segments_aligned_ == kSegmentAlignmentFalse ||
672  force_set_segment_alignment_) {
673  return;
674  }
675  if (representation_segment_start_times_.empty())
676  return;
677  if (representation_segment_start_times_.size() == 1) {
678  segments_aligned_ = kSegmentAlignmentTrue;
679  return;
680  }
681 
682  // This is not the most efficient implementation to compare the values
683  // because expected_time_line is compared against all other time lines, but
684  // probably the most readable.
685  const std::list<int64_t>& expected_time_line =
686  representation_segment_start_times_.begin()->second;
687 
688  bool all_segment_time_line_same_length = true;
689  // Note that the first entry is skipped because it is expected_time_line.
690  RepresentationTimeline::const_iterator it =
691  representation_segment_start_times_.begin();
692  for (++it; it != representation_segment_start_times_.end(); ++it) {
693  const std::list<int64_t>& other_time_line = it->second;
694  if (expected_time_line.size() != other_time_line.size()) {
695  all_segment_time_line_same_length = false;
696  }
697 
698  const std::list<int64_t>* longer_list = &other_time_line;
699  const std::list<int64_t>* shorter_list = &expected_time_line;
700  if (expected_time_line.size() > other_time_line.size()) {
701  shorter_list = &other_time_line;
702  longer_list = &expected_time_line;
703  }
704 
705  if (!std::equal(shorter_list->begin(), shorter_list->end(),
706  longer_list->begin())) {
707  // Some segments are definitely unaligned.
708  segments_aligned_ = kSegmentAlignmentFalse;
709  representation_segment_start_times_.clear();
710  return;
711  }
712  }
713 
714  // TODO(rkuroiwa): The right way to do this is to also check the durations.
715  // For example:
716  // (a) 3 4 5
717  // (b) 3 4 5 6
718  // could be true or false depending on the length of the third segment of (a).
719  // i.e. if length of the third segment is 2, then this is not aligned.
720  if (!all_segment_time_line_same_length) {
721  segments_aligned_ = kSegmentAlignmentUnknown;
722  return;
723  }
724 
725  segments_aligned_ = kSegmentAlignmentTrue;
726 }
727 
728 // Since all AdaptationSet cares about is the maxFrameRate, representation_id
729 // is not passed to this method.
730 void AdaptationSet::RecordFrameRate(int32_t frame_duration, int32_t timescale) {
731  if (frame_duration == 0) {
732  LOG(ERROR) << "Frame duration is 0 and cannot be set.";
733  return;
734  }
735  video_frame_rates_[static_cast<double>(timescale) / frame_duration] =
736  absl::StrFormat("%d/%d", timescale, frame_duration);
737 }
738 
739 } // namespace shaka
void OnNewSegmentForRepresentation(uint32_t representation_id, int64_t start_time, int64_t duration)
virtual Representation * AddRepresentation(const MediaInfo &media_info)
virtual void AddAccessibility(const std::string &scheme, const std::string &value)
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
virtual void ForceStartwithSAP(uint32_t sap_value)
virtual void ForceSetSegmentAlignment(bool segment_alignment)
void OnSetFrameRateForRepresentation(uint32_t representation_id, int32_t frame_duration, int32_t timescale)
virtual Representation * CopyRepresentation(const Representation &representation)
virtual void ForceSubsegmentStartswithSAP(uint32_t sap_value)
bool MatchAdaptationSet(const MediaInfo &media_info, bool content_protection_in_adaptation_set)
virtual void AddTrickPlayReference(const AdaptationSet *adaptation_set)
virtual void AddAdaptationSetSwitching(const AdaptationSet *adaptation_set)
std::optional< xml::XmlNode > GetXml()
AdaptationSet(const std::string &language, const MpdOptions &mpd_options, uint32_t *representation_counter)
const MediaInfo::ProtectedContent * protected_content() const
Return ProtectedContent.
void set_protected_content(const MediaInfo &media_info)
bool SwitchableAdaptationSet(const AdaptationSet &adaptation_set)
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
virtual void AddRole(Role role)
uint32_t id() const
AdaptationSetType specified in MPD.
Definition: xml_node.h:163
bool AddAccessibilityElement(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:353
bool AddLabelElement(const std::string &value)
Definition: xml_node.cc:364
bool AddRoleElement(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:359
bool AddEssentialProperty(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:311
bool AddSupplementalProperty(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:305
bool AddChild(XmlNode child)
Definition: xml_node.cc:162
bool SetStringAttribute(const std::string &attribute_name, const std::string &attribute)
Definition: xml_node.cc:205
bool SetId(uint32_t id)
Definition: xml_node.cc:227
bool SetIntegerAttribute(const std::string &attribute_name, uint64_t number)
Definition: xml_node.cc:212
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66
Defines Mpd Options.
Definition: mpd_options.h:25