mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-08 10:31:05 +00:00
de37491662
Remove the consumed/produced output fields from the resampler and converter. Let the caler specify the right number of input/output samples so we can be more optimal. Use just one function to update the converter configuration. Simplify some things internally. Make it possible to use writable input as temp space in audioconvert.
1115 lines
38 KiB
C
1115 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,
|
|
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, 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; \
|
|
\
|
|
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, 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; \
|
|
\
|
|
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.
|
|
*
|
|
* When @in_rate or @out_rate is 0, its value is unchanged.
|
|
*
|
|
* 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);
|
|
|
|
if (in_rate == 0)
|
|
in_rate = resampler->in_rate;
|
|
if (out_rate == 0)
|
|
out_rate = resampler->out_rate;
|
|
|
|
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)
|
|
{
|
|
gsize out;
|
|
|
|
out = ((in * resampler->out_rate -
|
|
resampler->samp_phase) / resampler->in_rate) + 1;
|
|
GST_LOG ("out %d = ((%d * %d - %d) / %d) + 1", (gint) out,
|
|
(gint) in, resampler->out_rate, resampler->samp_phase,
|
|
resampler->in_rate);
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
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;
|
|
|
|
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: 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 (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 (samples_avail < need) {
|
|
/* not enough samples to start */
|
|
return;
|
|
}
|
|
|
|
if (resampler->filling) {
|
|
/* if we are filling up our history duplicate the samples to the left */
|
|
resampler->mirror (resampler, sbuf);
|
|
resampler->filling = FALSE;
|
|
}
|
|
|
|
/* resample all channels */
|
|
resampler->resample (resampler, sbuf, samples_avail, out, out_frames,
|
|
&consumed, TRUE);
|
|
|
|
GST_LOG ("in %" G_GSIZE_FORMAT ", used %" G_GSIZE_FORMAT ", consumed %"
|
|
G_GSIZE_FORMAT, in_frames, samples_avail, consumed);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
}
|