Hide keyboard shortcuts

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. 

7 

8import sys 

9from threading import Lock 

10 

11from xpra.codecs.loader import load_codec, get_codec, get_codec_error 

12from xpra.util import csv, engs 

13from xpra.log import Logger 

14 

15log = Logger("codec", "video") 

16 

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 } 

30 

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 

40 

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 

48 

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") 

56 

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 

66 

67 

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" 

72 

73def get_decoder_module_name(x): 

74 return "dec_"+x #ie: "dec_vpx" 

75 

76def get_csc_module_name(x): 

77 return "csc_"+x #ie: "csc_swscale" 

78 

79 

80 

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 

91 

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 

101 

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 

111 

112 

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 """ 

120 

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 = [] 

128 

129 self._cleanup_modules = [] 

130 

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() 

135 

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)) 

149 

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 

170 

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) 

187 

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 

219 

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") 

232 

233 def get_encodings(self): 

234 return tuple(self._video_encoder_specs.keys()) 

235 

236 def get_decodings(self): 

237 return tuple(self._video_decoder_specs.keys()) 

238 

239 def get_csc_inputs(self): 

240 return tuple(self._csc_encoder_specs.keys()) 

241 

242 

243 def get_encoder_specs(self, encoding): 

244 return self._video_encoder_specs.get(encoding, {}) 

245 

246 def get_csc_specs(self, src_format): 

247 return self._csc_encoder_specs.get(src_format, {}) 

248 

249 def get_decoder_specs(self, encoding): 

250 return self._video_decoder_specs.get(encoding, {}) 

251 

252 

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)) 

274 

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) 

293 

294 def add_encoder_spec(self, encoding, colorspace, spec): 

295 self._video_encoder_specs.setdefault(encoding, {}).setdefault(colorspace, []).append(spec) 

296 

297 

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) 

314 

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) 

330 

331 def add_csc_spec(self, in_csc, out_csc, spec): 

332 self._csc_encoder_specs.setdefault(in_csc, {}).setdefault(out_csc, []).append(spec) 

333 

334 

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) 

348 

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 

372 

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 ) 

379 

380 

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 

403 

404 

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) 

419 

420 

421instance = None 

422def getVideoHelper(): 

423 global instance 

424 if instance is None: 

425 instance = VideoHelper() 

426 return instance 

427 

428 

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) 

445 

446 

447if __name__ == "__main__": 

448 main()