summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Jager <t.jager@gmx.de>2013-11-28 18:50:20 +0100
committerTorsten Jager <t.jager@gmx.de>2013-11-28 18:50:20 +0100
commit4e08e3cb4082666ec01504d5a19f5ab8e51a6a6c (patch)
tree88dd1727201130fc29650048c35493cde387efca
parentb5539e68ad9a8fc8980fe2d5068cc05939c0091c (diff)
downloadxine-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.c244
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;