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
12static const char* print_ns(UINT64 start, UINT64 end, char* buffer, size_t len)
13{
14 const UINT64 diff = end - start;
15 const unsigned ns = diff % 1000;
16 const unsigned us = (diff / 1000) % 1000;
17 const unsigned ms = (diff / 1000000) % 1000;
18 const unsigned s = (diff / 1000000000ULL) % 1000;
19 (void)_snprintf(buffer, len, "%03u %03u %03u %03uns", s, ms, us, ns);
20 return buffer;
21}
22
23static BOOL testContextOptions(BOOL compressor, uint32_t width, uint32_t height)
24{
25 BOOL rc = FALSE;
26 const UINT64 start = winpr_GetUnixTimeNS();
27 H264_CONTEXT* h264 = h264_context_new(FALSE);
28 if (!h264)
29 {
30 (void)fprintf(stderr, "[%s] h264_context_new failed\n", __func__);
31 return FALSE;
32 }
33
34 struct optpair_s
35 {
36 H264_CONTEXT_OPTION opt;
37 uint32_t val;
38 };
39 const struct optpair_s optpair[] = { { H264_CONTEXT_OPTION_RATECONTROL, H264_RATECONTROL_VBR },
40 { H264_CONTEXT_OPTION_BITRATE, 2323 },
41 { H264_CONTEXT_OPTION_FRAMERATE, 23 },
42 { H264_CONTEXT_OPTION_QP, 21 },
43 { H264_CONTEXT_OPTION_USAGETYPE, 23 } };
44 for (size_t x = 0; x < ARRAYSIZE(optpair); x++)
45 {
46 const struct optpair_s* cur = &optpair[x];
47 if (!h264_context_set_option(h264, cur->opt, cur->val))
48 {
49 (void)fprintf(stderr,
50 "[%s] h264_context_set_option %" PRIuz "{ %d, %" PRIu32 " } failed\n",
51 __func__, x, cur->opt, cur->val);
52 goto fail;
53 }
54 }
55 if (!h264_context_reset(h264, width, height))
56 {
57 (void)fprintf(stderr, "[%s] h264_context_reset\n", __func__);
58 goto fail;
59 }
60
61 rc = TRUE;
62fail:
63 h264_context_free(h264);
64 const UINT64 end = winpr_GetUnixTimeNS();
65
66 char buffer[64] = WINPR_C_ARRAY_INIT;
67 printf("[%s] %" PRIu32 "x%" PRIu32 " took %s\n", __func__, width, height,
68 print_ns(start, end, buffer, sizeof(buffer)));
69 return rc;
70}
71
72static void* allocRGB(uint32_t format, uint32_t width, uint32_t height, uint32_t* pstride)
73{
74 const size_t bpp = FreeRDPGetBytesPerPixel(format);
75 const size_t stride = bpp * width + 32;
76 WINPR_ASSERT(pstride);
77 *pstride = WINPR_ASSERTING_INT_CAST(uint32_t, stride);
78
79 uint8_t* rgb = calloc(stride, height);
80 if (!rgb)
81 return nullptr;
82
83 for (size_t x = 0; x < height; x++)
84 {
85 if (winpr_RAND(&rgb[x * stride], width * bpp) < 0)
86 {
87 free(rgb);
88 return nullptr;
89 }
90 }
91 return rgb;
92}
93
94static BOOL compareRGB(const uint8_t* src, const uint8_t* dst, uint32_t format, size_t width,
95 size_t stride, size_t height)
96{
97 const size_t bpp = FreeRDPGetBytesPerPixel(format);
98 for (size_t y = 0; y < height; y++)
99 {
100 const uint8_t* csrc = &src[y * stride];
101 const uint8_t* cdst = &dst[y * stride];
102 const int rc = memcmp(csrc, cdst, width * bpp);
103 // TODO: Both, AVC420 encoding and decoding are lossy.
104 // TODO: Find a proper error margin to check for
105#if 0
106 if (rc != 0)
107 return FALSE;
108#endif
109 }
110 return TRUE;
111}
112
113static BOOL testEncode(uint32_t format, uint32_t width, uint32_t height)
114{
115 BOOL rc = FALSE;
116 void* src = nullptr;
117 void* out = nullptr;
118 RDPGFX_H264_METABLOCK meta = WINPR_C_ARRAY_INIT;
119 H264_CONTEXT* h264 = h264_context_new(TRUE);
120 H264_CONTEXT* h264dec = h264_context_new(FALSE);
121 if (!h264 || !h264dec)
122 {
123 (void)fprintf(stderr, "[%s] encoder=%p, decoder=%p\n", __func__, (void*)h264,
124 (void*)h264dec);
125 goto fail;
126 }
127
128 if (!h264_context_reset(h264, width, height))
129 {
130 (void)fprintf(stderr, "[%s] h264_context_reset(encoder) failed\n", __func__);
131 goto fail;
132 }
133 if (!h264_context_reset(h264dec, width, height))
134 {
135 (void)fprintf(stderr, "[%s] h264_context_reset(decoder) failed\n", __func__);
136 goto fail;
137 }
138
139 uint32_t stride = 0;
140 uint32_t ostride = 0;
141 src = allocRGB(format, width, height, &stride);
142 out = allocRGB(format, width, height, &ostride);
143 if (!src || !out || (stride < width) || (stride != ostride))
144 {
145 (void)fprintf(stderr,
146 "[%s] src=%p, out=%p, stride(%" PRIu32 ") < width(%" PRIu32
147 "), stride != ostride(%" PRIu32 ")\n",
148 __func__, src, out, stride, width, ostride);
149 goto fail;
150 }
151
152 const RECTANGLE_16 rect = { .left = 0, .top = 0, .right = width, .bottom = height };
153 uint32_t dstsize = 0;
154 uint8_t* dst = nullptr;
155 const int res1 =
156 avc420_compress(h264, src, format, stride, width, height, &rect, &dst, &dstsize, &meta);
157 if (res1 < 0)
158 {
159 (void)fprintf(stderr, "[%s] avc420_compress failed: %d\n", __func__, res1);
160 goto fail;
161 }
162 if ((dstsize == 0) || !dst)
163 {
164 (void)fprintf(stderr, "[%s] dstsize=%" PRIu32 ", dst=%p\n", __func__, dstsize, (void*)dst);
165 goto fail;
166 }
167
168 const int res2 =
169 avc420_decompress(h264dec, dst, dstsize, out, format, stride, width, height, &rect, 1);
170 if (res2 < 0)
171 {
172 (void)fprintf(stderr, "[%s] avc420_decompress failed: %d\n", __func__, res2);
173 goto fail;
174 }
175
176 rc = compareRGB(src, out, format, width, stride, height);
177fail:
178 if (!rc)
179 (void)fprintf(stderr, "[%s] run failed\n", __func__);
180 h264_context_free(h264);
181 h264_context_free(h264dec);
182 free_h264_metablock(&meta);
183 free(src);
184 free(out);
185 return rc;
186}
187
188static BOOL testEncodeOffsetRegion(void)
189{
190 BOOL rc = FALSE;
191 const UINT32 width = 192;
192 const UINT32 height = 128;
193 const UINT32 format = PIXEL_FORMAT_BGRA32;
194 const RECTANGLE_16 full = { .left = 0, .top = 0, .right = width, .bottom = height };
195 const RECTANGLE_16 region = { .left = 32, .top = 16, .right = 160, .bottom = 112 };
196 RDPGFX_H264_METABLOCK meta = WINPR_C_ARRAY_INIT;
197 H264_CONTEXT* h264 = h264_context_new(TRUE);
198 UINT32 stride = 0;
199 BYTE* src = allocRGB(format, width, height, &stride);
200 BYTE* dst = nullptr;
201 UINT32 dstSize = 0;
202
203 if (!h264 || !src || !h264_context_reset(h264, width, height))
204 goto fail;
205
206 memset(src, 0, (size_t)stride * height);
207 const int res =
208 avc420_compress(h264, src, format, stride, width, height, &full, &dst, &dstSize, &meta);
209 if (res < 0)
210 {
211
212 (void)fprintf(stderr, "[%s] first avc420_compress failed: %d\n", __func__, res);
213 goto fail;
214 }
215 free_h264_metablock(&meta);
216
217 for (UINT32 y = region.top; y < region.bottom; y++)
218 {
219 BYTE* row = &src[(size_t)y * stride + (size_t)region.left * 4];
220 memset(row, 0xFF, (size_t)(region.right - region.left) * 4);
221 }
222
223 dst = nullptr;
224 dstSize = 0;
225 const int res2 =
226 avc420_compress(h264, src, format, stride, width, height, &region, &dst, &dstSize, &meta);
227 if (res2 < 0)
228 {
229 (void)fprintf(stderr, "[%s] second avc420_compress failed: %d\n", __func__, res2);
230 goto fail;
231 }
232 if ((meta.numRegionRects == 0) || (meta.regionRects[0].left != region.left) ||
233 (meta.regionRects[0].top != region.top))
234 {
235 (void)fprintf(
236 stderr, "%s failed: first changed tile does not start at (%" PRIu16 ", %" PRIu16 ")\n",
237 __func__, region.left, region.top);
238 goto fail;
239 }
240
241 rc = TRUE;
242fail:
243 if (!rc)
244 (void)fprintf(stderr, "[%s] run failed\n", __func__);
245 h264_context_free(h264);
246 free_h264_metablock(&meta);
247 free(src);
248 return rc;
249}
250
251/* 64x64 solid 0x3366CC, generated with
252 * ffmpeg -f lavfi -i color=c=0x3366CC:s=64x64 -frames:v 1 -c:v libx264 -profile:v baseline
253 * -bsf:v h264_mp4toannexb,filter_units=remove_types=6 -f h264 */
254static const BYTE solid_idr_64x64[] = {
255 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0a, 0xd9, 0x04, 0x26, 0xc0, 0x44, 0x00,
256 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xc8, 0x3c, 0x48, 0x99, 0x20, 0x00,
257 0x00, 0x00, 0x01, 0x68, 0xcb, 0x83, 0xcb, 0x20, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84,
258 0x0a, 0xf1, 0x18, 0xa0, 0x00, 0x22, 0x4b, 0x1c, 0x00, 0x04, 0x53, 0xa3, 0x80, 0x00,
259 0x85, 0x8c, 0x9c, 0x9c, 0x9d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5e
260};
261
262static BOOL testDecode(void)
263{
264 BOOL rc = FALSE;
265 const UINT32 width = 64;
266 const UINT32 height = 64;
267 const UINT32 format = PIXEL_FORMAT_BGRX32;
268 const UINT32 stride = width * 4;
269 const RECTANGLE_16 rect = { .left = 0, .top = 0, .right = width, .bottom = height };
270 H264_CONTEXT* h264 = h264_context_new(FALSE);
271 BYTE* dst = calloc(stride, height);
272
273 if (!h264 || !dst || !h264_context_reset(h264, width, height))
274 goto fail;
275
276 /* the IDR must come out of the first call, a decoder lagging one frame leaves dst empty */
277 const INT32 res = avc420_decompress(h264, solid_idr_64x64, sizeof(solid_idr_64x64), dst, format,
278 stride, width, height, &rect, 1);
279 if (res < 0)
280 {
281 (void)fprintf(stderr, "[%s] avc420_decompress failed: %" PRId32 "\n", __func__, res);
282 goto fail;
283 }
284
285 for (UINT32 y = 0; y < height; y++)
286 {
287 for (UINT32 x = 0; x < width; x++)
288 {
289 BYTE r = 0;
290 BYTE g = 0;
291 BYTE b = 0;
292 const UINT32 color = FreeRDPReadColor(&dst[y * stride + x * 4], format);
293 FreeRDPSplitColor(color, format, &r, &g, &b, nullptr, nullptr);
294 if ((abs(r - 0x33) > 16) || (abs(g - 0x66) > 16) || (abs(b - 0xCC) > 16))
295 {
296 (void)fprintf(
297 stderr, "[%s] pixel %" PRIu32 ",%" PRIu32 " is %02X%02X%02X, expected 3366CC\n",
298 __func__, x, y, r, g, b);
299 goto fail;
300 }
301 }
302 }
303
304 rc = TRUE;
305fail:
306 h264_context_free(h264);
307 free(dst);
308 return rc;
309}
310
311int TestFreeRDPCodecH264(int argc, char* argv[])
312{
313 WINPR_UNUSED(argc);
314 WINPR_UNUSED(argv);
315
316 UINT32 width = 124;
317 UINT32 height = 54;
318 const UINT32 formats[] = {
319 PIXEL_FORMAT_ABGR15, PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_BGR16,
320 PIXEL_FORMAT_BGR24, PIXEL_FORMAT_RGB15, PIXEL_FORMAT_RGB16, PIXEL_FORMAT_RGB24,
321 PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_XRGB32,
322 PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_BGRX32, PIXEL_FORMAT_RGBX32,
323 };
324
325 if (argc == 3)
326 {
327 errno = 0;
328 width = strtoul(argv[1], nullptr, 0);
329 height = strtoul(argv[2], nullptr, 0);
330 if ((errno != 0) || (width == 0) || (height == 0))
331 {
332 char buffer[128] = WINPR_C_ARRAY_INIT;
333 (void)fprintf(stderr, "%s failed: width=%" PRIu32 ", height=%" PRIu32 ", errno=%s\n",
334 __func__, width, height, winpr_strerror(errno, buffer, sizeof(buffer)));
335 return -1;
336 }
337 }
338
339#if !defined(WITH_MEDIACODEC) && !defined(WITH_MEDIA_FOUNDATION) && !defined(WITH_OPENH264) && \
340 !defined(WITH_VIDEO_FFMPEG)
341 (void)fprintf(stderr, "[%s] skipping, no H264 encoder/decoder support compiled in\n", __func__);
342 return 0;
343#endif
344
345 if (!testContextOptions(FALSE, width, height))
346 return -1;
347 if (!testContextOptions(TRUE, width, height))
348 return -1;
349 if (!testDecode())
350 return -1;
351#if !defined(WITH_MEDIA_FOUNDATION)
352 if (!testEncodeOffsetRegion())
353 return -1;
354
355 for (size_t x = 0; x < ARRAYSIZE(formats); x++)
356 {
357 const UINT32 SrcFormat = formats[x];
358 for (size_t y = 0; y < ARRAYSIZE(formats); y++)
359 {
360 if (!testEncode(SrcFormat, width, height))
361 return -1;
362 }
363 }
364#else
365 (void)fprintf(stderr,
366 "TODO: WITH_MEDIA_FOUNDATION: compression tests skipped, not implemented\n");
367#endif
368
369 return 0;
370}