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
|
@@ -0,0 +1,338 @@
|
|
|
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
|
+
import pickle
|
|
28
|
+
import ctypes
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
import pycuda.driver as cuda
|
|
31
|
+
import PyNvVideoCodec as nvc
|
|
32
|
+
from os.path import join, dirname, abspath
|
|
33
|
+
|
|
34
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
35
|
+
current_dir = dirname(abspath(__file__))
|
|
36
|
+
parent_dir = dirname(current_dir)
|
|
37
|
+
if parent_dir not in sys.path:
|
|
38
|
+
sys.path.append(parent_dir)
|
|
39
|
+
|
|
40
|
+
from Utils import cast_address_to_1d_bytearray
|
|
41
|
+
|
|
42
|
+
# Constants
|
|
43
|
+
MAX_CLOCK_TS = 3
|
|
44
|
+
|
|
45
|
+
class TIMECODESET(ctypes.Structure):
|
|
46
|
+
"""Structure for time code set information."""
|
|
47
|
+
_fields_ = [
|
|
48
|
+
("time_offset_value", ctypes.c_uint32), # unsigned int
|
|
49
|
+
("n_frames", ctypes.c_uint16), # unsigned short
|
|
50
|
+
("clock_timestamp_flag", ctypes.c_uint8), # unsigned char
|
|
51
|
+
("units_field_based_flag", ctypes.c_uint8), # unsigned char
|
|
52
|
+
("counting_type", ctypes.c_uint8), # unsigned char
|
|
53
|
+
("full_timestamp_flag", ctypes.c_uint8), # unsigned char
|
|
54
|
+
("discontinuity_flag", ctypes.c_uint8), # unsigned char
|
|
55
|
+
("cnt_dropped_flag", ctypes.c_uint8), # unsigned char
|
|
56
|
+
("seconds_value", ctypes.c_uint8), # unsigned char
|
|
57
|
+
("minutes_value", ctypes.c_uint8), # unsigned char
|
|
58
|
+
("hours_value", ctypes.c_uint8), # unsigned char
|
|
59
|
+
("seconds_flag", ctypes.c_uint8), # unsigned char
|
|
60
|
+
("minutes_flag", ctypes.c_uint8), # unsigned char
|
|
61
|
+
("hours_flag", ctypes.c_uint8), # unsigned char
|
|
62
|
+
("time_offset_length", ctypes.c_uint8), # unsigned char
|
|
63
|
+
("reserved", ctypes.c_uint8), # unsigned char
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
def __repr__(self):
|
|
67
|
+
return (
|
|
68
|
+
"TIMECODESET(\n"
|
|
69
|
+
f" time_offset_value={self.time_offset_value},\n"
|
|
70
|
+
f" n_frames={self.n_frames},\n"
|
|
71
|
+
f" clock_timestamp_flag={self.clock_timestamp_flag},\n"
|
|
72
|
+
f" units_field_based_flag={self.units_field_based_flag},\n"
|
|
73
|
+
f" counting_type={self.counting_type},\n"
|
|
74
|
+
f" full_timestamp_flag={self.full_timestamp_flag},\n"
|
|
75
|
+
f" discontinuity_flag={self.discontinuity_flag},\n"
|
|
76
|
+
f" cnt_dropped_flag={self.cnt_dropped_flag},\n"
|
|
77
|
+
f" seconds_value={self.seconds_value},\n"
|
|
78
|
+
f" minutes_value={self.minutes_value},\n"
|
|
79
|
+
f" hours_value={self.hours_value},\n"
|
|
80
|
+
f" seconds_flag={self.seconds_flag},\n"
|
|
81
|
+
f" minutes_flag={self.minutes_flag},\n"
|
|
82
|
+
f" hours_flag={self.hours_flag},\n"
|
|
83
|
+
f" time_offset_length={self.time_offset_length},\n"
|
|
84
|
+
f" reserved={self.reserved}\n"
|
|
85
|
+
")"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
class TIMECODE(ctypes.Structure):
|
|
89
|
+
"""Structure for time code information."""
|
|
90
|
+
_fields_ = [
|
|
91
|
+
("time_code_set", TIMECODESET * MAX_CLOCK_TS), # Array of TIMECODESET
|
|
92
|
+
("num_clock_ts", ctypes.c_uint8), # unsigned char
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
def __repr__(self):
|
|
96
|
+
time_code_set_repr = ",\n ".join(
|
|
97
|
+
repr(self.time_code_set[i]) for i in range(self.num_clock_ts)
|
|
98
|
+
)
|
|
99
|
+
return (
|
|
100
|
+
"TIMECODE(\n"
|
|
101
|
+
f" time_code_set=[\n {time_code_set_repr}\n ],\n"
|
|
102
|
+
f" num_clock_ts={self.num_clock_ts}\n"
|
|
103
|
+
")"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
class SEICONTENTLIGHTLEVELINFO(ctypes.Structure):
|
|
107
|
+
"""Structure for content light level information."""
|
|
108
|
+
_fields_ = [
|
|
109
|
+
("max_content_light_level", ctypes.c_uint16), # unsigned short
|
|
110
|
+
("max_pic_average_light_level", ctypes.c_uint16), # unsigned short
|
|
111
|
+
("reserved", ctypes.c_uint32), # unsigned int
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
def __repr__(self):
|
|
115
|
+
return (
|
|
116
|
+
"SEICONTENTLIGHTLEVELINFO(\n"
|
|
117
|
+
f" max_content_light_level={self.max_content_light_level},\n"
|
|
118
|
+
f" max_pic_average_light_level={self.max_pic_average_light_level},\n"
|
|
119
|
+
f" reserved={self.reserved}\n"
|
|
120
|
+
")"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
class SEIMASTERINGDISPLAYINFO(ctypes.Structure):
|
|
124
|
+
"""Structure for mastering display information."""
|
|
125
|
+
_fields_ = [
|
|
126
|
+
("display_primaries_x", ctypes.c_uint16 * 3), # Array of 3 unsigned short
|
|
127
|
+
("display_primaries_y", ctypes.c_uint16 * 3), # Array of 3 unsigned short
|
|
128
|
+
("white_point_x", ctypes.c_uint16), # unsigned short
|
|
129
|
+
("white_point_y", ctypes.c_uint16), # unsigned short
|
|
130
|
+
("max_display_mastering_luminance", ctypes.c_uint32), # unsigned int
|
|
131
|
+
("min_display_mastering_luminance", ctypes.c_uint32), # unsigned int
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
def __repr__(self):
|
|
135
|
+
return (
|
|
136
|
+
"SEIMASTERINGDISPLAYINFO(\n"
|
|
137
|
+
f" display_primaries_x={list(self.display_primaries_x)},\n"
|
|
138
|
+
f" display_primaries_y={list(self.display_primaries_y)},\n"
|
|
139
|
+
f" white_point_x={self.white_point_x},\n"
|
|
140
|
+
f" white_point_y={self.white_point_y},\n"
|
|
141
|
+
f" max_display_mastering_luminance={self.max_display_mastering_luminance},\n"
|
|
142
|
+
f" min_display_mastering_luminance={self.min_display_mastering_luminance}\n"
|
|
143
|
+
")"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
class TIMECODEMPEG2(ctypes.Structure):
|
|
147
|
+
"""Structure for MPEG2 time code information."""
|
|
148
|
+
_fields_ = [
|
|
149
|
+
("drop_frame_flag", ctypes.c_uint8), # unsigned char
|
|
150
|
+
("time_code_hours", ctypes.c_uint8), # unsigned char
|
|
151
|
+
("time_code_minutes", ctypes.c_uint8), # unsigned char
|
|
152
|
+
("marker_bit", ctypes.c_uint8), # unsigned char
|
|
153
|
+
("time_code_seconds", ctypes.c_uint8), # unsigned char
|
|
154
|
+
("time_code_pictures", ctypes.c_uint8), # unsigned char
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
def __repr__(self):
|
|
158
|
+
return (
|
|
159
|
+
"TIMECODEMPEG2(\n"
|
|
160
|
+
f" drop_frame_flag={self.drop_frame_flag},\n"
|
|
161
|
+
f" time_code_hours={self.time_code_hours},\n"
|
|
162
|
+
f" time_code_minutes={self.time_code_minutes},\n"
|
|
163
|
+
f" marker_bit={self.marker_bit},\n"
|
|
164
|
+
f" time_code_seconds={self.time_code_seconds},\n"
|
|
165
|
+
f" time_code_pictures={self.time_code_pictures}\n"
|
|
166
|
+
")"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
class SEIALTERNATIVETRANSFERCHARACTERISTICS(ctypes.Structure):
|
|
170
|
+
"""Structure for alternative transfer characteristics."""
|
|
171
|
+
_fields_ = [
|
|
172
|
+
("preferred_transfer_characteristics", ctypes.c_uint8), # unsigned char
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
def __repr__(self):
|
|
176
|
+
return f"SEIALTERNATIVETRANSFERCHARACTERISTICS(preferred_transfer_characteristics={self.preferred_transfer_characteristics})"
|
|
177
|
+
|
|
178
|
+
def decode(gpu_id, enc_file_path, dec_file_path, sei_file_path, use_device_memory):
|
|
179
|
+
"""
|
|
180
|
+
Function to decode media file, write raw frames and SEI messages into output files.
|
|
181
|
+
|
|
182
|
+
Parameters:
|
|
183
|
+
gpu_id (int): Ordinal of GPU to use
|
|
184
|
+
enc_file_path (str): Path to file to be decoded
|
|
185
|
+
dec_file_path (str): Path to output file for raw frames
|
|
186
|
+
sei_file_path (str): Path to output file for SEI messages
|
|
187
|
+
use_device_memory (int): If set to 1, output decoded frame is CUDeviceptr wrapped in CUDA Array Interface
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
None
|
|
191
|
+
"""
|
|
192
|
+
cuda_ctx = None
|
|
193
|
+
|
|
194
|
+
# Open files for storing SEI data
|
|
195
|
+
file_message = open(sei_file_path, "wb")
|
|
196
|
+
script_dir = dirname(abspath(__file__))
|
|
197
|
+
sei_type_message_path = join(script_dir, "sei_type_message.bin")
|
|
198
|
+
file_type_message = open(sei_type_message_path, "wb")
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
device_id = gpu_id
|
|
202
|
+
cuda_device = cuda.Device(device_id) # pyright: ignore[reportAttributeAccessIssue]
|
|
203
|
+
cuda_ctx = cuda_device.retain_primary_context()
|
|
204
|
+
cuda_ctx.push()
|
|
205
|
+
cuda_stream = cuda.Stream()
|
|
206
|
+
nv_dmx = nvc.CreateDemuxer(filename=enc_file_path)
|
|
207
|
+
nv_dec = nvc.CreateDecoder(
|
|
208
|
+
gpuid=gpu_id,
|
|
209
|
+
codec=nv_dmx.GetNvCodecId(),
|
|
210
|
+
cudacontext=cuda_ctx.handle,
|
|
211
|
+
cudastream=cuda_stream.handle,
|
|
212
|
+
usedevicememory=use_device_memory,
|
|
213
|
+
enableSEIMessage=1
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
decoded_frame_size = 0
|
|
217
|
+
raw_frame = None
|
|
218
|
+
seq_triggered = False
|
|
219
|
+
|
|
220
|
+
print(f"FPS = {nv_dmx.FrameRate()}")
|
|
221
|
+
|
|
222
|
+
sei_message_found = False
|
|
223
|
+
with open(dec_file_path, "wb") as decFile:
|
|
224
|
+
for packet in nv_dmx:
|
|
225
|
+
# Decode returns a list of packets, range of this list is from [0, size of (decode picture buffer)]
|
|
226
|
+
# size of (decode picture buffer) depends on GPU, for Turing series its 8
|
|
227
|
+
for decoded_frame in nv_dec.Decode(packet):
|
|
228
|
+
# 'decoded_frame' contains list of views implementing cuda array interface
|
|
229
|
+
# for nv12, it would contain 2 views for each plane and two planes would be contiguous
|
|
230
|
+
if not seq_triggered:
|
|
231
|
+
decoded_frame_size = nv_dec.GetFrameSize()
|
|
232
|
+
raw_frame = np.ndarray(shape=decoded_frame_size, dtype=np.uint8)
|
|
233
|
+
seq_triggered = True
|
|
234
|
+
|
|
235
|
+
luma_base_addr = decoded_frame.GetPtrToPlane(0)
|
|
236
|
+
if use_device_memory:
|
|
237
|
+
cuda.memcpy_dtoh(raw_frame, luma_base_addr)
|
|
238
|
+
bits = bytearray(raw_frame)
|
|
239
|
+
decFile.write(bits)
|
|
240
|
+
else:
|
|
241
|
+
new_array = cast_address_to_1d_bytearray(
|
|
242
|
+
base_address=luma_base_addr,
|
|
243
|
+
size=decoded_frame.framesize()
|
|
244
|
+
)
|
|
245
|
+
decFile.write(bytearray(new_array))
|
|
246
|
+
|
|
247
|
+
# Process SEI messages
|
|
248
|
+
seiMessage = decoded_frame.getSEIMessage()
|
|
249
|
+
if seiMessage:
|
|
250
|
+
sei_message_found = True
|
|
251
|
+
for sei_info, sei_message in seiMessage:
|
|
252
|
+
sei_type = sei_info["sei_type"]
|
|
253
|
+
sei_uncompressed = sei_info["sei_uncompressed"]
|
|
254
|
+
|
|
255
|
+
if sei_uncompressed == 1:
|
|
256
|
+
buffer = (ctypes.c_ubyte * len(sei_message))(*sei_message)
|
|
257
|
+
sei_struct = None
|
|
258
|
+
|
|
259
|
+
# Handle different SEI message types
|
|
260
|
+
if sei_type in (nvc.SEI_TYPE.TIME_CODE_H264, nvc.SEI_TYPE.TIME_CODE):
|
|
261
|
+
sei_struct = ctypes.cast(
|
|
262
|
+
buffer,
|
|
263
|
+
ctypes.POINTER(TIMECODEMPEG2 if nv_dmx.GetNvCodecId() == nvc.cudaVideoCodec.MPEG2 else TIMECODE)
|
|
264
|
+
).contents
|
|
265
|
+
elif sei_type == nvc.SEI_TYPE.MASTERING_DISPLAY_COLOR_VOLUME:
|
|
266
|
+
sei_struct = ctypes.cast(buffer, ctypes.POINTER(SEIMASTERINGDISPLAYINFO)).contents
|
|
267
|
+
elif sei_type == nvc.SEI_TYPE.CONTENT_LIGHT_LEVEL_INFO:
|
|
268
|
+
sei_struct = ctypes.cast(buffer, ctypes.POINTER(SEICONTENTLIGHTLEVELINFO)).contents
|
|
269
|
+
elif sei_type == nvc.SEI_TYPE.ALTERNATIVE_TRANSFER_CHARACTERISTICS:
|
|
270
|
+
sei_struct = ctypes.cast(buffer, ctypes.POINTER(SEIALTERNATIVETRANSFERCHARACTERISTICS)).contents
|
|
271
|
+
|
|
272
|
+
if sei_struct:
|
|
273
|
+
print(sei_struct)
|
|
274
|
+
|
|
275
|
+
file_message.write(bytearray(sei_message))
|
|
276
|
+
pickle.dump(seiMessage, file_type_message)
|
|
277
|
+
print(f"SEI message written to {sei_file_path}")
|
|
278
|
+
if not sei_message_found:
|
|
279
|
+
print("No SEI message found")
|
|
280
|
+
|
|
281
|
+
except nvc.PyNvVCExceptionUnsupported as e:
|
|
282
|
+
print(f"CreateDecoder failure: {e}")
|
|
283
|
+
except Exception as e:
|
|
284
|
+
print(f"An unexpected error occurred: {e}")
|
|
285
|
+
finally:
|
|
286
|
+
file_message.close()
|
|
287
|
+
file_type_message.close()
|
|
288
|
+
if cuda_ctx is not None:
|
|
289
|
+
cuda_ctx.pop()
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
parser = argparse.ArgumentParser(
|
|
293
|
+
description="This sample application demonstrates decoding media files and extracting SEI messages."
|
|
294
|
+
)
|
|
295
|
+
parser.add_argument(
|
|
296
|
+
"-g", "--gpu_id",
|
|
297
|
+
type=int,
|
|
298
|
+
default=0,
|
|
299
|
+
help="Check nvidia-smi for available GPUs. Default: 0"
|
|
300
|
+
)
|
|
301
|
+
parser.add_argument(
|
|
302
|
+
"-i", "--encoded_file_path",
|
|
303
|
+
type=Path,
|
|
304
|
+
required=True,
|
|
305
|
+
help="Encoded video file (read from)"
|
|
306
|
+
)
|
|
307
|
+
parser.add_argument(
|
|
308
|
+
"-o", "--raw_file_path",
|
|
309
|
+
type=Path,
|
|
310
|
+
help="Raw NV12 video file (write to). Default: <input_file_name>.yuv"
|
|
311
|
+
)
|
|
312
|
+
parser.add_argument(
|
|
313
|
+
"-f", "--sei_file_path",
|
|
314
|
+
type=Path,
|
|
315
|
+
default="sei_message.bin",
|
|
316
|
+
help="SEI binary file (write to). Default: sei_message.bin"
|
|
317
|
+
)
|
|
318
|
+
parser.add_argument(
|
|
319
|
+
"-d", "--use_device_memory",
|
|
320
|
+
type=int,
|
|
321
|
+
default=1,
|
|
322
|
+
help="Decoder output surface is in device memory (1) else in host memory (0). Default is 1."
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
args = parser.parse_args()
|
|
326
|
+
|
|
327
|
+
# Set default raw_file_path if not provided
|
|
328
|
+
if args.raw_file_path is None:
|
|
329
|
+
input_path = Path(args.encoded_file_path)
|
|
330
|
+
args.raw_file_path = input_path.with_suffix('.yuv')
|
|
331
|
+
|
|
332
|
+
decode(
|
|
333
|
+
args.gpu_id,
|
|
334
|
+
args.encoded_file_path.as_posix(),
|
|
335
|
+
args.raw_file_path.as_posix(),
|
|
336
|
+
args.sei_file_path.as_posix(),
|
|
337
|
+
args.use_device_memory
|
|
338
|
+
)
|
|
@@ -0,0 +1,185 @@
|
|
|
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 necessary libraries
|
|
25
|
+
import argparse
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
import pycuda.driver as cuda
|
|
28
|
+
import PyNvVideoCodec as nvc
|
|
29
|
+
import numpy as np
|
|
30
|
+
import pycuda.autoinit as context
|
|
31
|
+
import sys
|
|
32
|
+
from os.path import join, dirname, abspath
|
|
33
|
+
|
|
34
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
35
|
+
current_dir = dirname(abspath(__file__))
|
|
36
|
+
parent_dir = dirname(current_dir)
|
|
37
|
+
if parent_dir not in sys.path:
|
|
38
|
+
sys.path.append(parent_dir)
|
|
39
|
+
|
|
40
|
+
from Utils import cast_address_to_1d_bytearray
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class VideoStreamFeeder:
|
|
44
|
+
"""
|
|
45
|
+
Class to handle feeding video data in chunks to the demuxer.
|
|
46
|
+
|
|
47
|
+
This class reads a video file into memory and provides a method to feed
|
|
48
|
+
chunks of data to the demuxer buffer.
|
|
49
|
+
"""
|
|
50
|
+
def __init__(self, file_path):
|
|
51
|
+
"""
|
|
52
|
+
Initialize the VideoStreamFeeder with a video file.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
file_path (str): Path to the video file to be read
|
|
56
|
+
"""
|
|
57
|
+
with open(file_path, 'rb') as f:
|
|
58
|
+
self.video_buffer = bytearray(f.read())
|
|
59
|
+
self.current_pos = 0
|
|
60
|
+
self.bytes_remaining = len(self.video_buffer)
|
|
61
|
+
self.chunk_size = 0
|
|
62
|
+
|
|
63
|
+
def feed_chunk(self, demuxer_buffer):
|
|
64
|
+
"""
|
|
65
|
+
Feed next chunk of video data to demuxer buffer.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
demuxer_buffer: Pre-allocated buffer provided by demuxer
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
int: Number of bytes copied to buffer, 0 if no more data
|
|
72
|
+
"""
|
|
73
|
+
buffer_capacity = len(demuxer_buffer)
|
|
74
|
+
|
|
75
|
+
if self.bytes_remaining < buffer_capacity:
|
|
76
|
+
self.chunk_size = self.bytes_remaining
|
|
77
|
+
else:
|
|
78
|
+
self.chunk_size = buffer_capacity
|
|
79
|
+
|
|
80
|
+
if self.chunk_size == 0:
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
demuxer_buffer[:] = self.video_buffer[self.current_pos:self.current_pos + self.chunk_size]
|
|
84
|
+
|
|
85
|
+
self.current_pos += self.chunk_size
|
|
86
|
+
self.bytes_remaining -= self.chunk_size
|
|
87
|
+
return self.chunk_size
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def demux_from_byte_array(input_file, yuv_file, use_device_memory, gpu_id):
|
|
91
|
+
"""
|
|
92
|
+
Implement buffer-based pipeline that reads the input file in chunks.
|
|
93
|
+
|
|
94
|
+
This function demonstrates how to decode video by processing data directly
|
|
95
|
+
from memory buffers instead of reading from disk.
|
|
96
|
+
|
|
97
|
+
Parameters:
|
|
98
|
+
input_file (str): Path to the input video file to be decoded
|
|
99
|
+
yuv_file (str): Path where the buffer-based pipeline output will be saved
|
|
100
|
+
use_device_memory (int): If set to 1, output decoded frame is CUDeviceptr wrapped in CUDA Array Interface
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
None: Decoded frames are written to a raw file.
|
|
104
|
+
"""
|
|
105
|
+
print("Starting buffer-based decoding...")
|
|
106
|
+
data_feeder = VideoStreamFeeder(input_file)
|
|
107
|
+
buffer_demuxer = nvc.CreateDemuxer(data_feeder.feed_chunk)
|
|
108
|
+
|
|
109
|
+
buffer_decoder = nvc.CreateDecoder(
|
|
110
|
+
gpuid=gpu_id,
|
|
111
|
+
codec=buffer_demuxer.GetNvCodecId(),
|
|
112
|
+
cudacontext=0,
|
|
113
|
+
cudastream=0,
|
|
114
|
+
usedevicememory=use_device_memory
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
seq_triggered = False
|
|
118
|
+
decoded_frame_size = 0
|
|
119
|
+
raw_frame = None
|
|
120
|
+
|
|
121
|
+
with open(yuv_file, 'wb') as decFile:
|
|
122
|
+
for packet in buffer_demuxer:
|
|
123
|
+
for decoded_frame in buffer_decoder.Decode(packet):
|
|
124
|
+
if not seq_triggered:
|
|
125
|
+
decoded_frame_size = buffer_decoder.GetFrameSize()
|
|
126
|
+
raw_frame = np.ndarray(shape=decoded_frame_size, dtype=np.uint8)
|
|
127
|
+
seq_triggered = True
|
|
128
|
+
|
|
129
|
+
luma_base_addr = decoded_frame.GetPtrToPlane(0)
|
|
130
|
+
if use_device_memory:
|
|
131
|
+
cuda.memcpy_dtoh(raw_frame, luma_base_addr)
|
|
132
|
+
decFile.write(bytearray(raw_frame))
|
|
133
|
+
else:
|
|
134
|
+
new_array = cast_address_to_1d_bytearray(
|
|
135
|
+
base_address=luma_base_addr,
|
|
136
|
+
size=decoded_frame.framesize()
|
|
137
|
+
)
|
|
138
|
+
decFile.write(bytearray(new_array))
|
|
139
|
+
|
|
140
|
+
print(f"Frames are saved successfully in {yuv_file}")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
parser = argparse.ArgumentParser(
|
|
145
|
+
description="Demonstrates video demuxing by processing video data directly from memory buffers."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
parser.add_argument(
|
|
149
|
+
"-i", "--input_file",
|
|
150
|
+
type=Path,
|
|
151
|
+
required=True,
|
|
152
|
+
help="Path to input video file"
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"-g", "--gpu_id",
|
|
156
|
+
type=int,
|
|
157
|
+
default=0,
|
|
158
|
+
help="Check nvidia-smi for available GPUs. Default: 0"
|
|
159
|
+
)
|
|
160
|
+
parser.add_argument(
|
|
161
|
+
"-o", "--yuv_file_path",
|
|
162
|
+
type=Path,
|
|
163
|
+
help="Path to stream based output yuv file. Default: <input_file_name>.yuv"
|
|
164
|
+
)
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
"-d", "--use_device_memory",
|
|
167
|
+
type=int,
|
|
168
|
+
choices=[0, 1],
|
|
169
|
+
default=1,
|
|
170
|
+
help="Decoder output surface is in device memory (1) else in host memory (0). Default is 1."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
args = parser.parse_args()
|
|
174
|
+
|
|
175
|
+
# Set default yuv_file_path if not provided
|
|
176
|
+
if args.yuv_file_path is None:
|
|
177
|
+
input_path = Path(args.input_file)
|
|
178
|
+
args.yuv_file_path = input_path.with_suffix('.yuv')
|
|
179
|
+
|
|
180
|
+
demux_from_byte_array(
|
|
181
|
+
args.input_file.as_posix(),
|
|
182
|
+
args.yuv_file_path.as_posix(),
|
|
183
|
+
args.use_device_memory,
|
|
184
|
+
args.gpu_id
|
|
185
|
+
)
|