此案例结合FFmpeg的官方例子,用于学习FFmpeg的resample的使用。
一、什么是重采样
改变音频的采样率sample rate、采样格式sample format、声道数channels等任一参数,使之按照期望的参数输出。
二、为什么要重采样
在FFmpeg解码音频的时候,不同的音源有不同的格式,采样率等。在解码后的数据中的这些参数也会不一致(最新FFmpeg解码音频后,音频格式为AV_SAMPLE_FMT_FLTP,这个参数应该是一致的)。
如果接下来需要使用解码后的音频数据做其他操作,而这些参数的不一致导致会有很多额外的工作,此时直接对其进行重采样,获取我们定制的音频参数,这样就会方便很多。
在将音频进行SDL播放的时候,因为当前的SDL2.0不支持planar格式,也不支持浮点型的,而最新的FFmpeg会将音频解码为AV_SAMPLE_FMT_FLTP格式,因此需要进行重采样,使之可以在SDL2.0上进行播放。
三、如何重采样
采样率sample rate:采样设备每秒从连续信号中提取并组成离散信号的采样个数,用Hz表示;
采样格式sample format:每种音频格式有不同的量化精度(位宽),位数越多,表示值就越精确,声音表现就越精确;
- FFmpeg中音频格式有以下几种,每种格式有其占用的字节数:
enum AVSampleFormat { AV_SAMPLE_FMT_NONE = -1, AV_SAMPLE_FMT_U8, //unsigned 8 bits AV_SAMPLE_FMT_S16, //signed 16 bits AV_SAMPLE_FMT_S32, //signed 32 bits AV_SAMPLE_FMT_FLT, //float AV_SAMPLE_FMT_DBL, //double AV_SAMPLE_FMT_U8P, //unsigned 8 bits,planar AV_SAMPLE_FMT_S16P, //signed 16 bits,planar AV_SAMPLE_FMT_S32P, //signed 32 bits,planar AV_SAMPLE_FMT_FLTP, //float,planar AV_SAMPLE_FMT_DBLP, //double,planar AV_SAMPLE_FMT_S64, //signed 64 bits AV_SAMPLE_FMT_S64P, //signed 64 bits,planar AV_SAMPLE_FMT_NB //number of sample formats,DO NOT USE if linking dynamically }- 分片plane和打包packed,比如双声道:
- 带P(plane)的音频数据格式在存储时,其左声道和右声道的数据是分开存储的。左声道的数据存储在data[0],右声道的数据存储在data[1],每个声道所占用的字节数为linesize[0]和linesize[1],FFmepg默认是plane模式处理。
- 不带P(packed)的音频数据在存储时,是按照LRLRLR…的格式交替存储在data[0]中,linesize[0]表示总的数据量。
通道布局channel layout:可以通过此参数获取声道数nb_channels;
声道分布的定义在FFmpeg\libavutil\channel_layout.h中,一般用的比较多的是AV_CH_LAYOUT_STEREO(双声道)和AV_CH_LAYOUT_SURROUND(三声道),定义如下:#define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT) #define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO | AV_CH_FRONT_CENTER)采样个数(样本数)nb_samples:采样的个数,对于采样频率,采样频率就是一秒的采样个数;
对于一帧音频的采样个数,AAC固定是一帧采样1024个,MP3格式则为1152个。
音频帧的数据量计算:
一帧音频的数据量(字节byte) = channel * nb_samples(样本数) * 每个样本占用的字节数
比如音频帧是FLTP格式的PCM数据(也就是aac),包含1024个样本,双通道,那么该音频帧包含的音频数据量为:
data_size * nb_samples * channels = 4 * 1024 * 2 = 8192 byte
如果是AV_SAMPLE_FMT_FLTP,那么数据量为 8 * 1024 * 2 = 16384 byte
一帧音频的播放时间(ms) = nb_samples样本数 * 1000 / 采样率
比如以采样率44100Hz来计算,每秒44100个sample,正常一帧为为1024个sample,则每帧播放时间 = 1024 * 1000 / 44100 = 23.2ms
四、FFmpeg中几个重要的API
swr_alloc_set_opts:创建上下文,设置参数,也可以使用swr_alloc()
swr_init:初始化
av_samples_alloc_array_and_samples:给输入源分配内存空间
swr_convert:具体音频帧转换
swr_free:释放上下文占用的资源
流程图如下:

五、FFmpeg中示例resample_audio
/**
*@Author:Magic Miao
*@Blog: http://www.hust-miao.top
*@date: 2022/08/31
*description:FFmpeg 音频重新采样
* 创建一系列音频帧,然后进行重采样,设定格式和帧率,保存到另一个文件中
* 也就是改变一段音频的相关参数:采样率、采样通道、采样格式等<br>
*/
extern "C"
{
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
}
/**
* description:此函数用于格式转换
* @param fmt
* @param sample_fmt
* @return
*/
static int fc_get_format_from_sample_fmt(const char **fmt,enum AVSampleFormat sample_fmt)
{
struct sample_fmt_entry
{
enum AVSampleFormat sample_fmt;
const char *fmt_be,*fmt_le;
} sample_fmt_entries[] = {
{AV_SAMPLE_FMT_U8, "u8", "u8" },
{AV_SAMPLE_FMT_S16, "s16be", "s16le"},
{AV_SAMPLE_FMT_S32, "s32be", "s32le"},
{AV_SAMPLE_FMT_FLT, "f32be", "f32le"},
{AV_SAMPLE_FMT_DBL, "f64be", "f64le"},
};
*fmt = NULL;
for(int i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++)
{
struct sample_fmt_entry *entry = &sample_fmt_entries[i];
if(sample_fmt == entry->sample_fmt)
{
*fmt = AV_NE(entry->fmt_be,entry->fmt_le);
return 0;
}
}
fprintf(stderr,"Sample format %s not supported as output format\n",av_get_sample_fmt_name(sample_fmt));
return AVERROR(EINVAL);
}
/**
* description:随机产生音频数据,Fill dst buffer with nb_samples,generated starting from t
* @param dst
* @param nb_samples
* @param nb_channels
* @param sample_rate
* @param t
* @return
*/
static void fill_samples(double *dst,int nb_samples,int nb_channels,int sample_rate,double *t)
{
double tincr = 1.0 / sample_rate, *dstp = dst;
const double c = 2 * M_PI * 440.0;
//miao:产生双声道440Hz的数据
//miao:nb_samples是采样数
//miao:nb_channels为2
//miao:generate sin tone with 440Hz frequency and duplicated channels
for(int i = 0; i < nb_samples; i++)
{
*dstp = sin(c * *t);
for(int j = 1;j < nb_channels;j++)
{
dstp[j] = dstp[0];
}
dstp += nb_channels;
*t += tincr;
}
}
int main(int argc, char *argv[])
{
//miao:输入立体声,输出布局环绕声
AVChannelLayout src_ch_layout = AV_CHANNEL_LAYOUT_STEREO,dst_ch_layout = AV_CHANNEL_LAYOUT_SURROUND;
//miao:输入和输出的采样率
int src_rate = 48000,dst_rate = 44100;
uint8_t **src_data = NULL,**dst_data = NULL;
int src_nb_channels = 0,dst_nb_channels = 0;
int src_linesize,dst_linesize;
int src_nb_samples = 1024,dst_nb_samples,max_dst_nb_samples;
//miao:输入采样格式为“双精度”,输出为16位
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_DBL,dst_sample_fmt = AV_SAMPLE_FMT_S16;
const char *dst_filename = NULL;
FILE *dst_file;
int dst_bufsize;
const char *fmt;
struct SwrContext *swr_ctx;
char buf[64];
double t;
int ret;
if(argc != 2)
{
fprintf(stderr,"Usage: %s output_file\n"
"API example program to show how to resample an audio stream with libswresample.\n"
"This program generates a series of audio frames,resamples them to a specified"
"output format and rate and saves them to an output file named output_file.\n",argv[0]);
exit(1);
}
dst_filename = argv[1];
//miao:以“写出”模式,打开输出文件
dst_file = fopen(dst_filename,"wb");
if(!dst_file)
{
fprintf(stderr,"Could not open destination file %s\n",dst_filename);
exit(1);
}
//miao:Create resampler context
//miao:重采样上下文分配空间,重采样就是用来进行音频转换的
swr_ctx = swr_alloc();
if(!swr_ctx)
{
fprintf(stderr,"Could not allocate resampler context\n");
ret = AVERROR(ENOMEM);
goto end;
}
//miao:set options
//miao:创建输入和输出音频的参数
av_opt_set_chlayout( swr_ctx, "in_chlayout", &src_ch_layout, 0);
av_opt_set_int( swr_ctx, "in_sample_rate", src_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
av_opt_set_chlayout( swr_ctx, "out_chlayout", &dst_ch_layout, 0);
av_opt_set_int( swr_ctx, "out_sample_rate", dst_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
//miao:initialize the resampling context
//miao:设置完参数后,需要init初始化
if((ret = swr_init(swr_ctx)) < 0)
{
fprintf(stderr,"Failed to initialize the resampling context\n");
goto end;
}
//miao:allocate source and destination samples buffers
//miao:src通道布局的通道数
src_nb_channels = src_ch_layout.nb_channels;
//miao:利用通道数、采样格式、采样数,分配数据指针数组和数据长度
ret = av_samples_alloc_array_and_samples(&src_data,&src_linesize,src_nb_channels,
src_nb_samples,src_sample_fmt,0);
if(ret < 0)
{
fprintf(stderr,"Could not allocate source samples\n");
goto end;
}
//miao:compute the number of converted samples:buffering is avoided ensuring that
//miao:the output buffer will contain at lease all the converted input samples
//miao:a * bq / cq,计算转换后的样本数,也就是输出文件的样本数,AV_ROUND_UP向上圆整
max_dst_nb_samples = dst_nb_samples = av_rescale_rnd(src_nb_samples,dst_rate,src_rate,AV_ROUND_UP);
//miao:buffer is going to be directly written to a rawaudio file,no alignment
dst_nb_channels = dst_ch_layout.nb_channels;
//miao:为dst数据分配指针数组,和src一样
ret = av_samples_alloc_array_and_samples(&dst_data,&dst_linesize,dst_nb_channels,
dst_nb_samples,dst_sample_fmt,0);
if(ret < 0)
{
fprintf(stderr,"Could not allocate destination samples\n");
goto end;
}
t = 0;
do
{
//miao:这里根据t来处理循环,t又累加1/采样率,因此这里是转换10s的数据
//miao:generate synthetic audio
//miao:随机产生音频数据,添到src_data[0]数组中,src_data为二维数组
fill_samples((double*)src_data[0],src_nb_samples,src_nb_channels,src_rate,&t);
//miao:compute destination number of samples
//miao:a * b / c,计算目标采样数,同时加入了输入样本到输出样本间的延迟,得到输出的采样数目
dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx,src_rate) + src_nb_samples,dst_rate,src_rate,AV_ROUND_UP);
if(dst_nb_samples > max_dst_nb_samples)
{
//miao:计算的样本有误,需要重新获取数据指针
av_freep(&dst_data[0]);
//miao:重新分配输出数据的指针数组,这里更新了dst_linesize的数据
ret = av_samples_alloc(dst_data,&dst_linesize,dst_nb_channels,dst_nb_samples,dst_sample_fmt,1);
if(ret < 0) break;
max_dst_nb_samples = dst_nb_samples;
}
//miao:convert to desitination format
//miao:根据样本数目,把src数据转换成dst数据
ret = swr_convert(swr_ctx,dst_data,dst_nb_samples,(const uint8_t **)src_data,src_nb_samples);
if(ret < 0)
{
fprintf(stderr,"Error while converting\n");
goto end;
}
//miao:根据格式获取指定大小,也就是dst_buffer实际写入的数据
dst_bufsize = av_samples_get_buffer_size(&dst_linesize,dst_nb_channels,ret,dst_sample_fmt,1);
if(dst_bufsize < 0)
{
fprintf(stderr,"Could not get sample buffer size\n");
goto end;
}
printf("t:%f in:%d out:%d\n",t,src_nb_samples,ret);
//miao:把数据写到输出文件
fwrite(dst_data[0],1,dst_bufsize,dst_file);
}
while( t < 10);
if((ret = fc_get_format_from_sample_fmt(&fmt,dst_sample_fmt)) < 0) goto end;
av_channel_layout_describe(&dst_ch_layout,buf,sizeof(buf));
fprintf(stderr,"Resampling succeeded.Play the output filie with the command:\n"
"ffplay -f %s -channel_layout %s -channels %d -ar %d %s\n",
fmt,buf,dst_nb_channels,dst_rate,dst_filename);
end:
fclose(dst_file);
if(src_data)
{
av_freep(&src_data[0]);
}
av_freep(&src_data);
if(dst_data)
{
av_freep(&dst_data[0]);
}
av_freep(&dst_data);
swr_free(&swr_ctx);
return ret < 0;
}