diff options
author | van <[email protected]> | 2008-02-09 05:40:09 +0000 |
---|---|---|
committer | van <[email protected]> | 2008-02-09 05:40:09 +0000 |
commit | e22aa649e345c4d93e9a06ed56edbcd63f3f2720 (patch) | |
tree | 3b85bfe9dc33dc2cd24c12dc1385dd74d23d07e5 /libhb/stream.c | |
parent | 2ff79730d813e8c04a42c8e12b33c5ed73005d80 (diff) |
- More robust stream duration estimation: take 16 (position,pts) samples then do a max likelihood estimate of the average rate based on the rate between all pairs of samples.
- When we detect a sync error attempt to resync so that we can handle streams where a few bytes have been inserted or deleted.
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@1253 b64f7644-9d1e-0410-96f1-a4d463321fa5
Diffstat (limited to 'libhb/stream.c')
-rwxr-xr-x | libhb/stream.c | 128 |
1 files changed, 100 insertions, 28 deletions
diff --git a/libhb/stream.c b/libhb/stream.c index 109864f30..f3f75ce30 100755 --- a/libhb/stream.c +++ b/libhb/stream.c @@ -450,12 +450,37 @@ static uint64_t hb_ps_stream_getVideoPTS(hb_stream_t *stream) * hb_stream_duration *********************************************************************** * + * Finding stream duration is difficult. One issue is that the video file + * may have chunks from several different program fragments (main feature, + * commercials, station id, trailers, etc.) all with their own base pts + * value. We can't find the piece boundaries without reading the entire + * file but if we compute a rate based on time stamps from two different + * pieces the result will be meaningless. The second issue is that the + * data rate of compressed video normally varies by 5-10x over the length + * of the video. This says that we want to compute the rate over relatively + * long segments to get a representative average but long segments increase + * the likelihood that we'll cross a piece boundary. + * + * What we do is take time stamp samples at several places in the file + * (currently 16) then compute the average rate (i.e., ticks of video per + * byte of the file) for all pairs of samples (N^2 rates computed for N + * samples). Some of those rates will be absurd because the samples came + * from different segments. Some will be way low or high because the + * samples came from a low or high motion part of the segment. But given + * that we're comparing *all* pairs the majority of the computed rates + * should be near the overall average. So we median filter the computed + * rates to pick the most representative value. + * **********************************************************************/ struct pts_pos { uint64_t pos; /* file position of this PTS sample */ uint64_t pts; /* PTS from video stream */ }; +#define NDURSAMPLES 16 + +// get one (position, timestamp) sampple from a transport or program +// stream. static struct pts_pos hb_sample_pts(hb_stream_t *stream, uint64_t fpos) { struct pts_pos pp = { 0, 0 }; @@ -494,36 +519,74 @@ static struct pts_pos hb_sample_pts(hb_stream_t *stream, uint64_t fpos) return pp; } +static int dur_compare( const void *a, const void *b ) +{ + const double *aval = a, *bval = b; + return ( *aval < *bval ? -1 : ( *aval == *bval ? 0 : 1 ) ); +} + +// given an array of (position, time) samples, compute a max-likelihood +// estimate of the average rate by computing the rate between all pairs +// of samples then taking the median of those rates. +static double compute_stream_rate( struct pts_pos *pp, int n ) +{ + int i, j; + double rates[NDURSAMPLES * NDURSAMPLES / 2]; + double *rp = rates; + + // the following nested loops compute the rates between all pairs. + *rp = 0; + for ( i = 0; i < n-1; ++i ) + { + // Bias the median filter by not including pairs that are "far" + // frome one another. This is to handle cases where the file is + // made of roughly equal size pieces where a symmetric choice of + // pairs results in having the same number of intra-piece & + // inter-piece rate estimates. This would mean that the median + // could easily fall in the inter-piece part of the data which + // would give a bogus estimate. The 'ns' index creates an + // asymmetry that favors locality. + int ns = i + ( n >> 1 ); + if ( ns > n ) + ns = n; + for ( j = i+1; j < ns; ++j ) + { + if ( pp[j].pts != pp[i].pts && pp[j].pos > pp[i].pos ) + { + *rp = ((double)( pp[j].pts - pp[i].pts )) / + ((double)( pp[j].pos - pp[i].pos )); + ++rp; + } + } + } + // now compute and return the median of all the (n*n/2) rates we computed + // above. + int nrates = rp - rates; + qsort( rates, nrates, sizeof (rates[0] ), dur_compare ); + return rates[nrates >> 1]; +} + static void hb_stream_duration(hb_stream_t *stream, hb_title_t *inTitle) { - // To calculate the duration we get video presentation time stamps - // at a couple places in the file then use their difference scaled - // by the ratio of the distance between our measurement points & - // the size of the file. The issue is that our video file may have - // chunks from several different program fragments (main feature, - // commercials, station id, trailers, etc.) all with their own base - // pts value. We need to difference two pts's from the same program - // fragment. Since extraneous material is very likely at the beginning - // and end of the content, for now we take a time stamp from 25% - // into the file & 75% into the file then double their difference - // to get the total duration. - struct pts_pos first_pts, last_pts; + struct pts_pos ptspos[NDURSAMPLES]; + struct pts_pos *pp = ptspos; + int i; fseeko(stream->file_handle, 0, SEEK_END); uint64_t fsize = ftello(stream->file_handle); - first_pts = hb_sample_pts(stream, fsize >> 2); - last_pts = hb_sample_pts(stream, fsize - (fsize >> 2) ); - - if ( first_pts.pos && last_pts.pos && first_pts.pos != last_pts.pos ) + uint64_t fincr = fsize / NDURSAMPLES; + uint64_t fpos = fincr / 2; + for ( i = NDURSAMPLES; --i >= 0; fpos += fincr ) { - uint64_t dur = ( ( last_pts.pts - first_pts.pts ) * fsize ) / - ( last_pts.pos - first_pts.pos ); - inTitle->duration = dur; - dur /= 90000; - inTitle->hours = dur / 3600; - inTitle->minutes = ( dur % 3600 ) / 60; - inTitle->seconds = dur % 60; + *pp++ = hb_sample_pts(stream, fpos); } + uint64_t dur = compute_stream_rate( ptspos, pp - ptspos ) * (double)fsize; + inTitle->duration = dur; + dur /= 90000; + inTitle->hours = dur / 3600; + inTitle->minutes = ( dur % 3600 ) / 60; + inTitle->seconds = dur % 60; + rewind(stream->file_handle); } @@ -1888,7 +1951,6 @@ static void hb_ts_stream_decode(hb_stream_t *stream) int i = 0; for (i=0; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) { -// stream->ts_skipbad[kAudioStream] = stream->ts_skipbad[kVideoStream] = 0; stream->ts_skipbad[i] = 0; } @@ -1911,17 +1973,27 @@ static void hb_ts_stream_decode(hb_stream_t *stream) // so just discard the remainder (the partial buffer is useless) hb_log("hb_ts_stream_decode - eof"); stream->ps_decode_buffer[stream->ps_current_write_buffer_index].len = 0; - return; + return; } // Check sync byte if ((buf[0] != 0x47) && (buf[0] != 0x72) && (buf[0] != 0x29)) { - off_t pos = ftello(stream->file_handle); - hb_log("hb_ts_stream_decode - no sync byte 0x47 @ %lld", pos); + // lost sync - back up to where we started then try to + // re-establish sync. + off_t pos = ftello(stream->file_handle) - 188; + off_t pos2 = align_to_next_packet(stream->file_handle); + if ( pos2 == 0 ) + { + hb_log( "hb_ts_stream_decode: eof while re-establishing sync @ %lld", + pos ); + stream->ps_decode_buffer[stream->ps_current_write_buffer_index].len = 0; + return; + } + hb_log("hb_ts_stream_decode: sync lost @%lld, regained after %lld bytes", + pos, pos2 ); for (i=0; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) { - // stream->ts_skipbad[kAudioStream] = stream->ts_skipbad[kVideoStream] = 1; stream->ts_skipbad[i] = 1; } continue; |