mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-08 10:31:05 +00:00
75d668e152
Add a resampler to the processing chain when needed. port the audio resampler to the new audioconverter library
1121 lines
38 KiB
C
1121 lines
38 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>
|
|
|
|
#include "audio-resampler.h"
|
|
|
|
typedef struct _Tap
|
|
{
|
|
gpointer taps;
|
|
|
|
gint sample_inc;
|
|
gint next_phase;
|
|
gint size;
|
|
} Tap;
|
|
|
|
typedef void (*MakeTapsFunc) (GstAudioResampler * resampler, Tap * t, gint j);
|
|
typedef void (*ResampleFunc) (GstAudioResampler * resampler, gpointer in[],
|
|
gsize in_len, gpointer out[], gsize out_len, gsize * consumed,
|
|
gsize * produced, gboolean move);
|
|
typedef void (*DeinterleaveFunc) (GstAudioResampler * resampler,
|
|
gpointer * sbuf, gpointer in[], gsize in_frames);
|
|
typedef void (*MirrorFunc) (GstAudioResampler * resampler, gpointer * sbuf);
|
|
|
|
struct _GstAudioResampler
|
|
{
|
|
GstAudioResamplerMethod method;
|
|
GstAudioResamplerFlags flags;
|
|
GstAudioFormat format;
|
|
GstStructure *options;
|
|
guint channels;
|
|
gint in_rate;
|
|
gint out_rate;
|
|
gint bps, bpf;
|
|
gint ostride;
|
|
|
|
gdouble cutoff;
|
|
gdouble kaiser_beta;
|
|
/* for cubic */
|
|
gdouble b, c;
|
|
|
|
guint n_taps;
|
|
Tap *taps;
|
|
gpointer coeff;
|
|
gpointer tmpcoeff;
|
|
|
|
DeinterleaveFunc deinterleave;
|
|
MirrorFunc mirror;
|
|
ResampleFunc resample;
|
|
|
|
gboolean filling;
|
|
gint samp_inc;
|
|
gint samp_frac;
|
|
gint samp_index;
|
|
gint samp_phase;
|
|
gint skip;
|
|
|
|
gpointer samples;
|
|
gsize samples_len;
|
|
gsize samples_avail;
|
|
gpointer *sbuf;
|
|
};
|
|
|
|
#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 ("audio-resampler", 0,
|
|
"audio-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:gstaudioresampler
|
|
* @short_description: Utility structure for resampler information
|
|
*
|
|
* #GstAudioResampler is a structure which holds the information
|
|
* required to perform various kinds of resampling filtering.
|
|
*
|
|
*/
|
|
|
|
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
|
|
{
|
|
guint 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_QUALITY GST_AUDIO_RESAMPLER_QUALITY_DEFAULT
|
|
#define DEFAULT_OPT_CUBIC_B 1.0
|
|
#define DEFAULT_OPT_CUBIC_C 0.0
|
|
|
|
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_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)
|
|
|
|
#include "dbesi0.c"
|
|
#define bessel dbesi0
|
|
|
|
static inline gdouble
|
|
get_nearest_tap (GstAudioResampler * resampler, gdouble x)
|
|
{
|
|
gdouble a = fabs (x);
|
|
|
|
if (a < 0.5)
|
|
return 1.0;
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
static inline gdouble
|
|
get_linear_tap (GstAudioResampler * resampler, gdouble x)
|
|
{
|
|
gdouble a;
|
|
|
|
a = fabs (x) / resampler->n_taps;
|
|
|
|
if (a < 1.0)
|
|
return 1.0 - a;
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
static inline gdouble
|
|
get_cubic_tap (GstAudioResampler * resampler, gdouble x)
|
|
{
|
|
gdouble a, a2, a3, b, c;
|
|
|
|
a = fabs (x * 4.0) / resampler->n_taps;
|
|
a2 = a * a;
|
|
a3 = a2 * a;
|
|
|
|
b = resampler->b;
|
|
c = resampler->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 inline gdouble
|
|
get_blackman_nuttall_tap (GstAudioResampler * resampler, gdouble x)
|
|
{
|
|
gdouble s, y, w, Fc = resampler->cutoff;
|
|
|
|
y = G_PI * x;
|
|
s = (y == 0.0 ? Fc : sin (y * Fc) / y);
|
|
|
|
w = 2.0 * y / resampler->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 (GstAudioResampler * resampler, gdouble x)
|
|
{
|
|
gdouble s, y, w, Fc = resampler->cutoff;
|
|
|
|
y = G_PI * x;
|
|
s = (y == 0.0 ? Fc : sin (y * Fc) / y);
|
|
|
|
w = 2.0 * x / resampler->n_taps;
|
|
return s * bessel (resampler->kaiser_beta * sqrt (MAX (1 - w * w, 0)));
|
|
}
|
|
|
|
#define CONVERT_TAPS(type, precision) \
|
|
G_STMT_START { \
|
|
type *taps = t->taps = (type *) resampler->coeff + j * n_taps; \
|
|
gdouble multiplier = (1 << precision); \
|
|
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 += taps[j] = floor (offset + tmpcoeff[j] * multiplier / weight); \
|
|
if (sum == (1 << precision)) { \
|
|
exact = TRUE; \
|
|
break; \
|
|
} \
|
|
if (l_offset == h_offset) \
|
|
break; \
|
|
if (sum < (1 << precision)) { \
|
|
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; \
|
|
} \
|
|
} \
|
|
if (!exact) \
|
|
GST_WARNING ("can't find exact taps"); \
|
|
} G_STMT_END
|
|
|
|
#include "audio-resampler-core.h"
|
|
|
|
static void
|
|
make_taps (GstAudioResampler * resampler, Tap * t, gint j)
|
|
{
|
|
gint n_taps = resampler->n_taps;
|
|
gdouble x, weight = 0.0;
|
|
gdouble *tmpcoeff = resampler->tmpcoeff;
|
|
gint tap_offs = n_taps / 2;
|
|
gint out_rate = resampler->out_rate;
|
|
gint l;
|
|
|
|
x = ((double) (1.0 - tap_offs) - (double) j / out_rate);
|
|
|
|
switch (resampler->method) {
|
|
case GST_AUDIO_RESAMPLER_METHOD_NEAREST:
|
|
for (l = 0; l < n_taps; l++, x += 1.0)
|
|
weight += tmpcoeff[l] = get_nearest_tap (resampler, x);
|
|
break;
|
|
|
|
case GST_AUDIO_RESAMPLER_METHOD_LINEAR:
|
|
for (l = 0; l < n_taps; l++, x += 1.0)
|
|
weight += tmpcoeff[l] = get_linear_tap (resampler, x);
|
|
break;
|
|
|
|
case GST_AUDIO_RESAMPLER_METHOD_CUBIC:
|
|
for (l = 0; l < n_taps; l++, x += 1.0)
|
|
weight += tmpcoeff[l] = get_cubic_tap (resampler, x);
|
|
break;
|
|
|
|
case GST_AUDIO_RESAMPLER_METHOD_BLACKMAN_NUTTALL:
|
|
for (l = 0; l < n_taps; l++, x += 1.0)
|
|
weight += tmpcoeff[l] = get_blackman_nuttall_tap (resampler, x);
|
|
break;
|
|
|
|
case GST_AUDIO_RESAMPLER_METHOD_KAISER:
|
|
for (l = 0; l < n_taps; l++, x += 1.0)
|
|
weight += tmpcoeff[l] = get_kaiser_tap (resampler, x);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (resampler->format) {
|
|
case GST_AUDIO_FORMAT_F64:
|
|
{
|
|
gdouble *taps = t->taps = (gdouble *) resampler->coeff + j * n_taps;
|
|
for (l = 0; l < n_taps; l++)
|
|
taps[l] = tmpcoeff[l] / weight;
|
|
break;
|
|
}
|
|
case GST_AUDIO_FORMAT_F32:
|
|
{
|
|
gfloat *taps = t->taps = (gfloat *) resampler->coeff + j * n_taps;
|
|
for (l = 0; l < n_taps; l++)
|
|
taps[l] = tmpcoeff[l] / weight;
|
|
break;
|
|
}
|
|
case GST_AUDIO_FORMAT_S32:
|
|
CONVERT_TAPS (gint32, PRECISION_S32);
|
|
break;
|
|
case GST_AUDIO_FORMAT_S16:
|
|
CONVERT_TAPS (gint16, PRECISION_S16);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define MAKE_RESAMPLE_FUNC(type) \
|
|
static void \
|
|
resample_ ##type (GstAudioResampler * resampler, gpointer in[], gsize in_len, \
|
|
gpointer out[], gsize out_len, gsize * consumed, gsize * produced, \
|
|
gboolean move) \
|
|
{ \
|
|
gint c, di = 0; \
|
|
gint n_taps = resampler->n_taps; \
|
|
gint channels = resampler->channels; \
|
|
gint ostride = resampler->ostride; \
|
|
gint samp_index = 0; \
|
|
gint samp_phase = 0; \
|
|
\
|
|
for (c = 0; c < channels; c++) { \
|
|
type *ip = in[c]; \
|
|
type *op = ostride == 1 ? out[c] : (type *)out[0] + c; \
|
|
\
|
|
samp_index = resampler->samp_index; \
|
|
samp_phase = resampler->samp_phase; \
|
|
\
|
|
for (di = 0; di < out_len; di++) { \
|
|
Tap *t = &resampler->taps[samp_phase]; \
|
|
type *ipp = &ip[samp_index]; \
|
|
\
|
|
if (t->taps == NULL) \
|
|
make_taps (resampler, t, samp_phase); \
|
|
\
|
|
inner_product_ ##type (op, ipp, t->taps, n_taps); \
|
|
op += ostride; \
|
|
\
|
|
samp_phase = t->next_phase; \
|
|
samp_index += t->sample_inc; \
|
|
} \
|
|
if (move) \
|
|
memmove (ip, &ip[samp_index], (in_len - samp_index) * sizeof(type)); \
|
|
} \
|
|
*consumed = samp_index - resampler->samp_index; \
|
|
*produced = di; \
|
|
\
|
|
resampler->samp_index = move ? 0 : samp_index; \
|
|
resampler->samp_phase = samp_phase; \
|
|
}
|
|
|
|
MAKE_RESAMPLE_FUNC (gdouble);
|
|
MAKE_RESAMPLE_FUNC (gfloat);
|
|
MAKE_RESAMPLE_FUNC (gint32);
|
|
MAKE_RESAMPLE_FUNC (gint16);
|
|
|
|
#define MAKE_RESAMPLE_INTERLEAVED_FUNC(type,channels) \
|
|
static void \
|
|
resample_interleaved_ ##type##_##channels (GstAudioResampler * resampler, gpointer in[],\
|
|
gsize in_len, gpointer out[], gsize out_len, gsize * consumed, gsize * produced, \
|
|
gboolean move) \
|
|
{ \
|
|
gint di = 0; \
|
|
gint n_taps = resampler->n_taps; \
|
|
gint ostride = resampler->ostride; \
|
|
gint samp_index = 0; \
|
|
gint samp_phase = 0; \
|
|
\
|
|
{ \
|
|
type *ip = in[0]; \
|
|
type *op = out[0]; \
|
|
\
|
|
samp_index = resampler->samp_index; \
|
|
samp_phase = resampler->samp_phase; \
|
|
\
|
|
for (di = 0; di < out_len; di++) { \
|
|
Tap *t = &resampler->taps[samp_phase]; \
|
|
type *ipp = &ip[samp_index * channels]; \
|
|
\
|
|
if (t->taps == NULL) \
|
|
make_taps (resampler, t, samp_phase); \
|
|
\
|
|
inner_product_ ##type## _##channels (op, ipp, t->taps, n_taps); \
|
|
\
|
|
op += ostride; \
|
|
samp_phase = t->next_phase; \
|
|
samp_index += t->sample_inc; \
|
|
} \
|
|
if (move) \
|
|
memmove (ip, &ip[samp_index * channels], \
|
|
(in_len - samp_index) * sizeof(type) * channels); \
|
|
} \
|
|
*consumed = samp_index - resampler->samp_index; \
|
|
*produced = di; \
|
|
\
|
|
resampler->samp_index = move ? 0 : samp_index; \
|
|
resampler->samp_phase = samp_phase; \
|
|
}
|
|
|
|
MAKE_RESAMPLE_INTERLEAVED_FUNC (gdouble, 2);
|
|
MAKE_RESAMPLE_INTERLEAVED_FUNC (gint16, 2);
|
|
|
|
|
|
#define MAKE_DEINTERLEAVE_FUNC(type) \
|
|
static void \
|
|
deinterleave_ ##type (GstAudioResampler * resampler, gpointer sbuf[], \
|
|
gpointer in[], gsize in_frames) \
|
|
{ \
|
|
guint 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 (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 (gdouble);
|
|
MAKE_DEINTERLEAVE_FUNC (gfloat);
|
|
MAKE_DEINTERLEAVE_FUNC (gint32);
|
|
MAKE_DEINTERLEAVE_FUNC (gint16);
|
|
|
|
static void
|
|
deinterleave_copy (GstAudioResampler * resampler, gpointer sbuf[],
|
|
gpointer in[], gsize in_frames)
|
|
{
|
|
gsize samples_avail = resampler->samples_avail;
|
|
gint bpf = resampler->bpf;
|
|
|
|
if (in == NULL)
|
|
memset ((guint8 *) sbuf[0] + samples_avail * bpf, 0, in_frames * bpf);
|
|
else
|
|
memcpy ((guint8 *) sbuf[0] + samples_avail * bpf, in[0], in_frames * bpf);
|
|
}
|
|
|
|
static void
|
|
deinterleave_copy_n (GstAudioResampler * resampler, gpointer sbuf[],
|
|
gpointer in[], gsize in_frames)
|
|
{
|
|
guint c, channels = resampler->channels;
|
|
gsize samples_avail = resampler->samples_avail;
|
|
gint bps = resampler->bps;
|
|
|
|
for (c = 0; c < channels; c++) {
|
|
if (in == NULL)
|
|
memset ((guint8 *) sbuf[c] + samples_avail * bps, 0, in_frames * bps);
|
|
else
|
|
memcpy ((guint8 *) sbuf[c] + samples_avail * bps, in[c], in_frames * bps);
|
|
}
|
|
}
|
|
|
|
/* mirror input samples into the history when we have nothing else */
|
|
#define MAKE_MIRROR_FUNC(type) \
|
|
static void \
|
|
mirror_ ##type (GstAudioResampler * resampler, gpointer sbuf[]) \
|
|
{ \
|
|
guint i, c, channels = resampler->channels; \
|
|
gint si = resampler->n_taps / 2; \
|
|
gint n_taps = resampler->n_taps; \
|
|
for (c = 0; c < channels; c++) { \
|
|
type *s = sbuf[c]; \
|
|
for (i = 0; i < si; i++) \
|
|
s[i] = -s[n_taps - i]; \
|
|
} \
|
|
}
|
|
|
|
MAKE_MIRROR_FUNC (gdouble);
|
|
MAKE_MIRROR_FUNC (gfloat);
|
|
MAKE_MIRROR_FUNC (gint32);
|
|
MAKE_MIRROR_FUNC (gint16);
|
|
|
|
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
|
|
resampler_calculate_taps (GstAudioResampler * resampler)
|
|
{
|
|
gint bps;
|
|
gint j;
|
|
gint n_taps;
|
|
gint out_rate;
|
|
gint in_rate;
|
|
|
|
switch (resampler->method) {
|
|
case GST_AUDIO_RESAMPLER_METHOD_NEAREST:
|
|
resampler->n_taps = 2;
|
|
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);
|
|
break;
|
|
}
|
|
case GST_AUDIO_RESAMPLER_METHOD_KAISER:
|
|
calculate_kaiser_params (resampler);
|
|
break;
|
|
}
|
|
|
|
in_rate = resampler->in_rate;
|
|
out_rate = resampler->out_rate;
|
|
|
|
if (out_rate < in_rate) {
|
|
resampler->cutoff = resampler->cutoff * out_rate / in_rate;
|
|
resampler->n_taps = resampler->n_taps * in_rate / out_rate;
|
|
}
|
|
/* only round up for bigger taps, the small taps are used for nearest,
|
|
* linear and cubic and we want to use less taps for those. */
|
|
if (resampler->n_taps > 4)
|
|
resampler->n_taps = GST_ROUND_UP_8 (resampler->n_taps);
|
|
|
|
n_taps = resampler->n_taps;
|
|
bps = resampler->bps;
|
|
|
|
GST_LOG ("using n_taps %d cutoff %f", n_taps, resampler->cutoff);
|
|
|
|
resampler->taps = g_realloc_n (resampler->taps, out_rate, sizeof (Tap));
|
|
resampler->coeff = g_realloc_n (resampler->coeff, out_rate, bps * n_taps);
|
|
resampler->tmpcoeff =
|
|
g_realloc_n (resampler->tmpcoeff, n_taps, sizeof (gdouble));
|
|
|
|
resampler->samp_inc = in_rate / out_rate;
|
|
resampler->samp_frac = in_rate % out_rate;
|
|
|
|
for (j = 0; j < out_rate; j++) {
|
|
Tap *t = &resampler->taps[j];
|
|
t->taps = NULL;
|
|
t->sample_inc = (j + in_rate) / out_rate;
|
|
t->next_phase = (j + in_rate) % out_rate;
|
|
}
|
|
|
|
switch (resampler->format) {
|
|
case GST_AUDIO_FORMAT_F64:
|
|
if (resampler->channels == 2 && n_taps >= 4) {
|
|
resampler->resample = resample_interleaved_gdouble_2;
|
|
resampler->deinterleave = deinterleave_copy;
|
|
} else {
|
|
resampler->resample = resample_gdouble;
|
|
resampler->deinterleave = deinterleave_gdouble;
|
|
}
|
|
resampler->mirror = mirror_gdouble;
|
|
break;
|
|
case GST_AUDIO_FORMAT_F32:
|
|
resampler->resample = resample_gfloat;
|
|
resampler->deinterleave = deinterleave_gfloat;
|
|
resampler->mirror = mirror_gfloat;
|
|
break;
|
|
case GST_AUDIO_FORMAT_S32:
|
|
resampler->resample = resample_gint32;
|
|
resampler->deinterleave = deinterleave_gint32;
|
|
resampler->mirror = mirror_gint32;
|
|
break;
|
|
case GST_AUDIO_FORMAT_S16:
|
|
if (resampler->channels == 2 && n_taps >= 4) {
|
|
resampler->resample = resample_interleaved_gint16_2;
|
|
resampler->deinterleave = deinterleave_copy;
|
|
} else {
|
|
resampler->resample = resample_gint16;
|
|
resampler->deinterleave = deinterleave_gint16;
|
|
}
|
|
resampler->mirror = mirror_gint16;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (resampler->flags & GST_AUDIO_RESAMPLER_FLAG_NON_INTERLEAVED) {
|
|
resampler->deinterleave = deinterleave_copy_n;
|
|
resampler->ostride = 1;
|
|
} else {
|
|
resampler->ostride = resampler->channels;
|
|
}
|
|
}
|
|
|
|
#define PRINT_TAPS(type,print) \
|
|
G_STMT_START { \
|
|
type sum = 0.0, *taps; \
|
|
\
|
|
if (t->taps == NULL) \
|
|
make_taps (resampler, t, i); \
|
|
\
|
|
taps = t->taps; \
|
|
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;
|
|
Tap *t = &resampler->taps[i];
|
|
|
|
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, guint in_rate, guint out_rate, GstStructure * options)
|
|
{
|
|
g_return_if_fail (options != NULL);
|
|
g_return_if_fail (quality < 11);
|
|
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_audio_resampler_new:
|
|
* @resampler: a #GstAudioResampler
|
|
* @method: a #GstAudioResamplerMethod
|
|
* @flags: #GstAudioResamplerFlags
|
|
* @in_rate: input rate
|
|
* @out_rate: output rate
|
|
* @options: extra options
|
|
*
|
|
* Make a new resampler.
|
|
*
|
|
* Returns: %TRUE on success
|
|
*/
|
|
GstAudioResampler *
|
|
gst_audio_resampler_new (GstAudioResamplerMethod method,
|
|
GstAudioResamplerFlags flags,
|
|
GstAudioFormat format, guint channels,
|
|
guint in_rate, guint out_rate, GstStructure * options)
|
|
{
|
|
GstAudioResampler *resampler;
|
|
const GstAudioFormatInfo *info;
|
|
|
|
g_return_val_if_fail (in_rate != 0, FALSE);
|
|
g_return_val_if_fail (out_rate != 0, FALSE);
|
|
|
|
resampler = g_slice_new0 (GstAudioResampler);
|
|
resampler->method = method;
|
|
resampler->flags = flags;
|
|
resampler->format = format;
|
|
resampler->channels = channels;
|
|
|
|
info = gst_audio_format_get_info (format);
|
|
resampler->bps = GST_AUDIO_FORMAT_INFO_WIDTH (info) / 8;
|
|
resampler->bpf = resampler->bps * channels;
|
|
resampler->sbuf = g_malloc0 (sizeof (gpointer) * channels);
|
|
|
|
GST_DEBUG ("method %d, bps %d, bpf %d", method, resampler->bps,
|
|
resampler->bpf);
|
|
|
|
gst_audio_resampler_update (resampler, in_rate, out_rate, options);
|
|
|
|
return resampler;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Returns: %TRUE if the new parameters could be set
|
|
*/
|
|
gboolean
|
|
gst_audio_resampler_update (GstAudioResampler * resampler,
|
|
guint in_rate, guint out_rate, GstStructure * options)
|
|
{
|
|
gint gcd;
|
|
|
|
g_return_val_if_fail (resampler != NULL, FALSE);
|
|
g_return_val_if_fail (in_rate != 0, FALSE);
|
|
g_return_val_if_fail (out_rate != 0, FALSE);
|
|
|
|
gcd = gst_util_greatest_common_divisor (in_rate, out_rate);
|
|
in_rate /= gcd;
|
|
out_rate /= gcd;
|
|
|
|
resampler->in_rate = in_rate;
|
|
resampler->out_rate = out_rate;
|
|
if (options) {
|
|
if (resampler->options)
|
|
gst_structure_free (resampler->options);
|
|
resampler->options = gst_structure_copy (options);
|
|
}
|
|
|
|
GST_DEBUG ("%u->%u", in_rate, out_rate);
|
|
|
|
resampler_calculate_taps (resampler);
|
|
resampler_dump (resampler);
|
|
|
|
resampler->filling = TRUE;
|
|
resampler->samp_index = 0;
|
|
resampler->samp_phase = 0;
|
|
resampler->samples_avail = resampler->n_taps / 2 - 1;
|
|
|
|
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->taps);
|
|
g_free (resampler->coeff);
|
|
g_free (resampler->tmpcoeff);
|
|
g_free (resampler->samples);
|
|
g_free (resampler->sbuf);
|
|
if (resampler->options)
|
|
gst_structure_free (resampler->options);
|
|
g_slice_free (GstAudioResampler, resampler);
|
|
}
|
|
|
|
static inline gsize
|
|
calc_out (GstAudioResampler * resampler, gsize in)
|
|
{
|
|
return ((in * resampler->out_rate -
|
|
resampler->samp_phase) / resampler->in_rate) + 1;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
g_return_val_if_fail (resampler != NULL, 0);
|
|
|
|
need = resampler->n_taps + resampler->samp_index + resampler->skip;
|
|
avail = resampler->samples_avail + in_frames;
|
|
if (avail < need)
|
|
return 0;
|
|
|
|
return calc_out (resampler, avail - need);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/* make the buffers to hold the (deinterleaved) samples */
|
|
static inline gpointer *
|
|
get_sample_bufs (GstAudioResampler * resampler, gsize need)
|
|
{
|
|
if (resampler->samples_len < need) {
|
|
guint c, channels = resampler->channels;
|
|
GST_LOG ("realloc %d -> %d", (gint) resampler->samples_len, (gint) need);
|
|
/* FIXME, move history */
|
|
resampler->samples = g_realloc (resampler->samples, need * resampler->bpf);
|
|
resampler->samples_len = need;
|
|
/* set up new pointers */
|
|
for (c = 0; c < channels; c++)
|
|
resampler->sbuf[c] =
|
|
(gint8 *) resampler->samples +
|
|
(c * resampler->samples_len * resampler->bps);
|
|
}
|
|
return resampler->sbuf;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_resampler_resample:
|
|
* @resampler: a #GstAudioResampler
|
|
* @in: input samples
|
|
* @in_frames: number of input frames
|
|
* @out: output samples
|
|
* @out_frames: maximum output frames
|
|
* @consumed: number of frames consumed
|
|
* @produced: number of frames produced
|
|
*
|
|
* Perform resampling on @in_frames frames in @in and write at most
|
|
* @out_frames of 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 0 samples are pushed
|
|
* into the resampler.
|
|
*
|
|
* The number of frames consumed is returned in @consumed and can be
|
|
* less than @in_frames due to latency of the resampler or because
|
|
* the number of samples produced equals @out_frames.
|
|
*
|
|
* The number of frames produced is returned in @produced.
|
|
*/
|
|
void
|
|
gst_audio_resampler_resample (GstAudioResampler * resampler,
|
|
gpointer in[], gsize in_frames, gpointer out[], gsize out_frames,
|
|
gsize * consumed, gsize * produced)
|
|
{
|
|
gsize samples_avail;
|
|
gsize out2, need;
|
|
gpointer *sbuf;
|
|
|
|
/* do sample skipping */
|
|
if (resampler->skip >= in_frames) {
|
|
/* we need tp skip all input */
|
|
resampler->skip -= in_frames;
|
|
*consumed = in_frames;
|
|
*produced = 0;
|
|
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 (samples_avail < need) {
|
|
/* not enough samples to start */
|
|
*consumed = in_frames;
|
|
*produced = 0;
|
|
return;
|
|
}
|
|
|
|
if (resampler->filling) {
|
|
/* if we are filling up our history duplicate the samples to the left */
|
|
resampler->mirror (resampler, sbuf);
|
|
resampler->filling = FALSE;
|
|
}
|
|
|
|
/* calculate maximum number of available output samples */
|
|
out2 = calc_out (resampler, samples_avail - need);
|
|
out_frames = MIN (out2, out_frames);
|
|
|
|
/* resample all channels */
|
|
resampler->resample (resampler, sbuf, samples_avail, out, out_frames,
|
|
consumed, produced, TRUE);
|
|
|
|
GST_LOG ("in %" G_GSIZE_FORMAT ", used %" G_GSIZE_FORMAT ", consumed %"
|
|
G_GSIZE_FORMAT ", produced %" G_GSIZE_FORMAT, in_frames, samples_avail,
|
|
*consumed, *produced);
|
|
|
|
/* update pointers */
|
|
if (*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;
|
|
}
|
|
/* we always consume everything */
|
|
*consumed = in_frames;
|
|
}
|
|
}
|