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