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# This file is part of Xpra. 

2# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

3# Copyright (C) 2010-2019 Antoine Martin <antoine@xpra.org> 

4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

5# later version. See the file COPYING for details. 

6 

7import os 

8 

9from xpra.codecs.codec_constants import PREFERRED_ENCODING_ORDER, PROBLEMATIC_ENCODINGS 

10from xpra.codecs.loader import load_codec, codec_versions, has_codec, get_codec 

11from xpra.codecs.video_helper import getVideoHelper, NO_GFX_CSC_OPTIONS 

12from xpra.scripts.config import parse_bool_or_int 

13from xpra.net import compression 

14from xpra.util import envint, envbool, updict, csv, typedict 

15from xpra.client.mixins.stub_client_mixin import StubClientMixin 

16from xpra.log import Logger 

17 

18log = Logger("client", "encoding") 

19 

20B_FRAMES = envbool("XPRA_B_FRAMES", True) 

21PAINT_FLUSH = envbool("XPRA_PAINT_FLUSH", True) 

22MAX_SOFT_EXPIRED = envint("XPRA_MAX_SOFT_EXPIRED", 5) 

23SEND_TIMESTAMPS = envbool("XPRA_SEND_TIMESTAMPS", False) 

24VIDEO_MAX_SIZE = tuple(int(x) for x in os.environ.get("XPRA_VIDEO_MAX_SIZE", "4096,4096").replace("x", ",").split(",")) 

25SCROLL_ENCODING = envbool("XPRA_SCROLL_ENCODING", True) 

26 

27#we assume that any server will support at least those: 

28DEFAULT_ENCODINGS = os.environ.get("XPRA_DEFAULT_ENCODINGS", "rgb32,rgb24,jpeg,png").split(",") 

29 

30 

31""" 

32Mixin for adding encodings to a client 

33""" 

34class Encodings(StubClientMixin): 

35 

36 def __init__(self): 

37 StubClientMixin.__init__(self) 

38 self.allowed_encodings = [] 

39 self.encoding = None 

40 self.quality = -1 

41 self.min_quality = 0 

42 self.speed = 0 

43 self.min_speed = -1 

44 self.video_scaling = None 

45 self.video_max_size = VIDEO_MAX_SIZE 

46 

47 self.server_encodings = [] 

48 self.server_core_encodings = [] 

49 self.server_encodings_problematic = PROBLEMATIC_ENCODINGS 

50 self.server_encodings_with_speed = () 

51 self.server_encodings_with_quality = () 

52 self.server_encodings_with_lossless_mode = () 

53 

54 #what we told the server about our encoding defaults: 

55 self.encoding_defaults = {} 

56 

57 

58 def init(self, opts): 

59 self.allowed_encodings = opts.encodings 

60 self.encoding = opts.encoding 

61 if opts.video_scaling.lower() in ("auto", "on"): 

62 self.video_scaling = None 

63 else: 

64 self.video_scaling = parse_bool_or_int("video-scaling", opts.video_scaling) 

65 self.quality = opts.quality 

66 self.min_quality = opts.min_quality 

67 self.speed = opts.speed 

68 self.min_speed = opts.min_speed 

69 load_codec("dec_pillow") 

70 ae = self.allowed_encodings 

71 if "jpeg" in ae: 

72 #try to load the fast jpeg encoder: 

73 load_codec("dec_jpeg") 

74 if "webp" in ae: 

75 #try to load the fast webp encoder: 

76 load_codec("dec_webp") 

77 vh = getVideoHelper() 

78 vh.set_modules(video_decoders=opts.video_decoders, csc_modules=opts.csc_modules or NO_GFX_CSC_OPTIONS) 

79 vh.init() 

80 

81 

82 def cleanup(self): 

83 try: 

84 getVideoHelper().cleanup() 

85 except Exception: # pragma: no cover 

86 log.error("error on video cleanup", exc_info=True) 

87 

88 

89 def init_authenticated_packet_handlers(self): 

90 self.add_packet_handler("encodings", self._process_encodings, False) 

91 

92 

93 def _process_encodings(self, packet): 

94 caps = typedict(packet[1]) 

95 self._parse_server_capabilities(caps) 

96 

97 

98 def get_info(self): 

99 return { 

100 "encodings" : { 

101 "core" : self.get_core_encodings(), 

102 "window-icon" : self.get_window_icon_encodings(), 

103 "cursor" : self.get_cursor_encodings(), 

104 "quality" : self.quality, 

105 "min-quality" : self.min_quality, 

106 "speed" : self.speed, 

107 "min-speed" : self.min_speed, 

108 "encoding" : self.encoding or "auto", 

109 "video-scaling" : self.video_scaling if self.video_scaling is not None else "auto", 

110 }, 

111 "server-encodings" : self.server_core_encodings, 

112 } 

113 

114 

115 def get_caps(self) -> dict: 

116 caps = { 

117 "encodings" : self.get_encodings(), 

118 "encodings.core" : self.get_core_encodings(), 

119 "encodings.window-icon" : self.get_window_icon_encodings(), 

120 "encodings.cursor" : self.get_cursor_encodings(), 

121 "encodings.packet" : True, 

122 } 

123 updict(caps, "batch", self.get_batch_caps()) 

124 updict(caps, "encoding", self.get_encodings_caps()) 

125 return caps 

126 

127 def parse_server_capabilities(self, caps : typedict) -> bool: 

128 self._parse_server_capabilities(caps) 

129 return True 

130 

131 def _parse_server_capabilities(self, c): 

132 self.server_encodings = c.strtupleget("encodings", DEFAULT_ENCODINGS) 

133 self.server_core_encodings = c.strtupleget("encodings.core", self.server_encodings) 

134 #server is telling us to try to avoid those: 

135 self.server_encodings_problematic = c.strtupleget("encodings.problematic", PROBLEMATIC_ENCODINGS) 

136 #old servers only supported x264: 

137 self.server_encodings_with_speed = c.strtupleget("encodings.with_speed", ("h264",)) 

138 self.server_encodings_with_quality = c.strtupleget("encodings.with_quality", ("jpeg", "webp", "h264")) 

139 self.server_encodings_with_lossless_mode = c.strtupleget("encodings.with_lossless_mode", ()) 

140 e = c.strget("encoding") 

141 if e and not c.boolget("encodings.delayed"): 

142 if self.encoding and e!=self.encoding: 

143 if self.encoding not in self.server_core_encodings: 

144 log.warn("server does not support %s encoding and has switched to %s", self.encoding, e) 

145 else: 

146 log.info("server is using %s encoding instead of %s", e, self.encoding) 

147 self.encoding = e 

148 

149 

150 def get_batch_caps(self) -> dict: 

151 #batch options: 

152 caps = {} 

153 for bprop in ("always", "min_delay", "max_delay", "delay", "max_events", "max_pixels", "time_unit"): 

154 evalue = os.environ.get("XPRA_BATCH_%s" % bprop.upper()) 

155 if evalue: 

156 try: 

157 caps["batch.%s" % bprop] = int(evalue) 

158 except ValueError: 

159 log.error("Error: invalid environment value for %s: %s", bprop, evalue) 

160 log("get_batch_caps()=%s", caps) 

161 return caps 

162 

163 def get_encodings_caps(self) -> dict: 

164 if B_FRAMES: 

165 video_b_frames = ("h264", ) #only tested with dec_avcodec2 

166 else: 

167 video_b_frames = () 

168 caps = { 

169 "flush" : PAINT_FLUSH, #v4 servers assume this is available 

170 "video_scaling" : True, #v4 servers assume this is available 

171 "video_b_frames" : video_b_frames, 

172 "video_max_size" : self.video_max_size, 

173 "max-soft-expired" : MAX_SOFT_EXPIRED, 

174 "send-timestamps" : SEND_TIMESTAMPS, 

175 } 

176 if self.video_scaling is not None: 

177 caps["scaling.control"] = self.video_scaling 

178 if self.encoding: 

179 caps[""] = self.encoding 

180 for k,v in codec_versions.items(): 

181 caps["%s.version" % k] = v 

182 if self.quality>0: 

183 caps["quality"] = self.quality 

184 if self.min_quality>0: 

185 caps["min-quality"] = self.min_quality 

186 if self.speed>=0: 

187 caps["speed"] = self.speed 

188 if self.min_speed>=0: 

189 caps["min-speed"] = self.min_speed 

190 

191 #generic rgb compression flags: 

192 for x in compression.ALL_COMPRESSORS: 

193 caps["rgb_%s" % x] = x in compression.get_enabled_compressors() 

194 #these are the defaults - when we instantiate a window, 

195 #we can send different values as part of the map event 

196 #these are the RGB modes we want (the ones we are expected to be able to paint with): 

197 rgb_formats = ["RGB", "RGBX", "RGBA"] 

198 caps["rgb_formats"] = rgb_formats 

199 #figure out which CSC modes (usually YUV) can give us those RGB modes: 

200 full_csc_modes = getVideoHelper().get_server_full_csc_modes_for_rgb(*rgb_formats) 

201 if has_codec("dec_webp"): 

202 if self.opengl_enabled: 

203 full_csc_modes["webp"] = ("BGRX", "BGRA", "RGBX", "RGBA") 

204 else: 

205 full_csc_modes["webp"] = ("BGRX", "BGRA", ) 

206 log("supported full csc_modes=%s", full_csc_modes) 

207 caps["full_csc_modes"] = full_csc_modes 

208 

209 if "h264" in self.get_core_encodings(): 

210 # some profile options: "baseline", "main", "high", "high10", ... 

211 # set the default to "high10" for YUV420P 

212 # as the python client always supports all the profiles 

213 # whereas on the server side, the default is baseline to accomodate less capable clients. 

214 # YUV422P requires high422, and 

215 # YUV444P requires high444, 

216 # so we don't bother specifying anything for those two. 

217 h264_caps = {} 

218 for csc_name, default_profile in ( 

219 ("YUV420P", "high10"), 

220 ("YUV422P", ""), 

221 ("YUV444P", "")): 

222 profile = os.environ.get("XPRA_H264_%s_PROFILE" % (csc_name), default_profile) 

223 if profile: 

224 h264_caps["%s.profile" % (csc_name)] = profile 

225 h264_caps["fast-decode"] = envbool("XPRA_X264_FAST_DECODE", False) 

226 log("x264 encoding options: %s", h264_caps) 

227 updict(caps, "h264", h264_caps) 

228 log("encoding capabilities: %s", caps) 

229 return caps 

230 

231 def get_encodings(self): 

232 """ 

233 Unlike get_core_encodings(), this method returns "rgb" for both "rgb24" and "rgb32". 

234 That's because although we may support both, the encoding chosen is plain "rgb", 

235 and the actual encoding used ("rgb24" or "rgb32") depends on the window's bit depth. 

236 ("rgb32" if there is an alpha channel, and if the client supports it) 

237 """ 

238 cenc = self.get_core_encodings() 

239 if ("rgb24" in cenc or "rgb32" in cenc) and "rgb" not in cenc: 

240 cenc.append("rgb") 

241 if "grayscale" not in cenc: 

242 cenc.append("grayscale") 

243 return [x for x in PREFERRED_ENCODING_ORDER if x in cenc and x not in ("rgb32", "rgb24")] 

244 

245 def get_cursor_encodings(self): 

246 e = ["raw"] 

247 if "png" in self.get_core_encodings(): 

248 e.append("png") 

249 return e 

250 

251 def get_window_icon_encodings(self): 

252 e = ["premult_argb32", "BGRA", "default"] 

253 if "png" in self.get_core_encodings(): 

254 e.append("png") 

255 return e 

256 

257 def get_core_encodings(self): 

258 """ 

259 This method returns the actual encodings supported. 

260 ie: ["rgb24", "vp8", "webp", "png", "png/L", "png/P", "jpeg", "h264", "vpx"] 

261 It is often overriden in the actual client class implementations, 

262 where extra encodings can be added (generally just 'rgb32' for transparency), 

263 or removed if the toolkit implementation class is more limited. 

264 """ 

265 #we always support rgb: 

266 core_encodings = ["rgb24", "rgb32"] 

267 for codec in ("dec_pillow", "dec_webp", "dec_jpeg"): 

268 if has_codec(codec): 

269 c = get_codec(codec) 

270 encs = c.get_encodings() 

271 log("%s.get_encodings()=%s", codec, encs) 

272 for e in encs: 

273 if e not in core_encodings: 

274 core_encodings.append(e) 

275 if SCROLL_ENCODING: 

276 core_encodings.append("scroll") 

277 #we enable all the video decoders we know about, 

278 #what will actually get used by the server will still depend on the csc modes supported 

279 video_decodings = getVideoHelper().get_decodings() 

280 log("video_decodings=%s", video_decodings) 

281 for encoding in video_decodings: 

282 if encoding not in core_encodings: 

283 core_encodings.append(encoding) 

284 #remove duplicates and use prefered encoding order: 

285 core_encodings = [x for x in PREFERRED_ENCODING_ORDER 

286 if x in set(core_encodings) and x in self.allowed_encodings] 

287 return core_encodings 

288 

289 def set_encoding(self, encoding): 

290 log("set_encoding(%s)", encoding) 

291 if encoding=="auto": 

292 self.encoding = "" 

293 else: 

294 assert encoding in self.get_encodings(), "encoding %s is not supported!" % encoding 

295 if encoding not in self.server_encodings: 

296 log.error("Error: encoding %s is not supported by the server", encoding) 

297 log.error(" the only encodings allowed are:") 

298 log.error(" %s", csv(self.server_encodings)) 

299 return 

300 self.encoding = encoding 

301 self.send("encoding", self.encoding) 

302 

303 def send_quality(self): 

304 q = self.quality 

305 log("send_quality() quality=%s", q) 

306 assert q==-1 or 0<=q<=100, "invalid quality: %s" % q 

307 self.send("quality", q) 

308 

309 def send_min_quality(self): 

310 q = self.min_quality 

311 log("send_min_quality() min-quality=%s", q) 

312 assert q==-1 or 0<=q<=100, "invalid min-quality: %s" % q 

313 self.send("min-quality", q) 

314 

315 def send_speed(self): 

316 s = self.speed 

317 log("send_speed() min-speed=%s", s) 

318 assert s==-1 or 0<=s<=100, "invalid speed: %s" % s 

319 self.send("speed", s) 

320 

321 def send_min_speed(self): 

322 s = self.min_speed 

323 log("send_min_speed() min-speed=%s", s) 

324 assert s==-1 or 0<=s<=100, "invalid min-speed: %s" % s 

325 self.send("min-speed", s)