gstreamer/gst-libs/gst/audio/audio-resampler.c
Wim Taymans de37491662 audio-converter: simplify API
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.
2016-03-28 13:25:50 +02:00

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;
}
}
}