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
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
17namespace shaka {
18namespace {
19
20const std::string& GetDefaultAudioLanguage(const MpdOptions& mpd_options) {
21 return mpd_options.mpd_params.default_language;
22}
23
24const 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
30AdaptationSet::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
66Period::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
118std::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 (auto& adaptation_set : adaptation_sets_) {
164 adaptation_set->set_id(idx++);
165 }
166
167 for (const auto& adaptation_set : adaptation_sets_) {
168 auto child = adaptation_set->GetXml();
169 if (!child || !period.AddChild(std::move(*child)))
170 return std::nullopt;
171 }
172
173 if (output_period_duration) {
174 if (!period.SetStringAttribute("duration",
175 SecondsToXmlDuration(duration_seconds_))) {
176 return std::nullopt;
177 }
178 } else if (mpd_options_.mpd_type == MpdType::kDynamic) {
179 if (!period.SetStringAttribute(
180 "start", SecondsToXmlDuration(start_time_in_seconds_))) {
181 return std::nullopt;
182 }
183 }
184 return period;
185}
186
187const std::list<AdaptationSet*> Period::GetAdaptationSets() const {
188 std::list<AdaptationSet*> adaptation_sets;
189 for (const auto& adaptation_set : adaptation_sets_) {
190 adaptation_sets.push_back(adaptation_set.get());
191 }
192 return adaptation_sets;
193}
194
195std::unique_ptr<AdaptationSet> Period::NewAdaptationSet(
196 const std::string& language,
197 const MpdOptions& options,
198 uint32_t* representation_counter) {
199 return std::unique_ptr<AdaptationSet>(
200 new AdaptationSet(language, options, representation_counter));
201}
202
203bool Period::SetNewAdaptationSetAttributes(
204 const std::string& language,
205 const MediaInfo& media_info,
206 const std::list<AdaptationSet*>& adaptation_sets,
207 bool content_protection_in_adaptation_set,
208 AdaptationSet* new_adaptation_set) {
209 if (!media_info.dash_roles().empty()) {
210 for (const std::string& role_str : media_info.dash_roles()) {
211 AdaptationSet::Role role = RoleFromString(role_str);
212 if (role == AdaptationSet::kRoleUnknown) {
213 LOG(ERROR) << "Unrecognized role '" << role_str << "'.";
214 return false;
215 }
216 new_adaptation_set->AddRole(role);
217 }
218 } else if (!language.empty()) {
219 const bool is_main_role =
220 language == (media_info.has_audio_info()
221 ? GetDefaultAudioLanguage(mpd_options_)
222 : GetDefaultTextLanguage(mpd_options_));
223 if (is_main_role)
224 new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
225 }
226 for (const std::string& accessibility : media_info.dash_accessibilities()) {
227 size_t pos = accessibility.find('=');
228 if (pos == std::string::npos) {
229 LOG(ERROR)
230 << "Accessibility should be in scheme=value format, but seeing "
231 << accessibility;
232 return false;
233 }
234 new_adaptation_set->AddAccessibility(accessibility.substr(0, pos),
235 accessibility.substr(pos + 1));
236 }
237
238 const std::string& codec = GetBaseCodec(media_info);
239 new_adaptation_set->set_codec(codec);
240
241 if (media_info.has_video_info()) {
242 // Because 'language' is ignored for videos, |adaptation_sets| must have
243 // all the video AdaptationSets.
244 if (adaptation_sets.size() > 1) {
245 new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
246 } else if (adaptation_sets.size() == 1) {
247 (*adaptation_sets.begin())->AddRole(AdaptationSet::kRoleMain);
248 new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
249 }
250
251 if (media_info.video_info().has_playback_rate()) {
252 std::string trick_play_reference_adaptation_set_key;
253 AdaptationSet* trick_play_reference_adaptation_set =
254 FindMatchingAdaptationSetForTrickPlay(
255 media_info, content_protection_in_adaptation_set,
256 &trick_play_reference_adaptation_set_key);
257 if (trick_play_reference_adaptation_set) {
258 new_adaptation_set->AddTrickPlayReference(
259 trick_play_reference_adaptation_set);
260 } else {
261 trickplay_cache_[trick_play_reference_adaptation_set_key].push_back(
262 new_adaptation_set);
263 }
264 } else {
265 std::string trick_play_adaptation_set_key;
266 AdaptationSet* trickplay_adaptation_set =
267 FindMatchingAdaptationSetForTrickPlay(
268 media_info, content_protection_in_adaptation_set,
269 &trick_play_adaptation_set_key);
270 if (trickplay_adaptation_set) {
271 trickplay_adaptation_set->AddTrickPlayReference(new_adaptation_set);
272 trickplay_cache_.erase(trick_play_adaptation_set_key);
273 }
274 }
275
276 // Set transfer characteristics.
277 // https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf - 4.2.5.1
278 // ISO/IEC 23001-8 MPEG systems technologies — Part 8: Coding-independent
279 // code points. https://en.wikipedia.org/wiki/Coding-independent_code_points
280 // - Common CCIP values.
281 // Dolby vision:
282 // https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-DASH
283 // Transfer characteristics for Dolby Vision (dvh1 or dvhe) must be PQ
284 // irrespective of value present in SPS VUI.
285 if (new_adaptation_set->codec().find("dvh") == 0) {
286 new_adaptation_set->set_transfer_characteristics(kTransferFunctionPQ);
287 } else if (media_info.video_info().has_transfer_characteristics()) {
288 new_adaptation_set->set_transfer_characteristics(
289 media_info.video_info().transfer_characteristics());
290 }
291
292 new_adaptation_set->set_matrix_coefficients(
293 media_info.video_info().matrix_coefficients());
294 new_adaptation_set->set_color_primaries(
295 media_info.video_info().color_primaries());
296 } else if (media_info.has_audio_info()) {
297 if (codec == "mp4a" || codec == "ac-3" || codec == "ec-3" ||
298 codec == "ac-4") {
299 if (mpd_options_.dash_profile == DashProfile::kLive) {
300 new_adaptation_set->ForceStartwithSAP(1);
301 } else if (mpd_options_.dash_profile == DashProfile::kOnDemand) {
302 new_adaptation_set->ForceSubsegmentStartswithSAP(1);
303 }
304 }
305 } else if (media_info.has_text_info()) {
306 // IOP requires all AdaptationSets to have (sub)segmentAlignment set to
307 // true, so carelessly set it to true.
308 // In practice it doesn't really make sense to adapt between text tracks.
309 new_adaptation_set->ForceSetSegmentAlignment(true);
310 }
311
312 if (content_protection_in_adaptation_set &&
313 media_info.has_protected_content()) {
314 new_adaptation_set->set_protected_content(media_info);
315 AddContentProtectionElements(media_info, new_adaptation_set);
316 }
317
318 return true;
319}
320
321AdaptationSet* Period::FindMatchingAdaptationSetForTrickPlay(
322 const MediaInfo& media_info,
323 bool content_protection_in_adaptation_set,
324 std::string* adaptation_set_key) {
325 std::list<AdaptationSet*>* adaptation_sets = nullptr;
326 const bool is_trickplay_adaptation_set =
327 media_info.video_info().has_playback_rate();
328 if (is_trickplay_adaptation_set) {
329 *adaptation_set_key = GetAdaptationSetKeyForTrickPlay(media_info);
330 if (adaptation_set_list_map_.find(*adaptation_set_key) ==
331 adaptation_set_list_map_.end())
332 return nullptr;
333 adaptation_sets = &adaptation_set_list_map_[*adaptation_set_key];
334 } else {
335 *adaptation_set_key = GetAdaptationSetKey(
336 media_info, mpd_options_.mpd_params.allow_codec_switching);
337 if (trickplay_cache_.find(*adaptation_set_key) == trickplay_cache_.end())
338 return nullptr;
339 adaptation_sets = &trickplay_cache_[*adaptation_set_key];
340 }
341 for (AdaptationSet* adaptation_set : *adaptation_sets) {
342 if (adaptation_set->MatchAdaptationSet(
343 media_info, content_protection_in_adaptation_set))
344 return adaptation_set;
345 }
346
347 return nullptr;
348}
349
350std::string Period::GetAdaptationSetKeyForTrickPlay(
351 const MediaInfo& media_info) {
352 MediaInfo media_info_no_trickplay = media_info;
353 media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
354 return GetAdaptationSetKey(media_info_no_trickplay,
355 mpd_options_.mpd_params.allow_codec_switching);
356}
357
358Period::~Period() {
359 if (!trickplay_cache_.empty()) {
360 LOG(WARNING) << "Trickplay adaptation set did not get a valid adaptation "
361 "set match. Please check the command line options.";
362 }
363}
364
365} // 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:187
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:523
Defines Mpd Options.
Definition mpd_options.h:25