1
mirror of https://git.videolan.org/git/ffmpeg.git synced 2024-09-09 09:16:59 +02:00
ffmpeg/libavresample/resample.c
Justin Ruggles f1c2915ce1 lavr: remove automatic context close/open for resampling compensation
It adds unnecessary complication for insignificant usability improvement.
The user really should know if they'll need resampling compensation before
opening the context.

Note that only the documentation has changed. The current functionality will
still work until the next major bump.
2012-12-16 19:26:17 -05:00

469 lines
15 KiB
C

/*
* Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at>
* Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com>
*
* 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/common.h"
#include "libavutil/libm.h"
#include "libavutil/log.h"
#include "internal.h"
#include "audio_data.h"
struct ResampleContext {
AVAudioResampleContext *avr;
AudioData *buffer;
uint8_t *filter_bank;
int filter_length;
int ideal_dst_incr;
int dst_incr;
int index;
int frac;
int src_incr;
int compensation_distance;
int phase_shift;
int phase_mask;
int linear;
enum AVResampleFilterType filter_type;
int kaiser_beta;
double factor;
void (*set_filter)(void *filter, double *tab, int phase, int tap_count);
void (*resample_one)(struct ResampleContext *c, int no_filter, void *dst0,
int dst_index, const void *src0, int src_size,
int index, int frac);
};
/* double template */
#define CONFIG_RESAMPLE_DBL
#include "resample_template.c"
#undef CONFIG_RESAMPLE_DBL
/* float template */
#define CONFIG_RESAMPLE_FLT
#include "resample_template.c"
#undef CONFIG_RESAMPLE_FLT
/* s32 template */
#define CONFIG_RESAMPLE_S32
#include "resample_template.c"
#undef CONFIG_RESAMPLE_S32
/* s16 template */
#include "resample_template.c"
/* 0th order modified bessel function of the first kind. */
static double bessel(double x)
{
double v = 1;
double lastv = 0;
double t = 1;
int i;
x = x * x / 4;
for (i = 1; v != lastv; i++) {
lastv = v;
t *= x / (i * i);
v += t;
}
return v;
}
/* Build a polyphase filterbank. */
static int build_filter(ResampleContext *c)
{
int ph, i;
double x, y, w, factor;
double *tab;
int tap_count = c->filter_length;
int phase_count = 1 << c->phase_shift;
const int center = (tap_count - 1) / 2;
tab = av_malloc(tap_count * sizeof(*tab));
if (!tab)
return AVERROR(ENOMEM);
/* if upsampling, only need to interpolate, no filter */
factor = FFMIN(c->factor, 1.0);
for (ph = 0; ph < phase_count; ph++) {
double norm = 0;
for (i = 0; i < tap_count; i++) {
x = M_PI * ((double)(i - center) - (double)ph / phase_count) * factor;
if (x == 0) y = 1.0;
else y = sin(x) / x;
switch (c->filter_type) {
case AV_RESAMPLE_FILTER_TYPE_CUBIC: {
const float d = -0.5; //first order derivative = -0.5
x = fabs(((double)(i - center) - (double)ph / phase_count) * factor);
if (x < 1.0) y = 1 - 3 * x*x + 2 * x*x*x + d * ( -x*x + x*x*x);
else y = d * (-4 + 8 * x - 5 * x*x + x*x*x);
break;
}
case AV_RESAMPLE_FILTER_TYPE_BLACKMAN_NUTTALL:
w = 2.0 * x / (factor * tap_count) + M_PI;
y *= 0.3635819 - 0.4891775 * cos( w) +
0.1365995 * cos(2 * w) -
0.0106411 * cos(3 * w);
break;
case AV_RESAMPLE_FILTER_TYPE_KAISER:
w = 2.0 * x / (factor * tap_count * M_PI);
y *= bessel(c->kaiser_beta * sqrt(FFMAX(1 - w * w, 0)));
break;
}
tab[i] = y;
norm += y;
}
/* normalize so that an uniform color remains the same */
for (i = 0; i < tap_count; i++)
tab[i] = tab[i] / norm;
c->set_filter(c->filter_bank, tab, ph, tap_count);
}
av_free(tab);
return 0;
}
ResampleContext *ff_audio_resample_init(AVAudioResampleContext *avr)
{
ResampleContext *c;
int out_rate = avr->out_sample_rate;
int in_rate = avr->in_sample_rate;
double factor = FFMIN(out_rate * avr->cutoff / in_rate, 1.0);
int phase_count = 1 << avr->phase_shift;
int felem_size;
if (avr->internal_sample_fmt != AV_SAMPLE_FMT_S16P &&
avr->internal_sample_fmt != AV_SAMPLE_FMT_S32P &&
avr->internal_sample_fmt != AV_SAMPLE_FMT_FLTP &&
avr->internal_sample_fmt != AV_SAMPLE_FMT_DBLP) {
av_log(avr, AV_LOG_ERROR, "Unsupported internal format for "
"resampling: %s\n",
av_get_sample_fmt_name(avr->internal_sample_fmt));
return NULL;
}
c = av_mallocz(sizeof(*c));
if (!c)
return NULL;
c->avr = avr;
c->phase_shift = avr->phase_shift;
c->phase_mask = phase_count - 1;
c->linear = avr->linear_interp;
c->factor = factor;
c->filter_length = FFMAX((int)ceil(avr->filter_size / factor), 1);
c->filter_type = avr->filter_type;
c->kaiser_beta = avr->kaiser_beta;
switch (avr->internal_sample_fmt) {
case AV_SAMPLE_FMT_DBLP:
c->resample_one = resample_one_dbl;
c->set_filter = set_filter_dbl;
break;
case AV_SAMPLE_FMT_FLTP:
c->resample_one = resample_one_flt;
c->set_filter = set_filter_flt;
break;
case AV_SAMPLE_FMT_S32P:
c->resample_one = resample_one_s32;
c->set_filter = set_filter_s32;
break;
case AV_SAMPLE_FMT_S16P:
c->resample_one = resample_one_s16;
c->set_filter = set_filter_s16;
break;
}
felem_size = av_get_bytes_per_sample(avr->internal_sample_fmt);
c->filter_bank = av_mallocz(c->filter_length * (phase_count + 1) * felem_size);
if (!c->filter_bank)
goto error;
if (build_filter(c) < 0)
goto error;
memcpy(&c->filter_bank[(c->filter_length * phase_count + 1) * felem_size],
c->filter_bank, (c->filter_length - 1) * felem_size);
memcpy(&c->filter_bank[c->filter_length * phase_count * felem_size],
&c->filter_bank[(c->filter_length - 1) * felem_size], felem_size);
c->compensation_distance = 0;
if (!av_reduce(&c->src_incr, &c->dst_incr, out_rate,
in_rate * (int64_t)phase_count, INT32_MAX / 2))
goto error;
c->ideal_dst_incr = c->dst_incr;
c->index = -phase_count * ((c->filter_length - 1) / 2);
c->frac = 0;
/* allocate internal buffer */
c->buffer = ff_audio_data_alloc(avr->resample_channels, 0,
avr->internal_sample_fmt,
"resample buffer");
if (!c->buffer)
goto error;
av_log(avr, AV_LOG_DEBUG, "resample: %s from %d Hz to %d Hz\n",
av_get_sample_fmt_name(avr->internal_sample_fmt),
avr->in_sample_rate, avr->out_sample_rate);
return c;
error:
ff_audio_data_free(&c->buffer);
av_free(c->filter_bank);
av_free(c);
return NULL;
}
void ff_audio_resample_free(ResampleContext **c)
{
if (!*c)
return;
ff_audio_data_free(&(*c)->buffer);
av_free((*c)->filter_bank);
av_freep(c);
}
int avresample_set_compensation(AVAudioResampleContext *avr, int sample_delta,
int compensation_distance)
{
ResampleContext *c;
AudioData *fifo_buf = NULL;
int ret = 0;
if (compensation_distance < 0)
return AVERROR(EINVAL);
if (!compensation_distance && sample_delta)
return AVERROR(EINVAL);
if (!avr->resample_needed) {
#if FF_API_RESAMPLE_CLOSE_OPEN
/* if resampling was not enabled previously, re-initialize the
AVAudioResampleContext and force resampling */
int fifo_samples;
int restore_matrix = 0;
double matrix[AVRESAMPLE_MAX_CHANNELS * AVRESAMPLE_MAX_CHANNELS] = { 0 };
/* buffer any remaining samples in the output FIFO before closing */
fifo_samples = av_audio_fifo_size(avr->out_fifo);
if (fifo_samples > 0) {
fifo_buf = ff_audio_data_alloc(avr->out_channels, fifo_samples,
avr->out_sample_fmt, NULL);
if (!fifo_buf)
return AVERROR(EINVAL);
ret = ff_audio_data_read_from_fifo(avr->out_fifo, fifo_buf,
fifo_samples);
if (ret < 0)
goto reinit_fail;
}
/* save the channel mixing matrix */
if (avr->am) {
ret = avresample_get_matrix(avr, matrix, AVRESAMPLE_MAX_CHANNELS);
if (ret < 0)
goto reinit_fail;
restore_matrix = 1;
}
/* close the AVAudioResampleContext */
avresample_close(avr);
avr->force_resampling = 1;
/* restore the channel mixing matrix */
if (restore_matrix) {
ret = avresample_set_matrix(avr, matrix, AVRESAMPLE_MAX_CHANNELS);
if (ret < 0)
goto reinit_fail;
}
/* re-open the AVAudioResampleContext */
ret = avresample_open(avr);
if (ret < 0)
goto reinit_fail;
/* restore buffered samples to the output FIFO */
if (fifo_samples > 0) {
ret = ff_audio_data_add_to_fifo(avr->out_fifo, fifo_buf, 0,
fifo_samples);
if (ret < 0)
goto reinit_fail;
ff_audio_data_free(&fifo_buf);
}
#else
av_log(avr, AV_LOG_ERROR, "Unable to set resampling compensation\n");
return AVERROR(EINVAL);
#endif
}
c = avr->resample;
c->compensation_distance = compensation_distance;
if (compensation_distance) {
c->dst_incr = c->ideal_dst_incr - c->ideal_dst_incr *
(int64_t)sample_delta / compensation_distance;
} else {
c->dst_incr = c->ideal_dst_incr;
}
return 0;
reinit_fail:
ff_audio_data_free(&fifo_buf);
return ret;
}
static int resample(ResampleContext *c, void *dst, const void *src,
int *consumed, int src_size, int dst_size, int update_ctx)
{
int dst_index;
int index = c->index;
int frac = c->frac;
int dst_incr_frac = c->dst_incr % c->src_incr;
int dst_incr = c->dst_incr / c->src_incr;
int compensation_distance = c->compensation_distance;
if (!dst != !src)
return AVERROR(EINVAL);
if (compensation_distance == 0 && c->filter_length == 1 &&
c->phase_shift == 0) {
int64_t index2 = ((int64_t)index) << 32;
int64_t incr = (1LL << 32) * c->dst_incr / c->src_incr;
dst_size = FFMIN(dst_size,
(src_size-1-index) * (int64_t)c->src_incr /
c->dst_incr);
if (dst) {
for(dst_index = 0; dst_index < dst_size; dst_index++) {
c->resample_one(c, 1, dst, dst_index, src, 0, index2 >> 32, 0);
index2 += incr;
}
} else {
dst_index = dst_size;
}
index += dst_index * dst_incr;
index += (frac + dst_index * (int64_t)dst_incr_frac) / c->src_incr;
frac = (frac + dst_index * (int64_t)dst_incr_frac) % c->src_incr;
} else {
for (dst_index = 0; dst_index < dst_size; dst_index++) {
int sample_index = index >> c->phase_shift;
if (sample_index + c->filter_length > src_size ||
-sample_index >= src_size)
break;
if (dst)
c->resample_one(c, 0, dst, dst_index, src, src_size, index, frac);
frac += dst_incr_frac;
index += dst_incr;
if (frac >= c->src_incr) {
frac -= c->src_incr;
index++;
}
if (dst_index + 1 == compensation_distance) {
compensation_distance = 0;
dst_incr_frac = c->ideal_dst_incr % c->src_incr;
dst_incr = c->ideal_dst_incr / c->src_incr;
}
}
}
if (consumed)
*consumed = FFMAX(index, 0) >> c->phase_shift;
if (update_ctx) {
if (index >= 0)
index &= c->phase_mask;
if (compensation_distance) {
compensation_distance -= dst_index;
if (compensation_distance <= 0)
return AVERROR_BUG;
}
c->frac = frac;
c->index = index;
c->dst_incr = dst_incr_frac + c->src_incr*dst_incr;
c->compensation_distance = compensation_distance;
}
return dst_index;
}
int ff_audio_resample(ResampleContext *c, AudioData *dst, AudioData *src)
{
int ch, in_samples, in_leftover, consumed = 0, out_samples = 0;
int ret = AVERROR(EINVAL);
in_samples = src ? src->nb_samples : 0;
in_leftover = c->buffer->nb_samples;
/* add input samples to the internal buffer */
if (src) {
ret = ff_audio_data_combine(c->buffer, in_leftover, src, 0, in_samples);
if (ret < 0)
return ret;
} else if (!in_leftover) {
/* no remaining samples to flush */
return 0;
} else {
/* TODO: pad buffer to flush completely */
}
/* calculate output size and reallocate output buffer if needed */
/* TODO: try to calculate this without the dummy resample() run */
if (!dst->read_only && dst->allow_realloc) {
out_samples = resample(c, NULL, NULL, NULL, c->buffer->nb_samples,
INT_MAX, 0);
ret = ff_audio_data_realloc(dst, out_samples);
if (ret < 0) {
av_log(c->avr, AV_LOG_ERROR, "error reallocating output\n");
return ret;
}
}
/* resample each channel plane */
for (ch = 0; ch < c->buffer->channels; ch++) {
out_samples = resample(c, (void *)dst->data[ch],
(const void *)c->buffer->data[ch], &consumed,
c->buffer->nb_samples, dst->allocated_samples,
ch + 1 == c->buffer->channels);
}
if (out_samples < 0) {
av_log(c->avr, AV_LOG_ERROR, "error during resampling\n");
return out_samples;
}
/* drain consumed samples from the internal buffer */
ff_audio_data_drain(c->buffer, consumed);
av_dlog(c->avr, "resampled %d in + %d leftover to %d out + %d leftover\n",
in_samples, in_leftover, out_samples, c->buffer->nb_samples);
dst->nb_samples = out_samples;
return 0;
}
int avresample_get_delay(AVAudioResampleContext *avr)
{
if (!avr->resample_needed || !avr->resample)
return 0;
return avr->resample->buffer->nb_samples;
}