1 /*****************************************************************************
2 * resize.c: x264 video resize filter
3 *****************************************************************************
4 * Copyright (C) 2010 Steven Walters <kemuri9@gmail.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
19 *****************************************************************************/
23 #define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, NAME, __VA_ARGS__ )
25 cli_vid_filter_t resize_filter;
27 static int full_check( video_info_t *info, x264_param_t *param )
30 required |= info->csp != param->i_csp;
31 required |= info->width != param->i_width;
32 required |= info->height != param->i_height;
37 #undef DECLARE_ALIGNED
38 #include <libswscale/swscale.h>
40 /* this function is not a part of the swscale API but is defined in swscale_internal.h */
41 const char *sws_format_name( enum PixelFormat format );
53 cli_vid_filter_t prev_filter;
58 struct SwsContext *ctx;
60 int swap_chroma; /* state of swapping chroma planes */
61 frame_prop_t dst; /* desired output properties */
62 frame_prop_t scale; /* properties of the SwsContext input */
65 static void help( int longhelp )
67 printf( " "NAME":[width,height][,sar][,fittobox][,csp][,method]\n" );
70 printf( " resizes frames based on the given criteria:\n"
71 " - resolution only: resizes and adapts sar to avoid stretching\n"
72 " - sar only: sets the sar and resizes to avoid stretching\n"
73 " - resolution and sar: resizes to given resolution and sets the sar\n"
74 " - fittobox: resizes the video based on the desired contraints\n"
75 " - width, height, both\n"
76 " - fittobox and sar: same as above except with specified sar\n"
77 " simultaneously converting to the given colorspace\n"
78 " using resizer method [\"bicubic\"]\n"
79 " - fastbilinear, bilinear, bicubic, experimental, point,\n"
80 " - area, bicublin, gauss, sinc, lanczos, spline\n" );
83 static uint32_t convert_cpu_to_flag( uint32_t cpu )
85 uint32_t swscale_cpu = 0;
86 if( cpu & X264_CPU_ALTIVEC )
87 swscale_cpu |= SWS_CPU_CAPS_ALTIVEC;
88 if( cpu & X264_CPU_MMXEXT )
89 swscale_cpu |= SWS_CPU_CAPS_MMX | SWS_CPU_CAPS_MMX2;
93 static uint32_t convert_method_to_flag( const char *name )
96 if( !strcasecmp( name, "fastbilinear" ) )
97 flag = SWS_FAST_BILINEAR;
98 else if( !strcasecmp( name, "bilinear" ) )
100 else if( !strcasecmp( name, "bicubic" ) )
102 else if( !strcasecmp( name, "experimental" ) )
104 else if( !strcasecmp( name, "point" ) )
106 else if( !strcasecmp( name, "area" ) )
108 else if( !strcasecmp( name, "bicublin" ) )
110 else if( !strcasecmp( name, "guass" ) )
112 else if( !strcasecmp( name, "sinc" ) )
114 else if( !strcasecmp( name, "lanczos" ) )
116 else if( !strcasecmp( name, "spline" ) )
123 static int convert_csp_to_pix_fmt( int csp )
125 if( csp&X264_CSP_OTHER )
126 return csp&X264_CSP_MASK;
127 switch( csp&X264_CSP_MASK )
129 case X264_CSP_I420: return PIX_FMT_YUV420P;
130 case X264_CSP_I422: return PIX_FMT_YUV422P;
131 case X264_CSP_I444: return PIX_FMT_YUV444P;
132 case X264_CSP_NV12: return PIX_FMT_NV12;
133 case X264_CSP_YV12: return PIX_FMT_YUV420P; /* specially handled via swapping chroma */
134 case X264_CSP_BGR: return PIX_FMT_BGR24;
135 case X264_CSP_BGRA: return PIX_FMT_BGRA;
136 default: return PIX_FMT_NONE;
140 static int pick_closest_supported_csp( int csp )
142 int pix_fmt = convert_csp_to_pix_fmt( csp );
145 case PIX_FMT_YUV422P:
146 case PIX_FMT_YUV422P16LE:
147 case PIX_FMT_YUV422P16BE:
148 case PIX_FMT_YUYV422:
149 case PIX_FMT_UYVY422:
150 return X264_CSP_I422;
151 case PIX_FMT_YUV444P:
152 case PIX_FMT_YUV444P16LE:
153 case PIX_FMT_YUV444P16BE:
154 return X264_CSP_I444;
155 case PIX_FMT_RGB24: // convert rgb to bgr
156 case PIX_FMT_RGB48BE:
157 case PIX_FMT_RGB48LE:
158 case PIX_FMT_RGB565BE:
159 case PIX_FMT_RGB565LE:
160 case PIX_FMT_RGB555BE:
161 case PIX_FMT_RGB555LE:
163 case PIX_FMT_BGR565BE:
164 case PIX_FMT_BGR565LE:
165 case PIX_FMT_BGR555BE:
166 case PIX_FMT_BGR555LE:
172 return X264_CSP_BGRA;
175 return X264_CSP_NV12;
177 return X264_CSP_I420;
181 static int round_dbl( double val, int precision, int truncate )
183 int ret = (int)(val / precision) * precision;
184 if( !truncate && (val - ret) >= (precision/2) ) // use the remainder if we're not truncating it
189 static int handle_opts( const char **optlist, char **opts, video_info_t *info, resizer_hnd_t *h )
191 uint32_t out_sar_w, out_sar_h;
193 char *str_width = x264_get_option( optlist[0], opts );
194 char *str_height = x264_get_option( optlist[1], opts );
195 char *str_sar = x264_get_option( optlist[2], opts );
196 char *fittobox = x264_get_option( optlist[3], opts );
197 char *str_csp = x264_get_option( optlist[4], opts );
198 int width = x264_otoi( str_width, -1 );
199 int height = x264_otoi( str_height, -1 );
202 uint32_t in_sar_w = info->sar_width;
203 uint32_t in_sar_h = info->sar_height;
207 /* output csp was specified, lookup against valid values */
209 for( csp = X264_CSP_CLI_MAX-1; x264_cli_csps[csp].name && strcasecmp( x264_cli_csps[csp].name, str_csp ); )
211 FAIL_IF_ERROR( csp == X264_CSP_NONE, "unsupported colorspace `%s'\n", str_csp );
215 /* if the input sar is currently invalid, set it to 1:1 so it can be used in math */
216 if( !in_sar_w || !in_sar_h )
217 in_sar_w = in_sar_h = 1;
220 FAIL_IF_ERROR( 2 != sscanf( str_sar, "%u:%u", &out_sar_w, &out_sar_h ) &&
221 2 != sscanf( str_sar, "%u/%u", &out_sar_w, &out_sar_h ),
222 "invalid sar `%s'\n", str_sar )
225 out_sar_w = out_sar_h = 1;
228 /* resize the video to fit the box as much as possible */
229 double box_width = width;
230 double box_height = height;
231 if( !strcasecmp( fittobox, "both" ) )
233 FAIL_IF_ERROR( box_width <= 0 || box_height <= 0, "invalid box resolution %sx%s\n",
234 x264_otos( str_width, "unset" ), x264_otos( str_height, "unset" ) )
236 else if( !strcasecmp( fittobox, "width" ) )
238 FAIL_IF_ERROR( box_width <= 0, "invalid box width `%s'\n", x264_otos( str_width, "unset" ) )
239 box_height = INT_MAX;
241 else if( !strcasecmp( fittobox, "height" ) )
243 FAIL_IF_ERROR( box_height <= 0, "invalid box height `%s'\n", x264_otos( str_height, "unset" ) )
246 else FAIL_IF_ERROR( 1, "invalid fittobox mode `%s'\n", fittobox )
248 /* we now have the requested bounding box display dimensions, now adjust them for output sar */
249 if( out_sar_w > out_sar_h ) // SAR is wide, decrease width
250 box_width *= (double)out_sar_h / out_sar_w;
251 else // SAR is thin, decrease height
252 box_height *= (double)out_sar_w / out_sar_h;
254 /* get the display resolution of the clip as it is now */
255 double d_width = info->width;
256 double d_height = info->height;
257 if( in_sar_w > in_sar_h )
258 d_width *= (double)in_sar_w / in_sar_h;
260 d_height *= (double)in_sar_h / in_sar_w;
261 /* now convert it to the coded resolution in accordance with the output sar */
262 if( out_sar_w > out_sar_h )
263 d_width *= (double)out_sar_h / out_sar_w;
265 d_height *= (double)out_sar_w / out_sar_h;
267 /* maximally fit the new coded resolution to the box */
268 double scale = X264_MIN( box_width / d_width, box_height / d_height );
269 const x264_cli_csp_t *csp = x264_cli_get_csp( h->dst_csp );
270 width = round_dbl( scale * d_width, csp->mod_width, 1 );
271 height = round_dbl( scale * d_height, csp->mod_height, 1 );
275 if( str_width || str_height )
277 FAIL_IF_ERROR( width <= 0 || height <= 0, "invalid resolution %sx%s\n",
278 x264_otos( str_width, "unset" ), x264_otos( str_height, "unset" ) )
279 if( !str_sar ) /* res only -> adjust sar */
281 /* new_sar = (new_h * old_w * old_sar_w) / (old_h * new_w * old_sar_h) */
282 uint64_t num = (uint64_t)info->width * height;
283 uint64_t den = (uint64_t)info->height * width;
284 x264_reduce_fraction64( &num, &den );
285 out_sar_w = num * in_sar_w;
286 out_sar_h = den * in_sar_h;
287 x264_reduce_fraction( &out_sar_w, &out_sar_h );
290 else if( str_sar ) /* sar only -> adjust res */
292 const x264_cli_csp_t *csp = x264_cli_get_csp( h->dst_csp );
294 height = info->height;
295 if( (out_sar_w * in_sar_h) > (out_sar_h * in_sar_w) ) // SAR got wider, decrease width
296 width = round_dbl( (double)info->width * in_sar_w * out_sar_h
297 / in_sar_h / out_sar_w, csp->mod_width, 0 );
298 else // SAR got thinner, decrease height
299 height = round_dbl( (double)info->height * in_sar_h * out_sar_w
300 / in_sar_w / out_sar_h, csp->mod_height, 0 );
304 h->dst.width = info->width;
305 h->dst.height = info->height;
311 info->sar_width = out_sar_w;
312 info->sar_height = out_sar_h;
313 h->dst.width = width;
314 h->dst.height = height;
319 static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x264_param_t *param, char *opt_string )
321 /* if called for normalizing the csp to known formats and the format is not unknown, exit */
322 if( opt_string && !strcmp( opt_string, "normcsp" ) && !(info->csp&X264_CSP_OTHER) )
324 /* if called by x264cli and nothing needs to be done, exit */
325 if( !opt_string && !full_check( info, param ) )
328 static const char *optlist[] = { "width", "height", "sar", "fittobox", "csp", "method", NULL };
329 char **opts = x264_split_options( opt_string, optlist );
330 if( !opts && opt_string )
333 resizer_hnd_t *h = calloc( 1, sizeof(resizer_hnd_t) );
338 h->dst_csp = info->csp;
339 h->dst.width = info->width;
340 h->dst.height = info->height;
341 if( !strcmp( opt_string, "normcsp" ) )
342 h->dst_csp = pick_closest_supported_csp( info->csp );
343 else if( handle_opts( optlist, opts, info, h ) )
348 h->dst_csp = param->i_csp;
349 h->dst.width = param->i_width;
350 h->dst.height = param->i_height;
352 uint32_t method = convert_method_to_flag( x264_otos( x264_get_option( optlist[5], opts ), "" ) );
353 x264_free_string_array( opts );
355 h->ctx_flags = convert_cpu_to_flag( param->cpu ) | method;
356 if( method != SWS_FAST_BILINEAR )
357 h->ctx_flags |= SWS_FULL_CHR_H_INT | SWS_ACCURATE_RND;
358 h->dst.pix_fmt = convert_csp_to_pix_fmt( h->dst_csp );
360 /* swap chroma planes for yv12 to have it become i420 */
361 h->swap_chroma = (info->csp & X264_CSP_MASK) == X264_CSP_YV12;
362 int src_pix_fmt = convert_csp_to_pix_fmt( info->csp );
364 /* confirm swscale can support this conversion */
365 FAIL_IF_ERROR( !sws_isSupportedInput( src_pix_fmt ), "input colorspace %s is not supported\n", sws_format_name( src_pix_fmt ) )
366 FAIL_IF_ERROR( !sws_isSupportedOutput( h->dst.pix_fmt ), "output colorspace %s is not supported\n", sws_format_name( h->dst.pix_fmt ) )
367 FAIL_IF_ERROR( h->dst.height != info->height && info->interlaced,
368 "swscale is not compatible with interlaced vertical resizing\n" )
369 /* confirm that the desired resolution meets the colorspace requirements */
370 const x264_cli_csp_t *csp = x264_cli_get_csp( h->dst_csp );
371 FAIL_IF_ERROR( h->dst.width % csp->mod_width || h->dst.height % csp->mod_height,
372 "resolution %dx%d is not compliant with colorspace %s\n", h->dst.width, h->dst.height, csp->name )
374 if( h->dst.width != info->width || h->dst.height != info->height )
375 x264_cli_log( NAME, X264_LOG_INFO, "resizing to %dx%d\n", h->dst.width, h->dst.height );
376 if( h->dst.pix_fmt != src_pix_fmt )
377 x264_cli_log( NAME, X264_LOG_WARNING, "converting from %s to %s\n",
378 sws_format_name( src_pix_fmt ), sws_format_name( h->dst.pix_fmt ) );
379 h->dst_csp |= info->csp & X264_CSP_VFLIP; // preserve vflip
380 /* finished initing, overwrite values */
381 info->csp = h->dst_csp;
382 info->width = h->dst.width;
383 info->height = h->dst.height;
385 h->prev_filter = *filter;
386 h->prev_hnd = *handle;
388 *filter = resize_filter;
393 static int check_resizer( resizer_hnd_t *h, cli_pic_t *in )
395 frame_prop_t input_prop = { in->img.width, in->img.height, convert_csp_to_pix_fmt( in->img.csp ) };
396 if( !memcmp( &input_prop, &h->scale, sizeof(frame_prop_t) ) )
400 sws_freeContext( h->ctx );
401 x264_cli_log( NAME, X264_LOG_WARNING, "stream properties changed at pts %"PRId64"\n", in->pts );
403 h->scale = input_prop;
404 if( !h->buffer_allocated )
406 if( x264_cli_pic_alloc( &h->buffer, h->dst_csp, h->dst.width, h->dst.height ) )
408 h->buffer_allocated = 1;
410 h->ctx = sws_getContext( h->scale.width, h->scale.height, h->scale.pix_fmt, h->dst.width,
411 h->dst.height, h->dst.pix_fmt, h->ctx_flags, NULL, NULL, NULL );
412 FAIL_IF_ERROR( !h->ctx, "swscale init failed\n" )
416 static int get_frame( hnd_t handle, cli_pic_t *output, int frame )
418 resizer_hnd_t *h = handle;
419 if( h->prev_filter.get_frame( h->prev_hnd, output, frame ) )
421 if( check_resizer( h, output ) )
425 sws_scale( h->ctx, (const uint8_t* const*)output->img.plane, output->img.stride,
426 0, output->img.height, h->buffer.img.plane, h->buffer.img.stride );
427 output->img = h->buffer.img; /* copy img data */
430 output->img.csp = h->dst_csp;
432 XCHG( uint8_t*, output->img.plane[1], output->img.plane[2] );
437 static int release_frame( hnd_t handle, cli_pic_t *pic, int frame )
439 resizer_hnd_t *h = handle;
440 return h->prev_filter.release_frame( h->prev_hnd, pic, frame );
443 static void free_filter( hnd_t handle )
445 resizer_hnd_t *h = handle;
446 h->prev_filter.free( h->prev_hnd );
448 sws_freeContext( h->ctx );
449 if( h->buffer_allocated )
450 x264_cli_pic_clean( &h->buffer );
454 #else /* no swscale */
455 static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x264_param_t *param, char *opt_string )
460 ret = full_check( info, param );
463 if( !strcmp( opt_string, "normcsp" ) )
464 ret = info->csp & X264_CSP_OTHER;
469 /* pass if nothing needs to be done, otherwise fail */
470 FAIL_IF_ERROR( ret, "not compiled with swscale support\n" )
475 #define get_frame NULL
476 #define release_frame NULL
477 #define free_filter NULL
478 #define convert_csp_to_pix_fmt(x) (x & X264_CSP_MASK)
482 cli_vid_filter_t resize_filter = { NAME, help, init, get_frame, release_frame, free_filter, NULL };