在Linux环境下使用ffmpeg将PCM音频数据编码成aac数据
程序框图代码演示程序框图
将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_FLT44100, //输入的采样率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 layoutcodec_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=2048if (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};//ctxAVFormatContext *fmt_ctx=NULL;AVDictionary *option =NULL;//mic addresschar *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=512AV_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格式的音频数据。