diff --git a/ChangeLog b/ChangeLog index 58a1b7c233..42a679e5a7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2008-10-08 Mark Nauwelaerts + + * configure.ac: + * ext/Makefile.am: + * ext/libswscale/Makefile.am: + * ext/libswscale/gstffmpegscale.c: + (gst_ffmpegscale_method_get_type), (gst_ffmpegscale_base_init), + (gst_ffmpegscale_class_init), (gst_ffmpegscale_init), + (gst_ffmpegscale_reset), (gst_ffmpegscale_finalize), + (gst_ffmpegscale_caps_remove_format_info), + (gst_ffmpegscale_transform_caps), (gst_ffmpegscale_fixate_caps), + (gst_ffmpegscale_get_unit_size), (gst_ffmpeg_caps_to_pixfmt), + (gst_ffmpegscale_fill_info), (gst_ffmpegscale_set_caps), + (gst_ffmpegscale_transform), (gst_ffmpegscale_handle_src_event), + (gst_ffmpegscale_stop), (gst_ffmpegscale_set_property), + (gst_ffmpegscale_get_property), (gst_ffmpeg_log_callback), + (plugin_init): + Rewrite ffvideoscale using libswscale and put into a separate plugin. + Fixes #504056. + 2008-10-08 Sebastian Dröge Patch by: Damien Lespiau diff --git a/configure.ac b/configure.ac index db9ba321e9..3f8e7c46d8 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,7 @@ AC_ARG_WITH(system-ffmpeg, if test "x$with_system_ffmpeg" = "xyes"; then PKG_CHECK_MODULES(FFMPEG, libavutil libavcodec libavformat) PKG_CHECK_MODULES(POSTPROC, libavcodec libpostproc) + PKG_CHECK_MODULES(SWSCALE, libavutil libswcale) saved_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $FFMPEG_CFLAGS" AC_CHECK_HEADERS([avi.h]) @@ -278,6 +279,16 @@ else POSTPROC_LIBS="\$(top_builddir)/gst-libs/ext/ffmpeg/libpostproc/libpostproc.a \ \$(top_builddir)/gst-libs/ext/ffmpeg/libavutil/libavutil.a" + dnl + SWSCALE_CFLAGS="-I \$(top_srcdir)/gst-libs/ext/ffmpeg/libswscale \ + -I \$(top_srcdir)/gst-libs/ext/ffmpeg/libavutil \ + -I \$(top_srcdir)/gst-libs/ext/ffmpeg \ + -Wno-deprecated-declarations" + + dnl libgstswscale.la: libs to statically link to + SWSCALE_LIBS="\$(top_builddir)/gst-libs/ext/ffmpeg/libswscale/libswscale.a \ + \$(top_builddir)/gst-libs/ext/ffmpeg/libavutil/libavutil.a" + FFMPEG_SUBDIRS=gst-libs AC_DEFINE(HAVE_AVI_H) AC_DEFINE([FFMPEG_SOURCE], ["local snapshot"], [Describes where the FFmpeg libraries come from.]) @@ -288,7 +299,7 @@ else with_ffmpeg_extra_configure=no) # Enable shared and static so that we get .a files, but with PIC code. - ac_configure_args="$ac_configure_args --disable-vhook --disable-ffserver --disable-ffplay --enable-postproc --enable-gpl --enable-static --enable-shared --disable-encoder=flac --disable-decoder=cavs --disable-protocols --disable-devices --disable-network" + ac_configure_args="$ac_configure_args --disable-vhook --disable-ffserver --disable-ffplay --enable-postproc --enable-swscale --enable-gpl --enable-static --enable-shared --disable-encoder=flac --disable-decoder=cavs --disable-protocols --disable-devices --disable-network" # if we are cross-compiling, tell ffmpeg so if test "x$cross_compiling" = xyes; then @@ -330,6 +341,8 @@ AC_SUBST(FFMPEG_LIBS) AC_SUBST(FFMPEG_SUBDIRS) AC_SUBST(POSTPROC_CFLAGS) AC_SUBST(POSTPROC_LIBS) +AC_SUBST(SWSCALE_CFLAGS) +AC_SUBST(SWSCALE_LIBS) AC_SUBST(WIN32_LIBS) if test x$HAVE_FFMPEG_UNINSTALLED = x1; then @@ -343,6 +356,7 @@ gst-ffmpeg.spec ext/Makefile ext/ffmpeg/Makefile ext/libpostproc/Makefile +ext/libswscale/Makefile gst-libs/Makefile gst-libs/ext/Makefile docs/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index 0b91646cbc..b13b797aa6 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -1 +1 @@ -SUBDIRS = ffmpeg libpostproc +SUBDIRS = ffmpeg libpostproc libswscale diff --git a/ext/libswscale/Makefile.am b/ext/libswscale/Makefile.am new file mode 100644 index 0000000000..b06d275bbf --- /dev/null +++ b/ext/libswscale/Makefile.am @@ -0,0 +1,15 @@ +plugin_LTLIBRARIES = libgstffmpegscale.la + +libgstffmpegscale_la_SOURCES = gstffmpegscale.c + +libgstffmpegscale_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(LIBOIL_CFLAGS) $(SWSCALE_CFLAGS) +libgstffmpegscale_la_LIBADD = $(SWSCALE_LIBS) \ + $(GST_PLUGINS_BASE_LIBS) -lgstvideo-$(GST_MAJORMINOR) \ + $(GST_BASE_LIBS) $(LIBOIL_LIBS) $(LIBM) -lz +libgstffmpegscale_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(DARWIN_LDFLAGS) + +if HAVE_FFMPEG_UNINSTALLED +libgstffmpegscale_la_DEPENDENCIES = $(SWSCALE_LIBS) +endif diff --git a/ext/libswscale/gstffmpegscale.c b/ext/libswscale/gstffmpegscale.c new file mode 100644 index 0000000000..1f8b5aba16 --- /dev/null +++ b/ext/libswscale/gstffmpegscale.c @@ -0,0 +1,808 @@ +/* GStreamer libswscale wrapper + * Copyright (C) 2005 Luca Ognibene + * Copyright (C) 2006 Martin Zlomek + * Copyright (C) 2008 Mark Nauwelaerts + * + * 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 +#include +#include +#include +#include + +#include + +typedef struct _GstFFMpegScale +{ + GstBaseTransform element; + + /* pads */ + GstPad *sinkpad, *srcpad; + + /* state */ + gint in_width, in_height; + gint out_width, out_height; + + enum PixelFormat in_pixfmt, out_pixfmt; + struct SwsContext *ctx; + + /* cached auxiliary data */ + gint in_stride[3], in_offset[3]; + gint out_stride[3], out_offset[3]; + + /* property */ + gint method; +} GstFFMpegScale; + +typedef struct _GstFFMpegScaleClass +{ + GstBaseTransformClass parent_class; +} GstFFMpegScaleClass; + +#define GST_TYPE_FFMPEGSCALE \ + (gst_ffmpegscale_get_type()) +#define GST_FFMPEGSCALE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FFMPEGSCALE,GstFFMpegScale)) +#define GST_FFMPEGSCALE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FFMPEGSCALE,GstFFMpegScaleClass)) +#define GST_IS_FFMPEGSCALE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FFMPEGSCALE)) +#define GST_IS_FFMPEGSCALE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FFMPEGSCALE)) + +GST_DEBUG_CATEGORY (ffmpegscale_debug); +#define GST_CAT_DEFAULT ffmpegscale_debug + +static const GstElementDetails plugin_details = +GST_ELEMENT_DETAILS ("FFMPEG Scale element", + "Filter/Converter/Video", + "Converts video from one resolution to another", + "Luca Ognibene , Mark Nauwelaerts "); + +/* libswscale supported formats depend on endianness */ +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define VIDEO_CAPS \ + GST_VIDEO_CAPS_RGB "; " GST_VIDEO_CAPS_BGR "; " \ + GST_VIDEO_CAPS_xRGB "; " GST_VIDEO_CAPS_xBGR "; " \ + GST_VIDEO_CAPS_ARGB "; " GST_VIDEO_CAPS_ABGR "; " \ + GST_VIDEO_CAPS_YUV ("{ I420, YUY2, Y41B, Y42B }") +#else +#define VIDEO_CAPS \ + GST_VIDEO_CAPS_RGB "; " GST_VIDEO_CAPS_BGR "; " \ + GST_VIDEO_CAPS_RGBx "; " GST_VIDEO_CAPS_BGRx "; " \ + GST_VIDEO_CAPS_RGBA "; " GST_VIDEO_CAPS_BGRA "; " \ + GST_VIDEO_CAPS_YUV ("{ I420, YUY2, Y41B, Y42B }") +#endif + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS) + ); + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS) + ); + +static gint gst_ffmpegscale_method_flags[] = { + SWS_FAST_BILINEAR, + SWS_BILINEAR, + SWS_BICUBIC, + SWS_X, + SWS_POINT, + SWS_AREA, + SWS_BICUBLIN, + SWS_GAUSS, + SWS_SINC, + SWS_LANCZOS, + SWS_SPLINE, +}; + +#define GST_TYPE_FFMPEGSCALE_METHOD (gst_ffmpegscale_method_get_type()) +static GType +gst_ffmpegscale_method_get_type (void) +{ + static GType ffmpegscale_method_type = 0; + + static const GEnumValue ffmpegscale_methods[] = { + {0, "Fast Bilinear", "fast-bilinear"}, + {1, "Bilinear", "bilinear"}, + {2, "Bicubic", "bicubic"}, + {3, "Experimental", "experimental"}, + {4, "Nearest Neighbour", "nearest-neighbour"}, + {5, "Area", "area"}, + {6, "Luma Bicubic / Chroma Linear", "bicubic-lin"}, + {7, "Gauss", "gauss"}, + {8, "SincR", "sincr"}, + {9, "Lanczos", "lanczos"}, + {10, "Natural Bicubic Spline", "bicubic-spline"}, + {0, NULL, NULL}, + }; + + if (!ffmpegscale_method_type) { + ffmpegscale_method_type = + g_enum_register_static ("GstFFMpegVideoScaleMethod", + ffmpegscale_methods); + } + return ffmpegscale_method_type; +} + +#define DEFAULT_PROP_METHOD 2 + +enum +{ + PROP_0, + PROP_METHOD + /* FILL ME */ +}; + +GST_BOILERPLATE (GstFFMpegScale, gst_ffmpegscale, GstBaseTransform, + GST_TYPE_BASE_TRANSFORM); + +static void gst_ffmpegscale_finalize (GObject * object); +static void gst_ffmpegscale_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_ffmpegscale_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean gst_ffmpegscale_stop (GstBaseTransform * trans); +static GstCaps *gst_ffmpegscale_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps); +static void gst_ffmpegscale_fixate_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * othercaps); +static gboolean gst_ffmpegscale_get_unit_size (GstBaseTransform * trans, + GstCaps * caps, guint * size); +static gboolean gst_ffmpegscale_set_caps (GstBaseTransform * trans, + GstCaps * incaps, GstCaps * outcaps); +static GstFlowReturn gst_ffmpegscale_transform (GstBaseTransform * trans, + GstBuffer * inbuf, GstBuffer * outbuf); + +static gboolean gst_ffmpegscale_handle_src_event (GstPad * pad, + GstEvent * event); + +static void +gst_ffmpegscale_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory)); + gst_element_class_set_details (element_class, &plugin_details); +} + +static void +gst_ffmpegscale_class_init (GstFFMpegScaleClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass); + + gobject_class->finalize = gst_ffmpegscale_finalize; + gobject_class->set_property = gst_ffmpegscale_set_property; + gobject_class->get_property = gst_ffmpegscale_get_property; + + g_object_class_install_property (gobject_class, PROP_METHOD, + g_param_spec_enum ("method", "method", "method", + GST_TYPE_FFMPEGSCALE_METHOD, DEFAULT_PROP_METHOD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + trans_class->stop = GST_DEBUG_FUNCPTR (gst_ffmpegscale_stop); + trans_class->transform_caps = + GST_DEBUG_FUNCPTR (gst_ffmpegscale_transform_caps); + trans_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_ffmpegscale_fixate_caps); + trans_class->get_unit_size = + GST_DEBUG_FUNCPTR (gst_ffmpegscale_get_unit_size); + trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_ffmpegscale_set_caps); + trans_class->transform = GST_DEBUG_FUNCPTR (gst_ffmpegscale_transform); + + trans_class->passthrough_on_same_caps = TRUE; +} + +static void +gst_ffmpegscale_init (GstFFMpegScale * scale, GstFFMpegScaleClass * klass) +{ + GstBaseTransform *trans = GST_BASE_TRANSFORM (scale); + + gst_pad_set_event_function (trans->srcpad, gst_ffmpegscale_handle_src_event); + + scale->method = DEFAULT_PROP_METHOD; + scale->ctx = NULL; + scale->in_pixfmt = PIX_FMT_NONE; + scale->out_pixfmt = PIX_FMT_NONE; +} + +static void +gst_ffmpegscale_reset (GstFFMpegScale * scale) +{ + if (scale->ctx != NULL) { + sws_freeContext (scale->ctx); + scale->ctx = NULL; + } + + scale->in_pixfmt = PIX_FMT_NONE; + scale->out_pixfmt = PIX_FMT_NONE; +} + +static void +gst_ffmpegscale_finalize (GObject * object) +{ + GstFFMpegScale *scale = GST_FFMPEGSCALE (object); + + gst_ffmpegscale_reset (scale); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* copies the given caps */ +static GstCaps * +gst_ffmpegscale_caps_remove_format_info (GstCaps * caps) +{ + int i; + GstStructure *structure; + GstCaps *rgbcaps; + GstCaps *graycaps; + + caps = gst_caps_copy (caps); + + for (i = 0; i < gst_caps_get_size (caps); i++) { + structure = gst_caps_get_structure (caps, i); + + gst_structure_set_name (structure, "video/x-raw-yuv"); + gst_structure_remove_field (structure, "format"); + gst_structure_remove_field (structure, "endianness"); + gst_structure_remove_field (structure, "depth"); + gst_structure_remove_field (structure, "bpp"); + gst_structure_remove_field (structure, "red_mask"); + gst_structure_remove_field (structure, "green_mask"); + gst_structure_remove_field (structure, "blue_mask"); + gst_structure_remove_field (structure, "alpha_mask"); + gst_structure_remove_field (structure, "palette_data"); + } + + rgbcaps = gst_caps_copy (caps); + + for (i = 0; i < gst_caps_get_size (rgbcaps); i++) { + structure = gst_caps_get_structure (rgbcaps, i); + + gst_structure_set_name (structure, "video/x-raw-rgb"); + } + graycaps = gst_caps_copy (caps); + + for (i = 0; i < gst_caps_get_size (graycaps); i++) { + structure = gst_caps_get_structure (graycaps, i); + + gst_structure_set_name (structure, "video/x-raw-gray"); + } + + gst_caps_append (caps, graycaps); + gst_caps_append (caps, rgbcaps); + + return caps; +} + +static GstCaps * +gst_ffmpegscale_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps) +{ + GstFFMpegScale *scale; + GstCaps *ret; + GstStructure *structure; + const GValue *par; + + /* this function is always called with a simple caps */ + g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), NULL); + + scale = GST_FFMPEGSCALE (trans); + + structure = gst_caps_get_structure (caps, 0); + + ret = gst_caps_copy (caps); + structure = gst_structure_copy (gst_caps_get_structure (ret, 0)); + + gst_structure_set (structure, + "width", GST_TYPE_INT_RANGE, 1, G_MAXINT, + "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); + + gst_caps_merge_structure (ret, gst_structure_copy (structure)); + + /* if pixel aspect ratio, make a range of it */ + if ((par = gst_structure_get_value (structure, "pixel-aspect-ratio"))) { + gst_structure_set (structure, + "pixel-aspect-ratio", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); + + gst_caps_merge_structure (ret, structure); + } else { + gst_structure_free (structure); + } + + /* now also unfix colour space format */ + gst_caps_append (ret, gst_ffmpegscale_caps_remove_format_info (ret)); + + GST_DEBUG_OBJECT (trans, "returning caps: %" GST_PTR_FORMAT, ret); + + return ret; +} + +static void +gst_ffmpegscale_fixate_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * othercaps) +{ + GstStructure *ins, *outs; + const GValue *from_par, *to_par; + + g_return_if_fail (gst_caps_is_fixed (caps)); + + GST_DEBUG_OBJECT (trans, "trying to fixate othercaps %" GST_PTR_FORMAT + " based on caps %" GST_PTR_FORMAT, othercaps, caps); + + ins = gst_caps_get_structure (caps, 0); + outs = gst_caps_get_structure (othercaps, 0); + + from_par = gst_structure_get_value (ins, "pixel-aspect-ratio"); + to_par = gst_structure_get_value (outs, "pixel-aspect-ratio"); + + /* we have both PAR but they might not be fixated */ + if (from_par && to_par) { + gint from_w, from_h, from_par_n, from_par_d, to_par_n, to_par_d; + gint count = 0, w = 0, h = 0; + guint num, den; + + /* from_par should be fixed */ + g_return_if_fail (gst_value_is_fixed (from_par)); + + from_par_n = gst_value_get_fraction_numerator (from_par); + from_par_d = gst_value_get_fraction_denominator (from_par); + + /* fixate the out PAR */ + if (!gst_value_is_fixed (to_par)) { + GST_DEBUG_OBJECT (trans, "fixating to_par to %dx%d", from_par_n, + from_par_d); + gst_structure_fixate_field_nearest_fraction (outs, "pixel-aspect-ratio", + from_par_n, from_par_d); + } + + to_par_n = gst_value_get_fraction_numerator (to_par); + to_par_d = gst_value_get_fraction_denominator (to_par); + + /* if both width and height are already fixed, we can't do anything + * about it anymore */ + if (gst_structure_get_int (outs, "width", &w)) + ++count; + if (gst_structure_get_int (outs, "height", &h)) + ++count; + if (count == 2) { + GST_DEBUG_OBJECT (trans, "dimensions already set to %dx%d, not fixating", + w, h); + return; + } + + gst_structure_get_int (ins, "width", &from_w); + gst_structure_get_int (ins, "height", &from_h); + + if (!gst_video_calculate_display_ratio (&num, &den, from_w, from_h, + from_par_n, from_par_d, to_par_n, to_par_d)) { + GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), + ("Error calculating the output scaled size - integer overflow")); + return; + } + + GST_DEBUG_OBJECT (trans, + "scaling input with %dx%d and PAR %d/%d to output PAR %d/%d", + from_w, from_h, from_par_n, from_par_d, to_par_n, to_par_d); + GST_DEBUG_OBJECT (trans, "resulting output should respect ratio of %d/%d", + num, den); + + /* now find a width x height that respects this display ratio. + * prefer those that have one of w/h the same as the incoming video + * using wd / hd = num / den */ + + /* if one of the output width or height is fixed, we work from there */ + if (h) { + GST_DEBUG_OBJECT (trans, "height is fixed,scaling width"); + w = (guint) gst_util_uint64_scale_int (h, num, den); + } else if (w) { + GST_DEBUG_OBJECT (trans, "width is fixed, scaling height"); + h = (guint) gst_util_uint64_scale_int (w, den, num); + } else { + /* none of width or height is fixed, figure out both of them based only on + * the input width and height */ + /* check hd / den is an integer scale factor, and scale wd with the PAR */ + if (from_h % den == 0) { + GST_DEBUG_OBJECT (trans, "keeping video height"); + h = from_h; + w = (guint) gst_util_uint64_scale_int (h, num, den); + } else if (from_w % num == 0) { + GST_DEBUG_OBJECT (trans, "keeping video width"); + w = from_w; + h = (guint) gst_util_uint64_scale_int (w, den, num); + } else { + GST_DEBUG_OBJECT (trans, "approximating but keeping video height"); + h = from_h; + w = (guint) gst_util_uint64_scale_int (h, num, den); + } + } + GST_DEBUG_OBJECT (trans, "scaling to %dx%d", w, h); + + /* now fixate */ + gst_structure_fixate_field_nearest_int (outs, "width", w); + gst_structure_fixate_field_nearest_int (outs, "height", h); + } else { + gint width, height; + + if (gst_structure_get_int (ins, "width", &width)) { + if (gst_structure_has_field (outs, "width")) { + gst_structure_fixate_field_nearest_int (outs, "width", width); + } + } + if (gst_structure_get_int (ins, "height", &height)) { + if (gst_structure_has_field (outs, "height")) { + gst_structure_fixate_field_nearest_int (outs, "height", height); + } + } + } + + GST_DEBUG_OBJECT (trans, "fixated othercaps to %" GST_PTR_FORMAT, othercaps); +} + +static gboolean +gst_ffmpegscale_get_unit_size (GstBaseTransform * trans, GstCaps * caps, + guint * size) +{ + gint width, height; + GstVideoFormat format; + + if (!gst_video_format_parse_caps (caps, &format, &width, &height)) + return FALSE; + + *size = gst_video_format_get_size (format, width, height); + + GST_DEBUG_OBJECT (trans, "unit size = %d for format %d w %d height %d", + *size, format, width, height); + + return TRUE; +} + +/* Convert a GstCaps (video/raw) to a FFMPEG PixFmt + */ +static enum PixelFormat +gst_ffmpeg_caps_to_pixfmt (const GstCaps * caps) +{ + GstStructure *structure; + enum PixelFormat pix_fmt = PIX_FMT_NONE; + + GST_DEBUG ("converting caps %" GST_PTR_FORMAT, caps); + g_return_val_if_fail (gst_caps_get_size (caps) == 1, PIX_FMT_NONE); + structure = gst_caps_get_structure (caps, 0); + + if (strcmp (gst_structure_get_name (structure), "video/x-raw-yuv") == 0) { + guint32 fourcc; + + if (gst_structure_get_fourcc (structure, "format", &fourcc)) { + switch (fourcc) { + case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'): + pix_fmt = PIX_FMT_YUV422; + break; + case GST_MAKE_FOURCC ('I', '4', '2', '0'): + pix_fmt = PIX_FMT_YUV420P; + break; + case GST_MAKE_FOURCC ('Y', '4', '1', 'B'): + pix_fmt = PIX_FMT_YUV411P; + break; + case GST_MAKE_FOURCC ('Y', '4', '2', 'B'): + pix_fmt = PIX_FMT_YUV422P; + break; + case GST_MAKE_FOURCC ('Y', 'U', 'V', '9'): + pix_fmt = PIX_FMT_YUV410P; + break; + } + } + } else if (strcmp (gst_structure_get_name (structure), + "video/x-raw-rgb") == 0) { + gint bpp = 0, rmask = 0, endianness = 0; + + if (gst_structure_get_int (structure, "bpp", &bpp) && + gst_structure_get_int (structure, "endianness", &endianness) && + endianness == G_BIG_ENDIAN) { + if (gst_structure_get_int (structure, "red_mask", &rmask)) { + switch (bpp) { + case 32: + if (rmask == 0x00ff0000) + pix_fmt = PIX_FMT_ARGB; + else if (rmask == 0xff000000) + pix_fmt = PIX_FMT_RGBA; + else if (rmask == 0xff00) + pix_fmt = PIX_FMT_BGRA; + else if (rmask == 0xff) + pix_fmt = PIX_FMT_ABGR; + break; + case 24: + if (rmask == 0x0000FF) + pix_fmt = PIX_FMT_BGR24; + else + pix_fmt = PIX_FMT_RGB24; + break; + case 16: + if (endianness == G_BYTE_ORDER) + pix_fmt = PIX_FMT_RGB565; + break; + case 15: + if (endianness == G_BYTE_ORDER) + pix_fmt = PIX_FMT_RGB555; + break; + default: + /* nothing */ + break; + } + } else { + if (bpp == 8) { + pix_fmt = PIX_FMT_PAL8; + } + } + } + } + + return pix_fmt; +} + +static void +gst_ffmpegscale_fill_info (GstFFMpegScale * scale, GstVideoFormat format, + guint width, guint height, gint stride[], gint offset[]) +{ + gint i; + + for (i = 0; i < 3; i++) { + stride[i] = gst_video_format_get_row_stride (format, i, width); + offset[i] = gst_video_format_get_component_offset (format, i, width, + height); + /* stay close to the ffmpeg offset way */ + if (offset[i] < 3) + offset[i] = 0; + GST_DEBUG_OBJECT (scale, "format %d, component %d; stride %d, offset %d", + format, i, stride[i], offset[i]); + } +} + +static gboolean +gst_ffmpegscale_set_caps (GstBaseTransform * trans, GstCaps * incaps, + GstCaps * outcaps) +{ + GstFFMpegScale *scale = GST_FFMPEGSCALE (trans); + gint flags, swsflags; + GstVideoFormat in_format, out_format; + gboolean ok; + + g_return_val_if_fail (scale->method < + G_N_ELEMENTS (gst_ffmpegscale_method_flags), FALSE); + + if (scale->ctx) { + sws_freeContext (scale->ctx); + scale->ctx = NULL; + } + + ok = gst_video_format_parse_caps (incaps, &in_format, &scale->in_width, + &scale->in_height); + ok &= gst_video_format_parse_caps (outcaps, &out_format, &scale->out_width, + &scale->out_height); + scale->in_pixfmt = gst_ffmpeg_caps_to_pixfmt (incaps); + scale->out_pixfmt = gst_ffmpeg_caps_to_pixfmt (outcaps); + + if (!ok || scale->in_pixfmt == PIX_FMT_NONE || + scale->out_pixfmt == PIX_FMT_NONE || + in_format == GST_VIDEO_FORMAT_UNKNOWN || + out_format == GST_VIDEO_FORMAT_UNKNOWN) + goto refuse_caps; + + GST_DEBUG_OBJECT (scale, "format %d => %d, from=%dx%d -> to=%dx%d", in_format, + out_format, scale->in_width, scale->in_height, scale->out_width, + scale->out_height); + + gst_ffmpegscale_fill_info (scale, in_format, scale->in_width, + scale->in_height, scale->in_stride, scale->in_offset); + gst_ffmpegscale_fill_info (scale, out_format, scale->out_width, + scale->out_height, scale->out_stride, scale->out_offset); + + flags = oil_cpu_get_flags (); + swsflags = (flags & OIL_IMPL_FLAG_MMX ? SWS_CPU_CAPS_MMX : 0) + | (flags & OIL_IMPL_FLAG_MMXEXT ? SWS_CPU_CAPS_MMX2 : 0) + | (flags & OIL_IMPL_FLAG_3DNOW ? SWS_CPU_CAPS_3DNOW : 0) + | (flags & OIL_IMPL_FLAG_ALTIVEC ? SWS_CPU_CAPS_ALTIVEC : 0); + scale->ctx = sws_getContext (scale->in_width, scale->in_height, + scale->in_pixfmt, scale->out_width, scale->out_height, scale->out_pixfmt, + swsflags | gst_ffmpegscale_method_flags[scale->method], NULL, NULL, NULL); + if (!scale->ctx) + goto setup_failed; + + return TRUE; + + /* ERRORS */ +setup_failed: + { + GST_ELEMENT_ERROR (trans, LIBRARY, INIT, (NULL), (NULL)); + return FALSE; + } +refuse_caps: + { + GST_DEBUG_OBJECT (trans, "refused caps %" GST_PTR_FORMAT, incaps); + return FALSE; + } +} + +static GstFlowReturn +gst_ffmpegscale_transform (GstBaseTransform * trans, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + GstFFMpegScale *scale = GST_FFMPEGSCALE (trans); + guint8 *in_data[3] = { NULL, NULL, NULL }; + guint8 *out_data[3] = { NULL, NULL, NULL }; + gint i; + + for (i = 0; i < 3; i++) { + /* again; stay close to the ffmpeg offset way */ + if (!i || scale->in_offset[i]) + in_data[i] = GST_BUFFER_DATA (inbuf) + scale->in_offset[i]; + if (!i || scale->out_offset[i]) + out_data[i] = GST_BUFFER_DATA (outbuf) + scale->out_offset[i]; + } + + sws_scale (scale->ctx, in_data, scale->in_stride, 0, scale->in_height, + out_data, scale->out_stride); + + return GST_FLOW_OK; +} + +static gboolean +gst_ffmpegscale_handle_src_event (GstPad * pad, GstEvent * event) +{ + GstFFMpegScale *scale; + GstStructure *structure; + gdouble pointer; + gboolean res; + + scale = GST_FFMPEGSCALE (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NAVIGATION: + event = + GST_EVENT (gst_mini_object_make_writable (GST_MINI_OBJECT (event))); + + structure = (GstStructure *) gst_event_get_structure (event); + if (gst_structure_get_double (structure, "pointer_x", &pointer)) { + gst_structure_set (structure, + "pointer_x", G_TYPE_DOUBLE, + pointer * scale->in_width / scale->out_width, NULL); + } + if (gst_structure_get_double (structure, "pointer_y", &pointer)) { + gst_structure_set (structure, + "pointer_y", G_TYPE_DOUBLE, + pointer * scale->in_height / scale->out_height, NULL); + } + break; + default: + break; + } + + res = gst_pad_event_default (pad, event); + + gst_object_unref (scale); + + return res; +} + +static gboolean +gst_ffmpegscale_stop (GstBaseTransform * trans) +{ + GstFFMpegScale *scale = GST_FFMPEGSCALE (trans); + + gst_ffmpegscale_reset (scale); + + return TRUE; +} + +static void +gst_ffmpegscale_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstFFMpegScale *scale = GST_FFMPEGSCALE (object); + + switch (prop_id) { + case PROP_METHOD: + scale->method = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_ffmpegscale_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstFFMpegScale *scale = GST_FFMPEGSCALE (object); + + switch (prop_id) { + case PROP_METHOD: + g_value_set_enum (value, scale->method); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +#ifndef GST_DISABLE_GST_DEBUG +static void +gst_ffmpeg_log_callback (void *ptr, int level, const char *fmt, va_list vl) +{ + GstDebugLevel gst_level; + + switch (level) { + case AV_LOG_QUIET: + gst_level = GST_LEVEL_NONE; + break; + case AV_LOG_ERROR: + gst_level = GST_LEVEL_ERROR; + break; + case AV_LOG_INFO: + gst_level = GST_LEVEL_INFO; + break; + case AV_LOG_DEBUG: + gst_level = GST_LEVEL_DEBUG; + break; + default: + gst_level = GST_LEVEL_INFO; + break; + } + + gst_debug_log_valist (ffmpegscale_debug, gst_level, "", "", 0, NULL, fmt, vl); +} +#endif + +gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (ffmpegscale_debug, "ffvideoscale", 0, + "video scaling element"); +#ifndef GST_DISABLE_GST_DEBUG + av_log_set_callback (gst_ffmpeg_log_callback); +#endif + + return gst_element_register (plugin, "ffvideoscale", + GST_RANK_NONE, GST_TYPE_FFMPEGSCALE); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "ffvideoscale", + "videoscaling element (" FFMPEG_SOURCE ")", + plugin_init, + PACKAGE_VERSION, "GPL", "FFMpeg", "http://ffmpeg.sourceforge.net/")