mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-23 10:11:08 +00:00
448 lines
11 KiB
C
448 lines
11 KiB
C
/* GStreamer
|
|
* Copyright (C) <2014> Wim Taymans <wim.taymans@gmail.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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
#include "video-resampler.h"
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
#define GST_CAT_DEFAULT ensure_debug_category()
|
|
static GstDebugCategory *
|
|
ensure_debug_category (void)
|
|
{
|
|
static gsize cat_gonce = 0;
|
|
|
|
if (g_once_init_enter (&cat_gonce)) {
|
|
gsize cat_done;
|
|
|
|
cat_done = (gsize) _gst_debug_category_new ("video-resampler", 0,
|
|
"video-resampler object");
|
|
|
|
g_once_init_leave (&cat_gonce, cat_done);
|
|
}
|
|
|
|
return (GstDebugCategory *) cat_gonce;
|
|
}
|
|
#else
|
|
#define ensure_debug_category() /* NOOP */
|
|
#endif /* GST_DISABLE_GST_DEBUG */
|
|
|
|
/**
|
|
* SECTION:gstvideoresampler
|
|
* @title: GstVideoResampler
|
|
* @short_description: Utility structure for resampler information
|
|
*
|
|
* #GstVideoResampler is a structure which holds the information
|
|
* required to perform various kinds of resampling filtering.
|
|
*
|
|
*/
|
|
|
|
|
|
#define DEFAULT_OPT_CUBIC_B (1.0 / 3.0)
|
|
#define DEFAULT_OPT_CUBIC_C (1.0 / 3.0)
|
|
|
|
#define DEFAULT_OPT_ENVELOPE 2.0
|
|
#define DEFAULT_OPT_SHARPNESS 1.0
|
|
#define DEFAULT_OPT_SHARPEN 0.0
|
|
|
|
#define DEFAULT_OPT_MAX_TAPS 128
|
|
|
|
typedef struct _ResamplerParams ResamplerParams;
|
|
|
|
struct _ResamplerParams
|
|
{
|
|
GstVideoResamplerMethod method;
|
|
GstVideoResamplerFlags flags;
|
|
|
|
gdouble shift;
|
|
|
|
gdouble (*get_tap) (ResamplerParams * params, gint l, gint xi, gdouble x);
|
|
|
|
/* for cubic */
|
|
gdouble b, c;
|
|
/* used by lanczos */
|
|
gdouble ex, fx, dx;
|
|
/* extra params */
|
|
gdouble envelope;
|
|
gdouble sharpness;
|
|
gdouble sharpen;
|
|
|
|
GstVideoResampler *resampler;
|
|
};
|
|
|
|
static gdouble
|
|
get_opt_double (GstStructure * options, const gchar * name, gdouble def)
|
|
{
|
|
gdouble res;
|
|
if (!options || !gst_structure_get_double (options, name, &res))
|
|
res = def;
|
|
return res;
|
|
}
|
|
|
|
static gint
|
|
get_opt_int (GstStructure * options, const gchar * name, gint def)
|
|
{
|
|
gint res;
|
|
if (!options || !gst_structure_get_int (options, name, &res))
|
|
res = def;
|
|
return res;
|
|
}
|
|
|
|
#define GET_OPT_CUBIC_B(options) get_opt_double(options, \
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_B, DEFAULT_OPT_CUBIC_B)
|
|
#define GET_OPT_CUBIC_C(options) get_opt_double(options, \
|
|
GST_VIDEO_RESAMPLER_OPT_CUBIC_C, DEFAULT_OPT_CUBIC_C)
|
|
#define GET_OPT_ENVELOPE(options) get_opt_double(options, \
|
|
GST_VIDEO_RESAMPLER_OPT_ENVELOPE, DEFAULT_OPT_ENVELOPE)
|
|
#define GET_OPT_SHARPNESS(options) get_opt_double(options, \
|
|
GST_VIDEO_RESAMPLER_OPT_SHARPNESS, DEFAULT_OPT_SHARPNESS)
|
|
#define GET_OPT_SHARPEN(options) get_opt_double(options, \
|
|
GST_VIDEO_RESAMPLER_OPT_SHARPEN, DEFAULT_OPT_SHARPEN)
|
|
#define GET_OPT_MAX_TAPS(options) get_opt_int(options, \
|
|
GST_VIDEO_RESAMPLER_OPT_MAX_TAPS, DEFAULT_OPT_MAX_TAPS)
|
|
|
|
static double
|
|
sinc (double x)
|
|
{
|
|
if (x == 0)
|
|
return 1;
|
|
|
|
return sin (G_PI * x) / (G_PI * x);
|
|
}
|
|
|
|
static double
|
|
envelope (double x)
|
|
{
|
|
if (x <= -1 || x >= 1)
|
|
return 0;
|
|
return sinc (x);
|
|
}
|
|
|
|
static gdouble
|
|
get_nearest_tap (ResamplerParams * params, gint l, gint xi, gdouble x)
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
static gdouble
|
|
get_linear_tap (ResamplerParams * params, gint l, gint xi, gdouble x)
|
|
{
|
|
gdouble res, a;
|
|
gint xl = xi + l;
|
|
|
|
a = fabs (x - xl) * params->fx;
|
|
|
|
if (a < 1.0)
|
|
res = 1.0 - a;
|
|
else
|
|
res = 0.0;
|
|
|
|
return res;
|
|
}
|
|
|
|
static gdouble
|
|
get_cubic_tap (ResamplerParams * params, gint l, gint xi, gdouble x)
|
|
{
|
|
gdouble a, a2, a3, b, c;
|
|
gint xl = xi + l;
|
|
|
|
a = fabs (x - xl) * params->fx;
|
|
a2 = a * a;
|
|
a3 = a2 * a;
|
|
|
|
b = params->b;
|
|
c = params->c;
|
|
|
|
if (a <= 1.0)
|
|
return ((12.0 - 9.0 * b - 6.0 * c) * a3 +
|
|
(-18.0 + 12.0 * b + 6.0 * c) * a2 + (6.0 - 2.0 * b)) / 6.0;
|
|
else if (a <= 2.0)
|
|
return ((-b - 6.0 * c) * a3 +
|
|
(6.0 * b + 30.0 * c) * a2 +
|
|
(-12.0 * b - 48.0 * c) * a + (8.0 * b + 24.0 * c)) / 6.0;
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
static gdouble
|
|
get_sinc_tap (ResamplerParams * params, gint l, gint xi, gdouble x)
|
|
{
|
|
gint xl = xi + l;
|
|
return sinc ((x - xl) * params->fx);
|
|
}
|
|
|
|
static gdouble
|
|
get_lanczos_tap (ResamplerParams * params, gint l, gint xi, gdouble x)
|
|
{
|
|
gint xl = xi + l;
|
|
gdouble env = envelope ((x - xl) * params->ex);
|
|
return (sinc ((x - xl) * params->fx) - params->sharpen) * env;
|
|
}
|
|
|
|
static void
|
|
resampler_calculate_taps (ResamplerParams * params)
|
|
{
|
|
GstVideoResampler *resampler = params->resampler;
|
|
gint j;
|
|
guint32 *offset, *n_taps, *phase;
|
|
gint tap_offs;
|
|
gint max_taps;
|
|
gint in_size, out_size;
|
|
gdouble shift;
|
|
gdouble corr;
|
|
|
|
in_size = resampler->in_size;
|
|
out_size = resampler->out_size;
|
|
|
|
max_taps = resampler->max_taps;
|
|
tap_offs = (max_taps - 1) / 2;
|
|
corr = (max_taps == 1 ? 0.0 : 0.5);
|
|
|
|
shift = params->shift;
|
|
|
|
resampler->taps = g_malloc (sizeof (gdouble) * max_taps * out_size);
|
|
n_taps = resampler->n_taps = g_malloc (sizeof (guint32) * out_size);
|
|
offset = resampler->offset = g_malloc (sizeof (guint32) * out_size);
|
|
phase = resampler->phase = g_malloc (sizeof (guint32) * out_size);
|
|
|
|
for (j = 0; j < out_size; j++) {
|
|
gdouble ox, x;
|
|
gint xi;
|
|
gint l;
|
|
gdouble weight;
|
|
gdouble *taps;
|
|
|
|
/* center of the output pixel */
|
|
ox = (0.5 + (gdouble) j - shift) / out_size;
|
|
/* x is the source pixel to use, can be fractional */
|
|
x = ox * (gdouble) in_size - corr;
|
|
x = CLAMP (x, 0, in_size - 1);
|
|
/* this is the first source pixel to use */
|
|
xi = floor (x - tap_offs);
|
|
|
|
offset[j] = xi;
|
|
phase[j] = j;
|
|
n_taps[j] = max_taps;
|
|
weight = 0;
|
|
taps = resampler->taps + j * max_taps;
|
|
|
|
for (l = 0; l < max_taps; l++) {
|
|
taps[l] = params->get_tap (params, l, xi, x);
|
|
weight += taps[l];
|
|
}
|
|
|
|
for (l = 0; l < max_taps; l++)
|
|
taps[l] /= weight;
|
|
|
|
if (xi < 0) {
|
|
gint sh = -xi;
|
|
|
|
for (l = 0; l < sh; l++) {
|
|
taps[sh] += taps[l];
|
|
}
|
|
for (l = 0; l < max_taps - sh; l++) {
|
|
taps[l] = taps[sh + l];
|
|
}
|
|
for (; l < max_taps; l++) {
|
|
taps[l] = 0;
|
|
}
|
|
offset[j] += sh;
|
|
}
|
|
if (xi > in_size - max_taps) {
|
|
gint sh = xi - (in_size - max_taps);
|
|
|
|
for (l = 0; l < sh; l++) {
|
|
taps[max_taps - sh - 1] += taps[max_taps - sh + l];
|
|
}
|
|
for (l = 0; l < max_taps - sh; l++) {
|
|
taps[max_taps - 1 - l] = taps[max_taps - 1 - sh - l];
|
|
}
|
|
for (l = 0; l < sh; l++) {
|
|
taps[l] = 0;
|
|
}
|
|
offset[j] -= sh;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
resampler_dump (GstVideoResampler * resampler)
|
|
{
|
|
#if 0
|
|
gint i, max_taps, out_size;
|
|
|
|
out_size = resampler->out_size;
|
|
max_taps = resampler->max_taps;
|
|
|
|
for (i = 0; i < out_size; i++) {
|
|
gint j, o, phase, n_taps;
|
|
gdouble sum;
|
|
|
|
o = resampler->offset[i];
|
|
n_taps = resampler->n_taps[i];
|
|
phase = resampler->phase[i];
|
|
|
|
printf ("%u: \t%d ", i, o);
|
|
sum = 0;
|
|
for (j = 0; j < n_taps; j++) {
|
|
gdouble tap;
|
|
tap = resampler->taps[phase * max_taps + j];
|
|
printf ("\t%f ", tap);
|
|
sum += tap;
|
|
}
|
|
printf ("\t: sum %f\n", sum);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* gst_video_resampler_new:
|
|
* @resampler: a #GstVideoResampler
|
|
* @method: a #GstVideoResamplerMethod
|
|
* @flags: #GstVideoResamplerFlags
|
|
* @n_phases: number of phases to use
|
|
* @n_taps: number of taps to use
|
|
* @in_size: number of source elements
|
|
* @out_size: number of destination elements
|
|
* @options: extra options
|
|
*
|
|
* Make a new resampler. @in_size source elements will
|
|
* be resampled to @out_size destination elements.
|
|
*
|
|
* @n_taps specifies the amount of elements to use from the source for one output
|
|
* element. If n_taps is 0, this function chooses a good value automatically based
|
|
* on the @method and @in_size/@out_size.
|
|
*
|
|
* Returns: %TRUE on success
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
gboolean
|
|
gst_video_resampler_init (GstVideoResampler * resampler,
|
|
GstVideoResamplerMethod method, GstVideoResamplerFlags flags,
|
|
guint n_phases, guint n_taps, gdouble shift, guint in_size, guint out_size,
|
|
GstStructure * options)
|
|
{
|
|
ResamplerParams params;
|
|
gint max_taps;
|
|
gdouble scale_factor;
|
|
|
|
g_return_val_if_fail (in_size != 0, FALSE);
|
|
g_return_val_if_fail (out_size != 0, FALSE);
|
|
g_return_val_if_fail (n_phases == out_size, FALSE);
|
|
|
|
resampler->in_size = in_size;
|
|
resampler->out_size = out_size;
|
|
resampler->n_phases = n_phases;
|
|
|
|
params.method = method;
|
|
params.flags = flags;
|
|
params.shift = shift;
|
|
params.resampler = resampler;
|
|
|
|
GST_DEBUG ("%d %u %u->%u", method, n_taps, in_size, out_size);
|
|
|
|
params.sharpness = GET_OPT_SHARPNESS (options);
|
|
params.sharpen = GET_OPT_SHARPEN (options);
|
|
|
|
scale_factor = in_size / (gdouble) out_size;
|
|
if (scale_factor > 1.0) {
|
|
params.fx = (1.0 / scale_factor) * params.sharpness;
|
|
} else {
|
|
params.fx = (1.0) * params.sharpness;
|
|
}
|
|
|
|
max_taps = GET_OPT_MAX_TAPS (options);
|
|
n_taps = MIN (n_taps, max_taps);
|
|
|
|
switch (method) {
|
|
case GST_VIDEO_RESAMPLER_METHOD_NEAREST:
|
|
params.envelope = GET_OPT_ENVELOPE (options);
|
|
params.get_tap = get_nearest_tap;
|
|
if (n_taps == 0)
|
|
n_taps = 1;
|
|
break;
|
|
case GST_VIDEO_RESAMPLER_METHOD_LINEAR:
|
|
params.get_tap = get_linear_tap;
|
|
params.envelope = 1.0;
|
|
break;
|
|
case GST_VIDEO_RESAMPLER_METHOD_CUBIC:
|
|
params.b = GET_OPT_CUBIC_B (options);
|
|
params.c = GET_OPT_CUBIC_C (options);
|
|
params.envelope = 2.0;
|
|
params.get_tap = get_cubic_tap;
|
|
break;
|
|
case GST_VIDEO_RESAMPLER_METHOD_SINC:
|
|
params.envelope = GET_OPT_ENVELOPE (options);
|
|
params.get_tap = get_sinc_tap;
|
|
break;
|
|
case GST_VIDEO_RESAMPLER_METHOD_LANCZOS:
|
|
params.envelope = GET_OPT_ENVELOPE (options);
|
|
params.get_tap = get_lanczos_tap;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (n_taps == 0) {
|
|
params.dx = ceil (2.0 * params.envelope / params.fx);
|
|
n_taps = CLAMP (params.dx, 0, max_taps);
|
|
}
|
|
if (flags & GST_VIDEO_RESAMPLER_FLAG_HALF_TAPS && n_taps > 3)
|
|
n_taps /= 2;
|
|
params.fx = 2.0 * params.envelope / n_taps;
|
|
params.ex = 2.0 / n_taps;
|
|
|
|
if (n_taps > in_size)
|
|
n_taps = in_size;
|
|
|
|
resampler->max_taps = n_taps;
|
|
|
|
resampler_calculate_taps (¶ms);
|
|
|
|
resampler_dump (resampler);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_video_resampler_clear:
|
|
* @resampler: a #GstVideoResampler
|
|
*
|
|
* Clear a previously initialized #GstVideoResampler @resampler.
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
void
|
|
gst_video_resampler_clear (GstVideoResampler * resampler)
|
|
{
|
|
g_return_if_fail (resampler != NULL);
|
|
|
|
g_free (resampler->phase);
|
|
g_free (resampler->offset);
|
|
g_free (resampler->n_taps);
|
|
g_free (resampler->taps);
|
|
}
|