FFmpeg for video (v1.6)
1. Overview
The PPU SDK is compatible with the NVIDIA Video Codec SDK, enabling you to directly use FFmpeg plugins like cuvid, nvenc, and libnpp for hardware acceleration without any code modifications. The following FFmpeg versions have been verified:
FFmpeg 7.1.1 | Requires nv-codec-headers 12.2.72.0 Video Codec SDK must be version 12.2 or later. | Latest official FFmpeg stable release with a software VVC decoder. |
FFmpeg 7.0.1 | Requires nv-codec-headers 12.2.72.0 Video Codec SDK must be version 12.2 or later. | Latest official FFmpeg stable release. Significantly improves parallel transcode performance. |
FFmpeg 6.1.2 | Requires nv-codec-headers 12.1.14.0 Video Codec SDK must be version 12.1 or later. | Frameworks like PyAV depend on this version, making it the most widely used one. |
2. Build from source
FFmpeg: https://github.com/FFmpeg/FFmpeg
nv-codec-headers: https://github.com/FFmpeg/nv-codec-headers
Ensure that the FFmpeg version is compatible with the nv-codec-headers version.
This document provides build instructions for FFmpeg 7.0 and FFmpeg 6.1. Before you begin, install the PPU SDK and configure the runtime environment by running the envsetup.sh script.
2.1. Prepare FFmpeg source code
setup_ffmpeg.sh:
## Clone nv-codec-headers version 12.2 to match FFmpeg 7.0
git clone --branch n12.2.72.0 --depth 1 https://github.com/FFmpeg/nv-codec-headers
cp nv-codec-headers/ffnvcodec.pc.in nv-codec-headers/ffnvcodec.pc
## Download FFmpeg
wget https://ffmpeg.org/releases/ffmpeg-7.0.1.tar.gz --no-check-certificate
tar -xzvf ffmpeg-7.0.1.tar.gzPatch 1 (MUST):
The build fails because some CUDA runtime APIs are unsupported. This patch removes their definitions to ensure a successful build. For hardware-accelerated scaling, use the filters provided by libnpp.
Replace the dynlink_loader.h file in the nv-codec-headers/include/ffnvcodec/ directory with the provided file.
This patch effectively comments out the following definitions:
// tcuGLGetDevices_v2 *cuGLGetDevices;
// tcuGraphicsGLRegisterImage *cuGraphicsGLRegisterImage;
// tcuGraphicsUnregisterResource *cuGraphicsUnregisterResource;
// tcuGraphicsMapResources *cuGraphicsMapResources;
// tcuGraphicsUnmapResources *cuGraphicsUnmapResources;
// tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray;
// tcuGraphicsResourceGetMappedPointer *cuGraphicsResourceGetMappedPointer;
... ...
// tcuArrayCreate *cuArrayCreate;
// tcuArray3DCreate *cuArray3DCreate;
// tcuArrayDestroy *cuArrayDestroy;
// tcuEGLStreamProducerConnect *cuEGLStreamProducerConnect;
// tcuEGLStreamProducerDisconnect *cuEGLStreamProducerDisconnect;
// tcuEGLStreamConsumerDisconnect *cuEGLStreamConsumerDisconnect;
// tcuEGLStreamProducerPresentFrame *cuEGLStreamProducerPresentFrame;
// tcuEGLStreamProducerReturnFrame *cuEGLStreamProducerReturnFrame;... ...
// LOAD_SYMBOL(cuDevicePrimaryCtxRelease, tcuDevicePrimaryCtxRelease, "cuDevicePrimaryCtxRelease");
LOAD_SYMBOL(cuDevicePrimaryCtxRelease, tcuDevicePrimaryCtxRelease, "cuDevicePrimaryCtxRelease_v2");
// LOAD_SYMBOL(cuDevicePrimaryCtxSetFlags, tcuDevicePrimaryCtxSetFlags, "cuDevicePrimaryCtxSetFlags");
LOAD_SYMBOL(cuDevicePrimaryCtxSetFlags, tcuDevicePrimaryCtxSetFlags, "cuDevicePrimaryCtxSetFlags_v2");
... ...
// LOAD_SYMBOL(cuDevicePrimaryCtxReset, tcuDevicePrimaryCtxReset, "cuDevicePrimaryCtxReset");
LOAD_SYMBOL(cuDevicePrimaryCtxReset, tcuDevicePrimaryCtxReset, "cuDevicePrimaryCtxReset_v2");
... ...
// LOAD_SYMBOL(cuLinkCreate, tcuLinkCreate, "cuLinkCreate");
LOAD_SYMBOL(cuLinkCreate, tcuLinkCreate, "cuLinkCreate_v2");
// LOAD_SYMBOL(cuLinkAddData, tcuLinkAddData, "cuLinkAddData");
LOAD_SYMBOL(cuLinkAddData, tcuLinkAddData, "cuLinkAddData_v2");
... ...
// LOAD_SYMBOL(cuModuleGetGlobal, tcuModuleGetGlobal, "cuModuleGetGlobal");
LOAD_SYMBOL(cuModuleGetGlobal, tcuModuleGetGlobal, "cuModuleGetGlobal_v2");
... ...
// LOAD_SYMBOL(cuGLGetDevices, tcuGLGetDevices_v2, "cuGLGetDevices_v2");
// LOAD_SYMBOL(cuGraphicsGLRegisterImage, tcuGraphicsGLRegisterImage, "cuGraphicsGLRegisterImage");
// LOAD_SYMBOL(cuGraphicsUnregisterResource, tcuGraphicsUnregisterResource, "cuGraphicsUnregisterResource");
// LOAD_SYMBOL(cuGraphicsMapResources, tcuGraphicsMapResources, "cuGraphicsMapResources");
// LOAD_SYMBOL(cuGraphicsUnmapResources, tcuGraphicsUnmapResources, "cuGraphicsUnmapResources");
// LOAD_SYMBOL(cuGraphicsSubResourceGetMappedArray, tcuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray");
// LOAD_SYMBOL(cuGraphicsResourceGetMappedPointer, tcuGraphicsResourceGetMappedPointer, "cuGraphicsResourceGetMappedPointer_v2");
... ...
// LOAD_SYMBOL(cuArrayCreate, tcuArrayCreate, "cuArrayCreate_v2");
// LOAD_SYMBOL(cuArray3DCreate, tcuArray3DCreate, "cuArray3DCreate_v2");
// LOAD_SYMBOL(cuArrayDestroy, tcuArrayDestroy, "cuArrayDestroy");
// LOAD_SYMBOL_OPT(cuEGLStreamProducerConnect, tcuEGLStreamProducerConnect, "cuEGLStreamProducerConnect");
// LOAD_SYMBOL_OPT(cuEGLStreamProducerDisconnect, tcuEGLStreamProducerDisconnect, "cuEGLStreamProducerDisconnect");
// LOAD_SYMBOL_OPT(cuEGLStreamConsumerDisconnect, tcuEGLStreamConsumerDisconnect, "cuEGLStreamConsumerDisconnect");
// LOAD_SYMBOL_OPT(cuEGLStreamProducerPresentFrame, tcuEGLStreamProducerPresentFrame, "cuEGLStreamProducerPresentFrame");
// LOAD_SYMBOL_OPT(cuEGLStreamProducerReturnFrame, tcuEGLStreamProducerReturnFrame, "cuEGLStreamProducerReturnFrame");Patch 2 (Optional):
This patch redirects user requests for nvdec to cuvid decoding. This allows existing FFmpeg commands that specify only -hwaccel cuda (without -cuvid) to use cuvid decoding, eliminating the need to modify higher-level commands.
--- fftools/ffmpeg_dec.c 2025-04-17 12:45:51.890627513 +0000
+++ fftools/ffmpeg_dec.c 2025-04-21 03:32:55.503247974 +0000
@@ -1174,6 +1174,21 @@
dp->sar_override = o->par->sample_aspect_ratio;
+ if (dp->hwaccel_device_type == AV_HWDEVICE_TYPE_CUDA && codec->type == AVMEDIA_TYPE_VIDEO &&
+ (av_strcasecmp(codec->name, "rawvideo") != 0) && (!codec->wrapper_name || av_strcasecmp(codec->wrapper_name, "cuvid") != 0)) {
+ const char *cuvid_suffix = "_cuvid";
+ size_t cuvid_length = av_strnlen(codec->name, 64) + av_strnlen(cuvid_suffix, 64) + 1;
+ char *cuvid_name = (char*)av_malloc(cuvid_length);
+ if (cuvid_name) {
+ av_strlcpy(cuvid_name, codec->name, cuvid_length);
+ av_strlcat(cuvid_name, cuvid_suffix, cuvid_length);
+ const AVCodec *cuvid_codec = avcodec_find_decoder_by_name(cuvid_name);
+ if (cuvid_codec) {
+ codec = cuvid_codec;
+ }
+ av_free(cuvid_name);
+ }
+ }
dp->dec_ctx = avcodec_alloc_context3(codec);
if (!dp->dec_ctx)
return AVERROR(ENOMEM);2.2. Build FFmpeg
build_ffmpeg.sh:
mkdir output
cd ffmpeg-7.0.1
export PKG_CONFIG_PATH=$(pwd)/../nv-codec-headers:$PKG_CONFIG_PATH
./configure --enable-shared \
--enable-nonfree --enable-gpl \
--enable-cuvid --enable-nvenc --enable-libnpp \
--extra-cflags="-I$(pwd)/../nv-codec-headers/include -I$CUDA_HOME/include" \
--extra-ldflags="-L$CUDA_HOME/lib64" \
--prefix=$(pwd)/../output
make -j16 && make installThe --prefix option specifies the installation directory. If not set, FFmpeg is installed to the /usr/local directory by default.
After a successful compilation and installation, the bin, include, lib, and share directories are created in the installation directory. The lib directory contains a pkgconfig directory. You can run pkg-config --modversion libavcodec to view the version number.
3. Examples
cd ../output
export LD_LIBRARY_PATH=lib:$LD_LIBRARY_PATH
# Test FFmpeg decode with cuvid
bin/ffmpeg -vcodec h264_cuvid -i 640x360_y8c8.h264 -frames 10 -y out-dec-h264.yuv
bin/ffmpeg -hwaccel cuda -i 640x360_y8c8.mp4 -frames 10 -y out-dec-h264.yuv # Requires Patch 2
# Test FFmpeg transcode with cuvid and nvenc
bin/ffmpeg -hwaccel_output_format cuda -vcodec h264_cuvid -i 640x360_y8c8.h264 -vcodec hevc_nvenc -r 30.0 -b:v 15000000 -preset p4 output.hevc
# Alternatively, with Patch 2 applied:
bin/ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i 640x360_y8c8.h264 -vcodec hevc_nvenc -r 30.0 -b:v 15000000 -preset p4 output.hevc
# Test FFmpeg transcode with cuvid, nvenc, and npp-accelerated resizing
bin/ffmpeg -hwaccel_output_format cuda -vcodec h264_cuvid -i input.h264 -vcodec hevc_nvenc -frames 300 -preset p7 -acodec copy -y output.h2654. FAQ
Q. What hardware acceleration capabilities does FFmpeg support on PPU?
A. FFmpeg on PPU supports hardware decoding for h264_cuvid, hevc_cuvid, av1_cuvid, and vp9_cuvid. An additional patch is available upon request for AVS2 support. For hardware encoding, it supports h264_nvenc, hevc_nvenc, and av1_nvenc. It also supports libnpp-accelerated transpose, color-conversion, and scale operations.
5. Appendix A: Building FFmpeg 6.1
FFmpeg 7.1 introduced significant architectural changes to optimize parallel transcoding and multi-stream performance, leading to noticeable improvements. However, if you need to use FFmpeg 6.1 for compatibility reasons, you must download and install the older version.
## Clone nv-codec-headers version 12.1 to match FFmpeg 6.1
git clone --branch n12.1.14.0 --depth 1 https://github.com/FFmpeg/nv-codec-headers
cp nv-codec-headers/ffnvcodec.pc.in nv-codec-headers/ffnvcodec.pc
## Download FFmpeg
wget https://ffmpeg.org/releases/ffmpeg-6.1.2.tar.gz --no-check-certificate
tar -xzvf ffmpeg-6.1.2.tar.gzPatch 1 (MUST):
Replace the dynlink_loader.h file in the nv-codec-headers/include/ffnvcodec/ directory with the one provided in the attachment.
mkdir output
cd ffmpeg-6.1.2
export PKG_CONFIG_PATH=$(pwd)/../nv-codec-headers:$PKG_CONFIG_PATH
./configure --enable-shared \
--enable-nonfree --enable-gpl \
--enable-cuvid --enable-nvenc --enable-libnpp \
--extra-cflags="-I$(pwd)/../nv-codec-headers/include -I$CUDA_HOME/include" \
--extra-ldflags="-L$CUDA_HOME/lib64" \
--prefix=$(pwd)/../output
make -j16 && make installPatch 2 (Optional):
--- fftools/ffmpeg_demux.c 2025-04-17 12:45:51.890627513 +0000
+++ fftools/ffmpeg_demux.c 2025-04-21 03:32:55.503247974 +0000
@@ -1173,6 +1173,21 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st)
(o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA))
ist->user_set_discard = AVDISCARD_ALL;
+ if (ist->hwaccel_device_type == AV_HWDEVICE_TYPE_CUDA && ist->dec->type == AVMEDIA_TYPE_VIDEO &&
+ (av_strcasecmp(ist->dec->name, "rawvideo") != 0) && (!ist->dec->wrapper_name || av_strcasecmp(ist->dec->wrapper_name, "cuvid") != 0)) {
+ const char *cuvid_suffix = "_cuvid";
+ size_t cuvid_length = av_strnlen(ist->dec->name, 64) + av_strnlen(cuvid_suffix, 64) + 1;
+ char *cuvid_name = (char*)av_malloc(cuvid_length);
+ if (cuvid_name) {
+ av_strlcpy(cuvid_name, ist->dec->name, cuvid_length);
+ av_strlcat(cuvid_name, cuvid_suffix, cuvid_length);
+ const AVCodec *cuvid_codec = avcodec_find_decoder_by_name(cuvid_name);
+ if (cuvid_codec) {
+ ist->dec = cuvid_codec;
+ }
+ av_free(cuvid_name);
+ }
+ }
ist->dec_ctx = avcodec_alloc_context3(ist->dec);
if (!ist->dec_ctx)
return AVERROR(ENOMEM);6. Appendix B: FFmpeg ecosystem
6.1. ffmpeg-python
Official website: https://github.com/kkroening/ffmpeg-python
ffmpeg-python is a wrapper that builds an FFmpeg command from the provided arguments and then calls the FFmpeg executable. Therefore, FFmpeg must be installed in your runtime environment, or the paths to its bin and lib directories must be configured.
Because the PPU Video Codec supports only cuvid decode, configuring only hwaccel='cuda' is not sufficient. You must also configure the cuvid parameters as shown in the following example.
Example:
import ffmpeg
import sys
probe = ffmpeg.probe('input.mp4')
video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
if video_stream is None or video_stream['codec_name'] is None:
sys.exit()
if video_stream['codec_name'] in ['h264', 'hevc', 'av1', 'vp9']:
out, _ = (
ffmpeg.input('input.mp4', vcodec = video_stream['codec_name']+'_cuvid')
.output('output.rgb', format='rawvideo', pix_fmt='rgb24')
.run()
)
else:
out, _ = (
ffmpeg.input('input.mp4')
.output('output.rgb', format='rawvideo', pix_fmt='rgb24')
.run()
ffmpeg-python is a lightweight wrapper for the FFmpeg command-line tool. It can process data from files or via pipe: (stdin/stdout). Since the data is on the CPU while decoding happens on the GPU, this workflow requires an unavoidable data copy from the CPU to the GPU for decoding, which can degrade performance. Therefore, we do not recommend this method.
6.2. PyAV
Torchvision supports three backends: PyAV, video_reader, and cuda. The default backend is PyAV. The video_reader backend performs CPU-only encoding and decoding, while the cuda backend uses the GPU for decoding. PyAV calls FFmpeg to handle encoding and decoding. Therefore, PyAV relies on the hardware acceleration capabilities built into FFmpeg, and you must build and install a version of FFmpeg that supports hardware acceleration.
As of this writing, PyAV does not support FFmpeg 7.0. You must build the FFmpeg 6.1 version described in Appendix A.
PyAV official website: https://github.com/PyAV-Org/PyAV
Call path 1: pytorch->torchvision->PyAV->ffmpeg:
To enable hardware decoding support, you must modify the Torchvision source code (torchvision/io/video_reader.py):
--- a/torchvision/io/video_reader.py
+++ b/torchvision/io/video_reader.py
@@ -280,6 +280,13 @@ class VideoReader:
if self.backend == "pyav":
stream_type = stream.split(":")[0]
stream_id = 0 if len(stream.split(":")) == 1 else int(stream.split(":")[1])
+ video_stream = self.container.streams.video[stream_id]
+ # Setting up the codec with cuvid
+ if video_stream.codec.name in ('h264', 'hevc', 'av1', 'vp9'):
+ codec_name = f'{video_stream.codec.name}_cuvid'
+ else:
+ codec_name = video_stream.codec.name # Fallback to software decoding
+ video_stream.codec_context = av.codec.CodecContext.create(codec_name, 'r')
self.pyav_stream = {stream_type: stream_id}
self._c = self.container.decode(**self.pyav_stream)By default, PyAV outputs data in TCHW format.
Hardware encoding support does not require modifying Torchvision code, but you must specify the nvenc codec name in the function call:
from torchvision.io import write_video
# Explicitly specify the codec. We currently support h264_nvenc, av1_nvenc, and hevc_nvenc.
write_video(save_path, frames, fps=fps, video_codec="h264_nvenc")Call path 2: pytorch-> torchaudio-> PyAV -> ffmpeg:
Torchaudio also supports PyAV as a backend for hardware decoding. Torchaudio does not require code modifications, but you must explicitly specify the cuvid decoder name:
import torch
import torchaudio
from torchaudio.io import StreamReader
from torchaudio.utils import ffmpeg_utils
s = StreamReader(src)
# Explicitly specify the cuvid decoder. We currently support h264_cuvid, av1_cuvid, hevc_cuvid, and vp9_cuvid.
s.add_video_stream(int(s.get_src_stream_info(0).frame_rate), decoder="h264_cuvid")
s.fill_buffer()
(video,) = s.pop_chunks()Note: You can use torchvision.get_video_backend() to get the current backend and torchvision.set_video_backend("pyav") to set the default backend for torchvision. However, the default installation of torchvision does not support the "cuda" backend. You need to manually compile and install torchvision to use the "cuda" backend.
6.3. torchcodec
Official documentation: https://ai-gerrit.eng.t-head.cn/c/acvid/+/67622
Project repository: https://github.com/pytorch/torchcodec
Call path: pytorch-> torchcodec -> ffmpeg
The SAIL source provides pip packages for torchcodec 0.2.1 and 0.4. We recommend installing torchcodec directly from the SAIL source.
torchcodec versioning is tied to specific PyTorch and Python versions, as shown below:
torchcodec | torch | Python |
main / nightly | main / nightly | >=3.9, <=3.13 |
0.4 | 2.7 | >=3.9, <=3.13 |
0.3 | 2.7 | >=3.9, <=3.13 |
0.2.1 | 2.6 | >=3.9, <=3.13 |
0.2.0 | 2.6 | >=3.9, <=3.13 |
0.1.1 | 2.5 | >=3.9, <=3.12 |
The following steps show how to build and install torchcodec from source:
Install the FFmpeg dependency. Both FFmpeg 6.1 and 7.0 are supported.
Build from source and install the package:
# branch name can be: release/0.2.1, release/0.2.0, or release/0.1.1
git clone --branch <branch_name> https://github.com/pytorch/torchcodec.git
cd torchcodec/
git apply torchcodec_<version>.diff
ENABLE_CUDA=1 I_CONFIRM_THIS_IS_NOT_A_LICENSE_VIOLATION=1 pip install . --no-build-isolationThe torchcodec source code requires two modifications to run on the PPU:
diff --git a/src/torchcodec/_core/SingleStreamDecoder.cpp b/src/torchcodec/_core/SingleStreamDecoder.cpp
index b73f703..fc7780a 100644
--- a/src/torchcodec/_core/SingleStreamDecoder.cpp
+++ b/src/torchcodec/_core/SingleStreamDecoder.cpp
@@ -388,9 +388,21 @@ void SingleStreamDecoder::addStream(
// addStream() which is supposed to be generic
if (mediaType == AVMEDIA_TYPE_VIDEO) {
if (deviceInterface_) {
- avCodec = makeAVCodecOnlyUseForCallingAVFindBestStream(
- deviceInterface_->findCodec(streamInfo.stream->codecpar->codec_id)
- .value_or(avCodec));
+ if (device.type() != torch::kCUDA) {
+ avCodec = makeAVCodecOnlyUseForCallingAVFindBestStream(
+ deviceInterface_->findCodec(streamInfo.stream->codecpar->codec_id)
+ .value_or(avCodec));
+ }
+ else {
+ const char* cuvid_suffix = "_cuvid";
+ size_t cuvid_length = std::strlen(avCodec->name) + std::strlen(cuvid_suffix) + 1;
+ char* cuvid_name = new char[cuvid_length];
+ std::strcpy(cuvid_name, avCodec->name);
+ std::strcat(cuvid_name, cuvid_suffix);
+ avCodec = avcodec_find_decoder_by_name(cuvid_name);
+ delete[] cuvid_name;
+ TORCH_CHECK(avCodec != nullptr);
+ }
}
}
@@ -417,6 +429,12 @@ void SingleStreamDecoder::addStream(
throw std::invalid_argument(getFFMPEGErrorStringFromErrorCode(retVal));
}
+ if (device.type() == torch::kCUDA) {
+ codecContext->hw_frames_ctx = av_hwframe_ctx_alloc(codecContext->hw_device_ctx);
+ AVHWFramesContext* hwframe_ctx = (AVHWFramesContext*)codecContext->hw_frames_ctx->data;
+ // in avcodec_open2, cuvid will not set sw_format until sequence callback in avcodec_send_packet()
+ hwframe_ctx->sw_format = codecContext->sw_pix_fmt;
+ }
codecContext->time_base = streamInfo.stream->time_base;
containerMetadata_.allStreamMetadata[activeStreamIndex_].codecName =
std::string(avcodec_get_name(codecContext->codec_id));diff --git a/src/torchcodec/decoders/_core/VideoDecoder.cpp b/src/torchcodec/decoders/_core/VideoDecoder.cpp
index 97214ce..530541d 100644
--- a/src/torchcodec/decoders/_core/VideoDecoder.cpp
+++ b/src/torchcodec/decoders/_core/VideoDecoder.cpp
@@ -449,9 +449,14 @@ void VideoDecoder::addStream(
// TODO_CODE_QUALITY it's pretty meh to have a video-specific logic within
// addStream() which is supposed to be generic
if (mediaType == AVMEDIA_TYPE_VIDEO && device.type() == torch::kCUDA) {
- avCodec = makeAVCodecOnlyUseForCallingAVFindBestStream(
- findCudaCodec(device, streamInfo.stream->codecpar->codec_id)
- .value_or(avCodec));
+ const char* cuvid_suffix = "_cuvid";
+ size_t cuvid_length = std::strlen(avCodec->name) + std::strlen(cuvid_suffix) + 1;
+ char* cuvid_name = new char[cuvid_length];
+ std::strcpy(cuvid_name, avCodec->name);
+ std::strcat(cuvid_name, cuvid_suffix);
+ avCodec = avcodec_find_decoder_by_name(cuvid_name);
+ delete[] cuvid_name;
+ TORCH_CHECK(avCodec != nullptr);
}
AVCodecContext* codecContext = avcodec_alloc_context3(avCodec);
@@ -474,6 +479,12 @@ void VideoDecoder::addStream(
throw std::invalid_argument(getFFMPEGErrorStringFromErrorCode(retVal));
}
+ if (device.type() == torch::kCUDA) {
+ codecContext->hw_frames_ctx = av_hwframe_ctx_alloc(codecContext->hw_device_ctx);
+ AVHWFramesContext* hwframe_ctx = (AVHWFramesContext*)codecContext->hw_frames_ctx->data;
+ // in avcodec_open2, cuvid will not set sw_format until sequence callback in avcodec_send_packet()
+ hwframe_ctx->sw_format = codecContext->sw_pix_fmt;
+ }
codecContext->time_base = streamInfo.stream->time_base;
containerMetadata_.allStreamMetadata[activeStreamIndex_].codecName =
std::string(avcodec_get_name(codecContext->codec_id));Example usage:
python sample_torchcodec.pyimport torch
print(f"{torch.__version__=}")
print(f"{torch.cuda.is_available()=}")
print(f"{torch.cuda.get_device_properties(0)=}")
import torchcodec
from torchcodec.decoders import VideoDecoder
decoder = VideoDecoder("sample.mp4", device="cuda")
frame = decoder[0]6.4. imageio-ffmpeg
imageio-ffmpeg is an imageio plugin that provides a wrapper for FFmpeg. Similar to ffmpeg-python, it works by combining input parameters and then calling the ffmpeg executable. Therefore, you must install ffmpeg in your runtime environment or set the bin and lib directories for ffmpeg. We do not recommend using this plugin because the official documentation explicitly states: You should probably use PyAV instead; it is faster and offers more features.
imageio-ffmpeg: https://github.com/imageio/imageio-ffmpeg
Example usage:
import imageio
video_reader = imageio.get_reader('input.mp4', 'ffmpeg', ffmpeg_params=['-c:v', 'h264_cuvid'])
frame_list = []
for i, frame in enumerate(video_reader):
frame_list.append(frame)
imageio.mimsave('out.mp4', frame_list)6.5. Decord
The default installation of Decord is CPU-only and does not support hardware acceleration. You must build and install it from source to enable this feature.
Decord depends on FFmpeg, but only uses its demuxing capabilities. You do not need to build FFmpeg with hardware acceleration support.
A bug in older versions of FFmpeg can cause a deadlock in Decord. This issue was resolved in FFmpeg 7. Therefore, you must build and install FFmpeg version 7.0 or later.Download Decord:
git clone -b v0.6.0 --depth 1 --recursive https://github.com/dmlc/decord
cd decord
git apply decord_ffmpeg7.patchThe patch primarily updates Decord's dependency on FFmpeg from version 4.2 to 7.0 or later:
decord_ffmpeg7.patch:
+++ b/src/video/nvcodec/cuda_threaded_decoder.cc
@@ -17,7 +17,7 @@ namespace decord {
namespace cuda {
using namespace runtime;
-CUThreadedDecoder::CUThreadedDecoder(int device_id, AVCodecParameters *codecpar, AVInputFormat *iformat)
+CUThreadedDecoder::CUThreadedDecoder(int device_id, AVCodecParameters *codecpar, const AVInputFormat *iformat)
: device_id_(device_id), stream_({device_id, false}), device_{}, ctx_{}, parser_{}, decoder_{},
pkt_queue_{}, frame_queue_{},
run_(false), frame_count_(0), draining_(false),
@@ -70,7 +70,7 @@ CUThreadedDecoder::CUThreadedDecoder(int device_id, AVCodecParameters *codecpar,
}
}
-void CUThreadedDecoder::InitBitStreamFilter(AVCodecParameters *codecpar, AVInputFormat *iformat) {
+void CUThreadedDecoder::InitBitStreamFilter(AVCodecParameters *codecpar, const AVInputFormat *iformat) {
const char* bsf_name = nullptr;
if (AV_CODEC_ID_H264 == codecpar->codec_id) {
// H.264
diff --git a/src/video/nvcodec/cuda_threaded_decoder.h b/src/video/nvcodec/cuda_threaded_decoder.h
index d7e6fcd..61958a1 100644
--- a/src/video/nvcodec/cuda_threaded_decoder.h
+++ b/src/video/nvcodec/cuda_threaded_decoder.h
@@ -46,7 +46,7 @@ class CUThreadedDecoder final : public ThreadedDecoderInterface {
using FrameOrderQueuePtr = std::unique_ptr<FrameOrderQueue>;
public:
- CUThreadedDecoder(int device_id, AVCodecParameters *codecpar, AVInputFormat *iformat);
+ CUThreadedDecoder(int device_id, AVCodecParameters *codecpar, const AVInputFormat *iformat);
void SetCodecContext(AVCodecContext *dec_ctx, int width = -1, int height = -1, int rotation = 0);
bool Initialized() const;
void Start();
@@ -70,7 +70,7 @@ class CUThreadedDecoder final : public ThreadedDecoderInterface {
void LaunchThreadImpl();
void RecordInternalError(std::string message);
void CheckErrorStatus();
- void InitBitStreamFilter(AVCodecParameters *codecpar, AVInputFormat *iformat);
+ void InitBitStreamFilter(AVCodecParameters *codecpar, const AVInputFormat *iformat);
int device_id_;
CUStream stream_;
diff --git a/src/video/video_reader.cc b/src/video/video_reader.cc
index af4858d..99c9635 100644
--- a/src/video/video_reader.cc
+++ b/src/video/video_reader.cc
@@ -145,7 +145,7 @@ VideoReader::~VideoReader(){
void VideoReader::SetVideoStream(int stream_nb) {
if (!fmt_ctx_) return;
- AVCodec *dec;
+ const AVCodec *dec;
int st_nb = av_find_best_stream(fmt_ctx_.get(), AVMEDIA_TYPE_VIDEO, stream_nb, -1, &dec, 0);
// LOG(INFO) << "find best stream: " << st_nb;
CHECK_GE(st_nb, 0) << "ERROR cannot find video stream with wanted index: " << stream_nb;Build Decord:
mkdir build && cd build
cmake .. -DUSE_CUDA=ON -DCMAKE_BUILD_TYPE=Release
makeInstall Decord:
cd ../python
python3 setup.py install --userExample usage:
import decord
import torch
from decord import gpu, cpu
video_path="input.mp4"
vr = decord.VideoReader(video_path, ctx=gpu(0))
nframes = 40
total_frames = 500
idx = torch.linspace(0, total_frames - 1, nframes).round().long().tolist()
video = vr.get_batch(idx).asnumpy()