Coverage Report

Created: 2026-10-15 06:31

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