LCOV - code coverage report
Current view: top level - filters - out_route.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 824 1098 75.0 %
Date: 2021-04-29 23:48:07 Functions: 18 18 100.0 %

          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             : }

Generated by: LCOV version 1.13