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, &timescale);
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, &timescale);
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