这篇文章主要分析 NVCUVID 提供的解码器,里面提到的所有的源文件都可以在英伟达的 nvenc_sdk 中找到。

解码器的代码分析

SDK 中的 sample 文件夹下的 NvTranscoder 中包含了编码器和解码器的用法,编码器的内容不在这里分析,因为 FFMPEG 中已经包含了相关的代码,不需要其他的处理。

解码器在 SDK 中有一份封装,主要是 NvTranscoder 下的 VideoDecoder 类。目前这个类的具体用法还不是特别的清楚。分析将会从 main 函数开始。

main

NvTranscoder 有一个单独的文件,执行逻辑从 main() 函数开始。

cuInit(0) 应该是初始化 cuda 的相关代码。目前没有找到定义,估计类似于FFMPEG 中的 av_register_all

105 行之前的代码是编码相关的代码, 85 行以前设置各种编码参数,87 行分析从命令行读入的参数,100 行打开目标输出文件。

110 行开始代码应该是和解码相关的核心代码。cuDeviceGetcuCtxCreate cuCtxPopCurrentcuvidCtxLockCreate 应该是固定写法。初始化一些内部机制。

120 行的 InitVideoDecoder 是解码器创建的地方。这个函数需要认真的分析,整个解码器的关键代码应该就在这里面。

137 行初始化 119 行中创建的 FrameQueue。这个队列应该相当于解码器和编码器之间的一个缓冲区,解码器方内容进去,编码器从中取内容出来。

139 行到 180 都是关于编码器的设置。这里不做详细的分析。

187 行的注释显示 pthread_create 创建出来解码线程,所以解码工作是由这个线程完成的。线程执行的函数是 DecodeProc 这个函数,而这个函数只不过是调用的解码器的 Start() 方法。

195 行的代码注释来看,这后续的代码都是编码相关的代码,这里不做分析。最后在245 行和 246 行的未知 cuvidCtxLockDestroy 和 cuCtxDestroy 应该是对应于110
行的那些代码。

VideoDecoder.cpp

这个文件实现了解码器的封装类 CudaDecoder, 这个类是整个硬件解码器实现的关键,但是这个类其实比较简单。它在 main 函数中涉及到的方法只有InitVideoDecoderGetCodecParamStart 和 GetDecoder 这四个。

InitVideoDecoder

这个方法是首先被调用的方法。它负责编码器的初始化操作。

从 VideoDecoder.cpp 中的实现来看,初始化主要包括三个部分:

创建视频源

视频源的参数是 CUVIDSOURCEPARAMS,其中设置了一个 pfnVideoDateHandler,从字面上理解它是一个视频数据的回调处理函数。

创建视频源的方法是 cuvidCreateVideoSource() 函数,目前来说这个函数的致命问题在于它的接收 videoPath 作为参数,这似乎意味着它只能处理文件视频源。这个函数的函数原型定义在 nvcuvid.h 这个头文件中,这个头文件只定义了下面这些和视频源相关的接口:

cuvidCreateVideoSource();
cuvidCreateVideoSourceW();
cuvidDestroyVideoSource();
cuvidSetVideoSourceState();
cuvidGetVideoSourceState();
cuvidGetSourceVideoFormat();
cuvidGetSourceAudioFormat();

从接口来看只有 cuvidCreateVideoSource(); cuvidCreateVideoSourceW(); 这两个函数可用,而它们唯一的区别在于接收不同的文件路径字符串,前者是普通字符而后者是宽字符。

目前暂时没有其他的资料表明可以创建非文件类型的视频源,所以这个解码器的用处估计不会太大,至少在传屏应用中的用处会相对较小。

获取视频源的参数,并创建 cuvid 库的解码器

视频源的参数信息在创建视频源之后可以通过 cuvidGetSourceVideoFormat() 函数获得,在 InitVideoDecoder() 函数中获取参数最诡异的地方在于
111 行创建了一个 CUVIDOFORMATEX 类型的变量 oFormatEx,然后让 oFormat 引用这个变量的
format 字段。在调用 cuvidGetSourceVideoFormat 之后竟然可以直接访问oFormatEx 这个变量的 raw_seqhdr_data 字段,个人估计它的内部实现使用了类似 container_of 这样的技术访问了 oFormatEx。但是为什么这样设计不得而知。

创建解码器的函数是 cuvidCreateDecoder(),这个函数的原型定义在 cuviddec.h文件中。

cuvidcreatedecoder(cuvideodecoder *, CUVIDDECODECREATEINFO *);
cuvidDestroyDecoder(CUvideodecoder);

解码器的参数是通过 CUVIDDECODECREATEINFO 传递的,这个结构体的大部分字段都是通过前面获得的 oFormat 中的信息获得。

创建视频源的解析器

初始化的最后一步是创建一个视频源的解析器,其中设置了三个回调函数,HandleVideoSequenceHandlePictureDecodeHandlePictureDisplay,
nvidia 的文档中并没有说这些回调函数会在什么时候调用,也没有说明这些回调函数要完成的事情是什么,只能从名字中猜测 HandlePictureDecode这个函数是用来解码的。

Start

在 main 函数的解码线程函数中只调用了 CudaDecoder 类的 Start 函数。而Start 函数本身也非常的简单,只不过调用了
cuvidSetVideoSourceState() 把状态变成 cudaVideoState_Started 然后一直取状态直到状态不再是 started

从这个函数的实现来看,它的内部应该在把视频源设置为cudaVideoState_Started 状态之后开始读取视频源(文件)中的数据。然后通过回调函数进行处理。应该是首先调用 HandleVideoData(),
个人猜测这个函数在数据从文件中读取出来之后会被调用来解析原始数据,HandleVideoSequence 这个函数没有太大的用途,只是一些参数的检测而已。HandlePictureDecode 应该是在成功解析到数据帧的时候调用,这个函数调用了 cuvidDecodePicture 解码数据。数据解码出来之后会调用 HandlePictureDisplay 函数,该函数把数据放入到数据缓冲区 FrameQueue 中以便编码器能够把数据取出来。

总结

使用 CudaDecoder 首先需要调用 cuvidCreateVideoSource 创建一个文件视频源,然后调用 cuvidGetSourceVideoFormat 从文件中读取解码参数信息并使用参数信息创建一个 CUvideodecoder 解码器,之后再创建一个视频源解析器,设置回调函数处理视频的解码。

上面的初始化完成之后调用 cuvidSetVideoSourceState 把视频源的状态设置为cudaVideoState_Started,之后库的内部会开始读文件,把读取的数据交给HandleVideoData 解析,解析完成之后会把数据交给 HandlePictureDecode 调用 cuvidDecodePicture 进行解码。在解码完成之后调用HandlePictureDisplay 把数据放入到
FrameQueue 缓冲区里面。

补充:video source 和 nvcuvid

在 SDK 给出的例子中,数据是通过 video source 接口来提供的。但是这并不意味着我们在编写程序的时候只能使用它提供的 video
source
 接口。根据官方文档中第三小节最后给出的解释

Note: The low level decode APIs are supported on both Linux and
Windows platforms. The NVCUVID APIs for Parsing and Source Stream input are available only on Windows platforms.

NVCUVID 的 video source 只在 windows 平台可用,不过从最新的nvenc_sdk 的代码来看,videosource
和 sourcepraser 在 linux 平台下也是可用的。只不过从接口来看,这两个 API 只能用于文件的解析。

在文档的第四小节 4.2 中有这么一段话:

For Linux platforms, you will need to write your own video source andparsing functions that connect to the Video Decoding functions.

这一点明确说明,其实我们可以不使用它本身的 video source 接口,使用自己的接口提供视频源,然后使用 nvcuvid 最底层的解码接口对数据进行解码和后续处理。

MAP

在 nvcuvid 的官方文档中给出的接口中,最诡异的两个接口是

cuvidMapVideoFrame()
cuvidUnmapVideoFrame()

这两个函数好像是用于处理解码之后的数据的,但是这其中的原理是什么并不清楚,有待后续研究。

【并行计算-CUDA开发】英伟达硬件解码器分析的更多相关文章

  1. 【并行计算与CUDA开发】英伟达硬件加速编解码

    硬件加速 并行计算 OpenCL OpenCL API VS SDK 英伟达硬件编解码方案 基于 OpenCL 的 API 自己写一个编解码器 使用 SDK 中的编解码接口 使用编码器对于 OpenC ...

  2. 【并行计算与CUDA开发】英伟达硬件加速解码器在 FFMPEG 中的使用

    目录(?)[-] 私有驱动 编译 FFMPEG 使用 nvenc 这篇文档介绍如何在 ffmpeg 中使用 nvenc 硬件编码器. 私有驱动 nvenc 本身是依赖于 nvidia 底层的私有驱动的 ...

  3. CUDA学习笔记4:CUDA(英伟达显卡统一计算架构)代码运行时间测试

    CUDA内核运行时间的测量函数 cudaEvent_t start1; cudaEventCreate(&start1); cudaEvent_t stop1; cudaEventCreate ...

  4. CUDA学习笔记2:CUDA(英伟达显卡统一计算架构)与已有的VS项目结合

    一.步骤 1.先新建一个简单的控制台应用程序,项目名称为Mytest,如下图所示: 2.在项目中添加一个名为Test.cu文件,如下图所示: 3.在解决方案资源管理器中选择该项目并点击右键,在弹出的菜 ...

  5. 【并行计算-CUDA开发】从熟悉到精通 英伟达显卡选购指南

    举报 说到显卡,就不免令人想到英伟达和AMD两家面向个人消费级和企业级最大的显示芯片生产企业,英伟达和AMD,今天小编为大家简单的介绍一下英伟达的显卡选购方面的攻略,为一些想要购买显卡的用户提供一些参 ...

  6. 【视频开发】【CUDA开发】英伟达CUVID硬解,并通过FFmpeg读取文件

    虽然FFmpeg本身有cuvid硬解,但是找不到什么好的资料,英伟达的SDK比较容易懂,参考FFmpeg源码,将NVIDIA VIDEO CODEC SDK的数据获取改为FFmpeg获取,弥补原生SD ...

  7. 第一篇:CUDA 6.0 安装及配置( WIN7 64位 / 英伟达G卡 / VS2010 )

    前言 本文讲解如何在VS 2010开发平台中搭建CUDA开发环境. 当前配置: 系统:WIN7 64位 开发平台:VS 2010 显卡:英伟达G卡 CUDA版本:6.0 若配置不同,请谨慎参考本文. ...

  8. 英伟达GPU 嵌入式开发平台

    英伟达GPU  嵌入式开发平台 1.         JETSON TX1 开发者组件 JETSON TX1 开发者组件是视觉计算的全功能 开发平台,旨在让您能够快速地安装和运行. 该组件带有 Lin ...

  9. 【系统硬件】英伟达安培卡 vs 老推理卡硬件参数对比

      欢迎关注我的公众号 [极智视界],回复001获取Google编程规范   O_o   >_<   o_O   O_o   ~_~   o_O   本文分享一下英伟达安培卡 vs 老推理 ...

随机推荐

  1. 存储过程:SET Transaction Isolation Level Read语法的四种情况

    这几天一直在弄存储过程,现在在这里跟大伙共享下资料: SET Transaction Isolation Level Read UNCOMMITTED 使用这句东东呢可以分为四种情况,现在就在这里逐一 ...

  2. 解决每次执行Java等命令时都要重新source /etc/profile后才能执行,否则找不到命令

    linux mint 我们通常将环境变量设置在/etc/profile这个文件中,这个文件是全局的. /etc/profile:在登录时,操作系 统定制用户环境时使用的第一个文件 ,此文件为系统的每个 ...

  3. soapui如何发送xml格式的字符串

    一个服务需要的xml格式的字符串参数,用soapUI传递参数时要这样写: <![CDATA[<?xml version="1.0" encoding="UTF ...

  4. luogu 1903 [国家集训队]数颜色 / 维护队列 带修改莫队

    十分玄学的数据结构~ code: #include <bits/stdc++.h> #define N 1000006 #define setIO(s) freopen(s".i ...

  5. CF796C Bank Hacking 细节

    思路十分简单,答案只有 3 种可能,但是有一些细节需要额外注意一下. code: #include <bits/stdc++.h> #define N 300002 #define set ...

  6. socket编程和并发服务器

    socket这个词可以表示很多概念: 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”就称为socket. 在TCP协议中,建立连接的两个进程 ...

  7. (转)实验文档3:在kubernetes集群里集成Apollo配置中心

    使用ConfigMap管理应用配置 拆分环境 主机名 角色 ip HDSS7-11.host.com zk1.od.com(Test环境) 10.4.7.11 HDSS7-12.host.com zk ...

  8. 使用python开发ansible自定义模块的简单案例

    安装的版本ansible版本<=2.7,<=2.8是不行的哦 安装模块 pip install ansible==2.7 先导出环境变量 我们自定义模块的目录. 我存放的目录 export ...

  9. loadrunner11安装

    今天虚拟机里面装了下lr11,虚拟机版本是vm9.0,先在虚拟机里面装了windows2003,当然lr也是可以装在自己电脑上面的,但是最好是纯净的环境,由于我电脑东西比较多,所以我就装在虚拟机里面了 ...

  10. spring+mybatis事务的readonly属性无效

    在Spring配置事务中设置的read-only="true"不起作用,仍可以执行写操作:但是其他的正常.查看了一下DataSourceTransactionManager这个类的 ...