From 4d3c4787622d44eef8b813bc4324531546bd8aa5 Mon Sep 17 00:00:00 2001 From: Yusuke Nakamura Date: Sun, 28 Feb 2010 21:42:19 -0800 Subject: [PATCH] Timecode input/output --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 | 3 +- common/common.h | 2 +- encoder/encoder.c | 10 +- input/input.h | 2 + input/timecode.c | 488 ++++++++++++++++++++++++++++++++++++++++++++++ muxers.h | 5 + output/mp4.c | 5 +- x264.c | 97 ++++++++- 8 files changed, 600 insertions(+), 12 deletions(-) create mode 100644 input/timecode.c diff --git a/Makefile b/Makefile index c0927fce..f14549bd 100644 --- 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 diff --git a/common/common.h b/common/common.h index d69ce654..878eee24 100644 --- a/common/common.h +++ b/common/common.h @@ -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; diff --git a/encoder/encoder.c b/encoder/encoder.c index f5c2dc64..fa6fe0bd 100644 --- a/encoder/encoder.c +++ b/encoder/encoder.c @@ -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; diff --git a/input/input.h b/input/input.h index 6e386f4d..7be6fcf0 100644 --- a/input/input.h +++ b/input/input.h @@ -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 index 00000000..0dddb27b --- /dev/null +++ b/input/timecode.c @@ -0,0 +1,488 @@ +/***************************************************************************** + * timecode.c: x264 timecode format file input module + ***************************************************************************** + * Copyright (C) 2010 x264 project + * + * Authors: Yusuke Nakamura + * + * 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 + +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 }; diff --git a/muxers.h b/muxers.h index 041dbbc3..b3093202 100644 --- 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 ); diff --git a/output/mp4.c b/output/mp4.c index b99eaed0..bb348451 100644 --- a/output/mp4.c +++ b/output/mp4.c @@ -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 e8050420..45b1248c 100644 --- 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 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 Force timestamp generation with timecode file\n" ); + H2( " --tcfile-out Output timecode v2 file from input timestamps\n" ); + H2( " --timebase Specify timebase numerator and denominator\n" + " 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 ); -- 2.39.2