Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/codecs/video_helper.py : 82%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# This file is part of Xpra.
4# Copyright (C) 2013-2019 Antoine Martin <antoine@xpra.org>
5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
6# later version. See the file COPYING for details.
8import sys
9from threading import Lock
11from xpra.codecs.loader import load_codec, get_codec, get_codec_error
12from xpra.util import csv, engs
13from xpra.log import Logger
15log = Logger("codec", "video")
17#the codec loader uses the names...
18#but we need the module name to be able to probe without loading the codec:
19CODEC_TO_MODULE = {
20 "vpx" : "vpx",
21 "x264" : "enc_x264",
22 "x265" : "enc_x265",
23 "nvenc" : "nvenc",
24 "swscale" : "csc_swscale",
25 "cython" : "csc_cython",
26 "libyuv" : "csc_libyuv",
27 "avcodec2" : "dec_avcodec2",
28 "ffmpeg" : "enc_ffmpeg",
29 }
31def has_codec_module(module_name):
32 top_module = "xpra.codecs.%s" % module_name
33 try:
34 __import__(top_module, {}, {}, [])
35 log("codec module %s is installed", module_name)
36 return True
37 except Exception as e:
38 log("codec module %s cannot be loaded: %s", module_name, e)
39 return False
41def try_import_modules(*codec_names):
42 names = []
43 for codec_name in codec_names:
44 module_name = CODEC_TO_MODULE[codec_name]
45 if has_codec_module(module_name):
46 names.append(codec_name)
47 return names
49#all the codecs we know about:
50#try to import the module that contains them (cheap check):
51ALL_VIDEO_ENCODER_OPTIONS = try_import_modules("x264", "vpx", "x265", "nvenc", "ffmpeg")
52HARDWARE_ENCODER_OPTIONS = try_import_modules("nvenc")
53ALL_CSC_MODULE_OPTIONS = try_import_modules("swscale", "cython", "libyuv")
54NO_GFX_CSC_OPTIONS = []
55ALL_VIDEO_DECODER_OPTIONS = try_import_modules("avcodec2", "vpx")
57PREFERRED_ENCODER_ORDER = ("nvenc", "x264", "vpx", "x265")
58PREFERRED_DECODER_ORDER = ("avcodec2", "vpx")
59log("video_helper: ALL_VIDEO_ENCODER_OPTIONS=%s", ALL_VIDEO_ENCODER_OPTIONS)
60log("video_helper: ALL_CSC_MODULE_OPTIONS=%s", ALL_CSC_MODULE_OPTIONS)
61log("video_helper: NO_GFX_CSC_OPTIONS=%s", NO_GFX_CSC_OPTIONS)
62log("video_helper: ALL_VIDEO_DECODER_OPTIONS=%s", ALL_VIDEO_DECODER_OPTIONS)
63#for client side, using the gfx card for csc is a bit silly:
64#use it for OpenGL or don't use it at all
65#on top of that, there are compatibility problems with gtk at times: OpenCL AMD and TLS don't mix well
68def get_encoder_module_name(x):
69 if x.find("enc")>=0:
70 return x #ie: "nvenc" or "enc_vpx"
71 return "enc_"+x #ie: "enc_x264"
73def get_decoder_module_name(x):
74 return "dec_"+x #ie: "dec_vpx"
76def get_csc_module_name(x):
77 return "csc_"+x #ie: "csc_swscale"
81def get_DEFAULT_VIDEO_ENCODERS():
82 """ returns all the video encoders installed """
83 encoders = []
84 for x in tuple(ALL_VIDEO_ENCODER_OPTIONS):
85 mod = get_encoder_module_name(x)
86 c = get_codec(mod)
87 if c:
88 encoders.append(x)
89 break
90 return encoders
92def get_DEFAULT_CSC_MODULES():
93 """ returns all the csc modules installed """
94 csc = []
95 for x in tuple(ALL_CSC_MODULE_OPTIONS):
96 mod = get_csc_module_name(x)
97 c = get_codec(mod)
98 if c:
99 csc.append(x)
100 return csc
102def get_DEFAULT_VIDEO_DECODERS():
103 """ returns all the video decoders installed """
104 decoders = []
105 for x in tuple(ALL_VIDEO_DECODER_OPTIONS):
106 mod = get_decoder_module_name(x)
107 c = get_codec(mod)
108 if c:
109 decoders.append(x)
110 return decoders
113class VideoHelper:
114 """
115 This class is a bit like a registry of known encoders, csc modules and decoders.
116 The main instance, obtained by calling getVideoHelper, can be initialized
117 by the main class, using the command line arguments.
118 We can also clone it to modify it (used by per client proxy encoders)
119 """
121 def __init__(self, vencspecs=None, cscspecs=None, vdecspecs=None, init=False):
122 self._video_encoder_specs = vencspecs or {}
123 self._csc_encoder_specs = cscspecs or {}
124 self._video_decoder_specs = vdecspecs or {}
125 self.video_encoders = []
126 self.csc_modules = []
127 self.video_decoders = []
129 self._cleanup_modules = []
131 #bits needed to ensure we can initialize just once
132 #even when called from multiple threads:
133 self._initialized = init
134 self._lock = Lock()
136 def set_modules(self, video_encoders=(), csc_modules=(), video_decoders=()):
137 assert not self._initialized, "too late to set modules, the helper is already initialized!"
138 def filt(name, inlist, all_list):
139 notfound = tuple(x for x in inlist if x and x not in all_list)
140 if notfound:
141 log.warn("Warning: ignoring unknown %s: %s", name, csv(notfound))
142 return tuple(x for x in inlist if x in all_list)
143 self.video_encoders = filt("video encoders" , video_encoders, ALL_VIDEO_ENCODER_OPTIONS)
144 self.csc_modules = filt("csc modules" , csc_modules, ALL_CSC_MODULE_OPTIONS)
145 self.video_decoders = filt("video decoders" , video_decoders, ALL_VIDEO_DECODER_OPTIONS)
146 log("VideoHelper.set_modules(%s, %s, %s) video encoders=%s, csc=%s, video decoders=%s",
147 csv(video_encoders), csv(csc_modules), csv(video_decoders),
148 csv(self.video_encoders), csv(self.csc_modules), csv(self.video_decoders))
150 def cleanup(self):
151 with self._lock:
152 #check again with lock held (in case of race):
153 if not self._initialized:
154 return
155 cmods = self._cleanup_modules
156 self._cleanup_modules = []
157 log("VideoHelper.cleanup() cleanup modules=%s", cmods)
158 for module in cmods:
159 try:
160 module.cleanup_module()
161 except Exception:
162 log.error("Error cleaning up %s", module, exc_info=True)
163 self._video_encoder_specs = {}
164 self._csc_encoder_specs = {}
165 self._video_decoder_specs = {}
166 self.video_encoders = []
167 self.csc_modules = []
168 self.video_decoders = []
169 self._initialized = False
171 def clone(self):
172 if not self._initialized:
173 self.init()
174 #manual deep-ish copy: make new dictionaries and lists,
175 #but keep the same codec specs:
176 def deepish_clone_dict(indict):
177 outd = {}
178 for enc, d in indict.items():
179 for ifmt, l in d.items():
180 for v in l:
181 outd.setdefault(enc, {}).setdefault(ifmt, []).append(v)
182 return outd
183 ves = deepish_clone_dict(self._video_encoder_specs)
184 ces = deepish_clone_dict(self._csc_encoder_specs)
185 vds = deepish_clone_dict(self._video_decoder_specs)
186 return VideoHelper(ves, ces, vds, True)
188 def get_info(self) -> dict:
189 d = {}
190 einfo = d.setdefault("encoding", {})
191 dinfo = d.setdefault("decoding", {})
192 cinfo = d.setdefault("csc", {})
193 for encoding, encoder_specs in self._video_encoder_specs.items():
194 for in_csc, specs in encoder_specs.items():
195 for spec in specs:
196 einfo.setdefault("%s_to_%s" % (in_csc, encoding), []).append(spec.codec_type)
197 for in_csc, out_specs in self._csc_encoder_specs.items():
198 for out_csc, specs in out_specs.items():
199 cinfo["%s_to_%s" % (in_csc, out_csc)] = [spec.codec_type for spec in specs]
200 for encoding, decoder_specs in self._video_decoder_specs.items():
201 for out_csc, decoders in decoder_specs.items():
202 for decoder in decoders:
203 decoder_name = decoder[0]
204 dinfo.setdefault("%s_to_%s" % (encoding, out_csc), []).append(decoder_name)
205 def modstatus(x, def_list, active_list):
206 #the module is present
207 if x in active_list:
208 return "active"
209 if x in def_list:
210 return "disabled"
211 return "not found"
212 venc = einfo.setdefault("video-encoder", {})
213 for x in ALL_VIDEO_ENCODER_OPTIONS:
214 venc["%s" % x] = modstatus(x, get_DEFAULT_VIDEO_ENCODERS(), self.video_encoders)
215 cscm = einfo.setdefault("csc-module", {})
216 for x in ALL_CSC_MODULE_OPTIONS:
217 cscm["%s" % x] = modstatus(x, get_DEFAULT_CSC_MODULES(), self.csc_modules)
218 return d
220 def init(self):
221 log("VideoHelper.init()")
222 with self._lock:
223 #check again with lock held (in case of race):
224 log("VideoHelper.init() initialized=%s", self._initialized)
225 if self._initialized:
226 return
227 self.init_video_encoders_options()
228 self.init_csc_options()
229 self.init_video_decoders_options()
230 self._initialized = True
231 log("VideoHelper.init() done")
233 def get_encodings(self):
234 return tuple(self._video_encoder_specs.keys())
236 def get_decodings(self):
237 return tuple(self._video_decoder_specs.keys())
239 def get_csc_inputs(self):
240 return tuple(self._csc_encoder_specs.keys())
243 def get_encoder_specs(self, encoding):
244 return self._video_encoder_specs.get(encoding, {})
246 def get_csc_specs(self, src_format):
247 return self._csc_encoder_specs.get(src_format, {})
249 def get_decoder_specs(self, encoding):
250 return self._video_decoder_specs.get(encoding, {})
253 def init_video_encoders_options(self):
254 log("init_video_encoders_options()")
255 log(" will try video encoders: %s", csv(self.video_encoders))
256 for x in self.video_encoders:
257 try:
258 mod = get_encoder_module_name(x)
259 load_codec(mod)
260 log(" encoder for %s: %s", x, mod)
261 try:
262 self.init_video_encoder_option(mod)
263 except Exception as e:
264 log(" init_video_encoder_option(%s) error", mod, exc_info=True)
265 log.warn("Warning: cannot load %s video encoder:", mod)
266 log.warn(" %s", e)
267 del e
268 except Exception as e:
269 log("error on %s", x, exc_info=True)
270 log.warn("Warning: cannot add %s encoder: %s", x, e)
271 del e
272 log("found %i video encoder%s: %s",
273 len(self._video_encoder_specs), engs(self._video_encoder_specs), csv(self._video_encoder_specs))
275 def init_video_encoder_option(self, encoder_name):
276 encoder_module = get_codec(encoder_name)
277 log("init_video_encoder_option(%s)", encoder_name)
278 log(" module=%s", encoder_module)
279 if not encoder_module:
280 log(" video encoder '%s' could not be loaded:", encoder_name)
281 log(" %s", get_codec_error(encoder_name))
282 return
283 encoder_type = encoder_module.get_type()
284 encodings = encoder_module.get_encodings()
285 log(" %12s encodings=%s", encoder_type, csv(encodings))
286 for encoding in encodings:
287 colorspaces = encoder_module.get_input_colorspaces(encoding)
288 log(" %9s input colorspaces for %5s: %s", encoder_type, encoding, csv(colorspaces))
289 for colorspace in colorspaces:
290 spec = encoder_module.get_spec(encoding, colorspace)
291 self.add_encoder_spec(encoding, colorspace, spec)
292 log("video encoder options: %s", self._video_encoder_specs)
294 def add_encoder_spec(self, encoding, colorspace, spec):
295 self._video_encoder_specs.setdefault(encoding, {}).setdefault(colorspace, []).append(spec)
298 def init_csc_options(self):
299 log("init_csc_options()")
300 log(" will try csc modules: %s", csv(self.csc_modules))
301 for x in self.csc_modules:
302 try:
303 mod = get_csc_module_name(x)
304 load_codec(mod)
305 self.init_csc_option(mod)
306 except Exception:
307 log.warn("init_csc_options() cannot add %s csc", x, exc_info=True)
308 log(" csc specs: %s", csv(self._csc_encoder_specs))
309 for src_format, d in sorted(self._csc_encoder_specs.items()):
310 log(" %s - %s options:", src_format, len(d))
311 for dst_format, specs in sorted(d.items()):
312 log(" * %7s via: %s", dst_format, csv(sorted(spec.codec_type for spec in specs)))
313 log("csc options: %s", self._csc_encoder_specs)
315 def init_csc_option(self, csc_name):
316 csc_module = get_codec(csc_name)
317 log("init_csc_option(%s)", csc_name)
318 log(" module=%s", csc_module)
319 if csc_module is None:
320 log(" csc module %s could not be loaded:", csc_name)
321 log(" %s", get_codec_error(csc_name))
322 return
323 in_cscs = csc_module.get_input_colorspaces()
324 for in_csc in in_cscs:
325 out_cscs = csc_module.get_output_colorspaces(in_csc)
326 log("%9s output colorspaces for %7s: %s", csc_module.get_type(), in_csc, csv(out_cscs))
327 for out_csc in out_cscs:
328 spec = csc_module.get_spec(in_csc, out_csc)
329 self.add_csc_spec(in_csc, out_csc, spec)
331 def add_csc_spec(self, in_csc, out_csc, spec):
332 self._csc_encoder_specs.setdefault(in_csc, {}).setdefault(out_csc, []).append(spec)
335 def init_video_decoders_options(self):
336 log("init_video_decoders_options()")
337 log(" will try video decoders: %s", csv(self.video_decoders))
338 for x in self.video_decoders:
339 try:
340 mod = get_decoder_module_name(x)
341 load_codec(mod)
342 self.init_video_decoder_option(mod)
343 except Exception:
344 log.warn("Warning: cannot add %s decoder", x, exc_info=True)
345 log("found %s video decoder%s: %s",
346 len(self._video_decoder_specs), engs(self._video_decoder_specs), csv(self._video_decoder_specs))
347 log("video decoder options: %s", self._video_decoder_specs)
349 def init_video_decoder_option(self, decoder_name):
350 decoder_module = get_codec(decoder_name)
351 log("init_video_decoder_option(%s)", decoder_name)
352 log(" module=%s", decoder_module)
353 if not decoder_module:
354 log(" video decoder %s could not be loaded:", decoder_name)
355 log(" %s", get_codec_error(decoder_name))
356 return
357 decoder_type = decoder_module.get_type()
358 encodings = decoder_module.get_encodings()
359 log(" %s encodings=%s", decoder_type, csv(encodings))
360 for encoding in encodings:
361 colorspaces = decoder_module.get_input_colorspaces(encoding)
362 log(" %s input colorspaces for %s: %s", decoder_type, encoding, csv(colorspaces))
363 for colorspace in colorspaces:
364 output_colorspace = decoder_module.get_output_colorspace(encoding, colorspace)
365 log(" %s output colorspace for %5s/%7s: %s", decoder_type, encoding, colorspace, output_colorspace)
366 try:
367 assert decoder_module.Decoder
368 self.add_decoder(encoding, colorspace, decoder_name, decoder_module)
369 except Exception as e:
370 log.warn("failed to add decoder %s: %s", decoder_module, e)
371 del e
373 def add_decoder(self, encoding, colorspace, decoder_name, decoder_module):
374 self._video_decoder_specs.setdefault(
375 encoding, {}).setdefault(
376 colorspace, []).append(
377 (decoder_name, decoder_module)
378 )
381 def get_server_full_csc_modes(self, *client_supported_csc_modes):
382 """ given a list of CSC modes the client can handle,
383 returns the CSC modes per encoding that the server can encode with.
384 (taking into account the decoder's actual output colorspace for each encoding)
385 """
386 log("get_server_full_csc_modes(%s) decoder encodings=%s",
387 client_supported_csc_modes, self._video_decoder_specs.keys())
388 full_csc_modes = {}
389 for encoding, encoding_specs in self._video_decoder_specs.items():
390 assert encoding_specs is not None
391 for colorspace, decoder_specs in sorted(encoding_specs.items()):
392 for decoder_name, decoder_module in decoder_specs:
393 #figure out the actual output colorspace:
394 output_colorspace = decoder_module.get_output_colorspace(encoding, colorspace)
395 log("found decoder %12s for %5s with %7s mode, outputs '%s'",
396 decoder_name, encoding, colorspace, output_colorspace)
397 if output_colorspace in client_supported_csc_modes:
398 encoding_colorspaces = full_csc_modes.setdefault(encoding, [])
399 if colorspace not in encoding_colorspaces:
400 encoding_colorspaces.append(colorspace)
401 log("get_server_full_csc_modes(%s)=%s", client_supported_csc_modes, full_csc_modes)
402 return full_csc_modes
405 def get_server_full_csc_modes_for_rgb(self, *target_rgb_modes):
406 """ given a list of RGB modes the client can handle,
407 returns the CSC modes per encoding that the server can encode with,
408 this will include the RGB modes themselves too.
409 """
410 log("get_server_full_csc_modes_for_rgb%s", target_rgb_modes)
411 supported_csc_modes = list(target_rgb_modes)
412 for src_format, specs in self._csc_encoder_specs.items():
413 for dst_format, csc_specs in specs.items():
414 if dst_format in target_rgb_modes and csc_specs:
415 supported_csc_modes.append(src_format)
416 break
417 supported_csc_modes = sorted(supported_csc_modes)
418 return self.get_server_full_csc_modes(*supported_csc_modes)
421instance = None
422def getVideoHelper():
423 global instance
424 if instance is None:
425 instance = VideoHelper()
426 return instance
429def main():
430 from xpra.codecs.loader import log as loader_log, load_codecs
431 from xpra.util import print_nested_dict
432 from xpra.log import enable_color
433 from xpra.platform import program_context
434 with program_context("Video Helper"):
435 enable_color()
436 if "-v" in sys.argv or "--verbose" in sys.argv:
437 loader_log.enable_debug()
438 log.enable_debug()
439 load_codecs()
440 vh = getVideoHelper()
441 vh.set_modules(ALL_VIDEO_ENCODER_OPTIONS, ALL_CSC_MODULE_OPTIONS, ALL_VIDEO_DECODER_OPTIONS)
442 vh.init()
443 info = vh.get_info()
444 print_nested_dict(info)
447if __name__ == "__main__":
448 main()