Shaka Packager SDK
period.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/period.h>
8 
9 #include <absl/log/check.h>
10 #include <absl/log/log.h>
11 
12 #include <packager/mpd/base/adaptation_set.h>
13 #include <packager/mpd/base/mpd_options.h>
14 #include <packager/mpd/base/mpd_utils.h>
15 #include <packager/mpd/base/xml/xml_node.h>
16 
17 namespace shaka {
18 namespace {
19 
20 const std::string& GetDefaultAudioLanguage(const MpdOptions& mpd_options) {
21  return mpd_options.mpd_params.default_language;
22 }
23 
24 const std::string& GetDefaultTextLanguage(const MpdOptions& mpd_options) {
25  return mpd_options.mpd_params.default_text_language.empty()
26  ? mpd_options.mpd_params.default_language
27  : mpd_options.mpd_params.default_text_language;
28 }
29 
30 AdaptationSet::Role RoleFromString(const std::string& role_str) {
31  if (role_str == "caption")
32  return AdaptationSet::Role::kRoleCaption;
33  if (role_str == "subtitle")
34  return AdaptationSet::Role::kRoleSubtitle;
35  if (role_str == "main")
36  return AdaptationSet::Role::kRoleMain;
37  if (role_str == "alternate")
38  return AdaptationSet::Role::kRoleAlternate;
39  if (role_str == "supplementary")
40  return AdaptationSet::Role::kRoleSupplementary;
41  if (role_str == "commentary")
42  return AdaptationSet::Role::kRoleCommentary;
43  if (role_str == "dub")
44  return AdaptationSet::Role::kRoleDub;
45  if (role_str == "forced-subtitle")
46  return AdaptationSet::Role::kRoleForcedSubtitle;
47  if (role_str == "karaoke")
48  return AdaptationSet::Role::kRoleKaraoke;
49  if (role_str == "sign")
50  return AdaptationSet::Role::kRoleSign;
51  if (role_str == "metadata")
52  return AdaptationSet::Role::kRoleMetadata;
53  if (role_str == "enhanced-audio-intelligibility")
54  return AdaptationSet::Role::kRoleEnhancedAudioIntelligibility;
55  if (role_str == "emergency")
56  return AdaptationSet::Role::kRoleEmergency;
57  if (role_str == "easyreader")
58  return AdaptationSet::Role::kRoleEasyreader;
59  if (role_str == "description")
60  return AdaptationSet::Role::kRoleDescription;
61  return AdaptationSet::Role::kRoleUnknown;
62 }
63 
64 } // namespace
65 
66 Period::Period(uint32_t period_id,
67  double start_time_in_seconds,
68  const MpdOptions& mpd_options,
69  uint32_t* representation_counter)
70  : id_(period_id),
71  start_time_in_seconds_(start_time_in_seconds),
72  mpd_options_(mpd_options),
73  representation_counter_(representation_counter) {}
74 
76  const MediaInfo& media_info,
77  bool content_protection_in_adaptation_set) {
78  // Set duration if it is not set. It may be updated later from duration
79  // calculated from segments.
80  if (duration_seconds_ == 0)
81  duration_seconds_ = media_info.media_duration_seconds();
82 
83  const std::string key = GetAdaptationSetKey(
84  media_info, mpd_options_.mpd_params.allow_codec_switching);
85 
86  std::list<AdaptationSet*>& adaptation_sets = adaptation_set_list_map_[key];
87 
88  for (AdaptationSet* adaptation_set : adaptation_sets) {
89  if (adaptation_set->MatchAdaptationSet(
90  media_info, content_protection_in_adaptation_set))
91  return adaptation_set;
92  }
93 
94  // None of the adaptation sets match with the new content protection.
95  // Need a new one.
96  const std::string language = GetLanguage(media_info);
97  std::unique_ptr<AdaptationSet> new_adaptation_set =
98  NewAdaptationSet(language, mpd_options_, representation_counter_);
99  if (!SetNewAdaptationSetAttributes(language, media_info, adaptation_sets,
100  content_protection_in_adaptation_set,
101  new_adaptation_set.get())) {
102  return nullptr;
103  }
104 
105  for (AdaptationSet* adaptation_set : adaptation_sets) {
106  if (adaptation_set->SwitchableAdaptationSet(*new_adaptation_set)) {
107  adaptation_set->AddAdaptationSetSwitching(new_adaptation_set.get());
108  new_adaptation_set->AddAdaptationSetSwitching(adaptation_set);
109  }
110  }
111 
112  AdaptationSet* adaptation_set_ptr = new_adaptation_set.get();
113  adaptation_sets.push_back(adaptation_set_ptr);
114  adaptation_sets_.emplace_back(std::move(new_adaptation_set));
115  return adaptation_set_ptr;
116 }
117 
118 std::optional<xml::XmlNode> Period::GetXml(bool output_period_duration) {
119  adaptation_sets_.sort(
120  [](const std::unique_ptr<AdaptationSet>& adaptation_set_a,
121  const std::unique_ptr<AdaptationSet>& adaptation_set_b) {
122  auto index_a = adaptation_set_a->SortIndex();
123  auto index_b = adaptation_set_b->SortIndex();
124 
125  if (!index_a)
126  return false;
127  if (!index_b)
128  return true;
129  return index_a < index_b;
130  });
131 
132  xml::XmlNode period("Period");
133 
134  // Required for 'dynamic' MPDs.
135  if (!period.SetId(id_))
136  return std::nullopt;
137 
138  // Required for LL-DASH MPDs.
139  if (mpd_options_.mpd_params.low_latency_dash_mode) {
140  // Create ServiceDescription element.
141  xml::XmlNode service_description_node("ServiceDescription");
142  if (!service_description_node.SetIntegerAttribute("id", id_))
143  return std::nullopt;
144 
145  // Insert Latency into ServiceDescription element.
146  xml::XmlNode latency_node("Latency");
147  uint64_t target_latency_ms =
148  mpd_options_.mpd_params.target_latency_seconds * 1000;
149  if (!latency_node.SetIntegerAttribute("target", target_latency_ms))
150  return std::nullopt;
151  if (!service_description_node.AddChild(std::move(latency_node)))
152  return std::nullopt;
153 
154  // Insert ServiceDescription into Period element.
155  if (!period.AddChild(std::move(service_description_node)))
156  return std::nullopt;
157  }
158 
159  // Iterate thru AdaptationSets and add them to one big Period element.
160  // Also force AdaptationSets Id to incremental order, which might not
161  // be the case if force_cl_index is used.
162  int idx = 0;
163  for (const auto& adaptation_set : adaptation_sets_) {
164  auto child = adaptation_set->GetXml();
165  if (!child || !child->SetId(idx++) || !period.AddChild(std::move(*child)))
166  return std::nullopt;
167  }
168 
169  if (output_period_duration) {
170  if (!period.SetStringAttribute("duration",
171  SecondsToXmlDuration(duration_seconds_))) {
172  return std::nullopt;
173  }
174  } else if (mpd_options_.mpd_type == MpdType::kDynamic) {
175  if (!period.SetStringAttribute(
176  "start", SecondsToXmlDuration(start_time_in_seconds_))) {
177  return std::nullopt;
178  }
179  }
180  return period;
181 }
182 
183 const std::list<AdaptationSet*> Period::GetAdaptationSets() const {
184  std::list<AdaptationSet*> adaptation_sets;
185  for (const auto& adaptation_set : adaptation_sets_) {
186  adaptation_sets.push_back(adaptation_set.get());
187  }
188  return adaptation_sets;
189 }
190 
191 std::unique_ptr<AdaptationSet> Period::NewAdaptationSet(
192  const std::string& language,
193  const MpdOptions& options,
194  uint32_t* representation_counter) {
195  return std::unique_ptr<AdaptationSet>(
196  new AdaptationSet(language, options, representation_counter));
197 }
198 
199 bool Period::SetNewAdaptationSetAttributes(
200  const std::string& language,
201  const MediaInfo& media_info,
202  const std::list<AdaptationSet*>& adaptation_sets,
203  bool content_protection_in_adaptation_set,
204  AdaptationSet* new_adaptation_set) {
205  if (!media_info.dash_roles().empty()) {
206  for (const std::string& role_str : media_info.dash_roles()) {
207  AdaptationSet::Role role = RoleFromString(role_str);
208  if (role == AdaptationSet::kRoleUnknown) {
209  LOG(ERROR) << "Unrecognized role '" << role_str << "'.";
210  return false;
211  }
212  new_adaptation_set->AddRole(role);
213  }
214  } else if (!language.empty()) {
215  const bool is_main_role =
216  language == (media_info.has_audio_info()
217  ? GetDefaultAudioLanguage(mpd_options_)
218  : GetDefaultTextLanguage(mpd_options_));
219  if (is_main_role)
220  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
221  }
222  for (const std::string& accessibility : media_info.dash_accessibilities()) {
223  size_t pos = accessibility.find('=');
224  if (pos == std::string::npos) {
225  LOG(ERROR)
226  << "Accessibility should be in scheme=value format, but seeing "
227  << accessibility;
228  return false;
229  }
230  new_adaptation_set->AddAccessibility(accessibility.substr(0, pos),
231  accessibility.substr(pos + 1));
232  }
233 
234  const std::string& codec = GetBaseCodec(media_info);
235  new_adaptation_set->set_codec(codec);
236 
237  if (media_info.has_video_info()) {
238  // Because 'language' is ignored for videos, |adaptation_sets| must have
239  // all the video AdaptationSets.
240  if (adaptation_sets.size() > 1) {
241  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
242  } else if (adaptation_sets.size() == 1) {
243  (*adaptation_sets.begin())->AddRole(AdaptationSet::kRoleMain);
244  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
245  }
246 
247  if (media_info.video_info().has_playback_rate()) {
248  std::string trick_play_reference_adaptation_set_key;
249  AdaptationSet* trick_play_reference_adaptation_set =
250  FindMatchingAdaptationSetForTrickPlay(
251  media_info, content_protection_in_adaptation_set,
252  &trick_play_reference_adaptation_set_key);
253  if (trick_play_reference_adaptation_set) {
254  new_adaptation_set->AddTrickPlayReference(
255  trick_play_reference_adaptation_set);
256  } else {
257  trickplay_cache_[trick_play_reference_adaptation_set_key].push_back(
258  new_adaptation_set);
259  }
260  } else {
261  std::string trick_play_adaptation_set_key;
262  AdaptationSet* trickplay_adaptation_set =
263  FindMatchingAdaptationSetForTrickPlay(
264  media_info, content_protection_in_adaptation_set,
265  &trick_play_adaptation_set_key);
266  if (trickplay_adaptation_set) {
267  trickplay_adaptation_set->AddTrickPlayReference(new_adaptation_set);
268  trickplay_cache_.erase(trick_play_adaptation_set_key);
269  }
270  }
271 
272  // Set transfer characteristics.
273  // https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf - 4.2.5.1
274  // ISO/IEC 23001-8 MPEG systems technologies — Part 8: Coding-independent
275  // code points. https://en.wikipedia.org/wiki/Coding-independent_code_points
276  // - Common CCIP values.
277  // Dolby vision:
278  // https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-DASH
279  // Transfer characteristics for Dolby Vision (dvh1 or dvhe) must be PQ
280  // irrespective of value present in SPS VUI.
281  if (new_adaptation_set->codec().find("dvh") == 0) {
282  new_adaptation_set->set_transfer_characteristics(kTransferFunctionPQ);
283  } else if (media_info.video_info().has_transfer_characteristics()) {
284  new_adaptation_set->set_transfer_characteristics(
285  media_info.video_info().transfer_characteristics());
286  }
287 
288  new_adaptation_set->set_matrix_coefficients(
289  media_info.video_info().matrix_coefficients());
290  new_adaptation_set->set_color_primaries(
291  media_info.video_info().color_primaries());
292  } else if (media_info.has_audio_info()) {
293  if (codec == "mp4a" || codec == "ac-3" || codec == "ec-3" ||
294  codec == "ac-4") {
295  if (mpd_options_.dash_profile == DashProfile::kLive) {
296  new_adaptation_set->ForceStartwithSAP(1);
297  } else if (mpd_options_.dash_profile == DashProfile::kOnDemand) {
298  new_adaptation_set->ForceSubsegmentStartswithSAP(1);
299  }
300  }
301  } else if (media_info.has_text_info()) {
302  // IOP requires all AdaptationSets to have (sub)segmentAlignment set to
303  // true, so carelessly set it to true.
304  // In practice it doesn't really make sense to adapt between text tracks.
305  new_adaptation_set->ForceSetSegmentAlignment(true);
306  }
307 
308  if (content_protection_in_adaptation_set &&
309  media_info.has_protected_content()) {
310  new_adaptation_set->set_protected_content(media_info);
311  AddContentProtectionElements(media_info, new_adaptation_set);
312  }
313 
314  return true;
315 }
316 
317 AdaptationSet* Period::FindMatchingAdaptationSetForTrickPlay(
318  const MediaInfo& media_info,
319  bool content_protection_in_adaptation_set,
320  std::string* adaptation_set_key) {
321  std::list<AdaptationSet*>* adaptation_sets = nullptr;
322  const bool is_trickplay_adaptation_set =
323  media_info.video_info().has_playback_rate();
324  if (is_trickplay_adaptation_set) {
325  *adaptation_set_key = GetAdaptationSetKeyForTrickPlay(media_info);
326  if (adaptation_set_list_map_.find(*adaptation_set_key) ==
327  adaptation_set_list_map_.end())
328  return nullptr;
329  adaptation_sets = &adaptation_set_list_map_[*adaptation_set_key];
330  } else {
331  *adaptation_set_key = GetAdaptationSetKey(
332  media_info, mpd_options_.mpd_params.allow_codec_switching);
333  if (trickplay_cache_.find(*adaptation_set_key) == trickplay_cache_.end())
334  return nullptr;
335  adaptation_sets = &trickplay_cache_[*adaptation_set_key];
336  }
337  for (AdaptationSet* adaptation_set : *adaptation_sets) {
338  if (adaptation_set->MatchAdaptationSet(
339  media_info, content_protection_in_adaptation_set))
340  return adaptation_set;
341  }
342 
343  return nullptr;
344 }
345 
346 std::string Period::GetAdaptationSetKeyForTrickPlay(
347  const MediaInfo& media_info) {
348  MediaInfo media_info_no_trickplay = media_info;
349  media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
350  return GetAdaptationSetKey(media_info_no_trickplay,
351  mpd_options_.mpd_params.allow_codec_switching);
352 }
353 
354 Period::~Period() {
355  if (!trickplay_cache_.empty()) {
356  LOG(WARNING) << "Trickplay adaptation set did not get a valid adaptation "
357  "set match. Please check the command line options.";
358  }
359 }
360 
361 } // namespace shaka
virtual AdaptationSet * GetOrCreateAdaptationSet(const MediaInfo &media_info, bool content_protection_in_adaptation_set)
Definition: period.cc:75
std::optional< xml::XmlNode > GetXml(bool output_period_duration)
Definition: period.cc:118
Period(uint32_t period_id, double start_time_in_seconds, const MpdOptions &mpd_options, uint32_t *representation_counter)
Definition: period.cc:66
const std::list< AdaptationSet * > GetAdaptationSets() const
Definition: period.cc:183
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
void AddContentProtectionElements(const MediaInfo &media_info, Representation *parent)
Definition: mpd_utils.cc:523
Defines Mpd Options.
Definition: mpd_options.h:25