FFmpeg5.0源码阅读—— avcodec_send_packet&&avcodec_receive_frame

  摘要:本文主要描述了FFmpeg中用于解码的接口的具体调用流程,详细描述了该接口被调用时所作的具体工作。
  关键字ffmpegavcodec_send_packetavcodec_receive_frame
  读者须知:读者需要了解FFmpeg的基本使用流程,以及一些FFmpeg的基本常识,了解FFmpegIO相关的内容,以及大致的解码流程。

  avcodec_send_packet接口将AVPacket数据发送给解码器进行解码,然后通过avcodec_receive_frame获取数据。

1 avcodec_send_packet

在这里插入图片描述

  avcodec_send_packet首先是检查解码器的合法性以及数据是否为空,如果输入数据和Context符合要求就会删除AVcodecContext->internal->buffer_pkt中缓存的一帧码流数据,将输入的Packet拷贝到该buffer上。av_bsf_send_packet只是拷贝增加输入的Packet引用计数到AVBSFInternal->buffer_pkt,最后如果缓存的buffer_frame是空的就会调用decode_receive_frame_internal解码帧,该过程根据配置项可谓同步也可为异步。

1.1 decode_receive_frame_internal

  decode_receive_frame_internal内就是真正的调用解码流程,如果解码器的receive_frame函数指针不为空就直接调用解码器的receive_frame进行解码该过程是同步的。否则就会调用decode_simple_receive_frame进行解码。解码完成后需要根据解码的数据和当前解码器Context的一些pts相关的值计算当前帧的具体pts和dts,另外如果有指定FrameDecodeData还会调用后处理流程fdd->post_process进行解码。

1.2 decode_simple_receive_frame

  decode_simple_receive_frame主要是调用decode_simple_internal进行解码。这里使用的Packet就是前面存储在AVBSFInternal中的buffer_pkt。然后就是实际调用解码的流程,如果没有配置解码线程就直接调用每个解码器对应的函数指针的avctx->codec->decode直接同步拿到帧。否则就会调用ff_thread_decode_frame进行多线程解码。
  FFmpeg中每种格式,解码器等都有自己的描述结构,比如下面是gif的解码器描述。

static const AVClass decoder_class = {
    .class_name = "gif decoder",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
    .category   = AV_CLASS_CATEGORY_DECODER,
};

const AVCodec ff_gif_decoder = {
    .name           = "gif",
    .long_name      = NULL_IF_CONFIG_SMALL("GIF (Graphics Interchange Format)"),
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = AV_CODEC_ID_GIF,
    .priv_data_size = sizeof(GifState),
    .init           = gif_decode_init,
    .close          = gif_decode_close,
    .decode         = gif_decode_frame,
    .capabilities   = AV_CODEC_CAP_DR1,
    .caps_internal  = FF_CODEC_CAP_INIT_THREADSAFE |
                      FF_CODEC_CAP_INIT_CLEANUP,
    .priv_class     = &decoder_class,
};

  ff_thread_decode_frame内都是通过锁和条件变量进行同步的。首先根据当前的状态获取一个解码线程的Context,然后将当前的Packet提交到该线程上,提交就是将一帧数据增加引用让解码Context的avpkt也占用输入帧的引用计数,提交完成就会发送信号通知在等待的解码线程启动。
  解码线程起始在avcodec_open2的时候就已经创建好了,在wait数据。具体的执行函数就是frame_worker_thread,该函数内就是调用codec->decode进行解码解码完成后就会发送通知到ff_thread_decode_frame中取解码完的帧。令条件if (!p->avctx->thread_safe_callbacks && ( p->avctx->get_format != avcodec_default_get_format || p->avctx->get_buffer2 != avcodec_default_get_buffer2))为A,如果A为true则当前线程是会被阻塞的,完全就是同步运行,否则就是多线程的。

if (!p->avctx->thread_safe_callbacks && (
         p->avctx->get_format != avcodec_default_get_format ||
         p->avctx->get_buffer2 != avcodec_default_get_buffer2)) {
        while (atomic_load(&p->state) != STATE_SETUP_FINISHED && atomic_load(&p->state) != STATE_INPUT_READY) {
            int call_done = 1;
            pthread_mutex_lock(&p->progress_mutex);
            while (atomic_load(&p->state) == STATE_SETTING_UP)
                pthread_cond_wait(&p->progress_cond, &p->progress_mutex);

            switch (atomic_load_explicit(&p->state, memory_order_acquire)) {
            case STATE_GET_BUFFER:
                p->result = ff_get_buffer(p->avctx, p->requested_frame, p->requested_flags);
                break;
            case STATE_GET_FORMAT:
                p->result_format = ff_get_format(p->avctx, p->available_formats);
                break;
            default:
                call_done = 0;
                break;
            }
            if (call_done) {
                atomic_store(&p->state, STATE_SETTING_UP);
                pthread_cond_signal(&p->progress_cond);
            }
            pthread_mutex_unlock(&p->progress_mutex);
        }
    }

2 avcodec_receive_frame

  avcodec_receive_frame比较简单先检查buffer_frame有没有数据,有的话就直接返回,没有即调用decode_receive_frame_internal进行解码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值