OpenCV4.7.0、FFmpeg5.1 Nvidia GPU视频硬解码

1.环境

操作系统:Ubuntu18.04

GPU:Nvidia GeForce RTX 2080TI

2.安装2080TI驱动

请参考文章(158条消息) NVIDIA-GPU 驱动程序安装_洪流之源的博客-CSDN博客

3.安装cuda

请参考文章(158条消息) CUDA安装与卸载_洪流之源的博客-CSDN博客

4.安装cudnn

请参考文章(158条消息) cuDNN安装_洪流之源的博客-CSDN博客

5.安装nvidia-video-codec-sdk

nvidia-video-codec-sdk下载链接如下:

https://developer.nvidia.com/nvidia-video-codec-sdk/download

下载得到Video_Codec_SDK_12.0.16.zip的压缩包,解压后的目录有Read_Me.pdf文档,文档中有该版本SDK对驱动与CUDA最低版本的要求,比如12.0版本要求如下:

如果驱动版本或者CUDA不满足要求,可更新版本或者下载更低版本的nvidia-video-codec-sdk。

接下来安装nvidia-video-codec-sdk,其实在GPU驱动安装过程中,已经将nvidai-video-codec-sdk的库文件进行了安装,一般安装在/usr/lib/x86_64-linux-gnu/目录下,比如525.89.02版本的GPU驱动安装后,在/usr/lib/x86_64-linux-gnu/目录下存在libnvcuvid.so.525.89.02、libnvidia-encode.so.525.89.02的库文件。因此只需要安装头文件即可,如下命令将头文件拷贝至cuda/目录:

cp Video_Codec_SDK_12.0.16/Interface/* /usr/local/cuda/include/

注:上述只用了nvidia-video-codec-sdk中的头文件,而没有使用nvidia-video-codec-sdk中的libnvcuvid.so、libnvidia-encode.so库,原因是在安装显卡驱动的时候会默认安装与驱动版本兼容的libnvcuvid.so、libnvidia-encode.so,而nvidia-video-codec-sdk中的库很可能与我们安装的显卡驱动版本不一致,如果使用了nvidia-video-codec-sdk中的libnvcuvid.so、ibnvidia-encode.so编译的时候,可能不会有问题,但是运行时很可能会因为与驱动版本不兼容而报错,因为,拒绝使用nvidia-video-codec-sdk中的libnvcuvid.so、ibnvidia-encode.so库。这个可谓是Nvidia的天坑,一定要注意。

6.编译FFmpeg

opencv硬解码依赖nvidia-video-codec-sdk,如果不安装ffmpeg也不会影响opencv的硬解码,但是opencv软解码依赖ffmpeg,如果未安装ffmpeg的话,opencv无法进行软解码,因此为了保证opencv既能硬解码也能软件,接下来也安装ffmpeg,并且提供了ffmpeg英伟达硬解码的编译方式进行安装,这样ffmpeg也可通过Nvidia GPU进行硬解码。

安装依赖

sudo apt update
sudo apt install autoconf \
automake \
build-essential \
cmake \
git-core \
libass-dev \
libfreetype6-dev \
libgnutls28-dev \
libsdl2-dev \
libtool \
libva-dev \
libvdpau-dev \
libvorbis-dev \
libxcb1-dev \
libxcb-shm0-dev \
libxcb-xfixes0-dev \
pkg-config \
texinfo \
wget \
yasm \
zlib1g-dev

注意:

如果要在docker中编译ffmpeg nvidia硬解码,需要将在安装显卡驱动的时候安装的libnvcuvid.so、libnvidia-encode.so库,从宿主机拷贝到docker中,这两个库在宿主机的路径一般在/usr/lib/x86_64-linux-gnu/目录下,可提前将上述两个库拷贝至docker中,然后拷贝到docker的/lib64目录下,(一定要从宿主机目录进行拷贝,不要使用Video_Codec_SDK中的库,因为Video_Codec_SDK中的库很可能与本机安装的驱动不匹配,即便编译通过,但是运行时会出现驱动不兼容的问题)比如两个库是libnvcuvid.so.525.89.02、libnvidia-encode.so.525.89.02,在docker中操作如下:

cp libnvcuvid.so.525.89.02 /lib64/
cp libnvidia-encode.so.525.89.02 /lib64/
ln -s /lib64/libnvcuvid.so.525.89.02 /lib64/libnvcuvid.so.1
ln -s /lib64/libnvidia-encode.so.525.89.02 /lib64/libnvidia-encode.so.1
echo '/lib64' >> /etc/ld.so.conf
ldconfig

下载FFMPEG

考虑到opencv4.7.0开始支持ffmpeg5.x版本,因此下载了ffmpeg 5.1版本:

git clone https://github.com/FFmpeg/FFmpeg.git -b release/5.1

克隆ffnvcodec

ffnvcodec是ffmpeg英伟达硬解码的头文件,需要下载

git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers.git

下载的nv-codec-header目录下有README的文件,内容如下:

这个文件中指明了当前的nv-codec-header需要与Video Code SDK 12.0.6匹配(Video Code SDK即为上述下载的nvidia-video-codec-sdk,上述下载的也为12.0.6版本,满足要求),以及需要的最低驱动版本,如果驱动版本不满足要求,可更新驱动版本。

安装 ffnvcodec:

cd nv-codec-headers && sudo make install

编译ffmpeg脚本如下:

#!/bin/bash
  
./configure --enable-nonfree --enable-cuda-nvcc --enable-libnpp --extra-cflags=-I/usr/local/cuda/include --extra-ldflags=-L/usr/local/cuda/lib64 --disable-static --enable-shared

make -j$(nproc)

sudo make install

echo '/usr/local/ffmpeg/lib' >> /etc/ld.so.conf

ldconfig

测试ffmpeg硬解码支持:

分别执行如下两条命令,查看ffmpeg硬件访问与cuvid解码器:

ffmpeg -hwaccels
ffmpeg -codecs | grep cuvid

如下:

测试硬解码,执行如下命令:

ffmpeg -y -vsync 0 -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 5 -i 1.mp4 -c:a copy -c:v h264_nvenc -b:v 5M output.mp4

在执行上述命令的过程中,出现如下报错:

不清楚是什么原因导致的,但是好在依然能够正常解码,如果有知道原因的同学,可留言告知。

7.编译OpenCV

opencv的编译请参考文章(163条消息) Ubuntu20.04 编译opencv-4.5.0与opencv-contrib-4.5.0_洪流之源的博客-CSDN博客,中的1、2、3步骤。

假定你已经按照上述文章,执行了1、2、3步骤(注意opencv的源码都要换成4.7.0版本),接下来进行如下步骤:

安装Qt依赖

后续cmake配置命令开启了WITH_OPENGL选项,但是单纯的开启这个选项并不能真正的进行OPENGL的编译,需要安装Qt,因为OPENGL的编译依赖Qt,如果系统没有安装Qt,即便开启了OPENGL编译选项,因为找不到Qt,也不会进行OPENGL的编译,Qt的安装比较简单,如果安装Qt5,可直接执行如下命令:

sudo apt install qt5-default

配置cmake

因为Nvidia GPU是2080TI,需要设置CUDA_ARCH_BIN=7.5,如果是其它型号的GPU可在如下链接进行查询:

https://developer.nvidia.com/zh-cn/cuda-gpus#compute

配置命令如下:

mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
      -D CMAKE_INSTALL_PREFIX=install \
      -D WITH_TBB=ON \
      -D BUILD_TBB=ON  \
      -D ENABLE_FAST_MATH=1 \
      -D CUDA_FAST_MATH=1 \
      -D WITH_CUBLAS=1 \
      -D WITH_V4L=ON \
      -D WITH_LIBV4L=ON \
      -D WITH_CUDA=ON \
      -D WITH_CUDNN=ON \
      -D WITH_CUDEV=ON \
      -D WITH_GTK_2_X=ON \
      -D WITH_NVCUVID=ON \
      -D CUDA_ARCH_BIN=7.5 \
      -D OPENCV_EXTRA_MODULES_PATH=../opencv_contrib-4.7.0/modules \
      -D WITH_QT=ON \
      -D WITH_OPENGL=ON \
      -D WITH_FFMPEG=ON \
      ..

配置完成后,注意如下红框配置选项是否为“YES”,否则可能之前的依赖选项有问题,需要重新安装依赖,重新配置:

opencv是否支持Nvidia GPU硬解码,注意如下红框选项一定要是“YES”,并且NVIDIA CUDA选项括号中必须包含NVCUVID NVCUENV,否则编译出的opencv用Nvidia GPU硬解码时会报错:

注:如果NVDIA CUDA不包含NVCUVID NVCUENV的选项,考虑是不是没有把libnvcuvid.so、libnvidia-encode.so追加到库的搜索路径路径下,一般docker环境中会出现这种问题,可从宿主机/usr/lib/x86_64-linux-gnu目录下拷贝上述的库(一定要从宿主机目录进行拷贝,不要使用Video_Codec_SDK中的库,因为Video_Codec_SDK中的库很可能与本机安装的驱动不匹配,即便编译通过,但是运行时会出现驱动不兼容的问题),比如libnvcuvid.so.525.89.02、libnvidia-encode.so.525.89.02拷贝到docker中的/usr/lib/x86_64-linux-gnu目录下,并创建软连接,创建软连接脚本如下:

#!/bin/bash

sopath=/usr/lib/x86_64-linux-gnu

if [ ! -L ${sopath}/libcuda.so ]; then
    files=(`find $sopath/libcuda.so*`)
    raw_so=${files[0]}
    echo Create soft link ${raw_so}
    ln -s ${raw_so} ${sopath}/libcuda.so
fi

if [ ! -L ${sopath}/libnvcuvid.so ]; then
    echo Create soft link ${sopath}/libnvcuvid.so.1
    ln -s ${sopath}/libnvcuvid.so.1 ${sopath}/libnvcuvid.so
fi

if [ ! -L ${sopath}/libnvidia-encode.so ]; then
    echo Create soft link ${sopath}/libnvidia-encode.so.1
    ln -s ${sopath}/libnvidia-encode.so.1 ${sopath}/libnvidia-encode.so
fi
ldconfig

配置完成后,分别执行如下命令编译、安装

make -j$(nproc)

make install

8. 测试opencv 硬解码与软件

代码如下:

#include <opencv2/opencv.hpp>
#include <opencv2/cudacodec.hpp>
#include <chrono>

int gpu_test() 
{
    cv::cuda::printCudaDeviceInfo(cv::cuda::getDevice());
    int count = cv::cuda::getCudaEnabledDeviceCount();
    printf("GPU Device Count : %d \n", count);

    const std::string filename = "videos/1.mp4";
    // const std::string filename = "rtsp://172.17.0.1:554/1.mp4";
    cv::Ptr<cv::cudacodec::VideoReader> reader = cv::cudacodec::createVideoReader(filename);

    cv::cuda::GpuMat gpu_frame;
    
    int frame_id = 0;
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    while (reader->nextFrame(gpu_frame)) 
    {
        frame_id = frame_id + 1;
    }
    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    std::chrono::duration<double> time_useds = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
    double time_ms = time_useds.count() * 1000.0f;
    double fps = double(frame_id) / time_ms * 1000.0f;
    printf("GPU test took time: %f ms, frames: %d , FPS: %f\n", time_ms, frame_id, fps);

    reader.release();

    return 0;
}

int cpu_test()
{
    const std::string filename = "videos/1.mp4";
    // const std::string filename = "rtsp://172.17.0.1:554/1.mp4";
    cv::VideoCapture capture;
    capture.open(filename);
    if (!capture.isOpened())
    {
        printf("Open video failed !!! \n");
        return -1;
    }

    int width = (int)capture.get(cv::CAP_PROP_FRAME_WIDTH);
    int height = (int)capture.get(cv::CAP_PROP_FRAME_HEIGHT);
    printf("src video width: %d , %d \n", width, height);

    cv::Mat frame;

    int frame_id = 0;
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    while (capture.read(frame))
    {
        frame_id = frame_id + 1;

    }
    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    std::chrono::duration<double> time_useds = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
    double time_ms = time_useds.count() * 1000.0f;
    double fps = double(frame_id) / time_ms * 1000.0f;
    printf("CPU test took time: %f ms, frames: %d , FPS: %f\n", time_ms, frame_id, fps);
    
    capture.release();

    return 0;
}

int main(int argc, char const *argv[])
{
    
    gpu_test();

    cpu_test();

    return 0;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.4)

project(MoveDetection)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fPIC -O3 -std=c++11")

include_directories(
    opencv/include/opencv4
)

link_directories(
   opencv/lib
)

set(OPENCV_LIBS
    opencv_core
    opencv_highgui
    opencv_imgproc
    opencv_imgcodecs
    opencv_video
    opencv_videoio
    opencv_cudaimgproc
    opencv_cudacodec
    opencv_cudafilters
    opencv_cudabgsegm
    opencv_cudaarithm
    opencv_cudawarping
    opencv_gapi
)

add_executable(pro  src/main.cpp)

target_link_libraries(
    pro
    ${OPENCV_LIBS}
)

测试结果:

1920×1080分辨率的视频,GPU解码帧率是1565FPS,CPU解码帧率是441FPS,GPU解码效率是CPU的4倍。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2023年6月26日
下一篇 2023年6月26日

相关推荐