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
251int TestFreeRDPCodecH264(int argc, char* argv[])
252{
253 WINPR_UNUSED(argc);
254 WINPR_UNUSED(argv);
255
256 UINT32 width = 124;
257 UINT32 height = 54;
258 const UINT32 formats[] = {
259 PIXEL_FORMAT_ABGR15, PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_BGR16,
260 PIXEL_FORMAT_BGR24, PIXEL_FORMAT_RGB15, PIXEL_FORMAT_RGB16, PIXEL_FORMAT_RGB24,
261 PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_XRGB32,
262 PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_BGRX32, PIXEL_FORMAT_RGBX32,
263 };
264
265 if (argc == 3)
266 {
267 errno = 0;
268 width = strtoul(argv[1], nullptr, 0);
269 height = strtoul(argv[2], nullptr, 0);
270 if ((errno != 0) || (width == 0) || (height == 0))
271 {
272 char buffer[128] = WINPR_C_ARRAY_INIT;
273 (void)fprintf(stderr, "%s failed: width=%" PRIu32 ", height=%" PRIu32 ", errno=%s\n",
274 __func__, width, height, winpr_strerror(errno, buffer, sizeof(buffer)));
275 return -1;
276 }
277 }
278
279#if !defined(WITH_MEDIACODEC) && !defined(WITH_MEDIA_FOUNDATION) && !defined(WITH_OPENH264) && \
280 !defined(WITH_VIDEO_FFMPEG)
281 (void)fprintf(stderr, "[%s] skipping, no H264 encoder/decoder support compiled in\n", __func__);
282 return 0;
283#endif
284
285 if (!testContextOptions(FALSE, width, height))
286 return -1;
287 if (!testContextOptions(TRUE, width, height))
288 return -1;
289#if !defined(WITH_MEDIA_FOUNDATION)
290 if (!testEncodeOffsetRegion())
291 return -1;
292
293 for (size_t x = 0; x < ARRAYSIZE(formats); x++)
294 {
295 const UINT32 SrcFormat = formats[x];
296 for (size_t y = 0; y < ARRAYSIZE(formats); y++)
297 {
298 if (!testEncode(SrcFormat, width, height))
299 return -1;
300 }
301 }
302#else
303 (void)fprintf(stderr,
304 "TODO: WITH_MEDIA_FOUNDATION: compression tests skipped, not implemented\n");
305#endif
306
307 return 0;
308}