PyNvVideoCodec 2.2.1__cp313-cp313-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.
Files changed (63) hide show
  1. PyNvVideoCodec/PyNvVideoCodec_121.cp313-win_arm64.pyd +0 -0
  2. PyNvVideoCodec/PyNvVideoCodec_130.cp313-win_arm64.pyd +0 -0
  3. PyNvVideoCodec/VersionCheck.cp313-win_arm64.pyd +0 -0
  4. PyNvVideoCodec/__init__.py +251 -0
  5. PyNvVideoCodec/avformat-62.dll +0 -0
  6. PyNvVideoCodec/avutil-60.dll +0 -0
  7. PyNvVideoCodec/decoders/SimpleDecoder.py +273 -0
  8. PyNvVideoCodec/decoders/ThreadedDecoder.py +129 -0
  9. PyNvVideoCodec/decoders/__init__.py +22 -0
  10. PyNvVideoCodec/transcoder/Transcoder.py +55 -0
  11. PyNvVideoCodec/transcoder/__init__.py +22 -0
  12. benchmarks/cached_decoder_benchmark.py +813 -0
  13. benchmarks/frame_sampling_benchmark.py +765 -0
  14. benchmarks/requirements.txt +52 -0
  15. benchmarks/segmented_transcode_benchmark.py +1546 -0
  16. benchmarks/setup_pycuda_win_arm64.bat +531 -0
  17. pynvvideocodec-2.2.1.data/data/external/ffmpeg/readme.txt +39 -0
  18. pynvvideocodec-2.2.1.data/data/external/ffmpeg/src/ffmpeg-8.1.2.tar.xz +0 -0
  19. pynvvideocodec-2.2.1.dist-info/METADATA +97 -0
  20. pynvvideocodec-2.2.1.dist-info/RECORD +63 -0
  21. pynvvideocodec-2.2.1.dist-info/WHEEL +5 -0
  22. pynvvideocodec-2.2.1.dist-info/licenses/LICENSE.txt +22 -0
  23. pynvvideocodec-2.2.1.dist-info/licenses/NOTICES.txt +167 -0
  24. pynvvideocodec-2.2.1.dist-info/top_level.txt +3 -0
  25. samples/advanced/decode.py +173 -0
  26. samples/advanced/decode_from_memory_buffer.py +204 -0
  27. samples/advanced/decode_perf.py +299 -0
  28. samples/advanced/decode_reconfigure.py +184 -0
  29. samples/advanced/decode_sei_msg.py +193 -0
  30. samples/advanced/decode_with_cuda_control.py +214 -0
  31. samples/advanced/decode_with_low_latency.py +183 -0
  32. samples/advanced/encode_config.json +12 -0
  33. samples/advanced/encode_config_lowlatency.json +13 -0
  34. samples/advanced/encode_perf.py +338 -0
  35. samples/advanced/encode_reconfigure.py +211 -0
  36. samples/advanced/encode_sei_msg.py +188 -0
  37. samples/advanced/simple_decode_from_memory.py +151 -0
  38. samples/advanced/simple_decode_stats.py +179 -0
  39. samples/advanced/transcode.py +288 -0
  40. samples/basic/create_video_segments.py +187 -0
  41. samples/basic/encode.py +574 -0
  42. samples/basic/segments.txt +3 -0
  43. samples/basic/simple_decode_sampling.py +182 -0
  44. samples/basic/transcode_config.json +6 -0
  45. samples/jupyter/ReconfigureDecoder.png +0 -0
  46. samples/jupyter/SimpleDecoderAPIs.png +0 -0
  47. samples/jupyter/ThreadedDecoder.png +0 -0
  48. samples/jupyter/coco_classes.pickle +0 -0
  49. samples/jupyter/face_protect_tutorial.ipynb +881 -0
  50. samples/jupyter/object_detection_tutorial.ipynb +781 -0
  51. samples/jupyter/simple_decode_tutorial.ipynb +674 -0
  52. samples/requirements.txt +84 -0
  53. samples/setup_pycuda_win_arm64.bat +531 -0
  54. samples/utils/Utils.py +405 -0
  55. samples/utils/__init__.py +36 -0
  56. samples/utils/decode_parser.py +642 -0
  57. samples/utils/decode_stats_writer.py +177 -0
  58. samples/utils/encode_parallel_utils.py +174 -0
  59. samples/utils/encode_parser.py +379 -0
  60. samples/utils/frame_utils.py +89 -0
  61. samples/utils/sei_message_parser.py +223 -0
  62. samples/utils/sei_parser.py +223 -0
  63. 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
+ )