--- /dev/null
+/*****************************************************************************
+ * 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 };
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 */
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" );
}
#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[] =
{ "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}
};
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;
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:
{
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;
}
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 )
{
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); )
{
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 );
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 );