gstreamer/gst-libs/gst/audio/audio-quantize.c
Wim Taymans c36ac3ce45 audioconvert: move audio quantize code to libs
Move the audio quantize code from audioconvert to the audio library.
work on making an audio converter helper function similar to the video
converter.
Fold fastrandom directly into the quantizer, add some ORC code to
optimize this later.
2015-11-06 12:10:48 +01:00

485 lines
13 KiB
C

/* GStreamer
* Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org>
* (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* gstaudioquantize.c: quantizes audio to the target format and optionally
* applies dithering and noise shaping.
*
* 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.
*/
/* TODO: - Maybe drop 5-pole noise shaping and use coefficients
* generated by dmaker
* http://shibatch.sf.net
*/
#include <gst/gst.h>
#include <string.h>
#include <math.h>
#include "gstaudiopack.h"
#include "audio-quantize.h"
typedef void (*QuantizeFunc) (GstAudioQuantize * quant, const gpointer src,
gpointer dst, gint count);
struct _GstAudioQuantize
{
GstAudioDitherMethod dither;
GstAudioNoiseShapingMethod ns;
GstAudioQuantizeFlags flags;
GstAudioFormat format;
guint channels;
guint quantizer;
guint shift;
guint32 mask, bias;
/* last random number generated per channel for hifreq TPDF dither */
gpointer last_random;
/* contains the past quantization errors, error[out_channels][count] */
guint error_size;
gpointer error_buf;
/* buffer with dither values */
guint dither_size;
gpointer dither_buf;
/* noise shaping coefficients */
gpointer coeffs;
gint n_coeffs;
QuantizeFunc quantize;
};
#define ADDSS(res,val) \
if (val > 0 && res > 0 && G_MAXINT32 - res <= val){ \
res = G_MAXINT32; \
} else if (val < 0 && res < 0 && G_MININT32 - res >= val){ \
res = G_MININT32; \
} else \
res += val;
static void
gst_audio_quantize_quantize_memcpy (GstAudioQuantize * quant,
const gpointer src, gpointer dst, gint samples)
{
if (src != dst)
memcpy (dst, src, samples * sizeof (gint32) * quant->channels);
}
/* Quantize functions for gint32 as intermediate format */
static void
gst_audio_quantize_quantize_int_none_none (GstAudioQuantize * quant,
const gpointer src, gpointer dst, gint samples)
{
audio_orc_int_bias (dst, src, quant->bias, ~quant->mask,
samples * quant->channels);
}
/* This is the base function, implementing a linear congruential generator
* and returning a pseudo random number between 0 and 2^32 - 1.
*/
static inline guint32
gst_fast_random_uint32 (void)
{
static guint32 state = 0xdeadbeef;
return (state = state * 1103515245 + 12345);
}
static inline gint32
gst_fast_random_int32 (void)
{
return (gint32) gst_fast_random_uint32 ();
}
/* Assuming dither == 2^n,
* returns one of 2^(n+1) possible random values:
* -dither <= retval < dither */
#define RANDOM_INT_DITHER(dither) \
(- dither + (gst_fast_random_int32 () & ((dither << 1) - 1)))
static void
setup_dither_buf (GstAudioQuantize * quant, gint samples)
{
gboolean need_init = FALSE;
gint channels = quant->channels;
gint i, len = samples * channels;
guint shift = quant->shift;
guint32 bias;
gint32 dither, *d;
if (quant->dither_size < len) {
quant->dither_size = len;
quant->dither_buf = g_realloc (quant->dither_buf, len * sizeof (gint32));
need_init = TRUE;
}
bias = quant->bias;
d = quant->dither_buf;
switch (quant->dither) {
case GST_AUDIO_DITHER_NONE:
if (need_init) {
for (i = 0; i < len; i++)
d[i] = 0;
}
break;
case GST_AUDIO_DITHER_RPDF:
dither = 1 << (shift);
for (i = 0; i < len; i++)
d[i] = bias + RANDOM_INT_DITHER (dither);
break;
case GST_AUDIO_DITHER_TPDF:
dither = 1 << (shift - 1);
for (i = 0; i < len; i++)
d[i] = bias + RANDOM_INT_DITHER (dither) + RANDOM_INT_DITHER (dither);
break;
case GST_AUDIO_DITHER_TPDF_HF:
{
gint32 tmp, *last_random = quant->last_random;
dither = 1 << (shift - 1);
for (i = 0; i < len; i++) {
tmp = RANDOM_INT_DITHER (dither);
d[i] = bias + tmp - last_random[i % channels];
last_random[i % channels] = tmp;
}
break;
}
}
}
static void
gst_audio_quantize_quantize_int_dither_none (GstAudioQuantize * quant,
const gpointer src, gpointer dst, gint samples)
{
setup_dither_buf (quant, samples);
audio_orc_int_dither (dst, src, quant->dither_buf, ~quant->mask,
samples * quant->channels);
}
static void
setup_error_buf (GstAudioQuantize * quant, gint samples)
{
gint channels = quant->channels;
gint len = (samples + quant->n_coeffs) * channels;
if (quant->error_size < len) {
quant->error_buf = g_realloc (quant->error_buf, len * sizeof (gint32));
if (quant->error_size == 0)
memset ((gint32 *) quant->error_buf, 0,
channels * quant->n_coeffs * sizeof (gint32));
quant->error_size = len;
}
}
static void
gst_audio_quantize_quantize_int_dither_feedback (GstAudioQuantize * quant,
const gpointer src, gpointer dst, gint samples)
{
guint32 mask;
gint i, len, channels;
const gint32 *s = src;
gint32 *dith, *d = dst, v, o, *e, err;
setup_dither_buf (quant, samples);
setup_error_buf (quant, samples);
channels = quant->channels;
len = samples * channels;
dith = quant->dither_buf;
e = quant->error_buf;
mask = ~quant->mask;
for (i = 0; i < len; i++) {
o = v = s[i];
/* add dither */
err = dith[i];
/* remove error */
err -= e[i];
ADDSS (v, err);
v &= mask;
/* store new error */
e[i + channels] = e[i] + (v - o);
/* store result */
d[i] = v;
}
memmove (e, &e[len], sizeof (gint32) * channels);
}
#define SHIFT 10
#define REDUCE 8
#define RROUND (1<<(REDUCE-1))
#define SREDUCE 2
#define SROUND (1<<(SREDUCE-1))
static void
gst_audio_quantize_quantize_int_dither_noise_shape (GstAudioQuantize * quant,
const gpointer src, gpointer dst, gint samples)
{
guint32 mask;
gint i, j, k, len, channels, nc;
const gint32 *s = src;
gint32 *c, *dith, *d = dst, v, o, *e, err;
setup_dither_buf (quant, samples);
setup_error_buf (quant, samples);
channels = quant->channels;
len = samples * channels;
dith = quant->dither_buf;
e = quant->error_buf;
c = quant->coeffs;
nc = quant->n_coeffs;
mask = ~quant->mask;
for (i = 0; i < len; i++) {
v = s[i];
/* combine and remove error */
err = 0;
for (j = 0, k = i; j < nc; j++, k += channels)
err -= e[k] * c[j];
err = (err + SROUND) >> (SREDUCE);
ADDSS (v, err);
o = v;
/* add dither */
err = dith[i];
ADDSS (v, err);
/* quantize */
v &= mask;
/* store new error with reduced precision */
e[k] = (v - o + RROUND) >> REDUCE;
/* store result */
d[i] = v;
}
memmove (e, &e[len], sizeof (gint32) * channels * nc);
}
#define MAKE_QUANTIZE_FUNC_NAME(name) \
gst_audio_quantize_quantize_##name
static const QuantizeFunc quantize_funcs[] = {
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_none_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_feedback),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_feedback),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_feedback),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_feedback),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_dither_noise_shape),
};
/* Same as error feedback but also add 1/2 of the previous error value.
* This moves the noise a bit more into the higher frequencies. */
static const gdouble ns_simple_coeffs[] = {
-0.5, 1.0
};
/* Noise shaping coefficients from[1], moves most power of the
* error noise into inaudible frequency ranges.
*
* [1]
* "Minimally Audible Noise Shaping", Stanley P. Lipshitz,
* John Vanderkooy, and Robert A. Wannamaker,
* J. Audio Eng. Soc., Vol. 39, No. 11, November 1991. */
static const gdouble ns_medium_coeffs[] = {
0.6149, -1.590, 1.959, -2.165, 2.033
};
/* Noise shaping coefficients by David Schleef, moves most power of the
* error noise into inaudible frequency ranges */
static const gdouble ns_high_coeffs[] = {
-0.340122, 0.876066, -1.72008, 2.61339, -3.31399, 3.27918, -2.92975, 2.08484,
};
static void
gst_audio_quantize_setup_noise_shaping (GstAudioQuantize * quant)
{
gint i, n_coeffs = 0;
gint32 *q;
const gdouble *coeffs;
switch (quant->ns) {
case GST_AUDIO_NOISE_SHAPING_HIGH:
n_coeffs = 8;
coeffs = ns_high_coeffs;
break;
case GST_AUDIO_NOISE_SHAPING_MEDIUM:
n_coeffs = 5;
coeffs = ns_medium_coeffs;
break;
case GST_AUDIO_NOISE_SHAPING_SIMPLE:
n_coeffs = 2;
coeffs = ns_simple_coeffs;
break;
case GST_AUDIO_NOISE_SHAPING_ERROR_FEEDBACK:
break;
case GST_AUDIO_NOISE_SHAPING_NONE:
default:
break;
}
if (n_coeffs) {
quant->n_coeffs = n_coeffs;
q = quant->coeffs = g_new0 (gint32, quant->channels * n_coeffs);
for (i = 0; i < n_coeffs; i++)
q[i] = floor (coeffs[i] * (1 << SHIFT) + 0.5);
}
return;
}
static void
gst_audio_quantize_setup_dither (GstAudioQuantize * quant)
{
switch (quant->dither) {
case GST_AUDIO_DITHER_TPDF_HF:
quant->last_random = g_new0 (gint32, quant->channels);
break;
case GST_AUDIO_DITHER_RPDF:
case GST_AUDIO_DITHER_TPDF:
quant->last_random = NULL;
break;
case GST_AUDIO_DITHER_NONE:
default:
quant->last_random = NULL;
break;
}
return;
}
static void
gst_audio_quantize_setup_quantize_func (GstAudioQuantize * quant)
{
gint index;
if (quant->shift == 0) {
quant->quantize = (QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (memcpy);
return;
}
index = 5 * quant->dither + quant->ns;
quant->quantize = quantize_funcs[index];
}
static gint
count_power (guint v)
{
gint res = 0;
while (v > 1) {
res++;
v >>= 1;
}
return res;
}
/**
* gst_audio_quantize_new:
* @dither: a #GstAudioDitherMethod
* @ns: a #GstAudioNoiseShapingMethod
* @flags: #GstAudioQuantizeFlags
* @format: the #GstAudioFormat of the samples
* @channels: the amount of channels in the samples
* @quantizer: the quantizer to use
*
* Create a new quantizer object with the given parameters.
*
* Output samples will be quantized to a multiple of @quantizer. Better
* performance is achieved when @quantizer is a power of 2.
*
* Returns: a new #GstAudioQuantize. Free with gst_audio_quantize_free().
*/
GstAudioQuantize *
gst_audio_quantize_new (GstAudioDitherMethod dither,
GstAudioNoiseShapingMethod ns, GstAudioQuantizeFlags flags,
GstAudioFormat format, guint channels, guint quantizer)
{
GstAudioQuantize *quant;
g_return_val_if_fail (format == GST_AUDIO_FORMAT_S32, NULL);
g_return_val_if_fail (channels > 0, NULL);
quant = g_slice_new0 (GstAudioQuantize);
quant->dither = dither;
quant->ns = ns;
quant->flags = flags;
quant->format = format;
quant->channels = channels;
quant->quantizer = quantizer;
quant->shift = count_power (quantizer);
if (quant->shift > 0)
quant->bias = (1U << (quant->shift - 1));
else
quant->bias = 0;
quant->mask = (1U << quant->shift) - 1;
gst_audio_quantize_setup_dither (quant);
gst_audio_quantize_setup_noise_shaping (quant);
gst_audio_quantize_setup_quantize_func (quant);
return quant;
}
/**
* gst_audio_quantize_free:
* @quant: a #GstAudioQuantize
*
* Free a #GstAudioQuantize.
*/
void
gst_audio_quantize_free (GstAudioQuantize * quant)
{
g_return_if_fail (quant != NULL);
g_free (quant->error_buf);
g_free (quant->coeffs);
g_free (quant->last_random);
g_slice_free (GstAudioQuantize, quant);
}
void
gst_audio_quantize_samples (GstAudioQuantize * quant,
const gpointer src, gpointer dst, guint samples)
{
g_return_if_fail (quant != NULL);
g_return_if_fail (dst != NULL || samples == 0);
g_return_if_fail (src != NULL || samples == 0);
quant->quantize (quant, dst, src, samples);
}