FreeRDP
Loading...
Searching...
No Matches
TestFreeRDPCodecH264.c
1
2#include <math.h>
3#include <string.h>
4#include <stdlib.h>
5#include <errno.h>
6
7#include <winpr/sysinfo.h>
8
9#include <freerdp/freerdp.h>
10#include <freerdp/codec/color.h>
11
12WINPR_ATTR_NODISCARD
13static const char* print_ns(UINT64 start, UINT64 end, char* buffer, size_t len)
14{
15 WINPR_ASSERT(buffer);
16
17 const UINT64 diff = end - start;
18 const unsigned ns = diff % 1000;
19 const unsigned us = (diff / 1000) % 1000;
20 const unsigned ms = (diff / 1000000) % 1000;
21 const unsigned s = (diff / 1000000000ULL) % 1000;
22 (void)_snprintf(buffer, len, "%03u %03u %03u %03uns", s, ms, us, ns);
23 return buffer;
24}
25
26WINPR_ATTR_NODISCARD
27static BOOL testContextOptions(BOOL compressor, uint32_t width, uint32_t height)
28{
29 BOOL rc = FALSE;
30 const UINT64 start = winpr_GetUnixTimeNS();
31 /* Test the requested encoder or decoder. */
32 H264_CONTEXT* h264 = h264_context_new(compressor);
33 if (!h264)
34 {
35 (void)fprintf(stderr, "[%s] h264_context_new failed\n", __func__);
36 return FALSE;
37 }
38
39 struct optpair_s
40 {
41 H264_CONTEXT_OPTION opt;
42 uint32_t val;
43 };
44 const struct optpair_s optpair[] = { { H264_CONTEXT_OPTION_RATECONTROL, H264_RATECONTROL_VBR },
45 { H264_CONTEXT_OPTION_BITRATE, 2323 },
46 { H264_CONTEXT_OPTION_FRAMERATE, 23 },
47 { H264_CONTEXT_OPTION_QP, 21 },
48 { H264_CONTEXT_OPTION_USAGETYPE, 23 } };
49 for (size_t x = 0; x < ARRAYSIZE(optpair); x++)
50 {
51 const struct optpair_s* cur = &optpair[x];
52 if (!h264_context_set_option(h264, cur->opt, cur->val))
53 {
54 (void)fprintf(stderr,
55 "[%s] h264_context_set_option %" PRIuz "{ %d, %" PRIu32 " } failed\n",
56 __func__, x, cur->opt, cur->val);
57 goto fail;
58 }
59 }
60 if (!h264_context_reset(h264, width, height))
61 {
62 (void)fprintf(stderr, "[%s] h264_context_reset\n", __func__);
63 goto fail;
64 }
65
66 rc = TRUE;
67fail:
68 h264_context_free(h264);
69 const UINT64 end = winpr_GetUnixTimeNS();
70
71 char buffer[64] = WINPR_C_ARRAY_INIT;
72 printf("[%s] %" PRIu32 "x%" PRIu32 " took %s\n", __func__, width, height,
73 print_ns(start, end, buffer, sizeof(buffer)));
74 return rc;
75}
76
77WINPR_ATTR_MALLOC(free, 1)
78static void* allocRGB(uint32_t format, uint32_t width, uint32_t height, uint32_t* pstride)
79{
80 const size_t bpp = FreeRDPGetBytesPerPixel(format);
81 const size_t stride = bpp * width + 32;
82 WINPR_ASSERT(pstride);
83 *pstride = WINPR_ASSERTING_INT_CAST(uint32_t, stride);
84
85 uint8_t* rgb = calloc(stride, height);
86 if (!rgb)
87 return nullptr;
88
89 for (size_t x = 0; x < height; x++)
90 {
91 if (winpr_RAND(&rgb[x * stride], width * bpp) < 0)
92 {
93 free(rgb);
94 return nullptr;
95 }
96 }
97 return rgb;
98}
99
100WINPR_ATTR_NODISCARD
101static BOOL compareRGB(const uint8_t* src, const uint8_t* dst, uint32_t format, size_t width,
102 size_t stride, size_t height)
103{
104 WINPR_ASSERT(src);
105 WINPR_ASSERT(dst);
106
107 const size_t bpp = FreeRDPGetBytesPerPixel(format);
108 for (size_t y = 0; y < height; y++)
109 {
110 const uint8_t* csrc = &src[y * stride];
111 const uint8_t* cdst = &dst[y * stride];
112 const int rc = memcmp(csrc, cdst, width * bpp);
113 // TODO: Both, AVC420 encoding and decoding are lossy.
114 // TODO: Find a proper error margin to check for
115#if 0
116 if (rc != 0)
117 return FALSE;
118#endif
119 }
120 return TRUE;
121}
122
123WINPR_ATTR_NODISCARD
124static BOOL testEncode(uint32_t format, uint32_t width, uint32_t height)
125{
126 BOOL rc = FALSE;
127 void* src = nullptr;
128 void* out = nullptr;
129 RDPGFX_H264_METABLOCK meta = WINPR_C_ARRAY_INIT;
130 H264_CONTEXT* h264 = h264_context_new(TRUE);
131 H264_CONTEXT* h264dec = h264_context_new(FALSE);
132 if (!h264 || !h264dec)
133 {
134 (void)fprintf(stderr, "[%s] encoder=%p, decoder=%p\n", __func__, (void*)h264,
135 (void*)h264dec);
136 goto fail;
137 }
138
139 if (!h264_context_reset(h264, width, height))
140 {
141 (void)fprintf(stderr, "[%s] h264_context_reset(encoder) failed\n", __func__);
142 goto fail;
143 }
144 if (!h264_context_reset(h264dec, width, height))
145 {
146 (void)fprintf(stderr, "[%s] h264_context_reset(decoder) failed\n", __func__);
147 goto fail;
148 }
149
150 uint32_t stride = 0;
151 uint32_t ostride = 0;
152 src = allocRGB(format, width, height, &stride);
153 out = allocRGB(format, width, height, &ostride);
154 if (!src || !out || (stride < width) || (stride != ostride))
155 {
156 (void)fprintf(stderr,
157 "[%s] src=%p, out=%p, stride(%" PRIu32 ") < width(%" PRIu32
158 "), stride != ostride(%" PRIu32 ")\n",
159 __func__, src, out, stride, width, ostride);
160 goto fail;
161 }
162
163 const RECTANGLE_16 rect = { .left = 0, .top = 0, .right = width, .bottom = height };
164 uint32_t dstsize = 0;
165 uint8_t* dst = nullptr;
166 const int res1 =
167 avc420_compress(h264, src, format, stride, width, height, &rect, &dst, &dstsize, &meta);
168 if (res1 < 0)
169 {
170 (void)fprintf(stderr, "[%s] avc420_compress failed: %d\n", __func__, res1);
171 goto fail;
172 }
173 if ((dstsize == 0) || !dst)
174 {
175 (void)fprintf(stderr, "[%s] dstsize=%" PRIu32 ", dst=%p\n", __func__, dstsize, (void*)dst);
176 goto fail;
177 }
178
179 const int res2 =
180 avc420_decompress(h264dec, dst, dstsize, out, format, stride, width, height, &rect, 1);
181 if (res2 < 0)
182 {
183 (void)fprintf(stderr, "[%s] avc420_decompress failed: %d\n", __func__, res2);
184 goto fail;
185 }
186
187 rc = compareRGB(src, out, format, width, stride, height);
188fail:
189 if (!rc)
190 (void)fprintf(stderr, "[%s] run failed\n", __func__);
191 h264_context_free(h264);
192 h264_context_free(h264dec);
193 free_h264_metablock(&meta);
194 free(src);
195 free(out);
196 return rc;
197}
198
199WINPR_ATTR_NODISCARD
200static BOOL testEncodeOffsetRegion(void)
201{
202 BOOL rc = FALSE;
203 const UINT32 width = 192;
204 const UINT32 height = 128;
205 const UINT32 format = PIXEL_FORMAT_BGRA32;
206 const RECTANGLE_16 full = { .left = 0, .top = 0, .right = width, .bottom = height };
207 const RECTANGLE_16 region = { .left = 32, .top = 16, .right = 160, .bottom = 112 };
208 RDPGFX_H264_METABLOCK meta = WINPR_C_ARRAY_INIT;
209 H264_CONTEXT* h264 = h264_context_new(TRUE);
210 UINT32 stride = 0;
211 BYTE* src = allocRGB(format, width, height, &stride);
212 BYTE* dst = nullptr;
213 UINT32 dstSize = 0;
214
215 if (!h264 || !src || !h264_context_reset(h264, width, height))
216 goto fail;
217
218 memset(src, 0, (size_t)stride * height);
219 const int res =
220 avc420_compress(h264, src, format, stride, width, height, &full, &dst, &dstSize, &meta);
221 if (res < 0)
222 {
223
224 (void)fprintf(stderr, "[%s] first avc420_compress failed: %d\n", __func__, res);
225 goto fail;
226 }
227 free_h264_metablock(&meta);
228
229 for (UINT32 y = region.top; y < region.bottom; y++)
230 {
231 BYTE* row = &src[(size_t)y * stride + (size_t)region.left * 4];
232 memset(row, 0xFF, (size_t)(region.right - region.left) * 4);
233 }
234
235 dst = nullptr;
236 dstSize = 0;
237 const int res2 =
238 avc420_compress(h264, src, format, stride, width, height, &region, &dst, &dstSize, &meta);
239 if (res2 < 0)
240 {
241 (void)fprintf(stderr, "[%s] second avc420_compress failed: %d\n", __func__, res2);
242 goto fail;
243 }
244 if ((meta.numRegionRects == 0) || (meta.regionRects[0].left != region.left) ||
245 (meta.regionRects[0].top != region.top))
246 {
247 (void)fprintf(
248 stderr, "%s failed: first changed tile does not start at (%" PRIu16 ", %" PRIu16 ")\n",
249 __func__, region.left, region.top);
250 goto fail;
251 }
252
253 rc = TRUE;
254fail:
255 if (!rc)
256 (void)fprintf(stderr, "[%s] run failed\n", __func__);
257 h264_context_free(h264);
258 free_h264_metablock(&meta);
259 free(src);
260 return rc;
261}
262
263/* 64x64 solid 0x3366CC, generated with
264 * ffmpeg -f lavfi -i color=c=0x3366CC:s=64x64 -frames:v 1 -c:v libx264 -profile:v baseline
265 * -bsf:v h264_mp4toannexb,filter_units=remove_types=6 -f h264 */
266static const BYTE solid_idr_64x64[] = {
267 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0a, 0xd9, 0x04, 0x26, 0xc0, 0x44, 0x00,
268 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xc8, 0x3c, 0x48, 0x99, 0x20, 0x00,
269 0x00, 0x00, 0x01, 0x68, 0xcb, 0x83, 0xcb, 0x20, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84,
270 0x0a, 0xf1, 0x18, 0xa0, 0x00, 0x22, 0x4b, 0x1c, 0x00, 0x04, 0x53, 0xa3, 0x80, 0x00,
271 0x85, 0x8c, 0x9c, 0x9c, 0x9d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5e
272};
273
274WINPR_ATTR_NODISCARD
275static BOOL testDecode(BOOL separateParameters)
276{
277 BOOL rc = FALSE;
278 const UINT32 width = 64;
279 const UINT32 height = 64;
280 const UINT32 format = PIXEL_FORMAT_BGRX32;
281 const UINT32 stride = width * 4;
282 const RECTANGLE_16 rect = { .left = 0, .top = 0, .right = width, .bottom = height };
283 H264_CONTEXT* h264 = h264_context_new(FALSE);
284 BYTE* dst = calloc(stride, height);
285
286 if (!h264 || !dst || !h264_context_reset(h264, width, height))
287 goto fail;
288
289 /* The video channel can deliver SPS/PPS separately from the picture. */
290 const UINT32 offset = separateParameters ? 36 : 0; /* IDR start code follows SPS/PPS. */
291 if (separateParameters && (avc420_decompress(h264, solid_idr_64x64, offset, dst, format, stride,
292 width, height, &rect, 1) < 0))
293 goto fail;
294
295 /* The picture must be available immediately, with the expected RGB values. */
296 const INT32 res =
297 avc420_decompress(h264, solid_idr_64x64 + offset, sizeof(solid_idr_64x64) - offset, dst,
298 format, stride, width, height, &rect, 1);
299 if (res < 0)
300 {
301 (void)fprintf(stderr, "[%s] avc420_decompress failed: %" PRId32 "\n", __func__, res);
302 goto fail;
303 }
304
305 for (UINT32 y = 0; y < height; y++)
306 {
307 for (UINT32 x = 0; x < width; x++)
308 {
309 BYTE r = 0;
310 BYTE g = 0;
311 BYTE b = 0;
312 const UINT32 color = FreeRDPReadColor(&dst[y * stride + x * 4], format);
313 FreeRDPSplitColor(color, format, &r, &g, &b, nullptr, nullptr);
314 if ((abs(r - 0x33) > 16) || (abs(g - 0x66) > 16) || (abs(b - 0xCC) > 16))
315 {
316 (void)fprintf(
317 stderr, "[%s] pixel %" PRIu32 ",%" PRIu32 " is %02X%02X%02X, expected 3366CC\n",
318 __func__, x, y, r, g, b);
319 goto fail;
320 }
321 }
322 }
323
324 rc = TRUE;
325fail:
326 h264_context_free(h264);
327 free(dst);
328 return rc;
329}
330
331/* 64x64 I420: Y=64/U=90/V=170; the P-frame adds a 16x16 Y=96 block at (8,16).
332 * Generated with libx264: -qp 18 -profile:v high, bframes=0:slices=2:repeat-headers=1;
333 * SEI removed. The auxiliary frame depends on the preceding main frame. */
334static const BYTE avc444_idr[] = {
335 0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x0A, 0xAC, 0xB2, 0x08, 0x4D, 0x08, 0x00, 0x00, 0x03,
336 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0xE0, 0x78, 0x91, 0x32, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68,
337 0xEB, 0xC0, 0x8C, 0xB2, 0x2C, 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x01, 0x65, 0x88,
338 0x84, 0x3A, 0xFE, 0xC7, 0x6F, 0xE6, 0x59, 0x2A, 0x80, 0xC4, 0x0B, 0xD4, 0xF7, 0xD5, 0xB5, 0x93,
339 0x2C, 0x2D, 0x6E, 0xCB, 0x72, 0xFB, 0xC9, 0x1D, 0x47, 0x81, 0x00, 0x00, 0x01, 0x65, 0x12, 0x22,
340 0x10, 0xEB, 0xFE, 0xC7, 0x6F, 0xE6, 0x59, 0x2A, 0x80, 0xC4, 0x0B, 0xD4, 0xF7, 0xD5, 0xB5, 0x93,
341 0x2C, 0x2D, 0x6E, 0xCB, 0x72, 0xFB, 0xC9, 0x1D, 0x47, 0x81,
342};
343
344static const BYTE avc444_p[] = {
345 0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, 0x9A, 0x3B, 0x1F, 0xF9,
346 0x59, 0xCD, 0xEF, 0xFF, 0xFC, 0xF8, 0xAA, 0xD6, 0x9F, 0x73, 0x05, 0x6A, 0x32, 0x45,
347 0xA5, 0x7B, 0x00, 0x00, 0x01, 0x41, 0x12, 0x68, 0xEC, 0x7F, 0xFA, 0xE1, 0x80,
348};
349
350WINPR_ATTR_NODISCARD
351static BOOL testDecodeAvc444(UINT32 codec)
352{
353 BOOL rc = FALSE;
354 const RECTANGLE_16 full = { 0, 0, 64, 64 };
355 const RECTANGLE_16 partial = { 16, 16, 48, 32 };
356 const UINT32 stride = 64 * 4;
357 const UINT32 format = PIXEL_FORMAT_BGRX32;
358 const BYTE untouched[4] = { 0xA5, 0xA5, 0xA5, 0xA5 };
359 const size_t sampleOffset = partial.top * stride + partial.left * 4;
360 BYTE luma[4] = WINPR_C_ARRAY_INIT;
361 BYTE combined[64 * 64 * 4] = WINPR_C_ARRAY_INIT;
362 BYTE separate[64 * 64 * 4] = WINPR_C_ARRAY_INIT;
363 H264_CONTEXT* combinedContext = h264_context_new(FALSE);
364 H264_CONTEXT* separateContext = h264_context_new(FALSE);
365
366 if (!combinedContext || !separateContext || !h264_context_reset(combinedContext, 64, 64) ||
367 !h264_context_reset(separateContext, 64, 64))
368 goto fail;
369 memset(separate, 0xA5, sizeof(separate));
370
371 /* Op 0 decodes main and dependent auxiliary frames in one update. */
372 if (avc444_decompress(combinedContext, 0, &full, 1, avc444_idr, sizeof(avc444_idr), &full, 1,
373 avc444_p, sizeof(avc444_p), combined, format, stride, 64, 64, codec) != 0)
374 goto fail;
375
376 /* Ops 1 and 2 deliver the same frames as separate partial updates. */
377 if (avc444_decompress(separateContext, 1, &partial, 1, avc444_idr, sizeof(avc444_idr), nullptr,
378 0, nullptr, 0, separate, format, stride, 64, 64, codec) != 0)
379 goto fail;
380 memcpy(luma, &separate[sampleOffset], sizeof(luma));
381 if (avc444_decompress(separateContext, 2, &partial, 1, avc444_p, sizeof(avc444_p), nullptr, 0,
382 nullptr, 0, separate, format, stride, 64, 64, codec) != 0)
383 goto fail;
384
385 /* Both the main picture and the auxiliary correction must produce output. */
386 if ((memcmp(luma, untouched, sizeof(luma)) == 0) ||
387 (memcmp(luma, &separate[sampleOffset], sizeof(luma)) == 0))
388 {
389 (void)fprintf(stderr, "[%s] missing main or auxiliary output\n", __func__);
390 goto fail;
391 }
392
393 /* Compare RGB inside the update; keep all bytes unchanged outside. */
394 for (UINT32 y = 0; y < 64; y++)
395 {
396 for (UINT32 x = 0; x < 64; x++)
397 {
398 const size_t offset = y * stride + x * 4;
399 const BOOL inside = (x >= partial.left) && (x < partial.right) && (y >= partial.top) &&
400 (y < partial.bottom);
401 const BYTE* expected = inside ? &combined[offset] : untouched;
402 if (memcmp(&separate[offset], expected, inside ? 3 : 4) != 0)
403 {
404 (void)fprintf(stderr, "[%s] pixel %" PRIu32 ",%" PRIu32 " differs\n", __func__, x,
405 y);
406 goto fail;
407 }
408 }
409 }
410
411 rc = TRUE;
412fail:
413 if (!rc)
414 (void)fprintf(stderr, "[%s] codec 0x%04" PRIX32 " failed\n", __func__, codec);
415 h264_context_free(combinedContext);
416 h264_context_free(separateContext);
417 return rc;
418}
419
420WINPR_ATTR_NODISCARD
421int TestFreeRDPCodecH264(int argc, char* argv[])
422{
423 WINPR_ASSERT(argv);
424
425 WINPR_UNUSED(argc);
426 WINPR_UNUSED(argv);
427
428 UINT32 width = 124;
429 UINT32 height = 54;
430 const UINT32 formats[] = {
431 PIXEL_FORMAT_ABGR15, PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_BGR16,
432 PIXEL_FORMAT_BGR24, PIXEL_FORMAT_RGB15, PIXEL_FORMAT_RGB16, PIXEL_FORMAT_RGB24,
433 PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_XRGB32,
434 PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_BGRX32, PIXEL_FORMAT_RGBX32,
435 };
436
437 if (argc == 3)
438 {
439 errno = 0;
440 width = strtoul(argv[1], nullptr, 0);
441 height = strtoul(argv[2], nullptr, 0);
442 if ((errno != 0) || (width == 0) || (height == 0))
443 {
444 char buffer[128] = WINPR_C_ARRAY_INIT;
445 (void)fprintf(stderr, "%s failed: width=%" PRIu32 ", height=%" PRIu32 ", errno=%s\n",
446 __func__, width, height, winpr_strerror(errno, buffer, sizeof(buffer)));
447 return -1;
448 }
449 }
450
451 /* The shared flag includes all H.264 backends, including decoder-only VideoToolbox. */
452#if !defined(WITH_GFX_H264)
453 (void)fprintf(stderr, "[%s] skipping, no H264 encoder/decoder support compiled in\n", __func__);
454 return 0;
455#endif
456
457 /* Decoder tests also run when no encoder backend is configured. */
458 if (!testContextOptions(FALSE, width, height))
459 return -1;
460 if (!testDecode(FALSE))
461 return -1;
462#ifdef WITH_APPLE_VIDEOTOOLBOX
463 if (!testDecode(TRUE))
464 return -1;
465#endif
466 /* Both AVC444 packing formats must support combined and separate updates. */
467 if (!testDecodeAvc444(RDPGFX_CODECID_AVC444) || !testDecodeAvc444(RDPGFX_CODECID_AVC444v2))
468 return -1;
469
470#if !defined(WITH_MEDIA_FOUNDATION) && \
471 (defined(WITH_OPENH264) || defined(WITH_VIDEO_FFMPEG) || defined(WITH_MEDIACODEC))
472 /* Decoder-only builds cannot run the compression tests. */
473 if (!testContextOptions(TRUE, width, height))
474 return -1;
475 if (!testEncodeOffsetRegion())
476 return -1;
477
478 for (size_t x = 0; x < ARRAYSIZE(formats); x++)
479 {
480 const UINT32 SrcFormat = formats[x];
481 for (size_t y = 0; y < ARRAYSIZE(formats); y++)
482 {
483 if (!testEncode(SrcFormat, width, height))
484 return -1;
485 }
486 }
487#elif defined(WITH_MEDIA_FOUNDATION)
488 (void)fprintf(stderr,
489 "TODO: WITH_MEDIA_FOUNDATION: compression tests skipped, not implemented\n");
490#else
491 (void)fprintf(stderr, "H.264 compression tests skipped: no encoder configured\n");
492#endif
493
494 return 0;
495}