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,610 @@
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 os
25
+ import platform
26
+ import json
27
+ import time
28
+ import torch
29
+ import numpy as np
30
+ from concurrent.futures import ThreadPoolExecutor, wait
31
+ from pathlib import Path
32
+ import PyNvVideoCodec as nvc
33
+ import pycuda.driver as cuda
34
+ import pycuda.autoinit
35
+ from dataclasses import dataclass
36
+ from typing import List, Dict, Any, Optional, Tuple
37
+ from abc import ABC, abstractmethod
38
+ from tabulate import tabulate
39
+ import threading
40
+ import subprocess
41
+ import argparse
42
+
43
+ """
44
+ PyNvVideoCodec Benchmarking Script
45
+
46
+ This script performs benchmarking tests on the PyNvVideoCodec decoder to evaluate its performance
47
+ under different conditions. It measures:
48
+ 1. Decoding performance with different thread counts
49
+ 2. Efficiency of direct frame sampling vs sequential decoding
50
+ 3. Performance across different video configurations (resolution, GOP size)
51
+
52
+ The script creates test videos with various configurations and then runs benchmarks with:
53
+ - Different thread counts (1, n where n is the number of NVDecs)
54
+ - Different decoding patterns:
55
+ * Sequential decoding of first N frames
56
+ * Uniform sampling of frames
57
+ * Random sampling of frames
58
+
59
+ Usage:
60
+ python pynvc_benchmarking_script.py --nvdecs <number_of_nvdecs> [options]
61
+
62
+ Required Arguments:
63
+ --nvdecs <number> Number of NVDecs available on the system
64
+
65
+ Optional Arguments:
66
+ --verbose, -v Show detailed thread-level performance information (default: False)
67
+ --resolution, -res Video resolution (default: 1920x1080)
68
+ --gop, -g GOP sizes to test, space-separated list (default: 30 250)
69
+ --duration, -d Video duration in seconds (default: 30)
70
+ --fps, -f Video frames per second (default: 30)
71
+ --num-seq-frames, -seq Number of frames to decode sequentially (default: 100)
72
+ --num-samp-frames, -samp Number of frames to sample for uniform/random patterns (default: 30)
73
+
74
+ Example:
75
+ python pynvc_benchmarking_script.py --nvdecs 3 --resolution 1280x720 --gop 30 60 --verbose
76
+ This will run benchmarks with thread counts [1, 3], 720p resolution, GOP sizes 30 and 60,
77
+ and show detailed thread information.
78
+
79
+ Output:
80
+ The script generates two types of tables:
81
+ 1. Main Results Table: Shows FPS and efficiency for each configuration
82
+ 2. Thread Details Table (with --verbose): Shows per-thread FPS performance
83
+
84
+ Results are also saved to a JSON file for further analysis.
85
+ """
86
+
87
+
88
+ @dataclass
89
+ class DecoderPerformance:
90
+ decoder_name: str
91
+ video_file: str
92
+ frames_decoded: int
93
+ thread_execution_times: List[float]
94
+ overall_fps: float
95
+ per_thread_fps: List[float]
96
+ thread_count: int
97
+ test_description: str
98
+ sampling_method: str # "sequential", "uniform", or "random"
99
+ sequential_decode_time: Optional[float] = None
100
+ sampling_efficiency: Optional[float] = None # sequential_time / actual_time
101
+
102
+ class VideoFrameDecoder(ABC):
103
+ def __init__(self, worker_count: int = 4):
104
+ self.worker_count = worker_count
105
+
106
+ @abstractmethod
107
+ def get_frames_at_timestamps(self, video_path: str, timestamps: List[float]) -> Tuple[List[np.ndarray], List[float]]:
108
+ """Retrieve video frames at specified timestamps"""
109
+ pass
110
+
111
+ @abstractmethod
112
+ def get_sequential_frames(self, video_path: str, frame_count: int, target_timestamp: float = None) -> Tuple[List[np.ndarray], List[float]]:
113
+ """Retrieve frames sequentially from the start of the video"""
114
+ pass
115
+
116
+ def get_benchmark_description(self, frame_count: int, pattern_type: str) -> str:
117
+ """Generate description for the benchmark test case"""
118
+ if pattern_type == "sequential":
119
+ return f"Sequential retrieval of first {frame_count} frames"
120
+ return f"Sampling {frame_count} frames with {pattern_type} pattern"
121
+
122
+ class PyNvVideoCodecDecoder(VideoFrameDecoder):
123
+ def __init__(self, worker_count: int = 4):
124
+ super().__init__(worker_count)
125
+ self.decoders = []
126
+ self.video_file = None
127
+ self.lock = threading.Lock() # For thread-safe decoder initialization
128
+
129
+ def get_frames_at_timestamps(self, video_path: str, timestamps: List[float]) -> Tuple[List[np.ndarray], List[float]]:
130
+ """Retrieve video frames at specified timestamps"""
131
+ with self.lock:
132
+ if not self.decoders:
133
+ self.decoders = [nvc.SimpleDecoder(video_path, bWaitForSessionWarmUp=True) for _ in range(self.worker_count)]
134
+ else:
135
+ for decoder in self.decoders:
136
+ decoder.reconfigure_decoder(video_path)
137
+ self.video_file = video_path
138
+
139
+ nvc.PyNvDecoder.SetSessionCount(self.worker_count)
140
+
141
+ frames = []
142
+ thread_times = []
143
+
144
+ def decode_worker(decoder_idx: int, timestamps: List[float]) -> Tuple[List[np.ndarray], float]:
145
+ start_time = time.time()
146
+ batch_frames = []
147
+ decoder = self.decoders[decoder_idx]
148
+ target_indices = []
149
+
150
+ for timestamp in timestamps:
151
+ index = decoder.get_index_from_time_in_seconds(timestamp)
152
+ target_indices.append(index)
153
+
154
+ batch_frames = decoder.get_batch_frames_by_index(target_indices)
155
+
156
+ end_time = time.time()
157
+ elapsed_time = end_time - start_time
158
+ session_init_time = decoder.get_session_init_time()/1000
159
+ elapsed_time -= session_init_time
160
+ return batch_frames, elapsed_time
161
+
162
+ with ThreadPoolExecutor(max_workers=self.worker_count) as executor:
163
+ futures = []
164
+ for i in range(self.worker_count):
165
+ futures.append(executor.submit(decode_worker, i, timestamps))
166
+
167
+ for future in futures:
168
+ batch_frames, thread_time = future.result()
169
+ frames.extend(batch_frames)
170
+ thread_times.append(thread_time)
171
+
172
+ return frames, thread_times
173
+
174
+ def get_sequential_frames(self, video_path: str, frame_count: int = None, target_timestamp: float = None) -> Tuple[List[np.ndarray], List[float]]:
175
+ """Retrieve frames sequentially from the start of the video"""
176
+ with self.lock:
177
+ if not self.decoders:
178
+ self.decoders = [nvc.SimpleDecoder(video_path, bWaitForSessionWarmUp=True) for _ in range(self.worker_count)]
179
+ else:
180
+ for decoder in self.decoders:
181
+ decoder.reconfigure_decoder(video_path)
182
+ self.video_file = video_path
183
+
184
+ nvc.PyNvDecoder.SetSessionCount(self.worker_count)
185
+
186
+ frames = []
187
+ thread_times = []
188
+
189
+ def decode_worker(decoder_idx: int, frame_count: int, target_timestamp: float = None) -> Tuple[List[np.ndarray], float]:
190
+ batch_frames = []
191
+ decoder = self.decoders[decoder_idx]
192
+ if target_timestamp:
193
+ frame_count = decoder.get_index_from_time_in_seconds(target_timestamp)
194
+
195
+ start_time = time.time()
196
+ for i in range(0, frame_count, 5):
197
+ decoder.seek_to_index(i)
198
+ batch = decoder.get_batch_frames(5)
199
+ batch_frames.extend(batch)
200
+ if len(batch_frames) >= frame_count:
201
+ break
202
+
203
+ end_time = time.time()
204
+ elapsed_time = end_time - start_time
205
+ session_init_time = decoder.get_session_init_time()/1000
206
+ elapsed_time -= session_init_time
207
+ return batch_frames[:frame_count], elapsed_time
208
+
209
+ with ThreadPoolExecutor(max_workers=self.worker_count) as executor:
210
+ futures = []
211
+ for i in range(self.worker_count):
212
+ futures.append(executor.submit(decode_worker, i, frame_count, target_timestamp))
213
+
214
+ for future in futures:
215
+ batch_frames, thread_time = future.result()
216
+ frames.extend(batch_frames)
217
+ thread_times.append(thread_time)
218
+
219
+ return frames, thread_times
220
+
221
+
222
+ def get_system_metadata() -> Dict[str, Any]:
223
+ """Get system metadata for benchmarking results"""
224
+ return {
225
+ "cpu_count": os.cpu_count(),
226
+ "system": platform.system(),
227
+ "machine": platform.machine(),
228
+ "python_version": str(platform.python_version()),
229
+ "cuda": torch.cuda.get_device_properties(0).name if torch.cuda.is_available() else "not available",
230
+ "cuda_available": torch.cuda.is_available()
231
+ }
232
+
233
+ def generate_mandelbrot_video(width: int, height: int, duration: int, fps: int, gop: int, output_path: str) -> str:
234
+ """
235
+ Generate a Mandelbrot set video using FFmpeg
236
+
237
+ Parameters:
238
+ width (int): Video width
239
+ height (int): Video height
240
+ duration (int): Video duration in seconds
241
+ fps (int): Frames per second
242
+ gop (int): GOP size
243
+ output_path (str): Path to save the generated video
244
+
245
+ Returns:
246
+ str: Path to the generated video
247
+ """
248
+ print(f"Generating Mandelbrot set video at {width}x{height}, {duration}s duration, {fps} fps, {gop} GOP size")
249
+
250
+ # Create the directory if it doesn't exist
251
+ os.makedirs(os.path.dirname(output_path) or '.', exist_ok=True)
252
+
253
+ # FFmpeg command to generate a Mandelbrot set video
254
+ cmd = [
255
+ "ffmpeg", "-y",
256
+ "-v", "warning",
257
+ # Video input from mandelbrot filter
258
+ "-f", "lavfi",
259
+ "-i", f"mandelbrot=s={width}x{height}",
260
+ # Set duration
261
+ "-t", str(duration),
262
+ # Video encoding with h264 and specific parameters
263
+ "-c:v", "h264_nvenc",
264
+ "-preset", "medium",
265
+ "-b:v", "8M",
266
+ "-bf", "3", # 3 B-frames
267
+ "-g", str(gop), # GOP size
268
+ "-rc", "vbr", # Variable bitrate
269
+ "-profile:v", "high", # High profile for better quality
270
+ output_path
271
+ ]
272
+
273
+ try:
274
+ # Execute FFmpeg command and wait for completion
275
+ print("Starting FFmpeg process to generate Mandelbrot video...")
276
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
277
+ stdout, stderr = process.communicate()
278
+
279
+ if process.returncode != 0:
280
+ print(f"Error generating video: {stderr.decode()}")
281
+ raise subprocess.CalledProcessError(process.returncode, cmd, stderr)
282
+
283
+ print(f"Video generation complete: {output_path}")
284
+ # Print video information
285
+ print(f"Video codec: h264_nvenc with {gop} GOP size and 3 B-frames")
286
+ return output_path
287
+ except subprocess.CalledProcessError as e:
288
+ print(f"FFmpeg error: {e}")
289
+ raise
290
+ except Exception as e:
291
+ print(f"Error: {e}")
292
+ raise
293
+
294
+
295
+ def generate_benchmark_videos(
296
+ resolution: str,
297
+ duration: int,
298
+ fps: int,
299
+ gop_sizes: List[int],
300
+ output_dir: str
301
+ ) -> List[str]:
302
+ """
303
+ Generate benchmark videos with different GOP sizes
304
+
305
+ Parameters:
306
+ resolution (str): Video resolution (e.g., '1920x1080')
307
+ duration (int): Video duration in seconds
308
+ fps (int): Frames per second
309
+ gop_sizes (List[int]): List of GOP sizes to test
310
+ output_dir (str): Directory to save the generated videos
311
+
312
+ Returns:
313
+ List[str]: List of paths to the generated videos
314
+ """
315
+ width, height = map(int, resolution.split('x'))
316
+ generated_videos = []
317
+
318
+ for gop in gop_sizes:
319
+ output_path = f"{output_dir}/mandelbrot_{resolution}_{duration}s_{fps}fps_{gop}gop.mp4"
320
+
321
+ # Skip if video already exists
322
+ if os.path.exists(output_path):
323
+ print(f"Skipping {output_path} - already exists")
324
+ generated_videos.append(output_path)
325
+ continue
326
+
327
+ try:
328
+ video_path = generate_mandelbrot_video(width, height, duration, fps, gop, output_path)
329
+ generated_videos.append(video_path)
330
+ except Exception as e:
331
+ print(f"Failed to generate video with GOP {gop}: {e}")
332
+ continue
333
+
334
+ return generated_videos
335
+
336
+
337
+ def get_video_metadata(video_file: str) -> Dict[str, Any]:
338
+ """Get metadata for a video file using PyNvVideoCodec"""
339
+ decoder = nvc.SimpleDecoder(video_file)
340
+ metadata = decoder.get_stream_metadata()
341
+ return {
342
+ "duration": metadata.duration,
343
+ "width": metadata.width,
344
+ "height": metadata.height,
345
+ "fps": metadata.average_fps,
346
+ "codec": metadata.codec_name
347
+ }
348
+
349
+
350
+ def create_timestamp_list(video_file: str, num_samples: int, kind: str = "uniform") -> List[float]:
351
+ """Generate list of timestamps for frame sampling"""
352
+ metadata = get_video_metadata(video_file)
353
+ duration = metadata["duration"]
354
+
355
+ if kind == "uniform":
356
+ return [i * duration / num_samples for i in range(num_samples)]
357
+ elif kind == "random":
358
+ # Generate unique random indices and scale to duration
359
+ indices = torch.randperm(int(duration * 1000))[:num_samples] # Convert to milliseconds for more precision
360
+ return (indices.float() / 1000).tolist() # Convert back to seconds
361
+ else:
362
+ raise ValueError(f"Unknown sampling kind: {kind}")
363
+
364
+
365
+ def run_benchmark_test(
366
+ decoder: VideoFrameDecoder,
367
+ video_file: str,
368
+ num_threads: int,
369
+ num_samples: int,
370
+ decode_pattern: str = "first_n_frames"
371
+ ) -> DecoderPerformance:
372
+ """Run a single benchmark test for a decoder"""
373
+ description = ""
374
+ sequential_time = None
375
+ efficiency = None
376
+
377
+ if decode_pattern == "first_n_frames":
378
+ description = decoder.get_benchmark_description(num_samples, "sequential")
379
+ frames, thread_times = decoder.get_sequential_frames(video_file, frame_count=num_samples)
380
+ pattern_str = f"first_{num_samples}_frames"
381
+ else: # uniform or random
382
+ num_samples = 30
383
+ description = decoder.get_benchmark_description(num_samples, decode_pattern)
384
+ timestamp_list = create_timestamp_list(video_file, num_samples, decode_pattern)
385
+
386
+ # First measure sequential decode time to the last timestamp
387
+ last_timestamp = max(timestamp_list)
388
+ _, seq_thread_times = decoder.get_sequential_frames(video_file, frame_count=None, target_timestamp=last_timestamp)
389
+ sequential_time = sum(seq_thread_times)
390
+
391
+ # Then measure random/uniform decode time
392
+ frames, thread_times = decoder.get_frames_at_timestamps(video_file, timestamp_list)
393
+ efficiency = sequential_time / sum(thread_times) if sum(thread_times) > 0 else 0
394
+ pattern_str = f"{num_samples}_{decode_pattern}_frames"
395
+
396
+ total_frames = len(frames)
397
+ thread_fps = [num_samples / t for t in thread_times]
398
+ total_fps = sum(thread_fps)
399
+
400
+ return DecoderPerformance(
401
+ decoder_name=decoder.__class__.__name__,
402
+ video_file=video_file,
403
+ frames_decoded=total_frames,
404
+ thread_execution_times=thread_times,
405
+ overall_fps=total_fps,
406
+ per_thread_fps=thread_fps,
407
+ thread_count=num_threads,
408
+ test_description=description,
409
+ sampling_method=pattern_str,
410
+ sequential_decode_time=sequential_time,
411
+ sampling_efficiency=efficiency
412
+ )
413
+
414
+
415
+ def display_benchmark_results(results: List[DecoderPerformance], verbose: bool = False) -> None:
416
+ """Display benchmark results in formatted tables"""
417
+
418
+ videos = sorted(set(r.video_file for r in results))
419
+ patterns = sorted(set(r.sampling_method for r in results))
420
+ thread_counts = sorted(set(r.thread_count for r in results))
421
+
422
+ print("\nBenchmark Results:")
423
+
424
+ table_data = []
425
+ headers = ["Video Config", "Pattern"]
426
+
427
+ for thread_count in thread_counts:
428
+ headers.extend([f"T{thread_count} FPS", f"T{thread_count} Eff"])
429
+
430
+ for video in videos:
431
+ # Get the filename without path and extension
432
+ filename = os.path.basename(video)
433
+ # Split by underscore and remove .mp4 from the last part
434
+ parts = filename.split('_')
435
+ resolution = parts[1]
436
+ duration = parts[2].replace('s', '')
437
+ gop = parts[4].replace('gop.mp4', '')
438
+ config_str = f"{resolution} {gop}gop {duration}s"
439
+
440
+ for pattern in patterns:
441
+ row = [config_str, pattern]
442
+
443
+ for thread_count in thread_counts:
444
+ result = next((r for r in results
445
+ if r.video_file == video
446
+ and r.sampling_method == pattern
447
+ and r.thread_count == thread_count), None)
448
+ if result:
449
+ row.append(f"{result.overall_fps:.1f}")
450
+ if result.sampling_efficiency is not None:
451
+ row.append(f"{result.sampling_efficiency:.2f}x")
452
+ else:
453
+ row.append("N/A")
454
+ else:
455
+ row.append("N/A")
456
+ row.append("N/A")
457
+
458
+ table_data.append(row)
459
+
460
+ print(tabulate(table_data, headers=headers, tablefmt="grid"))
461
+
462
+ # Only show thread-level details if verbose mode is enabled
463
+ if verbose:
464
+ print(f"\nThread Details:")
465
+
466
+ thread_headers = ["Config", "Threads", "FPS/Thread"]
467
+ thread_data = []
468
+
469
+ for video in videos:
470
+ # Get the filename without path and extension
471
+ filename = os.path.basename(video)
472
+ # Split by underscore and remove .mp4 from the last part
473
+ parts = filename.split('_')
474
+ resolution = parts[1]
475
+ duration = parts[2].replace('s', '')
476
+ gop = parts[4].replace('gop.mp4', '')
477
+ config_str = f"{resolution} {gop}gop {duration}s"
478
+
479
+ for pattern in patterns:
480
+ for thread_count in thread_counts:
481
+ result = next((r for r in results
482
+ if r.video_file == video
483
+ and r.sampling_method == pattern
484
+ and r.thread_count == thread_count), None)
485
+
486
+ if result:
487
+ thread_fps_str = ",".join([f"{fps:.1f}" for fps in result.per_thread_fps])
488
+ thread_data.append([
489
+ f"{config_str} {pattern}",
490
+ thread_count,
491
+ thread_fps_str
492
+ ])
493
+
494
+ # Print thread-level details table
495
+ print(tabulate(thread_data, headers=thread_headers, tablefmt="grid"))
496
+
497
+ print("\nEfficiency Explanation:")
498
+ print("Efficiency (Eff) represents the performance comparison between two approaches:")
499
+ print("1. Direct sampling: Decoding specific frames directly using seek operations")
500
+ print("2. Sequential decode + sampling: Decoding all frames sequentially and then sampling the required frames")
501
+ print("The efficiency value shows how much faster direct sampling is compared to sequential decoding with sampling.")
502
+ print("Higher efficiency values indicate better performance of direct sampling approach.")
503
+
504
+
505
+ def save_benchmark_results(results: List[DecoderPerformance], output_file: str = "benchmark_results.json") -> None:
506
+ """Save benchmark results to a JSON file"""
507
+ output_data = {
508
+ "system_info": get_system_metadata(),
509
+ "benchmark_results": [
510
+ {
511
+ "decoder": r.decoder_name,
512
+ "video": r.video_file,
513
+ "frames_decoded": r.frames_decoded,
514
+ "thread_execution_times": r.thread_execution_times,
515
+ "overall_fps": r.overall_fps,
516
+ "per_thread_fps": r.per_thread_fps,
517
+ "thread_count": r.thread_count,
518
+ "test_description": r.test_description,
519
+ "sampling_method": r.sampling_method,
520
+ "sequential_decode_time": r.sequential_decode_time,
521
+ "sampling_efficiency": r.sampling_efficiency
522
+ }
523
+ for r in results
524
+ ]
525
+ }
526
+
527
+ with open(output_file, "w") as f:
528
+ json.dump(output_data, f, indent=4)
529
+
530
+ print(f"\nBenchmark results saved to {output_file}")
531
+
532
+
533
+ def main():
534
+ # Parse command line arguments
535
+ parser = argparse.ArgumentParser(description='PyNvVideoCodec Benchmarking Script')
536
+ parser.add_argument('--nvdecs', type=int, required=True,
537
+ help='Number of NVDecs available on the system')
538
+ parser.add_argument('--verbose', '-v', action='store_true',
539
+ help='Show detailed thread-level performance information (default: False)')
540
+ parser.add_argument('--resolution', '-res', type=str, default='1920x1080',
541
+ help='Video resolution (default: 1920x1080)')
542
+ parser.add_argument('--gop', '-g', type=int, nargs='+', default=[30, 250],
543
+ help='GOP sizes to test (space-separated list) (default: 30 250)')
544
+ parser.add_argument('--duration', '-d', type=int, default=30,
545
+ help='Video duration in seconds (default: 30)')
546
+ parser.add_argument('--fps', '-f', type=int, default=30,
547
+ help='Video frames per second (default: 30)')
548
+ parser.add_argument('--num-seq-frames', '-seq', type=int, default=100,
549
+ help='Number of frames to decode sequentially (default: 100)')
550
+ parser.add_argument('--num-samp-frames', '-samp', type=int, default=30,
551
+ help='Number of frames to sample for uniform/random patterns (default: 30)')
552
+ args = parser.parse_args()
553
+
554
+ # Set thread counts based on number of NVDecs
555
+ thread_counts = [1, args.nvdecs] if args.nvdecs > 1 else [1]
556
+ print(f"Using thread counts: {thread_counts} based on {args.nvdecs} NVDecs")
557
+
558
+ start_time = time.time()
559
+
560
+ # Create videos directory in the same location as the script
561
+ script_dir = Path(__file__).parent
562
+ videos_dir_path = script_dir / "benchmark_videos"
563
+ videos_dir_path.mkdir(parents=True, exist_ok=True)
564
+ print(f"Using videos directory: {videos_dir_path}")
565
+
566
+ # Generate benchmark videos
567
+ video_files = generate_benchmark_videos(
568
+ args.resolution,
569
+ args.duration,
570
+ args.fps,
571
+ args.gop,
572
+ str(videos_dir_path)
573
+ )
574
+
575
+ # Benchmark parameters
576
+ decode_patterns = ["first_n_frames", "uniform", "random"]
577
+
578
+ results = []
579
+
580
+ # Run benchmarks
581
+ for video_file in video_files:
582
+ if not os.path.exists(video_file):
583
+ print(f"Skipping {video_file} - file not found")
584
+ continue
585
+
586
+ for num_threads in thread_counts:
587
+ print(f"\nBenchmarking {video_file} with {num_threads} threads")
588
+
589
+ # PyNvVideoCodec benchmark
590
+ pynv_decoder = PyNvVideoCodecDecoder(num_threads)
591
+ for pattern in decode_patterns:
592
+ num_samples = args.num_seq_frames if pattern == "first_n_frames" else args.num_samp_frames
593
+ pynv_result = run_benchmark_test(pynv_decoder, video_file, num_threads, num_samples, pattern)
594
+ results.append(pynv_result)
595
+
596
+ # Display and save results
597
+ display_benchmark_results(results, verbose=args.verbose)
598
+ save_benchmark_results(results)
599
+
600
+ # Display total execution time
601
+ end_time = time.time()
602
+ total_time = end_time - start_time
603
+ hours = int(total_time // 3600)
604
+ minutes = int((total_time % 3600) // 60)
605
+ seconds = total_time % 60
606
+ print(f"\nTotal execution time: {hours:02d}:{minutes:02d}:{seconds:06.3f} ({total_time:.3f} seconds)")
607
+
608
+
609
+ if __name__ == "__main__":
610
+ main()