1 /*
2 * GPAC - Multimedia Framework C SDK
3 *
4 * Authors: Jean Le Feuvre
5 * Copyright (c) Telecom ParisTech 2017
6 * All rights reserved
7 *
8 * This file is part of GPAC / DASH/HLS demux filter
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/filters.h>
27 #include <gpac/constants.h>
28
29 #ifndef GPAC_DISABLE_DASH_CLIENT
30
31 #include <gpac/dash.h>
32
33 typedef struct
34 {
35 //opts
36 s32 shift_utc, debug_as, atsc_shift;
37 u32 max_buffer, auto_switch, timeshift, tiles_rate, store, delay40X, exp_threshold, switch_count;
38 Bool server_utc, screen_res, aggressive, speedadapt;
39 GF_DASHInitialSelectionMode start_with;
40 GF_DASHTileAdaptationMode tile_mode;
41 GF_DASHAdaptationAlgorithm algo;
42 Bool max_res, immediate, abort, use_bmin;
43 char *query;
44 Bool noxlink, split_as;
45 u32 lowlat;
46
47 GF_FilterPid *mpd_pid;
48 GF_Filter *filter;
49
50 GF_DashClient *dash;
51 //http io for manifest
52 GF_DASHFileIO dash_io;
53 GF_DownloadManager *dm;
54
55 Bool closed;
56 Bool reuse_download_session;
57
58 Bool initial_setup_done;
59 u32 nb_playing;
60
61 /*max width & height in all active representations*/
62 u32 width, height;
63
64 Double seek_request;
65 Double media_start_range;
66
67 Bool mpd_open;
68 Bool initial_play;
69 Bool check_eos;
70 } GF_DASHDmxCtx;
71
72 typedef struct
73 {
74 GF_DASHDmxCtx *ctx;
75 GF_Filter *seg_filter_src;
76
77 u32 idx;
78 Bool init_switch_seg_sent;
79 Bool segment_sent;
80
81 u32 nb_eos, nb_pids;
82 Bool stats_uploaded;
83 Bool wait_for_pck;
84 Bool eos_detected;
85
86 GF_DownloadSession *sess;
87 Bool is_timestamp_based, pto_setup;
88 Bool prev_is_init_segment;
89 u32 timescale;
90 s64 pto;
91 s64 max_cts_in_period;
92 bin128 key_IV;
93
94 Bool seg_was_not_ready;
95 Bool in_error;
96 Bool is_playing;
97 Bool force_seg_switch;
98 u32 nb_group_deps, current_group_dep;
99 } GF_DASHGroup;
100
101
dashdmx_forward_packet(GF_DASHDmxCtx * ctx,GF_FilterPacket * in_pck,GF_FilterPid * in_pid,GF_FilterPid * out_pid,GF_DASHGroup * group)102 void dashdmx_forward_packet(GF_DASHDmxCtx *ctx, GF_FilterPacket *in_pck, GF_FilterPid *in_pid, GF_FilterPid *out_pid, GF_DASHGroup *group)
103 {
104 GF_FilterPacket *dst_pck;
105 Bool do_map_time = GF_FALSE;
106 Bool seek_flag = 0;
107 u64 cts, dts;
108
109 if (gf_dash_is_m3u8(ctx->dash)) {
110 gf_filter_pck_forward(in_pck, out_pid);
111
112 if (!group->pto_setup) {
113 cts = gf_filter_pck_get_cts(in_pck);
114 gf_filter_pid_set_property_str(out_pid, "time:timestamp", &PROP_LONGUINT(cts) );
115 gf_filter_pid_set_property_str(out_pid, "time:media", &PROP_DOUBLE(ctx->media_start_range) );
116 group->pto_setup = GF_TRUE;
117 }
118
119 gf_filter_pid_drop_packet(in_pid);
120 return;
121 }
122
123 //filter any packet outside the current period
124 dts = gf_filter_pck_get_dts(in_pck);
125 cts = gf_filter_pck_get_cts(in_pck);
126 seek_flag = gf_filter_pck_get_seek_flag(in_pck);
127
128 //if sync is based on timestamps do not adjust the timestamps back
129 if (! group->is_timestamp_based) {
130 if (!group->pto_setup) {
131 Double scale;
132 s64 start, dur;
133 u64 pto;
134 u32 ts = gf_filter_pck_get_timescale(in_pck);
135 gf_dash_group_get_presentation_time_offset(ctx->dash, group->idx, &pto, &group->timescale);
136 group->pto = (s64) pto;
137 group->pto_setup = 1;
138
139 if (group->timescale && (group->timescale != ts)) {
140 group->pto *= ts;
141 group->pto /= group->timescale;
142 }
143 scale = ts;
144 scale /= 1000;
145
146 dur = (u64) (scale * gf_dash_get_period_duration(ctx->dash));
147 if (dur) {
148 group->max_cts_in_period = group->pto + dur;
149 } else {
150 group->max_cts_in_period = 0;
151 }
152
153 start = (u64) (scale * gf_dash_get_period_start(ctx->dash));
154 group->pto -= start;
155 }
156
157 if (group->max_cts_in_period && (s64) cts > group->max_cts_in_period) {
158 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASHDmx] Packet timestamp "LLU" larger than max CTS in period "LLU" - forcing seek flag\n", cts, group->max_cts_in_period));
159
160 seek_flag = 1;
161 }
162
163 //remap timestamps to our timeline
164 if (dts != GF_FILTER_NO_TS) {
165 if ((s64) dts >= group->pto)
166 dts -= group->pto;
167 else {
168 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] Packet DTS "LLU" less than PTO "LLU" - forcing DTS to 0\n", dts, group->pto));
169 dts = 0;
170 seek_flag = 1;
171 }
172 }
173 if (cts!=GF_FILTER_NO_TS) {
174 if ((s64) cts >= group->pto)
175 cts -= group->pto;
176 else {
177 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] Packet CTS "LLU" less than PTO "LLU" - forcing CTS to 0\n", cts, group->pto));
178 cts = 0;
179 seek_flag = 1;
180 }
181 }
182 } else if (!group->pto_setup) {
183 do_map_time = 1;
184 group->pto_setup = 1;
185 }
186
187 dst_pck = gf_filter_pck_new_ref(out_pid, NULL, 0, in_pck);
188 //this will copy over clock info for PCR in TS
189 gf_filter_pck_merge_properties(in_pck, dst_pck);
190 gf_filter_pck_set_dts(dst_pck, dts);
191 gf_filter_pck_set_cts(dst_pck, cts);
192 gf_filter_pck_set_seek_flag(dst_pck, seek_flag);
193 gf_filter_pck_send(dst_pck);
194 gf_filter_pid_drop_packet(in_pid);
195
196 if (do_map_time) {
197 gf_filter_pid_set_property_str(out_pid, "time:timestamp", &PROP_LONGUINT(cts) );
198 gf_filter_pid_set_property_str(out_pid, "time:media", &PROP_DOUBLE(ctx->media_start_range) );
199 }
200 }
201
202
dashdmx_on_filter_setup_error(GF_Filter * failed_filter,void * udta,GF_Err err)203 static void dashdmx_on_filter_setup_error(GF_Filter *failed_filter, void *udta, GF_Err err)
204 {
205 GF_DASHGroup *group = (GF_DASHGroup *)udta;
206 if (!udta) return;
207
208 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d download setup error %s\n", group->idx, gf_error_to_string(err) ));
209
210 gf_dash_set_group_download_state(group->ctx->dash, group->idx, err);
211 if (err) {
212 Bool group_done=GF_FALSE;
213
214 gf_dash_group_get_num_segments_ready(group->ctx->dash, group->idx, &group_done);
215
216 group->stats_uploaded = GF_TRUE;
217 group->segment_sent = GF_FALSE;
218 gf_filter_post_process_task(group->ctx->filter);
219 if (group_done) {
220 group->eos_detected = GF_TRUE;
221 } else {
222 group->in_error = GF_TRUE;
223 }
224 }
225 }
226
227 /*locates input service (demuxer) based on mime type or segment name*/
dashdmx_load_source(GF_DASHDmxCtx * ctx,u32 group_index,const char * mime,const char * init_segment_name,u64 start_range,u64 end_range)228 static GF_Err dashdmx_load_source(GF_DASHDmxCtx *ctx, u32 group_index, const char *mime, const char *init_segment_name, u64 start_range, u64 end_range)
229 {
230 GF_DASHGroup *group;
231 GF_Err e;
232 u32 url_type=0;
233 Bool has_sep = GF_FALSE;
234 char *sURL = NULL;
235 const char *base_url;
236 if (!init_segment_name) {
237 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] group %d Error locating plugin for segment - mime type %s\n", group_index, mime));
238 return GF_FILTER_NOT_FOUND;
239 }
240
241 GF_SAFEALLOC(group, GF_DASHGroup);
242 if (!group) return GF_OUT_OF_MEM;
243 group->ctx = ctx;
244 group->idx = group_index;
245 gf_dash_set_group_udta(ctx->dash, group_index, group);
246
247 base_url = gf_dash_get_url(ctx->dash);
248 if (!strnicmp(base_url, "http://", 7)) url_type=1;
249 else if (!strnicmp(base_url, "https://", 7)) url_type=2;
250 else url_type=0;
251
252 sURL = gf_malloc(sizeof(char) * (strlen(init_segment_name) + 200) );
253 strcpy(sURL, init_segment_name);
254 if (!strncmp(sURL, "isobmff://", 10)) {
255 if (url_type==1)
256 sprintf(sURL, "http://%s", init_segment_name);
257 else if (url_type==2)
258 sprintf(sURL, "http://%s", init_segment_name);
259 else
260 sprintf(sURL, "file://%s", init_segment_name);
261 }
262 //not from file system, set cache option
263 if (url_type) {
264 if (!ctx->store) {
265 if (!has_sep) { strcat(sURL, ":gpac"); has_sep = GF_TRUE; }
266 strcat(sURL, ":cache=mem");
267 }
268 else if (ctx->store==2) {
269 if (!has_sep) { strcat(sURL, ":gpac"); has_sep = GF_TRUE; }
270 strcat(sURL, ":cache=keep");
271 }
272 }
273
274 if (start_range || end_range) {
275 char szRange[500];
276 if (!has_sep) { strcat(sURL, ":gpac"); /* has_sep = GF_TRUE; */ }
277 snprintf(szRange, 500, ":range="LLU"-"LLU, start_range, end_range);
278 strcat(sURL, szRange);
279 }
280
281 group->seg_filter_src = gf_filter_connect_source(ctx->filter, sURL, NULL, &e);
282 if (!group->seg_filter_src) {
283 gf_free(sURL);
284 gf_free(group);
285 gf_dash_set_group_udta(ctx->dash, group_index, NULL);
286 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] group %d error locating plugin for segment - mime type %s name %s\n", group_index, mime, sURL));
287 return e;
288 }
289 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASHDmx] setting up group %d from %s\n", group->idx, sURL));
290
291 gf_filter_set_setup_failure_callback(ctx->filter, group->seg_filter_src, dashdmx_on_filter_setup_error, group);
292 gf_dash_group_discard_segment(ctx->dash, group->idx);
293 group->prev_is_init_segment = GF_TRUE;
294 group->nb_group_deps = gf_dash_group_get_num_groups_depending_on(ctx->dash, group_index);
295 group->current_group_dep = 0;
296 gf_free(sURL);
297 return GF_OK;
298 }
299
dashdmx_io_delete_cache_file(GF_DASHFileIO * dashio,GF_DASHFileIOSession session,const char * cache_url)300 void dashdmx_io_delete_cache_file(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *cache_url)
301 {
302 if (!session) {
303 return;
304 }
305 gf_dm_delete_cached_file_entry_session((GF_DownloadSession *)session, cache_url);
306 }
307
dashdmx_io_create(GF_DASHFileIO * dashio,Bool persistent,const char * url,s32 group_idx)308 GF_DASHFileIOSession dashdmx_io_create(GF_DASHFileIO *dashio, Bool persistent, const char *url, s32 group_idx)
309 {
310 GF_DownloadSession *sess;
311 const GF_PropertyValue *p;
312 GF_Err e;
313 u32 flags = GF_NETIO_SESSION_NOT_THREADED;
314 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta;
315
316 //we work in non-threaded mode, only MPD fetcher is allowed
317 if (group_idx>=0)
318 return NULL;
319
320 //crude hack when using gpac downloader to initialize the MPD pid: get the pointer to the download session
321 //this should be safe unless the mpd_pid is destroyed, which should only happen upon destruction of the DASH session
322 p = gf_filter_pid_get_property(ctx->mpd_pid, GF_PROP_PID_DOWNLOAD_SESSION);
323 if (p) {
324 sess = (GF_DownloadSession *) p->value.ptr;
325 if (!ctx->store) {
326 gf_dm_sess_force_memory_mode(sess);
327 }
328 ctx->reuse_download_session = GF_TRUE;
329 return (GF_DASHFileIOSession) sess;
330 }
331
332 if (!ctx->store) flags |= GF_NETIO_SESSION_MEMORY_CACHE;
333 if (persistent) flags |= GF_NETIO_SESSION_PERSISTENT;
334 sess = gf_dm_sess_new(ctx->dm, url, flags, NULL, NULL, &e);
335 return (GF_DASHFileIOSession) sess;
336 }
dashdmx_io_del(GF_DASHFileIO * dashio,GF_DASHFileIOSession session)337 void dashdmx_io_del(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
338 {
339 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta;
340 if (!ctx->reuse_download_session)
341 gf_dm_sess_del((GF_DownloadSession *)session);
342 }
dashdmx_io_init(GF_DASHFileIO * dashio,GF_DASHFileIOSession session)343 GF_Err dashdmx_io_init(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
344 {
345 return gf_dm_sess_process_headers((GF_DownloadSession *)session);
346 }
dashdmx_io_run(GF_DASHFileIO * dashio,GF_DASHFileIOSession session)347 GF_Err dashdmx_io_run(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
348 {
349 return gf_dm_sess_process((GF_DownloadSession *)session);
350 }
dashdmx_io_get_url(GF_DASHFileIO * dashio,GF_DASHFileIOSession session)351 const char *dashdmx_io_get_url(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
352 {
353 return gf_dm_sess_get_resource_name((GF_DownloadSession *)session);
354 }
dashdmx_io_get_cache_name(GF_DASHFileIO * dashio,GF_DASHFileIOSession session)355 const char *dashdmx_io_get_cache_name(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
356 {
357 return gf_dm_sess_get_cache_name((GF_DownloadSession *)session);
358 }
dashdmx_io_get_mime(GF_DASHFileIO * dashio,GF_DASHFileIOSession session)359 const char *dashdmx_io_get_mime(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
360 {
361 return gf_dm_sess_mime_type((GF_DownloadSession *)session);
362 }
dashdmx_io_get_header_value(GF_DASHFileIO * dashio,GF_DASHFileIOSession session,const char * header_name)363 const char *dashdmx_io_get_header_value(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *header_name)
364 {
365 return gf_dm_sess_get_header((GF_DownloadSession *)session, header_name);
366 }
dashdmx_io_get_utc_start_time(GF_DASHFileIO * dashio,GF_DASHFileIOSession session)367 u64 dashdmx_io_get_utc_start_time(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
368 {
369 return gf_dm_sess_get_utc_start((GF_DownloadSession *)session);
370 }
dashdmx_io_setup_from_url(GF_DASHFileIO * dashio,GF_DASHFileIOSession session,const char * url,s32 group_idx)371 GF_Err dashdmx_io_setup_from_url(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *url, s32 group_idx)
372 {
373 return gf_dm_sess_setup_from_url((GF_DownloadSession *)session, url, GF_FALSE);
374 }
dashdmx_io_set_range(GF_DASHFileIO * dashio,GF_DASHFileIOSession session,u64 start_range,u64 end_range,Bool discontinue_cache)375 GF_Err dashdmx_io_set_range(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, u64 start_range, u64 end_range, Bool discontinue_cache)
376 {
377 return gf_dm_sess_set_range((GF_DownloadSession *)session, start_range, end_range, discontinue_cache);
378 }
379
380 #if 0 //unused since we are in non threaded mode
381 void dashdmx_io_abort(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
382 {
383 gf_dm_sess_abort((GF_DownloadSession *)session);
384 }
385
386 u32 dashdmx_io_get_bytes_per_sec(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
387 {
388 u32 bps=0;
389 if (session) {
390 gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, NULL, NULL, &bps, NULL);
391 } else {
392 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta;
393 bps = gf_dm_get_data_rate(ctx->dm);
394 bps/=8;
395 }
396 return bps;
397 }
398 u32 dashdmx_io_get_total_size(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
399 {
400 u64 size=0;
401 gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, &size, NULL, NULL, NULL);
402 return (u32) size;
403 }
404 u32 dashdmx_io_get_bytes_done(GF_DASHFileIO *dashio, GF_DASHFileIOSession session)
405 {
406 u64 size=0;
407 gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, NULL, &size, NULL, NULL);
408 return (u32) size;
409 }
410 #endif
411
dashdmx_io_on_dash_event(GF_DASHFileIO * dashio,GF_DASHEventType dash_evt,s32 group_idx,GF_Err error_code)412 GF_Err dashdmx_io_on_dash_event(GF_DASHFileIO *dashio, GF_DASHEventType dash_evt, s32 group_idx, GF_Err error_code)
413 {
414 GF_Err e;
415 u32 i;
416 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta;
417
418 if (dash_evt==GF_DASH_EVENT_PERIOD_SETUP_ERROR) {
419 if (!ctx->initial_setup_done) {
420 gf_filter_setup_failure(ctx->filter, error_code);
421 ctx->initial_setup_done= GF_TRUE;
422 }
423 return GF_OK;
424 }
425
426 if (dash_evt==GF_DASH_EVENT_SELECT_GROUPS) {
427 #ifdef GPAC_ENABLE_COVERAGE
428 if (gf_sys_is_cov_mode()) {
429 gf_dash_groups_set_language(ctx->dash, gf_opts_get_key("core", "lang"));
430 //these are not used in the test suite (require JS)
431 gf_dash_switch_quality(ctx->dash, GF_TRUE, GF_TRUE);
432 }
433 #endif
434
435 //let the player decide which group to play: we declare everything
436 return GF_OK;
437 }
438
439 /*for all selected groups, create input service and connect to init/first segment*/
440 if (dash_evt==GF_DASH_EVENT_CREATE_PLAYBACK) {
441 //coverage of a few functions from old arch not deprecated (yet)
442 #ifdef GPAC_ENABLE_COVERAGE
443 if (gf_sys_is_cov_mode()) {
444 Bool done;
445 gf_dash_is_group_selected(ctx->dash, 0);
446 gf_dash_get_url(ctx->dash);
447 gf_dash_group_get_segment_init_keys(ctx->dash, 0, NULL);
448 gf_dash_group_get_max_segments_in_cache(ctx->dash, 0);
449 gf_dash_group_get_num_segments_ready(ctx->dash, 0, &done);
450 gf_dash_group_probe_current_download_segment_location(ctx->dash, 0, NULL, NULL, NULL, NULL, NULL);
451 gf_dash_group_loop_detected(ctx->dash, 0);
452 gf_dash_is_dynamic_mpd(ctx->dash);
453 gf_dash_group_get_language(ctx->dash, 0);
454 gf_dash_group_get_num_components(ctx->dash, 0);
455 gf_dash_group_get_download_rate(ctx->dash, 0);
456 gf_dash_get_utc_drift_estimate(ctx->dash);
457
458
459 //these are not used in the test suite (require decoding)
460 gf_dash_group_set_codec_stat(ctx->dash, 0, 0, 0, 0, 0, GF_FALSE, GF_FALSE);
461 gf_dash_group_set_buffer_levels(ctx->dash, 0, 0, 0, 0);
462
463 //these are not used in the test suite (require JS)
464 if (ctx->algo==GF_DASH_ALGO_NONE)
465 gf_dash_set_automatic_switching(ctx->dash, GF_FALSE);
466 gf_dash_group_select_quality(ctx->dash, (u32) -1, NULL, 0);
467
468 //these are not used yet in the test suite (require TEMI)
469 gf_dash_override_ntp(ctx->dash, 0);
470
471 //these are not used yet in the test suite (require tiling + long run)
472 gf_dash_group_set_quality_degradation_hint(ctx->dash, 0, 0);
473 gf_dash_group_set_visible_rect(ctx->dash, 0, 0, 0, 0, 0, 0);
474 //this happen only when error downloading a segment
475 gf_dash_set_group_download_state(ctx->dash, 0, GF_OK);
476 }
477 #endif
478 /*select input services if possible*/
479 for (i=0; i<gf_dash_get_group_count(ctx->dash); i++) {
480 const char *mime, *init_segment;
481 u64 start_range, end_range;
482 u32 j;
483 Bool playable = GF_TRUE;
484 //let the player decide which group to play
485 if (!gf_dash_is_group_selectable(ctx->dash, i))
486 continue;
487
488 j=0;
489 while (1) {
490 const char *desc_id, *desc_scheme, *desc_value;
491 if (! gf_dash_group_enum_descriptor(ctx->dash, i, GF_MPD_DESC_ESSENTIAL_PROPERTIES, j, &desc_id, &desc_scheme, &desc_value))
492 break;
493 j++;
494 if (!strcmp(desc_scheme, "urn:mpeg:dash:srd:2014")) {
495 } else {
496 playable = GF_FALSE;
497 break;
498 }
499 }
500 if (!playable) {
501 gf_dash_group_select(ctx->dash, i, GF_FALSE);
502 continue;
503 }
504
505 if (gf_dash_group_has_dependent_group(ctx->dash, i) >=0 ) {
506 gf_dash_group_select(ctx->dash, i, GF_TRUE);
507 continue;
508 }
509
510 mime = gf_dash_group_get_segment_mime(ctx->dash, i);
511 init_segment = gf_dash_group_get_segment_init_url(ctx->dash, i, &start_range, &end_range);
512
513 e = dashdmx_load_source(ctx, i, mime, init_segment, start_range, end_range);
514 if (e != GF_OK) {
515 gf_dash_group_select(ctx->dash, i, GF_FALSE);
516 } else {
517 u32 w, h;
518 /*connect our media service*/
519 gf_dash_group_get_video_info(ctx->dash, i, &w, &h);
520 if (w && h && w>ctx->width && h>ctx->height) {
521 ctx->width = w;
522 ctx->height = h;
523 }
524 if (gf_dash_group_get_srd_max_size_info(ctx->dash, i, &w, &h)) {
525 ctx->width = w;
526 ctx->height = h;
527 }
528 if (ctx->closed) return GF_OK;
529 }
530 }
531
532 if (!ctx->initial_setup_done) {
533 ctx->initial_setup_done = GF_TRUE;
534 }
535
536 //we had a seek outside of the period we were setting up, during period setup !
537 //request the seek again from the player
538 if (ctx->seek_request>=0) {
539 #ifdef FILTER_FIXME
540 GF_NetworkCommand com;
541 memset(&com, 0, sizeof(GF_NetworkCommand));
542 com.command_type = GF_NET_SERVICE_SEEK;
543 com.play.start_range = ctx->seek_request;
544 ctx->seek_request = 0;
545 gf_service_command(ctx->service, &com, GF_OK);
546 #endif
547
548 }
549 return GF_OK;
550 }
551
552 /*for all running services, stop service*/
553 if (dash_evt==GF_DASH_EVENT_DESTROY_PLAYBACK) {
554 for (i=0; i<gf_dash_get_group_count(ctx->dash); i++) {
555 GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, i);
556 if (!group) continue;
557 if (group->seg_filter_src) {
558 gf_filter_remove_src(ctx->filter, group->seg_filter_src);
559 group->seg_filter_src = NULL;
560 }
561 gf_free(group);
562 gf_dash_set_group_udta(ctx->dash, i, NULL);
563 }
564 for (i=0; i<gf_filter_get_opid_count(ctx->filter); i++) {
565 GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i);
566 gf_filter_pid_set_udta(opid, NULL);
567 }
568 return GF_OK;
569 }
570 if (dash_evt==GF_DASH_EVENT_ABORT_DOWNLOAD) {
571 GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, group_idx);
572 if (group) {
573 GF_FilterEvent evt;
574 GF_FEVT_INIT(evt, GF_FEVT_SOURCE_SWITCH, NULL);
575 evt.seek.start_offset = -1;
576 evt.seek.source_switch = NULL;
577 gf_filter_send_event(group->seg_filter_src, &evt, GF_FALSE);
578 }
579 return GF_OK;
580 }
581
582 if (dash_evt==GF_DASH_EVENT_QUALITY_SWITCH) {
583 if (group_idx>=0) {
584 GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, group_idx);
585 if (!group) {
586 group_idx = gf_dash_group_has_dependent_group(ctx->dash, group_idx);
587 group = gf_dash_get_group_udta(ctx->dash, group_idx);
588 }
589 if (group) {
590 for (i=0; i<gf_filter_get_opid_count(ctx->filter); i++) {
591 GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i);
592 if (gf_filter_pid_get_udta(opid) != group) continue;
593
594 s32 sel = gf_dash_group_get_active_quality(ctx->dash, group_idx);
595 if (sel>=0) {
596 gf_filter_pid_set_property_str(opid, "has:selected", &PROP_UINT(sel) );
597 }
598 gf_filter_pid_set_property_str(opid, "has:auto", &PROP_UINT(gf_dash_get_automatic_switching(ctx->dash) ) );
599 gf_filter_pid_set_property_str(opid, "has:tilemode", &PROP_UINT(gf_dash_get_tile_adaptation_mode(ctx->dash) ) );
600 }
601 }
602 }
603 return GF_OK;
604 }
605 if (dash_evt==GF_DASH_EVENT_TIMESHIFT_UPDATE) {
606 Double timeshift = gf_dash_get_timeshift_buffer_pos(ctx->dash);
607 for (i=0; i<gf_filter_get_opid_count(ctx->filter); i++) {
608 GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i);
609 gf_filter_pid_set_info(opid, GF_PROP_PID_TIMESHIFT_TIME, &PROP_DOUBLE(timeshift) );
610 }
611 return GF_OK;
612 }
613 if (dash_evt==GF_DASH_EVENT_TIMESHIFT_OVERFLOW) {
614 u32 evttype = (group_idx>=0) ? 2 : 1;
615 for (i=0; i<gf_filter_get_opid_count(ctx->filter); i++) {
616 GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i);
617 gf_filter_pid_set_info(opid, GF_PROP_PID_TIMESHIFT_STATE, &PROP_UINT(evttype) );
618 }
619 }
620
621 if (dash_evt==GF_DASH_EVENT_CODEC_STAT_QUERY) {
622 GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, group_idx);
623 for (i=0; i<gf_filter_get_opid_count(ctx->filter); i++) {
624 GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i);
625 if (gf_filter_pid_get_udta(opid) == group) {
626 GF_FilterPidStatistics stats;
627 if (gf_filter_pid_get_statistics(opid, &stats, GF_STATS_DECODER_SINK) != GF_OK)
628 continue;
629 if (stats.disconnected)
630 continue;
631
632 if (!stats.nb_processed) stats.nb_processed=1;
633 if (!stats.nb_saps) stats.nb_saps=1;
634
635 gf_dash_group_set_codec_stat(ctx->dash, group_idx, (u32) (stats.total_process_time/stats.nb_processed), stats.max_process_time, (u32) (stats.total_sap_process_time/stats.nb_saps), stats.max_sap_process_time, GF_FALSE, GF_FALSE);
636
637
638 gf_dash_group_set_buffer_levels(ctx->dash, group_idx, (u32) (stats.min_playout_time/1000), (u32) (stats.max_buffer_time/1000), (u32) (stats.buffer_time/1000) );
639
640 break;
641 }
642 }
643 }
644 return GF_OK;
645 }
646
dashdmx_setup_buffer(GF_DASHDmxCtx * ctx,GF_DASHGroup * group)647 static void dashdmx_setup_buffer(GF_DASHDmxCtx *ctx, GF_DASHGroup *group)
648 {
649 u32 buffer_ms, play_buf_ms;
650 gf_filter_get_output_buffer_max(ctx->filter, &buffer_ms, &play_buf_ms);
651 buffer_ms /= 1000;
652 play_buf_ms /= 1000;
653
654 //use min buffer from MPD
655 if (ctx->use_bmin) {
656 u64 mpd_buffer_ms = gf_dash_get_min_buffer_time(ctx->dash);
657 if (mpd_buffer_ms > buffer_ms)
658 buffer_ms = (u32) mpd_buffer_ms;
659 }
660 if (buffer_ms) {
661 gf_dash_set_user_buffer(ctx->dash, buffer_ms);
662 }
663 gf_dash_group_set_max_buffer_playout(ctx->dash, group->idx, play_buf_ms);
664 }
665
666 /*check in all groups if the service can support reverse playback (speed<0); return GF_OK only if service is supported in all groups*/
dashdmx_dash_playback_mode(GF_DASHDmxCtx * ctx)667 static u32 dashdmx_dash_playback_mode(GF_DASHDmxCtx *ctx)
668 {
669 u32 pmode, mode, i, count = gf_filter_get_ipid_count(ctx->filter);
670 mode = GF_PLAYBACK_MODE_REWIND;
671 for (i=0; i<count; i++) {
672 const GF_PropertyValue *p;
673 GF_FilterPid *pid = gf_filter_get_ipid(ctx->filter, i);
674 if (ctx->mpd_pid == pid) continue;
675
676 p = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE);
677 pmode = p ? p->value.uint : GF_PLAYBACK_MODE_REWIND;
678 if (pmode < mode) mode = pmode;
679 }
680 return mode;
681 }
682
683
dashdmx_group_idx_from_pid(GF_DASHDmxCtx * ctx,GF_FilterPid * src_pid)684 static s32 dashdmx_group_idx_from_pid(GF_DASHDmxCtx *ctx, GF_FilterPid *src_pid)
685 {
686 s32 i;
687
688 for (i=0; (u32) i < gf_dash_get_group_count(ctx->dash); i++) {
689 GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, i);
690 if (!group) continue;
691 if (gf_filter_pid_is_filter_in_parents(src_pid, group->seg_filter_src))
692 return i;
693 }
694 return -1;
695 }
696
dashdmx_create_output_pid(GF_Filter * filter,GF_FilterPid * input,u32 * run_status)697 static GF_FilterPid *dashdmx_create_output_pid(GF_Filter *filter, GF_FilterPid *input, u32 *run_status)
698 {
699 u32 global_score=0;
700 GF_FilterPid *output_pid = NULL;
701 u32 i, count = gf_filter_get_opid_count(filter);
702 const GF_PropertyValue *codec, *streamtype, *role, *lang;
703
704 *run_status = 0;
705
706 streamtype = gf_filter_pid_get_property(input, GF_PROP_PID_STREAM_TYPE);
707 if (streamtype && streamtype->value.uint==GF_STREAM_ENCRYPTED)
708 streamtype = gf_filter_pid_get_property(input, GF_PROP_PID_ORIG_STREAM_TYPE);
709
710 codec = gf_filter_pid_get_property(input, GF_PROP_PID_CODECID);
711 role = gf_filter_pid_get_property(input, GF_PROP_PID_ROLE);
712 lang = gf_filter_pid_get_property(input, GF_PROP_PID_LANGUAGE);
713
714 for (i=0; i<count; i++) {
715 u32 score;
716 const GF_PropertyValue *o_codec, *o_streamtype, *o_role, *o_lang;
717 GF_FilterPid *opid = gf_filter_get_opid(filter, i);
718 //in use by us
719 if (gf_filter_pid_get_udta(opid)) continue;
720
721 o_streamtype = gf_filter_pid_get_property(opid, GF_PROP_PID_STREAM_TYPE);
722 if (o_streamtype && o_streamtype->value.uint==GF_STREAM_ENCRYPTED)
723 o_streamtype = gf_filter_pid_get_property(opid, GF_PROP_PID_ORIG_STREAM_TYPE);
724
725 o_codec = gf_filter_pid_get_property(opid, GF_PROP_PID_CODECID);
726 o_role = gf_filter_pid_get_property(opid, GF_PROP_PID_ROLE);
727 o_lang = gf_filter_pid_get_property(opid, GF_PROP_PID_LANGUAGE);
728
729 if (!o_streamtype || !streamtype || !gf_props_equal(streamtype, o_streamtype))
730 continue;
731
732 score = 1;
733 //get highest priority for streams with same role
734 if (o_role && role && gf_props_equal(role, o_role)) score += 10;
735 //then high priority for streams with same lang
736 if (o_lang && lang && gf_props_equal(lang, o_lang)) score += 5;
737
738 //otherwise favour streams with same codec
739 if (!o_codec && codec && gf_props_equal(codec, o_codec)) score++;
740
741 if (global_score<score) {
742 global_score = score;
743 output_pid = opid;
744 }
745 }
746 if (output_pid) {
747 *run_status = gf_filter_pid_is_playing(output_pid) ? 1 : 2;
748 return output_pid;
749 }
750 //none found create a new PID
751 return gf_filter_pid_new(filter);
752 }
753
dashdmx_declare_properties(GF_DASHDmxCtx * ctx,GF_DASHGroup * group,u32 group_idx,GF_FilterPid * opid,GF_FilterPid * ipid)754 static void dashdmx_declare_properties(GF_DASHDmxCtx *ctx, GF_DASHGroup *group, u32 group_idx, GF_FilterPid *opid, GF_FilterPid *ipid)
755 {
756 GF_DASHQualityInfo qinfo;
757 GF_PropertyValue qualities, srd, srdref;
758 GF_Err e;
759 u32 count, i;
760 u32 dur, mode;
761
762 gf_filter_pid_copy_properties(opid, ipid);
763
764 if (!group->nb_group_deps) {
765 u32 asid;
766 char as_name[100];
767 asid = gf_dash_group_get_as_id(ctx->dash, group_idx);
768 if (!asid) asid = group_idx+1;
769 sprintf(as_name, "AS%d", asid);
770 gf_filter_pid_set_name(opid, as_name);
771 }
772
773 mode = dashdmx_dash_playback_mode(ctx);
774 gf_filter_pid_set_property(opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(mode));
775
776 if (ctx->max_res && gf_filter_pid_get_property(ipid, GF_PROP_PID_WIDTH)) {
777 gf_filter_pid_set_property(opid, GF_PROP_SERVICE_WIDTH, &PROP_UINT(ctx->width));
778 gf_filter_pid_set_property(opid, GF_PROP_SERVICE_HEIGHT, &PROP_UINT(ctx->height));
779 }
780
781 dur = (u32) (1000*gf_dash_get_duration(ctx->dash) );
782 if (dur>0)
783 gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(dur, 1000) );
784
785 dur = (1000*gf_dash_group_get_time_shift_buffer_depth(ctx->dash, group_idx) );
786 if (dur>0)
787 gf_filter_pid_set_property(opid, GF_PROP_PID_TIMESHIFT_DEPTH, &PROP_FRAC_INT(dur, 1000) );
788
789
790 if (ctx->use_bmin) {
791 u32 max = gf_dash_get_min_buffer_time(ctx->dash);
792 gf_filter_pid_set_property_str(opid, "BufferLength", &PROP_UINT(max));
793 }
794
795 memset(&qualities, 0, sizeof(GF_PropertyValue));
796 qualities.type = GF_PROP_STRING_LIST;
797 qualities.value.string_list = gf_list_new();
798
799 count = gf_dash_group_get_num_qualities(ctx->dash, group_idx);
800 for (i=0; i<count; i++) {
801 char szInfo[500];
802 char *qdesc = NULL;
803
804 e = gf_dash_group_get_quality_info(ctx->dash, group_idx, i, &qinfo);
805 if (e) break;
806 if (!qinfo.ID) qinfo.ID="";
807 if (!qinfo.mime) qinfo.mime="unknown";
808 if (!qinfo.codec) qinfo.codec="codec";
809
810 snprintf(szInfo, 500, "id=%s", qinfo.ID);
811
812 e = gf_dynstrcat(&qdesc, szInfo, "::");
813 if (e) break;
814
815 snprintf(szInfo, 500, "codec=%s", qinfo.codec);
816 e = gf_dynstrcat(&qdesc, szInfo, "::");
817 if (e) break;
818
819 snprintf(szInfo, 500, "mime=%s", qinfo.mime);
820 e = gf_dynstrcat(&qdesc, szInfo, "::");
821 if (e) break;
822
823 snprintf(szInfo, 500, "bw=%d", qinfo.bandwidth);
824 e = gf_dynstrcat(&qdesc, szInfo, "::");
825 if (e) break;
826
827 if (qinfo.disabled) {
828 e = gf_dynstrcat(&qdesc, "disabled", "::");
829 if (e) break;
830 }
831
832 if (qinfo.width && qinfo.height) {
833 snprintf(szInfo, 500, "w=%d", qinfo.width);
834 e = gf_dynstrcat(&qdesc, szInfo, "::");
835 if (e) break;
836
837 snprintf(szInfo, 500, "h=%d", qinfo.height);
838 e = gf_dynstrcat(&qdesc, szInfo, "::");
839 if (e) break;
840
841 if (qinfo.interlaced) {
842 e = gf_dynstrcat(&qdesc, "interlaced", "::");
843 if (e) break;
844 }
845 if (qinfo.fps_den) {
846 snprintf(szInfo, 500, "fps=%d/%d", qinfo.fps_num, qinfo.fps_den);
847 } else {
848 snprintf(szInfo, 500, "fps=%d", qinfo.fps_num);
849 }
850 e = gf_dynstrcat(&qdesc, szInfo, "::");
851 if (e) break;
852
853 if (qinfo.par_den && qinfo.par_num && (qinfo.par_den != qinfo.par_num)) {
854 snprintf(szInfo, 500, "sar=%d/%d", qinfo.par_num, qinfo.par_den);
855 e = gf_dynstrcat(&qdesc, szInfo, "::");
856 if (e) break;
857 }
858 }
859 if (qinfo.sample_rate) {
860 snprintf(szInfo, 500, "sr=%d", qinfo.sample_rate);
861 e = gf_dynstrcat(&qdesc, szInfo, "::");
862 if (e) break;
863
864 snprintf(szInfo, 500, "ch=%d", qinfo.nb_channels);
865 e = gf_dynstrcat(&qdesc, szInfo, "::");
866 if (e) break;
867 }
868 gf_list_add(qualities.value.string_list, qdesc);
869 qdesc = NULL;
870 }
871 gf_filter_pid_set_info_str(opid, "has:qualities", &qualities);
872
873 const char *title, *source;
874 gf_dash_get_info(ctx->dash, &title, &source);
875 gf_filter_pid_set_info(opid, GF_PROP_PID_SERVICE_NAME, &PROP_STRING(title) );
876 gf_filter_pid_set_info(opid, GF_PROP_PID_SERVICE_PROVIDER, &PROP_STRING(source) );
877
878 title = NULL;
879 gf_dash_group_enum_descriptor(ctx->dash, group_idx, GF_MPD_DESC_ROLE, 0, NULL, NULL, &title);
880 if (title)
881 gf_filter_pid_set_property(opid, GF_PROP_PID_ROLE, &PROP_STRING(title) );
882
883 title = NULL;
884 gf_dash_group_enum_descriptor(ctx->dash, group_idx, GF_MPD_DESC_ACCESSIBILITY, 0, NULL, NULL, &title);
885 if (title)
886 gf_filter_pid_set_property_str(opid, "accessibility", &PROP_STRING(title) );
887
888 title = NULL;
889 gf_dash_group_enum_descriptor(ctx->dash, group_idx, GF_MPD_DESC_RATING, 0, NULL, NULL, &title);
890 if (title)
891 gf_filter_pid_set_property_str(opid, "rating", &PROP_STRING(title) );
892
893 if (!gf_sys_is_test_mode()) {
894 gf_filter_pid_set_info_str(opid, "ntpdiff", &PROP_SINT(gf_dash_get_utc_drift_estimate(ctx->dash) ) );
895 }
896
897 #ifdef FILTER_FIXME
898 //need to implement back dependent group SRD and qualities (fir HEVC tiles)
899 if (com->srd.dependent_group_index) {
900 if (com->srd.dependent_group_index > gf_dash_group_get_num_groups_depending_on(ctx->dash, idx))
901 return GF_BAD_PARAM;
902
903 idx = gf_dash_get_dependent_group_index(ctx->dash, idx, com->srd.dependent_group_index-1);
904 }
905 #endif
906
907 memset(&srd, 0, sizeof(GF_PropertyValue));
908 memset(&srdref, 0, sizeof(GF_PropertyValue));
909 srd.type = GF_PROP_VEC4I;
910 srdref.type = GF_PROP_VEC2I;
911 if (gf_dash_group_get_srd_info(ctx->dash, group_idx, NULL,
912 &srd.value.vec4i.x,
913 &srd.value.vec4i.y,
914 &srd.value.vec4i.z,
915 &srd.value.vec4i.w,
916 &srdref.value.vec2i.x,
917 &srdref.value.vec2i.y)) {
918
919 gf_filter_pid_set_property(opid, GF_PROP_PID_SRD, &srd);
920 gf_filter_pid_set_property(opid, GF_PROP_PID_SRD_REF, &srdref);
921 }
922
923 //setup initial quality - this is disabled in test mode for the time being (invalidates all dash playback hashes)
924 if (!gf_sys_is_test_mode())
925 dashdmx_io_on_dash_event(&ctx->dash_io, GF_DASH_EVENT_QUALITY_SWITCH, group->idx, GF_OK);
926 }
927
dashdmx_configure_pid(GF_Filter * filter,GF_FilterPid * pid,Bool is_remove)928 static GF_Err dashdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
929 {
930 s32 group_idx;
931 GF_FilterPid *opid;
932 GF_Err e;
933 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter);
934 GF_DASHGroup *group;
935
936 if (is_remove) {
937 //TODO
938 if (pid==ctx->mpd_pid) {
939 u32 i;
940 for (i=0; i<gf_filter_get_opid_count(filter); i++) {
941 opid = gf_filter_get_opid(filter, i);
942 group = gf_filter_pid_get_udta(opid);
943 if (group && group->seg_filter_src) {
944 gf_filter_remove_src(filter, group->seg_filter_src);
945 }
946 }
947 gf_dash_close(ctx->dash);
948 ctx->mpd_pid = NULL;
949 } else {
950 opid = gf_filter_pid_get_udta(pid);
951 if (opid) {
952 if (!ctx->mpd_pid) {
953 gf_filter_pid_remove(opid);
954 } else if (gf_dash_all_groups_done(ctx->dash) && gf_dash_in_last_period(ctx->dash, GF_TRUE)) {
955 gf_filter_pid_remove(opid);
956 } else {
957 gf_filter_pid_set_udta(opid, NULL);
958 gf_filter_pid_set_udta(pid, NULL);
959 }
960 }
961 }
962 return GF_OK;
963 }
964 if (! gf_filter_pid_check_caps(pid)) {
965 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] Mismatch in input pid caps\n"));
966 return GF_NOT_SUPPORTED;
967 }
968
969 //configure MPD pid
970 if (!ctx->mpd_pid) {
971 const GF_PropertyValue *p;
972 p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL);
973 if (!p || !p->value.string) {
974 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] no URL on MPD pid\n"));
975 return GF_NOT_SUPPORTED;
976 }
977
978 gf_filter_pid_set_framing_mode(pid, GF_TRUE);
979 ctx->mpd_pid = pid;
980 ctx->seek_request = -1;
981 ctx->nb_playing = 0;
982
983 e = gf_dash_open(ctx->dash, p->value.string);
984 if (e) {
985 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] Error - cannot initialize DASH Client for %s: %s\n", p->value.string, gf_error_to_string(e)));
986 gf_filter_setup_failure(filter, e);
987 return e;
988 }
989 //we have a redirect URL on mpd pid, this means this comes from a service feeding the cache so we won't get any data on the pid.
990 //request a process task
991 p = gf_filter_pid_get_property(pid, GF_PROP_PID_REDIRECT_URL);
992 if (p && p->value.string)
993 gf_filter_post_process_task(filter);
994 return GF_OK;
995 } else if (ctx->mpd_pid == pid) {
996 return GF_OK;
997 }
998
999 //figure out group for this pid
1000 group_idx = dashdmx_group_idx_from_pid(ctx, pid);
1001 if (group_idx<0) {
1002 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] Failed to locate adaptation set for input pid\n"));
1003 return GF_SERVICE_ERROR;
1004 }
1005 group = gf_dash_get_group_udta(ctx->dash, group_idx);
1006
1007 //initial configure
1008 opid = gf_filter_pid_get_udta(pid);
1009 if (opid == NULL) {
1010 u32 run_status;
1011 group = gf_dash_get_group_udta(ctx->dash, group_idx);
1012 assert(group);
1013 //for now we declare every component from the input source
1014 opid = dashdmx_create_output_pid(filter, pid, &run_status);
1015 gf_filter_pid_set_udta(opid, group);
1016 gf_filter_pid_set_udta(pid, opid);
1017 group->nb_pids ++;
1018
1019 if (run_status) {
1020 GF_FilterEvent evt;
1021 GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid);
1022 gf_filter_pid_send_event(pid, &evt);
1023 group->is_playing = GF_TRUE;
1024 gf_dash_group_select(ctx->dash, group->idx, GF_TRUE);
1025
1026 if (run_status==2) {
1027 gf_dash_set_group_done(ctx->dash, group->idx, GF_TRUE);
1028 gf_dash_group_select(ctx->dash, group->idx, GF_FALSE);
1029
1030 GF_FEVT_INIT(evt, GF_FEVT_STOP, pid);
1031 gf_filter_pid_send_event(pid, &evt);
1032 group->is_playing = GF_FALSE;
1033 }
1034 }
1035 }
1036 dashdmx_declare_properties(ctx, group, group_idx, opid, pid);
1037
1038
1039 //reset the file cache property (init segment could be cached but not the rest)
1040 gf_filter_pid_set_property(opid, GF_PROP_PID_FILE_CACHED, NULL);
1041
1042 return GF_OK;
1043 }
1044
dashdmx_initialize(GF_Filter * filter)1045 static GF_Err dashdmx_initialize(GF_Filter *filter)
1046 {
1047 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter);
1048 ctx->filter = filter;
1049 ctx->dm = gf_filter_get_download_manager(filter);
1050 if (!ctx->dm) return GF_SERVICE_ERROR;
1051
1052 ctx->dash_io.udta = ctx;
1053 ctx->dash_io.delete_cache_file = dashdmx_io_delete_cache_file;
1054 ctx->dash_io.create = dashdmx_io_create;
1055 ctx->dash_io.del = dashdmx_io_del;
1056 ctx->dash_io.init = dashdmx_io_init;
1057 ctx->dash_io.run = dashdmx_io_run;
1058 ctx->dash_io.get_url = dashdmx_io_get_url;
1059 ctx->dash_io.get_cache_name = dashdmx_io_get_cache_name;
1060 ctx->dash_io.get_mime = dashdmx_io_get_mime;
1061 ctx->dash_io.get_header_value = dashdmx_io_get_header_value;
1062 ctx->dash_io.get_utc_start_time = dashdmx_io_get_utc_start_time;
1063 ctx->dash_io.setup_from_url = dashdmx_io_setup_from_url;
1064 ctx->dash_io.set_range = dashdmx_io_set_range;
1065
1066 #if 0 //unused since we are in non threaded mode
1067 ctx->dash_io.abort = dashdmx_io_abort;
1068 ctx->dash_io.get_bytes_per_sec = dashdmx_io_get_bytes_per_sec;
1069 ctx->dash_io.get_total_size = dashdmx_io_get_total_size;
1070 ctx->dash_io.get_bytes_done = dashdmx_io_get_bytes_done;
1071 #endif
1072
1073 ctx->dash_io.on_dash_event = dashdmx_io_on_dash_event;
1074
1075 ctx->dash = gf_dash_new(&ctx->dash_io, GF_DASH_THREAD_NONE, 0, ctx->auto_switch, (ctx->store==2) ? GF_TRUE : GF_FALSE, (ctx->algo==GF_DASH_ALGO_NONE) ? GF_TRUE : GF_FALSE, ctx->start_with, ctx->timeshift);
1076
1077 if (!ctx->dash) {
1078 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] Error - cannot create DASH Client\n"));
1079 return GF_IO_ERR;
1080 }
1081
1082 if (ctx->screen_res) {
1083 GF_FilterSessionCaps caps;
1084 gf_filter_get_session_caps(ctx->filter, &caps);
1085 gf_dash_set_max_resolution(ctx->dash, caps.max_screen_width, caps.max_screen_height, caps.max_screen_bpp);
1086 }
1087
1088 gf_dash_set_algo(ctx->dash, ctx->algo);
1089 gf_dash_set_utc_shift(ctx->dash, ctx->shift_utc);
1090 gf_dash_set_atsc_ast_shift(ctx->dash, ctx->atsc_shift);
1091 gf_dash_enable_utc_drift_compensation(ctx->dash, ctx->server_utc);
1092 gf_dash_set_tile_adaptation_mode(ctx->dash, ctx->tile_mode, ctx->tiles_rate);
1093
1094 gf_dash_set_min_timeout_between_404(ctx->dash, ctx->delay40X);
1095 gf_dash_set_segment_expiration_threshold(ctx->dash, ctx->exp_threshold);
1096 gf_dash_set_switching_probe_count(ctx->dash, ctx->switch_count);
1097 gf_dash_set_agressive_adaptation(ctx->dash, ctx->aggressive);
1098 gf_dash_debug_group(ctx->dash, ctx->debug_as);
1099 gf_dash_disable_speed_adaptation(ctx->dash, ctx->speedadapt);
1100 gf_dash_ignore_xlink(ctx->dash, ctx->noxlink);
1101 gf_dash_set_period_xlink_query_string(ctx->dash, ctx->query);
1102 gf_dash_set_low_latency_mode(ctx->dash, ctx->lowlat);
1103 if (ctx->split_as)
1104 gf_dash_split_adaptation_sets(ctx->dash);
1105
1106 ctx->initial_play = GF_TRUE;
1107 gf_filter_block_eos(filter, GF_TRUE);
1108
1109 //for coverage
1110 #ifdef GPAC_ENABLE_COVERAGE
1111 if (gf_sys_is_cov_mode()) {
1112 dashdmx_on_filter_setup_error(NULL, NULL, GF_OK);
1113 }
1114 #endif
1115 return GF_OK;
1116 }
1117
1118
dashdmx_finalize(GF_Filter * filter)1119 static void dashdmx_finalize(GF_Filter *filter)
1120 {
1121 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter);
1122 assert(ctx);
1123
1124 if (ctx->dash)
1125 gf_dash_del(ctx->dash);
1126 }
1127
dashdmx_process_event(GF_Filter * filter,const GF_FilterEvent * fevt)1128 static Bool dashdmx_process_event(GF_Filter *filter, const GF_FilterEvent *fevt)
1129 {
1130 u32 i, count;
1131 GF_FilterEvent src_evt;
1132 GF_FilterPid *ipid;
1133 u64 pto;
1134 u32 timescale;
1135 Bool initial_play;
1136 Double offset;
1137 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter);
1138 GF_DASHGroup *group;
1139
1140
1141 switch (fevt->base.type) {
1142 case GF_FEVT_QUALITY_SWITCH:
1143 if (fevt->quality_switch.set_tile_mode_plus_one) {
1144 GF_DASHTileAdaptationMode tile_mode = fevt->quality_switch.set_tile_mode_plus_one - 1;
1145 gf_dash_set_tile_adaptation_mode(ctx->dash, tile_mode, 100);
1146 } else if (fevt->quality_switch.q_idx < 0) {
1147 gf_dash_set_automatic_switching(ctx->dash, 1);
1148 } else if (fevt->base.on_pid) {
1149 s32 idx;
1150 group = gf_filter_pid_get_udta(fevt->base.on_pid);
1151 if (!group) return GF_TRUE;
1152 idx = group->idx;
1153
1154 gf_dash_group_set_quality_degradation_hint(ctx->dash, group->idx, fevt->quality_switch.quality_degradation);
1155
1156 if (fevt->quality_switch.dependent_group_index) {
1157 if (fevt->quality_switch.dependent_group_index > gf_dash_group_get_num_groups_depending_on(ctx->dash, group->idx))
1158 return GF_BAD_PARAM;
1159
1160 idx = gf_dash_get_dependent_group_index(ctx->dash, group->idx, fevt->quality_switch.dependent_group_index-1);
1161 if (idx==-1) return GF_BAD_PARAM;
1162 }
1163
1164 gf_dash_set_automatic_switching(ctx->dash, 0);
1165 gf_dash_group_select_quality(ctx->dash, idx, NULL, fevt->quality_switch.q_idx);
1166 } else {
1167 gf_dash_switch_quality(ctx->dash, fevt->quality_switch.up, ctx->immediate);
1168 }
1169 return GF_TRUE;
1170
1171 #ifdef FILTER_FIXME
1172
1173 case GF_NET_ASSOCIATED_CONTENT_TIMING:
1174 gf_dash_override_ntp(ctx->dash, com->addon_time.ntp);
1175 return GF_OK;
1176 #endif
1177 default:
1178 break;
1179 }
1180
1181 /*not supported*/
1182 if (!fevt->base.on_pid) return GF_NOT_SUPPORTED;
1183 group = gf_filter_pid_get_udta(fevt->base.on_pid);
1184 if (!group) return GF_NOT_SUPPORTED;
1185 count = gf_filter_get_ipid_count(filter);
1186 ipid = NULL;
1187 for (i=0; i<count; i++) {
1188 ipid = gf_filter_get_ipid(filter, i);
1189 if (gf_filter_pid_get_udta(ipid) == fevt->base.on_pid) break;
1190 ipid = NULL;
1191 }
1192
1193 switch (fevt->base.type) {
1194 case GF_FEVT_VISIBILITY_HINT:
1195 group = gf_filter_pid_get_udta(fevt->base.on_pid);
1196 if (!group) return GF_TRUE;
1197
1198 gf_dash_group_set_visible_rect(ctx->dash, group->idx, fevt->visibility_hint.min_x, fevt->visibility_hint.max_x, fevt->visibility_hint.min_y, fevt->visibility_hint.max_y, fevt->visibility_hint.is_gaze);
1199 return GF_TRUE;
1200
1201 case GF_FEVT_PLAY:
1202 src_evt = *fevt;
1203 group->is_playing = GF_TRUE;
1204 ctx->check_eos = GF_FALSE;
1205
1206 //adjust play range from media timestamps to MPD time
1207 if (fevt->play.timestamp_based) {
1208
1209 if (fevt->play.timestamp_based==1) {
1210 gf_dash_group_get_presentation_time_offset(ctx->dash, group->idx, &pto, ×cale);
1211 offset = (Double) pto;
1212 offset /= timescale;
1213 src_evt.play.start_range -= offset;
1214 if (src_evt.play.start_range < 0) src_evt.play.start_range = 0;
1215 }
1216
1217 group->is_timestamp_based = 1;
1218 group->pto_setup = 0;
1219 ctx->media_start_range = fevt->play.start_range;
1220 } else {
1221 group->is_timestamp_based = 0;
1222 group->pto_setup = 0;
1223 if (fevt->play.start_range<0) src_evt.play.start_range = 0;
1224 //in m3u8, we need also media start time for mapping time
1225 if (gf_dash_is_m3u8(ctx->dash))
1226 ctx->media_start_range = fevt->play.start_range;
1227 }
1228
1229 //we cannot handle seek request outside of a period being setup, this messes up our internal service setup
1230 //we postpone the seek and will request it later on ...
1231 if (gf_dash_in_period_setup(ctx->dash)) {
1232 u64 p_end = gf_dash_get_period_duration(ctx->dash);
1233 if (p_end) {
1234 p_end += gf_dash_get_period_start(ctx->dash);
1235 if (p_end<1000*fevt->play.start_range) {
1236 ctx->seek_request = fevt->play.start_range;
1237 return GF_TRUE;
1238 }
1239 }
1240 }
1241
1242 if (fevt->play.speed)
1243 gf_dash_set_speed(ctx->dash, fevt->play.speed);
1244
1245 initial_play = ctx->initial_play;
1246 if (fevt->play.initial_broadcast_play) initial_play = GF_TRUE;
1247
1248 /*don't seek if this command is the first PLAY request of objects declared by the subservice, unless start range is not default one (0) */
1249 if (!ctx->nb_playing) {
1250 if (!initial_play || (fevt->play.start_range>1.0)) {
1251
1252 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] Received Play command on group %d\n", group->idx));
1253
1254 if (fevt->play.end_range<=0) {
1255 u32 ms = (u32) ( 1000 * (-fevt->play.end_range) );
1256 if (ms<1000) ms = 0;
1257 gf_dash_set_timeshift(ctx->dash, ms);
1258 }
1259 gf_dash_seek(ctx->dash, fevt->play.start_range);
1260
1261 //to remove once we manage to keep the service alive
1262 /*don't forward commands if a switch of period is to be scheduled, we are killing the service anyway ...*/
1263 if (gf_dash_get_period_switch_status(ctx->dash)) return GF_TRUE;
1264 }
1265 }
1266 //otherwise in static mode, perform a group seek
1267 else if (!initial_play && !gf_dash_is_dynamic_mpd(ctx->dash) ) {
1268 /*don't forward commands if a switch of period is to be scheduled, we are killing the service anyway ...*/
1269 if (gf_dash_get_period_switch_status(ctx->dash)) return GF_TRUE;
1270
1271 //seek on a single group
1272
1273 gf_dash_group_seek(ctx->dash, group->idx, fevt->play.start_range);
1274 }
1275
1276 //check if current segment playback should be aborted
1277 src_evt.play.forced_dash_segment_switch = gf_dash_group_segment_switch_forced(ctx->dash, group->idx);
1278
1279 gf_dash_group_select(ctx->dash, group->idx, GF_TRUE);
1280 gf_dash_set_group_done(ctx->dash, (u32) group->idx, 0);
1281
1282 //adjust start range from MPD time to media time
1283 src_evt.play.start_range = gf_dash_group_get_start_range(ctx->dash, group->idx);
1284 gf_dash_group_get_presentation_time_offset(ctx->dash, group->idx, &pto, ×cale);
1285 src_evt.play.start_range += ((Double)pto) / timescale;
1286 src_evt.play.no_byterange_forward = 1;
1287
1288 dashdmx_setup_buffer(ctx, group);
1289
1290 gf_filter_prevent_blocking(filter, GF_TRUE);
1291
1292 ctx->nb_playing++;
1293 //forward new event to source pid
1294 src_evt.base.on_pid = ipid;
1295
1296 gf_filter_pid_send_event(ipid, &src_evt);
1297 gf_filter_post_process_task(filter);
1298 //cancel the event
1299 return GF_TRUE;
1300
1301 case GF_FEVT_STOP:
1302 gf_dash_set_group_done(ctx->dash, (u32) group->idx, 1);
1303 gf_dash_group_select(ctx->dash, (u32) group->idx, GF_FALSE);
1304 group->is_playing = GF_FALSE;
1305 if (ctx->nb_playing) {
1306 ctx->initial_play = GF_FALSE;
1307 group->force_seg_switch = GF_TRUE;
1308 ctx->nb_playing--;
1309 if (!ctx->nb_playing) ctx->check_eos = GF_TRUE;
1310 }
1311 //forward new event to source pid
1312 src_evt = *fevt;
1313 src_evt.base.on_pid = ipid;
1314 gf_filter_pid_send_event(ipid, &src_evt);
1315
1316 //cancel the event
1317 return GF_TRUE;
1318
1319 case GF_FEVT_CAPS_CHANGE:
1320 if (ctx->screen_res) {
1321 GF_FilterSessionCaps caps;
1322 gf_filter_get_session_caps(ctx->filter, &caps);
1323 gf_dash_set_max_resolution(ctx->dash, caps.max_screen_width, caps.max_screen_height, caps.max_screen_bpp);
1324 }
1325 return GF_TRUE;
1326 case GF_FEVT_INFO_UPDATE:
1327 //propagate
1328 return GF_FALSE;
1329 default:
1330 break;
1331 }
1332
1333 //by default cancel all events
1334 return GF_TRUE;
1335 }
1336
dashdmx_switch_segment(GF_DASHDmxCtx * ctx,GF_DASHGroup * group)1337 static void dashdmx_switch_segment(GF_DASHDmxCtx *ctx, GF_DASHGroup *group)
1338 {
1339 u32 dependent_representation_index = 0;
1340 GF_Err e;
1341 Bool has_next;
1342 GF_FilterEvent evt;
1343 const char *next_url, *next_url_init_or_switch_segment, *src_url, *key_url;
1344 u64 start_range, end_range, switch_start_range, switch_end_range;
1345 bin128 key_IV;
1346 u32 group_idx;
1347
1348 assert(group->nb_eos || group->seg_was_not_ready || group->in_error);
1349 group->wait_for_pck = GF_TRUE;
1350 group->in_error = GF_FALSE;
1351 if (group->segment_sent) {
1352 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d drop current segment\n", group->idx));
1353 if (!group->current_group_dep)
1354 gf_dash_group_discard_segment(ctx->dash, group->idx);
1355
1356 group->segment_sent = GF_FALSE;
1357 //no thread mode, we work with at most one entry in cache, call process right away to get the group next URL ready
1358 gf_dash_process(ctx->dash);
1359 }
1360
1361 group->stats_uploaded = GF_FALSE;
1362
1363 #if 0
1364 if (group_done) {
1365 if (!gf_dash_get_period_switch_status(ctx->dash) && gf_dash_in_last_period(ctx->dash, GF_TRUE)) {
1366 return;
1367 }
1368 }
1369 #endif
1370
1371 group_idx = group->idx;
1372 if (group->nb_group_deps) {
1373 if (group->current_group_dep) {
1374 dependent_representation_index = group->current_group_dep;
1375 // s32 res = gf_dash_get_dependent_group_index(ctx->dash, group->idx, group->current_group_dep-
1376 }
1377 }
1378 e = gf_dash_group_get_next_segment_location(ctx->dash, group_idx, dependent_representation_index, &next_url, &start_range, &end_range,
1379 NULL, &next_url_init_or_switch_segment, &switch_start_range , &switch_end_range,
1380 &src_url, &has_next, &key_url, &key_IV);
1381
1382 if (e == GF_EOS) {
1383 group->eos_detected = GF_TRUE;
1384 return;
1385 }
1386 group->eos_detected = GF_FALSE;
1387
1388 if (e != GF_OK) {
1389 if (e == GF_BUFFER_TOO_SMALL) {
1390 group->seg_was_not_ready = GF_TRUE;
1391 group->stats_uploaded = GF_TRUE;
1392 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d next segment name not known yet!\n", group->idx));
1393 gf_filter_ask_rt_reschedule(ctx->filter, 10000);
1394 // gf_filter_post_process_task(ctx->filter);
1395 } else {
1396 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] group %d error fetching next segment name: %s\n", group->idx, gf_error_to_string(e) ));
1397 }
1398 return;
1399 }
1400
1401 if (group->nb_group_deps) {
1402 group->current_group_dep++;
1403 if (group->current_group_dep>group->nb_group_deps)
1404 group->current_group_dep = 0;
1405 }
1406
1407 assert(next_url);
1408 group->seg_was_not_ready = GF_FALSE;
1409
1410 if (next_url_init_or_switch_segment && !group->init_switch_seg_sent) {
1411 GF_FEVT_INIT(evt, GF_FEVT_SOURCE_SWITCH, NULL);
1412 evt.seek.start_offset = switch_start_range;
1413 evt.seek.end_offset = switch_end_range;
1414 evt.seek.source_switch = next_url_init_or_switch_segment;
1415 evt.seek.previous_is_init_segment = group->prev_is_init_segment;
1416 evt.seek.skip_cache_expiration = GF_TRUE;
1417
1418 group->prev_is_init_segment = GF_TRUE;
1419
1420 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d queuing next init/switching segment %s\n", group->idx, next_url_init_or_switch_segment));
1421
1422 group->init_switch_seg_sent = GF_TRUE;
1423 gf_filter_send_event(group->seg_filter_src, &evt, GF_FALSE);
1424 return;
1425 }
1426 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d queuing next media segment %s\n", group->idx, next_url));
1427
1428 GF_FEVT_INIT(evt, GF_FEVT_SOURCE_SWITCH, NULL);
1429 evt.seek.source_switch = next_url;
1430 evt.seek.start_offset = start_range;
1431 evt.seek.end_offset = end_range;
1432 evt.seek.previous_is_init_segment = group->prev_is_init_segment;
1433 group->segment_sent = GF_TRUE;
1434 group->prev_is_init_segment = GF_FALSE;
1435 group->init_switch_seg_sent = GF_FALSE;
1436 gf_filter_send_event(group->seg_filter_src, &evt, GF_FALSE);
1437 }
1438
dashdmx_update_group_stats(GF_DASHDmxCtx * ctx,GF_DASHGroup * group)1439 static void dashdmx_update_group_stats(GF_DASHDmxCtx *ctx, GF_DASHGroup *group)
1440 {
1441 u32 bytes_per_sec = 0;
1442 u64 file_size = 0, bytes_done = 0;
1443 const GF_PropertyValue *p;
1444 GF_PropertyEntry *pe=NULL;
1445 Bool broadcast_flag = GF_FALSE;
1446 if (group->stats_uploaded) return;
1447 if (group->prev_is_init_segment) return;
1448 if (!group->seg_filter_src) return;
1449
1450 p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_FILE_CACHED, &pe);
1451 if (!p || !p->value.boolean) {
1452 gf_filter_release_property(pe);
1453 return;
1454 }
1455 group->stats_uploaded = GF_TRUE;
1456
1457 p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_DOWN_RATE, &pe);
1458 if (p) bytes_per_sec = p->value.uint / 8;
1459
1460 p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_DOWN_SIZE, &pe);
1461 if (p) file_size = p->value.longuint;
1462
1463 p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_DOWN_BYTES, &pe);
1464 if (p) bytes_done = p->value.longuint;
1465
1466 p = gf_filter_get_info_str(group->seg_filter_src, "x-atsc", &pe);
1467 if (p && p->value.string && !strcmp(p->value.string, "yes")) {
1468 broadcast_flag = GF_TRUE;
1469 }
1470
1471 gf_dash_group_store_stats(ctx->dash, group->idx, bytes_per_sec, (u32) file_size, (u32) bytes_done, broadcast_flag);
1472
1473 //we allow file abort, check the download
1474 if (ctx->abort)
1475 gf_dash_group_check_bandwidth(ctx->dash, group->idx);
1476
1477 p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_FILE_CACHED, &pe);
1478 if (p && p->value.boolean)
1479 group->stats_uploaded = GF_TRUE;
1480
1481 gf_filter_release_property(pe);
1482 }
1483
dashdmx_process(GF_Filter * filter)1484 GF_Err dashdmx_process(GF_Filter *filter)
1485 {
1486 u32 i, count;
1487 GF_FilterPacket *pck;
1488 GF_Err e;
1489 u32 next_time_ms = 0;
1490 GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter);
1491 Bool check_eos = ctx->check_eos;
1492 Bool has_pck = GF_FALSE;
1493
1494 //reset group states and update stats
1495 count = gf_dash_get_group_count(ctx->dash);
1496 for (i=0; i<count; i++) {
1497 GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, i);
1498 if (!group) continue;
1499 group->nb_eos = 0;
1500 if (group->eos_detected) check_eos = GF_TRUE;
1501 }
1502
1503 if (!ctx->mpd_pid)
1504 return GF_EOS;
1505
1506 //check MPD pid
1507 pck = gf_filter_pid_get_packet(ctx->mpd_pid);
1508 if (pck) {
1509 gf_filter_pid_drop_packet(ctx->mpd_pid);
1510 }
1511 e = gf_dash_process(ctx->dash);
1512 if (e == GF_IP_NETWORK_EMPTY) {
1513 gf_filter_ask_rt_reschedule(filter, 100000);
1514 return GF_OK;
1515 }
1516 if (e)
1517 return e;
1518
1519 next_time_ms = gf_dash_get_min_wait_ms(ctx->dash);
1520 if (next_time_ms>1000)
1521 next_time_ms=1000;
1522
1523 //flush all media input
1524 count = gf_filter_get_ipid_count(filter);
1525 for (i=0; i<count; i++) {
1526 GF_FilterPid *ipid = gf_filter_get_ipid(filter, i);
1527 GF_FilterPid *opid;
1528 GF_DASHGroup *group;
1529 if (ipid == ctx->mpd_pid) continue;
1530 opid = gf_filter_pid_get_udta(ipid);
1531 group = gf_filter_pid_get_udta(opid);
1532
1533 if (!group)
1534 continue;
1535
1536 while (1) {
1537 pck = gf_filter_pid_get_packet(ipid);
1538 if (!group->is_playing) {
1539 if (pck) {
1540 gf_filter_pid_drop_packet(ipid);
1541 continue;
1542 }
1543 break;
1544 }
1545
1546 if (!pck) {
1547 if (gf_filter_pid_is_eos(ipid) || !gf_filter_pid_is_playing(opid) || group->force_seg_switch) {
1548 group->nb_eos++;
1549
1550 //wait until all our inputs are done
1551 if (group->nb_eos == group->nb_pids) {
1552 u32 j, nb_block = 0;
1553 //check all pids in this group, postpone segment switch if blocking
1554 for (j=0; j<count; j++) {
1555 GF_FilterPid *an_ipid = gf_filter_get_ipid(filter, j);
1556 GF_FilterPid *an_opid = gf_filter_pid_get_udta(an_ipid);
1557 GF_DASHGroup *agroup;
1558 if (an_ipid == ctx->mpd_pid) continue;
1559 agroup = gf_filter_pid_get_udta(an_opid);
1560 if (!agroup || (agroup != group)) continue;
1561
1562 if (gf_filter_pid_would_block(an_opid)) {
1563 nb_block++;
1564 }
1565 }
1566 if (nb_block == group->nb_pids) {
1567 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASHDmx] End of segment for group %d but %d output pid(s) would block, postponing\n", nb_block, group->idx));
1568 break;
1569 }
1570
1571 //good to switch, cancel all end of stream signals on pids from this group and switch
1572 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] End of segment for group %d, updating stats and switching segment\n", group->idx));
1573 for (j=0; j<count; j++) {
1574 GF_FilterPid *an_ipid = gf_filter_get_ipid(filter, j);
1575 GF_FilterPid *an_opid = gf_filter_pid_get_udta(an_ipid);
1576 GF_DASHGroup *agroup;
1577 if (an_ipid == ctx->mpd_pid) continue;
1578 agroup = gf_filter_pid_get_udta(an_opid);
1579 if (!agroup || (agroup != group)) continue;
1580
1581 if (gf_filter_pid_is_eos(an_ipid)) {
1582 gf_filter_pid_clear_eos(an_ipid, GF_TRUE);
1583 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] Clearing EOS on pids from group %d\n", group->idx));
1584 }
1585 }
1586 dashdmx_update_group_stats(ctx, group);
1587 group->stats_uploaded = GF_TRUE;
1588 group->force_seg_switch = GF_FALSE;
1589 dashdmx_switch_segment(ctx, group);
1590
1591 gf_filter_prevent_blocking(filter, GF_FALSE);
1592 if (group->eos_detected && !has_pck) check_eos = GF_TRUE;
1593 }
1594 }
1595 else {
1596 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] No source packet group %d and not in end of stream\n", group->idx));
1597 }
1598 if (group->in_error || group->seg_was_not_ready) {
1599 dashdmx_switch_segment(ctx, group);
1600 gf_filter_prevent_blocking(filter, GF_FALSE);
1601 if (group->eos_detected && !has_pck) check_eos = GF_TRUE;
1602 }
1603 break;
1604 }
1605 has_pck = GF_TRUE;
1606 check_eos = GF_FALSE;
1607 dashdmx_forward_packet(ctx, pck, ipid, opid, group);
1608 group->wait_for_pck = GF_FALSE;
1609 dashdmx_update_group_stats(ctx, group);
1610 }
1611 }
1612
1613 if (check_eos) {
1614 Bool all_groups_done = GF_TRUE;
1615 Bool groups_not_playing = GF_TRUE;
1616 Bool is_in_last_period = gf_dash_in_last_period(ctx->dash, GF_TRUE);
1617
1618 //not last period, check if we are done playing all groups due to stop requests
1619 if (!is_in_last_period && !ctx->nb_playing) {
1620 Bool groups_done=GF_TRUE;
1621 for (i=0; i<count; i++) {
1622 GF_FilterPid *ipid = gf_filter_get_ipid(filter, i);
1623 GF_FilterPid *opid;
1624 GF_DASHGroup *group;
1625 if (ipid == ctx->mpd_pid) continue;
1626 opid = gf_filter_pid_get_udta(ipid);
1627 group = gf_filter_pid_get_udta(opid);
1628 if (!group) continue;
1629 if (!group->is_playing && group->eos_detected) continue;
1630 groups_done=GF_FALSE;
1631 }
1632 if (groups_done)
1633 is_in_last_period = GF_TRUE;
1634 }
1635
1636 for (i=0; i<count; i++) {
1637 GF_FilterPid *ipid = gf_filter_get_ipid(filter, i);
1638 GF_FilterPid *opid;
1639 GF_DASHGroup *group;
1640 if (ipid == ctx->mpd_pid) continue;
1641 opid = gf_filter_pid_get_udta(ipid);
1642 group = gf_filter_pid_get_udta(opid);
1643 //reset in progress
1644 if (!group) {
1645 all_groups_done = GF_FALSE;
1646 continue;
1647 }
1648 if (group->is_playing)
1649 groups_not_playing = GF_FALSE;
1650
1651 if (!group->eos_detected && group->is_playing) {
1652 all_groups_done = GF_FALSE;
1653 } else if (is_in_last_period) {
1654 if (gf_filter_pid_is_eos(ipid) || group->eos_detected)
1655 gf_filter_pid_set_eos(opid);
1656 else
1657 all_groups_done = GF_FALSE;
1658 }
1659 }
1660 if (all_groups_done) {
1661 if (is_in_last_period || groups_not_playing)
1662 return GF_EOS;
1663 if (!gf_dash_get_period_switch_status(ctx->dash)) {
1664 for (i=0; i<count; i++) {
1665 GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, i);
1666 if (!group) continue;
1667 group->nb_eos = 0;
1668 group->eos_detected = GF_FALSE;
1669 }
1670
1671 gf_dash_request_period_switch(ctx->dash);
1672 }
1673 }
1674 }
1675 if (gf_dash_is_in_setup(ctx->dash))
1676 gf_filter_post_process_task(filter);
1677 else if (next_time_ms) {
1678 gf_filter_ask_rt_reschedule(filter, 1000 * next_time_ms);
1679 }
1680 return GF_OK;
1681 }
1682
dashdmx_probe_data(const u8 * data,u32 size,GF_FilterProbeScore * score)1683 static const char *dashdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score)
1684 {
1685 char *d = (char *)data;
1686 char *res_dash, *res_m3u, *res_smooth;
1687 char last_c = d[size-1];
1688 d[size-1] = 0;
1689 res_dash = strstr(data, "<MPD ");
1690 res_m3u = strstr(data, "#EXTM3U");
1691 res_smooth = strstr(data, "<SmoothStreamingMedia");
1692 d[size-1] = last_c;
1693
1694 if (res_dash) {
1695 *score = GF_FPROBE_SUPPORTED;
1696 return "application/dash+xml";
1697 }
1698 if (res_m3u) {
1699 *score = GF_FPROBE_SUPPORTED;
1700 return "video/mpegurl";
1701 }
1702 if (res_smooth) {
1703 *score = GF_FPROBE_SUPPORTED;
1704 return "application/vnd.ms-sstr+xml";
1705 }
1706 return NULL;
1707 }
1708
1709 #define OFFS(_n) #_n, offsetof(GF_DASHDmxCtx, _n)
1710 static const GF_FilterArgs DASHDmxArgs[] =
1711 {
1712 { OFFS(auto_switch), "switch quality every N segments, disabled if 0", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT},
1713 { OFFS(store), "enable file caching\n"
1714 "- mem: all files are stored in memory, no disk IO\n"
1715 "- file: files are stored to disk but discarded once played\n"
1716 "- cache: all files are stored to disk and kept"
1717 "", GF_PROP_UINT, "mem", "mem|file|cache", GF_FS_ARG_HINT_ADVANCED},
1718 { OFFS(algo), "adaptation algorithm to use\n"\
1719 "- none: no adaptation logic\n"\
1720 "- grate: GAPC legacy algo based on available rate\n"\
1721 "- gbuf: GAPC legacy algo based on buffer occupancy\n"\
1722 "- bba0: BBA-0\n"\
1723 "- bolaf: BOLA Finite\n"\
1724 "- bolab: BOLA Basic\n"\
1725 "- bolau: BOLA-U\n"\
1726 "- bolao: BOLA-O"
1727 , GF_PROP_UINT, "gbuf", "none|grate|gbuf|bba0|bolaf|bolab|bolau|bolao", GF_FS_ARG_HINT_ADVANCED},
1728 { OFFS(start_with), "initial selection criteria\n"\
1729 "- min_q: start with lowest quality\n"\
1730 "- max_q: start with highest quality\n"\
1731 "- min_bw: start with lowest bitrate\n"\
1732 "- max_bw: start with highest bitrate; for tiles are used, all low priority tiles will have the lower (below max) bandwidth selected\n"\
1733 "- max_bw_tiles: start with highest bitrate; for tiles all low priority tiles will have their lowest bandwidth selected"
1734 , GF_PROP_UINT, "max_bw", "min_q|max_q|min_bw|max_bw|max_bw_tiles", 0},
1735
1736 { OFFS(max_res), "use max media resolution to configure display", GF_PROP_BOOL, "true", NULL, 0},
1737 { OFFS(immediate), "when interactive switching is requested and immediate is set, the buffer segments are trashed", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
1738 { OFFS(abort), "allow abort during a segment download", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT},
1739 { OFFS(use_bmin), "use the indicated min buffer time of the MPD if true, otherwise uses default player settings", GF_PROP_BOOL, "false", NULL, 0},
1740
1741 { OFFS(shift_utc), "shift DASH UTC clock in ms", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_EXPERT},
1742 { OFFS(atsc_shift), "shift ATSC requests time by given ms", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_EXPERT},
1743 { OFFS(server_utc), "use ServerUTC: or Date: http headers instead of local UTC", GF_PROP_BOOL, "yes", NULL, GF_FS_ARG_HINT_ADVANCED},
1744 { OFFS(screen_res), "use screen resolution in selection phase", GF_PROP_BOOL, "yes", NULL, GF_FS_ARG_HINT_ADVANCED},
1745 { OFFS(timeshift), "set initial timshift in ms (if >0) or in %% of timeshift buffer (if <0)", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED},
1746 { OFFS(tile_mode), "tile adaptation mode\n"\
1747 "- none: bitrate is shared equaly accross all tiles\n"\
1748 "- rows: bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row\n"\
1749 "- rrows: bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row\n"\
1750 "- mrows: bitrate decreased for top and bottom rows only, same rate for each tile on the row\n"\
1751 "- cols: bitrate decreases for each columns of tiles starting from the left, same rate for each tile on the columns\n"\
1752 "- rcols: bitrate decreases for each columns of tiles starting from the right, same rate for each tile on the columns\n"\
1753 "- mcols: bitrate decreased for left and right columns only, same rate for each tile on the columns\n"\
1754 "- center: bitrate decreased for all tiles on the edge of the picture\n"\
1755 "- edges: bitrate decreased for all tiles on the center of the picture"
1756 , GF_PROP_UINT, "none", "none|rows|rrows|mrows|cols|rcols|mcols|center|edges", GF_FS_ARG_HINT_EXPERT},
1757 { OFFS(tiles_rate), "indicate the amount of bandwidth to use at each quality level. The rate is recursively applied at each level, e.g. if 50%, Level1 gets 50%, level2 gets 25%, ... If 100, automatic rate allocation will be done by maximizing the quality in order of priority. If 0, bitstream will not be smoothed across tiles/qualities, and concurrency may happen between different media.", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_EXPERT},
1758 { OFFS(delay40X), "delay in millisconds to wait between two 40X on the same segment", GF_PROP_UINT, "500", NULL, GF_FS_ARG_HINT_ADVANCED},
1759 { OFFS(exp_threshold), "delay in millisconds to wait after the segment AvailabilityEndDate before considering the segment lost", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_ADVANCED},
1760 { OFFS(switch_count), "indicate how many segments the client shall wait before switching up bandwidth. If 0, switch will happen as soon as the bandwidth is enough, but this is more prone to network variations", GF_PROP_UINT, "1", NULL, GF_FS_ARG_HINT_ADVANCED},
1761 { OFFS(aggressive), "if enabled, switching algo targets the closest bandwidth fitting the available download rate. If no, switching algo targets the lowest bitrate representation that is above the currently played (eg does not try to switch to max bandwidth)", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_EXPERT},
1762 { OFFS(debug_as), "play only the adaptation set indicated by its index in the MPD; if negative, all sets are used", GF_PROP_UINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT},
1763 { OFFS(speedadapt), "enable adaptation based on playback speed", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_EXPERT},
1764 { OFFS(noxlink), "disable xlink if period has both xlink and adaptation sets", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_ADVANCED},
1765 { OFFS(query), "set query string (without initial '?') to append to xlink of periods", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
1766 { OFFS(split_as), "separate all qualities into different adaptation sets and stream all qualities", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_ADVANCED},
1767 { OFFS(lowlat), "segment scheduling policy in low latency mode\n"
1768 "- no: disable low latency\n"
1769 "- strict: strict respect of AST offset in low latency\n"
1770 "- early: allow fetching segments earlier than their AST in low latency when input demux is empty", GF_PROP_UINT, "early", "no|strict|early", GF_FS_ARG_HINT_EXPERT},
1771 {0}
1772 };
1773
1774
1775
1776 static const GF_FilterCapability DASHDmxCaps[] =
1777 {
1778 CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
1779 CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "mpd|m3u8|3gm|ism"),
1780 CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/dash+xml|video/vnd.3gpp.mpd|audio/vnd.3gpp.mpd|video/vnd.mpeg.dash.mpd|audio/vnd.mpeg.dash.mpd|audio/mpegurl|video/mpegurl|application/vnd.ms-sstr+xml"),
1781 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO),
1782 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
1783 CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW),
1784 {0},
1785 //accept any stream but files, framed
1786 { .code=GF_PROP_PID_STREAM_TYPE, .val.type=GF_PROP_UINT, .val.value.uint=GF_STREAM_FILE, .flags=(GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_EXCLUDED|GF_CAPFLAG_LOADED_FILTER) },
1787 { .code=GF_PROP_PID_UNFRAMED, .val.type=GF_PROP_BOOL, .val.value.boolean=GF_TRUE, .flags=(GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_EXCLUDED|GF_CAPFLAG_LOADED_FILTER) },
1788 CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW),
1789 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO),
1790 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
1791 CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW),
1792 };
1793
1794
1795 GF_FilterRegister DASHDmxRegister = {
1796 .name = "dashin",
1797 GF_FS_SET_DESCRIPTION("MPEG-DASH and HLS client")
1798 GF_FS_SET_HELP("This filter reads MPEG-DASH, HLS and MS Smooth (on demand only for now) manifests and produces media PIDs and frames.")
1799 .private_size = sizeof(GF_DASHDmxCtx),
1800 .initialize = dashdmx_initialize,
1801 .finalize = dashdmx_finalize,
1802 .args = DASHDmxArgs,
1803 SETCAPS(DASHDmxCaps),
1804 .flags = GF_FS_REG_REQUIRES_RESOLVER,
1805 .configure_pid = dashdmx_configure_pid,
1806 .process = dashdmx_process,
1807 .process_event = dashdmx_process_event,
1808 .probe_data = dashdmx_probe_data,
1809 //we accept as many input pids as loaded by the session
1810 .max_extra_pids = (u32) -1,
1811 };
1812
1813
1814 #endif //GPAC_DISABLE_DASH_CLIENT
1815
dashdmx_register(GF_FilterSession * session)1816 const GF_FilterRegister *dashdmx_register(GF_FilterSession *session)
1817 {
1818 #ifndef GPAC_DISABLE_DASH_CLIENT
1819 return &DASHDmxRegister;
1820 #else
1821 return NULL;
1822 #endif
1823 }
1824