FFmpeg5.0源码阅读——VideoToobox硬件解码

  摘要:本文描述了FFmpeg中videotoobox解码器如何进行解码工作,如何将一个编码的码流解码为最终的裸流。
  关键字:videotoobox,decoder,ffmpeg
  VideoToolbox 是一个低级框架,提供对硬件编码器和解码器的直接访问。 它提供视频压缩和解压缩服务,以及存储在 CoreVideo 像素缓冲区中的光栅图像格式之间的转换服务。 这些服务以会话对象(压缩、解压缩和像素传输)的形式提供,并作为 Core Foundation (CF) 类型输出。 VideoToolbox支持H.263, H.264, HEVC, MPEG-1, MPEG-2, MPEG-4 Part 2, ProRes解码,H.264, HEVC, ProRes编码,最新的版本似乎也支持了VP9解码。

1 主流程

1.1 涉及的Context

  FFmpeg中每个解码器都有自己的Context描述,该描述按照约定的格式描述对应的解码器参数和解码器的处理函数指针。FFmpeg中的VideoToolbox解码器主要实现代码在libavcodec/videotoobox.{h,c}中,其中针对每一种支持的解码格式定义了一个独立的Context,比如ff_h263_videotoolbox_hwaccel,ff_h263_videotoolbox_hwaccel,ff_h264_videotoolbox_hwaccel,...等,只是实现上有差异,我们主要关注其中一个即可,这里主要关注ff_h264_videotoolbox_hwaccel

const AVHWAccel ff_h264_videotoolbox_hwaccel = {
    .name           = "h264_videotoolbox",
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = AV_CODEC_ID_H264,
    .pix_fmt        = AV_PIX_FMT_VIDEOTOOLBOX,
    .alloc_frame    = ff_videotoolbox_alloc_frame,
    .start_frame    = ff_videotoolbox_h264_start_frame,
    .decode_slice   = ff_videotoolbox_h264_decode_slice,
    .decode_params  = videotoolbox_h264_decode_params,
    .end_frame      = videotoolbox_h264_end_frame,
    .frame_params   = ff_videotoolbox_frame_params,
    .init           = ff_videotoolbox_common_init,
    .uninit         = ff_videotoolbox_uninit,
    .priv_data_size = sizeof(VTContext),
};

  该结构中定义了:

  • 解码器的名称;
  • 解码数据的类型;
  • 解码器ID;
  • 硬件解码的格式;
  • 申请一个硬件相关的帧结构的函数指针;
  • 解码开始前针对帧进行内存拷贝之类的操作;
  • 解码数据;
  • 解析解码器需要的参数比如sps等;
  • 送帧结束后的后处理;
  • 初始化硬件解码器;
  • 销毁硬件解码器;
  • 当前硬件解码器的描述结构。

  ff_h264_videotoolbox_hwaccel是存储在hw_configs中的,运行时遍历该列表寻找期望的硬件解码器。所以解码工作是先经过FFmpeg内的ff_h264_decoder解码器再进入硬件解码器的。

const 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 *const []) {
#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 |
                             FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,
    .flush                 = h264_decode_flush,
    .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),
    .update_thread_context_for_user = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context_for_user),
    .profiles              = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
    .priv_class            = &h264_class,
};

  VTContextVT解码过程中描述VT的Context。

typedef struct VTContext {
    // The current bitstream buffer.
    uint8_t                     *bitstream;
    // The current size of the bitstream.
    int                         bitstream_size;
    // The reference size used for fast reallocation.
    int                         allocated_size;
    // The core video buffer
    CVImageBufferRef            frame;
    // Current dummy frames context (depends on exact CVImageBufferRef params).
    struct AVBufferRef         *cached_hw_frames_ctx;
    // Non-NULL if the new hwaccel API is used. This is only a separate struct
    // to ease compatibility with the old API.
    struct AVVideotoolboxContext *vt_ctx;

    // Current H264 parameters (used to trigger decoder restart on SPS changes).
    uint8_t                     sps[3];
    bool                        reconfig_needed;
    void *logctx;
} VTContext;

1.2 主要流程

在这里插入图片描述

2 每个步骤的具体实现

2.1ff_videotoolbox_common_init

  ff_videotoolbox_common_init在初始化解码器时调用,一般是在avcodec_open2时初始化硬件解码器。一般FFmpeg为了更加准确的探测当前视频的媒体信息,在avformat_find_stream_info时就会初始化解码器解码少部分的帧来进行流媒体信息探测。
  初始化时首先就时申请VT的Context内存,并设置一些参数,实际上只设置了VT的callback函数和PixFormat。之后及时根据需要初始化AVHWFramesContext,主要就是申请内存并设置帧格式比如宽高,格式等等。
  最后就是调用videotoolbox_start创建VT的Session,创建的过程比较简单就是直接调用Apple的API创建Session,需要重点关注的是如何设置的。具体的实现函数为videotoolbox_decoder_config_create,其中设置硬件加速的配置时写死的,无法进行配置。另外就是从当前的CodecCteonxt中取出sps等信息送给解码器,如果没有这些信息,解码器是无法准确识别出时间戳信息的。sps和pps的解析是由FFmpeg完成的。

    switch (codec_type) {
    case kCMVideoCodecType_MPEG4Video :
        if (avctx->extradata_size)
            data = videotoolbox_esds_extradata_create(avctx);
        if (data)
            CFDictionarySetValue(avc_info, CFSTR("esds"), data);
        break;
    case kCMVideoCodecType_H264 :
        data = ff_videotoolbox_avcc_extradata_create(avctx);
        if (data)
            CFDictionarySetValue(avc_info, CFSTR("avcC"), data);
        break;
    case kCMVideoCodecType_HEVC :
        data = ff_videotoolbox_hvcc_extradata_create(avctx);
        if (data)
            CFDictionarySetValue(avc_info, CFSTR("hvcC"), data);
        break;
#if CONFIG_VP9_VIDEOTOOLBOX_HWACCEL
    case kCMVideoCodecType_VP9 :
        data = ff_videotoolbox_vpcc_extradata_create(avctx);
        if (data)
            CFDictionarySetValue(avc_info, CFSTR("vpcC"), data);
        break;
#endif
    default:
        break;
    }

  解码callback的实现比较简单就是Retain一下CVPixelBuffer。

static void videotoolbox_decoder_callback(void *opaque,
                                          void *sourceFrameRefCon,
                                          OSStatus status,
                                          VTDecodeInfoFlags flags,
                                          CVImageBufferRef image_buffer,
                                          CMTime pts,
                                          CMTime duration)
{
    VTContext *vtctx = opaque;

    if (vtctx->frame) {
        CVPixelBufferRelease(vtctx->frame);
        vtctx->frame = NULL;
    }

    if (!image_buffer) {
        av_log(vtctx->logctx,  AV_LOG_DEBUG,
               "vt decoder cb: output image buffer is null: %i\n", status);
        return;
    }

    vtctx->frame = CVPixelBufferRetain(image_buffer);
}

2.2 videotoolbox_h264_decode_paramsff_videotoolbox_frame_params

 &esmp;videotoolbox_h264_decode_params主要的工作就是将上层解码出来额sps和pps信息拷贝到VTContext中。

case H264_NAL_SPS: {
    GetBitContext tmp_gb = nal->gb;
    if (avctx->hwaccel && avctx->hwaccel->decode_params) {
        ret = avctx->hwaccel->decode_params(avctx,
                                            nal->type,
                                            nal->raw_data,
                                            nal->raw_size);
        if (ret < 0)
            goto end;
    }
    if (ff_h264_decode_seq_parameter_set(&tmp_gb, avctx, &h->ps, 0) >= 0)
        break;
    av_log(h->avctx, AV_LOG_DEBUG,
            "SPS decoding failure, trying again with the complete NAL\n");
    init_get_bits8(&tmp_gb, nal->raw_data + 1, nal->raw_size - 1);
    if (ff_h264_decode_seq_parameter_set(&tmp_gb, avctx, &h->ps, 0) >= 0)
        break;
    ff_h264_decode_seq_parameter_set(&nal->gb, avctx, &h->ps, 1);
    break;

  ff_videotoolbox_frame_params比较简单就是将CodecContext中的参数传递给HWFramesContext。

ff_videotoolbox_alloc_frame,ff_videotoolbox_h264_start_frame,ff_videotoolbox_h264_decode_slice,videotoolbox_h264_end_frame

  这几个函数每一帧都会调用,顺序是alloc_frame->start_frame->decode_frame->end_frame
  ff_videotoolbox_alloc_frame用来申请一块内存,此时的内存只是一块儿裸内存只是将release函数指针设置成了VT的release指针,还未与CVPixelBuffer绑定,绑定是在解码器的Callback中进行的。
  ff_videotoolbox_h264_start_frame主要就是将上层传下来的stream数据流拷贝到VTContext中。
  videotoolbox_common_decode_slice也是拷贝数据流。
  videotoolbox_h264_end_frame才是具体将数据送给解码器的地方,核心的地方就是videotoolbox_session_decode_frame,这里送给解码器的数据流就上上面拷贝的数据流,需要注意的是在初始化时的callback中只是做了拷贝内存其他什么也没有做。这是因为在这里调用了VTDecompressionSessionWaitForAsynchronousFrames等待异步解码完成,能够保证上一帧解码完成后才送下一帧数据。

2.3 ff_videotoolbox_uninit

  ff_videotoolbox_uninit比较简单就是释放解码器的Context和缓存中的内存。

  • Apple Documentation——VideoToolbox

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMq-2安装与配置

Rabbitmq的安装 1.上传资源 注意&#xff1a;rabbitmq的版本必须与erlang编译器的版本适配 2.安装依赖环境 //打开虚拟机 yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel m4 ncurses-devel tk tc xz3.安装erlan…

第3天----在一行句子中寻找最长最短单词

今天我们将学习如何在一行句子中寻找(第一次出现的)最长最短单词。本节内容会或多或少地利用到第一讲/第二讲的知识点&#xff0c;需要的同学可以先去看看前面的内容。 一、小试牛刀&#xff1a; 题目描述 输入 1 行句子&#xff08;不多于 200 个单词&#xff0c;每个单词长度…

Spring学习笔记+SpringMvc+SpringBoot学习笔记

壹、核心概念&#xff1a; 1.1. IOC和DI IOC&#xff08;Inversion of Control&#xff09;控制反转&#xff1a;对象的创建控制权由程序转移到外部&#xff0c;这种思想称为控制反转。/使用对象时&#xff0c;由主动new产生对象转换为由外部提供对象&#xff0c;此过程种对象…

[四次挥手]TCP四次挥手握手由入门到精通(知识精讲)

⬜⬜⬜ &#x1f430;&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;(*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;&#x1f430;⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &am…

人工智能与云计算实训室建设方案

一、 人工智能与云计算系统概述 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是一种模拟人类智能的科学和工程&#xff0c;通过使用计算机系统来模拟、扩展和增强人类的智能能力。人工智能涉及多个领域&#xff0c;包括机器学习、深度学习、自然…

mysql的两张表left join 进行关联后,索引进行优化案例

一 mysql的案例 1.1 不加索引情况 1.表1没加索引 2.表2没加索引 3.查看索引 1.2 添加索引 1.表1添加索引 2.表2添加索引 3.查看

使用navicat连接postgresql报错问题解决

使用navicat连接postgresql报错问题解决 一、问题现象&#xff1a; 最近使用Navicat来连接postgreSQL数据库&#xff0c;发现连接不上&#xff0c;报错信息如下&#xff1a; 自己百度了一下&#xff0c;发现pgsql 15版本以后&#xff0c;有些系统表的列名改了&#xff0c;pg_…

一文科普,配资门户网是什么?

配资门户网是一个为投资者提供配资服务的平台。配资是指通过借用他人资金进行投资交易的一种金融操作方式。配资门户网作为一个线上平台&#xff0c;为投资者提供了方便、快捷的配资服务。 配资门户网提供了多种不同的配资方案&#xff0c;以满足不同投资者的需求。投资者可以…

录制游戏视频的软件有哪些?分享3款软件!

“有录制游戏视频的软件推荐吗&#xff1f;最近迷上了网游&#xff0c;想录制点自己高端操作的游戏画面&#xff0c;但是不知道用什么软件录屏比较好&#xff0c;就想问问大家&#xff0c;有没有好用的录制游戏视频软件。” 在游戏领域&#xff0c;玩家们喜欢通过录制游戏视频…

根据源码,模拟实现 RabbitMQ - 实现消息持久化,统一硬盘操作(3)

目录 一、实现消息持久化 1.1、消息的存储设定 1.1.1、存储方式 1.1.2、存储格式约定 1.1.3、queue_data.txt 文件内容 1.1.4、queue_stat.txt 文件内容 1.2、实现 MessageFileManager 类 1.2.1、设计目录结构和文件格式 1.2.2、实现消息的写入 1.2.3、实现消息的删除…

HCIP实验之MPLS

目录 一&#xff0c;实验题目 ​编辑 拓扑与IP地址规划如图所示 二&#xff0c;实验思路 三&#xff0c;实验步骤 3.1 私网部分IP地址配置 3.2 LSP部分配置 3.3 启动OSPF协议 3.4 启动MPLS协议 3.5 启动MPLS VPN 3.6 实现公网私网互通 3.7 配置BGP 3.8 双向重发布 …

常见的 Python 错误及其解决方案

此文整理了一些常见的 Python 错误及其解决方案。 1、SyntaxError: invalid syntax 说明&#xff1a;无效的语法是最常见的错误之一&#xff0c;通常是由于编写代码时违反了 Python 的语法规则。可能的原因&#xff1a; 忘记在 if、while、for 等语句后写冒号&#xff0c;或者…

我和 TiDB 的故事 | 远近高低各不同

作者&#xff1a; ShawnYan 原文来源&#xff1a; https://tidb.net/blog/b41a02e6 Hi, TiDB, Again! 书接上回&#xff0c; 《我和 TiDB 的故事 | 横看成岭侧成峰》 &#xff0c;一年时光如白驹过隙&#xff0c;这一年我好似在 TiDB 上投入的时间总量不是很多&#xff0…

vite打包配置以及性能优化

vite打包配置以及性能优化 安装插件 首先该安装的插件&#xff0c;你要安装一下吧 这三个是基本的插件&#xff0c;其他优化的插件下面会介绍到 "vite": "4.4.6","vite-plugin-html": "^3.2.0","vitejs/plugin-vue": &qu…

Eureka:集群环境配置

创建三个集群 导包 <!-- 导包--><dependencies><!-- Eureka -server --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-eureka-server</artifactId><version>1.…

信号

信号也是IPC中的一种&#xff0c;是和管道&#xff0c;消息队列&#xff0c;共享内存并列的概念。 本文参考&#xff1a; Linux中的信号_linux中信号_wolf鬼刀的博客-CSDN博客 Linux系统编程&#xff08;信号处理 sigacation函数和sigqueue函数 )_花落已飘的博客-CSDN博客 Linu…

回归预测 | MATLAB实现SA-SVM模拟退火算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现SA-SVM模拟退火算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现SA-SVM模拟退火算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本…

Scratch 之 RPG 引擎制作教程(1) / 地图行走

大家好&#xff0c;又和大家见面了&#xff0c;那么我们这期讲的就是RPG游戏的地图以及人物的行走。 我发现大家并不是很懂RPG游戏引擎&#xff0c;也就是说这种引擎对于技术的要求还是比较高的。为了让更多人直接上手制作RPG游戏&#xff0c;我打算开启这一系列教程。 这个教程…

Azure应用程序网关

文章目录 什么是应用程序网关实战演练创建虚拟网络创建虚拟机创建应用程序网关测试搭建结果 什么是应用程序网关 Azure应用程序网关是一种托管服务&#xff0c;用于提供安全、可缩放的 Web 应用程序前端点的应用程序传送控制和保护。它可以通过 SSL 终止、cookie 基于会话持久…

Cesium加载Supermap的wmts服务

最近使用cesium 加载supermap的wmts 服务&#xff0c;多次遇到加载异常与白页面问题&#xff0c;纠结好久最后才搞定[特此记录] 1、首先找到方法加载wmts 的api 文档 官方提示使用WebMapTileServiceImageryProvider加载wmts 2、然后编辑加载代码 //1.新建ImageryProviderlet…