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).
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
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;
}
int width;
int height;
int pix_fmt;
+ int range;
} frame_prop_t;
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 */
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 )
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;
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 */
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 */
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 );
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);
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 */
}
/* 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;
"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 );
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) );
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 */
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 */
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;
uint32_t timebase_num;
uint32_t timebase_den;
int vfr;
- int width;
} video_info_t;
/* image data type used by x264cli */
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 )
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 )
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" ) &&
0
};
+static const char * const range_names[] = { "auto", "tv", "pc", 0 };
+
typedef struct
{
int mod;
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",
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" );
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";
{ "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 },
{ "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}
};
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;
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;
#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, ¶m->vui.b_fullrange ), "Unknown range `%s'\n", optarg );
+ input_opt.output_range = param->vui.b_fullrange += RANGE_AUTO;
+ break;
default:
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;
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;
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. */
#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