GPU视频编解码:X86 VideoProcessFrame 视频编解码入门(二)

一.实验设备硬件情况说明

        1.VPF框架目前不支持Jetson系列,实验设备为X86主机,

root@autodl-container-2b344a8a9f-e5fa6b67:~/autodl-tmp/VideoProcessingFramework# cat /etc/os-release 
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
root@autodl-container-2b344a8a9f-e5fa6b67:~/autodl-tmp/VideoProcessingFramework# 

        2.设备端为auto-dl的远程设备(在wsl上cuda-11.8编译不出来PyNvCodec) 

root@autodl-container-2b344a8a9f-e5fa6b67:~/autodl-tmp/VideoProcessingFramework# nvidia-smi
Sat Mar 15 15:47:49 2025       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.14              Driver Version: 550.54.14      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4090 D      On  |   00000000:C2:00.0 Off |                  Off |
| 32%   33C    P0             44W /  425W |       0MiB /  24564MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+

        3.尽量选择cuda-12.1及其以上版本吧 

root@autodl-container-2b344a8a9f-e5fa6b67:~/autodl-tmp/VideoProcessingFramework# nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2023 NVIDIA Corporation
Built on Mon_Apr__3_17:16:06_PDT_2023
Cuda compilation tools, release 12.1, V12.1.105
Build cuda_12.1.r12.1/compiler.32688072_0

二.PyNvCodec安装

        1.基础配置:cuda12.1+python3.12,cuda不要低于12.1,python3.10以上

        2.git clone https://github.com/NVIDIA/VideoProcessingFramework.git

        3.cd VideoProcessingFramework

        4.中间会出现一个error,安装一下依赖就好了

root@autodl-container-2b344a8a9f-e5fa6b67:~/autodl-tmp/VideoProcessingFramework# apt-get install -y libavfilter-dev

        5.pip3 install . 就会提示安装成功

root@autodl-container-2b344a8a9f-e5fa6b67:~/autodl-tmp/VideoProcessingFramework# nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2023 NVIDIA Corporation
Built on Mon_Apr__3_17:16:06_PDT_2023
Cuda compilation tools, release 12.1, V12.1.105
Build cuda_12.1.r12.1/compiler.32688072_0
root@autodl-container-2b344a8a9f-e5fa6b67:~/autodl-tmp/VideoProcessingFramework# apt-get install -y libavfilter-dev^C
root@autodl-container-2b344a8a9f-e5fa6b67:~/autodl-tmp/VideoProcessingFramework# python3
Python 3.12.3 | packaged by Anaconda, Inc. | (main, May  6 2024, 19:46:43) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import PyNvCodec
>>> 

三.PyNvDecoder + PyNvEncoder 实例

        1.定义超参数

# 设定输入视频的基础信息
gt_width = 848  # 视频宽度
gt_height = 464  # 视频高度
gt_res_change = 47  # 预计第 47 帧发生分辨率变化
gt_is_vfr = False  # 是否是可变帧率(VFR)
gt_pix_fmt = nvc.PixelFormat.NV12  # 设定像素格式为 NV12
gt_framerate = 30  # 设定帧率为 30 FPS
gt_num_frames = 96  # 总帧数 96
gt_timebase = 8.1380e-5  # 设定时间基准
gt_color_space = nvc.ColorSpace.BT_709  # 设定颜色空间为 BT.709
gt_color_range = nvc.ColorRange.MPEG  # 设定颜色范围

        2. 测试 GPU 硬件编码功能:读取输入视频,解码并编码成 H.264,验证编码的帧数与解码的帧数是否一致

def test_encode_all_surfaces(self):
        """
        测试 GPU 硬件编码功能:
        - 读取输入视频,解码并编码成 H.264
        - 验证编码的帧数与解码的帧数是否一致
        """
        gpu_id = 0  # 选择 GPU 设备
        res = str(gt_width) + "x" + str(gt_height)  # 设定编码分辨率
        encFrame = np.ndarray(shape=(0), dtype=np.uint8)  # 初始化编码帧数据

        # 创建解码器
        nvDec = nvc.PyNvDecoder(gt_file, gpu_id)

        # 创建编码器(H.264)
        nvEnc = nvc.PyNvEncoder(
            {
                "preset": "P4",
                "tuning_info": "high_quality",
                "codec": "h264",
                "profile": "high",
                "s": res,
                "bitrate": "1M",
            },
            gpu_id,
        )

        frames_sent = 0  # 记录发送到编码器的帧数
        frames_recv = 0  # 记录从编码器获取的帧数

        while True:
            dec_surf = nvDec.DecodeSingleSurface()  # 读取解码后的单帧数据
            if not dec_surf or dec_surf.Empty():
                break  # 结束解码

            frames_sent += 1  # 统计发送到编码器的帧数
            nvEnc.EncodeSingleSurface(dec_surf, encFrame)  # 编码帧

            if encFrame.size:
                frames_recv += 1  # 统计成功编码的帧数

        # 结束编码,确保所有帧被正确编码
        while True:
            success = nvEnc.FlushSinglePacket(encFrame)
            if success and encFrame.size:
                frames_recv += 1
            else:
                break

        # 验证解码的帧数和编码的帧数一致
        self.assertEqual(frames_sent, frames_recv)

        3.测试在 输入视频分辨率动态变化 时,解码器和编码器的重新配置功能。

def test_reconfigure(self):
        """
        测试在 **输入视频分辨率动态变化** 时,解码器和编码器的重新配置功能。

        测试流程:
        1. 读取 `test_res_change.h264` 这个视频(分辨率会在播放过程中变化)。
        2. 逐帧解码,并检查当前帧的分辨率是否发生变化:
        - 若分辨率变化,则 **重新配置** 编码器,使其匹配新分辨率。
        - 重新配置时,编码器会插入 **IDR(关键帧)**,确保编码流连续性。
        3. 每次编码后,将编码帧解码回来,检查是否符合期望的输出分辨率。

        断言:
        - **编码器的分辨率在 reconfigure 之后应与解码的帧一致**。
        - **在分辨率变化前,解码得到的帧分辨率应与原始设定一致**。

        目的:
        - 验证 `PyNvDecoder`(解码器)能够自动检测分辨率变化。
        - 验证 `PyNvEncoder` 能够正确执行 `Reconfigure()` 并维持 H.264/H.265 编码流的稳定性。
        """
        gpu_id = 0  # 选择 GPU 设备
        res = str(gt_width) + "x" + str(gt_height)  # 设定默认编码分辨率
        encFrame = np.ndarray(shape=(0), dtype=np.uint8)  # 初始化存储编码数据的 NumPy 数组

        # 初始化解码器(用于处理分辨率变化的输入视频)
        nvDec = nvc.PyNvDecoder(gt_file_res_change, gpu_id)

        # 另一个解码器,用于解码回编码后的数据
        nvRcn = nvc.PyNvDecoder(gt_width, gt_height, nvc.PixelFormat.NV12, nvc.CudaVideoCodec.H264, gpu_id)

        # 初始化编码器(初始设定为固定分辨率)
        nvEnc = nvc.PyNvEncoder(
            {
                "preset": "P4",  # 设定编码器预设参数
                "tuning_info": "high_quality",  # 设定编码质量
                "codec": "h264",  # 选择 H.264 编码
                "profile": "high",  # 选择 High Profile
                "s": res,  # 设定初始分辨率
                "bitrate": "1M",  # 设定比特率为 1Mbps
            },
            gpu_id,
        )

        frames_recn = 0  # 统计解码并成功编码的帧数

        while True:
            # 从视频文件中读取并解码单帧数据
            dec_surf = nvDec.DecodeSingleSurface()
            if not dec_surf or dec_surf.Empty():
                break  # 读取完毕,结束循环

            # 获取当前解码帧的分辨率
            sw = dec_surf.Width()
            sh = dec_surf.Height()

            # 检查是否发生了分辨率变化
            if sw != gt_width or sh != gt_height:
                # 发生分辨率变化时,需要先将编码器缓冲区内的帧全部输出
                while nvEnc.FlushSinglePacket(encFrame):
                    frames_recn += 1

                # 重新配置编码器,使其匹配新的分辨率
                res = str(sw) + "x" + str(sh)
                self.assertTrue(nvEnc.Reconfigure({"s": res}, force_idr=True, reset_encoder=True))

                # 断言:编码器的分辨率已经正确更新
                self.assertEqual(nvEnc.Width(), sw)
                self.assertEqual(nvEnc.Height(), sh)

            # 使用新的编码器配置对当前帧进行编码
            nvEnc.EncodeSingleSurface(dec_surf, encFrame)

            if encFrame.size:
                # 解码编码后的数据,确保正确性
                dec_surf = nvRcn.DecodeSurfaceFromPacket(encFrame)
                if dec_surf and not dec_surf.Empty():
                    frames_recn += 1

                    if frames_recn < gt_res_change:
                        # 分辨率变化前,解码的帧应该仍然保持原始设定的大小
                        self.assertEqual(dec_surf.Width(), gt_width)
                        self.assertEqual(dec_surf.Height(), gt_height)
                    else:
                        # 发生变化后,解码帧的大小应匹配新的输入分辨率
                        self.assertEqual(dec_surf.Width(), sw)
                        self.assertEqual(dec_surf.Height(), sh)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值