mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-25 11:11:08 +00:00
4b5f78337a
This makes sure that we only build files that need explicit SIMD support with the relevant CFLAGS. This allows the rest of the code to be built without, and specific SSE* code is only called after runtime checks for CPU features. https://bugzilla.gnome.org/show_bug.cgi?id=729276
1781 lines
68 KiB
C
1781 lines
68 KiB
C
/* GStreamer
|
|
* Copyright (C) <2015> 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>
|
|
|
|
#ifdef HAVE_ORC
|
|
#include <orc/orc.h>
|
|
#endif
|
|
|
|
#include "audio-resampler.h"
|
|
#include "audio-resampler-private.h"
|
|
#include "audio-resampler-macros.h"
|
|
|
|
#define MEM_ALIGN(m,a) ((gint8 *)((guintptr)((gint8 *)(m) + ((a)-1)) & ~((a)-1)))
|
|
#define ALIGN 16
|
|
#define TAPS_OVERREAD 16
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (audio_resampler_debug);
|
|
#define GST_CAT_DEFAULT audio_resampler_debug
|
|
|
|
/**
|
|
* SECTION:gstaudioresampler
|
|
* @short_description: Utility structure for resampler information
|
|
*
|
|
* #GstAudioResampler is a structure which holds the information
|
|
* required to perform various kinds of resampling filtering.
|
|
*
|
|
*/
|
|
|
|
static const gint oversample_qualities[] = {
|
|
4, 4, 4, 8, 8, 16, 16, 16, 16, 32, 32
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
gdouble cutoff;
|
|
gdouble downsample_cutoff_factor;
|
|
gdouble stopband_attenuation;
|
|
gdouble transition_bandwidth;
|
|
} KaiserQualityMap;
|
|
|
|
static const KaiserQualityMap kaiser_qualities[] = {
|
|
{0.860, 0.96511, 60, 0.7}, /* 8 taps */
|
|
{0.880, 0.96591, 65, 0.29}, /* 16 taps */
|
|
{0.910, 0.96923, 70, 0.145}, /* 32 taps */
|
|
{0.920, 0.97600, 80, 0.105}, /* 48 taps */
|
|
{0.940, 0.97979, 85, 0.087}, /* 64 taps default quality */
|
|
{0.940, 0.98085, 95, 0.077}, /* 80 taps */
|
|
{0.945, 0.99471, 100, 0.068}, /* 96 taps */
|
|
{0.950, 1.0, 105, 0.055}, /* 128 taps */
|
|
{0.960, 1.0, 110, 0.045}, /* 160 taps */
|
|
{0.968, 1.0, 115, 0.039}, /* 192 taps */
|
|
{0.975, 1.0, 120, 0.0305} /* 256 taps */
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
gint n_taps;
|
|
gdouble cutoff;
|
|
} BlackmanQualityMap;
|
|
|
|
static const BlackmanQualityMap blackman_qualities[] = {
|
|
{8, 0.5,},
|
|
{16, 0.6,},
|
|
{24, 0.72,},
|
|
{32, 0.8,},
|
|
{48, 0.85,}, /* default */
|
|
{64, 0.90,},
|
|
{80, 0.92,},
|
|
{96, 0.933,},
|
|
{128, 0.950,},
|
|
{148, 0.955,},
|
|
{160, 0.960,}
|
|
};
|
|
|
|
#define DEFAULT_RESAMPLER_METHOD GST_AUDIO_RESAMPLER_METHOD_KAISER
|
|
#define DEFAULT_QUALITY GST_AUDIO_RESAMPLER_QUALITY_DEFAULT
|
|
#define DEFAULT_OPT_CUBIC_B 1.0
|
|
#define DEFAULT_OPT_CUBIC_C 0.0
|
|
#define DEFAULT_OPT_FILTER_MODE GST_AUDIO_RESAMPLER_FILTER_MODE_AUTO
|
|
#define DEFAULT_OPT_FILTER_MODE_THRESHOLD 1048576
|
|
#define DEFAULT_OPT_FILTER_INTERPOLATION GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_CUBIC
|
|
#define DEFAULT_OPT_FILTER_OVERSAMPLE 8
|
|
#define DEFAULT_OPT_MAX_PHASE_ERROR 0.1
|
|
|
|
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;
|
|
}
|
|
|
|
static gint
|
|
get_opt_enum (GstStructure * options, const gchar * name, GType type, gint def)
|
|
{
|
|
gint res;
|
|
if (!options || !gst_structure_get_enum (options, name, type, &res))
|
|
res = def;
|
|
return res;
|
|
}
|
|
|
|
|
|
#define GET_OPT_CUTOFF(options,def) get_opt_double(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_CUTOFF,def)
|
|
#define GET_OPT_DOWN_CUTOFF_FACTOR(options,def) get_opt_double(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_DOWN_CUTOFF_FACTOR, def)
|
|
#define GET_OPT_STOP_ATTENUATION(options,def) get_opt_double(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_STOP_ATTENUATION, def)
|
|
#define GET_OPT_TRANSITION_BANDWIDTH(options,def) get_opt_double(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_TRANSITION_BANDWIDTH, def)
|
|
#define GET_OPT_CUBIC_B(options) get_opt_double(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_CUBIC_B, DEFAULT_OPT_CUBIC_B)
|
|
#define GET_OPT_CUBIC_C(options) get_opt_double(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_CUBIC_C, DEFAULT_OPT_CUBIC_C)
|
|
#define GET_OPT_N_TAPS(options,def) get_opt_int(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_N_TAPS, def)
|
|
#define GET_OPT_FILTER_MODE(options) get_opt_enum(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_FILTER_MODE, GST_TYPE_AUDIO_RESAMPLER_FILTER_MODE, \
|
|
DEFAULT_OPT_FILTER_MODE)
|
|
#define GET_OPT_FILTER_MODE_THRESHOLD(options) get_opt_int(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_FILTER_MODE_THRESHOLD, DEFAULT_OPT_FILTER_MODE_THRESHOLD)
|
|
#define GET_OPT_FILTER_INTERPOLATION(options) get_opt_enum(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_FILTER_INTERPOLATION, GST_TYPE_AUDIO_RESAMPLER_FILTER_INTERPOLATION, \
|
|
DEFAULT_OPT_FILTER_INTERPOLATION)
|
|
#define GET_OPT_FILTER_OVERSAMPLE(options) get_opt_int(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_FILTER_OVERSAMPLE, DEFAULT_OPT_FILTER_OVERSAMPLE)
|
|
#define GET_OPT_MAX_PHASE_ERROR(options) get_opt_double(options, \
|
|
GST_AUDIO_RESAMPLER_OPT_MAX_PHASE_ERROR, DEFAULT_OPT_MAX_PHASE_ERROR)
|
|
|
|
#include "dbesi0.c"
|
|
#define bessel dbesi0
|
|
|
|
static inline gdouble
|
|
get_linear_tap (gdouble x, gint n_taps)
|
|
{
|
|
gdouble res = GST_ROUND_UP_2 (n_taps) / 2 - fabs (x);
|
|
return res;
|
|
}
|
|
|
|
static inline gdouble
|
|
get_cubic_tap (gdouble x, gint n_taps, gdouble b, gdouble c)
|
|
{
|
|
gdouble res, a, a2, a3;
|
|
|
|
a = fabs (x * 4.0) / n_taps;
|
|
a2 = a * a;
|
|
a3 = a2 * a;
|
|
|
|
if (a <= 1.0)
|
|
res = ((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)
|
|
res = ((-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
|
|
res = 0.0;
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline gdouble
|
|
get_blackman_nuttall_tap (gdouble x, gint n_taps, gdouble Fc)
|
|
{
|
|
gdouble s, y, w;
|
|
|
|
y = G_PI * x;
|
|
s = (y == 0.0 ? Fc : sin (y * Fc) / y);
|
|
|
|
w = 2.0 * y / n_taps + G_PI;
|
|
return s * (0.3635819 - 0.4891775 * cos (w) + 0.1365995 * cos (2 * w) -
|
|
0.0106411 * cos (3 * w));
|
|
}
|
|
|
|
static inline gdouble
|
|
get_kaiser_tap (gdouble x, gint n_taps, gdouble Fc, gdouble beta)
|
|
{
|
|
gdouble s, y, w;
|
|
|
|
y = G_PI * x;
|
|
s = (y == 0.0 ? Fc : sin (y * Fc) / y);
|
|
|
|
w = 2.0 * x / n_taps;
|
|
return s * bessel (beta * sqrt (MAX (1 - w * w, 0)));
|
|
}
|
|
|
|
#define MAKE_CONVERT_TAPS_INT_FUNC(type, precision) \
|
|
static void \
|
|
convert_taps_##type##_c (gdouble *tmp_taps, gpointer taps, \
|
|
gdouble weight, gint n_taps) \
|
|
{ \
|
|
gint64 one = (1LL << precision) - 1; \
|
|
type *t = taps; \
|
|
gdouble multiplier = one; \
|
|
gint i, j; \
|
|
gdouble offset, l_offset, h_offset; \
|
|
gboolean exact = FALSE; \
|
|
/* Round to integer, but with an adjustable bias that we use to */ \
|
|
/* eliminate the DC error. */ \
|
|
l_offset = 0.0; \
|
|
h_offset = 1.0; \
|
|
offset = 0.5; \
|
|
for (i = 0; i < 32; i++) { \
|
|
gint64 sum = 0; \
|
|
for (j = 0; j < n_taps; j++) \
|
|
sum += floor (offset + tmp_taps[j] * multiplier / weight); \
|
|
if (sum == one) { \
|
|
exact = TRUE; \
|
|
break; \
|
|
} \
|
|
if (l_offset == h_offset) \
|
|
break; \
|
|
if (sum < one) { \
|
|
if (offset > l_offset) \
|
|
l_offset = offset; \
|
|
offset += (h_offset - l_offset) / 2; \
|
|
} else { \
|
|
if (offset < h_offset) \
|
|
h_offset = offset; \
|
|
offset -= (h_offset - l_offset) / 2; \
|
|
} \
|
|
} \
|
|
for (j = 0; j < n_taps; j++) \
|
|
t[j] = floor (offset + tmp_taps[j] * multiplier / weight); \
|
|
if (!exact) \
|
|
GST_WARNING ("can't find exact taps"); \
|
|
}
|
|
|
|
#define MAKE_CONVERT_TAPS_FLOAT_FUNC(type) \
|
|
static void \
|
|
convert_taps_##type##_c (gdouble *tmp_taps, gpointer taps, \
|
|
gdouble weight, gint n_taps) \
|
|
{ \
|
|
gint i; \
|
|
type *t = taps; \
|
|
for (i = 0; i < n_taps; i++) \
|
|
t[i] = tmp_taps[i] / weight; \
|
|
}
|
|
|
|
MAKE_CONVERT_TAPS_INT_FUNC (gint16, PRECISION_S16);
|
|
MAKE_CONVERT_TAPS_INT_FUNC (gint32, PRECISION_S32);
|
|
MAKE_CONVERT_TAPS_FLOAT_FUNC (gfloat);
|
|
MAKE_CONVERT_TAPS_FLOAT_FUNC (gdouble);
|
|
|
|
static ConvertTapsFunc convert_taps_funcs[] = {
|
|
convert_taps_gint16_c,
|
|
convert_taps_gint32_c,
|
|
convert_taps_gfloat_c,
|
|
convert_taps_gdouble_c
|
|
};
|
|
|
|
#define convert_taps_gint16 convert_taps_funcs[0]
|
|
#define convert_taps_gint32 convert_taps_funcs[1]
|
|
#define convert_taps_gfloat convert_taps_funcs[2]
|
|
#define convert_taps_gdouble convert_taps_funcs[3]
|
|
|
|
static void
|
|
make_taps (GstAudioResampler * resampler, gdouble * res, gdouble x, gint n_taps)
|
|
{
|
|
gdouble weight = 0.0, *tmp_taps = resampler->tmp_taps;
|
|
gint i;
|
|
|
|
switch (resampler->method) {
|
|
case GST_AUDIO_RESAMPLER_METHOD_NEAREST:
|
|
break;
|
|
|
|
case GST_AUDIO_RESAMPLER_METHOD_LINEAR:
|
|
for (i = 0; i < n_taps; i++)
|
|
weight += tmp_taps[i] = get_linear_tap (x + i, resampler->n_taps);
|
|
break;
|
|
|
|
case GST_AUDIO_RESAMPLER_METHOD_CUBIC:
|
|
for (i = 0; i < n_taps; i++)
|
|
weight += tmp_taps[i] = get_cubic_tap (x + i, resampler->n_taps,
|
|
resampler->b, resampler->c);
|
|
break;
|
|
|
|
case GST_AUDIO_RESAMPLER_METHOD_BLACKMAN_NUTTALL:
|
|
for (i = 0; i < n_taps; i++)
|
|
weight += tmp_taps[i] =
|
|
get_blackman_nuttall_tap (x + i,
|
|
resampler->n_taps, resampler->cutoff);
|
|
break;
|
|
|
|
case GST_AUDIO_RESAMPLER_METHOD_KAISER:
|
|
for (i = 0; i < n_taps; i++)
|
|
weight += tmp_taps[i] =
|
|
get_kaiser_tap (x + i, resampler->n_taps,
|
|
resampler->cutoff, resampler->kaiser_beta);
|
|
break;
|
|
}
|
|
resampler->convert_taps (tmp_taps, res, weight, n_taps);
|
|
}
|
|
|
|
#define MAKE_COEFF_LINEAR_INT_FUNC(type,type2,prec) \
|
|
static inline void \
|
|
make_coeff_##type##_linear (gint num, gint denom, type *icoeff) \
|
|
{ \
|
|
type x = ((gint64)num << prec) / denom; \
|
|
icoeff[0] = icoeff[2] = x; \
|
|
icoeff[1] = icoeff[3] = (type)(((type2)1 << prec)-1) - x; \
|
|
}
|
|
#define MAKE_COEFF_LINEAR_FLOAT_FUNC(type) \
|
|
static inline void \
|
|
make_coeff_##type##_linear (gint num, gint denom, type *icoeff) \
|
|
{ \
|
|
type x = (type)num / denom; \
|
|
icoeff[0] = icoeff[2] = x; \
|
|
icoeff[1] = icoeff[3] = (type)1.0 - x; \
|
|
}
|
|
MAKE_COEFF_LINEAR_INT_FUNC (gint16, gint32, PRECISION_S16);
|
|
MAKE_COEFF_LINEAR_INT_FUNC (gint32, gint64, PRECISION_S32);
|
|
MAKE_COEFF_LINEAR_FLOAT_FUNC (gfloat);
|
|
MAKE_COEFF_LINEAR_FLOAT_FUNC (gdouble);
|
|
|
|
#define MAKE_COEFF_CUBIC_INT_FUNC(type,type2,prec) \
|
|
static inline void \
|
|
make_coeff_##type##_cubic (gint num, gint denom, type *icoeff) \
|
|
{ \
|
|
type2 one = ((type2)1 << prec) - 1; \
|
|
type2 x = ((gint64) num << prec) / denom; \
|
|
type2 x2 = (x * x) >> prec; \
|
|
type2 x3 = (x2 * x) >> prec; \
|
|
icoeff[0] = (((x3 - x) << prec) / 6) >> prec; \
|
|
icoeff[1] = x + ((x2 - x3) >> 1); \
|
|
icoeff[3] = -(((x << prec) / 3) >> prec) + \
|
|
(x2 >> 1) - (((x3 << prec) / 6) >> prec); \
|
|
icoeff[2] = one - icoeff[0] - icoeff[1] - icoeff[3]; \
|
|
}
|
|
#define MAKE_COEFF_CUBIC_FLOAT_FUNC(type) \
|
|
static inline void \
|
|
make_coeff_##type##_cubic (gint num, gint denom, type *icoeff) \
|
|
{ \
|
|
type x = (type) num / denom, x2 = x * x, x3 = x2 * x; \
|
|
icoeff[0] = 0.16667f * (x3 - x); \
|
|
icoeff[1] = x + 0.5f * (x2 - x3); \
|
|
icoeff[3] = -0.33333f * x + 0.5f * x2 - 0.16667f * x3; \
|
|
icoeff[2] = (type)1.0 - icoeff[0] - icoeff[1] - icoeff[3]; \
|
|
}
|
|
MAKE_COEFF_CUBIC_INT_FUNC (gint16, gint32, PRECISION_S16);
|
|
MAKE_COEFF_CUBIC_INT_FUNC (gint32, gint64, PRECISION_S32);
|
|
MAKE_COEFF_CUBIC_FLOAT_FUNC (gfloat);
|
|
MAKE_COEFF_CUBIC_FLOAT_FUNC (gdouble);
|
|
|
|
#define INTERPOLATE_INT_LINEAR_FUNC(type,type2,prec,limit) \
|
|
static inline void \
|
|
interpolate_##type##_linear_c (gpointer op, const gpointer ap, \
|
|
gint len, const gpointer icp, gint astride) \
|
|
{ \
|
|
gint i; \
|
|
type *o = op, *a = ap, *ic = icp; \
|
|
type2 tmp, c0 = ic[0]; \
|
|
const type *c[2] = {(type*)((gint8*)a + 0*astride), \
|
|
(type*)((gint8*)a + 1*astride)}; \
|
|
\
|
|
for (i = 0; i < len; i++) { \
|
|
tmp = ((type2)c[0][i] - (type2)c[1][i]) * c0 + \
|
|
(((type2)c[1][i]) << (prec)); \
|
|
o[i] = (tmp + ((type2)1 << ((prec) - 1))) >> (prec); \
|
|
} \
|
|
}
|
|
#define INTERPOLATE_FLOAT_LINEAR_FUNC(type) \
|
|
static inline void \
|
|
interpolate_##type##_linear_c (gpointer op, const gpointer ap, \
|
|
gint len, const gpointer icp, gint astride) \
|
|
{ \
|
|
gint i; \
|
|
type *o = op, *a = ap, *ic = icp; \
|
|
type c0 = ic[0]; \
|
|
const type *c[2] = {(type*)((gint8*)a + 0*astride), \
|
|
(type*)((gint8*)a + 1*astride)}; \
|
|
\
|
|
for (i = 0; i < len; i++) { \
|
|
o[i] = (c[0][i] - c[1][i]) * c0 + c[1][i]; \
|
|
} \
|
|
}
|
|
|
|
INTERPOLATE_INT_LINEAR_FUNC (gint16, gint32, PRECISION_S16, (gint32) 1 << 15);
|
|
INTERPOLATE_INT_LINEAR_FUNC (gint32, gint64, PRECISION_S32, (gint64) 1 << 31);
|
|
INTERPOLATE_FLOAT_LINEAR_FUNC (gfloat);
|
|
INTERPOLATE_FLOAT_LINEAR_FUNC (gdouble);
|
|
|
|
#define INTERPOLATE_INT_CUBIC_FUNC(type,type2,prec,limit) \
|
|
static inline void \
|
|
interpolate_##type##_cubic_c (gpointer op, const gpointer ap, \
|
|
gint len, const gpointer icp, gint astride) \
|
|
{ \
|
|
gint i; \
|
|
type *o = op, *a = ap, *ic = icp; \
|
|
type2 tmp, c0 = ic[0], c1 = ic[1], c2 = ic[2], c3 = ic[3]; \
|
|
const type *c[4] = {(type*)((gint8*)a + 0*astride), \
|
|
(type*)((gint8*)a + 1*astride), \
|
|
(type*)((gint8*)a + 2*astride), \
|
|
(type*)((gint8*)a + 3*astride)}; \
|
|
\
|
|
for (i = 0; i < len; i++) { \
|
|
tmp = (type2)c[0][i] * c0 + (type2)c[1][i] * c1 + \
|
|
(type2)c[2][i] * c2 + (type2)c[3][i] * c3; \
|
|
tmp = (tmp + ((type2)1 << ((prec) - 1))) >> (prec); \
|
|
o[i] = CLAMP (tmp, -(limit), (limit) - 1); \
|
|
} \
|
|
}
|
|
#define INTERPOLATE_FLOAT_CUBIC_FUNC(type) \
|
|
static inline void \
|
|
interpolate_##type##_cubic_c (gpointer op, const gpointer ap, \
|
|
gint len, const gpointer icp, gint astride) \
|
|
{ \
|
|
gint i; \
|
|
type *o = op, *a = ap, *ic = icp; \
|
|
type c0 = ic[0], c1 = ic[1], c2 = ic[2], c3 = ic[3]; \
|
|
const type *c[4] = {(type*)((gint8*)a + 0*astride), \
|
|
(type*)((gint8*)a + 1*astride), \
|
|
(type*)((gint8*)a + 2*astride), \
|
|
(type*)((gint8*)a + 3*astride)}; \
|
|
\
|
|
for (i = 0; i < len; i++) { \
|
|
o[i] = c[0][i] * c0 + c[1][i] * c1 + \
|
|
c[2][i] * c2 + c[3][i] * c3; \
|
|
} \
|
|
}
|
|
|
|
INTERPOLATE_INT_CUBIC_FUNC (gint16, gint32, PRECISION_S16, (gint32) 1 << 15);
|
|
INTERPOLATE_INT_CUBIC_FUNC (gint32, gint64, PRECISION_S32, (gint64) 1 << 31);
|
|
INTERPOLATE_FLOAT_CUBIC_FUNC (gfloat);
|
|
INTERPOLATE_FLOAT_CUBIC_FUNC (gdouble);
|
|
|
|
static InterpolateFunc interpolate_funcs[] = {
|
|
interpolate_gint16_linear_c,
|
|
interpolate_gint32_linear_c,
|
|
interpolate_gfloat_linear_c,
|
|
interpolate_gdouble_linear_c,
|
|
|
|
interpolate_gint16_cubic_c,
|
|
interpolate_gint32_cubic_c,
|
|
interpolate_gfloat_cubic_c,
|
|
interpolate_gdouble_cubic_c,
|
|
};
|
|
|
|
#define interpolate_gint16_linear interpolate_funcs[0]
|
|
#define interpolate_gint32_linear interpolate_funcs[1]
|
|
#define interpolate_gfloat_linear interpolate_funcs[2]
|
|
#define interpolate_gdouble_linear interpolate_funcs[3]
|
|
|
|
#define interpolate_gint16_cubic interpolate_funcs[4]
|
|
#define interpolate_gint32_cubic interpolate_funcs[5]
|
|
#define interpolate_gfloat_cubic interpolate_funcs[6]
|
|
#define interpolate_gdouble_cubic interpolate_funcs[7]
|
|
|
|
#define GET_TAPS_NEAREST_FUNC(type) \
|
|
static inline gpointer \
|
|
get_taps_##type##_nearest (GstAudioResampler * resampler, \
|
|
gint *samp_index, gint *samp_phase, type icoeff[4]) \
|
|
{ \
|
|
gint out_rate = resampler->out_rate; \
|
|
*samp_index += resampler->samp_inc; \
|
|
*samp_phase += resampler->samp_frac; \
|
|
if (*samp_phase >= out_rate) { \
|
|
*samp_phase -= out_rate; \
|
|
*samp_index += 1; \
|
|
} \
|
|
return NULL; \
|
|
}
|
|
GET_TAPS_NEAREST_FUNC (gint16);
|
|
GET_TAPS_NEAREST_FUNC (gint32);
|
|
GET_TAPS_NEAREST_FUNC (gfloat);
|
|
GET_TAPS_NEAREST_FUNC (gdouble);
|
|
|
|
#define get_taps_gint16_nearest get_taps_gint16_nearest
|
|
#define get_taps_gint32_nearest get_taps_gint32_nearest
|
|
#define get_taps_gfloat_nearest get_taps_gfloat_nearest
|
|
#define get_taps_gdouble_nearest get_taps_gdouble_nearest
|
|
|
|
#define GET_TAPS_FULL_FUNC(type) \
|
|
DECL_GET_TAPS_FULL_FUNC(type) \
|
|
{ \
|
|
gpointer res; \
|
|
gint out_rate = resampler->out_rate; \
|
|
gint n_phases = resampler->n_phases; \
|
|
gint phase = (n_phases == out_rate ? *samp_phase : \
|
|
((gint64)*samp_phase * n_phases) / out_rate); \
|
|
\
|
|
res = resampler->cached_phases[phase]; \
|
|
if (G_UNLIKELY (res == NULL)) { \
|
|
res = (gint8 *) resampler->cached_taps + \
|
|
phase * resampler->cached_taps_stride; \
|
|
switch (resampler->filter_interpolation) { \
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_NONE: \
|
|
{ \
|
|
gdouble x; \
|
|
gint n_taps = resampler->n_taps; \
|
|
\
|
|
x = 1.0 - n_taps / 2 - (gdouble) phase / n_phases; \
|
|
make_taps (resampler, res, x, n_taps); \
|
|
break; \
|
|
} \
|
|
default: \
|
|
{ \
|
|
gint offset, pos, frac; \
|
|
gint oversample = resampler->oversample; \
|
|
gint taps_stride = resampler->taps_stride; \
|
|
gint n_taps = resampler->n_taps; \
|
|
type ic[4], *taps; \
|
|
\
|
|
pos = phase * oversample; \
|
|
offset = (oversample - 1) - pos / n_phases; \
|
|
frac = pos % n_phases; \
|
|
\
|
|
taps = (type *) ((gint8 *) resampler->taps + offset * taps_stride); \
|
|
\
|
|
switch (resampler->filter_interpolation) { \
|
|
default: \
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_LINEAR: \
|
|
make_coeff_##type##_linear (frac, n_phases, ic); \
|
|
break; \
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_CUBIC: \
|
|
make_coeff_##type##_cubic (frac, n_phases, ic); \
|
|
break; \
|
|
} \
|
|
resampler->interpolate (res, taps, n_taps, ic, taps_stride); \
|
|
} \
|
|
} \
|
|
resampler->cached_phases[phase] = res; \
|
|
} \
|
|
*samp_index += resampler->samp_inc; \
|
|
*samp_phase += resampler->samp_frac; \
|
|
if (*samp_phase >= out_rate) { \
|
|
*samp_phase -= out_rate; \
|
|
*samp_index += 1; \
|
|
} \
|
|
return res; \
|
|
}
|
|
GET_TAPS_FULL_FUNC (gint16);
|
|
GET_TAPS_FULL_FUNC (gint32);
|
|
GET_TAPS_FULL_FUNC (gfloat);
|
|
GET_TAPS_FULL_FUNC (gdouble);
|
|
|
|
#define GET_TAPS_INTERPOLATE_FUNC(type,inter) \
|
|
DECL_GET_TAPS_INTERPOLATE_FUNC (type, inter) \
|
|
{ \
|
|
gpointer res; \
|
|
gint out_rate = resampler->out_rate; \
|
|
gint offset, frac, pos; \
|
|
gint oversample = resampler->oversample; \
|
|
gint taps_stride = resampler->taps_stride; \
|
|
\
|
|
pos = *samp_phase * oversample; \
|
|
offset = (oversample - 1) - pos / out_rate; \
|
|
frac = pos % out_rate; \
|
|
\
|
|
res = (gint8 *) resampler->taps + offset * taps_stride; \
|
|
make_coeff_##type##_##inter (frac, out_rate, icoeff); \
|
|
\
|
|
*samp_index += resampler->samp_inc; \
|
|
*samp_phase += resampler->samp_frac; \
|
|
if (*samp_phase >= out_rate) { \
|
|
*samp_phase -= out_rate; \
|
|
*samp_index += 1; \
|
|
} \
|
|
return res; \
|
|
}
|
|
|
|
GET_TAPS_INTERPOLATE_FUNC (gint16, linear);
|
|
GET_TAPS_INTERPOLATE_FUNC (gint32, linear);
|
|
GET_TAPS_INTERPOLATE_FUNC (gfloat, linear);
|
|
GET_TAPS_INTERPOLATE_FUNC (gdouble, linear);
|
|
|
|
GET_TAPS_INTERPOLATE_FUNC (gint16, cubic);
|
|
GET_TAPS_INTERPOLATE_FUNC (gint32, cubic);
|
|
GET_TAPS_INTERPOLATE_FUNC (gfloat, cubic);
|
|
GET_TAPS_INTERPOLATE_FUNC (gdouble, cubic);
|
|
|
|
#define INNER_PRODUCT_NEAREST_FUNC(type) \
|
|
static inline void \
|
|
inner_product_##type##_nearest_1_c (type * o, const type * a, \
|
|
const type * b, gint len, const type *ic, gint bstride) \
|
|
{ \
|
|
*o = *a; \
|
|
}
|
|
INNER_PRODUCT_NEAREST_FUNC (gint16);
|
|
INNER_PRODUCT_NEAREST_FUNC (gint32);
|
|
INNER_PRODUCT_NEAREST_FUNC (gfloat);
|
|
INNER_PRODUCT_NEAREST_FUNC (gdouble);
|
|
|
|
#define INNER_PRODUCT_INT_FULL_FUNC(type,type2,prec,limit) \
|
|
static inline void \
|
|
inner_product_##type##_full_1_c (type * o, const type * a, \
|
|
const type * b, gint len, const type *ic, gint bstride) \
|
|
{ \
|
|
gint i; \
|
|
type2 res[4] = { 0, 0, 0, 0 }; \
|
|
\
|
|
for (i = 0; i < len; i += 4) { \
|
|
res[0] += (type2) a[i + 0] * (type2) b[i + 0]; \
|
|
res[1] += (type2) a[i + 1] * (type2) b[i + 1]; \
|
|
res[2] += (type2) a[i + 2] * (type2) b[i + 2]; \
|
|
res[3] += (type2) a[i + 3] * (type2) b[i + 3]; \
|
|
} \
|
|
res[0] = res[0] + res[1] + res[2] + res[3]; \
|
|
res[0] = (res[0] + ((type2)1 << ((prec) - 1))) >> (prec); \
|
|
*o = CLAMP (res[0], -(limit), (limit) - 1); \
|
|
}
|
|
|
|
INNER_PRODUCT_INT_FULL_FUNC (gint16, gint32, PRECISION_S16, (gint32) 1 << 15);
|
|
INNER_PRODUCT_INT_FULL_FUNC (gint32, gint64, PRECISION_S32, (gint64) 1 << 31);
|
|
|
|
#define INNER_PRODUCT_INT_LINEAR_FUNC(type,type2,prec,limit) \
|
|
static inline void \
|
|
inner_product_##type##_linear_1_c (type * o, const type * a, \
|
|
const type * b, gint len, const type *ic, gint bstride) \
|
|
{ \
|
|
gint i; \
|
|
type2 res[4] = { 0, 0, 0, 0 }, c0 = ic[0]; \
|
|
const type *c[2] = {(type*)((gint8*)b + 0*bstride), \
|
|
(type*)((gint8*)b + 1*bstride)}; \
|
|
\
|
|
for (i = 0; i < len; i += 2) { \
|
|
res[0] += (type2) a[i + 0] * (type2) c[0][i + 0]; \
|
|
res[1] += (type2) a[i + 0] * (type2) c[1][i + 0]; \
|
|
res[2] += (type2) a[i + 1] * (type2) c[0][i + 1]; \
|
|
res[3] += (type2) a[i + 1] * (type2) c[1][i + 1]; \
|
|
} \
|
|
res[0] = (res[0] + res[2]) >> (prec); \
|
|
res[1] = (res[1] + res[3]) >> (prec); \
|
|
res[0] = ((type2)(type)res[0] - (type2)(type)res[1]) * c0 + \
|
|
((type2)(type)res[1] << (prec)); \
|
|
res[0] = (res[0] + ((type2)1 << ((prec) - 1))) >> (prec); \
|
|
*o = CLAMP (res[0], -(limit), (limit) - 1); \
|
|
}
|
|
|
|
INNER_PRODUCT_INT_LINEAR_FUNC (gint16, gint32, PRECISION_S16, (gint32) 1 << 15);
|
|
INNER_PRODUCT_INT_LINEAR_FUNC (gint32, gint64, PRECISION_S32, (gint64) 1 << 31);
|
|
|
|
#define INNER_PRODUCT_INT_CUBIC_FUNC(type,type2,prec,limit) \
|
|
static inline void \
|
|
inner_product_##type##_cubic_1_c (type * o, const type * a, \
|
|
const type * b, gint len, const type *ic, gint bstride) \
|
|
{ \
|
|
gint i; \
|
|
type2 res[4] = { 0, 0, 0, 0 }; \
|
|
const type *c[4] = {(type*)((gint8*)b + 0*bstride), \
|
|
(type*)((gint8*)b + 1*bstride), \
|
|
(type*)((gint8*)b + 2*bstride), \
|
|
(type*)((gint8*)b + 3*bstride)}; \
|
|
\
|
|
for (i = 0; i < len; i++) { \
|
|
res[0] += (type2) a[i] * (type2) c[0][i]; \
|
|
res[1] += (type2) a[i] * (type2) c[1][i]; \
|
|
res[2] += (type2) a[i] * (type2) c[2][i]; \
|
|
res[3] += (type2) a[i] * (type2) c[3][i]; \
|
|
} \
|
|
res[0] = (type2)(type)(res[0] >> (prec)) * (type2) ic[0] + \
|
|
(type2)(type)(res[1] >> (prec)) * (type2) ic[1] + \
|
|
(type2)(type)(res[2] >> (prec)) * (type2) ic[2] + \
|
|
(type2)(type)(res[3] >> (prec)) * (type2) ic[3]; \
|
|
res[0] = (res[0] + ((type2)1 << ((prec) - 1))) >> (prec); \
|
|
*o = CLAMP (res[0], -(limit), (limit) - 1); \
|
|
}
|
|
|
|
INNER_PRODUCT_INT_CUBIC_FUNC (gint16, gint32, PRECISION_S16, (gint32) 1 << 15);
|
|
INNER_PRODUCT_INT_CUBIC_FUNC (gint32, gint64, PRECISION_S32, (gint64) 1 << 31);
|
|
|
|
#define INNER_PRODUCT_FLOAT_FULL_FUNC(type) \
|
|
static inline void \
|
|
inner_product_##type##_full_1_c (type * o, const type * a, \
|
|
const type * b, gint len, const type *ic, gint bstride) \
|
|
{ \
|
|
gint i; \
|
|
type res[4] = { 0.0, 0.0, 0.0, 0.0 }; \
|
|
\
|
|
for (i = 0; i < len; i += 4) { \
|
|
res[0] += a[i + 0] * b[i + 0]; \
|
|
res[1] += a[i + 1] * b[i + 1]; \
|
|
res[2] += a[i + 2] * b[i + 2]; \
|
|
res[3] += a[i + 3] * b[i + 3]; \
|
|
} \
|
|
*o = res[0] + res[1] + res[2] + res[3]; \
|
|
}
|
|
|
|
INNER_PRODUCT_FLOAT_FULL_FUNC (gfloat);
|
|
INNER_PRODUCT_FLOAT_FULL_FUNC (gdouble);
|
|
|
|
#define INNER_PRODUCT_FLOAT_LINEAR_FUNC(type) \
|
|
static inline void \
|
|
inner_product_##type##_linear_1_c (type * o, const type * a, \
|
|
const type * b, gint len, const type *ic, gint bstride) \
|
|
{ \
|
|
gint i; \
|
|
type res[4] = { 0.0, 0.0, 0.0, 0.0 }; \
|
|
const type *c[2] = {(type*)((gint8*)b + 0*bstride), \
|
|
(type*)((gint8*)b + 1*bstride)}; \
|
|
\
|
|
for (i = 0; i < len; i += 2) { \
|
|
res[0] += a[i + 0] * c[0][i + 0]; \
|
|
res[1] += a[i + 0] * c[1][i + 0]; \
|
|
res[2] += a[i + 1] * c[0][i + 1]; \
|
|
res[3] += a[i + 1] * c[1][i + 1]; \
|
|
} \
|
|
res[0] += res[2]; \
|
|
res[1] += res[3]; \
|
|
*o = (res[0] - res[1]) * ic[0] + res[1]; \
|
|
}
|
|
INNER_PRODUCT_FLOAT_LINEAR_FUNC (gfloat);
|
|
INNER_PRODUCT_FLOAT_LINEAR_FUNC (gdouble);
|
|
|
|
#define INNER_PRODUCT_FLOAT_CUBIC_FUNC(type) \
|
|
static inline void \
|
|
inner_product_##type##_cubic_1_c (type * o, const type * a, \
|
|
const type * b, gint len, const type *ic, gint bstride) \
|
|
{ \
|
|
gint i; \
|
|
type res[4] = { 0.0, 0.0, 0.0, 0.0 }; \
|
|
const type *c[4] = {(type*)((gint8*)b + 0*bstride), \
|
|
(type*)((gint8*)b + 1*bstride), \
|
|
(type*)((gint8*)b + 2*bstride), \
|
|
(type*)((gint8*)b + 3*bstride)}; \
|
|
\
|
|
for (i = 0; i < len; i++) { \
|
|
res[0] += a[i] * c[0][i]; \
|
|
res[1] += a[i] * c[1][i]; \
|
|
res[2] += a[i] * c[2][i]; \
|
|
res[3] += a[i] * c[3][i]; \
|
|
} \
|
|
*o = res[0] * ic[0] + res[1] * ic[1] + \
|
|
res[2] * ic[2] + res[3] * ic[3]; \
|
|
}
|
|
INNER_PRODUCT_FLOAT_CUBIC_FUNC (gfloat);
|
|
INNER_PRODUCT_FLOAT_CUBIC_FUNC (gdouble);
|
|
|
|
MAKE_RESAMPLE_FUNC_STATIC (gint16, nearest, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gint32, nearest, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gfloat, nearest, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gdouble, nearest, 1, c);
|
|
|
|
MAKE_RESAMPLE_FUNC_STATIC (gint16, full, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gint32, full, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gfloat, full, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gdouble, full, 1, c);
|
|
|
|
MAKE_RESAMPLE_FUNC_STATIC (gint16, linear, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gint32, linear, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gfloat, linear, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gdouble, linear, 1, c);
|
|
|
|
MAKE_RESAMPLE_FUNC_STATIC (gint16, cubic, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gint32, cubic, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gfloat, cubic, 1, c);
|
|
MAKE_RESAMPLE_FUNC_STATIC (gdouble, cubic, 1, c);
|
|
|
|
static ResampleFunc resample_funcs[] = {
|
|
resample_gint16_nearest_1_c,
|
|
resample_gint32_nearest_1_c,
|
|
resample_gfloat_nearest_1_c,
|
|
resample_gdouble_nearest_1_c,
|
|
|
|
resample_gint16_full_1_c,
|
|
resample_gint32_full_1_c,
|
|
resample_gfloat_full_1_c,
|
|
resample_gdouble_full_1_c,
|
|
|
|
resample_gint16_linear_1_c,
|
|
resample_gint32_linear_1_c,
|
|
resample_gfloat_linear_1_c,
|
|
resample_gdouble_linear_1_c,
|
|
|
|
resample_gint16_cubic_1_c,
|
|
resample_gint32_cubic_1_c,
|
|
resample_gfloat_cubic_1_c,
|
|
resample_gdouble_cubic_1_c,
|
|
};
|
|
|
|
#define resample_gint16_nearest_1 resample_funcs[0]
|
|
#define resample_gint32_nearest_1 resample_funcs[1]
|
|
#define resample_gfloat_nearest_1 resample_funcs[2]
|
|
#define resample_gdouble_nearest_1 resample_funcs[3]
|
|
|
|
#define resample_gint16_full_1 resample_funcs[4]
|
|
#define resample_gint32_full_1 resample_funcs[5]
|
|
#define resample_gfloat_full_1 resample_funcs[6]
|
|
#define resample_gdouble_full_1 resample_funcs[7]
|
|
|
|
#define resample_gint16_linear_1 resample_funcs[8]
|
|
#define resample_gint32_linear_1 resample_funcs[9]
|
|
#define resample_gfloat_linear_1 resample_funcs[10]
|
|
#define resample_gdouble_linear_1 resample_funcs[11]
|
|
|
|
#define resample_gint16_cubic_1 resample_funcs[12]
|
|
#define resample_gint32_cubic_1 resample_funcs[13]
|
|
#define resample_gfloat_cubic_1 resample_funcs[14]
|
|
#define resample_gdouble_cubic_1 resample_funcs[15]
|
|
|
|
#if defined HAVE_ORC && !defined DISABLE_ORC
|
|
# if defined (HAVE_ARM_NEON)
|
|
# define CHECK_NEON
|
|
# include "audio-resampler-neon.h"
|
|
# endif
|
|
# if defined (__i386__) || defined (__x86_64__)
|
|
# define CHECK_X86
|
|
# include "audio-resampler-x86.h"
|
|
# endif
|
|
#endif
|
|
|
|
static void
|
|
audio_resampler_init (void)
|
|
{
|
|
static gsize init_gonce = 0;
|
|
|
|
if (g_once_init_enter (&init_gonce)) {
|
|
|
|
GST_DEBUG_CATEGORY_INIT (audio_resampler_debug, "audio-resampler", 0,
|
|
"audio-resampler object");
|
|
|
|
#if defined HAVE_ORC && !defined DISABLE_ORC
|
|
orc_init ();
|
|
{
|
|
OrcTarget *target = orc_target_get_default ();
|
|
gint i;
|
|
|
|
if (target) {
|
|
const gchar *name;
|
|
unsigned int flags = orc_target_get_default_flags (target);
|
|
|
|
for (i = -1; i < 32; ++i) {
|
|
if (i == -1) {
|
|
name = orc_target_get_name (target);
|
|
GST_DEBUG ("target %s, default flags %08x", name, flags);
|
|
} else if (flags & (1U << i)) {
|
|
name = orc_target_get_flag_name (target, i);
|
|
GST_DEBUG ("target flag %s", name);
|
|
} else
|
|
name = NULL;
|
|
|
|
if (name) {
|
|
#ifdef CHECK_X86
|
|
audio_resampler_check_x86 (name);
|
|
#endif
|
|
#ifdef CHECK_NEON
|
|
audio_resampler_check_neon (name);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
g_once_init_leave (&init_gonce, 1);
|
|
}
|
|
}
|
|
|
|
#define MAKE_DEINTERLEAVE_FUNC(type) \
|
|
static void \
|
|
deinterleave_ ##type (GstAudioResampler * resampler, gpointer sbuf[], \
|
|
gpointer in[], gsize in_frames) \
|
|
{ \
|
|
gint i, c, channels = resampler->channels; \
|
|
gsize samples_avail = resampler->samples_avail; \
|
|
for (c = 0; c < channels; c++) { \
|
|
type *s = (type *) sbuf[c] + samples_avail; \
|
|
if (G_UNLIKELY (in == NULL)) { \
|
|
for (i = 0; i < in_frames; i++) \
|
|
s[i] = 0; \
|
|
} else { \
|
|
type *ip = (type *) in[0] + c; \
|
|
for (i = 0; i < in_frames; i++, ip += channels) \
|
|
s[i] = *ip; \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
MAKE_DEINTERLEAVE_FUNC (gint16);
|
|
MAKE_DEINTERLEAVE_FUNC (gint32);
|
|
MAKE_DEINTERLEAVE_FUNC (gfloat);
|
|
MAKE_DEINTERLEAVE_FUNC (gdouble);
|
|
|
|
static DeinterleaveFunc deinterleave_funcs[] = {
|
|
deinterleave_gint16,
|
|
deinterleave_gint32,
|
|
deinterleave_gfloat,
|
|
deinterleave_gdouble
|
|
};
|
|
|
|
static void
|
|
calculate_kaiser_params (GstAudioResampler * resampler)
|
|
{
|
|
gdouble A, B, dw, tr_bw, Fc;
|
|
gint n;
|
|
const KaiserQualityMap *q = &kaiser_qualities[DEFAULT_QUALITY];
|
|
|
|
/* default cutoff */
|
|
Fc = q->cutoff;
|
|
if (resampler->out_rate < resampler->in_rate)
|
|
Fc *= q->downsample_cutoff_factor;
|
|
|
|
Fc = GET_OPT_CUTOFF (resampler->options, Fc);
|
|
A = GET_OPT_STOP_ATTENUATION (resampler->options, q->stopband_attenuation);
|
|
tr_bw =
|
|
GET_OPT_TRANSITION_BANDWIDTH (resampler->options,
|
|
q->transition_bandwidth);
|
|
|
|
GST_LOG ("Fc %f, A %f, tr_bw %f", Fc, A, tr_bw);
|
|
|
|
/* calculate Beta */
|
|
if (A > 50)
|
|
B = 0.1102 * (A - 8.7);
|
|
else if (A >= 21)
|
|
B = 0.5842 * pow (A - 21, 0.4) + 0.07886 * (A - 21);
|
|
else
|
|
B = 0.0;
|
|
/* calculate transition width in radians */
|
|
dw = 2 * G_PI * (tr_bw);
|
|
/* order of the filter */
|
|
n = (A - 8.0) / (2.285 * dw);
|
|
|
|
resampler->kaiser_beta = B;
|
|
resampler->n_taps = n + 1;
|
|
resampler->cutoff = Fc;
|
|
|
|
GST_LOG ("using Beta %f n_taps %d cutoff %f", resampler->kaiser_beta,
|
|
resampler->n_taps, resampler->cutoff);
|
|
}
|
|
|
|
static void
|
|
alloc_taps_mem (GstAudioResampler * resampler, gint bps, gint n_taps,
|
|
gint n_phases)
|
|
{
|
|
if (resampler->alloc_taps >= n_taps && resampler->alloc_phases >= n_phases)
|
|
return;
|
|
|
|
GST_DEBUG ("allocate bps %d n_taps %d n_phases %d", bps, n_taps, n_phases);
|
|
|
|
resampler->tmp_taps =
|
|
g_realloc_n (resampler->tmp_taps, n_taps, sizeof (gdouble));
|
|
|
|
resampler->taps_stride = GST_ROUND_UP_32 (bps * (n_taps + TAPS_OVERREAD));
|
|
|
|
g_free (resampler->taps_mem);
|
|
resampler->taps_mem =
|
|
g_malloc0 (n_phases * resampler->taps_stride + ALIGN - 1);
|
|
resampler->taps = MEM_ALIGN ((gint8 *) resampler->taps_mem, ALIGN);
|
|
resampler->alloc_taps = n_taps;
|
|
resampler->alloc_phases = n_phases;
|
|
}
|
|
|
|
static void
|
|
alloc_cache_mem (GstAudioResampler * resampler, gint bps, gint n_taps,
|
|
gint n_phases)
|
|
{
|
|
gsize phases_size;
|
|
|
|
resampler->tmp_taps =
|
|
g_realloc_n (resampler->tmp_taps, n_taps, sizeof (gdouble));
|
|
|
|
resampler->cached_taps_stride =
|
|
GST_ROUND_UP_32 (bps * (n_taps + TAPS_OVERREAD));
|
|
|
|
phases_size = sizeof (gpointer) * n_phases;
|
|
|
|
g_free (resampler->cached_taps_mem);
|
|
resampler->cached_taps_mem =
|
|
g_malloc0 (phases_size + n_phases * resampler->cached_taps_stride +
|
|
ALIGN - 1);
|
|
resampler->cached_taps =
|
|
MEM_ALIGN ((gint8 *) resampler->cached_taps_mem + phases_size, ALIGN);
|
|
resampler->cached_phases = resampler->cached_taps_mem;
|
|
}
|
|
|
|
static void
|
|
setup_functions (GstAudioResampler * resampler)
|
|
{
|
|
gint index, fidx;
|
|
|
|
index = resampler->format_index;
|
|
|
|
if (resampler->in_rate == resampler->out_rate)
|
|
resampler->resample = resample_funcs[index];
|
|
else {
|
|
switch (resampler->filter_interpolation) {
|
|
default:
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_NONE:
|
|
fidx = 0;
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_LINEAR:
|
|
GST_DEBUG ("using linear interpolation for filter coefficients");
|
|
fidx = 0;
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_CUBIC:
|
|
GST_DEBUG ("using cubic interpolation for filter coefficients");
|
|
fidx = 4;
|
|
break;
|
|
}
|
|
GST_DEBUG ("using filter interpolate function %d", index + fidx);
|
|
resampler->interpolate = interpolate_funcs[index + fidx];
|
|
|
|
switch (resampler->method) {
|
|
case GST_AUDIO_RESAMPLER_METHOD_NEAREST:
|
|
GST_DEBUG ("using nearest filter function");
|
|
break;
|
|
default:
|
|
index += 4;
|
|
switch (resampler->filter_mode) {
|
|
default:
|
|
case GST_AUDIO_RESAMPLER_FILTER_MODE_FULL:
|
|
GST_DEBUG ("using full filter function");
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_FILTER_MODE_INTERPOLATED:
|
|
index += 4 + fidx;
|
|
GST_DEBUG ("using interpolated filter function");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
GST_DEBUG ("using resample function %d", index);
|
|
resampler->resample = resample_funcs[index];
|
|
}
|
|
}
|
|
|
|
static void
|
|
resampler_calculate_taps (GstAudioResampler * resampler)
|
|
{
|
|
gint bps;
|
|
gint n_taps, oversample;
|
|
gint in_rate, out_rate;
|
|
gboolean scale = TRUE, sinc_table = FALSE;
|
|
GstAudioResamplerFilterInterpolation filter_interpolation;
|
|
|
|
switch (resampler->method) {
|
|
case GST_AUDIO_RESAMPLER_METHOD_NEAREST:
|
|
resampler->n_taps = 2;
|
|
scale = FALSE;
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_METHOD_LINEAR:
|
|
resampler->n_taps = GET_OPT_N_TAPS (resampler->options, 2);
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_METHOD_CUBIC:
|
|
resampler->n_taps = GET_OPT_N_TAPS (resampler->options, 4);
|
|
resampler->b = GET_OPT_CUBIC_B (resampler->options);
|
|
resampler->c = GET_OPT_CUBIC_C (resampler->options);;
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_METHOD_BLACKMAN_NUTTALL:
|
|
{
|
|
const BlackmanQualityMap *q = &blackman_qualities[DEFAULT_QUALITY];
|
|
resampler->n_taps = GET_OPT_N_TAPS (resampler->options, q->n_taps);
|
|
resampler->cutoff = GET_OPT_CUTOFF (resampler->options, q->cutoff);
|
|
sinc_table = TRUE;
|
|
break;
|
|
}
|
|
case GST_AUDIO_RESAMPLER_METHOD_KAISER:
|
|
calculate_kaiser_params (resampler);
|
|
sinc_table = TRUE;
|
|
break;
|
|
}
|
|
|
|
in_rate = resampler->in_rate;
|
|
out_rate = resampler->out_rate;
|
|
|
|
if (out_rate < in_rate && scale) {
|
|
resampler->cutoff = resampler->cutoff * out_rate / in_rate;
|
|
resampler->n_taps =
|
|
gst_util_uint64_scale_int (resampler->n_taps, in_rate, out_rate);
|
|
}
|
|
|
|
if (sinc_table) {
|
|
resampler->n_taps = GST_ROUND_UP_8 (resampler->n_taps);
|
|
resampler->filter_mode = GET_OPT_FILTER_MODE (resampler->options);
|
|
resampler->filter_threshold =
|
|
GET_OPT_FILTER_MODE_THRESHOLD (resampler->options);
|
|
filter_interpolation = GET_OPT_FILTER_INTERPOLATION (resampler->options);
|
|
|
|
} else {
|
|
resampler->filter_mode = GST_AUDIO_RESAMPLER_FILTER_MODE_FULL;
|
|
filter_interpolation = GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_NONE;
|
|
}
|
|
|
|
/* calculate oversampling for interpolated filter */
|
|
if (filter_interpolation != GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_NONE) {
|
|
gint mult = 2;
|
|
|
|
oversample = GET_OPT_FILTER_OVERSAMPLE (resampler->options);
|
|
while (oversample > 1) {
|
|
if (mult * out_rate >= in_rate)
|
|
break;
|
|
|
|
mult *= 2;
|
|
oversample >>= 1;
|
|
}
|
|
|
|
switch (filter_interpolation) {
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_LINEAR:
|
|
oversample *= 11;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
oversample = 1;
|
|
}
|
|
resampler->oversample = oversample;
|
|
|
|
n_taps = resampler->n_taps;
|
|
bps = resampler->bps;
|
|
|
|
GST_LOG ("using n_taps %d cutoff %f oversample %d", n_taps, resampler->cutoff,
|
|
oversample);
|
|
|
|
if (resampler->filter_mode == GST_AUDIO_RESAMPLER_FILTER_MODE_AUTO) {
|
|
if (out_rate <= oversample
|
|
&& !(resampler->flags & GST_AUDIO_RESAMPLER_FLAG_VARIABLE_RATE)) {
|
|
/* don't interpolate if we need to calculate at least the same amount
|
|
* of filter coefficients than the full table case */
|
|
resampler->filter_mode = GST_AUDIO_RESAMPLER_FILTER_MODE_FULL;
|
|
GST_DEBUG ("automatically selected full filter, %d <= %d", out_rate,
|
|
oversample);
|
|
} else if (bps * n_taps * out_rate < resampler->filter_threshold) {
|
|
/* switch to full filter when memory is below threshold */
|
|
resampler->filter_mode = GST_AUDIO_RESAMPLER_FILTER_MODE_FULL;
|
|
GST_DEBUG ("automatically selected full filter, memory %d <= %d",
|
|
bps * n_taps * out_rate, resampler->filter_threshold);
|
|
} else {
|
|
GST_DEBUG ("automatically selected interpolated filter");
|
|
resampler->filter_mode = GST_AUDIO_RESAMPLER_FILTER_MODE_INTERPOLATED;
|
|
}
|
|
}
|
|
/* interpolated table but no interpolation given, assume default */
|
|
if (resampler->filter_mode != GST_AUDIO_RESAMPLER_FILTER_MODE_FULL &&
|
|
filter_interpolation == GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_NONE)
|
|
filter_interpolation = DEFAULT_OPT_FILTER_INTERPOLATION;
|
|
|
|
resampler->filter_interpolation = filter_interpolation;
|
|
|
|
if (resampler->filter_mode == GST_AUDIO_RESAMPLER_FILTER_MODE_FULL &&
|
|
resampler->method != GST_AUDIO_RESAMPLER_METHOD_NEAREST) {
|
|
GST_DEBUG ("setting up filter cache");
|
|
resampler->n_phases = out_rate;
|
|
alloc_cache_mem (resampler, bps, n_taps, out_rate);
|
|
}
|
|
|
|
if (resampler->filter_interpolation !=
|
|
GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_NONE) {
|
|
gint i, isize;
|
|
gdouble x;
|
|
gpointer taps;
|
|
|
|
switch (resampler->filter_interpolation) {
|
|
default:
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_LINEAR:
|
|
GST_DEBUG ("using linear interpolation to build filter");
|
|
isize = 2;
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_CUBIC:
|
|
GST_DEBUG ("using cubic interpolation to build filter");
|
|
isize = 4;
|
|
break;
|
|
}
|
|
|
|
alloc_taps_mem (resampler, bps, n_taps, oversample + isize);
|
|
|
|
for (i = 0; i < oversample + isize; i++) {
|
|
x = -(n_taps / 2) + i / (gdouble) oversample;
|
|
taps = (gint8 *) resampler->taps + i * resampler->taps_stride;
|
|
make_taps (resampler, taps, x, n_taps);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define PRINT_TAPS(type,print) \
|
|
G_STMT_START { \
|
|
type sum = 0.0, *taps; \
|
|
type icoeff[4]; \
|
|
gint samp_index = 0, samp_phase = i; \
|
|
\
|
|
taps = get_taps_##type##_full (resampler, &samp_index,\
|
|
&samp_phase, icoeff); \
|
|
\
|
|
for (j = 0; j < n_taps; j++) { \
|
|
type tap = taps[j]; \
|
|
fprintf (stderr, "\t%" print " ", tap); \
|
|
sum += tap; \
|
|
} \
|
|
fprintf (stderr, "\t: sum %" print "\n", sum); \
|
|
} G_STMT_END
|
|
|
|
static void
|
|
resampler_dump (GstAudioResampler * resampler)
|
|
{
|
|
#if 0
|
|
gint i, n_taps, out_rate;
|
|
gint64 a;
|
|
|
|
out_rate = resampler->out_rate;
|
|
n_taps = resampler->n_taps;
|
|
|
|
fprintf (stderr, "out size %d, max taps %d\n", out_rate, n_taps);
|
|
|
|
a = g_get_monotonic_time ();
|
|
|
|
for (i = 0; i < out_rate; i++) {
|
|
gint j;
|
|
|
|
//fprintf (stderr, "%u: %d %d\t ", i, t->sample_inc, t->next_phase);
|
|
switch (resampler->format) {
|
|
case GST_AUDIO_FORMAT_F64:
|
|
PRINT_TAPS (gdouble, "f");
|
|
break;
|
|
case GST_AUDIO_FORMAT_F32:
|
|
PRINT_TAPS (gfloat, "f");
|
|
break;
|
|
case GST_AUDIO_FORMAT_S32:
|
|
PRINT_TAPS (gint32, "d");
|
|
break;
|
|
case GST_AUDIO_FORMAT_S16:
|
|
PRINT_TAPS (gint16, "d");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
fprintf (stderr, "time %" G_GUINT64_FORMAT "\n", g_get_monotonic_time () - a);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_options_set_quality:
|
|
* @method: a #GstAudioResamplerMethod
|
|
* @quality: the quality
|
|
* @in_rate: the input rate
|
|
* @out_rate: the output rate
|
|
* @options: a #GstStructure
|
|
*
|
|
* Set the parameters for resampling from @in_rate to @out_rate using @method
|
|
* for @quality in @options.
|
|
*/
|
|
void
|
|
gst_audio_resampler_options_set_quality (GstAudioResamplerMethod method,
|
|
guint quality, gint in_rate, gint out_rate, GstStructure * options)
|
|
{
|
|
g_return_if_fail (options != NULL);
|
|
g_return_if_fail (quality <= GST_AUDIO_RESAMPLER_QUALITY_MAX);
|
|
g_return_if_fail (in_rate > 0 && out_rate > 0);
|
|
|
|
switch (method) {
|
|
case GST_AUDIO_RESAMPLER_METHOD_NEAREST:
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_METHOD_LINEAR:
|
|
gst_structure_set (options,
|
|
GST_AUDIO_RESAMPLER_OPT_N_TAPS, G_TYPE_INT, 2, NULL);
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_METHOD_CUBIC:
|
|
gst_structure_set (options,
|
|
GST_AUDIO_RESAMPLER_OPT_N_TAPS, G_TYPE_INT, 4,
|
|
GST_AUDIO_RESAMPLER_OPT_CUBIC_B, G_TYPE_DOUBLE, DEFAULT_OPT_CUBIC_B,
|
|
GST_AUDIO_RESAMPLER_OPT_CUBIC_C, G_TYPE_DOUBLE, DEFAULT_OPT_CUBIC_C,
|
|
NULL);
|
|
break;
|
|
case GST_AUDIO_RESAMPLER_METHOD_BLACKMAN_NUTTALL:
|
|
{
|
|
const BlackmanQualityMap *map = &blackman_qualities[quality];
|
|
gst_structure_set (options,
|
|
GST_AUDIO_RESAMPLER_OPT_N_TAPS, G_TYPE_INT, map->n_taps,
|
|
GST_AUDIO_RESAMPLER_OPT_CUTOFF, G_TYPE_DOUBLE, map->cutoff, NULL);
|
|
break;
|
|
}
|
|
case GST_AUDIO_RESAMPLER_METHOD_KAISER:
|
|
{
|
|
const KaiserQualityMap *map = &kaiser_qualities[quality];
|
|
gdouble cutoff;
|
|
|
|
cutoff = map->cutoff;
|
|
if (out_rate < in_rate)
|
|
cutoff *= map->downsample_cutoff_factor;
|
|
|
|
gst_structure_set (options,
|
|
GST_AUDIO_RESAMPLER_OPT_CUTOFF, G_TYPE_DOUBLE, cutoff,
|
|
GST_AUDIO_RESAMPLER_OPT_STOP_ATTENUATION, G_TYPE_DOUBLE,
|
|
map->stopband_attenuation,
|
|
GST_AUDIO_RESAMPLER_OPT_TRANSITION_BANDWIDTH, G_TYPE_DOUBLE,
|
|
map->transition_bandwidth, NULL);
|
|
break;
|
|
}
|
|
}
|
|
gst_structure_set (options,
|
|
GST_AUDIO_RESAMPLER_OPT_FILTER_OVERSAMPLE, G_TYPE_INT,
|
|
oversample_qualities[quality], NULL);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_new:
|
|
* @method: a #GstAudioResamplerMethod
|
|
* @flags: #GstAudioResamplerFlags
|
|
* @in_rate: input rate
|
|
* @out_rate: output rate
|
|
* @options: extra options
|
|
*
|
|
* Make a new resampler.
|
|
*
|
|
* Returns: (skip) (transfer full): %TRUE on success
|
|
*/
|
|
GstAudioResampler *
|
|
gst_audio_resampler_new (GstAudioResamplerMethod method,
|
|
GstAudioResamplerFlags flags,
|
|
GstAudioFormat format, gint channels,
|
|
gint in_rate, gint out_rate, GstStructure * options)
|
|
{
|
|
gboolean non_interleaved;
|
|
GstAudioResampler *resampler;
|
|
const GstAudioFormatInfo *info;
|
|
GstStructure *def_options = NULL;
|
|
|
|
g_return_val_if_fail (method >= GST_AUDIO_RESAMPLER_METHOD_NEAREST
|
|
&& method <= GST_AUDIO_RESAMPLER_METHOD_KAISER, NULL);
|
|
g_return_val_if_fail (format == GST_AUDIO_FORMAT_S16 ||
|
|
format == GST_AUDIO_FORMAT_S32 || format == GST_AUDIO_FORMAT_F32 ||
|
|
format == GST_AUDIO_FORMAT_F64, NULL);
|
|
g_return_val_if_fail (channels > 0, NULL);
|
|
g_return_val_if_fail (in_rate > 0, NULL);
|
|
g_return_val_if_fail (out_rate > 0, NULL);
|
|
|
|
audio_resampler_init ();
|
|
|
|
resampler = g_slice_new0 (GstAudioResampler);
|
|
resampler->method = method;
|
|
resampler->flags = flags;
|
|
resampler->format = format;
|
|
resampler->channels = channels;
|
|
|
|
switch (format) {
|
|
case GST_AUDIO_FORMAT_S16:
|
|
resampler->format_index = 0;
|
|
break;
|
|
case GST_AUDIO_FORMAT_S32:
|
|
resampler->format_index = 1;
|
|
break;
|
|
case GST_AUDIO_FORMAT_F32:
|
|
resampler->format_index = 2;
|
|
break;
|
|
case GST_AUDIO_FORMAT_F64:
|
|
resampler->format_index = 3;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
info = gst_audio_format_get_info (format);
|
|
resampler->bps = GST_AUDIO_FORMAT_INFO_WIDTH (info) / 8;
|
|
resampler->sbuf = g_malloc0 (sizeof (gpointer) * channels);
|
|
|
|
non_interleaved =
|
|
(resampler->flags & GST_AUDIO_RESAMPLER_FLAG_NON_INTERLEAVED_OUT);
|
|
|
|
/* we resample each channel separately */
|
|
resampler->blocks = resampler->channels;
|
|
resampler->inc = 1;
|
|
resampler->ostride = non_interleaved ? 1 : resampler->channels;
|
|
resampler->deinterleave = deinterleave_funcs[resampler->format_index];
|
|
resampler->convert_taps = convert_taps_funcs[resampler->format_index];
|
|
|
|
GST_DEBUG ("method %d, bps %d, channels %d", method, resampler->bps,
|
|
resampler->channels);
|
|
|
|
if (options == NULL) {
|
|
options = def_options =
|
|
gst_structure_new_empty ("GstAudioResampler.options");
|
|
gst_audio_resampler_options_set_quality (DEFAULT_RESAMPLER_METHOD,
|
|
GST_AUDIO_RESAMPLER_QUALITY_DEFAULT, in_rate, out_rate, options);
|
|
}
|
|
|
|
gst_audio_resampler_update (resampler, in_rate, out_rate, options);
|
|
gst_audio_resampler_reset (resampler);
|
|
|
|
if (def_options)
|
|
gst_structure_free (def_options);
|
|
|
|
return resampler;
|
|
}
|
|
|
|
/* make the buffers to hold the (deinterleaved) samples */
|
|
static inline gpointer *
|
|
get_sample_bufs (GstAudioResampler * resampler, gsize need)
|
|
{
|
|
if (G_LIKELY (resampler->samples_len < need)) {
|
|
gint c, blocks = resampler->blocks;
|
|
gsize bytes, to_move = 0;
|
|
gint8 *ptr, *samples;
|
|
|
|
GST_LOG ("realloc %d -> %d", (gint) resampler->samples_len, (gint) need);
|
|
|
|
bytes = GST_ROUND_UP_N (need * resampler->bps * resampler->inc, ALIGN);
|
|
|
|
samples = g_malloc0 (blocks * bytes + ALIGN - 1);
|
|
ptr = MEM_ALIGN (samples, ALIGN);
|
|
|
|
/* if we had some data, move history */
|
|
if (resampler->samples_len > 0)
|
|
to_move = resampler->samples_avail * resampler->bps * resampler->inc;
|
|
|
|
/* set up new pointers */
|
|
for (c = 0; c < blocks; c++) {
|
|
memcpy (ptr + (c * bytes), resampler->sbuf[c], to_move);
|
|
resampler->sbuf[c] = ptr + (c * bytes);
|
|
}
|
|
g_free (resampler->samples);
|
|
resampler->samples = samples;
|
|
resampler->samples_len = need;
|
|
}
|
|
return resampler->sbuf;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_reset:
|
|
* @resampler: a #GstAudioResampler
|
|
*
|
|
* Reset @resampler to the state it was when it was first created, discarding
|
|
* all sample history.
|
|
*/
|
|
void
|
|
gst_audio_resampler_reset (GstAudioResampler * resampler)
|
|
{
|
|
g_return_if_fail (resampler != NULL);
|
|
|
|
if (resampler->samples) {
|
|
gsize bytes;
|
|
gint c, blocks, bpf;
|
|
|
|
bpf = resampler->bps * resampler->inc;
|
|
bytes = (resampler->n_taps / 2) * bpf;
|
|
blocks = resampler->blocks;
|
|
|
|
for (c = 0; c < blocks; c++)
|
|
memset (resampler->sbuf[c], 0, bytes);
|
|
}
|
|
/* half of the filter is filled with 0 */
|
|
resampler->samp_index = 0;
|
|
resampler->samples_avail = resampler->n_taps / 2 - 1;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_update:
|
|
* @resampler: a #GstAudioResampler
|
|
* @in_rate: new input rate
|
|
* @out_rate: new output rate
|
|
* @options: new options or %NULL
|
|
*
|
|
* Update the resampler parameters for @resampler. This function should
|
|
* not be called concurrently with any other function on @resampler.
|
|
*
|
|
* When @in_rate or @out_rate is 0, its value is unchanged.
|
|
*
|
|
* When @options is %NULL, the previously configured options are reused.
|
|
*
|
|
* Returns: %TRUE if the new parameters could be set
|
|
*/
|
|
gboolean
|
|
gst_audio_resampler_update (GstAudioResampler * resampler,
|
|
gint in_rate, gint out_rate, GstStructure * options)
|
|
{
|
|
gint gcd, samp_phase, old_n_taps;
|
|
gdouble max_error;
|
|
|
|
g_return_val_if_fail (resampler != NULL, FALSE);
|
|
|
|
if (in_rate <= 0)
|
|
in_rate = resampler->in_rate;
|
|
if (out_rate <= 0)
|
|
out_rate = resampler->out_rate;
|
|
|
|
if (resampler->out_rate > 0) {
|
|
GST_INFO ("old phase %d/%d", resampler->samp_phase, resampler->out_rate);
|
|
samp_phase =
|
|
gst_util_uint64_scale_int (resampler->samp_phase, out_rate,
|
|
resampler->out_rate);
|
|
} else
|
|
samp_phase = 0;
|
|
|
|
gcd = gst_util_greatest_common_divisor (in_rate, out_rate);
|
|
|
|
max_error = GET_OPT_MAX_PHASE_ERROR (resampler->options);
|
|
|
|
if (max_error < 1.0e-8) {
|
|
GST_INFO ("using exact phase divider");
|
|
gcd = gst_util_greatest_common_divisor (gcd, samp_phase);
|
|
} else {
|
|
while (gcd > 1) {
|
|
gdouble ph1 = (gdouble) samp_phase / out_rate;
|
|
gint factor = 2;
|
|
|
|
/* reduce the factor until we have a phase error of less than 10% */
|
|
gdouble ph2 = (gdouble) (samp_phase / gcd) / (out_rate / gcd);
|
|
|
|
if (fabs (ph1 - ph2) < max_error)
|
|
break;
|
|
|
|
while (gcd % factor != 0)
|
|
factor++;
|
|
gcd /= factor;
|
|
|
|
GST_INFO ("divide by factor %d, gcd %d", factor, gcd);
|
|
}
|
|
}
|
|
|
|
GST_INFO ("phase %d out_rate %d, in_rate %d, gcd %d", samp_phase, out_rate,
|
|
in_rate, gcd);
|
|
|
|
resampler->samp_phase = samp_phase /= gcd;
|
|
resampler->in_rate = in_rate /= gcd;
|
|
resampler->out_rate = out_rate /= gcd;
|
|
|
|
GST_INFO ("new phase %d/%d", resampler->samp_phase, resampler->out_rate);
|
|
|
|
resampler->samp_inc = in_rate / out_rate;
|
|
resampler->samp_frac = in_rate % out_rate;
|
|
|
|
if (options) {
|
|
GST_INFO ("have new options, reconfigure filter");
|
|
|
|
if (resampler->options)
|
|
gst_structure_free (resampler->options);
|
|
resampler->options = gst_structure_copy (options);
|
|
|
|
old_n_taps = resampler->n_taps;
|
|
|
|
resampler_calculate_taps (resampler);
|
|
resampler_dump (resampler);
|
|
|
|
if (old_n_taps > 0 && old_n_taps != resampler->n_taps) {
|
|
gpointer *sbuf;
|
|
gint i, bpf, bytes, soff, doff, diff;
|
|
|
|
sbuf = get_sample_bufs (resampler, resampler->n_taps);
|
|
|
|
bpf = resampler->bps * resampler->inc;
|
|
bytes = resampler->samples_avail * bpf;
|
|
soff = doff = resampler->samp_index * bpf;
|
|
|
|
diff = ((gint) resampler->n_taps - old_n_taps) / 2;
|
|
|
|
GST_DEBUG ("taps %d->%d, %d", old_n_taps, resampler->n_taps, diff);
|
|
|
|
if (diff < 0) {
|
|
/* diff < 0, decrease taps, adjust source */
|
|
soff += -diff * bpf;
|
|
bytes -= -diff * bpf;
|
|
} else {
|
|
/* diff > 0, increase taps, adjust dest */
|
|
doff += diff * bpf;
|
|
}
|
|
|
|
/* now shrink or enlarge the history buffer, when we enlarge we
|
|
* just leave the old samples in there. FIXME, probably do something better
|
|
* like mirror or fill with zeroes. */
|
|
for (i = 0; i < resampler->blocks; i++)
|
|
memmove ((gint8 *) sbuf[i] + doff, (gint8 *) sbuf[i] + soff, bytes);
|
|
|
|
resampler->samples_avail += diff;
|
|
}
|
|
} else if (resampler->filter_mode == GST_AUDIO_RESAMPLER_FILTER_MODE_FULL) {
|
|
GST_DEBUG ("setting up filter cache");
|
|
resampler->n_phases = resampler->out_rate;
|
|
alloc_cache_mem (resampler, resampler->bps, resampler->n_taps,
|
|
resampler->n_phases);
|
|
}
|
|
setup_functions (resampler);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_free:
|
|
* @resampler: a #GstAudioResampler
|
|
*
|
|
* Free a previously allocated #GstAudioResampler @resampler.
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
void
|
|
gst_audio_resampler_free (GstAudioResampler * resampler)
|
|
{
|
|
g_return_if_fail (resampler != NULL);
|
|
|
|
g_free (resampler->cached_taps_mem);
|
|
g_free (resampler->taps_mem);
|
|
g_free (resampler->tmp_taps);
|
|
g_free (resampler->samples);
|
|
g_free (resampler->sbuf);
|
|
if (resampler->options)
|
|
gst_structure_free (resampler->options);
|
|
g_slice_free (GstAudioResampler, resampler);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_get_out_frames:
|
|
* @resampler: a #GstAudioResampler
|
|
* @in_frames: number of input frames
|
|
*
|
|
* Get the number of output frames that would be currently available when
|
|
* @in_frames are given to @resampler.
|
|
*
|
|
* Returns: The number of frames that would be availabe after giving
|
|
* @in_frames as input to @resampler.
|
|
*/
|
|
gsize
|
|
gst_audio_resampler_get_out_frames (GstAudioResampler * resampler,
|
|
gsize in_frames)
|
|
{
|
|
gsize need, avail, out;
|
|
|
|
g_return_val_if_fail (resampler != NULL, 0);
|
|
|
|
need = resampler->n_taps + resampler->samp_index + resampler->skip;
|
|
avail = resampler->samples_avail + in_frames;
|
|
GST_LOG ("need %d = %d + %d + %d, avail %d = %d + %d", (gint) need,
|
|
resampler->n_taps, resampler->samp_index, resampler->skip,
|
|
(gint) avail, (gint) resampler->samples_avail, (gint) in_frames);
|
|
if (avail < need)
|
|
return 0;
|
|
|
|
out = (avail - need) * resampler->out_rate;
|
|
if (out < resampler->samp_phase)
|
|
return 0;
|
|
|
|
out = ((out - resampler->samp_phase) / resampler->in_rate) + 1;
|
|
GST_LOG ("out %d = ((%d * %d - %d) / %d) + 1", (gint) out,
|
|
(gint) (avail - need), resampler->out_rate, resampler->samp_phase,
|
|
resampler->in_rate);
|
|
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_get_in_frames:
|
|
* @resampler: a #GstAudioResampler
|
|
* @out_frames: number of input frames
|
|
*
|
|
* Get the number of input frames that would currently be needed
|
|
* to produce @out_frames from @resampler.
|
|
*
|
|
* Returns: The number of input frames needed for producing
|
|
* @out_frames of data from @resampler.
|
|
*/
|
|
gsize
|
|
gst_audio_resampler_get_in_frames (GstAudioResampler * resampler,
|
|
gsize out_frames)
|
|
{
|
|
gsize in_frames;
|
|
|
|
g_return_val_if_fail (resampler != NULL, 0);
|
|
|
|
in_frames =
|
|
(resampler->samp_phase +
|
|
out_frames * resampler->samp_frac) / resampler->out_rate;
|
|
in_frames += out_frames * resampler->samp_inc;
|
|
|
|
return in_frames;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_get_max_latency:
|
|
* @resampler: a #GstAudioResampler
|
|
*
|
|
* Get the maximum number of input samples that the resampler would
|
|
* need before producing output.
|
|
*
|
|
* Returns: the latency of @resampler as expressed in the number of
|
|
* frames.
|
|
*/
|
|
gsize
|
|
gst_audio_resampler_get_max_latency (GstAudioResampler * resampler)
|
|
{
|
|
g_return_val_if_fail (resampler != NULL, 0);
|
|
|
|
return resampler->n_taps / 2;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_resample:
|
|
* @resampler: a #GstAudioResampler
|
|
* @in: input samples
|
|
* @in_frames: number of input frames
|
|
* @out: output samples
|
|
* @out_frames: number of output frames
|
|
*
|
|
* Perform resampling on @in_frames frames in @in and write @out_frames to @out.
|
|
*
|
|
* In case the samples are interleaved, @in and @out must point to an
|
|
* array with a single element pointing to a block of interleaved samples.
|
|
*
|
|
* If non-interleaved samples are used, @in and @out must point to an
|
|
* array with pointers to memory blocks, one for each channel.
|
|
*
|
|
* @in may be %NULL, in which case @in_frames of silence samples are pushed
|
|
* into the resampler.
|
|
*
|
|
* This function always produces @out_frames of output and consumes @in_frames of
|
|
* input. Use gst_audio_resampler_get_out_frames() and
|
|
* gst_audio_resampler_get_in_frames() to make sure @in_frames and @out_frames
|
|
* are matching and @in and @out point to enough memory.
|
|
*/
|
|
void
|
|
gst_audio_resampler_resample (GstAudioResampler * resampler,
|
|
gpointer in[], gsize in_frames, gpointer out[], gsize out_frames)
|
|
{
|
|
gsize samples_avail;
|
|
gsize need, consumed;
|
|
gpointer *sbuf;
|
|
|
|
/* do sample skipping */
|
|
if (G_UNLIKELY (resampler->skip >= in_frames)) {
|
|
/* we need tp skip all input */
|
|
resampler->skip -= in_frames;
|
|
return;
|
|
}
|
|
/* skip the last samples by advancing the sample index */
|
|
resampler->samp_index += resampler->skip;
|
|
|
|
samples_avail = resampler->samples_avail;
|
|
|
|
/* make sure we have enough space to copy our samples */
|
|
sbuf = get_sample_bufs (resampler, in_frames + samples_avail);
|
|
|
|
/* copy/deinterleave the samples */
|
|
resampler->deinterleave (resampler, sbuf, in, in_frames);
|
|
|
|
/* update new amount of samples in our buffer */
|
|
resampler->samples_avail = samples_avail += in_frames;
|
|
|
|
need = resampler->n_taps + resampler->samp_index;
|
|
if (G_UNLIKELY (samples_avail < need)) {
|
|
/* not enough samples to start */
|
|
return;
|
|
}
|
|
|
|
/* resample all channels */
|
|
resampler->resample (resampler, sbuf, samples_avail, out, out_frames,
|
|
&consumed);
|
|
|
|
GST_LOG ("in %" G_GSIZE_FORMAT ", avail %" G_GSIZE_FORMAT ", consumed %"
|
|
G_GSIZE_FORMAT, in_frames, samples_avail, consumed);
|
|
|
|
/* update pointers */
|
|
if (G_LIKELY (consumed > 0)) {
|
|
gssize left = samples_avail - consumed;
|
|
if (left > 0) {
|
|
/* we consumed part of our samples */
|
|
resampler->samples_avail = left;
|
|
} else {
|
|
/* we consumed all our samples, empty our buffers */
|
|
resampler->samples_avail = 0;
|
|
resampler->skip = -left;
|
|
}
|
|
}
|
|
}
|