kapsikkum-unmanic – Blame information for rev 1

Subversion Repositories:
Rev:
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())