diff options
author | jstebbins <[email protected]> | 2012-03-27 20:11:26 +0000 |
---|---|---|
committer | jstebbins <[email protected]> | 2012-03-27 20:11:26 +0000 |
commit | 45b8f81a2e184e2b7deaf47afc49483766191a27 (patch) | |
tree | 30ed0892995cb4ad3255909f69269c453000800a /libhb/work.c | |
parent | 7eb7737023be00fa0dc9be75a4984b80c0e5ce57 (diff) |
Rework filter pipeline
This patch enhances the filter objects. The 2 key improvements are:
1. A filter can change the image dimensions as frames pass through it.
2. A filter can output more than one frame.
In addition, I have:
Moved cropping & scalling into a filter object
Added 90 degree rotation to the rotate filter
Moved subtitle burn-in rendering to a filter object.
Moved VFR/CFR handling into a framerate shaping filter object.
Removed render.c since all it's responsibilities got moved to filters.
Improves VOBSUB and SSA subtitle handling. Allows subtitle animations.
SSA karaoke support.
My apologies in advance if anything breaks ;)
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@4546 b64f7644-9d1e-0410-96f1-a4d463321fa5
Diffstat (limited to 'libhb/work.c')
-rw-r--r-- | libhb/work.c | 367 |
1 files changed, 253 insertions, 114 deletions
diff --git a/libhb/work.c b/libhb/work.c index 0487a07f0..7b84cd30b 100644 --- a/libhb/work.c +++ b/libhb/work.c @@ -21,6 +21,7 @@ typedef struct static void work_func(); static void do_job( hb_job_t *); static void work_loop( void * ); +static void filter_loop( void * ); #define FIFO_UNBOUNDED 65536 #define FIFO_UNBOUNDED_WAKE 65535 @@ -28,6 +29,8 @@ static void work_loop( void * ); #define FIFO_LARGE_WAKE 16 #define FIFO_SMALL 16 #define FIFO_SMALL_WAKE 15 +#define FIFO_MINI 4 +#define FIFO_MINI_WAKE 3 /** * Allocates work object and launches work thread with work_func. @@ -271,6 +274,29 @@ void hb_display_job_info( hb_job_t * job ) (float) job->pfr_vrate / (float) job->pfr_vrate_base ); } + // Filters can modify dimensions. So show them first. + if( hb_list_count( job->list_filter ) ) + { + hb_log(" + %s", hb_list_count( job->list_filter) > 1 ? "filters" : "filter" ); + for( i = 0; i < hb_list_count( job->list_filter ); i++ ) + { + hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); + if( filter->settings ) + hb_log(" + %s (%s)", filter->name, filter->settings); + else + hb_log(" + %s (default settings)", filter->name); + if( filter->info ) + { + hb_filter_info_t info; + filter->info( filter, &info ); + if( info.human_readable_desc[0] ) + { + hb_log(" %s", info.human_readable_desc); + } + } + } + } + if( job->anamorphic.mode ) { hb_log( " + %s anamorphic", job->anamorphic.mode == 1 ? "strict" : job->anamorphic.mode == 2? "loose" : "custom" ); @@ -278,9 +304,8 @@ void hb_display_job_info( hb_job_t * job ) { hb_log( " + keeping source display aspect ratio"); } - hb_log( " + storage dimensions: %d * %d -> %d * %d, crop %d/%d/%d/%d, mod %i", - title->width, title->height, job->width, job->height, - job->crop[0], job->crop[1], job->crop[2], job->crop[3], job->modulus ); + hb_log( " + storage dimensions: %d * %d, mod %i", + job->width, job->height, job->modulus ); if( job->anamorphic.itu_par ) { hb_log( " + using ITU pixel aspect ratio values"); @@ -291,27 +316,13 @@ void hb_display_job_info( hb_job_t * job ) } else { - hb_log( " + dimensions: %d * %d -> %d * %d, crop %d/%d/%d/%d, mod %i", - title->width, title->height, job->width, job->height, - job->crop[0], job->crop[1], job->crop[2], job->crop[3], job->modulus ); + hb_log( " + dimensions: %d * %d, mod %i", + job->width, job->height, job->modulus ); } if ( job->grayscale ) hb_log( " + grayscale mode" ); - if( hb_list_count( job->filters ) ) - { - hb_log(" + %s", hb_list_count( job->filters) > 1 ? "filters" : "filter" ); - for( i = 0; i < hb_list_count( job->filters ); i++ ) - { - hb_filter_object_t * filter = hb_list_item( job->filters, i ); - if (filter->settings) - hb_log(" + %s (%s)", filter->name, filter->settings); - else - hb_log(" + %s (default settings)", filter->name); - } - } - if( !job->indepth_scan ) { /* Video encoder */ @@ -510,10 +521,44 @@ static void do_job( hb_job_t * job ) hb_log( "starting job" ); - if( job->anamorphic.mode ) + // Filters have an effect on settings. + // So initialize the filters and update the job. + if( job->list_filter && hb_list_count( job->list_filter ) ) { - hb_set_anamorphic_size(job, &job->width, &job->height, &job->anamorphic.par_width, &job->anamorphic.par_height); + hb_filter_init_t init; + + init.job = job; + init.pix_fmt = PIX_FMT_YUV420P; + init.width = title->width; + init.height = title->height; + init.par_width = job->anamorphic.par_width; + init.par_height = job->anamorphic.par_height; + memcpy(init.crop, title->crop, sizeof(int[4])); + init.vrate_base = title->rate_base; + init.vrate = title->rate; + init.cfr = 0; + for( i = 0; i < hb_list_count( job->list_filter ); i++ ) + { + hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); + if( filter->init( filter, &init ) ) + { + hb_error( "Failure to initialise filter '%s'", filter->name ); + *job->die = 1; + goto cleanup; + } + } + job->width = init.width; + job->height = init.height; + job->anamorphic.par_width = init.par_width; + job->anamorphic.par_height = init.par_height; + memcpy(title->crop, init.crop, sizeof(int[4])); + job->vrate_base = init.vrate_base; + job->vrate = init.vrate; + job->cfr = init.cfr; + } + if( job->anamorphic.mode ) + { if( job->vcodec & HB_VCODEC_FFMPEG_MASK ) { /* Just to make working with ffmpeg even more fun, @@ -530,47 +575,6 @@ static void do_job( hb_job_t * job ) } } - /* Keep width and height within these boundaries, - but ignore for anamorphic. For "loose" anamorphic encodes, - this stuff is covered in the pixel_ratio section above. */ - if ( job->maxHeight && ( job->height > job->maxHeight ) && ( !job->anamorphic.mode ) ) - { - job->height = job->maxHeight; - hb_fix_aspect( job, HB_KEEP_HEIGHT ); - hb_log( "Height out of bounds, scaling down to %i", job->maxHeight ); - hb_log( "New dimensions %i * %i", job->width, job->height ); - } - if ( job->maxWidth && ( job->width > job->maxWidth ) && ( !job->anamorphic.mode ) ) - { - job->width = job->maxWidth; - hb_fix_aspect( job, HB_KEEP_WIDTH ); - hb_log( "Width out of bounds, scaling down to %i", job->maxWidth ); - hb_log( "New dimensions %i * %i", job->width, job->height ); - } - - if ( job->cfr == 0 ) - { - /* Ensure we're using "Same as source" FPS */ - job->vrate = title->rate; - job->vrate_base = title->rate_base; - } - else if ( job->cfr == 2 ) - { - job->pfr_vrate = job->vrate; - job->pfr_vrate_base = job->vrate_base; - - // Ensure we're using "Same as source" FPS, with peak set by pfr_vrate_* - // For PFR, we want the framerate based on the source's actual - // framerate, unless it's higher than the specified peak framerate. - double source_fps = (double)job->title->rate / job->title->rate_base; - double peak_l_fps = (double)job->vrate / job->vrate_base; - if ( source_fps < peak_l_fps ) - { - job->vrate_base = title->rate_base; - job->vrate = title->rate; - } - } - job->fifo_mpeg2 = hb_fifo_init( FIFO_LARGE, FIFO_LARGE_WAKE ); job->fifo_raw = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); job->fifo_sync = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); @@ -829,42 +833,6 @@ static void do_job( hb_job_t * job ) w->fifo_in = job->fifo_mpeg2; w->fifo_out = job->fifo_raw; - /* Video renderer */ - hb_list_add( job->list_work, ( w = hb_get_work( WORK_RENDER ) ) ); - w->fifo_in = job->fifo_sync; - if( !job->indepth_scan ) - w->fifo_out = job->fifo_render; - else - w->fifo_out = NULL; - - if( !job->indepth_scan ) - { - - /* Video encoder */ - switch( job->vcodec ) - { - case HB_VCODEC_FFMPEG_MPEG4: - w = hb_get_work( WORK_ENCAVCODEC ); - w->codec_param = CODEC_ID_MPEG4; - break; - case HB_VCODEC_FFMPEG_MPEG2: - w = hb_get_work( WORK_ENCAVCODEC ); - w->codec_param = CODEC_ID_MPEG2VIDEO; - break; - case HB_VCODEC_X264: - w = hb_get_work( WORK_ENCX264 ); - break; - case HB_VCODEC_THEORA: - w = hb_get_work( WORK_ENCTHEORA ); - break; - } - w->fifo_in = job->fifo_render; - w->fifo_out = job->fifo_mpeg4; - w->config = &job->config; - - hb_list_add( job->list_work, w ); - } - /* * Look for the scanned subtitle in the existing subtitle list * select_subtitle implies that we did a scan. @@ -919,7 +887,6 @@ static void do_job( hb_job_t * job ) } } - for( i=0; i < hb_list_count(title->list_subtitle); i++ ) { subtitle = hb_list_item( title->list_subtitle, i ); @@ -996,8 +963,61 @@ static void do_job( hb_job_t * job ) } } + /* Set up the video filter fifo pipeline */ if( !job->indepth_scan ) { + if( job->list_filter ) + { + int filter_count = hb_list_count( job->list_filter ); + int i; + hb_fifo_t * fifo_in = job->fifo_sync; + + for( i = 0; i < filter_count; i++ ) + { + hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); + + filter->fifo_in = fifo_in; + filter->fifo_out = hb_fifo_init( FIFO_MINI, FIFO_MINI_WAKE ); + fifo_in = filter->fifo_out; + } + job->fifo_render = fifo_in; + } + else if ( !job->list_filter ) + { + hb_log("work: Internal Error: no filters"); + job->fifo_render = NULL; + } + + /* Video encoder */ + switch( job->vcodec ) + { + case HB_VCODEC_FFMPEG_MPEG4: + w = hb_get_work( WORK_ENCAVCODEC ); + w->codec_param = CODEC_ID_MPEG4; + break; + case HB_VCODEC_FFMPEG_MPEG2: + w = hb_get_work( WORK_ENCAVCODEC ); + w->codec_param = CODEC_ID_MPEG2VIDEO; + break; + case HB_VCODEC_X264: + w = hb_get_work( WORK_ENCX264 ); + break; + case HB_VCODEC_THEORA: + w = hb_get_work( WORK_ENCTHEORA ); + break; + } + // Handle case where there are no filters. + // This really should never happen. + if ( job->fifo_render ) + w->fifo_in = job->fifo_render; + else + w->fifo_in = job->fifo_sync; + + w->fifo_out = job->fifo_mpeg4; + w->config = &job->config; + + hb_list_add( job->list_work, w ); + for( i = 0; i < hb_list_count( title->list_audio ); i++ ) { audio = hb_list_item( title->list_audio, i ); @@ -1069,6 +1089,25 @@ static void do_job( hb_job_t * job ) job->done = 0; + if( job->list_filter && !job->indepth_scan ) + { + int filter_count = hb_list_count( job->list_filter ); + int i; + + for( i = 0; i < filter_count; i++ ) + { + hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); + + if( !filter ) continue; + + // Filters were initialized earlier, so we just need + // to start the filter's thread + filter->done = &job->done; + filter->thread = hb_thread_init( filter->name, filter_loop, filter, + HB_LOW_PRIORITY ); + } + } + /* Launch processing threads */ for( i = 0; i < hb_list_count( job->list_work ); i++ ) { @@ -1180,6 +1219,26 @@ cleanup: /* Stop the write thread (thread_close will block until the muxer finishes) */ job->done = 1; + // Close render filter pipeline + if( job->list_filter ) + { + int filter_count = hb_list_count( job->list_filter ); + int i; + + for( i = 0; i < filter_count; i++ ) + { + hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); + + if( !filter ) continue; + + if( filter->thread != NULL ) + { + hb_thread_close( &filter->thread ); + } + filter->close( filter ); + } + } + /* Close work objects */ while( ( w = hb_list_item( job->list_work, 0 ) ) ) { @@ -1315,14 +1374,14 @@ cleanup: } } - if( job->filters ) + if( job->list_filter ) { - for( i = 0; i < hb_list_count( job->filters ); i++ ) + for( i = 0; i < hb_list_count( job->list_filter ); i++ ) { - hb_filter_object_t * filter = hb_list_item( job->filters, i ); + hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); hb_filter_close( &filter ); } - hb_list_close( &job->filters ); + hb_list_close( &job->list_filter ); } hb_buffer_pool_free(); @@ -1331,6 +1390,21 @@ cleanup: free( job ); } +static inline void copy_chapter( hb_buffer_t * dst, hb_buffer_t * src ) +{ + // Propagate any chapter breaks for the worker if and only if the + // output frame has the same time stamp as the input frame (any + // worker that delays frames has to propagate the chapter marks itself + // and workers that move chapter marks to a different time should set + // 'src' to NULL so that this code won't generate spurious duplicates.) + if( src && dst && src->s.start == dst->s.start) + { + // restore log below to debug chapter mark propagation problems + //hb_log("work %s: Copying Chapter Break @ %"PRId64, w->name, src->s.start); + dst->s.new_chap = src->s.new_chap; + } +} + /** * Performs the work object's specific work function. * Loops calling work function for associated work object. Sleeps when fifo is full. @@ -1361,17 +1435,7 @@ static void work_loop( void * _w ) buf_out = NULL; w->status = w->work( w, &buf_in, &buf_out ); - // Propagate any chapter breaks for the worker if and only if the - // output frame has the same time stamp as the input frame (any - // worker that delays frames has to propagate the chapter marks itself - // and workers that move chapter marks to a different time should set - // 'buf_in' to NULL so that this code won't generate spurious duplicates.) - if( buf_in && buf_out && buf_in->new_chap && buf_in->start == buf_out->start) - { - // restore log below to debug chapter mark propagation problems - //hb_log("work %s: Copying Chapter Break @ %"PRId64, w->name, buf_in->start); - buf_out->new_chap = buf_in->new_chap; - } + copy_chapter( buf_out, buf_in ); if( buf_in ) { @@ -1408,3 +1472,78 @@ static void work_loop( void * _w ) hb_buffer_close( &buf_in ); } } + +/** + * Performs the filter object's specific work function. + * Loops calling work function for associated filter object. + * Sleeps when fifo is full. + * Monitors work done indicator. + * Exits loop when work indiactor is set. + * @param _w Handle to work object. + */ +static void filter_loop( void * _f ) +{ + hb_filter_object_t * f = _f; + hb_buffer_t * buf_in, * buf_out; + + while( !*f->done && f->status != HB_FILTER_DONE ) + { + buf_in = hb_fifo_get_wait( f->fifo_in ); + if ( buf_in == NULL ) + continue; + + // Filters can drop buffers. Remember chapter information + // so that it can be propagated to the next buffer + if ( buf_in->s.new_chap ) + { + f->chapter_time = buf_in->s.start; + f->chapter_val = buf_in->s.new_chap; + } + if ( *f->done ) + { + if( buf_in ) + { + hb_buffer_close( &buf_in ); + } + break; + } + + buf_out = NULL; + f->status = f->work( f, &buf_in, &buf_out ); + + if ( buf_out && f->chapter_val && f->chapter_time <= buf_out->s.start ) + { + buf_out->s.new_chap = f->chapter_val; + f->chapter_val = 0; + } + + if( buf_in ) + { + hb_buffer_close( &buf_in ); + } + if ( buf_out && f->fifo_out == NULL ) + { + hb_buffer_close( &buf_out ); + } + if( buf_out ) + { + while ( !*f->done ) + { + if ( hb_fifo_full_wait( f->fifo_out ) ) + { + hb_fifo_push( f->fifo_out, buf_out ); + break; + } + } + } + } + // Consume data in incoming fifo till job complete so that + // residual data does not stall the pipeline + while( !*f->done ) + { + buf_in = hb_fifo_get_wait( f->fifo_in ); + if ( buf_in != NULL ) + hb_buffer_close( &buf_in ); + } +} + |