kapsikkum-unmanic – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | #!/usr/bin/env python3 |
2 | # -*- coding: utf-8 -*- |
||
3 | |||
4 | """ |
||
5 | unmanic.hardware_acceleration_handle.py |
||
6 | |||
7 | Written by: Josh.5 <jsunnex@gmail.com> |
||
8 | Date: 21 Feb 2021, (3:54 PM) |
||
9 | |||
10 | Copyright: |
||
11 | Copyright (C) Josh Sunnex - All Rights Reserved |
||
12 | |||
13 | Permission is hereby granted, free of charge, to any person obtaining a copy |
||
14 | of this software and associated documentation files (the "Software"), to deal |
||
15 | in the Software without restriction, including without limitation the rights |
||
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||
17 | copies of the Software, and to permit persons to whom the Software is |
||
18 | furnished to do so, subject to the following conditions: |
||
19 | |||
20 | The above copyright notice and this permission notice shall be included in all |
||
21 | copies or substantial portions of the Software. |
||
22 | |||
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||
26 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||
27 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
||
28 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE |
||
29 | OR OTHER DEALINGS IN THE SOFTWARE. |
||
30 | |||
31 | """ |
||
32 | import ctypes |
||
33 | import os |
||
34 | |||
35 | |||
36 | class HardwareAccelerationHandle(object): |
||
37 | """ |
||
38 | HardwareAccelerationHandle |
||
39 | |||
40 | Determine support for Hardware Acceleration on host |
||
41 | """ |
||
42 | |||
43 | def __init__(self, file_probe): |
||
44 | self.file_probe = file_probe |
||
45 | self.enable_hardware_accelerated_decoding = False |
||
46 | self.hardware_device = None |
||
47 | self.video_encoder = None |
||
48 | self.main_options = [] |
||
49 | self.advanced_options = [] |
||
50 | |||
51 | def get_hwaccel_devices(self): |
||
52 | """ |
||
53 | Return a list of the hosts compatible decoders |
||
54 | |||
55 | :return: |
||
56 | """ |
||
57 | decoders_list = [] |
||
58 | |||
59 | # Check for CUDA decoders |
||
60 | decoders_list = decoders_list + self.list_available_cuda_decoders() |
||
61 | |||
62 | # Append any discovered VAAPI decoders |
||
63 | decoders_list = decoders_list + self.list_available_vaapi_devices() |
||
64 | |||
65 | # Return the decoder list |
||
66 | return decoders_list |
||
67 | |||
68 | def set_hwaccel_args(self): |
||
69 | self.main_options = [] |
||
70 | |||
71 | # If Unmanic has settings configured for enabling 'HW Decoding', then fetch args based on selected HW type |
||
72 | if self.hardware_device: |
||
73 | hwaccel_type = self.hardware_device.get('hwaccel') |
||
74 | if hwaccel_type is not None: |
||
75 | if hwaccel_type == 'vaapi': |
||
76 | # Return decoder args for VAAPI |
||
77 | self.generate_vaapi_main_args() |
||
78 | elif hwaccel_type == 'cuda': |
||
79 | # Return decoder args for NVIDIA CUDA device |
||
80 | self.generate_cuda_main_args() |
||
81 | else: |
||
82 | # If no hardware decoder is set, then check that there is no need for setting the hardware device for the encoder |
||
83 | # Eg. The VAAPI encoder still needs to have the '-vaapi_device' main option configured to work even if no decoder |
||
84 | # is configured |
||
85 | |||
86 | # Check if VAAPI video encoder is enabled |
||
87 | if self.video_encoder and "vaapi" in self.video_encoder.lower(): |
||
88 | # Find the first decoder |
||
89 | vaapi_devices = self.list_available_vaapi_devices() |
||
90 | if vaapi_devices: |
||
91 | self.hardware_device = vaapi_devices[0] |
||
92 | self.generate_vaapi_main_args() |
||
93 | # self.main_options = self.main_options + ['-vaapi_device', vaapi_device.get('hwaccel_device')] |
||
94 | |||
95 | def update_main_options(self, main_options): |
||
96 | return main_options + self.main_options |
||
97 | |||
98 | def update_advanced_options(self, advanced_options): |
||
99 | return advanced_options + self.advanced_options |
||
100 | |||
101 | def generate_vaapi_main_args(self): |
||
102 | """ |
||
103 | Generate a list of args for using a VAAPI decoder |
||
104 | |||
105 | :return: |
||
106 | """ |
||
107 | # Check if we are using a VAAPI encoder also... |
||
108 | if self.video_encoder and "vaapi" in self.video_encoder.lower(): |
||
109 | if self.enable_hardware_accelerated_decoding: |
||
110 | # Configure args such that when the input may or may not be hardware decodable we can do: |
||
111 | # REF: https://trac.ffmpeg.org/wiki/Hardware/VAAPI#Encoding |
||
112 | self.main_options = [ |
||
113 | "-init_hw_device", "vaapi=vaapi0:{}".format(self.hardware_device.get('hwaccel_device')), |
||
114 | "-hwaccel", "vaapi", |
||
115 | "-hwaccel_output_format", "vaapi", |
||
116 | "-hwaccel_device", "vaapi0", |
||
117 | ] |
||
118 | # Use 'NV12' for hardware surfaces. I would think that 10-bit encoding encoding using |
||
119 | # the P010 input surfaces is an advanced feature |
||
120 | self.advanced_options = [ |
||
121 | "-filter_hw_device", "vaapi0", |
||
122 | "-vf", "format=nv12|vaapi,hwupload", |
||
123 | ] |
||
124 | else: |
||
125 | # Encode only (no decoding) |
||
126 | # REF: https://trac.ffmpeg.org/wiki/Hardware/VAAPI#Encode-only (sorta) |
||
127 | self.main_options = [ |
||
128 | "-vaapi_device", self.hardware_device.get('hwaccel_device'), |
||
129 | ] |
||
130 | # Use 'NV12' for hardware surfaces. I would think that 10-bit encoding encoding using |
||
131 | # the P010 input surfaces is an advanced feature |
||
132 | self.advanced_options = [ |
||
133 | "-vf", "format=nv12|vaapi,hwupload", |
||
134 | ] |
||
135 | else: |
||
136 | # Decode an input with hardware if possible, output in normal memory to encode with another encoder not vaapi: |
||
137 | # REF: https://trac.ffmpeg.org/wiki/Hardware/VAAPI#Decode-only |
||
138 | self.main_options = [ |
||
139 | "-hwaccel", "vaapi", |
||
140 | "-hwaccel_device", self.hardware_device.get('hwaccel_device') |
||
141 | ] |
||
142 | |||
143 | def generate_cuda_main_args(self): |
||
144 | """ |
||
145 | Generate a list of args for using an NVIDIA CUDA decoder |
||
146 | |||
147 | :return: |
||
148 | """ |
||
149 | self.main_options = ["-hwaccel", "cuda", "-hwaccel_device", self.hardware_device.get('hwaccel_device')] |
||
150 | |||
151 | def list_available_cuda_decoders(self): |
||
152 | """ |
||
153 | Check for the existance of a cuda encoder |
||
154 | Credit for code: |
||
155 | https://gist.github.com/f0k/63a664160d016a491b2cbea15913d549 |
||
156 | |||
157 | :return: |
||
158 | """ |
||
159 | decoders = [] |
||
160 | |||
161 | # Search for cuder libs |
||
162 | libnames = ('libcuda.so', 'libcuda.dylib', 'cuda.dll') |
||
163 | for libname in libnames: |
||
164 | try: |
||
165 | cuda = ctypes.CDLL(libname) |
||
166 | except OSError: |
||
167 | continue |
||
168 | else: |
||
169 | break |
||
170 | else: |
||
171 | return decoders |
||
172 | |||
173 | # For the available GPUs found, ensure that there is a cuda device |
||
174 | nGpus = ctypes.c_int() |
||
175 | device = ctypes.c_int() |
||
176 | result = cuda.cuInit(0) |
||
177 | if result != 0: |
||
178 | return decoders |
||
179 | result = cuda.cuDeviceGetCount(ctypes.byref(nGpus)) |
||
180 | if result != 0: |
||
181 | return decoders |
||
182 | |||
183 | # Loop over GPUs and list each one individually |
||
184 | for i in range(nGpus.value): |
||
185 | result = cuda.cuDeviceGet(ctypes.byref(device), i) |
||
186 | if result != 0: |
||
187 | continue |
||
188 | device_data = { |
||
189 | 'hwaccel': 'cuda', |
||
190 | 'hwaccel_device': "{}".format(i), |
||
191 | } |
||
192 | decoders.append(device_data) |
||
193 | |||
194 | return decoders |
||
195 | |||
196 | def list_available_vaapi_devices(self): |
||
197 | """ |
||
198 | Return a list of available VAAPI decoder devices |
||
199 | |||
200 | :return: |
||
201 | """ |
||
202 | decoders = [] |
||
203 | dir_path = os.path.join("/", "dev", "dri") |
||
204 | |||
205 | if os.path.exists(dir_path): |
||
206 | for device in sorted(os.listdir(dir_path)): |
||
207 | if device.startswith('render'): |
||
208 | device_data = { |
||
209 | 'hwaccel': 'vaapi', |
||
210 | 'hwaccel_device': os.path.join("/", "dev", "dri", device), |
||
211 | } |
||
212 | decoders.append(device_data) |
||
213 | |||
214 | # Return the list of decoders |
||
215 | return decoders |
||
216 | |||
217 | |||
218 | if __name__ == "__main__": |
||
219 | hw_a = HardwareAccelerationHandle('blah') |
||
220 | print(hw_a.get_hwaccel_devices()) |
||
221 | for hardware_decoder in hw_a.get_hwaccel_devices(): |
||
222 | hw_a.hardware_device = hardware_decoder |
||
223 | break |
||
224 | print(hw_a.args()) |