libx264 now takes three new API parameters.
b_vfr_input tells x264 whether or not the input is VFR, and is 1 by default.
i_timebase_num and i_timebase_den pass the timebase to x264.
x264_picture_t now returns the DTS of each frame: the calling app need not calculate it anymore.
Add libavformat and FFMS2 input support: requires libav* and ffms2 libraries respectively.
FFMS2 is _STRONGLY_ preferred over libavformat: we encourage all distributions to compile with FFMS2 support if at all possible.
FFMS2 can be found at http://code.google.com/p/ffmpegsource/.
--index, a new x264cli option, allows the user to store (or load) an FFMS2 index file for future use, to avoid re-indexing in the future.
Overhaul the muxers to pass through timestamps instead of assuming CFR.
Also overhaul muxers to correctly use b_annexb and b_repeat_headers to simplify the code.
Remove VFW input support, since it's now pretty much redundant with native AVS support and LAVF support.
Finally, overhaul a large part of the x264cli internals.
--force-cfr, a new x264cli option, allows the user to force the old method of timestamp handling. May be useful in case of a source with broken timestamps.
Avisynth, YUV, and Y4M input are all still CFR. LAVF or FFMS2 must be used for VFR support.
Do note that this patch does *not* add VFR ratecontrol yet.
Support for telecined input is also somewhat dubious at the moment.
Large parts of this patch by Mike Gurlitz <mike.gurlitz@gmail.com>, Steven Walters <kemuri9@gmail.com>, and Yusuke Nakamura <muken.the.vfrmaniac@gmail.com>.
MUXERS := $(shell grep -E "(IN|OUT)PUT" config.h)
# Optional muxer module sources
-ifneq ($(findstring VFW_INPUT, $(MUXERS)),)
-SRCCLI += input/vfw.c
-endif
-
ifneq ($(findstring AVS_INPUT, $(MUXERS)),)
SRCCLI += input/avs.c
endif
SRCCLI += input/thread.c
endif
+ifneq ($(findstring LAVF_INPUT, $(MUXERS)),)
+SRCCLI += input/lavf.c
+endif
+
+ifneq ($(findstring FFMS_INPUT, $(MUXERS)),)
+SRCCLI += input/ffms.c
+endif
+
ifneq ($(findstring MP4_OUTPUT, $(MUXERS)),)
SRCCLI += output/mp4.c
endif
$(SONAME): .depend $(OBJS) $(OBJASM)
$(CC) -shared -o $@ $(OBJS) $(OBJASM) $(SOFLAGS) $(LDFLAGS)
-x264$(EXE): $(OBJCLI) libx264.a
- $(CC) -o $@ $+ $(LDFLAGS)
+x264$(EXE): $(OBJCLI) libx264.a
+ $(CC) -o $@ $+ $(LDFLAGS) $(LDFLAGSCLI)
checkasm: tools/checkasm.o libx264.a
$(CC) -o $@ $+ $(LDFLAGS)
param->b_repeat_headers = 1;
param->b_annexb = 1;
param->b_aud = 0;
+ param->b_vfr_input = 1;
}
static int parse_enum( const char *arg, const char * const *names, int *dst )
p->b_repeat_headers = atobool(value);
OPT("annexb")
p->b_annexb = atobool(value);
+ OPT("force-cfr")
+ p->b_vfr_input = !atobool(value);
else
return X264_PARAM_BAD_NAME;
#undef OPT
int i_max_ref0;
int i_max_ref1;
int i_delay; /* Number of frames buffered for B reordering */
+ int i_bframe_delay;
+ int64_t i_bframe_delay_time;
int b_have_lowres; /* Whether 1/2 resolution luma planes are being used */
int b_have_sub8x8_esa;
} frames;
dst->i_type = src->i_type;
dst->i_qpplus1 = src->i_qpplus1;
- dst->i_pts = src->i_pts;
+ dst->i_pts = dst->i_dts = src->i_pts;
dst->param = src->param;
for( i=0; i<3; i++ )
int i_type;
int i_qpplus1;
int64_t i_pts;
+ int64_t i_dts;
x264_param_t *param;
int i_frame; /* Presentation frame number */
- int i_dts; /* Coded frame number */
+ int i_coded; /* Coded frame number */
int i_frame_num; /* 7.4.3 frame_num */
int b_kept_as_ref;
uint8_t b_fdec;
echo ""
echo " --help print this message"
echo " --disable-avs-input disables avisynth input (win32 only)"
+echo " --disable-lavf-input disables libavformat input"
+echo " --disable-ffms-input disables ffmpegsource input"
echo " --disable-mp4-output disables mp4 output (using gpac)"
echo " --disable-pthread disables multithreaded encoding"
echo " --disable-asm disables assembly optimizations on x86 and arm"
rm -f conftest.c
[ -n "$1" ] && echo "#include <$1>" > conftest.c
echo "int main () { $3 return 0; }" >> conftest.c
- $CC conftest.c $CFLAGS $LDFLAGS $2 -o conftest 2>$DEVNULL
+ $CC conftest.c $CFLAGS $LDFLAGS $LDFLAGSCLI $2 -o conftest 2>$DEVNULL
}
as_check() {
DEVNULL='/dev/null'
avs_input="auto"
+lavf_input="auto"
+ffms_input="auto"
mp4_output="auto"
pthread="auto"
asm="yes"
CFLAGS="$CFLAGS -Wall -I."
LDFLAGS="$LDFLAGS"
+LDFLAGSCLI="$LDFLAGSCLI"
ASFLAGS="$ASFLAGS"
HAVE_GETOPT_LONG=1
cross_prefix=""
--disable-asm)
asm="no"
;;
- --enable-avs-input=*)
- avs_input="$optarg"
- if [ "$avs_input" != "auto" -a "$avs_input" != "vfw" -a "$avs_input" != "avs" ] ; then
- echo "unrecognized enable-avis-input option '$avs_input'"
- echo "available options are 'auto', 'avs', or 'vfw'"
- avs_input="auto"
- fi
+ --enable-avs-input)
+ avs_input="auto"
;;
--disable-avs-input)
avs_input="no"
;;
+ --enable-lavf-input)
+ lavf_input="auto"
+ ;;
+ --disable-lavf-input)
+ lavf_input="no"
+ ;;
+ --enable-ffms-input)
+ ffms_input="auto"
+ ;;
+ --disable-ffms-input)
+ ffms_input="no"
+ ;;
--enable-mp4-output)
mp4_output="yes"
;;
LDFLAGS="$LDFLAGS $libpthread"
fi
+if [ "$lavf_input" = "auto" ] ; then
+ lavf_input="no"
+ if [ `pkg-config --exists libavformat libavcodec libswscale 2>$DEVNULL` ] ; then
+ LAVF_LDFLAGS="$LAVF_LDFLAGS $(pkg-config --libs libavformat libavcodec libswscale)"
+ LAVF_CDFLAGS="$LAVF_CFLAGS $(pkg-config --cflags libavformat libavcodec libswscale)"
+ fi
+ if [ -z "$LAVF_LDFLAGS" -a -z "$LAVF_CFLAGS" ]; then
+ LAVF_LDFLAGS="-lavformat -lswscale"
+ for lib in -lpostproc -lavcodec -lavutil -lm -lz -lbz2 $libpthread -lavifil32; do
+ cc_check "" $lib && LAVF_LDFLAGS="$LAVF_LDFLAGS $lib"
+ done
+ fi
+ LAVF_LDFLAGS="-L. $LAVF_LDFLAGS"
+ if cc_check libavformat/avformat.h "$LAVF_CFLAGS $LAVF_LDFLAGS" && \
+ cc_check libswscale/swscale.h "$LAVF_CFLAGS $LAVF_LDFLAGS" ; then
+ # avcodec_decode_video2 is currently the most recently added function that we use; it was added in r18351
+ if cc_check libavformat/avformat.h "$LAVF_CFLAGS $LAVF_LDFLAGS" "avcodec_decode_video2( NULL, NULL, NULL, NULL );" ; then
+ lavf_input="yes"
+ echo "#define LAVF_INPUT" >> config.h
+ LDFLAGSCLI="$LDFLAGSCLI $LAVF_LDFLAGS"
+ [ -n "$LAVF_CFLAGS" ] && CFLAGS="$CFLAGS $LAVF_CFLAGS"
+ else
+ echo "Warning: libavformat is too old, update to ffmpeg r18351+"
+ fi
+ fi
+fi
+
+if [ "$ffms_input" = "auto" ] ; then
+ ffms_input="no"
+ if [ "$lavf_input" = "yes" ] ; then
+ if cc_check ffms.h -lFFMS2 "FFMS_DestroyVideoSource(0);" ; then
+ ffms_input="yes"
+ echo "#define FFMS_INPUT" >> config.h
+ LDFLAGSCLI="$LDFLAGSCLI -lFFMS2"
+ elif cc_check ffms.h "-lFFMS2 $LAVF_LDFLAGS -lstdc++" "FFMS_DestroyVideoSource(0);" ; then
+ ffms_input="yes"
+ echo "#define FFMS_INPUT" >> config.h
+ LDFLAGSCLI="-lFFMS2 $LDFLAGSCLI -lstdc++"
+ fi
+ fi
+fi
+
MP4_LDFLAGS="-lgpac_static"
if [ $SYS = MINGW ]; then
MP4_LDFLAGS="$MP4_LDFLAGS -lwinmm"
fi
if [ "$mp4_output" = "yes" ] ; then
echo "#define MP4_OUTPUT" >> config.h
- LDFLAGS="$LDFLAGS $MP4_LDFLAGS"
+ LDFLAGSCLI="$LDFLAGSCLI $MP4_LDFLAGS"
fi
-if [ "$avs_input" = "auto" -o "$avs_input" = "avs" ] ; then
+if [ "$avs_input" = "auto" ] ; then
+ avs_input=no
if [ $SYS = MINGW ] && cc_check avisynth_c.h ; then
- avs_input="avs"
+ avs_input="yes"
echo "#define AVS_INPUT" >> config.h
echo "#define HAVE_AVISYNTH_C_H" >> config.h
elif [ $SYS = MINGW ] && cc_check extras/avisynth_c.h ; then
- avs_input="avs"
+ avs_input="yes"
echo "#define AVS_INPUT" >> config.h
- else
- avs_input="auto"
- fi
-fi
-if [ "$avs_input" = "auto" -o "$avs_input" = "vfw" ] ; then
- if [ $SYS = MINGW ] && cc_check "stdlib.h" -lvfw32 ; then
- echo "#define VFW_INPUT" >> config.h
- LDFLAGS="$LDFLAGS -lvfw32"
- avs_input="vfw"
- elif [ $SYS = MINGW ] && cc_check "stdlib.h" -lavifil32 ; then
- echo "#define VFW_INPUT" >> config.h
- LDFLAGS="$LDFLAGS -lavifil32"
- avs_input="vfw"
- else
- avs_input="no";
fi
fi
CC=$CC
CFLAGS=$CFLAGS
LDFLAGS=$LDFLAGS
+LDFLAGSCLI=$LDFLAGSCLI
AR=$AR
RANLIB=$RANLIB
STRIP=$STRIP
echo "System: $SYS"
echo "asm: $asm"
echo "avs input: $avs_input"
+echo "lavf input: $lavf_input"
+echo "ffms input: $ffms_input"
echo "mp4 output: $mp4_output"
echo "pthread: $pthread"
echo "debug: $debug"
h->param.i_width, h->param.i_height );
return -1;
}
- if( h->param.i_csp != X264_CSP_I420 && h->param.i_csp != X264_CSP_YV12 )
+ int i_csp = h->param.i_csp & X264_CSP_MASK;
+ if( i_csp != X264_CSP_I420 && i_csp != X264_CSP_YV12 )
{
x264_log( h, X264_LOG_ERROR, "invalid CSP (only I420/YV12 supported)\n" );
return -1;
h->param.rc.i_lookahead = X264_MIN( h->param.rc.i_lookahead, X264_MAX( h->param.i_keyint_max, bufsize*fps ) );
}
+ if( !h->param.i_timebase_num || !h->param.i_timebase_den )
+ {
+ h->param.i_timebase_num = h->param.i_fps_den;
+ h->param.i_timebase_den = h->param.i_fps_num;
+ }
+
h->param.rc.f_qcompress = x264_clip3f( h->param.rc.f_qcompress, 0.0, 1.0 );
if( !h->param.rc.i_lookahead || h->param.i_keyint_max == 1 || h->param.rc.f_qcompress == 1 )
h->param.rc.b_mb_tree = 0;
x264_set_aspect_ratio( h, param, 1 );
x264_reduce_fraction( &h->param.i_fps_num, &h->param.i_fps_den );
+ x264_reduce_fraction( &h->param.i_timebase_num, &h->param.i_timebase_den );
/* Init x264_t */
h->i_frame = -1;
h->frames.i_delay += h->param.i_threads - 1;
h->frames.i_delay = X264_MIN( h->frames.i_delay, X264_LOOKAHEAD_MAX );
h->frames.i_delay += h->param.i_sync_lookahead;
+ h->frames.i_bframe_delay = h->param.i_bframe ? (h->param.i_bframe_pyramid ? 2 : 1) : 0;
h->frames.i_max_ref0 = h->param.i_frame_reference;
h->frames.i_max_ref1 = h->sps->vui.i_num_reorder_frames;
/* look for delay frames -- chain must only contain frames that are disposable */
for( i = 0; h->frames.current[i] && IS_DISPOSABLE( h->frames.current[i]->i_type ); i++ )
- b_hasdelayframe |= h->frames.current[i]->i_dts
+ b_hasdelayframe |= h->frames.current[i]->i_coded
!= h->frames.current[i]->i_frame + h->sps->vui.i_num_reorder_frames;
if( h->param.i_bframe_pyramid != X264_B_PYRAMID_STRICT && !b_hasdelayframe )
fenc->i_frame = h->frames.i_input++;
+ if( h->frames.i_bframe_delay && fenc->i_frame == h->frames.i_bframe_delay )
+ h->frames.i_bframe_delay_time = fenc->i_pts;
+
if( h->frames.b_have_lowres )
{
if( h->param.analyse.i_weighted_pred == X264_WEIGHTP_FAKE || h->param.analyse.i_weighted_pred == X264_WEIGHTP_SMART )
pic_out->i_type = X264_TYPE_P;
else
pic_out->i_type = X264_TYPE_B;
+
pic_out->i_pts = h->fenc->i_pts;
+ pic_out->i_dts = h->fenc->i_dts - h->frames.i_bframe_delay_time;
pic_out->img.i_plane = h->fdec->i_plane;
for(i = 0; i < 3; i++)
}
sps->vui.b_timing_info_present = 0;
- if( param->i_fps_num > 0 && param->i_fps_den > 0)
+ if( param->i_timebase_num > 0 && param->i_timebase_den > 0 )
{
sps->vui.b_timing_info_present = 1;
- sps->vui.i_num_units_in_tick = param->i_fps_den;
- sps->vui.i_time_scale = param->i_fps_num * 2;
- sps->vui.b_fixed_frame_rate = 1;
+ sps->vui.i_num_units_in_tick = param->i_timebase_num;
+ sps->vui.i_time_scale = param->i_timebase_den * 2;
+ sps->vui.b_fixed_frame_rate = !param->b_vfr_input;
}
sps->vui.i_num_reorder_frames = param->i_bframe_pyramid ? 2 : param->i_bframe ? 1 : 0;
/* shift sequence to coded order.
use a small temporary list to avoid shifting the entire next buffer around */
- int i_dts = h->lookahead->next.list[0]->i_frame;
+ int i_coded = h->lookahead->next.list[0]->i_frame;
if( bframes )
{
int index[] = { brefs+1, 1 };
for( i = 0; i < bframes; i++ )
- frames[ index[h->lookahead->next.list[i]->i_type == X264_TYPE_BREF]++ ] = h->lookahead->next.list[i];
+ {
+ int idx = index[h->lookahead->next.list[i]->i_type == X264_TYPE_BREF]++;
+ frames[idx] = h->lookahead->next.list[i];
+ frames[idx]->i_dts = h->lookahead->next.list[idx]->i_pts;
+ }
frames[0] = h->lookahead->next.list[bframes];
+ frames[0]->i_dts = h->lookahead->next.list[0]->i_pts;
memcpy( h->lookahead->next.list, frames, (bframes+1) * sizeof(x264_frame_t*) );
}
for( i = 0; i <= bframes; i++ )
- h->lookahead->next.list[i]->i_dts = i_dts++;
+ h->lookahead->next.list[i]->i_coded = i_coded++;
}
int x264_rc_analyse_slice( x264_t *h )
filter[i++] = all_purpose[j];
}
-static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param )
+static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
FILE *fh = fopen( psz_filename, "r" );
if( !fh )
int i;
for( i = 0; filter[i]; i++ )
{
- fprintf( stderr, "avs [info]: Trying %s... ", filter[i] );
+ fprintf( stderr, "avs [info]: trying %s... ", filter[i] );
if( !h->func.avs_function_exists( h->env, filter[i] ) )
{
fprintf( stderr, "not found\n" );
}
if( !strncasecmp( filter[i], "FFmpegSource", 12 ) )
{
- fprintf( stderr, "Indexing... " );
+ fprintf( stderr, "indexing... " );
fflush( stderr );
}
res = h->func.avs_invoke( h->env, filter[i], arg, NULL );
{
h->func.avs_release_clip( h->clip );
fprintf( stderr, "avs %s\n", !avs_is_yv12( vi ) ? "[warning]: converting input clip to YV12"
- : "[info]: Avisynth 2.6+ detected, forcing conversion to YV12" );
+ : "[info]: avisynth 2.6+ detected, forcing conversion to YV12" );
const char *arg_name[2] = { NULL, "interlaced" };
- AVS_Value arg_arr[2] = { res, avs_new_value_bool( p_param->b_interlaced ) };
+ AVS_Value arg_arr[2] = { res, avs_new_value_bool( info->interlaced ) };
AVS_Value res2 = h->func.avs_invoke( h->env, "ConvertToYV12", avs_new_value_array( arg_arr, 2 ), arg_name );
if( avs_is_error( res2 ) )
{
- fprintf( stderr, "avs [error]: Couldn't convert input clip to YV12\n" );
+ fprintf( stderr, "avs [error]: couldn't convert input clip to YV12\n" );
return -1;
}
h->clip = h->func.avs_take_clip( res2, h->env );
}
h->func.avs_release_value( res );
- p_param->i_width = vi->width;
- p_param->i_height = vi->height;
- p_param->i_fps_num = vi->fps_numerator;
- p_param->i_fps_den = vi->fps_denominator;
+ info->width = vi->width;
+ info->height = vi->height;
+ info->fps_num = vi->fps_numerator;
+ info->fps_den = vi->fps_denominator;
h->num_frames = vi->num_frames;
- p_param->i_csp = X264_CSP_YV12;
-
- fprintf( stderr, "avs [info]: %dx%d @ %.2f fps (%d frames)\n",
- p_param->i_width, p_param->i_height,
- (double)p_param->i_fps_num / p_param->i_fps_den,
- h->num_frames );
+ info->csp = X264_CSP_YV12;
+ info->vfr = 0;
*p_handle = h;
return 0;
--- /dev/null
+/*****************************************************************************
+ * ffms.c: x264 ffmpegsource input module
+ *****************************************************************************
+ * Copyright (C) 2009 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.
+ *****************************************************************************/
+
+#include "muxers.h"
+#include <ffms.h>
+#undef DECLARE_ALIGNED
+#include <libavcodec/avcodec.h>
+#include <libswscale/swscale.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#define SetConsoleTitle(t)
+#endif
+
+typedef struct
+{
+ FFMS_VideoSource *video_source;
+ FFMS_Track *track;
+ int total_frames;
+ struct SwsContext *scaler;
+ int pts_offset_flag;
+ int64_t pts_offset;
+ int reduce_pts;
+ int vfr_input;
+
+ int init_width;
+ int init_height;
+
+ int cur_width;
+ int cur_height;
+ int cur_pix_fmt;
+} ffms_hnd_t;
+
+static int FFMS_CC update_progress( int64_t current, int64_t total, void *private )
+{
+ if( current % 10 )
+ return 0;
+ char buf[200];
+ sprintf( buf, "ffms [info]: indexing input file [%.1f%%]", 100.0 * current / total );
+ fprintf( stderr, "%s \r", buf+5 );
+ SetConsoleTitle( buf );
+ fflush( stderr );
+ return 0;
+}
+
+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 );
+ FFMS_ErrorInfo e;
+ e.BufferSize = 0;
+ int seekmode = opt->seek ? FFMS_SEEK_NORMAL : FFMS_SEEK_LINEAR_NO_RW;
+
+ FFMS_Index *index = NULL;
+ if( opt->index )
+ {
+ struct stat index_s, input_s;
+ if( !stat( opt->index, &index_s ) && !stat( psz_filename, &input_s ) &&
+ input_s.st_mtime < index_s.st_mtime )
+ index = FFMS_ReadIndex( opt->index, &e );
+ }
+ if( !index )
+ {
+ index = FFMS_MakeIndex( psz_filename, 0, 0, NULL, NULL, 0, update_progress, NULL, &e );
+ fprintf( stderr, " \r" );
+ if( !index )
+ {
+ fprintf( stderr, "ffms [error]: could not create index\n" );
+ return -1;
+ }
+ if( opt->index && FFMS_WriteIndex( opt->index, index, &e ) )
+ fprintf( stderr, "ffms [warning]: could not write index file\n" );
+ }
+
+ int trackno = FFMS_GetFirstTrackOfType( index, FFMS_TYPE_VIDEO, &e );
+ if( trackno < 0 )
+ {
+ fprintf( stderr, "ffms [error]: could not find video track\n" );
+ return -1;
+ }
+
+ h->video_source = FFMS_CreateVideoSource( psz_filename, trackno, index, 1, seekmode, &e );
+ if( !h->video_source )
+ {
+ fprintf( stderr, "ffms [error]: could not create video source\n" );
+ return -1;
+ }
+
+ h->track = FFMS_GetTrackFromVideo( h->video_source );
+ const FFMS_TrackTimeBase *timebase = FFMS_GetTimeBase( h->track );
+
+ FFMS_DestroyIndex( index );
+ const FFMS_VideoProperties *videop = FFMS_GetVideoProperties( h->video_source );
+ h->total_frames = videop->NumFrames;
+ info->sar_height = videop->SARDen;
+ info->sar_width = videop->SARNum;
+ info->fps_den = videop->FPSDenominator;
+ info->fps_num = videop->FPSNumerator;
+ info->timebase_num = (int)timebase->Num;
+ h->vfr_input = info->vfr;
+
+ const FFMS_Frame *frame = FFMS_GetFrame( h->video_source, 0, &e );
+ if( !frame )
+ {
+ fprintf( stderr, "ffms [error]: could not read frame 0\n" );
+ return -1;
+ }
+
+ h->init_width = h->cur_width = info->width = frame->EncodedWidth;
+ h->init_height = h->cur_height = info->height = frame->EncodedHeight;
+ h->cur_pix_fmt = frame->EncodedPixelFormat;
+ info->interlaced = frame->InterlacedFrame;
+
+ if( h->cur_pix_fmt != PIX_FMT_YUV420P )
+ fprintf( stderr, "ffms [warning]: converting from %s to YV12\n",
+ avcodec_get_pix_fmt_name( h->cur_pix_fmt ) );
+
+ /* ffms timestamps are in milliseconds. Increasing timebase denominator could cause integer overflow.
+ * Conversely, reducing PTS may lose too much accuracy */
+ if( h->vfr_input )
+ {
+ int64_t timebase_den = (int64_t)timebase->Den * 1000;
+
+ if( timebase_den > INT_MAX )
+ {
+ info->timebase_den = (int)timebase->Den;
+ h->reduce_pts = 1;
+ }
+ else
+ {
+ info->timebase_den = (int)timebase->Den * 1000;
+ h->reduce_pts = 0;
+ }
+ }
+
+ *p_handle = h;
+ return 0;
+}
+
+static int get_frame_total( hnd_t handle )
+{
+ return ((ffms_hnd_t*)handle)->total_frames;
+}
+
+static int check_swscale( ffms_hnd_t *h, const FFMS_Frame *frame, int i_frame )
+{
+ if( h->scaler && h->cur_width == frame->EncodedWidth && h->cur_height == frame->EncodedHeight &&
+ h->cur_pix_fmt == frame->EncodedPixelFormat )
+ return 0;
+ if( h->scaler )
+ {
+ sws_freeContext( h->scaler );
+ fprintf( stderr, "ffms [warning]: stream properties changed to %dx%d, %s at frame %d \n", frame->EncodedWidth,
+ frame->EncodedHeight, avcodec_get_pix_fmt_name( frame->EncodedPixelFormat ), i_frame );
+ h->cur_width = frame->EncodedWidth;
+ h->cur_height = frame->EncodedHeight;
+ h->cur_pix_fmt = frame->EncodedPixelFormat;
+ }
+ h->scaler = sws_getContext( h->cur_width, h->cur_height, h->cur_pix_fmt, h->init_width, h->init_height,
+ PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL );
+ if( !h->scaler )
+ {
+ fprintf( stderr, "ffms [error]: could not open swscale context\n" );
+ return -1;
+ }
+ return 0;
+}
+
+static int read_frame( x264_picture_t *p_pic, hnd_t handle, int i_frame )
+{
+ ffms_hnd_t *h = handle;
+ FFMS_ErrorInfo e;
+ e.BufferSize = 0;
+ const FFMS_Frame *frame = FFMS_GetFrame( h->video_source, i_frame, &e );
+ if( !frame )
+ {
+ fprintf( stderr, "ffms [error]: could not read frame %d\n", i_frame );
+ return -1;
+ }
+
+ if( check_swscale( h, frame, i_frame ) )
+ return -1;
+ /* FFMS_VideoSource has a single FFMS_Frame buffer for all calls to GetFrame.
+ * With threaded input, copying the pointers would result in the data changing during encoding.
+ * FIXME: don't do redundant sws_scales for singlethreaded input, or fix FFMS to allow
+ * multiple FFMS_Frame buffers. */
+ sws_scale( h->scaler, (uint8_t**)frame->Data, (int*)frame->Linesize, 0,
+ frame->EncodedHeight, p_pic->img.plane, p_pic->img.i_stride );
+
+ const FFMS_FrameInfo *info = FFMS_GetFrameInfo( h->track, i_frame );
+
+ if( h->vfr_input )
+ {
+ if( info->PTS == AV_NOPTS_VALUE )
+ {
+ fprintf( stderr, "ffms [error]: invalid timestamp. "
+ "Use --force-cfr and specify a framerate with --fps\n" );
+ return -1;
+ }
+
+ if( !h->pts_offset_flag )
+ {
+ h->pts_offset = info->PTS;
+ h->pts_offset_flag = 1;
+ }
+
+ if( h->reduce_pts )
+ p_pic->i_pts = (int64_t)(((info->PTS - h->pts_offset) / 1000) + 0.5);
+ else
+ p_pic->i_pts = info->PTS - h->pts_offset;
+ }
+ return 0;
+}
+
+static int close_file( hnd_t handle )
+{
+ ffms_hnd_t *h = handle;
+ sws_freeContext( h->scaler );
+ FFMS_DestroyVideoSource( h->video_source );
+ free( h );
+ return 0;
+}
+
+cli_input_t ffms_input = { open_file, get_frame_total, x264_picture_alloc, read_frame, NULL, x264_picture_clean, close_file };
*
* 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
#ifndef X264_INPUT_H
#define X264_INPUT_H
+/* options that are used by only some demuxers */
typedef struct
{
- int (*open_file)( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param );
+ char *index;
+ char *resolution; /* resolution string parsed by raw yuv input */
+ int seek;
+} cli_input_opt_t;
+
+/* properties of the source given by the demuxer */
+typedef struct
+{
+ int csp; /* X264_CSP_YV12 or X264_CSP_I420 */
+ int fps_num;
+ int fps_den;
+ int height;
+ int interlaced;
+ int sar_width;
+ int sar_height;
+ int timebase_num;
+ int timebase_den;
+ int vfr;
+ int width;
+} video_info_t;
+
+typedef struct
+{
+ int (*open_file)( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt );
int (*get_frame_total)( hnd_t handle );
int (*picture_alloc)( x264_picture_t *pic, int i_csp, int i_width, int i_height );
int (*read_frame)( x264_picture_t *p_pic, hnd_t handle, int i_frame );
extern cli_input_t y4m_input;
extern cli_input_t avs_input;
extern cli_input_t thread_input;
+extern cli_input_t lavf_input;
+extern cli_input_t ffms_input;
#endif
--- /dev/null
+/*****************************************************************************
+ * lavf.c: x264 libavformat input module
+ *****************************************************************************
+ * Copyright (C) 2009 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.
+ *****************************************************************************/
+
+#include "muxers.h"
+#undef DECLARE_ALIGNED
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+
+typedef struct
+{
+ AVFormatContext *lavf;
+ int stream_id;
+ int next_frame;
+ int vfr_input;
+ int vertical_flip;
+ struct SwsContext *scaler;
+ int pts_offset_flag;
+ int64_t pts_offset;
+ x264_picture_t *first_pic;
+
+ int init_width;
+ int init_height;
+
+ int cur_width;
+ int cur_height;
+ enum PixelFormat cur_pix_fmt;
+} lavf_hnd_t;
+
+typedef struct
+{
+ AVFrame frame;
+ AVPacket packet;
+} lavf_pic_t;
+
+static int check_swscale( lavf_hnd_t *h, AVCodecContext *c, int i_frame )
+{
+ if( h->scaler && (h->cur_width == c->width) && (h->cur_height == c->height) && (h->cur_pix_fmt == c->pix_fmt) )
+ return 0;
+ if( h->scaler )
+ {
+ sws_freeContext( h->scaler );
+ fprintf( stderr, "lavf [warning]: stream properties changed to %dx%d, %s at frame %d \n",
+ c->width, c->height, avcodec_get_pix_fmt_name( c->pix_fmt ), i_frame );
+ h->cur_width = c->width;
+ h->cur_height = c->height;
+ h->cur_pix_fmt = c->pix_fmt;
+ }
+ h->scaler = sws_getContext( h->cur_width, h->cur_height, h->cur_pix_fmt, h->init_width, h->init_height,
+ PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL );
+ if( !h->scaler )
+ {
+ fprintf( stderr, "lavf [error]: could not open swscale context\n" );
+ return -1;
+ }
+ return 0;
+}
+
+static int read_frame_internal( x264_picture_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( x264_image_t, p_pic->img, h->first_pic->img );
+ p_pic->i_pts = h->first_pic->i_pts;
+ }
+ lavf_input.picture_clean( h->first_pic );
+ free( h->first_pic );
+ h->first_pic = NULL;
+ if( !i_frame )
+ return 0;
+ }
+
+ AVCodecContext *c = h->lavf->streams[h->stream_id]->codec;
+ lavf_pic_t *pic_h = p_pic->opaque;
+ AVPacket *pkt = &pic_h->packet;
+ AVFrame *frame = &pic_h->frame;
+
+ while( i_frame >= h->next_frame )
+ {
+ int finished = 0;
+ while( !finished && av_read_frame( h->lavf, pkt ) >= 0 )
+ if( pkt->stream_index == h->stream_id )
+ {
+ c->reordered_opaque = pkt->pts;
+ if( avcodec_decode_video2( c, frame, &finished, pkt ) < 0 )
+ fprintf( stderr, "lavf [warning]: video decoding failed on frame %d\n", h->next_frame );
+ }
+ if( !finished )
+ {
+ if( avcodec_decode_video2( c, frame, &finished, pkt ) < 0 )
+ fprintf( stderr, "lavf [warning]: video decoding failed on frame %d\n", h->next_frame );
+ if( !finished )
+ return -1;
+ }
+ h->next_frame++;
+ }
+
+ if( check_swscale( h, c, i_frame ) )
+ return -1;
+ /* FIXME: avoid sws_scale where possible (no colorspace conversion). */
+ sws_scale( h->scaler, frame->data, frame->linesize, 0, c->height, p_pic->img.plane, p_pic->img.i_stride );
+
+ if( info )
+ info->interlaced = frame->interlaced_frame;
+
+ if( h->vfr_input )
+ {
+ p_pic->i_pts = 0;
+ if( frame->reordered_opaque != AV_NOPTS_VALUE )
+ p_pic->i_pts = frame->reordered_opaque;
+ else if( pkt->dts != AV_NOPTS_VALUE )
+ p_pic->i_pts = pkt->dts; // for AVI files
+ else if( info )
+ {
+ h->vfr_input = info->vfr = 0;
+ goto exit;
+ }
+ if( !h->pts_offset_flag )
+ {
+ h->pts_offset = p_pic->i_pts;
+ h->pts_offset_flag = 1;
+ }
+ p_pic->i_pts -= h->pts_offset;
+ }
+
+exit:
+ if( pkt->destruct )
+ pkt->destruct( pkt );
+ avcodec_get_frame_defaults( frame );
+ 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 = malloc( sizeof(lavf_hnd_t) );
+ if( !h )
+ return -1;
+ av_register_all();
+ h->scaler = NULL;
+ if( !strcmp( psz_filename, "-" ) )
+ psz_filename = "pipe:";
+
+ if( av_open_input_file( &h->lavf, psz_filename, NULL, 0, NULL ) )
+ {
+ fprintf( stderr, "lavf [error]: could not open input file\n" );
+ return -1;
+ }
+
+ if( av_find_stream_info( h->lavf ) < 0 )
+ {
+ fprintf( stderr, "lavf [error]: could not find input stream info\n" );
+ return -1;
+ }
+
+ int i = 0;
+ while( i < h->lavf->nb_streams && h->lavf->streams[i]->codec->codec_type != CODEC_TYPE_VIDEO )
+ i++;
+ if( i == h->lavf->nb_streams )
+ {
+ fprintf( stderr, "lavf [error]: could not find video stream\n" );
+ return -1;
+ }
+ h->stream_id = i;
+ h->next_frame = 0;
+ h->pts_offset_flag = 0;
+ h->pts_offset = 0;
+ AVCodecContext *c = h->lavf->streams[i]->codec;
+ h->init_width = h->cur_width = info->width = c->width;
+ h->init_height = h->cur_height = info->height = c->height;
+ h->cur_pix_fmt = c->pix_fmt;
+ info->fps_num = h->lavf->streams[i]->r_frame_rate.num;
+ info->fps_den = h->lavf->streams[i]->r_frame_rate.den;
+ info->timebase_num = h->lavf->streams[i]->time_base.num;
+ info->timebase_den = h->lavf->streams[i]->time_base.den;
+ h->vfr_input = info->vfr;
+ h->vertical_flip = 0;
+
+ /* avisynth stores rgb data vertically flipped. */
+ if( !strcasecmp( get_filename_extension( psz_filename ), "avs" ) &&
+ (h->cur_pix_fmt == PIX_FMT_BGRA || h->cur_pix_fmt == PIX_FMT_BGR24) )
+ info->csp |= X264_CSP_VFLIP;
+
+ if( h->cur_pix_fmt != PIX_FMT_YUV420P )
+ fprintf( stderr, "lavf [warning]: converting from %s to YV12\n",
+ avcodec_get_pix_fmt_name( h->cur_pix_fmt ) );
+
+ if( avcodec_open( c, avcodec_find_decoder( c->codec_id ) ) )
+ {
+ fprintf( stderr, "lavf [error]: could not find decoder for video stream\n" );
+ return -1;
+ }
+
+ /* prefetch the first frame and set/confirm flags */
+ h->first_pic = malloc( sizeof(x264_picture_t) );
+ if( !h->first_pic || lavf_input.picture_alloc( h->first_pic, info->csp, info->width, info->height ) )
+ {
+ fprintf( stderr, "lavf [error]: malloc failed\n" );
+ return -1;
+ }
+ else if( read_frame_internal( h->first_pic, h, 0, info ) )
+ return -1;
+
+ info->sar_height = c->sample_aspect_ratio.den;
+ info->sar_width = c->sample_aspect_ratio.num;
+ *p_handle = h;
+
+ return 0;
+}
+
+static int picture_alloc( x264_picture_t *pic, int i_csp, int i_width, int i_height )
+{
+ if( x264_picture_alloc( pic, i_csp, i_width, i_height ) )
+ return -1;
+ lavf_pic_t *pic_h = pic->opaque = malloc( sizeof(lavf_pic_t) );
+ if( !pic_h )
+ return -1;
+ avcodec_get_frame_defaults( &pic_h->frame );
+ av_init_packet( &pic_h->packet );
+ return 0;
+}
+
+/* FIXME */
+static int get_frame_total( hnd_t handle )
+{
+ return 0;
+}
+
+static int read_frame( x264_picture_t *p_pic, hnd_t handle, int i_frame )
+{
+ return read_frame_internal( p_pic, handle, i_frame, NULL );
+}
+
+static void picture_clean( x264_picture_t *pic )
+{
+ free( pic->opaque );
+ x264_picture_clean( pic );
+}
+
+static int close_file( hnd_t handle )
+{
+ lavf_hnd_t *h = handle;
+ sws_freeContext( h->scaler );
+ avcodec_close( h->lavf->streams[h->stream_id]->codec );
+ av_close_input_file( h->lavf );
+ free( h );
+ return 0;
+}
+
+cli_input_t lavf_input = { open_file, get_frame_total, picture_alloc, read_frame, NULL, picture_clean, close_file };
int status;
} thread_input_arg_t;
-static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param )
+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) );
- if( !h || input.picture_alloc( &h->pic, p_param->i_csp, p_param->i_width, p_param->i_height ) )
+ if( !h || input.picture_alloc( &h->pic, info->csp, info->width, info->height ) )
{
fprintf( stderr, "x264 [error]: malloc failed\n" );
return -1;
+++ /dev/null
-/*****************************************************************************
- * vfw.c: x264 avisynth input via VFW module
- *****************************************************************************
- * Copyright (C) 2003-2009 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.
- *****************************************************************************/
-
-#include "muxers.h"
-#include <windows.h>
-#include <vfw.h>
-
-typedef struct
-{
- PAVISTREAM p_avi;
- int width;
- int height;
-} vfw_hnd_t;
-
-static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param )
-{
- FILE *fh = fopen( psz_filename, "r" );
- if( !fh )
- return -1;
- else if( !x264_is_regular_file( fh ) )
- {
- fprintf( stderr, "vfw [error]: VFW input is incompatible with non-regular file `%s'\n", psz_filename );
- return -1;
- }
- fclose( fh );
-
- vfw_hnd_t *h = malloc( sizeof(vfw_hnd_t) );
- if( !h )
- return -1;
- AVISTREAMINFO info;
- int i;
-
- *p_handle = h;
-
- AVIFileInit();
- if( AVIStreamOpenFromFile( &h->p_avi, psz_filename, streamtypeVIDEO, 0, OF_READ, NULL ) )
- {
- AVIFileExit();
- return -1;
- }
-
- if( AVIStreamInfo( h->p_avi, &info, sizeof(AVISTREAMINFO) ) )
- {
- AVIStreamRelease( h->p_avi );
- AVIFileExit();
- return -1;
- }
-
- // check input format
- if( info.fccHandler != MAKEFOURCC('Y', 'V', '1', '2') )
- {
- fprintf( stderr, "vfw [error]: unsupported input format (%c%c%c%c)\n",
- (char)(info.fccHandler & 0xff), (char)((info.fccHandler >> 8) & 0xff),
- (char)((info.fccHandler >> 16) & 0xff), (char)((info.fccHandler >> 24)) );
-
- AVIStreamRelease( h->p_avi );
- AVIFileExit();
-
- return -1;
- }
-
- h->width =
- p_param->i_width = info.rcFrame.right - info.rcFrame.left;
- h->height =
- p_param->i_height = info.rcFrame.bottom - info.rcFrame.top;
- i = gcd( info.dwRate, info.dwScale );
- p_param->i_fps_den = info.dwScale / i;
- p_param->i_fps_num = info.dwRate / i;
- p_param->i_csp = X264_CSP_YV12;
-
- fprintf( stderr, "vfw [info]: %dx%d @ %.2f fps (%d frames)\n",
- p_param->i_width, p_param->i_height,
- (double)p_param->i_fps_num / (double)p_param->i_fps_den,
- (int)info.dwLength );
-
- return 0;
-}
-
-static int get_frame_total( hnd_t handle )
-{
- vfw_hnd_t *h = handle;
- AVISTREAMINFO info;
-
- if( AVIStreamInfo( h->p_avi, &info, sizeof(AVISTREAMINFO) ) )
- return -1;
-
- return info.dwLength;
-}
-
-static int read_frame( x264_picture_t *p_pic, hnd_t handle, int i_frame )
-{
- vfw_hnd_t *h = handle;
- return AVIStreamRead( h->p_avi, i_frame, 1, p_pic->img.plane[0], h->width * h->height * 3 / 2, NULL, NULL );
-}
-
-static int close_file( hnd_t handle )
-{
- vfw_hnd_t *h = handle;
- AVIStreamRelease( h->p_avi );
- AVIFileExit();
- free( h );
- return 0;
-}
-
-cli_input_t avs_input = { open_file, get_frame_total, x264_picture_alloc, read_frame, NULL, x264_picture_clean, close_file };
#define Y4M_FRAME_MAGIC "FRAME"
#define MAX_FRAME_HEADER 80
-static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param )
+static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
y4m_hnd_t *h = malloc( sizeof(y4m_hnd_t) );
int i, n, d;
return -1;
h->next_frame = 0;
+ info->vfr = 0;
if( !strcmp( psz_filename, "-" ) )
h->fh = stdin;
switch( *tokstart++ )
{
case 'W': /* Width. Required. */
- h->width = p_param->i_width = strtol( tokstart, &tokend, 10 );
+ h->width = info->width = strtol( tokstart, &tokend, 10 );
tokstart=tokend;
break;
case 'H': /* Height. Required. */
- h->height = p_param->i_height = strtol( tokstart, &tokend, 10 );
+ h->height = info->height = strtol( tokstart, &tokend, 10 );
tokstart=tokend;
break;
case 'C': /* Color space */
if( strncmp( "420", tokstart, 3 ) )
{
- fprintf( stderr, "Colorspace unhandled\n" );
+ fprintf( stderr, "y4m [error]: colorspace unhandled\n" );
return -1;
}
tokstart = strchr( tokstart, 0x20 );
case 'b':
case 'm':
default:
- fprintf( stderr, "Warning, this sequence might be interlaced\n" );
+ info->interlaced = 1;
}
break;
case 'F': /* Frame rate - 0:0 if unknown */
if( sscanf( tokstart, "%d:%d", &n, &d ) == 2 && n && d )
{
x264_reduce_fraction( &n, &d );
- p_param->i_fps_num = n;
- p_param->i_fps_den = 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, "%d:%d", &n, &d ) == 2 && n && d && !p_param->vui.i_sar_width && !p_param->vui.i_sar_height )
+ if( sscanf( tokstart, "%d:%d", &n, &d ) == 2 && n && d )
{
x264_reduce_fraction( &n, &d );
- p_param->vui.i_sar_width = n;
- p_param->vui.i_sar_height = d;
+ info->sar_width = n;
+ info->sar_height = d;
}
tokstart = strchr( tokstart, 0x20 );
break;
strncmp( "420MPEG2",tokstart, 8 ) &&
strncmp( "420PALDV",tokstart, 8 ) )
{
- fprintf( stderr, "Unsupported extended colorspace\n" );
+ fprintf( stderr, "y4m [error]: unsupported extended colorspace\n" );
return -1;
}
}
}
}
- fprintf( stderr, "yuv4mpeg: %ix%i@%i/%ifps, %i:%i\n",
- h->width, h->height, p_param->i_fps_num, p_param->i_fps_den,
- p_param->vui.i_sar_width, p_param->vui.i_sar_height );
-
*p_handle = h;
return 0;
}
header[slen] = 0;
if( strncmp( header, Y4M_FRAME_MAGIC, slen ) )
{
- fprintf( stderr, "Bad header magic (%"PRIx32" <=> %s)\n",
+ fprintf( stderr, "y4m [error]: bad header magic (%"PRIx32" <=> %s)\n",
M32(header), header );
return -1;
}
i++;
if( i == MAX_FRAME_HEADER )
{
- fprintf( stderr, "Bad frame header!\n" );
+ fprintf( stderr, "y4m [error]: bad frame header!\n" );
return -1;
}
h->frame_header_len = i+slen+1;
int next_frame;
} yuv_hnd_t;
-static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param )
+static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
yuv_hnd_t *h = malloc( sizeof(yuv_hnd_t) );
if( !h )
return -1;
- h->width = p_param->i_width;
- h->height = p_param->i_height;
+
+ if( !opt->resolution )
+ {
+ /* try to parse the file name */
+ char *p;
+ for( p = psz_filename; *p; p++ )
+ if( *p >= '0' && *p <= '9' && sscanf( p, "%ux%u", &info->width, &info->height ) == 2 )
+ break;
+ }
+ else
+ sscanf( opt->resolution, "%ux%u", &info->width, &info->height );
+ if( !info->width || !info->height )
+ {
+ fprintf( stderr, "yuv [error]: rawyuv input requires a resolution.\n" );
+ return -1;
+ }
+
h->next_frame = 0;
+ info->vfr = 0;
+ h->width = info->width;
+ h->height = info->height;
if( !strcmp( psz_filename, "-" ) )
h->fh = stdin;
/*****************************************************************************
* flv.c:
*****************************************************************************
- * Copyright (C) 2009 Kieran Kunhya
+ * Copyright (C) 2009 Kieran Kunhya <kieran@kunhya.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
#include "muxers.h"
#include "flv_bytestream.h"
+
#define CHECK(x)\
do {\
if( (x) < 0 )\
{
flv_buffer *c;
- uint8_t b_sps;
- uint8_t b_pps;
uint8_t *sei;
int sei_len;
int64_t i_fps_num;
int64_t i_fps_den;
- int64_t i_init_delay;
int64_t i_framenum;
- double d_mspf;
+ int i_init_delay;
+ int i_delay_time;
+ uint64_t i_framerate_pos;
uint64_t i_duration_pos;
uint64_t i_filesize_pos;
uint64_t i_bitrate_pos;
uint8_t b_write_length;
+ int64_t i_init_delta;
+ int64_t i_prev_timestamps[2];
+
+ int i_timebase_num;
+ int i_timebase_den;
+ int b_vfr_input;
unsigned start;
} flv_hnd_t;
x264_put_amf_double( c, p_param->i_height );
x264_put_amf_string( c, "framerate" );
- x264_put_amf_double( c, (double)p_param->i_fps_num / p_param->i_fps_den );
+
+ if( !p_param->b_vfr_input )
+ x264_put_amf_double( c, (double)p_param->i_fps_num / p_param->i_fps_den );
+ else
+ {
+ p_flv->i_framerate_pos = c->d_cur + c->d_total + 1;
+ x264_put_amf_double( c, 0 ); // written at end of encoding
+ }
x264_put_amf_string( c, "videocodecid" );
x264_put_amf_double( c, FLV_CODECID_H264 );
x264_put_amf_string( c, "duration" );
- p_flv->i_duration_pos = c->d_cur + c->d_total + 1; // + 1 because of the following AMF_DATA_TYPE_NUMBER byte
+ p_flv->i_duration_pos = c->d_cur + c->d_total + 1;
x264_put_amf_double( c, 0 ); // written at end of encoding
x264_put_amf_string( c, "filesize" );
p_flv->i_fps_num = p_param->i_fps_num;
p_flv->i_fps_den = p_param->i_fps_den;
+ p_flv->i_timebase_num = p_param->i_timebase_num;
+ p_flv->i_timebase_den = p_param->i_timebase_den;
p_flv->i_init_delay = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0;
- p_flv->d_mspf = 1000 * (double)p_flv->i_fps_den / p_flv->i_fps_num;
+ p_flv->b_vfr_input = p_param->b_vfr_input;
- fprintf( stderr, "flv [info]: initial delay %i frames\n",
- (int)p_flv->i_init_delay );
return 0;
}
-static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
+static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
- uint64_t dts = (uint64_t)p_flv->i_framenum * p_flv->d_mspf;
- uint64_t pts = (uint64_t)p_picture->i_pts * p_flv->d_mspf / p_flv->i_fps_den;
- uint64_t offset = p_flv->i_init_delay * p_flv->d_mspf + pts - dts;
- uint8_t type = p_nalu[4] & 0x1f;
- switch( type )
- {
- // sps
- case 0x07:
- if( !p_flv->b_sps )
- {
- uint8_t *sps = p_nalu + 4;
-
- x264_put_byte( c, FLV_TAG_TYPE_VIDEO );
- x264_put_be24( c, 0 ); // rewrite later, pps size unknown
- x264_put_be24( c, 0 ); // timestamp
- x264_put_byte( c, 0 ); // timestamp extended
- x264_put_be24( c, 0 ); // StreamID - Always 0
- p_flv->start = c->d_cur; // needed for overwriting length
-
- x264_put_byte( c, 7 | FLV_FRAME_KEY ); // Frametype and CodecID
- x264_put_byte( c, 0 ); // AVC sequence header
- x264_put_be24( c, 0 ); // composition time
-
- x264_put_byte( c, 1 ); // version
- x264_put_byte( c, sps[1] ); // profile
- x264_put_byte( c, sps[2] ); // profile
- x264_put_byte( c, sps[3] ); // level
- x264_put_byte( c, 0xff ); // 6 bits reserved (111111) + 2 bits nal size length - 1 (11)
- x264_put_byte( c, 0xe1 ); // 3 bits reserved (111) + 5 bits number of sps (00001)
-
- x264_put_be16( c, i_size - 4 );
- flv_append_data( c, sps, i_size - 4 );
-
- p_flv->b_sps = 1;
- }
- break;
-
- // pps
- case 0x08:
- if( !p_flv->b_pps )
- {
- x264_put_byte( c, 1 ); // number of pps
- x264_put_be16( c, i_size - 4 );
- flv_append_data( c, p_nalu + 4, i_size - 4 );
-
- // rewrite data length info
- unsigned length = c->d_cur - p_flv->start;
- rewrite_amf_be24( c, length, p_flv->start - 10 );
- x264_put_be32( c, length + 11 ); // Last tag size
-
- p_flv->b_pps = 1;
- }
- break;
-
- // slice
- case 0x1:
- case 0x5:
- if( !p_flv->b_write_length )
- {
- // A new frame - write packet header
- x264_put_byte( c, FLV_TAG_TYPE_VIDEO );
- x264_put_be24( c, 0 ); // calculated later
- x264_put_be24( c, dts );
- x264_put_byte( c, dts >> 24 );
- x264_put_be24( c, 0 );
-
- p_flv->start = c->d_cur;
- x264_put_byte( c, p_picture->i_type == X264_TYPE_IDR ? FLV_FRAME_KEY : FLV_FRAME_INTER );
- x264_put_byte( c, 1 ); // AVC NALU
- x264_put_be24( c, offset );
-
- p_flv->b_write_length = 1;
- }
- if( p_flv->sei )
- {
- flv_append_data( c, p_flv->sei, p_flv->sei_len );
- free( p_flv->sei );
- p_flv->sei = NULL;
- }
- flv_append_data( c, p_nalu, i_size );
- break;
- // sei
- case 0x6:
- /* It is within the spec to write this as-is but for
- * mplayer/ffmpeg playback this is deferred until before the first frame */
-
- p_flv->sei = malloc( i_size );
- if( !p_flv->sei )
- return -1;
- p_flv->sei_len = i_size;
-
- memcpy( p_flv->sei, p_nalu, i_size );
- break;
- }
- return i_size;
+ int sei_size = p_nal[0].i_payload;
+ int sps_size = p_nal[1].i_payload;
+ int pps_size = p_nal[2].i_payload;
+
+ // SEI
+ /* It is within the spec to write this as-is but for
+ * mplayer/ffmpeg playback this is deferred until before the first frame */
+
+ p_flv->sei = malloc( sei_size );
+ if( !p_flv->sei )
+ return -1;
+ p_flv->sei_len = sei_size;
+
+ memcpy( p_flv->sei, p_nal[0].p_payload, sei_size );
+
+ // SPS
+ uint8_t *sps = p_nal[1].p_payload + 4;
+
+ x264_put_byte( c, FLV_TAG_TYPE_VIDEO );
+ x264_put_be24( c, 0 ); // rewrite later
+ x264_put_be24( c, 0 ); // timestamp
+ x264_put_byte( c, 0 ); // timestamp extended
+ x264_put_be24( c, 0 ); // StreamID - Always 0
+ p_flv->start = c->d_cur; // needed for overwriting length
+
+ x264_put_byte( c, 7 | FLV_FRAME_KEY ); // Frametype and CodecID
+ x264_put_byte( c, 0 ); // AVC sequence header
+ x264_put_be24( c, 0 ); // composition time
+
+ x264_put_byte( c, 1 ); // version
+ x264_put_byte( c, sps[1] ); // profile
+ x264_put_byte( c, sps[2] ); // profile
+ x264_put_byte( c, sps[3] ); // level
+ x264_put_byte( c, 0xff ); // 6 bits reserved (111111) + 2 bits nal size length - 1 (11)
+ x264_put_byte( c, 0xe1 ); // 3 bits reserved (111) + 5 bits number of sps (00001)
+
+ x264_put_be16( c, sps_size - 4 );
+ flv_append_data( c, sps, sps_size - 4 );
+
+ // PPS
+ x264_put_byte( c, 1 ); // number of pps
+ x264_put_be16( c, pps_size - 4 );
+ flv_append_data( c, p_nal[2].p_payload + 4, pps_size - 4 );
+
+ // rewrite data length info
+ unsigned length = c->d_cur - p_flv->start;
+ rewrite_amf_be24( c, length, p_flv->start - 10 );
+ x264_put_be32( c, length + 11 ); // Last tag size
+ CHECK( flv_flush_data( c ) );
+
+ return sei_size + sps_size + pps_size;
}
-static int set_eop( hnd_t handle, x264_picture_t *p_picture )
+static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
- if( p_flv->b_write_length )
+ int64_t dts;
+ int64_t cts;
+ int64_t offset;
+
+ if( !p_flv->i_framenum )
+ p_flv->i_delay_time = p_picture->i_dts;
+
+ if( !p_flv->i_init_delay )
+ dts = cts = (int64_t)((p_picture->i_pts * 1000 * p_flv->i_timebase_num / p_flv->i_timebase_den) + 0.5);
+ else
+ {
+ // Use DTS compression
+ dts = p_picture->i_dts - p_flv->i_delay_time;
+
+ if( p_flv->i_framenum == 1 )
+ p_flv->i_init_delta = p_picture->i_dts - p_flv->i_delay_time;
+
+ if( p_flv->i_framenum > p_flv->i_init_delay )
+ {
+ dts = p_flv->i_prev_timestamps[ (p_flv->i_framenum - p_flv->i_init_delay) % p_flv->i_init_delay ];
+ dts = (int64_t)((dts * 1000 * p_flv->i_timebase_num / p_flv->i_timebase_den) + 0.5);
+ }
+ else if( p_flv->i_init_delta )
+ {
+ // Compressed DTSs might not fit in input timescale
+ double compressed_dts;
+ compressed_dts = (p_flv->i_framenum * ((double)p_flv->i_init_delta / (2 * p_flv->i_init_delay)));
+ dts = (int64_t)((compressed_dts * 1000 * p_flv->i_timebase_num / p_flv->i_timebase_den) + 0.5);
+ }
+
+ p_flv->i_prev_timestamps[ p_flv->i_framenum % p_flv->i_init_delay ] = p_picture->i_dts - p_flv->i_delay_time;
+
+ cts = p_picture->i_pts;
+ cts = (int64_t)((cts * 1000 * p_flv->i_timebase_num / p_flv->i_timebase_den) + 0.5);
+ }
+
+ offset = cts - dts;
+
+ assert( cts >= dts );
+
+ // A new frame - write packet header
+ x264_put_byte( c, FLV_TAG_TYPE_VIDEO );
+ x264_put_be24( c, 0 ); // calculated later
+ x264_put_be24( c, dts );
+ x264_put_byte( c, dts >> 24 );
+ x264_put_be24( c, 0 );
+
+ p_flv->start = c->d_cur;
+ x264_put_byte( c, p_picture->i_type == X264_TYPE_IDR ? FLV_FRAME_KEY : FLV_FRAME_INTER );
+ x264_put_byte( c, 1 ); // AVC NALU
+ x264_put_be24( c, offset );
+
+ if( p_flv->sei )
{
- unsigned length = c->d_cur - p_flv->start;
- rewrite_amf_be24( c, length, p_flv->start - 10 );
- x264_put_be32( c, 11 + length ); // Last tag size
- CHECK( flv_flush_data( c ) );
- p_flv->b_write_length = 0;
+ flv_append_data( c, p_flv->sei, p_flv->sei_len );
+ free( p_flv->sei );
+ p_flv->sei = NULL;
}
+ flv_append_data( c, p_nalu, i_size );
+
+ unsigned length = c->d_cur - p_flv->start;
+ rewrite_amf_be24( c, length, p_flv->start - 10 );
+ x264_put_be32( c, 11 + length ); // Last tag size
+ CHECK( flv_flush_data( c ) );
+
p_flv->i_framenum++;
- return 0;
+ return i_size;
}
static void rewrite_amf_double( FILE *fp, uint64_t position, double value )
fwrite( &x, 8, 1, fp );
}
-static int close_file( hnd_t handle )
+static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
CHECK( flv_flush_data( c ) );
+ double total_duration = (double)(2 * largest_pts - second_largest_pts) * p_flv->i_timebase_num / p_flv->i_timebase_den;
+
if( x264_is_regular_file( c->fp ) )
{
- double duration = p_flv->i_fps_den * p_flv->i_framenum / p_flv->i_fps_num;
+ double framerate;
uint64_t filesize = ftell( c->fp );
- rewrite_amf_double( c->fp, p_flv->i_duration_pos, duration );
+
+ if( p_flv->i_framerate_pos )
+ {
+ framerate = (double)p_flv->i_framenum / total_duration;
+ rewrite_amf_double( c->fp, p_flv->i_framerate_pos, framerate );
+ }
+
+ rewrite_amf_double( c->fp, p_flv->i_duration_pos, total_duration );
rewrite_amf_double( c->fp, p_flv->i_filesize_pos, filesize );
- rewrite_amf_double( c->fp, p_flv->i_bitrate_pos, filesize * 8 / ( duration * 1000 ) );
+ rewrite_amf_double( c->fp, p_flv->i_bitrate_pos, filesize * 8 / ( total_duration * 1000 ) );
}
fclose( c->fp );
return 0;
}
-cli_output_t flv_output = { open_file, set_param, write_nalu, set_eop, close_file };
+cli_output_t flv_output = { open_file, set_param, write_headers, write_frame, close_file };
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*****************************************************************************/
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-#include "common/common.h"
+#include "muxers.h"
#include "flv_bytestream.h"
uint64_t dbl2int( double value )
{
mk_writer *w;
- uint8_t *sps, *pps;
- int sps_len, pps_len;
-
int width, height, d_width, d_height;
int64_t frame_duration;
- int fps_num;
- int b_header_written;
char b_writing_frame;
-} mkv_hnd_t;
-
-static int write_header( mkv_hnd_t *p_mkv )
-{
- int ret;
- uint8_t *avcC;
- int avcC_len;
-
- if( !p_mkv->sps || !p_mkv->pps ||
- !p_mkv->width || !p_mkv->height ||
- !p_mkv->d_width || !p_mkv->d_height )
- return -1;
-
- avcC_len = 5 + 1 + 2 + p_mkv->sps_len + 1 + 2 + p_mkv->pps_len;
- avcC = malloc( avcC_len );
- if( !avcC )
- return -1;
-
- avcC[0] = 1;
- avcC[1] = p_mkv->sps[1];
- avcC[2] = p_mkv->sps[2];
- avcC[3] = p_mkv->sps[3];
- avcC[4] = 0xff; // nalu size length is four bytes
- avcC[5] = 0xe1; // one sps
-
- avcC[6] = p_mkv->sps_len >> 8;
- avcC[7] = p_mkv->sps_len;
+ int i_timebase_num;
+ int i_timebase_den;
- memcpy( avcC+8, p_mkv->sps, p_mkv->sps_len );
-
- avcC[8+p_mkv->sps_len] = 1; // one pps
- avcC[9+p_mkv->sps_len] = p_mkv->pps_len >> 8;
- avcC[10+p_mkv->sps_len] = p_mkv->pps_len;
-
- memcpy( avcC+11+p_mkv->sps_len, p_mkv->pps, p_mkv->pps_len );
-
- ret = mk_writeHeader( p_mkv->w, "x264", "V_MPEG4/ISO/AVC",
- avcC, avcC_len, p_mkv->frame_duration, 50000,
- p_mkv->width, p_mkv->height,
- p_mkv->d_width, p_mkv->d_height );
-
- free( avcC );
-
- p_mkv->b_header_written = 1;
-
- return ret;
-}
+} mkv_hnd_t;
static int open_file( char *psz_filename, hnd_t *p_handle )
{
mkv_hnd_t *p_mkv = handle;
int64_t dw, dh;
- if( p_param->i_fps_num > 0 )
+ if( p_param->i_fps_num > 0 && !p_param->b_vfr_input )
{
p_mkv->frame_duration = (int64_t)p_param->i_fps_den *
(int64_t)1000000000 / p_param->i_fps_num;
- p_mkv->fps_num = p_param->i_fps_num;
}
else
{
p_mkv->frame_duration = 0;
- p_mkv->fps_num = 1;
}
p_mkv->width = p_param->i_width;
p_mkv->d_width = (int)dw;
p_mkv->d_height = (int)dh;
+ p_mkv->i_timebase_num = p_param->i_timebase_num;
+ p_mkv->i_timebase_den = p_param->i_timebase_den;
return 0;
}
-static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
+static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
mkv_hnd_t *p_mkv = handle;
- uint8_t type = p_nalu[4] & 0x1f;
- uint8_t dsize[4];
- int psize;
- switch( type )
+ int sei_size = p_nal[0].i_payload;
+ int sps_size = p_nal[1].i_payload - 4;
+ int pps_size = p_nal[2].i_payload - 4;
+
+ uint8_t *sei = p_nal[0].p_payload;
+ uint8_t *sps = p_nal[1].p_payload + 4;
+ uint8_t *pps = p_nal[2].p_payload + 4;
+
+ int ret;
+ uint8_t *avcC;
+ int avcC_len;
+
+ if( !p_mkv->width || !p_mkv->height ||
+ !p_mkv->d_width || !p_mkv->d_height )
+ return -1;
+
+ avcC_len = 5 + 1 + 2 + sps_size + 1 + 2 + pps_size;
+ avcC = malloc( avcC_len );
+ if( !avcC )
+ return -1;
+
+ avcC[0] = 1;
+ avcC[1] = sps[1];
+ avcC[2] = sps[2];
+ avcC[3] = sps[3];
+ avcC[4] = 0xff; // nalu size length is four bytes
+ avcC[5] = 0xe1; // one sps
+
+ avcC[6] = sps_size >> 8;
+ avcC[7] = sps_size;
+
+ memcpy( avcC+8, sps, sps_size );
+
+ avcC[8+sps_size] = 1; // one pps
+ avcC[9+sps_size] = pps_size >> 8;
+ avcC[10+sps_size] = pps_size;
+
+ memcpy( avcC+11+sps_size, pps, pps_size );
+
+ ret = mk_writeHeader( p_mkv->w, "x264", "V_MPEG4/ISO/AVC",
+ avcC, avcC_len, p_mkv->frame_duration, 50000,
+ p_mkv->width, p_mkv->height,
+ p_mkv->d_width, p_mkv->d_height );
+
+ free( avcC );
+
+ // SEI
+
+ if( !p_mkv->b_writing_frame )
{
- // sps
- case 0x07:
- if( !p_mkv->sps )
- {
- p_mkv->sps = malloc( i_size - 4 );
- if( !p_mkv->sps )
- return -1;
- p_mkv->sps_len = i_size - 4;
- memcpy( p_mkv->sps, p_nalu + 4, i_size - 4 );
- }
- break;
-
- // pps
- case 0x08:
- if( !p_mkv->pps )
- {
- p_mkv->pps = malloc( i_size - 4 );
- if( !p_mkv->pps )
- return -1;
- p_mkv->pps_len = i_size - 4;
- memcpy( p_mkv->pps, p_nalu + 4, i_size - 4 );
- }
- break;
-
- // slice, sei
- case 0x1:
- case 0x5:
- case 0x6:
- if( !p_mkv->b_writing_frame )
- {
- if( mk_start_frame( p_mkv->w ) < 0 )
- return -1;
- p_mkv->b_writing_frame = 1;
- }
- psize = i_size - 4;
- dsize[0] = psize >> 24;
- dsize[1] = psize >> 16;
- dsize[2] = psize >> 8;
- dsize[3] = psize;
- if( mk_add_frame_data( p_mkv->w, dsize, 4 ) < 0 ||
- mk_add_frame_data( p_mkv->w, p_nalu + 4, i_size - 4 ) < 0 )
- return -1;
- break;
-
- default:
- break;
+ if( mk_start_frame( p_mkv->w ) < 0 )
+ return -1;
+ p_mkv->b_writing_frame = 1;
}
-
- if( !p_mkv->b_header_written && p_mkv->pps && p_mkv->sps &&
- write_header( p_mkv ) < 0 )
+ if( mk_add_frame_data( p_mkv->w, sei, sei_size ) < 0 )
return -1;
- return i_size;
+ return sei_size + sps_size + pps_size;
}
-static int set_eop( hnd_t handle, x264_picture_t *p_picture )
+static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
mkv_hnd_t *p_mkv = handle;
- int64_t i_stamp = (int64_t)(p_picture->i_pts * 1e9 / p_mkv->fps_num);
+
+ if( !p_mkv->b_writing_frame )
+ {
+ if( mk_start_frame( p_mkv->w ) < 0 )
+ return -1;
+ p_mkv->b_writing_frame = 1;
+ }
+
+ if( mk_add_frame_data( p_mkv->w, p_nalu, i_size ) < 0 )
+ return -1;
+
+ int64_t i_stamp = (int64_t)((p_picture->i_pts * 1e9 * p_mkv->i_timebase_num / p_mkv->i_timebase_den) + 0.5);
p_mkv->b_writing_frame = 0;
- return mk_set_frame_flags( p_mkv->w, i_stamp, p_picture->i_type == X264_TYPE_IDR );
+ if( mk_set_frame_flags( p_mkv->w, i_stamp, p_picture->i_type == X264_TYPE_IDR ) < 0 )
+ return -1;
+
+ return i_size;
}
-static int close_file( hnd_t handle )
+static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
mkv_hnd_t *p_mkv = handle;
int ret;
+ int64_t i_last_delta;
- if( p_mkv->sps )
- free( p_mkv->sps );
- if( p_mkv->pps )
- free( p_mkv->pps );
+ i_last_delta = (int64_t)(((largest_pts - second_largest_pts) * p_mkv->i_timebase_num / p_mkv->i_timebase_den) + 0.5);
- ret = mk_close( p_mkv->w );
+ ret = mk_close( p_mkv->w, i_last_delta );
free( p_mkv );
return ret;
}
-cli_output_t mkv_output = { open_file, set_param, write_nalu, set_eop, close_file };
+cli_output_t mkv_output = { open_file, set_param, write_headers, write_frame, close_file };
return mk_append_context_data( w->frame, data, size );
}
-int mk_close( mk_writer *w )
+int mk_close( mk_writer *w, int64_t last_delta )
{
int ret = 0;
if( mk_flush_frame( w ) < 0 || mk_close_cluster( w ) < 0 )
if( w->wrote_header && x264_is_regular_file( w->fp ) )
{
fseek( w->fp, w->duration_ptr, SEEK_SET );
- if( mk_write_float_raw( w->root, (float)((double)(w->max_frame_tc+w->def_duration) / w->timescale) ) < 0 ||
+ int64_t last_frametime = w->def_duration ? w->def_duration : last_delta;
+ int64_t total_duration = w->max_frame_tc+last_frametime;
+ if( mk_write_float_raw( w->root, (float)((double)total_duration / w->timescale) ) < 0 ||
mk_flush_context_data( w->root ) < 0 )
ret = -1;
}
int mk_start_frame( mk_writer *w );
int mk_add_frame_data( mk_writer *w, const void *data, unsigned size );
int mk_set_frame_flags( mk_writer *w, int64_t timestamp, int keyframe );
-int mk_close( mk_writer *w );
+int mk_close( mk_writer *w, int64_t last_delta );
#endif
GF_ISOSample *p_sample;
int i_track;
uint32_t i_descidx;
- int i_time_inc;
int i_time_res;
+ int64_t i_time_inc;
int i_numframe;
int i_init_delay;
- uint8_t b_sps;
- uint8_t b_pps;
+ int i_delay_time;
+
+ int64_t i_prev_timestamps[2];
+ int64_t i_init_delta;
} mp4_hnd_t;
static void recompute_bitrate_mp4( GF_ISOFile *p_file, int i_track )
for( i = 0; i < count; i++ )
{
GF_ISOSample *samp = gf_isom_get_sample_info( p_file, i_track, i+1, &di, &offset );
-
- if( samp->dataLength>esd->decoderConfig->bufferSizeDB )
- esd->decoderConfig->bufferSizeDB = samp->dataLength;
+ if( !samp )
+ {
+ fprintf( stderr, "mp4 [error]: failure reading back frame %u\n", i );
+ break;
+ }
if( esd->decoderConfig->bufferSizeDB < samp->dataLength )
esd->decoderConfig->bufferSizeDB = samp->dataLength;
gf_odf_desc_del( (GF_Descriptor*)esd );
}
-static int close_file( hnd_t handle )
+static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
mp4_hnd_t *p_mp4 = handle;
+ uint64_t total_duration = 0;
if( !p_mp4 )
return 0;
if( p_mp4->p_file )
{
+ /* The mdhd duration is defined as CTS[final] - CTS[0] + duration of last frame.
+ * The mdhd duration (in seconds) should be able to be longer than the tkhd duration since the track is managed by edts.
+ * So, if mdhd duration is equal to the last DTS or less, we give the last composition time delta to the last sample duration.
+ * And then, the mdhd duration is updated, but it time-wise doesn't give the actual duration.
+ * The tkhd duration is the actual track duration. */
+ uint64_t mdhd_duration = (2 * largest_pts - second_largest_pts - p_mp4->i_delay_time) * p_mp4->i_time_inc;
+ total_duration = gf_isom_get_media_duration( p_mp4->p_file, p_mp4->i_track );
+ if( mdhd_duration != total_duration )
+ {
+ uint64_t last_dts = gf_isom_get_sample_dts( p_mp4->p_file, p_mp4->i_track, p_mp4->i_numframe );
+ uint32_t last_duration = (uint32_t)( mdhd_duration > last_dts ? mdhd_duration - last_dts : (largest_pts - second_largest_pts) * p_mp4->i_time_inc );
+ gf_isom_set_last_sample_duration( p_mp4->p_file, p_mp4->i_track, last_duration );
+ total_duration = gf_isom_get_media_duration( p_mp4->p_file, p_mp4->i_track );
+ }
+
+ /* Write an Edit Box if the first CTS offset is positive.
+ * A media_time is given by not the mvhd timescale but rather the mdhd timescale.
+ * The reason is that an Edit Box maps the presentation time-line to the media time-line.
+ * Any demuxers should follow the Edit Box if it exists. */
+ GF_ISOSample *sample = gf_isom_get_sample_info( p_mp4->p_file, p_mp4->i_track, 1, NULL, NULL );
+ if( sample->CTS_Offset > 0 )
+ {
+ uint32_t mvhd_timescale = gf_isom_get_timescale( p_mp4->p_file );
+ uint64_t tkhd_duration = (uint64_t)( mdhd_duration * ( (double)mvhd_timescale / p_mp4->i_time_res ) );
+ gf_isom_append_edit_segment( p_mp4->p_file, p_mp4->i_track, tkhd_duration, sample->CTS_Offset, GF_ISOM_EDIT_NORMAL );
+ }
+ gf_isom_sample_del( &sample );
+
recompute_bitrate_mp4( p_mp4->p_file, p_mp4->i_track );
gf_isom_set_pl_indication( p_mp4->p_file, GF_ISOM_PL_VISUAL, 0x15 );
gf_isom_set_storage_mode( p_mp4->p_file, GF_ISOM_STORE_FLAT );
if( !(p_mp4->p_sample = gf_isom_sample_new()) )
{
- close_file( p_mp4 );
+ close_file( p_mp4, 0, 0 );
return -1;
}
{
mp4_hnd_t *p_mp4 = handle;
+ p_mp4->i_time_res = p_param->i_timebase_den;
+ p_mp4->i_time_inc = p_param->i_timebase_num;
+
+ p_mp4->i_init_delay = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0;
+
p_mp4->i_track = gf_isom_new_track( p_mp4->p_file, 0, GF_ISOM_MEDIA_VISUAL,
- p_param->i_fps_num );
+ p_mp4->i_time_res );
p_mp4->p_config = gf_odf_avc_cfg_new();
gf_isom_avc_config_new( p_mp4->p_file, p_mp4->i_track, p_mp4->p_config,
if( !p_mp4->p_sample->data )
return -1;
- p_mp4->i_time_res = p_param->i_fps_num;
- p_mp4->i_time_inc = p_param->i_fps_den;
- p_mp4->i_init_delay = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0;
- p_mp4->i_init_delay *= p_mp4->i_time_inc;
- fprintf( stderr, "mp4 [info]: initial delay %d (scale %d)\n",
- p_mp4->i_init_delay, p_mp4->i_time_res );
-
return 0;
}
-static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
+static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
mp4_hnd_t *p_mp4 = handle;
GF_AVCConfigSlot *p_slot;
- uint8_t type = p_nalu[4] & 0x1f;
- int psize;
- switch( type )
- {
- // sps
- case 0x07:
- if( !p_mp4->b_sps )
- {
- p_mp4->p_config->configurationVersion = 1;
- p_mp4->p_config->AVCProfileIndication = p_nalu[5];
- p_mp4->p_config->profile_compatibility = p_nalu[6];
- p_mp4->p_config->AVCLevelIndication = p_nalu[7];
- p_slot = malloc( sizeof(GF_AVCConfigSlot) );
- if( !p_slot )
- return -1;
- p_slot->size = i_size - 4;
- p_slot->data = malloc( p_slot->size );
- if( !p_slot->data )
- return -1;
- memcpy( p_slot->data, p_nalu + 4, i_size - 4 );
- gf_list_add( p_mp4->p_config->sequenceParameterSets, p_slot );
- p_slot = NULL;
- p_mp4->b_sps = 1;
- }
- break;
+ int sei_size = p_nal[0].i_payload;
+ int sps_size = p_nal[1].i_payload - 4;
+ int pps_size = p_nal[2].i_payload - 4;
- // pps
- case 0x08:
- if( !p_mp4->b_pps )
- {
- p_slot = malloc( sizeof(GF_AVCConfigSlot) );
- if( !p_slot )
- return -1;
- p_slot->size = i_size - 4;
- p_slot->data = malloc( p_slot->size );
- if( !p_slot->data )
- return -1;
- memcpy( p_slot->data, p_nalu + 4, i_size - 4 );
- gf_list_add( p_mp4->p_config->pictureParameterSets, p_slot );
- p_slot = NULL;
- p_mp4->b_pps = 1;
- if( p_mp4->b_sps )
- gf_isom_avc_config_update( p_mp4->p_file, p_mp4->i_track, 1, p_mp4->p_config );
- }
- break;
+ uint8_t *sei = p_nal[0].p_payload;
+ uint8_t *sps = p_nal[1].p_payload + 4;
+ uint8_t *pps = p_nal[2].p_payload + 4;
- // slice, sei
- case 0x1:
- case 0x5:
- case 0x6:
- psize = i_size - 4;
- memcpy( p_mp4->p_sample->data + p_mp4->p_sample->dataLength, p_nalu, i_size );
- p_mp4->p_sample->data[p_mp4->p_sample->dataLength + 0] = psize >> 24;
- p_mp4->p_sample->data[p_mp4->p_sample->dataLength + 1] = psize >> 16;
- p_mp4->p_sample->data[p_mp4->p_sample->dataLength + 2] = psize >> 8;
- p_mp4->p_sample->data[p_mp4->p_sample->dataLength + 3] = psize >> 0;
- p_mp4->p_sample->dataLength += i_size;
- break;
- }
+ // SPS
- return i_size;
-}
+ p_mp4->p_config->configurationVersion = 1;
+ p_mp4->p_config->AVCProfileIndication = sps[1];
+ p_mp4->p_config->profile_compatibility = sps[2];
+ p_mp4->p_config->AVCLevelIndication = sps[3];
+ p_slot = malloc( sizeof(GF_AVCConfigSlot) );
+ if( !p_slot )
+ return -1;
+ p_slot->size = sps_size;
+ p_slot->data = malloc( p_slot->size );
+ if( !p_slot->data )
+ return -1;
+ memcpy( p_slot->data, sps, sps_size );
+ gf_list_add( p_mp4->p_config->sequenceParameterSets, p_slot );
+
+ // PPS
-static int set_eop( hnd_t handle, x264_picture_t *p_picture )
+ p_slot = malloc( sizeof(GF_AVCConfigSlot) );
+ if( !p_slot )
+ return -1;
+ p_slot->size = pps_size;
+ p_slot->data = malloc( p_slot->size );
+ if( !p_slot->data )
+ return -1;
+ memcpy( p_slot->data, pps, pps_size );
+ gf_list_add( p_mp4->p_config->pictureParameterSets, p_slot );
+ gf_isom_avc_config_update( p_mp4->p_file, p_mp4->i_track, 1, p_mp4->p_config );
+
+ // SEI
+
+ memcpy( p_mp4->p_sample->data + p_mp4->p_sample->dataLength, sei, sei_size );
+ p_mp4->p_sample->dataLength += sei_size;
+
+ return sei_size + sps_size + pps_size;
+}
+static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
mp4_hnd_t *p_mp4 = handle;
- uint64_t dts = (uint64_t)p_mp4->i_numframe * p_mp4->i_time_inc;
- uint64_t pts = (uint64_t)p_picture->i_pts;
- int32_t offset = p_mp4->i_init_delay + pts - dts;
+ int64_t dts;
+ int64_t cts;
+ int32_t offset = 0;
+
+ memcpy( p_mp4->p_sample->data + p_mp4->p_sample->dataLength, p_nalu, i_size );
+ p_mp4->p_sample->dataLength += i_size;
+
+ if( !p_mp4->i_numframe )
+ p_mp4->i_delay_time = p_picture->i_dts * -1;
+
+ if( !p_mp4->i_init_delay )
+ dts = cts = p_picture->i_pts * p_mp4->i_time_inc;
+ else
+ {
+ if( p_mp4->i_numframe <= p_mp4->i_init_delay )
+ dts = p_picture->i_dts + p_mp4->i_delay_time;
+ else
+ dts = p_mp4->i_prev_timestamps[ (p_mp4->i_numframe - p_mp4->i_init_delay) % p_mp4->i_init_delay ] + p_mp4->i_delay_time;
+
+ // unordered pts
+ p_mp4->i_prev_timestamps[ p_mp4->i_numframe % p_mp4->i_init_delay ] = p_picture->i_dts + p_mp4->i_delay_time;
+
+ dts *= p_mp4->i_time_inc;
+ cts = (p_picture->i_pts + p_mp4->i_delay_time) * p_mp4->i_time_inc;
+
+ offset = cts - dts;
+ }
p_mp4->p_sample->IsRAP = p_picture->i_type == X264_TYPE_IDR ? 1 : 0;
p_mp4->p_sample->DTS = dts;
p_mp4->p_sample->dataLength = 0;
p_mp4->i_numframe++;
- return 0;
+ return i_size;
}
-cli_output_t mp4_output = { open_file, set_param, write_nalu, set_eop, close_file };
+cli_output_t mp4_output = { open_file, set_param, write_headers, write_frame, close_file };
{
int (*open_file)( char *psz_filename, hnd_t *p_handle );
int (*set_param)( hnd_t handle, x264_param_t *p_param );
- int (*write_nalu)( hnd_t handle, uint8_t *p_nal, int i_size, x264_picture_t *p_picture );
- int (*set_eop)( hnd_t handle, x264_picture_t *p_picture );
- int (*close_file)( hnd_t handle );
+ int (*write_headers)( hnd_t handle, x264_nal_t *p_nal );
+ int (*write_frame)( hnd_t handle, uint8_t *p_nal, int i_size, x264_picture_t *p_picture );
+ int (*close_file)( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts );
} cli_output_t;
extern cli_output_t raw_output;
return 0;
}
-static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
+static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
- if( fwrite( p_nalu, i_size, 1, (FILE*)handle ) > 0 )
- return i_size;
+ int size = p_nal[0].i_payload + p_nal[1].i_payload + p_nal[2].i_payload;
+
+ if( fwrite( p_nal[0].p_payload, size, 1, (FILE*)handle ) )
+ return size;
return -1;
}
-static int set_eop( hnd_t handle, x264_picture_t *p_picture )
+static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
- return 0;
+ if( fwrite( p_nalu, i_size, 1, (FILE*)handle ) )
+ return i_size;
+ return -1;
}
-static int close_file( hnd_t handle )
+static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
if( !handle || handle == stdout )
return 0;
return fclose( (FILE*)handle );
}
-cli_output_t raw_output = { open_file, set_param, write_nalu, set_eop, close_file };
+cli_output_t raw_output = { open_file, set_param, write_headers, write_frame, close_file };
+
*
* Authors: Loren Merritt <lorenm@u.washington.edu>
* Laurent Aimar <fenrir@via.ecp.fr>
+ * Steven Walters <kemuri9@gmail.com>
+ * Kieran Kunhya <kieran@kunhya.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
cli_input_t input;
static cli_output_t output;
-/* i/o modules that work with pipes (and fifos) */
-static const char * const stdin_format_names[] = { "yuv", "y4m", 0 };
-static const char * const stdout_format_names[] = { "raw", "mkv", "flv", 0 };
+static const char * const demuxer_names[] =
+{
+ "auto",
+ "yuv",
+ "y4m",
+#ifdef AVS_INPUT
+ "avs",
+#endif
+#ifdef LAVF_INPUT
+ "lavf",
+#endif
+#ifdef FFMS_INPUT
+ "ffms",
+#endif
+ 0
+};
+
+static const char * const muxer_names[] =
+{
+ "auto",
+ "raw",
+ "mkv",
+ "flv",
+#ifdef MP4_OUTPUT
+ "mp4",
+#endif
+ 0
+};
static void Help( x264_param_t *defaults, int longhelp );
static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt );
static int Encode( x264_param_t *param, cli_opt_t *opt );
-
/****************************************************************************
* main:
****************************************************************************/
return ( ( index >= 0 && index < i ) ? table[ index ] : "???" );
}
+static char *stringify_names( char *buf, const char * const names[] )
+{
+ int i = 0;
+ char *p = buf;
+ for( p[0] = 0; names[i]; i++ )
+ {
+ p += sprintf( p, "%s", names[i] );
+ if( names[i+1] )
+ p += sprintf( p, ", " );
+ }
+ return buf;
+}
+
/*****************************************************************************
* Help:
*****************************************************************************/
static void Help( x264_param_t *defaults, int longhelp )
{
+ char buf[50];
#define H0 printf
#define H1 if(longhelp>=1) printf
#define H2 if(longhelp==2) printf
"Infile can be raw YUV 4:2:0 (in which case resolution is required),\n"
" or YUV4MPEG 4:2:0 (*.y4m),\n"
" or Avisynth if compiled with support (%s).\n"
+ " or libav* formats if compiled with lavf support (%s) or ffms support (%s).\n"
"Outfile type is selected by filename:\n"
" .264 -> Raw bytestream\n"
" .mkv -> Matroska\n"
"\n",
X264_BUILD, X264_VERSION,
#ifdef AVS_INPUT
- "native",
-#elif defined(VFW_INPUT)
- "vfw (fallback)",
+ "yes",
+#else
+ "no",
+#endif
+#ifdef LAVF_INPUT
+ "yes",
+#else
+ "no",
+#endif
+#ifdef FFMS_INPUT
+ "yes",
#else
"no",
#endif
H0( "Input/Output:\n" );
H0( "\n" );
H0( " -o, --output Specify output file\n" );
- H1( " --stdout Specify stdout format [\"%s\"]\n"
- " - raw, mkv, flv\n", stdout_format_names[0] );
- H1( " --stdin Specify stdin format [\"%s\"]\n"
- " - yuv, y4m\n", stdin_format_names[0] );
+ H1( " --muxer <string> Specify output container format [\"%s\"]\n"
+ " - %s\n", muxer_names[0], stringify_names( buf, muxer_names ) );
+ H1( " --demuxer <string> Specify input container format [\"%s\"]\n"
+ " - %s\n", demuxer_names[0], stringify_names( buf, demuxer_names ) );
+ H1( " --index <string> Filename for input index file\n" );
H0( " --sar width:height Specify Sample Aspect Ratio\n" );
H0( " --fps <float|rational> Specify framerate\n" );
H0( " --seek <integer> First frame to encode\n" );
H2( " --dump-yuv <string> Save reconstructed frames\n" );
H2( " --sps-id <integer> Set SPS and PPS id numbers [%d]\n", defaults->i_sps_id );
H2( " --aud Use access unit delimiters\n" );
+ H2( " --force-cfr Force constant framerate timestamp generation\n" );
H0( "\n" );
}
#define OPT_SLOWFIRSTPASS 267
#define OPT_FULLHELP 268
#define OPT_FPS 269
-#define OPT_STDOUT_FORMAT 270
-#define OPT_STDIN_FORMAT 271
+#define OPT_MUXER 270
+#define OPT_DEMUXER 271
+#define OPT_INDEX 272
+#define OPT_INTERLACED 273
static char short_options[] = "8A:B:b:f:hI:i:m:o:p:q:r:t:Vvw";
static struct option long_options[] =
{ "no-deblock", no_argument, NULL, 0 },
{ "filter", required_argument, NULL, 0 },
{ "deblock", required_argument, NULL, 'f' },
- { "interlaced", no_argument, NULL, 0 },
+ { "interlaced", no_argument, NULL, OPT_INTERLACED },
+ { "no-interlaced", no_argument, NULL, OPT_INTERLACED },
{ "constrained-intra", no_argument, NULL, 0 },
{ "cabac", no_argument, NULL, 0 },
{ "no-cabac", no_argument, NULL, 0 },
{ "frames", required_argument, NULL, OPT_FRAMES },
{ "seek", required_argument, NULL, OPT_SEEK },
{ "output", required_argument, NULL, 'o' },
- { "stdout", required_argument, NULL, OPT_STDOUT_FORMAT },
- { "stdin", required_argument, NULL, OPT_STDIN_FORMAT },
+ { "muxer", required_argument, NULL, OPT_MUXER },
+ { "demuxer", required_argument, NULL, OPT_DEMUXER },
+ { "stdout", required_argument, NULL, OPT_MUXER },
+ { "stdin", required_argument, NULL, OPT_DEMUXER },
+ { "index", required_argument, NULL, OPT_INDEX },
{ "analyse", required_argument, NULL, 0 },
{ "partitions", required_argument, NULL, 'A' },
{ "direct", required_argument, NULL, 0 },
{ "transfer", required_argument, NULL, 0 },
{ "colormatrix", required_argument, NULL, 0 },
{ "chromaloc", required_argument, NULL, 0 },
+ { "force-cfr", no_argument, NULL, 0 },
{0, 0, 0, 0}
};
-static int select_output( char *filename, const char *pipe_format, x264_param_t *param )
+static int select_output( const char *muxer, char *filename, x264_param_t *param )
{
const char *ext = get_filename_extension( filename );
- if( !strcmp( filename, "-" ) )
- ext = pipe_format;
+ if( !strcmp( filename, "-" ) || strcasecmp( muxer, "auto" ) )
+ ext = muxer;
if( !strcasecmp( ext, "mp4" ) )
{
#ifdef MP4_OUTPUT
- output = mp4_output; // FIXME use b_annexb=0
+ output = mp4_output;
+ param->b_annexb = 0;
+ param->b_aud = 0;
+ param->b_repeat_headers = 0;
#else
fprintf( stderr, "x264 [error]: not compiled with MP4 output support\n" );
return -1;
#endif
}
else if( !strcasecmp( ext, "mkv" ) )
- output = mkv_output; // FIXME use b_annexb=0
+ {
+ output = mkv_output;
+ param->b_annexb = 0;
+ param->b_aud = 0;
+ param->b_repeat_headers = 0;
+ }
else if( !strcasecmp( ext, "flv" ) )
{
output = flv_output;
param->b_annexb = 0;
+ param->b_aud = 0;
+ param->b_repeat_headers = 0;
}
else
output = raw_output;
return 0;
}
-static int select_input( char *filename, char *resolution, const char *pipe_format, x264_param_t *param )
+static int select_input( const char *demuxer, char *used_demuxer, char *filename,
+ hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
const char *ext = get_filename_extension( filename );
- if( !strcmp( filename, "-" ) )
- ext = pipe_format;
+ int b_regular = strcmp( filename, "-" );
+ int b_auto = !strcasecmp( demuxer, "auto" );
+ if( !b_regular && b_auto )
+ ext = "yuv";
+ if( b_regular )
+ {
+ FILE *f = fopen( filename, "r" );
+ if( !f )
+ {
+ fprintf( stderr, "x264 [error]: could not open input file `%s'\n", filename );
+ return -1;
+ }
+ b_regular = x264_is_regular_file( f );
+ fclose( f );
+ }
+ const char *module = b_auto ? ext : demuxer;
- if( !strcasecmp( ext, "avi" ) || !strcasecmp( ext, "avs" ) )
+ if( !strcasecmp( module, "avs" ) || !strcasecmp( ext, "d2v" ) || !strcasecmp( ext, "dga" ) )
{
-#if defined(AVS_INPUT) || defined(VFW_INPUT)
+#ifdef AVS_INPUT
input = avs_input;
+ module = "avs";
#else
fprintf( stderr, "x264 [error]: not compiled with AVS input support\n" );
return -1;
#endif
}
- else if( !strcasecmp( ext, "y4m" ) )
+ else if( !strcasecmp( module, "y4m" ) )
input = y4m_input;
- else if( !strcasecmp( ext, "yuv" ) )
+ else if( !strcasecmp( module, "yuv" ) )
+ input = yuv_input;
+ else
{
- if( !resolution )
+#ifdef FFMS_INPUT
+ if( b_regular && (b_auto || !strcasecmp( demuxer, "ffms" )) &&
+ !ffms_input.open_file( filename, p_handle, info, opt ) )
{
- /* try to parse the file name */
- char *p;
- for( p = filename; *p; p++ )
- if( *p >= '0' && *p <= '9' &&
- sscanf( p, "%ux%u", ¶m->i_width, ¶m->i_height ) == 2 )
- {
- if( param->i_log_level >= X264_LOG_INFO )
- fprintf( stderr, "x264 [info]: %dx%d (given by file name) @ %.2f fps\n", param->i_width,
- param->i_height, (double)param->i_fps_num / param->i_fps_den );
- break;
- }
+ module = "ffms";
+ b_auto = 0;
+ input = ffms_input;
}
- else
+#endif
+#ifdef LAVF_INPUT
+ if( (b_auto || !strcasecmp( demuxer, "lavf" )) &&
+ (!b_regular || !lavf_input.open_file( filename, p_handle, info, opt )) )
{
- sscanf( resolution, "%ux%u", ¶m->i_width, ¶m->i_height );
- if( param->i_log_level >= X264_LOG_INFO )
- fprintf( stderr, "x264 [info]: %dx%d @ %.2f fps\n", param->i_width, param->i_height,
- (double)param->i_fps_num / param->i_fps_den );
+ module = "lavf";
+ b_auto = 0;
+ input = lavf_input;
}
- if( !param->i_width || !param->i_height )
+#endif
+#ifdef AVS_INPUT
+ if( b_regular && (b_auto || !strcasecmp( demuxer, "avs" )) &&
+ !avs_input.open_file( filename, p_handle, info, opt ) )
{
- fprintf( stderr, "x264 [error]: Rawyuv input requires a resolution.\n" );
- return -1;
+ module = "avs";
+ b_auto = 0;
+ input = avs_input;
}
- input = yuv_input;
- }
- else
- {
-#ifdef AVS_INPUT
- input = avs_input;
-#else
- input = yuv_input;
#endif
+ if( b_auto && !yuv_input.open_file( filename, p_handle, info, opt ) )
+ {
+ module = "yuv";
+ b_auto = 0;
+ input = yuv_input;
+ }
+
+ if( !(*p_handle) )
+ {
+ fprintf( stderr, "x264 [error]: could not open input file `%s' via any method!\n", filename );
+ return -1;
+ }
}
+ strcpy( used_demuxer, module );
return 0;
}
/*****************************************************************************
* Parse:
*****************************************************************************/
-static int Parse( int argc, char **argv,
- x264_param_t *param, cli_opt_t *opt )
+static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt )
{
char *input_filename = NULL;
- const char *stdin_format = stdin_format_names[0];
+ const char *demuxer = demuxer_names[0];
char *output_filename = NULL;
- const char *stdout_format = stdout_format_names[0];
+ const char *muxer = muxer_names[0];
x264_param_t defaults = *param;
char *profile = NULL;
int b_thread_input = 0;
int b_pass1 = 0;
int b_user_ref = 0;
int b_user_fps = 0;
+ int b_user_interlaced = 0;
int i;
+ cli_input_opt_t input_opt;
memset( opt, 0, sizeof(cli_opt_t) );
+ memset( &input_opt, 0, sizeof(cli_input_opt_t) );
opt->b_progress = 1;
/* Presets are applied before all other options. */
}
else
{
- fprintf( stderr, "x264 [error]: invalid preset: %s\n", optarg );
+ fprintf( stderr, "x264 [error]: invalid preset '%s'\n", optarg );
return -1;
}
}
}
else
{
- fprintf( stderr, "x264 [error]: invalid tune: %s\n", optarg );
+ fprintf( stderr, "x264 [error]: invalid tune '%s'\n", optarg );
return -1;
}
}
#endif
exit(0);
case OPT_FRAMES:
- param->i_frame_total = atoi( optarg );
+ param->i_frame_total = X264_MAX( atoi( optarg ), 0 );
break;
case OPT_SEEK:
- opt->i_seek = atoi( optarg );
+ opt->i_seek = input_opt.seek = X264_MAX( atoi( optarg ), 0 );
break;
case 'o':
output_filename = optarg;
break;
- case OPT_STDOUT_FORMAT:
- for( i = 0; stdout_format_names[i] && strcasecmp( stdout_format_names[i], optarg ); )
+ case OPT_MUXER:
+ for( i = 0; muxer_names[i] && strcasecmp( muxer_names[i], optarg ); )
i++;
- if( !stdout_format_names[i] )
+ if( !muxer_names[i] )
{
- fprintf( stderr, "x264 [error]: invalid stdout format `%s'\n", optarg );
+ fprintf( stderr, "x264 [error]: invalid muxer '%s'\n", optarg );
return -1;
}
- stdout_format = optarg;
+ muxer = optarg;
break;
- case OPT_STDIN_FORMAT:
- for( i = 0; stdin_format_names[i] && strcasecmp( stdin_format_names[i], optarg ); )
+ case OPT_DEMUXER:
+ for( i = 0; demuxer_names[i] && strcasecmp( demuxer_names[i], optarg ); )
i++;
- if( !stdin_format_names[i] )
+ if( !demuxer_names[i] )
{
- fprintf( stderr, "x264 [error]: invalid stdin format `%s'\n", optarg );
+ fprintf( stderr, "x264 [error]: invalid demuxer '%s'\n", optarg );
return -1;
}
- stdin_format = optarg;
+ demuxer = optarg;
+ break;
+ case OPT_INDEX:
+ input_opt.index = optarg;
break;
case OPT_QPFILE:
opt->qpfile = fopen( optarg, "rb" );
if( !opt->qpfile )
{
- fprintf( stderr, "x264 [error]: can't open `%s'\n", optarg );
+ fprintf( stderr, "x264 [error]: can't open qpfile `%s'\n", optarg );
return -1;
}
else if( !x264_is_regular_file( opt->qpfile ) )
case OPT_FPS:
b_user_fps = 1;
goto generic_option;
+ case OPT_INTERLACED:
+ b_user_interlaced = 1;
+ goto generic_option;
default:
generic_option:
{
optind > argc - 1 ? "input" : "output" );
return -1;
}
- input_filename = argv[optind++];
- if( select_output( output_filename, stdout_format, param ) )
+ if( select_output( muxer, output_filename, param ) )
return -1;
if( output.open_file( output_filename, &opt->hout ) )
{
return -1;
}
- if( select_input( input_filename, optind < argc ? argv[optind++] : NULL, stdin_format, param ) )
+ input_filename = argv[optind++];
+ input_opt.resolution = optind < argc ? argv[optind++] : NULL;
+ video_info_t info = {0};
+ char demuxername[5];
+
+ /* set info flags to param flags to be overwritten by demuxer as necessary. */
+ info.csp = param->i_csp;
+ info.fps_num = param->i_fps_num;
+ info.fps_den = param->i_fps_den;
+ info.interlaced = param->b_interlaced;
+ info.sar_width = param->vui.i_sar_width;
+ info.sar_height = param->vui.i_sar_height;
+ info.vfr = param->b_vfr_input;
+
+ if( select_input( demuxer, demuxername, input_filename, &opt->hin, &info, &input_opt ) )
return -1;
+ if( !opt->hin && input.open_file( input_filename, &opt->hin, &info, &input_opt ) )
{
- int i_fps_num = param->i_fps_num;
- int i_fps_den = param->i_fps_den;
+ fprintf( stderr, "x264 [error]: could not open input file `%s'\n", input_filename );
+ return -1;
+ }
- if( input.open_file( input_filename, &opt->hin, param ) )
- {
- fprintf( stderr, "x264 [error]: could not open input file `%s'\n", input_filename );
- return -1;
- }
- /* Restore the user's frame rate if fps has been explicitly set on the commandline. */
- if( b_user_fps )
- {
- param->i_fps_num = i_fps_num;
- param->i_fps_den = i_fps_den;
- }
+ x264_reduce_fraction( &info.sar_width, &info.sar_height );
+ x264_reduce_fraction( &info.fps_num, &info.fps_den );
+ if( param->i_log_level >= X264_LOG_INFO )
+ fprintf( stderr, "%s [info]: %dx%d%c %d:%d @ %d/%d fps (%cfr)\n", demuxername, info.width,
+ info.height, info.interlaced ? 'i' : 'p', info.sar_width, info.sar_height,
+ info.fps_num, info.fps_den, info.vfr ? 'v' : 'c' );
+
+ /* set param flags from the info flags as necessary */
+ param->i_csp = info.csp;
+ param->i_height = info.height;
+ param->b_vfr_input = info.vfr;
+ param->i_width = info.width;
+ if( !b_user_interlaced && info.interlaced )
+ {
+ fprintf( stderr, "x264 [warning]: input appears to be interlaced, enabling interlaced mode.\n"
+ " If you want otherwise, use --no-interlaced\n" );
+ param->b_interlaced = 1;
+ }
+ if( !b_user_fps )
+ {
+ param->i_fps_num = info.fps_num;
+ param->i_fps_den = info.fps_den;
+ }
+ if( param->b_vfr_input )
+ {
+ param->i_timebase_num = info.timebase_num;
+ param->i_timebase_den = info.timebase_den;
+ }
+ else
+ {
+ param->i_timebase_den = param->i_fps_num;
+ param->i_timebase_num = param->i_fps_den;
+ }
+ if( !param->vui.i_sar_width || !param->vui.i_sar_height )
+ {
+ param->vui.i_sar_width = info.sar_width;
+ param->vui.i_sar_height = info.sar_height;
}
#ifdef HAVE_PTHREAD
if( b_thread_input || param->i_threads > 1
|| (param->i_threads == X264_THREADS_AUTO && x264_cpu_num_processors() > 1) )
{
- if( thread_input.open_file( NULL, &opt->hin, param ) )
+ if( thread_input.open_file( NULL, &opt->hin, &info, NULL ) )
{
fprintf( stderr, "x264 [error]: threaded input failed\n" );
return -1;
* Encode:
*****************************************************************************/
-static int Encode_frame( x264_t *h, hnd_t hout, x264_picture_t *pic )
+static int Encode_frame( x264_t *h, hnd_t hout, x264_picture_t *pic, int64_t *last_pts )
{
x264_picture_t pic_out;
x264_nal_t *nal;
- int i_nal, i, i_nalu_size;
- int i_file = 0;
+ int i_nal;
+ int i_frame_size = 0;
+
+ i_frame_size = x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out );
- if( x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out ) < 0 )
+ if( i_frame_size < 0 )
{
fprintf( stderr, "x264 [error]: x264_encoder_encode failed\n" );
return -1;
}
- for( i = 0; i < i_nal; i++ )
+ if( i_frame_size )
{
- i_nalu_size = output.write_nalu( hout, nal[i].p_payload, nal[i].i_payload, &pic_out );
- if( i_nalu_size < 0 )
- return -1;
- i_file += i_nalu_size;
+ i_frame_size = output.write_frame( hout, nal[0].p_payload, i_frame_size, &pic_out );
+ *last_pts = pic_out.i_pts;
}
- if( i_nal )
- output.set_eop( hout, &pic_out );
- return i_file;
+ return i_frame_size;
}
-static void Print_status( int64_t i_start, int i_frame, int i_frame_total, int64_t i_file, x264_param_t *param )
+static void Print_status( int64_t i_start, int i_frame, int i_frame_total, int64_t i_file, x264_param_t *param, int64_t last_pts )
{
char buf[200];
int64_t i_elapsed = x264_mdate() - i_start;
double fps = i_elapsed > 0 ? i_frame * 1000000. / i_elapsed : 0;
- double bitrate = (double) i_file * 8 * param->i_fps_num / ( (double) param->i_fps_den * i_frame * 1000 );
+ double bitrate = (double) i_file * 8 / ( (double) last_pts * 1000 * param->i_timebase_num / param->i_timebase_den );
if( i_frame_total )
{
int eta = i_elapsed * (i_frame_total - i_frame) / ((int64_t)i_frame * 1000000);
int i_frame, i_frame_total, i_frame_output;
int64_t i_start, i_end;
- int64_t i_file;
+ int64_t i_file = 0;
int i_frame_size;
int i_update_interval;
+ int64_t last_pts = 0;
+# define MAX_PTS_WARNING 3 /* arbitrary */
+ int pts_warning_cnt = 0;
+ int64_t largest_pts = -1;
+ int64_t second_largest_pts = -1;
+ int64_t ticks_per_frame;
+ double duration;
opt->b_progress &= param->i_log_level < X264_LOG_DEBUG;
i_frame_total = input.get_frame_total( opt->hin );
{
fprintf( stderr, "x264 [error]: can't set outfile param\n" );
input.close_file( opt->hin );
- output.close_file( opt->hout );
+ output.close_file( opt->hout, largest_pts, second_largest_pts );
return -1;
}
}
i_start = x264_mdate();
+ /* ticks/frame = ticks/second / frames/second */
+ ticks_per_frame = (int64_t)h->param.i_timebase_den * h->param.i_fps_den / h->param.i_timebase_num / h->param.i_fps_num;
+ if( ticks_per_frame < 1 )
+ {
+ fprintf( stderr, "x264 [error]: ticks_per_frame invalid: %"PRId64"\n", ticks_per_frame );
+ return -1;
+ }
+
+ if( !h->param.b_repeat_headers )
+ {
+ // Write SPS/PPS/SEI
+ x264_nal_t *headers;
+ int i_nal;
+
+ if( x264_encoder_headers( h, &headers, &i_nal ) < 0 )
+ {
+ fprintf( stderr, "x264 [error]: x264_encoder_headers failed\n" );
+ return -1;
+ }
+
+ if( (i_file = output.write_headers( opt->hout, headers )) < 0 )
+ return -1;
+ }
/* Encode frames */
- for( i_frame = 0, i_file = 0, i_frame_output = 0; b_ctrl_c == 0 && (i_frame < i_frame_total || i_frame_total == 0); )
+ for( i_frame = 0, i_frame_output = 0; b_ctrl_c == 0 && (i_frame < i_frame_total || i_frame_total == 0); )
{
if( input.read_frame( &pic, opt->hin, i_frame + opt->i_seek ) )
break;
- pic.i_pts = (int64_t)i_frame * param->i_fps_den;
+ if( !param->b_vfr_input )
+ pic.i_pts = i_frame;
+ if( pic.i_pts <= largest_pts )
+ {
+ if( h->param.i_log_level >= X264_LOG_WARNING )
+ {
+ if( h->param.i_log_level >= X264_LOG_DEBUG || pts_warning_cnt < MAX_PTS_WARNING )
+ fprintf( stderr, "x264 [warning]: non-strictly-monotonic pts at frame %d (%"PRId64" <= %"PRId64")\n",
+ i_frame, pic.i_pts, largest_pts );
+ else if( pts_warning_cnt == MAX_PTS_WARNING )
+ fprintf( stderr, "x264 [warning]: too many nonmonotonic pts warnings, suppressing further ones\n" );
+ pts_warning_cnt++;
+ }
+ pic.i_pts = largest_pts + ticks_per_frame;
+ }
+ second_largest_pts = largest_pts;
+ largest_pts = pic.i_pts;
if( opt->qpfile )
parse_qpfile( opt, &pic, i_frame + opt->i_seek );
pic.i_qpplus1 = 0;
}
- i_frame_size = Encode_frame( h, opt->hout, &pic );
+ i_frame_size = Encode_frame( h, opt->hout, &pic, &last_pts );
if( i_frame_size < 0 )
return -1;
i_file += i_frame_size;
/* update status line (up to 1000 times per input file) */
if( opt->b_progress && i_frame_output % i_update_interval == 0 && i_frame_output )
- Print_status( i_start, i_frame_output, i_frame_total, i_file, param );
+ Print_status( i_start, i_frame_output, i_frame_total, i_file, param, last_pts );
}
/* Flush delayed frames */
while( !b_ctrl_c && x264_encoder_delayed_frames( h ) )
{
- i_frame_size = Encode_frame( h, opt->hout, NULL );
+ i_frame_size = Encode_frame( h, opt->hout, NULL, &last_pts );
if( i_frame_size < 0 )
return -1;
i_file += i_frame_size;
if( i_frame_size )
i_frame_output++;
if( opt->b_progress && i_frame_output % i_update_interval == 0 && i_frame_output )
- Print_status( i_start, i_frame_output, i_frame_total, i_file, param );
+ Print_status( i_start, i_frame_output, i_frame_total, i_file, param, last_pts );
}
+ if( pts_warning_cnt >= MAX_PTS_WARNING && h->param.i_log_level < X264_LOG_DEBUG )
+ fprintf( stderr, "x264 [warning]: %d suppressed nonmonotonic pts warnings\n", pts_warning_cnt-MAX_PTS_WARNING );
+
+ /* duration algorithm fails when only 1 frame is output */
+ if( i_frame_output == 1 )
+ duration = (double)param->i_fps_den / param->i_fps_num;
+ else
+ duration = (double)(2 * largest_pts - second_largest_pts) * param->i_timebase_num / param->i_timebase_den;
i_end = x264_mdate();
input.picture_clean( &pic );
fprintf( stderr, "aborted at input frame %d, output frame %d\n", opt->i_seek + i_frame, i_frame_output );
input.close_file( opt->hin );
- output.close_file( opt->hout );
+ output.close_file( opt->hout, largest_pts, second_largest_pts );
if( i_frame_output > 0 )
{
(double)( i_end - i_start );
fprintf( stderr, "encoded %d frames, %.2f fps, %.2f kb/s\n", i_frame_output, fps,
- (double) i_file * 8 * param->i_fps_num /
- ( (double) param->i_fps_den * i_frame_output * 1000 ) );
+ (double) i_file * 8 / ( 1000 * duration ) );
}
return 0;
#include <stdarg.h>
-#define X264_BUILD 80
+#define X264_BUILD 81
/* x264_t:
* opaque handler for encoder */
int b_annexb; /* if set, place start codes (4 bytes) before NAL units,
* otherwise place size (4 bytes) before NAL units. */
int i_sps_id; /* SPS and PPS id number */
+ int b_vfr_input; /* VFR input */
+ int i_timebase_num; /* Timebase numerator */
+ int i_timebase_den; /* Timebase denominator */
/* Slicing parameters */
int i_slice_max_size; /* Max size per slice in bytes; includes estimated NAL overhead. */
int i_qpplus1;
/* In: user pts, Out: pts of encoded picture (user)*/
int64_t i_pts;
+ /* Out: frame dts. Since the pts of the first frame is always zero,
+ * initial frames may have a negative dts which must be dealt with by any muxer */
+ int64_t i_dts;
/* In: custom encoding parameters to be set from this frame forwards
(in coded order, not display order). If NULL, continue using
parameters from the previous frame. Some parameters, such as