x264 source for verification 2026-05-22

This commit is contained in:
2026-05-22 16:45:04 +08:00
commit 4647f166e5
270 changed files with 166522 additions and 0 deletions

564
input/avs.c Normal file
View File

@@ -0,0 +1,564 @@
/*****************************************************************************
* avs.c: avisynth input
*****************************************************************************
* Copyright (C) 2009-2025 x264 project
*
* Authors: Steven Walters <kemuri9@gmail.com>
* Anton Mitrofanov <BugMaster@narod.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "input.h"
#if SYS_WINDOWS || SYS_CYGWIN
#include <windows.h>
#define avs_open() LoadLibraryW( L"avisynth" )
#define avs_close FreeLibrary
#define avs_address GetProcAddress
#else
#include <dlfcn.h>
#if SYS_MACOSX
#define avs_open() dlopen( "libavisynth.dylib", RTLD_NOW )
#else
#define avs_open() dlopen( "libavisynth.so", RTLD_NOW )
#endif
#define avs_close dlclose
#define avs_address dlsym
#endif
#define AVSC_NO_DECLSPEC
#undef EXTERN_C
#include "extras/avisynth_c.h"
#define AVSC_DECLARE_FUNC(name) name##_func name
#define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "avs", __VA_ARGS__ )
/* AVS uses a versioned interface to control backwards compatibility */
/* YV12 support is required, which was added in 2.5 */
#define AVS_INTERFACE_25 2
#if HAVE_SWSCALE
#include <libavutil/pixfmt.h>
#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*)avs_address( h->library, #name );\
if( !continue_on_fail && !h->func.name )\
goto fail;\
}
#define LOAD_AVS_FUNC_ALIAS(name, alias, continue_on_fail)\
{\
if( !h->func.name )\
h->func.name = (void*)avs_address( h->library, alias );\
if( !continue_on_fail && !h->func.name )\
goto fail;\
}
typedef struct
{
AVS_Clip *clip;
AVS_ScriptEnvironment *env;
void *library;
int num_frames;
struct
{
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 );
AVSC_DECLARE_FUNC( avs_is_yv24 );
AVSC_DECLARE_FUNC( avs_is_yv16 );
AVSC_DECLARE_FUNC( avs_is_yv12 );
AVSC_DECLARE_FUNC( avs_is_yv411 );
AVSC_DECLARE_FUNC( avs_is_y8 );
AVSC_DECLARE_FUNC( avs_get_pitch_p );
AVSC_DECLARE_FUNC( avs_get_read_ptr_p );
// AviSynth+ extension
AVSC_DECLARE_FUNC( avs_is_rgb48 );
AVSC_DECLARE_FUNC( avs_is_rgb64 );
AVSC_DECLARE_FUNC( avs_is_yuv444p16 );
AVSC_DECLARE_FUNC( avs_is_yuv422p16 );
AVSC_DECLARE_FUNC( avs_is_yuv420p16 );
AVSC_DECLARE_FUNC( avs_is_y16 );
AVSC_DECLARE_FUNC( avs_is_444 );
AVSC_DECLARE_FUNC( avs_is_422 );
AVSC_DECLARE_FUNC( avs_is_420 );
AVSC_DECLARE_FUNC( avs_is_y );
} func;
} avs_hnd_t;
/* load the library and functions we require from it */
static int custom_avs_load_library( avs_hnd_t *h )
{
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_video_info, 0 );
LOAD_AVS_FUNC( avs_function_exists, 0 );
LOAD_AVS_FUNC( avs_invoke, 0 );
LOAD_AVS_FUNC( avs_release_clip, 0 );
LOAD_AVS_FUNC( avs_release_value, 0 );
LOAD_AVS_FUNC( avs_release_video_frame, 0 );
LOAD_AVS_FUNC( avs_take_clip, 0 );
LOAD_AVS_FUNC( avs_is_yv24, 1 );
LOAD_AVS_FUNC( avs_is_yv16, 1 );
LOAD_AVS_FUNC( avs_is_yv12, 1 );
LOAD_AVS_FUNC( avs_is_yv411, 1 );
LOAD_AVS_FUNC( avs_is_y8, 1 );
LOAD_AVS_FUNC( avs_get_pitch_p, 1 );
LOAD_AVS_FUNC( avs_get_read_ptr_p, 1 );
// AviSynth+ extension
LOAD_AVS_FUNC( avs_is_rgb48, 1 );
LOAD_AVS_FUNC_ALIAS( avs_is_rgb48, "_avs_is_rgb48@4", 1 );
LOAD_AVS_FUNC( avs_is_rgb64, 1 );
LOAD_AVS_FUNC_ALIAS( avs_is_rgb64, "_avs_is_rgb64@4", 1 );
LOAD_AVS_FUNC( avs_is_yuv444p16, 1 );
LOAD_AVS_FUNC( avs_is_yuv422p16, 1 );
LOAD_AVS_FUNC( avs_is_yuv420p16, 1 );
LOAD_AVS_FUNC( avs_is_y16, 1 );
LOAD_AVS_FUNC( avs_is_444, 1 );
LOAD_AVS_FUNC( avs_is_422, 1 );
LOAD_AVS_FUNC( avs_is_420, 1 );
LOAD_AVS_FUNC( avs_is_y, 1 );
return 0;
fail:
avs_close( h->library );
h->library = NULL;
return -1;
}
#define AVS_IS_YV24( vi ) (h->func.avs_is_yv24 ? h->func.avs_is_yv24( vi ) : avs_is_yv24( vi ))
#define AVS_IS_YV16( vi ) (h->func.avs_is_yv16 ? h->func.avs_is_yv16( vi ) : avs_is_yv16( vi ))
#define AVS_IS_YV12( vi ) (h->func.avs_is_yv12 ? h->func.avs_is_yv12( vi ) : avs_is_yv12( vi ))
#define AVS_IS_YV411( vi ) (h->func.avs_is_yv411 ? h->func.avs_is_yv411( vi ) : avs_is_yv411( vi ))
#define AVS_IS_Y8( vi ) (h->func.avs_is_y8 ? h->func.avs_is_y8( vi ) : avs_is_y8( vi ))
#define AVS_GET_PITCH_P( p, plane ) (h->func.avs_get_pitch_p ? h->func.avs_get_pitch_p( p, plane ) : avs_get_pitch_p( p, plane ))
#define AVS_GET_READ_PTR_P( p, plane ) (h->func.avs_get_read_ptr_p ? h->func.avs_get_read_ptr_p( p, plane ) : avs_get_read_ptr_p( p, plane ))
#define AVS_IS_AVISYNTHPLUS (h->func.avs_is_420 && h->func.avs_is_422 && h->func.avs_is_444)
#define AVS_IS_420( vi ) (h->func.avs_is_420 ? h->func.avs_is_420( vi ) : AVS_IS_YV12( vi ))
#define AVS_IS_422( vi ) (h->func.avs_is_422 ? h->func.avs_is_422( vi ) : AVS_IS_YV16( vi ))
#define AVS_IS_444( vi ) (h->func.avs_is_444 ? h->func.avs_is_444( vi ) : AVS_IS_YV24( vi ))
#define AVS_IS_RGB48( vi ) (h->func.avs_is_rgb48 && h->func.avs_is_rgb48( vi ))
#define AVS_IS_RGB64( vi ) (h->func.avs_is_rgb64 && h->func.avs_is_rgb64( vi ))
#define AVS_IS_YUV420P16( vi ) (h->func.avs_is_yuv420p16 && h->func.avs_is_yuv420p16( vi ))
#define AVS_IS_YUV422P16( vi ) (h->func.avs_is_yuv422p16 && h->func.avs_is_yuv422p16( vi ))
#define AVS_IS_YUV444P16( vi ) (h->func.avs_is_yuv444p16 && h->func.avs_is_yuv444p16( vi ))
#define AVS_IS_Y( vi ) (h->func.avs_is_y ? h->func.avs_is_y( vi ) : AVS_IS_Y8( vi ))
#define AVS_IS_Y16( vi ) (h->func.avs_is_y16 && h->func.avs_is_y16( vi ))
/* generate a filter sequence to try based on the filename extension */
static void avs_build_filter_sequence( char *filename_ext, const char *filter[AVS_MAX_SEQUENCE+1] )
{
int i = 0;
#if SYS_WINDOWS || SYS_CYGWIN
const char *all_purpose[] = { "FFmpegSource2", "DSS2", "DirectShowSource", 0 };
if( !strcasecmp( filename_ext, "avi" ) )
filter[i++] = "AVISource";
if( !strcasecmp( filename_ext, "d2v" ) )
filter[i++] = "MPEG2Source";
if( !strcasecmp( filename_ext, "dga" ) )
filter[i++] = "AVCSource";
#else
const char *all_purpose[] = { "FFVideoSource", 0 };
#endif
for( int j = 0; all_purpose[j] && i < AVS_MAX_SEQUENCE; j++ )
filter[i++] = all_purpose[j];
}
static AVS_Value update_clip( avs_hnd_t *h, const AVS_VideoInfo **vi, AVS_Value res, AVS_Value release )
{
h->func.avs_release_clip( h->clip );
h->clip = h->func.avs_take_clip( res, h->env );
h->func.avs_release_value( release );
*vi = h->func.avs_get_video_info( h->clip );
return res;
}
static float get_avs_version( avs_hnd_t *h )
{
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;
}
#ifdef _WIN32
static char *utf16_to_ansi( const wchar_t *utf16 )
{
BOOL invalid;
int len = WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, utf16, -1, NULL, 0, NULL, &invalid );
if( len && !invalid )
{
char *ansi = malloc( len * sizeof( char ) );
if( ansi )
{
if( WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, utf16, -1, ansi, len, NULL, &invalid ) && !invalid )
return ansi;
free( ansi );
}
}
return NULL;
}
static char *utf8_to_ansi( const char *filename )
{
char *ansi = NULL;
wchar_t *filename_utf16 = x264_utf8_to_utf16( filename );
if( filename_utf16 )
{
/* Check if the filename already is valid ANSI. */
if( !(ansi = utf16_to_ansi( filename_utf16 )) )
{
/* Check for a legacy 8.3 short filename. */
int len = GetShortPathNameW( filename_utf16, NULL, 0 );
if( len )
{
wchar_t *short_utf16 = malloc( len * sizeof( wchar_t ) );
if( short_utf16 )
{
if( GetShortPathNameW( filename_utf16, short_utf16, len ) )
ansi = utf16_to_ansi( short_utf16 );
free( short_utf16 );
}
}
}
free( filename_utf16 );
}
return ansi;
}
#endif
static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
FILE *fh = x264_fopen( psz_filename, "r" );
if( !fh )
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 = calloc( 1, sizeof(avs_hnd_t) );
if( !h )
return -1;
FAIL_IF_ERROR( custom_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 )
{
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 = utf8_to_ansi( psz_filename );
FAIL_IF_ERROR( !ansi_filename, "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 );
#ifdef _WIN32
free( ansi_filename );
#endif
FAIL_IF_ERROR( avs_is_error( res ), "%s\n", avs_as_error( 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 mt_mode = avs_is_int( mt_test ) ? avs_as_int( mt_test ) : 0;
h->func.avs_release_value( mt_test );
if( mt_mode > 0 && mt_mode < 5 )
{
AVS_Value temp = h->func.avs_invoke( h->env, "Distributor", res, NULL );
h->func.avs_release_value( res );
res = temp;
}
}
else /* non script file */
{
/* cycle through known source filters to find one that works */
const char *filter[AVS_MAX_SEQUENCE+1] = { 0 };
avs_build_filter_sequence( filename_ext, filter );
int i;
for( i = 0; filter[i]; i++ )
{
x264_cli_log( "avs", X264_LOG_INFO, "trying %s... ", filter[i] );
if( !h->func.avs_function_exists( h->env, filter[i] ) )
{
x264_cli_printf( X264_LOG_INFO, "not found\n" );
continue;
}
if( !strncasecmp( filter[i], "FFmpegSource", 12 ) )
{
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 ) )
{
x264_cli_printf( X264_LOG_INFO, "succeeded\n" );
break;
}
x264_cli_printf( X264_LOG_INFO, "failed\n" );
}
#ifdef _WIN32
free( ansi_filename );
#endif
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 );
const AVS_VideoInfo *vi = h->func.avs_get_video_info( h->clip );
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 ) )
{
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 );
FAIL_IF_ERROR( avs_is_error( tmp ), "couldn't weave fields into frames: %s\n", avs_as_error( tmp ) );
res = update_clip( h, &vi, tmp, res );
info->interlaced = 1;
info->tff = avs_is_tff( vi );
}
#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_I400 || opt->output_csp == X264_CSP_I422 || opt->output_csp == X264_CSP_I444),
"avisynth >= 2.6 is required for i400/i422/i444 output\n" );
if( (opt->output_csp == X264_CSP_I400 && !AVS_IS_Y( vi )) ||
(opt->output_csp == X264_CSP_I420 && !AVS_IS_420( vi )) ||
(opt->output_csp == X264_CSP_I422 && !AVS_IS_422( vi )) ||
(opt->output_csp == X264_CSP_I444 && !AVS_IS_444( vi )) ||
(opt->output_csp == X264_CSP_RGB && !avs_is_rgb( vi )) )
{
const char *csp;
if( AVS_IS_AVISYNTHPLUS )
{
csp = opt->output_csp == X264_CSP_I400 ? "Y" :
opt->output_csp == X264_CSP_I420 ? "YUV420" :
opt->output_csp == X264_CSP_I422 ? "YUV422" :
opt->output_csp == X264_CSP_I444 ? "YUV444" :
"RGB";
}
else
{
csp = opt->output_csp == X264_CSP_I400 ? "Y8" :
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 );
if( opt->output_csp != X264_CSP_I400 )
{
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[16];
snprintf( conv_func, sizeof(conv_func), "ConvertTo%s", csp );
AVS_Value arg_arr[3];
const char *arg_name[3];
int arg_count = 1;
arg_arr[0] = res;
arg_name[0] = NULL;
if( opt->output_csp != X264_CSP_I400 )
{
arg_arr[arg_count] = avs_new_value_bool( info->interlaced );
arg_name[arg_count] = "interlaced";
arg_count++;
}
/* if doing a rgb <-> yuv conversion then range is handled via 'matrix'. though it's only supported in 2.56+ */
char matrix[7];
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_arr[arg_count] = avs_new_value_string( matrix );
arg_name[arg_count] = "matrix";
arg_count++;
// notification that the input range has changed to the desired one
opt->input_range = opt->output_range;
}
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: %s\n", csp, avs_as_error( res2 ) );
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->fps_num = vi->fps_numerator;
info->fps_den = vi->fps_denominator;
h->num_frames = info->num_frames = vi->num_frames;
info->thread_safe = 1;
if( AVS_IS_RGB64( vi ) )
info->csp = X264_CSP_BGRA | X264_CSP_VFLIP | X264_CSP_HIGH_DEPTH;
else if( avs_is_rgb32( vi ) )
info->csp = X264_CSP_BGRA | X264_CSP_VFLIP;
else if( AVS_IS_RGB48( vi ) )
info->csp = X264_CSP_BGR | X264_CSP_VFLIP | X264_CSP_HIGH_DEPTH;
else if( avs_is_rgb24( vi ) )
info->csp = X264_CSP_BGR | X264_CSP_VFLIP;
else if( AVS_IS_YUV444P16( vi ) )
info->csp = X264_CSP_I444 | X264_CSP_HIGH_DEPTH;
else if( AVS_IS_YV24( vi ) )
info->csp = X264_CSP_I444;
else if( AVS_IS_YUV422P16( vi ) )
info->csp = X264_CSP_I422 | X264_CSP_HIGH_DEPTH;
else if( AVS_IS_YV16( vi ) )
info->csp = X264_CSP_I422;
else if( AVS_IS_YUV420P16( vi ) )
info->csp = X264_CSP_I420 | X264_CSP_HIGH_DEPTH;
else if( AVS_IS_YV12( vi ) )
info->csp = X264_CSP_I420;
else if( AVS_IS_Y16( vi ) )
info->csp = X264_CSP_I400 | X264_CSP_HIGH_DEPTH;
else if( AVS_IS_Y8( vi ) )
info->csp = X264_CSP_I400;
else if( avs_is_yuy2( vi ) )
info->csp = X264_CSP_YUYV;
#if HAVE_SWSCALE
else if( AVS_IS_YV411( vi ) )
info->csp = AV_PIX_FMT_YUV411P | X264_CSP_OTHER;
#endif
else
{
AVS_Value pixel_type = h->func.avs_invoke( h->env, "PixelType", res, NULL );
const char *pixel_type_name = avs_is_string( pixel_type ) ? avs_as_string( pixel_type ) : "unknown";
FAIL_IF_ERROR( 1, "not supported pixel type: %s\n", pixel_type_name );
}
info->vfr = 0;
*p_handle = h;
return 0;
}
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;
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( cli_pic_t *pic, hnd_t handle, int i_frame )
{
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 = pic->opaque = h->func.avs_get_frame( h->clip, i_frame );
const char *err = h->func.avs_clip_get_error( h->clip );
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 */
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( 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( cli_pic_t *pic, hnd_t handle )
{
memset( pic, 0, sizeof(cli_pic_t) );
}
static int close_file( hnd_t handle )
{
avs_hnd_t *h = handle;
if( h->func.avs_release_clip && h->clip )
h->func.avs_release_clip( h->clip );
if( h->func.avs_delete_script_environment && h->env )
h->func.avs_delete_script_environment( h->env );
if( h->library )
avs_close( h->library );
free( h );
return 0;
}
const cli_input_t avs_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file };

223
input/ffms.c Normal file
View File

@@ -0,0 +1,223 @@
/*****************************************************************************
* ffms.c: ffmpegsource input
*****************************************************************************
* Copyright (C) 2009-2025 x264 project
*
* Authors: Mike Gurlitz <mike.gurlitz@gmail.com>
* Steven Walters <kemuri9@gmail.com>
* Henrik Gramner <henrik@gramner.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "input.h"
#include <ffms.h>
#undef DECLARE_ALIGNED
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "ffms", __VA_ARGS__ )
#define PROGRESS_LENGTH 36
typedef struct
{
FFMS_VideoSource *video_source;
FFMS_Track *track;
int reduce_pts;
int vfr_input;
int num_frames;
int64_t time;
} ffms_hnd_t;
static int FFMS_CC update_progress( int64_t current, int64_t total, void *private )
{
int64_t *update_time = private;
int64_t oldtime = *update_time;
int64_t newtime = x264_mdate();
if( oldtime && newtime - oldtime < UPDATE_INTERVAL )
return 0;
*update_time = newtime;
char buf[PROGRESS_LENGTH+5+1];
snprintf( buf, sizeof(buf), "ffms [info]: indexing input file [%.1f%%]", 100.0 * current / total );
fprintf( stderr, "%-*s\r", PROGRESS_LENGTH, buf+5 );
x264_cli_set_console_title( buf );
fflush( stderr );
return 0;
}
/* handle the deprecated jpeg pixel formats */
static int handle_jpeg( int csp, int *fullrange )
{
switch( csp )
{
case AV_PIX_FMT_YUVJ420P: *fullrange = 1; return AV_PIX_FMT_YUV420P;
case AV_PIX_FMT_YUVJ422P: *fullrange = 1; return AV_PIX_FMT_YUV422P;
case AV_PIX_FMT_YUVJ444P: *fullrange = 1; return AV_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) );
if( !h )
return -1;
FFMS_Init( 0, 1 );
FFMS_ErrorInfo e;
e.BufferSize = 0;
int seekmode = opt->seek ? FFMS_SEEK_NORMAL : FFMS_SEEK_LINEAR_NO_RW;
FFMS_Index *idx = NULL;
if( opt->index_file )
{
x264_struct_stat index_s, input_s;
if( !x264_stat( opt->index_file, &index_s ) && !x264_stat( psz_filename, &input_s ) && input_s.st_mtime < index_s.st_mtime )
{
idx = FFMS_ReadIndex( opt->index_file, &e );
if( idx && FFMS_IndexBelongsToFile( idx, psz_filename, &e ) )
{
FFMS_DestroyIndex( idx );
idx = NULL;
}
}
}
if( !idx )
{
FFMS_Indexer *indexer = FFMS_CreateIndexer( psz_filename, &e );
FAIL_IF_ERROR( !indexer, "could not create indexer\n" );
if( opt->progress )
FFMS_SetProgressCallback( indexer, update_progress, &h->time );
idx = FFMS_DoIndexing2( indexer, FFMS_IEH_ABORT, &e );
fprintf( stderr, "%*c", PROGRESS_LENGTH+1, '\r' );
FAIL_IF_ERROR( !idx, "could not create index\n" );
if( opt->index_file && FFMS_WriteIndex( opt->index_file, idx, &e ) )
x264_cli_log( "ffms", X264_LOG_WARNING, "could not write index file\n" );
}
int trackno = FFMS_GetFirstTrackOfType( idx, FFMS_TYPE_VIDEO, &e );
if( trackno >= 0 )
h->video_source = FFMS_CreateVideoSource( psz_filename, trackno, idx, 1, seekmode, &e );
FFMS_DestroyIndex( idx );
FAIL_IF_ERROR( trackno < 0, "could not find video track\n" );
FAIL_IF_ERROR( !h->video_source, "could not create video source\n" );
const FFMS_VideoProperties *videop = FFMS_GetVideoProperties( h->video_source );
info->num_frames = h->num_frames = videop->NumFrames;
info->sar_height = videop->SARDen;
info->sar_width = videop->SARNum;
info->fps_den = videop->FPSDenominator;
info->fps_num = videop->FPSNumerator;
h->vfr_input = info->vfr;
/* ffms is thread unsafe as it uses a single frame buffer for all frame requests */
info->thread_safe = 0;
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 = 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 */
if( h->vfr_input )
{
h->track = FFMS_GetTrackFromVideo( h->video_source );
const FFMS_TrackTimeBase *timebase = FFMS_GetTimeBase( h->track );
int64_t timebase_num = timebase->Num;
int64_t timebase_den = timebase->Den * 1000;
h->reduce_pts = 0;
while( timebase_num > UINT32_MAX || timebase_den > INT32_MAX )
{
timebase_num >>= 1;
timebase_den >>= 1;
h->reduce_pts++;
}
info->timebase_num = timebase_num;
info->timebase_den = timebase_den;
}
*p_handle = h;
return 0;
}
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;
pic->img.csp = csp;
pic->img.planes = 4;
return 0;
}
static int read_frame( cli_pic_t *pic, hnd_t handle, int i_frame )
{
ffms_hnd_t *h = handle;
if( i_frame >= h->num_frames )
return -1;
FFMS_ErrorInfo e;
e.BufferSize = 0;
const FFMS_Frame *frame = FFMS_GetFrame( h->video_source, i_frame, &e );
FAIL_IF_ERROR( !frame, "could not read frame %d \n", i_frame );
memcpy( pic->img.stride, frame->Linesize, sizeof(pic->img.stride) );
memcpy( pic->img.plane, frame->Data, sizeof(pic->img.plane) );
int is_fullrange = 0;
pic->img.width = frame->EncodedWidth;
pic->img.height = frame->EncodedHeight;
pic->img.csp = handle_jpeg( frame->EncodedPixelFormat, &is_fullrange ) | X264_CSP_OTHER;
if( h->vfr_input )
{
const FFMS_FrameInfo *info = FFMS_GetFrameInfo( h->track, i_frame );
FAIL_IF_ERROR( info->PTS == AV_NOPTS_VALUE, "invalid timestamp. "
"Use --force-cfr and specify a framerate with --fps\n" );
pic->pts = info->PTS >> h->reduce_pts;
pic->duration = 0;
}
return 0;
}
static void picture_clean( cli_pic_t *pic, hnd_t handle )
{
memset( pic, 0, sizeof(cli_pic_t) );
}
static int close_file( hnd_t handle )
{
ffms_hnd_t *h = handle;
FFMS_DestroyVideoSource( h->video_source );
free( h );
return 0;
}
const cli_input_t ffms_input = { open_file, picture_alloc, read_frame, NULL, picture_clean, close_file };

274
input/input.c Normal file
View File

@@ -0,0 +1,274 @@
/*****************************************************************************
* input.c: common input functions
*****************************************************************************
* Copyright (C) 2010-2025 x264 project
*
* Authors: Steven Walters <kemuri9@gmail.com>
* Henrik Gramner <henrik@gramner.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "input.h"
#ifdef _WIN32
#include <io.h>
#elif HAVE_MMAP
#include <sys/mman.h>
#include <unistd.h>
#endif
const x264_cli_csp_t x264_cli_csps[] = {
[X264_CSP_I400] = { "i400", 1, { 1 }, { 1 }, 1, 1 },
[X264_CSP_I420] = { "i420", 3, { 1, .5, .5 }, { 1, .5, .5 }, 2, 2 },
[X264_CSP_I422] = { "i422", 3, { 1, .5, .5 }, { 1, 1, 1 }, 2, 1 },
[X264_CSP_I444] = { "i444", 3, { 1, 1, 1 }, { 1, 1, 1 }, 1, 1 },
[X264_CSP_YV12] = { "yv12", 3, { 1, .5, .5 }, { 1, .5, .5 }, 2, 2 },
[X264_CSP_YV16] = { "yv16", 3, { 1, .5, .5 }, { 1, 1, 1 }, 2, 1 },
[X264_CSP_YV24] = { "yv24", 3, { 1, 1, 1 }, { 1, 1, 1 }, 1, 1 },
[X264_CSP_NV12] = { "nv12", 2, { 1, 1 }, { 1, .5 }, 2, 2 },
[X264_CSP_NV21] = { "nv21", 2, { 1, 1 }, { 1, .5 }, 2, 2 },
[X264_CSP_NV16] = { "nv16", 2, { 1, 1 }, { 1, 1 }, 2, 1 },
[X264_CSP_YUYV] = { "yuyv", 1, { 2 }, { 1 }, 2, 1 },
[X264_CSP_UYVY] = { "uyvy", 1, { 2 }, { 1 }, 2, 1 },
[X264_CSP_BGR] = { "bgr", 1, { 3 }, { 1 }, 1, 1 },
[X264_CSP_BGRA] = { "bgra", 1, { 4 }, { 1 }, 1, 1 },
[X264_CSP_RGB] = { "rgb", 1, { 3 }, { 1 }, 1, 1 },
};
int x264_cli_csp_is_invalid( int csp )
{
int csp_mask = csp & X264_CSP_MASK;
return csp_mask <= X264_CSP_NONE || csp_mask >= X264_CSP_CLI_MAX ||
csp_mask == X264_CSP_V210 || csp & X264_CSP_OTHER;
}
int x264_cli_csp_depth_factor( int csp )
{
if( x264_cli_csp_is_invalid( csp ) )
return 0;
return (csp & X264_CSP_HIGH_DEPTH) ? 2 : 1;
}
int64_t x264_cli_pic_plane_size( int csp, int width, int height, int plane )
{
int csp_mask = csp & X264_CSP_MASK;
if( x264_cli_csp_is_invalid( csp ) || plane < 0 || plane >= x264_cli_csps[csp_mask].planes )
return 0;
int64_t size = (int64_t)width * height;
size *= x264_cli_csps[csp_mask].width[plane] * x264_cli_csps[csp_mask].height[plane];
size *= x264_cli_csp_depth_factor( csp );
return size;
}
int64_t x264_cli_pic_size( int csp, int width, int height )
{
if( x264_cli_csp_is_invalid( csp ) )
return 0;
int64_t size = 0;
int csp_mask = csp & X264_CSP_MASK;
for( int i = 0; i < x264_cli_csps[csp_mask].planes; i++ )
size += x264_cli_pic_plane_size( csp, width, height, i );
return size;
}
static int cli_pic_init_internal( cli_pic_t *pic, int csp, int width, int height, int align, int alloc )
{
memset( pic, 0, sizeof(cli_pic_t) );
int csp_mask = csp & X264_CSP_MASK;
if( x264_cli_csp_is_invalid( csp ) )
pic->img.planes = 0;
else
pic->img.planes = x264_cli_csps[csp_mask].planes;
pic->img.csp = csp;
pic->img.width = width;
pic->img.height = height;
for( int i = 0; i < pic->img.planes; i++ )
{
int stride = width * x264_cli_csps[csp_mask].width[i];
stride *= x264_cli_csp_depth_factor( csp );
stride = ALIGN( stride, align );
pic->img.stride[i] = stride;
if( alloc )
{
int64_t size = (int64_t)(height * x264_cli_csps[csp_mask].height[i]) * stride;
pic->img.plane[i] = x264_malloc( size );
if( !pic->img.plane[i] )
return -1;
}
}
return 0;
}
int x264_cli_pic_alloc( cli_pic_t *pic, int csp, int width, int height )
{
return cli_pic_init_internal( pic, csp, width, height, 1, 1 );
}
int x264_cli_pic_alloc_aligned( cli_pic_t *pic, int csp, int width, int height )
{
return cli_pic_init_internal( pic, csp, width, height, NATIVE_ALIGN, 1 );
}
int x264_cli_pic_init_noalloc( cli_pic_t *pic, int csp, int width, int height )
{
return cli_pic_init_internal( pic, csp, width, height, 1, 0 );
}
void x264_cli_pic_clean( cli_pic_t *pic )
{
for( int i = 0; i < pic->img.planes; i++ )
x264_free( pic->img.plane[i] );
memset( pic, 0, sizeof(cli_pic_t) );
}
const x264_cli_csp_t *x264_cli_get_csp( int csp )
{
if( x264_cli_csp_is_invalid( csp ) )
return NULL;
return x264_cli_csps + (csp&X264_CSP_MASK);
}
/* Functions for handling memory-mapped input frames */
int x264_cli_mmap_init( cli_mmap_t *h, FILE *fh )
{
#if defined(_WIN32) || HAVE_MMAP
int fd = fileno( fh );
x264_struct_stat file_stat;
if( !x264_fstat( fd, &file_stat ) )
{
h->file_size = file_stat.st_size;
#ifdef _WIN32
HANDLE osfhandle = (HANDLE)_get_osfhandle( fd );
if( osfhandle != INVALID_HANDLE_VALUE )
{
SYSTEM_INFO si;
GetSystemInfo( &si );
h->page_mask = si.dwPageSize - 1;
h->align_mask = si.dwAllocationGranularity - 1;
h->prefetch_virtual_memory = (void*)GetProcAddress( GetModuleHandleW( L"kernel32.dll" ), "PrefetchVirtualMemory" );
h->process_handle = GetCurrentProcess();
h->map_handle = CreateFileMappingW( osfhandle, NULL, PAGE_READONLY, 0, 0, NULL );
return !h->map_handle;
}
#elif HAVE_MMAP && defined(_SC_PAGESIZE)
h->align_mask = sysconf( _SC_PAGESIZE ) - 1;
h->fd = fd;
return h->align_mask < 0 || fd < 0;
#endif
}
#endif
return -1;
}
/* Third-party filters such as swscale can overread the input buffer which may result
* in segfaults. We have to pad the buffer size as a workaround to avoid that. */
#define MMAP_PADDING 64
void *x264_cli_mmap( cli_mmap_t *h, int64_t offset, int64_t size )
{
#if defined(_WIN32) || HAVE_MMAP
uint8_t *base;
int align = offset & h->align_mask;
if( offset < 0 || size < 0 || (uint64_t)size > (SIZE_MAX - MMAP_PADDING - align) )
return NULL;
offset -= align;
size += align;
#ifdef _WIN32
/* If the padding crosses a page boundary we need to increase the mapping size. */
size_t padded_size = (-size & h->page_mask) < MMAP_PADDING ? size + MMAP_PADDING : size;
if( (uint64_t)offset + padded_size > (uint64_t)h->file_size )
{
/* It's not possible to do the POSIX mmap() remapping trick on Windows, so if the padding crosses a
* page boundary past the end of the file we have to copy the entire frame into a padded buffer. */
if( (base = MapViewOfFile( h->map_handle, FILE_MAP_READ, (uint64_t)offset >> 32, offset, size )) )
{
uint8_t *buf = NULL;
HANDLE anon_map = CreateFileMappingW( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, (uint64_t)padded_size >> 32, padded_size, NULL );
if( anon_map )
{
if( (buf = MapViewOfFile( anon_map, FILE_MAP_WRITE, 0, 0, 0 )) )
{
buf += align;
memcpy( buf, base + align, size - align );
}
CloseHandle( anon_map );
}
UnmapViewOfFile( base );
return buf;
}
}
else if( (base = MapViewOfFile( h->map_handle, FILE_MAP_READ, (uint64_t)offset >> 32, offset, padded_size )) )
{
/* PrefetchVirtualMemory() is only available on Windows 8 and newer. */
if( h->prefetch_virtual_memory )
{
struct { void *addr; size_t size; } mem_range = { base, size };
h->prefetch_virtual_memory( h->process_handle, 1, &mem_range, 0 );
}
return base + align;
}
#else
size_t padded_size = size + MMAP_PADDING;
if( (base = mmap( NULL, padded_size, PROT_READ, MAP_PRIVATE, h->fd, offset )) != MAP_FAILED )
{
/* Ask the OS to readahead pages. This improves performance whereas
* forcing page faults by manually accessing every page does not.
* Some systems have implemented madvise() but not posix_madvise()
* and vice versa, so check both to see if either is available. */
#ifdef MADV_WILLNEED
madvise( base, size, MADV_WILLNEED );
#elif defined(POSIX_MADV_WILLNEED)
posix_madvise( base, size, POSIX_MADV_WILLNEED );
#endif
/* Remap the file mapping of any padding that crosses a page boundary past the end of
* the file into a copy of the last valid page to prevent reads from invalid memory. */
size_t aligned_size = (padded_size - 1) & ~h->align_mask;
if( offset + aligned_size >= h->file_size )
mmap( base + aligned_size, padded_size - aligned_size, PROT_READ, MAP_PRIVATE|MAP_FIXED, h->fd, (offset + size - 1) & ~h->align_mask );
return base + align;
}
#endif
#endif
return NULL;
}
int x264_cli_munmap( cli_mmap_t *h, void *addr, int64_t size )
{
#if defined(_WIN32) || HAVE_MMAP
void *base = (void*)((intptr_t)addr & ~h->align_mask);
#ifdef _WIN32
return !UnmapViewOfFile( base );
#else
if( size < 0 || size > (SIZE_MAX - MMAP_PADDING - ((intptr_t)addr - (intptr_t)base)) )
return -1;
return munmap( base, size + MMAP_PADDING + (intptr_t)addr - (intptr_t)base );
#endif
#endif
return -1;
}
void x264_cli_mmap_close( cli_mmap_t *h )
{
#ifdef _WIN32
CloseHandle( h->map_handle );
#endif
}

158
input/input.h Normal file
View File

@@ -0,0 +1,158 @@
/*****************************************************************************
* input.h: file input
*****************************************************************************
* Copyright (C) 2003-2025 x264 project
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Loren Merritt <lorenm@u.washington.edu>
* Steven Walters <kemuri9@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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.
*****************************************************************************/
#ifndef X264_INPUT_H
#define X264_INPUT_H
#include "x264cli.h"
#ifdef _WIN32
#include <windows.h>
#endif
/* options that are used by only some demuxers */
typedef struct
{
char *index_file;
char *format;
char *resolution;
char *colorspace;
int bit_depth;
char *timebase;
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 */
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;
uint32_t sar_width;
uint32_t sar_height;
int tff;
int thread_safe; /* demuxer is thread_input safe */
uint32_t timebase_num;
uint32_t timebase_den;
int vfr;
} video_info_t;
/* image data type used by x264cli */
typedef struct
{
int csp; /* colorspace */
int width; /* width of the picture */
int height; /* height of the picture */
int planes; /* number of planes */
uint8_t *plane[4]; /* pointers for each plane */
int stride[4]; /* strides for each plane */
} cli_image_t;
typedef struct
{
cli_image_t img;
int64_t pts; /* input pts */
int64_t duration; /* frame duration - used for vfr */
void *opaque; /* opaque handle */
} cli_pic_t;
typedef struct
{
int (*open_file)( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt );
int (*picture_alloc)( cli_pic_t *pic, hnd_t handle, int csp, int width, int height );
int (*read_frame)( cli_pic_t *pic, hnd_t handle, int i_frame );
int (*release_frame)( cli_pic_t *pic, hnd_t handle );
void (*picture_clean)( cli_pic_t *pic, hnd_t handle );
int (*close_file)( hnd_t handle );
} cli_input_t;
extern const cli_input_t raw_input;
extern const cli_input_t y4m_input;
extern const cli_input_t avs_input;
extern const cli_input_t thread_8_input;
extern const cli_input_t thread_10_input;
extern const cli_input_t lavf_input;
extern const cli_input_t ffms_input;
extern const cli_input_t timecode_input;
extern cli_input_t cli_input;
/* extended colorspace list that isn't supported by libx264 but by the cli */
#define X264_CSP_CLI_MAX X264_CSP_MAX /* end of list */
#define X264_CSP_OTHER 0x4000 /* non x264 colorspace */
typedef struct
{
const char *name;
int planes;
float width[4];
float height[4];
int mod_width;
int mod_height;
} x264_cli_csp_t;
extern const x264_cli_csp_t x264_cli_csps[];
int x264_cli_csp_is_invalid( int csp );
int x264_cli_csp_depth_factor( int csp );
int x264_cli_pic_alloc( cli_pic_t *pic, int csp, int width, int height );
int x264_cli_pic_alloc_aligned( cli_pic_t *pic, int csp, int width, int height );
int x264_cli_pic_init_noalloc( cli_pic_t *pic, int csp, int width, int height );
void x264_cli_pic_clean( cli_pic_t *pic );
int64_t x264_cli_pic_plane_size( int csp, int width, int height, int plane );
int64_t x264_cli_pic_size( int csp, int width, int height );
const x264_cli_csp_t *x264_cli_get_csp( int csp );
typedef struct
{
int64_t file_size;
int align_mask;
#ifdef _WIN32
int page_mask;
BOOL (WINAPI *prefetch_virtual_memory)( HANDLE, ULONG_PTR, PVOID, ULONG );
HANDLE process_handle;
HANDLE map_handle;
#elif HAVE_MMAP
int fd;
#endif
} cli_mmap_t;
int x264_cli_mmap_init( cli_mmap_t *h, FILE *fh );
void *x264_cli_mmap( cli_mmap_t *h, int64_t offset, int64_t size );
int x264_cli_munmap( cli_mmap_t *h, void *addr, int64_t size );
void x264_cli_mmap_close( cli_mmap_t *h );
#endif

280
input/lavf.c Normal file
View File

@@ -0,0 +1,280 @@
/*****************************************************************************
* lavf.c: libavformat input
*****************************************************************************
* Copyright (C) 2009-2025 x264 project
*
* Authors: Mike Gurlitz <mike.gurlitz@gmail.com>
* Steven Walters <kemuri9@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "input.h"
#undef DECLARE_ALIGNED
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/dict.h>
#include <libavutil/error.h>
#include <libavutil/mem.h>
#include <libavutil/pixdesc.h>
#include <libavutil/version.h>
#define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "lavf", __VA_ARGS__ )
typedef struct
{
AVFormatContext *lavf;
AVCodecContext *lavc;
AVFrame *frame;
AVPacket *pkt;
int stream_id;
int next_frame;
int vfr_input;
cli_pic_t *first_pic;
} lavf_hnd_t;
/* handle the deprecated jpeg pixel formats */
static int handle_jpeg( int csp, int *fullrange )
{
switch( csp )
{
case AV_PIX_FMT_YUVJ420P: *fullrange = 1; return AV_PIX_FMT_YUV420P;
case AV_PIX_FMT_YUVJ422P: *fullrange = 1; return AV_PIX_FMT_YUV422P;
case AV_PIX_FMT_YUVJ444P: *fullrange = 1; return AV_PIX_FMT_YUV444P;
default: return csp;
}
}
static AVCodecContext *codec_from_stream( AVStream *stream )
{
AVCodec *codec = avcodec_find_decoder( stream->codecpar->codec_id );
if( !codec )
return NULL;
AVCodecContext *c = avcodec_alloc_context3( codec );
if( !c )
return NULL;
if( avcodec_parameters_to_context( c, stream->codecpar ) < 0 )
{
avcodec_free_context( &c );
return NULL;
}
return c;
}
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 )
{
/* see if the frame we are requesting is the frame we have already read and stored.
* if so, retrieve the pts and image data before freeing it. */
if( !i_frame )
{
XCHG( cli_image_t, p_pic->img, h->first_pic->img );
p_pic->pts = h->first_pic->pts;
}
lavf_input.picture_clean( h->first_pic, h );
free( h->first_pic );
h->first_pic = NULL;
if( !i_frame )
return 0;
}
AVPacket *pkt = h->pkt;
while( i_frame >= h->next_frame )
{
int ret;
while( (ret = avcodec_receive_frame( h->lavc, h->frame )) )
{
if( ret == AVERROR(EAGAIN) )
{
while( !(ret = av_read_frame( h->lavf, pkt )) && pkt->stream_index != h->stream_id )
av_packet_unref( pkt );
if( ret )
ret = avcodec_send_packet( h->lavc, NULL );
else
{
ret = avcodec_send_packet( h->lavc, pkt );
av_packet_unref( pkt );
}
}
else if( ret == AVERROR_EOF )
return -1;
if( ret )
{
x264_cli_log( "lavf", X264_LOG_WARNING, "video decoding failed on frame %d\n", h->next_frame );
return -1;
}
}
h->next_frame++;
}
memcpy( p_pic->img.stride, h->frame->linesize, sizeof(p_pic->img.stride) );
memcpy( p_pic->img.plane, h->frame->data, sizeof(p_pic->img.plane) );
int is_fullrange = 0;
p_pic->img.width = h->lavc->width;
p_pic->img.height = h->lavc->height;
p_pic->img.csp = handle_jpeg( h->lavc->pix_fmt, &is_fullrange ) | X264_CSP_OTHER;
if( info )
{
info->fullrange = is_fullrange;
#if LIBAVUTIL_VERSION_MAJOR < 60
info->interlaced = h->frame->interlaced_frame;
info->tff = h->frame->top_field_first;
#else
info->interlaced = !!(h->frame->flags & AV_FRAME_FLAG_INTERLACED);
info->tff = !!(h->frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST);
#endif
}
if( h->vfr_input )
{
p_pic->pts = p_pic->duration = 0;
if( h->frame->pts != AV_NOPTS_VALUE )
p_pic->pts = h->frame->pts;
else if( h->frame->pkt_dts != AV_NOPTS_VALUE )
p_pic->pts = h->frame->pkt_dts; // for AVI files
else if( info )
{
h->vfr_input = info->vfr = 0;
return 0;
}
}
return 0;
}
static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
lavf_hnd_t *h = calloc( 1, sizeof(lavf_hnd_t) );
if( !h )
return -1;
if( !strcmp( psz_filename, "-" ) )
psz_filename = "pipe:";
h->frame = av_frame_alloc();
if( !h->frame )
return -1;
h->pkt = av_packet_alloc();
if( !h->pkt )
return -1;
/* if resolution was passed in, place it and colorspace into options. this allows raw video support */
AVDictionary *options = NULL;
if( opt->resolution )
{
av_dict_set( &options, "video_size", opt->resolution, 0 );
const char *csp = opt->colorspace ? opt->colorspace : av_get_pix_fmt_name( AV_PIX_FMT_YUV420P );
av_dict_set( &options, "pixel_format", csp, 0 );
}
/* specify the input format. this is helpful when lavf fails to guess */
AVInputFormat *format = NULL;
if( opt->format )
FAIL_IF_ERROR( !(format = av_find_input_format( opt->format )), "unknown file format: %s\n", opt->format );
FAIL_IF_ERROR( avformat_open_input( &h->lavf, psz_filename, format, &options ), "could not open input file\n" );
if( options )
av_dict_free( &options );
FAIL_IF_ERROR( avformat_find_stream_info( h->lavf, NULL ) < 0, "could not find input stream info\n" );
int i = 0;
while( i < h->lavf->nb_streams && h->lavf->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO )
i++;
FAIL_IF_ERROR( i == h->lavf->nb_streams, "could not find video stream\n" );
h->stream_id = i;
h->next_frame = 0;
h->lavc = codec_from_stream( h->lavf->streams[i] );
if( !h->lavc )
return -1;
info->fps_num = h->lavf->streams[i]->avg_frame_rate.num;
info->fps_den = h->lavf->streams[i]->avg_frame_rate.den;
info->timebase_num = h->lavf->streams[i]->time_base.num;
info->timebase_den = h->lavf->streams[i]->time_base.den;
/* lavf is thread unsafe as calling av_read_frame invalidates previously read AVPackets */
info->thread_safe = 0;
h->vfr_input = info->vfr;
FAIL_IF_ERROR( avcodec_open2( h->lavc, avcodec_find_decoder( h->lavc->codec_id ), NULL ),
"could not find decoder for video stream\n" );
/* prefetch the first frame and set/confirm flags */
h->first_pic = malloc( sizeof(cli_pic_t) );
FAIL_IF_ERROR( !h->first_pic || lavf_input.picture_alloc( h->first_pic, h, X264_CSP_OTHER, info->width, info->height ),
"malloc failed\n" );
if( read_frame_internal( h->first_pic, h, 0, info ) )
return -1;
info->width = h->lavc->width;
info->height = h->lavc->height;
info->csp = h->first_pic->img.csp;
info->num_frames = h->lavf->streams[i]->nb_frames;
info->sar_height = h->lavc->sample_aspect_ratio.den;
info->sar_width = h->lavc->sample_aspect_ratio.num;
info->fullrange |= h->lavc->color_range == AVCOL_RANGE_JPEG;
/* avisynth stores rgb data vertically flipped. */
if( !strcasecmp( get_filename_extension( psz_filename ), "avs" ) &&
(h->lavc->pix_fmt == AV_PIX_FMT_BGRA || h->lavc->pix_fmt == AV_PIX_FMT_BGR24) )
info->csp |= X264_CSP_VFLIP;
*p_handle = h;
return 0;
}
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;
pic->img.csp = csp;
pic->img.planes = 4;
return 0;
}
static int read_frame( cli_pic_t *pic, hnd_t handle, int i_frame )
{
return read_frame_internal( pic, handle, i_frame, NULL );
}
static void picture_clean( cli_pic_t *pic, hnd_t handle )
{
memset( pic, 0, sizeof(cli_pic_t) );
}
static int close_file( hnd_t handle )
{
lavf_hnd_t *h = handle;
avcodec_free_context( &h->lavc );
avformat_close_input( &h->lavf );
av_packet_free( &h->pkt );
av_frame_free( &h->frame );
free( h );
return 0;
}
const cli_input_t lavf_input = { open_file, picture_alloc, read_frame, NULL, picture_clean, close_file };

206
input/raw.c Normal file
View File

@@ -0,0 +1,206 @@
/*****************************************************************************
* raw.c: raw input
*****************************************************************************
* Copyright (C) 2003-2025 x264 project
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Loren Merritt <lorenm@u.washington.edu>
* Steven Walters <kemuri9@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "input.h"
#define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "raw", __VA_ARGS__ )
typedef struct
{
FILE *fh;
int next_frame;
int64_t plane_size[4];
int64_t frame_size;
int bit_depth;
cli_mmap_t mmap;
int use_mmap;
} raw_hnd_t;
static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
raw_hnd_t *h = calloc( 1, sizeof(raw_hnd_t) );
if( !h )
return -1;
if( !opt->resolution )
{
/* try to parse the file name */
for( char *p = psz_filename; *p; p++ )
if( *p >= '0' && *p <= '9' && sscanf( p, "%dx%d", &info->width, &info->height ) == 2 )
break;
}
else
sscanf( opt->resolution, "%dx%d", &info->width, &info->height );
FAIL_IF_ERROR( !info->width || !info->height, "raw input requires a resolution.\n" );
if( opt->colorspace )
{
for( info->csp = X264_CSP_CLI_MAX-1; info->csp > X264_CSP_NONE; info->csp-- )
{
if( x264_cli_csps[info->csp].name && !strcasecmp( x264_cli_csps[info->csp].name, opt->colorspace ) )
break;
}
FAIL_IF_ERROR( info->csp == X264_CSP_NONE, "unsupported colorspace `%s'\n", opt->colorspace );
}
else /* default */
info->csp = X264_CSP_I420;
h->bit_depth = opt->bit_depth;
FAIL_IF_ERROR( h->bit_depth < 8 || h->bit_depth > 16, "unsupported bit depth `%d'\n", h->bit_depth );
if( h->bit_depth > 8 )
info->csp |= X264_CSP_HIGH_DEPTH;
if( !strcmp( psz_filename, "-" ) )
h->fh = stdin;
else
h->fh = x264_fopen( psz_filename, "rb" );
if( h->fh == NULL )
return -1;
info->thread_safe = 1;
info->num_frames = 0;
info->vfr = 0;
const x264_cli_csp_t *csp = x264_cli_get_csp( info->csp );
for( int i = 0; i < csp->planes; i++ )
{
h->plane_size[i] = x264_cli_pic_plane_size( info->csp, info->width, info->height, i );
h->frame_size += h->plane_size[i];
/* x264_cli_pic_plane_size returns the size in bytes, we need the value in pixels from here on */
h->plane_size[i] /= x264_cli_csp_depth_factor( info->csp );
}
if( x264_is_regular_file( h->fh ) )
{
fseek( h->fh, 0, SEEK_END );
int64_t size = ftell( h->fh );
fseek( h->fh, 0, SEEK_SET );
info->num_frames = size / h->frame_size;
FAIL_IF_ERROR( !info->num_frames, "empty input file\n" );
/* Attempt to use memory-mapped input frames if possible */
if( !(h->bit_depth & 7) )
h->use_mmap = !x264_cli_mmap_init( &h->mmap, h->fh );
}
*p_handle = h;
return 0;
}
static int read_frame_internal( cli_pic_t *pic, raw_hnd_t *h, int bit_depth_uc )
{
int pixel_depth = x264_cli_csp_depth_factor( pic->img.csp );
for( int i = 0; i < pic->img.planes; i++ )
{
if( h->use_mmap )
{
if( i )
pic->img.plane[i] = pic->img.plane[i-1] + pixel_depth * h->plane_size[i-1];
}
else if( fread( pic->img.plane[i], pixel_depth, h->plane_size[i], h->fh ) != (uint64_t)h->plane_size[i] )
return -1;
if( bit_depth_uc )
{
/* upconvert non 16bit high depth planes to 16bit using the same
* algorithm as used in the depth filter. */
uint16_t *plane = (uint16_t*)pic->img.plane[i];
int64_t pixel_count = h->plane_size[i];
int lshift = 16 - h->bit_depth;
for( int64_t j = 0; j < pixel_count; j++ )
plane[j] = plane[j] << lshift;
}
}
return 0;
}
static int read_frame( cli_pic_t *pic, hnd_t handle, int i_frame )
{
raw_hnd_t *h = handle;
if( h->use_mmap )
{
pic->img.plane[0] = x264_cli_mmap( &h->mmap, i_frame * h->frame_size, h->frame_size );
if( !pic->img.plane[0] )
return -1;
}
else if( i_frame > h->next_frame )
{
if( x264_is_regular_file( h->fh ) )
fseek( h->fh, i_frame * h->frame_size, SEEK_SET );
else
while( i_frame > h->next_frame )
{
if( read_frame_internal( pic, h, 0 ) )
return -1;
h->next_frame++;
}
}
if( read_frame_internal( pic, h, h->bit_depth & 7 ) )
return -1;
h->next_frame = i_frame+1;
return 0;
}
static int release_frame( cli_pic_t *pic, hnd_t handle )
{
raw_hnd_t *h = handle;
if( h->use_mmap )
return x264_cli_munmap( &h->mmap, pic->img.plane[0], h->frame_size );
return 0;
}
static int picture_alloc( cli_pic_t *pic, hnd_t handle, int csp, int width, int height )
{
raw_hnd_t *h = handle;
return (h->use_mmap ? x264_cli_pic_init_noalloc : x264_cli_pic_alloc)( pic, csp, width, height );
}
static void picture_clean( cli_pic_t *pic, hnd_t handle )
{
raw_hnd_t *h = handle;
if( h->use_mmap )
memset( pic, 0, sizeof(cli_pic_t) );
else
x264_cli_pic_clean( pic );
}
static int close_file( hnd_t handle )
{
raw_hnd_t *h = handle;
if( !h || !h->fh )
return 0;
if( h->use_mmap )
x264_cli_mmap_close( &h->mmap );
fclose( h->fh );
free( h );
return 0;
}
const cli_input_t raw_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file };

142
input/thread.c Normal file
View File

@@ -0,0 +1,142 @@
/*****************************************************************************
* thread.c: threaded input
*****************************************************************************
* Copyright (C) 2003-2025 x264 project
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Loren Merritt <lorenm@u.washington.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "input.h"
#include "common/common.h"
#define thread_input x264_glue3(thread, BIT_DEPTH, input)
typedef struct
{
cli_input_t input;
hnd_t p_handle;
cli_pic_t pic;
x264_threadpool_t *pool;
int next_frame;
int frame_total;
struct thread_input_arg_t *next_args;
} thread_hnd_t;
typedef struct thread_input_arg_t
{
thread_hnd_t *h;
cli_pic_t *pic;
int i_frame;
int status;
} thread_input_arg_t;
static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
thread_hnd_t *h = malloc( sizeof(thread_hnd_t) );
FAIL_IF_ERR( !h || cli_input.picture_alloc( &h->pic, *p_handle, info->csp, info->width, info->height ),
"x264", "malloc failed\n" );
h->input = cli_input;
h->p_handle = *p_handle;
h->next_frame = -1;
h->next_args = malloc( sizeof(thread_input_arg_t) );
if( !h->next_args )
return -1;
h->next_args->h = h;
h->next_args->status = 0;
h->frame_total = info->num_frames;
if( x264_threadpool_init( &h->pool, 1 ) )
return -1;
*p_handle = h;
return 0;
}
static void read_frame_thread_int( thread_input_arg_t *i )
{
i->status = i->h->input.read_frame( i->pic, i->h->p_handle, i->i_frame );
}
static int read_frame( cli_pic_t *p_pic, hnd_t handle, int i_frame )
{
thread_hnd_t *h = handle;
int ret = 0;
if( h->next_frame >= 0 )
{
x264_threadpool_wait( h->pool, h->next_args );
ret |= h->next_args->status;
}
if( h->next_frame == i_frame )
XCHG( cli_pic_t, *p_pic, h->pic );
else
{
if( h->next_frame >= 0 )
thread_input.release_frame( &h->pic, handle );
ret |= h->input.read_frame( p_pic, h->p_handle, i_frame );
}
if( !h->frame_total || i_frame+1 < h->frame_total )
{
h->next_frame =
h->next_args->i_frame = i_frame+1;
h->next_args->pic = &h->pic;
x264_threadpool_run( h->pool, (void*)read_frame_thread_int, h->next_args );
}
else
h->next_frame = -1;
return ret;
}
static int release_frame( cli_pic_t *pic, hnd_t handle )
{
thread_hnd_t *h = handle;
if( h->input.release_frame )
return h->input.release_frame( pic, h->p_handle );
return 0;
}
static int picture_alloc( cli_pic_t *pic, hnd_t handle, int csp, int width, int height )
{
thread_hnd_t *h = handle;
return h->input.picture_alloc( pic, h->p_handle, csp, width, height );
}
static void picture_clean( cli_pic_t *pic, hnd_t handle )
{
thread_hnd_t *h = handle;
h->input.picture_clean( pic, h->p_handle );
}
static int close_file( hnd_t handle )
{
thread_hnd_t *h = handle;
x264_threadpool_delete( h->pool );
h->input.picture_clean( &h->pic, h->p_handle );
h->input.close_file( h->p_handle );
free( h->next_args );
free( h );
return 0;
}
const cli_input_t thread_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file };

459
input/timecode.c Normal file
View File

@@ -0,0 +1,459 @@
/*****************************************************************************
* timecode.c: timecode file input
*****************************************************************************
* Copyright (C) 2010-2025 x264 project
*
* Authors: Yusuke Nakamura <muken.the.vfrmaniac@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "input.h"
#define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "timecode", __VA_ARGS__ )
typedef struct
{
cli_input_t input;
hnd_t p_handle;
int auto_timebase_num;
int auto_timebase_den;
uint64_t timebase_num;
uint64_t timebase_den;
int stored_pts_num;
int64_t *pts;
double assume_fps;
double last_timecode;
} timecode_hnd_t;
static inline double sigexp10( double value, double *exponent )
{
/* This function separates significand and exp10 from double floating point. */
*exponent = pow( 10, floor( log10( value ) ) );
return value / *exponent;
}
#define DOUBLE_EPSILON 5e-6
#define MKV_TIMEBASE_DEN 1000000000
static double correct_fps( double fps, timecode_hnd_t *h )
{
int i = 1;
uint64_t fps_num, fps_den;
double exponent;
double fps_sig = sigexp10( fps, &exponent );
while( 1 )
{
fps_den = i * h->timebase_num;
fps_num = round( fps_den * fps_sig ) * exponent;
FAIL_IF_ERROR( fps_num > UINT32_MAX, "tcfile fps correction failed.\n"
" Specify an appropriate timebase manually or remake tcfile.\n" );
if( fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
break;
++i;
}
if( h->auto_timebase_den )
{
h->timebase_den = h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
if( h->timebase_den > UINT32_MAX )
h->auto_timebase_den = 0;
}
return (double)fps_num / fps_den;
}
static int try_mkv_timebase_den( double *fpss, timecode_hnd_t *h, int loop_num )
{
h->timebase_num = 0;
h->timebase_den = MKV_TIMEBASE_DEN;
for( int num = 0; num < loop_num; num++ )
{
uint64_t fps_den;
double exponent;
double fps_sig = sigexp10( fpss[num], &exponent );
fps_den = round( MKV_TIMEBASE_DEN / fps_sig ) / exponent;
h->timebase_num = fps_den && h->timebase_num ? gcd( h->timebase_num, fps_den ) : fps_den;
FAIL_IF_ERROR( h->timebase_num > UINT32_MAX || !h->timebase_num, "automatic timebase generation failed.\n"
" Specify timebase manually.\n" );
}
return 0;
}
static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info )
{
char buff[256];
int ret, tcfv, num, seq_num, timecodes_num;
double *timecodes = NULL;
double *fpss = NULL;
ret = fgets( buff, sizeof(buff), tcfile_in ) != NULL &&
(sscanf( buff, "# timecode format v%d", &tcfv ) == 1 || sscanf( buff, "# timestamp format v%d", &tcfv ) == 1);
FAIL_IF_ERROR( !ret || (tcfv != 1 && tcfv != 2), "unsupported timecode format\n" );
#define NO_TIMECODE_LINE (buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r')
if( tcfv == 1 )
{
int64_t file_pos;
double assume_fps, seq_fps;
int start, end = -1;
int prev_start = -1, prev_end = -1;
h->assume_fps = 0;
for( num = 2; fgets( buff, sizeof(buff), tcfile_in ) != NULL; num++ )
{
if( NO_TIMECODE_LINE )
continue;
FAIL_IF_ERROR( sscanf( buff, "assume %lf", &h->assume_fps ) != 1 && sscanf( buff, "Assume %lf", &h->assume_fps ) != 1,
"tcfile parsing error: assumed fps not found\n" );
break;
}
FAIL_IF_ERROR( h->assume_fps <= 0, "invalid assumed fps %.6f\n", h->assume_fps );
file_pos = ftell( tcfile_in );
h->stored_pts_num = 0;
for( seq_num = 0; fgets( buff, sizeof(buff), tcfile_in ) != NULL; num++ )
{
if( NO_TIMECODE_LINE )
{
if( sscanf( buff, "# TDecimate Mode 3: Last Frame = %d", &end ) == 1 )
h->stored_pts_num = end + 1;
continue;
}
ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
FAIL_IF_ERROR( ret != 3 && ret != EOF, "invalid input tcfile\n" );
FAIL_IF_ERROR( start > end || start <= prev_start || end <= prev_end || seq_fps <= 0,
"invalid input tcfile at line %d: %s\n", num, buff );
prev_start = start;
prev_end = end;
if( h->auto_timebase_den || h->auto_timebase_num )
++seq_num;
}
if( !h->stored_pts_num )
h->stored_pts_num = end + 2;
timecodes_num = h->stored_pts_num;
fseek( tcfile_in, file_pos, SEEK_SET );
timecodes = malloc( timecodes_num * sizeof(double) );
if( !timecodes )
return -1;
if( h->auto_timebase_den || h->auto_timebase_num )
{
fpss = malloc( (seq_num + 1) * sizeof(double) );
if( !fpss )
goto fail;
}
assume_fps = correct_fps( h->assume_fps, h );
if( assume_fps < 0 )
goto fail;
timecodes[0] = 0;
for( num = seq_num = 0; num < timecodes_num - 1 && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
{
if( NO_TIMECODE_LINE )
continue;
ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
if( ret != 3 )
start = end = timecodes_num - 1;
for( ; num < start && num < timecodes_num - 1; num++ )
timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
if( num < timecodes_num - 1 )
{
if( h->auto_timebase_den || h->auto_timebase_num )
fpss[seq_num++] = seq_fps;
seq_fps = correct_fps( seq_fps, h );
if( seq_fps < 0 )
goto fail;
for( num = start; num <= end && num < timecodes_num - 1; num++ )
timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
}
}
for( ; num < timecodes_num - 1; num++ )
timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
if( h->auto_timebase_den || h->auto_timebase_num )
fpss[seq_num] = h->assume_fps;
if( h->auto_timebase_num && !h->auto_timebase_den )
{
double exponent;
double assume_fps_sig, seq_fps_sig;
if( try_mkv_timebase_den( fpss, h, seq_num + 1 ) < 0 )
goto fail;
fseek( tcfile_in, file_pos, SEEK_SET );
assume_fps_sig = sigexp10( h->assume_fps, &exponent );
assume_fps = MKV_TIMEBASE_DEN / ( round( MKV_TIMEBASE_DEN / assume_fps_sig ) / exponent );
for( num = 0; num < timecodes_num - 1 && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
{
if( NO_TIMECODE_LINE )
continue;
ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
if( ret != 3 )
start = end = timecodes_num - 1;
seq_fps_sig = sigexp10( seq_fps, &exponent );
seq_fps = MKV_TIMEBASE_DEN / ( round( MKV_TIMEBASE_DEN / seq_fps_sig ) / exponent );
for( ; num < start && num < timecodes_num - 1; num++ )
timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
for( num = start; num <= end && num < timecodes_num - 1; num++ )
timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
}
for( ; num < timecodes_num - 1; num++ )
timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
}
if( fpss )
{
free( fpss );
fpss = NULL;
}
h->assume_fps = assume_fps;
h->last_timecode = timecodes[timecodes_num - 1];
}
else /* tcfv == 2 */
{
int64_t file_pos = ftell( tcfile_in );
h->stored_pts_num = 0;
while( fgets( buff, sizeof(buff), tcfile_in ) != NULL )
{
if( NO_TIMECODE_LINE )
{
if( !h->stored_pts_num )
file_pos = ftell( tcfile_in );
continue;
}
h->stored_pts_num++;
}
timecodes_num = h->stored_pts_num;
FAIL_IF_ERROR( !timecodes_num, "input tcfile doesn't have any timecodes!\n" );
fseek( tcfile_in, file_pos, SEEK_SET );
timecodes = malloc( timecodes_num * sizeof(double) );
if( !timecodes )
return -1;
num = 0;
if( fgets( buff, sizeof(buff), tcfile_in ) != NULL )
{
ret = sscanf( buff, "%lf", &timecodes[0] );
timecodes[0] *= 1e-3; /* Timecode format v2 is expressed in milliseconds. */
FAIL_IF_ERROR( ret != 1, "invalid input tcfile for frame 0\n" );
for( num = 1; num < timecodes_num && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
{
if( NO_TIMECODE_LINE )
continue;
ret = sscanf( buff, "%lf", &timecodes[num] );
timecodes[num] *= 1e-3; /* Timecode format v2 is expressed in milliseconds. */
FAIL_IF_ERROR( ret != 1 || timecodes[num] <= timecodes[num - 1],
"invalid input tcfile for frame %d\n", num );
++num;
}
}
FAIL_IF_ERROR( num < timecodes_num, "failed to read input tcfile for frame %d", num );
if( timecodes_num == 1 )
h->timebase_den = info->fps_num;
else if( h->auto_timebase_den )
{
fpss = malloc( (timecodes_num - 1) * sizeof(double) );
if( !fpss )
goto fail;
for( num = 0; num < timecodes_num - 1; num++ )
{
fpss[num] = 1 / (timecodes[num + 1] - timecodes[num]);
if( h->auto_timebase_den )
{
int i = 1;
uint64_t fps_num, fps_den;
double exponent;
double fps_sig = sigexp10( fpss[num], &exponent );
while( 1 )
{
fps_den = i * h->timebase_num;
fps_num = round( fps_den * fps_sig ) * exponent;
if( fps_num > UINT32_MAX || fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
break;
++i;
}
h->timebase_den = fps_num && h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
if( h->timebase_den > UINT32_MAX )
{
h->auto_timebase_den = 0;
continue;
}
}
}
if( h->auto_timebase_num && !h->auto_timebase_den )
if( try_mkv_timebase_den( fpss, h, timecodes_num - 1 ) < 0 )
goto fail;
free( fpss );
fpss = NULL;
}
if( timecodes_num > 1 )
h->assume_fps = 1 / (timecodes[timecodes_num - 1] - timecodes[timecodes_num - 2]);
else
h->assume_fps = (double)info->fps_num / info->fps_den;
h->last_timecode = timecodes[timecodes_num - 1];
}
#undef NO_TIMECODE_LINE
if( h->auto_timebase_den || h->auto_timebase_num )
{
uint64_t i = gcd( h->timebase_num, h->timebase_den );
h->timebase_num /= i;
h->timebase_den /= i;
x264_cli_log( "timecode", X264_LOG_INFO, "automatic timebase generation %"PRIu64"/%"PRIu64"\n", h->timebase_num, h->timebase_den );
}
else FAIL_IF_ERROR( h->timebase_den > UINT32_MAX || !h->timebase_den, "automatic timebase generation failed.\n"
" Specify an appropriate timebase manually.\n" );
h->pts = malloc( h->stored_pts_num * sizeof(int64_t) );
if( !h->pts )
goto fail;
for( num = 0; num < h->stored_pts_num; num++ )
{
h->pts[num] = timecodes[num] * ((double)h->timebase_den / h->timebase_num) + 0.5;
FAIL_IF_ERROR( num > 0 && h->pts[num] <= h->pts[num - 1], "invalid timebase or timecode for frame %d\n", num );
}
free( timecodes );
return 0;
fail:
if( timecodes )
free( timecodes );
if( fpss )
free( fpss );
return -1;
}
#undef DOUBLE_EPSILON
#undef MKV_TIMEBASE_DEN
static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
int ret = 0;
FILE *tcfile_in;
timecode_hnd_t *h = malloc( sizeof(timecode_hnd_t) );
FAIL_IF_ERROR( !h, "malloc failed\n" );
h->input = cli_input;
h->p_handle = *p_handle;
h->pts = NULL;
if( opt->timebase )
{
ret = sscanf( opt->timebase, "%"SCNu64"/%"SCNu64, &h->timebase_num, &h->timebase_den );
if( ret == 1 )
{
h->timebase_num = strtoul( opt->timebase, NULL, 10 );
h->timebase_den = 0; /* set later by auto timebase generation */
}
FAIL_IF_ERROR( h->timebase_num > UINT32_MAX || h->timebase_den > UINT32_MAX,
"timebase you specified exceeds H.264 maximum\n" );
}
h->auto_timebase_num = !ret;
h->auto_timebase_den = ret < 2;
if( h->auto_timebase_num )
h->timebase_num = info->fps_den; /* can be changed later by auto timebase generation */
if( h->auto_timebase_den )
h->timebase_den = 0; /* set later by auto timebase generation */
tcfile_in = x264_fopen( psz_filename, "rb" );
FAIL_IF_ERROR( !tcfile_in, "can't open `%s'\n", psz_filename );
if( !x264_is_regular_file( tcfile_in ) )
{
x264_cli_log( "timecode", X264_LOG_ERROR, "tcfile input incompatible with non-regular file `%s'\n", psz_filename );
fclose( tcfile_in );
return -1;
}
if( parse_tcfile( tcfile_in, h, info ) < 0 )
{
if( h->pts )
free( h->pts );
fclose( tcfile_in );
return -1;
}
fclose( tcfile_in );
info->timebase_num = h->timebase_num;
info->timebase_den = h->timebase_den;
info->vfr = 1;
*p_handle = h;
return 0;
}
static int64_t get_frame_pts( timecode_hnd_t *h, int frame, int real_frame )
{
if( frame < h->stored_pts_num )
return h->pts[frame];
else
{
if( h->pts && real_frame )
{
x264_cli_log( "timecode", X264_LOG_INFO, "input timecode file missing data for frame %d and later\n"
" assuming constant fps %.6f\n", frame, h->assume_fps );
free( h->pts );
h->pts = NULL;
}
double timecode = h->last_timecode + 1 / h->assume_fps;
if( real_frame )
h->last_timecode = timecode;
return timecode * ((double)h->timebase_den / h->timebase_num) + 0.5;
}
}
static int read_frame( cli_pic_t *pic, hnd_t handle, int frame )
{
timecode_hnd_t *h = handle;
if( h->input.read_frame( pic, h->p_handle, frame ) )
return -1;
pic->pts = get_frame_pts( h, frame, 1 );
pic->duration = get_frame_pts( h, frame + 1, 0 ) - pic->pts;
return 0;
}
static int release_frame( cli_pic_t *pic, hnd_t handle )
{
timecode_hnd_t *h = handle;
if( h->input.release_frame )
return h->input.release_frame( pic, h->p_handle );
return 0;
}
static int picture_alloc( cli_pic_t *pic, hnd_t handle, int csp, int width, int height )
{
timecode_hnd_t *h = handle;
return h->input.picture_alloc( pic, h->p_handle, csp, width, height );
}
static void picture_clean( cli_pic_t *pic, hnd_t handle )
{
timecode_hnd_t *h = handle;
h->input.picture_clean( pic, h->p_handle );
}
static int close_file( hnd_t handle )
{
timecode_hnd_t *h = handle;
if( h->pts )
free( h->pts );
h->input.close_file( h->p_handle );
free( h );
return 0;
}
const cli_input_t timecode_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file };

370
input/y4m.c Normal file
View File

@@ -0,0 +1,370 @@
/*****************************************************************************
* y4m.c: y4m input
*****************************************************************************
* Copyright (C) 2003-2025 x264 project
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Loren Merritt <lorenm@u.washington.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "input.h"
#define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "y4m", __VA_ARGS__ )
typedef struct
{
FILE *fh;
int next_frame;
int seq_header_len;
int frame_header_len;
int64_t frame_size;
int64_t plane_size[3];
int bit_depth;
cli_mmap_t mmap;
int use_mmap;
} y4m_hnd_t;
#define Y4M_MAGIC "YUV4MPEG2"
#define Y4M_FRAME_MAGIC "FRAME"
#define Y4M_MAX_HEADER 256
static int parse_csp_and_depth( char *csp_name, int *bit_depth )
{
int csp = X264_CSP_MAX;
/* Set colorspace from known variants */
if( !strncmp( "mono", csp_name, 4 ) )
csp = X264_CSP_I400;
else if( !strncmp( "420", csp_name, 3 ) )
csp = X264_CSP_I420;
else if( !strncmp( "422", csp_name, 3 ) )
csp = X264_CSP_I422;
else if( !strncmp( "444", csp_name, 3 ) && strncmp( "444alpha", csp_name, 8 ) ) // only accept alphaless 4:4:4
csp = X264_CSP_I444;
/* Set high bit depth from known extensions */
if( sscanf( csp_name, "mono%d", bit_depth ) != 1 &&
sscanf( csp_name, "%*d%*[pP]%d", bit_depth ) != 1 )
*bit_depth = 8;
return csp;
}
static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
y4m_hnd_t *h = calloc( 1, sizeof(y4m_hnd_t) );
int i;
uint32_t n, d;
char header[Y4M_MAX_HEADER+10];
char *tokend, *header_end;
int colorspace = X264_CSP_NONE;
int alt_colorspace = X264_CSP_NONE;
int alt_bit_depth = 8;
if( !h )
return -1;
info->vfr = 0;
if( !strcmp( psz_filename, "-" ) )
h->fh = stdin;
else
h->fh = x264_fopen(psz_filename, "rb");
if( h->fh == NULL )
return -1;
/* Read header */
for( i = 0; i < Y4M_MAX_HEADER; i++ )
{
header[i] = fgetc( h->fh );
if( header[i] == '\n' )
{
/* Add a space after last option. Makes parsing "444" vs
"444alpha" easier. */
header[i+1] = 0x20;
header[i+2] = 0;
break;
}
}
FAIL_IF_ERROR( strncmp( header, Y4M_MAGIC, sizeof(Y4M_MAGIC)-1 ), "bad sequence header magic\n" );
FAIL_IF_ERROR( i == Y4M_MAX_HEADER, "bad sequence header length\n" );
/* Scan properties */
header_end = &header[i+1]; /* Include space */
h->seq_header_len = i+1;
for( char *tokstart = header + sizeof(Y4M_MAGIC); tokstart < header_end; tokstart++ )
{
if( *tokstart == 0x20 )
continue;
switch( *tokstart++ )
{
case 'W': /* Width. Required. */
info->width = strtol( tokstart, &tokend, 10 );
tokstart=tokend;
break;
case 'H': /* Height. Required. */
info->height = strtol( tokstart, &tokend, 10 );
tokstart=tokend;
break;
case 'C': /* Color space */
colorspace = parse_csp_and_depth( tokstart, &h->bit_depth );
tokstart = strchr( tokstart, 0x20 );
break;
case 'I': /* Interlace type */
switch( *tokstart++ )
{
case 't':
info->interlaced = 1;
info->tff = 1;
break;
case 'b':
info->interlaced = 1;
info->tff = 0;
break;
case 'm':
info->interlaced = 1;
break;
//case '?':
//case 'p':
default:
break;
}
break;
case 'F': /* Frame rate - 0:0 if unknown */
if( sscanf( tokstart, "%u:%u", &n, &d ) == 2 && n && d )
{
x264_reduce_fraction( &n, &d );
info->fps_num = n;
info->fps_den = d;
}
tokstart = strchr( tokstart, 0x20 );
break;
case 'A': /* Pixel aspect - 0:0 if unknown */
/* Don't override the aspect ratio if sar has been explicitly set on the commandline. */
if( sscanf( tokstart, "%u:%u", &n, &d ) == 2 && n && d )
{
x264_reduce_fraction( &n, &d );
info->sar_width = n;
info->sar_height = d;
}
tokstart = strchr( tokstart, 0x20 );
break;
case 'X': /* Vendor extensions */
if( !strncmp( "YSCSS=", tokstart, 6 ) )
{
/* Older nonstandard pixel format representation */
tokstart += 6;
alt_colorspace = parse_csp_and_depth( tokstart, &alt_bit_depth );
}
else if( !strncmp( "COLORRANGE=", tokstart, 11 ) )
{
/* ffmpeg's color range extension */
tokstart += 11;
if( !strncmp( "FULL", tokstart, 4 ) )
info->fullrange = 1;
else if( !strncmp( "LIMITED", tokstart, 7 ) )
info->fullrange = 0;
}
tokstart = strchr( tokstart, 0x20 );
break;
}
}
if( colorspace == X264_CSP_NONE )
{
colorspace = alt_colorspace;
h->bit_depth = alt_bit_depth;
}
// default to 8bit 4:2:0 if nothing is specified
if( colorspace == X264_CSP_NONE )
{
colorspace = X264_CSP_I420;
h->bit_depth = 8;
}
FAIL_IF_ERROR( colorspace <= X264_CSP_NONE || colorspace >= X264_CSP_MAX, "colorspace unhandled\n" );
FAIL_IF_ERROR( h->bit_depth < 8 || h->bit_depth > 16, "unsupported bit depth `%d'\n", h->bit_depth );
info->thread_safe = 1;
info->num_frames = 0;
info->csp = colorspace;
if( h->bit_depth > 8 )
info->csp |= X264_CSP_HIGH_DEPTH;
const x264_cli_csp_t *csp = x264_cli_get_csp( info->csp );
for( i = 0; i < csp->planes; i++ )
{
h->plane_size[i] = x264_cli_pic_plane_size( info->csp, info->width, info->height, i );
h->frame_size += h->plane_size[i];
/* x264_cli_pic_plane_size returns the size in bytes, we need the value in pixels from here on */
h->plane_size[i] /= x264_cli_csp_depth_factor( info->csp );
}
if( x264_is_regular_file( h->fh ) )
{
int64_t init_pos = ftell( h->fh );
/* Find out the length of the frame header */
size_t len = 1;
while( len <= Y4M_MAX_HEADER && fgetc( h->fh ) != '\n' )
len++;
FAIL_IF_ERROR( len > Y4M_MAX_HEADER || len < sizeof(Y4M_FRAME_MAGIC), "bad frame header length\n" );
h->frame_header_len = len;
h->frame_size += len;
fseek( h->fh, 0, SEEK_END );
int64_t i_size = ftell( h->fh );
fseek( h->fh, init_pos, SEEK_SET );
info->num_frames = (i_size - h->seq_header_len) / h->frame_size;
FAIL_IF_ERROR( !info->num_frames, "empty input file\n" );
/* Attempt to use memory-mapped input frames if possible */
if( !(h->bit_depth & 7) )
h->use_mmap = !x264_cli_mmap_init( &h->mmap, h->fh );
}
*p_handle = h;
return 0;
}
static int read_frame_internal( cli_pic_t *pic, y4m_hnd_t *h, int bit_depth_uc )
{
static const size_t slen = sizeof(Y4M_FRAME_MAGIC)-1;
int pixel_depth = x264_cli_csp_depth_factor( pic->img.csp );
int i = sizeof(Y4M_FRAME_MAGIC);
char header_buf[16];
char *header;
/* Verify that the frame header is valid */
if( h->use_mmap )
{
header = (char*)pic->img.plane[0];
pic->img.plane[0] += h->frame_header_len;
/* If the header length has changed between frames the size of the mapping will be invalid.
* It might be possible to work around it, but I'm not aware of any tool beside fuzzers that
* produces y4m files with variable-length frame headers so just error out if that happens. */
while( i <= h->frame_header_len && header[i-1] != '\n' )
i++;
FAIL_IF_ERROR( i != h->frame_header_len, "bad frame header length\n" );
}
else
{
header = header_buf;
if( fread( header, 1, slen, h->fh ) != slen )
return -1;
while( i <= Y4M_MAX_HEADER && fgetc( h->fh ) != '\n' )
i++;
FAIL_IF_ERROR( i > Y4M_MAX_HEADER, "bad frame header length\n" );
}
FAIL_IF_ERROR( memcmp( header, Y4M_FRAME_MAGIC, slen ), "bad frame header magic\n" );
for( i = 0; i < pic->img.planes; i++ )
{
if( h->use_mmap )
{
if( i )
pic->img.plane[i] = pic->img.plane[i-1] + pixel_depth * h->plane_size[i-1];
}
else if( fread( pic->img.plane[i], pixel_depth, h->plane_size[i], h->fh ) != (uint64_t)h->plane_size[i] )
return -1;
if( bit_depth_uc )
{
/* upconvert non 16bit high depth planes to 16bit using the same
* algorithm as used in the depth filter. */
uint16_t *plane = (uint16_t*)pic->img.plane[i];
int64_t pixel_count = h->plane_size[i];
int lshift = 16 - h->bit_depth;
for( int64_t j = 0; j < pixel_count; j++ )
plane[j] = plane[j] << lshift;
}
}
return 0;
}
static int read_frame( cli_pic_t *pic, hnd_t handle, int i_frame )
{
y4m_hnd_t *h = handle;
if( h->use_mmap )
{
pic->img.plane[0] = x264_cli_mmap( &h->mmap, h->frame_size * i_frame + h->seq_header_len, h->frame_size );
if( !pic->img.plane[0] )
return -1;
}
else if( i_frame > h->next_frame )
{
if( x264_is_regular_file( h->fh ) )
fseek( h->fh, h->frame_size * i_frame + h->seq_header_len, SEEK_SET );
else
while( i_frame > h->next_frame )
{
if( read_frame_internal( pic, h, 0 ) )
return -1;
h->next_frame++;
}
}
if( read_frame_internal( pic, h, h->bit_depth & 7 ) )
return -1;
h->next_frame = i_frame+1;
return 0;
}
static int release_frame( cli_pic_t *pic, hnd_t handle )
{
y4m_hnd_t *h = handle;
if( h->use_mmap )
return x264_cli_munmap( &h->mmap, pic->img.plane[0] - h->frame_header_len, h->frame_size );
return 0;
}
static int picture_alloc( cli_pic_t *pic, hnd_t handle, int csp, int width, int height )
{
y4m_hnd_t *h = handle;
return (h->use_mmap ? x264_cli_pic_init_noalloc : x264_cli_pic_alloc)( pic, csp, width, height );
}
static void picture_clean( cli_pic_t *pic, hnd_t handle )
{
y4m_hnd_t *h = handle;
if( h->use_mmap )
memset( pic, 0, sizeof(cli_pic_t) );
else
x264_cli_pic_clean( pic );
}
static int close_file( hnd_t handle )
{
y4m_hnd_t *h = handle;
if( !h || !h->fh )
return 0;
if( h->use_mmap )
x264_cli_mmap_close( &h->mmap );
fclose( h->fh );
free( h );
return 0;
}
const cli_input_t y4m_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file };