编码器和解码器都维护了一个缓冲区,在刚开始输入数据时,需要多输入几帧,等缓冲区被填充满后,才会在receive端接收到编码或解码后的数据。
另一方面因为存在AVPacket中的数据不一定是一帧(比如音频的数据可能1个AVPacket包含1s的数据,帧率为25的话,就包含25帧),但存在AVFrame中的是一帧数据,所以avcodec_send_packet和avcodec_receive_frame不一定一一对应,调用一次avcodec_send_packet后,可能需要多次调用avcodec_receive_frame,另一篇文章讨论av_read_frame每次返回的视频和音频帧数
解码接口:
avcodec_send_packet和avcodec_receive_frame
内存:frame只需要分配对象本身空间就好,frame->data的空间并不需要分配。函数会判断是否已经给frame->data分配空间,如果没有分配会在函数内部为frame->data分配合适的空间。另一篇文章,AVFrame内存讨论,AVFrame相关api
。举例如下:
int re = avcodec_send_packet(codec_ctx, pkt); if (re != 0) { return; } while( avcodec_receive_frame(codec_ctx, frame) >= 0) { //处理接收后的帧 }
avcodec_receive_frame:send端只把数据放入缓冲区,recive端才是解码并获得数据的函数,如果receive发现已有解码后的数据则直接获取,如果没有则开始解码。
编码接口
数据遗留:在最后应该向avcodec_send_frame(enc_ctx, NULL)传入NULL数据,这样编码器知道后面不会再有数据,就把放在缓冲区中的数据,全部编码并通过avcodec_receive_packet输出出来。
返回值:EAGAIN,意思是需要输入更多的数据,才会有新的数据编码后的数据返回。AVERROR_EOF在从文件中读取数据推流时,出现这个错误是因为文件内的数据读完了。
参数内存:newpkt->data并不需要分配数据空间,只需要给newpkt本身结构体分配空间就好。另一篇文章,关于AVPacket内存与avcodec_receive_packet的关系,AVPacket相关api
func() { avcodec_send_frame(enc_ctx, frame); if(ret < 0) { //Error } while(ret >= 0) { ret = avcodec_receive_packet(enc_ctx, newpkt); if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return;} else if(ret < 0){//Error} } }
在ubuntu虚拟机中采集pc摄像头,并推流实测编解码的四个函数是否一一对应关系,帧率为30:
解码
avcodec_send_packet
avcodec_receive_frame
记录:s1518 r1518 s2754 r2754
s发送的次数,r成功接收的次数
第一次调用avcodec_send_packet,avcodec_receive_frame就成功接收到数据了。并且以后每次也都一一对应调用两个函数。
编码
avcodec_send_frame
avcodec_receive_packet
//s1336 r1286 s867 r817 保持50帧
avcodec_send_frame连续调用50次后,avcodec_receive_packet才成功接收到一次数据,以后每次都一一对应。
在ubuntu虚拟机中采集pc声卡,并推流实测编解码的三个函数是否一一对应关系:
解码
avcodec_send_packet
avcodec_receive_frame
av_interleaved_write_frame
实测当分别送进去128,1024,940个音频采样到avcodec_send_frame时,avcodec_receive_packet一对一返回,开始并没有几帧缓存,av_interleaved_write_frame也一对一执行,也没有一开始缓存几帧。也就是三者总是保持一对一执行,后两者都有返回,并且一开始没有缓存。音频涉及到字节的处理,见另一篇博客:libfdk_aac音频采样数和编码字节数注意
附录:
libavcodec\encode.c
int attribute_align_arg avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) { av_packet_unref(avpkt); if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec)) return AVERROR(EINVAL); if (avctx->codec->receive_packet) { if (avctx->internal->draining && !(avctx->codec->capabilities & AV_CODEC_CAP_DELAY)) return AVERROR_EOF; return avctx->codec->receive_packet(avctx, avpkt); } // Emulation via old API. if (!avctx->internal->buffer_pkt_valid) { int got_packet; int ret; if (!avctx->internal->draining) return AVERROR(EAGAIN); ret = do_encode(avctx, NULL, &got_packet); if (ret < 0) return ret; if (ret >= 0 && !got_packet) return AVERROR_EOF; } av_packet_move_ref(avpkt, avctx->internal->buffer_pkt); avctx->internal->buffer_pkt_valid = 0; return 0; }
void av_packet_unref(AVPacket *pkt) { av_packet_free_side_data(pkt); av_buffer_unref(&pkt->buf); av_init_packet(pkt); pkt->data = NULL; pkt->size = 0; }