FFmpeg解码详细流程

news2024/11/5 15:05:52

介绍

  1. FFmpeg的 libavcodec 模块完成音视频多媒体的编解码模块。
  2. 老版本的 FFmpeg 将avcodec_decode_video2()作为视频的解码函数 API,将avcodec_decode_audio4()作为音频的解码函数 API;从 3.4版本开始已经将二者标记为废弃过时 API(attribute_deprecated)。
  3. 新版本 FFmpeg 将 avcodec_send_packet()avcodec_receive_frame() 作为音视频的解码函数 API,但同时仍然保留了对老接口的兼容,通过avcodec_decode_video2()、avcodec_decode_audio4()调用 compat_decode()完成对新 API 的封装。
//具体可以参考 FFmpeg 中 doc/APIchanges 中的记录.

2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
  Add a new audio/video encoding and decoding API with decoupled input
  and output -- avcodec_send_packet(), avcodec_receive_frame(),
  avcodec_send_frame() and avcodec_receive_packet().
  
2017-09-26 - b1cf151c4d - lavc 57.106.102 - avcodec.h
  Deprecate AVCodecContext.refcounted_frames. This was useful for deprecated
  API only (avcodec_decode_video2/avcodec_decode_audio4). The new decode APIs
  (avcodec_send_packet/avcodec_receive_frame) always work with reference
  counted frames.

视频解码详细流程

  1. 以解码 H264标准为例,从 main 函数到最后的 MB 宏块解码。
    在这里插入图片描述

解码的核心 API 介绍

avcodec_send_packet()

  1. API 申明介绍
 * @param avctx codec context
 * @param[in] avpkt The input AVPacket. Usually, this will be a single video
 *                  frame, or several complete audio frames.
 *                  Ownership of the packet remains with the caller, and the
 *                  decoder will not write to the packet. The decoder may create
 *                  a reference to the packet data (or copy it if the packet is
 *                  not reference-counted).
 *                  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.
 *
 * @return 0 on success, otherwise negative error code:
 *      AVERROR(EAGAIN):   input is not accepted in the current state - user
 *                         must read output with avcodec_receive_frame() (once
 *                         all output is read, the packet should be resent, and
 *                         the call will not fail with EAGAIN).
 *      AVERROR_EOF:       the decoder has been flushed, and no new packets can
 *                         be sent to it (also returned if more than 1 flush
 *                         packet is sent)
 *      AVERROR(EINVAL):   codec not opened, it is an encoder, or requires flush
 *      AVERROR(ENOMEM):   failed to add packet to internal queue, or similar
 *      other errors: legitimate decoding errors
 */
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
  1. avcodec_send_packet()定义解析
    该 API 可以理解为 FFmpeg 完成音视频解码的核心函数,从函数的实现中可以看到,首先会调用av_bsf_send_packet()函数将输入的 AVPacket 拷贝到 buffer 中。最后当 buffer_frame为空的时候,就调用decode_receive_frame_internal()函数完成真正的解码。

    decode_receive_frame_internal()内部会调用decode_simple_receive_frame()完成解码。

    decode_simple_receive_frame()内部会调用decode_simple_internal()完成解码。

    而在decode_simple_internal()通过函数指针(*decode())指向不同的解码器完成解码不同标准的解码过程;如果涉及到多线程解码,还会涉及到ff_thread_decode_frame(),多线程解码不详细介绍。
    在这里插入图片描述

  2. (*decode())函数指针
    在 decode_simple_internal()通过ret = avctx->codec->decode(avctx, frame, &got_frame, pkt)实现对不同解码器的调用,完成具体的解码过程。

    以h264解码为例,h264_decode_frame()完成了具体的解码过程,其中核心函数是 decode_nal_units()进行 NAUL 解码;其中ff_H2645_packet_split()进行码流解析, ff_h264_execute_decode_slices()进行 Slice 级解码。

    在ff_h264_execute_decode_slices()核心函数是 decode_slice(),对每个 Slice 进行解码。

    decode_slice()中是视频数据的核心解码过程,主要分成四个模块:熵解码宏块解码环路滤波错误隐藏。其中 ff_h264_decode_mb_cabac()完成熵解码(如果熵解码是 cavlc,则会ff_h264_decode_mb_cavlac 完成熵解码);ff_h264_decode_mb()完成宏块 MB 解码;loop_filter()完成环路滤波;er_add_slice()完成错误隐藏处理。
    在这里插入图片描述

avcodec_receive_frame()

  1. API 申明介绍
/**
 * Return decoded output data from a decoder.//从解码器返回解码后的输出数据
 *
 * @param avctx codec context
 * @param frame This will be set to a reference-counted video or audio
 *              frame (depending on the decoder type) allocated by the
 *              decoder. Note that the function will always call
 *              av_frame_unref(frame) before doing anything else.
 *
 * @return
 *      0:                 success, a frame was returned
 *      AVERROR(EAGAIN):   output is not available in this state - user must try
 *                         to send new input
 *      AVERROR_EOF:       the decoder has been fully flushed, and there will be
 *                         no more output frames
 *      AVERROR(EINVAL):   codec not opened, or it is an encoder
 *      AVERROR_INPUT_CHANGED:   current decoded frame has changed parameters
 *                               with respect to first decoded frame. Applicable
 *                               when flag AV_CODEC_FLAG_DROPCHANGED is set.
 *      other negative values: legitimate decoding errors
 */
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
  1. avcodec_receive_frame()定义解析
    从avcodec_receive_frame()函数内部实现可以看到,核心逻辑相对简单,先判断buffer_frame里是否有数据,如果有直接调用av_frame_move_ref()完成frame 拷贝过程,解码后数据从buffer_frame拷贝到 frame 中(即把数据从 AVCodecContext 中拷贝到 AVFrame 中);如果 buffer_frame中没有数据,则需要调用decode_receive_frame_internal()完成具体的解码,该步骤和 send packet 模块相同。
    在这里插入图片描述

官方解码实例

/*
 * Copyright (c) 2001 Fabrice Bellard
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @file
 * video decoding with libavcodec API example
 *
 * @example decode_video.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavcodec/avcodec.h>

#define INBUF_SIZE 4096

static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
                     char *filename)
{
    FILE *f;
    int i;

    f = fopen(filename,"w");
    fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
    for (i = 0; i < ysize; i++)
        fwrite(buf + i * wrap, 1, xsize, f);
    fclose(f);
}

static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
                   const char *filename)
{
    char buf[1024];
    int ret;

    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0) {
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }

    while (ret >= 0) {
        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);
        }

        printf("saving frame %3d\n", dec_ctx->frame_number);
        fflush(stdout);

        /* the picture is allocated by the decoder. no need to
           free it */
        snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
        pgm_save(frame->data[0], frame->linesize[0],
                 frame->width, frame->height, buf);
    }
}

int main(int argc, char **argv)
{
    const char *filename, *outfilename;
    const AVCodec *codec;
    AVCodecParserContext *parser;
    AVCodecContext *c= NULL;
    FILE *f;
    AVFrame *frame;
    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data;
    size_t   data_size;
    int ret;
    AVPacket *pkt;

    if (argc <= 2) {
        fprintf(stderr, "Usage: %s <input file> <output file>\n"
                "And check your input file is encoded by mpeg1video please.\n", argv[0]);
        exit(0);
    }
    filename    = argv[1];
    outfilename = argv[2];

    pkt = av_packet_alloc();
    if (!pkt)
        exit(1);

    /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
    memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    /* find the MPEG-1 video decoder */
    codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    parser = av_parser_init(codec->id);
    if (!parser) {
        fprintf(stderr, "parser not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    /* For some codecs, such as msmpeg4 and mpeg4, width and height
       MUST be initialized there because this information is not
       available in the bitstream. */

    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    f = fopen(filename, "rb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    while (!feof(f)) {
        /* read raw data from the input file */
        data_size = fread(inbuf, 1, INBUF_SIZE, f);
        if (!data_size)
            break;

        /* use the parser to split the data into frames */
        data = inbuf;
        while (data_size > 0) {
            ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
                                   data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
            if (ret < 0) {
                fprintf(stderr, "Error while parsing\n");
                exit(1);
            }
            data      += ret;
            data_size -= ret;

            if (pkt->size)
                decode(c, frame, pkt, outfilename);
        }
    }

    /* flush the decoder */
    decode(c, frame, NULL, outfilename);

    fclose(f);

    av_parser_close(parser);
    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_packet_free(&pkt);

    return 0;
}

参考

  1. http://ffmpeg.org/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/836712.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java.util.LinkedList 中的pop() poll() peek()三个方法的区别

先说结果&#xff1a; /** 这两个方法底层调用的都是 unlinkFirst () 这个方法** pop这个方法是通过removeFirst() 在去调用的unlinkFirst();* poll() 是直接调用的unlinkFirst();** 而 removeFirst() 这个方法&#xff0c;在头结点为空的时候会报NoSuchElementException();** …

re学习(27)攻防世界toddler_regs(字符串函数总结+交叉引用)

找不到main&#xff08;&#xff09;函数&#xff0c;查找字符串&#xff0c;发现特殊字符串 定位字符串出现的位置&#xff0c;反汇编出代码&#xff0c;进行分析“ flag{Xp0int_1s_n1c3_but_Xp0intJNU_is_we1rd} 总结 一.CP&#xff1a;shiftF12 展示所有的字符串; ctrlx查…

深度学习里面为什么喜欢用对数

1.这样可以简化计算并提高稳定性&#xff0c;有着相同的临界点 2.由P(A).P(B)给出两个独立事件A和B共同出现的概率。如果我们使用log&#xff0c;即log(P(A)) log(P(B))&#xff0c;这很容易映射到一个和。因此&#xff0c;更容易将神经元触发的“事件”作为线性函数来处理。…

对于数据库查询索引和查字典索引的理解

之前面试问过我对于数据库索引的理解&#xff0c;这个问题不是具体的问题太宽泛&#xff0c;面试官也没进行引导&#xff0c;我不知道怎么回答&#xff0c;下面是结合查字典进行理解。 查字典 拿查字典举例&#xff0c;知道一个字怎么写但是不知道具体的意思以及发音&#xff…

6.如何用CSV文件生成异构图数据集

我们将使用GroupLens研究小组收集的MovieLens数据集。 这个数据集描述了MovieLens的五星评级和标记活动。该数据集包含来自600多名用户的9000多部电影的约10万个评分。我们将使用该数据集生成两种节点类型&#xff0c;分别保存电影和用户的数据&#xff0c;以及一种连接…

【我们一起60天准备考研算法面试(大全)-第三十四天 34/60】【前缀和】【北邮】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

【Linux】Linux下git的使用

文章目录 一、什么是git二、git发展史三、Gitee仓库的创建1.新建仓库2.复制仓库链接3.在命令行克隆仓库3.1仓库里的.gitignore是什么3.2仓库里的git是什么 三、git的基本使用1.将克隆仓库的新增文件添加到暂存区(本地仓库)2.将暂存区的文件添加到.git仓库中3.将.git仓库中的变化…

4.msf辅助模块

目录 1 在虚拟机中设置与外部相同的网段 2 当前内网中的可用IP arp_sweep 3 搜索指定IP的TCP端口信息 portscan/tcp 4 扫描http服务的路由 http/dir_scanner 5 SSH密码爆破 ssh/ssh_login 1 在虚拟机中设置与外部相同的网段 我真实机的地址的网段是192.168.0 我虚拟…

【大模型】开源且可商用的大模型通义千问-7B(Qwen-7B)来了

【大模型】开源且可商用的大模型通义千问-7B&#xff08;Qwen-7B&#xff09;来了 新闻通义千问 - 7B 介绍评测表现快速使用环境要求安装相关的依赖库推荐安装flash-attention来提高你的运行效率以及降低显存占用使用 Transformers 运行模型使用 ModelScope 运行模型 量化长文本…

SAP标准搜索帮助(Search Help)改造之标准增强点

1. 搜索帮助加载前 包含程序&#xff1a;LWDTMO01 行&#xff1a;40 标准搜索帮助输出前的控制&#xff08;影响标准Search Help CDS View Search Help&#xff08;如果在标准Search Help搜索帮助出口函数上修改控制参数&#xff0c;则不会影响 CDS View Search Help&#xf…

【Kubernetes】Kubernetes之二进制部署

kubernetes 一、Kubernetes 的安装部署1. 常见的安装部署方式1.1 Minikube1.2 Kubeadm1.3 二进制安装部署2. K8S 部署 二进制与高可用的区别2.1 二进制部署2.2 kubeadm 部署二、Kubernetes 二进制部署过程1. 服务器相关设置以及架构2. 操作系统初始化配置3. 部署 etcd 集群4. 部…

Vue——formcreate表单设计器自定义组件实现(二)

前面我写过一个自定义电子签名的formcreate表单设计器组件&#xff0c;那时初识formcreate各种使用也颇为生疏&#xff0c;不过总算套出了一个组件不是。此次时隔半年又有机会接触formcreate&#xff0c;重新熟悉和领悟了一番各个方法和使用指南。趁热打铁将此次心得再次分享。…

python爬虫1:基础知识

python爬虫1&#xff1a;基础知识 前言 ​ python实现网络爬虫非常简单&#xff0c;只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点&#xff0c;方便以后复习。 目录结构 文章目录 python爬虫1&#xff1a;基础知识1. 基础认知1.1 什么是爬虫&…

【2023】XXL-Job 具体通过docker 配置安装容器,再通过springboot执行注册实现完整流程

【2023】XXL-Job 具体通过docker 配置安装容器&#xff0c;再通过springboot执行注册实现 一、概述二、安装1、拉取镜像2、创建数据库3、创建容器并运行3、查看容器和日志4、打开网页 127.0.0.1:9051/xxl-job-admin/ 三、实现注册测试1、创建一个SpringBoot项目、添加依赖。2、…

steam搬砖项目拆解,长久稳定

steam搬砖指将"CS:GO"的游戏道具从国外游戏平台搬到国内的游戏平台&#xff08;一般都是在网易BUFF&#xff09;进行贩卖&#xff0c;从而赚取道具商品差价或者汇率的差价。 首先&#xff0c;Steam是全球最大的游戏平台&#xff0c;拥有上亿的玩家&#xff0c;同时在…

ISC 2023︱诚邀您参与赛宁“安全验证评估”论坛

​​8月9日-10日&#xff0c;第十一届互联网安全大会&#xff08;简称ISC 2023&#xff09;将在北京国家会议中心举办。本次大会以“安全即服务&#xff0c;开启人工智能时代数字安全新范式”为主题&#xff0c;打造全球首场AI数字安全峰会&#xff0c;赋予安全即服务新时代内涵…

数据驱动+自动化测试

自动化测试代码优化 setUp 在每个测试用例执行之前执行 tearDown 在每个测试用例执行完以后执行 所以&#xff0c;可以利用setUp&#xff0c;把测试用例中的通用代码提取出来&#xff0c;减少冗余 数据驱动测试&#xff1a;优化自动化测试 安装&#xff1a; pip install p…

JDK19 - synchronized关键字导致的虚拟线程PINNED

JDK19 - synchronized关键字导致的虚拟线程PINNED 前言一. PINNED是什么意思1.1 synchronized 绑定测试1.2 synchronized 关键字的替代 二. -Djdk.tracePinnedThreads的作用和坑2.1 死锁案例测试2.2 发生原因的推测2.3 总结 前言 在 虚拟线程详解 这篇文章里面&#xff0c;我们…

Protues 仿真报错Internal Exception: access violation in module ‘UNKNOWN‘[7ADEEEA9]

在使用STM32F103C8进行Protues仿真设计的时候&#xff0c;出现了这个报错&#xff0c;通过查找和定位问题&#xff0c;发现是我在配置供电网络的时候配置错误&#xff0c;要配置成如下&#xff1a; 至于为什么回这样&#xff0c;我猜想应该是和这个软件导入STM32芯片的时候&…

300个智商测试FLASH智商游戏ACCESS数据库

最近在找IQ测试方面的数据&#xff0c;网上大多只留传着33道题这种类型&#xff0c;其他的又因各种条件&#xff08;比如图片含水印等&#xff09;不能弄&#xff0c;这是从测智网下载的一些测试智商的游戏数据&#xff0c;游戏文件是FLASH的&#xff0c;扩展名是SWF。 数据包总…