PyNvVideoCodec 2.2.1__cp312-cp312-win_arm64.whl
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.
- PyNvVideoCodec/PyNvVideoCodec_121.cp312-win_arm64.pyd +0 -0
- PyNvVideoCodec/PyNvVideoCodec_130.cp312-win_arm64.pyd +0 -0
- PyNvVideoCodec/VersionCheck.cp312-win_arm64.pyd +0 -0
- PyNvVideoCodec/__init__.py +251 -0
- PyNvVideoCodec/avformat-62.dll +0 -0
- PyNvVideoCodec/avutil-60.dll +0 -0
- PyNvVideoCodec/decoders/SimpleDecoder.py +273 -0
- PyNvVideoCodec/decoders/ThreadedDecoder.py +129 -0
- PyNvVideoCodec/decoders/__init__.py +22 -0
- PyNvVideoCodec/transcoder/Transcoder.py +55 -0
- PyNvVideoCodec/transcoder/__init__.py +22 -0
- benchmarks/cached_decoder_benchmark.py +813 -0
- benchmarks/frame_sampling_benchmark.py +765 -0
- benchmarks/requirements.txt +52 -0
- benchmarks/segmented_transcode_benchmark.py +1546 -0
- benchmarks/setup_pycuda_win_arm64.bat +531 -0
- pynvvideocodec-2.2.1.data/data/external/ffmpeg/readme.txt +39 -0
- pynvvideocodec-2.2.1.data/data/external/ffmpeg/src/ffmpeg-8.1.2.tar.xz +0 -0
- pynvvideocodec-2.2.1.dist-info/METADATA +97 -0
- pynvvideocodec-2.2.1.dist-info/RECORD +63 -0
- pynvvideocodec-2.2.1.dist-info/WHEEL +5 -0
- pynvvideocodec-2.2.1.dist-info/licenses/LICENSE.txt +22 -0
- pynvvideocodec-2.2.1.dist-info/licenses/NOTICES.txt +167 -0
- pynvvideocodec-2.2.1.dist-info/top_level.txt +3 -0
- samples/advanced/decode.py +173 -0
- samples/advanced/decode_from_memory_buffer.py +204 -0
- samples/advanced/decode_perf.py +299 -0
- samples/advanced/decode_reconfigure.py +184 -0
- samples/advanced/decode_sei_msg.py +193 -0
- samples/advanced/decode_with_cuda_control.py +214 -0
- samples/advanced/decode_with_low_latency.py +183 -0
- samples/advanced/encode_config.json +12 -0
- samples/advanced/encode_config_lowlatency.json +13 -0
- samples/advanced/encode_perf.py +338 -0
- samples/advanced/encode_reconfigure.py +211 -0
- samples/advanced/encode_sei_msg.py +188 -0
- samples/advanced/simple_decode_from_memory.py +151 -0
- samples/advanced/simple_decode_stats.py +179 -0
- samples/advanced/transcode.py +288 -0
- samples/basic/create_video_segments.py +187 -0
- samples/basic/encode.py +574 -0
- samples/basic/segments.txt +3 -0
- samples/basic/simple_decode_sampling.py +182 -0
- samples/basic/transcode_config.json +6 -0
- samples/jupyter/ReconfigureDecoder.png +0 -0
- samples/jupyter/SimpleDecoderAPIs.png +0 -0
- samples/jupyter/ThreadedDecoder.png +0 -0
- samples/jupyter/coco_classes.pickle +0 -0
- samples/jupyter/face_protect_tutorial.ipynb +881 -0
- samples/jupyter/object_detection_tutorial.ipynb +781 -0
- samples/jupyter/simple_decode_tutorial.ipynb +674 -0
- samples/requirements.txt +84 -0
- samples/setup_pycuda_win_arm64.bat +531 -0
- samples/utils/Utils.py +405 -0
- samples/utils/__init__.py +36 -0
- samples/utils/decode_parser.py +642 -0
- samples/utils/decode_stats_writer.py +177 -0
- samples/utils/encode_parallel_utils.py +174 -0
- samples/utils/encode_parser.py +379 -0
- samples/utils/frame_utils.py +89 -0
- samples/utils/sei_message_parser.py +223 -0
- samples/utils/sei_parser.py +223 -0
- samples/utils/transcode_parser.py +218 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
'''
|
|
25
|
+
This sample demonstrates how to measure video decoding performance by utilizing multiple NVDEC engines in parallel.
|
|
26
|
+
|
|
27
|
+
It shows how to:
|
|
28
|
+
1. Maximize GPU utilization using multiple decoder instances
|
|
29
|
+
2. Measure decoding performance (FPS) accurately for different modes
|
|
30
|
+
3. Scale decoding across available NVDEC engines
|
|
31
|
+
4. Use either threading or multiprocessing for parallelization
|
|
32
|
+
5. Track and report performance metrics
|
|
33
|
+
|
|
34
|
+
The sample implements two parallel execution modes:
|
|
35
|
+
Thread Mode:
|
|
36
|
+
- Better for: Higher performance (lower overhead), shared memory access (decoder instances can share the same GPU memory)
|
|
37
|
+
- Ideal when: Running on same GPU, memory constraints exist
|
|
38
|
+
- Benefits: Faster initialization, lower memory footprint, cuda context is shared between decoder instances
|
|
39
|
+
- Note: Python GIL is not a bottleneck as decoder operations are GIL-free
|
|
40
|
+
|
|
41
|
+
Process Mode:
|
|
42
|
+
- Better for: Complete isolation between decoder instances (each decoder instance has its own GPU memory)
|
|
43
|
+
- Ideal when: Running on multiple GPUs, need memory isolation
|
|
44
|
+
- Benefits: Better fault isolation, independent memory management
|
|
45
|
+
- Tradeoff: Higher memory usage, longer initialization time, cuda context is not shared between decoder instances
|
|
46
|
+
|
|
47
|
+
Usage: python decode_perf.py -i <input_file> -n <num_instances> -m <mode>
|
|
48
|
+
Example: python decode_perf.py -i input.mp4 -n 4 -m thread
|
|
49
|
+
|
|
50
|
+
Arguments:
|
|
51
|
+
-i: Input video file
|
|
52
|
+
-n: Number of parallel instances (default: 1)
|
|
53
|
+
-m: Execution mode: 'thread' or 'process' (default: thread)
|
|
54
|
+
-d: Use device memory (1) or host memory (0) (default: 1)
|
|
55
|
+
-g: GPU ID to use (default: 0)
|
|
56
|
+
-f: Number of frames to decode per instance (optional)
|
|
57
|
+
|
|
58
|
+
Note: This performance testing app does not write output files.
|
|
59
|
+
It only measures decoding throughput.
|
|
60
|
+
'''
|
|
61
|
+
|
|
62
|
+
import sys
|
|
63
|
+
import os
|
|
64
|
+
import time
|
|
65
|
+
import threading
|
|
66
|
+
import multiprocessing
|
|
67
|
+
from pathlib import Path
|
|
68
|
+
import PyNvVideoCodec as nvc
|
|
69
|
+
from os.path import join, dirname, abspath
|
|
70
|
+
|
|
71
|
+
# Set multiprocessing start method at the very beginning
|
|
72
|
+
# 'spawn' is required for CUDA operations and works consistently across platforms
|
|
73
|
+
if __name__ == '__main__':
|
|
74
|
+
multiprocessing.set_start_method('spawn')
|
|
75
|
+
|
|
76
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
77
|
+
current_dir = dirname(abspath(__file__))
|
|
78
|
+
parent_dir = dirname(current_dir)
|
|
79
|
+
if parent_dir not in sys.path:
|
|
80
|
+
sys.path.append(parent_dir)
|
|
81
|
+
|
|
82
|
+
# Import utilities for decoding
|
|
83
|
+
from utils.decode_parser import parse_multi_instance_args
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Global variables for tracking performance metrics in threading mode
|
|
87
|
+
total_fps = 0
|
|
88
|
+
num_frames = 0
|
|
89
|
+
perf_lock = threading.Lock()
|
|
90
|
+
|
|
91
|
+
def decode_thread(gpu_id, enc_file_path, frame_count=None):
|
|
92
|
+
"""
|
|
93
|
+
Function to decode media file and measure performance in a thread.
|
|
94
|
+
|
|
95
|
+
Parameters:
|
|
96
|
+
gpu_id (int): Ordinal of GPU to use
|
|
97
|
+
enc_file_path (str): Path to file to be decoded
|
|
98
|
+
frame_count (int, optional): Maximum number of frames to decode
|
|
99
|
+
"""
|
|
100
|
+
global total_fps, num_frames
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Normalize frame count
|
|
104
|
+
decode_all = frame_count is None or frame_count <= 0
|
|
105
|
+
if decode_all:
|
|
106
|
+
print(f"Thread {threading.current_thread().name}: Decoding all available frames")
|
|
107
|
+
frame_count = None
|
|
108
|
+
|
|
109
|
+
nv_dmx = nvc.CreateDemuxer(filename=enc_file_path)
|
|
110
|
+
nv_dec = nvc.CreateDecoder(
|
|
111
|
+
gpuid=gpu_id,
|
|
112
|
+
codec=nv_dmx.GetNvCodecId(),
|
|
113
|
+
usedevicememory=True,
|
|
114
|
+
bWaitForSessionWarmUp=True
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
start = time.perf_counter()
|
|
118
|
+
num_decoded_frames = 0
|
|
119
|
+
|
|
120
|
+
for packet in nv_dmx:
|
|
121
|
+
for _ in nv_dec.Decode(packet):
|
|
122
|
+
num_decoded_frames += 1
|
|
123
|
+
if frame_count is not None and num_decoded_frames >= frame_count:
|
|
124
|
+
print(f"Thread {threading.current_thread().name}: Successfully decoded requested {frame_count} frames")
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
if frame_count is not None and num_decoded_frames >= frame_count:
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
# If we get here, we've decoded all available frames
|
|
131
|
+
if frame_count is not None and num_decoded_frames < frame_count:
|
|
132
|
+
print(f"Thread {threading.current_thread().name}: Warning: Video ended before reaching requested frame count. Requested: {frame_count}, Decoded: {num_decoded_frames}")
|
|
133
|
+
else:
|
|
134
|
+
print(f"Thread {threading.current_thread().name}: Successfully decoded all {num_decoded_frames} frames")
|
|
135
|
+
|
|
136
|
+
# Calculate accurate elapsed time by subtracting initialization time
|
|
137
|
+
elapsed_time = time.perf_counter() - start
|
|
138
|
+
init_time = nv_dec.GetSessionInitTime() / 1000.0 # Convert to seconds
|
|
139
|
+
elapsed_time -= init_time
|
|
140
|
+
|
|
141
|
+
fps = num_decoded_frames / elapsed_time
|
|
142
|
+
print(f"Thread {threading.current_thread().name}: {num_decoded_frames} frames in {elapsed_time:.2f}s ({fps:.2f} FPS)")
|
|
143
|
+
|
|
144
|
+
# Update performance metrics with thread-safe operation
|
|
145
|
+
with perf_lock:
|
|
146
|
+
global total_fps, num_frames
|
|
147
|
+
total_fps += fps
|
|
148
|
+
num_frames += num_decoded_frames
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print(f"Thread {threading.current_thread().name} error: {e}")
|
|
152
|
+
|
|
153
|
+
def decode_process(gpu_id, enc_file_path, frame_count=None, result_queue=None):
|
|
154
|
+
"""
|
|
155
|
+
Function to decode media file and measure performance in a separate process.
|
|
156
|
+
|
|
157
|
+
Parameters:
|
|
158
|
+
gpu_id (int): Ordinal of GPU to use
|
|
159
|
+
enc_file_path (str): Path to file to be decoded
|
|
160
|
+
frame_count (int, optional): Maximum number of frames to decode
|
|
161
|
+
result_queue (Queue): Queue to store performance results
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
# Normalize frame count
|
|
165
|
+
decode_all = frame_count is None or frame_count <= 0
|
|
166
|
+
if decode_all:
|
|
167
|
+
print(f"Process {multiprocessing.current_process().name}: Decoding all available frames")
|
|
168
|
+
frame_count = None
|
|
169
|
+
|
|
170
|
+
nv_dmx = nvc.CreateDemuxer(filename=enc_file_path)
|
|
171
|
+
nv_dec = nvc.CreateDecoder(
|
|
172
|
+
gpuid=gpu_id,
|
|
173
|
+
codec=nv_dmx.GetNvCodecId(),
|
|
174
|
+
usedevicememory=True,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
start = time.perf_counter()
|
|
178
|
+
num_decoded_frames = 0
|
|
179
|
+
|
|
180
|
+
for packet in nv_dmx:
|
|
181
|
+
for _ in nv_dec.Decode(packet):
|
|
182
|
+
num_decoded_frames += 1
|
|
183
|
+
if frame_count is not None and num_decoded_frames >= frame_count:
|
|
184
|
+
print(f"Process {multiprocessing.current_process().name}: Successfully decoded requested {frame_count} frames")
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
if frame_count is not None and num_decoded_frames >= frame_count:
|
|
188
|
+
break
|
|
189
|
+
|
|
190
|
+
# If we get here, we've decoded all available frames
|
|
191
|
+
if frame_count is not None and num_decoded_frames < frame_count:
|
|
192
|
+
print(f"Process {multiprocessing.current_process().name}: Warning: Video ended before reaching requested frame count. Requested: {frame_count}, Decoded: {num_decoded_frames}")
|
|
193
|
+
else:
|
|
194
|
+
print(f"Process {multiprocessing.current_process().name}: Successfully decoded all {num_decoded_frames} frames")
|
|
195
|
+
|
|
196
|
+
elapsed_time = time.perf_counter() - start
|
|
197
|
+
fps = num_decoded_frames / elapsed_time
|
|
198
|
+
print(f"Process {multiprocessing.current_process().name}: {num_decoded_frames} frames in {elapsed_time:.2f}s ({fps:.2f} FPS)")
|
|
199
|
+
|
|
200
|
+
# Send results back through queue
|
|
201
|
+
if result_queue:
|
|
202
|
+
result_queue.put((fps, num_decoded_frames))
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
print(f"Process {multiprocessing.current_process().name} error: {e}")
|
|
206
|
+
if result_queue:
|
|
207
|
+
result_queue.put((0, 0)) # Send zero metrics on error
|
|
208
|
+
|
|
209
|
+
def run_parallel_decode(mode, num_instances, gpu_id, enc_file_path, frame_count=None):
|
|
210
|
+
"""
|
|
211
|
+
Run parallel video decoding using either threads or processes.
|
|
212
|
+
|
|
213
|
+
Parameters:
|
|
214
|
+
mode (str): 'thread' or 'process' to specify parallel execution mode
|
|
215
|
+
num_instances (int): Number of parallel decode instances to run
|
|
216
|
+
gpu_id (int): Ordinal of GPU to use
|
|
217
|
+
enc_file_path (str): Path to file to be decoded
|
|
218
|
+
frame_count (int, optional): Maximum number of frames to decode per instance
|
|
219
|
+
"""
|
|
220
|
+
global total_fps, num_frames
|
|
221
|
+
total_fps = 0
|
|
222
|
+
num_frames = 0
|
|
223
|
+
|
|
224
|
+
start_time = time.perf_counter()
|
|
225
|
+
|
|
226
|
+
if mode == 'thread':
|
|
227
|
+
# Threading mode
|
|
228
|
+
threads = []
|
|
229
|
+
nvc.PyNvDecoder.SetSessionCount(num_instances)
|
|
230
|
+
|
|
231
|
+
for i in range(num_instances):
|
|
232
|
+
t = threading.Thread(
|
|
233
|
+
target=decode_thread,
|
|
234
|
+
args=(gpu_id, enc_file_path, frame_count)
|
|
235
|
+
)
|
|
236
|
+
print(f"Starting decode thread {i+1}/{num_instances}")
|
|
237
|
+
t.start()
|
|
238
|
+
threads.append(t)
|
|
239
|
+
|
|
240
|
+
for t in threads:
|
|
241
|
+
t.join()
|
|
242
|
+
|
|
243
|
+
# Performance metrics are collected via global variables
|
|
244
|
+
|
|
245
|
+
else: # mode == 'process'
|
|
246
|
+
# Multiprocessing mode
|
|
247
|
+
processes = []
|
|
248
|
+
result_queue = multiprocessing.Queue()
|
|
249
|
+
|
|
250
|
+
for i in range(num_instances):
|
|
251
|
+
p = multiprocessing.Process(
|
|
252
|
+
target=decode_process,
|
|
253
|
+
args=(gpu_id, enc_file_path, frame_count, result_queue)
|
|
254
|
+
)
|
|
255
|
+
print(f"Starting decode process {i+1}/{num_instances}")
|
|
256
|
+
p.start()
|
|
257
|
+
processes.append(p)
|
|
258
|
+
|
|
259
|
+
for p in processes:
|
|
260
|
+
p.join()
|
|
261
|
+
|
|
262
|
+
# Collect results from queue
|
|
263
|
+
while not result_queue.empty():
|
|
264
|
+
fps, frames = result_queue.get()
|
|
265
|
+
total_fps += fps
|
|
266
|
+
num_frames += frames
|
|
267
|
+
|
|
268
|
+
total_time = time.perf_counter() - start_time
|
|
269
|
+
|
|
270
|
+
# Print performance summary
|
|
271
|
+
print("\n=== Performance Summary ===")
|
|
272
|
+
print(f"Mode: {mode.capitalize()}")
|
|
273
|
+
print(f"Number of instances: {num_instances}")
|
|
274
|
+
print(f"Total frames decoded: {num_frames}")
|
|
275
|
+
print(f"Total FPS: {total_fps:.2f}")
|
|
276
|
+
print(f"Average FPS per instance: {total_fps/num_instances:.2f}")
|
|
277
|
+
print(f"Total wall time: {total_time:.2f}s")
|
|
278
|
+
print("========================\n")
|
|
279
|
+
|
|
280
|
+
if __name__ == "__main__":
|
|
281
|
+
|
|
282
|
+
args = parse_multi_instance_args()
|
|
283
|
+
|
|
284
|
+
print("=" * 60)
|
|
285
|
+
print("Configuration:")
|
|
286
|
+
print(f"Input: {args.encoded_file_path}")
|
|
287
|
+
print(f"GPU ID: {args.gpu_id}")
|
|
288
|
+
print(f"Mode: {args.mode.upper()}")
|
|
289
|
+
print(f"Number of Instances: {args.num_instances}")
|
|
290
|
+
print(f"Frame Count: {args.frame_count if args.frame_count else 'All frames'}")
|
|
291
|
+
print("=" * 60)
|
|
292
|
+
|
|
293
|
+
run_parallel_decode(
|
|
294
|
+
mode=args.mode,
|
|
295
|
+
num_instances=args.num_instances,
|
|
296
|
+
gpu_id=args.gpu_id,
|
|
297
|
+
enc_file_path=args.encoded_file_path,
|
|
298
|
+
frame_count=args.frame_count
|
|
299
|
+
)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
'''
|
|
25
|
+
This sample demonstrates dynamic decoder reconfiguration for handling resolution changes.
|
|
26
|
+
It shows how to:
|
|
27
|
+
1. Create a decoder that can handle multiple resolutions
|
|
28
|
+
2. Switch between input streams with different dimensions
|
|
29
|
+
3. Reconfigure decoder parameters on the fly
|
|
30
|
+
4. Handle decoder state during reconfiguration
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Usage: python decode_reconfigure.py -i1 <input1> -i2 <input2> -o1 <output1> -o2 <output2>
|
|
34
|
+
Example: python decode_reconfigure.py -i1 video1.mp4 -i2 video2.mp4
|
|
35
|
+
|
|
36
|
+
Arguments:
|
|
37
|
+
-i1: First input video file
|
|
38
|
+
-i2: Second input video file (different resolution)
|
|
39
|
+
-o1: First output raw video file (default: <input1_name>.yuv)
|
|
40
|
+
-o2: Second output raw video file (default: <input2_name>.yuv)
|
|
41
|
+
-d: Use device memory (1) or host memory (0) (default: 1)
|
|
42
|
+
-g: GPU ID to use (default: 0)
|
|
43
|
+
-f: Number of frames to decode per stream (optional)
|
|
44
|
+
'''
|
|
45
|
+
|
|
46
|
+
import sys
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
import PyNvVideoCodec as nvc
|
|
49
|
+
from os.path import join, dirname, abspath, splitext
|
|
50
|
+
|
|
51
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
52
|
+
current_dir = dirname(abspath(__file__))
|
|
53
|
+
parent_dir = dirname(current_dir)
|
|
54
|
+
if parent_dir not in sys.path:
|
|
55
|
+
sys.path.append(parent_dir)
|
|
56
|
+
|
|
57
|
+
from utils.decode_parser import parse_reconfigure_args
|
|
58
|
+
from utils.Utils import get_frame_data
|
|
59
|
+
|
|
60
|
+
def decode_stream(nv_dec, demuxer, output_file, use_device_memory, frame_count=None):
|
|
61
|
+
"""
|
|
62
|
+
Helper function to decode a single stream and write frames to output file.
|
|
63
|
+
|
|
64
|
+
Parameters:
|
|
65
|
+
nv_dec: NvDecoder instance
|
|
66
|
+
demuxer: Demuxer instance for the input stream
|
|
67
|
+
output_file: File object to write decoded frames to
|
|
68
|
+
use_device_memory (int): If set to 1, output decoded frame is CUDeviceptr wrapped in CUDA Array Interface
|
|
69
|
+
frame_count (int, optional): Maximum number of frames to decode. If None, 0, or negative, decode all frames.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
int: Number of frames decoded
|
|
73
|
+
"""
|
|
74
|
+
frames_decoded = 0
|
|
75
|
+
for packet in demuxer:
|
|
76
|
+
for decoded_frame in nv_dec.Decode(packet):
|
|
77
|
+
frame_data = get_frame_data(decoded_frame, use_device_memory)
|
|
78
|
+
output_file.write(bytearray(frame_data))
|
|
79
|
+
frames_decoded += 1
|
|
80
|
+
if frame_count is not None and frame_count > 0 and frames_decoded >= frame_count:
|
|
81
|
+
break
|
|
82
|
+
if frame_count is not None and frame_count > 0 and frames_decoded >= frame_count:
|
|
83
|
+
break
|
|
84
|
+
return frames_decoded
|
|
85
|
+
|
|
86
|
+
def decode_with_reconfigure(gpu_id, enc_file_path1, enc_file_path2, dec_file_path1, dec_file_path2, use_device_memory, frame_count=None):
|
|
87
|
+
"""
|
|
88
|
+
Function to decode media files with dynamic decoder reconfiguration.
|
|
89
|
+
Demonstrates switching between two different input streams with different dimensions.
|
|
90
|
+
|
|
91
|
+
Parameters:
|
|
92
|
+
gpu_id (int): Ordinal of GPU to use
|
|
93
|
+
enc_file_path1 (str): Path to first input encoded file
|
|
94
|
+
enc_file_path2 (str): Path to second input encoded file with different dimensions
|
|
95
|
+
dec_file_path1 (str): Path to first output file into which raw frames are stored
|
|
96
|
+
dec_file_path2 (str): Path to second output file into which raw frames are stored
|
|
97
|
+
use_device_memory (int): If set to 1, output decoded frame is CUDeviceptr wrapped in CUDA Array Interface
|
|
98
|
+
frame_count (int, optional): Maximum number of frames to decode per stream. If None, 0, or negative, decode all frames.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
# Create first demuxer and get stream info
|
|
102
|
+
nv_dmx1 = nvc.CreateDemuxer(filename=enc_file_path1)
|
|
103
|
+
stream1_codec = nv_dmx1.GetNvCodecId()
|
|
104
|
+
stream1_width = nv_dmx1.Width()
|
|
105
|
+
stream1_height = nv_dmx1.Height()
|
|
106
|
+
print(f"\nStream 1 info:")
|
|
107
|
+
print(f"Dimensions: {stream1_width}x{stream1_height}")
|
|
108
|
+
print(f"Codec: {stream1_codec}")
|
|
109
|
+
print(f"FPS: {nv_dmx1.FrameRate()}")
|
|
110
|
+
|
|
111
|
+
# Create second demuxer and get stream info
|
|
112
|
+
nv_dmx2 = nvc.CreateDemuxer(filename=enc_file_path2)
|
|
113
|
+
stream2_codec = nv_dmx2.GetNvCodecId()
|
|
114
|
+
stream2_width = nv_dmx2.Width()
|
|
115
|
+
stream2_height = nv_dmx2.Height()
|
|
116
|
+
print(f"\nStream 2 info:")
|
|
117
|
+
print(f"Dimensions: {stream2_width}x{stream2_height}")
|
|
118
|
+
print(f"Codec: {stream2_codec}")
|
|
119
|
+
print(f"FPS: {nv_dmx2.FrameRate()}\n")
|
|
120
|
+
|
|
121
|
+
# Create decoder
|
|
122
|
+
nv_dec = nvc.CreateDecoder(
|
|
123
|
+
gpuid=gpu_id,
|
|
124
|
+
codec=stream1_codec,
|
|
125
|
+
usedevicememory=use_device_memory,
|
|
126
|
+
maxwidth=max(stream1_width, stream2_width),
|
|
127
|
+
maxheight=max(stream1_height, stream2_height)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Open output files for writing decoded frames
|
|
131
|
+
with open(dec_file_path1, "wb") as decFile1, open(dec_file_path2, "wb") as decFile2:
|
|
132
|
+
# First decode stream 1
|
|
133
|
+
print("Decoding stream 1...")
|
|
134
|
+
frames_decoded = decode_stream(nv_dec, nv_dmx1, decFile1, use_device_memory, frame_count)
|
|
135
|
+
print(f"\nCompleted stream 1: {frames_decoded} frames decoded")
|
|
136
|
+
if frames_decoded > 0:
|
|
137
|
+
print(f"Decoded frames written to {dec_file_path1}\n")
|
|
138
|
+
else:
|
|
139
|
+
print("No frames were decoded from stream 1\n")
|
|
140
|
+
print("Reconfiguring decoder for stream 2...")
|
|
141
|
+
|
|
142
|
+
# Reconfigure decoder for stream 2
|
|
143
|
+
nv_dec.setReconfigParams(stream2_width, stream2_height)
|
|
144
|
+
print(f"Decoder reconfigured to dimensions: {stream2_width}x{stream2_height}\n")
|
|
145
|
+
|
|
146
|
+
# Now decode stream 2
|
|
147
|
+
print("Decoding stream 2...")
|
|
148
|
+
frames_decoded = decode_stream(nv_dec, nv_dmx2, decFile2, use_device_memory, frame_count)
|
|
149
|
+
print(f"\nCompleted stream 2: {frames_decoded} frames decoded")
|
|
150
|
+
if frames_decoded > 0:
|
|
151
|
+
print(f"Decoded frames written to {dec_file_path2}")
|
|
152
|
+
else:
|
|
153
|
+
print("No frames were decoded from stream 2")
|
|
154
|
+
|
|
155
|
+
except nvc.PyNvVCExceptionUnsupported as e:
|
|
156
|
+
print(f"CreateDecoder failure: {e}")
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(f"An unexpected error occurred: {e}")
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
args = parse_reconfigure_args(
|
|
162
|
+
"This sample application demonstrates decoder reconfiguration between two input streams with different dimensions."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
print("=" * 60)
|
|
166
|
+
print("Configuration:")
|
|
167
|
+
print(f"Input 1: {args.input_file1}")
|
|
168
|
+
print(f"Input 2: {args.input_file2}")
|
|
169
|
+
print(f"Output 1: {args.raw_file_path1}")
|
|
170
|
+
print(f"Output 2: {args.raw_file_path2}")
|
|
171
|
+
print(f"GPU ID: {args.gpu_id}")
|
|
172
|
+
print(f"Use Device Memory: {args.use_device_memory}")
|
|
173
|
+
print(f"Frame Count: {args.frame_count if args.frame_count else 'All frames'}")
|
|
174
|
+
print("=" * 60)
|
|
175
|
+
|
|
176
|
+
decode_with_reconfigure(
|
|
177
|
+
args.gpu_id,
|
|
178
|
+
args.input_file1,
|
|
179
|
+
args.input_file2,
|
|
180
|
+
args.raw_file_path1,
|
|
181
|
+
args.raw_file_path2,
|
|
182
|
+
args.use_device_memory,
|
|
183
|
+
args.frame_count
|
|
184
|
+
)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
'''
|
|
25
|
+
This sample demonstrates how to extract and parse Supplemental Enhancement Information (SEI) from video streams.
|
|
26
|
+
|
|
27
|
+
It shows how to:
|
|
28
|
+
1. Access SEI messages from decoded frames and write them to a file
|
|
29
|
+
2. Parse different SEI message types
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
SEI messages are additional data embedded in video streams that provide supplementary information:
|
|
33
|
+
- HDR/Display Metadata: Color volume, light levels, and transfer characteristics for HDR content
|
|
34
|
+
- Timecode Data: Frame timing and sequence information
|
|
35
|
+
- Custom Metadata: User-defined data for application-specific needs
|
|
36
|
+
|
|
37
|
+
Common Use Cases:
|
|
38
|
+
- Video Playback: HDR display configuration and color management
|
|
39
|
+
- Content Creation: Frame accurate editing and post-processing
|
|
40
|
+
- Broadcast: Timing synchronization and content identification
|
|
41
|
+
- Custom Applications: Embedding application-specific metadata in the video stream
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Usage: python decode_sei_msg.py -i <input_file> -s <sei_file>
|
|
45
|
+
Example: python decode_sei_msg.py -i input.mp4 -s sei_data.bin
|
|
46
|
+
|
|
47
|
+
Arguments:
|
|
48
|
+
-i: Input video file
|
|
49
|
+
-s: Output SEI message file (default: sei_message.bin).
|
|
50
|
+
-st: Output SEI type message file (default: sei_type_message.bin)
|
|
51
|
+
-d: Use device memory (1) or host memory (0) (default: 1)
|
|
52
|
+
-g: GPU ID to use (default: 0)
|
|
53
|
+
'''
|
|
54
|
+
|
|
55
|
+
import sys
|
|
56
|
+
import numpy as np
|
|
57
|
+
import pickle
|
|
58
|
+
import ctypes
|
|
59
|
+
import argparse
|
|
60
|
+
import pycuda.driver as cuda
|
|
61
|
+
import PyNvVideoCodec as nvc
|
|
62
|
+
from os.path import join, dirname, abspath
|
|
63
|
+
|
|
64
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
65
|
+
current_dir = dirname(abspath(__file__))
|
|
66
|
+
parent_dir = dirname(current_dir)
|
|
67
|
+
if parent_dir not in sys.path:
|
|
68
|
+
sys.path.append(parent_dir)
|
|
69
|
+
|
|
70
|
+
from utils.decode_parser import parse_sei_args
|
|
71
|
+
|
|
72
|
+
from utils.sei_message_parser import (
|
|
73
|
+
TIMECODE, TIMECODEMPEG2, SEICONTENTLIGHTLEVELINFO,
|
|
74
|
+
SEIMASTERINGDISPLAYINFO, SEIALTERNATIVETRANSFERCHARACTERISTICS
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def extract_sei_messages(gpu_id, enc_file_path, sei_file_path, sei_type_file_path, use_device_memory):
|
|
78
|
+
"""
|
|
79
|
+
Function to extract SEI messages from a video file.
|
|
80
|
+
|
|
81
|
+
This function decodes a video file and extracts SEI messages, producing three output files:
|
|
82
|
+
1. sei_message.bin - Raw binary SEI messages
|
|
83
|
+
2. sei_type_message.bin - Pickled SEI type information
|
|
84
|
+
|
|
85
|
+
Parameters:
|
|
86
|
+
gpu_id (int): Ordinal of GPU to use
|
|
87
|
+
enc_file_path (str): Path to file to be decoded
|
|
88
|
+
sei_file_path (str): Path to output file for SEI messages. Sei type message is written to sei_type_message.bin
|
|
89
|
+
sei_type_file_path (str): Path to output file for SEI type messages.
|
|
90
|
+
use_device_memory (int): If set to 1, output decoded frame is CUDeviceptr wrapped in CUDA Array Interface
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
None
|
|
94
|
+
"""
|
|
95
|
+
cuda_ctx = None
|
|
96
|
+
|
|
97
|
+
# Open files for storing SEI data
|
|
98
|
+
file_message = open(sei_file_path, "wb")
|
|
99
|
+
file_type_message = open(sei_type_file_path, "wb")
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
cuda.init()
|
|
103
|
+
device_id = gpu_id
|
|
104
|
+
cuda_device = cuda.Device(device_id) # pyright: ignore[reportAttributeAccessIssue]
|
|
105
|
+
cuda_ctx = cuda_device.retain_primary_context()
|
|
106
|
+
cuda_ctx.push()
|
|
107
|
+
cuda_stream = cuda.Stream()
|
|
108
|
+
|
|
109
|
+
nv_dmx = nvc.CreateDemuxer(filename=enc_file_path)
|
|
110
|
+
|
|
111
|
+
nv_dec = nvc.CreateDecoder(
|
|
112
|
+
gpuid=gpu_id,
|
|
113
|
+
codec=nv_dmx.GetNvCodecId(),
|
|
114
|
+
cudacontext=cuda_ctx.handle,
|
|
115
|
+
cudastream=cuda_stream.handle,
|
|
116
|
+
usedevicememory=use_device_memory,
|
|
117
|
+
enableSEIMessage=1
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
sei_message_found = False
|
|
121
|
+
print(f"FPS = {nv_dmx.FrameRate()}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
for packet in nv_dmx:
|
|
125
|
+
for decoded_frame in nv_dec.Decode(packet):
|
|
126
|
+
|
|
127
|
+
# Process SEI messages
|
|
128
|
+
seiMessage = decoded_frame.getSEIMessage()
|
|
129
|
+
if seiMessage:
|
|
130
|
+
sei_message_found = True
|
|
131
|
+
for sei_info, sei_message in seiMessage:
|
|
132
|
+
sei_type = sei_info["sei_type"]
|
|
133
|
+
sei_uncompressed = sei_info["sei_uncompressed"]
|
|
134
|
+
|
|
135
|
+
if sei_uncompressed == 1:
|
|
136
|
+
buffer = (ctypes.c_ubyte * len(sei_message))(*sei_message)
|
|
137
|
+
sei_struct = None
|
|
138
|
+
|
|
139
|
+
# Handle different SEI message types
|
|
140
|
+
if sei_type in (nvc.SEI_TYPE.TIME_CODE_H264, nvc.SEI_TYPE.TIME_CODE):
|
|
141
|
+
sei_struct = ctypes.cast(
|
|
142
|
+
buffer,
|
|
143
|
+
ctypes.POINTER(TIMECODEMPEG2 if nv_dmx.GetNvCodecId() == nvc.cudaVideoCodec.MPEG2 else TIMECODE)
|
|
144
|
+
).contents
|
|
145
|
+
elif sei_type == nvc.SEI_TYPE.MASTERING_DISPLAY_COLOR_VOLUME:
|
|
146
|
+
sei_struct = ctypes.cast(buffer, ctypes.POINTER(SEIMASTERINGDISPLAYINFO)).contents
|
|
147
|
+
elif sei_type == nvc.SEI_TYPE.CONTENT_LIGHT_LEVEL_INFO:
|
|
148
|
+
sei_struct = ctypes.cast(buffer, ctypes.POINTER(SEICONTENTLIGHTLEVELINFO)).contents
|
|
149
|
+
elif sei_type == nvc.SEI_TYPE.ALTERNATIVE_TRANSFER_CHARACTERISTICS:
|
|
150
|
+
sei_struct = ctypes.cast(buffer, ctypes.POINTER(SEIALTERNATIVETRANSFERCHARACTERISTICS)).contents
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
file_message.write(bytearray(sei_message))
|
|
154
|
+
pickle.dump(seiMessage, file_type_message)
|
|
155
|
+
|
|
156
|
+
if sei_message_found:
|
|
157
|
+
print(f"SEI message written to:")
|
|
158
|
+
print(f" Binary data: {sei_file_path}")
|
|
159
|
+
print(f" Type info: {sei_type_file_path}")
|
|
160
|
+
else:
|
|
161
|
+
print("No SEI message found")
|
|
162
|
+
|
|
163
|
+
except nvc.PyNvVCExceptionUnsupported as e:
|
|
164
|
+
print(f"CreateDecoder failure: {e}")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
print(f"An unexpected error occurred: {e}")
|
|
167
|
+
finally:
|
|
168
|
+
file_message.close()
|
|
169
|
+
file_type_message.close()
|
|
170
|
+
if cuda_ctx is not None:
|
|
171
|
+
cuda_ctx.pop()
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
args = parse_sei_args(
|
|
175
|
+
"This sample application demonstrates extracting SEI messages from video files."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
print("=" * 60)
|
|
179
|
+
print("Configuration:")
|
|
180
|
+
print(f"Input: {args.encoded_file_path}")
|
|
181
|
+
print(f"SEI Output: {args.sei_file_path}")
|
|
182
|
+
print(f"SEI Type Output: {args.sei_type_file_path}")
|
|
183
|
+
print(f"GPU ID: {args.gpu_id}")
|
|
184
|
+
print(f"Use Device Memory: {args.use_device_memory}")
|
|
185
|
+
print("=" * 60)
|
|
186
|
+
|
|
187
|
+
extract_sei_messages(
|
|
188
|
+
args.gpu_id,
|
|
189
|
+
args.encoded_file_path,
|
|
190
|
+
args.sei_file_path,
|
|
191
|
+
args.sei_type_file_path,
|
|
192
|
+
args.use_device_memory
|
|
193
|
+
)
|