Timecode input/output
authorYusuke Nakamura <muken.the.vfrmaniac@gmail.com>
Mon, 1 Mar 2010 05:42:19 +0000 (21:42 -0800)
committerJason Garrett-Glaser <darkshikari@gmail.com>
Sat, 27 Mar 2010 19:46:57 +0000 (12:46 -0700)
--tcfile-in allows a user to specify a timecode v1 or v2 file to override input timestamps.
Useful for dealing with VFR input, especially when FFMS/LAVF support isn't available.
--tcfile-out writes a timecode v2 file containing the timecodes of the output file.
New --timebase option allows a user to change the stream timebase.
Intended primarily for forcing timebase with timecode files if necessary.
When using --seek, note that x264 will seek in the timecode file as well.

Makefile
common/common.h
encoder/encoder.c
input/input.h
input/timecode.c [new file with mode: 0644]
muxers.h
output/mp4.c
x264.c

index c0927fc..f14549b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,8 @@ SRCS = common/mc.c common/predict.c common/pixel.c common/macroblock.c \
        encoder/set.c encoder/macroblock.c encoder/cabac.c \
        encoder/cavlc.c encoder/encoder.c encoder/lookahead.c
 
-SRCCLI = x264.c input/yuv.c input/y4m.c output/raw.c \
+SRCCLI = x264.c input/timecode.c \
+         input/yuv.c input/y4m.c output/raw.c \
          output/matroska.c output/matroska_ebml.c \
          output/flv.c output/flv_bytestream.c
 
index d69ce65..878eee2 100644 (file)
@@ -431,7 +431,7 @@ struct x264_t
         int     i_bframe_delay;
         int64_t i_bframe_delay_time;
         int64_t i_init_delta;
-        int64_t i_prev_dts[2];
+        int64_t i_prev_reordered_pts[2];
         int b_have_lowres;  /* Whether 1/2 resolution luma planes are being used */
         int b_have_sub8x8_esa;
     } frames;
index f99add2..6a7aaf4 100644 (file)
@@ -900,10 +900,12 @@ x264_t *x264_encoder_open( x264_param_t *param )
         /* h->i_dts_compress_multiplier == h->frames.i_bframe_delay + 1 */
         h->i_dts_compress_multiplier = h->param.i_bframe ? (h->param.i_bframe_pyramid ? 3 : 2) : 1;
         if( h->i_dts_compress_multiplier != 1 )
+        {
             x264_log( h, X264_LOG_DEBUG, "DTS compresion changed timebase: %d/%d -> %d/%d\n",
                       h->param.i_timebase_num, h->param.i_timebase_den,
                       h->param.i_timebase_num, h->param.i_timebase_den * h->i_dts_compress_multiplier );
-        h->param.i_timebase_den *= h->i_dts_compress_multiplier;
+            h->param.i_timebase_den *= h->i_dts_compress_multiplier;
+        }
     }
     else
         h->i_dts_compress_multiplier = 1;
@@ -2462,7 +2464,7 @@ static int x264_encoder_frame_end( x264_t *h, x264_t *thread_current,
     pic_out->i_pts = h->fenc->i_pts *= h->i_dts_compress_multiplier;
     if( h->frames.i_bframe_delay )
     {
-        int64_t *i_prev_dts = thread_current->frames.i_prev_dts;
+        int64_t *prev_reordered_pts = thread_current->frames.i_prev_reordered_pts;
         if( h->i_frame <= h->frames.i_bframe_delay )
         {
             if( h->i_dts_compress_multiplier == 1 )
@@ -2476,8 +2478,8 @@ static int x264_encoder_frame_end( x264_t *h, x264_t *thread_current,
             }
         }
         else
-            pic_out->i_dts = i_prev_dts[ (h->i_frame - h->frames.i_bframe_delay) % h->frames.i_bframe_delay ];
-        i_prev_dts[ h->i_frame % h->frames.i_bframe_delay ] = h->fenc->i_reordered_pts * h->i_dts_compress_multiplier;
+            pic_out->i_dts = prev_reordered_pts[ (h->i_frame - h->frames.i_bframe_delay) % h->frames.i_bframe_delay ];
+        prev_reordered_pts[ h->i_frame % h->frames.i_bframe_delay ] = h->fenc->i_reordered_pts * h->i_dts_compress_multiplier;
     }
     else
         pic_out->i_dts = h->fenc->i_reordered_pts;
index 6e386f4..7be6fcf 100644 (file)
@@ -30,6 +30,7 @@ typedef struct
 {
     char *index;
     char *resolution; /* resolution string parsed by raw yuv input */
+    char *timebase;
     int seek;
 } cli_input_opt_t;
 
@@ -66,5 +67,6 @@ extern const cli_input_t avs_input;
 extern cli_input_t thread_input;
 extern const cli_input_t lavf_input;
 extern const cli_input_t ffms_input;
+extern cli_input_t timecode_input;
 
 #endif
diff --git a/input/timecode.c b/input/timecode.c
new file mode 100644 (file)
index 0000000..0dddb27
--- /dev/null
@@ -0,0 +1,488 @@
+/*****************************************************************************
+ * timecode.c: x264 timecode format file input module
+ *****************************************************************************
+ * Copyright (C) 2010 x264 project
+ *
+ * Authors: Yusuke Nakamura <muken.the.vfrmaniac@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#include "muxers.h"
+#include <math.h>
+
+extern cli_input_t input;
+
+typedef struct
+{
+    cli_input_t input;
+    hnd_t p_handle;
+    int frame_total;
+    int auto_timebase_num;
+    int auto_timebase_den;
+    int timebase_num;
+    int timebase_den;
+    int seek;
+    int stored_pts_num;
+    int64_t *pts;
+    double assume_fps;
+    double last_timecode;
+} timecode_hnd_t;
+
+static inline double sigexp10( double value, double *exp )
+{
+    /* This function separates significand and exp10 from double floating point. */
+    *exp = pow( 10, floor( log10( value ) ) );
+    return value / *exp;
+}
+
+#define DOUBLE_EPSILON 5e-6
+#define MKV_TIMEBASE_DEN 1000000000
+
+static double correct_fps( double fps, timecode_hnd_t *h )
+{
+    int64_t i = 1;
+    int64_t fps_num, fps_den;
+    double exponent;
+    double fps_sig = sigexp10( fps, &exponent );
+    while( 1 )
+    {
+        fps_den = i * h->timebase_num;
+        fps_num = round( fps_den * fps_sig ) * exponent;
+        if( fps_num < 0 )
+        {
+            fprintf( stderr, "timecode [error]: tcfile fps correction failed.\n"
+                             "                  Specify an appropriate timebase manually or remake tcfile.\n" );
+            return -1;
+        }
+        if( fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
+            break;
+        ++i;
+    }
+    if( h->auto_timebase_den )
+    {
+        h->timebase_den = h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
+        if( h->timebase_den < 0 )
+            h->auto_timebase_den = 0;
+    }
+    return (double)fps_num / fps_den;
+}
+
+static int try_mkv_timebase_den( double *fpss, timecode_hnd_t *h, int loop_num )
+{
+    int num;
+    h->timebase_num = 0;
+    h->timebase_den = MKV_TIMEBASE_DEN;
+    for( num = 0; num < loop_num; num++ )
+    {
+        int fps_den;
+        double exponent;
+        double fps_sig = sigexp10( fpss[num], &exponent );
+        fps_den = round( MKV_TIMEBASE_DEN / fps_sig ) / exponent;
+        h->timebase_num = fps_den > 0 && h->timebase_num ? gcd( h->timebase_num, fps_den ) : fps_den;
+        if( h->timebase_num <= 0 )
+        {
+            fprintf( stderr, "timecode [error]: automatic timebase generation failed.\n"
+                             "                  Specify timebase manually.\n" );
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info )
+{
+    char buff[256];
+    int ret, tcfv, num, seq_num, timecodes_num;
+    int64_t pts_seek_offset;
+    double *timecodes = NULL;
+    double *fpss = NULL;
+
+    ret = fscanf( tcfile_in, "# timecode format v%d", &tcfv );
+    if( ret != 1 || (tcfv != 1 && tcfv != 2) )
+    {
+        fprintf( stderr, "timecode [error]: unsupported timecode format\n" );
+        return -1;
+    }
+
+    if( tcfv == 1 )
+    {
+        uint64_t file_pos;
+        double assume_fps, seq_fps;
+        int start, end = h->seek;
+        int prev_start = -1, prev_end = -1;
+
+        h->assume_fps = 0;
+        for( num = 2; fgets( buff, sizeof(buff), tcfile_in ) != NULL; num++ )
+        {
+            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+                continue;
+            if( sscanf( buff, "assume %lf", &h->assume_fps ) != 1 && sscanf( buff, "Assume %lf", &h->assume_fps ) != 1 )
+            {
+                fprintf( stderr, "timecode [error]: tcfile parsing error: assumed fps not found\n" );
+                return -1;
+            }
+            break;
+        }
+        if( h->assume_fps <= 0 )
+        {
+            fprintf( stderr, "timecode [error]: invalid assumed fps %.6f\n", h->assume_fps );
+            return -1;
+        }
+
+        file_pos = ftell( tcfile_in );
+        h->stored_pts_num = 0;
+        for( seq_num = 0; fgets( buff, sizeof(buff), tcfile_in ) != NULL; num++ )
+        {
+            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+            {
+                if( sscanf( buff, "# TDecimate Mode 3:  Last Frame = %d", &end ) == 1 )
+                    h->stored_pts_num = end + 1 - h->seek;
+                continue;
+            }
+            ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
+            if( ret != 3 && ret != EOF )
+            {
+                fprintf( stderr, "timecode [error]: invalid input tcfile\n" );
+                return -1;
+            }
+            if( start > end || start <= prev_start || end <= prev_end || seq_fps <= 0 )
+            {
+                fprintf( stderr, "timecode [error]: invalid input tcfile at line %d: %s\n", num, buff );
+                return -1;
+            }
+            prev_start = start;
+            prev_end = end;
+            if( h->auto_timebase_den || h->auto_timebase_num )
+                ++seq_num;
+        }
+        if( !h->stored_pts_num )
+            h->stored_pts_num = end + 1 - h->seek;
+        timecodes_num = h->stored_pts_num + h->seek;
+        fseek( tcfile_in, file_pos, SEEK_SET );
+
+        timecodes = malloc( timecodes_num * sizeof(double) );
+        if( !timecodes )
+            return -1;
+        if( h->auto_timebase_den || h->auto_timebase_num )
+        {
+            fpss = malloc( (seq_num + 1) * sizeof(double) );
+            if( !fpss )
+                goto fail;
+        }
+
+        assume_fps = correct_fps( h->assume_fps, h );
+        if( assume_fps < 0 )
+            goto fail;
+        timecodes[0] = 0;
+        for( num = seq_num = 0; num < timecodes_num - 1; )
+        {
+            fgets( buff, sizeof(buff), tcfile_in );
+            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+                continue;
+            ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
+            if( ret != 3 )
+                start = end = timecodes_num - 1;
+            if( h->auto_timebase_den || h->auto_timebase_num )
+                fpss[seq_num++] = seq_fps;
+            seq_fps = correct_fps( seq_fps, h );
+            if( seq_fps < 0 )
+                goto fail;
+            for( ; num < start && num < timecodes_num - 1; num++ )
+                timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
+            for( num = start; num <= end && num < timecodes_num - 1; num++ )
+                timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
+        }
+        if( h->auto_timebase_den || h->auto_timebase_num )
+            fpss[seq_num] = h->assume_fps;
+
+        if( h->auto_timebase_num && !h->auto_timebase_den )
+        {
+            double exponent;
+            double assume_fps_sig, seq_fps_sig;
+            if( try_mkv_timebase_den( fpss, h, seq_num + 1 ) < 0 )
+                goto fail;
+            fseek( tcfile_in, file_pos, SEEK_SET );
+            assume_fps_sig = sigexp10( h->assume_fps, &exponent );
+            assume_fps = MKV_TIMEBASE_DEN / ( round( MKV_TIMEBASE_DEN / assume_fps_sig ) / exponent );
+            for( num = 0; num < timecodes_num - 1; )
+            {
+                fgets( buff, sizeof(buff), tcfile_in );
+                if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+                    continue;
+                ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
+                if( ret != 3 )
+                    start = end = timecodes_num - 1;
+                seq_fps_sig = sigexp10( seq_fps, &exponent );
+                seq_fps = MKV_TIMEBASE_DEN / ( round( MKV_TIMEBASE_DEN / seq_fps_sig ) / exponent );
+                for( ; num < start - h->seek && num < timecodes_num - 1; num++ )
+                    timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
+                for( num = start; num <= end && num < timecodes_num - 1; num++ )
+                    timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
+            }
+        }
+        if( fpss )
+            free( fpss );
+
+        h->assume_fps = assume_fps;
+        h->last_timecode = timecodes[timecodes_num - 1];
+    }
+    else    /* tcfv == 2 */
+    {
+        uint64_t file_pos = ftell( tcfile_in );
+
+        num = h->stored_pts_num = 0;
+        while( fgets( buff, sizeof(buff), tcfile_in ) != NULL )
+        {
+            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+            {
+                if( !num )
+                    file_pos = ftell( tcfile_in );
+                continue;
+            }
+            if( num >= h->seek )
+                ++h->stored_pts_num;
+            ++num;
+        }
+        timecodes_num = h->stored_pts_num + h->seek;
+        if( !timecodes_num )
+        {
+            fprintf( stderr, "timecode [error]: input tcfile doesn't have any timecodes!\n" );
+            return -1;
+        }
+        fseek( tcfile_in, file_pos, SEEK_SET );
+
+        timecodes = malloc( timecodes_num * sizeof(double) );
+        if( !timecodes )
+            return -1;
+
+        fgets( buff, sizeof(buff), tcfile_in );
+        ret = sscanf( buff, "%lf", &timecodes[0] );
+        if( ret != 1 )
+        {
+            fprintf( stderr, "timecode [error]: invalid input tcfile for frame 0\n" );
+            goto fail;
+        }
+        for( num = 1; num < timecodes_num; )
+        {
+            fgets( buff, sizeof(buff), tcfile_in );
+            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+                continue;
+            ret = sscanf( buff, "%lf", &timecodes[num] );
+            timecodes[num] *= 1e-3;         /* Timecode format v2 is expressed in milliseconds. */
+            if( ret != 1 || timecodes[num] <= timecodes[num - 1] )
+            {
+                fprintf( stderr, "timecode [error]: invalid input tcfile for frame %d\n", num );
+                goto fail;
+            }
+            ++num;
+        }
+
+        if( timecodes_num == 1 )
+            h->timebase_den = info->fps_num;
+        else if( h->auto_timebase_den )
+        {
+            fpss = malloc( (timecodes_num - 1) * sizeof(double) );
+            if( !fpss )
+                goto fail;
+            for( num = 0; num < timecodes_num - 1; num++ )
+            {
+                fpss[num] = 1 / (timecodes[num + 1] - timecodes[num]);
+                if( h->timebase_den >= 0 )
+                {
+                    int i = 1;
+                    int fps_num, fps_den;
+                    double exponent;
+                    double fps_sig = sigexp10( fpss[num], &exponent );
+                    while( 1 )
+                    {
+                        fps_den = i * h->timebase_num;
+                        fps_num = round( fps_den * fps_sig ) * exponent;
+                        if( fps_num < 0 || fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
+                            break;
+                        ++i;
+                    }
+                    h->timebase_den = fps_num > 0 && h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
+                    if( h->timebase_den < 0 )
+                    {
+                        h->auto_timebase_den = 0;
+                        continue;
+                    }
+                }
+            }
+            if( h->auto_timebase_num && !h->auto_timebase_den )
+                if( try_mkv_timebase_den( fpss, h, timecodes_num - 1 ) < 0 )
+                    goto fail;
+            free( fpss );
+        }
+
+        if( timecodes_num > 1 )
+            h->assume_fps = 1 / (timecodes[timecodes_num - 1] - timecodes[timecodes_num - 2]);
+        else
+            h->assume_fps = (double)info->fps_num / info->fps_den;
+        h->last_timecode = timecodes[timecodes_num - 1];
+    }
+
+    if( h->auto_timebase_den || h->auto_timebase_num )
+    {
+        x264_reduce_fraction( &h->timebase_num, &h->timebase_den );
+        fprintf( stderr, "timecode [info]: automatic timebase generation %d/%d\n", h->timebase_num, h->timebase_den );
+    }
+    else if( h->timebase_den <= 0 )
+    {
+        fprintf( stderr, "timecode [error]: automatic timebase generation failed.\n"
+                         "                  Specify an appropriate timebase manually.\n" );
+        goto fail;
+    }
+
+    h->pts = malloc( h->stored_pts_num * sizeof(int64_t) );
+    if( !h->pts )
+        goto fail;
+    pts_seek_offset = (int64_t)( timecodes[h->seek] * ((double)h->timebase_den / h->timebase_num) + 0.5 );
+    h->pts[0] = 0;
+    for( num = 1; num < h->stored_pts_num; num++ )
+    {
+        h->pts[num] = (int64_t)( timecodes[h->seek + num] * ((double)h->timebase_den / h->timebase_num) + 0.5 );
+        h->pts[num] -= pts_seek_offset;
+        if( h->pts[num] <= h->pts[num - 1] )
+        {
+            fprintf( stderr, "timecode [error]: invalid timebase or timecode for frame %d\n", num );
+            goto fail;
+        }
+    }
+
+    free( timecodes );
+    return 0;
+
+fail:
+    if( timecodes )
+        free( timecodes );
+    if( fpss )
+        free( fpss );
+    return -1;
+}
+
+#undef DOUBLE_EPSILON
+#undef MKV_TIMEBASE_DEN
+
+static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
+{
+    int ret = 0;
+    FILE *tcfile_in;
+    timecode_hnd_t *h = malloc( sizeof(timecode_hnd_t) );
+    if( !h )
+    {
+        fprintf( stderr, "timecode [error]: malloc failed\n" );
+        return -1;
+    }
+    h->input = input;
+    h->p_handle = *p_handle;
+    h->frame_total = input.get_frame_total( h->p_handle );
+    h->seek = opt->seek;
+    if( opt->timebase )
+        ret = sscanf( opt->timebase, "%d/%d", &h->timebase_num, &h->timebase_den );
+    if( ret == 1 )
+        h->timebase_num = atoi( opt->timebase );
+    h->auto_timebase_num = !ret;
+    h->auto_timebase_den = ret < 2;
+    if( h->auto_timebase_num )
+        h->timebase_num = info->fps_den; /* can be changed later by auto timebase generation */
+    if( h->auto_timebase_den )
+        h->timebase_den = 0;             /* set later by auto timebase generation */
+    timecode_input.picture_alloc = h->input.picture_alloc;
+    timecode_input.picture_clean = h->input.picture_clean;
+
+    *p_handle = h;
+
+    tcfile_in = fopen( psz_filename, "rb" );
+    if( !tcfile_in )
+    {
+        fprintf( stderr, "timecode [error]: can't open `%s'\n", psz_filename );
+        return -1;
+    }
+    else if( !x264_is_regular_file( tcfile_in ) )
+    {
+        fprintf( stderr, "timecode [error]: tcfile input incompatible with non-regular file `%s'\n", psz_filename );
+        fclose( tcfile_in );
+        return -1;
+    }
+
+    if( parse_tcfile( tcfile_in, h, info ) < 0 )
+    {
+        if( h->pts )
+            free( h->pts );
+        fclose( tcfile_in );
+        return -1;
+    }
+    fclose( tcfile_in );
+
+    info->timebase_num = h->timebase_num;
+    info->timebase_den = h->timebase_den;
+    info->vfr = 1;
+
+    return 0;
+}
+
+static int get_frame_total( hnd_t handle )
+{
+    timecode_hnd_t *h = handle;
+    return h->frame_total;
+}
+
+static int read_frame( x264_picture_t *p_pic, hnd_t handle, int i_frame )
+{
+    timecode_hnd_t *h = handle;
+    int ret = h->input.read_frame( p_pic, h->p_handle, i_frame );
+
+    if( i_frame - h->seek < h->stored_pts_num )
+    {
+        assert( i_frame >= h->seek );
+        p_pic->i_pts = h->pts[i_frame - h->seek];
+    }
+    else
+    {
+        if( h->pts )
+        {
+            fprintf( stderr, "timecode [info]: input timecode file missing data for frame %d and later\n"
+                             "                 assuming constant fps %.6f\n", i_frame, h->assume_fps );
+            free( h->pts );
+            h->pts = NULL;
+        }
+        h->last_timecode += 1 / h->assume_fps;
+        p_pic->i_pts = (int64_t)( h->last_timecode * ((double)h->timebase_den / h->timebase_num) + 0.5 );
+    }
+
+    return ret;
+}
+
+static int release_frame( x264_picture_t *pic, hnd_t handle )
+{
+    timecode_hnd_t *h = handle;
+    if( h->input.release_frame )
+        return h->input.release_frame( pic, h->p_handle );
+    return 0;
+}
+
+static int close_file( hnd_t handle )
+{
+    timecode_hnd_t *h = handle;
+    if( h->pts )
+        free( h->pts );
+    h->input.close_file( h->p_handle );
+    free( h );
+    return 0;
+}
+
+cli_input_t timecode_input = { open_file, get_frame_total, NULL, read_frame, release_frame, NULL, close_file };
index 041dbbc..b309320 100644 (file)
--- a/muxers.h
+++ b/muxers.h
@@ -41,6 +41,11 @@ static inline int64_t gcd( int64_t a, int64_t b )
     }
 }
 
+static inline int64_t lcm( int64_t a, int64_t b )
+{
+    return ( a / gcd( a, b ) ) * b;
+}
+
 static inline char *get_filename_extension( char *filename )
 {
     char *ext = filename + strlen( filename );
index b99eaed..bb34845 100644 (file)
@@ -93,7 +93,6 @@ static void recompute_bitrate_mp4( GF_ISOFile *p_file, int i_track )
 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;
@@ -117,13 +116,11 @@ static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest
          * 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 )
+        if( mdhd_duration != gf_isom_get_media_duration( p_mp4->p_file, p_mp4->i_track ) )
         {
             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.
diff --git a/x264.c b/x264.c
index e805042..45b1248 100644 (file)
--- a/x264.c
+++ b/x264.c
@@ -57,6 +57,8 @@ typedef struct {
     hnd_t hin;
     hnd_t hout;
     FILE *qpfile;
+    FILE *tcfile_out;
+    double timebase_convert_multiplier;
 } cli_opt_t;
 
 /* i/o file operation function pointer structs */
@@ -506,6 +508,11 @@ static void Help( x264_param_t *defaults, int longhelp )
     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" );
+    H2( "      --tcfile-in <string>    Force timestamp generation with timecode file\n" );
+    H2( "      --tcfile-out <string>   Output timecode v2 file from input timestamps\n" );
+    H2( "      --timebase <int/int>    Specify timebase numerator and denominator\n"
+        "                 <integer>    Specify timebase numerator for input timecode file\n"
+        "                              or specify timebase denominator for other input\n" );
     H0( "\n" );
 }
 
@@ -527,6 +534,9 @@ static void Help( x264_param_t *defaults, int longhelp )
 #define OPT_DEMUXER 271
 #define OPT_INDEX 272
 #define OPT_INTERLACED 273
+#define OPT_TCFILE_IN 274
+#define OPT_TCFILE_OUT 275
+#define OPT_TIMEBASE 276
 
 static char short_options[] = "8A:B:b:f:hI:i:m:o:p:q:r:t:Vvw";
 static struct option long_options[] =
@@ -661,6 +671,9 @@ static struct option long_options[] =
     { "colormatrix", required_argument, NULL, 0 },
     { "chromaloc",   required_argument, NULL, 0 },
     { "force-cfr",         no_argument, NULL, 0 },
+    { "tcfile-in",   required_argument, NULL, OPT_TCFILE_IN },
+    { "tcfile-out",  required_argument, NULL, OPT_TCFILE_OUT },
+    { "timebase",    required_argument, NULL, OPT_TIMEBASE },
     {0, 0, 0, 0}
 };
 
@@ -793,6 +806,7 @@ static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt )
     const char *demuxer = demuxer_names[0];
     char *output_filename = NULL;
     const char *muxer = muxer_names[0];
+    char *tcfile_name = NULL;
     x264_param_t defaults;
     char *profile = NULL;
     int b_thread_input = 0;
@@ -953,6 +967,20 @@ static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt )
             case OPT_INTERLACED:
                 b_user_interlaced = 1;
                 goto generic_option;
+            case OPT_TCFILE_IN:
+                tcfile_name = optarg;
+                break;
+            case OPT_TCFILE_OUT:
+                opt->tcfile_out = fopen( optarg, "wb" );
+                if( !opt->tcfile_out )
+                {
+                    fprintf( stderr, "x264 [error]: can't open `%s'\n", optarg );
+                    return -1;
+                }
+                break;
+            case OPT_TIMEBASE:
+                input_opt.timebase = optarg;
+                break;
             default:
 generic_option:
             {
@@ -1038,6 +1066,27 @@ generic_option:
                  info.height, info.interlaced ? 'i' : 'p', info.sar_width, info.sar_height,
                  info.fps_num, info.fps_den, info.vfr ? 'v' : 'c' );
 
+    if( tcfile_name )
+    {
+        if( b_user_fps )
+        {
+            fprintf( stderr, "x264 [error]: --fps + --tcfile-in is incompatible.\n" );
+            return -1;
+        }
+        if( timecode_input.open_file( tcfile_name, &opt->hin, &info, &input_opt ) )
+        {
+            fprintf( stderr, "x264 [error]: timecode input failed\n" );
+            return -1;
+        }
+        else
+            input = timecode_input;
+    }
+    else if( !info.vfr && input_opt.timebase )
+    {
+        fprintf( stderr, "x264 [error]: --timebase is incompatible with cfr input\n" );
+        return -1;
+    }
+
     /* set param flags from the info flags as necessary */
     param->i_csp       = info.csp;
     param->i_height    = info.height;
@@ -1061,8 +1110,35 @@ generic_option:
     }
     else
     {
-        param->i_timebase_den = param->i_fps_num;
         param->i_timebase_num = param->i_fps_den;
+        param->i_timebase_den = param->i_fps_num;
+    }
+    if( !tcfile_name && input_opt.timebase )
+    {
+        int i_user_timebase_num;
+        int i_user_timebase_den;
+        int ret = sscanf( input_opt.timebase, "%d/%d", &i_user_timebase_num, &i_user_timebase_den );
+        if( !ret )
+        {
+            fprintf( stderr, "x264 [error]: invalid argument: timebase = %s\n", input_opt.timebase );
+            return -1;
+        }
+        else if( ret == 1 )
+        {
+            i_user_timebase_num = param->i_timebase_num;
+            i_user_timebase_den = atoi( input_opt.timebase );
+        }
+        opt->timebase_convert_multiplier = ((double)i_user_timebase_den / param->i_timebase_den)
+                                         * ((double)param->i_timebase_num / i_user_timebase_num);
+        if( opt->timebase_convert_multiplier < 1 )
+        {
+            fprintf( stderr, "x264 [error]: timebase you specified will generate nonmonotonic pts: %d/%d\n",
+                     i_user_timebase_num, i_user_timebase_den );
+            return -1;
+        }
+        param->i_timebase_num = i_user_timebase_num;
+        param->i_timebase_den = i_user_timebase_den;
+        param->b_vfr_input = 1;
     }
     if( !param->vui.i_sar_width || !param->vui.i_sar_height )
     {
@@ -1274,6 +1350,9 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
             return -1;
     }
 
+    if( opt->tcfile_out )
+        fprintf( opt->tcfile_out, "# timecode format v2\n" );
+
     /* Encode frames */
     for( i_frame = 0, i_frame_output = 0; b_ctrl_c == 0 && (i_frame < i_frame_total || i_frame_total == 0); )
     {
@@ -1282,21 +1361,29 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
 
         if( !param->b_vfr_input )
             pic.i_pts = i_frame;
+        if( opt->timebase_convert_multiplier )
+            pic.i_pts = (int64_t)( pic.i_pts * opt->timebase_convert_multiplier + 0.5 );
+
+        int64_t output_pts = pic.i_pts * dts_compress_multiplier;   /* pts libx264 returns */
+
         if( pic.i_pts <= largest_pts )
         {
             if( param->i_log_level >= X264_LOG_WARNING )
             {
                 if( 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 * dts_compress_multiplier, largest_pts * dts_compress_multiplier );
+                             i_frame, output_pts, largest_pts * dts_compress_multiplier );
                 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;
+            output_pts = pic.i_pts * dts_compress_multiplier;
         }
         second_largest_pts = largest_pts;
         largest_pts = pic.i_pts;
+        if( opt->tcfile_out )
+            fprintf( opt->tcfile_out, "%.6f\n", output_pts * ((double)param->i_timebase_num / param->i_timebase_den) * 1e3 );
 
         if( opt->qpfile )
             parse_qpfile( opt, &pic, i_frame + opt->i_seek );
@@ -1356,6 +1443,12 @@ static int  Encode( x264_param_t *param, cli_opt_t *opt )
     if( b_ctrl_c )
         fprintf( stderr, "aborted at input frame %d, output frame %d\n", opt->i_seek + i_frame, i_frame_output );
 
+    if( opt->tcfile_out )
+    {
+        fclose( opt->tcfile_out );
+        opt->tcfile_out = NULL;
+    }
+
     input.close_file( opt->hin );
     output.close_file( opt->hout, largest_pts, second_largest_pts );