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
12#if defined(WITH_APPLE_VIDEOTOOLBOX)
13#include <CoreMedia/CoreMedia.h>
14#include <freerdp/codec/video.h>
15#include "../h264.h"
16#endif
17
18WINPR_ATTR_NODISCARD
19static const char* print_ns(UINT64 start, UINT64 end, char* buffer, size_t len)
20{
21 WINPR_ASSERT(buffer);
22
23 const UINT64 diff = end - start;
24 const unsigned ns = diff % 1000;
25 const unsigned us = (diff / 1000) % 1000;
26 const unsigned ms = (diff / 1000000) % 1000;
27 const unsigned s = (diff / 1000000000ULL) % 1000;
28 (void)_snprintf(buffer, len, "%03u %03u %03u %03uns", s, ms, us, ns);
29 return buffer;
30}
31
32WINPR_ATTR_NODISCARD
33static BOOL testContextOptions(BOOL compressor, uint32_t width, uint32_t height)
34{
35 BOOL rc = FALSE;
36 const UINT64 start = winpr_GetUnixTimeNS();
37 /* Test the requested encoder or decoder. */
38 H264_CONTEXT* h264 = h264_context_new(compressor);
39 if (!h264)
40 {
41 (void)fprintf(stderr, "[%s] h264_context_new failed\n", __func__);
42 return FALSE;
43 }
44
45 struct optpair_s
46 {
47 H264_CONTEXT_OPTION opt;
48 uint32_t val;
49 };
50 const struct optpair_s optpair[] = { { H264_CONTEXT_OPTION_RATECONTROL, H264_RATECONTROL_VBR },
51 { H264_CONTEXT_OPTION_BITRATE, 2323 },
52 { H264_CONTEXT_OPTION_FRAMERATE, 23 },
53 { H264_CONTEXT_OPTION_QP, 21 },
54 { H264_CONTEXT_OPTION_USAGETYPE, 23 } };
55 for (size_t x = 0; x < ARRAYSIZE(optpair); x++)
56 {
57 const struct optpair_s* cur = &optpair[x];
58 if (!h264_context_set_option(h264, cur->opt, cur->val))
59 {
60 (void)fprintf(stderr,
61 "[%s] h264_context_set_option %" PRIuz "{ %d, %" PRIu32 " } failed\n",
62 __func__, x, cur->opt, cur->val);
63 goto fail;
64 }
65 }
66 if (!h264_context_reset(h264, width, height))
67 {
68 (void)fprintf(stderr, "[%s] h264_context_reset\n", __func__);
69 goto fail;
70 }
71
72 rc = TRUE;
73fail:
74 h264_context_free(h264);
75 const UINT64 end = winpr_GetUnixTimeNS();
76
77 char buffer[64] = WINPR_C_ARRAY_INIT;
78 printf("[%s] %" PRIu32 "x%" PRIu32 " took %s\n", __func__, width, height,
79 print_ns(start, end, buffer, sizeof(buffer)));
80 return rc;
81}
82
83WINPR_ATTR_MALLOC(free, 1)
84static void* allocRGB(uint32_t format, uint32_t width, uint32_t height, uint32_t* pstride)
85{
86 const size_t bpp = FreeRDPGetBytesPerPixel(format);
87 const size_t stride = bpp * width + 32;
88 WINPR_ASSERT(pstride);
89 *pstride = WINPR_ASSERTING_INT_CAST(uint32_t, stride);
90
91 uint8_t* rgb = calloc(stride, height);
92 if (!rgb)
93 return nullptr;
94
95 const UINT32 color = FreeRDPGetColor(format, 0x12, 0xaa, 0x63, 0x80);
96 for (size_t y = 0; y < height; y++)
97 {
98 uint8_t* line = &rgb[y * stride];
99 for (size_t x = 0; x < width; x++)
100 {
101 FreeRDPWriteColor(&line[bpp * x], format, color);
102 }
103 }
104 return rgb;
105}
106
107WINPR_ATTR_NODISCARD
108#define printIfFail(line, offset, what, a, b) \
109 printIfFail_(__func__, __LINE__, (line), (offset), (what), (a), (b))
110static BOOL printIfFail_(const char* fkt, size_t fline, size_t line, size_t offset,
111 const char* what, BYTE a, BYTE b)
112{
113 const double maxDiff = 8.0;
114 const double diffR = fabs(1.0 * a - b);
115 if (diffR > maxDiff)
116 {
117 (void)fprintf(stderr,
118 "[%s:%" PRIuz "]{%" PRIuz "x%" PRIuz "} %s diff %lf [0x%02" PRIx8
119 " - 0x%02" PRIx8 "]\n",
120 fkt, fline, offset, line, what, diffR, a, b);
121 return FALSE;
122 }
123 return TRUE;
124}
125
126WINPR_ATTR_NODISCARD
127static BOOL compareRGBColors(size_t line, size_t offset, uint32_t colora, uint32_t colorb,
128 uint32_t format)
129{
130 BYTE cR = 0;
131 BYTE cG = 0;
132 BYTE cB = 0;
133 BYTE cA = 0;
134 BYTE dR = 0;
135 BYTE dG = 0;
136 BYTE dB = 0;
137 BYTE dA = 0;
138
139 FreeRDPSplitColor(colora, format, &cR, &cG, &cB, &cA, nullptr);
140 FreeRDPSplitColor(colorb, format, &dR, &dG, &dB, &dA, nullptr);
141
142 if (!printIfFail(line, offset, "red", cR, dR))
143 return FALSE;
144 if (!printIfFail(line, offset, "green", cG, dG))
145 return FALSE;
146 if (!printIfFail(line, offset, "blue", cB, dB))
147 return FALSE;
148 if (FreeRDPColorHasAlpha(format))
149 {
150 if (!printIfFail(line, offset, "alpha", cA, dA))
151 return FALSE;
152 }
153 return TRUE;
154}
155
156WINPR_ATTR_NODISCARD
157static BOOL compareRGBLine(size_t line, const uint8_t* src, const uint8_t* dst, uint32_t format,
158 size_t width)
159{
160 const size_t bpp = FreeRDPGetBytesPerPixel(format);
161 for (size_t x = 0; x < width; x++)
162 {
163 const UINT32 srccolor = FreeRDPReadColor(&src[x * bpp], format);
164 const UINT32 dstcolor = FreeRDPReadColor(&dst[x * bpp], format);
165 if (!compareRGBColors(line, x, srccolor, dstcolor, format))
166 return FALSE;
167 }
168 return TRUE;
169}
170
171WINPR_ATTR_NODISCARD
172static BOOL compareRGB(const uint8_t* src, const uint8_t* dst, uint32_t format, size_t width,
173 size_t stride, size_t height)
174{
175 WINPR_ASSERT(src);
176 WINPR_ASSERT(dst);
177
178 const size_t bits = FreeRDPGetBitsPerPixel(format);
179 if (bits != 32)
180 {
181 printf("skipping color compare for format %s, only 32bpp colors are checked\n",
182 FreeRDPGetColorFormatName(format));
183 return TRUE;
184 }
185
186 for (size_t y = 0; y < height; y++)
187 {
188 const uint8_t* csrc = &src[y * stride];
189 const uint8_t* cdst = &dst[y * stride];
190 if (!compareRGBLine(y, csrc, cdst, format, width))
191 return FALSE;
192 }
193 return TRUE;
194}
195
196WINPR_ATTR_NODISCARD
197static BOOL testEncode(uint32_t format, uint32_t width, uint32_t height)
198{
199 BOOL rc = FALSE;
200 void* src = nullptr;
201 void* out = nullptr;
202 void* copy = nullptr;
203 RDPGFX_H264_METABLOCK meta = WINPR_C_ARRAY_INIT;
204 H264_CONTEXT* h264 = h264_context_new(TRUE);
205 H264_CONTEXT* h264dec = h264_context_new(FALSE);
206 if (!h264 || !h264dec)
207 {
208 (void)fprintf(stderr, "[%s] encoder=%p, decoder=%p\n", __func__, (void*)h264,
209 (void*)h264dec);
210 goto fail;
211 }
212
213 if (!h264_context_reset(h264, width, height))
214 {
215 (void)fprintf(stderr, "[%s] h264_context_reset(encoder) failed\n", __func__);
216 goto fail;
217 }
218 if (!h264_context_reset(h264dec, width, height))
219 {
220 (void)fprintf(stderr, "[%s] h264_context_reset(decoder) failed\n", __func__);
221 goto fail;
222 }
223
224 uint32_t stride = 0;
225 uint32_t ostride = 0;
226 src = allocRGB(format, width, height, &stride);
227 out = allocRGB(format, width, height, &ostride);
228 if (!src || !out || (stride < width) || (stride != ostride))
229 {
230 (void)fprintf(stderr,
231 "[%s] src=%p, out=%p, stride(%" PRIu32 ") < width(%" PRIu32
232 "), stride != ostride(%" PRIu32 ")\n",
233 __func__, src, out, stride, width, ostride);
234 goto fail;
235 }
236
237 copy = calloc(height, stride);
238 if (!copy)
239 goto fail;
240 memcpy(copy, src, 1ull * height * stride);
241
242 const RECTANGLE_16 rect = { .left = 0, .top = 0, .right = width, .bottom = height };
243 uint32_t dstsize = 0;
244 uint8_t* dst = nullptr;
245 const int res1 =
246 avc420_compress(h264, src, format, stride, width, height, &rect, &dst, &dstsize, &meta);
247 if (res1 < 0)
248 {
249 (void)fprintf(stderr, "[%s] avc420_compress failed: %d\n", __func__, res1);
250 goto fail;
251 }
252 if ((dstsize == 0) || !dst)
253 {
254 (void)fprintf(stderr, "[%s] dstsize=%" PRIu32 ", dst=%p\n", __func__, dstsize, (void*)dst);
255 goto fail;
256 }
257 if (memcmp(copy, src, 1ull * height * stride) != 0)
258 {
259 (void)fprintf(stderr, "[%s] avc420_compress modified source data!\n", __func__);
260 goto fail;
261 }
262
263 const int res2 =
264 avc420_decompress(h264dec, dst, dstsize, out, format, stride, width, height, &rect, 1);
265 if (res2 < 0)
266 {
267 (void)fprintf(stderr, "[%s] avc420_decompress failed: %d\n", __func__, res2);
268 goto fail;
269 }
270
271 rc = compareRGB(src, out, format, width, stride, height);
272fail:
273 if (!rc)
274 (void)fprintf(stderr, "[%s] run failed\n", __func__);
275 h264_context_free(h264);
276 h264_context_free(h264dec);
277 free_h264_metablock(&meta);
278 free(src);
279 free(copy);
280 free(out);
281 return rc;
282}
283
284WINPR_ATTR_NODISCARD
285static BOOL testEncodeOffsetRegion(void)
286{
287 BOOL rc = FALSE;
288 const UINT32 width = 192;
289 const UINT32 height = 128;
290 const UINT32 format = PIXEL_FORMAT_BGRA32;
291 const RECTANGLE_16 full = { .left = 0, .top = 0, .right = width, .bottom = height };
292 const RECTANGLE_16 region = { .left = 32, .top = 16, .right = 160, .bottom = 112 };
293 RDPGFX_H264_METABLOCK meta = WINPR_C_ARRAY_INIT;
294 H264_CONTEXT* h264 = h264_context_new(TRUE);
295 UINT32 stride = 0;
296 BYTE* src = allocRGB(format, width, height, &stride);
297 BYTE* dst = nullptr;
298 UINT32 dstSize = 0;
299
300 if (!h264 || !src || !h264_context_reset(h264, width, height))
301 goto fail;
302
303 memset(src, 0, (size_t)stride * height);
304 const int res =
305 avc420_compress(h264, src, format, stride, width, height, &full, &dst, &dstSize, &meta);
306 if (res < 0)
307 {
308
309 (void)fprintf(stderr, "[%s] first avc420_compress failed: %d\n", __func__, res);
310 goto fail;
311 }
312 free_h264_metablock(&meta);
313
314 for (UINT32 y = region.top; y < region.bottom; y++)
315 {
316 BYTE* row = &src[(size_t)y * stride + (size_t)region.left * 4];
317 memset(row, 0xFF, (size_t)(region.right - region.left) * 4);
318 }
319
320 dst = nullptr;
321 dstSize = 0;
322 const int res2 =
323 avc420_compress(h264, src, format, stride, width, height, &region, &dst, &dstSize, &meta);
324 if (res2 < 0)
325 {
326 (void)fprintf(stderr, "[%s] second avc420_compress failed: %d\n", __func__, res2);
327 goto fail;
328 }
329 if ((meta.numRegionRects == 0) || (meta.regionRects[0].left != region.left) ||
330 (meta.regionRects[0].top != region.top))
331 {
332 (void)fprintf(
333 stderr, "%s failed: first changed tile does not start at (%" PRIu16 ", %" PRIu16 ")\n",
334 __func__, region.left, region.top);
335 goto fail;
336 }
337
338 rc = TRUE;
339fail:
340 if (!rc)
341 (void)fprintf(stderr, "[%s] run failed\n", __func__);
342 h264_context_free(h264);
343 free_h264_metablock(&meta);
344 free(src);
345 return rc;
346}
347
348/* 64x64 solid 0x3366CC, generated with
349 * ffmpeg -f lavfi -i color=c=0x3366CC:s=64x64 -frames:v 1 -c:v libx264 -profile:v baseline
350 * -bsf:v h264_mp4toannexb,filter_units=remove_types=6 -f h264 */
351static const BYTE solid_idr_64x64[] = {
352 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0a, 0xd9, 0x04, 0x26, 0xc0, 0x44, 0x00,
353 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xc8, 0x3c, 0x48, 0x99, 0x20, 0x00,
354 0x00, 0x00, 0x01, 0x68, 0xcb, 0x83, 0xcb, 0x20, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84,
355 0x0a, 0xf1, 0x18, 0xa0, 0x00, 0x22, 0x4b, 0x1c, 0x00, 0x04, 0x53, 0xa3, 0x80, 0x00,
356 0x85, 0x8c, 0x9c, 0x9c, 0x9d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5e
357};
358
359WINPR_ATTR_NODISCARD
360static BOOL testDecode(BOOL separateParameters)
361{
362 BOOL rc = FALSE;
363 const UINT32 width = 64;
364 const UINT32 height = 64;
365 const UINT32 format = PIXEL_FORMAT_BGRX32;
366 const UINT32 stride = width * 4;
367 const RECTANGLE_16 rect = { .left = 0, .top = 0, .right = width, .bottom = height };
368 H264_CONTEXT* h264 = h264_context_new(FALSE);
369 BYTE* dst = calloc(stride, height);
370
371 if (!h264 || !dst || !h264_context_reset(h264, width, height))
372 goto fail;
373
374 /* The video channel can deliver SPS/PPS separately from the picture. */
375 const UINT32 offset = separateParameters ? 36 : 0; /* IDR start code follows SPS/PPS. */
376 if (separateParameters && (avc420_decompress(h264, solid_idr_64x64, offset, dst, format, stride,
377 width, height, &rect, 1) < 0))
378 goto fail;
379
380 /* The picture must be available immediately, with the expected RGB values. */
381 const INT32 res =
382 avc420_decompress(h264, solid_idr_64x64 + offset, sizeof(solid_idr_64x64) - offset, dst,
383 format, stride, width, height, &rect, 1);
384 if (res < 0)
385 {
386 (void)fprintf(stderr, "[%s] avc420_decompress failed: %" PRId32 "\n", __func__, res);
387 goto fail;
388 }
389
390 for (UINT32 y = 0; y < height; y++)
391 {
392 for (UINT32 x = 0; x < width; x++)
393 {
394 BYTE r = 0;
395 BYTE g = 0;
396 BYTE b = 0;
397 const UINT32 color = FreeRDPReadColor(&dst[y * stride + x * 4], format);
398 FreeRDPSplitColor(color, format, &r, &g, &b, nullptr, nullptr);
399 if ((abs(r - 0x33) > 16) || (abs(g - 0x66) > 16) || (abs(b - 0xCC) > 16))
400 {
401 (void)fprintf(
402 stderr, "[%s] pixel %" PRIu32 ",%" PRIu32 " is %02X%02X%02X, expected 3366CC\n",
403 __func__, x, y, r, g, b);
404 goto fail;
405 }
406 }
407 }
408
409 rc = TRUE;
410fail:
411 h264_context_free(h264);
412 free(dst);
413 return rc;
414}
415
416/* 64x64 I420: Y=64/U=90/V=170; the P-frame adds a 16x16 Y=96 block at (8,16).
417 * Generated with libx264: -qp 18 -profile:v high, bframes=0:slices=2:repeat-headers=1;
418 * SEI removed. The auxiliary frame depends on the preceding main frame. */
419static const BYTE avc444_idr[] = {
420 0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x0A, 0xAC, 0xB2, 0x08, 0x4D, 0x08, 0x00, 0x00, 0x03,
421 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0xE0, 0x78, 0x91, 0x32, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68,
422 0xEB, 0xC0, 0x8C, 0xB2, 0x2C, 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x01, 0x65, 0x88,
423 0x84, 0x3A, 0xFE, 0xC7, 0x6F, 0xE6, 0x59, 0x2A, 0x80, 0xC4, 0x0B, 0xD4, 0xF7, 0xD5, 0xB5, 0x93,
424 0x2C, 0x2D, 0x6E, 0xCB, 0x72, 0xFB, 0xC9, 0x1D, 0x47, 0x81, 0x00, 0x00, 0x01, 0x65, 0x12, 0x22,
425 0x10, 0xEB, 0xFE, 0xC7, 0x6F, 0xE6, 0x59, 0x2A, 0x80, 0xC4, 0x0B, 0xD4, 0xF7, 0xD5, 0xB5, 0x93,
426 0x2C, 0x2D, 0x6E, 0xCB, 0x72, 0xFB, 0xC9, 0x1D, 0x47, 0x81,
427};
428
429static const BYTE avc444_p[] = {
430 0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, 0x9A, 0x3B, 0x1F, 0xF9,
431 0x59, 0xCD, 0xEF, 0xFF, 0xFC, 0xF8, 0xAA, 0xD6, 0x9F, 0x73, 0x05, 0x6A, 0x32, 0x45,
432 0xA5, 0x7B, 0x00, 0x00, 0x01, 0x41, 0x12, 0x68, 0xEC, 0x7F, 0xFA, 0xE1, 0x80,
433};
434
435WINPR_ATTR_NODISCARD
436static BOOL testDecodeAvc444(UINT32 codec)
437{
438 BOOL rc = FALSE;
439 const RECTANGLE_16 full = { 0, 0, 64, 64 };
440 const RECTANGLE_16 partial = { 16, 16, 48, 32 };
441 const UINT32 stride = 64 * 4;
442 const UINT32 format = PIXEL_FORMAT_BGRX32;
443 const BYTE untouched[4] = { 0xA5, 0xA5, 0xA5, 0xA5 };
444 const size_t sampleOffset = partial.top * stride + partial.left * 4;
445 BYTE luma[4] = WINPR_C_ARRAY_INIT;
446 BYTE combined[64 * 64 * 4] = WINPR_C_ARRAY_INIT;
447 BYTE separate[64 * 64 * 4] = WINPR_C_ARRAY_INIT;
448 H264_CONTEXT* combinedContext = h264_context_new(FALSE);
449 H264_CONTEXT* separateContext = h264_context_new(FALSE);
450
451 if (!combinedContext || !separateContext || !h264_context_reset(combinedContext, 64, 64) ||
452 !h264_context_reset(separateContext, 64, 64))
453 goto fail;
454 memset(separate, 0xA5, sizeof(separate));
455
456 /* Op 0 decodes main and dependent auxiliary frames in one update. */
457 if (avc444_decompress(combinedContext, 0, &full, 1, avc444_idr, sizeof(avc444_idr), &full, 1,
458 avc444_p, sizeof(avc444_p), combined, format, stride, 64, 64, codec) != 0)
459 goto fail;
460
461 /* Ops 1 and 2 deliver the same frames as separate partial updates. */
462 if (avc444_decompress(separateContext, 1, &partial, 1, avc444_idr, sizeof(avc444_idr), nullptr,
463 0, nullptr, 0, separate, format, stride, 64, 64, codec) != 0)
464 goto fail;
465 memcpy(luma, &separate[sampleOffset], sizeof(luma));
466 if (avc444_decompress(separateContext, 2, &partial, 1, avc444_p, sizeof(avc444_p), nullptr, 0,
467 nullptr, 0, separate, format, stride, 64, 64, codec) != 0)
468 goto fail;
469
470 /* Both the main picture and the auxiliary correction must produce output. */
471 if ((memcmp(luma, untouched, sizeof(luma)) == 0) ||
472 (memcmp(luma, &separate[sampleOffset], sizeof(luma)) == 0))
473 {
474 (void)fprintf(stderr, "[%s] missing main or auxiliary output\n", __func__);
475 goto fail;
476 }
477
478 /* Compare RGB inside the update; keep all bytes unchanged outside. */
479 for (UINT32 y = 0; y < 64; y++)
480 {
481 for (UINT32 x = 0; x < 64; x++)
482 {
483 const size_t offset = y * stride + x * 4;
484 const BOOL inside = (x >= partial.left) && (x < partial.right) && (y >= partial.top) &&
485 (y < partial.bottom);
486 const BYTE* expected = inside ? &combined[offset] : untouched;
487 if (memcmp(&separate[offset], expected, inside ? 3 : 4) != 0)
488 {
489 (void)fprintf(stderr, "[%s] pixel %" PRIu32 ",%" PRIu32 " differs\n", __func__, x,
490 y);
491 goto fail;
492 }
493 }
494 }
495
496 rc = TRUE;
497fail:
498 if (!rc)
499 (void)fprintf(stderr, "[%s] codec 0x%04" PRIX32 " failed\n", __func__, codec);
500 h264_context_free(combinedContext);
501 h264_context_free(separateContext);
502 return rc;
503}
504
505#if defined(WITH_APPLE_VIDEOTOOLBOX)
506/* VideoToolbox writes SPS/PPS first, each with a four-byte Annex B start code. */
507WINPR_ATTR_NODISCARD
508static BOOL testH264Range(const BYTE* data, size_t size, BOOL fullRange)
509{
510 WINPR_ASSERT(data);
511 const BYTE startCode[] = { 0, 0, 0, 1 };
512 const BYTE* parameters[2] = WINPR_C_ARRAY_INIT;
513 size_t lengths[2] = WINPR_C_ARRAY_INIT;
514 size_t offset = 0;
515 for (size_t i = 0; i < ARRAYSIZE(parameters); i++)
516 {
517 if ((size - offset < 5) || (memcmp(data + offset, startCode, sizeof(startCode)) != 0) ||
518 ((data[offset + 4] & 0x1F) != 7 + i))
519 return FALSE;
520 offset += sizeof(startCode);
521 parameters[i] = data + offset;
522 const size_t start = offset;
523 while ((size - offset >= sizeof(startCode)) &&
524 (memcmp(data + offset, startCode, sizeof(startCode)) != 0))
525 offset++;
526 if (size - offset < sizeof(startCode))
527 return FALSE;
528 lengths[i] = offset - start;
529 }
530 CMVideoFormatDescriptionRef format = nullptr;
531 if (CMVideoFormatDescriptionCreateFromH264ParameterSets(
532 kCFAllocatorDefault, ARRAYSIZE(parameters), parameters, lengths, 4, &format) != noErr)
533 return FALSE;
534 const BOOL actual = CMFormatDescriptionGetExtension(
535 format, kCMFormatDescriptionExtension_FullRangeVideo) == kCFBooleanTrue
536 ? TRUE
537 : FALSE;
538 CFRelease(format);
539 if (actual != fullRange)
540 (void)fprintf(stderr, "[%s] full range %d, expected %d\n", __func__, actual, fullRange);
541 return actual == fullRange ? TRUE : FALSE;
542}
543
544WINPR_ATTR_NODISCARD
545static BOOL testCameraEncode(UINT32 width, UINT32 height)
546{
547 BOOL rc = FALSE;
548 const BYTE samples[][3] = { { 16, 128, 128 }, { 235, 128, 128 }, { 96, 64, 192 } };
549 const size_t pixels = (size_t)width * height;
550 BYTE* nv12 = calloc(pixels * 3 / 2, 1);
551 wStream* output = Stream_New(nullptr, 1);
552 FREERDP_VIDEO_CONTEXT* video = freerdp_video_context_new(width, height);
553 H264_CONTEXT* decoder = h264_context_new(FALSE);
554 if (!nv12 || !output || !video || !decoder || !h264_context_reset(decoder, width, height) ||
555 !freerdp_video_conversion_supported(FREERDP_VIDEO_FORMAT_NV12, FREERDP_VIDEO_FORMAT_H264))
556 goto fail;
557
558 for (size_t frame = 0; frame < ARRAYSIZE(samples); frame++)
559 {
560 /* Camera redirection reconfigures before every sample. */
561 if (!freerdp_video_context_reconfigure(video, width, height, 30, 0,
562 H264_CAMERA_VIDEO_REAL_TIME))
563 goto fail;
564 memset(nv12, samples[frame][0], pixels);
565 for (size_t i = 0; i < pixels / 2; i += 2)
566 {
567 nv12[pixels + i] = samples[frame][1];
568 nv12[pixels + i + 1] = samples[frame][2];
569 }
570 Stream_ResetPosition(output);
571 Stream_Write_UINT8(output, 0x5a);
572 if (!freerdp_video_sample_convert(video, FREERDP_VIDEO_FORMAT_NV12, nv12, pixels * 3 / 2,
573 FREERDP_VIDEO_FORMAT_H264, output) ||
574 (Stream_GetPosition(output) <= 1) || (Stream_Buffer(output)[0] != 0x5a))
575 goto fail;
576 const BYTE* encoded = Stream_Buffer(output) + 1;
577 const size_t size = Stream_GetPosition(output) - 1;
578 if ((size > UINT32_MAX) || ((frame == 0) && !testH264Range(encoded, size, FALSE)) ||
579 (decoder->subsystem->Decompress(decoder, encoded, (UINT32)size) <= 0))
580 goto fail;
581 /* Inspect decoded planes: the desktop RGB converter assumes full-range YUV. */
582 for (size_t plane = 0; plane < 3; plane++)
583 {
584 const UINT32 divisor = plane == 0 ? 1 : 2;
585 const size_t center =
586 (size_t)(height / (2 * divisor)) * decoder->iStride[plane] + width / (2 * divisor);
587 if (!decoder->pYUVData[plane] ||
588 (abs(decoder->pYUVData[plane][center] - samples[frame][plane]) > 4))
589 goto fail;
590 }
591 }
592 rc = TRUE;
593fail:
594 if (!rc)
595 (void)fprintf(stderr, "[%s] NV12 camera encoding failed at %" PRIu32 "x%" PRIu32 "\n",
596 __func__, width, height);
597 h264_context_free(decoder);
598 freerdp_video_context_free(video);
599 Stream_Free(output, TRUE);
600 free(nv12);
601 return rc;
602}
603
604WINPR_ATTR_NODISCARD
605static BOOL testEncodeRange(void)
606{
607 BOOL rc = FALSE;
608 H264_CONTEXT* encoder = h264_context_new(TRUE);
609 if (!encoder || !h264_context_reset(encoder, 64, 48))
610 goto fail;
611 BYTE* planes[3] = WINPR_C_ARRAY_INIT;
612 UINT32 strides[3] = WINPR_C_ARRAY_INIT;
613 if (h264_get_yuv_buffer(encoder, 64, 64, 48, planes, strides) < 0)
614 goto fail;
615 for (size_t plane = 0; plane < 3; plane++)
616 memset(planes[plane], 128, (size_t)strides[plane] * (plane == 0 ? 48 : 24));
617 const H264_USAGETYPE usage[] = { H264_SCREEN_CONTENT_REAL_TIME, H264_CAMERA_VIDEO_REAL_TIME,
618 H264_SCREEN_CONTENT_NON_REAL_TIME,
619 H264_CAMERA_VIDEO_NON_REAL_TIME,
620 H264_SCREEN_CONTENT_REAL_TIME };
621 for (size_t i = 0; i < ARRAYSIZE(usage); i++)
622 {
623 BYTE* encoded = nullptr;
624 UINT32 size = 0;
625 const BOOL fullRange = i % 2 == 0 ? TRUE : FALSE;
626 if (!h264_context_set_option(encoder, H264_CONTEXT_OPTION_USAGETYPE, usage[i]) ||
627 (h264_context_get_option(encoder, H264_CONTEXT_OPTION_USAGETYPE) != usage[i]) ||
628 (h264_compress(encoder, &encoded, &size) < 0) || !encoded ||
629 !testH264Range(encoded, size, fullRange))
630 goto fail;
631 }
632 rc = TRUE;
633fail:
634 if (!rc)
635 (void)fprintf(stderr, "[%s] encoder range change failed\n", __func__);
636 h264_context_free(encoder);
637 return rc;
638}
639#endif
640
641WINPR_ATTR_NODISCARD
642int TestFreeRDPCodecH264(int argc, char* argv[])
643{
644 WINPR_ASSERT(argv);
645
646 WINPR_UNUSED(argc);
647 WINPR_UNUSED(argv);
648
649 UINT32 width = 124;
650 UINT32 height = 54;
651 const UINT32 formats[] = {
652 PIXEL_FORMAT_ABGR15, PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_BGR16,
653 PIXEL_FORMAT_BGR24, PIXEL_FORMAT_RGB15, PIXEL_FORMAT_RGB16, PIXEL_FORMAT_RGB24,
654 PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_XRGB32,
655 PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_BGRX32, PIXEL_FORMAT_RGBX32,
656 };
657
658 if (argc == 3)
659 {
660 errno = 0;
661 width = strtoul(argv[1], nullptr, 0);
662 height = strtoul(argv[2], nullptr, 0);
663 if ((errno != 0) || (width == 0) || (height == 0))
664 {
665 char buffer[128] = WINPR_C_ARRAY_INIT;
666 (void)fprintf(stderr, "%s failed: width=%" PRIu32 ", height=%" PRIu32 ", errno=%s\n",
667 __func__, width, height, winpr_strerror(errno, buffer, sizeof(buffer)));
668 return -1;
669 }
670 }
671
672 /* The shared flag includes all H.264 backends. */
673#if !defined(WITH_GFX_H264)
674 (void)fprintf(stderr, "[%s] skipping, no H264 encoder/decoder support compiled in\n", __func__);
675 return 0;
676#endif
677
678#if defined(WITH_H264_WEBCODECS)
679 /* WebCodecs is probed at runtime; node (ctest) has no VideoDecoder. */
680 H264_CONTEXT* probe = h264_context_new(FALSE);
681 if (!probe)
682 {
683 (void)fprintf(stderr, "[%s] skipping, the runtime cannot decode H.264 with WebCodecs\n",
684 __func__);
685 return 0;
686 }
687 h264_context_free(probe);
688#endif
689
690 /* Decoder tests also run when no encoder backend is configured. */
691 if (!testContextOptions(FALSE, width, height))
692 return -1;
693 if (!testDecode(FALSE))
694 return -1;
695#if defined(WITH_APPLE_VIDEOTOOLBOX) || defined(WITH_H264_WEBCODECS)
696 if (!testDecode(TRUE))
697 return -1;
698#endif
699 /* Both AVC444 packing formats must support combined and separate updates. */
700 if (!testDecodeAvc444(RDPGFX_CODECID_AVC444) || !testDecodeAvc444(RDPGFX_CODECID_AVC444v2))
701 return -1;
702
703#if defined(WITH_MEDIA_FOUNDATION) || defined(WITH_OPENH264) || defined(WITH_VIDEO_FFMPEG) || \
704 defined(WITH_MEDIACODEC) || defined(WITH_APPLE_VIDEOTOOLBOX)
705#if defined(WITH_MEDIA_FOUNDATION)
706 /* Windows ships the Media Foundation H.264 encoder, but not every edition: N editions
707 * and Windows Server without the Media Foundation feature have none. */
708 H264_CONTEXT* encoder = h264_context_new(TRUE);
709 if (!encoder)
710 {
711 (void)fprintf(stderr, "[%s] H.264 compression tests skipped: no encoder on this system\n",
712 __func__);
713 return 0;
714 }
715 h264_context_free(encoder);
716#endif
717
718 /* Decoder-only builds cannot run the compression tests. */
719 if (!testContextOptions(TRUE, width, height))
720 return -1;
721 if (!testEncodeOffsetRegion())
722 return -1;
723
724#if defined(WITH_APPLE_VIDEOTOOLBOX)
725 H264_CONTEXT* vtEncoder = h264_context_new(TRUE);
726 const BOOL useVideoToolbox =
727 vtEncoder && (strcmp(vtEncoder->subsystem->name, "VideoToolbox") == 0) ? TRUE : FALSE;
728 h264_context_free(vtEncoder);
729 if (useVideoToolbox)
730 {
731 if (!testCameraEncode(64, 48) || !testCameraEncode(128, 96) || !testEncodeRange())
732 return -1;
733 }
734#endif
735
736 for (size_t x = 0; x < ARRAYSIZE(formats); x++)
737 {
738 const UINT32 SrcFormat = formats[x];
739 for (size_t y = 0; y < ARRAYSIZE(formats); y++)
740 {
741 if (!testEncode(SrcFormat, width, height))
742 return -1;
743 }
744 }
745#else
746 (void)fprintf(stderr, "H.264 compression tests skipped: no encoder configured\n");
747#endif
748
749 return 0;
750}