cli: Use memory-mapped input frames for yuv and y4m
[x264/x264-sandbox.git] / input / avs.c
index a8731a6..7bf6eca 100644 (file)
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * avs.c: avisynth input
  *****************************************************************************
- * Copyright (C) 2009-2011 x264 project
+ * Copyright (C) 2009-2015 x264 project
  *
  * Authors: Steven Walters <kemuri9@gmail.com>
  *
  *****************************************************************************/
 
 #include "input.h"
+#if USE_AVXSYNTH
+#include <dlfcn.h>
+#if SYS_MACOSX
+#define avs_open() dlopen( "libavxsynth.dylib", RTLD_NOW )
+#else
+#define avs_open() dlopen( "libavxsynth.so", RTLD_NOW )
+#endif
+#define avs_close dlclose
+#define avs_address dlsym
+#else
 #include <windows.h>
+#define avs_open() LoadLibraryW( L"avisynth" )
+#define avs_close FreeLibrary
+#define avs_address GetProcAddress
+#endif
 #define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "avs", __VA_ARGS__ )
 
 #define AVSC_NO_DECLSPEC
 #undef EXTERN_C
+#if USE_AVXSYNTH
+#include "extras/avxsynth_c.h"
+#else
 #include "extras/avisynth_c.h"
+#endif
 #define AVSC_DECLARE_FUNC(name) name##_func name
 
 /* AVS uses a versioned interface to control backwards compatibility */
 #include <libavutil/pixfmt.h>
 #endif
 
+/* AvxSynth doesn't have yv24, yv16, yv411, or y8, so disable them. */
+#if USE_AVXSYNTH
+#define avs_is_yv24( vi ) 0
+#define avs_is_yv16( vi ) 0
+#define avs_is_yv411( vi ) 0
+#define avs_is_y8( vi ) 0
+#endif
+
 /* maximum size of the sequence of filters to try on non script files */
 #define AVS_MAX_SEQUENCE 5
 
 #define LOAD_AVS_FUNC(name, continue_on_fail)\
 {\
-    h->func.name = (void*)GetProcAddress( h->library, #name );\
+    h->func.name = (void*)avs_address( h->library, #name );\
     if( !continue_on_fail && !h->func.name )\
         goto fail;\
 }
@@ -54,7 +80,7 @@ typedef struct
 {
     AVS_Clip *clip;
     AVS_ScriptEnvironment *env;
-    HMODULE library;
+    void *library;
     int num_frames;
     struct
     {
@@ -76,7 +102,7 @@ typedef struct
 /* load the library and functions we require from it */
 static int x264_avs_load_library( avs_hnd_t *h )
 {
-    h->library = LoadLibrary( "avisynth" );
+    h->library = avs_open();
     if( !h->library )
         return -1;
     LOAD_AVS_FUNC( avs_clip_get_error, 0 );
@@ -93,7 +119,7 @@ static int x264_avs_load_library( avs_hnd_t *h )
     LOAD_AVS_FUNC( avs_take_clip, 0 );
     return 0;
 fail:
-    FreeLibrary( h->library );
+    avs_close( h->library );
     return -1;
 }
 
@@ -101,6 +127,9 @@ fail:
 static void avs_build_filter_sequence( char *filename_ext, const char *filter[AVS_MAX_SEQUENCE+1] )
 {
     int i = 0;
+#if USE_AVXSYNTH
+    const char *all_purpose[] = { "FFVideoSource", 0 };
+#else
     const char *all_purpose[] = { "FFmpegSource2", "DSS2", "DirectShowSource", 0 };
     if( !strcasecmp( filename_ext, "avi" ) )
         filter[i++] = "AVISource";
@@ -108,6 +137,7 @@ static void avs_build_filter_sequence( char *filename_ext, const char *filter[AV
         filter[i++] = "MPEG2Source";
     if( !strcasecmp( filename_ext, "dga" ) )
         filter[i++] = "AVCSource";
+#endif
     for( int j = 0; all_purpose[j] && i < AVS_MAX_SEQUENCE; j++ )
         filter[i++] = all_purpose[j];
 }
@@ -121,13 +151,33 @@ static AVS_Value update_clip( avs_hnd_t *h, const AVS_VideoInfo **vi, AVS_Value
     return res;
 }
 
+static float get_avs_version( avs_hnd_t *h )
+{
+/* AvxSynth has its version defined starting at 4.0, even though it's based on
+   AviSynth 2.5.8. This is troublesome for get_avs_version and working around
+   the new colorspaces in 2.6.  So if AvxSynth is detected, explicitly define
+   the version as 2.58. */
+#if USE_AVXSYNTH
+    return 2.58f;
+#else
+    FAIL_IF_ERROR( !h->func.avs_function_exists( h->env, "VersionNumber" ), "VersionNumber does not exist\n" )
+    AVS_Value ver = h->func.avs_invoke( h->env, "VersionNumber", avs_new_value_array( NULL, 0 ), NULL );
+    FAIL_IF_ERROR( avs_is_error( ver ), "unable to determine avisynth version: %s\n", avs_as_error( ver ) )
+    FAIL_IF_ERROR( !avs_is_float( ver ), "VersionNumber did not return a float value\n" );
+    float ret = avs_as_float( ver );
+    h->func.avs_release_value( ver );
+    return ret;
+#endif
+}
+
 static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
 {
-    FILE *fh = fopen( psz_filename, "r" );
+    FILE *fh = x264_fopen( psz_filename, "r" );
     if( !fh )
         return -1;
-    FAIL_IF_ERROR( !x264_is_regular_file( fh ), "AVS input is incompatible with non-regular file `%s'\n", psz_filename );
+    int b_regular = x264_is_regular_file( fh );
     fclose( fh );
+    FAIL_IF_ERROR( !b_regular, "AVS input is incompatible with non-regular file `%s'\n", psz_filename );
 
     avs_hnd_t *h = malloc( sizeof(avs_hnd_t) );
     if( !h )
@@ -139,7 +189,20 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c
         const char *error = h->func.avs_get_error( h->env );
         FAIL_IF_ERROR( error, "%s\n", error );
     }
+    float avs_version = get_avs_version( h );
+    if( avs_version <= 0 )
+        return -1;
+    x264_cli_log( "avs", X264_LOG_DEBUG, "using avisynth version %.2f\n", avs_version );
+
+#ifdef _WIN32
+    /* Avisynth doesn't support Unicode filenames. */
+    char ansi_filename[MAX_PATH];
+    FAIL_IF_ERROR( !x264_ansi_filename( psz_filename, ansi_filename, MAX_PATH, 0 ), "invalid ansi filename\n" );
+    AVS_Value arg = avs_new_value_string( ansi_filename );
+#else
     AVS_Value arg = avs_new_value_string( psz_filename );
+#endif
+
     AVS_Value res;
     char *filename_ext = get_filename_extension( psz_filename );
 
@@ -203,18 +266,63 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c
         info->tff = avs_is_tff( vi );
     }
 #if !HAVE_SWSCALE
-    /* if swscale is not available, convert CSPs to yv12 */
-    if( !avs_is_yv12( vi ) )
+    /* if swscale is not available, convert the CSP if necessary */
+    FAIL_IF_ERROR( avs_version < 2.6f && (opt->output_csp == X264_CSP_I422 || opt->output_csp == X264_CSP_I444),
+                   "avisynth >= 2.6 is required for i422/i444 output\n" )
+    if( (opt->output_csp == X264_CSP_I420 && !avs_is_yv12( vi )) || (opt->output_csp == X264_CSP_I422 && !avs_is_yv16( vi )) ||
+        (opt->output_csp == X264_CSP_I444 && !avs_is_yv24( vi )) || (opt->output_csp == X264_CSP_RGB && !avs_is_rgb( vi )) )
+    {
+
+        const char *csp = opt->output_csp == X264_CSP_I420 ? "YV12" :
+                          opt->output_csp == X264_CSP_I422 ? "YV16" :
+                          opt->output_csp == X264_CSP_I444 ? "YV24" : "RGB";
+        x264_cli_log( "avs", X264_LOG_WARNING, "converting input clip to %s\n", csp );
+        FAIL_IF_ERROR( opt->output_csp < X264_CSP_I444 && (vi->width&1),
+                       "input clip width not divisible by 2 (%dx%d)\n", vi->width, vi->height )
+        FAIL_IF_ERROR( opt->output_csp == X264_CSP_I420 && info->interlaced && (vi->height&3),
+                       "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 )
+        char conv_func[14];
+        snprintf( conv_func, sizeof(conv_func), "ConvertTo%s", csp );
+        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;
+            snprintf( matrix, sizeof(matrix), "%s601", use_pc_matrix ? "PC." : "Rec" ); /* 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[3];
+        arg_arr[0] = res;
+        arg_arr[1] = avs_new_value_bool( info->interlaced );
+        arg_arr[2] = 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) )
     {
-        x264_cli_log( "avs", X264_LOG_WARNING, "converting input clip to YV12\n" );
-        FAIL_IF_ERROR( vi->width&1 || vi->height&1, "input clip width or 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 ) };
-        AVS_Value res2 = h->func.avs_invoke( h->env, "ConvertToYV12", avs_new_value_array( arg_arr, 2 ), arg_name );
-        FAIL_IF_ERROR( avs_is_error( res2 ), "couldn't convert input clip to YV12\n" )
+        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[2];
+        arg_arr[0] = res;
+        arg_arr[1] = 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 );
 
     info->width   = vi->width;
@@ -223,35 +331,33 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c
     info->fps_den = vi->fps_denominator;
     h->num_frames = info->num_frames = vi->num_frames;
     info->thread_safe = 1;
-#if HAVE_SWSCALE
     if( avs_is_rgb32( vi ) )
         info->csp = X264_CSP_BGRA | X264_CSP_VFLIP;
     else if( avs_is_rgb24( vi ) )
         info->csp = X264_CSP_BGR | X264_CSP_VFLIP;
-    else if( avs_is_yuy2( vi ) )
-        info->csp = PIX_FMT_YUYV422 | X264_CSP_OTHER;
     else if( avs_is_yv24( vi ) )
         info->csp = X264_CSP_I444;
     else if( avs_is_yv16( vi ) )
         info->csp = X264_CSP_I422;
     else if( avs_is_yv12( vi ) )
-         info->csp = X264_CSP_I420;
+        info->csp = X264_CSP_I420;
+#if HAVE_SWSCALE
+    else if( avs_is_yuy2( vi ) )
+        info->csp = AV_PIX_FMT_YUYV422 | X264_CSP_OTHER;
     else if( avs_is_yv411( vi ) )
-        info->csp = PIX_FMT_YUV411P | X264_CSP_OTHER;
+        info->csp = AV_PIX_FMT_YUV411P | X264_CSP_OTHER;
     else if( avs_is_y8( vi ) )
-        info->csp = PIX_FMT_GRAY8 | X264_CSP_OTHER;
+        info->csp = AV_PIX_FMT_GRAY8 | X264_CSP_OTHER;
+#endif
     else
         info->csp = X264_CSP_NONE;
-#else
-    info->csp = X264_CSP_I420;
-#endif
     info->vfr = 0;
 
     *p_handle = h;
     return 0;
 }
 
-static int picture_alloc( cli_pic_t *pic, int csp, int width, int height )
+static int picture_alloc( cli_pic_t *pic, hnd_t handle, int csp, int width, int height )
 {
     if( x264_cli_pic_alloc( pic, X264_CSP_NONE, width, height ) )
         return -1;
@@ -260,7 +366,7 @@ static int picture_alloc( cli_pic_t *pic, int csp, int width, int height )
     if( cli_csp )
         pic->img.planes = cli_csp->planes;
 #if HAVE_SWSCALE
-    else if( csp == (PIX_FMT_YUV411P | X264_CSP_OTHER) )
+    else if( csp == (AV_PIX_FMT_YUV411P | X264_CSP_OTHER) )
         pic->img.planes = 3;
     else
         pic->img.planes = 1; //y8 and yuy2 are one plane
@@ -293,7 +399,7 @@ static int release_frame( cli_pic_t *pic, hnd_t handle )
     return 0;
 }
 
-static void picture_clean( cli_pic_t *pic )
+static void picture_clean( cli_pic_t *pic, hnd_t handle )
 {
     memset( pic, 0, sizeof(cli_pic_t) );
 }
@@ -304,7 +410,7 @@ static int close_file( hnd_t handle )
     h->func.avs_release_clip( h->clip );
     if( h->func.avs_delete_script_environment )
         h->func.avs_delete_script_environment( h->env );
-    FreeLibrary( h->library );
+    avs_close( h->library );
     free( h );
     return 0;
 }