diff options
author | jstebbins <[email protected]> | 2009-09-27 17:55:28 +0000 |
---|---|---|
committer | jstebbins <[email protected]> | 2009-09-27 17:55:28 +0000 |
commit | 4025614015154c8c5cd6239a7a81dec8bcedec55 (patch) | |
tree | c83fcc098725f0f49d7b3bbf256ef7ebb4027a8a /libhb/enctheora.c | |
parent | ec407541651b15e9ac2947613e87fc0796628a77 (diff) |
theora: improvements to our theora implementation
- support 2-pass mode which is new to theora 1.1
- set soft target rate control so that single pass abr behaves more like
theora 1.0 and doesn't look quite so awful.
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@2842 b64f7644-9d1e-0410-96f1-a4d463321fa5
Diffstat (limited to 'libhb/enctheora.c')
-rw-r--r-- | libhb/enctheora.c | 212 |
1 files changed, 209 insertions, 3 deletions
diff --git a/libhb/enctheora.c b/libhb/enctheora.c index 4206a2f70..042262c76 100644 --- a/libhb/enctheora.c +++ b/libhb/enctheora.c @@ -21,18 +21,39 @@ hb_work_object_t hb_enctheora = struct hb_work_private_s { - hb_job_t * job; + hb_job_t * job; - th_enc_ctx * ctx; + th_enc_ctx * ctx; + + FILE * file; + unsigned char stat_buf[80]; + int stat_read; + int stat_fill; }; int enctheoraInit( hb_work_object_t * w, hb_job_t * job ) { + int keyframe_frequency, log_keyframe, ret; hb_work_private_t * pv = calloc( 1, sizeof( hb_work_private_t ) ); w->private_data = pv; pv->job = job; + if( job->pass != 0 && job->pass != -1 ) + { + char filename[1024]; + memset( filename, 0, 1024 ); + hb_get_tempory_filename( job->h, filename, "theroa.log" ); + if ( job->pass == 1 ) + { + pv->file = fopen( filename, "wb" ); + } + else + { + pv->file = fopen( filename, "rb" ); + } + } + th_info ti; th_comment tc; ogg_packet op; @@ -77,7 +98,94 @@ int enctheoraInit( hb_work_object_t * w, hb_job_t * job ) } } + if ( job->pass == 2 && !job->cfr ) + { + /* Even though the framerate might be different due to VFR, + we still want the same keyframe intervals as the 1st pass, + so the 1st pass stats won't conflict on frame decisions. */ + hb_interjob_t * interjob = hb_interjob_get( job->h ); + keyframe_frequency = ( 10 * interjob->vrate / interjob->vrate_base ) + 1; + } + else + { + int fps = job->vrate / job->vrate_base; + + /* adjust +1 when fps has remainder to bump + { 23.976, 29.976, 59.94 } to { 24, 30, 60 } */ + if (job->vrate % job->vrate_base) + fps += 1; + + keyframe_frequency = fps * 10; + } + int tmp = keyframe_frequency - 1; + for (log_keyframe = 0; tmp; log_keyframe++) + tmp >>= 1; + + hb_log("theora: keyint: %i", keyframe_frequency); + + ti.keyframe_granule_shift = log_keyframe; + pv->ctx = th_encode_alloc( &ti ); + th_info_clear( &ti ); + + ret = th_encode_ctl(pv->ctx, TH_ENCCTL_SET_KEYFRAME_FREQUENCY_FORCE, + &keyframe_frequency, sizeof(keyframe_frequency)); + if( ret < 0 ) + { + hb_log("theora: Could not set keyframe interval to %d", keyframe_frequency); + } + + /* Set "soft target" rate control which improves quality at the + * expense of solid bitrate caps */ + int arg = TH_RATECTL_CAP_UNDERFLOW; + ret = th_encode_ctl(pv->ctx, TH_ENCCTL_SET_RATE_FLAGS, &arg, sizeof(arg)); + if( ret < 0 ) + { + hb_log("theora: Could not set soft ratecontrol"); + } + if( job->pass != 0 && job->pass != -1 ) + { + arg = keyframe_frequency * 7 >> 1; + ret = th_encode_ctl(pv->ctx, TH_ENCCTL_SET_RATE_BUFFER, &arg, sizeof(arg)); + if( ret < 0 ) + { + hb_log("theora: Could not set rate control buffer"); + } + } + + if( job->pass == 1 ) + { + unsigned char *buffer; + int bytes; + bytes = th_encode_ctl(pv->ctx, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer)); + if( bytes < 0 ) + { + hb_error("Could not set up the first pass of two-pass mode.\n"); + hb_error("Did you remember to specify an estimated bitrate?\n"); + return 1; + } + if( fwrite( buffer, 1, bytes, pv->file ) < bytes ) + { + hb_error("Unable to write to two-pass data file.\n"); + return 1; + } + fflush( pv->file ); + } + if( job->pass == 2 ) + { + /* Enable the second pass here. + * We make this call just to set the encoder into 2-pass mode, because + * by default enabling two-pass sets the buffer delay to the whole file + * (because there's no way to explicitly request that behavior). + * If we waited until we were actually encoding, it would overwite our + * settings.*/ + hb_log("enctheora: init 2nd pass"); + if( th_encode_ctl( pv->ctx, TH_ENCCTL_2PASS_IN, NULL, 0) < 0) + { + hb_log("theora: Could not set up the second pass of two-pass mode."); + return 1; + } + } th_comment_init( &tc ); @@ -109,6 +217,10 @@ void enctheoraClose( hb_work_object_t * w ) th_encode_free( pv->ctx ); + if( pv->file ) + { + fclose( pv->file ); + } free( pv ); w->private_data = NULL; } @@ -138,9 +250,82 @@ int enctheoraWork( hb_work_object_t * w, hb_buffer_t ** buf_in, // drop them for now. *buf_out = in; *buf_in = NULL; - return HB_WORK_DONE; + th_encode_packetout( pv->ctx, 1, &op ); + if( job->pass == 1 ) + { + unsigned char *buffer; + int bytes; + + bytes = th_encode_ctl(pv->ctx, TH_ENCCTL_2PASS_OUT, + &buffer, sizeof(buffer)); + if( bytes < 0 ) + { + fprintf(stderr,"Could not read two-pass data from encoder.\n"); + return HB_WORK_DONE; + } + fseek( pv->file, 0, SEEK_SET ); + if( fwrite( buffer, 1, bytes, pv->file ) < bytes) + { + fprintf(stderr,"Unable to write to two-pass data file.\n"); + return HB_WORK_DONE; + } + fflush( pv->file ); + } + return HB_WORK_DONE; } + if( job->pass == 2 ) + { + for(;;) + { + int bytes, size, ret; + /*Ask the encoder how many bytes it would like.*/ + bytes = th_encode_ctl( pv->ctx, TH_ENCCTL_2PASS_IN, NULL, 0 ); + if( bytes < 0 ) + { + hb_error("Error requesting stats size in second pass."); + *job->die = 1; + return HB_WORK_DONE; + } + + /*If it's got enough, stop.*/ + if( bytes == 0 ) break; + + /*Read in some more bytes, if necessary.*/ + if( bytes > pv->stat_fill - pv->stat_read ) + size = bytes - (pv->stat_fill - pv->stat_read); + else + size = 0; + if( size > 80 - pv->stat_fill ) + size = 80 - pv->stat_fill; + if( size > 0 && + fread( pv->stat_buf+pv->stat_fill, 1, size, pv->file ) < size ) + { + hb_error("Could not read frame data from two-pass data file!"); + *job->die = 1; + return HB_WORK_DONE; + } + pv->stat_fill += size; + + /*And pass them off.*/ + if( bytes > pv->stat_fill - pv->stat_read ) + bytes = pv->stat_fill - pv->stat_read; + ret = th_encode_ctl( pv->ctx, TH_ENCCTL_2PASS_IN, + pv->stat_buf+pv->stat_read, bytes); + if( ret < 0 ) + { + hb_error("Error submitting pass data in second pass."); + *job->die = 1; + return HB_WORK_DONE; + } + /*If the encoder consumed the whole buffer, reset it.*/ + if( ret >= pv->stat_fill - pv->stat_read ) + pv->stat_read = pv->stat_fill = 0; + /*Otherwise remember how much it used.*/ + else + pv->stat_read += ret; + } + } memset(&op, 0, sizeof(op)); memset(&ycbcr, 0, sizeof(ycbcr)); @@ -163,6 +348,27 @@ int enctheoraWork( hb_work_object_t * w, hb_buffer_t ** buf_in, th_encode_ycbcr_in( pv->ctx, ycbcr ); + if( job->pass == 1 ) + { + unsigned char *buffer; + int bytes; + + bytes = th_encode_ctl(pv->ctx, TH_ENCCTL_2PASS_OUT, + &buffer, sizeof(buffer)); + if( bytes < 0 ) + { + fprintf(stderr,"Could not read two-pass data from encoder.\n"); + *job->die = 1; + return HB_WORK_DONE; + } + if( fwrite( buffer, 1, bytes, pv->file ) < bytes) + { + fprintf(stderr,"Unable to write to two-pass data file.\n"); + *job->die = 1; + return HB_WORK_DONE; + } + fflush( pv->file ); + } th_encode_packetout( pv->ctx, 0, &op ); buf = hb_buffer_init( op.bytes + sizeof(op) ); |