PyNvVideoCodec
PyNvVideoCodec provides Python bindings to NVIDIA’s hardware-accelerated video codec APIs (NVDEC/NVENC). It enables efficient video decoding and encoding directly on GPU, perfect for video processing pipelines with CV-CUDA.
Key Points:
Hardware-accelerated video decoding (H.264, NV12, etc.)
Decodes directly to GPU memory
Batch frame decoding for better throughput
Hardware-accelerated encoding for output
Minimal CPU involvement in the entire pipeline
Required Imports:
from __future__ import annotations
from pathlib import Path
import sys
import cvcuda
try:
import PyNvVideoCodec as nvc
except ModuleNotFoundError as error:
if error.name != "PyNvVideoCodec" or sys.version_info < (3, 13):
raise
nvc = None
Setup PyNvVideoCodec:
# Setup paths
video_path = (
Path(__file__).parent.parent
/ "assets"
/ "videos"
/ "pexels-chiel-slotman-4423925-1920x1080-25fps.mp4"
)
cvcuda_root = Path(__file__).parent.parent.parent
output_dir = cvcuda_root / ".cache"
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / "pexels-chiel-slotman-640x480.h264"
device_id = 0
# Create decoder
decoder = nvc.SimpleDecoder(
str(video_path),
gpu_id=device_id,
output_color_type=nvc.OutputColorType.RGB,
need_scanned_stream_metadata=False,
max_width=1920,
max_height=1080,
use_device_memory=True,
)
The decoder is configured to output RGB frames directly to device memory
Read video frames into CV-CUDA tensors and process:
processed_frames: list[cvcuda.Tensor] = []
frame_idx = 0
num_frames = decoder.get_stream_metadata().num_frames
while frame_idx < num_frames:
batch_size = min(10, num_frames - frame_idx)
frame_chunk = decoder.get_batch_frames(batch_size)
for frame in frame_chunk:
# get the CV-CUDA tensor from the PyNvVideoCodec DecodedFrame
cvcuda_tensor = cvcuda.as_tensor(frame, "HWC")
# process the frame with CV-CUDA
# importantly since CV-CUDA did not copy from the PyNvVideoCodec buffer
# we must use the buffer before we read the next batch of frames
# here we process one frame at a time for simplicity
resized = cvcuda.resize(cvcuda_tensor, (480, 640, 3), cvcuda.Interp.LINEAR)
nv12_frame = cvcuda.cvtcolor(resized, cvcuda.ColorConversion.RGB2YUV_NV12)
# store the new CV-CUDA tensor (resize and cvtcolor make new buffers)
processed_frames.append(nv12_frame)
frame_idx += 1
Frames are decoded in batches for efficiency. Each frame is converted to a CV-CUDA tensor with HWC (Height × Width × Channels) layout.
CV-CUDA operations can be applied to each frame. Here we resize from 1920×1080 to 640×480 and convert
from BGR to NV12 (YUV 4:2:0 planar) to match the encoder’s expected format.
Alternatively, you could use cvcuda.stack() to stack the frames into a single tensor and
apply operations on the stacked tensor. For clarity, we process each frame individually here.
Encode processed frames:
# Encode frames (only if encoder hardware is available)
try:
encoder = nvc.CreateEncoder(
width=640,
height=480,
fmt="NV12",
codec="h264",
usecpuinputbuffer=False,
)
except Exception as e:
print(f"Encoder not available - will skip encoding step: {e}")
return
with output_path.open("wb") as f:
for frame in processed_frames:
bitstream = encoder.Encode(frame)
f.write(bytearray(bitstream))
bitstream = encoder.EndEncode()
f.write(bytearray(bitstream))
print(f"Successfully encoded video to {output_path}")
The encoder is set up to accept NV12 pixel format frames at the target resolution (640×480). If a hardware video encoder (NVENC) is not available, the creation of the encoder object will raise an exception and the encoding step is skipped.
The processed frames are encoded back to video. The encoder produces compressed bitstreams that are
written to the output file. Note that EndEncode() must be called to flush any remaining frames.
Typical Use Cases:
Video analytics preprocessing
Real-time video transformation
Video transcoding pipelines
Video quality enhancement
Object detection/segmentation on video
Complete Example: See samples/interoperability/pynvvideocodec_interop.py