Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2020
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / ROUTE output 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 :
27 : #include <gpac/filters.h>
28 : #include <gpac/constants.h>
29 : #include <gpac/xml.h>
30 : #include <gpac/route.h>
31 : #include <gpac/network.h>
32 :
33 :
34 : enum
35 : {
36 : LCT_SPLIT_NONE=0,
37 : LCT_SPLIT_TYPE,
38 : LCT_SPLIT_ALL,
39 : };
40 :
41 : typedef struct
42 : {
43 : char *dst, *ext, *mime, *ifce, *ip;
44 : u32 carousel, first_port, bsid, mtu, splitlct, ttl, brinc, runfor;
45 : Bool korean, llmode, noreg;
46 :
47 : GF_FilterCapability in_caps[2];
48 : char szExt[10];
49 :
50 : GF_List *services;
51 :
52 : //ATSC3
53 : GF_Socket *sock_atsc_lls;
54 :
55 : u64 clock_init, clock, clock_stats;
56 : u64 last_lls_clock;
57 :
58 : u8 *lls_time_table;
59 : u32 lls_time_table_len;
60 :
61 : u8 *lls_slt_table;
62 : u32 lls_slt_table_len;
63 :
64 : u64 bytes_sent;
65 : u8 *lct_buffer;
66 :
67 : u64 reschedule_us;
68 : u32 next_raw_file_toi;
69 :
70 : Bool reporting_on;
71 : u64 total_size, total_bytes;
72 : Bool total_size_unknown;
73 : u32 nb_resources;
74 :
75 : Bool done;
76 : } GF_ROUTEOutCtx;
77 :
78 : typedef struct
79 : {
80 : char *ip;
81 : u32 port;
82 :
83 : //for now we use a single route session per service, differenciated by TSI
84 : GF_Socket *sock;
85 :
86 : } ROUTELCT;
87 :
88 : typedef struct
89 : {
90 : u32 service_id;
91 : GF_List *pids;
92 : u32 dash_mode;
93 :
94 : GF_List *rlcts;
95 : ROUTELCT *rlct_base;
96 :
97 : Bool is_done;
98 : Bool wait_for_inputs;
99 :
100 : //storage for main manifest - all manifests (including HLS sub-playlists) are sent in the same PID
101 : //HLS sub-playlists are stored on their related PID to be pushed at each new seg
102 : char *manifest, *manifest_name, *manifest_mime, *manifest_server, *manifest_url;
103 : u32 manifest_version, manifest_crc;
104 : Bool stsid_changed;
105 : u32 stsid_version;
106 : u32 first_port;
107 :
108 : u8 *stsid_bundle;
109 : u32 stsid_bundle_size;
110 : u32 stsid_bundle_toi;
111 :
112 : u64 last_stsid_clock;
113 : u32 manifest_type;
114 : u32 creation_time;
115 : } ROUTEService;
116 :
117 : typedef struct
118 : {
119 : GF_FilterPid *pid;
120 :
121 : ROUTEService *route;
122 : ROUTELCT *rlct;
123 :
124 : u32 tsi, bandwidth, stream_type;
125 : GF_Fraction dash_dur;
126 : //we cannot hold a ref to the init segment packet, as this may lock the input waiting for its realease to dispatch next packets
127 : u8 *init_seg_data;
128 : u32 init_seg_size;
129 : u32 init_seg_crc;
130 : char *init_seg_name;
131 :
132 : //0: not manifest, 1: MPD, 2: HLS
133 : u32 manifest_type;
134 :
135 : Bool init_seg_sent;
136 :
137 : char *template;
138 :
139 : char *hld_child_pl, *hld_child_pl_name;
140 : u32 hld_child_pl_version, hld_child_pl_crc;
141 : u64 hls_ref_id;
142 : Bool update_hls_child_pl;
143 :
144 : u32 fmtp, mode;
145 :
146 : GF_FilterPacket *current_pck;
147 : u32 current_toi;
148 :
149 : const u8 *pck_data;
150 : u32 pck_size, pck_offset;
151 : u64 res_size, offset_at_seg_start;
152 : char *seg_name;
153 :
154 : u32 timescale;
155 : u64 clock_at_first_pck;
156 : u64 cts_first_pck;
157 : u64 current_cts_us;
158 : u64 current_dur_us;
159 : u64 carousel_time_us;
160 : u64 clock_at_pck;
161 : Bool raw_file, use_basename;
162 :
163 : u32 full_frame_size, cumulated_frag_size, frag_offset;
164 : u32 frag_idx;
165 : Bool push_init, force_tol_send;
166 : u64 clock_at_frame_start, cts_us_at_frame_start, cts_at_frame_start;
167 : u32 pck_dur_at_frame_start;
168 :
169 : u32 bitrate;
170 : } ROUTEPid;
171 :
172 :
173 7 : ROUTELCT *route_create_lct_channel(GF_ROUTEOutCtx *ctx, const char *ip, u32 port, GF_Err *e)
174 : {
175 7 : *e = GF_OUT_OF_MEM;
176 : ROUTELCT *rlct;
177 7 : GF_SAFEALLOC(rlct, ROUTELCT);
178 7 : if (!rlct) return NULL;
179 :
180 7 : rlct->ip = gf_strdup(ip ? ip : ctx->ip);
181 7 : if (!rlct->ip) goto fail;
182 7 : if (port) {
183 7 : rlct->port = port;
184 : } else {
185 0 : rlct->port = ctx->first_port;
186 0 : ctx->first_port ++;
187 : }
188 :
189 7 : if (rlct->ip) {
190 7 : rlct->sock = gf_sk_new(GF_SOCK_TYPE_UDP);
191 7 : if (rlct->sock) {
192 7 : *e = gf_sk_setup_multicast(rlct->sock, rlct->ip, rlct->port, ctx->ttl, GF_FALSE, ctx->ifce);
193 7 : if (*e) {
194 0 : gf_sk_del(rlct->sock);
195 0 : rlct->sock = NULL;
196 0 : goto fail;
197 : }
198 : }
199 : }
200 7 : *e = GF_OK;
201 7 : return rlct;
202 0 : fail:
203 0 : if (rlct->ip) gf_free(rlct->ip);
204 0 : gf_free(rlct);
205 0 : return NULL;
206 : }
207 :
208 7 : ROUTEService *routeout_create_service(GF_ROUTEOutCtx *ctx, u32 service_id, const char *ip, u32 port, GF_Err *e)
209 : {
210 : ROUTEService *rserv;
211 : ROUTELCT *rlct = NULL;
212 7 : *e = GF_OUT_OF_MEM;
213 7 : GF_SAFEALLOC(rserv, ROUTEService);
214 7 : if (!rserv) return NULL;
215 :
216 7 : rlct = route_create_lct_channel(ctx, ip, port, e);
217 7 : if (!rlct) {
218 0 : gf_free(rserv);
219 0 : return NULL;
220 : }
221 7 : if (port) {
222 7 : rserv->first_port = port+1;
223 : }
224 :
225 7 : rserv->pids = gf_list_new();
226 7 : if (!rserv->pids) goto fail;
227 7 : rserv->rlcts = gf_list_new();
228 7 : if (!rserv->rlcts) goto fail;
229 :
230 7 : gf_list_add(rserv->rlcts, rlct);
231 7 : rserv->rlct_base = rlct;
232 :
233 7 : rserv->service_id = service_id;
234 7 : gf_list_add(ctx->services, rserv);
235 :
236 7 : if (ctx->lls_slt_table) {
237 0 : gf_free(ctx->lls_slt_table);
238 0 : ctx->lls_slt_table = NULL;
239 0 : ctx->last_lls_clock = 0;
240 : }
241 7 : *e = GF_OK;
242 7 : return rserv;
243 :
244 0 : fail:
245 0 : *e = GF_OUT_OF_MEM;
246 0 : if (rlct->ip) gf_free(rlct->ip);
247 0 : gf_free(rlct);
248 0 : if (rserv->pids) gf_list_del(rserv->pids);
249 0 : if (rserv->rlcts) gf_list_del(rserv->rlcts);
250 0 : gf_free(rserv);
251 0 : return NULL;
252 : }
253 :
254 16 : void routeout_remove_pid(ROUTEPid *rpid, Bool is_rem)
255 : {
256 16 : if (!is_rem) {
257 0 : gf_list_del_item(rpid->route->pids, rpid);
258 0 : rpid->route->stsid_changed = GF_TRUE;
259 : }
260 16 : if (rpid->rlct != rpid->route->rlct_base) {
261 0 : gf_list_del_item(rpid->route->rlcts, rpid->rlct);
262 0 : gf_free(rpid->rlct->ip);
263 0 : gf_sk_del(rpid->rlct->sock);
264 0 : gf_free(rpid->rlct);
265 : }
266 :
267 16 : if (rpid->init_seg_data) gf_free(rpid->init_seg_data);
268 16 : if (rpid->init_seg_name) gf_free(rpid->init_seg_name);
269 16 : if (rpid->hld_child_pl) gf_free(rpid->hld_child_pl);
270 16 : if (rpid->hld_child_pl_name) gf_free(rpid->hld_child_pl_name);
271 16 : if (rpid->template) gf_free(rpid->template);
272 16 : if (rpid->seg_name) gf_free(rpid->seg_name);
273 :
274 16 : if (rpid->current_pck)
275 4 : gf_filter_pck_unref(rpid->current_pck);
276 16 : gf_free(rpid);
277 16 : }
278 :
279 7 : void routeout_delete_service(ROUTEService *serv)
280 : {
281 30 : while (gf_list_count(serv->pids)) {
282 16 : ROUTEPid *rpid = gf_list_pop_back(serv->pids);
283 16 : routeout_remove_pid(rpid, GF_TRUE);
284 : }
285 7 : gf_list_del(serv->pids);
286 :
287 21 : while (gf_list_count(serv->rlcts)) {
288 7 : ROUTELCT *rlct = gf_list_pop_back(serv->rlcts);
289 7 : gf_sk_del(rlct->sock);
290 7 : gf_free(rlct->ip);
291 7 : gf_free(rlct);
292 : }
293 7 : gf_list_del(serv->rlcts);
294 :
295 7 : if (serv->manifest_mime) gf_free(serv->manifest_mime);
296 7 : if (serv->manifest_name) gf_free(serv->manifest_name);
297 7 : if (serv->manifest_server) gf_free(serv->manifest_server);
298 7 : if (serv->manifest_url) gf_free(serv->manifest_url);
299 :
300 7 : if (serv->manifest) gf_free(serv->manifest);
301 7 : if (serv->stsid_bundle) gf_free(serv->stsid_bundle);
302 7 : gf_free(serv);
303 7 : }
304 :
305 44 : static GF_Err routeout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
306 : {
307 : const GF_PropertyValue *p;
308 : u32 i;
309 : GF_FilterEvent evt;
310 : ROUTEService *rserv;
311 : u32 manifest_type;
312 : u32 service_id = 0;
313 : u32 pid_dash_mode = 0;
314 44 : GF_ROUTEOutCtx *ctx = (GF_ROUTEOutCtx *) gf_filter_get_udta(filter);
315 : ROUTEPid *rpid;
316 :
317 44 : rpid = gf_filter_pid_get_udta(pid);
318 44 : if (is_remove) {
319 0 : if (rpid) routeout_remove_pid(rpid, GF_FALSE);
320 : return GF_OK;
321 : }
322 44 : if (! gf_filter_pid_check_caps(pid))
323 : return GF_FILTER_NOT_SUPPORTED;
324 :
325 : //we currently ignore any reconfiguration of the pids
326 44 : if (rpid) {
327 : //any change to a raw file will require reconfiguring S-TSID
328 28 : if (rpid->raw_file) {
329 0 : rpid->route->stsid_changed = GF_TRUE;
330 : }
331 28 : else if (!rpid->manifest_type) {
332 24 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_TEMPLATE);
333 24 : if (p && p->value.string) {
334 24 : if (!rpid->template || strcmp(rpid->template, p->value.string)) {
335 0 : if (rpid->template) gf_free(rpid->template);
336 0 : rpid->template = gf_strdup(p->value.string);
337 0 : rpid->route->stsid_changed = GF_TRUE;
338 0 : rpid->use_basename = (strchr(rpid->template, '/')==NULL) ? GF_TRUE : GF_FALSE;
339 : }
340 0 : } else if (rpid->template) {
341 0 : gf_free(rpid->template);
342 0 : rpid->template = NULL;
343 0 : rpid->route->stsid_changed = GF_TRUE;
344 : }
345 : }
346 :
347 : return GF_OK;
348 : }
349 :
350 16 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE);
351 16 : if (p && p->value.uint) {
352 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Delivering files with progressive download disabled is not possible in ROUTE !\n"));
353 : return GF_FILTER_NOT_SUPPORTED;
354 : }
355 :
356 16 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE);
357 16 : if (p) pid_dash_mode = p->value.uint;
358 :
359 16 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID);
360 16 : if (p) service_id = p->value.uint;
361 :
362 : rserv = NULL;
363 16 : for (i=0; i<gf_list_count(ctx->services); i++) {
364 9 : rserv = gf_list_get(ctx->services, i);
365 9 : if (service_id == rserv->service_id) break;
366 : rserv = NULL;
367 : }
368 :
369 : manifest_type = 0;
370 16 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME);
371 16 : if (p && p->value.string) {
372 16 : if (strstr(p->value.string, "dash")) manifest_type = 1;
373 9 : else if (strstr(p->value.string, "mpd")) manifest_type = 1;
374 9 : else if (strstr(p->value.string, "mpegurl")) manifest_type = 2;
375 : }
376 : if (!manifest_type) {
377 9 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_EXT);
378 9 : if (p && p->value.string) {
379 9 : if (strstr(p->value.string, "mpd")) manifest_type = 1;
380 9 : else if (strstr(p->value.string, "m3u8")) manifest_type = 2;
381 9 : else if (strstr(p->value.string, "3gm")) manifest_type = 1;
382 : }
383 : }
384 16 : if (manifest_type) {
385 7 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_ORIG_STREAM_TYPE);
386 7 : if (!p || (p->value.uint!=GF_STREAM_FILE)) {
387 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("[ROUTE] Manifest file detected but no dashin filter, file will be uploaded as is !\n"));
388 : manifest_type = 0;
389 : }
390 : }
391 :
392 16 : if (!rserv) {
393 : GF_Err e;
394 7 : u32 port = ctx->first_port;
395 7 : const char *service_ip = ctx->ip;
396 :
397 : //cannot have 2 manifest pids connecting in route mode
398 7 : if (!ctx->sock_atsc_lls && gf_list_count(ctx->services) && manifest_type) {
399 0 : if (strchr(ctx->dst, '$')) {
400 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("[ROUTE] Multiple services in route mode, creating a new output filter\n"));
401 0 : return GF_REQUIRES_NEW_INSTANCE;
402 : }
403 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Multiple services in route mode and no URL templating, cannot create new output\n"));
404 : return GF_FILTER_NOT_SUPPORTED;
405 : }
406 :
407 7 : if (ctx->sock_atsc_lls) {
408 2 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_ROUTE_IP);
409 2 : if (p && p->value.string) service_ip = p->value.string;
410 :
411 2 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_ROUTE_PORT);
412 2 : if (p && p->value.uint) port = p->value.uint;
413 2 : else ctx->first_port++;
414 : }
415 :
416 7 : rserv = routeout_create_service(ctx, service_id, service_ip, port, &e);
417 7 : if (!rserv) return e;
418 7 : rserv->dash_mode = pid_dash_mode;
419 : }
420 :
421 16 : if (!rserv->manifest_type)
422 7 : rserv->manifest_type = manifest_type;
423 :
424 16 : if (rserv->dash_mode != pid_dash_mode){
425 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Mix of raw and muxed files should never happen - please report bug !\n"));
426 : return GF_SERVICE_ERROR;
427 : }
428 :
429 16 : GF_SAFEALLOC(rpid, ROUTEPid);
430 16 : if (!rpid) return GF_OUT_OF_MEM;
431 16 : rpid->route = rserv;
432 16 : rpid->pid = pid;
433 16 : rpid->fmtp = 128;
434 16 : rpid->mode = 1;
435 16 : rpid->tsi = 0;
436 16 : rpid->manifest_type = manifest_type;
437 16 : if (manifest_type) {
438 7 : rserv->creation_time = gf_sys_clock();
439 7 : gf_filter_pid_ignore_blocking(pid, GF_TRUE);
440 : }
441 :
442 16 : gf_list_add(rserv->pids, rpid);
443 16 : gf_filter_pid_set_udta(pid, rpid);
444 :
445 16 : rpid->rlct = rserv->rlct_base;
446 :
447 16 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_BITRATE);
448 16 : if (p) rpid->bitrate = p->value.uint * (100+ctx->brinc) / 100;
449 :
450 16 : rpid->stream_type = 0;
451 16 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ORIG_STREAM_TYPE);
452 16 : if (!p) {
453 0 : if (!rpid->manifest_type) {
454 0 : rpid->raw_file = GF_TRUE;
455 : }
456 : } else {
457 16 : rpid->stream_type = p->value.uint;
458 : }
459 :
460 16 : rpid->bandwidth = 0;
461 16 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_BITRATE);
462 16 : if (p) rpid->bandwidth = p->value.uint;
463 :
464 16 : rpid->dash_dur.num = 1;
465 16 : rpid->dash_dur.den = 1;
466 16 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_DASH_DUR);
467 16 : if (p) rpid->dash_dur = p->value.frac;
468 :
469 16 : if (!rpid->manifest_type && !rpid->raw_file) {
470 9 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_TEMPLATE);
471 9 : if (p && p->value.string) rpid->template = gf_strdup(p->value.string);
472 :
473 : else {
474 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("[ROUTE] Segment file PID detected but no template assigned, assuming raw file upload!\n"));
475 0 : rpid->raw_file = GF_TRUE;
476 : }
477 : }
478 :
479 16 : if (rpid->raw_file) {
480 0 : rpid->tsi = 1;
481 0 : rpid->current_toi = ctx->next_raw_file_toi;
482 0 : ctx->next_raw_file_toi ++;
483 : }
484 :
485 16 : if (!rpid->manifest_type && !rpid->raw_file) {
486 : Bool do_split = GF_FALSE;
487 :
488 9 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_TIMESCALE);
489 9 : if (p) rpid->timescale = p->value.uint;
490 :
491 9 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PCK_HLS_REF);
492 9 : if (p) rpid->hls_ref_id = p->value.longuint;
493 :
494 9 : rpid->tsi = gf_list_find(rserv->pids, rpid) * 10;
495 :
496 : //do we put this on the main LCT of the service or do we split ?
497 9 : if (ctx->splitlct==LCT_SPLIT_ALL) {
498 0 : if (gf_list_count(rserv->pids)>1)
499 : do_split = GF_TRUE;
500 : }
501 9 : else if (ctx->splitlct && rpid->stream_type) {
502 0 : for (i=0; i<gf_list_count(rserv->pids); i++) {
503 : u32 astreamtype;
504 0 : ROUTEPid *apid = gf_list_get(rserv->pids, i);
505 0 : if (apid->manifest_type) continue;
506 0 : if (apid == rpid) continue;
507 0 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ORIG_STREAM_TYPE);
508 0 : if (!p) continue;
509 0 : astreamtype = p->value.uint;
510 0 : if (astreamtype==rpid->stream_type) {
511 : do_split = GF_TRUE;
512 : break;
513 : }
514 : }
515 : }
516 0 : if (do_split) {
517 : GF_Err e;
518 0 : rserv->first_port++;
519 0 : ctx->first_port++;
520 0 : rpid->rlct = route_create_lct_channel(ctx, NULL, rserv->first_port, &e);
521 0 : if (e) return e;
522 0 : if (rpid->rlct) {
523 0 : gf_list_add(rserv->rlcts, rpid->rlct);
524 : }
525 : }
526 : }
527 :
528 :
529 16 : gf_filter_pid_init_play_event(pid, &evt, 0, 1.0, "ROUTEOut");
530 16 : gf_filter_pid_send_event(pid, &evt);
531 :
532 16 : rserv->wait_for_inputs = GF_TRUE;
533 : //low-latency mode, consume media segment files as they are received (don't wait for full segment reconstruction)
534 16 : if (ctx->llmode && !rpid->manifest_type && !rpid->raw_file) {
535 5 : gf_filter_pid_set_framing_mode(pid, GF_FALSE);
536 : } else {
537 11 : gf_filter_pid_set_framing_mode(pid, GF_TRUE);
538 : }
539 : return GF_OK;
540 : }
541 :
542 11 : static GF_Err routeout_initialize(GF_Filter *filter)
543 : {
544 : char *base_name;
545 : Bool is_atsc = GF_TRUE;
546 : char *ext=NULL;
547 11 : GF_ROUTEOutCtx *ctx = (GF_ROUTEOutCtx *) gf_filter_get_udta(filter);
548 :
549 11 : if (!ctx || !ctx->dst) return GF_BAD_PARAM;
550 :
551 11 : if (!strnicmp(ctx->dst, "route://", 8)) {
552 : is_atsc = GF_FALSE;
553 4 : } else if (strnicmp(ctx->dst, "atsc://", 7)) {
554 : return GF_NOT_SUPPORTED;
555 : }
556 :
557 11 : if (ctx->ext) {
558 : ext = ctx->ext;
559 : } else {
560 11 : if (is_atsc) {
561 4 : base_name = gf_file_basename(ctx->dst);
562 : } else {
563 7 : char *sep = strchr(ctx->dst + 8, '/');
564 7 : base_name = sep ? gf_file_basename(ctx->dst) : NULL;
565 : }
566 11 : ext = base_name ? gf_file_ext_start(base_name) : NULL;
567 11 : if (ext) ext++;
568 : }
569 :
570 : #if 0
571 : if (!ext) {
572 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Missing destination manifest type, cannot infer format!\n"));
573 : return GF_BAD_PARAM;
574 : }
575 : #endif
576 :
577 11 : if (is_atsc) {
578 4 : if (!ctx->ip) return GF_BAD_PARAM;
579 : } else {
580 : char *sep, *root;
581 7 : sep = strrchr(ctx->dst+8, ':');
582 7 : if (sep) sep[0] = 0;
583 7 : root = sep ? strchr(sep+1, '/') : NULL;
584 7 : if (root) root[0] = 0;
585 7 : if (ctx->ip) gf_free(ctx->ip);
586 7 : ctx->ip = gf_strdup(ctx->dst+8);
587 7 : if (sep) {
588 14 : ctx->first_port = atoi(sep+1);
589 7 : sep[0] = ':';
590 : }
591 7 : if (root) root[0] = '/';
592 : }
593 :
594 11 : if (!gf_sk_is_multicast_address(ctx->ip)) {
595 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] IP %s is not a multicast address\n", ctx->ip));
596 : return GF_BAD_PARAM;
597 : }
598 :
599 11 : if (ext || ctx->mime) {
600 : //static cap, streamtype = file
601 6 : ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE;
602 6 : ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE);
603 6 : ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC;
604 :
605 6 : if (ctx->mime) {
606 4 : ctx->in_caps[1].code = GF_PROP_PID_MIME;
607 4 : ctx->in_caps[1].val = PROP_NAME( ctx->mime );
608 4 : ctx->in_caps[1].flags = GF_CAPS_INPUT;
609 : } else {
610 2 : strncpy(ctx->szExt, ext, 9);
611 2 : ctx->szExt[9] = 0;
612 2 : strlwr(ctx->szExt);
613 2 : ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT;
614 2 : ctx->in_caps[1].val = PROP_NAME( ctx->szExt );
615 2 : ctx->in_caps[1].flags = GF_CAPS_INPUT;
616 : }
617 6 : gf_filter_override_caps(filter, ctx->in_caps, 2);
618 : }
619 :
620 :
621 : /*this is an alias for our main filter, nothing to initialize*/
622 11 : if (gf_filter_is_alias(filter)) {
623 : return GF_OK;
624 : }
625 :
626 7 : ctx->services = gf_list_new();
627 :
628 7 : if (is_atsc) {
629 2 : ctx->sock_atsc_lls = gf_sk_new(GF_SOCK_TYPE_UDP);
630 2 : gf_sk_setup_multicast(ctx->sock_atsc_lls, GF_ATSC_MCAST_ADDR, GF_ATSC_MCAST_PORT, 0, GF_FALSE, ctx->ifce);
631 : }
632 :
633 7 : ctx->lct_buffer = gf_malloc(sizeof(u8) * ctx->mtu);
634 7 : ctx->clock_init = gf_sys_clock_high_res();
635 7 : ctx->clock_stats = ctx->clock_init;
636 :
637 7 : if (!ctx->carousel) ctx->carousel = 1000;
638 : //move to microseconds
639 7 : ctx->carousel *= 1000;
640 7 : ctx->next_raw_file_toi = 1;
641 7 : return GF_OK;
642 : }
643 :
644 11 : static void routeout_finalize(GF_Filter *filter)
645 : {
646 : GF_ROUTEOutCtx *ctx;
647 : /*this is an alias for our main filter, nothing to finalize*/
648 11 : if (gf_filter_is_alias(filter))
649 : return;
650 :
651 7 : ctx = (GF_ROUTEOutCtx *) gf_filter_get_udta(filter);
652 :
653 21 : while (gf_list_count(ctx->services)) {
654 7 : routeout_delete_service(gf_list_pop_back(ctx->services));
655 : }
656 7 : gf_list_del(ctx->services);
657 7 : if (ctx->sock_atsc_lls)
658 2 : gf_sk_del(ctx->sock_atsc_lls);
659 :
660 7 : if (ctx->lct_buffer) gf_free(ctx->lct_buffer);
661 7 : if (ctx->lls_slt_table) gf_free(ctx->lls_slt_table);
662 7 : if (ctx->lls_time_table) gf_free(ctx->lls_time_table);
663 : }
664 :
665 9 : char *routeout_strip_base(ROUTEService *serv, char *url)
666 : {
667 : u32 len;
668 9 : if (!url) return NULL;
669 9 : if (!serv->manifest_server && !serv->manifest_url)
670 4 : return gf_strdup(url);
671 :
672 5 : len = serv->manifest_server ? (u32) strlen(serv->manifest_server) : 0;
673 4 : if (!len || !strncmp(url, serv->manifest_server, len)) {
674 1 : url += len;
675 1 : char *sep = len ? strchr(url, '/') : url;
676 1 : if (sep) {
677 1 : if (len) sep += 1;
678 1 : len = (u32) strlen(serv->manifest_url);
679 1 : if (!strncmp(sep, serv->manifest_url, len)) {
680 0 : return gf_strdup(sep + len);
681 : }
682 : }
683 : }
684 5 : return gf_strdup(url);
685 : }
686 :
687 : #define MULTIPART_BOUNDARY "_GPAC_BOUNDARY_ROUTE_.67706163_"
688 : #define ROUTE_INIT_TOI 0xFFFFFFFF
689 6266968 : static GF_Err routeout_check_service_updates(GF_ROUTEOutCtx *ctx, ROUTEService *serv)
690 : {
691 : u32 i, j, count;
692 : char temp[1000];
693 6266968 : char *payload_text = NULL;
694 : Bool manifest_updated = GF_FALSE;
695 : u32 nb_media=0, nb_media_init=0, nb_raw_files=0;
696 :
697 6266968 : count = gf_list_count(serv->pids);
698 : //check no changes in init segment or in manifests
699 22373830 : for (i=0; i<count; i++) {
700 : const GF_PropertyValue *p;
701 16106862 : ROUTEPid *rpid = gf_list_get(serv->pids, i);
702 :
703 : //raw file, nothing to check
704 16106862 : if (rpid->raw_file) {
705 0 : nb_raw_files++;
706 0 : continue;
707 : }
708 : //media file, check for init segment and hls child manifest
709 16106862 : if (!rpid->manifest_type) {
710 9839894 : nb_media++;
711 : while (1) {
712 9839903 : GF_FilterPacket *pck = gf_filter_pid_get_packet(rpid->pid);
713 9839903 : if (!pck) break;
714 :
715 2063386 : p = gf_filter_pck_get_property(pck, GF_PROP_PCK_INIT);
716 2063386 : if (p && p->value.boolean) {
717 : const u8 *data;
718 : u32 len, crc;
719 9 : data = gf_filter_pck_get_data(pck, &len);
720 9 : crc = gf_crc_32(data, len);
721 : //whenever init seg changes, bump stsid version
722 9 : if (crc != rpid->init_seg_crc) {
723 9 : if (rpid->init_seg_data) gf_free(rpid->init_seg_data);
724 9 : rpid->init_seg_data = gf_malloc(len);
725 9 : memcpy(rpid->init_seg_data, data, len);
726 9 : rpid->init_seg_size = len;
727 9 : rpid->init_seg_crc = crc;
728 9 : rpid->init_seg_sent = GF_FALSE;
729 9 : serv->stsid_changed = GF_TRUE;
730 9 : rpid->current_toi = 0;
731 :
732 9 : p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME);
733 9 : if (!p) p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PCK_FILENAME);
734 9 : if (rpid->init_seg_name) gf_free(rpid->init_seg_name);
735 9 : rpid->init_seg_name = p ? routeout_strip_base(rpid->route, p->value.string) : NULL;
736 : }
737 9 : gf_filter_pid_drop_packet(rpid->pid);
738 9 : continue;
739 : }
740 :
741 : break;
742 : }
743 9839894 : if (rpid->init_seg_data) {
744 9839894 : nb_media_init ++;
745 9839894 : if (serv->manifest_type==2) {
746 0 : if (!rpid->hld_child_pl_name)
747 : nb_media_init --;
748 : }
749 : }
750 9839894 : continue;
751 : }
752 : //manifest pid, wait for manifest
753 16 : while (1) {
754 : char szLocManfest[100];
755 : u32 man_size, man_crc;
756 : const char *file_name=NULL, *proto;
757 6266984 : GF_FilterPacket *pck = gf_filter_pid_get_packet(rpid->pid);
758 6266984 : if (!pck) break;
759 :
760 16 : p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME);
761 16 : if (!p) p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_URL);
762 :
763 16 : if (p)
764 16 : file_name = p->value.string;
765 : else
766 0 : file_name = ctx->dst;
767 :
768 16 : if (file_name) {
769 16 : proto = strstr(file_name, "://");
770 16 : if (proto) {
771 13 : if (ctx->sock_atsc_lls) {
772 8 : file_name = proto[3] ? proto+3 : NULL;
773 : } else {
774 5 : file_name = strchr(proto+3, '/');
775 5 : if (file_name) file_name ++;
776 : }
777 : }
778 : }
779 :
780 8 : if (!file_name) {
781 8 : snprintf(szLocManfest, 100, "manifest.%s", (serv->manifest_type==2) ? "m3u8" : "mpd");
782 : file_name = szLocManfest;
783 8 : GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("[ROUTE] Cannot guess manifest name, assuming %s\n", file_name));
784 : }
785 :
786 : //child subplaylist
787 16 : p = gf_filter_pck_get_property(pck, GF_PROP_PCK_HLS_REF);
788 16 : if (p && p->value.uint) {
789 : u32 k;
790 : ROUTEPid *media_pid = NULL;
791 0 : for (k=0; k<count; k++) {
792 0 : media_pid = gf_list_get(serv->pids, k);
793 0 : if (media_pid->hls_ref_id == p->value.longuint)
794 : break;
795 : media_pid = NULL;
796 : }
797 : //not yet available - happens when loading an existing m3u8 session
798 0 : if (!media_pid)
799 : break;
800 :
801 : const u8 *data;
802 : u32 len, crc;
803 0 : data = gf_filter_pck_get_data(pck, &len);
804 0 : crc = gf_crc_32(data, len);
805 0 : if (crc != media_pid->hld_child_pl_crc) {
806 0 : if (media_pid->hld_child_pl) gf_free(media_pid->hld_child_pl);
807 0 : media_pid->hld_child_pl = gf_malloc(len+1);
808 0 : memcpy(media_pid->hld_child_pl, data, len);
809 0 : media_pid->hld_child_pl[len] = 0;
810 0 : media_pid->hld_child_pl_crc = crc;
811 :
812 0 : if (!media_pid->hld_child_pl_name || strcmp(media_pid->hld_child_pl_name, file_name)) {
813 0 : if (media_pid->hld_child_pl_name) gf_free(media_pid->init_seg_name);
814 0 : media_pid->hld_child_pl_name = routeout_strip_base(rpid->route, (char *)file_name);
815 0 : serv->stsid_changed = GF_TRUE;
816 : }
817 0 : media_pid->update_hls_child_pl = GF_TRUE;
818 : }
819 : } else {
820 16 : const u8 *man_data = gf_filter_pck_get_data(pck, &man_size);
821 16 : man_crc = gf_crc_32(man_data, man_size);
822 16 : if (man_crc != serv->manifest_crc) {
823 16 : serv->manifest_crc = man_crc;
824 16 : if (serv->manifest) gf_free(serv->manifest);
825 16 : serv->manifest = gf_malloc(man_size+1);
826 16 : memcpy(serv->manifest, man_data, man_size);
827 16 : serv->manifest[man_size] = 0;
828 16 : serv->manifest_version++;
829 16 : if (serv->manifest_name) {
830 9 : if (strcmp(serv->manifest_name, file_name)) serv->stsid_changed = GF_TRUE;
831 9 : gf_free(serv->manifest_name);
832 : }
833 16 : serv->manifest_name = gf_strdup(file_name);
834 : manifest_updated = GF_TRUE;
835 :
836 16 : if (serv->manifest_server) gf_free(serv->manifest_server);
837 16 : if (serv->manifest_url) gf_free(serv->manifest_url);
838 16 : serv->manifest_server = serv->manifest_url = NULL;
839 :
840 16 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_URL);
841 16 : serv->manifest_url = p ? gf_strdup(p->value.string) : NULL;
842 16 : if (serv->manifest_url) {
843 16 : char *sep = strstr(serv->manifest_url, file_name);
844 16 : if (sep) sep[0] = 0;
845 16 : sep = strstr(serv->manifest_url, "://");
846 16 : if (sep) sep = strchr(sep + 3, '/');
847 16 : if (sep) {
848 7 : serv->manifest_server = serv->manifest_url;
849 7 : serv->manifest_url = gf_strdup(sep+1);
850 7 : sep[0] = 0;
851 7 : sep = strchr(serv->manifest_server + 8, ':');
852 7 : if (sep) sep[0] = 0;
853 : }
854 : }
855 :
856 :
857 16 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MIME);
858 16 : if (p) {
859 16 : if (serv->manifest_mime) gf_free(serv->manifest_mime);
860 16 : serv->manifest_mime = gf_strdup(p->value.string);
861 : }
862 : }
863 : }
864 16 : gf_filter_pid_drop_packet(rpid->pid);
865 : }
866 : }
867 : //not ready, waiting for init
868 6266968 : if ((nb_media+nb_raw_files==0) || (nb_media_init<nb_media) || (serv->manifest && !nb_media)) {
869 8 : u32 now = gf_sys_clock() - serv->creation_time;
870 8 : if (now > 5000) {
871 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] No media PIDs found for HAS service after %d ms, aborting !\n", now));
872 0 : serv->is_done = GF_TRUE;
873 0 : return GF_SERVICE_ERROR;
874 : }
875 : return GF_OK;
876 : }
877 :
878 : //not ready, waiting for manifest
879 6266960 : if (!serv->manifest && !nb_raw_files) {
880 : return GF_OK;
881 : }
882 : //already setup and no changes
883 6266956 : else if (!serv->wait_for_inputs) {
884 6266948 : if (!manifest_updated && !serv->stsid_changed)
885 : return GF_OK;
886 : }
887 17 : if (serv->wait_for_inputs) {
888 8 : if (!serv->manifest) {
889 : assert(nb_raw_files);
890 0 : serv->stsid_changed = GF_TRUE;
891 : }
892 8 : serv->wait_for_inputs = GF_FALSE;
893 : }
894 :
895 17 : if (serv->stsid_changed) {
896 8 : serv->stsid_version++;
897 :
898 26 : for (i=0; i<count; i++) {
899 18 : ROUTEPid *rpid = gf_list_get(serv->pids, i);
900 18 : if (rpid->manifest_type) continue;
901 10 : rpid->clock_at_first_pck = 0;
902 : }
903 : }
904 :
905 : //ATSC3: mbms enveloppe, service description, astcROUTE bundle
906 17 : if (ctx->sock_atsc_lls) {
907 : const GF_PropertyValue *p;
908 : char *service_name;
909 : ROUTEPid *rpid;
910 8 : u32 service_id = serv->service_id;
911 8 : if (!service_id) {
912 : service_id = 1;
913 : }
914 8 : gf_dynstrcat(&payload_text, "Content-Type: multipart/related; type=\"application/mbms-envelope+xml\"; boundary=\""MULTIPART_BOUNDARY"\"\r\n\r\n", NULL);
915 :
916 8 : gf_dynstrcat(&payload_text, "--"MULTIPART_BOUNDARY"\r\nContent-Type: application/mbms-envelope+xml\r\nContent-Location: envelope.xml\r\n\r\n", NULL);
917 :
918 : //dump usd first, then S-TSID then manifest
919 8 : gf_dynstrcat(&payload_text,
920 : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
921 : "<metadataEnvelope xmlns=\"urn:3gpp:metadata:2005:MBMS:envelope\">\n"
922 : " <item metadataURI=\"usbd.xml\" version=\"", NULL);
923 8 : snprintf(temp, 100, "%d", serv->stsid_version);
924 8 : gf_dynstrcat(&payload_text, temp, NULL);
925 :
926 8 : gf_dynstrcat(&payload_text, "\" contentType=\"", NULL);
927 8 : if (ctx->korean)
928 0 : gf_dynstrcat(&payload_text, "application/mbms-user-service-description+xml", NULL);
929 : else
930 8 : gf_dynstrcat(&payload_text, "application/route-usd+xml", NULL);
931 8 : gf_dynstrcat(&payload_text, "\"/>\n", NULL);
932 :
933 8 : gf_dynstrcat(&payload_text,
934 : " <item metadataURI=\"stsid.xml\" version=\"", NULL);
935 8 : snprintf(temp, 100, "%d", serv->stsid_version);
936 8 : gf_dynstrcat(&payload_text, temp, NULL);
937 :
938 8 : gf_dynstrcat(&payload_text, "\" contentType=\"", NULL);
939 8 : if (ctx->korean)
940 0 : gf_dynstrcat(&payload_text, "application/s-tsid", NULL);
941 : else
942 8 : gf_dynstrcat(&payload_text, "application/route-s-tsid+xml", NULL);
943 8 : gf_dynstrcat(&payload_text, "\"/>\n", NULL);
944 :
945 :
946 8 : if (serv->manifest) {
947 8 : snprintf(temp, 1000, " <item metadataURI=\"%s\" version=\"%d\" contentType=\"%s\"/>\n", serv->manifest_name, serv->manifest_version, serv->manifest_mime);
948 8 : gf_dynstrcat(&payload_text, temp, NULL);
949 : }
950 :
951 8 : gf_dynstrcat(&payload_text, "</metadataEnvelope>\n\r\n", NULL);
952 :
953 8 : gf_dynstrcat(&payload_text, "--"MULTIPART_BOUNDARY"\r\nContent-Type: ", NULL);
954 8 : if (ctx->korean)
955 0 : gf_dynstrcat(&payload_text, "application/mbms-user-service-description+xml", NULL);
956 : else
957 8 : gf_dynstrcat(&payload_text, "application/route-usd+xml", NULL);
958 8 : gf_dynstrcat(&payload_text, "\r\nContent-Location: usbd.xml\r\n\r\n", NULL);
959 :
960 8 : rpid = gf_list_get(serv->pids, 0);
961 8 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_SERVICE_NAME);
962 8 : if (p && p->value.string)
963 0 : service_name = p->value.string;
964 : else
965 : service_name = "GPAC TV";
966 :
967 : snprintf(temp, 1000, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
968 : "<BundleDescriptionROUTE xmlns=\"tag:atsc.org,2016:XMLSchemas/ATSC3/Delivery/ROUTEUSD/1.0/\">\n"
969 : " <UserServiceDescription serviceId=\"%d\">\n"
970 : " <Name lang=\"eng\">%s</Name>\n"
971 : " <DeliveryMethod>\n"
972 : " <BroadcastAppService>\n", service_id, service_name);
973 8 : gf_dynstrcat(&payload_text, temp, NULL);
974 :
975 24 : for (i=0;i<count; i++) {
976 16 : rpid = gf_list_get(serv->pids, i);
977 16 : if (rpid->manifest_type) continue;
978 : //set template
979 8 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_TEMPLATE);
980 8 : if (p) {
981 8 : char *tpl = gf_strdup(p->value.string);
982 8 : char *sep = strchr(tpl, '$');
983 8 : if (sep) sep[0] = 0;
984 8 : if (!strstr(payload_text, tpl)) {
985 8 : gf_dynstrcat(&payload_text, " <BasePattern>", NULL);
986 8 : gf_dynstrcat(&payload_text, tpl, NULL);
987 8 : gf_dynstrcat(&payload_text, "</BasePattern>\n", NULL);
988 : }
989 8 : gf_free(tpl);
990 : }
991 : }
992 :
993 8 : gf_dynstrcat(&payload_text, " </BroadcastAppService>\n"
994 : " </DeliveryMethod>\n"
995 : " </UserServiceDescription>\n"
996 : "</BundleDescriptionROUTE>\n\r\n", NULL);
997 : }
998 : //ROUTE: only inject manifest and S-TSID
999 : else {
1000 9 : gf_dynstrcat(&payload_text, "Content-Type: multipart/related; type=\"", NULL);
1001 9 : if (serv->manifest) {
1002 9 : gf_dynstrcat(&payload_text, serv->manifest_mime, NULL);
1003 : }
1004 0 : else if (ctx->korean)
1005 0 : gf_dynstrcat(&payload_text, "application/s-tsid", NULL);
1006 : else
1007 0 : gf_dynstrcat(&payload_text, "application/route-s-tsid+xml", NULL);
1008 :
1009 9 : gf_dynstrcat(&payload_text, "\"; boundary=\""MULTIPART_BOUNDARY"\"\r\n\r\n", NULL);
1010 : }
1011 :
1012 17 : if (serv->manifest) {
1013 17 : gf_dynstrcat(&payload_text, "--"MULTIPART_BOUNDARY"\r\nContent-Type: ", NULL);
1014 17 : gf_dynstrcat(&payload_text, serv->manifest_mime, NULL);
1015 17 : gf_dynstrcat(&payload_text, "\r\nContent-Location: ", NULL);
1016 17 : gf_dynstrcat(&payload_text, serv->manifest_name, NULL);
1017 17 : gf_dynstrcat(&payload_text, "\r\n\r\n", NULL);
1018 17 : gf_dynstrcat(&payload_text, serv->manifest, NULL);
1019 17 : gf_dynstrcat(&payload_text, "\r\n\r\n", NULL);
1020 : }
1021 :
1022 17 : gf_dynstrcat(&payload_text, "--"MULTIPART_BOUNDARY"\r\nContent-Type: ", NULL);
1023 17 : if (ctx->korean)
1024 0 : gf_dynstrcat(&payload_text, "application/s-tsid", NULL);
1025 : else
1026 17 : gf_dynstrcat(&payload_text, "application/route-s-tsid+xml", NULL);
1027 17 : gf_dynstrcat(&payload_text, "\r\nContent-Location: stsid.xml\r\n\r\n", NULL);
1028 :
1029 17 : gf_dynstrcat(&payload_text, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1030 : "<S-TSID xmlns=\"tag:atsc.org,2016:XMLSchemas/ATSC3/Delivery/S-TSID/1.0/\" xmlns:afdt=\"tag:atsc.org,2016:XMLSchemas/ATSC3/Delivery/ATSC-FDT/1.0/\" xmlns:fdt=\"urn:ietf:params:xml:ns:fdt\">\n", NULL);
1031 :
1032 34 : for (j=0; j<gf_list_count(serv->rlcts); j++) {
1033 17 : ROUTELCT *rlct = gf_list_get(serv->rlcts, j);
1034 :
1035 : const char *src_ip;
1036 : char szIP[GF_MAX_IP_NAME_LEN];
1037 17 : src_ip = ctx->ifce;
1038 17 : if (!src_ip) {
1039 17 : gf_sk_get_local_ip(rlct->sock, szIP);
1040 : src_ip = szIP;
1041 : }
1042 :
1043 17 : snprintf(temp, 1000, " <RS dIpAddr=\"%s\" dPort=\"%d\" sIpAddr=\"%s\">\n", rlct->ip, rlct->port, src_ip);
1044 17 : gf_dynstrcat(&payload_text, temp, NULL);
1045 :
1046 53 : for (i=0; i<count; i++) {
1047 : const GF_PropertyValue *p;
1048 36 : ROUTEPid *rpid = gf_list_get(serv->pids, i);
1049 36 : if (rpid->manifest_type) continue;
1050 19 : if (rpid->rlct != rlct) continue;
1051 :
1052 19 : if (rpid->bandwidth) {
1053 6 : u32 kbps = rpid->bandwidth / 1000;
1054 6 : kbps *= 110;
1055 6 : kbps /= 100;
1056 6 : snprintf(temp, 100, " <LS tsi=\"%d\" bw=\"%d\">\n", rpid->tsi, kbps);
1057 : } else {
1058 13 : snprintf(temp, 100, " <LS tsi=\"%d\">\n", rpid->tsi);
1059 : }
1060 19 : gf_dynstrcat(&payload_text, temp, NULL);
1061 :
1062 19 : if (serv->manifest) {
1063 19 : gf_dynstrcat(&payload_text, " <SrcFlow rt=\"true\">\n", NULL);
1064 : } else {
1065 0 : gf_dynstrcat(&payload_text, " <SrcFlow rt=\"false\">\n", NULL);
1066 : }
1067 :
1068 19 : if (ctx->korean) {
1069 0 : gf_dynstrcat(&payload_text, " <EFDT version=\"0\">\n", NULL);
1070 : } else {
1071 19 : gf_dynstrcat(&payload_text, " <EFDT>\n", NULL);
1072 : }
1073 :
1074 19 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_TEMPLATE);
1075 19 : if (p) {
1076 : char *sep;
1077 19 : strcpy(temp, p->value.string);
1078 19 : sep = strstr(temp, "$Number");
1079 19 : if (sep) {
1080 19 : sep[0] = 0;
1081 : strcat(temp, "$TOI");
1082 19 : sep = strstr(p->value.string, "$Number");
1083 19 : strcat(temp, sep + 7);
1084 : }
1085 : }
1086 :
1087 19 : if (ctx->korean) {
1088 0 : if (p) {
1089 0 : gf_dynstrcat(&payload_text, " <FileTemplate>", NULL);
1090 0 : gf_dynstrcat(&payload_text, temp, NULL);
1091 0 : gf_dynstrcat(&payload_text, "</FileTemplate>\n", NULL);
1092 : }
1093 0 : gf_dynstrcat(&payload_text, " <FDTParameters>\n", NULL);
1094 : } else {
1095 : u32 max_size = 0;
1096 19 : gf_dynstrcat(&payload_text, " <FDT-Instance afdt:efdtVersion=\"0\"", NULL);
1097 19 : if (p) {
1098 19 : gf_dynstrcat(&payload_text, " afdt:fileTemplate=\"", NULL);
1099 19 : gf_dynstrcat(&payload_text, temp, NULL);
1100 19 : gf_dynstrcat(&payload_text, "\"", NULL);
1101 : }
1102 :
1103 19 : if (!rpid->bandwidth || !rpid->dash_dur.den || !rpid->dash_dur.num) {
1104 13 : switch (rpid->stream_type) {
1105 : case GF_STREAM_VISUAL: max_size = 5000000; break;
1106 0 : case GF_STREAM_AUDIO: max_size = 1000000; break;
1107 0 : default: max_size = 100000; break;
1108 : }
1109 : } else {
1110 6 : max_size = rpid->bandwidth / 8;
1111 6 : max_size *= rpid->dash_dur.num;
1112 6 : max_size /= rpid->dash_dur.den;
1113 : //use 2x avg rate announced as safety
1114 6 : max_size *= 2;
1115 : }
1116 :
1117 : snprintf(temp, 1000, " Expires=\"4294967295\" afdt:maxTransportSize=\"%d\">\n", max_size);
1118 19 : gf_dynstrcat(&payload_text, temp, NULL);
1119 : }
1120 :
1121 19 : if (rpid->init_seg_name) {
1122 : snprintf(temp, 1000, " <fdt:File Content-Location=\"%s\" TOI=\"%u\"/>\n", rpid->init_seg_name, ROUTE_INIT_TOI);
1123 19 : gf_dynstrcat(&payload_text, temp, NULL);
1124 : }
1125 19 : if (rpid->hld_child_pl_name) {
1126 : snprintf(temp, 1000, " <fdt:File Content-Location=\"%s\" TOI=\"%u\"/>\n", rpid->hld_child_pl_name, ROUTE_INIT_TOI - 1);
1127 0 : gf_dynstrcat(&payload_text, temp, NULL);
1128 : }
1129 19 : if (rpid->raw_file) {
1130 : const char *mime, *url;
1131 0 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MIME);
1132 0 : if (p && p->value.string && strcmp(p->value.string, "*"))
1133 0 : mime = p->value.string;
1134 : else {
1135 : mime = "application/octet-string";
1136 : }
1137 0 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ROUTE_NAME);
1138 0 : if (p && p->value.string)
1139 : url = p->value.string;
1140 : else {
1141 0 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_URL);
1142 0 : url = (p && p->value.string) ? gf_file_basename(p->value.string) : "/dev/null";
1143 : }
1144 0 : snprintf(temp, 1000, " <fdt:File Content-Location=\"%s\" Content-Type=\"%s\" TOI=\"%u\"/>\n", url, mime, rpid->current_toi);
1145 0 : gf_dynstrcat(&payload_text, temp, NULL);
1146 : }
1147 :
1148 19 : if (ctx->korean) {
1149 0 : gf_dynstrcat(&payload_text, " </FDTParameters>\n", NULL);
1150 : } else {
1151 19 : gf_dynstrcat(&payload_text, " </FDT-Instance>\n", NULL);
1152 : }
1153 19 : gf_dynstrcat(&payload_text, " </EFDT>\n", NULL);
1154 :
1155 19 : if (rpid->stream_type) {
1156 : const char *rep_id;
1157 19 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_REP_ID);
1158 19 : if (p && p->value.string) {
1159 : rep_id = p->value.string;
1160 : } else {
1161 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Missing representation ID on PID (broken input filter), using \"1\"\n"));
1162 : rep_id = "1";
1163 : }
1164 19 : gf_dynstrcat(&payload_text, " <ContentInfo>\n", NULL);
1165 19 : gf_dynstrcat(&payload_text, " <MediaInfo repId=\"", NULL);
1166 19 : gf_dynstrcat(&payload_text, rep_id, NULL);
1167 19 : gf_dynstrcat(&payload_text, "\"", NULL);
1168 19 : switch (rpid->stream_type) {
1169 16 : case GF_STREAM_VISUAL:
1170 16 : gf_dynstrcat(&payload_text, " contentType=\"video\"", NULL);
1171 16 : break;
1172 3 : case GF_STREAM_AUDIO:
1173 3 : gf_dynstrcat(&payload_text, " contentType=\"audio\"", NULL);
1174 3 : break;
1175 0 : case GF_STREAM_TEXT:
1176 0 : gf_dynstrcat(&payload_text, " contentType=\"subtitles\"", NULL);
1177 0 : break;
1178 : //other stream types are not mapped in ATSC3, do not set content type
1179 : }
1180 19 : gf_dynstrcat(&payload_text, "/>\n", NULL);
1181 19 : gf_dynstrcat(&payload_text, " </ContentInfo>\n", NULL);
1182 : }
1183 : //setup payload format - we remove srcFecPayloadId=\"0\" as there is no FEC support yet
1184 19 : snprintf(temp, 1000,
1185 : " <Payload codePoint=\"%d\" formatId=\"%d\" frag=\"0\" order=\"true\"/>\n"
1186 : , rpid->fmtp, rpid->mode);
1187 :
1188 19 : gf_dynstrcat(&payload_text, temp, NULL);
1189 :
1190 19 : gf_dynstrcat(&payload_text,
1191 : " </SrcFlow>\n"
1192 : " </LS>\n", NULL);
1193 : }
1194 17 : gf_dynstrcat(&payload_text, " </RS>\n", NULL);
1195 : }
1196 :
1197 17 : gf_dynstrcat(&payload_text, "</S-TSID>\n\r\n", NULL);
1198 :
1199 17 : gf_dynstrcat(&payload_text, "--"MULTIPART_BOUNDARY"--\n", NULL);
1200 :
1201 :
1202 17 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Updated Manifest+S-TSID bundle to:\n%s\n", payload_text));
1203 :
1204 : //compress and store as final payload
1205 17 : if (serv->stsid_bundle) gf_free(serv->stsid_bundle);
1206 17 : serv->stsid_bundle = (u8 *) payload_text;
1207 17 : serv->stsid_bundle_size = 1 + (u32) strlen(payload_text);
1208 17 : gf_gz_compress_payload(&serv->stsid_bundle, serv->stsid_bundle_size, &serv->stsid_bundle_size);
1209 :
1210 17 : serv->stsid_bundle_toi = 0x80000000; //compressed
1211 17 : if (manifest_updated) serv->stsid_bundle_toi |= (1<<18);
1212 17 : if (serv->stsid_changed) {
1213 8 : serv->stsid_bundle_toi |= (1<<17);
1214 8 : serv->stsid_bundle_toi |= (serv->stsid_version & 0xFF);
1215 9 : } else if (manifest_updated) {
1216 9 : serv->stsid_bundle_toi |= (serv->manifest_version & 0xFF);
1217 : }
1218 :
1219 17 : serv->stsid_changed = GF_FALSE;
1220 :
1221 : //reset last sent time
1222 17 : serv->last_stsid_clock = 0;
1223 17 : return GF_OK;
1224 : }
1225 :
1226 :
1227 4334 : u32 routeout_lct_send(GF_ROUTEOutCtx *ctx, GF_Socket *sock, u32 tsi, u32 toi, u32 codepoint, u8 *payload, u32 len, u32 offset, u32 service_id, u32 total_size, u32 offset_in_frame)
1228 : {
1229 4334 : u32 max_size = ctx->mtu;
1230 : u32 send_payl_size;
1231 : u32 hdr_len = 4;
1232 : u32 hpos;
1233 : GF_Err e;
1234 :
1235 4334 : if (total_size) {
1236 : //TOL extension
1237 1288 : if (total_size<=0xFFFFFF) hdr_len += 1;
1238 : else hdr_len += 2;
1239 : }
1240 :
1241 : //start offset is not in header
1242 4334 : send_payl_size = 4 * (hdr_len+1) + len - offset;
1243 4334 : if (send_payl_size > max_size) {
1244 4096 : send_payl_size = max_size - 4 * (hdr_len+1);
1245 : } else {
1246 238 : send_payl_size = len - offset;
1247 : }
1248 4334 : ctx->lct_buffer[0] = 0x12; //V=b0001, C=b00, PSI=b10
1249 4334 : ctx->lct_buffer[1] = 0xA0; //S=b1, 0=b01, h=b0, res=b00, A=b0, B=X
1250 : //set close flag only if total_len is known
1251 4334 : if (total_size && (offset + send_payl_size == len))
1252 127 : ctx->lct_buffer[1] |= 1;
1253 :
1254 4334 : ctx->lct_buffer[2] = hdr_len;
1255 4334 : ctx->lct_buffer[3] = (u8) codepoint;
1256 : hpos = 4;
1257 :
1258 : #define PUT_U32(_val)\
1259 : ctx->lct_buffer[hpos] = (_val>>24 & 0xFF);\
1260 : ctx->lct_buffer[hpos+1] = (_val>>16 & 0xFF);\
1261 : ctx->lct_buffer[hpos+2] = (_val>>8 & 0xFF);\
1262 : ctx->lct_buffer[hpos+3] = (_val & 0xFF); \
1263 : hpos+=4;
1264 :
1265 : //CCI=0
1266 4334 : PUT_U32(0);
1267 4334 : PUT_U32(tsi);
1268 4334 : PUT_U32(toi);
1269 :
1270 : //total length
1271 4334 : if (total_size) {
1272 1288 : if (total_size<=0xFFFFFF) {
1273 1288 : ctx->lct_buffer[hpos] = GF_LCT_EXT_TOL24;
1274 1288 : ctx->lct_buffer[hpos+1] = total_size>>16 & 0xFF;
1275 1288 : ctx->lct_buffer[hpos+2] = total_size>>8 & 0xFF;
1276 1288 : ctx->lct_buffer[hpos+3] = total_size & 0xFF;
1277 : hpos+=4;
1278 : } else {
1279 0 : ctx->lct_buffer[hpos] = GF_LCT_EXT_TOL48;
1280 0 : ctx->lct_buffer[hpos+1] = 2; //2 x 32 bits for header ext
1281 0 : ctx->lct_buffer[hpos+2] = 0;
1282 0 : ctx->lct_buffer[hpos+3] = 0;
1283 : hpos+=4;
1284 0 : PUT_U32(total_size);
1285 : }
1286 : }
1287 :
1288 : //start_offset
1289 4334 : PUT_U32(offset_in_frame);
1290 :
1291 : assert(send_payl_size+hpos <= ctx->mtu);
1292 :
1293 4334 : GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("[ROUTE] LCT SID %u TSI %u TOI %u size %u (frag %u total %u) offset %u (%u in obj)\n", service_id, tsi, toi, send_payl_size, len, total_size, offset, offset_in_frame));
1294 :
1295 4334 : memcpy(ctx->lct_buffer + hpos, payload + offset, send_payl_size);
1296 4334 : e = gf_sk_send(sock, ctx->lct_buffer, send_payl_size + hpos);
1297 4334 : if (e) {
1298 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Failed to send LCT object TSI %u TOI %u fragment: %s\n", tsi, toi, gf_error_to_string(e) ));
1299 : }
1300 : //store what we actually sent including header for rate estimation
1301 4334 : ctx->bytes_sent += send_payl_size + hpos;
1302 : //but return what we sent from the source
1303 4334 : return send_payl_size;
1304 : }
1305 :
1306 47 : static GF_Err routeout_service_send_bundle(GF_ROUTEOutCtx *ctx, ROUTEService *serv)
1307 : {
1308 : u32 offset = 0;
1309 :
1310 : //we don't regulate
1311 141 : while (offset < serv->stsid_bundle_size) {
1312 : /*send lct with codepoint "NRT - Unsigned Package Mode" (multipart):
1313 : "In broadcast delivery of SLS for a DASH-formatted streaming Service delivered using ROUTE, since SLS fragments are NRT files in nature,
1314 : their carriage over the ROUTE session/LCT channel assigned by the SLT shall be in accordance to the Unsigned Packaged Mode or
1315 : the Signed Package Mode as described in Section A.3.3.4 and A.3.3.5, respectively"
1316 : */
1317 47 : offset += routeout_lct_send(ctx, serv->rlct_base->sock, 0, serv->stsid_bundle_toi, 3, serv->stsid_bundle, serv->stsid_bundle_size, offset, serv->service_id, serv->stsid_bundle_size, offset);
1318 : }
1319 47 : GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("[ROUTE] Sent service %d bundle (%d bytes)\n", serv->service_id, serv->stsid_bundle_size));
1320 47 : return GF_OK;
1321 : }
1322 :
1323 1095 : static void routeout_fetch_packet(GF_ROUTEOutCtx *ctx, ROUTEPid *rpid)
1324 : {
1325 : const GF_PropertyValue *p;
1326 : Bool start, end;
1327 : u64 pck_dur;
1328 : u64 ts;
1329 : Bool has_ts;
1330 : Bool pck_dur_for_segment;
1331 :
1332 : retry:
1333 :
1334 1095 : rpid->current_pck = gf_filter_pid_get_packet(rpid->pid);
1335 1095 : if (!rpid->current_pck) {
1336 942 : if (gf_filter_pid_is_eos(rpid->pid)) {
1337 : #if 0
1338 : if (gf_filter_reporting_enabled(filter)) {
1339 : char szStatus[1024];
1340 : snprintf(szStatus, 1024, "%s: done - wrote "LLU" bytes", gf_file_basename(ctx->szFileName), ctx->nb_write);
1341 : gf_filter_update_status(filter, 10000, szStatus);
1342 : }
1343 : #endif
1344 :
1345 5 : if (rpid->route->dash_mode && rpid->res_size) {
1346 : GF_FilterEvent evt;
1347 0 : GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, rpid->pid);
1348 : evt.seg_size.seg_url = NULL;
1349 :
1350 0 : if (rpid->route->dash_mode==1) {
1351 0 : evt.seg_size.is_init = GF_TRUE;
1352 0 : rpid->route->dash_mode = 2;
1353 0 : evt.seg_size.media_range_start = 0;
1354 0 : evt.seg_size.media_range_end = 0;
1355 0 : gf_filter_pid_send_event(rpid->pid, &evt);
1356 : } else {
1357 : evt.seg_size.is_init = GF_FALSE;
1358 0 : evt.seg_size.media_range_start = rpid->offset_at_seg_start;
1359 0 : evt.seg_size.media_range_end = rpid->res_size - 1;
1360 0 : gf_filter_pid_send_event(rpid->pid, &evt);
1361 : }
1362 : }
1363 : //done
1364 5 : rpid->res_size = 0;
1365 942 : return;
1366 : }
1367 : return;
1368 : }
1369 153 : gf_filter_pck_ref(&rpid->current_pck);
1370 153 : gf_filter_pid_drop_packet(rpid->pid);
1371 :
1372 153 : gf_filter_pck_get_framing(rpid->current_pck, &start, &end);
1373 :
1374 : //trash redundant info for raw files
1375 153 : if (rpid->raw_file) {
1376 0 : u32 dep_flags = gf_filter_pck_get_dependency_flags(rpid->current_pck);
1377 : //redundant packet, do not store
1378 0 : if ((dep_flags & 0x3) == 1) {
1379 0 : gf_filter_pck_unref(rpid->current_pck);
1380 0 : rpid->current_pck = NULL;
1381 : goto retry;
1382 : }
1383 : }
1384 :
1385 153 : rpid->pck_data = gf_filter_pck_get_data(rpid->current_pck, &rpid->pck_size);
1386 153 : rpid->pck_offset = 0;
1387 :
1388 153 : p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_INIT);
1389 153 : if (p && p->value.boolean) {
1390 : u32 crc;
1391 : assert(start && end);
1392 0 : crc = gf_crc_32(rpid->pck_data, rpid->pck_size);
1393 : //whenever init seg changes, bump stsid version
1394 0 : if (crc != rpid->init_seg_crc) {
1395 0 : rpid->init_seg_crc = crc;
1396 0 : rpid->route->stsid_changed = GF_TRUE;
1397 0 : rpid->current_toi = 0;
1398 0 : if (rpid->init_seg_data) gf_free(rpid->init_seg_data);
1399 0 : rpid->init_seg_data = gf_malloc(rpid->pck_size);
1400 0 : memcpy(rpid->init_seg_data, rpid->pck_data, rpid->pck_size);
1401 0 : rpid->init_seg_size = rpid->pck_size;
1402 0 : rpid->init_seg_sent = GF_FALSE;
1403 0 : p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_FILENAME);
1404 0 : if (rpid->init_seg_name) gf_free(rpid->init_seg_name);
1405 0 : rpid->init_seg_name = p ? routeout_strip_base(rpid->route, p->value.string) : NULL;
1406 : }
1407 0 : gf_filter_pck_unref(rpid->current_pck);
1408 0 : rpid->current_pck = NULL;
1409 : goto retry;
1410 : }
1411 :
1412 153 : p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PID_HLS_PLAYLIST);
1413 153 : if (p && p->value.string) {
1414 : u32 crc;
1415 : assert(start && end);
1416 0 : crc = gf_crc_32(rpid->pck_data, rpid->pck_size);
1417 : //whenever init seg changes, bump stsid version
1418 0 : if (crc != rpid->hld_child_pl_crc) {
1419 0 : rpid->hld_child_pl_crc = crc;
1420 0 : if (rpid->hld_child_pl) gf_free(rpid->hld_child_pl);
1421 0 : rpid->hld_child_pl = gf_malloc(rpid->pck_size+1);
1422 0 : memcpy(rpid->hld_child_pl, rpid->pck_data, rpid->pck_size);
1423 0 : rpid->hld_child_pl[rpid->pck_size] = 0;
1424 :
1425 0 : if (!rpid->hld_child_pl_name || strcmp(rpid->hld_child_pl_name, p->value.string)) {
1426 0 : rpid->route->stsid_changed = GF_TRUE;
1427 0 : if (rpid->hld_child_pl_name) gf_free(rpid->hld_child_pl_name);
1428 0 : rpid->hld_child_pl_name = routeout_strip_base(rpid->route, p->value.string);
1429 : }
1430 : }
1431 0 : gf_filter_pck_unref(rpid->current_pck);
1432 0 : rpid->current_pck = NULL;
1433 : goto retry;
1434 : }
1435 153 : if (rpid->route->dash_mode) {
1436 0 : p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_FILENUM);
1437 0 : if (p) {
1438 : GF_FilterEvent evt;
1439 :
1440 0 : GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, rpid->pid);
1441 : evt.seg_size.seg_url = NULL;
1442 :
1443 0 : if (rpid->route->dash_mode==1) {
1444 0 : evt.seg_size.is_init = GF_TRUE;
1445 0 : rpid->route->dash_mode = 2;
1446 0 : evt.seg_size.media_range_start = 0;
1447 0 : evt.seg_size.media_range_end = 0;
1448 0 : gf_filter_pid_send_event(rpid->pid, &evt);
1449 : } else {
1450 : evt.seg_size.is_init = GF_FALSE;
1451 0 : evt.seg_size.media_range_start = rpid->offset_at_seg_start;
1452 0 : evt.seg_size.media_range_end = rpid->res_size - 1;
1453 0 : rpid->offset_at_seg_start = evt.seg_size.media_range_end;
1454 0 : gf_filter_pid_send_event(rpid->pid, &evt);
1455 : }
1456 0 : if ( gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_FILENAME))
1457 0 : start = GF_TRUE;
1458 : }
1459 : }
1460 :
1461 153 : if (rpid->raw_file) {
1462 : assert(start && end);
1463 :
1464 0 : if (rpid->seg_name) gf_free(rpid->seg_name);
1465 0 : rpid->seg_name = "unknown";
1466 0 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ROUTE_NAME);
1467 0 : if (p)
1468 0 : rpid->seg_name = p->value.string;
1469 : else {
1470 0 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_URL);
1471 0 : if (p) rpid->seg_name = gf_file_basename(p->value.string);
1472 : }
1473 0 : rpid->seg_name = gf_strdup(rpid->seg_name);
1474 :
1475 : //setup carousel period
1476 0 : rpid->carousel_time_us = ctx->carousel;
1477 0 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ROUTE_CAROUSEL);
1478 0 : if (p) {
1479 0 : rpid->carousel_time_us = p->value.frac.num;
1480 0 : rpid->carousel_time_us *= 1000000;
1481 0 : rpid->carousel_time_us /= p->value.frac.den;
1482 : }
1483 : //setup upload time
1484 0 : rpid->current_dur_us = ctx->carousel;
1485 0 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ROUTE_SENDTIME);
1486 0 : if (p) {
1487 0 : rpid->current_dur_us = p->value.frac.num;
1488 0 : rpid->current_dur_us *= 1000000;
1489 0 : rpid->current_dur_us /= p->value.frac.den;
1490 : }
1491 0 : if (rpid->carousel_time_us && (rpid->current_dur_us>rpid->carousel_time_us)) {
1492 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("[ROUTE] Requested upload time of file "LLU" is greater than its carousel time "LLU", adjusting carousel\n", rpid->current_dur_us, rpid->carousel_time_us));
1493 0 : rpid->carousel_time_us = rpid->current_dur_us;
1494 : }
1495 0 : rpid->clock_at_pck = rpid->current_cts_us = rpid->cts_us_at_frame_start = ctx->clock;
1496 0 : rpid->full_frame_size = rpid->pck_size;
1497 : return;
1498 : }
1499 :
1500 153 : if (start) {
1501 42 : p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_FILENAME);
1502 42 : if (rpid->seg_name) gf_free(rpid->seg_name);
1503 42 : if (p) {
1504 42 : rpid->seg_name = rpid->use_basename ? gf_file_basename(p->value.string) : p->value.string;
1505 : } else {
1506 0 : rpid->seg_name = "unknown";
1507 : }
1508 42 : rpid->seg_name = gf_strdup(rpid->seg_name);
1509 :
1510 : //file num increased per packet, open new file
1511 42 : p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_FILENUM);
1512 42 : if (p)
1513 42 : rpid->current_toi = p->value.uint;
1514 : else {
1515 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Missing filenum on segment %s, something is wrong in demux chain - assuming +1 increase\n", rpid->seg_name));
1516 0 : rpid->current_toi ++;
1517 : }
1518 42 : rpid->frag_idx = 0;
1519 42 : rpid->full_frame_size = end ? rpid->pck_size : 0;
1520 42 : rpid->cumulated_frag_size = rpid->pck_size;
1521 42 : rpid->push_init = GF_TRUE;
1522 42 : rpid->frag_offset = 0;
1523 : } else {
1524 111 : rpid->frag_idx++;
1525 111 : rpid->cumulated_frag_size += rpid->pck_size;
1526 111 : if (end) {
1527 18 : rpid->full_frame_size = rpid->cumulated_frag_size;
1528 18 : rpid->force_tol_send = GF_TRUE;
1529 : }
1530 : }
1531 :
1532 :
1533 153 : pck_dur = gf_filter_pck_get_duration(rpid->current_pck);
1534 : //check if duration is for the entire segment or this fragment (cf forward=file in dmx_dash.c)
1535 : pck_dur_for_segment = GF_FALSE;
1536 153 : if (start) {
1537 42 : rpid->pck_dur_at_frame_start = 0;
1538 42 : if (gf_filter_pck_get_carousel_version(rpid->current_pck))
1539 : pck_dur_for_segment = GF_TRUE;
1540 : }
1541 :
1542 153 : ts = gf_filter_pck_get_cts(rpid->current_pck);
1543 153 : has_ts = (ts==GF_FILTER_NO_TS) ? GF_FALSE : GF_TRUE;
1544 :
1545 153 : if (
1546 : //no TS and not initial fragment, recompute timing and dur
1547 228 : (!start && !has_ts && rpid->bitrate && rpid->pck_dur_at_frame_start)
1548 : //has TS, initial fragment and duration for the entire segment, recompute dur only
1549 78 : || (has_ts && start && !end && pck_dur && pck_dur_for_segment)
1550 : ) {
1551 : u64 frag_time, tot_est_size;
1552 :
1553 : //fragment start, store packed duration
1554 93 : if (has_ts) {
1555 18 : rpid->pck_dur_at_frame_start = (u32) pck_dur;
1556 : }
1557 : //compute estimated file size based on segment duration and rate, use 10% overhead
1558 93 : tot_est_size = rpid->bitrate;
1559 93 : tot_est_size *= rpid->pck_dur_at_frame_start;
1560 93 : tot_est_size /= rpid->timescale;
1561 93 : tot_est_size /= 8;
1562 :
1563 : //our estimate was too small...
1564 93 : if (tot_est_size < rpid->cumulated_frag_size)
1565 : tot_est_size = rpid->cumulated_frag_size;
1566 :
1567 : //compute timing proportional to packet duration, with ratio of current size / tot_est_size
1568 93 : pck_dur = rpid->pck_size * rpid->pck_dur_at_frame_start / tot_est_size;
1569 93 : if (!pck_dur) pck_dur = 1;
1570 :
1571 93 : if (!has_ts) {
1572 75 : frag_time = (rpid->cumulated_frag_size-rpid->pck_size) * rpid->pck_dur_at_frame_start / tot_est_size;
1573 75 : ts = rpid->cts_at_frame_start + frag_time;
1574 : } else {
1575 : frag_time = 0;
1576 : }
1577 93 : GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("[ROUTE] Missing timing for fragment %d of segment %s - timing estimated from bitrate: TS "LLU" ("LLU" in segment) dur "LLU"\n", rpid->frag_idx, rpid->seg_name, ts, frag_time, pck_dur));
1578 : }
1579 :
1580 153 : if (ts!=GF_FILTER_NO_TS) {
1581 : u64 diff;
1582 153 : if (!rpid->clock_at_first_pck) {
1583 9 : rpid->clock_at_first_pck = ctx->clock;
1584 9 : rpid->cts_first_pck = ts;
1585 : }
1586 : //move to microsecs
1587 153 : diff = ts - rpid->cts_first_pck;
1588 153 : diff *= 1000000;
1589 153 : diff /= rpid->timescale;
1590 :
1591 153 : rpid->current_cts_us = rpid->clock_at_first_pck + diff;
1592 153 : rpid->clock_at_pck = ctx->clock;
1593 :
1594 153 : if (start) {
1595 42 : rpid->clock_at_frame_start = ctx->clock;
1596 42 : rpid->cts_us_at_frame_start = rpid->current_cts_us;
1597 42 : rpid->cts_at_frame_start = ts;
1598 : }
1599 :
1600 153 : rpid->current_dur_us = pck_dur;
1601 153 : if (!rpid->current_dur_us) {
1602 0 : rpid->current_dur_us = rpid->timescale;
1603 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Missing duration on segment %s, something is wrong in demux chain, will not be able to regulate correctly\n", rpid->seg_name));
1604 : }
1605 153 : rpid->current_dur_us *= 1000000;
1606 153 : rpid->current_dur_us /= rpid->timescale;
1607 0 : } else if (start && end) {
1608 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Missing timing on segment %s, using previous fragment timing CTS "LLU" duration "LLU" us\nSomething could be wrong in demux chain, will not be able to regulate correctly\n", rpid->seg_name, rpid->current_cts_us-rpid->clock_at_first_pck, rpid->current_dur_us));
1609 : } else {
1610 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("[ROUTE] Missing timing on fragment %d of segment %s, using previous fragment timing CTS "LLU" duration "LLU" us\nSomething could be wrong in demux chain, will not be able to regulate correctly\n", rpid->frag_idx, rpid->seg_name, rpid->current_cts_us-rpid->clock_at_first_pck, rpid->current_dur_us));
1611 : }
1612 :
1613 :
1614 153 : rpid->res_size += rpid->pck_size;
1615 : }
1616 :
1617 :
1618 6266968 : static GF_Err routeout_process_service(GF_ROUTEOutCtx *ctx, ROUTEService *serv)
1619 : {
1620 : u32 i, count, nb_done;
1621 : GF_Err e;
1622 :
1623 6266968 : e = routeout_check_service_updates(ctx, serv);
1624 :
1625 6266968 : if (serv->stsid_bundle) {
1626 6266956 : u64 diff = ctx->clock - serv->last_stsid_clock;
1627 6266956 : if (diff >= ctx->carousel) {
1628 47 : routeout_service_send_bundle(ctx, serv);
1629 47 : serv->last_stsid_clock = ctx->clock;
1630 : } else {
1631 6266909 : u64 next_sched = ctx->carousel - diff;
1632 6266909 : if (next_sched < ctx->reschedule_us)
1633 262616 : ctx->reschedule_us = next_sched;
1634 : }
1635 : } else {
1636 : //not ready
1637 : return e;
1638 : }
1639 :
1640 : nb_done = 0;
1641 6266956 : count = gf_list_count(serv->pids);
1642 22373802 : for (i=0; i<count; i++) {
1643 16106846 : ROUTEPid *rpid = gf_list_get(serv->pids, i);
1644 16106846 : Bool send_hls_child = rpid->update_hls_child_pl;
1645 16106846 : if (rpid->manifest_type) {
1646 6266956 : nb_done++;
1647 6266956 : continue;
1648 : }
1649 :
1650 9839890 : if (ctx->reporting_on) {
1651 0 : if (rpid->full_frame_size) {
1652 0 : ctx->total_size += rpid->full_frame_size;
1653 : } else {
1654 0 : ctx->total_size += rpid->pck_size;
1655 0 : ctx->total_size_unknown = GF_TRUE;
1656 : }
1657 0 : ctx->total_bytes += rpid->pck_offset;
1658 0 : ctx->nb_resources++;
1659 : }
1660 :
1661 9839890 : next_packet:
1662 :
1663 9840039 : if (!rpid->current_pck) {
1664 : u32 offset;
1665 1095 : routeout_fetch_packet(ctx, rpid);
1666 1095 : if (!rpid->current_pck) {
1667 942 : if (gf_filter_pid_is_eos(rpid->pid))
1668 5 : nb_done++;
1669 942 : continue;
1670 : }
1671 :
1672 153 : if (ctx->reporting_on) {
1673 0 : if (rpid->full_frame_size) {
1674 0 : ctx->total_size += rpid->full_frame_size;
1675 : } else {
1676 0 : ctx->total_size += rpid->pck_size;
1677 0 : ctx->total_size_unknown = GF_TRUE;
1678 : }
1679 0 : ctx->nb_resources++;
1680 : }
1681 :
1682 153 : if (rpid->push_init) {
1683 42 : rpid->push_init = GF_FALSE;
1684 : send_hls_child = GF_TRUE;
1685 42 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Sending init segment %s\n", rpid->init_seg_name));
1686 :
1687 : //send init asap
1688 : offset = 0;
1689 84 : while (offset < rpid->init_seg_size) {
1690 : //we use codepoint 5 (new IS) or 7 (repeated IS)
1691 : u32 codepoint;
1692 42 : if (rpid->init_seg_sent) {
1693 : codepoint = 7;
1694 : } else {
1695 42 : rpid->init_seg_sent = GF_FALSE;
1696 : codepoint = 5;
1697 : }
1698 42 : offset += routeout_lct_send(ctx, rpid->rlct->sock, rpid->tsi, ROUTE_INIT_TOI, codepoint, (u8 *) rpid->init_seg_data, rpid->init_seg_size, offset, serv->service_id, rpid->init_seg_size, offset);
1699 : }
1700 42 : if (ctx->reporting_on) {
1701 0 : ctx->total_size += rpid->init_seg_size;
1702 0 : ctx->total_bytes = rpid->init_seg_size;
1703 0 : ctx->nb_resources++;
1704 : }
1705 : }
1706 : }
1707 : //send child m3u8 asap
1708 9839055 : if (send_hls_child && rpid->hld_child_pl) {
1709 0 : u32 hls_len = (u32) strlen(rpid->hld_child_pl);
1710 : u32 offset = 0;
1711 0 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Sending HLS sub playlist %s: \n%s\n", rpid->hld_child_pl_name, rpid->hld_child_pl));
1712 :
1713 0 : while (offset < hls_len) {
1714 : //we use codepoint 1 (NRT - file mode) for subplaylists
1715 0 : offset += routeout_lct_send(ctx, rpid->rlct->sock, rpid->tsi, ROUTE_INIT_TOI-1, 1, (u8 *) rpid->hld_child_pl, hls_len, offset, serv->service_id, hls_len, offset);
1716 : }
1717 0 : if (ctx->reporting_on) {
1718 0 : ctx->total_size += hls_len;
1719 0 : ctx->total_bytes = hls_len;
1720 0 : ctx->nb_resources++;
1721 : }
1722 0 : rpid->update_hls_child_pl = GF_FALSE;
1723 : }
1724 :
1725 9843342 : while ((rpid->pck_offset < rpid->pck_size) || rpid->force_tol_send) {
1726 : u32 sent, codepoint;
1727 : //we have timing info, let's regulate
1728 9843193 : if (rpid->current_cts_us!=GF_FILTER_NO_TS) {
1729 : u64 cur_time_us;
1730 9843193 : ctx->clock = gf_sys_clock_high_res();
1731 : //carousel, not yet ready
1732 9843193 : if (ctx->clock < rpid->clock_at_frame_start)
1733 : break;
1734 :
1735 : //send delay proportionnal to send progress - ultimately we should follow the frame timing, not the segment timing
1736 : //but for the time being we only push complete segments (no LL)
1737 : //it may happen that pck_size is 0 (last_chunk=0) when we force sending a TOL
1738 9843193 : if (!ctx->noreg && rpid->pck_size) {
1739 9843193 : cur_time_us = rpid->pck_offset;
1740 9843193 : cur_time_us *= rpid->current_dur_us;
1741 9843193 : cur_time_us /= rpid->pck_size;
1742 9843193 : cur_time_us += rpid->current_cts_us;
1743 :
1744 9843193 : if (cur_time_us > ctx->clock ) {
1745 9838948 : u64 next_sched = (cur_time_us - ctx->clock) / 2;
1746 9838948 : if (next_sched < ctx->reschedule_us)
1747 7667670 : ctx->reschedule_us = next_sched;
1748 : break;
1749 : }
1750 : }
1751 : }
1752 : //we use codepoint 8 (media segment, file mode) for media segments, otherwise as listed in S-TSID
1753 4245 : codepoint = rpid->raw_file ? rpid->fmtp : 8;
1754 4245 : sent = routeout_lct_send(ctx, rpid->rlct->sock, rpid->tsi, rpid->current_toi, codepoint, (u8 *) rpid->pck_data, rpid->pck_size, rpid->pck_offset, serv->service_id, rpid->full_frame_size, rpid->pck_offset + rpid->frag_offset);
1755 4245 : rpid->pck_offset += sent;
1756 4245 : if (ctx->reporting_on) {
1757 0 : ctx->total_bytes += sent;
1758 : }
1759 4245 : rpid->force_tol_send = GF_FALSE;
1760 : }
1761 : assert (rpid->pck_offset <= rpid->pck_size);
1762 :
1763 9839097 : if (rpid->pck_offset == rpid->pck_size) {
1764 : //print fragment push info except if single fragment
1765 149 : if (rpid->frag_idx || !rpid->full_frame_size) {
1766 129 : GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("[ROUTE] pushed fragment %s#%d (%d bytes) in "LLU" us - target push "LLU" us\n", rpid->seg_name, rpid->frag_idx+1, rpid->pck_size, ctx->clock - rpid->clock_at_pck, rpid->current_dur_us));
1767 : }
1768 : #ifndef GPAC_DISABLE_LOG
1769 : //print full object push info
1770 149 : if (rpid->full_frame_size && (gf_log_get_tool_level(GF_LOG_ROUTE)>=GF_LOG_INFO)) {
1771 : char szFInfo[1000], szSID[31];
1772 : u64 seg_clock, target_push_dur;
1773 :
1774 18 : if (rpid->pck_dur_at_frame_start) {
1775 14 : target_push_dur = rpid->pck_dur_at_frame_start;
1776 14 : target_push_dur *= 1000000;
1777 14 : target_push_dur /= rpid->timescale;
1778 : } else {
1779 4 : target_push_dur = rpid->current_dur_us + rpid->current_cts_us - rpid->cts_us_at_frame_start;
1780 : }
1781 18 : if (ctx->sock_atsc_lls) {
1782 0 : snprintf(szSID, 20, "Service%d ", serv->service_id);
1783 0 : szSID[30] = 0;
1784 : }
1785 : else
1786 18 : szSID[0] = 0;
1787 :
1788 18 : if (rpid->frag_idx)
1789 18 : snprintf(szFInfo, 100, "%s%s (%d frags %d bytes)", szSID, rpid->seg_name, rpid->frag_idx+1, rpid->full_frame_size);
1790 : else
1791 0 : snprintf(szFInfo, 100, "%s%s (%d bytes)", szSID, rpid->seg_name, rpid->full_frame_size);
1792 :
1793 18 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Pushed %s in "LLU" us - target push "LLU" us\n", szFInfo, ctx->clock - rpid->clock_at_frame_start, target_push_dur));
1794 :
1795 : //real-time stream, check we are not out of sync
1796 18 : if (!rpid->raw_file) {
1797 : //clock time is the clock at first pck of first seg + cts_diff(cur_seg, first_seg)
1798 18 : seg_clock = rpid->cts_us_at_frame_start + target_push_dur;
1799 :
1800 : //if segment clock time is greater than clock, we've been pushing too fast
1801 18 : if (seg_clock > ctx->clock) {
1802 17 : GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("[ROUTE] Segment %s pushed early by "LLU" us\n", rpid->seg_name, seg_clock - ctx->clock));
1803 : }
1804 : //otherwise we've been pushing too slowly
1805 1 : else if (ctx->clock > 1000 + seg_clock) {
1806 1 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Segment %s pushed too late by "LLU" us\n", rpid->seg_name, ctx->clock - seg_clock));
1807 : }
1808 :
1809 18 : if (rpid->pck_dur_at_frame_start && ctx->llmode) {
1810 14 : u64 seg_rate = rpid->full_frame_size * 8;
1811 14 : seg_rate *= rpid->timescale;
1812 14 : seg_rate /= rpid->pck_dur_at_frame_start;
1813 :
1814 14 : if (seg_rate > rpid->bitrate) {
1815 3 : GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("[ROUTE] Segment %s rate "LLU" but stream rate "LLU", updating bitrate\n", rpid->seg_name, seg_rate, rpid->bitrate));
1816 3 : rpid->bitrate = (u32) seg_rate;
1817 : }
1818 : }
1819 : }
1820 : }
1821 : #endif
1822 : //raw file, keep on sending data if carousel period is set and no new packet
1823 149 : if (rpid->raw_file && rpid->carousel_time_us) {
1824 0 : GF_FilterPacket *pck_next = gf_filter_pid_get_packet(rpid->pid);
1825 0 : if (!pck_next) {
1826 0 : rpid->pck_offset = 0;
1827 0 : rpid->current_cts_us += rpid->carousel_time_us;
1828 0 : rpid->clock_at_pck = rpid->current_cts_us;
1829 0 : rpid->clock_at_frame_start += rpid->carousel_time_us;
1830 0 : rpid->cts_us_at_frame_start = rpid->current_cts_us;
1831 : //exit sending for this pid
1832 0 : continue;
1833 : }
1834 : }
1835 149 : gf_filter_pck_unref(rpid->current_pck);
1836 149 : rpid->current_pck = NULL;
1837 149 : rpid->frag_offset = rpid->cumulated_frag_size;
1838 149 : goto next_packet;
1839 : }
1840 : }
1841 6266956 : serv->is_done = (nb_done==count) ? GF_TRUE : GF_FALSE;
1842 6266956 : return GF_OK;
1843 : }
1844 :
1845 : #define GF_TAI_UTC_OFFSET 37
1846 1353816 : static void routeout_send_lls(GF_ROUTEOutCtx *ctx)
1847 : {
1848 1353816 : char *payload_text = NULL;
1849 : u8 *payload = NULL, *pay_start;
1850 : char tmp[2000];
1851 : u32 i, count, len, comp_size;
1852 : s32 timezone, h, m;
1853 1353816 : u64 diff = ctx->clock - ctx->last_lls_clock;
1854 1353816 : if (diff < ctx->carousel) {
1855 1353804 : u64 next_sched = ctx->carousel - diff;
1856 1353804 : if (next_sched < ctx->reschedule_us)
1857 76636 : ctx->reschedule_us = next_sched;
1858 1353804 : return;
1859 : }
1860 12 : ctx->last_lls_clock = ctx->clock;
1861 :
1862 12 : if (!ctx->bytes_sent) ctx->clock_stats = ctx->clock;
1863 :
1864 : //we send 2 LLS tables, SysTime and SLT
1865 :
1866 : //SysTime
1867 12 : if (!ctx->lls_time_table) {
1868 2 : gf_dynstrcat(&payload_text, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SystemTime currentUtcOffset=\"", NULL);
1869 : sprintf(tmp, "%d", GF_TAI_UTC_OFFSET);
1870 2 : gf_dynstrcat(&payload_text, tmp, NULL);
1871 2 : gf_dynstrcat(&payload_text, "\" utcLocalOffset=\"", NULL);
1872 2 : timezone = -gf_net_get_timezone() / 60;
1873 2 : h = timezone / 60;
1874 2 : m = timezone - h*60;
1875 2 : if (m)
1876 0 : sprintf(tmp, "%sPT%dH%dM", (h<0) ? "-" : "", ABS(h), m);
1877 : else
1878 2 : sprintf(tmp, "%sPT%dH", (h<0) ? "-" : "", ABS(h));
1879 2 : gf_dynstrcat(&payload_text, tmp, NULL);
1880 2 : gf_dynstrcat(&payload_text, "\" dsStatus=\"", NULL);
1881 2 : gf_dynstrcat(&payload_text, gf_net_time_is_dst() ? "true" : "false", NULL);
1882 2 : gf_dynstrcat(&payload_text, "\"/>\n", NULL);
1883 :
1884 2 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Updating ATSC3 LLS.SysTime:\n%s\n", payload_text));
1885 2 : len = (u32) strlen(payload_text);
1886 2 : comp_size = 2*len;
1887 2 : payload = gf_malloc(sizeof(char)*(comp_size+4));
1888 2 : pay_start = payload + 4;
1889 2 : gf_gz_compress_payload_ex((u8 **) &payload_text, len, &comp_size, 0, GF_FALSE, &pay_start);
1890 2 : gf_free(payload_text);
1891 2 : payload_text = NULL;
1892 :
1893 2 : comp_size += 4;
1894 : //format header
1895 2 : payload[0] = 3; //tableID
1896 2 : payload[1] = 0; //groupid
1897 2 : payload[2] = 0; //lls_group_count_minus_one
1898 2 : payload[3] = 1; //lls_table_version
1899 :
1900 2 : ctx->lls_time_table = payload;
1901 2 : ctx->lls_time_table_len = comp_size;
1902 : }
1903 :
1904 12 : GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("[ROUTE] Sending ATSC3 LLS.SysTime\n"));
1905 12 : gf_sk_send(ctx->sock_atsc_lls, ctx->lls_time_table, ctx->lls_time_table_len);
1906 12 : ctx->bytes_sent += ctx->lls_time_table_len;
1907 :
1908 : //SLT
1909 12 : if (!ctx->lls_slt_table) {
1910 2 : count = gf_list_count(ctx->services);
1911 2 : snprintf(tmp, 1000, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SLT bsid=\"%d\">\n", ctx->bsid);
1912 2 : gf_dynstrcat(&payload_text, tmp, NULL);
1913 4 : for (i=0; i<count; i++) {
1914 : const GF_PropertyValue *p;
1915 : const char *src_ip, *service_name;
1916 : char szIP[GF_MAX_IP_NAME_LEN];
1917 : ROUTEPid *rpid;
1918 2 : ROUTEService *serv = gf_list_get(ctx->services, i);
1919 2 : u32 sid = serv->service_id;
1920 2 : if (!sid) sid = 1;
1921 :
1922 2 : rpid = gf_list_get(serv->pids, 0);
1923 2 : p = gf_filter_pid_get_property_str(rpid->pid, "ShortServiceName");
1924 2 : if (!p)
1925 2 : p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_SERVICE_NAME);
1926 2 : service_name = (p && p->value.string) ? p->value.string : "GPAC";
1927 2 : len = (u32) strlen(service_name);
1928 2 : if (len>7) len = 7;
1929 2 : strncpy(szIP, service_name, len);
1930 2 : szIP[len] = 0;
1931 :
1932 2 : snprintf(tmp, 2000,
1933 : " <Service serviceId=\"%d\" sltSvcSeqNum=\"0 \" serviceCategory=\"1\" globalServiceId=\"urn:gpac:atsc:serviceid:%d.%d\" majorChannelNo=\"666\" minorChannelNo=\"666\" shortServiceName=\"%s\">\n", sid, ctx->bsid, sid, szIP);
1934 2 : gf_dynstrcat(&payload_text, tmp, NULL);
1935 :
1936 2 : src_ip = ctx->ifce;
1937 2 : if (!src_ip) {
1938 2 : gf_sk_get_local_ip(serv->rlct_base->sock, szIP);
1939 : src_ip = szIP;
1940 : }
1941 2 : int res = snprintf(tmp, 1000, " <BroadcastSvcSignaling slsProtocol=\"1\" slsDestinationIpAddress=\"%s\" slsDestinationUdpPort=\"%d\" slsSourceIpAddress=\"%s\"/>\n"
1942 2 : " </Service>\n", serv->rlct_base->ip, serv->rlct_base->port, src_ip);
1943 2 : if (res<0) {
1944 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("[ROUTE] String truncated will trying to write: <BroadcastSvcSignaling slsProtocol=\"1\" slsDestinationIpAddress=\"%s\" slsDestinationUdpPort=\"%d\" slsSourceIpAddress=\"%s\"/>\n", serv->rlct_base->ip, serv->rlct_base->port, src_ip));
1945 : }
1946 2 : gf_dynstrcat(&payload_text, tmp, NULL);
1947 : }
1948 2 : gf_dynstrcat(&payload_text, "</SLT>\n", NULL);
1949 :
1950 2 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Updating ATSC3 LLS.SLT:\n%s\n", payload_text));
1951 :
1952 2 : len = (u32) strlen(payload_text);
1953 2 : comp_size = 2*len;
1954 2 : payload = gf_malloc(sizeof(char)*(comp_size+4));
1955 2 : pay_start = payload + 4;
1956 2 : gf_gz_compress_payload_ex((u8 **) &payload_text, len, &comp_size, 0, GF_FALSE, &pay_start);
1957 2 : gf_free(payload_text);
1958 2 : payload_text = NULL;
1959 :
1960 2 : comp_size += 4;
1961 : //format header
1962 2 : payload[0] = 1; //tableID
1963 2 : payload[1] = 0; //groupid
1964 2 : payload[2] = 0; //lls_group_count_minus_one
1965 2 : payload[3] = 1; //lls_table_version
1966 :
1967 2 : ctx->lls_slt_table = payload;
1968 2 : ctx->lls_slt_table_len = comp_size;
1969 : }
1970 :
1971 12 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Sending ATSC3 LLS SysTime and SLT\n"));
1972 12 : gf_sk_send(ctx->sock_atsc_lls, ctx->lls_slt_table, ctx->lls_slt_table_len);
1973 12 : ctx->bytes_sent += ctx->lls_slt_table_len;
1974 : }
1975 :
1976 6267146 : static GF_Err routeout_process(GF_Filter *filter)
1977 : {
1978 : GF_Err e = GF_OK;
1979 : u32 i, count;
1980 : Bool all_serv_done = GF_TRUE;
1981 6267146 : GF_ROUTEOutCtx *ctx = (GF_ROUTEOutCtx *) gf_filter_get_udta(filter);
1982 :
1983 6267146 : ctx->clock = gf_sys_clock_high_res();
1984 6267146 : ctx->reschedule_us = MIN(ctx->carousel, 50000);
1985 6267146 : ctx->reporting_on = gf_filter_reporting_enabled(filter);
1986 6267146 : if (ctx->reporting_on) {
1987 0 : ctx->total_size_unknown = GF_FALSE;
1988 0 : ctx->total_size = 0;
1989 0 : ctx->total_bytes = GF_FALSE;
1990 0 : ctx->nb_resources = 0;
1991 : }
1992 :
1993 6267146 : if (ctx->runfor) {
1994 3573042 : if (ctx->clock - ctx->clock_init > ctx->runfor*1000) {
1995 99 : if (!ctx->done) {
1996 2 : ctx->done = GF_TRUE;
1997 2 : count = gf_filter_get_ipid_count(filter);
1998 8 : for (i=0; i<count; i++) {
1999 : GF_FilterEvent evt;
2000 6 : GF_FilterPid *pid = gf_filter_get_ipid(filter, i);
2001 6 : GF_FEVT_INIT(evt, GF_FEVT_STOP, pid);
2002 6 : gf_filter_pid_send_event(pid, &evt);
2003 6 : gf_filter_pid_set_discard(pid, GF_TRUE);
2004 : }
2005 : }
2006 : return GF_EOS;
2007 : }
2008 : }
2009 :
2010 6267047 : if (ctx->sock_atsc_lls)
2011 1353816 : routeout_send_lls(ctx);
2012 :
2013 6267047 : count = gf_list_count(ctx->services);
2014 12534094 : for (i=0; i<count; i++) {
2015 6267047 : ROUTEService *serv = gf_list_get(ctx->services, i);
2016 6267047 : if (!serv->is_done) {
2017 6266968 : e |= routeout_process_service(ctx, serv);
2018 6266968 : if (!serv->is_done)
2019 : all_serv_done = GF_FALSE;
2020 : }
2021 : }
2022 :
2023 6267047 : if (all_serv_done) {
2024 84 : return e ? e : GF_EOS;
2025 : }
2026 :
2027 6266963 : if (ctx->clock - ctx->clock_stats >= 1000000) {
2028 42 : u64 rate = ctx->bytes_sent * 8 * 1000 / (ctx->clock - ctx->clock_stats);
2029 42 : GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("[ROUTE] Mux rate "LLU" kbps\n", rate));
2030 42 : if (ctx->reporting_on) {
2031 : u32 progress = 0;
2032 : char szStatus[200];
2033 0 : if (!ctx->total_size_unknown && ctx->total_bytes)
2034 0 : progress = (u32) (10000*ctx->total_bytes / ctx->total_size);
2035 :
2036 0 : if (ctx->sock_atsc_lls) {
2037 0 : snprintf(szStatus, 200, "Mux rate "LLU" kbps - %d services - %d active resources %.02f %% done", rate, count, ctx->nb_resources, ((Double)progress) / 100);
2038 : } else {
2039 0 : snprintf(szStatus, 200, "Mux rate "LLU" kbps - %d active resources %.02f %% done", rate, ctx->nb_resources, ((Double)progress) / 100);
2040 : }
2041 0 : gf_filter_update_status(filter, 0, szStatus);
2042 : }
2043 42 : ctx->bytes_sent = 0;
2044 42 : ctx->clock_stats = ctx->clock;
2045 : }
2046 6266963 : ctx->reschedule_us++;
2047 6266963 : gf_filter_ask_rt_reschedule(filter, (u32) ctx->reschedule_us);
2048 6266963 : return e;
2049 : }
2050 :
2051 2198 : static GF_FilterProbeScore routeout_probe_url(const char *url, const char *mime)
2052 : {
2053 2198 : if (!strnicmp(url, "atsc://", 7)) return GF_FPROBE_SUPPORTED;
2054 2194 : if (!strnicmp(url, "route://", 8)) return GF_FPROBE_SUPPORTED;
2055 2187 : return GF_FPROBE_NOT_SUPPORTED;
2056 : }
2057 4 : static Bool routeout_use_alias(GF_Filter *filter, const char *url, const char *mime)
2058 : {
2059 : const char *sep;
2060 : u32 len;
2061 4 : GF_ROUTEOutCtx *ctx = (GF_ROUTEOutCtx *) gf_filter_get_udta(filter);
2062 :
2063 : //atsc, do not analyze IP and port
2064 4 : if (ctx->sock_atsc_lls) {
2065 2 : if (!strncmp(url, "atsc://", 7)) return GF_TRUE;
2066 0 : return GF_FALSE;
2067 : }
2068 :
2069 : //check we have same hostname. If so, accept this destination as a source for our filter
2070 : //- if atsc://, single instance of the filter
2071 : //- if route://IP:PORT, same instance if same IP:PORT
2072 2 : sep = strstr(url, "://");
2073 2 : if (!sep) return GF_FALSE;
2074 2 : sep += 3;
2075 2 : sep = strchr(sep, '/');
2076 2 : if (!sep) {
2077 0 : if (!strcmp(ctx->dst, url)) return GF_TRUE;
2078 0 : return GF_FALSE;
2079 : }
2080 2 : len = (u32) (sep - url);
2081 2 : if (!strncmp(ctx->dst, url, len)) return GF_TRUE;
2082 0 : return GF_FALSE;
2083 : }
2084 :
2085 :
2086 : #define OFFS(_n) #_n, offsetof(GF_ROUTEOutCtx, _n)
2087 :
2088 : static const GF_FilterArgs ROUTEOutArgs[] =
2089 : {
2090 : { OFFS(dst), "destination URL - see filter help", GF_PROP_NAME, NULL, NULL, 0},
2091 : { OFFS(ext), "set extension for graph resolution, regardless of file extension", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_EXPERT},
2092 : { OFFS(mime), "set mime type for graph resolution", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_EXPERT},
2093 : { OFFS(ifce), "default interface to use for multicast. If NULL, the default system interface will be used", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
2094 : { OFFS(carousel), "carousel period in ms for repeating signaling and raw file data - see filter help", GF_PROP_UINT, "1000", NULL, GF_FS_ARG_HINT_EXPERT},
2095 : { OFFS(first_port), "port number of first ROUTE session in ATSC mode", GF_PROP_UINT, "6000", NULL, GF_FS_ARG_HINT_EXPERT},
2096 : { OFFS(ip), "mulicast IP address for ROUTE session in ATSC mode", GF_PROP_STRING, "225.1.1.0", NULL, GF_FS_ARG_HINT_EXPERT},
2097 : { OFFS(ttl), "time-to-live for multicast packets", GF_PROP_UINT, "0", NULL, 0},
2098 : { OFFS(bsid), "ID for ATSC broadcast stream", GF_PROP_UINT, "800", NULL, GF_FS_ARG_HINT_EXPERT},
2099 : { OFFS(mtu), "size of LCT MTU in bytes", GF_PROP_UINT, "1472", NULL, 0},
2100 : { OFFS(splitlct), "split mode for LCT channels\n"
2101 : "- off: all streams are in the same LCT channel\n"
2102 : "- type: each new stream type results in a new LCT channel\n"
2103 : "- all: all streams are in dedicated LCT channel, the first stream being used for STSID signaling"
2104 : , GF_PROP_UINT, "off", "off|type|all", 0},
2105 : { OFFS(korean), "use Korean version of ATSC 3.0 spec instead of US", GF_PROP_BOOL, "false", NULL, 0},
2106 : { OFFS(llmode), "use low-latency mode", GF_PROP_BOOL, "false", NULL, GF_ARG_HINT_EXPERT},
2107 : { OFFS(brinc), "bitrate increase in percent when estimating timing in low latency mode - see filter help", GF_PROP_UINT, "10", NULL, GF_ARG_HINT_EXPERT},
2108 : { OFFS(noreg), "disable rate regulation for media segments, pushing them as fast as received", GF_PROP_BOOL, "false", NULL, GF_ARG_HINT_EXPERT},
2109 :
2110 : { OFFS(runfor), "run for the given time in ms", GF_PROP_UINT, "0", NULL, 0},
2111 : {0}
2112 : };
2113 :
2114 : static const GF_FilterCapability ROUTEOutCaps[] =
2115 : {
2116 : CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
2117 : CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"),
2118 : };
2119 :
2120 :
2121 : GF_FilterRegister ROUTEOutRegister = {
2122 : .name = "routeout",
2123 : GF_FS_SET_DESCRIPTION("ROUTE output")
2124 : GF_FS_SET_HELP("The ROUTE output filter is used to distribute a live file-based session using ROUTE.\n"
2125 : "The filter supports DASH and HLS inputs, ATSC3.0 signaling and generic ROUTE signaling.\n"
2126 : "\n"
2127 : "The filter is identified using the following URL schemes:\n"
2128 : "- `atsc://`: session is a full ATSC 3.0 session\n"
2129 : "- `route://IP:port`: session is a ROUTE session running on given multicast IP and port\n"
2130 : "\n"
2131 : "The filter only accepts input PIDs of type `FILE`.\n"
2132 : "- HAS Manifests files are detected by file extension and/or MIME types, and sent as part of the signaling bundle or as LCT object files for HLS subplaylists.\n"
2133 : "- HAS Media segments are detected using the `OrigStreamType` property, and send as LCT object files using the DASH template string.\n"
2134 : "- A PID without `OrigStreamType` property set is delivered as a regular LCT object file (called `raw` hereafter).\n"
2135 : " \n"
2136 : "For `raw` file PIDs, the filter will look for the following properties:\n"
2137 : "- `ROUTEName`: set resource name. If not found, uses basename of URL\n"
2138 : "- `ROUTECarousel`: set repeat period. If not found, uses [-carousel](). If 0, the file is only sent once\n"
2139 : "- `ROUTEUpload`: set resource upload time. If not found, uses [-carousel](). If 0, the file will be sent as fast as possible.\n"
2140 : "\n"
2141 : "When DASHing for ROUTE or single service ATSC, a file extension, either in [-dst]() or in [-ext](), may be used to identify the HAS session type (DASH or HLS).\n"
2142 : "EX \"route://IP:PORT/manifest.mpd\", \"route://IP:PORT/:ext=mpd\"\n"
2143 : "\n"
2144 : "When DASHing for multi-service ATSC, forcing an extension will force all service to use the same formats.\n"
2145 : "EX \"atsc://:ext=mpd\", \"route://IP:PORT/manifest.mpd\"\n"
2146 : "If multiple services with different formats are needed, you will need to explicit your filters:\n"
2147 : "EX gpac -i DASH_URL:#ServiceID=1 @ dashin:forward=file:FID=1 -i HLS_URL:#ServiceID=2 @ dashin:forward=file:FID=2 -o atsc://:SID=1,2\n"
2148 : "EX gpac -i MOVIE1:#ServiceID=1 @ dasher:FID=1:mname=manifest.mpd -i MOVIE2:#ServiceID=2 @ dasher:FID=2:mname=manifest.m3u8 -o atsc://:SID=1,2\n"
2149 : "\n"
2150 : "Warning: When forwarding an existing DASH/HLS session, do NOT set any extension or manifest name.\n"
2151 : "\n"
2152 : "By default, all streams in a service are assigned to a single route session, and differentiated by ROUTE TSI (see [-splitlct]()).\n"
2153 : "TSI are assigned as follows:\n"
2154 : "- signaling TSI is always 0\n"
2155 : "- raw files are assigned TSI 1 and increasing number of TOI\n"
2156 : "- otherwise, the first PID found is assigned TSI 10, the second TSI 20 etc ...\n"
2157 : "\n"
2158 : "Init segments and HLS subplaylists are sent before each new segment, independently of [-carousel]().\n"
2159 : "# ATSC 3.0 mode\n"
2160 : "In this mode, the filter allows multiple service multiplexing, identified through the `ServiceID` property.\n"
2161 : "By default, a single multicast IP is used for route sessions, each service will be assigned a different port.\n"
2162 : "The filter will look for `ROUTEIP` and `ROUTEPort` properties on the incoming PID. If not found, the default [-ip]() and [-port]() will be used.\n"
2163 : "\n"
2164 : "The ATSC short service name can be set using PID property `ShortServiceName`. If not found, `ServiceName` is checked, otherwise default to `GPAC`.\n"
2165 : "\n"
2166 : "# ROUTE mode\n"
2167 : "In this mode, only a single service can be distributed by the ROUTE session.\n"
2168 : "Note: [-ip]() is ignored, and [-first_port]() is used if no port is specified in [-dst]().\n"
2169 : "The ROUTE session will include a multi-part MIME unsigned package containing manifest and S-TSID, sent on TSI=0.\n"
2170 : "\n"
2171 : "# Low latency mode\n"
2172 : "When using low-latency mode, the input media segments are not re-assembled in a single packet but are instead sent as they are received.\n"
2173 : "In order for the real-time scheduling of data chunks to work, each fragment of the segment should have a CTS and timestamp describing its timing.\n"
2174 : "If this is not the case (typically when used with an existing DASH session in file mode), the scheduler will estimate CTS and duration based on the stream bitrate and segment duration. The indicated bitrate is increased by [-brinc]() percent for safety.\n"
2175 : "If this fails, the muxer will trigger warnings and send as fast as possible.\n"
2176 : "Note: The LCT objects are sent with no length (TOL header) assigned until the final segment size is known, potentially leading to a final 0-size LCT fragment signaling only the final size.\n"
2177 : "\n"
2178 : "# Examples\n"
2179 : "Since the ROUTE filter only consumes files, it is required to insert:\n"
2180 : "- the dash demuxer in file forwarding mode when loading a DASH session\n"
2181 : "- the dash muxer when creating a DASH session\n"
2182 : "\n"
2183 : "Muxing an existing DASH session in route:\n"
2184 : "EX gpac -i source.mpd dashin:forward=file @ -o route://225.1.1.0:6000/\n"
2185 : "Muxing an existing DASH session in atsc:\n"
2186 : "EX gpac -i source.mpd dashin:forward=file @ -o atsc://\n"
2187 : "Dashing and muxing in route:\n"
2188 : "EX gpac -i source.mp4 dasher:profile=live @ -o route://225.1.1.0:6000/manifest.mpd\n"
2189 : "Dashing and muxing in route Low Latency (experimental):\n"
2190 : "EX gpac -i source.mp4 dasher @ -o route://225.1.1.0:6000/manifest.mpd:profile=live:cdur=0.2:llmode\n"
2191 : "\n"
2192 : "Sending a single file in ROUTE using half a second upload time, 2 seconds carousel:\n"
2193 : "EX gpac -i URL:#ROUTEUpload=0.5:#ROUTECarousel=2 -o route://225.1.1.0:6000/\n"
2194 : "\n"
2195 : "Common mistakes:\n"
2196 : "EX gpac -i source.mpd -o route://225.1.1.0:6000/\n"
2197 : "This will only send the manifest file as a regular object and will not load the dash session.\n"
2198 : "EX gpac -i source.mpd dasher @ -o route://225.1.1.0:6000/\n"
2199 : "EX gpac -i source.mpd dasher @ -o route://225.1.1.0:6000/manifest.mpd\n"
2200 : "These will load the dash session, instantiate a new dasher filter (hence a new DASH manifest), sending the output of the dasher to ROUTE\n"
2201 : "EX gpac -i source.mpd dashin:forward=file @ -o route://225.1.1.0:6000/manifest.mpd\n"
2202 : "This will force the ROUTE muxer to only accept .mpd files, and will drop all segment files (same if [-ext]() is used).\n"
2203 : )
2204 : .private_size = sizeof(GF_ROUTEOutCtx),
2205 : .max_extra_pids = -1,
2206 : .args = ROUTEOutArgs,
2207 : SETCAPS(ROUTEOutCaps),
2208 : .probe_url = routeout_probe_url,
2209 : .initialize = routeout_initialize,
2210 : .finalize = routeout_finalize,
2211 : .configure_pid = routeout_configure_pid,
2212 : .process = routeout_process,
2213 : .use_alias = routeout_use_alias
2214 : };
2215 :
2216 :
2217 2877 : const GF_FilterRegister *routeout_register(GF_FilterSession *session)
2218 : {
2219 2877 : return &ROUTEOutRegister;
2220 : }
|