Commit 3e760449 authored by Jan Gerber's avatar Jan Gerber
Browse files

add 2 pass encoding

parent 33b9d4ec
......@@ -7,6 +7,8 @@ svn
- update to ffmpeg trunk and new ffmepg api
- use new libtheora encoding api
add new encoding options --soft-target, --buf-delay
- two pass encoding, --two-pass
or in two calls with --first-pass and --second-pass
0.24 2009-03-12
- fix to make --max_size --no_upscaling work
......
......@@ -53,6 +53,9 @@ enum {
NULL_FLAG,
DEINTERLACE_FLAG,
SOFTTARGET_FLAG,
TWOPASS_FLAG,
FIRSTPASS_FLAG,
SECONDPASS_FLAG,
OPTIMIZE_FLAG,
SYNC_FLAG,
NOAUDIO_FLAG,
......@@ -178,7 +181,7 @@ static ff2theora ff2theora_init() {
this->picture_height=0; // set to 0 to not resize the output
this->video_quality=-1; // defaults set later
this->video_bitrate=0;
this->keyint=64;
this->keyint=0;
this->force_input_fps.num = -1;
this->force_input_fps.den = 1;
this->sync=0;
......@@ -764,7 +767,7 @@ void ff2theora_output(ff2theora this) {
this->picture_width, this->picture_height, this->pix_fmt,
sws_flags, NULL, NULL, NULL
);
if (!info.frontend) {
if (!info.frontend && !(info.twopass==3 && info.passno==2)) {
if (this->frame_topBand || this->frame_bottomBand ||
this->frame_leftBand || this->frame_rightBand ||
this->picture_width != (display_width-this->frame_leftBand - this->frame_rightBand) ||
......@@ -1021,11 +1024,15 @@ void ff2theora_output(ff2theora this) {
info.speed_level = max_speed_level;
th_encode_ctl(info.td, TH_ENCCTL_SET_SPLEVEL, &info.speed_level, sizeof(int));
}
if(this->buf_delay >= 0){
if(info.passno!=1 && this->buf_delay >= 0){
int arg = this->buf_delay;
ret = th_encode_ctl(info.td, TH_ENCCTL_SET_RATE_BUFFER,
&this->buf_delay, sizeof(this->buf_delay));
if (this->buf_delay != arg)
fprintf(stderr, "Warning: could not set desired buffer delay of %d, using %d instead.\n",
arg, this->buf_delay);
if(ret < 0){
fprintf(stderr, "Warning: could not set desired buffer delay. %d\n", ret);
fprintf(stderr, "Warning: could not set desired buffer delay.\n");
}
}
/* setting just the granule shift only allows power-of-two keyframe
......@@ -1042,16 +1049,58 @@ void ff2theora_output(ff2theora this) {
if(ret<0)
fprintf(stderr, "Could not set encoder flags for --soft-target\n");
/* Default buffer control is overridden on two-pass */
if(this->buf_delay<0){
if((this->keyint*7>>1)>5*this->framerate_new.num/this->framerate_new.den)
arg = this->keyint*7>>1;
else
arg = 30*this->framerate_new.num/this->framerate_new.den;
ret = th_encode_ctl(info.td, TH_ENCCTL_SET_RATE_BUFFER, &arg,sizeof(arg));
if(ret<0)
fprintf(stderr, "Could not set rate control buffer for --soft-target\n");
if(!info.twopass && this->buf_delay<0){
if((this->keyint*7>>1)>5*this->framerate_new.num/this->framerate_new.den)
arg = this->keyint*7>>1;
else
arg = 30*this->framerate_new.num/this->framerate_new.den;
ret = th_encode_ctl(info.td, TH_ENCCTL_SET_RATE_BUFFER, &arg,sizeof(arg));
if(ret<0)
fprintf(stderr, "Could not set rate control buffer for --soft-target\n");
}
}
/* set up two-pass if needed */
if(info.passno==1){
unsigned char *buffer;
int bytes;
bytes=th_encode_ctl(info.td,TH_ENCCTL_2PASS_OUT,&buffer,sizeof(buffer));
if(bytes<0){
fprintf(stderr,"Could not set up the first pass of two-pass mode.\n");
fprintf(stderr,"Did you remember to specify an estimated bitrate?\n");
exit(1);
}
/*Perform a seek test to ensure we can overwrite this placeholder data at
the end; this is better than letting the user sit through a whole
encode only to find out their pass 1 file is useless at the end.*/
if(fseek(info.twopass_file,0,SEEK_SET)<0){
fprintf(stderr,"Unable to seek in two-pass data file.\n");
exit(1);
}
if(fwrite(buffer,1,bytes,info.twopass_file)<bytes){
fprintf(stderr,"Unable to write to two-pass data file.\n");
exit(1);
}
fflush(info.twopass_file);
}
if(info.passno==2){
/* enable second pass here, actual data feeding comes later */
if(th_encode_ctl(info.td,TH_ENCCTL_2PASS_IN,NULL,0)<0){
fprintf(stderr,"Could not set up the second pass of two-pass mode.\n");
exit(1);
}
if(info.twopass==3){
/* 'automatic' second pass */
if(av_seek_frame( this->context, -1, (int64_t)AV_TIME_BASE*this->start_time, 1)<0){
fprintf(stderr,"Could not rewind video input file for second pass!\n");
exit(1);
}
if(fseek(info.twopass_file,0,SEEK_SET)<0){
fprintf(stderr,"Unable to seek in two-pass data file.\n");
exit(1);
}
}
}
}
/* audio settings here */
info.channels = this->channels;
......@@ -1302,7 +1351,8 @@ void ff2theora_output(ff2theora this) {
}
}
}
if ((audio_eos && !audio_done) || (ret >= 0 && pkt.stream_index == this->audio_index)) {
if (info.passno!=1)
if ((audio_eos && !audio_done) || (ret >= 0 && pkt.stream_index == this->audio_index)) {
this->pts_offset = (double) pkt.pts / AV_TIME_BASE -
(double) this->sample_count / this->sample_rate;
while((audio_eos && !audio_done) || avpkt.size > 0 ) {
......@@ -1352,6 +1402,7 @@ void ff2theora_output(ff2theora this) {
}
}
if (info.passno!=1)
if (!this->disable_subtitles && subtitles_enabled[pkt.stream_index] && is_supported_subtitle_stream(this, pkt.stream_index)) {
AVStream *stream=this->context->streams[pkt.stream_index];
AVCodecContext *enc = stream->codec;
......@@ -1425,7 +1476,7 @@ void ff2theora_output(ff2theora this) {
}
/* if we have subtitles starting before then, add it */
if (info.with_kate) {
if (info.passno!=1 && info.with_kate) {
double avtime = info.audio_only ? info.audiotime :
info.video_only ? info.videotime :
info.audiotime < info.videotime ? info.audiotime : info.videotime;
......@@ -1446,8 +1497,8 @@ void ff2theora_output(ff2theora this) {
}
/* flush out the file */
oggmux_flush (&info, video_eos + audio_eos);
av_free_packet (&pkt);
} while (ret >= 0 && !(audio_done && video_done));
......@@ -1651,6 +1702,19 @@ void print_usage() {
" higher/smoother overall. Soft target also\n"
" allows an optional -v setting to specify\n"
" a minimum allowed quality.\n\n"
" --two-pass Compress input using two-pass rate control\n"
" This option requires that the input to the\n"
" to the encoder is seekable and performs\n"
" both passes automatically.\n\n"
" --first-pass <filename> Perform first-pass of a two-pass rate\n"
" controlled encoding, saving pass data to\n"
" <filename> for a later second pass\n\n"
" --second-pass <filename> Perform second-pass of a two-pass rate\n"
" controlled encoding, reading first-pass\n"
" data from <filename>. The first pass\n"
" data must come from a first encoding pass\n"
" using identical input video to work\n"
" properly.\n\n"
" --optimize optimize video output filesize (slower) (same as speedlevel 0)\n"
" --speedlevel [0 2] encoding is faster with higher values the cost is quality and bandwidth\n"
......@@ -1800,7 +1864,9 @@ int main(int argc, char **argv) {
{"videobitrate",required_argument,NULL,'V'},
{"audioquality",required_argument,NULL,'a'},
{"audiobitrate",required_argument,NULL,'A'},
{"soft-target",0,&flag,SOFTTARGET_FLAG},
{"two-pass",0,&flag,TWOPASS_FLAG},
{"first-pass",required_argument,&flag,FIRSTPASS_FLAG},
{"second-pass",required_argument,&flag,SECONDPASS_FLAG},
{"keyint",required_argument,NULL,'K'},
{"buf-delay",required_argument,NULL,'d'},
{"deinterlace",0,&flag,DEINTERLACE_FLAG},
......@@ -1887,6 +1953,33 @@ int main(int argc, char **argv) {
convert->soft_target = 1;
flag = -1;
break;
case TWOPASS_FLAG:
info.twopass = 3;
info.twopass_file = tmpfile();
if(!info.twopass_file){
fprintf(stderr,"Unable to open temporary file for twopass data\n");
exit(1);
}
flag = -1;
break;
case FIRSTPASS_FLAG:
info.twopass = 1;
info.twopass_file = fopen(optarg,"wb");
if(!info.twopass_file){
fprintf(stderr,"Unable to open \'%s\' for twopass data\n", optarg);
exit(1);
}
flag = -1;
break;
case SECONDPASS_FLAG:
info.twopass = 2;
info.twopass_file = fopen(optarg,"rb");
if(!info.twopass_file){
fprintf(stderr,"Unable to open \'%s\' for twopass data\n", optarg);
exit(1);
}
flag = -1;
break;
case PP_FLAG:
if (!strcmp(optarg, "help")) {
fprintf(stdout, "%s", pp_help);
......@@ -2256,6 +2349,12 @@ int main(int argc, char **argv) {
exit(1);
}
if(convert->keyint <= 0) {
/*Use a default keyframe frequency of 64 for 1-pass (streaming) mode, and
256 for two-pass mode.*/
convert->keyint = info.twopass?256:64;
}
if (convert->soft_target) {
if (convert->video_bitrate <= 0) {
fprintf(stderr,"Soft rate target (--soft-tagret) requested without a bitrate (-V).\n");
......@@ -2320,13 +2419,15 @@ int main(int argc, char **argv) {
info.outfile = stdout;
}
else {
info.outfile = fopen(outputfile_name,"wb");
if(info.twopass!=1)
info.outfile = fopen(outputfile_name,"wb");
}
#else
if (!strcmp(outputfile_name,"-")) {
snprintf(outputfile_name,sizeof(outputfile_name),"/dev/stdout");
}
info.outfile = fopen(outputfile_name,"wb");
if(info.twopass!=1)
info.outfile = fopen(outputfile_name,"wb");
#endif
if (output_json) {
if (using_stdin) {
......@@ -2358,7 +2459,7 @@ int main(int argc, char **argv) {
convert->pts_offset =
(double) convert->context->start_time / AV_TIME_BASE;
if (!info.outfile) {
if (info.twopass!=1 && !info.outfile) {
if (info.frontend)
fprintf(info.frontend, "\"{result\": \"Unable to open output file.\"}\n");
else
......@@ -2368,7 +2469,9 @@ int main(int argc, char **argv) {
if (convert->context->duration != AV_NOPTS_VALUE) {
info.duration = (double)convert->context->duration / AV_TIME_BASE;
}
ff2theora_output(convert);
for(info.passno=(info.twopass==3?1:info.twopass);info.passno<=(info.twopass==3?2:info.twopass);info.passno++){
ff2theora_output(convert);
}
convert->audio_index = convert->video_index = -1;
}
else{
......
......@@ -103,6 +103,7 @@ typedef struct ff2theora{
int uv_lut_used;
unsigned char y_lut[256];
unsigned char uv_lut[256];
}
*ff2theora;
......
......@@ -71,6 +71,10 @@ void init_info(oggmux_info *info) {
info->k_page=0;
#endif
info->twopass_file = NULL;
info->twopass = 0;
info->passno = 0;
info->with_kate = 0;
info->n_kate_streams = 0;
info->kate_streams = NULL;
......@@ -254,7 +258,6 @@ void oggmux_init (oggmux_info *info) {
ogg_stream_init (&info->to, rand ()); /* oops, add one ot the above */
}
/* init theora done */
/* initialize Vorbis too, if we have audio. */
if (!info->video_only) {
int ret;
......@@ -285,7 +288,7 @@ void oggmux_init (oggmux_info *info) {
/* audio init done */
/* initialize kate if we have subtitles */
if (info->with_kate) {
if (info->with_kate && info->passno!=1) {
#ifdef HAVE_KATE
int ret, n;
for (n=0; n<info->n_kate_streams; ++n) {
......@@ -309,7 +312,7 @@ void oggmux_init (oggmux_info *info) {
/* first packet should be skeleton fishead packet, if skeleton is used */
if (info->with_skeleton) {
if (info->with_skeleton && info->passno!=1) {
ogg_stream_init (&info->so, rand());
add_fishead_packet (info);
if (ogg_stream_pageout (&info->so, &og) != 1) {
......@@ -337,13 +340,15 @@ void oggmux_init (oggmux_info *info) {
fprintf(stderr, "Internal Theora library error.\n");
exit(1);
}
ogg_stream_packetin(&info->to, &op);
if(ogg_stream_pageout(&info->to, &og) != 1) {
fprintf(stderr, "Internal Ogg library error.\n");
exit(1);
if(info->passno!=1){
ogg_stream_packetin(&info->to, &op);
if(ogg_stream_pageout(&info->to, &og) != 1) {
fprintf(stderr, "Internal Ogg library error.\n");
exit(1);
}
fwrite(og.header, 1, og.header_len, info->outfile);
fwrite(og.body, 1, og.body_len, info->outfile);
}
fwrite(og.header, 1, og.header_len, info->outfile);
fwrite(og.body, 1, og.body_len, info->outfile);
/* create the remaining theora headers */
for(;;){
......@@ -353,10 +358,11 @@ void oggmux_init (oggmux_info *info) {
exit(1);
}
else if(!ret) break;
ogg_stream_packetin(&info->to, &op);
if(info->passno!=1)
ogg_stream_packetin(&info->to, &op);
}
}
if (!info->video_only) {
if (!info->video_only && info->passno!=1) {
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
......@@ -378,7 +384,7 @@ void oggmux_init (oggmux_info *info) {
}
#ifdef HAVE_KATE
if (info->with_kate) {
if (info->with_kate && info->passno!=1) {
int n;
for (n=0; n<info->n_kate_streams; ++n) {
oggmux_kate_stream *ks=info->kate_streams+n;
......@@ -406,7 +412,7 @@ void oggmux_init (oggmux_info *info) {
#endif
/* output the appropriate fisbone packets */
if (info->with_skeleton) {
if (info->with_skeleton && info->passno!=1) {
add_fisbone_packet (info);
while (1) {
int result = ogg_stream_flush (&info->so, &og);
......@@ -425,7 +431,7 @@ void oggmux_init (oggmux_info *info) {
/* Flush the rest of our headers. This ensures
* the actual data in each stream will start
* on a new page, as per spec. */
while (1 && !info->audio_only) {
while (1 && !info->audio_only && info->passno!=1) {
int result = ogg_stream_flush (&info->to, &og);
if (result < 0) {
/* can't get here */
......@@ -437,7 +443,7 @@ void oggmux_init (oggmux_info *info) {
fwrite (og.header, 1, og.header_len, info->outfile);
fwrite (og.body, 1, og.body_len, info->outfile);
}
while (1 && !info->video_only) {
while (1 && !info->video_only && info->passno!=1) {
int result = ogg_stream_flush (&info->vo, &og);
if (result < 0) {
/* can't get here */
......@@ -450,7 +456,7 @@ void oggmux_init (oggmux_info *info) {
fwrite (og.body, 1, og.body_len, info->outfile);
}
#ifdef HAVE_KATE
if (info->with_kate) {
if (info->with_kate && info->passno!=1) {
int n;
for (n=0; n<info->n_kate_streams; ++n) {
oggmux_kate_stream *ks=info->kate_streams+n;
......@@ -470,7 +476,7 @@ void oggmux_init (oggmux_info *info) {
}
#endif
if (info->with_skeleton) {
if (info->with_skeleton && info->passno!=1) {
int result;
/* build and add the e_o_s packet */
......@@ -505,12 +511,79 @@ void oggmux_init (oggmux_info *info) {
*/
void oggmux_add_video (oggmux_info *info, th_ycbcr_buffer ycbcr, int e_o_s) {
ogg_packet op;
int r;
int r, ret;
if(info->passno==2){
for(;;){
static unsigned char buffer[80];
static int buf_pos;
int bytes;
/*Ask the encoder how many bytes it would like.*/
bytes=th_encode_ctl(info->td,TH_ENCCTL_2PASS_IN,NULL,0);
if(bytes<0){
fprintf(stderr,"Error submitting pass data in second pass.\n");
exit(1);
}
/*If it's got enough, stop.*/
if(bytes==0)break;
/*Read in some more bytes, if necessary.*/
if(bytes>80-buf_pos)bytes=80-buf_pos;
if(bytes>0&&fread(buffer+buf_pos,1,bytes,info->twopass_file)<bytes){
fprintf(stderr,"Could not read frame data from two-pass data file!\n");
exit(1);
}
/*And pass them off.*/
ret=th_encode_ctl(info->td,TH_ENCCTL_2PASS_IN,buffer,bytes);
if(ret<0){
fprintf(stderr,"Error submitting pass data in second pass.\n");
exit(1);
}
/*If the encoder consumed the whole buffer, reset it.*/
if(ret>=bytes)buf_pos=0;
/*Otherwise remember how much it used.*/
else buf_pos+=ret;
}
}
th_encode_ycbcr_in(info->td, ycbcr);
/* in two-pass mode's first pass we need to extract and save the pass data */
if(info->passno==1){
unsigned char *buffer;
int bytes = th_encode_ctl(info->td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer));
if(bytes<0){
fprintf(stderr,"Could not read two-pass data from encoder.\n");
exit(1);
}
if(fwrite(buffer,1,bytes,info->twopass_file)<bytes){
fprintf(stderr,"Unable to write to two-pass data file.\n");
exit(1);
}
fflush(info->twopass_file);
}
while (th_encode_packetout (info->td, e_o_s, &op) > 0) {
ogg_stream_packetin (&info->to, &op);
info->v_pkg++;
}
if(info->passno==1 && e_o_s){
/* need to read the final (summary) packet */
unsigned char *buffer;
int bytes = th_encode_ctl(info->td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer));
if(bytes<0){
fprintf(stderr,"Could not read two-pass summary data from encoder.\n");
exit(1);
}
if(fseek(info->twopass_file,0,SEEK_SET)<0){
fprintf(stderr,"Unable to seek in two-pass data file.\n");
exit(1);
}
if(fwrite(buffer,1,bytes,info->twopass_file)<bytes){
fprintf(stderr,"Unable to write to two-pass data file.\n");
exit(1);
}
fflush(info->twopass_file);
}
}
/**
......@@ -641,7 +714,17 @@ static void print_stats(oggmux_info *info, double timebase) {
int remaining_seconds = (long) remaining % 60;
int remaining_minutes = ((long) remaining / 60) % 60;
int remaining_hours = (long) remaining / 3600;
if (timebase - last > 0.5) {
if (info->passno==1) {
remaining = time(NULL) - info->start_time;
remaining_seconds = (long) remaining % 60;
remaining_minutes = ((long) remaining / 60) % 60;
remaining_hours = (long) remaining / 3600;
fprintf (stderr,"\r Scanning video first pass, time elapsed: %02d:%02d:%02d ",
remaining_hours, remaining_minutes, remaining_seconds
);
}
else if (timebase - last > 0.5) {
last = timebase;
if (info->frontend) {
fprintf(info->frontend, "{\"duration\": %lf, \"position\": %.02lf, \"audio_kbps\": %d, \"video_kbps\": %d, \"remaining\": %.02lf}\n",
......@@ -652,7 +735,7 @@ static void print_stats(oggmux_info *info, double timebase) {
);
fflush (info->frontend);
}
else if (timebase > 0 ) {
else if (timebase > 0) {
if (!remaining) {
remaining = time(NULL) - info->start_time;
remaining_seconds = (long) remaining % 60;
......@@ -665,11 +748,12 @@ static void print_stats(oggmux_info *info, double timebase) {
);
}
else {
fprintf (stderr,"\r %d:%02d:%02d.%02d audio: %dkbps video: %dkbps, ET: %02d:%02d:%02d, est. size: %.01lf MB ",
fprintf (stderr,"\r %d:%02d:%02d.%02d audio: %dkbps video: %dkbps, ET: %02d:%02d:%02d, est. size: %.01lf MB ",
hours, minutes, seconds, hundredths,
info->akbps, info->vkbps,
remaining_hours, remaining_minutes, remaining_seconds,
estimated_size(info, timebase)
estimated_size(info, timebase),
info->passno
);
}
}
......@@ -777,6 +861,10 @@ void oggmux_flush (oggmux_info *info, int e_o_s)
ogg_page og;
int best;
if (info->passno==1) {
print_stats(info, info->videotime);
return;
}
/* flush out the ogg pages to info->outfile */
while (1) {
/* Get pages for both streams, if not already present, and if available.*/
......@@ -936,8 +1024,10 @@ void oggmux_close (oggmux_info *info) {
if (info->with_skeleton)
ogg_stream_clear (&info->so);
if (info->outfile && info->outfile != stdout)
if (info->passno!=1 && info->outfile && info->outfile != stdout)
fclose (info->outfile);
if(info->twopass_file)
fclose(info->twopass_file);
if (info->videopage)
free(info->videopage);
......
......@@ -125,6 +125,10 @@ typedef struct
int k_page;
#endif
FILE *twopass_file;
int twopass;
int passno;
int n_kate_streams;
oggmux_kate_stream *kate_streams;
}
......@@ -140,4 +144,5 @@ extern void oggmux_add_kate_end_packet (oggmux_info *info, int idx, double t);
extern void oggmux_flush (oggmux_info *info, int e_o_s);
extern void oggmux_close (oggmux_info *info);
#endif
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment