旧API使用avcodec_decode_video2来进行
写法:
//旧API需要循环获取视频帧,需要自己实现video_queue
while (av_read_frame(fmt_ctx.get(), &pkt) >= 0) {
if (pkt.stream_index == video_stream_index) {
//packet_queue_put(&video_queue, &pkt);
} else {
av_free_packet(&pkt);
}
}
而新APIavcodec_send_packet & avcodec_receive_frame
通过上下文来操作视频帧:
官方注释:
Unlike with older APIs, the packet is always fully consumed, and if it contains multiple frames (e.g. some audio codecs), will require you to call avcodec_receive_frame() multiple times afterwards before you can send a new packet. It can be NULL (or an AVPacket with data set to NULL and size set to 0); in this case, it is considered a flush packet, which signals the end of the stream. Sending the first flush packet will return success. Subsequent ones are unnecessary and will return AVERROR_EOF. If the decoder still has frames buffered, it will return them after sending a flush packet.
while (av_read_frame(fmt_ctx.get(), m_Packet.get()) >= 0) {
if (m_Packet->stream_index == m_StreamIndex) {
if (avcodec_send_packet(vcodec_ctx.get(), m_Packet.get()) != 0) {
return 0;
}
while (avcodec_receive_frame(vcodec_ctx.get(), m_Frame.get()) == 0) {
//获取到 m_Frame 解码数据,进行格式转换,然后进行渲染
}
}
av_packet_unref(m_Packet.get()); //释放 m_Packet 引用,防止内存泄漏
}
下面就来看一下位于 源码路径 libavcodec/decode.c 的代码(加注释 删判断)
int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
//获取AVCodecInternal结构体
AVCodecInternal *avci = avctx->internal;
av_packet_unref(avci->buffer_pkt);
if (avpkt && (avpkt->data || avpkt->side_data_elems)) {
//数据浅拷贝至avci->buffer_pkt
ret = av_packet_ref(avci->buffer_pkt, avpkt);
}
//再进行一次数据拷贝
//pkt的内容转移到buffer_pkt->bsf
ret = av_bsf_send_packet(avci->bsf, avci->buffer_pkt);
if (ret < 0) {
//释放引用,防止内存泄漏
av_packet_unref(avci->buffer_pkt);
return ret;
}
if (!avci->buffer_frame->buf[0]) {
ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
}
return 0;
}
其实分析的重点就在decode_receive_frame_internal函数中,他调用了 ff_decode_get_packet函数,如注释:
int ff_decode_get_packet(AVCodecContext *avctx, AVPacket *pkt)
{
xxx
//去除部分冗余信息
ret = av_bsf_receive_packet(avci->bsf, pkt);
if (ret == AVERROR_EOF)
avci->draining = 1;
//AVPacket放入链表
ret = extract_packet_props(avctx->internal, pkt);
ret = apply_param_change(avctx, pkt);
#if FF_API_OLD_ENCDEC
if (avctx->codec->receive_frame)
avci->compat_decode_consumed += pkt->size;
#endif
xxx
}
回到上面的官方注释,每加入一个AVPacke到上下文,就必须使用完,不能留到下一次解析,如果是旧API,还需要设置avci->compat_decode_consumed,