Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2019
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / generic socket 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/network.h>
30 :
31 : typedef struct
32 : {
33 : GF_Socket *socket;
34 : Bool is_tuned;
35 : char address[GF_MAX_IP_NAME_LEN];
36 : Bool pck_pending;
37 : } GF_SockOutClient;
38 :
39 : typedef struct
40 : {
41 : //options
42 : Double start, speed;
43 : char *dst, *mime, *ext, *ifce;
44 : Bool listen;
45 : u32 maxc, port, sockbuf, ka, kp, rate, ttl;
46 : GF_Fraction pckr, pckd;
47 :
48 : GF_Socket *socket;
49 : //only one output pid
50 : GF_FilterPid *pid;
51 :
52 : GF_List *clients;
53 :
54 : Bool pid_started;
55 : Bool had_clients;
56 : Bool pck_pending;
57 :
58 : GF_FilterCapability in_caps[2];
59 : char szExt[10];
60 : char szFileName[GF_MAX_PATH];
61 :
62 : u32 nb_pck_processed;
63 : u64 start_time;
64 : u64 nb_bytes_sent;
65 :
66 : GF_FilterPacket *rev_pck;
67 : u32 next_pckd_idx, next_pckr_idx;
68 : u32 nb_pckd_wnd, nb_pckr_wnd;
69 : } GF_SockOutCtx;
70 :
71 :
72 10 : static GF_Err sockout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
73 : {
74 : const GF_PropertyValue *p;
75 10 : GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter);
76 10 : if (is_remove) {
77 0 : ctx->pid = NULL;
78 0 : gf_sk_del(ctx->socket);
79 0 : ctx->socket = NULL;
80 0 : return GF_OK;
81 : }
82 10 : gf_filter_pid_check_caps(pid);
83 :
84 10 : if (!ctx->pid && (!ctx->listen || gf_list_count(ctx->clients) ) ) {
85 : GF_FilterEvent evt;
86 7 : gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "SockOut");
87 7 : gf_filter_pid_send_event(pid, &evt);
88 7 : ctx->pid_started = GF_TRUE;
89 : }
90 10 : ctx->pid = pid;
91 :
92 10 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE);
93 10 : if (p && p->value.uint) {
94 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Block patching is not supported by socket output\n"));
95 : return GF_NOT_SUPPORTED;
96 : }
97 : return GF_OK;
98 : }
99 :
100 10 : static GF_Err sockout_initialize(GF_Filter *filter)
101 : {
102 : GF_Err e;
103 : char *str, *url;
104 : u16 port;
105 : char *ext;
106 : u32 sock_type = 0;
107 10 : GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter);
108 :
109 10 : if (!ctx || !ctx->dst) return GF_OK;
110 : e = GF_NOT_SUPPORTED;
111 10 : if (!strncmp(ctx->dst, "tcp://", 6)) e = GF_OK;
112 4 : else if (!strncmp(ctx->dst, "udp://", 6)) e = GF_OK;
113 0 : else if (!strncmp(ctx->dst, "tcpu://", 7)) e = GF_OK;
114 0 : else if (!strncmp(ctx->dst, "udpu://", 7)) e = GF_OK;
115 :
116 :
117 : if (e) {
118 0 : gf_filter_setup_failure(filter, GF_NOT_SUPPORTED);
119 0 : return GF_NOT_SUPPORTED;
120 : }
121 10 : if (ctx->ext) ext = ctx->ext;
122 : else {
123 0 : ext = gf_file_ext_start(ctx->dst);
124 0 : if (ext) ext++;
125 0 : if (ext) {
126 0 : const char *szport = strchr(ext, ':');
127 0 : if (szport)
128 0 : ext = gf_file_ext_start(szport);
129 : }
130 : }
131 :
132 10 : if (!ext && !ctx->mime) {
133 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] No extension provided nor mime type for output file %s, cannot infer format\n", ctx->dst));
134 : return GF_NOT_SUPPORTED;
135 : }
136 :
137 10 : if (ctx->listen) {
138 3 : ctx->clients = gf_list_new();
139 3 : if (!ctx->clients) return GF_OUT_OF_MEM;
140 : }
141 :
142 : //static cap, streamtype = file
143 10 : ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE;
144 10 : ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE);
145 10 : ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC;
146 :
147 10 : if (ctx->mime) {
148 0 : ctx->in_caps[1].code = GF_PROP_PID_MIME;
149 0 : ctx->in_caps[1].val = PROP_NAME( ctx->mime );
150 0 : ctx->in_caps[1].flags = GF_CAPS_INPUT;
151 : } else {
152 10 : strncpy(ctx->szExt, ext, 9);
153 10 : ctx->szExt[9] = 0;
154 10 : strlwr(ctx->szExt);
155 10 : ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT;
156 10 : ctx->in_caps[1].val = PROP_NAME( ctx->szExt );
157 10 : ctx->in_caps[1].flags = GF_CAPS_INPUT;
158 : }
159 10 : gf_filter_override_caps(filter, ctx->in_caps, 2);
160 :
161 : /*create our ourput socket*/
162 :
163 10 : if (!strnicmp(ctx->dst, "udp://", 6)) {
164 : sock_type = GF_SOCK_TYPE_UDP;
165 4 : ctx->listen = GF_FALSE;
166 6 : } else if (!strnicmp(ctx->dst, "tcp://", 6)) {
167 : sock_type = GF_SOCK_TYPE_TCP;
168 : #ifdef GPAC_HAS_SOCK_UN
169 0 : } else if (!strnicmp(ctx->dst, "udpu://", 7)) {
170 : sock_type = GF_SOCK_TYPE_UDP_UN;
171 0 : ctx->listen = GF_FALSE;
172 0 : } else if (!strnicmp(ctx->dst, "tcpu://", 7)) {
173 : sock_type = GF_SOCK_TYPE_TCP_UN;
174 : #endif
175 : } else {
176 : return GF_NOT_SUPPORTED;
177 : }
178 :
179 : //skip ://
180 10 : url = strchr(ctx->dst, ':');
181 10 : url += 3;
182 :
183 10 : ctx->socket = gf_sk_new(sock_type);
184 10 : if (! ctx->socket ) {
185 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Failed to open socket for %s\n", ctx->dst));
186 : return GF_IO_ERR;
187 : }
188 :
189 : /*setup port and src*/
190 10 : port = ctx->port;
191 10 : str = strrchr(url, ':');
192 : /*take care of IPv6 address*/
193 10 : if (str && strchr(str, ']')) str = strchr(url, ':');
194 10 : if (str) {
195 20 : port = atoi(str+1);
196 10 : str[0] = 0;
197 : }
198 :
199 10 : if (gf_sk_is_multicast_address(url)) {
200 : //server socket, do not bind
201 1 : e = gf_sk_setup_multicast(ctx->socket, url, port, ctx->ttl, GF_TRUE, ctx->ifce);
202 1 : ctx->listen = GF_FALSE;
203 18 : } else if ((sock_type == GF_SOCK_TYPE_UDP)
204 : #ifdef GPAC_HAS_SOCK_UN
205 9 : || (sock_type == GF_SOCK_TYPE_UDP_UN)
206 : #endif
207 : ) {
208 3 : e = gf_sk_bind(ctx->socket, ctx->ifce, port, url, port, GF_SOCK_REUSE_PORT | GF_SOCK_FAKE_BIND);
209 3 : ctx->listen = GF_FALSE;
210 6 : } else if (ctx->listen) {
211 3 : e = gf_sk_bind(ctx->socket, NULL, port, url, 0, GF_SOCK_REUSE_PORT);
212 3 : if (!e) e = gf_sk_listen(ctx->socket, ctx->maxc);
213 3 : if (!e) {
214 3 : gf_filter_post_process_task(filter);
215 3 : gf_sk_server_mode(ctx->socket, GF_TRUE);
216 : }
217 : } else {
218 3 : e = gf_sk_connect(ctx->socket, url, port, NULL);
219 : }
220 :
221 10 : if (str) str[0] = ':';
222 :
223 10 : if (e) {
224 0 : gf_sk_del(ctx->socket);
225 0 : ctx->socket = NULL;
226 0 : return e;
227 : }
228 :
229 10 : gf_sk_set_buffer_size(ctx->socket, 0, ctx->sockbuf);
230 :
231 10 : return GF_OK;
232 : }
233 :
234 10 : static void sockout_finalize(GF_Filter *filter)
235 : {
236 10 : GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter);
237 10 : if (ctx->clients) {
238 6 : while (gf_list_count(ctx->clients)) {
239 3 : GF_SockOutClient *sc = gf_list_pop_back(ctx->clients);
240 3 : if (sc->socket) gf_sk_del(sc->socket);
241 3 : gf_free(sc);
242 : }
243 3 : gf_list_del(ctx->clients);
244 : }
245 :
246 10 : if (ctx->socket) gf_sk_del(ctx->socket);
247 10 : }
248 :
249 1175 : static GF_Err sockout_send_packet(GF_SockOutCtx *ctx, GF_FilterPacket *pck, GF_Socket *dst_sock)
250 : {
251 : GF_Err e;
252 : const GF_PropertyValue *p;
253 : u32 w, h, stride, stride_uv, pf;
254 : u32 nb_planes, uv_height;
255 : const char *pck_data;
256 : u32 pck_size;
257 : GF_FilterFrameInterface *hwf=NULL;
258 1175 : if (!dst_sock) return GF_OK;
259 :
260 1175 : pck_data = gf_filter_pck_get_data(pck, &pck_size);
261 1175 : if (pck_data) {
262 1175 : e = gf_sk_send(dst_sock, pck_data, pck_size);
263 1175 : if (e) {
264 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Write error: %s\n", gf_error_to_string(e) ));
265 : }
266 1175 : ctx->nb_bytes_sent += pck_size;
267 : return e;
268 : }
269 0 : hwf = gf_filter_pck_get_frame_interface(pck);
270 0 : if (!hwf) {
271 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] output file handle is not opened, discarding %d bytes\n", pck_size));
272 : return GF_OK;
273 : }
274 :
275 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_WIDTH);
276 0 : w = p ? p->value.uint : 0;
277 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_HEIGHT);
278 0 : h = p ? p->value.uint : 0;
279 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_PIXFMT);
280 0 : pf = p ? p->value.uint : 0;
281 :
282 : //get stride/stride_uv with no padding
283 0 : stride = stride_uv = 0;
284 0 : if (gf_pixel_get_size_info(pf, w, h, NULL, &stride, &stride_uv, &nb_planes, &uv_height) == GF_TRUE) {
285 : u32 i;
286 0 : for (i=0; i<nb_planes; i++) {
287 : u32 j, write_h, lsize;
288 : const u8 *out_ptr;
289 0 : u32 out_stride = i ? stride_uv : stride;
290 0 : e = hwf->get_plane(hwf, i, &out_ptr, &out_stride);
291 0 : if (e) {
292 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Failed to fetch plane data from hardware frame, cannot write\n"));
293 0 : break;
294 : }
295 0 : if (i) {
296 0 : write_h = uv_height;
297 0 : lsize = stride_uv;
298 : } else {
299 : write_h = h;
300 0 : lsize = stride;
301 : }
302 0 : for (j=0; j<write_h; j++) {
303 0 : e = gf_sk_send(dst_sock, out_ptr, lsize);
304 0 : if (e) {
305 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Write error: %s\n", gf_error_to_string(e) ));
306 : }
307 0 : out_ptr += out_stride;
308 0 : ctx->nb_bytes_sent += lsize;
309 : }
310 : }
311 : }
312 : return GF_OK;
313 : }
314 :
315 :
316 2545 : static GF_Err sockout_process(GF_Filter *filter)
317 : {
318 : GF_Err e;
319 : Bool is_pck_ref = GF_FALSE;
320 :
321 : GF_FilterPacket *pck;
322 2545 : GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter);
323 :
324 2545 : if (!ctx->socket)
325 : return GF_EOS;
326 :
327 2540 : if (ctx->rate) {
328 1236 : if (!ctx->start_time) ctx->start_time = gf_sys_clock_high_res();
329 : else {
330 1232 : u64 now = gf_sys_clock_high_res() - ctx->start_time;
331 1232 : if (ctx->nb_bytes_sent*8*1000000 > ctx->rate * now) {
332 833 : u64 diff = ctx->nb_bytes_sent*8*1000000 / ctx->rate - now;
333 833 : gf_filter_ask_rt_reschedule(filter, (u32) MAX(diff, 1000) );
334 833 : return GF_OK;
335 399 : } else if (gf_filter_reporting_enabled(filter)) {
336 : char szMsg[200];
337 0 : snprintf(szMsg, 199, "Sending at "LLU" kbps\r", ctx->nb_bytes_sent*8*1000/now);
338 0 : szMsg[199] = 0;
339 0 : gf_filter_update_status(filter, 0, szMsg);
340 : }
341 : }
342 : }
343 :
344 1707 : if (ctx->listen) {
345 915 : GF_Socket *new_conn=NULL;
346 915 : e = gf_sk_accept(ctx->socket, &new_conn);
347 915 : if ((e==GF_OK) && new_conn) {
348 : GF_SockOutClient *sc;
349 3 : GF_SAFEALLOC(sc, GF_SockOutClient);
350 3 : if (!sc) return GF_OUT_OF_MEM;
351 :
352 3 : sc->socket = new_conn;
353 3 : strcpy(sc->address, "unknown");
354 3 : gf_sk_get_remote_address(new_conn, sc->address);
355 :
356 3 : GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[SockOut] Accepting new connection from %s\n", sc->address));
357 3 : gf_list_add(ctx->clients, sc);
358 3 : ctx->had_clients = GF_TRUE;
359 3 : if (!ctx->pid_started && ctx->pid) {
360 : GF_FilterEvent evt;
361 3 : gf_filter_pid_init_play_event(ctx->pid, &evt, ctx->start, ctx->speed, "SockOut");
362 3 : gf_filter_pid_send_event(ctx->pid, &evt);
363 3 : ctx->pid_started = GF_TRUE;
364 : }
365 3 : sc->pck_pending = ctx->pck_pending;
366 3 : if (!ctx->nb_pck_processed)
367 3 : sc->is_tuned = GF_TRUE;
368 : }
369 : }
370 1707 : if (!ctx->pid) {
371 6 : if (ctx->listen) gf_filter_post_process_task(filter);
372 : return GF_OK;
373 : }
374 :
375 1701 : pck = gf_filter_pid_get_packet(ctx->pid);
376 1701 : if (!pck) {
377 13 : if (gf_filter_pid_is_eos(ctx->pid)) {
378 10 : if (ctx->rev_pck) {
379 : is_pck_ref = GF_TRUE;
380 : pck = ctx->rev_pck;
381 : } else {
382 10 : if (!ctx->listen) {
383 7 : gf_sk_del(ctx->socket);
384 7 : ctx->socket = NULL;
385 7 : return GF_EOS;
386 : }
387 3 : if (!ctx->ka)
388 : return GF_EOS;
389 : //keep alive, ask for real-time reschedule of 100 ms - we should use socket groups and selects !
390 0 : gf_filter_ask_rt_reschedule(filter, 100000);
391 : }
392 : }
393 3 : if (!pck)
394 : return GF_OK;
395 : }
396 :
397 1688 : if (ctx->pckd.den && !ctx->rev_pck) {
398 315 : u32 pck_idx = ctx->pckd.num;
399 315 : u32 nb_pck = ctx->nb_pckd_wnd * ctx->pckd.den;
400 :
401 315 : if (!pck_idx) {
402 315 : if (!ctx->next_pckd_idx) ctx->next_pckd_idx = gf_rand() % ctx->pckd.den;
403 315 : pck_idx = ctx->next_pckd_idx;
404 : }
405 315 : nb_pck += pck_idx;
406 :
407 315 : if (nb_pck == 1+ctx->nb_pck_processed) {
408 1 : ctx->nb_pck_processed++;
409 1 : GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("[SockOut] dropping packet %d per user request\r", ctx->nb_pck_processed));
410 1 : gf_filter_pid_drop_packet(ctx->pid);
411 1 : ctx->next_pckd_idx = 0;
412 1 : ctx->nb_pckd_wnd ++;
413 1 : return GF_OK;
414 : }
415 : }
416 1687 : if (ctx->pckr.den && !ctx->rev_pck) {
417 314 : u32 pck_idx = ctx->pckr.num;
418 314 : u32 nb_pck = ctx->nb_pckr_wnd * ctx->pckr.den;
419 :
420 314 : if (!pck_idx) {
421 314 : if (!ctx->next_pckr_idx) ctx->next_pckr_idx = gf_rand() % ctx->pckr.den;
422 314 : pck_idx = ctx->next_pckr_idx;
423 : }
424 314 : nb_pck += pck_idx;
425 :
426 314 : if (nb_pck == 1+ctx->nb_pck_processed) {
427 35 : ctx->rev_pck = pck;
428 35 : gf_filter_pck_ref(&ctx->rev_pck);
429 35 : GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("[SockOut] Reverting packet %d per user request\r", ctx->nb_pck_processed));
430 35 : ctx->nb_pck_processed++;
431 35 : gf_filter_pid_drop_packet(ctx->pid);
432 35 : pck = gf_filter_pid_get_packet(ctx->pid);
433 35 : if (!pck) return GF_OK;
434 : }
435 : }
436 :
437 1657 : if (ctx->listen) {
438 903 : Bool had_pck_pending = ctx->pck_pending;
439 903 : u32 i, nb_clients = gf_list_count(ctx->clients);
440 903 : u8 dep_flags = gf_filter_pck_get_dependency_flags(pck);
441 : if ((dep_flags & 0x3) == 1) {
442 : }
443 903 : ctx->pck_pending = GF_FALSE;
444 :
445 903 : if (!nb_clients) {
446 : //client disconnected, drop packet if needed
447 517 : if (ctx->had_clients && !ctx->kp) {
448 0 : if (is_pck_ref) {
449 0 : gf_filter_pck_unref(pck);
450 0 : ctx->rev_pck = NULL;
451 : } else {
452 0 : gf_filter_pid_drop_packet(ctx->pid);
453 : }
454 : }
455 : return GF_OK;
456 : }
457 386 : for (i=0; i<nb_clients; i++) {
458 386 : GF_SockOutClient *sc = gf_list_get(ctx->clients, i);
459 : if (!sc->is_tuned) {
460 :
461 : }
462 386 : if (had_pck_pending && !sc->pck_pending) {
463 0 : continue;
464 : }
465 386 : sc->pck_pending = GF_FALSE;
466 :
467 386 : e = sockout_send_packet(ctx, pck, sc->socket);
468 386 : if (e == GF_BUFFER_TOO_SMALL) {
469 0 : sc->pck_pending = GF_TRUE;
470 0 : ctx->pck_pending = GF_TRUE;
471 : }
472 : }
473 386 : if (ctx->pck_pending) return GF_OK;
474 :
475 : } else {
476 754 : e = sockout_send_packet(ctx, pck, ctx->socket);
477 754 : if (e == GF_BUFFER_TOO_SMALL) return GF_OK;
478 : }
479 :
480 1140 : ctx->nb_pck_processed++;
481 :
482 1140 : if (is_pck_ref) {
483 0 : gf_filter_pck_unref(pck);
484 0 : ctx->rev_pck = NULL;
485 : } else {
486 1140 : gf_filter_pid_drop_packet(ctx->pid);
487 1140 : if (ctx->rev_pck) {
488 35 : e = sockout_send_packet(ctx, ctx->rev_pck, ctx->socket);
489 35 : if (e == GF_BUFFER_TOO_SMALL) return GF_OK;
490 35 : gf_filter_pck_unref(ctx->rev_pck);
491 35 : ctx->rev_pck = NULL;
492 35 : ctx->next_pckr_idx=0;
493 35 : ctx->nb_pckr_wnd++;
494 : }
495 : }
496 : return GF_OK;
497 : }
498 :
499 2198 : static GF_FilterProbeScore sockout_probe_url(const char *url, const char *mime)
500 : {
501 2198 : if (!strnicmp(url, "tcp://", 6)) return GF_FPROBE_SUPPORTED;
502 2192 : if (!strnicmp(url, "udp://", 6)) return GF_FPROBE_SUPPORTED;
503 : #ifdef GPAC_HAS_SOCK_UN
504 2188 : if (!strnicmp(url, "tcpu://", 7)) return GF_FPROBE_SUPPORTED;
505 2188 : if (!strnicmp(url, "udpu://", 7)) return GF_FPROBE_SUPPORTED;
506 : #endif
507 2188 : return GF_FPROBE_NOT_SUPPORTED;
508 : }
509 :
510 :
511 : #define OFFS(_n) #_n, offsetof(GF_SockOutCtx, _n)
512 :
513 : static const GF_FilterArgs SockOutArgs[] =
514 : {
515 : { OFFS(dst), "URL of destination - see filter help", GF_PROP_NAME, NULL, NULL, 0},
516 : { OFFS(sockbuf), "block size used to read file", GF_PROP_UINT, "65536", NULL, GF_FS_ARG_HINT_ADVANCED},
517 : { OFFS(port), "default port if not specified", GF_PROP_UINT, "1234", NULL, 0},
518 : { OFFS(ifce), "default multicast interface", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
519 : { OFFS(ext), "file extension of pipe data - see filter help", GF_PROP_STRING, NULL, NULL, 0},
520 : { OFFS(mime), "mime type of pipe data - see filter help", GF_PROP_STRING, NULL, NULL, 0},
521 : { OFFS(listen), "indicate the output socket works in server mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
522 : { OFFS(maxc), "max number of concurrent connections", GF_PROP_UINT, "+I", NULL, GF_FS_ARG_HINT_ADVANCED},
523 : { OFFS(ka), "keep socket alive if no more connections", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
524 : { OFFS(kp), "keep packets in queue if no more clients", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED},
525 : { OFFS(start), "set playback start offset. Negative value means percent of media duration with -1 equal to duration", GF_PROP_DOUBLE, "0.0", NULL, 0},
526 : { OFFS(speed), "set playback speed. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0},
527 : { OFFS(rate), "set send rate in bps, disabled by default (as fast as possible)", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED},
528 : { OFFS(pckr), "reverse packet every N - see filter help", GF_PROP_FRACTION, "0/0", NULL, GF_FS_ARG_HINT_EXPERT},
529 : { OFFS(pckd), "drop packet every N - see filter help", GF_PROP_FRACTION, "0/0", NULL, GF_FS_ARG_HINT_EXPERT},
530 : { OFFS(ttl), "multicast TTL", GF_PROP_UINT, "0", "0-127", GF_FS_ARG_HINT_EXPERT},
531 : {0}
532 : };
533 :
534 : static const GF_FilterCapability SockOutCaps[] =
535 : {
536 : CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
537 : CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"),
538 : CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_MIME, "*"),
539 : };
540 :
541 :
542 : GF_FilterRegister SockOutRegister = {
543 : .name = "sockout",
544 : GF_FS_SET_DESCRIPTION("UDP/TCP output")
545 : #ifndef GPAC_DISABLE_DOC
546 : .help = "This filter handles generic output sockets (mono-directional) in blocking mode only.\n"
547 : "The filter can work in server mode, waiting for source connections, or or in client mode, directly connecting.\n"
548 : "In server mode, the filter can be instructed to keep running at the end of the stream.\n"
549 : "In server mode, the default behavior is to keep input packets when no more clients are connected; "
550 : "this can be adjusted though the [-kp]() option, however there is no realtime regulation of how fast packets are dropped.\n"
551 : "If your sources are not real time, consider adding a real-time scheduler in the chain (cf reframer filter), or set the send [-rate]() option.\n"
552 : "\n"
553 : "- UDP sockets are used for destinations URLs formatted as `udp://NAME`\n"
554 : "- TCP sockets are used for destinations URLs formatted as `tcp://NAME`\n"
555 : #ifdef GPAC_HAS_SOCK_UN
556 : "- UDP unix domain sockets are used for destinations URLs formatted as `udpu://NAME`\n"
557 : "- TCP unix domain sockets are used for destinations URLs formatted as `tcpu://NAME`\n"
558 : #else
559 : "Your platform does not supports unix domain sockets"
560 : #endif
561 : "\n"
562 : "When ports are specified in the URL and the default option separators are used (see `gpac -h doc`), the URL must either:\n"
563 : "- have a trailing '/', eg `udp://localhost:1234/[:opts]`\n"
564 : "- use `gpac` '/', eg `udp://localhost:1234[:gpac:opts]`\n"
565 : "\n"
566 : "The socket output can be configured to drop or revert packet order for test purposes.\n"
567 : "For both mode, a window size in packets is specified as the drop/revert fraction denominator, and the index of the packet to drop/revert is given as the numerator/\n"
568 : "If the numerator is 0, a packet is randomly chosen in that window.\n"
569 : "EX :pckd=4/10\n"\
570 : "This drops every 4th packet of each 10 packet window.\n"
571 : "EX :pckr=0/100\n"\
572 : "This reverts the send order of one random packet in each 100 packet window.\n"
573 : "\n",
574 : #endif //GPAC_DISABLE_DOC
575 : .private_size = sizeof(GF_SockOutCtx),
576 : .args = SockOutArgs,
577 : SETCAPS(SockOutCaps),
578 : .probe_url = sockout_probe_url,
579 : .initialize = sockout_initialize,
580 : .finalize = sockout_finalize,
581 : .configure_pid = sockout_configure_pid,
582 : .process = sockout_process
583 : };
584 :
585 :
586 2877 : const GF_FilterRegister *sockout_register(GF_FilterSession *session)
587 : {
588 2877 : return &SockOutRegister;
589 : }
590 :
|