nvidia-codec 0.0.16__tar.gz → 0.0.18__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nvidia_codec-0.0.16/src/nvidia_codec.egg-info → nvidia_codec-0.0.18}/PKG-INFO +1 -1
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/setup.cfg +1 -1
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/core/decode.py +89 -279
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/libavformat.py +39 -20
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/libavutil.py +13 -6
- nvidia_codec-0.0.18/src/nvidia_codec/utils/__init__.py +10 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/utils/compat.py +21 -10
- nvidia_codec-0.0.18/src/nvidia_codec/utils/decode.py +467 -0
- nvidia_codec-0.0.18/src/nvidia_codec/utils/demux.py +260 -0
- nvidia_codec-0.0.18/src/nvidia_codec/utils/player.py +42 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18/src/nvidia_codec.egg-info}/PKG-INFO +1 -1
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec.egg-info/SOURCES.txt +2 -0
- nvidia_codec-0.0.16/src/nvidia_codec/utils/__init__.py +0 -12
- nvidia_codec-0.0.16/src/nvidia_codec/utils/player.py +0 -762
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/LICENSE +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/README.md +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/pyproject.toml +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/__init__.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/common.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/core/__init__.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/core/common.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/core/cuda.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/core/cuviddec.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/core/nvcuvid.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/__init__.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/adapt.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/common.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/include/__init__.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/include/libavcodec.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/include/libavformat.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/include/libavutil.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/libavcodec.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/ffmpeg/test.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec/utils/color.py +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec.egg-info/dependency_links.txt +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec.egg-info/requires.txt +0 -0
- {nvidia_codec-0.0.16 → nvidia_codec-0.0.18}/src/nvidia_codec.egg-info/top_level.txt +0 -0
|
@@ -1,46 +1,22 @@
|
|
|
1
|
-
"""Low-level NVDEC decoder
|
|
2
|
-
|
|
3
|
-
This module provides the core decoding interface
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
3. Call Picture.map() to get a Surface (applies cropping/scaling)
|
|
13
|
-
4. Access Surface data via __cuda_array_interface__ (works with PyTorch, CuPy)
|
|
14
|
-
5. Call Surface.free() and Picture.free() when done (or let them be garbage collected)
|
|
15
|
-
|
|
16
|
-
Memory Management:
|
|
17
|
-
- Pictures and Surfaces are backed by pre-allocated GPU memory pools
|
|
18
|
-
- `num_pictures` controls the decode buffer size (max 32)
|
|
19
|
-
- `num_surfaces` controls the output buffer size (no hard limit)
|
|
20
|
-
- Calling free() marks the slot as reusable, but memory stays allocated
|
|
21
|
-
- The decoder owns all memory until BaseDecoder.free() is called
|
|
22
|
-
|
|
23
|
-
Example:
|
|
24
|
-
decoder = BaseDecoder(cudaVideoCodec.H264, decide=my_decide_callback)
|
|
25
|
-
def on_recv(picture, pts, result):
|
|
26
|
-
if picture is None: # End of stream
|
|
27
|
-
return result
|
|
28
|
-
surface = picture.map(cuda_stream)
|
|
29
|
-
tensor = torch.as_tensor(surface, device='cuda')
|
|
30
|
-
picture.free()
|
|
31
|
-
surface.free()
|
|
32
|
-
return tensor
|
|
33
|
-
result = decoder.send(packet_data, on_recv, pts=12345)
|
|
1
|
+
"""Low-level NVDEC decoder — thin wrapper around CUVID API.
|
|
2
|
+
|
|
3
|
+
This module provides the core decoding interface. It does NOT manage
|
|
4
|
+
threading or slot ownership; that is the caller's responsibility
|
|
5
|
+
(see nvidia_codec.utils.player for the higher-level interface).
|
|
6
|
+
|
|
7
|
+
Callbacks:
|
|
8
|
+
pre_decode(pic_idx): Called before cuvidDecodePicture. The caller
|
|
9
|
+
should block here if the picture slot is in use.
|
|
10
|
+
post_decode(pic): Called when a decoded frame is ready for display
|
|
11
|
+
(the "display callback"). pic is a Picture or None (EOS).
|
|
34
12
|
"""
|
|
35
13
|
|
|
36
14
|
from . import cuda
|
|
37
15
|
from .nvcuvid import *
|
|
38
16
|
from .common import *
|
|
39
17
|
from .. import CodecNotSupportedError
|
|
40
|
-
from queue import Empty, Queue
|
|
41
18
|
import numpy as np
|
|
42
19
|
from ctypes import *
|
|
43
|
-
from threading import Condition, Semaphore
|
|
44
20
|
|
|
45
21
|
import logging
|
|
46
22
|
|
|
@@ -52,87 +28,57 @@ nvcuvid = cdll.LoadLibrary("libnvcuvid.so")
|
|
|
52
28
|
class Surface:
|
|
53
29
|
"""Post-processed decoded frame accessible as a CUDA array.
|
|
54
30
|
|
|
55
|
-
A Surface contains the final decoded frame data after cropping and scaling.
|
|
56
|
-
It implements __cuda_array_interface__ for zero-copy access from PyTorch,
|
|
57
|
-
CuPy, and other CUDA-aware libraries.
|
|
58
|
-
|
|
59
31
|
Do not instantiate directly; use Picture.map() instead.
|
|
60
|
-
|
|
61
|
-
Attributes:
|
|
62
|
-
format: Surface pixel format (NV12, P016, YUV444, etc.)
|
|
63
|
-
width: Frame width in pixels
|
|
64
|
-
height: Frame height in pixels
|
|
65
|
-
shape: Array shape for __cuda_array_interface__
|
|
66
32
|
"""
|
|
67
33
|
|
|
68
|
-
def __init__(self, decoder, index, params, stream):
|
|
69
|
-
"""Internal constructor. Use Picture.map() instead."""
|
|
70
|
-
|
|
34
|
+
def __init__(self, decoder, index, params, stream, pts):
|
|
71
35
|
self.decoder = decoder
|
|
36
|
+
self.pts = pts
|
|
72
37
|
|
|
73
38
|
self.params = CUVIDPROCPARAMS()
|
|
74
39
|
memmove(byref(self.params), byref(params), sizeof(CUVIDEOFORMAT))
|
|
75
40
|
|
|
76
41
|
self.params.output_stream = stream
|
|
77
42
|
|
|
78
|
-
self.c_devptr = (
|
|
79
|
-
c_ulonglong()
|
|
80
|
-
) # according to cuviddec, the argument type of cuvidmapvideoframe64
|
|
43
|
+
self.c_devptr = c_ulonglong()
|
|
81
44
|
self.c_pitch = c_uint()
|
|
82
45
|
|
|
83
|
-
with self.decoder.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
byref(self.c_pitch),
|
|
92
|
-
byref(self.params),
|
|
93
|
-
)
|
|
46
|
+
with cuda.Device(self.decoder.device):
|
|
47
|
+
cuda.check(
|
|
48
|
+
nvcuvid.cuvidMapVideoFrame64(
|
|
49
|
+
self.decoder.cuvid_decoder,
|
|
50
|
+
c_int(index),
|
|
51
|
+
byref(self.c_devptr),
|
|
52
|
+
byref(self.c_pitch),
|
|
53
|
+
byref(self.params),
|
|
94
54
|
)
|
|
95
|
-
|
|
96
|
-
self.decoder.surfaces_sem -= 1
|
|
97
|
-
self.decoder.condition.notify_all()
|
|
55
|
+
)
|
|
98
56
|
|
|
99
57
|
@property
|
|
100
58
|
def format(self) -> cudaVideoSurfaceFormat:
|
|
101
|
-
"""Surface pixel format (NV12, P016, YUV444, or YUV444_16Bit)."""
|
|
102
59
|
return self.decoder.surface_format
|
|
103
60
|
|
|
104
61
|
@property
|
|
105
62
|
def height(self):
|
|
106
|
-
"""Frame height in pixels."""
|
|
107
63
|
return self.decoder.target_height
|
|
108
64
|
|
|
109
65
|
@property
|
|
110
66
|
def width(self):
|
|
111
|
-
"""Frame width in pixels."""
|
|
112
67
|
return self.decoder.target_width
|
|
113
68
|
|
|
114
69
|
@property
|
|
115
70
|
def size(self):
|
|
116
|
-
"""Frame dimensions as {'width': int, 'height': int}."""
|
|
117
71
|
return {"width": self.width, "height": self.height}
|
|
118
72
|
|
|
119
73
|
def free(self):
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
with self.decoder.condition:
|
|
126
|
-
if self.c_devptr and self.decoder.cuvid_decoder:
|
|
127
|
-
with cuda.Device(self.decoder.device):
|
|
128
|
-
cuda.check(
|
|
129
|
-
nvcuvid.cuvidUnmapVideoFrame64(
|
|
130
|
-
self.decoder.cuvid_decoder, self.c_devptr
|
|
131
|
-
)
|
|
74
|
+
if self.c_devptr and self.decoder.cuvid_decoder:
|
|
75
|
+
with cuda.Device(self.decoder.device):
|
|
76
|
+
cuda.check(
|
|
77
|
+
nvcuvid.cuvidUnmapVideoFrame64(
|
|
78
|
+
self.decoder.cuvid_decoder, self.c_devptr
|
|
132
79
|
)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
self.decoder.condition.notify_all()
|
|
80
|
+
)
|
|
81
|
+
self.c_devptr = c_ulonglong()
|
|
136
82
|
|
|
137
83
|
def __del__(self):
|
|
138
84
|
self.free()
|
|
@@ -169,7 +115,7 @@ class Surface:
|
|
|
169
115
|
"typestr": typestr,
|
|
170
116
|
"strides": strides,
|
|
171
117
|
"version": 3,
|
|
172
|
-
"data": (self.c_devptr.value, False),
|
|
118
|
+
"data": (self.c_devptr.value, False),
|
|
173
119
|
"stream": self.params.output_stream,
|
|
174
120
|
}
|
|
175
121
|
|
|
@@ -177,75 +123,28 @@ class Surface:
|
|
|
177
123
|
class Picture:
|
|
178
124
|
"""Decoded frame before post-processing.
|
|
179
125
|
|
|
180
|
-
A
|
|
181
|
-
|
|
182
|
-
a Surface that can be read.
|
|
183
|
-
|
|
184
|
-
Do not instantiate directly; Pictures are created by the decoder and
|
|
185
|
-
passed to your on_recv callback.
|
|
126
|
+
A data object — does not own the picture slot. Slot ownership is
|
|
127
|
+
managed by the caller via pre_decode/pic_release callbacks.
|
|
186
128
|
"""
|
|
187
129
|
|
|
188
|
-
def __init__(self, decoder, index, proc_params):
|
|
189
|
-
"""Internal constructor. Pictures are created by the decoder."""
|
|
130
|
+
def __init__(self, decoder, index, proc_params, pts):
|
|
190
131
|
self.decoder = decoder
|
|
191
132
|
self.index = index
|
|
192
133
|
self.params = proc_params
|
|
193
|
-
|
|
194
|
-
self.decoder.pictures_used.add(self.index)
|
|
134
|
+
self.pts = pts
|
|
195
135
|
|
|
196
136
|
def free(self):
|
|
197
|
-
"""
|
|
198
|
-
|
|
199
|
-
The underlying GPU memory remains allocated by the decoder and will be
|
|
200
|
-
reused for future frames. Called automatically on garbage collection.
|
|
201
|
-
"""
|
|
202
|
-
log.debug(f"freeing picture {self.index}")
|
|
203
|
-
|
|
204
|
-
with self.decoder.condition:
|
|
205
|
-
# discard instead of remove
|
|
206
|
-
# so this won't trigger exception
|
|
207
|
-
# which is possible
|
|
208
|
-
self.decoder.pictures_used.discard(self.index)
|
|
209
|
-
self.decoder.condition.notify_all()
|
|
210
|
-
|
|
211
|
-
def __del__(self):
|
|
212
|
-
self.free()
|
|
137
|
+
"""No-op. Slot ownership is managed by the caller (player.py)."""
|
|
138
|
+
pass
|
|
213
139
|
|
|
214
140
|
def map(self, stream: int = 0):
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
Applies cropping, scaling, and color format conversion as configured
|
|
218
|
-
in the decoder. The operation is queued on the specified CUDA stream.
|
|
219
|
-
|
|
220
|
-
Args:
|
|
221
|
-
stream: CUDA stream for the operation. Pass 0 or 2 for the
|
|
222
|
-
per-thread default stream, or a stream pointer/handle.
|
|
223
|
-
|
|
224
|
-
Returns:
|
|
225
|
-
Surface that can be accessed via __cuda_array_interface__.
|
|
226
|
-
"""
|
|
227
|
-
return Surface(self.decoder, self.index, self.params, stream)
|
|
141
|
+
return Surface(self.decoder, self.index, self.params, stream, self.pts)
|
|
228
142
|
|
|
229
143
|
|
|
230
144
|
def decide_surface_format(
|
|
231
145
|
chroma_format, bit_depth, supported_surface_formats, allow_high=False
|
|
232
146
|
):
|
|
233
|
-
"""Select the best surface format for the given video parameters.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
chroma_format: Video chroma subsampling (YUV420, YUV444, etc.)
|
|
237
|
-
bit_depth: Bits per color channel (usually 8 or 10)
|
|
238
|
-
supported_surface_formats: Formats supported by this GPU/codec combination
|
|
239
|
-
allow_high: If True, use 16-bit output for >8-bit video. If False,
|
|
240
|
-
always use 8-bit output (lossy for HDR content).
|
|
241
|
-
|
|
242
|
-
Returns:
|
|
243
|
-
The selected cudaVideoSurfaceFormat.
|
|
244
|
-
|
|
245
|
-
Raises:
|
|
246
|
-
Exception: If the chroma format is unsupported or no compatible
|
|
247
|
-
surface format is available.
|
|
248
|
-
"""
|
|
147
|
+
"""Select the best surface format for the given video parameters."""
|
|
249
148
|
if chroma_format in [
|
|
250
149
|
cudaVideoChromaFormat.YUV420,
|
|
251
150
|
cudaVideoChromaFormat.MONOCHROME,
|
|
@@ -266,7 +165,6 @@ def decide_surface_format(
|
|
|
266
165
|
else:
|
|
267
166
|
raise Exception(f"unexpected chroma format {chroma_format}")
|
|
268
167
|
|
|
269
|
-
# check if the selected format is supported. If not, check fallback options
|
|
270
168
|
if f not in supported_surface_formats:
|
|
271
169
|
if cudaVideoSurfaceFormat.NV12 in supported_surface_formats:
|
|
272
170
|
f = cudaVideoSurfaceFormat.NV12
|
|
@@ -286,26 +184,19 @@ def decide_surface_format(
|
|
|
286
184
|
|
|
287
185
|
|
|
288
186
|
class BaseDecoder:
|
|
289
|
-
"""NVDEC hardware decoder
|
|
290
|
-
|
|
291
|
-
Decodes video packets using NVIDIA's CUVID API. For each decoded frame,
|
|
292
|
-
the provided on_recv callback is invoked with a Picture object. This
|
|
293
|
-
is the low-level decoder; most users should use Player
|
|
294
|
-
from nvidia_codec.utils instead.
|
|
187
|
+
"""NVDEC hardware decoder — thin wrapper around CUVID API.
|
|
295
188
|
|
|
296
|
-
|
|
297
|
-
|
|
189
|
+
Threading and slot ownership are NOT managed here. The caller provides
|
|
190
|
+
callbacks (pre_decode, pic_release, surface_acquire, surface_release)
|
|
191
|
+
to handle synchronization.
|
|
298
192
|
"""
|
|
299
193
|
|
|
300
194
|
def catch_exception(self, func, return_on_error=0):
|
|
301
|
-
"""Wrap callbacks to capture exceptions and return error codes."""
|
|
302
|
-
|
|
303
195
|
def wrapper(*args, **kwargs):
|
|
304
196
|
try:
|
|
305
197
|
return func(*args, **kwargs)
|
|
306
|
-
except BaseException as e:
|
|
198
|
+
except BaseException as e:
|
|
307
199
|
log.debug(f"callback exception logged {e}")
|
|
308
|
-
# we record the exception; to be checked when nvcuvid propagate the error return code
|
|
309
200
|
self.exception = e
|
|
310
201
|
log.debug(f"returning error code {return_on_error}")
|
|
311
202
|
return return_on_error
|
|
@@ -317,8 +208,6 @@ class BaseDecoder:
|
|
|
317
208
|
vf = pVideoFormat.contents
|
|
318
209
|
|
|
319
210
|
if self.cuvid_decoder:
|
|
320
|
-
# Only compare fields that affect decoder configuration
|
|
321
|
-
# frame_rate and other metadata can vary after seek within the same video
|
|
322
211
|
essential = [
|
|
323
212
|
"codec",
|
|
324
213
|
"coded_width",
|
|
@@ -339,7 +228,6 @@ class BaseDecoder:
|
|
|
339
228
|
f"decoder already initialized with different format: {diff}"
|
|
340
229
|
)
|
|
341
230
|
memmove(byref(self.video_format), byref(vf), sizeof(CUVIDEOFORMAT))
|
|
342
|
-
# save for user use
|
|
343
231
|
|
|
344
232
|
caps = CUVIDDECODECAPS(
|
|
345
233
|
eCodecType=vf.codec,
|
|
@@ -362,7 +250,7 @@ class BaseDecoder:
|
|
|
362
250
|
) <= caps.nMaxMBCount, "too many macroblocks"
|
|
363
251
|
|
|
364
252
|
supported_surface_formats = []
|
|
365
|
-
for surface_format in range(4):
|
|
253
|
+
for surface_format in range(4):
|
|
366
254
|
if caps.nOutputFormatMask & (1 << surface_format):
|
|
367
255
|
supported_surface_formats.append(cudaVideoSurfaceFormat(surface_format))
|
|
368
256
|
|
|
@@ -373,14 +261,13 @@ class BaseDecoder:
|
|
|
373
261
|
"min_num_pictures": vf.min_num_decode_surfaces,
|
|
374
262
|
}
|
|
375
263
|
|
|
376
|
-
# the default values
|
|
377
264
|
decision = {
|
|
378
|
-
"num_pictures": p["min_num_pictures"] + 1,
|
|
379
|
-
"num_surfaces": 1 + 1,
|
|
265
|
+
"num_pictures": p["min_num_pictures"] + 1,
|
|
266
|
+
"num_surfaces": 1 + 1,
|
|
380
267
|
"surface_format": decide_surface_format(
|
|
381
268
|
p["chroma_format"], p["bit_depth"], p["supported_surface_formats"]
|
|
382
269
|
),
|
|
383
|
-
"cropping": lambda height, width: {
|
|
270
|
+
"cropping": lambda height, width: {
|
|
384
271
|
"left": 0,
|
|
385
272
|
"right": width,
|
|
386
273
|
"top": 0,
|
|
@@ -389,8 +276,8 @@ class BaseDecoder:
|
|
|
389
276
|
"target_size": lambda cropped_height, cropped_width: (
|
|
390
277
|
cropped_height,
|
|
391
278
|
cropped_width,
|
|
392
|
-
),
|
|
393
|
-
"target_rect": lambda target_height, target_width: {
|
|
279
|
+
),
|
|
280
|
+
"target_rect": lambda target_height, target_width: {
|
|
394
281
|
"left": 0,
|
|
395
282
|
"right": target_width,
|
|
396
283
|
"top": 0,
|
|
@@ -413,7 +300,6 @@ class BaseDecoder:
|
|
|
413
300
|
), f"surface format {decision['surface_format']} not supported for codec {caps.eCodecType} chroma {caps.eChromaFormat} depth {caps.nBitDepthMinus8 + 8}"
|
|
414
301
|
assert decision["num_surfaces"] >= 0, "number of surfaces must be non-negative"
|
|
415
302
|
da = vf.display_area
|
|
416
|
-
# the provided cropping is offsetted
|
|
417
303
|
display_area = SRECT(
|
|
418
304
|
left=da.left + c["left"],
|
|
419
305
|
top=da.top + c["top"],
|
|
@@ -450,9 +336,6 @@ class BaseDecoder:
|
|
|
450
336
|
enableHistogram=0,
|
|
451
337
|
)
|
|
452
338
|
|
|
453
|
-
# for field_name, filed_type in p._fields_:
|
|
454
|
-
# print(field_name, getattr(p, field_name))
|
|
455
|
-
|
|
456
339
|
with cuda.Device(self.device):
|
|
457
340
|
cuda.check(
|
|
458
341
|
nvcuvid.cuvidCreateDecoder(
|
|
@@ -460,77 +343,64 @@ class BaseDecoder:
|
|
|
460
343
|
)
|
|
461
344
|
)
|
|
462
345
|
|
|
463
|
-
# this contains all picture indices that are still being used
|
|
464
|
-
# including the ones in above, and the ones passed to user via on_recv
|
|
465
|
-
self.pictures_used = set()
|
|
466
|
-
|
|
467
|
-
self.surfaces_sem = decision["num_surfaces"] # number of surfaces available
|
|
468
|
-
|
|
469
346
|
log.debug("sequence successful")
|
|
470
347
|
return decision["num_pictures"]
|
|
471
348
|
|
|
472
349
|
@property
|
|
473
350
|
def codec(self):
|
|
474
|
-
"""Video codec (cudaVideoCodec enum)."""
|
|
475
351
|
return self.decode_create_info.CodecType
|
|
476
352
|
|
|
477
353
|
@property
|
|
478
354
|
def height(self):
|
|
479
|
-
"""Original video height in pixels (before scaling)."""
|
|
480
355
|
return (
|
|
481
356
|
self.video_format.display_area.bottom - self.video_format.display_area.top
|
|
482
357
|
)
|
|
483
358
|
|
|
484
359
|
@property
|
|
485
360
|
def width(self):
|
|
486
|
-
"""Original video width in pixels (before scaling)."""
|
|
487
361
|
return (
|
|
488
362
|
self.video_format.display_area.right - self.video_format.display_area.left
|
|
489
363
|
)
|
|
490
364
|
|
|
491
365
|
@property
|
|
492
366
|
def target_width(self):
|
|
493
|
-
"""Output width in pixels (after scaling)."""
|
|
494
367
|
return self.decode_create_info.ulTargetWidth
|
|
495
368
|
|
|
496
369
|
@property
|
|
497
370
|
def target_height(self):
|
|
498
|
-
"""Output height in pixels (after scaling)."""
|
|
499
371
|
return self.decode_create_info.ulTargetHeight
|
|
500
372
|
|
|
501
373
|
@property
|
|
502
374
|
def surface_format(self):
|
|
503
|
-
"""Output surface format (NV12, P016, YUV444, etc.)."""
|
|
504
375
|
return self.decode_create_info.OutputFormat
|
|
505
376
|
|
|
377
|
+
def pre_decode(self, idx):
|
|
378
|
+
"""NVDEC is reclaiming picture slot idx. After this returns,
|
|
379
|
+
slot ownership is transferred back to NVDEC."""
|
|
380
|
+
raise NotImplementedError
|
|
381
|
+
|
|
382
|
+
def post_decode(self, pic):
|
|
383
|
+
"""NVDEC is releasing picture slot for display.
|
|
384
|
+
pic is a Picture or None (EOS)."""
|
|
385
|
+
raise NotImplementedError
|
|
386
|
+
|
|
506
387
|
def handlePictureDecode(self, pUserData, pPicParams):
|
|
507
388
|
log.debug("decode")
|
|
508
389
|
pp = pPicParams.contents
|
|
509
|
-
# assert self.cuvid_decoder, "decoder not initialized"
|
|
510
390
|
log.debug(f"decode picture index: {pp.CurrPicIdx}")
|
|
511
391
|
|
|
512
|
-
|
|
513
|
-
log.info(f"{self.pictures_used}")
|
|
514
|
-
with self.condition:
|
|
515
|
-
# wait until picture slot is available again
|
|
516
|
-
log.debug("wait_for_pictures started")
|
|
517
|
-
self.condition.wait_for(lambda: pp.CurrPicIdx not in self.pictures_used)
|
|
518
|
-
log.debug("wait_for_pictures finished")
|
|
392
|
+
self.pre_decode(pp.CurrPicIdx)
|
|
519
393
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
with cuda.Device(self.device):
|
|
523
|
-
cuda.check(nvcuvid.cuvidDecodePicture(self.cuvid_decoder, byref(pp)))
|
|
394
|
+
with cuda.Device(self.device):
|
|
395
|
+
cuda.check(nvcuvid.cuvidDecodePicture(self.cuvid_decoder, byref(pp)))
|
|
524
396
|
|
|
525
397
|
return 1
|
|
526
398
|
|
|
527
399
|
def handlePictureDisplay(self, pUserData, pDispInfo):
|
|
528
400
|
log.debug("display")
|
|
529
401
|
if not bool(pDispInfo):
|
|
530
|
-
|
|
531
|
-
self.on_recv(None, 0)
|
|
402
|
+
self.post_decode(None)
|
|
532
403
|
return 1
|
|
533
|
-
# di = cast(pDispInfo, POINTER(CUVIDPARSERDISPINFO)).contents
|
|
534
404
|
di = pDispInfo.contents
|
|
535
405
|
|
|
536
406
|
params = CUVIDPROCPARAMS(
|
|
@@ -540,16 +410,11 @@ class BaseDecoder:
|
|
|
540
410
|
unpaired_field=di.repeat_first_field < 0,
|
|
541
411
|
)
|
|
542
412
|
|
|
543
|
-
picture = Picture(self, di.picture_index, params)
|
|
544
|
-
|
|
545
|
-
self.on_recv(picture, di.timestamp)
|
|
546
|
-
except:
|
|
547
|
-
picture.free()
|
|
548
|
-
raise
|
|
413
|
+
picture = Picture(self, di.picture_index, params, di.timestamp)
|
|
414
|
+
self.post_decode(picture)
|
|
549
415
|
return 1
|
|
550
416
|
|
|
551
417
|
def handleOperatingPoint(self, pUserData, pOPInfo):
|
|
552
|
-
"""Handle AV1 operating point selection (internal callback)."""
|
|
553
418
|
opi = pOPInfo.contents
|
|
554
419
|
if opi.codec == cudaVideoCodec.AV1:
|
|
555
420
|
if opi.av1.operating_points_cnt > 1:
|
|
@@ -567,40 +432,17 @@ class BaseDecoder:
|
|
|
567
432
|
coded_width=0,
|
|
568
433
|
coded_height=0,
|
|
569
434
|
):
|
|
570
|
-
"""Initialize the decoder.
|
|
571
|
-
|
|
572
|
-
Args:
|
|
573
|
-
codec: Video codec (e.g., cudaVideoCodec.H264, cudaVideoCodec.HEVC)
|
|
574
|
-
decide: Callback to configure decoder parameters. Called with a dict:
|
|
575
|
-
- 'chroma_format': cudaVideoChromaFormat (YUV420, YUV444, etc.)
|
|
576
|
-
- 'bit_depth': int (8 or 10)
|
|
577
|
-
- 'supported_surface_formats': list of cudaVideoSurfaceFormat
|
|
578
|
-
- 'min_num_pictures': int (minimum decode buffer size)
|
|
579
|
-
|
|
580
|
-
Return a dict with any of these optional overrides:
|
|
581
|
-
- 'num_pictures': Decode buffer size (default: min + 1, max: 32)
|
|
582
|
-
- 'num_surfaces': Output buffer size (default: 2)
|
|
583
|
-
- 'surface_format': Output format (default: auto-selected)
|
|
584
|
-
- 'cropping': Function (h, w) -> {'left', 'top', 'right', 'bottom'}
|
|
585
|
-
- 'target_size': Function (h, w) -> (new_h, new_w) for scaling
|
|
586
|
-
- 'target_rect': Function (h, w) -> {'left', 'top', 'right', 'bottom'}
|
|
587
|
-
|
|
588
|
-
device: CUDA device ID (default: current device)
|
|
589
|
-
extradata: Codec sequence header from container (required for VC1/WMV3)
|
|
590
|
-
coded_width: Video width hint for codecs needing extradata
|
|
591
|
-
coded_height: Video height hint for codecs needing extradata
|
|
592
|
-
"""
|
|
593
435
|
self.dirty = False
|
|
594
436
|
self.decide = decide
|
|
595
437
|
self.exception = None
|
|
596
|
-
self.cuvid_parser = CUvideoparser()
|
|
597
|
-
self.cuvid_decoder = CUvideodecoder()
|
|
438
|
+
self.cuvid_parser = CUvideoparser()
|
|
439
|
+
self.cuvid_decoder = CUvideodecoder()
|
|
440
|
+
|
|
598
441
|
|
|
599
442
|
self.operating_point = 0
|
|
600
443
|
self.disp_all_layers = False
|
|
601
|
-
self.condition = Condition()
|
|
602
444
|
|
|
603
|
-
self.video_format = CUVIDEOFORMAT()
|
|
445
|
+
self.video_format = CUVIDEOFORMAT()
|
|
604
446
|
|
|
605
447
|
self.handleVideoSequenceCallback = PFNVIDSEQUENCECALLBACK(
|
|
606
448
|
self.catch_exception(self.handleVideoSequence)
|
|
@@ -615,17 +457,14 @@ class BaseDecoder:
|
|
|
615
457
|
self.catch_exception(self.handleOperatingPoint, -1)
|
|
616
458
|
)
|
|
617
459
|
|
|
618
|
-
# Prepare extended video info with sequence header for codecs like VC1/WMV3
|
|
619
460
|
self.ext_video_info = None
|
|
620
461
|
pExtVideoInfo = None
|
|
621
462
|
if extradata is not None and len(extradata) > 0:
|
|
622
463
|
self.ext_video_info = CUVIDEOFORMATEX()
|
|
623
|
-
# Set format info from container (needed for VC1 Simple/Main which lacks in-band headers)
|
|
624
464
|
self.ext_video_info.format.codec = codec
|
|
625
465
|
self.ext_video_info.format.coded_width = coded_width
|
|
626
466
|
self.ext_video_info.format.coded_height = coded_height
|
|
627
|
-
self.ext_video_info.format.chroma_format = 1
|
|
628
|
-
# Copy extradata into raw_seqhdr_data (max 1024 bytes)
|
|
467
|
+
self.ext_video_info.format.chroma_format = 1
|
|
629
468
|
copy_len = min(len(extradata), 1024)
|
|
630
469
|
memmove(self.ext_video_info.raw_seqhdr_data, extradata, copy_len)
|
|
631
470
|
self.ext_video_info.format.seqhdr_data_length = copy_len
|
|
@@ -649,11 +488,8 @@ class BaseDecoder:
|
|
|
649
488
|
|
|
650
489
|
self.device = cuda.get_current_device(device)
|
|
651
490
|
|
|
652
|
-
# Early codec support check before creating parser
|
|
653
|
-
# (cuvidCreateVideoParser may succeed even for unsupported codecs,
|
|
654
|
-
# then cuvidParseVideoData fails with an unhelpful "unknown error")
|
|
655
491
|
caps = CUVIDDECODECAPS(
|
|
656
|
-
eCodecType=codec, eChromaFormat=1, nBitDepthMinus8=0
|
|
492
|
+
eCodecType=codec, eChromaFormat=1, nBitDepthMinus8=0
|
|
657
493
|
)
|
|
658
494
|
with cuda.Device(self.device):
|
|
659
495
|
cuda.check(nvcuvid.cuvidGetDecoderCaps(byref(caps)))
|
|
@@ -662,34 +498,20 @@ class BaseDecoder:
|
|
|
662
498
|
f"Codec not supported: {cudaVideoCodec(codec)}"
|
|
663
499
|
)
|
|
664
500
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
)
|
|
669
|
-
self.condition.notify_all()
|
|
501
|
+
cuda.check(
|
|
502
|
+
nvcuvid.cuvidCreateVideoParser(byref(self.cuvid_parser), byref(p))
|
|
503
|
+
)
|
|
670
504
|
|
|
671
|
-
def send(self, packet
|
|
505
|
+
def send(self, packet):
|
|
672
506
|
"""Send a compressed packet to the decoder.
|
|
673
507
|
|
|
674
|
-
|
|
675
|
-
|
|
508
|
+
Calls self.pre_decode(idx) before each decode and
|
|
509
|
+
self.post_decode(pic) for each displayed frame.
|
|
676
510
|
|
|
677
511
|
Args:
|
|
678
|
-
packet:
|
|
679
|
-
signal end-of-stream (triggers EOS callback with picture=None).
|
|
680
|
-
on_recv: Callback function(picture, pts) called for each decoded frame.
|
|
681
|
-
- picture: Picture object, or None at end of stream
|
|
682
|
-
- pts: Presentation timestamp passed to send()
|
|
683
|
-
pts: Presentation timestamp for this packet (passed to on_recv).
|
|
684
|
-
|
|
685
|
-
Note:
|
|
686
|
-
The packet buffer can be reused immediately after send() returns.
|
|
687
|
-
Call flush() to drain any remaining frames from the reorder buffer.
|
|
512
|
+
packet: (pts, data) tuple or None for end-of-stream.
|
|
688
513
|
"""
|
|
689
|
-
|
|
690
514
|
if packet is None:
|
|
691
|
-
# this will reset the parser internal state
|
|
692
|
-
# also triggers dummy display callback
|
|
693
515
|
p = CUVIDSOURCEDATAPACKET(
|
|
694
516
|
flags=(
|
|
695
517
|
CUvideopacketflags.ENDOFSTREAM | CUvideopacketflags.NOTIFY_EOS
|
|
@@ -699,44 +521,32 @@ class BaseDecoder:
|
|
|
699
521
|
timestamp=0,
|
|
700
522
|
)
|
|
701
523
|
else:
|
|
524
|
+
pts, data = packet
|
|
702
525
|
p = CUVIDSOURCEDATAPACKET(
|
|
703
526
|
flags=CUvideopacketflags.TIMESTAMP.value,
|
|
704
|
-
payload_size=
|
|
705
|
-
payload=
|
|
527
|
+
payload_size=data.shape[0],
|
|
528
|
+
payload=data.ctypes.data_as(POINTER(c_uint8)),
|
|
706
529
|
timestamp=pts,
|
|
707
530
|
)
|
|
708
531
|
self.exception = None
|
|
709
|
-
self.on_recv = on_recv
|
|
710
532
|
with cuda.Device(self.device):
|
|
711
533
|
try:
|
|
712
534
|
cuda.check(nvcuvid.cuvidParseVideoData(self.cuvid_parser, byref(p)))
|
|
713
535
|
except cuda.CUError:
|
|
714
|
-
# If a callback stored an exception, raise that instead of CUError
|
|
715
536
|
if self.exception is not None:
|
|
716
537
|
raise self.exception
|
|
717
538
|
raise
|
|
718
|
-
# catch: cuvidParseVideoData will not propagate error return code 0 of HandleVideoSequenceCallback
|
|
719
|
-
# it still returns CUDA_SUCCESS and simply ignore all future incoming packets
|
|
720
|
-
# therefore we must check the exception here, even if the last call succeeded
|
|
721
539
|
if self.exception is not None:
|
|
722
|
-
# the exception is caused by our callback, not by cuvid
|
|
723
540
|
raise self.exception
|
|
724
541
|
|
|
725
542
|
def free(self):
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
cuda.check(nvcuvid.cuvidDestroyVideoParser(self.cuvid_parser))
|
|
734
|
-
self.cuvid_parser = CUvideoparser()
|
|
735
|
-
if self.cuvid_decoder:
|
|
736
|
-
with cuda.Device(self.device):
|
|
737
|
-
cuda.check(nvcuvid.cuvidDestroyDecoder(self.cuvid_decoder))
|
|
738
|
-
self.cuvid_decoder = CUvideodecoder()
|
|
739
|
-
self.condition.notify_all()
|
|
543
|
+
if getattr(self, 'cuvid_parser', None):
|
|
544
|
+
cuda.check(nvcuvid.cuvidDestroyVideoParser(self.cuvid_parser))
|
|
545
|
+
self.cuvid_parser = CUvideoparser()
|
|
546
|
+
if getattr(self, 'cuvid_decoder', None):
|
|
547
|
+
with cuda.Device(self.device):
|
|
548
|
+
cuda.check(nvcuvid.cuvidDestroyDecoder(self.cuvid_decoder))
|
|
549
|
+
self.cuvid_decoder = CUvideodecoder()
|
|
740
550
|
|
|
741
551
|
def __del__(self):
|
|
742
552
|
self.free()
|