Skip to content

Commit 6295cb6

Browse files
committed
First attempt for nvcuvid dynamic loader
1 parent c1798db commit 6295cb6

File tree

5 files changed

+215
-28
lines changed

5 files changed

+215
-28
lines changed

src/torchcodec/_core/BetaCudaDeviceInterface.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "src/torchcodec/_core/DeviceInterface.h"
1515
#include "src/torchcodec/_core/FFMPEGCommon.h"
1616
#include "src/torchcodec/_core/NVDECCache.h"
17+
#include "src/torchcodec/_core/NVCUVIDLoader.h"
1718

1819
// #include <cuda_runtime.h> // For cudaStreamSynchronize
1920
#include "src/torchcodec/_core/nvcuvid_include/cuviddec.h"
@@ -53,12 +54,13 @@ pfnDisplayPictureCallback(void* pUserData, CUVIDPARSERDISPINFO* dispInfo) {
5354
}
5455

5556
static UniqueCUvideodecoder createDecoder(CUVIDEOFORMAT* videoFormat) {
57+
const auto& nvcuvid = NVCUVIDLoader::instance().api();
5658
// Check decoder capabilities - same checks as DALI
5759
auto caps = CUVIDDECODECAPS{};
5860
caps.eCodecType = videoFormat->codec;
5961
caps.eChromaFormat = videoFormat->chroma_format;
6062
caps.nBitDepthMinus8 = videoFormat->bit_depth_luma_minus8;
61-
CUresult result = cuvidGetDecoderCaps(&caps);
63+
CUresult result = nvcuvid.cuvidGetDecoderCaps(&caps);
6264
TORCH_CHECK(result == CUDA_SUCCESS, "Failed to get decoder caps: ", result);
6365

6466
TORCH_CHECK(
@@ -157,7 +159,7 @@ static UniqueCUvideodecoder createDecoder(CUVIDEOFORMAT* videoFormat) {
157159
decoderParams.display_area.bottom = videoFormat->display_area.bottom;
158160

159161
CUvideodecoder* decoder = new CUvideodecoder();
160-
result = cuvidCreateDecoder(decoder, &decoderParams);
162+
result = nvcuvid.cuvidCreateDecoder(decoder, &decoderParams);
161163
TORCH_CHECK(
162164
result == CUDA_SUCCESS, "Failed to create NVDEC decoder: ", result);
163165
return UniqueCUvideodecoder(decoder, CUvideoDecoderDeleter{});
@@ -221,7 +223,8 @@ BetaCudaDeviceInterface::~BetaCudaDeviceInterface() {
221223
}
222224

223225
if (videoParser_) {
224-
cuvidDestroyVideoParser(videoParser_);
226+
const auto& nvcuvid = NVCUVIDLoader::instance().api();
227+
nvcuvid.cuvidDestroyVideoParser(videoParser_);
225228
videoParser_ = nullptr;
226229
}
227230

@@ -253,7 +256,11 @@ void BetaCudaDeviceInterface::initialize(
253256
parserParams.pfnDecodePicture = pfnDecodePictureCallback;
254257
parserParams.pfnDisplayPicture = pfnDisplayPictureCallback;
255258

256-
CUresult result = cuvidCreateVideoParser(&videoParser_, &parserParams);
259+
TORCH_CHECK(
260+
NVCUVIDLoader::instance().ensureLoaded(),
261+
"NVDEC runtime library (libnvcuvid) could not be loaded. Make sure the NVIDIA Video Codec SDK runtime is installed and libnvcuvid.so is present on your system.");
262+
const auto& nvcuvid = NVCUVIDLoader::instance().api();
263+
CUresult result = nvcuvid.cuvidCreateVideoParser(&videoParser_, &parserParams);
257264
TORCH_CHECK(
258265
result == CUDA_SUCCESS, "Failed to create video parser: ", result);
259266
}
@@ -415,7 +422,8 @@ int BetaCudaDeviceInterface::sendEOFPacket() {
415422

416423
int BetaCudaDeviceInterface::sendCuvidPacket(
417424
CUVIDSOURCEDATAPACKET& cuvidPacket) {
418-
CUresult result = cuvidParseVideoData(videoParser_, &cuvidPacket);
425+
const auto& nvcuvid = NVCUVIDLoader::instance().api();
426+
CUresult result = nvcuvid.cuvidParseVideoData(videoParser_, &cuvidPacket);
419427
return result == CUDA_SUCCESS ? AVSUCCESS : AVERROR_EXTERNAL;
420428
}
421429

@@ -453,7 +461,8 @@ int BetaCudaDeviceInterface::frameReadyForDecoding(CUVIDPICPARAMS* picParams) {
453461
TORCH_CHECK(picParams != nullptr, "Invalid picture parameters");
454462
TORCH_CHECK(decoder_, "Decoder not initialized before picture decode");
455463
// Send frame to be decoded by NVDEC - non-blocking call.
456-
CUresult result = cuvidDecodePicture(*decoder_.get(), picParams);
464+
const auto& nvcuvid = NVCUVIDLoader::instance().api();
465+
CUresult result = nvcuvid.cuvidDecodePicture(*decoder_.get(), picParams);
457466

458467
// Yes, you're reading that right, 0 means error, 1 means success
459468
return (result == CUDA_SUCCESS);
@@ -506,7 +515,8 @@ int BetaCudaDeviceInterface::receiveFrame(UniqueAVFrame& avFrame) {
506515
// SingleStreamDecoder. Either way, the underlying output surface can be
507516
// safely re-used.
508517
unmapPreviousFrame();
509-
CUresult result = cuvidMapVideoFrame(
518+
const auto& nvcuvid2 = NVCUVIDLoader::instance().api();
519+
CUresult result = nvcuvid2.cuvidMapVideoFrame(
510520
*decoder_.get(), dispInfo.picture_index, &framePtr, &pitch, &procParams);
511521
if (result != CUDA_SUCCESS) {
512522
return AVERROR_EXTERNAL;
@@ -523,7 +533,8 @@ void BetaCudaDeviceInterface::unmapPreviousFrame() {
523533
return;
524534
}
525535
CUresult result =
526-
cuvidUnmapVideoFrame(*decoder_.get(), previouslyMappedFrame_);
536+
NVCUVIDLoader::instance().api().cuvidUnmapVideoFrame(
537+
*decoder_.get(), previouslyMappedFrame_);
527538
TORCH_CHECK(
528539
result == CUDA_SUCCESS, "Failed to unmap previous frame: ", result);
529540
previouslyMappedFrame_ = 0;

src/torchcodec/_core/CMakeLists.txt

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ function(make_torchcodec_libraries
9999
)
100100

101101
if(ENABLE_CUDA)
102-
list(APPEND core_sources CudaDeviceInterface.cpp BetaCudaDeviceInterface.cpp NVDECCache.cpp CUDACommon.cpp)
102+
list(APPEND core_sources CudaDeviceInterface.cpp BetaCudaDeviceInterface.cpp NVDECCache.cpp CUDACommon.cpp NVCUVIDLoader.cpp)
103103
endif()
104104

105105
set(core_library_dependencies
@@ -108,28 +108,14 @@ function(make_torchcodec_libraries
108108
)
109109

110110
if(ENABLE_CUDA)
111-
# Try to find NVCUVID. Try the normal way first. This should work locally.
112-
find_library(NVCUVID_LIBRARY NAMES nvcuvid)
113-
# If not found, try with version suffix, or hardcoded path. Appears
114-
# to be necessary on the CI.
115-
if(NOT NVCUVID_LIBRARY)
116-
find_library(NVCUVID_LIBRARY NAMES nvcuvid.1 PATHS /usr/lib64 /usr/lib)
117-
endif()
118-
if(NOT NVCUVID_LIBRARY)
119-
set(NVCUVID_LIBRARY "/usr/lib64/libnvcuvid.so.1")
120-
endif()
121-
122-
if(NVCUVID_LIBRARY)
123-
message(STATUS "Found NVCUVID: ${NVCUVID_LIBRARY}")
124-
else()
125-
message(FATAL_ERROR "Could not find NVCUVID library")
126-
endif()
127-
128111
list(APPEND core_library_dependencies
129112
${CUDA_nppi_LIBRARY}
130113
${CUDA_nppicc_LIBRARY}
131-
${NVCUVID_LIBRARY}
132114
)
115+
# We link dl to load dynamically nvcuvid
116+
if(UNIX AND NOT APPLE)
117+
list(APPEND core_library_dependencies ${CMAKE_DL_LIBS})
118+
endif()
133119
endif()
134120

135121
make_torchcodec_sublibrary(
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) Meta Platforms, Inc. and affiliates.
2+
// All rights reserved.
3+
//
4+
// This source code is licensed under the BSD-style license found in the
5+
// LICENSE file in the root directory of this source tree.
6+
7+
#include "src/torchcodec/_core/NVCUVIDLoader.h"
8+
9+
#include <cstdio>
10+
11+
namespace facebook::torchcodec {
12+
13+
namespace {
14+
15+
#if defined(_WIN32)
16+
constexpr const wchar_t* kLibName = L"nvcuvid.dll";
17+
#else
18+
constexpr const char* kLibName = "libnvcuvid.so";
19+
#endif
20+
21+
template <typename T>
22+
inline bool ResolveSymbol(NVCUVIDLoader::LibHandle handle, const char* name, T*& out) {
23+
#if defined(_WIN32)
24+
FARPROC p = GetProcAddress(handle, name);
25+
out = reinterpret_cast<T*>(p);
26+
#else
27+
void* p = dlsym(handle, name);
28+
out = reinterpret_cast<T*>(p);
29+
#endif
30+
return out != nullptr;
31+
}
32+
33+
} // namespace
34+
35+
NVCUVIDLoader& NVCUVIDLoader::instance() {
36+
static NVCUVIDLoader loader;
37+
return loader;
38+
}
39+
40+
NVCUVIDLoader::~NVCUVIDLoader() {
41+
#if defined(_WIN32)
42+
if (handle_) {
43+
FreeLibrary(handle_);
44+
}
45+
#else
46+
if (handle_) {
47+
dlclose(handle_);
48+
}
49+
#endif
50+
}
51+
52+
bool NVCUVIDLoader::ensureLoaded() {
53+
if (loaded_) {
54+
return true;
55+
}
56+
if (!loadLibrary()) {
57+
return false;
58+
}
59+
loaded_ = resolveSymbols();
60+
return loaded_;
61+
}
62+
63+
const NVCUVIDLoader::API& NVCUVIDLoader::api() {
64+
if (!ensureLoaded()) {
65+
// Keep the error message concise; callers should convert this to a
66+
// TORCH_CHECK with more context.
67+
std::fputs("Failed to load libnvcuvid and resolve required symbols\n", stderr);
68+
}
69+
return api_;
70+
}
71+
72+
bool NVCUVIDLoader::loadLibrary() {
73+
#if defined(_WIN32)
74+
handle_ = LoadLibraryW(kLibName);
75+
#else
76+
handle_ = dlopen(kLibName, RTLD_NOW);
77+
if (!handle_) {
78+
// Fallback to common soname with version suffix used on some systems, as done by dali
79+
// https://github.com/NVIDIA/DALI/blob/a10cef187c0a5f27b6415df5d023c8057b9b43e2/dali/operators/video/dynlink_nvcuvid/dynlink_nvcuvid.cc#L35C18-L35C34
80+
handle_ = dlopen("libnvcuvid.so.1", RTLD_NOW);
81+
}
82+
#endif
83+
return handle_ != nullptr;
84+
}
85+
86+
bool NVCUVIDLoader::resolveSymbols() {
87+
bool ok = true;
88+
89+
ok &= ResolveSymbol(handle_, "cuvidCreateVideoParser", api_.cuvidCreateVideoParser);
90+
ok &= ResolveSymbol(handle_, "cuvidParseVideoData", api_.cuvidParseVideoData);
91+
ok &= ResolveSymbol(handle_, "cuvidDestroyVideoParser", api_.cuvidDestroyVideoParser);
92+
93+
ok &= ResolveSymbol(handle_, "cuvidGetDecoderCaps", api_.cuvidGetDecoderCaps);
94+
ok &= ResolveSymbol(handle_, "cuvidCreateDecoder", api_.cuvidCreateDecoder);
95+
ok &= ResolveSymbol(handle_, "cuvidDestroyDecoder", api_.cuvidDestroyDecoder);
96+
ok &= ResolveSymbol(handle_, "cuvidDecodePicture", api_.cuvidDecodePicture);
97+
98+
ok &= ResolveSymbol(handle_, "cuvidMapVideoFrame", api_.cuvidMapVideoFrame);
99+
ok &= ResolveSymbol(handle_, "cuvidUnmapVideoFrame", api_.cuvidUnmapVideoFrame);
100+
101+
return ok;
102+
}
103+
104+
} // namespace facebook::torchcodec
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright (c) Meta Platforms, Inc. and affiliates.
2+
// All rights reserved.
3+
//
4+
// This source code is licensed under the BSD-style license found in the
5+
// LICENSE file in the root directory of this source tree.
6+
7+
#pragma once
8+
9+
#include <cstddef>
10+
11+
#if defined(_WIN32)
12+
#include <Windows.h>
13+
#else
14+
#include <dlfcn.h>
15+
#endif
16+
17+
#include "src/torchcodec/_core/nvcuvid_include/cuviddec.h"
18+
#include "src/torchcodec/_core/nvcuvid_include/nvcuvid.h"
19+
20+
namespace facebook::torchcodec {
21+
22+
// Thin runtime loader for NVCUVID (NVDEC) symbols so we don't need to
23+
// hard-link against libnvcuvid. This follows NVIDIA's guidance for dynamic
24+
// loading.
25+
class NVCUVIDLoader {
26+
public:
27+
struct API {
28+
// Parser
29+
CUresult(CUDAAPI* cuvidCreateVideoParser)(
30+
CUvideoparser*, CUVIDPARSERPARAMS*);
31+
CUresult(CUDAAPI* cuvidParseVideoData)(
32+
CUvideoparser, CUVIDSOURCEDATAPACKET*);
33+
CUresult(CUDAAPI* cuvidDestroyVideoParser)(CUvideoparser);
34+
35+
// Decoder
36+
CUresult(CUDAAPI* cuvidGetDecoderCaps)(CUVIDDECODECAPS*);
37+
CUresult(CUDAAPI* cuvidCreateDecoder)(
38+
CUvideodecoder*, CUVIDDECODECREATEINFO*);
39+
CUresult(CUDAAPI* cuvidDestroyDecoder)(CUvideodecoder);
40+
CUresult(CUDAAPI* cuvidDecodePicture)(
41+
CUvideodecoder, CUVIDPICPARAMS*);
42+
43+
// Frame mapping
44+
CUresult(CUDAAPI* cuvidMapVideoFrame)(
45+
CUvideodecoder,
46+
int,
47+
CUdeviceptr*,
48+
unsigned int*,
49+
CUVIDPROCPARAMS*);
50+
CUresult(CUDAAPI* cuvidUnmapVideoFrame)(
51+
CUvideodecoder, unsigned int /* DevPtr */);
52+
};
53+
54+
// Singleton
55+
static NVCUVIDLoader& instance();
56+
57+
// Returns true if the library is loaded and required symbols resolved.
58+
bool ensureLoaded();
59+
60+
// Access resolved API. ensureLoaded() will be called implicitly; returns a
61+
// reference to a fully populated API or aborts if unavailable.
62+
const API& api();
63+
64+
private:
65+
NVCUVIDLoader() = default;
66+
~NVCUVIDLoader();
67+
NVCUVIDLoader(const NVCUVIDLoader&) = delete;
68+
NVCUVIDLoader& operator=(const NVCUVIDLoader&) = delete;
69+
70+
#if defined(_WIN32)
71+
using LibHandle = HMODULE;
72+
#else
73+
using LibHandle = void*;
74+
#endif
75+
76+
LibHandle handle_ = nullptr;
77+
bool loaded_ = false;
78+
API api_{};
79+
80+
bool loadLibrary();
81+
bool resolveSymbols();
82+
};
83+
84+
} // namespace facebook::torchcodec

src/torchcodec/_core/NVDECCache.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <torch/types.h>
1515
#include "src/torchcodec/_core/nvcuvid_include/cuviddec.h"
1616
#include "src/torchcodec/_core/nvcuvid_include/nvcuvid.h"
17+
#include "src/torchcodec/_core/NVCUVIDLoader.h"
1718

1819
namespace facebook::torchcodec {
1920

@@ -24,7 +25,8 @@ namespace facebook::torchcodec {
2425
struct CUvideoDecoderDeleter {
2526
void operator()(CUvideodecoder* decoderPtr) const {
2627
if (decoderPtr && *decoderPtr) {
27-
cuvidDestroyDecoder(*decoderPtr);
28+
// Destroy via dynamic loader to avoid hard dependency on libnvcuvid.
29+
NVCUVIDLoader::instance().api().cuvidDestroyDecoder(*decoderPtr);
2830
delete decoderPtr;
2931
}
3032
}

0 commit comments

Comments
 (0)