LAVF/FFMS input support, native VFR timestamp handling
authorKieran Kunhya <kieran@kunhya.com>
Mon, 28 Dec 2009 15:42:17 +0000 (10:42 -0500)
committerJason Garrett-Glaser <darkshikari@gmail.com>
Thu, 14 Jan 2010 04:33:23 +0000 (23:33 -0500)
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>.

27 files changed:
Makefile
common/common.c
common/common.h
common/frame.c
common/frame.h
configure
encoder/encoder.c
encoder/set.c
encoder/slicetype.c
input/avs.c
input/ffms.c [new file with mode: 0644]
input/input.h
input/lavf.c [new file with mode: 0644]
input/thread.c
input/vfw.c [deleted file]
input/y4m.c
input/yuv.c
output/flv.c
output/flv_bytestream.c
output/matroska.c
output/matroska_ebml.c
output/matroska_ebml.h
output/mp4.c
output/output.h
output/raw.c
x264.c
x264.h

index ea6edd4..fdb42ac 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -19,10 +19,6 @@ SRCCLI = x264.c input/yuv.c input/y4m.c output/raw.c \
 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
@@ -31,6 +27,14 @@ ifneq ($(findstring HAVE_PTHREAD, $(CFLAGS)),)
 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
@@ -110,8 +114,8 @@ libx264.a: .depend $(OBJS) $(OBJASM)
 $(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)
index e471c68..0833dcf 100644 (file)
@@ -156,6 +156,7 @@ void    x264_param_default( x264_param_t *param )
     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 )
@@ -615,6 +616,8 @@ int x264_param_parse( x264_param_t *p, const char *name, const char *value )
         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
index 417ac9e..a1f4573 100644 (file)
@@ -431,6 +431,8 @@ struct x264_t
         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;
index 6fadacb..1485d80 100644 (file)
@@ -223,7 +223,7 @@ int x264_frame_copy_picture( x264_t *h, x264_frame_t *dst, x264_picture_t *src )
 
     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++ )
index e255bb6..66196d5 100644 (file)
@@ -35,10 +35,11 @@ typedef struct x264_frame
     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;
index 0cc246d..e9ff21c 100755 (executable)
--- a/configure
+++ b/configure
@@ -8,6 +8,8 @@ echo "available options:"
 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"
@@ -29,7 +31,7 @@ cc_check() {
     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() {
@@ -52,6 +54,8 @@ includedir='${prefix}/include'
 DEVNULL='/dev/null'
 
 avs_input="auto"
+lavf_input="auto"
+ffms_input="auto"
 mp4_output="auto"
 pthread="auto"
 asm="yes"
@@ -63,6 +67,7 @@ shared="no"
 
 CFLAGS="$CFLAGS -Wall -I."
 LDFLAGS="$LDFLAGS"
+LDFLAGSCLI="$LDFLAGSCLI"
 ASFLAGS="$ASFLAGS"
 HAVE_GETOPT_LONG=1
 cross_prefix=""
@@ -95,17 +100,24 @@ for opt do
         --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"
             ;;
@@ -402,6 +414,48 @@ if test "$pthread" = "yes" ; then
     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"
@@ -412,32 +466,18 @@ if [ "$mp4_output" = "auto" ] ; then
 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
 
@@ -486,6 +526,7 @@ SYS=$SYS
 CC=$CC
 CFLAGS=$CFLAGS
 LDFLAGS=$LDFLAGS
+LDFLAGSCLI=$LDFLAGSCLI
 AR=$AR
 RANLIB=$RANLIB
 STRIP=$STRIP
@@ -541,6 +582,8 @@ echo "Platform:   $ARCH"
 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"
index 82a5b8e..b6c1ec6 100644 (file)
@@ -395,7 +395,8 @@ static int x264_validate_parameters( x264_t *h )
                   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;
@@ -560,6 +561,12 @@ static int x264_validate_parameters( x264_t *h )
         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;
@@ -833,6 +840,7 @@ x264_t *x264_encoder_open( x264_param_t *param )
     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;
@@ -866,6 +874,7 @@ x264_t *x264_encoder_open( x264_param_t *param )
         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;
@@ -1542,7 +1551,7 @@ static inline void x264_reference_hierarchy_reset( x264_t *h )
 
     /* 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 )
@@ -2040,6 +2049,9 @@ int     x264_encoder_encode( x264_t *h,
 
         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 )
@@ -2308,7 +2320,9 @@ static int x264_encoder_frame_end( x264_t *h, x264_t *thread_current,
         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++)
index 5ceb6b6..1b86ab3 100644 (file)
@@ -180,12 +180,12 @@ void x264_sps_init( x264_sps_t *sps, int i_id, x264_param_t *param )
     }
 
     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;
index 4269fd8..59c0fe1 100644 (file)
@@ -1327,17 +1327,22 @@ void x264_slicetype_decide( x264_t *h )
 
     /* 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 )
index a88b86e..522f8fe 100644 (file)
@@ -117,7 +117,7 @@ static void avs_build_filter_sequence( char *filename_ext, const char *filter[AV
         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 )
@@ -175,7 +175,7 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
         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" );
@@ -183,7 +183,7 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
             }
             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 );
@@ -225,13 +225,13 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
     {
         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 );
@@ -240,17 +240,13 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
     }
     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;
diff --git a/input/ffms.c b/input/ffms.c
new file mode 100644 (file)
index 0000000..b680967
--- /dev/null
@@ -0,0 +1,247 @@
+/*****************************************************************************
+ * 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 };
index 220a414..9fb425c 100644 (file)
@@ -5,6 +5,7 @@
  *
  * 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 );
@@ -39,5 +64,7 @@ extern cli_input_t yuv_input;
 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
diff --git a/input/lavf.c b/input/lavf.c
new file mode 100644 (file)
index 0000000..180e509
--- /dev/null
@@ -0,0 +1,272 @@
+/*****************************************************************************
+ * 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 };
index 4aa25e7..a88cfae 100644 (file)
@@ -45,10 +45,10 @@ typedef struct thread_input_arg_t
     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;
diff --git a/input/vfw.c b/input/vfw.c
deleted file mode 100644 (file)
index 479d610..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*****************************************************************************
- * 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 };
index 6092b92..1619f74 100644 (file)
@@ -37,7 +37,7 @@ typedef struct
 #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;
@@ -47,6 +47,7 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
         return -1;
 
     h->next_frame = 0;
+    info->vfr = 0;
 
     if( !strcmp( psz_filename, "-" ) )
         h->fh = stdin;
@@ -83,17 +84,17 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
         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 );
@@ -107,25 +108,25 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
                     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;
@@ -138,7 +139,7 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
                         strncmp( "420MPEG2",tokstart, 8 ) &&
                         strncmp( "420PALDV",tokstart, 8 ) )
                     {
-                        fprintf( stderr, "Unsupported extended colorspace\n" );
+                        fprintf( stderr, "y4m [error]: unsupported extended colorspace\n" );
                         return -1;
                     }
                 }
@@ -147,10 +148,6 @@ static int open_file( char *psz_filename, hnd_t *p_handle, x264_param_t *p_param
         }
     }
 
-    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;
 }
@@ -187,7 +184,7 @@ static int read_frame_internal( x264_picture_t *p_pic, y4m_hnd_t *h )
     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;
     }
@@ -197,7 +194,7 @@ static int read_frame_internal( x264_picture_t *p_pic, y4m_hnd_t *h )
         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;
index ca4b082..dbd0317 100644 (file)
@@ -30,14 +30,32 @@ typedef struct
     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;
index a04b131..7e4b63c 100644 (file)
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * 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
@@ -20,6 +20,7 @@
 
 #include "muxers.h"
 #include "flv_bytestream.h"
+
 #define CHECK(x)\
 do {\
     if( (x) < 0 )\
@@ -30,22 +31,27 @@ typedef struct
 {
     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;
@@ -104,13 +110,20 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
     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" );
@@ -131,133 +144,146 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
 
     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 )
@@ -267,20 +293,29 @@ 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 );
@@ -290,4 +325,4 @@ static int close_file( hnd_t handle )
     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 };
index 54d17ce..316114c 100644 (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 )
index 8c9d29f..5d9346b 100644 (file)
@@ -25,63 +25,15 @@ typedef struct
 {
     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 )
 {
@@ -112,16 +64,14 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
     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;
@@ -147,99 +97,113 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
 
     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 };
index c374d09..d1c6e13 100644 (file)
@@ -541,7 +541,7 @@ int mk_add_frame_data( mk_writer *w, const void *data, unsigned size )
     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 )
@@ -549,7 +549,9 @@ int mk_close( mk_writer *w )
     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;
     }
index f8c641b..252e781 100644 (file)
@@ -36,6 +36,6 @@ int mk_writeHeader( mk_writer *w, const char *writing_app,
 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
index e0d35fc..261b817 100644 (file)
@@ -31,12 +31,14 @@ typedef struct
     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 )
@@ -59,9 +61,11 @@ 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;
@@ -90,9 +94,10 @@ static void recompute_bitrate_mp4( GF_ISOFile *p_file, int i_track )
     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;
@@ -110,6 +115,34 @@ static int close_file( hnd_t handle )
 
     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 );
@@ -144,7 +177,7 @@ static int open_file( char *psz_filename, hnd_t *p_handle )
 
     if( !(p_mp4->p_sample = gf_isom_sample_new()) )
     {
-        close_file( p_mp4 );
+        close_file( p_mp4, 0, 0 );
         return -1;
     }
 
@@ -159,8 +192,13 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
 {
     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,
@@ -187,90 +225,88 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
     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;
@@ -280,7 +316,7 @@ static int set_eop( hnd_t handle, x264_picture_t *p_picture )
     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 };
index e448041..851b819 100644 (file)
@@ -28,9 +28,9 @@ typedef struct
 {
     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;
index 42a707f..a4d1175 100644 (file)
@@ -38,19 +38,23 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
     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;
@@ -58,4 +62,5 @@ static int close_file( hnd_t handle )
     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 };
+
diff --git a/x264.c b/x264.c
index a05990b..3726141 100644 (file)
--- a/x264.c
+++ b/x264.c
@@ -5,6 +5,8 @@
  *
  * 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
@@ -61,15 +63,39 @@ typedef struct {
 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:
  ****************************************************************************/
@@ -114,11 +140,25 @@ static char const *strtable_lookup( const char * const table[], int index )
     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
@@ -128,6 +168,7 @@ static void Help( x264_param_t *defaults, int longhelp )
         "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"
@@ -142,9 +183,17 @@ static void Help( x264_param_t *defaults, int longhelp )
         "\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
@@ -432,10 +481,11 @@ static void Help( x264_param_t *defaults, int longhelp )
     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" );
@@ -458,6 +508,7 @@ static void Help( x264_param_t *defaults, int longhelp )
     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" );
 }
 
@@ -475,8 +526,10 @@ static void Help( x264_param_t *defaults, int longhelp )
 #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[] =
@@ -503,7 +556,8 @@ 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 },
@@ -521,8 +575,11 @@ static struct option long_options[] =
     { "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 },
@@ -605,91 +662,125 @@ static struct option long_options[] =
     { "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", &param->i_width, &param->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", &param->i_width, &param->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;
 }
@@ -697,13 +788,12 @@ static int select_input( char *filename, char *resolution, const char *pipe_form
 /*****************************************************************************
  * 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;
@@ -711,9 +801,12 @@ static int  Parse( int argc, char **argv,
     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. */
@@ -823,7 +916,7 @@ static int  Parse( int argc, char **argv,
             }
             else
             {
-                fprintf( stderr, "x264 [error]: invalid preset: %s\n", optarg );
+                fprintf( stderr, "x264 [error]: invalid preset '%s'\n", optarg );
                 return -1;
             }
         }
@@ -904,7 +997,7 @@ static int  Parse( int argc, char **argv,
             }
             else
             {
-                fprintf( stderr, "x264 [error]: invalid tune: %s\n", optarg );
+                fprintf( stderr, "x264 [error]: invalid tune '%s'\n", optarg );
                 return -1;
             }
         }
@@ -950,39 +1043,42 @@ static int  Parse( int argc, char **argv,
 #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 ) )
@@ -1030,6 +1126,9 @@ static int  Parse( int argc, char **argv,
             case OPT_FPS:
                 b_user_fps = 1;
                 goto generic_option;
+            case OPT_INTERLACED:
+                b_user_interlaced = 1;
+                goto generic_option;
             default:
 generic_option:
             {
@@ -1117,9 +1216,8 @@ 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 ) )
     {
@@ -1127,31 +1225,73 @@ generic_option:
         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;
@@ -1225,38 +1365,36 @@ static void parse_qpfile( cli_opt_t *opt, x264_picture_t *pic, int i_frame )
  * 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);
@@ -1280,9 +1418,16 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
 
     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 );
@@ -1304,7 +1449,7 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
     {
         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;
     }
 
@@ -1316,14 +1461,53 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
     }
 
     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 );
@@ -1334,7 +1518,7 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
             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;
@@ -1348,20 +1532,28 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
 
         /* 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 );
@@ -1375,7 +1567,7 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
         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 )
     {
@@ -1383,8 +1575,7 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
                      (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;
diff --git a/x264.h b/x264.h
index 3a65912..62aecc1 100644 (file)
--- a/x264.h
+++ b/x264.h
@@ -35,7 +35,7 @@
 
 #include <stdarg.h>
 
-#define X264_BUILD 80
+#define X264_BUILD 81
 
 /* x264_t:
  *      opaque handler for encoder */
@@ -311,6 +311,9 @@ typedef struct x264_param_t
     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. */
@@ -382,6 +385,9 @@ typedef struct
     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