From e7fcbe117702391308089d4bb3d074d4e888fb98 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 19 Jul 2006 16:35:13 +0000 Subject: [PATCH] ext/ffmpeg/gstffmpegdec.c: Make some enums const. Original commit message from CVS: * ext/ffmpeg/gstffmpegdec.c: (gst_ffmpegdec_lowres_get_type), (gst_ffmpegdec_skipframe_get_type), (gst_ffmpegdec_class_init), (gst_ffmpegdec_init), (gst_ffmpegdec_open), (gst_ffmpegdec_setcaps), (gst_ffmpegdec_negotiate), (gst_ffmpegdec_do_qos), (clip_video_buffer), (check_keyframe), (get_output_buffer), (gst_ffmpegdec_video_frame), (gst_ffmpegdec_frame), (gst_ffmpegdec_sink_event), (gst_ffmpegdec_chain): Make some enums const. Cleanups, refactoring. Better video frame clipping. Timestamp fixe: use timestamp from incomming buffer even if there is no input framerate given (as this is totally unrelated). --- ChangeLog | 16 ++ common | 2 +- ext/ffmpeg/gstffmpegdec.c | 592 ++++++++++++++++++++++---------------- 3 files changed, 358 insertions(+), 252 deletions(-) diff --git a/ChangeLog b/ChangeLog index cb01eac4f0..6c1615bd0c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2006-07-19 Wim Taymans + + * ext/ffmpeg/gstffmpegdec.c: (gst_ffmpegdec_lowres_get_type), + (gst_ffmpegdec_skipframe_get_type), (gst_ffmpegdec_class_init), + (gst_ffmpegdec_init), (gst_ffmpegdec_open), + (gst_ffmpegdec_setcaps), (gst_ffmpegdec_negotiate), + (gst_ffmpegdec_do_qos), (clip_video_buffer), (check_keyframe), + (get_output_buffer), (gst_ffmpegdec_video_frame), + (gst_ffmpegdec_frame), (gst_ffmpegdec_sink_event), + (gst_ffmpegdec_chain): + Make some enums const. + Cleanups, refactoring. + Better video frame clipping. + Timestamp fixe: use timestamp from incomming buffer even if there + is no input framerate given (as this is totally unrelated). + 2006-06-12 Edward Hervey * .cvsignore: diff --git a/common b/common index dd85f55044..53ecdc0c97 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit dd85f550441bd5722b98f4dd304e40826383240f +Subproject commit 53ecdc0c97a2992e5abeddd41d514bc142401e5d diff --git a/ext/ffmpeg/gstffmpegdec.c b/ext/ffmpeg/gstffmpegdec.c index f0b57d0216..d2f4772cca 100644 --- a/ext/ffmpeg/gstffmpegdec.c +++ b/ext/ffmpeg/gstffmpegdec.c @@ -163,7 +163,7 @@ gst_ffmpegdec_lowres_get_type (void) static GType ffmpegdec_lowres_type = 0; if (!ffmpegdec_lowres_type) { - static GEnumValue ffmpegdec_lowres[] = { + static const GEnumValue ffmpegdec_lowres[] = { {0, "0", "full"}, {1, "1", "1/2-size"}, {2, "2", "1/4-size"}, @@ -184,7 +184,7 @@ gst_ffmpegdec_skipframe_get_type (void) static GType ffmpegdec_skipframe_type = 0; if (!ffmpegdec_skipframe_type) { - static GEnumValue ffmpegdec_skipframe[] = { + static const GEnumValue ffmpegdec_skipframe[] = { {0, "0", "Skip nothing"}, {1, "1", "Skip B-frames"}, {2, "2", "Skip IDCT/Dequantization"}, @@ -254,7 +254,6 @@ gst_ffmpegdec_class_init (GstFFMpegDecClass * klass) gobject_class->set_property = gst_ffmpegdec_set_property; gobject_class->get_property = gst_ffmpegdec_get_property; - gstelement_class->change_state = gst_ffmpegdec_change_state; if (klass->in_plugin->type == CODEC_TYPE_VIDEO) { g_object_class_install_property (gobject_class, ARG_SKIPFRAME, @@ -266,6 +265,8 @@ gst_ffmpegdec_class_init (GstFFMpegDecClass * klass) "At which resolution to decode images", GST_FFMPEGDEC_TYPE_LOWRES, 0, G_PARAM_READWRITE)); } + + gstelement_class->change_state = gst_ffmpegdec_change_state; } static void @@ -277,9 +278,12 @@ gst_ffmpegdec_init (GstFFMpegDec * ffmpegdec) /* setup pads */ ffmpegdec->sinkpad = gst_pad_new_from_template (oclass->sinktempl, "sink"); - gst_pad_set_setcaps_function (ffmpegdec->sinkpad, gst_ffmpegdec_setcaps); - gst_pad_set_event_function (ffmpegdec->sinkpad, gst_ffmpegdec_sink_event); - gst_pad_set_chain_function (ffmpegdec->sinkpad, gst_ffmpegdec_chain); + gst_pad_set_setcaps_function (ffmpegdec->sinkpad, + GST_DEBUG_FUNCPTR (gst_ffmpegdec_setcaps)); + gst_pad_set_event_function (ffmpegdec->sinkpad, + GST_DEBUG_FUNCPTR (gst_ffmpegdec_sink_event)); + gst_pad_set_chain_function (ffmpegdec->sinkpad, + GST_DEBUG_FUNCPTR (gst_ffmpegdec_chain)); gst_element_add_pad (GST_ELEMENT (ffmpegdec), ffmpegdec->sinkpad); ffmpegdec->srcpad = gst_pad_new_from_template (oclass->srctempl, "src"); @@ -360,7 +364,6 @@ gst_ffmpegdec_query (GstPad * pad, GstQuery * query) return res; } -/* FIXME, make me ATOMIC */ static void gst_ffmpegdec_update_qos (GstFFMpegDec * ffmpegdec, gdouble proportion, GstClockTime time) @@ -474,7 +477,8 @@ gst_ffmpegdec_open (GstFFMpegDec * ffmpegdec) ffmpegdec->opened = TRUE; - GST_LOG_OBJECT (ffmpegdec, "Opened ffmpeg codec %s", oclass->in_plugin->name); + GST_LOG_OBJECT (ffmpegdec, "Opened ffmpeg codec %s, id %d", + oclass->in_plugin->name, oclass->in_plugin->id); /* open a parser if we can - exclude mp3 because it doesn't work (?), * and mjpeg because ... */ @@ -482,6 +486,7 @@ gst_ffmpegdec_open (GstFFMpegDec * ffmpegdec) oclass->in_plugin->id != CODEC_ID_MJPEG && oclass->in_plugin->id != CODEC_ID_MP3 && oclass->in_plugin->id != CODEC_ID_H264) { + GST_LOG_OBJECT (ffmpegdec, "Using parser"); ffmpegdec->pctx = av_parser_init (oclass->in_plugin->id); } @@ -555,6 +560,7 @@ gst_ffmpegdec_setcaps (GstPad * pad, GstCaps * caps) /* get pixel aspect ratio if it's set */ structure = gst_caps_get_structure (caps, 0); + par = gst_structure_get_value (structure, "pixel-aspect-ratio"); if (par) { GST_DEBUG_OBJECT (ffmpegdec, "sink caps have pixel-aspect-ratio of %d:%d", @@ -567,6 +573,8 @@ gst_ffmpegdec_setcaps (GstPad * pad, GstCaps * caps) gst_value_init_and_copy (ffmpegdec->par, par); } + /* get the framerate from incomming caps. fps_n is set to -1 when + * there is no valid framerate */ fps = gst_structure_get_value (structure, "framerate"); if (fps != NULL && GST_VALUE_HOLDS_FRACTION (fps)) { ffmpegdec->format.video.fps_n = gst_value_get_fraction_numerator (fps); @@ -841,7 +849,7 @@ gst_ffmpegdec_negotiate (GstFFMpegDec * ffmpegdec) /* If a demuxer provided a framerate then use it (#313970) */ if (ffmpegdec->format.video.fps_n != -1) { - gst_structure_set (gst_caps_get_structure (caps, 0), "framerate", + gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, ffmpegdec->format.video.fps_n, ffmpegdec->format.video.fps_d, NULL); } @@ -893,14 +901,14 @@ gst_ffmpegdec_do_qos (GstFFMpegDec * ffmpegdec, GstClockTime timestamp, *mode_switch = FALSE; /* no timestamp, can't do QoS */ - if (!GST_CLOCK_TIME_IS_VALID (timestamp)) + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (timestamp))) goto no_qos; /* get latest QoS observation values */ gst_ffmpegdec_read_qos (ffmpegdec, &proportion, &earliest_time); /* skip qos if we have no observation (yet) */ - if (!GST_CLOCK_TIME_IS_VALID (earliest_time)) { + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (earliest_time))) { /* no hurry_up initialy */ ffmpegdec->context->hurry_up = 0; goto no_qos; @@ -979,6 +987,155 @@ hurry_up: } } +/* returns TRUE if buffer is within segment, else FALSE. + * if Buffer is on segment border, it's timestamp and duration will be clipped */ +static gboolean +clip_video_buffer (GstFFMpegDec * dec, GstBuffer * buf, GstClockTime in_ts, + GstClockTime in_dur) +{ + gboolean res = TRUE; + gint64 cstart, cstop; + GstClockTime stop; + + GST_LOG_OBJECT (dec, + "timestamp:%" GST_TIME_FORMAT " , duration:%" GST_TIME_FORMAT, + GST_TIME_ARGS (in_ts), GST_TIME_ARGS (in_dur)); + + /* can't clip without TIME segment */ + if (dec->segment.format != GST_FORMAT_TIME) + goto beach; + + /* we need a start time */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (in_ts))) + goto beach; + + /* generate valid stop, if duration unknown, we have unknown stop */ + stop = + GST_CLOCK_TIME_IS_VALID (in_dur) ? (in_ts + in_dur) : GST_CLOCK_TIME_NONE; + + /* now clip */ + if (G_UNLIKELY (!(res = gst_segment_clip (&dec->segment, GST_FORMAT_TIME, + in_ts, stop, &cstart, &cstop)))) + goto beach; + + /* update timestamp and possibly duration if the clipped stop time is + * valid */ + GST_BUFFER_TIMESTAMP (buf) = cstart; + if (GST_CLOCK_TIME_IS_VALID (cstop)) + GST_BUFFER_DURATION (buf) = cstop - cstart; + +beach: + GST_LOG_OBJECT (dec, "%sdropping", (res ? "not " : "")); + return res; +} + + +/* figure out if the current picture is a keyframe, return TRUE if that is + * the case. */ +static gboolean +check_keyframe (GstFFMpegDec * ffmpegdec) +{ + GstFFMpegDecClass *oclass; + gboolean is_itype = FALSE; + gboolean is_reference = FALSE; + gboolean iskeyframe; + + /* figure out if we are dealing with a keyframe */ + oclass = (GstFFMpegDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); + + is_itype = (ffmpegdec->picture->pict_type == FF_I_TYPE); + is_reference = (ffmpegdec->picture->reference == 1); + + iskeyframe = (is_itype || is_reference || ffmpegdec->picture->key_frame) + || (oclass->in_plugin->id == CODEC_ID_INDEO3) + || (oclass->in_plugin->id == CODEC_ID_MSZH) + || (oclass->in_plugin->id == CODEC_ID_ZLIB) + || (oclass->in_plugin->id == CODEC_ID_VP3) + || (oclass->in_plugin->id == CODEC_ID_HUFFYUV); + + GST_LOG_OBJECT (ffmpegdec, + "current picture: is_keyframe:%d, is_itype:%d, is_reference:%d", + iskeyframe, is_itype, is_reference); + + return iskeyframe; +} + +/* get an outbuf buffer with the current picture */ +static GstFlowReturn +get_output_buffer (GstFFMpegDec * ffmpegdec, GstBuffer ** outbuf) +{ + GstFlowReturn ret; + + ret = GST_FLOW_ERROR; + *outbuf = NULL; + + /* libavcodec constantly crashes on stupid buffer allocation + * errors inside. This drives me crazy, so we let it allocate + * its own buffers and copy to our own buffer afterwards... */ + /* BUFFER CREATION */ + if (ffmpegdec->picture->opaque != NULL) { + *outbuf = (GstBuffer *) ffmpegdec->picture->opaque; + if (*outbuf == ffmpegdec->last_buffer) + ffmpegdec->last_buffer = NULL; + if (*outbuf != NULL) + ret = GST_FLOW_OK; + } else { + AVPicture pic; + gint fsize; + + /* see if we need renegotiation */ + if (G_UNLIKELY (!gst_ffmpegdec_negotiate (ffmpegdec))) + goto negotiate_failed; + + fsize = gst_ffmpeg_avpicture_get_size (ffmpegdec->context->pix_fmt, + ffmpegdec->context->width, ffmpegdec->context->height); + + if (!ffmpegdec->context->palctrl) { + ret = gst_pad_alloc_buffer_and_set_caps (ffmpegdec->srcpad, + GST_BUFFER_OFFSET_NONE, fsize, + GST_PAD_CAPS (ffmpegdec->srcpad), outbuf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto alloc_failed; + + } else { + /* for paletted data we can't use pad_alloc_buffer(), because + * fsize contains the size of the palette, so the overall size + * is bigger than ffmpegcolorspace's unit size, which will + * prompt GstBaseTransform to complain endlessly ... */ + *outbuf = gst_buffer_new_and_alloc (fsize); + gst_buffer_set_caps (*outbuf, GST_PAD_CAPS (ffmpegdec->srcpad)); + ret = GST_FLOW_OK; + } + + /* original ffmpeg code does not handle odd sizes correctly. + * This patched up version does */ + gst_ffmpeg_avpicture_fill (&pic, GST_BUFFER_DATA (*outbuf), + ffmpegdec->context->pix_fmt, + ffmpegdec->context->width, ffmpegdec->context->height); + + /* the original convert function did not do the right thing, this + * is a patched up version that adjust widht/height so that the + * ffmpeg one works correctly. */ + gst_ffmpeg_img_convert (&pic, ffmpegdec->context->pix_fmt, + (AVPicture *) ffmpegdec->picture, + ffmpegdec->context->pix_fmt, + ffmpegdec->context->width, ffmpegdec->context->height); + } + return ret; + + /* special cases */ +negotiate_failed: + { + GST_DEBUG_OBJECT (ffmpegdec, "negotiate failed"); + return GST_FLOW_NOT_NEGOTIATED; + } +alloc_failed: + { + GST_DEBUG_OBJECT (ffmpegdec, "pad_alloc failed"); + return ret; + } +} + /* gst_ffmpegdec_[video|audio]_frame: * ffmpegdec: * data: pointer to the data to decode @@ -997,15 +1154,11 @@ gst_ffmpegdec_video_frame (GstFFMpegDec * ffmpegdec, guint8 * data, guint size, GstBuffer * inbuf, GstBuffer ** outbuf, GstFlowReturn * ret) { - GstFFMpegDecClass *oclass = - (GstFFMpegDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); gint len = -1; gint have_data; - gboolean iskeyframe = FALSE; - gboolean is_itype = FALSE; - gboolean is_reference = FALSE; - gboolean mode_switch = FALSE; - guint64 dec_time = GST_CLOCK_TIME_NONE; + gboolean iskeyframe; + gboolean mode_switch; + GstClockTime in_ts, in_dur; *ret = GST_FLOW_OK; *outbuf = NULL; @@ -1014,222 +1167,146 @@ gst_ffmpegdec_video_frame (GstFFMpegDec * ffmpegdec, /* run QoS code, returns FALSE if we can skip decoding this * frame entirely. */ - if (inbuf) - if (!gst_ffmpegdec_do_qos (ffmpegdec, GST_BUFFER_TIMESTAMP (inbuf), - &mode_switch)) - goto beach; + if (G_LIKELY (inbuf)) { + in_ts = GST_BUFFER_TIMESTAMP (inbuf); + in_dur = GST_BUFFER_DURATION (inbuf); + if (G_UNLIKELY (!gst_ffmpegdec_do_qos (ffmpegdec, in_ts, &mode_switch))) + goto drop_qos; + } else { + in_ts = GST_CLOCK_TIME_NONE; + in_dur = GST_CLOCK_TIME_NONE; + mode_switch = FALSE; + } - ffmpegdec->picture->pict_type = -1; /* in case we skip frames */ + /* in case we skip frames */ + ffmpegdec->picture->pict_type = -1; + /* now decode the frame */ len = avcodec_decode_video (ffmpegdec->context, ffmpegdec->picture, &have_data, data, size); - /* Get timestamp from FFMPEG */ - dec_time = gst_ffmpeg_time_ff_to_gst ((guint64) ffmpegdec->picture->pts, - ffmpegdec->context->time_base); - GST_DEBUG_OBJECT (ffmpegdec, - "Picture : key_frame:%d , pict_type:%d, pts:%lld => [%" GST_TIME_FORMAT - "]", ffmpegdec->picture->key_frame, ffmpegdec->picture->pict_type, - ffmpegdec->picture->pts, GST_TIME_ARGS (dec_time)); + GST_DEBUG_OBJECT (ffmpegdec, "after decode: len %d, have_data %d", + len, have_data); /* when we are in hurry_up mode, don't complain when ffmpeg returned * no data because we told it to skip stuff. */ if (len < 0 && (mode_switch || ffmpegdec->context->hurry_up)) len = 0; - is_itype = (ffmpegdec->picture->pict_type == FF_I_TYPE); - is_reference = (ffmpegdec->picture->reference == 1); - iskeyframe = (is_itype || is_reference || ffmpegdec->picture->key_frame) - || (oclass->in_plugin->id == CODEC_ID_INDEO3) - || (oclass->in_plugin->id == CODEC_ID_MSZH) - || (oclass->in_plugin->id == CODEC_ID_ZLIB) - || (oclass->in_plugin->id == CODEC_ID_VP3) - || (oclass->in_plugin->id == CODEC_ID_HUFFYUV); - GST_LOG_OBJECT (ffmpegdec, - "Decoded video: len=%d, have_data=%d, " - "is_keyframe:%d, is_itype:%d, is_reference:%d", - len, have_data, iskeyframe, is_itype, is_reference); + /* no data, we're done */ + if (len < 0 || have_data <= 0) + goto beach; - if (ffmpegdec->waiting_for_key) { - if (iskeyframe) { - ffmpegdec->waiting_for_key = FALSE; - } else { - GST_WARNING_OBJECT (ffmpegdec, "Dropping non-keyframe (seek/init)"); - goto beach; - } + /* check if we are dealing with a keyframe here */ + iskeyframe = check_keyframe (ffmpegdec); + + /* when we're waiting for a keyframe, see if we have one or drop the current + * non-keyframe */ + if (G_UNLIKELY (ffmpegdec->waiting_for_key)) { + if (G_LIKELY (!iskeyframe)) + goto drop_non_keyframe; + + /* we have a keyframe, we can stop waiting for one */ + ffmpegdec->waiting_for_key = FALSE; } - if (len >= 0 && have_data > 0) { - /* libavcodec constantly crashes on stupid buffer allocation - * errors inside. This drives me crazy, so we let it allocate - * its own buffers and copy to our own buffer afterwards... */ + /* get a handle to the output buffer */ + *ret = get_output_buffer (ffmpegdec, outbuf); + if (G_UNLIKELY (*ret != GST_FLOW_OK)) + goto no_output; - /* BUFFER CREATION */ - if (ffmpegdec->picture->opaque != NULL) { - *outbuf = (GstBuffer *) ffmpegdec->picture->opaque; - if (*outbuf == ffmpegdec->last_buffer) - ffmpegdec->last_buffer = NULL; + /* + * Timestamps: + * + * 1) Copy input timestamp if valid + * 2) else interpolate from previous input timestamp + */ + /* always take timestamps from the input buffer if any */ + if (!GST_CLOCK_TIME_IS_VALID (in_ts)) { + GST_LOG_OBJECT (ffmpegdec, "using timestamp returned by ffmpeg"); + /* Get (interpolated) timestamp from FFMPEG */ + in_ts = gst_ffmpeg_time_ff_to_gst ((guint64) ffmpegdec->picture->pts, + ffmpegdec->context->time_base); + } + GST_BUFFER_TIMESTAMP (*outbuf) = in_ts; + + /* + * Duration: + * + * 1) Copy input duration if valid + * 2) else use input framerate + * 3) else use ffmpeg framerate + */ + if (!GST_CLOCK_TIME_IS_VALID (in_dur)) { + /* if we have an input framerate, use that */ + if (ffmpegdec->format.video.fps_n != -1) { + GST_LOG_OBJECT (ffmpegdec, "using input framerate for duration"); + in_dur = gst_util_uint64_scale_int (GST_SECOND, + ffmpegdec->format.video.fps_d, ffmpegdec->format.video.fps_n); } else { - AVPicture pic; - gint fsize = gst_ffmpeg_avpicture_get_size (ffmpegdec->context->pix_fmt, - ffmpegdec->context->width, ffmpegdec->context->height); - - if (!gst_ffmpegdec_negotiate (ffmpegdec)) - return -1; - - if (!ffmpegdec->context->palctrl) { - if ((*ret = - gst_pad_alloc_buffer_and_set_caps (ffmpegdec->srcpad, - GST_BUFFER_OFFSET_NONE, fsize, - GST_PAD_CAPS (ffmpegdec->srcpad), outbuf)) != GST_FLOW_OK) { - len = -1; - goto beach; - } - } else { - /* for paletted data we can't use pad_alloc_buffer(), because - * fsize contains the size of the palette, so the overall size - * is bigger than ffmpegcolorspace's unit size, which will - * prompt GstBaseTransform to complain endlessly ... */ - *outbuf = gst_buffer_new_and_alloc (fsize); - gst_buffer_set_caps (*outbuf, GST_PAD_CAPS (ffmpegdec->srcpad)); - } - - /* original ffmpeg code does not handle odd sizes correctly. - * This patched up version does */ - gst_ffmpeg_avpicture_fill (&pic, GST_BUFFER_DATA (*outbuf), - ffmpegdec->context->pix_fmt, - ffmpegdec->context->width, ffmpegdec->context->height); - - /* the original convert function did not do the right thing, this - * is a patched up version that adjust widht/height so that the - * ffmpeg one works correctly. */ - gst_ffmpeg_img_convert (&pic, ffmpegdec->context->pix_fmt, - (AVPicture *) ffmpegdec->picture, - ffmpegdec->context->pix_fmt, - ffmpegdec->context->width, ffmpegdec->context->height); - } - - - ffmpegdec->waiting_for_key = FALSE; - if (!iskeyframe) { - GST_BUFFER_FLAG_SET (*outbuf, GST_BUFFER_FLAG_DELTA_UNIT); - } - - - /* - * Timestamp and duration - */ - - /* If we have used the framerate from the demuxer then - * also use the demuxer's timestamp information (#317596) */ - if (ffmpegdec->format.video.fps_n != -1 && inbuf != NULL) { - if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (inbuf))) { - GST_LOG_OBJECT (ffmpegdec, "using incoming buffer's timestamps"); - GST_LOG_OBJECT (ffmpegdec, - "incoming timestamp %" GST_TIME_FORMAT " duration:%" - GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)), - GST_TIME_ARGS (GST_BUFFER_DURATION (inbuf))); - gst_buffer_stamp (*outbuf, inbuf); - } else { - GST_LOG_OBJECT (ffmpegdec, "using timestamp returned by ffmpeg"); - GST_BUFFER_TIMESTAMP (*outbuf) = dec_time; - } - if (!(GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (*outbuf)))) { - /* some codecs don't have normal time_base */ - if (ffmpegdec->context->time_base.den >= 1000) - GST_BUFFER_DURATION (*outbuf) = - gst_util_uint64_scale_int (GST_SECOND, - ffmpegdec->format.video.fps_d, ffmpegdec->format.video.fps_n); - else - GST_BUFFER_DURATION (*outbuf) = - gst_util_uint64_scale_int (GST_SECOND, - ffmpegdec->context->time_base.num, - ffmpegdec->context->time_base.den); - /* Take repeat_pict into account */ - GST_BUFFER_DURATION (*outbuf) += GST_BUFFER_DURATION (*outbuf) - * ffmpegdec->picture->repeat_pict / 2; - - GST_DEBUG_OBJECT (ffmpegdec, "Buffer duration is now %" GST_TIME_FORMAT, - GST_TIME_ARGS (GST_BUFFER_DURATION (*outbuf))); - } - } else { - GST_LOG_OBJECT (ffmpegdec, "using decoder's timestamps"); - GST_BUFFER_TIMESTAMP (*outbuf) = dec_time; + /* don't try to use the decoder's framerate when it seems a bit abnormal, + * which we assume when den >= 1000... */ if (ffmpegdec->context->time_base.num != 0 && - ffmpegdec->context->time_base.den != 0) { - GST_BUFFER_DURATION (*outbuf) = - gst_util_uint64_scale_int (GST_SECOND, + (ffmpegdec->context->time_base.den > 0 && + ffmpegdec->context->time_base.den < 1000)) { + GST_LOG_OBJECT (ffmpegdec, "using decoder's framerate for duration"); + in_dur = gst_util_uint64_scale_int (GST_SECOND, ffmpegdec->context->time_base.num, ffmpegdec->context->time_base.den); - - /* Take repeat_pict into account */ - GST_BUFFER_DURATION (*outbuf) += GST_BUFFER_DURATION (*outbuf) - * ffmpegdec->picture->repeat_pict / 2; + } else { + GST_LOG_OBJECT (ffmpegdec, "no valid duration found"); } } - GST_LOG_OBJECT (ffmpegdec, "outgoing timestamp %" GST_TIME_FORMAT, - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (*outbuf))); } + /* Take repeat_pict into account */ + if (GST_CLOCK_TIME_IS_VALID (in_dur)) { + in_dur += in_dur * ffmpegdec->picture->repeat_pict / 2; + } + GST_BUFFER_DURATION (*outbuf) = in_dur; /* palette is not part of raw video frame in gst and the size * of the outgoing buffer needs to be adjusted accordingly */ - if (ffmpegdec->context->palctrl != NULL && *outbuf != NULL) + if (ffmpegdec->context->palctrl != NULL) GST_BUFFER_SIZE (*outbuf) -= AVPALETTE_SIZE; + /* now see if we need to clip the buffer against the segment boundaries. */ + if (G_UNLIKELY (!clip_video_buffer (ffmpegdec, *outbuf, in_ts, in_dur))) + goto clipped; - if (*outbuf) { - ffmpegdec->next_ts = - GST_BUFFER_TIMESTAMP (outbuf) + GST_BUFFER_DURATION (outbuf); - - /* Clipping/dropping */ - GST_DEBUG_OBJECT (ffmpegdec, - "clipping/droping check for buffer %" GST_TIME_FORMAT " [duration:%" - GST_TIME_FORMAT "]", GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (*outbuf)), - GST_TIME_ARGS (GST_BUFFER_DURATION (*outbuf))); - if ((ffmpegdec->segment.format == GST_FORMAT_TIME) - && (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (*outbuf)))) { - gint64 start, stop, clip_start, clip_stop; - - start = GST_BUFFER_TIMESTAMP (*outbuf); - - if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (*outbuf))) { - stop = start + GST_BUFFER_DURATION (*outbuf); - - if (!(gst_segment_clip (&ffmpegdec->segment, GST_FORMAT_TIME, - start, stop, &clip_start, &clip_stop))) { - GST_LOG_OBJECT (ffmpegdec, - "dropping buffer since it's outside configured segment [%" - GST_TIME_FORMAT "-%" GST_TIME_FORMAT "]", - GST_TIME_ARGS (ffmpegdec->segment.start), - GST_TIME_ARGS (ffmpegdec->segment.stop)); - gst_buffer_unref (*outbuf); - *outbuf = NULL; - } else { - GST_BUFFER_TIMESTAMP (*outbuf) = clip_start; - GST_BUFFER_DURATION (*outbuf) = clip_stop - clip_start; - GST_LOG_OBJECT (ffmpegdec, "Buffer %" GST_TIME_FORMAT " [duration:%" - GST_TIME_FORMAT "] is inside configured segment", - GST_TIME_ARGS (clip_start), - GST_TIME_ARGS (clip_stop - clip_start)); - } - } else { - if ((start < ffmpegdec->segment.start) - || (start >= ffmpegdec->segment.stop)) { - GST_LOG_OBJECT (ffmpegdec, - "dropping buffer since it's start time is outside configured segment"); - gst_buffer_unref (*outbuf); - *outbuf = NULL; - } - } - - } else - GST_DEBUG_OBJECT (ffmpegdec, - "segment wasn't configured, or buffer has no timestamp. No clipping done"); - } + /* mark as keyframe or delta unit */ + if (!iskeyframe) + GST_BUFFER_FLAG_SET (*outbuf, GST_BUFFER_FLAG_DELTA_UNIT); beach: + GST_DEBUG_OBJECT (ffmpegdec, "return flow %d, out %p, len %d", + *ret, *outbuf, len); return len; + + /* special cases */ +drop_qos: + { + GST_WARNING_OBJECT (ffmpegdec, "Dropping frame because of QoS"); + goto beach; + } +drop_non_keyframe: + { + GST_WARNING_OBJECT (ffmpegdec, "Dropping non-keyframe (seek/init)"); + goto beach; + } +no_output: + { + GST_DEBUG_OBJECT (ffmpegdec, "no output buffer"); + len = -1; + goto beach; + } +clipped: + { + GST_DEBUG_OBJECT (ffmpegdec, "buffer clipped"); + gst_buffer_unref (*outbuf); + *outbuf = NULL; + goto beach; + } } @@ -1363,8 +1440,8 @@ gst_ffmpegdec_frame (GstFFMpegDec * ffmpegdec, GstBuffer *outbuf = NULL; gint have_data = 0, len = 0; - if (ffmpegdec->context->codec == NULL) - return -1; + if (G_UNLIKELY (ffmpegdec->context->codec == NULL)) + goto no_codec; GST_LOG_OBJECT (ffmpegdec, "data:%p, size:%d, inbuf:%p inbuf.ts:%" @@ -1386,13 +1463,12 @@ gst_ffmpegdec_frame (GstFFMpegDec * ffmpegdec, ret); break; default: - g_assert (0); + g_assert_not_reached (); break; } - if (outbuf) { + if (outbuf) have_data = 1; - } if (len < 0 || have_data < 0) { GST_WARNING_OBJECT (ffmpegdec, @@ -1423,6 +1499,13 @@ gst_ffmpegdec_frame (GstFFMpegDec * ffmpegdec, beach: return len; + + /* ERRORS */ +no_codec: + { + GST_ERROR_OBJECT (ffmpegdec, "no codec context"); + return -1; + } } static void @@ -1488,11 +1571,12 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event) gboolean update; GstFormat fmt; gint64 start, stop, time; - gdouble rate; + gdouble rate, arate; - gst_event_parse_new_segment (event, &update, &rate, &fmt, &start, &stop, - &time); + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &fmt, + &start, &stop, &time); + /* no negative rates for now */ if (rate <= 0.0) goto newseg_wrong_rate; @@ -1526,7 +1610,7 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event) /* create new converted time segment */ fmt = GST_FORMAT_TIME; /* FIXME, bitrate is not good enough too find a good stop, let's - * hope start and time were 0... */ + * hope start and time were 0... meh. */ stop = -1; event = gst_event_new_new_segment (update, rate, fmt, start, stop, time); @@ -1542,25 +1626,15 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event) GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); /* and store the values */ - gst_segment_set_newsegment (&ffmpegdec->segment, update, - rate, fmt, start, stop, time); - - /* FIXME, newsegment does not define the next timestamp */ - ffmpegdec->next_ts = start; - - /* FIXME, newsegment does not mean a DISCONT */ - if (ffmpegdec->opened) { - avcodec_flush_buffers (ffmpegdec->context); - } - ffmpegdec->waiting_for_key = TRUE; - - /* and push segment downstream */ + gst_segment_set_newsegment_full (&ffmpegdec->segment, update, + rate, arate, fmt, start, stop, time); break; } default: break; } + /* and push segment downstream */ ret = gst_pad_push_event (ffmpegdec->srcpad, event); done: @@ -1602,62 +1676,71 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf) ffmpegdec = (GstFFMpegDec *) (GST_PAD_PARENT (pad)); - oclass = (GstFFMpegDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); - if (G_UNLIKELY (!ffmpegdec->opened)) goto not_negotiated; - GST_DEBUG_OBJECT (ffmpegdec, "chain"); + in_ts = GST_BUFFER_TIMESTAMP (inbuf); + GST_DEBUG_OBJECT (ffmpegdec, "chain, ts: %" GST_TIME_FORMAT, + GST_TIME_ARGS (in_ts)); + + /* The discont flags marks a buffer that is not continuous with the previous + * buffer. This means we need to clear whatever data we currently have. We + * currently also wait for a new keyframe, which might be suboptimal in the + * case of a network error, better show the errors than to drop all data.. */ if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DISCONT))) { GST_DEBUG_OBJECT (ffmpegdec, "received DISCONT"); gst_ffmpegdec_flush_pcache (ffmpegdec); + avcodec_flush_buffers (ffmpegdec->context); ffmpegdec->waiting_for_key = TRUE; } - in_ts = GST_BUFFER_TIMESTAMP (inbuf); - + /* do early keyframe check pretty bad to rely on the keyframe flag in the + * source for this as it might not even be parsed (UDP/file/..). */ if (G_UNLIKELY (ffmpegdec->waiting_for_key)) { if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DELTA_UNIT)) goto skip_keyframe; - GST_DEBUG_OBJECT (ffmpegdec, "got keyframe %" GST_TIME_FORMAT, - GST_TIME_ARGS (in_ts)); + GST_DEBUG_OBJECT (ffmpegdec, "got keyframe"); ffmpegdec->waiting_for_key = FALSE; } GST_LOG_OBJECT (ffmpegdec, "Received new data of size %d, time %" GST_TIME_FORMAT " next_ts %" GST_TIME_FORMAT, GST_BUFFER_SIZE (inbuf), - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)), - GST_TIME_ARGS (ffmpegdec->next_ts)); + GST_TIME_ARGS (in_ts), GST_TIME_ARGS (ffmpegdec->next_ts)); + + oclass = (GstFFMpegDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); /* parse cache joining */ if (ffmpegdec->pcache) { - GstClockTime timestamp = GST_BUFFER_TIMESTAMP (ffmpegdec->pcache); - GstClockTime duration = GST_CLOCK_TIME_NONE; + GstClockTime timestamp; + GstClockTime duration; + + timestamp = GST_BUFFER_TIMESTAMP (ffmpegdec->pcache); if (GST_BUFFER_DURATION_IS_VALID (ffmpegdec->pcache) && GST_BUFFER_DURATION_IS_VALID (inbuf)) duration = GST_BUFFER_DURATION (ffmpegdec->pcache) + GST_BUFFER_DURATION (inbuf); + else + duration = GST_CLOCK_TIME_NONE; inbuf = gst_buffer_join (ffmpegdec->pcache, inbuf); /* update time info as appropriate */ GST_BUFFER_TIMESTAMP (inbuf) = timestamp; GST_BUFFER_DURATION (inbuf) = duration; + GST_LOG_OBJECT (ffmpegdec, "joined parse cache, inbuf now has ts %" GST_TIME_FORMAT " and duration %" GST_TIME_FORMAT, - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)), GST_TIME_ARGS (duration)); + GST_TIME_ARGS (timestamp), GST_TIME_ARGS (duration)); + ffmpegdec->pcache = NULL; - bdata = GST_BUFFER_DATA (inbuf); - bsize = GST_BUFFER_SIZE (inbuf); /* If we're decoding something else, we'll use next_ts */ - next_ts = in_ts; - in_ts = timestamp; + next_ts = in_ts = timestamp; } /* workarounds, functions write to buffers: * libavcodec/svq1.c:svq1_decode_frame writes to the given buffer. @@ -1666,13 +1749,11 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf) else if (oclass->in_plugin->id == CODEC_ID_SVQ1 || oclass->in_plugin->id == CODEC_ID_SVQ3) { inbuf = gst_buffer_make_writable (inbuf); - bdata = GST_BUFFER_DATA (inbuf); - bsize = GST_BUFFER_SIZE (inbuf); - } else { - bdata = GST_BUFFER_DATA (inbuf); - bsize = GST_BUFFER_SIZE (inbuf); } + bdata = GST_BUFFER_DATA (inbuf); + bsize = GST_BUFFER_SIZE (inbuf); + do { GST_LOG_OBJECT (ffmpegdec, "bdata:%p , bsize:%d", bdata, bsize); /* parse, if at all possible */ @@ -1684,9 +1765,14 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf) "Calling av_parser_parse with ts:%" GST_TIME_FORMAT, GST_TIME_ARGS (in_ts)); + /* convert timestamp to ffmpeg timestamp */ ffpts = gst_ffmpeg_time_gst_to_ff (in_ts, ffmpegdec->context->time_base); + + /* feed the parser */ res = av_parser_parse (ffmpegdec->pctx, ffmpegdec->context, &data, &size, bdata, bsize, ffpts, ffpts); + + /* get new timestamp from the parser */ in_ts = gst_ffmpeg_time_ff_to_gst (ffmpegdec->pctx->pts, ffmpegdec->context->time_base); @@ -1696,7 +1782,7 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf) if (res == 0 || size == 0) { GST_LOG_OBJECT (ffmpegdec, - "parser returned null res or size, breaking"); + "parser returned res %d and size %d, breaking", res, size); break; } else { bsize -= res; @@ -1707,23 +1793,26 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf) size = bsize; } - if ((len = gst_ffmpegdec_frame (ffmpegdec, data, size, - &have_data, inbuf, &ret)) < 0 || ret != GST_FLOW_OK) + /* decode a frame of audio/video now */ + len = gst_ffmpegdec_frame (ffmpegdec, data, size, &have_data, inbuf, &ret); + if (len < 0 || ret != GST_FLOW_OK) break; if (!ffmpegdec->pctx) { bsize -= len; bdata += len; } - if (!have_data) { GST_LOG_OBJECT (ffmpegdec, "Decoding didn't return any data, breaking"); break; } - in_ts = next_ts; - GST_BUFFER_TIMESTAMP (inbuf) = in_ts; + + /* now we don;t know the timestamp anymore, the parser should help */ + GST_BUFFER_TIMESTAMP (inbuf) = in_ts = GST_CLOCK_TIME_NONE; + if (!GST_CLOCK_TIME_IS_VALID (next_ts)) next_ts = ffmpegdec->next_ts; + GST_LOG_OBJECT (ffmpegdec, "Before (while bsize>0). bsize:%d , bdata:%p", bsize, bdata); } while (bsize > 0); @@ -1749,6 +1838,7 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf) /* ERRORS */ not_negotiated: { + oclass = (GstFFMpegDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); GST_ELEMENT_ERROR (ffmpegdec, CORE, NEGOTIATION, (NULL), ("ffdec_%s: input format was not set before data start", oclass->in_plugin->name));