audioconvert: rework audioconvert

Rewrite audioconvert to try to make it more clear what steps are
executed during conversion.
Add passthrough step that just does a memcpy when possible.
Add ORC optimized dither and quantization functions.
Implement noise-shaping on S32 samples only and allow for arbitrary
noise shaping coefficients if we want this later.
This commit is contained in:
Wim Taymans 2015-10-30 17:36:48 +01:00
parent e1569ce76a
commit 5cf367ae57
5 changed files with 428 additions and 471 deletions

View file

@ -32,29 +32,14 @@
#include "gstaudioconvertorc.h"
/**
* int -> int
* - unpack S32
* - convert F64
* - (channel mix S32) (channel mix F64)
* - (quantize+dither S32) quantize+dither+ns F64->S32
* - pack from S32
* int/int int/float float/int float/float
*
* int -> float
* - unpack S32
* - convert F64
* - (channel mix F64)
* - pack from F64
*
* float -> int
* - unpack F64
* - (channel mix F64)
* - quantize+dither+ns F64->S32
* - pack from S32
*
* float -> float
* - unpack F64
* - (channel mix F64)
* - pack from F64
* unpack S32 S32 F64 F64
* convert S32->F64
* channel mix S32 F64 F64 F64
* convert F64->S32
* quantize S32 S32
* pack S32 F64 S32 F64
*/
gboolean
audio_convert_prepare_context (AudioConvertCtx * ctx, GstAudioInfo * in,
@ -63,6 +48,8 @@ audio_convert_prepare_context (AudioConvertCtx * ctx, GstAudioInfo * in,
{
gint in_depth, out_depth;
GstChannelMixFlags flags;
gboolean in_int, out_int;
GstAudioFormat format;
g_return_val_if_fail (ctx != NULL, FALSE);
g_return_val_if_fail (in != NULL, FALSE);
@ -78,30 +65,15 @@ audio_convert_prepare_context (AudioConvertCtx * ctx, GstAudioInfo * in,
ctx->in = *in;
ctx->out = *out;
GST_INFO ("unitsizes: %d -> %d", in->bpf, out->bpf);
in_depth = GST_AUDIO_FORMAT_INFO_DEPTH (in->finfo);
out_depth = GST_AUDIO_FORMAT_INFO_DEPTH (out->finfo);
GST_INFO ("depth in %d, out %d", in_depth, out_depth);
/* Don't dither or apply noise shaping if target depth is bigger than 20 bits
* as DA converters only can do a SNR up to 20 bits in reality.
* Also don't dither or apply noise shaping if target depth is larger than
* source depth. */
if (out_depth <= 20 && (!GST_AUDIO_FORMAT_INFO_IS_INTEGER (in->finfo)
|| in_depth >= out_depth)) {
dither = dither;
ns = ns;
GST_INFO ("using dither %d and noise shaping %d", dither, ns);
} else {
dither = GST_AUDIO_DITHER_NONE;
ns = GST_AUDIO_NOISE_SHAPING_NONE;
GST_INFO ("using no dither and noise shaping");
}
/* Use simple error feedback when output sample rate is smaller than
* 32000 as the other methods might move the noise to audible ranges */
if (ns > GST_AUDIO_NOISE_SHAPING_ERROR_FEEDBACK && out->rate < 32000)
ns = GST_AUDIO_NOISE_SHAPING_ERROR_FEEDBACK;
in_int = GST_AUDIO_FORMAT_INFO_IS_INTEGER (in->finfo);
out_int = GST_AUDIO_FORMAT_INFO_IS_INTEGER (out->finfo);
flags =
GST_AUDIO_INFO_IS_UNPOSITIONED (in) ?
@ -110,63 +82,70 @@ audio_convert_prepare_context (AudioConvertCtx * ctx, GstAudioInfo * in,
GST_AUDIO_INFO_IS_UNPOSITIONED (out) ?
GST_CHANNEL_MIX_FLAGS_UNPOSITIONED_OUT : 0;
ctx->mix = gst_channel_mix_new (flags, in->channels, in->position,
out->channels, out->position);
if (!GST_AUDIO_FORMAT_INFO_IS_INTEGER (ctx->in.finfo) ||
!GST_AUDIO_FORMAT_INFO_IS_INTEGER (ctx->out.finfo) ||
(ns != GST_AUDIO_NOISE_SHAPING_NONE))
ctx->mix_format = GST_AUDIO_FORMAT_F64;
else
ctx->mix_format = GST_AUDIO_FORMAT_S32;
/* step 1, unpack */
format = in->finfo->unpack_format;
ctx->in_default = in->finfo->unpack_format == in->finfo->format;
GST_INFO ("unpack format %s to %s",
gst_audio_format_to_string (in->finfo->format),
gst_audio_format_to_string (format));
/* if one formats is float/double or we use noise shaping use double as
* intermediate format and switch mixing */
if (ctx->mix_format == GST_AUDIO_FORMAT_F64) {
GST_INFO ("use float mixing");
if (ctx->in.finfo->unpack_format != GST_AUDIO_FORMAT_F64) {
ctx->convert = audio_convert_orc_s32_to_double;
GST_INFO ("convert input to F64");
}
/* check if input needs to be unpacked to intermediate format */
ctx->in_default =
GST_AUDIO_FORMAT_INFO_FORMAT (in->finfo) == GST_AUDIO_FORMAT_F64;
if (GST_AUDIO_FORMAT_INFO_IS_INTEGER (out->finfo)) {
/* quantization will convert to s32, check if this is our final output format */
ctx->out_default =
GST_AUDIO_FORMAT_INFO_FORMAT (out->finfo) == GST_AUDIO_FORMAT_S32;
} else {
ctx->out_default =
GST_AUDIO_FORMAT_INFO_FORMAT (out->finfo) == GST_AUDIO_FORMAT_F64;
}
} else {
GST_INFO ("use int mixing");
/* check if input needs to be unpacked to intermediate format */
ctx->in_default =
GST_AUDIO_FORMAT_INFO_FORMAT (in->finfo) == GST_AUDIO_FORMAT_S32;
/* check if output is in default format */
ctx->out_default =
GST_AUDIO_FORMAT_INFO_FORMAT (out->finfo) == GST_AUDIO_FORMAT_S32;
/* step 2, optional convert from S32 to F64 for channel mix */
if (in_int && !out_int) {
GST_INFO ("convert S32 to F64");
ctx->convert_in = (AudioConvertFunc) audio_convert_orc_s32_to_double;
format = GST_AUDIO_FORMAT_F64;
}
GST_INFO ("unitsizes: %d -> %d", in->bpf, out->bpf);
/* check if channel mixer is passthrough */
/* step 3, channel mix */
ctx->mix_format = format;
ctx->mix = gst_channel_mix_new (flags, in->channels, in->position,
out->channels, out->position);
ctx->mix_passthrough = gst_channel_mix_is_passthrough (ctx->mix);
ctx->quant_default =
GST_AUDIO_FORMAT_INFO_FORMAT (out->finfo) == ctx->mix_format;
GST_INFO ("mix format %s, passthrough %d, in_channels %d, out_channels %d",
gst_audio_format_to_string (format), ctx->mix_passthrough,
in->channels, out->channels);
GST_INFO ("in default %d, mix passthrough %d, out default %d",
ctx->in_default, ctx->mix_passthrough, ctx->out_default);
/* step 4, optional convert for quantize */
if (!in_int && out_int) {
GST_INFO ("convert F64 to S32");
ctx->convert_out = (AudioConvertFunc) audio_convert_orc_double_to_s32;
format = GST_AUDIO_FORMAT_S32;
}
/* step 5, optional quantize */
/* Don't dither or apply noise shaping if target depth is bigger than 20 bits
* as DA converters only can do a SNR up to 20 bits in reality.
* Also don't dither or apply noise shaping if target depth is larger than
* source depth. */
if (out_depth > 20 || (in_int && out_depth >= in_depth)) {
dither = GST_AUDIO_DITHER_NONE;
ns = GST_AUDIO_NOISE_SHAPING_NONE;
GST_INFO ("using no dither and noise shaping");
} else {
GST_INFO ("using dither %d and noise shaping %d", dither, ns);
/* Use simple error feedback when output sample rate is smaller than
* 32000 as the other methods might move the noise to audible ranges */
if (ns > GST_AUDIO_NOISE_SHAPING_ERROR_FEEDBACK && out->rate < 32000)
ns = GST_AUDIO_NOISE_SHAPING_ERROR_FEEDBACK;
}
/* we still want to run the quantization step when reducing bits to get
* the rounding correct */
if (out_int && out_depth < 32) {
GST_INFO ("quantize to %d bits, dither %d, ns %d", out_depth, dither, ns);
ctx->quant = gst_audio_quantize_new (dither, ns, 0, format,
out->channels, 1U << (32 - out_depth));
}
/* step 6, pack */
g_assert (out->finfo->unpack_format == format);
ctx->out_default = format == out->finfo->format;
GST_INFO ("pack format %s to %s", gst_audio_format_to_string (format),
gst_audio_format_to_string (out->finfo->format));
ctx->out_scale =
GST_AUDIO_FORMAT_INFO_IS_INTEGER (out->finfo) ? (32 - out_depth) : 0;
GST_INFO ("scale out %d", ctx->out_scale);
ctx->quant = gst_audio_quantize_new (dither, ns, 0, ctx->mix_format,
out->channels, ctx->out_scale);
/* optimize */
if (out->finfo->format == in->finfo->format && ctx->mix_passthrough) {
GST_INFO ("same formats and passthrough mixing -> passthrough");
ctx->passthrough = TRUE;
}
return TRUE;
@ -185,15 +164,19 @@ audio_convert_clean_context (AudioConvertCtx * ctx)
if (ctx->quant)
gst_audio_quantize_free (ctx->quant);
ctx->quant = NULL;
if (ctx->mix)
gst_channel_mix_free (ctx->mix);
ctx->mix = NULL;
gst_audio_info_init (&ctx->in);
gst_audio_info_init (&ctx->out);
ctx->convert = NULL;
ctx->convert_in = NULL;
ctx->convert_out = NULL;
g_free (ctx->tmpbuf);
g_free (ctx->tmpbuf2);
ctx->tmpbuf = NULL;
ctx->tmpbuf2 = NULL;
ctx->tmpbufsize = 0;
return TRUE;
@ -217,10 +200,8 @@ gboolean
audio_convert_convert (AudioConvertCtx * ctx, gpointer src,
gpointer dst, gint samples, gboolean src_writable)
{
guint insize, outsize, size;
gpointer outbuf, tmpbuf;
guint intemp = 0, outtemp = 0, biggest;
gint in_width, out_width;
guint size;
gpointer outbuf, tmpbuf, tmpbuf2;
g_return_val_if_fail (ctx != NULL, FALSE);
g_return_val_if_fail (src != NULL, FALSE);
@ -230,90 +211,83 @@ audio_convert_convert (AudioConvertCtx * ctx, gpointer src,
if (samples == 0)
return TRUE;
insize = ctx->in.bpf * samples;
outsize = ctx->out.bpf * samples;
in_width = GST_AUDIO_FORMAT_INFO_WIDTH (ctx->in.finfo);
out_width = GST_AUDIO_FORMAT_INFO_WIDTH (ctx->out.finfo);
/* find biggest temp buffer size */
size = (ctx->mix_format == GST_AUDIO_FORMAT_F64) ? sizeof (gdouble)
: sizeof (gint32);
if (!ctx->in_default)
intemp = gst_util_uint64_scale (insize, size * 8, in_width);
if (!ctx->mix_passthrough || !ctx->quant_default)
outtemp = gst_util_uint64_scale (outsize, size * 8, out_width);
biggest = MAX (intemp, outtemp);
/* see if one of the buffers can be used as temp */
if ((outsize >= biggest) && (ctx->out.bpf <= size))
tmpbuf = dst;
else if ((insize >= biggest) && src_writable && (ctx->in.bpf >= size))
tmpbuf = src;
else {
if (biggest > ctx->tmpbufsize) {
ctx->tmpbuf = g_realloc (ctx->tmpbuf, biggest);
ctx->tmpbufsize = biggest;
}
tmpbuf = ctx->tmpbuf;
if (ctx->passthrough) {
memcpy (dst, src, samples * ctx->in.bpf);
return TRUE;
}
/* start conversion */
size = sizeof (gdouble) * samples * MAX (ctx->in.channels, ctx->out.channels);
if (size > ctx->tmpbufsize) {
ctx->tmpbuf = g_realloc (ctx->tmpbuf, size);
ctx->tmpbuf2 = g_realloc (ctx->tmpbuf2, size);
ctx->tmpbufsize = size;
}
tmpbuf = ctx->tmpbuf;
tmpbuf2 = ctx->tmpbuf2;
/* 1. unpack */
if (!ctx->in_default) {
gpointer t;
/* check if final conversion */
if (!(ctx->quant_default && ctx->mix_passthrough))
outbuf = tmpbuf;
else
if (!ctx->convert_in && ctx->mix_passthrough && !ctx->convert_out
&& !ctx->quant && ctx->out_default)
outbuf = dst;
/* move samples to the middle of the array so that we can
* convert them in-place */
if (ctx->convert)
t = ((gint32 *) outbuf) + (samples * ctx->in.channels);
else
t = outbuf;
outbuf = tmpbuf;
/* unpack to default format */
ctx->in.finfo->unpack_func (ctx->in.finfo, 0, t, src,
ctx->in.finfo->unpack_func (ctx->in.finfo, 0, outbuf, src,
samples * ctx->in.channels);
if (ctx->convert)
ctx->convert (outbuf, t, samples * ctx->in.channels);
src = outbuf;
}
/* 2. optionally convert for mixing */
if (ctx->convert_in) {
if (ctx->mix_passthrough && !ctx->convert_out && !ctx->quant
&& ctx->out_default)
outbuf = dst;
else if (src == tmpbuf)
outbuf = tmpbuf2;
else
outbuf = tmpbuf;
ctx->convert_in (outbuf, src, samples * ctx->in.channels);
src = outbuf;
}
/* step 3, channel mix if not passthrough */
if (!ctx->mix_passthrough) {
/* check if final conversion */
if (ctx->quant_default)
if (!ctx->convert_out && !ctx->quant && ctx->out_default)
outbuf = dst;
else
outbuf = tmpbuf;
/* convert channels */
gst_channel_mix_mix (ctx->mix, ctx->mix_format, ctx->in.layout, src, outbuf,
samples);
src = outbuf;
}
/* step 4, optional convert F64 -> S32 for quantize */
if (ctx->convert_out) {
if (!ctx->quant && ctx->out_default)
outbuf = dst;
else
outbuf = tmpbuf;
ctx->convert_out (outbuf, src, samples * ctx->out.channels);
src = outbuf;
}
/* we only need to quantize if output format is int */
if (GST_AUDIO_FORMAT_INFO_IS_INTEGER (ctx->out.finfo)) {
/* step 5, optional quantize */
if (ctx->quant) {
if (ctx->out_default)
outbuf = dst;
else
outbuf = tmpbuf;
gst_audio_quantize_samples (ctx->quant, src, samples);
outbuf = src;
gst_audio_quantize_samples (ctx->quant, outbuf, src, samples);
src = outbuf;
}
/* step 6, pack */
if (!ctx->out_default) {
/* pack default format into dst */
ctx->out.finfo->pack_func (ctx->out.finfo, 0, src, dst,
samples * ctx->out.channels);
}

View file

@ -33,28 +33,32 @@ GST_DEBUG_CATEGORY_EXTERN (audio_convert_debug);
typedef struct _AudioConvertCtx AudioConvertCtx;
typedef void (*AudioConvertToF64) (gdouble *dst, const gint32 *src, gint count);
typedef void (*AudioConvertFunc) (gpointer dst, const gpointer src, gint count);
struct _AudioConvertCtx
{
GstAudioInfo in;
GstAudioInfo out;
gboolean in_default;
AudioConvertFunc convert_in;
GstAudioFormat mix_format;
gboolean mix_passthrough;
GstChannelMix *mix;
AudioConvertFunc convert_out;
GstAudioQuantize *quant;
gboolean in_default;
gboolean mix_passthrough;
gboolean quant_default;
gboolean out_default;
gboolean passthrough;
gpointer tmpbuf;
gpointer tmpbuf2;
gint tmpbufsize;
gint out_scale;
AudioConvertToF64 convert;
};
gboolean audio_convert_prepare_context (AudioConvertCtx * ctx,

View file

@ -13,3 +13,24 @@ divd d1, t1, 0x41DFFFFFFFC00000L
muld t1, s1, 0x41DFFFFFFFC00000L
convdl d1, t1
.function audio_convert_orc_int_bias
.dest 4 d1 gint32
.source 4 s1 gint32
.param 4 bias gint32
.param 4 mask gint32
.temp 4 t1
addssl t1, s1, bias
andl d1, t1, mask
.function audio_convert_orc_int_dither
.dest 4 d1 gint32
.source 4 s1 gint32
.source 4 dither gint32
.param 4 mask gint32
.temp 4 t1
addssl t1, s1, dither
andl d1, t1, mask

View file

@ -1,5 +1,6 @@
/* 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.
@ -20,12 +21,6 @@
* Boston, MA 02110-1301, USA.
*/
/*
* FIXME: When doing dithering with int as intermediate format
* one gets audible harmonics while the noise floor is
* constant for double as intermediate format!
*/
/* TODO: - Maybe drop 5-pole noise shaping and use coefficients
* generated by dmaker
* http://shibatch.sf.net
@ -39,7 +34,7 @@
#include "gstfastrandom.h"
typedef void (*QuantizeFunc) (GstAudioQuantize * quant, gpointer src,
typedef void (*QuantizeFunc) (GstAudioQuantize * quant, const gpointer src,
gpointer dst, gint count);
struct _GstAudioQuantize
@ -51,208 +46,247 @@ struct _GstAudioQuantize
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] */
gdouble *error_buf;
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 MAKE_QUANTIZE_FUNC_NAME(name) \
gst_audio_quantize_quantize_##name
#define ADDSS(res,val) \
if (val > 0 && res > 0 && G_MAXINT32 - res <= val) \
if (val > 0 && res > 0 && G_MAXINT32 - res <= val){ \
res = G_MAXINT32; \
else if (val < 0 && res < 0 && G_MININT32 - res >= val) \
} else if (val < 0 && res < 0 && G_MININT32 - res >= val){ \
res = G_MININT32; \
else \
} 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 */
#define MAKE_QUANTIZE_FUNC_I(name, DITHER_INIT_FUNC, ADD_DITHER_FUNC, \
ROUND_FUNC) \
static void \
MAKE_QUANTIZE_FUNC_NAME (name) (GstAudioQuantize *quant, gint32 *src, \
gint32 *dst, gint count) \
{ \
gint scale = quant->quantizer; \
gint channels = quant->channels; \
gint chan_pos; \
\
if (scale > 0) { \
gint32 tmp; \
guint32 mask = 0xffffffff & (0xffffffff << scale); \
guint32 bias = 1U << (scale - 1); \
DITHER_INIT_FUNC() \
\
for (;count;count--) { \
for (chan_pos = 0; chan_pos < channels; chan_pos++) { \
tmp = *src++; \
ADD_DITHER_FUNC() \
ROUND_FUNC() \
*dst = tmp & mask; \
dst++; \
} \
} \
} else { \
memcpy (dst, src, count * channels * 4); \
} \
static void
gst_audio_quantize_quantize_int_none_none (GstAudioQuantize * quant,
const gpointer src, gpointer dst, gint samples)
{
audio_convert_orc_int_bias (dst, src, quant->bias, ~quant->mask,
samples * quant->channels);
}
/* Quantize functions for gdouble as intermediate format with
* int as target */
#define MAKE_QUANTIZE_FUNC_F(name, DITHER_INIT_FUNC, NS_INIT_FUNC, \
ADD_NS_FUNC, ADD_DITHER_FUNC, \
UPDATE_ERROR_FUNC) \
static void \
MAKE_QUANTIZE_FUNC_NAME (name) (GstAudioQuantize *quant, gdouble *src, \
gint32 *dst, gint count) \
{ \
gint scale = quant->quantizer; \
gint channels = quant->channels; \
gint chan_pos; \
gdouble tmp, d, factor = (1U<<(32-scale-1)); \
\
if (scale > 0) { \
DITHER_INIT_FUNC() \
NS_INIT_FUNC() \
\
for (;count;count--) { \
for (chan_pos = 0; chan_pos < channels; chan_pos++) { \
tmp = *src++; \
ADD_NS_FUNC() \
ADD_DITHER_FUNC() \
tmp = floor(tmp * factor); \
d = CLAMP (tmp, -factor, factor - 1); \
UPDATE_ERROR_FUNC() \
*dst++ = ((gint32)d) << scale; \
} \
} \
} else { \
audio_convert_orc_double_to_s32 (dst, src, count * channels); \
} \
}
/* Rounding functions for int as intermediate format, only used when
* not using dithering. With dithering we include this offset in our
* dither noise instead. */
#define ROUND() \
if (tmp > 0 && G_MAXINT32 - tmp <= bias) \
tmp = G_MAXINT32; \
else \
tmp += bias;
#define NONE_FUNC()
/* Dithering definitions
* See http://en.wikipedia.org/wiki/Dithering or
* http://www.users.qwest.net/~volt42/cadenzarecording/DitherExplained.pdf for explainations.
*
* We already add the rounding offset to the dither noise here
* to have only one overflow check instead of two. */
#define INIT_DITHER_RPDF_I() \
gint32 rand; \
gint32 dither = (1<<(scale));
/* 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)))
#define ADD_DITHER_RPDF_I() \
rand = bias + RANDOM_INT_DITHER(dither); \
ADDSS (tmp, rand);
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;
#define INIT_DITHER_RPDF_F() \
gdouble dither = 1.0/(1U<<(32 - scale - 1));
if (quant->dither_size < len) {
quant->dither_size = len;
quant->dither_buf = g_realloc (quant->dither_buf, len * sizeof (gint32));
need_init = TRUE;
}
#define ADD_DITHER_RPDF_F() \
tmp += gst_fast_random_double_range (- dither, dither);
bias = quant->bias;
d = quant->dither_buf;
#define INIT_DITHER_TPDF_I() \
gint32 rand; \
gint32 dither = (1<<(scale - 1));
switch (quant->dither) {
case GST_AUDIO_DITHER_NONE:
if (need_init) {
for (i = 0; i < len; i++)
d[i] = 0;
}
break;
#define ADD_DITHER_TPDF_I() \
rand = bias + RANDOM_INT_DITHER(dither) \
+ RANDOM_INT_DITHER(dither); \
ADDSS (tmp, rand);
case GST_AUDIO_DITHER_RPDF:
dither = 1 << (shift);
for (i = 0; i < len; i++)
d[i] = bias + RANDOM_INT_DITHER (dither);
break;
#define INIT_DITHER_TPDF_F() \
gdouble dither = 1.0/(1U<<(32 - scale));
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;
#define ADD_DITHER_TPDF_F() \
tmp += gst_fast_random_double_range (- dither, dither) \
+ gst_fast_random_double_range (- dither, dither);
case GST_AUDIO_DITHER_TPDF_HF:
{
gint32 tmp, *last_random = quant->last_random;
#define INIT_DITHER_TPDF_HF_I() \
gint32 rand; \
gint32 dither = (1<<(scale-1)); \
gint32 *last_random = (gint32 *) quant->last_random, tmp_rand;
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;
}
}
}
#define ADD_DITHER_TPDF_HF_I() \
tmp_rand = RANDOM_INT_DITHER(dither); \
rand = bias + tmp_rand - last_random[chan_pos]; \
last_random[chan_pos] = tmp_rand; \
ADDSS (tmp, rand);
static void
gst_audio_quantize_quantize_int_dither_none (GstAudioQuantize * quant,
const gpointer src, gpointer dst, gint samples)
{
setup_dither_buf (quant, samples);
/* Like TPDF dither but the dither noise is oriented more to the
* higher frequencies */
audio_convert_orc_int_dither (dst, src, quant->dither_buf, ~quant->mask,
samples * quant->channels);
}
#define INIT_DITHER_TPDF_HF_F() \
gdouble rand; \
gdouble dither = 1.0/(1U<<(32 - scale)); \
gdouble *last_random = (gdouble *) quant->last_random, tmp_rand;
static void
setup_error_buf (GstAudioQuantize * quant, gint samples)
{
gint channels = quant->channels;
gint len = (samples + quant->n_coeffs) * channels;
#define ADD_DITHER_TPDF_HF_F() \
tmp_rand = gst_fast_random_double_range (- dither, dither); \
rand = tmp_rand - last_random[chan_pos]; \
last_random[chan_pos] = tmp_rand; \
tmp += rand;
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;
}
}
/* Noise shaping definitions.
* See http://en.wikipedia.org/wiki/Noise_shaping for explanations. */
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);
/* Simple error feedback: Just accumulate the dithering and quantization
* error and remove it from each sample. */
channels = quant->channels;
len = samples * channels;
dith = quant->dither_buf;
e = quant->error_buf;
mask = ~quant->mask;
#define INIT_NS_ERROR_FEEDBACK() \
gdouble orig; \
gdouble *errors = quant->error_buf;
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 ADD_NS_ERROR_FEEDBACK() \
orig = tmp; \
tmp -= errors[chan_pos];
#define SHIFT 10
#define REDUCE 8
#define RROUND (1<<(REDUCE-1))
#define SREDUCE 2
#define SROUND (1<<(SREDUCE-1))
#define UPDATE_ERROR_ERROR_FEEDBACK() \
errors[chan_pos] += (d)/factor - orig;
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. */
#define INIT_NS_SIMPLE() \
gdouble orig; \
gdouble *errors = quant->error_buf, cur_error;
#define ADD_NS_SIMPLE() \
cur_error = errors[chan_pos*2] - 0.5 * errors[chan_pos*2 + 1]; \
tmp -= cur_error; \
orig = tmp;
#define UPDATE_ERROR_SIMPLE() \
errors[chan_pos*2 + 1] = errors[chan_pos*2]; \
errors[chan_pos*2] = (d)/factor - orig;
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.
@ -263,158 +297,53 @@ MAKE_QUANTIZE_FUNC_NAME (name) (GstAudioQuantize *quant, gdouble *src, \
* J. Audio Eng. Soc., Vol. 39, No. 11, November 1991. */
static const gdouble ns_medium_coeffs[] = {
2.033, -2.165, 1.959, -1.590, 0.6149
0.6149, -1.590, 1.959, -2.165, 2.033
};
#define INIT_NS_MEDIUM() \
gdouble orig; \
gdouble *errors = quant->error_buf, cur_error; \
int j;
#define ADD_NS_MEDIUM() \
cur_error = 0.0; \
for (j = 0; j < 5; j++) \
cur_error += errors[chan_pos*5 + j] * ns_medium_coeffs[j]; \
tmp -= cur_error; \
orig = tmp;
#define UPDATE_ERROR_MEDIUM() \
for (j = 4; j > 0; j--) \
errors[chan_pos*5 + j] = errors[chan_pos*5 + j-1]; \
errors[chan_pos*5] = (d)/factor - orig;
/* Noise shaping coefficients by David Schleef, moves most power of the
* error noise into inaudible frequency ranges */
static const gdouble ns_high_coeffs[] = {
2.08484, -2.92975, 3.27918, -3.31399, 2.61339, -1.72008, 0.876066, -0.340122
-0.340122, 0.876066, -1.72008, 2.61339, -3.31399, 3.27918, -2.92975, 2.08484,
};
#define INIT_NS_HIGH() \
gdouble orig; \
gdouble *errors = quant->error_buf, cur_error; \
int j;
#define ADD_NS_HIGH() \
cur_error = 0.0; \
for (j = 0; j < 8; j++) \
cur_error += errors[chan_pos*8 + j] * ns_high_coeffs[j]; \
tmp -= cur_error; \
orig = tmp;
#define UPDATE_ERROR_HIGH() \
for (j = 7; j > 0; j--) \
errors[chan_pos*8 + j] = errors[chan_pos*8 + j-1]; \
errors[chan_pos*8] = (d)/factor - orig;
MAKE_QUANTIZE_FUNC_I (int_none_none, NONE_FUNC, NONE_FUNC, ROUND);
MAKE_QUANTIZE_FUNC_I (int_rpdf_none, INIT_DITHER_RPDF_I, ADD_DITHER_RPDF_I,
NONE_FUNC);
MAKE_QUANTIZE_FUNC_I (int_tpdf_none, INIT_DITHER_TPDF_I, ADD_DITHER_TPDF_I,
NONE_FUNC);
MAKE_QUANTIZE_FUNC_I (int_tpdf_hf_none, INIT_DITHER_TPDF_HF_I,
ADD_DITHER_TPDF_HF_I, NONE_FUNC);
MAKE_QUANTIZE_FUNC_F (float_none_none, NONE_FUNC,
NONE_FUNC, NONE_FUNC, NONE_FUNC, NONE_FUNC);
MAKE_QUANTIZE_FUNC_F (float_none_error_feedback, NONE_FUNC,
INIT_NS_ERROR_FEEDBACK, ADD_NS_ERROR_FEEDBACK, NONE_FUNC,
UPDATE_ERROR_ERROR_FEEDBACK);
MAKE_QUANTIZE_FUNC_F (float_none_simple, NONE_FUNC, INIT_NS_SIMPLE,
ADD_NS_SIMPLE, NONE_FUNC, UPDATE_ERROR_SIMPLE);
MAKE_QUANTIZE_FUNC_F (float_none_medium, NONE_FUNC, INIT_NS_MEDIUM,
ADD_NS_MEDIUM, NONE_FUNC, UPDATE_ERROR_MEDIUM);
MAKE_QUANTIZE_FUNC_F (float_none_high, NONE_FUNC, INIT_NS_HIGH, ADD_NS_HIGH,
NONE_FUNC, UPDATE_ERROR_HIGH);
MAKE_QUANTIZE_FUNC_F (float_rpdf_none, INIT_DITHER_RPDF_F,
NONE_FUNC, NONE_FUNC, ADD_DITHER_RPDF_F, NONE_FUNC);
MAKE_QUANTIZE_FUNC_F (float_rpdf_error_feedback, INIT_DITHER_RPDF_F,
INIT_NS_ERROR_FEEDBACK, ADD_NS_ERROR_FEEDBACK, ADD_DITHER_RPDF_F,
UPDATE_ERROR_ERROR_FEEDBACK);
MAKE_QUANTIZE_FUNC_F (float_rpdf_simple, INIT_DITHER_RPDF_F, INIT_NS_SIMPLE,
ADD_NS_SIMPLE, ADD_DITHER_RPDF_F, UPDATE_ERROR_SIMPLE);
MAKE_QUANTIZE_FUNC_F (float_rpdf_medium, INIT_DITHER_RPDF_F, INIT_NS_MEDIUM,
ADD_NS_MEDIUM, ADD_DITHER_RPDF_F, UPDATE_ERROR_MEDIUM);
MAKE_QUANTIZE_FUNC_F (float_rpdf_high, INIT_DITHER_RPDF_F, INIT_NS_HIGH,
ADD_NS_HIGH, ADD_DITHER_RPDF_F, UPDATE_ERROR_HIGH);
MAKE_QUANTIZE_FUNC_F (float_tpdf_none, INIT_DITHER_TPDF_F,
NONE_FUNC, NONE_FUNC, ADD_DITHER_TPDF_F, NONE_FUNC);
MAKE_QUANTIZE_FUNC_F (float_tpdf_error_feedback, INIT_DITHER_TPDF_F,
INIT_NS_ERROR_FEEDBACK, ADD_NS_ERROR_FEEDBACK, ADD_DITHER_TPDF_F,
UPDATE_ERROR_ERROR_FEEDBACK);
MAKE_QUANTIZE_FUNC_F (float_tpdf_simple, INIT_DITHER_TPDF_F, INIT_NS_SIMPLE,
ADD_NS_SIMPLE, ADD_DITHER_TPDF_F, UPDATE_ERROR_SIMPLE);
MAKE_QUANTIZE_FUNC_F (float_tpdf_medium, INIT_DITHER_TPDF_F, INIT_NS_MEDIUM,
ADD_NS_MEDIUM, ADD_DITHER_TPDF_F, UPDATE_ERROR_MEDIUM);
MAKE_QUANTIZE_FUNC_F (float_tpdf_high, INIT_DITHER_TPDF_F, INIT_NS_HIGH,
ADD_NS_HIGH, ADD_DITHER_TPDF_F, UPDATE_ERROR_HIGH);
MAKE_QUANTIZE_FUNC_F (float_tpdf_hf_none, INIT_DITHER_TPDF_HF_F,
NONE_FUNC, NONE_FUNC, ADD_DITHER_TPDF_HF_F, NONE_FUNC);
MAKE_QUANTIZE_FUNC_F (float_tpdf_hf_error_feedback, INIT_DITHER_TPDF_HF_F,
INIT_NS_ERROR_FEEDBACK, ADD_NS_ERROR_FEEDBACK, ADD_DITHER_TPDF_HF_F,
UPDATE_ERROR_ERROR_FEEDBACK);
MAKE_QUANTIZE_FUNC_F (float_tpdf_hf_simple, INIT_DITHER_TPDF_HF_F,
INIT_NS_SIMPLE, ADD_NS_SIMPLE, ADD_DITHER_TPDF_HF_F, UPDATE_ERROR_SIMPLE);
MAKE_QUANTIZE_FUNC_F (float_tpdf_hf_medium, INIT_DITHER_TPDF_HF_F,
INIT_NS_MEDIUM, ADD_NS_MEDIUM, ADD_DITHER_TPDF_HF_F, UPDATE_ERROR_MEDIUM);
MAKE_QUANTIZE_FUNC_F (float_tpdf_hf_high, INIT_DITHER_TPDF_HF_F, INIT_NS_HIGH,
ADD_NS_HIGH, ADD_DITHER_TPDF_HF_F, UPDATE_ERROR_HIGH);
static const QuantizeFunc quantize_funcs[] = {
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_none_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_rpdf_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_tpdf_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (int_tpdf_hf_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_none_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_none_error_feedback),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_none_simple),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_none_medium),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_none_high),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_rpdf_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_rpdf_error_feedback),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_rpdf_simple),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_rpdf_medium),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_rpdf_high),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_error_feedback),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_simple),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_medium),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_high),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_hf_none),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_hf_error_feedback),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_hf_simple),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_hf_medium),
(QuantizeFunc) MAKE_QUANTIZE_FUNC_NAME (float_tpdf_hf_high)
};
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:{
quant->error_buf = g_new0 (gdouble, quant->channels * 8);
case GST_AUDIO_NOISE_SHAPING_HIGH:
n_coeffs = 8;
coeffs = ns_high_coeffs;
break;
}
case GST_AUDIO_NOISE_SHAPING_MEDIUM:{
quant->error_buf = g_new0 (gdouble, quant->channels * 5);
case GST_AUDIO_NOISE_SHAPING_MEDIUM:
n_coeffs = 5;
coeffs = ns_medium_coeffs;
break;
}
case GST_AUDIO_NOISE_SHAPING_SIMPLE:{
quant->error_buf = g_new0 (gdouble, quant->channels * 2);
case GST_AUDIO_NOISE_SHAPING_SIMPLE:
n_coeffs = 2;
coeffs = ns_simple_coeffs;
break;
}
case GST_AUDIO_NOISE_SHAPING_ERROR_FEEDBACK:
quant->error_buf = g_new0 (gdouble, quant->channels);
break;
case GST_AUDIO_NOISE_SHAPING_NONE:
default:
quant->error_buf = NULL;
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;
}
@ -423,7 +352,7 @@ gst_audio_quantize_setup_dither (GstAudioQuantize * quant)
{
switch (quant->dither) {
case GST_AUDIO_DITHER_TPDF_HF:
quant->last_random = g_new0 (gdouble, quant->channels);
quant->last_random = g_new0 (gint32, quant->channels);
break;
case GST_AUDIO_DITHER_RPDF:
case GST_AUDIO_DITHER_TPDF:
@ -440,20 +369,28 @@ gst_audio_quantize_setup_dither (GstAudioQuantize * quant)
static void
gst_audio_quantize_setup_quantize_func (GstAudioQuantize * quant)
{
gint index = 0;
gint index;
if (quant->ns == GST_AUDIO_NOISE_SHAPING_NONE
&& quant->format == GST_AUDIO_FORMAT_S32) {
index += quant->dither;
} else {
g_assert (quant->format == GST_AUDIO_FORMAT_F64);
index += 4 + (5 * quant->dither);
index += quant->ns;
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
@ -465,6 +402,9 @@ gst_audio_quantize_setup_quantize_func (GstAudioQuantize * quant)
*
* 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 *
@ -474,6 +414,9 @@ gst_audio_quantize_new (GstAudioDitherMethod dither,
{
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;
@ -482,6 +425,13 @@ gst_audio_quantize_new (GstAudioDitherMethod dither,
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);
@ -498,7 +448,10 @@ gst_audio_quantize_new (GstAudioDitherMethod dither,
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);
@ -506,7 +459,11 @@ gst_audio_quantize_free (GstAudioQuantize * quant)
void
gst_audio_quantize_samples (GstAudioQuantize * quant,
gpointer data, guint samples)
const gpointer src, gpointer dst, guint samples)
{
quant->quantize (quant, data, data, 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);
}

View file

@ -84,6 +84,7 @@ GstAudioQuantize * gst_audio_quantize_new (GstAudioDitherMethod dither,
void gst_audio_quantize_free (GstAudioQuantize * quant);
void gst_audio_quantize_samples (GstAudioQuantize * quant,
gpointer data, guint samples);
const gpointer src,
gpointer dst, guint samples);
#endif /* __GST_AUDIO_QUANTIZE_H__ */