1 /*
2  *			GPAC - Multimedia Framework C SDK
3  *
4  *			Authors: Samir Mustapha - Jean Le Feuvre
5  *			Copyright (c) Telecom ParisTech 2019
6  *					All rights reserved
7  *
8  *  This file is part of GPAC / video flip 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 #include <gpac/filters.h>
27 #include <gpac/list.h>
28 #include <gpac/constants.h>
29 #include <gpac/network.h>
30 
31 typedef struct
32 {
33 	//options
34 	u32 mode;
35 
36 	//internal data
37 	Bool initialized;
38 
39 	GF_FilterPid *ipid, *opid;
40 	u32 w, h, stride, s_pfmt;
41 	u32 bps;
42 	GF_Fraction ar;
43 	Bool passthrough;
44 	u32 dst_width, dst_height;
45 
46 	u32 dst_stride[5];
47 	u32 src_stride[5];
48 	u32 nb_planes, nb_src_planes, out_size, out_src_size, src_uv_height, dst_uv_height;
49 
50 	Bool use_reference;
51 	Bool packed_422;
52 
53 
54 	char *line_buffer_vf; //vertical flip
55 	char *line_buffer_hf; //horizontal flip
56 
57 } GF_VFlipCtx;
58 
59 enum
60 {
61 	VFLIP_OFF = 0,
62 	VFLIP_VERT,
63 	VFLIP_HORIZ,
64 	VFLIP_BOTH,
65 };
66 
67 
68 
swap_2Ys_YUVpixel(GF_VFlipCtx * ctx,u8 * line_src,u8 * line_dst,u32 FourBytes_start_index)69 static void swap_2Ys_YUVpixel(GF_VFlipCtx *ctx, u8 *line_src, u8 *line_dst, u32 FourBytes_start_index)
70 {
71 	u32 isFirstY_indexOne;
72 	switch (ctx->s_pfmt) {
73 	case GF_PIXEL_YUYV:
74 	case GF_PIXEL_YVYU:
75 		isFirstY_indexOne= (u32) 0;
76 		break;
77 	case GF_PIXEL_UYVY:
78 	case GF_PIXEL_VYUY:
79 		isFirstY_indexOne= (u32) 1;
80 		break;
81 	default:
82 		return;
83 	}
84 
85 	//Y2_dst = Y1_src
86 	line_dst[FourBytes_start_index + 2 + isFirstY_indexOne]=line_src[FourBytes_start_index + 0 + isFirstY_indexOne];
87 
88 	//Y1_dst = Y2_src
89 	line_dst[FourBytes_start_index + 0 + isFirstY_indexOne]=line_src[FourBytes_start_index + 2 + isFirstY_indexOne];
90 }
91 
horizontal_flip_per_line(GF_VFlipCtx * ctx,u8 * line_src,u8 * line_dst,u32 plane_idx,u32 wiB)92 static void horizontal_flip_per_line(GF_VFlipCtx *ctx, u8 *line_src, u8 *line_dst, u32 plane_idx, u32 wiB)
93 {
94 	u32 j, line_size = wiB;
95 
96 	if (ctx->s_pfmt == GF_PIXEL_RGB || ctx->s_pfmt == GF_PIXEL_BGR || ctx->s_pfmt == GF_PIXEL_XRGB || ctx->s_pfmt == GF_PIXEL_RGBX || ctx->s_pfmt == GF_PIXEL_XBGR || ctx->s_pfmt == GF_PIXEL_BGRX){
97 		//to avoid "3*j > line_size - 3*j - 3" or "4*j > line_size - 4*j - 4"
98 		//jmax=line_size/(2*3) or jmax=line_size/(2*4)
99 		for (j=0; j < line_size/(2*ctx->bps); j++) {
100 			u8 pix[4];
101 			memcpy(pix, line_src + line_size - ctx->bps*(j+1), ctx->bps);
102 			memcpy(line_dst + line_size - ctx->bps*(j+1), line_src + ctx->bps*j, ctx->bps);
103 			memcpy(line_dst + ctx->bps*j, pix, ctx->bps);
104 		}
105 	} else if (ctx->packed_422) {
106 		//If the source data is assigned to the output packet during the destination pack allocation
107 		//i.e dst_planes[0]= src_planes[0], line_src is going to change while reading it as far as writing on line_dst=line_src
108 		//To avoid this situation, ctx->line_buffer_hf keeps the values of line_src
109 		memcpy(ctx->line_buffer_hf, line_src, wiB);
110 
111 		//reversing of 4-bytes sequences
112 		u32 fourBytesSize = wiB/4;
113 		for (j=0; j < fourBytesSize; j++) {
114 			//buffer = last 4 columns
115 			u32 last_4bytes_index = wiB-4-(4*j);
116 			u32 p, first_4bytes_index = 4*j;
117 			for (p = 0; p < 4; p++) {
118 				line_dst[first_4bytes_index+p] = ctx->line_buffer_hf[last_4bytes_index+p];
119 			}
120 			//exchanging of Ys within a yuv pixel
121 			swap_2Ys_YUVpixel(ctx, line_dst, line_dst, first_4bytes_index);
122 		}
123 	} else {
124 		//nv12/21
125 		//second plane is U-plane={u1,v1, u2,v2...}
126 		if (ctx->nb_planes==2 && plane_idx==1){
127 			if (ctx->bps==1) {
128 				//to avoid "line_size - 2*j - 2 > 2*j", jmax=line_size/4
129 				for (j=0; j < line_size/4; j++) {
130 					u8 u_comp, v_comp;
131 					u_comp = line_src[line_size - 2*j - 2];
132 					v_comp = line_src[line_size - 2*j - 1];
133 
134 					line_dst[line_size - 2*j - 2] = line_src[2*j];
135 					line_dst[line_size - 2*j - 1] = line_src[2*j + 1];
136 
137 					line_dst[2*j] = u_comp;
138 					line_dst[2*j + 1] = v_comp;
139 				}
140 			} else {
141 				for (j=0; j < line_size/4; j++) {
142 					u16 u_comp, v_comp;
143 					u_comp = line_src[line_size - 2*j - 2];
144 					v_comp = line_src[line_size - 2*j - 1];
145 
146 					((u16 *)line_dst)[line_size - 2*j - 2] = ((u16 *)line_src)[2*j];
147 					((u16 *)line_dst)[line_size - 2*j - 1] = ((u16 *)line_src)[2*j + 1];
148 
149 					((u16 *)line_dst)[2*j] = u_comp;
150 					((u16 *)line_dst)[2*j + 1] = v_comp;
151 				}
152 			}
153 		} else if (ctx->bps==1) {
154 			u32 wx = line_size/2;
155 			u8 tmp;
156 			for (j=0; j < wx; j++) {
157 				//tmp = last column
158 				tmp = line_src[line_size-1-j];
159 
160 				//last column = first column
161 				line_dst[line_size-1-j] = line_src[j];
162 
163 				//first column = tmp
164 				line_dst[j]=tmp;
165 			}
166 		} else {
167 			line_size /= 2;
168 			u32 wx = line_size/2;
169 			u16 tmp;
170 			for (j=0; j < wx; j++) {
171 				//tmp = last column
172 				tmp = (u16) ( ((u16 *)line_src) [line_size-1-j] );
173 
174 				//last column = first column
175 				((u16 *)line_dst) [line_size-1-j] = ((u16*)line_src)[j];
176 
177 				//first column = tmp
178 				((u16 *)line_dst) [j]=tmp;
179 			}
180 		}
181 	}
182 }
183 
horizontal_flip(GF_VFlipCtx * ctx,u8 * src_plane,u8 * dst_plane,u32 height,u32 plane_idx,u32 wiB,u32 * src_stride)184 static void horizontal_flip(GF_VFlipCtx *ctx, u8 *src_plane, u8 *dst_plane, u32 height, u32 plane_idx, u32 wiB, u32 *src_stride)
185 {
186 	u32 i;
187 	for (i=0; i<height; i++) {
188 		u8 *src_first_line = src_plane + i * src_stride[plane_idx];
189 		u8 *dst_first_line = dst_plane + i * ctx->dst_stride[plane_idx];
190 
191 		horizontal_flip_per_line(ctx, src_first_line, dst_first_line, plane_idx, wiB);
192 	}
193 }
194 
vertical_flip(GF_VFlipCtx * ctx,u8 * src_plane,u8 * dst_plane,u32 height,u32 plane_idx,u32 wiB)195 static void vertical_flip(GF_VFlipCtx *ctx, u8 *src_plane, u8 *dst_plane, u32 height, u32 plane_idx, u32 wiB){
196 	u32 hy, i;
197 	hy = height/2;
198 	for (i=0; i<hy; i++) {
199 		u8 *src_first_line = src_plane+ i*ctx->src_stride[plane_idx];
200 		u8 *src_last_line  = src_plane+ (height  - 1 - i) * ctx->src_stride[plane_idx];
201 
202 		u8 *dst_first_line = dst_plane+ i*ctx->dst_stride[plane_idx];
203 		u8 *dst_last_line  = dst_plane+ (height  - 1 - i) * ctx->dst_stride[plane_idx];
204 
205 		memcpy(ctx->line_buffer_vf, src_last_line, wiB);
206 		memcpy(dst_last_line, src_first_line, wiB);
207 		memcpy(dst_first_line, ctx->line_buffer_vf, wiB);
208 	}
209 }
210 
vflip_process(GF_Filter * filter)211 static GF_Err vflip_process(GF_Filter *filter)
212 {
213 	const char *data;
214 	u8 *output;
215 	u32 size;
216 	u32 i;
217 	u32 wiB, height; //wiB: width in Bytes of a plane
218 	u8 *src_planes[5];
219 	u8 *dst_planes[5];
220 	GF_FilterPacket *dst_pck;
221 	GF_FilterFrameInterface *frame_ifce;
222 	GF_VFlipCtx *ctx = gf_filter_get_udta(filter);
223 	GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid);
224 
225 	if (!pck) {
226 		if (gf_filter_pid_is_eos(ctx->ipid)) {
227 			gf_filter_pid_set_eos(ctx->opid);
228 			return GF_EOS;
229 		}
230 		return GF_OK;
231 	}
232 
233 	if (ctx->passthrough) {
234 		gf_filter_pck_forward(pck, ctx->opid);
235 		gf_filter_pid_drop_packet(ctx->ipid);
236 		return GF_OK;
237 	}
238 	data = gf_filter_pck_get_data(pck, &size);
239 	frame_ifce = gf_filter_pck_get_frame_interface(pck);
240 
241 	memset(src_planes, 0, sizeof(src_planes));
242 	memset(dst_planes, 0, sizeof(dst_planes));
243 
244 	if (data) {
245 		src_planes[0] = (u8 *) data;
246 
247 		if (ctx->nb_src_planes==1) {
248 		} else if (ctx->nb_src_planes==2) {
249 			src_planes[1] = src_planes[0] + ctx->src_stride[0]*ctx->h;
250 		} else if (ctx->nb_src_planes==3) {
251 			src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h;
252 			src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height;
253 		} else if (ctx->nb_src_planes==4) {
254 			src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h;
255 			src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height;
256 			src_planes[3] = src_planes[2] + ctx->src_stride[2] * ctx->src_uv_height;
257 		}
258 	} else if (frame_ifce && frame_ifce->get_plane) {
259 		for (i=0; i<4; i++) {
260 			if (frame_ifce->get_plane(frame_ifce, i, (const u8 **) &src_planes[i], &ctx->src_stride[i])!=GF_OK)
261 				break;
262 		}
263 	} else {
264 		GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VFlip] No data associated with packet, not supported\n"));
265 		gf_filter_pid_drop_packet(ctx->ipid);
266 		return GF_NOT_SUPPORTED;
267 	}
268 
269 	ctx->bps = gf_pixel_get_bytes_per_pixel(ctx->s_pfmt);
270 
271 
272 	if (frame_ifce){
273 		dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &output);
274 		gf_filter_pck_merge_properties(pck, dst_pck);
275 	} else {
276 		dst_pck = gf_filter_pck_new_clone(ctx->opid, pck, &output);
277 	}
278 
279 	if (!dst_pck) {
280 		gf_filter_pid_drop_packet(ctx->ipid);
281 		return GF_OUT_OF_MEM;
282 	}
283 
284 
285 	dst_planes[0] = output;
286 	if (ctx->nb_planes==1) {
287 	} else if (ctx->nb_planes==2) {
288 		dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height;
289 	} else if (ctx->nb_planes==3) {
290 		dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height;
291 		dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height;
292 	} else if (ctx->nb_planes==4) {
293 		dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height;
294 		dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height;
295 		dst_planes[3] = dst_planes[2] + ctx->dst_stride[2]*ctx->dst_uv_height;
296 	}
297 
298 	//computing of height and wiB
299 	//YUYV variations need *2 on horizontal dimension
300 	for (i=0; i<ctx->nb_planes; i++) {
301 		if (i==0) {
302 			if (ctx->packed_422) {
303 				wiB = ctx->bps * ctx->dst_width *2;
304 			} else {
305 				wiB = ctx->bps * ctx->dst_width;
306 			}
307 			height = ctx->h;
308 		}else {
309 			//nv12/21
310 			if (i==1 && ctx->nb_planes==2) {
311 				//half vertical res (/2)
312 				//half horizontal res (/2) but two chroma packed per pixel (*2)
313 				wiB = ctx->bps * ctx->dst_width;
314 				height = ctx->h / 2;
315 			} else if (ctx->nb_planes>=3) {
316 				u32 div_x, div_y;
317 				//alpha/depth/other plane, treat as luma plane
318 				if (i==3 && ctx->nb_planes==4) {
319 					wiB = ctx->bps * ctx->dst_width;
320 					height = ctx->h;
321 				}else if (i==1 || i==2) {
322 					div_x = (ctx->src_stride[1]==ctx->src_stride[0]) ? 1 : 2;
323 					div_y = (ctx->src_uv_height==ctx->h) ? 1 : 2;
324 					height = ctx->dst_height;
325 					height /= div_y;
326 					wiB = ctx->bps * ctx->dst_width;
327 					wiB /= div_x;
328 				}
329 			}
330 		}
331 
332 		//processing according selected mode
333 		if (ctx->mode==VFLIP_VERT){
334 			vertical_flip(ctx, src_planes[i], dst_planes[i], height, i, wiB);
335 		}else if (ctx->mode==VFLIP_HORIZ){
336 			horizontal_flip(ctx, src_planes[i], dst_planes[i], height, i, wiB, ctx->src_stride);
337 		}else if (ctx->mode==VFLIP_BOTH){
338 			vertical_flip(ctx, src_planes[i], dst_planes[i], height, i, wiB);
339 			horizontal_flip(ctx, dst_planes[i], dst_planes[i], height, i, wiB, ctx->dst_stride);
340 		}
341 	}
342 
343 	gf_filter_pck_send(dst_pck);
344 	gf_filter_pid_drop_packet(ctx->ipid);
345 	return GF_OK;
346 }
347 
348 
vflip_configure_pid(GF_Filter * filter,GF_FilterPid * pid,Bool is_remove)349 static GF_Err vflip_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
350 {
351 	const GF_PropertyValue *p;
352 	u32 w, h, stride, pfmt;
353 	GF_Fraction sar;
354 	GF_VFlipCtx *ctx = gf_filter_get_udta(filter);
355 
356 	if (is_remove) {
357 		if (ctx->opid) {
358 			gf_filter_pid_remove(ctx->opid);
359 		}
360 		return GF_OK;
361 	}
362 	if (! gf_filter_pid_check_caps(pid))
363 		return GF_NOT_SUPPORTED;
364 
365 	if (!ctx->opid) {
366 		ctx->opid = gf_filter_pid_new(filter);
367 	}
368 	//copy properties at init or reconfig
369 	gf_filter_pid_copy_properties(ctx->opid, pid);
370 
371 	if (!ctx->ipid) {
372 		ctx->ipid = pid;
373 	}
374 	w = h = pfmt = stride = 0;
375 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
376 	if (p) w = p->value.uint;
377 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
378 	if (p) h = p->value.uint;
379 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE);
380 	if (p) stride = p->value.uint;
381 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT);
382 	if (p) pfmt = p->value.uint;
383 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR);
384 	if (p) sar = p->value.frac;
385 	else sar.den = sar.num = 1;
386 
387 	if (!w || !h || !pfmt) {
388 		ctx->passthrough = GF_TRUE;
389 		return GF_OK;
390 	}
391 
392 	if ((ctx->w == w) && (ctx->h == h) && (ctx->s_pfmt == pfmt) && (ctx->stride == stride)) {
393 		//nothing to reconfigure
394 		ctx->passthrough = GF_TRUE;
395 	} else if (ctx->mode==VFLIP_OFF) {
396 		ctx->passthrough = GF_TRUE;
397 	} else {
398 		Bool res;
399 
400 		ctx->w = w;
401 		ctx->h = h;
402 		ctx->s_pfmt = pfmt;
403 		ctx->stride = stride;
404 		ctx->dst_width  = w;
405 		ctx->dst_height = h;
406 		ctx->passthrough = GF_FALSE;
407 
408 		//get layout info for source
409 		memset(ctx->src_stride, 0, sizeof(ctx->src_stride));
410 		if (ctx->stride) ctx->src_stride[0] = ctx->stride;
411 
412 		res = gf_pixel_get_size_info(pfmt, w, h, &ctx->out_src_size, &ctx->src_stride[0], &ctx->src_stride[1], &ctx->nb_src_planes, &ctx->src_uv_height);
413 		if (!res) {
414 			GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VFlip] Failed to query source pixel format characteristics\n"));
415 			return GF_NOT_SUPPORTED;
416 		}
417 		if (ctx->nb_src_planes==3) ctx->src_stride[2] = ctx->src_stride[1];
418 		if (ctx->nb_src_planes==4) ctx->src_stride[3] = ctx->src_stride[0];
419 
420 
421 		//get layout info for dest
422 		memset(ctx->dst_stride, 0, sizeof(ctx->dst_stride));
423 		res = gf_pixel_get_size_info(pfmt, ctx->dst_width, ctx->dst_height, &ctx->out_size, &ctx->dst_stride[0], &ctx->dst_stride[1], &ctx->nb_planes, &ctx->dst_uv_height);
424 		if (!res) {
425 			GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VFlip] Failed to query output pixel format characteristics\n"));
426 			return GF_NOT_SUPPORTED;
427 		}
428 		if (ctx->nb_planes==3) ctx->dst_stride[2] = ctx->dst_stride[1];
429 		if (ctx->nb_planes==4) ctx->dst_stride[3] = ctx->dst_stride[0];
430 
431 
432 		ctx->w = w;
433 		ctx->h = h;
434 		ctx->s_pfmt = pfmt;
435 
436 		GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[VFlip] Configured output full frame size %dx%d\n", ctx->w, ctx->h));
437 
438 		ctx->line_buffer_vf = gf_realloc(ctx->line_buffer_vf, sizeof(char)*ctx->dst_stride[0] );
439 		ctx->line_buffer_hf = gf_realloc(ctx->line_buffer_hf, sizeof(char)*ctx->src_stride[0] );
440 
441 		ctx->packed_422 = GF_FALSE;
442 		switch (pfmt) {
443 		//for YUV 422, adjust to multiple of 2 on horizontal dim
444 		case GF_PIXEL_YUYV:
445 		case GF_PIXEL_YVYU:
446 		case GF_PIXEL_UYVY:
447 		case GF_PIXEL_VYUY:
448 			ctx->packed_422 = GF_TRUE;
449 			break;
450 		}
451 	}
452 
453 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->dst_width));
454 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->dst_height));
455 
456 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->dst_stride[0] ));
457 	if (ctx->nb_planes>1)
458 		gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(ctx->dst_stride[1]));
459 
460 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(sar) );
461 
462 	//an access unit corresponds to a single packet
463 	gf_filter_pid_set_framing_mode(pid, GF_TRUE);
464 	return GF_OK;
465 }
466 
vflip_finalize(GF_Filter * filter)467 void vflip_finalize(GF_Filter *filter)
468 {
469 	GF_VFlipCtx *ctx = gf_filter_get_udta(filter);
470 	if (ctx->line_buffer_vf) gf_free(ctx->line_buffer_vf);
471 	if (ctx->line_buffer_hf) gf_free(ctx->line_buffer_hf);
472 }
473 
474 
475 #define OFFS(_n)	#_n, offsetof(GF_VFlipCtx, _n)
476 static GF_FilterArgs VFlipArgs[] =
477 {
478 		{ OFFS(mode), "flip mode\n"
479 		"- off: no flipping (passthrough)\n"
480 		"- vert: vertical flip\n"
481 		"- horiz: horizontal flip\n"
482 		"- both: horizontal and vertical flip"
483 		"", GF_PROP_UINT, "vert", "off|vert|horiz|both", GF_FS_ARG_UPDATE | GF_FS_ARG_HINT_ADVANCED},
484 		{0}
485 };
486 
487 static const GF_FilterCapability VFlipCaps[] =
488 {
489 		CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
490 		CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW)
491 };
492 
493 GF_FilterRegister VFlipRegister = {
494 		.name = "vflip",
495 		GF_FS_SET_DESCRIPTION("Video flip")
496 		GF_FS_SET_HELP("Filter used to flip video frames vertically, horizontally, in both directions or no flip")
497 		.private_size = sizeof(GF_VFlipCtx),
498 		.flags = GF_FS_REG_EXPLICIT_ONLY,
499 		.args = VFlipArgs,
500 		.configure_pid = vflip_configure_pid,
501 		SETCAPS(VFlipCaps),
502 		.process = vflip_process,
503 		.finalize = vflip_finalize,
504 };
505 
506 
507 
vflip_register(GF_FilterSession * session)508 const GF_FilterRegister *vflip_register(GF_FilterSession *session)
509 {
510 	return &VFlipRegister;
511 }
512