nvidia-codec 0.0.17__tar.gz → 0.0.19__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.17/src/nvidia_codec.egg-info → nvidia_codec-0.0.19}/PKG-INFO +1 -1
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/setup.cfg +1 -1
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/core/decode.py +83 -270
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/libavformat.py +39 -20
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/libavutil.py +13 -6
- nvidia_codec-0.0.19/src/nvidia_codec/utils/__init__.py +10 -0
- nvidia_codec-0.0.19/src/nvidia_codec/utils/decode.py +470 -0
- nvidia_codec-0.0.19/src/nvidia_codec/utils/demux.py +260 -0
- nvidia_codec-0.0.19/src/nvidia_codec/utils/player.py +42 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19/src/nvidia_codec.egg-info}/PKG-INFO +1 -1
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec.egg-info/SOURCES.txt +2 -0
- nvidia_codec-0.0.17/src/nvidia_codec/utils/__init__.py +0 -12
- nvidia_codec-0.0.17/src/nvidia_codec/utils/player.py +0 -849
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/LICENSE +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/README.md +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/pyproject.toml +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/__init__.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/common.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/core/__init__.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/core/common.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/core/cuda.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/core/cuviddec.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/core/nvcuvid.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/__init__.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/adapt.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/common.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/include/__init__.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/include/libavcodec.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/include/libavformat.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/include/libavutil.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/libavcodec.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/ffmpeg/test.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/utils/color.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec/utils/compat.py +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec.egg-info/dependency_links.txt +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/src/nvidia_codec.egg-info/requires.txt +0 -0
- {nvidia_codec-0.0.17 → nvidia_codec-0.0.19}/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,76 +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
130
|
def __init__(self, decoder, index, proc_params, pts):
|
|
189
|
-
"""Internal constructor. Pictures are created by the decoder."""
|
|
190
131
|
self.decoder = decoder
|
|
191
132
|
self.index = index
|
|
192
133
|
self.params = proc_params
|
|
193
134
|
self.pts = pts
|
|
194
|
-
with self.decoder.condition:
|
|
195
|
-
self.decoder.pictures_used.add(self.index)
|
|
196
135
|
|
|
197
136
|
def free(self):
|
|
198
|
-
"""
|
|
199
|
-
|
|
200
|
-
The underlying GPU memory remains allocated by the decoder and will be
|
|
201
|
-
reused for future frames. Called automatically on garbage collection.
|
|
202
|
-
"""
|
|
203
|
-
log.debug(f"freeing picture {self.index}")
|
|
204
|
-
|
|
205
|
-
with self.decoder.condition:
|
|
206
|
-
# discard instead of remove
|
|
207
|
-
# so this won't trigger exception
|
|
208
|
-
# which is possible
|
|
209
|
-
self.decoder.pictures_used.discard(self.index)
|
|
210
|
-
self.decoder.condition.notify_all()
|
|
211
|
-
|
|
212
|
-
def __del__(self):
|
|
213
|
-
self.free()
|
|
137
|
+
"""No-op. Slot ownership is managed by the caller (player.py)."""
|
|
138
|
+
pass
|
|
214
139
|
|
|
215
140
|
def map(self, stream: int = 0):
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
Applies cropping, scaling, and color format conversion as configured
|
|
219
|
-
in the decoder. The operation is queued on the specified CUDA stream.
|
|
220
|
-
|
|
221
|
-
Args:
|
|
222
|
-
stream: CUDA stream for the operation. Pass 0 or 2 for the
|
|
223
|
-
per-thread default stream, or a stream pointer/handle.
|
|
224
|
-
|
|
225
|
-
Returns:
|
|
226
|
-
Surface that can be accessed via __cuda_array_interface__.
|
|
227
|
-
"""
|
|
228
|
-
return Surface(self.decoder, self.index, self.params, stream)
|
|
141
|
+
return Surface(self.decoder, self.index, self.params, stream, self.pts)
|
|
229
142
|
|
|
230
143
|
|
|
231
144
|
def decide_surface_format(
|
|
232
145
|
chroma_format, bit_depth, supported_surface_formats, allow_high=False
|
|
233
146
|
):
|
|
234
|
-
"""Select the best surface format for the given video parameters.
|
|
235
|
-
|
|
236
|
-
Args:
|
|
237
|
-
chroma_format: Video chroma subsampling (YUV420, YUV444, etc.)
|
|
238
|
-
bit_depth: Bits per color channel (usually 8 or 10)
|
|
239
|
-
supported_surface_formats: Formats supported by this GPU/codec combination
|
|
240
|
-
allow_high: If True, use 16-bit output for >8-bit video. If False,
|
|
241
|
-
always use 8-bit output (lossy for HDR content).
|
|
242
|
-
|
|
243
|
-
Returns:
|
|
244
|
-
The selected cudaVideoSurfaceFormat.
|
|
245
|
-
|
|
246
|
-
Raises:
|
|
247
|
-
Exception: If the chroma format is unsupported or no compatible
|
|
248
|
-
surface format is available.
|
|
249
|
-
"""
|
|
147
|
+
"""Select the best surface format for the given video parameters."""
|
|
250
148
|
if chroma_format in [
|
|
251
149
|
cudaVideoChromaFormat.YUV420,
|
|
252
150
|
cudaVideoChromaFormat.MONOCHROME,
|
|
@@ -267,7 +165,6 @@ def decide_surface_format(
|
|
|
267
165
|
else:
|
|
268
166
|
raise Exception(f"unexpected chroma format {chroma_format}")
|
|
269
167
|
|
|
270
|
-
# check if the selected format is supported. If not, check fallback options
|
|
271
168
|
if f not in supported_surface_formats:
|
|
272
169
|
if cudaVideoSurfaceFormat.NV12 in supported_surface_formats:
|
|
273
170
|
f = cudaVideoSurfaceFormat.NV12
|
|
@@ -287,26 +184,19 @@ def decide_surface_format(
|
|
|
287
184
|
|
|
288
185
|
|
|
289
186
|
class BaseDecoder:
|
|
290
|
-
"""NVDEC hardware decoder
|
|
291
|
-
|
|
292
|
-
Decodes video packets using NVIDIA's CUVID API. For each decoded frame,
|
|
293
|
-
the provided on_recv callback is invoked with a Picture object. This
|
|
294
|
-
is the low-level decoder; most users should use Player
|
|
295
|
-
from nvidia_codec.utils instead.
|
|
187
|
+
"""NVDEC hardware decoder — thin wrapper around CUVID API.
|
|
296
188
|
|
|
297
|
-
|
|
298
|
-
|
|
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.
|
|
299
192
|
"""
|
|
300
193
|
|
|
301
194
|
def catch_exception(self, func, return_on_error=0):
|
|
302
|
-
"""Wrap callbacks to capture exceptions and return error codes."""
|
|
303
|
-
|
|
304
195
|
def wrapper(*args, **kwargs):
|
|
305
196
|
try:
|
|
306
197
|
return func(*args, **kwargs)
|
|
307
|
-
except BaseException as e:
|
|
198
|
+
except BaseException as e:
|
|
308
199
|
log.debug(f"callback exception logged {e}")
|
|
309
|
-
# we record the exception; to be checked when nvcuvid propagate the error return code
|
|
310
200
|
self.exception = e
|
|
311
201
|
log.debug(f"returning error code {return_on_error}")
|
|
312
202
|
return return_on_error
|
|
@@ -318,8 +208,6 @@ class BaseDecoder:
|
|
|
318
208
|
vf = pVideoFormat.contents
|
|
319
209
|
|
|
320
210
|
if self.cuvid_decoder:
|
|
321
|
-
# Only compare fields that affect decoder configuration
|
|
322
|
-
# frame_rate and other metadata can vary after seek within the same video
|
|
323
211
|
essential = [
|
|
324
212
|
"codec",
|
|
325
213
|
"coded_width",
|
|
@@ -340,7 +228,6 @@ class BaseDecoder:
|
|
|
340
228
|
f"decoder already initialized with different format: {diff}"
|
|
341
229
|
)
|
|
342
230
|
memmove(byref(self.video_format), byref(vf), sizeof(CUVIDEOFORMAT))
|
|
343
|
-
# save for user use
|
|
344
231
|
|
|
345
232
|
caps = CUVIDDECODECAPS(
|
|
346
233
|
eCodecType=vf.codec,
|
|
@@ -363,7 +250,7 @@ class BaseDecoder:
|
|
|
363
250
|
) <= caps.nMaxMBCount, "too many macroblocks"
|
|
364
251
|
|
|
365
252
|
supported_surface_formats = []
|
|
366
|
-
for surface_format in range(4):
|
|
253
|
+
for surface_format in range(4):
|
|
367
254
|
if caps.nOutputFormatMask & (1 << surface_format):
|
|
368
255
|
supported_surface_formats.append(cudaVideoSurfaceFormat(surface_format))
|
|
369
256
|
|
|
@@ -374,14 +261,13 @@ class BaseDecoder:
|
|
|
374
261
|
"min_num_pictures": vf.min_num_decode_surfaces,
|
|
375
262
|
}
|
|
376
263
|
|
|
377
|
-
# the default values
|
|
378
264
|
decision = {
|
|
379
|
-
"num_pictures": p["min_num_pictures"] + 1,
|
|
380
|
-
"num_surfaces": 1 + 1,
|
|
265
|
+
"num_pictures": p["min_num_pictures"] + 1,
|
|
266
|
+
"num_surfaces": 1 + 1,
|
|
381
267
|
"surface_format": decide_surface_format(
|
|
382
268
|
p["chroma_format"], p["bit_depth"], p["supported_surface_formats"]
|
|
383
269
|
),
|
|
384
|
-
"cropping": lambda height, width: {
|
|
270
|
+
"cropping": lambda height, width: {
|
|
385
271
|
"left": 0,
|
|
386
272
|
"right": width,
|
|
387
273
|
"top": 0,
|
|
@@ -390,8 +276,8 @@ class BaseDecoder:
|
|
|
390
276
|
"target_size": lambda cropped_height, cropped_width: (
|
|
391
277
|
cropped_height,
|
|
392
278
|
cropped_width,
|
|
393
|
-
),
|
|
394
|
-
"target_rect": lambda target_height, target_width: {
|
|
279
|
+
),
|
|
280
|
+
"target_rect": lambda target_height, target_width: {
|
|
395
281
|
"left": 0,
|
|
396
282
|
"right": target_width,
|
|
397
283
|
"top": 0,
|
|
@@ -414,7 +300,6 @@ class BaseDecoder:
|
|
|
414
300
|
), f"surface format {decision['surface_format']} not supported for codec {caps.eCodecType} chroma {caps.eChromaFormat} depth {caps.nBitDepthMinus8 + 8}"
|
|
415
301
|
assert decision["num_surfaces"] >= 0, "number of surfaces must be non-negative"
|
|
416
302
|
da = vf.display_area
|
|
417
|
-
# the provided cropping is offsetted
|
|
418
303
|
display_area = SRECT(
|
|
419
304
|
left=da.left + c["left"],
|
|
420
305
|
top=da.top + c["top"],
|
|
@@ -451,9 +336,6 @@ class BaseDecoder:
|
|
|
451
336
|
enableHistogram=0,
|
|
452
337
|
)
|
|
453
338
|
|
|
454
|
-
# for field_name, filed_type in p._fields_:
|
|
455
|
-
# print(field_name, getattr(p, field_name))
|
|
456
|
-
|
|
457
339
|
with cuda.Device(self.device):
|
|
458
340
|
cuda.check(
|
|
459
341
|
nvcuvid.cuvidCreateDecoder(
|
|
@@ -461,77 +343,64 @@ class BaseDecoder:
|
|
|
461
343
|
)
|
|
462
344
|
)
|
|
463
345
|
|
|
464
|
-
# this contains all picture indices that are still being used
|
|
465
|
-
# including the ones in above, and the ones passed to user via on_recv
|
|
466
|
-
self.pictures_used = set()
|
|
467
|
-
|
|
468
|
-
self.surfaces_sem = decision["num_surfaces"] # number of surfaces available
|
|
469
|
-
|
|
470
346
|
log.debug("sequence successful")
|
|
471
347
|
return decision["num_pictures"]
|
|
472
348
|
|
|
473
349
|
@property
|
|
474
350
|
def codec(self):
|
|
475
|
-
"""Video codec (cudaVideoCodec enum)."""
|
|
476
351
|
return self.decode_create_info.CodecType
|
|
477
352
|
|
|
478
353
|
@property
|
|
479
354
|
def height(self):
|
|
480
|
-
"""Original video height in pixels (before scaling)."""
|
|
481
355
|
return (
|
|
482
356
|
self.video_format.display_area.bottom - self.video_format.display_area.top
|
|
483
357
|
)
|
|
484
358
|
|
|
485
359
|
@property
|
|
486
360
|
def width(self):
|
|
487
|
-
"""Original video width in pixels (before scaling)."""
|
|
488
361
|
return (
|
|
489
362
|
self.video_format.display_area.right - self.video_format.display_area.left
|
|
490
363
|
)
|
|
491
364
|
|
|
492
365
|
@property
|
|
493
366
|
def target_width(self):
|
|
494
|
-
"""Output width in pixels (after scaling)."""
|
|
495
367
|
return self.decode_create_info.ulTargetWidth
|
|
496
368
|
|
|
497
369
|
@property
|
|
498
370
|
def target_height(self):
|
|
499
|
-
"""Output height in pixels (after scaling)."""
|
|
500
371
|
return self.decode_create_info.ulTargetHeight
|
|
501
372
|
|
|
502
373
|
@property
|
|
503
374
|
def surface_format(self):
|
|
504
|
-
"""Output surface format (NV12, P016, YUV444, etc.)."""
|
|
505
375
|
return self.decode_create_info.OutputFormat
|
|
506
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
|
+
|
|
507
387
|
def handlePictureDecode(self, pUserData, pPicParams):
|
|
508
388
|
log.debug("decode")
|
|
509
389
|
pp = pPicParams.contents
|
|
510
|
-
# assert self.cuvid_decoder, "decoder not initialized"
|
|
511
390
|
log.debug(f"decode picture index: {pp.CurrPicIdx}")
|
|
512
391
|
|
|
513
|
-
|
|
514
|
-
log.info(f"{self.pictures_used}")
|
|
515
|
-
with self.condition:
|
|
516
|
-
# wait until picture slot is available again
|
|
517
|
-
log.debug("wait_for_pictures started")
|
|
518
|
-
self.condition.wait_for(lambda: pp.CurrPicIdx not in self.pictures_used)
|
|
519
|
-
log.debug("wait_for_pictures finished")
|
|
392
|
+
self.pre_decode(pp.CurrPicIdx)
|
|
520
393
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
with cuda.Device(self.device):
|
|
524
|
-
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)))
|
|
525
396
|
|
|
526
397
|
return 1
|
|
527
398
|
|
|
528
399
|
def handlePictureDisplay(self, pUserData, pDispInfo):
|
|
529
400
|
log.debug("display")
|
|
530
401
|
if not bool(pDispInfo):
|
|
531
|
-
|
|
532
|
-
self.on_recv(None)
|
|
402
|
+
self.post_decode(None)
|
|
533
403
|
return 1
|
|
534
|
-
# di = cast(pDispInfo, POINTER(CUVIDPARSERDISPINFO)).contents
|
|
535
404
|
di = pDispInfo.contents
|
|
536
405
|
|
|
537
406
|
params = CUVIDPROCPARAMS(
|
|
@@ -542,15 +411,10 @@ class BaseDecoder:
|
|
|
542
411
|
)
|
|
543
412
|
|
|
544
413
|
picture = Picture(self, di.picture_index, params, di.timestamp)
|
|
545
|
-
|
|
546
|
-
self.on_recv(picture)
|
|
547
|
-
except:
|
|
548
|
-
picture.free()
|
|
549
|
-
raise
|
|
414
|
+
self.post_decode(picture)
|
|
550
415
|
return 1
|
|
551
416
|
|
|
552
417
|
def handleOperatingPoint(self, pUserData, pOPInfo):
|
|
553
|
-
"""Handle AV1 operating point selection (internal callback)."""
|
|
554
418
|
opi = pOPInfo.contents
|
|
555
419
|
if opi.codec == cudaVideoCodec.AV1:
|
|
556
420
|
if opi.av1.operating_points_cnt > 1:
|
|
@@ -568,40 +432,17 @@ class BaseDecoder:
|
|
|
568
432
|
coded_width=0,
|
|
569
433
|
coded_height=0,
|
|
570
434
|
):
|
|
571
|
-
"""Initialize the decoder.
|
|
572
|
-
|
|
573
|
-
Args:
|
|
574
|
-
codec: Video codec (e.g., cudaVideoCodec.H264, cudaVideoCodec.HEVC)
|
|
575
|
-
decide: Callback to configure decoder parameters. Called with a dict:
|
|
576
|
-
- 'chroma_format': cudaVideoChromaFormat (YUV420, YUV444, etc.)
|
|
577
|
-
- 'bit_depth': int (8 or 10)
|
|
578
|
-
- 'supported_surface_formats': list of cudaVideoSurfaceFormat
|
|
579
|
-
- 'min_num_pictures': int (minimum decode buffer size)
|
|
580
|
-
|
|
581
|
-
Return a dict with any of these optional overrides:
|
|
582
|
-
- 'num_pictures': Decode buffer size (default: min + 1, max: 32)
|
|
583
|
-
- 'num_surfaces': Output buffer size (default: 2)
|
|
584
|
-
- 'surface_format': Output format (default: auto-selected)
|
|
585
|
-
- 'cropping': Function (h, w) -> {'left', 'top', 'right', 'bottom'}
|
|
586
|
-
- 'target_size': Function (h, w) -> (new_h, new_w) for scaling
|
|
587
|
-
- 'target_rect': Function (h, w) -> {'left', 'top', 'right', 'bottom'}
|
|
588
|
-
|
|
589
|
-
device: CUDA device ID (default: current device)
|
|
590
|
-
extradata: Codec sequence header from container (required for VC1/WMV3)
|
|
591
|
-
coded_width: Video width hint for codecs needing extradata
|
|
592
|
-
coded_height: Video height hint for codecs needing extradata
|
|
593
|
-
"""
|
|
594
435
|
self.dirty = False
|
|
595
436
|
self.decide = decide
|
|
596
437
|
self.exception = None
|
|
597
|
-
self.cuvid_parser = CUvideoparser()
|
|
598
|
-
self.cuvid_decoder = CUvideodecoder()
|
|
438
|
+
self.cuvid_parser = CUvideoparser()
|
|
439
|
+
self.cuvid_decoder = CUvideodecoder()
|
|
440
|
+
|
|
599
441
|
|
|
600
442
|
self.operating_point = 0
|
|
601
443
|
self.disp_all_layers = False
|
|
602
|
-
self.condition = Condition()
|
|
603
444
|
|
|
604
|
-
self.video_format = CUVIDEOFORMAT()
|
|
445
|
+
self.video_format = CUVIDEOFORMAT()
|
|
605
446
|
|
|
606
447
|
self.handleVideoSequenceCallback = PFNVIDSEQUENCECALLBACK(
|
|
607
448
|
self.catch_exception(self.handleVideoSequence)
|
|
@@ -616,17 +457,14 @@ class BaseDecoder:
|
|
|
616
457
|
self.catch_exception(self.handleOperatingPoint, -1)
|
|
617
458
|
)
|
|
618
459
|
|
|
619
|
-
# Prepare extended video info with sequence header for codecs like VC1/WMV3
|
|
620
460
|
self.ext_video_info = None
|
|
621
461
|
pExtVideoInfo = None
|
|
622
462
|
if extradata is not None and len(extradata) > 0:
|
|
623
463
|
self.ext_video_info = CUVIDEOFORMATEX()
|
|
624
|
-
# Set format info from container (needed for VC1 Simple/Main which lacks in-band headers)
|
|
625
464
|
self.ext_video_info.format.codec = codec
|
|
626
465
|
self.ext_video_info.format.coded_width = coded_width
|
|
627
466
|
self.ext_video_info.format.coded_height = coded_height
|
|
628
|
-
self.ext_video_info.format.chroma_format = 1
|
|
629
|
-
# Copy extradata into raw_seqhdr_data (max 1024 bytes)
|
|
467
|
+
self.ext_video_info.format.chroma_format = 1
|
|
630
468
|
copy_len = min(len(extradata), 1024)
|
|
631
469
|
memmove(self.ext_video_info.raw_seqhdr_data, extradata, copy_len)
|
|
632
470
|
self.ext_video_info.format.seqhdr_data_length = copy_len
|
|
@@ -650,11 +488,8 @@ class BaseDecoder:
|
|
|
650
488
|
|
|
651
489
|
self.device = cuda.get_current_device(device)
|
|
652
490
|
|
|
653
|
-
# Early codec support check before creating parser
|
|
654
|
-
# (cuvidCreateVideoParser may succeed even for unsupported codecs,
|
|
655
|
-
# then cuvidParseVideoData fails with an unhelpful "unknown error")
|
|
656
491
|
caps = CUVIDDECODECAPS(
|
|
657
|
-
eCodecType=codec, eChromaFormat=1, nBitDepthMinus8=0
|
|
492
|
+
eCodecType=codec, eChromaFormat=1, nBitDepthMinus8=0
|
|
658
493
|
)
|
|
659
494
|
with cuda.Device(self.device):
|
|
660
495
|
cuda.check(nvcuvid.cuvidGetDecoderCaps(byref(caps)))
|
|
@@ -663,28 +498,19 @@ class BaseDecoder:
|
|
|
663
498
|
f"Codec not supported: {cudaVideoCodec(codec)}"
|
|
664
499
|
)
|
|
665
500
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
)
|
|
670
|
-
self.condition.notify_all()
|
|
501
|
+
cuda.check(
|
|
502
|
+
nvcuvid.cuvidCreateVideoParser(byref(self.cuvid_parser), byref(p))
|
|
503
|
+
)
|
|
671
504
|
|
|
672
|
-
def send(self, packet
|
|
505
|
+
def send(self, packet):
|
|
673
506
|
"""Send a compressed packet to the decoder.
|
|
674
507
|
|
|
675
|
-
|
|
676
|
-
|
|
508
|
+
Calls self.pre_decode(idx) before each decode and
|
|
509
|
+
self.post_decode(pic) for each displayed frame.
|
|
677
510
|
|
|
678
511
|
Args:
|
|
679
|
-
packet:
|
|
680
|
-
or None to signal end-of-stream (triggers EOS callback with picture=None).
|
|
681
|
-
on_recv: Callback function(picture) called for each decoded frame.
|
|
682
|
-
- picture: Picture object (with .pts field), or None at end of stream
|
|
683
|
-
|
|
684
|
-
Note:
|
|
685
|
-
The packet buffer can be reused immediately after send() returns.
|
|
512
|
+
packet: (pts, data) tuple or None for end-of-stream.
|
|
686
513
|
"""
|
|
687
|
-
|
|
688
514
|
if packet is None:
|
|
689
515
|
p = CUVIDSOURCEDATAPACKET(
|
|
690
516
|
flags=(
|
|
@@ -703,37 +529,24 @@ class BaseDecoder:
|
|
|
703
529
|
timestamp=pts,
|
|
704
530
|
)
|
|
705
531
|
self.exception = None
|
|
706
|
-
self.on_recv = on_recv
|
|
707
532
|
with cuda.Device(self.device):
|
|
708
533
|
try:
|
|
709
534
|
cuda.check(nvcuvid.cuvidParseVideoData(self.cuvid_parser, byref(p)))
|
|
710
535
|
except cuda.CUError:
|
|
711
|
-
# If a callback stored an exception, raise that instead of CUError
|
|
712
536
|
if self.exception is not None:
|
|
713
537
|
raise self.exception
|
|
714
538
|
raise
|
|
715
|
-
# catch: cuvidParseVideoData will not propagate error return code 0 of HandleVideoSequenceCallback
|
|
716
|
-
# it still returns CUDA_SUCCESS and simply ignore all future incoming packets
|
|
717
|
-
# therefore we must check the exception here, even if the last call succeeded
|
|
718
539
|
if self.exception is not None:
|
|
719
|
-
# the exception is caused by our callback, not by cuvid
|
|
720
540
|
raise self.exception
|
|
721
541
|
|
|
722
542
|
def free(self):
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
cuda.check(nvcuvid.cuvidDestroyVideoParser(self.cuvid_parser))
|
|
731
|
-
self.cuvid_parser = CUvideoparser()
|
|
732
|
-
if self.cuvid_decoder:
|
|
733
|
-
with cuda.Device(self.device):
|
|
734
|
-
cuda.check(nvcuvid.cuvidDestroyDecoder(self.cuvid_decoder))
|
|
735
|
-
self.cuvid_decoder = CUvideodecoder()
|
|
736
|
-
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()
|
|
737
550
|
|
|
738
551
|
def __del__(self):
|
|
739
552
|
self.free()
|