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)