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# -*- coding: utf-8 -*- 

2# This file is part of Xpra. 

3# Copyright (C) 2015-2020 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 

7#pylint: disable=line-too-long 

8 

9import binascii 

10 

11from xpra.util import csv, typedict, roundup 

12from xpra.log import Logger 

13log = Logger("encoding") 

14 

15#Warning: many systems will fail above 8k because of memory constraints 

16# encoders can allocate many times more memory to hold the frames.. 

17TEST_LIMIT_W, TEST_LIMIT_H = 8192, 8192 

18 

19 

20#this test data was generated using a 24x16 blank image as input 

21TEST_COMPRESSED_DATA = { 

22 "h264": { 

23 "YUV420P" : binascii.unhexlify("000000016764000aacb317cbc2000003000200000300651e244cd00000000168e970312c8b0000010605ffff56dc45e9bde6d948b7962cd820d923eeef78323634202d20636f726520313432202d20482e3236342f4d5045472d342041564320636f646563202d20436f70796c65667420323030332d32303134202d20687474703a2f2f7777772e766964656f6c616e2e6f72672f783236342e68746d6c202d206f7074696f6e733a2063616261633d31207265663d35206465626c6f636b3d313a303a3020616e616c7973653d3078333a3078313133206d653d756d68207375626d653d38207073793d31207073795f72643d312e30303a302e3030206d697865645f7265663d31206d655f72616e67653d3136206368726f6d615f6d653d31207472656c6c69733d31203878386463743d312063716d3d3020646561647a6f6e653d32312c313120666173745f70736b69703d31206368726f6d615f71705f6f66667365743d2d3220746872656164733d31206c6f6f6b61686561645f746872656164733d3120736c696365645f746872656164733d30206e723d3020646563696d6174653d3120696e7465726c616365643d3020626c757261795f636f6d7061743d3020636f6e73747261696e65645f696e7472613d3020626672616d65733d3020776569676874703d32206b6579696e743d393939393939206b6579696e745f6d696e3d353030303030207363656e656375743d343020696e7472615f726566726573683d302072633d637266206d62747265653d30206372663d33382e322071636f6d703d302e36302071706d696e3d302071706d61783d3639207170737465703d342069705f726174696f3d312e34302061713d313a312e3030008000000165888404bffe841fc0a667f891ea1728763fecb5e1"), 

24 "YUV422P" : binascii.unhexlify("00000001677a000abcb317cbc2000003000200000300651e244cd00000000168e970312c8b0000010605ffff56dc45e9bde6d948b7962cd820d923eeef78323634202d20636f726520313432202d20482e3236342f4d5045472d342041564320636f646563202d20436f70796c65667420323030332d32303134202d20687474703a2f2f7777772e766964656f6c616e2e6f72672f783236342e68746d6c202d206f7074696f6e733a2063616261633d31207265663d35206465626c6f636b3d313a303a3020616e616c7973653d3078333a3078313133206d653d756d68207375626d653d38207073793d31207073795f72643d312e30303a302e3030206d697865645f7265663d31206d655f72616e67653d3136206368726f6d615f6d653d31207472656c6c69733d31203878386463743d312063716d3d3020646561647a6f6e653d32312c313120666173745f70736b69703d31206368726f6d615f71705f6f66667365743d2d3220746872656164733d31206c6f6f6b61686561645f746872656164733d3120736c696365645f746872656164733d30206e723d3020646563696d6174653d3120696e7465726c616365643d3020626c757261795f636f6d7061743d3020636f6e73747261696e65645f696e7472613d3020626672616d65733d3020776569676874703d32206b6579696e743d393939393939206b6579696e745f6d696e3d353030303030207363656e656375743d343020696e7472615f726566726573683d302072633d637266206d62747265653d30206372663d33382e322071636f6d703d302e36302071706d696e3d302071706d61783d3639207170737465703d342069705f726174696f3d312e34302061713d313a312e3030008000000165888404bffe841fc0a667f891ec3d121e72aecb5f"), 

25 "YUV444P" : binascii.unhexlify("0000000167f4000a919662f89e1000000300100000030328f12266800000000168e970311121100000010605ffff55dc45e9bde6d948b7962cd820d923eeef78323634202d20636f726520313432202d20482e3236342f4d5045472d342041564320636f646563202d20436f70796c65667420323030332d32303134202d20687474703a2f2f7777772e766964656f6c616e2e6f72672f783236342e68746d6c202d206f7074696f6e733a2063616261633d31207265663d35206465626c6f636b3d313a303a3020616e616c7973653d3078333a3078313133206d653d756d68207375626d653d38207073793d31207073795f72643d312e30303a302e3030206d697865645f7265663d31206d655f72616e67653d3136206368726f6d615f6d653d31207472656c6c69733d31203878386463743d312063716d3d3020646561647a6f6e653d32312c313120666173745f70736b69703d31206368726f6d615f71705f6f66667365743d3420746872656164733d31206c6f6f6b61686561645f746872656164733d3120736c696365645f746872656164733d30206e723d3020646563696d6174653d3120696e7465726c616365643d3020626c757261795f636f6d7061743d3020636f6e73747261696e65645f696e7472613d3020626672616d65733d3020776569676874703d32206b6579696e743d393939393939206b6579696e745f6d696e3d353030303030207363656e656375743d343020696e7472615f726566726573683d302072633d637266206d62747265653d30206372663d33382e322071636f6d703d302e36302071706d696e3d302071706d61783d3639207170737465703d342069705f726174696f3d312e34302061713d313a312e3030008000000165888404bffeeb1fc0a667f75e658f9a9fccb1f341ffff"), 

26 }, 

27 "vp8" : { 

28 "YUV420P" : binascii.unhexlify("1003009d012a1800100000070885858899848800281013ad501fc01fd01050122780feffbb029ffffa2546bd18c06f7ffe8951fffe8951af46301bdfffa22a00"), 

29 }, 

30 "vp9" : { 

31 "YUV420P" : binascii.unhexlify("8249834200017000f60038241c18000000200000047ffffffba9da00059fffffff753b413bffffffeea7680000"), 

32 "YUV444P" : binascii.unhexlify("a249834200002e001ec007048383000000040000223fffffeea76800c7ffffffeea7680677ffffff753b40081000"), 

33 }, 

34} 

35 

36def makebuf(size, b=0x20): 

37 return (chr(b).encode())*size 

38 

39 

40def make_test_image(pixel_format, w, h): 

41 from xpra.codecs.image_wrapper import ImageWrapper 

42 from xpra.codecs.codec_constants import get_subsampling_divs 

43 #import time 

44 #start = monotonic_time() 

45 if pixel_format.startswith("YUV") or pixel_format.startswith("GBRP") or pixel_format=="NV12": 

46 divs = get_subsampling_divs(pixel_format) 

47 try: 

48 depth = int(pixel_format.split("P")[1]) #ie: YUV444P10 -> 10 

49 except (IndexError, ValueError): 

50 depth = 8 

51 Bpp = roundup(depth, 8)//8 

52 nplanes = len(divs) 

53 ydiv = divs[0] #always (1, 1) 

54 y = makebuf(w//ydiv[0]*h//ydiv[1]*Bpp) 

55 udiv = divs[1] 

56 u = makebuf(w//udiv[0]*h//udiv[1]*Bpp) 

57 planes = [y, u] 

58 strides = [w//ydiv[0]*Bpp, w//udiv[0]*Bpp] 

59 if nplanes==3: 

60 vdiv = divs[2] 

61 v = makebuf(w//vdiv[0]*h//vdiv[1]*Bpp) 

62 planes.append(v) 

63 strides.append(w//vdiv[0]*Bpp) 

64 image = ImageWrapper(0, 0, w, h, planes, pixel_format, 32, strides, planes=nplanes, thread_safe=True) 

65 #l = len(y)+len(u)+len(v) 

66 elif pixel_format in ("RGB", "BGR", "RGBX", "BGRX", "XRGB", "BGRA", "RGBA", "r210", "BGR48"): 

67 if pixel_format=="BGR48": 

68 stride = w*6 

69 else: 

70 stride = w*len(pixel_format) 

71 rgb_data = makebuf(stride*h) 

72 image = ImageWrapper(0, 0, w, h, rgb_data, pixel_format, 32, stride, planes=ImageWrapper.PACKED, thread_safe=True) 

73 #l = len(rgb_data) 

74 else: 

75 raise Exception("don't know how to create a %s image" % pixel_format) 

76 #log("make_test_image%30s took %3ims for %6iMBytes", 

77 # (pixel_format, w, h), 1000*(monotonic_time()-start), l//1024//1024) 

78 return image 

79 

80 

81def testdecoder(decoder_module, full): 

82 codecs = list(decoder_module.get_encodings()) 

83 for encoding in tuple(codecs): 

84 try: 

85 testdecoding(decoder_module, encoding, full) 

86 except Exception as e: 

87 log("%s: %s decoding failed", decoder_module.get_type(), encoding, exc_info=True) 

88 log.warn("%s: %s decoding failed: %s", decoder_module.get_type(), encoding, e) 

89 del e 

90 codecs.remove(encoding) 

91 if not codecs: 

92 log.error("%s: all the codecs have failed! (%s)", 

93 decoder_module.get_type(), csv(decoder_module.get_encodings())) 

94 return tuple(codecs) 

95 

96def testdecoding(decoder_module, encoding, full): 

97 W = 24 

98 H = 16 

99 test_data_set = TEST_COMPRESSED_DATA.get(encoding) 

100 if not test_data_set: 

101 log("%s: no test data for %s", decoder_module.get_type(), encoding) 

102 return 

103 for cs in decoder_module.get_input_colorspaces(encoding): 

104 e = decoder_module.Decoder() 

105 try: 

106 e.init_context(encoding, W, H, cs) 

107 test_data = test_data_set.get(cs) 

108 if test_data: 

109 log("%s: testing %s / %s with %s bytes of data", 

110 decoder_module.get_type(), encoding, cs, len(test_data)) 

111 image = e.decompress_image(test_data) 

112 assert image is not None, "failed to decode test data for encoding '%s' with colorspace '%s'" % (encoding, cs) 

113 assert image.get_width()==W, "expected image of width %s but got %s" % (W, image.get_width()) 

114 assert image.get_height()==H, "expected image of height %s but got %s" % (H, image.get_height()) 

115 if full: 

116 log("%s: testing %s / %s with junk data", decoder_module.get_type(), encoding, cs) 

117 #test failures: 

118 try: 

119 image = e.decompress_image(b"junk") 

120 except Exception: 

121 image = None 

122 if image is not None: 

123 raise Exception("decoding junk with %s should have failed, got %s instead" % (decoder_module.get_type(), image)) 

124 finally: 

125 e.clean() 

126 

127 

128def testencoder(encoder_module, full): 

129 codecs = list(encoder_module.get_encodings()) 

130 for encoding in tuple(codecs): 

131 try: 

132 testencoding(encoder_module, encoding, full) 

133 except Exception as e: 

134 log("%s: %s encoding failed", encoder_module.get_type(), encoding, exc_info=True) 

135 log.warn("%s: %s encoding failed: %s", encoder_module.get_type(), encoding, e) 

136 del e 

137 codecs.remove(encoding) 

138 if not codecs: 

139 log.error("%s: all the codecs have failed! (%s)", 

140 encoder_module.get_type(), csv(encoder_module.get_encodings())) 

141 return tuple(codecs) 

142 

143def testencoding(encoder_module, encoding, full): 

144 #test a bit bigger so we exercise more code: 

145 W = 64 

146 H = 32 

147 do_testencoding(encoder_module, encoding, W, H, full) 

148 

149def get_encoder_max_sizes(encoder_module): 

150 w, h = TEST_LIMIT_W, TEST_LIMIT_H 

151 for encoding in encoder_module.get_encodings(): 

152 ew, eh = get_encoder_max_size(encoder_module, encoding) 

153 w = min(w, ew) 

154 h = min(h, eh) 

155 return w, h 

156 

157def get_encoder_max_size(encoder_module, encoding, limit_w=TEST_LIMIT_W, limit_h=TEST_LIMIT_H): 

158 #probe to find the max dimensions: 

159 #(it may go higher but we don't care as windows can't) 

160 def einfo(): 

161 return "%s %s %s" % (encoder_module.get_type(), encoding, encoder_module.get_version()) 

162 log("get_encoder_max_size%s", (encoder_module, encoding, limit_w, limit_h)) 

163 maxw = w = 512 

164 while w<=limit_w: 

165 try: 

166 do_testencoding(encoder_module, encoding, w, 128) 

167 maxw = w 

168 w *= 2 

169 except Exception as e: 

170 log("%s is limited to max width=%i for %s:", einfo(), maxw, encoding) 

171 log(" %s", e) 

172 del e 

173 break 

174 log("%s max width=%i", einfo(), maxw) 

175 maxh = h = 512 

176 while h<=limit_h: 

177 try: 

178 do_testencoding(encoder_module, encoding, 128, h) 

179 maxh = h 

180 h *= 2 

181 except Exception as e: 

182 log("%s is limited to max height=%i for %s:", einfo(), maxh, encoding) 

183 log(" %s", e) 

184 del e 

185 break 

186 log("%s max height=%i", einfo(), maxh) 

187 #now try combining width and height 

188 #as there might be a lower limit based on the total number of pixels: 

189 MAX_WIDTH, MAX_HEIGHT = maxw, maxh 

190 #start at half: 

191 v = max(512, min(maxw, maxh)//2) 

192 while v<max(limit_w, limit_h): 

193 for tw, th in ((v, v), (v*2, v)): 

194 if tw>limit_w or th>limit_h: 

195 continue 

196 try: 

197 w = min(maxw, tw) 

198 h = min(maxh, th) 

199 do_testencoding(encoder_module, encoding, w, h) 

200 log("%s can handle %ix%i for %s", einfo(), w, h, encoding) 

201 MAX_WIDTH, MAX_HEIGHT = w, h 

202 except Exception as e: 

203 log("%s is limited to %ix%i for %s", einfo(), MAX_WIDTH, MAX_HEIGHT, encoding) 

204 log(" %s", e) 

205 del e 

206 break 

207 v *= 2 

208 log("%s max dimensions for %s: %ix%i", einfo(), encoding, MAX_WIDTH, MAX_HEIGHT) 

209 return MAX_WIDTH, MAX_HEIGHT 

210 

211 

212def do_testencoding(encoder_module, encoding, W, H, full=False, limit_w=TEST_LIMIT_W, limit_h=TEST_LIMIT_H): 

213 for cs_in in encoder_module.get_input_colorspaces(encoding): 

214 for cs_out in encoder_module.get_output_colorspaces(encoding, cs_in): 

215 e = encoder_module.Encoder() 

216 try: 

217 options = typedict({"b-frames" : True}) 

218 e.init_context(W, H, cs_in, [cs_out], encoding, 0, 100, (1, 1), options) 

219 for i in range(2): 

220 image = make_test_image(cs_in, W, H) 

221 v = e.compress_image(image) 

222 if v is None: 

223 raise Exception("%s compression failed" % encoding) 

224 data, meta = v 

225 if not data: 

226 delayed = meta.get("delayed", 0) 

227 assert delayed>0, "data is empty and there are no delayed frames!" 

228 if i>0: 

229 #now we should get one: 

230 data, meta = e.flush(delayed) 

231 del image 

232 assert data is not None, "None data for %s using %s encoding with %s / %s" % (encoder_module.get_type(), encoding, cs_in, cs_out) 

233 assert data, "no compressed data for %s using %s encoding with %s / %s" % (encoder_module.get_type(), encoding, cs_in, cs_out) 

234 assert meta is not None, "missing metadata for %s using %s encoding with %s / %s" % (encoder_module.get_type(), encoding, cs_in, cs_out) 

235 log("%s: %s / %s / %s passed", encoder_module, encoding, cs_in, cs_out) 

236 #print("test_encoder: %s.compress_image(%s)=%s" % (encoder_module.get_type(), image, (data, meta))) 

237 #print("compressed data with %s: %s bytes (%s), metadata: %s" % (encoder_module.get_type(), len(data), type(data), meta)) 

238 #print("compressed data(%s, %s)=%s" % (encoding, cs_in, binascii.hexlify(data))) 

239 if full: 

240 wrong_formats = [x for x in ("YUV420P", "YUV444P", "BGRX", "r210") if x!=cs_in] 

241 #log("wrong formats (not %s): %s", cs_in, wrong_formats) 

242 if wrong_formats: 

243 wrong_format = wrong_formats[0] 

244 try: 

245 image = make_test_image(wrong_format, W, H) 

246 out = e.compress_image(image, options=options) 

247 except Exception: 

248 out = None 

249 assert out is None, "encoder %s should have failed using %s encoding with %s instead of %s / %s" % (encoder_module.get_type(), encoding, wrong_format, cs_in, cs_out) 

250 for w,h in ((W//2, H//2), (W*2, H//2), (W//2, H**2)): 

251 if w>limit_w or h>limit_h: 

252 continue 

253 try: 

254 image = make_test_image(cs_in, w, h) 

255 out = e.compress_image(image, options=options) 

256 except Exception: 

257 out = None 

258 assert out is None, "encoder %s, info=%s should have failed using %s encoding with invalid size %ix%i vs %ix%i" % (encoder_module.get_type(), e.get_info(), encoding, w, h, W, H) 

259 finally: 

260 e.clean() 

261 

262 

263def testcsc(csc_module, scaling=True, full=False, test_cs_in=None, test_cs_out=None): 

264 W = 48 

265 H = 32 

266 log("test_csc(%s, %s, %s, %s)", csc_module, full, test_cs_in, test_cs_out) 

267 do_testcsc(csc_module, W, H, W, H, full, test_cs_in, test_cs_out) 

268 if full and scaling: 

269 do_testcsc(csc_module, W, H, W*2, H*2, full, test_cs_in, test_cs_out) 

270 do_testcsc(csc_module, W, H, W//2, H//2, full, test_cs_in, test_cs_out) 

271 

272def get_csc_max_size(colorspace_converter, test_cs_in=None, test_cs_out=None, limit_w=TEST_LIMIT_W, limit_h=TEST_LIMIT_H): 

273 #probe to find the max dimensions: 

274 #(it may go higher but we don't care as windows can't) 

275 MAX_WIDTH, MAX_HEIGHT = 512, 512 

276 #as there might be a lower limit based on the total number of pixels: 

277 v = 512 

278 while v<=min(limit_w, limit_h): 

279 for tw, th in ((v, v), (v*2, v)): 

280 if tw>limit_w or th>limit_h: 

281 break 

282 try: 

283 do_testcsc(colorspace_converter, tw, th, tw, th, False, test_cs_in, test_cs_out, limit_w, limit_h) 

284 log("%s can handle %ix%i", colorspace_converter, tw, th) 

285 MAX_WIDTH, MAX_HEIGHT = tw, th 

286 except Exception: 

287 log("%s is limited to %ix%i for %s", 

288 colorspace_converter, MAX_WIDTH, MAX_HEIGHT, (test_cs_in, test_cs_out), exc_info=True) 

289 break 

290 v *= 2 

291 log("%s max dimensions: %ix%i", colorspace_converter, MAX_WIDTH, MAX_HEIGHT) 

292 return MAX_WIDTH, MAX_HEIGHT 

293 

294 

295def do_testcsc(csc_module, iw, ih, ow, oh, full=False, test_cs_in=None, test_cs_out=None, limit_w=TEST_LIMIT_W, limit_h=TEST_LIMIT_H): 

296 log("do_testcsc%s", (csc_module, iw, ih, ow, oh, full, test_cs_in, test_cs_out, TEST_LIMIT_W, TEST_LIMIT_H)) 

297 cs_in_list = test_cs_in 

298 if cs_in_list is None: 

299 cs_in_list = csc_module.get_input_colorspaces() 

300 for cs_in in cs_in_list: 

301 cs_out_list = test_cs_out 

302 if cs_out_list is None: 

303 cs_out_list = csc_module.get_output_colorspaces(cs_in) 

304 for cs_out in cs_out_list: 

305 log("%s: testing %s / %s", csc_module.get_type(), cs_in, cs_out) 

306 e = csc_module.ColorspaceConverter() 

307 try: 

308 e.init_context(iw, ih, cs_in, ow, oh, cs_out) 

309 image = make_test_image(cs_in, iw, ih) 

310 out = e.convert_image(image) 

311 #print("convert_image(%s)=%s" % (image, out)) 

312 assert out.get_width()==ow, "expected image of width %s but got %s" % (ow, out.get_width()) 

313 assert out.get_height()==oh, "expected image of height %s but got %s" % (oh, out.get_height()) 

314 assert out.get_pixel_format()==cs_out, "expected pixel format %s but got %s" % (cs_out, out.get_pixel_format()) 

315 if full: 

316 for w,h in ((iw*2, ih//2), (iw//2, ih**2)): 

317 if w>limit_w or h>limit_h: 

318 continue 

319 try: 

320 image = make_test_image(cs_in, w, h) 

321 out = e.convert_image(image) 

322 except Exception: 

323 out = None 

324 if out is not None: 

325 raise Exception("converting an image of a smaller size with %s should have failed, got %s instead" % (csc_module.get_type(), out)) 

326 finally: 

327 e.clean()