From ef408ada576bbb9c927efc3ea434a5c5915f1ab2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 19 Nov 2012 12:57:19 +0100 Subject: [PATCH] avauddec: Port to audio base classes --- ext/libav/gstavauddec.c | 788 +++++++--------------------------------------- ext/libav/gstavauddec.h | 54 +--- ext/libav/gstavcodecmap.c | 42 +-- ext/libav/gstavcodecmap.h | 2 + 4 files changed, 148 insertions(+), 738 deletions(-) diff --git a/ext/libav/gstavauddec.c b/ext/libav/gstavauddec.c index 66e59dc..2609f22 100644 --- a/ext/libav/gstavauddec.c +++ b/ext/libav/gstavauddec.c @@ -1,5 +1,7 @@ /* GStreamer * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2012> Collabora Ltd. + * Author: Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -35,50 +37,18 @@ GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE); -#define GST_TS_INFO_NONE &ts_info_none -static const GstTSInfo ts_info_none = { -1, -1, -1, -1 }; - -static const GstTSInfo * -gst_ts_info_store (GstFFMpegAudDec * dec, GstClockTime dts, GstClockTime pts, - GstClockTime duration, gint64 offset) -{ - gint idx = dec->ts_idx; - dec->ts_info[idx].idx = idx; - dec->ts_info[idx].dts = dts; - dec->ts_info[idx].pts = pts; - dec->ts_info[idx].duration = duration; - dec->ts_info[idx].offset = offset; - dec->ts_idx = (idx + 1) & MAX_TS_MASK; - - return &dec->ts_info[idx]; -} - -static const GstTSInfo * -gst_ts_info_get (GstFFMpegAudDec * dec, gint idx) -{ - if (G_UNLIKELY (idx < 0 || idx > MAX_TS_MASK)) - return GST_TS_INFO_NONE; - - return &dec->ts_info[idx]; -} - /* A number of function prototypes are given so we can refer to them later. */ static void gst_ffmpegauddec_base_init (GstFFMpegAudDecClass * klass); static void gst_ffmpegauddec_class_init (GstFFMpegAudDecClass * klass); static void gst_ffmpegauddec_init (GstFFMpegAudDec * ffmpegdec); static void gst_ffmpegauddec_finalize (GObject * object); -static gboolean gst_ffmpegauddec_setcaps (GstFFMpegAudDec * ffmpegdec, +static gboolean gst_ffmpegauddec_stop (GstAudioDecoder * decoder); +static void gst_ffmpegauddec_flush (GstAudioDecoder * decoder, gboolean hard); +static gboolean gst_ffmpegauddec_set_format (GstAudioDecoder * decoder, GstCaps * caps); -static gboolean gst_ffmpegauddec_sink_event (GstPad * pad, GstObject * parent, - GstEvent * event); -static gboolean gst_ffmpegauddec_sink_query (GstPad * pad, GstObject * parent, - GstQuery * query); -static GstFlowReturn gst_ffmpegauddec_chain (GstPad * pad, GstObject * parent, - GstBuffer * buf); - -static GstStateChangeReturn gst_ffmpegauddec_change_state (GstElement * element, - GstStateChange transition); +static GstFlowReturn gst_ffmpegauddec_handle_frame (GstAudioDecoder * decoder, + GstBuffer * inbuf); static gboolean gst_ffmpegauddec_negotiate (GstFFMpegAudDec * ffmpegdec, gboolean force); @@ -144,43 +114,29 @@ static void gst_ffmpegauddec_class_init (GstFFMpegAudDecClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GstAudioDecoderClass *gstaudiodecoder_class = GST_AUDIO_DECODER_CLASS (klass); parent_class = g_type_class_peek_parent (klass); gobject_class->finalize = gst_ffmpegauddec_finalize; - gstelement_class->change_state = gst_ffmpegauddec_change_state; + gstaudiodecoder_class->stop = GST_DEBUG_FUNCPTR (gst_ffmpegauddec_stop); + gstaudiodecoder_class->set_format = + GST_DEBUG_FUNCPTR (gst_ffmpegauddec_set_format); + gstaudiodecoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_ffmpegauddec_handle_frame); + gstaudiodecoder_class->flush = GST_DEBUG_FUNCPTR (gst_ffmpegauddec_flush); } static void gst_ffmpegauddec_init (GstFFMpegAudDec * ffmpegdec) { - GstFFMpegAudDecClass *oclass; - - oclass = (GstFFMpegAudDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); - - /* setup pads */ - ffmpegdec->sinkpad = gst_pad_new_from_template (oclass->sinktempl, "sink"); - gst_pad_set_query_function (ffmpegdec->sinkpad, - GST_DEBUG_FUNCPTR (gst_ffmpegauddec_sink_query)); - gst_pad_set_event_function (ffmpegdec->sinkpad, - GST_DEBUG_FUNCPTR (gst_ffmpegauddec_sink_event)); - gst_pad_set_chain_function (ffmpegdec->sinkpad, - GST_DEBUG_FUNCPTR (gst_ffmpegauddec_chain)); - gst_element_add_pad (GST_ELEMENT (ffmpegdec), ffmpegdec->sinkpad); - - ffmpegdec->srcpad = gst_pad_new_from_template (oclass->srctempl, "src"); - gst_pad_use_fixed_caps (ffmpegdec->srcpad); - gst_element_add_pad (GST_ELEMENT (ffmpegdec), ffmpegdec->srcpad); - /* some ffmpeg data */ ffmpegdec->context = avcodec_alloc_context (); - ffmpegdec->pctx = NULL; - ffmpegdec->pcache = NULL; ffmpegdec->opened = FALSE; - gst_segment_init (&ffmpegdec->segment, GST_FORMAT_TIME); + gst_audio_decoder_set_drainable (GST_AUDIO_DECODER (ffmpegdec), TRUE); + gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (ffmpegdec), TRUE); } static void @@ -190,17 +146,12 @@ gst_ffmpegauddec_finalize (GObject * object) if (ffmpegdec->context != NULL) av_free (ffmpegdec->context); + ffmpegdec->context = NULL; G_OBJECT_CLASS (parent_class)->finalize (object); } -static void -gst_ffmpegauddec_reset_ts (GstFFMpegAudDec * ffmpegdec) -{ - ffmpegdec->next_out = GST_CLOCK_TIME_NONE; -} - -/* with LOCK */ +/* With LOCK */ static void gst_ffmpegauddec_close (GstFFMpegAudDec * ffmpegdec) { @@ -224,15 +175,20 @@ gst_ffmpegauddec_close (GstFFMpegAudDec * ffmpegdec) av_free (ffmpegdec->context->extradata); ffmpegdec->context->extradata = NULL; } +} - if (ffmpegdec->pctx) { - if (ffmpegdec->pcache) { - gst_buffer_unref (ffmpegdec->pcache); - ffmpegdec->pcache = NULL; - } - av_parser_close (ffmpegdec->pctx); - ffmpegdec->pctx = NULL; - } +static gboolean +gst_ffmpegauddec_stop (GstAudioDecoder * decoder) +{ + GstFFMpegAudDec *ffmpegdec = (GstFFMpegAudDec *) decoder; + + GST_OBJECT_LOCK (ffmpegdec); + gst_ffmpegauddec_close (ffmpegdec); + GST_OBJECT_UNLOCK (ffmpegdec); + gst_audio_info_init (&ffmpegdec->info); + gst_caps_replace (&ffmpegdec->last_caps, NULL); + + return TRUE; } /* with LOCK */ @@ -251,21 +207,7 @@ gst_ffmpegauddec_open (GstFFMpegAudDec * ffmpegdec) GST_LOG_OBJECT (ffmpegdec, "Opened libav codec %s, id %d", oclass->in_plugin->name, oclass->in_plugin->id); - if (!ffmpegdec->turnoff_parser) { - ffmpegdec->pctx = av_parser_init (oclass->in_plugin->id); - if (ffmpegdec->pctx) - GST_LOG_OBJECT (ffmpegdec, "Using parser %p", ffmpegdec->pctx); - else - GST_LOG_OBJECT (ffmpegdec, "No parser for codec"); - } else { - GST_LOG_OBJECT (ffmpegdec, "Parser deactivated for format"); - } - - ffmpegdec->samplerate = 0; - ffmpegdec->channels = 0; - ffmpegdec->depth = 0; - - gst_ffmpegauddec_reset_ts (ffmpegdec); + gst_audio_info_init (&ffmpegdec->info); return TRUE; @@ -280,10 +222,10 @@ could_not_open: } static gboolean -gst_ffmpegauddec_setcaps (GstFFMpegAudDec * ffmpegdec, GstCaps * caps) +gst_ffmpegauddec_set_format (GstAudioDecoder * decoder, GstCaps * caps) { + GstFFMpegAudDec *ffmpegdec = (GstFFMpegAudDec *) decoder; GstFFMpegAudDecClass *oclass; - GstStructure *structure; gboolean ret = TRUE; oclass = (GstFFMpegAudDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); @@ -292,6 +234,14 @@ gst_ffmpegauddec_setcaps (GstFFMpegAudDec * ffmpegdec, GstCaps * caps) GST_OBJECT_LOCK (ffmpegdec); + if (ffmpegdec->last_caps && gst_caps_is_equal (ffmpegdec->last_caps, caps)) { + GST_DEBUG_OBJECT (ffmpegdec, "same caps"); + GST_OBJECT_UNLOCK (ffmpegdec); + return TRUE; + } + + gst_caps_replace (&ffmpegdec->last_caps, caps); + /* close old session */ if (ffmpegdec->opened) { GST_OBJECT_UNLOCK (ffmpegdec); @@ -303,32 +253,10 @@ gst_ffmpegauddec_setcaps (GstFFMpegAudDec * ffmpegdec, GstCaps * caps) avcodec_get_context_defaults (ffmpegdec->context); } - /* default is to let format decide if it needs a parser */ - ffmpegdec->turnoff_parser = FALSE; - /* get size and so */ gst_ffmpeg_caps_with_codecid (oclass->in_plugin->id, oclass->in_plugin->type, caps, ffmpegdec->context); - /* get pixel aspect ratio if it's set */ - structure = gst_caps_get_structure (caps, 0); - - /* for AAC we only use av_parse if not on stream-format==raw or ==loas */ - if (oclass->in_plugin->id == CODEC_ID_AAC - || oclass->in_plugin->id == CODEC_ID_AAC_LATM) { - const gchar *format = gst_structure_get_string (structure, "stream-format"); - - if (format == NULL || strcmp (format, "raw") == 0) { - ffmpegdec->turnoff_parser = TRUE; - } - } - - /* for FLAC, don't parse if it's already parsed */ - if (oclass->in_plugin->id == CODEC_ID_FLAC) { - if (gst_structure_has_field (structure, "streamheader")) - ffmpegdec->turnoff_parser = TRUE; - } - /* workaround encoder bugs */ ffmpegdec->context->workaround_bugs |= FF_BUG_AUTODETECT; ffmpegdec->context->error_recognition = 1; @@ -357,53 +285,43 @@ static gboolean gst_ffmpegauddec_negotiate (GstFFMpegAudDec * ffmpegdec, gboolean force) { GstFFMpegAudDecClass *oclass; - GstCaps *caps; gint depth; + GstAudioFormat format; GstAudioChannelPosition pos[64] = { 0, }; oclass = (GstFFMpegAudDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); - depth = av_smp_format_depth (ffmpegdec->context->sample_fmt); - gst_ffmpeg_channel_layout_to_gst (ffmpegdec->context, pos); + depth = av_smp_format_depth (ffmpegdec->context->sample_fmt) * 8; + format = gst_ffmpeg_smpfmt_to_audioformat (ffmpegdec->context->sample_fmt); + if (format == GST_AUDIO_FORMAT_UNKNOWN) + goto no_caps; - if (!force && ffmpegdec->samplerate == + if (!force && ffmpegdec->info.rate == ffmpegdec->context->sample_rate && - ffmpegdec->channels == ffmpegdec->context->channels && - ffmpegdec->depth == depth) + ffmpegdec->info.channels == ffmpegdec->context->channels && + ffmpegdec->info.finfo->depth == depth) return TRUE; GST_DEBUG_OBJECT (ffmpegdec, "Renegotiating audio from %dHz@%dchannels (%d) to %dHz@%dchannels (%d)", - ffmpegdec->samplerate, ffmpegdec->channels, - ffmpegdec->depth, + ffmpegdec->info.rate, ffmpegdec->info.channels, + ffmpegdec->info.finfo->depth, ffmpegdec->context->sample_rate, ffmpegdec->context->channels, depth); - ffmpegdec->samplerate = ffmpegdec->context->sample_rate; - ffmpegdec->channels = ffmpegdec->context->channels; - ffmpegdec->depth = depth; + gst_ffmpeg_channel_layout_to_gst (ffmpegdec->context, pos); memcpy (ffmpegdec->ffmpeg_layout, pos, sizeof (GstAudioChannelPosition) * ffmpegdec->context->channels); /* Get GStreamer channel layout */ - memcpy (ffmpegdec->gst_layout, - ffmpegdec->ffmpeg_layout, - sizeof (GstAudioChannelPosition) * ffmpegdec->channels); - gst_audio_channel_positions_to_valid_order (ffmpegdec->gst_layout, - ffmpegdec->channels); - - caps = gst_ffmpeg_codectype_to_caps (oclass->in_plugin->type, - ffmpegdec->context, oclass->in_plugin->id, FALSE); - - if (caps == NULL) - goto no_caps; + gst_audio_channel_positions_to_valid_order (pos, + ffmpegdec->context->channels); + gst_audio_info_set_format (&ffmpegdec->info, format, + ffmpegdec->context->sample_rate, ffmpegdec->context->channels, pos); - GST_LOG_OBJECT (ffmpegdec, "output caps %" GST_PTR_FORMAT, caps); - - if (!gst_pad_set_caps (ffmpegdec->srcpad, caps)) + if (!gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (ffmpegdec), + &ffmpegdec->info)) goto caps_failed; - gst_caps_unref (caps); - return TRUE; /* ERRORS */ @@ -432,45 +350,12 @@ caps_failed: GST_ELEMENT_ERROR (ffmpegdec, CORE, NEGOTIATION, (NULL), ("Could not set caps for libav decoder (%s), not fixed?", oclass->in_plugin->name)); - gst_caps_unref (caps); return FALSE; } } static void -clear_queued (GstFFMpegAudDec * ffmpegdec) -{ - g_list_foreach (ffmpegdec->queued, (GFunc) gst_mini_object_unref, NULL); - g_list_free (ffmpegdec->queued); - ffmpegdec->queued = NULL; -} - -static GstFlowReturn -flush_queued (GstFFMpegAudDec * ffmpegdec) -{ - GstFlowReturn res = GST_FLOW_OK; - - while (ffmpegdec->queued) { - GstBuffer *buf = GST_BUFFER_CAST (ffmpegdec->queued->data); - - GST_LOG_OBJECT (ffmpegdec, "pushing buffer %p, offset %" - G_GUINT64_FORMAT ", timestamp %" - GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT, buf, - GST_BUFFER_OFFSET (buf), - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), - GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); - - /* iterate ouput queue an push downstream */ - res = gst_pad_push (ffmpegdec->srcpad, buf); - - ffmpegdec->queued = - g_list_delete_link (ffmpegdec->queued, ffmpegdec->queued); - } - return res; -} - -static void gst_avpacket_init (AVPacket * packet, guint8 * data, guint size) { memset (packet, 0, sizeof (AVPacket)); @@ -478,39 +363,22 @@ gst_avpacket_init (AVPacket * packet, guint8 * data, guint size) packet->size = size; } -/* 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_audio_buffer (GstFFMpegAudDec * dec, GstBuffer ** buf) -{ - *buf = - gst_audio_buffer_clip (*buf, &dec->segment, dec->samplerate, - dec->depth * dec->channels); - - return *buf != NULL; -} - static gint gst_ffmpegauddec_audio_frame (GstFFMpegAudDec * ffmpegdec, AVCodec * in_plugin, guint8 * data, guint size, - const GstTSInfo * dec_info, GstBuffer ** outbuf, GstFlowReturn * ret) + GstBuffer ** outbuf, GstFlowReturn * ret) { gint len = -1; gint have_data = AVCODEC_MAX_AUDIO_FRAME_SIZE; - GstClockTime out_pts, out_duration; GstMapInfo map; - gint64 out_offset; int16_t *odata; AVPacket packet; - GST_DEBUG_OBJECT (ffmpegdec, - "size:%d, offset:%" G_GINT64_FORMAT ", dts:%" GST_TIME_FORMAT ", pts:%" - GST_TIME_FORMAT ", dur:%" GST_TIME_FORMAT ", ffmpegdec->next_out:%" - GST_TIME_FORMAT, size, dec_info->offset, GST_TIME_ARGS (dec_info->dts), - GST_TIME_ARGS (dec_info->pts), GST_TIME_ARGS (dec_info->duration), - GST_TIME_ARGS (ffmpegdec->next_out)); + GST_DEBUG_OBJECT (ffmpegdec, "size: %d", size); - *outbuf = new_aligned_buffer (AVCODEC_MAX_AUDIO_FRAME_SIZE); + *outbuf = + gst_audio_decoder_allocate_output_buffer (GST_AUDIO_DECODER (ffmpegdec), + AVCODEC_MAX_AUDIO_FRAME_SIZE); gst_buffer_map (*outbuf, &map, GST_MAP_WRITE); odata = (int16_t *) map.data; @@ -522,8 +390,6 @@ gst_ffmpegauddec_audio_frame (GstFFMpegAudDec * ffmpegdec, "Decode audio: len=%d, have_data=%d", len, have_data); if (len >= 0 && have_data > 0) { - GstAudioFormat fmt; - /* Buffer size */ gst_buffer_unmap (*outbuf, &map); gst_buffer_resize (*outbuf, 0, have_data); @@ -536,58 +402,13 @@ gst_ffmpegauddec_audio_frame (GstFFMpegAudDec * ffmpegdec, goto beach; } - /* - * 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 (dec_info->pts)) { - out_pts = dec_info->pts; - } else { - out_pts = ffmpegdec->next_out; - } - - /* - * Duration: - * - * 1) calculate based on number of samples - */ - out_duration = gst_util_uint64_scale (have_data, GST_SECOND, - ffmpegdec->depth * ffmpegdec->channels * ffmpegdec->samplerate); - - /* offset: - * - * Just copy - */ - out_offset = dec_info->offset; - - GST_DEBUG_OBJECT (ffmpegdec, - "Buffer created. Size:%d , pts:%" GST_TIME_FORMAT " , duration:%" - GST_TIME_FORMAT, have_data, - GST_TIME_ARGS (out_pts), GST_TIME_ARGS (out_duration)); - - GST_BUFFER_PTS (*outbuf) = out_pts; - GST_BUFFER_DURATION (*outbuf) = out_duration; - GST_BUFFER_OFFSET (*outbuf) = out_offset; - - /* the next timestamp we'll use when interpolating */ - if (GST_CLOCK_TIME_IS_VALID (out_pts)) - ffmpegdec->next_out = out_pts + out_duration; - - /* now see if we need to clip the buffer against the segment boundaries. */ - if (G_UNLIKELY (!clip_audio_buffer (ffmpegdec, outbuf))) - goto clipped; + GST_DEBUG_OBJECT (ffmpegdec, "Buffer created. Size: %d", have_data); /* Reorder channels to the GStreamer channel order */ /* Only the width really matters here... and it's stored as depth */ - fmt = - gst_audio_format_build_integer (TRUE, G_BYTE_ORDER, - ffmpegdec->depth * 8, ffmpegdec->depth * 8); - - gst_audio_buffer_reorder_channels (*outbuf, fmt, - ffmpegdec->channels, ffmpegdec->ffmpeg_layout, ffmpegdec->gst_layout); + gst_audio_buffer_reorder_channels (*outbuf, ffmpegdec->info.finfo->format, + ffmpegdec->info.channels, ffmpegdec->ffmpeg_layout, + ffmpegdec->info.position); } else { gst_buffer_unmap (*outbuf, &map); gst_buffer_unref (*outbuf); @@ -598,16 +419,6 @@ beach: GST_DEBUG_OBJECT (ffmpegdec, "return flow %d, out %p, len %d", *ret, *outbuf, len); return len; - - /* ERRORS */ -clipped: - { - GST_DEBUG_OBJECT (ffmpegdec, "buffer clipped"); - if (*outbuf) - gst_buffer_unref (*outbuf); - *outbuf = NULL; - goto beach; - } } /* gst_ffmpegauddec_frame: @@ -626,8 +437,7 @@ clipped: static gint gst_ffmpegauddec_frame (GstFFMpegAudDec * ffmpegdec, - guint8 * data, guint size, gint * got_data, const GstTSInfo * dec_info, - GstFlowReturn * ret) + guint8 * data, guint size, gint * got_data, GstFlowReturn * ret) { GstFFMpegAudDecClass *oclass; GstBuffer *outbuf = NULL; @@ -636,8 +446,7 @@ gst_ffmpegauddec_frame (GstFFMpegAudDec * ffmpegdec, if (G_UNLIKELY (ffmpegdec->context->codec == NULL)) goto no_codec; - GST_LOG_OBJECT (ffmpegdec, "data:%p, size:%d, id:%d", data, size, - dec_info->idx); + GST_LOG_OBJECT (ffmpegdec, "data:%p, size:%d", data, size); *ret = GST_FLOW_OK; ffmpegdec->context->frame_number++; @@ -646,15 +455,7 @@ gst_ffmpegauddec_frame (GstFFMpegAudDec * ffmpegdec, len = gst_ffmpegauddec_audio_frame (ffmpegdec, oclass->in_plugin, data, size, - dec_info, &outbuf, ret); - - /* if we did not get an output buffer and we have a pending discont, don't - * clear the input timestamps, we will put them on the next buffer because - * else we might create the first buffer with a very big timestamp gap. */ - if (outbuf == NULL && ffmpegdec->discont) { - GST_DEBUG_OBJECT (ffmpegdec, "no buffer but keeping timestamp"); - ffmpegdec->clear_ts = FALSE; - } + &outbuf, ret); if (outbuf) have_data = 1; @@ -674,27 +475,11 @@ gst_ffmpegauddec_frame (GstFFMpegAudDec * ffmpegdec, } if (outbuf) { - GST_LOG_OBJECT (ffmpegdec, - "Decoded data, now pushing buffer %p with offset %" G_GINT64_FORMAT - ", timestamp %" GST_TIME_FORMAT " and duration %" GST_TIME_FORMAT, - outbuf, GST_BUFFER_OFFSET (outbuf), - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), - GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf))); - - /* mark pending discont */ - if (ffmpegdec->discont) { - GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); - ffmpegdec->discont = FALSE; - } - if (ffmpegdec->segment.rate > 0.0) { - /* and off we go */ - *ret = gst_pad_push (ffmpegdec->srcpad, outbuf); - } else { - /* reverse playback, queue frame till later when we get a discont. */ - GST_DEBUG_OBJECT (ffmpegdec, "queued frame"); - ffmpegdec->queued = g_list_prepend (ffmpegdec->queued, outbuf); - *ret = GST_FLOW_OK; - } + GST_LOG_OBJECT (ffmpegdec, "Decoded data, now pushing buffer %p", outbuf); + + *ret = + gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (ffmpegdec), outbuf, + 1); } else { GST_DEBUG_OBJECT (ffmpegdec, "We didn't get a decoded buffer"); } @@ -726,213 +511,25 @@ gst_ffmpegauddec_drain (GstFFMpegAudDec * ffmpegdec) do { GstFlowReturn ret; - len = - gst_ffmpegauddec_frame (ffmpegdec, NULL, 0, &have_data, &ts_info_none, - &ret); + len = gst_ffmpegauddec_frame (ffmpegdec, NULL, 0, &have_data, &ret); if (len < 0 || have_data == 0) break; } while (try++ < 10); } - if (ffmpegdec->segment.rate < 0.0) { - /* if we have some queued frames for reverse playback, flush them now */ - flush_queued (ffmpegdec); - } } static void -gst_ffmpegauddec_flush_pcache (GstFFMpegAudDec * ffmpegdec) -{ - if (ffmpegdec->pctx) { - gint size, bsize; - guint8 *data; - guint8 bdata[FF_INPUT_BUFFER_PADDING_SIZE]; - - bsize = FF_INPUT_BUFFER_PADDING_SIZE; - memset (bdata, 0, bsize); - - /* parse some dummy data to work around some ffmpeg weirdness where it keeps - * the previous pts around */ - av_parser_parse2 (ffmpegdec->pctx, ffmpegdec->context, - &data, &size, bdata, bsize, -1, -1, -1); - ffmpegdec->pctx->pts = -1; - ffmpegdec->pctx->dts = -1; - } - - if (ffmpegdec->pcache) { - gst_buffer_unref (ffmpegdec->pcache); - ffmpegdec->pcache = NULL; - } -} - -static gboolean -gst_ffmpegauddec_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) -{ - GstFFMpegAudDec *ffmpegdec; - gboolean ret = FALSE; - - ffmpegdec = (GstFFMpegAudDec *) parent; - - GST_DEBUG_OBJECT (ffmpegdec, "Handling %s event", - GST_EVENT_TYPE_NAME (event)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_EOS: - { - gst_ffmpegauddec_drain (ffmpegdec); - break; - } - case GST_EVENT_FLUSH_STOP: - { - if (ffmpegdec->opened) { - avcodec_flush_buffers (ffmpegdec->context); - } - gst_ffmpegauddec_reset_ts (ffmpegdec); - gst_ffmpegauddec_flush_pcache (ffmpegdec); - gst_segment_init (&ffmpegdec->segment, GST_FORMAT_TIME); - clear_queued (ffmpegdec); - break; - } - case GST_EVENT_CAPS: - { - GstCaps *caps; - - gst_event_parse_caps (event, &caps); - - if (!ffmpegdec->last_caps - || !gst_caps_is_equal (ffmpegdec->last_caps, caps)) { - ret = gst_ffmpegauddec_setcaps (ffmpegdec, caps); - if (ret) { - gst_caps_replace (&ffmpegdec->last_caps, caps); - } - } else { - ret = TRUE; - } - - gst_event_unref (event); - goto done; - } - case GST_EVENT_SEGMENT: - { - GstSegment segment; - - gst_event_copy_segment (event, &segment); - - switch (segment.format) { - case GST_FORMAT_TIME: - /* fine, our native segment format */ - break; - case GST_FORMAT_BYTES: - { - gint bit_rate; - - bit_rate = ffmpegdec->context->bit_rate; - - /* convert to time or fail */ - if (!bit_rate) - goto no_bitrate; - - GST_DEBUG_OBJECT (ffmpegdec, "bitrate: %d", bit_rate); - - /* convert values to TIME */ - if (segment.start != -1) - segment.start = - gst_util_uint64_scale_int (segment.start, GST_SECOND, bit_rate); - if (segment.stop != -1) - segment.stop = - gst_util_uint64_scale_int (segment.stop, GST_SECOND, bit_rate); - if (segment.time != -1) - segment.time = - gst_util_uint64_scale_int (segment.time, GST_SECOND, bit_rate); - - /* unref old event */ - gst_event_unref (event); - - /* create new converted time segment */ - segment.format = GST_FORMAT_TIME; - /* FIXME, bitrate is not good enough too find a good stop, let's - * hope start and time were 0... meh. */ - segment.stop = -1; - event = gst_event_new_segment (&segment); - break; - } - default: - /* invalid format */ - goto invalid_format; - } - - GST_DEBUG_OBJECT (ffmpegdec, "SEGMENT in time %" GST_SEGMENT_FORMAT, - &segment); - - /* and store the values */ - gst_segment_copy_into (&segment, &ffmpegdec->segment); - break; - } - default: - break; - } - - /* and push segment downstream */ - ret = gst_pad_push_event (ffmpegdec->srcpad, event); - -done: - - return ret; - - /* ERRORS */ -no_bitrate: - { - GST_WARNING_OBJECT (ffmpegdec, "no bitrate to convert BYTES to TIME"); - gst_event_unref (event); - goto done; - } -invalid_format: - { - GST_WARNING_OBJECT (ffmpegdec, "unknown format received in NEWSEGMENT"); - gst_event_unref (event); - goto done; - } -} - -static gboolean -gst_ffmpegauddec_sink_query (GstPad * pad, GstObject * parent, GstQuery * query) +gst_ffmpegauddec_flush (GstAudioDecoder * decoder, gboolean hard) { - GstFFMpegAudDec *ffmpegdec; - gboolean ret = FALSE; - - ffmpegdec = (GstFFMpegAudDec *) parent; - - GST_DEBUG_OBJECT (ffmpegdec, "Handling %s query", - GST_QUERY_TYPE_NAME (query)); - - switch (GST_QUERY_TYPE (query)) { - case GST_QUERY_ACCEPT_CAPS: - { - GstPadTemplate *templ; + GstFFMpegAudDec *ffmpegdec = (GstFFMpegAudDec *) decoder; - ret = FALSE; - if ((templ = GST_PAD_PAD_TEMPLATE (pad))) { - GstCaps *tcaps; - - if ((tcaps = GST_PAD_TEMPLATE_CAPS (templ))) { - GstCaps *caps; - - gst_query_parse_accept_caps (query, &caps); - gst_query_set_accept_caps_result (query, - gst_caps_is_subset (caps, tcaps)); - ret = TRUE; - } - } - break; - } - default: - ret = gst_pad_query_default (pad, parent, query); - break; + if (ffmpegdec->opened) { + avcodec_flush_buffers (ffmpegdec->context); } - return ret; } static GstFlowReturn -gst_ffmpegauddec_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf) +gst_ffmpegauddec_handle_frame (GstAudioDecoder * decoder, GstBuffer * inbuf) { GstFFMpegAudDec *ffmpegdec; GstFFMpegAudDecClass *oclass; @@ -940,61 +537,27 @@ gst_ffmpegauddec_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf) GstMapInfo map; gint size, bsize, len, have_data; GstFlowReturn ret = GST_FLOW_OK; - GstClockTime in_pts, in_dts, in_duration; - gboolean discont; - gint64 in_offset; - const GstTSInfo *in_info; - const GstTSInfo *dec_info; - ffmpegdec = (GstFFMpegAudDec *) parent; + ffmpegdec = (GstFFMpegAudDec *) decoder; if (G_UNLIKELY (!ffmpegdec->opened)) goto not_negotiated; - discont = GST_BUFFER_IS_DISCONT (inbuf); - - /* 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 let - * ffmpeg continue with the data that it has. We currently drain the old - * frames that might be inside the decoder and we clear any partial data in - * the pcache, we might be able to remove the drain and flush too. */ - if (G_UNLIKELY (discont)) { - GST_DEBUG_OBJECT (ffmpegdec, "received DISCONT"); - /* drain what we have queued */ + if (inbuf == NULL) { gst_ffmpegauddec_drain (ffmpegdec); - gst_ffmpegauddec_flush_pcache (ffmpegdec); - ffmpegdec->discont = TRUE; - gst_ffmpegauddec_reset_ts (ffmpegdec); - } - /* by default we clear the input timestamp after decoding each frame so that - * interpollation can work. */ - ffmpegdec->clear_ts = TRUE; - - oclass = (GstFFMpegAudDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); - - /* parse cache joining. If there is cached data */ - if (ffmpegdec->pcache) { - /* join with previous data */ - GST_LOG_OBJECT (ffmpegdec, "join parse cache"); - inbuf = gst_buffer_append (ffmpegdec->pcache, inbuf); - /* no more cached data, we assume we can consume the complete cache */ - ffmpegdec->pcache = NULL; + return GST_FLOW_OK; } - in_dts = GST_BUFFER_DTS (inbuf); - in_pts = GST_BUFFER_PTS (inbuf); - in_duration = GST_BUFFER_DURATION (inbuf); - in_offset = GST_BUFFER_OFFSET (inbuf); + inbuf = gst_buffer_ref (inbuf); - /* get handle to timestamp info, we can pass this around to ffmpeg */ - in_info = - gst_ts_info_store (ffmpegdec, in_dts, in_pts, in_duration, in_offset); + oclass = (GstFFMpegAudDecClass *) (G_OBJECT_GET_CLASS (ffmpegdec)); GST_LOG_OBJECT (ffmpegdec, "Received new data of size %u, offset:%" G_GUINT64_FORMAT ", ts:%" - GST_TIME_FORMAT ", dur:%" GST_TIME_FORMAT ", info %d", + GST_TIME_FORMAT ", dur:%" GST_TIME_FORMAT, gst_buffer_get_size (inbuf), GST_BUFFER_OFFSET (inbuf), - GST_TIME_ARGS (in_pts), GST_TIME_ARGS (in_duration), in_info->idx); + GST_TIME_ARGS (GST_BUFFER_PTS (inbuf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (inbuf))); /* workarounds, functions write to buffers: * libavcodec/svq1.c:svq1_decode_frame writes to the given buffer. @@ -1010,73 +573,12 @@ gst_ffmpegauddec_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf) bdata = map.data; bsize = map.size; - GST_LOG_OBJECT (ffmpegdec, - "Received new data of size %u, offset:%" G_GUINT64_FORMAT ", dts:%" - GST_TIME_FORMAT ", pts:%" GST_TIME_FORMAT ", dur:%" GST_TIME_FORMAT - ", info %d", bsize, in_offset, GST_TIME_ARGS (in_dts), - GST_TIME_ARGS (in_pts), GST_TIME_ARGS (in_duration), in_info->idx); - do { - /* parse, if at all possible */ - if (ffmpegdec->pctx) { - gint res; - - GST_LOG_OBJECT (ffmpegdec, - "Calling av_parser_parse2 with offset %" G_GINT64_FORMAT ", ts:%" - GST_TIME_FORMAT " size %d", in_offset, GST_TIME_ARGS (in_pts), bsize); - - /* feed the parser. We pass the timestamp info so that we can recover all - * info again later */ - res = av_parser_parse2 (ffmpegdec->pctx, ffmpegdec->context, - &data, &size, bdata, bsize, in_info->idx, in_info->idx, in_offset); - - GST_LOG_OBJECT (ffmpegdec, - "parser returned res %d and size %d, id %" G_GINT64_FORMAT, res, size, - (gint64) ffmpegdec->pctx->pts); - - /* store pts for decoding */ - if (ffmpegdec->pctx->pts != AV_NOPTS_VALUE && ffmpegdec->pctx->pts != -1) - dec_info = gst_ts_info_get (ffmpegdec, ffmpegdec->pctx->pts); - else { - /* ffmpeg sometimes loses track after a flush, help it by feeding a - * valid start time */ - ffmpegdec->pctx->pts = in_info->idx; - ffmpegdec->pctx->dts = in_info->idx; - dec_info = in_info; - } - - GST_LOG_OBJECT (ffmpegdec, "consuming %d bytes. id %d", size, - dec_info->idx); - - if (res) { - /* there is output, set pointers for next round. */ - bsize -= res; - bdata += res; - } else { - /* Parser did not consume any data, make sure we don't clear the - * timestamp for the next round */ - ffmpegdec->clear_ts = FALSE; - } - - /* if there is no output, we must break and wait for more data. also the - * timestamp in the context is not updated. */ - if (size == 0) { - if (bsize > 0) - continue; - else - break; - } - } else { - data = bdata; - size = bsize; - - dec_info = in_info; - } + data = bdata; + size = bsize; /* decode a frame of audio now */ - len = - gst_ffmpegauddec_frame (ffmpegdec, data, size, &have_data, dec_info, - &ret); + len = gst_ffmpegauddec_frame (ffmpegdec, data, size, &have_data, &ret); if (ret != GST_FLOW_OK) { GST_LOG_OBJECT (ffmpegdec, "breaking because of flow ret %s", @@ -1085,76 +587,35 @@ gst_ffmpegauddec_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf) bsize = 0; break; } - if (!ffmpegdec->pctx) { - if (len == 0 && !have_data) { - /* nothing was decoded, this could be because no data was available or - * because we were skipping frames. - * If we have no context we must exit and wait for more data, we keep the - * data we tried. */ - GST_LOG_OBJECT (ffmpegdec, "Decoding didn't return any data, breaking"); - break; - } else if (len < 0) { - /* a decoding error happened, we must break and try again with next data. */ - GST_LOG_OBJECT (ffmpegdec, "Decoding error, breaking"); - bsize = 0; - break; - } - /* prepare for the next round, for codecs with a context we did this - * already when using the parser. */ - bsize -= len; - bdata += len; - } else { - if (len == 0) { - /* nothing was decoded, this could be because no data was available or - * because we were skipping frames. Since we have a parser we can - * continue with the next frame */ - GST_LOG_OBJECT (ffmpegdec, - "Decoding didn't return any data, trying next"); - } else if (len < 0) { - /* we have a context that will bring us to the next frame */ - GST_LOG_OBJECT (ffmpegdec, "Decoding error, trying next"); - } - } - /* make sure we don't use the same old timestamp for the next frame and let - * the interpollation take care of it. */ - if (ffmpegdec->clear_ts) { - in_dts = GST_CLOCK_TIME_NONE; - in_pts = GST_CLOCK_TIME_NONE; - in_duration = GST_CLOCK_TIME_NONE; - in_offset = GST_BUFFER_OFFSET_NONE; - in_info = GST_TS_INFO_NONE; - } else { - ffmpegdec->clear_ts = TRUE; + if (len == 0 && !have_data) { + /* nothing was decoded, this could be because no data was available or + * because we were skipping frames. + * If we have no context we must exit and wait for more data, we keep the + * data we tried. */ + GST_LOG_OBJECT (ffmpegdec, "Decoding didn't return any data, breaking"); + break; + } else if (len < 0) { + /* a decoding error happened, we must break and try again with next data. */ + GST_LOG_OBJECT (ffmpegdec, "Decoding error, breaking"); + bsize = 0; + break; } + /* prepare for the next round, for codecs with a context we did this + * already when using the parser. */ + bsize -= len; + bdata += len; GST_LOG_OBJECT (ffmpegdec, "Before (while bsize>0). bsize:%d , bdata:%p", bsize, bdata); } while (bsize > 0); gst_buffer_unmap (inbuf, &map); + gst_buffer_unref (inbuf); - /* keep left-over */ - if (ffmpegdec->pctx && bsize > 0) { - in_pts = GST_BUFFER_PTS (inbuf); - in_dts = GST_BUFFER_DTS (inbuf); - in_offset = GST_BUFFER_OFFSET (inbuf); - - GST_LOG_OBJECT (ffmpegdec, - "Keeping %d bytes of data with offset %" G_GINT64_FORMAT ", pts %" - GST_TIME_FORMAT, bsize, in_offset, GST_TIME_ARGS (in_pts)); - - ffmpegdec->pcache = gst_buffer_copy_region (inbuf, GST_BUFFER_COPY_ALL, - gst_buffer_get_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_PTS (ffmpegdec->pcache) = in_pts; - GST_BUFFER_DTS (ffmpegdec->pcache) = in_dts; - GST_BUFFER_OFFSET (ffmpegdec->pcache) = in_offset; - } else if (bsize > 0) { + if (bsize > 0) { GST_DEBUG_OBJECT (ffmpegdec, "Dropping %d bytes of data", bsize); } - gst_buffer_unref (inbuf); return ret; @@ -1165,33 +626,10 @@ not_negotiated: GST_ELEMENT_ERROR (ffmpegdec, CORE, NEGOTIATION, (NULL), ("avdec_%s: input format was not set before data start", oclass->in_plugin->name)); - gst_buffer_unref (inbuf); return GST_FLOW_NOT_NEGOTIATED; } } -static GstStateChangeReturn -gst_ffmpegauddec_change_state (GstElement * element, GstStateChange transition) -{ - GstFFMpegAudDec *ffmpegdec = (GstFFMpegAudDec *) element; - GstStateChangeReturn ret; - - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - - switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_READY: - GST_OBJECT_LOCK (ffmpegdec); - gst_ffmpegauddec_close (ffmpegdec); - GST_OBJECT_UNLOCK (ffmpegdec); - clear_queued (ffmpegdec); - break; - default: - break; - } - - return ret; -} - gboolean gst_ffmpegauddec_register (GstPlugin * plugin) { @@ -1270,7 +708,9 @@ gst_ffmpegauddec_register (GstPlugin * plugin) if (!type) { /* create the gtype now */ - type = g_type_register_static (GST_TYPE_ELEMENT, type_name, &typeinfo, 0); + type = + g_type_register_static (GST_TYPE_AUDIO_DECODER, type_name, &typeinfo, + 0); g_type_set_qdata (type, GST_FFDEC_PARAMS_QDATA, (gpointer) in_plugin); } diff --git a/ext/libav/gstavauddec.h b/ext/libav/gstavauddec.h index 1b38430..8c33e40 100644 --- a/ext/libav/gstavauddec.h +++ b/ext/libav/gstavauddec.h @@ -22,70 +22,32 @@ G_BEGIN_DECLS #include +#include +#include #include -#define MAX_TS_MASK 0xff - -/* for each incomming buffer we keep all timing info in a structure like this. - * We keep a circular array of these structures around to store the timing info. - * The index in the array is what we pass as opaque data (to pictures) and - * pts (to parsers) so that ffmpeg can remember them for us. */ -typedef struct -{ - gint idx; - GstClockTime dts; - GstClockTime pts; - GstClockTime duration; - gint64 offset; -} GstTSInfo; - typedef struct _GstFFMpegAudDec GstFFMpegAudDec; struct _GstFFMpegAudDec { - GstElement element; - - /* We need to keep track of our pads, so we do so here. */ - GstPad *srcpad; - GstPad *sinkpad; + GstAudioDecoder parent; /* decoding */ AVCodecContext *context; gboolean opened; - /* current output format */ - gint channels, samplerate, depth; - GstAudioChannelPosition ffmpeg_layout[64], gst_layout[64]; - - gboolean discont; - gboolean clear_ts; - - /* for tracking DTS/PTS */ - GstClockTime next_out; - - /* parsing */ - gboolean turnoff_parser; /* used for turning off aac raw parsing - * See bug #566250 */ - AVCodecParserContext *pctx; - GstBuffer *pcache; - - /* clipping segment */ - GstSegment segment; - - GstTSInfo ts_info[MAX_TS_MASK + 1]; - gint ts_idx; - - /* reverse playback queue */ - GList *queued; - /* prevent reopening the decoder on GST_EVENT_CAPS when caps are same as last time. */ GstCaps *last_caps; + + /* current output format */ + GstAudioInfo info; + GstAudioChannelPosition ffmpeg_layout[64]; }; typedef struct _GstFFMpegAudDecClass GstFFMpegAudDecClass; struct _GstFFMpegAudDecClass { - GstElementClass parent_class; + GstAudioDecoderClass parent_class; AVCodec *in_plugin; GstPadTemplate *srctempl, *sinktempl; diff --git a/ext/libav/gstavcodecmap.c b/ext/libav/gstavcodecmap.c index 5d7dd23..b33691c 100644 --- a/ext/libav/gstavcodecmap.c +++ b/ext/libav/gstavcodecmap.c @@ -1787,38 +1787,44 @@ gst_ffmpeg_pixfmt_to_caps (enum PixelFormat pix_fmt, AVCodecContext * context, return caps; } -/* Convert a FFMPEG Sample Format and optional AVCodecContext - * to a GstCaps. If the context is ommitted, no fixed values - * for video/audio size will be included in the GstCaps - * - * See below for usefullness - */ - -static GstCaps * -gst_ffmpeg_smpfmt_to_caps (enum AVSampleFormat sample_fmt, - AVCodecContext * context, enum CodecID codec_id) +GstAudioFormat +gst_ffmpeg_smpfmt_to_audioformat (enum AVSampleFormat sample_fmt) { - GstCaps *caps = NULL; - GstAudioFormat format; - switch (sample_fmt) { case AV_SAMPLE_FMT_S16: - format = GST_AUDIO_FORMAT_S16; + return GST_AUDIO_FORMAT_S16; break; case AV_SAMPLE_FMT_S32: - format = GST_AUDIO_FORMAT_S32; + return GST_AUDIO_FORMAT_S32; break; case AV_SAMPLE_FMT_FLT: - format = GST_AUDIO_FORMAT_F32; + return GST_AUDIO_FORMAT_F32; break; case AV_SAMPLE_FMT_DBL: - format = GST_AUDIO_FORMAT_F64; + return GST_AUDIO_FORMAT_F64; break; default: /* .. */ - format = GST_AUDIO_FORMAT_UNKNOWN; + return GST_AUDIO_FORMAT_UNKNOWN; break; } +} + +/* Convert a FFMPEG Sample Format and optional AVCodecContext + * to a GstCaps. If the context is ommitted, no fixed values + * for video/audio size will be included in the GstCaps + * + * See below for usefullness + */ + +static GstCaps * +gst_ffmpeg_smpfmt_to_caps (enum AVSampleFormat sample_fmt, + AVCodecContext * context, enum CodecID codec_id) +{ + GstCaps *caps = NULL; + GstAudioFormat format; + + format = gst_ffmpeg_smpfmt_to_audioformat (sample_fmt); if (format != GST_AUDIO_FORMAT_UNKNOWN) { caps = gst_ff_aud_caps_new (context, codec_id, TRUE, "audio/x-raw", diff --git a/ext/libav/gstavcodecmap.h b/ext/libav/gstavcodecmap.h index f396525..2ef4088 100644 --- a/ext/libav/gstavcodecmap.h +++ b/ext/libav/gstavcodecmap.h @@ -94,6 +94,8 @@ gst_ffmpeg_videoinfo_to_context (GstVideoInfo *info, GstVideoFormat gst_ffmpeg_pixfmt_to_videoformat (enum PixelFormat pixfmt); enum PixelFormat gst_ffmpeg_videoformat_to_pixfmt (GstVideoFormat format); +GstAudioFormat gst_ffmpeg_smpfmt_to_audioformat (enum AVSampleFormat sample_fmt); + /* * _formatid_to_caps () is meant for muxers/demuxers, it * transforms a name (ffmpeg way of ID'ing these, why don't -- 2.7.4