---
Hi,
This patch does a few things differently the previous patches.
For simple rectangular and triangular dither without filtering, the
conversion of LFG values to float is done as SIMD along with the
sample conversion.
I added SIMD for separate conversion of LFG values to float.
I added SIMD for sample conversion for use with the triangular dither
with high-pass filter.
I included the input scaling with the conversion functions. Having it
separate is not much advantage since the conversions use SIMD. This
also avoids copying the whole frame if the input is not writable.
Thanks,
Justin
libavresample/Makefile | 1 +
libavresample/audio_convert.c | 33 +++-
libavresample/audio_convert.h | 22 ++-
libavresample/avresample.h | 9 +
libavresample/dither.c | 464 +++++++++++++++++++++++++++++++++++++++
libavresample/dither.h | 94 ++++++++
libavresample/internal.h | 1 +
libavresample/options.c | 6 +
libavresample/utils.c | 10 +-
libavresample/version.h | 2 +-
libavresample/x86/Makefile | 2 +
libavresample/x86/dither.asm | 139 ++++++++++++
libavresample/x86/dither_init.c | 87 ++++++++
13 files changed, 858 insertions(+), 12 deletions(-)
create mode 100644 libavresample/dither.c
create mode 100644 libavresample/dither.h
create mode 100644 libavresample/x86/dither.asm
create mode 100644 libavresample/x86/dither_init.c
diff --git a/libavresample/Makefile b/libavresample/Makefile
index c0c20a9..6805280 100644
--- a/libavresample/Makefile
+++ b/libavresample/Makefile
@@ -8,6 +8,7 @@ OBJS = audio_convert.o
\
audio_data.o \
audio_mix.o \
audio_mix_matrix.o \
+ dither.o \
options.o \
resample.o \
utils.o \
diff --git a/libavresample/audio_convert.c b/libavresample/audio_convert.c
index dcf8a39..eb3bc1f 100644
--- a/libavresample/audio_convert.c
+++ b/libavresample/audio_convert.c
@@ -29,6 +29,8 @@
#include "libavutil/samplefmt.h"
#include "audio_convert.h"
#include "audio_data.h"
+#include "dither.h"
+#include "internal.h"
enum ConvFuncType {
CONV_FUNC_TYPE_FLAT,
@@ -46,6 +48,7 @@ typedef void (conv_func_deinterleave)(uint8_t **out, const
uint8_t *in, int len,
struct AudioConvert {
AVAudioResampleContext *avr;
+ DitherContext *dc;
enum AVSampleFormat in_fmt;
enum AVSampleFormat out_fmt;
int channels;
@@ -246,10 +249,18 @@ static void set_generic_function(AudioConvert *ac)
SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_DBL)
}
+void ff_audio_convert_free(AudioConvert **ac)
+{
+ if (!*ac)
+ return;
+ ff_dither_free(&(*ac)->dc);
+ av_freep(ac);
+}
+
AudioConvert *ff_audio_convert_alloc(AVAudioResampleContext *avr,
enum AVSampleFormat out_fmt,
enum AVSampleFormat in_fmt,
- int channels)
+ int channels, int sample_rate)
{
AudioConvert *ac;
int in_planar, out_planar;
@@ -263,6 +274,17 @@ AudioConvert
*ff_audio_convert_alloc(AVAudioResampleContext *avr,
ac->in_fmt = in_fmt;
ac->channels = channels;
+ if (avr->dither_method != AV_RESAMPLE_DITHER_NONE &&
+ av_get_packed_sample_fmt(out_fmt) == AV_SAMPLE_FMT_S16 &&
+ av_get_bytes_per_sample(in_fmt) > 2) {
+ ac->dc = ff_dither_alloc(avr, out_fmt, in_fmt, channels, sample_rate);
+ if (!ac->dc) {
+ av_free(ac);
+ return NULL;
+ }
+ return ac;
+ }
+
in_planar = av_sample_fmt_is_planar(in_fmt);
out_planar = av_sample_fmt_is_planar(out_fmt);
@@ -289,6 +311,15 @@ int ff_audio_convert(AudioConvert *ac, AudioData *out,
AudioData *in)
int use_generic = 1;
int len = in->nb_samples;
+ if (ac->dc) {
+ /* dithered conversion */
+ av_dlog(ac->avr, "%d samples - audio_convert: %s to %s (dithered)\n",
+ len, av_get_sample_fmt_name(ac->in_fmt),
+ av_get_sample_fmt_name(ac->out_fmt));
+
+ return ff_convert_dither(ac->dc, out, in);
+ }
+
/* determine whether to use the optimized function based on pointer and
samples alignment in both the input and output */
if (ac->has_optimized_func) {
diff --git a/libavresample/audio_convert.h b/libavresample/audio_convert.h
index bc27223..b8808f1 100644
--- a/libavresample/audio_convert.h
+++ b/libavresample/audio_convert.h
@@ -54,16 +54,26 @@ void ff_audio_convert_set_func(AudioConvert *ac, enum
AVSampleFormat out_fmt,
/**
* Allocate and initialize AudioConvert context for sample format conversion.
*
- * @param avr AVAudioResampleContext
- * @param out_fmt output sample format
- * @param in_fmt input sample format
- * @param channels number of channels
- * @return newly-allocated AudioConvert context
+ * @param avr AVAudioResampleContext
+ * @param out_fmt output sample format
+ * @param in_fmt input sample format
+ * @param channels number of channels
+ * @param sample_rate sample rate (used for dithering)
+ * @return newly-allocated AudioConvert context
*/
AudioConvert *ff_audio_convert_alloc(AVAudioResampleContext *avr,
enum AVSampleFormat out_fmt,
enum AVSampleFormat in_fmt,
- int channels);
+ int channels, int sample_rate);
+
+/**
+ * Free AudioConvert.
+ *
+ * The AudioConvert must have been previously allocated with
ff_audio_convert_alloc().
+ *
+ * @param ac AudioConvert struct
+ */
+void ff_audio_convert_free(AudioConvert **ac);
/**
* Convert audio data from one sample format to another.
diff --git a/libavresample/avresample.h b/libavresample/avresample.h
index affeeeb..fc7f138 100644
--- a/libavresample/avresample.h
+++ b/libavresample/avresample.h
@@ -119,6 +119,15 @@ enum AVResampleFilterType {
AV_RESAMPLE_FILTER_TYPE_KAISER, /**< Kaiser Windowed Sinc */
};
+enum AVResampleDitherMethod {
+ AV_RESAMPLE_DITHER_NONE, /**< Do not use dithering */
+ AV_RESAMPLE_DITHER_RECTANGULAR, /**< Rectangular Dither */
+ AV_RESAMPLE_DITHER_TRIANGULAR, /**< Triangular Dither*/
+ AV_RESAMPLE_DITHER_TRIANGULAR_HP, /**< Triangular Dither with High Pass
*/
+ AV_RESAMPLE_DITHER_TRIANGULAR_NS, /**< Triangular Dither with Noise
Shaping */
+ AV_RESAMPLE_DITHER_NB, /**< Number of dither types. Not part
of ABI. */
+};
+
/**
* Return the LIBAVRESAMPLE_VERSION_INT constant.
*/
diff --git a/libavresample/dither.c b/libavresample/dither.c
new file mode 100644
index 0000000..10bdc19
--- /dev/null
+++ b/libavresample/dither.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) 2012 Justin Ruggles <[email protected]>
+ *
+ * Triangular with Noise Shaping is based on opusfile.
+ * Copyright (c) 1994-2012 by the Xiph.Org Foundation and contributors
+ *
+ * This file is part of Libav.
+ *
+ * Libav is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Libav 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Dithered Audio Sample Quantization
+ *
+ * Converts from dbl, flt, or s32 to s16 using dithering.
+ */
+
+#include <math.h>
+#include <stdint.h>
+
+#include "libavutil/common.h"
+#include "libavutil/float_dsp.h"
+#include "libavutil/lfg.h"
+#include "libavutil/mem.h"
+#include "libavutil/samplefmt.h"
+#include "audio_convert.h"
+#include "dither.h"
+#include "internal.h"
+
+typedef struct DitherState {
+ int mute;
+ AVLFG lfg;
+ float dither_a[4];
+ float dither_b[4];
+} DitherState;
+
+struct DitherContext {
+ AVFloatDSPContext fdsp;
+ DitherDSPContext ddsp;
+ enum AVResampleDitherMethod method;
+
+ int mute_dither_threshold; // threshold for disabling dither
+ int mute_reset_threshold; // threshold for resetting noise shaping
+ const float *ns_coef_b; // noise shaping coeffs
+ const float *ns_coef_a; // noise shaping coeffs
+
+ DitherState *state; // dither states for each channel
+
+ AudioData *flt_data; // input data in fltp
+ AudioData *s16_data; // dithered output in s16p
+ AudioConvert *ac_in; // converter for input to fltp
+ AudioConvert *ac_out; // converter for s16p to s16 (if needed)
+
+ void (*quantize)(int16_t *dst, const float *src, void *dither, int len);
+ int samples_align;
+};
+
+/* mute threshold, in seconds */
+#define MUTE_THRESHOLD_SEC 0.000333
+
+/* scale factor for 16-bit output.
+ The signal is attenuated slightly to avoid clipping */
+#define S16_SCALE 32753.0f
+
+/* scale to convert lfg from INT_MIN/INT_MAX to -0.5/0.5 */
+#define LFG_SCALE (1.0f / (2.0f * INT32_MAX))
+
+/* noise shaping coefficients */
+
+static const float ns_48_coef_b[4] = {
+ 2.2374f, -0.7339f, -0.1251f, -0.6033f
+};
+
+static const float ns_48_coef_a[4] = {
+ 0.9030f, 0.0116f, -0.5853f, -0.2571f
+};
+
+static const float ns_44_coef_b[4] = {
+ 2.2061f, -0.4707f, -0.2534f, -0.6213f
+};
+
+static const float ns_44_coef_a[4] = {
+ 1.0587f, 0.0676f, -0.6054f, -0.2738f
+};
+
+static void quantize_int_rectangular_c(int16_t *dst, const float *src,
+ void *dither, int len)
+{
+ int i;
+ int *dither0 = dither;
+ for (i = 0; i < len; i++) {
+ float r = dither0[i] * LFG_SCALE;
+ dst[i] = av_clip_int16(lrintf(src[i] * S16_SCALE + r));
+ }
+}
+
+static void quantize_int_triangular_c(int16_t *dst, const float *src,
+ void *dither, int len)
+{
+ int i;
+ int *dither0 = dither;
+ int *dither1 = dither0 + len;
+
+ for (i = 0; i < len; i++) {
+ float r = dither0[i] * LFG_SCALE;
+ r += dither1[i] * LFG_SCALE;
+ dst[i] = av_clip_int16(lrintf(src[i] * S16_SCALE + r));
+ }
+}
+
+static void dither_int_to_float_c(float *dst, int *src0, int len)
+{
+ int i;
+ int *src1 = src0 + len;
+
+ for (i = 0; i < len; i++) {
+ float r = src0[i] * LFG_SCALE;
+ r += src1[i] * LFG_SCALE;
+ dst[i] = r;
+ }
+}
+
+static void quantize_float_c(int16_t *dst, const float *src, void *dither,
+ int len)
+{
+ int i;
+ float *dither0 = dither;
+
+ for (i = 0; i < len; i++)
+ dst[i] = av_clip_int16(lrintf(src[i] * S16_SCALE + dither0[i]));
+}
+
+#define SQRT_1_6 0.40824829046386301723f
+
+static void dither_highpass_filter(float *dst, float *src, float *state,
+ int len)
+{
+ int i;
+
+ /* filter is from libswresample in FFmpeg */
+ dst[0] = (-state[0] + 2 * state[1] - src[0]) * SQRT_1_6;
+ dst[1] = (-state[1] + 2 * src[0] - src[1]) * SQRT_1_6;
+ for (i = 2; i < len; i++)
+ dst[i] = (-src[i - 2] + 2 * src[i -1] - src[i]) * SQRT_1_6;
+ state[0] = src[len - 2];
+ state[1] = src[len - 1];
+}
+
+static void quantize_filtered(DitherContext *c, DitherState *state,
+ int16_t *dst, const float *src, float *dither,
+ int nb_samples)
+{
+ int i, j;
+
+ if (state->mute > c->mute_reset_threshold)
+ memset(state->dither_a, 0, sizeof(state->dither_a));
+
+ for (i = 0; i < nb_samples; i++) {
+ if (src[i] == 0) {
+ state->mute++;
+ if (state->mute > c->mute_dither_threshold)
+ dither[i] = 0;
+ } else {
+ state->mute = 0;
+ }
+ }
+
+ if (c->method == AV_RESAMPLE_DITHER_TRIANGULAR_HP) {
+ int dither_samples = FFALIGN(nb_samples, 16);
+
+ dither_highpass_filter(dither + dither_samples, dither,
state->dither_a,
+ dither_samples);
+ dither += dither_samples;
+
+ c->quantize(dst, src, dither, FFALIGN(nb_samples, c->samples_align));
+ } else if (c->method == AV_RESAMPLE_DITHER_TRIANGULAR_NS) {
+ for (i = 0; i < nb_samples; i++) {
+ float err = 0;
+ float sample = src[i] * S16_SCALE;
+
+ for (j = 0; j < 4; j++) {
+ err += c->ns_coef_b[j] * state->dither_b[j] -
+ c->ns_coef_a[j] * state->dither_a[j];
+ }
+ for (j = 3; j > 0; j--) {
+ state->dither_a[j] = state->dither_a[j - 1];
+ state->dither_b[j] = state->dither_b[j - 1];
+ }
+ state->dither_a[0] = err;
+ sample -= err;
+
+ dst[i] = av_clip_int16(lrintf(sample + dither[i]));
+
+ state->dither_b[0] = av_clipf(dst[i] - sample, -1.5f, 1.5f);
+ }
+ }
+}
+
+static int convert_samples(DitherContext *c, int16_t **dst, float * const *src,
+ int channels, int nb_samples)
+{
+ int ch, i, j;
+ unsigned int *dither_buf;
+ int dither_samples, lfg_per_sample;
+
+ if (c->method == AV_RESAMPLE_DITHER_RECTANGULAR ||
+ c->method == AV_RESAMPLE_DITHER_TRIANGULAR)
+ dither_samples = FFALIGN(nb_samples, c->samples_align);
+ else
+ dither_samples = FFALIGN(nb_samples, 16);
+
+ lfg_per_sample = c->method == AV_RESAMPLE_DITHER_RECTANGULAR ? 1 : 2;
+
+ dither_buf = av_mallocz(dither_samples * lfg_per_sample *
+ sizeof(*dither_buf));
+ if (!dither_buf)
+ return AVERROR(ENOMEM);
+
+ for (ch = 0; ch < channels; ch++) {
+ DitherState *state = &c->state[ch];
+
+ for (j = 0; j < lfg_per_sample; j++)
+ for (i = 0; i < nb_samples; i++)
+ dither_buf[j * dither_samples + i] =
av_lfg_get(&c->state[ch].lfg);
+
+ if (c->method == AV_RESAMPLE_DITHER_RECTANGULAR ||
+ c->method == AV_RESAMPLE_DITHER_TRIANGULAR) {
+ c->quantize(dst[ch], src[ch], (int *)dither_buf, dither_samples);
+ } else {
+ float *dither = (float *)dither_buf;
+ c->ddsp.dither_int_to_float(dither, (int *)dither_buf,
+ dither_samples);
+
+ quantize_filtered(c, state, dst[ch], src[ch], dither, nb_samples);
+ }
+ }
+ av_free(dither_buf);
+
+ return 0;
+}
+
+int ff_convert_dither(DitherContext *c, AudioData *dst, AudioData *src)
+{
+ int ret;
+ AudioData *flt_data;
+
+ /* output directly to dst if it is planar */
+ if (dst->sample_fmt == AV_SAMPLE_FMT_S16P)
+ c->s16_data = dst;
+ else {
+ /* make sure s16_data is large enough for the output */
+ ret = ff_audio_data_realloc(c->s16_data, src->nb_samples);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (src->sample_fmt != AV_SAMPLE_FMT_FLTP) {
+ /* make sure flt_data is large enough for the input */
+ ret = ff_audio_data_realloc(c->flt_data, src->nb_samples);
+ if (ret < 0)
+ return ret;
+ flt_data = c->flt_data;
+
+ /* convert input samples to fltp and scale to s16 range */
+ ret = ff_audio_convert(c->ac_in, flt_data, src);
+ if (ret < 0)
+ return ret;
+ } else {
+ flt_data = src;
+ }
+
+ /* check alignment and padding constraints */
+ if (c->method != AV_RESAMPLE_DITHER_TRIANGULAR_NS) {
+ int ptr_align = FFMIN(flt_data->ptr_align,
c->s16_data->ptr_align);
+ int samples_align = FFMIN(flt_data->samples_align,
c->s16_data->samples_align);
+ int aligned_len = FFALIGN(src->nb_samples, c->ddsp.samples_align);
+
+ if (!(ptr_align % c->ddsp.ptr_align) && samples_align >= aligned_len) {
+ c->quantize = c->ddsp.quantize;
+ c->samples_align = c->ddsp.samples_align;
+ } else {
+ switch (c->method) {
+ case AV_RESAMPLE_DITHER_RECTANGULAR:
+ c->quantize = quantize_int_rectangular_c;
+ break;
+ case AV_RESAMPLE_DITHER_TRIANGULAR:
+ c->quantize = quantize_int_triangular_c;
+ break;
+ case AV_RESAMPLE_DITHER_TRIANGULAR_HP:
+ c->quantize = quantize_float_c;
+ break;
+ }
+ c->samples_align = 1;
+ }
+ }
+
+ ret = convert_samples(c, (int16_t **)c->s16_data->data,
+ (float * const *)flt_data->data, src->channels,
+ src->nb_samples);
+ if (ret < 0)
+ return ret;
+
+ c->s16_data->nb_samples = src->nb_samples;
+
+ /* interleave output to dst if needed */
+ if (dst->sample_fmt == AV_SAMPLE_FMT_S16) {
+ ret = ff_audio_convert(c->ac_out, dst, c->s16_data);
+ if (ret < 0)
+ return ret;
+ } else
+ c->s16_data = NULL;
+
+ return 0;
+}
+
+void ff_dither_free(DitherContext **c)
+{
+ if (!*c)
+ return;
+ ff_audio_data_free(&(*c)->flt_data);
+ ff_audio_data_free(&(*c)->s16_data);
+ ff_audio_convert_free(&(*c)->ac_in);
+ ff_audio_convert_free(&(*c)->ac_out);
+ av_free((*c)->state);
+ av_freep(c);
+}
+
+static void dither_init(DitherDSPContext *ddsp,
+ enum AVResampleDitherMethod method)
+{
+ switch (method) {
+ case AV_RESAMPLE_DITHER_RECTANGULAR:
+ ddsp->quantize = quantize_int_rectangular_c;
+ break;
+ case AV_RESAMPLE_DITHER_TRIANGULAR:
+ ddsp->quantize = quantize_int_triangular_c;
+ break;
+ case AV_RESAMPLE_DITHER_TRIANGULAR_HP:
+ ddsp->quantize = quantize_float_c;
+ case AV_RESAMPLE_DITHER_TRIANGULAR_NS:
+ ddsp->dither_int_to_float = dither_int_to_float_c;
+ break;
+ }
+ ddsp->ptr_align = 1;
+ ddsp->samples_align = 1;
+
+ if (ARCH_X86)
+ ff_dither_init_x86(ddsp, method);
+}
+
+DitherContext *ff_dither_alloc(AVAudioResampleContext *avr,
+ enum AVSampleFormat out_fmt,
+ enum AVSampleFormat in_fmt,
+ int channels, int sample_rate)
+{
+ AVLFG seed_gen;
+ DitherContext *c;
+ int ch;
+
+ if (av_get_packed_sample_fmt(out_fmt) != AV_SAMPLE_FMT_S16 ||
+ av_get_bytes_per_sample(in_fmt) <= 2) {
+ av_log(avr, AV_LOG_ERROR, "dithering %s to %s is not supported\n",
+ av_get_sample_fmt_name(in_fmt),
av_get_sample_fmt_name(out_fmt));
+ return NULL;
+ }
+
+ c = av_mallocz(sizeof(*c));
+ if (!c)
+ return NULL;
+
+ if (avr->dither_method == AV_RESAMPLE_DITHER_TRIANGULAR_NS &&
+ sample_rate != 48000 && sample_rate != 44100) {
+ av_log(avr, AV_LOG_WARNING, "sample rate must be 48000 or 44100 Hz "
+ "for triangular_ns dither. using triangular_hp instead.\n");
+ avr->dither_method = AV_RESAMPLE_DITHER_TRIANGULAR_HP;
+ }
+ c->method = avr->dither_method;
+ dither_init(&c->ddsp, c->method);
+
+ if (c->method == AV_RESAMPLE_DITHER_TRIANGULAR_NS) {
+ if (sample_rate == 48000) {
+ c->ns_coef_b = ns_48_coef_b;
+ c->ns_coef_a = ns_48_coef_a;
+ } else {
+ c->ns_coef_b = ns_44_coef_b;
+ c->ns_coef_a = ns_44_coef_a;
+ }
+ }
+
+ c->flt_data = ff_audio_data_alloc(channels, 1024, AV_SAMPLE_FMT_FLTP,
+ "dither flt buffer");
+ if (!c->flt_data)
+ goto fail;
+
+ /* Either s16 or s16p output format is allowed, but s16p is used
+ internally, so we need to use a temp buffer and interleave if the output
+ format is s16 */
+ if (out_fmt != AV_SAMPLE_FMT_S16P) {
+ c->s16_data = ff_audio_data_alloc(channels, 1024, AV_SAMPLE_FMT_S16P,
+ "dither s16 buffer");
+ if (!c->s16_data)
+ goto fail;
+
+ c->ac_out = ff_audio_convert_alloc(avr, out_fmt, AV_SAMPLE_FMT_S16P,
+ channels, sample_rate);
+ if (!c->ac_out)
+ goto fail;
+ }
+
+ if (in_fmt != AV_SAMPLE_FMT_FLTP) {
+ c->ac_in = ff_audio_convert_alloc(avr, AV_SAMPLE_FMT_FLTP, in_fmt,
+ channels, sample_rate);
+ if (!c->ac_in)
+ goto fail;
+ }
+
+ c->state = av_mallocz(channels * sizeof(*c->state));
+ if (!c->state) {
+ av_free(c);
+ return NULL;
+ }
+
+ /* calculate thresholds for turning off dithering during periods of
+ silence to avoid replacing digital silence with quiet dither noise */
+ c->mute_dither_threshold = lrintf(sample_rate * MUTE_THRESHOLD_SEC);
+ c->mute_reset_threshold = c->mute_dither_threshold * 4;
+
+ /* initialize dither states */
+ av_lfg_init(&seed_gen, 0xC0FFEE);
+ for (ch = 0; ch < channels; ch++) {
+ DitherState *state = &c->state[ch];
+ state->mute = c->mute_reset_threshold + 1;
+ av_lfg_init(&state->lfg, av_lfg_get(&seed_gen));
+ /* prime the noise buffer for triangular high pass */
+ if (c->method == AV_RESAMPLE_DITHER_TRIANGULAR_HP) {
+ int i;
+ unsigned dither[4];
+ for (i = 0; i < 4; i++)
+ dither[i] = av_lfg_get(&state->lfg);
+ dither_int_to_float_c(state->dither_a, (int *)dither, 2);
+ }
+ }
+
+ avpriv_float_dsp_init(&c->fdsp, 0);
+
+ return c;
+
+fail:
+ ff_dither_free(&c);
+ return NULL;
+}
diff --git a/libavresample/dither.h b/libavresample/dither.h
new file mode 100644
index 0000000..eb7006e
--- /dev/null
+++ b/libavresample/dither.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012 Justin Ruggles <[email protected]>
+ *
+ * This file is part of Libav.
+ *
+ * Libav is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Libav 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVRESAMPLE_DITHER_H
+#define AVRESAMPLE_DITHER_H
+
+#include "avresample.h"
+#include "audio_data.h"
+
+typedef struct DitherContext DitherContext;
+
+typedef struct DitherDSPContext {
+ /**
+ * Convert samples from flt to s16 with added dither noise.
+ *
+ * @param dst destination float array, range -0.5 to 0.5
+ * @param src source int array, range INT_MIN to INT_MAX.
+ * @param dither dither noise array (float or int depending on dither
method)
+ * the array size depends on the dither method
+ * @param len number of samples
+ */
+ void (*quantize)(int16_t *dst, const float *src, void *dither, int len);
+
+ int ptr_align; ///< src and dst constraits for quantize()
+ int samples_align; ///< len constraits for quantize()
+
+ /**
+ * Convert dither noise from int to float with triangular distribution.
+ *
+ * @param dst destination float array, range -0.5 to 0.5
+ * constraints: 32-byte aligned
+ * @param src0 source int array, range INT_MIN to INT_MAX.
+ * the array size is len * 2
+ * constraints: 32-byte aligned
+ * @param len number of output noise samples
+ * constraints: multiple of 16
+ */
+ void (*dither_int_to_float)(float *dst, int *src0, int len);
+} DitherDSPContext;
+
+/**
+ * Allocate and initialize a DitherContext.
+ *
+ * The parameters in the AVAudioResampleContext are used to initialize the
+ * DitherContext.
+ *
+ * @param avr AVAudioResampleContext
+ * @return newly-allocated DitherContext
+ */
+DitherContext *ff_dither_alloc(AVAudioResampleContext *avr,
+ enum AVSampleFormat out_fmt,
+ enum AVSampleFormat in_fmt,
+ int channels, int sample_rate);
+
+/**
+ * Free a DitherContext.
+ *
+ * @param c DitherContext
+ */
+void ff_dither_free(DitherContext **c);
+
+/**
+ * Convert audio sample format with dithering.
+ *
+ * @param c DitherContext
+ * @param dst destination audio data
+ * @param src source audio data
+ * @return 0 if ok, negative AVERROR code on failure
+ */
+int ff_convert_dither(DitherContext *c, AudioData *dst, AudioData *src);
+
+/* arch-specific initialization functions */
+
+void ff_dither_init_x86(DitherDSPContext *ddsp,
+ enum AVResampleDitherMethod method);
+
+#endif /* AVRESAMPLE_DITHER_H */
diff --git a/libavresample/internal.h b/libavresample/internal.h
index 006b6fd..e76240c 100644
--- a/libavresample/internal.h
+++ b/libavresample/internal.h
@@ -74,6 +74,7 @@ struct AVAudioResampleContext {
ResampleContext *resample; /**< resampling context */
AudioMix *am; /**< channel mixing context */
enum AVMatrixEncoding matrix_encoding; /**< matrixed stereo encoding
*/
+ enum AVResampleDitherMethod dither_method; /**< dither method */
};
#endif /* AVRESAMPLE_INTERNAL_H */
diff --git a/libavresample/options.c b/libavresample/options.c
index 8f64370..1097f12 100644
--- a/libavresample/options.c
+++ b/libavresample/options.c
@@ -63,6 +63,12 @@ static const AVOption options[] = {
{ "blackman_nuttall", "Blackman Nuttall Windowed Sinc", 0,
AV_OPT_TYPE_CONST, { .i64 = AV_RESAMPLE_FILTER_TYPE_BLACKMAN_NUTTALL },
INT_MIN, INT_MAX, PARAM, "filter_type" },
{ "kaiser", "Kaiser Windowed Sinc", 0,
AV_OPT_TYPE_CONST, { .i64 = AV_RESAMPLE_FILTER_TYPE_KAISER },
INT_MIN, INT_MAX, PARAM, "filter_type" },
{ "kaiser_beta", "Kaiser Window Beta",
OFFSET(kaiser_beta), AV_OPT_TYPE_INT, { .i64 = 9 },
2, 16, PARAM },
+ { "dither_method", "Dither Method",
OFFSET(dither_method), AV_OPT_TYPE_INT, { .i64 =
AV_RESAMPLE_DITHER_NONE }, 0, AV_RESAMPLE_DITHER_NB-1, PARAM, "dither_method"},
+ {"none", "No Dithering", 0,
AV_OPT_TYPE_CONST, { .i64 = AV_RESAMPLE_DITHER_NONE }, INT_MIN,
INT_MAX, PARAM, "dither_method"},
+ {"rectangular", "Rectangular Dither", 0,
AV_OPT_TYPE_CONST, { .i64 = AV_RESAMPLE_DITHER_RECTANGULAR }, INT_MIN,
INT_MAX, PARAM, "dither_method"},
+ {"triangular", "Triangular Dither", 0,
AV_OPT_TYPE_CONST, { .i64 = AV_RESAMPLE_DITHER_TRIANGULAR }, INT_MIN,
INT_MAX, PARAM, "dither_method"},
+ {"triangular_hp", "Triangular Dither With High Pass", 0,
AV_OPT_TYPE_CONST, { .i64 = AV_RESAMPLE_DITHER_TRIANGULAR_HP }, INT_MIN,
INT_MAX, PARAM, "dither_method"},
+ {"triangular_ns", "Triangular Dither With Noise Shaping", 0,
AV_OPT_TYPE_CONST, { .i64 = AV_RESAMPLE_DITHER_TRIANGULAR_NS }, INT_MIN,
INT_MAX, PARAM, "dither_method"},
{ NULL },
};
diff --git a/libavresample/utils.c b/libavresample/utils.c
index 3fdeeb8..e46029f 100644
--- a/libavresample/utils.c
+++ b/libavresample/utils.c
@@ -142,7 +142,8 @@ int avresample_open(AVAudioResampleContext *avr)
/* setup contexts */
if (avr->in_convert_needed) {
avr->ac_in = ff_audio_convert_alloc(avr, avr->internal_sample_fmt,
- avr->in_sample_fmt,
avr->in_channels);
+ avr->in_sample_fmt,
avr->in_channels,
+ avr->in_sample_rate);
if (!avr->ac_in) {
ret = AVERROR(ENOMEM);
goto error;
@@ -155,7 +156,8 @@ int avresample_open(AVAudioResampleContext *avr)
else
src_fmt = avr->in_sample_fmt;
avr->ac_out = ff_audio_convert_alloc(avr, avr->out_sample_fmt, src_fmt,
- avr->out_channels);
+ avr->out_channels,
+ avr->out_sample_rate);
if (!avr->ac_out) {
ret = AVERROR(ENOMEM);
goto error;
@@ -188,8 +190,8 @@ void avresample_close(AVAudioResampleContext *avr)
ff_audio_data_free(&avr->out_buffer);
av_audio_fifo_free(avr->out_fifo);
avr->out_fifo = NULL;
- av_freep(&avr->ac_in);
- av_freep(&avr->ac_out);
+ ff_audio_convert_free(&avr->ac_in);
+ ff_audio_convert_free(&avr->ac_out);
ff_audio_resample_free(&avr->resample);
ff_audio_mix_close(avr->am);
return;
diff --git a/libavresample/version.h b/libavresample/version.h
index 53ba802..295cddb 100644
--- a/libavresample/version.h
+++ b/libavresample/version.h
@@ -21,7 +21,7 @@
#define LIBAVRESAMPLE_VERSION_MAJOR 1
#define LIBAVRESAMPLE_VERSION_MINOR 0
-#define LIBAVRESAMPLE_VERSION_MICRO 0
+#define LIBAVRESAMPLE_VERSION_MICRO 1
#define LIBAVRESAMPLE_VERSION_INT AV_VERSION_INT(LIBAVRESAMPLE_VERSION_MAJOR,
\
LIBAVRESAMPLE_VERSION_MINOR,
\
diff --git a/libavresample/x86/Makefile b/libavresample/x86/Makefile
index 65bed89..2e8786f 100644
--- a/libavresample/x86/Makefile
+++ b/libavresample/x86/Makefile
@@ -1,5 +1,7 @@
OBJS += x86/audio_convert_init.o \
x86/audio_mix_init.o \
+ x86/dither_init.o \
YASM-OBJS += x86/audio_convert.o \
x86/audio_mix.o \
+ x86/dither.o \
diff --git a/libavresample/x86/dither.asm b/libavresample/x86/dither.asm
new file mode 100644
index 0000000..808798c
--- /dev/null
+++ b/libavresample/x86/dither.asm
@@ -0,0 +1,139 @@
+;******************************************************************************
+;* x86 optimized dithering format conversion
+;* Copyright (c) 2012 Justin Ruggles <[email protected]>
+;*
+;* This file is part of Libav.
+;*
+;* Libav is free software; you can redistribute it and/or
+;* modify it under the terms of the GNU Lesser General Public
+;* License as published by the Free Software Foundation; either
+;* version 2.1 of the License, or (at your option) any later version.
+;*
+;* Libav 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
+;* Lesser General Public License for more details.
+;*
+;* You should have received a copy of the GNU Lesser General Public
+;* License along with Libav; if not, write to the Free Software
+;* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;******************************************************************************
+
+%include "libavutil/x86/x86util.asm"
+
+SECTION_RODATA 32
+
+; 1.0f / (2.0f * INT32_MAX)
+pf_dither_scale: times 8 dd 2.32830643762e-10
+
+pf_s16_scale: times 4 dd 32753.0
+
+pf_dither_coeffs: dd 0, -0.408248290464, 0.816496580928, -0.408248290464
+
+SECTION_TEXT
+
+;------------------------------------------------------------------------------
+; void ff_quantize_int_rectangular/triangular(int16_t *dst, float *src,
+; int *dither, int len);
+;------------------------------------------------------------------------------
+
+%macro QUANTIZE_INT 1
+cglobal quantize_int_%1, 4,5,6, dst, src, dither0, len, dither1
+ lea lenq, [2*lend]
+ add dstq, lenq
+ lea srcq, [srcq+2*lenq]
+%ifidn %1, triangular
+ lea dither1q, [dither0q+4*lenq]
+%endif
+ lea dither0q, [dither0q+2*lenq]
+ neg lenq
+ mova m4, [pf_dither_scale]
+ mova m5, [pf_s16_scale]
+.loop:
+ cvtdq2ps m0, [dither0q+2*lenq]
+ cvtdq2ps m1, [dither0q+2*lenq+mmsize]
+%ifidn %1, triangular
+ cvtdq2ps m2, [dither1q+2*lenq]
+ cvtdq2ps m3, [dither1q+2*lenq+mmsize]
+ addps m0, m2
+ addps m1, m3
+%endif
+ mulps m2, m5, [srcq+2*lenq]
+ mulps m3, m5, [srcq+2*lenq+mmsize]
+ fmaddps m0, m0, m4, m2, m3
+ fmaddps m1, m1, m4, m3, m2
+ cvtps2dq m0, m0
+ cvtps2dq m1, m1
+ packssdw m0, m1
+ mova [dstq+lenq], m0
+ add lenq, mmsize
+ jl .loop
+ REP_RET
+%endmacro
+
+INIT_XMM sse2
+QUANTIZE_INT rectangular
+QUANTIZE_INT triangular
+%if HAVE_FMA4_EXTERNAL
+INIT_XMM fma4
+QUANTIZE_INT rectangular
+QUANTIZE_INT triangular
+%endif
+
+;------------------------------------------------------------------------------
+; void ff_quantize_float(int16_t *dst, float *src, float *dither, int len);
+;------------------------------------------------------------------------------
+
+INIT_XMM sse2
+cglobal quantize_float, 4,4,3, dst, src, dither, len
+ lea lenq, [2*lend]
+ add dstq, lenq
+ lea srcq, [srcq+2*lenq]
+ lea ditherq, [ditherq+2*lenq]
+ neg lenq
+ mova m2, [pf_s16_scale]
+.loop:
+ mulps m0, m2, [srcq+2*lenq]
+ mulps m1, m2, [srcq+2*lenq+mmsize]
+ addps m0, [ditherq+2*lenq]
+ addps m1, [ditherq+2*lenq+mmsize]
+ cvtps2dq m0, m0
+ cvtps2dq m1, m1
+ packssdw m0, m1
+ mova [dstq+lenq], m0
+ add lenq, mmsize
+ jl .loop
+ REP_RET
+
+;------------------------------------------------------------------------------
+; void ff_dither_int_to_float(float *dst, int *src0, int len)
+;------------------------------------------------------------------------------
+
+%macro DITHER_INT_TO_FLOAT 0
+cglobal dither_int_to_float, 3,4,5, dst, src0, len, src1
+ lea lenq, [4*lend]
+ lea src1q, [src0q+2*lenq]
+ add src0q, lenq
+ add dstq, lenq
+ neg lenq
+ mova m0, [pf_dither_scale]
+.loop:
+ cvtdq2ps m1, [src0q+lenq]
+ cvtdq2ps m2, [src0q+lenq+mmsize]
+ cvtdq2ps m3, [src1q+lenq]
+ cvtdq2ps m4, [src1q+lenq+mmsize]
+ addps m1, m1, m3
+ addps m2, m2, m4
+ mulps m1, m1, m0
+ mulps m2, m2, m0
+ mova [dstq+lenq], m1
+ mova [dstq+lenq+mmsize], m2
+ add lenq, 2*mmsize
+ jl .loop
+ REP_RET
+%endmacro
+
+INIT_XMM sse2
+DITHER_INT_TO_FLOAT
+INIT_YMM avx
+DITHER_INT_TO_FLOAT
diff --git a/libavresample/x86/dither_init.c b/libavresample/x86/dither_init.c
new file mode 100644
index 0000000..6c77c1e
--- /dev/null
+++ b/libavresample/x86/dither_init.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2012 Justin Ruggles <[email protected]>
+ *
+ * This file is part of Libav.
+ *
+ * Libav is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Libav 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "libavutil/cpu.h"
+#include "libavutil/x86/cpu.h"
+#include "libavresample/dither.h"
+
+extern void ff_quantize_int_rectangular_sse2(int16_t *dst, const float *src,
+ void *dither, int len);
+extern void ff_quantize_int_rectangular_fma4(int16_t *dst, const float *src,
+ void *dither, int len);
+
+extern void ff_quantize_int_triangular_sse2(int16_t *dst, const float *src,
+ void *dither, int len);
+extern void ff_quantize_int_triangular_fma4(int16_t *dst, const float *src,
+ void *dither, int len);
+
+extern void ff_quantize_float_sse2(int16_t *dst, const float *src, void
*dither,
+ int len);
+
+extern void ff_dither_int_to_float_sse2(float *dst, int *src0, int len);
+extern void ff_dither_int_to_float_avx(float *dst, int *src0, int len);
+
+av_cold void ff_dither_init_x86(DitherDSPContext *ddsp,
+ enum AVResampleDitherMethod method)
+{
+ int mm_flags = av_get_cpu_flags();
+
+ switch (method) {
+ case AV_RESAMPLE_DITHER_RECTANGULAR:
+ if (EXTERNAL_SSE2(mm_flags)) {
+ ddsp->quantize = ff_quantize_int_rectangular_sse2;
+ ddsp->ptr_align = 16;
+ ddsp->samples_align = 8;
+ }
+ if (EXTERNAL_FMA4(mm_flags)) {
+ ddsp->quantize = ff_quantize_int_rectangular_fma4;
+ ddsp->ptr_align = 16;
+ ddsp->samples_align = 8;
+ }
+ break;
+ case AV_RESAMPLE_DITHER_TRIANGULAR:
+ if (EXTERNAL_SSE2(mm_flags)) {
+ ddsp->quantize = ff_quantize_int_triangular_sse2;
+ ddsp->ptr_align = 16;
+ ddsp->samples_align = 8;
+ }
+ if (EXTERNAL_FMA4(mm_flags)) {
+ ddsp->quantize = ff_quantize_int_triangular_fma4;
+ ddsp->ptr_align = 16;
+ ddsp->samples_align = 8;
+ }
+ break;
+ case AV_RESAMPLE_DITHER_TRIANGULAR_HP:
+ if (EXTERNAL_SSE2(mm_flags)) {
+ ddsp->quantize = ff_quantize_float_sse2;
+ ddsp->ptr_align = 16;
+ ddsp->samples_align = 8;
+ }
+ case AV_RESAMPLE_DITHER_TRIANGULAR_NS:
+ if (EXTERNAL_SSE2(mm_flags)) {
+ ddsp->dither_int_to_float = ff_dither_int_to_float_sse2;
+ }
+ if (EXTERNAL_AVX(mm_flags)) {
+ ddsp->dither_int_to_float = ff_dither_int_to_float_avx;
+ }
+ break;
+ }
+}
--
1.7.1
_______________________________________________
libav-devel mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel