英伟达CUVID硬解,并通过FFmpeg读取文件

时间:2022-12-30 20:50:05

虽然FFmpeg本身有cuvid硬解,但是找不到什么好的资料,英伟达的SDK比较容易懂,参考FFmpeg源码,将NVIDIA VIDEO CODEC SDK的数据获取改为FFmpeg获取,弥补原生SDK不能以流作为数据源的不足。所用SDK版本为Video_Codec_SDK_7.1.9,英伟达官网可下载。

1.修改数据源

首先是FFmpeg的一些常规的初始化

bool VideoSource::init(const std::string sFileName, FrameQueue *pFrameQueue)
{
assert(0 != pFrameQueue);
oSourceData_.hVideoParser = 0;
oSourceData_.pFrameQueue = pFrameQueue; int i;
AVCodec *pCodec; av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, sFileName.c_str(), NULL, NULL) != 0){
printf("Couldn't open input stream.\n");
return false;
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0){
printf("Couldn't find stream information.\n");
return false;
}
videoindex = -1;
for (i = 0; i<pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
videoindex = i;
break;
} if (videoindex == -1){
printf("Didn't find a video stream.\n");
return false;
} pCodecCtx = pFormatCtx->streams[videoindex]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL){
printf("Codec not found.\n");
return false;
} //Output Info-----------------------------
printf("--------------- File Information ----------------\n");
av_dump_format(pFormatCtx, 0, sFileName.c_str(), 0);
printf("-------------------------------------------------\n"); memset(&g_stFormat, 0, sizeof(CUVIDEOFORMAT)); switch (pCodecCtx->codec_id) {
case AV_CODEC_ID_H263:
g_stFormat.codec = cudaVideoCodec_MPEG4;
break; case AV_CODEC_ID_H264:
g_stFormat.codec = cudaVideoCodec_H264;
break; case AV_CODEC_ID_HEVC:
g_stFormat.codec = cudaVideoCodec_HEVC;
break; case AV_CODEC_ID_MJPEG:
g_stFormat.codec = cudaVideoCodec_JPEG;
break; case AV_CODEC_ID_MPEG1VIDEO:
g_stFormat.codec = cudaVideoCodec_MPEG1;
break; case AV_CODEC_ID_MPEG2VIDEO:
g_stFormat.codec = cudaVideoCodec_MPEG2;
break; case AV_CODEC_ID_MPEG4:
g_stFormat.codec = cudaVideoCodec_MPEG4;
break; case AV_CODEC_ID_VP8:
g_stFormat.codec = cudaVideoCodec_VP8;
break; case AV_CODEC_ID_VP9:
g_stFormat.codec = cudaVideoCodec_VP9;
break; case AV_CODEC_ID_VC1:
g_stFormat.codec = cudaVideoCodec_VC1;
break;
default:
return false;
} //这个地方的FFmoeg与cuvid的对应关系不是很确定,不过用这个参数似乎最靠谱
switch (pCodecCtx->sw_pix_fmt)
{
case AV_PIX_FMT_YUV420P:
g_stFormat.chroma_format = cudaVideoChromaFormat_420;
break;
case AV_PIX_FMT_YUV422P:
g_stFormat.chroma_format = cudaVideoChromaFormat_422;
break;
case AV_PIX_FMT_YUV444P:
g_stFormat.chroma_format = cudaVideoChromaFormat_444;
break;
default:
g_stFormat.chroma_format = cudaVideoChromaFormat_420;
break;
} //找了好久,总算是找到了FFmpeg中标识场格式和帧格式的标识位
//场格式是隔行扫描的,需要做去隔行处理
switch (pCodecCtx->field_order)
{
case AV_FIELD_PROGRESSIVE:
case AV_FIELD_UNKNOWN:
g_stFormat.progressive_sequence = true;
break;
default:
g_stFormat.progressive_sequence = false;
break;
} pCodecCtx->thread_safe_callbacks = 1; g_stFormat.coded_width = pCodecCtx->coded_width;
g_stFormat.coded_height = pCodecCtx->coded_height; g_stFormat.display_area.right = pCodecCtx->width;
g_stFormat.display_area.left = 0;
g_stFormat.display_area.bottom = pCodecCtx->height;
g_stFormat.display_area.top = 0; if (pCodecCtx->codec_id == AV_CODEC_ID_H264 || pCodecCtx->codec_id == AV_CODEC_ID_HEVC) {
if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
else
h264bsfc = av_bitstream_filter_init("hevc_mp4toannexb");
} return true;
}

这里面非常重要的一段代码是

if (pCodecCtx->codec_id == AV_CODEC_ID_H264 || pCodecCtx->codec_id == AV_CODEC_ID_HEVC) {
if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
else
h264bsfc = av_bitstream_filter_init("hevc_mp4toannexb");
}

网上有许多代码和伪代码都说实现了把数据源修改为FFmpeg,但我在尝试的时候发现cuvidCreateVideoParser创建的Parser的回调函数都没有调用。经过一番折腾,综合英伟达网站、*和FFmpeg源码,才发现对H264数据要做一个处理才能把AVPacket有效的转为CUVIDSOURCEDATAPACKET。其中h264bsfc的定义为AVBitStreamFilterContext* h264bsfc = NULL;

2.AVPacket转CUVIDSOURCEDATAPACKET,并交给cuvidParseVideoData

void VideoSource::play_thread(LPVOID lpParam)
{
AVPacket *avpkt;
avpkt = (AVPacket *)av_malloc(sizeof(AVPacket));
CUVIDSOURCEDATAPACKET cupkt;
int iPkt = 0;
CUresult oResult;
while (av_read_frame(pFormatCtx, avpkt) >= 0){
if (bThreadExit){
break;
}
bStarted = true;
if (avpkt->stream_index == videoindex){ cuCtxPushCurrent(g_oContext); if (avpkt && avpkt->size) {
if (h264bsfc)
{
av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL, &avpkt->data, &avpkt->size, avpkt->data, avpkt->size, 0);
} cupkt.payload_size = (unsigned long)avpkt->size;
cupkt.payload = (const unsigned char*)avpkt->data; if (avpkt->pts != AV_NOPTS_VALUE) {
cupkt.flags = CUVID_PKT_TIMESTAMP;
if (pCodecCtx->pkt_timebase.num && pCodecCtx->pkt_timebase.den){
AVRational tb;
tb.num = 1;
tb.den = AV_TIME_BASE;
cupkt.timestamp = av_rescale_q(avpkt->pts, pCodecCtx->pkt_timebase, tb);
}
else
cupkt.timestamp = avpkt->pts;
}
}
else {
cupkt.flags = CUVID_PKT_ENDOFSTREAM;
} oResult = cuvidParseVideoData(oSourceData_.hVideoParser, &cupkt);
if ((cupkt.flags & CUVID_PKT_ENDOFSTREAM) || (oResult != CUDA_SUCCESS)){
break;
}
iPkt++;
//printf("Succeed to read avpkt %d !\n", iPkt);
checkCudaErrors(cuCtxPopCurrent(NULL));
}
av_free_packet(avpkt);
} oSourceData_.pFrameQueue->endDecode();
bStarted = false;
}

这里FFmpeg读取数据包后,对H264和HEVC格式,有一个重要的处理,就是前面提到的,

if (h264bsfc)
{
av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL, &avpkt->data, &avpkt->size, avpkt->data, avpkt->size, 0);
}

这个处理的含义见雷霄华的博客http://blog.csdn.net/leixiaohua1020/article/details/39767055

这样,通过FFmpeg,CUVID就可以对流进行处理了。个人尝试过读取本地文件和rtsp流。FFmpeg读取rtsp流的方式竟然只需要把文件改为rtsp流的地址就可以,以前没做过流式的,我还以为会很复杂的。

3.一点数据

英伟达CUVID硬解,并通过FFmpeg读取文件

这是在GTX 1080上把解码进程(没做显示)开了20路解码得到的数据。20路1920X1080解码还能到平局37fps,这显卡也是6得不行。

工程源码:http://download.csdn.net/download/qq_33892166/9792997

源码遇到了一个问题,没找到原因。代码在GTX 1080和Tesla P4上的解码效果很好。P4由于驱动模式是TCC模式,所以只能解码,不能显示;1080上可解码,可显示。但是在我自己电脑上的GT940M上,即时是原生SDK在cuvidCreateDecoder的时候也总是报错CUDA_ERROR_NO_DEVICE。驱动似乎没问题,试了CUDA的demo,CUDA运算也是正常的,查的资料表明GT940M应该是支持CUVID的。希望知道原因的朋友能指教一二。

-----------------------------------------2017.7.7更新----------------------------------------

修改代码中的一处内存泄漏问题:

把play_thread()中的

if (avpkt && avpkt->size) {
if (h264bsfc)
{
av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL, &avpkt->data, &avpkt->size, avpkt->data, avpkt->size, 0);
} cupkt.payload_size = (unsigned long)avpkt->size;
cupkt.payload = (const unsigned char*)avpkt->data; if (avpkt->pts != AV_NOPTS_VALUE) {
cupkt.flags = CUVID_PKT_TIMESTAMP;
if (pCodecCtx->pkt_timebase.num && pCodecCtx->pkt_timebase.den){
AVRational tb;
tb.num = 1;
tb.den = AV_TIME_BASE;
cupkt.timestamp = av_rescale_q(avpkt->pts, pCodecCtx->pkt_timebase, tb);
}
else
cupkt.timestamp = avpkt->pts;
}
}
else {
cupkt.flags = CUVID_PKT_ENDOFSTREAM;
} oResult = cuvidParseVideoData(oSourceData_.hVideoParser, &cupkt);
if ((cupkt.flags & CUVID_PKT_ENDOFSTREAM) || (oResult != CUDA_SUCCESS)){
break;
}
iPkt++;

AVPacket new_pkt = *avpkt;

if (avpkt && avpkt->size)
{
if (h264bsfc){ int a = av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL,
&new_pkt.data, &new_pkt.size,
avpkt->data, avpkt->size,
avpkt->flags & AV_PKT_FLAG_KEY); if (a>0){
if (new_pkt.data != avpkt->data)//-added this
{
av_free_packet(avpkt); avpkt->data = new_pkt.data;
avpkt->size = new_pkt.size;
}
}
else if (a<0){
goto LOOP0;
} *avpkt = new_pkt;
} cupkt.payload_size = (unsigned long)avpkt->size;
cupkt.payload = (const unsigned char*)avpkt->data; if (avpkt->pts != AV_NOPTS_VALUE)
{
cupkt.flags = CUVID_PKT_TIMESTAMP;
if (pCodecCtx->pkt_timebase.num && pCodecCtx->pkt_timebase.den)
{
AVRational tb;
tb.num = 1;
tb.den = AV_TIME_BASE;
cupkt.timestamp = av_rescale_q(avpkt->pts, pCodecCtx->pkt_timebase, tb);
}
else
cupkt.timestamp = avpkt->pts;
}
}
else
{
cupkt.flags = CUVID_PKT_ENDOFSTREAM;
} oResult = cuvidParseVideoData(oSourceData_.hVideoParser, &cupkt);
if ((cupkt.flags & CUVID_PKT_ENDOFSTREAM) || (oResult != CUDA_SUCCESS))
{
break;
} av_free(new_pkt.data);

这个泄漏是av_bitstream_filter_filter造成的,解决办法参考http://blog.csdn.net/lg1259156776/article/details/73283920

-----------------------------2017.8.30 补-----------------------------------

貌似还是有小伙伴被内存泄漏难住了,这里我给出我最新的读取数据包线程函数的完整代码,希望有所帮助

void VideoSource::play_thread(LPVOID lpParam)
{
AVPacket *avpkt;
avpkt = (AVPacket *)av_malloc(sizeof(AVPacket));
CUVIDSOURCEDATAPACKET cupkt;
CUresult oResult;
while (av_read_frame(pFormatCtx, avpkt) >= 0){
LOOP0:
if (bThreadExit){
break;
} if (avpkt->stream_index == videoindex)
{
AVPacket new_pkt = *avpkt; if (avpkt && avpkt->size)
{
if (h264bsfc){ int a = av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL,
&new_pkt.data, &new_pkt.size,
avpkt->data, avpkt->size,
avpkt->flags & AV_PKT_FLAG_KEY); if (a>0){
if (new_pkt.data != avpkt->data)//-added this
{
av_free_packet(avpkt); avpkt->data = new_pkt.data;
avpkt->size = new_pkt.size;
}
}
else if (a<0){
goto LOOP0;
} *avpkt = new_pkt;
} cupkt.payload_size = (unsigned long)avpkt->size;
cupkt.payload = (const unsigned char*)avpkt->data; if (avpkt->pts != AV_NOPTS_VALUE)
{
cupkt.flags = CUVID_PKT_TIMESTAMP;
if (pCodecCtx->pkt_timebase.num && pCodecCtx->pkt_timebase.den)
{
AVRational tb;
tb.num = 1;
tb.den = AV_TIME_BASE;
cupkt.timestamp = av_rescale_q(avpkt->pts, pCodecCtx->pkt_timebase, tb);
}
else
cupkt.timestamp = avpkt->pts;
}
}
else
{
cupkt.flags = CUVID_PKT_ENDOFSTREAM;
} oResult = cuvidParseVideoData(oSourceData_.hVideoParser, &cupkt);
if ((cupkt.flags & CUVID_PKT_ENDOFSTREAM) || (oResult != CUDA_SUCCESS))
{
break;
} av_free(new_pkt.data);
}
else
av_free_packet(avpkt);
} oSourceData_.pFrameQueue->endDecode();
bStarted = false; if (pCodecCtx->codec_id == AV_CODEC_ID_H264 || pCodecCtx->codec_id == AV_CODEC_ID_HEVC) {
av_bitstream_filter_close(h264bsfc);
}
}

英伟达CUVID硬解,并通过FFmpeg读取文件的更多相关文章

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

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

  2. 不用写代码就能实现深度学习?手把手教你用英伟达 DIGITS 解决图像分类问题

    2006年,机器学习界泰斗Hinton,在Science上发表了一篇使用深度神经网络进行维数约简的论文 ,自此,神经网络再次走进人们的视野,进而引发了一场深度学习革命.深度学习之所以如此受关注,是因为 ...

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

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

  4. 【并行计算-CUDA开发】英伟达硬件解码器分析

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

  5. CentOS7&period;X安装英伟达显卡采坑之路

    1.系统信息 操作系统版本:CentOS7.X 显卡版本:英伟达 Tesla P100 其他软件包安装信息: CUDA 9.0 CUDNN 7.4.2.24 lightgbm 2.2.X Boost ...

  6. 第一篇:CUDA 6&period;0 安装及配置&lpar; WIN7 64位 &sol; 英伟达G卡 &sol; VS2010 &rpar;

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

  7. Ubuntu18&period;04安装英伟达显卡驱动

    前几天买了一张RTX2060显卡,想自学一下人工智能,跑一些图形计算,安装Ubuntu18.04后发现英伟达显卡驱动安装还是有点小麻烦,所以这里记录一下安装过程,以供参考: 1.卸载系统里低版本的英伟 ...

  8. NCCL&lpar;Nvidia Collective multi-GPU Communication Library&rpar; Nvidia英伟达的Multi-GPU多卡通信框架NCCL 学习;PCIe 速率调研;

    为了了解,上来先看几篇中文博客进行简单了解: 如何理解Nvidia英伟达的Multi-GPU多卡通信框架NCCL?(较为优秀的文章) 使用NCCL进行NVIDIA GPU卡之间的通信(GPU卡通信模式 ...

  9. 玩深度学习选哪块英伟达 GPU?有性价比排名还不够!

    本文來源地址:https://www.leiphone.com/news/201705/uo3MgYrFxgdyTRGR.html 与“传统” AI 算法相比,深度学习(DL)的计算性能要求,可以说完 ...

随机推荐

  1. jQuery Ajax遍历表格,填充数据,将表格中的数据一条一条拼成Jason数组

    $.ajax({ url: baseURL + "InvoiceSale/OnQuotaInvoiceSale", //点击核销单号时,点击核销时,交互的页面           ...

  2. NYOJ-975 关于521 AC 分类: NYOJ 2014-02-25 22&colon;14 349人阅读 评论&lpar;0&rpar; 收藏

    #include<stdio.h> struct AC { int x,y; }a[1000004]; int main() { int i,j,k=0;a[125].x=1,a[521] ...

  3. mysql导出csv&sol;excel文件的几种方法&comma;mysql的load导入csv数据

    方法一 php教程用mysql的命令和shell select * into outfile './bestlovesky.xls' from bestlovesky where 1 order by ...

  4. acdream 1157Segments cdq分治

    题目链接 #include <iostream> #include <vector> #include <cstdio> #include <cstring& ...

  5. Android采用Application总结一下

    什么是 Application Application和Activity,Service由于是android框架的一个系统组件,当android程序启动时系统会创建一个 application对象.用 ...

  6. HDU 1541 Stars (树状数组)

    Problem Description Astronomers often examine star maps where stars are represented by points on a p ...

  7. 为什么使用 SLF4J 而不是 Log4J 来做 Java 日志

    转自:为什么使用 SLF4J 而不是 Log4J 来做 Java 日志 英文原文:Why use SLF4J over Log4J for logging in Java 每个Java开发人员都知道日 ...

  8. Android开发学习之路--UI之ListView

    这里再学习写android的ListView,其实我们都使用过ListView,就像手机的联系人,就是用的ListView了.下面就实现下简单的ListView吧,首先是xml文件中添加相关的代码: ...

  9. MySQL的BLOB类型(解决mysql不支持mb4编码的时候存储emoji表情问题)

    今天在存储emoji表情的时候,发现无法存储,mysql版本太低也没办法使用uft8mb4格式编码,只能将数据字段设置为blob BLOB是一个二进制大对象,可以容纳可变数量的数据.有4种BLOB类型 ...

  10. &lbrack;AWS vs Azure&rsqb; 云计算里AWS和Azure的探究&lpar;4&rpar;

    云计算里AWS和Azure的探究(4) ——Amazon EC2 和 Windows Azure Virtual Machine 接下来我们来看看Azure VM的创建.Azure里面虚拟机的创建跟A ...