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

news2024/9/25 21:27:30

  摘要:本文描述了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.coloradmin.cn/o/899709.html

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

相关文章

基于java毕业设计之旅游管理系统(springboot源码+LW+PPT)

项目源码 lwPPT 文章目录 前言一、主要技术javaMysql数据库JSP技术 二、系统设计三、功能截图总结 前言 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的交易方式逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。互联网具有…

CLion:最好用的c/c++编写工具(最详细安装教程)

目录 一.前言介绍 1.下载安装 1.1右上角点击下载 1.2选择自己操作系统&#xff0c;然后点击下载 1.3选择next 1.4 更改路径 1.5D盘最好 1.6 按照我的选择配置环境 1.7install安装 1.8 安装完成 2、mingw64安装 2.1下载资源压缩包 2.2mingw64放入到合适的位置&#xff0c;…

Python+request+unittest实现接口测试框架集成实例

这篇文章主要介绍了Pythonrequestunittest实现接口测试框架集成实例&#xff0c;小编觉得挺不错的&#xff0c;现在分享给大家&#xff0c;也给大家做个参考。一起跟随小编过来看看吧 1、为什么要写代码实现接口自动化 大家知道很多接口测试工具可以实现对接口的测试&#xf…

使用pnpm workspace管理Monorepo架构

在开发项目的过程中&#xff0c;我们需要在一个仓库中管理多个项目&#xff0c;每个项目有独立的依赖、脚手架&#xff0c;这种形式的项目结构我们称之为Monorepo&#xff0c;pnpm workspace就是管理这类项目的方案之一。 一、pnpm简介 1、pnpm概述 pnpm代表performance npm…

注入Servlet、Filter、Listener的两种方式

文章目录 注入Servlet、Filter、Listener官方文档基本介绍应用实例1-使用注解方式注入需求:应用实例-实现注意: 应用实例2-使用RegistrationBean 方式注入需求:应用实例-实现 注意事项和细节说明原因分析说明 源码分析 注入Servlet、Filter、Listener 官方文档 文档: https:/…

高效mac常用快捷键整理

高效mac常用快捷键整理 macOS全局、剪切、拷贝、粘贴finder 和 系统快捷键文稿 浏览器chrome标签页和窗口快捷键功能快捷键网页快捷键 Idea & Pycharm必记编辑查询&替换导航重构编译、运行、调试 iTerm标签分屏编辑 文本编辑 sublime text导航&#xff1a;打开&关闭…

高级艺术二维码制作教程

最近不少关于二维码制作的&#xff0c;而且都是付费。大概就是一个好看的二维码&#xff0c;扫描后跳转网址。本篇文章使用Python来实现&#xff0c;这么简单花啥钱呢&#xff1f;学会&#xff0c;拿去卖便宜点吧。 文章目录 高级二维码制作环境安装普通二维码艺术二维码动态 …

2023国赛数学建模思路 - 案例:FPTree-频繁模式树算法

文章目录 算法介绍FP树表示法构建FP树实现代码 建模资料 ## 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模式树算法&#xff0c…

让Linux融入生活! 家用服务器折腾指南《树莓派不吃灰》系列文章更新满20篇!

title: 让Linux融入生活! 家用服务器折腾指南《树莓派不吃灰》系列文章更新满20篇&#xff01; 截止2023年8月19日&#xff0c;《树莓派防吃灰》&#xff08;也可以叫《树莓派不吃灰》&#xff0c;怎么好念怎么来&#xff09;系列&#xff0c;已经更新了20篇。这是一份基于Ubu…

LeetCode[面试题04.12]求和路径

难度&#xff1a;Medium 题目&#xff1a; 给定一棵二叉树&#xff0c;其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法&#xff0c;打印节点数值总和等于某个给定值的所有路径的数量。注意&#xff0c;路径不一定非得从二叉树的根节点或叶节点开始或结束&#x…

第六章在web中应用MyBatis

准备 MyBatis内部可以帮我们生成dao接口的实现类&#xff08;代理类&#xff0c;dao接口的代理&#xff09; 使用这种代理机制的前提是&#xff0c;SqlMapper.xml文件中的namespace必须是dao接口的全限定名称&#xff0c;id必须是dao接口的方法名 开始 创建一个SpringBoot项…

jdk 1.8新特性 01内部类和lambda

01.内部类&#xff1a; 1、成员内部类 内部类与成员外部类的关系 a.成员内部类的创建需要依赖于外部类对象-&#xff08;成员方法必须通过对象调用&#xff09;&#xff0c;在没有外部类实例之前无法创建成员内部类对象 b.内部类与外部类相对独立&#xff0c;不是is a 的关系…

vs2022配置opencv进行监控 c++

下载opencv文件 下载好的目录结构是 以上就是用到的文件和目录 在vs2022配置 最后&#xff1a;此处运行提示找不到 opencv_world480.dll 解决办法&#xff1a;直接从 复制到windows下

Android 场景Scene的使用

Scene 翻译过来是场景&#xff0c;开发者提供起始布局和结束布局&#xff0c;就可以实现布局之间的过渡动画。 具体可参考 使用过渡为布局变化添加动画效果 大白话&#xff0c;在 Activity 的各个页面之间切换&#xff0c;会带有过渡动画。 打个比方&#xff0c;使用起来类似…

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

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

《Zookeeper》源码分析(十五)之 选举算法

FastLeaderElection FastLeaderElection实现了接口Election&#xff0c;选举方法为lookForLeader()&#xff0c;选举算法的核心逻辑也在该方法中。 数据结构 构造函数 start() 启动选举通信网络 lookForLeader() 选举核心算法 FastLeaderElection.logicalclock属性用于标…

2023最新红盟云卡个人自动发卡系统源码 全开源

​ 简介&#xff1a; 2023最新红盟云卡个人自动发卡系统源码 全开源 该系统完全开源且无任何加密&#xff0c;可商业使用&#xff0c;并支持个人免签多个接口。 ​ 图片&#xff1a;

2021年03月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;找和为K的两个元素 在一个长度为n(n < 1000)的整数序列中&#xff0c;判断是否存在某两个元素之和为k。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 输入 第一行输入序列的长度n和k&#xff0c;用空格分开。 第二行输入序列中的n个整数&#xff…

Redis 扩展资料

Redis 扩展资料 1.缓存简介2.缓存分类3.常⻅缓存使⽤4.Redis 数据类型和使⽤5.持久化6.常⻅⾯试题7.Redis 集群&#xff08;选学&#xff09; 1.缓存简介 2.缓存分类 3.常⻅缓存使⽤ 4.Redis 数据类型和使⽤ 5.持久化 Redis 和 Memcached 有什么区别&#xff1f; 6.常⻅⾯试…

独立站SEO是什么意思?自主网站SEO的含义?

什么是独立站SEO优化&#xff1f;自建站搜索引擎优化是指什么&#xff1f; 独立站SEO&#xff0c;作为网络营销的重要一环&#xff0c;正在逐渐引起人们的关注。在当今数字化时代&#xff0c;独立站已经成为许多企业、个人宣传推广的首选平台之一。那么&#xff0c;究竟什么是…