1 /**
2 * GPAC - Multimedia Framework C SDK
3 *
4 * Authors: Jean Le Feuvre, Cyril Concolato
5 * Copyright (c) Telecom ParisTech 2010-2020
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/thread.h>
27 #include <gpac/network.h>
28 #include <gpac/dash.h>
29 #include <gpac/mpd.h>
30 #include <gpac/internal/m3u8.h>
31 #include <gpac/internal/isomedia_dev.h>
32 #include <gpac/base_coding.h>
33 #include <string.h>
34 #include <sys/stat.h>
35
36 #include <math.h>
37
38
39 #ifndef GPAC_DISABLE_DASH_CLIENT
40
41 /*ISO 639 languages*/
42 #include <gpac/iso639.h>
43
44 /*set to 1 if you want MPD to use SegmentTemplate if possible instead of SegmentList*/
45 #define M3U8_TO_MPD_USE_TEMPLATE 0
46 /*set to 1 if you want MPD to use SegmentTimeline*/
47 #define M3U8_TO_MPD_USE_SEGTIMELINE 0
48
49 typedef enum {
50 GF_DASH_STATE_STOPPED = 0,
51 /*period setup and playback chain creation*/
52 GF_DASH_STATE_SETUP,
53 /*request to start playback chain*/
54 GF_DASH_STATE_CONNECTING,
55 GF_DASH_STATE_RUNNING,
56 } GF_DASH_STATE;
57
58
59 //shifts AST in the past(>0) or future (<0) so that client starts request in the future or in the past
60 //#define FORCE_DESYNC 4000
61
62 /* WARNING: GF_DASH_Group does not represent a Group in DASH
63 It corresponds to an AdaptationSet with additional live information not present in the MPD
64 (e.g. current active representation)
65 */
66 typedef struct __dash_group GF_DASH_Group;
67
68 struct __dash_client
69 {
70 GF_DASHFileIO *dash_io;
71
72 /*interface to mpd parser - get rid of this and use the DASHIO instead ?*/
73 GF_FileDownload getter;
74
75 char *base_url;
76
77 u32 max_cache_duration, max_width, max_height;
78 u8 max_bit_per_pixel;
79 u32 auto_switch_count;
80 Bool keep_files, disable_switching, allow_local_mpd_update, estimate_utc_drift, ntp_forced;
81 Bool is_m3u8, is_smooth;
82 Bool split_adaptation_set;
83 GF_DASHLowLatencyMode low_latency_mode;
84 //set when MPD downloading fails. Will resetup DASH live once MPD is sync again
85 Bool in_error;
86
87 u64 mpd_fetch_time;
88 GF_DASHInitialSelectionMode first_select_mode;
89
90 /* MPD downloader*/
91 GF_DASHFileIOSession mpd_dnload;
92 /* MPD */
93 GF_MPD *mpd;
94 /* number of time the MPD has been reloaded and last update time*/
95 u32 reload_count, last_update_time;
96 /*signature of last MPD*/
97 u8 lastMPDSignature[GF_SHA1_DIGEST_SIZE];
98 /*mime type of media segments (m3u8)*/
99 char *mimeTypeForM3U8Segments;
100
101 /* active period in MPD */
102 u32 active_period_index;
103 u32 reinit_period_index;
104 u32 request_period_switch;
105
106 Bool next_period_checked;
107
108 u64 start_time_in_active_period;
109
110 Bool ignore_mpd_duration;
111 u32 initial_time_shift_value;
112
113 const char *query_string;
114
115 /*list of groups in the active period*/
116 GF_List *groups;
117
118 /*Main Thread handling segment downloads and MPD/M3U8 update*/
119 GF_Thread *dash_thread;
120 /*mutex for MPD updates and group access*/
121 GF_Mutex *dash_mutex;
122
123 /* one of the above state*/
124 GF_DASH_STATE dash_state;
125 Bool mpd_stop_request;
126 Bool in_period_setup;
127 Bool all_groups_done_notified;
128
129 s64 utc_drift_estimate;
130 s32 utc_shift;
131
132 Double start_range_period;
133
134 Double speed;
135 Bool is_rt_speed;
136 u32 probe_times_before_switch;
137 Bool agressive_switching;
138 u32 min_wait_ms_before_next_request;
139 u32 min_wait_sys_clock;
140
141 Bool force_mpd_update;
142
143 u32 user_buffer_ms;
144
145 u32 min_timeout_between_404, segment_lost_after_ms;
146
147 GF_DASHThreadMode thread_mode;
148
149 Bool ignore_xlink;
150
151 //0: not atsc - 1: atsc but clock not init 2- atsc clock init
152 u32 atsc_clock_state;
153 //atsc AST shift in ms
154 u32 atsc_ast_shift;
155
156 Bool initial_period_tunein;
157
158 //in ms
159 u32 time_in_tsb, prev_time_in_tsb;
160 u32 tsb_exceeded;
161 s32 debug_group_index;
162 Bool disable_speed_adaptation;
163
164 Bool period_groups_setup;
165 u32 tile_rate_decrease;
166 GF_DASHTileAdaptationMode tile_adapt_mode;
167
168 GF_List *SRDs;
169
170 GF_DASHAdaptationAlgorithm adaptation_algorithm;
171
172 s32 (*rate_adaptation_algo)(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
173 u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
174 GF_MPD_Representation *rep, Bool go_up_bitrate);
175
176 GF_Err (*rate_adaptation_download_monitor)(GF_DashClient *dash, GF_DASH_Group *group);
177 };
178
179 static void gf_dash_seek_group(GF_DashClient *dash, GF_DASH_Group *group, Double seek_to, Bool is_dynamic);
180
181
182 typedef struct
183 {
184 char *cache;
185 char *url;
186 u64 start_range, end_range;
187 /*representation index in adaptation_set->representations*/
188 u32 representation_index;
189 Bool loop_detected;
190 u32 duration;
191 char *key_url;
192 bin128 key_IV;
193 Bool has_dep_following;
194 } segment_cache_entry;
195
196 typedef enum
197 {
198 /*set if group cannot be selected (wrong MPD)*/
199 GF_DASH_GROUP_NOT_SELECTABLE = 0,
200 GF_DASH_GROUP_NOT_SELECTED,
201 GF_DASH_GROUP_SELECTED,
202 } GF_DASHGroupSelection;
203
204 /*this structure Group is the implementation of the adaptationSet element of the MPD.*/
205 struct __dash_group
206 {
207 GF_DashClient *dash;
208
209 /*pointer to adaptation set*/
210 GF_MPD_AdaptationSet *adaptation_set;
211 /*pointer to active period*/
212 GF_MPD_Period *period;
213
214 /*active representation index in adaptation_set->representations*/
215 u32 active_rep_index;
216
217 u32 prev_active_rep_index;
218
219 Bool timeline_setup;
220
221 GF_DASHGroupSelection selection;
222
223 /*may be mpd@time_shift_buffer_depth or rep@time_shift_buffer_depth*/
224 u32 time_shift_buffer_depth;
225
226 Bool bitstream_switching, dont_delete_first_segment;
227 GF_DASH_Group *depend_on_group;
228 Bool done;
229 //if set, will redownload the last segment partially downloaded
230 Bool force_switch_bandwidth;
231 Bool min_bandwidth_selected;
232 u32 download_start_time;
233 u32 active_bitrate, max_bitrate, min_bitrate;
234 u32 min_representation_bitrate;
235
236 u32 nb_segments_in_rep;
237
238 /* Segment duration as advertised in the MPD
239 for the real duration of the segment being downloaded see current_downloaded_segment_duration */
240 Double segment_duration;
241
242 Double start_playback_range;
243
244 Bool group_setup;
245
246 Bool was_segment_base;
247 /*local file playback, do not delete them*/
248 Bool local_files;
249 /*next segment to download for this group - negative number to take into account SN wrapping*/
250 s32 download_segment_index;
251 /*number of segments pruged since the start of the period*/
252 u32 nb_segments_purged;
253
254 u32 nb_retry_on_last_segment;
255 s32 start_number_at_last_ast;
256 u64 ast_at_init;
257
258 /*next file (cached) to delete at next GF_NET_SERVICE_QUERY_NEXT for this group*/
259 char * urlToDeleteNext;
260 volatile u32 max_cached_segments, nb_cached_segments, max_buffer_segments;
261 segment_cache_entry *cached;
262
263 GF_DASHFileIOSession segment_download;
264 //0: not set, 1: abort because group has been stopped - 2: abort because bandwidth was too low
265 u32 download_abort_type;
266 /*usually 0-0 (no range) but can be non-zero when playing local MPD/DASH sessions*/
267 u64 bs_switching_init_segment_url_start_range, bs_switching_init_segment_url_end_range;
268 char *bs_switching_init_segment_url;
269
270 u32 nb_segments_done;
271 u32 last_segment_time;
272 u32 nb_segments_since_switch;
273
274 //stats of last downloaded segment
275 u32 total_size, bytes_per_sec, bytes_done, backup_Bps;
276
277
278 Bool segment_must_be_streamed;
279 Bool broken_timing;
280 Bool buffering;
281 u32 maybe_end_of_stream;
282 u32 cache_duration;
283 u32 time_at_first_reload_required;
284 u32 force_representation_idx_plus_one;
285
286 Bool force_segment_switch;
287 Bool is_downloading;
288 Bool loop_detected;
289
290 u32 time_at_first_failure;
291 Bool prev_segment_ok, segment_in_valid_range;
292 //this is the number of 404
293 u32 nb_consecutive_segments_lost;
294 u64 retry_after_utc;
295 /*set when switching segment, indicates the current downloaded segment duration*/
296 u64 current_downloaded_segment_duration;
297
298 char *service_mime;
299
300 /* base representation index of this group plus one, or 0 if all representations in this group are independent*/
301 u32 base_rep_index_plus_one;
302
303 /* maximum representation index we want to download*/
304 u32 max_complementary_rep_index;
305 //start time and timescales of currently downloaded segment
306 u64 current_start_time;
307 u32 current_timescale;
308
309 void *udta;
310
311 Bool has_pending_enhancement;
312
313 /*Codec statistics*/
314 u32 avg_dec_time, max_dec_time, irap_avg_dec_time, irap_max_dec_time;
315 Bool codec_reset;
316 Bool decode_only_rap;
317 /*display statistics*/
318 u32 display_width, display_height;
319 /*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)*/
320 u32 max_buffer_playout_ms;
321 /*buffer status*/
322 u32 buffer_min_ms, buffer_max_ms, buffer_occupancy_ms;
323 u32 buffer_occupancy_at_last_seg;
324
325 u32 m3u8_start_media_seq;
326 u64 hls_next_start_time;
327
328 GF_List *groups_depending_on;
329 u32 current_dep_idx;
330
331 u32 target_new_rep;
332
333 u32 srd_x, srd_y, srd_w, srd_h, srd_row_idx, srd_col_idx;
334 struct _dash_srd_desc *srd_desc;
335
336 /*mutex for group->cache access (read and write in download)*/
337 GF_Mutex *cache_mutex;
338
339 GF_Thread *download_th;
340 Bool download_th_done;
341
342 /*current index of the base URL used*/
343 u32 current_base_url_idx;
344
345 u32 quality_degradation_hint;
346
347 Bool rate_adaptation_postponed;
348
349 /* current segment index in BBA and BOLA algorithm */
350 u32 current_index;
351
352 //in non-threaded mode, indicates that the demux for this group has nothing to do...
353 Bool force_early_fetch;
354 Bool is_low_latency;
355 };
356
357 static void gf_dash_solve_period_xlink(GF_DashClient *dash, GF_List *period_list, u32 period_idx);
358
359 struct _dash_srd_desc
360 {
361 u32 srd_nb_rows, srd_nb_cols;
362 u32 id, width, height, srd_fw, srd_fh;
363 };
364
365 void drm_decrypt(unsigned char * data, unsigned long dataSize, const char * decryptMethod, const char * keyfileURL, const unsigned char * keyIV);
366
367
368
gf_dash_get_mime_type(GF_MPD_SubRepresentation * subrep,GF_MPD_Representation * rep,GF_MPD_AdaptationSet * set)369 static const char *gf_dash_get_mime_type(GF_MPD_SubRepresentation *subrep, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set)
370 {
371 if (subrep && subrep->mime_type) return subrep->mime_type;
372 if (rep && rep->mime_type) return rep->mime_type;
373 if (set && set->mime_type) return set->mime_type;
374 return NULL;
375 }
376
377
dash_get_fetch_time(GF_DashClient * dash)378 static u64 dash_get_fetch_time(GF_DashClient *dash)
379 {
380 u64 utc = 0;
381
382 if (dash->mpd_dnload && dash->dash_io->get_utc_start_time)
383 utc = dash->dash_io->get_utc_start_time(dash->dash_io, dash->mpd_dnload);
384 if (!utc)
385 utc = gf_net_get_utc();
386 return utc;
387 }
388
389
gf_dash_group_count_rep_needed(GF_DASH_Group * group)390 static u32 gf_dash_group_count_rep_needed(GF_DASH_Group *group)
391 {
392 u32 count, nb_rep_need, next_rep_index_plus_one;
393 GF_MPD_Representation *rep;
394 count = gf_list_count(group->adaptation_set->representations);
395 nb_rep_need = 1;
396 if (!group->base_rep_index_plus_one || (group->base_rep_index_plus_one == group->max_complementary_rep_index+1))
397 return nb_rep_need; // we need to download only one representation
398 rep = gf_list_get(group->adaptation_set->representations, group->base_rep_index_plus_one-1);
399 next_rep_index_plus_one = rep->playback.enhancement_rep_index_plus_one;
400 while ((nb_rep_need < count) && rep->playback.enhancement_rep_index_plus_one) {
401 nb_rep_need++;
402 if (next_rep_index_plus_one == group->max_complementary_rep_index+1)
403 break;
404 rep = gf_list_get(group->adaptation_set->representations, next_rep_index_plus_one-1);
405 next_rep_index_plus_one = rep->playback.enhancement_rep_index_plus_one;
406 }
407
408 assert(nb_rep_need <= count);
409
410 return nb_rep_need;
411 }
412
413 static
gf_dash_check_mpd_root_type(const char * local_url)414 u32 gf_dash_check_mpd_root_type(const char *local_url)
415 {
416 if (local_url) {
417 char *rtype = gf_xml_get_root_type(local_url, NULL);
418 if (rtype) {
419 u32 handled = 0;
420 if (!strcmp(rtype, "MPD")) {
421 handled = 1;
422 }
423 else if (!strcmp(rtype, "SmoothStreamingMedia")) {
424 handled = 2;
425 }
426 gf_free(rtype);
427 return handled;
428 }
429 }
430 return GF_FALSE;
431 }
432
433
gf_dash_group_timeline_setup(GF_MPD * mpd,GF_DASH_Group * group,u64 fetch_time)434 static void gf_dash_group_timeline_setup(GF_MPD *mpd, GF_DASH_Group *group, u64 fetch_time)
435 {
436 GF_MPD_SegmentTimeline *timeline = NULL;
437 GF_MPD_Representation *rep = NULL;
438 const char *val;
439 u32 shift, timescale;
440 u64 current_time, current_time_no_timeshift, availabilityStartTime;
441 u32 ast_diff, start_number;
442 Double ast_offset = 0;
443
444 if (mpd->type==GF_MPD_TYPE_STATIC)
445 return;
446
447 //always init clock even if active period is a remote one
448 #if 0
449 if (group->period->origin_base_url && (group->period->type != GF_MPD_TYPE_DYNAMIC))
450 return;
451 #endif
452
453 /*M3U8 does not use NTP sync */
454 if (group->dash->is_m3u8)
455 return;
456
457 if (group->broken_timing )
458 return;
459
460
461 /*if no AST, do not use NTP sync */
462 if (! group->dash->mpd->availabilityStartTime) {
463 group->broken_timing = GF_TRUE;
464 return;
465 }
466
467 if (!fetch_time) {
468 //when we initialize the timeline without an explicit fetch time, use our local clock - this allows for better precision
469 //when trying to locate the live edge
470 fetch_time = gf_net_get_utc();
471 }
472 //if ATSC and clock not setup, do it
473 val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-dash-atsc");
474 if (val && !group->dash->utc_drift_estimate) {
475 u32 i;
476 GF_MPD_Period *dyn_period=NULL;
477 Bool found = GF_FALSE;
478 u64 timeline_offset_ms=0;
479 if (!group->dash->atsc_clock_state) {
480 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Detected ATSC DASH service ID %s\n", val));
481 group->dash->atsc_clock_state = 1;
482 }
483
484 for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
485 dyn_period = gf_list_get(group->dash->mpd->periods, i);
486 if (!dyn_period->xlink_href && !dyn_period->origin_base_url) break;
487 if (dyn_period->xlink_href && !dyn_period->origin_base_url && gf_list_count(dyn_period->adaptation_sets) ) break;
488 dyn_period = NULL;
489 }
490 if (!dyn_period) {
491 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ATSC with no dynamic period, cannot init clock yet\n"));
492 return;
493 }
494
495 for (i=0; i<gf_list_count(dyn_period->adaptation_sets); i++) {
496 u64 sr, seg_dur;
497 u32 j, len, nb_space=0;
498 GF_MPD_AdaptationSet *set;
499 char *sep, *start, *end, *seg_url = NULL;
500 val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-dash-first-seg");
501 if (!val) {
502 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Waiting for ATSC clock ...\n"));
503 return;
504 }
505
506 set = gf_list_get(dyn_period->adaptation_sets, i);
507 for (j=0; j<gf_list_count(set->representations); j++) {
508 u64 dur = dyn_period->duration;
509 rep = gf_list_get(set->representations, j);
510
511 dyn_period->duration = 0;
512
513 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);
514
515 dyn_period->duration = dur;
516
517 sep = seg_url ? strstr(seg_url, "987") : NULL;
518 if (!sep) {
519 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Failed to resolve template for segment #9876 on rep #%d\n", j+1));
520 if (seg_url) gf_free(seg_url);
521 continue;
522 }
523 start = sep;
524 end = sep+4;
525 while (start>seg_url && (*(start-1)=='0')) { start--; nb_space++;}
526 start[0]=0;
527 len = (u32) strlen(seg_url)-2;
528 if (!strncmp(val, seg_url+2, len)) {
529 u32 number=0;
530 char szTemplate[100];
531
532 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Resolve ATSC clock on bootstrap segment URL %s template %s\n", val, seg_url+2));
533
534 strcpy(szTemplate, seg_url+2);
535 strcat(szTemplate, "%");
536 if (nb_space) {
537 char szFmt[20];
538 sprintf(szFmt, "0%d", nb_space+4);
539 strcat(szTemplate, szFmt);
540 }
541 strcat(szTemplate, "d");
542 strcat(szTemplate, end);
543 if (sscanf(val, szTemplate, &number) == 1) {
544 u32 startNum = 1;
545 //safety check for now, in case one of the segment is send too early compared to the rest
546 if (number) number--;
547 if (dyn_period->segment_template) startNum = dyn_period->segment_template->start_number;
548 if (set->segment_template) startNum = set->segment_template->start_number;
549 if (rep->segment_template) startNum = rep->segment_template->start_number;
550 if (number>=startNum) {
551 timeline_offset_ms = seg_dur*(number-startNum);
552 }
553 found = GF_TRUE;
554 }
555 } else {
556 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] ATSC bootstrap segment URL %s does not match template %s for rep #%d\n", val, seg_url+2, j+1));
557 }
558 gf_free(seg_url);
559 if (found) break;
560 }
561 if (found) break;
562 }
563 if (found) {
564 //adjust so that nb_seg = current_time/segdur = (fetch-ast)/seg_dur;
565 // = (fetch- ( mpd->availabilityStartTime + group->dash->utc_shift + group->dash->utc_drift_estimate) / segdur;
566 //hence nb_seg*seg_dur = fetch - mpd->availabilityStartTime - group->dash->utc_shift - group->dash->utc_drift_estimate
567 //so group->dash->utc_drift_estimate = fetch - (mpd->availabilityStartTime + nb_seg*seg_dur)
568
569
570 u64 utc = mpd->availabilityStartTime + dyn_period->start + timeline_offset_ms;
571 group->dash->utc_drift_estimate = ((s64) fetch_time - (s64) utc);
572 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff of ATSC 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));
573
574 group->dash->atsc_clock_state = 2;
575 } else {
576 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Failed to setup ATSC clock from segment template with bootstrap URL %s, using NTP\n", val));
577 group->dash->atsc_clock_state = 3;
578 }
579 }
580 else if (val) {
581 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] ATSC clock already setup - UTC diff of ATSC broadcast "LLD" ms\n", group->dash->utc_drift_estimate));
582 } else {
583 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] No ATSC entity on HTPP request\n"));
584 }
585
586 if ((!group->dash->atsc_clock_state || (group->dash->atsc_clock_state>2)) && !group->dash->ntp_forced && group->dash->estimate_utc_drift && !group->dash->utc_drift_estimate && group->dash->mpd_dnload && group->dash->dash_io->get_header_value) {
587 val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "Server-UTC");
588 if (val) {
589 u64 utc;
590 sscanf(val, LLU, &utc);
591 group->dash->utc_drift_estimate = ((s64) fetch_time - (s64) utc);
592 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));
593 } else {
594 s64 drift_estimate = 0;
595 u64 utc = 0;
596 val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "Date");
597 if (val)
598 utc = gf_net_parse_date(val);
599 if (utc)
600 drift_estimate = ((s64) fetch_time - (s64) utc);
601
602 //HTTP date is in second - if the clock diff is less than 1 sec, we cannot infer anything
603 if (ABS(drift_estimate) > 1000) {
604 group->dash->utc_drift_estimate = drift_estimate;
605 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));
606 } else {
607 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));
608 }
609 }
610 }
611
612 availabilityStartTime = 0;
613 if ((s64) mpd->availabilityStartTime + group->dash->utc_shift > (s64) - group->dash->utc_drift_estimate) {
614 availabilityStartTime = mpd->availabilityStartTime + group->dash->utc_shift + group->dash->utc_drift_estimate;
615 }
616
617
618 #ifdef FORCE_DESYNC
619 availabilityStartTime -= FORCE_DESYNC;
620 #endif
621
622 ast_diff = (u32) (availabilityStartTime - group->dash->mpd->availabilityStartTime);
623 current_time = fetch_time;
624
625 if (current_time < availabilityStartTime) {
626 //if more than 1 sec consider we have a pb
627 if (availabilityStartTime - current_time >= 1000) {
628 Bool broken_timing = GF_TRUE;
629 #ifndef _WIN32_WCE
630 time_t gtime1, gtime2;
631 struct tm *t1, *t2;
632 gtime1 = current_time / 1000;
633 t1 = gf_gmtime(>ime1);
634 gtime2 = availabilityStartTime / 1000;
635 t2 = gf_gmtime(>ime2);
636 if (t1 == t2) {
637 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) ));
638 current_time = 0;
639 broken_timing = GF_FALSE;
640 }
641 else if (t1 && t2) {
642 t1->tm_year = t2->tm_year;
643 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",
644 1900+t1->tm_year, t1->tm_mon+1, t1->tm_mday, t1->tm_hour, t1->tm_min, t1->tm_sec,
645 1900+t2->tm_year, t2->tm_mon+1, t2->tm_mday, t2->tm_hour, t2->tm_min, t2->tm_sec,
646 (u32) (availabilityStartTime - current_time)
647 ));
648 } else {
649 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in UTC clock: could not retrieve time!\n"));
650 }
651
652 #endif
653 if (broken_timing) {
654 if (group->dash->utc_shift + group->dash->utc_drift_estimate > 0) {
655 availabilityStartTime = current_time;
656 } else {
657 group->broken_timing = GF_TRUE;
658 return;
659 }
660 }
661 } else {
662 availabilityStartTime = current_time;
663 current_time = 0;
664 }
665 }
666 else current_time -= availabilityStartTime;
667
668 if (gf_list_count(group->dash->mpd->periods)) {
669 u64 seg_start_ms = current_time;
670 u64 seg_end_ms = (u64) (seg_start_ms + group->segment_duration*1000);
671 u32 i;
672 u64 start = 0;
673 for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
674 GF_MPD_Period *ap = gf_list_get(group->dash->mpd->periods, i);
675 if (ap->start) start = ap->start;
676
677 if (group->dash->initial_period_tunein
678 && (seg_start_ms>=ap->start)
679 && (!ap->duration || (seg_end_ms<=start + ap->duration))
680 ) {
681 if (i != group->dash->active_period_index) {
682 group->dash->reinit_period_index = 1+i;
683 group->dash->start_range_period = (Double) seg_start_ms;
684 group->dash->start_range_period -= ap->start;
685 group->dash->start_range_period /= 1000;
686 return;
687 }
688 }
689
690 if (!ap->duration) break;
691 start += ap->duration;
692 }
693 }
694
695 //compute current time in period
696 if (current_time < group->period->start)
697 current_time = 0;
698 else {
699 if (group->dash->initial_period_tunein) {
700 current_time -= group->period->start;
701 } else {
702 //initial period was setup, consider we are moving to a new period, so time in this period is 0
703 current_time = 0;
704 if (group->start_playback_range) current_time = (u64) (group->start_playback_range*1000);
705 }
706 }
707
708 current_time_no_timeshift = current_time;
709 if ( ((s32) mpd->time_shift_buffer_depth>=0)) {
710
711 if (group->dash->initial_time_shift_value) {
712 if (group->dash->initial_time_shift_value<=100) {
713 shift = mpd->time_shift_buffer_depth;
714 shift *= group->dash->initial_time_shift_value;
715 shift /= 100;
716 } else {
717 shift = (u32) group->dash->initial_time_shift_value;
718 if (shift > mpd->time_shift_buffer_depth) shift = mpd->time_shift_buffer_depth;
719 }
720
721 if (current_time < shift) current_time = 0;
722 else current_time -= shift;
723 }
724 }
725 group->dash->time_in_tsb = group->dash->prev_time_in_tsb = 0;
726
727 timeline = NULL;
728 timescale=1;
729 start_number=0;
730 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
731
732 if (group->period->segment_list) {
733 if (group->period->segment_list->segment_timeline) timeline = group->period->segment_list->segment_timeline;
734 if (group->period->segment_list->timescale) timescale = group->period->segment_list->timescale;
735 if (group->period->segment_list->start_number) start_number = group->period->segment_list->start_number;
736 if (group->period->segment_list->availability_time_offset) ast_offset = group->period->segment_list->availability_time_offset;
737 }
738 if (group->adaptation_set->segment_list) {
739 if (group->adaptation_set->segment_list->segment_timeline) timeline = group->adaptation_set->segment_list->segment_timeline;
740 if (group->adaptation_set->segment_list->timescale) timescale = group->adaptation_set->segment_list->timescale;
741 if (group->adaptation_set->segment_list->start_number) start_number = group->adaptation_set->segment_list->start_number;
742 if (group->adaptation_set->segment_list->availability_time_offset) ast_offset = group->adaptation_set->segment_list->availability_time_offset;
743 }
744 if (rep->segment_list) {
745 if (rep->segment_list->segment_timeline) timeline = rep->segment_list->segment_timeline;
746 if (rep->segment_list->timescale) timescale = rep->segment_list->timescale;
747 if (rep->segment_list->start_number) start_number = rep->segment_list->start_number;
748 if (rep->segment_list->availability_time_offset) ast_offset = rep->segment_list->availability_time_offset;
749 }
750
751 if (group->period->segment_template) {
752 if (group->period->segment_template->segment_timeline) timeline = group->period->segment_template->segment_timeline;
753 if (group->period->segment_template->timescale) timescale = group->period->segment_template->timescale;
754 if (group->period->segment_template->start_number) start_number = group->period->segment_template->start_number;
755 if (group->period->segment_template->availability_time_offset) ast_offset = group->period->segment_template->availability_time_offset;
756 }
757 if (group->adaptation_set->segment_template) {
758 if (group->adaptation_set->segment_template->segment_timeline) timeline = group->adaptation_set->segment_template->segment_timeline;
759 if (group->adaptation_set->segment_template->timescale) timescale = group->adaptation_set->segment_template->timescale;
760 if (group->adaptation_set->segment_template->start_number) start_number = group->adaptation_set->segment_template->start_number;
761 if (group->adaptation_set->segment_template->availability_time_offset) ast_offset = group->adaptation_set->segment_template->availability_time_offset;
762 }
763 if (rep->segment_template) {
764 if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline;
765 if (rep->segment_template->timescale) timescale = rep->segment_template->timescale;
766 if (rep->segment_template->start_number) start_number = rep->segment_template->start_number;
767 if (rep->segment_template->availability_time_offset) ast_offset = rep->segment_template->availability_time_offset;
768 }
769
770 group->is_low_latency = GF_FALSE;
771 if (group->dash->low_latency_mode==GF_DASH_LL_DISABLE) {
772 ast_offset = 0;
773 } else if (ast_offset>0) {
774 group->is_low_latency = GF_TRUE;
775 }
776 if (timeline) {
777 u64 start_segtime = 0;
778 u64 segtime = 0;
779 u64 current_time_rescale;
780 u64 timeline_duration = 0;
781 u32 count;
782 u64 last_s_dur=0;
783 u32 i, seg_idx = 0;
784
785 current_time_rescale = current_time;
786 current_time_rescale *= timescale;
787 current_time_rescale /= 1000;
788
789 count = gf_list_count(timeline->entries);
790 for (i=0; i<count; i++) {
791 GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
792
793 if (!i && (current_time_rescale + ent->duration < ent->start_time)) {
794 current_time_rescale = current_time_no_timeshift * timescale / 1000;
795 }
796 timeline_duration += (1+ent->repeat_count)*ent->duration;
797
798 if (i+1 == count) timeline_duration -= ent->duration;
799 last_s_dur=ent->duration;
800 }
801
802
803 if (!group->dash->mpd->minimum_update_period) {
804 last_s_dur *= 1000;
805 last_s_dur /= timescale;
806 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));
807
808 group->dash->mpd->minimum_update_period = (u32) last_s_dur;
809 }
810
811 for (i=0; i<count; i++) {
812 u32 repeat;
813 GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
814 if (!segtime) {
815 start_segtime = segtime = ent->start_time;
816
817 //if current time is before the start of the previous segment, consider our timing is broken
818 if (current_time_rescale + ent->duration < segtime) {
819 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));
820 group->download_segment_index = seg_idx;
821 group->nb_segments_in_rep = count;
822 group->start_playback_range = (segtime)*1.0/timescale;
823 group->ast_at_init = availabilityStartTime - (u32) (ast_offset*1000);
824 group->broken_timing = GF_TRUE;
825 return;
826 }
827 }
828
829 repeat = 1+ent->repeat_count;
830 while (repeat) {
831 if ((current_time_rescale >= segtime) && (current_time_rescale < segtime + ent->duration)) {
832 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));
833
834 group->download_segment_index = seg_idx;
835 group->nb_segments_in_rep = seg_idx + count - i;
836 group->start_playback_range = (current_time)/1000.0;
837 group->ast_at_init = availabilityStartTime - (u32) (ast_offset*1000);
838
839 //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
840 if (group->dash->utc_drift_estimate<0) {
841 group->ast_at_init -= (timeline_duration - (segtime-start_segtime)) *1000/timescale;
842 }
843 return;
844 }
845 segtime += ent->duration;
846 repeat--;
847 seg_idx++;
848 last_s_dur=ent->duration;
849 }
850 }
851 //check if we're ahead of time but "reasonnably" ahead (max 1 min) - otherwise consider the timing is broken
852 if ((current_time_rescale + last_s_dur >= segtime) && (current_time_rescale <= segtime + 60*timescale)) {
853 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));
854 group->download_segment_index = seg_idx-1;
855 group->nb_segments_in_rep = seg_idx;
856 group->start_playback_range = current_time/1000.0;
857 group->ast_at_init = availabilityStartTime - (u32) (ast_offset*1000);
858 //force an update in half the target period
859 group->dash->last_update_time = gf_sys_clock() + group->dash->mpd->minimum_update_period/2;
860 } else {
861 //NOT FOUND !!
862 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));
863 group->download_segment_index = 0;
864 group->nb_segments_in_rep = 10;
865 group->broken_timing = GF_TRUE;
866 }
867
868 return;
869 }
870
871 if (group->segment_duration) {
872 u32 nb_segs_in_update = (u32) (mpd->minimum_update_period / (1000*group->segment_duration) );
873 Double nb_seg = (Double) current_time;
874 nb_seg /= 1000;
875 nb_seg /= group->segment_duration;
876 shift = (u32) nb_seg;
877
878 if ((group->dash->atsc_clock_state == 2) && shift) {
879 //shift currently points to the next segment after the one used for clock bootstrap, use the right one
880 shift--;
881 //avoid querying too early the cache since segments do not usually arrive exactly on time ...
882 availabilityStartTime += group->dash->atsc_ast_shift;
883 }
884
885 if (group->dash->initial_period_tunein) {
886 u64 seg_start_ms, seg_end_ms;
887
888 seg_start_ms = (u64) (group->segment_duration * (shift+start_number) * 1000);
889 seg_end_ms = (u64) (seg_start_ms + group->segment_duration*1000);
890 //we are in the right period
891 if (seg_start_ms>=group->period->start && (!group->period->duration || (seg_end_ms<=group->period->start+group->period->duration)) ) {
892 } else {
893 u32 i;
894 u64 start = 0;
895 for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
896 GF_MPD_Period *ap = gf_list_get(group->dash->mpd->periods, i);
897 if (ap->start) start = ap->start;
898
899 if ((seg_start_ms>=ap->start) && (!ap->duration || (seg_end_ms<=start + ap->duration))) {
900 group->dash->reinit_period_index = 1+i;
901 group->dash->start_range_period = (Double) seg_start_ms;
902 group->dash->start_range_period -= ap->start;
903 group->dash->start_range_period /= 1000;
904 return;
905 }
906
907 if (!ap->duration) break;
908 start += ap->duration;
909 }
910 }
911 }
912
913 //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
914
915 if (!group->dash->initial_time_shift_value) {
916 Double time_in_seg;
917 //by default playback starts at begining of segment
918 group->start_playback_range = shift * group->segment_duration;
919
920 time_in_seg = (Double) current_time/1000.0;
921 time_in_seg -= group->start_playback_range;
922
923 //if low latency, try to adjust
924 if (ast_offset) {
925 Double ast_diff_d;
926 if (ast_offset>group->segment_duration) ast_offset = group->segment_duration;
927 ast_diff_d = group->segment_duration - ast_offset;
928
929 //we assume that in low latency mode, chunks are made available every (group->segment_duration - ast_offset)
930 //we need to seek such that the remaining time R satisfies now + R = NextSegAST
931 //hence S(n) + ms_in_seg + R = S(n+1) + Aoffset
932 //which gives us R = S(n+1) + Aoffset - S(n) - ms_in_seg = D + Aoffset - ms_in_seg
933 //seek = D - R = D - (D + Aoffset - ms_in_seg) = ms_in_seg - Ao
934 if (time_in_seg > ast_diff_d) {
935 group->start_playback_range += time_in_seg - ast_diff_d;
936 }
937 }
938 } else {
939 group->start_playback_range = (Double) current_time / 1000.0;
940 }
941
942 if (!group->start_number_at_last_ast) {
943 group->download_segment_index = shift;
944 group->start_number_at_last_ast = start_number;
945
946 group->ast_at_init = availabilityStartTime - (u32) (ast_offset*1000);
947 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AST at init "LLD"\n", group->ast_at_init));
948
949 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));
950 } else {
951 group->download_segment_index += start_number;
952 if (group->download_segment_index > group->start_number_at_last_ast) {
953 group->download_segment_index -= group->start_number_at_last_ast;
954 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));
955 } else {
956 group->download_segment_index = shift;
957 group->ast_at_init = availabilityStartTime - (u32) (ast_offset*1000);
958 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));
959 }
960 group->start_number_at_last_ast = start_number;
961 }
962 if (group->nb_segments_in_rep) {
963 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));
964 } else {
965 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] UTC time indicates first segment in period is %d\n", group->download_segment_index));
966 }
967
968 if (group->nb_segments_in_rep && (group->download_segment_index + nb_segs_in_update > group->nb_segments_in_rep)) {
969 if (group->download_segment_index < (s32)group->nb_segments_in_rep) {
970
971 } else {
972 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 ));
973 group->nb_segments_in_rep = shift + nb_segs_in_update;
974 group->dash->ignore_mpd_duration = GF_TRUE;
975 }
976 }
977 group->prev_segment_ok = GF_TRUE;
978 } else {
979 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Segment duration unknown - cannot estimate current startNumber\n"));
980 }
981 }
982
983
984
985
986
987 /*!
988 * Returns true if given mime type is a MPD file
989 \param mime the mime-type to check
990 \return true if mime-type if MPD-OK
991 */
gf_dash_is_dash_mime(const char * mime)992 static Bool gf_dash_is_dash_mime(const char *mime) {
993 u32 i;
994 if (!mime)
995 return GF_FALSE;
996 for (i = 0 ; GF_DASH_MPD_MIME_TYPES[i] ; i++) {
997 if ( !stricmp(mime, GF_DASH_MPD_MIME_TYPES[i]))
998 return GF_TRUE;
999 }
1000 return GF_FALSE;
1001 }
1002
1003 /*!
1004 * Returns true if mime type of a given URL is an M3U8 mime-type
1005 \param url The url to check
1006 \param mime The mime-type to check
1007 \return true if mime-type is OK for M3U8
1008 */
gf_dash_is_m3u8_mime(const char * url,const char * mime)1009 static Bool gf_dash_is_m3u8_mime(const char *url, const char * mime) {
1010 u32 i;
1011 if (!url || !mime)
1012 return GF_FALSE;
1013 if (strstr(url, ".mpd") || strstr(url, ".MPD"))
1014 return GF_FALSE;
1015
1016 for (i = 0 ; GF_DASH_M3U8_MIME_TYPES[i] ; i++) {
1017 if ( !stricmp(mime, GF_DASH_M3U8_MIME_TYPES[i]))
1018 return GF_TRUE;
1019 }
1020 return GF_FALSE;
1021 }
1022
gf_dash_is_smooth_mime(const char * url,const char * mime)1023 static Bool gf_dash_is_smooth_mime(const char *url, const char * mime)
1024 {
1025 u32 i;
1026 if (!url || !mime)
1027 return GF_FALSE;
1028 if (strstr(url, ".mpd") || strstr(url, ".MPD"))
1029 return GF_FALSE;
1030
1031 for (i = 0 ; GF_DASH_SMOOTH_MIME_TYPES[i] ; i++) {
1032 if ( !stricmp(mime, GF_DASH_SMOOTH_MIME_TYPES[i]))
1033 return GF_TRUE;
1034 }
1035 return GF_FALSE;
1036 }
1037
1038
1039 GF_EXPORT
gf_dash_group_check_bandwidth(GF_DashClient * dash,u32 idx)1040 GF_Err gf_dash_group_check_bandwidth(GF_DashClient *dash, u32 idx)
1041 {
1042 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
1043 if (!group) return GF_BAD_PARAM;
1044
1045 if (dash->rate_adaptation_download_monitor)
1046 return dash->rate_adaptation_download_monitor(dash, group);
1047
1048 return GF_OK;
1049 }
1050
1051 /*!
1052 * Download a file with possible retry if GF_IP_CONNECTION_FAILURE|GF_IP_NETWORK_FAILURE
1053 * (I discovered that with my WIFI connection, I had many issues with BFM-TV downloads)
1054 * Similar to gf_service_download_new() and gf_dm_sess_process().
1055 * Parameters are identical to the ones of gf_service_download_new.
1056 * \see gf_service_download_new()
1057 */
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)1058 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)
1059 {
1060 s32 group_idx = -1;
1061 Bool had_sess = GF_FALSE;
1062 Bool retry = GF_TRUE;
1063 GF_Err e;
1064 GF_DASHFileIO *dash_io = dash->dash_io;
1065
1066 if (!dash_io) return GF_BAD_PARAM;
1067 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading %s starting at UTC "LLU" ms\n", url, gf_net_get_utc() ));
1068
1069 if (group) {
1070 group_idx = gf_list_find(group->dash->groups, group);
1071 }
1072
1073 if (! *sess) {
1074 *sess = dash_io->create(dash_io, persistent_mode ? 1 : 0, url, group_idx);
1075 if (!(*sess)) {
1076 if (dash->atsc_clock_state)
1077 return GF_IP_NETWORK_EMPTY;
1078 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot try to download %s... out of memory ?\n", url));
1079 return GF_OUT_OF_MEM;
1080 }
1081 } else {
1082 had_sess = GF_TRUE;
1083 if (persistent_mode!=2) {
1084 e = dash_io->setup_from_url(dash_io, *sess, url, group_idx);
1085 if (e) {
1086 //with ATSC we may have 404 right away if nothing in cache yet, not an error
1087 GF_LOG(dash->atsc_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) ));
1088 return e;
1089 }
1090 }
1091 }
1092 if (group) {
1093 group->is_downloading = GF_TRUE;
1094 group->download_start_time = gf_sys_clock();
1095 }
1096
1097 retry:
1098
1099 if (end_range) {
1100 e = dash_io->set_range(dash_io, *sess, start_range, end_range, (persistent_mode==2) ? GF_FALSE : GF_TRUE);
1101 if (e) {
1102 if (had_sess) {
1103 dash_io->del(dash_io, *sess);
1104 *sess = NULL;
1105 return gf_dash_download_resource(dash, sess, url, start_range, end_range, persistent_mode ? 1 : 0, group);
1106 }
1107 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot setup byte-range download for %s: %s\n", url, gf_error_to_string(e) ));
1108 if (group)
1109 group->is_downloading = GF_FALSE;
1110
1111 return e;
1112 }
1113 }
1114 assert(*sess);
1115
1116 /*issue HTTP GET for headers only*/
1117 e = dash_io->init(dash_io, *sess);
1118
1119 if (e>=GF_OK) {
1120 /*check mime type of the adaptation set if not provided*/
1121 if (group) {
1122 const char *mime = *sess ? dash_io->get_mime(dash_io, *sess) : NULL;
1123 if (mime && !group->service_mime) {
1124 group->service_mime = gf_strdup(mime);
1125 }
1126 /*we allow servers to give us broken mim types for the representation served ...*/
1127 #if 0
1128 else if (mime && stricmp(group->service_mime, mime)) {
1129 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
1130 if (! gf_dash_get_mime_type(NULL, rep, group->adaptation_set) )
1131 rep->mime_type = gf_strdup(mime);
1132 rep->disabled = 1;
1133 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Disabling representation since mime does not match: expected %s, but had %s for %s!\n", group->service_mime, mime, url));
1134 group->force_switch_bandwidth = 1;
1135 if (group->segment_download) dash_io->abort(dash_io, group->segment_download);
1136 group->is_downloading = 0;
1137 return GF_OK;
1138 }
1139 #endif
1140
1141 /*file cannot be cached on disk !*/
1142 if (dash_io->get_cache_name(dash_io, *sess ) == NULL) {
1143 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Segment %s cannot be cached on disk, will use direct streaming\n", url));
1144 group->segment_must_be_streamed = GF_TRUE;
1145 if (group->segment_download) dash_io->abort(dash_io, group->segment_download);
1146 group->is_downloading = GF_TRUE;
1147 return GF_OK;
1148 }
1149 group->segment_must_be_streamed = GF_FALSE;
1150 }
1151
1152 //release dl_mutex while downloading segment
1153 /*we can download the file*/
1154 e = dash_io->run(dash_io, *sess);
1155 } else {
1156 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)));
1157 }
1158 if (group && group->download_abort_type) {
1159 group->is_downloading = GF_FALSE;
1160 return GF_IP_CONNECTION_CLOSED;
1161 }
1162 switch (e) {
1163 case GF_IP_CONNECTION_FAILURE:
1164 case GF_IP_NETWORK_FAILURE:
1165 if (!dash->in_error || group) {
1166 dash_io->del(dash_io, *sess);
1167 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] failed to download, retrying once with %s...\n", url));
1168 *sess = dash_io->create(dash_io, 0, url, group_idx);
1169 if (! (*sess)) {
1170 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot retry to download %s... out of memory ?\n", url));
1171 if (group)
1172 group->is_downloading = GF_FALSE;
1173 return GF_OUT_OF_MEM;
1174 }
1175
1176 if (retry) {
1177 retry = GF_FALSE;
1178 goto retry;
1179 }
1180 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] two consecutive failures, aborting the download %s.\n", url));
1181 } else if (dash->in_error) {
1182 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Download still in error for %s.\n", url));
1183 }
1184 break;
1185 case GF_OK:
1186 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Download %s complete at UTC "LLU" ms\n", url, gf_net_get_utc() ));
1187 break;
1188 default:
1189 //log as warning, maybe the dash client can recover from this error
1190 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Failed to download %s = %s...\n", url, gf_error_to_string(e)));
1191 break;
1192 }
1193 if (group)
1194 group->is_downloading = GF_FALSE;
1195
1196 return e;
1197 }
1198
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)1199 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)
1200 {
1201 u32 i, count;
1202 u64 period_duration, start_time, dur;
1203 if (period->duration) {
1204 period_duration = period->duration;
1205 } else {
1206 period_duration = mpd->media_presentation_duration - period->start;
1207 }
1208 period_duration *= timescale;
1209 period_duration /= 1000;
1210
1211 *nb_segments = 0;
1212 if (max_seg_duration) *max_seg_duration = 0;
1213 start_time = 0;
1214 dur = 0;
1215 count = gf_list_count(timeline->entries);
1216 for (i=0; i<count; i++) {
1217 GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
1218 if ((s32)ent->repeat_count >=0) {
1219 *nb_segments += 1 + ent->repeat_count;
1220 if (ent->start_time) {
1221 start_time = ent->start_time;
1222 dur = (1 + ent->repeat_count);
1223 } else {
1224 dur += (1 + ent->repeat_count);
1225 }
1226 dur *= ent->duration;
1227 } else {
1228 u32 nb_seg = 0;
1229 if (i+1<count) {
1230 GF_MPD_SegmentTimelineEntry *ent2 = gf_list_get(timeline->entries, i+1);
1231 if (ent2->start_time>0) {
1232 nb_seg = (u32) ( (ent2->start_time - start_time - dur) / ent->duration);
1233 dur += ((u64)nb_seg) * ent->duration;
1234 }
1235 }
1236 if (!nb_seg) {
1237 nb_seg = (u32) ( (period_duration - start_time) / ent->duration );
1238 dur += ((u64)nb_seg) * ent->duration;
1239 }
1240 *nb_segments += nb_seg;
1241 }
1242 if (max_seg_duration && (*max_seg_duration < ent->duration)) *max_seg_duration = ent->duration;
1243 }
1244 }
1245
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)1246 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)
1247 {
1248 Double mediaDuration;
1249 Bool single_segment = GF_FALSE;
1250 u32 timescale;
1251 u64 duration;
1252 GF_MPD_SegmentTimeline *timeline = NULL;
1253 *nb_segments = timescale = 0;
1254 duration = 0;
1255
1256 if (rep->segment_list || set->segment_list || period->segment_list) {
1257 GF_List *segments = NULL;
1258 if (period->segment_list) {
1259 if (period->segment_list->duration) duration = period->segment_list->duration;
1260 if (period->segment_list->timescale) timescale = period->segment_list->timescale;
1261 if (period->segment_list->segment_URLs) segments = period->segment_list->segment_URLs;
1262 if (period->segment_list->segment_timeline) timeline = period->segment_list->segment_timeline;
1263 }
1264 if (set->segment_list) {
1265 if (set->segment_list->duration) duration = set->segment_list->duration;
1266 if (set->segment_list->timescale) timescale = set->segment_list->timescale;
1267 if (set->segment_list->segment_URLs) segments = set->segment_list->segment_URLs;
1268 if (set->segment_list->segment_timeline) timeline = set->segment_list->segment_timeline;
1269 }
1270 if (rep->segment_list) {
1271 if (rep->segment_list->duration) duration = rep->segment_list->duration;
1272 if (rep->segment_list->timescale) timescale = rep->segment_list->timescale;
1273 if (rep->segment_list->segment_URLs) segments = rep->segment_list->segment_URLs;
1274 if (rep->segment_list->segment_timeline) timeline = rep->segment_list->segment_timeline;
1275 }
1276 if (!timescale) timescale=1;
1277
1278 if (timeline) {
1279 gf_dash_get_timeline_duration(mpd, period, timeline, timescale, nb_segments, max_seg_duration);
1280 if (max_seg_duration) *max_seg_duration /= timescale;
1281 } else {
1282 if (segments)
1283 *nb_segments = gf_list_count(segments);
1284 if (max_seg_duration) {
1285 *max_seg_duration = (Double) duration;
1286 *max_seg_duration /= timescale;
1287 }
1288 }
1289 return;
1290 }
1291
1292 /*single segment*/
1293 if (rep->segment_base || set->segment_base || period->segment_base) {
1294 *max_seg_duration = (Double)mpd->media_presentation_duration;
1295 *max_seg_duration /= 1000;
1296 *nb_segments = 1;
1297 return;
1298 }
1299
1300 single_segment = GF_TRUE;
1301 if (period->segment_template) {
1302 single_segment = GF_FALSE;
1303 if (period->segment_template->duration) duration = period->segment_template->duration;
1304 if (period->segment_template->timescale) timescale = period->segment_template->timescale;
1305 if (period->segment_template->segment_timeline) timeline = period->segment_template->segment_timeline;
1306 }
1307 if (set->segment_template) {
1308 single_segment = GF_FALSE;
1309 if (set->segment_template->duration) duration = set->segment_template->duration;
1310 if (set->segment_template->timescale) timescale = set->segment_template->timescale;
1311 if (set->segment_template->segment_timeline) timeline = set->segment_template->segment_timeline;
1312 }
1313 if (rep->segment_template) {
1314 single_segment = GF_FALSE;
1315 if (rep->segment_template->duration) duration = rep->segment_template->duration;
1316 if (rep->segment_template->timescale) timescale = rep->segment_template->timescale;
1317 if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline;
1318 }
1319 if (!timescale) timescale=1;
1320
1321 /*if no SegmentXXX is found, this is a single segment representation (onDemand profile)*/
1322 if (single_segment) {
1323 *max_seg_duration = (Double)mpd->media_presentation_duration;
1324 *max_seg_duration /= 1000;
1325 *nb_segments = 1;
1326 return;
1327 }
1328
1329 if (timeline) {
1330 gf_dash_get_timeline_duration(mpd, period, timeline, timescale, nb_segments, max_seg_duration);
1331 if (max_seg_duration) *max_seg_duration /= timescale;
1332 } else {
1333 if (max_seg_duration) {
1334 *max_seg_duration = (Double) duration;
1335 *max_seg_duration /= timescale;
1336 }
1337 mediaDuration = (Double)period->duration;
1338 if (!mediaDuration && mpd->media_presentation_duration) {
1339 u32 i, count = gf_list_count(mpd->periods);
1340 Double start = 0;
1341 for (i=0; i<count; i++) {
1342 GF_MPD_Period *ap = gf_list_get(mpd->periods, i);
1343 if (ap==period) break;
1344 if (ap->start) start = (Double)ap->start;
1345 start += ap->duration;
1346 }
1347 mediaDuration = mpd->media_presentation_duration - start;
1348 }
1349 if (mediaDuration && duration) {
1350 Double nb_seg = (Double) mediaDuration;
1351 /*duration is given in ms*/
1352 nb_seg /= 1000;
1353 nb_seg *= timescale;
1354 nb_seg /= duration;
1355 *nb_segments = (u32) nb_seg;
1356 if (*nb_segments < nb_seg) (*nb_segments)++;
1357 }
1358 }
1359 }
1360
1361
gf_dash_get_segment_start_time_with_timescale(GF_DASH_Group * group,u64 * segment_duration,u32 * scale)1362 static u64 gf_dash_get_segment_start_time_with_timescale(GF_DASH_Group *group, u64 *segment_duration, u32 *scale)
1363 {
1364 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
1365 GF_MPD_AdaptationSet *set = group->adaptation_set;
1366 GF_MPD_Period *period = group->period;
1367 s32 segment_index = group->download_segment_index;
1368 u64 start_time = 0;
1369
1370 gf_mpd_get_segment_start_time_with_timescale(segment_index,
1371 period, set, rep,
1372 &start_time, segment_duration, scale);
1373
1374 return start_time;
1375 }
1376
gf_dash_get_segment_start_time(GF_DASH_Group * group,Double * segment_duration)1377 static Double gf_dash_get_segment_start_time(GF_DASH_Group *group, Double *segment_duration)
1378 {
1379 u64 start = 0;
1380 u64 dur = 0;
1381 u32 scale = 1000;
1382
1383 start = gf_dash_get_segment_start_time_with_timescale(group, &dur, &scale);
1384 if (segment_duration) {
1385 *segment_duration = (Double) dur;
1386 *segment_duration /= scale;
1387 }
1388 return ((Double)start)/scale;
1389 }
1390
gf_dash_get_segment_availability_start_time(GF_MPD * mpd,GF_DASH_Group * group,u32 segment_index,u32 * seg_dur_ms)1391 static u64 gf_dash_get_segment_availability_start_time(GF_MPD *mpd, GF_DASH_Group *group, u32 segment_index, u32 *seg_dur_ms)
1392 {
1393 Double seg_ast, seg_dur=0.0;
1394 seg_ast = gf_dash_get_segment_start_time(group, &seg_dur);
1395 if (seg_dur_ms) *seg_dur_ms = (u32) (seg_dur * 1000);
1396
1397 seg_ast += seg_dur;
1398 seg_ast *= 1000;
1399 seg_ast += group->period->start + group->ast_at_init;
1400 return (u64) seg_ast;
1401 }
1402
gf_dash_get_index_in_timeline(GF_MPD_SegmentTimeline * timeline,u64 segment_start,u64 start_timescale,u64 timescale)1403 static u32 gf_dash_get_index_in_timeline(GF_MPD_SegmentTimeline *timeline, u64 segment_start, u64 start_timescale, u64 timescale)
1404 {
1405 u64 start_time = 0;
1406 u32 idx = 0;
1407 u32 i, count, repeat;
1408 count = gf_list_count(timeline->entries);
1409 for (i=0; i<count; i++) {
1410 GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
1411
1412 if (!i || ent->start_time) start_time = ent->start_time;
1413
1414 repeat = ent->repeat_count+1;
1415 while (repeat) {
1416 if (start_timescale==timescale) {
1417 if (start_time == segment_start ) return idx;
1418 if (start_time > segment_start) {
1419 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));
1420 return idx;
1421 }
1422 } else {
1423 if (start_time*start_timescale == segment_start * timescale) return idx;
1424 if (start_time*start_timescale > segment_start * timescale) {
1425 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));
1426 return idx;
1427 }
1428 }
1429 start_time+=ent->duration;
1430 repeat--;
1431 idx++;
1432 }
1433 }
1434 //end of list in regular case: segment was the last one of the previous list and no changes happend
1435 if (start_timescale==timescale) {
1436 if (start_time == segment_start ) return idx;
1437 } else {
1438 if (start_time*start_timescale == segment_start * timescale) return idx;
1439 }
1440
1441 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"));
1442 return idx;
1443 }
1444
1445
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)1446 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)
1447 {
1448 GF_MPD_SegmentTimeline *old_timeline, *new_timeline;
1449 u32 i, idx, timescale, nb_new_segs;
1450 GF_MPD_SegmentTimelineEntry *ent;
1451
1452 old_timeline = new_timeline = NULL;
1453 if (old_list && old_list->segment_timeline) {
1454 if (!new_list || !new_list->segment_timeline) {
1455 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: segment timeline not present in new MPD segmentList\n"));
1456 return GF_NON_COMPLIANT_BITSTREAM;
1457 }
1458 old_timeline = old_list->segment_timeline;
1459 new_timeline = new_list->segment_timeline;
1460 timescale = new_list->timescale;
1461 } else if (old_template && old_template->segment_timeline) {
1462 if (!new_template || !new_template->segment_timeline) {
1463 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: segment timeline not present in new MPD segmentTemplate\n"));
1464 return GF_NON_COMPLIANT_BITSTREAM;
1465 }
1466 old_timeline = old_template->segment_timeline;
1467 new_timeline = new_template->segment_timeline;
1468 timescale = new_template->timescale;
1469 }
1470 if (!old_timeline && !new_timeline) return GF_OK;
1471
1472 if (group) {
1473 group->current_start_time = gf_dash_get_segment_start_time_with_timescale(group, NULL, &group->current_timescale);
1474 } else {
1475 for (i=0; i<gf_list_count(dash->groups); i++) {
1476 GF_DASH_Group *a_group = gf_list_get(dash->groups, i);
1477 a_group->current_start_time = gf_dash_get_segment_start_time_with_timescale(a_group, NULL, &a_group->current_timescale);
1478 }
1479 }
1480
1481 nb_new_segs = 0;
1482 idx=0;
1483 while ((ent = gf_list_enum(new_timeline->entries, &idx))) {
1484 nb_new_segs += 1 + ent->repeat_count;
1485 }
1486
1487 if (group) {
1488 #ifndef GPAC_DISABLE_LOG
1489 u32 prev_idx = group->download_segment_index;
1490 #endif
1491 group->nb_segments_in_rep = nb_new_segs;
1492 group->download_segment_index = gf_dash_get_index_in_timeline(new_timeline, group->current_start_time, group->current_timescale, timescale ? timescale : group->current_timescale);
1493
1494 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));
1495 } else {
1496 for (i=0; i<gf_list_count(dash->groups); i++) {
1497 GF_DASH_Group *a_group = gf_list_get(dash->groups, i);
1498 #ifndef GPAC_DISABLE_LOG
1499 u32 prev_idx = a_group->download_segment_index;
1500 #endif
1501 a_group->nb_segments_in_rep = nb_new_segs;
1502 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);
1503
1504 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));
1505 }
1506 }
1507
1508
1509 #ifndef GPAC_DISABLE_LOG
1510 if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO) ) {
1511 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] New SegmentTimeline: \n"));
1512 for (idx=0; idx<gf_list_count(new_timeline->entries); idx++) {
1513 ent = gf_list_get(new_timeline->entries, idx);
1514 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tt="LLU" d=%d r=%d\n", ent->start_time, ent->duration, ent->repeat_count));
1515 }
1516 }
1517 #endif
1518
1519 return GF_OK;
1520 }
1521
gf_dash_purge_segment_timeline(GF_DASH_Group * group,Double min_start_time)1522 static u32 gf_dash_purge_segment_timeline(GF_DASH_Group *group, Double min_start_time)
1523 {
1524 u32 nb_removed, time_scale;
1525 u64 start_time, min_start, duration;
1526 GF_MPD_SegmentTimeline *timeline=NULL;
1527 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
1528
1529 if (!min_start_time) return 0;
1530 gf_mpd_resolve_segment_duration(rep, group->adaptation_set, group->period, &duration, &time_scale, NULL, &timeline);
1531 if (!timeline) return 0;
1532
1533 min_start = (u64) (min_start_time*time_scale);
1534 start_time = 0;
1535 nb_removed=0;
1536 while (1) {
1537 GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, 0);
1538 if (!ent) break;
1539 if (ent->start_time) start_time = ent->start_time;
1540
1541 while (start_time + ent->duration < min_start) {
1542 if (! ent->repeat_count) break;
1543 ent->repeat_count--;
1544 nb_removed++;
1545 start_time += ent->duration;
1546 }
1547 /*this entry is in our range, keep it and make sure it has a start time*/
1548 if (start_time + ent->duration >= min_start) {
1549 if (!ent->start_time) ent->start_time = start_time;
1550 break;
1551 }
1552 start_time += ent->duration;
1553 gf_list_rem(timeline->entries, 0);
1554 gf_free(ent);
1555 nb_removed++;
1556 }
1557 if (nb_removed) {
1558 GF_MPD_SegmentList *segment_list;
1559 /*update next download index*/
1560 group->download_segment_index -= nb_removed;
1561 assert(group->nb_segments_in_rep >= nb_removed);
1562 group->nb_segments_in_rep -= nb_removed;
1563 /*clean segmentList*/
1564 segment_list = NULL;
1565 if (group->period && group->period->segment_list) segment_list = group->period->segment_list;
1566 if (group->adaptation_set && group->adaptation_set->segment_list) segment_list = group->adaptation_set->segment_list;
1567 if (rep && rep->segment_list) segment_list = rep->segment_list;
1568
1569 if (segment_list) {
1570 u32 i = nb_removed;
1571 while (i) {
1572 GF_MPD_SegmentURL *seg_url = gf_list_get(segment_list->segment_URLs, 0);
1573 gf_list_rem(segment_list->segment_URLs, 0);
1574 gf_mpd_segment_url_free(seg_url);
1575 i--;
1576 }
1577 }
1578 group->nb_segments_purged += nb_removed;
1579 }
1580 return nb_removed;
1581 }
1582
gf_dash_solve_representation_xlink(GF_DashClient * dash,GF_MPD_Representation * rep)1583 static GF_Err gf_dash_solve_representation_xlink(GF_DashClient *dash, GF_MPD_Representation *rep)
1584 {
1585 u32 count, i;
1586 GF_Err e;
1587 Bool is_local=GF_FALSE;
1588 const char *local_url;
1589 char *url;
1590 GF_DOMParser *parser;
1591 if (!rep->segment_list->xlink_href) return GF_BAD_PARAM;
1592
1593 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Resolving Representation SegmentList XLINK %s\n", rep->segment_list->xlink_href));
1594
1595 //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.
1596 if (!strcmp(rep->segment_list->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013")) {
1597 gf_mpd_delete_segment_list(rep->segment_list);
1598 rep->segment_list = NULL;
1599 return GF_OK;
1600 }
1601
1602 //xlink relative to our MPD base URL
1603 url = gf_url_concatenate(dash->base_url, rep->segment_list->xlink_href);
1604
1605 if (!strstr(url, "://") || !strnicmp(url, "file://", 7) ) {
1606 local_url = url;
1607 is_local=GF_TRUE;
1608 e = GF_OK;
1609 } else {
1610 /*use non-persistent connection for MPD*/
1611 e = gf_dash_download_resource(dash, &(dash->mpd_dnload), url ? url : rep->segment_list->xlink_href, 0, 0, 0, NULL);
1612 gf_free(url);
1613 }
1614
1615 if (e) {
1616 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)));
1617 gf_free(rep->segment_list->xlink_href);
1618 rep->segment_list->xlink_href = NULL;
1619 return e;
1620 }
1621
1622 if (!is_local) {
1623 /*in case the session has been restarted, local_url may have been destroyed - get it back*/
1624 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
1625 }
1626
1627 parser = gf_xml_dom_new();
1628 e = gf_xml_dom_parse(parser, local_url, NULL, NULL);
1629 if (is_local) gf_free(url);
1630
1631 if (e != GF_OK) {
1632 gf_xml_dom_del(parser);
1633 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)));
1634 gf_free(rep->segment_list->xlink_href);
1635 rep->segment_list->xlink_href = NULL;
1636 return GF_NON_COMPLIANT_BITSTREAM;
1637 }
1638
1639 count = gf_xml_dom_get_root_nodes_count(parser);
1640 if (count > 1) {
1641 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));
1642 gf_mpd_delete_segment_list(rep->segment_list);
1643 rep->segment_list = NULL;
1644 return GF_NON_COMPLIANT_BITSTREAM;
1645 }
1646 for (i=0; i<count; i++) {
1647 GF_XMLNode *root = gf_xml_dom_get_root_idx(parser, i);
1648 if (!strcmp(root->name, "SegmentList")) {
1649 GF_MPD_SegmentList *new_seg_list = gf_mpd_solve_segment_list_xlink(dash->mpd, root);
1650 //forbiden
1651 if (new_seg_list && new_seg_list->xlink_href) {
1652 if (new_seg_list->xlink_actuate_on_load) {
1653 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));
1654 gf_mpd_delete_segment_list(new_seg_list);
1655 new_seg_list = NULL;
1656 } else {
1657 new_seg_list->consecutive_xlink_count = rep->segment_list->consecutive_xlink_count + 1;
1658 }
1659 }
1660 //replace current segment list by the one from remote element entity (located by xlink:href)
1661 gf_mpd_delete_segment_list(rep->segment_list);
1662 rep->segment_list = new_seg_list;
1663 }
1664 else
1665 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] XML node %s is not a representation segmentlist - ignoring it\n", root->name));
1666 }
1667 return GF_OK;
1668 }
1669
dash_purge_xlink(GF_MPD * new_mpd)1670 static void dash_purge_xlink(GF_MPD *new_mpd)
1671 {
1672 u32 i, count = gf_list_count(new_mpd->periods);
1673 for (i=0; i<count; i++) {
1674 GF_MPD_Period *period = gf_list_get(new_mpd->periods, i);
1675 if (!gf_list_count(period->adaptation_sets)) continue;
1676 if (!period->xlink_href) continue;
1677 gf_free(period->xlink_href);
1678 period->xlink_href = NULL;
1679 }
1680 }
1681
gf_dash_mark_group_done(GF_DASH_Group * group)1682 static void gf_dash_mark_group_done(GF_DASH_Group *group)
1683 {
1684 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) ));
1685 group->done = GF_TRUE;
1686 }
1687
gf_dash_update_manifest(GF_DashClient * dash)1688 static GF_Err gf_dash_update_manifest(GF_DashClient *dash)
1689 {
1690 GF_Err e;
1691 Bool force_timeline_setup = GF_FALSE;
1692 u32 group_idx, rep_idx, i, j;
1693 u64 fetch_time=0;
1694 GF_DOMParser *mpd_parser;
1695 u8 signature[GF_SHA1_DIGEST_SIZE];
1696 GF_MPD_Period *period, *new_period;
1697 const char *local_url;
1698 char mime[128];
1699 char * purl;
1700 Double timeline_start_time;
1701 GF_MPD *new_mpd=NULL;
1702 Bool fetch_only = GF_FALSE;
1703
1704 if (!dash->mpd_dnload) {
1705 local_url = purl = NULL;
1706 if (!gf_list_count(dash->mpd->locations)) {
1707 FILE *t = gf_fopen(dash->base_url, "rt");
1708 if (t) {
1709 local_url = dash->base_url;
1710 gf_fclose(t);
1711 }
1712 if (!local_url) {
1713 /*we will no longer attempt to update the MPD ...*/
1714 dash->mpd->minimum_update_period = 0;
1715 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: no HTTP source for MPD could be found\n"));
1716 return GF_BAD_PARAM;
1717 }
1718 }
1719 if (!local_url) {
1720 purl = gf_strdup(gf_list_get(dash->mpd->locations, 0));
1721
1722 /*if no absolute URL, use <Location> to get MPD in case baseURL is relative...*/
1723 if (!strstr(dash->base_url, "://")) {
1724 gf_free(dash->base_url);
1725 dash->base_url = gf_strdup(purl);
1726 }
1727 fetch_only = 1;
1728 }
1729 } else {
1730 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
1731 if (local_url) {
1732 gf_file_delete(local_url);
1733 }
1734 //use the redirected url stored in base URL - DO NOT USE the redirected URL of the session since
1735 //the session may have been reused for period XLINK dowload.
1736 purl = gf_strdup( dash->base_url );
1737 }
1738
1739 /*if update location is specified, update - spec does not say whether location is a relative or absoute URL*/
1740 if (gf_list_count(dash->mpd->locations)) {
1741 char *update_loc = gf_list_get(dash->mpd->locations, 0);
1742 char *update_url = gf_url_concatenate(purl, update_loc);
1743 if (update_url) {
1744 gf_free(purl);
1745 purl = update_url;
1746 }
1747 }
1748
1749 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updating Playlist %s...\n", purl ? purl : local_url));
1750 if (purl) {
1751 const char *mime_type;
1752 /*use non-persistent connection for MPD*/
1753 e = gf_dash_download_resource(dash, &(dash->mpd_dnload), purl, 0, 0, 0, NULL);
1754 if (e!=GF_OK) {
1755 if (!dash->in_error) {
1756 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: download problem %s for MPD file\n", gf_error_to_string(e)));
1757 dash->in_error = GF_TRUE;
1758 }
1759 gf_free(purl);
1760 //try to refetch MPD every second
1761 dash->last_update_time+=1000;
1762 return e;
1763 } else {
1764 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playlist %s updated with success\n", purl));
1765 }
1766
1767 mime_type = dash->dash_io->get_mime(dash->dash_io, dash->mpd_dnload) ;
1768 strcpy(mime, mime_type ? mime_type : "");
1769 strlwr(mime);
1770
1771 /*in case the session has been restarted, local_url may have been destroyed - get it back*/
1772 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
1773
1774 /* Some servers, for instance http://tv.freebox.fr, serve m3u8 as text/plain */
1775 if (gf_dash_is_m3u8_mime(purl, mime) || strstr(purl, ".m3u8")) {
1776 new_mpd = gf_mpd_new();
1777 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);
1778 if (e) {
1779 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in MPD creation %s\n", gf_error_to_string(e)));
1780 gf_mpd_del(new_mpd);
1781 return GF_NON_COMPLIANT_BITSTREAM;
1782 }
1783 } else if (strlen(mime) && !gf_dash_is_dash_mime(mime)) {
1784 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] mime '%s' should be m3u8 or mpd\n", mime));
1785 }
1786
1787 gf_free(purl);
1788
1789 purl = (char *) dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload) ;
1790
1791 /*if relocated, reassign MPD base URL*/
1792 if (strcmp(purl, dash->base_url)) {
1793 gf_free(dash->base_url);
1794 dash->base_url = gf_strdup(purl);
1795
1796 }
1797
1798 purl = NULL;
1799 }
1800
1801 fetch_time = dash_get_fetch_time(dash);
1802
1803 // parse the mpd file for filling the GF_MPD structure. Note: for m3u8, MPD has been fetched above
1804 if (!new_mpd) {
1805 u32 res = gf_dash_check_mpd_root_type(local_url);
1806 if ((res==1) && dash->is_smooth) res = 0;
1807 else if ((res==2) && !dash->is_smooth) res = 0;
1808
1809 if (!res) {
1810 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: MPD file type is not correct %s\n", local_url));
1811 return GF_NON_COMPLIANT_BITSTREAM;
1812 }
1813
1814 if (gf_sha1_file( local_url, signature) != GF_OK) {
1815 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] : cannot SHA1 file %s\n", local_url));
1816 return GF_IO_ERR;
1817 }
1818
1819 if (!dash->in_error && ! memcmp( signature, dash->lastMPDSignature, GF_SHA1_DIGEST_SIZE)) {
1820
1821 dash->reload_count++;
1822 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] MPD file did not change for %d consecutive reloads\n", dash->reload_count));
1823 /*if the MPD did not change, we should refresh "soon" but cannot wait a full refresh cycle in case of
1824 low latencies as we could miss a segment*/
1825
1826 if (dash->is_m3u8) {
1827 dash->last_update_time += dash->mpd->minimum_update_period/2;
1828 } else {
1829 dash->last_update_time = gf_sys_clock();
1830 }
1831
1832 dash->mpd_fetch_time = fetch_time;
1833 return GF_OK;
1834 }
1835
1836 force_timeline_setup = dash->in_error;
1837 dash->in_error = GF_FALSE;
1838 dash->reload_count = 0;
1839 memcpy(dash->lastMPDSignature, signature, GF_SHA1_DIGEST_SIZE);
1840
1841 /* It means we have to reparse the file ... */
1842 /* parse the MPD */
1843 mpd_parser = gf_xml_dom_new();
1844 e = gf_xml_dom_parse(mpd_parser, local_url, NULL, NULL);
1845 if (e != GF_OK) {
1846 gf_xml_dom_del(mpd_parser);
1847 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in XML parsing %s\n", gf_error_to_string(e)));
1848 return GF_NON_COMPLIANT_BITSTREAM;
1849 }
1850 new_mpd = gf_mpd_new();
1851 e = gf_mpd_init_from_dom(gf_xml_dom_get_root(mpd_parser), new_mpd, purl);
1852 gf_xml_dom_del(mpd_parser);
1853 if (e) {
1854 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in MPD creation %s\n", gf_error_to_string(e)));
1855 gf_mpd_del(new_mpd);
1856 return GF_NON_COMPLIANT_BITSTREAM;
1857 }
1858 if (dash->ignore_xlink)
1859 dash_purge_xlink(new_mpd);
1860
1861 if (!e && dash->split_adaptation_set)
1862 gf_mpd_split_adaptation_sets(new_mpd);
1863
1864 }
1865
1866 assert(new_mpd);
1867
1868 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
1869 if (fetch_only && !period) goto exit;
1870
1871 #ifndef GPAC_DISABLE_LOG
1872 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Updated manifest:\n"));
1873 for (i=0; i<gf_list_count(new_mpd->periods); i++) {
1874 GF_MPD_Period *ap = gf_list_get(new_mpd->periods, i);
1875 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"));
1876 }
1877 #endif
1878
1879 //if current period was a remote period, do a pass on the new manifest periods, check for xlink
1880 if (period->origin_base_url) {
1881 restart_period_check:
1882 for (i=0; i<gf_list_count(new_mpd->periods); i++) {
1883 new_period = gf_list_get(new_mpd->periods, i);
1884 if (!new_period->xlink_href) continue;
1885
1886 if ((new_period->start<=period->start) &&
1887 ( (new_period->start+new_period->duration >= period->start + period->duration) || (!new_period->duration))
1888 ) {
1889 const char *base_url;
1890 u32 insert_idx;
1891 if (period->type == GF_MPD_TYPE_DYNAMIC) {
1892 gf_dash_solve_period_xlink(dash, new_mpd->periods, i);
1893 goto restart_period_check;
1894 }
1895 //this is a static period xlink, no need to further update the mpd, jsut swap the old periods in the new MPD
1896
1897 base_url = period->origin_base_url;
1898 if (!base_url) {
1899 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - found new Xlink period overlapping a non-xlink period in original manifest\n"));
1900 gf_mpd_del(new_mpd);
1901 return GF_NON_COMPLIANT_BITSTREAM;
1902 }
1903 insert_idx = i;
1904 gf_list_rem(new_mpd->periods, i);
1905 for (j=0; j<gf_list_count(dash->mpd->periods); j++) {
1906 GF_MPD_Period *ap = gf_list_get(dash->mpd->periods, j);
1907 if (!ap->origin_base_url) continue;
1908 if (strcmp(ap->origin_base_url, base_url)) continue;
1909 gf_list_rem(dash->mpd->periods, j);
1910 j--;
1911 gf_list_insert(new_mpd->periods, ap, insert_idx);
1912 insert_idx++;
1913 }
1914 //update active period index in new list
1915 dash->active_period_index = gf_list_find(new_mpd->periods, period);
1916 assert((s32)dash->active_period_index >= 0);
1917 //this will do the garbage collection
1918 gf_list_add(dash->mpd->periods, new_period);
1919 goto exit;
1920 }
1921 new_period=NULL;
1922 }
1923 }
1924
1925 new_period = NULL;
1926 for (i=0; i<gf_list_count(new_mpd->periods); i++) {
1927 new_period = gf_list_get(new_mpd->periods, i);
1928 if (new_period->start==period->start) break;
1929 new_period=NULL;
1930 }
1931
1932 if (!new_period) {
1933 if (dash->mpd->type == GF_MPD_TYPE_DYNAMIC) {
1934 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"));
1935
1936 //assume the old period is no longer valid
1937 dash->active_period_index = 0;
1938 dash->request_period_switch = GF_TRUE;
1939 for (i=0; i<gf_list_count(dash->groups); i++) {
1940 GF_DASH_Group *group = gf_list_get(dash->groups, i);
1941 gf_dash_mark_group_done(group);
1942 group->adaptation_set = NULL;
1943 }
1944 goto exit;
1945 }
1946 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing period\n"));
1947 gf_mpd_del(new_mpd);
1948 return GF_NON_COMPLIANT_BITSTREAM;
1949 }
1950
1951 dash->active_period_index = gf_list_find(new_mpd->periods, new_period);
1952 assert((s32)dash->active_period_index >= 0);
1953
1954 if (gf_list_count(period->adaptation_sets) != gf_list_count(new_period->adaptation_sets)) {
1955 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing AdaptationSet\n"));
1956 gf_mpd_del(new_mpd);
1957 return GF_NON_COMPLIANT_BITSTREAM;
1958 }
1959
1960 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updating playlist at UTC time "LLU" - availabilityStartTime "LLU"\n", fetch_time, new_mpd->availabilityStartTime));
1961
1962 timeline_start_time = 0;
1963 /*if not infinity for timeShift, compute min media time before merge and adjust it*/
1964 if (dash->mpd->time_shift_buffer_depth != (u32) -1) {
1965 Double timeshift = dash->mpd->time_shift_buffer_depth;
1966 timeshift /= 1000;
1967
1968 for (group_idx=0; group_idx<gf_list_count(dash->groups); group_idx++) {
1969 GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
1970 if (group->selection!=GF_DASH_GROUP_NOT_SELECTABLE) {
1971 Double group_start = gf_dash_get_segment_start_time(group, NULL);
1972 if (!group_idx || (timeline_start_time > group_start) ) timeline_start_time = group_start;
1973 }
1974 }
1975 /*we can rewind our segments from timeshift*/
1976 if (timeline_start_time > timeshift) timeline_start_time -= timeshift;
1977 /*we can rewind all segments*/
1978 else timeline_start_time = 0;
1979 }
1980
1981 /*update segmentTimeline at Period level*/
1982 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);
1983 if (e) {
1984 gf_mpd_del(new_mpd);
1985 return e;
1986 }
1987
1988 for (group_idx=0; group_idx<gf_list_count(dash->groups); group_idx++) {
1989 GF_MPD_AdaptationSet *set, *new_set;
1990 u32 rep_i;
1991 GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
1992
1993 /*update info even if the group is not selected !*/
1994 if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE)
1995 continue;
1996
1997 set = group->adaptation_set;
1998 new_set = gf_list_get(new_period->adaptation_sets, group_idx);
1999
2000 //sort by bandwidth and quality
2001 for (rep_i = 1; rep_i < gf_list_count(new_set->representations); rep_i++) {
2002 Bool swap=GF_FALSE;
2003 GF_MPD_Representation *r2 = gf_list_get(new_set->representations, rep_i);
2004 GF_MPD_Representation *r1 = gf_list_get(new_set->representations, rep_i-1);
2005 if (r1->bandwidth > r2->bandwidth) {
2006 swap=GF_TRUE;
2007 } else if ((r1->bandwidth == r2->bandwidth) && (r1->quality_ranking<r2->quality_ranking)) {
2008 swap=GF_TRUE;
2009 }
2010 if (swap) {
2011 gf_list_rem(new_set->representations, rep_i);
2012 gf_list_insert(new_set->representations, r2, rep_i-1);
2013 rep_i=0;
2014 }
2015 }
2016
2017 if (gf_list_count(new_set->representations) != gf_list_count(group->adaptation_set->representations)) {
2018 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing representation in adaptation set\n"));
2019 gf_mpd_del(new_mpd);
2020 return GF_NON_COMPLIANT_BITSTREAM;
2021 }
2022
2023 /*get all representations in both periods*/
2024 for (rep_idx = 0; rep_idx <gf_list_count(group->adaptation_set->representations); rep_idx++) {
2025 GF_List *segments, *new_segments;
2026 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_idx);
2027 GF_MPD_Representation *new_rep = gf_list_get(new_set->representations, rep_idx);
2028
2029 if (rep->segment_base || group->adaptation_set->segment_base || period->segment_base) {
2030 if (!new_rep->segment_base && !new_set->segment_base && !new_period->segment_base) {
2031 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment base as previous version\n"));
2032 gf_mpd_del(new_mpd);
2033 return GF_NON_COMPLIANT_BITSTREAM;
2034 }
2035 /*what else should we check ??*/
2036
2037 /*OK, this rep is fine*/
2038 }
2039
2040 else if (rep->segment_template || group->adaptation_set->segment_template || period->segment_template) {
2041 if (!new_rep->segment_template && !new_set->segment_template && !new_period->segment_template) {
2042 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment template as previous version\n"));
2043 gf_mpd_del(new_mpd);
2044 return GF_NON_COMPLIANT_BITSTREAM;
2045 }
2046 //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)
2047 if ((period->segment_template && period->segment_template->segment_timeline)
2048 || (set->segment_template && set->segment_template->segment_timeline)
2049 || (rep->segment_template && rep->segment_template->segment_timeline)
2050 ) {
2051 } else {
2052 s32 sn_diff = 0;
2053
2054 if (period->segment_template && (period->segment_template->start_number != (u32) -1) ) sn_diff = period->segment_template->start_number;
2055 else if (set->segment_template && (set->segment_template->start_number != (u32) -1) ) sn_diff = set->segment_template->start_number;
2056 else if (rep->segment_template && (rep->segment_template->start_number != (u32) -1) ) sn_diff = rep->segment_template->start_number;
2057
2058 if (new_period->segment_template && (new_period->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_period->segment_template->start_number;
2059 else if (new_set->segment_template && (new_set->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_set->segment_template->start_number;
2060 else if (new_rep->segment_template && (new_rep->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_rep->segment_template->start_number;
2061
2062 if (sn_diff != 0) {
2063 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] startNumber change for SegmentTemplate without SegmentTimeline - adjusting current segment index by %d\n", sn_diff));
2064 group->download_segment_index += sn_diff;
2065 }
2066 }
2067 /*OK, this rep is fine*/
2068 }
2069 else {
2070 /*we're using segment list*/
2071 assert(rep->segment_list || group->adaptation_set->segment_list || period->segment_list);
2072
2073 //if we have a xlink_href in segment_list, solve it
2074 while (new_rep->segment_list->xlink_href && (group->active_rep_index==rep_idx)) {
2075 u32 retry=10;
2076 Bool is_static = GF_FALSE;
2077 u64 dur = 0;
2078
2079 if (new_rep->segment_list->consecutive_xlink_count) {
2080 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));
2081 }
2082
2083 while (retry) {
2084 if (dash->is_m3u8) {
2085 e = gf_m3u8_solve_representation_xlink(new_rep, &group->dash->getter, &is_static, &dur);
2086 } else {
2087 e = gf_dash_solve_representation_xlink(group->dash, new_rep);
2088 }
2089 if (e==GF_OK) break;
2090 if (e==GF_NON_COMPLIANT_BITSTREAM) break;
2091 if (e==GF_OUT_OF_MEM) break;
2092 if (group->dash->dash_state != GF_DASH_STATE_RUNNING)
2093 break;
2094
2095 gf_sleep(100);
2096 retry --;
2097 }
2098
2099 if (e==GF_OK) {
2100 if (dash->is_m3u8 && is_static) {
2101 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[m3u8] MPD type changed from dynamic to static\n"));
2102 group->dash->mpd->type = GF_MPD_TYPE_STATIC;
2103 group->dash->mpd->media_presentation_duration = dur;
2104 group->dash->mpd->minimum_update_period = 0;
2105 group->period->duration = dur;
2106 }
2107 }
2108 }
2109
2110
2111 if (!new_rep->segment_list && !new_set->segment_list && !new_period->segment_list) {
2112 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment list as previous version\n"));
2113 gf_mpd_del(new_mpd);
2114 return GF_NON_COMPLIANT_BITSTREAM;
2115 }
2116 /*get the segment list*/
2117 segments = new_segments = NULL;
2118 if (period->segment_list && period->segment_list->segment_URLs) segments = period->segment_list->segment_URLs;
2119 if (set->segment_list && set->segment_list->segment_URLs) segments = set->segment_list->segment_URLs;
2120 if (rep->segment_list && rep->segment_list->segment_URLs) segments = rep->segment_list->segment_URLs;
2121
2122 if (new_period->segment_list && new_period->segment_list->segment_URLs) new_segments = new_period->segment_list->segment_URLs;
2123 if (new_set->segment_list && new_set->segment_list->segment_URLs) new_segments = new_set->segment_list->segment_URLs;
2124 if (new_rep->segment_list && new_rep->segment_list->segment_URLs) new_segments = new_rep->segment_list->segment_URLs;
2125
2126
2127 for (i=0; i<gf_list_count(new_segments); i++) {
2128 GF_MPD_SegmentURL *new_seg = gf_list_get(new_segments, i);
2129 Bool found = GF_FALSE;
2130 for (j=0; j<gf_list_count(segments); j++) {
2131 GF_MPD_SegmentURL *seg = gf_list_get(segments, j);
2132 if (seg->media && new_seg->media && !strcmp(seg->media, new_seg->media)) {
2133 found=1;
2134 break;
2135 }
2136 if (seg->media_range && new_seg->media_range && (seg->media_range->start_range==new_seg->media_range->start_range) && (seg->media_range->end_range==new_seg->media_range->end_range) ) {
2137 found=1;
2138 break;
2139 }
2140 }
2141 /*this is a new segment, merge it: we remove from new list and push to old one, before doing a final swap
2142 this ensures that indexing in the segment_list is still correct after merging*/
2143 if (!found) {
2144 gf_list_rem(new_segments, i);
2145 i--;
2146 gf_list_add(segments, new_seg);
2147 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation #%d: Adding new segment %s\n", rep_idx+1, new_seg->media));
2148 }
2149 }
2150 /*what else should we check ?*/
2151
2152 /*swap segment list content*/
2153 gf_list_swap(new_segments, segments);
2154
2155 //HLS live: if a new time is set (active group only), we just switched betwwe qualities
2156 //locate the segment with the same start time in the manifest, and purge previous ones
2157 //it may happen that the manifest does still not contain the segment we are looking for, force an MPD update
2158 if (group->hls_next_start_time && (group->active_rep_index==rep_idx)) {
2159 u32 k;
2160
2161 for (k=0; k<gf_list_count(new_segments); k++) {
2162 s64 diff;
2163 GF_MPD_SegmentURL *segu = (GF_MPD_SegmentURL *) gf_list_get(new_segments, k);
2164 diff = (s64) group->hls_next_start_time;
2165 diff -= (s64) segu->hls_utc_start_time;
2166 if (abs( (s32) diff)<200) {
2167 group->download_segment_index = k;
2168 group->hls_next_start_time=0;
2169 break;
2170 }
2171 //purge old segments
2172 if (segu->hls_utc_start_time < group->hls_next_start_time) {
2173 gf_mpd_segment_url_free(segu);
2174 gf_list_rem(new_segments, k);
2175 k--;
2176 }
2177 if (segu->hls_utc_start_time > group->hls_next_start_time) {
2178 group->download_segment_index = k;
2179 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Waiting for HLS segment start "LLU" but found segment at "LLU" - missing segment ?\n", group->hls_next_start_time, segu->hls_utc_start_time));
2180 group->hls_next_start_time=0;
2181 break;
2182 }
2183 }
2184 //not yet available
2185 if (group->hls_next_start_time) {
2186 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Cannot find segment for given HLS start time "LLU" - forcing manifest update\n", group->hls_next_start_time));
2187 dash->force_mpd_update=GF_TRUE;
2188 //force sleep of half sec to avoid updating manifest too often - this will need refinement for low latency !!
2189 gf_sleep(500);
2190 }
2191 }
2192
2193 /*current representation is the active one in the group - update the number of segments*/
2194 if (group->active_rep_index==rep_idx) {
2195 group->nb_segments_in_rep = gf_list_count(new_segments);
2196 }
2197 }
2198
2199 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);
2200 if (e) {
2201 gf_mpd_del(new_mpd);
2202 return e;
2203 }
2204
2205 //move redirections in representations base URLs (we could do that on AS as well )
2206 if (rep->base_URLs && new_rep->base_URLs) {
2207 u32 k;
2208 for (i=0; i<gf_list_count(new_rep->base_URLs); i++) {
2209 GF_MPD_BaseURL *n_url = gf_list_get(new_rep->base_URLs, i);
2210 if (!n_url->URL) continue;
2211
2212 for (k=0; k<gf_list_count(rep->base_URLs); k++) {
2213 GF_MPD_BaseURL *o_url = gf_list_get(rep->base_URLs, k);
2214 if (o_url->URL && !strcmp(o_url->URL, n_url->URL)) {
2215 n_url->redirection = o_url->redirection;
2216 o_url->redirection = NULL;
2217 }
2218 }
2219 }
2220 }
2221
2222 /*what else should we check ??*/
2223
2224
2225 /*switch all internal GPAC stuff*/
2226 memcpy(&new_rep->playback, &rep->playback, sizeof(GF_DASH_RepresentationPlayback));
2227 if (rep->playback.cached_init_segment_url) rep->playback.cached_init_segment_url = NULL;
2228
2229 if (!new_rep->mime_type) {
2230 new_rep->mime_type = rep->mime_type;
2231 rep->mime_type = NULL;
2232 }
2233 }
2234
2235 /*update segmentTimeline at AdaptationSet level before switching the set (old setup needed to compute current timing of each group) */
2236 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);
2237 if (e) {
2238 gf_mpd_del(new_mpd);
2239 return e;
2240 }
2241 }
2242 //good to go, switch pointers
2243 for (group_idx=0; group_idx<gf_list_count(dash->groups); group_idx++) {
2244 Double seg_dur;
2245 Bool reset_segment_count;
2246 GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
2247 /*update group/period to new period*/
2248 j = gf_list_find(group->period->adaptation_sets, group->adaptation_set);
2249 group->adaptation_set = gf_list_get(new_period->adaptation_sets, j);
2250 group->period = new_period;
2251 assert(group->adaptation_set);
2252
2253 j = gf_list_count(group->adaptation_set->representations);
2254 assert(j);
2255
2256 /*now that all possible SegmentXXX have been updated, purge them if needed: all segments ending before timeline_start_time
2257 will be removed from MPD*/
2258 if (timeline_start_time) {
2259 u32 nb_segments_removed = gf_dash_purge_segment_timeline(group, timeline_start_time);
2260 if (nb_segments_removed) {
2261 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));
2262 }
2263 }
2264
2265 if (force_timeline_setup) {
2266 group->timeline_setup = 0;
2267 group->start_number_at_last_ast = 0;
2268 gf_dash_group_timeline_setup(new_mpd, group, fetch_time);
2269 }
2270 else if (new_mpd->availabilityStartTime != dash->mpd->availabilityStartTime) {
2271 s64 diff = new_mpd->availabilityStartTime;
2272 diff -= dash->mpd->availabilityStartTime;
2273 if (diff < 0) diff = -diff;
2274 if (diff>3000)
2275 gf_dash_group_timeline_setup(new_mpd, group, fetch_time);
2276 }
2277
2278 group->maybe_end_of_stream = 0;
2279 reset_segment_count = GF_FALSE;
2280 /*compute fetchTime + minUpdatePeriod and check period end time*/
2281 if (new_mpd->minimum_update_period && new_mpd->media_presentation_duration) {
2282 u64 endTime = fetch_time - new_mpd->availabilityStartTime - period->start;
2283 if (endTime > new_mpd->media_presentation_duration) {
2284 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));
2285 new_mpd->media_presentation_duration = 0;
2286 reset_segment_count = GF_TRUE;
2287 } else {
2288 endTime += new_mpd->minimum_update_period;
2289 if (endTime > new_mpd->media_presentation_duration) {
2290 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));
2291 group->maybe_end_of_stream = 1;
2292 }
2293 }
2294 }
2295
2296 /*update number of segments in active rep*/
2297 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);
2298
2299 if (reset_segment_count) {
2300 u32 nb_segs_in_mpd_period = (u32) (dash->mpd->minimum_update_period / (1000*seg_dur) );
2301 group->nb_segments_in_rep = group->download_segment_index + nb_segs_in_mpd_period;
2302 }
2303 /*check if number of segments are coherent ...*/
2304 else if (!group->maybe_end_of_stream && new_mpd->minimum_update_period && new_mpd->media_presentation_duration) {
2305 u32 nb_segs_in_mpd_period = (u32) (dash->mpd->minimum_update_period / (1000*seg_dur) );
2306
2307 if (group->download_segment_index + nb_segs_in_mpd_period >= group->nb_segments_in_rep) {
2308 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));
2309 group->maybe_end_of_stream = 1;
2310 }
2311 }
2312
2313 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updated AdaptationSet %d - %d segments\n", group_idx+1, group->nb_segments_in_rep));
2314
2315 if (!period->duration && new_period->duration) {
2316 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));
2317 if (group->download_segment_index > (s32) group->nb_segments_in_rep)
2318 gf_dash_mark_group_done(group);
2319 }
2320
2321 }
2322
2323 exit:
2324 /*swap MPDs*/
2325 if (dash->mpd) {
2326 if (!new_mpd->minimum_update_period && (new_mpd->type==GF_MPD_TYPE_DYNAMIC))
2327 new_mpd->minimum_update_period = dash->mpd->minimum_update_period;
2328 gf_mpd_del(dash->mpd);
2329 }
2330 dash->mpd = new_mpd;
2331 dash->last_update_time = gf_sys_clock();
2332 dash->mpd_fetch_time = fetch_time;
2333
2334 #ifndef GPAC_DISABLE_LOG
2335 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Manifest after update:\n"));
2336 for (i=0; i<gf_list_count(new_mpd->periods); i++) {
2337 GF_MPD_Period *ap = gf_list_get(new_mpd->periods, i);
2338 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"));
2339 }
2340 #endif
2341
2342 return GF_OK;
2343 }
2344
2345
gf_dash_set_group_representation(GF_DASH_Group * group,GF_MPD_Representation * rep)2346 static void gf_dash_set_group_representation(GF_DASH_Group *group, GF_MPD_Representation *rep)
2347 {
2348 #ifndef GPAC_DISABLE_LOG
2349 u32 width=0, height=0, samplerate=0;
2350 GF_MPD_Fractional *framerate=NULL;
2351 #endif
2352 u32 k;
2353 s32 timeshift;
2354 GF_MPD_AdaptationSet *set;
2355 GF_MPD_Period *period;
2356 u32 nb_segs;
2357 u32 i = gf_list_find(group->adaptation_set->representations, rep);
2358 u32 prev_active_rep_index = group->active_rep_index;
2359 u32 nb_cached_seg_per_rep = group->max_cached_segments / gf_dash_group_count_rep_needed(group);
2360 assert((s32) i >= 0);
2361
2362 /* in case of dependent representations: we set max_complementary_rep_index than active_rep_index*/
2363 if (group->base_rep_index_plus_one)
2364 group->max_complementary_rep_index = i;
2365 else
2366 group->active_rep_index = i;
2367 group->active_bitrate = rep->bandwidth;
2368 group->max_cached_segments = nb_cached_seg_per_rep * gf_dash_group_count_rep_needed(group);
2369 nb_segs = group->nb_segments_in_rep;
2370
2371 group->min_bandwidth_selected = GF_TRUE;
2372 for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
2373 GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
2374 if (group->active_bitrate > arep->bandwidth) {
2375 group->min_bandwidth_selected = GF_FALSE;
2376 break;
2377 }
2378 }
2379
2380 while (rep->segment_list && rep->segment_list->xlink_href) {
2381 Bool is_static = GF_FALSE;
2382 u64 dur = 0;
2383 u32 retry=10;
2384 GF_Err e=GF_OK;
2385
2386 if (rep->segment_list->consecutive_xlink_count) {
2387 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));
2388 }
2389
2390 while (retry) {
2391 if (group->dash->is_m3u8) {
2392 e = gf_m3u8_solve_representation_xlink(rep, &group->dash->getter, &is_static, &dur);
2393 } else {
2394 e = gf_dash_solve_representation_xlink(group->dash, rep);
2395 }
2396 if (e==GF_OK) break;
2397 if (e==GF_NON_COMPLIANT_BITSTREAM) break;
2398 if (group->dash->dash_state != GF_DASH_STATE_RUNNING)
2399 break;
2400
2401 retry--;
2402 gf_sleep(100);
2403 }
2404
2405 //after resolving xlink: if this represenstation is marked as disabled, we have nothing to do
2406 if (rep->playback.disabled)
2407 return;
2408
2409 if (e) {
2410 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Could not reslove XLINK %s in time - using old representation\n", (rep->segment_list && rep->segment_list->xlink_href) ? rep->segment_list->xlink_href : ""));
2411 group->active_rep_index = prev_active_rep_index;
2412 return;
2413 }
2414
2415 //we only know this is a static or dynamic MPD after parsing the first subplaylist
2416 //if this is static, we need to update infos in mpd and period
2417 if (group->dash->is_m3u8 && is_static) {
2418 group->dash->mpd->type = GF_MPD_TYPE_STATIC;
2419 group->dash->mpd->media_presentation_duration = dur;
2420 group->dash->mpd->minimum_update_period = 0;
2421 group->period->duration = dur;
2422 }
2423 }
2424
2425 if (group->dash->is_m3u8) {
2426 //here we change to another representation: we need to remove all URLs from segment list and adjust the download segment index for this group
2427 if (group->dash->dash_state == GF_DASH_STATE_RUNNING) {
2428 u32 next_media_seq = group->m3u8_start_media_seq + group->download_segment_index;
2429 GF_MPD_Representation *prev_active_rep = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, prev_active_rep_index);
2430
2431 if (group->dash->mpd->type == GF_MPD_TYPE_DYNAMIC) {
2432 u64 current_start_time = 0;
2433 Bool next_found=GF_FALSE;
2434
2435 //find the start time of the next segment on the old representation
2436 GF_MPD_SegmentURL *seg_url = gf_list_get(prev_active_rep->segment_list->segment_URLs, group->download_segment_index);
2437 if (!seg_url) {
2438 //end of segment list, assume next was last one plus duration
2439 seg_url = gf_list_last(prev_active_rep->segment_list->segment_URLs);
2440 if (seg_url) current_start_time = seg_url->hls_utc_start_time + (seg_url->duration ? seg_url->duration : prev_active_rep->segment_list->duration);
2441 } else {
2442 current_start_time = seg_url->hls_utc_start_time;
2443 }
2444 group->hls_next_start_time = 0;
2445
2446 //check in new list where the start is
2447 for (k=0; rep->segment_list && k<gf_list_count(rep->segment_list->segment_URLs); k++) {
2448 s64 start_diff;
2449 seg_url = (GF_MPD_SegmentURL *) gf_list_get(rep->segment_list->segment_URLs, k);
2450
2451 start_diff = (s64) current_start_time;
2452 start_diff -= (s64) seg_url->hls_utc_start_time;
2453 //Warning, we may have precision issues in start times, add 200 ms for safety
2454 if (ABS(start_diff) <= 200) {
2455 group->download_segment_index = k;
2456 next_media_seq = rep->m3u8_media_seq_min + group->download_segment_index;
2457 next_found = GF_TRUE;
2458 break;
2459 }
2460 if (current_start_time < seg_url->hls_utc_start_time) {
2461 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Switching to HLS start time "LLU" but found earlier segment with start time "LLU" - probably lost one segment\n", current_start_time, seg_url->hls_utc_start_time));
2462 group->download_segment_index = k;
2463 next_media_seq = rep->m3u8_media_seq_min + group->download_segment_index;
2464 next_found = GF_TRUE;
2465 break;
2466 }
2467 }
2468 //no segment in the new playlist is found for this start time, force an MPD update
2469 if (!next_found) {
2470 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] No segment in new rep for current HLS time "LLU", updating manifest\n", current_start_time));
2471 group->hls_next_start_time = current_start_time;
2472 //this will force the MPD update below
2473 next_media_seq = 1+rep->m3u8_media_seq_max;
2474 }
2475 }
2476
2477 if (rep->m3u8_media_seq_min > next_media_seq) {
2478 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Something wrong here: next media segment %d vs min media segment in segment list %d - some segments missing\n", next_media_seq, rep->m3u8_media_seq_min));
2479 group->download_segment_index = rep->m3u8_media_seq_min;
2480 } else if (rep->m3u8_media_seq_max < next_media_seq) {
2481 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Too late: next media segment %d vs max media segment in segment list %d - force updating mpd\n", next_media_seq, rep->m3u8_media_seq_max));
2482 group->dash->force_mpd_update = GF_TRUE;
2483 } else {
2484 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] next media segment %d found in segment list (min %d - max %d) - adjusting download segment index\n", next_media_seq, rep->m3u8_media_seq_min, rep->m3u8_media_seq_max));
2485 group->download_segment_index = next_media_seq - rep->m3u8_media_seq_min;
2486 }
2487 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] after switching download segment index should be %d\n", group->download_segment_index));
2488 }
2489 group->m3u8_start_media_seq = rep->m3u8_media_seq_min;
2490 }
2491
2492 set = group->adaptation_set;
2493 period = group->period;
2494
2495 #ifndef GPAC_DISABLE_LOG
2496
2497 #define GET_REP_ATTR(_a) _a = rep->_a; if (!_a) _a = set->_a;
2498
2499 GET_REP_ATTR(width);
2500 GET_REP_ATTR(height);
2501 GET_REP_ATTR(samplerate);
2502 GET_REP_ATTR(framerate);
2503
2504 if (width || height) {
2505 u32 num=25, den=1;
2506 if (framerate) {
2507 num = framerate->num;
2508 if (framerate->den) den = framerate->den;
2509 }
2510
2511 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/1024, width, height, num, den, group->dash->speed));
2512 }
2513 else if (samplerate) {
2514 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/1024, samplerate, group->dash->speed));
2515 }
2516 else {
2517 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/1024, group->dash->speed));
2518 }
2519 #endif
2520
2521 gf_dash_get_segment_duration(rep, set, period, group->dash->mpd, &group->nb_segments_in_rep, &group->segment_duration);
2522
2523 /*if broken indication in duration restore previous seg count*/
2524 if (group->dash->ignore_mpd_duration)
2525 group->nb_segments_in_rep = nb_segs;
2526
2527 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) ) );
2528 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) ) );
2529 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) ) );
2530
2531 if (timeshift == -1) timeshift = (s32) group->dash->mpd->time_shift_buffer_depth;
2532 group->time_shift_buffer_depth = (u32) timeshift;
2533
2534 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);
2535 }
2536
gf_dash_switch_group_representation(GF_DashClient * mpd,GF_DASH_Group * group)2537 static void gf_dash_switch_group_representation(GF_DashClient *mpd, GF_DASH_Group *group)
2538 {
2539 u32 i, bandwidth, min_bandwidth;
2540 GF_MPD_Representation *rep_sel = NULL;
2541 GF_MPD_Representation *min_rep_sel = NULL;
2542 Bool min_bandwidth_selected = GF_FALSE;
2543 bandwidth = 0;
2544 min_bandwidth = (u32) -1;
2545
2546 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Checking representations between %d and %d kbps\n", group->min_bitrate/1024, group->max_bitrate/1024));
2547
2548 if (group->force_representation_idx_plus_one) {
2549 rep_sel = gf_list_get(group->adaptation_set->representations, group->force_representation_idx_plus_one - 1);
2550 group->force_representation_idx_plus_one = 0;
2551 }
2552
2553 if (!rep_sel) {
2554 for (i=0; i<gf_list_count(group->adaptation_set->representations); i++) {
2555 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, i);
2556 if (rep->playback.disabled) continue;
2557 if ((rep->bandwidth > bandwidth) && (rep->bandwidth < group->max_bitrate )) {
2558 rep_sel = rep;
2559 bandwidth = rep->bandwidth;
2560 }
2561 if (rep->bandwidth < min_bandwidth) {
2562 min_rep_sel = rep;
2563 min_bandwidth = rep->bandwidth;
2564 }
2565 }
2566 }
2567
2568 if (!rep_sel) {
2569 if (!min_rep_sel) {
2570 min_rep_sel = gf_list_get(group->adaptation_set->representations, 0);
2571 }
2572 rep_sel = min_rep_sel;
2573 min_bandwidth_selected = 1;
2574 }
2575 assert(rep_sel);
2576 i = gf_list_find(group->adaptation_set->representations, rep_sel);
2577
2578 assert((s32) i >= 0);
2579
2580 group->force_switch_bandwidth = 0;
2581 group->max_bitrate = 0;
2582 group->min_bitrate = (u32) -1;
2583
2584 if (i != group->active_rep_index) {
2585 if (min_bandwidth_selected) {
2586 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/1024, rep_sel->bandwidth/1024));
2587 }
2588 gf_dash_set_group_representation(group, rep_sel);
2589 }
2590 }
2591
2592
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)2593 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)
2594 {
2595 GF_Err e;
2596 GF_MPD_AdaptationSet *set = group->adaptation_set;
2597 GF_MPD_Period *period = group->period;
2598 u32 timescale;
2599
2600 if (!mpd_url) return GF_BAD_PARAM;
2601
2602 if (!strncmp(mpd_url, "gfio://", 7))
2603 mpd_url = gf_file_basename(gf_fileio_translate_url(mpd_url));
2604
2605 if (!group->timeline_setup) {
2606 gf_dash_group_timeline_setup(mpd, group, 0);
2607 //we must wait for ATSC 3.0 clock to initialize, even if first period is static remote (we need to know when to tune)
2608 if (group->dash->atsc_clock_state==1)
2609 return GF_IP_NETWORK_EMPTY;
2610
2611 if (group->dash->reinit_period_index)
2612 return GF_IP_NETWORK_EMPTY;
2613 group->timeline_setup = 1;
2614 item_index = group->download_segment_index;
2615 }
2616
2617 gf_mpd_resolve_segment_duration(rep, set, period, segment_duration, ×cale, NULL, NULL);
2618 *segment_duration = (resolve_type==GF_MPD_RESOLVE_URL_MEDIA) ? (u32) ((Double) ((*segment_duration) * 1000.0) / timescale) : 0;
2619 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);
2620
2621
2622 if (e == GF_NON_COMPLIANT_BITSTREAM) {
2623 // group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
2624 }
2625 if (!*out_url) {
2626 return e;
2627 }
2628
2629 if (*out_url && data_url_process && !strncmp(*out_url, "data:", 5)) {
2630 char *sep;
2631 sep = strstr(*out_url, ";base64,");
2632 if (sep) {
2633 GF_Blob *blob;
2634 u32 len;
2635 sep+=8;
2636 len = (u32)strlen(sep) + 1;
2637 GF_SAFEALLOC(blob, GF_Blob);
2638 if (!blob) return GF_OUT_OF_MEM;
2639
2640 blob->data = (char *)gf_malloc(len);
2641 blob->size = gf_base64_decode(sep, len, blob->data, len);
2642 sprintf(*out_url, "gmem://%p", blob);
2643 *data_url_process = GF_TRUE;
2644 } else {
2645 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("data scheme with encoding different from base64 not supported\n"));
2646 }
2647 }
2648
2649 return e;
2650 }
2651
2652 /* 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*/
gf_dash_get_max_available_speed(GF_DashClient * dash,GF_DASH_Group * group,GF_MPD_Representation * rep)2653 static Double gf_dash_get_max_available_speed(GF_DashClient *dash, GF_DASH_Group *group, GF_MPD_Representation *rep)
2654 {
2655 Double max_available_speed = 0;
2656 Double max_dl_speed, max_decoding_speed;
2657 u32 framerate;
2658 u32 bytes_per_sec;
2659
2660 if (!group->irap_max_dec_time && !group->avg_dec_time) {
2661 return 0;
2662 }
2663 bytes_per_sec = group->backup_Bps;
2664 max_dl_speed = 8.0*bytes_per_sec / rep->bandwidth;
2665
2666 //if framerate is not in MPD, suppose that it is 25 fps
2667 framerate = 25;
2668 if (rep->framerate) {
2669 framerate = rep->framerate->num;
2670 if (rep->framerate->den) {
2671 framerate /= rep->framerate->den;
2672 }
2673 }
2674
2675 if (group->decode_only_rap)
2676 max_decoding_speed = group->irap_max_dec_time ? 1000000.0 / group->irap_max_dec_time : 0;
2677 else
2678 max_decoding_speed = group->avg_dec_time ? 1000000.0 / (group->max_dec_time + group->avg_dec_time*(framerate - 1)) : 0;
2679 max_available_speed = max_decoding_speed > max_dl_speed ? max_dl_speed : max_decoding_speed;
2680 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));
2681
2682 return max_available_speed;
2683 }
2684
dash_store_stats(GF_DashClient * dash,GF_DASH_Group * group,u32 bytes_per_sec,u32 file_size,Bool is_broadcast)2685 static void dash_store_stats(GF_DashClient *dash, GF_DASH_Group *group, u32 bytes_per_sec, u32 file_size, Bool is_broadcast)
2686 {
2687 const char *url;
2688 GF_MPD_Representation *rep;
2689
2690 if (!group->nb_cached_segments)
2691 return;
2692 url = strrchr( group->cached[group->nb_cached_segments-1].url, '/');
2693 if (!url) url = strrchr( group->cached[group->nb_cached_segments-1].url, '\\');
2694 if (url) url+=1;
2695 else url = group->cached[group->nb_cached_segments-1].url;
2696
2697 group->total_size = file_size;
2698 //in broadcast mode, just store the rate
2699 if (is_broadcast) group->bytes_per_sec = bytes_per_sec;
2700 //otherwise store the min rate we got (to deal with complementary representations)
2701 else if (!group->bytes_per_sec || group->bytes_per_sec > bytes_per_sec) group->bytes_per_sec = bytes_per_sec;
2702
2703 group->last_segment_time = gf_sys_clock();
2704 group->nb_segments_since_switch ++;
2705
2706 if (!dash->thread_mode) {
2707 group->prev_segment_ok = GF_TRUE;
2708 if (group->time_at_first_failure) {
2709 if (group->current_base_url_idx) {
2710 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 by switching baseURL\n", url));
2711 } else {
2712 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 - was our download schedule %d too early ?\n", url, gf_sys_clock() - group->time_at_first_failure));
2713 }
2714 group->time_at_first_failure = 0;
2715 }
2716 group->nb_consecutive_segments_lost = 0;
2717 group->current_base_url_idx = 0;
2718 }
2719
2720 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
2721 rep->playback.broadcast_flag = is_broadcast;
2722
2723 #ifndef GPAC_DISABLE_LOG
2724 if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
2725 u32 i, buffer_ms = 0;
2726 Double bitrate, time;
2727 //force a call go query buffer
2728 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, gf_list_find(dash->groups, group), GF_OK);
2729 buffer_ms = group->buffer_occupancy_ms;
2730 for (i=0; i < group->nb_cached_segments; i++) {
2731 buffer_ms += group->cached[i].duration;
2732 }
2733
2734 bitrate=0;
2735 time=0;
2736 if (group->current_downloaded_segment_duration) {
2737 bitrate = 8*group->total_size;
2738 bitrate /= group->current_downloaded_segment_duration;
2739 }
2740
2741 if (bytes_per_sec) {
2742 time = group->total_size;
2743 time /= bytes_per_sec;
2744 }
2745 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d got %s stats: %d bytes in %g sec (%d kbps) - duration %g sec - Media Rate: indicated %d - computed %d kbps - buffer %d ms\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), url, group->total_size, time, 8*bytes_per_sec/1000, group->current_downloaded_segment_duration/1000.0, rep->bandwidth/1000, (u32) bitrate, buffer_ms));
2746 }
2747 #endif
2748 }
2749
dash_do_rate_monitor_default(GF_DashClient * dash,GF_DASH_Group * group)2750 static GF_Err dash_do_rate_monitor_default(GF_DashClient *dash, GF_DASH_Group *group)
2751 {
2752 Bool default_switch_mode = GF_FALSE;
2753 u32 download_rate, set_idx, time_since_start, done, tot_size, time_until_end;
2754 if (group->depend_on_group) return GF_BAD_PARAM;
2755 if (group->dash->disable_switching) return GF_OK;
2756
2757 if (group->buffering)
2758 return GF_OK;
2759
2760 if (group->segment_download) {
2761 download_rate = group->dash->dash_io->get_bytes_per_sec(group->dash->dash_io, group->segment_download);
2762 done = group->dash->dash_io->get_bytes_done(group->dash->dash_io, group->segment_download);
2763 tot_size = group->dash->dash_io->get_total_size(group->dash->dash_io, group->segment_download);
2764 } else {
2765 download_rate = group->bytes_per_sec;
2766 done = group->bytes_done;
2767 tot_size = group->total_size;
2768
2769 }
2770 if (!download_rate) return GF_OK;
2771
2772 time_until_end = 0;
2773 if (tot_size) {
2774 time_until_end = 1000*(tot_size-done) / download_rate;
2775 }
2776
2777 download_rate *= 8;
2778 if (download_rate<group->min_bitrate) group->min_bitrate = download_rate;
2779 if (download_rate>group->max_bitrate) group->max_bitrate = download_rate;
2780
2781 if (download_rate > group->active_bitrate) {
2782 return GF_OK;
2783 }
2784
2785 set_idx = gf_list_find(group->period->adaptation_sets, group->adaptation_set)+1;
2786 time_since_start = gf_sys_clock() - group->download_start_time;
2787
2788 if (group->min_bandwidth_selected) {
2789 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%d at rate %d kbps but media bitrate is"
2790 " %d kbps - no lower bitrate available ...\n", set_idx, download_rate/1024, group->active_bitrate/1024 ));
2791 return GF_OK;
2792 }
2793
2794 //TODO - when do we start checking ?
2795 if (time_since_start < 200) {
2796 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%ds at rate %d kbps but "
2797 "media bitrate is %d kbps\n", set_idx, download_rate/1024, group->active_bitrate/1024 ));
2798 return GF_OK;
2799 }
2800
2801 if (time_until_end) {
2802 u32 i, cache_dur=0;
2803 for (i=1; i<group->nb_cached_segments; i++) {
2804 cache_dur += group->cached[i].duration;
2805 }
2806 //we have enough cache data to go until end of this download, perform rate switching at next segment
2807 if (time_until_end<cache_dur) {
2808 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%ds at rate %d kbps but "
2809 "media bitrate is %d kbps - %d till end of download and %d in cache - "
2810 "going on with download\n", set_idx, download_rate/1024, group->active_bitrate/1024,time_until_end, cache_dur ));
2811 return GF_OK;
2812 }
2813 }
2814
2815 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Downloading from set #%d at rate %d kbps but "
2816 "media bitrate is %d kbps - %d/%d in cache - killing connection and "
2817 "switching\n", set_idx, download_rate/1024, group->active_bitrate/1024, group->nb_cached_segments, group->max_cached_segments ));
2818
2819 if (dash->thread_mode) {
2820 group->download_abort_type = 2;
2821 group->dash->dash_io->abort(group->dash->dash_io, group->segment_download);
2822 } else {
2823 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_ABORT_DOWNLOAD, gf_list_find(dash->groups, group), GF_OK);
2824 }
2825 //in live we just abort current download and go to next. In onDemand, we may want to rebuffer
2826 default_switch_mode = (group->dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_FALSE : GF_TRUE;
2827
2828 //if we have time to download from another rep ?
2829 if (group->current_downloaded_segment_duration <= time_since_start) {
2830 //don't force bandwidth switch (it's too late anyway, consider we lost the segment), let the rate adaptation decide
2831 group->force_switch_bandwidth = default_switch_mode;
2832
2833 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Download time longer than segment duration - trying to resync on next "
2834 "segment\n"));
2835 } else {
2836 u32 target_rate;
2837 //compute min bitrate needed to fetch the segment in another rep, with the time remaining
2838 Double ratio = ((u32)group->current_downloaded_segment_duration - time_since_start);
2839 ratio /= (u32)group->current_downloaded_segment_duration;
2840
2841 target_rate = (u32) (download_rate * ratio);
2842
2843 if (target_rate < group->min_representation_bitrate) {
2844 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Download rate lower than min available rate ...\n"));
2845 target_rate = group->min_representation_bitrate;
2846 //don't force bandwidth switch, we won't have time to redownload the segment.
2847 group->force_switch_bandwidth = default_switch_mode;
2848 } else {
2849 group->force_switch_bandwidth = GF_TRUE;
2850 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Attempting to re-download at target rate %d\n", target_rate));
2851 }
2852 //cap max bitrate for next rate adaptation pass
2853 group->max_bitrate = target_rate;
2854 }
2855 return GF_OK;
2856 }
2857
dash_do_rate_adaptation_legacy_rate(GF_DashClient * dash,GF_DASH_Group * group,GF_DASH_Group * base_group,u32 dl_rate,Double speed,Double max_available_speed,Bool force_lower_complexity,GF_MPD_Representation * rep,Bool go_up_bitrate)2858 static s32 dash_do_rate_adaptation_legacy_rate(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
2859 u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
2860 GF_MPD_Representation *rep, Bool go_up_bitrate)
2861 {
2862 u32 k;
2863 Bool do_switch;
2864 GF_MPD_Representation *new_rep;
2865 s32 new_index = group->active_rep_index;
2866
2867 /* records the number of representations between the current one and the next chosen one */
2868 u32 nb_inter_rep = 0;
2869
2870 /* We assume that there will be a change in quality */
2871 do_switch = GF_TRUE;
2872
2873 /*find best bandwidth that fits our bitrate and playing speed*/
2874 new_rep = NULL;
2875
2876 /* for each playable representation, if we still need to switch, evaluate it */
2877 for (k = 0; k<gf_list_count(group->adaptation_set->representations) && do_switch; k++) {
2878 GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
2879 if (!arep->playback.prev_max_available_speed) {
2880 arep->playback.prev_max_available_speed = 1.0;
2881 }
2882 if (arep->playback.disabled) {
2883 continue;
2884 }
2885 if (arep->playback.prev_max_available_speed && (speed > arep->playback.prev_max_available_speed)) {
2886 continue;
2887 }
2888 /* Only try to switch to a representation, if download rate is greater than its bitrate */
2889 if (dl_rate >= arep->bandwidth) {
2890
2891 /* First check if we are adapting to CPU */
2892 if (!dash->disable_speed_adaptation && force_lower_complexity) {
2893
2894 /*try to switch to highest quality below the current one*/
2895 if ((arep->quality_ranking < rep->quality_ranking) ||
2896 (arep->width < rep->width) || (arep->height < rep->height)) {
2897
2898 /* If we hadn't found a new representation, use it
2899 otherwise use it only if current representation is better*/
2900 if (!new_rep) {
2901 new_rep = arep;
2902 new_index = k;
2903 }
2904 else if ((arep->quality_ranking > new_rep->quality_ranking) ||
2905 (arep->width > new_rep->width) || (arep->height > new_rep->height)) {
2906 new_rep = arep;
2907 new_index = k;
2908 }
2909 }
2910 rep->playback.prev_max_available_speed = max_available_speed;
2911 go_up_bitrate = GF_FALSE;
2912 }
2913 else {
2914 /* if speed adaptation is not used or used, but the new representation can be played at the right speed */
2915
2916 if (!new_rep) {
2917 new_rep = arep;
2918 new_index = k;
2919 }
2920 else if (go_up_bitrate) {
2921
2922 /* agressive switching is configured in the GPAC configuration */
2923 if (dash->agressive_switching) {
2924 /*be agressive, try to switch to highest bitrate below available download rate*/
2925 if (arep->bandwidth > new_rep->bandwidth) {
2926 if (new_rep->bandwidth > rep->bandwidth) {
2927 nb_inter_rep++;
2928 }
2929 new_rep = arep;
2930 new_index = k;
2931 }
2932 else if (arep->bandwidth > rep->bandwidth) {
2933 nb_inter_rep++;
2934 }
2935 }
2936 else {
2937 /*don't be agressive, try to switch to lowest bitrate above our current rep*/
2938 if (new_rep->bandwidth <= rep->bandwidth) {
2939 new_rep = arep;
2940 new_index = k;
2941 }
2942 else if ((arep->bandwidth < new_rep->bandwidth) && (arep->bandwidth > rep->bandwidth)) {
2943 new_rep = arep;
2944 new_index = k;
2945 }
2946 }
2947 }
2948 else {
2949 /* go_up_bitrate is GF_FALSE */
2950 /*try to switch to highest bitrate below available download rate*/
2951 if (arep->bandwidth > new_rep->bandwidth) {
2952 new_rep = arep;
2953 new_index = k;
2954 }
2955 }
2956 }
2957 }
2958 }
2959
2960 if (!new_rep || (new_rep == rep)) {
2961 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));
2962 do_switch = GF_FALSE;
2963 }
2964
2965 if (do_switch) {
2966 //if we're switching to the next upper bitrate (no intermediate bitrates), do not immediately switch
2967 //but for a given number of segments - this avoids fluctuation in the quality
2968 if (go_up_bitrate && !nb_inter_rep) {
2969 new_rep->playback.probe_switch_count++;
2970 if (new_rep->playback.probe_switch_count > dash->probe_times_before_switch) {
2971 new_rep->playback.probe_switch_count = 0;
2972 } else {
2973 new_index = group->active_rep_index;
2974 }
2975 }
2976 }
2977 return new_index;
2978 }
2979
dash_do_rate_adaptation_legacy_buffer(GF_DashClient * dash,GF_DASH_Group * group,GF_DASH_Group * base_group,u32 dl_rate,Double speed,Double max_available_speed,Bool force_lower_complexity,GF_MPD_Representation * rep,Bool go_up_bitrate)2980 static s32 dash_do_rate_adaptation_legacy_buffer(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
2981 u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
2982 GF_MPD_Representation *rep, Bool go_up_bitrate)
2983 {
2984 Bool do_switch;
2985 s32 new_index = group->active_rep_index;
2986
2987 /* We assume that there will be a change in quality
2988 and then set it to no change or increase if this is not the case */
2989 do_switch = GF_TRUE;
2990
2991 /* if the current representation bitrate is smaller than the measured bandwidth,
2992 tentatively start to increase bitrate */
2993 if (rep->bandwidth < dl_rate) {
2994 go_up_bitrate = GF_TRUE;
2995 }
2996
2997 /* clamp download bitrate to the lowest representation rate, to allow choosing it */
2998 if (dl_rate < group->min_representation_bitrate) {
2999 dl_rate = group->min_representation_bitrate;
3000 }
3001
3002 /* buffer_max_ms is non-null when the adaptation algorithm requires buffer information (e.g. GF_DASH_ALGO_GPAC_LEGACY_BUFFER ) */
3003 /* if the cache is full (i.e. player did not fetch downloaded data yet)
3004 if we are below half of the buffer don't try to go up and limit rate to less than our current rep bandwidth*/
3005 if (group->buffer_max_ms && (group->nb_cached_segments<group->max_cached_segments)) {
3006 u32 buf_high_threshold, buf_low_threshold;
3007 s32 occ;
3008
3009 if (group->current_downloaded_segment_duration && (group->buffer_max_ms > group->current_downloaded_segment_duration)) {
3010 buf_high_threshold = group->buffer_max_ms - (u32)group->current_downloaded_segment_duration;
3011 }
3012 else {
3013 buf_high_threshold = 2 * group->buffer_max_ms / 3;
3014 }
3015 buf_low_threshold = (group->current_downloaded_segment_duration && (group->buffer_min_ms>10)) ? group->buffer_min_ms : (u32)group->current_downloaded_segment_duration;
3016 if (buf_low_threshold > group->buffer_max_ms) buf_low_threshold = 1 * group->buffer_max_ms / 3;
3017
3018 //compute how much we managed to refill (current state minus previous state)
3019 occ = (s32)group->buffer_occupancy_ms;
3020 occ -= (s32)group->buffer_occupancy_at_last_seg;
3021 //if above max buffer force occ>0 since a segment may still be pending and not dispatched (buffer regulation)
3022 if (group->buffer_occupancy_ms>group->buffer_max_ms) occ = 1;
3023
3024 //switch down if current buffer falls below min threshold
3025 if ((s32)group->buffer_occupancy_ms < (s32)buf_low_threshold) {
3026 if (!group->buffer_occupancy_ms) {
3027 dl_rate = group->min_representation_bitrate;
3028 }
3029 else {
3030 dl_rate = (rep->bandwidth > 10) ? rep->bandwidth - 10 : 1;
3031 }
3032 go_up_bitrate = GF_FALSE;
3033 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));
3034 }
3035 //switch up if above max threshold and buffer refill is fast enough
3036 else if ((occ>0) && (group->buffer_occupancy_ms > buf_high_threshold)) {
3037 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));
3038 go_up_bitrate = GF_TRUE;
3039 }
3040 //don't do anything in the middle range of the buffer or if refill not fast enough
3041 else {
3042 do_switch = GF_FALSE;
3043 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));
3044 }
3045 }
3046
3047 /* Unless the switching has been turned off (e.g. middle buffer range),
3048 we apply rate-based adaptation */
3049 if (do_switch) {
3050 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);
3051 }
3052
3053 return new_index;
3054 }
3055
3056 // returns the bitrate and index of the representation having the minimum bitrate above the given rate
get_min_rate_above(GF_List * representations,double rate,s32 * index)3057 static u32 get_min_rate_above(GF_List *representations, double rate, s32 *index) {
3058 u32 k;
3059 u32 min_rate = GF_INT_MAX;
3060 GF_MPD_Representation *rep;
3061
3062 u32 nb_reps = gf_list_count(representations);
3063 for (k = 0; k < nb_reps; k++) {
3064 rep = gf_list_get(representations, k);
3065 if ((rep->bandwidth < min_rate) && (rep->bandwidth > rate)) {
3066 min_rate = rep->bandwidth;
3067 if (index) {
3068 *index = k;
3069 }
3070 return min_rate; // representations are sorted by bandwidth
3071 }
3072 }
3073 return min_rate;
3074 }
3075
3076 // returns the bitrate and index of the representation having the maximum bitrate below the given rate
get_max_rate_below(GF_List * representations,double rate,s32 * index)3077 static u32 get_max_rate_below(GF_List *representations, double rate, s32 *index) {
3078 s32 k;
3079 u32 max_rate = 0;
3080 GF_MPD_Representation *rep;
3081
3082 u32 nb_reps = gf_list_count(representations);
3083 for (k = (s32) nb_reps-1; k >=0 ; k--) {
3084 rep = gf_list_get(representations, k);
3085 if ((rep->bandwidth > max_rate) && (rep->bandwidth < rate)) {
3086 max_rate = rep->bandwidth;
3087 if (index) {
3088 *index = k;
3089 }
3090 return max_rate; // representations are sorted by bandwidth
3091 }
3092 }
3093 return max_rate;
3094 }
3095
3096 /**
3097 Adaptation Algorithm as described in
3098 T.-Y. Huang et al. 2014. A buffer-based approach to rate adaptation: evidence from a large video streaming service.
3099 In Proceedings of the 2014 ACM conference on SIGCOMM (SIGCOMM '14).
3100 */
dash_do_rate_adaptation_bba0(GF_DashClient * dash,GF_DASH_Group * group,GF_DASH_Group * base_group,u32 dl_rate,Double speed,Double max_available_speed,Bool force_lower_complexity,GF_MPD_Representation * rep,Bool go_up_bitrate)3101 static s32 dash_do_rate_adaptation_bba0(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
3102 u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
3103 GF_MPD_Representation *rep, Bool go_up_bitrate)
3104 {
3105 u32 rate_plus;
3106 u32 rate_minus;
3107 u32 rate_prev = group->active_bitrate;
3108 u32 rate_max;
3109 u32 rate_min;
3110 s32 new_index;
3111 u32 r; // reservoir
3112 u32 cu; // cushion
3113 u32 buf_now = group->buffer_occupancy_ms;
3114 u32 buf_max = group->buffer_max_ms;
3115 double f_buf_now;
3116
3117 /* We don't use the segment duration as advertised in the MPD because it may not be there due to segment timeline*/
3118 u32 segment_duration_ms = (u32)group->current_downloaded_segment_duration;
3119
3120 rate_min = ((GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, 0))->bandwidth;
3121 rate_max = ((GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, gf_list_count(group->adaptation_set->representations) - 1))->bandwidth;
3122
3123 if (!buf_max) buf_max = 3*segment_duration_ms;
3124 /* if the current buffer cannot hold an entire new segment, we indicate that we don't want to download it
3125 NOTE: This is not described in the paper
3126 */
3127 if (group->buffer_occupancy_ms + segment_duration_ms > buf_max) {
3128 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] BBA-0: not enough space to download new segment: %d\n", group->buffer_occupancy_ms));
3129 return -1;
3130 }
3131
3132 if (rate_prev == rate_max) {
3133 rate_plus = rate_max;
3134 } else {
3135 rate_plus = get_min_rate_above(group->adaptation_set->representations, rate_prev, NULL);
3136 }
3137
3138 if (rate_prev == rate_min) {
3139 rate_minus = rate_min;
3140 } else {
3141 rate_minus = get_max_rate_below(group->adaptation_set->representations, rate_prev, NULL);
3142 }
3143
3144 /*
3145 * the size of the reservoir is 37.5% of the buffer size, but at least = 1 chunk duration)
3146 * the size of the upper reservoir is 10% of the buffer size
3147 * the size of cushion is between 37.5% and 90% of the buffer size
3148 * the rate map is piece-wise
3149 */
3150 if (buf_max <= segment_duration_ms) {
3151 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));
3152 return -1;
3153 }
3154 r = (u32)(37.5*buf_max / 100);
3155 if (r < segment_duration_ms) {
3156 r = segment_duration_ms;
3157 }
3158 cu = (u32)((90-37.5)*buf_max / 100);
3159
3160 if (buf_now <= r) {
3161 f_buf_now = rate_min;
3162 }
3163 else if (buf_now >= (cu + r)) {
3164 f_buf_now = rate_max;
3165 }
3166 else {
3167 f_buf_now = rate_min + (rate_max - rate_min)*((buf_now - r) * 1.0 / cu);
3168 }
3169
3170 if (f_buf_now == rate_max) {
3171 // rate_next = rate_max;
3172 new_index = gf_list_count(group->adaptation_set->representations) - 1;
3173 }
3174 else if (f_buf_now == rate_min) {
3175 // rate_next = rate_min;
3176 new_index = 0;
3177 }
3178 else if (f_buf_now >= rate_plus) {
3179 // rate_next = max of Ri st. Ri < f_buf_now
3180 new_index = 0;
3181 get_max_rate_below(group->adaptation_set->representations, f_buf_now, &new_index);
3182 }
3183 else if (f_buf_now <= rate_minus) {
3184 // rate_next = min of Ri st. Ri > f_buf_now
3185 new_index = gf_list_count(group->adaptation_set->representations) - 1;
3186 get_min_rate_above(group->adaptation_set->representations, f_buf_now, &new_index);
3187 }
3188 else {
3189 // no change
3190 new_index = group->active_rep_index;
3191 }
3192
3193 if (new_index != -1) {
3194 #ifndef GPAC_DISABLE_LOG
3195 GF_MPD_Representation *result = gf_list_get(group->adaptation_set->representations, (u32)new_index);
3196 #endif
3197 // increment the segment number for debug purposes
3198 group->current_index++;
3199 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));
3200 }
3201 return new_index;
3202 }
3203
3204 /* returns the index of the representation which maximises BOLA utility function
3205 based on the relative log-based utility of each representation compared to the one with the lowest bitrate
3206 NOTE: V can represent V (in BOLA BASIC) or V_D (in other modes of BOLA) */
bola_find_max_utility_index(GF_List * representations,Double V,Double gamma,Double p,Double Q)3207 static s32 bola_find_max_utility_index(GF_List *representations, Double V, Double gamma, Double p, Double Q) {
3208 u32 k;
3209 Double max_utility = GF_MIN_DOUBLE;
3210 u32 nb_reps = gf_list_count(representations);
3211 s32 new_index = -1;
3212
3213 for (k = 0; k < nb_reps; k++) {
3214 GF_MPD_Representation *rep = gf_list_get(representations, k);
3215 Double utility = (V * rep->playback.bola_v + V*gamma*p - Q) / (rep->bandwidth*p);
3216 if (utility >= max_utility) {
3217 max_utility = utility;
3218 new_index = k;
3219 }
3220 }
3221 return new_index;
3222 }
3223
3224 /**
3225 Adaptation Algorithm as described in
3226 K. Spiteri et al. 2016. BOLA: Near-Optimal Bitrate Adaptation for Online Videos
3227 Arxiv.org
3228 */
dash_do_rate_adaptation_bola(GF_DashClient * dash,GF_DASH_Group * group,GF_DASH_Group * base_group,u32 dl_rate,Double speed,Double max_available_speed,Bool force_lower_complexity,GF_MPD_Representation * rep,Bool go_up_bitrate)3229 static s32 dash_do_rate_adaptation_bola(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
3230 u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
3231 GF_MPD_Representation *rep, Bool go_up_bitrate)
3232 {
3233 s32 new_index = -1;
3234 u32 k;
3235 Double p = group->current_downloaded_segment_duration / 1000.0; // segment duration
3236 Double gamma = (double)5/(double)p;
3237 Double Qmax = group->buffer_max_ms / 1000.0 / p; // max nb of segments in the buffer
3238 Double Q = group->buffer_occupancy_ms / 1000.0 / p; // current buffer occupancy in number of segments
3239
3240 GF_MPD_Representation *min_rep;
3241 GF_MPD_Representation *max_rep;
3242 u32 nb_reps;
3243
3244 if (dash->mpd->type != GF_MPD_TYPE_STATIC) {
3245 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] BOLA: Cannot be used for live MPD\n"));
3246 return -1;
3247 }
3248
3249 nb_reps = gf_list_count(group->adaptation_set->representations);
3250 min_rep = gf_list_get(group->adaptation_set->representations, 0);
3251 max_rep = gf_list_get(group->adaptation_set->representations, nb_reps - 1);
3252
3253 // Computing the log-based utility of each segment (recomputing each time for period changes)
3254 for (k = 0; k < nb_reps; k++) {
3255 GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
3256 a_rep->playback.bola_v = log(((Double)a_rep->bandwidth) / min_rep->bandwidth);
3257 }
3258
3259 if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_BASIC) {
3260 /* BOLA Basic is the variant of BOLA that assumes infinite duration streams (no wind-up/down, no rate use, no oscillation control)
3261 It simply consists in finding the maximum utility */
3262 // NOTE in BOLA, representation indices decrease when the quality increases [1 = best quality]
3263 Double V = (Qmax - 1) / (gamma * p + max_rep->playback.bola_v);
3264 new_index = bola_find_max_utility_index(group->adaptation_set->representations, V, gamma, p, Q);
3265 }
3266 else if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_FINITE ||
3267 dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_O ||
3268 dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U) {
3269 /* BOLA FINITE is the same as BOLA Basic with the wind-up and down phases */
3270 /* BOLA O and U add extra steps to BOLA FINITE */
3271 Double t_bgn; //play time from begin
3272 Double t_end; //play time to the end
3273 Double t;
3274 Double t_prime;
3275 Double Q_Dmax;
3276 Double V_D;
3277 Double N = dash->mpd->media_presentation_duration / p;
3278
3279 t_bgn = p*group->current_index;
3280 t_end = (N - group->current_index)*p;
3281 t = MIN(t_bgn, t_end);
3282 t_prime = MAX(t / 2, 3 * p);
3283 Q_Dmax = MIN(Qmax, t_prime / p);
3284 V_D = (Q_Dmax - 1) / (gamma * p + max_rep->playback.bola_v);
3285
3286 new_index = bola_find_max_utility_index(group->adaptation_set->representations, V_D, gamma, p, Q);
3287
3288 if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U || dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_O) {
3289 //Bola U algorithm
3290 if ((new_index != -1) && ((u32)new_index > group->active_rep_index)) {
3291 u32 r = group->bytes_per_sec*8;
3292
3293 // index_prime the min m such that (Sm[m]/p)<= max(bandwidth_previous,S_M/p))
3294 // NOTE in BOLA, representation indices decrease when the quality increases [1 = best quality]
3295 u32 m_prime = 0;
3296 get_max_rate_below(group->adaptation_set->representations, MAX(r, min_rep->bandwidth), &m_prime);
3297 if (m_prime >= (u32)new_index) {
3298 m_prime = new_index;
3299 }
3300 else if (m_prime < group->active_rep_index) {
3301 m_prime = group->active_rep_index;
3302 }
3303 else {
3304 if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U) {
3305 m_prime++;
3306 }
3307 else { //GF_DASH_ALGO_BOLA_O
3308 #if 0
3309 GF_MPD_Representation *rep_m_prime, *rep_m_prime_plus_one;
3310 Double Sm_prime, Sm_prime_plus_one, f_m_prime, f_m_prime_1, bola_o_pause;
3311
3312 assert(m_prime >= 0 && m_prime < nb_reps - 2);
3313 rep_m_prime = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, m_prime);
3314 rep_m_prime_plus_one = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, m_prime+1);
3315 Sm_prime = rep_m_prime->bandwidth*p;
3316 Sm_prime_plus_one = rep_m_prime_plus_one->bandwidth*p;
3317 f_m_prime = V_D*(rep_m_prime->playback.bola_v + gamma*p) / Sm_prime;
3318 f_m_prime_1 = V_D*(rep_m_prime_plus_one->playback.bola_v + gamma*p) / Sm_prime_plus_one;
3319 // TODO wait for bola_o_pause before making the download
3320 bola_o_pause = Q - (f_m_prime - f_m_prime_1) / (1 / Sm_prime - 1 / Sm_prime_plus_one);
3321 #endif
3322 }
3323 }
3324 new_index = m_prime;
3325 }
3326 }
3327 // TODO trigger pause for max(p*(Q-Q_Dmax+1), 0)
3328 }
3329
3330 if (new_index != -1) {
3331 #ifndef GPAC_DISABLE_LOG
3332 GF_MPD_Representation *result = gf_list_get(group->adaptation_set->representations, (u32)new_index);
3333 #endif
3334 // increment the segment number for debug purposes
3335 group->current_index++;
3336 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));
3337 }
3338 return new_index;
3339 }
3340
3341 /* This function is called each time a new segment has been downloaded */
dash_do_rate_adaptation(GF_DashClient * dash,GF_DASH_Group * group)3342 static void dash_do_rate_adaptation(GF_DashClient *dash, GF_DASH_Group *group)
3343 {
3344 Double speed;
3345 Double max_available_speed;
3346 u32 dl_rate;
3347 u32 k;
3348 s32 new_index, old_index;
3349 GF_DASH_Group *base_group;
3350 GF_MPD_Representation *rep;
3351 Bool force_lower_complexity;
3352
3353 /* Don't do adaptation if configured switching to happen systematically (debug) */
3354 if (dash->auto_switch_count) {
3355 return;
3356 }
3357 /* Don't do adaptation if GPAC config disabled switching */
3358 if (group->dash->disable_switching) {
3359 return;
3360 }
3361
3362 /* The bytes_per_sec field is set each time a segment is downloaded,
3363 (this may need to be adjusted in the future to accomodate algorithms
3364 that smooth download rate over several segments)
3365 if set to 0, this means that no segment was downloaded since the last call
3366 because this AdaptationSet is not selected
3367 So no rate adaptation should be done*/
3368 if (!group->bytes_per_sec) {
3369 return;
3370 }
3371
3372 /* Find the AdaptationSet on which this AdaptationSet depends, if any
3373 (e.g. used for specific coding schemes: scalable streams, tiled streams, ...)*/
3374 base_group = group;
3375 while (base_group->depend_on_group) {
3376 base_group = base_group->depend_on_group;
3377 }
3378
3379 /* adjust the download rate according to the playback speed
3380 All adaptation algorithms should use this value */
3381 speed = dash->speed;
3382 if (speed<0) speed = -speed;
3383 dl_rate = (u32) (8*group->bytes_per_sec / speed);
3384
3385 /* Get the active representation in the AdaptationSet */
3386 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
3387
3388 /*check whether we can play with this speed (i.e. achieve target frame rate);
3389 if not force, let algorithm know that they should switch to a lower resolution*/
3390 max_available_speed = gf_dash_get_max_available_speed(dash, base_group, rep);
3391 if (!dash->disable_speed_adaptation && !rep->playback.waiting_codec_reset) {
3392 if (max_available_speed && (speed > max_available_speed)) {
3393 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Forcing a lower complexity to achieve desired playback speed\n"));
3394 force_lower_complexity = GF_TRUE;
3395 }
3396 else {
3397 force_lower_complexity = GF_FALSE;
3398 }
3399 }
3400 else {
3401 force_lower_complexity = GF_FALSE;
3402 }
3403
3404 /*query codec and buffer statistics for buffer-based algorithms */
3405 group->buffer_max_ms = 0;
3406 group->buffer_occupancy_ms = 0;
3407 group->codec_reset = 0;
3408 /* the DASH Client asks the player for its buffer level
3409 (uses a function pointer to avoid depenencies on the player code, to reuse the DASH client in different situations)*/
3410 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, gf_list_find(group->dash->groups, group), GF_OK);
3411
3412 //adjust buffer with current segments not yet consumed by player
3413 for (k=0; k<group->nb_cached_segments; k++) {
3414 group->buffer_occupancy_ms += group->cached[k].duration;
3415 }
3416
3417
3418 /* If the playback for the current representation was waiting for a codec reset and it happened,
3419 indicate that this representation does not need a reset anymore */
3420 if (rep->playback.waiting_codec_reset && group->codec_reset) {
3421 rep->playback.waiting_codec_reset = GF_FALSE;
3422 }
3423
3424 old_index = group->active_rep_index;
3425 //scalable case, force the rate algo to consider the active rep is the max rep
3426 if (group->base_rep_index_plus_one) {
3427 group->active_rep_index = group->max_complementary_rep_index;
3428 }
3429 if (group->dash->atsc_clock_state) {
3430 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
3431 if (rep->playback.broadcast_flag && (dl_rate < rep->bandwidth)) {
3432 dl_rate = rep->bandwidth+1;
3433 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));
3434 }
3435 }
3436
3437 /* Call a specific adaptation algorithm (see GPAC configuration)
3438 Each algorithm should:
3439 - return the new_index value to the desired quality
3440
3441 It can use:
3442 - the information about each available representation (group->adaptation_set->representations, e.g. bandwidth required for that representation)
3443 - the information of the current representation (rep)
3444 - the download_rate dl_rate (computed on the previously downloaded segment, and adjusted to the playback speed),
3445 - the buffer levels:
3446 - current: group->buffer_occupancy_ms,
3447 - previous: group->buffer_occupancy_at_last_seg
3448 - max: group->buffer_max_ms,
3449 - the playback speed,
3450 - the maximum achievable speed at the current resolution,
3451 - the indicator that the current representation is too demanding CPU-wise (force_lower_complexity)
3452
3453 Private algorithm information should be stored in the dash object if global to all AdaptationSets,
3454 or in the group if local to an AdaptationSet.
3455
3456 TODO: document how to access other possible parameters (e.g. segment sizes if available, ...)
3457 */
3458 new_index = group->active_rep_index;
3459 if (dash->rate_adaptation_algo) {
3460 new_index = dash->rate_adaptation_algo(dash, group, base_group,
3461 dl_rate, speed, max_available_speed, force_lower_complexity,
3462 rep, GF_FALSE);
3463 }
3464
3465 if (new_index==-1) {
3466 group->active_rep_index = old_index;
3467 group->rate_adaptation_postponed = GF_TRUE;
3468 return;
3469 }
3470 group->rate_adaptation_postponed = GF_FALSE;
3471
3472 if (new_index != group->active_rep_index) {
3473 GF_MPD_Representation *new_rep = gf_list_get(group->adaptation_set->representations, (u32)new_index);
3474 if (!new_rep) {
3475 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error: Cannot find new representation index %d\n", new_index));
3476 return;
3477 }
3478
3479 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),
3480 group->nb_segments_since_switch, group->active_rep_index, new_index));
3481 group->nb_segments_since_switch = 0;
3482
3483 if (force_lower_complexity) {
3484 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Requesting codec reset\n"));
3485 new_rep->playback.waiting_codec_reset = GF_TRUE;
3486 }
3487 /* request downloads for the new representation */
3488 gf_dash_set_group_representation(group, new_rep);
3489
3490 /* Reset smoothing of switches
3491 (note: should really apply only to algorithms using the switch_probe_count (smoothing the aggressiveness of the change)
3492 for now: only GF_DASH_ALGO_GPAC_LEGACY_RATE and GF_DASH_ALGO_GPAC_LEGACY_BUFFER */
3493 for (k = 0; k < gf_list_count(group->adaptation_set->representations); k++) {
3494 GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
3495 if (new_rep == arep) continue;
3496 arep->playback.probe_switch_count = 0;
3497 }
3498
3499 } else {
3500 group->active_rep_index = old_index;
3501 if (force_lower_complexity) {
3502 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Speed %f is too fast to play - speed down\n", dash->speed));
3503 /*FIXME: should do something here*/
3504 }
3505 }
3506
3507 /* Remembering the buffer level for the processing of the next segment */
3508 group->buffer_occupancy_at_last_seg = group->buffer_occupancy_ms;
3509 }
3510
gf_dash_get_fileio_url(const char * base_url,char * res_url)3511 static char *gf_dash_get_fileio_url(const char *base_url, char *res_url)
3512 {
3513 const char *new_res;
3514 GF_FileIO *gfio;
3515 if (!base_url)
3516 return NULL;
3517 if (strncmp(base_url, "gfio://", 7))
3518 return res_url;
3519
3520 gfio = gf_fileio_from_url(base_url);
3521
3522 new_res = gf_fileio_factory(gfio, res_url);
3523 if (!new_res) return res_url;
3524 gf_free(res_url);
3525 return gf_strdup(new_res);
3526 }
3527
gf_dash_download_init_segment(GF_DashClient * dash,GF_DASH_Group * group)3528 static GF_Err gf_dash_download_init_segment(GF_DashClient *dash, GF_DASH_Group *group)
3529 {
3530 GF_Err e;
3531 char *base_init_url;
3532 char *init_segment_local_url=NULL;
3533 GF_MPD_Representation *rep;
3534 u64 start_range, end_range;
3535 char mime[128];
3536 const char *mime_type;
3537 Bool data_url_processed = GF_FALSE;
3538 /* This variable is 0 if there is a initURL, the index of first segment downloaded otherwise */
3539 u32 nb_segment_read = 0;
3540 u32 file_size=0, Bps= 0;
3541 char *base_url=NULL;
3542 char *base_url_orig=NULL;
3543 char *key_url=NULL;
3544 bin128 key_iv;
3545
3546 if (!dash || !group)
3547 return GF_BAD_PARAM;
3548
3549 assert(group->adaptation_set && group->adaptation_set->representations);
3550 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
3551 if (!rep) {
3552 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to find any representation, aborting.\n"));
3553 return GF_IO_ERR;
3554 }
3555 start_range = end_range = 0;
3556 base_url = dash->base_url;
3557 if (group->period->origin_base_url) base_url = group->period->origin_base_url;
3558
3559 base_url_orig = base_url;
3560 if (base_url && !strncmp(base_url, "gfio://", 7)) {
3561 GF_FileIO *gfio = gf_fileio_from_url(base_url);
3562 base_url = (char *) gf_file_basename(gf_fileio_resource_url(gfio));
3563 }
3564
3565 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);
3566 if (e) {
3567 if (e != GF_IP_NETWORK_EMPTY) {
3568 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve initialization URL: %s\n", gf_error_to_string(e) ));
3569 }
3570 return e;
3571 }
3572
3573 if (!base_init_url && rep->dependency_id) {
3574 return GF_OK;
3575 }
3576
3577 /*no error and no init segment, go for media segment - this is needed for TS so that the set of media streams can be
3578 declared to the player */
3579 if (!base_init_url) {
3580 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);
3581 if (e) {
3582 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve media URL: %s\n", gf_error_to_string(e) ));
3583 return e;
3584 }
3585 nb_segment_read = 1;
3586 } else if (!group->bitstream_switching) {
3587 group->dont_delete_first_segment = 1;
3588 }
3589
3590 base_url = base_url_orig;
3591 base_init_url = gf_dash_get_fileio_url(base_url, base_init_url);
3592
3593 if (!strstr(base_init_url, "://") || !strnicmp(base_init_url, "file://", 7) || !strnicmp(base_init_url, "gmem://", 7)
3594 || !strnicmp(base_init_url, "views://", 8) || !strnicmp(base_init_url, "mosaic://", 9)
3595 || !strnicmp(base_init_url, "isobmff://", 10) || !strnicmp(base_init_url, "gfio://", 7)
3596 ) {
3597 //if file-based, check if file exists, if not switch base URL
3598 if ( strnicmp(base_init_url, "gmem://", 7) && strnicmp(base_init_url, "gfio://", 7)) {
3599 FILE *ftest = gf_fopen(base_init_url, "rb");
3600 if (!ftest) {
3601 if (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) ){
3602 group->current_base_url_idx++;
3603 gf_free(base_init_url);
3604 return gf_dash_download_init_segment(dash, group);
3605 }
3606 } else {
3607 file_size = (u32) gf_fsize(ftest);
3608 gf_fclose(ftest);
3609 }
3610 }
3611 //we don't reset the baseURL index until we are done fetching all init segments
3612
3613 assert(!group->nb_cached_segments);
3614 group->cached[0].cache = gf_strdup(base_init_url);
3615 group->cached[0].url = gf_strdup(base_init_url);
3616 group->cached[0].representation_index = group->active_rep_index;
3617 group->prev_active_rep_index = group->active_rep_index;
3618 if (key_url) {
3619 group->cached[0].key_url = key_url;
3620 memcpy(group->cached[0].key_IV, key_iv, sizeof(bin128));
3621 }
3622
3623 group->nb_cached_segments = 1;
3624 /*do not erase local files*/
3625 group->local_files = group->was_segment_base ? 0 : 1;
3626
3627 group->download_segment_index += nb_segment_read;
3628 init_segment_local_url = group->cached[0].cache;
3629 if (group->bitstream_switching) {
3630 group->bs_switching_init_segment_url = gf_strdup(init_segment_local_url);
3631 group->bs_switching_init_segment_url_start_range = start_range;
3632 group->bs_switching_init_segment_url_end_range = end_range;
3633 if (data_url_processed) {
3634 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("URL with data scheme not handled for Bistream Switching Segments, probable memory leak"));
3635 }
3636 } else {
3637 rep->playback.cached_init_segment_url = gf_strdup(init_segment_local_url);
3638 rep->playback.owned_gmem = data_url_processed;
3639 rep->playback.init_start_range = start_range;
3640 rep->playback.init_end_range = end_range;
3641 }
3642
3643
3644 /*finally download all init segments if any*/
3645 if (!group->bitstream_switching) {
3646 u32 k;
3647 for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
3648 char *a_base_init_url = NULL;
3649 u64 a_start = 0, a_end = 0, a_dur = 0;
3650 GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
3651 if (a_rep==rep) continue;
3652 if (a_rep->playback.disabled) continue;
3653
3654 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);
3655 if (!e && a_base_init_url) {
3656 a_rep->playback.cached_init_segment_url = a_base_init_url;
3657 a_rep->playback.init_start_range = a_start;
3658 a_rep->playback.init_end_range =a_end ;
3659 } else if (e) {
3660 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - discarding representation\n", gf_error_to_string(e) ));
3661 a_rep->playback.disabled = 1;
3662 }
3663 }
3664 }
3665 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] First segment is %s \n", init_segment_local_url));
3666 gf_free(base_init_url);
3667 group->current_base_url_idx=0;
3668 dash_store_stats(dash, group, 0, file_size, GF_FALSE);
3669 return GF_OK;
3670 }
3671
3672 group->max_bitrate = 0;
3673 group->min_bitrate = (u32)-1;
3674
3675
3676 if (!dash->thread_mode) {
3677
3678 if (dash->atsc_clock_state && !group->period->origin_base_url) {
3679 GF_DASHFileIOSession sess = NULL;
3680 /*check the init segment has been received*/
3681 e = gf_dash_download_resource(dash, &sess, base_init_url, start_range, end_range, 1, NULL);
3682 dash->dash_io->del(dash->dash_io, sess);
3683
3684 if (e==GF_OK) {
3685
3686 } else {
3687 return e;
3688 }
3689 }
3690
3691 assert(!group->nb_cached_segments);
3692 group->cached[0].url = base_init_url;
3693 group->cached[0].cache = gf_strdup(base_init_url);
3694 group->cached[0].representation_index = group->active_rep_index;
3695 group->cached[0].duration = (u32) group->current_downloaded_segment_duration;
3696
3697 if (group->bitstream_switching) {
3698 group->bs_switching_init_segment_url = gf_strdup(base_init_url);
3699 group->bs_switching_init_segment_url_start_range = start_range;
3700 group->bs_switching_init_segment_url_end_range = end_range;
3701 if (data_url_processed) {
3702 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("URL with data scheme not handled for Bistream Switching Segments, probable memory leak"));
3703 }
3704 } else {
3705 rep->playback.cached_init_segment_url = gf_strdup(base_init_url);
3706 rep->playback.init_start_range = start_range;
3707 rep->playback.init_end_range = end_range;
3708 rep->playback.owned_gmem = data_url_processed;
3709 }
3710 group->nb_cached_segments = 1;
3711 group->download_segment_index += nb_segment_read;
3712
3713 /*download all init segments if any*/
3714 if (!group->bitstream_switching) {
3715 u32 k;
3716 for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
3717 char *a_base_init_url = NULL;
3718 u64 a_start, a_end, a_dur;
3719 GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
3720 if (a_rep==rep) continue;
3721 if (a_rep->playback.disabled) continue;
3722
3723 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);
3724 if (!e && a_base_init_url) {
3725
3726 a_rep->playback.cached_init_segment_url = a_base_init_url;
3727 a_rep->playback.init_start_range = a_start;
3728 a_rep->playback.init_end_range = a_end;
3729 } else if (e) {
3730 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - disabling representation\n", gf_error_to_string(e) ));
3731 a_rep->playback.disabled = 1;
3732 }
3733 }
3734 }
3735 return GF_OK;
3736 }
3737
3738 /*use persistent connection for segment downloads*/
3739 e = gf_dash_download_resource(dash, &(group->segment_download), base_init_url, start_range, end_range, 1, group);
3740
3741 if ((e==GF_OK) && group->force_switch_bandwidth && !dash->auto_switch_count) {
3742 gf_free(base_init_url);
3743 if (key_url) gf_free(key_url);
3744 gf_dash_switch_group_representation(dash, group);
3745 return gf_dash_download_init_segment(dash, group);
3746 }
3747
3748 if ((e==GF_URL_ERROR) && base_init_url) {
3749 if (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) ){
3750 group->current_base_url_idx++;
3751 gf_free(base_init_url);
3752 if (key_url) gf_free(key_url);
3753 return gf_dash_download_init_segment(dash, group);
3754 }
3755 }
3756
3757
3758 if ((e==GF_URL_ERROR) && base_init_url && !group->download_abort_type) { /* We have a 404 and started with segments */
3759 /* It is possible that the first segment has been deleted while we made the first request...
3760 * so we try with the next segment on some M3U8 servers */
3761 gf_free(base_init_url);
3762 if (key_url) gf_free(key_url);
3763 e = gf_dash_resolve_url(dash->mpd, rep, group, dash->base_url, GF_MPD_RESOLVE_URL_MEDIA, group->download_segment_index + 1, &base_init_url, &start_range, &end_range, &group->current_downloaded_segment_duration, NULL, &key_url, &key_iv, NULL);
3764 if (e != GF_OK) {
3765 return e;
3766 }
3767 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Download of first segment failed... retrying with second one : %s\n", base_init_url));
3768 nb_segment_read = 2;
3769 /*use persistent connection for segment downloads*/
3770 e = gf_dash_download_resource(dash, &(group->segment_download), base_init_url, 0, 0, 1, group);
3771 } /* end of 404 */
3772
3773
3774 if ((e==GF_IP_CONNECTION_CLOSED) && group->download_abort_type) {
3775 group->download_abort_type = 0;
3776 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Aborted while downloading init segment (seek ?)%s \n", base_init_url));
3777 gf_free(base_init_url);
3778 if (key_url) gf_free(key_url);
3779 return GF_OK;
3780 }
3781
3782 if (e!= GF_OK && !group->segment_must_be_streamed) {
3783 gf_free(base_init_url);
3784 if (key_url) gf_free(key_url);
3785 if (!group->dash->atsc_clock_state || (group->dash->atsc_clock_state==3)) {
3786 dash->mpd_stop_request = 1;
3787 }
3788 return e;
3789 }
3790
3791
3792 if (!group->nb_segments_in_rep) {
3793 if (dash->mpd->type==GF_MPD_TYPE_STATIC) {
3794 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] 0 segments in static representation (MPD duration "LLU", will probably have 404\n", group->dash->mpd->media_presentation_duration));
3795 }
3796 } else if (!group->groups_depending_on && (group->nb_segments_in_rep < group->max_cached_segments)) {
3797 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Resizing to %u max_cached_segments elements instead of %u.\n", group->nb_segments_in_rep, group->max_cached_segments));
3798 /* OK, we have a problem, it may ends download */
3799 group->max_cached_segments = group->nb_segments_in_rep;
3800 }
3801
3802 /* Mime-Type check */
3803 mime_type = dash->dash_io->get_mime(dash->dash_io, group->segment_download) ;
3804 strcpy(mime, mime_type ? mime_type : "");
3805 strlwr(mime);
3806
3807 if (dash->mimeTypeForM3U8Segments)
3808 gf_free(dash->mimeTypeForM3U8Segments);
3809 dash->mimeTypeForM3U8Segments = gf_strdup( mime );
3810 mime_type = gf_dash_get_mime_type(NULL, rep, group->adaptation_set);
3811 if (!rep->mime_type) {
3812 rep->mime_type = gf_strdup( mime_type ? mime_type : mime );
3813
3814 //disable mime type check
3815 #if 0
3816 mime_type = gf_dash_get_mime_type(NULL, rep, group->adaptation_set);
3817 }
3818 if (stricmp(mime, mime_type)) {
3819 Bool valid = GF_FALSE;
3820 char *stype1, *stype2;
3821 stype1 = strchr(mime_type, '/');
3822 stype2 = strchr(mime, '/');
3823 if (stype1 && stype2 && !strcmp(stype1, stype2)) valid = 1;
3824
3825 if (!valid) {
3826 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Mime '%s' is not correct for '%s', it should be '%s'\n", mime, base_init_url, mime_type));
3827 dash->mpd_stop_request = 0;
3828 gf_free(base_init_url);
3829 if (key_url) gf_free(key_url);
3830 return GF_BAD_PARAM;
3831 }
3832 }
3833 if (!rep->mime_type) {
3834 rep->mime_type = gf_strdup( mime_type ? mime_type : mime );
3835 #endif
3836 }
3837
3838 if (group->segment_must_be_streamed ) {
3839 init_segment_local_url = (char *) dash->dash_io->get_url(dash->dash_io, group->segment_download);
3840 e = GF_OK;
3841 } else {
3842 init_segment_local_url = (char *) dash->dash_io->get_cache_name(dash->dash_io, group->segment_download);
3843 }
3844
3845 if ((e!=GF_OK) || !init_segment_local_url) {
3846 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error with initialization segment: download result:%s, cache file: %s\n", gf_error_to_string(e), init_segment_local_url ? init_segment_local_url : "UNKNOWN"));
3847 dash->mpd_stop_request = 1;
3848 gf_free(base_init_url);
3849 if (key_url) gf_free(key_url);
3850 return GF_BAD_PARAM;
3851 }
3852
3853 file_size = dash->dash_io->get_total_size(dash->dash_io, group->segment_download) ;
3854 Bps = dash->dash_io->get_bytes_per_sec(dash->dash_io, group->segment_download) ;
3855
3856 assert(!group->nb_cached_segments);
3857 group->cached[0].cache = gf_strdup(init_segment_local_url);
3858 group->cached[0].url = gf_strdup( dash->dash_io->get_url(dash->dash_io, group->segment_download) );
3859 group->cached[0].representation_index = group->active_rep_index;
3860 group->cached[0].duration = (u32) group->current_downloaded_segment_duration;
3861
3862 if (group->bitstream_switching) {
3863 group->bs_switching_init_segment_url = gf_strdup(init_segment_local_url);
3864 group->bs_switching_init_segment_url_start_range = 0;
3865 group->bs_switching_init_segment_url_end_range = 0;
3866 if (data_url_processed) {
3867 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("URL with data scheme not handled for Bistream Switching Segments, probable memory leak"));
3868 }
3869 } else {
3870 rep->playback.cached_init_segment_url = gf_strdup(init_segment_local_url);
3871 rep->playback.init_start_range = 0;
3872 rep->playback.init_end_range = 0;
3873 rep->playback.owned_gmem = data_url_processed;
3874 }
3875
3876 group->nb_cached_segments = 1;
3877 group->download_segment_index += nb_segment_read;
3878
3879 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Adding initialization segment %s to cache: %s\n", init_segment_local_url, group->cached[0].url ));
3880
3881 gf_free(base_init_url);
3882
3883 /*download all init segments if any*/
3884 if (!group->bitstream_switching) {
3885 u32 k;
3886 for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
3887 char *a_base_init_url = NULL;
3888 u64 a_start, a_end, a_dur;
3889 GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
3890 if (a_rep==rep) continue;
3891 if (a_rep->playback.disabled) continue;
3892
3893 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);
3894 if (!e && a_base_init_url) {
3895 e = gf_dash_download_resource(dash, &(group->segment_download), a_base_init_url, a_start, a_end, 1, group);
3896
3897 if ((e==GF_IP_CONNECTION_CLOSED) && group->download_abort_type) {
3898 group->download_abort_type = 0;
3899 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Aborted while downloading init segment (seek ?)%s \n", a_base_init_url));
3900
3901 gf_free(a_base_init_url);
3902 return GF_OK;
3903 }
3904
3905 if (e) {
3906 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot retrieve initialization segment %s for representation: %s - discarding representation\n", a_base_init_url, gf_error_to_string(e) ));
3907 a_rep->playback.disabled = 1;
3908 } else {
3909 a_rep->playback.cached_init_segment_url = gf_strdup( dash->dash_io->get_cache_name(dash->dash_io, group->segment_download) );
3910 a_rep->playback.init_start_range = 0;
3911 a_rep->playback.init_end_range = 0;
3912 }
3913 gf_free(a_base_init_url);
3914 } else if (e) {
3915 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - discarding representation\n", gf_error_to_string(e) ));
3916 a_rep->playback.disabled = 1;
3917 }
3918
3919 }
3920 }
3921 //reset baseURL idx to use first base URL
3922 group->current_base_url_idx = 0;
3923 /*if this was not an init segment, perform rate adaptation*/
3924 if (nb_segment_read) {
3925 dash_store_stats(dash, group, Bps, file_size, GF_FALSE);
3926 dash_do_rate_adaptation(dash, group);
3927 }
3928
3929 if (dash->atsc_clock_state) {
3930 u32 i, j;
3931 for (i=0; i<gf_list_count(group->adaptation_set->representations); i++) {
3932 GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, i);
3933 for (j=0; j<gf_list_count(a_rep->base_URLs); j++) {
3934 GF_MPD_BaseURL *b_url = gf_list_get(a_rep->base_URLs, j);
3935 char *nURL = gf_url_concatenate(dash->base_url, b_url->URL);
3936 if (nURL) {
3937 u32 len = (u32) strlen(nURL);
3938 if (nURL[len] != '/') {
3939 e = gf_dash_download_resource(dash, &(group->segment_download), nURL, 0, 0, 1, group);
3940 if (!e) {
3941 const char *redirected_url = dash->dash_io->get_url(dash->dash_io, group->segment_download);
3942 if (redirected_url && strcmp(redirected_url, nURL)) {
3943 b_url->redirection = gf_strdup(redirected_url);
3944 }
3945 }
3946 }
3947 gf_free(nURL);
3948 }
3949 }
3950 }
3951 }
3952
3953 return GF_OK;
3954 }
3955
gf_dash_skip_disabled_representation(GF_DASH_Group * group,GF_MPD_Representation * rep,Bool for_autoswitch)3956 static void gf_dash_skip_disabled_representation(GF_DASH_Group *group, GF_MPD_Representation *rep, Bool for_autoswitch)
3957 {
3958 s32 rep_idx, orig_idx;
3959 u32 bandwidth = 0xFFFFFFFF;
3960 if (for_autoswitch && group->segment_download) {
3961 bandwidth = 8*group->dash->dash_io->get_bytes_per_sec(group->dash->dash_io, group->segment_download);
3962 }
3963
3964 rep_idx = orig_idx = gf_list_find(group->adaptation_set->representations, rep);
3965 while (1) {
3966 rep_idx++;
3967 if (rep_idx==gf_list_count(group->adaptation_set->representations)) rep_idx = 0;
3968 //none other than current one
3969 if (orig_idx==rep_idx) return;
3970
3971 rep = gf_list_get(group->adaptation_set->representations, rep_idx);
3972 if (rep->playback.disabled) continue;
3973
3974 if (rep->bandwidth<=bandwidth) break;
3975 assert(for_autoswitch);
3976 //go to next rep
3977 }
3978 assert(rep && !rep->playback.disabled);
3979 gf_dash_set_group_representation(group, rep);
3980 }
3981
3982
gf_dash_group_reset_cache_entry(segment_cache_entry * cached)3983 static void gf_dash_group_reset_cache_entry(segment_cache_entry *cached)
3984 {
3985 gf_free(cached->cache);
3986 gf_free(cached->url);
3987 if (cached->key_url) gf_free(cached->key_url);
3988 memset(cached, 0, sizeof(segment_cache_entry));
3989 }
3990
gf_dash_group_reset(GF_DashClient * dash,GF_DASH_Group * group)3991 static void gf_dash_group_reset(GF_DashClient *dash, GF_DASH_Group *group)
3992 {
3993 if (group->urlToDeleteNext) {
3994 if (!dash->keep_files && !group->local_files)
3995 if (dash->dash_io) dash->dash_io->delete_cache_file(dash->dash_io, group->segment_download, group->urlToDeleteNext);
3996
3997 gf_free(group->urlToDeleteNext);
3998 group->urlToDeleteNext = NULL;
3999 }
4000 if (group->segment_download) {
4001 if (dash->dash_io) dash->dash_io->del(dash->dash_io, group->segment_download);
4002 group->segment_download = NULL;
4003 }
4004 while (group->nb_cached_segments) {
4005 group->nb_cached_segments--;
4006 if (!dash->keep_files && !group->local_files)
4007 gf_file_delete(group->cached[group->nb_cached_segments].cache);
4008
4009 gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
4010 }
4011
4012 group->timeline_setup = 0;
4013 }
4014
gf_dash_reset_groups(GF_DashClient * dash)4015 static void gf_dash_reset_groups(GF_DashClient *dash)
4016 {
4017 /*send playback destroy event*/
4018 if (dash->dash_io) dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_DESTROY_PLAYBACK, -1, GF_OK);
4019
4020 while (gf_list_count(dash->groups)) {
4021 GF_DASH_Group *group = gf_list_last(dash->groups);
4022 gf_list_rem_last(dash->groups);
4023
4024 gf_dash_group_reset(dash, group);
4025
4026 gf_list_del(group->groups_depending_on);
4027 gf_free(group->cached);
4028 if (group->service_mime)
4029 gf_free(group->service_mime);
4030
4031 if (group->download_th)
4032 gf_th_del(group->download_th);
4033
4034 if (group->cache_mutex)
4035 gf_mx_del(group->cache_mutex);
4036 if (group->bs_switching_init_segment_url)
4037 gf_free(group->bs_switching_init_segment_url);
4038
4039 gf_free(group);
4040 }
4041 gf_list_del(dash->groups);
4042 dash->groups = NULL;
4043
4044 while (gf_list_count(dash->SRDs)) {
4045 struct _dash_srd_desc *srd = gf_list_last(dash->SRDs);
4046 gf_list_rem_last(dash->SRDs);
4047 gf_free(srd);
4048 }
4049 gf_list_del(dash->SRDs);
4050 dash->SRDs = NULL;
4051 }
4052
4053 #ifndef GPAC_DISABLE_LOG
gf_dash_get_start_number(GF_DASH_Group * group,GF_MPD_Representation * rep)4054 static u32 gf_dash_get_start_number(GF_DASH_Group *group, GF_MPD_Representation *rep)
4055 {
4056 if (rep->segment_list && rep->segment_list->start_number) return rep->segment_list->start_number;
4057 if (group->adaptation_set->segment_list && group->adaptation_set->segment_list->start_number) return group->adaptation_set->segment_list->start_number;
4058 if (group->period->segment_list && group->period->segment_list->start_number) return group->period->segment_list->start_number;
4059
4060 if (rep->segment_template && rep->segment_template->start_number) return rep->segment_template->start_number;
4061 if (group->adaptation_set->segment_template && group->adaptation_set->segment_template->start_number) return group->adaptation_set->segment_template->start_number;
4062 if (group->period->segment_template && group->period->segment_template->start_number) return group->period->segment_template->start_number;
4063
4064 return 0;
4065 }
4066 #endif
4067
gf_dash_find_rep(GF_DashClient * dash,const char * dependency_id,GF_DASH_Group ** rep_group)4068 static GF_MPD_Representation *gf_dash_find_rep(GF_DashClient *dash, const char *dependency_id, GF_DASH_Group **rep_group)
4069 {
4070 u32 i, j, nb_groups, nb_reps;
4071 GF_MPD_Representation *rep;
4072
4073 if (rep_group) *rep_group = NULL;
4074
4075 if (!dependency_id) return NULL;
4076
4077 nb_groups = gf_list_count(dash->groups);
4078 for (i=0; i<nb_groups; i++) {
4079 GF_DASH_Group *group = gf_list_get(dash->groups, i);
4080 nb_reps = gf_list_count(group->adaptation_set->representations);
4081 for (j=0; j<nb_reps; j++) {
4082 rep = gf_list_get(group->adaptation_set->representations, j);
4083 if (rep->id && !strcmp(rep->id, dependency_id)) {
4084 if (rep_group) *rep_group = group;
4085 return rep;
4086 }
4087 }
4088 }
4089 return NULL;
4090 }
4091
4092 static
gf_dash_group_get_dependency_group(GF_DashClient * dash,u32 idx)4093 s32 gf_dash_group_get_dependency_group(GF_DashClient *dash, u32 idx)
4094 {
4095 GF_MPD_Representation *rep;
4096 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
4097 if (!group) return idx;
4098
4099 rep = gf_list_get(group->adaptation_set->representations, 0);
4100
4101 while (rep && rep->dependency_id) {
4102 char *sep = strchr(rep->dependency_id, ' ');
4103 if (sep) sep[0] = 0;
4104 rep = gf_dash_find_rep(dash, rep->dependency_id, &group);
4105 if (sep) sep[0] = ' ';
4106 }
4107 return gf_list_find(dash->groups, group);
4108 }
4109
4110 GF_EXPORT
gf_dash_group_has_dependent_group(GF_DashClient * dash,u32 idx)4111 s32 gf_dash_group_has_dependent_group(GF_DashClient *dash, u32 idx)
4112 {
4113 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
4114 if (!group) return GF_FALSE;
4115 return group->depend_on_group ? gf_list_find(dash->groups, group->depend_on_group) : -1;
4116 }
4117
4118 GF_EXPORT
gf_dash_group_get_num_groups_depending_on(GF_DashClient * dash,u32 idx)4119 u32 gf_dash_group_get_num_groups_depending_on(GF_DashClient *dash, u32 idx)
4120 {
4121 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
4122 if (!group) return 0;
4123 return group->groups_depending_on ? gf_list_count(group->groups_depending_on) : 0;
4124 }
4125
4126 GF_EXPORT
gf_dash_get_dependent_group_index(GF_DashClient * dash,u32 idx,u32 group_depending_on_dep_idx)4127 s32 gf_dash_get_dependent_group_index(GF_DashClient *dash, u32 idx, u32 group_depending_on_dep_idx)
4128 {
4129 GF_DASH_Group *group_depending_on;
4130 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
4131 if (!group || !group->groups_depending_on) return -1;
4132 group_depending_on = gf_list_get(group->groups_depending_on, group_depending_on_dep_idx);
4133 if (!group_depending_on) return -1;
4134 return gf_list_find(dash->groups, group_depending_on);
4135 }
4136
4137 /* create groups (implementation of adaptations set) */
gf_dash_setup_groups(GF_DashClient * dash)4138 GF_Err gf_dash_setup_groups(GF_DashClient *dash)
4139 {
4140 GF_Err e;
4141 u32 i, j, count, nb_dependent_rep;
4142 GF_MPD_Period *period;
4143
4144 if (!dash->groups) {
4145 dash->groups = gf_list_new();
4146 if (!dash->groups) return GF_OUT_OF_MEM;
4147 }
4148
4149 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
4150 if (!period) return GF_BAD_PARAM;
4151
4152 count = gf_list_count(period->adaptation_sets);
4153 for (i=0; i<count; i++) {
4154 Double seg_dur;
4155 GF_DASH_Group *group;
4156 Bool found = GF_FALSE;
4157 Bool has_dependent_representations = GF_FALSE;
4158 GF_MPD_AdaptationSet *set = gf_list_get(period->adaptation_sets, i);
4159 for (j=0; j<gf_list_count(dash->groups); j++) {
4160 group = gf_list_get(dash->groups, j);
4161 if (group->adaptation_set==set) {
4162 found = 1;
4163 break;
4164 }
4165 }
4166
4167 if (found) continue;
4168
4169 if (! gf_list_count(set->representations)) {
4170 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Empty adaptation set found (ID %s) - ignoring\n", set->id));
4171 continue;
4172 }
4173
4174
4175 GF_SAFEALLOC(group, GF_DASH_Group);
4176 if (!group) return GF_OUT_OF_MEM;
4177 group->dash = dash;
4178 group->adaptation_set = set;
4179 group->period = period;
4180 if (dash->thread_mode) {
4181 if (dash->thread_mode == GF_DASH_THREAD_ALL)
4182 group->download_th = gf_th_new("DashGroupDownload");
4183
4184 group->cache_mutex = gf_mx_new("DashGroupMutex");
4185 }
4186
4187 group->bitstream_switching = (set->bitstream_switching || period->bitstream_switching) ? GF_TRUE : GF_FALSE;
4188
4189 seg_dur = 0;
4190 nb_dependent_rep = 0;
4191 for (j=0; j<gf_list_count(set->representations); j++) {
4192 Double dur;
4193 u32 nb_seg, k;
4194 Bool cp_supported;
4195 GF_MPD_Representation *rep = gf_list_get(set->representations, j);
4196 gf_dash_get_segment_duration(rep, set, period, dash->mpd, &nb_seg, &dur);
4197 if (dur>seg_dur) seg_dur = dur;
4198
4199 if (group->bitstream_switching && (set->segment_base || period->segment_base || rep->segment_base) ) {
4200 group->bitstream_switching = GF_FALSE;
4201 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] bitstreamSwitching set for onDemand content - ignoring flag\n"));
4202 }
4203
4204 if (dash->dash_io->dash_codec_supported) {
4205 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);
4206 if (!res) {
4207 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation not supported by playback engine - ignoring\n"));
4208 rep->playback.disabled = 1;
4209 continue;
4210 }
4211 }
4212 //filter out everything above HD
4213 if ((dash->max_width>2000) && (dash->max_height>2000)) {
4214 if ((rep->width>dash->max_width) || (rep->height>dash->max_height)) {
4215 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));
4216 rep->playback.disabled = 1;
4217 continue;
4218 }
4219 }
4220 if (rep->codecs && (dash->max_bit_per_pixel > 8) ) {
4221 char *vid_type = strstr(rep->codecs, "hvc");
4222 if (!vid_type) vid_type = strstr(rep->codecs, "hev");
4223 if (!vid_type) vid_type = strstr(rep->codecs, "avc");
4224 if (!vid_type) vid_type = strstr(rep->codecs, "svc");
4225 if (!vid_type) vid_type = strstr(rep->codecs, "mvc");
4226 //HEVC
4227 if (vid_type && (!strnicmp(rep->codecs, "hvc", 3) || !strnicmp(rep->codecs, "hev", 3))) {
4228 char *pidc = rep->codecs+5;
4229 if ((pidc[0]=='A') || (pidc[0]=='B') || (pidc[0]=='C')) pidc++;
4230 //Main 10 !!
4231 if (!strncmp(pidc, "2.", 2)) {
4232 rep->playback.disabled = 1;
4233 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation bit depth higher than max display bit depth - ignoring\n"));
4234 continue;
4235 }
4236 }
4237 //AVC
4238 if (vid_type && (!strnicmp(rep->codecs, "avc", 3) || !strnicmp(rep->codecs, "svc", 3) || !strnicmp(rep->codecs, "mvc", 3))) {
4239 char prof_string[3];
4240 u8 prof;
4241 strncpy(prof_string, vid_type+5, 2);
4242 prof_string[2]=0;
4243 prof = atoi(prof_string);
4244 //Main 10
4245 if (prof==0x6E) {
4246 rep->playback.disabled = 1;
4247 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation bit depth higher than max display bit depth - ignoring\n"));
4248 continue;
4249 }
4250 }
4251 }
4252
4253 for (k=0; k<gf_list_count(rep->essential_properties); k++) {
4254 GF_MPD_Descriptor *mpd_desc = gf_list_get(rep->essential_properties, k);
4255
4256 //we don't know any defined scheme for now
4257 if (! strstr(mpd_desc->scheme_id_uri, "gpac") ) {
4258 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation with unrecognized EssentialProperty %s - ignoring because not supported\n", mpd_desc->scheme_id_uri));
4259 rep->playback.disabled = 1;
4260 break;
4261 }
4262 }
4263 if (rep->playback.disabled) {
4264 continue;
4265 }
4266
4267 cp_supported = 1;
4268 for (k=0; k<gf_list_count(rep->content_protection); k++) {
4269 GF_MPD_Descriptor *mpd_desc = gf_list_get(rep->content_protection, k);
4270 //we don't know any defined scheme for now
4271 if (strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:mp4protection:2011")) {
4272 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Representation with unrecognized ContentProtection %s\n", mpd_desc->scheme_id_uri));
4273 cp_supported = 0;
4274 } else {
4275 cp_supported = 1;
4276 break;
4277 }
4278 }
4279 if (!cp_supported) {
4280 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation with no supported ContentProtection\n"));
4281 rep->playback.disabled = 1;
4282 continue;
4283 }
4284
4285 rep->playback.disabled = 0;
4286 if (rep->width>set->max_width) {
4287 set->max_width = rep->width;
4288 set->max_height = rep->height;
4289 }
4290 if (rep->dependency_id && strlen(rep->dependency_id))
4291 has_dependent_representations = GF_TRUE;
4292 else
4293 group->base_rep_index_plus_one = j+1;
4294 rep->playback.enhancement_rep_index_plus_one = 0;
4295 for (k = 0; k < gf_list_count(set->representations); k++) {
4296 GF_MPD_Representation *a_rep = gf_list_get(set->representations, k);
4297 if (a_rep->dependency_id) {
4298 char * tmp = strrchr(a_rep->dependency_id, ' ');
4299 if (tmp)
4300 tmp = tmp + 1;
4301 else
4302 tmp = a_rep->dependency_id;
4303 if (!strcmp(tmp, rep->id))
4304 rep->playback.enhancement_rep_index_plus_one = k + 1;
4305 }
4306 }
4307 if (!rep->playback.enhancement_rep_index_plus_one)
4308 group->max_complementary_rep_index = j;
4309 if (!rep->playback.disabled && rep->dependency_id)
4310 nb_dependent_rep++;
4311 }
4312
4313 if (!seg_dur && !dash->is_m3u8) {
4314 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Cannot compute default segment duration\n"));
4315 }
4316
4317 group->cache_duration = dash->max_cache_duration;
4318 if (group->cache_duration < dash->mpd->min_buffer_time)
4319 group->cache_duration = dash->mpd->min_buffer_time;
4320
4321 //we want at least 2 segments available in the cache, in order to perform rate adaptation with one cache ahead
4322 group->max_cached_segments = 2;
4323 if (seg_dur) {
4324 while (group->max_cached_segments * seg_dur * 1000 < group->cache_duration)
4325 group->max_cached_segments ++;
4326
4327 group->max_buffer_segments = group->max_cached_segments;
4328
4329 #if 0
4330 /*unless we are in low latency modes*/
4331 if (dash->max_cache_duration>1000) {
4332 /*we need one more entry in cache for segment being currently played*/
4333 if (group->max_cached_segments<3)
4334 group->max_cached_segments ++;
4335 }
4336 #endif
4337 group->max_cached_segments *= (nb_dependent_rep+1);
4338 group->max_buffer_segments *= (nb_dependent_rep+1);
4339 }
4340
4341 if (!dash->thread_mode) {
4342 group->max_cached_segments = (nb_dependent_rep+1);
4343 group->max_buffer_segments = (nb_dependent_rep+1);
4344 }
4345
4346 if (!has_dependent_representations)
4347 group->base_rep_index_plus_one = 0; // all representations in this group are independent
4348
4349 if (group->max_cached_segments>50) {
4350 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));
4351 group->max_cached_segments = 50;
4352 }
4353 group->cached = gf_malloc(sizeof(segment_cache_entry)*group->max_cached_segments);
4354 memset(group->cached, 0, sizeof(segment_cache_entry)*group->max_cached_segments);
4355 if (!group->cached) {
4356 gf_free(group);
4357 return GF_OUT_OF_MEM;
4358 }
4359 e = gf_list_add(dash->groups, group);
4360 if (e) {
4361 gf_free(group->cached);
4362 gf_free(group);
4363 return e;
4364 }
4365 }
4366
4367
4368 count = gf_list_count(dash->groups);
4369 for (i=0; i<count; i++) {
4370 GF_DASH_Group *group = gf_list_get(dash->groups, i);
4371 j = gf_dash_group_get_dependency_group(dash, i);
4372 if (i != j) {
4373 GF_DASH_Group *base_group = gf_list_get(dash->groups, j);
4374 assert(base_group);
4375 group->depend_on_group = base_group;
4376 if (!base_group->groups_depending_on) {
4377 base_group->groups_depending_on = gf_list_new();
4378 }
4379 gf_list_add(base_group->groups_depending_on, group);
4380 }
4381 }
4382
4383 for (i=0; i<count; i++) {
4384 GF_DASH_Group *group = gf_list_get(dash->groups, i);
4385 if (group->groups_depending_on) {
4386 u32 nb_dep_groups = gf_list_count(group->groups_depending_on);
4387 //all dependent groups will be stored in the base group
4388 group->max_cached_segments *= (1+nb_dep_groups);
4389 group->max_buffer_segments *= (1+nb_dep_groups);
4390 group->cached = gf_realloc(group->cached, sizeof(segment_cache_entry)*group->max_cached_segments);
4391 memset(group->cached, 0, sizeof(segment_cache_entry)*group->max_cached_segments);
4392
4393 for (j=0; j<nb_dep_groups; j++) {
4394 GF_DASH_Group *dep_group = gf_list_get(group->groups_depending_on, j);
4395
4396 dep_group->max_cached_segments = 0;
4397
4398 /* the rest of the code assumes that at least group->cached[0] is allocated */
4399 dep_group->cached = gf_realloc(dep_group->cached, sizeof(segment_cache_entry));
4400 memset(dep_group->cached, 0, sizeof(segment_cache_entry));
4401
4402 }
4403 }
4404 }
4405
4406 return GF_OK;
4407 }
4408
gf_dash_load_sidx(GF_BitStream * bs,GF_MPD_Representation * rep,Bool separate_index,u64 sidx_offset)4409 static GF_Err gf_dash_load_sidx(GF_BitStream *bs, GF_MPD_Representation *rep, Bool separate_index, u64 sidx_offset)
4410 {
4411 #ifdef GPAC_DISABLE_ISOM
4412 return GF_NOT_SUPPORTED;
4413 #else
4414 u64 anchor_position, prev_pos;
4415 GF_SegmentIndexBox *sidx = NULL;
4416 u32 i, size, type;
4417 GF_Err e;
4418 u64 offset;
4419
4420 prev_pos = gf_bs_get_position(bs);
4421 gf_bs_seek(bs, sidx_offset);
4422 size = gf_bs_read_u32(bs);
4423 type = gf_bs_read_u32(bs);
4424 if (type != GF_ISOM_BOX_TYPE_SIDX) {
4425 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 ));
4426 return GF_ISOM_INVALID_FILE;
4427 }
4428
4429 gf_bs_seek(bs, sidx_offset);
4430
4431 anchor_position = sidx_offset + size;
4432 if (separate_index)
4433 anchor_position = 0;
4434
4435 e = gf_isom_box_parse((GF_Box **) &sidx, bs);
4436 if (e) return e;
4437
4438 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));
4439
4440 offset = sidx->first_offset + anchor_position;
4441 rep->segment_list->timescale = sidx->timescale;
4442 for (i=0; i<sidx->nb_refs; i++) {
4443 if (sidx->refs[i].reference_type) {
4444 e = gf_dash_load_sidx(bs, rep, separate_index, offset);
4445 if (e) {
4446 break;
4447 }
4448 } else {
4449 GF_MPD_SegmentURL *seg;
4450 GF_SAFEALLOC(seg, GF_MPD_SegmentURL);
4451 if (!seg) return GF_OUT_OF_MEM;
4452 GF_SAFEALLOC(seg->media_range, GF_MPD_ByteRange);
4453 if (!seg->media_range) return GF_OUT_OF_MEM;
4454 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",
4455 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));
4456
4457 seg->media_range->start_range = offset;
4458 offset += sidx->refs[i].reference_size;
4459 seg->media_range->end_range = offset - 1;
4460 seg->duration = sidx->refs[i].subsegment_duration;
4461 gf_list_add(rep->segment_list->segment_URLs, seg);
4462 }
4463 }
4464 gf_isom_box_del((GF_Box*)sidx);
4465 gf_bs_seek(bs, prev_pos);
4466 return e;
4467 #endif
4468 }
4469
gf_dash_load_representation_sidx(GF_DASH_Group * group,GF_MPD_Representation * rep,const char * cache_name,Bool separate_index,Bool needs_mov_range)4470 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)
4471 {
4472 GF_Err e;
4473 GF_BitStream *bs;
4474 FILE *f=NULL;
4475 if (!cache_name) return GF_BAD_PARAM;
4476
4477 if (!strncmp(cache_name, "gmem://", 7)) {
4478 u32 size;
4479 u8 *mem_address;
4480 e = gf_blob_get_data(cache_name, &mem_address, &size);
4481 if (e) return e;
4482
4483 bs = gf_bs_new(mem_address, size, GF_BITSTREAM_READ);
4484 } else {
4485 f = gf_fopen(cache_name, "rb");
4486 if (!f) return GF_IO_ERR;
4487 bs = gf_bs_from_file(f, GF_BITSTREAM_READ);
4488 }
4489 e = GF_OK;
4490 while (gf_bs_available(bs)) {
4491 u32 size = gf_bs_read_u32(bs);
4492 u32 type = gf_bs_read_u32(bs);
4493 if (type != GF_ISOM_BOX_TYPE_SIDX) {
4494 gf_bs_skip_bytes(bs, size-8);
4495
4496 if (needs_mov_range && (type==GF_ISOM_BOX_TYPE_MOOV )) {
4497 GF_SAFEALLOC(rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange);
4498 if (rep->segment_list->initialization_segment->byte_range)
4499 rep->segment_list->initialization_segment->byte_range->end_range = gf_bs_get_position(bs);
4500 }
4501 continue;
4502 }
4503 gf_bs_seek(bs, gf_bs_get_position(bs)-8);
4504 e = gf_dash_load_sidx(bs, rep, separate_index, gf_bs_get_position(bs) );
4505
4506 /*we could also parse the sub sidx*/
4507 break;
4508 }
4509 gf_bs_del(bs);
4510 if (f) gf_fclose(f);
4511 return e;
4512 }
4513
dash_load_box_type(const char * cache_name,u32 offset,u32 * box_type,u32 * box_size)4514 static GF_Err dash_load_box_type(const char *cache_name, u32 offset, u32 *box_type, u32 *box_size)
4515 {
4516 *box_type = *box_size = 0;
4517 if (!strncmp(cache_name, "gmem://", 7)) {
4518 GF_Err e;
4519 u32 size;
4520 u8 *mem_address;
4521 e = gf_blob_get_data(cache_name, &mem_address, &size);
4522 if (e) return e;
4523
4524 if (offset+8 > size)
4525 return GF_IO_ERR;
4526 mem_address += offset;
4527 *box_size = GF_4CC(mem_address[0], mem_address[1], mem_address[2], mem_address[3]);
4528 *box_type = GF_4CC(mem_address[4], mem_address[5], mem_address[6], mem_address[7]);
4529 } else {
4530 unsigned char data[4];
4531 FILE *f = gf_fopen(cache_name, "rb");
4532 if (!f) return GF_IO_ERR;
4533 if (gf_fseek(f, offset, SEEK_SET))
4534 return GF_IO_ERR;
4535 if (gf_fread(data, 4, f) == 4) {
4536 *box_size = GF_4CC(data[0], data[1], data[2], data[3]);
4537 if (gf_fread(data, 4, f) == 4) {
4538 *box_type = GF_4CC(data[0], data[1], data[2], data[3]);
4539 }
4540 }
4541 gf_fclose(f);
4542 }
4543 return GF_OK;
4544 }
4545
4546 extern void gf_mpd_segment_template_free(void *_item);
gf_dash_setup_single_index_mode(GF_DASH_Group * group)4547 static GF_Err gf_dash_setup_single_index_mode(GF_DASH_Group *group)
4548 {
4549 u32 i;
4550 GF_Err e = GF_OK;
4551 char *init_url = NULL;
4552 char *index_url = NULL;
4553 GF_DASHFileIOSession *download_sess = &group->segment_download;
4554 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, 0);
4555
4556 if (!group->dash->thread_mode) download_sess = &group->dash->mpd_dnload;
4557
4558 if (!rep->segment_base && !group->adaptation_set->segment_base && !group->period->segment_base) {
4559 if (rep->segment_template || group->adaptation_set->segment_template || group->period->segment_template) return GF_OK;
4560 if (rep->segment_list || group->adaptation_set->segment_list || group->period->segment_list) return GF_OK;
4561 } else {
4562 char *profile = rep->profiles;
4563 if (!profile) profile = group->adaptation_set->profiles;
4564 if (!profile) profile = group->dash->mpd->profiles;
4565
4566 //if on-demand cleanup all segment templates and segment list if we have base URLs
4567 if (profile && strstr(profile, "on-demand")) {
4568 u32 nb_rem=0;
4569 if (rep->segment_template) {
4570 nb_rem++;
4571 gf_mpd_segment_template_free(rep->segment_template);
4572 rep->segment_template = NULL;
4573 }
4574 if (group->adaptation_set->segment_template) {
4575 nb_rem++;
4576 gf_mpd_segment_template_free(group->adaptation_set->segment_template);
4577 group->adaptation_set->segment_template = NULL;
4578 }
4579
4580 if (group->period->segment_template) {
4581 nb_rem++;
4582 gf_mpd_segment_template_free(group->period->segment_template);
4583 group->period->segment_template = NULL;
4584 }
4585 if (nb_rem) {
4586 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] SegmentTemplate present for on-demand with SegmentBase present - skipping SegmentTemplate\n"));
4587 }
4588 nb_rem=0;
4589 if (rep->segment_list) {
4590 nb_rem++;
4591 gf_mpd_segment_template_free(rep->segment_list);
4592 rep->segment_list = NULL;
4593 }
4594 if (group->adaptation_set->segment_list) {
4595 nb_rem++;
4596 gf_mpd_segment_template_free(group->adaptation_set->segment_list);
4597 group->adaptation_set->segment_list = NULL;
4598 }
4599 if (group->period->segment_list) {
4600 nb_rem++;
4601 gf_mpd_segment_template_free(group->period->segment_list);
4602 group->period->segment_list = NULL;
4603 }
4604 if (nb_rem) {
4605 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] SegmentList present for on-demand with SegmentBase present - skipping SegmentList\n"));
4606 }
4607 }
4608 }
4609
4610 /*OK we are in single-file mode, download all required indexes & co*/
4611 for (i=0; i<gf_list_count(group->adaptation_set->representations); i++) {
4612 char *sidx_file = NULL;
4613 u64 duration, index_start_range = 0, index_end_range = 0, init_start_range, init_end_range;
4614 Bool index_in_base, init_in_base;
4615 Bool init_needs_byte_range = GF_FALSE;
4616 Bool has_seen_sidx = GF_FALSE;
4617 Bool is_isom = GF_TRUE;
4618 rep = gf_list_get(group->adaptation_set->representations, i);
4619
4620 index_in_base = init_in_base = GF_FALSE;
4621 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);
4622 if (e) goto exit;
4623
4624 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);
4625 if (e) goto exit;
4626
4627
4628 if (is_isom && (init_in_base || index_in_base)) {
4629 if (!strstr(init_url, "://") || (!strnicmp(init_url, "file://", 7) ) ) {
4630 GF_SAFEALLOC(rep->segment_list, GF_MPD_SegmentList);
4631 if (!rep->segment_list) {
4632 e = GF_OUT_OF_MEM;
4633 goto exit;
4634 }
4635 rep->segment_list->segment_URLs = gf_list_new();
4636
4637 if (init_in_base) {
4638 GF_SAFEALLOC(rep->segment_list->initialization_segment, GF_MPD_URL);
4639 if (!rep->segment_list->initialization_segment) {
4640 e = GF_OUT_OF_MEM;
4641 goto exit;
4642 }
4643 rep->segment_list->initialization_segment->sourceURL = gf_strdup(init_url);
4644 rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
4645 /*we don't want to load the entire movie */
4646 init_needs_byte_range = 1;
4647 }
4648 if (index_in_base) {
4649 sidx_file = (char *)init_url;
4650 }
4651 }
4652 /*we need to download the init segment, at least partially*/
4653 else {
4654 u32 offset = 0;
4655 u32 box_type = 0;
4656 u32 box_size = 0;
4657 u32 sidx_start = 0;
4658 const char *cache_name;
4659
4660 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Downloading init segment and SIDX for representation %s\n", init_url));
4661
4662 /*download first 8 bytes and check if we do have a box starting there*/
4663 e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, 7, 1, group);
4664 if (e) goto exit;
4665 cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
4666 e = dash_load_box_type(cache_name, offset, &box_type, &box_size);
4667 offset = 8;
4668 while (box_type) {
4669 /*we got the moov, stop here */
4670 if (!index_in_base && (box_type==GF_ISOM_BOX_TYPE_MOOV)) {
4671 e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, offset+box_size-9, 2, group);
4672 break;
4673 } else {
4674 const u32 offset_ori = offset;
4675 e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, offset+box_size-1, 2, group);
4676 if (e < 0) goto exit;
4677 offset += box_size;
4678 /*we need to refresh the cache name because of our memory astorage thing ...*/
4679 cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
4680 e = dash_load_box_type(cache_name, offset-8, &box_type, &box_size);
4681 if (e == GF_IO_ERR) {
4682 /*if the socket was closed then gf_dash_download_resource() with gmem:// was reset - retry*/
4683 e = dash_load_box_type(cache_name, offset-offset_ori-8, &box_type, &box_size);
4684 if (box_type == GF_ISOM_BOX_TYPE_SIDX) {
4685 offset -= 8;
4686 /*FIXME sidx found, reload the full resource*/
4687 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] have to re-downloading init and SIDX for rep %s\n", init_url));
4688 e = gf_dash_download_resource(group->dash, download_sess, init_url, 0, offset+box_size-1, 2, group);
4689 break;
4690 }
4691 }
4692
4693 if (box_type == GF_ISOM_BOX_TYPE_SIDX) {
4694 if (!sidx_start) sidx_start = offset;
4695 has_seen_sidx = 1;
4696 } else if (has_seen_sidx)
4697 break;
4698 }
4699 }
4700 if (e < 0) goto exit;
4701
4702 if (box_type == 0) {
4703 e = GF_ISOM_INVALID_FILE;
4704 goto exit;
4705 }
4706 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Done downloading init segment and SIDX\n"));
4707
4708 GF_SAFEALLOC(rep->segment_list, GF_MPD_SegmentList);
4709 if (!rep->segment_list) {
4710 e = GF_OUT_OF_MEM;
4711 goto exit;
4712 }
4713 rep->segment_list->segment_URLs = gf_list_new();
4714
4715 cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
4716 if (init_in_base) {
4717 char szName[100];
4718 GF_SAFEALLOC(rep->segment_list->initialization_segment, GF_MPD_URL);
4719 if (!rep->segment_list->initialization_segment) {
4720 e = GF_OUT_OF_MEM;
4721 goto exit;
4722 }
4723
4724 if (!group->dash->thread_mode) {
4725 rep->segment_list->initialization_segment->sourceURL = gf_strdup(init_url);
4726 GF_SAFEALLOC(rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange);
4727 if (rep->segment_list->initialization_segment->byte_range) {
4728 rep->segment_list->initialization_segment->byte_range->start_range = init_start_range;
4729 rep->segment_list->initialization_segment->byte_range->end_range = init_end_range ? init_end_range : (sidx_start-1);
4730 }
4731 }
4732 //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 ..
4733 else if (!strnicmp(cache_name, "gmem://", 7)) {
4734 u8 *mem_address;
4735 e = gf_blob_get_data(cache_name, &mem_address, &rep->playback.init_segment.size);
4736 if (e) {
4737 goto exit;
4738 }
4739 rep->playback.init_segment.data = gf_malloc(sizeof(char) * rep->playback.init_segment.size);
4740 memcpy(rep->playback.init_segment.data, mem_address, sizeof(char) * rep->playback.init_segment.size);
4741
4742 sprintf(szName, "gmem://%p", &rep->playback.init_segment);
4743 rep->segment_list->initialization_segment->sourceURL = gf_strdup(szName);
4744 rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
4745 } else {
4746 FILE *t = gf_fopen(cache_name, "rb");
4747 if (t) {
4748 s32 res;
4749 rep->playback.init_segment.size = (u32) gf_fsize(t);
4750
4751 rep->playback.init_segment.data = gf_malloc(sizeof(char) * rep->playback.init_segment.size);
4752 res = (s32) gf_fread(rep->playback.init_segment.data, rep->playback.init_segment.size, t);
4753 if (res != rep->playback.init_segment.size) {
4754 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to store init segment\n"));
4755 } else if (rep->segment_list && rep->segment_list->initialization_segment) {
4756 sprintf(szName, "gmem://%p", &rep->playback.init_segment);
4757 rep->segment_list->initialization_segment->sourceURL = gf_strdup(szName);
4758 rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
4759 }
4760 }
4761 }
4762 if (group->dash->thread_mode) {
4763 cache_name = rep->segment_list->initialization_segment->sourceURL;
4764 //cleanup cache right away
4765 group->dash->dash_io->delete_cache_file(group->dash->dash_io, *download_sess, init_url);
4766 }
4767
4768 }
4769 if (index_in_base) {
4770 sidx_file = (char *)cache_name;
4771 }
4772 }
4773 }
4774 /*we have index url, download it*/
4775 if (! index_in_base) {
4776 e = gf_dash_download_resource(group->dash, download_sess, index_url, index_start_range, index_end_range, 1, group);
4777 if (e) goto exit;
4778 sidx_file = (char *)group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
4779 }
4780
4781 /*load sidx*/
4782 e = gf_dash_load_representation_sidx(group, rep, sidx_file, !index_in_base, init_needs_byte_range);
4783 if (e) {
4784 rep->playback.disabled = 1;
4785 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to load segment index for this representation - disabling\n"));
4786 }
4787
4788 if (!group->dash->thread_mode) {
4789 //cleanup cache right away
4790 group->dash->dash_io->delete_cache_file(group->dash->dash_io, *download_sess, init_url);
4791 }
4792
4793 /*reset all seg based stuff*/
4794 if (rep->segment_base) {
4795 gf_mpd_segment_base_free(rep->segment_base);
4796 rep->segment_base = NULL;
4797 }
4798
4799 gf_free(index_url);
4800 index_url = NULL;
4801 gf_free(init_url);
4802 init_url = NULL;
4803 }
4804 if (group->adaptation_set->segment_base) {
4805 gf_mpd_segment_base_free(group->adaptation_set->segment_base);
4806 group->adaptation_set->segment_base = NULL;
4807 }
4808 group->was_segment_base = 1;
4809
4810 exit:
4811 if (init_url) gf_free(init_url);
4812 if (index_url) gf_free(index_url);
4813 return e;
4814 }
4815
gf_dash_solve_period_xlink(GF_DashClient * dash,GF_List * period_list,u32 period_idx)4816 static void gf_dash_solve_period_xlink(GF_DashClient *dash, GF_List *period_list, u32 period_idx)
4817 {
4818 u32 count, i;
4819 GF_Err e;
4820 u64 start = 0;
4821 u64 src_duration = 0;
4822 Bool is_local=GF_FALSE;
4823 const char *local_url;
4824 char *url, *period_xlink;
4825 GF_DOMParser *parser;
4826 GF_MPD *new_mpd;
4827 GF_MPD_Period *period;
4828 GF_DASHFileIOSession xlink_sess=NULL;
4829
4830 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
4831
4832 period = gf_list_get(period_list, period_idx);
4833 if (!period->xlink_href || (dash->atsc_clock_state==1)) {
4834 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
4835 return;
4836 }
4837 start = period->start;
4838 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Resolving period XLINK %s\n", period->xlink_href));
4839
4840 if (!strcmp(period->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013")) {
4841 //spec is not very clear here, I suppose it means "remove the element"
4842 gf_list_rem(period_list, period_idx);
4843 gf_mpd_period_free(period);
4844 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
4845 return;
4846 }
4847
4848 //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://
4849 period_xlink = strstr(period->xlink_href, "http://");
4850 if (!period_xlink) period_xlink = strstr(period->xlink_href, "HTTP://");
4851 if (!period_xlink) period_xlink = strstr(period->xlink_href, "https://");
4852 if (!period_xlink) period_xlink = strstr(period->xlink_href, "HTTPS://");
4853 if (!period_xlink) period_xlink = period->xlink_href;
4854
4855 //xlink relative to our MPD base URL
4856 url = gf_url_concatenate(dash->base_url, period_xlink);
4857
4858 if (!strstr(url, "://") || !strnicmp(url, "file://", 7) ) {
4859 local_url = url;
4860 is_local=GF_TRUE;
4861 e = GF_OK;
4862 } else {
4863 if (dash->query_string) {
4864 char *full_url;
4865 char *purl, *sep;
4866 u32 len;
4867 purl = url ? url : period_xlink;
4868 len = (u32) (2 + strlen(purl) + strlen(dash->query_string) + (period->ID ? strlen(period->ID) : 0 ) );
4869 full_url = gf_malloc(sizeof(char)*len);
4870
4871 strcpy(full_url, purl);
4872 if (strchr(purl, '?')) strcat(full_url, "&");
4873 else strcat(full_url, "?");
4874
4875 //append the query string
4876 strcat(full_url, dash->query_string);
4877 //if =PID is given, replace by period ID
4878 sep = strstr(dash->query_string, "=PID");
4879 if (sep && period->ID) {
4880 char *sep2 = strstr(full_url, "=PID");
4881 assert(sep2);
4882 sep2[1] = 0;
4883 strcat(full_url, period->ID);
4884 strcat(full_url, sep+4);
4885 }
4886
4887 /*use non-persistent connection for MPD*/
4888 e = gf_dash_download_resource(dash, &xlink_sess, full_url, 0, 0, 0, NULL);
4889 gf_free(full_url);
4890
4891 } else {
4892 /*use non-persistent connection for MPD*/
4893 e = gf_dash_download_resource(dash, &xlink_sess, url ? url : period_xlink, 0, 0, 0, NULL);
4894 }
4895 }
4896
4897 if (e) {
4898 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)));
4899 gf_free(period->xlink_href);
4900 period->xlink_href = NULL;
4901 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
4902 if (xlink_sess) dash->dash_io->del(dash->dash_io, xlink_sess);
4903 if (url) gf_free(url);
4904 return;
4905 }
4906
4907 if (!is_local) {
4908 /*in case the session has been restarted, local_url may have been destroyed - get it back*/
4909 local_url = dash->dash_io->get_cache_name(dash->dash_io, xlink_sess);
4910 }
4911
4912 /* parse the MPD */
4913 parser = gf_xml_dom_new();
4914 e = gf_xml_dom_parse(parser, local_url, NULL, NULL);
4915 if (url) gf_free(url);
4916 url = NULL;
4917
4918 if (xlink_sess) {
4919 //get redirected URL
4920 url = (char *) dash->dash_io->get_url(dash->dash_io, xlink_sess);
4921 if (url) url = gf_strdup(url);
4922 dash->dash_io->del(dash->dash_io, xlink_sess);
4923 }
4924 if (e != GF_OK) {
4925 gf_xml_dom_del(parser);
4926 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)));
4927 gf_free(period->xlink_href);
4928 period->xlink_href = NULL;
4929 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
4930 if (url) gf_free(url);
4931 return;
4932 }
4933 new_mpd = gf_mpd_new();
4934
4935 count = gf_xml_dom_get_root_nodes_count(parser);
4936 for (i=0; i<count; i++) {
4937 GF_XMLNode *root = gf_xml_dom_get_root_idx(parser, i);
4938 if (i) {
4939 e = gf_mpd_complete_from_dom(root, new_mpd, period->xlink_href);
4940 } else {
4941 e = gf_mpd_init_from_dom(root, new_mpd, period->xlink_href);
4942 }
4943 if (e) break;
4944 }
4945 gf_xml_dom_del(parser);
4946 if (e) {
4947 gf_free(period->xlink_href);
4948 period->xlink_href = NULL;
4949 gf_mpd_del(new_mpd);
4950 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
4951 if (url) gf_free(url);
4952 return;
4953 }
4954
4955 gf_list_rem(period_list, period_idx);
4956
4957 if (dash->split_adaptation_set)
4958 gf_mpd_split_adaptation_sets(new_mpd);
4959
4960 if (!period->duration) {
4961 GF_MPD_Period *next_period = gf_list_get(period_list, period_idx);
4962 if (next_period && next_period->start)
4963 period->duration = next_period->start - period->start;
4964 }
4965 src_duration = period->duration;
4966 //insert all periods
4967 while (gf_list_count(new_mpd->periods)) {
4968 GF_MPD_Period *inserted_period = gf_list_get(new_mpd->periods, 0);
4969 gf_list_rem(new_mpd->periods, 0);
4970 //forbiden
4971 if (inserted_period->xlink_href && inserted_period->xlink_actuate_on_load) {
4972 gf_mpd_period_free(inserted_period);
4973 continue;
4974 }
4975 inserted_period->origin_base_url = url ? gf_strdup(url) : NULL;
4976 inserted_period->start = start;
4977 inserted_period->type = GF_MPD_TYPE_STATIC;
4978
4979 gf_list_insert(period_list, inserted_period, period_idx);
4980 period_idx++;
4981
4982 if (period->duration) {
4983 //truncate duration
4984 if (inserted_period->duration > src_duration) {
4985 inserted_period->duration = src_duration;
4986 break;
4987 } else {
4988 src_duration -= inserted_period->duration;
4989 }
4990 }
4991 start += inserted_period->duration;
4992 }
4993 if (url) gf_free(url);
4994
4995 //this will do the garbage collection
4996 gf_list_add(new_mpd->periods, period);
4997
4998 gf_mpd_del(new_mpd);
4999
5000 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
5001 }
5002
gf_dash_get_tiles_quality_rank(GF_DashClient * dash,GF_DASH_Group * tile_group)5003 static u32 gf_dash_get_tiles_quality_rank(GF_DashClient *dash, GF_DASH_Group *tile_group)
5004 {
5005 s32 res, res2;
5006 struct _dash_srd_desc *srd = tile_group->srd_desc;
5007
5008 //no SRD is max quality for now
5009 if (!srd) return 0;
5010 if (!tile_group->srd_w || !tile_group->srd_h) return 0;
5011
5012 if (tile_group->quality_degradation_hint) {
5013 u32 v = tile_group->quality_degradation_hint * MAX(srd->srd_nb_rows, srd->srd_nb_cols);
5014 v/=100;
5015 return v;
5016 }
5017
5018
5019 switch (dash->tile_adapt_mode) {
5020 case GF_DASH_ADAPT_TILE_NONE:
5021 return 0;
5022 case GF_DASH_ADAPT_TILE_ROWS:
5023 return tile_group->srd_row_idx;
5024 case GF_DASH_ADAPT_TILE_ROWS_REVERSE:
5025 return srd->srd_nb_rows - 1 - tile_group->srd_row_idx;
5026 case GF_DASH_ADAPT_TILE_ROWS_MIDDLE:
5027 res = srd->srd_nb_rows/2;
5028 res -= tile_group->srd_row_idx;
5029 return ABS(res);
5030 case GF_DASH_ADAPT_TILE_COLUMNS:
5031 return tile_group->srd_col_idx;
5032 case GF_DASH_ADAPT_TILE_COLUMNS_REVERSE:
5033 return srd->srd_nb_cols - 1 - tile_group->srd_col_idx;
5034 case GF_DASH_ADAPT_TILE_COLUMNS_MIDDLE:
5035 res = srd->srd_nb_cols/2;
5036 res -= tile_group->srd_col_idx;
5037 return ABS(res);
5038 case GF_DASH_ADAPT_TILE_CENTER:
5039 res = srd->srd_nb_rows/2 - tile_group->srd_row_idx;
5040 res2 = srd->srd_nb_cols/2 - tile_group->srd_col_idx;
5041 return MAX( ABS(res), ABS(res2) );
5042 case GF_DASH_ADAPT_TILE_EDGES:
5043 res = srd->srd_nb_rows/2 - tile_group->srd_row_idx;
5044 res = srd->srd_nb_rows/2 - ABS(res);
5045 res2 = srd->srd_nb_cols/2 - tile_group->srd_col_idx;
5046 res2 = srd->srd_nb_cols/2 - ABS(res2);
5047 return MIN( res, res2 );
5048 }
5049 return 0;
5050 }
5051
5052 //used upon startup of the session only
gf_dash_set_tiles_quality(GF_DashClient * dash,struct _dash_srd_desc * srd)5053 static void gf_dash_set_tiles_quality(GF_DashClient *dash, struct _dash_srd_desc *srd)
5054 {
5055 u32 i, count;
5056 Bool tiles_use_lowest = (dash->first_select_mode==GF_DASH_SELECT_BANDWIDTH_HIGHEST_TILES) ? GF_TRUE : GF_FALSE;
5057
5058 count = gf_list_count(dash->groups);
5059 for (i=0; i<count; i++) {
5060 GF_DASH_Group *group = gf_list_get(dash->groups, i);
5061 u32 lower_quality;
5062 if (group->srd_desc != srd) continue;
5063
5064 lower_quality = gf_dash_get_tiles_quality_rank(dash, group);
5065 if (!lower_quality) continue;
5066
5067 if (tiles_use_lowest && (group->active_rep_index >= lower_quality)) {
5068 lower_quality = group->active_rep_index - lower_quality;
5069 } else {
5070 lower_quality = 0;
5071 }
5072 gf_dash_set_group_representation(group,
5073 gf_list_get(group->adaptation_set->representations, lower_quality) );
5074 }
5075 }
5076
gf_dash_get_srd_desc(GF_DashClient * dash,u32 srd_id,Bool do_create)5077 static struct _dash_srd_desc *gf_dash_get_srd_desc(GF_DashClient *dash, u32 srd_id, Bool do_create)
5078 {
5079 u32 i, count;
5080 struct _dash_srd_desc *srd;
5081 count = dash->SRDs ? gf_list_count(dash->SRDs) : 0;
5082 for (i=0; i<count; i++) {
5083 srd = gf_list_get(dash->SRDs, i);
5084 if (srd->id==srd_id) return srd;
5085 }
5086 if (!do_create) return NULL;
5087 GF_SAFEALLOC(srd, struct _dash_srd_desc);
5088 if (!srd) return NULL;
5089 srd->id = srd_id;
5090 if (!dash->SRDs) dash->SRDs = gf_list_new();
5091 gf_list_add(dash->SRDs, srd);
5092 return srd;
5093 }
5094
gf_dash_setup_period(GF_DashClient * dash)5095 static GF_Err gf_dash_setup_period(GF_DashClient *dash)
5096 {
5097 GF_MPD_Period *period;
5098 u32 rep_i, as_i, group_i, j, nb_groups_ok;
5099 u32 retry = 10;
5100
5101 //solve xlink - if
5102 while (retry) {
5103 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
5104 if (!period) return GF_EOS;
5105 if (!period->xlink_href) break;
5106 gf_dash_solve_period_xlink(dash, dash->mpd->periods, dash->active_period_index);
5107 retry --;
5108 }
5109 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
5110 if (period->xlink_href && (dash->atsc_clock_state!=1) ) {
5111 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Too many xlink indirections on the same period - not supported\n"));
5112 return GF_NOT_SUPPORTED;
5113 }
5114
5115 if (!period->duration) {
5116 GF_MPD_Period *next_period = gf_list_get(dash->mpd->periods, dash->active_period_index+1);
5117 if (next_period && next_period->start)
5118 period->duration = next_period->start - period->start;
5119 }
5120
5121 /*we are not able to process webm dash (youtube)*/
5122 j = gf_list_count(period->adaptation_sets);
5123 for (as_i=0; as_i<j; as_i++) {
5124 GF_MPD_AdaptationSet *set = (GF_MPD_AdaptationSet*)gf_list_get(period->adaptation_sets, as_i);
5125 if (set->mime_type && strstr(set->mime_type, "webm")) {
5126 u32 k;
5127 for (k=0; k<gf_list_count(set->representations); ++k) {
5128 GF_MPD_Representation *rep = (GF_MPD_Representation*)gf_list_get(set->representations, k);
5129 rep->playback.disabled = GF_TRUE;
5130 }
5131 }
5132 }
5133
5134 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"));
5135
5136 /*setup all groups*/
5137 gf_dash_setup_groups(dash);
5138
5139 if (dash->debug_group_index>=0) {
5140 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Debuging adaptation set #%d in period, ignoring other ones!\n\n", dash->debug_group_index + 1));
5141 }
5142
5143 nb_groups_ok = 0;
5144 for (group_i=0; group_i<gf_list_count(dash->groups); group_i++) {
5145 GF_MPD_Representation *rep_sel;
5146 u32 active_rep, nb_rep;
5147 const char *mime_type;
5148 u32 nb_rep_ok = 0;
5149 Bool group_has_video = GF_FALSE;
5150 Bool disabled = GF_FALSE;
5151 Bool cp_supported = GF_FALSE;
5152 GF_DASH_Group *group = gf_list_get(dash->groups, group_i);
5153 Bool active_rep_found;
5154
5155 active_rep = 0;
5156
5157 if ((dash->debug_group_index>=0) && (group_i != (u32) dash->debug_group_index)) {
5158 group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
5159 continue;
5160 }
5161
5162 nb_rep = gf_list_count(group->adaptation_set->representations);
5163
5164 //on HLS get rid of audio only adaptation set if not in fMP4 mode
5165 if (dash->is_m3u8
5166 && !group->adaptation_set->max_width
5167 && !group->adaptation_set->max_height
5168 && (gf_list_count(dash->groups)>1)
5169 ) {
5170 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, 0);
5171 if ((!rep->segment_template || !rep->segment_template->initialization)
5172 && (!rep->segment_list || (!rep->segment_list->initialization_segment && !rep->segment_list->xlink_href))
5173 ) {
5174 group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
5175 continue;
5176 }
5177 }
5178
5179 if ((nb_rep>1) && !group->adaptation_set->segment_alignment && !group->adaptation_set->subsegment_alignment) {
5180 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet without segmentAlignment flag set - may result in broken adaptation\n"));
5181 }
5182 if (group->adaptation_set->xlink_href) {
5183 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));
5184 continue;
5185 }
5186
5187 for (j=0; j<gf_list_count(group->adaptation_set->essential_properties); j++) {
5188 GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->essential_properties, j);
5189 if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:srd:2014")) {
5190 u32 id, w, h, res;
5191 w = h = 0;
5192 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);
5193 if (res != 7) {
5194 res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h);
5195 if (res!=5) res=0;
5196 }
5197 if (res) {
5198 group->srd_desc = gf_dash_get_srd_desc(dash, id, GF_TRUE);
5199 if (!w) w = group->srd_x + group->srd_w;
5200 if (!h) h = group->srd_y + group->srd_h;
5201
5202 if (w>group->srd_desc->srd_fw)
5203 group->srd_desc->srd_fw = w;
5204 if (h>group->srd_desc->srd_fh)
5205 group->srd_desc->srd_fh = h;
5206 }
5207
5208 } else {
5209 //we don't know any defined scheme for now
5210 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet with unrecognized EssentialProperty %s - ignoring because not supported\n", mpd_desc->scheme_id_uri));
5211 disabled = 1;
5212 break;
5213 }
5214 }
5215 if (disabled) {
5216 continue;
5217 }
5218
5219 cp_supported = 1;
5220 for (j=0; j<gf_list_count(group->adaptation_set->content_protection); j++) {
5221 GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->content_protection, j);
5222 //we don't know any defined scheme for now
5223 if (strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:mp4protection:2011")) {
5224 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AdaptationSet with unrecognized ContentProtection %s\n", mpd_desc->scheme_id_uri));
5225 cp_supported = 0;
5226 } else {
5227 cp_supported = 1;
5228 break;
5229 }
5230 }
5231 if (!cp_supported) {
5232 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet with no supported ContentProtection - ignoring\n"));
5233 continue;
5234 }
5235
5236 for (j=0; j<gf_list_count(group->adaptation_set->supplemental_properties); j++) {
5237 GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->supplemental_properties, j);
5238 if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:srd:2014")) {
5239 u32 id, w, h, res;
5240 w = h = 0;
5241 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);
5242 if (res != 7) {
5243 res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h);
5244 if (res != 5) res=0;
5245 }
5246 if (res) {
5247 group->srd_desc = gf_dash_get_srd_desc(dash, id, GF_TRUE);
5248 if (!w) w = group->srd_x + group->srd_w;
5249 if (!h) h = group->srd_y + group->srd_h;
5250
5251 if (w>group->srd_desc->srd_fw)
5252 group->srd_desc->srd_fw = w;
5253 if (h>group->srd_desc->srd_fh)
5254 group->srd_desc->srd_fh = h;
5255 }
5256 }
5257 }
5258
5259 /*translate from single-indexed file to SegmentList*/
5260 gf_dash_setup_single_index_mode(group);
5261
5262 /* Select the appropriate representation in the given period */
5263 for (rep_i = 0; rep_i < nb_rep; rep_i++) {
5264 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
5265 if (rep->width && rep->height) group_has_video = GF_TRUE;
5266 }
5267
5268 //sort by ascending bandwidth and quality
5269 for (rep_i = 1; rep_i < nb_rep; rep_i++) {
5270 Bool swap=GF_FALSE;
5271 GF_MPD_Representation *r2 = gf_list_get(group->adaptation_set->representations, rep_i);
5272 GF_MPD_Representation *r1 = gf_list_get(group->adaptation_set->representations, rep_i-1);
5273 if (r1->bandwidth > r2->bandwidth) {
5274 swap=GF_TRUE;
5275 } else if ((r1->bandwidth == r2->bandwidth) && (r1->quality_ranking<r2->quality_ranking)) {
5276 swap=GF_TRUE;
5277 }
5278 if (swap) {
5279 gf_list_rem(group->adaptation_set->representations, rep_i);
5280 gf_list_insert(group->adaptation_set->representations, r2, rep_i-1);
5281 rep_i=0;
5282 }
5283 }
5284
5285 select_active_rep:
5286 group->min_representation_bitrate = (u32) -1;
5287 active_rep_found = GF_FALSE;
5288 for (rep_i = 0; rep_i < nb_rep; rep_i++) {
5289 u32 first_select_mode = dash->first_select_mode;
5290 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
5291 if (rep->playback.disabled)
5292 continue;
5293 if (!active_rep_found) {
5294 active_rep = rep_i;
5295 active_rep_found = GF_TRUE;
5296 }
5297
5298 rep_sel = gf_list_get(group->adaptation_set->representations, active_rep);
5299
5300 if (group_has_video && !rep->width && !rep->height) {
5301 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Adaptation %s: non-video in a video group - disabling it\n", rep->id));
5302 rep->playback.disabled = 1;
5303 continue;
5304 }
5305
5306 if (group_has_video && !rep_sel->width && !rep_sel->height && rep->width && rep->height) {
5307 rep_sel = rep;
5308 }
5309
5310 if (rep->bandwidth < group->min_representation_bitrate) {
5311 group->min_representation_bitrate = rep->bandwidth;
5312 }
5313
5314 if (rep_i) {
5315 Bool ok;
5316 if (rep->codecs && rep_sel->codecs) {
5317 char *sep = strchr(rep_sel->codecs, '.');
5318 if (sep) sep[0] = 0;
5319 ok = !strnicmp(rep->codecs, rep_sel->codecs, strlen(rep_sel->codecs) );
5320 //check for scalable coding
5321 if (!ok && rep->dependency_id) {
5322 if (!strncmp(rep_sel->codecs, "avc", 3)) {
5323 //we accept LHVC with different configs as enhancement for AVC
5324 if (!strncmp(rep->codecs, "lhv", 3) || !strncmp(rep->codecs, "lhe", 3) ) ok = 1;
5325 //we accept SVC and MVC as enhancement for AVC
5326 else if (!strncmp(rep->codecs, "svc", 3) || !strncmp(rep->codecs, "mvc", 3) ) ok = 1;
5327 }
5328 else if (!strncmp(rep_sel->codecs, "hvc", 3) || !strncmp(rep_sel->codecs, "hev", 3)) {
5329 //we accept HEVC and HEVC+LHVC with different configs
5330 if (!strncmp(rep->codecs, "hvc", 3) || !strncmp(rep->codecs, "hev", 3) ) ok = 1;
5331 //we accept LHVC with different configs
5332 else if (!strncmp(rep->codecs, "lhv", 3) || !strncmp(rep->codecs, "lhe", 3) ) ok = 1;
5333 }
5334 }
5335
5336 if (sep) sep[0] = '.';
5337 if (!ok) {
5338 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));
5339 //we don(t support mixes
5340 rep->playback.disabled = 1;
5341 continue;
5342 }
5343 }
5344 }
5345 //move to highest rate if ATSC session and rep is not a remote one (baseURL set)
5346 if (dash->atsc_clock_state && (first_select_mode==GF_DASH_SELECT_BANDWIDTH_LOWEST) && !gf_list_count(rep->base_URLs))
5347 first_select_mode = GF_DASH_SELECT_BANDWIDTH_HIGHEST;
5348
5349 switch (first_select_mode) {
5350 case GF_DASH_SELECT_QUALITY_LOWEST:
5351 if (rep->quality_ranking && (rep->quality_ranking < rep_sel->quality_ranking)) {
5352 active_rep = rep_i;
5353 break;
5354 }/*fallthrough if quality is not indicated*/
5355 case GF_DASH_SELECT_BANDWIDTH_LOWEST:
5356 if ((rep->width&&rep->height) || !group_has_video) {
5357 if (rep->bandwidth < rep_sel->bandwidth) {
5358 active_rep = rep_i;
5359 }
5360 }
5361 break;
5362 case GF_DASH_SELECT_QUALITY_HIGHEST:
5363 if (rep->quality_ranking > rep_sel->quality_ranking) {
5364 active_rep = rep_i;
5365 break;
5366 }
5367 /*fallthrough if quality is not indicated*/
5368 case GF_DASH_SELECT_BANDWIDTH_HIGHEST:
5369 if (rep->bandwidth > rep_sel->bandwidth) {
5370 active_rep = rep_i;
5371 }
5372 break;
5373 default:
5374 break;
5375 }
5376 }
5377 for (rep_i = 0; rep_i < nb_rep; rep_i++) {
5378 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
5379 if (!rep->playback.disabled)
5380 nb_rep_ok++;
5381 }
5382
5383 if (! nb_rep_ok) {
5384 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] No valid representation in this group - disabling\n"));
5385 group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
5386 continue;
5387 }
5388
5389 rep_sel = gf_list_get(group->adaptation_set->representations, active_rep);
5390
5391 gf_dash_set_group_representation(group, rep_sel);
5392 // active representation is marked as disabled, we need to redo the selection
5393 if (rep_sel->playback.disabled)
5394 goto select_active_rep;
5395
5396 //adjust seek
5397 if (dash->start_range_period) {
5398 gf_dash_seek_group(dash, group, dash->start_range_period, 0);
5399 }
5400
5401 mime_type = gf_dash_get_mime_type(NULL, rep_sel, group->adaptation_set);
5402
5403 if (!mime_type) {
5404 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Missing MIME type for AdaptationSet - skipping\n"));
5405 continue;
5406 }
5407
5408 /* TODO: Generate segment names if urltemplates are used */
5409 if (!rep_sel->segment_base && !rep_sel->segment_list && !rep_sel->segment_template
5410 && !group->adaptation_set->segment_base && !group->adaptation_set->segment_list && !group->adaptation_set->segment_template
5411 && !group->period->segment_base && !group->period->segment_list && !group->period->segment_template
5412 && !gf_list_count(rep_sel->base_URLs)
5413 ) {
5414 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Segment URLs are not present for AdaptationSet - skipping\n"));
5415 continue;
5416 }
5417
5418 group->selection = GF_DASH_GROUP_NOT_SELECTED;
5419 nb_groups_ok++;
5420 }
5421 dash->start_range_period = 0;
5422
5423 //setup SRDs
5424 for (as_i = 0; as_i<gf_list_count(dash->SRDs); as_i++) {
5425 u32 cols[10], rows[10];
5426 struct _dash_srd_desc *srd = gf_list_get(dash->SRDs, as_i);
5427
5428 srd->srd_nb_rows = srd->srd_nb_cols = 0;
5429
5430 //sort SRDs
5431 for (j=1; j < gf_list_count(dash->groups); j++) {
5432 GF_DASH_Group *dg2 = gf_list_get(dash->groups, j);
5433 GF_DASH_Group *dg1 = gf_list_get(dash->groups, j-1);
5434 u32 dg1_weight = dg1->srd_y << 16 | dg1->srd_x;
5435 u32 dg2_weight = dg2->srd_y << 16 | dg2->srd_x;
5436
5437 if (dg1->srd_desc != srd) continue;
5438 if (dg2->srd_desc != srd) continue;
5439
5440 if (dg1_weight > dg2_weight) {
5441 gf_list_rem(dash->groups, j);
5442 gf_list_insert(dash->groups, dg2, j-1);
5443 j=0;
5444 }
5445 }
5446
5447 //groups are now sorted for this srd, locate col/row positions
5448 for (group_i=0; group_i<gf_list_count(dash->groups); group_i++) {
5449 u32 k;
5450 Bool found = GF_FALSE;
5451 GF_DASH_Group *group = gf_list_get(dash->groups, group_i);
5452 if (group->srd_desc != srd) continue;
5453
5454 if (!group->srd_w || !group->srd_h) continue;
5455
5456 for (k=0; k<srd->srd_nb_cols; k++) {
5457 if (cols[k]==group->srd_x) {
5458 found=GF_TRUE;
5459 break;
5460 }
5461 }
5462 if (!found) {
5463 cols[srd->srd_nb_cols] = group->srd_x;
5464 group->srd_col_idx = srd->srd_nb_cols;
5465 srd->srd_nb_cols++;
5466
5467 srd->width += group->adaptation_set->max_width;
5468
5469 } else {
5470 group->srd_col_idx = k;
5471 }
5472
5473 found = GF_FALSE;
5474 for (k=0; k<srd->srd_nb_rows; k++) {
5475 if (rows[k]==group->srd_y) {
5476 found=GF_TRUE;
5477 break;
5478 }
5479 }
5480 if (!found) {
5481 rows[srd->srd_nb_rows] = group->srd_y;
5482 group->srd_row_idx = srd->srd_nb_rows;
5483 srd->srd_nb_rows++;
5484 srd->height += group->adaptation_set->max_height;
5485 } else {
5486 group->srd_row_idx = k;
5487 }
5488
5489 }
5490 gf_dash_set_tiles_quality(dash, srd);
5491 }
5492
5493 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
5494
5495 if (period->segment_base) {
5496 gf_mpd_segment_base_free(period->segment_base);
5497 period->segment_base = NULL;
5498 }
5499
5500 if (!nb_groups_ok) {
5501 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] No AdaptationSet could be selected in the MPD - Cannot play\n"));
5502 return GF_NON_COMPLIANT_BITSTREAM;
5503 }
5504
5505 /*and seek if needed*/
5506 return GF_OK;
5507 }
5508
5509
gf_dash_group_check_time(GF_DASH_Group * group)5510 static void gf_dash_group_check_time(GF_DASH_Group *group)
5511 {
5512 s64 check_time;
5513 u32 nb_dropped;
5514
5515 if (group->dash->is_m3u8) return;
5516 if (! group->timeline_setup) return;
5517 if (group->broken_timing) return;
5518
5519 check_time = (s64) gf_net_get_utc();
5520 nb_dropped = 0;
5521
5522 while (1) {
5523 u32 seg_dur_ms;
5524 u64 seg_ast = gf_dash_get_segment_availability_start_time(group->dash->mpd, group, group->download_segment_index, &seg_dur_ms);
5525
5526 s64 now = check_time + (s64) seg_dur_ms;
5527 if (now <= (s64) seg_ast) {
5528 group->dash->tsb_exceeded = (u32) -1;
5529 return;
5530 }
5531
5532 now -= (s64) seg_ast;
5533 if (now <= (s64) seg_dur_ms) {
5534 group->dash->tsb_exceeded = (u32) -1;
5535 return;
5536 }
5537 if (((s32) group->time_shift_buffer_depth > 0) && (now > group->time_shift_buffer_depth)) {
5538 group->download_segment_index ++;
5539 nb_dropped ++;
5540 group->dash->time_in_tsb = 0;
5541 continue;
5542 }
5543
5544 if (nb_dropped > group->dash->tsb_exceeded) {
5545 group->dash->tsb_exceeded = nb_dropped;
5546 }
5547
5548 now -= group->dash->user_buffer_ms;
5549 if (now<0) return;
5550
5551 if (now>group->dash->time_in_tsb)
5552 group->dash->time_in_tsb = (u32) now;
5553 return;
5554 }
5555 }
5556
5557 typedef enum
5558 {
5559 GF_DASH_DownloadCancel,
5560 GF_DASH_DownloadRestart,
5561 GF_DASH_DownloadSuccess,
5562 } DownloadGroupStatus;
5563
5564
5565 static DownloadGroupStatus dash_download_group_download(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following);
5566
dash_set_min_wait(GF_DashClient * dash,u32 min_wait)5567 static GFINLINE void dash_set_min_wait(GF_DashClient *dash, u32 min_wait)
5568 {
5569 if (!dash->min_wait_ms_before_next_request || (min_wait < dash->min_wait_ms_before_next_request)) {
5570 dash->min_wait_ms_before_next_request = min_wait;
5571 dash->min_wait_sys_clock = gf_sys_clock();
5572 }
5573 }
5574
5575 /*TODO decide what is the best, fetch from another representation or ignore ...*/
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)5576 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)
5577 {
5578 u32 clock_time;
5579 Bool will_retry = GF_FALSE;
5580 Bool is_live = GF_FALSE;
5581 if (!dash || !group)
5582 return GF_DASH_DownloadCancel;
5583
5584 clock_time = gf_sys_clock();
5585
5586 dash_set_min_wait(dash, dash->min_timeout_between_404);
5587
5588 group->retry_after_utc = dash->min_timeout_between_404 + gf_net_get_utc();
5589 if (!group->period->origin_base_url && (dash->mpd->type==GF_MPD_TYPE_DYNAMIC))
5590 is_live = GF_TRUE;
5591
5592 if (e==GF_REMOTE_SERVICE_ERROR) {
5593 gf_dash_mark_group_done(group);
5594 }
5595 //failure on last segment in non dynamic mode: likely due to rounding in dash segment duration, assume no error
5596 //in dynamic mode, we need to check if this is a download schedule issue
5597 else if (!is_live && group->period->duration && (group->download_segment_index + 1 >= (s32) group->nb_segments_in_rep) ) {
5598 gf_dash_mark_group_done(group);
5599 }
5600 else if (group->maybe_end_of_stream) {
5601 if (group->maybe_end_of_stream==2) {
5602 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)));
5603 group->maybe_end_of_stream = 0;
5604 gf_dash_mark_group_done(group);
5605 }
5606 group->maybe_end_of_stream++;
5607 } else if (group->segment_in_valid_range) {
5608 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)));
5609 if (dash->speed >= 0) {
5610 group->download_segment_index++;
5611 } else if (group->download_segment_index) {
5612 group->download_segment_index--;
5613 } else {
5614 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
5615 gf_dash_mark_group_done(group);
5616 }
5617 group->segment_in_valid_range=0;
5618 } else if (group->prev_segment_ok && !group->time_at_first_failure) {
5619 if (!group->loop_detected) {
5620 group->time_at_first_failure = clock_time;
5621 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Error in downloading new segment: %s %s - starting countdown for %d ms\n", new_base_seg_url, gf_error_to_string(e), group->current_downloaded_segment_duration));
5622
5623 will_retry = GF_TRUE;
5624 }
5625 }
5626 //if multiple baseURL, try switching the base
5627 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) )) {
5628 group->current_base_url_idx++;
5629 if (new_base_seg_url) gf_free(new_base_seg_url);
5630 if (key_url) gf_free(key_url);
5631 return dash_download_group_download(dash, group, base_group, has_dep_following);
5632 }
5633 //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
5634 else if (group->prev_segment_ok && (clock_time - group->time_at_first_failure <= group->current_downloaded_segment_duration + dash->segment_lost_after_ms )) {
5635 will_retry = GF_TRUE;
5636 } else {
5637 if ((group->dash->atsc_clock_state==2) && (e==GF_URL_ERROR)) {
5638 const char *val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-atsc-loop");
5639 Bool is_loop = (val && !strcmp(val, "yes")) ? GF_TRUE : GF_FALSE;
5640 //if explicit loop or more than 5 consecutive seg lost restart synchro
5641 if ((group->nb_consecutive_segments_lost >= 5) || is_loop) {
5642 u32 i=0;
5643 if (is_loop) {
5644 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ATSC loop detected, reseting timeline\n"));
5645 } else {
5646 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ATSC lost %d consecutive segments, resetup tune-in\n", group->nb_consecutive_segments_lost));
5647 }
5648 dash->utc_drift_estimate = 0;
5649 dash->initial_period_tunein = GF_TRUE;
5650 dash->atsc_clock_state = 1;
5651 while ((group = gf_list_enum(dash->groups, &i))) {
5652 group->start_number_at_last_ast = 0;
5653 gf_dash_group_timeline_setup(dash->mpd, group, 0);
5654 group->loop_detected = is_loop;
5655 group->time_at_first_failure = 0;
5656 group->prev_segment_ok = GF_TRUE;
5657 }
5658 if (new_base_seg_url) gf_free(new_base_seg_url);
5659 if (key_url) gf_free(key_url);
5660 return GF_DASH_DownloadCancel;
5661 }
5662 }
5663 if (group->prev_segment_ok) {
5664 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));
5665 group->time_at_first_failure = 0;
5666 group->prev_segment_ok = GF_FALSE;
5667 }
5668 group->nb_consecutive_segments_lost ++;
5669
5670 //we are lost ....
5671 if (group->nb_consecutive_segments_lost == 20) {
5672 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"));
5673 dash_set_min_wait(dash, 1000);
5674 group->maybe_end_of_stream = 1;
5675 } else {
5676 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)));
5677 if (dash->speed >= 0) {
5678 group->download_segment_index++;
5679 } else if (group->download_segment_index) {
5680 group->download_segment_index--;
5681 } else {
5682 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
5683 gf_dash_mark_group_done(group);
5684 }
5685 }
5686 }
5687 //if retry, do not reset dependency status
5688 if (!will_retry) {
5689 if (rep->dependency_id) {
5690 segment_cache_entry *cache_entry = &base_group->cached[base_group->nb_cached_segments];
5691 cache_entry->has_dep_following = 0;
5692 }
5693
5694 if (group->base_rep_index_plus_one) {
5695 group->active_rep_index = group->base_rep_index_plus_one - 1;
5696 group->has_pending_enhancement = GF_FALSE;
5697 }
5698 }
5699
5700 if (new_base_seg_url) gf_free(new_base_seg_url);
5701 if (key_url) gf_free(key_url);
5702 return GF_DASH_DownloadCancel;
5703 }
5704
dash_download_group_download(GF_DashClient * dash,GF_DASH_Group * group,GF_DASH_Group * base_group,Bool has_dep_following)5705 static DownloadGroupStatus dash_download_group_download(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following)
5706 {
5707 //commented out as we end up doing too many requets
5708 GF_Err e;
5709 GF_MPD_Representation *rep;
5710 char *new_base_seg_url=NULL;
5711 char *key_url=NULL;
5712 bin128 key_iv;
5713 u64 start_range, end_range;
5714 Bool use_byterange;
5715 u32 representation_index;
5716 u32 clock_time, file_size=0, Bps=0;
5717 Bool empty_file = GF_FALSE;
5718 Bool remote_file = GF_FALSE;
5719 const char *local_file_name = NULL;
5720 const char *resource_name = NULL;
5721 const char *hdr = NULL;
5722 const char *base_url = NULL;
5723 GF_MPD_Type dyn_type = dash->mpd->type;
5724 if (group->period->origin_base_url)
5725 dyn_type = group->period->type;
5726
5727 if (group->done) return GF_DASH_DownloadSuccess;
5728 if (!base_group) return GF_DASH_DownloadSuccess;
5729
5730 if (group->selection != GF_DASH_GROUP_SELECTED) return GF_DASH_DownloadSuccess;
5731
5732 if (base_group->nb_cached_segments>=base_group->max_cached_segments) {
5733 return GF_DASH_DownloadCancel;
5734 }
5735
5736 /*remember the active rep index, since group->active_rep_index may change because of bandwidth control algorithm*/
5737 representation_index = group->active_rep_index;
5738 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
5739 rep->playback.broadcast_flag = GF_FALSE;
5740
5741 /* if the index of the segment to be downloaded is greater or equal to the last segment (as seen in the playlist),
5742 we need to check if a new playlist is ready */
5743 if (group->nb_segments_in_rep && (group->download_segment_index >= (s32) group->nb_segments_in_rep)) {
5744 u32 timer = gf_sys_clock() - dash->last_update_time;
5745 Bool update_playlist = 0;
5746
5747 /* this period is done*/
5748 if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && group->period->duration) {
5749 }
5750 /* update of the playlist, only if indicated */
5751 else if (dash->mpd->minimum_update_period && (timer > dash->mpd->minimum_update_period)) {
5752 update_playlist = 1;
5753 }
5754 /* if media_presentation_duration is 0 and we are in live, force a refresh (not in the spec but safety check*/
5755 else if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && !dash->mpd->media_presentation_duration) {
5756 if (group->segment_duration && (timer > group->segment_duration*1000))
5757 update_playlist = 1;
5758 }
5759 if (update_playlist) {
5760 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playlist should be updated, postponing group download until playlist is updated\n"));
5761 dash->force_mpd_update = 1;
5762 return GF_DASH_DownloadCancel;
5763 }
5764 /* Now that the playlist is up to date, we can check again */
5765 if (group->download_segment_index >= (s32) group->nb_segments_in_rep) {
5766 /* if there is a specified update period, we redo the whole process */
5767 if (dash->mpd->minimum_update_period || dyn_type==GF_MPD_TYPE_DYNAMIC) {
5768
5769 if (dyn_type==GF_MPD_TYPE_STATIC) {
5770 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Last segment in static period (dynamic MPD) - group is done\n"));
5771 gf_dash_mark_group_done(group);
5772 return GF_DASH_DownloadCancel;
5773 }
5774 else if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && group->period->duration) {
5775 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Last segment in period (dynamic mode) - group is done\n"));
5776 gf_dash_mark_group_done(group);
5777 return GF_DASH_DownloadCancel;
5778 }
5779 else if (! group->maybe_end_of_stream) {
5780 u32 now = gf_sys_clock();
5781 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));
5782 if (group->nb_cached_segments) {
5783 if (dash->is_m3u8 && (group->nb_cached_segments <= 1)) {
5784 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] There is only %d segment in cache, force MPD update\n", group->nb_cached_segments));
5785 dash->force_mpd_update = GF_TRUE;
5786 }
5787 return GF_DASH_DownloadCancel;
5788 }
5789
5790 if (!group->time_at_first_reload_required) {
5791 group->time_at_first_reload_required = now;
5792 return GF_DASH_DownloadCancel;
5793 }
5794 if (now - group->time_at_first_reload_required < group->cache_duration)
5795 return GF_DASH_DownloadCancel;
5796 if (dash->mpd->minimum_update_period) {
5797 if (now - group->time_at_first_reload_required < dash->mpd->minimum_update_period)
5798 return GF_DASH_DownloadCancel;
5799 } else if (dyn_type==GF_MPD_TYPE_DYNAMIC) {
5800 if (timer < group->nb_segments_in_rep * group->segment_duration * 1000)
5801 return GF_DASH_DownloadCancel;
5802 }
5803
5804 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));
5805 gf_dash_mark_group_done(group);
5806 return GF_DASH_DownloadCancel;
5807 }
5808 } else {
5809 /* if not, we are really at the end of the playlist, we can quit */
5810 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] End of period reached for group\n"));
5811 gf_dash_mark_group_done(group);
5812 if (!dash->request_period_switch && !group->has_pending_enhancement && !has_dep_following)
5813 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, gf_list_find(dash->groups, base_group), GF_OK);
5814 return GF_DASH_DownloadCancel;
5815 }
5816 }
5817 }
5818 group->time_at_first_reload_required = 0;
5819
5820 if (group->force_switch_bandwidth && !dash->auto_switch_count) {
5821 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Forcing representation switch, retesting group"));
5822 gf_dash_switch_group_representation(dash, group);
5823 /*restart*/
5824 return GF_DASH_DownloadRestart;
5825 }
5826
5827 /*check availablity start time of segment in Live !!*/
5828 if (!group->broken_timing && (dyn_type==GF_MPD_TYPE_DYNAMIC) && !dash->is_m3u8) {
5829 s32 to_wait = 0;
5830 u32 seg_dur_ms=0;
5831 #ifndef GPAC_DISABLE_LOG
5832 u32 start_number = gf_dash_get_start_number(group, rep);
5833 #endif
5834 s64 segment_ast = (s64) gf_dash_get_segment_availability_start_time(dash->mpd, group, group->download_segment_index, &seg_dur_ms);
5835 s64 now = (s64) gf_net_get_utc();
5836
5837
5838 if (group->retry_after_utc > (u64) now) {
5839 to_wait = (u32) (group->retry_after_utc - (u64) now);
5840 dash_set_min_wait(dash, (u32) to_wait);
5841
5842 return GF_DASH_DownloadCancel;
5843 }
5844
5845 clock_time = gf_sys_clock();
5846 to_wait = (s32) (segment_ast - now);
5847
5848 if (group->force_early_fetch) {
5849 if (to_wait>1) {
5850 GF_LOG(GF_LOG_WARNING, 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));
5851 to_wait = 0;
5852 } else {
5853 //we officially reached segment AST
5854 group->force_early_fetch = GF_FALSE;
5855 }
5856 }
5857
5858 /*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 ...*/
5859 if (to_wait > 1) {
5860 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)/1000.0, to_wait));
5861 if (group->last_segment_time) {
5862 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time));
5863 }
5864
5865 dash_set_min_wait(dash, (u32) to_wait);
5866
5867 return GF_DASH_DownloadCancel;
5868 } else {
5869 GF_LOG(GF_LOG_DEBUG, 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)/1000.0, -to_wait));
5870
5871 if (group->last_segment_time) {
5872 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time));
5873 }
5874 #if 0
5875 /*check if we are in the segment availability end time*/
5876 if (now < segment_ast + seg_dur_ms + group->time_shift_buffer_depth )
5877 in_segment_avail_time = 1;
5878 #endif
5879 }
5880 }
5881
5882 base_url = dash->base_url;
5883 if (group->period->origin_base_url) base_url = group->period->origin_base_url;
5884 /* At this stage, there are some segments left to be downloaded */
5885 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);
5886
5887 if (e || !new_base_seg_url) {
5888 if (e==GF_EOS) {
5889 gf_dash_mark_group_done(group);
5890 } else {
5891 /*do something!!*/
5892 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error resolving URL of next segment: %s\n", gf_error_to_string(e) ));
5893 }
5894 if (new_base_seg_url) gf_free(new_base_seg_url);
5895 return GF_DASH_DownloadCancel;
5896 }
5897 use_byterange = (start_range || end_range) ? 1 : 0;
5898
5899 if (use_byterange) {
5900 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Queuing new segment: %s (range: "LLD"-"LLD")\n", new_base_seg_url, start_range, end_range));
5901 }
5902
5903 /*local file*/
5904 if (strnicmp(base_url, "gfio://", 7)
5905 && (!strstr(new_base_seg_url, "://") || (!strnicmp(new_base_seg_url, "file://", 7) || !strnicmp(new_base_seg_url, "gmem://", 7) ) )
5906 ) {
5907 FILE *ftest;
5908 resource_name = local_file_name = (char *) new_base_seg_url;
5909 e = GF_OK;
5910 /*do not erase local files*/
5911 group->local_files = 1;
5912 if (group->force_switch_bandwidth && !dash->auto_switch_count) {
5913 if (new_base_seg_url) gf_free(new_base_seg_url);
5914 gf_dash_switch_group_representation(dash, group);
5915 /*restart*/
5916 return GF_DASH_DownloadRestart;
5917 }
5918 ftest = gf_fopen(local_file_name, "rb");
5919 if (!ftest) {
5920 if (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) ){
5921 group->current_base_url_idx++;
5922 if (new_base_seg_url) gf_free(new_base_seg_url);
5923 if (key_url) gf_free(key_url);
5924 return dash_download_group_download(dash, group, base_group, has_dep_following);
5925 } else if (group->period->duration && (group->download_segment_index + 1 == group->nb_segments_in_rep) ) {
5926 if (new_base_seg_url) gf_free(new_base_seg_url);
5927 gf_dash_mark_group_done(group);
5928 return GF_DASH_DownloadCancel;
5929 } else {
5930 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] File %s not found on disk\n", local_file_name));
5931 group->current_base_url_idx = 0;
5932 return on_group_download_error(dash, group, base_group, GF_NOT_FOUND, rep, new_base_seg_url, key_url, has_dep_following);
5933 }
5934 } else {
5935 file_size = (u32) gf_fsize(ftest);
5936 gf_fclose(ftest);
5937 }
5938 group->current_base_url_idx = 0;
5939 } else if (dash->thread_mode) {
5940 base_group->max_bitrate = 0;
5941 base_group->min_bitrate = (u32)-1;
5942
5943 /*use persistent connection for segment downloads*/
5944 if (use_byterange) {
5945 e = gf_dash_download_resource(dash, &(base_group->segment_download), new_base_seg_url, start_range, end_range, 1, base_group);
5946 } else {
5947 e = gf_dash_download_resource(dash, &(base_group->segment_download), new_base_seg_url, 0, 0, 1, base_group);
5948 }
5949
5950 if ((e==GF_IP_CONNECTION_CLOSED) && group->download_abort_type) {
5951 base_group->download_abort_type = 0;
5952 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Aborted while downloading segment (seek ?)%s \n", new_base_seg_url));
5953 if (new_base_seg_url) gf_free(new_base_seg_url);
5954 if (key_url) gf_free(key_url);
5955 return GF_DASH_DownloadSuccess;
5956 }
5957
5958 if (e != GF_OK) {
5959 return on_group_download_error(dash, group, base_group, e, rep, new_base_seg_url, key_url, has_dep_following);
5960 }
5961
5962 group->prev_segment_ok = GF_TRUE;
5963 if (group->time_at_first_failure) {
5964 if (group->current_base_url_idx) {
5965 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 by switching baseURL\n", new_base_seg_url));
5966 } else {
5967 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 - was our download schedule %d ms too early ?\n", new_base_seg_url, gf_sys_clock() - group->time_at_first_failure));
5968 }
5969 group->time_at_first_failure = 0;
5970 }
5971 group->nb_consecutive_segments_lost = 0;
5972 group->current_base_url_idx = 0;
5973
5974 if ((e==GF_OK) && group->force_switch_bandwidth) {
5975 if (!dash->auto_switch_count) {
5976 gf_dash_switch_group_representation(dash, group);
5977 if (new_base_seg_url) gf_free(new_base_seg_url);
5978 if (key_url) gf_free(key_url);
5979 /*restart*/
5980 return GF_DASH_DownloadRestart;
5981 }
5982 if (rep->playback.disabled) {
5983 gf_dash_skip_disabled_representation(group, rep, GF_FALSE);
5984 if (new_base_seg_url) gf_free(new_base_seg_url);
5985 if (key_url) gf_free(key_url);
5986 /*restart*/
5987 return GF_DASH_DownloadRestart;
5988 }
5989 }
5990 group->segment_must_be_streamed = base_group->segment_must_be_streamed;
5991
5992 if (group->segment_must_be_streamed)
5993 local_file_name = dash->dash_io->get_url(dash->dash_io, base_group->segment_download);
5994 else
5995 local_file_name = dash->dash_io->get_cache_name(dash->dash_io, base_group->segment_download);
5996
5997 file_size = dash->dash_io->get_total_size(dash->dash_io, base_group->segment_download);
5998 if (file_size==0) {
5999 empty_file = GF_TRUE;
6000 }
6001 resource_name = dash->dash_io->get_url(dash->dash_io, base_group->segment_download);
6002
6003 Bps = dash->dash_io->get_bytes_per_sec(dash->dash_io, base_group->segment_download);
6004 }//unthreaded mode
6005 else {
6006 resource_name = local_file_name = new_base_seg_url;
6007 remote_file = GF_TRUE;
6008
6009 hdr = dash->dash_io->get_header_value(dash->dash_io, base_group->segment_download, "x-atsc");
6010 if (hdr && !strcmp(hdr, "yes"))
6011 rep->playback.broadcast_flag = GF_TRUE;
6012 }
6013
6014 if (local_file_name && (e == GF_OK || group->segment_must_be_streamed || !dash->thread_mode ) ) {
6015 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
6016
6017 assert(base_group->nb_cached_segments<base_group->max_cached_segments);
6018 assert(local_file_name);
6019
6020 if (!empty_file) {
6021 segment_cache_entry *cache_entry = &base_group->cached[base_group->nb_cached_segments];
6022
6023 cache_entry->cache = gf_strdup(local_file_name);
6024 cache_entry->url = gf_strdup( resource_name );
6025 if (use_byterange && remote_file) {
6026 cache_entry->start_range = start_range;
6027 cache_entry->end_range = end_range;
6028 } else {
6029 cache_entry->start_range = 0;
6030 cache_entry->end_range = 0;
6031 }
6032 cache_entry->representation_index = representation_index;
6033 cache_entry->duration = (u32) group->current_downloaded_segment_duration;
6034 cache_entry->loop_detected = group->loop_detected;
6035 cache_entry->has_dep_following = has_dep_following;
6036 if (key_url) {
6037 cache_entry->key_url = key_url;
6038 memcpy(cache_entry->key_IV, key_iv, sizeof(bin128));
6039 key_url = NULL;
6040 }
6041
6042 group->loop_detected = GF_FALSE;
6043
6044 if (group->local_files && use_byterange) {
6045 cache_entry->start_range = start_range;
6046 cache_entry->end_range = end_range;
6047 }
6048 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));
6049
6050 base_group->nb_cached_segments++;
6051 }
6052 if (file_size)
6053 dash_store_stats(dash, group, Bps, file_size, rep->playback.broadcast_flag);
6054
6055 /* download enhancement representation of this segment*/
6056 if ((representation_index != group->max_complementary_rep_index) && rep->playback.enhancement_rep_index_plus_one) {
6057 group->active_rep_index = rep->playback.enhancement_rep_index_plus_one - 1;
6058 group->has_pending_enhancement = GF_TRUE;
6059 }
6060 /* if we have downloaded all enhancement representations of this segment, restart from base representation and increase dowloaded segment index by 1*/
6061 else {
6062 if (group->base_rep_index_plus_one) group->active_rep_index = group->base_rep_index_plus_one - 1;
6063 if (dash->speed >= 0) {
6064 group->download_segment_index++;
6065 } else if (group->download_segment_index) {
6066 group->download_segment_index--;
6067 } else {
6068 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
6069 gf_dash_mark_group_done(group);
6070 }
6071 group->has_pending_enhancement = GF_FALSE;
6072 }
6073 if (dash->auto_switch_count) {
6074 group->nb_segments_done++;
6075 if (group->nb_segments_done==dash->auto_switch_count) {
6076 group->nb_segments_done=0;
6077 gf_dash_skip_disabled_representation(group, rep, GF_TRUE);
6078 }
6079 }
6080
6081 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
6082
6083 //do not notify segments if there is a pending period switch - since these are decided by the user, we don't
6084 //want to notify old segments
6085 if (!dash->request_period_switch && !group->has_pending_enhancement && !has_dep_following)
6086 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, gf_list_find(dash->groups, base_group), GF_OK);
6087
6088 }
6089 if (new_base_seg_url) gf_free(new_base_seg_url);
6090 if (key_url) gf_free(key_url);
6091 if (e) return GF_DASH_DownloadCancel;
6092 return GF_DASH_DownloadSuccess;
6093 }
6094
6095
dash_download_group(GF_DashClient * dash,GF_DASH_Group * group,GF_DASH_Group * base_group,Bool has_dep_following)6096 static DownloadGroupStatus dash_download_group(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following)
6097 {
6098 DownloadGroupStatus res;
6099
6100 if (!group->current_dep_idx) {
6101 res = dash_download_group_download(dash, group, base_group, has_dep_following);
6102 if (res==GF_DASH_DownloadRestart) return res;
6103 if (res==GF_DASH_DownloadCancel) return res;
6104 group->current_dep_idx = 1;
6105 }
6106
6107 if (group->groups_depending_on) {
6108 u32 i, count = gf_list_count(group->groups_depending_on);
6109 i = group->current_dep_idx - 1;
6110 for (; i<count; i++) {
6111
6112 GF_DASH_Group *dep_group = gf_list_get(group->groups_depending_on, i);
6113 if ((i+1==count) && !dep_group->groups_depending_on)
6114 has_dep_following = GF_FALSE;
6115
6116 res = dash_download_group(dash, dep_group, base_group, has_dep_following);
6117 if (res==GF_DASH_DownloadRestart) {
6118 i--;
6119 continue;
6120 }
6121 group->current_dep_idx = i + 1;
6122 if (res==GF_DASH_DownloadCancel)
6123 return GF_DASH_DownloadCancel;
6124 }
6125 }
6126 group->current_dep_idx = 0;
6127 return GF_DASH_DownloadSuccess;
6128 }
6129
6130 //tile based adaptation
dash_global_rate_adaptation(GF_DashClient * dash,Bool for_postponed_only)6131 static void dash_global_rate_adaptation(GF_DashClient *dash, Bool for_postponed_only)
6132 {
6133 u32 quality_rank;
6134 u32 min_bandwidth = 0;
6135 Bool force_rep_idx = GF_FALSE;
6136 Bool local_file_mode = GF_FALSE;
6137 GF_MPD_Representation *rep, *rep_new;
6138 u32 total_rate, max_fsize, bandwidths[20], groups_per_quality[20], max_level;
6139 u32 q_idx, nb_qualities = 0;
6140 u32 i, count = gf_list_count(dash->groups), local_files = 0;
6141
6142 //initialize min/max bandwidth
6143 min_bandwidth = 0;
6144 max_level = 0;
6145 total_rate = (u32) -1;
6146 nb_qualities = 1;
6147 max_fsize = 0;
6148
6149 //get max qualities due to SRD descriptions
6150 //for now, consider all non-SRDs group to run in max quality
6151 for (i=0; i<gf_list_count(dash->SRDs); i++) {
6152 struct _dash_srd_desc *srd = gf_list_get(dash->SRDs, i);
6153 u32 nb_q = MAX(srd->srd_nb_cols, srd->srd_nb_rows);
6154 if (nb_q > nb_qualities) nb_qualities = nb_q;
6155 }
6156
6157 //estimate bitrate
6158 for (i=0; i<count; i++) {
6159 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6160 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6161 if (group->local_files) local_files ++;
6162 if (!group->bytes_per_sec) {
6163 if (!for_postponed_only && !dash->thread_mode)
6164 return;
6165 continue;
6166 }
6167 if (group->done) continue;
6168
6169 group->backup_Bps = group->bytes_per_sec;
6170 //only count broadband ones
6171 if (dash->atsc_clock_state && !gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs) && !group->period->origin_base_url) {
6172 u32 j;
6173 //get all active rep, count bandwidth for broadband ones
6174 for (j=0; j<=group->max_complementary_rep_index; j++) {
6175 rep = gf_list_get(group->adaptation_set->representations, j);
6176 //this rep is not in broadcast, add bandwidth
6177 if (gf_list_count(rep->base_URLs)) {
6178 total_rate = group->bytes_per_sec;
6179 }
6180 }
6181 } else {
6182 //use rate of largest downloaded file to perform rate adaptation
6183 //TODO: we should split rate adaptation per baseURL
6184 if (!max_fsize || (max_fsize<group->total_size)) {
6185 if (total_rate > group->bytes_per_sec) {
6186 total_rate = group->bytes_per_sec;
6187 max_fsize = group->total_size;
6188 }
6189 }
6190 }
6191 }
6192 if (total_rate == (u32) -1) {
6193 total_rate = 0;
6194 }
6195 if (local_files==count) {
6196 total_rate = dash->dash_io->get_bytes_per_sec ? dash->dash_io->get_bytes_per_sec(dash->dash_io, NULL) : 0;
6197 if (!total_rate) local_file_mode = GF_TRUE;
6198 } else if (!total_rate) {
6199 return;
6200 }
6201
6202 for (q_idx=0; q_idx<nb_qualities; q_idx++) {
6203 bandwidths[q_idx] = 0;
6204 groups_per_quality[q_idx] = 0;
6205
6206 for (i=0; i<count; i++) {
6207 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6208 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6209 if (group->done) continue;
6210
6211 quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
6212 if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
6213 if (quality_rank != q_idx) continue;
6214
6215 group->target_new_rep = 0;
6216 rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
6217 bandwidths[q_idx] += rep->bandwidth;
6218 groups_per_quality[q_idx] ++;
6219 if (max_level < 1 + quality_rank) max_level = 1+quality_rank;
6220
6221 //quick trick here: if no download cap in local playback, compute the total rate based on
6222 //quality rank
6223 if (local_file_mode) {
6224 u32 nb_reps = gf_list_count(group->adaptation_set->representations);
6225 //get rep matching the given quality rank - quality 0 is the highest and our
6226 //reps are sorted from lowest !
6227 u32 rep_target;
6228 if (!quality_rank)
6229 rep_target = nb_reps-1;
6230 else
6231 rep_target = (nb_qualities - quality_rank) * nb_reps / nb_qualities;
6232
6233 rep = gf_list_get(group->adaptation_set->representations, rep_target);
6234 total_rate += rep->bandwidth;
6235 }
6236 }
6237 min_bandwidth += bandwidths[q_idx];
6238 }
6239 if (local_file_mode) {
6240 //total rate is in bytes per second, and add a safety of 10 bytes to ensure selection
6241 total_rate = 10 + total_rate / 8;
6242 }
6243
6244 /*no per-quality adaptation, we may have oscillations*/
6245 if (!dash->tile_rate_decrease) {
6246 }
6247 /*automatic rate alloc*/
6248 else if (dash->tile_rate_decrease==100) {
6249 //for each quality level (starting from highest priority), increase the bitrate if possible
6250 for (q_idx=0; q_idx < max_level; q_idx++) {
6251 Bool test_pass = GF_TRUE;
6252 while (1) {
6253 u32 nb_rep_increased = 0;
6254 u32 nb_rep_in_qidx = 0;
6255 u32 cumulated_bw_in_pass = 0;
6256
6257 for (i=0; i<count; i++) {
6258 u32 diff;
6259 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6260 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6261 if (group->done) continue;
6262
6263 quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
6264 if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
6265 if (quality_rank != q_idx) continue;
6266
6267 if (group->target_new_rep + 1 == gf_list_count(group->adaptation_set->representations))
6268 continue;
6269
6270 nb_rep_in_qidx++;
6271
6272 rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
6273 diff = rep->bandwidth;
6274 rep_new = gf_list_get(group->adaptation_set->representations, group->target_new_rep+1);
6275 diff = rep_new->bandwidth - diff;
6276
6277 if (dash->atsc_clock_state) {
6278 //if baseURL in period or adaptation set, we assume we are in broadband mode, otherwise we re in broadcast, don't count bitrate
6279 if (!gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs)) {
6280 //new rep is in broadcast, force diff to 0 to select the rep
6281 if (!gf_list_count(rep_new->base_URLs)) {
6282 diff = 0;
6283 }
6284 //new rep is in broadband, prev rep is in broadcast, diff is the new rep bandwidth
6285 else if (!gf_list_count(rep->base_URLs)) {
6286 diff = rep_new->bandwidth;
6287 }
6288 }
6289 }
6290
6291 if (test_pass) {
6292 cumulated_bw_in_pass+= diff;
6293 nb_rep_increased ++;
6294 } else if (min_bandwidth + diff < 8*total_rate) {
6295 min_bandwidth += diff;
6296 nb_rep_increased ++;
6297 bandwidths[q_idx] += diff;
6298 group->target_new_rep++;
6299 }
6300 }
6301 if (test_pass) {
6302 //all reps cannot be switched up in this quality level, do it
6303 if ( min_bandwidth + cumulated_bw_in_pass > 8*total_rate) {
6304 break;
6305 }
6306 }
6307 //no more adjustement possible for this quality level
6308 if (! nb_rep_increased)
6309 break;
6310
6311 test_pass = !test_pass;
6312 }
6313 }
6314
6315 if (! for_postponed_only) {
6316 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));
6317
6318 for (q_idx=0; q_idx<max_level; q_idx++) {
6319 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 ));
6320 }
6321 }
6322
6323 force_rep_idx = GF_TRUE;
6324 }
6325 /*for each quality level get tile_rate_decrease% of the available bandwidth*/
6326 else {
6327 u32 rate = bandwidths[0] = total_rate * dash->tile_rate_decrease / 100;
6328 for (i=1; i<max_level; i++) {
6329 u32 remain = total_rate - rate;
6330 if (i+1==max_level) {
6331 bandwidths[i] = remain;
6332 } else {
6333 bandwidths[i] = remain * dash->tile_rate_decrease/100;
6334 rate += bandwidths[i];
6335 }
6336 }
6337
6338 if (! for_postponed_only) {
6339 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));
6340 for (q_idx=0; q_idx<max_level; q_idx++) {
6341 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 ));
6342 }
6343 }
6344 }
6345
6346 //GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DEBUG. 2. dowload at %d \n", 8*bandwidths[q_idx]/1000));
6347 //bandwitdh sharing done, perform rate adaptation with theses new numbers
6348 for (i=0; i<count; i++) {
6349 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6350 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
6351 if (group->done) continue;
6352
6353 if (force_rep_idx) {
6354 rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
6355 //add 100 bytes/sec to make sure we select the target one
6356 group->bytes_per_sec = 100 + rep->bandwidth / 8;
6357 }
6358 //decrease by quality level
6359 else if (dash->tile_rate_decrease) {
6360 quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
6361 if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
6362 assert(groups_per_quality[quality_rank]);
6363 group->bytes_per_sec = bandwidths[quality_rank] / groups_per_quality[quality_rank];
6364 }
6365
6366 if (for_postponed_only) {
6367 if (group->rate_adaptation_postponed)
6368 dash_do_rate_adaptation(dash, group);
6369 } else {
6370 dash_do_rate_adaptation(dash, group);
6371 }
6372 group->bytes_per_sec = group->backup_Bps;
6373 }
6374 }
6375
6376
dash_download_threaded(void * par)6377 static u32 dash_download_threaded(void *par)
6378 {
6379 GF_DASH_Group *group = (GF_DASH_Group *) par;
6380 if (!group) return 0;
6381 group->download_th_done = GF_FALSE;
6382
6383 while (1) {
6384 DownloadGroupStatus res = dash_download_group(group->dash, group, group, group->groups_depending_on ? GF_TRUE : GF_FALSE);
6385 if (res==GF_DASH_DownloadRestart) {
6386 continue;
6387 }
6388 break;
6389 }
6390 group->download_th_done = GF_TRUE;
6391 return 0;
6392 }
6393
6394
dash_setup_period_and_groups(GF_DashClient * dash)6395 static GF_Err dash_setup_period_and_groups(GF_DashClient *dash)
6396 {
6397 u32 i, group_count;
6398 GF_Err e;
6399
6400 //don't resetup the entire period, only the broken group(s) ...
6401 if (!dash->period_groups_setup) {
6402 /*setup period*/
6403 e = gf_dash_setup_period(dash);
6404 if (e) {
6405 //move to stop state before sending the error event otherwise we might deadlock when disconnecting the dash client
6406 dash->dash_state = GF_DASH_STATE_STOPPED;
6407 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_PERIOD_SETUP_ERROR, -1, e);
6408 return e;
6409 }
6410 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SELECT_GROUPS, -1, GF_OK);
6411
6412 dash->period_groups_setup = GF_TRUE;
6413 dash->all_groups_done_notified = GF_FALSE;
6414 }
6415
6416 e = GF_OK;
6417 group_count = gf_list_count(dash->groups);
6418 for (i=0; i<group_count; i++) {
6419 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6420 if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE)
6421 continue;
6422
6423 if (group->group_setup) continue;
6424
6425 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
6426 e = gf_dash_download_init_segment(dash, group);
6427
6428 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
6429 //might happen with broadcast DASH (eg ATSC3)
6430 if (e == GF_IP_NETWORK_EMPTY) {
6431 if (dash->mpd_stop_request)
6432 return GF_OK;
6433
6434 if (dash->reinit_period_index) {
6435 gf_dash_reset_groups(dash);
6436 dash->active_period_index = dash->reinit_period_index-1;
6437 dash->reinit_period_index = 0;
6438 dash->period_groups_setup = GF_FALSE;
6439 return dash_setup_period_and_groups(dash);
6440 }
6441
6442 return e;
6443 }
6444 group->group_setup = GF_TRUE;
6445 if (e) break;
6446 }
6447 dash->initial_period_tunein = GF_FALSE;
6448
6449 /*if error signal to the user*/
6450 if (e != GF_OK) {
6451 //move to stop state before sending the error event otherwise we might deadlock when disconnecting the dash client
6452 dash->dash_state = GF_DASH_STATE_STOPPED;
6453 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_PERIOD_SETUP_ERROR, -1, e);
6454 return e;
6455 }
6456
6457 return GF_OK;
6458 }
6459
dash_do_groups(GF_DashClient * dash)6460 static void dash_do_groups(GF_DashClient *dash)
6461 {
6462 GF_Err e;
6463 u32 i, group_count = gf_list_count(dash->groups);
6464
6465 dash->min_wait_ms_before_next_request = 0;
6466
6467 /*for each selected groups*/
6468 for (i=0; i<group_count; i++) {
6469 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6470 if (group->selection != GF_DASH_GROUP_SELECTED) {
6471 if (group->nb_cached_segments && !group->dont_delete_first_segment) {
6472 gf_dash_group_reset(dash, group);
6473 }
6474 group->download_th_done = GF_TRUE;
6475 continue;
6476 }
6477
6478 if (group->depend_on_group) continue;
6479 //not yet scheduled for download
6480 if (group->rate_adaptation_postponed) {
6481 group->download_th_done = GF_TRUE;
6482 continue;
6483 }
6484
6485 if (dash->thread_mode == GF_DASH_THREAD_ALL) {
6486 group->download_th_done = GF_FALSE;
6487 e = gf_th_run(group->download_th, dash_download_threaded, group);
6488 if (e!=GF_OK) {
6489 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot launch download thread for AdaptationSet #%d - error %s\n", i+1, gf_error_to_string(e)));
6490 group->download_th_done = GF_TRUE;
6491 }
6492 } else {
6493 DownloadGroupStatus res;
6494 group->download_th_done = GF_FALSE;
6495 res = dash_download_group(dash, group, group, group->groups_depending_on ? GF_TRUE : GF_FALSE);
6496 if (res==GF_DASH_DownloadRestart) {
6497 i--;
6498 continue;
6499 }
6500 group->download_th_done = GF_TRUE;
6501 }
6502 }
6503
6504 while (dash->thread_mode == GF_DASH_THREAD_ALL) {
6505 Bool all_done = GF_TRUE;
6506 for (i=0; i<group_count; i++) {
6507 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6508 if (group->selection != GF_DASH_GROUP_SELECTED) {
6509 continue;
6510 }
6511 if (group->depend_on_group) continue;
6512 //not yet scheduled for download
6513 if (group->rate_adaptation_postponed) continue;
6514
6515 if (!group->download_th_done) {
6516 all_done = GF_FALSE;
6517 break;
6518 }
6519 }
6520 if (all_done)
6521 break;
6522
6523 if (dash->thread_mode)
6524 gf_sleep(1);
6525 }
6526 //in non threaded mode we need to wait for the stats
6527 if (dash->thread_mode)
6528 dash_global_rate_adaptation(dash, GF_FALSE);
6529 }
6530
dash_check_mpd_update_and_cache(GF_DashClient * dash,Bool * cache_is_full)6531 static GF_Err dash_check_mpd_update_and_cache(GF_DashClient *dash, Bool *cache_is_full)
6532 {
6533 GF_Err e = GF_OK;
6534 u32 i, group_count;
6535 u32 timer = gf_sys_clock() - dash->last_update_time;
6536 Bool has_postponed_rate_adaptation;
6537
6538 (*cache_is_full) = GF_TRUE;
6539 has_postponed_rate_adaptation = GF_FALSE;
6540
6541 group_count = gf_list_count(dash->groups);
6542 /*refresh MPD*/
6543 if (dash->force_mpd_update || (dash->mpd->minimum_update_period && (timer > dash->mpd->minimum_update_period))) {
6544 u32 diff = gf_sys_clock();
6545 if (dash->force_mpd_update || dash->mpd->minimum_update_period) {
6546 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] At %d Time to update the playlist (%u ms elapsed since last refresh and min reload rate is %u)\n", gf_sys_clock() , timer, dash->mpd->minimum_update_period));
6547 }
6548 dash->force_mpd_update = 0;
6549
6550 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
6551 e = gf_dash_update_manifest(dash);
6552 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6553
6554 diff = gf_sys_clock() - diff;
6555 if (e) {
6556 if (!dash->in_error) {
6557 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error updating MPD %s\n", gf_error_to_string(e)));
6558 }
6559 } else {
6560 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updated MPD in %d ms\n", diff));
6561 }
6562 } else {
6563 Bool all_groups_done = GF_TRUE;
6564 Bool cache_full = GF_TRUE;
6565
6566 has_postponed_rate_adaptation = GF_FALSE;
6567
6568 /*wait if nothing is ready to be downloaded*/
6569 if (dash->min_wait_ms_before_next_request > 1) {
6570 if (dash->thread_mode) {
6571 u32 sleep_for = MIN(dash->min_wait_ms_before_next_request/2, 1000);
6572 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] No segments available on the server until %d ms - going to sleep for %d ms\n", dash->min_wait_ms_before_next_request, sleep_for));
6573 gf_sleep(sleep_for);
6574 } else {
6575 if (gf_sys_clock() < dash->min_wait_sys_clock + dash->min_wait_ms_before_next_request) {
6576 return GF_EOS;
6577 }
6578 dash->min_wait_ms_before_next_request = 0;
6579 }
6580 }
6581
6582 /*check if cache is not full*/
6583 dash->tsb_exceeded = 0;
6584 dash->time_in_tsb = 0;
6585 for (i=0; i<group_count; i++) {
6586 GF_MPD_Type type = dash->mpd->type;
6587 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6588
6589 if (group->period->origin_base_url)
6590 type = group->period->type;
6591
6592 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
6593
6594 if ((group->selection != GF_DASH_GROUP_SELECTED)
6595 || group->depend_on_group
6596 || (group->done && !group->nb_cached_segments)
6597 ) {
6598 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
6599 continue;
6600 }
6601 all_groups_done = 0;
6602 if (type==GF_MPD_TYPE_DYNAMIC) {
6603 gf_dash_group_check_time(group);
6604 }
6605 //cache is full, notify client some segments are pending
6606 if ((group->nb_cached_segments == group->max_cached_segments)
6607 && !dash->request_period_switch
6608 && !group->has_pending_enhancement
6609 ) {
6610 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, i, GF_OK);
6611 }
6612 if (group->done && group->nb_cached_segments) {
6613 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, i, GF_OK);
6614 }
6615
6616 if (group->nb_cached_segments<group->max_cached_segments) {
6617 cache_full = 0;
6618 }
6619 if (group->rate_adaptation_postponed)
6620 has_postponed_rate_adaptation = GF_TRUE;
6621
6622 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
6623 if (!cache_full)
6624 break;
6625 }
6626
6627 if (dash->tsb_exceeded) {
6628 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_TIMESHIFT_OVERFLOW, (s32) dash->tsb_exceeded, GF_OK);
6629 dash->tsb_exceeded = 0;
6630 } else if (dash->time_in_tsb != dash->prev_time_in_tsb) {
6631 dash->prev_time_in_tsb = dash->time_in_tsb;
6632 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_TIMESHIFT_UPDATE, 0, GF_OK);
6633 }
6634
6635
6636 if (!cache_full) {
6637 (*cache_is_full) = GF_FALSE;
6638 } else {
6639 //seek request
6640 if (dash->request_period_switch==2) all_groups_done = 1;
6641
6642 if (all_groups_done && dash->next_period_checked) {
6643 dash->next_period_checked = 1;
6644 }
6645 if (all_groups_done && dash->request_period_switch) {
6646 gf_dash_reset_groups(dash);
6647 if (dash->request_period_switch == 1) {
6648 if (dash->speed<0) {
6649 if (dash->active_period_index) {
6650 dash->active_period_index--;
6651 }
6652 } else {
6653 dash->active_period_index++;
6654 }
6655 }
6656
6657 (*cache_is_full) = GF_FALSE;
6658 dash->request_period_switch = 0;
6659 dash->period_groups_setup = GF_FALSE;
6660 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Switching to period #%d\n", dash->active_period_index+1));
6661 dash->dash_state = GF_DASH_STATE_SETUP;
6662
6663 if (!dash->all_groups_done_notified) {
6664 dash->all_groups_done_notified = GF_TRUE;
6665 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_END_OF_PERIOD, 0, GF_OK);
6666 }
6667
6668 } else {
6669 (*cache_is_full) = GF_TRUE;
6670 return GF_OK;
6671 }
6672 }
6673 }
6674
6675 if (!dash->mpd_stop_request && has_postponed_rate_adaptation) {
6676 dash_global_rate_adaptation(dash, GF_TRUE);
6677 }
6678 return GF_OK;
6679 }
6680
gf_dash_process_internal(GF_DashClient * dash)6681 static GF_Err gf_dash_process_internal(GF_DashClient *dash)
6682 {
6683 GF_Err e;
6684 Bool cache_is_full;
6685 if (dash->mpd_stop_request) return GF_EOS;
6686
6687 switch (dash->dash_state) {
6688 case GF_DASH_STATE_SETUP:
6689 dash->in_period_setup = 1;
6690 e = dash_setup_period_and_groups(dash);
6691 if (e) return e;
6692
6693 dash->last_update_time = gf_sys_clock();
6694 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
6695 dash->dash_state = GF_DASH_STATE_CONNECTING;
6696 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6697 return GF_OK;
6698
6699 case GF_DASH_STATE_CONNECTING:
6700 /*ask the user to connect to desired groups*/
6701 e = dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CREATE_PLAYBACK, -1, GF_OK);
6702 if (e) return e;
6703 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
6704 dash->in_period_setup = 0;
6705 dash->dash_state = GF_DASH_STATE_RUNNING;
6706 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6707 dash->min_wait_ms_before_next_request = 0;
6708 return GF_OK;
6709 case GF_DASH_STATE_RUNNING:
6710 e = dash_check_mpd_update_and_cache(dash, &cache_is_full);
6711 if (e || cache_is_full) {
6712 return GF_OK;
6713 }
6714 if (dash->dash_state == GF_DASH_STATE_SETUP)
6715 return GF_OK;
6716
6717 dash->initial_period_tunein = GF_FALSE;
6718 dash_do_groups(dash);
6719 return GF_OK;
6720 case GF_DASH_STATE_STOPPED:
6721 return GF_EOS;
6722 }
6723 return GF_OK;
6724 }
6725
gf_dash_process(GF_DashClient * dash)6726 GF_Err gf_dash_process(GF_DashClient *dash)
6727 {
6728 if (dash->thread_mode) return GF_BAD_PARAM;
6729 return gf_dash_process_internal(dash);
6730 }
6731
dash_main_thread_proc(void * par)6732 static u32 dash_main_thread_proc(void *par)
6733 {
6734 GF_Err e;
6735 u32 ret = 0;
6736 Bool go_on = GF_TRUE;
6737 GF_DashClient *dash = (GF_DashClient*) par;
6738
6739 if (!dash)
6740 return 0;
6741
6742 if (!dash->mpd) {
6743 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Incorrect state, no dash->mpd for URL=%s, already stopped ?\n", dash->base_url));
6744 return 1;
6745 }
6746
6747 restart_period:
6748
6749 /* Setting the download status in exclusive code */
6750 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
6751 dash->dash_state = GF_DASH_STATE_SETUP;
6752 dash->period_groups_setup = GF_FALSE;
6753 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6754
6755 //period setup state
6756 e = gf_dash_process_internal(dash);
6757 if (e) {
6758 ret = 1;
6759 goto exit;
6760 }
6761
6762 //group creation
6763 e = gf_dash_process_internal(dash);
6764 if (e || dash->mpd_stop_request) {
6765 ret = 1;
6766 goto exit;
6767 }
6768
6769 while (go_on) {
6770 Bool cache_is_full = GF_FALSE;
6771
6772
6773 /*wait until next segment is needed*/
6774 while (!dash->mpd_stop_request) {
6775 e = dash_check_mpd_update_and_cache(dash, &cache_is_full);
6776 if (e || ! cache_is_full) break;
6777
6778 if (dash->dash_state == GF_DASH_STATE_SETUP)
6779 goto restart_period;
6780
6781 gf_sleep(30);
6782 }
6783
6784 /* stop the thread if requested */
6785 if (dash->mpd_stop_request) {
6786 break;
6787 }
6788 dash_do_groups(dash);
6789 }
6790
6791 exit:
6792 /* Signal that the download thread has ended */
6793 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
6794
6795 /*an error occured during playback chain creation and we couldn't release our plyayback chain in time, do it now*/
6796 if (dash->dash_state == GF_DASH_STATE_CONNECTING)
6797 gf_dash_reset_groups(dash);
6798
6799 dash->dash_state = GF_DASH_STATE_STOPPED;
6800 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6801 return ret;
6802 }
6803
gf_dash_period_index_from_time(GF_DashClient * dash,u64 time)6804 static u32 gf_dash_period_index_from_time(GF_DashClient *dash, u64 time)
6805 {
6806 u32 i, count, active_period_plus_one;
6807 u64 cumul_start = 0;
6808 Bool is_dyn = GF_FALSE;
6809 GF_MPD_Period *period;
6810
6811 if (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
6812 u64 now, availabilityStartTime;
6813 availabilityStartTime = dash->mpd->availabilityStartTime + dash->utc_shift;
6814 availabilityStartTime += dash->utc_drift_estimate;
6815
6816 now = dash->mpd_fetch_time + (gf_sys_clock() - dash->last_update_time) - availabilityStartTime;
6817 if (dash->initial_time_shift_value<=100) {
6818 now -= dash->mpd->time_shift_buffer_depth * dash->initial_time_shift_value / 100;
6819 } else {
6820 now -= dash->initial_time_shift_value;
6821 }
6822 if (!time) is_dyn = GF_TRUE;
6823 time += now;
6824 }
6825
6826
6827 restart:
6828 count = gf_list_count(dash->mpd->periods);
6829 cumul_start = 0;
6830 active_period_plus_one = 0;
6831 for (i = 0; i<count; i++) {
6832 period = gf_list_get(dash->mpd->periods, i);
6833
6834 if (period->xlink_href && !period->duration) {
6835 if (!active_period_plus_one || period->xlink_actuate_on_load) {
6836 gf_dash_solve_period_xlink(dash, dash->mpd->periods, i);
6837 goto restart;
6838 }
6839 }
6840
6841 if ((period->start > time) || (cumul_start > time)) {
6842 } else {
6843 cumul_start += period->duration;
6844
6845 if (!active_period_plus_one && (time < cumul_start)) {
6846 active_period_plus_one = i+1;
6847 }
6848 }
6849 }
6850 if (is_dyn) {
6851 for (i = 0; i<count; i++) {
6852 period = gf_list_get(dash->mpd->periods, i);
6853 if (!period->xlink_href && !period->origin_base_url) return i;
6854 }
6855 }
6856 return active_period_plus_one ? active_period_plus_one-1 : 0;
6857 }
6858
gf_dash_download_stop(GF_DashClient * dash)6859 static void gf_dash_download_stop(GF_DashClient *dash)
6860 {
6861 u32 i;
6862 assert(dash);
6863 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
6864 if (dash->groups) {
6865 for (i=0; i<gf_list_count(dash->groups); i++) {
6866 GF_DASH_Group *group = gf_list_get(dash->groups, i);
6867 assert(group);
6868 if ((group->selection == GF_DASH_GROUP_SELECTED) && group->segment_download) {
6869 dash->dash_io->abort(dash->dash_io, group->segment_download);
6870 gf_dash_mark_group_done(group);
6871 }
6872 }
6873 }
6874 if (!dash->thread_mode) {
6875 dash->mpd_stop_request = GF_TRUE;
6876 return;
6877 }
6878
6879 /* stop the download thread */
6880 dash->mpd_stop_request = GF_TRUE;
6881 if (dash->dash_state != GF_DASH_STATE_STOPPED) {
6882 dash->mpd_stop_request = 1;
6883 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6884 while (1) {
6885 /* waiting for the download thread to stop */
6886 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
6887 if (dash->dash_state == GF_DASH_STATE_STOPPED) {
6888 /* it's stopped we can continue */
6889 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6890 break;
6891 }
6892 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6893 }
6894 } else {
6895 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6896 }
6897 dash->mpd_stop_request = GF_TRUE;
6898 }
6899
6900
6901
gf_dash_seek_periods(GF_DashClient * dash,Double seek_time)6902 static Bool gf_dash_seek_periods(GF_DashClient *dash, Double seek_time)
6903 {
6904 Double start_time;
6905 /*we have an arch issue here: we may get a seek request in normal multiperiod playback
6906 with a seek time close but before to the active period, typically if the stream is shorter
6907 than the advertised period duration. We will use a safety check of 0.5 seconds, any seek request
6908 at Start(Pn) - 0.5 will resolve in seek at Start(Pn)
6909 * */
6910 Bool at_period_boundary=GF_FALSE;
6911 u32 i, period_idx;
6912 u32 nb_retry = 10;
6913 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
6914
6915 dash->start_range_period = 0;
6916 start_time = 0;
6917 period_idx = 0;
6918 for (i=0; i<gf_list_count(dash->mpd->periods); i++) {
6919 GF_MPD_Period *period = gf_list_get(dash->mpd->periods, i);
6920 Double dur;
6921
6922 if (period->xlink_href) {
6923 gf_dash_solve_period_xlink(dash, dash->mpd->periods, i);
6924 if (nb_retry) {
6925 nb_retry --;
6926 i--;
6927 continue;
6928 }
6929 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));
6930 gf_free(period->xlink_href);
6931 period->xlink_href = NULL;
6932 } else {
6933 nb_retry = 10;
6934 }
6935 dur = (Double)period->duration;
6936 dur /= 1000;
6937 if (seek_time + 0.5 >= start_time) {
6938 if ((i+1==gf_list_count(dash->mpd->periods)) || (seek_time + 0.5 < start_time + dur) ) {
6939 if (seek_time > start_time + dur) {
6940 at_period_boundary = GF_TRUE;
6941 }
6942 period_idx = i;
6943 break;
6944 }
6945 }
6946 start_time += dur;
6947 }
6948 if (period_idx != dash->active_period_index) {
6949 seek_time -= start_time;
6950 dash->active_period_index = period_idx;
6951 dash->request_period_switch = 2;
6952
6953 dash->start_range_period = seek_time;
6954 } else if (seek_time < start_time) {
6955 at_period_boundary = GF_TRUE;
6956 }
6957
6958 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
6959 if (at_period_boundary) return GF_TRUE;
6960 return dash->request_period_switch ? 1 : 0;
6961 }
6962
gf_dash_seek_group(GF_DashClient * dash,GF_DASH_Group * group,Double seek_to,Bool is_dynamic)6963 static void gf_dash_seek_group(GF_DashClient *dash, GF_DASH_Group *group, Double seek_to, Bool is_dynamic)
6964 {
6965 GF_Err e;
6966 u32 first_downloaded, last_downloaded, segment_idx, orig_idx;
6967
6968 if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) return;
6969
6970 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
6971
6972 group->force_segment_switch = 0;
6973 if (!is_dynamic) {
6974 /*figure out where to seek*/
6975 orig_idx = group->download_segment_index;
6976 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);
6977 if (e<0)
6978 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)));
6979
6980 group->download_segment_index = orig_idx;
6981
6982 /*remember to seek to given duration*/
6983 group->start_playback_range = seek_to;
6984
6985 first_downloaded = last_downloaded = group->download_segment_index;
6986 if (group->download_segment_index + 1 >= (s32) group->nb_cached_segments) {
6987 first_downloaded = group->download_segment_index + 1 - group->nb_cached_segments;
6988 }
6989 /*we are seeking in our download range, just go on*/
6990 if ((segment_idx>=first_downloaded) && (segment_idx<=last_downloaded)) {
6991 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
6992 return;
6993 }
6994
6995 group->force_segment_switch = 1;
6996 group->download_segment_index = segment_idx;
6997 } else {
6998 group->start_number_at_last_ast = 0;
6999 /*remember to adjust time in timeline steup*/
7000 group->start_playback_range = seek_to;
7001 group->timeline_setup = 0;
7002 }
7003
7004
7005 if (group->segment_download)
7006 dash->dash_io->abort(dash->dash_io, group->segment_download);
7007
7008 if (group->urlToDeleteNext) {
7009 if (!dash->keep_files && !group->local_files)
7010 dash->dash_io->delete_cache_file(dash->dash_io, group->segment_download, group->urlToDeleteNext);
7011
7012 gf_free(group->urlToDeleteNext);
7013 group->urlToDeleteNext = NULL;
7014 }
7015
7016 if (group->segment_download) {
7017 dash->dash_io->abort(dash->dash_io, group->segment_download);
7018 dash->dash_io->del(dash->dash_io, group->segment_download);
7019 group->segment_download = NULL;
7020 }
7021 while (group->nb_cached_segments) {
7022 group->nb_cached_segments--;
7023 if (!dash->keep_files && !group->local_files && !group->segment_must_be_streamed)
7024 gf_file_delete(group->cached[group->nb_cached_segments].cache);
7025
7026 gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
7027 }
7028 group->done = 0;
7029
7030 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
7031 }
7032
7033 GF_EXPORT
gf_dash_group_seek(GF_DashClient * dash,u32 group_idx,Double seek_to)7034 void gf_dash_group_seek(GF_DashClient *dash, u32 group_idx, Double seek_to)
7035 {
7036 GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
7037 if (!group) return;
7038 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
7039 gf_dash_seek_group(dash, group, seek_to, (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_TRUE : GF_FALSE);
7040 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
7041 }
7042
gf_dash_seek_groups(GF_DashClient * dash,Double seek_time,Bool is_dynamic)7043 static void gf_dash_seek_groups(GF_DashClient *dash, Double seek_time, Bool is_dynamic)
7044 {
7045 u32 i;
7046
7047 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
7048
7049 if (dash->active_period_index) {
7050 Double dur = 0;
7051 for (i=0; i<dash->active_period_index; i++) {
7052 GF_MPD_Period *period = gf_list_get(dash->mpd->periods, dash->active_period_index-1);
7053 dur += period->duration/1000.0;
7054 }
7055 seek_time -= dur;
7056 }
7057 for (i=0; i<gf_list_count(dash->groups); i++) {
7058 GF_DASH_Group *group = gf_list_get(dash->groups, i);
7059 gf_dash_seek_group(dash, group, seek_time, is_dynamic);
7060 }
7061
7062 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
7063 }
7064
7065
http_ifce_get(GF_FileDownload * getter,char * url)7066 static GF_Err http_ifce_get(GF_FileDownload *getter, char *url)
7067 {
7068 GF_Err e;
7069 GF_DASHFileIOSession *sess;
7070 GF_DashClient *dash = (GF_DashClient*) getter->udta;
7071 if (!getter->session) {
7072 if (!dash->mpd_dnload || (dash->thread_mode!=GF_DASH_THREAD_NONE)) {
7073 sess = dash->dash_io->create(dash->dash_io, 1, url, -1);
7074 if (!sess) return GF_IO_ERR;
7075 getter->session = sess;
7076 e = GF_OK;
7077 } else {
7078 sess = getter->session = dash->mpd_dnload;
7079 e = dash->dash_io->setup_from_url(dash->dash_io, getter->session, url, -1);
7080 }
7081 }
7082 else {
7083 u32 group_idx = -1, i;
7084 for (i = 0; i < gf_list_count(dash->groups); i++) {
7085 GF_DASH_Group *group = gf_list_get(dash->groups, i);
7086 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
7087 group_idx = i;
7088 break;
7089 }
7090 e = dash->dash_io->setup_from_url(dash->dash_io, getter->session, url, group_idx);
7091 if (e) {
7092 //with ATSC we may have 404 right away if nothing in cache yet, not an error
7093 GF_LOG(dash->atsc_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) ));
7094 return e;
7095 }
7096 sess = (GF_DASHFileIOSession *)getter->session;
7097 }
7098 if (!e)
7099 e = dash->dash_io->init(dash->dash_io, sess);
7100
7101 if (e) {
7102 dash->dash_io->del(dash->dash_io, sess);
7103 if (getter->session == sess)
7104 getter->session = NULL;
7105 return e;
7106 }
7107 return dash->dash_io->run(dash->dash_io, sess);
7108 }
7109
http_ifce_clean(GF_FileDownload * getter)7110 static void http_ifce_clean(GF_FileDownload *getter)
7111 {
7112 GF_DashClient *dash = (GF_DashClient*) getter->udta;
7113 if (getter->session) dash->dash_io->del(dash->dash_io, getter->session);
7114 getter->session = NULL;
7115 }
7116
http_ifce_cache_name(GF_FileDownload * getter)7117 static const char *http_ifce_cache_name(GF_FileDownload *getter)
7118 {
7119 GF_DashClient *dash = (GF_DashClient*) getter->udta;
7120 if (getter->session) return dash->dash_io->get_cache_name(dash->dash_io, getter->session);
7121 return NULL;
7122 }
7123
7124 GF_EXPORT
gf_dash_open(GF_DashClient * dash,const char * manifest_url)7125 GF_Err gf_dash_open(GF_DashClient *dash, const char *manifest_url)
7126 {
7127 char local_path[GF_MAX_PATH];
7128 const char *local_url;
7129 char *sep_cgi = NULL;
7130 char *sep_frag = NULL;
7131 GF_Err e;
7132 GF_MPD_Period *period;
7133 GF_DOMParser *mpd_parser=NULL;
7134 Bool is_local = GF_FALSE;
7135
7136 if (!dash || !manifest_url) return GF_BAD_PARAM;
7137
7138 memset( dash->lastMPDSignature, 0, GF_SHA1_DIGEST_SIZE);
7139 dash->reload_count = 0;
7140
7141 if (dash->base_url) gf_free(dash->base_url);
7142 sep_cgi = strrchr(manifest_url, '?');
7143 if (sep_cgi) sep_cgi[0] = 0;
7144 dash->base_url = gf_strdup(manifest_url);
7145 if (sep_cgi) sep_cgi[0] = '?';
7146
7147 dash->getter.udta = dash;
7148 dash->getter.new_session = http_ifce_get;
7149 dash->getter.del_session = http_ifce_clean;
7150 dash->getter.get_cache_name = http_ifce_cache_name;
7151 dash->getter.session = NULL;
7152
7153
7154
7155 if (dash->mpd_dnload) dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7156 dash->mpd_dnload = NULL;
7157 local_url = NULL;
7158
7159 if (!strnicmp(manifest_url, "file://", 7)) {
7160 local_url = manifest_url + 7;
7161 is_local = 1;
7162 if (strstr(manifest_url, ".m3u8")) {
7163 dash->is_m3u8 = GF_TRUE;
7164 }
7165 } else if (strstr(manifest_url, "://") && strncmp(manifest_url, "gfio://", 7)) {
7166 const char *reloc_url, *mtype;
7167 char mime[128];
7168 e = gf_dash_download_resource(dash, &(dash->mpd_dnload), manifest_url, 0, 0, 1, NULL);
7169 if (e!=GF_OK) {
7170 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));
7171 dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7172 dash->mpd_dnload = NULL;
7173 return e;
7174 }
7175
7176 mtype = dash->dash_io->get_mime(dash->dash_io, dash->mpd_dnload);
7177 strcpy(mime, mtype ? mtype : "");
7178 strlwr(mime);
7179
7180 reloc_url = dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload);
7181 /* Some servers, for instance http://tv.freebox.fr, serve m3u8 as text/plain */
7182 if (gf_dash_is_m3u8_mime(reloc_url, mime) || strstr(reloc_url, ".m3u8") || strstr(reloc_url, ".M3U8")) {
7183 dash->is_m3u8 = 1;
7184 } else if (gf_dash_is_smooth_mime(reloc_url, mime)) {
7185 dash->is_smooth = 1;
7186 } else if (!gf_dash_is_dash_mime(mime) && !strstr(reloc_url, ".mpd") && !strstr(reloc_url, ".MPD")) {
7187 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] mime '%s' for '%s' should be m3u8 or mpd\n", mime, reloc_url));
7188 dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7189 dash->mpd_dnload = NULL;
7190 return GF_REMOTE_SERVICE_ERROR;
7191 }
7192
7193 /*if relocated use new URL as base URL for all requests*/
7194 if (strcmp(reloc_url, manifest_url)) {
7195 gf_free(dash->base_url);
7196 dash->base_url = gf_strdup(reloc_url);
7197 }
7198
7199 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
7200 if (!local_url) {
7201 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: cache problem %s\n", local_url));
7202 dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7203 dash->mpd_dnload = NULL;
7204 return GF_IO_ERR;
7205 }
7206 } else {
7207 local_url = manifest_url;
7208 is_local = 1;
7209 if (strstr(manifest_url, ".m3u8"))
7210 dash->is_m3u8 = 1;
7211 }
7212
7213 if (is_local && strncmp(manifest_url, "gfio://", 7)) {
7214 FILE *f = gf_fopen(local_url, "rt");
7215 if (!f) {
7216 sep_cgi = strrchr(local_url, '?');
7217 if (sep_cgi) sep_cgi[0] = 0;
7218 sep_frag = strrchr(local_url, '#');
7219 if (sep_frag) sep_frag[0] = 0;
7220
7221 f = gf_fopen(local_url, "rt");
7222 if (!f) {
7223 if (sep_cgi) sep_cgi[0] = '?';
7224 if (sep_frag) sep_frag[0] = '#';
7225 return GF_URL_ERROR;
7226 }
7227 }
7228 gf_fclose(f);
7229 }
7230 dash->mpd_fetch_time = dash_get_fetch_time(dash);
7231
7232 gf_dash_reset_groups(dash);
7233
7234 if (dash->mpd)
7235 gf_mpd_del(dash->mpd);
7236
7237 dash->mpd = gf_mpd_new();
7238 if (!dash->mpd) {
7239 e = GF_OUT_OF_MEM;
7240 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD creation problem %s\n", gf_error_to_string(e)));
7241 goto exit;
7242 }
7243
7244 if (dash->is_m3u8) {
7245 if (is_local) {
7246 char *sep;
7247 strcpy(local_path, local_url);
7248 sep = gf_file_ext_start(local_path);
7249 if (sep) sep[0]=0;
7250 strcat(local_path, ".mpd");
7251
7252 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);
7253 } else {
7254 const char *redirected_url = dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload);
7255 if (!redirected_url) redirected_url=manifest_url;
7256
7257 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);
7258 }
7259 } else {
7260 u32 res = gf_dash_check_mpd_root_type(local_url);
7261 if (res==2) {
7262 dash->is_smooth = 1;
7263 }
7264 if (!dash->is_smooth && !res) {
7265 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: wrong file type %s\n", local_url));
7266 dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7267 dash->mpd_dnload = NULL;
7268 return GF_URL_ERROR;
7269 }
7270
7271 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] parsing MPD %s\n", local_url));
7272
7273 /* parse the MPD */
7274 mpd_parser = gf_xml_dom_new();
7275 e = gf_xml_dom_parse(mpd_parser, local_url, NULL, NULL);
7276
7277 if (sep_cgi) sep_cgi[0] = '?';
7278 if (sep_frag) sep_frag[0] = '#';
7279
7280 if (e != GF_OK) {
7281 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) ));
7282 gf_xml_dom_del(mpd_parser);
7283 dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7284 dash->mpd_dnload = NULL;
7285 return GF_URL_ERROR;
7286 }
7287
7288 if (dash->is_smooth) {
7289 e = gf_mpd_init_smooth_from_dom(gf_xml_dom_get_root(mpd_parser), dash->mpd, manifest_url);
7290 } else {
7291 e = gf_mpd_init_from_dom(gf_xml_dom_get_root(mpd_parser), dash->mpd, manifest_url);
7292 }
7293 gf_xml_dom_del(mpd_parser);
7294
7295 if (!e && dash->split_adaptation_set)
7296 gf_mpd_split_adaptation_sets(dash->mpd);
7297
7298 if (dash->ignore_xlink)
7299 dash_purge_xlink(dash->mpd);
7300
7301 if (!is_local) {
7302 const char *hdr = dash->dash_io->get_header_value(dash->dash_io, dash->mpd_dnload, "x-dash-atsc");
7303 if (hdr) {
7304 if (!dash->atsc_clock_state) {
7305 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Detected ATSC DASH service ID %s\n", hdr));
7306 dash->atsc_clock_state = 1;
7307 }
7308 }
7309 }
7310 }
7311
7312 if (e != GF_OK) {
7313 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD creation problem %s\n", gf_error_to_string(e)));
7314 goto exit;
7315 }
7316 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));
7317
7318 if (is_local && dash->mpd->minimum_update_period) {
7319 e = gf_dash_update_manifest(dash);
7320 if (e != GF_OK) {
7321 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update MPD: %s\n", gf_error_to_string(e)));
7322 goto exit;
7323 }
7324 }
7325
7326 /* Get the right period from the given time */
7327 dash->active_period_index = gf_dash_period_index_from_time(dash, 0);
7328 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
7329 if (period->xlink_href) {
7330 gf_dash_solve_period_xlink(dash, dash->mpd->periods, dash->active_period_index);
7331 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
7332 }
7333 if (!period || !gf_list_count(period->adaptation_sets) ) {
7334 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot start: not enough periods or representations in MPD\n"));
7335 e = GF_URL_ERROR;
7336 goto exit;
7337 }
7338
7339 dash->dash_state = GF_DASH_STATE_SETUP;
7340 dash->mpd_stop_request = 0;
7341 if (dash->thread_mode) {
7342 return gf_th_run(dash->dash_thread, dash_main_thread_proc, dash);
7343 } else {
7344 return GF_OK;
7345 }
7346
7347 exit:
7348 if (dash->dash_io) {
7349 dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7350 dash->mpd_dnload = NULL;
7351 }
7352
7353 if (dash->mpd)
7354 gf_mpd_del(dash->mpd);
7355 dash->mpd = NULL;
7356 return e;
7357 }
7358
7359 GF_EXPORT
gf_dash_close(GF_DashClient * dash)7360 void gf_dash_close(GF_DashClient *dash)
7361 {
7362 assert(dash);
7363
7364 if (dash->dash_io) {
7365 gf_dash_download_stop(dash);
7366 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
7367 if (dash->mpd_dnload) {
7368 dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
7369 dash->mpd_dnload = NULL;
7370 }
7371
7372 if (dash->getter.del_session)
7373 dash->getter.del_session(&dash->getter);
7374
7375 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
7376 }
7377 if (dash->mpd)
7378 gf_mpd_del(dash->mpd);
7379 dash->mpd = NULL;
7380
7381 if (dash->dash_state != GF_DASH_STATE_CONNECTING)
7382 gf_dash_reset_groups(dash);
7383 }
7384
7385 GF_EXPORT
gf_dash_set_algo(GF_DashClient * dash,GF_DASHAdaptationAlgorithm algo)7386 void gf_dash_set_algo(GF_DashClient *dash, GF_DASHAdaptationAlgorithm algo)
7387 {
7388 dash->adaptation_algorithm = algo;
7389 switch (dash->adaptation_algorithm) {
7390 case GF_DASH_ALGO_GPAC_LEGACY_BUFFER:
7391 dash->rate_adaptation_algo = dash_do_rate_adaptation_legacy_buffer;
7392 dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
7393 break;
7394 case GF_DASH_ALGO_GPAC_LEGACY_RATE:
7395 dash->rate_adaptation_algo = dash_do_rate_adaptation_legacy_rate;
7396 dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
7397 break;
7398 case GF_DASH_ALGO_BBA0:
7399 dash->rate_adaptation_algo = dash_do_rate_adaptation_bba0;
7400 dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
7401 break;
7402 case GF_DASH_ALGO_BOLA_FINITE:
7403 case GF_DASH_ALGO_BOLA_BASIC:
7404 case GF_DASH_ALGO_BOLA_U:
7405 case GF_DASH_ALGO_BOLA_O:
7406 dash->rate_adaptation_algo = dash_do_rate_adaptation_bola;
7407 dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
7408 break;
7409 case GF_DASH_ALGO_NONE:
7410 default:
7411 dash->rate_adaptation_algo = NULL;
7412 break;
7413 }
7414 }
7415
7416 GF_EXPORT
gf_dash_new(GF_DASHFileIO * dash_io,GF_DASHThreadMode thread_mode,u32 max_cache_duration,u32 auto_switch_count,Bool keep_files,Bool disable_switching,GF_DASHInitialSelectionMode first_select_mode,u32 initial_time_shift_percent)7417 GF_DashClient *gf_dash_new(GF_DASHFileIO *dash_io, GF_DASHThreadMode thread_mode, u32 max_cache_duration, u32 auto_switch_count, Bool keep_files, Bool disable_switching, GF_DASHInitialSelectionMode first_select_mode, u32 initial_time_shift_percent)
7418 {
7419 GF_DashClient *dash;
7420 if (!dash_io) {
7421 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot create client withou sync IO for HTTP\n"));
7422 return NULL;
7423 }
7424
7425 GF_SAFEALLOC(dash, GF_DashClient);
7426 if (!dash) return NULL;
7427 dash->dash_io = dash_io;
7428 dash->speed = 1.0;
7429 dash->is_rt_speed = GF_TRUE;
7430 dash->thread_mode = thread_mode;
7431 dash->low_latency_mode = GF_DASH_LL_STRICT;
7432
7433 //wait one segment to validate we have enough bandwidth
7434 dash->probe_times_before_switch = 1;
7435 if (dash->thread_mode) {
7436 dash->dash_thread = gf_th_new("DashClientMainThread");
7437 dash->dash_mutex = gf_mx_new("DashClientMainMutex");
7438 }
7439 //FIXME: mime type for segments MUST be mp2t, webvtt or a Packed Audio file (like AAC)
7440 dash->mimeTypeForM3U8Segments = gf_strdup( "video/mp2t" );
7441
7442 dash->max_cache_duration = max_cache_duration;
7443 dash->initial_time_shift_value = initial_time_shift_percent;
7444
7445 dash->auto_switch_count = auto_switch_count;
7446 dash->keep_files = keep_files;
7447 dash->disable_switching = disable_switching;
7448 dash->first_select_mode = first_select_mode;
7449 dash->min_timeout_between_404 = 500;
7450 dash->segment_lost_after_ms = 100;
7451 dash->debug_group_index = -1;
7452 dash->tile_rate_decrease = 100;
7453 dash->atsc_ast_shift = 1000;
7454 dash->initial_period_tunein = GF_TRUE;
7455 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Client created\n"));
7456
7457 #ifdef GPAC_ENABLE_COVERAGE
7458 if (gf_sys_is_cov_mode()) {
7459 on_group_download_error(NULL, NULL, NULL, GF_OK, NULL, NULL, NULL, GF_FALSE);
7460 dash_download_threaded(NULL);
7461 dash_main_thread_proc(NULL);
7462 gf_dash_is_running(dash);
7463 }
7464 #endif
7465 return dash;
7466 }
7467
7468 GF_EXPORT
gf_dash_del(GF_DashClient * dash)7469 void gf_dash_del(GF_DashClient *dash)
7470 {
7471 //force group cleanup
7472 dash->dash_state = GF_DASH_STATE_STOPPED;
7473 gf_dash_close(dash);
7474 if (dash->dash_thread)
7475 gf_th_del(dash->dash_thread);
7476 if (dash->dash_mutex) gf_mx_del(dash->dash_mutex);
7477
7478 if (dash->mimeTypeForM3U8Segments) gf_free(dash->mimeTypeForM3U8Segments);
7479 if (dash->base_url) gf_free(dash->base_url);
7480
7481 gf_free(dash);
7482 }
7483
7484 GF_EXPORT
gf_dash_enable_utc_drift_compensation(GF_DashClient * dash,Bool estimate_utc_drift)7485 void gf_dash_enable_utc_drift_compensation(GF_DashClient *dash, Bool estimate_utc_drift)
7486 {
7487 dash->estimate_utc_drift = estimate_utc_drift;
7488 }
7489
7490 GF_EXPORT
gf_dash_set_switching_probe_count(GF_DashClient * dash,u32 switch_probe_count)7491 void gf_dash_set_switching_probe_count(GF_DashClient *dash, u32 switch_probe_count)
7492 {
7493 dash->probe_times_before_switch = switch_probe_count;
7494 }
7495
7496 GF_EXPORT
gf_dash_set_agressive_adaptation(GF_DashClient * dash,Bool agressive_switch)7497 void gf_dash_set_agressive_adaptation(GF_DashClient *dash, Bool agressive_switch)
7498 {
7499 dash->agressive_switching = agressive_switch;
7500 }
7501
7502
7503 GF_EXPORT
gf_dash_get_group_count(GF_DashClient * dash)7504 u32 gf_dash_get_group_count(GF_DashClient *dash)
7505 {
7506 return gf_list_count(dash->groups);
7507 }
7508
7509
7510 GF_EXPORT
gf_dash_get_group_udta(GF_DashClient * dash,u32 idx)7511 void *gf_dash_get_group_udta(GF_DashClient *dash, u32 idx)
7512 {
7513 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7514 if (!group) return NULL;
7515 return group->udta;
7516 }
7517
7518 GF_EXPORT
gf_dash_set_group_udta(GF_DashClient * dash,u32 idx,void * udta)7519 GF_Err gf_dash_set_group_udta(GF_DashClient *dash, u32 idx, void *udta)
7520 {
7521 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7522 if (!group) return GF_BAD_PARAM;
7523 group->udta = udta;
7524 return GF_OK;
7525 }
7526
7527 GF_EXPORT
gf_dash_is_group_selected(GF_DashClient * dash,u32 idx)7528 Bool gf_dash_is_group_selected(GF_DashClient *dash, u32 idx)
7529 {
7530 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7531 if (!group) return 0;
7532 return (group->selection == GF_DASH_GROUP_SELECTED) ? 1 : 0;
7533 }
7534
7535 GF_EXPORT
gf_dash_is_group_selectable(GF_DashClient * dash,u32 idx)7536 Bool gf_dash_is_group_selectable(GF_DashClient *dash, u32 idx)
7537 {
7538 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7539 if (!group) return 0;
7540 return (group->selection == GF_DASH_GROUP_NOT_SELECTABLE) ? 0 : 1;
7541 }
7542
7543 GF_EXPORT
gf_dash_get_info(GF_DashClient * dash,const char ** title,const char ** source)7544 void gf_dash_get_info(GF_DashClient *dash, const char **title, const char **source)
7545 {
7546 GF_MPD_ProgramInfo *info = dash ? gf_list_get(dash->mpd->program_infos, 0) : NULL;
7547 if (title) *title = info ? info->title : NULL;
7548 if (source) *source = info ? info->source : NULL;
7549 }
7550
7551
7552 GF_EXPORT
gf_dash_switch_quality(GF_DashClient * dash,Bool switch_up,Bool immediate_switch)7553 void gf_dash_switch_quality(GF_DashClient *dash, Bool switch_up, Bool immediate_switch)
7554 {
7555 u32 i;
7556 for (i=0; i<gf_list_count(dash->groups); i++) {
7557 u32 switch_to_rep_idx = 0;
7558 u32 bandwidth, quality, k;
7559 GF_MPD_Representation *rep, *active_rep;
7560 GF_DASH_Group *group = gf_list_get(dash->groups, i);
7561 u32 current_idx = group->active_rep_index;
7562 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
7563
7564 if (group->base_rep_index_plus_one) current_idx = group->max_complementary_rep_index;
7565 if (group->force_representation_idx_plus_one) current_idx = group->force_representation_idx_plus_one - 1;
7566
7567 active_rep = gf_list_get(group->adaptation_set->representations, current_idx);
7568 if (!active_rep) continue;
7569 bandwidth = switch_up ? (u32) -1 : 0;
7570 quality = switch_up ? (u32) -1 : 0;
7571
7572 for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
7573 rep = gf_list_get(group->adaptation_set->representations, k);
7574 if (switch_up) {
7575 if ((rep->quality_ranking>active_rep->quality_ranking) || (rep->bandwidth>active_rep->bandwidth)) {
7576 if ((rep->quality_ranking < quality) || (rep->bandwidth < bandwidth)) {
7577 bandwidth = rep->bandwidth;
7578 quality = rep->quality_ranking;
7579 switch_to_rep_idx = k+1;
7580 }
7581 }
7582 } else {
7583 if ((rep->quality_ranking < active_rep->quality_ranking) || (rep->bandwidth < active_rep->bandwidth)) {
7584 if ((rep->quality_ranking > quality) || (rep->bandwidth > bandwidth)) {
7585 bandwidth = rep->bandwidth;
7586 quality = rep->quality_ranking;
7587 switch_to_rep_idx = k+1;
7588 }
7589 }
7590 }
7591 }
7592 if (switch_to_rep_idx && (switch_to_rep_idx-1 != current_idx) ) {
7593 u32 nb_cached_seg_per_rep = group->max_cached_segments / gf_dash_group_count_rep_needed(group);
7594
7595 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
7596
7597 group->force_switch_bandwidth = 1;
7598 if (!group->base_rep_index_plus_one)
7599 group->force_representation_idx_plus_one = switch_to_rep_idx;
7600 else
7601 group->max_complementary_rep_index = switch_to_rep_idx-1;
7602
7603
7604 if (group->local_files || immediate_switch) {
7605 u32 keep_seg_index = 0;
7606 //keep all scalable enhancements of the first segment
7607 rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
7608 if (rep->playback.enhancement_rep_index_plus_one) {
7609 u32 rep_idx = rep->playback.enhancement_rep_index_plus_one;
7610 while (keep_seg_index + 1 < group->nb_cached_segments) {
7611 rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index+1].representation_index);
7612 if (rep_idx == group->cached[keep_seg_index+1].representation_index+1) {
7613 keep_seg_index ++;
7614 rep_idx = rep->playback.enhancement_rep_index_plus_one;
7615 }
7616 else
7617 break;
7618 }
7619 }
7620
7621 if (!group->base_rep_index_plus_one) {
7622 /*in local playback just switch at the end of the current segment
7623 for remote, we should let the user decide*/
7624 while (group->nb_cached_segments > keep_seg_index + 1) {
7625 group->nb_cached_segments--;
7626 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));
7627
7628 if (!group->local_files && group->cached[group->nb_cached_segments].cache) {
7629 gf_file_delete( group->cached[group->nb_cached_segments].cache );
7630 }
7631 gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
7632
7633 group->cached[group->nb_cached_segments].duration = (u32) group->current_downloaded_segment_duration;
7634 if (group->download_segment_index>1)
7635 group->download_segment_index--;
7636 }
7637 } else {
7638 if (switch_up) {
7639 //first, we keep the second segment and remove all segments from the third one
7640 keep_seg_index++;
7641 rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index].representation_index);
7642 if (rep->playback.enhancement_rep_index_plus_one) {
7643 u32 rep_idx = rep->playback.enhancement_rep_index_plus_one;
7644 while (keep_seg_index + 1 < group->nb_cached_segments) {
7645 rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index+1].representation_index);
7646 if (rep_idx == group->cached[keep_seg_index+1].representation_index+1) {
7647 keep_seg_index ++;
7648 rep_idx = rep->playback.enhancement_rep_index_plus_one;
7649 }
7650 else
7651 break;
7652 }
7653 }
7654 while (group->nb_cached_segments > keep_seg_index + 1) {
7655 Bool decrease_download_segment_index = (group->cached[group->nb_cached_segments-1].representation_index == current_idx) ? GF_TRUE : GF_FALSE;
7656 group->nb_cached_segments--;
7657 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));
7658
7659 if (!group->local_files && group->cached[group->nb_cached_segments].cache) {
7660 gf_file_delete( group->cached[group->nb_cached_segments].cache );
7661 }
7662
7663 gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
7664
7665 group->cached[group->nb_cached_segments].duration = (u32) group->current_downloaded_segment_duration;
7666
7667 if (decrease_download_segment_index && group->download_segment_index>1)
7668 group->download_segment_index--;
7669 }
7670 /*force to download scalable enhancement of the second segment*/
7671 group->force_representation_idx_plus_one = switch_to_rep_idx;
7672 group->active_rep_index = switch_to_rep_idx - 1;
7673 group->download_segment_index--;
7674 }
7675 else if (group->nb_cached_segments) {
7676 /* we remove highest scalable enhancements of the dowloaded segments, and keep another segments*/
7677 for (k = group->nb_cached_segments - 1; k > keep_seg_index; k--) {
7678 if (group->cached[k].representation_index != current_idx)
7679 continue;
7680 group->nb_cached_segments--;
7681 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group %d switching quality - delete cached segment: %s\n", i, group->cached[k].url));
7682 if (k != group->nb_cached_segments) {
7683 memmove(&group->cached[k], &group->cached[k+1], (group->nb_cached_segments-k)*sizeof(segment_cache_entry));
7684 }
7685 memset(&group->cached[group->nb_cached_segments], 0, sizeof(segment_cache_entry));
7686 }
7687 }
7688 }
7689 }
7690 /*resize max cached segment*/
7691 group->max_cached_segments = nb_cached_seg_per_rep * gf_dash_group_count_rep_needed(group);
7692
7693 if (group->srd_desc)
7694 gf_dash_set_tiles_quality(dash, group->srd_desc);
7695
7696 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
7697 }
7698 }
7699 }
7700
7701 GF_EXPORT
gf_dash_get_duration(GF_DashClient * dash)7702 Double gf_dash_get_duration(GF_DashClient *dash)
7703 {
7704 return gf_mpd_get_duration(dash->mpd);
7705 }
7706
7707 GF_EXPORT
gf_dash_group_get_time_shift_buffer_depth(GF_DashClient * dash,u32 idx)7708 u32 gf_dash_group_get_time_shift_buffer_depth(GF_DashClient *dash, u32 idx)
7709 {
7710 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7711 if (!group) return 0;
7712 return group->time_shift_buffer_depth;
7713 }
7714
7715 GF_EXPORT
gf_dash_get_url(GF_DashClient * dash)7716 const char *gf_dash_get_url(GF_DashClient *dash)
7717 {
7718 return dash->base_url;
7719 }
7720
7721 GF_EXPORT
gf_dash_is_m3u8(GF_DashClient * dash)7722 Bool gf_dash_is_m3u8(GF_DashClient *dash) {
7723 return dash->is_m3u8;
7724 }
7725
7726 GF_EXPORT
gf_dash_group_get_segment_mime(GF_DashClient * dash,u32 idx)7727 const char *gf_dash_group_get_segment_mime(GF_DashClient *dash, u32 idx)
7728 {
7729 GF_MPD_Representation *rep;
7730 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7731 if (!group) return NULL;
7732
7733 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
7734 return gf_dash_get_mime_type(NULL, rep, group->adaptation_set);
7735 }
7736
7737
7738
7739 GF_EXPORT
gf_dash_group_get_segment_init_url(GF_DashClient * dash,u32 idx,u64 * start_range,u64 * end_range)7740 const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 idx, u64 *start_range, u64 *end_range)
7741 {
7742 GF_MPD_Representation *rep;
7743 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7744 if (!group) return NULL;
7745
7746 /*solve dependencies if any - we first test highest: if this is a complementary rep, keep the highest for init
7747 otherwise use the selected one
7748 this is need for scalable because we only init once the demuxer*/
7749
7750 rep = gf_list_last(group->adaptation_set->representations);
7751 if (!rep->dependency_id)
7752 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
7753
7754 if (group->bs_switching_init_segment_url) {
7755 if (start_range) *start_range = group->bs_switching_init_segment_url_start_range;
7756 if (end_range) *end_range = group->bs_switching_init_segment_url_end_range;
7757 return group->bs_switching_init_segment_url;
7758 }
7759
7760 //no rep found or no init for the rep, check all reps
7761 //this may happen when the adaptation rate algo changed the rep between the init (TS) and the next segment
7762 if (!rep || !rep->playback.cached_init_segment_url) {
7763 u32 i, count;
7764 count = gf_list_count(group->adaptation_set->representations);
7765 for (i=0; i<count; i++) {
7766 rep = gf_list_get(group->adaptation_set->representations, i);
7767 if (rep->playback.cached_init_segment_url) break;
7768 rep = NULL;
7769 }
7770 }
7771 if (!rep) return NULL;
7772 if (start_range) *start_range = rep->playback.init_start_range;
7773 if (end_range) *end_range = rep->playback.init_end_range;
7774 return rep->playback.cached_init_segment_url;
7775 }
7776
7777 GF_EXPORT
gf_dash_group_get_segment_init_keys(GF_DashClient * dash,u32 idx,bin128 * key_IV)7778 const char *gf_dash_group_get_segment_init_keys(GF_DashClient *dash, u32 idx, bin128 *key_IV)
7779 {
7780 GF_MPD_Representation *rep;
7781 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7782 if (!group) return NULL;
7783
7784 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
7785 if (!rep) return NULL;
7786
7787 if (key_IV) memcpy(*key_IV, rep->playback.key_IV, sizeof(bin128));
7788 return rep->playback.key_url;
7789 }
7790
7791 GF_EXPORT
gf_dash_group_select(GF_DashClient * dash,u32 idx,Bool select)7792 void gf_dash_group_select(GF_DashClient *dash, u32 idx, Bool select)
7793 {
7794 Bool needs_resetup = GF_FALSE;
7795 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
7796 if (!group) return;
7797 if (group->selection == GF_DASH_GROUP_NOT_SELECTABLE)
7798 return;
7799
7800 if ((group->selection==GF_DASH_GROUP_NOT_SELECTED) && select) needs_resetup = 1;
7801
7802 group->selection = select ? GF_DASH_GROUP_SELECTED : GF_DASH_GROUP_NOT_SELECTED;
7803 /*this set is part of a group, make sure no all other sets from the indicated group are unselected*/
7804 if (select && (group->adaptation_set->group>=0)) {
7805 u32 i;
7806 for (i=0; i<gf_dash_get_group_count(dash); i++) {
7807 GF_DASH_Group *agroup = gf_list_get(dash->groups, i);
7808 if (agroup==group) continue;
7809
7810 /*either one Representation from group 0, if present,*/
7811 if ((group->adaptation_set->group==0)
7812 /*or the combination of at most one Representation from each non-zero group*/
7813 || (group->adaptation_set->group==agroup->adaptation_set->group)
7814 ) {
7815 agroup->selection = GF_DASH_GROUP_NOT_SELECTED;
7816 }
7817 }
7818 }
7819 //TODO: recompute grop download index based on current playback ...
7820 if (needs_resetup) {
7821
7822 }
7823 }
7824
7825 GF_EXPORT
gf_dash_groups_set_language(GF_DashClient * dash,const char * lang_code_rfc_5646)7826 void gf_dash_groups_set_language(GF_DashClient *dash, const char *lang_code_rfc_5646)
7827 {
7828 u32 i, len;
7829 s32 lang_idx;
7830 GF_List *groups_selected;
7831 if (!lang_code_rfc_5646) return;
7832
7833 groups_selected = gf_list_new();
7834
7835 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
7836
7837 //first pass, check exact match
7838 for (i=0; i<gf_list_count(dash->groups); i++) {
7839 GF_DASH_Group *group = gf_list_get(dash->groups, i);
7840 if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) continue;
7841 if (!group->adaptation_set->lang) continue;
7842
7843 if (!stricmp(group->adaptation_set->lang, lang_code_rfc_5646)) {
7844 gf_dash_group_select(dash, i, 1);
7845 gf_list_add(groups_selected, group);
7846 }
7847 }
7848
7849 lang_idx = gf_lang_find(lang_code_rfc_5646);
7850 if (lang_idx>=0) {
7851 const char *n2cc = gf_lang_get_2cc(lang_idx);
7852 const char *n3cc = gf_lang_get_3cc(lang_idx);
7853
7854 for (i=0; i<gf_list_count(dash->groups); i++) {
7855 char *sep;
7856 GF_DASH_Group *group = gf_list_get(dash->groups, i);
7857 if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) continue;
7858 if (!group->adaptation_set->lang) continue;
7859 if (gf_list_find(groups_selected, group) >= 0) continue;
7860
7861 //check we didn't select one AS in this group in the previous pass or in this pass
7862 if (group->adaptation_set->group>=0) {
7863 u32 k;
7864 Bool found = GF_FALSE;
7865 for (k=0; k<gf_list_count(groups_selected); k++) {
7866 GF_DASH_Group *ag = gf_list_get(groups_selected, k);
7867
7868 if (ag->adaptation_set->group == group->adaptation_set->group) {
7869 found = 1;
7870 break;
7871 }
7872 }
7873 if (found) continue;
7874 }
7875 //get the 2 or 3 land code
7876 sep = strchr(group->adaptation_set->lang, '-');
7877 if (sep) {
7878 sep[0] = 0;
7879 }
7880 len = (u32) strlen(group->adaptation_set->lang);
7881 //compare with what we found
7882 if ( ((len==3) && !stricmp(group->adaptation_set->lang, n3cc))
7883 || ((len==2) && !stricmp(group->adaptation_set->lang, n2cc))
7884 ) {
7885 gf_dash_group_select(dash, i, 1);
7886 gf_list_add(groups_selected, group);
7887 }
7888
7889 if (sep) sep[0] = '-';
7890 }
7891 }
7892
7893 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
7894
7895 gf_list_del(groups_selected);
7896 }
7897
7898 GF_EXPORT
gf_dash_is_running(GF_DashClient * dash)7899 Bool gf_dash_is_running(GF_DashClient *dash)
7900 {
7901 return (dash->dash_state==GF_DASH_STATE_STOPPED) ? GF_FALSE : GF_TRUE;
7902 }
7903
7904 GF_EXPORT
gf_dash_is_in_setup(GF_DashClient * dash)7905 Bool gf_dash_is_in_setup(GF_DashClient *dash)
7906 {
7907 if (dash->dash_state==GF_DASH_STATE_SETUP) return GF_TRUE;
7908 if (dash->dash_state==GF_DASH_STATE_CONNECTING) return GF_TRUE;
7909 if (dash->request_period_switch) return GF_TRUE;
7910 return GF_FALSE;
7911 }
7912
7913 GF_EXPORT
gf_dash_get_period_switch_status(GF_DashClient * dash)7914 u32 gf_dash_get_period_switch_status(GF_DashClient *dash)
7915 {
7916 return dash->request_period_switch;
7917 }
7918 GF_EXPORT
gf_dash_request_period_switch(GF_DashClient * dash)7919 void gf_dash_request_period_switch(GF_DashClient *dash)
7920 {
7921 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Period switch has been requested\n"));
7922 dash->request_period_switch = 1;
7923 }
7924 GF_EXPORT
gf_dash_in_last_period(GF_DashClient * dash,Bool check_eos)7925 Bool gf_dash_in_last_period(GF_DashClient *dash, Bool check_eos)
7926 {
7927 Bool res;
7928
7929 switch (dash->dash_state) {
7930 case GF_DASH_STATE_SETUP:
7931 case GF_DASH_STATE_CONNECTING:
7932 return GF_FALSE;
7933 default:
7934 break;
7935 }
7936
7937 res = (dash->active_period_index+1 < gf_list_count(dash->mpd->periods)) ? 0 : 1;
7938 //this code seems buggy, commented for now
7939 #if 0
7940 if (res && dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
7941 GF_MPD_Period*period = gf_list_last(dash->mpd->periods);
7942 //consider we are
7943 if (!period->duration || dash->mpd->media_presentation_duration) res = GF_FALSE;
7944 }
7945 #endif
7946 return res;
7947 }
7948 GF_EXPORT
gf_dash_in_period_setup(GF_DashClient * dash)7949 Bool gf_dash_in_period_setup(GF_DashClient *dash)
7950 {
7951 return dash->in_period_setup;
7952 }
7953
7954 GF_EXPORT
gf_dash_set_speed(GF_DashClient * dash,Double speed)7955 void gf_dash_set_speed(GF_DashClient *dash, Double speed)
7956 {
7957 u32 i;
7958 if (!dash) return;
7959 if (!speed) speed = 1.0;
7960 if (dash->speed == speed) return;
7961
7962 for (i=0; i<gf_list_count(dash->groups); i++) {
7963 GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, i);
7964 GF_MPD_Representation *active_rep;
7965 Double max_available_speed;
7966 if (!group || (group->selection != GF_DASH_GROUP_SELECTED)) continue;
7967 active_rep = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, group->active_rep_index);
7968 if (speed < 0)
7969 group->decode_only_rap = GF_TRUE;
7970
7971 max_available_speed = gf_dash_get_max_available_speed(dash, group, active_rep);
7972
7973 /*verify if this representation support this speed*/
7974 if (!max_available_speed || (ABS(speed) <= max_available_speed)) {
7975 //nothing to do
7976 } else {
7977 /*if the representation does not support this speed, search for another which support it*/
7978 u32 switch_to_rep_idx = 0;
7979 u32 bandwidth = 0, quality = 0, k;
7980 GF_MPD_Representation *rep;
7981 for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
7982 rep = gf_list_get(group->adaptation_set->representations, k);
7983 if ((ABS(speed) <= rep->max_playout_rate) && ((rep->quality_ranking > quality) || (rep->bandwidth > bandwidth))) {
7984 bandwidth = rep->bandwidth;
7985 quality = rep->quality_ranking;
7986 switch_to_rep_idx = k+1;
7987 }
7988 }
7989 if (switch_to_rep_idx) {
7990 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Switching representation for adapting playing speed\n"));
7991 group->force_switch_bandwidth = 1;
7992 group->force_representation_idx_plus_one = switch_to_rep_idx;
7993 }
7994 }
7995 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing at %f speed \n", speed));
7996 dash->speed = speed;
7997 dash->is_rt_speed = (ABS(speed - 1.0)<0.1) ? GF_TRUE : GF_FALSE;
7998
7999 }
8000 }
8001
8002
8003 GF_EXPORT
gf_dash_group_get_max_segments_in_cache(GF_DashClient * dash,u32 idx)8004 u32 gf_dash_group_get_max_segments_in_cache(GF_DashClient *dash, u32 idx)
8005 {
8006 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8007 if (group) return group->max_cached_segments;
8008 return 0;
8009 }
8010
8011
8012 GF_EXPORT
gf_dash_group_get_num_segments_ready(GF_DashClient * dash,u32 idx,Bool * group_is_done)8013 u32 gf_dash_group_get_num_segments_ready(GF_DashClient *dash, u32 idx, Bool *group_is_done)
8014 {
8015 u32 res = 0;
8016 GF_DASH_Group *group;
8017
8018 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
8019 group = gf_list_get(dash->groups, idx);
8020 if (!group) {
8021 *group_is_done = 1;
8022 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8023 return 0;
8024 }
8025 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
8026
8027 *group_is_done = group->done;
8028 res = group->nb_cached_segments;
8029
8030 if (group->buffering) {
8031 res = 0;
8032 }
8033
8034 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
8035 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8036 return res;
8037 }
8038
8039 GF_EXPORT
gf_dash_group_discard_segment(GF_DashClient * dash,u32 idx)8040 void gf_dash_group_discard_segment(GF_DashClient *dash, u32 idx)
8041 {
8042 GF_DASH_Group *group;
8043 Bool delete_next;
8044
8045 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
8046 group = gf_list_get(dash->groups, idx);
8047 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
8048
8049 discard_segment:
8050 if (!group->nb_cached_segments) {
8051 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
8052 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8053 return;
8054 }
8055 delete_next = group->cached[0].has_dep_following ? GF_TRUE : GF_FALSE;
8056
8057 if (group->cached[0].cache) {
8058 if (group->urlToDeleteNext) {
8059 if (!group->local_files && !dash->keep_files && strncmp(group->urlToDeleteNext, "gmem://", 7) )
8060 dash->dash_io->delete_cache_file(dash->dash_io, group->segment_download, group->urlToDeleteNext);
8061
8062 gf_free(group->urlToDeleteNext);
8063 group->urlToDeleteNext = NULL;
8064 }
8065 assert(group->cached[0].url);
8066
8067 if (group->dont_delete_first_segment) {
8068 group->dont_delete_first_segment = 0;
8069 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] deleting cache file %s : %s (kept in HTTP cache)\n", group->cached[0].url, group->cached[0].cache));
8070 } else {
8071 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] deleting cache file %s : %s\n", group->cached[0].url, group->cached[0].cache));
8072 group->urlToDeleteNext = gf_strdup( group->cached[0].url );
8073 }
8074
8075 //remember the representation index of the last segment
8076 group->prev_active_rep_index = group->cached[0].representation_index;
8077
8078 gf_dash_group_reset_cache_entry(&group->cached[0]);
8079 }
8080
8081 memmove(&group->cached[0], &group->cached[1], sizeof(segment_cache_entry)*(group->nb_cached_segments-1));
8082 memset(&(group->cached[group->nb_cached_segments-1]), 0, sizeof(segment_cache_entry));
8083 group->nb_cached_segments--;
8084
8085 if (delete_next) {
8086 goto discard_segment;
8087 }
8088
8089 /*if we have dependency representations, we need also discard them*/
8090 if (group->base_rep_index_plus_one) {
8091 if (group->cached[0].cache && (group->cached[0].representation_index != group->base_rep_index_plus_one-1))
8092 goto discard_segment;
8093 }
8094
8095 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
8096 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8097 }
8098
8099 GF_EXPORT
gf_dash_set_group_done(GF_DashClient * dash,u32 idx,Bool done)8100 void gf_dash_set_group_done(GF_DashClient *dash, u32 idx, Bool done)
8101 {
8102 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8103 if (group) {
8104 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
8105 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
8106 group->done = done;
8107 if (done && group->segment_download) {
8108 group->download_abort_type = 1;
8109 dash->dash_io->abort(dash->dash_io, group->segment_download);
8110 }
8111 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
8112 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8113 }
8114 }
8115
8116 GF_EXPORT
gf_dash_group_get_presentation_time_offset(GF_DashClient * dash,u32 idx,u64 * presentation_time_offset,u32 * timescale)8117 GF_Err gf_dash_group_get_presentation_time_offset(GF_DashClient *dash, u32 idx, u64 *presentation_time_offset, u32 *timescale)
8118 {
8119 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8120 if (group) {
8121 u64 duration;
8122 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8123 gf_mpd_resolve_segment_duration(rep, group->adaptation_set, group->period, &duration, timescale, presentation_time_offset, NULL);
8124 return GF_OK;
8125 }
8126 return GF_BAD_PARAM;
8127 }
8128
8129 GF_EXPORT
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)8130 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)
8131 {
8132 GF_DASH_Group *group;
8133 u32 index;
8134 Bool has_dep_following;
8135 *url = NULL;
8136 if (switching_url) *switching_url = NULL;
8137 if (start_range) *start_range = 0;
8138 if (end_range) *end_range = 0;
8139 if (switching_start_range) *switching_start_range = 0;
8140 if (switching_end_range) *switching_end_range = 0;
8141 if (original_url) *original_url = NULL;
8142 if (switching_index) *switching_index = -1;
8143 if (has_next_segment) *has_next_segment = GF_FALSE;
8144
8145 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
8146 group = gf_list_get(dash->groups, idx);
8147
8148 if (!group) {
8149 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8150 return GF_BAD_PARAM;
8151 }
8152
8153 if (group->cache_mutex) gf_mx_p(group->cache_mutex);
8154
8155 if (!group->nb_cached_segments) {
8156 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
8157 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8158 if (group->done) return GF_EOS;
8159 if ((dash->low_latency_mode==GF_DASH_LL_EARLY_FETCH)
8160 && group->is_low_latency
8161 && dash->is_rt_speed
8162 && !dash->time_in_tsb
8163 ) {
8164 group->force_early_fetch = GF_TRUE;
8165 dash->min_wait_ms_before_next_request = 1;
8166 }
8167 return GF_BUFFER_TOO_SMALL;
8168 }
8169
8170 /*check the dependent rep is in the cache and does not target next segment (next in time)*/
8171 has_dep_following = group->cached[0].has_dep_following;
8172 index = 0;
8173 while (dependent_representation_index) {
8174 GF_Err err = GF_OK;
8175
8176 if (has_dep_following) {
8177 if (index+1 >= group->nb_cached_segments)
8178 err = GF_BUFFER_TOO_SMALL;
8179 else if (! group->cached[index].has_dep_following)
8180 err = GF_BAD_PARAM;
8181 } else {
8182 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->cached[index].representation_index);
8183
8184 if (index+1 >= group->nb_cached_segments) err = GF_BUFFER_TOO_SMALL;
8185 else if (!rep->playback.enhancement_rep_index_plus_one) err = GF_BAD_PARAM;
8186 else if (rep->playback.enhancement_rep_index_plus_one != group->cached[index+1].representation_index + 1) err = GF_BAD_PARAM;
8187 }
8188
8189 if (err) {
8190 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8191 return err;
8192 }
8193 index ++;
8194 dependent_representation_index--;
8195 }
8196
8197 *url = group->cached[index].cache;
8198 if (start_range)
8199 *start_range = group->cached[index].start_range;
8200 if (end_range)
8201 *end_range = group->cached[index].end_range;
8202 if (original_url) *original_url = group->cached[index].url;
8203 if (key_url) *key_url = group->cached[index].key_url;
8204 if (key_IV) memcpy((*key_IV), group->cached[index].key_IV, sizeof(bin128));
8205
8206 if (!group->base_rep_index_plus_one && (group->cached[index].representation_index != group->prev_active_rep_index)) {
8207 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
8208 if (switching_index)
8209 *switching_index = group->cached[0].representation_index;
8210 if (switching_start_range)
8211 *switching_start_range = rep->playback.init_start_range;
8212 if (switching_end_range)
8213 *switching_end_range = rep->playback.init_end_range;
8214 if (switching_url)
8215 *switching_url = rep->playback.cached_init_segment_url;
8216 }
8217 group->force_segment_switch = 0;
8218
8219 if (group->cached[index].has_dep_following) {
8220 if (has_next_segment) *has_next_segment = GF_TRUE;
8221 } else if ((index+1<group->max_cached_segments) && group->cached[index+1].cache && group->adaptation_set) {
8222 GF_MPD_Representation *rep;
8223
8224 rep = gf_list_get(group->adaptation_set->representations, group->cached[index].representation_index);
8225 if (rep && (rep->playback.enhancement_rep_index_plus_one == group->cached[index+1].representation_index+1) ) {
8226 if (has_next_segment)
8227 *has_next_segment = GF_TRUE;
8228 }
8229 }
8230 if (group->cache_mutex) gf_mx_v(group->cache_mutex);
8231 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8232 return GF_OK;
8233 }
8234
8235 GF_EXPORT
gf_dash_group_probe_current_download_segment_location(GF_DashClient * dash,u32 idx,const char ** url,s32 * switching_index,const char ** switching_url,const char ** original_url,Bool * switched)8236 GF_Err gf_dash_group_probe_current_download_segment_location(GF_DashClient *dash, u32 idx, const char **url, s32 *switching_index, const char **switching_url, const char **original_url, Bool *switched)
8237 {
8238 GF_DASH_Group *group;
8239
8240 if (url) *url = NULL;
8241 if (switching_url) *switching_url = NULL;
8242 if (original_url) *original_url = NULL;
8243 if (switching_index) *switching_index = -1;
8244
8245 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
8246 group = gf_list_get(dash->groups, idx);
8247 if (!group) {
8248 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8249 return GF_BAD_PARAM;
8250 }
8251
8252 if (!group->is_downloading) {
8253 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8254 return GF_OK;
8255 }
8256
8257 *switched = GF_FALSE;
8258 if (group->download_abort_type==2) {
8259 group->download_abort_type = 0;
8260 *switched = GF_TRUE;
8261 }
8262
8263 //no download yet
8264 if ( ! dash->dash_io->get_bytes_done(dash->dash_io, group->segment_download)) {
8265 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8266 return GF_OK;
8267 }
8268
8269 if (url) *url = dash->dash_io->get_cache_name(dash->dash_io, group->segment_download);
8270 if (original_url) *original_url = dash->dash_io->get_url(dash->dash_io, group->segment_download);
8271
8272 if (group->active_rep_index != group->prev_active_rep_index) {
8273 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
8274 if (switching_index)
8275 *switching_index = group->active_rep_index;
8276 if (switching_url)
8277 *switching_url = rep->playback.cached_init_segment_url;
8278 }
8279 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8280 return GF_OK;
8281 }
8282
8283 GF_EXPORT
gf_dash_seek(GF_DashClient * dash,Double start_range)8284 void gf_dash_seek(GF_DashClient *dash, Double start_range)
8285 {
8286 Bool is_dynamic = GF_FALSE;
8287 if (dash->dash_mutex) gf_mx_p(dash->dash_mutex);
8288
8289 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Seek request - playing from %g\n", start_range));
8290
8291 //are we live ? if so adjust start range
8292 if (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
8293 u64 now, availabilityStartTime;
8294 availabilityStartTime = dash->mpd->availabilityStartTime + dash->utc_shift;
8295 availabilityStartTime += dash->utc_drift_estimate;
8296
8297 now = dash->mpd_fetch_time + (gf_sys_clock() - dash->last_update_time) - availabilityStartTime;
8298
8299 if (dash->initial_time_shift_value<=100) {
8300 now -= dash->mpd->time_shift_buffer_depth * dash->initial_time_shift_value / 100;
8301 } else {
8302 now -= dash->initial_time_shift_value;
8303 }
8304 // now += (u64) (start_range*1000);
8305 start_range = (Double) now;
8306 start_range /= 1000;
8307
8308 is_dynamic = 1;
8309 dash->initial_period_tunein = GF_TRUE;
8310 }
8311
8312 /*first check if we seek to another period*/
8313 if (! gf_dash_seek_periods(dash, start_range)) {
8314 /*if no, seek in group*/
8315 gf_dash_seek_groups(dash, start_range, is_dynamic);
8316 }
8317 if (dash->dash_mutex) gf_mx_v(dash->dash_mutex);
8318 }
8319
8320 GF_EXPORT
gf_dash_group_segment_switch_forced(GF_DashClient * dash,u32 idx)8321 Bool gf_dash_group_segment_switch_forced(GF_DashClient *dash, u32 idx)
8322 {
8323 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8324 return group->force_segment_switch;
8325 }
8326
8327 GF_EXPORT
gf_dash_set_utc_shift(GF_DashClient * dash,s32 shift_utc_sec)8328 void gf_dash_set_utc_shift(GF_DashClient *dash, s32 shift_utc_sec)
8329 {
8330 if (dash) dash->utc_shift = shift_utc_sec;
8331 }
8332
8333 GF_EXPORT
gf_dash_group_get_video_info(GF_DashClient * dash,u32 idx,u32 * max_width,u32 * max_height)8334 GF_Err gf_dash_group_get_video_info(GF_DashClient *dash, u32 idx, u32 *max_width, u32 *max_height)
8335 {
8336 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8337 if (!group || !max_width || !max_height) return GF_BAD_PARAM;
8338
8339 *max_width = group->adaptation_set->max_width;
8340 *max_height = group->adaptation_set->max_height;
8341 return GF_OK;
8342 }
8343
8344 GF_EXPORT
gf_dash_group_get_srd_max_size_info(GF_DashClient * dash,u32 idx,u32 * max_width,u32 * max_height)8345 Bool gf_dash_group_get_srd_max_size_info(GF_DashClient *dash, u32 idx, u32 *max_width, u32 *max_height)
8346 {
8347 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8348 if (!group || !group->srd_desc || !max_width || !max_height) return GF_FALSE;
8349
8350 *max_width = group->srd_desc->width;
8351 *max_height = group->srd_desc->height;
8352 return GF_TRUE;
8353 }
8354
8355 GF_EXPORT
gf_dash_set_min_timeout_between_404(GF_DashClient * dash,u32 min_timeout_between_404)8356 GF_Err gf_dash_set_min_timeout_between_404(GF_DashClient *dash, u32 min_timeout_between_404)
8357 {
8358 if (!dash) return GF_BAD_PARAM;
8359 dash->min_timeout_between_404 = min_timeout_between_404;
8360 return GF_OK;
8361 }
8362
8363 GF_EXPORT
gf_dash_set_segment_expiration_threshold(GF_DashClient * dash,u32 expire_after_ms)8364 GF_Err gf_dash_set_segment_expiration_threshold(GF_DashClient *dash, u32 expire_after_ms)
8365 {
8366 if (!dash) return GF_BAD_PARAM;
8367 dash->segment_lost_after_ms = expire_after_ms;
8368 return GF_OK;
8369 }
8370
8371 GF_EXPORT
gf_dash_group_loop_detected(GF_DashClient * dash,u32 idx)8372 Bool gf_dash_group_loop_detected(GF_DashClient *dash, u32 idx)
8373 {
8374 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8375 return (group && group->nb_cached_segments) ? group->cached[0].loop_detected : GF_FALSE;
8376 }
8377
8378 GF_EXPORT
gf_dash_group_get_start_range(GF_DashClient * dash,u32 idx)8379 Double gf_dash_group_get_start_range(GF_DashClient *dash, u32 idx)
8380 {
8381 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8382 if (!group) return 0.0;
8383 return group->start_playback_range;
8384 }
8385
8386
8387 GF_EXPORT
gf_dash_is_dynamic_mpd(GF_DashClient * dash)8388 Bool gf_dash_is_dynamic_mpd(GF_DashClient *dash)
8389 {
8390 return (dash && dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? 1 : 0;
8391 }
8392
8393 GF_EXPORT
gf_dash_get_min_buffer_time(GF_DashClient * dash)8394 u32 gf_dash_get_min_buffer_time(GF_DashClient *dash)
8395 {
8396 return dash ? dash->mpd->min_buffer_time : 0;
8397 }
8398
8399 #if 0 //unused
8400 GF_Err gf_dash_resync_to_segment(GF_DashClient *dash, const char *latest_segment_name, const char *earliest_segment_name)
8401 {
8402 Bool found = GF_FALSE;
8403 u32 i, j, latest_segment_number, earliest_segment_number, start_number;
8404 /*Double latest_segment_time, earliest_segment_time;*/ //FIX : set but not used
8405 u64 start_range, end_range, current_dur;
8406 char *seg_url, *seg_name, *seg_sep;
8407 GF_MPD_Representation *rep;
8408 GF_DASH_Group *group = NULL;
8409 if (!latest_segment_name) return GF_BAD_PARAM;
8410
8411 seg_url = NULL;
8412 for (i=0; i<gf_list_count(dash->groups); i++) {
8413 group = gf_list_get(dash->groups, i);
8414 for (j=0; j<gf_list_count(group->adaptation_set->representations); j++) {
8415 GF_Err e;
8416 rep = gf_list_get(group->adaptation_set->representations, j);
8417 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);
8418 if (e)
8419 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve media template URL: %s\n", gf_error_to_string(e)));
8420
8421 if (!seg_url) continue;
8422
8423 seg_sep = NULL;
8424 seg_name = strstr(seg_url, "://");
8425 if (seg_name) seg_name = strchr(seg_name+4, '/');
8426 if (!seg_name) {
8427 gf_free(seg_url);
8428 continue;
8429 }
8430 seg_sep = strchr(seg_name+1, '$');
8431 if (seg_sep) seg_sep[0] = 0;
8432
8433 if (!strncmp(seg_name, latest_segment_name, strlen(seg_name)))
8434 found = GF_TRUE;
8435
8436 if (found) break;
8437
8438 if (seg_sep) seg_sep[0] = '$';
8439 gf_free(seg_url);
8440 continue;
8441 }
8442 if (found) break;
8443 }
8444
8445 if (!found) {
8446 if (seg_url) gf_free(seg_url);
8447 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] No representation found matching the resync segment name %s\n", latest_segment_name));
8448 return GF_BAD_PARAM;
8449 }
8450
8451 start_number = gf_dash_get_start_number(group, rep);
8452
8453 if (seg_sep) {
8454 char *sep_template, *sep_name, c;
8455 char *latest_template = (char *) (latest_segment_name + strlen(seg_name));
8456 char *earliest_template = earliest_segment_name ? (char *) (earliest_segment_name + strlen(seg_name)) : NULL;
8457
8458 latest_segment_number = earliest_segment_number = 0;
8459 /*latest_segment_time = earliest_segment_time = 0;*/
8460
8461 seg_sep[0] = '$';
8462 while (seg_sep) {
8463 sep_template = strchr(seg_sep+1, '$');
8464 if (!sep_template) break;
8465 c = sep_template[1];
8466 sep_template[1] = 0;
8467 //solve template for latest
8468 sep_name = strchr(latest_template, c);
8469 if (!sep_name) break;
8470
8471 sep_name[0] = 0;
8472 if (!strcmp(seg_sep, "$Number$")) {
8473 latest_segment_number = atoi(latest_template);
8474 }
8475 /*else if (!strcmp(seg_sep, "$Time$")) {
8476 latest_segment_time = atof(latest_template);
8477 }*/
8478 sep_name[0] = c;
8479 latest_template = sep_name;
8480
8481 //solve template for earliest
8482 if (earliest_template) {
8483 sep_name = strchr(earliest_template, c);
8484 if (!sep_name) break;
8485
8486 sep_name[0] = 0;
8487 if (!strcmp(seg_sep, "$Number$")) {
8488 earliest_segment_number = atoi(earliest_template);
8489 }
8490 /*else if (!strcmp(seg_sep, "$Time$")) {
8491 earliest_segment_time = atof(earliest_template);
8492 }*/
8493 sep_name[0] = c;
8494 earliest_template = sep_name;
8495 }
8496
8497 sep_template[1] = c;
8498 seg_sep = sep_template+1;
8499 //find next $ - if any, move the segment name of the same amount of chars that what found in the template
8500 sep_template = strchr(seg_sep, '$');
8501 if (!sep_template) break;
8502 sep_template[0]=0;
8503 latest_template += strlen(sep_template);
8504 sep_template[0]='$';
8505 }
8506 if (earliest_segment_number && !latest_segment_number) {
8507 latest_segment_number = earliest_segment_number;
8508 }
8509
8510 gf_free(seg_url);
8511
8512 //todo - recompute an AST offset so that the AST of the new segment equals UTC(now) + offset
8513 if (latest_segment_number) {
8514 Bool loop_detected = GF_FALSE;
8515 s32 nb_seg_diff = 0;
8516 s32 range_in = 0;
8517 //how many segment ahead are we ?
8518 nb_seg_diff = start_number + group->download_segment_index;
8519 nb_seg_diff -= latest_segment_number;
8520
8521 //we are just too early for this request, do request later
8522 if (nb_seg_diff == 1 ) {
8523 //set to false, eg don't increment seg index
8524 group->segment_in_valid_range = GF_FALSE;
8525 return GF_OK;
8526 }
8527
8528 //if earliest is not given, allow 5 segments
8529 if (!earliest_segment_number) range_in = 4;
8530 else range_in = latest_segment_number - earliest_segment_number;
8531
8532 //loop
8533 if (latest_segment_number <= start_number ) {
8534 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));
8535 loop_detected = GF_TRUE;
8536 }
8537 //we are behind live
8538 else if (nb_seg_diff<0) {
8539 //we fall in the buffer of the sender, we liklely have a loss
8540 if (nb_seg_diff + range_in >= 0) {
8541 group->segment_in_valid_range = GF_TRUE;
8542 return GF_OK;
8543 }
8544 //we are late (something wrong happen locally maybe) - If not too late (5 segs) jump to newest
8545 else if (earliest_segment_number && (start_number + group->download_segment_index + 5 >= earliest_segment_number)) {
8546 group->download_segment_index = latest_segment_number - start_number;
8547 group->segment_in_valid_range = GF_FALSE;
8548 return GF_OK;
8549 }
8550 //we are too late resync...
8551 }
8552
8553 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Sync to live was lost - reloading MPD (loop detected %d)\n", loop_detected));
8554 for (i=0; i< gf_list_count(dash->groups); i++) {
8555 group = gf_list_get(dash->groups, i);
8556 //force reinit of timeline for this group
8557 group->start_number_at_last_ast = 0;
8558 if (loop_detected)
8559 group->loop_detected = GF_TRUE;
8560 }
8561 dash->force_mpd_update = GF_TRUE;
8562 }
8563 return GF_OK;
8564 }
8565
8566 //TODO segment list addressing:
8567 return GF_OK;
8568 }
8569 #endif
8570
8571
8572 GF_EXPORT
gf_dash_set_max_resolution(GF_DashClient * dash,u32 width,u32 height,u8 max_display_bpp)8573 GF_Err gf_dash_set_max_resolution(GF_DashClient *dash, u32 width, u32 height, u8 max_display_bpp)
8574 {
8575 if (dash) {
8576 dash->max_width = width;
8577 dash->max_height = height;
8578 dash->max_bit_per_pixel = max_display_bpp;
8579 return GF_OK;
8580 }
8581 return GF_BAD_PARAM;
8582 }
8583
8584 GF_EXPORT
gf_dash_debug_group(GF_DashClient * dash,s32 group_index)8585 void gf_dash_debug_group(GF_DashClient *dash, s32 group_index)
8586 {
8587 dash->debug_group_index = group_index;
8588 }
8589
8590 GF_EXPORT
gf_dash_split_adaptation_sets(GF_DashClient * dash)8591 void gf_dash_split_adaptation_sets(GF_DashClient *dash)
8592 {
8593 dash->split_adaptation_set = GF_TRUE;
8594 }
8595
8596 GF_EXPORT
gf_dash_set_low_latency_mode(GF_DashClient * dash,GF_DASHLowLatencyMode low_lat_mode)8597 void gf_dash_set_low_latency_mode(GF_DashClient *dash, GF_DASHLowLatencyMode low_lat_mode)
8598 {
8599 dash->low_latency_mode = low_lat_mode;
8600 }
8601
8602
8603 GF_EXPORT
gf_dash_set_user_buffer(GF_DashClient * dash,u32 buffer_time_ms)8604 void gf_dash_set_user_buffer(GF_DashClient *dash, u32 buffer_time_ms)
8605 {
8606 if (dash) dash->user_buffer_ms = buffer_time_ms;
8607 }
8608
8609 /*returns active period start in ms*/
8610 GF_EXPORT
gf_dash_get_period_start(GF_DashClient * dash)8611 u64 gf_dash_get_period_start(GF_DashClient *dash)
8612 {
8613 u64 start;
8614 u32 i;
8615 GF_MPD_Period *period;
8616 if (!dash || !dash->mpd) return 0;
8617
8618 start = 0;
8619 for (i=0; i<=dash->active_period_index; i++) {
8620 period = gf_list_get(dash->mpd->periods, i);
8621 if (period->start) start = period->start;
8622
8623 if (i<dash->active_period_index) start += period->duration;
8624 }
8625 return start;
8626 }
8627
8628
8629 /*returns active period duration in ms*/
8630 GF_EXPORT
gf_dash_get_period_duration(GF_DashClient * dash)8631 u64 gf_dash_get_period_duration(GF_DashClient *dash)
8632 {
8633 u64 start;
8634 u32 i;
8635 GF_MPD_Period *period = NULL;
8636 if (!dash || !dash->mpd) return 0;
8637
8638 start = 0;
8639 for (i=0; i<=dash->active_period_index; i++) {
8640 period = gf_list_get(dash->mpd->periods, i);
8641 if (period->start) start = period->start;
8642 if (i<dash->active_period_index) start += period->duration;
8643 }
8644 if (!period) return 0;
8645 if (period->duration) return period->duration;
8646 period = gf_list_get(dash->mpd->periods, dash->active_period_index+1);
8647
8648 if (!period) {
8649 //infered from MPD duration
8650 if (dash->mpd->media_presentation_duration) return dash->mpd->media_presentation_duration - start;
8651 //duration is not known (live)
8652 if (dash->mpd->type==GF_MPD_TYPE_STATIC) {
8653 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Period duration is not computable: last period without duration and no MPD duration !\n"));
8654 }
8655 return 0;
8656 }
8657 if (!period->start) {
8658 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"));
8659 return 0;
8660 }
8661 return period->start - start;
8662 }
8663
8664 GF_EXPORT
gf_dash_group_get_language(GF_DashClient * dash,u32 idx)8665 const char *gf_dash_group_get_language(GF_DashClient *dash, u32 idx)
8666 {
8667 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8668 if (!group) return NULL;
8669 return group->adaptation_set->lang;
8670 }
8671
8672 GF_EXPORT
gf_dash_group_get_audio_channels(GF_DashClient * dash,u32 idx)8673 u32 gf_dash_group_get_audio_channels(GF_DashClient *dash, u32 idx)
8674 {
8675 GF_MPD_Descriptor *mpd_desc;
8676 u32 i=0;
8677 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8678 if (!group) return 0;
8679
8680 while ((mpd_desc=gf_list_enum(group->adaptation_set->audio_channels, &i))) {
8681 if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:23003:3:audio_channel_configuration:2011")) {
8682 return atoi(mpd_desc->value);
8683 }
8684 }
8685 return 0;
8686 }
8687
8688 GF_EXPORT
gf_dash_group_get_num_qualities(GF_DashClient * dash,u32 idx)8689 u32 gf_dash_group_get_num_qualities(GF_DashClient *dash, u32 idx)
8690 {
8691 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8692 if (!group) return 0;
8693 return gf_list_count(group->adaptation_set->representations);
8694 }
8695
8696 GF_EXPORT
gf_dash_group_get_num_components(GF_DashClient * dash,u32 idx)8697 u32 gf_dash_group_get_num_components(GF_DashClient *dash, u32 idx)
8698 {
8699 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8700 if (!group) return 0;
8701 return gf_list_count(group->adaptation_set->content_component);
8702 }
8703
8704
8705 GF_EXPORT
gf_dash_group_get_quality_info(GF_DashClient * dash,u32 idx,u32 quality_idx,GF_DASHQualityInfo * quality)8706 GF_Err gf_dash_group_get_quality_info(GF_DashClient *dash, u32 idx, u32 quality_idx, GF_DASHQualityInfo *quality)
8707 {
8708 GF_MPD_Fractional *sar;
8709 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8710 GF_MPD_Representation *rep;
8711 if (!group || !quality) return GF_BAD_PARAM;
8712 rep = gf_list_get(group->adaptation_set->representations, quality_idx);
8713 if (!rep) return GF_BAD_PARAM;
8714
8715 memset(quality, 0, sizeof(GF_DASHQualityInfo));
8716 quality->mime = rep->mime_type ? rep->mime_type : group->adaptation_set->mime_type;
8717 quality->codec = rep->codecs ? rep->codecs : group->adaptation_set->codecs;
8718 quality->disabled = rep->playback.disabled;
8719 sar = rep->framerate ? rep->framerate : group->adaptation_set->framerate;
8720 if (sar) {
8721 quality->fps_den = sar->den;
8722 quality->fps_num = sar->num;
8723 }
8724 quality->height = rep->height ? rep->height : group->adaptation_set->height;
8725 quality->width = rep->width ? rep->width : group->adaptation_set->width;
8726 quality->nb_channels = gf_dash_group_get_audio_channels(dash, idx);
8727 sar = rep->sar ? rep->sar : group->adaptation_set->sar;
8728 if (sar) {
8729 quality->par_num = sar->num;
8730 quality->par_den = sar->den;
8731 }
8732 quality->sample_rate = rep->samplerate ? rep->samplerate : group->adaptation_set->samplerate;
8733 quality->bandwidth = rep->bandwidth;
8734 quality->ID = rep->id;
8735 quality->interlaced = (rep->scan_type == GF_MPD_SCANTYPE_INTERLACED) ? 1 : ( (group->adaptation_set->scan_type == GF_MPD_SCANTYPE_INTERLACED) ? 1 : 0);
8736
8737 //scalable rep, selected quality is max_complementary_rep_index
8738 if (group->base_rep_index_plus_one) {
8739 quality->is_selected = (quality_idx==group->max_complementary_rep_index) ? 1 : 0;
8740 } else {
8741 quality->is_selected = (quality_idx==group->active_rep_index) ? 1 : 0;
8742 }
8743 return GF_OK;
8744 }
8745
8746
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)8747 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)
8748 {
8749 GF_MPD_Descriptor *mpd_desc;
8750 if (idx>=gf_list_count(descs)) return 0;
8751 mpd_desc = gf_list_get(descs, idx);
8752 if (desc_value) *desc_value = mpd_desc->value;
8753 if (desc_scheme) *desc_scheme = mpd_desc->scheme_id_uri;
8754 if (desc_id) *desc_id = mpd_desc->id;
8755 return 1;
8756 }
8757
8758 GF_EXPORT
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)8759 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)
8760 {
8761 GF_List *descs = NULL;
8762 GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
8763 if (!group) return 0;
8764 switch (desc_type) {
8765 case GF_MPD_DESC_ACCESSIBILITY:
8766 descs = group->adaptation_set->accessibility;
8767 break;
8768 case GF_MPD_DESC_AUDIOCONFIG:
8769 descs = group->adaptation_set->audio_channels;
8770 break;
8771 case GF_MPD_DESC_CONTENT_PROTECTION:
8772 descs = group->adaptation_set->content_protection;
8773 break;
8774 case GF_MPD_DESC_ESSENTIAL_PROPERTIES:
8775 descs = group->adaptation_set->essential_properties;
8776 break;
8777 case GF_MPD_DESC_SUPPLEMENTAL_PROPERTIES:
8778 descs = group->adaptation_set->supplemental_properties;
8779 break;
8780 case GF_MPD_DESC_FRAME_PACKING:
8781 descs = group->adaptation_set->frame_packing;
8782 break;
8783 case GF_MPD_DESC_ROLE:
8784 descs = group->adaptation_set->role;
8785 break;
8786 case GF_MPD_DESC_RATING:
8787 descs = group->adaptation_set->rating;
8788 break;
8789 case GF_MPD_DESC_VIEWPOINT:
8790 descs = group->adaptation_set->viewpoint;
8791 break;
8792 default:
8793 return 0;
8794 }
8795 return gf_dash_group_enum_descriptor_list(dash, desc_idx, descs, desc_id, desc_scheme, desc_value);
8796 }
8797
8798 GF_EXPORT
gf_dash_get_automatic_switching(GF_DashClient * dash)8799 Bool gf_dash_get_automatic_switching(GF_DashClient *dash)
8800 {
8801 return (dash && dash->disable_switching) ? GF_FALSE : GF_TRUE;
8802 }
8803
8804 GF_EXPORT
gf_dash_set_automatic_switching(GF_DashClient * dash,Bool enable_switching)8805 GF_Err gf_dash_set_automatic_switching(GF_DashClient *dash, Bool enable_switching)
8806 {
8807 if (!dash) return GF_BAD_PARAM;
8808 dash->disable_switching = !enable_switching;
8809 return GF_OK;
8810 }
8811
8812 GF_EXPORT
gf_dash_group_select_quality(GF_DashClient * dash,u32 idx,const char * ID,u32 q_idx)8813 GF_Err gf_dash_group_select_quality(GF_DashClient *dash, u32 idx, const char *ID, u32 q_idx)
8814 {
8815 u32 i, count;
8816 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8817 if (!group) return GF_BAD_PARAM;
8818
8819 if (!ID) {
8820 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, q_idx);
8821 if (!rep) return GF_BAD_PARAM;
8822 group->force_representation_idx_plus_one = q_idx+1;
8823 group->force_switch_bandwidth = 1;
8824 return GF_OK;
8825 }
8826
8827 count = gf_list_count(group->adaptation_set->representations);
8828 for (i=0; i<count; i++) {
8829 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, i);
8830 if (rep->id && !strcmp(rep->id, ID)) {
8831 group->force_representation_idx_plus_one = i+1;
8832 group->force_switch_bandwidth = 1;
8833 return GF_OK;
8834 }
8835 }
8836 return GF_BAD_PARAM;
8837 }
8838
8839 GF_EXPORT
gf_dash_group_get_active_quality(GF_DashClient * dash,u32 idx)8840 s32 gf_dash_group_get_active_quality(GF_DashClient *dash, u32 idx)
8841 {
8842 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8843 if (!group) return -1;
8844 return group->active_rep_index;
8845 }
8846
8847
8848 GF_EXPORT
gf_dash_group_get_download_rate(GF_DashClient * dash,u32 idx)8849 u32 gf_dash_group_get_download_rate(GF_DashClient *dash, u32 idx)
8850 {
8851 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8852 if (!group || !group->segment_download) return 0;
8853
8854 return dash->dash_io->get_bytes_per_sec(dash->dash_io, group->segment_download);
8855 }
8856
8857
8858 GF_EXPORT
gf_dash_set_timeshift(GF_DashClient * dash,u32 ms_in_timeshift)8859 GF_Err gf_dash_set_timeshift(GF_DashClient *dash, u32 ms_in_timeshift)
8860 {
8861 if (!dash) return GF_BAD_PARAM;
8862 dash->initial_time_shift_value = ms_in_timeshift;
8863 return GF_OK;
8864 }
8865
8866 GF_EXPORT
gf_dash_get_timeshift_buffer_pos(GF_DashClient * dash)8867 Double gf_dash_get_timeshift_buffer_pos(GF_DashClient *dash)
8868 {
8869 return dash ? dash->prev_time_in_tsb / 1000.0 : 0.0;
8870 }
8871
8872 GF_EXPORT
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)8873 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)
8874 {
8875 GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, idx);
8876 if (!group) return;
8877 group->avg_dec_time = avg_dec_time;
8878 group->max_dec_time = max_dec_time;
8879 group->irap_avg_dec_time = irap_avg_dec_time;
8880 group->irap_max_dec_time = irap_max_dec_time;
8881 group->codec_reset = codec_reset;
8882 group->decode_only_rap = decode_only_rap;
8883 }
8884
8885 GF_EXPORT
gf_dash_group_set_buffer_levels(GF_DashClient * dash,u32 idx,u32 buffer_min_ms,u32 buffer_max_ms,u32 buffer_occupancy_ms)8886 void gf_dash_group_set_buffer_levels(GF_DashClient *dash, u32 idx, u32 buffer_min_ms, u32 buffer_max_ms, u32 buffer_occupancy_ms)
8887 {
8888 GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, idx);
8889 if (!group) return;
8890 group->buffer_min_ms = buffer_min_ms;
8891 group->buffer_max_ms = buffer_max_ms;
8892 if (group->max_buffer_playout_ms > buffer_max_ms) {
8893 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));
8894 group->max_buffer_playout_ms = buffer_max_ms;
8895 }
8896 group->buffer_occupancy_ms = buffer_occupancy_ms;
8897 }
8898
8899
8900 GF_EXPORT
gf_dash_disable_speed_adaptation(GF_DashClient * dash,Bool disable)8901 void gf_dash_disable_speed_adaptation(GF_DashClient *dash, Bool disable)
8902 {
8903 dash->disable_speed_adaptation = disable;
8904 }
8905
8906 GF_EXPORT
gf_dash_override_ntp(GF_DashClient * dash,u64 server_ntp)8907 void gf_dash_override_ntp(GF_DashClient *dash, u64 server_ntp)
8908 {
8909 if (server_ntp) {
8910 dash->utc_drift_estimate = gf_net_get_ntp_diff_ms(server_ntp);
8911 dash->ntp_forced = 1;
8912 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));
8913 } else {
8914 dash->utc_drift_estimate = 0;
8915 dash->ntp_forced = 0;
8916 }
8917 }
8918
8919 GF_EXPORT
gf_dash_get_utc_drift_estimate(GF_DashClient * dash)8920 s32 gf_dash_get_utc_drift_estimate(GF_DashClient *dash) {
8921 return (s32) dash->utc_drift_estimate;
8922 }
8923
8924 GF_EXPORT
gf_dash_get_tile_adaptation_mode(GF_DashClient * dash)8925 GF_DASHTileAdaptationMode gf_dash_get_tile_adaptation_mode(GF_DashClient *dash)
8926 {
8927 return dash->tile_adapt_mode;
8928 }
8929
8930 GF_EXPORT
gf_dash_set_tile_adaptation_mode(GF_DashClient * dash,GF_DASHTileAdaptationMode mode,u32 tile_rate_decrease)8931 void gf_dash_set_tile_adaptation_mode(GF_DashClient *dash, GF_DASHTileAdaptationMode mode, u32 tile_rate_decrease)
8932 {
8933 u32 i;
8934 dash->tile_adapt_mode = mode;
8935 dash->tile_rate_decrease = (tile_rate_decrease<100) ? tile_rate_decrease : 100;
8936 for (i=0; i<gf_list_count(dash->groups); i++) {
8937 GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, i);
8938 if (group->srd_desc) gf_dash_set_tiles_quality(dash, group->srd_desc);
8939 }
8940 }
8941
8942 GF_EXPORT
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)8943 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)
8944 {
8945 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8946 if (!group || !group->srd_desc) return GF_FALSE;
8947
8948 if (group->srd_desc) {
8949 if (srd_id) (*srd_id) = group->srd_desc->id;
8950 if (srd_width) (*srd_width) = group->srd_desc->srd_fw;
8951 if (srd_height) (*srd_height) = group->srd_desc->srd_fh;
8952 }
8953
8954 if (srd_x) (*srd_x) = group->srd_x;
8955 if (srd_y) (*srd_y) = group->srd_y;
8956 if (srd_w) (*srd_w) = group->srd_w;
8957 if (srd_h) (*srd_h) = group->srd_h;
8958
8959
8960 return GF_TRUE;
8961 }
8962
8963 GF_EXPORT
gf_dash_ignore_xlink(GF_DashClient * dash,Bool ignore_xlink)8964 void gf_dash_ignore_xlink(GF_DashClient *dash, Bool ignore_xlink)
8965 {
8966 dash->ignore_xlink = ignore_xlink;
8967 }
8968
8969 GF_EXPORT
gf_dash_set_atsc_ast_shift(GF_DashClient * dash,u32 ast_shift)8970 void gf_dash_set_atsc_ast_shift(GF_DashClient *dash, u32 ast_shift)
8971 {
8972 dash->atsc_ast_shift = ast_shift;
8973 }
8974
8975 GF_EXPORT
gf_dash_group_set_max_buffer_playout(GF_DashClient * dash,u32 idx,u32 max_buffer_playout_ms)8976 GF_Err gf_dash_group_set_max_buffer_playout(GF_DashClient *dash, u32 idx, u32 max_buffer_playout_ms)
8977 {
8978 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8979 if (!group) return GF_BAD_PARAM;
8980 group->max_buffer_playout_ms = max_buffer_playout_ms;
8981 return GF_OK;
8982 }
8983
8984 GF_EXPORT
gf_dash_group_set_quality_degradation_hint(GF_DashClient * dash,u32 idx,u32 quality_degradation_hint)8985 GF_Err gf_dash_group_set_quality_degradation_hint(GF_DashClient *dash, u32 idx, u32 quality_degradation_hint)
8986 {
8987 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
8988 if (!group) return GF_BAD_PARAM;
8989
8990 group->quality_degradation_hint = quality_degradation_hint;
8991 if (group->quality_degradation_hint > 100) group->quality_degradation_hint=100;
8992 return GF_OK;
8993 }
8994
8995
8996 GF_EXPORT
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)8997 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)
8998 {
8999 u32 i, count;
9000 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9001 if (!group) return GF_BAD_PARAM;
9002
9003 if (!min_x && !max_x && !min_y && !max_y) {
9004 group->quality_degradation_hint = 0;
9005 }
9006
9007
9008 //TODO - single video, we may want to switch down quality if not a lot of the video is visible
9009 //we will need the zoom factor as well
9010 if (!group->groups_depending_on) return GF_OK;
9011
9012 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));
9013 count = gf_list_count(group->groups_depending_on);
9014 for (i=0; i<count; i++) {
9015 Bool is_visible = GF_TRUE;
9016 GF_DASH_Group *a_group = gf_list_get(group->groups_depending_on, i);
9017 if (!a_group->srd_w || !a_group->srd_h) continue;
9018
9019 if (is_gaze) {
9020
9021 if (min_x < a_group->srd_x)
9022 is_visible = GF_FALSE;
9023 else if (min_x > a_group->srd_x + a_group->srd_w)
9024 is_visible = GF_FALSE;
9025 else if (min_y < a_group->srd_y)
9026 is_visible = GF_FALSE;
9027 else if (min_y > a_group->srd_y + a_group->srd_h)
9028 is_visible = GF_FALSE;
9029
9030 } else {
9031
9032 //single rectangle case
9033 if (min_x<max_x) {
9034 if (a_group->srd_x+a_group->srd_w <min_x) is_visible = GF_FALSE;
9035 else if (a_group->srd_x>max_x) is_visible = GF_FALSE;
9036 } else {
9037 if ( (a_group->srd_x>max_x) && (a_group->srd_x+a_group->srd_w<min_x)) is_visible = GF_FALSE;
9038 }
9039
9040 if (a_group->srd_y>max_y) is_visible = GF_FALSE;
9041 else if (a_group->srd_y+a_group->srd_h < min_y) is_visible = GF_FALSE;
9042
9043 }
9044
9045 a_group->quality_degradation_hint = is_visible ? 0 : 100;
9046
9047 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"));
9048 }
9049 return GF_OK;
9050 }
9051
gf_dash_set_group_download_state(GF_DashClient * dash,u32 idx,GF_Err err)9052 void gf_dash_set_group_download_state(GF_DashClient *dash, u32 idx, GF_Err err)
9053 {
9054 GF_MPD_Representation *rep;
9055 Bool has_dep_following;
9056 char *key_url, *url;
9057 GF_DASH_Group *base_group;
9058 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9059 if (dash->thread_mode) return;
9060 if (!group) return;
9061
9062 //we forced early fetch because demux was empty, consider all errors as 404
9063 if (group->force_early_fetch && err) {
9064 err = GF_URL_ERROR;
9065 }
9066
9067 if (!err) {
9068 group->force_early_fetch = GF_FALSE;
9069 return;
9070 }
9071 if (!group->nb_cached_segments) return;
9072 rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
9073 //FILTER_FIXME: find base group and current cache idx for scalability
9074 has_dep_following = group->cached[0].has_dep_following;
9075 key_url = group->cached[0].key_url;
9076 url = group->cached[0].url;
9077 gf_free(group->cached[0].cache);
9078 group->nb_cached_segments--;
9079 assert(!group->nb_cached_segments);
9080
9081 base_group = group;
9082 while (base_group->depend_on_group) {
9083 base_group = base_group->depend_on_group;
9084 }
9085 on_group_download_error(dash, group, base_group, err, rep, url, key_url, has_dep_following);
9086
9087
9088 if (dash->speed>=0) {
9089 group->download_segment_index--;
9090 } else {
9091 group->download_segment_index++;
9092 }
9093 }
9094
gf_dash_group_store_stats(GF_DashClient * dash,u32 idx,u32 bytes_per_sec,u32 file_size,u32 bytes_done,Bool is_broadcast)9095 void gf_dash_group_store_stats(GF_DashClient *dash, u32 idx, u32 bytes_per_sec, u32 file_size, u32 bytes_done, Bool is_broadcast)
9096 {
9097 GF_DASH_Group *group = gf_list_get(dash->groups, idx);
9098 if (dash->thread_mode) return;
9099 if (!group) return;
9100 if (!group->nb_cached_segments) return;
9101
9102 dash_store_stats(dash, group, bytes_per_sec, file_size, is_broadcast);
9103
9104 if (file_size==bytes_done) {
9105 dash_global_rate_adaptation(dash, GF_FALSE);
9106 }
9107 }
9108
gf_dash_get_min_wait_ms(GF_DashClient * dash)9109 u32 gf_dash_get_min_wait_ms(GF_DashClient *dash)
9110 {
9111 if (dash && dash->min_wait_ms_before_next_request) {
9112 u32 ellapsed = gf_sys_clock() - dash->min_wait_sys_clock;
9113 if (ellapsed < dash->min_wait_ms_before_next_request) dash->min_wait_ms_before_next_request -= ellapsed;
9114 else dash->min_wait_ms_before_next_request = 0;
9115 return dash->min_wait_ms_before_next_request;
9116 }
9117 return 0;
9118 }
9119
9120 GF_EXPORT
gf_dash_all_groups_done(GF_DashClient * dash)9121 Bool gf_dash_all_groups_done(GF_DashClient *dash)
9122 {
9123 u32 i, count = gf_list_count(dash->groups);
9124 for (i=0; i<count; i++) {
9125 GF_DASH_Group *group = gf_list_get(dash->groups, i);
9126 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
9127 if (!group->done) return GF_FALSE;
9128 if (group->nb_cached_segments) return GF_FALSE;
9129 }
9130 return GF_TRUE;
9131 }
9132
9133 GF_EXPORT
gf_dash_set_period_xlink_query_string(GF_DashClient * dash,const char * query_string)9134 void gf_dash_set_period_xlink_query_string(GF_DashClient *dash, const char *query_string)
9135 {
9136 if (dash) dash->query_string = query_string;
9137 }
9138
9139 GF_EXPORT
gf_dash_group_get_as_id(GF_DashClient * dash,u32 group_idx)9140 u32 gf_dash_group_get_as_id(GF_DashClient *dash, u32 group_idx)
9141 {
9142 GF_DASH_Group *group;
9143 if (!dash) return 0;
9144 group = gf_list_get(dash->groups, group_idx);
9145 if (!group) return 0;
9146 return group->adaptation_set->id;
9147 }
9148
9149 #endif //GPAC_DISABLE_DASH_CLIENT
9150
9151