diff options
-rw-r--r-- | libhb/decssasub.c | 6 | ||||
-rw-r--r-- | libhb/fifo.c | 35 | ||||
-rw-r--r-- | libhb/internal.h | 2 | ||||
-rw-r--r-- | libhb/render.c | 63 | ||||
-rw-r--r-- | libhb/sync.c | 294 |
5 files changed, 366 insertions, 34 deletions
diff --git a/libhb/decssasub.c b/libhb/decssasub.c index 8d0a9995d..b0ed845d5 100644 --- a/libhb/decssasub.c +++ b/libhb/decssasub.c @@ -50,6 +50,8 @@ typedef enum { sec * 1000L +\ centi * 10L ) ) +#define SSA_VERBOSE_PACKETS 0 + static StyleSet ssa_parse_style_override( uint8_t *pos, StyleSet prevStyles ) { StyleSet nextStyles = prevStyles; @@ -592,6 +594,10 @@ static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_buffer_t * in = *buf_in; hb_buffer_t * out_list = NULL; +#if SSA_VERBOSE_PACKETS + printf("\nPACKET(%"PRId64",%"PRId64"): %.*s\n", in->start/90, in->stop/90, in->size, in->data); +#endif + if ( in->size > 0 ) { out_list = ssa_decode_packet(w, in); } else { diff --git a/libhb/fifo.c b/libhb/fifo.c index 755a6d164..cd348c863 100644 --- a/libhb/fifo.c +++ b/libhb/fifo.c @@ -190,6 +190,7 @@ void hb_buffer_realloc( hb_buffer_t * b, int size ) } } +// Frees the specified buffer list. void hb_buffer_close( hb_buffer_t ** _b ) { hb_buffer_t * b = *_b; @@ -294,6 +295,8 @@ float hb_fifo_percent_full( hb_fifo_t * f ) return ret; } +// Pulls the first packet out of this FIFO, blocking until such a packet is available. +// Returns NULL if this FIFO has been closed or flushed. hb_buffer_t * hb_fifo_get_wait( hb_fifo_t * f ) { hb_buffer_t * b; @@ -323,6 +326,7 @@ hb_buffer_t * hb_fifo_get_wait( hb_fifo_t * f ) return b; } +// Pulls a packet out of this FIFO, or returns NULL if no packet is available. hb_buffer_t * hb_fifo_get( hb_fifo_t * f ) { hb_buffer_t * b; @@ -368,6 +372,8 @@ hb_buffer_t * hb_fifo_see_wait( hb_fifo_t * f ) return b; } +// Returns the first packet in the specified FIFO. +// If the FIFO is empty, returns NULL. hb_buffer_t * hb_fifo_see( hb_fifo_t * f ) { hb_buffer_t * b; @@ -400,6 +406,8 @@ hb_buffer_t * hb_fifo_see2( hb_fifo_t * f ) return b; } +// Waits until the specified FIFO is no longer full or until FIFO_TIMEOUT milliseconds have elapsed. +// Returns whether the FIFO is non-full upon return. int hb_fifo_full_wait( hb_fifo_t * f ) { int result; @@ -415,6 +423,8 @@ int hb_fifo_full_wait( hb_fifo_t * f ) return result; } +// Pushes the specified buffer onto the specified FIFO, +// blocking until the FIFO has space available. void hb_fifo_push_wait( hb_fifo_t * f, hb_buffer_t * b ) { if( !b ) @@ -451,6 +461,7 @@ void hb_fifo_push_wait( hb_fifo_t * f, hb_buffer_t * b ) hb_unlock( f->lock ); } +// Appends the specified packet list to the end of the specified FIFO. void hb_fifo_push( hb_fifo_t * f, hb_buffer_t * b ) { if( !b ) @@ -482,6 +493,7 @@ void hb_fifo_push( hb_fifo_t * f, hb_buffer_t * b ) hb_unlock( f->lock ); } +// Prepends the specified packet list to the start of the specified FIFO. void hb_fifo_push_head( hb_fifo_t * f, hb_buffer_t * b ) { hb_buffer_t * tmp; @@ -519,6 +531,29 @@ void hb_fifo_push_head( hb_fifo_t * f, hb_buffer_t * b ) hb_unlock( f->lock ); } +// Pushes a list of packets onto the specified FIFO as a single element. +void hb_fifo_push_list_element( hb_fifo_t *fifo, hb_buffer_t *buffer_list ) +{ + hb_buffer_t *container = hb_buffer_init( 0 ); + // XXX: Using an arbitrary hb_buffer_t pointer (other than 'next') + // to carry the list inside a single "container" buffer + container->next_subpicture = buffer_list; + + hb_fifo_push( fifo, container ); +} + +// Removes a list of packets from the specified FIFO that were stored as a single element. +hb_buffer_t *hb_fifo_get_list_element( hb_fifo_t *fifo ) +{ + hb_buffer_t *container = hb_fifo_get( fifo ); + // XXX: Using an arbitrary hb_buffer_t pointer (other than 'next') + // to carry the list inside a single "container" buffer + hb_buffer_t *buffer_list = container->next_subpicture; + hb_buffer_close( &container ); + + return buffer_list; +} + void hb_fifo_close( hb_fifo_t ** _f ) { hb_fifo_t * f = *_f; diff --git a/libhb/internal.h b/libhb/internal.h index 335c9a854..7896bee16 100644 --- a/libhb/internal.h +++ b/libhb/internal.h @@ -126,6 +126,8 @@ void hb_fifo_push( hb_fifo_t *, hb_buffer_t * ); void hb_fifo_push_wait( hb_fifo_t *, hb_buffer_t * ); int hb_fifo_full_wait( hb_fifo_t * f ); void hb_fifo_push_head( hb_fifo_t *, hb_buffer_t * ); +void hb_fifo_push_list_element( hb_fifo_t *fifo, hb_buffer_t *buffer_list ); +hb_buffer_t * hb_fifo_get_list_element( hb_fifo_t *fifo ); void hb_fifo_close( hb_fifo_t ** ); void hb_fifo_flush( hb_fifo_t * f ); diff --git a/libhb/render.c b/libhb/render.c index 8c7e9a2d8..c16b9403d 100644 --- a/libhb/render.c +++ b/libhb/render.c @@ -67,6 +67,8 @@ static uint8_t *getV(uint8_t *data, int width, int height, int x, int y) return(&data[(y>>1) * w2 + (x>>1) + width*height + w2*h2]); } +// Draws the specified PICTURESUB subtitle on the specified video packet. +// Disposes the subtitle afterwards. static void ApplySub( hb_job_t * job, hb_buffer_t * buf, hb_buffer_t ** _sub ) { @@ -397,11 +399,11 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in, /* Push subtitles onto queue just in case we need to delay a frame */ if( in->sub ) { - hb_fifo_push( pv->subtitle_queue, in->sub ); + hb_fifo_push_list_element( pv->subtitle_queue, in->sub ); } else { - hb_fifo_push( pv->subtitle_queue, hb_buffer_init(0) ); + hb_fifo_push_list_element( pv->subtitle_queue, NULL ); } /* If there's a chapter mark remember it in case we delay or drop its frame */ @@ -451,11 +453,15 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in, } else if( result == FILTER_DELAY ) { + // Process the current frame later + buf_tmp_in = NULL; break; } else if( result == FILTER_DROP ) { + // Drop the current frame + /* We need to compensate for the time lost by dropping this frame. Spread its duration out in quarters, because usually dropped frames maintain a 1-out-of-5 pattern and this spreads it out amongst the remaining ones. @@ -471,15 +477,28 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in, pv->total_lost_time += temp_duration; pv->dropped_frames++; - /* Pop the frame's subtitle and dispose of it. */ - hb_buffer_t * subpicture_list = hb_fifo_get( pv->subtitle_queue ); - hb_buffer_t * subpicture; - hb_buffer_t * subpicture_next; - for ( subpicture = subpicture_list; subpicture; subpicture = subpicture_next ) + /* Pop the frame's subtitle list and dispose of it. */ + hb_buffer_t * sub_list = hb_fifo_get_list_element( pv->subtitle_queue ); + hb_buffer_t * sub; + hb_buffer_t * sub_next; + for ( sub = sub_list; sub; sub = sub_next ) { - subpicture_next = subpicture->next_subpicture; - hb_buffer_close( &subpicture ); + sub_next = sub->next; + // XXX: Prevent hb_buffer_close from killing the whole list + // before we finish iterating over it + sub->next = NULL; + + hb_buffer_t * subpicture_list = sub; + hb_buffer_t * subpicture; + hb_buffer_t * subpicture_next; + for ( subpicture = subpicture_list; subpicture; subpicture = subpicture_next ) + { + subpicture_next = subpicture->next_subpicture; + + hb_buffer_close( &subpicture ); + } } + buf_tmp_in = NULL; break; } @@ -503,16 +522,28 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in, pv->last_stop[0] = pv->last_start[0] + (buf_tmp_in->stop - buf_tmp_in->start); } - /* Apply subtitles */ + /* Apply subtitles and dispose them */ if( buf_tmp_in ) { - hb_buffer_t * subpicture_list = hb_fifo_get( pv->subtitle_queue ); - hb_buffer_t * subpicture; - hb_buffer_t * subpicture_next; - for ( subpicture = subpicture_list; subpicture; subpicture = subpicture_next ) + hb_buffer_t * sub_list = hb_fifo_get_list_element( pv->subtitle_queue ); + hb_buffer_t * sub; + hb_buffer_t * sub_next; + for ( sub = sub_list; sub; sub = sub_next ) { - subpicture_next = subpicture->next_subpicture; - ApplySub( job, buf_tmp_in, &subpicture ); + sub_next = sub->next; + // XXX: Prevent hb_buffer_close inside ApplySub from killing the whole list + // before we finish iterating over it + sub->next = NULL; + + hb_buffer_t * subpicture_list = sub; + hb_buffer_t * subpicture; + hb_buffer_t * subpicture_next; + for ( subpicture = subpicture_list; subpicture; subpicture = subpicture_next ) + { + subpicture_next = subpicture->next_subpicture; + + ApplySub( job, buf_tmp_in, &subpicture ); + } } } diff --git a/libhb/sync.c b/libhb/sync.c index ad26fb656..9a532a94b 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -68,6 +68,9 @@ typedef struct uint64_t st_counts[4]; uint64_t st_dates[4]; uint64_t st_first; + + /* Subtitles */ + hb_buffer_t * sub_list; /* list of subtitles to be passed thru or rendered */ } hb_sync_video_t; struct hb_work_private_s @@ -549,11 +552,204 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, } /* - * Track the video sequence number localy so that we can sync the audio + * Track the video sequence number locally so that we can sync the audio * to it using the sequence number as well as the PTS. */ sync->video_sequence = cur->sequence; + + /* Process subtitles that apply to this video frame */ + + // NOTE: There is no logic in either subtitle-sync algorithm that waits for the + // subtitle-decoder if it is lagging behind the video-decoder. + // + // Therefore there is the implicit assumption that the subtitle-decoder + // is always faster than the video-decoder. This assumption is definitely + // incorrect in some cases where the SSA subtitle decoder is used. + // Enable the SUBSYNC_VERBOSE_TIMING flag below to debug. + +#define SUBSYNC_ALGORITHM_SIMULTANEOUS 1 +#define SUBSYNC_ALGORITHM_CLASSIC 0 + +/* + * Enables logging of three kinds of events: + * SUB***: Subtitle received by sync object + * SUB+++: Subtitle now shown + * SUB---: Subtitle now hidden and disposed + * + * Lead times on SUB*** events should be positive. + * Negative lead times lead to lag times on SUB+++ or the complete drop of a subtitle. + * Lag times on SUB+++ and SUB--- should be small positive values in the 0-40ms range. + */ +#define SUBSYNC_VERBOSE_TIMING 0 + +#if SUBSYNC_ALGORITHM_SIMULTANEOUS + #define sub_list sync->sub_list + /* + * 1. Find all subtitles that need to be burned into the current video frame + * and attach them to the frame. + * 2. Find all subtitles that need to be passed thru and do so immediately. + */ + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) + { + subtitle = hb_list_item( job->list_subtitle, i ); + + // If this subtitle track's packets are to be passed thru, do so immediately + if( subtitle->config.dest == PASSTHRUSUB ) + { + while ( ( sub = hb_fifo_get( subtitle->fifo_raw ) ) != NULL ) + { + if ( subtitle->source == VOBSUB ) + { + hb_fifo_push( subtitle->fifo_sync, sub ); + } + else + { + hb_fifo_push( subtitle->fifo_out, sub ); + } + } + } + // If this subtitle track's packets are to be rendered, identify the + // packets that need to be rendered on the current video frame + else if( subtitle->config.dest == RENDERSUB ) + { + // Migrate subtitles from 'subtitle->fifo_raw' to 'sub_list' immediately. + // Note that the size of 'sub_list' is unbounded. + while ( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) != NULL ) + { + sub = hb_fifo_get( subtitle->fifo_raw ); // pop + + #if SUBSYNC_VERBOSE_TIMING + printf( "\nSUB*** (%"PRId64"/%"PRId64":%"PRId64") @ %"PRId64"/%"PRId64":%"PRId64" (lead by %"PRId64"ms)\n", + sub->start/90, sub->start/90/1000/60, sub->start/90/1000%60, + cur->start/90, cur->start/90/1000/60, cur->start/90/1000%60, + (sub->start - cur->start)/90); + if (pv->common->video_pts_slip) + { + printf( " VIDEO-LAG: %"PRId64"\n", pv->common->video_pts_slip ); + } + #endif + + // Prepend to sub_list + hb_buffer_t *sub_list_next = sub_list; + sub_list = sub; + sub_list->next = sub_list_next; + } + + hb_buffer_t *last_sub = NULL; + for ( sub = sub_list; sub != NULL; ) + { + // NOTE: Strictly speaking this sequence check is probably unnecessary. + // It is a holdover behavior inherited from the classic subsync algorithm. + if ( sub->sequence > cur->sequence ) + { + // Subtitle sequence in the future + + // (Keep the subtitle in the stream) + last_sub = sub; + sub = sub->next; + continue; + } + + if ( cur->start < sub->start ) + { + // Subtitle starts in the future + + // (Keep the subtitle in the stream) + last_sub = sub; + sub = sub->next; + continue; + } + else + { + // Subtitle starts in the past... + + if ( cur->start < sub->stop ) + { + // Subtitle starts in the past and finishes in the future + + // Attach a copy of the subtitle packet to the current video packet + // to be burned in by the 'render' work-object. + // (Can't just alias it because we don't know when the 'render' + // work-object will dispose of it.) + hb_buffer_t * old_sublist_head = cur->sub; + cur->sub = copy_subtitle( sub ); + cur->sub->next = old_sublist_head; + + #if SUBSYNC_VERBOSE_TIMING + if (!(sub->new_chap & 0x01)) + { + printf( "\nSUB+++ (%"PRId64"/%"PRId64":%"PRId64") @ %"PRId64"/%"PRId64":%"PRId64" (lag by %"PRId64"ms)\n", + sub->start/90, sub->start/90/1000/60, sub->start/90/1000%60, + cur->start/90, cur->start/90/1000/60, cur->start/90/1000%60, + (cur->start - sub->start)/90 ); + if (pv->common->video_pts_slip) + { + printf( " VIDEO-LAG: %"PRId64"\n", pv->common->video_pts_slip ); + } + + sub->new_chap |= 0x01; + } + #endif + + // (Keep the subtitle in the stream) + last_sub = sub; + sub = sub->next; + continue; + } + else + { + // Subtitle starts in the past and has already finished + + #if SUBSYNC_VERBOSE_TIMING + printf( "\nSUB--- (%"PRId64"/%"PRId64":%"PRId64") @ %"PRId64"/%"PRId64":%"PRId64" (lag by %"PRId64"ms)\n", + sub->start/90, sub->start/90/1000/60, sub->start/90/1000%60, + cur->start/90, cur->start/90/1000/60, cur->start/90/1000%60, + (cur->start - sub->stop)/90 ); + if (pv->common->video_pts_slip) + { + printf( " VIDEO-LAG: %"PRId64"\n", pv->common->video_pts_slip ); + } + #endif + + // Remove it from the stream... + if (last_sub != NULL) + { + last_sub->next = sub->next; + } + if (sub_list == sub) + { + sub_list = sub->next; + } + + // ...and trash it + hb_buffer_t *next_sub = sub->next; + // XXX: Prevent hb_buffer_close from killing the whole list + // before we finish iterating over it + sub->next = NULL; + + hb_buffer_t * subpicture_list = sub; + hb_buffer_t * subpicture; + hb_buffer_t * subpicture_next; + for ( subpicture = subpicture_list; subpicture; subpicture = subpicture_next ) + { + subpicture_next = subpicture->next_subpicture; + + hb_buffer_close( &subpicture ); + } + + // (last_sub remains the same) + sub = next_sub; + continue; + } + } + } + } + } // end subtitles + #undef sub_list +#elif SUBSYNC_ALGORITHM_CLASSIC + // NOTE: This algorithm does not correctly support the simultaneous display of temporally overlapping subtitles. + /* * Look for a subtitle for this frame. * @@ -651,13 +847,19 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, duration = sub->stop - sub->start; sub_stop = sub_start + duration; - /* If two subtitles overlap, make the first one stop + /* If two DVD subtitles overlap, make the first one stop when the second one starts */ - sub2 = hb_fifo_see2( subtitle->fifo_raw ); - if( sub2 && sub->stop > sub2->start ) + // TODO: Consider removing this entirely. Currently retained + // to preserve old DVD subtitle behavior. + if ( subtitle->source == VOBSUB ) { - sub->stop = sub2->start; + sub2 = hb_fifo_see2( subtitle->fifo_raw ); + if( sub2 && sub->stop > sub2->start ) + { + sub->stop = sub2->start; + } } + // hb_log("0x%x: video seq: %"PRId64" subtitle sequence: %"PRId64, // sub, cur->sequence, sub->sequence); @@ -674,8 +876,22 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, break; } + #if SUBSYNC_VERBOSE_TIMING + if (!(sub->new_chap & 0x02)) + { + printf( "\nSUB*** (%"PRId64"/%"PRId64":%"PRId64") @ %"PRId64"/%"PRId64":%"PRId64" (lead by %"PRId64"ms)\n", + sub->start/90, sub->start/90/1000/60, sub->start/90/1000%60, + cur->start/90, cur->start/90/1000/60, cur->start/90/1000%60, + (sub->start - cur->start)/90); + + sub->new_chap |= 0x02; + } + #endif + if( sub_stop > start ) { + // CONDITION: cur->start < sub->stop + /* * The stop time is in the future, so fall through * and we'll deal with it in the next block of @@ -687,6 +903,8 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, */ if( sub_stop > sub_start) { + // CONDITION: {cur->start, sub->start} < sub->stop + /* * Normal subtitle which ends after it starts, * check to see that the current video is between @@ -695,6 +913,8 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, if( start > sub_start && start < sub_stop ) { + // CONDITION: sub->start < cur->start < sub->stop + /* * We should be playing this, so leave the * subtitle in place. @@ -704,6 +924,8 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, } else { + // CONDITION: cur->start < sub->start < sub->stop + /* * Defer until the play point is within * the subtitle @@ -713,12 +935,16 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, } else { + // CONDITION: cur->start < sub->stop < sub->start + /* * The end of the subtitle is less than the start, * this is a sign of a PTS discontinuity. */ if( sub_start > start ) { + // CONDITION: cur->start < sub->stop < sub->start + /* * we haven't reached the start time yet, or * we have jumped backwards after having @@ -726,6 +952,8 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, */ if( start < sub_stop ) { + // CONDITION: cur->start < sub->stop < sub->start + /* * We have jumped backwards and so should * continue displaying this subtitle. @@ -735,6 +963,8 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, } else { + // CONDITION: Mathematically impossible to get here + /* * Defer until the play point is * within the subtitle @@ -742,6 +972,8 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, sub = NULL; } } else { + // CONDITION: Mathematically impossible to get here + /* * Play this subtitle as the start is * greater than our video point. @@ -754,6 +986,14 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, } else { + // CONDITION: sub->stop < cur->start + + #if SUBSYNC_VERBOSE_TIMING + printf( "\nSUB--- (%"PRId64"/%"PRId64":%"PRId64") @ %"PRId64"/%"PRId64":%"PRId64" (lag by %"PRId64"ms)\n", + sub->start/90, sub->start/90/1000/60, sub->start/90/1000%60, + cur->start/90, cur->start/90/1000/60, cur->start/90/1000%60, + (cur->start - sub->stop)/90 ); + #endif /* * The subtitle is older than this picture, trash it @@ -766,24 +1006,39 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, /* If we have a subtitle for this picture, copy it */ if( sub ) { + #if SUBSYNC_VERBOSE_TIMING + if (!(sub->new_chap & 0x01)) + { + printf( "\nSUB+++ (%"PRId64"/%"PRId64":%"PRId64") @ %"PRId64"/%"PRId64":%"PRId64" (lag by %"PRId64"ms)\n", + sub->start/90, sub->start/90/1000/60, sub->start/90/1000%60, + cur->start/90, cur->start/90/1000/60, cur->start/90/1000%60, + (cur->start - sub->start)/90 ); + + sub->new_chap |= 0x01; + } + #endif + if( sub->size > 0 ) { if( subtitle->config.dest == RENDERSUB ) { - // Only allow one subtitle to be showing at once; ignore others - if ( cur->sub == NULL ) - { - /* - * Tack onto the video buffer for rendering - */ - /* FIXME: we should avoid this memcpy */ - cur->sub = copy_subtitle( sub ); - cur->sub->start = sub_start; - cur->sub->stop = sub_stop; + /* + * Tack onto the video buffer for rendering. + * + * Note that there may be multiple subtitles + * whose time intervals overlap which must display + * on the same frame. + */ + hb_buffer_t * old_sublist_head = cur->sub; + + /* FIXME: we should avoid this memcpy */ + cur->sub = copy_subtitle( sub ); + cur->sub->next = old_sublist_head; + cur->sub->start = sub_start; + cur->sub->stop = sub_stop; - // Leave the subtitle on the raw queue - // (until it no longer needs to be displayed) - } + // Leave the subtitle on the raw queue + // (until it no longer needs to be displayed) } else { /* * Pass-Through, pop it off of the raw queue, @@ -811,6 +1066,9 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, } } } // end subtitles +#else + #error "Must select a subtitle sync algorithm." +#endif /* * Adjust the pts of the current frame so that it's contiguous |