创建编码器的流程图
如何创建并打开编码器
- 创建编码器 avcodec_find_encoder(<#enum AVCodecID id#>)
- 创建上下文 avcodec_alloc_context3(<#const AVCodec *codec#>)
- 打开编码器 avcodec_open2(<#AVCodecContext *avctx#>, <#const AVCodec *codec#>, <#AVDictionary **options#>)
- 将数据送给编码器 avcodec_send_frame(<#AVCodecContext *avctx#>, <#const AVFrame *frame#>)
- 获取编码后的数据 avcodec_receive_packet(<#AVCodecContext *avctx#>, <#AVPacket *avpkt#>), <#AVFrame *frame#>)
- AVFrame 没有编码的数据
- AVPacket 编码后的数据。
AVCodecContext* open_coder(){
//查找编码器
int ret;
char error[1024] = {0, };
AVCodec *codec = avcodec_find_encoder_by_name("libfdk_aac");
// AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
//
// //创建上下文
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16; //输入音频的采样率
codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; //输入音频的通道布局
codec_ctx->channels = 2; //输入音频的通道个数
codec_ctx->sample_rate = 44100; //输入音频的采样率
//bitrate 和profile互斥,bit_rate设置为0后,才会查找profile的值
codec_ctx->bit_rate = 0; //AAC 128K AAC HE 64K AAC HE V2 32K
codec_ctx->profile = FF_PROFILE_AAC_HE_V2;
//打开编码器
if((ret = avcodec_open2(codec_ctx, codec, NULL)) < 0){
av_strerror(ret, error, 1024);
printf(stderr, "Failed to open audio devices, [%d] %s\n", ret, error);
return NULL;
}
return codec_ctx;
}
在打开编码器的工程中发现以下两个问题
1、通过avcodec_find_encoder_by_name查找的aac编码器,查看编码的位深是
那么上下文的位深必须是16位 AV_SAMPLE_FMT_S16
通道数必须是2
通道布局必须是AV_CH_LAYOUT_STEREO
我自己设置为通道数是1 通道布局是AV_CH_LAYOUT_MONO。打开编码器的时候提示SBR初始化错误。具体原因还需查找。
2、通过avcodec_find_encoder查找的aac编码器,
位深是AV_SAMPLE_FMT_FLTP,否则打开编码的过程中发现参数错误。这个我是通过查找的编码器后在
那么上下文的位深必须是float类型 AV_SAMPLE_FMT_FLTP
查找编码器,分配上下文,设置上下文参数,打开编码器后。接下来就应该将数据输入编码器。
如何将数据输入编码器呢?
这里讲两个概念AVFrame,AVPacket.
AVFrame是没有编码的数据,AVPacket是编码后的数据。
所以就是将AVFrame输入编码器进行编码,然后得到AVPacket数据,然后将AVPacket的保存进文件就是aac数据了。
由于ffmpeg的avformat_open_input是将多媒体文件打开。avformat_open_input既可以打开文件,也可以打开设备。avformat_open_input就是将所有的东西当做多媒体文件打开,所以通过av_read_frame读出AVPacket数据,按道理说AVPacket存的是编码后的数据,可是这里存储的是PCM原始数据。
编码器需要的是AVFrame原始数据,所以我们这里把AVPacket数据存进AVFrame当中。
如何创建AVFrame
有两个函数
av_frame_alloc
av_frame_get_buffer 实际上数据是存在AVFrame中buffer当中,会根据设置的frame的采样大小(位深),通道个数,采样个数进行分配buffer。
AVFrame* create_frame(){
AVFrame *frame = av_frame_alloc(); //其中的buffer才是存着主要的数据
if(!frame){
return NULL;
}
frame->nb_samples = 512; //因为我的电脑的每个包的大小是2048/位深32位(4个字节)/1个通道 所以就是2048/4/1=512
frame->format = AV_SAMPLE_FMT_S16;//AAC需要的位深
frame->channel_layout = AV_CH_LAYOUT_STEREO; //AAC需要的通道
av_frame_get_buffer(frame, 0);
if(!frame->data[0]){
printf("Error, Failed to alloc buf in frame!\n");
//内存泄漏
goto __ERROR;
}
return frame;
__ERROR:
if(frame){
av_frame_free(&frame);
}
return NULL;
}
如何存进去呢?
1、创建缓存区
av_samples_alloc_array_and_samples(&dst_data,
&dst_linesize,
2,
512,
AV_SAMPLE_FMT_S16,
0);
2、将AVPacket中的数据放进缓存区
memcpy((void *)dst_data[0], (void *)pkt.data, pkt.size);
3、将缓存区的数据放进AVFrame
memcpy((void *)frame->data[0], (void*)dst_data[0], dst_linesize);
4、将frame数据送给编码器
ret = avcodec_send_frame(codec_ctx, frame);
5、接受编码后的数据AVPacket
ret = avcodec_receive_packet(codec_ctx, pkt);
avcodec_send_frame传入要编码的数据不一定传入就会马上返回编码后的数据,而是编码好多数据后多次返回。
所以获取编码后的数据要下面这样写
while(ret > 0){
ret = avcodec_receive_packet(codec_ctx, pkt);
if(ret < 0 ){
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
break;
}else{
printf("error encodeing avframe");
exit(-1);
}
}
fwrite(pkt->data,pkt->size, 1, outFile);
fflush(outFile);
av_packet_unref(&pkt);
}
直到avcodec_receive_packet返回<= 0,说明当前没有要处理的数据了,可以接受下一个AVFrame数据进行处理了。
这个<=0 又有下面的情况
返回值是AVERROR(EAGAIN) 当前数据还不能处理
返回值是AVERROR_EOF 当前数据读完了
其他情况 发生了编码异常,直接终止程序
6、读取到编码后的数据后写入文件
fwrite(pkt->data,pkt->size, 1, outFile);
fflush(outFile);
在实践过程中 总是发现解码返回-35.那么对于这样就应该休眠一会再继续。
源代码地址
https://gitee.com/creat151/ffmpeg.git