PyNvVideoCodec 2.0.0__cp313-cp313-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.cp313-win_amd64.pyd +0 -0
- PyNvVideoCodec/PyNvVideoCodec_130.cp313-win_amd64.pyd +0 -0
- PyNvVideoCodec/VersionCheck.cp313-win_amd64.pyd +0 -0
- PyNvVideoCodec/__init__.py +237 -0
- PyNvVideoCodec/avcodec-58.dll +0 -0
- PyNvVideoCodec/avcodec-61.dll +0 -0
- PyNvVideoCodec/avcodec.dll +0 -0
- PyNvVideoCodec/avdevice-58.dll +0 -0
- PyNvVideoCodec/avdevice-61.dll +0 -0
- PyNvVideoCodec/avdevice.dll +0 -0
- PyNvVideoCodec/avfilter-10.dll +0 -0
- PyNvVideoCodec/avfilter-7.dll +0 -0
- PyNvVideoCodec/avfilter.dll +0 -0
- PyNvVideoCodec/avformat-58.dll +0 -0
- PyNvVideoCodec/avformat-61.dll +0 -0
- PyNvVideoCodec/avformat.dll +0 -0
- PyNvVideoCodec/avutil-56.dll +0 -0
- PyNvVideoCodec/avutil-59.dll +0 -0
- PyNvVideoCodec/avutil.dll +0 -0
- PyNvVideoCodec/decoders/SimpleDecoder.py +225 -0
- PyNvVideoCodec/decoders/ThreadedDecoder.py +119 -0
- PyNvVideoCodec/decoders/__init__.py +22 -0
- PyNvVideoCodec/swresample-3.dll +0 -0
- PyNvVideoCodec/swresample-5.dll +0 -0
- PyNvVideoCodec/swresample.dll +0 -0
- PyNvVideoCodec/swscale-5.dll +0 -0
- PyNvVideoCodec/swscale-8.dll +0 -0
- PyNvVideoCodec/swscale.dll +0 -0
- PyNvVideoCodec/transcoder/Transcoder.py +55 -0
- PyNvVideoCodec/transcoder/__init__.py +22 -0
- PyNvVideoCodec-2.0.0.data/data/external/ffmpeg/readme.txt +10 -0
- PyNvVideoCodec-2.0.0.data/data/external/ffmpeg/src/ffmpeg-7.0.2.tar.xz +0 -0
- PyNvVideoCodec-2.0.0.dist-info/METADATA +77 -0
- PyNvVideoCodec-2.0.0.dist-info/RECORD +64 -0
- PyNvVideoCodec-2.0.0.dist-info/WHEEL +5 -0
- PyNvVideoCodec-2.0.0.dist-info/licenses/LICENSE.txt +22 -0
- PyNvVideoCodec-2.0.0.dist-info/licenses/NOTICES.txt +167 -0
- PyNvVideoCodec-2.0.0.dist-info/top_level.txt +3 -0
- benchmarks/cached_decoder_benchmark.py +635 -0
- benchmarks/frame_sampling_benchmark.py +610 -0
- benchmarks/segmented_transcode_benchmark.py +1072 -0
- samples/Decode.py +194 -0
- samples/DecodeMultiprocessing.py +162 -0
- samples/DecodePerf.py +215 -0
- samples/DecodeSEIMsgExtraction.py +338 -0
- samples/DemuxFromByteArray.py +185 -0
- samples/Encode.py +189 -0
- samples/EncodeFromCPUBuffer.py +220 -0
- samples/EncodeMultiprocessing.py +268 -0
- samples/EncodePerf.py +380 -0
- samples/EncodeReconfigure.py +201 -0
- samples/EncodeSEIMsgInsertion.py +189 -0
- samples/ObjectDetection.py +218 -0
- samples/SimpleDecoder/EnsembleApp.py +416 -0
- samples/SimpleDecoder/SubsamplingAndReconfigure.py +341 -0
- samples/SimpleDecoder/fileLists.txt +4 -0
- samples/SimpleDecoder/segments.txt +3 -0
- samples/SimpleDecoder/timeListInSeconds.txt +5 -0
- samples/SimpleDecoder/transcode_config.json +6 -0
- samples/Utils.py +290 -0
- samples/coco_classes.pickle +0 -0
- samples/encode_config.json +16 -0
- samples/encode_config_lowlatency.json +11 -0
- samples/encode_config_perf.json +9 -0
samples/Decode.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 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
|
+
import sys
|
|
25
|
+
import argparse
|
|
26
|
+
import numpy as np
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
import pycuda.driver as cuda
|
|
29
|
+
import PyNvVideoCodec as nvc
|
|
30
|
+
from os.path import join, dirname, abspath, splitext
|
|
31
|
+
|
|
32
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
33
|
+
current_dir = dirname(abspath(__file__))
|
|
34
|
+
parent_dir = dirname(current_dir)
|
|
35
|
+
if parent_dir not in sys.path:
|
|
36
|
+
sys.path.append(parent_dir)
|
|
37
|
+
|
|
38
|
+
from Utils import cast_address_to_1d_bytearray
|
|
39
|
+
|
|
40
|
+
def decode(gpu_id, enc_file_path, dec_file_path, use_device_memory, decode_latency,frame_count=None):
|
|
41
|
+
"""
|
|
42
|
+
Function to decode media file and write raw frames into an output file.
|
|
43
|
+
|
|
44
|
+
This function will read a media file and split it into chunks of data (packets).
|
|
45
|
+
A Packet contains elementary bitstream belonging to one frame and conforms to annex.b standard.
|
|
46
|
+
Packet is sent to decoder for parsing and hardware accelerated decoding. Decoder returns list of raw YUV
|
|
47
|
+
frames which can be iterated upon.
|
|
48
|
+
|
|
49
|
+
Parameters:
|
|
50
|
+
gpu_id (int): Ordinal of GPU to use
|
|
51
|
+
enc_file_path (str): Path to file to be decoded
|
|
52
|
+
dec_file_path (str): Path to output file into which raw frames are stored
|
|
53
|
+
use_device_memory (int): If set to 1, output decoded frame is CUDeviceptr wrapped in CUDA Array Interface
|
|
54
|
+
frame_count (int, optional): Maximum number of frames to decode. If None, 0, or negative, decode all frames.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> decode(0, "path/to/input/media/file","path/to/output/yuv", 1)
|
|
58
|
+
Function to decode media file and write raw frames into an output file.
|
|
59
|
+
"""
|
|
60
|
+
# Validate frame count if provided
|
|
61
|
+
if frame_count is None or frame_count <= 0:
|
|
62
|
+
print(f"Decoding all available frames")
|
|
63
|
+
frame_count = None # Reset to None to decode all frames
|
|
64
|
+
|
|
65
|
+
cuda_ctx = None
|
|
66
|
+
try:
|
|
67
|
+
device_id = gpu_id
|
|
68
|
+
cuda_device = cuda.Device(device_id) # pyright: ignore[reportAttributeAccessIssue]
|
|
69
|
+
cuda_ctx = cuda_device.retain_primary_context()
|
|
70
|
+
cuda_ctx.push()
|
|
71
|
+
cuda_stream = cuda.Stream()
|
|
72
|
+
nv_dmx = nvc.CreateDemuxer(filename=enc_file_path)
|
|
73
|
+
|
|
74
|
+
caps = nvc.GetDecoderCaps(
|
|
75
|
+
gpuid=gpu_id,
|
|
76
|
+
codec=nv_dmx.GetNvCodecId(),
|
|
77
|
+
chromaformat=nv_dmx.ChromaFormat(),
|
|
78
|
+
bitdepth=nv_dmx.BitDepth()
|
|
79
|
+
)
|
|
80
|
+
if "num_decoder_engines" in caps:
|
|
81
|
+
print("Number of NVDECs:", caps["num_decoder_engines"])
|
|
82
|
+
|
|
83
|
+
nv_dec = nvc.CreateDecoder(gpuid=gpu_id,
|
|
84
|
+
codec=nv_dmx.GetNvCodecId(),
|
|
85
|
+
cudacontext=cuda_ctx.handle,
|
|
86
|
+
cudastream=cuda_stream.handle,
|
|
87
|
+
usedevicememory=use_device_memory,
|
|
88
|
+
latency=decode_latency)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
decoded_frame_size = 0
|
|
92
|
+
raw_frame = None
|
|
93
|
+
|
|
94
|
+
seq_triggered = False
|
|
95
|
+
# printing out FPS and pixel format of the stream for convenience
|
|
96
|
+
print("FPS =", nv_dmx.FrameRate())
|
|
97
|
+
# open the file to be decoded in write mode
|
|
98
|
+
with open(dec_file_path, "wb") as decFile:
|
|
99
|
+
# demuxer can be iterated, fetch the packet from demuxer
|
|
100
|
+
frames_decoded = 0
|
|
101
|
+
for packet in nv_dmx:
|
|
102
|
+
# Decode returns a list of packets, range of this list is from [0, size of (decode picture buffer)]
|
|
103
|
+
# size of (decode picture buffer) depends on GPU, fur Turing series its 8
|
|
104
|
+
if decode_latency == nvc.DisplayDecodeLatencyType.LOW or decode_latency == nvc.DisplayDecodeLatencyType.ZERO:
|
|
105
|
+
# Set when the packet contains exactly one frame or one field bitstream data, parser will trigger decode callback immediately when this flag is set.
|
|
106
|
+
packet.decode_flag = nvc.VideoPacketFlag.ENDOFPICTURE
|
|
107
|
+
|
|
108
|
+
for decoded_frame in nv_dec.Decode(packet):
|
|
109
|
+
# 'decoded_frame' contains list of views implementing cuda array interface
|
|
110
|
+
# for nv12, it would contain 2 views for each plane and two planes would be contiguous
|
|
111
|
+
if not seq_triggered:
|
|
112
|
+
decoded_frame_size = nv_dec.GetFrameSize()
|
|
113
|
+
raw_frame = np.ndarray(shape=decoded_frame_size, dtype=np.uint8)
|
|
114
|
+
seq_triggered = True
|
|
115
|
+
|
|
116
|
+
luma_base_addr = decoded_frame.GetPtrToPlane(0)
|
|
117
|
+
if use_device_memory:
|
|
118
|
+
cuda.memcpy_dtoh(raw_frame, luma_base_addr)
|
|
119
|
+
bits = bytearray(raw_frame)
|
|
120
|
+
decFile.write(bits)
|
|
121
|
+
else:
|
|
122
|
+
new_array = cast_address_to_1d_bytearray(base_address=luma_base_addr, size=decoded_frame.framesize())
|
|
123
|
+
decFile.write(bytearray(new_array))
|
|
124
|
+
|
|
125
|
+
frames_decoded += 1
|
|
126
|
+
if frame_count is not None and frame_count > 0 and frames_decoded >= frame_count:
|
|
127
|
+
print(f"Reached requested frame count: {frame_count}")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if frame_count is not None and frame_count > 0 and frames_decoded < frame_count:
|
|
131
|
+
print(f"Video ended before reaching requested frame count. Decoded {frames_decoded} frames")
|
|
132
|
+
except nvc.PyNvVCExceptionUnsupported as e:
|
|
133
|
+
print(f"CreateDecoder failure: {e}")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"An unexpected error occurred: {e}")
|
|
136
|
+
finally:
|
|
137
|
+
if cuda_ctx is not None:
|
|
138
|
+
cuda_ctx.pop()
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
parser = argparse.ArgumentParser(
|
|
142
|
+
description="This sample application illustrates the demuxing and decoding of a media file."
|
|
143
|
+
)
|
|
144
|
+
parser.add_argument(
|
|
145
|
+
"-g", "--gpu_id",
|
|
146
|
+
type=int,
|
|
147
|
+
default=0,
|
|
148
|
+
help="Check nvidia-smi for available GPUs. Default: 0"
|
|
149
|
+
)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"-i", "--encoded_file_path",
|
|
152
|
+
type=Path,
|
|
153
|
+
required=True,
|
|
154
|
+
help="Encoded video file (read from)"
|
|
155
|
+
)
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
"-o", "--raw_file_path",
|
|
158
|
+
type=Path,
|
|
159
|
+
help="Raw NV12 video file (write to). Default: <input_file_name>.yuv"
|
|
160
|
+
)
|
|
161
|
+
parser.add_argument(
|
|
162
|
+
"-d", "--use_device_memory",
|
|
163
|
+
type=int,
|
|
164
|
+
default=1,
|
|
165
|
+
help="Decoder output surface is in device memory (1) else in host memory (0) (default: 1)"
|
|
166
|
+
)
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
"-f",
|
|
169
|
+
"--frame_count", type=int, help="Maximum number of frames to decode")
|
|
170
|
+
parser.add_argument(
|
|
171
|
+
"-dl", "--decode_latency", type=int, default=0, help=""
|
|
172
|
+
"0 - Decoder input and output have a latency of 4 frames, output in display order"
|
|
173
|
+
"1 - Decoder input and output have a latency of 0 frames, output in display order"
|
|
174
|
+
"2 - Decoder input and output have a latency of 0 frames, output in decode order",
|
|
175
|
+
)
|
|
176
|
+
args = parser.parse_args()
|
|
177
|
+
decode_latency = nvc.DisplayDecodeLatencyType.NATIVE
|
|
178
|
+
if args.decode_latency == 1:
|
|
179
|
+
decode_latency = nvc.DisplayDecodeLatencyType.LOW
|
|
180
|
+
elif args.decode_latency == 2:
|
|
181
|
+
decode_latency = nvc.DisplayDecodeLatencyType.ZERO
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Set default raw_file_path if not provided
|
|
185
|
+
if args.raw_file_path is None:
|
|
186
|
+
input_path = Path(args.encoded_file_path)
|
|
187
|
+
args.raw_file_path = input_path.with_suffix('.yuv')
|
|
188
|
+
|
|
189
|
+
decode(args.gpu_id, args.encoded_file_path.as_posix(),
|
|
190
|
+
args.raw_file_path.as_posix(),
|
|
191
|
+
args.use_device_memory,
|
|
192
|
+
decode_latency,
|
|
193
|
+
args.frame_count)
|
|
194
|
+
print(f"Decoded frames written to {args.raw_file_path}")
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 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
|
+
import sys
|
|
25
|
+
import os
|
|
26
|
+
import argparse
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
import pycuda.driver as cuda
|
|
29
|
+
import PyNvVideoCodec as nvc
|
|
30
|
+
from multiprocessing import Process
|
|
31
|
+
import multiprocessing
|
|
32
|
+
from os.path import join, dirname, abspath
|
|
33
|
+
import pycuda.autoinit
|
|
34
|
+
|
|
35
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
36
|
+
current_dir = dirname(abspath(__file__))
|
|
37
|
+
parent_dir = dirname(current_dir)
|
|
38
|
+
if parent_dir not in sys.path:
|
|
39
|
+
sys.path.append(parent_dir)
|
|
40
|
+
|
|
41
|
+
def decode_process(gpu_id, enc_file_path, frame_count=None):
|
|
42
|
+
"""
|
|
43
|
+
Function to decode media file in a separate process.
|
|
44
|
+
|
|
45
|
+
Parameters:
|
|
46
|
+
gpu_id (int): Ordinal of GPU to use
|
|
47
|
+
enc_file_path (str): Path to file to be decoded
|
|
48
|
+
frame_count (int, optional): Maximum number of frames to decode. If None, 0, or negative, decode all frames.
|
|
49
|
+
"""
|
|
50
|
+
# Validate frame count if provided
|
|
51
|
+
if frame_count is None or frame_count <= 0:
|
|
52
|
+
print(f"Decoding all available frames")
|
|
53
|
+
frame_count = None # Reset to None to decode all frames
|
|
54
|
+
|
|
55
|
+
cuda_ctx = None
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
device_id = gpu_id
|
|
59
|
+
cuda_device = cuda.Device(device_id) # pyright: ignore[reportAttributeAccessIssue]
|
|
60
|
+
cuda_ctx = cuda_device.retain_primary_context()
|
|
61
|
+
cuda_ctx.push()
|
|
62
|
+
cuda_stream = cuda.Stream()
|
|
63
|
+
nv_dmx = nvc.CreateDemuxer(filename=enc_file_path)
|
|
64
|
+
nv_dec = nvc.CreateDecoder(
|
|
65
|
+
gpuid=gpu_id,
|
|
66
|
+
codec=nv_dmx.GetNvCodecId(),
|
|
67
|
+
cudacontext=cuda_ctx.handle,
|
|
68
|
+
cudastream=cuda_stream.handle,
|
|
69
|
+
usedevicememory=True
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
num_decoded_frames = 0
|
|
73
|
+
for packet in nv_dmx:
|
|
74
|
+
for _ in nv_dec.Decode(packet):
|
|
75
|
+
num_decoded_frames += 1
|
|
76
|
+
cuda_stream.synchronize()
|
|
77
|
+
|
|
78
|
+
if frame_count is not None and frame_count > 0 and num_decoded_frames >= frame_count:
|
|
79
|
+
print(f"Process {multiprocessing.current_process().name} reached requested frame count: {frame_count}")
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
if frame_count is not None and frame_count > 0 and num_decoded_frames >= frame_count:
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
if frame_count is not None and frame_count > 0 and num_decoded_frames < frame_count:
|
|
86
|
+
print(f"Video ended before reaching requested frame count for process {multiprocessing.current_process().name}. Decoded {num_decoded_frames} frames")
|
|
87
|
+
|
|
88
|
+
print(f"Process {multiprocessing.current_process().name} completed decoding {num_decoded_frames} frames")
|
|
89
|
+
|
|
90
|
+
except nvc.PyNvVCExceptionUnsupported as e:
|
|
91
|
+
print(f"CreateDecoder failure: {e}")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"An unexpected error occurred: {e}")
|
|
94
|
+
finally:
|
|
95
|
+
if cuda_ctx is not None:
|
|
96
|
+
cuda_ctx.pop()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def run_parallel_decode(num_processes, gpu_id, enc_file_path, frame_count=None):
|
|
100
|
+
"""
|
|
101
|
+
This function demonstrates parallel video decoding using multiple processes.
|
|
102
|
+
|
|
103
|
+
The application creates multiple python processes and runs a different decoding session on each process.
|
|
104
|
+
Each process decodes the same input file independently.
|
|
105
|
+
|
|
106
|
+
Parameters:
|
|
107
|
+
num_processes (int): Number of parallel decoding processes to run
|
|
108
|
+
gpu_id (int): Ordinal of GPU to use
|
|
109
|
+
enc_file_path (str): Path to file to be decoded
|
|
110
|
+
frame_count (int, optional): Maximum number of frames to decode per process
|
|
111
|
+
"""
|
|
112
|
+
multiprocessing.set_start_method('spawn')
|
|
113
|
+
processes = []
|
|
114
|
+
|
|
115
|
+
for i in range(num_processes):
|
|
116
|
+
p = Process(target=decode_process, args=(gpu_id, enc_file_path, frame_count))
|
|
117
|
+
p.start()
|
|
118
|
+
processes.append(p)
|
|
119
|
+
print(f"Started decode process {i+1}/{num_processes}")
|
|
120
|
+
|
|
121
|
+
for p in processes:
|
|
122
|
+
p.join()
|
|
123
|
+
|
|
124
|
+
print(f"All {num_processes} decode processes completed")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
parser = argparse.ArgumentParser(
|
|
129
|
+
description="This sample application demonstrates parallel video decoding using multiple processes."
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"-g", "--gpu_id",
|
|
133
|
+
type=int,
|
|
134
|
+
default=0,
|
|
135
|
+
help="Check nvidia-smi for available GPUs. Default: 0"
|
|
136
|
+
)
|
|
137
|
+
parser.add_argument(
|
|
138
|
+
"-i", "--encoded_file_path",
|
|
139
|
+
type=Path,
|
|
140
|
+
required=True,
|
|
141
|
+
help="Encoded video file (read from)"
|
|
142
|
+
)
|
|
143
|
+
parser.add_argument(
|
|
144
|
+
"-n", "--num_processes",
|
|
145
|
+
default=1,
|
|
146
|
+
type=int,
|
|
147
|
+
help="Number of parallel decode processes to run. (default: 1)"
|
|
148
|
+
)
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
"-f", "--frame_count",
|
|
151
|
+
type=int,
|
|
152
|
+
help="Maximum number of frames to decode per process. If not provided, all frames will be decoded."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
args = parser.parse_args()
|
|
156
|
+
|
|
157
|
+
run_parallel_decode(
|
|
158
|
+
args.num_processes,
|
|
159
|
+
args.gpu_id,
|
|
160
|
+
args.encoded_file_path.as_posix(),
|
|
161
|
+
args.frame_count
|
|
162
|
+
)
|
samples/DecodePerf.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 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
|
+
from struct import pack
|
|
25
|
+
import sys
|
|
26
|
+
import os
|
|
27
|
+
import argparse
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from enum import Enum
|
|
30
|
+
import pycuda.driver as cuda
|
|
31
|
+
import PyNvVideoCodec as nvc
|
|
32
|
+
import numpy as np
|
|
33
|
+
from pycuda.compiler import SourceModule
|
|
34
|
+
import time
|
|
35
|
+
import pycuda.autoinit
|
|
36
|
+
import threading
|
|
37
|
+
from os.path import join, dirname, abspath
|
|
38
|
+
|
|
39
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
40
|
+
current_dir = dirname(abspath(__file__))
|
|
41
|
+
parent_dir = dirname(current_dir)
|
|
42
|
+
if parent_dir not in sys.path:
|
|
43
|
+
sys.path.append(parent_dir)
|
|
44
|
+
|
|
45
|
+
# Global variables for tracking performance metrics
|
|
46
|
+
total_fps = 0
|
|
47
|
+
num_frames = 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def decode_parallel(cuda_ctx, cuda_stream, gpu_id, enc_file_path, frame_count=None):
|
|
51
|
+
"""
|
|
52
|
+
Function to decode media file and measure performance in a thread.
|
|
53
|
+
|
|
54
|
+
Parameters:
|
|
55
|
+
cuda_ctx: CUDA context for the thread
|
|
56
|
+
cuda_stream: CUDA stream for the thread
|
|
57
|
+
gpu_id (int): Ordinal of GPU to use [Parameter not in use]
|
|
58
|
+
enc_file_path (str): Path to file to be decoded
|
|
59
|
+
frame_count (int, optional): Maximum number of frames to decode. If None, 0, or negative, decode all frames.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
None
|
|
63
|
+
"""
|
|
64
|
+
global total_fps, num_frames
|
|
65
|
+
|
|
66
|
+
cuda_ctx.push()
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
if frame_count is None or frame_count <= 0:
|
|
70
|
+
print(f"Decoding all available frames")
|
|
71
|
+
frame_count = None # Reset to None to decode all frames
|
|
72
|
+
nv_dmx = nvc.CreateDemuxer(filename=enc_file_path)
|
|
73
|
+
nv_dec = nvc.CreateDecoder(
|
|
74
|
+
gpuid=gpu_id,
|
|
75
|
+
codec=nv_dmx.GetNvCodecId(),
|
|
76
|
+
cudacontext=cuda_ctx.handle,
|
|
77
|
+
cudastream=cuda_stream.handle,
|
|
78
|
+
usedevicememory=True,
|
|
79
|
+
bWaitForSessionWarmUp=True
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
start = time.perf_counter()
|
|
83
|
+
num_decoded_frames = 0
|
|
84
|
+
|
|
85
|
+
for packet in nv_dmx:
|
|
86
|
+
for _ in nv_dec.Decode(packet):
|
|
87
|
+
num_decoded_frames += 1
|
|
88
|
+
if frame_count is not None and frame_count > 0 and num_decoded_frames >= frame_count:
|
|
89
|
+
print(f"Thread {threading.current_thread().name} reached requested frame count: {frame_count}")
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
if frame_count is not None and frame_count > 0 and num_decoded_frames >= frame_count:
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
if frame_count is not None and frame_count > 0 and num_decoded_frames < frame_count:
|
|
96
|
+
print(f"Video ended before reaching requested frame count for thread {threading.current_thread().name}. Decoded {num_decoded_frames} frames")
|
|
97
|
+
|
|
98
|
+
# Synchronize stream after all decoding is done
|
|
99
|
+
cuda_stream.synchronize()
|
|
100
|
+
|
|
101
|
+
# Calculate accurate elapsed time by subtracting initialization time
|
|
102
|
+
elapsed_time = time.perf_counter() - start
|
|
103
|
+
init_time = nv_dec.GetSessionInitTime()
|
|
104
|
+
init_time /= 1000.00 # Convert to seconds
|
|
105
|
+
elapsed_time -= init_time
|
|
106
|
+
|
|
107
|
+
print(f"Thread {threading.current_thread().name} decoded {num_decoded_frames} frames in {elapsed_time:.2f} seconds")
|
|
108
|
+
|
|
109
|
+
# Update performance metrics with thread-safe operation
|
|
110
|
+
with threading.Lock():
|
|
111
|
+
global total_fps, num_frames
|
|
112
|
+
total_fps += (num_decoded_frames / elapsed_time)
|
|
113
|
+
num_frames += num_decoded_frames
|
|
114
|
+
|
|
115
|
+
except nvc.PyNvVCExceptionUnsupported as e:
|
|
116
|
+
print(f"CreateDecoder failure: {e}")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
print(f"An unexpected error occurred: {e}")
|
|
119
|
+
finally:
|
|
120
|
+
cuda_ctx.pop()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def run_parallel_decode(N, gpu_id, enc_file_path, frame_count=None):
|
|
124
|
+
"""
|
|
125
|
+
This function demonstrates decoding using multiple threads.
|
|
126
|
+
|
|
127
|
+
The application creates multiple python threads and runs a different decoding session on each thread.
|
|
128
|
+
The number of sessions can be controlled by the CLI option "--num_threads".
|
|
129
|
+
The application supports decoding frames in device memory and provides detailed performance metrics.
|
|
130
|
+
|
|
131
|
+
Parameters:
|
|
132
|
+
N (int): Number of parallel decoding sessions to run
|
|
133
|
+
gpu_id (int): Ordinal of GPU to use [Parameter not in use]
|
|
134
|
+
enc_file_path (str): Path to file to be decoded
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
None
|
|
138
|
+
"""
|
|
139
|
+
global total_fps, num_frames
|
|
140
|
+
total_fps = 0
|
|
141
|
+
num_frames = 0
|
|
142
|
+
threads = []
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
cuda_device = cuda.Device(gpu_id)
|
|
146
|
+
cuda_ctx = cuda_device.make_context()
|
|
147
|
+
cuda_stream = cuda.Stream()
|
|
148
|
+
nvc.PyNvDecoder.SetSessionCount(N)
|
|
149
|
+
|
|
150
|
+
for i in range(0, N):
|
|
151
|
+
t = threading.Thread(
|
|
152
|
+
target=decode_parallel,
|
|
153
|
+
args=(cuda_ctx, cuda_stream, gpu_id, enc_file_path, frame_count)
|
|
154
|
+
)
|
|
155
|
+
print(f"Started decode thread {i+1}/{N}")
|
|
156
|
+
t.start()
|
|
157
|
+
threads.append(t)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
for t in threads:
|
|
161
|
+
t.join()
|
|
162
|
+
|
|
163
|
+
cuda_ctx.pop()
|
|
164
|
+
cuda_ctx.detach()
|
|
165
|
+
|
|
166
|
+
# Print performance summary
|
|
167
|
+
print("\n--- Performance Summary ---")
|
|
168
|
+
print(f"Number of threads: {N}")
|
|
169
|
+
print(f"Total frames decoded: {num_frames}")
|
|
170
|
+
print(f"Total FPS: {total_fps:.2f}")
|
|
171
|
+
print(f"Average FPS per thread: {total_fps/N:.2f}")
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
print(f"Error in parallel decode: {e}")
|
|
175
|
+
if 'cuda_ctx' in locals():
|
|
176
|
+
cuda_ctx.pop()
|
|
177
|
+
cuda_ctx.detach()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
parser = argparse.ArgumentParser(
|
|
182
|
+
description="This sample application demonstrates video decoding using multiple threads."
|
|
183
|
+
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"-g", "--gpu_id",
|
|
186
|
+
type=int,
|
|
187
|
+
default=0,
|
|
188
|
+
help="Check nvidia-smi for available GPUs. Default: 0"
|
|
189
|
+
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"-i", "--encoded_file_path",
|
|
192
|
+
type=Path,
|
|
193
|
+
required=True,
|
|
194
|
+
help="Encoded video file (read from)"
|
|
195
|
+
)
|
|
196
|
+
parser.add_argument(
|
|
197
|
+
"-n", "--num_threads",
|
|
198
|
+
default=1,
|
|
199
|
+
type=int,
|
|
200
|
+
help="Number of parallel decode threads to run. Default: 1"
|
|
201
|
+
)
|
|
202
|
+
parser.add_argument(
|
|
203
|
+
"-f", "--frame_count",
|
|
204
|
+
type=int,
|
|
205
|
+
help="Maximum number of frames to decode. If not provided, all frames will be decoded."
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
args = parser.parse_args()
|
|
209
|
+
|
|
210
|
+
run_parallel_decode(
|
|
211
|
+
args.num_threads,
|
|
212
|
+
args.gpu_id,
|
|
213
|
+
args.encoded_file_path.as_posix(),
|
|
214
|
+
args.frame_count
|
|
215
|
+
)
|