PyNvVideoCodec 2.0.0__cp312-cp312-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.cp312-win_amd64.pyd +0 -0
- PyNvVideoCodec/PyNvVideoCodec_130.cp312-win_amd64.pyd +0 -0
- PyNvVideoCodec/VersionCheck.cp312-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,341 @@
|
|
|
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 argparse
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import List, Optional, Tuple
|
|
29
|
+
import numpy as np
|
|
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
|
+
|
|
43
|
+
class CUDAInitializationError(Exception):
|
|
44
|
+
"""Exception raised when CUDA initialization fails."""
|
|
45
|
+
def __init__(self, message: str, gpu_id: int):
|
|
46
|
+
self.message = message
|
|
47
|
+
self.gpu_id = gpu_id
|
|
48
|
+
super().__init__(f"{message} (GPU ID: {gpu_id})")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SubsamplingAndReconfigure:
|
|
52
|
+
"""
|
|
53
|
+
A class that handles video frame subsampling and decoder reconfiguration.
|
|
54
|
+
|
|
55
|
+
This class provides functionality to:
|
|
56
|
+
1. Extract frames from videos at specified target frame rates
|
|
57
|
+
2. Reuse the same decoder instance for multiple video files
|
|
58
|
+
3. Verify decoded frames against golden reference files
|
|
59
|
+
4. Save decoded frames in YUV format
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, create_cuda: bool, gpu_id: int) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Initialize the SubsamplingAndReconfigure instance.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
create_cuda: If True, performs CUDA initialization in the sample app
|
|
68
|
+
gpu_id: ID of the GPU to use
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
CUDAInitializationError: If CUDA initialization fails due to invalid GPU ID
|
|
72
|
+
"""
|
|
73
|
+
self.simple_decoder: Optional[nvc.SimpleDecoder] = None
|
|
74
|
+
self.cuda_ctx: Optional[cuda.Context] = None
|
|
75
|
+
self.cuda_stream: Optional[cuda.Stream] = None
|
|
76
|
+
self.cuda_device: Optional[cuda.Device] = None
|
|
77
|
+
self.gpu_id: int = gpu_id
|
|
78
|
+
|
|
79
|
+
if create_cuda:
|
|
80
|
+
try:
|
|
81
|
+
device_id = self.gpu_id
|
|
82
|
+
self.cuda_device = cuda.Device(device_id)
|
|
83
|
+
self.cuda_ctx = self.cuda_device.retain_primary_context()
|
|
84
|
+
except Exception as e:
|
|
85
|
+
raise CUDAInitializationError(str(e), gpu_id)
|
|
86
|
+
|
|
87
|
+
def _process_frame_data(self, frame, decoded_frame_size: int, use_device_memory: bool) -> np.ndarray:
|
|
88
|
+
"""
|
|
89
|
+
Process frame data from either device or host memory.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
frame: Frame object to process
|
|
93
|
+
decoded_frame_size: Size of the frame in bytes
|
|
94
|
+
use_device_memory: Whether to fetch frame data from device memory
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
numpy.ndarray: Processed frame data
|
|
98
|
+
"""
|
|
99
|
+
reference = np.ndarray(shape=decoded_frame_size, dtype=np.uint8)
|
|
100
|
+
luma_base_addr = frame.GetPtrToPlane(0)
|
|
101
|
+
|
|
102
|
+
if use_device_memory:
|
|
103
|
+
cuda.memcpy_dtoh(reference, luma_base_addr)
|
|
104
|
+
else:
|
|
105
|
+
reference = cast_address_to_1d_bytearray(
|
|
106
|
+
base_address=luma_base_addr,
|
|
107
|
+
size=decoded_frame_size
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return reference
|
|
111
|
+
|
|
112
|
+
def verify(self, input_file_name: str, indices_list: List[int],
|
|
113
|
+
target_frames: List[nvc.DecodedFrame], decoded_frame_size: int,
|
|
114
|
+
use_device_memory: bool) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Verify decoded frames against a golden reference YUV file.
|
|
117
|
+
|
|
118
|
+
The golden file should be generated using the Decode sample application and follow
|
|
119
|
+
the naming convention: <base_name_of_input_file>_golden.yuv
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
input_file_name: Base name to derive the golden file name
|
|
123
|
+
indices_list: List of frame indices to verify
|
|
124
|
+
target_frames: Decoded frame objects
|
|
125
|
+
decoded_frame_size: Size of each decoded frame in bytes
|
|
126
|
+
use_device_memory: Whether to fetch frame data from device memory
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
bool: True if all frames match the golden reference, False otherwise
|
|
130
|
+
"""
|
|
131
|
+
golden_file = f"{input_file_name}_golden.yuv"
|
|
132
|
+
matching = True
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
with open(golden_file, "rb") as testfile:
|
|
136
|
+
for i, frame in zip(indices_list, target_frames):
|
|
137
|
+
testfile.seek(i * decoded_frame_size, 0)
|
|
138
|
+
golden = testfile.read(decoded_frame_size)
|
|
139
|
+
npgolden = np.frombuffer(golden, dtype=np.uint8)
|
|
140
|
+
|
|
141
|
+
reference = self._process_frame_data(frame, decoded_frame_size, use_device_memory)
|
|
142
|
+
|
|
143
|
+
if not (npgolden == reference).all():
|
|
144
|
+
matching = False
|
|
145
|
+
print(f"Reference for frame {i} does not match golden file")
|
|
146
|
+
except FileNotFoundError:
|
|
147
|
+
print(f"Warning: Golden reference file {golden_file} not found")
|
|
148
|
+
matching = False
|
|
149
|
+
|
|
150
|
+
return matching
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _initialize_decoder(self, input_file_path: str, create_cuda: bool,
|
|
154
|
+
use_device_memory: bool) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Initialize or reconfigure the decoder.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
input_file_path: Path to the input video file
|
|
160
|
+
create_cuda: Whether to create CUDA context and stream
|
|
161
|
+
use_device_memory: Whether to use device memory for frame processing
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
if self.simple_decoder is None:
|
|
165
|
+
if create_cuda:
|
|
166
|
+
print("Initializing CUDA in the sample application...")
|
|
167
|
+
self.cuda_stream = cuda.Stream()
|
|
168
|
+
self.simple_decoder = nvc.SimpleDecoder(
|
|
169
|
+
input_file_path,
|
|
170
|
+
gpu_id=self.gpu_id,
|
|
171
|
+
cuda_context=self.cuda_ctx.handle,
|
|
172
|
+
cuda_stream=self.cuda_stream.handle,
|
|
173
|
+
use_device_memory=use_device_memory
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
print("Using default CUDA initialization...")
|
|
177
|
+
self.simple_decoder = nvc.SimpleDecoder(
|
|
178
|
+
input_file_path,
|
|
179
|
+
gpu_id=self.gpu_id,
|
|
180
|
+
use_device_memory=use_device_memory
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
print("Reconfiguring existing decoder...")
|
|
184
|
+
self.simple_decoder.reconfigure_decoder(input_file_path)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print(f"Error initializing decoder: {e}")
|
|
187
|
+
|
|
188
|
+
def sub_sampling(self, input_file_path: str, target_fps: float, create_cuda: bool,
|
|
189
|
+
use_device_memory: bool, verify_with_golden_yuv: int = 0) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Extract frames from a video at a specified target frame rate.
|
|
192
|
+
|
|
193
|
+
This method calculates which frames to extract to achieve the desired frame rate,
|
|
194
|
+
creates batches of frame indices (up to 8 frames per batch) for efficient processing,
|
|
195
|
+
and optionally verifies the output against a golden reference file.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
input_file_path: Path to the input video file
|
|
199
|
+
target_fps: Desired output frame rate in frames per second
|
|
200
|
+
create_cuda: If True, performs CUDA initialization in the sample app
|
|
201
|
+
use_device_memory: Whether to use device memory for frame processing
|
|
202
|
+
verify_with_golden_yuv: If 1, verifies output against golden reference file
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
If video_fps = 30, target_fps = 10, and total_frames = 100:
|
|
206
|
+
- Will extract every 3rd frame (30/10 = 3)
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
if create_cuda and self.cuda_ctx is not None:
|
|
210
|
+
self.cuda_ctx.push()
|
|
211
|
+
|
|
212
|
+
self._initialize_decoder(input_file_path, create_cuda, use_device_memory)
|
|
213
|
+
|
|
214
|
+
# Get video metadata
|
|
215
|
+
metadata = self.simple_decoder.get_stream_metadata()
|
|
216
|
+
total_frames = metadata.num_frames
|
|
217
|
+
video_fps = metadata.average_fps
|
|
218
|
+
|
|
219
|
+
print(f"Video FPS = {video_fps}, Total Frames = {total_frames}")
|
|
220
|
+
|
|
221
|
+
# Calculate frame indices for target FPS
|
|
222
|
+
step = int(video_fps / target_fps)
|
|
223
|
+
step_list = list(range(0, total_frames, step))
|
|
224
|
+
|
|
225
|
+
# Split into batches of up to 8 frames
|
|
226
|
+
step_list_of_lists = [step_list[i:i + 8] for i in range(0, len(step_list), 8)]
|
|
227
|
+
|
|
228
|
+
input_file_name = os.path.splitext(os.path.basename(input_file_path))[0]
|
|
229
|
+
matching = True
|
|
230
|
+
|
|
231
|
+
# Open output file once for all batches
|
|
232
|
+
dec_file_path = f"{str(input_file_name)}.yuv"
|
|
233
|
+
with open(dec_file_path, "wb") as decFile:
|
|
234
|
+
# Process each batch
|
|
235
|
+
for batch_indices in step_list_of_lists:
|
|
236
|
+
batch_frames = self.simple_decoder.get_batch_frames_by_index(batch_indices)
|
|
237
|
+
|
|
238
|
+
if verify_with_golden_yuv:
|
|
239
|
+
matching = (matching and self.verify(
|
|
240
|
+
input_file_name,
|
|
241
|
+
batch_indices,
|
|
242
|
+
batch_frames,
|
|
243
|
+
batch_frames[0].framesize(),
|
|
244
|
+
use_device_memory
|
|
245
|
+
))
|
|
246
|
+
|
|
247
|
+
# Save frames to the already open file
|
|
248
|
+
for frame in batch_frames:
|
|
249
|
+
decoded_frame_size = frame.framesize()
|
|
250
|
+
reference = self._process_frame_data(frame, decoded_frame_size, use_device_memory)
|
|
251
|
+
decFile.write(bytearray(reference))
|
|
252
|
+
|
|
253
|
+
if verify_with_golden_yuv and matching:
|
|
254
|
+
print(f"All frames match golden reference for {input_file_name}.yuv")
|
|
255
|
+
|
|
256
|
+
if create_cuda and self.cuda_ctx is not None:
|
|
257
|
+
self.cuda_ctx.pop()
|
|
258
|
+
|
|
259
|
+
print(f"Frames saved successfully in {input_file_name}.yuv\n")
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
print(f"An unexpected error occurred: {e}")
|
|
263
|
+
if create_cuda and self.cuda_ctx is not None:
|
|
264
|
+
self.cuda_ctx.pop()
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
if __name__ == "__main__":
|
|
268
|
+
parser = argparse.ArgumentParser(
|
|
269
|
+
description="This sample application demonstrates reusing the same decoder instance "
|
|
270
|
+
"for subsampling multiple video files using Simple Decoder."
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
parser.add_argument(
|
|
274
|
+
"-i", "--input_file_list",
|
|
275
|
+
type=Path,
|
|
276
|
+
default=join(current_dir, "fileLists.txt"),
|
|
277
|
+
help="Text file containing video file paths, one per line. "
|
|
278
|
+
"Each line should contain the absolute or relative path to a video file. "
|
|
279
|
+
"Default: fileLists.txt in the same directory"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
parser.add_argument(
|
|
283
|
+
"-fps", "--target_fps",
|
|
284
|
+
default=2,
|
|
285
|
+
type=int,
|
|
286
|
+
help="Desired output frame rate in frames per second. Default is 2."
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
parser.add_argument(
|
|
290
|
+
"-c", "--create_cuda",
|
|
291
|
+
type=int,
|
|
292
|
+
choices=[0, 1],
|
|
293
|
+
default=1,
|
|
294
|
+
help="Set to 1 to enable CUDA stream and context, 0 for using default initializations. Default is 1."
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
parser.add_argument(
|
|
298
|
+
"-d", "--use_device_memory",
|
|
299
|
+
choices=[0, 1],
|
|
300
|
+
default=1,
|
|
301
|
+
type=int,
|
|
302
|
+
help="Decoder output surface is in device memory (1) else in host memory (0). Default is 1."
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
parser.add_argument(
|
|
306
|
+
"-v", "--verify_with_golden_yuv",
|
|
307
|
+
choices=[0, 1],
|
|
308
|
+
default=0,
|
|
309
|
+
type=int,
|
|
310
|
+
help="Set to 1 to enable verification against a golden YUV file; default is 0."
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
parser.add_argument(
|
|
314
|
+
"-g", "--gpu_id",
|
|
315
|
+
type=int,
|
|
316
|
+
default=0,
|
|
317
|
+
help="GPU ID to use. Default is 0."
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
args = parser.parse_args()
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
simple_dec_reconfigure = SubsamplingAndReconfigure(args.create_cuda, args.gpu_id)
|
|
324
|
+
|
|
325
|
+
with open(args.input_file_list, 'r') as file:
|
|
326
|
+
for line in file:
|
|
327
|
+
if line.strip():
|
|
328
|
+
input_file_path = line.strip()
|
|
329
|
+
simple_dec_reconfigure.sub_sampling(
|
|
330
|
+
input_file_path,
|
|
331
|
+
args.target_fps,
|
|
332
|
+
args.create_cuda,
|
|
333
|
+
args.use_device_memory,
|
|
334
|
+
args.verify_with_golden_yuv
|
|
335
|
+
)
|
|
336
|
+
except CUDAInitializationError as e:
|
|
337
|
+
print(f"Error initializing CUDA: {e}")
|
|
338
|
+
sys.exit(1)
|
|
339
|
+
except Exception as e:
|
|
340
|
+
print(f"An unexpected error occurred: {e}")
|
|
341
|
+
sys.exit(1)
|
samples/Utils.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
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 logging
|
|
26
|
+
|
|
27
|
+
from struct import pack
|
|
28
|
+
import sys
|
|
29
|
+
import os
|
|
30
|
+
import argparse
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from enum import Enum
|
|
33
|
+
|
|
34
|
+
import pycuda.driver as cuda
|
|
35
|
+
import PyNvVideoCodec as nvc
|
|
36
|
+
import numpy as np
|
|
37
|
+
from pycuda.compiler import SourceModule
|
|
38
|
+
import ctypes as C
|
|
39
|
+
|
|
40
|
+
import pycuda.autoinit as context
|
|
41
|
+
import json
|
|
42
|
+
|
|
43
|
+
from contextlib import contextmanager
|
|
44
|
+
|
|
45
|
+
import io
|
|
46
|
+
import tempfile
|
|
47
|
+
|
|
48
|
+
SERVICE_LOGGING_FORMAT = (
|
|
49
|
+
"[{filename:s}][{funcName:s}:{lineno:d}]" + "[{levelname:s}] {message:s}"
|
|
50
|
+
)
|
|
51
|
+
SERVICE_LOGGING_STREAM = sys.stdout
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_logger(logger_name, log_level="info"):
|
|
55
|
+
SERVICE_LOGGING_LEVEL = getattr(logging, log_level.upper(), None)
|
|
56
|
+
|
|
57
|
+
logger = logging.getLogger(logger_name)
|
|
58
|
+
logger.setLevel(SERVICE_LOGGING_LEVEL)
|
|
59
|
+
ch = logging.StreamHandler(SERVICE_LOGGING_STREAM)
|
|
60
|
+
formatter = logging.Formatter(SERVICE_LOGGING_FORMAT, style="{")
|
|
61
|
+
ch.setFormatter(formatter)
|
|
62
|
+
ch.setLevel(SERVICE_LOGGING_LEVEL)
|
|
63
|
+
logger.addHandler(ch)
|
|
64
|
+
logger.propagate = False
|
|
65
|
+
|
|
66
|
+
return logger
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
logger = get_logger(__file__)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def cast_address_to_1d_bytearray(base_address, size):
|
|
73
|
+
return np.ctypeslib.as_array(C.cast(base_address, C.POINTER(C.c_uint8)),
|
|
74
|
+
shape=(size,))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
from contextlib import contextmanager
|
|
78
|
+
import ctypes
|
|
79
|
+
import io
|
|
80
|
+
import os, sys
|
|
81
|
+
import tempfile
|
|
82
|
+
|
|
83
|
+
class AppCAI:
|
|
84
|
+
def __init__(self, shape, stride, typestr, gpualloc):
|
|
85
|
+
shape_int = tuple([int(x) for x in shape])
|
|
86
|
+
stride_int = tuple([int(x) for x in stride])
|
|
87
|
+
self.__cuda_array_interface__ = {"shape": shape_int, "strides": stride_int, "data": (int(gpualloc), False),
|
|
88
|
+
"typestr": typestr, "version": 3}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class AppFrame:
|
|
92
|
+
def __init__(self, width, height, format):
|
|
93
|
+
if format == "NV12":
|
|
94
|
+
nv12_frame_size = int(width * height * 3 / 2)
|
|
95
|
+
self.gpuAlloc = cuda.mem_alloc(nv12_frame_size)
|
|
96
|
+
self.cai = []
|
|
97
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", self.gpuAlloc))
|
|
98
|
+
chroma_alloc = int(self.gpuAlloc) + width * height
|
|
99
|
+
self.cai.append(AppCAI((int(height / 2), int(width / 2), 2), (width, 2, 1), "|u1", chroma_alloc))
|
|
100
|
+
self.frameSize = nv12_frame_size
|
|
101
|
+
if format == "ARGB" or format == "ABGR":
|
|
102
|
+
self.frameSize = width * height * 4
|
|
103
|
+
self.gpuAlloc = cuda.mem_alloc(self.frameSize)
|
|
104
|
+
self.cai = AppCAI((height, width, 4), (4 * width, 4, 1), "|u1", self.gpuAlloc)
|
|
105
|
+
if format == "YUV444":
|
|
106
|
+
self.frameSize = width * height * 3
|
|
107
|
+
self.gpuAlloc = cuda.mem_alloc(self.frameSize)
|
|
108
|
+
self.cai = []
|
|
109
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", self.gpuAlloc))
|
|
110
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", int(self.gpuAlloc) + width * height))
|
|
111
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", int(self.gpuAlloc) + 2 * width * height))
|
|
112
|
+
if format == "YUV420":
|
|
113
|
+
self.frameSize = int(width * height * 3 / 2)
|
|
114
|
+
self.gpuAlloc = cuda.mem_alloc(self.frameSize)
|
|
115
|
+
self.cai = []
|
|
116
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", self.gpuAlloc))
|
|
117
|
+
self.cai.append(
|
|
118
|
+
AppCAI((height / 2, width / 2, 1), (width / 2, 1, 1), "|u1", int(self.gpuAlloc) + width * height))
|
|
119
|
+
self.cai.append(AppCAI((height / 2, width / 2, 1), (width / 2, 1, 1), "|u1",
|
|
120
|
+
int(self.gpuAlloc) + width * height + width / 2 * height / 2))
|
|
121
|
+
if format == "P010" or format == "YUV420_10BIT":
|
|
122
|
+
self.frameSize = int(width * height * 3 / 2 * 2)
|
|
123
|
+
self.gpuAlloc = cuda.mem_alloc(self.frameSize)
|
|
124
|
+
self.cai = []
|
|
125
|
+
self.cai.append(AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", self.gpuAlloc))
|
|
126
|
+
chroma_alloc = int(self.gpuAlloc) + width * height * 2
|
|
127
|
+
self.cai.append(AppCAI((int(height / 2), int(width / 2), 2), (width*2, 2, 1), "|u2", chroma_alloc))
|
|
128
|
+
if format == "P016" or format == "YUV444_16BIT":
|
|
129
|
+
self.frameSize = int(width * height * 3 * 2)
|
|
130
|
+
self.gpuAlloc = cuda.mem_alloc(self.frameSize)
|
|
131
|
+
self.cai = []
|
|
132
|
+
self.cai.append(AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", self.gpuAlloc))
|
|
133
|
+
self.cai.append(
|
|
134
|
+
AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", int(self.gpuAlloc) + width * height * 2))
|
|
135
|
+
self.cai.append(
|
|
136
|
+
AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", int(self.gpuAlloc) + width * height * 4))
|
|
137
|
+
|
|
138
|
+
if format == "NV16":
|
|
139
|
+
self.frameSize = width * height * 2
|
|
140
|
+
self.gpuAlloc = cuda.mem_alloc(self.frameSize)
|
|
141
|
+
self.cai = []
|
|
142
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", self.gpuAlloc))
|
|
143
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1 ), "|u1", int(self.gpuAlloc) + width * height))
|
|
144
|
+
|
|
145
|
+
if format == "P210":
|
|
146
|
+
self.frameSize = width * height * 2 * 2
|
|
147
|
+
self.gpuAlloc = cuda.mem_alloc(self.frameSize)
|
|
148
|
+
self.cai = []
|
|
149
|
+
self.cai.append(AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", self.gpuAlloc))
|
|
150
|
+
self.cai.append(AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", int(self.gpuAlloc) + width * height * 2))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def cuda(self):
|
|
155
|
+
return self.cai
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class AppFramePerf:
|
|
159
|
+
def __init__(self, width, height, format, dataptr, frame_idx):
|
|
160
|
+
if format == "NV12":
|
|
161
|
+
nv12_frame_size = int(width * height * 3 / 2)
|
|
162
|
+
self.gpuAlloc = int(dataptr) + (frame_idx * nv12_frame_size)
|
|
163
|
+
self.cai = []
|
|
164
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", self.gpuAlloc))
|
|
165
|
+
chroma_alloc = int(self.gpuAlloc) + width * height
|
|
166
|
+
self.cai.append(AppCAI((int(height / 2), int(width / 2), 2), (width, 2, 1), "|u1", chroma_alloc))
|
|
167
|
+
self.frameSize = nv12_frame_size
|
|
168
|
+
if format == "ARGB" or format == "ABGR":
|
|
169
|
+
self.frameSize = width * height * 4
|
|
170
|
+
self.gpuAlloc = int(dataptr) + (frame_idx * self.frameSize)
|
|
171
|
+
self.cai = AppCAI((height, width, 4), (4 * width, 4, 1), "|u1", self.gpuAlloc)
|
|
172
|
+
if format == "YUV444":
|
|
173
|
+
self.frameSize = width * height * 3
|
|
174
|
+
self.gpuAlloc = int(dataptr) + (frame_idx * self.frameSize)
|
|
175
|
+
self.cai = []
|
|
176
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", self.gpuAlloc))
|
|
177
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", int(self.gpuAlloc) + width * height))
|
|
178
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", int(self.gpuAlloc) + 2 * width * height))
|
|
179
|
+
if format == "YUV420":
|
|
180
|
+
self.frameSize = int(width * height * 3 / 2)
|
|
181
|
+
self.gpuAlloc = int(dataptr) + (frame_idx * self.frameSize)
|
|
182
|
+
self.cai = []
|
|
183
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", self.gpuAlloc))
|
|
184
|
+
self.cai.append(
|
|
185
|
+
AppCAI((height / 2, width / 2, 1), (width / 2, 1, 1), "|u1", int(self.gpuAlloc) + width * height))
|
|
186
|
+
self.cai.append(AppCAI((height / 2, width / 2, 1), (width / 2, 1, 1), "|u1",
|
|
187
|
+
int(self.gpuAlloc) + width * height + width / 2 * height / 2))
|
|
188
|
+
if format == "P010" or format == "YUV420_10BIT":
|
|
189
|
+
self.frameSize = int(width * height * 3 / 2 * 2)
|
|
190
|
+
self.gpuAlloc = int(dataptr) + (frame_idx * self.frameSize)
|
|
191
|
+
self.cai = []
|
|
192
|
+
self.cai.append(AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", self.gpuAlloc))
|
|
193
|
+
chroma_alloc = int(self.gpuAlloc) + width * height * 2
|
|
194
|
+
self.cai.append(AppCAI((int(height / 2), int(width / 2), 2), (width*2, 2, 1), "|u2", chroma_alloc))
|
|
195
|
+
if format == "P016" or format == "YUV444_16BIT":
|
|
196
|
+
self.frameSize = int(width * height * 3 * 2)
|
|
197
|
+
self.gpuAlloc = int(dataptr) + (frame_idx * self.frameSize)
|
|
198
|
+
self.cai = []
|
|
199
|
+
self.cai.append(AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", self.gpuAlloc))
|
|
200
|
+
self.cai.append(
|
|
201
|
+
AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", int(self.gpuAlloc) + width * height * 2))
|
|
202
|
+
self.cai.append(
|
|
203
|
+
AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", int(self.gpuAlloc) + width * height * 4))
|
|
204
|
+
if format == "NV16":
|
|
205
|
+
self.frameSize = width * height * 2
|
|
206
|
+
self.gpuAlloc = int(dataptr) + (frame_idx * self.frameSize)
|
|
207
|
+
self.cai = []
|
|
208
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", self.gpuAlloc))
|
|
209
|
+
self.cai.append(AppCAI((height, width, 1), (width, 1, 1), "|u1", int(self.gpuAlloc) + width * height))
|
|
210
|
+
if format == "P210":
|
|
211
|
+
self.frameSize = width * height * 2 * 2
|
|
212
|
+
self.gpuAlloc = int(dataptr) + (frame_idx * self.frameSize)
|
|
213
|
+
self.cai = []
|
|
214
|
+
self.cai.append(AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", self.gpuAlloc))
|
|
215
|
+
self.cai.append(AppCAI((height, width, 1), (width * 2, 2, 1), "|u2", int(self.gpuAlloc) + width * height * 2))
|
|
216
|
+
|
|
217
|
+
def cuda(self):
|
|
218
|
+
return self.cai
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def FetchGPUFrame(input_frame_list, GetCPUFrameFunc, num_frames):
|
|
222
|
+
for i in range(num_frames):
|
|
223
|
+
n = i % len(input_frame_list)
|
|
224
|
+
raw_frame = GetCPUFrameFunc()
|
|
225
|
+
if not raw_frame.size:
|
|
226
|
+
return
|
|
227
|
+
cuda.memcpy_htod(input_frame_list[n].gpuAlloc, raw_frame)
|
|
228
|
+
cuda.Context.synchronize()
|
|
229
|
+
yield input_frame_list[n]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def FetchCPUFrame(dec_file, frame_size):
|
|
234
|
+
def InnerFunc():
|
|
235
|
+
return np.fromfile(dec_file, np.uint8, count=frame_size)
|
|
236
|
+
|
|
237
|
+
return InnerFunc
|
|
238
|
+
|
|
239
|
+
def GetChromaFormat(demuxer):
|
|
240
|
+
strFormat = "NV12"
|
|
241
|
+
|
|
242
|
+
bitdepth = demuxer.BitDepth()
|
|
243
|
+
chromaformat = demuxer.ChromaFormat()
|
|
244
|
+
|
|
245
|
+
#print (" bitdepth : " + str(bitdepth))
|
|
246
|
+
#print (" chromaformat : " + str(chromaformat))
|
|
247
|
+
|
|
248
|
+
if str(chromaformat) == str("cudaVideoChromaFormat.444"):
|
|
249
|
+
if bitdepth == 10:
|
|
250
|
+
strFormat = "YUV444_10BIT"
|
|
251
|
+
elif bitdepth == 16:
|
|
252
|
+
strFormat = "YUV444_16BIT"
|
|
253
|
+
elif bitdepth == 12:
|
|
254
|
+
strFormat = "YUV444_16BIT"
|
|
255
|
+
print ("Unsupported bitdepth for YUV444 12BIT. It will be treated as YUV444_16BIT" )
|
|
256
|
+
elif bitdepth == 8:
|
|
257
|
+
strFormat = "YUV444"
|
|
258
|
+
elif str(chromaformat) == str("cudaVideoChromaFormat.420"):
|
|
259
|
+
if bitdepth == 8:
|
|
260
|
+
strFormat = "NV12"
|
|
261
|
+
elif bitdepth == 10:
|
|
262
|
+
strFormat = "YUV420_10BIT"
|
|
263
|
+
elif bitdepth == 16:
|
|
264
|
+
strFormat = "P016"
|
|
265
|
+
elif str(chromaformat) == str("cudaVideoChromaFormat.422"):
|
|
266
|
+
if bitdepth == 10:
|
|
267
|
+
strFormat = "YUV422_10BIT"
|
|
268
|
+
elif bitdepth == 16:
|
|
269
|
+
strFormat = "YUV422_16BIT"
|
|
270
|
+
elif bitdepth == 8:
|
|
271
|
+
strFormat = "YUV422"
|
|
272
|
+
|
|
273
|
+
#print (" strFormat : " + str(strFormat))
|
|
274
|
+
|
|
275
|
+
return strFormat
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def GetVideoCodec(demuxer):
|
|
279
|
+
codec=str(demuxer.GetNvCodecId())
|
|
280
|
+
#print (" codec : " + str(codec))
|
|
281
|
+
|
|
282
|
+
# Common prefix to remove
|
|
283
|
+
common_prefix = "cudaVideoCodec."
|
|
284
|
+
|
|
285
|
+
strCodec = codec.removeprefix(common_prefix)
|
|
286
|
+
|
|
287
|
+
#print (" codec : " + str(strCodec))
|
|
288
|
+
return str(strCodec)
|
|
289
|
+
|
|
290
|
+
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"gpu_id": 0,
|
|
3
|
+
"preset" : "P3",
|
|
4
|
+
"tuning_info" : "high_quality",
|
|
5
|
+
"rc": "vbr",
|
|
6
|
+
"fps" : 30,
|
|
7
|
+
"gop" : 250,
|
|
8
|
+
"bf" : 3,
|
|
9
|
+
"bitrate" : "10M",
|
|
10
|
+
"maxbitrate" : 0,
|
|
11
|
+
"vbvinit" : 0,
|
|
12
|
+
"vbvbufsize" : 0,
|
|
13
|
+
"qmin" : "0,0,0",
|
|
14
|
+
"qmax" : "0,0,0",
|
|
15
|
+
"initqp" : "0,0,0"
|
|
16
|
+
}
|