YUV range detection and support for x264CLI
authorSteven Walters <kemuri9@gmail.com>
Sun, 6 Nov 2011 17:48:30 +0000 (09:48 -0800)
committerJason Garrett-Glaser <jason@x264.com>
Thu, 1 Dec 2011 20:27:47 +0000 (12:27 -0800)
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).

configure
filters/video/resize.c
input/avs.c
input/ffms.c
input/input.h
input/lavf.c
x264.c
x264cli.h

index f338b5f..4e474ec 100755 (executable)
--- a/configure
+++ b/configure
@@ -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
index 8768726..fac9f5e 100644 (file)
@@ -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;
index 0169746..5fc3006 100644 (file)
@@ -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 );
 
index ebfdb68..626b783 100644 (file)
@@ -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 */
index bd7e421..ecd1f38 100644 (file)
@@ -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 */
index 1073ee5..4b35422 100644 (file)
@@ -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" ) &&
diff --git a/x264.c b/x264.c
index b40b873..54359c6 100644 (file)
--- a/x264.c
+++ b/x264.c
@@ -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. */
index 29e961c..cc44a1a 100644 (file)
--- a/x264cli.h
+++ b/x264cli.h
@@ -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