ffmpeg中转码时的数据流走向

一.ffmpeg中的解码操作是在函数static int process_input(int file_index)中完成的。

1.在该函数中首先会调用static int get_input_packet(InputFile *f, AVPacket *pkt)函数将通过av_read_frame(f->ctx, pkt)函数将文件中的视频或音频数据读取到AVPacket结构体中。

2.然后通过static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof)函数进行解码操作。

二.在process_input_packet函数中会分别调用decode_audio和decode_video进行音视频的解码。

1.decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_t *duration_pts, int eof, int *decode_failed)此时数据是在AVPacket *pkt中。此函数会调用decode(ist->dec_ctx, decoded_frame, got_output, pkt ? &avpkt : NULL)函数进行解码,待解码的数据在avpkt中。解码后的数据放在decoded_frame中。decode函数的定义为:  

static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
{
    int ret;

    *got_frame = 0;

    if (pkt) {
        ret = avcodec_send_packet(avctx, pkt); //完成此操作后,解码后的数据放在avctx->internal->buff_frame中。
        // In particular, we don't expect AVERROR(EAGAIN), because we read all
        // decoded frames with avcodec_receive_frame() until done.
        if (ret < 0 && ret != AVERROR_EOF)
            return ret;
    }

    ret = avcodec_receive_frame(avctx, frame); //此函数只是进行一个拷贝的操作,将avctx中解码后的数据拷贝给avframe。
    if (ret < 0 && ret != AVERROR(EAGAIN))
        return ret;
    if (ret >= 0)
        *got_frame = 1;

    return 0;
}

主要的解码操作是在avcodec_send_packet中,其定义为:

int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

    if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec)) // 检查 AVCodecContext 是否已打开,并且 AVCodec 是否为解码器
        return AVERROR(EINVAL);

    if (avctx->internal->draining)
        return AVERROR_EOF;

    if (avpkt && !avpkt->size && avpkt->data)
        return AVERROR(EINVAL);

    av_packet_unref(avci->buffer_pkt);
    if (avpkt && (avpkt->data || avpkt->side_data_elems)) {
        ret = av_packet_ref(avci->buffer_pkt, avpkt);     
        if (ret < 0)
            return ret;
    }
    //提交数据包进行过滤,把AVPacket的数据传给 avci->filter.bsfs[0],会将AVPacket的数据清掉,bsf为比特流过滤器,如h264_mp4toannexb
    ret = av_bsf_send_packet(avci->filter.bsfs[0], 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);  //真正的解码函数,也说明解码后的frame是放在avctx->internal->buffer_frame
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
            return ret;
    }

    return 0;
}

解码函数decode_receive_frame_internal,此时的数据是在avctx->internal->filter.bsfs[0]中,该函数会调用decode_simple_receive_frame(avctx, frame)函数,进而调用decode_simple_internal(avctx, frame)函数。

decode_simple_internal(avctx, frame)会先调用ff_decode_get_packet(avctx, pkt)将avpkt数据从avctx->internal->filter.bsf[0]->internal->buff_pkt中提取出到pkt中,pkt的定义为:
    AVCodecInternal   *avci = avctx->internal;
    DecodeSimpleContext *ds = &avci->ds;
    AVPacket           *pkt = ds->in_pkt;
decode_simple_internal(avctx, frame)然后调用avctx->codec->decode(avctx, frame, &got_frame, pkt)进行解码。此语句会根据解码器的类型调用相应的解码器,下面以h264解码为例:

AVCodec ff_h264_decoder = {
    .name                  = "h264",
    .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type                  = AVMEDIA_TYPE_VIDEO,
    .id                    = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .init                  = h264_decode_init,
    .close                 = h264_decode_end,
    .decode                = h264_decode_frame,
    .capabilities          = /*AV_CODEC_CAP_DRAW_HORIZ_BAND |*/ AV_CODEC_CAP_DR1 |
                             AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
                             AV_CODEC_CAP_FRAME_THREADS,
    .hw_configs            = (const AVCodecHWConfigInternal*[]) {
#if CONFIG_H264_DXVA2_HWACCEL
                               HWACCEL_DXVA2(h264),
#endif
#if CONFIG_H264_D3D11VA_HWACCEL
                               HWACCEL_D3D11VA(h264),
#endif
#if CONFIG_H264_D3D11VA2_HWACCEL
                               HWACCEL_D3D11VA2(h264),
#endif
#if CONFIG_H264_NVDEC_HWACCEL
                               HWACCEL_NVDEC(h264),
#endif
#if CONFIG_H264_VAAPI_HWACCEL
                               HWACCEL_VAAPI(h264),
#endif
#if CONFIG_H264_VDPAU_HWACCEL
                               HWACCEL_VDPAU(h264),
#endif
#if CONFIG_H264_VIDEOTOOLBOX_HWACCEL
                               HWACCEL_VIDEOTOOLBOX(h264),
#endif
                               NULL
                           },
    .caps_internal         = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_EXPORTS_CROPPING,
    .flush                 = flush_dpb,
    .init_thread_copy      = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),
    .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),
    .profiles              = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
    .priv_class            = &h264_class,
};

avctx->codec->decode(avctx, frame, &got_frame, pkt)会直接指向h264_decode_frame(AVCodecContext *avctx, void *data,
int *got_frame, AVPacket *avpkt)函数。在h264_decode_frame中会先定义以下四个变量。
    const uint8_t *buf = avpkt->data;
    int buf_size       = avpkt->size;
    H264Context *h     = avctx->priv_data;
    AVFrame *pict      = data;
然后调用decode_nal_units(h, buf, buf_size)函数解码NALU单元。在decode_nal_units函数中:
1.先调用ff_h2645_packet_split(&h->pkt, buf, buf_size, avctx, h->is_avc,
                                h->nal_length_size, avctx->codec_id, avctx->flags2 & AV_CODEC_FLAG2_FAST)函数,进行码流的解析,在其中调用h264_parse_nal_header(nal, logctx)解析NALU单元头部。得到NALU数据。而且这函数会对h->pkt进行数据填充。此时的数据被转移到h->pkt中。
2.然后调用ff_h264_queue_decode_slice(h, nal)函数进行片解码,其中H2645NAL *nal = &h->pkt.nals[i]。

最后会调用finalize_frame(h, pict, h->next_output_pic, got_frame)函数将解码后的数据赋给pict也就是data,从而最后将解码后的数据传输出去。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值