音频应用有时遇到44.1kHz/48KHz/32kHz/16kHz以及8kHz之间互相转换,这一过程称为SRC(sample rate converter),产品上有用codec芯片硬件实现SRC功能,有用软件实现SRC。
采样率转换的基本思想是抽取和内插,从信号角度看音频重采样就是滤波。滤波函数的窗口大小以及插值函数一旦被确定,其重采样的性能也就确定了。
resample函数是一个数字信号处理中的重采样函数,它用于将一个原始信号的采样频率改变为另一个频率。重采样的目的是改变信号的频率,以便在信号处理过程中对其进行重新调整。
重采样的基本原理是通过使用一种称为插值的技术来生成新的采样点,从而改变信号的频率。插值方法可以是线性插值、插值多项式、插值样条等。
具体的,resample函数会对信号的每一个采样点进行处理,并通过计算与该采样点相邻的数据点的加权平均值来生成新的采样点。最终,它会返回一个新的信号,该信号具有指定的采样频率。
swresample是FFmpeg中的音频重采样库,它主要包括:
Cubic interpolation
是一种插值方法,用于在已知的离散数据点之间估计未知的数据点。它使用三次多项式来逼近数据点之间的曲线,从而得到更平滑的插值结果。Cubic interpolation通常用于图像处理和计算机图形学中,用于对图像进行缩放和旋转等操作。它的优点是插值结果平滑,缺点是可能会引入一些误差,特别是在数据点之间存在急剧变化的情况下。Blackman Nuttall windowed sinc interpolation
是一种数字信号处理中的插值方法,它使用Blackman Nuttall窗口函数对Sinc插值函数进行加窗处理,从而得到更好的频域和时域特性,用于在离散时间信号中进行高质量的插值处理。Kaiser windowed sinc interpolation
使用Kaiser窗口函数对Sinc插值函数进行加窗处理,从而得到更好的频域和时域特性,用于在离散时间信号中进行高质量的插值处理。Kaiser窗口函数是一种可调节的窗口函数,可以通过调节窗口函数的参数来平衡频域和时域特性,从而得到更好的插值效果。Kaiser windowed sinc interpolation
通常用于音频处理和数字信号处理中,用于对信号进行重采样和插值等操作。它的优点是插值结果平滑,且可以通过调节窗口函数的参数来控制插值效果,缺点是计算复杂度较高。SWR_FILTER_TYPE_CUBIC, /**< Cubic */
SWR_FILTER_TYPE_BLACKMAN_NUTTALL, /**< Blackman Nuttall windowed sinc */
SWR_FILTER_TYPE_KAISER, /**< Kaiser windowed sinc */
重采样引擎除了支持sw resampler
外,还支持sox resampler
,sox依赖于external库soxr。
SWR_ENGINE_SWR, /**< SW Resampler */
SWR_ENGINE_SOXR, /**< SoX Resampler */
SWR_ENGINE_NB, ///< not part of API/ABI
这里说的auto resampler是指ffmpeg中,在没有指定resampler的情况下,根据输入输出,自动调用resample的情况。
给定下面的播放命令:
ffmpeg -i test-44.1k.wav -ar 48000 test-48k.wav -v 56
增加-v
参数后,可以从log中看到自动插入了resampler
,这里是auto_resampler_0
:
[AVFilterGraph @ 0x557f1a953080] query_formats: 4 queried, 6 merged, 3 already done, 0 delayed
[auto_resampler_0 @ 0x557f1a9a5000] [SWR @ 0x557f1a9a5380] Using s16p internally between filters
[auto_resampler_0 @ 0x557f1a9a5000] ch:2 chl:stereo fmt:s16 r:44100Hz -> ch:2 chl:stereo fmt:s16 r:48000Hz
前面auto_resampler_0的名字是通过下面这个snprintf语句生成,找到对应代码,就可以看到conversion_filter的上下文:
if (!(filter = avfilter_get_by_name(neg->conversion_filter))) {
av_log(log_ctx, AV_LOG_ERROR,
"'%s' filter not present, cannot convert formats.\n",
neg->conversion_filter);
return AVERROR(EINVAL);
}
snprintf(inst_name, sizeof(inst_name), "auto_%s_%d",
neg->conversion_filter, converter_count++);
ff_filter_get_negotiation的时候,根据media类型返回video或者audio的negotiate,video用的scale,audio是aresample。
const AVFilterNegotiation *ff_filter_get_negotiation(AVFilterLink *link)
{
switch (link->type) {
case AVMEDIA_TYPE_VIDEO: return &negotiate_video;
case AVMEDIA_TYPE_AUDIO: return &negotiate_audio;
default: return NULL;
}
}
static const AVFilterNegotiation negotiate_audio = {
.nb_mergers = FF_ARRAY_ELEMS(mergers_audio),
.mergers = mergers_audio,
.conversion_filter = "aresample",
.conversion_opts_offset = offsetof(AVFilterGraph, aresample_swr_opts),
};
static const AVFilterNegotiation negotiate_video = {
.nb_mergers = FF_ARRAY_ELEMS(mergers_video),
.mergers = mergers_video,
.conversion_filter = "scale",
.conversion_opts_offset = offsetof(AVFilterGraph, scale_sws_opts),
};
前面的命令中没有指定resampler filter,auto_resampler_0上是通过filter graph插入的,ffmpeg中即使没有显示指定filter,也会configure filter。
从调用栈可以看到,decode_audio
的时候,通过send_frame_to_filters
将frame发送给filter处理。
query_formats(AVFilterGraph * graph, void * log_ctx) (libavfilter/avfiltergraph.c:487)
graph_config_formats(AVFilterGraph * graph, void * log_ctx) (libavfilter/avfiltergraph.c:1102)
avfilter_graph_config(AVFilterGraph * graphctx, void * log_ctx) (libavfilter/avfiltergraph.c:1172)
configure_filtergraph(FilterGraph * fg) (fftools/ffmpeg_filter.c:1090)
ifilter_send_frame(InputFilter * ifilter, AVFrame * frame, int keep_reference) (fftools/ffmpeg.c:2000)
send_frame_to_filters(InputStream * ist, AVFrame * decoded_frame) (fftools/ffmpeg.c:2076)
decode_audio(InputStream * ist, AVPacket * pkt, int * got_output, int * decode_failed) (fftools/ffmpeg.c:2142)
process_input_packet(InputStream * ist, const AVPacket * pkt, int no_eof) (fftools/ffmpeg.c:2414)
process_input(int file_index) (fftools/ffmpeg.c:4171)
transcode_step() (fftools/ffmpeg.c:4311)
transcode() (fftools/ffmpeg.c:4365)
main(int argc, char ** argv) (fftools/ffmpeg.c:4560)
指定filter_complex
如果命令中指定filter_complex
选项,则在parse_optgroup后,根据filter_complex
对应的处理函数,会调用opt_filter_complex()函数,完成ffmpeg中的全局变量filtergraphs的内存分配,同时nb_filtergraphs加1:
static int opt_filter_complex(void *optctx, const char *opt, const char *arg)
{
/* 给filtergraphs分配内存 */
FilterGraph *fg = ALLOC_ARRAY_ELEM(filtergraphs, nb_filtergraphs);
fg->index = nb_filtergraphs - 1;
fg->graph_desc = av_strdup(arg);
if (!fg->graph_desc)
return AVERROR(ENOMEM);
input_stream_potentially_available = 1;
return 0;
}
而后再调用init_complex_filters()
完成初始化:
/* create the complex filtergraphs */
ret = init_complex_filters();
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error initializing complex filters.\n");
goto fail;
}
没有指定filter_complex
如果命令中没有指定filter_complex
选项,对于output stream是audio或者video类型,都会调用init_simple_filtergraph()
初始化一个simple graph:
if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
err = init_simple_filtergraph(ist, ost);
if (err < 0) {
av_log(NULL, AV_LOG_ERROR,
"Error initializing a simple filtergraph between streams "
"%d:%d->%d:%d\n", ist->file_index, ost->source_index,
nb_output_files - 1, ost->st->index);
exit_program(1);
}
}
在ffmpeg_opt.c中有这个定义:
int auto_conversion_filters = 1;
如果是0,那么audio conversion是都可以关掉的,这段代码在configure_filtergraph()函数中,flag设置为AVFILTER_AUTO_CONVERT_NONE
,所有的自动转换会被禁用。
if (!auto_conversion_filters)
avfilter_graph_set_auto_convert(fg->graph, AVFILTER_AUTO_CONVERT_NONE);
44100到48000:resamle
ffmpeg -i chengdu-44100.wav -ar 48000 chengdu-48000-ff.wav -v 56
1ch到2ch:rematrix
ffmpeg -i mono-lc3.wav -ac 2 2ch-testout.wav
在libswresample/resample_dsp.c中根据不同的format参数会生成不同的resample函数:
#define TEMPLATE_RESAMPLE_S16
#include "resample_template.c"
#undef TEMPLATE_RESAMPLE_S16
#define TEMPLATE_RESAMPLE_S32
#include "resample_template.c"
#undef TEMPLATE_RESAMPLE_S32
#define TEMPLATE_RESAMPLE_FLT
#include "resample_template.c"
#undef TEMPLATE_RESAMPLE_FLT
#define TEMPLATE_RESAMPLE_DBL
#include "resample_template.c"
#undef TEMPLATE_RESAMPLE_DBL
#elif defined(TEMPLATE_RESAMPLE_S16)
# define RENAME(N) N ## _int16
static int RENAME(resample_common)(ResampleContext *c,
void *dest, const void *source,
int n, int update_ctx)
这里用的是resample_common_int16:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OILDTCpW-1681640653492)(.images/image-20230324161150964.png)]
resample_init的时候,会根据不同参数创建filter bank,在resample_common_int16中,通过filterbank计算输出的采样值,这个会计算到每个字节的数据,所以整体是比较耗时的。
for (dst_index = 0; dst_index < n; dst_index++) {
FELEM *filter = ((FELEM *) c->filter_bank) + c->filter_alloc * index;
FELEM2 val = FOFFSET;
FELEM2 val2= 0;
int i;
for (i = 0; i + 1 < c->filter_length; i+=2) {
val += src[sample_index + i ] * (FELEM2)filter[i ];
val2 += src[sample_index + i + 1] * (FELEM2)filter[i + 1];
}
if (i < c->filter_length)
val += src[sample_index + i ] * (FELEM2)filter[i ];
#ifdef FELEML
OUT(dst[dst_index], val + (FELEML)val2);
#else
OUT(dst[dst_index], val + val2);
#endif
frac += c->dst_incr_mod;
index += c->dst_incr_div;
if (frac >= c->src_incr) {
frac -= c->src_incr;
index++;
}
while (index >= c->phase_count) {
sample_index++;
index -= c->phase_count;
}
}
if(update_ctx){
c->frac= frac;
c->index= index;
}
return sample_index;
}
ffmpeg通过swr_init()
初始化swresample的时候,如果输入输出channel不一致,就会初始化rematrix:
s->rematrix = av_channel_layout_compare(&s->out_ch_layout, &s->in_ch_layout) ||
s->rematrix_volume!=1.0 ||
s->rematrix_custom;
if(s->rematrix || s->dither.method) {
ret = swri_rematrix_init(s);
if (ret < 0)
goto fail;
}
以2channel到1channel的rematrix来看,s->matrix的值经过计算,s->matrix的值如下:
#其中s->matrix修改为s->matrix[4][4]
s->matrix[0][0] = 0.5
s->matrix[0][1] = 0.5
s->matrix[0][2] = 0
s->matrix[0][3] = 0
从输出log中也可以看到:
[auto_resampler_0 @ 0x557f75bfa6c0] [SWR @ 0x557f75bfab40] Matrix coefficients: [auto_resampler_0 @ 0x557f75bfa6c0] [SWR @ 0x557f75bfab40] FC: FL:0.500000 FR:0.500000
然后调用swri_rematrix完成rematrix:
if(s->resample_first){
if(postin != midbuf)
if ((out_count = resample(s, midbuf, out_count, postin, in_count)) < 0)
return out_count;
if(midbuf != preout) // rematrix
swri_rematrix(s, preout, midbuf, out_count, preout==out);
}else{
if(postin != midbuf) // rematrix
swri_rematrix(s, midbuf, postin, in_count, midbuf==out);
if(midbuf != preout)
if ((out_count = resample(s, preout, out_count, midbuf, in_count)) < 0)
return out_count;
}
ffmpeg -i 2ch-44.1k.wav -i 2ch-16k.wav -filter_complex " \
[0:a][1:a]amix=inputs=2[aout]" \
-map [aout] -f null -
对比发现,这个和-i参数后面的次序有关,默认会选用第一个的samplerate作为输出的samplerate。
FFmpeg源码分析:resample重采样