|
|
|
@ -24,20 +24,14 @@ |
|
|
|
|
* resampling audio filter |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
#include "libavutil/eval.h" |
|
|
|
|
#include "libavcodec/avcodec.h" |
|
|
|
|
#include "libswresample/swresample.h" |
|
|
|
|
#include "avfilter.h" |
|
|
|
|
#include "internal.h" |
|
|
|
|
|
|
|
|
|
typedef struct { |
|
|
|
|
struct AVResampleContext *resample; |
|
|
|
|
int out_rate; |
|
|
|
|
double ratio; |
|
|
|
|
AVFilterBufferRef *outsamplesref; |
|
|
|
|
int unconsumed_nb_samples, |
|
|
|
|
max_cached_nb_samples; |
|
|
|
|
int16_t *cached_data[8], |
|
|
|
|
*resampled_data[8]; |
|
|
|
|
struct SwrContext *swr; |
|
|
|
|
} AResampleContext; |
|
|
|
|
|
|
|
|
|
static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) |
|
|
|
@ -58,23 +52,12 @@ static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) |
|
|
|
|
static av_cold void uninit(AVFilterContext *ctx) |
|
|
|
|
{ |
|
|
|
|
AResampleContext *aresample = ctx->priv; |
|
|
|
|
if (aresample->outsamplesref) { |
|
|
|
|
int nb_channels = |
|
|
|
|
av_get_channel_layout_nb_channels( |
|
|
|
|
aresample->outsamplesref->audio->channel_layout); |
|
|
|
|
avfilter_unref_buffer(aresample->outsamplesref); |
|
|
|
|
while (nb_channels--) { |
|
|
|
|
av_freep(&(aresample->cached_data[nb_channels])); |
|
|
|
|
av_freep(&(aresample->resampled_data[nb_channels])); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (aresample->resample) |
|
|
|
|
av_resample_close(aresample->resample); |
|
|
|
|
swr_free(&aresample->swr); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int config_output(AVFilterLink *outlink) |
|
|
|
|
{ |
|
|
|
|
int ret; |
|
|
|
|
AVFilterContext *ctx = outlink->src; |
|
|
|
|
AVFilterLink *inlink = ctx->inputs[0]; |
|
|
|
|
AResampleContext *aresample = ctx->priv; |
|
|
|
@ -85,9 +68,16 @@ static int config_output(AVFilterLink *outlink) |
|
|
|
|
outlink->sample_rate = aresample->out_rate; |
|
|
|
|
outlink->time_base = (AVRational) {1, aresample->out_rate}; |
|
|
|
|
|
|
|
|
|
//TODO: make the resampling parameters configurable
|
|
|
|
|
aresample->resample = av_resample_init(aresample->out_rate, inlink->sample_rate, |
|
|
|
|
16, 10, 0, 0.8); |
|
|
|
|
//TODO: make the resampling parameters (filter size, phrase shift, linear, cutoff) configurable
|
|
|
|
|
aresample->swr = swr_alloc_set_opts(aresample->swr, |
|
|
|
|
inlink->channel_layout, inlink->format, aresample->out_rate, |
|
|
|
|
inlink->channel_layout, inlink->format, inlink->sample_rate, |
|
|
|
|
0, ctx); |
|
|
|
|
if (!aresample->swr) |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
ret = swr_init(aresample->swr); |
|
|
|
|
if (ret < 0) |
|
|
|
|
return ret; |
|
|
|
|
|
|
|
|
|
aresample->ratio = (double)outlink->sample_rate / inlink->sample_rate; |
|
|
|
|
|
|
|
|
@ -96,235 +86,24 @@ static int config_output(AVFilterLink *outlink) |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int query_formats(AVFilterContext *ctx) |
|
|
|
|
{ |
|
|
|
|
AVFilterFormats *formats = NULL; |
|
|
|
|
|
|
|
|
|
avfilter_add_format(&formats, AV_SAMPLE_FMT_S16); |
|
|
|
|
if (!formats) |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
avfilter_set_common_sample_formats(ctx, formats); |
|
|
|
|
|
|
|
|
|
formats = avfilter_make_all_channel_layouts(); |
|
|
|
|
if (!formats) |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
avfilter_set_common_channel_layouts(ctx, formats); |
|
|
|
|
|
|
|
|
|
formats = avfilter_make_all_packing_formats(); |
|
|
|
|
if (!formats) |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
avfilter_set_common_packing_formats(ctx, formats); |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void deinterleave(int16_t **outp, int16_t *in, |
|
|
|
|
int nb_channels, int nb_samples) |
|
|
|
|
{ |
|
|
|
|
int16_t *out[8]; |
|
|
|
|
memcpy(out, outp, nb_channels * sizeof(int16_t*)); |
|
|
|
|
|
|
|
|
|
switch (nb_channels) { |
|
|
|
|
case 2: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out[0]++ = *in++; |
|
|
|
|
*out[1]++ = *in++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 3: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out[0]++ = *in++; |
|
|
|
|
*out[1]++ = *in++; |
|
|
|
|
*out[2]++ = *in++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 4: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out[0]++ = *in++; |
|
|
|
|
*out[1]++ = *in++; |
|
|
|
|
*out[2]++ = *in++; |
|
|
|
|
*out[3]++ = *in++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 5: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out[0]++ = *in++; |
|
|
|
|
*out[1]++ = *in++; |
|
|
|
|
*out[2]++ = *in++; |
|
|
|
|
*out[3]++ = *in++; |
|
|
|
|
*out[4]++ = *in++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 6: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out[0]++ = *in++; |
|
|
|
|
*out[1]++ = *in++; |
|
|
|
|
*out[2]++ = *in++; |
|
|
|
|
*out[3]++ = *in++; |
|
|
|
|
*out[4]++ = *in++; |
|
|
|
|
*out[5]++ = *in++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 8: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out[0]++ = *in++; |
|
|
|
|
*out[1]++ = *in++; |
|
|
|
|
*out[2]++ = *in++; |
|
|
|
|
*out[3]++ = *in++; |
|
|
|
|
*out[4]++ = *in++; |
|
|
|
|
*out[5]++ = *in++; |
|
|
|
|
*out[6]++ = *in++; |
|
|
|
|
*out[7]++ = *in++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void interleave(int16_t *out, int16_t **inp, |
|
|
|
|
int nb_channels, int nb_samples) |
|
|
|
|
{ |
|
|
|
|
int16_t *in[8]; |
|
|
|
|
memcpy(in, inp, nb_channels * sizeof(int16_t*)); |
|
|
|
|
|
|
|
|
|
switch (nb_channels) { |
|
|
|
|
case 2: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out++ = *in[0]++; |
|
|
|
|
*out++ = *in[1]++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 3: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out++ = *in[0]++; |
|
|
|
|
*out++ = *in[1]++; |
|
|
|
|
*out++ = *in[2]++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 4: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out++ = *in[0]++; |
|
|
|
|
*out++ = *in[1]++; |
|
|
|
|
*out++ = *in[2]++; |
|
|
|
|
*out++ = *in[3]++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 5: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out++ = *in[0]++; |
|
|
|
|
*out++ = *in[1]++; |
|
|
|
|
*out++ = *in[2]++; |
|
|
|
|
*out++ = *in[3]++; |
|
|
|
|
*out++ = *in[4]++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 6: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out++ = *in[0]++; |
|
|
|
|
*out++ = *in[1]++; |
|
|
|
|
*out++ = *in[2]++; |
|
|
|
|
*out++ = *in[3]++; |
|
|
|
|
*out++ = *in[4]++; |
|
|
|
|
*out++ = *in[5]++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case 8: |
|
|
|
|
while (nb_samples--) { |
|
|
|
|
*out++ = *in[0]++; |
|
|
|
|
*out++ = *in[1]++; |
|
|
|
|
*out++ = *in[2]++; |
|
|
|
|
*out++ = *in[3]++; |
|
|
|
|
*out++ = *in[4]++; |
|
|
|
|
*out++ = *in[5]++; |
|
|
|
|
*out++ = *in[6]++; |
|
|
|
|
*out++ = *in[7]++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamplesref) |
|
|
|
|
{ |
|
|
|
|
AResampleContext *aresample = inlink->dst->priv; |
|
|
|
|
AVFilterLink * const outlink = inlink->dst->outputs[0]; |
|
|
|
|
int i, |
|
|
|
|
in_nb_samples = insamplesref->audio->nb_samples, |
|
|
|
|
cached_nb_samples = in_nb_samples + aresample->unconsumed_nb_samples, |
|
|
|
|
requested_out_nb_samples = aresample->ratio * cached_nb_samples, |
|
|
|
|
nb_channels = |
|
|
|
|
av_get_channel_layout_nb_channels(inlink->channel_layout); |
|
|
|
|
|
|
|
|
|
if (cached_nb_samples > aresample->max_cached_nb_samples) { |
|
|
|
|
for (i = 0; i < nb_channels; i++) { |
|
|
|
|
aresample->cached_data[i] = |
|
|
|
|
av_realloc(aresample->cached_data[i], cached_nb_samples * sizeof(int16_t)); |
|
|
|
|
aresample->resampled_data[i] = |
|
|
|
|
av_realloc(aresample->resampled_data[i], |
|
|
|
|
FFALIGN(sizeof(int16_t) * requested_out_nb_samples, 16)); |
|
|
|
|
|
|
|
|
|
if (aresample->cached_data[i] == NULL || aresample->resampled_data[i] == NULL) |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
aresample->max_cached_nb_samples = cached_nb_samples; |
|
|
|
|
|
|
|
|
|
if (aresample->outsamplesref) |
|
|
|
|
avfilter_unref_buffer(aresample->outsamplesref); |
|
|
|
|
|
|
|
|
|
aresample->outsamplesref = |
|
|
|
|
avfilter_get_audio_buffer(outlink, AV_PERM_WRITE, requested_out_nb_samples); |
|
|
|
|
outlink->out_buf = aresample->outsamplesref; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
avfilter_copy_buffer_ref_props(aresample->outsamplesref, insamplesref); |
|
|
|
|
aresample->outsamplesref->audio->sample_rate = outlink->sample_rate; |
|
|
|
|
aresample->outsamplesref->pts = |
|
|
|
|
av_rescale(outlink->sample_rate, insamplesref->pts, inlink->sample_rate); |
|
|
|
|
|
|
|
|
|
/* av_resample() works with planar audio buffers */ |
|
|
|
|
if (!inlink->planar && nb_channels > 1) { |
|
|
|
|
int16_t *out[8]; |
|
|
|
|
for (i = 0; i < nb_channels; i++) |
|
|
|
|
out[i] = aresample->cached_data[i] + aresample->unconsumed_nb_samples; |
|
|
|
|
|
|
|
|
|
deinterleave(out, (int16_t *)insamplesref->data[0], |
|
|
|
|
nb_channels, in_nb_samples); |
|
|
|
|
} else { |
|
|
|
|
for (i = 0; i < nb_channels; i++) |
|
|
|
|
memcpy(aresample->cached_data[i] + aresample->unconsumed_nb_samples, |
|
|
|
|
insamplesref->data[i], |
|
|
|
|
in_nb_samples * sizeof(int16_t)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (i = 0; i < nb_channels; i++) { |
|
|
|
|
int consumed_nb_samples; |
|
|
|
|
const int is_last = i+1 == nb_channels; |
|
|
|
|
|
|
|
|
|
aresample->outsamplesref->audio->nb_samples = |
|
|
|
|
av_resample(aresample->resample, |
|
|
|
|
aresample->resampled_data[i], aresample->cached_data[i], |
|
|
|
|
&consumed_nb_samples, |
|
|
|
|
cached_nb_samples, |
|
|
|
|
requested_out_nb_samples, is_last); |
|
|
|
|
|
|
|
|
|
/* move unconsumed data back to the beginning of the cache */ |
|
|
|
|
aresample->unconsumed_nb_samples = cached_nb_samples - consumed_nb_samples; |
|
|
|
|
memmove(aresample->cached_data[i], |
|
|
|
|
aresample->cached_data[i] + consumed_nb_samples, |
|
|
|
|
aresample->unconsumed_nb_samples * sizeof(int16_t)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* copy resampled data to the output samplesref */ |
|
|
|
|
if (!inlink->planar && nb_channels > 1) { |
|
|
|
|
interleave((int16_t *)aresample->outsamplesref->data[0], |
|
|
|
|
aresample->resampled_data, |
|
|
|
|
nb_channels, aresample->outsamplesref->audio->nb_samples); |
|
|
|
|
} else { |
|
|
|
|
for (i = 0; i < nb_channels; i++) |
|
|
|
|
memcpy(aresample->outsamplesref->data[i], aresample->resampled_data[i], |
|
|
|
|
aresample->outsamplesref->audio->nb_samples * sizeof(int16_t)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
avfilter_filter_samples(outlink, avfilter_ref_buffer(aresample->outsamplesref, ~0)); |
|
|
|
|
AResampleContext *aresample = inlink->dst->priv; |
|
|
|
|
const int n_in = insamplesref->audio->nb_samples; |
|
|
|
|
int n_out = n_in * aresample->ratio; |
|
|
|
|
AVFilterLink *const outlink = inlink->dst->outputs[0]; |
|
|
|
|
AVFilterBufferRef *outsamplesref = avfilter_get_audio_buffer(outlink, AV_PERM_WRITE, n_out); |
|
|
|
|
|
|
|
|
|
n_out = swr_convert(aresample->swr, outsamplesref->data, n_out, |
|
|
|
|
(void *)insamplesref->data, n_in); |
|
|
|
|
|
|
|
|
|
avfilter_copy_buffer_ref_props(outsamplesref, insamplesref); |
|
|
|
|
outsamplesref->audio->sample_rate = outlink->sample_rate; |
|
|
|
|
outsamplesref->audio->nb_samples = n_out; |
|
|
|
|
outsamplesref->pts = av_rescale(outlink->sample_rate, insamplesref->pts, |
|
|
|
|
inlink ->sample_rate); |
|
|
|
|
|
|
|
|
|
avfilter_filter_samples(outlink, outsamplesref); |
|
|
|
|
avfilter_unref_buffer(insamplesref); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -333,7 +112,6 @@ AVFilter avfilter_af_aresample = { |
|
|
|
|
.description = NULL_IF_CONFIG_SMALL("Resample audio data."), |
|
|
|
|
.init = init, |
|
|
|
|
.uninit = uninit, |
|
|
|
|
.query_formats = query_formats, |
|
|
|
|
.priv_size = sizeof(AResampleContext), |
|
|
|
|
|
|
|
|
|
.inputs = (const AVFilterPad[]) {{ .name = "default", |
|
|
|
|