为什么要进行音频重采样
- 从设备采集的音频数据与编码器要求的数据不一致
- 扬声器要求的音频数据与要播放的音频数据不一致
- 更方便运算,例如:处理回音消除时,需要将音频数据转换成单声道的数据,方便计算
ffmpeg重采样主要API
swr_init : 初始化重采样上下文
swr_alloc_set_opts: 设置重采样参数
swr_convert: 重采样
swr_free: 释放重采样上下文空间
重采样
- 初始化重采样上下文
// 初始化重采样上下文
SwrContext* init_swr()
{
// 重采样上下文
SwrContext *swr_ctx = NULL;
swr_ctx = swr_alloc_set_opts(NULL, // ctx
AV_CH_LAYOUT_STEREO, // 输出channel布局
AV_SAMPLE_FMT_FLT, // 输出的采样大小
44100, // 输出的采样率
AV_CH_LAYOUT_STEREO, // 输入的channel布局
AV_SAMPLE_FMT_S16, // 输入的采样大小
44100, // 输入的采样率
0, NULL);
if (!swr_ctx)
{
printf("Failed to init SwrContext, \n");
return NULL;
}
if (swr_init(swr_ctx) < 0)
{
printf("Failed to init SwrContext, \n");
return NULL;
}
return swr_ctx;
}
- 进行重采样,并将重采样的音频数据写入文件
// 初始化重采样上下文
SwrContext *swr_ctx = init_swr();
// 初始化重采样缓冲区
uint8_t **src_data = NULL;
int src_linesize = 0;
uint8_t **dst_data = NULL;
int dst_linesize = 0;
// 创建输入缓冲区
av_samples_alloc_array_and_samples(&src_data, // 输入数据的缓冲区地址
&src_linesize, // 缓冲区大小
2, // 通道数
512, // 单通道采样个数
//注:512=2048//2/2。采集的每包数据2048个字节,采样大小为16位,即2个字节,双通道采集。所以2048除以2为每个通道采集的字节数,再除以2(采样大小,2个字节),为每个通道采样的个数
AV_SAMPLE_FMT_S16, // 采样大小
0);
// 创建输出缓冲区
av_samples_alloc_array_and_samples(&dst_data, // 输出数据的缓冲区地址
&dst_linesize, // 缓冲区大小
2, // 通道数
512, // 单通道采样个数
AV_SAMPLE_FMT_FLT, // 采样大小
0);
int swr_num = 0;
// 我的环境是Linux虚拟机,每次获取的音频数据只有64个字节,而ffmpeg中最低采样个数为32个,所以只有 64 字节数据时,无法进行重采样。所以这里增加了一个存储音频数据的缓冲区,等缓冲区达到一定大小时再进行重采样。
uint8_t *bufferData = new uint8_t[2048];
// 从设备读取数据
while ((ret = av_read_frame(fmt_ctx, &packet) == 0) && m_status)
{
av_log(NULL, AV_LOG_INFO, "Packet size: %d(%p), count = %d\n",
packet.size, packet.data, count++);
#ifdef false // 错误代码,swr_num到2048以后不进if中保存数据,直接到else中使用缓冲区进行重采样,但是此时av_read_frame也读了一包数据,没有放到bufferData中,所以会丢掉一包数据。就是每读2048字节的数据就会漏掉一包数据
if (swr_num < 2048)
{
for (int i = 0; i < packet.size; ++i)
{
bufferData[i + swr_num] = packet.data[i];
}
swr_num += packet.size;
}
else
{
#else
// 更正
if (swr_num < 2048)
{
for (int i = 0; i < packet.size; ++i)
{
bufferData[i + swr_num] = packet.data[i];
}
swr_num += packet.size;
}
else
{
//把最后一次判断未进if中存放的一包数据放入临时缓冲区bufferData中
for (int i = 0; i < packet.size; ++i)
{
bufferData[i + swr_num] = packet.data[i];
}
swr_num += packet.size;
#endif
swr_num = 0;
memcpy(src_data[0], bufferData, 2048);
// 重采样
swr_convert(swr_ctx, // 重采样上下文
dst_data, // 输出缓冲区
512, // 输出每个通道的采样数
(const uint8_t **)src_data, // 输入缓冲区
512); // 输入每个通道的采样数
// 写入文件
//fwrite(packet.data, packet.size, 1, outFile);
fwrite(dst_data[0], 1, dst_linesize, outFile);
fflush(outFile);
}
// 释放packet空间
av_packet_unref(&packet);
}
// 释放重采样上下文
av_free(swr_ctx);
我的环境是Linux虚拟机,每次获取的音频数据只有64个字节,而ffmpeg中最低采样个数为32个,所以只有 64 字节数据时,无法进行重采样。所以代码中增加了一个存储音频数据的缓冲区,等缓冲区达到一定大小时再进行重采样。非虚拟机环境,不需要增加缓冲区,直接使用**memcpy(src_data[0], packet.data, packet.size);**即可。
我的设备采集音频的参数是采样率44100, 双声道,16位采样大小。重采样后的音频数据为采样率44100,双声道,采样大小32位。
下图显示了未进行重采样时音频文件的数据格式:
使用ffplay播放重采样后的音频文件,可以看到重采样后的音频数据发生了变化,由16位采样大小变成了浮点类型,32位。