Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/codecs/codec_checks.py : 80%
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.
7#pylint: disable=line-too-long
9import binascii
11from xpra.util import csv, typedict, roundup
12from xpra.log import Logger
13log = Logger("encoding")
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
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}
36def makebuf(size, b=0x20):
37 return (chr(b).encode())*size
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
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)
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()
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)
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)
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
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
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()
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)
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
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()