Commit 6767f967 authored by Steven Walters's avatar Steven Walters Committed by Fiona Glaser
Browse files

YUV range detection and support for x264CLI

Two new options: --input-range and --range.
--input-range forces the range of the input in case of misdetection; auto by default.
-- range sets the range of the output; x264cli will convert if necessary, TV by default.
--fullrange is now removed as a CLI option (but the libx264 API is unchanged).
parent f9a4c4d9
......@@ -795,7 +795,7 @@ if [ "$lavf" = "auto" ] ; then
fi
if [ "$ffms" = "auto" ] ; then
ffms_major="2"; ffms_minor="14"; ffms_micro="0"; ffms_bump="0"
ffms_major="2"; ffms_minor="16"; ffms_micro="2"; ffms_bump="0"
ffms="no"
if ${cross_prefix}pkg-config --exists ffms2 2>/dev/null; then
......
......@@ -32,9 +32,10 @@ cli_vid_filter_t resize_filter;
static int full_check( video_info_t *info, x264_param_t *param )
{
int required = 0;
required |= info->csp != param->i_csp;
required |= info->width != param->i_width;
required |= info->height != param->i_height;
required |= info->csp != param->i_csp;
required |= info->width != param->i_width;
required |= info->height != param->i_height;
required |= info->fullrange != param->vui.b_fullrange;
return required;
}
......@@ -49,6 +50,7 @@ typedef struct
int width;
int height;
int pix_fmt;
int range;
} frame_prop_t;
typedef struct
......@@ -59,6 +61,7 @@ typedef struct
cli_pic_t buffer;
int buffer_allocated;
int dst_csp;
int input_range;
struct SwsContext *ctx;
uint32_t ctx_flags;
/* state of swapping chroma planes pre and post resize */
......@@ -343,27 +346,6 @@ static int handle_opts( const char **optlist, char **opts, video_info_t *info, r
return 0;
}
static int handle_jpeg( int *format )
{
switch( *format )
{
case PIX_FMT_YUVJ420P:
*format = PIX_FMT_YUV420P;
return 1;
case PIX_FMT_YUVJ422P:
*format = PIX_FMT_YUV422P;
return 1;
case PIX_FMT_YUVJ444P:
*format = PIX_FMT_YUV444P;
return 1;
case PIX_FMT_YUVJ440P:
*format = PIX_FMT_YUV440P;
return 1;
default:
return 0;
}
}
static int x264_init_sws_context( resizer_hnd_t *h )
{
if( !h->ctx )
......@@ -373,27 +355,22 @@ static int x264_init_sws_context( resizer_hnd_t *h )
return -1;
/* set flags that will not change */
int dst_format = h->dst.pix_fmt;
int dst_range = handle_jpeg( &dst_format );
av_set_int( h->ctx, "sws_flags", h->ctx_flags );
av_set_int( h->ctx, "dstw", h->dst.width );
av_set_int( h->ctx, "dsth", h->dst.height );
av_set_int( h->ctx, "dst_format", dst_format );
av_set_int( h->ctx, "dst_range", dst_range ); /* FIXME: use the correct full range value */
av_set_int( h->ctx, "dst_format", h->dst.pix_fmt );
av_set_int( h->ctx, "dst_range", h->dst.range );
}
int src_format = h->scale.pix_fmt;
int src_range = handle_jpeg( &src_format );
av_set_int( h->ctx, "srcw", h->scale.width );
av_set_int( h->ctx, "srch", h->scale.height );
av_set_int( h->ctx, "src_format", src_format );
av_set_int( h->ctx, "src_range", src_range ); /* FIXME: use the correct full range value */
av_set_int( h->ctx, "src_format", h->scale.pix_fmt );
av_set_int( h->ctx, "src_range", h->scale.range );
/* FIXME: use the correct full range values
* FIXME: use the correct matrix coefficients (only YUV -> RGB conversions are supported) */
/* FIXME: use the correct matrix coefficients (only YUV -> RGB conversions are supported) */
sws_setColorspaceDetails( h->ctx,
sws_getCoefficients( SWS_CS_DEFAULT ), src_range,
sws_getCoefficients( SWS_CS_DEFAULT ), av_get_int( h->ctx, "dst_range", NULL ),
sws_getCoefficients( SWS_CS_DEFAULT ), h->scale.range,
sws_getCoefficients( SWS_CS_DEFAULT ), h->dst.range,
0, 1<<16, 1<<16 );
return sws_init_context( h->ctx, NULL, NULL ) < 0;
......@@ -401,7 +378,7 @@ static int x264_init_sws_context( resizer_hnd_t *h )
static int check_resizer( resizer_hnd_t *h, cli_pic_t *in )
{
frame_prop_t input_prop = { in->img.width, in->img.height, convert_csp_to_pix_fmt( in->img.csp ) };
frame_prop_t input_prop = { in->img.width, in->img.height, convert_csp_to_pix_fmt( in->img.csp ), h->input_range };
if( !memcmp( &input_prop, &h->scale, sizeof(frame_prop_t) ) )
return 0;
/* also warn if the resizer was initialized after the first frame */
......@@ -440,6 +417,7 @@ static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x2
h->dst_csp = info->csp;
h->dst.width = info->width;
h->dst.height = info->height;
h->dst.range = info->fullrange; // maintain input range
if( !strcmp( opt_string, "normcsp" ) )
{
/* only in normalization scenarios is the input capable of changing properties */
......@@ -459,6 +437,7 @@ static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x2
h->dst_csp = param->i_csp;
h->dst.width = param->i_width;
h->dst.height = param->i_height;
h->dst.range = param->vui.b_fullrange; // change to libx264's range
}
h->ctx_flags = convert_method_to_flag( x264_otos( x264_get_option( optlist[5], opts ), "" ) );
x264_free_string_array( opts );
......@@ -467,6 +446,7 @@ static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x2
h->ctx_flags |= SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP | SWS_ACCURATE_RND;
h->dst.pix_fmt = convert_csp_to_pix_fmt( h->dst_csp );
h->scale = h->dst;
h->input_range = info->fullrange;
/* swap chroma planes if YV12/YV16/YV24 is involved, as libswscale works with I420/I422/I444 */
int src_csp = info->csp & (X264_CSP_MASK | X264_CSP_OTHER);
......@@ -500,6 +480,9 @@ static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x2
if( h->dst.pix_fmt != src_pix_fmt )
x264_cli_log( NAME, X264_LOG_WARNING, "converting from %s to %s\n",
av_get_pix_fmt_name( src_pix_fmt ), av_get_pix_fmt_name( h->dst.pix_fmt ) );
else if( h->dst.range != h->input_range )
x264_cli_log( NAME, X264_LOG_WARNING, "converting range from %s to %s\n",
h->input_range ? "PC" : "TV", h->dst.range ? "PC" : "TV" );
h->dst_csp |= info->csp & X264_CSP_VFLIP; // preserve vflip
/* if the input is not variable, initialize the context */
......@@ -511,9 +494,10 @@ static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x2
}
/* finished initing, overwrite values */
info->csp = h->dst_csp;
info->width = h->dst.width;
info->height = h->dst.height;
info->csp = h->dst_csp;
info->width = h->dst.width;
info->height = h->dst.height;
info->fullrange = h->dst.range;
h->prev_filter = *filter;
h->prev_hnd = *handle;
......
......@@ -235,14 +235,40 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c
"input clip height not divisible by 4 (%dx%d)\n", vi->width, vi->height )
FAIL_IF_ERROR( (opt->output_csp == X264_CSP_I420 || info->interlaced) && (vi->height&1),
"input clip height not divisible by 2 (%dx%d)\n", vi->width, vi->height )
const char *arg_name[2] = { NULL, "interlaced" };
AVS_Value arg_arr[2] = { res, avs_new_value_bool( info->interlaced ) };
char conv_func[14] = { "ConvertTo" };
strcat( conv_func, csp );
AVS_Value res2 = h->func.avs_invoke( h->env, conv_func, avs_new_value_array( arg_arr, 2 ), arg_name );
char matrix[7] = "";
int arg_count = 2;
/* if doing a rgb <-> yuv conversion then range is handled via 'matrix'. though it's only supported in 2.56+ */
if( avs_version >= 2.56f && ((opt->output_csp == X264_CSP_RGB && avs_is_yuv( vi )) || (opt->output_csp != X264_CSP_RGB && avs_is_rgb( vi ))) )
{
// if converting from yuv, then we specify the matrix for the input, otherwise use the output's.
int use_pc_matrix = avs_is_yuv( vi ) ? opt->input_range == RANGE_PC : opt->output_range == RANGE_PC;
strcpy( matrix, use_pc_matrix ? "PC." : "Rec" );
strcat( matrix, "601" ); /* FIXME: use correct coefficients */
arg_count++;
// notification that the input range has changed to the desired one
opt->input_range = opt->output_range;
}
const char *arg_name[] = { NULL, "interlaced", "matrix" };
AVS_Value arg_arr[] = { res, avs_new_value_bool( info->interlaced ), avs_new_value_string( matrix ) };
AVS_Value res2 = h->func.avs_invoke( h->env, conv_func, avs_new_value_array( arg_arr, arg_count ), arg_name );
FAIL_IF_ERROR( avs_is_error( res2 ), "couldn't convert input clip to %s\n", csp )
res = update_clip( h, &vi, res2, res );
}
/* if swscale is not available, change the range if necessary. This only applies to YUV-based CSPs however */
if( avs_is_yuv( vi ) && opt->output_range != RANGE_AUTO && ((opt->input_range == RANGE_PC) != opt->output_range) )
{
const char *levels = opt->output_range ? "TV->PC" : "PC->TV";
x264_cli_log( "avs", X264_LOG_WARNING, "performing %s conversion\n", levels );
AVS_Value arg_arr[] = { res, avs_new_value_string( levels ) };
const char *arg_name[] = { NULL, "levels" };
AVS_Value res2 = h->func.avs_invoke( h->env, "ColorYUV", avs_new_value_array( arg_arr, 2 ), arg_name );
FAIL_IF_ERROR( avs_is_error( res2 ), "couldn't convert range: %s\n", avs_as_error( res2 ) )
res = update_clip( h, &vi, res2, res );
// notification that the input range has changed to the desired one
opt->input_range = opt->output_range;
}
#endif
h->func.avs_release_value( res );
......
......@@ -65,6 +65,18 @@ static int FFMS_CC update_progress( int64_t current, int64_t total, void *privat
return 0;
}
/* handle the deprecated jpeg pixel formats */
static int handle_jpeg( int csp, int *fullrange )
{
switch( csp )
{
case PIX_FMT_YUVJ420P: *fullrange = 1; return PIX_FMT_YUV420P;
case PIX_FMT_YUVJ422P: *fullrange = 1; return PIX_FMT_YUV422P;
case PIX_FMT_YUVJ444P: *fullrange = 1; return PIX_FMT_YUV444P;
default: return csp;
}
}
static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
ffms_hnd_t *h = calloc( 1, sizeof(ffms_hnd_t) );
......@@ -119,11 +131,13 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c
const FFMS_Frame *frame = FFMS_GetFrame( h->video_source, 0, &e );
FAIL_IF_ERROR( !frame, "could not read frame 0\n" )
info->fullrange = 0;
info->width = frame->EncodedWidth;
info->height = frame->EncodedHeight;
info->csp = frame->EncodedPixelFormat | X264_CSP_OTHER;
info->csp = handle_jpeg( frame->EncodedPixelFormat, &info->fullrange ) | X264_CSP_OTHER;
info->interlaced = frame->InterlacedFrame;
info->tff = frame->TopFieldFirst;
info->fullrange |= frame->ColorRange == FFMS_CR_JPEG;
/* ffms timestamps are in milliseconds. ffms also uses int64_ts for timebase,
* so we need to reduce large timebases to prevent overflow */
......
......@@ -42,6 +42,8 @@ typedef struct
int seek;
int progress;
int output_csp; /* convert to this csp, if applicable */
int output_range; /* user desired output range */
int input_range; /* user override input range */
} cli_input_opt_t;
/* properties of the source given by the demuxer */
......@@ -50,6 +52,8 @@ typedef struct
int csp; /* colorspace of the input */
uint32_t fps_num;
uint32_t fps_den;
int fullrange; /* has 2^bit_depth-1 instead of 219*2^(bit_depth-8) ranges (YUV only) */
int width;
int height;
int interlaced;
int num_frames;
......@@ -60,7 +64,6 @@ typedef struct
uint32_t timebase_num;
uint32_t timebase_den;
int vfr;
int width;
} video_info_t;
/* image data type used by x264cli */
......
......@@ -46,6 +46,18 @@ typedef struct
av_init_packet( pkt );\
}
/* handle the deprecated jpeg pixel formats */
static int handle_jpeg( int csp, int *fullrange )
{
switch( csp )
{
case PIX_FMT_YUVJ420P: *fullrange = 1; return PIX_FMT_YUV420P;
case PIX_FMT_YUVJ422P: *fullrange = 1; return PIX_FMT_YUV422P;
case PIX_FMT_YUVJ444P: *fullrange = 1; return PIX_FMT_YUV444P;
default: return csp;
}
}
static int read_frame_internal( cli_pic_t *p_pic, lavf_hnd_t *h, int i_frame, video_info_t *info )
{
if( h->first_pic && !info )
......@@ -101,14 +113,16 @@ static int read_frame_internal( cli_pic_t *p_pic, lavf_hnd_t *h, int i_frame, vi
memcpy( p_pic->img.stride, frame.linesize, sizeof(p_pic->img.stride) );
memcpy( p_pic->img.plane, frame.data, sizeof(p_pic->img.plane) );
p_pic->img.height = c->height;
p_pic->img.csp = c->pix_fmt | X264_CSP_OTHER;
int is_fullrange = 0;
p_pic->img.width = c->width;
p_pic->img.height = c->height;
p_pic->img.csp = handle_jpeg( c->pix_fmt, &is_fullrange ) | X264_CSP_OTHER;
if( info )
{
info->fullrange = is_fullrange;
info->interlaced = frame.interlaced_frame;
info->tff = frame.top_field_first;
info->tff = frame.top_field_first;
}
if( h->vfr_input )
......@@ -186,6 +200,7 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c
info->num_frames = h->lavf->streams[i]->nb_frames;
info->sar_height = c->sample_aspect_ratio.den;
info->sar_width = c->sample_aspect_ratio.num;
info->fullrange |= c->color_range == AVCOL_RANGE_JPEG;
/* avisynth stores rgb data vertically flipped. */
if( !strcasecmp( get_filename_extension( psz_filename ), "avs" ) &&
......
......@@ -136,6 +136,8 @@ static const char * const output_csp_names[] =
0
};
static const char * const range_names[] = { "auto", "tv", "pc", 0 };
typedef struct
{
int mod;
......@@ -735,9 +737,8 @@ static void help( x264_param_t *defaults, int longhelp )
H2( " --videoformat <string> Specify video format [\"%s\"]\n"
" - component, pal, ntsc, secam, mac, undef\n",
strtable_lookup( x264_vidformat_names, defaults->vui.i_vidformat ) );
H2( " --fullrange <string> Specify full range samples setting [\"%s\"]\n"
" - off, on\n",
strtable_lookup( x264_fullrange_names, defaults->vui.b_fullrange ) );
H2( " --range <string> Specify color range [\"%s\"]\n"
" - %s\n", range_names[0], stringify_names( buf, range_names ) );
H2( " --colorprim <string> Specify color primaries [\"%s\"]\n"
" - undef, bt709, bt470m, bt470bg\n"
" smpte170m, smpte240m, film\n",
......@@ -773,6 +774,8 @@ static void help( x264_param_t *defaults, int longhelp )
H1( " --output-csp <string> Specify output colorspace [\"%s\"]\n"
" - %s\n", output_csp_names[0], stringify_names( buf, output_csp_names ) );
H1( " --input-depth <integer> Specify input bit depth for raw input\n" );
H1( " --input-range <string> Specify input color range [\"%s\"]\n"
" - %s\n", range_names[0], stringify_names( buf, range_names ) );
H1( " --input-res <intxint> Specify input resolution (width x height)\n" );
H1( " --index <string> Filename for input index file\n" );
H0( " --sar width:height Specify Sample Aspect Ratio\n" );
......@@ -854,7 +857,9 @@ typedef enum
OPT_INPUT_CSP,
OPT_INPUT_DEPTH,
OPT_DTS_COMPRESSION,
OPT_OUTPUT_CSP
OPT_OUTPUT_CSP,
OPT_INPUT_RANGE,
OPT_RANGE
} OptionsOPT;
static char short_options[] = "8A:B:b:f:hI:i:m:o:p:q:r:t:Vvw";
......@@ -991,7 +996,7 @@ static struct option long_options[] =
{ "cqm8p", required_argument, NULL, 0 },
{ "overscan", required_argument, NULL, 0 },
{ "videoformat", required_argument, NULL, 0 },
{ "fullrange", required_argument, NULL, 0 },
{ "range", required_argument, NULL, OPT_RANGE },
{ "colorprim", required_argument, NULL, 0 },
{ "transfer", required_argument, NULL, 0 },
{ "colormatrix", required_argument, NULL, 0 },
......@@ -1014,6 +1019,7 @@ static struct option long_options[] =
{ "input-depth", required_argument, NULL, OPT_INPUT_DEPTH },
{ "dts-compress", no_argument, NULL, OPT_DTS_COMPRESSION },
{ "output-csp", required_argument, NULL, OPT_OUTPUT_CSP },
{ "input-range", required_argument, NULL, OPT_INPUT_RANGE },
{0, 0, 0, 0}
};
......@@ -1177,6 +1183,9 @@ static int init_vid_filters( char *sequence, hnd_t *handle, video_info_t *info,
else if( output_csp == X264_CSP_RGB && (csp < X264_CSP_BGR || csp > X264_CSP_RGB) )
param->i_csp = X264_CSP_RGB;
param->i_csp |= info->csp & X264_CSP_HIGH_DEPTH;
/* if the output range is not forced, assign it to the input one now */
if( param->vui.b_fullrange == RANGE_AUTO )
param->vui.b_fullrange = info->fullrange;
if( x264_init_vid_filter( "resize", handle, &filter, info, param, NULL ) )
return -1;
......@@ -1238,6 +1247,7 @@ static int parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt )
memset( &input_opt, 0, sizeof(cli_input_opt_t) );
memset( &output_opt, 0, sizeof(cli_output_opt_t) );
input_opt.bit_depth = 8;
input_opt.input_range = input_opt.output_range = param->vui.b_fullrange = RANGE_AUTO;
int output_csp = defaults.i_csp;
opt->b_progress = 1;
......@@ -1403,6 +1413,14 @@ static int parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt )
#endif
param->i_csp = output_csp = output_csp_fix[output_csp];
break;
case OPT_INPUT_RANGE:
FAIL_IF_ERROR( parse_enum_value( optarg, range_names, &input_opt.input_range ), "Unknown input range `%s'\n", optarg )
input_opt.input_range += RANGE_AUTO;
break;
case OPT_RANGE:
FAIL_IF_ERROR( parse_enum_value( optarg, range_names, &param->vui.b_fullrange ), "Unknown range `%s'\n", optarg );
input_opt.output_range = param->vui.b_fullrange += RANGE_AUTO;
break;
default:
generic_option:
{
......@@ -1453,10 +1471,11 @@ generic_option:
video_info_t info = {0};
char demuxername[5];
/* set info flags to param flags to be overwritten by demuxer as necessary. */
/* set info flags to be overwritten by demuxer as necessary. */
info.csp = param->i_csp;
info.fps_num = param->i_fps_num;
info.fps_den = param->i_fps_den;
info.fullrange = input_opt.input_range == RANGE_PC;
info.interlaced = param->b_interlaced;
info.sar_width = param->vui.i_sar_width;
info.sar_height = param->vui.i_sar_height;
......@@ -1541,6 +1560,8 @@ generic_option:
info.interlaced = param->b_interlaced;
info.tff = param->b_tff;
}
if( input_opt.input_range != RANGE_AUTO )
info.fullrange = input_opt.input_range;
if( init_vid_filters( vid_filters, &opt->hin, &info, param, output_csp ) )
return -1;
......@@ -1572,6 +1593,15 @@ generic_option:
x264_cli_log( "x264", X264_LOG_WARNING, "input appears to be interlaced, but not compiled with interlaced support\n" );
#endif
}
/* if the user never specified the output range and the input is now rgb, default it to pc */
int csp = param->i_csp & X264_CSP_MASK;
if( csp >= X264_CSP_BGR && csp <= X264_CSP_RGB )
{
if( input_opt.output_range == RANGE_AUTO )
param->vui.b_fullrange = RANGE_PC;
/* otherwise fail if they specified tv */
FAIL_IF_ERROR( !param->vui.b_fullrange, "RGB must be PC range" )
}
/* Automatically reduce reference frame count to match the user's target level
* if the user didn't explicitly set a reference frame count. */
......
......@@ -72,4 +72,11 @@ if( cond )\
#define FAIL_IF_ERR( cond, name, ... ) RETURN_IF_ERR( cond, name, -1, __VA_ARGS__ )
typedef enum
{
RANGE_AUTO = -1,
RANGE_TV,
RANGE_PC
} range_enum;
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment