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