ext/ffmpeg/gstffmpegdec.c: Split out audio and video frame decoding.

Original commit message from CVS:
* ext/ffmpeg/gstffmpegdec.c: (gst_ffmpegdec_init),
(gst_ffmpegdec_query), (gst_ffmpegdec_update_qos),
(gst_ffmpegdec_reset_qos), (gst_ffmpegdec_read_qos),
(gst_ffmpegdec_open), (gst_ffmpegdec_setcaps),
(gst_ffmpegdec_get_buffer), (gst_ffmpegdec_release_buffer),
(gst_ffmpegdec_add_pixel_aspect_ratio), (gst_ffmpegdec_negotiate),
(gst_ffmpegdec_do_qos), (gst_ffmpegdec_video_frame),
(gst_ffmpegdec_audio_frame), (gst_ffmpegdec_frame),
(gst_ffmpegdec_flush_pcache), (gst_ffmpegdec_sink_event),
(gst_ffmpegdec_chain):
Split out audio and video frame decoding.
Added dropping/clipping of decoded buffers.
Ran gst-indent on code.
Small non-invasive code cleanups.
This commit is contained in:
Edward Hervey 2006-06-02 12:29:38 +00:00
parent 4c30f87706
commit 280d6dd420
2 changed files with 551 additions and 354 deletions

View file

@ -1,3 +1,20 @@
2006-06-02 Edward Hervey <edward@fluendo.com>
* ext/ffmpeg/gstffmpegdec.c: (gst_ffmpegdec_init),
(gst_ffmpegdec_query), (gst_ffmpegdec_update_qos),
(gst_ffmpegdec_reset_qos), (gst_ffmpegdec_read_qos),
(gst_ffmpegdec_open), (gst_ffmpegdec_setcaps),
(gst_ffmpegdec_get_buffer), (gst_ffmpegdec_release_buffer),
(gst_ffmpegdec_add_pixel_aspect_ratio), (gst_ffmpegdec_negotiate),
(gst_ffmpegdec_do_qos), (gst_ffmpegdec_video_frame),
(gst_ffmpegdec_audio_frame), (gst_ffmpegdec_frame),
(gst_ffmpegdec_flush_pcache), (gst_ffmpegdec_sink_event),
(gst_ffmpegdec_chain):
Split out audio and video frame decoding.
Added dropping/clipping of decoded buffers.
Ran gst-indent on code.
Small non-invasive code cleanups.
2006-06-02 Michael Smith <msmith@fluendo.com>
* ext/ffmpeg/gstffmpegdemux.c: (gst_ffmpegdemux_register):

View file

@ -67,7 +67,7 @@ struct _GstFFMpegDec
} audio;
} format;
gboolean waiting_for_key;
guint64 next_ts, synctime;
guint64 next_ts;
/* parsing */
AVCodecParserContext *pctx;
@ -337,10 +337,10 @@ gst_ffmpegdec_query (GstPad * pad, GstQuery * query)
res = gst_pad_query (peer, query);
gst_object_unref (peer);
}
#if 0
{
GstFormat bfmt;
bfmt = GST_FORMAT_BYTES;
/* ok, do bitrate calc... */
@ -362,7 +362,8 @@ gst_ffmpegdec_query (GstPad * pad, GstQuery * query)
/* FIXME, make me ATOMIC */
static void
gst_ffmpegdec_update_qos (GstFFMpegDec *ffmpegdec, gdouble proportion, GstClockTime time)
gst_ffmpegdec_update_qos (GstFFMpegDec * ffmpegdec, gdouble proportion,
GstClockTime time)
{
GST_OBJECT_LOCK (ffmpegdec);
ffmpegdec->proportion = proportion;
@ -377,7 +378,8 @@ gst_ffmpegdec_reset_qos (GstFFMpegDec *ffmpegdec)
}
static void
gst_ffmpegdec_read_qos (GstFFMpegDec *ffmpegdec, gdouble *proportion, GstClockTime *time)
gst_ffmpegdec_read_qos (GstFFMpegDec * ffmpegdec, gdouble * proportion,
GstClockTime * time)
{
GST_OBJECT_LOCK (ffmpegdec);
*proportion = ffmpegdec->proportion;
@ -474,11 +476,9 @@ gst_ffmpegdec_open (GstFFMpegDec * ffmpegdec)
GST_LOG_OBJECT (ffmpegdec, "Opened ffmpeg codec %s", oclass->in_plugin->name);
/* open a parser if we can - exclude mpeg4, because it is already
* framed (divx), mp3 because it doesn't work (?) and mjpeg because
* of $(see mpeg4)... */
if (oclass->in_plugin->id != CODEC_ID_MPEG4 &&
oclass->in_plugin->id != CODEC_ID_MJPEG &&
/* open a parser if we can - exclude mp3 because it doesn't work (?),
* and mjpeg because ... */
if (oclass->in_plugin->id != CODEC_ID_MJPEG &&
oclass->in_plugin->id != CODEC_ID_MP3 &&
oclass->in_plugin->id != CODEC_ID_H264) {
ffmpegdec->pctx = av_parser_init (oclass->in_plugin->id);
@ -498,7 +498,6 @@ gst_ffmpegdec_open (GstFFMpegDec * ffmpegdec)
break;
}
ffmpegdec->next_ts = 0;
ffmpegdec->synctime = GST_CLOCK_TIME_NONE;
ffmpegdec->last_buffer = NULL;
/* FIXME, reset_qos holds the LOCK */
ffmpegdec->proportion = 0.0;
@ -653,8 +652,9 @@ gst_ffmpegdec_get_buffer (AVCodecContext * context, AVFrame * picture)
return avcodec_default_get_buffer (context, picture);
}
if (gst_pad_alloc_buffer_and_set_caps (ffmpegdec->srcpad, GST_BUFFER_OFFSET_NONE,
bufsize, GST_PAD_CAPS (ffmpegdec->srcpad), &buf) != GST_FLOW_OK)
if (gst_pad_alloc_buffer_and_set_caps (ffmpegdec->srcpad,
GST_BUFFER_OFFSET_NONE, bufsize, GST_PAD_CAPS (ffmpegdec->srcpad),
&buf) != GST_FLOW_OK)
return -1;
ffmpegdec->last_buffer = buf;
@ -726,7 +726,8 @@ gst_ffmpegdec_add_pixel_aspect_ratio (GstFFMpegDec * ffmpegdec,
demuxer_num = gst_value_get_fraction_numerator (ffmpegdec->par);
demuxer_denom = gst_value_get_fraction_denominator (ffmpegdec->par);
demuxer_par_set = TRUE;
GST_DEBUG_OBJECT (ffmpegdec, "Demuxer PAR: %d:%d", demuxer_num, demuxer_denom);
GST_DEBUG_OBJECT (ffmpegdec, "Demuxer PAR: %d:%d", demuxer_num,
demuxer_denom);
}
if (ffmpegdec->context->sample_aspect_ratio.num &&
@ -734,7 +735,8 @@ gst_ffmpegdec_add_pixel_aspect_ratio (GstFFMpegDec * ffmpegdec,
decoder_num = ffmpegdec->context->sample_aspect_ratio.num;
decoder_denom = ffmpegdec->context->sample_aspect_ratio.den;
decoder_par_set = TRUE;
GST_DEBUG_OBJECT (ffmpegdec, "Decoder PAR: %d:%d", decoder_num, decoder_denom);
GST_DEBUG_OBJECT (ffmpegdec, "Decoder PAR: %d:%d", decoder_num,
decoder_denom);
}
GST_OBJECT_UNLOCK (ffmpegdec);
@ -761,24 +763,27 @@ gst_ffmpegdec_add_pixel_aspect_ratio (GstFFMpegDec * ffmpegdec,
/* fall through and use decoder pixel aspect ratio */
use_decoder_par:
{
GST_DEBUG_OBJECT (ffmpegdec, "Setting decoder provided pixel-aspect-ratio of %u:%u",
decoder_num, decoder_denom);
gst_structure_set (s, "pixel-aspect-ratio", GST_TYPE_FRACTION,
decoder_num, decoder_denom, NULL);
GST_DEBUG_OBJECT (ffmpegdec,
"Setting decoder provided pixel-aspect-ratio of %u:%u", decoder_num,
decoder_denom);
gst_structure_set (s, "pixel-aspect-ratio", GST_TYPE_FRACTION, decoder_num,
decoder_denom, NULL);
return;
}
use_demuxer_par:
{
GST_DEBUG_OBJECT (ffmpegdec, "Setting demuxer provided pixel-aspect-ratio of %u:%u",
demuxer_num, demuxer_denom);
gst_structure_set (s, "pixel-aspect-ratio", GST_TYPE_FRACTION,
demuxer_num, demuxer_denom, NULL);
GST_DEBUG_OBJECT (ffmpegdec,
"Setting demuxer provided pixel-aspect-ratio of %u:%u", demuxer_num,
demuxer_denom);
gst_structure_set (s, "pixel-aspect-ratio", GST_TYPE_FRACTION, demuxer_num,
demuxer_denom, NULL);
return;
}
no_par:
{
GST_DEBUG_OBJECT (ffmpegdec, "Neither demuxer nor codec provide a pixel-aspect-ratio");
GST_DEBUG_OBJECT (ffmpegdec,
"Neither demuxer nor codec provide a pixel-aspect-ratio");
return;
}
}
@ -816,7 +821,8 @@ gst_ffmpegdec_negotiate (GstFFMpegDec * ffmpegdec)
ffmpegdec->context->sample_rate &&
ffmpegdec->format.audio.channels == ffmpegdec->context->channels)
return TRUE;
GST_DEBUG_OBJECT (ffmpegdec, "Renegotiating audio from %dHz@%dchannels to %dHz@%dchannels",
GST_DEBUG_OBJECT (ffmpegdec,
"Renegotiating audio from %dHz@%dchannels to %dHz@%dchannels",
ffmpegdec->format.audio.samplerate, ffmpegdec->format.audio.channels,
ffmpegdec->context->sample_rate, ffmpegdec->context->channels);
ffmpegdec->format.audio.samplerate = ffmpegdec->context->sample_rate;
@ -876,7 +882,8 @@ caps_failed:
* entirely.
*/
static gboolean
gst_ffmpegdec_do_qos (GstFFMpegDec *ffmpegdec, GstClockTime timestamp, gboolean *mode_switch)
gst_ffmpegdec_do_qos (GstFFMpegDec * ffmpegdec, GstClockTime timestamp,
gboolean * mode_switch)
{
GstClockTimeDiff diff;
gdouble proportion;
@ -914,16 +921,15 @@ gst_ffmpegdec_do_qos (GstFFMpegDec *ffmpegdec, GstClockTime timestamp, gboolean
* speed up again when we were slow. */
if (proportion < 0.4 && diff < 0) {
goto normal_mode;
}
else {
} else {
/* if we're more than two seconds late, switch to the next keyframe */
/* FIXME, let the demuxer decide what's the best since we might be dropping
* a lot of frames when the keyframe is far away or we even might not get a new
* keyframe at all.. */
if (diff > ((GstClockTimeDiff) GST_SECOND * 2) && !ffmpegdec->waiting_for_key) {
if (diff > ((GstClockTimeDiff) GST_SECOND * 2)
&& !ffmpegdec->waiting_for_key) {
goto skip_to_keyframe;
}
else if (diff >= 0) {
} else if (diff >= 0) {
/* we're too slow, try to speed up */
if (ffmpegdec->waiting_for_key) {
/* we were waiting for a keyframe, that's ok */
@ -955,7 +961,8 @@ skip_to_keyframe:
ffmpegdec->context->hurry_up = 1;
ffmpegdec->waiting_for_key = TRUE;
*mode_switch = TRUE;
GST_DEBUG_OBJECT (ffmpegdec, "QOS: keyframe, %"G_GINT64_FORMAT" > GST_SECOND/2", diff);
GST_DEBUG_OBJECT (ffmpegdec,
"QOS: keyframe, %" G_GINT64_FORMAT " > GST_SECOND/2", diff);
/* we can skip the current frame */
return FALSE;
}
@ -964,55 +971,66 @@ hurry_up:
if (ffmpegdec->context->hurry_up != 1) {
ffmpegdec->context->hurry_up = 1;
*mode_switch = TRUE;
GST_DEBUG_OBJECT (ffmpegdec, "QOS: hurry up, diff %"G_GINT64_FORMAT" >= 0", diff);
GST_DEBUG_OBJECT (ffmpegdec,
"QOS: hurry up, diff %" G_GINT64_FORMAT " >= 0", diff);
}
return TRUE;
}
}
/* gst_ffmpegdec_[video|audio]_frame:
* ffmpegdec:
* data: pointer to the data to decode
* size: size of data in bytes
* inbuf: incoming buffer. Must have valid timestamp.
* The incoming buffer can be NULL, but only used for EOS draining.
* outbuf: outgoing buffer. Different from NULL ONLY if it contains decoded data.
* ret: Return flow.
*
* Returns: number of bytes used in decoding. The check for successful decode is
* outbuf being non-NULL.
*/
static gint
gst_ffmpegdec_frame (GstFFMpegDec * ffmpegdec,
guint8 * data, guint size, gint * got_data, guint64 * in_ts,
GstBuffer * inbuf, GstFlowReturn * ret)
{
GstFFMpegDecClass *oclass;
GstBuffer *outbuf = NULL;
gint have_data = 0, len = 0;
if (ffmpegdec->context->codec == NULL)
return -1;
GST_LOG_OBJECT (ffmpegdec,
"data:%p, size:%d, *in_ts:%" GST_TIME_FORMAT " inbuf:%p inbuf.ts:%"
GST_TIME_FORMAT, data, size, GST_TIME_ARGS (*in_ts), inbuf,
GST_TIME_ARGS ((inbuf) ? GST_BUFFER_TIMESTAMP (inbuf) : 0));
ffmpegdec->context->frame_number++;
oclass = (GstFFMpegDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec));
switch (oclass->in_plugin->type) {
case CODEC_TYPE_VIDEO:
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;
*ret = GST_FLOW_OK;
*outbuf = NULL;
ffmpegdec->context->opaque = ffmpegdec;
/* run QoS code, returns FALSE if we can skip decoding this
* frame entirely. */
if (!gst_ffmpegdec_do_qos (ffmpegdec, *in_ts, &mode_switch)) {
have_data = 0;
break;
}
if (inbuf)
if (!gst_ffmpegdec_do_qos (ffmpegdec, GST_BUFFER_TIMESTAMP (inbuf),
&mode_switch))
goto beach;
ffmpegdec->picture->pict_type = -1; /* in case we skip frames */
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));
/* 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))
@ -1036,60 +1054,23 @@ gst_ffmpegdec_frame (GstFFMpegDec * ffmpegdec,
ffmpegdec->waiting_for_key = FALSE;
} else {
GST_WARNING_OBJECT (ffmpegdec, "Dropping non-keyframe (seek/init)");
have_data = 0;
break;
goto beach;
}
}
/* note that ffmpeg sometimes gets the FPS wrong.
* For B-frame containing movies, we get all pictures delayed
* except for the I frames, so we synchronize only on I frames
* and keep an internal counter based on FPS for the others. */
if (!(oclass->in_plugin->capabilities & CODEC_CAP_DELAY) ||
((iskeyframe || !GST_CLOCK_TIME_IS_VALID (ffmpegdec->next_ts)) &&
GST_CLOCK_TIME_IS_VALID (*in_ts))) {
GST_LOG_OBJECT (ffmpegdec, "setting next_ts to %" GST_TIME_FORMAT,
GST_TIME_ARGS (*in_ts));
ffmpegdec->next_ts = *in_ts;
*in_ts = GST_CLOCK_TIME_NONE;
}
/* precise seeking.... */
if (GST_CLOCK_TIME_IS_VALID (ffmpegdec->synctime)) {
if (ffmpegdec->next_ts >= ffmpegdec->synctime) {
ffmpegdec->synctime = GST_CLOCK_TIME_NONE;
} else {
GST_WARNING_OBJECT (ffmpegdec,
"Dropping frame for synctime %" GST_TIME_FORMAT
", expected(next_ts) %" GST_TIME_FORMAT,
GST_TIME_ARGS (ffmpegdec->synctime),
GST_TIME_ARGS (ffmpegdec->next_ts));
if (ffmpegdec->picture->opaque != NULL) {
outbuf = (GstBuffer *) ffmpegdec->picture->opaque;
gst_buffer_unref (outbuf);
}
have_data = 0;
/* don´t break here! Timestamps are updated below */
}
}
if (ffmpegdec->waiting_for_key && !iskeyframe) {
have_data = 0;
} else if (len >= 0 && have_data > 0) {
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... */
/* BUFFER CREATION */
if (ffmpegdec->picture->opaque != NULL) {
outbuf = (GstBuffer *) ffmpegdec->picture->opaque;
if (outbuf == ffmpegdec->last_buffer)
*outbuf = (GstBuffer *) ffmpegdec->picture->opaque;
if (*outbuf == ffmpegdec->last_buffer)
ffmpegdec->last_buffer = NULL;
} else {
AVPicture pic;
gint fsize =
gst_ffmpeg_avpicture_get_size (ffmpegdec->context->pix_fmt,
gint fsize = gst_ffmpeg_avpicture_get_size (ffmpegdec->context->pix_fmt,
ffmpegdec->context->width, ffmpegdec->context->height);
if (!gst_ffmpegdec_negotiate (ffmpegdec))
@ -1099,21 +1080,22 @@ gst_ffmpegdec_frame (GstFFMpegDec * ffmpegdec,
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)
return -1;
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));
*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),
gst_ffmpeg_avpicture_fill (&pic, GST_BUFFER_DATA (*outbuf),
ffmpegdec->context->pix_fmt,
ffmpegdec->context->width, ffmpegdec->context->height);
@ -1126,140 +1108,319 @@ gst_ffmpegdec_frame (GstFFMpegDec * ffmpegdec,
ffmpegdec->context->width, ffmpegdec->context->height);
}
ffmpegdec->waiting_for_key = FALSE;
ffmpegdec->waiting_for_key = FALSE;
if (!iskeyframe) {
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
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,
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)));
gst_buffer_stamp (outbuf, inbuf);
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) = ffmpegdec->next_ts;
GST_BUFFER_TIMESTAMP (*outbuf) = dec_time;
if (ffmpegdec->context->time_base.num != 0 &&
ffmpegdec->context->time_base.den != 0) {
GST_BUFFER_DURATION (outbuf) =
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)
GST_BUFFER_DURATION (*outbuf) += GST_BUFFER_DURATION (*outbuf)
* ffmpegdec->picture->repeat_pict / 2;
GST_DEBUG_OBJECT (ffmpegdec,
"advancing next_ts by duration of %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)));
ffmpegdec->next_ts += GST_BUFFER_DURATION (outbuf);
} else {
GST_DEBUG_OBJECT (ffmpegdec, "setting next_ts to NONE");
ffmpegdec->next_ts = GST_CLOCK_TIME_NONE;
}
}
GST_LOG_OBJECT (ffmpegdec, "outgoing timestamp %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)));
} else if (ffmpegdec->picture->pict_type != -1 &&
oclass->in_plugin->capabilities & CODEC_CAP_DELAY) {
/* update time for skip-frame */
if ((have_data == 0) ||
((iskeyframe || !GST_CLOCK_TIME_IS_VALID (ffmpegdec->next_ts))
&& GST_CLOCK_TIME_IS_VALID (*in_ts))) {
GST_DEBUG_OBJECT (ffmpegdec, "setting next_ts to *in_ts");
ffmpegdec->next_ts = *in_ts;
*in_ts = GST_CLOCK_TIME_NONE;
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (*outbuf)));
}
if (ffmpegdec->context->time_base.num != 0 &&
ffmpegdec->context->time_base.den != 0) {
guint64 dur = GST_SECOND *
ffmpegdec->context->time_base.num /
ffmpegdec->context->time_base.den;
/* Take repeat_pict into account */
dur += dur * ffmpegdec->picture->repeat_pict / 2;
GST_DEBUG_OBJECT (ffmpegdec,
"Advancing next_ts by dur:%" GST_TIME_FORMAT,
GST_TIME_ARGS (dur));
ffmpegdec->next_ts += dur;
} else {
GST_DEBUG_OBJECT (ffmpegdec, "setting next_ts to NONE");
ffmpegdec->next_ts = GST_CLOCK_TIME_NONE;
}
}
/* 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)
GST_BUFFER_SIZE (outbuf) -= AVPALETTE_SIZE;
break;
if (ffmpegdec->context->palctrl != NULL && *outbuf != NULL)
GST_BUFFER_SIZE (*outbuf) -= AVPALETTE_SIZE;
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));
}
case CODEC_TYPE_AUDIO:
} 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");
}
beach:
return len;
}
static gint
gst_ffmpegdec_audio_frame (GstFFMpegDec * ffmpegdec,
guint8 * data, guint size,
GstBuffer * inbuf, GstBuffer ** outbuf, GstFlowReturn * ret)
{
gint len = -1;
gint have_data;
GST_DEBUG_OBJECT (ffmpegdec,
"size:%d, inbuf.ts %" GST_TIME_FORMAT " , ffmpegdec->next_ts:%"
GST_TIME_FORMAT, size, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)),
GST_TIME_ARGS (ffmpegdec->next_ts));
/* outgoing buffer */
if (!ffmpegdec->last_buffer)
outbuf = gst_buffer_new_and_alloc (AVCODEC_MAX_AUDIO_FRAME_SIZE);
*outbuf = gst_buffer_new_and_alloc (AVCODEC_MAX_AUDIO_FRAME_SIZE);
else {
outbuf = ffmpegdec->last_buffer;
*outbuf = ffmpegdec->last_buffer;
ffmpegdec->last_buffer = NULL;
}
len = avcodec_decode_audio (ffmpegdec->context,
(int16_t *) GST_BUFFER_DATA (outbuf), &have_data, data, size);
(int16_t *) GST_BUFFER_DATA (*outbuf), &have_data, data, size);
GST_DEBUG_OBJECT (ffmpegdec,
"Decode audio: len=%d, have_data=%d", len, have_data);
if (len >= 0 && have_data > 0) {
GST_DEBUG_OBJECT (ffmpegdec, "Creating output buffer");
if (!gst_ffmpegdec_negotiate (ffmpegdec)) {
gst_buffer_unref (outbuf);
return -1;
gst_buffer_unref (*outbuf);
*outbuf = NULL;
len = -1;
goto beach;
}
GST_BUFFER_SIZE (outbuf) = have_data;
if (GST_CLOCK_TIME_IS_VALID (*in_ts)) {
ffmpegdec->next_ts = *in_ts;
}
GST_BUFFER_TIMESTAMP (outbuf) = ffmpegdec->next_ts;
GST_BUFFER_DURATION (outbuf) = (have_data * GST_SECOND) /
/* Buffer size timestamp and duration */
GST_BUFFER_SIZE (*outbuf) = have_data;
GST_BUFFER_TIMESTAMP (*outbuf) = ffmpegdec->next_ts;
GST_BUFFER_DURATION (*outbuf) = (have_data * GST_SECOND) /
(2 * ffmpegdec->context->channels * ffmpegdec->context->sample_rate);
GST_DEBUG_OBJECT (ffmpegdec,
"Buffer created. Size:%d , timestamp:%" GST_TIME_FORMAT " , duration:%"
GST_TIME_FORMAT, GST_BUFFER_SIZE (*outbuf),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (*outbuf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (*outbuf)));
ffmpegdec->next_ts =
GST_BUFFER_TIMESTAMP (*outbuf) + GST_BUFFER_DURATION (*outbuf);
/* clipping/dropping of outgoing audio buffers */
if (ffmpegdec->segment.format == GST_FORMAT_TIME) {
gint64 start, stop, cstart, cstop, diff;
start = GST_BUFFER_TIMESTAMP (*outbuf);
stop = ffmpegdec->next_ts;
if (!gst_segment_clip (&ffmpegdec->segment, GST_FORMAT_TIME, start, stop,
&cstart, &cstop)) {
GST_DEBUG_OBJECT (ffmpegdec, "Dropping buffer outside segment");
gst_buffer_unref (*outbuf);
*outbuf = NULL;
} else {
diff = cstart - start;
if (diff > 0) {
GST_BUFFER_TIMESTAMP (*outbuf) = cstart;
GST_BUFFER_DURATION (*outbuf) -= diff;
/* time -> bytes *//* FIXME : should go to samples first */
diff =
gst_util_uint64_scale (diff,
(2 * ffmpegdec->context->channels *
ffmpegdec->context->sample_rate);
ffmpegdec->next_ts += GST_BUFFER_DURATION (outbuf);
if (GST_CLOCK_TIME_IS_VALID (*in_ts))
*in_ts += GST_BUFFER_DURATION (outbuf);
ffmpegdec->context->sample_rate), GST_SECOND);
GST_BUFFER_DATA (*outbuf) += diff;
GST_BUFFER_SIZE (*outbuf) -= diff;
}
diff = stop - cstop;
if (diff > 0) {
GST_BUFFER_DURATION (*outbuf) -= diff;
diff =
gst_util_uint64_scale (diff,
(2 * ffmpegdec->context->channels *
ffmpegdec->context->sample_rate), GST_SECOND);
GST_BUFFER_SIZE (*outbuf) -= diff;
}
GST_DEBUG_OBJECT (ffmpegdec,
"clipped buffer to %" GST_TIME_FORMAT " / duration:%"
GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (*outbuf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (*outbuf)));
}
}
} else if (len > 0 && have_data == 0) {
/* cache output, because it may be used for caching (in-place) */
ffmpegdec->last_buffer = outbuf;
ffmpegdec->last_buffer = *outbuf;
*outbuf = NULL;
} else {
gst_buffer_unref (outbuf);
gst_buffer_unref (*outbuf);
*outbuf = NULL;
}
beach:
return len;
}
/* gst_ffmpegdec_frame:
* ffmpegdec:
* data: pointer to the data to decode
* size: size of data in bytes
* got_data: 0 if no data was decoded, != 0 otherwise.
* inbuf: incoming buffer. The incoming buffer should be properly timestamped.
* The incoming buffer can be NULL, but only used for EOS draining.
* ret: GstFlowReturn to return in the chain function
*
* Decode the given frame and pushes it downstream.
*
* Returns: Number of bytes used in decoding, -1 on error/failure.
*/
static gint
gst_ffmpegdec_frame (GstFFMpegDec * ffmpegdec,
guint8 * data, guint size, gint * got_data,
GstBuffer * inbuf, GstFlowReturn * ret)
{
GstFFMpegDecClass *oclass =
(GstFFMpegDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec));
GstBuffer *outbuf = NULL;
gint have_data = 0, len = 0;
if (ffmpegdec->context->codec == NULL)
return -1;
GST_LOG_OBJECT (ffmpegdec,
"data:%p, size:%d, inbuf:%p inbuf.ts:%"
GST_TIME_FORMAT, data, size, inbuf,
GST_TIME_ARGS ((inbuf) ? GST_BUFFER_TIMESTAMP (inbuf) : 0));
*ret = GST_FLOW_OK;
ffmpegdec->context->frame_number++;
switch (oclass->in_plugin->type) {
case CODEC_TYPE_VIDEO:
len =
gst_ffmpegdec_video_frame (ffmpegdec, data, size, inbuf, &outbuf,
ret);
break;
case CODEC_TYPE_AUDIO:
len =
gst_ffmpegdec_audio_frame (ffmpegdec, data, size, inbuf, &outbuf,
ret);
break;
default:
g_assert (0);
break;
}
if (outbuf) {
have_data = 1;
}
if (len < 0 || have_data < 0) {
GST_ERROR_OBJECT (ffmpegdec,
GST_WARNING_OBJECT (ffmpegdec,
"ffdec_%s: decoding error (len: %d, have_data: %d)",
oclass->in_plugin->name, len, have_data);
*got_data = 0;
return len;
goto beach;
} else if (len == 0 && have_data == 0) {
*got_data = 0;
return 0;
goto beach;
} else {
/* this is where I lost my last clue on ffmpeg... */
*got_data = 1; //(ffmpegdec->pctx || have_data) ? 1 : 0;
}
if (have_data) {
GST_LOG_OBJECT (ffmpegdec, "Decoded data, now pushing with timestamp %"
GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)));
if (outbuf) {
GST_LOG_OBJECT (ffmpegdec,
"Decoded data, now pushing buffer with timestamp %" GST_TIME_FORMAT
" and duration %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)));
gst_buffer_set_caps (outbuf, GST_PAD_CAPS (ffmpegdec->srcpad));
*ret = gst_pad_push (ffmpegdec->srcpad, outbuf);
} else {
GST_DEBUG_OBJECT (ffmpegdec, "We didn't get a decoded buffer");
}
beach:
return len;
}
@ -1298,16 +1459,18 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event)
if (oclass->in_plugin->capabilities & CODEC_CAP_DELAY) {
gint have_data, len, try = 0;
GST_LOG_OBJECT (ffmpegdec,
"codec has delay capabilities, calling until ffmpeg has drained everything");
do {
GstFlowReturn ret;
len = gst_ffmpegdec_frame (ffmpegdec, NULL, 0, &have_data,
&ffmpegdec->next_ts, NULL, &ret);
NULL, &ret);
if (len < 0 || have_data == 0)
break;
} while (try++ < 10);
}
ret = gst_pad_push_event (ffmpegdec->srcpad, event);
break;
}
case GST_EVENT_FLUSH_STOP:{
@ -1318,7 +1481,6 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event)
gst_ffmpegdec_flush_pcache (ffmpegdec);
ffmpegdec->waiting_for_key = TRUE;
gst_segment_init (&ffmpegdec->segment, GST_FORMAT_TIME);
ret = gst_pad_push_event (ffmpegdec->srcpad, event);
break;
}
case GST_EVENT_NEWSEGMENT:{
@ -1351,14 +1513,11 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event)
/* convert values to TIME */
if (start != -1)
start = gst_util_uint64_scale_int (start, GST_SECOND,
bit_rate);
start = gst_util_uint64_scale_int (start, GST_SECOND, bit_rate);
if (stop != -1)
stop = gst_util_uint64_scale_int (stop, GST_SECOND,
bit_rate);
stop = gst_util_uint64_scale_int (stop, GST_SECOND, bit_rate);
if (time != -1)
time = gst_util_uint64_scale_int (time, GST_SECOND,
bit_rate);
time = gst_util_uint64_scale_int (time, GST_SECOND, bit_rate);
/* unref old event */
gst_event_unref (event);
@ -1387,7 +1546,6 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event)
/* FIXME, newsegment does not define the next timestamp */
ffmpegdec->next_ts = start;
ffmpegdec->synctime = start;
/* FIXME, newsegment does not mean a DISCONT */
if (ffmpegdec->opened) {
@ -1396,14 +1554,14 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event)
ffmpegdec->waiting_for_key = TRUE;
/* and push segment downstream */
ret = gst_pad_push_event (ffmpegdec->srcpad, event);
break;
}
default:
ret = gst_pad_push_event (ffmpegdec->srcpad, event);
break;
}
ret = gst_pad_push_event (ffmpegdec->srcpad, event);
done:
gst_object_unref (ffmpegdec);
@ -1438,6 +1596,7 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
guint8 *bdata, *data;
gint bsize, size, len, have_data;
guint64 in_ts;
guint64 next_ts = GST_CLOCK_TIME_NONE;
GstFlowReturn ret = GST_FLOW_OK;
ffmpegdec = (GstFFMpegDec *) (GST_PAD_PARENT (pad));
@ -1447,6 +1606,8 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
if (G_UNLIKELY (!ffmpegdec->opened))
goto not_negotiated;
GST_DEBUG_OBJECT (ffmpegdec, "chain");
if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DISCONT))) {
GST_DEBUG_OBJECT (ffmpegdec, "received DISCONT");
gst_ffmpegdec_flush_pcache (ffmpegdec);
@ -1459,7 +1620,8 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
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 %" GST_TIME_FORMAT,
GST_TIME_ARGS (in_ts));
ffmpegdec->waiting_for_key = FALSE;
}
@ -1471,16 +1633,9 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
/* parse cache joining */
if (ffmpegdec->pcache) {
GstClockTime timestamp = GST_CLOCK_TIME_NONE;
GstClockTime timestamp = GST_BUFFER_TIMESTAMP (ffmpegdec->pcache);
GstClockTime duration = GST_CLOCK_TIME_NONE;
/* decide on resulting timestamp/duration before we give away our ref */
/* since the cache is all data that did not result in an outgoing frame,
* we should timestamp with the new incoming buffer. This is probably
* not entirely correct though, but better than nothing. */
if (GST_BUFFER_TIMESTAMP_IS_VALID (inbuf))
timestamp = GST_BUFFER_TIMESTAMP (inbuf);
if (GST_BUFFER_DURATION_IS_VALID (ffmpegdec->pcache)
&& GST_BUFFER_DURATION_IS_VALID (inbuf))
duration = GST_BUFFER_DURATION (ffmpegdec->pcache) +
@ -1491,12 +1646,17 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * 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 (timestamp),
GST_TIME_ARGS (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));
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;
}
/* workarounds, functions write to buffers:
* libavcodec/svq1.c:svq1_decode_frame writes to the given buffer.
@ -1513,24 +1673,34 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
}
do {
GST_LOG_OBJECT (ffmpegdec, "bdata:%p , bsize:%d", bdata, bsize);
/* parse, if at all possible */
if (ffmpegdec->pctx) {
gint res;
gint64 ffpts;
dump_parser_context (ffmpegdec->pctx, ffmpegdec->context);
GST_LOG_OBJECT (ffmpegdec,
"Calling av_parser_parse with ts:%" GST_TIME_FORMAT,
GST_TIME_ARGS (in_ts));
ffpts = gst_ffmpeg_time_gst_to_ff (in_ts, ffmpegdec->context->time_base);
res = av_parser_parse (ffmpegdec->pctx, ffmpegdec->context,
&data, &size, bdata, bsize, ffpts, ffpts);
GST_LOG_OBJECT (ffmpegdec, "Parsed video frame, res=%d, size=%d",
res, size);
in_ts = gst_ffmpeg_time_ff_to_gst (ffmpegdec->pctx->pts,
ffmpegdec->context->time_base);
if (res == 0 || size == 0)
GST_LOG_OBJECT (ffmpegdec,
"Parsed video frame, res=%d, size=%d, data=%p, in_ts:%"
GST_TIME_FORMAT, res, size, data, GST_TIME_ARGS (in_ts));
dump_parser_context (ffmpegdec->pctx, ffmpegdec->context);
if (res == 0 || size == 0) {
GST_LOG_OBJECT (ffmpegdec,
"parser returned null res or size, breaking");
break;
else {
} else {
bsize -= res;
bdata += res;
}
@ -1540,7 +1710,7 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
}
if ((len = gst_ffmpegdec_frame (ffmpegdec, data, size,
&have_data, &in_ts, inbuf, &ret)) < 0 || ret != GST_FLOW_OK)
&have_data, inbuf, &ret)) < 0 || ret != GST_FLOW_OK)
break;
if (!ffmpegdec->pctx) {
@ -1549,18 +1719,28 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
}
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;
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);
/* keep left-over */
if ((ffmpegdec->pctx || oclass->in_plugin->id == CODEC_ID_MP3) && bsize > 0) {
GST_LOG_OBJECT (ffmpegdec, "Keeping %d bytes of data", bsize);
GST_LOG_OBJECT (ffmpegdec,
"Keeping %d bytes of data with timestamp %" GST_TIME_FORMAT, bsize,
GST_TIME_ARGS (in_ts));
ffmpegdec->pcache = gst_buffer_create_sub (inbuf,
GST_BUFFER_SIZE (inbuf) - bsize, bsize);
/* we keep timestamp, even though all we really know is that the correct
* timestamp is not below the one from inbuf */
GST_BUFFER_TIMESTAMP (ffmpegdec->pcache) = GST_BUFFER_TIMESTAMP (inbuf);
GST_BUFFER_TIMESTAMP (ffmpegdec->pcache) = in_ts;
} else if (bsize > 0) {
GST_DEBUG_OBJECT (ffmpegdec, "Dropping %d bytes of data", bsize);
}