在Linux环境下使用ffmpeg进行音频重采样
感谢这位博主提供的参考文章
关于音频重采样
什么是音频重采样?
所谓音频重采样就是将音频的三元组(采样率,采样大小,通道数)的值转换成另外一组音频三元组的值。三元组中任何一组值发生改变
都称之为重采样,当然也可以三组数据都改变。
什么是音频三元组?
采样率
采样率表示的是我们是按照多少的频率去采集音频信号的,常见的采样率位8k,16k,32k,44.1k,48k,采样率越高采集到的数字信号就更接近被采集的模拟信号的波形
采样大小
采样大小有时候也被称为位深,就是采集到的一个样本使用多少bit存放,通常为16bit,也是意味着可以表示2^16=65536个采样的数据,其表示的区间为-32768~+32768.位深越大,可以描述的声音就越大,采样量化的精度就越高。
通道数(声道数)
有单声道,双声道,多声道,多声道也可以称为立体声
音频流码率计算
PCM音频流码率=采样率×采样大小×通道数
例如采样率为44.1khz,采样大小为16bit,双通道的PCM编码的WAV文件它的码率为44.1k×16×2=1411.2Kb/s,也就是1秒产生了大约1.4M的数据
为什么要进行音频重采样
1、从设备采集到的音频数据如编码器要求的数据不一致时,我们要进行重采样,达到编码器的要求。
2、扬声器要求的音频数据与要播放的音频数据不一致。
3、方便运算。
如何使用ffmpeg实现音频重采样
创建重采样上下文
swr_alloc_set_opts()调用此函数就可以创建一个重采样的上下文,并设置重采样的参数
初始化重采样
使用swr_init()函数对上下文进行初始化
对每一个音频帧进行重采样
使用swr_convert()函数进行重采样,swr_convert()函数原型:
int swr_convert(struct SwrContext *s, // 重采样上下文
uint8_t **out, // 输出缓冲区
int out_count, // 输出每个通道的采样数
const uint8_t **in ,// 输入缓冲区
int in_count);// 输入每个通道的采样数
其中输入数数据缓冲区和输出数据缓冲区这两个参数需要使用av_samples_alloc_array_and_samples()函数来构造,函数原型如下:
int av_samples_alloc_array_and_samples(
uint8_t ***audio_data, //输入缓冲区地址
int *linesize, //缓冲区的大小
int nb_channels, //通道数
int nb_samples, //通道采样个数
enum AVSampleFormat sample_fmt, //采样格式
int align
);
其中的通道采样数个数nb_samples的计算方法为nb_samples=音频帧数据包大小/采样格式的大小(其中AV_SAMPLE_FMT_S16大小为两个字节)/通道数(计算单个通道的数据量)。一下面的代码为例进行计算虽然在我在Ubuntu环境中av_read_frame()读出的数据包的大小为packet.size=64.但是由于64的数据包大小太小无法进行重采样,所以要先将数据缓存起来等到数据大小为1024或2048或者更大再进行重采样。下面的代码是将数据包缓存到2048再进行重采样。一下面的代码为例计算,设备的采样格式为有符号16位AV_SAMPLE_FMT_S16(两个字节),双通道
nb_samples=2048/2=1024/2=512
代码演示:
.c文件
#include "ffmpeg_function.h"
void ffmpeg_record_save_file(void)
{
char errors[1024]={0};
int file_fd=0; //存放音频数据的文件描述符
int data_num = 0; //
uint8_t **src_data=NULL; //存放输入数据
int src_linesize=0; //存放输入数据的大小
uint8_t **dts_data=NULL; //存放输出数据
int dts_linesize=0; //存放输出数据的大小
uint8_t *buf=(uint8_t*)malloc(2048*sizeof(uint8_t)); //音频数据帧缓冲区
file_fd=open("./voice.pcm",O_CREAT|O_RDWR,0666);
//ctx
AVFormatContext *fmt_ctx=NULL;
AVDictionary *option =NULL;
//packet
int count =0;
AVPacket pkt;
//mic address
char *devicename="hw:0";
//注册音频设备
avdevice_register_all();
//获取音频格式
AVInputFormat *iformat=av_find_input_format("alsa");
//打开设备
int ret=avformat_open_input(&fmt_ctx,devicename,iformat,&option);
if (ret<0)
{
av_strerror(ret,errors,1024);
return;
}
av_init_packet(&pkt);
SwrContext*swr_ctx=NULL; //创建上下文指针
swr_ctx=swr_alloc_set_opts(NULL, //ctx上下文
AV_CH_LAYOUT_STEREO, //输出channel布局
AV_SAMPLE_FMT_S32, //输出的采样格式
48000, //输出的采样率
AV_CH_LAYOUT_STEREO, //输入的channel布局
AV_SAMPLE_FMT_S16, //输入的采样格式 AV_SAMPLE_FMT_FLT
44100, //输入的采样率
0,
NULL);
if (NULL==swr_ctx) //判对返回的上下文指针是否为空
{
printf("swr_ctx errpr\n");
return;
}
if (swr_init(swr_ctx)<0) //初始化上下文
{
printf("swr_ctx init errpr\n");
return;
}
//2048/2=1024/2=512
//创建输入缓冲区
av_samples_alloc_array_and_samples(&src_data, //输入缓冲区地址
&src_linesize, //缓冲区的大小
2, //通道数
512, //通道采样个数
AV_SAMPLE_FMT_S16, //采样格式
0);
//创建输出缓冲区
av_samples_alloc_array_and_samples(&dts_data, //输出缓冲区地址
&dts_linesize, //缓冲区的大小
2, //通道数
512, //通道采样个数
AV_SAMPLE_FMT_S32, //采样格式
0);
while (ret=av_read_frame(fmt_ctx,&pkt)==0&&count++<40000)
{
printf("packet size is %d(%p),count=%d\n",pkt.size,pkt.data,count);
if (data_num < 1984) //数据缓存
{
for (int i = 0; i < pkt.size; ++i)
{
buf[i + data_num] = pkt.data[i];
}
data_num +=pkt.size;
}
else
{
//数据包大小1984距离2048还差最后一个64大小的数据包,
//将最后一个数据包加入大小就为2048避免了音频包丢失
for (int i = 0; i < pkt.size; ++i)
{
buf[i + data_num] = pkt.data[i];
}
data_num += pkt.size;
data_num = 0;
memcpy(src_data[0],buf, 2048);
// 重采样
swr_convert(swr_ctx, // 重采样上下文
dts_data, // 输出缓冲区
512, // 输出每个通道的采样数
(const uint8_t **)src_data, // 输入缓冲区
512); // 输入每个通道的采样数
write(file_fd,dts_data[0],dts_linesize);
}
}
av_packet_unref(&pkt); //解引用音频包
//释放源的缓冲区
if (src_data) av_freep(&src_data[0]);
av_free(src_data);
//释放目的的缓冲区
if (dts_data) av_freep(&dts_data[0]);
av_free(dts_data);
//释放重采样的上下文
swr_free(&swr_ctx);
close(file_fd);
avformat_close_input(&fmt_ctx);
av_log_set_level(AV_LOG_DEBUG);
}
.h文件
#ifndef FFMPEG_FUNCTION_H
#define FFMPEG_FUNCTION_H
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "libavformat/avformat.h"
#include "libavformat/avformat.h"
#include "libavutil/avconfig.h"
#include "libavcodec/avcodec.h"
#include "libavdevice/avdevice.h"
#include "libswresample/swresample.h"
void ffmpeg_record_save_file(void);
#endif
注意事项
可以看到我们在对从av_read_frame()读取的音频帧进行缓冲的时候,使用2048-64=1984大小作为if语句的判断条件。那是因为如果使用2048作为if语句的判断条件的话,当满足2048的时候就会退出但是此时新的数据包已经到来了,如果我们对最新一个到来的数据包进行缓存的话,数据包的大小就不是原先的设定值2048了,如果不对新来的数据包进行缓存的话又会造成音频数据的丢包。所以我们if的判断条件就小于我们设置的缓冲区大小2048,在Linux环境下每一个音频帧packet.size的大小为64,所以在缓存数据小于一个音频帧大小的时候进入else语句,在else语句完成对最后一帧音频的缓冲和对整个缓冲区的音频数据进行重采样。