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

2023-12-06 19:38

本文主要是介绍英伟达CUVID硬解,并通过FFmpeg读取文件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

      虽然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");elseh264bsfc = 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");elseh264bsfc = av_bitstream_filter_init("hevc_mp4toannexb");
}

网上有许多代码和伪代码都说实现了把数据源修改为FFmpeg,但我在尝试的时候发现cuvidCreateVideoParser创建的Parser的回调函数都没有调用。经过一番折腾,综合英伟达网站、stackoverflow和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);}elsecupkt.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.一点数据

20路

    这是在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);}elsecupkt.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);}elsecupkt.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);}elsecupkt.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);}elseav_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读取文件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

解决Java中基于GeoTools的Shapefile读取乱码的问题

《解决Java中基于GeoTools的Shapefile读取乱码的问题》本文主要讨论了在使用Java编程语言进行地理信息数据解析时遇到的Shapefile属性信息乱码问题,以及根据不同的编码设置进行属... 目录前言1、Shapefile属性字段编码的情况:一、Shp文件常见的字符集编码1、System编码

利用Python实现添加或读取Excel公式

《利用Python实现添加或读取Excel公式》Excel公式是数据处理的核心工具,从简单的加减运算到复杂的逻辑判断,掌握基础语法是高效工作的起点,下面我们就来看看如何使用Python进行Excel公... 目录python Excel 库安装Python 在 Excel 中添加公式/函数Python 读取

Python如何实现读取csv文件时忽略文件的编码格式

《Python如何实现读取csv文件时忽略文件的编码格式》我们再日常读取csv文件的时候经常会发现csv文件的格式有多种,所以这篇文章为大家介绍了Python如何实现读取csv文件时忽略文件的编码格式... 目录1、背景介绍2、库的安装3、核心代码4、完整代码1、背景介绍我们再日常读取csv文件的时候经常

C#中读取XML文件的四种常用方法

《C#中读取XML文件的四种常用方法》Xml是Internet环境中跨平台的,依赖于内容的技术,是当前处理结构化文档信息的有力工具,下面我们就来看看C#中读取XML文件的方法都有哪些吧... 目录XML简介格式C#读取XML文件方法使用XmlDocument使用XmlTextReader/XmlTextWr

Java读取InfluxDB数据库的方法详解

《Java读取InfluxDB数据库的方法详解》本文介绍基于Java语言,读取InfluxDB数据库的方法,包括读取InfluxDB的所有数据库,以及指定数据库中的measurement、field、... 首先,创建一个Java项目,用于撰写代码。接下来,配置所需要的依赖;这里我们就选择可用于与Infl

C#读取本地网络配置信息全攻略分享

《C#读取本地网络配置信息全攻略分享》在当今数字化时代,网络已深度融入我们生活与工作的方方面面,对于软件开发而言,掌握本地计算机的网络配置信息显得尤为关键,而在C#编程的世界里,我们又该如何巧妙地读取... 目录一、引言二、C# 读取本地网络配置信息的基础准备2.1 引入关键命名空间2.2 理解核心类与方法

SpringBoot使用Apache POI库读取Excel文件的操作详解

《SpringBoot使用ApachePOI库读取Excel文件的操作详解》在日常开发中,我们经常需要处理Excel文件中的数据,无论是从数据库导入数据、处理数据报表,还是批量生成数据,都可能会遇到... 目录项目背景依赖导入读取Excel模板的实现代码实现代码解析ExcelDemoInfoDTO 数据传输

Python读取TIF文件的两种方法实现

《Python读取TIF文件的两种方法实现》本文主要介绍了Python读取TIF文件的两种方法实现,包括使用tifffile库和Pillow库逐帧读取TIFF文件,具有一定的参考价值,感兴趣的可以了解... 目录方法 1:使用 tifffile 逐帧读取安装 tifffile:逐帧读取代码:方法 2:使用

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显