PyNvVideoCodec 2.2.1__cp312-cp312-win_arm64.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_arm64.pyd +0 -0
- PyNvVideoCodec/PyNvVideoCodec_130.cp312-win_arm64.pyd +0 -0
- PyNvVideoCodec/VersionCheck.cp312-win_arm64.pyd +0 -0
- PyNvVideoCodec/__init__.py +251 -0
- PyNvVideoCodec/avformat-62.dll +0 -0
- PyNvVideoCodec/avutil-60.dll +0 -0
- PyNvVideoCodec/decoders/SimpleDecoder.py +273 -0
- PyNvVideoCodec/decoders/ThreadedDecoder.py +129 -0
- PyNvVideoCodec/decoders/__init__.py +22 -0
- PyNvVideoCodec/transcoder/Transcoder.py +55 -0
- PyNvVideoCodec/transcoder/__init__.py +22 -0
- benchmarks/cached_decoder_benchmark.py +813 -0
- benchmarks/frame_sampling_benchmark.py +765 -0
- benchmarks/requirements.txt +52 -0
- benchmarks/segmented_transcode_benchmark.py +1546 -0
- benchmarks/setup_pycuda_win_arm64.bat +531 -0
- pynvvideocodec-2.2.1.data/data/external/ffmpeg/readme.txt +39 -0
- pynvvideocodec-2.2.1.data/data/external/ffmpeg/src/ffmpeg-8.1.2.tar.xz +0 -0
- pynvvideocodec-2.2.1.dist-info/METADATA +97 -0
- pynvvideocodec-2.2.1.dist-info/RECORD +63 -0
- pynvvideocodec-2.2.1.dist-info/WHEEL +5 -0
- pynvvideocodec-2.2.1.dist-info/licenses/LICENSE.txt +22 -0
- pynvvideocodec-2.2.1.dist-info/licenses/NOTICES.txt +167 -0
- pynvvideocodec-2.2.1.dist-info/top_level.txt +3 -0
- samples/advanced/decode.py +173 -0
- samples/advanced/decode_from_memory_buffer.py +204 -0
- samples/advanced/decode_perf.py +299 -0
- samples/advanced/decode_reconfigure.py +184 -0
- samples/advanced/decode_sei_msg.py +193 -0
- samples/advanced/decode_with_cuda_control.py +214 -0
- samples/advanced/decode_with_low_latency.py +183 -0
- samples/advanced/encode_config.json +12 -0
- samples/advanced/encode_config_lowlatency.json +13 -0
- samples/advanced/encode_perf.py +338 -0
- samples/advanced/encode_reconfigure.py +211 -0
- samples/advanced/encode_sei_msg.py +188 -0
- samples/advanced/simple_decode_from_memory.py +151 -0
- samples/advanced/simple_decode_stats.py +179 -0
- samples/advanced/transcode.py +288 -0
- samples/basic/create_video_segments.py +187 -0
- samples/basic/encode.py +574 -0
- samples/basic/segments.txt +3 -0
- samples/basic/simple_decode_sampling.py +182 -0
- samples/basic/transcode_config.json +6 -0
- samples/jupyter/ReconfigureDecoder.png +0 -0
- samples/jupyter/SimpleDecoderAPIs.png +0 -0
- samples/jupyter/ThreadedDecoder.png +0 -0
- samples/jupyter/coco_classes.pickle +0 -0
- samples/jupyter/face_protect_tutorial.ipynb +881 -0
- samples/jupyter/object_detection_tutorial.ipynb +781 -0
- samples/jupyter/simple_decode_tutorial.ipynb +674 -0
- samples/requirements.txt +84 -0
- samples/setup_pycuda_win_arm64.bat +531 -0
- samples/utils/Utils.py +405 -0
- samples/utils/__init__.py +36 -0
- samples/utils/decode_parser.py +642 -0
- samples/utils/decode_stats_writer.py +177 -0
- samples/utils/encode_parallel_utils.py +174 -0
- samples/utils/encode_parser.py +379 -0
- samples/utils/frame_utils.py +89 -0
- samples/utils/sei_message_parser.py +223 -0
- samples/utils/sei_parser.py +223 -0
- samples/utils/transcode_parser.py +218 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
'''
|
|
25
|
+
This sample demonstrates advanced parallel video encoding using multiple threads or processes.
|
|
26
|
+
It shows how to:
|
|
27
|
+
1. Maximize GPU utilization using multiple encoder instances
|
|
28
|
+
2. Measure encoding performance (FPS) accurately for different modes
|
|
29
|
+
3. Scale encoding across available NVENC engines
|
|
30
|
+
4. Use either threading or multiprocessing for parallelization
|
|
31
|
+
5. Manage shared resources and cleanup
|
|
32
|
+
6. Track and report performance metrics
|
|
33
|
+
|
|
34
|
+
The sample implements two parallel execution modes:
|
|
35
|
+
- Thread Mode:
|
|
36
|
+
* Multiple encoders in the same process
|
|
37
|
+
* Shared memory access
|
|
38
|
+
* Lower overhead, simpler synchronization
|
|
39
|
+
* Best for CPU-bound workloads
|
|
40
|
+
|
|
41
|
+
- Process Mode:
|
|
42
|
+
* Multiple encoders in separate processes
|
|
43
|
+
* IPC memory sharing
|
|
44
|
+
* Better isolation and stability
|
|
45
|
+
* Best for GPU-bound workloads
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Usage:
|
|
49
|
+
python encode_perf.py -m <mode> -i <input_file> -s <size> [options]
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
python encode_perf.py -m threads -i input.yuv -s 1920x1080 -n 4
|
|
53
|
+
|
|
54
|
+
Arguments:
|
|
55
|
+
-m: Execution mode ('threads' or 'processes')
|
|
56
|
+
-i: Raw input video file
|
|
57
|
+
-s: Frame size (e.g., 1920x1080)
|
|
58
|
+
-if: Input format (default: NV12)
|
|
59
|
+
-n: Number of workers (default: 1)
|
|
60
|
+
-f: Frames per worker (default: all)
|
|
61
|
+
-g: GPU ID (default: 0)
|
|
62
|
+
-c: Output codec (h264/hevc/av1); overrides codec in JSON config
|
|
63
|
+
-json: Encoder config file (controls codec, fps, gop, preset, rc, bitrate, etc.)
|
|
64
|
+
|
|
65
|
+
Note: This performance testing app does not write output files.
|
|
66
|
+
It only measures encoding throughput.
|
|
67
|
+
|
|
68
|
+
Note: For optimal performance:
|
|
69
|
+
- Use thread mode for smaller workloads or when CPU is the bottleneck
|
|
70
|
+
- Use process mode for larger workloads or when GPU is the bottleneck
|
|
71
|
+
- Adjust worker count based on available CPU cores and GPU resources
|
|
72
|
+
'''
|
|
73
|
+
|
|
74
|
+
import gc
|
|
75
|
+
import sys
|
|
76
|
+
import atexit
|
|
77
|
+
import os
|
|
78
|
+
import time
|
|
79
|
+
import queue
|
|
80
|
+
import threading
|
|
81
|
+
import multiprocessing
|
|
82
|
+
from multiprocessing import Process
|
|
83
|
+
import pycuda.driver as cuda
|
|
84
|
+
import PyNvVideoCodec as nvc
|
|
85
|
+
from os.path import dirname, abspath
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
89
|
+
current_dir = dirname(abspath(__file__))
|
|
90
|
+
parent_dir = dirname(current_dir)
|
|
91
|
+
if parent_dir not in sys.path:
|
|
92
|
+
sys.path.append(parent_dir)
|
|
93
|
+
|
|
94
|
+
# Import utilities for encoding
|
|
95
|
+
from utils.Utils import AppFramePerf
|
|
96
|
+
from utils.encode_parser import parse_perf_args, PERF_MAX_FRAMES
|
|
97
|
+
from utils.frame_utils import get_frame_size, create_memory_map, allocate_device_memory
|
|
98
|
+
from utils.encode_parallel_utils import (
|
|
99
|
+
print_performance_summary,
|
|
100
|
+
encode_thread_worker,
|
|
101
|
+
encode_process_worker
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Global variables to track resources for cleanup
|
|
106
|
+
g_devicedata = None
|
|
107
|
+
g_mmap = None
|
|
108
|
+
g_context = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def cleanup_resources():
|
|
112
|
+
"""Clean up global resources including memory map, GPU memory, and CUDA context."""
|
|
113
|
+
global g_devicedata, g_mmap, g_context
|
|
114
|
+
|
|
115
|
+
gc.collect()
|
|
116
|
+
|
|
117
|
+
if g_mmap:
|
|
118
|
+
try:
|
|
119
|
+
g_mmap.close()
|
|
120
|
+
g_mmap = None
|
|
121
|
+
except:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
if g_devicedata:
|
|
125
|
+
try:
|
|
126
|
+
g_devicedata.free()
|
|
127
|
+
g_devicedata = None
|
|
128
|
+
except:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
if g_context:
|
|
132
|
+
try:
|
|
133
|
+
g_context.detach()
|
|
134
|
+
g_context = None
|
|
135
|
+
except:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
atexit.register(cleanup_resources)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def run_threaded_encode(args):
|
|
143
|
+
"""Run encoding with multiple threads using shared device memory."""
|
|
144
|
+
print(f"Initializing {args.num_workers} encode thread{'s' if args.num_workers > 1 else ''}")
|
|
145
|
+
|
|
146
|
+
# Calculate total frames and show warning if needed
|
|
147
|
+
framesize = get_frame_size(args.width, args.height, args.format)
|
|
148
|
+
file_size = os.path.getsize(args.raw_file_path)
|
|
149
|
+
available_frames = file_size // framesize
|
|
150
|
+
|
|
151
|
+
if available_frames > PERF_MAX_FRAMES and args.frame_count == 0:
|
|
152
|
+
print(f"\nWarning: File contains {available_frames} frames. Limiting to {PERF_MAX_FRAMES} frames for performance testing.")
|
|
153
|
+
elif args.frame_count > PERF_MAX_FRAMES:
|
|
154
|
+
print(f"\nWarning: Requested {args.frame_count} frames. Limiting to {PERF_MAX_FRAMES} frames for performance testing.")
|
|
155
|
+
|
|
156
|
+
global g_devicedata, g_mmap, g_context
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
# Initialize CUDA
|
|
160
|
+
cuda.init()
|
|
161
|
+
cuda_device = cuda.Device(args.gpu_id)
|
|
162
|
+
g_context = cuda_device.make_context()
|
|
163
|
+
shared_stream = cuda.Stream()
|
|
164
|
+
|
|
165
|
+
# Open input file and prepare data
|
|
166
|
+
with open(args.raw_file_path, "rb") as input_file:
|
|
167
|
+
framesize = get_frame_size(args.width, args.height, args.format)
|
|
168
|
+
g_mmap = create_memory_map(input_file)
|
|
169
|
+
|
|
170
|
+
# Calculate frames to process
|
|
171
|
+
frames_to_process = args.frame_count if args.frame_count > 0 else available_frames
|
|
172
|
+
frames_to_process = min(frames_to_process, available_frames, PERF_MAX_FRAMES)
|
|
173
|
+
bytes_to_read = frames_to_process * framesize
|
|
174
|
+
|
|
175
|
+
print(f"File contains {available_frames} frames, processing {frames_to_process} frames")
|
|
176
|
+
|
|
177
|
+
# Read and copy data to GPU
|
|
178
|
+
hostdata = g_mmap.read(bytes_to_read)
|
|
179
|
+
g_devicedata = allocate_device_memory(hostdata)
|
|
180
|
+
|
|
181
|
+
# Create encoders
|
|
182
|
+
encoders = []
|
|
183
|
+
for _ in range(args.num_workers):
|
|
184
|
+
config = args.config.copy()
|
|
185
|
+
encoder = nvc.CreateEncoder(
|
|
186
|
+
args.width, args.height, args.format, False,
|
|
187
|
+
cudacontext=g_context.handle,
|
|
188
|
+
cudastream=shared_stream.handle,
|
|
189
|
+
**config
|
|
190
|
+
)
|
|
191
|
+
encoders.append(encoder)
|
|
192
|
+
|
|
193
|
+
# Run encoding threads
|
|
194
|
+
result_queue = queue.Queue()
|
|
195
|
+
begin = time.perf_counter()
|
|
196
|
+
|
|
197
|
+
threads = []
|
|
198
|
+
for i in range(args.num_workers):
|
|
199
|
+
t = threading.Thread(
|
|
200
|
+
target=encode_thread_worker,
|
|
201
|
+
args=(
|
|
202
|
+
i, args.raw_file_path, encoders[i], g_devicedata,
|
|
203
|
+
framesize, args.width, args.height, args.format,
|
|
204
|
+
args.frame_count, result_queue
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
threads.append(t)
|
|
208
|
+
t.start()
|
|
209
|
+
|
|
210
|
+
total_frames = sum(result_queue.get() for _ in range(args.num_workers))
|
|
211
|
+
duration = time.perf_counter() - begin
|
|
212
|
+
for t in threads:
|
|
213
|
+
t.join()
|
|
214
|
+
|
|
215
|
+
print_performance_summary("threads", args.num_workers, total_frames, duration)
|
|
216
|
+
|
|
217
|
+
# Cleanup
|
|
218
|
+
for encoder in encoders:
|
|
219
|
+
del encoder
|
|
220
|
+
encoders.clear()
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
print(f"An error occurred: {e}")
|
|
224
|
+
finally:
|
|
225
|
+
if g_context is not None:
|
|
226
|
+
g_context.pop()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def run_multiprocess_encode(args):
|
|
230
|
+
"""Run encoding with multiple processes using IPC memory sharing."""
|
|
231
|
+
print(f"Initializing {args.num_workers} encode process{'es' if args.num_workers > 1 else ''}")
|
|
232
|
+
|
|
233
|
+
# Calculate total frames and show warning if needed
|
|
234
|
+
framesize = get_frame_size(args.width, args.height, args.format)
|
|
235
|
+
file_size = os.path.getsize(args.raw_file_path)
|
|
236
|
+
available_frames = file_size // framesize
|
|
237
|
+
|
|
238
|
+
if available_frames > PERF_MAX_FRAMES and args.frame_count == 0:
|
|
239
|
+
print(f"\nWarning: File contains {available_frames} frames. Limiting to {PERF_MAX_FRAMES} frames for performance testing.")
|
|
240
|
+
elif args.frame_count > PERF_MAX_FRAMES:
|
|
241
|
+
print(f"\nWarning: Requested {args.frame_count} frames. Limiting to {PERF_MAX_FRAMES} frames for performance testing.")
|
|
242
|
+
|
|
243
|
+
framesize = get_frame_size(args.width, args.height, args.format)
|
|
244
|
+
|
|
245
|
+
with open(args.raw_file_path, "rb") as input_file:
|
|
246
|
+
# Calculate frames to process
|
|
247
|
+
file_size = os.path.getsize(args.raw_file_path)
|
|
248
|
+
total_frames = file_size // framesize
|
|
249
|
+
frames_to_process = args.frame_count if args.frame_count > 0 else total_frames
|
|
250
|
+
frames_to_process = min(frames_to_process, total_frames, PERF_MAX_FRAMES)
|
|
251
|
+
bytes_to_read = frames_to_process * framesize
|
|
252
|
+
|
|
253
|
+
print(f"File contains {total_frames} frames, processing {frames_to_process} frames")
|
|
254
|
+
|
|
255
|
+
cuda.init()
|
|
256
|
+
parent_ctx = cuda.Device(args.gpu_id).make_context()
|
|
257
|
+
m = None
|
|
258
|
+
devicedata = None
|
|
259
|
+
ctx_popped_for_workers = False
|
|
260
|
+
try:
|
|
261
|
+
m = create_memory_map(input_file)
|
|
262
|
+
hostdata = m.read(bytes_to_read)
|
|
263
|
+
devicedata = allocate_device_memory(hostdata)
|
|
264
|
+
devptrhandle = cuda.mem_get_ipc_handle(devicedata)
|
|
265
|
+
# Drop parent's CUDA context while workers run. Otherwise the parent and
|
|
266
|
+
# children keep separate contexts resident on the same GPU and Nsight
|
|
267
|
+
# shows heavy GPU_CONTEXT_SWITCH_EVENTS (order-of-magnitude vs thread mode).
|
|
268
|
+
parent_ctx.pop()
|
|
269
|
+
ctx_popped_for_workers = True
|
|
270
|
+
|
|
271
|
+
result_queue = multiprocessing.Queue()
|
|
272
|
+
ready_queue = multiprocessing.Queue()
|
|
273
|
+
go_event = multiprocessing.Event()
|
|
274
|
+
|
|
275
|
+
processes = []
|
|
276
|
+
for i in range(args.num_workers):
|
|
277
|
+
config = args.config.copy()
|
|
278
|
+
p = Process(
|
|
279
|
+
target=encode_process_worker,
|
|
280
|
+
args=(
|
|
281
|
+
i, args.raw_file_path, args.width, args.height,
|
|
282
|
+
args.format, devptrhandle, framesize, config,
|
|
283
|
+
frames_to_process, result_queue, ready_queue, go_event
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
processes.append(p)
|
|
287
|
+
p.start()
|
|
288
|
+
|
|
289
|
+
readies = [ready_queue.get() for _ in range(args.num_workers)]
|
|
290
|
+
if not all(ok for _pid, ok in readies):
|
|
291
|
+
for p in processes:
|
|
292
|
+
if p.is_alive():
|
|
293
|
+
p.terminate()
|
|
294
|
+
p.join()
|
|
295
|
+
print("Encoding aborted: setup failed in one or more workers.")
|
|
296
|
+
else:
|
|
297
|
+
begin = time.perf_counter()
|
|
298
|
+
go_event.set()
|
|
299
|
+
# Stop the timer when workers report frame counts, not when processes exit.
|
|
300
|
+
# The child still runs finally (encoder destructor, CUDA context teardown) after
|
|
301
|
+
# result_queue.put; Process.join() waits for that too (~0.3–0.5s+), unlike
|
|
302
|
+
# Thread.join() after put (see thread path above—same get-then-join pattern).
|
|
303
|
+
total_frames = sum(result_queue.get() for _ in range(args.num_workers))
|
|
304
|
+
duration = time.perf_counter() - begin
|
|
305
|
+
for p in processes:
|
|
306
|
+
p.join()
|
|
307
|
+
print_performance_summary("processes", args.num_workers, total_frames, duration)
|
|
308
|
+
finally:
|
|
309
|
+
if ctx_popped_for_workers:
|
|
310
|
+
parent_ctx.push()
|
|
311
|
+
if devicedata is not None:
|
|
312
|
+
devicedata.free()
|
|
313
|
+
if m is not None:
|
|
314
|
+
m.close()
|
|
315
|
+
parent_ctx.pop()
|
|
316
|
+
parent_ctx.detach()
|
|
317
|
+
|
|
318
|
+
print("All encoding processes completed")
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
args = parse_perf_args(
|
|
323
|
+
"Parallel video encoding application supporting both multithreading and multiprocessing for performance testing."
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Set multiprocessing start method for compatibility
|
|
327
|
+
if args.mode == "process":
|
|
328
|
+
multiprocessing.set_start_method('spawn')
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
if args.mode == "thread":
|
|
332
|
+
run_threaded_encode(args)
|
|
333
|
+
else: # processes
|
|
334
|
+
run_multiprocess_encode(args)
|
|
335
|
+
except Exception as e:
|
|
336
|
+
print(f"An error occurred: {e}")
|
|
337
|
+
finally:
|
|
338
|
+
cleanup_resources()
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
'''
|
|
25
|
+
This sample demonstrates dynamic encoder reconfiguration for bitrate control.
|
|
26
|
+
It shows how to:
|
|
27
|
+
1. Change encoder parameters at runtime
|
|
28
|
+
2. Modify bitrate without resetting the encoder
|
|
29
|
+
3. Handle VBV (Video Buffer Verifier) parameters
|
|
30
|
+
4. Maintain encoding quality during changes
|
|
31
|
+
|
|
32
|
+
Note: Bitrate changes occur every 100 frames:
|
|
33
|
+
- Frame 0: Original bitrate
|
|
34
|
+
- Frame 100: Half bitrate
|
|
35
|
+
- Frame 200: Original bitrate
|
|
36
|
+
- And so on...
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
python encode_reconfigure.py -i <input_file> -s <size> [options]
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
python encode_reconfigure.py -i input.yuv -s 1920x1080
|
|
43
|
+
|
|
44
|
+
Arguments:
|
|
45
|
+
-i: Raw input video file
|
|
46
|
+
-s: Frame size (e.g., 1920x1080)
|
|
47
|
+
-if: Input format (default: NV12)
|
|
48
|
+
-f: Number of frames (default: all)
|
|
49
|
+
-g: GPU ID (default: 0)
|
|
50
|
+
-c: Output codec (h264/hevc/av1); overrides codec in JSON config
|
|
51
|
+
-json: Encoder config file (controls codec, fps, gop, preset, rc, bitrate, etc.)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
'''
|
|
55
|
+
|
|
56
|
+
import PyNvVideoCodec as nvc
|
|
57
|
+
import numpy as np
|
|
58
|
+
import sys
|
|
59
|
+
import os
|
|
60
|
+
from os.path import dirname, abspath
|
|
61
|
+
|
|
62
|
+
# Add the parent directory to Python path in a way that works from any working directory
|
|
63
|
+
current_dir = dirname(abspath(__file__))
|
|
64
|
+
parent_dir = dirname(current_dir)
|
|
65
|
+
if parent_dir not in sys.path:
|
|
66
|
+
sys.path.append(parent_dir)
|
|
67
|
+
|
|
68
|
+
# Import utilities for GPU buffer mode
|
|
69
|
+
try:
|
|
70
|
+
from utils.Utils import AppFrame, FetchCPUFrame, FetchGPUFrame
|
|
71
|
+
GPU_UTILS_AVAILABLE = True
|
|
72
|
+
except ImportError:
|
|
73
|
+
GPU_UTILS_AVAILABLE = False
|
|
74
|
+
print("Warning: Utils module not found. GPU buffer mode will be disabled.")
|
|
75
|
+
|
|
76
|
+
from utils.encode_parser import parse_reconfigure_encode_args
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Constants
|
|
80
|
+
BITRATE_CHANGE_INTERVAL = 100
|
|
81
|
+
BITRATE_REDUCTION_FACTOR = 2
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def encode(gpu_id, dec_file_path, enc_file_path, width, height, fmt, config_params, frame_count=0):
|
|
85
|
+
"""
|
|
86
|
+
Encode video with dynamic bitrate reconfiguration.
|
|
87
|
+
|
|
88
|
+
This function demonstrates bitrate change at runtime without the need to reset the encoder session.
|
|
89
|
+
The application reduces the bitrate by half and then restores it to the original value after
|
|
90
|
+
every 100 frames.
|
|
91
|
+
|
|
92
|
+
Parameters:
|
|
93
|
+
gpu_id (int): Ordinal of GPU to use [Parameter not in use]
|
|
94
|
+
dec_file_path (str): Path to file to be decoded
|
|
95
|
+
enc_file_path (str): Path to output file for encoded frames
|
|
96
|
+
width (int): Width of encoded frame
|
|
97
|
+
height (int): Height of encoded frame
|
|
98
|
+
fmt (str): Surface format string in uppercase (e.g., "NV12")
|
|
99
|
+
config_params (dict): Key-value pairs providing fine-grained control on encoding
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
None
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
>>> encode(0, "input.yuv", "output.h264", 1920, 1080, "NV12", {"codec": "h264"})
|
|
106
|
+
Encode 1080p NV12 raw YUV into elementary bitstream using H.264 codec
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
with open(dec_file_path, "rb") as decFile, open(enc_file_path, "wb") as encFile:
|
|
110
|
+
# Create encoder object
|
|
111
|
+
config_params["gpu_id"] = gpu_id
|
|
112
|
+
|
|
113
|
+
if "tuning_info" in config_params:
|
|
114
|
+
tuning_info_val = config_params['tuning_info']
|
|
115
|
+
if tuning_info_val == "low_latency":
|
|
116
|
+
config_params['bf'] = 0
|
|
117
|
+
|
|
118
|
+
nvenc = nvc.CreateEncoder(width, height, fmt, False, **config_params)
|
|
119
|
+
# Create input frame list
|
|
120
|
+
input_frame_list = [AppFrame(width, height, fmt) for _ in range(1, 5)]
|
|
121
|
+
|
|
122
|
+
# Get initial encoder parameters
|
|
123
|
+
reconf_params = nvenc.GetEncodeReconfigureParams()
|
|
124
|
+
original_avgbitrate = reconf_params.averageBitrate
|
|
125
|
+
if "vbvbufsize" in config_params:
|
|
126
|
+
original_vbvbuffersize = config_params['vbvbufsize']
|
|
127
|
+
else:
|
|
128
|
+
original_vbvbuffersize = int(original_avgbitrate * reconf_params.frameRateDen / reconf_params.frameRateNum)
|
|
129
|
+
|
|
130
|
+
original_vbvinitdelay = original_vbvbuffersize
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Process frames
|
|
134
|
+
# Calculate total available frames from file size
|
|
135
|
+
file_size = os.path.getsize(dec_file_path)
|
|
136
|
+
available_frames = file_size // input_frame_list[0].frameSize
|
|
137
|
+
|
|
138
|
+
# Determine frames to process
|
|
139
|
+
frames_to_process = frame_count if frame_count > 0 else available_frames
|
|
140
|
+
frames_to_process = min(frames_to_process, available_frames)
|
|
141
|
+
|
|
142
|
+
print(f"File contains {available_frames} frames, processing {frames_to_process} frames")
|
|
143
|
+
|
|
144
|
+
for i, input_gpu_frame in enumerate(
|
|
145
|
+
FetchGPUFrame(
|
|
146
|
+
input_frame_list,
|
|
147
|
+
FetchCPUFrame(decFile, input_frame_list[0].frameSize),
|
|
148
|
+
frames_to_process
|
|
149
|
+
)
|
|
150
|
+
):
|
|
151
|
+
# Reconfigure bitrate every 100 frames
|
|
152
|
+
if i % BITRATE_CHANGE_INTERVAL == 0:
|
|
153
|
+
if i % (BITRATE_CHANGE_INTERVAL * 2) != 0:
|
|
154
|
+
# Reduce bitrate by half
|
|
155
|
+
reconf_params.averageBitrate = int(original_avgbitrate / BITRATE_REDUCTION_FACTOR)
|
|
156
|
+
reconf_params.vbvBufferSize = int(original_vbvbuffersize / BITRATE_REDUCTION_FACTOR)
|
|
157
|
+
reconf_params.vbvInitialDelay = int(original_vbvinitdelay / BITRATE_REDUCTION_FACTOR)
|
|
158
|
+
else:
|
|
159
|
+
# Restore original bitrate
|
|
160
|
+
reconf_params.averageBitrate = original_avgbitrate
|
|
161
|
+
reconf_params.vbvBufferSize = original_vbvbuffersize
|
|
162
|
+
reconf_params.vbvInitialDelay = original_vbvinitdelay
|
|
163
|
+
|
|
164
|
+
nvenc.Reconfigure(reconf_params)
|
|
165
|
+
|
|
166
|
+
# Encode frame
|
|
167
|
+
pic_params = nvc.NV_ENC_PIC_PARAMS()
|
|
168
|
+
pic_params.inputTimeStamp = i
|
|
169
|
+
packets = nvenc.Encode(input_gpu_frame, pic_params)
|
|
170
|
+
encFile.write(
|
|
171
|
+
bytearray(b"".join(bytes(p["data"]) for p in packets))
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Flush encoder queue
|
|
175
|
+
flush_packets = nvenc.EndEncode()
|
|
176
|
+
encFile.write(bytearray(b"".join(bytes(p["data"]) for p in flush_packets)))
|
|
177
|
+
print(f"Completed encoding {frames_to_process} frames using CPU buffers")
|
|
178
|
+
print(f"Output file: {enc_file_path}")
|
|
179
|
+
|
|
180
|
+
except nvc.PyNvVCExceptionUnsupported as e:
|
|
181
|
+
print(f"CreateEncoder failure: {e}")
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f"An unexpected error occurred: {e}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if __name__ == "__main__":
|
|
187
|
+
args = parse_reconfigure_encode_args(
|
|
188
|
+
"This sample application demonstrates bitrate change at runtime without resetting the encoder session."
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
print("=" * 60)
|
|
192
|
+
print("Configuration:")
|
|
193
|
+
print(f"Input: {args.raw_file_path}")
|
|
194
|
+
print(f"Output: {args.encoded_file_path}")
|
|
195
|
+
print(f"Size: {args.width}x{args.height}")
|
|
196
|
+
print(f"Format: {args.format}")
|
|
197
|
+
print(f"Codec: {args.codec}")
|
|
198
|
+
print(f"GPU ID: {args.gpu_id}")
|
|
199
|
+
print(f"Frame count: {args.frame_count if args.frame_count > 0 else 'All frames'}")
|
|
200
|
+
print("=" * 60)
|
|
201
|
+
|
|
202
|
+
encode(
|
|
203
|
+
args.gpu_id,
|
|
204
|
+
args.raw_file_path.as_posix(),
|
|
205
|
+
args.encoded_file_path.as_posix(),
|
|
206
|
+
args.width,
|
|
207
|
+
args.height,
|
|
208
|
+
args.format,
|
|
209
|
+
args.config,
|
|
210
|
+
args.frame_count
|
|
211
|
+
)
|