From 33ab5ef7247bc4694d00b5e96f32ec1b1d4a6357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Wed, 31 Oct 2007 23:59:34 +0100 Subject: Improve PTS wrap detection for large A/V offsets. The last fix to PTS wrap detection could not handle streams with A/V offsets larger than 3 seconds. The improved version can deal with them now. --HG-- extra : transplant_source : %89%1F%7E%12%D5r%A8%CE%95N%BAG%96%02%60%0C%10%9Aar --- src/demuxers/demux_mpeg_pes.c | 112 +++++++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 40 deletions(-) diff --git a/src/demuxers/demux_mpeg_pes.c b/src/demuxers/demux_mpeg_pes.c index 9ef38f97d..705756dd6 100644 --- a/src/demuxers/demux_mpeg_pes.c +++ b/src/demuxers/demux_mpeg_pes.c @@ -136,13 +136,79 @@ static int32_t parse_IEC14496_FlexMux_stream(demux_mpeg_pes_t *this, uint8_t *p, static int32_t parse_program_stream_directory(demux_mpeg_pes_t *this, uint8_t *p, buf_element_t *buf); static int32_t parse_program_stream_pack_header(demux_mpeg_pes_t *this, uint8_t *p, buf_element_t *buf); -static void check_newpts( demux_mpeg_pes_t *this, int64_t pts, int video ) +static int detect_pts_discontinuity( demux_mpeg_pes_t *this, int64_t pts, int video ) { int64_t diff; - + + /* discontinuity detection is difficult to implement in the demuxer as it gets + * for example video packets in decoding order and there can be multiple audio + * and video tracks. So for simplicity, let's just deal with a single audio and + * a single video track. + * + * To start with, let's have a look at the audio and video track independently. + * Whenever pts differs from last_pts[video] by at least WRAP_THRESHOLD, a jump + * in pts is detected. Such a jump can happen for example when the pts counter + * overflows, as shown below (video decoding order ignored for simplicity; the + * variable values are shown after returning from the below function check_newpts; + * an asterisk means that this value has been cleared (see check_newpts)): + * + * pts: 7v 7a 8v 9v 9a : 0v 1v 1a 2v 3v 3a 4v + * last_pts[0]: 6 7 7 7 9 : * * 1 1 1 3 3 + * last_pts[1]: 7 7 8 9 9 : 0 1 1 2 3 3 4 + * | | | + * | | +--- audio pts wrap ignored + * | +--------- video pts wrap detected + * +----------- pts wrap boundary + */ diff = pts - this->last_pts[video]; - - if( pts && (this->send_newpts || (this->last_pts[video] && abs(diff)>WRAP_THRESHOLD) ) ) { + + if (this->last_pts[video] && abs(diff)>WRAP_THRESHOLD) + return 1; + + /* but the above code can cause a huge delay while replaying when audio and video + * track are not aligned on a common pts wrap boundery, as shown below: + * + * pts: 7v 8v 7a 9v : 0v 9a 1v 2v : 1a 3v 4v 3a + * last_pts[0]: 6 6 7 7 : * 9 9 9 : 1 1 1 3 + * last_pts[1]: 7 8 8 9 : 0 0 1 2 : * 3 4 4 + * | | | | | + * | | | | +--- audio pts wrap detected + * | | | +----- audio pts wrap boundary + * | | +-------------- audio packet causes a huge delay + * | +----------------- video pts wrap detected + * +------------------- video pts wrap boundery + * + * So there is the need to compare audio track pts against video track pts + * to detect when pts values are in between pts wrap bounderies, where a + * jump needs to be detected too, as shown below: + * + * pts: 7v 8v 7a 9v : 0v 9a 1v 2v : 1a 3v 4v 3a + * last_pts[0]: 6 6 7 7 : * 9 * * : 1 1 1 3 + * last_pts[1]: 7 8 8 9 : 0 * 1 2 : 2 3 4 4 + * | | | | | | + * | | | | | +--- (audio pts wrap ignored) + * | | | | +----- audio pts wrap boundary + * | | | +----------- video pts wrap detected + * | | +-------------- audio pts wrap detected + * | +----------------- video pts wrap detected + * +------------------- (video pts wrap boundery) + * + * Basically, it's almost the same test like above, but against the other track's + * pts value and with a different limit. As the pts counter is a 33 bit unsigned + * integer, we choose 2^31 as limit (2^32 would require the tracks to be aligned). + */ + diff = pts - this->last_pts[1-video]; + + if (this->last_pts[1-video] && abs(diff)>(1u<<31)) + return 1; + + /* no discontinuity detected */ + return 0; +} + +static void check_newpts( demux_mpeg_pes_t *this, int64_t pts, int video ) +{ + if( pts && (this->send_newpts || detect_pts_discontinuity(this, pts, video) ) ) { /* check if pts is outside nav pts range. any stream without nav must enter here. */ if( pts > this->nav_last_end_pts || pts < this->nav_last_start_pts ) @@ -159,47 +225,13 @@ static void check_newpts( demux_mpeg_pes_t *this, int64_t pts, int video ) } else { lprintf("no wrap detected\n" ); } - + + /* clear pts on the other track to avoid detecting the same discontinuity again */ this->last_pts[1-video] = 0; } if( pts ) - { - /* don't detect a discontinuity only for video respectively audio. It's also a discontinuity - indication when audio and video pts differ to much e. g. when a pts wrap happens. - The original code worked well when the wrap happend like this: - - V7 A7 V8 V9 A9 Dv V0 V1 da A1 V2 V3 A3 V4 - - Legend: - Vn = video packet with timestamp n - An = audio packet with timestamp n - Dv = discontinuity detected on following video packet - Da = discontinuity detected on following audio packet - dv = discontinuity detected on following video packet but ignored - da = discontinuity detected on following audio packet but ignored - - But with a certain delay between audio and video packets (e. g. the way DVB-S broadcasts - the packets) the code didn't work: - - V7 V8 A7 V9 Dv V0 _A9_ V1 V2 Da _A1_ V3 V4 A3 - - Packet A9 caused audio to jump forward and A1 caused it to jump backward with inserting - a delay of almoust 26.5 hours! - - The new code gives the following sequences for the above examples: - - V7 A7 V8 V9 A9 Dv V0 V1 A1 V2 V3 A3 V4 - - V7 V8 A7 V9 Dv V0 Da A9 Dv V1 V2 A1 V3 V4 A3 - - After proving this code it should be cleaned up to use just a single variable "last_pts". */ - -/* this->last_pts[video] = pts; -*/ - this->last_pts[video] = this->last_pts[1-video] = pts; - } } static off_t read_data(demux_mpeg_pes_t *this, uint8_t *buf, off_t nlen) -- cgit v1.2.3 From e2aafb0f72934a33a9318faf411c79b4cf170428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Thu, 1 Nov 2007 00:14:33 +0100 Subject: Improve interfacing FFmpeg especially for interlaced content. The changes include setting VO_INTERLACED_FLAG for interlaced frames, adjusting frame height to a multiple of 32 lines (i. e. the height of one macroblock per field) and cropping away the extra lines, setting progressive_frame and top_field_first as supplied by FFmpeg, fixing the workaround for demux_mpep_pes sending fields as frames and bad frame allocation (i. e. use at least 16x32 pixels). --HG-- extra : transplant_source : %C7c%951J%F5o%C5%F1%16%B4%05%95%D0J%C0j%7F%E6%F8 --- src/libffmpeg/ff_video_decoder.c | 45 +++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/libffmpeg/ff_video_decoder.c b/src/libffmpeg/ff_video_decoder.c index 83c3f2945..e81e963ab 100644 --- a/src/libffmpeg/ff_video_decoder.c +++ b/src/libffmpeg/ff_video_decoder.c @@ -1157,6 +1157,7 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { int offset = 0; int codec_type = buf->type & 0xFFFF0000; int video_step_to_use; + int frame_flags_to_use; /* pad input data */ /* note: bitstream, alt bitstream reader or something will cause @@ -1200,6 +1201,12 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { /* use externally provided video_step or fall back to stream's time_base otherwise */ video_step_to_use = (this->video_step || !this->context->time_base.den) ? this->video_step : (int)(90000ll * this->context->time_base.num / this->context->time_base.den); + frame_flags_to_use = this->frame_flags; + + /* adjust frame flags for interlaced frames when running in demux_mpeg_pes context */ + if (!this->video_step && this->av_frame->interlaced_frame) + frame_flags_to_use |= VO_INTERLACED_FLAG; + /* aspect ratio provided by ffmpeg, override previous setting */ if ((this->aspect_ratio_prio < 2) && av_cmp_q(this->context->sample_aspect_ratio, avr00)) { @@ -1246,10 +1253,10 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { img = this->stream->video_out->get_frame (this->stream->video_out, this->bih.biWidth, - this->bih.biHeight, + (this->bih.biHeight + 31) & ~31, this->aspect_ratio, this->output_format, - VO_BOTH_FIELDS|this->frame_flags); + VO_BOTH_FIELDS|frame_flags_to_use); free_img = 1; } else { /* DR1 */ @@ -1267,10 +1274,10 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { /* DR1 */ img = this->stream->video_out->get_frame (this->stream->video_out, img->width, - img->height, + (img->height + 31) & ~31, this->aspect_ratio, this->output_format, - VO_BOTH_FIELDS|this->frame_flags); + VO_BOTH_FIELDS|frame_flags_to_use); free_img = 1; } @@ -1289,10 +1296,6 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { img->pts = this->pts; this->pts = 0; - /* workaround for demux_mpeg_pes sending fields as frames */ - if (!this->video_step && this->av_frame->interlaced_frame) - video_step_to_use /= 2; - /* workaround for weird 120fps streams */ if( video_step_to_use == 750 ) { /* fallback to the VIDEO_PTS_MODE */ @@ -1305,8 +1308,14 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { img->duration = video_step_to_use; img->crop_right = this->crop_right; - img->crop_bottom = this->crop_bottom; - + img->crop_bottom = this->crop_bottom + (img->height - this->bih.biHeight); + + /* transfer some more frame settings when running in demux_mpeg_pes context */ + if (!this->video_step) { + img->progressive_frame = !this->av_frame->interlaced_frame; + img->top_field_first = this->av_frame->top_field_first; + } + this->skipframes = img->draw(img, this->stream); if(free_img) @@ -1314,19 +1323,27 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { } } - if (!got_one_picture) { + /* workaround for demux_mpeg_pes sending fields as frames: + * do not generate a bad frame for the first field picture + */ + if (!got_one_picture && (this->size || this->video_step || !this->av_frame->interlaced_frame)) { /* skipped frame, output a bad frame (of size 1x1 when size still uninitialized) */ img = this->stream->video_out->get_frame (this->stream->video_out, - (this->bih.biWidth <= 0) ? 1 : this->bih.biWidth, - (this->bih.biHeight <= 0) ? 1 : this->bih.biHeight, + (this->bih.biWidth <= 0) ? 16 : this->bih.biWidth, + (this->bih.biHeight <= 0) ? 32 : this->bih.biHeight, this->aspect_ratio, this->output_format, - VO_BOTH_FIELDS|this->frame_flags); + VO_BOTH_FIELDS|frame_flags_to_use); /* set PTS to allow early syncing */ img->pts = this->pts; this->pts = 0; img->duration = video_step_to_use; + /* transfer some more frame settings when running in demux_mpeg_pes context */ + if (!this->video_step) { + img->progressive_frame = !this->av_frame->interlaced_frame; + img->top_field_first = this->av_frame->top_field_first; + } img->bad_frame = 1; this->skipframes = img->draw(img, this->stream); img->free(img); -- cgit v1.2.3