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.
Files changed (64) hide show
  1. PyNvVideoCodec/PyNvVideoCodec_121.cp313-win_amd64.pyd +0 -0
  2. PyNvVideoCodec/PyNvVideoCodec_130.cp313-win_amd64.pyd +0 -0
  3. PyNvVideoCodec/VersionCheck.cp313-win_amd64.pyd +0 -0
  4. PyNvVideoCodec/__init__.py +237 -0
  5. PyNvVideoCodec/avcodec-58.dll +0 -0
  6. PyNvVideoCodec/avcodec-61.dll +0 -0
  7. PyNvVideoCodec/avcodec.dll +0 -0
  8. PyNvVideoCodec/avdevice-58.dll +0 -0
  9. PyNvVideoCodec/avdevice-61.dll +0 -0
  10. PyNvVideoCodec/avdevice.dll +0 -0
  11. PyNvVideoCodec/avfilter-10.dll +0 -0
  12. PyNvVideoCodec/avfilter-7.dll +0 -0
  13. PyNvVideoCodec/avfilter.dll +0 -0
  14. PyNvVideoCodec/avformat-58.dll +0 -0
  15. PyNvVideoCodec/avformat-61.dll +0 -0
  16. PyNvVideoCodec/avformat.dll +0 -0
  17. PyNvVideoCodec/avutil-56.dll +0 -0
  18. PyNvVideoCodec/avutil-59.dll +0 -0
  19. PyNvVideoCodec/avutil.dll +0 -0
  20. PyNvVideoCodec/decoders/SimpleDecoder.py +225 -0
  21. PyNvVideoCodec/decoders/ThreadedDecoder.py +119 -0
  22. PyNvVideoCodec/decoders/__init__.py +22 -0
  23. PyNvVideoCodec/swresample-3.dll +0 -0
  24. PyNvVideoCodec/swresample-5.dll +0 -0
  25. PyNvVideoCodec/swresample.dll +0 -0
  26. PyNvVideoCodec/swscale-5.dll +0 -0
  27. PyNvVideoCodec/swscale-8.dll +0 -0
  28. PyNvVideoCodec/swscale.dll +0 -0
  29. PyNvVideoCodec/transcoder/Transcoder.py +55 -0
  30. PyNvVideoCodec/transcoder/__init__.py +22 -0
  31. PyNvVideoCodec-2.0.0.data/data/external/ffmpeg/readme.txt +10 -0
  32. PyNvVideoCodec-2.0.0.data/data/external/ffmpeg/src/ffmpeg-7.0.2.tar.xz +0 -0
  33. PyNvVideoCodec-2.0.0.dist-info/METADATA +77 -0
  34. PyNvVideoCodec-2.0.0.dist-info/RECORD +64 -0
  35. PyNvVideoCodec-2.0.0.dist-info/WHEEL +5 -0
  36. PyNvVideoCodec-2.0.0.dist-info/licenses/LICENSE.txt +22 -0
  37. PyNvVideoCodec-2.0.0.dist-info/licenses/NOTICES.txt +167 -0
  38. PyNvVideoCodec-2.0.0.dist-info/top_level.txt +3 -0
  39. benchmarks/cached_decoder_benchmark.py +635 -0
  40. benchmarks/frame_sampling_benchmark.py +610 -0
  41. benchmarks/segmented_transcode_benchmark.py +1072 -0
  42. samples/Decode.py +194 -0
  43. samples/DecodeMultiprocessing.py +162 -0
  44. samples/DecodePerf.py +215 -0
  45. samples/DecodeSEIMsgExtraction.py +338 -0
  46. samples/DemuxFromByteArray.py +185 -0
  47. samples/Encode.py +189 -0
  48. samples/EncodeFromCPUBuffer.py +220 -0
  49. samples/EncodeMultiprocessing.py +268 -0
  50. samples/EncodePerf.py +380 -0
  51. samples/EncodeReconfigure.py +201 -0
  52. samples/EncodeSEIMsgInsertion.py +189 -0
  53. samples/ObjectDetection.py +218 -0
  54. samples/SimpleDecoder/EnsembleApp.py +416 -0
  55. samples/SimpleDecoder/SubsamplingAndReconfigure.py +341 -0
  56. samples/SimpleDecoder/fileLists.txt +4 -0
  57. samples/SimpleDecoder/segments.txt +3 -0
  58. samples/SimpleDecoder/timeListInSeconds.txt +5 -0
  59. samples/SimpleDecoder/transcode_config.json +6 -0
  60. samples/Utils.py +290 -0
  61. samples/coco_classes.pickle +0 -0
  62. samples/encode_config.json +16 -0
  63. samples/encode_config_lowlatency.json +11 -0
  64. samples/encode_config_perf.json +9 -0
@@ -0,0 +1,635 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a
5
+ # copy of this software and associated documentation files (the "Software"),
6
+ # to deal in the Software without restriction, including without limitation
7
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
+ # and/or sell copies of the Software, and to permit persons to whom the
9
+ # Software is furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20
+ # DEALINGS IN THE SOFTWARE.
21
+
22
+ from fileinput import filename
23
+ import argparse
24
+ from pathlib import Path
25
+ import os
26
+ import subprocess
27
+ import time
28
+ import random
29
+ from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
30
+ from concurrent.futures import wait
31
+ import pycuda.autoinit
32
+ import pycuda.driver as cuda
33
+ import PyNvVideoCodec as nvc
34
+ from itertools import product
35
+ import platform
36
+ from typing import List, Dict, Any
37
+ from tabulate import tabulate
38
+ import json
39
+ from datetime import datetime
40
+ from dataclasses import dataclass, asdict
41
+ import threading
42
+ import matplotlib.pyplot as plt
43
+ import numpy as np
44
+ import glob
45
+
46
+ """
47
+ Cached Decoder Benchmark
48
+
49
+ This script measures and compares the performance of NVIDIA's video decoder in two modes:
50
+ 1. Simple Decoder: Creates a new decoder instance for each video
51
+ 2. Cached Decoder: Reuses the same decoder instance across multiple videos
52
+
53
+ The benchmark generates test videos in different resolutions (360p to 4K) and durations (2s and 30s),
54
+ then measures decoding performance in terms of FPS (Frames Per Second) and total frames decoded.
55
+
56
+ Features:
57
+ - Supports multiple video codecs (H.264, HEVC, AV1)
58
+ - Tests different thread counts (1 and N NVDECs)
59
+ - Generates performance plots comparing simple vs cached decoder
60
+ - Saves results in both JSON and CSV formats
61
+ - Creates bar graphs for visual performance comparison
62
+
63
+ Command Line Arguments:
64
+ --plot-only: Only generate plots from existing JSON files (default: False)
65
+ --codec: Video codec to use (h264, hevc, or av1) (default: h264)
66
+ --nvdecs: Number of NVDECs to use (default: 3)
67
+ --fps: Frame rate for video generation (default: 30)
68
+ --gop: GOP size for video generation (default: 60)
69
+
70
+ Usage Examples:
71
+ 1. Run full benchmark with default settings (H.264, 3 threads):
72
+ python simple_dec_reuse_benchmark.py
73
+
74
+ 2. Run benchmark with HEVC codec and [1, 4] threads:
75
+ python simple_dec_reuse_benchmark.py --codec hevc --nvdecs 4
76
+
77
+ 3. Generate plots from existing results:
78
+ python simple_dec_reuse_benchmark.py --plot-only
79
+
80
+ 4. Run benchmark with custom video settings:
81
+ python simple_dec_reuse_benchmark.py --codec av1 --fps 60 --gop 120
82
+
83
+ Output Files:
84
+ - JSON results: cached_decoder_performance_{codec}_{threads}_threads.json
85
+ - Performance plots: cached_decoder_performance_{codec}_{threads}_threads.png
86
+ - Test videos: test_videos_{codec}/short_videos/ and test_videos_{codec}/long_videos/
87
+
88
+ """
89
+
90
+ @dataclass
91
+ class DecoderResult:
92
+ time_taken: float
93
+ fps: float
94
+ frames: int
95
+
96
+ @dataclass
97
+ class ResolutionResult:
98
+ resolution: str
99
+ new_decoder: DecoderResult
100
+ cached_decoder: DecoderResult
101
+
102
+ def to_dict(self):
103
+ return asdict(self)
104
+
105
+ @dataclass
106
+ class DurationResult:
107
+ duration_type: str
108
+ resolution_results: List[ResolutionResult]
109
+
110
+ def to_dict(self):
111
+ return {
112
+ "duration_type": self.duration_type,
113
+ "resolution_results": [res.to_dict() for res in self.resolution_results]
114
+ }
115
+
116
+ def create_test_videos(
117
+ video_configs: List[Dict[str, Any]],
118
+ encoding: str,
119
+ pattern: str,
120
+ fps: int,
121
+ pix_fmt: str,
122
+ ffmpeg_path: str,
123
+ base_output_dir: str,
124
+ duration_type: str,
125
+ resolution: str,
126
+ target_count: int = 1,
127
+ cq: int = 25
128
+ ) -> None:
129
+ """Create test videos with different configurations using ffmpeg
130
+
131
+ Args:
132
+ video_configs: List of video configuration dictionaries
133
+ encoding: Video encoding to use (e.g., 'h264_nvenc')
134
+ pattern: Test pattern to use (e.g., 'mandelbrot')
135
+ fps: Frames per second
136
+ pix_fmt: Pixel format (e.g., 'yuv420p')
137
+ ffmpeg_path: Path to ffmpeg executable
138
+ base_output_dir: Base directory for output
139
+ duration_type: Type of duration ('short' or 'long')
140
+ resolution: Resolution name (e.g., '360p')
141
+ target_count: Number of videos to generate per resolution (default: 1)
142
+ cq: Constant Quality value
143
+ """
144
+ # Create resolution-specific output directory
145
+ output_dir = os.path.join(base_output_dir, resolution)
146
+ if not os.path.exists(output_dir):
147
+ os.makedirs(output_dir)
148
+
149
+ # Count existing videos
150
+ existing_videos = []
151
+ if os.path.exists(output_dir):
152
+ for file in os.listdir(output_dir):
153
+ if file.lower().endswith(".mp4"):
154
+ existing_videos.append(file)
155
+
156
+ existing_count = len(existing_videos)
157
+ videos_needed = target_count - existing_count
158
+
159
+ if videos_needed <= 0:
160
+ print(f"Already have {existing_count} video(s) for {resolution} resolution in {duration_type} duration folder")
161
+ return
162
+
163
+ print(f"Generating {videos_needed} video(s) for {resolution} resolution in {duration_type} duration folder")
164
+ videos_created = 0
165
+
166
+ # Generate videos for this resolution
167
+ for config in video_configs:
168
+ if videos_created >= videos_needed:
169
+ break
170
+
171
+ # Add an index to the filename if generating multiple videos
172
+ index_suffix = f"_{videos_created + 1}" if target_count > 1 else ""
173
+ outfile = f"{output_dir}/{pattern}_{config['resolution']}_{config['duration']}s_{fps}fps_{config['gop']}gop_{encoding}_{pix_fmt}{index_suffix}.mp4"
174
+
175
+ ffmpeg_cmd = [
176
+ ffmpeg_path,
177
+ "-y",
178
+ "-f",
179
+ "lavfi",
180
+ "-i",
181
+ f"{pattern}=s={config['resolution']}",
182
+ "-t",
183
+ str(config['duration']),
184
+ "-c:v",
185
+ encoding,
186
+ "-r",
187
+ str(fps),
188
+ "-g",
189
+ str(config['gop']),
190
+ "-pix_fmt",
191
+ pix_fmt,
192
+ "-cq",
193
+ str(cq),
194
+ outfile,
195
+ ]
196
+
197
+ print(f"Creating video {videos_created + 1}/{videos_needed}: {' '.join(ffmpeg_cmd)}")
198
+ subprocess.check_call(ffmpeg_cmd)
199
+ videos_created += 1
200
+
201
+ print(f"Created {videos_created} new video(s) for {resolution} resolution in {duration_type} duration folder")
202
+
203
+ def split_list(videos, n):
204
+ k, m = divmod(len(videos), n)
205
+ result = []
206
+ start = 0
207
+ for i in range(n):
208
+ end = start + k + (1 if i < m else 0)
209
+ result.append(videos[start:end])
210
+ start = end
211
+ return result
212
+
213
+ def decode_using_new_decoder(clips):
214
+ device_id = 0
215
+ cuda_device = cuda.Device(device_id)
216
+ cuda_ctx = cuda_device.retain_primary_context()
217
+ cuda_ctx.push()
218
+ cuda_stream = cuda.Stream()
219
+ total_frames = 0
220
+ for video_file_path in clips:
221
+ decoder = nvc.SimpleDecoder(video_file_path, cuda_context=cuda_ctx.handle, cuda_stream=cuda_stream.handle)
222
+ metadata = decoder.get_stream_metadata()
223
+ num_frames = metadata.num_frames
224
+ for i in range(num_frames):
225
+ frame = decoder[i]
226
+ total_frames += num_frames
227
+ cuda_stream.synchronize()
228
+ cuda_ctx.pop()
229
+ return total_frames
230
+
231
+ def decode_using_cached_decoder(clips):
232
+ decoder = None
233
+ device_id = 0
234
+ cuda_device = cuda.Device(device_id)
235
+ cuda_ctx = cuda_device.retain_primary_context()
236
+ cuda_ctx.push()
237
+ cuda_stream = cuda.Stream()
238
+ total_frames = 0
239
+ for video_file_path in clips:
240
+ if decoder is None:
241
+ decoder = nvc.SimpleDecoder(video_file_path,
242
+ cuda_context=cuda_ctx.handle, cuda_stream=cuda_stream.handle)
243
+ else:
244
+ decoder.reconfigure_decoder(video_file_path)
245
+ metadata = decoder.get_stream_metadata()
246
+ num_frames = metadata.num_frames
247
+ for i in range(num_frames):
248
+ frame = decoder[i]
249
+ total_frames += num_frames
250
+ cuda_stream.synchronize()
251
+
252
+ cuda_ctx.pop()
253
+ return total_frames
254
+
255
+ def run_thread(func, clips_chunks, N):
256
+ threads = []
257
+ start_time = time.perf_counter()
258
+ total_frames = 0
259
+
260
+ # Use a list to store results from threads
261
+ results = [0] * N
262
+
263
+ def thread_wrapper(func, clips, index):
264
+ results[index] = func(clips)
265
+
266
+ for m in range(0, N):
267
+ t = threading.Thread(target=thread_wrapper, args=(func, clips_chunks[m], m))
268
+ t.start()
269
+ threads.append(t)
270
+
271
+ for t in threads:
272
+ t.join()
273
+
274
+ total_frames = sum(results)
275
+ elapsed_time = time.perf_counter() - start_time
276
+ return elapsed_time, total_frames
277
+
278
+ def decode_perf_results(clips_by_resolution: Dict[str, List[str]], N: int, duration_type: str) -> DurationResult:
279
+ resolution_results = []
280
+
281
+ for resolution, clips in clips_by_resolution.items():
282
+ clips_chunks = split_list(clips, N)
283
+
284
+ elapsed_time_case_1, frames_case_1 = run_thread(decode_using_new_decoder,
285
+ clips_chunks, N)
286
+
287
+ elapsed_time_case_2, frames_case_2 = run_thread(decode_using_cached_decoder,
288
+ clips_chunks, N)
289
+
290
+ fps_case_1 = round(frames_case_1 / elapsed_time_case_1)
291
+ fps_case_2 = round(frames_case_2 / elapsed_time_case_2)
292
+
293
+ res_result = ResolutionResult(
294
+ resolution=resolution,
295
+ new_decoder=DecoderResult(
296
+ time_taken=round(elapsed_time_case_1, 2),
297
+ fps=fps_case_1,
298
+ frames=frames_case_1
299
+ ),
300
+ cached_decoder=DecoderResult(
301
+ time_taken=round(elapsed_time_case_2, 2),
302
+ fps=fps_case_2,
303
+ frames=frames_case_2
304
+ )
305
+ )
306
+ resolution_results.append(res_result)
307
+
308
+ return DurationResult(duration_type=duration_type, resolution_results=resolution_results)
309
+
310
+ def save_results_to_json(all_results: List[DurationResult]):
311
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
312
+ filename = f"decoder_performance_results_{timestamp}.json"
313
+
314
+ # Convert dataclass objects to dictionaries
315
+ results_dict = [result.to_dict() for result in all_results]
316
+
317
+ with open(filename, 'w') as f:
318
+ json.dump(results_dict, f, indent=4)
319
+
320
+ print(f"\nResults saved to {filename}")
321
+
322
+ def display_results_table(all_results: List[DurationResult]):
323
+ headers = ["Duration Type", "Resolution", "Decoder Type", "Time Taken (s)", "FPS", "Frames Decoded"]
324
+ table_data = []
325
+
326
+ duration_labels = {
327
+ "short": "Short (2 sec)",
328
+ "long": "Long (30 sec)"
329
+ }
330
+
331
+ for result in all_results:
332
+ duration_label = duration_labels.get(result.duration_type, result.duration_type)
333
+
334
+ # Add duration type header row
335
+ table_data.append([f"{duration_label} Videos"] + ["" for _ in range(len(headers)-1)])
336
+
337
+ # Sort resolution results by resolution (360p, 480p, etc.)
338
+ def resolution_key(res):
339
+ if res.resolution == '4k':
340
+ return 4000 # 4k is equivalent to 4000p
341
+ return int(res.resolution.replace("p", ""))
342
+
343
+ sorted_results = sorted(result.resolution_results, key=resolution_key)
344
+
345
+ for res_result in sorted_results:
346
+ # Add row for simple decoder
347
+ table_data.append([
348
+ "", # Empty duration type (already shown in header)
349
+ res_result.resolution,
350
+ "Simple",
351
+ res_result.new_decoder.time_taken,
352
+ int(res_result.new_decoder.fps), # Convert to integer
353
+ res_result.new_decoder.frames
354
+ ])
355
+ # Add row for cached decoder
356
+ table_data.append([
357
+ "", # Empty duration type (already shown in header)
358
+ res_result.resolution,
359
+ "Cached",
360
+ res_result.cached_decoder.time_taken,
361
+ int(res_result.cached_decoder.fps), # Convert to integer
362
+ res_result.cached_decoder.frames
363
+ ])
364
+
365
+ # Add separator between duration types using the same style as header
366
+ if result.duration_type == "short":
367
+ table_data.append(["-" * 20, "-" * 12, "-" * 13, "-" * 16, "-" * 5, "-" * 15])
368
+
369
+ print("\nDecoder Performance Results:")
370
+ print(tabulate(table_data, headers=headers, tablefmt="simple", floatfmt=".2f"))
371
+
372
+ def collect_videos(base_dir, resolutions):
373
+ videos_by_resolution = {}
374
+ for res_name, _ in resolutions:
375
+ res_dir = os.path.join(base_dir, res_name)
376
+ if os.path.exists(res_dir):
377
+ videos = []
378
+ for file in os.listdir(res_dir):
379
+ if file.lower().endswith(".mp4"):
380
+ full_path = os.path.join(res_dir, file)
381
+ # Add the same video 50 times to the list
382
+ videos.extend([full_path] * 50)
383
+ if videos:
384
+ videos_by_resolution[res_name] = videos
385
+ return videos_by_resolution
386
+
387
+ def plot_decoder_performance():
388
+ """Create bar plots for decoder performance across resolutions for each thread count"""
389
+ # Read JSON files for the specific codec
390
+ results = {}
391
+ for filename in glob.glob(f"cached_decoder_performance_{args.codec}_*_threads.json"):
392
+ # Extract thread count from filename
393
+ thread_count = int(filename.split('_')[4])
394
+ with open(filename, 'r') as f:
395
+ results[thread_count] = json.load(f)
396
+
397
+ if not results:
398
+ print(f"No JSON files found for codec {args.codec}")
399
+ return
400
+
401
+ # Sort thread counts to ensure correct order
402
+ thread_counts = sorted(results.keys())
403
+
404
+ # Define resolution order for consistent plotting
405
+ resolution_order = ['360p', '480p', '720p', '1080p', '4k']
406
+
407
+ for thread_count in thread_counts:
408
+ # Create a figure for each thread count
409
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
410
+
411
+ # Extract data for this thread count
412
+ data = {
413
+ 'short': {'new': [], 'cached': [], 'resolutions': []},
414
+ 'long': {'new': [], 'cached': [], 'resolutions': []}
415
+ }
416
+
417
+ # Process each duration type
418
+ for duration_result in results[thread_count]:
419
+ duration_type = duration_result['duration_type']
420
+
421
+ # Sort results by resolution order
422
+ sorted_results = sorted(
423
+ duration_result['resolution_results'],
424
+ key=lambda x: resolution_order.index(x['resolution'])
425
+ )
426
+
427
+ for res_result in sorted_results:
428
+ data[duration_type]['resolutions'].append(res_result['resolution'])
429
+ data[duration_type]['new'].append(res_result['new_decoder']['fps'])
430
+ data[duration_type]['cached'].append(res_result['cached_decoder']['fps'])
431
+
432
+ # Plot for short duration videos
433
+ x = np.arange(len(data['short']['resolutions']))
434
+ width = 0.35
435
+
436
+ ax1.bar(x - width/2, data['short']['new'], width, label='Simple Decoder')
437
+ ax1.bar(x + width/2, data['short']['cached'], width, label='Cached Decoder')
438
+ ax1.set_title(f'Short Duration Videos (2s) - {thread_count} Thread(s) - {args.codec.upper()}')
439
+ ax1.set_xlabel('Resolution')
440
+ ax1.set_ylabel('FPS')
441
+ ax1.set_xticks(x)
442
+ ax1.set_xticklabels(data['short']['resolutions'])
443
+ ax1.grid(True, axis='y')
444
+ ax1.legend()
445
+
446
+ # Plot for long duration videos
447
+ x = np.arange(len(data['long']['resolutions']))
448
+ ax2.bar(x - width/2, data['long']['new'], width, label='Simple Decoder')
449
+ ax2.bar(x + width/2, data['long']['cached'], width, label='Cached Decoder')
450
+ ax2.set_title(f'Long Duration Videos (30s) - {thread_count} Thread(s) - {args.codec.upper()}')
451
+ ax2.set_xlabel('Resolution')
452
+ ax2.set_ylabel('FPS')
453
+ ax2.set_xticks(x)
454
+ ax2.set_xticklabels(data['long']['resolutions'])
455
+ ax2.grid(True, axis='y')
456
+ ax2.legend()
457
+
458
+ # Adjust layout and save
459
+ plt.tight_layout()
460
+ plt.savefig(f'cached_decoder_performance_{args.codec}_{thread_count}_threads.png')
461
+ plt.close()
462
+
463
+ print(f"Plot saved as 'cached_decoder_performance_{args.codec}_{thread_count}_threads.png'")
464
+
465
+ if __name__=="__main__":
466
+ script_start_time = time.perf_counter()
467
+
468
+ parser = argparse.ArgumentParser(
469
+ 'This sample application measures decoding performance in FPS.'
470
+ )
471
+ parser.add_argument(
472
+ "--plot-only", action="store_true", default=False,
473
+ help="only generate plots from existing JSON files (default: False)")
474
+ parser.add_argument(
475
+ "--codec", type=str, choices=['h264', 'hevc', 'av1'], default='h264',
476
+ help="video codec to use (h264, hevc, or av1)")
477
+ parser.add_argument(
478
+ "--nvdecs", type=int, default=3, help="number of NVDECs to use (default: 3)")
479
+ parser.add_argument(
480
+ "--fps", type=int, default=30, help="frame rate for video generation (default: 30)")
481
+ parser.add_argument(
482
+ "--gop", type=int, default=60, help="GOP size for video generation (default: 60)")
483
+
484
+ args = parser.parse_args()
485
+
486
+ if args.plot_only:
487
+ plot_decoder_performance()
488
+ exit(0)
489
+
490
+ # Define thread counts to test - only 1 and max threads
491
+ num_of_threads = [1, args.nvdecs] if args.nvdecs > 1 else [1]
492
+
493
+ # Define resolutions with their dimensions
494
+ resolutions = [
495
+ ("360p", "640x360"),
496
+ ("480p", "854x480"),
497
+ ("720p", "1280x720"),
498
+ ("1080p", "1920x1080"),
499
+ ("4k", "3840x2160")
500
+ ]
501
+
502
+ # Use the test videos directory in the same directory as the script
503
+ base_videos_dir = os.path.join(os.path.dirname(__file__), f"test_videos_{args.codec}")
504
+ short_videos_dir = os.path.join(base_videos_dir, "short_videos")
505
+ long_videos_dir = os.path.join(base_videos_dir, "long_videos")
506
+
507
+ # Create base directories if they don't exist
508
+ os.makedirs(short_videos_dir, exist_ok=True)
509
+ os.makedirs(long_videos_dir, exist_ok=True)
510
+
511
+ # Define video generation parameters
512
+ codec_map = {
513
+ 'h264': 'h264_nvenc',
514
+ 'hevc': 'hevc_nvenc',
515
+ 'av1': 'av1_nvenc'
516
+ }
517
+ encoding = codec_map[args.codec] # Use selected codec
518
+ pattern = "mandelbrot" # Test pattern
519
+ fps = args.fps # Frame rate from command line
520
+ pix_fmt = "yuv420p" # Standard pixel format
521
+ gop_size = args.gop # GOP size from command line
522
+ cq = 25 # Constant Quality value
523
+
524
+
525
+ # Find ffmpeg in a platform-independent way
526
+ ffmpeg_path = "ffmpeg"
527
+ if platform.system() == "Windows":
528
+ # Try to find ffmpeg in PATH
529
+ for path in os.environ["PATH"].split(os.pathsep):
530
+ ffmpeg_exe = Path(path) / "ffmpeg.exe"
531
+ if ffmpeg_exe.exists():
532
+ ffmpeg_path = str(ffmpeg_exe)
533
+ break
534
+
535
+ # Generate videos for each resolution
536
+ for res_name, res_dim in resolutions:
537
+ # Short duration videos
538
+ short_video_configs = [{
539
+ "resolution": res_dim,
540
+ "gop": gop_size,
541
+ "duration": 2 # Fixed 2 seconds
542
+ }]
543
+ create_test_videos(
544
+ short_video_configs,
545
+ encoding,
546
+ pattern,
547
+ fps,
548
+ pix_fmt,
549
+ ffmpeg_path,
550
+ str(short_videos_dir),
551
+ "short",
552
+ res_name,
553
+ 1, # Always generate 1 video per resolution
554
+ cq
555
+ )
556
+
557
+ # Long duration videos
558
+ long_video_configs = [{
559
+ "resolution": res_dim,
560
+ "gop": gop_size,
561
+ "duration": 30 # Fixed 30 seconds
562
+ }]
563
+ create_test_videos(
564
+ long_video_configs,
565
+ encoding,
566
+ pattern,
567
+ fps,
568
+ pix_fmt,
569
+ ffmpeg_path,
570
+ str(long_videos_dir),
571
+ "long",
572
+ res_name,
573
+ 1, # Always generate 1 video per resolution
574
+ cq
575
+ )
576
+
577
+ # Collect videos from all resolution directories
578
+ short_clips = collect_videos(short_videos_dir, resolutions)
579
+ long_clips = collect_videos(long_videos_dir, resolutions)
580
+
581
+ all_results = []
582
+
583
+ # Run benchmark for each thread count
584
+ for thread_count in num_of_threads:
585
+ print(f"\nRunning benchmark with {thread_count} thread(s)")
586
+
587
+ if short_clips:
588
+ print(f"\nProcessing short duration videos:")
589
+ for res, clips in short_clips.items():
590
+ print(f" Generating stats for {res} short videos")
591
+ print(f" {res}: {len(clips)} video(s)")
592
+ short_results = decode_perf_results(short_clips, thread_count, "short")
593
+ all_results.append(short_results)
594
+
595
+ if long_clips:
596
+ print(f"\nProcessing long duration videos:")
597
+ for res, clips in long_clips.items():
598
+ print(f" Generating stats for {res} long videos")
599
+ print(f" {res}: {len(clips)} video(s)")
600
+ long_results = decode_perf_results(long_clips, thread_count, "long")
601
+ all_results.append(long_results)
602
+
603
+ # Print video configuration details
604
+ print("\nVideo Configuration:")
605
+ print(f"Pattern: {pattern}")
606
+ print(f"Frame Rate: {fps} fps")
607
+ print(f"GOP Size: {gop_size}")
608
+ print(f"Number of threads: {thread_count}")
609
+
610
+ if all_results:
611
+ # Display tabulated results
612
+ display_results_table(all_results)
613
+
614
+ # Save results to JSON file with codec and thread count in filename
615
+ filename = f"cached_decoder_performance_{args.codec}_{thread_count}_threads.json"
616
+
617
+ # Convert dataclass objects to dictionaries
618
+ results_dict = [result.to_dict() for result in all_results]
619
+
620
+ with open(filename, 'w') as f:
621
+ json.dump(results_dict, f, indent=4)
622
+
623
+ print(f"\nResults saved to {filename}")
624
+
625
+ # Clear results for next thread count
626
+ all_results = []
627
+ else:
628
+ print("No videos were found or generated. Please check the directory structure and video generation process.")
629
+
630
+ # After running the benchmark, generate plots
631
+ plot_decoder_performance()
632
+
633
+ # Print total execution time
634
+ total_time = time.perf_counter() - script_start_time
635
+ print(f"\nTotal execution time: {total_time:.2f} seconds")