FFMPEG-based colorspace conversion plugin
authorRonald S. Bultje <rbultje@ronald.bitfreak.net>
Sat, 25 Oct 2003 16:29:13 +0000 (16:29 +0000)
committerRonald S. Bultje <rbultje@ronald.bitfreak.net>
Sat, 25 Oct 2003 16:29:13 +0000 (16:29 +0000)
Original commit message from CVS:
FFMPEG-based colorspace conversion plugin

ext/ffmpeg/Makefile.am
ext/ffmpeg/gstffmpeg.c
ext/ffmpeg/gstffmpegcolorspace.c [new file with mode: 0644]

index fef3225..b7dcd4f 100644 (file)
@@ -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 \
index 1a25bf4..9550347 100644 (file)
@@ -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 (file)
index 0000000..fc96105
--- /dev/null
@@ -0,0 +1,520 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * This file:
+ * Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * 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 <avcodec.h>
+#else
+#include <ffmpeg/avcodec.h>
+#endif
+#include <gst/gst.h>
+
+#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 <rbultje@ronald.bitfreak.net>",
+  "(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;
+}