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