FreeRDP
Loading...
Searching...
No Matches
h264.c
1
21#include <freerdp/config.h>
22
23#include <winpr/crt.h>
24#include <winpr/print.h>
25#include <winpr/library.h>
26#include <winpr/bitstream.h>
27#include <winpr/synch.h>
28
29#include <freerdp/primitives.h>
30#include <freerdp/codec/h264.h>
31#include <freerdp/codec/yuv.h>
32#include <freerdp/log.h>
33#include <freerdp/codec/region.h>
34
35#include "h264.h"
36
37#define TAG FREERDP_TAG("codec")
38
39WINPR_ATTR_NODISCARD
40static BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight);
41
42WINPR_ATTR_NODISCARD
43static BOOL yuv_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT32 height)
44{
45 BOOL isNull = FALSE;
46 UINT32 pheight = height;
47
48 if (!h264)
49 return FALSE;
50
51 if (stride == 0)
52 {
53 const UINT32 pad = width % 16;
54 stride = width;
55 if (pad > 0)
56 width += 16 - pad;
57 }
58
59 /* Add padding lines. Allows relaxing bounds checks in decoder functions */
60 stride += 32 - stride % 16;
61 pheight += 32 - pheight % 16;
62
63 const size_t nPlanes = h264->hwAccel ? 2 : 3;
64
65 for (size_t x = 0; x < nPlanes; x++)
66 {
67 if (!h264->pYUVData[x] || !h264->pOldYUVData[x])
68 isNull = TRUE;
69 }
70
71 if (pheight == 0)
72 return FALSE;
73 if (stride == 0)
74 return FALSE;
75
76 if (isNull || (width != h264->width) || (height != h264->height) ||
77 (stride != h264->iStride[0]))
78 {
79 UINT32 iStride[3] = WINPR_C_ARRAY_INIT;
80 if (h264->hwAccel) /* NV12 */
81 {
82 iStride[0] = stride;
83 iStride[1] = stride;
84 iStride[2] = 0;
85 }
86 else /* I420 */
87 {
88 iStride[0] = stride;
89 iStride[1] = (stride + 1) / 2;
90 iStride[2] = (stride + 1) / 2;
91 }
92
93 for (size_t x = 0; x < nPlanes; x++)
94 {
95 BYTE* tmp1 = winpr_aligned_recalloc(h264->pYUVData[x], iStride[x], pheight, 16);
96 BYTE* tmp2 = winpr_aligned_recalloc(h264->pOldYUVData[x], iStride[x], pheight, 16);
97 if (tmp1)
98 h264->pYUVData[x] = tmp1;
99 if (tmp2)
100 h264->pOldYUVData[x] = tmp2;
101 if (!tmp1 || !tmp2)
102 return FALSE;
103 }
104 /* Only update the strides after the buffers were successfully allocated */
105 memcpy(h264->iStride, iStride, sizeof(h264->iStride));
106 h264->width = width;
107 h264->height = height;
108 }
109
110 return TRUE;
111}
112
113BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT32 height)
114{
115 return yuv_ensure_buffer(h264, stride, width, height);
116}
117
118WINPR_ATTR_NODISCARD
119static BOOL isRectValid(wLog* log, size_t pos, UINT32 width, UINT32 height,
120 const RECTANGLE_16* rect)
121{
122 WINPR_ASSERT(rect);
123 if ((rect->left > width) || (rect->right > width) || (rect->left >= rect->right) ||
124 (rect->top > height) || (rect->bottom > height) || (rect->top >= rect->bottom))
125 {
126 char buffer[64] = WINPR_C_ARRAY_INIT;
127 WLog_Print(log, WLOG_WARN,
128 "Rectangle %" PRIuz " %s outside of bounding frame %" PRIu32 "x%" PRIu32, pos,
129 rectangle_to_string(rect, buffer, sizeof(buffer)), width, height);
130 return FALSE;
131 }
132
133 return TRUE;
134}
135
136WINPR_ATTR_NODISCARD
137static BOOL areRectsValid(wLog* log, UINT32 width, UINT32 height, const RECTANGLE_16* rects,
138 UINT32 count)
139{
140 WINPR_ASSERT(rects || (count == 0));
141 for (size_t x = 0; x < count; x++)
142 {
143 const RECTANGLE_16* rect = &rects[x];
144 if (!isRectValid(log, x, width, height, rect))
145 return FALSE;
146 }
147 return TRUE;
148}
149
150WINPR_ATTR_NODISCARD
151static int log_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize,
152 const RECTANGLE_16* rects, UINT32 nrRects)
153{
154 WINPR_ASSERT(h264);
155 WINPR_ASSERT(h264->subsystem);
156 WINPR_ASSERT(h264->subsystem->Decompress);
157
158 const int status = h264->subsystem->Decompress(h264, pSrcData, SrcSize);
159 if (status < 0)
160 {
161 WLog_Print(h264->log, WLOG_WARN, "H264 decompress failed with %d", status);
162 return status;
163 }
164
165 /* no picture decoded, nothing to validate */
166 if (status == 0)
167 return 0;
168
169 /* some server implementations (krdc) use H264 frames smaller than the surface sizes,
170 * validate the regions against this size as well */
171 if (!areRectsValid(h264->log, h264->YUVWidth, h264->YUVHeight, rects, nrRects))
172 return -1015;
173 if (!areRectsValid(h264->log, h264->width, h264->height, rects, nrRects))
174 return -1016;
175 return status;
176}
177
178INT32 avc420_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstData,
179 DWORD DstFormat, UINT32 nDstStep, WINPR_ATTR_UNUSED UINT32 nDstWidth,
180 WINPR_ATTR_UNUSED UINT32 nDstHeight, const RECTANGLE_16* regionRects,
181 UINT32 numRegionRects)
182{
183 int status = 0;
184 const BYTE* pYUVData[3];
185
186 if (!h264 || h264->Compressor)
187 return -1001;
188
189 if (!areRectsValid(h264->log, nDstWidth, nDstHeight, regionRects, numRegionRects))
190 return -1013;
191
192 status = log_decompress(h264, pSrcData, SrcSize, regionRects, numRegionRects);
193
194 if (status == 0)
195 return 1;
196
197 if (status < 0)
198 return status;
199
200 pYUVData[0] = h264->pYUVData[0];
201 pYUVData[1] = h264->pYUVData[1];
202 pYUVData[2] = h264->pYUVData[2];
203 if (!yuv420_context_decode(h264->yuv, pYUVData, h264->iStride, h264->YUVHeight, DstFormat,
204 pDstData, nDstStep, regionRects, numRegionRects))
205 return -1002;
206
207 return 1;
208}
209
210WINPR_ATTR_NODISCARD
211static BOOL allocate_h264_metablock(UINT32 QP, RECTANGLE_16* rectangles,
212 RDPGFX_H264_METABLOCK* meta, size_t count)
213{
214 /* [MS-RDPEGFX] 2.2.4.4.2 RDPGFX_AVC420_QUANT_QUALITY */
215 if (!meta || (QP > UINT8_MAX))
216 {
217 free(rectangles);
218 return FALSE;
219 }
220
221 meta->regionRects = rectangles;
222 if (count == 0)
223 return TRUE;
224
225 if (count > UINT32_MAX)
226 return FALSE;
227
228 meta->quantQualityVals = calloc(count, sizeof(RDPGFX_H264_QUANT_QUALITY));
229
230 if (!meta->quantQualityVals || !meta->regionRects)
231 return FALSE;
232 meta->numRegionRects = (UINT32)count;
233 for (size_t x = 0; x < count; x++)
234 {
235 RDPGFX_H264_QUANT_QUALITY* cur = &meta->quantQualityVals[x];
236 cur->qp = (UINT8)QP;
237
238 /* qpVal bit 6 and 7 are flags, so mask them out here.
239 * qualityVal is [0-100] so 100 - qpVal [0-64] is always in range */
240 cur->qualityVal = 100 - (QP & 0x3F);
241 }
242 return TRUE;
243}
244
245WINPR_ATTR_NODISCARD
246static inline BOOL diff_tile(const RECTANGLE_16* regionRect, BYTE* pYUVData[3],
247 BYTE* pOldYUVData[3], UINT32 const iStride[3])
248{
249 size_t size = 0;
250
251 if (!regionRect || !pYUVData || !pOldYUVData || !iStride)
252 return FALSE;
253 size = regionRect->right - regionRect->left;
254 if (regionRect->right > iStride[0])
255 return FALSE;
256 if (regionRect->right / 2u > iStride[1])
257 return FALSE;
258 if (regionRect->right / 2u > iStride[2])
259 return FALSE;
260
261 for (size_t y = regionRect->top; y < regionRect->bottom; y++)
262 {
263 const BYTE* cur0 = &pYUVData[0][y * iStride[0]];
264 const BYTE* cur1 = &pYUVData[1][y * iStride[1]];
265 const BYTE* cur2 = &pYUVData[2][y * iStride[2]];
266 const BYTE* old0 = &pOldYUVData[0][y * iStride[0]];
267 const BYTE* old1 = &pOldYUVData[1][y * iStride[1]];
268 const BYTE* old2 = &pOldYUVData[2][y * iStride[2]];
269
270 if (memcmp(&cur0[regionRect->left], &old0[regionRect->left], size) != 0)
271 return TRUE;
272 if (memcmp(&cur1[regionRect->left / 2], &old1[regionRect->left / 2], size / 2) != 0)
273 return TRUE;
274 if (memcmp(&cur2[regionRect->left / 2], &old2[regionRect->left / 2], size / 2) != 0)
275 return TRUE;
276 }
277 return FALSE;
278}
279
280WINPR_ATTR_NODISCARD
281static BOOL detect_changes(BOOL firstFrameDone, const UINT32 QP, const RECTANGLE_16* regionRect,
282 BYTE* pYUVData[3], BYTE* pOldYUVData[3], UINT32 const iStride[3],
284{
285 size_t count = 0;
286 size_t wc = 0;
287 size_t hc = 0;
288 RECTANGLE_16* rectangles = nullptr;
289
290 if (!regionRect || !pYUVData || !pOldYUVData || !iStride || !meta)
291 return FALSE;
292
293 wc = (regionRect->right - regionRect->left) / 64 + 1;
294 hc = (regionRect->bottom - regionRect->top) / 64 + 1;
295 rectangles = calloc(wc * hc, sizeof(RECTANGLE_16));
296 if (!rectangles)
297 return FALSE;
298 if (!firstFrameDone)
299 {
300 rectangles[0] = *regionRect;
301 count = 1;
302 }
303 else
304 {
305 for (size_t y = 0; y < regionRect->bottom - regionRect->top; y += 64)
306 {
307 for (size_t x = 0; x < regionRect->right - regionRect->left; x += 64)
308 {
309 RECTANGLE_16 rect;
310 rect.left = (UINT16)MIN(UINT16_MAX, regionRect->left + x);
311 rect.top = (UINT16)MIN(UINT16_MAX, regionRect->top + y);
312 rect.right =
313 (UINT16)MIN(UINT16_MAX, MIN(regionRect->left + x + 64, regionRect->right));
314 rect.bottom =
315 (UINT16)MIN(UINT16_MAX, MIN(regionRect->top + y + 64, regionRect->bottom));
316 if (diff_tile(&rect, pYUVData, pOldYUVData, iStride))
317 rectangles[count++] = rect;
318 }
319 }
320 }
321 if (!allocate_h264_metablock(QP, rectangles, meta, count))
322 return FALSE;
323 return TRUE;
324}
325
326INT32 h264_get_yuv_buffer(H264_CONTEXT* h264, UINT32 nSrcStride, UINT32 nSrcWidth,
327 UINT32 nSrcHeight, BYTE* YUVData[3], UINT32 stride[3])
328{
329 if (!h264 || !h264->Compressor || !h264->subsystem || !h264->subsystem->Compress)
330 return -1;
331
332 if (!yuv_ensure_buffer(h264, nSrcStride, nSrcWidth, nSrcHeight))
333 return -1;
334
335 for (size_t x = 0; x < 3; x++)
336 {
337 YUVData[x] = h264->pYUVData[x];
338 stride[x] = h264->iStride[x];
339 }
340
341 return 0;
342}
343
344INT32 h264_compress(H264_CONTEXT* h264, BYTE** ppDstData, UINT32* pDstSize)
345{
346 if (!h264 || !h264->Compressor || !h264->subsystem || !h264->subsystem->Compress)
347 return -1;
348
349 const BYTE* pcYUVData[3] = { h264->pYUVData[0], h264->pYUVData[1], h264->pYUVData[2] };
350
351 return h264->subsystem->Compress(h264, pcYUVData, h264->iStride, ppDstData, pDstSize);
352}
353
354INT32 avc420_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
355 UINT32 nSrcWidth, UINT32 nSrcHeight, const RECTANGLE_16* regionRect,
356 BYTE** ppDstData, UINT32* pDstSize, RDPGFX_H264_METABLOCK* meta)
357{
358 INT32 rc = -1;
359 BYTE* pYUVData[3] = WINPR_C_ARRAY_INIT;
360 const BYTE* pcYUVData[3] = WINPR_C_ARRAY_INIT;
361 BYTE* pOldYUVData[3] = WINPR_C_ARRAY_INIT;
362
363 if (!h264 || !regionRect || !meta || !h264->Compressor)
364 return -1;
365
366 if (!h264->subsystem->Compress)
367 return -1;
368
369 if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight))
370 return -1;
371
372 if (h264->encodingBuffer)
373 {
374 for (size_t x = 0; x < 3; x++)
375 {
376 pYUVData[x] = h264->pYUVData[x];
377 pOldYUVData[x] = h264->pOldYUVData[x];
378 }
379 }
380 else
381 {
382 for (size_t x = 0; x < 3; x++)
383 {
384 pYUVData[x] = h264->pOldYUVData[x];
385 pOldYUVData[x] = h264->pYUVData[x];
386 }
387 }
388 h264->encodingBuffer = !h264->encodingBuffer;
389
390 if (!yuv420_context_encode(h264->yuv, pSrcData, nSrcStep, SrcFormat, h264->iStride, pYUVData,
391 regionRect, 1))
392 goto fail;
393
394 if (!detect_changes(h264->firstLumaFrameDone, h264->QP, regionRect, pYUVData, pOldYUVData,
395 h264->iStride, meta))
396 goto fail;
397
398 if (meta->numRegionRects == 0)
399 {
400 rc = 0;
401 goto fail;
402 }
403
404 for (size_t x = 0; x < 3; x++)
405 pcYUVData[x] = pYUVData[x];
406
407 rc = h264->subsystem->Compress(h264, pcYUVData, h264->iStride, ppDstData, pDstSize);
408 if (rc >= 0)
409 h264->firstLumaFrameDone = TRUE;
410
411fail:
412 if (rc < 0)
413 free_h264_metablock(meta);
414 return rc;
415}
416
417INT32 avc444_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
418 UINT32 nSrcWidth, UINT32 nSrcHeight, BYTE version, const RECTANGLE_16* region,
419 BYTE* op, BYTE** ppDstData, UINT32* pDstSize, BYTE** ppAuxDstData,
420 UINT32* pAuxDstSize, RDPGFX_H264_METABLOCK* meta,
421 RDPGFX_H264_METABLOCK* auxMeta)
422{
423 int rc = -1;
424 BYTE* coded = nullptr;
425 UINT32 codedSize = 0;
426 BYTE** pYUV444Data = nullptr;
427 BYTE** pOldYUV444Data = nullptr;
428 BYTE** pYUVData = nullptr;
429 BYTE** pOldYUVData = nullptr;
430
431 if (!h264 || !h264->Compressor)
432 return -1;
433
434 if (!h264->subsystem->Compress)
435 return -1;
436
437 if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight))
438 return -1;
439
440 if (!avc444_ensure_buffer(h264, nSrcHeight))
441 return -1;
442
443 if (h264->encodingBuffer)
444 {
445 pYUV444Data = h264->pOldYUV444Data;
446 pOldYUV444Data = h264->pYUV444Data;
447 pYUVData = h264->pOldYUVData;
448 pOldYUVData = h264->pYUVData;
449 }
450 else
451 {
452 pYUV444Data = h264->pYUV444Data;
453 pOldYUV444Data = h264->pOldYUV444Data;
454 pYUVData = h264->pYUVData;
455 pOldYUVData = h264->pOldYUVData;
456 }
457 h264->encodingBuffer = !h264->encodingBuffer;
458
459 if (!yuv444_context_encode(h264->yuv, version, pSrcData, nSrcStep, SrcFormat, h264->iStride,
460 pYUV444Data, pYUVData, region, 1))
461 goto fail;
462
463 if (!detect_changes(h264->firstLumaFrameDone, h264->QP, region, pYUV444Data, pOldYUV444Data,
464 h264->iStride, meta))
465 goto fail;
466 if (!detect_changes(h264->firstChromaFrameDone, h264->QP, region, pYUVData, pOldYUVData,
467 h264->iStride, auxMeta))
468 goto fail;
469
470 /* [MS-RDPEGFX] 2.2.4.5 RFX_AVC444_BITMAP_STREAM
471 * LC:
472 * 0 ... Luma & Chroma
473 * 1 ... Luma
474 * 2 ... Chroma
475 */
476 if ((meta->numRegionRects > 0) && (auxMeta->numRegionRects > 0))
477 *op = 0;
478 else if (meta->numRegionRects > 0)
479 *op = 1;
480 else if (auxMeta->numRegionRects > 0)
481 *op = 2;
482 else
483 {
484 WLog_Print(h264->log, WLOG_TRACE, "no changes detected for luma or chroma frame");
485 rc = 0;
486 goto fail;
487 }
488
489 if ((*op == 0) || (*op == 1))
490 {
491 const BYTE* pcYUV444Data[3] = { pYUV444Data[0], pYUV444Data[1], pYUV444Data[2] };
492
493 if (h264->subsystem->Compress(h264, pcYUV444Data, h264->iStride, &coded, &codedSize) < 0)
494 goto fail;
495 h264->firstLumaFrameDone = TRUE;
496 memcpy(h264->lumaData, coded, codedSize);
497 *ppDstData = h264->lumaData;
498 *pDstSize = codedSize;
499 }
500
501 if ((*op == 0) || (*op == 2))
502 {
503 const BYTE* pcYUVData[3] = { pYUVData[0], pYUVData[1], pYUVData[2] };
504
505 if (h264->subsystem->Compress(h264, pcYUVData, h264->iStride, &coded, &codedSize) < 0)
506 goto fail;
507 h264->firstChromaFrameDone = TRUE;
508 *ppAuxDstData = coded;
509 *pAuxDstSize = codedSize;
510 }
511
512 rc = 1;
513fail:
514 if (rc < 0)
515 {
516 free_h264_metablock(meta);
517 free_h264_metablock(auxMeta);
518 }
519 return rc;
520}
521
522BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight)
523{
524 WINPR_ASSERT(h264);
525
526 const UINT32* piMainStride = h264->iStride;
527 UINT32* piDstSize = h264->iYUV444Size;
528 UINT32* piDstStride = h264->iYUV444Stride;
529 BYTE** ppYUVDstData = h264->pYUV444Data;
530 BYTE** ppOldYUVDstData = h264->pOldYUV444Data;
531
532 nDstHeight = MAX(h264->height, nDstHeight);
533 const UINT32 pad = nDstHeight % 16;
534 UINT32 padDstHeight = nDstHeight; /* Need alignment to 16x16 blocks */
535
536 if (pad != 0)
537 padDstHeight += 16 - pad;
538
539 /* Add padding lines. Allows relaxing bounds checks in decoder functions */
540 padDstHeight += 16;
541
542 if ((piMainStride[0] == 0) || (padDstHeight == 0))
543 return FALSE;
544
545 const uint64_t dstsize = 1ull * piMainStride[0] * padDstHeight;
546 if (dstsize > UINT32_MAX)
547 return FALSE;
548
549 if ((piMainStride[0] != piDstStride[0]) || (piDstSize[0] != dstsize))
550 {
551 const UINT32 size = WINPR_ASSERTING_INT_CAST(UINT32, dstsize);
552
553 for (UINT32 x = 0; x < 3; x++)
554 {
555 BYTE* tmp1 = winpr_aligned_recalloc(ppYUVDstData[x], size, 1, 16);
556 if (!tmp1)
557 return FALSE;
558 ppYUVDstData[x] = tmp1;
559 BYTE* tmp2 = winpr_aligned_recalloc(ppOldYUVDstData[x], size, 1, 16);
560 if (!tmp2)
561 return FALSE;
562 ppOldYUVDstData[x] = tmp2;
563 }
564
565 {
566 BYTE* tmp = winpr_aligned_recalloc(h264->lumaData, size, 4, 16);
567 if (!tmp)
568 goto fail;
569 h264->lumaData = tmp;
570 }
571
572 /* Only update stride and size after all buffers were successfully allocated,
573 * otherwise the next call would skip the reallocation and use too small planes */
574 for (UINT32 x = 0; x < 3; x++)
575 {
576 piDstStride[x] = piMainStride[0];
577 piDstSize[x] = size;
578 }
579 }
580
581 for (UINT32 x = 0; x < 3; x++)
582 {
583 if (!ppOldYUVDstData[x] || !ppYUVDstData[x] || (piDstSize[x] == 0) || (piDstStride[x] == 0))
584 {
585 WLog_Print(h264->log, WLOG_ERROR,
586 "YUV buffer not initialized! check your decoder settings");
587 goto fail;
588 }
589 }
590
591 if (!h264->lumaData)
592 goto fail;
593
594 return TRUE;
595fail:
596 return FALSE;
597}
598
599WINPR_ATTR_NODISCARD
600static BOOL avc444_process_rects(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize,
601 BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
602 WINPR_ATTR_UNUSED UINT32 nDstWidth, UINT32 nDstHeight,
603 const RECTANGLE_16* rects, UINT32 nrRects, avc444_frame_type type)
604{
605 const BYTE* pYUVData[3] = WINPR_C_ARRAY_INIT;
606 BYTE* pYUVDstData[3] = WINPR_C_ARRAY_INIT;
607 UINT32* piDstStride = h264->iYUV444Stride;
608 BYTE** ppYUVDstData = h264->pYUV444Data;
609 const UINT32* piStride = h264->iStride;
610
611 const int status = log_decompress(h264, pSrcData, SrcSize, rects, nrRects);
612 if (status < 0)
613 return FALSE;
614 if (status == 0)
615 return TRUE;
616
617 pYUVData[0] = h264->pYUVData[0];
618 pYUVData[1] = h264->pYUVData[1];
619 pYUVData[2] = h264->pYUVData[2];
620 if (!avc444_ensure_buffer(h264, nDstHeight))
621 return FALSE;
622
623 pYUVDstData[0] = ppYUVDstData[0];
624 pYUVDstData[1] = ppYUVDstData[1];
625 pYUVDstData[2] = ppYUVDstData[2];
626 return (yuv444_context_decode(h264->yuv, (BYTE)type, pYUVData, piStride, h264->YUVHeight,
627 pYUVDstData, piDstStride, DstFormat, pDstData, nDstStep, rects,
628 nrRects));
629}
630
631#if defined(AVC444_FRAME_STAT)
632static UINT64 op1 = 0;
633static double op1sum = 0;
634static UINT64 op2 = 0;
635static double op2sum = 0;
636static UINT64 op3 = 0;
637static double op3sum = 0;
638static double avg(UINT64* count, double old, double size)
639{
640 double tmp = size + *count * old;
641 (*count)++;
642 tmp = tmp / *count;
643 return tmp;
644}
645#endif
646
647INT32 avc444_decompress(H264_CONTEXT* h264, BYTE op, const RECTANGLE_16* regionRects,
648 UINT32 numRegionRects, const BYTE* pSrcData, UINT32 SrcSize,
649 const RECTANGLE_16* auxRegionRects, UINT32 numAuxRegionRect,
650 const BYTE* pAuxSrcData, UINT32 AuxSrcSize, BYTE* pDstData, DWORD DstFormat,
651 UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight, UINT32 codecId)
652{
653 INT32 status = -1;
654 avc444_frame_type chroma =
655 (codecId == RDPGFX_CODECID_AVC444) ? AVC444_CHROMAv1 : AVC444_CHROMAv2;
656
657 if (!h264 || !regionRects || !pSrcData || !pDstData || h264->Compressor)
658 return -1001;
659
660 if (!areRectsValid(h264->log, nDstWidth, nDstHeight, regionRects, numRegionRects))
661 return -1013;
662 if (!areRectsValid(h264->log, nDstWidth, nDstHeight, auxRegionRects, numAuxRegionRect))
663 return -1014;
664
665 switch (op)
666 {
667 case 0: /* YUV420 in stream 1
668 * Chroma420 in stream 2 */
669 if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
670 nDstWidth, nDstHeight, regionRects, numRegionRects,
671 AVC444_LUMA))
672 status = -1;
673 else if (!avc444_process_rects(h264, pAuxSrcData, AuxSrcSize, pDstData, DstFormat,
674 nDstStep, nDstWidth, nDstHeight, auxRegionRects,
675 numAuxRegionRect, chroma))
676 status = -1;
677 else
678 status = 0;
679
680 break;
681
682 case 2: /* Chroma420 in stream 1 */
683 if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
684 nDstWidth, nDstHeight, regionRects, numRegionRects, chroma))
685 status = -1;
686 else
687 status = 0;
688
689 break;
690
691 case 1: /* YUV420 in stream 1 */
692 if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
693 nDstWidth, nDstHeight, regionRects, numRegionRects,
694 AVC444_LUMA))
695 status = -1;
696 else
697 status = 0;
698
699 break;
700
701 default: /* WTF? */
702 break;
703 }
704
705#if defined(AVC444_FRAME_STAT)
706
707 switch (op)
708 {
709 case 0:
710 op1sum = avg(&op1, op1sum, SrcSize + AuxSrcSize);
711 break;
712
713 case 1:
714 op2sum = avg(&op2, op2sum, SrcSize);
715 break;
716
717 case 2:
718 op3sum = avg(&op3, op3sum, SrcSize);
719 break;
720
721 default:
722 break;
723 }
724
725 WLog_Print(h264->log, WLOG_INFO,
726 "luma=%" PRIu64 " [avg=%lf] chroma=%" PRIu64 " [avg=%lf] combined=%" PRIu64
727 " [avg=%lf]",
728 op1, op1sum, op2, op2sum, op3, op3sum);
729#endif
730 return status;
731}
732
733#define MAX_SUBSYSTEMS 10
734static INIT_ONCE subsystems_once = INIT_ONCE_STATIC_INIT;
735static const H264_CONTEXT_SUBSYSTEM* subSystems[MAX_SUBSYSTEMS] = WINPR_C_ARRAY_INIT;
736
737WINPR_ATTR_NODISCARD
738static BOOL CALLBACK h264_register_subsystems(WINPR_ATTR_UNUSED PINIT_ONCE once,
739 WINPR_ATTR_UNUSED PVOID param,
740 WINPR_ATTR_UNUSED PVOID* context)
741{
742 int i = 0;
743
744#ifdef WITH_MEDIACODEC
745 {
746 subSystems[i] = &g_Subsystem_mediacodec;
747 i++;
748 }
749#endif
750#if defined(_WIN32) && defined(WITH_MEDIA_FOUNDATION)
751 {
752 subSystems[i] = &g_Subsystem_MF;
753 i++;
754 }
755#endif
756#ifdef WITH_APPLE_VIDEOTOOLBOX
757 {
758 subSystems[i] = &g_Subsystem_VideoToolbox;
759 i++;
760 }
761#endif
762#ifdef WITH_OPENH264
763 {
764 subSystems[i] = &g_Subsystem_OpenH264;
765 i++;
766 }
767#endif
768#ifdef WITH_VIDEO_FFMPEG
769 {
770 subSystems[i] = &g_Subsystem_libavcodec;
771 i++;
772 }
773#endif
774#ifdef WITH_H264_WEBCODECS
775 {
776 subSystems[i] = &g_Subsystem_WebCodecs;
777 i++;
778 }
779#endif
780 return i > 0;
781}
782
783WINPR_ATTR_NODISCARD
784static BOOL h264_context_init(H264_CONTEXT* h264)
785{
786 if (!h264)
787 return FALSE;
788
789 h264->subsystem = nullptr;
790 if (!InitOnceExecuteOnce(&subsystems_once, h264_register_subsystems, nullptr, nullptr))
791 return FALSE;
792
793 for (size_t i = 0; i < MAX_SUBSYSTEMS; i++)
794 {
795 const H264_CONTEXT_SUBSYSTEM* subsystem = subSystems[i];
796
797 if (!subsystem || !subsystem->Init)
798 break;
799
800 if (subsystem->Init(h264))
801 {
802 h264->subsystem = subsystem;
803 return TRUE;
804 }
805 }
806
807 return FALSE;
808}
809
810static void h264_buffers_free(H264_CONTEXT* h264)
811{
812 WINPR_ASSERT(h264);
813 for (size_t x = 0; x < 3; x++)
814 {
815 if (h264->Compressor)
816 {
817 winpr_aligned_free(h264->pYUVData[x]);
818 winpr_aligned_free(h264->pOldYUVData[x]);
819 }
820 winpr_aligned_free(h264->pYUV444Data[x]);
821 winpr_aligned_free(h264->pOldYUV444Data[x]);
822
823 h264->pYUVData[x] = nullptr;
824 h264->pOldYUVData[x] = nullptr;
825 h264->pYUV444Data[x] = nullptr;
826 h264->pOldYUV444Data[x] = nullptr;
827 }
828 winpr_aligned_free(h264->lumaData);
829 h264->lumaData = nullptr;
830}
831
832BOOL h264_context_reset(H264_CONTEXT* h264, UINT32 width, UINT32 height)
833{
834 if (!h264)
835 return FALSE;
836
837 if (width > UINT16_MAX)
838 {
839 WLog_Print(h264->log, WLOG_ERROR, "h264::width %" PRIu32 " > %d, aborting", width,
840 UINT16_MAX);
841 return FALSE;
842 }
843 if (height > UINT16_MAX)
844 {
845 WLog_Print(h264->log, WLOG_ERROR, "h264::height %" PRIu32 " > %d, aborting", height,
846 UINT16_MAX);
847 return FALSE;
848 }
849
850 if (h264->subsystem && h264->subsystem->Uninit)
851 h264->subsystem->Uninit(h264);
852
853 h264_buffers_free(h264);
854 h264->width = width;
855 h264->height = height;
856
857 if (!h264_context_init(h264))
858 return FALSE;
859
860 return yuv_context_reset(h264->yuv, width, height);
861}
862
863H264_CONTEXT* h264_context_new(BOOL Compressor)
864{
865 H264_CONTEXT* h264 = (H264_CONTEXT*)calloc(1, sizeof(H264_CONTEXT));
866 if (!h264)
867 return nullptr;
868
869 h264->log = WLog_Get(TAG);
870
871 if (!h264->log)
872 goto fail;
873
874 h264->Compressor = Compressor;
875 if (Compressor)
876 {
877 /* Default compressor settings, may be changed by caller */
878 h264->BitRate = 1000000;
879 h264->FrameRate = 30;
880 }
881
882 if (!h264_context_init(h264))
883 goto fail;
884
885 h264->yuv = yuv_context_new(Compressor, 0);
886 if (!h264->yuv)
887 goto fail;
888
889 return h264;
890
891fail:
892 WINPR_PRAGMA_DIAG_PUSH
893 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
894 h264_context_free(h264);
895 WINPR_PRAGMA_DIAG_POP
896 return nullptr;
897}
898
899void h264_context_free(H264_CONTEXT* h264)
900{
901 if (!h264)
902 return;
903
904 if (h264->subsystem)
905 {
906 WINPR_ASSERT(h264->subsystem->Uninit);
907 h264->subsystem->Uninit(h264);
908 }
909
910 h264_buffers_free(h264);
911
912 yuv_context_free(h264->yuv);
913 free(h264);
914}
915
916void free_h264_metablock(RDPGFX_H264_METABLOCK* meta)
917{
918 RDPGFX_H264_METABLOCK m = WINPR_C_ARRAY_INIT;
919 if (!meta)
920 return;
921 free(meta->quantQualityVals);
922 free(meta->regionRects);
923 *meta = m;
924}
925
926BOOL h264_context_set_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option, UINT32 value)
927{
928 WINPR_ASSERT(h264);
929 switch (option)
930 {
931 case H264_CONTEXT_OPTION_BITRATE:
932 h264->BitRate = value;
933 return TRUE;
934 case H264_CONTEXT_OPTION_FRAMERATE:
935 h264->FrameRate = value;
936 return TRUE;
937 case H264_CONTEXT_OPTION_RATECONTROL:
938 {
939 switch (value)
940 {
941 case H264_RATECONTROL_VBR:
942 h264->RateControlMode = H264_RATECONTROL_VBR;
943 return TRUE;
944 case H264_RATECONTROL_CQP:
945 h264->RateControlMode = H264_RATECONTROL_CQP;
946 return TRUE;
947 default:
948 WLog_Print(h264->log, WLOG_WARN,
949 "Unknown H264_CONTEXT_OPTION_RATECONTROL value [0x%08" PRIx32 "]",
950 value);
951 return FALSE;
952 }
953 return TRUE;
954 }
955 case H264_CONTEXT_OPTION_QP:
956 h264->QP = value;
957 return TRUE;
958 case H264_CONTEXT_OPTION_USAGETYPE:
959 h264->UsageType = value;
960 return TRUE;
961 case H264_CONTEXT_OPTION_HW_ACCEL:
962#ifdef WITH_APPLE_VIDEOTOOLBOX
963 /* VideoToolbox follows Apple's decoder-selection policy;
964 * ignore this option without reinitializing the decoder. */
965 if (h264->subsystem == &g_Subsystem_VideoToolbox)
966 return TRUE;
967#endif
968 if (h264->hwAccel == value)
969 return TRUE;
970 h264->hwAccel = (value);
971 IFCALL(h264->subsystem->Uninit, h264);
972 return IFCALLRESULT(TRUE, h264->subsystem->Init, h264);
973 default:
974 WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]",
975 option);
976 return FALSE;
977 }
978}
979
980UINT32 h264_context_get_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option)
981{
982 WINPR_ASSERT(h264);
983 switch (option)
984 {
985 case H264_CONTEXT_OPTION_BITRATE:
986 return h264->BitRate;
987 case H264_CONTEXT_OPTION_FRAMERATE:
988 return h264->FrameRate;
989 case H264_CONTEXT_OPTION_RATECONTROL:
990 return h264->RateControlMode;
991 case H264_CONTEXT_OPTION_QP:
992 return h264->QP;
993 case H264_CONTEXT_OPTION_USAGETYPE:
994 return h264->UsageType;
995 case H264_CONTEXT_OPTION_HW_ACCEL:
996 return h264->hwAccel;
997 default:
998 WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]",
999 option);
1000 return 0;
1001 }
1002}