From 5b255e085e7441c99564be5d0d734f8c75fe7cf6 Mon Sep 17 00:00:00 2001 From: "Ronald S. Bultje" Date: Sat, 25 Oct 2003 16:29:13 +0000 Subject: [PATCH] FFMPEG-based colorspace conversion plugin Original commit message from CVS: FFMPEG-based colorspace conversion plugin --- ext/ffmpeg/Makefile.am | 3 +- ext/ffmpeg/gstffmpeg.c | 2 + ext/ffmpeg/gstffmpegcolorspace.c | 520 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 524 insertions(+), 1 deletion(-) create mode 100644 ext/ffmpeg/gstffmpegcolorspace.c diff --git a/ext/ffmpeg/Makefile.am b/ext/ffmpeg/Makefile.am index fef3225..b7dcd4f 100644 --- a/ext/ffmpeg/Makefile.am +++ b/ext/ffmpeg/Makefile.am @@ -7,7 +7,8 @@ libgstffmpeg_la_SOURCES = gstffmpeg.c \ gstffmpegdemux.c \ gstffmpegenc.c \ gstffmpegmux.c \ - gstffmpegprotocol.c + gstffmpegprotocol.c \ + gstffmpegcolorspace.c libgstffmpeg_la_CFLAGS = $(GST_CFLAGS) \ -I $(top_srcdir)/gst-libs/ext/ffmpeg/ffmpeg/libavcodec \ diff --git a/ext/ffmpeg/gstffmpeg.c b/ext/ffmpeg/gstffmpeg.c index 1a25bf4..9550347 100644 --- a/ext/ffmpeg/gstffmpeg.c +++ b/ext/ffmpeg/gstffmpeg.c @@ -36,6 +36,7 @@ extern gboolean gst_ffmpegdemux_register (GstPlugin *plugin); extern gboolean gst_ffmpegdec_register (GstPlugin *plugin); extern gboolean gst_ffmpegenc_register (GstPlugin *plugin); extern gboolean gst_ffmpegmux_register (GstPlugin *plugin); +extern gboolean gst_ffmpegcsp_register (GstPlugin *plugin); extern URLProtocol gstreamer_protocol; @@ -50,6 +51,7 @@ plugin_init (GModule *module, GstPlugin *plugin) gst_ffmpegdec_register (plugin); /*gst_ffmpegdemux_register (plugin);*/ /*gst_ffmpegmux_register (plugin);*/ + gst_ffmpegcsp_register (plugin); /*register_protocol (&gstreamer_protocol);*/ diff --git a/ext/ffmpeg/gstffmpegcolorspace.c b/ext/ffmpeg/gstffmpegcolorspace.c new file mode 100644 index 0000000..fc96105 --- /dev/null +++ b/ext/ffmpeg/gstffmpegcolorspace.c @@ -0,0 +1,520 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * This file: + * Copyright (C) 2003 Ronald Bultje + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_FFMPEG_UNINSTALLED +#include +#else +#include +#endif +#include + +#include "gstffmpegcodecmap.h" + +#define GST_TYPE_FFMPEGCSP \ + (gst_ffmpegcsp_get_type()) +#define GST_FFMPEGCSP(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FFMPEGCSP,GstFFMpegCsp)) +#define GST_FFMPEGCSP_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FFMPEGCSP,GstFFMpegCsp)) +#define GST_IS_FFMPEGCSP(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FFMPEGCSP)) +#define GST_IS_FFMPEGCSP_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FFMPEGCSP)) + +typedef struct _GstFFMpegCsp GstFFMpegCsp; +typedef struct _GstFFMpegCspClass GstFFMpegCspClass; + +struct _GstFFMpegCsp { + GstElement element; + + GstPad *sinkpad, *srcpad; + + gint width, height; + gfloat fps; + enum PixelFormat + from_pixfmt, + to_pixfmt; + AVFrame *from_frame, + *to_frame; + GstCaps *sinkcaps; + + GstBufferPool *pool; +}; + +struct _GstFFMpegCspClass { + GstElementClass parent_class; +}; + +/* elementfactory information */ +static GstElementDetails ffmpegcsp_details = { + "FFMPEG Colorspace converter", + "Filter/Effect", + "LGPL", + "Converts video from one colorspace to another", + VERSION, + "The FFMPEG crew, " + "Ronald Bultje ", + "(C) 2003", +}; + + +/* Stereo signals and args */ +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +enum { + ARG_0, +}; + +static GType gst_ffmpegcsp_get_type (void); + +static void gst_ffmpegcsp_class_init (GstFFMpegCspClass *klass); +static void gst_ffmpegcsp_init (GstFFMpegCsp *space); + +static void gst_ffmpegcsp_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gst_ffmpegcsp_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static GstPadLinkReturn + gst_ffmpegcsp_sinkconnect (GstPad *pad, + GstCaps *caps); +static GstPadLinkReturn + gst_ffmpegcsp_srcconnect (GstPad *pad, + GstCaps *caps); +static GstPadLinkReturn + gst_ffmpegcsp_srcconnect_func (GstPad *pad, + GstCaps *caps, + gboolean newcaps); + +static void gst_ffmpegcsp_chain (GstPad *pad, + GstData *data); +static GstElementStateReturn + gst_ffmpegcsp_change_state (GstElement *element); + +static GstPadTemplate *srctempl, *sinktempl; +static GstElementClass *parent_class = NULL; +/*static guint gst_ffmpegcsp_signals[LAST_SIGNAL] = { 0 }; */ + +static GstBufferPool * +ffmpegcsp_get_bufferpool (GstPad *pad) +{ + GstFFMpegCsp *space; + + space = GST_FFMPEGCSP (gst_pad_get_parent (pad)); + + if (space->from_pixfmt == space->to_pixfmt && + space->from_pixfmt != PIX_FMT_NB) { + return gst_pad_get_bufferpool (space->srcpad); + } + + return NULL; +} + +static GstCaps * +gst_ffmpegcsp_getcaps (GstPad *pad, + GstCaps *caps) +{ + GstFFMpegCsp *space; + GstCaps *result; + GstCaps *peercaps; + GstCaps *ourcaps; + + space = GST_FFMPEGCSP (gst_pad_get_parent (pad)); + + /* we can do everything our peer can... */ + peercaps = gst_caps_copy (gst_pad_get_allowed_caps (space->srcpad)); + + /* and our own template of course */ + ourcaps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + + /* merge them together, we prefer the peercaps first */ + result = gst_caps_prepend (ourcaps, peercaps); + + return result; +} + +static GstPadLinkReturn +gst_ffmpegcsp_srcconnect_func (GstPad *pad, + GstCaps *caps, + gboolean newcaps) +{ + AVCodecContext *ctx; + GstFFMpegCsp *space; + GstCaps *peercaps; + GstCaps *ourcaps; + + space = GST_FFMPEGCSP (gst_pad_get_parent (pad)); + + /* we cannot operate if we didn't get src caps */ + if (!(ourcaps = space->sinkcaps)) { + if (newcaps) { + gst_pad_recalc_allowed_caps (space->sinkpad); + } + + return GST_PAD_LINK_DELAYED; + } + + /* first see if we can do the format natively by filtering the peer caps + * with our incomming caps */ + if ((peercaps = gst_caps_intersect (caps, ourcaps)) != NULL) { + /* see if the peer likes it too, it should as the caps say so.. */ + if (gst_pad_try_set_caps (space->srcpad, peercaps) > 0) { + space->from_pixfmt = space->to_pixfmt = -1; + return GST_PAD_LINK_DONE; + } + } + + /* then see what the peer has that matches the size */ + peercaps = gst_caps_intersect (caps, + gst_caps_append ( + GST_CAPS_NEW ( + "ffmpegcsp_filter", + "video/x-raw-yuv", + "width", GST_PROPS_INT (space->width), + "height", GST_PROPS_INT (space->height), + "framerate", GST_PROPS_FLOAT (space->fps) + ), GST_CAPS_NEW ( + "ffmpegcsp_filter", + "video/x-raw-rgb", + "width", GST_PROPS_INT (space->width), + "height", GST_PROPS_INT (space->height), + "framerate", GST_PROPS_FLOAT (space->fps) + ))); + + /* we are looping over the caps, so we have to get rid of the lists */ + peercaps = gst_caps_normalize (peercaps); + + /* loop over all possibilities and select the first one we can convert and + * is accepted by the peer */ + ctx = avcodec_alloc_context (); + while (peercaps) { + ctx->width = space->width; + ctx->height = space->height; + ctx->pix_fmt = PIX_FMT_NB; + gst_ffmpeg_caps_to_codectype (CODEC_TYPE_VIDEO, peercaps, ctx); + if (ctx->pix_fmt != PIX_FMT_NB) { + GstCaps *one = gst_caps_copy_1 (peercaps); + if (gst_pad_try_set_caps (space->srcpad, one) > 0) { + space->to_pixfmt = ctx->pix_fmt; + gst_caps_unref (one); + av_free (ctx); + if (space->from_frame) + av_free (space->from_frame); + if (space->to_frame) + av_free (space->to_frame); + space->from_frame = avcodec_alloc_frame (); + space->to_frame = avcodec_alloc_frame (); + return GST_PAD_LINK_DONE; + } + gst_caps_unref (one); + } + peercaps = peercaps->next; + } + av_free (ctx); + + /* we disable ourself here */ + space->from_pixfmt = space->to_pixfmt = PIX_FMT_NB; + + return GST_PAD_LINK_REFUSED; +} + +static GstPadLinkReturn +gst_ffmpegcsp_sinkconnect (GstPad *pad, + GstCaps *caps) +{ + AVCodecContext *ctx; + GstFFMpegCsp *space; + GstPad *peer; + + space = GST_FFMPEGCSP (gst_pad_get_parent (pad)); + + if (!GST_CAPS_IS_FIXED (caps)) { + return GST_PAD_LINK_DELAYED; + } + + ctx = avcodec_alloc_context (); + ctx->width = 0; + ctx->height = 0; + ctx->pix_fmt = PIX_FMT_NB; + + gst_ffmpeg_caps_to_codectype (CODEC_TYPE_VIDEO, caps, ctx); + if (!ctx->width || !ctx->height || ctx->pix_fmt == PIX_FMT_NB) { + return GST_PAD_LINK_REFUSED; + } + + gst_caps_get_float (caps, "framerate", &space->fps); + space->width = ctx->width; + space->height = ctx->height; + space->from_pixfmt = ctx->pix_fmt; + av_free (ctx); + + GST_INFO ( "size: %dx%d", space->width, space->height); + + space->sinkcaps = caps; + + if ((peer = gst_pad_get_peer (pad)) != NULL) { + GstPadLinkReturn ret; + ret = gst_ffmpegcsp_srcconnect_func (pad, + gst_pad_get_allowed_caps (space->srcpad), + FALSE); + if (ret <= 0) { + space->sinkcaps = NULL; + return ret; + } + + return GST_PAD_LINK_DONE; + } + + return GST_PAD_LINK_OK; +} + +static GstPadLinkReturn +gst_ffmpegcsp_srcconnect (GstPad *pad, + GstCaps *caps) +{ + return gst_ffmpegcsp_srcconnect_func (pad, caps, TRUE); +} + +static GType +gst_ffmpegcsp_get_type (void) +{ + static GType ffmpegcsp_type = 0; + + if (!ffmpegcsp_type) { + static const GTypeInfo ffmpegcsp_info = { + sizeof (GstFFMpegCspClass), + NULL, + NULL, + (GClassInitFunc) gst_ffmpegcsp_class_init, + NULL, + NULL, + sizeof (GstFFMpegCsp), + 0, + (GInstanceInitFunc) gst_ffmpegcsp_init, + }; + + ffmpegcsp_type = g_type_register_static (GST_TYPE_ELEMENT, + "GstFFMpegColorspace", + &ffmpegcsp_info, 0); + } + + return ffmpegcsp_type; +} + +static void +gst_ffmpegcsp_class_init (GstFFMpegCspClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass*) klass; + gstelement_class = (GstElementClass*) klass; + + parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + + gobject_class->set_property = gst_ffmpegcsp_set_property; + gobject_class->get_property = gst_ffmpegcsp_get_property; + + gstelement_class->change_state = gst_ffmpegcsp_change_state; +} + +static void +gst_ffmpegcsp_init (GstFFMpegCsp *space) +{ + space->sinkpad = gst_pad_new_from_template (sinktempl, "sink"); + gst_pad_set_link_function (space->sinkpad, gst_ffmpegcsp_sinkconnect); + gst_pad_set_getcaps_function (space->sinkpad, gst_ffmpegcsp_getcaps); + gst_pad_set_bufferpool_function (space->sinkpad, ffmpegcsp_get_bufferpool); + gst_pad_set_chain_function (space->sinkpad,gst_ffmpegcsp_chain); + gst_element_add_pad (GST_ELEMENT(space), space->sinkpad); + + space->srcpad = gst_pad_new_from_template (srctempl, "src"); + gst_element_add_pad (GST_ELEMENT (space), space->srcpad); + gst_pad_set_link_function (space->srcpad, gst_ffmpegcsp_srcconnect); + + space->pool = NULL; + space->from_pixfmt = space->to_pixfmt = PIX_FMT_NB; + space->from_frame = space->to_frame = NULL; +} + +static void +gst_ffmpegcsp_chain (GstPad *pad, + GstData *data) +{ + GstBuffer *inbuf = GST_BUFFER (data); + GstFFMpegCsp *space; + GstBuffer *outbuf = NULL; + + g_return_if_fail (pad != NULL); + g_return_if_fail (GST_IS_PAD (pad)); + g_return_if_fail (inbuf != NULL); + + space = GST_FFMPEGCSP (gst_pad_get_parent (pad)); + + g_return_if_fail (space != NULL); + g_return_if_fail (GST_IS_FFMPEGCSP (space)); + + if (space->from_pixfmt == PIX_FMT_NB || + space->to_pixfmt == PIX_FMT_NB) { + gst_buffer_unref (inbuf); + return; + } + + if (space->from_pixfmt == space->to_pixfmt) { + outbuf = inbuf; + } else { + if (space->pool) { + outbuf = gst_buffer_new_from_pool (space->pool, 0, 0); + } + + if (!outbuf) { + guint size = avpicture_get_size (space->to_pixfmt, + space->width, + space->height); + outbuf = gst_buffer_new_and_alloc (size); + } + + /* convert */ + avpicture_fill ((AVPicture *) space->from_frame, GST_BUFFER_DATA (inbuf), + space->from_pixfmt, space->width, space->height); + avpicture_fill ((AVPicture *) space->to_frame, GST_BUFFER_DATA (outbuf), + space->to_pixfmt, space->width, space->height); + img_convert ((AVPicture *) space->to_frame, space->to_pixfmt, + (AVPicture *) space->from_frame, space->from_pixfmt, + space->width, space->height); + + GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (inbuf); + GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (inbuf); + + gst_buffer_unref (inbuf); + } + + gst_pad_push (space->srcpad, GST_DATA (outbuf)); +} + +static GstElementStateReturn +gst_ffmpegcsp_change_state (GstElement *element) +{ + GstFFMpegCsp *space; + + space = GST_FFMPEGCSP (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_PAUSED_TO_PLAYING: + space->pool = gst_pad_get_bufferpool (space->srcpad); + break; + case GST_STATE_PLAYING_TO_PAUSED: + space->pool = NULL; + case GST_STATE_PAUSED_TO_READY: + if (space->from_frame) + av_free (space->from_frame); + if (space->to_frame) + av_free (space->to_frame); + space->from_frame = NULL; + space->to_frame = NULL; + break; + } + + if (parent_class->change_state) + return parent_class->change_state (element); + + return GST_STATE_SUCCESS; +} + +static void +gst_ffmpegcsp_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GstFFMpegCsp *space; + + /* it's not null if we got it, but it might not be ours */ + g_return_if_fail (GST_IS_FFMPEGCSP (object)); + space = GST_FFMPEGCSP (object); + + switch (prop_id) { + default: + break; + } +} + +static void +gst_ffmpegcsp_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GstFFMpegCsp *space; + + /* it's not null if we got it, but it might not be ours */ + g_return_if_fail (GST_IS_FFMPEGCSP (object)); + space = GST_FFMPEGCSP (object); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gboolean +gst_ffmpegcsp_register (GstPlugin *plugin) +{ + GstElementFactory *factory; + GstCaps *caps; + + factory = gst_element_factory_new ("ffcolorspace", GST_TYPE_FFMPEGCSP, + &ffmpegcsp_details); + g_return_val_if_fail (factory != NULL, FALSE); + + /* template caps */ + caps = gst_ffmpeg_codectype_to_caps (CODEC_TYPE_VIDEO, NULL); + + /* build templates */ + srctempl = gst_pad_template_new ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + caps, NULL); + gst_caps_ref (caps); + sinktempl = gst_pad_template_new ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + caps, NULL); + + gst_element_factory_add_pad_template (factory, srctempl); + gst_element_factory_add_pad_template (factory, sinktempl); + + gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); + + return TRUE; +} -- 2.7.4