swr_convert音频重采样介绍

在做音频处理的时候,我们有时候需要调整音频流的采样率 或者 采样格式,可能是喇叭不支持 48000 采样率,所以需要降低到 44100 采样了.也可能因为各种业务原因,需要调整 采样率,采样格式,或者声道布局。

FFmpeg 提供了 swr_convert() 函数来实现上面的功能。

需要注意的是,调整采样率,是不会影响音频流的播放时长的,原来是 10 分钟的音频文件,你调高或者降低采样率,它还是 10 分钟的播放时长。

不过 swr_convert() 是支持调整播放时长的,这个后面说。


下面通过一个代码实例演示 swr_convert() 函数的用法,代码下载地址:GitHub,编译环境是 Qt 5.15.2MSVC2019_64bit

这个代码实例主要是把 音频流的 采样率 从 48000 降低到 44100,把音频格式 从 fltp 转成 s64,声道布局不变。

int tgt_fmt = AV_SAMPLE_FMT_S64;
int tgt_freq = 44100;

重点代码如下:

1-2

音频重采样库的API函数调用流程如下:

1-3


下面来介绍一下各个函数。

1,swr_alloc_set_opts(),定义如下:

struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
                                      int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
                                      int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
                                      int log_offset, void *log_ctx);

参数解释如下:

  • struct SwrContext *s,如果传 NULL,他内部会申请一块内存,非NULL可以复用之前的内存,不用申请。
  • int64_t out_ch_layout,目标声道布局
  • enum AVSampleFormat out_sample_fmt,目标采样格式
  • int out_sample_rate,目标采样率
  • int64_t in_ch_layout,原始声道布局
  • enum AVSampleFormat in_sample_fmt,原始采样格式
  • int in_sample_rate,原始采样率
  • int log_offset,不知道做什么的,填 0 就行。
  • void *log_ctx,不知道做什么的,填 NULL 就行。

2,swr_init(),初始化重采样函数,如果你更改了重采样上下文的 options,也就是改了选项,例如改了采样率,必须调 swr_init() 才能生效。。

3,swr_convert(),转换函数,定义如下:

/** Convert audio.
 *
 * in and in_count can be set to 0 to flush the last few samples out at the
 * end.
 *
 * If more input is provided than output space, then the input will be buffered.
 * You can avoid this buffering by using swr_get_out_samples() to retrieve an
 * upper bound on the required number of output samples for the given number of
 * input samples. Conversion will run directly without copying whenever possible.
 *
 * @param s         allocated Swr context, with parameters set
 * @param out       output buffers, only the first one need be set in case of packed audio
 * @param out_count amount of space available for output in samples per channel
 * @param in        input buffers, only the first one need to be set in case of packed audio
 * @param in_count  number of input samples available in one channel
 *
 * @return number of samples output per channel, negative value on error
 */
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                const uint8_t **in , int in_count);

参数解释如下:

  • struct SwrContext *s,重采样上下文,也叫重采样实例。
  • uint8_t **out,输出的内存地址。
  • int out_count,每声道有多少个样本,这个值通常建议设置得大一点,避免内存空间不够,不够空间写入,就会缓存在重采样实例里面,越积越多。
  • const uint8_t **in,输入的内存地址。
  • int in_count,输入的音频流,每声道有多少个样本。

swr_convert() 函数的返回值是实际的样本数。


项目代码的运行结果如下:

1-4

本文项目代码的重点是 out_count 的计算,如下:

out_count = (int64_t)frame->nb_samples * tgt_freq / frame->sample_rate + 256;

由于源文件 juren-30s.mp4 大部分音频帧是 1024 个样本数,从 48000 降低 44100,也就是说 1024个样本 会 变成 940 个样本。

但是从上图的运行结果可以看到,有时候是转换出 941 个样本的,比 940 多了一个。所以 out_count 通常会在本来的大小上 加上 256,让写空间大一点。

如果不够空间写入,就会缓存在重采样实例里面,越积越多。

+256 也是 ffplay 播放器的做法。


最后还有一个重点是,SwrContext 上下文里面可能会有残留数据,当没有数据输入的时候,需要再调 swr_convert() ,把残留的数据刷出来,如下:

1-5

1-6

可以看到,最后刷出来了 16 个残留样本


上面讲的是播放器的重采样场景,重采样之后,获取到了内存 out ,直接把 out 的内存丢给 SDL 即可播放。

但是有时候,我们是需要把转换之后的数据进行编码保存的,所以这种情况下,需要把 out 的内存挂在 AVFrame 里面,具体做法如下:

AVFrame frame;
frame->extended_data = out;
frame->data = out;
frame->nb_samples = out_nb_samples;

然后设置好 framepts 即可,这样应该是可以的,不过我没编码测试过,后面补充。

音频也可以用 av_samples_alloc() 来申请内存,但是由于本文要 +256 ,所以没有使用这个函数。


扩展知识:

音频与视频的转换函数,命名是类似的,例如:

av_samples_alloc()
av_image_alloc()
av_samples_fill_arrays()
av_image_fill_arrays()

av_frame_get_buffer() 函数可以同时用于音频,视频的申请内存,前提是设置好 AVFrame 的 格式,宽高,采样率,声道。


swr_convert() 函数使用起来,我个人觉得有点繁琐,其实音频的 aformat 格式滤镜也可以调整 采样率,采样格式,声道布局。

滤镜的语法比较统一,只是 aformat 滤镜不能调整播放时长,推荐阅读《FFmpeg的音频aformat滤镜介绍

最后需要用swr_free() 释放调重采样实例。


一开始留的彩蛋,调整播放时长,是用 swr_set_compensation() 实现,推荐阅读《如何调整音频播放时长


版权所属 xianwanzhiyin.net 罗上文 2022 all right reserved,powered by Gitbook该文件修订时间: 2022-10-10 15:34:06

results matching ""

    No results matching ""