Line data Source code
1 : /**
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre, Cyril Concolato
5 : * Copyright (c) Telecom ParisTech 2010-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / Adaptive HTTP Streaming
9 : *
10 : * GPAC is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU Lesser General Public License as published by
12 : * the Free Software Foundation; either version 2, or (at your option)
13 : * any later version.
14 : *
15 : * GPAC is distributed in the hope that it will be useful,
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Lesser General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Lesser General Public
21 : * License along with this library; see the file COPYING. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 : #include <gpac/network.h>
27 : #include <gpac/dash.h>
28 : #include <gpac/mpd.h>
29 : #include <gpac/internal/m3u8.h>
30 : #include <gpac/internal/isomedia_dev.h>
31 : #include <gpac/base_coding.h>
32 : #include <string.h>
33 : #include <sys/stat.h>
34 :
35 : #include <math.h>
36 :
37 :
38 : #ifndef GPAC_DISABLE_DASH_CLIENT
39 :
40 : /*ISO 639 languages*/
41 : #include <gpac/iso639.h>
42 :
43 : /*set to 1 if you want MPD to use SegmentTemplate if possible instead of SegmentList*/
44 : #define M3U8_TO_MPD_USE_TEMPLATE 0
45 : /*set to 1 if you want MPD to use SegmentTimeline*/
46 : #define M3U8_TO_MPD_USE_SEGTIMELINE 0
47 :
48 : typedef enum {
49 : GF_DASH_STATE_STOPPED = 0,
50 : /*period setup and playback chain creation*/
51 : GF_DASH_STATE_SETUP,
52 : /*request to start playback chain*/
53 : GF_DASH_STATE_CONNECTING,
54 : GF_DASH_STATE_RUNNING,
55 : } GF_DASH_STATE;
56 :
57 :
58 : //shifts AST in the past(>0) or future (<0) so that client starts request in the future or in the past
59 : //#define FORCE_DESYNC 4000
60 :
61 : /* WARNING: GF_DASH_Group does not represent a Group in DASH
62 : It corresponds to an AdaptationSet with additional live information not present in the MPD
63 : (e.g. current active representation)
64 : */
65 : typedef struct __dash_group GF_DASH_Group;
66 :
67 : struct __dash_client
68 : {
69 : GF_DASHFileIO *dash_io;
70 :
71 : /*interface to mpd parser - get rid of this and use the DASHIO instead ?*/
72 : GF_FileDownload getter;
73 :
74 : char *base_url;
75 :
76 : u32 max_cache_duration, max_width, max_height;
77 : u8 max_bit_per_pixel;
78 : u32 auto_switch_count;
79 : Bool keep_files, disable_switching, allow_local_mpd_update, estimate_utc_drift, ntp_forced;
80 : Bool is_m3u8, is_smooth;
81 : Bool split_adaptation_set;
82 : GF_DASHLowLatencyMode low_latency_mode;
83 : //set when MPD downloading fails. Will resetup DASH live once MPD is sync again
84 : Bool in_error;
85 :
86 : u64 mpd_fetch_time;
87 : GF_DASHInitialSelectionMode first_select_mode;
88 :
89 : /* MPD downloader*/
90 : GF_DASHFileIOSession mpd_dnload;
91 : /* MPD */
92 : GF_MPD *mpd;
93 : /* number of time the MPD has been reloaded and last update time*/
94 : u32 reload_count, last_update_time;
95 : /*signature of last MPD*/
96 : u8 lastMPDSignature[GF_SHA1_DIGEST_SIZE];
97 : /*mime type of media segments (m3u8)*/
98 : char *mimeTypeForM3U8Segments;
99 :
100 : /* active period in MPD */
101 : u32 active_period_index;
102 : u32 reinit_period_index;
103 : u32 request_period_switch;
104 :
105 : Bool next_period_checked;
106 :
107 : u64 start_time_in_active_period;
108 :
109 : Bool ignore_mpd_duration;
110 : u32 initial_time_shift_value;
111 :
112 : const char *query_string;
113 :
114 : /*list of groups in the active period*/
115 : GF_List *groups;
116 :
117 : /* one of the above state*/
118 : GF_DASH_STATE dash_state;
119 :
120 : Bool in_period_setup;
121 : Bool all_groups_done_notified;
122 :
123 : s64 utc_drift_estimate;
124 : s32 utc_shift;
125 :
126 : Double start_range_period;
127 :
128 : Double speed;
129 : Bool is_rt_speed;
130 : u32 probe_times_before_switch;
131 : Bool agressive_switching;
132 : u32 min_wait_ms_before_next_request;
133 : u32 min_wait_sys_clock;
134 :
135 : Bool force_mpd_update;
136 : u32 force_period_reload;
137 :
138 : u32 user_buffer_ms;
139 :
140 : u32 min_timeout_between_404, segment_lost_after_ms;
141 :
142 : Bool ignore_xlink;
143 :
144 : //0: not ROUTE - 1: ROUTE but clock not init - 2: ROUTE clock init
145 : u32 route_clock_state;
146 : //ROUTE AST shift in ms
147 : u32 route_ast_shift;
148 : u32 route_skip_segments_ms;
149 : Bool route_low_latency;
150 :
151 : Bool initial_period_tunein;
152 :
153 : Bool llhls_single_range;
154 : Bool m3u8_reload_master;
155 : u32 hls_reload_time;
156 :
157 :
158 : //in ms
159 : u32 time_in_tsb, prev_time_in_tsb;
160 : u32 tsb_exceeded;
161 : const u32 *dbg_grps_index;
162 : u32 nb_dbg_grps;
163 : Bool disable_speed_adaptation;
164 :
165 : Bool period_groups_setup;
166 : u32 tile_rate_decrease;
167 : GF_DASHTileAdaptationMode tile_adapt_mode;
168 : Bool disable_low_quality_tiles;
169 :
170 : GF_List *SRDs;
171 :
172 : GF_DASHAdaptationAlgorithm adaptation_algorithm;
173 :
174 : s32 (*rate_adaptation_algo)(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
175 : u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
176 : GF_MPD_Representation *rep, Bool go_up_bitrate);
177 :
178 : s32 (*rate_adaptation_download_monitor)(GF_DashClient *dash, GF_DASH_Group *group, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start, u32 buffer_dur_ms, u32 current_seg_dur);
179 :
180 : //for custom algo, total rate of all active groups being downloaded
181 : u32 total_rate;
182 :
183 : gf_dash_rate_adaptation rate_adaptation_algo_custom;
184 : gf_dash_download_monitor rate_adaptation_download_monitor_custom;
185 : void *udta_custom_algo;
186 : };
187 :
188 : static void gf_dash_seek_group(GF_DashClient *dash, GF_DASH_Group *group, Double seek_to, Bool is_dynamic);
189 :
190 :
191 : enum
192 : {
193 : SEG_FLAG_LOOP_DETECTED = 1,
194 : SEG_FLAG_DEP_FOLLOWING = 1<<1,
195 : SEG_FLAG_DISABLED = 1<<2,
196 : };
197 :
198 : typedef struct
199 : {
200 : char *url;
201 : u64 start_range, end_range;
202 : /*representation index in adaptation_set->representations*/
203 : u32 representation_index;
204 : u32 duration;
205 : char *key_url;
206 : bin128 key_IV;
207 : u32 seg_number;
208 : const char *seg_name_start;
209 : GF_Fraction64 time;
210 :
211 : u32 flags;
212 : } segment_cache_entry;
213 :
214 : typedef enum
215 : {
216 : /*set if group cannot be selected (wrong MPD)*/
217 : GF_DASH_GROUP_NOT_SELECTABLE = 0,
218 : GF_DASH_GROUP_NOT_SELECTED,
219 : GF_DASH_GROUP_SELECTED,
220 : } GF_DASHGroupSelection;
221 :
222 : /*this structure Group is the implementation of the adaptationSet element of the MPD.*/
223 : struct __dash_group
224 : {
225 : GF_DashClient *dash;
226 :
227 : /*pointer to adaptation set*/
228 : GF_MPD_AdaptationSet *adaptation_set;
229 : /*pointer to active period*/
230 : GF_MPD_Period *period;
231 :
232 : /*active representation index in adaptation_set->representations*/
233 : u32 active_rep_index;
234 :
235 : u32 prev_active_rep_index;
236 :
237 : Bool timeline_setup;
238 : Bool force_timeline_reeval;
239 : Bool first_hls_chunk;
240 :
241 : GF_DASHGroupSelection selection;
242 :
243 : /*may be mpd@time_shift_buffer_depth or rep@time_shift_buffer_depth*/
244 : u32 time_shift_buffer_depth;
245 :
246 : Bool bitstream_switching;
247 : GF_DASH_Group *depend_on_group;
248 : Bool done;
249 : //if set, will redownload the last segment partially downloaded
250 : Bool force_switch_bandwidth;
251 : Bool min_bandwidth_selected;
252 :
253 : u32 active_bitrate, max_bitrate, min_bitrate;
254 : u32 min_representation_bitrate;
255 :
256 : u32 nb_segments_in_rep;
257 :
258 : /* Segment duration as advertised in the MPD
259 : for the real duration of the segment being downloaded see current_downloaded_segment_duration */
260 : Double segment_duration;
261 :
262 : Double start_playback_range;
263 :
264 : Bool group_setup;
265 :
266 : Bool was_segment_base;
267 : /*local file playback, do not delete them*/
268 : Bool local_files;
269 : /*next segment to download for this group - negative number to take into account SN wrapping*/
270 : s32 download_segment_index;
271 : /*number of segments pruged since the start of the period*/
272 : u32 nb_segments_purged;
273 :
274 : u32 nb_retry_on_last_segment;
275 : s32 start_number_at_last_ast;
276 : u64 ast_at_init;
277 : u32 ast_offset;
278 :
279 : u32 max_cached_segments, nb_cached_segments;
280 : segment_cache_entry *cached;
281 :
282 : /*usually 0-0 (no range) but can be non-zero when playing local MPD/DASH sessions*/
283 : u64 bs_switching_init_segment_url_start_range, bs_switching_init_segment_url_end_range;
284 : char *bs_switching_init_segment_url;
285 : const char *bs_switching_init_segment_url_name_start;
286 :
287 : u32 nb_segments_done;
288 : u32 last_segment_time;
289 : u32 nb_segments_since_switch;
290 :
291 : //stats of last downloaded segment
292 : u32 total_size, bytes_per_sec, bytes_done, backup_Bps;
293 :
294 :
295 : Bool segment_must_be_streamed;
296 : Bool broken_timing;
297 :
298 : u32 maybe_end_of_stream;
299 : u32 cache_duration;
300 : u32 time_at_first_reload_required;
301 : u32 force_representation_idx_plus_one;
302 :
303 : Bool force_segment_switch;
304 : Bool loop_detected;
305 :
306 : u32 time_at_first_failure, time_at_last_request;
307 : Bool prev_segment_ok, segment_in_valid_range;
308 : //this is the number of 404
309 : u32 nb_consecutive_segments_lost;
310 : u64 retry_after_utc;
311 : /*set when switching segment, indicates the current downloaded segment duration*/
312 : u64 current_downloaded_segment_duration;
313 :
314 : char *service_mime;
315 :
316 : /* base representation index of this group plus one, or 0 if all representations in this group are independent*/
317 : u32 base_rep_index_plus_one;
318 :
319 : /* maximum representation index we want to download*/
320 : u32 max_complementary_rep_index;
321 : //start time and timescales of currently downloaded segment
322 : u64 current_start_time;
323 : u32 current_timescale;
324 :
325 : void *udta;
326 :
327 : Bool has_pending_enhancement;
328 :
329 : /*Codec statistics*/
330 : u32 avg_dec_time, max_dec_time, irap_avg_dec_time, irap_max_dec_time;
331 : Bool codec_reset;
332 : Bool decode_only_rap;
333 : /*display statistics*/
334 : u32 display_width, display_height;
335 : /*sets by user, indicates when the client will decide to play/resume after a buffering period (this is a static value for the entire session)*/
336 : u32 max_buffer_playout_ms;
337 : /*buffer status*/
338 : u32 buffer_min_ms, buffer_max_ms, buffer_occupancy_ms;
339 : u32 buffer_occupancy_at_last_seg;
340 :
341 : u32 m3u8_start_media_seq;
342 : u32 hls_next_seq_num;
343 :
344 : GF_List *groups_depending_on;
345 : u32 current_dep_idx;
346 :
347 : u32 target_new_rep;
348 :
349 : u32 srd_x, srd_y, srd_w, srd_h, srd_row_idx, srd_col_idx;
350 : struct _dash_srd_desc *srd_desc;
351 :
352 : /*current index of the base URL used*/
353 : u32 current_base_url_idx;
354 :
355 : u32 quality_degradation_hint;
356 :
357 : Bool rate_adaptation_postponed;
358 : Bool update_tile_qualities;
359 :
360 : //for dash custom, allows temporary disabling a group
361 : Bool disabled;
362 :
363 : /* current segment index in BBA and BOLA algorithm */
364 : u32 current_index;
365 :
366 : //in non-threaded mode, indicates that the demux for this group has nothing to do...
367 : Bool force_early_fetch;
368 : Bool is_low_latency;
369 :
370 : u32 hint_visible_width, hint_visible_height;
371 :
372 : //last chunk scheduled for download
373 : GF_MPD_SegmentURL *llhls_edge_chunk;
374 : Bool llhls_last_was_merged;
375 : s32 llhls_switch_request;
376 : u32 last_mpd_change_time;
377 : };
378 :
379 : //wait time before requesting again a M3U8 child playlist update when something goes wrong during the update: either same file or the expected next segment is not there
380 : #define HLS_MIN_RELOAD_TIME(_dash) _dash->hls_reload_time = 50 + gf_sys_clock();
381 :
382 :
383 : static void gf_dash_solve_period_xlink(GF_DashClient *dash, GF_List *period_list, u32 period_idx);
384 :
385 : struct _dash_srd_desc
386 : {
387 : u32 srd_nb_rows, srd_nb_cols;
388 : u32 id, width, height, srd_fw, srd_fh;
389 : };
390 :
391 : void drm_decrypt(unsigned char * data, unsigned long dataSize, const char * decryptMethod, const char * keyfileURL, const unsigned char * keyIV);
392 :
393 :
394 :
395 : static const char *gf_dash_get_mime_type(GF_MPD_SubRepresentation *subrep, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set)
396 : {
397 : if (subrep && subrep->mime_type) return subrep->mime_type;
398 459 : if (rep && rep->mime_type) return rep->mime_type;
399 4 : if (set && set->mime_type) return set->mime_type;
400 : return NULL;
401 : }
402 :
403 :
404 218 : static u64 dash_get_fetch_time(GF_DashClient *dash)
405 : {
406 : u64 utc = 0;
407 :
408 218 : if (dash->mpd_dnload && dash->dash_io->get_utc_start_time)
409 133 : utc = dash->dash_io->get_utc_start_time(dash->dash_io, dash->mpd_dnload);
410 133 : if (!utc)
411 92 : utc = gf_net_get_utc();
412 218 : return utc;
413 : }
414 :
415 :
416 792 : static u32 gf_dash_group_count_rep_needed(GF_DASH_Group *group)
417 : {
418 : u32 count, nb_rep_need, next_rep_index_plus_one;
419 : GF_MPD_Representation *rep;
420 792 : count = gf_list_count(group->adaptation_set->representations);
421 : nb_rep_need = 1;
422 792 : if (!group->base_rep_index_plus_one || (group->base_rep_index_plus_one == group->max_complementary_rep_index+1))
423 : return nb_rep_need; // we need to download only one representation
424 0 : rep = gf_list_get(group->adaptation_set->representations, group->base_rep_index_plus_one-1);
425 0 : next_rep_index_plus_one = rep->playback.enhancement_rep_index_plus_one;
426 0 : while ((nb_rep_need < count) && rep->playback.enhancement_rep_index_plus_one) {
427 0 : nb_rep_need++;
428 0 : if (next_rep_index_plus_one == group->max_complementary_rep_index+1)
429 : break;
430 0 : rep = gf_list_get(group->adaptation_set->representations, next_rep_index_plus_one-1);
431 0 : next_rep_index_plus_one = rep->playback.enhancement_rep_index_plus_one;
432 : }
433 :
434 : assert(nb_rep_need <= count);
435 :
436 : return nb_rep_need;
437 : }
438 :
439 : static
440 113 : u32 gf_dash_check_mpd_root_type(const char *local_url)
441 : {
442 113 : if (local_url) {
443 113 : char *rtype = gf_xml_get_root_type(local_url, NULL);
444 113 : if (rtype) {
445 : u32 handled = 0;
446 113 : if (!strcmp(rtype, "MPD")) {
447 : handled = 1;
448 : }
449 2 : else if (!strcmp(rtype, "SmoothStreamingMedia")) {
450 : handled = 2;
451 : }
452 113 : gf_free(rtype);
453 113 : return handled;
454 : }
455 : }
456 : return GF_FALSE;
457 : }
458 :
459 0 : static Bool gf_dash_get_date(GF_DashClient *dash, char *scheme_id, char *url, u64 *utc)
460 : {
461 : GF_DASHFileIOSession session;
462 : GF_Err e;
463 : u8 *data;
464 : u32 len;
465 : Bool res = GF_TRUE;
466 : const char *cache_name;
467 0 : *utc = 0;
468 :
469 : //unsupported schemes
470 0 : if (!strcmp(scheme_id, "urn:mpeg:dash:utc:ntp:2014")) return GF_FALSE;
471 0 : if (!strcmp(scheme_id, "urn:mpeg:dash:utc:sntp:2014")) return GF_FALSE;
472 :
473 0 : if (!dash->dash_io) return GF_FALSE;
474 :
475 0 : session = dash->dash_io->create(dash->dash_io, GF_FALSE, url, -2);
476 0 : if (!session) return GF_FALSE;
477 0 : e = dash->dash_io->run(dash->dash_io, session);
478 0 : if (e) {
479 0 : dash->dash_io->del(dash->dash_io, session);
480 : return GF_FALSE;
481 : }
482 0 : cache_name = dash->dash_io->get_cache_name(dash->dash_io, session);
483 0 : gf_blob_get(cache_name, &data, &len, NULL);
484 :
485 0 : if (!strcmp(scheme_id, "urn:mpeg:dash:utc:http-head:2014")) {
486 0 : const char *hdr = dash->dash_io->get_header_value(dash->dash_io, session, "Date");
487 0 : if (hdr)
488 0 : *utc = gf_net_parse_date(hdr);
489 : else
490 : res = GF_FALSE;
491 : }
492 0 : else if (!data) {
493 : res = GF_FALSE;
494 : } else {
495 0 : if (!strcmp(scheme_id, "urn:mpeg:dash:utc:http-xsdate:2014")) {
496 0 : *utc = gf_mpd_parse_date(data);
497 : }
498 0 : else if (!strcmp(scheme_id, "urn:mpeg:dash:utc:http-iso:2014")) {
499 0 : *utc = gf_net_parse_date(data);
500 : }
501 0 : else if (!strcmp(scheme_id, "urn:mpeg:dash:utc:http-ntp:2014")) {
502 : u64 ntp_ts;
503 0 : if (sscanf((char *) data, LLU, &ntp_ts) == 1) {
504 : //ntp value not counted since 1900, assume format is seconds till 1 jan 1970
505 0 : if (ntp_ts<=GF_NTP_SEC_1900_TO_1970) {
506 0 : *utc = ntp_ts*1000;
507 : } else {
508 0 : *utc = gf_net_ntp_to_utc(ntp_ts);
509 : }
510 : } else {
511 : res = GF_FALSE;
512 : }
513 : }
514 : }
515 0 : gf_blob_release(cache_name);
516 :
517 0 : dash->dash_io->del(dash->dash_io, session);
518 : return res;
519 : }
520 :
521 : GF_Err gf_dash_download_resource(GF_DashClient *dash, GF_DASHFileIOSession *sess, const char *url, u64 start_range, u64 end_range, u32 persistent_mode, GF_DASH_Group *group);
522 :
523 546 : static void gf_dash_group_timeline_setup(GF_MPD *mpd, GF_DASH_Group *group, u64 fetch_time)
524 : {
525 : GF_MPD_SegmentTimeline *timeline = NULL;
526 : GF_MPD_Representation *rep = NULL;
527 : GF_MPD_Descriptor *utc_timing = NULL;
528 : const char *val;
529 : u32 shift, timescale;
530 : u64 current_time, current_time_no_timeshift, availabilityStartTime;
531 : u32 ast_diff, start_number;
532 : Double ast_offset = 0;
533 :
534 546 : if (mpd->type==GF_MPD_TYPE_STATIC) {
535 443 : if (group->dash->route_clock_state)
536 : goto setup_route;
537 : return;
538 : }
539 :
540 : //always init clock even if active period is a remote one
541 : #if 0
542 : if (group->period->origin_base_url && (group->period->type != GF_MPD_TYPE_DYNAMIC))
543 : return;
544 : #endif
545 :
546 : /*M3U8 does not use NTP sync, we solve edge while loading subplaylist */
547 103 : if (group->dash->is_m3u8) {
548 : return;
549 : }
550 :
551 81 : if (group->dash->is_smooth) {
552 : u32 seg_idx = 0;
553 : u64 timeshift = 0;
554 0 : if (group->dash->initial_time_shift_value && ((s32) mpd->time_shift_buffer_depth>=0)) {
555 0 : if (group->dash->initial_time_shift_value<=100) {
556 0 : timeshift = mpd->time_shift_buffer_depth;
557 0 : timeshift *= group->dash->initial_time_shift_value;
558 0 : timeshift /= 100;
559 : } else {
560 0 : timeshift = (u32) group->dash->initial_time_shift_value;
561 0 : if (timeshift > mpd->time_shift_buffer_depth) timeshift = mpd->time_shift_buffer_depth;
562 : }
563 0 : timeshift = mpd->time_shift_buffer_depth - timeshift;
564 : }
565 :
566 0 : if (!timeshift && group->adaptation_set->smooth_max_chunks) {
567 : seg_idx = group->adaptation_set->smooth_max_chunks;
568 : } else {
569 : u32 i, count;
570 : u64 start = 0;
571 : u64 cumulated_dur = 0;
572 : GF_MPD_SegmentTimeline *stl;
573 0 : if (!group->adaptation_set->segment_template || !group->adaptation_set->segment_template->segment_timeline)
574 : return;
575 :
576 0 : timeshift *= group->adaptation_set->segment_template->timescale;
577 0 : timeshift /= 1000;
578 : stl = group->adaptation_set->segment_template->segment_timeline;
579 0 : count = gf_list_count(stl->entries);
580 0 : for (i=0; i<count; i++) {
581 : u64 dur;
582 0 : GF_MPD_SegmentTimelineEntry *e = gf_list_get(stl->entries, i);
583 0 : if (!e->duration)
584 0 : continue;
585 : if (e->start_time)
586 : start = e->start_time;
587 :
588 0 : dur = e->duration * (e->repeat_count+1);
589 0 : if (cumulated_dur + dur >= timeshift) {
590 0 : u32 nb_segs = (u32) ( (timeshift - cumulated_dur) / e->duration );
591 0 : seg_idx += nb_segs;
592 0 : break;
593 : }
594 : cumulated_dur += dur;
595 : start += dur;
596 0 : seg_idx += e->repeat_count+1;
597 0 : if (group->adaptation_set->smooth_max_chunks && (seg_idx>=group->adaptation_set->smooth_max_chunks)) {
598 : seg_idx = group->adaptation_set->smooth_max_chunks;
599 : break;
600 : }
601 : }
602 : }
603 0 : group->download_segment_index = (seg_idx>1) ? seg_idx - 1 : 0;
604 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Smooth group tuned in at segment %d\n", group->download_segment_index));
605 : return;
606 : }
607 :
608 81 : if (group->broken_timing )
609 : return;
610 :
611 :
612 : /*if no AST, do not use NTP sync */
613 81 : if (! group->dash->mpd->availabilityStartTime) {
614 0 : group->broken_timing = GF_TRUE;
615 0 : return;
616 : }
617 :
618 81 : if (!fetch_time) {
619 : //when we initialize the timeline without an explicit fetch time, use our local clock - this allows for better precision
620 : //when trying to locate the live edge
621 81 : fetch_time = gf_net_get_utc();
622 : }
623 : //if ROUTE and clock not setup, do it
624 87 : setup_route:
625 84 : val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route");
626 84 : if (val && !group->dash->utc_drift_estimate) {
627 : u32 i;
628 : GF_MPD_Period *dyn_period=NULL;
629 : u32 found = 0;
630 : u64 timeline_offset_ms=0;
631 40 : if (!group->dash->route_clock_state) {
632 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Detected ROUTE DASH service ID %s\n", val));
633 0 : group->dash->route_clock_state = 1;
634 : }
635 :
636 0 : for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
637 40 : dyn_period = gf_list_get(group->dash->mpd->periods, i);
638 40 : if (!dyn_period->xlink_href && !dyn_period->origin_base_url) break;
639 0 : if (dyn_period->xlink_href && !dyn_period->origin_base_url && gf_list_count(dyn_period->adaptation_sets) ) break;
640 : dyn_period = NULL;
641 : }
642 40 : if (!dyn_period) {
643 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ROUTE with no dynamic period, cannot init clock yet\n"));
644 : return;
645 : }
646 :
647 : //for m3u8 we force refreshing the root manifest, because the download session might be tuned on a child playlist
648 : //which will not have the x-route-first-seg set
649 40 : if (group->dash->is_m3u8) {
650 0 : gf_dash_download_resource(group->dash, &(group->dash->mpd_dnload), group->dash->base_url, 0, 0, 1, NULL);
651 : }
652 40 : val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-first-seg");
653 40 : if (!val) {
654 35 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Waiting for ROUTE clock ...\n"));
655 : return;
656 : }
657 :
658 0 : for (i=0; i<gf_list_count(dyn_period->adaptation_sets); i++) {
659 : u64 sr, seg_dur;
660 : u32 j, len, nb_space=0;
661 : GF_MPD_AdaptationSet *set;
662 5 : char *sep, *start, *end, *seg_url = NULL;
663 :
664 5 : set = gf_list_get(dyn_period->adaptation_sets, i);
665 5 : for (j=0; j<gf_list_count(set->representations); j++) {
666 5 : u64 dur = dyn_period->duration;
667 5 : rep = gf_list_get(set->representations, j);
668 :
669 5 : dyn_period->duration = 0;
670 :
671 5 : if (group->dash->is_m3u8) {
672 : u32 k, count;
673 0 : if (found) break;
674 0 : if (!rep->segment_list)
675 0 : continue;
676 0 : count = gf_list_count(rep->segment_list->segment_URLs);
677 0 : for (k=0; k<count; k++) {
678 0 : GF_MPD_SegmentURL *surl = gf_list_get(rep->segment_list->segment_URLs, k);
679 0 : if (surl->media && strstr(surl->media, val)) {
680 0 : found = k+1;
681 0 : break;
682 : }
683 : }
684 0 : continue;
685 : }
686 :
687 5 : gf_mpd_resolve_url(group->dash->mpd, rep, set, dyn_period, "./", 0, GF_MPD_RESOLVE_URL_MEDIA_NOSTART, 9876, 0, &seg_url, &sr, &sr, &seg_dur, NULL, NULL, NULL, NULL);
688 :
689 5 : dyn_period->duration = dur;
690 :
691 5 : sep = seg_url ? strstr(seg_url, "987") : NULL;
692 5 : if (!sep) {
693 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Failed to resolve template for segment #9876 on rep #%d\n", j+1));
694 0 : if (seg_url) gf_free(seg_url);
695 0 : continue;
696 : }
697 : start = sep;
698 5 : end = sep+4;
699 5 : while (start>seg_url && (*(start-1)=='0')) { start--; nb_space++;}
700 5 : start[0]=0;
701 5 : len = (u32) strlen(seg_url)-2;
702 5 : if (!strncmp(val, seg_url+2, len)) {
703 5 : u32 number=0;
704 : char szTemplate[100];
705 :
706 5 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Resolve ROUTE clock on bootstrap segment URL %s template %s\n", val, seg_url+2));
707 :
708 5 : strcpy(szTemplate, seg_url+2);
709 : strcat(szTemplate, "%");
710 5 : if (nb_space) {
711 : char szFmt[20];
712 1 : sprintf(szFmt, "0%d", nb_space+4);
713 : strcat(szTemplate, szFmt);
714 : }
715 : strcat(szTemplate, "d");
716 : strcat(szTemplate, end);
717 5 : if (sscanf(val, szTemplate, &number) == 1) {
718 : u32 startNum = 1;
719 5 : if (dyn_period->segment_template) startNum = dyn_period->segment_template->start_number;
720 5 : if (set->segment_template) startNum = set->segment_template->start_number;
721 5 : if (rep->segment_template) startNum = rep->segment_template->start_number;
722 5 : if (number>=startNum) {
723 : //clock is init which means the segment is available, so the timeline offset must match the AST of the segment (includes seg dur)
724 :
725 5 : const char *ll_val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-ll");
726 5 : if (ll_val && !strcmp(ll_val, "yes")) {
727 : //low latency case, we are currently receiving the segment
728 0 : group->dash->route_low_latency = GF_TRUE;
729 0 : number--;
730 : }
731 :
732 5 : timeline_offset_ms = seg_dur * ( 1 + number - startNum);
733 : }
734 : found = 1;
735 : }
736 : } else {
737 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] ROUTE bootstrap segment URL %s does not match template %s for rep #%d\n", val, seg_url+2, j+1));
738 : }
739 5 : gf_free(seg_url);
740 5 : if (found) break;
741 : }
742 5 : if (found) break;
743 : }
744 5 : if (found) {
745 5 : if (group->dash->is_m3u8) {
746 : //purge segments (we assume we roughly are in the same state on all child playlists, we could keep one for safety)
747 0 : for (i=0; i<gf_list_count(dyn_period->adaptation_sets); i++) {
748 : u32 j;
749 0 : GF_MPD_AdaptationSet *set = gf_list_get(dyn_period->adaptation_sets, i);
750 0 : for (j=0; j<gf_list_count(set->representations); j++) {
751 : u32 to_rem;
752 0 : rep = gf_list_get(set->representations, j);
753 0 : if (!rep->segment_list) continue;
754 0 : to_rem = found-1;
755 0 : while (to_rem) {
756 0 : GF_MPD_SegmentURL *surl = gf_list_pop_front(rep->segment_list->segment_URLs);
757 0 : gf_mpd_segment_url_free(surl);
758 0 : to_rem--;
759 : }
760 : }
761 : }
762 : } else {
763 : //adjust so that nb_seg = current_time/segdur = (fetch-ast)/seg_dur;
764 : // = (fetch- ( mpd->availabilityStartTime + group->dash->utc_shift + group->dash->utc_drift_estimate) / segdur;
765 : //hence nb_seg*seg_dur = fetch - mpd->availabilityStartTime - group->dash->utc_shift - group->dash->utc_drift_estimate
766 : //so group->dash->utc_drift_estimate = fetch - (mpd->availabilityStartTime + nb_seg*seg_dur)
767 :
768 :
769 5 : u64 utc = mpd->availabilityStartTime + dyn_period->start + timeline_offset_ms;
770 5 : group->dash->utc_drift_estimate = ((s64) fetch_time - (s64) utc);
771 5 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff of ROUTE broadcast "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU" - bootstraping on segment %s\n", group->dash->utc_drift_estimate, fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime, val));
772 : }
773 5 : group->dash->route_clock_state = 2;
774 : } else {
775 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Failed to setup ROUTE clock from segment template with bootstrap URL %s, using NTP\n", val));
776 0 : group->dash->route_clock_state = 3;
777 : }
778 5 : if (mpd->type==GF_MPD_TYPE_STATIC) {
779 1 : if (found)
780 1 : group->dash->route_skip_segments_ms = (u32) timeline_offset_ms;
781 1 : group->timeline_setup = GF_TRUE;
782 1 : return;
783 : }
784 : }
785 44 : else if (val) {
786 1 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] ROUTE clock already setup - UTC diff of ROUTE broadcast "LLD" ms\n", group->dash->utc_drift_estimate));
787 : } else {
788 43 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] No ROUTE entity on HTPP request\n"));
789 : }
790 :
791 48 : if (!group->dash->route_clock_state || (group->dash->route_clock_state>2)) {
792 43 : GF_MPD_ProducerReferenceTime *pref = gf_list_get(group->adaptation_set->producer_reference_time, 0);
793 43 : if (pref)
794 0 : utc_timing = pref->utc_timing;
795 0 : if (!utc_timing)
796 43 : utc_timing = gf_list_get(group->dash->mpd->utc_timings, 0);
797 : }
798 :
799 43 : if (utc_timing && utc_timing->scheme_id_uri) {
800 : Bool res = GF_FALSE;
801 0 : u64 utc=0;
802 : s64 drift_estimate;
803 :
804 0 : if (!strcmp(utc_timing->scheme_id_uri, "urn:mpeg:dash:utc:direct:2014")) {
805 0 : utc = gf_net_parse_date(utc_timing->value);
806 : res = GF_TRUE;
807 : } else {
808 0 : char *time_refs = utc_timing->value;
809 : utc = 0;
810 0 : while (time_refs) {
811 0 : char *sep = strchr(time_refs, ' ');
812 0 : if (sep) sep[0] = 0;
813 :
814 0 : res = gf_dash_get_date(group->dash, utc_timing->scheme_id_uri, time_refs, &utc);
815 :
816 : time_refs = NULL;
817 0 : if (sep) {
818 0 : sep[0] = ' ';
819 0 : time_refs = sep+1;
820 : }
821 0 : if (res) break;
822 : }
823 : }
824 0 : if (res) {
825 0 : drift_estimate = ((s64) fetch_time - (s64) utc);
826 0 : group->dash->utc_drift_estimate = drift_estimate;
827 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff between client and server (%s): "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU"\n", utc_timing->value, group->dash->utc_drift_estimate, fetch_time, utc,
828 : group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime));
829 : } else {
830 : utc_timing = NULL;
831 : }
832 : }
833 :
834 48 : if ((!group->dash->route_clock_state || (group->dash->route_clock_state>2))
835 43 : && !group->dash->ntp_forced
836 43 : && group->dash->estimate_utc_drift
837 43 : && !group->dash->utc_drift_estimate
838 37 : && group->dash->mpd_dnload
839 8 : && group->dash->dash_io->get_header_value
840 8 : && !utc_timing
841 : ) {
842 8 : val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "Server-UTC");
843 8 : if (val) {
844 : u64 utc;
845 2 : sscanf(val, LLU, &utc);
846 2 : group->dash->utc_drift_estimate = ((s64) fetch_time - (s64) utc);
847 2 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff between client and server "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU"\n", group->dash->utc_drift_estimate, fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime));
848 : } else {
849 : s64 drift_estimate = 0;
850 : u64 utc = 0;
851 6 : val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "Date");
852 6 : if (val)
853 6 : utc = gf_net_parse_date(val);
854 6 : if (utc)
855 6 : drift_estimate = ((s64) fetch_time - (s64) utc);
856 :
857 : //HTTP date is in second - if the clock diff is less than 1 sec, we cannot infer anything
858 6 : if (ABS(drift_estimate) > 1000) {
859 3 : group->dash->utc_drift_estimate = 1 + drift_estimate;
860 3 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff between client and server "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU"\n", group->dash->utc_drift_estimate, fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime));
861 : } else {
862 3 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] No UTC diff between client and server (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU"\n", fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime));
863 3 : group->dash->utc_drift_estimate = 1;
864 : }
865 : }
866 : }
867 :
868 : availabilityStartTime = 0;
869 48 : if ((s64) mpd->availabilityStartTime + group->dash->utc_shift > (s64) - group->dash->utc_drift_estimate) {
870 48 : availabilityStartTime = mpd->availabilityStartTime + group->dash->utc_shift + group->dash->utc_drift_estimate;
871 : }
872 :
873 :
874 : #ifdef FORCE_DESYNC
875 : availabilityStartTime -= FORCE_DESYNC;
876 : #endif
877 :
878 48 : ast_diff = (u32) (availabilityStartTime - group->dash->mpd->availabilityStartTime);
879 : current_time = fetch_time;
880 :
881 48 : if (current_time < availabilityStartTime) {
882 : //if more than 1 sec consider we have a pb
883 0 : if (availabilityStartTime - current_time >= 1000) {
884 : Bool broken_timing = GF_TRUE;
885 : #ifndef _WIN32_WCE
886 : time_t gtime1, gtime2;
887 : struct tm *t1, *t2;
888 0 : gtime1 = current_time / 1000;
889 0 : t1 = gf_gmtime(>ime1);
890 0 : gtime2 = availabilityStartTime / 1000;
891 0 : t2 = gf_gmtime(>ime2);
892 0 : if (t1 == t2) {
893 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Slight drift in UTC clock at time %d-%02d-%02dT%02d:%02d:%02dZ: diff AST - now %d ms\n", 1900+t1->tm_year, t1->tm_mon+1, t1->tm_mday, t1->tm_hour, t1->tm_min, t1->tm_sec, (s32) (availabilityStartTime - current_time) ));
894 : current_time = 0;
895 : broken_timing = GF_FALSE;
896 : }
897 0 : else if (t1 && t2) {
898 0 : t1->tm_year = t2->tm_year;
899 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in UTC clock: current time %d-%02d-%02dT%02d:%02d:%02dZ is less than AST %d-%02d-%02dT%02d:%02d:%02dZ - diff AST-now %d ms\n",
900 : 1900+t1->tm_year, t1->tm_mon+1, t1->tm_mday, t1->tm_hour, t1->tm_min, t1->tm_sec,
901 : 1900+t2->tm_year, t2->tm_mon+1, t2->tm_mday, t2->tm_hour, t2->tm_min, t2->tm_sec,
902 : (u32) (availabilityStartTime - current_time)
903 : ));
904 : } else {
905 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in UTC clock: could not retrieve time!\n"));
906 : }
907 :
908 : #endif
909 : if (broken_timing) {
910 0 : if (group->dash->utc_shift + group->dash->utc_drift_estimate > 0) {
911 : availabilityStartTime = current_time;
912 : } else {
913 0 : group->broken_timing = GF_TRUE;
914 0 : return;
915 : }
916 : }
917 : } else {
918 : availabilityStartTime = current_time;
919 : current_time = 0;
920 : }
921 : }
922 48 : else current_time -= availabilityStartTime;
923 :
924 48 : if (gf_list_count(group->dash->mpd->periods)) {
925 : u64 seg_start_ms = current_time;
926 48 : u64 seg_end_ms = (u64) (seg_start_ms + group->segment_duration*1000);
927 : u32 i;
928 : u64 start = 0;
929 53 : for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
930 53 : GF_MPD_Period *ap = gf_list_get(group->dash->mpd->periods, i);
931 53 : if (ap->start) start = ap->start;
932 :
933 53 : if (group->dash->initial_period_tunein
934 30 : && (seg_start_ms>=ap->start)
935 27 : && (!ap->duration || (seg_end_ms<=start + ap->duration))
936 : ) {
937 24 : if (i != group->dash->active_period_index) {
938 0 : group->dash->reinit_period_index = 1+i;
939 0 : group->dash->start_range_period = (Double) seg_start_ms;
940 0 : group->dash->start_range_period -= ap->start;
941 0 : group->dash->start_range_period /= 1000;
942 0 : return;
943 : }
944 : }
945 :
946 53 : if (!ap->duration) break;
947 5 : start += ap->duration;
948 : }
949 : }
950 :
951 : //compute current time in period
952 48 : if (current_time < group->period->start) {
953 4 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Period will start in %d ms\n", group->period->start - current_time));
954 : current_time = 0;
955 : } else {
956 44 : if (group->dash->initial_period_tunein || group->force_timeline_reeval) {
957 44 : current_time -= group->period->start;
958 : } else {
959 : //initial period was setup, consider we are moving to a new period, so time in this period is 0
960 : current_time = 0;
961 0 : if (group->start_playback_range) current_time = (u64) (group->start_playback_range*1000);
962 : }
963 : }
964 :
965 : current_time_no_timeshift = current_time;
966 48 : if ( ((s32) mpd->time_shift_buffer_depth>=0)) {
967 :
968 19 : if (group->dash->initial_time_shift_value) {
969 0 : if (group->dash->initial_time_shift_value<=100) {
970 : shift = mpd->time_shift_buffer_depth;
971 0 : shift *= group->dash->initial_time_shift_value;
972 0 : shift /= 100;
973 : } else {
974 : shift = (u32) group->dash->initial_time_shift_value;
975 0 : if (shift > mpd->time_shift_buffer_depth) shift = mpd->time_shift_buffer_depth;
976 : }
977 :
978 0 : if (current_time < shift) current_time = 0;
979 0 : else current_time -= shift;
980 : }
981 : }
982 48 : group->dash->time_in_tsb = group->dash->prev_time_in_tsb = 0;
983 :
984 : timeline = NULL;
985 : timescale=1;
986 : start_number=0;
987 48 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
988 :
989 48 : if (group->period->segment_list) {
990 0 : if (group->period->segment_list->segment_timeline) timeline = group->period->segment_list->segment_timeline;
991 0 : if (group->period->segment_list->timescale) timescale = group->period->segment_list->timescale;
992 0 : if (group->period->segment_list->start_number) start_number = group->period->segment_list->start_number;
993 0 : if (group->period->segment_list->availability_time_offset) ast_offset = group->period->segment_list->availability_time_offset;
994 : }
995 48 : if (group->adaptation_set->segment_list) {
996 0 : if (group->adaptation_set->segment_list->segment_timeline) timeline = group->adaptation_set->segment_list->segment_timeline;
997 0 : if (group->adaptation_set->segment_list->timescale) timescale = group->adaptation_set->segment_list->timescale;
998 0 : if (group->adaptation_set->segment_list->start_number) start_number = group->adaptation_set->segment_list->start_number;
999 0 : if (group->adaptation_set->segment_list->availability_time_offset) ast_offset = group->adaptation_set->segment_list->availability_time_offset;
1000 : }
1001 48 : if (rep->segment_list) {
1002 0 : if (rep->segment_list->segment_timeline) timeline = rep->segment_list->segment_timeline;
1003 0 : if (rep->segment_list->timescale) timescale = rep->segment_list->timescale;
1004 0 : if (rep->segment_list->start_number) start_number = rep->segment_list->start_number;
1005 0 : if (rep->segment_list->availability_time_offset) ast_offset = rep->segment_list->availability_time_offset;
1006 : }
1007 :
1008 48 : if (group->period->segment_template) {
1009 0 : if (group->period->segment_template->segment_timeline) timeline = group->period->segment_template->segment_timeline;
1010 0 : if (group->period->segment_template->timescale) timescale = group->period->segment_template->timescale;
1011 0 : if (group->period->segment_template->start_number) start_number = group->period->segment_template->start_number;
1012 0 : if (group->period->segment_template->availability_time_offset) ast_offset = group->period->segment_template->availability_time_offset;
1013 : }
1014 48 : if (group->adaptation_set->segment_template) {
1015 38 : if (group->adaptation_set->segment_template->segment_timeline) timeline = group->adaptation_set->segment_template->segment_timeline;
1016 38 : if (group->adaptation_set->segment_template->timescale) timescale = group->adaptation_set->segment_template->timescale;
1017 38 : if (group->adaptation_set->segment_template->start_number) start_number = group->adaptation_set->segment_template->start_number;
1018 38 : if (group->adaptation_set->segment_template->availability_time_offset) ast_offset = group->adaptation_set->segment_template->availability_time_offset;
1019 : }
1020 48 : if (rep->segment_template) {
1021 10 : if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline;
1022 10 : if (rep->segment_template->timescale) timescale = rep->segment_template->timescale;
1023 10 : if (rep->segment_template->start_number) start_number = rep->segment_template->start_number;
1024 10 : if (rep->segment_template->availability_time_offset) ast_offset = rep->segment_template->availability_time_offset;
1025 : }
1026 :
1027 48 : group->is_low_latency = GF_FALSE;
1028 48 : if (group->dash->low_latency_mode==GF_DASH_LL_DISABLE) {
1029 : ast_offset = 0;
1030 48 : } else if (ast_offset>0) {
1031 13 : group->is_low_latency = GF_TRUE;
1032 : }
1033 48 : if (timeline) {
1034 : u64 start_segtime = 0;
1035 : u64 segtime = 0;
1036 : u64 current_time_rescale;
1037 : u64 timeline_duration = 0;
1038 : u32 count;
1039 : u64 last_s_dur=0;
1040 : u32 i, seg_idx = 0;
1041 :
1042 : current_time_rescale = current_time;
1043 4 : current_time_rescale *= timescale;
1044 4 : current_time_rescale /= 1000;
1045 :
1046 4 : count = gf_list_count(timeline->entries);
1047 160 : for (i=0; i<count; i++) {
1048 152 : GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
1049 :
1050 152 : if (!i && (current_time_rescale + ent->duration < ent->start_time)) {
1051 0 : current_time_rescale = current_time_no_timeshift * timescale / 1000;
1052 : }
1053 152 : timeline_duration += (1+ent->repeat_count)*ent->duration;
1054 :
1055 152 : if (i+1 == count) timeline_duration -= ent->duration;
1056 152 : last_s_dur=ent->duration;
1057 : }
1058 :
1059 :
1060 4 : if (!group->dash->mpd->minimum_update_period) {
1061 1 : last_s_dur *= 1000;
1062 1 : last_s_dur /= timescale;
1063 1 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] dynamic MPD but no update period specified and SegmentTimeline used - will use segment duration %d ms as default update rate\n", last_s_dur));
1064 :
1065 1 : group->dash->mpd->minimum_update_period = (u32) last_s_dur;
1066 : }
1067 :
1068 152 : for (i=0; i<count; i++) {
1069 : u32 repeat;
1070 152 : GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
1071 152 : if (!segtime) {
1072 4 : start_segtime = segtime = ent->start_time;
1073 :
1074 : //if current time is before the start of the previous segment, consider our timing is broken
1075 4 : if (current_time_rescale + ent->duration < segtime) {
1076 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] current time "LLU" is before start time "LLU" of first segment in timeline (timescale %d) by %g sec - using first segment as starting point\n", current_time_rescale, segtime, timescale, (segtime-current_time_rescale)*1.0/timescale));
1077 0 : group->download_segment_index = seg_idx;
1078 0 : group->nb_segments_in_rep = count;
1079 0 : group->start_playback_range = (segtime)*1.0/timescale;
1080 0 : group->ast_at_init = availabilityStartTime;
1081 0 : group->ast_offset = (u32) (ast_offset*1000);
1082 0 : group->broken_timing = GF_TRUE;
1083 0 : return;
1084 : }
1085 : }
1086 :
1087 152 : repeat = 1+ent->repeat_count;
1088 904 : while (repeat) {
1089 600 : if ((current_time_rescale >= segtime) && (current_time_rescale < segtime + ent->duration)) {
1090 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Found segment %d for current time "LLU" is in SegmentTimeline ["LLU"-"LLU"] (timecale %d - current index %d - startNumber %d)\n", seg_idx, current_time_rescale, start_segtime, segtime + ent->duration, timescale, group->download_segment_index, start_number));
1091 :
1092 0 : group->download_segment_index = seg_idx;
1093 0 : group->nb_segments_in_rep = seg_idx + count - i;
1094 0 : group->start_playback_range = (current_time)/1000.0;
1095 0 : group->ast_at_init = availabilityStartTime;
1096 0 : group->ast_offset = (u32) (ast_offset*1000);
1097 :
1098 : //to remove - this is a hack to speedup starting for some strange MPDs which announce the live point as the first segment but have already produced the complete timeline
1099 0 : if (group->dash->utc_drift_estimate<0) {
1100 0 : group->ast_at_init -= (timeline_duration - (segtime-start_segtime)) *1000/timescale;
1101 : }
1102 : return;
1103 : }
1104 600 : segtime += ent->duration;
1105 600 : repeat--;
1106 600 : seg_idx++;
1107 : last_s_dur=ent->duration;
1108 : }
1109 : }
1110 : //check if we're ahead of time but "reasonnably" ahead (max 1 min) - otherwise consider the timing is broken
1111 4 : if ((current_time_rescale + last_s_dur >= segtime) && (current_time_rescale <= segtime + 60*timescale)) {
1112 4 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] current time "LLU" is greater than last SegmentTimeline end "LLU" - defaulting to last entry in SegmentTimeline\n", current_time_rescale, segtime));
1113 4 : group->download_segment_index = seg_idx-1;
1114 4 : group->nb_segments_in_rep = seg_idx;
1115 : //we can't trust our UTC check, play from last segment with start_range=0 (eg from start of first segment)
1116 4 : group->start_playback_range = 0;
1117 :
1118 4 : group->ast_at_init = availabilityStartTime;
1119 4 : group->ast_offset = (u32) (ast_offset*1000);
1120 : //force an update in half the target period
1121 4 : group->dash->last_update_time = gf_sys_clock() + group->dash->mpd->minimum_update_period/2;
1122 : } else {
1123 : //NOT FOUND !!
1124 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] current time "LLU" is NOT in SegmentTimeline ["LLU"-"LLU"] - cannot estimate current startNumber, default to 0 ...\n", current_time_rescale, start_segtime, segtime));
1125 0 : group->download_segment_index = 0;
1126 0 : group->nb_segments_in_rep = 10;
1127 0 : group->broken_timing = GF_TRUE;
1128 : }
1129 :
1130 : return;
1131 : }
1132 :
1133 44 : if (group->segment_duration) {
1134 44 : u32 nb_segs_in_update = (u32) (mpd->minimum_update_period / (1000*group->segment_duration) );
1135 44 : Double nb_seg = (Double) current_time;
1136 44 : nb_seg /= 1000;
1137 44 : nb_seg /= group->segment_duration;
1138 44 : shift = (u32) nb_seg;
1139 :
1140 44 : if ((group->dash->route_clock_state == 2) && shift) {
1141 : //shift currently points to the next segment after the one used for clock bootstrap
1142 5 : if (!group->dash->route_low_latency)
1143 5 : shift--;
1144 : //avoid querying too early the cache since segments do not usually arrive exactly on time ...
1145 5 : availabilityStartTime += group->dash->route_ast_shift;
1146 : }
1147 :
1148 44 : if (group->dash->initial_period_tunein || group->force_timeline_reeval) {
1149 : u64 seg_start_ms, seg_end_ms;
1150 44 : if (group->force_timeline_reeval) {
1151 19 : group->start_number_at_last_ast = 0;
1152 19 : group->force_timeline_reeval = GF_FALSE;
1153 : }
1154 44 : seg_start_ms = (u64) (group->segment_duration * (shift+start_number) * 1000);
1155 44 : seg_end_ms = (u64) (seg_start_ms + group->segment_duration*1000);
1156 : //we are in the right period
1157 44 : if (seg_start_ms>=group->period->start && (!group->period->duration || (seg_end_ms<=group->period->start+group->period->duration)) ) {
1158 : } else {
1159 : u32 i;
1160 : u64 start = 0;
1161 1 : for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
1162 2 : GF_MPD_Period *ap = gf_list_get(group->dash->mpd->periods, i);
1163 2 : if (ap->start) start = ap->start;
1164 :
1165 2 : if ((seg_start_ms>=ap->start) && (!ap->duration || (seg_end_ms<=start + ap->duration))) {
1166 1 : group->dash->reinit_period_index = 1+i;
1167 1 : group->dash->start_range_period = (Double) seg_start_ms;
1168 1 : group->dash->start_range_period -= ap->start;
1169 1 : group->dash->start_range_period /= 1000;
1170 1 : return;
1171 : }
1172 :
1173 1 : if (!ap->duration) break;
1174 1 : start += ap->duration;
1175 : }
1176 : }
1177 : }
1178 :
1179 : //not time shifting, we are at the live edge, we must stick to start of segment otherwise we won't have enough data to play until next segment is ready
1180 :
1181 43 : if (!group->dash->initial_time_shift_value) {
1182 : Double time_in_seg;
1183 : //by default playback starts at beginning of segment
1184 43 : group->start_playback_range = shift * group->segment_duration;
1185 :
1186 : time_in_seg = (Double) current_time/1000.0;
1187 43 : time_in_seg -= group->start_playback_range;
1188 :
1189 : //if low latency, try to adjust
1190 43 : if (ast_offset) {
1191 : Double ast_diff_d;
1192 13 : if (ast_offset>group->segment_duration) ast_offset = group->segment_duration;
1193 13 : ast_diff_d = group->segment_duration - ast_offset;
1194 :
1195 : //we assume that in low latency mode, chunks are made available every (group->segment_duration - ast_offset)
1196 : //we need to seek such that the remaining time R satisfies now + R = NextSegAST
1197 : //hence S(n) + ms_in_seg + R = S(n+1) + Aoffset
1198 : //which gives us R = S(n+1) + Aoffset - S(n) - ms_in_seg = D + Aoffset - ms_in_seg
1199 : //seek = D - R = D - (D + Aoffset - ms_in_seg) = ms_in_seg - Ao
1200 13 : if (time_in_seg > ast_diff_d) {
1201 13 : group->start_playback_range += time_in_seg - ast_diff_d;
1202 : }
1203 : }
1204 : } else {
1205 0 : group->start_playback_range = (Double) current_time / 1000.0;
1206 : }
1207 :
1208 43 : if (!group->start_number_at_last_ast) {
1209 43 : group->download_segment_index = shift;
1210 43 : group->start_number_at_last_ast = start_number;
1211 :
1212 43 : group->ast_at_init = availabilityStartTime;
1213 43 : group->ast_offset = (u32) (ast_offset*1000);
1214 43 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AST at init "LLD"\n", group->ast_at_init));
1215 :
1216 43 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] At current time "LLD" ms: Initializing Timeline: startNumber=%d segmentNumber=%d segmentDuration=%f - %.03f seconds in segment (start range %g)\n", current_time, start_number, shift, group->segment_duration, group->start_playback_range ? group->start_playback_range - shift*group->segment_duration : 0, group->start_playback_range));
1217 : } else {
1218 0 : group->download_segment_index += start_number;
1219 0 : if (group->download_segment_index > group->start_number_at_last_ast) {
1220 0 : group->download_segment_index -= group->start_number_at_last_ast;
1221 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] At current time %d ms: Updating Timeline: startNumber=%d segmentNumber=%d downloadSegmentIndex=%d segmentDuration=%g AST_diff=%d\n", current_time, start_number, shift, group->download_segment_index, group->segment_duration, ast_diff));
1222 : } else {
1223 0 : group->download_segment_index = shift;
1224 0 : group->ast_at_init = availabilityStartTime;
1225 0 : group->ast_offset = (u32) (ast_offset*1000);
1226 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] At current time "LLU" ms: Re-Initializing Timeline: startNumber=%d segmentNumber=%d segmentDuration=%g AST_diff=%d\n", current_time, start_number, shift, group->segment_duration, ast_diff));
1227 : }
1228 0 : group->start_number_at_last_ast = start_number;
1229 : }
1230 43 : if (group->nb_segments_in_rep) {
1231 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] UTC time indicates first segment in period is %d, MPD indicates %d segments are available\n", group->download_segment_index , group->nb_segments_in_rep));
1232 : } else {
1233 43 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] UTC time indicates first segment in period is %d\n", group->download_segment_index));
1234 : }
1235 :
1236 43 : if (group->nb_segments_in_rep && (group->download_segment_index + nb_segs_in_update > group->nb_segments_in_rep)) {
1237 0 : if (group->download_segment_index < (s32)group->nb_segments_in_rep) {
1238 :
1239 : } else {
1240 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Not enough segments (%d needed vs %d indicated) to reach period endTime indicated in MPD - ignoring MPD duration\n", nb_segs_in_update, group->nb_segments_in_rep - group->download_segment_index ));
1241 0 : group->nb_segments_in_rep = shift + nb_segs_in_update;
1242 0 : group->dash->ignore_mpd_duration = GF_TRUE;
1243 : }
1244 : }
1245 43 : group->prev_segment_ok = GF_TRUE;
1246 : } else {
1247 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Segment duration unknown - cannot estimate current startNumber\n"));
1248 : }
1249 : }
1250 :
1251 :
1252 : /*!
1253 : * Returns true if mime type of a given URL is an M3U8 mime-type
1254 : \param url The url to check
1255 : \param mime The mime-type to check
1256 : \return true if mime-type is OK for M3U8
1257 : */
1258 8 : static Bool gf_dash_is_m3u8_mime(const char *url, const char * mime) {
1259 : u32 i;
1260 8 : if (!url || !mime)
1261 : return GF_FALSE;
1262 8 : if (strstr(url, ".mpd") || strstr(url, ".MPD"))
1263 : return GF_FALSE;
1264 :
1265 0 : for (i = 0 ; GF_DASH_M3U8_MIME_TYPES[i] ; i++) {
1266 0 : if ( !stricmp(mime, GF_DASH_M3U8_MIME_TYPES[i]))
1267 : return GF_TRUE;
1268 : }
1269 : return GF_FALSE;
1270 : }
1271 :
1272 : GF_EXPORT
1273 118 : GF_Err gf_dash_group_check_bandwidth(GF_DashClient *dash, u32 group_idx, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start)
1274 : {
1275 : s32 res;
1276 118 : GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
1277 118 : if (!group) return GF_BAD_PARAM;
1278 :
1279 118 : if (! dash->rate_adaptation_download_monitor) return GF_OK;
1280 : //do not abort if other groups depend on this one
1281 118 : if (group->groups_depending_on) return GF_OK;
1282 118 : if (group->dash->disable_switching) return GF_OK;
1283 118 : if (!total_bytes || !bytes_done || !bits_per_sec) return GF_OK;
1284 118 : if (total_bytes == bytes_done) return GF_OK;
1285 :
1286 : //force a call go query buffer
1287 10 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, group_idx, GF_OK);
1288 :
1289 10 : res = dash->rate_adaptation_download_monitor(dash, group, bits_per_sec, total_bytes, bytes_done, us_since_start, group->buffer_occupancy_ms, (u32) group->current_downloaded_segment_duration);
1290 :
1291 10 : if (res==-1) return GF_OK;
1292 :
1293 0 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_ABORT_DOWNLOAD, gf_list_find(dash->groups, group), GF_OK);
1294 :
1295 : //internal return value, switching has already been setup
1296 0 : if (res<0) return GF_OK;
1297 :
1298 0 : group->force_segment_switch = GF_TRUE;
1299 0 : group->force_representation_idx_plus_one = (u32) res + 1;
1300 0 : return GF_OK;
1301 : }
1302 :
1303 : /*!
1304 : * Download a file with possible retry if GF_IP_CONNECTION_FAILURE|GF_IP_NETWORK_FAILURE
1305 : * (I discovered that with my WIFI connection, I had many issues with BFM-TV downloads)
1306 : * Similar to gf_service_download_new() and gf_dm_sess_process().
1307 : * Parameters are identical to the ones of gf_service_download_new.
1308 : * \see gf_service_download_new()
1309 : */
1310 110 : GF_Err gf_dash_download_resource(GF_DashClient *dash, GF_DASHFileIOSession *sess, const char *url, u64 start_range, u64 end_range, u32 persistent_mode, GF_DASH_Group *group)
1311 : {
1312 : s32 group_idx = -1;
1313 : Bool had_sess = GF_FALSE;
1314 : Bool retry = GF_TRUE;
1315 : GF_Err e;
1316 110 : GF_DASHFileIO *dash_io = dash->dash_io;
1317 :
1318 110 : if (!dash_io) return GF_BAD_PARAM;
1319 110 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading %s starting at UTC "LLU" ms\n", url, gf_net_get_utc() ));
1320 :
1321 110 : if (group) {
1322 50 : group_idx = gf_list_find(group->dash->groups, group);
1323 : }
1324 :
1325 110 : if (! *sess) {
1326 52 : *sess = dash_io->create(dash_io, persistent_mode ? 1 : 0, url, group_idx);
1327 52 : if (!(*sess)) {
1328 2 : if (dash->route_clock_state)
1329 : return GF_IP_NETWORK_EMPTY;
1330 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot try to download %s... out of memory ?\n", url));
1331 : return GF_OUT_OF_MEM;
1332 : }
1333 : } else {
1334 : had_sess = GF_TRUE;
1335 58 : if (persistent_mode!=2) {
1336 18 : e = dash_io->setup_from_url(dash_io, *sess, url, group_idx);
1337 18 : if (e) {
1338 : //with ROUTE we may have 404 right away if nothing in cache yet, not an error
1339 0 : GF_LOG(dash->route_clock_state ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot resetup downloader for url %s: %s\n", url, gf_error_to_string(e) ));
1340 : return e;
1341 : }
1342 : }
1343 : }
1344 :
1345 58 : retry:
1346 :
1347 108 : if (end_range) {
1348 50 : e = dash_io->set_range(dash_io, *sess, start_range, end_range, (persistent_mode==2) ? GF_FALSE : GF_TRUE);
1349 50 : if (e) {
1350 0 : if (had_sess) {
1351 0 : dash_io->del(dash_io, *sess);
1352 0 : *sess = NULL;
1353 0 : return gf_dash_download_resource(dash, sess, url, start_range, end_range, persistent_mode ? 1 : 0, group);
1354 : }
1355 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot setup byte-range download for %s: %s\n", url, gf_error_to_string(e) ));
1356 : return e;
1357 : }
1358 : }
1359 : assert(*sess);
1360 :
1361 : /*issue HTTP GET for headers only*/
1362 108 : e = dash_io->init(dash_io, *sess);
1363 :
1364 108 : if (e>=GF_OK) {
1365 : /*check mime type of the adaptation set if not provided*/
1366 108 : if (group) {
1367 50 : const char *mime = *sess ? dash_io->get_mime(dash_io, *sess) : NULL;
1368 50 : if (mime && !group->service_mime) {
1369 2 : group->service_mime = gf_strdup(mime);
1370 : }
1371 :
1372 :
1373 : /*file cannot be cached on disk !*/
1374 50 : if (dash_io->get_cache_name(dash_io, *sess ) == NULL) {
1375 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Segment %s cannot be cached on disk, will use direct streaming\n", url));
1376 0 : group->segment_must_be_streamed = GF_TRUE;
1377 0 : return GF_OK;
1378 : }
1379 50 : group->segment_must_be_streamed = GF_FALSE;
1380 : }
1381 :
1382 : //release dl_mutex while downloading segment
1383 : /*we can download the file*/
1384 108 : e = dash_io->run(dash_io, *sess);
1385 : } else {
1386 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] At "LLU" error %s - released dl_mutex\n", gf_net_get_utc(), gf_error_to_string(e)));
1387 : }
1388 :
1389 108 : switch (e) {
1390 0 : case GF_IP_CONNECTION_FAILURE:
1391 : case GF_IP_NETWORK_FAILURE:
1392 0 : if (!dash->in_error || group) {
1393 0 : dash_io->del(dash_io, *sess);
1394 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] failed to download, retrying once with %s...\n", url));
1395 0 : *sess = dash_io->create(dash_io, 0, url, group_idx);
1396 0 : if (! (*sess)) {
1397 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot retry to download %s... out of memory ?\n", url));
1398 : return GF_OUT_OF_MEM;
1399 : }
1400 :
1401 0 : if (retry) {
1402 : retry = GF_FALSE;
1403 : goto retry;
1404 : }
1405 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] two consecutive failures, aborting the download %s.\n", url));
1406 : } else if (dash->in_error) {
1407 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Download still in error for %s.\n", url));
1408 : }
1409 : break;
1410 108 : case GF_OK:
1411 108 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Download %s complete at UTC "LLU" ms\n", url, gf_net_get_utc() ));
1412 : break;
1413 0 : default:
1414 : //log as warning, maybe the dash client can recover from this error
1415 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Failed to download %s: %s\n", url, gf_error_to_string(e)));
1416 : break;
1417 : }
1418 : return e;
1419 : }
1420 :
1421 37 : static void gf_dash_get_timeline_duration(GF_MPD *mpd, GF_MPD_Period *period, GF_MPD_SegmentTimeline *timeline, u32 timescale, u32 *nb_segments, Double *max_seg_duration)
1422 : {
1423 : u32 i, count;
1424 : u64 period_duration, start_time, dur;
1425 37 : if (period->duration) {
1426 : period_duration = period->duration;
1427 : } else {
1428 37 : period_duration = mpd->media_presentation_duration - period->start;
1429 : }
1430 37 : period_duration *= timescale;
1431 37 : period_duration /= 1000;
1432 :
1433 37 : *nb_segments = 0;
1434 37 : if (max_seg_duration) *max_seg_duration = 0;
1435 : start_time = 0;
1436 : dur = 0;
1437 37 : count = gf_list_count(timeline->entries);
1438 1185 : for (i=0; i<count; i++) {
1439 1185 : GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
1440 1185 : if ((s32)ent->repeat_count >=0) {
1441 1185 : *nb_segments += 1 + ent->repeat_count;
1442 1185 : if (ent->start_time) {
1443 : start_time = ent->start_time;
1444 12 : dur = (1 + ent->repeat_count);
1445 : } else {
1446 1173 : dur += (1 + ent->repeat_count);
1447 : }
1448 1185 : dur *= ent->duration;
1449 : } else {
1450 : u32 nb_seg = 0;
1451 0 : if (i+1<count) {
1452 0 : GF_MPD_SegmentTimelineEntry *ent2 = gf_list_get(timeline->entries, i+1);
1453 0 : if (ent2->start_time>0) {
1454 0 : nb_seg = (u32) ( (ent2->start_time - start_time - dur) / ent->duration);
1455 0 : dur += ((u64)nb_seg) * ent->duration;
1456 : }
1457 : }
1458 0 : if (!nb_seg) {
1459 0 : nb_seg = (u32) ( (period_duration - start_time) / ent->duration );
1460 0 : dur += ((u64)nb_seg) * ent->duration;
1461 : }
1462 0 : *nb_segments += nb_seg;
1463 : }
1464 1185 : if (max_seg_duration && (*max_seg_duration < ent->duration)) *max_seg_duration = ent->duration;
1465 : }
1466 37 : }
1467 :
1468 801 : static void gf_dash_get_segment_duration(GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, GF_MPD *mpd, u32 *nb_segments, Double *max_seg_duration)
1469 : {
1470 : Double mediaDuration;
1471 : Bool single_segment = GF_FALSE;
1472 : u32 timescale;
1473 : u64 duration;
1474 : GF_MPD_SegmentTimeline *timeline = NULL;
1475 801 : *nb_segments = timescale = 0;
1476 : duration = 0;
1477 :
1478 801 : if (rep->segment_list || set->segment_list || period->segment_list) {
1479 : GF_List *segments = NULL;
1480 155 : if (period->segment_list) {
1481 0 : if (period->segment_list->duration) duration = period->segment_list->duration;
1482 0 : if (period->segment_list->timescale) timescale = period->segment_list->timescale;
1483 0 : if (period->segment_list->segment_URLs) segments = period->segment_list->segment_URLs;
1484 0 : if (period->segment_list->segment_timeline) timeline = period->segment_list->segment_timeline;
1485 : }
1486 155 : if (set->segment_list) {
1487 0 : if (set->segment_list->duration) duration = set->segment_list->duration;
1488 0 : if (set->segment_list->timescale) timescale = set->segment_list->timescale;
1489 0 : if (set->segment_list->segment_URLs) segments = set->segment_list->segment_URLs;
1490 0 : if (set->segment_list->segment_timeline) timeline = set->segment_list->segment_timeline;
1491 : }
1492 155 : if (rep->segment_list) {
1493 155 : if (rep->segment_list->duration) duration = rep->segment_list->duration;
1494 155 : if (rep->segment_list->timescale) timescale = rep->segment_list->timescale;
1495 155 : if (rep->segment_list->segment_URLs) segments = rep->segment_list->segment_URLs;
1496 155 : if (rep->segment_list->segment_timeline) timeline = rep->segment_list->segment_timeline;
1497 : }
1498 155 : if (!timescale) timescale=1;
1499 :
1500 155 : if (timeline) {
1501 0 : gf_dash_get_timeline_duration(mpd, period, timeline, timescale, nb_segments, max_seg_duration);
1502 0 : if (max_seg_duration) *max_seg_duration /= timescale;
1503 : } else {
1504 155 : if (segments)
1505 103 : *nb_segments = gf_list_count(segments);
1506 155 : if (max_seg_duration) {
1507 155 : *max_seg_duration = (Double) duration;
1508 155 : *max_seg_duration /= timescale;
1509 : }
1510 : }
1511 : return;
1512 : }
1513 :
1514 : /*single segment*/
1515 646 : if (rep->segment_base || set->segment_base || period->segment_base) {
1516 18 : *max_seg_duration = (Double)mpd->media_presentation_duration;
1517 18 : *max_seg_duration /= 1000;
1518 18 : *nb_segments = 1;
1519 18 : return;
1520 : }
1521 :
1522 : single_segment = GF_TRUE;
1523 628 : if (period->segment_template) {
1524 : single_segment = GF_FALSE;
1525 0 : if (period->segment_template->duration) duration = period->segment_template->duration;
1526 0 : if (period->segment_template->timescale) timescale = period->segment_template->timescale;
1527 0 : if (period->segment_template->segment_timeline) timeline = period->segment_template->segment_timeline;
1528 : }
1529 628 : if (set->segment_template) {
1530 : single_segment = GF_FALSE;
1531 537 : if (set->segment_template->duration) duration = set->segment_template->duration;
1532 537 : if (set->segment_template->timescale) timescale = set->segment_template->timescale;
1533 537 : if (set->segment_template->segment_timeline) timeline = set->segment_template->segment_timeline;
1534 : }
1535 628 : if (rep->segment_template) {
1536 : single_segment = GF_FALSE;
1537 222 : if (rep->segment_template->duration) duration = rep->segment_template->duration;
1538 222 : if (rep->segment_template->timescale) timescale = rep->segment_template->timescale;
1539 222 : if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline;
1540 : }
1541 628 : if (!timescale) timescale=1;
1542 :
1543 : /*if no SegmentXXX is found, this is a single segment representation (onDemand profile)*/
1544 628 : if (single_segment) {
1545 0 : *max_seg_duration = (Double)mpd->media_presentation_duration;
1546 0 : *max_seg_duration /= 1000;
1547 0 : *nb_segments = 1;
1548 0 : return;
1549 : }
1550 :
1551 628 : if (timeline) {
1552 37 : gf_dash_get_timeline_duration(mpd, period, timeline, timescale, nb_segments, max_seg_duration);
1553 37 : if (max_seg_duration) *max_seg_duration /= timescale;
1554 : } else {
1555 591 : if (max_seg_duration) {
1556 591 : *max_seg_duration = (Double) duration;
1557 591 : *max_seg_duration /= timescale;
1558 : }
1559 591 : mediaDuration = (Double)period->duration;
1560 591 : if (!mediaDuration && mpd->media_presentation_duration) {
1561 2 : u32 i, count = gf_list_count(mpd->periods);
1562 : Double start = 0;
1563 4 : for (i=0; i<count; i++) {
1564 4 : GF_MPD_Period *ap = gf_list_get(mpd->periods, i);
1565 4 : if (ap==period) break;
1566 2 : if (ap->start) start = (Double)ap->start;
1567 2 : start += ap->duration;
1568 : }
1569 2 : mediaDuration = mpd->media_presentation_duration - start;
1570 : }
1571 591 : if (mediaDuration && duration) {
1572 : Double nb_seg = (Double) mediaDuration;
1573 : /*duration is given in ms*/
1574 540 : nb_seg /= 1000;
1575 540 : nb_seg *= timescale;
1576 540 : nb_seg /= duration;
1577 540 : *nb_segments = (u32) nb_seg;
1578 540 : if (*nb_segments < nb_seg) (*nb_segments)++;
1579 : }
1580 : }
1581 : }
1582 :
1583 :
1584 4420 : static u64 gf_dash_get_segment_start_time_with_timescale(GF_DASH_Group *group, u64 *segment_duration, u32 *scale)
1585 : {
1586 4420 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
1587 4420 : GF_MPD_AdaptationSet *set = group->adaptation_set;
1588 4420 : GF_MPD_Period *period = group->period;
1589 4420 : s32 segment_index = group->download_segment_index;
1590 4420 : u64 start_time = 0;
1591 :
1592 4420 : gf_mpd_get_segment_start_time_with_timescale(segment_index,
1593 : period, set, rep,
1594 : &start_time, segment_duration, scale);
1595 :
1596 4420 : return start_time;
1597 : }
1598 :
1599 1365 : static Double gf_dash_get_segment_start_time(GF_DASH_Group *group, Double *segment_duration)
1600 : {
1601 : u64 start = 0;
1602 1376 : u64 dur = 0;
1603 1376 : u32 scale = 1000;
1604 :
1605 1376 : start = gf_dash_get_segment_start_time_with_timescale(group, &dur, &scale);
1606 1365 : if (segment_duration) {
1607 1365 : *segment_duration = (Double) dur;
1608 1365 : *segment_duration /= scale;
1609 : }
1610 1376 : return ((Double)start)/scale;
1611 : }
1612 :
1613 1365 : static u64 gf_dash_get_segment_availability_start_time(GF_MPD *mpd, GF_DASH_Group *group, u32 segment_index, u32 *seg_dur_ms)
1614 : {
1615 1365 : Double seg_ast, seg_dur=0.0;
1616 1365 : seg_ast = gf_dash_get_segment_start_time(group, &seg_dur);
1617 1365 : if (seg_dur_ms) *seg_dur_ms = (u32) (seg_dur * 1000);
1618 :
1619 1365 : seg_ast += seg_dur;
1620 1365 : seg_ast *= 1000;
1621 1365 : seg_ast += group->period->start + group->ast_at_init;
1622 1365 : seg_ast -= group->ast_offset;
1623 1365 : return (u64) seg_ast;
1624 : }
1625 :
1626 8 : static u32 gf_dash_get_index_in_timeline(GF_MPD_SegmentTimeline *timeline, u64 segment_start, u64 start_timescale, u64 timescale)
1627 : {
1628 : u64 start_time = 0;
1629 : u32 idx = 0;
1630 : u32 i, count, repeat;
1631 8 : count = gf_list_count(timeline->entries);
1632 306 : for (i=0; i<count; i++) {
1633 305 : GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
1634 :
1635 305 : if (!i || ent->start_time) start_time = ent->start_time;
1636 :
1637 305 : repeat = ent->repeat_count+1;
1638 1806 : while (repeat) {
1639 1203 : if (start_timescale==timescale) {
1640 1203 : if (start_time == segment_start )
1641 : return idx;
1642 1196 : if (start_time > segment_start) {
1643 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Warning: segment timeline entry start "LLU" greater than segment start "LLU", using current entry\n", start_time, segment_start));
1644 : return idx;
1645 : }
1646 : } else {
1647 0 : if (start_time*start_timescale == segment_start * timescale) return idx;
1648 0 : if (start_time*start_timescale > segment_start * timescale) {
1649 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Warning: segment timeline entry start "LLU" greater than segment start "LLU", using current entry\n", start_time, segment_start));
1650 : return idx;
1651 : }
1652 : }
1653 1196 : start_time+=ent->duration;
1654 1196 : repeat--;
1655 1196 : idx++;
1656 : }
1657 : }
1658 : //end of list in regular case: segment was the last one of the previous list and no changes happend
1659 1 : if (start_timescale==timescale) {
1660 1 : if (start_time == segment_start )
1661 : return idx;
1662 : } else {
1663 0 : if (start_time*start_timescale == segment_start * timescale)
1664 : return idx;
1665 : }
1666 :
1667 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error: could not find previous segment start in current timeline ! seeking to end of timeline\n"));
1668 : return idx;
1669 : }
1670 :
1671 :
1672 96 : static GF_Err gf_dash_merge_segment_timeline(GF_DASH_Group *group, GF_DashClient *dash, GF_MPD_SegmentList *old_list, GF_MPD_SegmentTemplate *old_template, GF_MPD_SegmentList *new_list, GF_MPD_SegmentTemplate *new_template, Double min_start_time)
1673 : {
1674 : GF_MPD_SegmentTimeline *old_timeline, *new_timeline;
1675 : u32 i, idx, timescale, nb_new_segs;
1676 : GF_MPD_SegmentTimelineEntry *ent;
1677 :
1678 : old_timeline = new_timeline = NULL;
1679 96 : if (old_list && old_list->segment_timeline) {
1680 0 : if (!new_list || !new_list->segment_timeline) {
1681 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: segment timeline not present in new MPD segmentList\n"));
1682 : return GF_NON_COMPLIANT_BITSTREAM;
1683 : }
1684 : old_timeline = old_list->segment_timeline;
1685 : new_timeline = new_list->segment_timeline;
1686 0 : timescale = new_list->timescale;
1687 96 : } else if (old_template && old_template->segment_timeline) {
1688 8 : if (!new_template || !new_template->segment_timeline) {
1689 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: segment timeline not present in new MPD segmentTemplate\n"));
1690 : return GF_NON_COMPLIANT_BITSTREAM;
1691 : }
1692 : old_timeline = old_template->segment_timeline;
1693 : new_timeline = new_template->segment_timeline;
1694 8 : timescale = new_template->timescale;
1695 : }
1696 96 : if (!old_timeline && !new_timeline) return GF_OK;
1697 :
1698 8 : if (group) {
1699 8 : group->current_start_time = gf_dash_get_segment_start_time_with_timescale(group, NULL, &group->current_timescale);
1700 : } else {
1701 0 : for (i=0; i<gf_list_count(dash->groups); i++) {
1702 0 : GF_DASH_Group *a_group = gf_list_get(dash->groups, i);
1703 0 : a_group->current_start_time = gf_dash_get_segment_start_time_with_timescale(a_group, NULL, &a_group->current_timescale);
1704 : }
1705 : }
1706 :
1707 : nb_new_segs = 0;
1708 8 : idx=0;
1709 314 : while ((ent = gf_list_enum(new_timeline->entries, &idx))) {
1710 306 : nb_new_segs += 1 + ent->repeat_count;
1711 : }
1712 :
1713 8 : if (group) {
1714 : #ifndef GPAC_DISABLE_LOG
1715 8 : u32 prev_idx = group->download_segment_index;
1716 : #endif
1717 8 : group->nb_segments_in_rep = nb_new_segs;
1718 8 : group->download_segment_index = gf_dash_get_index_in_timeline(new_timeline, group->current_start_time, group->current_timescale, timescale ? timescale : group->current_timescale);
1719 :
1720 8 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Updated SegmentTimeline: New segment number %d - old %d - start time "LLD"\n", group->download_segment_index , prev_idx, group->current_start_time));
1721 : } else {
1722 0 : for (i=0; i<gf_list_count(dash->groups); i++) {
1723 0 : GF_DASH_Group *a_group = gf_list_get(dash->groups, i);
1724 : #ifndef GPAC_DISABLE_LOG
1725 0 : u32 prev_idx = a_group->download_segment_index;
1726 : #endif
1727 0 : a_group->nb_segments_in_rep = nb_new_segs;
1728 0 : a_group->download_segment_index = gf_dash_get_index_in_timeline(new_timeline, a_group->current_start_time, a_group->current_timescale, timescale ? timescale : a_group->current_timescale);
1729 :
1730 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Updated SegmentTimeline: New segment number %d - old %d - start time "LLD"\n", a_group->download_segment_index , prev_idx, a_group->current_start_time));
1731 : }
1732 : }
1733 :
1734 :
1735 : #ifndef GPAC_DISABLE_LOG
1736 8 : if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO) ) {
1737 : u64 start = 0;
1738 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Old SegmentTimeline: \n"));
1739 0 : for (idx=0; idx<gf_list_count(old_timeline->entries); idx++) {
1740 0 : ent = gf_list_get(old_timeline->entries, idx);
1741 0 : if (ent->start_time) start = ent->start_time;
1742 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tt="LLU" d=%d r=%d (current start "LLU")\n", ent->start_time, ent->duration, ent->repeat_count, start));
1743 0 : start += ent->duration * (1+ent->repeat_count);
1744 : }
1745 :
1746 :
1747 : start = 0;
1748 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] New SegmentTimeline: \n"));
1749 0 : for (idx=0; idx<gf_list_count(new_timeline->entries); idx++) {
1750 0 : ent = gf_list_get(new_timeline->entries, idx);
1751 0 : if (ent->start_time) start = ent->start_time;
1752 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tt="LLU" d=%d r=%d (current start "LLU")\n", ent->start_time, ent->duration, ent->repeat_count, start));
1753 0 : start += ent->duration * (1+ent->repeat_count);
1754 : }
1755 : }
1756 : #endif
1757 :
1758 : return GF_OK;
1759 : }
1760 :
1761 8 : static u32 gf_dash_purge_segment_timeline(GF_DASH_Group *group, Double min_start_time)
1762 : {
1763 : u32 nb_removed, time_scale;
1764 : u64 start_time, min_start, duration;
1765 8 : GF_MPD_SegmentTimeline *timeline=NULL;
1766 8 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
1767 :
1768 8 : if (!min_start_time) return 0;
1769 8 : gf_mpd_resolve_segment_duration(rep, group->adaptation_set, group->period, &duration, &time_scale, NULL, &timeline);
1770 8 : if (!timeline) return 0;
1771 :
1772 8 : min_start = (u64) (min_start_time*time_scale);
1773 : start_time = 0;
1774 : nb_removed=0;
1775 0 : while (1) {
1776 8 : GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, 0);
1777 8 : if (!ent) break;
1778 8 : if (ent->start_time) start_time = ent->start_time;
1779 :
1780 8 : while (start_time + ent->duration < min_start) {
1781 0 : if (! ent->repeat_count) break;
1782 0 : ent->repeat_count--;
1783 0 : nb_removed++;
1784 : start_time += ent->duration;
1785 : }
1786 : /*this entry is in our range, keep it and make sure it has a start time*/
1787 8 : if (start_time + ent->duration >= min_start) {
1788 8 : if (!ent->start_time)
1789 0 : ent->start_time = start_time;
1790 : break;
1791 : }
1792 : start_time += ent->duration;
1793 0 : gf_list_rem(timeline->entries, 0);
1794 0 : gf_free(ent);
1795 0 : nb_removed++;
1796 : }
1797 8 : if (nb_removed) {
1798 : GF_MPD_SegmentList *segment_list;
1799 : /*update next download index*/
1800 0 : group->download_segment_index -= nb_removed;
1801 : assert(group->nb_segments_in_rep >= nb_removed);
1802 0 : group->nb_segments_in_rep -= nb_removed;
1803 : /*clean segmentList*/
1804 : segment_list = NULL;
1805 0 : if (group->period && group->period->segment_list) segment_list = group->period->segment_list;
1806 0 : if (group->adaptation_set && group->adaptation_set->segment_list) segment_list = group->adaptation_set->segment_list;
1807 0 : if (rep && rep->segment_list) segment_list = rep->segment_list;
1808 :
1809 0 : if (segment_list) {
1810 : u32 i = nb_removed;
1811 0 : while (i) {
1812 0 : GF_MPD_SegmentURL *seg_url = gf_list_get(segment_list->segment_URLs, 0);
1813 0 : gf_list_rem(segment_list->segment_URLs, 0);
1814 0 : gf_mpd_segment_url_free(seg_url);
1815 0 : i--;
1816 : }
1817 : }
1818 0 : group->nb_segments_purged += nb_removed;
1819 : }
1820 : return nb_removed;
1821 : }
1822 :
1823 0 : static GF_Err gf_dash_solve_representation_xlink(GF_DashClient *dash, GF_MPD_Representation *rep, u8 last_sig[GF_SHA1_DIGEST_SIZE])
1824 : {
1825 : u32 count, i;
1826 : GF_Err e;
1827 : Bool is_local=GF_FALSE;
1828 : const char *local_url;
1829 : char *url;
1830 : GF_DOMParser *parser;
1831 : u8 signature[GF_SHA1_DIGEST_SIZE];
1832 0 : if (!rep->segment_list->xlink_href) return GF_BAD_PARAM;
1833 :
1834 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Resolving Representation SegmentList XLINK %s\n", rep->segment_list->xlink_href));
1835 :
1836 : //SPEC: If this value is present, the element containing the xlink:href attribute and all @xlink attributes included in the element containing @xlink:href shall be removed at the time when the resolution is due.
1837 0 : if (!strcmp(rep->segment_list->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013")) {
1838 0 : gf_mpd_delete_segment_list(rep->segment_list);
1839 0 : rep->segment_list = NULL;
1840 : return GF_OK;
1841 : }
1842 :
1843 : //xlink relative to our MPD base URL
1844 0 : url = gf_url_concatenate(dash->base_url, rep->segment_list->xlink_href);
1845 :
1846 0 : if (!strstr(url, "://") || !strnicmp(url, "file://", 7) ) {
1847 : local_url = url;
1848 : is_local=GF_TRUE;
1849 : e = GF_OK;
1850 : } else {
1851 : /*use non-persistent connection for MPD*/
1852 0 : e = gf_dash_download_resource(dash, &(dash->mpd_dnload), url ? url : rep->segment_list->xlink_href, 0, 0, 0, NULL);
1853 0 : gf_free(url);
1854 : }
1855 :
1856 0 : if (e) {
1857 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot download Representation SegmentList XLINK %s: error %s\n", rep->segment_list->xlink_href, gf_error_to_string(e)));
1858 0 : gf_free(rep->segment_list->xlink_href);
1859 0 : rep->segment_list->xlink_href = NULL;
1860 : return e;
1861 : }
1862 :
1863 0 : if (!is_local) {
1864 : /*in case the session has been restarted, local_url may have been destroyed - get it back*/
1865 0 : local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
1866 : }
1867 :
1868 0 : gf_sha1_file(local_url, signature);
1869 0 : if (! memcmp(signature, last_sig, GF_SHA1_DIGEST_SIZE)) {
1870 0 : if (is_local) gf_free(url);
1871 : return GF_EOS;
1872 : }
1873 : memcpy(last_sig, signature, GF_SHA1_DIGEST_SIZE);
1874 :
1875 0 : parser = gf_xml_dom_new();
1876 0 : e = gf_xml_dom_parse(parser, local_url, NULL, NULL);
1877 0 : if (is_local) gf_free(url);
1878 :
1879 0 : if (e != GF_OK) {
1880 0 : gf_xml_dom_del(parser);
1881 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot parse Representation SegmentList XLINK: error in XML parsing %s\n", gf_error_to_string(e)));
1882 0 : gf_free(rep->segment_list->xlink_href);
1883 0 : rep->segment_list->xlink_href = NULL;
1884 : return GF_NON_COMPLIANT_BITSTREAM;
1885 : }
1886 :
1887 0 : count = gf_xml_dom_get_root_nodes_count(parser);
1888 0 : if (count > 1) {
1889 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] XLINK %s has more than one segment list - ignoring it\n", rep->segment_list->xlink_href));
1890 0 : gf_mpd_delete_segment_list(rep->segment_list);
1891 0 : rep->segment_list = NULL;
1892 : return GF_NON_COMPLIANT_BITSTREAM;
1893 : }
1894 0 : for (i=0; i<count; i++) {
1895 0 : GF_XMLNode *root = gf_xml_dom_get_root_idx(parser, i);
1896 0 : if (!strcmp(root->name, "SegmentList")) {
1897 0 : GF_MPD_SegmentList *new_seg_list = gf_mpd_solve_segment_list_xlink(dash->mpd, root);
1898 : //forbiden
1899 0 : if (new_seg_list && new_seg_list->xlink_href) {
1900 0 : if (new_seg_list->xlink_actuate_on_load) {
1901 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] XLINK %s references to remote element entities that contain another @xlink:href attribute with xlink:actuate set to onLoad - forbiden\n", rep->segment_list->xlink_href));
1902 0 : gf_mpd_delete_segment_list(new_seg_list);
1903 : new_seg_list = NULL;
1904 : } else {
1905 0 : new_seg_list->consecutive_xlink_count = rep->segment_list->consecutive_xlink_count + 1;
1906 : }
1907 : }
1908 : //replace current segment list by the one from remote element entity (located by xlink:href)
1909 0 : gf_mpd_delete_segment_list(rep->segment_list);
1910 0 : rep->segment_list = new_seg_list;
1911 : }
1912 : else
1913 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] XML node %s is not a representation segmentlist - ignoring it\n", root->name));
1914 : }
1915 : return GF_OK;
1916 : }
1917 :
1918 0 : static void dash_purge_xlink(GF_MPD *new_mpd)
1919 : {
1920 0 : u32 i, count = gf_list_count(new_mpd->periods);
1921 0 : for (i=0; i<count; i++) {
1922 0 : GF_MPD_Period *period = gf_list_get(new_mpd->periods, i);
1923 0 : if (!gf_list_count(period->adaptation_sets)) continue;
1924 0 : if (!period->xlink_href) continue;
1925 0 : gf_free(period->xlink_href);
1926 0 : period->xlink_href = NULL;
1927 : }
1928 0 : }
1929 :
1930 107 : static void gf_dash_mark_group_done(GF_DASH_Group *group)
1931 : {
1932 107 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d group is done\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set) ));
1933 107 : group->done = GF_TRUE;
1934 107 : }
1935 :
1936 :
1937 3380 : static const char *dash_strip_base_url(const char *url, const char *base_url)
1938 : {
1939 3380 : const char *url_start = gf_url_get_path(url);
1940 3380 : const char *base_url_start = gf_url_get_path(base_url);
1941 3380 : const char *base_url_end = base_url_start ? strrchr(base_url_start, '/') : NULL;
1942 :
1943 3380 : if (base_url_start && url_start) {
1944 3351 : u32 diff = (u32) (base_url_end - base_url_start);
1945 3351 : if (!strncmp(url_start, base_url_start, diff))
1946 3345 : return url_start + diff + 1;
1947 : }
1948 : return url;
1949 : }
1950 :
1951 130 : static GF_Err gf_dash_solve_m3u8_representation_xlink(GF_DASH_Group *group, GF_MPD_Representation *rep, Bool *is_static, u64 *duration, u8 signature[GF_SHA1_DIGEST_SIZE])
1952 : {
1953 : GF_Err e;
1954 : char *xlink_copy;
1955 : const char *name, *local_url;
1956 130 : GF_DashClient *dash = group->dash;
1957 130 : if (!dash->dash_io->manifest_updated) {
1958 127 : return gf_m3u8_solve_representation_xlink(rep, &dash->getter, is_static, duration, signature);
1959 : }
1960 :
1961 3 : xlink_copy = gf_strdup(rep->segment_list->xlink_href);;
1962 3 : e = gf_m3u8_solve_representation_xlink(rep, &dash->getter, is_static, duration, signature);
1963 : //do not notify m3u8 update if same as last one
1964 3 : if (e==GF_EOS) {
1965 0 : gf_free(xlink_copy);
1966 0 : return e;
1967 : }
1968 :
1969 3 : if (gf_url_is_local(xlink_copy)) {
1970 : local_url = xlink_copy;
1971 : } else {
1972 0 : local_url = dash->getter.get_cache_name(&dash->getter);
1973 : }
1974 3 : name = dash_strip_base_url(xlink_copy, dash->base_url);
1975 3 : dash->dash_io->manifest_updated(dash->dash_io, name, local_url, gf_list_find(dash->groups, group));
1976 3 : gf_free(xlink_copy);
1977 3 : return e;
1978 : }
1979 :
1980 68 : static u32 ls_hls_purge_segments(s32 live_edge_idx, GF_List *l)
1981 : {
1982 68 : u32 i=0, count = gf_list_count(l);
1983 : u32 nb_removed_before_live = 0;
1984 :
1985 : //first remove all ll chunks until live edge if set
1986 68 : if (live_edge_idx>=0) {
1987 21 : count = live_edge_idx;
1988 :
1989 148 : for (i=0; i<count; i++) {
1990 127 : GF_MPD_SegmentURL *seg = gf_list_get(l, i);
1991 127 : if (!seg->hls_ll_chunk_type)
1992 19 : continue;
1993 :
1994 108 : gf_list_rem(l, i);
1995 108 : gf_mpd_segment_url_free(seg);
1996 108 : i--;
1997 108 : count--;
1998 108 : live_edge_idx--;
1999 108 : nb_removed_before_live++;
2000 : }
2001 21 : if (live_edge_idx<0) return nb_removed_before_live;
2002 21 : count = gf_list_count(l);
2003 : //skip live edge
2004 21 : i++;
2005 :
2006 : //skip parts following live edge
2007 77 : while (i<count) {
2008 35 : GF_MPD_SegmentURL *seg = gf_list_get(l, i);
2009 35 : if (!seg->hls_ll_chunk_type)
2010 : break;
2011 35 : i++;
2012 : }
2013 : }
2014 :
2015 : //locate last full seg
2016 68 : while (i<count) {
2017 47 : GF_MPD_SegmentURL *seg = gf_list_get(l, count-1);
2018 47 : if (!seg->hls_ll_chunk_type)
2019 : break;
2020 : count--;
2021 0 : if (!count) break;
2022 : }
2023 : //purge the rest
2024 126 : for (; i<count; i++) {
2025 126 : GF_MPD_SegmentURL *seg = gf_list_get(l, i);
2026 126 : if (!seg->hls_ll_chunk_type)
2027 120 : continue;
2028 :
2029 6 : gf_list_rem(l, i);
2030 6 : gf_mpd_segment_url_free(seg);
2031 6 : i--;
2032 6 : count--;
2033 : }
2034 : return nb_removed_before_live;
2035 : }
2036 :
2037 100 : static GF_Err gf_dash_update_manifest(GF_DashClient *dash)
2038 : {
2039 : GF_Err e;
2040 : Bool force_timeline_setup = GF_FALSE;
2041 : u32 group_idx, rep_idx, i, j;
2042 : u64 fetch_time=0;
2043 : GF_DOMParser *mpd_parser;
2044 : u8 signature[GF_SHA1_DIGEST_SIZE];
2045 : GF_MPD_Period *period=NULL, *new_period=NULL;
2046 : const char *local_url;
2047 : char mime[128];
2048 : char * purl;
2049 : Double timeline_start_time=0;
2050 : GF_MPD *new_mpd=NULL;
2051 : Bool fetch_only = GF_FALSE;
2052 : u32 nb_group_unchanged = 0;
2053 : Bool has_reps_unchanged = GF_FALSE;
2054 :
2055 : //HLS: do not reload the playlist, directly update the reps
2056 100 : if (dash->is_m3u8 && !dash->m3u8_reload_master) {
2057 : new_mpd = NULL;
2058 : new_period = NULL;
2059 84 : fetch_time = dash_get_fetch_time(dash);
2060 84 : period = gf_list_get(dash->mpd->periods, dash->active_period_index);
2061 84 : goto process_m3u8_manifest;
2062 : }
2063 :
2064 16 : if (!dash->mpd_dnload) {
2065 : local_url = purl = NULL;
2066 8 : if (!gf_list_count(dash->mpd->locations)) {
2067 8 : if (gf_file_exists(dash->base_url)) {
2068 8 : local_url = dash->base_url;
2069 : }
2070 8 : if (!local_url) {
2071 : /*we will no longer attempt to update the MPD ...*/
2072 0 : dash->mpd->minimum_update_period = 0;
2073 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: no HTTP source for MPD could be found\n"));
2074 : return GF_BAD_PARAM;
2075 : }
2076 : }
2077 : if (!local_url) {
2078 0 : purl = gf_strdup(gf_list_get(dash->mpd->locations, 0));
2079 :
2080 : /*if no absolute URL, use <Location> to get MPD in case baseURL is relative...*/
2081 0 : if (!strstr(dash->base_url, "://")) {
2082 0 : gf_free(dash->base_url);
2083 0 : dash->base_url = gf_strdup(purl);
2084 : }
2085 : fetch_only = 1;
2086 : }
2087 : } else {
2088 8 : local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
2089 8 : if (local_url) {
2090 8 : gf_file_delete(local_url);
2091 : }
2092 : //use the redirected url stored in base URL - DO NOT USE the redirected URL of the session since
2093 : //the session may have been reused for period XLINK dowload.
2094 8 : purl = gf_strdup( dash->base_url );
2095 : }
2096 :
2097 : /*if update location is specified, update - spec does not say whether location is a relative or absoute URL*/
2098 16 : if (gf_list_count(dash->mpd->locations)) {
2099 0 : char *update_loc = gf_list_get(dash->mpd->locations, 0);
2100 0 : char *update_url = gf_url_concatenate(purl, update_loc);
2101 0 : if (update_url) {
2102 0 : gf_free(purl);
2103 : purl = update_url;
2104 : }
2105 : }
2106 :
2107 16 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updating Playlist %s...\n", purl ? purl : local_url));
2108 16 : if (purl) {
2109 : const char *mime_type;
2110 : /*use non-persistent connection for MPD*/
2111 8 : e = gf_dash_download_resource(dash, &(dash->mpd_dnload), purl, 0, 0, 0, NULL);
2112 8 : if (e!=GF_OK) {
2113 0 : if (!dash->in_error) {
2114 0 : if (e==GF_URL_ERROR) {
2115 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update manifest: %s, aborting\n", gf_error_to_string(e)));
2116 0 : dash->in_error = GF_TRUE;
2117 : } else {
2118 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Error while fetching new manifest (%s) - will retry later\n", gf_error_to_string(e)));
2119 : }
2120 : }
2121 0 : gf_free(purl);
2122 : //try to refetch MPD every second
2123 0 : dash->last_update_time+=1000;
2124 0 : return e;
2125 : } else {
2126 8 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playlist %s updated with success\n", purl));
2127 : }
2128 :
2129 8 : mime_type = dash->dash_io->get_mime(dash->dash_io, dash->mpd_dnload) ;
2130 8 : strcpy(mime, mime_type ? mime_type : "");
2131 8 : strlwr(mime);
2132 :
2133 : /*in case the session has been restarted, local_url may have been destroyed - get it back*/
2134 8 : local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
2135 :
2136 : /* Some servers, for instance http://tv.freebox.fr, serve m3u8 as text/plain */
2137 8 : if (gf_dash_is_m3u8_mime(purl, mime) || strstr(purl, ".m3u8")) {
2138 0 : new_mpd = gf_mpd_new();
2139 0 : e = gf_m3u8_to_mpd(local_url, purl, NULL, dash->reload_count, dash->mimeTypeForM3U8Segments, 0, M3U8_TO_MPD_USE_TEMPLATE, M3U8_TO_MPD_USE_SEGTIMELINE, &dash->getter, new_mpd, GF_FALSE, dash->keep_files);
2140 0 : if (e) {
2141 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in MPD creation %s\n", gf_error_to_string(e)));
2142 0 : gf_mpd_del(new_mpd);
2143 0 : return GF_NON_COMPLIANT_BITSTREAM;
2144 : }
2145 : }
2146 :
2147 8 : gf_free(purl);
2148 :
2149 8 : purl = (char *) dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload) ;
2150 :
2151 : /*if relocated, reassign MPD base URL*/
2152 8 : if (strcmp(purl, dash->base_url)) {
2153 0 : gf_free(dash->base_url);
2154 0 : dash->base_url = gf_strdup(purl);
2155 :
2156 : }
2157 : purl = NULL;
2158 : }
2159 :
2160 16 : fetch_time = dash_get_fetch_time(dash);
2161 :
2162 : // parse the mpd file for filling the GF_MPD structure. Note: for m3u8, MPD has been fetched above
2163 16 : if (!new_mpd) {
2164 16 : u32 res = gf_dash_check_mpd_root_type(local_url);
2165 16 : if ((res==1) && dash->is_smooth) res = 0;
2166 16 : else if ((res==2) && !dash->is_smooth) res = 0;
2167 :
2168 16 : if (!res) {
2169 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: MPD file type is not correct %s\n", local_url));
2170 : return GF_NON_COMPLIANT_BITSTREAM;
2171 : }
2172 :
2173 16 : if (gf_sha1_file( local_url, signature) != GF_OK) {
2174 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] : cannot SHA1 file %s\n", local_url));
2175 : return GF_IO_ERR;
2176 : }
2177 :
2178 16 : if (!dash->in_error && ! memcmp( signature, dash->lastMPDSignature, GF_SHA1_DIGEST_SIZE)) {
2179 :
2180 1 : dash->reload_count++;
2181 1 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] MPD file did not change for %d consecutive reloads\n", dash->reload_count));
2182 : /*if the MPD did not change, we should refresh "soon" but cannot wait a full refresh cycle in case of
2183 : low latencies as we could miss a segment*/
2184 :
2185 1 : if (dash->is_m3u8) {
2186 0 : dash->last_update_time += dash->mpd->minimum_update_period/2;
2187 : } else {
2188 1 : dash->last_update_time = gf_sys_clock();
2189 : }
2190 :
2191 1 : dash->mpd_fetch_time = fetch_time;
2192 1 : return GF_OK;
2193 : }
2194 :
2195 : force_timeline_setup = dash->in_error;
2196 15 : dash->in_error = GF_FALSE;
2197 15 : dash->reload_count = 0;
2198 15 : memcpy(dash->lastMPDSignature, signature, GF_SHA1_DIGEST_SIZE);
2199 :
2200 15 : if (dash->dash_io->manifest_updated) {
2201 0 : char *szName = gf_file_basename(dash->base_url);
2202 0 : dash->dash_io->manifest_updated(dash->dash_io, szName, local_url, -1);
2203 : }
2204 :
2205 : /* It means we have to reparse the file ... */
2206 : /* parse the MPD */
2207 15 : mpd_parser = gf_xml_dom_new();
2208 15 : e = gf_xml_dom_parse(mpd_parser, local_url, NULL, NULL);
2209 15 : if (e != GF_OK) {
2210 0 : gf_xml_dom_del(mpd_parser);
2211 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in XML parsing %s\n", gf_error_to_string(e)));
2212 : return GF_NON_COMPLIANT_BITSTREAM;
2213 : }
2214 15 : new_mpd = gf_mpd_new();
2215 15 : if (dash->is_smooth)
2216 0 : e = gf_mpd_init_smooth_from_dom(gf_xml_dom_get_root(mpd_parser), new_mpd, purl);
2217 : else
2218 15 : e = gf_mpd_init_from_dom(gf_xml_dom_get_root(mpd_parser), new_mpd, purl);
2219 15 : gf_xml_dom_del(mpd_parser);
2220 15 : if (e) {
2221 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in MPD creation %s\n", gf_error_to_string(e)));
2222 0 : gf_mpd_del(new_mpd);
2223 0 : return GF_NON_COMPLIANT_BITSTREAM;
2224 : }
2225 15 : if (dash->ignore_xlink)
2226 0 : dash_purge_xlink(new_mpd);
2227 :
2228 15 : if (!e && dash->split_adaptation_set)
2229 0 : gf_mpd_split_adaptation_sets(new_mpd);
2230 :
2231 : }
2232 :
2233 : assert(new_mpd);
2234 :
2235 15 : period = gf_list_get(dash->mpd->periods, dash->active_period_index);
2236 15 : if (fetch_only && !period) goto exit;
2237 :
2238 : #ifndef GPAC_DISABLE_LOG
2239 15 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Updated manifest:\n"));
2240 18 : for (i=0; i<gf_list_count(new_mpd->periods); i++) {
2241 18 : GF_MPD_Period *ap = gf_list_get(new_mpd->periods, i);
2242 18 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tP#%d: start "LLU" - duration " LLU" - xlink %s\n", i+1, ap->start, ap->duration, ap->xlink_href ? ap->xlink_href : "none"));
2243 : }
2244 : #endif
2245 :
2246 : //if current period was a remote period, do a pass on the new manifest periods, check for xlink
2247 30 : if (period->origin_base_url || period->broken_xlink) {
2248 0 : restart_period_check:
2249 0 : for (i=0; i<gf_list_count(new_mpd->periods); i++) {
2250 0 : new_period = gf_list_get(new_mpd->periods, i);
2251 0 : if (!new_period->xlink_href) continue;
2252 :
2253 0 : if ((new_period->start<=period->start) &&
2254 0 : ( (new_period->start+new_period->duration >= period->start + period->duration) || (!new_period->duration))
2255 : ) {
2256 : const char *base_url;
2257 : u32 insert_idx;
2258 0 : if (period->type == GF_MPD_TYPE_DYNAMIC) {
2259 0 : gf_dash_solve_period_xlink(dash, new_mpd->periods, i);
2260 0 : goto restart_period_check;
2261 : }
2262 : //this is a static period xlink, no need to further update the mpd, jsut swap the old periods in the new MPD
2263 :
2264 0 : base_url = period->origin_base_url ? period->origin_base_url : period->broken_xlink;
2265 0 : if (!base_url) {
2266 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - found new Xlink period overlapping a non-xlink period in original manifest\n"));
2267 0 : gf_mpd_del(new_mpd);
2268 0 : return GF_NON_COMPLIANT_BITSTREAM;
2269 : }
2270 : insert_idx = i;
2271 0 : gf_list_rem(new_mpd->periods, i);
2272 0 : for (j=0; j<gf_list_count(dash->mpd->periods); j++) {
2273 0 : GF_MPD_Period *ap = gf_list_get(dash->mpd->periods, j);
2274 0 : if (!ap->origin_base_url && !ap->broken_xlink) continue;
2275 0 : if (ap->origin_base_url && strcmp(ap->origin_base_url, base_url)) continue;
2276 0 : if (ap->broken_xlink && strcmp(ap->broken_xlink, base_url)) continue;
2277 0 : gf_list_rem(dash->mpd->periods, j);
2278 0 : j--;
2279 0 : gf_list_insert(new_mpd->periods, ap, insert_idx);
2280 0 : insert_idx++;
2281 : }
2282 : //update active period index in new list
2283 0 : dash->active_period_index = gf_list_find(new_mpd->periods, period);
2284 : assert((s32)dash->active_period_index >= 0);
2285 : //this will do the garbage collection
2286 0 : gf_list_add(dash->mpd->periods, new_period);
2287 0 : goto exit;
2288 : }
2289 : new_period=NULL;
2290 : }
2291 : }
2292 :
2293 : new_period = NULL;
2294 1 : for (i=0; i<gf_list_count(new_mpd->periods); i++) {
2295 16 : new_period = gf_list_get(new_mpd->periods, i);
2296 16 : if (new_period->start==period->start) break;
2297 : new_period=NULL;
2298 : }
2299 :
2300 15 : if (!new_period) {
2301 0 : if (dash->mpd->type == GF_MPD_TYPE_DYNAMIC) {
2302 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] current active period not found in MPD update - assuming end of active period and switching to first period in new MPD\n"));
2303 :
2304 : //assume the old period is no longer valid
2305 0 : dash->active_period_index = 0;
2306 0 : dash->request_period_switch = GF_TRUE;
2307 0 : for (i=0; i<gf_list_count(dash->groups); i++) {
2308 0 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
2309 0 : gf_dash_mark_group_done(group);
2310 0 : group->adaptation_set = NULL;
2311 : }
2312 : goto exit;
2313 : }
2314 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing period\n"));
2315 0 : gf_mpd_del(new_mpd);
2316 0 : return GF_NON_COMPLIANT_BITSTREAM;
2317 : }
2318 :
2319 15 : dash->active_period_index = gf_list_find(new_mpd->periods, new_period);
2320 : assert((s32)dash->active_period_index >= 0);
2321 :
2322 15 : if (gf_list_count(period->adaptation_sets) != gf_list_count(new_period->adaptation_sets)) {
2323 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing AdaptationSet\n"));
2324 0 : gf_mpd_del(new_mpd);
2325 0 : return GF_NON_COMPLIANT_BITSTREAM;
2326 : }
2327 :
2328 15 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updating playlist at UTC time "LLU" - availabilityStartTime "LLU"\n", fetch_time, new_mpd->availabilityStartTime));
2329 :
2330 : timeline_start_time = 0;
2331 : /*if not infinity for timeShift, compute min media time before merge and adjust it*/
2332 15 : if (dash->mpd->time_shift_buffer_depth != (u32) -1) {
2333 7 : Double timeshift = dash->mpd->time_shift_buffer_depth;
2334 7 : timeshift /= 1000;
2335 :
2336 18 : for (group_idx=0; group_idx<gf_list_count(dash->groups); group_idx++) {
2337 11 : GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
2338 11 : if (group->selection!=GF_DASH_GROUP_NOT_SELECTABLE) {
2339 : Double group_start = gf_dash_get_segment_start_time(group, NULL);
2340 11 : if (!group_idx || (timeline_start_time > group_start) ) timeline_start_time = group_start;
2341 : }
2342 : }
2343 : /*we can rewind our segments from timeshift*/
2344 7 : if (timeline_start_time > timeshift) timeline_start_time -= timeshift;
2345 : /*we can rewind all segments*/
2346 : else timeline_start_time = 0;
2347 : }
2348 :
2349 : /*update segmentTimeline at Period level*/
2350 15 : e = gf_dash_merge_segment_timeline(NULL, dash, period->segment_list, period->segment_template, new_period->segment_list, new_period->segment_template, timeline_start_time);
2351 15 : if (e) {
2352 0 : gf_mpd_del(new_mpd);
2353 0 : return e;
2354 : }
2355 :
2356 114 : process_m3u8_manifest:
2357 :
2358 196 : for (group_idx=0; group_idx<gf_list_count(dash->groups); group_idx++) {
2359 : GF_MPD_AdaptationSet *set, *new_set=NULL;
2360 : u32 rep_i;
2361 : u32 nb_rep_unchanged = 0;
2362 97 : GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
2363 :
2364 : /*update info even if the group is not selected !*/
2365 97 : if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE)
2366 0 : continue;
2367 :
2368 97 : set = group->adaptation_set;
2369 :
2370 97 : if (new_period) {
2371 13 : new_set = gf_list_get(new_period->adaptation_sets, group_idx);
2372 :
2373 : //sort by bandwidth and quality
2374 13 : for (rep_i = 1; rep_i < gf_list_count(new_set->representations); rep_i++) {
2375 : Bool swap=GF_FALSE;
2376 0 : GF_MPD_Representation *r2 = gf_list_get(new_set->representations, rep_i);
2377 0 : GF_MPD_Representation *r1 = gf_list_get(new_set->representations, rep_i-1);
2378 0 : if (r1->bandwidth > r2->bandwidth) {
2379 : swap=GF_TRUE;
2380 0 : } else if ((r1->bandwidth == r2->bandwidth) && (r1->quality_ranking<r2->quality_ranking)) {
2381 : swap=GF_TRUE;
2382 : }
2383 : if (swap) {
2384 0 : gf_list_rem(new_set->representations, rep_i);
2385 0 : gf_list_insert(new_set->representations, r2, rep_i-1);
2386 : rep_i=0;
2387 : }
2388 : }
2389 :
2390 13 : if (gf_list_count(new_set->representations) != gf_list_count(group->adaptation_set->representations)) {
2391 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing representation in adaptation set\n"));
2392 0 : gf_mpd_del(new_mpd);
2393 0 : return GF_NON_COMPLIANT_BITSTREAM;
2394 : }
2395 : }
2396 :
2397 : /*get all representations in both periods*/
2398 267 : for (rep_idx = 0; rep_idx <gf_list_count(group->adaptation_set->representations); rep_idx++) {
2399 : GF_List *segments, *new_segments;
2400 170 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_idx);
2401 170 : GF_MPD_Representation *new_rep = new_set ? gf_list_get(new_set->representations, rep_idx) : NULL;
2402 : GF_MPD_Representation *hls_temp_rep = NULL;
2403 :
2404 170 : rep->playback.not_modified = GF_FALSE;
2405 :
2406 170 : if (rep->segment_base || group->adaptation_set->segment_base || period->segment_base) {
2407 : assert(new_rep);
2408 0 : if (!new_rep->segment_base && !new_set->segment_base && !new_period->segment_base) {
2409 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment base as previous version\n"));
2410 0 : gf_mpd_del(new_mpd);
2411 0 : return GF_NON_COMPLIANT_BITSTREAM;
2412 : }
2413 : /*what else should we check ??*/
2414 :
2415 : /*OK, this rep is fine*/
2416 : }
2417 :
2418 170 : else if (rep->segment_template || group->adaptation_set->segment_template || period->segment_template) {
2419 : assert(new_rep);
2420 13 : if (!new_rep->segment_template && !new_set->segment_template && !new_period->segment_template) {
2421 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment template as previous version\n"));
2422 0 : gf_mpd_del(new_mpd);
2423 0 : return GF_NON_COMPLIANT_BITSTREAM;
2424 : }
2425 : //if no segment timeline, adjust current idx of the group if start number changes (not needed if SegmentTimeline, otherwise, we will look for the index with the same starttime in the timeline)
2426 13 : if ((period->segment_template && period->segment_template->segment_timeline)
2427 13 : || (set->segment_template && set->segment_template->segment_timeline)
2428 5 : || (rep->segment_template && rep->segment_template->segment_timeline)
2429 : ) {
2430 : } else {
2431 : s32 sn_diff = 0;
2432 :
2433 5 : if (period->segment_template && (period->segment_template->start_number != (u32) -1) ) sn_diff = period->segment_template->start_number;
2434 5 : else if (set->segment_template && (set->segment_template->start_number != (u32) -1) ) sn_diff = set->segment_template->start_number;
2435 0 : else if (rep->segment_template && (rep->segment_template->start_number != (u32) -1) ) sn_diff = rep->segment_template->start_number;
2436 :
2437 5 : if (new_period->segment_template && (new_period->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_period->segment_template->start_number;
2438 5 : else if (new_set->segment_template && (new_set->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_set->segment_template->start_number;
2439 0 : else if (new_rep->segment_template && (new_rep->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_rep->segment_template->start_number;
2440 :
2441 5 : if (sn_diff != 0) {
2442 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] startNumber change for SegmentTemplate without SegmentTimeline - adjusting current segment index by %d\n", sn_diff));
2443 0 : group->download_segment_index += sn_diff;
2444 : }
2445 : }
2446 : /*OK, this rep is fine*/
2447 : }
2448 : else {
2449 : Bool same_rep = GF_FALSE;
2450 : /*we're using segment list*/
2451 : assert(rep->segment_list || group->adaptation_set->segment_list || period->segment_list);
2452 :
2453 : //HLS case
2454 157 : if (!new_rep) {
2455 : assert(rep->segment_list);
2456 : assert(rep->segment_list->previous_xlink_href || rep->segment_list->xlink_href);
2457 157 : hls_temp_rep = gf_mpd_representation_new();
2458 157 : GF_SAFEALLOC(hls_temp_rep->segment_list, GF_MPD_SegmentList);
2459 157 : hls_temp_rep->segment_list->segment_URLs = gf_list_new();
2460 157 : if (rep->segment_list->xlink_href)
2461 0 : hls_temp_rep->segment_list->xlink_href = gf_strdup(rep->segment_list->xlink_href);
2462 : else
2463 157 : hls_temp_rep->segment_list->xlink_href = gf_strdup(rep->segment_list->previous_xlink_href);
2464 :
2465 : new_rep = hls_temp_rep;
2466 : }
2467 157 : if (group->active_rep_index!=rep_idx)
2468 : same_rep = GF_TRUE;
2469 :
2470 : //if we have a xlink_href in segment_list, solve it for the active quality only
2471 212 : while (new_rep->segment_list->xlink_href && !same_rep) {
2472 : u32 retry=10;
2473 84 : Bool is_static = GF_FALSE;
2474 84 : u64 dur = 0;
2475 :
2476 84 : if (new_rep->segment_list->consecutive_xlink_count) {
2477 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Resolving a XLINK pointed from another XLINK (%d consecutive XLINK in segment list)\n", new_rep->segment_list->consecutive_xlink_count));
2478 : }
2479 :
2480 84 : while (retry) {
2481 84 : if (dash->is_m3u8) {
2482 84 : if (!rep_idx)
2483 : rep_idx = 0;
2484 84 : e = gf_dash_solve_m3u8_representation_xlink(group, new_rep, &is_static, &dur, rep->playback.xlink_digest);
2485 : } else {
2486 0 : e = gf_dash_solve_representation_xlink(group->dash, new_rep, rep->playback.xlink_digest);
2487 : }
2488 84 : if (e==GF_OK) break;
2489 29 : if (e==GF_EOS) {
2490 : same_rep = GF_TRUE;
2491 : e = GF_OK;
2492 : break;
2493 : }
2494 0 : if (e==GF_NON_COMPLIANT_BITSTREAM) break;
2495 0 : if (e==GF_OUT_OF_MEM) break;
2496 0 : if (group->dash->dash_state != GF_DASH_STATE_RUNNING)
2497 : break;
2498 :
2499 0 : gf_sleep(100);
2500 0 : retry --;
2501 : }
2502 :
2503 84 : if (same_rep) break;
2504 :
2505 55 : if (e==GF_OK) {
2506 55 : if (dash->is_m3u8 && is_static) {
2507 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[m3u8] MPD type changed from dynamic to static\n"));
2508 0 : group->dash->mpd->type = GF_MPD_TYPE_STATIC;
2509 0 : group->dash->mpd->minimum_update_period = 0;
2510 0 : if (group->dash->mpd->media_presentation_duration < dur)
2511 0 : group->dash->mpd->media_presentation_duration = dur;
2512 0 : if (group->period->duration < dur)
2513 0 : group->period->duration = dur;
2514 : }
2515 : }
2516 : }
2517 :
2518 157 : if (same_rep==GF_TRUE) {
2519 102 : rep->playback.not_modified = GF_TRUE;
2520 102 : nb_rep_unchanged ++;
2521 : has_reps_unchanged = GF_TRUE;
2522 102 : if (hls_temp_rep)
2523 102 : gf_mpd_representation_free(hls_temp_rep);
2524 102 : if (group->hls_next_seq_num) {
2525 90 : HLS_MIN_RELOAD_TIME(dash)
2526 : }
2527 102 : continue;
2528 : }
2529 :
2530 55 : if (!new_rep->segment_list && (!new_set || !new_set->segment_list) && (!new_period || !new_period->segment_list)) {
2531 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment list as previous version\n"));
2532 0 : gf_mpd_del(new_mpd);
2533 0 : return GF_NON_COMPLIANT_BITSTREAM;
2534 : }
2535 : /*get the segment list*/
2536 : segments = new_segments = NULL;
2537 55 : if (period->segment_list && period->segment_list->segment_URLs) segments = period->segment_list->segment_URLs;
2538 55 : if (set->segment_list && set->segment_list->segment_URLs) segments = set->segment_list->segment_URLs;
2539 55 : if (rep->segment_list && rep->segment_list->segment_URLs) segments = rep->segment_list->segment_URLs;
2540 :
2541 55 : if (new_period && new_period->segment_list && new_period->segment_list->segment_URLs) new_segments = new_period->segment_list->segment_URLs;
2542 55 : if (new_set && new_set->segment_list && new_set->segment_list->segment_URLs) new_segments = new_set->segment_list->segment_URLs;
2543 55 : if (new_rep->segment_list && new_rep->segment_list->segment_URLs) new_segments = new_rep->segment_list->segment_URLs;
2544 :
2545 : Bool skip_next_seg_url = GF_FALSE;
2546 : Bool has_ll_hls = GF_FALSE;
2547 : Bool live_edge_passed = GF_FALSE;
2548 : u32 dld_index_offset = 0;
2549 : GF_MPD_SegmentURL *first_added_chunk = NULL;
2550 55 : GF_MPD_SegmentURL *hls_last_chunk = group->llhls_edge_chunk;
2551 :
2552 55 : if (dash->is_m3u8 && group->is_low_latency) {
2553 48 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation #%d: merging segments, current live chunk %s\n", rep_idx+1, hls_last_chunk ? hls_last_chunk->media : "none"));
2554 :
2555 : //active rep, figure out the diff between the scheduled last chunk and the download index
2556 : //typically if update_mpd is called after a segment is downloaded but before next segment is evaluated for download
2557 : //there is a diff of 1 seg
2558 : //we reapply this diff when updating download_segment_index below
2559 48 : if ((group->active_rep_index==rep_idx) && hls_last_chunk) {
2560 48 : s32 pos = gf_list_find(segments, hls_last_chunk);
2561 48 : if ((pos>=0) && (pos < group->download_segment_index))
2562 11 : dld_index_offset = (s32) group->download_segment_index - pos;
2563 : }
2564 : }
2565 :
2566 : //#define DUMP_LIST
2567 :
2568 55 : if (dash->is_m3u8) {
2569 : //preprocess new segment list, starting from end and locate the most recent full segment already present
2570 : // in old list before the live edge
2571 : //we start browsing from old list since it is likely the last segments in new list are not present at all in the old
2572 55 : u32 nb_new_segs = gf_list_count(new_segments);
2573 55 : u32 nb_old_segs = gf_list_count(segments);
2574 : s32 purge_segs_until = -1;
2575 :
2576 : #ifdef DUMP_LIST
2577 : fprintf(stderr, "New list received:\n");
2578 : for (i=0; i<nb_new_segs; i++) {
2579 : GF_MPD_SegmentURL *surl = gf_list_get(new_segments, i);
2580 : fprintf(stderr, "\tsegment %s - chunk type %d\n", surl->media, surl->hls_ll_chunk_type);
2581 : }
2582 : #endif
2583 :
2584 :
2585 168 : for (j=nb_old_segs; j>0; j--) {
2586 100 : GF_MPD_SegmentURL *old_seg = gf_list_get(segments, j-1);
2587 : //ignore chunks
2588 100 : if (old_seg->hls_ll_chunk_type) continue;
2589 : //if live edge dont't trash anything after it, append and parts merge is done below
2590 79 : if (hls_last_chunk && (hls_last_chunk->hls_seq_num <= old_seg->hls_seq_num)) continue;
2591 256 : for (i=nb_new_segs; i>0; i--) {
2592 256 : GF_MPD_SegmentURL *new_seg = gf_list_get(new_segments, i-1);
2593 : //ignore chunks
2594 256 : if (new_seg->hls_ll_chunk_type) continue;
2595 :
2596 : //match, we will trash everything in new list until this point
2597 94 : if (old_seg->hls_seq_num == new_seg->hls_seq_num) {
2598 42 : purge_segs_until = (s32) i-1;
2599 42 : break;
2600 : }
2601 : }
2602 42 : if (purge_segs_until>=0) break;
2603 : }
2604 449 : while (purge_segs_until>=0) {
2605 394 : GF_MPD_SegmentURL *new_seg = gf_list_pop_front(new_segments);
2606 394 : gf_mpd_segment_url_free(new_seg);
2607 394 : purge_segs_until--;
2608 : }
2609 : }
2610 :
2611 : //browse new list - this is for both DASH or HLS
2612 325 : for (i=0; i<gf_list_count(new_segments); i++) {
2613 325 : GF_MPD_SegmentURL *new_seg = gf_list_get(new_segments, i);
2614 325 : u32 nb_segs = gf_list_count(segments);
2615 : Bool found = GF_FALSE;
2616 :
2617 : //part of a segment
2618 325 : if (new_seg->hls_ll_chunk_type) {
2619 : has_ll_hls = GF_TRUE;
2620 :
2621 : //remaining chunk of our live edge, add it (skip loop below)
2622 254 : if (skip_next_seg_url)
2623 : nb_segs = 0;
2624 : }
2625 :
2626 : //find this segment in the old list
2627 958 : for (j = 0; j<nb_segs; j++) {
2628 696 : GF_MPD_SegmentURL *seg = gf_list_get(segments, j);
2629 :
2630 : //compare only segs or parts
2631 696 : if (new_seg->hls_ll_chunk_type && !seg->hls_ll_chunk_type)
2632 435 : continue;
2633 261 : if (!new_seg->hls_ll_chunk_type && seg->hls_ll_chunk_type)
2634 29 : continue;
2635 :
2636 : //full segment
2637 232 : if (!seg->media || !new_seg->media || strcmp(seg->media, new_seg->media))
2638 133 : continue;
2639 :
2640 99 : if (seg->media_range && new_seg->media_range) {
2641 58 : if ((seg->media_range->start_range != new_seg->media_range->start_range) ||
2642 11 : (seg->media_range->end_range != new_seg->media_range->end_range)
2643 : ) {
2644 36 : continue;
2645 : }
2646 : }
2647 : //we already had the segment, do not add
2648 : found = 1;
2649 :
2650 : //this is the live edge !
2651 63 : if (seg==hls_last_chunk) {
2652 : skip_next_seg_url = GF_TRUE;
2653 : }
2654 : break;
2655 : }
2656 :
2657 : //remove all part before live edge - parts after live edge with a full seg following will be purged once merged
2658 325 : if (!found && !skip_next_seg_url && new_seg->hls_ll_chunk_type && !live_edge_passed) {
2659 : found = GF_TRUE;
2660 : }
2661 : //first full seg after our live edge, insert before the first fragment of this segment still in our list
2662 325 : if (!new_seg->hls_ll_chunk_type && skip_next_seg_url && !found) {
2663 : //starting from our current live edge, rewind and insert after the first full segment found
2664 5 : s32 pos = group->download_segment_index;
2665 33 : while (pos>0) {
2666 25 : GF_MPD_SegmentURL *prev = gf_list_get(segments, pos);
2667 25 : if (!prev) break;
2668 25 : if (!prev->hls_ll_chunk_type) {
2669 2 : gf_list_insert(segments, new_seg, pos+1);
2670 : pos = pos+1;
2671 2 : break;
2672 : }
2673 23 : pos--;
2674 : }
2675 : assert(pos>=0);
2676 5 : if (pos==0) {
2677 3 : gf_list_insert(segments, new_seg, 0);
2678 : }
2679 : //remove from new segments
2680 5 : gf_list_rem(new_segments, i);
2681 5 : i--;
2682 :
2683 5 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation #%d: Injecting segment %s before LL chunk\n", rep_idx+1, new_seg->media));
2684 : found = GF_TRUE;
2685 : //no longer at live edge point
2686 : skip_next_seg_url = GF_FALSE;
2687 : //edge is now passed, do not discard following parts
2688 : live_edge_passed = GF_TRUE;
2689 : first_added_chunk = NULL;
2690 : }
2691 :
2692 : /*this is a new segment, merge it: we remove from new list and push to old one, before doing a final swap
2693 : this ensures that indexing in the segment_list is still correct after merging*/
2694 325 : if (!found) {
2695 59 : gf_list_rem(new_segments, i);
2696 59 : i--;
2697 :
2698 59 : if (new_seg->hls_ll_chunk_type) {
2699 35 : if (!first_added_chunk) {
2700 : first_added_chunk = new_seg;
2701 : }
2702 : } else {
2703 : //we just flushed a new full seg, remove all parts
2704 24 : if (first_added_chunk) {
2705 0 : s32 pos = gf_list_find(segments, first_added_chunk);
2706 : assert(pos>=0);
2707 0 : while (pos < (s32) gf_list_count(segments)) {
2708 0 : GF_MPD_SegmentURL *surl = gf_list_pop_back(segments);
2709 : assert(surl->hls_ll_chunk_type);
2710 0 : gf_mpd_segment_url_free(surl);
2711 : }
2712 : first_added_chunk = NULL;
2713 : }
2714 : }
2715 :
2716 59 : gf_list_add(segments, new_seg);
2717 59 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation #%d: Adding new segment %s\n", rep_idx+1, new_seg->media));
2718 : }
2719 : }
2720 : /*what else should we check ?*/
2721 :
2722 : #ifdef DUMP_LIST
2723 : fprintf(stderr, "updated segment list before purge\n");
2724 : for (i=0; i<gf_list_count(segments); i++) {
2725 : GF_MPD_SegmentURL *surl = gf_list_get(segments, i);
2726 : fprintf(stderr, "\tsegment %s - chunk type %d\n", surl->media, surl->hls_ll_chunk_type);
2727 : }
2728 : #endif
2729 :
2730 55 : if (dash->is_m3u8) {
2731 : //also remove all segments older than min seq num in new rep
2732 0 : while (1) {
2733 55 : GF_MPD_SegmentURL *old_seg = gf_list_get(segments, 0);
2734 55 : if (!old_seg) break;
2735 55 : if (old_seg->hls_seq_num >= new_rep->m3u8_media_seq_min)
2736 : break;
2737 :
2738 0 : gf_list_rem(segments, 0);
2739 0 : gf_mpd_segment_url_free(old_seg);
2740 : }
2741 : } else {
2742 : //todo for MPD with rep update on xlink (no existing profile use this)
2743 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation using segment list in live: purging not implemented, patch welcome\n"));
2744 : }
2745 :
2746 : /*swap segment list content*/
2747 55 : gf_list_swap(new_segments, segments);
2748 :
2749 : /*LL-HLS, cleanup everything except part around "our" live edge and part at manifest live edge*/
2750 55 : if (has_ll_hls) {
2751 : s32 live_edge_idx = -1;
2752 : //active rep, find live edge index before purge
2753 46 : if (group->llhls_edge_chunk && (group->active_rep_index==rep_idx)) {
2754 46 : live_edge_idx = gf_list_find(new_segments, group->llhls_edge_chunk);
2755 : }
2756 46 : ls_hls_purge_segments(live_edge_idx, new_segments);
2757 :
2758 : //active rep, update download_segment_index after purge
2759 46 : if (group->llhls_edge_chunk && (group->active_rep_index==rep_idx)) {
2760 46 : live_edge_idx = gf_list_find(new_segments, group->llhls_edge_chunk);
2761 46 : if (live_edge_idx>=0)
2762 11 : group->download_segment_index = (u32) live_edge_idx + dld_index_offset;
2763 : }
2764 : }
2765 :
2766 : #ifdef DUMP_LIST
2767 : fprintf(stderr, "%d updated segment list - min/max seq num in new list %d / %d\n", gf_sys_clock(), new_rep->m3u8_media_seq_min, new_rep->m3u8_media_seq_max);
2768 :
2769 : for (i=0; i<gf_list_count(new_segments); i++) {
2770 : GF_MPD_SegmentURL *surl = gf_list_get(new_segments, i);
2771 : fprintf(stderr, "\tsegment %s - chunk type %d\n", surl->media, surl->hls_ll_chunk_type);
2772 : }
2773 : #endif
2774 :
2775 : //HLS live: if a new time is set (active group only), we just switched between qualities
2776 : //locate the segment with the same start time in the manifest, and purge previous ones
2777 : //it may happen that the manifest does still not contain the segment we are looking for, force an MPD update
2778 55 : if (group->hls_next_seq_num && (group->active_rep_index==rep_idx)) {
2779 : Bool found = GF_FALSE;
2780 40 : u32 k, count = gf_list_count(new_segments);
2781 :
2782 : //browse new segment list in reverse order, looking for our desired seq num
2783 80 : for (k=count; k>0; k--) {
2784 40 : GF_MPD_SegmentURL *segu = (GF_MPD_SegmentURL *) gf_list_get(new_segments, k-1);
2785 40 : if (segu->hls_ll_chunk_type && !segu->is_first_part)
2786 0 : continue;
2787 :
2788 40 : if (group->hls_next_seq_num == segu->hls_seq_num) {
2789 12 : group->download_segment_index = k-1;
2790 : found = GF_TRUE;
2791 12 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] HLS switching qualities on %s%s\n", segu->media, segu->hls_ll_chunk_type ? " - live edge" : ""));
2792 : break;
2793 : }
2794 : /*
2795 : "In order to play the presentation normally, the next Media Segment to load is the one with the
2796 : lowest Media Sequence Number that is greater than the Media Sequence Number of the last Media Segment loaded."
2797 :
2798 : so we store this one, but continue until we find an exact match, if any
2799 : */
2800 28 : else if (segu->hls_seq_num > group->hls_next_seq_num) {
2801 0 : group->download_segment_index = k-1;
2802 : found = GF_TRUE;
2803 : }
2804 : //seg num is lower than our requested next, abort browsing
2805 : else
2806 : break;
2807 : }
2808 : //not yet available
2809 28 : if (!found) {
2810 : //use group last modification time
2811 28 : u32 timer = gf_sys_clock() - group->last_mpd_change_time;
2812 28 : if (!group->segment_duration || (timer < group->segment_duration * 2000) ) {
2813 28 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Cannot find segment for given HLS SN %d - forcing manifest update\n", group->hls_next_seq_num));
2814 28 : HLS_MIN_RELOAD_TIME(dash)
2815 : } else {
2816 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Segment list has not been updated for more than %d ms - assuming end of period\n", timer));
2817 0 : gf_dash_mark_group_done(group);
2818 0 : group->hls_next_seq_num = 0;
2819 : }
2820 : } else {
2821 12 : group->hls_next_seq_num = 0;
2822 : }
2823 : }
2824 :
2825 : /*current representation is the active one in the group - update the number of segments*/
2826 55 : if (group->active_rep_index==rep_idx) {
2827 55 : group->nb_segments_in_rep = gf_list_count(new_segments);
2828 : }
2829 : }
2830 :
2831 68 : e = gf_dash_merge_segment_timeline(group, NULL, rep->segment_list, rep->segment_template, new_rep->segment_list, new_rep->segment_template, timeline_start_time);
2832 68 : if (e) {
2833 0 : gf_mpd_del(new_mpd);
2834 0 : return e;
2835 : }
2836 :
2837 : //move redirections in representations base URLs (we could do that on AS as well )
2838 68 : if (rep->base_URLs && new_rep->base_URLs) {
2839 : u32 k;
2840 0 : for (i=0; i<gf_list_count(new_rep->base_URLs); i++) {
2841 0 : GF_MPD_BaseURL *n_url = gf_list_get(new_rep->base_URLs, i);
2842 0 : if (!n_url->URL) continue;
2843 :
2844 0 : for (k=0; k<gf_list_count(rep->base_URLs); k++) {
2845 0 : GF_MPD_BaseURL *o_url = gf_list_get(rep->base_URLs, k);
2846 0 : if (o_url->URL && !strcmp(o_url->URL, n_url->URL)) {
2847 0 : n_url->redirection = o_url->redirection;
2848 0 : o_url->redirection = NULL;
2849 : }
2850 : }
2851 : }
2852 : }
2853 :
2854 : /*what else should we check ??*/
2855 :
2856 :
2857 68 : if (hls_temp_rep) {
2858 : //reswap segment lists: new segments contain the actual list
2859 55 : rep->segment_list->segment_URLs = new_segments;
2860 55 : new_rep->segment_list->segment_URLs = segments;
2861 : //gf_list_swap(new_segments, segments);
2862 :
2863 : //destroy temporary rep
2864 55 : gf_mpd_representation_free(hls_temp_rep);
2865 55 : group->last_mpd_change_time = gf_sys_clock();
2866 : } else {
2867 : /*switch all internal GPAC stuff*/
2868 13 : memcpy(&new_rep->playback, &rep->playback, sizeof(GF_DASH_RepresentationPlayback));
2869 13 : if (rep->playback.cached_init_segment_url) rep->playback.cached_init_segment_url = NULL;
2870 :
2871 13 : if (!new_rep->mime_type) {
2872 8 : new_rep->mime_type = rep->mime_type;
2873 8 : rep->mime_type = NULL;
2874 : }
2875 : }
2876 : }
2877 :
2878 : /*update segmentTimeline at AdaptationSet level before switching the set (old setup needed to compute current timing of each group) */
2879 97 : if (new_set) {
2880 13 : e = gf_dash_merge_segment_timeline(group, NULL, set->segment_list, set->segment_template, new_set->segment_list, new_set->segment_template, timeline_start_time);
2881 13 : if (e) {
2882 0 : gf_mpd_del(new_mpd);
2883 0 : return e;
2884 : }
2885 :
2886 13 : if (nb_rep_unchanged == gf_list_count(new_set->representations))
2887 : nb_group_unchanged++;
2888 : else
2889 13 : group->last_mpd_change_time = gf_sys_clock();
2890 : }
2891 : }
2892 : //good to go, switch pointers
2893 110 : for (group_idx=0; group_idx<gf_list_count(dash->groups) && new_period; group_idx++) {
2894 : Double seg_dur;
2895 : Bool reset_segment_count;
2896 : GF_MPD_AdaptationSet *new_as;
2897 13 : GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
2898 :
2899 : /*update group/period to new period*/
2900 13 : j = gf_list_find(group->period->adaptation_sets, group->adaptation_set);
2901 13 : new_as = gf_list_get(new_period->adaptation_sets, j);
2902 : assert(new_as);
2903 :
2904 13 : if (has_reps_unchanged) {
2905 : //swap all unchanged reps from old MPD to new MPD
2906 0 : for (j=0; j<gf_list_count(group->adaptation_set->representations); j++) {
2907 0 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, j);
2908 0 : GF_MPD_Representation *new_rep = gf_list_get(new_as->representations, j);
2909 0 : if (!rep->playback.not_modified) continue;
2910 0 : gf_list_rem(group->adaptation_set->representations, j);
2911 0 : gf_list_rem(new_as->representations, j);
2912 0 : gf_list_insert(group->adaptation_set->representations, new_rep, j);
2913 0 : gf_list_insert(new_as->representations, rep, j);
2914 0 : rep->playback.not_modified = GF_FALSE;
2915 : }
2916 : }
2917 :
2918 : /*swap group period/AS to new period/AS*/
2919 13 : group->adaptation_set = new_as;
2920 13 : group->period = new_period;
2921 :
2922 13 : j = gf_list_count(group->adaptation_set->representations);
2923 : assert(j);
2924 :
2925 : /*now that all possible SegmentXXX have been updated, purge them if needed: all segments ending before timeline_start_time
2926 : will be removed from MPD*/
2927 13 : if (timeline_start_time) {
2928 8 : u32 nb_segments_removed = gf_dash_purge_segment_timeline(group, timeline_start_time);
2929 8 : if (nb_segments_removed) {
2930 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AdaptationSet %d - removed %d segments from timeline (%d since start of the period)\n", group_idx+1, nb_segments_removed, group->nb_segments_purged));
2931 : }
2932 : }
2933 :
2934 13 : if (force_timeline_setup) {
2935 0 : group->timeline_setup = GF_FALSE;
2936 0 : group->start_number_at_last_ast = 0;
2937 0 : gf_dash_group_timeline_setup(new_mpd, group, fetch_time);
2938 : }
2939 13 : else if (new_mpd->availabilityStartTime != dash->mpd->availabilityStartTime) {
2940 : s64 diff = new_mpd->availabilityStartTime;
2941 2 : diff -= dash->mpd->availabilityStartTime;
2942 2 : if (diff < 0) diff = -diff;
2943 2 : if (diff>3000)
2944 2 : gf_dash_group_timeline_setup(new_mpd, group, fetch_time);
2945 : }
2946 :
2947 13 : group->maybe_end_of_stream = 0;
2948 : reset_segment_count = GF_FALSE;
2949 : /*compute fetchTime + minUpdatePeriod and check period end time*/
2950 13 : if (new_mpd->minimum_update_period && new_mpd->media_presentation_duration) {
2951 0 : u64 endTime = fetch_time - new_mpd->availabilityStartTime - period->start;
2952 0 : if (endTime > new_mpd->media_presentation_duration) {
2953 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Period EndTime is signaled to "LLU", less than fetch time "LLU" ! Ignoring mediaPresentationDuration\n", new_mpd->media_presentation_duration, endTime));
2954 0 : new_mpd->media_presentation_duration = 0;
2955 : reset_segment_count = GF_TRUE;
2956 : } else {
2957 0 : endTime += new_mpd->minimum_update_period;
2958 0 : if (endTime > new_mpd->media_presentation_duration) {
2959 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Period EndTime is signaled to "LLU", less than fetch time + next update "LLU" - maybe end of stream ?\n", new_mpd->availabilityStartTime, endTime));
2960 0 : group->maybe_end_of_stream = 1;
2961 : }
2962 : }
2963 : }
2964 :
2965 : /*update number of segments in active rep*/
2966 13 : gf_dash_get_segment_duration(gf_list_get(group->adaptation_set->representations, group->active_rep_index), group->adaptation_set, group->period, new_mpd, &group->nb_segments_in_rep, &seg_dur);
2967 :
2968 13 : if (reset_segment_count) {
2969 0 : u32 nb_segs_in_mpd_period = (u32) (dash->mpd->minimum_update_period / (1000*seg_dur) );
2970 0 : group->nb_segments_in_rep = group->download_segment_index + nb_segs_in_mpd_period;
2971 : }
2972 : /*check if number of segments are coherent ...*/
2973 13 : else if (!group->maybe_end_of_stream && new_mpd->minimum_update_period && new_mpd->media_presentation_duration) {
2974 0 : u32 nb_segs_in_mpd_period = (u32) (dash->mpd->minimum_update_period / (1000*seg_dur) );
2975 :
2976 0 : if (group->download_segment_index + nb_segs_in_mpd_period >= group->nb_segments_in_rep) {
2977 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Period has %d segments but %d are needed until next refresh. Maybe end of stream is near ?\n", group->nb_segments_in_rep, group->download_segment_index + nb_segs_in_mpd_period));
2978 0 : group->maybe_end_of_stream = 1;
2979 : }
2980 : }
2981 :
2982 13 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updated AdaptationSet %d - %d segments\n", group_idx+1, group->nb_segments_in_rep));
2983 :
2984 13 : if (!period->duration && new_period->duration) {
2985 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("End of period upcoming, current segment index for group #%d: %d\n", group_idx+1, group->download_segment_index));
2986 0 : if (group->download_segment_index > (s32) group->nb_segments_in_rep)
2987 0 : gf_dash_mark_group_done(group);
2988 : }
2989 :
2990 : }
2991 :
2992 99 : exit:
2993 : /*swap MPDs*/
2994 99 : if (new_mpd) {
2995 15 : if (dash->mpd) {
2996 15 : if (!new_mpd->minimum_update_period && (new_mpd->type==GF_MPD_TYPE_DYNAMIC))
2997 4 : new_mpd->minimum_update_period = dash->mpd->minimum_update_period;
2998 15 : gf_mpd_del(dash->mpd);
2999 : }
3000 15 : dash->mpd = new_mpd;
3001 : }
3002 99 : dash->last_update_time = gf_sys_clock();
3003 99 : dash->mpd_fetch_time = fetch_time;
3004 :
3005 : #ifndef GPAC_DISABLE_LOG
3006 99 : if (new_period) {
3007 15 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Manifest after update:\n"));
3008 18 : for (i=0; i<gf_list_count(new_mpd->periods); i++) {
3009 18 : GF_MPD_Period *ap = gf_list_get(new_mpd->periods, i);
3010 18 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tP#%d: start "LLU" - duration " LLU" - xlink %s\n", i+1, ap->start, ap->duration, ap->xlink_href ? ap->xlink_href : ap->origin_base_url ? ap->origin_base_url : "none"));
3011 : }
3012 : }
3013 : #endif
3014 :
3015 : return GF_OK;
3016 : }
3017 :
3018 11 : static void m3u8_setup_timeline(GF_DASH_Group *group, GF_MPD_Representation *rep)
3019 : {
3020 : u64 timeshift = 0;
3021 : u64 tsb_depth = 0;
3022 : u32 i, count;
3023 :
3024 11 : if (!group->dash->initial_time_shift_value || !rep->segment_list) return;
3025 :
3026 0 : count = gf_list_count(rep->segment_list->segment_URLs);
3027 0 : for (i=0; i<count; i++) {
3028 0 : GF_MPD_SegmentURL *s = gf_list_get(rep->segment_list->segment_URLs, i);
3029 0 : tsb_depth += s->duration;
3030 : }
3031 :
3032 0 : if (group->dash->initial_time_shift_value<=100) {
3033 : timeshift = tsb_depth;
3034 0 : timeshift *= group->dash->initial_time_shift_value;
3035 0 : timeshift /= 100;
3036 : } else {
3037 0 : timeshift = (u32) group->dash->initial_time_shift_value;
3038 0 : timeshift *= rep->segment_list->timescale;
3039 0 : timeshift /= 1000;
3040 0 : if (timeshift > tsb_depth) timeshift = tsb_depth;
3041 : }
3042 : tsb_depth = 0;
3043 0 : for (i=count; i>0; i--) {
3044 0 : GF_MPD_SegmentURL *s = gf_list_get(rep->segment_list->segment_URLs, i-1);
3045 0 : tsb_depth += s->duration;
3046 : //only check on independent parts
3047 0 : if (s->hls_ll_chunk_type == 1)
3048 0 : continue;
3049 0 : if (tsb_depth > timeshift) {
3050 0 : group->download_segment_index = i-1;
3051 : break;
3052 : }
3053 : }
3054 : }
3055 :
3056 :
3057 3501 : static GF_Err gf_dash_resolve_url(GF_MPD *mpd, GF_MPD_Representation *rep, GF_DASH_Group *group, const char *mpd_url, GF_MPD_URLResolveType resolve_type, u32 item_index, char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration, Bool *is_in_base_url, char **out_key_url, bin128 *out_key_iv, Bool *data_url_process, u32 *out_start_number)
3058 : {
3059 : GF_Err e;
3060 3501 : GF_MPD_AdaptationSet *set = group->adaptation_set;
3061 3501 : GF_MPD_Period *period = group->period;
3062 : u32 timescale;
3063 :
3064 3501 : if (!mpd_url) return GF_BAD_PARAM;
3065 :
3066 3501 : if (!strncmp(mpd_url, "gfio://", 7))
3067 9 : mpd_url = gf_file_basename(gf_fileio_translate_url(mpd_url));
3068 :
3069 3501 : if (!group->timeline_setup) {
3070 296 : gf_dash_group_timeline_setup(mpd, group, 0);
3071 : //we must wait for ROUTE clock to initialize, even if first period is static remote (we need to know when to tune)
3072 296 : if (group->dash->route_clock_state==1)
3073 : return GF_IP_NETWORK_EMPTY;
3074 :
3075 261 : if (group->dash->reinit_period_index)
3076 : return GF_IP_NETWORK_EMPTY;
3077 260 : group->timeline_setup = GF_TRUE;
3078 260 : item_index = group->download_segment_index;
3079 : }
3080 :
3081 3465 : gf_mpd_resolve_segment_duration(rep, set, period, segment_duration, ×cale, NULL, NULL);
3082 3465 : *segment_duration = (resolve_type==GF_MPD_RESOLVE_URL_MEDIA) ? (u32) ((Double) ((*segment_duration) * 1000.0) / timescale) : 0;
3083 3465 : e = gf_mpd_resolve_url(mpd, rep, set, period, mpd_url, group->current_base_url_idx, resolve_type, item_index, group->nb_segments_purged, out_url, out_range_start, out_range_end, segment_duration, is_in_base_url, out_key_url, out_key_iv, out_start_number);
3084 :
3085 :
3086 : if (e == GF_NON_COMPLIANT_BITSTREAM) {
3087 : // group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
3088 : }
3089 3465 : if (!*out_url) {
3090 : return e;
3091 : }
3092 :
3093 3435 : if (*out_url && data_url_process && !strncmp(*out_url, "data:", 5)) {
3094 : char *sep;
3095 0 : sep = strstr(*out_url, ";base64,");
3096 0 : if (sep) {
3097 : GF_Blob *blob;
3098 : u32 len;
3099 0 : sep+=8;
3100 0 : len = (u32)strlen(sep) + 1;
3101 0 : GF_SAFEALLOC(blob, GF_Blob);
3102 0 : if (!blob) return GF_OUT_OF_MEM;
3103 :
3104 0 : blob->data = (char *)gf_malloc(len);
3105 0 : blob->size = gf_base64_decode(sep, len, blob->data, len);
3106 0 : sprintf(*out_url, "gmem://%p", blob);
3107 0 : *data_url_process = GF_TRUE;
3108 : } else {
3109 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("data scheme with encoding different from base64 not supported\n"));
3110 : }
3111 : }
3112 :
3113 : return e;
3114 : }
3115 :
3116 :
3117 395 : static void gf_dash_set_group_representation(GF_DASH_Group *group, GF_MPD_Representation *rep, Bool is_next_schedule)
3118 : {
3119 : #ifndef GPAC_DISABLE_LOG
3120 : u32 width=0, height=0, samplerate=0;
3121 : GF_MPD_Fractional *framerate=NULL;
3122 : #endif
3123 : u32 k;
3124 : s32 timeshift;
3125 : GF_MPD_AdaptationSet *set;
3126 : GF_MPD_Period *period;
3127 : u32 ol_nb_segs_in_rep;
3128 395 : u32 i = gf_list_find(group->adaptation_set->representations, rep);
3129 395 : u32 prev_active_rep_index = group->active_rep_index;
3130 395 : u32 nb_cached_seg_per_rep = group->max_cached_segments / gf_dash_group_count_rep_needed(group);
3131 : assert((s32) i >= 0);
3132 :
3133 395 : if (group->llhls_edge_chunk && group->llhls_edge_chunk->hls_ll_chunk_type) {
3134 0 : group->llhls_switch_request = i;
3135 : return;
3136 : }
3137 395 : group->llhls_switch_request = -1;
3138 :
3139 : //we do not support switching in the middle of a segment
3140 395 : if (group->llhls_edge_chunk && group->llhls_edge_chunk->hls_ll_chunk_type) {
3141 : return;
3142 : }
3143 :
3144 : /* in case of dependent representations: we set max_complementary_rep_index than active_rep_index*/
3145 395 : if (group->base_rep_index_plus_one)
3146 0 : group->max_complementary_rep_index = i;
3147 : else {
3148 395 : group->active_rep_index = i;
3149 : // if (group->timeline_setup)
3150 : // group->llhls_edge_chunk = NULL;
3151 : }
3152 395 : group->active_bitrate = rep->bandwidth;
3153 395 : group->max_cached_segments = nb_cached_seg_per_rep * gf_dash_group_count_rep_needed(group);
3154 395 : ol_nb_segs_in_rep = group->nb_segments_in_rep;
3155 :
3156 395 : group->min_bandwidth_selected = GF_TRUE;
3157 379 : for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
3158 512 : GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
3159 512 : if (group->active_bitrate > arep->bandwidth) {
3160 133 : group->min_bandwidth_selected = GF_FALSE;
3161 : break;
3162 : }
3163 : }
3164 :
3165 441 : while (rep->segment_list && rep->segment_list->xlink_href) {
3166 46 : Bool is_static = GF_FALSE;
3167 46 : u64 dur = 0;
3168 : GF_Err e=GF_OK;
3169 :
3170 46 : if (rep->segment_list->consecutive_xlink_count) {
3171 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Resolving a XLINK pointed from another XLINK (%d consecutive XLINK in segment list)\n", rep->segment_list->consecutive_xlink_count));
3172 : }
3173 :
3174 46 : if (group->dash->is_m3u8) {
3175 46 : e = gf_dash_solve_m3u8_representation_xlink(group, rep, &is_static, &dur, rep->playback.xlink_digest);
3176 : } else {
3177 0 : e = gf_dash_solve_representation_xlink(group->dash, rep, rep->playback.xlink_digest);
3178 : }
3179 46 : if (is_static)
3180 31 : group->dash->mpd->type = GF_MPD_TYPE_STATIC;
3181 :
3182 : //after resolving xlink: if this represenstation is marked as disabled, we have nothing to do
3183 46 : if (rep->playback.disabled)
3184 0 : return;
3185 :
3186 46 : if (e) {
3187 0 : if (group->dash->dash_state != GF_DASH_STATE_RUNNING) {
3188 0 : group->dash->force_period_reload = 1;
3189 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Could not reslove XLINK %s of initial rep, will retry\n", (rep->segment_list && rep->segment_list->xlink_href) ? rep->segment_list->xlink_href : "", gf_error_to_string(e) ));
3190 : } else {
3191 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Could not reslove XLINK %s in time: %s - using old representation\n", (rep->segment_list && rep->segment_list->xlink_href) ? rep->segment_list->xlink_href : "", gf_error_to_string(e) ));
3192 0 : group->active_rep_index = prev_active_rep_index;
3193 : }
3194 : return;
3195 : }
3196 :
3197 : //we only know this is a static or dynamic MPD after parsing the first subplaylist
3198 : //if this is static, we need to update infos in mpd and period
3199 46 : if (group->dash->is_m3u8 && is_static) {
3200 31 : group->dash->mpd->type = GF_MPD_TYPE_STATIC;
3201 31 : group->dash->mpd->minimum_update_period = 0;
3202 31 : if (group->dash->mpd->media_presentation_duration < dur)
3203 11 : group->dash->mpd->media_presentation_duration = dur;
3204 31 : if (group->period->duration < dur)
3205 11 : group->period->duration = dur;
3206 : }
3207 : }
3208 :
3209 395 : if (group->dash->is_m3u8) {
3210 : //here we change to another representation: we need to remove all URLs from segment list and adjust the download segment index for this group
3211 63 : if (group->dash->dash_state == GF_DASH_STATE_RUNNING) {
3212 26 : u32 group_dld_index = group->download_segment_index;
3213 : u32 next_media_seq;
3214 : Bool found = GF_FALSE;
3215 26 : Bool is_dynamic = (group->dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_TRUE : GF_FALSE;
3216 : u32 nb_segs;
3217 26 : GF_MPD_Representation *prev_active_rep = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, prev_active_rep_index);
3218 : GF_MPD_SegmentURL *last_seg_url;
3219 : assert(rep->segment_list);
3220 : assert(prev_active_rep->segment_list);
3221 :
3222 26 : last_seg_url = gf_list_get(prev_active_rep->segment_list->segment_URLs, group_dld_index);
3223 26 : if (last_seg_url)
3224 6 : next_media_seq = last_seg_url->hls_seq_num;
3225 : else {
3226 20 : last_seg_url = gf_list_get(prev_active_rep->segment_list->segment_URLs, group_dld_index-1);
3227 20 : if (last_seg_url)
3228 20 : next_media_seq = last_seg_url->hls_seq_num+1;
3229 : else
3230 0 : next_media_seq = rep->m3u8_media_seq_max;
3231 : }
3232 :
3233 26 : nb_segs = gf_list_count(rep->segment_list->segment_URLs);
3234 223 : for (k=nb_segs; k>0; k--) {
3235 223 : GF_MPD_SegmentURL *seg_url = (GF_MPD_SegmentURL *) gf_list_get(rep->segment_list->segment_URLs, k-1);
3236 223 : if (seg_url->hls_ll_chunk_type) continue;
3237 :
3238 223 : if (next_media_seq == seg_url->hls_seq_num) {
3239 6 : group->download_segment_index = k-1;
3240 : found = GF_TRUE;
3241 : break;
3242 : }
3243 : /*
3244 : "In order to play the presentation normally, the next Media Segment to load is the one with the
3245 : lowest Media Sequence Number that is greater than the Media Sequence Number of the last Media Segment loaded."
3246 :
3247 : so we store this one, but continue until we find an exact match, if any
3248 : */
3249 217 : else if (next_media_seq < seg_url->hls_seq_num) {
3250 197 : group->download_segment_index = k-1;
3251 : found = GF_TRUE;
3252 : }
3253 : //segment before our current target, abort
3254 : else {
3255 : break;
3256 : }
3257 : }
3258 :
3259 20 : if (!found) {
3260 20 : if (is_dynamic) {
3261 : //we switch quality but next seg is not known, force an update NOW
3262 16 : group->dash->force_mpd_update = GF_TRUE;
3263 16 : group->hls_next_seq_num = next_media_seq;
3264 : } else {
3265 4 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] next media segment %d not found in new variant stream (min %d - max %d), aborting\n", next_media_seq, rep->m3u8_media_seq_min, rep->m3u8_media_seq_max));
3266 4 : group->done = GF_TRUE;
3267 : }
3268 : } else {
3269 6 : if (is_dynamic) {
3270 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] HLS next seg %d found in new rep, no manifest update\n", next_media_seq));
3271 : }
3272 : }
3273 : }
3274 :
3275 : //switching to a rep for which we didn't solve the init segment yet
3276 63 : if (!rep->playback.cached_init_segment_url) {
3277 : GF_Err e;
3278 46 : Bool timeline_setup = group->timeline_setup;
3279 46 : char *r_base_init_url = NULL;
3280 46 : u64 r_start = 0, r_end = 0, r_dur = 0;
3281 :
3282 46 : e = gf_dash_resolve_url(group->dash->mpd, rep, group, group->dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &r_base_init_url, &r_start, &r_end, &r_dur, NULL, &rep->playback.key_url, &rep->playback.key_IV, &rep->playback.owned_gmem, NULL);
3283 :
3284 46 : group->timeline_setup = timeline_setup;
3285 46 : if (!e && r_base_init_url) {
3286 45 : rep->playback.cached_init_segment_url = r_base_init_url;
3287 45 : rep->playback.init_start_range = r_start;
3288 45 : rep->playback.init_end_range = r_end;
3289 45 : rep->playback.init_seg_name_start = dash_strip_base_url(r_base_init_url, group->dash->base_url);
3290 1 : } else if (e) {
3291 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - discarding representation\n", gf_error_to_string(e) ));
3292 0 : rep->playback.disabled = 1;
3293 : }
3294 : }
3295 :
3296 63 : group->m3u8_start_media_seq = rep->m3u8_media_seq_min;
3297 63 : if (rep->m3u8_low_latency)
3298 22 : group->is_low_latency = GF_TRUE;
3299 63 : if (group->dash->mpd->type==GF_MPD_TYPE_STATIC)
3300 36 : group->timeline_setup = GF_TRUE;
3301 :
3302 63 : if (!group->current_downloaded_segment_duration && rep->segment_list && rep->segment_list->timescale)
3303 38 : group->current_downloaded_segment_duration = rep->segment_list->duration * 1000 / rep->segment_list->timescale;
3304 :
3305 : //setup tune point
3306 63 : if (!group->timeline_setup) {
3307 : //tune to last entry (live edge)
3308 11 : group->download_segment_index = rep->m3u8_media_seq_indep_last;
3309 11 : if (rep->m3u8_low_latency && rep->segment_list) {
3310 10 : u32 nb_removed = ls_hls_purge_segments(group->download_segment_index, rep->segment_list->segment_URLs);
3311 10 : group->download_segment_index -= nb_removed;
3312 : }
3313 : //if TSB set, roll back
3314 11 : m3u8_setup_timeline(group, rep);
3315 11 : group->timeline_setup = GF_TRUE;
3316 11 : group->first_hls_chunk = GF_TRUE;
3317 : } else {
3318 52 : if (rep->m3u8_low_latency && rep->segment_list) {
3319 12 : ls_hls_purge_segments(-1, rep->segment_list->segment_URLs);
3320 : }
3321 : }
3322 : }
3323 :
3324 395 : set = group->adaptation_set;
3325 395 : period = group->period;
3326 :
3327 : #ifndef GPAC_DISABLE_LOG
3328 :
3329 : #define GET_REP_ATTR(_a) _a = rep->_a; if (!_a) _a = set->_a;
3330 :
3331 395 : GET_REP_ATTR(width);
3332 395 : GET_REP_ATTR(height);
3333 395 : GET_REP_ATTR(samplerate);
3334 395 : GET_REP_ATTR(framerate);
3335 :
3336 395 : if (width || height) {
3337 : u32 num=25, den=1;
3338 301 : if (framerate) {
3339 237 : num = framerate->num;
3340 237 : if (framerate->den) den = framerate->den;
3341 : }
3342 :
3343 301 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d changed quality to bitrate %d kbps - Width %d Height %d FPS %d/%d (playback speed %g)\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth/1000, width, height, num, den, group->dash->speed));
3344 : }
3345 94 : else if (samplerate) {
3346 76 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d changed quality to bitrate %d kbps - sample rate %u (playback speed %g)\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth/1000, samplerate, group->dash->speed));
3347 : }
3348 : else {
3349 18 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d changed quality to bitrate %d kbps (playback speed %g)\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth/1000, group->dash->speed));
3350 : }
3351 : #endif
3352 :
3353 395 : gf_dash_get_segment_duration(rep, set, period, group->dash->mpd, &group->nb_segments_in_rep, &group->segment_duration);
3354 :
3355 : /*if broken indication in duration restore previous seg count*/
3356 395 : if (group->dash->ignore_mpd_duration)
3357 0 : group->nb_segments_in_rep = ol_nb_segs_in_rep;
3358 :
3359 395 : timeshift = (s32) (rep->segment_base ? rep->segment_base->time_shift_buffer_depth : (rep->segment_list ? rep->segment_list->time_shift_buffer_depth : (rep->segment_template ? rep->segment_template->time_shift_buffer_depth : -1) ) );
3360 191 : if (timeshift == -1) timeshift = (s32) (set->segment_base ? set->segment_base->time_shift_buffer_depth : (set->segment_list ? set->segment_list->time_shift_buffer_depth : (set->segment_template ? set->segment_template->time_shift_buffer_depth : -1) ) );
3361 395 : if (timeshift == -1) timeshift = (s32) (period->segment_base ? period->segment_base->time_shift_buffer_depth : (period->segment_list ? period->segment_list->time_shift_buffer_depth : (period->segment_template ? period->segment_template->time_shift_buffer_depth : -1) ) );
3362 :
3363 395 : if (timeshift == -1) timeshift = (s32) group->dash->mpd->time_shift_buffer_depth;
3364 395 : group->time_shift_buffer_depth = (u32) timeshift;
3365 :
3366 395 : group->dash->dash_io->on_dash_event(group->dash->dash_io, GF_DASH_EVENT_QUALITY_SWITCH, gf_list_find(group->dash->groups, group), GF_OK);
3367 : }
3368 :
3369 2 : static void gf_dash_switch_group_representation(GF_DashClient *mpd, GF_DASH_Group *group)
3370 : {
3371 : u32 i, bandwidth, min_bandwidth;
3372 : GF_MPD_Representation *rep_sel = NULL;
3373 : GF_MPD_Representation *min_rep_sel = NULL;
3374 : Bool min_bandwidth_selected = GF_FALSE;
3375 : bandwidth = 0;
3376 : min_bandwidth = (u32) -1;
3377 :
3378 2 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Checking representations between %d and %d kbps\n", group->min_bitrate/1000, group->max_bitrate/1000));
3379 :
3380 2 : if (group->force_representation_idx_plus_one) {
3381 2 : rep_sel = gf_list_get(group->adaptation_set->representations, group->force_representation_idx_plus_one - 1);
3382 2 : group->force_representation_idx_plus_one = 0;
3383 : }
3384 :
3385 2 : if (!rep_sel) {
3386 0 : for (i=0; i<gf_list_count(group->adaptation_set->representations); i++) {
3387 0 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, i);
3388 0 : if (rep->playback.disabled) continue;
3389 0 : if ((rep->bandwidth > bandwidth) && (rep->bandwidth < group->max_bitrate )) {
3390 : rep_sel = rep;
3391 : bandwidth = rep->bandwidth;
3392 : }
3393 0 : if (rep->bandwidth < min_bandwidth) {
3394 : min_rep_sel = rep;
3395 : min_bandwidth = rep->bandwidth;
3396 : }
3397 : }
3398 : }
3399 :
3400 2 : if (!rep_sel) {
3401 0 : if (!min_rep_sel) {
3402 0 : min_rep_sel = gf_list_get(group->adaptation_set->representations, 0);
3403 : }
3404 : rep_sel = min_rep_sel;
3405 : min_bandwidth_selected = 1;
3406 : }
3407 : assert(rep_sel);
3408 2 : i = gf_list_find(group->adaptation_set->representations, rep_sel);
3409 :
3410 : assert((s32) i >= 0);
3411 :
3412 2 : group->force_switch_bandwidth = 0;
3413 2 : group->max_bitrate = 0;
3414 2 : group->min_bitrate = (u32) -1;
3415 :
3416 2 : if (i != group->active_rep_index) {
3417 2 : if (min_bandwidth_selected) {
3418 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] No representation found with bandwidth below %d kbps - using representation @ %d kbps\n", group->max_bitrate/1000, rep_sel->bandwidth/1000));
3419 : }
3420 2 : gf_dash_set_group_representation(group, rep_sel, GF_FALSE);
3421 : }
3422 2 : }
3423 :
3424 : /* Estimate the maximum speed that we can play, using our statistic. If it is below the max_playout_rate in MPD, return max_playout_rate*/
3425 2409 : static Double gf_dash_get_max_available_speed(GF_DashClient *dash, GF_DASH_Group *group, GF_MPD_Representation *rep)
3426 : {
3427 : Double max_available_speed = 0;
3428 : Double max_dl_speed, max_decoding_speed;
3429 : u32 framerate;
3430 : u32 bytes_per_sec;
3431 :
3432 2409 : if (!group->irap_max_dec_time && !group->avg_dec_time) {
3433 : return 0;
3434 : }
3435 64 : bytes_per_sec = group->backup_Bps;
3436 64 : max_dl_speed = 8.0*bytes_per_sec / rep->bandwidth;
3437 :
3438 : //if framerate is not in MPD, suppose that it is 25 fps
3439 : framerate = 25;
3440 64 : if (rep->framerate) {
3441 64 : framerate = rep->framerate->num;
3442 64 : if (rep->framerate->den) {
3443 64 : framerate /= rep->framerate->den;
3444 : }
3445 : }
3446 :
3447 64 : if (group->decode_only_rap)
3448 0 : max_decoding_speed = group->irap_max_dec_time ? 1000000.0 / group->irap_max_dec_time : 0;
3449 : else
3450 64 : max_decoding_speed = group->avg_dec_time ? 1000000.0 / (group->max_dec_time + group->avg_dec_time*(framerate - 1)) : 0;
3451 64 : max_available_speed = max_decoding_speed > max_dl_speed ? max_dl_speed : max_decoding_speed;
3452 64 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation %s max playout rate: in MPD %f - calculated by stat: %f\n", rep->id, rep->max_playout_rate, max_available_speed));
3453 :
3454 : return max_available_speed;
3455 : }
3456 :
3457 2981 : static void dash_store_stats(GF_DashClient *dash, GF_DASH_Group *group, u32 bytes_per_sec, u32 file_size, Bool is_broadcast, u32 cur_dep_idx_plus_one, u64 us_since_start)
3458 : {
3459 : #ifndef GPAC_DISABLE_LOG
3460 : const char *url=NULL, *full_url=NULL;
3461 : #endif
3462 : GF_MPD_Representation *rep;
3463 :
3464 2981 : if (!group->nb_cached_segments)
3465 : return;
3466 :
3467 : #ifndef GPAC_DISABLE_LOG
3468 2981 : if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
3469 48 : if (cur_dep_idx_plus_one) {
3470 : u32 i=0;
3471 49 : while (i<group->nb_cached_segments) {
3472 48 : full_url = group->cached[i].url;
3473 48 : if (group->cached[i].representation_index==cur_dep_idx_plus_one-1)
3474 : break;
3475 1 : i++;
3476 : }
3477 : } else {
3478 0 : full_url = group->cached[group->nb_cached_segments-1].url;
3479 : }
3480 48 : url = strrchr(full_url, '/');
3481 48 : if (!url) url = strrchr(full_url, '\\');
3482 48 : if (url) url+=1;
3483 : else url = full_url;
3484 : }
3485 : #endif
3486 :
3487 :
3488 2981 : if (!bytes_per_sec && group->local_files) {
3489 : bytes_per_sec = (u32) -1;
3490 : bytes_per_sec /= 8;
3491 : }
3492 :
3493 2981 : group->total_size = file_size;
3494 : //in broadcast mode, just store the rate
3495 2981 : if (is_broadcast) group->bytes_per_sec = bytes_per_sec;
3496 : //otherwise store the min rate we got (to deal with complementary representations)
3497 2981 : else if (!group->bytes_per_sec || group->bytes_per_sec > bytes_per_sec)
3498 2794 : group->bytes_per_sec = bytes_per_sec;
3499 :
3500 2981 : group->last_segment_time = gf_sys_clock();
3501 2981 : group->nb_segments_since_switch ++;
3502 :
3503 2981 : group->prev_segment_ok = GF_TRUE;
3504 2981 : if (group->time_at_first_failure) {
3505 : #ifndef GPAC_DISABLE_LOG
3506 0 : if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
3507 0 : if (group->current_base_url_idx) {
3508 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 by switching baseURL\n", url));
3509 : } else {
3510 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 - was our download schedule %d too early ?\n", url, group->time_at_last_request - group->time_at_first_failure));
3511 : }
3512 : }
3513 : #endif
3514 0 : group->time_at_first_failure = 0;
3515 : }
3516 2981 : group->nb_consecutive_segments_lost = 0;
3517 2981 : group->current_base_url_idx = 0;
3518 :
3519 :
3520 2981 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
3521 2981 : rep->playback.broadcast_flag = is_broadcast;
3522 :
3523 : /*
3524 : we "merged" the following segments with the current one in a single open byte-range: the downloaded segment duration indicated
3525 : is the one of the PART, NOT of the segment
3526 : We need to compute (for ABR logic) the segment duration but we likely don't have the information since we issued this merge
3527 : on the live edge and have not performed a forced manifest update since then, so last PARTs are not known
3528 :
3529 : We therefore assume a default duration of the average indicated, and check (in case we are lucky) if we have the full segment in the
3530 : list (i.e. before live edge)
3531 : if not, we SHOULD force an update just to fetch this duration before calling the ABR, but if the ABR decides to change right now
3532 : we would fetch this manifest for nothing, and we are already fetching a LOT of manifests in HLS...
3533 : So for the time being, we assume the target duration is a good approximation
3534 : */
3535 2981 : if (dash->llhls_single_range && group->llhls_last_was_merged) {
3536 : u64 duration;
3537 : assert(rep->segment_list);
3538 : assert(rep->segment_list->timescale);
3539 1 : duration = rep->segment_list->duration;
3540 1 : if (group->llhls_edge_chunk) {
3541 1 : s32 pos = group->download_segment_index-1;
3542 3 : while (pos>=0) {
3543 2 : GF_MPD_SegmentURL *surl = gf_list_get(rep->segment_list->segment_URLs, pos);
3544 2 : if (surl->hls_seq_num < group->llhls_edge_chunk->hls_seq_num) break;
3545 1 : if (!surl->hls_ll_chunk_type) {
3546 0 : duration = surl->duration;
3547 0 : break;
3548 : }
3549 1 : pos--;
3550 : }
3551 1 : group->current_downloaded_segment_duration = duration * 1000 / rep->segment_list->timescale;
3552 : }
3553 : }
3554 :
3555 : #ifndef GPAC_DISABLE_LOG
3556 2981 : if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
3557 : u32 i, buffer_ms = 0;
3558 : Double bitrate, time_sec;
3559 : //force a call go query buffer
3560 48 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, gf_list_find(dash->groups, group), GF_OK);
3561 48 : buffer_ms = group->buffer_occupancy_ms;
3562 96 : for (i=0; i < group->nb_cached_segments; i++) {
3563 48 : buffer_ms += group->cached[i].duration;
3564 : }
3565 :
3566 : bitrate=0;
3567 : time_sec=0;
3568 48 : if (group->current_downloaded_segment_duration) {
3569 48 : bitrate = 8*group->total_size;
3570 48 : bitrate /= group->current_downloaded_segment_duration;
3571 : }
3572 :
3573 48 : if (!us_since_start) {
3574 0 : if (bytes_per_sec) {
3575 0 : time_sec = group->total_size;
3576 0 : time_sec /= bytes_per_sec;
3577 : }
3578 : } else {
3579 48 : time_sec = (Double) us_since_start;
3580 48 : time_sec /= 1000000;
3581 : }
3582 :
3583 48 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d got %s stats: %d bytes in %.03g sec at %d kbps - dur %g sec - bitrate: %d (avg %d) kbps - buffer %d ms\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), url, group->total_size, time_sec, 8*bytes_per_sec/1000, group->current_downloaded_segment_duration/1000.0, (u32) bitrate, rep->bandwidth/1000, buffer_ms));
3584 : }
3585 : #endif
3586 : }
3587 :
3588 0 : static s32 dash_do_rate_monitor_default(GF_DashClient *dash, GF_DASH_Group *group, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start, u32 buffer_dur_ms, u32 current_seg_dur)
3589 : {
3590 : Bool default_switch_mode;
3591 : u32 set_idx, time_until_end;
3592 :
3593 : //do not abort if we are downloading faster than current rate
3594 0 : if (bits_per_sec > group->active_bitrate) {
3595 : return -1;
3596 : }
3597 :
3598 0 : set_idx = gf_list_find(group->period->adaptation_sets, group->adaptation_set)+1;
3599 :
3600 0 : if (group->min_bandwidth_selected) {
3601 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%d at rate %d kbps but media bitrate is"
3602 : " %d kbps - no lower bitrate available ...\n", set_idx, bits_per_sec/1000, group->active_bitrate/1000 ));
3603 : return -1;
3604 : }
3605 :
3606 : //we start checking after 100ms
3607 0 : if (us_since_start < 100000) {
3608 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%d at rate %d kbps (media bitrate %d kbps) but 100ms only ellapsed, waiting\n", set_idx, bits_per_sec/1000, group->active_bitrate/1000 ));
3609 : return -1;
3610 : }
3611 :
3612 0 : time_until_end = (u32) (8000*(total_bytes-bytes_done) / bits_per_sec);
3613 :
3614 0 : if (bits_per_sec<group->min_bitrate)
3615 0 : group->min_bitrate = bits_per_sec;
3616 0 : if (bits_per_sec>group->max_bitrate)
3617 0 : group->max_bitrate = bits_per_sec;
3618 :
3619 : //we have enough cache data to go until end of this download, perform rate switching at next segment
3620 0 : if (time_until_end < buffer_dur_ms) {
3621 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%ds at rate %d kbps (media bitrate %d kbps) - %d ms until end of download and %d ms in buffer, not aborting\n", set_idx, bits_per_sec/1000, group->active_bitrate/1000, time_until_end, buffer_dur_ms));
3622 : return -1;
3623 : }
3624 :
3625 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Downloading from set #%d at rate %d kbps but media bitrate is %d kbps - %d ms until end of download but %d ms in buffer - aborting segment download\n", set_idx, bits_per_sec/1000, group->active_bitrate/1000, buffer_dur_ms));
3626 :
3627 : //in live we just abort current download and go to next. In onDemand, we may want to rebuffer
3628 0 : default_switch_mode = (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_FALSE : GF_TRUE;
3629 :
3630 0 : us_since_start /= 1000;
3631 : //if we have time to download from another rep ?
3632 0 : if (current_seg_dur <= us_since_start) {
3633 : //don't force bandwidth switch (it's too late anyway, consider we lost the segment), let the rate adaptation decide
3634 0 : group->force_switch_bandwidth = default_switch_mode;
3635 :
3636 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Download time longer than segment duration - trying to resync on next segment\n"));
3637 : } else {
3638 : u32 target_rate;
3639 : //compute min bitrate needed to fetch the segment in another rep, with the time remaining
3640 0 : Double ratio = (Double) ((u32)current_seg_dur - us_since_start);
3641 0 : ratio /= (u32) current_seg_dur;
3642 :
3643 0 : target_rate = (u32) (bits_per_sec * ratio);
3644 :
3645 0 : if (target_rate < group->min_representation_bitrate) {
3646 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Download rate lower than min available rate ...\n"));
3647 0 : target_rate = group->min_representation_bitrate;
3648 : //don't force bandwidth switch, we won't have time to redownload the segment.
3649 0 : group->force_switch_bandwidth = default_switch_mode;
3650 : } else {
3651 0 : group->force_switch_bandwidth = GF_TRUE;
3652 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Attempting to re-download at target rate %d\n", target_rate));
3653 : }
3654 : //cap max bitrate for next rate adaptation pass
3655 0 : group->max_bitrate = target_rate;
3656 : }
3657 : return -2;
3658 : }
3659 :
3660 2116 : static s32 dash_do_rate_adaptation_legacy_rate(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
3661 : u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
3662 : GF_MPD_Representation *rep, Bool go_up_bitrate)
3663 : {
3664 : u32 k;
3665 : Bool do_switch;
3666 : GF_MPD_Representation *new_rep;
3667 2116 : s32 new_index = group->active_rep_index;
3668 :
3669 : /* records the number of representations between the current one and the next chosen one */
3670 : u32 nb_inter_rep = 0;
3671 :
3672 : /* We assume that there will be a change in quality */
3673 : do_switch = GF_TRUE;
3674 :
3675 : /*find best bandwidth that fits our bitrate and playing speed*/
3676 : new_rep = NULL;
3677 :
3678 : /* for each playable representation, if we still need to switch, evaluate it */
3679 4939 : for (k = 0; k<gf_list_count(group->adaptation_set->representations) && do_switch; k++) {
3680 2823 : GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
3681 2823 : if (!arep->playback.prev_max_available_speed) {
3682 233 : arep->playback.prev_max_available_speed = 1.0;
3683 : }
3684 2823 : if (arep->playback.disabled) {
3685 0 : continue;
3686 : }
3687 2823 : if (arep->playback.prev_max_available_speed && (speed > arep->playback.prev_max_available_speed)) {
3688 0 : continue;
3689 : }
3690 : /* Only try to switch to a representation, if download rate is greater than its bitrate */
3691 2823 : if (dl_rate >= arep->bandwidth) {
3692 :
3693 : /* First check if we are adapting to CPU */
3694 2798 : if (!dash->disable_speed_adaptation && force_lower_complexity) {
3695 :
3696 : /*try to switch to highest quality below the current one*/
3697 0 : if ((arep->quality_ranking < rep->quality_ranking) ||
3698 0 : (arep->width < rep->width) || (arep->height < rep->height)) {
3699 :
3700 : /* If we hadn't found a new representation, use it
3701 : otherwise use it only if current representation is better*/
3702 0 : if (!new_rep) {
3703 : new_rep = arep;
3704 0 : new_index = k;
3705 : }
3706 0 : else if ((arep->quality_ranking > new_rep->quality_ranking) ||
3707 0 : (arep->width > new_rep->width) || (arep->height > new_rep->height)) {
3708 : new_rep = arep;
3709 0 : new_index = k;
3710 : }
3711 : }
3712 0 : rep->playback.prev_max_available_speed = max_available_speed;
3713 0 : go_up_bitrate = GF_FALSE;
3714 : }
3715 : else {
3716 : /* if speed adaptation is not used or used, but the new representation can be played at the right speed */
3717 :
3718 2798 : if (!new_rep) {
3719 : new_rep = arep;
3720 2113 : new_index = k;
3721 : }
3722 685 : else if (go_up_bitrate) {
3723 :
3724 : /* agressive switching is configured in the GPAC configuration */
3725 635 : if (dash->agressive_switching) {
3726 : /*be agressive, try to switch to highest bitrate below available download rate*/
3727 0 : if (arep->bandwidth > new_rep->bandwidth) {
3728 0 : if (new_rep->bandwidth > rep->bandwidth) {
3729 0 : nb_inter_rep++;
3730 : }
3731 : new_rep = arep;
3732 0 : new_index = k;
3733 : }
3734 0 : else if (arep->bandwidth > rep->bandwidth) {
3735 0 : nb_inter_rep++;
3736 : }
3737 : }
3738 : else {
3739 : /*don't be agressive, try to switch to lowest bitrate above our current rep*/
3740 635 : if (new_rep->bandwidth <= rep->bandwidth) {
3741 : new_rep = arep;
3742 625 : new_index = k;
3743 : }
3744 10 : else if ((arep->bandwidth < new_rep->bandwidth) && (arep->bandwidth > rep->bandwidth)) {
3745 : new_rep = arep;
3746 0 : new_index = k;
3747 : }
3748 : }
3749 : }
3750 : else {
3751 : /* go_up_bitrate is GF_FALSE */
3752 : /*try to switch to highest bitrate below available download rate*/
3753 50 : if (arep->bandwidth > new_rep->bandwidth) {
3754 : new_rep = arep;
3755 50 : new_index = k;
3756 : }
3757 : }
3758 : }
3759 : }
3760 : }
3761 :
3762 2116 : if (!new_rep || (new_rep == rep)) {
3763 2097 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d no better match for requested bandwidth %d - not switching (AS bitrate %d)!\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), dl_rate, rep->bandwidth));
3764 : do_switch = GF_FALSE;
3765 : }
3766 :
3767 : if (do_switch) {
3768 : //if we're switching to the next upper bitrate (no intermediate bitrates), do not immediately switch
3769 : //but for a given number of segments - this avoids fluctuation in the quality
3770 19 : if (go_up_bitrate && !nb_inter_rep) {
3771 11 : new_rep->playback.probe_switch_count++;
3772 11 : if (new_rep->playback.probe_switch_count > dash->probe_times_before_switch) {
3773 4 : new_rep->playback.probe_switch_count = 0;
3774 : } else {
3775 7 : new_index = group->active_rep_index;
3776 : }
3777 : }
3778 : }
3779 2116 : return new_index;
3780 : }
3781 :
3782 2248 : static s32 dash_do_rate_adaptation_legacy_buffer(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
3783 : u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
3784 : GF_MPD_Representation *rep, Bool go_up_bitrate)
3785 : {
3786 : Bool do_switch;
3787 2248 : s32 new_index = group->active_rep_index;
3788 :
3789 : /* We assume that there will be a change in quality
3790 : and then set it to no change or increase if this is not the case */
3791 : do_switch = GF_TRUE;
3792 :
3793 : /* if the current representation bitrate is smaller than the measured bandwidth,
3794 : tentatively start to increase bitrate */
3795 2248 : if (rep->bandwidth < dl_rate) {
3796 : go_up_bitrate = GF_TRUE;
3797 : }
3798 :
3799 : /* clamp download bitrate to the lowest representation rate, to allow choosing it */
3800 2248 : if (dl_rate < group->min_representation_bitrate) {
3801 : dl_rate = group->min_representation_bitrate;
3802 : }
3803 :
3804 : //we have buffered output
3805 2248 : if (group->buffer_max_ms) {
3806 : u32 buf_high_threshold, buf_low_threshold;
3807 : s32 occ;
3808 :
3809 808 : if (group->current_downloaded_segment_duration && (group->buffer_max_ms > group->current_downloaded_segment_duration)) {
3810 100 : buf_high_threshold = group->buffer_max_ms - (u32)group->current_downloaded_segment_duration;
3811 : }
3812 : else {
3813 708 : buf_high_threshold = 2 * group->buffer_max_ms / 3;
3814 : }
3815 808 : buf_low_threshold = (group->current_downloaded_segment_duration && (group->buffer_min_ms>10)) ? group->buffer_min_ms : (u32)group->current_downloaded_segment_duration;
3816 808 : if (buf_low_threshold > group->buffer_max_ms) buf_low_threshold = 1 * group->buffer_max_ms / 3;
3817 :
3818 : //compute how much we managed to refill (current state minus previous state)
3819 808 : occ = (s32)group->buffer_occupancy_ms;
3820 808 : occ -= (s32)group->buffer_occupancy_at_last_seg;
3821 : //if above max buffer force occ>0 since a segment may still be pending and not dispatched (buffer regulation)
3822 808 : if (group->buffer_occupancy_ms>group->buffer_max_ms) occ = 1;
3823 :
3824 : //switch down if current buffer falls below min threshold
3825 808 : if ((s32)group->buffer_occupancy_ms < (s32)buf_low_threshold) {
3826 13 : if (!group->buffer_occupancy_ms) {
3827 : dl_rate = group->min_representation_bitrate;
3828 : }
3829 : else {
3830 11 : dl_rate = (rep->bandwidth > 10) ? rep->bandwidth - 10 : 1;
3831 : }
3832 : go_up_bitrate = GF_FALSE;
3833 13 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d bitrate %d bps buffer max %d current %d refill since last %d - running low, switching down, target rate %d\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ, dl_rate));
3834 : }
3835 : //switch up if above max threshold and buffer refill is fast enough
3836 795 : else if ((occ>0) && (group->buffer_occupancy_ms > buf_high_threshold)) {
3837 649 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d bitrate %d bps buffer max %d current %d refill since last %d - running high, will try to switch up, target rate %d\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ, dl_rate));
3838 : go_up_bitrate = GF_TRUE;
3839 : }
3840 : //don't do anything in the middle range of the buffer or if refill not fast enough
3841 : else {
3842 : do_switch = GF_FALSE;
3843 146 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d bitrate %d bps buffer max %d current %d refill since last %d - steady\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ));
3844 : }
3845 : }
3846 :
3847 : /* Unless the switching has been turned off (e.g. middle buffer range),
3848 : we apply rate-based adaptation */
3849 2248 : if (do_switch) {
3850 2102 : new_index = dash_do_rate_adaptation_legacy_rate(dash, group, base_group, dl_rate, speed, max_available_speed, force_lower_complexity, rep, go_up_bitrate);
3851 : }
3852 :
3853 2248 : return new_index;
3854 : }
3855 :
3856 : // returns the bitrate and index of the representation having the minimum bitrate above the given rate
3857 9 : static u32 get_min_rate_above(GF_List *representations, double rate, s32 *index) {
3858 : u32 k;
3859 : u32 min_rate = GF_INT_MAX;
3860 : GF_MPD_Representation *rep;
3861 :
3862 9 : u32 nb_reps = gf_list_count(representations);
3863 24 : for (k = 0; k < nb_reps; k++) {
3864 24 : rep = gf_list_get(representations, k);
3865 24 : if ((rep->bandwidth < min_rate) && (rep->bandwidth > rate)) {
3866 : min_rate = rep->bandwidth;
3867 9 : if (index) {
3868 0 : *index = k;
3869 : }
3870 : return min_rate; // representations are sorted by bandwidth
3871 : }
3872 : }
3873 : return min_rate;
3874 : }
3875 :
3876 : // returns the bitrate and index of the representation having the maximum bitrate below the given rate
3877 19 : static u32 get_max_rate_below(GF_List *representations, double rate, s32 *index) {
3878 : s32 k;
3879 : u32 max_rate = 0;
3880 : GF_MPD_Representation *rep;
3881 :
3882 19 : u32 nb_reps = gf_list_count(representations);
3883 44 : for (k = (s32) nb_reps-1; k >=0 ; k--) {
3884 44 : rep = gf_list_get(representations, k);
3885 44 : if ((rep->bandwidth > max_rate) && (rep->bandwidth < rate)) {
3886 : max_rate = rep->bandwidth;
3887 19 : if (index) {
3888 5 : *index = k;
3889 : }
3890 : return max_rate; // representations are sorted by bandwidth
3891 : }
3892 : }
3893 : return max_rate;
3894 : }
3895 :
3896 : /**
3897 : Adaptation Algorithm as described in
3898 : T.-Y. Huang et al. 2014. A buffer-based approach to rate adaptation: evidence from a large video streaming service.
3899 : In Proceedings of the 2014 ACM conference on SIGCOMM (SIGCOMM '14).
3900 : */
3901 41 : static s32 dash_do_rate_adaptation_bba0(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
3902 : u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
3903 : GF_MPD_Representation *rep, Bool go_up_bitrate)
3904 : {
3905 : u32 rate_plus;
3906 : u32 rate_minus;
3907 41 : u32 rate_prev = group->active_bitrate;
3908 : u32 rate_max;
3909 : u32 rate_min;
3910 : s32 new_index;
3911 : u32 r; // reservoir
3912 : u32 cu; // cushion
3913 41 : u32 buf_now = group->buffer_occupancy_ms;
3914 41 : u32 buf_max = group->buffer_max_ms;
3915 : double f_buf_now;
3916 :
3917 : /* We don't use the segment duration as advertised in the MPD because it may not be there due to segment timeline*/
3918 41 : u32 segment_duration_ms = (u32)group->current_downloaded_segment_duration;
3919 :
3920 41 : rate_min = ((GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, 0))->bandwidth;
3921 41 : rate_max = ((GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, gf_list_count(group->adaptation_set->representations) - 1))->bandwidth;
3922 :
3923 41 : if (!buf_max) buf_max = 3*segment_duration_ms;
3924 :
3925 : /* buffer level higher than max buffer, keep high quality*/
3926 41 : if (group->buffer_occupancy_ms > buf_max) {
3927 0 : return gf_list_count(group->adaptation_set->representations) - 1;
3928 : }
3929 : /* we cannot run bba if segments are longer than the max buffer*/
3930 41 : if (buf_max < segment_duration_ms) {
3931 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] BBA-0: max buffer %d shorter than segment duration %d, cannot adapt - will use current quality\n", buf_max, group->buffer_occupancy_ms));
3932 0 : return group->active_rep_index;
3933 : }
3934 : /* if the current buffer cannot hold an entire new segment, we indicate that we don't want to download it now
3935 : NOTE: This is not described in the paper
3936 : */
3937 41 : if (group->buffer_occupancy_ms + segment_duration_ms > buf_max) {
3938 22 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] BBA-0: not enough space to download new segment: %d\n", group->buffer_occupancy_ms));
3939 : return -1;
3940 : }
3941 :
3942 19 : if (rate_prev == rate_max) {
3943 : rate_plus = rate_max;
3944 : } else {
3945 9 : rate_plus = get_min_rate_above(group->adaptation_set->representations, rate_prev, NULL);
3946 : }
3947 :
3948 19 : if (rate_prev == rate_min) {
3949 : rate_minus = rate_min;
3950 : } else {
3951 14 : rate_minus = get_max_rate_below(group->adaptation_set->representations, rate_prev, NULL);
3952 : }
3953 :
3954 : /*
3955 : * the size of the reservoir is 37.5% of the buffer size, but at least = 1 chunk duration)
3956 : * the size of the upper reservoir is 10% of the buffer size
3957 : * the size of cushion is between 37.5% and 90% of the buffer size
3958 : * the rate map is piece-wise
3959 : */
3960 19 : if (buf_max <= segment_duration_ms) {
3961 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] BBA-0: cannot initialize BBA-0 given the buffer size (%d) and segment duration (%d)\n", group->buffer_max_ms, group->segment_duration*1000));
3962 : return -1;
3963 : }
3964 19 : r = (u32)(37.5*buf_max / 100);
3965 19 : if (r < segment_duration_ms) {
3966 : r = segment_duration_ms;
3967 : }
3968 19 : cu = (u32)((90-37.5)*buf_max / 100);
3969 :
3970 19 : if (buf_now <= r) {
3971 4 : f_buf_now = rate_min;
3972 : }
3973 15 : else if (buf_now >= (cu + r)) {
3974 1 : f_buf_now = rate_max;
3975 : }
3976 : else {
3977 14 : f_buf_now = rate_min + (rate_max - rate_min)*((buf_now - r) * 1.0 / cu);
3978 : }
3979 :
3980 19 : if (f_buf_now == rate_max) {
3981 : // rate_next = rate_max;
3982 1 : new_index = gf_list_count(group->adaptation_set->representations) - 1;
3983 : }
3984 18 : else if (f_buf_now == rate_min) {
3985 : // rate_next = rate_min;
3986 4 : new_index = 0;
3987 : }
3988 14 : else if (f_buf_now >= rate_plus) {
3989 : // rate_next = max of Ri st. Ri < f_buf_now
3990 2 : new_index = 0;
3991 2 : get_max_rate_below(group->adaptation_set->representations, f_buf_now, &new_index);
3992 : }
3993 12 : else if (f_buf_now <= rate_minus) {
3994 : // rate_next = min of Ri st. Ri > f_buf_now
3995 0 : new_index = gf_list_count(group->adaptation_set->representations) - 1;
3996 0 : get_min_rate_above(group->adaptation_set->representations, f_buf_now, &new_index);
3997 : }
3998 : else {
3999 : // no change
4000 12 : new_index = group->active_rep_index;
4001 : }
4002 :
4003 19 : if (new_index != -1) {
4004 : #ifndef GPAC_DISABLE_LOG
4005 19 : GF_MPD_Representation *result = gf_list_get(group->adaptation_set->representations, (u32)new_index);
4006 : #endif
4007 : // increment the segment number for debug purposes
4008 19 : group->current_index++;
4009 19 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] BBA-0: buffer %d ms, segment number %d, new quality %d with rate %d\n", group->buffer_occupancy_ms, group->current_index, new_index, result->bandwidth));
4010 : }
4011 19 : return new_index;
4012 : }
4013 :
4014 : /* returns the index of the representation which maximises BOLA utility function
4015 : based on the relative log-based utility of each representation compared to the one with the lowest bitrate
4016 : NOTE: V can represent V (in BOLA BASIC) or V_D (in other modes of BOLA) */
4017 63 : static s32 bola_find_max_utility_index(GF_List *representations, Double V, Double gamma, Double p, Double Q) {
4018 : u32 k;
4019 : Double max_utility = GF_MIN_DOUBLE;
4020 63 : u32 nb_reps = gf_list_count(representations);
4021 : s32 new_index = -1;
4022 :
4023 269 : for (k = 0; k < nb_reps; k++) {
4024 206 : GF_MPD_Representation *rep = gf_list_get(representations, k);
4025 206 : Double utility = (V * rep->playback.bola_v + V*gamma*p - Q) / (rep->bandwidth*p);
4026 206 : if (utility >= max_utility) {
4027 : max_utility = utility;
4028 164 : new_index = k;
4029 : }
4030 : }
4031 63 : return new_index;
4032 : }
4033 :
4034 : /**
4035 : Adaptation Algorithm as described in
4036 : K. Spiteri et al. 2016. BOLA: Near-Optimal Bitrate Adaptation for Online Videos
4037 : Arxiv.org
4038 : */
4039 63 : static s32 dash_do_rate_adaptation_bola(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
4040 : u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
4041 : GF_MPD_Representation *rep, Bool go_up_bitrate)
4042 : {
4043 : s32 new_index = -1;
4044 : u32 k;
4045 63 : Double p = group->current_downloaded_segment_duration / 1000.0; // segment duration
4046 63 : Double gamma = (double)5/(double)p;
4047 63 : Double Qmax = group->buffer_max_ms / 1000.0 / p; // max nb of segments in the buffer
4048 63 : Double Q = group->buffer_occupancy_ms / 1000.0 / p; // current buffer occupancy in number of segments
4049 :
4050 : GF_MPD_Representation *min_rep;
4051 : GF_MPD_Representation *max_rep;
4052 : u32 nb_reps;
4053 :
4054 63 : if (dash->mpd->type != GF_MPD_TYPE_STATIC) {
4055 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] BOLA: Cannot be used for live MPD\n"));
4056 : return -1;
4057 : }
4058 :
4059 63 : nb_reps = gf_list_count(group->adaptation_set->representations);
4060 63 : min_rep = gf_list_get(group->adaptation_set->representations, 0);
4061 63 : max_rep = gf_list_get(group->adaptation_set->representations, nb_reps - 1);
4062 :
4063 : // Computing the log-based utility of each segment (recomputing each time for period changes)
4064 269 : for (k = 0; k < nb_reps; k++) {
4065 206 : GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
4066 206 : a_rep->playback.bola_v = log(((Double)a_rep->bandwidth) / min_rep->bandwidth);
4067 : }
4068 :
4069 63 : if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_BASIC) {
4070 : /* BOLA Basic is the variant of BOLA that assumes infinite duration streams (no wind-up/down, no rate use, no oscillation control)
4071 : It simply consists in finding the maximum utility */
4072 : // NOTE in BOLA, representation indices decrease when the quality increases [1 = best quality]
4073 18 : Double V = (Qmax - 1) / (gamma * p + max_rep->playback.bola_v);
4074 18 : new_index = bola_find_max_utility_index(group->adaptation_set->representations, V, gamma, p, Q);
4075 : }
4076 45 : else if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_FINITE ||
4077 15 : dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_O ||
4078 : dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U) {
4079 : /* BOLA FINITE is the same as BOLA Basic with the wind-up and down phases */
4080 : /* BOLA O and U add extra steps to BOLA FINITE */
4081 : Double t_bgn; //play time from begin
4082 : Double t_end; //play time to the end
4083 : Double t;
4084 : Double t_prime;
4085 : Double Q_Dmax;
4086 : Double V_D;
4087 45 : Double N = dash->mpd->media_presentation_duration / p;
4088 :
4089 45 : t_bgn = p*group->current_index;
4090 45 : t_end = (N - group->current_index)*p;
4091 45 : t = MIN(t_bgn, t_end);
4092 45 : t_prime = MAX(t / 2, 3 * p);
4093 45 : Q_Dmax = MIN(Qmax, t_prime / p);
4094 45 : V_D = (Q_Dmax - 1) / (gamma * p + max_rep->playback.bola_v);
4095 :
4096 45 : new_index = bola_find_max_utility_index(group->adaptation_set->representations, V_D, gamma, p, Q);
4097 :
4098 45 : if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U || dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_O) {
4099 : //Bola U algorithm
4100 30 : if ((new_index != -1) && ((u32)new_index > group->active_rep_index)) {
4101 3 : u32 r = group->bytes_per_sec*8;
4102 :
4103 : // index_prime the min m such that (Sm[m]/p)<= max(bandwidth_previous,S_M/p))
4104 : // NOTE in BOLA, representation indices decrease when the quality increases [1 = best quality]
4105 3 : u32 m_prime = 0;
4106 3 : get_max_rate_below(group->adaptation_set->representations, MAX(r, min_rep->bandwidth), &m_prime);
4107 3 : if (m_prime >= (u32)new_index) {
4108 1 : m_prime = new_index;
4109 : }
4110 2 : else if (m_prime < group->active_rep_index) {
4111 0 : m_prime = group->active_rep_index;
4112 : }
4113 : else {
4114 2 : if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U) {
4115 1 : m_prime++;
4116 : }
4117 : else { //GF_DASH_ALGO_BOLA_O
4118 : #if 0
4119 : GF_MPD_Representation *rep_m_prime, *rep_m_prime_plus_one;
4120 : Double Sm_prime, Sm_prime_plus_one, f_m_prime, f_m_prime_1, bola_o_pause;
4121 :
4122 : assert(m_prime >= 0 && m_prime < nb_reps - 2);
4123 : rep_m_prime = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, m_prime);
4124 : rep_m_prime_plus_one = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, m_prime+1);
4125 : Sm_prime = rep_m_prime->bandwidth*p;
4126 : Sm_prime_plus_one = rep_m_prime_plus_one->bandwidth*p;
4127 : f_m_prime = V_D*(rep_m_prime->playback.bola_v + gamma*p) / Sm_prime;
4128 : f_m_prime_1 = V_D*(rep_m_prime_plus_one->playback.bola_v + gamma*p) / Sm_prime_plus_one;
4129 : // TODO wait for bola_o_pause before making the download
4130 : bola_o_pause = Q - (f_m_prime - f_m_prime_1) / (1 / Sm_prime - 1 / Sm_prime_plus_one);
4131 : #endif
4132 : }
4133 : }
4134 3 : new_index = m_prime;
4135 : }
4136 : }
4137 : // TODO trigger pause for max(p*(Q-Q_Dmax+1), 0)
4138 : }
4139 :
4140 63 : if (new_index != -1) {
4141 : #ifndef GPAC_DISABLE_LOG
4142 63 : GF_MPD_Representation *result = gf_list_get(group->adaptation_set->representations, (u32)new_index);
4143 : #endif
4144 : // increment the segment number for debug purposes
4145 63 : group->current_index++;
4146 63 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] BOLA: buffer %d ms, segment number %d, new quality %d with rate %d\n", group->buffer_occupancy_ms, group->current_index, new_index, result->bandwidth));
4147 : }
4148 : return new_index;
4149 : }
4150 :
4151 : /* This function is called each time a new segment has been downloaded */
4152 2728 : static void dash_do_rate_adaptation(GF_DashClient *dash, GF_DASH_Group *group)
4153 : {
4154 : Double speed;
4155 : Double max_available_speed;
4156 : u32 dl_rate;
4157 : u32 k;
4158 : s32 new_index, old_index;
4159 : GF_DASH_Group *base_group;
4160 : GF_MPD_Representation *rep;
4161 : Bool force_lower_complexity;
4162 :
4163 : /* Don't do adaptation if configured switching to happen systematically (debug) */
4164 2728 : if (dash->auto_switch_count) {
4165 : return;
4166 : }
4167 : /* Don't do adaptation if GPAC config disabled switching */
4168 2627 : if (group->dash->disable_switching) {
4169 : return;
4170 : }
4171 :
4172 : /* The bytes_per_sec field is set each time a segment is downloaded,
4173 : (this may need to be adjusted in the future to accomodate algorithms
4174 : that smooth download rate over several segments)
4175 : if set to 0, this means that no segment was downloaded since the last call
4176 : because this AdaptationSet is not selected
4177 : So no rate adaptation should be done*/
4178 2409 : if (!group->bytes_per_sec) {
4179 : return;
4180 : }
4181 :
4182 : /* Find the AdaptationSet on which this AdaptationSet depends, if any
4183 : (e.g. used for specific coding schemes: scalable streams, tiled streams, ...)*/
4184 : base_group = group;
4185 3849 : while (base_group->depend_on_group) {
4186 : base_group = base_group->depend_on_group;
4187 : }
4188 :
4189 : /* adjust the download rate according to the playback speed
4190 : All adaptation algorithms should use this value */
4191 2409 : speed = dash->speed;
4192 2409 : if (speed<0) speed = -speed;
4193 2409 : dl_rate = (u32) (8 * (u64) group->bytes_per_sec / speed);
4194 2409 : if ((s32) dl_rate < 0)
4195 : dl_rate = GF_INT_MAX;
4196 :
4197 : /* Get the active representation in the AdaptationSet */
4198 2409 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
4199 :
4200 : /*check whether we can play with this speed (i.e. achieve target frame rate);
4201 : if not force, let algorithm know that they should switch to a lower resolution*/
4202 2409 : max_available_speed = gf_dash_get_max_available_speed(dash, base_group, rep);
4203 2409 : if (!dash->disable_speed_adaptation && !rep->playback.waiting_codec_reset) {
4204 0 : if (max_available_speed && (0.9 * speed > max_available_speed)) {
4205 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Forcing a lower complexity to achieve desired playback speed\n"));
4206 : force_lower_complexity = GF_TRUE;
4207 : } else {
4208 : force_lower_complexity = GF_FALSE;
4209 : }
4210 : } else {
4211 : force_lower_complexity = GF_FALSE;
4212 : }
4213 :
4214 : /*query codec and buffer statistics for buffer-based algorithms */
4215 2409 : group->buffer_max_ms = 0;
4216 2409 : group->buffer_occupancy_ms = 0;
4217 2409 : group->codec_reset = 0;
4218 : /* the DASH Client asks the player for its buffer level
4219 : (uses a function pointer to avoid depenencies on the player code, to reuse the DASH client in different situations)*/
4220 2409 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, gf_list_find(group->dash->groups, group), GF_OK);
4221 :
4222 : /* If the playback for the current representation was waiting for a codec reset and it happened,
4223 : indicate that this representation does not need a reset anymore */
4224 2409 : if (rep->playback.waiting_codec_reset && group->codec_reset) {
4225 0 : rep->playback.waiting_codec_reset = GF_FALSE;
4226 : }
4227 :
4228 2409 : old_index = group->active_rep_index;
4229 : //scalable case, force the rate algo to consider the active rep is the max rep
4230 2409 : if (group->base_rep_index_plus_one) {
4231 0 : group->active_rep_index = group->max_complementary_rep_index;
4232 : }
4233 2409 : if (group->dash->route_clock_state) {
4234 0 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
4235 0 : if (rep->playback.broadcast_flag && (dl_rate < rep->bandwidth)) {
4236 0 : dl_rate = rep->bandwidth+1;
4237 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d representation %d segment sent over broadcast, forcing bandwidth to %d\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), group->active_rep_index, dl_rate));
4238 : }
4239 : }
4240 :
4241 : /* Call a specific adaptation algorithm (see GPAC configuration)
4242 : Each algorithm should:
4243 : - return the new_index value to the desired quality
4244 :
4245 : It can use:
4246 : - the information about each available representation (group->adaptation_set->representations, e.g. bandwidth required for that representation)
4247 : - the information of the current representation (rep)
4248 : - the download_rate dl_rate (computed on the previously downloaded segment, and adjusted to the playback speed),
4249 : - the buffer levels:
4250 : - current: group->buffer_occupancy_ms,
4251 : - previous: group->buffer_occupancy_at_last_seg
4252 : - max: group->buffer_max_ms,
4253 : - the playback speed,
4254 : - the maximum achievable speed at the current resolution,
4255 : - the indicator that the current representation is too demanding CPU-wise (force_lower_complexity)
4256 :
4257 : Private algorithm information should be stored in the dash object if global to all AdaptationSets,
4258 : or in the group if local to an AdaptationSet.
4259 :
4260 : TODO: document how to access other possible parameters (e.g. segment sizes if available, ...)
4261 : */
4262 2409 : new_index = group->active_rep_index;
4263 2409 : if (dash->rate_adaptation_algo) {
4264 2409 : new_index = dash->rate_adaptation_algo(dash, group, base_group,
4265 : dl_rate, speed, max_available_speed, force_lower_complexity,
4266 : rep, GF_FALSE);
4267 : }
4268 :
4269 2409 : if (new_index==-1) {
4270 22 : group->active_rep_index = old_index;
4271 22 : group->rate_adaptation_postponed = GF_TRUE;
4272 22 : return;
4273 : }
4274 2387 : group->rate_adaptation_postponed = GF_FALSE;
4275 2387 : if (new_index < 0) {
4276 0 : if (new_index == -2) {
4277 0 : group->disabled = GF_TRUE;
4278 : }
4279 0 : group->active_rep_index = old_index;
4280 0 : return;
4281 : }
4282 2387 : if (new_index != group->active_rep_index) {
4283 34 : GF_MPD_Representation *new_rep = gf_list_get(group->adaptation_set->representations, (u32)new_index);
4284 34 : group->disabled = GF_FALSE;
4285 34 : if (!new_rep) {
4286 0 : group->active_rep_index = old_index;
4287 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Cannot find new representation index %d, using previous one\n", new_index));
4288 : return;
4289 : }
4290 :
4291 34 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d switching after playing %d segments, from rep %d to rep %d\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set),
4292 : group->nb_segments_since_switch, group->active_rep_index, new_index));
4293 34 : group->nb_segments_since_switch = 0;
4294 :
4295 34 : if (force_lower_complexity) {
4296 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Requesting codec reset\n"));
4297 0 : new_rep->playback.waiting_codec_reset = GF_TRUE;
4298 : }
4299 : /* request downloads for the new representation */
4300 34 : gf_dash_set_group_representation(group, new_rep, GF_FALSE);
4301 :
4302 : /* Reset smoothing of switches
4303 : (note: should really apply only to algorithms using the switch_probe_count (smoothing the aggressiveness of the change)
4304 : for now: only GF_DASH_ALGO_GPAC_LEGACY_RATE and GF_DASH_ALGO_GPAC_LEGACY_BUFFER */
4305 166 : for (k = 0; k < gf_list_count(group->adaptation_set->representations); k++) {
4306 132 : GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
4307 132 : if (new_rep == arep) continue;
4308 98 : arep->playback.probe_switch_count = 0;
4309 : }
4310 :
4311 : } else {
4312 2353 : group->active_rep_index = old_index;
4313 2353 : if (force_lower_complexity) {
4314 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Forced to lower quality/rate because of playback speed %f higher than max speed possible %f, but no other quality available: cannot switch down\n", speed, max_available_speed));
4315 : /*FIXME: should do something here*/
4316 : }
4317 : }
4318 :
4319 : /* Remembering the buffer level for the processing of the next segment */
4320 2387 : group->buffer_occupancy_at_last_seg = group->buffer_occupancy_ms;
4321 : }
4322 :
4323 262 : static char *gf_dash_get_fileio_url(const char *base_url, char *res_url)
4324 : {
4325 : const char *new_res;
4326 : GF_FileIO *gfio;
4327 262 : if (!base_url)
4328 : return NULL;
4329 262 : if (strncmp(base_url, "gfio://", 7))
4330 : return res_url;
4331 :
4332 1 : gfio = gf_fileio_from_url(base_url);
4333 :
4334 1 : new_res = gf_fileio_factory(gfio, res_url);
4335 1 : if (!new_res) return res_url;
4336 1 : gf_free(res_url);
4337 1 : return gf_strdup(new_res);
4338 : }
4339 :
4340 298 : static GF_Err gf_dash_download_init_segment(GF_DashClient *dash, GF_DASH_Group *group)
4341 : {
4342 : GF_Err e;
4343 : char *base_init_url;
4344 : GF_MPD_Representation *rep;
4345 : u64 start_range, end_range;
4346 298 : Bool data_url_processed = GF_FALSE;
4347 : /* This variable is 0 if there is a initURL, the index of first segment downloaded otherwise */
4348 : u32 nb_segment_read = 0;
4349 : char *base_url=NULL;
4350 : char *base_url_orig=NULL;
4351 298 : char *key_url=NULL;
4352 : bin128 key_iv;
4353 298 : u32 start_number = 0;
4354 :
4355 298 : if (!dash || !group)
4356 : return GF_BAD_PARAM;
4357 :
4358 : assert(group->adaptation_set && group->adaptation_set->representations);
4359 298 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
4360 298 : if (!rep) {
4361 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to find any representation, aborting.\n"));
4362 : return GF_IO_ERR;
4363 : }
4364 298 : start_range = end_range = 0;
4365 298 : base_url = dash->base_url;
4366 298 : if (group->period->origin_base_url) base_url = group->period->origin_base_url;
4367 :
4368 : base_url_orig = base_url;
4369 298 : if (base_url && !strncmp(base_url, "gfio://", 7)) {
4370 1 : GF_FileIO *gfio = gf_fileio_from_url(base_url);
4371 1 : base_url = (char *) gf_file_basename(gf_fileio_resource_url(gfio));
4372 : }
4373 :
4374 298 : e = gf_dash_resolve_url(dash->mpd, rep, group, base_url, GF_MPD_RESOLVE_URL_INIT, 0, &base_init_url, &start_range, &end_range, &group->current_downloaded_segment_duration, NULL, &key_url, &key_iv, &data_url_processed, NULL);
4375 298 : if (e) {
4376 36 : if (e != GF_IP_NETWORK_EMPTY) {
4377 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve initialization URL: %s\n", gf_error_to_string(e) ));
4378 : }
4379 : return e;
4380 : }
4381 :
4382 262 : if (!base_init_url && rep->dependency_id) {
4383 : return GF_OK;
4384 : }
4385 :
4386 : /*no error and no init segment, go for media segment - this is needed for TS so that the set of media streams can be
4387 : declared to the player */
4388 262 : if (!base_init_url) {
4389 6 : e = gf_dash_resolve_url(dash->mpd, rep, group, dash->base_url, GF_MPD_RESOLVE_URL_MEDIA, group->download_segment_index, &base_init_url, &start_range, &end_range, &group->current_downloaded_segment_duration, NULL, &key_url, &key_iv, NULL, &start_number);
4390 6 : if (e) {
4391 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve media URL: %s\n", gf_error_to_string(e) ));
4392 : return e;
4393 : }
4394 : nb_segment_read = 1;
4395 : }
4396 :
4397 : base_url = base_url_orig;
4398 262 : base_init_url = gf_dash_get_fileio_url(base_url, base_init_url);
4399 :
4400 262 : if (!strstr(base_init_url, "://") || !strnicmp(base_init_url, "file://", 7) || !strnicmp(base_init_url, "gmem://", 7)
4401 70 : || !strnicmp(base_init_url, "views://", 8) || !strnicmp(base_init_url, "mosaic://", 9)
4402 70 : || !strnicmp(base_init_url, "isobmff://", 10) || !strnicmp(base_init_url, "gfio://", 7)
4403 : ) {
4404 : //if file-based, check if file exists, if not switch base URL
4405 198 : if ( strnicmp(base_init_url, "gmem://", 7) && strnicmp(base_init_url, "gfio://", 7)) {
4406 197 : if (! gf_file_exists(base_init_url) ) {
4407 5 : if (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) ){
4408 0 : group->current_base_url_idx++;
4409 0 : gf_free(base_init_url);
4410 0 : return gf_dash_download_init_segment(dash, group);
4411 : }
4412 : }
4413 : }
4414 : //we don't reset the baseURL index until we are done fetching all init segments
4415 :
4416 : assert(!group->nb_cached_segments);
4417 : //transfer mem
4418 198 : group->cached[0].url = base_init_url;
4419 198 : group->cached[0].representation_index = group->active_rep_index;
4420 198 : group->prev_active_rep_index = group->active_rep_index;
4421 198 : if (key_url) {
4422 0 : group->cached[0].key_url = key_url;
4423 0 : memcpy(group->cached[0].key_IV, key_iv, sizeof(bin128));
4424 : }
4425 198 : group->cached[0].seg_number = start_number + group->download_segment_index;
4426 :
4427 198 : group->nb_cached_segments = 1;
4428 : /*do not erase local files*/
4429 198 : group->local_files = group->was_segment_base ? 0 : 1;
4430 :
4431 198 : group->download_segment_index += nb_segment_read;
4432 198 : if (group->bitstream_switching) {
4433 40 : group->bs_switching_init_segment_url = gf_strdup(base_init_url);
4434 40 : group->bs_switching_init_segment_url_start_range = start_range;
4435 40 : group->bs_switching_init_segment_url_end_range = end_range;
4436 40 : group->bs_switching_init_segment_url_name_start = dash_strip_base_url(group->bs_switching_init_segment_url, base_url);
4437 40 : if (data_url_processed) {
4438 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("URL with data scheme not handled for Bistream Switching Segments, probable memory leak"));
4439 : }
4440 : } else {
4441 158 : if (rep->playback.cached_init_segment_url) gf_free(rep->playback.cached_init_segment_url);
4442 158 : rep->playback.cached_init_segment_url = gf_strdup(base_init_url);
4443 158 : rep->playback.owned_gmem = data_url_processed;
4444 158 : rep->playback.init_start_range = start_range;
4445 158 : rep->playback.init_end_range = end_range;
4446 158 : rep->playback.init_seg_name_start = dash_strip_base_url(rep->playback.cached_init_segment_url, base_url);
4447 158 : if (key_url) {
4448 0 : rep->playback.key_url = gf_strdup(key_url);
4449 0 : memcpy(rep->playback.key_IV, key_iv, sizeof(bin128) );
4450 : }
4451 : }
4452 :
4453 :
4454 : /*finally download all init segments if any*/
4455 198 : if (!group->bitstream_switching) {
4456 : u32 k;
4457 183 : for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
4458 183 : char *a_base_init_url = NULL;
4459 183 : u64 a_start = 0, a_end = 0, a_dur = 0;
4460 183 : GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
4461 341 : if (a_rep==rep) continue;
4462 25 : if (a_rep->playback.disabled) continue;
4463 :
4464 25 : e = gf_dash_resolve_url(dash->mpd, a_rep, group, dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &a_base_init_url, &a_start, &a_end, &a_dur, NULL, &a_rep->playback.key_url, &a_rep->playback.key_IV, &a_rep->playback.owned_gmem, NULL);
4465 25 : if (!e && a_base_init_url) {
4466 21 : if (a_rep->playback.cached_init_segment_url) gf_free(a_rep->playback.cached_init_segment_url);
4467 21 : a_rep->playback.cached_init_segment_url = a_base_init_url;
4468 21 : a_rep->playback.init_start_range = a_start;
4469 21 : a_rep->playback.init_end_range = a_end;
4470 21 : a_rep->playback.init_seg_name_start = dash_strip_base_url(a_base_init_url, base_url);
4471 4 : } else if (e) {
4472 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - discarding representation\n", gf_error_to_string(e) ));
4473 0 : a_rep->playback.disabled = 1;
4474 : }
4475 : }
4476 : }
4477 198 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] First segment is %s \n", base_init_url));
4478 : //do NOT free base_init_url, it is now in group->cached[0].url
4479 198 : group->current_base_url_idx=0;
4480 198 : return GF_OK;
4481 : }
4482 :
4483 64 : group->max_bitrate = 0;
4484 64 : group->min_bitrate = (u32)-1;
4485 :
4486 :
4487 64 : if (dash->route_clock_state && !group->period->origin_base_url) {
4488 8 : GF_DASHFileIOSession sess = NULL;
4489 : /*check the init segment has been received*/
4490 8 : e = gf_dash_download_resource(dash, &sess, base_init_url, start_range, end_range, 1, NULL);
4491 8 : dash->dash_io->del(dash->dash_io, sess);
4492 :
4493 8 : if (e!=GF_OK) {
4494 2 : gf_free(base_init_url);
4495 2 : return e;
4496 : }
4497 : }
4498 :
4499 : assert(!group->nb_cached_segments);
4500 62 : group->cached[0].url = base_init_url;
4501 62 : group->cached[0].representation_index = group->active_rep_index;
4502 62 : group->cached[0].duration = (u32) group->current_downloaded_segment_duration;
4503 :
4504 62 : if (group->bitstream_switching) {
4505 31 : group->bs_switching_init_segment_url = gf_strdup(base_init_url);
4506 31 : group->bs_switching_init_segment_url_name_start = dash_strip_base_url(group->bs_switching_init_segment_url, base_url);
4507 :
4508 31 : group->bs_switching_init_segment_url_start_range = start_range;
4509 31 : group->bs_switching_init_segment_url_end_range = end_range;
4510 31 : if (data_url_processed) {
4511 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("URL with data scheme not handled for Bistream Switching Segments, probable memory leak"));
4512 : }
4513 : } else {
4514 31 : if (rep->playback.cached_init_segment_url) gf_free(rep->playback.cached_init_segment_url);
4515 31 : rep->playback.cached_init_segment_url = gf_strdup(base_init_url);
4516 31 : rep->playback.init_start_range = start_range;
4517 31 : rep->playback.init_end_range = end_range;
4518 31 : rep->playback.owned_gmem = data_url_processed;
4519 31 : rep->playback.init_seg_name_start = dash_strip_base_url(rep->playback.cached_init_segment_url, base_url);
4520 : }
4521 62 : group->nb_cached_segments = 1;
4522 62 : group->download_segment_index += nb_segment_read;
4523 :
4524 : /*download all init segments if any*/
4525 62 : if (!group->bitstream_switching) {
4526 : u32 k;
4527 57 : for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
4528 57 : char *a_base_init_url = NULL;
4529 : u64 a_start, a_end, a_dur;
4530 57 : GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
4531 88 : if (a_rep==rep) continue;
4532 26 : if (a_rep->playback.disabled) continue;
4533 :
4534 26 : e = gf_dash_resolve_url(dash->mpd, a_rep, group, dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &a_base_init_url, &a_start, &a_end, &a_dur, NULL, &a_rep->playback.key_url, &a_rep->playback.key_IV, &a_rep->playback.owned_gmem, NULL);
4535 26 : if (!e && a_base_init_url) {
4536 15 : if (a_rep->playback.cached_init_segment_url) gf_free(a_rep->playback.cached_init_segment_url);
4537 15 : a_rep->playback.cached_init_segment_url = a_base_init_url;
4538 15 : a_rep->playback.init_start_range = a_start;
4539 15 : a_rep->playback.init_end_range = a_end;
4540 15 : a_rep->playback.init_seg_name_start = dash_strip_base_url(a_rep->playback.cached_init_segment_url, base_url);
4541 11 : } else if (e) {
4542 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - disabling representation\n", gf_error_to_string(e) ));
4543 0 : a_rep->playback.disabled = 1;
4544 : }
4545 : }
4546 : }
4547 : return GF_OK;
4548 : }
4549 :
4550 120 : static void gf_dash_skip_disabled_representation(GF_DASH_Group *group, GF_MPD_Representation *rep, Bool for_autoswitch)
4551 : {
4552 : s32 rep_idx, orig_idx;
4553 : u32 bandwidth = 0xFFFFFFFF;
4554 :
4555 120 : rep_idx = orig_idx = gf_list_find(group->adaptation_set->representations, rep);
4556 : while (1) {
4557 120 : rep_idx++;
4558 120 : if (rep_idx==gf_list_count(group->adaptation_set->representations)) rep_idx = 0;
4559 : //none other than current one
4560 120 : if (orig_idx==rep_idx) return;
4561 :
4562 91 : rep = gf_list_get(group->adaptation_set->representations, rep_idx);
4563 91 : if (rep->playback.disabled) continue;
4564 :
4565 : if (rep->bandwidth<=bandwidth) break;
4566 : assert(for_autoswitch);
4567 : //go to next rep
4568 : }
4569 : assert(rep && !rep->playback.disabled);
4570 91 : gf_dash_set_group_representation(group, rep, GF_FALSE);
4571 : }
4572 :
4573 :
4574 3287 : static void gf_dash_group_reset_cache_entry(segment_cache_entry *cached)
4575 : {
4576 3287 : gf_free(cached->url);
4577 3287 : if (cached->key_url) gf_free(cached->key_url);
4578 : memset(cached, 0, sizeof(segment_cache_entry));
4579 3287 : }
4580 :
4581 284 : static void gf_dash_group_reset(GF_DashClient *dash, GF_DASH_Group *group)
4582 : {
4583 441 : while (group->nb_cached_segments) {
4584 157 : group->nb_cached_segments--;
4585 157 : gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
4586 : }
4587 284 : group->llhls_edge_chunk = NULL;
4588 :
4589 284 : group->timeline_setup = GF_FALSE;
4590 284 : }
4591 :
4592 244 : static void gf_dash_reset_groups(GF_DashClient *dash)
4593 : {
4594 : /*send playback destroy event*/
4595 244 : if (dash->dash_io) dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_DESTROY_PLAYBACK, -1, GF_OK);
4596 :
4597 506 : while (gf_list_count(dash->groups)) {
4598 262 : GF_DASH_Group *group = gf_list_last(dash->groups);
4599 262 : gf_list_rem_last(dash->groups);
4600 :
4601 262 : gf_dash_group_reset(dash, group);
4602 :
4603 262 : gf_list_del(group->groups_depending_on);
4604 262 : gf_free(group->cached);
4605 262 : if (group->service_mime)
4606 2 : gf_free(group->service_mime);
4607 :
4608 262 : if (group->bs_switching_init_segment_url)
4609 71 : gf_free(group->bs_switching_init_segment_url);
4610 :
4611 262 : gf_free(group);
4612 : }
4613 244 : gf_list_del(dash->groups);
4614 244 : dash->groups = NULL;
4615 :
4616 495 : while (gf_list_count(dash->SRDs)) {
4617 7 : struct _dash_srd_desc *srd = gf_list_last(dash->SRDs);
4618 7 : gf_list_rem_last(dash->SRDs);
4619 7 : gf_free(srd);
4620 : }
4621 244 : gf_list_del(dash->SRDs);
4622 244 : dash->SRDs = NULL;
4623 244 : }
4624 :
4625 : #ifndef GPAC_DISABLE_LOG
4626 126 : static u32 gf_dash_get_start_number(GF_DASH_Group *group, GF_MPD_Representation *rep)
4627 : {
4628 126 : if (rep->segment_list && rep->segment_list->start_number) return rep->segment_list->start_number;
4629 126 : if (group->adaptation_set->segment_list && group->adaptation_set->segment_list->start_number) return group->adaptation_set->segment_list->start_number;
4630 126 : if (group->period->segment_list && group->period->segment_list->start_number) return group->period->segment_list->start_number;
4631 :
4632 126 : if (rep->segment_template && rep->segment_template->start_number) return rep->segment_template->start_number;
4633 104 : if (group->adaptation_set->segment_template && group->adaptation_set->segment_template->start_number) return group->adaptation_set->segment_template->start_number;
4634 0 : if (group->period->segment_template && group->period->segment_template->start_number) return group->period->segment_template->start_number;
4635 :
4636 : return 0;
4637 : }
4638 : #endif
4639 :
4640 63 : static GF_MPD_Representation *gf_dash_find_rep(GF_DashClient *dash, const char *dependency_id, GF_DASH_Group **rep_group)
4641 : {
4642 : u32 i, j, nb_groups, nb_reps;
4643 : GF_MPD_Representation *rep;
4644 :
4645 63 : if (rep_group) *rep_group = NULL;
4646 :
4647 63 : if (!dependency_id) return NULL;
4648 :
4649 63 : nb_groups = gf_list_count(dash->groups);
4650 0 : for (i=0; i<nb_groups; i++) {
4651 63 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
4652 63 : nb_reps = gf_list_count(group->adaptation_set->representations);
4653 0 : for (j=0; j<nb_reps; j++) {
4654 63 : rep = gf_list_get(group->adaptation_set->representations, j);
4655 63 : if (rep->id && !strcmp(rep->id, dependency_id)) {
4656 63 : if (rep_group) *rep_group = group;
4657 : return rep;
4658 : }
4659 : }
4660 : }
4661 : return NULL;
4662 : }
4663 :
4664 : static
4665 262 : s32 gf_dash_group_get_dependency_group(GF_DashClient *dash, u32 idx)
4666 : {
4667 : GF_MPD_Representation *rep;
4668 262 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
4669 262 : if (!group) return idx;
4670 :
4671 262 : rep = gf_list_get(group->adaptation_set->representations, 0);
4672 :
4673 587 : while (rep && rep->dependency_id) {
4674 63 : char *sep = strchr(rep->dependency_id, ' ');
4675 63 : if (sep) sep[0] = 0;
4676 63 : rep = gf_dash_find_rep(dash, rep->dependency_id, &group);
4677 63 : if (sep) sep[0] = ' ';
4678 : }
4679 262 : return gf_list_find(dash->groups, group);
4680 : }
4681 :
4682 : GF_EXPORT
4683 528 : s32 gf_dash_group_has_dependent_group(GF_DashClient *dash, u32 idx)
4684 : {
4685 528 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
4686 528 : if (!group) return GF_FALSE;
4687 528 : return group->depend_on_group ? gf_list_find(dash->groups, group->depend_on_group) : -1;
4688 : }
4689 :
4690 : GF_EXPORT
4691 197 : u32 gf_dash_group_get_num_groups_depending_on(GF_DashClient *dash, u32 idx)
4692 : {
4693 197 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
4694 197 : if (!group) return 0;
4695 197 : return group->groups_depending_on ? gf_list_count(group->groups_depending_on) : 0;
4696 : }
4697 :
4698 : GF_EXPORT
4699 0 : s32 gf_dash_get_dependent_group_index(GF_DashClient *dash, u32 idx, u32 group_depending_on_dep_idx)
4700 : {
4701 : GF_DASH_Group *group_depending_on;
4702 0 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
4703 0 : if (!group || !group->groups_depending_on) return -1;
4704 0 : group_depending_on = gf_list_get(group->groups_depending_on, group_depending_on_dep_idx);
4705 0 : if (!group_depending_on) return -1;
4706 0 : return gf_list_find(dash->groups, group_depending_on);
4707 : }
4708 :
4709 : /* create groups (implementation of adaptations set) */
4710 123 : GF_Err gf_dash_setup_groups(GF_DashClient *dash)
4711 : {
4712 : GF_Err e;
4713 : u32 i, j, count, nb_dependent_rep;
4714 : GF_MPD_Period *period;
4715 :
4716 123 : if (!dash->groups) {
4717 123 : dash->groups = gf_list_new();
4718 123 : if (!dash->groups) return GF_OUT_OF_MEM;
4719 : }
4720 :
4721 123 : period = gf_list_get(dash->mpd->periods, dash->active_period_index);
4722 123 : if (!period) return GF_BAD_PARAM;
4723 :
4724 123 : count = gf_list_count(period->adaptation_sets);
4725 385 : for (i=0; i<count; i++) {
4726 : Double seg_dur;
4727 : GF_DASH_Group *group;
4728 : Bool found = GF_FALSE;
4729 : Bool has_dependent_representations = GF_FALSE;
4730 262 : GF_MPD_AdaptationSet *set = gf_list_get(period->adaptation_sets, i);
4731 669 : for (j=0; j<gf_list_count(dash->groups); j++) {
4732 407 : group = gf_list_get(dash->groups, j);
4733 407 : if (group->adaptation_set==set) {
4734 : found = 1;
4735 : break;
4736 : }
4737 : }
4738 :
4739 262 : if (found) continue;
4740 :
4741 262 : if (! gf_list_count(set->representations)) {
4742 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Empty adaptation set found (ID %d) - ignoring\n", set->id));
4743 0 : continue;
4744 : }
4745 :
4746 :
4747 262 : GF_SAFEALLOC(group, GF_DASH_Group);
4748 262 : if (!group) return GF_OUT_OF_MEM;
4749 262 : group->dash = dash;
4750 262 : group->adaptation_set = set;
4751 262 : group->period = period;
4752 262 : group->bitstream_switching = (set->bitstream_switching || period->bitstream_switching) ? GF_TRUE : GF_FALSE;
4753 :
4754 : seg_dur = 0;
4755 : nb_dependent_rep = 0;
4756 655 : for (j=0; j<gf_list_count(set->representations); j++) {
4757 : Double dur;
4758 : u32 nb_seg, k;
4759 : Bool cp_supported;
4760 393 : GF_MPD_Representation *rep = gf_list_get(set->representations, j);
4761 393 : gf_dash_get_segment_duration(rep, set, period, dash->mpd, &nb_seg, &dur);
4762 393 : if (dur>seg_dur) seg_dur = dur;
4763 :
4764 393 : if (group->bitstream_switching && (set->segment_base || period->segment_base || rep->segment_base) ) {
4765 0 : group->bitstream_switching = GF_FALSE;
4766 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] bitstreamSwitching set for onDemand content - ignoring flag\n"));
4767 : }
4768 :
4769 393 : if (dash->dash_io->dash_codec_supported) {
4770 0 : Bool res = dash->dash_io->dash_codec_supported(dash->dash_io, rep->codecs, rep->width, rep->height, (rep->scan_type==GF_MPD_SCANTYPE_INTERLACED) ? 1 : 0, rep->framerate ? rep->framerate->num : 0, rep->framerate ? rep->framerate->den : 0, rep->samplerate);
4771 0 : if (!res) {
4772 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation not supported by playback engine - ignoring\n"));
4773 0 : rep->playback.disabled = 1;
4774 0 : continue;
4775 : }
4776 : }
4777 : //filter out everything above HD
4778 393 : if ((dash->max_width>2000) && (dash->max_height>2000)) {
4779 0 : if ((rep->width>dash->max_width) || (rep->height>dash->max_height)) {
4780 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation size %dx%d exceeds max display size allowed %dx%d - ignoring\n", rep->width, rep->height, dash->max_width, dash->max_height));
4781 0 : rep->playback.disabled = 1;
4782 0 : continue;
4783 : }
4784 : }
4785 393 : if (rep->codecs && (dash->max_bit_per_pixel > 8) ) {
4786 0 : char *vid_type = strstr(rep->codecs, "hvc");
4787 0 : if (!vid_type) vid_type = strstr(rep->codecs, "hev");
4788 0 : if (!vid_type) vid_type = strstr(rep->codecs, "avc");
4789 0 : if (!vid_type) vid_type = strstr(rep->codecs, "svc");
4790 0 : if (!vid_type) vid_type = strstr(rep->codecs, "mvc");
4791 : //HEVC
4792 0 : if (vid_type && (!strnicmp(rep->codecs, "hvc", 3) || !strnicmp(rep->codecs, "hev", 3))) {
4793 0 : char *pidc = rep->codecs+5;
4794 0 : if ((pidc[0]=='A') || (pidc[0]=='B') || (pidc[0]=='C')) pidc++;
4795 : //Main 10 !!
4796 0 : if (!strncmp(pidc, "2.", 2)) {
4797 0 : rep->playback.disabled = 1;
4798 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation bit depth higher than max display bit depth - ignoring\n"));
4799 0 : continue;
4800 : }
4801 : }
4802 : //AVC
4803 0 : if (vid_type && (!strnicmp(rep->codecs, "avc", 3) || !strnicmp(rep->codecs, "svc", 3) || !strnicmp(rep->codecs, "mvc", 3))) {
4804 : char prof_string[3];
4805 : u8 prof;
4806 0 : strncpy(prof_string, vid_type+5, 2);
4807 0 : prof_string[2]=0;
4808 0 : prof = atoi(prof_string);
4809 : //Main 10
4810 0 : if (prof==0x6E) {
4811 0 : rep->playback.disabled = 1;
4812 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation bit depth higher than max display bit depth - ignoring\n"));
4813 0 : continue;
4814 : }
4815 : }
4816 : }
4817 :
4818 0 : for (k=0; k<gf_list_count(rep->essential_properties); k++) {
4819 0 : GF_MPD_Descriptor *mpd_desc = gf_list_get(rep->essential_properties, k);
4820 :
4821 : //we don't know any defined scheme for now
4822 0 : if (! strstr(mpd_desc->scheme_id_uri, "gpac") ) {
4823 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation with unrecognized EssentialProperty %s - ignoring because not supported\n", mpd_desc->scheme_id_uri));
4824 0 : rep->playback.disabled = 1;
4825 0 : break;
4826 : }
4827 : }
4828 393 : if (rep->playback.disabled) {
4829 0 : continue;
4830 : }
4831 :
4832 : cp_supported = 1;
4833 0 : for (k=0; k<gf_list_count(rep->content_protection); k++) {
4834 0 : GF_MPD_Descriptor *mpd_desc = gf_list_get(rep->content_protection, k);
4835 : //we don't know any defined scheme for now
4836 0 : if (strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:mp4protection:2011")) {
4837 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Representation with unrecognized ContentProtection %s\n", mpd_desc->scheme_id_uri));
4838 : cp_supported = 0;
4839 : } else {
4840 : cp_supported = 1;
4841 : break;
4842 : }
4843 : }
4844 393 : if (!cp_supported) {
4845 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation with no supported ContentProtection\n"));
4846 0 : rep->playback.disabled = 1;
4847 0 : continue;
4848 : }
4849 :
4850 393 : rep->playback.disabled = 0;
4851 393 : if (rep->width>set->max_width) {
4852 36 : set->max_width = rep->width;
4853 36 : set->max_height = rep->height;
4854 : }
4855 393 : if (rep->dependency_id && strlen(rep->dependency_id))
4856 : has_dependent_representations = GF_TRUE;
4857 : else
4858 303 : group->base_rep_index_plus_one = j+1;
4859 393 : rep->playback.enhancement_rep_index_plus_one = 0;
4860 1276 : for (k = 0; k < gf_list_count(set->representations); k++) {
4861 883 : GF_MPD_Representation *a_rep = gf_list_get(set->representations, k);
4862 883 : if (a_rep->dependency_id) {
4863 144 : char * tmp = strrchr(a_rep->dependency_id, ' ');
4864 144 : if (tmp)
4865 0 : tmp = tmp + 1;
4866 : else
4867 : tmp = a_rep->dependency_id;
4868 144 : if (!strcmp(tmp, rep->id))
4869 0 : rep->playback.enhancement_rep_index_plus_one = k + 1;
4870 : }
4871 : }
4872 393 : if (!rep->playback.enhancement_rep_index_plus_one)
4873 393 : group->max_complementary_rep_index = j;
4874 393 : if (!rep->playback.disabled && rep->dependency_id)
4875 90 : nb_dependent_rep++;
4876 : }
4877 :
4878 262 : if (!seg_dur && !dash->is_m3u8) {
4879 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Cannot compute default segment duration\n"));
4880 : }
4881 :
4882 262 : group->cache_duration = dash->max_cache_duration;
4883 262 : if (group->cache_duration < dash->mpd->min_buffer_time)
4884 257 : group->cache_duration = dash->mpd->min_buffer_time;
4885 :
4886 262 : group->max_cached_segments = (nb_dependent_rep+1);
4887 :
4888 262 : if (!has_dependent_representations)
4889 199 : group->base_rep_index_plus_one = 0; // all representations in this group are independent
4890 :
4891 262 : if (group->max_cached_segments>50) {
4892 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Too many cached segments (%d), segment duration %g - using 50 max\n", group->max_cached_segments, seg_dur));
4893 0 : group->max_cached_segments = 50;
4894 : }
4895 262 : group->cached = gf_malloc(sizeof(segment_cache_entry)*group->max_cached_segments);
4896 262 : memset(group->cached, 0, sizeof(segment_cache_entry)*group->max_cached_segments);
4897 262 : if (!group->cached) {
4898 0 : gf_free(group);
4899 0 : return GF_OUT_OF_MEM;
4900 : }
4901 262 : e = gf_list_add(dash->groups, group);
4902 262 : if (e) {
4903 0 : gf_free(group->cached);
4904 0 : gf_free(group);
4905 0 : return e;
4906 : }
4907 : }
4908 :
4909 :
4910 123 : count = gf_list_count(dash->groups);
4911 385 : for (i=0; i<count; i++) {
4912 262 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
4913 262 : j = gf_dash_group_get_dependency_group(dash, i);
4914 262 : if (i != j) {
4915 63 : GF_DASH_Group *base_group = gf_list_get(dash->groups, j);
4916 : assert(base_group);
4917 63 : group->depend_on_group = base_group;
4918 63 : if (!base_group->groups_depending_on) {
4919 7 : base_group->groups_depending_on = gf_list_new();
4920 : }
4921 63 : gf_list_add(base_group->groups_depending_on, group);
4922 : }
4923 : }
4924 :
4925 262 : for (i=0; i<count; i++) {
4926 262 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
4927 262 : if (group->groups_depending_on) {
4928 7 : u32 nb_dep_groups = gf_list_count(group->groups_depending_on);
4929 : //all dependent groups will be stored in the base group
4930 7 : group->max_cached_segments *= (1+nb_dep_groups);
4931 7 : group->cached = gf_realloc(group->cached, sizeof(segment_cache_entry)*group->max_cached_segments);
4932 7 : memset(group->cached, 0, sizeof(segment_cache_entry)*group->max_cached_segments);
4933 :
4934 70 : for (j=0; j<nb_dep_groups; j++) {
4935 63 : GF_DASH_Group *dep_group = gf_list_get(group->groups_depending_on, j);
4936 :
4937 63 : dep_group->max_cached_segments = 0;
4938 :
4939 : /* the rest of the code assumes that at least group->cached[0] is allocated */
4940 63 : dep_group->cached = gf_realloc(dep_group->cached, sizeof(segment_cache_entry));
4941 : memset(dep_group->cached, 0, sizeof(segment_cache_entry));
4942 :
4943 : }
4944 : }
4945 : }
4946 :
4947 : return GF_OK;
4948 : }
4949 :
4950 14 : static GF_Err gf_dash_load_sidx(GF_BitStream *bs, GF_MPD_Representation *rep, Bool separate_index, u64 sidx_offset)
4951 : {
4952 : #ifdef GPAC_DISABLE_ISOM
4953 : return GF_NOT_SUPPORTED;
4954 : #else
4955 : u64 anchor_position, prev_pos;
4956 14 : GF_SegmentIndexBox *sidx = NULL;
4957 : u32 i, size, type;
4958 : GF_Err e;
4959 : u64 offset;
4960 :
4961 14 : prev_pos = gf_bs_get_position(bs);
4962 14 : gf_bs_seek(bs, sidx_offset);
4963 14 : size = gf_bs_read_u32(bs);
4964 14 : type = gf_bs_read_u32(bs);
4965 14 : if (type != GF_ISOM_BOX_TYPE_SIDX) {
4966 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error parsing SIDX: type is %s (box start offset "LLD")\n", gf_4cc_to_str(type), gf_bs_get_position(bs)-8 ));
4967 : return GF_ISOM_INVALID_FILE;
4968 : }
4969 :
4970 14 : gf_bs_seek(bs, sidx_offset);
4971 :
4972 14 : anchor_position = sidx_offset + size;
4973 14 : if (separate_index)
4974 : anchor_position = 0;
4975 :
4976 14 : e = gf_isom_box_parse((GF_Box **) &sidx, bs);
4977 14 : if (e) return e;
4978 :
4979 14 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Loading SIDX - %d entries - Earliest Presentation Time "LLD"\n", sidx->nb_refs, sidx->earliest_presentation_time));
4980 :
4981 14 : offset = sidx->first_offset + anchor_position;
4982 14 : rep->segment_list->timescale = sidx->timescale;
4983 621 : for (i=0; i<sidx->nb_refs; i++) {
4984 607 : if (sidx->refs[i].reference_type) {
4985 0 : e = gf_dash_load_sidx(bs, rep, separate_index, offset);
4986 0 : if (e) {
4987 : break;
4988 : }
4989 : } else {
4990 : GF_MPD_SegmentURL *seg;
4991 607 : GF_SAFEALLOC(seg, GF_MPD_SegmentURL);
4992 607 : if (!seg) return GF_OUT_OF_MEM;
4993 607 : GF_SAFEALLOC(seg->media_range, GF_MPD_ByteRange);
4994 607 : if (!seg->media_range) return GF_OUT_OF_MEM;
4995 607 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Found media segment size %d - duration %d - start with SAP: %d - SAP type %d - SAP Deltat Time %d\n",
4996 : sidx->refs[i].reference_size, sidx->refs[i].subsegment_duration, sidx->refs[i].starts_with_SAP, sidx->refs[i].SAP_type, sidx->refs[i].SAP_delta_time));
4997 :
4998 607 : seg->media_range->start_range = offset;
4999 607 : offset += sidx->refs[i].reference_size;
5000 607 : seg->media_range->end_range = offset - 1;
5001 607 : seg->duration = sidx->refs[i].subsegment_duration;
5002 607 : gf_list_add(rep->segment_list->segment_URLs, seg);
5003 : }
5004 : }
5005 14 : gf_isom_box_del((GF_Box*)sidx);
5006 14 : gf_bs_seek(bs, prev_pos);
5007 14 : return e;
5008 : #endif
5009 : }
5010 :
5011 18 : static GF_Err gf_dash_load_representation_sidx(GF_DASH_Group *group, GF_MPD_Representation *rep, const char *cache_name, Bool separate_index, Bool needs_mov_range)
5012 : {
5013 : GF_Err e;
5014 : GF_BitStream *bs;
5015 : FILE *f=NULL;
5016 18 : if (!cache_name) return GF_BAD_PARAM;
5017 :
5018 18 : if (!strncmp(cache_name, "gmem://", 7)) {
5019 : u32 size;
5020 : u8 *mem_address;
5021 10 : e = gf_blob_get(cache_name, &mem_address, &size, NULL);
5022 10 : if (e) return e;
5023 :
5024 10 : bs = gf_bs_new(mem_address, size, GF_BITSTREAM_READ);
5025 10 : gf_blob_release(cache_name);
5026 : } else {
5027 8 : f = gf_fopen(cache_name, "rb");
5028 8 : if (!f) return GF_IO_ERR;
5029 8 : bs = gf_bs_from_file(f, GF_BITSTREAM_READ);
5030 : }
5031 : e = GF_OK;
5032 87 : while (gf_bs_available(bs)) {
5033 83 : u32 size = gf_bs_read_u32(bs);
5034 83 : u32 type = gf_bs_read_u32(bs);
5035 83 : if (type != GF_ISOM_BOX_TYPE_SIDX) {
5036 69 : gf_bs_skip_bytes(bs, size-8);
5037 :
5038 69 : if (needs_mov_range && (type==GF_ISOM_BOX_TYPE_MOOV )) {
5039 4 : GF_SAFEALLOC(rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange);
5040 4 : if (rep->segment_list->initialization_segment->byte_range)
5041 4 : rep->segment_list->initialization_segment->byte_range->end_range = gf_bs_get_position(bs);
5042 : }
5043 69 : continue;
5044 : }
5045 14 : gf_bs_seek(bs, gf_bs_get_position(bs)-8);
5046 14 : e = gf_dash_load_sidx(bs, rep, separate_index, gf_bs_get_position(bs) );
5047 :
5048 : /*we could also parse the sub sidx*/
5049 : break;
5050 : }
5051 18 : gf_bs_del(bs);
5052 18 : if (f) gf_fclose(f);
5053 : return e;
5054 : }
5055 :
5056 50 : static GF_Err dash_load_box_type(const char *cache_name, u32 offset, u32 *box_type, u32 *box_size)
5057 : {
5058 50 : *box_type = *box_size = 0;
5059 50 : if (!strncmp(cache_name, "gmem://", 7)) {
5060 : GF_Err e;
5061 : u32 size;
5062 : u8 *mem_address;
5063 50 : e = gf_blob_get(cache_name, &mem_address, &size, NULL);
5064 50 : if (e) return e;
5065 50 : gf_blob_release(cache_name);
5066 50 : if (offset+8 > size)
5067 : return GF_IO_ERR;
5068 50 : mem_address += offset;
5069 50 : *box_size = GF_4CC(mem_address[0], mem_address[1], mem_address[2], mem_address[3]);
5070 50 : *box_type = GF_4CC(mem_address[4], mem_address[5], mem_address[6], mem_address[7]);
5071 : } else {
5072 : unsigned char data[4];
5073 0 : FILE *f = gf_fopen(cache_name, "rb");
5074 0 : if (!f) return GF_IO_ERR;
5075 0 : if (gf_fseek(f, offset, SEEK_SET))
5076 : return GF_IO_ERR;
5077 0 : if (gf_fread(data, 4, f) == 4) {
5078 0 : *box_size = GF_4CC(data[0], data[1], data[2], data[3]);
5079 0 : if (gf_fread(data, 4, f) == 4) {
5080 0 : *box_type = GF_4CC(data[0], data[1], data[2], data[3]);
5081 : }
5082 : }
5083 0 : gf_fclose(f);
5084 : }
5085 : return GF_OK;
5086 : }
5087 :
5088 : extern void gf_mpd_segment_template_free(void *_item);
5089 262 : static GF_Err gf_dash_setup_single_index_mode(GF_DASH_Group *group)
5090 : {
5091 : u32 i;
5092 : GF_Err e = GF_OK;
5093 262 : char *init_url = NULL;
5094 262 : char *index_url = NULL;
5095 : GF_DASHFileIOSession *download_sess;
5096 262 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, 0);
5097 :
5098 262 : download_sess = &group->dash->mpd_dnload;
5099 :
5100 262 : if (!rep->segment_base && !group->adaptation_set->segment_base && !group->period->segment_base) {
5101 250 : if (rep->segment_template || group->adaptation_set->segment_template || group->period->segment_template) return GF_OK;
5102 50 : if (rep->segment_list || group->adaptation_set->segment_list || group->period->segment_list) return GF_OK;
5103 : } else {
5104 12 : char *profile = rep->profiles;
5105 12 : if (!profile) profile = group->adaptation_set->profiles;
5106 12 : if (!profile) profile = group->dash->mpd->profiles;
5107 :
5108 : //if on-demand cleanup all segment templates and segment list if we have base URLs
5109 12 : if (profile && strstr(profile, "on-demand")) {
5110 : u32 nb_rem=0;
5111 12 : if (rep->segment_template) {
5112 : nb_rem++;
5113 0 : gf_mpd_segment_template_free(rep->segment_template);
5114 0 : rep->segment_template = NULL;
5115 : }
5116 12 : if (group->adaptation_set->segment_template) {
5117 0 : nb_rem++;
5118 0 : gf_mpd_segment_template_free(group->adaptation_set->segment_template);
5119 0 : group->adaptation_set->segment_template = NULL;
5120 : }
5121 :
5122 12 : if (group->period->segment_template) {
5123 0 : nb_rem++;
5124 0 : gf_mpd_segment_template_free(group->period->segment_template);
5125 0 : group->period->segment_template = NULL;
5126 : }
5127 12 : if (nb_rem) {
5128 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] SegmentTemplate present for on-demand with SegmentBase present - skipping SegmentTemplate\n"));
5129 : }
5130 : nb_rem=0;
5131 12 : if (rep->segment_list) {
5132 : nb_rem++;
5133 0 : gf_mpd_segment_template_free(rep->segment_list);
5134 0 : rep->segment_list = NULL;
5135 : }
5136 12 : if (group->adaptation_set->segment_list) {
5137 0 : nb_rem++;
5138 0 : gf_mpd_segment_template_free(group->adaptation_set->segment_list);
5139 0 : group->adaptation_set->segment_list = NULL;
5140 : }
5141 12 : if (group->period->segment_list) {
5142 0 : nb_rem++;
5143 0 : gf_mpd_segment_template_free(group->period->segment_list);
5144 0 : group->period->segment_list = NULL;
5145 : }
5146 12 : if (nb_rem) {
5147 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] SegmentList present for on-demand with SegmentBase present - skipping SegmentList\n"));
5148 : }
5149 : }
5150 : }
5151 :
5152 : /*OK we are in single-file mode, download all required indexes & co*/
5153 18 : for (i=0; i<gf_list_count(group->adaptation_set->representations); i++) {
5154 : char *sidx_file = NULL;
5155 18 : u64 duration, index_start_range = 0, index_end_range = 0, init_start_range, init_end_range;
5156 : Bool index_in_base, init_in_base;
5157 : Bool init_needs_byte_range = GF_FALSE;
5158 : Bool has_seen_sidx = GF_FALSE;
5159 : Bool is_isom = GF_TRUE;
5160 18 : rep = gf_list_get(group->adaptation_set->representations, i);
5161 :
5162 18 : index_in_base = init_in_base = GF_FALSE;
5163 18 : e = gf_dash_resolve_url(group->dash->mpd, rep, group, group->dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &init_url, &init_start_range, &init_end_range, &duration, &init_in_base, NULL, NULL, NULL, NULL);
5164 18 : if (e) goto exit;
5165 :
5166 18 : e = gf_dash_resolve_url(group->dash->mpd, rep, group, group->dash->base_url, GF_MPD_RESOLVE_URL_INDEX, 0, &index_url, &index_start_range, &index_end_range, &duration, &index_in_base, NULL, NULL, NULL, NULL);
5167 18 : if (e) goto exit;
5168 :
5169 :
5170 18 : if (is_isom && (init_in_base || index_in_base)) {
5171 18 : if (!strstr(init_url, "://") || (!strnicmp(init_url, "file://", 7) ) ) {
5172 8 : GF_SAFEALLOC(rep->segment_list, GF_MPD_SegmentList);
5173 8 : if (!rep->segment_list) {
5174 : e = GF_OUT_OF_MEM;
5175 : goto exit;
5176 : }
5177 8 : rep->segment_list->segment_URLs = gf_list_new();
5178 :
5179 8 : if (rep->segment_base) rep->segment_list->presentation_time_offset = rep->segment_base->presentation_time_offset;
5180 0 : else if (group->adaptation_set->segment_base) rep->segment_list->presentation_time_offset = group->adaptation_set->segment_base->presentation_time_offset;
5181 0 : else if (group->period->segment_base) rep->segment_list->presentation_time_offset = group->period->segment_base->presentation_time_offset;
5182 :
5183 8 : if (init_in_base) {
5184 8 : GF_SAFEALLOC(rep->segment_list->initialization_segment, GF_MPD_URL);
5185 8 : if (!rep->segment_list->initialization_segment) {
5186 : e = GF_OUT_OF_MEM;
5187 : goto exit;
5188 : }
5189 8 : rep->segment_list->initialization_segment->sourceURL = gf_strdup(init_url);
5190 8 : rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
5191 : /*we don't want to load the entire movie */
5192 : init_needs_byte_range = 1;
5193 : }
5194 8 : if (index_in_base) {
5195 8 : sidx_file = (char *)init_url;
5196 : }
5197 : }
5198 : /*we need to download the init segment, at least partially*/
5199 : else {
5200 : u32 offset = 0;
5201 10 : u32 box_type = 0;
5202 10 : u32 box_size = 0;
5203 : u32 sidx_start = 0;
5204 : const char *cache_name;
5205 :
5206 10 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Downloading init segment and SIDX for representation %s\n", init_url));
5207 :
5208 : /*download first 8 bytes and check if we do have a box starting there*/
5209 10 : e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, 7, 1, group);
5210 10 : if (e) goto exit;
5211 10 : cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
5212 10 : e = dash_load_box_type(cache_name, offset, &box_type, &box_size);
5213 : offset = 8;
5214 50 : while (box_type) {
5215 : /*we got the moov, stop here */
5216 40 : if (!index_in_base && (box_type==GF_ISOM_BOX_TYPE_MOOV)) {
5217 0 : e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, offset+box_size-9, 2, group);
5218 0 : break;
5219 : } else {
5220 : const u32 offset_ori = offset;
5221 40 : e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, offset+box_size-1, 2, group);
5222 40 : if (e < 0) goto exit;
5223 40 : offset += box_size;
5224 : /*we need to refresh the cache name because of our memory astorage thing ...*/
5225 40 : cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
5226 40 : e = dash_load_box_type(cache_name, offset-8, &box_type, &box_size);
5227 40 : if (e == GF_IO_ERR) {
5228 : /*if the socket was closed then gf_dash_download_resource() with gmem:// was reset - retry*/
5229 0 : e = dash_load_box_type(cache_name, offset-offset_ori-8, &box_type, &box_size);
5230 0 : if (box_type == GF_ISOM_BOX_TYPE_SIDX) {
5231 : offset -= 8;
5232 : /*FIXME sidx found, reload the full resource*/
5233 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] have to re-downloading init and SIDX for rep %s\n", init_url));
5234 0 : e = gf_dash_download_resource(group->dash, download_sess, init_url, 0, offset+box_size-1, 2, group);
5235 0 : break;
5236 : }
5237 : }
5238 :
5239 40 : if (box_type == GF_ISOM_BOX_TYPE_SIDX) {
5240 10 : if (!sidx_start) sidx_start = offset;
5241 : has_seen_sidx = 1;
5242 30 : } else if (has_seen_sidx)
5243 : break;
5244 : }
5245 : }
5246 10 : if (e < 0) goto exit;
5247 :
5248 10 : if (box_type == 0) {
5249 : e = GF_ISOM_INVALID_FILE;
5250 : goto exit;
5251 : }
5252 10 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Done downloading init segment and SIDX\n"));
5253 :
5254 10 : GF_SAFEALLOC(rep->segment_list, GF_MPD_SegmentList);
5255 10 : if (!rep->segment_list) {
5256 : e = GF_OUT_OF_MEM;
5257 : goto exit;
5258 : }
5259 10 : rep->segment_list->segment_URLs = gf_list_new();
5260 :
5261 10 : cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
5262 10 : if (init_in_base) {
5263 : char szName[100];
5264 10 : GF_SAFEALLOC(rep->segment_list->initialization_segment, GF_MPD_URL);
5265 10 : if (!rep->segment_list->initialization_segment) {
5266 : e = GF_OUT_OF_MEM;
5267 0 : goto exit;
5268 : }
5269 :
5270 10 : rep->segment_list->initialization_segment->sourceURL = gf_strdup(init_url);
5271 10 : GF_SAFEALLOC(rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange);
5272 10 : if (rep->segment_list->initialization_segment->byte_range) {
5273 10 : rep->segment_list->initialization_segment->byte_range->start_range = init_start_range;
5274 10 : rep->segment_list->initialization_segment->byte_range->end_range = init_end_range ? init_end_range : (sidx_start-1);
5275 : }
5276 :
5277 : //we need to store the init segment since it has the same name as the rest of the segments and will be destroyed when cleaning up the cache ..
5278 0 : else if (!strnicmp(cache_name, "gmem://", 7)) {
5279 : u8 *mem_address;
5280 0 : e = gf_blob_get(cache_name, &mem_address, &rep->playback.init_segment.size, NULL);
5281 0 : if (e) {
5282 : goto exit;
5283 : }
5284 0 : rep->playback.init_segment.data = gf_malloc(sizeof(char) * rep->playback.init_segment.size);
5285 0 : memcpy(rep->playback.init_segment.data, mem_address, sizeof(char) * rep->playback.init_segment.size);
5286 :
5287 0 : sprintf(szName, "gmem://%p", &rep->playback.init_segment);
5288 0 : rep->segment_list->initialization_segment->sourceURL = gf_strdup(szName);
5289 0 : rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
5290 0 : gf_blob_release(cache_name);
5291 : } else {
5292 0 : FILE *t = gf_fopen(cache_name, "rb");
5293 0 : if (t) {
5294 : s32 res;
5295 0 : rep->playback.init_segment.size = (u32) gf_fsize(t);
5296 :
5297 0 : rep->playback.init_segment.data = gf_malloc(sizeof(char) * rep->playback.init_segment.size);
5298 0 : res = (s32) gf_fread(rep->playback.init_segment.data, rep->playback.init_segment.size, t);
5299 0 : if (res != rep->playback.init_segment.size) {
5300 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to store init segment\n"));
5301 0 : } else if (rep->segment_list && rep->segment_list->initialization_segment) {
5302 0 : sprintf(szName, "gmem://%p", &rep->playback.init_segment);
5303 0 : rep->segment_list->initialization_segment->sourceURL = gf_strdup(szName);
5304 0 : rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
5305 : }
5306 : }
5307 : }
5308 : }
5309 10 : if (index_in_base) {
5310 : sidx_file = (char *)cache_name;
5311 : }
5312 : }
5313 : }
5314 : /*we have index url, download it*/
5315 18 : if (! index_in_base) {
5316 0 : e = gf_dash_download_resource(group->dash, download_sess, index_url, index_start_range, index_end_range, 1, group);
5317 0 : if (e) goto exit;
5318 0 : sidx_file = (char *)group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
5319 : }
5320 :
5321 : /*load sidx*/
5322 18 : e = gf_dash_load_representation_sidx(group, rep, sidx_file, !index_in_base, init_needs_byte_range);
5323 18 : if (e) {
5324 0 : rep->playback.disabled = 1;
5325 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to load segment index for this representation - disabling\n"));
5326 : }
5327 :
5328 : //cleanup cache right away
5329 18 : group->dash->dash_io->delete_cache_file(group->dash->dash_io, *download_sess, init_url);
5330 :
5331 : /*reset all seg based stuff*/
5332 18 : if (rep->segment_base) {
5333 18 : gf_mpd_segment_base_free(rep->segment_base);
5334 18 : rep->segment_base = NULL;
5335 : }
5336 :
5337 18 : gf_free(index_url);
5338 18 : index_url = NULL;
5339 18 : gf_free(init_url);
5340 18 : init_url = NULL;
5341 : }
5342 12 : if (group->adaptation_set->segment_base) {
5343 0 : gf_mpd_segment_base_free(group->adaptation_set->segment_base);
5344 0 : group->adaptation_set->segment_base = NULL;
5345 : }
5346 12 : group->was_segment_base = 1;
5347 :
5348 12 : exit:
5349 12 : if (init_url) gf_free(init_url);
5350 12 : if (index_url) gf_free(index_url);
5351 : return e;
5352 : }
5353 :
5354 4 : static void gf_dash_solve_period_xlink(GF_DashClient *dash, GF_List *period_list, u32 period_idx)
5355 : {
5356 : u32 count, i;
5357 : GF_Err e;
5358 : u64 start = 0;
5359 : u64 src_duration = 0;
5360 : Bool is_local=GF_FALSE;
5361 : const char *local_url;
5362 : char *url, *period_xlink;
5363 : GF_DOMParser *parser;
5364 : GF_MPD *new_mpd;
5365 : GF_MPD_Period *period;
5366 : u32 nb_inserted = 0;
5367 4 : GF_DASHFileIOSession xlink_sess=NULL;
5368 :
5369 4 : period = gf_list_get(period_list, period_idx);
5370 4 : if (!period->xlink_href || (dash->route_clock_state==1)) {
5371 0 : return;
5372 : }
5373 4 : start = period->start;
5374 4 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Resolving period XLINK %s\n", period->xlink_href));
5375 :
5376 4 : if (!strcmp(period->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013")) {
5377 : //spec is not very clear here, I suppose it means "remove the element"
5378 0 : gf_list_rem(period_list, period_idx);
5379 0 : gf_mpd_period_free(period);
5380 0 : return;
5381 : }
5382 :
5383 : //ATSC puts a tag in front of the url ("tag:atsc.org,2016:xlink") - in case others decide to follow this crazy example, whe search for http:// or https://
5384 4 : period_xlink = strstr(period->xlink_href, "http://");
5385 4 : if (!period_xlink) period_xlink = strstr(period->xlink_href, "HTTP://");
5386 4 : if (!period_xlink) period_xlink = strstr(period->xlink_href, "https://");
5387 4 : if (!period_xlink) period_xlink = strstr(period->xlink_href, "HTTPS://");
5388 4 : if (!period_xlink) period_xlink = period->xlink_href;
5389 :
5390 : //xlink relative to our MPD base URL
5391 4 : url = gf_url_concatenate(dash->base_url, period_xlink);
5392 :
5393 4 : if (!strstr(url, "://") || !strnicmp(url, "file://", 7) ) {
5394 : local_url = url;
5395 : is_local=GF_TRUE;
5396 : e = GF_OK;
5397 : } else {
5398 3 : if (dash->query_string) {
5399 : char *full_url;
5400 : char *purl, *sep;
5401 : u32 len;
5402 : purl = url ? url : period_xlink;
5403 0 : len = (u32) (2 + strlen(purl) + strlen(dash->query_string) + (period->ID ? strlen(period->ID) : 0 ) );
5404 0 : full_url = gf_malloc(sizeof(char)*len);
5405 :
5406 : strcpy(full_url, purl);
5407 0 : if (strchr(purl, '?')) strcat(full_url, "&");
5408 : else strcat(full_url, "?");
5409 :
5410 : //append the query string
5411 0 : strcat(full_url, dash->query_string);
5412 : //if =PID is given, replace by period ID
5413 0 : sep = strstr(dash->query_string, "=PID");
5414 0 : if (sep && period->ID) {
5415 0 : char *sep2 = strstr(full_url, "=PID");
5416 : assert(sep2);
5417 0 : sep2[1] = 0;
5418 0 : strcat(full_url, period->ID);
5419 0 : strcat(full_url, sep+4);
5420 : }
5421 :
5422 : /*use non-persistent connection for MPD*/
5423 0 : e = gf_dash_download_resource(dash, &xlink_sess, full_url, 0, 0, 0, NULL);
5424 0 : gf_free(full_url);
5425 :
5426 : } else {
5427 : /*use non-persistent connection for MPD*/
5428 3 : e = gf_dash_download_resource(dash, &xlink_sess, url ? url : period_xlink, 0, 0, 0, NULL);
5429 : }
5430 : }
5431 :
5432 4 : if (e) {
5433 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot download xlink from periods %s: error %s\n", period->xlink_href, gf_error_to_string(e)));
5434 0 : gf_free(period->xlink_href);
5435 0 : period->xlink_href = NULL;
5436 0 : if (xlink_sess) dash->dash_io->del(dash->dash_io, xlink_sess);
5437 0 : if (url) gf_free(url);
5438 : return;
5439 : }
5440 :
5441 4 : if (!is_local) {
5442 : /*in case the session has been restarted, local_url may have been destroyed - get it back*/
5443 3 : local_url = dash->dash_io->get_cache_name(dash->dash_io, xlink_sess);
5444 : }
5445 :
5446 : /* parse the MPD */
5447 4 : parser = gf_xml_dom_new();
5448 4 : e = gf_xml_dom_parse(parser, local_url, NULL, NULL);
5449 4 : if (url) gf_free(url);
5450 : url = NULL;
5451 :
5452 4 : if (xlink_sess) {
5453 : //get redirected URL
5454 3 : url = (char *) dash->dash_io->get_url(dash->dash_io, xlink_sess);
5455 3 : if (url) url = gf_strdup(url);
5456 3 : dash->dash_io->del(dash->dash_io, xlink_sess);
5457 : }
5458 4 : if (e != GF_OK) {
5459 0 : gf_xml_dom_del(parser);
5460 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot parse xlink periods: error in XML parsing %s\n", gf_error_to_string(e)));
5461 0 : gf_free(period->xlink_href);
5462 0 : period->xlink_href = NULL;
5463 0 : if (url) gf_free(url);
5464 : return;
5465 : }
5466 4 : new_mpd = gf_mpd_new();
5467 :
5468 4 : count = gf_xml_dom_get_root_nodes_count(parser);
5469 8 : for (i=0; i<count; i++) {
5470 4 : GF_XMLNode *root = gf_xml_dom_get_root_idx(parser, i);
5471 4 : if (i) {
5472 0 : e = gf_mpd_complete_from_dom(root, new_mpd, period->xlink_href);
5473 : } else {
5474 4 : e = gf_mpd_init_from_dom(root, new_mpd, period->xlink_href);
5475 : }
5476 4 : if (e) break;
5477 : }
5478 4 : gf_xml_dom_del(parser);
5479 4 : if (e) {
5480 0 : gf_free(period->xlink_href);
5481 0 : period->xlink_href = NULL;
5482 0 : gf_mpd_del(new_mpd);
5483 0 : if (url) gf_free(url);
5484 : return;
5485 : }
5486 :
5487 4 : gf_list_rem(period_list, period_idx);
5488 :
5489 4 : if (dash->split_adaptation_set)
5490 0 : gf_mpd_split_adaptation_sets(new_mpd);
5491 :
5492 4 : if (!period->duration) {
5493 3 : GF_MPD_Period *next_period = gf_list_get(period_list, period_idx);
5494 3 : if (next_period && next_period->start)
5495 0 : period->duration = next_period->start - period->start;
5496 : }
5497 4 : src_duration = period->duration;
5498 : //insert all periods
5499 11 : while (gf_list_count(new_mpd->periods)) {
5500 4 : GF_MPD_Period *inserted_period = gf_list_get(new_mpd->periods, 0);
5501 4 : gf_list_rem(new_mpd->periods, 0);
5502 : //forbiden
5503 4 : if (inserted_period->xlink_href && inserted_period->xlink_actuate_on_load) {
5504 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Invalid remote period with xlink:actuate=\"onLoad\" and xlink:href set, removing parent period.\n"));
5505 0 : gf_mpd_period_free(inserted_period);
5506 0 : continue;
5507 : }
5508 4 : inserted_period->origin_base_url = url ? gf_strdup(url) : NULL;
5509 4 : inserted_period->start = start;
5510 4 : inserted_period->type = GF_MPD_TYPE_STATIC;
5511 :
5512 4 : gf_list_insert(period_list, inserted_period, period_idx);
5513 4 : period_idx++;
5514 4 : nb_inserted++;
5515 :
5516 4 : if (period->duration) {
5517 : //truncate duration
5518 1 : if (inserted_period->duration > src_duration) {
5519 1 : inserted_period->duration = src_duration;
5520 1 : break;
5521 : } else {
5522 0 : src_duration -= inserted_period->duration;
5523 : }
5524 : }
5525 3 : start += inserted_period->duration;
5526 : }
5527 4 : if (url) gf_free(url);
5528 :
5529 4 : if (!nb_inserted && gf_list_count(period->adaptation_sets)) {
5530 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] No periods inserted during ad insertion, but origin period not empty - ignoring ad insertion.\n"));
5531 0 : gf_list_insert(period_list, period, period_idx);
5532 0 : period->broken_xlink = period->xlink_href;
5533 0 : period->xlink_href = NULL;
5534 : } else {
5535 : //this will do the garbage collection
5536 4 : gf_list_add(new_mpd->periods, period);
5537 : }
5538 4 : gf_mpd_del(new_mpd);
5539 : }
5540 :
5541 11740 : static u32 gf_dash_get_tiles_quality_rank(GF_DashClient *dash, GF_DASH_Group *tile_group)
5542 : {
5543 : s32 res, res2;
5544 11740 : struct _dash_srd_desc *srd = tile_group->srd_desc;
5545 :
5546 : //no SRD is max quality for now
5547 11740 : if (!srd) return 0;
5548 7826 : if (!tile_group->srd_w || !tile_group->srd_h) return 0;
5549 :
5550 7045 : if (tile_group->quality_degradation_hint) {
5551 48 : u32 v = tile_group->quality_degradation_hint * MAX(srd->srd_nb_rows, srd->srd_nb_cols);
5552 48 : v/=100;
5553 48 : if (dash->disable_low_quality_tiles)
5554 0 : tile_group->disabled = GF_TRUE;
5555 : return v;
5556 : }
5557 6997 : tile_group->disabled = GF_FALSE;
5558 :
5559 : //TODO - use visibility rect as well
5560 :
5561 6997 : switch (dash->tile_adapt_mode) {
5562 : case GF_DASH_ADAPT_TILE_NONE:
5563 : return 0;
5564 0 : case GF_DASH_ADAPT_TILE_ROWS:
5565 0 : return tile_group->srd_row_idx;
5566 0 : case GF_DASH_ADAPT_TILE_ROWS_REVERSE:
5567 0 : return srd->srd_nb_rows - 1 - tile_group->srd_row_idx;
5568 0 : case GF_DASH_ADAPT_TILE_ROWS_MIDDLE:
5569 0 : res = srd->srd_nb_rows/2;
5570 0 : res -= tile_group->srd_row_idx;
5571 0 : return ABS(res);
5572 0 : case GF_DASH_ADAPT_TILE_COLUMNS:
5573 0 : return tile_group->srd_col_idx;
5574 0 : case GF_DASH_ADAPT_TILE_COLUMNS_REVERSE:
5575 0 : return srd->srd_nb_cols - 1 - tile_group->srd_col_idx;
5576 0 : case GF_DASH_ADAPT_TILE_COLUMNS_MIDDLE:
5577 0 : res = srd->srd_nb_cols/2;
5578 0 : res -= tile_group->srd_col_idx;
5579 0 : return ABS(res);
5580 0 : case GF_DASH_ADAPT_TILE_CENTER:
5581 0 : res = srd->srd_nb_rows/2 - tile_group->srd_row_idx;
5582 0 : res2 = srd->srd_nb_cols/2 - tile_group->srd_col_idx;
5583 0 : return MAX( ABS(res), ABS(res2) );
5584 0 : case GF_DASH_ADAPT_TILE_EDGES:
5585 0 : res = srd->srd_nb_rows/2 - tile_group->srd_row_idx;
5586 0 : res = srd->srd_nb_rows/2 - ABS(res);
5587 0 : res2 = srd->srd_nb_cols/2 - tile_group->srd_col_idx;
5588 0 : res2 = srd->srd_nb_cols/2 - ABS(res2);
5589 0 : return MIN( res, res2 );
5590 : }
5591 : return 0;
5592 : }
5593 :
5594 : //used upon startup of the session only
5595 9 : static void gf_dash_set_tiles_quality(GF_DashClient *dash, struct _dash_srd_desc *srd, Bool force_all)
5596 : {
5597 : u32 i, count;
5598 9 : Bool tiles_use_lowest = (dash->first_select_mode==GF_DASH_SELECT_BANDWIDTH_HIGHEST_TILES) ? GF_TRUE : GF_FALSE;
5599 :
5600 9 : count = gf_list_count(dash->groups);
5601 99 : for (i=0; i<count; i++) {
5602 90 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
5603 : u32 lower_quality;
5604 90 : if (group->srd_desc != srd) continue;
5605 :
5606 : //dynamic changes of qualities, only update if changed
5607 90 : if (!force_all) {
5608 20 : if (!group->update_tile_qualities) continue;
5609 16 : group->update_tile_qualities = GF_FALSE;
5610 : }
5611 :
5612 86 : lower_quality = gf_dash_get_tiles_quality_rank(dash, group);
5613 86 : if (!lower_quality) continue;
5614 :
5615 6 : if (tiles_use_lowest && (group->active_rep_index >= lower_quality)) {
5616 0 : lower_quality = group->active_rep_index - lower_quality;
5617 : } else {
5618 : lower_quality = 0;
5619 : }
5620 6 : gf_dash_set_group_representation(group, gf_list_get(group->adaptation_set->representations, lower_quality), GF_FALSE);
5621 : }
5622 9 : }
5623 :
5624 70 : static struct _dash_srd_desc *gf_dash_get_srd_desc(GF_DashClient *dash, u32 srd_id, Bool do_create)
5625 : {
5626 : u32 i, count;
5627 : struct _dash_srd_desc *srd;
5628 70 : count = dash->SRDs ? gf_list_count(dash->SRDs) : 0;
5629 0 : for (i=0; i<count; i++) {
5630 63 : srd = gf_list_get(dash->SRDs, i);
5631 63 : if (srd->id==srd_id) return srd;
5632 : }
5633 7 : if (!do_create) return NULL;
5634 7 : GF_SAFEALLOC(srd, struct _dash_srd_desc);
5635 7 : if (!srd) return NULL;
5636 7 : srd->id = srd_id;
5637 7 : if (!dash->SRDs) dash->SRDs = gf_list_new();
5638 7 : gf_list_add(dash->SRDs, srd);
5639 : return srd;
5640 : }
5641 :
5642 123 : static GF_Err gf_dash_setup_period(GF_DashClient *dash)
5643 : {
5644 : GF_MPD_Period *period;
5645 : u32 rep_i, as_i, group_i, j, nb_groups_ok;
5646 : u32 retry = 10;
5647 :
5648 : //solve xlink - if
5649 247 : while (retry) {
5650 124 : period = gf_list_get(dash->mpd->periods, dash->active_period_index);
5651 124 : if (!period) return GF_EOS;
5652 124 : if (!period->xlink_href) break;
5653 1 : gf_dash_solve_period_xlink(dash, dash->mpd->periods, dash->active_period_index);
5654 1 : retry --;
5655 : }
5656 123 : period = gf_list_get(dash->mpd->periods, dash->active_period_index);
5657 123 : if (period->xlink_href && (dash->route_clock_state!=1) ) {
5658 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Too many xlink indirections on the same period - not supported\n"));
5659 : return GF_NOT_SUPPORTED;
5660 : }
5661 :
5662 123 : if (!period->duration) {
5663 39 : GF_MPD_Period *next_period = gf_list_get(dash->mpd->periods, dash->active_period_index+1);
5664 39 : if (next_period && next_period->start)
5665 1 : period->duration = next_period->start - period->start;
5666 : }
5667 :
5668 : /*we are not able to process webm dash (youtube)*/
5669 123 : j = gf_list_count(period->adaptation_sets);
5670 385 : for (as_i=0; as_i<j; as_i++) {
5671 262 : GF_MPD_AdaptationSet *set = (GF_MPD_AdaptationSet*)gf_list_get(period->adaptation_sets, as_i);
5672 262 : if (set->mime_type && strstr(set->mime_type, "webm")) {
5673 : u32 k;
5674 0 : for (k=0; k<gf_list_count(set->representations); ++k) {
5675 0 : GF_MPD_Representation *rep = (GF_MPD_Representation*)gf_list_get(set->representations, k);
5676 0 : rep->playback.disabled = GF_TRUE;
5677 : }
5678 : }
5679 : }
5680 :
5681 123 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Setting up period start "LLU" duration "LLU" xlink %s ID %s\n", period->start, period->duration, period->origin_base_url ? period->origin_base_url : "none", period->ID ? period->ID : "none"));
5682 :
5683 : /*setup all groups*/
5684 123 : gf_dash_setup_groups(dash);
5685 :
5686 : nb_groups_ok = 0;
5687 385 : for (group_i=0; group_i<gf_list_count(dash->groups); group_i++) {
5688 : GF_MPD_Representation *rep_sel;
5689 : u32 active_rep, nb_rep;
5690 : const char *mime_type;
5691 : u32 nb_rep_ok = 0;
5692 : Bool group_has_video = GF_FALSE;
5693 : Bool disabled = GF_FALSE;
5694 : Bool cp_supported = GF_FALSE;
5695 262 : GF_DASH_Group *group = gf_list_get(dash->groups, group_i);
5696 : Bool active_rep_found;
5697 :
5698 : active_rep = 0;
5699 :
5700 262 : if (dash->dbg_grps_index) {
5701 : Bool disable = GF_TRUE;
5702 : u32 gidx;
5703 0 : for (gidx=0; gidx<dash->nb_dbg_grps; gidx++) {
5704 0 : if (group_i == dash->dbg_grps_index[gidx]) {
5705 : disable = GF_FALSE;
5706 : break;
5707 : }
5708 : }
5709 0 : if (disable) {
5710 0 : group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
5711 0 : continue;
5712 : }
5713 : }
5714 :
5715 262 : nb_rep = gf_list_count(group->adaptation_set->representations);
5716 :
5717 : //on HLS get rid of audio only adaptation set if not in fMP4 mode
5718 262 : if (dash->is_m3u8
5719 37 : && !group->adaptation_set->max_width
5720 13 : && !group->adaptation_set->max_height
5721 13 : && (gf_list_count(dash->groups)>1)
5722 : ) {
5723 13 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, 0);
5724 13 : if ((!rep->segment_template || !rep->segment_template->initialization)
5725 13 : && (!rep->segment_list || (!rep->segment_list->initialization_segment && !rep->segment_list->xlink_href))
5726 : ) {
5727 0 : group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
5728 0 : continue;
5729 : }
5730 : }
5731 :
5732 262 : if ((nb_rep>1) && !group->adaptation_set->segment_alignment && !group->adaptation_set->subsegment_alignment) {
5733 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet without segmentAlignment flag set - may result in broken adaptation\n"));
5734 : }
5735 262 : if (group->adaptation_set->xlink_href) {
5736 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet with xlink:href to %s - ignoring because not supported\n", group->adaptation_set->xlink_href));
5737 0 : continue;
5738 : }
5739 :
5740 276 : for (j=0; j<gf_list_count(group->adaptation_set->essential_properties); j++) {
5741 7 : GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->essential_properties, j);
5742 7 : if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:srd:2014")) {
5743 : u32 id, w, h, res;
5744 7 : w = h = 0;
5745 7 : res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h, &w, &h);
5746 7 : if (res != 7) {
5747 0 : res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h);
5748 0 : if (res!=5) res=0;
5749 : }
5750 7 : if (res) {
5751 7 : group->srd_desc = gf_dash_get_srd_desc(dash, id, GF_TRUE);
5752 7 : if (!w) w = group->srd_x + group->srd_w;
5753 7 : if (!h) h = group->srd_y + group->srd_h;
5754 :
5755 7 : if (w>group->srd_desc->srd_fw)
5756 7 : group->srd_desc->srd_fw = w;
5757 7 : if (h>group->srd_desc->srd_fh)
5758 7 : group->srd_desc->srd_fh = h;
5759 : }
5760 :
5761 : } else {
5762 : //we don't know any defined scheme for now
5763 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet with unrecognized EssentialProperty %s - ignoring because not supported\n", mpd_desc->scheme_id_uri));
5764 : disabled = 1;
5765 : break;
5766 : }
5767 : }
5768 : if (disabled) {
5769 0 : continue;
5770 : }
5771 :
5772 : cp_supported = 1;
5773 0 : for (j=0; j<gf_list_count(group->adaptation_set->content_protection); j++) {
5774 11 : GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->content_protection, j);
5775 : //we don't know any defined scheme for now
5776 11 : if (strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:mp4protection:2011")) {
5777 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AdaptationSet with unrecognized ContentProtection %s\n", mpd_desc->scheme_id_uri));
5778 : cp_supported = 0;
5779 : } else {
5780 : cp_supported = 1;
5781 : break;
5782 : }
5783 : }
5784 262 : if (!cp_supported) {
5785 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet with no supported ContentProtection - ignoring\n"));
5786 0 : continue;
5787 : }
5788 :
5789 63 : for (j=0; j<gf_list_count(group->adaptation_set->supplemental_properties); j++) {
5790 63 : GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->supplemental_properties, j);
5791 63 : if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:srd:2014")) {
5792 : u32 id, w, h, res;
5793 63 : w = h = 0;
5794 63 : res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h, &w, &h);
5795 63 : if (res != 7) {
5796 63 : res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h);
5797 63 : if (res != 5) res=0;
5798 : }
5799 63 : if (res) {
5800 63 : group->srd_desc = gf_dash_get_srd_desc(dash, id, GF_TRUE);
5801 63 : if (!w) w = group->srd_x + group->srd_w;
5802 63 : if (!h) h = group->srd_y + group->srd_h;
5803 :
5804 63 : if (w>group->srd_desc->srd_fw)
5805 0 : group->srd_desc->srd_fw = w;
5806 63 : if (h>group->srd_desc->srd_fh)
5807 0 : group->srd_desc->srd_fh = h;
5808 : }
5809 : }
5810 : }
5811 :
5812 : /*translate from single-indexed file to SegmentList*/
5813 262 : gf_dash_setup_single_index_mode(group);
5814 :
5815 : /* Select the appropriate representation in the given period */
5816 655 : for (rep_i = 0; rep_i < nb_rep; rep_i++) {
5817 393 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
5818 393 : if (rep->width && rep->height) group_has_video = GF_TRUE;
5819 : }
5820 :
5821 : //sort by ascending bandwidth and quality
5822 406 : for (rep_i = 1; rep_i < nb_rep; rep_i++) {
5823 : Bool swap=GF_FALSE;
5824 406 : GF_MPD_Representation *r2 = gf_list_get(group->adaptation_set->representations, rep_i);
5825 406 : GF_MPD_Representation *r1 = gf_list_get(group->adaptation_set->representations, rep_i-1);
5826 406 : if (r1->bandwidth > r2->bandwidth) {
5827 : swap=GF_TRUE;
5828 299 : } else if ((r1->bandwidth == r2->bandwidth) && (r1->quality_ranking<r2->quality_ranking)) {
5829 : swap=GF_TRUE;
5830 : }
5831 : if (swap) {
5832 107 : gf_list_rem(group->adaptation_set->representations, rep_i);
5833 107 : gf_list_insert(group->adaptation_set->representations, r2, rep_i-1);
5834 : rep_i=0;
5835 : }
5836 : }
5837 :
5838 262 : select_active_rep:
5839 262 : group->min_representation_bitrate = (u32) -1;
5840 : active_rep_found = GF_FALSE;
5841 655 : for (rep_i = 0; rep_i < nb_rep; rep_i++) {
5842 393 : u32 first_select_mode = dash->first_select_mode;
5843 393 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
5844 393 : if (rep->playback.disabled)
5845 0 : continue;
5846 393 : if (!active_rep_found) {
5847 : active_rep = rep_i;
5848 : active_rep_found = GF_TRUE;
5849 : }
5850 :
5851 393 : rep_sel = gf_list_get(group->adaptation_set->representations, active_rep);
5852 :
5853 393 : if (group_has_video && !rep->width && !rep->height) {
5854 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Adaptation %s: non-video in a video group - disabling it\n", rep->id));
5855 0 : rep->playback.disabled = 1;
5856 0 : continue;
5857 : }
5858 :
5859 393 : if (group_has_video && !rep_sel->width && !rep_sel->height && rep->width && rep->height) {
5860 : rep_sel = rep;
5861 : }
5862 :
5863 393 : if (rep->bandwidth < group->min_representation_bitrate) {
5864 262 : group->min_representation_bitrate = rep->bandwidth;
5865 : }
5866 :
5867 393 : if (rep_i) {
5868 : Bool ok;
5869 131 : if (rep->codecs && rep_sel->codecs) {
5870 117 : char *sep = strchr(rep_sel->codecs, '.');
5871 117 : if (sep) sep[0] = 0;
5872 117 : ok = !strnicmp(rep->codecs, rep_sel->codecs, strlen(rep_sel->codecs) );
5873 : //check for scalable coding
5874 117 : if (!ok && rep->dependency_id) {
5875 0 : if (!strncmp(rep_sel->codecs, "avc", 3)) {
5876 : //we accept LHVC with different configs as enhancement for AVC
5877 0 : if (!strncmp(rep->codecs, "lhv", 3) || !strncmp(rep->codecs, "lhe", 3) ) ok = 1;
5878 : //we accept SVC and MVC as enhancement for AVC
5879 0 : else if (!strncmp(rep->codecs, "svc", 3) || !strncmp(rep->codecs, "mvc", 3) ) ok = 1;
5880 : }
5881 0 : else if (!strncmp(rep_sel->codecs, "hvc", 3) || !strncmp(rep_sel->codecs, "hev", 3)) {
5882 : //we accept HEVC and HEVC+LHVC with different configs
5883 0 : if (!strncmp(rep->codecs, "hvc", 3) || !strncmp(rep->codecs, "hev", 3) ) ok = 1;
5884 : //we accept LHVC with different configs
5885 0 : else if (!strncmp(rep->codecs, "lhv", 3) || !strncmp(rep->codecs, "lhe", 3) ) ok = 1;
5886 : }
5887 : }
5888 :
5889 117 : if (sep) sep[0] = '.';
5890 117 : if (!ok) {
5891 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Different codec types (%s vs %s) in same AdaptationSet - disabling rep %s\n", rep_sel->codecs, rep->codecs, rep->codecs));
5892 : //we don(t support mixes
5893 0 : rep->playback.disabled = 1;
5894 0 : continue;
5895 : }
5896 : }
5897 : }
5898 : //move to highest rate if ROUTE session and rep is not a remote one (baseURL set)
5899 393 : if (dash->route_clock_state && (first_select_mode==GF_DASH_SELECT_BANDWIDTH_LOWEST) && !gf_list_count(rep->base_URLs))
5900 : first_select_mode = GF_DASH_SELECT_BANDWIDTH_HIGHEST;
5901 :
5902 393 : switch (first_select_mode) {
5903 0 : case GF_DASH_SELECT_QUALITY_LOWEST:
5904 0 : if (rep->quality_ranking && (rep->quality_ranking < rep_sel->quality_ranking)) {
5905 : active_rep = rep_i;
5906 : break;
5907 : }/*fallthrough if quality is not indicated*/
5908 : case GF_DASH_SELECT_BANDWIDTH_LOWEST:
5909 12 : if ((rep->width&&rep->height) || !group_has_video) {
5910 12 : if (rep->bandwidth < rep_sel->bandwidth) {
5911 : active_rep = rep_i;
5912 : }
5913 : }
5914 : break;
5915 0 : case GF_DASH_SELECT_QUALITY_HIGHEST:
5916 0 : if (rep->quality_ranking > rep_sel->quality_ranking) {
5917 : active_rep = rep_i;
5918 : break;
5919 : }
5920 : /*fallthrough if quality is not indicated*/
5921 : case GF_DASH_SELECT_BANDWIDTH_HIGHEST:
5922 381 : if (rep->bandwidth > rep_sel->bandwidth) {
5923 : active_rep = rep_i;
5924 : }
5925 : break;
5926 : default:
5927 : break;
5928 : }
5929 : }
5930 393 : for (rep_i = 0; rep_i < nb_rep; rep_i++) {
5931 393 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
5932 393 : if (!rep->playback.disabled)
5933 393 : nb_rep_ok++;
5934 : }
5935 :
5936 262 : if (! nb_rep_ok) {
5937 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] No valid representation in this group - disabling\n"));
5938 0 : group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
5939 0 : continue;
5940 : }
5941 :
5942 262 : rep_sel = gf_list_get(group->adaptation_set->representations, active_rep);
5943 :
5944 262 : gf_dash_set_group_representation(group, rep_sel, GF_FALSE);
5945 262 : if (group->dash->force_period_reload) {
5946 0 : gf_dash_reset_groups(dash);
5947 0 : dash->period_groups_setup = GF_FALSE;
5948 0 : dash->dash_state = GF_DASH_STATE_SETUP;
5949 0 : return GF_OK;
5950 : }
5951 :
5952 : // active representation is marked as disabled, we need to redo the selection
5953 262 : if (rep_sel->playback.disabled)
5954 : goto select_active_rep;
5955 :
5956 : //adjust seek
5957 262 : if (dash->start_range_period) {
5958 2 : gf_dash_seek_group(dash, group, dash->start_range_period, 0);
5959 : }
5960 :
5961 262 : mime_type = gf_dash_get_mime_type(NULL, rep_sel, group->adaptation_set);
5962 :
5963 262 : if (!mime_type) {
5964 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Missing MIME type for AdaptationSet - skipping\n"));
5965 0 : continue;
5966 : }
5967 :
5968 : /* TODO: Generate segment names if urltemplates are used */
5969 262 : if (!rep_sel->segment_base && !rep_sel->segment_list && !rep_sel->segment_template
5970 134 : && !group->adaptation_set->segment_base && !group->adaptation_set->segment_list && !group->adaptation_set->segment_template
5971 0 : && !group->period->segment_base && !group->period->segment_list && !group->period->segment_template
5972 0 : && !gf_list_count(rep_sel->base_URLs)
5973 : ) {
5974 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Segment URLs are not present for AdaptationSet - skipping\n"));
5975 0 : continue;
5976 : }
5977 :
5978 262 : group->selection = GF_DASH_GROUP_NOT_SELECTED;
5979 262 : nb_groups_ok++;
5980 : }
5981 123 : dash->start_range_period = 0;
5982 :
5983 : //setup SRDs
5984 130 : for (as_i = 0; as_i<gf_list_count(dash->SRDs); as_i++) {
5985 : u32 cols[10], rows[10];
5986 7 : struct _dash_srd_desc *srd = gf_list_get(dash->SRDs, as_i);
5987 :
5988 7 : srd->srd_nb_rows = srd->srd_nb_cols = 0;
5989 :
5990 : //sort SRDs
5991 70 : for (j=1; j < gf_list_count(dash->groups); j++) {
5992 63 : GF_DASH_Group *dg2 = gf_list_get(dash->groups, j);
5993 63 : GF_DASH_Group *dg1 = gf_list_get(dash->groups, j-1);
5994 63 : u32 dg1_weight = dg1->srd_y << 16 | dg1->srd_x;
5995 63 : u32 dg2_weight = dg2->srd_y << 16 | dg2->srd_x;
5996 :
5997 63 : if (dg1->srd_desc != srd) continue;
5998 63 : if (dg2->srd_desc != srd) continue;
5999 :
6000 63 : if (dg1_weight > dg2_weight) {
6001 0 : gf_list_rem(dash->groups, j);
6002 0 : gf_list_insert(dash->groups, dg2, j-1);
6003 : j=0;
6004 : }
6005 : }
6006 :
6007 : //groups are now sorted for this srd, locate col/row positions
6008 70 : for (group_i=0; group_i<gf_list_count(dash->groups); group_i++) {
6009 : u32 k;
6010 : Bool found = GF_FALSE;
6011 70 : GF_DASH_Group *group = gf_list_get(dash->groups, group_i);
6012 70 : if (group->srd_desc != srd) continue;
6013 :
6014 70 : if (!group->srd_w || !group->srd_h) continue;
6015 :
6016 63 : for (k=0; k<srd->srd_nb_cols; k++) {
6017 105 : if (cols[k]==group->srd_x) {
6018 : found=GF_TRUE;
6019 : break;
6020 : }
6021 : }
6022 63 : if (!found) {
6023 21 : cols[srd->srd_nb_cols] = group->srd_x;
6024 21 : group->srd_col_idx = srd->srd_nb_cols;
6025 21 : srd->srd_nb_cols++;
6026 :
6027 21 : srd->width += group->adaptation_set->max_width;
6028 :
6029 : } else {
6030 42 : group->srd_col_idx = k;
6031 : }
6032 :
6033 : found = GF_FALSE;
6034 126 : for (k=0; k<srd->srd_nb_rows; k++) {
6035 105 : if (rows[k]==group->srd_y) {
6036 : found=GF_TRUE;
6037 : break;
6038 : }
6039 : }
6040 63 : if (!found) {
6041 21 : rows[srd->srd_nb_rows] = group->srd_y;
6042 21 : group->srd_row_idx = srd->srd_nb_rows;
6043 21 : srd->srd_nb_rows++;
6044 21 : srd->height += group->adaptation_set->max_height;
6045 : } else {
6046 42 : group->srd_row_idx = k;
6047 : }
6048 :
6049 : }
6050 7 : gf_dash_set_tiles_quality(dash, srd, GF_TRUE);
6051 : }
6052 :
6053 123 : period = gf_list_get(dash->mpd->periods, dash->active_period_index);
6054 :
6055 123 : if (period->segment_base) {
6056 0 : gf_mpd_segment_base_free(period->segment_base);
6057 0 : period->segment_base = NULL;
6058 : }
6059 :
6060 123 : if (!nb_groups_ok) {
6061 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] No AdaptationSet could be selected in the MPD - Cannot play\n"));
6062 : return GF_NON_COMPLIANT_BITSTREAM;
6063 : }
6064 :
6065 : /*and seek if needed*/
6066 : return GF_OK;
6067 : }
6068 :
6069 :
6070 1587 : static void gf_dash_group_check_time(GF_DASH_Group *group)
6071 : {
6072 : s64 check_time;
6073 : u32 nb_dropped;
6074 :
6075 1587 : if (group->dash->is_m3u8) return;
6076 1251 : if (! group->timeline_setup) return;
6077 1239 : if (group->broken_timing) return;
6078 :
6079 1239 : check_time = (s64) gf_net_get_utc();
6080 : nb_dropped = 0;
6081 :
6082 : while (1) {
6083 : u32 seg_dur_ms;
6084 1239 : u64 seg_ast = gf_dash_get_segment_availability_start_time(group->dash->mpd, group, group->download_segment_index, &seg_dur_ms);
6085 :
6086 1239 : s64 now = check_time + (s64) seg_dur_ms;
6087 1239 : if (now <= (s64) seg_ast) {
6088 49 : group->dash->tsb_exceeded = (u32) -1;
6089 1288 : return;
6090 : }
6091 :
6092 1190 : now -= (s64) seg_ast;
6093 1190 : if (now <= (s64) seg_dur_ms) {
6094 223 : group->dash->tsb_exceeded = (u32) -1;
6095 223 : return;
6096 : }
6097 967 : if (((s32) group->time_shift_buffer_depth > 0) && (now > group->time_shift_buffer_depth)) {
6098 0 : group->download_segment_index++;
6099 0 : nb_dropped ++;
6100 0 : group->dash->time_in_tsb = 0;
6101 0 : continue;
6102 : }
6103 :
6104 967 : if (nb_dropped > group->dash->tsb_exceeded) {
6105 0 : group->dash->tsb_exceeded = nb_dropped;
6106 : }
6107 :
6108 967 : now -= group->dash->user_buffer_ms;
6109 967 : if (now<0) return;
6110 :
6111 498 : if (now>group->dash->time_in_tsb)
6112 497 : group->dash->time_in_tsb = (u32) now;
6113 : return;
6114 : }
6115 : }
6116 :
6117 : typedef enum
6118 : {
6119 : GF_DASH_DownloadCancel,
6120 : GF_DASH_DownloadRestart,
6121 : GF_DASH_DownloadSuccess,
6122 : } DownloadGroupStatus;
6123 :
6124 :
6125 : static DownloadGroupStatus dash_download_group_download(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following);
6126 :
6127 : static GFINLINE void dash_set_min_wait(GF_DashClient *dash, u32 min_wait)
6128 : {
6129 77 : if (!dash->min_wait_ms_before_next_request || (min_wait < dash->min_wait_ms_before_next_request)) {
6130 54 : dash->min_wait_ms_before_next_request = min_wait;
6131 54 : dash->min_wait_sys_clock = gf_sys_clock();
6132 : }
6133 : }
6134 :
6135 : /*TODO decide what is the best, fetch from another representation or ignore ...*/
6136 139 : static DownloadGroupStatus on_group_download_error(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, GF_Err e, GF_MPD_Representation *rep, char *new_base_seg_url, char *key_url, Bool has_dep_following)
6137 : {
6138 : u32 clock_time;
6139 : Bool will_retry = GF_FALSE;
6140 : Bool is_live = GF_FALSE;
6141 : u32 min_wait;
6142 139 : if (!dash || !group)
6143 : return GF_DASH_DownloadCancel;
6144 :
6145 25 : clock_time = gf_sys_clock();
6146 :
6147 25 : min_wait = dash->min_timeout_between_404;
6148 25 : if (dash->route_clock_state) {
6149 2 : if (!group->period->origin_base_url)
6150 : min_wait = 50; //50 ms between retries if route and not a remote period
6151 : }
6152 :
6153 : dash_set_min_wait(dash, min_wait);
6154 :
6155 25 : group->retry_after_utc = min_wait + gf_net_get_utc();
6156 25 : if (!group->period->origin_base_url && (dash->mpd->type==GF_MPD_TYPE_DYNAMIC))
6157 : is_live = GF_TRUE;
6158 :
6159 25 : if (e==GF_REMOTE_SERVICE_ERROR) {
6160 0 : gf_dash_mark_group_done(group);
6161 : }
6162 : //failure on last segment in non dynamic mode: likely due to rounding in dash segment duration, assume no error
6163 : //in dynamic mode, we need to check if this is a download schedule issue
6164 25 : else if (!is_live && group->period->duration && (group->download_segment_index + 1 >= (s32) group->nb_segments_in_rep) ) {
6165 2 : gf_dash_mark_group_done(group);
6166 : }
6167 23 : else if (group->maybe_end_of_stream) {
6168 0 : if (group->maybe_end_of_stream==2) {
6169 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Couldn't get segment %s (error %s) and end of period was guessed during last update - stopping playback\n", new_base_seg_url, gf_error_to_string(e)));
6170 0 : group->maybe_end_of_stream = 0;
6171 0 : gf_dash_mark_group_done(group);
6172 : }
6173 0 : group->maybe_end_of_stream++;
6174 23 : } else if (group->segment_in_valid_range) {
6175 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in downloading new segment: %s %s - segment was lost at server/proxy side\n", new_base_seg_url, gf_error_to_string(e)));
6176 0 : if (dash->speed >= 0) {
6177 0 : group->download_segment_index++;
6178 0 : } else if (group->download_segment_index) {
6179 0 : group->download_segment_index--;
6180 : } else {
6181 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
6182 0 : gf_dash_mark_group_done(group);
6183 : }
6184 0 : group->segment_in_valid_range=0;
6185 : }
6186 : //ROUTE case, the file was removed from cache by the route demuxer
6187 23 : else if (e==GF_URL_REMOVED) {
6188 0 : if (dash->speed >= 0) {
6189 0 : group->download_segment_index++;
6190 0 : } else if (group->download_segment_index) {
6191 0 : group->download_segment_index--;
6192 : }
6193 23 : } else if (group->prev_segment_ok && !group->time_at_first_failure && !group->loop_detected) {
6194 4 : group->time_at_first_failure = clock_time;
6195 4 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Error in downloading new segment: %s %s - starting countdown for %d ms (delay between retry %d ms)\n", new_base_seg_url, gf_error_to_string(e), group->current_downloaded_segment_duration, min_wait));
6196 :
6197 : will_retry = GF_TRUE;
6198 : }
6199 : //if multiple baseURL, try switching the base
6200 19 : else if ((e==GF_URL_ERROR) && (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) )) {
6201 0 : group->current_base_url_idx++;
6202 0 : if (new_base_seg_url) gf_free(new_base_seg_url);
6203 0 : if (key_url) gf_free(key_url);
6204 0 : return dash_download_group_download(dash, group, base_group, has_dep_following);
6205 : }
6206 : //if previous segment download was OK, we are likely asking too early - retry for the complete duration in case one segment was lost - we add some default safety safety
6207 19 : else if (group->prev_segment_ok && (clock_time - group->time_at_first_failure <= group->current_downloaded_segment_duration + dash->segment_lost_after_ms )) {
6208 : will_retry = GF_TRUE;
6209 : } else {
6210 9 : if ((group->dash->route_clock_state==2) && (e==GF_URL_ERROR)) {
6211 0 : const char *val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-loop");
6212 0 : Bool is_loop = (val && !strcmp(val, "yes")) ? GF_TRUE : GF_FALSE;
6213 : //if explicit loop or more than 5 consecutive seg lost restart synchro
6214 0 : if ((group->nb_consecutive_segments_lost >= 5) || is_loop) {
6215 0 : u32 i=0;
6216 0 : if (is_loop) {
6217 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ROUTE loop detected, reseting timeline\n"));
6218 : } else {
6219 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ROUTE lost %d consecutive segments, resetup tune-in\n", group->nb_consecutive_segments_lost));
6220 : }
6221 0 : dash->utc_drift_estimate = 0;
6222 0 : dash->initial_period_tunein = GF_TRUE;
6223 0 : dash->route_clock_state = 1;
6224 0 : while ((group = gf_list_enum(dash->groups, &i))) {
6225 0 : group->start_number_at_last_ast = 0;
6226 0 : gf_dash_group_timeline_setup(dash->mpd, group, 0);
6227 0 : group->loop_detected = is_loop;
6228 0 : group->time_at_first_failure = 0;
6229 0 : group->prev_segment_ok = GF_TRUE;
6230 : }
6231 0 : if (new_base_seg_url) gf_free(new_base_seg_url);
6232 0 : if (key_url) gf_free(key_url);
6233 : return GF_DASH_DownloadCancel;
6234 : }
6235 : }
6236 9 : if (group->prev_segment_ok) {
6237 3 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in downloading new segment %s: %s - waited %d ms but segment still not available, checking next one ...\n", new_base_seg_url, gf_error_to_string(e), clock_time - group->time_at_first_failure));
6238 3 : group->time_at_first_failure = 0;
6239 : //for route we still consider the previous segment valid and don't attempt to resync the timeline
6240 3 : if (!group->dash->route_clock_state)
6241 3 : group->prev_segment_ok = GF_FALSE;
6242 : }
6243 9 : group->nb_consecutive_segments_lost ++;
6244 :
6245 : //we are lost ....
6246 9 : if (group->nb_consecutive_segments_lost == 20) {
6247 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Too many consecutive segments not found, sync or signal has been lost - entering end of stream detection mode\n"));
6248 : dash_set_min_wait(dash, 1000);
6249 0 : group->maybe_end_of_stream = 1;
6250 : } else {
6251 9 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in downloading new segment %s: %s\n", new_base_seg_url, gf_error_to_string(e)));
6252 9 : if (dash->speed >= 0) {
6253 9 : group->download_segment_index++;
6254 0 : } else if (group->download_segment_index) {
6255 0 : group->download_segment_index--;
6256 : } else {
6257 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
6258 0 : gf_dash_mark_group_done(group);
6259 : }
6260 : }
6261 : }
6262 : //if retry, do not reset dependency status
6263 : if (!will_retry) {
6264 11 : if (rep->dependency_id) {
6265 0 : segment_cache_entry *cache_entry = &base_group->cached[base_group->nb_cached_segments];
6266 0 : cache_entry->flags &= ~SEG_FLAG_DEP_FOLLOWING;
6267 : }
6268 :
6269 11 : if (group->base_rep_index_plus_one) {
6270 0 : group->active_rep_index = group->base_rep_index_plus_one - 1;
6271 0 : group->has_pending_enhancement = GF_FALSE;
6272 : }
6273 : }
6274 :
6275 25 : if (new_base_seg_url) gf_free(new_base_seg_url);
6276 25 : if (key_url) gf_free(key_url);
6277 : return GF_DASH_DownloadCancel;
6278 : }
6279 :
6280 5014 : static DownloadGroupStatus dash_download_group_download(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following)
6281 : {
6282 : //commented out as we end up doing too many requets
6283 : GF_Err e;
6284 : GF_MPD_Representation *rep;
6285 5014 : char *new_base_seg_url=NULL;
6286 5014 : char *key_url=NULL;
6287 : bin128 key_iv;
6288 : u64 start_range, end_range;
6289 : Bool use_byterange;
6290 : u32 llhls_live_edge_type=0;
6291 : u32 representation_index;
6292 : u32 clock_time;
6293 : Bool remote_file = GF_FALSE;
6294 : const char *base_url = NULL;
6295 5014 : u32 start_number=0;
6296 : u64 seg_dur;
6297 : u32 seg_scale;
6298 : segment_cache_entry *cache_entry;
6299 :
6300 5014 : GF_MPD_Type dyn_type = dash->mpd->type;
6301 5014 : if (group->period->origin_base_url)
6302 0 : dyn_type = group->period->type;
6303 :
6304 5014 : if (group->done)
6305 : return GF_DASH_DownloadSuccess;
6306 4766 : if (!base_group)
6307 : return GF_DASH_DownloadSuccess;
6308 :
6309 : //we are waiting for the playlist to be updated to find the next segment to play
6310 4766 : if (group->hls_next_seq_num) {
6311 : return GF_DASH_DownloadCancel;
6312 : }
6313 :
6314 4617 : if (group->selection != GF_DASH_GROUP_SELECTED) return GF_DASH_DownloadSuccess;
6315 :
6316 4617 : if (base_group->nb_cached_segments>=base_group->max_cached_segments) {
6317 : return GF_DASH_DownloadCancel;
6318 : }
6319 3890 : if (!group->timeline_setup) {
6320 248 : gf_dash_group_timeline_setup(dash->mpd, group, 0);
6321 248 : group->timeline_setup = GF_TRUE;
6322 : }
6323 :
6324 : /*remember the active rep index, since group->active_rep_index may change because of bandwidth control algorithm*/
6325 3890 : representation_index = group->active_rep_index;
6326 3890 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
6327 3890 : rep->playback.broadcast_flag = GF_FALSE;
6328 :
6329 3890 : llhls_rety:
6330 : //special case for LL-HLS: if we have a switch request pending, check if next fragment is the first of a new seg
6331 : //or a complete seg (we do not switch in the middle of a segment)
6332 3890 : if (group->llhls_switch_request>=0) {
6333 0 : GF_MPD_SegmentURL *hlsseg = gf_list_get(rep->segment_list->segment_URLs, group->download_segment_index);
6334 0 : if (hlsseg && (! hlsseg->hls_ll_chunk_type || hlsseg->is_first_part)) {
6335 0 : rep = gf_list_get(group->adaptation_set->representations, group->llhls_switch_request);
6336 0 : group->llhls_edge_chunk = NULL;
6337 0 : gf_dash_set_group_representation(group, rep, GF_TRUE);
6338 : assert(group->llhls_switch_request<0);
6339 : //we are waiting for playlist update, return
6340 0 : if (group->hls_next_seq_num) {
6341 : return GF_DASH_DownloadCancel;
6342 : }
6343 : //otherwise new rep is set
6344 0 : representation_index = group->active_rep_index;
6345 0 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
6346 0 : rep->playback.broadcast_flag = GF_FALSE;
6347 : }
6348 : }
6349 :
6350 : /* if the index of the segment to be downloaded is greater or equal to the last segment (as seen in the playlist),
6351 : we need to check if a new playlist is ready */
6352 3890 : if (group->nb_segments_in_rep && (group->download_segment_index >= (s32) group->nb_segments_in_rep)) {
6353 772 : u32 timer = gf_sys_clock() - dash->last_update_time;
6354 : Bool update_playlist = 0;
6355 :
6356 : /* this period is done*/
6357 772 : if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && group->period->duration) {
6358 : }
6359 : /* update of the playlist, only if indicated */
6360 772 : else if (dash->mpd->minimum_update_period && (timer > dash->mpd->minimum_update_period)) {
6361 : update_playlist = 1;
6362 : }
6363 : /* if media_presentation_duration is 0 and we are in live, force a refresh (not in the spec but safety check*/
6364 772 : else if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && !dash->mpd->media_presentation_duration) {
6365 680 : if (group->segment_duration && (timer > group->segment_duration*1000))
6366 : update_playlist = 1;
6367 : }
6368 : if (update_playlist) {
6369 1 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playlist should be updated, postponing group download until playlist is updated\n"));
6370 1 : dash->force_mpd_update = GF_TRUE;
6371 1 : return GF_DASH_DownloadCancel;
6372 : }
6373 : /* Now that the playlist is up to date, we can check again */
6374 771 : if (group->download_segment_index >= (s32) group->nb_segments_in_rep) {
6375 : /* if there is a specified update period, we redo the whole process */
6376 771 : if (dash->mpd->minimum_update_period || dyn_type==GF_MPD_TYPE_DYNAMIC) {
6377 :
6378 679 : if (dyn_type==GF_MPD_TYPE_STATIC) {
6379 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Last segment in static period (dynamic MPD) - group is done\n"));
6380 0 : gf_dash_mark_group_done(group);
6381 0 : return GF_DASH_DownloadCancel;
6382 : }
6383 679 : else if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && group->period->duration) {
6384 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Last segment in period (dynamic mode) - group is done\n"));
6385 0 : gf_dash_mark_group_done(group);
6386 0 : return GF_DASH_DownloadCancel;
6387 : }
6388 679 : else if (! group->maybe_end_of_stream) {
6389 679 : u32 now = gf_sys_clock();
6390 679 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] End of segment list reached (%d segments but idx is %d), waiting for next MPD update\n", group->nb_segments_in_rep, group->download_segment_index));
6391 :
6392 679 : if (group->nb_cached_segments) {
6393 : return GF_DASH_DownloadCancel;
6394 : }
6395 :
6396 679 : if (dash->is_m3u8 && (dyn_type==GF_MPD_TYPE_DYNAMIC)) {
6397 11 : if (!group->time_at_first_reload_required)
6398 8 : group->time_at_first_reload_required = now;
6399 :
6400 : //use group last modification time
6401 11 : timer = now - group->last_mpd_change_time;
6402 11 : if (timer < group->segment_duration * 2000) {
6403 : //no more segment, force a manifest update now
6404 11 : dash->force_mpd_update = GF_TRUE;
6405 : } else {
6406 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] HLS Segment list has not been updated for more than %d ms - assuming end of session\n", now - group->time_at_first_reload_required));
6407 0 : gf_dash_mark_group_done(group);
6408 : }
6409 : return GF_DASH_DownloadCancel;
6410 : }
6411 :
6412 668 : if (!group->time_at_first_reload_required) {
6413 5 : group->time_at_first_reload_required = now;
6414 5 : return GF_DASH_DownloadCancel;
6415 : }
6416 663 : if (now - group->time_at_first_reload_required < group->cache_duration)
6417 : return GF_DASH_DownloadCancel;
6418 1 : if (dash->mpd->minimum_update_period) {
6419 1 : if (now - group->time_at_first_reload_required < dash->mpd->minimum_update_period)
6420 : return GF_DASH_DownloadCancel;
6421 0 : } else if (dyn_type==GF_MPD_TYPE_DYNAMIC) {
6422 : //use group last modification time
6423 0 : timer = now - group->last_mpd_change_time;
6424 0 : if (timer < 2 * group->segment_duration * 2000)
6425 : return GF_DASH_DownloadCancel;
6426 : }
6427 :
6428 1 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Segment list has not been updated for more than %d ms - assuming end of period\n", now - group->time_at_first_reload_required));
6429 1 : gf_dash_mark_group_done(group);
6430 1 : return GF_DASH_DownloadCancel;
6431 : }
6432 : } else {
6433 : /* if not, we are really at the end of the playlist, we can quit */
6434 92 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] End of period reached for group\n"));
6435 92 : gf_dash_mark_group_done(group);
6436 92 : if (!dash->request_period_switch && !group->has_pending_enhancement && !has_dep_following)
6437 87 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, gf_list_find(dash->groups, base_group), GF_OK);
6438 : return GF_DASH_DownloadCancel;
6439 : }
6440 : }
6441 : }
6442 3118 : group->time_at_first_reload_required = 0;
6443 :
6444 3118 : if (group->force_switch_bandwidth && !dash->auto_switch_count) {
6445 2 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Forcing representation switch, retesting group"));
6446 2 : gf_dash_switch_group_representation(dash, group);
6447 : /*restart*/
6448 2 : return GF_DASH_DownloadRestart;
6449 : }
6450 :
6451 : /*check availablity start time of segment in Live !!*/
6452 3116 : if (!group->broken_timing && (dyn_type==GF_MPD_TYPE_DYNAMIC) && !dash->is_m3u8 && !dash->is_smooth) {
6453 : s32 to_wait = 0;
6454 126 : u32 seg_dur_ms=0;
6455 126 : s64 segment_ast = (s64) gf_dash_get_segment_availability_start_time(dash->mpd, group, group->download_segment_index, &seg_dur_ms);
6456 126 : s64 now = (s64) gf_net_get_utc();
6457 : #ifndef GPAC_DISABLE_LOG
6458 126 : start_number = gf_dash_get_start_number(group, rep);
6459 : #endif
6460 :
6461 :
6462 126 : if (group->retry_after_utc > (u64) now) {
6463 8 : to_wait = (u32) (group->retry_after_utc - (u64) now);
6464 : dash_set_min_wait(dash, (u32) to_wait);
6465 :
6466 52 : return GF_DASH_DownloadCancel;
6467 : }
6468 :
6469 118 : clock_time = gf_sys_clock();
6470 118 : to_wait = (s32) (segment_ast - now);
6471 :
6472 118 : if (group->force_early_fetch) {
6473 2 : if (to_wait>1) {
6474 2 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Set #%d demux empty but wait time for segment %d is still %d ms, forcing scheduling\n", 1+gf_list_find(dash->groups, group), group->download_segment_index + start_number, to_wait));
6475 : to_wait = 0;
6476 : } else {
6477 : //we officially reached segment AST
6478 0 : group->force_early_fetch = GF_FALSE;
6479 : }
6480 : }
6481 :
6482 : /*if segment AST is greater than now, it is not yet available - we would need an estimate on how long the request takes to be sent to the server in order to be more reactive ...*/
6483 116 : if (to_wait > 1) {
6484 44 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Set #%d At %d Next segment %d (AST "LLD" - sec in period %g) is not yet available on server - requesting later in %d ms\n", 1+gf_list_find(dash->groups, group), gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init + group->ast_offset)/1000.0, to_wait));
6485 44 : if (group->last_segment_time) {
6486 28 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time));
6487 : }
6488 :
6489 : dash_set_min_wait(dash, (u32) to_wait);
6490 :
6491 : return GF_DASH_DownloadCancel;
6492 : } else {
6493 74 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Set #%d At %d Next segment %d (AST "LLD" - sec in period %g) should now be available on server since %d ms - requesting it\n", 1+gf_list_find(dash->groups, group), gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init + group->ast_offset)/1000.0, -to_wait));
6494 :
6495 74 : if (group->last_segment_time) {
6496 48 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time));
6497 : }
6498 : }
6499 74 : group->time_at_last_request = gf_sys_clock();
6500 : }
6501 :
6502 3064 : base_url = dash->base_url;
6503 3064 : if (group->period->origin_base_url) base_url = group->period->origin_base_url;
6504 : /* At this stage, there are some segments left to be downloaded */
6505 3064 : e = gf_dash_resolve_url(dash->mpd, rep, group, base_url, GF_MPD_RESOLVE_URL_MEDIA, group->download_segment_index, &new_base_seg_url, &start_range, &end_range, &group->current_downloaded_segment_duration, NULL, &key_url, &key_iv, NULL, &start_number);
6506 :
6507 :
6508 3064 : if ((e==GF_EOS) && group->llhls_edge_chunk && group->llhls_edge_chunk->hls_ll_chunk_type) {
6509 : //no more segments, force update now
6510 0 : dash->force_mpd_update = GF_TRUE;
6511 0 : return GF_DASH_DownloadCancel;
6512 : }
6513 :
6514 3064 : if (e || !new_base_seg_url) {
6515 8 : if (e==GF_EOS) {
6516 8 : gf_dash_mark_group_done(group);
6517 : } else {
6518 : /*do something!!*/
6519 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error resolving URL of next segment: %s\n", gf_error_to_string(e) ));
6520 : }
6521 8 : if (new_base_seg_url) gf_free(new_base_seg_url);
6522 8 : if (key_url) gf_free(key_url);
6523 8 : group->llhls_edge_chunk = NULL;
6524 8 : return GF_DASH_DownloadCancel;
6525 : }
6526 :
6527 3056 : if (dash->is_m3u8 && group->is_low_latency) {
6528 : assert(rep->segment_list);
6529 42 : GF_MPD_SegmentURL *hlsseg = gf_list_get(rep->segment_list->segment_URLs, group->download_segment_index);
6530 : assert(hlsseg);
6531 :
6532 42 : if (dash->llhls_single_range && hlsseg->media_range && (hlsseg->can_merge || group->llhls_last_was_merged) ) {
6533 : //if not very first request (tune in) and not first part of seg, if mergeable issue a single byterange
6534 1 : if (!group->first_hls_chunk && hlsseg->media_range->start_range) {
6535 0 : if (!hlsseg->can_merge) {
6536 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] LL-HLS part cannot be merged with previously open byte-range request, disabling merging !\n"));
6537 0 : dash->llhls_single_range = GF_FALSE;
6538 : }
6539 0 : group->download_segment_index++;
6540 0 : if (new_base_seg_url) gf_free(new_base_seg_url);
6541 0 : new_base_seg_url = NULL;
6542 0 : if (key_url) gf_free(key_url);
6543 0 : key_url = NULL;
6544 0 : goto llhls_rety;
6545 : }
6546 1 : group->first_hls_chunk = GF_FALSE;
6547 1 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH]Â Changing LL-HLS request %s @ "LLU"->"LLU" to open end range\n", new_base_seg_url, start_range, end_range));
6548 1 : end_range = (u64) -1;
6549 : llhls_live_edge_type = 2;
6550 1 : group->llhls_last_was_merged = GF_TRUE;
6551 : } else {
6552 41 : group->llhls_last_was_merged = GF_FALSE;
6553 41 : if (hlsseg->hls_ll_chunk_type)
6554 : llhls_live_edge_type = 1;
6555 : }
6556 42 : group->llhls_edge_chunk = hlsseg;
6557 : }
6558 3056 : use_byterange = (start_range || end_range) ? 1 : 0;
6559 :
6560 : #ifndef GPAC_DISABLE_LOG
6561 3056 : if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
6562 53 : if (llhls_live_edge_type==2) {
6563 1 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Queing next segment: %s (live edge merged range: "LLU" -> END)\n", gf_file_basename(new_base_seg_url), start_range));
6564 52 : } else if (use_byterange) {
6565 28 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Queing next %s: %s (range: "LLU" -> "LLU")\n", (llhls_live_edge_type==1) ? "LL-HLS part" : "segment", gf_file_basename(new_base_seg_url), start_range, end_range));
6566 : } else {
6567 24 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Queing next %s: %s\n", (llhls_live_edge_type==1) ? "LL-HLS part" : "segment", gf_file_basename(new_base_seg_url)));
6568 : }
6569 : }
6570 : #endif
6571 :
6572 : /*local file*/
6573 3056 : if (strnicmp(base_url, "gfio://", 7)
6574 3047 : && (!strstr(new_base_seg_url, "://") || (!strnicmp(new_base_seg_url, "file://", 7) || !strnicmp(new_base_seg_url, "gmem://", 7) ) )
6575 : ) {
6576 : e = GF_OK;
6577 : /*do not erase local files*/
6578 2529 : group->local_files = 1;
6579 2529 : if (group->force_switch_bandwidth && !dash->auto_switch_count) {
6580 0 : if (new_base_seg_url) gf_free(new_base_seg_url);
6581 0 : gf_dash_switch_group_representation(dash, group);
6582 : /*restart*/
6583 0 : return GF_DASH_DownloadRestart;
6584 : }
6585 2529 : if (! gf_file_exists(new_base_seg_url)) {
6586 20 : if (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) ){
6587 0 : group->current_base_url_idx++;
6588 0 : if (new_base_seg_url) gf_free(new_base_seg_url);
6589 0 : if (key_url) gf_free(key_url);
6590 0 : return dash_download_group_download(dash, group, base_group, has_dep_following);
6591 20 : } else if (group->period->duration && (group->download_segment_index + 1 == group->nb_segments_in_rep) ) {
6592 4 : if (new_base_seg_url) gf_free(new_base_seg_url);
6593 4 : gf_dash_mark_group_done(group);
6594 4 : return GF_DASH_DownloadCancel;
6595 : } else {
6596 16 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] File %s not found on disk\n", new_base_seg_url));
6597 16 : group->current_base_url_idx = 0;
6598 16 : return on_group_download_error(dash, group, base_group, GF_NOT_FOUND, rep, new_base_seg_url, key_url, has_dep_following);
6599 : }
6600 : }
6601 2509 : group->current_base_url_idx = 0;
6602 : } else {
6603 : remote_file = GF_TRUE;
6604 : }
6605 :
6606 :
6607 : assert(base_group->nb_cached_segments<base_group->max_cached_segments);
6608 3036 : cache_entry = &base_group->cached[base_group->nb_cached_segments];
6609 :
6610 : //assign url
6611 3036 : cache_entry->url = new_base_seg_url;
6612 3036 : if (use_byterange && remote_file) {
6613 34 : cache_entry->start_range = start_range;
6614 34 : cache_entry->end_range = end_range;
6615 : } else {
6616 3002 : cache_entry->start_range = 0;
6617 3002 : cache_entry->end_range = 0;
6618 : }
6619 3036 : cache_entry->representation_index = representation_index;
6620 3036 : cache_entry->duration = (u32) group->current_downloaded_segment_duration;
6621 3036 : cache_entry->flags = group->loop_detected ? SEG_FLAG_LOOP_DETECTED : 0;
6622 3036 : if (has_dep_following) cache_entry->flags |= SEG_FLAG_DEP_FOLLOWING;
6623 3036 : if (group->disabled)
6624 0 : cache_entry->flags |= SEG_FLAG_DISABLED;
6625 3036 : if (key_url) {
6626 6 : cache_entry->key_url = key_url;
6627 6 : memcpy(cache_entry->key_IV, key_iv, sizeof(bin128));
6628 : //set to NULL since we stored it, so that it won't be freed when exiting this function
6629 6 : key_url = NULL;
6630 : }
6631 :
6632 3036 : cache_entry->time.num = gf_dash_get_segment_start_time_with_timescale(group, &seg_dur, &seg_scale);
6633 3036 : cache_entry->time.den = seg_scale;
6634 :
6635 3036 : cache_entry->seg_number = group->download_segment_index + start_number;
6636 3036 : cache_entry->seg_name_start = dash_strip_base_url(cache_entry->url, base_url);
6637 3036 : group->loop_detected = GF_FALSE;
6638 :
6639 3036 : if (group->local_files && use_byterange) {
6640 291 : cache_entry->start_range = start_range;
6641 291 : cache_entry->end_range = end_range;
6642 : }
6643 3036 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Added file to cache (%u/%u in cache): %s\n", base_group->nb_cached_segments+1, base_group->max_cached_segments, cache_entry->url));
6644 :
6645 3036 : base_group->nb_cached_segments++;
6646 :
6647 : /* download enhancement representation of this segment*/
6648 3036 : if ((representation_index != group->max_complementary_rep_index) && rep->playback.enhancement_rep_index_plus_one) {
6649 0 : group->active_rep_index = rep->playback.enhancement_rep_index_plus_one - 1;
6650 0 : group->has_pending_enhancement = GF_TRUE;
6651 : }
6652 : /* if we have downloaded all enhancement representations of this segment, restart from base representation and increase downloaded segment index by 1*/
6653 : else {
6654 3036 : if (group->base_rep_index_plus_one) group->active_rep_index = group->base_rep_index_plus_one - 1;
6655 3036 : if (dash->speed >= 0) {
6656 3036 : group->download_segment_index++;
6657 0 : } else if (group->download_segment_index) {
6658 0 : group->download_segment_index--;
6659 : } else {
6660 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
6661 0 : gf_dash_mark_group_done(group);
6662 : }
6663 3036 : group->has_pending_enhancement = GF_FALSE;
6664 : }
6665 3036 : if (dash->auto_switch_count) {
6666 120 : if (group->llhls_edge_chunk && group->llhls_edge_chunk->hls_ll_chunk_type) {
6667 0 : if (group->llhls_edge_chunk->is_first_part)
6668 0 : group->nb_segments_done++;
6669 : } else {
6670 120 : group->nb_segments_done++;
6671 : }
6672 120 : if (group->nb_segments_done==dash->auto_switch_count) {
6673 120 : group->nb_segments_done=0;
6674 120 : gf_dash_skip_disabled_representation(group, rep, GF_TRUE);
6675 : }
6676 : }
6677 :
6678 : //do not notify segments if there is a pending period switch - since these are decided by the user, we don't
6679 : //want to notify old segments
6680 3036 : if (!dash->request_period_switch && !group->has_pending_enhancement && !has_dep_following)
6681 1587 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, gf_list_find(dash->groups, base_group), GF_OK);
6682 :
6683 : //do NOT free new_base_seg_url, it is now in cache_entry->url
6684 3036 : if (key_url) gf_free(key_url);
6685 : if (e) return GF_DASH_DownloadCancel;
6686 : return GF_DASH_DownloadSuccess;
6687 : }
6688 :
6689 :
6690 5014 : static DownloadGroupStatus dash_download_group(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following)
6691 : {
6692 : DownloadGroupStatus res;
6693 :
6694 5014 : if (!group->current_dep_idx) {
6695 5014 : res = dash_download_group_download(dash, group, base_group, has_dep_following);
6696 5014 : if (res==GF_DASH_DownloadRestart) return res;
6697 5012 : if (res==GF_DASH_DownloadCancel) return res;
6698 3284 : group->current_dep_idx = 1;
6699 : }
6700 :
6701 3284 : if (group->groups_depending_on) {
6702 161 : u32 i, count = gf_list_count(group->groups_depending_on);
6703 161 : i = group->current_dep_idx - 1;
6704 1610 : for (; i<count; i++) {
6705 :
6706 1449 : GF_DASH_Group *dep_group = gf_list_get(group->groups_depending_on, i);
6707 1449 : if ((i+1==count) && !dep_group->groups_depending_on)
6708 : has_dep_following = GF_FALSE;
6709 :
6710 1449 : res = dash_download_group(dash, dep_group, base_group, has_dep_following);
6711 1449 : if (res==GF_DASH_DownloadRestart) {
6712 0 : i--;
6713 0 : continue;
6714 : }
6715 1449 : group->current_dep_idx = i + 1;
6716 1449 : if (res==GF_DASH_DownloadCancel)
6717 : return GF_DASH_DownloadCancel;
6718 : }
6719 : }
6720 3284 : group->current_dep_idx = 0;
6721 3284 : return GF_DASH_DownloadSuccess;
6722 : }
6723 :
6724 : //tile based adaptation
6725 1556 : static void dash_global_rate_adaptation(GF_DashClient *dash, Bool for_postponed_only)
6726 : {
6727 : u32 quality_rank;
6728 : u32 min_bandwidth = 0;
6729 : Bool force_rep_idx = GF_FALSE;
6730 : Bool local_file_mode = GF_FALSE;
6731 : Bool use_custom_algo = GF_FALSE;
6732 : GF_MPD_Representation *rep, *rep_new;
6733 : u32 total_rate, max_fsize, bandwidths[20], groups_per_quality[20], max_level;
6734 : u32 q_idx, nb_qualities = 0;
6735 1556 : u32 i, count = gf_list_count(dash->groups), local_files = 0;
6736 :
6737 : //initialize min/max bandwidth
6738 : min_bandwidth = 0;
6739 : max_level = 0;
6740 : total_rate = (u32) -1;
6741 : nb_qualities = 1;
6742 : max_fsize = 0;
6743 :
6744 : //get max qualities due to SRD descriptions
6745 : //for now, consider all non-SRDs group to run in max quality
6746 1716 : for (i=0; i<gf_list_count(dash->SRDs); i++) {
6747 160 : struct _dash_srd_desc *srd = gf_list_get(dash->SRDs, i);
6748 160 : u32 nb_q = MAX(srd->srd_nb_cols, srd->srd_nb_rows);
6749 160 : if (nb_q > nb_qualities) nb_qualities = nb_q;
6750 : }
6751 :
6752 : //estimate bitrate
6753 3091 : for (i=0; i<count; i++) {
6754 3595 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
6755 3595 : if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6756 3519 : if (group->local_files) local_files ++;
6757 3519 : if (!group->bytes_per_sec) {
6758 505 : if (!for_postponed_only && !group->disabled)
6759 511 : return;
6760 1 : continue;
6761 : }
6762 3014 : if (group->done) continue;
6763 :
6764 : //change of tile qualities
6765 2926 : if (group->update_tile_qualities) {
6766 2 : group->update_tile_qualities = GF_FALSE;
6767 2 : if (!dash->rate_adaptation_algo_custom) {
6768 2 : if (group->srd_desc)
6769 2 : gf_dash_set_tiles_quality(dash, group->srd_desc, GF_FALSE);
6770 : }
6771 : }
6772 :
6773 :
6774 2926 : group->backup_Bps = group->bytes_per_sec;
6775 : //only count broadband ones
6776 2926 : if (dash->route_clock_state && !gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs) && !group->period->origin_base_url) {
6777 : u32 j;
6778 : //get all active rep, count bandwidth for broadband ones
6779 8 : for (j=0; j<=group->max_complementary_rep_index; j++) {
6780 8 : rep = gf_list_get(group->adaptation_set->representations, j);
6781 : //this rep is not in broadcast, add bandwidth
6782 8 : if (gf_list_count(rep->base_URLs)) {
6783 0 : total_rate = group->bytes_per_sec;
6784 : }
6785 : }
6786 : } else {
6787 : //use rate of largest downloaded file to perform rate adaptation
6788 : //TODO: we should split rate adaptation per baseURL
6789 2918 : if (!max_fsize || (max_fsize<group->total_size)) {
6790 2702 : if (total_rate > group->bytes_per_sec) {
6791 : total_rate = group->bytes_per_sec;
6792 1209 : max_fsize = group->total_size;
6793 : }
6794 : }
6795 : }
6796 : }
6797 :
6798 1052 : if (total_rate == (u32) -1) {
6799 : total_rate = 0;
6800 : }
6801 1052 : if (local_files==count) {
6802 796 : total_rate = dash->dash_io->get_bytes_per_sec ? dash->dash_io->get_bytes_per_sec(dash->dash_io, NULL) : 0;
6803 0 : if (!total_rate) local_file_mode = GF_TRUE;
6804 256 : } else if (!total_rate) {
6805 : return;
6806 : }
6807 :
6808 1045 : if (dash->rate_adaptation_algo_custom) {
6809 : use_custom_algo = GF_TRUE;
6810 23 : dash->total_rate = total_rate;
6811 23 : goto custom_algo;
6812 : }
6813 :
6814 1342 : for (q_idx=0; q_idx<nb_qualities; q_idx++) {
6815 1342 : bandwidths[q_idx] = 0;
6816 1342 : groups_per_quality[q_idx] = 0;
6817 :
6818 7394 : for (i=0; i<count; i++) {
6819 6052 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
6820 6052 : if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6821 5978 : if (group->done) continue;
6822 :
6823 5894 : quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
6824 5894 : if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
6825 5894 : if (quality_rank != q_idx) continue;
6826 :
6827 2694 : group->target_new_rep = 0;
6828 2694 : rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
6829 2694 : bandwidths[q_idx] += rep->bandwidth;
6830 2694 : groups_per_quality[q_idx] ++;
6831 2694 : if (max_level < 1 + quality_rank) max_level = 1+quality_rank;
6832 :
6833 : //quick trick here: if no download cap in local playback, compute the total rate based on
6834 : //quality rank
6835 2694 : if (local_file_mode) {
6836 2376 : u32 nb_reps = gf_list_count(group->adaptation_set->representations);
6837 : //get rep matching the given quality rank - quality 0 is the highest and our
6838 : //reps are sorted from lowest !
6839 : u32 rep_target;
6840 2376 : if (!quality_rank)
6841 2370 : rep_target = nb_reps-1;
6842 : else
6843 6 : rep_target = (nb_qualities - quality_rank) * nb_reps / nb_qualities;
6844 :
6845 2376 : rep = gf_list_get(group->adaptation_set->representations, rep_target);
6846 2376 : total_rate += rep->bandwidth;
6847 : }
6848 : }
6849 1342 : min_bandwidth += bandwidths[q_idx];
6850 : }
6851 1022 : if (local_file_mode) {
6852 : //total rate is in bytes per second, and add a safety of 10 bytes to ensure selection
6853 796 : total_rate = 10 + total_rate / 8;
6854 : }
6855 :
6856 : /*no per-quality adaptation, we may have oscillations*/
6857 1022 : if (!dash->tile_rate_decrease) {
6858 : }
6859 : /*automatic rate alloc*/
6860 1022 : else if (dash->tile_rate_decrease==100) {
6861 : //for each quality level (starting from highest priority), increase the bitrate if possible
6862 1026 : for (q_idx=0; q_idx < max_level; q_idx++) {
6863 : Bool test_pass = GF_TRUE;
6864 1292 : while (1) {
6865 : u32 nb_rep_increased = 0;
6866 : u32 nb_rep_in_qidx = 0;
6867 : u32 cumulated_bw_in_pass = 0;
6868 :
6869 8776 : for (i=0; i<count; i++) {
6870 : u32 diff;
6871 6458 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
6872 6458 : if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6873 5958 : if (group->done) continue;
6874 :
6875 5760 : quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
6876 5760 : if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
6877 5760 : if (quality_rank != q_idx) continue;
6878 :
6879 5714 : if (group->target_new_rep + 1 == gf_list_count(group->adaptation_set->representations))
6880 3228 : continue;
6881 :
6882 : nb_rep_in_qidx++;
6883 :
6884 2486 : rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
6885 2486 : diff = rep->bandwidth;
6886 2486 : rep_new = gf_list_get(group->adaptation_set->representations, group->target_new_rep+1);
6887 2486 : diff = rep_new->bandwidth - diff;
6888 :
6889 2486 : if (dash->route_clock_state) {
6890 : //if baseURL in period or adaptation set, we assume we are in broadband mode, otherwise we re in broadcast, don't count bitrate
6891 0 : if (!gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs)) {
6892 : //new rep is in broadcast, force diff to 0 to select the rep
6893 0 : if (!gf_list_count(rep_new->base_URLs)) {
6894 : diff = 0;
6895 : }
6896 : //new rep is in broadband, prev rep is in broadcast, diff is the new rep bandwidth
6897 0 : else if (!gf_list_count(rep->base_URLs)) {
6898 0 : diff = rep_new->bandwidth;
6899 : }
6900 : }
6901 : }
6902 :
6903 2486 : if (test_pass) {
6904 1259 : cumulated_bw_in_pass+= diff;
6905 1259 : nb_rep_increased ++;
6906 1227 : } else if (min_bandwidth + diff < 8*total_rate) {
6907 : min_bandwidth += diff;
6908 1227 : nb_rep_increased ++;
6909 1227 : bandwidths[q_idx] += diff;
6910 1227 : group->target_new_rep++;
6911 : }
6912 : }
6913 2318 : if (test_pass) {
6914 : //all reps cannot be switched up in this quality level, do it
6915 1672 : if ( min_bandwidth + cumulated_bw_in_pass > 8*total_rate) {
6916 : break;
6917 : }
6918 : }
6919 : //no more adjustement possible for this quality level
6920 2284 : if (! nb_rep_increased)
6921 : break;
6922 :
6923 1292 : test_pass = !test_pass;
6924 : }
6925 : }
6926 :
6927 1022 : if (! for_postponed_only) {
6928 1000 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Rate Adaptation - download rate %d kbps - %d quality levels (cumulated representations rate %d kbps)\n", 8*total_rate/1000, max_level, min_bandwidth/1000));
6929 :
6930 1004 : for (q_idx=0; q_idx<max_level; q_idx++) {
6931 1004 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH]\tLevel #%d - %d Adaptation Sets for a total %d kbps allocated\n", q_idx+1, groups_per_quality[q_idx], bandwidths[q_idx]/1000 ));
6932 : }
6933 : }
6934 :
6935 : force_rep_idx = GF_TRUE;
6936 : }
6937 : /*for each quality level get tile_rate_decrease% of the available bandwidth*/
6938 : else {
6939 0 : u32 rate = bandwidths[0] = total_rate * dash->tile_rate_decrease / 100;
6940 0 : for (i=1; i<max_level; i++) {
6941 0 : u32 remain = total_rate - rate;
6942 0 : if (i+1==max_level) {
6943 0 : bandwidths[i] = remain;
6944 : } else {
6945 0 : bandwidths[i] = remain * dash->tile_rate_decrease/100;
6946 0 : rate += bandwidths[i];
6947 : }
6948 : }
6949 :
6950 0 : if (! for_postponed_only) {
6951 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Rate Adaptation - download rate %d kbps - %d quality levels (cumulated rate %d kbps)\n", 8*total_rate/1000, max_level, 8*min_bandwidth/1000));
6952 0 : for (q_idx=0; q_idx<max_level; q_idx++) {
6953 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH]\tLevel #%d - %d Adaptation Sets for a total %d kbps allocated\n", q_idx+1, groups_per_quality[q_idx], 8*bandwidths[q_idx]/1000 ));
6954 : }
6955 : }
6956 : }
6957 :
6958 0 : custom_algo:
6959 :
6960 : //GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DEBUG. 2. dowload at %d \n", 8*bandwidths[q_idx]/1000));
6961 : //bandwitdh sharing done, perform rate adaptation with theses new numbers
6962 3942 : for (i=0; i<count; i++) {
6963 2897 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
6964 2897 : if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6965 2821 : if (group->done) continue;
6966 :
6967 : //in custom algo case, we don't change the bitrate of the group
6968 2737 : if (!use_custom_algo) {
6969 2694 : if (force_rep_idx) {
6970 2694 : rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
6971 : //add 100 bytes/sec to make sure we select the target one
6972 2694 : group->bytes_per_sec = 100 + rep->bandwidth / 8;
6973 : }
6974 : //decrease by quality level
6975 0 : else if (dash->tile_rate_decrease) {
6976 0 : quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
6977 0 : if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
6978 : assert(groups_per_quality[quality_rank]);
6979 0 : group->bytes_per_sec = bandwidths[quality_rank] / groups_per_quality[quality_rank];
6980 : }
6981 : }
6982 :
6983 2737 : if (for_postponed_only) {
6984 31 : if (group->rate_adaptation_postponed)
6985 22 : dash_do_rate_adaptation(dash, group);
6986 31 : group->bytes_per_sec = group->backup_Bps;
6987 : } else {
6988 2706 : dash_do_rate_adaptation(dash, group);
6989 : //reset/restore bytes_per_sec once all groups have been called
6990 : }
6991 : }
6992 :
6993 1045 : if (!for_postponed_only) {
6994 2853 : for (i=0; i<count; i++) {
6995 2853 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
6996 2853 : if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6997 2790 : if (group->done) continue;
6998 :
6999 2706 : if (!group->rate_adaptation_postponed)
7000 2704 : group->bytes_per_sec = 0;
7001 : else
7002 2 : group->bytes_per_sec = group->backup_Bps;
7003 : }
7004 : }
7005 : }
7006 :
7007 :
7008 :
7009 :
7010 159 : static GF_Err dash_setup_period_and_groups(GF_DashClient *dash)
7011 : {
7012 : u32 i, group_count;
7013 : GF_Err e;
7014 :
7015 : //don't resetup the entire period, only the broken group(s) ...
7016 160 : if (!dash->period_groups_setup) {
7017 : /*setup period*/
7018 123 : e = gf_dash_setup_period(dash);
7019 123 : if (e) {
7020 : //move to stop state before sending the error event otherwise we might deadlock when disconnecting the dash client
7021 0 : dash->dash_state = GF_DASH_STATE_STOPPED;
7022 0 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_PERIOD_SETUP_ERROR, -1, e);
7023 0 : return e;
7024 : }
7025 123 : if (dash->force_period_reload) return GF_OK;
7026 :
7027 123 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SELECT_GROUPS, -1, GF_OK);
7028 :
7029 123 : dash->period_groups_setup = GF_TRUE;
7030 123 : dash->all_groups_done_notified = GF_FALSE;
7031 : }
7032 :
7033 : e = GF_OK;
7034 160 : group_count = gf_list_count(dash->groups);
7035 422 : for (i=0; i<group_count; i++) {
7036 300 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
7037 300 : if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE)
7038 0 : continue;
7039 :
7040 300 : if (group->group_setup) continue;
7041 :
7042 298 : e = gf_dash_download_init_segment(dash, group);
7043 :
7044 : //might happen with broadcast DASH (eg ATSC3)
7045 298 : if (e == GF_IP_NETWORK_EMPTY) {
7046 38 : if (dash->reinit_period_index) {
7047 1 : gf_dash_reset_groups(dash);
7048 1 : dash->active_period_index = dash->reinit_period_index-1;
7049 1 : dash->reinit_period_index = 0;
7050 1 : dash->period_groups_setup = GF_FALSE;
7051 1 : return dash_setup_period_and_groups(dash);
7052 : }
7053 :
7054 : return e;
7055 : }
7056 260 : group->group_setup = GF_TRUE;
7057 260 : if (dash->initial_period_tunein && !dash->route_clock_state) {
7058 248 : group->timeline_setup = GF_FALSE;
7059 248 : group->force_timeline_reeval = GF_TRUE;
7060 : }
7061 260 : if (e) break;
7062 : }
7063 122 : dash->initial_period_tunein = GF_FALSE;
7064 :
7065 : /*if error signal to the user*/
7066 122 : if (e != GF_OK) {
7067 : //move to stop state before sending the error event otherwise we might deadlock when disconnecting the dash client
7068 0 : dash->dash_state = GF_DASH_STATE_STOPPED;
7069 0 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_PERIOD_SETUP_ERROR, -1, e);
7070 0 : return e;
7071 : }
7072 :
7073 : return GF_OK;
7074 : }
7075 :
7076 2247 : static void dash_do_groups(GF_DashClient *dash)
7077 : {
7078 2247 : u32 i, group_count = gf_list_count(dash->groups);
7079 :
7080 2247 : dash->min_wait_ms_before_next_request = 0;
7081 :
7082 : /*for each selected groups*/
7083 7455 : for (i=0; i<group_count; i++) {
7084 5208 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
7085 5208 : if (group->selection != GF_DASH_GROUP_SELECTED) {
7086 128 : if (group->nb_cached_segments) {
7087 22 : gf_dash_group_reset(dash, group);
7088 : }
7089 128 : continue;
7090 : }
7091 :
7092 5080 : if (group->depend_on_group) continue;
7093 : //not yet scheduled for download
7094 3586 : if (group->rate_adaptation_postponed) {
7095 21 : continue;
7096 : }
7097 :
7098 : DownloadGroupStatus res;
7099 3565 : res = dash_download_group(dash, group, group, group->groups_depending_on ? GF_TRUE : GF_FALSE);
7100 3565 : if (res==GF_DASH_DownloadRestart) {
7101 2 : i--;
7102 2 : continue;
7103 : }
7104 : }
7105 2247 : }
7106 :
7107 40460 : static GF_Err dash_check_mpd_update_and_cache(GF_DashClient *dash, Bool *cache_is_full)
7108 : {
7109 : GF_Err e = GF_OK;
7110 : u32 i, group_count;
7111 40460 : u32 now = gf_sys_clock();
7112 40460 : u32 timer = now - dash->last_update_time;
7113 : Bool has_postponed_rate_adaptation;
7114 :
7115 40460 : (*cache_is_full) = GF_TRUE;
7116 : has_postponed_rate_adaptation = GF_FALSE;
7117 :
7118 40460 : group_count = gf_list_count(dash->groups);
7119 :
7120 : /*refresh MPD*/
7121 40460 : if (dash->force_mpd_update
7122 : //regular MPD update
7123 40432 : || (dash->mpd->minimum_update_period && (timer > dash->mpd->minimum_update_period))
7124 : //pending HLS playlist refresh
7125 40424 : || (dash->hls_reload_time && (now > dash->hls_reload_time))
7126 : ) {
7127 93 : if (dash->force_mpd_update) {
7128 28 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Forcing playlist refresh (last segment reached)\n"));
7129 65 : } else if (dash->mpd->minimum_update_period) {
7130 8 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Update the playlist (%u ms elapsed since last refresh / min reload rate %u ms)\n", gf_sys_clock() , timer, dash->mpd->minimum_update_period));
7131 : }
7132 93 : dash->force_mpd_update = GF_FALSE;
7133 93 : dash->hls_reload_time = 0;
7134 93 : e = gf_dash_update_manifest(dash);
7135 93 : if (e) {
7136 0 : if (!dash->in_error) {
7137 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error updating MPD %s\n", gf_error_to_string(e)));
7138 : }
7139 : } else {
7140 93 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updated MPD in %d ms\n", gf_sys_clock() - now));
7141 : }
7142 : } else {
7143 : Bool all_groups_done = GF_TRUE;
7144 : Bool cache_full = GF_TRUE;
7145 :
7146 : has_postponed_rate_adaptation = GF_FALSE;
7147 :
7148 : /*wait if nothing is ready to be downloaded*/
7149 40367 : if (dash->min_wait_ms_before_next_request > 1) {
7150 83 : if (gf_sys_clock() < dash->min_wait_sys_clock + dash->min_wait_ms_before_next_request) {
7151 : return GF_EOS;
7152 : }
7153 30 : dash->min_wait_ms_before_next_request = 0;
7154 : }
7155 :
7156 : /*check if cache is not full*/
7157 40314 : dash->tsb_exceeded = 0;
7158 40314 : dash->time_in_tsb = 0;
7159 176301 : for (i=0; i<group_count; i++) {
7160 138234 : GF_MPD_Type type = dash->mpd->type;
7161 138234 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
7162 :
7163 138234 : if ((group->selection != GF_DASH_GROUP_SELECTED)
7164 116522 : || group->depend_on_group
7165 50363 : || !group->adaptation_set
7166 50363 : || (group->done && !group->nb_cached_segments)
7167 : ) {
7168 90067 : continue;
7169 : }
7170 48167 : if (group->period->origin_base_url)
7171 0 : type = group->period->type;
7172 :
7173 : all_groups_done = 0;
7174 48167 : if (type==GF_MPD_TYPE_DYNAMIC) {
7175 1587 : gf_dash_group_check_time(group);
7176 : }
7177 : //cache is full, notify client some segments are pending
7178 48167 : if ((group->nb_cached_segments == group->max_cached_segments)
7179 45920 : && !dash->request_period_switch
7180 45920 : && !group->has_pending_enhancement
7181 : ) {
7182 45920 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, i, GF_OK);
7183 : }
7184 48167 : if (group->done && group->nb_cached_segments) {
7185 32 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, i, GF_OK);
7186 : }
7187 :
7188 48167 : if (group->nb_cached_segments<group->max_cached_segments) {
7189 : cache_full = 0;
7190 : }
7191 48167 : if (group->rate_adaptation_postponed)
7192 : has_postponed_rate_adaptation = GF_TRUE;
7193 :
7194 48167 : if (!cache_full)
7195 : break;
7196 : }
7197 :
7198 40314 : if (dash->tsb_exceeded) {
7199 214 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_TIMESHIFT_OVERFLOW, (s32) dash->tsb_exceeded, GF_OK);
7200 214 : dash->tsb_exceeded = 0;
7201 40100 : } else if (dash->time_in_tsb != dash->prev_time_in_tsb) {
7202 192 : dash->prev_time_in_tsb = dash->time_in_tsb;
7203 192 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_TIMESHIFT_UPDATE, 0, GF_OK);
7204 : }
7205 :
7206 :
7207 40314 : if (!cache_full) {
7208 2247 : (*cache_is_full) = GF_FALSE;
7209 : } else {
7210 : //seek request
7211 38067 : if (dash->request_period_switch==2) all_groups_done = 1;
7212 :
7213 38067 : if (all_groups_done && dash->next_period_checked) {
7214 0 : dash->next_period_checked = 1;
7215 : }
7216 38067 : if (all_groups_done && dash->request_period_switch) {
7217 4 : gf_dash_reset_groups(dash);
7218 4 : if (dash->request_period_switch == 1) {
7219 4 : if (dash->speed<0) {
7220 0 : if (dash->active_period_index) {
7221 0 : dash->active_period_index--;
7222 : }
7223 : } else {
7224 4 : dash->active_period_index++;
7225 : }
7226 : }
7227 :
7228 4 : (*cache_is_full) = GF_FALSE;
7229 4 : dash->request_period_switch = 0;
7230 4 : dash->period_groups_setup = GF_FALSE;
7231 4 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Switching to period #%d\n", dash->active_period_index+1));
7232 4 : dash->dash_state = GF_DASH_STATE_SETUP;
7233 :
7234 4 : if (!dash->all_groups_done_notified) {
7235 4 : dash->all_groups_done_notified = GF_TRUE;
7236 4 : dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_END_OF_PERIOD, 0, GF_OK);
7237 : }
7238 :
7239 : } else {
7240 38063 : (*cache_is_full) = GF_TRUE;
7241 38063 : return GF_OK;
7242 : }
7243 : }
7244 : }
7245 :
7246 2251 : if (has_postponed_rate_adaptation) {
7247 22 : dash_global_rate_adaptation(dash, GF_TRUE);
7248 : }
7249 : return GF_OK;
7250 : }
7251 :
7252 40619 : static GF_Err gf_dash_process_internal(GF_DashClient *dash)
7253 : {
7254 : GF_Err e;
7255 : Bool cache_is_full;
7256 :
7257 40619 : if (dash->in_error) return GF_SERVICE_ERROR;
7258 :
7259 40619 : if (dash->force_period_reload) {
7260 0 : if (gf_sys_clock() - dash->force_period_reload < 500) return GF_OK;
7261 0 : dash->force_period_reload = 0;
7262 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Retrying period reload after previous failure\n"));
7263 : }
7264 :
7265 40619 : switch (dash->dash_state) {
7266 159 : case GF_DASH_STATE_SETUP:
7267 159 : dash->in_period_setup = 1;
7268 159 : e = dash_setup_period_and_groups(dash);
7269 159 : if (e) return e;
7270 :
7271 122 : if (dash->force_period_reload) {
7272 0 : dash->force_period_reload = gf_sys_clock();
7273 0 : return GF_OK;
7274 : }
7275 :
7276 122 : dash->last_update_time = gf_sys_clock();
7277 122 : dash->dash_state = GF_DASH_STATE_CONNECTING;
7278 :
7279 : //fallthrough
7280 :
7281 122 : case GF_DASH_STATE_CONNECTING:
7282 : /*ask the user to connect to desired groups*/
7283 122 : e = dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CREATE_PLAYBACK, -1, GF_OK);
7284 122 : if (e) return e;
7285 122 : dash->in_period_setup = 0;
7286 122 : dash->dash_state = GF_DASH_STATE_RUNNING;
7287 122 : dash->min_wait_ms_before_next_request = 0;
7288 122 : return GF_OK;
7289 40460 : case GF_DASH_STATE_RUNNING:
7290 40460 : e = dash_check_mpd_update_and_cache(dash, &cache_is_full);
7291 40460 : if (e || cache_is_full) {
7292 : return GF_OK;
7293 : }
7294 2251 : if (dash->dash_state == GF_DASH_STATE_SETUP)
7295 : return GF_OK;
7296 :
7297 2247 : dash->initial_period_tunein = GF_FALSE;
7298 2247 : dash_do_groups(dash);
7299 2247 : return GF_OK;
7300 : case GF_DASH_STATE_STOPPED:
7301 : return GF_EOS;
7302 : }
7303 0 : return GF_OK;
7304 : }
7305 :
7306 40619 : GF_Err gf_dash_process(GF_DashClient *dash)
7307 : {
7308 40619 : return gf_dash_process_internal(dash);
7309 : }
7310 :
7311 :
7312 118 : static u32 gf_dash_period_index_from_time(GF_DashClient *dash, u64 time)
7313 : {
7314 : u32 i, count, active_period_plus_one;
7315 : u64 cumul_start = 0;
7316 : Bool is_dyn = GF_FALSE;
7317 : GF_MPD_Period *period;
7318 :
7319 118 : if (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
7320 : u64 now, availabilityStartTime;
7321 36 : availabilityStartTime = dash->mpd->availabilityStartTime + dash->utc_shift;
7322 36 : availabilityStartTime += dash->utc_drift_estimate;
7323 :
7324 36 : now = dash->mpd_fetch_time + (gf_sys_clock() - dash->last_update_time) - availabilityStartTime;
7325 36 : if (dash->initial_time_shift_value<=100) {
7326 36 : now -= dash->mpd->time_shift_buffer_depth * dash->initial_time_shift_value / 100;
7327 : } else {
7328 0 : now -= dash->initial_time_shift_value;
7329 : }
7330 36 : if (!time) is_dyn = GF_TRUE;
7331 36 : time += now;
7332 : }
7333 :
7334 :
7335 200 : restart:
7336 121 : count = gf_list_count(dash->mpd->periods);
7337 : cumul_start = 0;
7338 : active_period_plus_one = 0;
7339 252 : for (i = 0; i<count; i++) {
7340 134 : period = gf_list_get(dash->mpd->periods, i);
7341 :
7342 134 : if (period->xlink_href && !period->duration) {
7343 3 : if (!active_period_plus_one || period->xlink_actuate_on_load) {
7344 3 : gf_dash_solve_period_xlink(dash, dash->mpd->periods, i);
7345 3 : goto restart;
7346 : }
7347 : }
7348 :
7349 131 : if ((period->start > time) || (cumul_start > time)) {
7350 : } else {
7351 121 : cumul_start += period->duration;
7352 :
7353 121 : if (!active_period_plus_one && (time < cumul_start)) {
7354 83 : active_period_plus_one = i+1;
7355 : }
7356 : }
7357 : }
7358 118 : if (is_dyn) {
7359 0 : for (i = 0; i<count; i++) {
7360 36 : period = gf_list_get(dash->mpd->periods, i);
7361 36 : if (!period->xlink_href && !period->origin_base_url) return i;
7362 : }
7363 : }
7364 82 : return active_period_plus_one ? active_period_plus_one-1 : 0;
7365 : }
7366 :
7367 3 : static Bool gf_dash_seek_periods(GF_DashClient *dash, Double seek_time)
7368 : {
7369 : Double start_time;
7370 : /*we have an arch issue here: we may get a seek request in normal multiperiod playback
7371 : with a seek time close but before to the active period, typically if the stream is shorter
7372 : than the advertised period duration. We will use a safety check of 0.5 seconds, any seek request
7373 : at Start(Pn) - 0.5 will resolve in seek at Start(Pn)
7374 : * */
7375 : Bool at_period_boundary=GF_FALSE;
7376 : u32 i, period_idx;
7377 : u32 nb_retry = 10;
7378 :
7379 3 : dash->start_range_period = 0;
7380 : start_time = 0;
7381 : period_idx = 0;
7382 5 : for (i=0; i<gf_list_count(dash->mpd->periods); i++) {
7383 5 : GF_MPD_Period *period = gf_list_get(dash->mpd->periods, i);
7384 : Double dur;
7385 :
7386 5 : if (period->xlink_href) {
7387 0 : gf_dash_solve_period_xlink(dash, dash->mpd->periods, i);
7388 0 : if (nb_retry) {
7389 0 : nb_retry --;
7390 0 : i--;
7391 0 : continue;
7392 : }
7393 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Period still resolves to XLINK %s for more than 10 consecutive retry, ignoring it ...\n", period->xlink_href));
7394 0 : gf_free(period->xlink_href);
7395 0 : period->xlink_href = NULL;
7396 : } else {
7397 : nb_retry = 10;
7398 : }
7399 5 : dur = (Double)period->duration;
7400 5 : dur /= 1000;
7401 5 : if (seek_time + 0.5 >= start_time) {
7402 5 : if ((i+1==gf_list_count(dash->mpd->periods)) || (seek_time + 0.5 < start_time + dur) ) {
7403 3 : if (seek_time > start_time + dur) {
7404 : at_period_boundary = GF_TRUE;
7405 : }
7406 : period_idx = i;
7407 : break;
7408 : }
7409 : }
7410 2 : start_time += dur;
7411 : }
7412 3 : if (period_idx != dash->active_period_index) {
7413 1 : seek_time -= start_time;
7414 1 : dash->active_period_index = period_idx;
7415 1 : dash->request_period_switch = 2;
7416 :
7417 1 : dash->start_range_period = seek_time;
7418 2 : } else if (seek_time < start_time) {
7419 : at_period_boundary = GF_TRUE;
7420 : }
7421 :
7422 3 : if (at_period_boundary) return GF_TRUE;
7423 3 : return dash->request_period_switch ? 1 : 0;
7424 : }
7425 :
7426 4 : static void gf_dash_seek_group(GF_DashClient *dash, GF_DASH_Group *group, Double seek_to, Bool is_dynamic)
7427 : {
7428 : GF_Err e;
7429 : u32 first_downloaded, last_downloaded, segment_idx, orig_idx;
7430 :
7431 6 : if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) return;
7432 :
7433 2 : group->force_segment_switch = 0;
7434 2 : if (!is_dynamic) {
7435 : /*figure out where to seek*/
7436 2 : orig_idx = group->download_segment_index;
7437 2 : e = gf_mpd_seek_in_period(seek_to, MPD_SEEK_PREV, group->period, group->adaptation_set, gf_list_get(group->adaptation_set->representations, group->active_rep_index), &segment_idx, NULL);
7438 2 : if (e<0)
7439 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] An error occured while seeking to time %lf: %s\n", seek_to, gf_error_to_string(e)));
7440 :
7441 2 : group->download_segment_index = orig_idx;
7442 :
7443 : /*remember to seek to given duration*/
7444 2 : group->start_playback_range = seek_to;
7445 :
7446 : first_downloaded = last_downloaded = group->download_segment_index;
7447 2 : if (group->download_segment_index + 1 >= (s32) group->nb_cached_segments) {
7448 2 : first_downloaded = group->download_segment_index + 1 - group->nb_cached_segments;
7449 : }
7450 : /*we are seeking in our download range, just go on*/
7451 2 : if ((segment_idx>=first_downloaded) && (segment_idx<=last_downloaded)) {
7452 : return;
7453 : }
7454 :
7455 2 : group->force_segment_switch = 1;
7456 2 : group->download_segment_index = segment_idx;
7457 : } else {
7458 0 : group->start_number_at_last_ast = 0;
7459 : /*remember to adjust time in timeline steup*/
7460 0 : group->start_playback_range = seek_to;
7461 0 : group->timeline_setup = GF_FALSE;
7462 : }
7463 :
7464 4 : while (group->nb_cached_segments) {
7465 2 : group->nb_cached_segments--;
7466 2 : gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
7467 : }
7468 2 : group->done = 0;
7469 : }
7470 :
7471 : GF_EXPORT
7472 0 : void gf_dash_group_seek(GF_DashClient *dash, u32 group_idx, Double seek_to)
7473 : {
7474 0 : GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
7475 0 : if (!group) return;
7476 0 : gf_dash_seek_group(dash, group, seek_to, (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_TRUE : GF_FALSE);
7477 : }
7478 :
7479 1 : static void gf_dash_seek_groups(GF_DashClient *dash, Double seek_time, Bool is_dynamic)
7480 : {
7481 : u32 i;
7482 :
7483 1 : if (dash->active_period_index) {
7484 : Double dur = 0;
7485 0 : for (i=0; i<dash->active_period_index; i++) {
7486 0 : GF_MPD_Period *period = gf_list_get(dash->mpd->periods, dash->active_period_index-1);
7487 0 : dur += period->duration/1000.0;
7488 : }
7489 0 : seek_time -= dur;
7490 : }
7491 3 : for (i=0; i<gf_list_count(dash->groups); i++) {
7492 2 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
7493 2 : gf_dash_seek_group(dash, group, seek_time, is_dynamic);
7494 : }
7495 1 : }
7496 :
7497 :
7498 101 : static GF_Err http_ifce_get(GF_FileDownload *getter, char *url)
7499 : {
7500 : GF_Err e;
7501 : Bool owns_sess = GF_FALSE;
7502 : GF_DASHFileIOSession *sess;
7503 101 : GF_DashClient *dash = (GF_DashClient*) getter->udta;
7504 101 : if (!getter->session) {
7505 8 : if (!dash->mpd_dnload) {
7506 0 : sess = dash->dash_io->create(dash->dash_io, 1, url, -1);
7507 0 : if (!sess) return GF_IO_ERR;
7508 0 : getter->session = sess;
7509 : e = GF_OK;
7510 : owns_sess = GF_TRUE;
7511 : } else {
7512 8 : sess = getter->session = dash->mpd_dnload;
7513 8 : e = dash->dash_io->setup_from_url(dash->dash_io, getter->session, url, -1);
7514 : }
7515 : }
7516 : else {
7517 : u32 group_idx = -1, i;
7518 21 : for (i = 0; i < gf_list_count(dash->groups); i++) {
7519 109 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
7520 109 : if (group->selection != GF_DASH_GROUP_SELECTED) continue;
7521 : group_idx = i;
7522 : break;
7523 : }
7524 93 : e = dash->dash_io->setup_from_url(dash->dash_io, getter->session, url, group_idx);
7525 93 : if (e) {
7526 : //with ROUTE we may have 404 right away if nothing in cache yet, not an error
7527 0 : GF_LOG(dash->route_clock_state ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot resetup downloader for url %s: %s\n", url, gf_error_to_string(e) ));
7528 : return e;
7529 : }
7530 93 : sess = (GF_DASHFileIOSession *)getter->session;
7531 : }
7532 101 : if (!e)
7533 101 : e = dash->dash_io->init(dash->dash_io, sess);
7534 :
7535 101 : if (e) {
7536 0 : if (owns_sess) {
7537 0 : dash->dash_io->del(dash->dash_io, sess);
7538 0 : if (getter->session == sess)
7539 0 : getter->session = NULL;
7540 : }
7541 : return e;
7542 : }
7543 101 : return dash->dash_io->run(dash->dash_io, sess);
7544 : }
7545 :
7546 0 : static void http_ifce_clean(GF_FileDownload *getter)
7547 : {
7548 0 : GF_DashClient *dash = (GF_DashClient*) getter->udta;
7549 0 : if (getter->session) dash->dash_io->del(dash->dash_io, getter->session);
7550 0 : getter->session = NULL;
7551 0 : }
7552 :
7553 101 : static const char *http_ifce_cache_name(GF_FileDownload *getter)
7554 : {
7555 101 : GF_DashClient *dash = (GF_DashClient*) getter->udta;
7556 101 : if (getter->session) return dash->dash_io->get_cache_name(dash->dash_io, getter->session);
7557 : return NULL;
7558 : }
7559 :
7560 : GF_EXPORT
7561 118 : GF_Err gf_dash_open(GF_DashClient *dash, const char *manifest_url)
7562 : {
7563 : char local_path[GF_MAX_PATH];
7564 : const char *local_url;
7565 : char *sep_cgi = NULL;
7566 : char *sep_frag = NULL;
7567 : GF_Err e;
7568 : GF_MPD_Period *period;
7569 : GF_DOMParser *mpd_parser=NULL;
7570 : Bool is_local = GF_FALSE;
7571 :
7572 118 : if (!dash || !manifest_url) return GF_BAD_PARAM;
7573 :
7574 118 : memset( dash->lastMPDSignature, 0, GF_SHA1_DIGEST_SIZE);
7575 118 : dash->reload_count = 0;
7576 :
7577 118 : if (dash->base_url) gf_free(dash->base_url);
7578 118 : sep_cgi = strrchr(manifest_url, '?');
7579 118 : if (sep_cgi) sep_cgi[0] = 0;
7580 118 : dash->base_url = gf_strdup(manifest_url);
7581 118 : if (sep_cgi) sep_cgi[0] = '?';
7582 :
7583 118 : dash->getter.udta = dash;
7584 118 : dash->getter.new_session = http_ifce_get;
7585 118 : dash->getter.del_session = http_ifce_clean;
7586 118 : dash->getter.get_cache_name = http_ifce_cache_name;
7587 118 : dash->getter.session = NULL;
7588 :
7589 :
7590 :
7591 118 : if (dash->mpd_dnload) dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7592 118 : dash->mpd_dnload = NULL;
7593 : local_url = NULL;
7594 :
7595 118 : if (!strnicmp(manifest_url, "file://", 7)) {
7596 0 : local_url = manifest_url + 7;
7597 : is_local = 1;
7598 0 : if (strstr(manifest_url, ".m3u8")) {
7599 0 : dash->is_m3u8 = GF_TRUE;
7600 : }
7601 118 : } else if (strstr(manifest_url, "://") && strncmp(manifest_url, "gfio://", 7)) {
7602 : const char *reloc_url, *mtype;
7603 : char mime[128];
7604 41 : e = gf_dash_download_resource(dash, &(dash->mpd_dnload), manifest_url, 0, 0, 1, NULL);
7605 41 : if (e!=GF_OK) {
7606 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD downloading problem %s for %s\n", gf_error_to_string(e), manifest_url));
7607 0 : dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7608 0 : dash->mpd_dnload = NULL;
7609 0 : return e;
7610 : }
7611 :
7612 41 : mtype = dash->dash_io->get_mime(dash->dash_io, dash->mpd_dnload);
7613 41 : strcpy(mime, mtype ? mtype : "");
7614 41 : strlwr(mime);
7615 :
7616 41 : reloc_url = dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload);
7617 :
7618 : /*if relocated use new URL as base URL for all requests*/
7619 41 : if (strcmp(reloc_url, manifest_url)) {
7620 8 : gf_free(dash->base_url);
7621 8 : dash->base_url = gf_strdup(reloc_url);
7622 : }
7623 :
7624 41 : local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
7625 41 : if (!local_url) {
7626 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: cache problem %s\n", local_url));
7627 0 : dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7628 0 : dash->mpd_dnload = NULL;
7629 0 : return GF_IO_ERR;
7630 : }
7631 : } else {
7632 : local_url = manifest_url;
7633 : is_local = 1;
7634 : }
7635 :
7636 77 : if (is_local && strncmp(manifest_url, "gfio://", 7)) {
7637 76 : FILE *f = gf_fopen(local_url, "rt");
7638 76 : if (!f) {
7639 0 : sep_cgi = strrchr(local_url, '?');
7640 0 : if (sep_cgi) sep_cgi[0] = 0;
7641 0 : sep_frag = strrchr(local_url, '#');
7642 0 : if (sep_frag) sep_frag[0] = 0;
7643 :
7644 0 : f = gf_fopen(local_url, "rt");
7645 0 : if (!f) {
7646 0 : if (sep_cgi) sep_cgi[0] = '?';
7647 0 : if (sep_frag) sep_frag[0] = '#';
7648 : return GF_URL_ERROR;
7649 : }
7650 : }
7651 76 : gf_fclose(f);
7652 : }
7653 118 : dash->mpd_fetch_time = dash_get_fetch_time(dash);
7654 :
7655 118 : gf_dash_reset_groups(dash);
7656 :
7657 118 : if (dash->mpd)
7658 0 : gf_mpd_del(dash->mpd);
7659 :
7660 118 : dash->mpd = gf_mpd_new();
7661 118 : if (!dash->mpd) {
7662 : e = GF_OUT_OF_MEM;
7663 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD creation problem %s\n", gf_error_to_string(e)));
7664 : goto exit;
7665 : }
7666 118 : if (dash->dash_io->manifest_updated) {
7667 7 : const char *szName = gf_file_basename(manifest_url);
7668 7 : dash->dash_io->manifest_updated(dash->dash_io, szName, local_url, -1);
7669 : }
7670 :
7671 : //peek payload, check if m3u8 - MPD and SmoothStreaming are checked after
7672 : char szLine[100];
7673 118 : FILE *f = gf_fopen(local_url, "r");
7674 118 : if (f) {
7675 118 : gf_fread(szLine, 100, f);
7676 118 : gf_fclose(f);
7677 : }
7678 118 : szLine[99] = 0;
7679 118 : if (strstr(szLine, "#EXTM3U"))
7680 21 : dash->is_m3u8 = 1;
7681 :
7682 118 : if (dash->is_m3u8) {
7683 21 : if (is_local) {
7684 : char *sep;
7685 : strcpy(local_path, local_url);
7686 13 : sep = gf_file_ext_start(local_path);
7687 13 : if (sep) sep[0]=0;
7688 : strcat(local_path, ".mpd");
7689 :
7690 13 : e = gf_m3u8_to_mpd(local_url, manifest_url, local_path, dash->reload_count, dash->mimeTypeForM3U8Segments, 0, M3U8_TO_MPD_USE_TEMPLATE, M3U8_TO_MPD_USE_SEGTIMELINE, &dash->getter, dash->mpd, GF_FALSE, dash->keep_files);
7691 : } else {
7692 8 : const char *redirected_url = dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload);
7693 8 : if (!redirected_url) redirected_url=manifest_url;
7694 :
7695 8 : e = gf_m3u8_to_mpd(local_url, redirected_url, NULL, dash->reload_count, dash->mimeTypeForM3U8Segments, 0, M3U8_TO_MPD_USE_TEMPLATE, M3U8_TO_MPD_USE_SEGTIMELINE, &dash->getter, dash->mpd, GF_FALSE, dash->keep_files);
7696 : }
7697 :
7698 21 : if (!e && dash->split_adaptation_set)
7699 3 : gf_mpd_split_adaptation_sets(dash->mpd);
7700 :
7701 : } else {
7702 97 : u32 res = gf_dash_check_mpd_root_type(local_url);
7703 97 : if (res==2) {
7704 2 : dash->is_smooth = 1;
7705 : }
7706 97 : if (!dash->is_smooth && !res) {
7707 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: wrong file type %s\n", local_url));
7708 0 : dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7709 0 : dash->mpd_dnload = NULL;
7710 0 : return GF_URL_ERROR;
7711 : }
7712 :
7713 97 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] parsing %s manifest %s\n", dash->is_smooth ? "SmoothStreaming" : "DASH-MPD", local_url));
7714 :
7715 : /* parse the MPD */
7716 97 : mpd_parser = gf_xml_dom_new();
7717 97 : e = gf_xml_dom_parse(mpd_parser, local_url, NULL, NULL);
7718 :
7719 97 : if (sep_cgi) sep_cgi[0] = '?';
7720 97 : if (sep_frag) sep_frag[0] = '#';
7721 :
7722 97 : if (e != GF_OK) {
7723 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD parsing problem %s\n", gf_xml_dom_get_error(mpd_parser) ));
7724 0 : gf_xml_dom_del(mpd_parser);
7725 0 : dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7726 0 : dash->mpd_dnload = NULL;
7727 0 : return GF_URL_ERROR;
7728 : }
7729 :
7730 97 : if (dash->is_smooth) {
7731 2 : e = gf_mpd_init_smooth_from_dom(gf_xml_dom_get_root(mpd_parser), dash->mpd, manifest_url);
7732 : } else {
7733 95 : e = gf_mpd_init_from_dom(gf_xml_dom_get_root(mpd_parser), dash->mpd, manifest_url);
7734 : }
7735 97 : gf_xml_dom_del(mpd_parser);
7736 :
7737 97 : if (!e && dash->split_adaptation_set)
7738 7 : gf_mpd_split_adaptation_sets(dash->mpd);
7739 :
7740 97 : if (dash->ignore_xlink)
7741 0 : dash_purge_xlink(dash->mpd);
7742 :
7743 : }
7744 : //for both DASH and HLS, we support ROUTE
7745 118 : if (!is_local) {
7746 41 : const char *hdr = dash->dash_io->get_header_value(dash->dash_io, dash->mpd_dnload, "x-route");
7747 41 : if (hdr) {
7748 5 : if (!dash->route_clock_state) {
7749 5 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Detected ROUTE DASH service ID %s\n", hdr));
7750 5 : dash->route_clock_state = 1;
7751 : }
7752 : }
7753 : }
7754 :
7755 118 : if (e != GF_OK) {
7756 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD creation problem %s\n", gf_error_to_string(e)));
7757 : goto exit;
7758 : }
7759 118 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] DASH client initialized from MPD at UTC time "LLU" - availabilityStartTime "LLU"\n", dash->mpd_fetch_time , dash->mpd->availabilityStartTime));
7760 :
7761 118 : if (is_local && dash->mpd->minimum_update_period) {
7762 7 : e = gf_dash_update_manifest(dash);
7763 7 : if (e != GF_OK) {
7764 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update MPD: %s\n", gf_error_to_string(e)));
7765 : goto exit;
7766 : }
7767 : }
7768 :
7769 : /* Get the right period from the given time */
7770 118 : dash->active_period_index = gf_dash_period_index_from_time(dash, 0);
7771 118 : period = gf_list_get(dash->mpd->periods, dash->active_period_index);
7772 118 : if (period->xlink_href) {
7773 0 : gf_dash_solve_period_xlink(dash, dash->mpd->periods, dash->active_period_index);
7774 0 : period = gf_list_get(dash->mpd->periods, dash->active_period_index);
7775 : }
7776 118 : if (!period || !gf_list_count(period->adaptation_sets) ) {
7777 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot start: not enough periods or representations in MPD\n"));
7778 : e = GF_URL_ERROR;
7779 : goto exit;
7780 : }
7781 :
7782 118 : dash->dash_state = GF_DASH_STATE_SETUP;
7783 118 : return GF_OK;
7784 :
7785 0 : exit:
7786 0 : if (dash->dash_io) {
7787 0 : dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7788 0 : dash->mpd_dnload = NULL;
7789 : }
7790 :
7791 0 : if (dash->mpd)
7792 0 : gf_mpd_del(dash->mpd);
7793 0 : dash->mpd = NULL;
7794 0 : return e;
7795 : }
7796 :
7797 : GF_EXPORT
7798 121 : void gf_dash_close(GF_DashClient *dash)
7799 : {
7800 : assert(dash);
7801 :
7802 121 : if (dash->dash_io) {
7803 121 : if (dash->mpd_dnload) {
7804 41 : if (dash->mpd_dnload == dash->getter.session)
7805 8 : dash->getter.session = NULL;
7806 41 : dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7807 41 : dash->mpd_dnload = NULL;
7808 : }
7809 :
7810 121 : if (dash->getter.del_session && dash->getter.session)
7811 0 : dash->getter.del_session(&dash->getter);
7812 : }
7813 121 : if (dash->mpd) {
7814 118 : gf_mpd_del(dash->mpd);
7815 118 : dash->mpd = NULL;
7816 : }
7817 :
7818 121 : if (dash->dash_state != GF_DASH_STATE_CONNECTING)
7819 121 : gf_dash_reset_groups(dash);
7820 121 : }
7821 :
7822 : GF_EXPORT
7823 118 : void gf_dash_set_algo(GF_DashClient *dash, GF_DASHAdaptationAlgorithm algo)
7824 : {
7825 118 : dash->adaptation_algorithm = algo;
7826 118 : switch (dash->adaptation_algorithm) {
7827 97 : case GF_DASH_ALGO_GPAC_LEGACY_BUFFER:
7828 97 : dash->rate_adaptation_algo = dash_do_rate_adaptation_legacy_buffer;
7829 97 : dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
7830 97 : break;
7831 1 : case GF_DASH_ALGO_GPAC_LEGACY_RATE:
7832 1 : dash->rate_adaptation_algo = dash_do_rate_adaptation_legacy_rate;
7833 1 : dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
7834 1 : break;
7835 1 : case GF_DASH_ALGO_BBA0:
7836 1 : dash->rate_adaptation_algo = dash_do_rate_adaptation_bba0;
7837 1 : dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
7838 1 : break;
7839 4 : case GF_DASH_ALGO_BOLA_FINITE:
7840 : case GF_DASH_ALGO_BOLA_BASIC:
7841 : case GF_DASH_ALGO_BOLA_U:
7842 : case GF_DASH_ALGO_BOLA_O:
7843 4 : dash->rate_adaptation_algo = dash_do_rate_adaptation_bola;
7844 4 : dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
7845 4 : break;
7846 15 : case GF_DASH_ALGO_NONE:
7847 : default:
7848 15 : dash->rate_adaptation_algo = NULL;
7849 15 : break;
7850 : }
7851 118 : }
7852 :
7853 43 : static s32 dash_do_rate_adaptation_custom(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
7854 : u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
7855 : GF_MPD_Representation *rep, Bool go_up_bitrate)
7856 : {
7857 : GF_DASHCustomAlgoInfo stats;
7858 43 : u32 g_idx = gf_list_find(dash->groups, group);
7859 43 : u32 b_idx = gf_list_find(dash->groups, base_group);
7860 :
7861 43 : stats.download_rate = dl_rate;
7862 43 : stats.file_size = group->total_size;
7863 43 : stats.speed = speed;
7864 43 : stats.max_available_speed = max_available_speed;
7865 43 : stats.disp_width = group->hint_visible_width;
7866 43 : stats.disp_height = group->hint_visible_height;
7867 43 : stats.active_quality_idx = group->active_rep_index;
7868 43 : stats.buffer_min_ms = group->buffer_min_ms;
7869 43 : stats.buffer_max_ms = group->buffer_max_ms;
7870 43 : stats.buffer_occupancy_ms = group->buffer_occupancy_ms;
7871 43 : stats.quality_degradation_hint = group->quality_degradation_hint;
7872 43 : stats.total_rate = dash->total_rate;
7873 :
7874 43 : return dash->rate_adaptation_algo_custom(dash->udta_custom_algo, g_idx, b_idx, force_lower_complexity, &stats);
7875 :
7876 : }
7877 :
7878 10 : static s32 dash_do_rate_monitor_custom(GF_DashClient *dash, GF_DASH_Group *group, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start, u32 buffer_dur_ms, u32 current_seg_dur)
7879 : {
7880 10 : u32 g_idx = gf_list_find(dash->groups, group);
7881 10 : return dash->rate_adaptation_download_monitor_custom(dash->udta_custom_algo, g_idx, bits_per_sec, total_bytes, bytes_done, us_since_start, buffer_dur_ms, current_seg_dur);
7882 : }
7883 :
7884 : GF_EXPORT
7885 3 : void gf_dash_set_algo_custom(GF_DashClient *dash, void *udta,
7886 : gf_dash_rate_adaptation algo_custom,
7887 : gf_dash_download_monitor download_monitor_custom)
7888 : {
7889 3 : dash->adaptation_algorithm = GF_DASH_ALGO_CUSTOM;
7890 3 : dash->rate_adaptation_algo = dash_do_rate_adaptation_custom;
7891 3 : dash->rate_adaptation_download_monitor = dash_do_rate_monitor_custom;
7892 :
7893 3 : dash->udta_custom_algo = udta;
7894 3 : dash->rate_adaptation_algo_custom = algo_custom;
7895 3 : dash->rate_adaptation_download_monitor_custom = download_monitor_custom;
7896 :
7897 3 : }
7898 :
7899 : GF_EXPORT
7900 118 : GF_DashClient *gf_dash_new(GF_DASHFileIO *dash_io, u32 max_cache_duration, u32 auto_switch_count, Bool keep_files, Bool disable_switching, GF_DASHInitialSelectionMode first_select_mode, u32 initial_time_shift_percent)
7901 : {
7902 : GF_DashClient *dash;
7903 118 : if (!dash_io) {
7904 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot create client withou sync IO for HTTP\n"));
7905 : return NULL;
7906 : }
7907 :
7908 118 : GF_SAFEALLOC(dash, GF_DashClient);
7909 118 : if (!dash) return NULL;
7910 118 : dash->dash_io = dash_io;
7911 118 : dash->speed = 1.0;
7912 118 : dash->is_rt_speed = GF_TRUE;
7913 118 : dash->low_latency_mode = GF_DASH_LL_STRICT;
7914 :
7915 : //wait one segment to validate we have enough bandwidth
7916 118 : dash->probe_times_before_switch = 1;
7917 : //FIXME: mime type for segments MUST be mp2t, webvtt or a Packed Audio file (like AAC)
7918 118 : dash->mimeTypeForM3U8Segments = gf_strdup( "video/mp2t" );
7919 :
7920 118 : dash->max_cache_duration = max_cache_duration;
7921 118 : dash->initial_time_shift_value = initial_time_shift_percent;
7922 :
7923 118 : dash->auto_switch_count = auto_switch_count;
7924 118 : dash->keep_files = keep_files;
7925 118 : dash->disable_switching = disable_switching;
7926 118 : dash->first_select_mode = first_select_mode;
7927 118 : dash->min_timeout_between_404 = 500;
7928 118 : dash->segment_lost_after_ms = 100;
7929 118 : dash->dbg_grps_index = NULL;
7930 118 : dash->tile_rate_decrease = 100;
7931 118 : dash->route_ast_shift = 1000;
7932 118 : dash->initial_period_tunein = GF_TRUE;
7933 118 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Client created\n"));
7934 :
7935 : #ifdef GPAC_ENABLE_COVERAGE
7936 118 : if (gf_sys_is_cov_mode()) {
7937 114 : on_group_download_error(NULL, NULL, NULL, GF_OK, NULL, NULL, NULL, GF_FALSE);
7938 114 : gf_dash_is_running(dash);
7939 : }
7940 : #endif
7941 :
7942 : return dash;
7943 : }
7944 :
7945 : GF_EXPORT
7946 118 : void gf_dash_del(GF_DashClient *dash)
7947 : {
7948 : //force group cleanup
7949 118 : dash->dash_state = GF_DASH_STATE_STOPPED;
7950 118 : gf_dash_close(dash);
7951 :
7952 118 : if (dash->mimeTypeForM3U8Segments) gf_free(dash->mimeTypeForM3U8Segments);
7953 118 : if (dash->base_url) gf_free(dash->base_url);
7954 :
7955 118 : gf_free(dash);
7956 118 : }
7957 :
7958 : GF_EXPORT
7959 118 : void gf_dash_enable_utc_drift_compensation(GF_DashClient *dash, Bool estimate_utc_drift)
7960 : {
7961 118 : dash->estimate_utc_drift = estimate_utc_drift;
7962 118 : }
7963 :
7964 : GF_EXPORT
7965 118 : void gf_dash_set_switching_probe_count(GF_DashClient *dash, u32 switch_probe_count)
7966 : {
7967 118 : dash->probe_times_before_switch = switch_probe_count;
7968 118 : }
7969 :
7970 : GF_EXPORT
7971 118 : void gf_dash_enable_single_range_llhls(GF_DashClient *dash, Bool enable)
7972 : {
7973 118 : dash->llhls_single_range = enable;
7974 118 : }
7975 :
7976 : GF_EXPORT
7977 118 : void gf_dash_set_agressive_adaptation(GF_DashClient *dash, Bool agressive_switch)
7978 : {
7979 118 : dash->agressive_switching = agressive_switch;
7980 118 : }
7981 :
7982 :
7983 : GF_EXPORT
7984 39249 : u32 gf_dash_get_group_count(GF_DashClient *dash)
7985 : {
7986 39249 : return gf_list_count(dash->groups);
7987 : }
7988 :
7989 :
7990 : GF_EXPORT
7991 127500 : void *gf_dash_get_group_udta(GF_DashClient *dash, u32 idx)
7992 : {
7993 127500 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7994 127500 : if (!group) return NULL;
7995 127297 : return group->udta;
7996 : }
7997 :
7998 : GF_EXPORT
7999 394 : GF_Err gf_dash_set_group_udta(GF_DashClient *dash, u32 idx, void *udta)
8000 : {
8001 394 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8002 394 : if (!group) return GF_BAD_PARAM;
8003 394 : group->udta = udta;
8004 394 : return GF_OK;
8005 : }
8006 :
8007 : GF_EXPORT
8008 118 : Bool gf_dash_is_group_selected(GF_DashClient *dash, u32 idx)
8009 : {
8010 118 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8011 118 : if (!group) return 0;
8012 118 : return (group->selection == GF_DASH_GROUP_SELECTED) ? 1 : 0;
8013 : }
8014 :
8015 : GF_EXPORT
8016 260 : Bool gf_dash_is_group_selectable(GF_DashClient *dash, u32 idx)
8017 : {
8018 260 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8019 260 : if (!group) return 0;
8020 260 : return (group->selection == GF_DASH_GROUP_NOT_SELECTABLE) ? 0 : 1;
8021 : }
8022 :
8023 : GF_EXPORT
8024 553 : void gf_dash_get_info(GF_DashClient *dash, const char **title, const char **source)
8025 : {
8026 553 : GF_MPD_ProgramInfo *info = dash ? gf_list_get(dash->mpd->program_infos, 0) : NULL;
8027 553 : if (title) *title = info ? info->title : NULL;
8028 553 : if (source) *source = info ? info->source : NULL;
8029 553 : }
8030 :
8031 :
8032 : GF_EXPORT
8033 120 : void gf_dash_switch_quality(GF_DashClient *dash, Bool switch_up, Bool immediate_switch)
8034 : {
8035 : u32 i;
8036 360 : for (i=0; i<gf_list_count(dash->groups); i++) {
8037 : u32 switch_to_rep_idx = 0;
8038 : u32 bandwidth, quality, k;
8039 : GF_MPD_Representation *rep, *active_rep;
8040 240 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
8041 240 : u32 current_idx = group->active_rep_index;
8042 240 : if (group->selection != GF_DASH_GROUP_SELECTED) continue;
8043 :
8044 239 : if (group->base_rep_index_plus_one) current_idx = group->max_complementary_rep_index;
8045 239 : if (group->force_representation_idx_plus_one) current_idx = group->force_representation_idx_plus_one - 1;
8046 :
8047 239 : active_rep = gf_list_get(group->adaptation_set->representations, current_idx);
8048 239 : if (!active_rep) continue;
8049 239 : bandwidth = switch_up ? (u32) -1 : 0;
8050 : quality = switch_up ? (u32) -1 : 0;
8051 :
8052 357 : for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
8053 357 : rep = gf_list_get(group->adaptation_set->representations, k);
8054 357 : if (switch_up) {
8055 357 : if ((rep->quality_ranking>active_rep->quality_ranking) || (rep->bandwidth>active_rep->bandwidth)) {
8056 7 : if ((rep->quality_ranking < quality) || (rep->bandwidth < bandwidth)) {
8057 1 : bandwidth = rep->bandwidth;
8058 : quality = rep->quality_ranking;
8059 1 : switch_to_rep_idx = k+1;
8060 : }
8061 : }
8062 : } else {
8063 0 : if ((rep->quality_ranking < active_rep->quality_ranking) || (rep->bandwidth < active_rep->bandwidth)) {
8064 0 : if ((rep->quality_ranking > quality) || (rep->bandwidth > bandwidth)) {
8065 0 : bandwidth = rep->bandwidth;
8066 : quality = rep->quality_ranking;
8067 0 : switch_to_rep_idx = k+1;
8068 : }
8069 : }
8070 : }
8071 : }
8072 239 : if (switch_to_rep_idx && (switch_to_rep_idx-1 != current_idx) ) {
8073 1 : u32 nb_cached_seg_per_rep = group->max_cached_segments / gf_dash_group_count_rep_needed(group);
8074 :
8075 1 : group->force_switch_bandwidth = 1;
8076 1 : if (!group->base_rep_index_plus_one)
8077 1 : group->force_representation_idx_plus_one = switch_to_rep_idx;
8078 : else
8079 0 : group->max_complementary_rep_index = switch_to_rep_idx-1;
8080 :
8081 :
8082 1 : if (group->local_files || immediate_switch) {
8083 : u32 keep_seg_index = 0;
8084 : //keep all scalable enhancements of the first segment
8085 1 : rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
8086 1 : if (rep->playback.enhancement_rep_index_plus_one) {
8087 : u32 rep_idx = rep->playback.enhancement_rep_index_plus_one;
8088 0 : while (keep_seg_index + 1 < group->nb_cached_segments) {
8089 0 : rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index+1].representation_index);
8090 0 : if (rep_idx == group->cached[keep_seg_index+1].representation_index+1) {
8091 : keep_seg_index ++;
8092 0 : rep_idx = rep->playback.enhancement_rep_index_plus_one;
8093 : }
8094 : else
8095 : break;
8096 : }
8097 : }
8098 :
8099 1 : if (!group->base_rep_index_plus_one) {
8100 : /*in local playback just switch at the end of the current segment
8101 : for remote, we should let the user decide*/
8102 1 : while (group->nb_cached_segments > keep_seg_index + 1) {
8103 0 : group->nb_cached_segments--;
8104 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group %d switching quality - delete cached segment: %s\n", i, group->cached[group->nb_cached_segments].url));
8105 :
8106 0 : gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
8107 :
8108 0 : group->cached[group->nb_cached_segments].duration = (u32) group->current_downloaded_segment_duration;
8109 0 : if (group->download_segment_index>1)
8110 0 : group->download_segment_index--;
8111 : }
8112 : } else {
8113 0 : if (switch_up) {
8114 : //first, we keep the second segment and remove all segments from the third one
8115 0 : keep_seg_index++;
8116 0 : rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index].representation_index);
8117 0 : if (rep->playback.enhancement_rep_index_plus_one) {
8118 : u32 rep_idx = rep->playback.enhancement_rep_index_plus_one;
8119 0 : while (keep_seg_index + 1 < group->nb_cached_segments) {
8120 0 : rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index+1].representation_index);
8121 0 : if (rep_idx == group->cached[keep_seg_index+1].representation_index+1) {
8122 : keep_seg_index ++;
8123 0 : rep_idx = rep->playback.enhancement_rep_index_plus_one;
8124 : }
8125 : else
8126 : break;
8127 : }
8128 : }
8129 0 : while (group->nb_cached_segments > keep_seg_index + 1) {
8130 0 : Bool decrease_download_segment_index = (group->cached[group->nb_cached_segments-1].representation_index == current_idx) ? GF_TRUE : GF_FALSE;
8131 0 : group->nb_cached_segments--;
8132 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group %d switching quality - delete cached segment: %s\n", i, group->cached[group->nb_cached_segments].url));
8133 :
8134 0 : gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
8135 :
8136 0 : group->cached[group->nb_cached_segments].duration = (u32) group->current_downloaded_segment_duration;
8137 :
8138 0 : if (decrease_download_segment_index && group->download_segment_index>1)
8139 0 : group->download_segment_index--;
8140 : }
8141 : /*force to download scalable enhancement of the second segment*/
8142 0 : group->force_representation_idx_plus_one = switch_to_rep_idx;
8143 0 : group->active_rep_index = switch_to_rep_idx - 1;
8144 0 : group->download_segment_index--;
8145 : }
8146 0 : else if (group->nb_cached_segments) {
8147 : /* we remove highest scalable enhancements of the dowloaded segments, and keep another segments*/
8148 0 : for (k = group->nb_cached_segments - 1; k > keep_seg_index; k--) {
8149 0 : if (group->cached[k].representation_index != current_idx)
8150 0 : continue;
8151 0 : group->nb_cached_segments--;
8152 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group %d switching quality - delete cached segment: %s\n", i, group->cached[k].url));
8153 0 : if (k != group->nb_cached_segments) {
8154 0 : memmove(&group->cached[k], &group->cached[k+1], (group->nb_cached_segments-k)*sizeof(segment_cache_entry));
8155 : }
8156 0 : memset(&group->cached[group->nb_cached_segments], 0, sizeof(segment_cache_entry));
8157 : }
8158 : }
8159 : }
8160 : }
8161 : /*resize max cached segment*/
8162 1 : group->max_cached_segments = nb_cached_seg_per_rep * gf_dash_group_count_rep_needed(group);
8163 :
8164 1 : if (group->srd_desc)
8165 0 : gf_dash_set_tiles_quality(dash, group->srd_desc, GF_TRUE);
8166 : }
8167 : }
8168 120 : }
8169 :
8170 : GF_EXPORT
8171 553 : Double gf_dash_get_duration(GF_DashClient *dash)
8172 : {
8173 553 : return gf_mpd_get_duration(dash->mpd);
8174 : }
8175 :
8176 : GF_EXPORT
8177 553 : u32 gf_dash_group_get_time_shift_buffer_depth(GF_DashClient *dash, u32 idx)
8178 : {
8179 553 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8180 553 : if (!group) return 0;
8181 553 : return group->time_shift_buffer_depth;
8182 : }
8183 :
8184 : GF_EXPORT
8185 315 : const char *gf_dash_get_url(GF_DashClient *dash)
8186 : {
8187 315 : return dash->base_url;
8188 : }
8189 :
8190 : GF_EXPORT
8191 379 : Bool gf_dash_is_m3u8(GF_DashClient *dash) {
8192 379 : return dash->is_m3u8;
8193 : }
8194 : GF_EXPORT
8195 97 : Bool gf_dash_is_smooth_streaming(GF_DashClient *dash) {
8196 97 : return dash->is_smooth;
8197 : }
8198 :
8199 : GF_EXPORT
8200 197 : const char *gf_dash_group_get_segment_mime(GF_DashClient *dash, u32 idx)
8201 : {
8202 : GF_MPD_Representation *rep;
8203 197 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8204 197 : if (!group) return NULL;
8205 :
8206 197 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8207 197 : return gf_dash_get_mime_type(NULL, rep, group->adaptation_set);
8208 : }
8209 :
8210 :
8211 :
8212 : GF_EXPORT
8213 197 : const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 idx, u64 *start_range, u64 *end_range)
8214 : {
8215 : GF_MPD_Representation *rep;
8216 197 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8217 197 : if (!group) return NULL;
8218 :
8219 : /*solve dependencies if any - we first test highest: if this is a complementary rep, keep the highest for init
8220 : otherwise use the selected one
8221 : this is need for scalable because we only init once the demuxer*/
8222 :
8223 197 : rep = gf_list_last(group->adaptation_set->representations);
8224 197 : if (!rep->dependency_id)
8225 197 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8226 :
8227 197 : if (group->bs_switching_init_segment_url) {
8228 44 : if (start_range) *start_range = group->bs_switching_init_segment_url_start_range;
8229 44 : if (end_range) *end_range = group->bs_switching_init_segment_url_end_range;
8230 44 : return group->bs_switching_init_segment_url;
8231 : }
8232 :
8233 : //no rep found or no init for the rep, check all reps
8234 : //this may happen when the adaptation rate algo changed the rep between the init (TS) and the next segment
8235 153 : if (!rep || !rep->playback.cached_init_segment_url) {
8236 : u32 i, count;
8237 0 : count = gf_list_count(group->adaptation_set->representations);
8238 0 : for (i=0; i<count; i++) {
8239 0 : rep = gf_list_get(group->adaptation_set->representations, i);
8240 0 : if (rep->playback.cached_init_segment_url) break;
8241 : rep = NULL;
8242 : }
8243 : }
8244 153 : if (!rep) return NULL;
8245 153 : if (start_range) *start_range = rep->playback.init_start_range;
8246 153 : if (end_range) *end_range = rep->playback.init_end_range;
8247 153 : return rep->playback.cached_init_segment_url;
8248 : }
8249 :
8250 : GF_EXPORT
8251 197 : const char *gf_dash_group_get_segment_init_keys(GF_DashClient *dash, u32 idx, u32 *crypt_type, bin128 *key_IV)
8252 : {
8253 : GF_MPD_Representation *rep;
8254 197 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8255 197 : if (!group) return NULL;
8256 :
8257 197 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8258 197 : if (!rep) return NULL;
8259 :
8260 197 : if (crypt_type) {
8261 197 : *crypt_type = rep->crypto_type;
8262 : }
8263 197 : if (key_IV) memcpy(*key_IV, rep->playback.key_IV, sizeof(bin128));
8264 197 : return rep->playback.key_url;
8265 : }
8266 :
8267 : GF_EXPORT
8268 672 : void gf_dash_group_select(GF_DashClient *dash, u32 idx, Bool select)
8269 : {
8270 : Bool needs_resetup = GF_FALSE;
8271 672 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8272 672 : if (!group) return;
8273 672 : if (group->selection == GF_DASH_GROUP_NOT_SELECTABLE)
8274 : return;
8275 :
8276 : if ((group->selection==GF_DASH_GROUP_NOT_SELECTED) && select) needs_resetup = 1;
8277 :
8278 672 : group->selection = select ? GF_DASH_GROUP_SELECTED : GF_DASH_GROUP_NOT_SELECTED;
8279 : /*this set is part of a group, make sure no all other sets from the indicated group are unselected*/
8280 672 : if (select && (group->adaptation_set->group>=0)) {
8281 : u32 i;
8282 0 : for (i=0; i<gf_dash_get_group_count(dash); i++) {
8283 0 : GF_DASH_Group *agroup = gf_list_get(dash->groups, i);
8284 0 : if (agroup==group) continue;
8285 :
8286 : /*either one Representation from group 0, if present,*/
8287 0 : if ((group->adaptation_set->group==0)
8288 : /*or the combination of at most one Representation from each non-zero group*/
8289 0 : || (group->adaptation_set->group==agroup->adaptation_set->group)
8290 : ) {
8291 0 : agroup->selection = GF_DASH_GROUP_NOT_SELECTED;
8292 : }
8293 : }
8294 : }
8295 : //TODO: recompute group download index based on current playback ...
8296 : if (needs_resetup) {
8297 :
8298 : }
8299 : }
8300 :
8301 : GF_EXPORT
8302 119 : void gf_dash_groups_set_language(GF_DashClient *dash, const char *lang_code_rfc_5646)
8303 : {
8304 : u32 i, len;
8305 : s32 lang_idx;
8306 : GF_List *groups_selected;
8307 119 : if (!lang_code_rfc_5646) lang_code_rfc_5646 = "und";
8308 :
8309 119 : groups_selected = gf_list_new();
8310 :
8311 : //first pass, check exact match
8312 358 : for (i=0; i<gf_list_count(dash->groups); i++) {
8313 239 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
8314 239 : if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) continue;
8315 :
8316 : //select groups with no language info or undetermined or matching our code
8317 239 : if (!group->adaptation_set->lang
8318 158 : || !stricmp(group->adaptation_set->lang, lang_code_rfc_5646)
8319 1 : || !strnicmp(group->adaptation_set->lang, "und", 3)
8320 1 : || !strnicmp(group->adaptation_set->lang, "unkn", 4)
8321 : ) {
8322 238 : gf_dash_group_select(dash, i, 1);
8323 238 : gf_list_add(groups_selected, group);
8324 : }
8325 : }
8326 :
8327 119 : lang_idx = gf_lang_find(lang_code_rfc_5646);
8328 119 : if (lang_idx>=0) {
8329 119 : const char *n2cc = gf_lang_get_2cc(lang_idx);
8330 119 : const char *n3cc = gf_lang_get_3cc(lang_idx);
8331 :
8332 358 : for (i=0; i<gf_list_count(dash->groups); i++) {
8333 : char *sep;
8334 239 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
8335 239 : if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) continue;
8336 239 : if (!group->adaptation_set->lang) continue;
8337 158 : if (gf_list_find(groups_selected, group) >= 0) continue;
8338 :
8339 : //check we didn't select one AS in this group in the previous pass or in this pass
8340 1 : if (group->adaptation_set->group>=0) {
8341 : u32 k;
8342 : Bool found = GF_FALSE;
8343 0 : for (k=0; k<gf_list_count(groups_selected); k++) {
8344 0 : GF_DASH_Group *ag = gf_list_get(groups_selected, k);
8345 :
8346 0 : if (ag->adaptation_set->group == group->adaptation_set->group) {
8347 : found = 1;
8348 : break;
8349 : }
8350 : }
8351 0 : if (found) continue;
8352 : }
8353 : //get the 2 or 3 land code
8354 1 : sep = strchr(group->adaptation_set->lang, '-');
8355 1 : if (sep) {
8356 0 : sep[0] = 0;
8357 : }
8358 1 : len = (u32) strlen(group->adaptation_set->lang);
8359 : //compare with what we found
8360 1 : if ( ((len==3) && !stricmp(group->adaptation_set->lang, n3cc))
8361 1 : || ((len==2) && !stricmp(group->adaptation_set->lang, n2cc))
8362 : ) {
8363 0 : gf_dash_group_select(dash, i, 1);
8364 0 : gf_list_add(groups_selected, group);
8365 : } else {
8366 1 : gf_dash_group_select(dash, i, 0);
8367 : }
8368 :
8369 1 : if (sep) sep[0] = '-';
8370 : }
8371 : }
8372 :
8373 119 : gf_list_del(groups_selected);
8374 119 : }
8375 :
8376 : GF_EXPORT
8377 114 : Bool gf_dash_is_running(GF_DashClient *dash)
8378 : {
8379 114 : return (dash->dash_state==GF_DASH_STATE_STOPPED) ? GF_FALSE : GF_TRUE;
8380 : }
8381 :
8382 : GF_EXPORT
8383 31651 : Bool gf_dash_is_in_setup(GF_DashClient *dash)
8384 : {
8385 31651 : if (dash->dash_state==GF_DASH_STATE_SETUP) return GF_TRUE;
8386 31647 : if (dash->dash_state==GF_DASH_STATE_CONNECTING) return GF_TRUE;
8387 31647 : if (dash->request_period_switch) return GF_TRUE;
8388 31643 : return GF_FALSE;
8389 : }
8390 :
8391 : GF_EXPORT
8392 7 : u32 gf_dash_get_period_switch_status(GF_DashClient *dash)
8393 : {
8394 7 : return dash->request_period_switch;
8395 : }
8396 : GF_EXPORT
8397 4 : void gf_dash_request_period_switch(GF_DashClient *dash)
8398 : {
8399 4 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Period switch has been requested\n"));
8400 4 : dash->request_period_switch = 1;
8401 4 : }
8402 : GF_EXPORT
8403 6518 : Bool gf_dash_in_last_period(GF_DashClient *dash, Bool check_eos)
8404 : {
8405 : Bool res;
8406 :
8407 6518 : switch (dash->dash_state) {
8408 : case GF_DASH_STATE_SETUP:
8409 : case GF_DASH_STATE_CONNECTING:
8410 : return GF_FALSE;
8411 : default:
8412 : break;
8413 : }
8414 :
8415 6512 : res = (dash->active_period_index+1 < gf_list_count(dash->mpd->periods)) ? 0 : 1;
8416 : //this code seems buggy, commented for now
8417 : #if 0
8418 : if (res && dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
8419 : GF_MPD_Period*period = gf_list_last(dash->mpd->periods);
8420 : //consider we are
8421 : if (!period->duration || dash->mpd->media_presentation_duration) res = GF_FALSE;
8422 : }
8423 : #endif
8424 6512 : return res;
8425 : }
8426 : GF_EXPORT
8427 261 : Bool gf_dash_in_period_setup(GF_DashClient *dash)
8428 : {
8429 261 : return dash->in_period_setup;
8430 : }
8431 :
8432 : GF_EXPORT
8433 282 : void gf_dash_set_speed(GF_DashClient *dash, Double speed)
8434 : {
8435 : u32 i;
8436 282 : if (!dash) return;
8437 282 : if (!speed) speed = 1.0;
8438 282 : if (dash->speed == speed) return;
8439 :
8440 0 : for (i=0; i<gf_list_count(dash->groups); i++) {
8441 0 : GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, i);
8442 : GF_MPD_Representation *active_rep;
8443 : Double max_available_speed;
8444 0 : if (!group || (group->selection != GF_DASH_GROUP_SELECTED)) continue;
8445 0 : active_rep = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8446 0 : if (speed < 0)
8447 0 : group->decode_only_rap = GF_TRUE;
8448 :
8449 0 : max_available_speed = gf_dash_get_max_available_speed(dash, group, active_rep);
8450 :
8451 : /*verify if this representation support this speed*/
8452 0 : if (!max_available_speed || (ABS(speed) <= max_available_speed)) {
8453 : //nothing to do
8454 : } else {
8455 : /*if the representation does not support this speed, search for another which support it*/
8456 : u32 switch_to_rep_idx = 0;
8457 : u32 bandwidth = 0, quality = 0, k;
8458 : GF_MPD_Representation *rep;
8459 0 : for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
8460 0 : rep = gf_list_get(group->adaptation_set->representations, k);
8461 0 : if ((ABS(speed) <= rep->max_playout_rate) && ((rep->quality_ranking > quality) || (rep->bandwidth > bandwidth))) {
8462 0 : bandwidth = rep->bandwidth;
8463 : quality = rep->quality_ranking;
8464 0 : switch_to_rep_idx = k+1;
8465 : }
8466 : }
8467 0 : if (switch_to_rep_idx) {
8468 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Switching representation for adapting playing speed\n"));
8469 0 : group->force_switch_bandwidth = 1;
8470 0 : group->force_representation_idx_plus_one = switch_to_rep_idx;
8471 : }
8472 : }
8473 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing at %f speed \n", speed));
8474 0 : dash->speed = speed;
8475 0 : dash->is_rt_speed = (ABS(speed - 1.0)<0.1) ? GF_TRUE : GF_FALSE;
8476 :
8477 : }
8478 : }
8479 :
8480 : GF_EXPORT
8481 78 : GF_Err gf_dash_group_get_segment_duration(GF_DashClient *dash, u32 idx, u32 *dur, u32 *timescale)
8482 : {
8483 : GF_MPD_Representation *rep;
8484 78 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8485 78 : if (!group) return GF_BAD_PARAM;
8486 78 : if (!group->adaptation_set) return GF_BAD_PARAM;
8487 78 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8488 78 : if (!rep) return GF_BAD_PARAM;
8489 :
8490 78 : *dur = 0;
8491 78 : *timescale = 0;
8492 78 : if (rep->segment_template) { *dur = (u32) rep->segment_template->duration; (*timescale) = rep->segment_template->timescale; }
8493 26 : else if (rep->segment_list) { *dur = (u32) rep->segment_list->duration; *timescale = rep->segment_list->timescale; }
8494 :
8495 78 : if (group->adaptation_set->segment_template && ! *dur) *dur = (u32) group->adaptation_set->segment_template->duration;
8496 67 : else if (group->adaptation_set->segment_list && ! *dur) *dur = (u32) group->adaptation_set->segment_list->duration;
8497 :
8498 78 : if (group->adaptation_set->segment_template && ! *timescale) *timescale = group->adaptation_set->segment_template->timescale;
8499 67 : else if (group->adaptation_set->segment_list && ! *timescale) *timescale = group->adaptation_set->segment_list->timescale;
8500 :
8501 78 : if (group->period->segment_template && ! *dur) *dur = (u32) group->period->segment_template->timescale;
8502 78 : else if (group->period->segment_list && ! *dur) *dur = (u32) group->period->segment_list->timescale;
8503 :
8504 78 : if (group->period->segment_template && ! *timescale) *timescale = group->period->segment_template->timescale;
8505 78 : else if (group->period->segment_list && ! *timescale) *timescale = group->period->segment_list->timescale;
8506 : return GF_OK;
8507 : }
8508 :
8509 : GF_EXPORT
8510 132 : GF_Err gf_dash_group_next_seg_info(GF_DashClient *dash, u32 group_idx, u32 dependent_representation_index, const char **seg_name, u32 *seg_number, GF_Fraction64 *seg_time, u32 *seg_dur_ms, const char **init_segment)
8511 : {
8512 : GF_Err res = GF_OK;
8513 132 : GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
8514 132 : if (!group) return GF_BAD_PARAM;
8515 :
8516 132 : if (init_segment) {
8517 52 : if (group->bs_switching_init_segment_url) {
8518 18 : *init_segment = group->bs_switching_init_segment_url_name_start;
8519 : } else {
8520 34 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8521 34 : *init_segment = rep ? rep->playback.init_seg_name_start : NULL;
8522 : }
8523 : } else {
8524 : u32 rep_idx = dependent_representation_index;
8525 80 : if (group->nb_cached_segments <= rep_idx) {
8526 : res = GF_BUFFER_TOO_SMALL;
8527 : } else {
8528 80 : if (seg_name) *seg_name = group->cached[rep_idx].seg_name_start;
8529 80 : if (seg_number) *seg_number = group->cached[rep_idx].seg_number;
8530 80 : if (seg_time) *seg_time = group->cached[rep_idx].time;
8531 80 : if (seg_dur_ms) *seg_dur_ms = group->cached[rep_idx].duration;
8532 : }
8533 : }
8534 : return res;
8535 : }
8536 :
8537 : GF_EXPORT
8538 45 : const char *gf_dash_group_get_representation_id(GF_DashClient *dash, u32 idx)
8539 : {
8540 : GF_MPD_Representation *rep;
8541 45 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8542 45 : if (!group) return NULL;
8543 45 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8544 45 : return rep->id;
8545 : }
8546 :
8547 :
8548 : GF_EXPORT
8549 1697 : void gf_dash_group_discard_segment(GF_DashClient *dash, u32 idx)
8550 : {
8551 : GF_DASH_Group *group;
8552 : Bool delete_next;
8553 :
8554 1697 : group = gf_list_get(dash->groups, idx);
8555 :
8556 1431 : discard_segment:
8557 3128 : if (!group->nb_cached_segments) {
8558 : return;
8559 : }
8560 3128 : delete_next = (group->cached[0].flags & SEG_FLAG_DEP_FOLLOWING) ? GF_TRUE : GF_FALSE;
8561 : assert(group->cached[0].url);
8562 :
8563 3128 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] removing segment %s from list\n", group->cached[0].url));
8564 :
8565 : //remember the representation index of the last segment
8566 3128 : group->prev_active_rep_index = group->cached[0].representation_index;
8567 :
8568 3128 : gf_dash_group_reset_cache_entry(&group->cached[0]);
8569 :
8570 3128 : memmove(&group->cached[0], &group->cached[1], sizeof(segment_cache_entry)*(group->nb_cached_segments-1));
8571 3128 : memset(&(group->cached[group->nb_cached_segments-1]), 0, sizeof(segment_cache_entry));
8572 3128 : group->nb_cached_segments--;
8573 :
8574 3128 : if (delete_next) {
8575 : goto discard_segment;
8576 : }
8577 :
8578 : /*if we have dependency representations, we need also discard them*/
8579 1697 : if (group->base_rep_index_plus_one) {
8580 0 : if (group->cached[0].url && (group->cached[0].representation_index != group->base_rep_index_plus_one-1))
8581 : goto discard_segment;
8582 : }
8583 : }
8584 :
8585 : GF_EXPORT
8586 364 : void gf_dash_set_group_done(GF_DashClient *dash, u32 idx, Bool done)
8587 : {
8588 364 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8589 364 : if (group) group->done = done;
8590 364 : }
8591 :
8592 : GF_EXPORT
8593 16915 : Bool gf_dash_get_group_done(GF_DashClient *dash, u32 idx)
8594 : {
8595 16915 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8596 16915 : if (group) return group->done;
8597 : return GF_FALSE;
8598 : }
8599 :
8600 : GF_EXPORT
8601 349 : GF_Err gf_dash_group_get_presentation_time_offset(GF_DashClient *dash, u32 idx, u64 *presentation_time_offset, u32 *timescale)
8602 : {
8603 349 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8604 349 : if (group) {
8605 : u64 duration;
8606 349 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8607 349 : gf_mpd_resolve_segment_duration(rep, group->adaptation_set, group->period, &duration, timescale, presentation_time_offset, NULL);
8608 : return GF_OK;
8609 : }
8610 : return GF_BAD_PARAM;
8611 : }
8612 :
8613 : GF_EXPORT
8614 4326 : GF_Err gf_dash_group_get_next_segment_location(GF_DashClient *dash, u32 idx, u32 dependent_representation_index, const char **url, u64 *start_range, u64 *end_range, s32 *switching_index, const char **switching_url, u64 *switching_start_range, u64 *switching_end_range, const char **original_url, Bool *has_next_segment, const char **key_url, bin128 *key_IV)
8615 : {
8616 : GF_DASH_Group *group;
8617 : u32 index;
8618 : Bool has_dep_following;
8619 4326 : *url = NULL;
8620 4326 : if (switching_url) *switching_url = NULL;
8621 4326 : if (start_range) *start_range = 0;
8622 4326 : if (end_range) *end_range = 0;
8623 4326 : if (switching_start_range) *switching_start_range = 0;
8624 4326 : if (switching_end_range) *switching_end_range = 0;
8625 4326 : if (original_url) *original_url = NULL;
8626 4326 : if (switching_index) *switching_index = -1;
8627 4326 : if (has_next_segment) *has_next_segment = GF_FALSE;
8628 :
8629 4326 : group = gf_list_get(dash->groups, idx);
8630 :
8631 4326 : if (!group) {
8632 : return GF_BAD_PARAM;
8633 : }
8634 :
8635 4326 : if (!group->nb_cached_segments) {
8636 1245 : if (group->done) return GF_EOS;
8637 1044 : if ((dash->low_latency_mode==GF_DASH_LL_EARLY_FETCH)
8638 1044 : && group->is_low_latency
8639 96 : && dash->is_rt_speed
8640 96 : && !dash->time_in_tsb
8641 : ) {
8642 96 : group->force_early_fetch = GF_TRUE;
8643 96 : dash->min_wait_ms_before_next_request = 1;
8644 : }
8645 : return GF_BUFFER_TOO_SMALL;
8646 : }
8647 :
8648 : /*check the dependent rep is in the cache and does not target next segment (next in time)*/
8649 3081 : has_dep_following = (group->cached[0].flags & SEG_FLAG_DEP_FOLLOWING) ? GF_TRUE : GF_FALSE;
8650 : index = 0;
8651 13383 : while (dependent_representation_index) {
8652 : GF_Err err = GF_OK;
8653 :
8654 7221 : if (has_dep_following) {
8655 7221 : if (index+1 >= group->nb_cached_segments)
8656 : err = GF_BUFFER_TOO_SMALL;
8657 7221 : else if (! (group->cached[index].flags & SEG_FLAG_DEP_FOLLOWING) )
8658 : err = GF_BAD_PARAM;
8659 : } else {
8660 0 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->cached[index].representation_index);
8661 :
8662 0 : if (index+1 >= group->nb_cached_segments) err = GF_BUFFER_TOO_SMALL;
8663 0 : else if (!rep->playback.enhancement_rep_index_plus_one) err = GF_BAD_PARAM;
8664 0 : else if (rep->playback.enhancement_rep_index_plus_one != group->cached[index+1].representation_index + 1) err = GF_BAD_PARAM;
8665 : }
8666 :
8667 : if (err) {
8668 : return err;
8669 : }
8670 7221 : index ++;
8671 7221 : dependent_representation_index--;
8672 : }
8673 :
8674 3081 : *url = group->cached[index].url;
8675 3081 : if (start_range)
8676 3081 : *start_range = group->cached[index].start_range;
8677 3081 : if (end_range)
8678 3081 : *end_range = group->cached[index].end_range;
8679 3081 : if (original_url) *original_url = group->cached[index].url;
8680 3081 : if (key_url) *key_url = group->cached[index].key_url;
8681 3081 : if (key_IV) memcpy((*key_IV), group->cached[index].key_IV, sizeof(bin128));
8682 :
8683 3081 : if (!group->base_rep_index_plus_one && (group->cached[index].representation_index != group->prev_active_rep_index)) {
8684 247 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
8685 247 : if (switching_index)
8686 0 : *switching_index = group->cached[0].representation_index;
8687 247 : if (switching_start_range)
8688 247 : *switching_start_range = rep->playback.init_start_range;
8689 247 : if (switching_end_range)
8690 247 : *switching_end_range = rep->playback.init_end_range;
8691 247 : if (switching_url)
8692 247 : *switching_url = rep->playback.cached_init_segment_url;
8693 : }
8694 3081 : group->force_segment_switch = 0;
8695 :
8696 3081 : if (has_next_segment) {
8697 3081 : if (group->cached[index].flags & SEG_FLAG_DEP_FOLLOWING) {
8698 1447 : *has_next_segment = GF_TRUE;
8699 1634 : } else if ((index+1<group->max_cached_segments) && group->cached[index+1].url && group->adaptation_set) {
8700 : GF_MPD_Representation *rep;
8701 :
8702 0 : rep = gf_list_get(group->adaptation_set->representations, group->cached[index].representation_index);
8703 0 : if (rep && (rep->playback.enhancement_rep_index_plus_one == group->cached[index+1].representation_index+1) ) {
8704 0 : *has_next_segment = GF_TRUE;
8705 : }
8706 1634 : } else if (group->has_pending_enhancement) {
8707 0 : *has_next_segment = GF_TRUE;
8708 : }
8709 : }
8710 3081 : if (group->cached[index].flags & SEG_FLAG_DISABLED)
8711 : return GF_URL_REMOVED;
8712 3081 : return GF_OK;
8713 : }
8714 :
8715 :
8716 : GF_EXPORT
8717 3 : void gf_dash_seek(GF_DashClient *dash, Double start_range)
8718 : {
8719 : Bool is_dynamic = GF_FALSE;
8720 :
8721 3 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Seek request - playing from %g\n", start_range));
8722 :
8723 : //are we live ? if so adjust start range
8724 3 : if (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
8725 : u64 now, availabilityStartTime;
8726 0 : availabilityStartTime = dash->mpd->availabilityStartTime + dash->utc_shift;
8727 0 : availabilityStartTime += dash->utc_drift_estimate;
8728 :
8729 0 : now = dash->mpd_fetch_time + (gf_sys_clock() - dash->last_update_time) - availabilityStartTime;
8730 :
8731 0 : if (dash->initial_time_shift_value<=100) {
8732 0 : now -= dash->mpd->time_shift_buffer_depth * dash->initial_time_shift_value / 100;
8733 : } else {
8734 0 : now -= dash->initial_time_shift_value;
8735 : }
8736 : // now += (u64) (start_range*1000);
8737 0 : start_range = (Double) now;
8738 0 : start_range /= 1000;
8739 :
8740 : is_dynamic = 1;
8741 0 : dash->initial_period_tunein = GF_TRUE;
8742 : }
8743 :
8744 : /*first check if we seek to another period*/
8745 3 : if (! gf_dash_seek_periods(dash, start_range)) {
8746 : /*if no, seek in group*/
8747 1 : gf_dash_seek_groups(dash, start_range, is_dynamic);
8748 : }
8749 3 : }
8750 :
8751 : GF_EXPORT
8752 259 : Bool gf_dash_group_segment_switch_forced(GF_DashClient *dash, u32 idx)
8753 : {
8754 259 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8755 259 : return group->force_segment_switch;
8756 : }
8757 :
8758 : GF_EXPORT
8759 118 : void gf_dash_set_utc_shift(GF_DashClient *dash, s32 shift_utc_sec)
8760 : {
8761 118 : if (dash) dash->utc_shift = shift_utc_sec;
8762 118 : }
8763 :
8764 : GF_EXPORT
8765 197 : GF_Err gf_dash_group_get_video_info(GF_DashClient *dash, u32 idx, u32 *max_width, u32 *max_height)
8766 : {
8767 197 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8768 197 : if (!group || !max_width || !max_height) return GF_BAD_PARAM;
8769 :
8770 197 : *max_width = group->adaptation_set->max_width;
8771 197 : *max_height = group->adaptation_set->max_height;
8772 197 : return GF_OK;
8773 : }
8774 :
8775 : GF_EXPORT
8776 197 : Bool gf_dash_group_get_srd_max_size_info(GF_DashClient *dash, u32 idx, u32 *max_width, u32 *max_height)
8777 : {
8778 197 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8779 197 : if (!group || !group->srd_desc || !max_width || !max_height) return GF_FALSE;
8780 :
8781 7 : *max_width = group->srd_desc->width;
8782 7 : *max_height = group->srd_desc->height;
8783 7 : return GF_TRUE;
8784 : }
8785 :
8786 : GF_EXPORT
8787 118 : GF_Err gf_dash_set_min_timeout_between_404(GF_DashClient *dash, u32 min_timeout_between_404)
8788 : {
8789 118 : if (!dash) return GF_BAD_PARAM;
8790 118 : dash->min_timeout_between_404 = min_timeout_between_404;
8791 118 : return GF_OK;
8792 : }
8793 :
8794 : GF_EXPORT
8795 118 : GF_Err gf_dash_set_segment_expiration_threshold(GF_DashClient *dash, u32 expire_after_ms)
8796 : {
8797 118 : if (!dash) return GF_BAD_PARAM;
8798 118 : dash->segment_lost_after_ms = expire_after_ms;
8799 118 : return GF_OK;
8800 : }
8801 :
8802 : GF_EXPORT
8803 118 : Bool gf_dash_group_loop_detected(GF_DashClient *dash, u32 idx)
8804 : {
8805 118 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8806 118 : if (!group || !group->nb_cached_segments)
8807 : return GF_FALSE;
8808 118 : return (group->cached[0].flags & SEG_FLAG_LOOP_DETECTED) ? GF_TRUE : GF_FALSE;
8809 : }
8810 :
8811 : GF_EXPORT
8812 222 : Double gf_dash_group_get_start_range(GF_DashClient *dash, u32 idx)
8813 : {
8814 222 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8815 222 : if (!group) return 0.0;
8816 222 : return group->start_playback_range;
8817 : }
8818 :
8819 :
8820 : GF_EXPORT
8821 380 : Bool gf_dash_is_dynamic_mpd(GF_DashClient *dash)
8822 : {
8823 380 : return (dash && dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? 1 : 0;
8824 : }
8825 :
8826 : GF_EXPORT
8827 5 : u32 gf_dash_get_min_buffer_time(GF_DashClient *dash)
8828 : {
8829 5 : return dash ? dash->mpd->min_buffer_time : 0;
8830 : }
8831 :
8832 : #if 0 //unused
8833 : GF_Err gf_dash_resync_to_segment(GF_DashClient *dash, const char *latest_segment_name, const char *earliest_segment_name)
8834 : {
8835 : Bool found = GF_FALSE;
8836 : u32 i, j, latest_segment_number, earliest_segment_number, start_number;
8837 : /*Double latest_segment_time, earliest_segment_time;*/ //FIX : set but not used
8838 : u64 start_range, end_range, current_dur;
8839 : char *seg_url, *seg_name, *seg_sep;
8840 : GF_MPD_Representation *rep;
8841 : GF_DASH_Group *group = NULL;
8842 : if (!latest_segment_name) return GF_BAD_PARAM;
8843 :
8844 : seg_url = NULL;
8845 : for (i=0; i<gf_list_count(dash->groups); i++) {
8846 : group = gf_list_get(dash->groups, i);
8847 : for (j=0; j<gf_list_count(group->adaptation_set->representations); j++) {
8848 : GF_Err e;
8849 : rep = gf_list_get(group->adaptation_set->representations, j);
8850 : e = gf_dash_resolve_url(dash->mpd, rep, group, dash->base_url, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE, i, &seg_url, &start_range, &end_range, ¤t_dur, NULL, NULL, NULL, NULL, NULL);
8851 : if (e)
8852 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve media template URL: %s\n", gf_error_to_string(e)));
8853 :
8854 : if (!seg_url) continue;
8855 :
8856 : seg_sep = NULL;
8857 : seg_name = strstr(seg_url, "://");
8858 : if (seg_name) seg_name = strchr(seg_name+4, '/');
8859 : if (!seg_name) {
8860 : gf_free(seg_url);
8861 : continue;
8862 : }
8863 : seg_sep = strchr(seg_name+1, '$');
8864 : if (seg_sep) seg_sep[0] = 0;
8865 :
8866 : if (!strncmp(seg_name, latest_segment_name, strlen(seg_name)))
8867 : found = GF_TRUE;
8868 :
8869 : if (found) break;
8870 :
8871 : if (seg_sep) seg_sep[0] = '$';
8872 : gf_free(seg_url);
8873 : continue;
8874 : }
8875 : if (found) break;
8876 : }
8877 :
8878 : if (!found) {
8879 : if (seg_url) gf_free(seg_url);
8880 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] No representation found matching the resync segment name %s\n", latest_segment_name));
8881 : return GF_BAD_PARAM;
8882 : }
8883 :
8884 : start_number = gf_dash_get_start_number(group, rep);
8885 :
8886 : if (seg_sep) {
8887 : char *sep_template, *sep_name, c;
8888 : char *latest_template = (char *) (latest_segment_name + strlen(seg_name));
8889 : char *earliest_template = earliest_segment_name ? (char *) (earliest_segment_name + strlen(seg_name)) : NULL;
8890 :
8891 : latest_segment_number = earliest_segment_number = 0;
8892 : /*latest_segment_time = earliest_segment_time = 0;*/
8893 :
8894 : seg_sep[0] = '$';
8895 : while (seg_sep) {
8896 : sep_template = strchr(seg_sep+1, '$');
8897 : if (!sep_template) break;
8898 : c = sep_template[1];
8899 : sep_template[1] = 0;
8900 : //solve template for latest
8901 : sep_name = strchr(latest_template, c);
8902 : if (!sep_name) break;
8903 :
8904 : sep_name[0] = 0;
8905 : if (!strcmp(seg_sep, "$Number$")) {
8906 : latest_segment_number = atoi(latest_template);
8907 : }
8908 : /*else if (!strcmp(seg_sep, "$Time$")) {
8909 : latest_segment_time = atof(latest_template);
8910 : }*/
8911 : sep_name[0] = c;
8912 : latest_template = sep_name;
8913 :
8914 : //solve template for earliest
8915 : if (earliest_template) {
8916 : sep_name = strchr(earliest_template, c);
8917 : if (!sep_name) break;
8918 :
8919 : sep_name[0] = 0;
8920 : if (!strcmp(seg_sep, "$Number$")) {
8921 : earliest_segment_number = atoi(earliest_template);
8922 : }
8923 : /*else if (!strcmp(seg_sep, "$Time$")) {
8924 : earliest_segment_time = atof(earliest_template);
8925 : }*/
8926 : sep_name[0] = c;
8927 : earliest_template = sep_name;
8928 : }
8929 :
8930 : sep_template[1] = c;
8931 : seg_sep = sep_template+1;
8932 : //find next $ - if any, move the segment name of the same amount of chars that what found in the template
8933 : sep_template = strchr(seg_sep, '$');
8934 : if (!sep_template) break;
8935 : sep_template[0]=0;
8936 : latest_template += strlen(sep_template);
8937 : sep_template[0]='$';
8938 : }
8939 : if (earliest_segment_number && !latest_segment_number) {
8940 : latest_segment_number = earliest_segment_number;
8941 : }
8942 :
8943 : gf_free(seg_url);
8944 :
8945 : //todo - recompute an AST offset so that the AST of the new segment equals UTC(now) + offset
8946 : if (latest_segment_number) {
8947 : Bool loop_detected = GF_FALSE;
8948 : s32 nb_seg_diff = 0;
8949 : s32 range_in = 0;
8950 : //how many segment ahead are we ?
8951 : nb_seg_diff = start_number + group->download_segment_index;
8952 : nb_seg_diff -= latest_segment_number;
8953 :
8954 : //we are just too early for this request, do request later
8955 : if (nb_seg_diff == 1 ) {
8956 : //set to false, eg don't increment seg index
8957 : group->segment_in_valid_range = GF_FALSE;
8958 : return GF_OK;
8959 : }
8960 :
8961 : //if earliest is not given, allow 5 segments
8962 : if (!earliest_segment_number) range_in = 4;
8963 : else range_in = latest_segment_number - earliest_segment_number;
8964 :
8965 : //loop
8966 : if (latest_segment_number <= start_number ) {
8967 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Loop in segment start numbers detected - old start %d new seg %d\n", start_number , latest_segment_number));
8968 : loop_detected = GF_TRUE;
8969 : }
8970 : //we are behind live
8971 : else if (nb_seg_diff<0) {
8972 : //we fall in the buffer of the sender, we liklely have a loss
8973 : if (nb_seg_diff + range_in >= 0) {
8974 : group->segment_in_valid_range = GF_TRUE;
8975 : return GF_OK;
8976 : }
8977 : //we are late (something wrong happen locally maybe) - If not too late (5 segs) jump to newest
8978 : else if (earliest_segment_number && (start_number + group->download_segment_index + 5 >= earliest_segment_number)) {
8979 : group->download_segment_index = latest_segment_number - start_number;
8980 : group->segment_in_valid_range = GF_FALSE;
8981 : return GF_OK;
8982 : }
8983 : //we are too late resync...
8984 : }
8985 :
8986 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Sync to live was lost - reloading MPD (loop detected %d)\n", loop_detected));
8987 : for (i=0; i< gf_list_count(dash->groups); i++) {
8988 : group = gf_list_get(dash->groups, i);
8989 : //force reinit of timeline for this group
8990 : group->start_number_at_last_ast = 0;
8991 : if (loop_detected)
8992 : group->loop_detected = GF_TRUE;
8993 : }
8994 : dash->force_mpd_update = GF_TRUE;
8995 : }
8996 : return GF_OK;
8997 : }
8998 :
8999 : //TODO segment list addressing:
9000 : return GF_OK;
9001 : }
9002 : #endif
9003 :
9004 :
9005 : GF_EXPORT
9006 109 : GF_Err gf_dash_set_max_resolution(GF_DashClient *dash, u32 width, u32 height, u8 max_display_bpp)
9007 : {
9008 109 : if (dash) {
9009 109 : dash->max_width = width;
9010 109 : dash->max_height = height;
9011 109 : dash->max_bit_per_pixel = max_display_bpp;
9012 109 : return GF_OK;
9013 : }
9014 : return GF_BAD_PARAM;
9015 : }
9016 :
9017 : GF_EXPORT
9018 118 : void gf_dash_debug_groups(GF_DashClient *dash, const u32 *groups_idx, u32 nb_groups)
9019 : {
9020 118 : dash->dbg_grps_index = groups_idx;
9021 118 : dash->nb_dbg_grps = nb_groups;
9022 118 : }
9023 :
9024 : GF_EXPORT
9025 10 : void gf_dash_split_adaptation_sets(GF_DashClient *dash)
9026 : {
9027 10 : dash->split_adaptation_set = GF_TRUE;
9028 10 : }
9029 :
9030 : GF_EXPORT
9031 118 : void gf_dash_set_low_latency_mode(GF_DashClient *dash, GF_DASHLowLatencyMode low_lat_mode)
9032 : {
9033 118 : dash->low_latency_mode = low_lat_mode;
9034 118 : }
9035 :
9036 :
9037 : GF_EXPORT
9038 259 : void gf_dash_set_user_buffer(GF_DashClient *dash, u32 buffer_time_ms)
9039 : {
9040 259 : if (dash) dash->user_buffer_ms = buffer_time_ms;
9041 259 : }
9042 :
9043 : /*returns active period start in ms*/
9044 : GF_EXPORT
9045 172 : u64 gf_dash_get_period_start(GF_DashClient *dash)
9046 : {
9047 : u64 start;
9048 : u32 i;
9049 : GF_MPD_Period *period;
9050 172 : if (!dash || !dash->mpd) return 0;
9051 :
9052 : start = 0;
9053 183 : for (i=0; i<=dash->active_period_index; i++) {
9054 183 : period = gf_list_get(dash->mpd->periods, i);
9055 183 : if (period->start) start = period->start;
9056 :
9057 183 : if (i<dash->active_period_index) start += period->duration;
9058 : }
9059 : return start;
9060 : }
9061 :
9062 :
9063 : /*returns active period duration in ms*/
9064 : GF_EXPORT
9065 132 : u64 gf_dash_get_period_duration(GF_DashClient *dash)
9066 : {
9067 : u64 start;
9068 : u32 i;
9069 : GF_MPD_Period *period = NULL;
9070 132 : if (!dash || !dash->mpd) return 0;
9071 :
9072 : start = 0;
9073 143 : for (i=0; i<=dash->active_period_index; i++) {
9074 143 : period = gf_list_get(dash->mpd->periods, i);
9075 143 : if (period->start) start = period->start;
9076 143 : if (i<dash->active_period_index) start += period->duration;
9077 : }
9078 132 : if (!period) return 0;
9079 132 : if (period->duration) return period->duration;
9080 20 : period = gf_list_get(dash->mpd->periods, dash->active_period_index+1);
9081 :
9082 20 : if (!period) {
9083 : //infered from MPD duration
9084 20 : if (dash->mpd->media_presentation_duration) return dash->mpd->media_presentation_duration - start;
9085 : //duration is not known (live)
9086 20 : if (dash->mpd->type==GF_MPD_TYPE_STATIC) {
9087 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Period duration is not computable: last period without duration and no MPD duration !\n"));
9088 : }
9089 : return 0;
9090 : }
9091 0 : if (!period->start) {
9092 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Period duration is not computable, paeriod has no duration and next period has no start !\n"));
9093 : return 0;
9094 : }
9095 0 : return period->start - start;
9096 : }
9097 :
9098 : GF_EXPORT
9099 118 : const char *gf_dash_group_get_language(GF_DashClient *dash, u32 idx)
9100 : {
9101 118 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9102 118 : if (!group) return NULL;
9103 118 : return group->adaptation_set->lang;
9104 : }
9105 :
9106 : GF_EXPORT
9107 915 : u32 gf_dash_group_get_audio_channels(GF_DashClient *dash, u32 idx)
9108 : {
9109 : GF_MPD_Descriptor *mpd_desc;
9110 915 : u32 i=0;
9111 915 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9112 915 : if (!group) return 0;
9113 :
9114 915 : while ((mpd_desc=gf_list_enum(group->adaptation_set->audio_channels, &i))) {
9115 28 : if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:23003:3:audio_channel_configuration:2011")) {
9116 56 : return atoi(mpd_desc->value);
9117 : }
9118 : }
9119 : return 0;
9120 : }
9121 :
9122 : GF_EXPORT
9123 558 : u32 gf_dash_group_get_num_qualities(GF_DashClient *dash, u32 idx)
9124 : {
9125 558 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9126 558 : if (!group) return 0;
9127 558 : return gf_list_count(group->adaptation_set->representations);
9128 : }
9129 :
9130 : GF_EXPORT
9131 118 : u32 gf_dash_group_get_num_components(GF_DashClient *dash, u32 idx)
9132 : {
9133 118 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9134 118 : if (!group) return 0;
9135 118 : return gf_list_count(group->adaptation_set->content_component);
9136 : }
9137 :
9138 : GF_EXPORT
9139 78 : char *gf_dash_group_get_template(GF_DashClient *dash, u32 idx)
9140 : {
9141 78 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9142 : GF_MPD_Representation *rep;
9143 : const char *tpl;
9144 : char *solved_template;
9145 78 : if (!group) return NULL;
9146 78 : rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
9147 78 : if (!rep)
9148 0 : rep = gf_list_get(group->adaptation_set->representations, 0);
9149 :
9150 : tpl = NULL;
9151 78 : if (rep && rep->segment_template) tpl = rep->segment_template->media;
9152 52 : if (!tpl && group->adaptation_set && group->adaptation_set->segment_template) tpl = group->adaptation_set->segment_template->media;
9153 78 : if (!tpl && group->period && group->period->segment_template) tpl = group->period->segment_template->media;
9154 :
9155 78 : if (tpl) {
9156 : u64 range_start, range_end, segment_duration_in_ms;
9157 63 : gf_mpd_resolve_url(dash->mpd, rep, group->adaptation_set, group->period, "", 0, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE, 0, 0, &solved_template, &range_start, &range_end, &segment_duration_in_ms, NULL, NULL, NULL, NULL);
9158 63 : return solved_template;
9159 : }
9160 15 : if (dash->is_m3u8 && rep) {
9161 : char *ext;
9162 : u32 i, len, last_num, last_non_num;
9163 15 : GF_MPD_SegmentURL *first_seg = gf_list_get(rep->segment_list->segment_URLs, 0);
9164 : //GF_MPD_SegmentURL *last_seg = gf_list_last(rep->segment_list->segment_URLs);
9165 15 : if (!first_seg) return NULL;
9166 15 : if (!first_seg->media) return NULL;
9167 15 : if (first_seg->media_range) return NULL;
9168 :
9169 15 : solved_template = NULL;
9170 15 : ext = strrchr(first_seg->media, '.');
9171 15 : if (ext) ext[0] = 0;
9172 15 : gf_dynstrcat(&solved_template, first_seg->media, NULL);
9173 15 : if (ext) ext[0] = '.';
9174 15 : len = (u32) strlen(solved_template);
9175 : last_num = last_non_num = 0;
9176 45 : for (i=len; i>0; i--) {
9177 30 : if (isdigit(solved_template[i-1])) {
9178 15 : if (!last_num) last_num = i-1;
9179 : } else {
9180 15 : if (last_num) {
9181 : last_non_num = i-1;
9182 : break;
9183 : }
9184 : }
9185 : }
9186 15 : if (!last_non_num || (last_num>=last_non_num+1)) {
9187 : u32 num;
9188 : char szVal[100];
9189 15 : solved_template[last_num] = 0;
9190 30 : num = atoi(solved_template+last_non_num+1);
9191 : snprintf(szVal, 100, "%u", num);
9192 15 : len = (u32) strlen(szVal);
9193 15 : if (len < last_num - last_non_num) {
9194 0 : u32 pad = last_num - last_non_num - len;
9195 : snprintf(szVal, 100, "$Number%%0%dd$", pad);
9196 0 : gf_dynstrcat(&solved_template, szVal, NULL);
9197 : } else {
9198 15 : gf_dynstrcat(&solved_template, "$Number$", NULL);
9199 : }
9200 15 : gf_dynstrcat(&solved_template, first_seg->media + last_num + 1, NULL);
9201 15 : return solved_template;
9202 : }
9203 :
9204 : }
9205 :
9206 : return NULL;
9207 : }
9208 :
9209 :
9210 : GF_EXPORT
9211 915 : GF_Err gf_dash_group_get_quality_info(GF_DashClient *dash, u32 idx, u32 quality_idx, GF_DASHQualityInfo *quality)
9212 : {
9213 : GF_MPD_Fractional *sar;
9214 : u32 timescale = 0;
9215 915 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9216 : GF_MPD_Representation *rep;
9217 915 : if (!group || !quality) return GF_BAD_PARAM;
9218 915 : rep = gf_list_get(group->adaptation_set->representations, quality_idx);
9219 915 : if (!rep) return GF_BAD_PARAM;
9220 :
9221 : memset(quality, 0, sizeof(GF_DASHQualityInfo));
9222 915 : quality->mime = rep->mime_type ? rep->mime_type : group->adaptation_set->mime_type;
9223 915 : quality->codec = rep->codecs ? rep->codecs : group->adaptation_set->codecs;
9224 915 : quality->disabled = rep->playback.disabled;
9225 915 : sar = rep->framerate ? rep->framerate : group->adaptation_set->framerate;
9226 915 : if (sar) {
9227 596 : quality->fps_den = sar->den;
9228 596 : quality->fps_num = sar->num;
9229 : }
9230 915 : quality->height = rep->height ? rep->height : group->adaptation_set->height;
9231 915 : quality->width = rep->width ? rep->width : group->adaptation_set->width;
9232 915 : quality->nb_channels = gf_dash_group_get_audio_channels(dash, idx);
9233 915 : sar = rep->sar ? rep->sar : group->adaptation_set->sar;
9234 915 : if (sar) {
9235 590 : quality->par_num = sar->num;
9236 590 : quality->par_den = sar->den;
9237 : }
9238 915 : quality->sample_rate = rep->samplerate ? rep->samplerate : group->adaptation_set->samplerate;
9239 915 : quality->bandwidth = rep->bandwidth;
9240 915 : quality->ID = rep->id;
9241 915 : quality->interlaced = (rep->scan_type == GF_MPD_SCANTYPE_INTERLACED) ? 1 : ( (group->adaptation_set->scan_type == GF_MPD_SCANTYPE_INTERLACED) ? 1 : 0);
9242 :
9243 915 : if (group->was_segment_base && rep->segment_list)
9244 34 : quality->seg_urls = rep->segment_list->segment_URLs;
9245 :
9246 : //scalable rep, selected quality is max_complementary_rep_index
9247 915 : if (group->base_rep_index_plus_one) {
9248 0 : quality->is_selected = (quality_idx==group->max_complementary_rep_index) ? 1 : 0;
9249 : } else {
9250 915 : quality->is_selected = (quality_idx==group->active_rep_index) ? 1 : 0;
9251 : }
9252 :
9253 915 : if (rep->segment_template) {
9254 240 : if (!quality->ast_offset) quality->ast_offset = rep->segment_template->availability_time_offset;
9255 240 : if (!timescale) timescale = rep->segment_template->timescale;
9256 240 : if (!quality->average_duration) quality->average_duration = (Double) rep->segment_template->duration;
9257 : }
9258 915 : if (group->adaptation_set->segment_template) {
9259 524 : if (!quality->ast_offset) quality->ast_offset = group->adaptation_set->segment_template->availability_time_offset;
9260 524 : if (!timescale) timescale = group->adaptation_set->segment_template->timescale;
9261 524 : if (!quality->average_duration) quality->average_duration = (Double) group->adaptation_set->segment_template->duration;
9262 : }
9263 915 : if (group->period->segment_template) {
9264 0 : if (!quality->ast_offset) quality->ast_offset = group->period->segment_template->availability_time_offset;
9265 0 : if (!timescale) timescale = group->period->segment_template->timescale;
9266 0 : if (!quality->average_duration) quality->average_duration = (Double) group->period->segment_template->duration;
9267 : }
9268 :
9269 915 : if (timescale) {
9270 686 : quality->average_duration /= timescale;
9271 : } else {
9272 229 : quality->average_duration = 0;
9273 : }
9274 : return GF_OK;
9275 : }
9276 :
9277 :
9278 1928 : static Bool gf_dash_group_enum_descriptor_list(GF_DashClient *dash, u32 idx, GF_List *descs, const char **desc_id, const char **desc_scheme, const char **desc_value)
9279 : {
9280 : GF_MPD_Descriptor *mpd_desc;
9281 1928 : if (idx>=gf_list_count(descs)) return 0;
9282 10 : mpd_desc = gf_list_get(descs, idx);
9283 10 : if (desc_value) *desc_value = mpd_desc->value;
9284 10 : if (desc_scheme) *desc_scheme = mpd_desc->scheme_id_uri;
9285 10 : if (desc_id) *desc_id = mpd_desc->id;
9286 : return 1;
9287 : }
9288 :
9289 : GF_EXPORT
9290 1928 : Bool gf_dash_group_enum_descriptor(GF_DashClient *dash, u32 group_idx, GF_DashDescriptorType desc_type, u32 desc_idx, const char **desc_id, const char **desc_scheme, const char **desc_value)
9291 : {
9292 : GF_List *descs = NULL;
9293 1928 : GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
9294 1928 : if (!group) return 0;
9295 1928 : switch (desc_type) {
9296 553 : case GF_MPD_DESC_ACCESSIBILITY:
9297 553 : descs = group->adaptation_set->accessibility;
9298 553 : break;
9299 0 : case GF_MPD_DESC_AUDIOCONFIG:
9300 0 : descs = group->adaptation_set->audio_channels;
9301 0 : break;
9302 0 : case GF_MPD_DESC_CONTENT_PROTECTION:
9303 0 : descs = group->adaptation_set->content_protection;
9304 0 : break;
9305 267 : case GF_MPD_DESC_ESSENTIAL_PROPERTIES:
9306 267 : descs = group->adaptation_set->essential_properties;
9307 267 : break;
9308 2 : case GF_MPD_DESC_SUPPLEMENTAL_PROPERTIES:
9309 2 : descs = group->adaptation_set->supplemental_properties;
9310 2 : break;
9311 0 : case GF_MPD_DESC_FRAME_PACKING:
9312 0 : descs = group->adaptation_set->frame_packing;
9313 0 : break;
9314 553 : case GF_MPD_DESC_ROLE:
9315 553 : descs = group->adaptation_set->role;
9316 553 : break;
9317 553 : case GF_MPD_DESC_RATING:
9318 553 : descs = group->adaptation_set->rating;
9319 553 : break;
9320 0 : case GF_MPD_DESC_VIEWPOINT:
9321 0 : descs = group->adaptation_set->viewpoint;
9322 0 : break;
9323 : default:
9324 : return 0;
9325 : }
9326 1928 : return gf_dash_group_enum_descriptor_list(dash, desc_idx, descs, desc_id, desc_scheme, desc_value);
9327 : }
9328 :
9329 : GF_EXPORT
9330 411 : Bool gf_dash_get_automatic_switching(GF_DashClient *dash)
9331 : {
9332 411 : return (dash && dash->disable_switching) ? GF_FALSE : GF_TRUE;
9333 : }
9334 :
9335 : GF_EXPORT
9336 8 : GF_Err gf_dash_set_automatic_switching(GF_DashClient *dash, Bool enable_switching)
9337 : {
9338 8 : if (!dash) return GF_BAD_PARAM;
9339 8 : dash->disable_switching = !enable_switching;
9340 8 : return GF_OK;
9341 : }
9342 :
9343 : GF_EXPORT
9344 119 : GF_Err gf_dash_group_select_quality(GF_DashClient *dash, u32 idx, const char *ID, u32 q_idx)
9345 : {
9346 : u32 i, count;
9347 119 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9348 119 : if (!group) return GF_BAD_PARAM;
9349 :
9350 1 : if (!ID) {
9351 1 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, q_idx);
9352 1 : if (!rep) return GF_BAD_PARAM;
9353 1 : group->force_representation_idx_plus_one = q_idx+1;
9354 1 : group->force_switch_bandwidth = 1;
9355 1 : return GF_OK;
9356 : }
9357 :
9358 0 : count = gf_list_count(group->adaptation_set->representations);
9359 0 : for (i=0; i<count; i++) {
9360 0 : GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, i);
9361 0 : if (rep->id && !strcmp(rep->id, ID)) {
9362 0 : group->force_representation_idx_plus_one = i+1;
9363 0 : group->force_switch_bandwidth = 1;
9364 0 : return GF_OK;
9365 : }
9366 : }
9367 : return GF_BAD_PARAM;
9368 : }
9369 :
9370 : GF_EXPORT
9371 561 : s32 gf_dash_group_get_active_quality(GF_DashClient *dash, u32 idx)
9372 : {
9373 561 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9374 561 : if (!group) return -1;
9375 561 : return group->active_rep_index;
9376 : }
9377 :
9378 : GF_EXPORT
9379 3 : GF_Err gf_dash_set_timeshift(GF_DashClient *dash, u32 ms_in_timeshift)
9380 : {
9381 3 : if (!dash) return GF_BAD_PARAM;
9382 3 : dash->initial_time_shift_value = ms_in_timeshift;
9383 3 : return GF_OK;
9384 : }
9385 :
9386 : GF_EXPORT
9387 192 : Double gf_dash_get_timeshift_buffer_pos(GF_DashClient *dash)
9388 : {
9389 192 : return dash ? dash->prev_time_in_tsb / 1000.0 : 0.0;
9390 : }
9391 :
9392 : GF_EXPORT
9393 135 : void gf_dash_group_set_codec_stat(GF_DashClient *dash, u32 idx, u32 avg_dec_time, u32 max_dec_time, u32 irap_avg_dec_time, u32 irap_max_dec_time, Bool codec_reset, Bool decode_only_rap)
9394 : {
9395 135 : GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, idx);
9396 135 : if (!group) return;
9397 135 : group->avg_dec_time = avg_dec_time;
9398 135 : group->max_dec_time = max_dec_time;
9399 135 : group->irap_avg_dec_time = irap_avg_dec_time;
9400 135 : group->irap_max_dec_time = irap_max_dec_time;
9401 135 : group->codec_reset = codec_reset;
9402 135 : group->decode_only_rap = decode_only_rap;
9403 : }
9404 :
9405 : GF_EXPORT
9406 1145 : void gf_dash_group_set_buffer_levels(GF_DashClient *dash, u32 idx, u32 buffer_min_ms, u32 buffer_max_ms, u32 buffer_occupancy_ms)
9407 : {
9408 1145 : GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, idx);
9409 1145 : if (!group) return;
9410 1145 : group->buffer_min_ms = buffer_min_ms;
9411 1145 : group->buffer_max_ms = buffer_max_ms;
9412 1145 : if (group->max_buffer_playout_ms > buffer_max_ms) {
9413 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Max buffer %d less than max playout buffer %d, overwriting max playout buffer\n", buffer_max_ms, group->max_buffer_playout_ms));
9414 0 : group->max_buffer_playout_ms = buffer_max_ms;
9415 : }
9416 1145 : group->buffer_occupancy_ms = buffer_occupancy_ms;
9417 : }
9418 :
9419 :
9420 : GF_EXPORT
9421 118 : void gf_dash_disable_speed_adaptation(GF_DashClient *dash, Bool disable)
9422 : {
9423 118 : dash->disable_speed_adaptation = disable;
9424 118 : }
9425 :
9426 : GF_EXPORT
9427 118 : void gf_dash_override_ntp(GF_DashClient *dash, u64 server_ntp)
9428 : {
9429 118 : if (server_ntp) {
9430 0 : dash->utc_drift_estimate = gf_net_get_ntp_diff_ms(server_ntp);
9431 0 : dash->ntp_forced = 1;
9432 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Overwriting local NTP "LLU" to given one "LLU"\n", gf_net_get_ntp_ts(), server_ntp));
9433 : } else {
9434 118 : dash->utc_drift_estimate = 0;
9435 118 : dash->ntp_forced = 0;
9436 : }
9437 118 : }
9438 :
9439 : GF_EXPORT
9440 163 : s32 gf_dash_get_utc_drift_estimate(GF_DashClient *dash) {
9441 163 : return (s32) dash->utc_drift_estimate;
9442 : }
9443 :
9444 : GF_EXPORT
9445 411 : GF_DASHTileAdaptationMode gf_dash_get_tile_adaptation_mode(GF_DashClient *dash)
9446 : {
9447 411 : return dash->tile_adapt_mode;
9448 : }
9449 :
9450 : GF_EXPORT
9451 118 : void gf_dash_set_tile_adaptation_mode(GF_DashClient *dash, GF_DASHTileAdaptationMode mode, u32 tile_rate_decrease)
9452 : {
9453 : u32 i;
9454 118 : dash->tile_adapt_mode = mode;
9455 118 : dash->tile_rate_decrease = (tile_rate_decrease<100) ? tile_rate_decrease : 100;
9456 118 : for (i=0; i<gf_list_count(dash->groups); i++) {
9457 0 : GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, i);
9458 0 : if (group->srd_desc) gf_dash_set_tiles_quality(dash, group->srd_desc, GF_TRUE);
9459 : }
9460 118 : }
9461 :
9462 : GF_EXPORT
9463 118 : void gf_dash_disable_low_quality_tiles(GF_DashClient *dash, Bool disable_tiles)
9464 : {
9465 118 : dash->disable_low_quality_tiles = disable_tiles;
9466 118 : }
9467 :
9468 : GF_EXPORT
9469 558 : Bool gf_dash_group_get_srd_info(GF_DashClient *dash, u32 idx, u32 *srd_id, u32 *srd_x, u32 *srd_y, u32 *srd_w, u32 *srd_h, u32 *srd_width, u32 *srd_height)
9470 : {
9471 558 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9472 558 : if (!group || !group->srd_desc) return GF_FALSE;
9473 :
9474 : if (group->srd_desc) {
9475 140 : if (srd_id) (*srd_id) = group->srd_desc->id;
9476 140 : if (srd_width) (*srd_width) = group->srd_desc->srd_fw;
9477 140 : if (srd_height) (*srd_height) = group->srd_desc->srd_fh;
9478 : }
9479 :
9480 140 : if (srd_x) (*srd_x) = group->srd_x;
9481 140 : if (srd_y) (*srd_y) = group->srd_y;
9482 140 : if (srd_w) (*srd_w) = group->srd_w;
9483 140 : if (srd_h) (*srd_h) = group->srd_h;
9484 :
9485 :
9486 : return GF_TRUE;
9487 : }
9488 :
9489 : GF_EXPORT
9490 118 : void gf_dash_ignore_xlink(GF_DashClient *dash, Bool ignore_xlink)
9491 : {
9492 118 : dash->ignore_xlink = ignore_xlink;
9493 118 : }
9494 :
9495 : GF_EXPORT
9496 118 : void gf_dash_set_route_ast_shift(GF_DashClient *dash, u32 ast_shift)
9497 : {
9498 118 : dash->route_ast_shift = ast_shift;
9499 118 : }
9500 :
9501 : GF_EXPORT
9502 259 : GF_Err gf_dash_group_set_max_buffer_playout(GF_DashClient *dash, u32 idx, u32 max_buffer_playout_ms)
9503 : {
9504 259 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9505 259 : if (!group) return GF_BAD_PARAM;
9506 259 : group->max_buffer_playout_ms = max_buffer_playout_ms;
9507 259 : return GF_OK;
9508 : }
9509 :
9510 : GF_EXPORT
9511 119 : GF_Err gf_dash_group_set_quality_degradation_hint(GF_DashClient *dash, u32 idx, u32 quality_degradation_hint)
9512 : {
9513 119 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9514 119 : if (!group) return GF_BAD_PARAM;
9515 :
9516 119 : group->quality_degradation_hint = quality_degradation_hint;
9517 119 : if (group->quality_degradation_hint > 100) group->quality_degradation_hint=100;
9518 : return GF_OK;
9519 : }
9520 :
9521 :
9522 : GF_EXPORT
9523 40 : GF_Err gf_dash_group_set_visible_rect(GF_DashClient *dash, u32 idx, u32 min_x, u32 max_x, u32 min_y, u32 max_y, Bool is_gaze)
9524 : {
9525 : u32 i, count;
9526 40 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9527 40 : if (!group) return GF_BAD_PARAM;
9528 :
9529 40 : if (!min_x && !max_x && !min_y && !max_y) {
9530 0 : group->quality_degradation_hint = 0;
9531 : }
9532 :
9533 : //for both regular or tiled, store visible width/height
9534 40 : group->hint_visible_width = max_x - min_x;
9535 40 : group->hint_visible_height = max_y - min_y;
9536 :
9537 40 : if (!group->groups_depending_on) return GF_OK;
9538 :
9539 40 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group Visible rect %d,%d,%d,%d \n", min_x, max_x, min_y, max_y));
9540 40 : count = gf_list_count(group->groups_depending_on);
9541 400 : for (i=0; i<count; i++) {
9542 : Bool is_visible = GF_TRUE;
9543 360 : GF_DASH_Group *a_group = gf_list_get(group->groups_depending_on, i);
9544 : u32 old_hint;
9545 360 : if (!a_group->srd_w || !a_group->srd_h) continue;
9546 :
9547 360 : old_hint = a_group->quality_degradation_hint;
9548 360 : if (is_gaze) {
9549 :
9550 0 : if (min_x < a_group->srd_x)
9551 : is_visible = GF_FALSE;
9552 0 : else if (min_x > a_group->srd_x + a_group->srd_w)
9553 : is_visible = GF_FALSE;
9554 0 : else if (min_y < a_group->srd_y)
9555 : is_visible = GF_FALSE;
9556 0 : else if (min_y > a_group->srd_y + a_group->srd_h)
9557 : is_visible = GF_FALSE;
9558 :
9559 : } else {
9560 :
9561 : //single rectangle case
9562 360 : if (min_x<max_x) {
9563 360 : if (a_group->srd_x+a_group->srd_w <min_x) is_visible = GF_FALSE;
9564 240 : else if (a_group->srd_x>max_x) is_visible = GF_FALSE;
9565 : } else {
9566 0 : if ( (a_group->srd_x>max_x) && (a_group->srd_x+a_group->srd_w<min_x)) is_visible = GF_FALSE;
9567 : }
9568 :
9569 360 : if (a_group->srd_y>max_y) is_visible = GF_FALSE;
9570 300 : else if (a_group->srd_y+a_group->srd_h < min_y) is_visible = GF_FALSE;
9571 :
9572 : }
9573 240 : a_group->quality_degradation_hint = is_visible ? 0 : 100;
9574 360 : if (old_hint != a_group->quality_degradation_hint) {
9575 26 : GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group SRD %d,%d,%d,%d is %s\n", a_group->srd_x, a_group->srd_w, a_group->srd_y, a_group->srd_h, is_visible ? "visible" : "hidden"));
9576 :
9577 : //remember to update tile quality for non-custom algo
9578 26 : a_group->update_tile_qualities = GF_TRUE;
9579 26 : group->update_tile_qualities = GF_TRUE;
9580 : }
9581 : }
9582 : return GF_OK;
9583 : }
9584 :
9585 9 : void gf_dash_set_group_download_state(GF_DashClient *dash, u32 idx, u32 cur_dep_idx, GF_Err err)
9586 : {
9587 : GF_MPD_Representation *rep;
9588 : Bool has_dep_following;
9589 : char *key_url, *url;
9590 : GF_DASH_Group *base_group;
9591 9 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9592 9 : if (!group) return;
9593 :
9594 : //we forced early fetch because demux was empty, consider all errors as 404
9595 9 : if (group->force_early_fetch && err) {
9596 : err = GF_URL_ERROR;
9597 : }
9598 :
9599 8 : if (!err) {
9600 0 : group->force_early_fetch = GF_FALSE;
9601 0 : return;
9602 : }
9603 9 : if (!group->nb_cached_segments) return;
9604 9 : rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
9605 :
9606 18 : while (group->cached[0].representation_index < cur_dep_idx) {
9607 0 : gf_free(group->cached[0].key_url);
9608 0 : gf_free(group->cached[0].url);
9609 0 : group->nb_cached_segments--;
9610 0 : memmove(&group->cached[0], &group->cached[1], sizeof(segment_cache_entry) * group->nb_cached_segments);
9611 0 : if (!group->nb_cached_segments)
9612 : break;
9613 : }
9614 9 : has_dep_following = (group->cached[0].flags & SEG_FLAG_DEP_FOLLOWING) ? GF_TRUE : GF_FALSE;
9615 : //detach URL and key URL, they will be freed in on_group_download_error below
9616 9 : key_url = group->cached[0].key_url;
9617 9 : url = group->cached[0].url;
9618 9 : group->nb_cached_segments--;
9619 : assert(!group->nb_cached_segments);
9620 :
9621 : base_group = group;
9622 9 : while (base_group->depend_on_group) {
9623 : base_group = base_group->depend_on_group;
9624 : }
9625 9 : on_group_download_error(dash, group, base_group, err, rep, url, key_url, has_dep_following);
9626 :
9627 9 : if (dash->speed>=0) {
9628 9 : group->download_segment_index--;
9629 : } else {
9630 0 : group->download_segment_index++;
9631 : }
9632 : }
9633 :
9634 2981 : void gf_dash_group_store_stats(GF_DashClient *dash, u32 idx, u32 dep_rep_idx, u32 bytes_per_sec, u64 file_size, Bool is_broadcast, u64 us_since_start)
9635 : {
9636 2981 : GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9637 2981 : if (!group) return;
9638 2981 : if (!group->nb_cached_segments) return;
9639 :
9640 2981 : if (group->groups_depending_on) {
9641 1607 : Bool is_last = (dep_rep_idx == gf_list_count(group->groups_depending_on)) ? GF_TRUE : GF_FALSE;
9642 1607 : if (dep_rep_idx) {
9643 1446 : group = gf_list_get(group->groups_depending_on, dep_rep_idx-1);
9644 1446 : if (!group)
9645 : return;
9646 : }
9647 1607 : dash_store_stats(dash, group, bytes_per_sec, (u32) file_size, is_broadcast, 1+dep_rep_idx, us_since_start);
9648 :
9649 1607 : if (is_last)
9650 160 : dash_global_rate_adaptation(dash, GF_FALSE);
9651 : } else {
9652 1374 : dash_store_stats(dash, group, bytes_per_sec, (u32) file_size, is_broadcast, 1+dep_rep_idx, us_since_start);
9653 :
9654 1374 : dash_global_rate_adaptation(dash, GF_FALSE);
9655 : }
9656 : }
9657 :
9658 37636 : u32 gf_dash_get_min_wait_ms(GF_DashClient *dash)
9659 : {
9660 37636 : if (dash && dash->min_wait_ms_before_next_request) {
9661 105 : u32 ellapsed = gf_sys_clock() - dash->min_wait_sys_clock;
9662 105 : if (ellapsed < dash->min_wait_ms_before_next_request) dash->min_wait_ms_before_next_request -= ellapsed;
9663 26 : else dash->min_wait_ms_before_next_request = 0;
9664 105 : return dash->min_wait_ms_before_next_request;
9665 : }
9666 : return 0;
9667 : }
9668 :
9669 : GF_EXPORT
9670 6 : Bool gf_dash_all_groups_done(GF_DashClient *dash)
9671 : {
9672 6 : u32 i, count = gf_list_count(dash->groups);
9673 6 : for (i=0; i<count; i++) {
9674 0 : GF_DASH_Group *group = gf_list_get(dash->groups, i);
9675 0 : if (group->selection != GF_DASH_GROUP_SELECTED) continue;
9676 0 : if (!group->done) return GF_FALSE;
9677 0 : if (group->nb_cached_segments) return GF_FALSE;
9678 : }
9679 : return GF_TRUE;
9680 : }
9681 :
9682 : GF_EXPORT
9683 118 : void gf_dash_set_period_xlink_query_string(GF_DashClient *dash, const char *query_string)
9684 : {
9685 118 : if (dash) dash->query_string = query_string;
9686 118 : }
9687 :
9688 : GF_EXPORT
9689 413 : s32 gf_dash_group_get_as_id(GF_DashClient *dash, u32 group_idx)
9690 : {
9691 : GF_DASH_Group *group;
9692 413 : if (!dash) return 0;
9693 413 : group = gf_list_get(dash->groups, group_idx);
9694 413 : if (!group) return 0;
9695 413 : return group->adaptation_set->id;
9696 : }
9697 :
9698 :
9699 0 : GF_Err gf_dash_group_push_tfrf(GF_DashClient *dash, u32 group_idx, void *_tfrf, u32 timescale)
9700 : {
9701 : GF_MSSTimeRefBox *tfrf = (GF_MSSTimeRefBox *)_tfrf;
9702 : u32 i;
9703 : GF_MPD_SegmentTemplate *stpl;
9704 : GF_DASH_Group *group;
9705 0 : if (!dash) return GF_BAD_PARAM;
9706 0 : if (!dash->is_smooth) return GF_OK;
9707 0 : if (dash->mpd->type != GF_MPD_TYPE_DYNAMIC) return GF_OK;
9708 :
9709 0 : group = gf_list_get(dash->groups, group_idx);
9710 0 : if (!group) return GF_BAD_PARAM;
9711 0 : if (!group->adaptation_set || !group->adaptation_set->segment_template || !group->adaptation_set->segment_template->segment_timeline)
9712 : return GF_BAD_PARAM;
9713 :
9714 : stpl = group->adaptation_set->segment_template;
9715 :
9716 0 : for (i=0; i<tfrf->frags_count; i++) {
9717 : u32 k, nb_segs;
9718 : Bool exists = GF_FALSE;
9719 : GF_MPD_SegmentTimelineEntry *se = NULL;
9720 : u64 start = 0;
9721 0 : u64 frag_time = tfrf->frags[i].absolute_time_in_track_timescale;
9722 0 : u64 frag_dur = tfrf->frags[i].fragment_duration_in_track_timescale;
9723 0 : if (timescale != stpl->timescale) {
9724 0 : frag_time *= stpl->timescale;
9725 0 : frag_time /= timescale;
9726 0 : frag_dur *= stpl->timescale;
9727 0 : frag_dur /= timescale;
9728 : }
9729 0 : nb_segs = gf_list_count(stpl->segment_timeline->entries);
9730 0 : for (k=0; k<nb_segs; k++) {
9731 : u64 se_dur;
9732 0 : se = gf_list_get(stpl->segment_timeline->entries, k);
9733 0 : if (se->start_time)
9734 : start = se->start_time;
9735 0 : se_dur = se->duration * (1+se->repeat_count);
9736 0 : if (start + se_dur <= frag_time) {
9737 : start += se_dur;
9738 0 : continue;
9739 : }
9740 : exists = GF_TRUE;
9741 : break;
9742 : }
9743 0 : if (!exists) {
9744 0 : if (se && se->duration==frag_dur) {
9745 0 : se->repeat_count++;
9746 : } else {
9747 0 : GF_SAFEALLOC(se, GF_MPD_SegmentTimelineEntry);
9748 0 : if (frag_time != start)
9749 0 : se->start_time = frag_time;
9750 0 : se->duration = (u32) frag_dur;
9751 0 : gf_list_add(stpl->segment_timeline->entries, se);
9752 : }
9753 0 : GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Smooth push new fragment start "LLU" dur "LLU" (inserted at start_time "LLU")\n", frag_time, frag_dur, start));
9754 0 : group->nb_segments_in_rep++;
9755 : }
9756 : }
9757 : return GF_OK;
9758 : }
9759 :
9760 : #endif //GPAC_DISABLE_DASH_CLIENT
9761 :
|