I had a need to generate variable frame rate avi files and timecode stamps for muxing into other containers (specifically .mkv) and hacked in a -makevfr option into mencoder. There are only a handful of really awful unmaintained windows apps that manage these currently. What this does is it generates an avi output file without any null frames, which is pretty much unusable by itself, but it also generates a timecodes version2 file (timecodes.txt). The resulting avi file and timecodes file can then be muxed into a true variable frame rate container such as mkv using mkvtoolnix. So what this is useful for, is the various pseudo variable frame rate videos out there such as wmv (which imitates 1000fps) and the so called 120fps ntsc video captures. These are usually a blend of various combinations of 24000/1001, 30000/1001 and 60000/1001 videos. No fixed frame rate ever creates smooth video from them. To use this, you must use nosound as the resultant file is meaningless without it. Any frame rate input should be able to be used, and the resultant output *should* always have audio video sync. Eg: mencoder -nosound -ovc lavc -mc 0 -makevfr -o temporary_vfr.avi source.wmv the resultant temporary_vfr.avi file then needs to be muxed with mkvtoolnix specifying the timecodes for that video stream and the audio needs to be added separately. mplayer -vo null -ao pcm:fast:file=audio.wav source.wmv faac audio.wav mkvmerge -o vfr.mkv --timecodes 0:timecodes.txt temporary_vfr.avi audio.aac This should generate a real variable frame rate in a container that supports it without losing audio sync from any input. The use of -ofps can be used to set the maximum frame rate, but is usually not required. I'd actually suggest using it for wmv because at a precision of 1000fps it actually isn't any meaningful multiple of the real frame rates. The best is actually that 120 number used by avi, so -ofps 120000/1001 Generally speaking, those who already know about making variable frame rate anime will already know what to do. I realise mencoder has lots of issues and is considered broken at the moment, but there are many of us who depend on it as it fills a niche nothing else does. I also realise that I have not studied the code style of mplayer/mencoder extensively and have probably committed numerous sins in the way I've hacked this up. Thus I don't expect the code to be committed. However, I offer it as a demonstration piece and of interest since it has been incredibly useful to me, dealing with variable frame rate mkv, mp4, 120fps avi, wmv. Signed-off-by: Con Kolivas --- cfg-mencoder.h | 3 +++ libmpdemux/muxer.c | 24 +++++++++++++++++++++++- mencoder.c | 23 +++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) Index: MPlayer-1.0rc2/libmpdemux/muxer.c =================================================================== --- MPlayer-1.0rc2.orig/libmpdemux/muxer.c 2008-02-18 11:03:55.221531553 +1100 +++ MPlayer-1.0rc2/libmpdemux/muxer.c 2008-02-18 11:04:09.222335021 +1100 @@ -18,6 +18,9 @@ #include "help_mp.h" #include "stheader.h" +extern short makevfr; +extern FILE* timecodes_file; + muxer_t *muxer_new_muxer(int type,stream_t *stream){ muxer_t* muxer=calloc(1,sizeof(muxer_t)); if(!muxer) @@ -58,8 +61,27 @@ * (a) have at least one frame from each stream * (b) run out of memory */ void muxer_write_chunk(muxer_stream_t *s, size_t len, unsigned int flags, double dts, double pts) { - if(dts == MP_NOPTS_VALUE) dts= s->timer; if(pts == MP_NOPTS_VALUE) pts= s->timer; // this is wrong + if(dts == MP_NOPTS_VALUE) { + if (makevfr) { + /* In variable frame rate video, we just update the time but don't output a frame */ + if(s->h.dwSampleSize){ + // CBR + s->h.dwLength+=len/s->h.dwSampleSize; + if(len%s->h.dwSampleSize) mp_msg(MSGT_MUXER, MSGL_WARN, MSGTR_WarningLenIsntDivisible); + } else { + // VBR + s->h.dwLength++; + } + dts= s->timer; + s->timer=(double)s->h.dwLength*s->h.dwScale/s->h.dwRate; + return; + } + dts= s->timer; +} + + if (makevfr) + fprintf(timecodes_file, "%f\n",(s->timer * 1000)); if (s->muxer->muxbuf_skip_buffer) { s->muxer->cont_write_chunk(s, len, flags, dts, pts); Index: MPlayer-1.0rc2/cfg-mencoder.h =================================================================== --- MPlayer-1.0rc2.orig/cfg-mencoder.h 2008-02-18 11:03:55.221531553 +1100 +++ MPlayer-1.0rc2/cfg-mencoder.h 2008-02-18 11:04:09.222335021 +1100 @@ -205,6 +205,9 @@ {"noskiplimit", &skip_limit, CONF_TYPE_FLAG, 0, 0, -1, NULL}, {"noskip", &skip_limit, CONF_TYPE_FLAG, 0, 0, 0, NULL}, + {"makevfr", &makevfr, CONF_TYPE_FLAG, 0, 0, 1, NULL}, + {"timecodesfile", &timecodesfile, CONF_TYPE_STRING, CONF_GLOBAL, 0, 0, NULL}, + {"audio-density", &audio_density, CONF_TYPE_INT, CONF_RANGE|CONF_GLOBAL, 1, 50, NULL}, {"audio-preload", &audio_preload, CONF_TYPE_FLOAT, CONF_RANGE|CONF_GLOBAL, 0, 2, NULL}, {"audio-delay", &audio_delay_fix, CONF_TYPE_FLOAT, CONF_GLOBAL, 0, 0, NULL}, Index: MPlayer-1.0rc2/mencoder.c =================================================================== --- MPlayer-1.0rc2.orig/mencoder.c 2008-02-18 11:03:55.221531553 +1100 +++ MPlayer-1.0rc2/mencoder.c 2008-02-18 11:04:09.222335021 +1100 @@ -181,6 +181,7 @@ int force_audiofmttag=-1; char* passtmpfile="divx2pass.log"; +char* timecodesfile="timecodes.txt"; static int play_n_frames=-1; static int play_n_frames_mf=-1; @@ -296,6 +297,7 @@ \return 1 for success, 0 for failure, 2 for EOF. */ static int edl_seek(edl_record_ptr next_edl_record, demuxer_t* demuxer, demux_stream_t *d_audio, muxer_stream_t* mux_a, s_frame_data * frame_data, int framecopy); +short makevfr; ///Generate no null frame variable frame rate and timecode file. #include "cfg-mencoder.h" @@ -372,6 +374,17 @@ extern void print_wave_header(WAVEFORMATEX *h, int verbose_level); +FILE *timecodes_file; + +static int init_timecodes_output(void) +{ + timecodes_file = fopen(timecodesfile, "w"); + if(timecodes_file == NULL) + return 1; + fprintf(timecodes_file, "# timecode format v2\n"); + return 0; +} + int main(int argc,char* argv[]){ stream_t* stream=NULL; @@ -745,6 +758,12 @@ mp_msg(MSGT_MENCODER, MSGL_FATAL, MSGTR_CannotInitializeMuxer); mencoder_exit(1,NULL); } + +if (makevfr) { + if (init_timecodes_output()) + mp_msg(MSGT_MENCODER,MSGL_ERR,"Failed to open timecodes file: filename=%s\n", timecodesfile); +} + #if 0 //disabled: it horrybly distorts filtered sound if(out_file_format == MUXER_TYPE_MPEG) audio_preload = 0; @@ -1519,6 +1538,8 @@ if(sh_video){ uninit_video(sh_video);sh_video=NULL; } if(demuxer) free_demuxer(demuxer); if(stream) free_stream(stream); // kill cache thread + if (makevfr) + fclose(timecodes_file); at_eof = 0; @@ -1573,6 +1594,8 @@ if(sh_video){ uninit_video(sh_video);sh_video=NULL; } if(demuxer) free_demuxer(demuxer); if(stream) free_stream(stream); // kill cache thread +if (makevfr) + fclose(timecodes_file); return interrupted; }