Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2018
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / generic pipe 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 :
30 : #ifdef WIN32
31 :
32 : #include <windows.h>
33 :
34 : #else
35 :
36 : #include <fcntl.h>
37 : #include <unistd.h>
38 :
39 : #ifdef GPAC_CONFIG_LINUX
40 : #include <sys/types.h>
41 : #include <sys/stat.h>
42 : #endif
43 :
44 : #ifndef __BEOS__
45 : #include <errno.h>
46 : #endif
47 :
48 : #endif
49 :
50 :
51 :
52 : typedef struct
53 : {
54 : //options
55 : Double start, speed;
56 : char *dst, *mime, *ext;
57 : Bool dynext, mkp;
58 : u32 block_size;
59 :
60 :
61 : //only one input pid
62 : GF_FilterPid *pid;
63 :
64 : #ifdef WIN32
65 : HANDLE pipe;
66 : #else
67 : int fd;
68 : #endif
69 :
70 : GF_FilterCapability in_caps[2];
71 : char szExt[10];
72 : char szFileName[GF_MAX_PATH];
73 : Bool owns_pipe;
74 :
75 : } GF_PipeOutCtx;
76 :
77 : const char *gf_errno_str(int errnoval);
78 :
79 :
80 28 : static GF_Err pipeout_open_close(GF_PipeOutCtx *ctx, const char *filename, const char *ext, u32 file_idx, Bool explicit_overwrite)
81 : {
82 : GF_Err e = GF_OK;
83 : char szName[GF_MAX_PATH], szFinalName[GF_MAX_PATH];
84 :
85 28 : if (!filename) {
86 : #ifdef WIN32
87 : if (ctx->pipe != INVALID_HANDLE_VALUE) CloseHandle(ctx->pipe);
88 : ctx->pipe = INVALID_HANDLE_VALUE;
89 : #else
90 20 : if (ctx->fd>=0) close(ctx->fd);
91 20 : ctx->fd = -1;
92 : #endif
93 : return GF_OK;
94 : }
95 :
96 8 : if (!strncmp(filename, "pipe://", 7)) filename+=7;
97 :
98 8 : if (ctx->dynext) {
99 0 : Bool has_ext = (strchr(filename, '.')==NULL) ? GF_FALSE : GF_TRUE;
100 :
101 : strcpy(szName, filename);
102 0 : if (!has_ext && ext) {
103 : strcat(szName, ".");
104 : strcat(szName, ext);
105 : }
106 : } else {
107 : strcpy(szName, filename);
108 : }
109 8 : gf_filter_pid_resolve_file_template(ctx->pid, szName, szFinalName, file_idx, NULL);
110 :
111 8 : if (!strcmp(szFinalName, ctx->szFileName)
112 : #ifdef WIN32
113 : && (ctx->pipe != INVALID_HANDLE_VALUE)
114 : #else
115 0 : && ctx->fd>=0
116 : #endif
117 : ) return GF_OK;
118 :
119 : #ifdef WIN32
120 : if (ctx->pipe != INVALID_HANDLE_VALUE) CloseHandle(ctx->pipe);
121 : ctx->pipe = INVALID_HANDLE_VALUE;
122 : char szNamedPipe[GF_MAX_PATH];
123 : if (!strncmp(szFinalName, "\\\\", 2)) {
124 : strcpy(szNamedPipe, szFinalName);
125 : }
126 : else {
127 : strcpy(szNamedPipe, "\\\\.\\pipe\\gpac\\");
128 : strcat(szNamedPipe, szFinalName);
129 : }
130 : if (strchr(szFinalName, '/')) {
131 : u32 i, len = (u32)strlen(szNamedPipe);
132 : for (i = 0; i < len; i++) {
133 : if (szNamedPipe[i] == '/')
134 : szNamedPipe[i] = '\\';
135 : }
136 : }
137 :
138 : if (WaitNamedPipeA(szNamedPipe, 1) == FALSE) {
139 : if (!ctx->mkp) {
140 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to open %s: %d\n", szNamedPipe, GetLastError()));
141 : e = GF_URL_ERROR;
142 : }
143 : else {
144 : ctx->pipe = CreateNamedPipe(szNamedPipe, PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
145 : PIPE_TYPE_BYTE | PIPE_WAIT,
146 : 10,
147 : ctx->block_size,
148 : ctx->block_size,
149 : 0,
150 : NULL);
151 :
152 : if (ctx->pipe == INVALID_HANDLE_VALUE) {
153 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to create named pipe %s: %d\n", szNamedPipe, GetLastError()));
154 : e = GF_IO_ERR;
155 : }
156 : else {
157 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeOut] Waiting for client connection for %s, blocking\n", szNamedPipe));
158 : if (!ConnectNamedPipe(ctx->pipe, NULL) && (GetLastError() != ERROR_PIPE_CONNECTED)) {
159 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to connect named pipe %s: %d\n", szNamedPipe, GetLastError()));
160 : e = GF_IO_ERR;
161 : CloseHandle(ctx->pipe);
162 : ctx->pipe = INVALID_HANDLE_VALUE;
163 : }
164 : else {
165 : ctx->owns_pipe = GF_TRUE;
166 : }
167 : }
168 : }
169 : }
170 : else {
171 : ctx->pipe = CreateFile(szNamedPipe, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
172 : if (ctx->pipe == INVALID_HANDLE_VALUE) {
173 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to open %s: %d\n", szNamedPipe, GetLastError()));
174 : e = GF_URL_ERROR;
175 : }
176 : }
177 :
178 : #else
179 8 : if (ctx->fd>=0) close(ctx->fd);
180 8 : ctx->fd = -1;
181 :
182 8 : if (!gf_file_exists(szFinalName) && ctx->mkp) {
183 : #ifdef GPAC_CONFIG_DARWIN
184 : mknod(szFinalName, S_IFIFO | 0666, 0);
185 : #else
186 1 : mkfifo(szFinalName, 0666);
187 : #endif
188 1 : ctx->owns_pipe = GF_TRUE;
189 : }
190 :
191 8 : ctx->fd = open(szFinalName, O_WRONLY );
192 :
193 8 : if (ctx->fd<0) {
194 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Cannot open output pipe %s: %s\n", ctx->szFileName, gf_errno_str(errno)));
195 0 : e = ctx->owns_pipe ? GF_IO_ERR : GF_URL_ERROR;
196 : }
197 : #endif
198 : if (e) {
199 : return e;
200 : }
201 : strncpy(ctx->szFileName, szFinalName, GF_MAX_PATH-1);
202 8 : ctx->szFileName[GF_MAX_PATH-1] = 0;
203 : return GF_OK;
204 : }
205 :
206 8 : static GF_Err pipeout_setup_file(GF_PipeOutCtx *ctx, Bool explicit_overwrite)
207 : {
208 : const GF_PropertyValue *p;
209 8 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH);
210 8 : if (p && p->value.string) {
211 0 : return pipeout_open_close(ctx, p->value.string, NULL, 0, explicit_overwrite);
212 8 : } else if (ctx->dynext) {
213 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PCK_FILENUM);
214 0 : if (!p) {
215 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT);
216 0 : if (p && p->value.string) {
217 0 : return pipeout_open_close(ctx, ctx->dst, p->value.string, 0, explicit_overwrite);
218 : }
219 : }
220 : } else {
221 8 : return pipeout_open_close(ctx, ctx->dst, NULL, 0, explicit_overwrite);
222 : }
223 : return GF_BAD_PARAM;
224 : }
225 8 : static GF_Err pipeout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
226 : {
227 : const GF_PropertyValue *p;
228 8 : GF_PipeOutCtx *ctx = (GF_PipeOutCtx *) gf_filter_get_udta(filter);
229 8 : if (is_remove) {
230 0 : ctx->pid = NULL;
231 0 : pipeout_open_close(ctx, NULL, NULL, 0, GF_FALSE);
232 0 : return GF_OK;
233 : }
234 8 : gf_filter_pid_check_caps(pid);
235 :
236 8 : if (!ctx->pid) {
237 : GF_FilterEvent evt;
238 8 : gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "PipeOut");
239 8 : gf_filter_pid_send_event(pid, &evt);
240 : }
241 8 : ctx->pid = pid;
242 :
243 8 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE);
244 8 : if (p && p->value.uint) {
245 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Block patching is not supported by pipe output\n"));
246 : return GF_NOT_SUPPORTED;
247 : }
248 : return GF_OK;
249 : }
250 :
251 8 : static GF_Err pipeout_initialize(GF_Filter *filter)
252 : {
253 : char *ext;
254 8 : GF_PipeOutCtx *ctx = (GF_PipeOutCtx *) gf_filter_get_udta(filter);
255 :
256 8 : if (!ctx || !ctx->dst) return GF_OK;
257 :
258 8 : if (strnicmp(ctx->dst, "pipe://", 7) && strstr(ctx->dst, "://")) {
259 0 : gf_filter_setup_failure(filter, GF_NOT_SUPPORTED);
260 0 : return GF_NOT_SUPPORTED;
261 : }
262 8 : if (ctx->dynext) return GF_OK;
263 :
264 8 : if (ctx->ext) ext = ctx->ext;
265 : else {
266 0 : ext = gf_file_ext_start(ctx->dst);
267 0 : if (ext) ext++;
268 : }
269 :
270 : #ifdef WIN32
271 : ctx->pipe = INVALID_HANDLE_VALUE;
272 : #else
273 8 : ctx->fd = -1;
274 : #endif
275 8 : if (!ext && !ctx->mime) {
276 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] No extension provided nor mime type for output file %s, cannot infer format\n", ctx->dst));
277 : return GF_NOT_SUPPORTED;
278 : }
279 : //static cap, streamtype = file
280 8 : ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE;
281 8 : ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE);
282 8 : ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC;
283 :
284 8 : if (ctx->mime) {
285 0 : ctx->in_caps[1].code = GF_PROP_PID_MIME;
286 0 : ctx->in_caps[1].val = PROP_NAME( ctx->mime );
287 0 : ctx->in_caps[1].flags = GF_CAPS_INPUT;
288 : } else {
289 8 : strncpy(ctx->szExt, ext, 9);
290 8 : ctx->szExt[9] = 0;
291 8 : strlwr(ctx->szExt);
292 8 : ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT;
293 8 : ctx->in_caps[1].val = PROP_NAME( ctx->szExt );
294 8 : ctx->in_caps[1].flags = GF_CAPS_INPUT;
295 : }
296 8 : gf_filter_override_caps(filter, ctx->in_caps, 2);
297 8 : return GF_OK;
298 : }
299 :
300 8 : static void pipeout_finalize(GF_Filter *filter)
301 : {
302 8 : GF_PipeOutCtx *ctx = (GF_PipeOutCtx *) gf_filter_get_udta(filter);
303 8 : pipeout_open_close(ctx, NULL, NULL, 0, GF_FALSE);
304 :
305 8 : if (ctx->owns_pipe)
306 1 : gf_file_delete(ctx->szFileName);
307 8 : }
308 :
309 1080 : static GF_Err pipeout_process(GF_Filter *filter)
310 : {
311 : GF_FilterPacket *pck;
312 : const GF_PropertyValue *fname, *p;
313 : Bool start, end;
314 : const char *pck_data;
315 : u32 pck_size;
316 : s32 nb_write;
317 1080 : GF_PipeOutCtx *ctx = (GF_PipeOutCtx *) gf_filter_get_udta(filter);
318 :
319 1080 : pck = gf_filter_pid_get_packet(ctx->pid);
320 1080 : if (!pck) {
321 8 : if (gf_filter_pid_is_eos(ctx->pid)) {
322 8 : pipeout_open_close(ctx, NULL, NULL, 0, GF_FALSE);
323 8 : return GF_EOS;
324 : }
325 : return GF_OK;
326 : }
327 :
328 1072 : gf_filter_pck_get_framing(pck, &start, &end);
329 :
330 1072 : if (start) {
331 : const GF_PropertyValue *fext, *fnum;
332 :
333 : Bool explicit_overwrite = GF_FALSE;
334 : const char *name = NULL;
335 : fname = fext = NULL;
336 : //file num increased per packet, open new file
337 8 : fnum = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM);
338 8 : if (fnum) {
339 0 : fname = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH);
340 0 : fext = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT);
341 0 : if (!fname) name = ctx->dst;
342 : }
343 : //filename change at packet start, open new file
344 8 : if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME);
345 8 : if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH);
346 8 : if (!fext) fext = gf_filter_pck_get_property(pck, GF_PROP_PID_FILE_EXT);
347 8 : if (fname) name = fname->value.string;
348 :
349 8 : if (end && gf_filter_pck_get_seek_flag(pck))
350 : explicit_overwrite = GF_TRUE;
351 :
352 8 : if (name) {
353 0 : pipeout_open_close(ctx, name, fext ? fext->value.string : NULL, fnum ? fnum->value.uint : 0, explicit_overwrite);
354 8 : } else if (
355 : #ifdef WIN32
356 : ctx->pipe==INVALID_HANDLE_VALUE
357 : #else
358 8 : ctx->fd<0
359 : #endif
360 : ) {
361 8 : GF_Err e = pipeout_setup_file(ctx, explicit_overwrite);
362 8 : if (e) {
363 0 : gf_filter_setup_failure(filter, e);
364 0 : return e;
365 : }
366 : }
367 : }
368 :
369 1072 : pck_data = gf_filter_pck_get_data(pck, &pck_size);
370 1072 : if (
371 : #ifdef WIN32
372 : ctx->pipe != INVALID_HANDLE_VALUE
373 : #else
374 1072 : ctx->fd>=0
375 : #endif
376 : ) {
377 1072 : GF_FilterFrameInterface *hwf = gf_filter_pck_get_frame_interface(pck);
378 1072 : if (pck_data) {
379 : #ifdef WIN32
380 : if (! WriteFile(ctx->pipe, pck_data, pck_size, (LPDWORD) &nb_write, NULL)) {
381 : nb_write = 0;
382 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Write error, wrote %d bytes but had %u to write: error %d\n", nb_write, pck_size, GetLastError() ));
383 : }
384 : #else
385 1072 : nb_write = (s32) write(ctx->fd, pck_data, pck_size);
386 1072 : if (nb_write != pck_size) {
387 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Write error, wrote %d bytes but had %u to write: %s\n", nb_write, pck_size, gf_errno_str(errno)));
388 : }
389 : #endif
390 0 : } else if (hwf) {
391 : u32 w, h, stride, stride_uv, pf;
392 : u32 nb_planes, uv_height;
393 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_WIDTH);
394 0 : w = p ? p->value.uint : 0;
395 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_HEIGHT);
396 0 : h = p ? p->value.uint : 0;
397 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_PIXFMT);
398 0 : pf = p ? p->value.uint : 0;
399 :
400 : //get stride/stride_uv with no padding
401 0 : stride = stride_uv = 0;
402 0 : if (gf_pixel_get_size_info(pf, w, h, NULL, &stride, &stride_uv, &nb_planes, &uv_height) == GF_TRUE) {
403 : u32 i;
404 0 : for (i=0; i<nb_planes; i++) {
405 : u32 j, write_h, lsize;
406 : const u8 *out_ptr;
407 0 : u32 out_stride = i ? stride_uv : stride;
408 0 : GF_Err e = hwf->get_plane(hwf, i, &out_ptr, &out_stride);
409 0 : if (e) {
410 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to fetch plane data from hardware frame, cannot write\n"));
411 0 : break;
412 : }
413 0 : if (i) {
414 0 : write_h = uv_height;
415 0 : lsize = stride_uv;
416 : } else {
417 : write_h = h;
418 0 : lsize = stride;
419 : }
420 :
421 0 : for (j=0; j<write_h; j++) {
422 : #ifdef WIN32
423 : if (!WriteFile(ctx->pipe, out_ptr, lsize, (LPDWORD) &nb_write, NULL)) {
424 : nb_write = 0;
425 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Write error, wrote %d bytes but had %u to write: %d\n", nb_write, pck_size, GetLastError()));
426 : }
427 : #else
428 0 : nb_write = (s32) write(ctx->fd, out_ptr, lsize);
429 0 : if (nb_write != lsize) {
430 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Write error, wrote %d bytes but had %u to write: %s\n", nb_write, lsize, gf_errno_str(errno)));
431 : }
432 : #endif
433 0 : out_ptr += out_stride;
434 : }
435 : }
436 : }
437 : } else {
438 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeOut] No data associated with packet, cannot write\n"));
439 : }
440 : } else {
441 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Output file handle is not opened, discarding %d bytes\n", pck_size));
442 : }
443 1072 : gf_filter_pid_drop_packet(ctx->pid);
444 1072 : if (end) {
445 4 : pipeout_open_close(ctx, NULL, NULL, 0, GF_FALSE);
446 : }
447 : return GF_OK;
448 : }
449 :
450 2198 : static GF_FilterProbeScore pipeout_probe_url(const char *url, const char *mime)
451 : {
452 2198 : if (!strnicmp(url, "pipe://", 7)) return GF_FPROBE_SUPPORTED;
453 2190 : if (!strnicmp(url, "pipe:", 5)) return GF_FPROBE_SUPPORTED;
454 2190 : return GF_FPROBE_NOT_SUPPORTED;
455 : }
456 :
457 :
458 : #define OFFS(_n) #_n, offsetof(GF_PipeOutCtx, _n)
459 :
460 : static const GF_FilterArgs PipeOutArgs[] =
461 : {
462 : { OFFS(dst), "name of destination pipe", GF_PROP_NAME, NULL, NULL, 0},
463 : { OFFS(ext), "indicate file extension of pipe data", GF_PROP_STRING, NULL, NULL, 0},
464 : { OFFS(mime), "indicate mime type of pipe data", GF_PROP_STRING, NULL, NULL, 0},
465 : { OFFS(dynext), "indicate the file extension is set by filter chain, not dst", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
466 : { 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},
467 : { 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},
468 : { OFFS(mkp), "create pipe if not found - see filter help", GF_PROP_BOOL, "false", NULL, 0 },
469 : { OFFS(block_size), "buffer size used to write to pipe, windows only", GF_PROP_UINT, "5000", NULL, GF_FS_ARG_HINT_ADVANCED },
470 : {0}
471 : };
472 :
473 : static const GF_FilterCapability PipeOutCaps[] =
474 : {
475 : CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
476 : CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"),
477 : CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_MIME, "*"),
478 : };
479 :
480 :
481 : GF_FilterRegister PipeOutRegister = {
482 : .name = "pout",
483 : GF_FS_SET_DESCRIPTION("pipe output")
484 : GF_FS_SET_HELP("This filter handles generic output pipes (mono-directional) in blocking mode only.\n"\
485 : "Warning: Output pipes do not currently support non blocking mode.\n"\
486 : "The associated protocol scheme is `pipe://` when loaded as a generic output (eg, -o `pipe://URL` where URL is a relative or absolute pipe name).\n"\
487 : "Data format of the pipe **shall** be specified using extension (either in filename or through [-ext]() option) or MIME type through [-mime]()\n"\
488 : "The pipe name indicated in [-dst]() can use template mechanisms from gpac, e.g. `dst=pipe_$ServiceID$`\n"\
489 : "\n"\
490 : "On Windows hosts, the default pipe prefix is `\\\\.\\pipe\\gpac\\` if no prefix is set \n"\
491 : "EX dst=mypipe resolves in \\\\.\\pipe\\gpac\\mypipe\n"\
492 : "EX dst=\\\\.\\pipe\\myapp\\mypipe resolves in \\\\.\\pipe\\myapp\\mypipe\n"
493 : "Any destination name starting with `\\\\` is used as is, with `\\` translated in `/`\n"\
494 : "\n"\
495 : "The pipe input can create the pipe if not found using [-mkp](). On windows hosts, this will create a pipe server.\n"\
496 : "On non windows hosts, the created pipe will delete the pipe file upon filter destruction."\
497 : "")
498 : .private_size = sizeof(GF_PipeOutCtx),
499 : .args = PipeOutArgs,
500 : SETCAPS(PipeOutCaps),
501 : .probe_url = pipeout_probe_url,
502 : .initialize = pipeout_initialize,
503 : .finalize = pipeout_finalize,
504 : .configure_pid = pipeout_configure_pid,
505 : .process = pipeout_process
506 : };
507 :
508 :
509 2877 : const GF_FilterRegister *pipeout_register(GF_FilterSession *session)
510 : {
511 2877 : return &PipeOutRegister;
512 : }
513 :
|