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/EncodePerf.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
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
|
+
from pathlib import Path
|
|
26
|
+
import pycuda.driver as cuda
|
|
27
|
+
import PyNvVideoCodec as nvc
|
|
28
|
+
import time
|
|
29
|
+
import mmap
|
|
30
|
+
import json
|
|
31
|
+
import threading
|
|
32
|
+
import numpy as np
|
|
33
|
+
import gc
|
|
34
|
+
import sys
|
|
35
|
+
import atexit
|
|
36
|
+
import os
|
|
37
|
+
from os.path import join, dirname, abspath
|
|
38
|
+
from Utils import AppFramePerf
|
|
39
|
+
|
|
40
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
41
|
+
current_dir = dirname(abspath(__file__))
|
|
42
|
+
parent_dir = dirname(current_dir)
|
|
43
|
+
if parent_dir not in sys.path:
|
|
44
|
+
sys.path.append(parent_dir)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Global variables for tracking performance
|
|
48
|
+
total_frames = 0
|
|
49
|
+
|
|
50
|
+
# Global variables to track resources for cleanup
|
|
51
|
+
g_devicedata = None
|
|
52
|
+
g_mmap = None
|
|
53
|
+
g_context = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cleanup_resources():
|
|
57
|
+
"""
|
|
58
|
+
Clean up global resources including memory map, GPU memory, and CUDA context.
|
|
59
|
+
|
|
60
|
+
This function is registered with atexit to ensure proper cleanup of resources
|
|
61
|
+
when the program exits.
|
|
62
|
+
"""
|
|
63
|
+
global g_devicedata, g_mmap, g_context
|
|
64
|
+
|
|
65
|
+
# Force garbage collection first
|
|
66
|
+
gc.collect()
|
|
67
|
+
|
|
68
|
+
# Close memory map
|
|
69
|
+
if g_mmap:
|
|
70
|
+
try:
|
|
71
|
+
g_mmap.close()
|
|
72
|
+
g_mmap = None
|
|
73
|
+
except:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# Free GPU memory
|
|
77
|
+
if g_devicedata:
|
|
78
|
+
try:
|
|
79
|
+
g_devicedata.free()
|
|
80
|
+
g_devicedata = None
|
|
81
|
+
except:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
# Clean up CUDA context
|
|
85
|
+
if g_context:
|
|
86
|
+
try:
|
|
87
|
+
g_context.detach()
|
|
88
|
+
g_context = None
|
|
89
|
+
except:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Register the cleanup function to run at exit
|
|
94
|
+
atexit.register(cleanup_resources)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def encode(thread_id, dec_file_path, encoder, devicedata, framesize, width, height, fmt, frame_count=1000):
|
|
98
|
+
"""
|
|
99
|
+
Thread function that encodes frames and updates the global counter.
|
|
100
|
+
|
|
101
|
+
Parameters:
|
|
102
|
+
thread_id (int): ID of the thread
|
|
103
|
+
dec_file_path (str): Path to the input file
|
|
104
|
+
encoder: NVENC encoder instance
|
|
105
|
+
devicedata: CUDA device memory handle
|
|
106
|
+
framesize (int): Size of each frame in bytes
|
|
107
|
+
width (int): Width of the frame
|
|
108
|
+
height (int): Height of the frame
|
|
109
|
+
fmt (str): Surface format string in uppercase (e.g., "NV12")
|
|
110
|
+
frame_count (int, optional): Number of frames to encode. If None, 0, or negative, encode all frames from input.
|
|
111
|
+
"""
|
|
112
|
+
global total_frames
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
print(f"Starting encode thread {thread_id}")
|
|
116
|
+
|
|
117
|
+
# Calculate total available frames from file size
|
|
118
|
+
file_size = os.path.getsize(dec_file_path)
|
|
119
|
+
available_frames = file_size // framesize
|
|
120
|
+
|
|
121
|
+
if frame_count is None or frame_count == 0:
|
|
122
|
+
print(f"Thread {thread_id} will process all available frames")
|
|
123
|
+
frame_count = available_frames
|
|
124
|
+
elif frame_count < 0 or frame_count > available_frames:
|
|
125
|
+
print(f"Warning: Invalid frame count {frame_count}. Thread {thread_id} will process all available frames.")
|
|
126
|
+
frame_count = available_frames
|
|
127
|
+
|
|
128
|
+
# Encode frames
|
|
129
|
+
for i in range(frame_count):
|
|
130
|
+
# Create a new frame object for each frame
|
|
131
|
+
input_gpu_frame = AppFramePerf(width, height, fmt, devicedata, i)
|
|
132
|
+
|
|
133
|
+
# Encode the frame
|
|
134
|
+
encoder.Encode(input_gpu_frame)
|
|
135
|
+
|
|
136
|
+
# Flush encoder
|
|
137
|
+
encoder.EndEncode()
|
|
138
|
+
|
|
139
|
+
print(f"Thread {thread_id} encoded {frame_count} frames")
|
|
140
|
+
|
|
141
|
+
# Update global counter
|
|
142
|
+
with threading.Lock():
|
|
143
|
+
total_frames += frame_count
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print(f"Thread {thread_id}: An unexpected error occurred: {e}")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def GetFrameSize(width, height, surface_format):
|
|
150
|
+
"""
|
|
151
|
+
Calculate the size of a frame in bytes based on its dimensions and format.
|
|
152
|
+
|
|
153
|
+
Parameters:
|
|
154
|
+
width (int): Width of the frame
|
|
155
|
+
height (int): Height of the frame
|
|
156
|
+
surface_format (str): Format of the surface (e.g., "NV12", "ARGB", "YUV444")
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
int: Size of the frame in bytes
|
|
160
|
+
"""
|
|
161
|
+
if surface_format in ("ARGB", "ABGR"):
|
|
162
|
+
return width * height * 4
|
|
163
|
+
elif surface_format == "YUV444":
|
|
164
|
+
return width * height * 3
|
|
165
|
+
elif surface_format == "YUV420":
|
|
166
|
+
return int(width * height * 3 / 2)
|
|
167
|
+
elif surface_format == "P010":
|
|
168
|
+
return int(width * height * 3 / 2 * 2)
|
|
169
|
+
elif surface_format == "YUV444_16BIT":
|
|
170
|
+
return int(width * height * 3 * 2)
|
|
171
|
+
elif surface_format == "NV16":
|
|
172
|
+
return width * height * 2
|
|
173
|
+
elif surface_format == "P210":
|
|
174
|
+
return width * height * 2 * 2
|
|
175
|
+
else:
|
|
176
|
+
return int(width * height * 3 / 2)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def run_parallel_encode(num_threads, gpu_id, dec_file_path, width, height, fmt, config_params, frame_count):
|
|
180
|
+
"""
|
|
181
|
+
Run encoding test with multiple threads.
|
|
182
|
+
|
|
183
|
+
Parameters:
|
|
184
|
+
num_threads (int): Number of parallel threads to use
|
|
185
|
+
gpu_id (int): Ordinal of GPU to use
|
|
186
|
+
dec_file_path (str): Path to the input file
|
|
187
|
+
width (int): Width of the frame
|
|
188
|
+
height (int): Height of the frame
|
|
189
|
+
fmt (str): Surface format string in uppercase (e.g., "NV12")
|
|
190
|
+
config_params (dict): Key-value pairs providing fine-grained control on encoding
|
|
191
|
+
frame_count (int): Number of frames to encode. If 0, encodes all frames from input.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
None: Prints performance metrics to console
|
|
195
|
+
"""
|
|
196
|
+
print(f"Initializing {num_threads} encode threads")
|
|
197
|
+
global g_devicedata, g_mmap, g_context, total_frames
|
|
198
|
+
|
|
199
|
+
# Reset global counter
|
|
200
|
+
total_frames = 0
|
|
201
|
+
|
|
202
|
+
g_context = None
|
|
203
|
+
try:
|
|
204
|
+
# Initialize CUDA
|
|
205
|
+
cuda.init()
|
|
206
|
+
cuda_device = cuda.Device(gpu_id)
|
|
207
|
+
|
|
208
|
+
# Create a CUDA context
|
|
209
|
+
g_context = cuda_device.make_context()
|
|
210
|
+
|
|
211
|
+
# Create a CUDA stream
|
|
212
|
+
shared_stream = cuda.Stream()
|
|
213
|
+
# Open input file and prepare data
|
|
214
|
+
with open(dec_file_path, "rb") as dec_file:
|
|
215
|
+
# Calculate frame size based on format
|
|
216
|
+
framesize = GetFrameSize(width, height, fmt)
|
|
217
|
+
|
|
218
|
+
# Read data into memory and copy to GPU
|
|
219
|
+
if sys.platform == 'win32':
|
|
220
|
+
g_mmap = mmap.mmap(dec_file.fileno(), 0, access=mmap.ACCESS_READ)
|
|
221
|
+
else:
|
|
222
|
+
g_mmap = mmap.mmap(dec_file.fileno(), 0, prot=mmap.PROT_READ)
|
|
223
|
+
hostdata = g_mmap.read(g_mmap.size())
|
|
224
|
+
g_devicedata = cuda.mem_alloc(g_mmap.size())
|
|
225
|
+
cuda.memcpy_htod(g_devicedata, hostdata)
|
|
226
|
+
|
|
227
|
+
# Create encoders for all threads
|
|
228
|
+
encoders = []
|
|
229
|
+
for i in range(num_threads):
|
|
230
|
+
# Create a copy of config_params for each encoder
|
|
231
|
+
config_copy = config_params.copy()
|
|
232
|
+
config_copy["gpu_id"] = gpu_id
|
|
233
|
+
# Create encoder
|
|
234
|
+
encoder = nvc.CreateEncoder(
|
|
235
|
+
width, height, fmt, False,
|
|
236
|
+
cudacontext=g_context.handle,
|
|
237
|
+
cudastream=shared_stream.handle,
|
|
238
|
+
**config_copy
|
|
239
|
+
)
|
|
240
|
+
encoders.append(encoder)
|
|
241
|
+
|
|
242
|
+
# Start timing before encoding
|
|
243
|
+
begin = time.perf_counter()
|
|
244
|
+
|
|
245
|
+
# Create and start threads
|
|
246
|
+
threads = []
|
|
247
|
+
for i in range(num_threads):
|
|
248
|
+
t = threading.Thread(
|
|
249
|
+
target=encode,
|
|
250
|
+
args=(
|
|
251
|
+
i,
|
|
252
|
+
dec_file_path,
|
|
253
|
+
encoders[i],
|
|
254
|
+
g_devicedata,
|
|
255
|
+
framesize,
|
|
256
|
+
width,
|
|
257
|
+
height,
|
|
258
|
+
fmt,
|
|
259
|
+
frame_count
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
threads.append(t)
|
|
263
|
+
t.start()
|
|
264
|
+
|
|
265
|
+
# Wait for all threads to complete
|
|
266
|
+
for t in threads:
|
|
267
|
+
t.join()
|
|
268
|
+
|
|
269
|
+
# Stop timing after all threads complete
|
|
270
|
+
end = time.perf_counter()
|
|
271
|
+
|
|
272
|
+
duration = end - begin # Time in seconds
|
|
273
|
+
|
|
274
|
+
# Print summary
|
|
275
|
+
print("\n--- Performance Summary ---")
|
|
276
|
+
print(f"Number of threads: {num_threads}")
|
|
277
|
+
print(f"Total frames encoded: {total_frames}")
|
|
278
|
+
if duration > 0:
|
|
279
|
+
fps = total_frames / duration
|
|
280
|
+
print(f"Duration: {duration:.2f} seconds")
|
|
281
|
+
print(f"Total FPS: {fps:.2f}")
|
|
282
|
+
print(f"Average FPS per thread: {fps / num_threads:.2f}")
|
|
283
|
+
|
|
284
|
+
# Clear encoder references
|
|
285
|
+
for encoder in encoders:
|
|
286
|
+
del encoder
|
|
287
|
+
encoders.clear()
|
|
288
|
+
|
|
289
|
+
except Exception as e:
|
|
290
|
+
print(f"An error occurred: {e}")
|
|
291
|
+
|
|
292
|
+
finally:
|
|
293
|
+
# Pop the context
|
|
294
|
+
if g_context is not None:
|
|
295
|
+
g_context.pop()
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
if __name__ == "__main__":
|
|
299
|
+
parser = argparse.ArgumentParser(
|
|
300
|
+
description="This sample application measures encoding performance in FPS with multiple threads."
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
parser.add_argument(
|
|
304
|
+
"-g", "--gpu_id",
|
|
305
|
+
type=int,
|
|
306
|
+
default=0,
|
|
307
|
+
help="Check nvidia-smi for available GPUs. Default: 0"
|
|
308
|
+
)
|
|
309
|
+
parser.add_argument(
|
|
310
|
+
"-i", "--raw_file_path",
|
|
311
|
+
type=Path,
|
|
312
|
+
required=True,
|
|
313
|
+
help="Raw video file (read from)"
|
|
314
|
+
)
|
|
315
|
+
parser.add_argument(
|
|
316
|
+
"-s", "--size",
|
|
317
|
+
type=str,
|
|
318
|
+
required=True,
|
|
319
|
+
help="WidthxHeight of raw frame (e.g., 1920x1080)"
|
|
320
|
+
)
|
|
321
|
+
parser.add_argument(
|
|
322
|
+
"-if", "--format",
|
|
323
|
+
type=str,
|
|
324
|
+
default="NV12",
|
|
325
|
+
help="Format of input file. Default is NV12"
|
|
326
|
+
)
|
|
327
|
+
parser.add_argument(
|
|
328
|
+
"-c", "--codec",
|
|
329
|
+
type=str,
|
|
330
|
+
help="Video codec (HEVC, H264, AV1). If not specified, uses the codec from config file."
|
|
331
|
+
)
|
|
332
|
+
parser.add_argument(
|
|
333
|
+
"-json", "--config_file",
|
|
334
|
+
type=str,
|
|
335
|
+
default=join(dirname(__file__), "encode_config_perf.json"),
|
|
336
|
+
help="Path of JSON config file (default: encode_config_perf.json in the same directory)"
|
|
337
|
+
)
|
|
338
|
+
parser.add_argument(
|
|
339
|
+
"-n", "--num_threads",
|
|
340
|
+
type=int,
|
|
341
|
+
default=1,
|
|
342
|
+
help="Number of parallel threads. Default is 1"
|
|
343
|
+
)
|
|
344
|
+
parser.add_argument(
|
|
345
|
+
"-f", "--frame_count",
|
|
346
|
+
type=int,
|
|
347
|
+
default=0,
|
|
348
|
+
help="Number of frames to encode (0 for all frames). Default is 0."
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
args = parser.parse_args()
|
|
352
|
+
config = {}
|
|
353
|
+
|
|
354
|
+
if args.config_file:
|
|
355
|
+
with open(args.config_file) as jsonFile:
|
|
356
|
+
config = json.loads(jsonFile.read())
|
|
357
|
+
config["preset"] = config["preset"].upper()
|
|
358
|
+
|
|
359
|
+
# Only set codec from command line if it was explicitly provided
|
|
360
|
+
if args.codec is not None:
|
|
361
|
+
args.codec = args.codec.lower()
|
|
362
|
+
config["codec"] = args.codec
|
|
363
|
+
|
|
364
|
+
args.format = args.format.upper()
|
|
365
|
+
width, height = map(int, args.size.split("x"))
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
run_parallel_encode(
|
|
369
|
+
args.num_threads,
|
|
370
|
+
args.gpu_id,
|
|
371
|
+
args.raw_file_path.as_posix(),
|
|
372
|
+
width,
|
|
373
|
+
height,
|
|
374
|
+
args.format,
|
|
375
|
+
config,
|
|
376
|
+
args.frame_count
|
|
377
|
+
)
|
|
378
|
+
finally:
|
|
379
|
+
# Force cleanup before exit
|
|
380
|
+
cleanup_resources()
|
|
@@ -0,0 +1,201 @@
|
|
|
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 PyNvVideoCodec as nvc
|
|
25
|
+
import numpy as np
|
|
26
|
+
import json
|
|
27
|
+
import argparse
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
import sys
|
|
30
|
+
import os
|
|
31
|
+
from os.path import join, dirname, abspath
|
|
32
|
+
|
|
33
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
34
|
+
current_dir = dirname(abspath(__file__))
|
|
35
|
+
parent_dir = dirname(current_dir)
|
|
36
|
+
if parent_dir not in sys.path:
|
|
37
|
+
sys.path.append(parent_dir)
|
|
38
|
+
|
|
39
|
+
from Utils import AppFrame
|
|
40
|
+
from Utils import FetchCPUFrame
|
|
41
|
+
from Utils import FetchGPUFrame
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Constants
|
|
45
|
+
TOTAL_NUM_FRAMES = 1000
|
|
46
|
+
BITRATE_CHANGE_INTERVAL = 100
|
|
47
|
+
BITRATE_REDUCTION_FACTOR = 2
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def encode(gpu_id, dec_file_path, enc_file_path, width, height, fmt, config_params):
|
|
51
|
+
"""
|
|
52
|
+
Encode video with dynamic bitrate reconfiguration.
|
|
53
|
+
|
|
54
|
+
This function demonstrates bitrate change at runtime without the need to reset the encoder session.
|
|
55
|
+
The application reduces the bitrate by half and then restores it to the original value after
|
|
56
|
+
every 100 frames.
|
|
57
|
+
|
|
58
|
+
Parameters:
|
|
59
|
+
gpu_id (int): Ordinal of GPU to use [Parameter not in use]
|
|
60
|
+
dec_file_path (str): Path to file to be decoded
|
|
61
|
+
enc_file_path (str): Path to output file for encoded frames
|
|
62
|
+
width (int): Width of encoded frame
|
|
63
|
+
height (int): Height of encoded frame
|
|
64
|
+
fmt (str): Surface format string in uppercase (e.g., "NV12")
|
|
65
|
+
config_params (dict): Key-value pairs providing fine-grained control on encoding
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
None
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
>>> encode(0, "input.yuv", "output.h264", 1920, 1080, "NV12", {"codec": "h264"})
|
|
72
|
+
Encode 1080p NV12 raw YUV into elementary bitstream using H.264 codec
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
with open(dec_file_path, "rb") as decFile, open(enc_file_path, "wb") as encFile:
|
|
76
|
+
# Create encoder object
|
|
77
|
+
config_params["gpu_id"] = gpu_id
|
|
78
|
+
nvenc = nvc.CreateEncoder(width, height, fmt, False, **config_params)
|
|
79
|
+
|
|
80
|
+
# Create input frame list
|
|
81
|
+
input_frame_list = [AppFrame(width, height, fmt) for _ in range(1, 5)]
|
|
82
|
+
|
|
83
|
+
# Get initial encoder parameters
|
|
84
|
+
reconf_params = nvenc.GetEncodeReconfigureParams()
|
|
85
|
+
original_avgbitrate = reconf_params.averageBitrate
|
|
86
|
+
original_vbvbuffersize = int(original_avgbitrate * reconf_params.frameRateDen / reconf_params.frameRateNum)
|
|
87
|
+
original_vbvinitdelay = original_vbvbuffersize
|
|
88
|
+
|
|
89
|
+
# Process frames
|
|
90
|
+
for i, input_gpu_frame in enumerate(
|
|
91
|
+
FetchGPUFrame(
|
|
92
|
+
input_frame_list,
|
|
93
|
+
FetchCPUFrame(decFile, input_frame_list[0].frameSize),
|
|
94
|
+
TOTAL_NUM_FRAMES
|
|
95
|
+
)
|
|
96
|
+
):
|
|
97
|
+
# Reconfigure bitrate every 100 frames
|
|
98
|
+
if i % BITRATE_CHANGE_INTERVAL == 0:
|
|
99
|
+
if i % (BITRATE_CHANGE_INTERVAL * 2) != 0:
|
|
100
|
+
# Reduce bitrate by half
|
|
101
|
+
reconf_params.averageBitrate = int(original_avgbitrate / BITRATE_REDUCTION_FACTOR)
|
|
102
|
+
reconf_params.vbvBufferSize = int(
|
|
103
|
+
reconf_params.averageBitrate * reconf_params.frameRateDen / reconf_params.frameRateNum
|
|
104
|
+
)
|
|
105
|
+
reconf_params.vbvInitialDelay = reconf_params.vbvBufferSize
|
|
106
|
+
else:
|
|
107
|
+
# Restore original bitrate
|
|
108
|
+
reconf_params.averageBitrate = original_avgbitrate
|
|
109
|
+
reconf_params.vbvBufferSize = original_vbvbuffersize
|
|
110
|
+
reconf_params.vbvInitialDelay = original_vbvinitdelay
|
|
111
|
+
|
|
112
|
+
nvenc.Reconfigure(reconf_params)
|
|
113
|
+
|
|
114
|
+
# Encode frame
|
|
115
|
+
bitstream = nvenc.Encode(input_gpu_frame)
|
|
116
|
+
encFile.write(bytearray(bitstream))
|
|
117
|
+
|
|
118
|
+
# Flush encoder queue
|
|
119
|
+
bitstream = nvenc.EndEncode()
|
|
120
|
+
encFile.write(bytearray(bitstream))
|
|
121
|
+
|
|
122
|
+
except nvc.PyNvVCExceptionUnsupported as e:
|
|
123
|
+
print(f"CreateEncoder failure: {e}")
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f"An unexpected error occurred: {e}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
parser = argparse.ArgumentParser(
|
|
130
|
+
description="This sample application demonstrates bitrate change at runtime without resetting the encoder session."
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"-g", "--gpu_id",
|
|
135
|
+
type=int,
|
|
136
|
+
default=0,
|
|
137
|
+
help="Check nvidia-smi for available GPUs. Default: 0"
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"-i", "--raw_file_path",
|
|
141
|
+
type=Path,
|
|
142
|
+
required=True,
|
|
143
|
+
help="Raw video file (read from)"
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"-o", "--encoded_file_path",
|
|
147
|
+
type=Path,
|
|
148
|
+
help="Encoded video file (write to). Default: <input_file_name>.<codec>"
|
|
149
|
+
)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"-s", "--size",
|
|
152
|
+
type=str,
|
|
153
|
+
required=True,
|
|
154
|
+
help="WidthxHeight of raw frame (e.g., 1920x1080)"
|
|
155
|
+
)
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
"-if", "--format",
|
|
158
|
+
type=str,
|
|
159
|
+
default="NV12",
|
|
160
|
+
help="Format of input file. Default is NV12"
|
|
161
|
+
)
|
|
162
|
+
parser.add_argument(
|
|
163
|
+
"-c", "--codec",
|
|
164
|
+
type=str,
|
|
165
|
+
default="H264",
|
|
166
|
+
help="Video codec (HEVC, H264, AV1). Default is H264"
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"-json", "--config_file",
|
|
170
|
+
type=str,
|
|
171
|
+
default=join(dirname(__file__), "encode_config_perf.json"),
|
|
172
|
+
help="Path of JSON config file (default: encode_config_perf.json in the same directory)"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
args = parser.parse_args()
|
|
176
|
+
config = {}
|
|
177
|
+
|
|
178
|
+
# Set default encoded_file_path if not provided
|
|
179
|
+
if args.encoded_file_path is None:
|
|
180
|
+
input_path = Path(args.raw_file_path)
|
|
181
|
+
args.encoded_file_path = input_path.with_suffix(f'.{args.codec.lower()}')
|
|
182
|
+
|
|
183
|
+
if args.config_file:
|
|
184
|
+
with open(args.config_file) as jsonFile:
|
|
185
|
+
config = json.loads(jsonFile.read())
|
|
186
|
+
config["preset"] = config["preset"].upper()
|
|
187
|
+
|
|
188
|
+
args.codec = args.codec.lower()
|
|
189
|
+
args.format = args.format.upper()
|
|
190
|
+
config["codec"] = args.codec
|
|
191
|
+
width, height = map(int, args.size.split("x"))
|
|
192
|
+
|
|
193
|
+
encode(
|
|
194
|
+
args.gpu_id,
|
|
195
|
+
args.raw_file_path.as_posix(),
|
|
196
|
+
args.encoded_file_path.as_posix(),
|
|
197
|
+
width,
|
|
198
|
+
height,
|
|
199
|
+
args.format,
|
|
200
|
+
config
|
|
201
|
+
)
|