/src/ffmpeg/libavformat/dashdec.c
Line | Count | Source |
1 | | /* |
2 | | * Dynamic Adaptive Streaming over HTTP demux |
3 | | * Copyright (c) 2017 samsamsam@o2.pl based on HLS demux |
4 | | * Copyright (c) 2017 Steven Liu |
5 | | * |
6 | | * This file is part of FFmpeg. |
7 | | * |
8 | | * FFmpeg is free software; you can redistribute it and/or |
9 | | * modify it under the terms of the GNU Lesser General Public |
10 | | * License as published by the Free Software Foundation; either |
11 | | * version 2.1 of the License, or (at your option) any later version. |
12 | | * |
13 | | * FFmpeg is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | | * Lesser General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public |
19 | | * License along with FFmpeg; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
21 | | */ |
22 | | #include <libxml/parser.h> |
23 | | #include <time.h> |
24 | | #include "libavutil/avassert.h" |
25 | | #include "libavutil/bprint.h" |
26 | | #include "libavutil/mem.h" |
27 | | #include "libavutil/opt.h" |
28 | | #include "libavutil/time.h" |
29 | | #include "libavutil/parseutils.h" |
30 | | #include "internal.h" |
31 | | #include "avio_internal.h" |
32 | | #include "dash.h" |
33 | | #include "demux.h" |
34 | | #include "url.h" |
35 | | |
36 | 0 | #define INITIAL_BUFFER_SIZE 32768 |
37 | | |
38 | | struct fragment { |
39 | | int64_t url_offset; |
40 | | int64_t size; |
41 | | char *url; |
42 | | }; |
43 | | |
44 | | /* |
45 | | * reference to : ISO_IEC_23009-1-DASH-2012 |
46 | | * Section: 5.3.9.6.2 |
47 | | * Table: Table 17 — Semantics of SegmentTimeline element |
48 | | * */ |
49 | | struct timeline { |
50 | | /* starttime: Element or Attribute Name |
51 | | * specifies the MPD start time, in @timescale units, |
52 | | * the first Segment in the series starts relative to the beginning of the Period. |
53 | | * The value of this attribute must be equal to or greater than the sum of the previous S |
54 | | * element earliest presentation time and the sum of the contiguous Segment durations. |
55 | | * If the value of the attribute is greater than what is expressed by the previous S element, |
56 | | * it expresses discontinuities in the timeline. |
57 | | * If not present then the value shall be assumed to be zero for the first S element |
58 | | * and for the subsequent S elements, the value shall be assumed to be the sum of |
59 | | * the previous S element's earliest presentation time and contiguous duration |
60 | | * (i.e. previous S@starttime + @duration * (@repeat + 1)). |
61 | | * */ |
62 | | int64_t starttime; |
63 | | /* repeat: Element or Attribute Name |
64 | | * specifies the repeat count of the number of following contiguous Segments with |
65 | | * the same duration expressed by the value of @duration. This value is zero-based |
66 | | * (e.g. a value of three means four Segments in the contiguous series). |
67 | | * */ |
68 | | int64_t repeat; |
69 | | /* duration: Element or Attribute Name |
70 | | * specifies the Segment duration, in units of the value of the @timescale. |
71 | | * */ |
72 | | int64_t duration; |
73 | | }; |
74 | | |
75 | | /* |
76 | | * Each playlist has its own demuxer. If it is currently active, |
77 | | * it has an opened AVIOContext too, and potentially an AVPacket |
78 | | * containing the next packet from this stream. |
79 | | */ |
80 | | struct representation { |
81 | | char *url_template; |
82 | | FFIOContext pb; |
83 | | AVIOContext *input; |
84 | | AVFormatContext *parent; |
85 | | AVFormatContext *ctx; |
86 | | int stream_index; |
87 | | |
88 | | char *id; |
89 | | char *lang; |
90 | | int bandwidth; |
91 | | char *dependencyid; |
92 | | char *codecs; |
93 | | AVRational framerate; |
94 | | AVStream **assoc_stream; /* demuxer streams associated with this representation */ |
95 | | int nb_assoc_stream; |
96 | | |
97 | | int n_fragments; |
98 | | struct fragment **fragments; /* VOD list of fragment for profile */ |
99 | | |
100 | | int n_timelines; |
101 | | struct timeline **timelines; |
102 | | |
103 | | int64_t first_seq_no; |
104 | | int64_t last_seq_no; |
105 | | int64_t start_number; /* used in case when we have dynamic list of segment to know which segments are new one*/ |
106 | | |
107 | | int64_t fragment_duration; |
108 | | int64_t fragment_timescale; |
109 | | |
110 | | int64_t presentation_timeoffset; |
111 | | |
112 | | int64_t cur_seq_no; |
113 | | int64_t cur_seg_offset; |
114 | | int64_t cur_seg_size; |
115 | | struct fragment *cur_seg; |
116 | | int n_open_failures; /* consecutive open_input failures since last good read */ |
117 | | |
118 | | /* Currently active Media Initialization Section */ |
119 | | struct fragment *init_section; |
120 | | uint8_t *init_sec_buf; |
121 | | uint32_t init_sec_buf_size; |
122 | | uint32_t init_sec_data_len; |
123 | | uint32_t init_sec_buf_read_offset; |
124 | | int64_t cur_timestamp; |
125 | | int is_restart_needed; |
126 | | }; |
127 | | |
128 | | typedef struct DASHContext { |
129 | | const AVClass *class; |
130 | | char *base_url; |
131 | | |
132 | | int n_videos; |
133 | | struct representation **videos; |
134 | | int n_audios; |
135 | | struct representation **audios; |
136 | | int n_subtitles; |
137 | | struct representation **subtitles; |
138 | | |
139 | | /* MediaPresentationDescription Attribute */ |
140 | | int64_t media_presentation_duration; |
141 | | int64_t suggested_presentation_delay; |
142 | | int64_t availability_start_time; |
143 | | int64_t availability_end_time; |
144 | | int64_t publish_time; |
145 | | int64_t minimum_update_period; |
146 | | int64_t time_shift_buffer_depth; |
147 | | int64_t min_buffer_time; |
148 | | |
149 | | /* Period Attribute */ |
150 | | uint64_t period_duration; |
151 | | uint64_t period_start; |
152 | | |
153 | | /* AdaptationSet Attribute */ |
154 | | char *adaptionset_lang; |
155 | | |
156 | | int is_live; |
157 | | AVIOInterruptCB *interrupt_callback; |
158 | | char *allowed_extensions; |
159 | | AVDictionary *avio_opts; |
160 | | int max_url_size; |
161 | | int max_reload; |
162 | | char *cenc_decryption_key; |
163 | | char *cenc_decryption_keys; |
164 | | |
165 | | /* Flags for init section*/ |
166 | | int is_init_section_common_video; |
167 | | int is_init_section_common_audio; |
168 | | int is_init_section_common_subtitle; |
169 | | |
170 | | } DASHContext; |
171 | | |
172 | | static int ishttp(char *url) |
173 | 0 | { |
174 | 0 | const char *proto_name = avio_find_protocol_name(url); |
175 | 0 | return proto_name && av_strstart(proto_name, "http", NULL); |
176 | 0 | } |
177 | | |
178 | | static int aligned(int val) |
179 | 0 | { |
180 | 0 | return ((val + 0x3F) >> 6) << 6; |
181 | 0 | } |
182 | | |
183 | | static int64_t get_current_time_in_sec(void) |
184 | 0 | { |
185 | 0 | return av_gettime() / 1000000; |
186 | 0 | } |
187 | | |
188 | | static int64_t get_utc_date_time_insec(AVFormatContext *s, const char *datetime) |
189 | 0 | { |
190 | 0 | struct tm timeinfo; |
191 | 0 | int year = 0; |
192 | 0 | int month = 0; |
193 | 0 | int day = 0; |
194 | 0 | int hour = 0; |
195 | 0 | int minute = 0; |
196 | 0 | int ret = 0; |
197 | 0 | float second = 0.0; |
198 | | |
199 | | /* ISO-8601 date parser */ |
200 | 0 | if (!datetime) |
201 | 0 | return 0; |
202 | | |
203 | 0 | ret = sscanf(datetime, "%d-%d-%dT%d:%d:%fZ", &year, &month, &day, &hour, &minute, &second); |
204 | | /* year, month, day, hour, minute, second 6 arguments */ |
205 | 0 | if (ret != 6) { |
206 | 0 | av_log(s, AV_LOG_WARNING, "get_utc_date_time_insec get a wrong time format\n"); |
207 | 0 | } |
208 | 0 | timeinfo.tm_year = year - 1900; |
209 | 0 | timeinfo.tm_mon = month - 1; |
210 | 0 | timeinfo.tm_mday = day; |
211 | 0 | timeinfo.tm_hour = hour; |
212 | 0 | timeinfo.tm_min = minute; |
213 | 0 | timeinfo.tm_sec = (int)second; |
214 | |
|
215 | 0 | return av_timegm(&timeinfo); |
216 | 0 | } |
217 | | |
218 | | static uint32_t get_duration_insec(AVFormatContext *s, const char *duration) |
219 | 0 | { |
220 | | /* ISO-8601 duration parser */ |
221 | 0 | uint32_t days = 0; |
222 | 0 | uint32_t hours = 0; |
223 | 0 | uint32_t mins = 0; |
224 | 0 | uint32_t secs = 0; |
225 | 0 | int size = 0; |
226 | 0 | float value = 0; |
227 | 0 | char type = '\0'; |
228 | 0 | const char *ptr = duration; |
229 | |
|
230 | 0 | while (*ptr) { |
231 | 0 | if (*ptr == 'P' || *ptr == 'T') { |
232 | 0 | ptr++; |
233 | 0 | continue; |
234 | 0 | } |
235 | | |
236 | 0 | if (sscanf(ptr, "%f%c%n", &value, &type, &size) != 2) { |
237 | 0 | av_log(s, AV_LOG_WARNING, "get_duration_insec get a wrong time format\n"); |
238 | 0 | return 0; /* parser error */ |
239 | 0 | } |
240 | 0 | switch (type) { |
241 | 0 | case 'D': |
242 | 0 | days = (uint32_t)value; |
243 | 0 | break; |
244 | 0 | case 'H': |
245 | 0 | hours = (uint32_t)value; |
246 | 0 | break; |
247 | 0 | case 'M': |
248 | 0 | mins = (uint32_t)value; |
249 | 0 | break; |
250 | 0 | case 'S': |
251 | 0 | secs = (uint32_t)value; |
252 | 0 | break; |
253 | 0 | default: |
254 | | // handle invalid type |
255 | 0 | break; |
256 | 0 | } |
257 | 0 | ptr += size; |
258 | 0 | } |
259 | 0 | return ((days * 24 + hours) * 60 + mins) * 60 + secs; |
260 | 0 | } |
261 | | |
262 | | static int64_t get_segment_start_time_based_on_timeline(struct representation *pls, int64_t cur_seq_no) |
263 | 0 | { |
264 | 0 | int64_t start_time = 0; |
265 | 0 | int64_t i = 0; |
266 | 0 | int64_t j = 0; |
267 | 0 | int64_t num = 0; |
268 | |
|
269 | 0 | cur_seq_no -= pls->first_seq_no; |
270 | 0 | if (pls->n_timelines) { |
271 | 0 | for (i = 0; i < pls->n_timelines; i++) { |
272 | 0 | if (pls->timelines[i]->starttime > 0) { |
273 | 0 | start_time = pls->timelines[i]->starttime; |
274 | 0 | } |
275 | 0 | if (num == cur_seq_no) |
276 | 0 | goto finish; |
277 | | |
278 | 0 | start_time += pls->timelines[i]->duration; |
279 | |
|
280 | 0 | if (pls->timelines[i]->repeat == -1) { |
281 | 0 | start_time = pls->timelines[i]->duration * cur_seq_no; |
282 | 0 | goto finish; |
283 | 0 | } |
284 | | |
285 | 0 | for (j = 0; j < pls->timelines[i]->repeat; j++) { |
286 | 0 | num++; |
287 | 0 | if (num == cur_seq_no) |
288 | 0 | goto finish; |
289 | 0 | start_time += pls->timelines[i]->duration; |
290 | 0 | } |
291 | 0 | num++; |
292 | 0 | } |
293 | 0 | } |
294 | 0 | finish: |
295 | 0 | return start_time; |
296 | 0 | } |
297 | | |
298 | | static int64_t calc_next_seg_no_from_timelines(struct representation *pls, int64_t cur_time) |
299 | 0 | { |
300 | 0 | int64_t i = 0; |
301 | 0 | int64_t j = 0; |
302 | 0 | int64_t num = 0; |
303 | 0 | int64_t start_time = 0; |
304 | |
|
305 | 0 | for (i = 0; i < pls->n_timelines; i++) { |
306 | 0 | if (pls->timelines[i]->starttime > 0) { |
307 | 0 | start_time = pls->timelines[i]->starttime; |
308 | 0 | } |
309 | 0 | if (start_time > cur_time) |
310 | 0 | goto finish; |
311 | | |
312 | 0 | start_time += pls->timelines[i]->duration; |
313 | 0 | for (j = 0; j < pls->timelines[i]->repeat; j++) { |
314 | 0 | num++; |
315 | 0 | if (start_time > cur_time) |
316 | 0 | goto finish; |
317 | 0 | start_time += pls->timelines[i]->duration; |
318 | 0 | } |
319 | 0 | num++; |
320 | 0 | } |
321 | | |
322 | 0 | return -1; |
323 | | |
324 | 0 | finish: |
325 | 0 | return num + pls->first_seq_no; |
326 | 0 | } |
327 | | |
328 | | static void free_fragment(struct fragment **seg) |
329 | 0 | { |
330 | 0 | if (!(*seg)) { |
331 | 0 | return; |
332 | 0 | } |
333 | 0 | av_freep(&(*seg)->url); |
334 | 0 | av_freep(seg); |
335 | 0 | } |
336 | | |
337 | | static void free_fragment_list(struct representation *pls) |
338 | 0 | { |
339 | 0 | int i; |
340 | |
|
341 | 0 | for (i = 0; i < pls->n_fragments; i++) { |
342 | 0 | free_fragment(&pls->fragments[i]); |
343 | 0 | } |
344 | 0 | av_freep(&pls->fragments); |
345 | 0 | pls->n_fragments = 0; |
346 | 0 | } |
347 | | |
348 | | static void free_timelines_list(struct representation *pls) |
349 | 0 | { |
350 | 0 | int i; |
351 | |
|
352 | 0 | for (i = 0; i < pls->n_timelines; i++) { |
353 | 0 | av_freep(&pls->timelines[i]); |
354 | 0 | } |
355 | 0 | av_freep(&pls->timelines); |
356 | 0 | pls->n_timelines = 0; |
357 | 0 | } |
358 | | |
359 | | static void free_representation(struct representation *pls) |
360 | 0 | { |
361 | 0 | free_fragment_list(pls); |
362 | 0 | free_timelines_list(pls); |
363 | 0 | free_fragment(&pls->cur_seg); |
364 | 0 | free_fragment(&pls->init_section); |
365 | 0 | av_freep(&pls->init_sec_buf); |
366 | 0 | av_freep(&pls->pb.pub.buffer); |
367 | 0 | ff_format_io_close(pls->parent, &pls->input); |
368 | 0 | if (pls->ctx) { |
369 | 0 | pls->ctx->pb = NULL; |
370 | 0 | avformat_close_input(&pls->ctx); |
371 | 0 | } |
372 | |
|
373 | 0 | av_freep(&pls->assoc_stream); |
374 | 0 | av_freep(&pls->url_template); |
375 | 0 | av_freep(&pls->lang); |
376 | 0 | av_freep(&pls->dependencyid); |
377 | 0 | av_freep(&pls->codecs); |
378 | 0 | av_freep(&pls->id); |
379 | 0 | av_freep(&pls); |
380 | 0 | } |
381 | | |
382 | | static void free_video_list(DASHContext *c) |
383 | 16.8k | { |
384 | 16.8k | int i; |
385 | 16.8k | for (i = 0; i < c->n_videos; i++) { |
386 | 0 | struct representation *pls = c->videos[i]; |
387 | 0 | free_representation(pls); |
388 | 0 | } |
389 | 16.8k | av_freep(&c->videos); |
390 | 16.8k | c->n_videos = 0; |
391 | 16.8k | } |
392 | | |
393 | | static void free_audio_list(DASHContext *c) |
394 | 16.8k | { |
395 | 16.8k | int i; |
396 | 16.8k | for (i = 0; i < c->n_audios; i++) { |
397 | 0 | struct representation *pls = c->audios[i]; |
398 | 0 | free_representation(pls); |
399 | 0 | } |
400 | 16.8k | av_freep(&c->audios); |
401 | 16.8k | c->n_audios = 0; |
402 | 16.8k | } |
403 | | |
404 | | static void free_subtitle_list(DASHContext *c) |
405 | 16.8k | { |
406 | 16.8k | int i; |
407 | 16.8k | for (i = 0; i < c->n_subtitles; i++) { |
408 | 0 | struct representation *pls = c->subtitles[i]; |
409 | 0 | free_representation(pls); |
410 | 0 | } |
411 | 16.8k | av_freep(&c->subtitles); |
412 | 16.8k | c->n_subtitles = 0; |
413 | 16.8k | } |
414 | | |
415 | | static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url, |
416 | | AVDictionary **opts, AVDictionary *opts2, int *is_http) |
417 | 0 | { |
418 | 0 | DASHContext *c = s->priv_data; |
419 | 0 | AVDictionary *tmp = NULL; |
420 | 0 | const char *proto_name = NULL; |
421 | 0 | int proto_name_len; |
422 | 0 | int ret; |
423 | |
|
424 | 0 | if (av_strstart(url, "crypto", NULL)) { |
425 | 0 | if (url[6] == '+' || url[6] == ':') |
426 | 0 | proto_name = avio_find_protocol_name(url + 7); |
427 | 0 | } |
428 | |
|
429 | 0 | if (!proto_name) |
430 | 0 | proto_name = avio_find_protocol_name(url); |
431 | |
|
432 | 0 | if (!proto_name) |
433 | 0 | return AVERROR_INVALIDDATA; |
434 | | |
435 | 0 | proto_name_len = strlen(proto_name); |
436 | | // only http(s) & file are allowed |
437 | 0 | if (av_strstart(proto_name, "file", NULL)) { |
438 | 0 | if (strcmp(c->allowed_extensions, "ALL") && !av_match_ext(url, c->allowed_extensions)) { |
439 | 0 | av_log(s, AV_LOG_ERROR, |
440 | 0 | "Filename extension of \'%s\' is not a common multimedia extension, blocked for security reasons.\n" |
441 | 0 | "If you wish to override this adjust allowed_extensions, you can set it to \'ALL\' to allow all\n", |
442 | 0 | url); |
443 | 0 | return AVERROR_INVALIDDATA; |
444 | 0 | } |
445 | 0 | } else if (av_strstart(proto_name, "http", NULL)) { |
446 | 0 | ; |
447 | 0 | } else |
448 | 0 | return AVERROR_INVALIDDATA; |
449 | | |
450 | 0 | if (!strncmp(proto_name, url, proto_name_len) && url[proto_name_len] == ':') |
451 | 0 | ; |
452 | 0 | else if (av_strstart(url, "crypto", NULL) && !strncmp(proto_name, url + 7, proto_name_len) && url[7 + proto_name_len] == ':') |
453 | 0 | ; |
454 | 0 | else if (strcmp(proto_name, "file") || !strncmp(url, "file,", 5)) |
455 | 0 | return AVERROR_INVALIDDATA; |
456 | | |
457 | 0 | av_freep(pb); |
458 | 0 | av_dict_copy(&tmp, *opts, 0); |
459 | 0 | av_dict_copy(&tmp, opts2, 0); |
460 | 0 | ret = s->io_open(s, pb, url, AVIO_FLAG_READ, &tmp); |
461 | 0 | if (ret >= 0) { |
462 | | // update cookies on http response with setcookies. |
463 | 0 | char *new_cookies = NULL; |
464 | |
|
465 | 0 | if (!(s->flags & AVFMT_FLAG_CUSTOM_IO)) |
466 | 0 | av_opt_get(*pb, "cookies", AV_OPT_SEARCH_CHILDREN, (uint8_t**)&new_cookies); |
467 | |
|
468 | 0 | if (new_cookies) { |
469 | 0 | av_dict_set(opts, "cookies", new_cookies, AV_DICT_DONT_STRDUP_VAL); |
470 | 0 | } |
471 | |
|
472 | 0 | } |
473 | |
|
474 | 0 | av_dict_free(&tmp); |
475 | |
|
476 | 0 | if (is_http) |
477 | 0 | *is_http = av_strstart(proto_name, "http", NULL); |
478 | |
|
479 | 0 | return ret; |
480 | 0 | } |
481 | | |
482 | | static char *get_content_url(xmlNodePtr *baseurl_nodes, |
483 | | int n_baseurl_nodes, |
484 | | int max_url_size, |
485 | | char *rep_id_val, |
486 | | char *rep_bandwidth_val, |
487 | | char *val) |
488 | 0 | { |
489 | 0 | int i; |
490 | 0 | char *text; |
491 | 0 | char *url = NULL; |
492 | 0 | char *tmp_str = av_mallocz(max_url_size); |
493 | |
|
494 | 0 | if (!tmp_str) |
495 | 0 | return NULL; |
496 | | |
497 | 0 | for (i = 0; i < n_baseurl_nodes; ++i) { |
498 | 0 | if (baseurl_nodes[i] && |
499 | 0 | baseurl_nodes[i]->children && |
500 | 0 | baseurl_nodes[i]->children->type == XML_TEXT_NODE) { |
501 | 0 | text = xmlNodeGetContent(baseurl_nodes[i]->children); |
502 | 0 | if (text) { |
503 | 0 | memset(tmp_str, 0, max_url_size); |
504 | 0 | ff_make_absolute_url(tmp_str, max_url_size, "", text); |
505 | 0 | xmlFree(text); |
506 | 0 | } |
507 | 0 | } |
508 | 0 | } |
509 | |
|
510 | 0 | if (val) |
511 | 0 | ff_make_absolute_url(tmp_str, max_url_size, tmp_str, val); |
512 | |
|
513 | 0 | if (rep_id_val) { |
514 | 0 | url = av_strireplace(tmp_str, "$RepresentationID$", rep_id_val); |
515 | 0 | if (!url) { |
516 | 0 | goto end; |
517 | 0 | } |
518 | 0 | av_strlcpy(tmp_str, url, max_url_size); |
519 | 0 | } |
520 | 0 | if (rep_bandwidth_val && tmp_str[0] != '\0') { |
521 | | // free any previously assigned url before reassigning |
522 | 0 | av_free(url); |
523 | 0 | url = av_strireplace(tmp_str, "$Bandwidth$", rep_bandwidth_val); |
524 | 0 | if (!url) { |
525 | 0 | goto end; |
526 | 0 | } |
527 | 0 | } |
528 | 0 | end: |
529 | 0 | av_free(tmp_str); |
530 | 0 | return url; |
531 | 0 | } |
532 | | |
533 | | static char *get_val_from_nodes_tab(xmlNodePtr *nodes, const int n_nodes, const char *attrname) |
534 | 0 | { |
535 | 0 | int i; |
536 | 0 | char *val; |
537 | |
|
538 | 0 | for (i = 0; i < n_nodes; ++i) { |
539 | 0 | if (nodes[i]) { |
540 | 0 | val = xmlGetProp(nodes[i], attrname); |
541 | 0 | if (val) |
542 | 0 | return val; |
543 | 0 | } |
544 | 0 | } |
545 | | |
546 | 0 | return NULL; |
547 | 0 | } |
548 | | |
549 | | static xmlNodePtr find_child_node_by_name(xmlNodePtr rootnode, const char *nodename) |
550 | 0 | { |
551 | 0 | xmlNodePtr node = rootnode; |
552 | 0 | if (!node) { |
553 | 0 | return NULL; |
554 | 0 | } |
555 | | |
556 | 0 | node = xmlFirstElementChild(node); |
557 | 0 | while (node) { |
558 | 0 | if (!av_strcasecmp(node->name, nodename)) { |
559 | 0 | return node; |
560 | 0 | } |
561 | 0 | node = xmlNextElementSibling(node); |
562 | 0 | } |
563 | 0 | return NULL; |
564 | 0 | } |
565 | | |
566 | | static enum AVMediaType get_content_type(xmlNodePtr node) |
567 | 0 | { |
568 | 0 | enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN; |
569 | 0 | int i = 0; |
570 | 0 | const char *attr; |
571 | 0 | char *val = NULL; |
572 | |
|
573 | 0 | if (node) { |
574 | 0 | for (i = 0; i < 2; i++) { |
575 | 0 | attr = i ? "mimeType" : "contentType"; |
576 | 0 | val = xmlGetProp(node, attr); |
577 | 0 | if (val) { |
578 | 0 | if (av_stristr(val, "video")) { |
579 | 0 | type = AVMEDIA_TYPE_VIDEO; |
580 | 0 | } else if (av_stristr(val, "audio")) { |
581 | 0 | type = AVMEDIA_TYPE_AUDIO; |
582 | 0 | } else if (av_stristr(val, "text")) { |
583 | 0 | type = AVMEDIA_TYPE_SUBTITLE; |
584 | 0 | } |
585 | 0 | xmlFree(val); |
586 | 0 | } |
587 | 0 | } |
588 | 0 | } |
589 | 0 | return type; |
590 | 0 | } |
591 | | |
592 | | static struct fragment *get_fragment(AVFormatContext *s, char *range) |
593 | 0 | { |
594 | 0 | struct fragment *seg = av_mallocz(sizeof(struct fragment)); |
595 | |
|
596 | 0 | if (!seg) |
597 | 0 | return NULL; |
598 | | |
599 | 0 | seg->size = -1; |
600 | 0 | if (range) { |
601 | 0 | char *str_end_offset; |
602 | 0 | char *str_offset = av_strtok(range, "-", &str_end_offset); |
603 | 0 | if (!str_offset || !str_end_offset) { |
604 | 0 | av_log(s, AV_LOG_WARNING, "%s will get invalid range\n", range); |
605 | 0 | av_freep(&seg); |
606 | 0 | return NULL; |
607 | 0 | } |
608 | 0 | seg->url_offset = strtoll(str_offset, NULL, 10); |
609 | 0 | seg->size = strtoll(str_end_offset, NULL, 10) - seg->url_offset + 1; |
610 | 0 | } |
611 | | |
612 | 0 | return seg; |
613 | 0 | } |
614 | | |
615 | | static int parse_manifest_segmenturlnode(AVFormatContext *s, struct representation *rep, |
616 | | xmlNodePtr fragmenturl_node, |
617 | | xmlNodePtr *baseurl_nodes, |
618 | | char *rep_id_val, |
619 | | char *rep_bandwidth_val) |
620 | 0 | { |
621 | 0 | DASHContext *c = s->priv_data; |
622 | 0 | char *initialization_val = NULL; |
623 | 0 | char *media_val = NULL; |
624 | 0 | char *range_val = NULL; |
625 | 0 | int max_url_size = c ? c->max_url_size: MAX_URL_SIZE; |
626 | 0 | int err; |
627 | |
|
628 | 0 | if (!av_strcasecmp(fragmenturl_node->name, "Initialization")) { |
629 | 0 | initialization_val = xmlGetProp(fragmenturl_node, "sourceURL"); |
630 | 0 | range_val = xmlGetProp(fragmenturl_node, "range"); |
631 | 0 | if (initialization_val || range_val) { |
632 | 0 | free_fragment(&rep->init_section); |
633 | 0 | rep->init_section = get_fragment(s, range_val); |
634 | 0 | xmlFree(range_val); |
635 | 0 | if (!rep->init_section) { |
636 | 0 | xmlFree(initialization_val); |
637 | 0 | return AVERROR(ENOMEM); |
638 | 0 | } |
639 | 0 | rep->init_section->url = get_content_url(baseurl_nodes, 4, |
640 | 0 | max_url_size, |
641 | 0 | rep_id_val, |
642 | 0 | rep_bandwidth_val, |
643 | 0 | initialization_val); |
644 | 0 | xmlFree(initialization_val); |
645 | 0 | if (!rep->init_section->url) { |
646 | 0 | av_freep(&rep->init_section); |
647 | 0 | return AVERROR(ENOMEM); |
648 | 0 | } |
649 | 0 | } |
650 | 0 | } else if (!av_strcasecmp(fragmenturl_node->name, "SegmentURL")) { |
651 | 0 | media_val = xmlGetProp(fragmenturl_node, "media"); |
652 | 0 | range_val = xmlGetProp(fragmenturl_node, "mediaRange"); |
653 | 0 | if (media_val || range_val) { |
654 | 0 | struct fragment *seg = get_fragment(s, range_val); |
655 | 0 | xmlFree(range_val); |
656 | 0 | if (!seg) { |
657 | 0 | xmlFree(media_val); |
658 | 0 | return AVERROR(ENOMEM); |
659 | 0 | } |
660 | 0 | seg->url = get_content_url(baseurl_nodes, 4, |
661 | 0 | max_url_size, |
662 | 0 | rep_id_val, |
663 | 0 | rep_bandwidth_val, |
664 | 0 | media_val); |
665 | 0 | xmlFree(media_val); |
666 | 0 | if (!seg->url) { |
667 | 0 | av_free(seg); |
668 | 0 | return AVERROR(ENOMEM); |
669 | 0 | } |
670 | 0 | err = av_dynarray_add_nofree(&rep->fragments, &rep->n_fragments, seg); |
671 | 0 | if (err < 0) { |
672 | 0 | free_fragment(&seg); |
673 | 0 | return err; |
674 | 0 | } |
675 | 0 | } |
676 | 0 | } |
677 | | |
678 | 0 | return 0; |
679 | 0 | } |
680 | | |
681 | | static int parse_manifest_segmenttimeline(AVFormatContext *s, struct representation *rep, |
682 | | xmlNodePtr fragment_timeline_node) |
683 | 0 | { |
684 | 0 | xmlAttrPtr attr = NULL; |
685 | 0 | char *val = NULL; |
686 | 0 | int err; |
687 | |
|
688 | 0 | if (!av_strcasecmp(fragment_timeline_node->name, "S")) { |
689 | 0 | struct timeline *tml = av_mallocz(sizeof(struct timeline)); |
690 | 0 | if (!tml) { |
691 | 0 | return AVERROR(ENOMEM); |
692 | 0 | } |
693 | 0 | attr = fragment_timeline_node->properties; |
694 | 0 | while (attr) { |
695 | 0 | val = xmlGetProp(fragment_timeline_node, attr->name); |
696 | |
|
697 | 0 | if (!val) { |
698 | 0 | av_log(s, AV_LOG_WARNING, "parse_manifest_segmenttimeline attr->name = %s val is NULL\n", attr->name); |
699 | 0 | continue; |
700 | 0 | } |
701 | | |
702 | 0 | if (!av_strcasecmp(attr->name, "t")) { |
703 | 0 | tml->starttime = (int64_t)strtoll(val, NULL, 10); |
704 | 0 | } else if (!av_strcasecmp(attr->name, "r")) { |
705 | 0 | tml->repeat =(int64_t) strtoll(val, NULL, 10); |
706 | 0 | } else if (!av_strcasecmp(attr->name, "d")) { |
707 | 0 | tml->duration = (int64_t)strtoll(val, NULL, 10); |
708 | 0 | } |
709 | 0 | attr = attr->next; |
710 | 0 | xmlFree(val); |
711 | 0 | } |
712 | 0 | err = av_dynarray_add_nofree(&rep->timelines, &rep->n_timelines, tml); |
713 | 0 | if (err < 0) { |
714 | 0 | av_free(tml); |
715 | 0 | return err; |
716 | 0 | } |
717 | 0 | } |
718 | | |
719 | 0 | return 0; |
720 | 0 | } |
721 | | |
722 | | static int resolve_content_path(AVFormatContext *s, const char *url, int *max_url_size, xmlNodePtr *baseurl_nodes, int n_baseurl_nodes) |
723 | 0 | { |
724 | 0 | char *tmp_str = NULL; |
725 | 0 | char *path = NULL; |
726 | 0 | char *mpdName = NULL; |
727 | 0 | xmlNodePtr node = NULL; |
728 | 0 | char *baseurl = NULL; |
729 | 0 | char *root_url = NULL; |
730 | 0 | char *text = NULL; |
731 | 0 | char *tmp = NULL; |
732 | 0 | int isRootHttp = 0; |
733 | 0 | char token ='/'; |
734 | 0 | int start = 0; |
735 | 0 | int rootId = 0; |
736 | 0 | int updated = 0; |
737 | 0 | int size = 0; |
738 | 0 | int i; |
739 | 0 | int tmp_max_url_size = strlen(url); |
740 | |
|
741 | 0 | for (i = n_baseurl_nodes-1; i >= 0 ; i--) { |
742 | 0 | text = xmlNodeGetContent(baseurl_nodes[i]); |
743 | 0 | if (!text) |
744 | 0 | continue; |
745 | 0 | tmp_max_url_size += strlen(text); |
746 | 0 | if (ishttp(text)) { |
747 | 0 | xmlFree(text); |
748 | 0 | break; |
749 | 0 | } |
750 | 0 | xmlFree(text); |
751 | 0 | } |
752 | |
|
753 | 0 | tmp_max_url_size = aligned(tmp_max_url_size); |
754 | 0 | text = av_mallocz(tmp_max_url_size + 1); |
755 | 0 | if (!text) { |
756 | 0 | updated = AVERROR(ENOMEM); |
757 | 0 | goto end; |
758 | 0 | } |
759 | 0 | av_strlcpy(text, url, strlen(url)+1); |
760 | 0 | tmp = text; |
761 | 0 | while (mpdName = av_strtok(tmp, "/", &tmp)) { |
762 | 0 | size = strlen(mpdName); |
763 | 0 | } |
764 | 0 | av_free(text); |
765 | |
|
766 | 0 | path = av_mallocz(tmp_max_url_size + 2); |
767 | 0 | tmp_str = av_mallocz(tmp_max_url_size); |
768 | 0 | if (!tmp_str || !path) { |
769 | 0 | updated = AVERROR(ENOMEM); |
770 | 0 | goto end; |
771 | 0 | } |
772 | | |
773 | 0 | av_strlcpy (path, url, strlen(url) - size + 1); |
774 | 0 | for (rootId = n_baseurl_nodes - 1; rootId > 0; rootId --) { |
775 | 0 | if (!(node = baseurl_nodes[rootId])) { |
776 | 0 | continue; |
777 | 0 | } |
778 | 0 | text = xmlNodeGetContent(node); |
779 | 0 | if (ishttp(text)) { |
780 | 0 | xmlFree(text); |
781 | 0 | break; |
782 | 0 | } |
783 | 0 | xmlFree(text); |
784 | 0 | } |
785 | |
|
786 | 0 | node = baseurl_nodes[rootId]; |
787 | 0 | baseurl = xmlNodeGetContent(node); |
788 | 0 | if (baseurl) { |
789 | 0 | size_t len = xmlStrlen(baseurl)+2; |
790 | 0 | char *tmp = xmlRealloc(baseurl, len); |
791 | 0 | if (!tmp) { |
792 | 0 | updated = AVERROR(ENOMEM); |
793 | 0 | goto end; |
794 | 0 | } |
795 | 0 | baseurl = tmp; |
796 | 0 | } |
797 | 0 | root_url = (av_strcasecmp(baseurl, "")) ? baseurl : path; |
798 | 0 | if (node) { |
799 | 0 | xmlChar *escaped = xmlEncodeSpecialChars(NULL, root_url); |
800 | 0 | if (!escaped) { |
801 | 0 | updated = AVERROR(ENOMEM); |
802 | 0 | goto end; |
803 | 0 | } |
804 | 0 | xmlNodeSetContent(node, escaped); |
805 | 0 | xmlFree(escaped); |
806 | 0 | updated = 1; |
807 | 0 | } |
808 | | |
809 | 0 | size = strlen(root_url); |
810 | 0 | isRootHttp = ishttp(root_url); |
811 | |
|
812 | 0 | if (size > 0 && root_url[size - 1] != token) { |
813 | 0 | av_strlcat(root_url, "/", size + 2); |
814 | 0 | size += 2; |
815 | 0 | } |
816 | |
|
817 | 0 | for (i = 0; i < n_baseurl_nodes; ++i) { |
818 | 0 | if (i == rootId) { |
819 | 0 | continue; |
820 | 0 | } |
821 | 0 | text = xmlNodeGetContent(baseurl_nodes[i]); |
822 | 0 | if (text && !av_strstart(text, "/", NULL)) { |
823 | 0 | memset(tmp_str, 0, strlen(tmp_str)); |
824 | 0 | if (!ishttp(text) && isRootHttp) { |
825 | 0 | av_strlcpy(tmp_str, root_url, size + 1); |
826 | 0 | } |
827 | 0 | start = (text[0] == token); |
828 | 0 | if (start && av_stristr(tmp_str, text)) { |
829 | 0 | char *p = tmp_str; |
830 | 0 | if (!av_strncasecmp(tmp_str, "http://", 7)) { |
831 | 0 | p += 7; |
832 | 0 | } else if (!av_strncasecmp(tmp_str, "https://", 8)) { |
833 | 0 | p += 8; |
834 | 0 | } |
835 | 0 | p = strchr(p, '/'); |
836 | 0 | memset(p + 1, 0, strlen(p)); |
837 | 0 | } |
838 | 0 | av_strlcat(tmp_str, text + start, tmp_max_url_size); |
839 | 0 | xmlFree(text); |
840 | 0 | xmlChar* escaped = xmlEncodeSpecialChars(NULL, tmp_str); |
841 | 0 | if (!escaped) { |
842 | 0 | updated = AVERROR(ENOMEM); |
843 | 0 | goto end; |
844 | 0 | } |
845 | 0 | xmlNodeSetContent(baseurl_nodes[i], escaped); |
846 | 0 | updated = 1; |
847 | 0 | xmlFree(escaped); |
848 | 0 | } |
849 | 0 | } |
850 | | |
851 | 0 | end: |
852 | 0 | if (tmp_max_url_size > *max_url_size) { |
853 | 0 | *max_url_size = tmp_max_url_size; |
854 | 0 | } |
855 | 0 | av_free(path); |
856 | 0 | av_free(tmp_str); |
857 | 0 | xmlFree(baseurl); |
858 | 0 | return updated; |
859 | |
|
860 | 0 | } |
861 | | |
862 | 0 | #define SET_REPRESENTATION_SEQUENCE_BASE_INFO(arg, cnt) { \ |
863 | 0 | val = get_val_from_nodes_tab((arg), (cnt), "duration"); \ |
864 | 0 | if (val) { \ |
865 | 0 | int64_t fragment_duration = (int64_t) strtoll(val, NULL, 10); \ |
866 | 0 | if (fragment_duration < 0) { \ |
867 | 0 | av_log(s, AV_LOG_WARNING, "duration invalid, autochanged to 0.\n"); \ |
868 | 0 | fragment_duration = 0; \ |
869 | 0 | } \ |
870 | 0 | rep->fragment_duration = fragment_duration; \ |
871 | 0 | av_log(s, AV_LOG_TRACE, "rep->fragment_duration = [%"PRId64"]\n", rep->fragment_duration); \ |
872 | 0 | xmlFree(val); \ |
873 | 0 | } \ |
874 | 0 | val = get_val_from_nodes_tab((arg), (cnt), "timescale"); \ |
875 | 0 | if (val) { \ |
876 | 0 | int64_t fragment_timescale = (int64_t) strtoll(val, NULL, 10); \ |
877 | 0 | if (fragment_timescale < 0) { \ |
878 | 0 | av_log(s, AV_LOG_WARNING, "timescale invalid, autochanged to 0.\n"); \ |
879 | 0 | fragment_timescale = 0; \ |
880 | 0 | } \ |
881 | 0 | rep->fragment_timescale = fragment_timescale; \ |
882 | 0 | av_log(s, AV_LOG_TRACE, "rep->fragment_timescale = [%"PRId64"]\n", rep->fragment_timescale); \ |
883 | 0 | xmlFree(val); \ |
884 | 0 | } \ |
885 | 0 | val = get_val_from_nodes_tab((arg), (cnt), "startNumber"); \ |
886 | 0 | if (val) { \ |
887 | 0 | int64_t start_number = (int64_t) strtoll(val, NULL, 10); \ |
888 | 0 | if (start_number < 0) { \ |
889 | 0 | av_log(s, AV_LOG_WARNING, "startNumber invalid, autochanged to 0.\n"); \ |
890 | 0 | start_number = 0; \ |
891 | 0 | } \ |
892 | 0 | rep->start_number = rep->first_seq_no = start_number; \ |
893 | 0 | av_log(s, AV_LOG_TRACE, "rep->first_seq_no = [%"PRId64"]\n", rep->first_seq_no); \ |
894 | 0 | xmlFree(val); \ |
895 | 0 | } \ |
896 | 0 | } |
897 | | |
898 | | |
899 | | static int parse_manifest_representation(AVFormatContext *s, const char *url, |
900 | | xmlNodePtr node, |
901 | | xmlNodePtr adaptionset_node, |
902 | | xmlNodePtr mpd_baseurl_node, |
903 | | xmlNodePtr period_baseurl_node, |
904 | | xmlNodePtr period_segmenttemplate_node, |
905 | | xmlNodePtr period_segmentlist_node, |
906 | | xmlNodePtr fragment_template_node, |
907 | | xmlNodePtr content_component_node, |
908 | | xmlNodePtr adaptionset_baseurl_node, |
909 | | xmlNodePtr adaptionset_segmentlist_node, |
910 | | xmlNodePtr adaptionset_supplementalproperty_node) |
911 | 0 | { |
912 | 0 | int32_t ret = 0; |
913 | 0 | DASHContext *c = s->priv_data; |
914 | 0 | struct representation *rep = NULL; |
915 | 0 | struct fragment *seg = NULL; |
916 | 0 | xmlNodePtr representation_segmenttemplate_node = NULL; |
917 | 0 | xmlNodePtr representation_baseurl_node = NULL; |
918 | 0 | xmlNodePtr representation_segmentlist_node = NULL; |
919 | 0 | xmlNodePtr segmentlists_tab[3]; |
920 | 0 | xmlNodePtr fragment_timeline_node = NULL; |
921 | 0 | xmlNodePtr fragment_templates_tab[5]; |
922 | 0 | char *val = NULL; |
923 | 0 | xmlNodePtr baseurl_nodes[4]; |
924 | 0 | xmlNodePtr representation_node = node; |
925 | 0 | char *rep_bandwidth_val; |
926 | 0 | char *rep_codecs_val; |
927 | 0 | char *rep_dependencyid_val; |
928 | 0 | enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN; |
929 | | |
930 | | // try get information from representation |
931 | 0 | if (type == AVMEDIA_TYPE_UNKNOWN) |
932 | 0 | type = get_content_type(representation_node); |
933 | | // try get information from contentComponen |
934 | 0 | if (type == AVMEDIA_TYPE_UNKNOWN) |
935 | 0 | type = get_content_type(content_component_node); |
936 | | // try get information from adaption set |
937 | 0 | if (type == AVMEDIA_TYPE_UNKNOWN) |
938 | 0 | type = get_content_type(adaptionset_node); |
939 | 0 | if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO && |
940 | 0 | type != AVMEDIA_TYPE_SUBTITLE) { |
941 | 0 | av_log(s, AV_LOG_VERBOSE, "Parsing '%s' - skip not supported representation type\n", url); |
942 | 0 | return 0; |
943 | 0 | } |
944 | | |
945 | | // convert selected representation to our internal struct |
946 | 0 | rep = av_mallocz(sizeof(struct representation)); |
947 | 0 | if (!rep) |
948 | 0 | return AVERROR(ENOMEM); |
949 | 0 | if (c->adaptionset_lang) { |
950 | 0 | rep->lang = av_strdup(c->adaptionset_lang); |
951 | 0 | if (!rep->lang) { |
952 | 0 | av_log(s, AV_LOG_ERROR, "alloc language memory failure\n"); |
953 | 0 | av_freep(&rep); |
954 | 0 | return AVERROR(ENOMEM); |
955 | 0 | } |
956 | 0 | } |
957 | 0 | rep->parent = s; |
958 | 0 | representation_segmenttemplate_node = find_child_node_by_name(representation_node, "SegmentTemplate"); |
959 | 0 | representation_baseurl_node = find_child_node_by_name(representation_node, "BaseURL"); |
960 | 0 | representation_segmentlist_node = find_child_node_by_name(representation_node, "SegmentList"); |
961 | 0 | rep_bandwidth_val = xmlGetProp(representation_node, "bandwidth"); |
962 | 0 | rep_dependencyid_val = xmlGetProp(representation_node, "dependencyId"); |
963 | 0 | rep_codecs_val = xmlGetProp(representation_node, "codecs"); |
964 | 0 | if (!rep_codecs_val) |
965 | 0 | rep_codecs_val = xmlGetProp(adaptionset_node, "codecs"); |
966 | 0 | val = xmlGetProp(representation_node, "id"); |
967 | 0 | if (val) { |
968 | 0 | rep->id = av_strdup(val); |
969 | 0 | xmlFree(val); |
970 | 0 | if (!rep->id) |
971 | 0 | goto enomem; |
972 | 0 | } |
973 | | |
974 | 0 | baseurl_nodes[0] = mpd_baseurl_node; |
975 | 0 | baseurl_nodes[1] = period_baseurl_node; |
976 | 0 | baseurl_nodes[2] = adaptionset_baseurl_node; |
977 | 0 | baseurl_nodes[3] = representation_baseurl_node; |
978 | |
|
979 | 0 | ret = resolve_content_path(s, url, &c->max_url_size, baseurl_nodes, 4); |
980 | 0 | c->max_url_size = aligned(c->max_url_size |
981 | 0 | + (rep->id ? strlen(rep->id) : 0) |
982 | 0 | + (rep_bandwidth_val ? strlen(rep_bandwidth_val) : 0)); |
983 | 0 | if (ret == AVERROR(ENOMEM) || ret == 0) |
984 | 0 | goto free; |
985 | 0 | if (representation_segmenttemplate_node || fragment_template_node || period_segmenttemplate_node) { |
986 | 0 | fragment_timeline_node = NULL; |
987 | 0 | fragment_templates_tab[0] = representation_segmenttemplate_node; |
988 | 0 | fragment_templates_tab[1] = adaptionset_segmentlist_node; |
989 | 0 | fragment_templates_tab[2] = fragment_template_node; |
990 | 0 | fragment_templates_tab[3] = period_segmenttemplate_node; |
991 | 0 | fragment_templates_tab[4] = period_segmentlist_node; |
992 | |
|
993 | 0 | val = get_val_from_nodes_tab(fragment_templates_tab, 4, "initialization"); |
994 | 0 | if (val) { |
995 | 0 | rep->init_section = av_mallocz(sizeof(struct fragment)); |
996 | 0 | if (!rep->init_section) { |
997 | 0 | xmlFree(val); |
998 | 0 | goto enomem; |
999 | 0 | } |
1000 | 0 | c->max_url_size = aligned(c->max_url_size + strlen(val)); |
1001 | 0 | rep->init_section->url = get_content_url(baseurl_nodes, 4, |
1002 | 0 | c->max_url_size, rep->id, |
1003 | 0 | rep_bandwidth_val, val); |
1004 | 0 | xmlFree(val); |
1005 | 0 | if (!rep->init_section->url) |
1006 | 0 | goto enomem; |
1007 | 0 | rep->init_section->size = -1; |
1008 | 0 | } |
1009 | 0 | val = get_val_from_nodes_tab(fragment_templates_tab, 4, "media"); |
1010 | 0 | if (val) { |
1011 | 0 | c->max_url_size = aligned(c->max_url_size + strlen(val)); |
1012 | 0 | rep->url_template = get_content_url(baseurl_nodes, 4, |
1013 | 0 | c->max_url_size, rep->id, |
1014 | 0 | rep_bandwidth_val, val); |
1015 | 0 | xmlFree(val); |
1016 | 0 | } |
1017 | 0 | val = get_val_from_nodes_tab(fragment_templates_tab, 4, "presentationTimeOffset"); |
1018 | 0 | if (val) { |
1019 | 0 | int64_t presentation_timeoffset = (int64_t) strtoll(val, NULL, 10); |
1020 | 0 | if (presentation_timeoffset < 0) { |
1021 | 0 | av_log(s, AV_LOG_WARNING, "presentationTimeOffset invalid, autochanged to 0.\n"); |
1022 | 0 | presentation_timeoffset = 0; |
1023 | 0 | } |
1024 | 0 | rep->presentation_timeoffset = presentation_timeoffset; |
1025 | 0 | av_log(s, AV_LOG_TRACE, "rep->presentation_timeoffset = [%"PRId64"]\n", rep->presentation_timeoffset); |
1026 | 0 | xmlFree(val); |
1027 | 0 | } |
1028 | |
|
1029 | 0 | SET_REPRESENTATION_SEQUENCE_BASE_INFO(fragment_templates_tab, 4); |
1030 | 0 | if (adaptionset_supplementalproperty_node) { |
1031 | 0 | char *scheme_id_uri = xmlGetProp(adaptionset_supplementalproperty_node, "schemeIdUri"); |
1032 | 0 | if (scheme_id_uri) { |
1033 | 0 | int is_last_segment_number = !av_strcasecmp(scheme_id_uri, "http://dashif.org/guidelines/last-segment-number"); |
1034 | 0 | xmlFree(scheme_id_uri); |
1035 | 0 | if (is_last_segment_number) { |
1036 | 0 | val = xmlGetProp(adaptionset_supplementalproperty_node, "value"); |
1037 | 0 | if (!val) { |
1038 | 0 | av_log(s, AV_LOG_ERROR, "Missing value attribute in adaptionset_supplementalproperty_node\n"); |
1039 | 0 | } else { |
1040 | 0 | rep->last_seq_no = (int64_t)strtoll(val, NULL, 10) - 1; |
1041 | 0 | xmlFree(val); |
1042 | 0 | } |
1043 | 0 | } |
1044 | 0 | } |
1045 | 0 | } |
1046 | |
|
1047 | 0 | fragment_timeline_node = find_child_node_by_name(representation_segmenttemplate_node, "SegmentTimeline"); |
1048 | |
|
1049 | 0 | if (!fragment_timeline_node) |
1050 | 0 | fragment_timeline_node = find_child_node_by_name(fragment_template_node, "SegmentTimeline"); |
1051 | 0 | if (!fragment_timeline_node) |
1052 | 0 | fragment_timeline_node = find_child_node_by_name(adaptionset_segmentlist_node, "SegmentTimeline"); |
1053 | 0 | if (!fragment_timeline_node) |
1054 | 0 | fragment_timeline_node = find_child_node_by_name(period_segmentlist_node, "SegmentTimeline"); |
1055 | 0 | if (fragment_timeline_node) { |
1056 | 0 | fragment_timeline_node = xmlFirstElementChild(fragment_timeline_node); |
1057 | 0 | while (fragment_timeline_node) { |
1058 | 0 | ret = parse_manifest_segmenttimeline(s, rep, fragment_timeline_node); |
1059 | 0 | if (ret < 0) |
1060 | 0 | goto free; |
1061 | 0 | fragment_timeline_node = xmlNextElementSibling(fragment_timeline_node); |
1062 | 0 | } |
1063 | 0 | } |
1064 | 0 | } else if (representation_baseurl_node && !representation_segmentlist_node) { |
1065 | 0 | seg = av_mallocz(sizeof(struct fragment)); |
1066 | 0 | if (!seg) |
1067 | 0 | goto enomem; |
1068 | 0 | ret = av_dynarray_add_nofree(&rep->fragments, &rep->n_fragments, seg); |
1069 | 0 | if (ret < 0) { |
1070 | 0 | av_free(seg); |
1071 | 0 | goto free; |
1072 | 0 | } |
1073 | 0 | seg->url = get_content_url(baseurl_nodes, 4, c->max_url_size, |
1074 | 0 | rep->id, rep_bandwidth_val, NULL); |
1075 | 0 | if (!seg->url) |
1076 | 0 | goto enomem; |
1077 | 0 | seg->size = -1; |
1078 | 0 | } else if (representation_segmentlist_node) { |
1079 | | // TODO: https://www.brendanlong.com/the-structure-of-an-mpeg-dash-mpd.html |
1080 | | // http://www-itec.uni-klu.ac.at/dash/ddash/mpdGenerator.php?fragmentlength=15&type=full |
1081 | 0 | xmlNodePtr fragmenturl_node = NULL; |
1082 | 0 | segmentlists_tab[0] = representation_segmentlist_node; |
1083 | 0 | segmentlists_tab[1] = adaptionset_segmentlist_node; |
1084 | 0 | segmentlists_tab[2] = period_segmentlist_node; |
1085 | |
|
1086 | 0 | SET_REPRESENTATION_SEQUENCE_BASE_INFO(segmentlists_tab, 3) |
1087 | 0 | fragmenturl_node = xmlFirstElementChild(representation_segmentlist_node); |
1088 | 0 | while (fragmenturl_node) { |
1089 | 0 | ret = parse_manifest_segmenturlnode(s, rep, fragmenturl_node, |
1090 | 0 | baseurl_nodes, rep->id, |
1091 | 0 | rep_bandwidth_val); |
1092 | 0 | if (ret < 0) |
1093 | 0 | goto free; |
1094 | 0 | fragmenturl_node = xmlNextElementSibling(fragmenturl_node); |
1095 | 0 | } |
1096 | | |
1097 | 0 | fragment_timeline_node = find_child_node_by_name(adaptionset_segmentlist_node, "SegmentTimeline"); |
1098 | 0 | if (!fragment_timeline_node) |
1099 | 0 | fragment_timeline_node = find_child_node_by_name(period_segmentlist_node, "SegmentTimeline"); |
1100 | 0 | if (fragment_timeline_node) { |
1101 | 0 | fragment_timeline_node = xmlFirstElementChild(fragment_timeline_node); |
1102 | 0 | while (fragment_timeline_node) { |
1103 | 0 | ret = parse_manifest_segmenttimeline(s, rep, fragment_timeline_node); |
1104 | 0 | if (ret < 0) |
1105 | 0 | goto free; |
1106 | 0 | fragment_timeline_node = xmlNextElementSibling(fragment_timeline_node); |
1107 | 0 | } |
1108 | 0 | } |
1109 | 0 | } else { |
1110 | 0 | av_log(s, AV_LOG_ERROR, "Unknown format of Representation node id '%s' \n", |
1111 | 0 | rep->id ? rep->id : ""); |
1112 | 0 | goto free; |
1113 | 0 | } |
1114 | | |
1115 | 0 | if (rep->fragment_duration > 0 && !rep->fragment_timescale) |
1116 | 0 | rep->fragment_timescale = 1; |
1117 | 0 | rep->bandwidth = rep_bandwidth_val ? atoi(rep_bandwidth_val) : 0; |
1118 | 0 | if (rep_dependencyid_val) { |
1119 | 0 | rep->dependencyid = av_strdup(rep_dependencyid_val); |
1120 | 0 | if (!rep->dependencyid) { |
1121 | 0 | xmlFree(rep_dependencyid_val); |
1122 | 0 | goto enomem; |
1123 | 0 | } |
1124 | 0 | } |
1125 | 0 | if (rep_codecs_val) { |
1126 | 0 | rep->codecs = av_strdup(rep_codecs_val); |
1127 | 0 | if (!rep->codecs) { |
1128 | 0 | xmlFree(rep_codecs_val); |
1129 | 0 | goto enomem; |
1130 | 0 | } |
1131 | 0 | } |
1132 | 0 | rep->framerate = av_make_q(0, 0); |
1133 | 0 | if (type == AVMEDIA_TYPE_VIDEO) { |
1134 | 0 | char *rep_framerate_val = xmlGetProp(representation_node, "frameRate"); |
1135 | 0 | if (rep_framerate_val) { |
1136 | 0 | ret = av_parse_video_rate(&rep->framerate, rep_framerate_val); |
1137 | 0 | if (ret < 0) |
1138 | 0 | av_log(s, AV_LOG_VERBOSE, "Ignoring invalid frame rate '%s'\n", rep_framerate_val); |
1139 | 0 | xmlFree(rep_framerate_val); |
1140 | 0 | } |
1141 | 0 | } |
1142 | |
|
1143 | 0 | switch (type) { |
1144 | 0 | case AVMEDIA_TYPE_VIDEO: |
1145 | 0 | ret = av_dynarray_add_nofree(&c->videos, &c->n_videos, rep); |
1146 | 0 | break; |
1147 | 0 | case AVMEDIA_TYPE_AUDIO: |
1148 | 0 | ret = av_dynarray_add_nofree(&c->audios, &c->n_audios, rep); |
1149 | 0 | break; |
1150 | 0 | case AVMEDIA_TYPE_SUBTITLE: |
1151 | 0 | ret = av_dynarray_add_nofree(&c->subtitles, &c->n_subtitles, rep); |
1152 | 0 | break; |
1153 | 0 | } |
1154 | 0 | if (ret < 0) |
1155 | 0 | goto free; |
1156 | | |
1157 | 0 | end: |
1158 | 0 | if (rep_bandwidth_val) |
1159 | 0 | xmlFree(rep_bandwidth_val); |
1160 | 0 | if (rep_dependencyid_val) |
1161 | 0 | xmlFree(rep_dependencyid_val); |
1162 | 0 | if (rep_codecs_val) |
1163 | 0 | xmlFree(rep_codecs_val); |
1164 | |
|
1165 | 0 | return ret; |
1166 | 0 | enomem: |
1167 | 0 | ret = AVERROR(ENOMEM); |
1168 | 0 | free: |
1169 | 0 | free_representation(rep); |
1170 | 0 | goto end; |
1171 | 0 | } |
1172 | | |
1173 | | static int parse_manifest_adaptationset_attr(AVFormatContext *s, xmlNodePtr adaptionset_node) |
1174 | 0 | { |
1175 | 0 | DASHContext *c = s->priv_data; |
1176 | |
|
1177 | 0 | if (!adaptionset_node) { |
1178 | 0 | av_log(s, AV_LOG_WARNING, "Cannot get AdaptionSet\n"); |
1179 | 0 | return AVERROR(EINVAL); |
1180 | 0 | } |
1181 | 0 | c->adaptionset_lang = xmlGetProp(adaptionset_node, "lang"); |
1182 | |
|
1183 | 0 | return 0; |
1184 | 0 | } |
1185 | | |
1186 | | static int parse_manifest_adaptationset(AVFormatContext *s, const char *url, |
1187 | | xmlNodePtr adaptionset_node, |
1188 | | xmlNodePtr mpd_baseurl_node, |
1189 | | xmlNodePtr period_baseurl_node, |
1190 | | xmlNodePtr period_segmenttemplate_node, |
1191 | | xmlNodePtr period_segmentlist_node) |
1192 | 0 | { |
1193 | 0 | int ret = 0; |
1194 | 0 | DASHContext *c = s->priv_data; |
1195 | 0 | xmlNodePtr fragment_template_node = NULL; |
1196 | 0 | xmlNodePtr content_component_node = NULL; |
1197 | 0 | xmlNodePtr adaptionset_baseurl_node = NULL; |
1198 | 0 | xmlNodePtr adaptionset_segmentlist_node = NULL; |
1199 | 0 | xmlNodePtr adaptionset_supplementalproperty_node = NULL; |
1200 | 0 | xmlNodePtr node = NULL; |
1201 | |
|
1202 | 0 | ret = parse_manifest_adaptationset_attr(s, adaptionset_node); |
1203 | 0 | if (ret < 0) |
1204 | 0 | return ret; |
1205 | | |
1206 | 0 | node = xmlFirstElementChild(adaptionset_node); |
1207 | 0 | while (node) { |
1208 | 0 | if (!av_strcasecmp(node->name, "SegmentTemplate")) { |
1209 | 0 | fragment_template_node = node; |
1210 | 0 | } else if (!av_strcasecmp(node->name, "ContentComponent")) { |
1211 | 0 | content_component_node = node; |
1212 | 0 | } else if (!av_strcasecmp(node->name, "BaseURL")) { |
1213 | 0 | adaptionset_baseurl_node = node; |
1214 | 0 | } else if (!av_strcasecmp(node->name, "SegmentList")) { |
1215 | 0 | adaptionset_segmentlist_node = node; |
1216 | 0 | } else if (!av_strcasecmp(node->name, "SupplementalProperty")) { |
1217 | 0 | adaptionset_supplementalproperty_node = node; |
1218 | 0 | } else if (!av_strcasecmp(node->name, "Representation")) { |
1219 | 0 | ret = parse_manifest_representation(s, url, node, |
1220 | 0 | adaptionset_node, |
1221 | 0 | mpd_baseurl_node, |
1222 | 0 | period_baseurl_node, |
1223 | 0 | period_segmenttemplate_node, |
1224 | 0 | period_segmentlist_node, |
1225 | 0 | fragment_template_node, |
1226 | 0 | content_component_node, |
1227 | 0 | adaptionset_baseurl_node, |
1228 | 0 | adaptionset_segmentlist_node, |
1229 | 0 | adaptionset_supplementalproperty_node); |
1230 | 0 | if (ret < 0) |
1231 | 0 | goto err; |
1232 | 0 | } |
1233 | 0 | node = xmlNextElementSibling(node); |
1234 | 0 | } |
1235 | | |
1236 | 0 | err: |
1237 | 0 | xmlFree(c->adaptionset_lang); |
1238 | 0 | c->adaptionset_lang = NULL; |
1239 | 0 | return ret; |
1240 | 0 | } |
1241 | | |
1242 | | static int parse_programinformation(AVFormatContext *s, xmlNodePtr node) |
1243 | 0 | { |
1244 | 0 | xmlChar *val = NULL; |
1245 | |
|
1246 | 0 | node = xmlFirstElementChild(node); |
1247 | 0 | while (node) { |
1248 | 0 | if (!av_strcasecmp(node->name, "Title")) { |
1249 | 0 | val = xmlNodeGetContent(node); |
1250 | 0 | if (val) { |
1251 | 0 | av_dict_set(&s->metadata, "Title", val, 0); |
1252 | 0 | } |
1253 | 0 | } else if (!av_strcasecmp(node->name, "Source")) { |
1254 | 0 | val = xmlNodeGetContent(node); |
1255 | 0 | if (val) { |
1256 | 0 | av_dict_set(&s->metadata, "Source", val, 0); |
1257 | 0 | } |
1258 | 0 | } else if (!av_strcasecmp(node->name, "Copyright")) { |
1259 | 0 | val = xmlNodeGetContent(node); |
1260 | 0 | if (val) { |
1261 | 0 | av_dict_set(&s->metadata, "Copyright", val, 0); |
1262 | 0 | } |
1263 | 0 | } |
1264 | 0 | node = xmlNextElementSibling(node); |
1265 | 0 | xmlFree(val); |
1266 | 0 | val = NULL; |
1267 | 0 | } |
1268 | 0 | return 0; |
1269 | 0 | } |
1270 | | |
1271 | | static int parse_manifest(AVFormatContext *s, const char *url, AVIOContext *in) |
1272 | 16.8k | { |
1273 | 16.8k | DASHContext *c = s->priv_data; |
1274 | 16.8k | int ret = 0; |
1275 | 16.8k | int close_in = 0; |
1276 | 16.8k | AVBPrint buf; |
1277 | 16.8k | AVDictionary *opts = NULL; |
1278 | 16.8k | xmlDoc *doc = NULL; |
1279 | 16.8k | xmlNodePtr root_element = NULL; |
1280 | 16.8k | xmlNodePtr node = NULL; |
1281 | 16.8k | xmlNodePtr period_node = NULL; |
1282 | 16.8k | xmlNodePtr tmp_node = NULL; |
1283 | 16.8k | xmlNodePtr mpd_baseurl_node = NULL; |
1284 | 16.8k | xmlNodePtr period_baseurl_node = NULL; |
1285 | 16.8k | xmlNodePtr period_segmenttemplate_node = NULL; |
1286 | 16.8k | xmlNodePtr period_segmentlist_node = NULL; |
1287 | 16.8k | xmlNodePtr adaptionset_node = NULL; |
1288 | 16.8k | xmlAttrPtr attr = NULL; |
1289 | 16.8k | char *val = NULL; |
1290 | 16.8k | uint32_t period_duration_sec = 0; |
1291 | 16.8k | uint32_t period_start_sec = 0; |
1292 | | |
1293 | 16.8k | if (!in) { |
1294 | 0 | close_in = 1; |
1295 | |
|
1296 | 0 | av_dict_copy(&opts, c->avio_opts, 0); |
1297 | 0 | ret = s->io_open(s, &in, url, AVIO_FLAG_READ, &opts); |
1298 | 0 | av_dict_free(&opts); |
1299 | 0 | if (ret < 0) |
1300 | 0 | return ret; |
1301 | 0 | } |
1302 | | |
1303 | 16.8k | if (av_opt_get(in, "location", AV_OPT_SEARCH_CHILDREN, (uint8_t**)&c->base_url) < 0) |
1304 | 16.8k | c->base_url = av_strdup(url); |
1305 | | |
1306 | 16.8k | av_bprint_init(&buf, 0, INT_MAX); // xmlReadMemory uses integer bufsize |
1307 | | |
1308 | 16.8k | if ((ret = avio_read_to_bprint(in, &buf, SIZE_MAX)) < 0 || |
1309 | 16.8k | !avio_feof(in)) { |
1310 | 0 | av_log(s, AV_LOG_ERROR, "Unable to read to manifest '%s'\n", url); |
1311 | 0 | if (ret == 0) |
1312 | 0 | ret = AVERROR_INVALIDDATA; |
1313 | 16.8k | } else { |
1314 | 16.8k | LIBXML_TEST_VERSION |
1315 | | |
1316 | 16.8k | doc = xmlReadMemory(buf.str, buf.len, c->base_url, NULL, 0); |
1317 | 16.8k | root_element = xmlDocGetRootElement(doc); |
1318 | 16.8k | node = root_element; |
1319 | | |
1320 | 16.8k | if (!node) { |
1321 | 16.7k | ret = AVERROR_INVALIDDATA; |
1322 | 16.7k | av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - missing root node\n", url); |
1323 | 16.7k | goto cleanup; |
1324 | 16.7k | } |
1325 | | |
1326 | 43 | if (node->type != XML_ELEMENT_NODE || |
1327 | 43 | av_strcasecmp(node->name, "MPD")) { |
1328 | 42 | ret = AVERROR_INVALIDDATA; |
1329 | 42 | av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - wrong root node name[%s] type[%d]\n", url, node->name, (int)node->type); |
1330 | 42 | goto cleanup; |
1331 | 42 | } |
1332 | | |
1333 | 1 | val = xmlGetProp(node, "type"); |
1334 | 1 | if (!val) { |
1335 | 1 | av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - missing type attrib\n", url); |
1336 | 1 | ret = AVERROR_INVALIDDATA; |
1337 | 1 | goto cleanup; |
1338 | 1 | } |
1339 | 0 | if (!av_strcasecmp(val, "dynamic")) |
1340 | 0 | c->is_live = 1; |
1341 | 0 | xmlFree(val); |
1342 | |
|
1343 | 0 | attr = node->properties; |
1344 | 0 | while (attr) { |
1345 | 0 | val = xmlGetProp(node, attr->name); |
1346 | |
|
1347 | 0 | if (!av_strcasecmp(attr->name, "availabilityStartTime")) { |
1348 | 0 | c->availability_start_time = get_utc_date_time_insec(s, val); |
1349 | 0 | av_log(s, AV_LOG_TRACE, "c->availability_start_time = [%"PRId64"]\n", c->availability_start_time); |
1350 | 0 | } else if (!av_strcasecmp(attr->name, "availabilityEndTime")) { |
1351 | 0 | c->availability_end_time = get_utc_date_time_insec(s, val); |
1352 | 0 | av_log(s, AV_LOG_TRACE, "c->availability_end_time = [%"PRId64"]\n", c->availability_end_time); |
1353 | 0 | } else if (!av_strcasecmp(attr->name, "publishTime")) { |
1354 | 0 | c->publish_time = get_utc_date_time_insec(s, val); |
1355 | 0 | av_log(s, AV_LOG_TRACE, "c->publish_time = [%"PRId64"]\n", c->publish_time); |
1356 | 0 | } else if (!av_strcasecmp(attr->name, "minimumUpdatePeriod")) { |
1357 | 0 | c->minimum_update_period = get_duration_insec(s, val); |
1358 | 0 | av_log(s, AV_LOG_TRACE, "c->minimum_update_period = [%"PRId64"]\n", c->minimum_update_period); |
1359 | 0 | } else if (!av_strcasecmp(attr->name, "timeShiftBufferDepth")) { |
1360 | 0 | c->time_shift_buffer_depth = get_duration_insec(s, val); |
1361 | 0 | av_log(s, AV_LOG_TRACE, "c->time_shift_buffer_depth = [%"PRId64"]\n", c->time_shift_buffer_depth); |
1362 | 0 | } else if (!av_strcasecmp(attr->name, "minBufferTime")) { |
1363 | 0 | c->min_buffer_time = get_duration_insec(s, val); |
1364 | 0 | av_log(s, AV_LOG_TRACE, "c->min_buffer_time = [%"PRId64"]\n", c->min_buffer_time); |
1365 | 0 | } else if (!av_strcasecmp(attr->name, "suggestedPresentationDelay")) { |
1366 | 0 | c->suggested_presentation_delay = get_duration_insec(s, val); |
1367 | 0 | av_log(s, AV_LOG_TRACE, "c->suggested_presentation_delay = [%"PRId64"]\n", c->suggested_presentation_delay); |
1368 | 0 | } else if (!av_strcasecmp(attr->name, "mediaPresentationDuration")) { |
1369 | 0 | c->media_presentation_duration = get_duration_insec(s, val); |
1370 | 0 | av_log(s, AV_LOG_TRACE, "c->media_presentation_duration = [%"PRId64"]\n", c->media_presentation_duration); |
1371 | 0 | } |
1372 | 0 | attr = attr->next; |
1373 | 0 | xmlFree(val); |
1374 | 0 | } |
1375 | |
|
1376 | 0 | tmp_node = find_child_node_by_name(node, "BaseURL"); |
1377 | 0 | if (tmp_node) { |
1378 | 0 | mpd_baseurl_node = xmlCopyNode(tmp_node,1); |
1379 | 0 | } else { |
1380 | 0 | mpd_baseurl_node = xmlNewNode(NULL, "BaseURL"); |
1381 | 0 | } |
1382 | | |
1383 | | // at now we can handle only one period, with the longest duration |
1384 | 0 | node = xmlFirstElementChild(node); |
1385 | 0 | while (node) { |
1386 | 0 | if (!av_strcasecmp(node->name, "Period")) { |
1387 | 0 | period_duration_sec = 0; |
1388 | 0 | period_start_sec = 0; |
1389 | 0 | attr = node->properties; |
1390 | 0 | while (attr) { |
1391 | 0 | val = xmlGetProp(node, attr->name); |
1392 | 0 | if (!av_strcasecmp(attr->name, "duration")) { |
1393 | 0 | period_duration_sec = get_duration_insec(s, val); |
1394 | 0 | } else if (!av_strcasecmp(attr->name, "start")) { |
1395 | 0 | period_start_sec = get_duration_insec(s, val); |
1396 | 0 | } |
1397 | 0 | attr = attr->next; |
1398 | 0 | xmlFree(val); |
1399 | 0 | } |
1400 | 0 | if ((period_duration_sec) >= (c->period_duration)) { |
1401 | 0 | period_node = node; |
1402 | 0 | c->period_duration = period_duration_sec; |
1403 | 0 | c->period_start = period_start_sec; |
1404 | 0 | if (c->period_start > 0) |
1405 | 0 | c->media_presentation_duration = c->period_duration; |
1406 | 0 | } |
1407 | 0 | } else if (!av_strcasecmp(node->name, "ProgramInformation")) { |
1408 | 0 | parse_programinformation(s, node); |
1409 | 0 | } |
1410 | 0 | node = xmlNextElementSibling(node); |
1411 | 0 | } |
1412 | 0 | if (!period_node) { |
1413 | 0 | av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - missing Period node\n", url); |
1414 | 0 | ret = AVERROR_INVALIDDATA; |
1415 | 0 | goto cleanup; |
1416 | 0 | } |
1417 | | |
1418 | 0 | adaptionset_node = xmlFirstElementChild(period_node); |
1419 | 0 | while (adaptionset_node) { |
1420 | 0 | if (!av_strcasecmp(adaptionset_node->name, "BaseURL")) { |
1421 | 0 | period_baseurl_node = adaptionset_node; |
1422 | 0 | } else if (!av_strcasecmp(adaptionset_node->name, "SegmentTemplate")) { |
1423 | 0 | period_segmenttemplate_node = adaptionset_node; |
1424 | 0 | } else if (!av_strcasecmp(adaptionset_node->name, "SegmentList")) { |
1425 | 0 | period_segmentlist_node = adaptionset_node; |
1426 | 0 | } else if (!av_strcasecmp(adaptionset_node->name, "AdaptationSet")) { |
1427 | 0 | parse_manifest_adaptationset(s, url, adaptionset_node, mpd_baseurl_node, period_baseurl_node, period_segmenttemplate_node, period_segmentlist_node); |
1428 | 0 | } |
1429 | 0 | adaptionset_node = xmlNextElementSibling(adaptionset_node); |
1430 | 0 | } |
1431 | 16.8k | cleanup: |
1432 | | /*free the document */ |
1433 | 16.8k | xmlFreeDoc(doc); |
1434 | 16.8k | xmlCleanupParser(); |
1435 | 16.8k | xmlFreeNode(mpd_baseurl_node); |
1436 | 16.8k | } |
1437 | | |
1438 | 16.8k | av_bprint_finalize(&buf, NULL); |
1439 | 16.8k | if (close_in) { |
1440 | 0 | ff_format_io_close(s, &in); |
1441 | 0 | } |
1442 | 16.8k | return ret; |
1443 | 16.8k | } |
1444 | | |
1445 | | static int64_t calc_cur_seg_no(AVFormatContext *s, struct representation *pls) |
1446 | 0 | { |
1447 | 0 | DASHContext *c = s->priv_data; |
1448 | 0 | int64_t num = 0; |
1449 | 0 | int64_t start_time_offset = 0; |
1450 | |
|
1451 | 0 | if (c->is_live) { |
1452 | 0 | if (pls->n_fragments) { |
1453 | 0 | av_log(s, AV_LOG_TRACE, "in n_fragments mode\n"); |
1454 | 0 | num = pls->first_seq_no; |
1455 | 0 | } else if (pls->n_timelines) { |
1456 | 0 | av_log(s, AV_LOG_TRACE, "in n_timelines mode\n"); |
1457 | 0 | start_time_offset = get_segment_start_time_based_on_timeline(pls, 0xFFFFFFFF) - 60 * pls->fragment_timescale; // 60 seconds before end |
1458 | 0 | num = calc_next_seg_no_from_timelines(pls, start_time_offset); |
1459 | 0 | if (num == -1) |
1460 | 0 | num = pls->first_seq_no; |
1461 | 0 | } else if (pls->fragment_duration){ |
1462 | 0 | av_log(s, AV_LOG_TRACE, "in fragment_duration mode fragment_timescale = %"PRId64", presentation_timeoffset = %"PRId64"\n", pls->fragment_timescale, pls->presentation_timeoffset); |
1463 | 0 | if (pls->presentation_timeoffset) { |
1464 | 0 | num = pls->first_seq_no + ((get_current_time_in_sec() - c->availability_start_time) * pls->fragment_timescale) / pls->fragment_duration - c->min_buffer_time; |
1465 | 0 | } else if (c->publish_time > 0 && !c->availability_start_time) { |
1466 | 0 | if (c->min_buffer_time) { |
1467 | 0 | num = pls->first_seq_no + (((c->publish_time + pls->fragment_duration) - c->suggested_presentation_delay) * pls->fragment_timescale) / pls->fragment_duration - c->min_buffer_time; |
1468 | 0 | } else { |
1469 | 0 | num = pls->first_seq_no + (((c->publish_time - c->time_shift_buffer_depth + pls->fragment_duration) - c->suggested_presentation_delay) * pls->fragment_timescale) / pls->fragment_duration; |
1470 | 0 | } |
1471 | 0 | } else { |
1472 | 0 | num = pls->first_seq_no + (((get_current_time_in_sec() - c->availability_start_time) - c->suggested_presentation_delay) * pls->fragment_timescale) / pls->fragment_duration; |
1473 | 0 | } |
1474 | 0 | } |
1475 | 0 | } else { |
1476 | 0 | num = pls->first_seq_no; |
1477 | 0 | } |
1478 | 0 | return num; |
1479 | 0 | } |
1480 | | |
1481 | | static int64_t calc_min_seg_no(AVFormatContext *s, struct representation *pls) |
1482 | 0 | { |
1483 | 0 | DASHContext *c = s->priv_data; |
1484 | 0 | int64_t num = 0; |
1485 | |
|
1486 | 0 | if (c->is_live && pls->fragment_duration) { |
1487 | 0 | av_log(s, AV_LOG_TRACE, "in live mode\n"); |
1488 | 0 | num = pls->first_seq_no + (((get_current_time_in_sec() - c->availability_start_time) - c->time_shift_buffer_depth) * pls->fragment_timescale) / pls->fragment_duration; |
1489 | 0 | } else { |
1490 | 0 | num = pls->first_seq_no; |
1491 | 0 | } |
1492 | 0 | return num; |
1493 | 0 | } |
1494 | | |
1495 | | static int64_t calc_max_seg_no(struct representation *pls, DASHContext *c) |
1496 | 0 | { |
1497 | 0 | int64_t num = 0; |
1498 | |
|
1499 | 0 | if (pls->n_fragments) { |
1500 | 0 | num = pls->first_seq_no + pls->n_fragments - 1; |
1501 | 0 | } else if (pls->n_timelines) { |
1502 | 0 | int i = 0; |
1503 | 0 | num = pls->first_seq_no + pls->n_timelines - 1; |
1504 | 0 | for (i = 0; i < pls->n_timelines; i++) { |
1505 | 0 | if (pls->timelines[i]->repeat == -1) { |
1506 | 0 | num = pls->timelines[i]->duration ? av_rescale(c->period_duration, pls->fragment_timescale, pls->timelines[i]->duration) : pls->first_seq_no; |
1507 | 0 | } else { |
1508 | 0 | num += pls->timelines[i]->repeat; |
1509 | 0 | } |
1510 | 0 | } |
1511 | 0 | } else if (c->is_live && pls->fragment_duration) { |
1512 | 0 | num = pls->first_seq_no + (((get_current_time_in_sec() - c->availability_start_time)) * pls->fragment_timescale) / pls->fragment_duration; |
1513 | 0 | } else if (pls->fragment_duration) { |
1514 | 0 | num = pls->first_seq_no + av_rescale_rnd(1, c->media_presentation_duration * pls->fragment_timescale, pls->fragment_duration, AV_ROUND_UP); |
1515 | 0 | } |
1516 | |
|
1517 | 0 | return num; |
1518 | 0 | } |
1519 | | |
1520 | | static void move_timelines(struct representation *rep_src, struct representation *rep_dest, DASHContext *c) |
1521 | 0 | { |
1522 | 0 | if (rep_dest && rep_src ) { |
1523 | 0 | free_timelines_list(rep_dest); |
1524 | 0 | rep_dest->timelines = rep_src->timelines; |
1525 | 0 | rep_dest->n_timelines = rep_src->n_timelines; |
1526 | 0 | rep_dest->first_seq_no = rep_src->first_seq_no; |
1527 | 0 | rep_dest->last_seq_no = calc_max_seg_no(rep_dest, c); |
1528 | 0 | rep_src->timelines = NULL; |
1529 | 0 | rep_src->n_timelines = 0; |
1530 | 0 | rep_dest->cur_seq_no = rep_src->cur_seq_no; |
1531 | 0 | } |
1532 | 0 | } |
1533 | | |
1534 | | static void move_segments(struct representation *rep_src, struct representation *rep_dest, DASHContext *c) |
1535 | 0 | { |
1536 | 0 | if (rep_dest && rep_src ) { |
1537 | 0 | free_fragment_list(rep_dest); |
1538 | 0 | if (rep_src->start_number > (rep_dest->start_number + rep_dest->n_fragments)) |
1539 | 0 | rep_dest->cur_seq_no = 0; |
1540 | 0 | else |
1541 | 0 | rep_dest->cur_seq_no += rep_src->start_number - rep_dest->start_number; |
1542 | 0 | rep_dest->fragments = rep_src->fragments; |
1543 | 0 | rep_dest->n_fragments = rep_src->n_fragments; |
1544 | 0 | rep_dest->parent = rep_src->parent; |
1545 | 0 | rep_dest->last_seq_no = calc_max_seg_no(rep_dest, c); |
1546 | 0 | rep_src->fragments = NULL; |
1547 | 0 | rep_src->n_fragments = 0; |
1548 | 0 | } |
1549 | 0 | } |
1550 | | |
1551 | | |
1552 | | static int refresh_manifest(AVFormatContext *s) |
1553 | 0 | { |
1554 | 0 | int ret = 0, i; |
1555 | 0 | DASHContext *c = s->priv_data; |
1556 | | // save current context |
1557 | 0 | int n_videos = c->n_videos; |
1558 | 0 | struct representation **videos = c->videos; |
1559 | 0 | int n_audios = c->n_audios; |
1560 | 0 | struct representation **audios = c->audios; |
1561 | 0 | int n_subtitles = c->n_subtitles; |
1562 | 0 | struct representation **subtitles = c->subtitles; |
1563 | 0 | char *base_url = c->base_url; |
1564 | |
|
1565 | 0 | c->base_url = NULL; |
1566 | 0 | c->n_videos = 0; |
1567 | 0 | c->videos = NULL; |
1568 | 0 | c->n_audios = 0; |
1569 | 0 | c->audios = NULL; |
1570 | 0 | c->n_subtitles = 0; |
1571 | 0 | c->subtitles = NULL; |
1572 | 0 | ret = parse_manifest(s, s->url, NULL); |
1573 | 0 | if (ret) |
1574 | 0 | goto finish; |
1575 | | |
1576 | 0 | if (c->n_videos != n_videos) { |
1577 | 0 | av_log(c, AV_LOG_ERROR, |
1578 | 0 | "new manifest has mismatched no. of video representations, %d -> %d\n", |
1579 | 0 | n_videos, c->n_videos); |
1580 | 0 | return AVERROR_INVALIDDATA; |
1581 | 0 | } |
1582 | 0 | if (c->n_audios != n_audios) { |
1583 | 0 | av_log(c, AV_LOG_ERROR, |
1584 | 0 | "new manifest has mismatched no. of audio representations, %d -> %d\n", |
1585 | 0 | n_audios, c->n_audios); |
1586 | 0 | return AVERROR_INVALIDDATA; |
1587 | 0 | } |
1588 | 0 | if (c->n_subtitles != n_subtitles) { |
1589 | 0 | av_log(c, AV_LOG_ERROR, |
1590 | 0 | "new manifest has mismatched no. of subtitles representations, %d -> %d\n", |
1591 | 0 | n_subtitles, c->n_subtitles); |
1592 | 0 | return AVERROR_INVALIDDATA; |
1593 | 0 | } |
1594 | | |
1595 | 0 | for (i = 0; i < n_videos; i++) { |
1596 | 0 | struct representation *cur_video = videos[i]; |
1597 | 0 | struct representation *ccur_video = c->videos[i]; |
1598 | 0 | if (cur_video->timelines && cur_video->fragment_timescale > 0) { |
1599 | | // calc current time |
1600 | 0 | int64_t currentTime = get_segment_start_time_based_on_timeline(cur_video, cur_video->cur_seq_no) / cur_video->fragment_timescale; |
1601 | | // update segments |
1602 | 0 | ccur_video->cur_seq_no = calc_next_seg_no_from_timelines(ccur_video, currentTime * cur_video->fragment_timescale - 1); |
1603 | 0 | if (ccur_video->cur_seq_no >= 0) { |
1604 | 0 | move_timelines(ccur_video, cur_video, c); |
1605 | 0 | } |
1606 | 0 | } |
1607 | 0 | if (cur_video->fragments) { |
1608 | 0 | move_segments(ccur_video, cur_video, c); |
1609 | 0 | } |
1610 | 0 | } |
1611 | 0 | for (i = 0; i < n_audios; i++) { |
1612 | 0 | struct representation *cur_audio = audios[i]; |
1613 | 0 | struct representation *ccur_audio = c->audios[i]; |
1614 | 0 | if (cur_audio->timelines && cur_audio->fragment_timescale > 0) { |
1615 | | // calc current time |
1616 | 0 | int64_t currentTime = get_segment_start_time_based_on_timeline(cur_audio, cur_audio->cur_seq_no) / cur_audio->fragment_timescale; |
1617 | | // update segments |
1618 | 0 | ccur_audio->cur_seq_no = calc_next_seg_no_from_timelines(ccur_audio, currentTime * cur_audio->fragment_timescale - 1); |
1619 | 0 | if (ccur_audio->cur_seq_no >= 0) { |
1620 | 0 | move_timelines(ccur_audio, cur_audio, c); |
1621 | 0 | } |
1622 | 0 | } |
1623 | 0 | if (cur_audio->fragments) { |
1624 | 0 | move_segments(ccur_audio, cur_audio, c); |
1625 | 0 | } |
1626 | 0 | } |
1627 | |
|
1628 | 0 | finish: |
1629 | | // restore context |
1630 | 0 | if (c->base_url) |
1631 | 0 | av_free(base_url); |
1632 | 0 | else |
1633 | 0 | c->base_url = base_url; |
1634 | |
|
1635 | 0 | if (c->subtitles) |
1636 | 0 | free_subtitle_list(c); |
1637 | 0 | if (c->audios) |
1638 | 0 | free_audio_list(c); |
1639 | 0 | if (c->videos) |
1640 | 0 | free_video_list(c); |
1641 | |
|
1642 | 0 | c->n_subtitles = n_subtitles; |
1643 | 0 | c->subtitles = subtitles; |
1644 | 0 | c->n_audios = n_audios; |
1645 | 0 | c->audios = audios; |
1646 | 0 | c->n_videos = n_videos; |
1647 | 0 | c->videos = videos; |
1648 | 0 | return ret; |
1649 | 0 | } |
1650 | | |
1651 | | static struct fragment *get_current_fragment(struct representation *pls) |
1652 | 0 | { |
1653 | 0 | int64_t min_seq_no = 0; |
1654 | 0 | int64_t max_seq_no = 0; |
1655 | 0 | struct fragment *seg = NULL; |
1656 | 0 | struct fragment *seg_ptr = NULL; |
1657 | 0 | DASHContext *c = pls->parent->priv_data; |
1658 | 0 | int reload_count = 0; |
1659 | |
|
1660 | 0 | while (( !ff_check_interrupt(c->interrupt_callback)&& pls->n_fragments > 0)) { |
1661 | 0 | if (pls->cur_seq_no < pls->n_fragments) { |
1662 | 0 | seg_ptr = pls->fragments[pls->cur_seq_no]; |
1663 | 0 | seg = av_mallocz(sizeof(struct fragment)); |
1664 | 0 | if (!seg) { |
1665 | 0 | return NULL; |
1666 | 0 | } |
1667 | 0 | seg->url = av_strdup(seg_ptr->url); |
1668 | 0 | if (!seg->url) { |
1669 | 0 | av_free(seg); |
1670 | 0 | return NULL; |
1671 | 0 | } |
1672 | 0 | seg->size = seg_ptr->size; |
1673 | 0 | seg->url_offset = seg_ptr->url_offset; |
1674 | 0 | return seg; |
1675 | 0 | } else if (c->is_live) { |
1676 | 0 | if (reload_count++ >= c->max_reload) { |
1677 | 0 | av_log(pls->parent, AV_LOG_ERROR, |
1678 | 0 | "Reached max manifest reloads (%d) at seq %"PRId64"\n", |
1679 | 0 | c->max_reload, pls->cur_seq_no); |
1680 | 0 | return NULL; |
1681 | 0 | } |
1682 | 0 | refresh_manifest(pls->parent); |
1683 | 0 | } else { |
1684 | 0 | break; |
1685 | 0 | } |
1686 | 0 | } |
1687 | 0 | if (c->is_live) { |
1688 | 0 | min_seq_no = calc_min_seg_no(pls->parent, pls); |
1689 | 0 | max_seq_no = calc_max_seg_no(pls, c); |
1690 | |
|
1691 | 0 | if (pls->timelines || pls->fragments) { |
1692 | 0 | refresh_manifest(pls->parent); |
1693 | 0 | } |
1694 | 0 | if (pls->cur_seq_no <= min_seq_no) { |
1695 | 0 | av_log(pls->parent, AV_LOG_VERBOSE, "old fragment: cur[%"PRId64"] min[%"PRId64"] max[%"PRId64"]\n", (int64_t)pls->cur_seq_no, min_seq_no, max_seq_no); |
1696 | 0 | pls->cur_seq_no = calc_cur_seg_no(pls->parent, pls); |
1697 | 0 | } else if (pls->cur_seq_no > max_seq_no) { |
1698 | 0 | av_log(pls->parent, AV_LOG_VERBOSE, "new fragment: min[%"PRId64"] max[%"PRId64"]\n", min_seq_no, max_seq_no); |
1699 | 0 | } |
1700 | 0 | seg = av_mallocz(sizeof(struct fragment)); |
1701 | 0 | if (!seg) { |
1702 | 0 | return NULL; |
1703 | 0 | } |
1704 | 0 | } else if (pls->cur_seq_no <= pls->last_seq_no) { |
1705 | 0 | seg = av_mallocz(sizeof(struct fragment)); |
1706 | 0 | if (!seg) { |
1707 | 0 | return NULL; |
1708 | 0 | } |
1709 | 0 | } |
1710 | 0 | if (seg) { |
1711 | 0 | char *tmpfilename; |
1712 | 0 | if (!pls->url_template) { |
1713 | 0 | av_log(pls->parent, AV_LOG_ERROR, "Cannot get fragment, missing template URL\n"); |
1714 | 0 | av_free(seg); |
1715 | 0 | return NULL; |
1716 | 0 | } |
1717 | 0 | tmpfilename = av_mallocz(c->max_url_size); |
1718 | 0 | if (!tmpfilename) { |
1719 | 0 | av_free(seg); |
1720 | 0 | return NULL; |
1721 | 0 | } |
1722 | 0 | ff_dash_fill_tmpl_params(tmpfilename, c->max_url_size, pls->url_template, 0, pls->cur_seq_no, 0, get_segment_start_time_based_on_timeline(pls, pls->cur_seq_no)); |
1723 | 0 | seg->url = av_strireplace(pls->url_template, pls->url_template, tmpfilename); |
1724 | 0 | if (!seg->url) { |
1725 | 0 | av_log(pls->parent, AV_LOG_WARNING, "Unable to resolve template url '%s', try to use origin template\n", pls->url_template); |
1726 | 0 | seg->url = av_strdup(pls->url_template); |
1727 | 0 | if (!seg->url) { |
1728 | 0 | av_log(pls->parent, AV_LOG_ERROR, "Cannot resolve template url '%s'\n", pls->url_template); |
1729 | 0 | av_free(tmpfilename); |
1730 | 0 | av_free(seg); |
1731 | 0 | return NULL; |
1732 | 0 | } |
1733 | 0 | } |
1734 | 0 | av_free(tmpfilename); |
1735 | 0 | seg->size = -1; |
1736 | 0 | } |
1737 | | |
1738 | 0 | return seg; |
1739 | 0 | } |
1740 | | |
1741 | | static int read_from_url(struct representation *pls, struct fragment *seg, |
1742 | | uint8_t *buf, int buf_size) |
1743 | 0 | { |
1744 | 0 | int ret; |
1745 | | |
1746 | | /* limit read if the fragment was only a part of a file */ |
1747 | 0 | if (seg->size >= 0) |
1748 | 0 | buf_size = FFMIN(buf_size, pls->cur_seg_size - pls->cur_seg_offset); |
1749 | |
|
1750 | 0 | ret = avio_read(pls->input, buf, buf_size); |
1751 | 0 | if (ret > 0) |
1752 | 0 | pls->cur_seg_offset += ret; |
1753 | |
|
1754 | 0 | return ret; |
1755 | 0 | } |
1756 | | |
1757 | | static int open_input(DASHContext *c, struct representation *pls, struct fragment *seg) |
1758 | 0 | { |
1759 | 0 | AVDictionary *opts = NULL; |
1760 | 0 | char *url = NULL; |
1761 | 0 | int ret = 0; |
1762 | |
|
1763 | 0 | url = av_mallocz(c->max_url_size); |
1764 | 0 | if (!url) { |
1765 | 0 | ret = AVERROR(ENOMEM); |
1766 | 0 | goto cleanup; |
1767 | 0 | } |
1768 | | |
1769 | 0 | if (seg->size >= 0) { |
1770 | | /* try to restrict the HTTP request to the part we want |
1771 | | * (if this is in fact a HTTP request) */ |
1772 | 0 | av_dict_set_int(&opts, "offset", seg->url_offset, 0); |
1773 | 0 | av_dict_set_int(&opts, "end_offset", seg->url_offset + seg->size, 0); |
1774 | 0 | } |
1775 | |
|
1776 | 0 | ff_make_absolute_url(url, c->max_url_size, c->base_url, seg->url); |
1777 | 0 | av_log(pls->parent, AV_LOG_VERBOSE, "DASH request for url '%s', offset %"PRId64"\n", |
1778 | 0 | url, seg->url_offset); |
1779 | 0 | ret = open_url(pls->parent, &pls->input, url, &c->avio_opts, opts, NULL); |
1780 | |
|
1781 | 0 | cleanup: |
1782 | 0 | av_free(url); |
1783 | 0 | av_dict_free(&opts); |
1784 | 0 | pls->cur_seg_offset = 0; |
1785 | 0 | pls->cur_seg_size = seg->size; |
1786 | 0 | return ret; |
1787 | 0 | } |
1788 | | |
1789 | | static int update_init_section(struct representation *pls) |
1790 | 0 | { |
1791 | 0 | static const int max_init_section_size = 1024 * 1024; |
1792 | 0 | DASHContext *c = pls->parent->priv_data; |
1793 | 0 | int64_t sec_size; |
1794 | 0 | int64_t urlsize; |
1795 | 0 | int ret; |
1796 | |
|
1797 | 0 | if (!pls->init_section || pls->init_sec_buf) |
1798 | 0 | return 0; |
1799 | | |
1800 | 0 | ret = open_input(c, pls, pls->init_section); |
1801 | 0 | if (ret < 0) { |
1802 | 0 | av_log(pls->parent, AV_LOG_WARNING, |
1803 | 0 | "Failed to open an initialization section\n"); |
1804 | 0 | return ret; |
1805 | 0 | } |
1806 | | |
1807 | 0 | if (pls->init_section->size >= 0) |
1808 | 0 | sec_size = pls->init_section->size; |
1809 | 0 | else if ((urlsize = avio_size(pls->input)) >= 0) |
1810 | 0 | sec_size = urlsize; |
1811 | 0 | else |
1812 | 0 | sec_size = max_init_section_size; |
1813 | |
|
1814 | 0 | av_log(pls->parent, AV_LOG_DEBUG, |
1815 | 0 | "Downloading an initialization section of size %"PRId64"\n", |
1816 | 0 | sec_size); |
1817 | |
|
1818 | 0 | sec_size = FFMIN(sec_size, max_init_section_size); |
1819 | |
|
1820 | 0 | av_fast_malloc(&pls->init_sec_buf, &pls->init_sec_buf_size, sec_size); |
1821 | |
|
1822 | 0 | ret = read_from_url(pls, pls->init_section, pls->init_sec_buf, |
1823 | 0 | pls->init_sec_buf_size); |
1824 | 0 | ff_format_io_close(pls->parent, &pls->input); |
1825 | |
|
1826 | 0 | if (ret < 0) |
1827 | 0 | return ret; |
1828 | | |
1829 | 0 | pls->init_sec_data_len = ret; |
1830 | 0 | pls->init_sec_buf_read_offset = 0; |
1831 | |
|
1832 | 0 | return 0; |
1833 | 0 | } |
1834 | | |
1835 | | static int64_t seek_data(void *opaque, int64_t offset, int whence) |
1836 | 0 | { |
1837 | 0 | struct representation *v = opaque; |
1838 | 0 | if (v->n_fragments && !v->init_sec_data_len) { |
1839 | 0 | return avio_seek(v->input, offset, whence); |
1840 | 0 | } |
1841 | | |
1842 | 0 | return AVERROR(ENOSYS); |
1843 | 0 | } |
1844 | | |
1845 | | static int read_data(void *opaque, uint8_t *buf, int buf_size) |
1846 | 0 | { |
1847 | 0 | int ret = 0; |
1848 | 0 | struct representation *v = opaque; |
1849 | 0 | DASHContext *c = v->parent->priv_data; |
1850 | |
|
1851 | 0 | restart: |
1852 | 0 | if (!v->input) { |
1853 | 0 | free_fragment(&v->cur_seg); |
1854 | 0 | v->cur_seg = get_current_fragment(v); |
1855 | 0 | if (!v->cur_seg) { |
1856 | 0 | ret = AVERROR_EOF; |
1857 | 0 | goto end; |
1858 | 0 | } |
1859 | | |
1860 | | /* load/update Media Initialization Section, if any */ |
1861 | 0 | ret = update_init_section(v); |
1862 | 0 | if (ret) |
1863 | 0 | goto end; |
1864 | | |
1865 | 0 | ret = open_input(c, v, v->cur_seg); |
1866 | 0 | if (ret < 0) { |
1867 | 0 | if (ff_check_interrupt(c->interrupt_callback)) { |
1868 | 0 | ret = AVERROR_EXIT; |
1869 | 0 | goto end; |
1870 | 0 | } |
1871 | 0 | av_log(v->parent, AV_LOG_WARNING, "Failed to open fragment of playlist\n"); |
1872 | 0 | if (++v->n_open_failures > c->max_reload) { |
1873 | 0 | av_log(v->parent, AV_LOG_ERROR, |
1874 | 0 | "Reached max consecutive fragment open failures (%d), giving up\n", |
1875 | 0 | c->max_reload); |
1876 | 0 | ret = AVERROR_EOF; |
1877 | 0 | goto end; |
1878 | 0 | } |
1879 | 0 | v->cur_seq_no++; |
1880 | 0 | goto restart; |
1881 | 0 | } |
1882 | 0 | v->n_open_failures = 0; |
1883 | 0 | } |
1884 | | |
1885 | 0 | if (v->init_sec_buf_read_offset < v->init_sec_data_len) { |
1886 | | /* Push init section out first before first actual fragment */ |
1887 | 0 | int copy_size = FFMIN(v->init_sec_data_len - v->init_sec_buf_read_offset, buf_size); |
1888 | 0 | memcpy(buf, v->init_sec_buf, copy_size); |
1889 | 0 | v->init_sec_buf_read_offset += copy_size; |
1890 | 0 | ret = copy_size; |
1891 | 0 | goto end; |
1892 | 0 | } |
1893 | | |
1894 | | /* check the v->cur_seg, if it is null, get current and double check if the new v->cur_seg*/ |
1895 | 0 | if (!v->cur_seg) { |
1896 | 0 | v->cur_seg = get_current_fragment(v); |
1897 | 0 | } |
1898 | 0 | if (!v->cur_seg) { |
1899 | 0 | ret = AVERROR_EOF; |
1900 | 0 | goto end; |
1901 | 0 | } |
1902 | 0 | ret = read_from_url(v, v->cur_seg, buf, buf_size); |
1903 | 0 | if (ret > 0) |
1904 | 0 | goto end; |
1905 | | |
1906 | 0 | if (c->is_live || v->cur_seq_no < v->last_seq_no) { |
1907 | 0 | if (!v->is_restart_needed) |
1908 | 0 | v->cur_seq_no++; |
1909 | 0 | v->is_restart_needed = 1; |
1910 | 0 | } |
1911 | |
|
1912 | 0 | end: |
1913 | 0 | return ret; |
1914 | 0 | } |
1915 | | |
1916 | | static int nested_io_open(AVFormatContext *s, AVIOContext **pb, const char *url, |
1917 | | int flags, AVDictionary **opts) |
1918 | 0 | { |
1919 | 0 | av_log(s, AV_LOG_ERROR, |
1920 | 0 | "A DASH playlist item '%s' referred to an external file '%s'. " |
1921 | 0 | "Opening this file was forbidden for security reasons\n", |
1922 | 0 | s->url, url); |
1923 | 0 | return AVERROR(EPERM); |
1924 | 0 | } |
1925 | | |
1926 | | static void close_demux_for_component(struct representation *pls) |
1927 | 0 | { |
1928 | | /* note: the internal buffer could have changed */ |
1929 | 0 | av_freep(&pls->pb.pub.buffer); |
1930 | 0 | memset(&pls->pb, 0x00, sizeof(pls->pb)); |
1931 | 0 | pls->ctx->pb = NULL; |
1932 | 0 | avformat_close_input(&pls->ctx); |
1933 | 0 | } |
1934 | | |
1935 | | static int reopen_demux_for_component(AVFormatContext *s, struct representation *pls) |
1936 | 0 | { |
1937 | 0 | DASHContext *c = s->priv_data; |
1938 | 0 | const AVInputFormat *in_fmt = NULL; |
1939 | 0 | AVDictionary *in_fmt_opts = NULL; |
1940 | 0 | uint8_t *avio_ctx_buffer = NULL; |
1941 | 0 | int ret = 0, i; |
1942 | |
|
1943 | 0 | if (pls->ctx) { |
1944 | 0 | close_demux_for_component(pls); |
1945 | 0 | } |
1946 | |
|
1947 | 0 | if (ff_check_interrupt(&s->interrupt_callback)) { |
1948 | 0 | ret = AVERROR_EXIT; |
1949 | 0 | goto fail; |
1950 | 0 | } |
1951 | | |
1952 | 0 | if (!(pls->ctx = avformat_alloc_context())) { |
1953 | 0 | ret = AVERROR(ENOMEM); |
1954 | 0 | goto fail; |
1955 | 0 | } |
1956 | | |
1957 | 0 | avio_ctx_buffer = av_malloc(INITIAL_BUFFER_SIZE); |
1958 | 0 | if (!avio_ctx_buffer ) { |
1959 | 0 | ret = AVERROR(ENOMEM); |
1960 | 0 | avformat_free_context(pls->ctx); |
1961 | 0 | pls->ctx = NULL; |
1962 | 0 | goto fail; |
1963 | 0 | } |
1964 | 0 | ffio_init_context(&pls->pb, avio_ctx_buffer, INITIAL_BUFFER_SIZE, 0, |
1965 | 0 | pls, read_data, NULL, c->is_live ? NULL : seek_data); |
1966 | 0 | pls->pb.pub.seekable = 0; |
1967 | |
|
1968 | 0 | if ((ret = ff_copy_whiteblacklists(pls->ctx, s)) < 0) |
1969 | 0 | goto fail; |
1970 | | |
1971 | 0 | pls->ctx->flags = AVFMT_FLAG_CUSTOM_IO; |
1972 | 0 | pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * 4; |
1973 | 0 | pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? s->max_analyze_duration : 4 * AV_TIME_BASE; |
1974 | 0 | pls->ctx->interrupt_callback = s->interrupt_callback; |
1975 | 0 | ret = av_probe_input_buffer(&pls->pb.pub, &in_fmt, "", NULL, 0, 0); |
1976 | 0 | if (ret < 0) { |
1977 | 0 | av_log(s, AV_LOG_ERROR, "Error when loading first fragment of playlist\n"); |
1978 | 0 | avformat_free_context(pls->ctx); |
1979 | 0 | pls->ctx = NULL; |
1980 | 0 | goto fail; |
1981 | 0 | } |
1982 | | |
1983 | 0 | pls->ctx->pb = &pls->pb.pub; |
1984 | 0 | pls->ctx->io_open = nested_io_open; |
1985 | |
|
1986 | 0 | if (c->cenc_decryption_key) |
1987 | 0 | av_dict_set(&in_fmt_opts, "decryption_key", c->cenc_decryption_key, 0); |
1988 | 0 | if (c->cenc_decryption_keys) |
1989 | 0 | av_dict_set(&in_fmt_opts, "decryption_keys", c->cenc_decryption_keys, 0); |
1990 | | |
1991 | | // provide additional information from mpd if available |
1992 | 0 | ret = avformat_open_input(&pls->ctx, "", in_fmt, &in_fmt_opts); //pls->init_section->url |
1993 | 0 | av_dict_free(&in_fmt_opts); |
1994 | 0 | if (ret < 0) |
1995 | 0 | goto fail; |
1996 | 0 | if (pls->n_fragments) { |
1997 | 0 | #if FF_API_R_FRAME_RATE |
1998 | 0 | if (pls->framerate.den) { |
1999 | 0 | for (i = 0; i < pls->ctx->nb_streams; i++) |
2000 | 0 | pls->ctx->streams[i]->r_frame_rate = pls->framerate; |
2001 | 0 | } |
2002 | 0 | #endif |
2003 | 0 | ret = avformat_find_stream_info(pls->ctx, NULL); |
2004 | 0 | if (ret < 0) |
2005 | 0 | goto fail; |
2006 | 0 | } |
2007 | | |
2008 | 0 | fail: |
2009 | 0 | return ret; |
2010 | 0 | } |
2011 | | |
2012 | | static int open_demux_for_component(AVFormatContext *s, struct representation *pls) |
2013 | 0 | { |
2014 | 0 | int ret = 0; |
2015 | 0 | int i; |
2016 | |
|
2017 | 0 | pls->parent = s; |
2018 | 0 | pls->cur_seq_no = calc_cur_seg_no(s, pls); |
2019 | |
|
2020 | 0 | if (!pls->last_seq_no) |
2021 | 0 | pls->last_seq_no = calc_max_seg_no(pls, s->priv_data); |
2022 | |
|
2023 | 0 | ret = reopen_demux_for_component(s, pls); |
2024 | 0 | if (ret < 0) |
2025 | 0 | return ret; |
2026 | | |
2027 | 0 | for (i = 0; i < pls->ctx->nb_streams; i++) { |
2028 | 0 | AVStream *st = avformat_new_stream(s, NULL); |
2029 | 0 | FFStream *sti = ffstream(st); |
2030 | 0 | const AVStream *ist = pls->ctx->streams[i]; |
2031 | 0 | const FFStream *isti = cffstream(ist); |
2032 | 0 | if (!st) |
2033 | 0 | return AVERROR(ENOMEM); |
2034 | | |
2035 | 0 | st->id = i + pls->stream_index; |
2036 | |
|
2037 | 0 | ret = avcodec_parameters_copy(st->codecpar, ist->codecpar); |
2038 | 0 | if (ret < 0) |
2039 | 0 | return ret; |
2040 | | |
2041 | 0 | avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); |
2042 | | |
2043 | | // copy disposition |
2044 | 0 | st->disposition = ist->disposition; |
2045 | 0 | sti->need_parsing = isti->need_parsing; |
2046 | 0 | } |
2047 | | |
2048 | 0 | for (i = 0; i < pls->ctx->nb_stream_groups; i++) { |
2049 | 0 | AVStreamGroup *istg = pls->ctx->stream_groups[i]; |
2050 | 0 | AVStreamGroup *stg; |
2051 | |
|
2052 | 0 | if (istg->type != AV_STREAM_GROUP_PARAMS_LCEVC) |
2053 | 0 | continue; |
2054 | | |
2055 | 0 | stg = avformat_stream_group_create(s, istg->type, NULL); |
2056 | 0 | if (!stg) |
2057 | 0 | return AVERROR(ENOMEM); |
2058 | | |
2059 | 0 | stg->id = s->nb_stream_groups; |
2060 | |
|
2061 | 0 | for (int j = 0; j < istg->nb_streams; j++) { |
2062 | 0 | AVStream *ist = istg->streams[j]; |
2063 | 0 | AVStream *st = s->streams[ist->index + pls->stream_index]; |
2064 | 0 | ret = avformat_stream_group_add_stream(stg, st); |
2065 | 0 | if (ret < 0) |
2066 | 0 | return ret; |
2067 | 0 | } |
2068 | | |
2069 | 0 | switch (stg->type) { |
2070 | 0 | case AV_STREAM_GROUP_PARAMS_LCEVC: { |
2071 | 0 | AVStreamGroupLayeredVideo *ilcevc = istg->params.layered_video; |
2072 | 0 | AVStreamGroupLayeredVideo *lcevc = stg->params.layered_video; |
2073 | 0 | ret = av_opt_copy(lcevc, ilcevc); |
2074 | 0 | if (ret < 0) |
2075 | 0 | return ret; |
2076 | 0 | break; |
2077 | 0 | } |
2078 | 0 | default: |
2079 | 0 | av_unreachable("Unsupported Stream Group type should have been checked above"); |
2080 | 0 | } |
2081 | | |
2082 | | // copy disposition |
2083 | 0 | stg->disposition = istg->disposition; |
2084 | 0 | } |
2085 | | |
2086 | 0 | return 0; |
2087 | 0 | } |
2088 | | |
2089 | | static int is_common_init_section_exist(struct representation **pls, int n_pls) |
2090 | 0 | { |
2091 | 0 | struct fragment *first_init_section = pls[0]->init_section; |
2092 | 0 | char *url =NULL; |
2093 | 0 | int64_t url_offset = -1; |
2094 | 0 | int64_t size = -1; |
2095 | 0 | int i = 0; |
2096 | |
|
2097 | 0 | if (first_init_section == NULL || n_pls == 0) |
2098 | 0 | return 0; |
2099 | | |
2100 | 0 | url = first_init_section->url; |
2101 | 0 | url_offset = first_init_section->url_offset; |
2102 | 0 | size = pls[0]->init_section->size; |
2103 | 0 | for (i=0;i<n_pls;i++) { |
2104 | 0 | if (!pls[i]->init_section) |
2105 | 0 | continue; |
2106 | | |
2107 | 0 | if (av_strcasecmp(pls[i]->init_section->url, url) || |
2108 | 0 | pls[i]->init_section->url_offset != url_offset || |
2109 | 0 | pls[i]->init_section->size != size) { |
2110 | 0 | return 0; |
2111 | 0 | } |
2112 | 0 | } |
2113 | 0 | return 1; |
2114 | 0 | } |
2115 | | |
2116 | | static int copy_init_section(struct representation *rep_dest, struct representation *rep_src) |
2117 | 0 | { |
2118 | 0 | rep_dest->init_sec_buf = av_mallocz(rep_src->init_sec_buf_size); |
2119 | 0 | if (!rep_dest->init_sec_buf) { |
2120 | 0 | av_log(rep_dest->ctx, AV_LOG_WARNING, "Cannot alloc memory for init_sec_buf\n"); |
2121 | 0 | return AVERROR(ENOMEM); |
2122 | 0 | } |
2123 | 0 | memcpy(rep_dest->init_sec_buf, rep_src->init_sec_buf, rep_src->init_sec_data_len); |
2124 | 0 | rep_dest->init_sec_buf_size = rep_src->init_sec_buf_size; |
2125 | 0 | rep_dest->init_sec_data_len = rep_src->init_sec_data_len; |
2126 | 0 | rep_dest->cur_timestamp = rep_src->cur_timestamp; |
2127 | |
|
2128 | 0 | return 0; |
2129 | 0 | } |
2130 | | |
2131 | | static void move_metadata(AVStream *st, const char *key, char **value) |
2132 | 0 | { |
2133 | 0 | if (*value) { |
2134 | 0 | av_dict_set(&st->metadata, key, *value, AV_DICT_DONT_STRDUP_VAL); |
2135 | 0 | *value = NULL; |
2136 | 0 | } |
2137 | 0 | } |
2138 | | |
2139 | | static int dash_read_header(AVFormatContext *s) |
2140 | 16.8k | { |
2141 | 16.8k | DASHContext *c = s->priv_data; |
2142 | 16.8k | struct representation *rep; |
2143 | 16.8k | AVProgram *program; |
2144 | 16.8k | int ret = 0; |
2145 | 16.8k | int stream_index = 0; |
2146 | 16.8k | int i, j; |
2147 | | |
2148 | 16.8k | c->interrupt_callback = &s->interrupt_callback; |
2149 | | |
2150 | 16.8k | if ((ret = ffio_copy_url_options(s->pb, &c->avio_opts)) < 0) |
2151 | 0 | return ret; |
2152 | | |
2153 | 16.8k | if ((ret = parse_manifest(s, s->url, s->pb)) < 0) |
2154 | 16.8k | return ret; |
2155 | | |
2156 | | /* If this isn't a live stream, fill the total duration of the |
2157 | | * stream. */ |
2158 | 0 | if (!c->is_live) { |
2159 | 0 | s->duration = (int64_t) c->media_presentation_duration * AV_TIME_BASE; |
2160 | 0 | } else { |
2161 | 0 | av_dict_set(&c->avio_opts, "seekable", "0", 0); |
2162 | 0 | } |
2163 | |
|
2164 | 0 | if(c->n_videos) |
2165 | 0 | c->is_init_section_common_video = is_common_init_section_exist(c->videos, c->n_videos); |
2166 | | |
2167 | | /* Open the demuxer for video and audio components if available */ |
2168 | 0 | for (i = 0; i < c->n_videos; i++) { |
2169 | 0 | rep = c->videos[i]; |
2170 | 0 | if (i > 0 && c->is_init_section_common_video) { |
2171 | 0 | ret = copy_init_section(rep, c->videos[0]); |
2172 | 0 | if (ret < 0) |
2173 | 0 | return ret; |
2174 | 0 | } |
2175 | 0 | rep->stream_index = stream_index; |
2176 | 0 | ret = open_demux_for_component(s, rep); |
2177 | |
|
2178 | 0 | if (ret) |
2179 | 0 | return ret; |
2180 | 0 | if (rep->ctx->nb_streams == 0) |
2181 | 0 | return AVERROR_PATCHWELCOME; |
2182 | 0 | stream_index += rep->ctx->nb_streams; |
2183 | 0 | } |
2184 | | |
2185 | 0 | if(c->n_audios) |
2186 | 0 | c->is_init_section_common_audio = is_common_init_section_exist(c->audios, c->n_audios); |
2187 | |
|
2188 | 0 | for (i = 0; i < c->n_audios; i++) { |
2189 | 0 | rep = c->audios[i]; |
2190 | 0 | if (i > 0 && c->is_init_section_common_audio) { |
2191 | 0 | ret = copy_init_section(rep, c->audios[0]); |
2192 | 0 | if (ret < 0) |
2193 | 0 | return ret; |
2194 | 0 | } |
2195 | 0 | rep->stream_index = stream_index; |
2196 | 0 | ret = open_demux_for_component(s, rep); |
2197 | |
|
2198 | 0 | if (ret) |
2199 | 0 | return ret; |
2200 | 0 | if (rep->ctx->nb_streams == 0) |
2201 | 0 | return AVERROR_PATCHWELCOME; |
2202 | 0 | stream_index += rep->ctx->nb_streams; |
2203 | 0 | } |
2204 | | |
2205 | 0 | if (c->n_subtitles) |
2206 | 0 | c->is_init_section_common_subtitle = is_common_init_section_exist(c->subtitles, c->n_subtitles); |
2207 | |
|
2208 | 0 | for (i = 0; i < c->n_subtitles; i++) { |
2209 | 0 | rep = c->subtitles[i]; |
2210 | 0 | if (i > 0 && c->is_init_section_common_subtitle) { |
2211 | 0 | ret = copy_init_section(rep, c->subtitles[0]); |
2212 | 0 | if (ret < 0) |
2213 | 0 | return ret; |
2214 | 0 | } |
2215 | 0 | rep->stream_index = stream_index; |
2216 | 0 | ret = open_demux_for_component(s, rep); |
2217 | |
|
2218 | 0 | if (ret) |
2219 | 0 | return ret; |
2220 | 0 | if (rep->ctx->nb_streams == 0) |
2221 | 0 | return AVERROR_PATCHWELCOME; |
2222 | 0 | stream_index += rep->ctx->nb_streams; |
2223 | 0 | } |
2224 | | |
2225 | 0 | if (!stream_index) |
2226 | 0 | return AVERROR_INVALIDDATA; |
2227 | | |
2228 | | /* Create a program */ |
2229 | 0 | program = av_new_program(s, 0); |
2230 | 0 | if (!program) |
2231 | 0 | return AVERROR(ENOMEM); |
2232 | | |
2233 | 0 | for (i = 0; i < c->n_videos; i++) { |
2234 | 0 | rep = c->videos[i]; |
2235 | 0 | rep->assoc_stream = av_malloc_array(rep->ctx->nb_streams, sizeof(*rep->assoc_stream)); |
2236 | 0 | if (!rep->assoc_stream) |
2237 | 0 | return AVERROR(ENOMEM); |
2238 | 0 | rep->nb_assoc_stream = rep->ctx->nb_streams; |
2239 | 0 | for (int j = 0; j < rep->ctx->nb_streams; j++) { |
2240 | 0 | av_program_add_stream_index(s, 0, rep->stream_index + j); |
2241 | 0 | rep->assoc_stream[j] = s->streams[rep->stream_index + j]; |
2242 | 0 | } |
2243 | 0 | if (rep->bandwidth > 0) |
2244 | 0 | av_dict_set_int(&rep->assoc_stream[0]->metadata, "variant_bitrate", rep->bandwidth, 0); |
2245 | 0 | move_metadata(rep->assoc_stream[0], "id", &rep->id); |
2246 | 0 | } |
2247 | 0 | for (i = 0; i < c->n_audios; i++) { |
2248 | 0 | rep = c->audios[i]; |
2249 | 0 | rep->assoc_stream = av_malloc_array(rep->ctx->nb_streams, sizeof(*rep->assoc_stream)); |
2250 | 0 | if (!rep->assoc_stream) |
2251 | 0 | return AVERROR(ENOMEM); |
2252 | 0 | rep->nb_assoc_stream = rep->ctx->nb_streams; |
2253 | 0 | for (int j = 0; j < rep->ctx->nb_streams; j++) { |
2254 | 0 | av_program_add_stream_index(s, 0, rep->stream_index + j); |
2255 | 0 | rep->assoc_stream[j] = s->streams[rep->stream_index + j]; |
2256 | 0 | } |
2257 | 0 | if (rep->bandwidth > 0) |
2258 | 0 | av_dict_set_int(&rep->assoc_stream[0]->metadata, "variant_bitrate", rep->bandwidth, 0); |
2259 | 0 | move_metadata(rep->assoc_stream[0], "id", &rep->id); |
2260 | 0 | move_metadata(rep->assoc_stream[0], "language", &rep->lang); |
2261 | 0 | } |
2262 | 0 | for (i = 0; i < c->n_subtitles; i++) { |
2263 | 0 | rep = c->subtitles[i]; |
2264 | 0 | rep->assoc_stream = av_malloc_array(rep->ctx->nb_streams, sizeof(*rep->assoc_stream)); |
2265 | 0 | if (!rep->assoc_stream) |
2266 | 0 | return AVERROR(ENOMEM); |
2267 | 0 | rep->nb_assoc_stream = rep->ctx->nb_streams; |
2268 | 0 | for (int j = 0; j < rep->ctx->nb_streams; j++) { |
2269 | 0 | av_program_add_stream_index(s, 0, rep->stream_index + j); |
2270 | 0 | rep->assoc_stream[j] = s->streams[rep->stream_index + j]; |
2271 | 0 | } |
2272 | 0 | move_metadata(rep->assoc_stream[0], "id", &rep->id); |
2273 | 0 | move_metadata(rep->assoc_stream[0], "language", &rep->lang); |
2274 | 0 | } |
2275 | | |
2276 | | /* Create stream groups if needed */ |
2277 | 0 | for (i = 0; i < c->n_videos; i++) { |
2278 | 0 | struct representation *ref; |
2279 | 0 | rep = c->videos[i]; |
2280 | 0 | if (!rep->dependencyid || !rep->nb_assoc_stream) |
2281 | 0 | continue; |
2282 | 0 | for (j = 0; j < c->n_videos; j++) { |
2283 | 0 | if (j == i) |
2284 | 0 | continue; |
2285 | 0 | ref = c->videos[j]; |
2286 | 0 | if (!ref->nb_assoc_stream) |
2287 | 0 | continue; |
2288 | 0 | const AVDictionaryEntry *id = av_dict_get(ref->assoc_stream[0]->metadata, "id", NULL, AV_DICT_MATCH_CASE); |
2289 | 0 | if (!strcmp(rep->dependencyid, id->value)) |
2290 | 0 | break; |
2291 | 0 | } |
2292 | 0 | if (j >= c->n_videos || !av_strstart(rep->codecs, "lvc1", NULL) || |
2293 | 0 | rep->assoc_stream[0]->codecpar->codec_id != AV_CODEC_ID_LCEVC) |
2294 | 0 | continue; |
2295 | 0 | AVStreamGroup *stg = avformat_stream_group_create(s, AV_STREAM_GROUP_PARAMS_LCEVC, NULL); |
2296 | 0 | if (!stg) |
2297 | 0 | return AVERROR(ENOMEM); |
2298 | 0 | stg->params.layered_video->width = rep->assoc_stream[0]->codecpar->width; |
2299 | 0 | stg->params.layered_video->height = rep->assoc_stream[0]->codecpar->height; |
2300 | 0 | ret = avformat_stream_group_add_stream(stg, ref->assoc_stream[0]); |
2301 | 0 | if (ret < 0) |
2302 | 0 | return ret; |
2303 | 0 | ret = avformat_stream_group_add_stream(stg, rep->assoc_stream[0]); |
2304 | 0 | if (ret < 0) |
2305 | 0 | return ret; |
2306 | 0 | stg->id = stg->index; |
2307 | 0 | stg->params.layered_video->el_index = stg->nb_streams - 1; |
2308 | 0 | } |
2309 | | |
2310 | 0 | return 0; |
2311 | 0 | } |
2312 | | |
2313 | | static void recheck_discard_flags(AVFormatContext *s, struct representation **p, int n) |
2314 | 0 | { |
2315 | 0 | int i, j; |
2316 | |
|
2317 | 0 | for (i = 0; i < n; i++) { |
2318 | 0 | struct representation *pls = p[i]; |
2319 | 0 | int needed = !pls->nb_assoc_stream; |
2320 | |
|
2321 | 0 | for (int j = 0; j < pls->nb_assoc_stream; j++) |
2322 | 0 | needed |= pls->assoc_stream[j]->discard < AVDISCARD_ALL; |
2323 | |
|
2324 | 0 | if (needed && !pls->ctx) { |
2325 | 0 | pls->cur_seg_offset = 0; |
2326 | 0 | pls->init_sec_buf_read_offset = 0; |
2327 | | /* Catch up */ |
2328 | 0 | for (j = 0; j < n; j++) { |
2329 | 0 | pls->cur_seq_no = FFMAX(pls->cur_seq_no, p[j]->cur_seq_no); |
2330 | 0 | } |
2331 | 0 | reopen_demux_for_component(s, pls); |
2332 | 0 | av_log(s, AV_LOG_INFO, "Now receiving stream_index %d\n", pls->stream_index); |
2333 | 0 | } else if (!needed && pls->ctx) { |
2334 | 0 | close_demux_for_component(pls); |
2335 | 0 | ff_format_io_close(pls->parent, &pls->input); |
2336 | 0 | av_log(s, AV_LOG_INFO, "No longer receiving stream_index %d\n", pls->stream_index); |
2337 | 0 | } |
2338 | 0 | } |
2339 | 0 | } |
2340 | | |
2341 | | static int dash_read_packet(AVFormatContext *s, AVPacket *pkt) |
2342 | 0 | { |
2343 | 0 | DASHContext *c = s->priv_data; |
2344 | 0 | int ret = 0, i; |
2345 | 0 | int64_t mints = 0; |
2346 | 0 | struct representation *cur = NULL; |
2347 | 0 | struct representation *rep = NULL; |
2348 | |
|
2349 | 0 | recheck_discard_flags(s, c->videos, c->n_videos); |
2350 | 0 | recheck_discard_flags(s, c->audios, c->n_audios); |
2351 | 0 | recheck_discard_flags(s, c->subtitles, c->n_subtitles); |
2352 | |
|
2353 | 0 | for (i = 0; i < c->n_videos; i++) { |
2354 | 0 | rep = c->videos[i]; |
2355 | 0 | if (!rep->ctx) |
2356 | 0 | continue; |
2357 | 0 | if (!cur || rep->cur_timestamp < mints) { |
2358 | 0 | cur = rep; |
2359 | 0 | mints = rep->cur_timestamp; |
2360 | 0 | } |
2361 | 0 | } |
2362 | 0 | for (i = 0; i < c->n_audios; i++) { |
2363 | 0 | rep = c->audios[i]; |
2364 | 0 | if (!rep->ctx) |
2365 | 0 | continue; |
2366 | 0 | if (!cur || rep->cur_timestamp < mints) { |
2367 | 0 | cur = rep; |
2368 | 0 | mints = rep->cur_timestamp; |
2369 | 0 | } |
2370 | 0 | } |
2371 | |
|
2372 | 0 | for (i = 0; i < c->n_subtitles; i++) { |
2373 | 0 | rep = c->subtitles[i]; |
2374 | 0 | if (!rep->ctx) |
2375 | 0 | continue; |
2376 | 0 | if (!cur || rep->cur_timestamp < mints) { |
2377 | 0 | cur = rep; |
2378 | 0 | mints = rep->cur_timestamp; |
2379 | 0 | } |
2380 | 0 | } |
2381 | |
|
2382 | 0 | if (!cur) { |
2383 | 0 | return AVERROR_INVALIDDATA; |
2384 | 0 | } |
2385 | 0 | while (!ff_check_interrupt(c->interrupt_callback) && !ret) { |
2386 | 0 | ret = av_read_frame(cur->ctx, pkt); |
2387 | 0 | if (ret >= 0) { |
2388 | | /* If we got a packet, return it */ |
2389 | 0 | cur->cur_timestamp = av_rescale(pkt->pts, (int64_t)cur->ctx->streams[0]->time_base.num * 90000, cur->ctx->streams[0]->time_base.den); |
2390 | 0 | pkt->stream_index += cur->stream_index; |
2391 | 0 | return 0; |
2392 | 0 | } |
2393 | 0 | if (cur->is_restart_needed) { |
2394 | 0 | cur->cur_seg_offset = 0; |
2395 | 0 | cur->init_sec_buf_read_offset = 0; |
2396 | 0 | cur->is_restart_needed = 0; |
2397 | 0 | ff_format_io_close(cur->parent, &cur->input); |
2398 | 0 | ret = reopen_demux_for_component(s, cur); |
2399 | 0 | } |
2400 | 0 | } |
2401 | 0 | return AVERROR_EOF; |
2402 | 0 | } |
2403 | | |
2404 | | static int dash_close(AVFormatContext *s) |
2405 | 16.8k | { |
2406 | 16.8k | DASHContext *c = s->priv_data; |
2407 | 16.8k | free_audio_list(c); |
2408 | 16.8k | free_video_list(c); |
2409 | 16.8k | free_subtitle_list(c); |
2410 | 16.8k | av_dict_free(&c->avio_opts); |
2411 | 16.8k | av_freep(&c->base_url); |
2412 | 16.8k | return 0; |
2413 | 16.8k | } |
2414 | | |
2415 | | static int dash_seek(AVFormatContext *s, struct representation *pls, int64_t seek_pos_msec, int flags, int dry_run) |
2416 | 0 | { |
2417 | 0 | int ret = 0; |
2418 | 0 | int i = 0; |
2419 | 0 | int j = 0; |
2420 | 0 | int64_t duration = 0; |
2421 | |
|
2422 | 0 | av_log(pls->parent, AV_LOG_VERBOSE, "DASH seek pos[%"PRId64"ms] %s\n", |
2423 | 0 | seek_pos_msec, dry_run ? " (dry)" : ""); |
2424 | | |
2425 | | // single fragment mode |
2426 | 0 | if (pls->n_fragments == 1) { |
2427 | 0 | pls->cur_timestamp = 0; |
2428 | 0 | pls->cur_seg_offset = 0; |
2429 | 0 | if (dry_run) |
2430 | 0 | return 0; |
2431 | 0 | ff_read_frame_flush(pls->ctx); |
2432 | 0 | return av_seek_frame(pls->ctx, -1, seek_pos_msec * 1000, flags); |
2433 | 0 | } |
2434 | | |
2435 | 0 | ff_format_io_close(pls->parent, &pls->input); |
2436 | | |
2437 | | // find the nearest fragment |
2438 | 0 | if (pls->n_timelines > 0 && pls->fragment_timescale > 0) { |
2439 | 0 | int64_t num = pls->first_seq_no; |
2440 | 0 | av_log(pls->parent, AV_LOG_VERBOSE, "dash_seek with SegmentTimeline start n_timelines[%d] " |
2441 | 0 | "last_seq_no[%"PRId64"].\n", |
2442 | 0 | (int)pls->n_timelines, (int64_t)pls->last_seq_no); |
2443 | 0 | for (i = 0; i < pls->n_timelines; i++) { |
2444 | 0 | if (pls->timelines[i]->starttime > 0) { |
2445 | 0 | duration = pls->timelines[i]->starttime; |
2446 | 0 | } |
2447 | 0 | duration += pls->timelines[i]->duration; |
2448 | 0 | if (seek_pos_msec < ((duration * 1000) / pls->fragment_timescale)) { |
2449 | 0 | goto set_seq_num; |
2450 | 0 | } |
2451 | 0 | for (j = 0; j < pls->timelines[i]->repeat; j++) { |
2452 | 0 | duration += pls->timelines[i]->duration; |
2453 | 0 | num++; |
2454 | 0 | if (seek_pos_msec < ((duration * 1000) / pls->fragment_timescale)) { |
2455 | 0 | goto set_seq_num; |
2456 | 0 | } |
2457 | 0 | } |
2458 | 0 | num++; |
2459 | 0 | } |
2460 | | |
2461 | 0 | set_seq_num: |
2462 | 0 | pls->cur_seq_no = num > pls->last_seq_no ? pls->last_seq_no : num; |
2463 | 0 | av_log(pls->parent, AV_LOG_VERBOSE, "dash_seek with SegmentTimeline end cur_seq_no[%"PRId64"].\n", |
2464 | 0 | (int64_t)pls->cur_seq_no); |
2465 | 0 | } else if (pls->fragment_duration > 0) { |
2466 | 0 | pls->cur_seq_no = pls->first_seq_no + ((seek_pos_msec * pls->fragment_timescale) / pls->fragment_duration) / 1000; |
2467 | 0 | } else { |
2468 | 0 | av_log(pls->parent, AV_LOG_ERROR, "dash_seek missing timeline or fragment_duration\n"); |
2469 | 0 | pls->cur_seq_no = pls->first_seq_no; |
2470 | 0 | } |
2471 | 0 | pls->cur_timestamp = 0; |
2472 | 0 | pls->cur_seg_offset = 0; |
2473 | 0 | pls->init_sec_buf_read_offset = 0; |
2474 | 0 | ret = dry_run ? 0 : reopen_demux_for_component(s, pls); |
2475 | |
|
2476 | 0 | return ret; |
2477 | 0 | } |
2478 | | |
2479 | | static int dash_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) |
2480 | 0 | { |
2481 | 0 | int ret = 0, i; |
2482 | 0 | DASHContext *c = s->priv_data; |
2483 | 0 | int64_t seek_pos_msec = av_rescale_rnd(timestamp, 1000, |
2484 | 0 | s->streams[stream_index]->time_base.den, |
2485 | 0 | flags & AVSEEK_FLAG_BACKWARD ? |
2486 | 0 | AV_ROUND_DOWN : AV_ROUND_UP); |
2487 | 0 | if ((flags & AVSEEK_FLAG_BYTE) || c->is_live) |
2488 | 0 | return AVERROR(ENOSYS); |
2489 | | |
2490 | | /* Seek in discarded streams with dry_run=1 to avoid reopening them */ |
2491 | 0 | for (i = 0; i < c->n_videos; i++) { |
2492 | 0 | if (!ret) |
2493 | 0 | ret = dash_seek(s, c->videos[i], seek_pos_msec, flags, !c->videos[i]->ctx); |
2494 | 0 | } |
2495 | 0 | for (i = 0; i < c->n_audios; i++) { |
2496 | 0 | if (!ret) |
2497 | 0 | ret = dash_seek(s, c->audios[i], seek_pos_msec, flags, !c->audios[i]->ctx); |
2498 | 0 | } |
2499 | 0 | for (i = 0; i < c->n_subtitles; i++) { |
2500 | 0 | if (!ret) |
2501 | 0 | ret = dash_seek(s, c->subtitles[i], seek_pos_msec, flags, !c->subtitles[i]->ctx); |
2502 | 0 | } |
2503 | |
|
2504 | 0 | return ret; |
2505 | 0 | } |
2506 | | |
2507 | | static int dash_probe(const AVProbeData *p) |
2508 | 967k | { |
2509 | 967k | if (!av_stristr(p->buf, "<MPD")) |
2510 | 967k | return 0; |
2511 | | |
2512 | 297 | if (av_stristr(p->buf, "dash:profile:isoff-on-demand:2011") || |
2513 | 297 | av_stristr(p->buf, "dash:profile:isoff-live:2011") || |
2514 | 297 | av_stristr(p->buf, "dash:profile:isoff-live:2012") || |
2515 | 297 | av_stristr(p->buf, "dash:profile:isoff-main:2011") || |
2516 | 297 | av_stristr(p->buf, "3GPP:PSS:profile:DASH1")) { |
2517 | 0 | return AVPROBE_SCORE_MAX; |
2518 | 0 | } |
2519 | 297 | if (av_stristr(p->buf, "dash:profile")) { |
2520 | 0 | return AVPROBE_SCORE_MAX; |
2521 | 0 | } |
2522 | | |
2523 | 297 | return 0; |
2524 | 297 | } |
2525 | | |
2526 | | #define OFFSET(x) offsetof(DASHContext, x) |
2527 | | #define FLAGS AV_OPT_FLAG_DECODING_PARAM |
2528 | | static const AVOption dash_options[] = { |
2529 | | {"allowed_extensions", "List of file extensions that dash is allowed to access", |
2530 | | OFFSET(allowed_extensions), AV_OPT_TYPE_STRING, |
2531 | | {.str = "aac,m4a,m4s,m4v,mov,mp4,webm,ts"}, |
2532 | | INT_MIN, INT_MAX, FLAGS}, |
2533 | | { "cenc_decryption_key", "Media default decryption key (hex)", OFFSET(cenc_decryption_key), AV_OPT_TYPE_STRING, {.str = NULL}, INT_MIN, INT_MAX, .flags = FLAGS }, |
2534 | | { "cenc_decryption_keys", "Media decryption keys by KID (hex)", OFFSET(cenc_decryption_keys), AV_OPT_TYPE_STRING, {.str = NULL}, INT_MIN, INT_MAX, .flags = FLAGS }, |
2535 | | { "max_reload", "Maximum number of manifest reloads in get_current_fragment() before giving up", |
2536 | | OFFSET(max_reload), AV_OPT_TYPE_INT, { .i64 = 100 }, 0, INT_MAX, FLAGS }, |
2537 | | {NULL} |
2538 | | }; |
2539 | | |
2540 | | static const AVClass dash_class = { |
2541 | | .class_name = "dash", |
2542 | | .item_name = av_default_item_name, |
2543 | | .option = dash_options, |
2544 | | .version = LIBAVUTIL_VERSION_INT, |
2545 | | }; |
2546 | | |
2547 | | const FFInputFormat ff_dash_demuxer = { |
2548 | | .p.name = "dash", |
2549 | | .p.long_name = NULL_IF_CONFIG_SMALL("Dynamic Adaptive Streaming over HTTP"), |
2550 | | .p.priv_class = &dash_class, |
2551 | | .p.flags = AVFMT_NO_BYTE_SEEK, |
2552 | | .priv_data_size = sizeof(DASHContext), |
2553 | | .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP, |
2554 | | .read_probe = dash_probe, |
2555 | | .read_header = dash_read_header, |
2556 | | .read_packet = dash_read_packet, |
2557 | | .read_close = dash_close, |
2558 | | .read_seek = dash_read_seek, |
2559 | | }; |