x264 source for verification 2026-05-22
This commit is contained in:
564
input/avs.c
Normal file
564
input/avs.c
Normal 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
223
input/ffms.c
Normal 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
274
input/input.c
Normal 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
158
input/input.h
Normal 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
280
input/lavf.c
Normal 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
206
input/raw.c
Normal 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
142
input/thread.c
Normal 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
459
input/timecode.c
Normal 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
370
input/y4m.c
Normal 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 };
|
||||
Reference in New Issue
Block a user