ffmpeg avcodec_send_packet avcodec_receive_frame解码接口使用讲解

解码demo

通常来说,一个packet会被解码出一个frame,不过也存在一个packet被解码出多个frame或者多个packet才能解码出一个frame的情况,甚至也有些解码器在输入以及输出端上可能会有延迟。因此原来的API在某种程度上存在对调用者误导的可能,使得调用者认为输入的一个或者多个Packet就对应着解码器所输出的一个frame,但实际上可能并非如此。

新的API完全隐藏了“解码”这一概念,只提供一个输入packet的接口以及输出frame的接口,如此一来调用者可以不必了解解码器的具体细节,只需要了解这两个接口的调用规则就能写出适用于所有解码器的代码。
上demo

void decodeThread(void *arg)
{
    AVPacket *pkt;
    AVPacket pending_pkt;
    bool eof = false;
    bool packet_eof = false;
    int ret;
    bool pkt_pending = false;
    Input *input = (Input *)arg;
    av_init_packet(&pending_pkt);
    while (!input->is_req_exit() && !eof) {
        int got_picture = 0;
        AVFrame *frame;
        //线程内自动回收解码后的帧(帧链表大小为kMaxFrameCacheNum)超过则释放
        if (input->vfrm_list.size() >= Input::kMaxFrameCacheNum) {
            if (input->realtime) {
                AVFrame *first_frame = input->PopFrame();
                // printf("drop frame pts: %ld\n", first_frame->pts);
                av_frame_free(&first_frame);
            } else {
                msleep(5);
            }
            continue;
        }

        if (!pkt_pending) {
            pkt = input->PopPacket();
            if (!pkt) {
                if (packet_eof)
                    goto get_frame;
                msleep(1);
                continue;
            }
            if (pkt->size == 0 && !input->realtime)
            {
                printf("packet_eof = true \n");
                packet_eof = true;
            }
        } else {
            pkt = &pending_pkt;//发上次没发成功的包
            pkt_pending = false;
        }
        ret = avcodec_send_packet(input->acc, pkt);
        if (ret == AVERROR(EAGAIN)) {
            //if(input->slice_idx == 1)
            //    av_log(NULL, AV_LOG_ERROR, "pkt pending\n");
            pkt_pending = true;
            if (pkt != &pending_pkt)
                av_packet_move_ref(&pending_pkt, pkt);
        } else {
            if (pkt == &pending_pkt)
            {
                av_packet_unref(pkt);
            }
        }
        if (pkt != &pending_pkt)
        {
            av_packet_free(&pkt);
        }
        
    get_frame:
        frame = av_frame_alloc();
        av_assert0(frame);
        do {
            if (input->is_req_exit())
                break;
            ret = avcodec_receive_frame(input->acc, frame);
            if (ret == AVERROR_EOF) {//取完数据帧复位解码器
                avcodec_flush_buffers(input->acc);
                av_log(NULL, AV_LOG_ERROR, "avcodec_receive_frame eof\n");
                eof = true;
                break;
            }
            if (ret >= 0) {           
                got_picture = 1;
                break;
            }
            //if (ret == AVERROR(EAGAIN))
            //    msleep(5);
        } while (ret != AVERROR(EAGAIN));
        if (got_picture) {
            input->PushFrame(frame);
        } else {
            av_frame_free(&frame);
        }
    }
}
1、获取源数据pkt
pkt = input->PopPacket();
2、将包发送到解码器
avcodec_send_packet(input->acc, pkt);
3、接收解码帧
avcodec_receive_frame(input->acc, frame);

关于接口返回值:
avcodec_send_packet返回AVERROR(EAGAIN)表示当前还无法接受新的packet,还有frame没有取出来,所以有了:

if (ret == AVERROR(EAGAIN)) {
    pkt_pending = true;
    if (pkt != &pending_pkt)
        av_packet_move_ref(&pending_pkt, pkt);
} 

无法接收的pkt放到pending_pkt,接着取解码后的帧。取新的pkt时判断是否pkt_pending 是则使用之前保存的pending_pkt。
可能是存在B帧的时候会这样,因为B帧需要依赖后面的帧,所以不会解码出来,等到后面的帧传入后,就会有多个帧需要读取。这时解码器应该就不接受新的packet。
另,av_packet_move_ref这个函数就是完全的只复制,source的值完全的搬到destination,并且把source重置掉。其实就是搬了个位置,buf的引用数不改变。
新一代API是一个状态机。调用API是一种动作,API的返回值就是一种状态,通过动作可以进行状态的转换。正常情况下,状态机有6种状态:

  • send 0 :send_packet返回值为0,正常状态,意味着输入的packet被解码器正常接收。
  • send EAGAIN :send_packet返回值为EAGAIN,输入的packet未被接收,需要输出一个或多个的frame后才能重新输入当前packet。
  • send EOF :send_packet返回值为EOF,当send_packet输入为NULL时才会触发该状态,用于通知解码器输入packet已结束。
  • receive 0 :receive_frame返回值为0,正常状态,意味着已经输出一帧。
  • receive EAGAIN:receive_frame返回值为EAGAIN,未能输出frame,需要输入更多的packet才能输出当前frame。
  • receive EOF :receive_frame返回值为EOF,当处于send EOF状态后,调用一次或者多次receive_frame后就能得到该状态,表示所有的帧已经被输出。
    在这里插入图片描述
    解码帧全部接收完成会返回EOF这时需要调用
    avcodec_flush_buffers(input->acc);
    来复位重置解码器。
    本文参考:
    https://zhuanlan.zhihu.com/p/112023575
    https://www.cnblogs.com/TaigaCon/p/10041926.html
  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值