mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-26 10:10:32 +00:00
5e752f4eda
Fix problem with the line cache where it would forget the first line in the cache in some cases. Keep as much backlog as we have taps. This generally works better and we could do even better by calculating the overlap in all taps. Allocated enough lines for the line cache. Use only half the number of taps for the interlaced lines because we only have half the number of lines. The pixel shift should be relative to the new output pixel size so scale it. Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=767921
447 lines
11 KiB
C
447 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
|
|
* @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);
|
|
}
|