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 / ffmpeg video rescaler 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/setup.h>
27 
28 #ifdef GPAC_HAS_FFMPEG
29 
30 #include "ff_common.h"
31 
32 typedef struct
33 {
34 	//options
35 	GF_PropVec2i osize;
36 	u32 ofmt, scale;
37 	Double p1, p2;
38 
39 	//internal data
40 	Bool initialized;
41 
42 	GF_FilterPid *ipid, *opid;
43 	u32 w, h, stride, s_pfmt;
44 	GF_Fraction ar;
45 	Bool passthrough;
46 
47 	struct SwsContext *swscaler;
48 
49 	u32 dst_stride[5];
50 	u32 src_stride[5];
51 	u32 nb_planes, nb_src_planes, out_size, out_src_size, src_uv_height, dst_uv_height, ow, oh;
52 
53 	u32 swap_idx_1, swap_idx_2;
54 
55 } GF_FFSWScaleCtx;
56 
ffsws_process(GF_Filter * filter)57 static GF_Err ffsws_process(GF_Filter *filter)
58 {
59 	const char *data;
60 	u8 *output;
61 	u32 osize;
62 	s32 res;
63 	u8 *src_planes[5];
64 	u8 *dst_planes[5];
65 	GF_FilterPacket *dst_pck;
66 	GF_FilterFrameInterface *frame_ifce;
67 	GF_FFSWScaleCtx *ctx = gf_filter_get_udta(filter);
68 	GF_FilterPacket *pck;
69 
70 	pck = gf_filter_pid_get_packet(ctx->ipid);
71 
72 	if (!pck) {
73 		if (gf_filter_pid_is_eos(ctx->ipid)) {
74 			gf_filter_pid_set_eos(ctx->opid);
75 			return GF_EOS;
76 		}
77 		return GF_OK;
78 	}
79 
80 	if (ctx->passthrough) {
81 		gf_filter_pck_forward(pck, ctx->opid);
82 		gf_filter_pid_drop_packet(ctx->ipid);
83 		return GF_OK;
84 	}
85 	//not yet configured
86 	if (!ctx->ofmt && !ctx->ow && !ctx->oh)
87 		return GF_OK;
88 
89 	if (!ctx->swscaler) {
90 		gf_filter_pid_drop_packet(ctx->ipid);
91 		return GF_NOT_SUPPORTED;
92 	}
93 
94 	data = gf_filter_pck_get_data(pck, &osize);
95 	frame_ifce = gf_filter_pck_get_frame_interface(pck);
96 	//we may have biffer input (padding) but shall not have smaller
97 	if (osize && (ctx->out_src_size > osize) ) {
98 		GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Mismatched in source osize, expected %d got %d - stride issue ?\n", ctx->out_src_size, osize));
99 		gf_filter_pid_drop_packet(ctx->ipid);
100 		return GF_NOT_SUPPORTED;
101 	}
102 
103 	memset(src_planes, 0, sizeof(src_planes));
104 	memset(dst_planes, 0, sizeof(dst_planes));
105 	dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &output);
106 	if (!dst_pck) {
107 		gf_filter_pid_drop_packet(ctx->ipid);
108 		return GF_OUT_OF_MEM;
109 	}
110 	gf_filter_pck_merge_properties(pck, dst_pck);
111 
112 	if (data) {
113 		src_planes[0] = (u8 *) data;
114 
115 		if (ctx->nb_src_planes==1) {
116 		} else if (ctx->nb_src_planes==2) {
117 			src_planes[1] = src_planes[0] + ctx->src_stride[0]*ctx->h;
118 		} else if (ctx->nb_src_planes==3) {
119 			src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h;
120 			src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height;
121 		} else if (ctx->nb_src_planes==4) {
122 			src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h;
123 			src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height;
124 			src_planes[3] = src_planes[2] + ctx->src_stride[2] * ctx->src_uv_height;
125 		}
126 	} else if (frame_ifce && frame_ifce->get_plane) {
127 		u32 i=0;
128 		for (i=0; i<ctx->nb_src_planes; i++) {
129 			if (frame_ifce->get_plane(frame_ifce, i, (const u8 **) &src_planes[i], &ctx->src_stride[i])!=GF_OK)
130 				break;
131 		}
132 	} else {
133 		GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] No data associated with packet, not supported\n"));
134 		gf_filter_pid_drop_packet(ctx->ipid);
135 		return GF_NOT_SUPPORTED;
136 	}
137 	dst_planes[0] = output;
138 	if (ctx->nb_planes==1) {
139 	} else if (ctx->nb_planes==2) {
140 		dst_planes[1] = output + ctx->dst_stride[0] * ctx->oh;
141 	} else if (ctx->nb_planes==3) {
142 		dst_planes[1] = output + ctx->dst_stride[0] * ctx->oh;
143 		dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height;
144 	} else if (ctx->nb_planes==4) {
145 		dst_planes[1] = output + ctx->dst_stride[0] * ctx->oh;
146 		dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height;
147 		dst_planes[3] = dst_planes[2] + ctx->dst_stride[2]*ctx->dst_uv_height;
148 	}
149 
150 	//rescale the cropped frame
151 	res = sws_scale(ctx->swscaler, (const u8**) src_planes, ctx->src_stride, 0, ctx->h, dst_planes, ctx->dst_stride);
152 	if (res != ctx->oh) {
153 		GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Error during scale, expected height %d got %d\n", ctx->oh, res));
154 		gf_filter_pid_drop_packet(ctx->ipid);
155 		gf_filter_pck_discard(dst_pck);
156 		return GF_NOT_SUPPORTED;
157 	}
158 
159 	if (ctx->swap_idx_1 || ctx->swap_idx_2) {
160 		u32 i, j;
161 		for (i=0; i<ctx->h; i++) {
162 			u8 *dst = output + ctx->dst_stride[0]*i;
163 			for (j=0; j<ctx->dst_stride[0]; j+=4) {
164 				u8 tmp = dst[ctx->swap_idx_1];
165 				dst[ctx->swap_idx_1] = dst[ctx->swap_idx_2];
166 				dst[ctx->swap_idx_2] = tmp;
167 				dst += 4;
168 			}
169 		}
170 	}
171 
172 	gf_filter_pck_send(dst_pck);
173 	gf_filter_pid_drop_packet(ctx->ipid);
174 	return GF_OK;
175 }
176 
get_sws_mode(u32 mode,u32 * has_param)177 static u32 get_sws_mode(u32 mode, u32 *has_param)
178 {
179 	switch (mode) {
180 	case 0: return SWS_FAST_BILINEAR;
181 	case 1: return SWS_BILINEAR;
182 	case 2: *has_param = 2; return SWS_BICUBIC;
183 	case 3: return SWS_X;
184 	case 4: return SWS_POINT;
185 	case 5: return SWS_AREA;
186 	case 6: return SWS_BICUBLIN;
187 	case 7: *has_param = 1; return SWS_GAUSS;
188 	case 8: return SWS_SINC;
189 	case 9: *has_param = 1; return SWS_LANCZOS;
190 	case 10: return SWS_SPLINE;
191 	default: break;
192 	}
193 	*has_param = 2;
194 	return SWS_BICUBIC;
195 }
196 
ffsws_configure_pid(GF_Filter * filter,GF_FilterPid * pid,Bool is_remove)197 static GF_Err ffsws_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
198 {
199 	const GF_PropertyValue *p;
200 	u32 w, h, stride, ofmt;
201 	GF_Fraction sar;
202 	Double par[2], *par_p=NULL;
203 	GF_FFSWScaleCtx *ctx = gf_filter_get_udta(filter);
204 
205 	if (is_remove) {
206 		if (ctx->opid) {
207 			gf_filter_pid_remove(ctx->opid);
208 		}
209 		return GF_OK;
210 	}
211 	if (! gf_filter_pid_check_caps(pid))
212 		return GF_NOT_SUPPORTED;
213 
214 	if (!ctx->opid) {
215 		ctx->opid = gf_filter_pid_new(filter);
216 	}
217 
218 	if (!ctx->ipid) {
219 		ctx->ipid = pid;
220 	}
221 
222 	//if nothing is set we, consider we run as an adaptation filter, wait for caps to be set to declare output
223 	if (!ctx->ofmt && !ctx->osize.x && !ctx->osize.y)
224 		return GF_OK;
225 
226 
227 
228 	w = h = ofmt = stride = 0;
229 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
230 	if (p) w = p->value.uint;
231 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
232 	if (p) h = p->value.uint;
233 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE);
234 	if (p) stride = p->value.uint;
235 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT);
236 	if (p) ofmt = p->value.uint;
237 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR);
238 	if (p) sar = p->value.frac;
239 	else sar.den = sar.num = 1;
240 
241 	//ctx->ofmt may be 0 if the filter is instantiated dynamically, we haven't yet been called for reconfigure
242 	if (!w || !h || !ofmt) {
243 		return GF_OK;
244 	}
245 	//copy properties at init or reconfig
246 	gf_filter_pid_copy_properties(ctx->opid, ctx->ipid);
247 
248 	if (!ctx->ofmt)
249 		ctx->ofmt = ofmt;
250 
251 	ctx->passthrough = GF_FALSE;
252 
253 	ctx->ow = ctx->osize.x ? ctx->osize.x : w;
254 	ctx->oh = ctx->osize.y ? ctx->osize.y : h;
255 	if ((ctx->w == w) && (ctx->h == h) && (ctx->s_pfmt == ofmt) && (ctx->stride == stride)) {
256 		//nothing to reconfigure
257 	}
258 	//passthrough mode
259 	else if ((ctx->ow == w) && (ctx->oh == h) && (ctx->s_pfmt == ofmt) && (ofmt==ctx->ofmt) ) {
260 		memset(ctx->dst_stride, 0, sizeof(ctx->dst_stride));
261 		gf_pixel_get_size_info(ctx->ofmt, ctx->ow, ctx->oh, &ctx->out_size, &ctx->dst_stride[0], &ctx->dst_stride[1], &ctx->nb_planes, &ctx->dst_uv_height);
262 		ctx->passthrough = GF_TRUE;
263 	} else {
264 		u32 nb_par = 0;
265 		nb_par = 0;
266 		Bool res;
267 		u32 mode = get_sws_mode(ctx->scale, &nb_par);
268 		u32 ff_src_pfmt = ffmpeg_pixfmt_from_gpac(ofmt);
269 		u32 ff_dst_pfmt = ffmpeg_pixfmt_from_gpac(ctx->ofmt);
270 
271 		//get layout info for source
272 		memset(ctx->src_stride, 0, sizeof(ctx->src_stride));
273 		if (ctx->stride) ctx->src_stride[0] = ctx->stride;
274 
275 
276 		res = gf_pixel_get_size_info(ofmt, w, h, &ctx->out_src_size, &ctx->src_stride[0], &ctx->src_stride[1], &ctx->nb_src_planes, &ctx->src_uv_height);
277 		if (!res) {
278 			GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Failed to query source pixel format characteristics\n"));
279 			return GF_NOT_SUPPORTED;
280 		}
281 		if (ctx->nb_src_planes==3) ctx->src_stride[2] = ctx->src_stride[1];
282 		if (ctx->nb_src_planes==4) ctx->src_stride[3] = ctx->src_stride[0];
283 
284 		//get layout info for dest
285 		memset(ctx->dst_stride, 0, sizeof(ctx->dst_stride));
286 		res = gf_pixel_get_size_info(ctx->ofmt, ctx->ow, ctx->oh, &ctx->out_size, &ctx->dst_stride[0], &ctx->dst_stride[1], &ctx->nb_planes, &ctx->dst_uv_height);
287 		if (!res) {
288 			GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Failed to query output pixel format characteristics\n"));
289 			return GF_NOT_SUPPORTED;
290 		}
291 		if (ctx->nb_planes==3) ctx->dst_stride[2] = ctx->dst_stride[1];
292 		if (ctx->nb_planes==4) ctx->dst_stride[3] = ctx->dst_stride[0];
293 
294 
295 		if (nb_par) {
296 			if ((nb_par==1) && (ctx->p1!=GF_MAX_DOUBLE) ) {
297 				par[0] = ctx->p1;
298 				par_p = (Double *)par;
299 			} else if ((nb_par==2) && (ctx->p1!=GF_MAX_DOUBLE) && (ctx->p2!=GF_MAX_DOUBLE)) {
300 				par[0] = ctx->p1;
301 				par[1] = ctx->p2;
302 				par_p = (Double *)par;
303 			}
304 		}
305 		//create/get a swscale context
306 		ctx->swscaler = sws_getCachedContext(ctx->swscaler, w, h, ff_src_pfmt, ctx->ow, ctx->oh, ff_dst_pfmt, mode, NULL, NULL, par_p);
307 
308 		if (!ctx->swscaler) {
309 #ifndef GPAC_DISABLE_LOG
310 			Bool in_ok = sws_isSupportedInput(ff_src_pfmt);
311 			Bool out_ok = sws_isSupportedInput(ff_dst_pfmt);
312 			GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Cannot allocate context for required format - input %s output %s\n", in_ok ? "OK" : "not supported" , out_ok ? "OK" : "not supported"));
313 #endif
314 			return GF_NOT_SUPPORTED;
315 		}
316 		ctx->w = w;
317 		ctx->h = h;
318 		ctx->s_pfmt = ofmt;
319 		GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[FFSWS] Setup rescaler from %dx%d fmt %s to %dx%d fmt %s\n", w, h, gf_pixel_fmt_name(ofmt), ctx->ow, ctx->oh, gf_pixel_fmt_name(ctx->ofmt)));
320 
321 		ctx->swap_idx_1 = ctx->swap_idx_2 = 0;
322 		//if same source / dest pixel format, don'( swap UV components
323 		if (ctx->s_pfmt != ctx->ofmt) {
324 			if (ctx->ofmt==GF_PIXEL_VYUY) {
325 				ctx->swap_idx_1 = 0;
326 				ctx->swap_idx_2 = 2;
327 			}
328 			else if (ctx->ofmt==GF_PIXEL_YVYU) {
329 				ctx->swap_idx_1 = 1;
330 				ctx->swap_idx_2 = 3;
331 			}
332 		}
333 	}
334 
335 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->ow));
336 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->oh));
337 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->dst_stride[0]));
338 	if (ctx->nb_planes>1)
339 		gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(ctx->dst_stride[1]));
340 
341 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW));
342 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->ofmt));
343 	gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(sar) );
344 	return GF_OK;
345 }
346 
ffsws_finalize(GF_Filter * filter)347 static void ffsws_finalize(GF_Filter *filter)
348 {
349 	GF_FFSWScaleCtx *ctx = gf_filter_get_udta(filter);
350 	if (ctx->swscaler) sws_freeContext(ctx->swscaler);
351 	return;
352 }
353 
354 
ffsws_reconfigure_output(GF_Filter * filter,GF_FilterPid * pid)355 static GF_Err ffsws_reconfigure_output(GF_Filter *filter, GF_FilterPid *pid)
356 {
357 	const GF_PropertyValue *p;
358 	GF_FFSWScaleCtx *ctx = gf_filter_get_udta(filter);
359 	if (ctx->opid != pid) return GF_BAD_PARAM;
360 
361 	p = gf_filter_pid_caps_query(pid, GF_PROP_PID_WIDTH);
362 	if (p) ctx->osize.x = p->value.uint;
363 
364 	p = gf_filter_pid_caps_query(pid, GF_PROP_PID_HEIGHT);
365 	if (p) ctx->osize.y = p->value.uint;
366 
367 	p = gf_filter_pid_caps_query(pid, GF_PROP_PID_PIXFMT);
368 	if (p) ctx->ofmt = p->value.uint;
369 	return ffsws_configure_pid(filter, ctx->ipid, GF_FALSE);
370 }
371 
372 
373 #define OFFS(_n)	#_n, offsetof(GF_FFSWScaleCtx, _n)
374 static GF_FilterArgs FFSWSArgs[] =
375 {
376 	{ OFFS(osize), "osize of output video. When not set, input osize is used", GF_PROP_VEC2I, NULL, NULL, 0},
377 	{ OFFS(ofmt), "pixel format for output video. When not set, input format is used", GF_PROP_PIXFMT, "none", NULL, 0},
378 	{ OFFS(scale), "scaling mode - see filter info", GF_PROP_UINT, "bicubic", "fastbilinear|bilinear|bicubic|X|point|area|bicublin|gauss|sinc|lanzcos|spline", GF_FS_ARG_HINT_ADVANCED},
379 	{ OFFS(p1), "scaling algo param1 - see filter info", GF_PROP_DOUBLE, "+I", NULL, GF_FS_ARG_HINT_ADVANCED},
380 	{ OFFS(p2), "scaling algo param2 - see filter info", GF_PROP_DOUBLE, "+I", NULL, GF_FS_ARG_HINT_ADVANCED},
381 	{0}
382 };
383 
384 static const GF_FilterCapability FFSWSCaps[] =
385 {
386 	CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
387 	CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW)
388 };
389 
390 
391 GF_FilterRegister FFSWSRegister = {
392 	.name = "ffsws",
393 	.version=LIBSWSCALE_IDENT,
394 	GF_FS_SET_DESCRIPTION("FFMPEG video rescaler")
395 	GF_FS_SET_HELP("For bicubic, to tune the shape of the basis function, [-p1]() tunes f(1) and [-p2]() f´(1)\n"\
396 				"For gauss [-p1]() tunes the exponent and thus cutoff frequency\n"\
397 				"For lanczos [-p1]() tunes the width of the window function"\
398 				"\nSee FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details")
399 
400 	.private_size = sizeof(GF_FFSWScaleCtx),
401 	.args = FFSWSArgs,
402 	.configure_pid = ffsws_configure_pid,
403 	SETCAPS(FFSWSCaps),
404 	.finalize = ffsws_finalize,
405 	.process = ffsws_process,
406 	.reconfigure_output = ffsws_reconfigure_output,
407 };
408 
409 #else
410 #include <gpac/filters.h>
411 #endif //GPAC_HAS_FFMPEG
412 
ffsws_register(GF_FilterSession * session)413 const GF_FilterRegister *ffsws_register(GF_FilterSession *session)
414 {
415 #ifdef GPAC_HAS_FFMPEG
416 	FFSWSArgs[1].min_max_enum = gf_pixel_fmt_all_names();
417 	return &FFSWSRegister;
418 #else
419 	return NULL;
420 #endif
421 }
422 
423