diff options
author | Torsten Jager <t.jager@gmx.de> | 2013-11-28 18:50:20 +0100 |
---|---|---|
committer | Torsten Jager <t.jager@gmx.de> | 2013-11-28 18:50:20 +0100 |
commit | 4e08e3cb4082666ec01504d5a19f5ab8e51a6a6c (patch) | |
tree | 88dd1727201130fc29650048c35493cde387efca | |
parent | b5539e68ad9a8fc8980fe2d5068cc05939c0091c (diff) | |
download | xine-lib-4e08e3cb4082666ec01504d5a19f5ab8e51a6a6c.tar.gz xine-lib-4e08e3cb4082666ec01504d5a19f5ab8e51a6a6c.tar.bz2 |
ff_video_decoder: attempt to really fix the orphaned DR1 frames issue.
-rw-r--r-- | src/combined/ffmpeg/ff_video_decoder.c | 244 |
1 files changed, 223 insertions, 21 deletions
diff --git a/src/combined/ffmpeg/ff_video_decoder.c b/src/combined/ffmpeg/ff_video_decoder.c index b5f7631f8..d4c26820b 100644 --- a/src/combined/ffmpeg/ff_video_decoder.c +++ b/src/combined/ffmpeg/ff_video_decoder.c @@ -163,6 +163,15 @@ struct ff_video_decoder_s { vaapi_accel_t *accel; vo_frame_t *accel_img; #endif + + /* Ugly: 2nd guess the reason for flush. + ff_flush () should really have an extra argument telling this. */ +#define STATE_RESET 0 +#define STATE_DISCONTINUITY 1 +#define STATE_READING_DATA 2 +#define STATE_FRAME_SENT 3 +#define STATE_FLUSHED 4 + int state; }; /* import color matrix names */ @@ -1671,6 +1680,7 @@ static void ff_handle_mpeg12_buffer (ff_video_decoder_t *this, buf_element_t *bu #endif /* ENABLE_VAAPi */ this->skipframes = img->draw(img, this->stream); + this->state = STATE_FRAME_SENT; if(free_img) img->free(img); @@ -2028,6 +2038,7 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { #endif /* ENABLE_VAAPI */ this->skipframes = img->draw(img, this->stream); + this->state = STATE_FRAME_SENT; if(free_img) img->free(img); @@ -2058,6 +2069,7 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { img->bad_frame = 1; this->skipframes = img->draw(img, this->stream); img->free(img); + this->state = STATE_FRAME_SENT; } this->assume_bad_field_picture = !got_one_picture; @@ -2109,6 +2121,7 @@ static void ff_decode_data (video_decoder_t *this_gen, buf_element_t *buf) { if (buf->pts && (buf->pts != this->last_pts)) this->last_pts = this->pts = buf->pts; + this->state = STATE_READING_DATA; if ((buf->type & 0xFFFF0000) == BUF_VIDEO_MPEG) { ff_handle_mpeg12_buffer(this, buf); } else { @@ -2119,8 +2132,208 @@ static void ff_decode_data (video_decoder_t *this_gen, buf_element_t *buf) { } } +static void ff_flush_internal (ff_video_decoder_t *this, int display) { + vo_frame_t *img; + int free_img, got_picture, len, frames = 0; + int video_step_to_use = this->video_step; + AVRational avr00 = {0, 1}; + + /* This is a stripped version of ff_handle_buffer (). It shall return yet undisplayed frames. */ + if (!this->context || !this->decoder_ok || this->state == STATE_FLUSHED) + return; + this->state = STATE_FLUSHED; + + while (1) { +#if AVVIDEO > 1 + AVPacket avpkt; + av_init_packet (&avpkt); + avpkt.data = NULL; + avpkt.size = 0; + got_picture = 0; +# if ENABLE_VAAPI + if (this->accel) + len = this->accel->avcodec_decode_video2 (this->accel_img, this->context, + this->av_frame, &got_picture, &avpkt); + else +# endif + len = avcodec_decode_video2 (this->context, this->av_frame, &got_picture, &avpkt); +#else + got_picture = 0; +# if ENABLE_VAAPI + if (this->accel) + len = this->accel->avcodec_decode_video (this->accel_img, this->context, + this->av_frame, &got_picture, NULL, 0); + else +# endif + len = avcodec_decode_video (this->context, this->av_frame, &got_picture, NULL, 0); +#endif + if (len < 0 || !got_picture || !this->av_frame->data[0]) + break; + + frames++; + if (!display) + continue; + + /* All that jizz just to view the last 2 frames of a stream ;-) */ + video_step_to_use = this->video_step || !this->context->time_base.den ? + this->video_step : + 90000ll * this->context->ticks_per_frame * this->context->time_base.num / + this->context->time_base.den; + + if ((this->aspect_ratio_prio < 2) && av_cmp_q (this->context->sample_aspect_ratio, avr00)) { + if (!this->bih.biWidth || !this->bih.biHeight) { + this->bih.biWidth = this->context->width; + this->bih.biHeight = this->context->height; + } + this->aspect_ratio = av_q2d(this->context->sample_aspect_ratio) * + (double)this->bih.biWidth / (double)this->bih.biHeight; + this->aspect_ratio_prio = 2; + set_stream_info (this); + } + + if (this->set_stream_info) { + set_stream_info (this); + this->set_stream_info = 0; + } + + if (!this->av_frame->opaque) { + /* indirect rendering */ + if (this->aspect_ratio_prio == 0) { + this->aspect_ratio = (double)this->bih.biWidth / (double)this->bih.biHeight; + this->aspect_ratio_prio = 1; + lprintf ("default aspect ratio: %f\n", this->aspect_ratio); + set_stream_info (this); + } + /* xine-lib expects the framesize to be a multiple of 16x16 (macroblock) */ + img = this->stream->video_out->get_frame (this->stream->video_out, + (this->bih.biWidth + 15) & ~15, (this->bih.biHeight + 15) & ~15, + this->aspect_ratio, this->output_format, VO_BOTH_FIELDS | this->frame_flags); + img->crop_right = img->width - this->bih.biWidth; + img->crop_bottom = img->height - this->bih.biHeight; + free_img = 1; + } else { + /* DR1 */ + img = (vo_frame_t*)this->av_frame->opaque; + free_img = 0; + } + + /* post processing */ + if (this->pp_quality != this->class->pp_quality && this->context->pix_fmt != PIX_FMT_VAAPI_VLD) + pp_change_quality (this); + if (this->pp_available && this->pp_quality && this->context->pix_fmt != PIX_FMT_VAAPI_VLD) { + if (this->av_frame->opaque) { + /* DR1: filter into a new frame. Same size to avoid reallcation, just move the + image to top left corner. */ + img = this->stream->video_out->get_frame (this->stream->video_out, img->width, img->height, + this->aspect_ratio, this->output_format, VO_BOTH_FIELDS | this->frame_flags); + img->crop_right = img->width - this->bih.biWidth; + img->crop_bottom = img->height - this->bih.biHeight; + free_img = 1; + } +#ifdef AV_BUFFER + { + int qstride, qtype; + int8_t *qtable = av_frame_get_qp_table (this->av_frame, &qstride, &qtype); + + pp_postprocess ((const uint8_t **)this->av_frame->data, this->av_frame->linesize, + img->base, img->pitches, this->bih.biWidth, this->bih.biHeight, + qtable, qstride, this->our_mode, this->our_context, + this->av_frame->pict_type | (qtype ? PP_PICT_TYPE_QP2 : 0)); + } +#else + pp_postprocess ((const uint8_t **)this->av_frame->data, this->av_frame->linesize, + img->base, img->pitches, this->bih.biWidth, this->bih.biHeight, + this->av_frame->qscale_table, this->av_frame->qstride, + this->our_mode, this->our_context, this->av_frame->pict_type); +#endif + } else if (!this->av_frame->opaque) { + /* colorspace conversion or copy */ + if (this->context->pix_fmt != PIX_FMT_VAAPI_VLD) + ff_convert_frame (this, img, this->av_frame); + } + + img->pts = ff_untag_pts (this, this->av_frame->reordered_opaque); + ff_check_pts_tagging (this, this->av_frame->reordered_opaque); + + if (video_step_to_use == 750) + video_step_to_use = 0; + img->duration = this->av_frame->repeat_pict ? video_step_to_use * 3 / 2 : video_step_to_use; + img->progressive_frame = !this->av_frame->interlaced_frame; + img->top_field_first = this->av_frame->top_field_first; + +#ifdef ENABLE_VAAPI + if (this->context->pix_fmt == PIX_FMT_VAAPI_VLD) { + if (this->accel->guarded_render(this->accel_img)) { + ff_vaapi_surface_t *va_surface = (ff_vaapi_surface_t *)this->av_frame->data[0]; + this->accel->render_vaapi_surface (img, va_surface); + } + } +#endif /* ENABLE_VAAPI */ + + this->skipframes = img->draw (img, this->stream); + if (free_img) + img->free (img); + } + + if (frames) + xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, + "ffmpeg_video_dec: flushed out %s%d frames.\n", + display ? "and displayed " : "", frames); +} + +static void ff_free_dr1_frames (ff_video_decoder_t *this, int all) { + xine_list_iterator_t it; + int frames; + /* Some codecs (wmv2, vp6, svq3...) are hard-wired to a few reference frames. + They will only be replaced when new ones arrive, and freed on codec close. + They also have no AVCodec.flush () callback for manual freeing (that is, + avcodec_flush_buffers () does nothing). + So lets tolerate this behaviour on plain stream seek. */ + if (!all) { + it = NULL; + frames = 0; + while ((it = xine_list_next (this->dr1_frames, it)) != NULL) + frames++; + if (!frames) + return; + if (frames < 5) { + xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, + "ffmpeg_video_dec: tolerating %d held DR1 frames.\n", frames); + return; + } + } + /* frame garbage collector here - workaround for buggy ffmpeg codecs that + * don't release their DR1 frames */ + /* TJ. A note on libavcodec v55. + Looks like I found a way not to get to this point. + If it should happen anyway, we may earn some small memory leaks here. + Using the old get_buffer () API emulation makes even bigger leaks + (per frame, +3*AVBufferRef, +1*AVBuffer, +1*AVCodecContext, +1*AVFrame). + Tracking and killing the AVBufferRef's themselves would risk heap + corruption and segfaults. + Now tell me whether I really sigh too much... */ + it = NULL; + frames = 0; + while ((it = xine_list_next (this->dr1_frames, it)) != NULL) { + vo_frame_t *img = (vo_frame_t *)xine_list_get_value(this->dr1_frames, it); + if (img) { + img->free (img); + frames++; + } + } + xine_list_clear (this->dr1_frames); + /* we probably never get this. */ + if (frames) + xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, + "ffmpeg_video_dec: freed %d orphaned DR1 frames.\n", frames); +} + static void ff_flush (video_decoder_t *this_gen) { + ff_video_decoder_t *this = (ff_video_decoder_t *) this_gen; + lprintf ("ff_flush\n"); + if (this->state == STATE_FRAME_SENT) + ff_flush_internal (this, 1); } static void ff_reset (video_decoder_t *this_gen) { @@ -2129,22 +2342,16 @@ static void ff_reset (video_decoder_t *this_gen) { lprintf ("ff_reset\n"); this->size = 0; + this->state = STATE_RESET; if(this->context && this->decoder_ok) { - xine_list_iterator_t it = NULL; - + /* Discard any undisplayed frames. */ + ff_flush_internal (this, 0); + /* Ask decoder to free held reference frames (which it may ignore). */ avcodec_flush_buffers(this->context); - - /* frame garbage collector here - workaround for buggy ffmpeg codecs that - * don't release their DR1 frames */ - while ((it = xine_list_next (this->dr1_frames, it)) != NULL) - { - vo_frame_t *img = (vo_frame_t *)xine_list_get_value(this->dr1_frames, it); - if (img) - img->free(img); - } - xine_list_clear(this->dr1_frames); + /* Free obviously too many DR1 frames. */ + ff_free_dr1_frames (this, 0); } if (this->is_mpeg12) @@ -2161,6 +2368,7 @@ static void ff_discontinuity (video_decoder_t *this_gen) { lprintf ("ff_discontinuity\n"); this->pts = 0; + this->state = STATE_DISCONTINUITY; /* * there is currently no way to reset all the pts which are stored in the decoder. @@ -2201,23 +2409,17 @@ static void ff_dispose (video_decoder_t *this_gen) { lprintf ("ff_dispose\n"); + ff_flush_internal (this, 0); + rgb2yuy2_free (this->rgb2yuy2); if (this->decoder_ok) { - xine_list_iterator_t it = NULL; pthread_mutex_lock(&ffmpeg_lock); avcodec_close (this->context); pthread_mutex_unlock(&ffmpeg_lock); - /* frame garbage collector here - workaround for buggy ffmpeg codecs that - * don't release their DR1 frames */ - while ((it = xine_list_next (this->dr1_frames, it)) != NULL) - { - vo_frame_t *img = (vo_frame_t *)xine_list_get_value(this->dr1_frames, it); - if (img) - img->free(img); - } + ff_free_dr1_frames (this, 1); this->stream->video_out->close(this->stream->video_out, this->stream); this->decoder_ok = 0; |