/*****************************************************************************
- * avs.c: x264 avisynth input module
+ * avs.c: avisynth input
*****************************************************************************
- * Copyright (C) 2009 x264 project
+ * Copyright (C) 2009-2016 x264 project
*
* Authors: Steven Walters <kemuri9@gmail.com>
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
+ *
+ * This program is also available under a commercial proprietary license.
+ * For more information, contact us at licensing@x264.com.
*****************************************************************************/
-#include "muxers.h"
+#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__ )
-/* the AVS interface currently uses __declspec to link function declarations to their definitions in the dll.
- this has a side effect of preventing program execution if the avisynth dll is not found,
- so define __declspec(dllimport) to nothing and work around this */
-#undef __declspec
-#define __declspec(i)
+#define AVSC_NO_DECLSPEC
#undef EXTERN_C
-
-#ifdef HAVE_AVISYNTH_C_H
-#include <avisynth_c.h>
+#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 */
-/* YV12 support is required */
-#define AVS_INTERFACE_YV12 2
-/* when AVS supports other planar colorspaces, a workaround is required */
-#define AVS_INTERFACE_OTHER_PLANAR 5
+/* YV12 support is required, which was added in 2.5 */
+#define AVS_INTERFACE_25 2
+
+#if HAVE_SWSCALE
+#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;\
}
{
AVS_Clip *clip;
AVS_ScriptEnvironment *env;
- HMODULE library;
+ void *library;
int num_frames;
- /* declare function pointers for the utilized functions to be loaded without __declspec,
- as the avisynth header does not compensate for this type of usage */
struct
{
- const char *(__stdcall *avs_clip_get_error)( AVS_Clip *clip );
- AVS_ScriptEnvironment *(__stdcall *avs_create_script_environment)( int version );
- void (__stdcall *avs_delete_script_environment)( AVS_ScriptEnvironment *env );
- AVS_VideoFrame *(__stdcall *avs_get_frame)( AVS_Clip *clip, int n );
- int (__stdcall *avs_get_version)( AVS_Clip *clip );
- const AVS_VideoInfo *(__stdcall *avs_get_video_info)( AVS_Clip *clip );
- int (__stdcall *avs_function_exists)( AVS_ScriptEnvironment *env, const char *name );
- AVS_Value (__stdcall *avs_invoke)( AVS_ScriptEnvironment *env, const char *name,
- AVS_Value args, const char **arg_names );
- void (__stdcall *avs_release_clip)( AVS_Clip *clip );
- void (__stdcall *avs_release_value)( AVS_Value value );
- void (__stdcall *avs_release_video_frame)( AVS_VideoFrame *frame );
- AVS_Clip *(__stdcall *avs_take_clip)( AVS_Value, AVS_ScriptEnvironment *env );
+ AVSC_DECLARE_FUNC( avs_clip_get_error );
+ AVSC_DECLARE_FUNC( avs_create_script_environment );
+ AVSC_DECLARE_FUNC( avs_delete_script_environment );
+ AVSC_DECLARE_FUNC( avs_get_error );
+ AVSC_DECLARE_FUNC( avs_get_frame );
+ AVSC_DECLARE_FUNC( avs_get_video_info );
+ AVSC_DECLARE_FUNC( avs_function_exists );
+ AVSC_DECLARE_FUNC( avs_invoke );
+ AVSC_DECLARE_FUNC( avs_release_clip );
+ AVSC_DECLARE_FUNC( avs_release_value );
+ AVSC_DECLARE_FUNC( avs_release_video_frame );
+ AVSC_DECLARE_FUNC( avs_take_clip );
} func;
} avs_hnd_t;
/* load the library and functions we require from it */
-static int avs_load_library( avs_hnd_t *h )
+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 );
LOAD_AVS_FUNC( avs_create_script_environment, 0 );
LOAD_AVS_FUNC( avs_delete_script_environment, 1 );
+ LOAD_AVS_FUNC( avs_get_error, 1 );
LOAD_AVS_FUNC( avs_get_frame, 0 );
- LOAD_AVS_FUNC( avs_get_version, 0 );
LOAD_AVS_FUNC( avs_get_video_info, 0 );
LOAD_AVS_FUNC( avs_function_exists, 0 );
LOAD_AVS_FUNC( avs_invoke, 0 );
LOAD_AVS_FUNC( avs_take_clip, 0 );
return 0;
fail:
- FreeLibrary( h->library );
+ avs_close( h->library );
return -1;
}
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";
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];
}
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;
- else if( !x264_is_regular_file( fh ) )
- {
- fprintf( stderr, "avs [error]: AVS input is incompatible with non-regular file `%s'\n", psz_filename );
- return -1;
- }
+ 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 )
return -1;
- if( avs_load_library( h ) )
+ FAIL_IF_ERROR( x264_avs_load_library( h ), "failed to load avisynth\n" )
+ h->env = h->func.avs_create_script_environment( AVS_INTERFACE_25 );
+ if( h->func.avs_get_error )
{
- fprintf( stderr, "avs [error]: failed to load avisynth\n" );
- return -1;
+ const char *error = h->func.avs_get_error( h->env );
+ FAIL_IF_ERROR( error, "%s\n", error );
}
- h->env = h->func.avs_create_script_environment( AVS_INTERFACE_YV12 );
- if( !h->env )
- {
- fprintf( stderr, "avs [error]: failed to initiate avisynth\n" );
+ 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 );
if( !strcasecmp( filename_ext, "avs" ) )
{
res = h->func.avs_invoke( h->env, "Import", arg, NULL );
- if( avs_is_error( res ) )
- {
- fprintf( stderr, "avs [error]: %s\n", avs_as_string( res ) );
- return -1;
- }
+ FAIL_IF_ERROR( avs_is_error( res ), "%s\n", avs_as_string( res ) )
/* check if the user is using a multi-threaded script and apply distributor if necessary.
adapted from avisynth's vfw interface */
AVS_Value mt_test = h->func.avs_invoke( h->env, "GetMTMode", avs_new_value_bool( 0 ), NULL );
int i;
for( i = 0; filter[i]; i++ )
{
- fprintf( stderr, "avs [info]: trying %s... ", filter[i] );
+ x264_cli_log( "avs", X264_LOG_INFO, "trying %s... ", filter[i] );
if( !h->func.avs_function_exists( h->env, filter[i] ) )
{
- fprintf( stderr, "not found\n" );
+ x264_cli_printf( X264_LOG_INFO, "not found\n" );
continue;
}
if( !strncasecmp( filter[i], "FFmpegSource", 12 ) )
{
- fprintf( stderr, "indexing... " );
+ x264_cli_printf( X264_LOG_INFO, "indexing... " );
fflush( stderr );
}
res = h->func.avs_invoke( h->env, filter[i], arg, NULL );
if( !avs_is_error( res ) )
{
- fprintf( stderr, "succeeded\n" );
+ x264_cli_printf( X264_LOG_INFO, "succeeded\n" );
break;
}
- fprintf( stderr, "failed\n" );
+ x264_cli_printf( X264_LOG_INFO, "failed\n" );
}
- if( !filter[i] )
- {
- fprintf( stderr, "avs [error]: unable to find source filter to open `%s'\n", psz_filename );
- return -1;
- }
- }
- if( !avs_is_clip( res ) )
- {
- fprintf( stderr, "avs [error]: `%s' didn't return a video clip\n", psz_filename );
- return -1;
+ FAIL_IF_ERROR( !filter[i], "unable to find source filter to open `%s'\n", psz_filename )
}
+ FAIL_IF_ERROR( !avs_is_clip( res ), "`%s' didn't return a video clip\n", psz_filename )
h->clip = h->func.avs_take_clip( res, h->env );
- int avs_version = h->func.avs_get_version( h->clip );
const AVS_VideoInfo *vi = h->func.avs_get_video_info( h->clip );
- if( !avs_has_video( vi ) )
- {
- fprintf( stderr, "avs [error]: `%s' has no video data\n", psz_filename );
- return -1;
- }
+ FAIL_IF_ERROR( !avs_has_video( vi ), "`%s' has no video data\n", psz_filename )
/* if the clip is made of fields instead of frames, call weave to make them frames */
if( avs_is_field_based( vi ) )
{
- fprintf( stderr, "avs [warning]: detected fieldbased (separated) input, weaving to frames\n" );
+ x264_cli_log( "avs", X264_LOG_WARNING, "detected fieldbased (separated) input, weaving to frames\n" );
AVS_Value tmp = h->func.avs_invoke( h->env, "Weave", res, NULL );
- if( avs_is_error( tmp ) )
- {
- fprintf( stderr, "avs [error]: couldn't weave fields into frames\n" );
- return -1;
- }
+ FAIL_IF_ERROR( avs_is_error( tmp ), "couldn't weave fields into frames\n" )
res = update_clip( h, &vi, tmp, res );
info->interlaced = 1;
info->tff = avs_is_tff( vi );
}
- if( vi->width&1 || vi->height&1 )
+#if !HAVE_SWSCALE
+ /* 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 )) )
{
- fprintf( stderr, "avs [error]: input clip width or height not divisible by 2 (%dx%d)\n",
- vi->width, vi->height );
- return -1;
- }
- /* always call ConvertToYV12 to convert non YV12 planar colorspaces to YV12 when user's AVS supports them,
- as all planar colorspaces are flagged as YV12. If it is already YV12 in this case, the call does nothing */
- if( !avs_is_yv12( vi ) || avs_version >= AVS_INTERFACE_OTHER_PLANAR )
- {
- fprintf( stderr, "avs %s\n", !avs_is_yv12( vi ) ? "[warning]: converting input clip to YV12"
- : "[info]: avisynth 2.6+ detected, forcing conversion to YV12" );
- 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 );
- if( avs_is_error( res2 ) )
+
+ 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 ))) )
{
- fprintf( stderr, "avs [error]: couldn't convert input clip to YV12\n" );
- return -1;
+ // 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) )
+ {
+ 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;
- info->height = vi->height;
+ info->width = vi->width;
+ info->height = vi->height;
info->fps_num = vi->fps_numerator;
info->fps_den = vi->fps_denominator;
- h->num_frames = vi->num_frames;
- info->csp = X264_CSP_YV12;
+ h->num_frames = info->num_frames = vi->num_frames;
+ info->thread_safe = 1;
+ 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_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;
+#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 = AV_PIX_FMT_YUV411P | X264_CSP_OTHER;
+ else if( avs_is_y8( vi ) )
+ info->csp = AV_PIX_FMT_GRAY8 | X264_CSP_OTHER;
+#endif
+ else
+ info->csp = X264_CSP_NONE;
info->vfr = 0;
*p_handle = h;
return 0;
}
-static int get_frame_total( hnd_t handle )
-{
- avs_hnd_t *h = handle;
- return h->num_frames;
-}
-
-static int picture_alloc( x264_picture_t *pic, int i_csp, int i_width, int i_height )
+static int picture_alloc( cli_pic_t *pic, hnd_t handle, int csp, int width, int height )
{
- pic->img.i_csp = i_csp;
- pic->img.i_plane = 3;
- pic->param = NULL;
- pic->i_pic_struct = PIC_STRUCT_AUTO;
+ if( x264_cli_pic_alloc( pic, X264_CSP_NONE, width, height ) )
+ return -1;
+ pic->img.csp = csp;
+ const x264_cli_csp_t *cli_csp = x264_cli_get_csp( csp );
+ if( cli_csp )
+ pic->img.planes = cli_csp->planes;
+#if HAVE_SWSCALE
+ 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
+#endif
return 0;
}
-static int read_frame( x264_picture_t *p_pic, hnd_t handle, int i_frame )
+static int read_frame( cli_pic_t *pic, hnd_t handle, int i_frame )
{
- static int plane[3] = { AVS_PLANAR_Y, AVS_PLANAR_V, AVS_PLANAR_U };
+ static const int plane[3] = { AVS_PLANAR_Y, AVS_PLANAR_U, AVS_PLANAR_V };
avs_hnd_t *h = handle;
if( i_frame >= h->num_frames )
return -1;
- AVS_VideoFrame *frm = p_pic->opaque = h->func.avs_get_frame( h->clip, i_frame );
+ AVS_VideoFrame *frm = pic->opaque = h->func.avs_get_frame( h->clip, i_frame );
const char *err = h->func.avs_clip_get_error( h->clip );
- if( err )
- {
- fprintf( stderr, "avs [error]: %s occurred while reading frame %d\n", err, i_frame );
- return -1;
- }
- for( int i = 0; i < 3; i++ )
+ FAIL_IF_ERROR( err, "%s occurred while reading frame %d\n", err, i_frame )
+ for( int i = 0; i < pic->img.planes; i++ )
{
/* explicitly cast away the const attribute to avoid a warning */
- p_pic->img.plane[i] = (uint8_t*)avs_get_read_ptr_p( frm, plane[i] );
- p_pic->img.i_stride[i] = avs_get_pitch_p( frm, plane[i] );
+ pic->img.plane[i] = (uint8_t*)avs_get_read_ptr_p( frm, plane[i] );
+ pic->img.stride[i] = avs_get_pitch_p( frm, plane[i] );
}
return 0;
}
-static int release_frame( x264_picture_t *pic, hnd_t handle )
+static int release_frame( cli_pic_t *pic, hnd_t handle )
{
avs_hnd_t *h = handle;
h->func.avs_release_video_frame( pic->opaque );
return 0;
}
-static void picture_clean( x264_picture_t *pic )
+static void picture_clean( cli_pic_t *pic, hnd_t handle )
{
- memset( pic, 0, sizeof(x264_picture_t) );
+ memset( pic, 0, sizeof(cli_pic_t) );
}
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;
}
-const cli_input_t avs_input = { open_file, get_frame_total, picture_alloc, read_frame, release_frame, picture_clean, close_file };
+const cli_input_t avs_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file };