将pcm数据经过aac编码器编码成aac数据,我是将从设备上采集的数据经过重采样送入aac编码器进行编码,由于我的测试设备是Ubuntu 18.04虚拟机,采样率为44100hz,采样大小为有符号16位数,满足编码器的输入数据参数要求,所以也可以直接将从设备获取的音频数据送入编码器进行编码,无需经过重采样,但是下面的示例代码我是将从设备读取的音频数据经过重采样再送入编码器进行编码,但是我设置重采样的输入输出音频三元组没有发生变化,所以下面的代码,我重采样相当于采了个寂寞,颇有点脱裤子放屁的意思。在此说明防止大家阅读代码的时候产生误解。
#include "ffmpeg_function.h"
#define BUFFER 2048
/*****************************
函数名:SwrContext* init_swr(SwrContext*swr_ctx)
函数作用:创建重采样上下文,和设置重采样参数
函数参数:SwrContext*swr_ctx上下文指针
函数返回值:SwrContext*swr_ctx上下文指针
******************************/
static SwrContext* init_swr(SwrContext*swr_ctx)
{
swr_ctx=swr_alloc_set_opts(NULL, //ctx上下文
AV_CH_LAYOUT_STEREO, //输出channel布局
AV_SAMPLE_FMT_S16, //输出的采样格式
44100, //输出的采样率
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 NULL;
}
if (swr_init(swr_ctx)<0) //初始化上下文
{
printf("swr_ctx init errpr\n");
return NULL;
}
return swr_ctx;
}
/*****************************
函数名:static AVCodecContext *open_coder(void)
函数作用:打开AAC编码器,并设置编码器的音频输入参数
函数参数:无
函数返回值:AVCodecContext * codec_ctx打开编码器的上下文指针
******************************/
static AVCodecContext *open_coder(void)
{
//查找编码器
AVCodec *codec=avcodec_find_encoder_by_name("libfdk_aac");
if (NULL==codec)
{
printf("avcodec_find_decoder_by_name error\n");
exit(-1);
}
//AVCodec *codec=avcodec_find_encoder(AV_CODEC_ID_AAC);
//创建编码器上下文
AVCodecContext * codec_ctx=avcodec_alloc_context3(codec);
if (NULL==codec_ctx)
{
printf("avcodec_alloc_context3 error\n");
exit(-1);
}
codec_ctx->sample_fmt=AV_SAMPLE_FMT_S16; //输入音频的采样大小
codec_ctx->channel_layout=AV_CH_LAYOUT_STEREO; //输入音频的channel layout
codec_ctx->channels=2; //输入音频的channel数
codec_ctx->sample_rate=44100; //输入音频的采样率
codec_ctx->bit_rate=0; //编码器码流大小AAC_LC:128K,AAC HE:64K,AAC HE V2:32K 默认的AAC为AAC_LC
//如果使用了codec_ctx->profile选项选择对应的编码器,就要设置codec->bit_rate=0,只有在codec->bit_rate
//设置成0之后ffmpeg才回去查找codec_ctx->profile这个选项
codec_ctx->profile=FF_PROFILE_AAC_HE_V2; //选择对应的编码器
//打开编码器
printf("error test3\n");
if(avcodec_open2(codec_ctx,codec,NULL)<0)
{
printf("open aac error\n");
return NULL;
}
else
{
return codec_ctx;
}
}
/*****************************
函数名:static AVFrame* creat_frame(AVFrame*frame)
函数作用:创建编码器的输入数据的缓冲区结构体
函数参数:AVFrame*frame编码器输入数据结构体指针
函数返回值:AVFrame*frame编码器输入数据结构体指针
******************************/
static AVFrame* creat_frame(AVFrame*frame)
{
frame=av_frame_alloc(); //分配编码器输入数据结构体AVFrame,在堆中分配
if (NULL==frame)
{
printf("av_frame_alloc error\n");
return NULL;
}
frame->nb_samples=512; //设置分配buffer大小,即为单通道一个音频帧的采样数
frame->format=AV_SAMPLE_FMT_S16; // 每个采样的大小
frame->channel_layout=AV_CH_LAYOUT_STEREO;
av_frame_get_buffer(frame,0); //分配空间给AVFrame里面的存放音视频数据的buffer
//实际大小为512*2*2=2048
if (NULL==frame->buf[0])
{
printf("av_frame_get_buffer error\n");
av_frame_free(frame);
return NULL;
}
return frame;
}
/*****************************
函数名:static void encode_aac_file(AVCodecContext *codec_ctx,AVFrame *frame,AVPacket *new_packet,int file_fd)
函数作用:将重采样的音频数据喂入编码器,并将编码后的输出数据保存在文件中。
函数参数:AVCodecContext * codec_ctx 打开编码器的上下文指针
AVFrame*frame 编码器输入编码数据结构体指针
AVPacket *new_packet 编码器输出编码数据结构体指针
int file_fd 保存文件的文件描述符
函数返回值:无
******************************/
static void encode_aac_file(AVCodecContext *codec_ctx,AVFrame *frame,AVPacket *new_packet,int file_fd)
{
//将数据送入编码器
int ret=0;
ret=avcodec_send_frame(codec_ctx,frame);
//如果ret>=0说明数据设置成功
while (ret>=0) //获取编码后的音频数据
{
ret=0;
//获取编码后的数据,如果成功,需要重复获取,直到失败为止
ret=avcodec_receive_packet(codec_ctx,new_packet);
if (ret<0)
{
//编码器没有数据了,需要退出向编码器喂数据
if (ret==AVERROR(EAGAIN)||AVERROR_EOF) return;
else //编码器出错程序退出
{
printf("error encoding audio frame\n");
exit(-1);
}
}
write(file_fd,new_packet->data,new_packet->size);
}
}
/*****************************
函数名:static AVFormatContext* open_dev(void)
函数作用:打开音频输入设备
函数参数:无
函数返回值:AVFormatContext *fmt_ctx 打开音频设备的上下文指针
******************************/
static AVFormatContext* open_dev(void)
{
char errors[1024]={0};
//ctx
AVFormatContext *fmt_ctx=NULL;
AVDictionary *option =NULL;
//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 NULL;
}
return fmt_ctx;
}
/*****************************
函数名:static uint8_t* packet_data_buffer(AVPacket *pkt,uint8_t *buf)
函数作用:对从av_read_frame()函数读取到的音频数据进行缓冲,数据太小无法重采样
函数参数:无
函数返回值:AVFormatContext *fmt_ctx 打开音频设备的上下文指针
******************************/
static uint8_t* packet_data_buffer(AVPacket *pkt,uint8_t *buf)
{
static int data_num=0;
if (data_num < (BUFFER-64))
{
for (int i = 0; i < pkt->size; ++i)
{
buf[i + data_num] = pkt->data[i];
}
data_num +=pkt->size;
return NULL;
}
else
{
//把最后一次判断未进if中存放的一包数据放入临时缓冲区bufferData中
for (int i = 0; i < pkt->size; ++i)
{
buf[i + data_num] = pkt->data[i];
}
data_num += pkt->size;
data_num = 0;
return buf;
}
}
/*****************************
函数名:static uint8_t* packet_data_buffer(AVPacket *pkt,uint8_t *buf)
函数作用:录制音频数据并编码保存成acc格式的音频文件
函数参数:无
函数返回值:无
******************************/
void ffmpeg_record_aac(void)
{
int file_fd=0; //存放音频数据的文件描述符
int count =0;
int ret=0;
uint8_t **src_data=NULL; //存放输入数据
int src_linesize=0; //存放输入数据的大小
uint8_t **dts_data=NULL; //存放输出数据
int dts_linesize=0; //存放输出数据的大小
SwrContext*swr_ctx=NULL; //创建上下文指针
uint8_t *buf=(uint8_t*)malloc(BUFFER*sizeof(uint8_t)); //音频数据帧缓冲区
file_fd=open("./voice.aac",O_CREAT|O_RDWR,0666);
AVFormatContext* fmt_ctx=open_dev(); //打开设备
AVPacket pkt;
AVPacket *pkt_pointer=NULL;
av_init_packet(&pkt);
AVCodecContext * codec_ctx=open_coder(); //打开编码器
if (NULL==codec_ctx)
{
return;
}
AVFrame*frame=NULL;
frame=creat_frame(frame); //设置编码器输入参数,并创建输入缓冲区
if (NULL==frame)
{
return;
}
AVPacket *new_packet=NULL;
new_packet=av_packet_alloc(); //分配编码后的数据空间
if (NULL==new_packet)
{
printf("av_packet_alloc from new_packet\n");
}
swr_ctx=init_swr(swr_ctx); //创建重采样上下文
//创建输入缓冲区
av_samples_alloc_array_and_samples(&src_data, //输入缓冲区地址
&src_linesize, //缓冲区的大小
2, //通道数
512, //通道采样个数 2048/2=1024/2=512
AV_SAMPLE_FMT_S16, //采样格式
0);
//创建输出缓冲区
av_samples_alloc_array_and_samples(&dts_data, //输出缓冲区地址
&dts_linesize, //缓冲区的大小
2, //通道数
512, //通道采样个数
AV_SAMPLE_FMT_S16, //采样格式
0);
while (ret=av_read_frame(fmt_ctx,&pkt)==0&&count++<30000)
{
printf("packet size is %d(%p),count=%d\n",pkt.size,pkt.data,count);
if (NULL!=packet_data_buffer(&pkt,buf)) //判断数据是否缓冲足够,如果足够就进行重采样,并将重采样的数据送入编码器
{
memcpy(src_data[0],buf, BUFFER);
// 重采样
swr_convert(swr_ctx, // 重采样上下文
dts_data, // 输出缓冲区
512, // 输出每个通道的采样数
(const uint8_t **)src_data, // 输入缓冲区
512); // 输入每个通道的采样数
memcpy((void*)frame->data[0],dts_data[0],BUFFER);
encode_aac_file(codec_ctx,frame,new_packet,file_fd);
}
}
encode_aac_file(codec_ctx,NULL,new_packet,file_fd); //已经没有音频数据喂给编码器了,让编码器将最后一点数据输出,保存到文件
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);
}
在主函数直接调用void ffmpeg_record_aac(void)函数即可完成对音频的录制并编码保存成aac格式的音频数据。