PyNvVideoCodec 2.0.4__cp314-cp314-win_amd64.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.cp314-win_amd64.pyd +0 -0
- PyNvVideoCodec/PyNvVideoCodec_130.cp314-win_amd64.pyd +0 -0
- PyNvVideoCodec/VersionCheck.cp314-win_amd64.pyd +0 -0
- PyNvVideoCodec/__init__.py +246 -0
- PyNvVideoCodec/avcodec-62.dll +0 -0
- PyNvVideoCodec/avdevice-62.dll +0 -0
- PyNvVideoCodec/avfilter-11.dll +0 -0
- PyNvVideoCodec/avformat-62.dll +0 -0
- PyNvVideoCodec/avutil-60.dll +0 -0
- PyNvVideoCodec/decoders/SimpleDecoder.py +230 -0
- PyNvVideoCodec/decoders/ThreadedDecoder.py +129 -0
- PyNvVideoCodec/decoders/__init__.py +22 -0
- PyNvVideoCodec/swresample-6.dll +0 -0
- PyNvVideoCodec/swscale-9.dll +0 -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 +47 -0
- benchmarks/segmented_transcode_benchmark.py +1546 -0
- pynvvideocodec-2.0.4.data/data/external/ffmpeg/readme.txt +15 -0
- pynvvideocodec-2.0.4.data/data/external/ffmpeg/src/ffmpeg-8.1.1.tar.xz +0 -0
- pynvvideocodec-2.0.4.dist-info/METADATA +84 -0
- pynvvideocodec-2.0.4.dist-info/RECORD +65 -0
- pynvvideocodec-2.0.4.dist-info/WHEEL +5 -0
- pynvvideocodec-2.0.4.dist-info/licenses/LICENSE.txt +22 -0
- pynvvideocodec-2.0.4.dist-info/licenses/NOTICES.txt +167 -0
- pynvvideocodec-2.0.4.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 +9 -0
- samples/advanced/encode_config_lowlatency.json +11 -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_stats.py +179 -0
- samples/advanced/transcode.py +288 -0
- samples/basic/create_video_segments.py +187 -0
- samples/basic/encode.py +347 -0
- samples/basic/encode_config.json +9 -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/object_detection_tutorial.ipynb +762 -0
- samples/jupyter/simple_decode_tutorial.ipynb +651 -0
- samples/requirements.txt +78 -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 +357 -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,204 @@
|
|
|
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
|
+
'''
|
|
26
|
+
This sample demonstrates buffer-based video decoding using memory chunks.
|
|
27
|
+
|
|
28
|
+
This approach is useful when video data comes from memory buffers
|
|
29
|
+
rather than direct file access (e.g., network streams, memory-mapped files). This saves file I/O overhead.
|
|
30
|
+
|
|
31
|
+
It shows how to:
|
|
32
|
+
1. Create a custom data feeder for memory-based video input
|
|
33
|
+
2. Set up a demuxer that reads from memory buffers
|
|
34
|
+
3. Process video data without direct file I/O
|
|
35
|
+
4. Handle streaming data in chunks
|
|
36
|
+
|
|
37
|
+
Key Learning Outcomes:
|
|
38
|
+
1. Using callback-based demuxer for custom data sources
|
|
39
|
+
2. Managing video data buffers and chunk sizes efficiently
|
|
40
|
+
3. Implementing proper buffer position tracking and EOF handling
|
|
41
|
+
4. Working with both device and host memory for decoded frames
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Usage: python decode_from_memory_buffer.py -i <input_file> -o <output_file>
|
|
45
|
+
Example: python decode_from_memory_buffer.py -i input.mp4 -o output.yuv
|
|
46
|
+
|
|
47
|
+
Arguments:
|
|
48
|
+
-i: Input video file
|
|
49
|
+
-o: Output raw video file (default: <input_name>.yuv)
|
|
50
|
+
-d: Use device memory (1) or host memory (0) (default: 1)
|
|
51
|
+
-g: GPU ID to use (default: 0)
|
|
52
|
+
-f: Number of frames to decode (optional)
|
|
53
|
+
|
|
54
|
+
'''
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
import pycuda.driver as cuda
|
|
59
|
+
import PyNvVideoCodec as nvc
|
|
60
|
+
import pycuda.autoinit as context
|
|
61
|
+
import sys
|
|
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
|
+
# Import utilities for decoding
|
|
71
|
+
from utils.decode_parser import parse_decode_args
|
|
72
|
+
from utils.Utils import get_frame_data
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class VideoStreamFeeder:
|
|
77
|
+
"""
|
|
78
|
+
Class to handle feeding video data in chunks to the demuxer.
|
|
79
|
+
|
|
80
|
+
This class reads a video file into memory and provides a method to feed
|
|
81
|
+
chunks of data to the demuxer buffer.
|
|
82
|
+
"""
|
|
83
|
+
def __init__(self, file_path):
|
|
84
|
+
"""
|
|
85
|
+
Initialize the VideoStreamFeeder with a video file.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
file_path (str): Path to the video file to be read
|
|
89
|
+
"""
|
|
90
|
+
with open(file_path, 'rb') as f:
|
|
91
|
+
self.video_buffer = bytearray(f.read())
|
|
92
|
+
self.current_pos = 0
|
|
93
|
+
self.bytes_remaining = len(self.video_buffer)
|
|
94
|
+
self.chunk_size = 0
|
|
95
|
+
|
|
96
|
+
def feed_chunk(self, demuxer_buffer):
|
|
97
|
+
"""
|
|
98
|
+
Feed next chunk of video data to demuxer buffer.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
demuxer_buffer: Pre-allocated buffer provided by demuxer
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
int: Number of bytes copied to buffer, 0 if no more data
|
|
105
|
+
"""
|
|
106
|
+
buffer_capacity = len(demuxer_buffer)
|
|
107
|
+
|
|
108
|
+
if self.bytes_remaining < buffer_capacity:
|
|
109
|
+
self.chunk_size = self.bytes_remaining
|
|
110
|
+
else:
|
|
111
|
+
self.chunk_size = buffer_capacity
|
|
112
|
+
|
|
113
|
+
if self.chunk_size == 0:
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
demuxer_buffer[:] = self.video_buffer[self.current_pos:self.current_pos + self.chunk_size]
|
|
117
|
+
|
|
118
|
+
self.current_pos += self.chunk_size
|
|
119
|
+
self.bytes_remaining -= self.chunk_size
|
|
120
|
+
return self.chunk_size
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def decode_from_byte_array(input_file, yuv_file, use_device_memory, gpu_id, frame_count=None):
|
|
124
|
+
"""
|
|
125
|
+
Implement buffer-based pipeline that reads the input file in chunks.
|
|
126
|
+
|
|
127
|
+
This function demonstrates how to decode video by processing data directly
|
|
128
|
+
from memory buffers instead of reading from disk.
|
|
129
|
+
|
|
130
|
+
Parameters:
|
|
131
|
+
input_file (str): Path to the input video file to be decoded
|
|
132
|
+
yuv_file (str): Path where the buffer-based pipeline output will be saved
|
|
133
|
+
use_device_memory (int): If set to 1, output decoded frame is CUDeviceptr wrapped in CUDA Array Interface
|
|
134
|
+
gpu_id (int): GPU device ID to use
|
|
135
|
+
frame_count (int, optional): Maximum number of frames to decode. If None, 0, or negative, decode all frames.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
None: Decoded frames are written to a raw file.
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
print("Starting buffer-based decoding...")
|
|
142
|
+
data_feeder = VideoStreamFeeder(input_file)
|
|
143
|
+
buffer_demuxer = nvc.CreateDemuxer(data_feeder.feed_chunk)
|
|
144
|
+
|
|
145
|
+
buffer_decoder = nvc.CreateDecoder(
|
|
146
|
+
gpuid=gpu_id,
|
|
147
|
+
codec=buffer_demuxer.GetNvCodecId(),
|
|
148
|
+
cudacontext=0,
|
|
149
|
+
cudastream=0,
|
|
150
|
+
usedevicememory=use_device_memory
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Normalize frame count
|
|
154
|
+
decode_all = frame_count is None or frame_count <= 0
|
|
155
|
+
if decode_all:
|
|
156
|
+
print("Decoding all available frames")
|
|
157
|
+
frame_count = None
|
|
158
|
+
|
|
159
|
+
frames_decoded = 0
|
|
160
|
+
|
|
161
|
+
with open(yuv_file, 'wb') as decFile:
|
|
162
|
+
for packet in buffer_demuxer:
|
|
163
|
+
for decoded_frame in buffer_decoder.Decode(packet):
|
|
164
|
+
|
|
165
|
+
frame_data = get_frame_data(decoded_frame, use_device_memory)
|
|
166
|
+
decFile.write(bytearray(frame_data))
|
|
167
|
+
|
|
168
|
+
frames_decoded += 1
|
|
169
|
+
if frame_count is not None and frames_decoded >= frame_count:
|
|
170
|
+
print(f"Successfully decoded requested {frame_count} frames to {yuv_file}")
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
# If we get here, we've decoded all available frames
|
|
174
|
+
if frame_count is not None and frames_decoded < frame_count:
|
|
175
|
+
print(f"Warning: Video ended before reaching requested frame count. Requested: {frame_count}, Decoded: {frames_decoded}")
|
|
176
|
+
print(f"Decoded frames written to {yuv_file}")
|
|
177
|
+
else:
|
|
178
|
+
print(f"Successfully decoded all {frames_decoded} frames to {yuv_file}")
|
|
179
|
+
except Exception as e:
|
|
180
|
+
print(f"An unexpected error occurred: {e}")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
|
|
185
|
+
args = parse_decode_args(
|
|
186
|
+
"Demonstrates video demuxing by processing video data directly from memory buffers."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
print("=" * 60)
|
|
190
|
+
print("Configuration:")
|
|
191
|
+
print(f"Input: {args.encoded_file_path}")
|
|
192
|
+
print(f"Output: {args.raw_file_path}")
|
|
193
|
+
print(f"GPU ID: {args.gpu_id}")
|
|
194
|
+
print(f"Use Device Memory: {args.use_device_memory}")
|
|
195
|
+
print(f"Frame Count: {args.frame_count if args.frame_count else 'All frames'}")
|
|
196
|
+
print("=" * 60)
|
|
197
|
+
|
|
198
|
+
decode_from_byte_array(
|
|
199
|
+
input_file=args.encoded_file_path,
|
|
200
|
+
yuv_file=args.raw_file_path,
|
|
201
|
+
use_device_memory=args.use_device_memory,
|
|
202
|
+
gpu_id=args.gpu_id,
|
|
203
|
+
frame_count=args.frame_count
|
|
204
|
+
)
|
|
@@ -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
|
+
)
|