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  // AdaptationSets with the same key should only differ in ContentProtection,
83  // which also means that if |content_protection_in_adaptation_set| is false,
84  // there should be at most one entry in |adaptation_sets|.
85  const std::string key = GetAdaptationSetKey(media_info);
86  std::list<AdaptationSet*>& adaptation_sets = adaptation_set_list_map_[key];
87  if (content_protection_in_adaptation_set) {
88  for (AdaptationSet* adaptation_set : adaptation_sets) {
89  if (protected_adaptation_set_map_.Match(*adaptation_set, media_info))
90  return adaptation_set;
91  }
92  } else {
93  if (!adaptation_sets.empty()) {
94  DCHECK_EQ(adaptation_sets.size(), 1u);
95  return adaptation_sets.front();
96  }
97  }
98  // None of the adaptation sets match with the new content protection.
99  // Need a new one.
100  const std::string language = GetLanguage(media_info);
101  std::unique_ptr<AdaptationSet> new_adaptation_set =
102  NewAdaptationSet(language, mpd_options_, representation_counter_);
103  if (!SetNewAdaptationSetAttributes(language, media_info, adaptation_sets,
104  new_adaptation_set.get())) {
105  return nullptr;
106  }
107 
108  if (content_protection_in_adaptation_set &&
109  media_info.has_protected_content()) {
110  protected_adaptation_set_map_.Register(*new_adaptation_set, media_info);
111  AddContentProtectionElements(media_info, new_adaptation_set.get());
112 
113  for (AdaptationSet* adaptation_set : adaptation_sets) {
114  if (protected_adaptation_set_map_.Switchable(*adaptation_set,
115  *new_adaptation_set)) {
116  adaptation_set->AddAdaptationSetSwitching(new_adaptation_set.get());
117  new_adaptation_set->AddAdaptationSetSwitching(adaptation_set);
118  }
119  }
120  }
121  AdaptationSet* adaptation_set_ptr = new_adaptation_set.get();
122  adaptation_sets.push_back(adaptation_set_ptr);
123  adaptation_sets_.emplace_back(std::move(new_adaptation_set));
124  return adaptation_set_ptr;
125 }
126 
127 xml::scoped_xml_ptr<xmlNode> Period::GetXml(bool output_period_duration) {
128  adaptation_sets_.sort(
129  [](const std::unique_ptr<AdaptationSet>& adaptation_set_a,
130  const std::unique_ptr<AdaptationSet>& adaptation_set_b) {
131  if (!adaptation_set_a->has_id())
132  return false;
133  if (!adaptation_set_b->has_id())
134  return true;
135  return adaptation_set_a->id() < adaptation_set_b->id();
136  });
137 
138  xml::XmlNode period("Period");
139 
140  // Required for 'dynamic' MPDs.
141  period.SetId(id_);
142  // Iterate thru AdaptationSets and add them to one big Period element.
143  for (const auto& adaptation_set : adaptation_sets_) {
144  xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
145  if (!child || !period.AddChild(std::move(child)))
146  return nullptr;
147  }
148 
149  if (output_period_duration) {
150  period.SetStringAttribute("duration",
151  SecondsToXmlDuration(duration_seconds_));
152  } else if (mpd_options_.mpd_type == MpdType::kDynamic) {
153  period.SetStringAttribute("start",
154  SecondsToXmlDuration(start_time_in_seconds_));
155  }
156  return period.PassScopedPtr();
157 }
158 
159 const std::list<AdaptationSet*> Period::GetAdaptationSets() const {
160  std::list<AdaptationSet*> adaptation_sets;
161  for (const auto& adaptation_set : adaptation_sets_) {
162  adaptation_sets.push_back(adaptation_set.get());
163  }
164  return adaptation_sets;
165 }
166 
167 std::unique_ptr<AdaptationSet> Period::NewAdaptationSet(
168  const std::string& language,
169  const MpdOptions& options,
170  uint32_t* representation_counter) {
171  return std::unique_ptr<AdaptationSet>(
172  new AdaptationSet(language, options, representation_counter));
173 }
174 
175 bool Period::SetNewAdaptationSetAttributes(
176  const std::string& language,
177  const MediaInfo& media_info,
178  const std::list<AdaptationSet*>& adaptation_sets,
179  AdaptationSet* new_adaptation_set) {
180  if (!media_info.dash_roles().empty()) {
181  for (const std::string& role_str : media_info.dash_roles()) {
182  AdaptationSet::Role role = RoleFromString(role_str);
183  if (role == AdaptationSet::kRoleUnknown) {
184  LOG(ERROR) << "Unrecognized role '" << role_str << "'.";
185  return false;
186  }
187  new_adaptation_set->AddRole(role);
188  }
189  } else if (!language.empty()) {
190  const bool is_main_role =
191  language == (media_info.has_audio_info()
192  ? GetDefaultAudioLanguage(mpd_options_)
193  : GetDefaultTextLanguage(mpd_options_));
194  if (is_main_role)
195  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
196  }
197  for (const std::string& accessibility : media_info.dash_accessibilities()) {
198  size_t pos = accessibility.find('=');
199  if (pos == std::string::npos) {
200  LOG(ERROR)
201  << "Accessibility should be in scheme=value format, but seeing "
202  << accessibility;
203  return false;
204  }
205  new_adaptation_set->AddAccessibility(accessibility.substr(0, pos),
206  accessibility.substr(pos + 1));
207  }
208 
209  if (media_info.has_video_info()) {
210  // Because 'language' is ignored for videos, |adaptation_sets| must have
211  // all the video AdaptationSets.
212  if (adaptation_sets.size() > 1) {
213  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
214  } else if (adaptation_sets.size() == 1) {
215  (*adaptation_sets.begin())->AddRole(AdaptationSet::kRoleMain);
216  new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
217  }
218 
219  if (media_info.video_info().has_playback_rate()) {
220  const AdaptationSet* trick_play_reference_adaptation_set =
221  FindOriginalAdaptationSetForTrickPlay(media_info);
222  if (!trick_play_reference_adaptation_set) {
223  LOG(ERROR) << "Failed to find original AdaptationSet for trick play.";
224  return false;
225  }
226  new_adaptation_set->AddTrickPlayReference(
227  trick_play_reference_adaptation_set);
228  }
229  } else if (media_info.has_text_info()) {
230  // IOP requires all AdaptationSets to have (sub)segmentAlignment set to
231  // true, so carelessly set it to true.
232  // In practice it doesn't really make sense to adapt between text tracks.
233  new_adaptation_set->ForceSetSegmentAlignment(true);
234  }
235  return true;
236 }
237 
238 const AdaptationSet* Period::FindOriginalAdaptationSetForTrickPlay(
239  const MediaInfo& media_info) {
240  MediaInfo media_info_no_trickplay = media_info;
241  media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
242 
243  std::string key = GetAdaptationSetKey(media_info_no_trickplay);
244  const std::list<AdaptationSet*>& adaptation_sets =
245  adaptation_set_list_map_[key];
246  for (AdaptationSet* adaptation_set : adaptation_sets) {
247  if (protected_adaptation_set_map_.Match(*adaptation_set, media_info)) {
248  return adaptation_set;
249  }
250  }
251  return nullptr;
252 }
253 
254 void Period::ProtectedAdaptationSetMap::Register(
255  const AdaptationSet& adaptation_set,
256  const MediaInfo& media_info) {
257  DCHECK(!ContainsKey(protected_content_map_, &adaptation_set));
258  protected_content_map_[&adaptation_set] = media_info.protected_content();
259 }
260 
261 bool Period::ProtectedAdaptationSetMap::Match(
262  const AdaptationSet& adaptation_set,
263  const MediaInfo& media_info) {
264  const auto protected_content_it =
265  protected_content_map_.find(&adaptation_set);
266  // If the AdaptationSet ID is not registered in the map, then it is clear
267  // content.
268  if (protected_content_it == protected_content_map_.end())
269  return !media_info.has_protected_content();
270  if (!media_info.has_protected_content())
271  return false;
272  return ProtectedContentEq(protected_content_it->second,
273  media_info.protected_content());
274 }
275 
276 bool Period::ProtectedAdaptationSetMap::Switchable(
277  const AdaptationSet& adaptation_set_a,
278  const AdaptationSet& adaptation_set_b) {
279  const auto protected_content_it_a =
280  protected_content_map_.find(&adaptation_set_a);
281  const auto protected_content_it_b =
282  protected_content_map_.find(&adaptation_set_b);
283 
284  if (protected_content_it_a == protected_content_map_.end())
285  return protected_content_it_b == protected_content_map_.end();
286  if (protected_content_it_b == protected_content_map_.end())
287  return false;
288  // Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the
289  // same UUIDs then those are switchable.
290  return GetUUIDs(protected_content_it_a->second) ==
291  GetUUIDs(protected_content_it_b->second);
292 }
293 
294 } // namespace shaka
virtual AdaptationSet * GetOrCreateAdaptationSet(const MediaInfo &media_info, bool content_protection_in_adaptation_set)
Definition: period.cc:74
scoped_xml_ptr< xmlNode > PassScopedPtr()
Definition: xml_node.cc:204
All the methods that are virtual are virtual for mocking.
void SetStringAttribute(const char *attribute_name, const std::string &attribute)
Definition: xml_node.cc:166
bool AddChild(scoped_xml_ptr< xmlNode > child)
Definition: xml_node.cc:121
virtual void AddRole(Role role)
xml::scoped_xml_ptr< xmlNode > GetXml(bool output_period_duration)
Definition: period.cc:127
void AddContentProtectionElements(const MediaInfo &media_info, Representation *parent)
Definition: mpd_utils.cc:436
void SetId(uint32_t id)
Definition: xml_node.cc:189
virtual void ForceSetSegmentAlignment(bool segment_alignment)
Period(uint32_t period_id, double start_time_in_seconds, const MpdOptions &mpd_options, uint32_t *representation_counter)
Definition: period.cc:65
virtual void AddAccessibility(const std::string &scheme, const std::string &value)
const std::list< AdaptationSet * > GetAdaptationSets() const
Definition: period.cc:159
Defines Mpd Options.
Definition: mpd_options.h:25
virtual void AddTrickPlayReference(const AdaptationSet *adaptation_set)