音视频学习笔记——ffmpeg解码流程

✊✊🌈大家好!本篇文章主要记录自己在进行音视频学习中,整理的部分ffmpeg解码相关的内容重点😇。
首先重新梳理了ffmpeg解码流程,重点学习avcodec_send_packet()avcodec_receive_frame()在解码中的应用,以及函数返回值的意义。


本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习,梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育


1.ffmpeg解码

视频的存储方式一般都是MP4、avi、FLV等封装的格式,如果需要在设备进行播放视频,就需要对其进行相应的处理,图片一般需要yuv或rgb格式的图片数据才能进行显示,音频需要pcm的格式数据进行播放。
ffmpeg音视频编解码依赖libavcodec。其为我们提供一套架构,其中包含了编解码器。

以下为ffmpeg解码过程中API的使用:

FFmpeg提供了两组函数,分别⽤于编码和解码:
解码:avcodec_send_packet()、avcodec_receive_frame()。
编码:avcodec_send_frame()、avcodec_receive_packet()。

在FFMPEG解码过程中 avcodec_send_packet() 和 avcodec_receive_frame() 通常是同时使用的,先调用 avcodec_send_packet() 送入要解码的数据包,然后调用 avcodec_receive_frame()获取解码后的音视频数据。

使用流程如下:

  1. 配置并打开解码器。

  2. 输⼊有效的数据:
    解码:调⽤avcodec_send_packet()给解码器传⼊包含原始的压缩数据的AVPacket对象。
    编码:调⽤ avcodec_send_frame()给编码器传⼊包含解压数据的AVFrame对象。
    两种情况下推荐AVPacket和AVFrame都使⽤refcounted(引⽤计数)的模式,否则libavcodec可能不得不对输⼊的数据进⾏拷⻉。

  3. 在⼀个循环体内去接收codec的输出,即周期性地调⽤avcodec_receive_*()来接收codec输出的数据:
    解码:调⽤avcodec_receive_frame(),如果成功会返回⼀个包含未压缩数据的AVFrame。
    编码:调⽤avcodec_receive_packet(),如果成功会返回⼀个包含压缩数据的AVPacket。
    反复地调⽤avcodec_receive_packet()直到返回 AVERROR(EAGAIN)或其他错误。

    返回AVERROR(EAGAIN)错误表示codec需要新的输⼊来输出更多的数据。对于每个输⼊的packet或frame,codec⼀般会输出⼀个frame或packet,但是也有可能输出0个或者多于1个。

    • 通常解码开始,通过avcodec_send_packet()送入几十个数据包,对应的avcodec_receive_frame()都没有音视频帧输出。等送入的数据包足够多后,avcodec_receive_frame()才开始输出前面一开始送入进行解码的音视频帧。
  4. 流处理结束的时候需要flush(冲刷) codec。因为codec可能在内部缓冲多个frame或packet,出于性能或其他必要的情况(如考虑B帧的情况),此时avcodec_receive_frame()还是会有音视频帧输出。

    处理流程如下:

    调⽤avcodec_send_*()传⼊的AVFrame或AVPacket指针设置为NULL。 这将进⼊draining mode(排⽔模式)。

    当重新开启codec时,需要先调⽤ avcodec_flush_buffers()来重置codec。

2.avcodec_send_packet()、avcodec_receive_frame()

重点介绍下avcodec_send_packet()、avcodec_receive_frame()在解码中的应用:

static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
                   FILE *outfile)
{
    int ret;
    /* 发送数据给解码器,返回数据大小 */
    ret = avcodec_send_packet(dec_ctx, pkt);
    if(ret == AVERROR(EAGAIN))
    {
        fprintf(stderr, "由于解码器内部缓存已满,送入的packet未被接收,需要avcodec_receive_frame()读取掉一些已经解码的音视频帧后,才能继续送入。");
    }
    else if (ret < 0)
    {
        fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
                av_get_err(ret), pkt->size);
        return;
    }
		//数据有效时
    while (ret >= 0)
    {
        // avcodec_receive_frame内部每次都先调用,返回以解码的输出数据
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0)
        {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }
        static int s_print_format = 0;
        if(s_print_format == 0)
        {
            s_print_format = 1;
            print_video_format(frame);
        }

        // 一般H264默认为 AV_PIX_FMT_YUV420P
        // frame->linesize[1]  对齐的问题
        // 正确写法  linesize[]代表每行的字节数量,所以每行的偏移是linesize[]
        for(int j=0; j<frame->height; j++)
            fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);
        for(int j=0; j<frame->height/2; j++)
            fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);
        for(int j=0; j<frame->height/2; j++)
            fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);
    }
}

函数返回值

1.int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

通常有:

  • 0:正常返回,意味着送入的packet被解码器正常接收。
  • AVERROR(EAGAIN):由于解码器内部缓存已满,送入的packet未被接收,需要avcodec_receive_frame()读取掉一些已经解码的音视频帧后,才能继续送入。
  • AVERROR(EOF):当send_packet送入为NULL时才会触发该状态,通知解码器输入packet已结束,后续不再送入packet。
  • AVERROR(EINVAL):解码器没有打开
  • AVERROR(ENOMEM):通常是内存不足

2.int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

  • 0:正常返回,意味着输出一帧解码后的音频/视频帧,frame中有解码后的帧数据填充。
  • AVERROR(EAGAIN):由于解码器内部已空,没有音视频帧解码输出,frame中没有填充,需要avcodec_send_packet()继续送入数据包以便后续继续解码。
  • AVERROR(EOF):当send_packet送入为NULL时才会触发该状态,通知解码器输入packet已结束,后续不再送入packet。
  • AVERROR(EINVAL):解码器没有打开

3.小结

结合课程内容和其他博客,首先重新梳理了ffmpeg解码流程,重点学习avcodec_send_packet()、avcodec_receive_frame()在解码中的应用,以及函数返回值的意义。通过写博客加深自己对解码过程的理解。

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

君莫笑lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值