Shaka Packager SDK
period.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/period.h"
8 
9 #include "packager/base/stl_util.h"
10 #include "packager/mpd/base/adaptation_set.h"
11 #include "packager/mpd/base/mpd_options.h"
12 #include "packager/mpd/base/mpd_utils.h"
13 #include "packager/mpd/base/xml/xml_node.h"
14 
15 namespace shaka {
16 namespace {
17 
18 // The easiest way to check whether two protobufs are equal, is to compare the
19 // serialized version.
20 bool ProtectedContentEq(
21  const MediaInfo::ProtectedContent& content_protection1,
22  const MediaInfo::ProtectedContent& content_protection2) {
23  return content_protection1.SerializeAsString() ==
24  content_protection2.SerializeAsString();
25 }
26 
27 std::set<std::string> GetUUIDs(
28  const MediaInfo::ProtectedContent& protected_content) {
29  std::set<std::string> uuids;
30  for (const auto& entry : protected_content.content_protection_entry())
31  uuids.insert(entry.uuid());
32  return uuids;
33 }
34 
35 const std::string& GetDefaultAudioLanguage(const MpdOptions& mpd_options) {
36  return mpd_options.mpd_params.default_language;
37 }
38 
39 const std::string& GetDefaultTextLanguage(const MpdOptions& mpd_options) {
40  return mpd_options.mpd_params.default_text_language.empty()
41  ? mpd_options.mpd_params.default_language
42  : mpd_options.mpd_params.default_text_language;
43 }
44 
45 AdaptationSet::Role RoleFromString(const std::string& role_str) {
46  if (role_str == "caption")
47  return AdaptationSet::Role::kRoleCaption;
48  if (role_str == "subtitle")
49  return AdaptationSet::Role::kRoleSubtitle;
50  if (role_str == "main")
51  return AdaptationSet::Role::kRoleMain;
52  if (role_str == "alternate")
53  return AdaptationSet::Role::kRoleAlternate;
54  if (role_str == "supplementary")
55  return AdaptationSet::Role::kRoleSupplementary;
56  if (role_str == "commentary")
57  return AdaptationSet::Role::kRoleCommentary;
58  if (role_str == "dub")
59  return AdaptationSet::Role::kRoleDub;
60  return AdaptationSet::Role::kRoleUnknown;
61 }
62 
63 } // namespace
64 
65 Period::Period(uint32_t period_id,
66  double start_time_in_seconds,
67  const MpdOptions& mpd_options,
68  uint32_t* representation_counter)
69  : id_(period_id),
70  start_time_in_seconds_(start_time_in_seconds),
71  mpd_options_(mpd_options),
72  representation_counter_(representation_counter) {}
73 
75  const MediaInfo& media_info,
76  bool content_protection_in_adaptation_set) {
77  // Set duration if it is not set. It may be updated later from duration
78  // calculated from segments.
79  if (duration_seconds_ == 0)
80  duration_seconds_ = media_info.media_duration_seconds();
81 
82  const std::string key = GetAdaptationSetKey(
83  media_info, mpd_options_.mpd_params.allow_codec_switching);
84 
85  std::list<AdaptationSet*>& adaptation_sets = adaptation_set_list_map_[key];
86 
87  for (AdaptationSet* adaptation_set : adaptation_sets) {
88  if (protected_adaptation_set_map_.Match(
89  *adaptation_set, media_info, content_protection_in_adaptation_set))
90  return adaptation_set;
91  }
92 
93  // None of the adaptation sets match with the new content protection.
94  // Need a new one.
95  const std::string language = GetLanguage(media_info);
96  std::unique_ptr<AdaptationSet> new_adaptation_set =
97  NewAdaptationSet(language, mpd_options_, representation_counter_);
98  if (!SetNewAdaptationSetAttributes(language, media_info, adaptation_sets,
99  content_protection_in_adaptation_set,
100  new_adaptation_set.get())) {
101  return nullptr;
102  }
103 
104  if (content_protection_in_adaptation_set &&
105  media_info.has_protected_content()) {
106  protected_adaptation_set_map_.Register(*new_adaptation_set, media_info);
107  AddContentProtectionElements(media_info, new_adaptation_set.get());
108  }
109  for (AdaptationSet* adaptation_set : adaptation_sets) {
110  if (protected_adaptation_set_map_.Switchable(*adaptation_set,
111  *new_adaptation_set)) {
112  adaptation_set->AddAdaptationSetSwitching(new_adaptation_set.get());
113  new_adaptation_set->AddAdaptationSetSwitching(adaptation_set);
114  }
115  }
116 
117  AdaptationSet* adaptation_set_ptr = new_adaptation_set.get();
118  adaptation_sets.push_back(adaptation_set_ptr);
119  adaptation_sets_.emplace_back(std::move(new_adaptation_set));
120  return adaptation_set_ptr;
121 }
122 
123 base::Optional<xml::XmlNode> Period::GetXml(bool output_period_duration) {
124  adaptation_sets_.sort(
125  [](const std::unique_ptr<AdaptationSet>& adaptation_set_a,
126  const std::unique_ptr<AdaptationSet>& adaptation_set_b) {
127  if (!adaptation_set_a->has_id())
128  return false;
129  if (!adaptation_set_b->has_id())
130  return true;
131  return adaptation_set_a->id() < adaptation_set_b->id();
132  });
133 
134  xml::XmlNode period("Period");
135 
136  // Required for 'dynamic' MPDs.
137  if (!period.SetId(id_))
138  return base::nullopt;
139  // Iterate thru AdaptationSets and add them to one big Period element.
140  for (const auto& adaptation_set : adaptation_sets_) {
141  auto child = adaptation_set->GetXml();
142  if (!child || !period.AddChild(std::move(*child)))
143  return base::nullopt;
144  }
145 
146  if (output_period_duration) {
147  if (!period.SetStringAttribute("duration",
148  SecondsToXmlDuration(duration_seconds_))) {
149  return base::nullopt;
150  }
151  } else if (mpd_options_.mpd_type == MpdType::kDynamic) {
152  if (!period.SetStringAttribute(
153  "start", SecondsToXmlDuration(start_time_in_seconds_))) {
154  return base::nullopt;
155  }
156  }
157  return period;
158 }
159 
160 const std::list<AdaptationSet*> Period::GetAdaptationSets() const {
161  std::list<AdaptationSet*> adaptation_sets;
162  for (const auto& adaptation_set : adaptation_sets_) {
163  adaptation_sets.push_back(adaptation_set.get());
164  }
165  return adaptation_sets;
166 }
167 
168 std::unique_ptr<AdaptationSet> Period::NewAdaptationSet(
169  const std::string& language,
170  const MpdOptions& options,
171  uint32_t* representation_counter) {
172  return std::unique_ptr<AdaptationSet>(
173  new AdaptationSet(language, options, representation_counter));
174 }
175 
176 bool Period::SetNewAdaptationSetAttributes(
177  const std::string& language,
178  const MediaInfo& media_info,
179  const std::list<AdaptationSet*>& adaptation_sets,
180  bool content_protection_in_adaptation_set,
181  AdaptationSet* new_adaptation_set) {
182  if (!media_info.dash_roles().empty()) {
183  for (const std::string& role_str : media_info.dash_roles()) {
184  AdaptationSet::Role role = RoleFromString(role_str);
185  if (role == AdaptationSet::kRoleUnknown) {
186  LOG(ERROR) << "Unrecognized role '" << role_str << "'.";
187  return false;
188  }
189  new_adaptation_set->AddRole(role);
190  }
191  } else if (!language.empty()) {
192  const bool is_main_role =
193  language == (media_info.has_audio_info()
194  ? GetDefaultAudioLanguage(mpd_options_)
195  : GetDefaultTextLanguage(mpd_options_));
196  if (is_main_role)
197  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
198  }
199  for (const std::string& accessibility : media_info.dash_accessibilities()) {
200  size_t pos = accessibility.find('=');
201  if (pos == std::string::npos) {
202  LOG(ERROR)
203  << "Accessibility should be in scheme=value format, but seeing "
204  << accessibility;
205  return false;
206  }
207  new_adaptation_set->AddAccessibility(accessibility.substr(0, pos),
208  accessibility.substr(pos + 1));
209  }
210 
211  new_adaptation_set->set_codec(GetBaseCodec(media_info));
212 
213  if (media_info.has_video_info()) {
214  // Because 'language' is ignored for videos, |adaptation_sets| must have
215  // all the video AdaptationSets.
216  if (adaptation_sets.size() > 1) {
217  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
218  } else if (adaptation_sets.size() == 1) {
219  (*adaptation_sets.begin())->AddRole(AdaptationSet::kRoleMain);
220  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
221  }
222 
223  if (media_info.video_info().has_playback_rate()) {
224  std::string trick_play_reference_adaptation_set_key;
225  AdaptationSet* trick_play_reference_adaptation_set =
226  FindMatchingAdaptationSetForTrickPlay(
227  media_info, content_protection_in_adaptation_set,
228  &trick_play_reference_adaptation_set_key);
229  if (trick_play_reference_adaptation_set) {
230  new_adaptation_set->AddTrickPlayReference(
231  trick_play_reference_adaptation_set);
232  } else {
233  trickplay_cache_[trick_play_reference_adaptation_set_key].push_back(
234  new_adaptation_set);
235  }
236  } else {
237  std::string trick_play_adaptation_set_key;
238  AdaptationSet* trickplay_adaptation_set =
239  FindMatchingAdaptationSetForTrickPlay(
240  media_info, content_protection_in_adaptation_set,
241  &trick_play_adaptation_set_key);
242  if (trickplay_adaptation_set) {
243  trickplay_adaptation_set->AddTrickPlayReference(new_adaptation_set);
244  trickplay_cache_.erase(trick_play_adaptation_set_key);
245  }
246  }
247 
248  } else if (media_info.has_text_info()) {
249  // IOP requires all AdaptationSets to have (sub)segmentAlignment set to
250  // true, so carelessly set it to true.
251  // In practice it doesn't really make sense to adapt between text tracks.
252  new_adaptation_set->ForceSetSegmentAlignment(true);
253  }
254  return true;
255 }
256 
257 AdaptationSet* Period::FindMatchingAdaptationSetForTrickPlay(
258  const MediaInfo& media_info,
259  bool content_protection_in_adaptation_set,
260  std::string* adaptation_set_key) {
261  std::list<AdaptationSet*>* adaptation_sets = nullptr;
262  const bool is_trickplay_adaptation_set =
263  media_info.video_info().has_playback_rate();
264  if (is_trickplay_adaptation_set) {
265  *adaptation_set_key = GetAdaptationSetKeyForTrickPlay(media_info);
266  if (adaptation_set_list_map_.find(*adaptation_set_key) ==
267  adaptation_set_list_map_.end())
268  return nullptr;
269  adaptation_sets = &adaptation_set_list_map_[*adaptation_set_key];
270  } else {
271  *adaptation_set_key = GetAdaptationSetKey(
272  media_info, mpd_options_.mpd_params.allow_codec_switching);
273  if (trickplay_cache_.find(*adaptation_set_key) == trickplay_cache_.end())
274  return nullptr;
275  adaptation_sets = &trickplay_cache_[*adaptation_set_key];
276  }
277  for (AdaptationSet* adaptation_set : *adaptation_sets) {
278  if (protected_adaptation_set_map_.Match(
279  *adaptation_set, media_info,
280  content_protection_in_adaptation_set)) {
281  return adaptation_set;
282  }
283  }
284 
285  return nullptr;
286 }
287 
288 std::string Period::GetAdaptationSetKeyForTrickPlay(
289  const MediaInfo& media_info) {
290  MediaInfo media_info_no_trickplay = media_info;
291  media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
292  return GetAdaptationSetKey(media_info_no_trickplay,
293  mpd_options_.mpd_params.allow_codec_switching);
294 }
295 
296 void Period::ProtectedAdaptationSetMap::Register(
297  const AdaptationSet& adaptation_set,
298  const MediaInfo& media_info) {
299  DCHECK(!ContainsKey(protected_content_map_, &adaptation_set));
300  protected_content_map_[&adaptation_set] = media_info.protected_content();
301 }
302 
303 bool Period::ProtectedAdaptationSetMap::Match(
304  const AdaptationSet& adaptation_set,
305  const MediaInfo& media_info,
306  bool content_protection_in_adaptation_set) {
307  if (adaptation_set.codec() != GetBaseCodec(media_info))
308  return false;
309 
310  if (!content_protection_in_adaptation_set)
311  return true;
312 
313  const auto protected_content_it =
314  protected_content_map_.find(&adaptation_set);
315  // If the AdaptationSet ID is not registered in the map, then it is clear
316  // content.
317  if (protected_content_it == protected_content_map_.end())
318  return !media_info.has_protected_content();
319  if (!media_info.has_protected_content())
320  return false;
321 
322  return ProtectedContentEq(protected_content_it->second,
323  media_info.protected_content());
324 }
325 
326 bool Period::ProtectedAdaptationSetMap::Switchable(
327  const AdaptationSet& adaptation_set_a,
328  const AdaptationSet& adaptation_set_b) {
329  const auto protected_content_it_a =
330  protected_content_map_.find(&adaptation_set_a);
331  const auto protected_content_it_b =
332  protected_content_map_.find(&adaptation_set_b);
333 
334  if (protected_content_it_a == protected_content_map_.end())
335  return protected_content_it_b == protected_content_map_.end();
336  if (protected_content_it_b == protected_content_map_.end())
337  return false;
338  // Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the
339  // same UUIDs then those are switchable.
340  return GetUUIDs(protected_content_it_a->second) ==
341  GetUUIDs(protected_content_it_b->second);
342 }
343 
344 Period::~Period() {
345  if (!trickplay_cache_.empty()) {
346  LOG(WARNING) << "Trickplay adaptation set did not get a valid adaptation "
347  "set match. Please check the command line options.";
348  }
349 }
350 
351 } // namespace shaka
virtual AdaptationSet * GetOrCreateAdaptationSet(const MediaInfo &media_info, bool content_protection_in_adaptation_set)
Definition: period.cc:74
Period(uint32_t period_id, double start_time_in_seconds, const MpdOptions &mpd_options, uint32_t *representation_counter)
Definition: period.cc:65
base::Optional< xml::XmlNode > GetXml(bool output_period_duration)
Definition: period.cc:123
const std::list< AdaptationSet * > GetAdaptationSets() const
Definition: period.cc:160
bool AddChild(XmlNode child) WARN_UNUSED_RESULT
Definition: xml_node.cc:140
bool SetStringAttribute(const std::string &attribute_name, const std::string &attribute) WARN_UNUSED_RESULT
Definition: xml_node.cc:183
bool SetId(uint32_t id) WARN_UNUSED_RESULT
Definition: xml_node.cc:204
All the methods that are virtual are virtual for mocking.
void AddContentProtectionElements(const MediaInfo &media_info, Representation *parent)
Definition: mpd_utils.cc:473
Defines Mpd Options.
Definition: mpd_options.h:25
bool allow_codec_switching
Definition: mpd_params.h:85