mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 09:40:37 +00:00
f55b36e6aa
Pipeline such as: gst-launch-1.0 -vf videotestsrc ! video/x-raw,format=NV12,colorimetry=\(string\)bt709 \ ! videoscale ! video/x-raw,format=I420 ! fakesink Always trigger a error: ERROR video-info video-info.c:556:gst_video_info_from_caps: no width property given Because it is called before the fixate_size(), the src caps' resolution may be absent or not fixed. That causes that the src video info can not be created correctly and we can not inherit the colorimetry and chroma-site from the input caps. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2289>
1838 lines
65 KiB
C
1838 lines
65 KiB
C
/* GStreamer
|
|
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
|
* Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
|
|
* Copyright (C) 2005-2012 David Schleef <ds@schleef.org>
|
|
* Copyright (C) 2022 Thibault Saunier <tsaunier@igalia.com>
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-videoconvertscale
|
|
* @title: videoconvertscale
|
|
*
|
|
* This element resizes video frames and allows changing colorspace. By default
|
|
* the element will try to negotiate to the same size on the source and sinkpad
|
|
* so that no scaling is needed. It is therefore safe to insert this element in
|
|
* a pipeline to get more robust behaviour without any cost if no scaling is
|
|
* needed.
|
|
*
|
|
* This element supports a wide range of color spaces including various YUV and
|
|
* RGB formats and is therefore generally able to operate anywhere in a
|
|
* pipeline.
|
|
*
|
|
* ## Example pipelines
|
|
* |[
|
|
* gst-launch-1.0 -v filesrc location=videotestsrc.ogg ! oggdemux ! theoradec ! videoconvertscale ! autovideosink
|
|
* ]|
|
|
* Decode an Ogg/Theora and display the video. If the video sink chosen
|
|
* cannot perform scaling, the video scaling will be performed by videoconvertscale
|
|
* when you resize the video window.
|
|
* To create the test Ogg/Theora file refer to the documentation of theoraenc.
|
|
* |[
|
|
* gst-launch-1.0 -v filesrc location=videotestsrc.ogg ! oggdemux ! theoradec ! videoconvertscale ! video/x-raw,width=100 ! autovideosink
|
|
* ]|
|
|
* Decode an Ogg/Theora and display the video with a width of 100.
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
|
|
/*
|
|
* Formulas for PAR, DAR, width and height relations:
|
|
*
|
|
* dar_n w par_n
|
|
* ----- = - * -----
|
|
* dar_d h par_d
|
|
*
|
|
* par_n h dar_n
|
|
* ----- = - * -----
|
|
* par_d w dar_d
|
|
*
|
|
* dar_n par_d
|
|
* w = h * ----- * -----
|
|
* dar_d par_n
|
|
*
|
|
* dar_d par_n
|
|
* h = w * ----- * -----
|
|
* dar_n par_d
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <gst/video/gstvideometa.h>
|
|
#include <gst/video/gstvideopool.h>
|
|
|
|
#include "gstvideoconvertscale.h"
|
|
|
|
typedef struct
|
|
{
|
|
/* properties */
|
|
GstVideoScaleMethod method;
|
|
gboolean add_borders;
|
|
double sharpness;
|
|
double sharpen;
|
|
int submethod;
|
|
double envelope;
|
|
gint n_threads;
|
|
GstVideoDitherMethod dither;
|
|
guint dither_quantization;
|
|
GstVideoResamplerMethod chroma_resampler;
|
|
GstVideoAlphaMode alpha_mode;
|
|
GstVideoChromaMode chroma_mode;
|
|
GstVideoMatrixMode matrix_mode;
|
|
GstVideoGammaMode gamma_mode;
|
|
GstVideoPrimariesMode primaries_mode;
|
|
gdouble alpha_value;
|
|
|
|
GstVideoConverter *convert;
|
|
|
|
gint borders_h;
|
|
gint borders_w;
|
|
|
|
gboolean scales;
|
|
gboolean converts;
|
|
} GstVideoConvertScalePrivate;
|
|
|
|
#define gst_video_convert_scale_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GstVideoConvertScale, gst_video_convert_scale,
|
|
GST_TYPE_VIDEO_FILTER);
|
|
GST_ELEMENT_REGISTER_DEFINE (videoconvertscale, "videoconvertscale",
|
|
GST_RANK_SECONDARY, GST_TYPE_VIDEO_CONVERT_SCALE);
|
|
|
|
#define PRIV(self) ((GstVideoConvertScalePrivate*)gst_video_convert_scale_get_instance_private(((GstVideoConvertScale*) self)))
|
|
|
|
#define GST_CAT_DEFAULT video_convertscale_debug
|
|
GST_DEBUG_CATEGORY_STATIC (video_convertscale_debug);
|
|
GST_DEBUG_CATEGORY_STATIC (CAT_PERFORMANCE);
|
|
|
|
#define DEFAULT_PROP_METHOD GST_VIDEO_SCALE_BILINEAR
|
|
#define DEFAULT_PROP_ADD_BORDERS TRUE
|
|
#define DEFAULT_PROP_SHARPNESS 1.0
|
|
#define DEFAULT_PROP_SHARPEN 0.0
|
|
#define DEFAULT_PROP_DITHER GST_VIDEO_DITHER_BAYER
|
|
#define DEFAULT_PROP_ENVELOPE 2.0
|
|
#define DEFAULT_PROP_DITHER_QUANTIZATION 1
|
|
#define DEFAULT_PROP_CHROMA_RESAMPLER GST_VIDEO_RESAMPLER_METHOD_LINEAR
|
|
#define DEFAULT_PROP_ALPHA_MODE GST_VIDEO_ALPHA_MODE_COPY
|
|
#define DEFAULT_PROP_ALPHA_VALUE 1.0
|
|
#define DEFAULT_PROP_CHROMA_MODE GST_VIDEO_CHROMA_MODE_FULL
|
|
#define DEFAULT_PROP_MATRIX_MODE GST_VIDEO_MATRIX_MODE_FULL
|
|
#define DEFAULT_PROP_GAMMA_MODE GST_VIDEO_GAMMA_MODE_NONE
|
|
#define DEFAULT_PROP_PRIMARIES_MODE GST_VIDEO_PRIMARIES_MODE_NONE
|
|
#define DEFAULT_PROP_N_THREADS 1
|
|
|
|
static GQuark _colorspace_quark;
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_METHOD,
|
|
PROP_ADD_BORDERS,
|
|
PROP_SHARPNESS,
|
|
PROP_SHARPEN,
|
|
PROP_DITHER,
|
|
PROP_SUBMETHOD,
|
|
PROP_ENVELOPE,
|
|
PROP_N_THREADS,
|
|
PROP_DITHER_QUANTIZATION,
|
|
PROP_CHROMA_RESAMPLER,
|
|
PROP_ALPHA_MODE,
|
|
PROP_ALPHA_VALUE,
|
|
PROP_CHROMA_MODE,
|
|
PROP_MATRIX_MODE,
|
|
PROP_GAMMA_MODE,
|
|
PROP_PRIMARIES_MODE,
|
|
};
|
|
|
|
#undef GST_VIDEO_SIZE_RANGE
|
|
#define GST_VIDEO_SIZE_RANGE "(int) [ 1, 32767]"
|
|
|
|
/* FIXME: add v210 support
|
|
* FIXME: add v216 support
|
|
* FIXME: add UYVP support
|
|
* FIXME: add A420 support
|
|
* FIXME: add YUV9 support
|
|
* FIXME: add YVU9 support
|
|
* FIXME: add IYU1 support
|
|
* FIXME: add r210 support
|
|
*/
|
|
|
|
#define GST_VIDEO_FORMATS GST_VIDEO_FORMATS_ALL
|
|
|
|
static GstStaticCaps gst_video_convert_scale_format_caps =
|
|
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS) ";"
|
|
GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS));
|
|
|
|
static GQuark _size_quark;
|
|
static GQuark _scale_quark;
|
|
|
|
#define GST_TYPE_VIDEO_SCALE_METHOD (gst_video_scale_method_get_type())
|
|
static GType
|
|
gst_video_scale_method_get_type (void)
|
|
{
|
|
static GType video_scale_method_type = 0;
|
|
|
|
static const GEnumValue video_scale_methods[] = {
|
|
{GST_VIDEO_SCALE_NEAREST, "Nearest Neighbour", "nearest-neighbour"},
|
|
{GST_VIDEO_SCALE_BILINEAR, "Bilinear (2-tap)", "bilinear"},
|
|
{GST_VIDEO_SCALE_4TAP, "4-tap Sinc", "4-tap"},
|
|
{GST_VIDEO_SCALE_LANCZOS, "Lanczos", "lanczos"},
|
|
{GST_VIDEO_SCALE_BILINEAR2, "Bilinear (multi-tap)", "bilinear2"},
|
|
{GST_VIDEO_SCALE_SINC, "Sinc (multi-tap)", "sinc"},
|
|
{GST_VIDEO_SCALE_HERMITE, "Hermite (multi-tap)", "hermite"},
|
|
{GST_VIDEO_SCALE_SPLINE, "Spline (multi-tap)", "spline"},
|
|
{GST_VIDEO_SCALE_CATROM, "Catmull-Rom (multi-tap)", "catrom"},
|
|
{GST_VIDEO_SCALE_MITCHELL, "Mitchell (multi-tap)", "mitchell"},
|
|
{0, NULL, NULL},
|
|
};
|
|
|
|
if (!video_scale_method_type) {
|
|
video_scale_method_type =
|
|
g_enum_register_static ("GstVideoScaleMethod", video_scale_methods);
|
|
}
|
|
return video_scale_method_type;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_video_convert_scale_get_capslist (void)
|
|
{
|
|
static GstCaps *caps = NULL;
|
|
static gsize inited = 0;
|
|
|
|
if (g_once_init_enter (&inited)) {
|
|
caps = gst_static_caps_get (&gst_video_convert_scale_format_caps);
|
|
g_once_init_leave (&inited, 1);
|
|
}
|
|
return caps;
|
|
}
|
|
|
|
static GstPadTemplate *
|
|
gst_video_convert_scale_src_template_factory (void)
|
|
{
|
|
return gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
|
|
gst_video_convert_scale_get_capslist ());
|
|
}
|
|
|
|
static GstPadTemplate *
|
|
gst_video_convert_scale_sink_template_factory (void)
|
|
{
|
|
return gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
|
|
gst_video_convert_scale_get_capslist ());
|
|
}
|
|
|
|
|
|
static void gst_video_convert_scale_finalize (GstVideoConvertScale * self);
|
|
static gboolean gst_video_convert_scale_src_event (GstBaseTransform * trans,
|
|
GstEvent * event);
|
|
|
|
/* base transform vmethods */
|
|
static GstCaps *gst_video_convert_scale_transform_caps (GstBaseTransform *
|
|
trans, GstPadDirection direction, GstCaps * caps, GstCaps * filter);
|
|
static GstCaps *gst_video_convert_scale_fixate_caps (GstBaseTransform * base,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
|
|
static gboolean gst_video_convert_scale_transform_meta (GstBaseTransform *
|
|
trans, GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf);
|
|
|
|
static gboolean gst_video_convert_scale_set_info (GstVideoFilter * filter,
|
|
GstCaps * in, GstVideoInfo * in_info, GstCaps * out,
|
|
GstVideoInfo * out_info);
|
|
static GstFlowReturn gst_video_convert_scale_transform_frame (GstVideoFilter *
|
|
filter, GstVideoFrame * in, GstVideoFrame * out);
|
|
|
|
static void gst_video_convert_scale_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec);
|
|
static void gst_video_convert_scale_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec);
|
|
|
|
static GstCapsFeatures *features_format_interlaced,
|
|
*features_format_interlaced_sysmem;
|
|
|
|
static gboolean
|
|
gst_video_convert_scale_filter_meta (GstBaseTransform * trans, GstQuery * query,
|
|
GType api, const GstStructure * params)
|
|
{
|
|
/* This element cannot passthrough the crop meta, because it would convert the
|
|
* wrong sub-region of the image, and worst, our output image may not be large
|
|
* enough for the crop to be applied later */
|
|
if (api == GST_VIDEO_CROP_META_API_TYPE)
|
|
return FALSE;
|
|
|
|
/* propose all other metadata upstream */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_video_convert_scale_class_init (GstVideoConvertScaleClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
GstElementClass *element_class = (GstElementClass *) klass;
|
|
GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
|
|
GstVideoFilterClass *filter_class = (GstVideoFilterClass *) klass;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (video_convertscale_debug, "videoconvertscale", 0,
|
|
"videoconvertscale element");
|
|
GST_DEBUG_CATEGORY_GET (CAT_PERFORMANCE, "GST_PERFORMANCE");
|
|
|
|
features_format_interlaced =
|
|
gst_caps_features_new (GST_CAPS_FEATURE_FORMAT_INTERLACED, NULL);
|
|
features_format_interlaced_sysmem =
|
|
gst_caps_features_copy (features_format_interlaced);
|
|
gst_caps_features_add (features_format_interlaced_sysmem,
|
|
GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY);
|
|
|
|
_colorspace_quark = g_quark_from_static_string ("colorspace");
|
|
|
|
gobject_class->finalize =
|
|
(GObjectFinalizeFunc) gst_video_convert_scale_finalize;
|
|
gobject_class->set_property = gst_video_convert_scale_set_property;
|
|
gobject_class->get_property = gst_video_convert_scale_get_property;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_METHOD,
|
|
g_param_spec_enum ("method", "method", "method",
|
|
GST_TYPE_VIDEO_SCALE_METHOD, DEFAULT_PROP_METHOD,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_ADD_BORDERS,
|
|
g_param_spec_boolean ("add-borders", "Add Borders",
|
|
"Add black borders if necessary to keep the display aspect ratio",
|
|
DEFAULT_PROP_ADD_BORDERS,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SHARPNESS,
|
|
g_param_spec_double ("sharpness", "Sharpness",
|
|
"Sharpness of filter", 0.5, 1.5, DEFAULT_PROP_SHARPNESS,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SHARPEN,
|
|
g_param_spec_double ("sharpen", "Sharpen",
|
|
"Sharpening", 0.0, 1.0, DEFAULT_PROP_SHARPEN,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DITHER,
|
|
g_param_spec_enum ("dither", "Dither", "Apply dithering while converting",
|
|
gst_video_dither_method_get_type (), DEFAULT_PROP_DITHER,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_ENVELOPE,
|
|
g_param_spec_double ("envelope", "Envelope",
|
|
"Size of filter envelope", 1.0, 5.0, DEFAULT_PROP_ENVELOPE,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_N_THREADS,
|
|
g_param_spec_uint ("n-threads", "Threads",
|
|
"Maximum number of threads to use", 0, G_MAXUINT,
|
|
DEFAULT_PROP_N_THREADS,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DITHER_QUANTIZATION,
|
|
g_param_spec_uint ("dither-quantization", "Dither Quantize",
|
|
"Quantizer to use", 0, G_MAXUINT, DEFAULT_PROP_DITHER_QUANTIZATION,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_CHROMA_RESAMPLER,
|
|
g_param_spec_enum ("chroma-resampler", "Chroma resampler",
|
|
"Chroma resampler method", gst_video_resampler_method_get_type (),
|
|
DEFAULT_PROP_CHROMA_RESAMPLER,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_ALPHA_MODE,
|
|
g_param_spec_enum ("alpha-mode", "Alpha Mode",
|
|
"Alpha Mode to use", gst_video_alpha_mode_get_type (),
|
|
DEFAULT_PROP_ALPHA_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_ALPHA_VALUE,
|
|
g_param_spec_double ("alpha-value", "Alpha Value",
|
|
"Alpha Value to use", 0.0, 1.0,
|
|
DEFAULT_PROP_ALPHA_VALUE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_CHROMA_MODE,
|
|
g_param_spec_enum ("chroma-mode", "Chroma Mode", "Chroma Resampling Mode",
|
|
gst_video_chroma_mode_get_type (), DEFAULT_PROP_CHROMA_MODE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_MATRIX_MODE,
|
|
g_param_spec_enum ("matrix-mode", "Matrix Mode", "Matrix Conversion Mode",
|
|
gst_video_matrix_mode_get_type (), DEFAULT_PROP_MATRIX_MODE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_GAMMA_MODE,
|
|
g_param_spec_enum ("gamma-mode", "Gamma Mode", "Gamma Conversion Mode",
|
|
gst_video_gamma_mode_get_type (), DEFAULT_PROP_GAMMA_MODE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_PRIMARIES_MODE,
|
|
g_param_spec_enum ("primaries-mode", "Primaries Mode",
|
|
"Primaries Conversion Mode", gst_video_primaries_mode_get_type (),
|
|
DEFAULT_PROP_PRIMARIES_MODE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"Video colorspace converter and scaler",
|
|
"Filter/Converter/Video/Scaler/Colorspace",
|
|
"Resizes video and allow color conversion",
|
|
"Wim Taymans <wim.taymans@gmail.com>");
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_video_convert_scale_sink_template_factory ());
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_video_convert_scale_src_template_factory ());
|
|
|
|
_size_quark = g_quark_from_static_string (GST_META_TAG_VIDEO_SIZE_STR);
|
|
_scale_quark = gst_video_meta_transform_scale_get_quark ();
|
|
|
|
gst_type_mark_as_plugin_api (GST_TYPE_VIDEO_SCALE_METHOD, 0);
|
|
trans_class->transform_caps =
|
|
GST_DEBUG_FUNCPTR (gst_video_convert_scale_transform_caps);
|
|
trans_class->fixate_caps =
|
|
GST_DEBUG_FUNCPTR (gst_video_convert_scale_fixate_caps);
|
|
trans_class->filter_meta =
|
|
GST_DEBUG_FUNCPTR (gst_video_convert_scale_filter_meta);
|
|
trans_class->src_event =
|
|
GST_DEBUG_FUNCPTR (gst_video_convert_scale_src_event);
|
|
trans_class->transform_meta =
|
|
GST_DEBUG_FUNCPTR (gst_video_convert_scale_transform_meta);
|
|
|
|
filter_class->set_info = GST_DEBUG_FUNCPTR (gst_video_convert_scale_set_info);
|
|
filter_class->transform_frame =
|
|
GST_DEBUG_FUNCPTR (gst_video_convert_scale_transform_frame);
|
|
}
|
|
|
|
static void
|
|
gst_video_convert_scale_init (GstVideoConvertScale * self)
|
|
{
|
|
GstVideoConvertScalePrivate *priv = PRIV (self);
|
|
|
|
priv->method = DEFAULT_PROP_METHOD;
|
|
priv->add_borders = DEFAULT_PROP_ADD_BORDERS;
|
|
priv->sharpness = DEFAULT_PROP_SHARPNESS;
|
|
priv->sharpen = DEFAULT_PROP_SHARPEN;
|
|
priv->envelope = DEFAULT_PROP_ENVELOPE;
|
|
priv->n_threads = DEFAULT_PROP_N_THREADS;
|
|
priv->dither = DEFAULT_PROP_DITHER;
|
|
priv->dither_quantization = DEFAULT_PROP_DITHER_QUANTIZATION;
|
|
priv->chroma_resampler = DEFAULT_PROP_CHROMA_RESAMPLER;
|
|
priv->alpha_mode = DEFAULT_PROP_ALPHA_MODE;
|
|
priv->alpha_value = DEFAULT_PROP_ALPHA_VALUE;
|
|
priv->chroma_mode = DEFAULT_PROP_CHROMA_MODE;
|
|
priv->matrix_mode = DEFAULT_PROP_MATRIX_MODE;
|
|
priv->gamma_mode = DEFAULT_PROP_GAMMA_MODE;
|
|
priv->primaries_mode = DEFAULT_PROP_PRIMARIES_MODE;
|
|
priv->scales = TRUE;
|
|
priv->converts = TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_video_convert_scale_finalize (GstVideoConvertScale * self)
|
|
{
|
|
GstVideoConvertScalePrivate *priv = PRIV (self);
|
|
|
|
if (priv->convert)
|
|
gst_video_converter_free (priv->convert);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT (self));
|
|
}
|
|
|
|
static void
|
|
gst_video_convert_scale_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstVideoConvertScalePrivate *priv = PRIV (object);
|
|
|
|
GST_OBJECT_LOCK (object);
|
|
switch (prop_id) {
|
|
case PROP_METHOD:
|
|
priv->method = g_value_get_enum (value);
|
|
break;
|
|
case PROP_ADD_BORDERS:
|
|
priv->add_borders = g_value_get_boolean (value);
|
|
GST_OBJECT_UNLOCK (object);
|
|
|
|
gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM_CAST (object));
|
|
return;
|
|
case PROP_SHARPNESS:
|
|
priv->sharpness = g_value_get_double (value);
|
|
break;
|
|
case PROP_SHARPEN:
|
|
priv->sharpen = g_value_get_double (value);
|
|
break;
|
|
case PROP_SUBMETHOD:
|
|
priv->submethod = g_value_get_int (value);
|
|
break;
|
|
case PROP_ENVELOPE:
|
|
priv->envelope = g_value_get_double (value);
|
|
break;
|
|
case PROP_N_THREADS:
|
|
priv->n_threads = g_value_get_uint (value);
|
|
break;
|
|
case PROP_DITHER:
|
|
priv->dither = g_value_get_enum (value);
|
|
break;
|
|
case PROP_CHROMA_RESAMPLER:
|
|
priv->chroma_resampler = g_value_get_enum (value);
|
|
break;
|
|
case PROP_ALPHA_MODE:
|
|
priv->alpha_mode = g_value_get_enum (value);
|
|
break;
|
|
case PROP_ALPHA_VALUE:
|
|
priv->alpha_value = g_value_get_double (value);
|
|
break;
|
|
case PROP_CHROMA_MODE:
|
|
priv->chroma_mode = g_value_get_enum (value);
|
|
break;
|
|
case PROP_MATRIX_MODE:
|
|
priv->matrix_mode = g_value_get_enum (value);
|
|
break;
|
|
case PROP_GAMMA_MODE:
|
|
priv->gamma_mode = g_value_get_enum (value);
|
|
break;
|
|
case PROP_PRIMARIES_MODE:
|
|
priv->primaries_mode = g_value_get_enum (value);
|
|
break;
|
|
case PROP_DITHER_QUANTIZATION:
|
|
priv->dither_quantization = g_value_get_uint (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
GST_OBJECT_UNLOCK (object);
|
|
}
|
|
|
|
static void
|
|
gst_video_convert_scale_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstVideoConvertScalePrivate *priv = PRIV (object);
|
|
|
|
GST_OBJECT_LOCK (object);
|
|
switch (prop_id) {
|
|
case PROP_METHOD:
|
|
g_value_set_enum (value, priv->method);
|
|
break;
|
|
case PROP_ADD_BORDERS:
|
|
g_value_set_boolean (value, priv->add_borders);
|
|
break;
|
|
case PROP_SHARPNESS:
|
|
g_value_set_double (value, priv->sharpness);
|
|
break;
|
|
case PROP_SHARPEN:
|
|
g_value_set_double (value, priv->sharpen);
|
|
break;
|
|
case PROP_SUBMETHOD:
|
|
g_value_set_int (value, priv->submethod);
|
|
break;
|
|
case PROP_ENVELOPE:
|
|
g_value_set_double (value, priv->envelope);
|
|
break;
|
|
case PROP_N_THREADS:
|
|
g_value_set_uint (value, priv->n_threads);
|
|
break;
|
|
case PROP_DITHER:
|
|
g_value_set_enum (value, priv->dither);
|
|
break;
|
|
case PROP_CHROMA_RESAMPLER:
|
|
g_value_set_enum (value, priv->chroma_resampler);
|
|
break;
|
|
case PROP_ALPHA_MODE:
|
|
g_value_set_enum (value, priv->alpha_mode);
|
|
break;
|
|
case PROP_ALPHA_VALUE:
|
|
g_value_set_double (value, priv->alpha_value);
|
|
break;
|
|
case PROP_CHROMA_MODE:
|
|
g_value_set_enum (value, priv->chroma_mode);
|
|
break;
|
|
case PROP_MATRIX_MODE:
|
|
g_value_set_enum (value, priv->matrix_mode);
|
|
break;
|
|
case PROP_GAMMA_MODE:
|
|
g_value_set_enum (value, priv->gamma_mode);
|
|
break;
|
|
case PROP_PRIMARIES_MODE:
|
|
g_value_set_enum (value, priv->primaries_mode);
|
|
break;
|
|
case PROP_DITHER_QUANTIZATION:
|
|
g_value_set_uint (value, priv->dither_quantization);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
GST_OBJECT_UNLOCK (object);
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_video_convert_caps_remove_format_and_rangify_size_info (GstVideoConvertScale
|
|
* self, GstCaps * caps)
|
|
{
|
|
GstCaps *ret;
|
|
GstStructure *structure;
|
|
GstCapsFeatures *features;
|
|
gint i, n;
|
|
GstVideoConvertScalePrivate *priv = PRIV (self);
|
|
|
|
ret = gst_caps_new_empty ();
|
|
|
|
n = gst_caps_get_size (caps);
|
|
for (i = 0; i < n; i++) {
|
|
structure = gst_caps_get_structure (caps, i);
|
|
features = gst_caps_get_features (caps, i);
|
|
|
|
/* If this is already expressed by the existing caps
|
|
* skip this structure */
|
|
if (i > 0 && gst_caps_is_subset_structure_full (ret, structure, features))
|
|
continue;
|
|
|
|
structure = gst_structure_copy (structure);
|
|
/* Only remove format info for the cases when we can actually convert */
|
|
if (!gst_caps_features_is_any (features)
|
|
&& (gst_caps_features_is_equal (features,
|
|
GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY)
|
|
|| gst_caps_features_is_equal (features, features_format_interlaced)
|
|
|| gst_caps_features_is_equal (features,
|
|
features_format_interlaced_sysmem))) {
|
|
|
|
if (priv->scales) {
|
|
gst_structure_set (structure, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
|
|
"height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
|
|
/* if pixel aspect ratio, make a range of it */
|
|
if (gst_structure_has_field (structure, "pixel-aspect-ratio")) {
|
|
gst_structure_set (structure, "pixel-aspect-ratio",
|
|
GST_TYPE_FRACTION_RANGE, 1, G_MAXINT, G_MAXINT, 1, NULL);
|
|
}
|
|
}
|
|
|
|
if (priv->converts) {
|
|
gst_structure_remove_fields (structure, "format", "colorimetry",
|
|
"chroma-site", NULL);
|
|
}
|
|
}
|
|
gst_caps_append_structure_full (ret, structure,
|
|
gst_caps_features_copy (features));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_video_convert_scale_transform_caps (GstBaseTransform * trans,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * filter)
|
|
{
|
|
gint i;
|
|
GstCaps *ret;
|
|
GstVideoConvertScale *self = GST_VIDEO_CONVERT_SCALE (trans);
|
|
|
|
GST_DEBUG_OBJECT (trans,
|
|
"Transforming caps %" GST_PTR_FORMAT " in direction %s", caps,
|
|
(direction == GST_PAD_SINK) ? "sink" : "src");
|
|
|
|
ret = gst_video_convert_caps_remove_format_and_rangify_size_info (self, caps);
|
|
if (filter) {
|
|
GstCaps *intersection;
|
|
|
|
intersection =
|
|
gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (ret);
|
|
ret = intersection;
|
|
}
|
|
|
|
if (GST_VIDEO_CONVERT_SCALE_GET_CLASS (trans)->any_memory)
|
|
return ret;
|
|
|
|
for (i = 0; i < gst_caps_get_size (ret); i++) {
|
|
gint j;
|
|
GstCapsFeatures *f = gst_caps_get_features (ret, i);
|
|
|
|
if (!f || gst_caps_features_is_any (f) ||
|
|
gst_caps_features_is_equal (f, GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY))
|
|
continue;
|
|
|
|
for (j = 0; j < gst_caps_features_get_size (f); j++) {
|
|
const gchar *feature = gst_caps_features_get_nth (f, j);
|
|
|
|
if (g_str_has_prefix (feature, "memory:")) {
|
|
GST_DEBUG_OBJECT (trans, "Can not work with memory `%s`", feature);
|
|
gst_caps_remove_structure (ret, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (trans, "returning caps: %" GST_PTR_FORMAT, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_convert_scale_transform_meta (GstBaseTransform * trans,
|
|
GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf)
|
|
{
|
|
GstVideoFilter *videofilter = GST_VIDEO_FILTER (trans);
|
|
const GstMetaInfo *info = meta->info;
|
|
const gchar *const *tags;
|
|
const gchar *const *curr = NULL;
|
|
gboolean should_copy = TRUE;
|
|
const gchar *const valid_tags[] = {
|
|
GST_META_TAG_VIDEO_STR,
|
|
GST_META_TAG_VIDEO_ORIENTATION_STR,
|
|
GST_META_TAG_VIDEO_SIZE_STR,
|
|
};
|
|
|
|
tags = gst_meta_api_type_get_tags (info->api);
|
|
|
|
/* No specific tags, we are good to copy */
|
|
if (!tags) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (gst_meta_api_type_has_tag (info->api, _colorspace_quark)) {
|
|
/* don't copy colorspace specific metadata, FIXME, we need a MetaTransform
|
|
* for the colorspace metadata. */
|
|
return FALSE;
|
|
}
|
|
|
|
/* We are only changing size, we can preserve other metas tagged as
|
|
orientation and colorspace */
|
|
for (curr = tags; *curr; ++curr) {
|
|
|
|
/* We dont handle any other tag */
|
|
if (!g_strv_contains (valid_tags, *curr)) {
|
|
should_copy = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Cant handle the tags in this meta, let the parent class handle it */
|
|
if (!should_copy) {
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->transform_meta (trans,
|
|
outbuf, meta, inbuf);
|
|
}
|
|
|
|
/* This meta is size sensitive, try to transform it accordingly */
|
|
if (gst_meta_api_type_has_tag (info->api, _size_quark)) {
|
|
GstVideoMetaTransform trans =
|
|
{ &videofilter->in_info, &videofilter->out_info };
|
|
|
|
if (info->transform_func)
|
|
info->transform_func (outbuf, meta, inbuf, _scale_quark, &trans);
|
|
return FALSE;
|
|
}
|
|
|
|
/* No need to transform, we can safely copy this meta */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_convert_scale_set_info (GstVideoFilter * filter, GstCaps * in,
|
|
GstVideoInfo * in_info, GstCaps * out, GstVideoInfo * out_info)
|
|
{
|
|
GstVideoConvertScale *self = GST_VIDEO_CONVERT_SCALE (filter);
|
|
GstVideoConvertScalePrivate *priv = PRIV (self);
|
|
gint from_dar_n, from_dar_d, to_dar_n, to_dar_d;
|
|
GstVideoInfo tmp_info;
|
|
|
|
if (priv->convert) {
|
|
gst_video_converter_free (priv->convert);
|
|
priv->convert = NULL;
|
|
}
|
|
|
|
if (!gst_util_fraction_multiply (in_info->width,
|
|
in_info->height, in_info->par_n, in_info->par_d, &from_dar_n,
|
|
&from_dar_d)) {
|
|
from_dar_n = from_dar_d = -1;
|
|
}
|
|
|
|
if (!gst_util_fraction_multiply (out_info->width,
|
|
out_info->height, out_info->par_n, out_info->par_d, &to_dar_n,
|
|
&to_dar_d)) {
|
|
to_dar_n = to_dar_d = -1;
|
|
}
|
|
|
|
priv->borders_w = priv->borders_h = 0;
|
|
if (to_dar_n != from_dar_n || to_dar_d != from_dar_d) {
|
|
if (priv->add_borders) {
|
|
gint n, d, to_h, to_w;
|
|
|
|
if (from_dar_n != -1 && from_dar_d != -1
|
|
&& gst_util_fraction_multiply (from_dar_n, from_dar_d,
|
|
out_info->par_d, out_info->par_n, &n, &d)) {
|
|
to_h = gst_util_uint64_scale_int (out_info->width, d, n);
|
|
if (to_h <= out_info->height) {
|
|
priv->borders_h = out_info->height - to_h;
|
|
priv->borders_w = 0;
|
|
} else {
|
|
to_w = gst_util_uint64_scale_int (out_info->height, n, d);
|
|
g_assert (to_w <= out_info->width);
|
|
priv->borders_h = 0;
|
|
priv->borders_w = out_info->width - to_w;
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT (self, "Can't calculate borders");
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT (self, "Can't keep DAR!");
|
|
}
|
|
}
|
|
|
|
/* if present, these must match */
|
|
if (in_info->interlace_mode != out_info->interlace_mode)
|
|
goto format_mismatch;
|
|
|
|
/* if the only thing different in the caps is the transfer function, and
|
|
* we're converting between equivalent transfer functions, do passthrough */
|
|
tmp_info = *in_info;
|
|
tmp_info.colorimetry.transfer = out_info->colorimetry.transfer;
|
|
if (gst_video_info_is_equal (&tmp_info, out_info)) {
|
|
if (gst_video_transfer_function_is_equivalent (in_info->colorimetry.
|
|
transfer, in_info->finfo->bits, out_info->colorimetry.transfer,
|
|
out_info->finfo->bits)) {
|
|
gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (filter), TRUE);
|
|
}
|
|
} else {
|
|
GstStructure *options;
|
|
GST_CAT_DEBUG_OBJECT (CAT_PERFORMANCE, filter, "setup videoscaling");
|
|
gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (filter), FALSE);
|
|
|
|
options = gst_structure_new_empty ("videoconvertscale");
|
|
|
|
switch (priv->method) {
|
|
case GST_VIDEO_SCALE_NEAREST:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_NEAREST,
|
|
NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_BILINEAR:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_LINEAR,
|
|
GST_VIDEO_RESAMPLER_OPT_MAX_TAPS, G_TYPE_INT, 2, NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_4TAP:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_SINC,
|
|
GST_VIDEO_RESAMPLER_OPT_MAX_TAPS, G_TYPE_INT, 4, NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_LANCZOS:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_LANCZOS,
|
|
NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_BILINEAR2:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_LINEAR,
|
|
NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_SINC:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_SINC,
|
|
NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_HERMITE:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_CUBIC,
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_B, G_TYPE_DOUBLE, (gdouble) 0.0,
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_C, G_TYPE_DOUBLE, (gdouble) 0.0,
|
|
NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_SPLINE:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_CUBIC,
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_B, G_TYPE_DOUBLE, (gdouble) 1.0,
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_C, G_TYPE_DOUBLE, (gdouble) 0.0,
|
|
NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_CATROM:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_CUBIC,
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_B, G_TYPE_DOUBLE, (gdouble) 0.0,
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_C, G_TYPE_DOUBLE, (gdouble) 0.5,
|
|
NULL);
|
|
break;
|
|
case GST_VIDEO_SCALE_MITCHELL:
|
|
gst_structure_set (options,
|
|
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, GST_VIDEO_RESAMPLER_METHOD_CUBIC,
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_B, G_TYPE_DOUBLE, (gdouble) 1.0 / 3.0,
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_C, G_TYPE_DOUBLE, (gdouble) 1.0 / 3.0,
|
|
NULL);
|
|
break;
|
|
}
|
|
gst_structure_set (options,
|
|
GST_VIDEO_RESAMPLER_OPT_ENVELOPE, G_TYPE_DOUBLE, priv->envelope,
|
|
GST_VIDEO_RESAMPLER_OPT_SHARPNESS, G_TYPE_DOUBLE, priv->sharpness,
|
|
GST_VIDEO_RESAMPLER_OPT_SHARPEN, G_TYPE_DOUBLE, priv->sharpen,
|
|
GST_VIDEO_CONVERTER_OPT_DEST_X, G_TYPE_INT, priv->borders_w / 2,
|
|
GST_VIDEO_CONVERTER_OPT_DEST_Y, G_TYPE_INT, priv->borders_h / 2,
|
|
GST_VIDEO_CONVERTER_OPT_DEST_WIDTH, G_TYPE_INT,
|
|
out_info->width - priv->borders_w, GST_VIDEO_CONVERTER_OPT_DEST_HEIGHT,
|
|
G_TYPE_INT, out_info->height - priv->borders_h,
|
|
GST_VIDEO_CONVERTER_OPT_DITHER_METHOD, GST_TYPE_VIDEO_DITHER_METHOD,
|
|
priv->dither, GST_VIDEO_CONVERTER_OPT_DITHER_QUANTIZATION, G_TYPE_UINT,
|
|
priv->dither_quantization,
|
|
GST_VIDEO_CONVERTER_OPT_CHROMA_RESAMPLER_METHOD,
|
|
GST_TYPE_VIDEO_RESAMPLER_METHOD, priv->chroma_resampler,
|
|
GST_VIDEO_CONVERTER_OPT_ALPHA_MODE, GST_TYPE_VIDEO_ALPHA_MODE,
|
|
priv->alpha_mode, GST_VIDEO_CONVERTER_OPT_ALPHA_VALUE, G_TYPE_DOUBLE,
|
|
priv->alpha_value, GST_VIDEO_CONVERTER_OPT_CHROMA_MODE,
|
|
GST_TYPE_VIDEO_CHROMA_MODE, priv->chroma_mode,
|
|
GST_VIDEO_CONVERTER_OPT_MATRIX_MODE, GST_TYPE_VIDEO_MATRIX_MODE,
|
|
priv->matrix_mode, GST_VIDEO_CONVERTER_OPT_GAMMA_MODE,
|
|
GST_TYPE_VIDEO_GAMMA_MODE, priv->gamma_mode,
|
|
GST_VIDEO_CONVERTER_OPT_PRIMARIES_MODE, GST_TYPE_VIDEO_PRIMARIES_MODE,
|
|
priv->primaries_mode, GST_VIDEO_CONVERTER_OPT_THREADS, G_TYPE_UINT,
|
|
priv->n_threads, NULL);
|
|
|
|
priv->convert = gst_video_converter_new (in_info, out_info, options);
|
|
if (priv->convert == NULL)
|
|
goto no_convert;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (filter, "converting format %s -> %s",
|
|
gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (in_info)),
|
|
gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (out_info)));
|
|
GST_DEBUG_OBJECT (self, "from=%dx%d (par=%d/%d dar=%d/%d), size %"
|
|
G_GSIZE_FORMAT " -> to=%dx%d (par=%d/%d dar=%d/%d borders=%d:%d), "
|
|
"size %" G_GSIZE_FORMAT,
|
|
in_info->width, in_info->height, in_info->par_n, in_info->par_d,
|
|
from_dar_n, from_dar_d, in_info->size, out_info->width,
|
|
out_info->height, out_info->par_n, out_info->par_d, to_dar_n, to_dar_d,
|
|
priv->borders_w, priv->borders_h, out_info->size);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
format_mismatch:
|
|
{
|
|
GST_ERROR_OBJECT (self, "input and output formats do not match");
|
|
return FALSE;
|
|
}
|
|
no_convert:
|
|
{
|
|
GST_ERROR_OBJECT (self, "could not create converter");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This is an incomplete matrix of in formats and a score for the preferred output
|
|
* format.
|
|
*
|
|
* out: RGB24 RGB16 ARGB AYUV YUV444 YUV422 YUV420 YUV411 YUV410 PAL GRAY
|
|
* in
|
|
* RGB24 0 2 1 2 2 3 4 5 6 7 8
|
|
* RGB16 1 0 1 2 2 3 4 5 6 7 8
|
|
* ARGB 2 3 0 1 4 5 6 7 8 9 10
|
|
* AYUV 3 4 1 0 2 5 6 7 8 9 10
|
|
* YUV444 2 4 3 1 0 5 6 7 8 9 10
|
|
* YUV422 3 5 4 2 1 0 6 7 8 9 10
|
|
* YUV420 4 6 5 3 2 1 0 7 8 9 10
|
|
* YUV411 4 6 5 3 2 1 7 0 8 9 10
|
|
* YUV410 6 8 7 5 4 3 2 1 0 9 10
|
|
* PAL 1 3 2 6 4 6 7 8 9 0 10
|
|
* GRAY 1 4 3 2 1 5 6 7 8 9 0
|
|
*
|
|
* PAL or GRAY are never preferred, if we can we would convert to PAL instead
|
|
* of GRAY, though
|
|
* less subsampling is preferred and if any, preferably horizontal
|
|
* We would like to keep the alpha, even if we would need to to colorspace conversion
|
|
* or lose depth.
|
|
*/
|
|
#define SCORE_FORMAT_CHANGE 1
|
|
#define SCORE_DEPTH_CHANGE 1
|
|
#define SCORE_ALPHA_CHANGE 1
|
|
#define SCORE_CHROMA_W_CHANGE 1
|
|
#define SCORE_CHROMA_H_CHANGE 1
|
|
#define SCORE_PALETTE_CHANGE 1
|
|
|
|
#define SCORE_COLORSPACE_LOSS 2 /* RGB <-> YUV */
|
|
#define SCORE_DEPTH_LOSS 4 /* change bit depth */
|
|
#define SCORE_ALPHA_LOSS 8 /* lose the alpha channel */
|
|
#define SCORE_CHROMA_W_LOSS 16 /* vertical subsample */
|
|
#define SCORE_CHROMA_H_LOSS 32 /* horizontal subsample */
|
|
#define SCORE_PALETTE_LOSS 64 /* convert to palette format */
|
|
#define SCORE_COLOR_LOSS 128 /* convert to GRAY */
|
|
|
|
#define COLORSPACE_MASK (GST_VIDEO_FORMAT_FLAG_YUV | \
|
|
GST_VIDEO_FORMAT_FLAG_RGB | GST_VIDEO_FORMAT_FLAG_GRAY)
|
|
#define ALPHA_MASK (GST_VIDEO_FORMAT_FLAG_ALPHA)
|
|
#define PALETTE_MASK (GST_VIDEO_FORMAT_FLAG_PALETTE)
|
|
|
|
/* calculate how much loss a conversion would be */
|
|
static void
|
|
score_value (GstBaseTransform * base, const GstVideoFormatInfo * in_info,
|
|
const GValue * val, gint * min_loss, const GstVideoFormatInfo ** out_info)
|
|
{
|
|
const gchar *fname;
|
|
const GstVideoFormatInfo *t_info;
|
|
GstVideoFormatFlags in_flags, t_flags;
|
|
gint loss;
|
|
|
|
fname = g_value_get_string (val);
|
|
t_info = gst_video_format_get_info (gst_video_format_from_string (fname));
|
|
if (!t_info || t_info->format == GST_VIDEO_FORMAT_UNKNOWN)
|
|
return;
|
|
|
|
/* accept input format immediately without loss */
|
|
if (in_info == t_info) {
|
|
*min_loss = 0;
|
|
*out_info = t_info;
|
|
return;
|
|
}
|
|
|
|
loss = SCORE_FORMAT_CHANGE;
|
|
|
|
in_flags = GST_VIDEO_FORMAT_INFO_FLAGS (in_info);
|
|
in_flags &= ~GST_VIDEO_FORMAT_FLAG_LE;
|
|
in_flags &= ~GST_VIDEO_FORMAT_FLAG_COMPLEX;
|
|
in_flags &= ~GST_VIDEO_FORMAT_FLAG_UNPACK;
|
|
|
|
t_flags = GST_VIDEO_FORMAT_INFO_FLAGS (t_info);
|
|
t_flags &= ~GST_VIDEO_FORMAT_FLAG_LE;
|
|
t_flags &= ~GST_VIDEO_FORMAT_FLAG_COMPLEX;
|
|
t_flags &= ~GST_VIDEO_FORMAT_FLAG_UNPACK;
|
|
|
|
if ((t_flags & PALETTE_MASK) != (in_flags & PALETTE_MASK)) {
|
|
loss += SCORE_PALETTE_CHANGE;
|
|
if (t_flags & PALETTE_MASK)
|
|
loss += SCORE_PALETTE_LOSS;
|
|
}
|
|
|
|
if ((t_flags & COLORSPACE_MASK) != (in_flags & COLORSPACE_MASK)) {
|
|
loss += SCORE_COLORSPACE_LOSS;
|
|
if (t_flags & GST_VIDEO_FORMAT_FLAG_GRAY)
|
|
loss += SCORE_COLOR_LOSS;
|
|
}
|
|
|
|
if ((t_flags & ALPHA_MASK) != (in_flags & ALPHA_MASK)) {
|
|
loss += SCORE_ALPHA_CHANGE;
|
|
if (in_flags & ALPHA_MASK)
|
|
loss += SCORE_ALPHA_LOSS;
|
|
}
|
|
|
|
if ((in_info->h_sub[1]) != (t_info->h_sub[1])) {
|
|
loss += SCORE_CHROMA_H_CHANGE;
|
|
if ((in_info->h_sub[1]) < (t_info->h_sub[1]))
|
|
loss += SCORE_CHROMA_H_LOSS;
|
|
}
|
|
if ((in_info->w_sub[1]) != (t_info->w_sub[1])) {
|
|
loss += SCORE_CHROMA_W_CHANGE;
|
|
if ((in_info->w_sub[1]) < (t_info->w_sub[1]))
|
|
loss += SCORE_CHROMA_W_LOSS;
|
|
}
|
|
|
|
if ((in_info->bits) != (t_info->bits)) {
|
|
loss += SCORE_DEPTH_CHANGE;
|
|
if ((in_info->bits) > (t_info->bits))
|
|
loss += SCORE_DEPTH_LOSS;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (base, "score %s -> %s = %d",
|
|
GST_VIDEO_FORMAT_INFO_NAME (in_info),
|
|
GST_VIDEO_FORMAT_INFO_NAME (t_info), loss);
|
|
|
|
if (loss < *min_loss) {
|
|
GST_DEBUG_OBJECT (base, "found new best %d", loss);
|
|
*out_info = t_info;
|
|
*min_loss = loss;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_video_convert_scale_fixate_format (GstBaseTransform * base, GstCaps * caps,
|
|
GstCaps * result)
|
|
{
|
|
GstStructure *ins, *outs;
|
|
const gchar *in_format;
|
|
const GstVideoFormatInfo *in_info, *out_info = NULL;
|
|
gint min_loss = G_MAXINT;
|
|
guint i, capslen;
|
|
|
|
ins = gst_caps_get_structure (caps, 0);
|
|
in_format = gst_structure_get_string (ins, "format");
|
|
if (!in_format)
|
|
return;
|
|
|
|
GST_DEBUG_OBJECT (base, "source format %s", in_format);
|
|
|
|
in_info =
|
|
gst_video_format_get_info (gst_video_format_from_string (in_format));
|
|
if (!in_info)
|
|
return;
|
|
|
|
outs = gst_caps_get_structure (result, 0);
|
|
|
|
capslen = gst_caps_get_size (result);
|
|
GST_DEBUG_OBJECT (base, "iterate %d structures", capslen);
|
|
for (i = 0; i < capslen; i++) {
|
|
GstStructure *tests;
|
|
const GValue *format;
|
|
|
|
tests = gst_caps_get_structure (result, i);
|
|
format = gst_structure_get_value (tests, "format");
|
|
gst_structure_remove_fields (tests, "height", "width", "pixel-aspect-ratio",
|
|
"display-aspect-ratio", NULL);
|
|
/* should not happen */
|
|
if (format == NULL)
|
|
continue;
|
|
|
|
if (GST_VALUE_HOLDS_LIST (format)) {
|
|
gint j, len;
|
|
|
|
len = gst_value_list_get_size (format);
|
|
GST_DEBUG_OBJECT (base, "have %d formats", len);
|
|
for (j = 0; j < len; j++) {
|
|
const GValue *val;
|
|
|
|
val = gst_value_list_get_value (format, j);
|
|
if (G_VALUE_HOLDS_STRING (val)) {
|
|
score_value (base, in_info, val, &min_loss, &out_info);
|
|
if (min_loss == 0)
|
|
break;
|
|
}
|
|
}
|
|
} else if (G_VALUE_HOLDS_STRING (format)) {
|
|
score_value (base, in_info, format, &min_loss, &out_info);
|
|
}
|
|
}
|
|
if (out_info)
|
|
gst_structure_set (outs, "format", G_TYPE_STRING,
|
|
GST_VIDEO_FORMAT_INFO_NAME (out_info), NULL);
|
|
}
|
|
|
|
static gboolean
|
|
subsampling_unchanged (GstVideoInfo * in_info, GstVideoInfo * out_info)
|
|
{
|
|
gint i;
|
|
const GstVideoFormatInfo *in_format, *out_format;
|
|
|
|
if (GST_VIDEO_INFO_N_COMPONENTS (in_info) !=
|
|
GST_VIDEO_INFO_N_COMPONENTS (out_info))
|
|
return FALSE;
|
|
|
|
in_format = in_info->finfo;
|
|
out_format = out_info->finfo;
|
|
|
|
for (i = 0; i < GST_VIDEO_INFO_N_COMPONENTS (in_info); i++) {
|
|
if (GST_VIDEO_FORMAT_INFO_W_SUB (in_format,
|
|
i) != GST_VIDEO_FORMAT_INFO_W_SUB (out_format, i))
|
|
return FALSE;
|
|
if (GST_VIDEO_FORMAT_INFO_H_SUB (in_format,
|
|
i) != GST_VIDEO_FORMAT_INFO_H_SUB (out_format, i))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
transfer_colorimetry_from_input (GstBaseTransform * trans, GstCaps * in_caps,
|
|
GstCaps * out_caps)
|
|
{
|
|
GstStructure *out_caps_s = gst_caps_get_structure (out_caps, 0);
|
|
GstStructure *in_caps_s = gst_caps_get_structure (in_caps, 0);
|
|
gboolean have_colorimetry =
|
|
gst_structure_has_field (out_caps_s, "colorimetry");
|
|
gboolean have_chroma_site =
|
|
gst_structure_has_field (out_caps_s, "chroma-site");
|
|
|
|
/* If the output already has colorimetry and chroma-site, stop,
|
|
* otherwise try and transfer what we can from the input caps */
|
|
if (have_colorimetry && have_chroma_site)
|
|
return;
|
|
|
|
{
|
|
GstVideoInfo in_info, out_info;
|
|
const GValue *in_colorimetry =
|
|
gst_structure_get_value (in_caps_s, "colorimetry");
|
|
GstCaps *tmp_caps = NULL;
|
|
GstStructure *tmp_caps_s;
|
|
|
|
if (!gst_video_info_from_caps (&in_info, in_caps)) {
|
|
GST_WARNING_OBJECT (trans,
|
|
"Failed to convert sink pad caps to video info");
|
|
return;
|
|
}
|
|
|
|
/* We are before fixate_size(), the width and height of
|
|
the output caps may be absent or not fixed. */
|
|
tmp_caps = gst_caps_copy (out_caps);
|
|
tmp_caps = gst_caps_fixate (tmp_caps);
|
|
tmp_caps_s = gst_caps_get_structure (tmp_caps, 0);
|
|
if (!gst_structure_has_field (tmp_caps_s, "width"))
|
|
gst_structure_set_value (tmp_caps_s, "width",
|
|
gst_structure_get_value (in_caps_s, "width"));
|
|
if (!gst_structure_has_field (tmp_caps_s, "height"))
|
|
gst_structure_set_value (tmp_caps_s, "height",
|
|
gst_structure_get_value (in_caps_s, "height"));
|
|
|
|
if (!gst_video_info_from_caps (&out_info, tmp_caps)) {
|
|
gst_clear_caps (&tmp_caps);
|
|
GST_WARNING_OBJECT (trans,
|
|
"Failed to convert src pad caps to video info");
|
|
return;
|
|
}
|
|
gst_clear_caps (&tmp_caps);
|
|
|
|
if (!have_colorimetry && in_colorimetry != NULL) {
|
|
if ((GST_VIDEO_INFO_IS_YUV (&out_info)
|
|
&& GST_VIDEO_INFO_IS_YUV (&in_info))
|
|
|| (GST_VIDEO_INFO_IS_RGB (&out_info)
|
|
&& GST_VIDEO_INFO_IS_RGB (&in_info))
|
|
|| (GST_VIDEO_INFO_IS_GRAY (&out_info)
|
|
&& GST_VIDEO_INFO_IS_GRAY (&in_info))) {
|
|
/* Can transfer the colorimetry intact from the input if it has it */
|
|
gst_structure_set_value (out_caps_s, "colorimetry", in_colorimetry);
|
|
} else {
|
|
gchar *colorimetry_str;
|
|
|
|
/* Changing between YUV/RGB - forward primaries and transfer function, but use
|
|
* default range and matrix.
|
|
* the primaries is used for conversion between RGB and XYZ (CIE 1931 coordinate).
|
|
* the transfer function could be another reference (e.g., HDR)
|
|
*/
|
|
out_info.colorimetry.primaries = in_info.colorimetry.primaries;
|
|
out_info.colorimetry.transfer = in_info.colorimetry.transfer;
|
|
|
|
colorimetry_str =
|
|
gst_video_colorimetry_to_string (&out_info.colorimetry);
|
|
gst_caps_set_simple (out_caps, "colorimetry", G_TYPE_STRING,
|
|
colorimetry_str, NULL);
|
|
g_free (colorimetry_str);
|
|
}
|
|
}
|
|
|
|
/* Only YUV output needs chroma-site. If the input was also YUV and had the same chroma
|
|
* subsampling, transfer the siting. If the sub-sampling is changing, then the planes get
|
|
* scaled anyway so there's no real reason to prefer the input siting. */
|
|
if (!have_chroma_site && GST_VIDEO_INFO_IS_YUV (&out_info)) {
|
|
if (GST_VIDEO_INFO_IS_YUV (&in_info)) {
|
|
const GValue *in_chroma_site =
|
|
gst_structure_get_value (in_caps_s, "chroma-site");
|
|
if (in_chroma_site != NULL
|
|
&& subsampling_unchanged (&in_info, &out_info))
|
|
gst_structure_set_value (out_caps_s, "chroma-site", in_chroma_site);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static GstCaps *
|
|
gst_video_convert_scale_get_fixed_format (GstBaseTransform * trans,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
|
|
{
|
|
GstCaps *result;
|
|
|
|
result = gst_caps_intersect (othercaps, caps);
|
|
if (gst_caps_is_empty (result)) {
|
|
gst_caps_unref (result);
|
|
result = gst_caps_copy (othercaps);
|
|
}
|
|
|
|
gst_video_convert_scale_fixate_format (trans, caps, result);
|
|
|
|
/* fixate remaining fields */
|
|
result = gst_caps_fixate (result);
|
|
|
|
if (direction == GST_PAD_SINK) {
|
|
if (gst_caps_is_subset (caps, result)) {
|
|
gst_caps_replace (&result, caps);
|
|
} else {
|
|
/* Try and preserve input colorimetry / chroma information */
|
|
transfer_colorimetry_from_input (trans, caps, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_video_convert_scale_fixate_size (GstBaseTransform * base,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
|
|
{
|
|
GstStructure *ins, *outs;
|
|
const GValue *from_par, *to_par;
|
|
GValue fpar = { 0, };
|
|
GValue tpar = { 0, };
|
|
|
|
othercaps = gst_caps_truncate (othercaps);
|
|
othercaps = gst_caps_make_writable (othercaps);
|
|
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");
|
|
|
|
/* If we're fixating from the sinkpad we always set the PAR and
|
|
* assume that missing PAR on the sinkpad means 1/1 and
|
|
* missing PAR on the srcpad means undefined
|
|
*/
|
|
if (direction == GST_PAD_SINK) {
|
|
if (!from_par) {
|
|
g_value_init (&fpar, GST_TYPE_FRACTION);
|
|
gst_value_set_fraction (&fpar, 1, 1);
|
|
from_par = &fpar;
|
|
}
|
|
if (!to_par) {
|
|
g_value_init (&tpar, GST_TYPE_FRACTION_RANGE);
|
|
gst_value_set_fraction_range_full (&tpar, 1, G_MAXINT, G_MAXINT, 1);
|
|
to_par = &tpar;
|
|
}
|
|
} else {
|
|
if (!to_par) {
|
|
g_value_init (&tpar, GST_TYPE_FRACTION);
|
|
gst_value_set_fraction (&tpar, 1, 1);
|
|
to_par = &tpar;
|
|
|
|
gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
|
|
NULL);
|
|
}
|
|
if (!from_par) {
|
|
g_value_init (&fpar, GST_TYPE_FRACTION);
|
|
gst_value_set_fraction (&fpar, 1, 1);
|
|
from_par = &fpar;
|
|
}
|
|
}
|
|
|
|
/* we have both PAR but they might not be fixated */
|
|
{
|
|
gint from_w, from_h, from_par_n, from_par_d, to_par_n, to_par_d;
|
|
gint w = 0, h = 0;
|
|
gint from_dar_n, from_dar_d;
|
|
gint num, den;
|
|
|
|
/* from_par should be fixed */
|
|
g_return_val_if_fail (gst_value_is_fixed (from_par), othercaps);
|
|
|
|
from_par_n = gst_value_get_fraction_numerator (from_par);
|
|
from_par_d = gst_value_get_fraction_denominator (from_par);
|
|
|
|
gst_structure_get_int (ins, "width", &from_w);
|
|
gst_structure_get_int (ins, "height", &from_h);
|
|
|
|
gst_structure_get_int (outs, "width", &w);
|
|
gst_structure_get_int (outs, "height", &h);
|
|
|
|
/* if both width and height are already fixed, we can't do anything
|
|
* about it anymore */
|
|
if (w && h) {
|
|
guint n, d;
|
|
|
|
GST_DEBUG_OBJECT (base, "dimensions already set to %dx%d, not fixating",
|
|
w, h);
|
|
if (!gst_value_is_fixed (to_par)) {
|
|
if (gst_video_calculate_display_ratio (&n, &d, from_w, from_h,
|
|
from_par_n, from_par_d, w, h)) {
|
|
GST_DEBUG_OBJECT (base, "fixating to_par to %dx%d", n, d);
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio"))
|
|
gst_structure_fixate_field_nearest_fraction (outs,
|
|
"pixel-aspect-ratio", n, d);
|
|
else if (n != d)
|
|
gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
|
|
n, d, NULL);
|
|
}
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
/* Calculate input DAR */
|
|
if (!gst_util_fraction_multiply (from_w, from_h, from_par_n, from_par_d,
|
|
&from_dar_n, &from_dar_d)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
goto done;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (base, "Input DAR is %d/%d", from_dar_n, from_dar_d);
|
|
|
|
/* If either width or height are fixed there's not much we
|
|
* can do either except choosing a height or width and PAR
|
|
* that matches the DAR as good as possible
|
|
*/
|
|
if (h) {
|
|
GstStructure *tmp;
|
|
gint set_w, set_par_n, set_par_d;
|
|
|
|
GST_DEBUG_OBJECT (base, "height is fixed (%d)", h);
|
|
|
|
/* If the PAR is fixed too, there's not much to do
|
|
* except choosing the width that is nearest to the
|
|
* width with the same DAR */
|
|
if (gst_value_is_fixed (to_par)) {
|
|
to_par_n = gst_value_get_fraction_numerator (to_par);
|
|
to_par_d = gst_value_get_fraction_denominator (to_par);
|
|
|
|
GST_DEBUG_OBJECT (base, "PAR is fixed %d/%d", to_par_n, to_par_d);
|
|
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d,
|
|
to_par_n, &num, &den)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
goto done;
|
|
}
|
|
|
|
w = (guint) gst_util_uint64_scale_int_round (h, num, den);
|
|
gst_structure_fixate_field_nearest_int (outs, "width", w);
|
|
|
|
goto done;
|
|
}
|
|
|
|
/* The PAR is not fixed and it's quite likely that we can set
|
|
* an arbitrary PAR. */
|
|
|
|
/* Check if we can keep the input width */
|
|
tmp = gst_structure_copy (outs);
|
|
gst_structure_fixate_field_nearest_int (tmp, "width", from_w);
|
|
gst_structure_get_int (tmp, "width", &set_w);
|
|
|
|
/* Might have failed but try to keep the DAR nonetheless by
|
|
* adjusting the PAR */
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, h, set_w,
|
|
&to_par_n, &to_par_d)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
gst_structure_free (tmp);
|
|
goto done;
|
|
}
|
|
|
|
if (!gst_structure_has_field (tmp, "pixel-aspect-ratio"))
|
|
gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par);
|
|
gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio",
|
|
to_par_n, to_par_d);
|
|
gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n,
|
|
&set_par_d);
|
|
gst_structure_free (tmp);
|
|
|
|
/* Check if the adjusted PAR is accepted */
|
|
if (set_par_n == to_par_n && set_par_d == to_par_d) {
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
|
|
set_par_n != set_par_d)
|
|
gst_structure_set (outs, "width", G_TYPE_INT, set_w,
|
|
"pixel-aspect-ratio", GST_TYPE_FRACTION, set_par_n, set_par_d,
|
|
NULL);
|
|
goto done;
|
|
}
|
|
|
|
/* Otherwise scale the width to the new PAR and check if the
|
|
* adjusted with is accepted. If all that fails we can't keep
|
|
* the DAR */
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d,
|
|
set_par_n, &num, &den)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
goto done;
|
|
}
|
|
|
|
w = (guint) gst_util_uint64_scale_int_round (h, num, den);
|
|
gst_structure_fixate_field_nearest_int (outs, "width", w);
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
|
|
set_par_n != set_par_d)
|
|
gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
|
|
set_par_n, set_par_d, NULL);
|
|
|
|
goto done;
|
|
} else if (w) {
|
|
GstStructure *tmp;
|
|
gint set_h, set_par_n, set_par_d;
|
|
|
|
GST_DEBUG_OBJECT (base, "width is fixed (%d)", w);
|
|
|
|
/* If the PAR is fixed too, there's not much to do
|
|
* except choosing the height that is nearest to the
|
|
* height with the same DAR */
|
|
if (gst_value_is_fixed (to_par)) {
|
|
to_par_n = gst_value_get_fraction_numerator (to_par);
|
|
to_par_d = gst_value_get_fraction_denominator (to_par);
|
|
|
|
GST_DEBUG_OBJECT (base, "PAR is fixed %d/%d", to_par_n, to_par_d);
|
|
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d,
|
|
to_par_n, &num, &den)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
goto done;
|
|
}
|
|
|
|
h = (guint) gst_util_uint64_scale_int_round (w, den, num);
|
|
gst_structure_fixate_field_nearest_int (outs, "height", h);
|
|
|
|
goto done;
|
|
}
|
|
|
|
/* The PAR is not fixed and it's quite likely that we can set
|
|
* an arbitrary PAR. */
|
|
|
|
/* Check if we can keep the input height */
|
|
tmp = gst_structure_copy (outs);
|
|
gst_structure_fixate_field_nearest_int (tmp, "height", from_h);
|
|
gst_structure_get_int (tmp, "height", &set_h);
|
|
|
|
/* Might have failed but try to keep the DAR nonetheless by
|
|
* adjusting the PAR */
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_h, w,
|
|
&to_par_n, &to_par_d)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
gst_structure_free (tmp);
|
|
goto done;
|
|
}
|
|
if (!gst_structure_has_field (tmp, "pixel-aspect-ratio"))
|
|
gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par);
|
|
gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio",
|
|
to_par_n, to_par_d);
|
|
gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n,
|
|
&set_par_d);
|
|
gst_structure_free (tmp);
|
|
|
|
/* Check if the adjusted PAR is accepted */
|
|
if (set_par_n == to_par_n && set_par_d == to_par_d) {
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
|
|
set_par_n != set_par_d)
|
|
gst_structure_set (outs, "height", G_TYPE_INT, set_h,
|
|
"pixel-aspect-ratio", GST_TYPE_FRACTION, set_par_n, set_par_d,
|
|
NULL);
|
|
goto done;
|
|
}
|
|
|
|
/* Otherwise scale the height to the new PAR and check if the
|
|
* adjusted with is accepted. If all that fails we can't keep
|
|
* the DAR */
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d,
|
|
set_par_n, &num, &den)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
goto done;
|
|
}
|
|
|
|
h = (guint) gst_util_uint64_scale_int_round (w, den, num);
|
|
gst_structure_fixate_field_nearest_int (outs, "height", h);
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
|
|
set_par_n != set_par_d)
|
|
gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
|
|
set_par_n, set_par_d, NULL);
|
|
|
|
goto done;
|
|
} else if (gst_value_is_fixed (to_par)) {
|
|
GstStructure *tmp;
|
|
gint set_h, set_w, f_h, f_w;
|
|
|
|
to_par_n = gst_value_get_fraction_numerator (to_par);
|
|
to_par_d = gst_value_get_fraction_denominator (to_par);
|
|
|
|
/* Calculate scale factor for the PAR change */
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_n,
|
|
to_par_d, &num, &den)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
goto done;
|
|
}
|
|
|
|
/* Try to keep the input height (because of interlacing) */
|
|
tmp = gst_structure_copy (outs);
|
|
gst_structure_fixate_field_nearest_int (tmp, "height", from_h);
|
|
gst_structure_get_int (tmp, "height", &set_h);
|
|
|
|
/* This might have failed but try to scale the width
|
|
* to keep the DAR nonetheless */
|
|
w = (guint) gst_util_uint64_scale_int_round (set_h, num, den);
|
|
gst_structure_fixate_field_nearest_int (tmp, "width", w);
|
|
gst_structure_get_int (tmp, "width", &set_w);
|
|
gst_structure_free (tmp);
|
|
|
|
/* We kept the DAR and the height is nearest to the original height */
|
|
if (set_w == w) {
|
|
gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
|
|
G_TYPE_INT, set_h, NULL);
|
|
goto done;
|
|
}
|
|
|
|
f_h = set_h;
|
|
f_w = set_w;
|
|
|
|
/* If the former failed, try to keep the input width at least */
|
|
tmp = gst_structure_copy (outs);
|
|
gst_structure_fixate_field_nearest_int (tmp, "width", from_w);
|
|
gst_structure_get_int (tmp, "width", &set_w);
|
|
|
|
/* This might have failed but try to scale the width
|
|
* to keep the DAR nonetheless */
|
|
h = (guint) gst_util_uint64_scale_int_round (set_w, den, num);
|
|
gst_structure_fixate_field_nearest_int (tmp, "height", h);
|
|
gst_structure_get_int (tmp, "height", &set_h);
|
|
gst_structure_free (tmp);
|
|
|
|
/* We kept the DAR and the width is nearest to the original width */
|
|
if (set_h == h) {
|
|
gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
|
|
G_TYPE_INT, set_h, NULL);
|
|
goto done;
|
|
}
|
|
|
|
/* If all this failed, keep the dimensions with the DAR that was closest
|
|
* to the correct DAR. This changes the DAR but there's not much else to
|
|
* do here.
|
|
*/
|
|
if (set_w * ABS (set_h - h) < ABS (f_w - w) * f_h) {
|
|
f_h = set_h;
|
|
f_w = set_w;
|
|
}
|
|
gst_structure_set (outs, "width", G_TYPE_INT, f_w, "height", G_TYPE_INT,
|
|
f_h, NULL);
|
|
goto done;
|
|
} else {
|
|
GstStructure *tmp;
|
|
gint set_h, set_w, set_par_n, set_par_d, tmp2;
|
|
|
|
/* width, height and PAR are not fixed but passthrough is not possible */
|
|
|
|
/* First try to keep the height and width as good as possible
|
|
* and scale PAR */
|
|
tmp = gst_structure_copy (outs);
|
|
gst_structure_fixate_field_nearest_int (tmp, "height", from_h);
|
|
gst_structure_get_int (tmp, "height", &set_h);
|
|
gst_structure_fixate_field_nearest_int (tmp, "width", from_w);
|
|
gst_structure_get_int (tmp, "width", &set_w);
|
|
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_h, set_w,
|
|
&to_par_n, &to_par_d)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
gst_structure_free (tmp);
|
|
goto done;
|
|
}
|
|
|
|
if (!gst_structure_has_field (tmp, "pixel-aspect-ratio"))
|
|
gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par);
|
|
gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio",
|
|
to_par_n, to_par_d);
|
|
gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n,
|
|
&set_par_d);
|
|
gst_structure_free (tmp);
|
|
|
|
if (set_par_n == to_par_n && set_par_d == to_par_d) {
|
|
gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
|
|
G_TYPE_INT, set_h, NULL);
|
|
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
|
|
set_par_n != set_par_d)
|
|
gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
|
|
set_par_n, set_par_d, NULL);
|
|
goto done;
|
|
}
|
|
|
|
/* Otherwise try to scale width to keep the DAR with the set
|
|
* PAR and height */
|
|
if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d,
|
|
set_par_n, &num, &den)) {
|
|
GST_ELEMENT_ERROR (base, CORE, NEGOTIATION, (NULL),
|
|
("Error calculating the output scaled size - integer overflow"));
|
|
goto done;
|
|
}
|
|
|
|
w = (guint) gst_util_uint64_scale_int_round (set_h, num, den);
|
|
tmp = gst_structure_copy (outs);
|
|
gst_structure_fixate_field_nearest_int (tmp, "width", w);
|
|
gst_structure_get_int (tmp, "width", &tmp2);
|
|
gst_structure_free (tmp);
|
|
|
|
if (tmp2 == w) {
|
|
gst_structure_set (outs, "width", G_TYPE_INT, tmp2, "height",
|
|
G_TYPE_INT, set_h, NULL);
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
|
|
set_par_n != set_par_d)
|
|
gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
|
|
set_par_n, set_par_d, NULL);
|
|
goto done;
|
|
}
|
|
|
|
/* ... or try the same with the height */
|
|
h = (guint) gst_util_uint64_scale_int_round (set_w, den, num);
|
|
tmp = gst_structure_copy (outs);
|
|
gst_structure_fixate_field_nearest_int (tmp, "height", h);
|
|
gst_structure_get_int (tmp, "height", &tmp2);
|
|
gst_structure_free (tmp);
|
|
|
|
if (tmp2 == h) {
|
|
gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
|
|
G_TYPE_INT, tmp2, NULL);
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
|
|
set_par_n != set_par_d)
|
|
gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
|
|
set_par_n, set_par_d, NULL);
|
|
goto done;
|
|
}
|
|
|
|
/* If all fails we can't keep the DAR and take the nearest values
|
|
* for everything from the first try */
|
|
gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
|
|
G_TYPE_INT, set_h, NULL);
|
|
if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
|
|
set_par_n != set_par_d)
|
|
gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
|
|
set_par_n, set_par_d, NULL);
|
|
}
|
|
}
|
|
|
|
done:
|
|
othercaps = gst_caps_fixate (othercaps);
|
|
|
|
GST_DEBUG_OBJECT (base, "fixated othercaps to %" GST_PTR_FORMAT, othercaps);
|
|
|
|
if (from_par == &fpar)
|
|
g_value_unset (&fpar);
|
|
if (to_par == &tpar)
|
|
g_value_unset (&tpar);
|
|
|
|
return othercaps;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_video_convert_scale_fixate_caps (GstBaseTransform * base,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
|
|
{
|
|
GstCaps *format;
|
|
|
|
GST_DEBUG_OBJECT (base,
|
|
"trying to fixate othercaps %" GST_PTR_FORMAT " based on caps %"
|
|
GST_PTR_FORMAT, othercaps, caps);
|
|
|
|
format = gst_video_convert_scale_get_fixed_format (base, direction, caps,
|
|
othercaps);
|
|
|
|
if (gst_caps_is_empty (format)) {
|
|
GST_ERROR_OBJECT (base, "Could not convert formats");
|
|
return format;
|
|
}
|
|
|
|
othercaps =
|
|
gst_video_convert_scale_fixate_size (base, direction, caps, othercaps);
|
|
if (gst_caps_get_size (othercaps) == 1) {
|
|
gint i;
|
|
const gchar *format_fields[] = { "format", "colorimetry", "chroma-site" };
|
|
GstStructure *format_struct = gst_caps_get_structure (format, 0);
|
|
GstStructure *fixated_struct;
|
|
|
|
othercaps = gst_caps_make_writable (othercaps);
|
|
fixated_struct = gst_caps_get_structure (othercaps, 0);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (format_fields); i++) {
|
|
if (gst_structure_has_field (format_struct, format_fields[i])) {
|
|
gst_structure_set (fixated_struct, format_fields[i], G_TYPE_STRING,
|
|
gst_structure_get_string (format_struct, format_fields[i]), NULL);
|
|
} else {
|
|
gst_structure_remove_field (fixated_struct, format_fields[i]);
|
|
}
|
|
}
|
|
}
|
|
gst_caps_unref (format);
|
|
|
|
GST_DEBUG_OBJECT (base, "fixated othercaps to %" GST_PTR_FORMAT, othercaps);
|
|
|
|
return othercaps;
|
|
}
|
|
|
|
#define GET_LINE(frame, line) \
|
|
(gpointer)(((guint8*)(GST_VIDEO_FRAME_PLANE_DATA (frame, 0))) + \
|
|
GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) * (line))
|
|
|
|
static GstFlowReturn
|
|
gst_video_convert_scale_transform_frame (GstVideoFilter * filter,
|
|
GstVideoFrame * in_frame, GstVideoFrame * out_frame)
|
|
{
|
|
GstVideoConvertScalePrivate *priv = PRIV (filter);
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
GST_CAT_DEBUG_OBJECT (CAT_PERFORMANCE, filter, "doing video scaling");
|
|
|
|
gst_video_converter_frame (priv->convert, in_frame, out_frame);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_convert_scale_src_event (GstBaseTransform * trans, GstEvent * event)
|
|
{
|
|
GstVideoConvertScale *self = GST_VIDEO_CONVERT_SCALE_CAST (trans);
|
|
GstVideoFilter *filter = GST_VIDEO_FILTER_CAST (trans);
|
|
gboolean ret;
|
|
gdouble x, y;
|
|
|
|
GST_DEBUG_OBJECT (self, "handling %s event", GST_EVENT_TYPE_NAME (event));
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_NAVIGATION:
|
|
if (filter->in_info.width != filter->out_info.width ||
|
|
filter->in_info.height != filter->out_info.height) {
|
|
event = gst_event_make_writable (event);
|
|
|
|
if (gst_navigation_event_get_coordinates (event, &x, &y)) {
|
|
gst_navigation_event_set_coordinates (event,
|
|
x * filter->in_info.width / filter->out_info.width,
|
|
y * filter->in_info.height / filter->out_info.height);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_BASE_TRANSFORM_CLASS (parent_class)->src_event (trans, event);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
gst_video_convert_scale_set_scales (GstVideoConvertScale * self,
|
|
gboolean scales)
|
|
{
|
|
GstVideoConvertScalePrivate *priv = PRIV (self);
|
|
|
|
if (!scales)
|
|
g_assert (priv->converts);
|
|
|
|
priv->scales = scales;
|
|
}
|
|
|
|
void
|
|
gst_video_convert_scale_set_converts (GstVideoConvertScale * self,
|
|
gboolean converts)
|
|
{
|
|
GstVideoConvertScalePrivate *priv = PRIV (self);
|
|
|
|
if (!converts)
|
|
g_assert (priv->scales);
|
|
|
|
priv->converts = converts;
|
|
}
|
|
|
|
gboolean
|
|
gst_video_convert_scale_get_scales (GstVideoConvertScale * self)
|
|
{
|
|
return PRIV (self)->scales;
|
|
}
|
|
|
|
gboolean
|
|
gst_video_convert_scale_get_converts (GstVideoConvertScale * self)
|
|
{
|
|
return PRIV (self)->converts;
|
|
}
|