FreeRDP
Loading...
Searching...
No Matches
av1.c
1
21#include <winpr/sysinfo.h>
22
23#include <freerdp/codec/av1.h>
24#include <freerdp/primitives.h>
25#include <freerdp/log.h>
26#include <freerdp/codec/region.h>
27
28#define TAG FREERDP_TAG("codec.av1")
29
30#if defined(WITH_LIBAOM)
31#include <aom/aom.h>
32
33#include <aom/aom_decoder.h>
34#include <aom/aom_encoder.h>
35
36#include <aom/aom_image.h>
37#include <aom/aomcx.h>
38#include <aom/aomdx.h>
39#endif
40
41#if defined(WITH_DAV1D)
42#include <dav1d/dav1d.h>
43#endif
44
45#if defined(WITH_AV1_WEBCODECS)
46#include "av1_webcodecs.h"
47#endif
48
49#if defined(WITH_LIBAOM) || defined(WITH_DAV1D)
50
51#if defined(WITH_LIBYUV)
52#include <libyuv.h>
53#endif
54
55struct S_FREERDP_AV1_CONTEXT
56{
57 wLog* log;
58 bool encoder;
59 bool initialized;
60#if defined(WITH_LIBAOM)
61 aom_codec_ctx_t ctx;
62 aom_codec_enc_cfg_t ecfg;
63 aom_codec_pts_t framecount;
64 aom_enc_frame_flags_t eflags;
65 aom_codec_flags_t flags;
66 aom_codec_iface_t* iface;
67 /* the next frame converts the whole surface to YUV (after a reset or a failure) */
68 bool fullFrameNext;
69 /* encoder statistics, see av1_encoder_stats() */
70 struct
71 {
72 UINT64 startNS;
73 UINT64 encodeNS;
74 UINT64 maxEncodeNS;
75 UINT64 bytes;
76 UINT32 frames;
77 } stats;
78#endif
79#if defined(WITH_LIBAOM) && !defined(WITH_DAV1D)
80 aom_codec_dec_cfg_t dcfg;
81#endif
82#if defined(WITH_DAV1D)
83 Dav1dContext* dav1d;
84#endif
85#if defined(WITH_AV1_WEBCODECS)
86 int webcodecs; /* VideoDecoder handle, 0 if unused */
87#endif
88 BYTE* yuvdata[3];
89 UINT32 yuvWidth;
90 UINT32 yuvStride[3];
91 UINT32 yuvHeight;
92};
93
94#if defined(WITH_LIBAOM)
95static void av1_aom_destroy(FREERDP_AV1_CONTEXT* av1)
96{
97 const aom_codec_err_t rc = aom_codec_destroy(&av1->ctx);
98 if (rc != AOM_CODEC_OK)
99 WLog_Print(av1->log, WLOG_WARN, "aom_codec_destroy: %s", aom_codec_err_to_string(rc));
100}
101
102/* Speed preset for AOM_USAGE_REALTIME, 0 (slowest) to 12 (fastest). libaom defaults to 0 and
103 * raises it to 7 only for frames of 1080 lines or more, so smaller desktops got the slowest
104 * search. */
105#define AV1_AOM_RT_SPEED 10
106
107/* libaom encodes on one thread by default; more than 4 did not make 1080p faster. */
108#define AV1_AOM_MAX_THREADS 4
109
110static unsigned int av1_aom_threads(void)
111{
112 SYSTEM_INFO sysInfo = WINPR_C_ARRAY_INIT;
113 GetNativeSystemInfo(&sysInfo);
114 if (sysInfo.dwNumberOfProcessors < 1)
115 return 1;
116 if (sysInfo.dwNumberOfProcessors > AV1_AOM_MAX_THREADS)
117 return AV1_AOM_MAX_THREADS;
118 return sysInfo.dwNumberOfProcessors;
119}
120
121/* aom_codec_enc_init() starts from the defaults again, so apply these after every init. */
122WINPR_ATTR_NODISCARD
123static BOOL av1_aom_apply_controls(FREERDP_AV1_CONTEXT* av1)
124{
125 const struct
126 {
127 int id;
128 int value;
129 const char* name;
130 } controls[] = {
131 { AOME_SET_CPUUSED, AV1_AOM_RT_SPEED, "AOME_SET_CPUUSED" },
132 /* screen content tools (palette, intra block copy): sharp text at a lower bit rate */
133 { AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN, "AV1E_SET_TUNE_CONTENT" },
134 { AV1E_SET_ROW_MT, 1, "AV1E_SET_ROW_MT" },
135 };
136
137 for (size_t x = 0; x < ARRAYSIZE(controls); x++)
138 {
139 const aom_codec_err_t rc = aom_codec_control(&av1->ctx, controls[x].id, controls[x].value);
140 if (rc != AOM_CODEC_OK)
141 {
142 WLog_Print(av1->log, WLOG_WARN, "aom_codec_control(%s, %d): %s", controls[x].name,
143 controls[x].value, aom_codec_err_to_string(rc));
144 return FALSE;
145 }
146 }
147 return TRUE;
148}
149
150/* Clip the damaged rectangle to the frame and round it out to even coordinates, so the
151 * I420 chroma samples it touches are whole. FALSE: encode the whole frame. */
152WINPR_ATTR_NODISCARD
153static BOOL av1_aom_damage(const FREERDP_AV1_CONTEXT* av1, const RECTANGLE_16* regionRect,
154 UINT32 width, UINT32 height, RECTANGLE_16* damage)
155{
156 if (av1->fullFrameNext || !regionRect)
157 return FALSE;
158
159 const UINT32 left = regionRect->left & ~1u;
160 const UINT32 top = regionRect->top & ~1u;
161 const UINT32 right = MIN(width, (regionRect->right + 1u) & ~1u);
162 const UINT32 bottom = MIN(height, (regionRect->bottom + 1u) & ~1u);
163 if ((left >= right) || (top >= bottom))
164 return FALSE;
165 if ((left == 0) && (top == 0) && (right == width) && (bottom == height))
166 return FALSE;
167
168 damage->left = (UINT16)left;
169 damage->top = (UINT16)top;
170 damage->right = (UINT16)right;
171 damage->bottom = (UINT16)bottom;
172 return TRUE;
173}
174
175WINPR_ATTR_NODISCARD
176static BOOL allocate_h264_metablock(UINT32 QP, RECTANGLE_16* rectangles,
177 RDPGFX_H264_METABLOCK* meta, size_t count)
178{
179 /* [MS-RDPEGFX] 2.2.4.4.2 RDPGFX_AVC420_QUANT_QUALITY */
180 if (!meta || (QP > UINT8_MAX))
181 {
182 free(rectangles);
183 return FALSE;
184 }
185
186 meta->regionRects = rectangles;
187 if (count == 0)
188 return TRUE;
189
190 if (count > UINT32_MAX)
191 return FALSE;
192
193 meta->quantQualityVals = calloc(count, sizeof(RDPGFX_H264_QUANT_QUALITY));
194
195 if (!meta->quantQualityVals || !meta->regionRects)
196 return FALSE;
197 meta->numRegionRects = (UINT32)count;
198 for (size_t x = 0; x < count; x++)
199 {
200 RDPGFX_H264_QUANT_QUALITY* cur = &meta->quantQualityVals[x];
201 cur->qp = (UINT8)QP;
202
203 /* qpVal bit 6 and 7 are flags, so mask them out here.
204 * qualityVal is [0-100] so 100 - qpVal [0-64] is always in range */
205 cur->qualityVal = 100 - (QP & 0x3F);
206 }
207 return TRUE;
208}
209
210#endif
211
212BOOL freerdp_av1_context_set_option(FREERDP_AV1_CONTEXT* av1, FREERDP_AV1_CONTEXT_OPTION option,
213 UINT32 value)
214{
215#if defined(WITH_LIBAOM)
216 switch (option)
217 {
218 case FREERDP_AV1_CONTEXT_OPTION_PROFILE:
219 if (!av1->encoder)
220 return FALSE;
221
222 av1->ecfg.g_profile = value;
223 break;
224
225 case FREERDP_AV1_CONTEXT_OPTION_RATECONTROL:
226 if (!av1->encoder)
227 return FALSE;
228 switch (value)
229 {
230 case FREERDP_AV1_VBR:
231 case FREERDP_AV1_CBR:
232 case FREERDP_AV1_CQ:
233 case FREERDP_AV1_Q:
234 break;
235 default:
236 WLog_Print(av1->log, WLOG_WARN,
237 "Unknown FREERDP_AV1_CONTEXT_OPTION_RATECONTROL value [0x%08" PRIx32
238 "]",
239 value);
240 return FALSE;
241 }
242 av1->ecfg.rc_end_usage = WINPR_ASSERTING_INT_CAST(enum aom_rc_mode, value);
243 break;
244 case FREERDP_AV1_CONTEXT_OPTION_BITRATE:
245 if (!av1->encoder)
246 return FALSE;
247 av1->ecfg.rc_target_bitrate = value;
248 break;
249 case FREERDP_AV1_CONTEXT_OPTION_USAGETYPE:
250 if (!av1->encoder)
251 return FALSE;
252 av1->ecfg.g_usage = value;
253 break;
254 default:
255 WLog_Print(av1->log, WLOG_ERROR, "Unknown FREERDP_AV1_CONTEXT_OPTION[0x%08" PRIx32 "]",
256 option);
257 return TRUE;
258 }
259 return freerdp_av1_context_reset(av1, av1->ecfg.g_w, av1->ecfg.g_h);
260#else
261 WLog_Print(av1->log, WLOG_WARN, "AV1 encoder requires libaom, recompile with '-DWITH_AOM=ON'");
262 WINPR_UNUSED(option);
263 WINPR_UNUSED(value);
264 return FALSE;
265#endif
266}
267
268UINT32 freerdp_av1_context_get_option(FREERDP_AV1_CONTEXT* av1, FREERDP_AV1_CONTEXT_OPTION option)
269{
270#if defined(WITH_LIBAOM)
271 switch (option)
272 {
273 case FREERDP_AV1_CONTEXT_OPTION_PROFILE:
274 if (!av1->encoder)
275 return 0;
276 return av1->ecfg.g_profile;
277 case FREERDP_AV1_CONTEXT_OPTION_RATECONTROL:
278 if (!av1->encoder)
279 return 0;
280 return av1->ecfg.rc_end_usage;
281 case FREERDP_AV1_CONTEXT_OPTION_BITRATE:
282 if (!av1->encoder)
283 return 0;
284 return av1->ecfg.rc_target_bitrate;
285 case FREERDP_AV1_CONTEXT_OPTION_USAGETYPE:
286 if (!av1->encoder)
287 return 0;
288 return av1->ecfg.g_usage;
289 default:
290 WLog_Print(av1->log, WLOG_ERROR, "Unknown FREERDP_AV1_CONTEXT_OPTION[0x%08" PRIx32 "]",
291 option);
292 return 0;
293 }
294#else
295 WINPR_UNUSED(option);
296 return 0;
297#endif
298}
299
300#if defined(WITH_LIBAOM)
301/* Encoder statistics, logged every few seconds at debug level to com.freerdp.codec.av1
302 * (e.g. with WLOG_LEVEL=DEBUG) */
303#define AV1_STATS_INTERVAL_NS (5ull * 1000ull * 1000ull * 1000ull)
304
305static void av1_encoder_stats(FREERDP_AV1_CONTEXT* av1, UINT64 startNS, size_t bytes)
306{
307 WINPR_ASSERT(av1);
308
309 if (!WLog_IsLevelActive(av1->log, WLOG_DEBUG))
310 return;
311
312 const UINT64 endNS = winpr_GetTickCount64NS();
313 const UINT64 encodeNS = endNS - startNS;
314 if (av1->stats.frames == 0)
315 av1->stats.startNS = startNS;
316 av1->stats.frames++;
317 av1->stats.encodeNS += encodeNS;
318 av1->stats.bytes += bytes;
319 if (encodeNS > av1->stats.maxEncodeNS)
320 av1->stats.maxEncodeNS = encodeNS;
321
322 const UINT64 elapsedNS = endNS - av1->stats.startNS;
323 if (elapsedNS < AV1_STATS_INTERVAL_NS)
324 return;
325
326 const double seconds = (double)elapsedNS / 1e9;
327 WLog_Print(av1->log, WLOG_DEBUG,
328 "AV1 %" PRIu32 "x%" PRIu32 " %s: %" PRIu32 " frames in %.1f s (%.1f fps), "
329 "encode avg %.1f ms max %.1f ms, %.0f kbit/s",
330 av1->yuvWidth, av1->yuvHeight, (av1->ecfg.g_profile == 1) ? "I444" : "I420",
331 av1->stats.frames, seconds, av1->stats.frames / seconds,
332 (double)av1->stats.encodeNS / av1->stats.frames / 1e6,
333 (double)av1->stats.maxEncodeNS / 1e6,
334 (double)av1->stats.bytes * 8.0 / 1000.0 / seconds);
335
336 av1->stats.frames = 0;
337 av1->stats.encodeNS = 0;
338 av1->stats.maxEncodeNS = 0;
339 av1->stats.bytes = 0;
340}
341#endif
342
343INT32 freerdp_av1_compress(FREERDP_AV1_CONTEXT* av1, const BYTE* pSrcData, DWORD SrcFormat,
344 UINT32 nSrcStep, UINT32 nSrcWidth, UINT32 nSrcHeight,
345 const RECTANGLE_16* regionRect, BYTE** ppDstData, UINT32* pDstSize,
347{
348 *ppDstData = nullptr;
349 *pDstSize = 0;
350
351 if (!av1->encoder)
352 {
353 WLog_Print(av1->log, WLOG_ERROR, "av1->encoder: %d", av1->encoder);
354 return -1;
355 }
356
357 if ((nSrcWidth == 0) || (nSrcWidth > UINT16_MAX) || (nSrcHeight == 0) ||
358 (nSrcHeight > UINT16_MAX))
359 {
360 WLog_Print(av1->log, WLOG_ERROR, "%" PRIu32 "x%" PRIu32 " out of range, maximum %dx%d",
361 nSrcWidth, nSrcHeight, UINT16_MAX, UINT16_MAX);
362 return -1;
363 }
364
365#if defined(WITH_LIBAOM)
366 const UINT64 startNS = winpr_GetTickCount64NS();
367 primitives_t* primitives = primitives_get();
368 if (!primitives)
369 {
370 WLog_Print(av1->log, WLOG_ERROR, "primitives_get(): nullptr");
371 return -1;
372 }
373
374 if (av1->yuvWidth != nSrcWidth)
375 {
376 WLog_Print(av1->log, WLOG_ERROR, "av1->yuvWidth[%" PRIu32 "] != nSrcWidth[%" PRIu32 "]",
377 av1->yuvWidth, nSrcWidth);
378 return -1;
379 }
380
381 if (av1->yuvHeight != nSrcHeight)
382 {
383 WLog_Print(av1->log, WLOG_ERROR, "av1->yuvHeight[%" PRIu32 "] != nSrcHeight[%" PRIu32 "]",
384 av1->yuvHeight, nSrcHeight);
385 return -1;
386 }
387
388 /* Only the damaged part is converted: the YUV planes still hold the previous frame. */
389 RECTANGLE_16 damage = WINPR_C_ARRAY_INIT;
390 const BOOL partial = av1_aom_damage(av1, regionRect, nSrcWidth, nSrcHeight, &damage);
391 if (!partial)
392 {
393 damage.left = 0;
394 damage.top = 0;
395 damage.right = WINPR_ASSERTING_INT_CAST(UINT16, nSrcWidth);
396 damage.bottom = WINPR_ASSERTING_INT_CAST(UINT16, nSrcHeight);
397 }
398
399 const UINT32 bpp = FreeRDPGetBytesPerPixel(SrcFormat);
400 if (bpp == 0)
401 {
402 WLog_Print(av1->log, WLOG_ERROR, "Unsupported source format %s",
403 FreeRDPGetColorFormatName(SrcFormat));
404 return -1;
405 }
406
407 const prim_size_t roi = { .width = damage.right - damage.left,
408 .height = damage.bottom - damage.top };
409 const UINT32 chromaShift = (av1->ecfg.g_profile == 0) ? 1 : 0;
410 const size_t chromaX = damage.left >> chromaShift;
411 const size_t chromaY = damage.top >> chromaShift;
412 const BYTE* pSrc = &pSrcData[1ull * damage.top * nSrcStep + 1ull * damage.left * bpp];
413 BYTE* pDst[3] = { &av1->yuvdata[0][1ull * damage.top * av1->yuvStride[0] + damage.left],
414 &av1->yuvdata[1][chromaY * av1->yuvStride[1] + chromaX],
415 &av1->yuvdata[2][chromaY * av1->yuvStride[2] + chromaX] };
416
417 aom_image_t buffer = WINPR_C_ARRAY_INIT;
418
419 aom_image_t* img = &buffer;
420 pstatus_t rec = -1;
421#if defined(WITH_LIBYUV)
422 /* ARGBToI420()/ARGBToI444() always read pSrcData as packed BGRA8888 regardless of
423 * SrcFormat, so the fast path only applies for that layout; anything else must go
424 * through the format-aware primitives conversion below. */
425 if ((SrcFormat == PIXEL_FORMAT_BGRA32) || (SrcFormat == PIXEL_FORMAT_BGRX32))
426 {
427 switch (av1->ecfg.g_profile)
428 {
429 case 0:
430 img->fmt = AOM_IMG_FMT_I420;
431 img->x_chroma_shift = 1;
432 img->y_chroma_shift = 1;
433 rec = ARGBToI420(pSrc, nSrcStep, pDst[0], av1->yuvStride[0], pDst[1],
434 av1->yuvStride[1], pDst[2],
435 WINPR_ASSERTING_INT_CAST(int, av1->yuvStride[2]),
436 WINPR_ASSERTING_INT_CAST(int, roi.width),
437 WINPR_ASSERTING_INT_CAST(int, roi.height));
438 break;
439 case 1:
440 img->fmt = AOM_IMG_FMT_I444;
441 rec = ARGBToI444(pSrc, nSrcStep, pDst[0], av1->yuvStride[0], pDst[1],
442 av1->yuvStride[1], pDst[2],
443 WINPR_ASSERTING_INT_CAST(int, av1->yuvStride[2]),
444 WINPR_ASSERTING_INT_CAST(int, roi.width),
445 WINPR_ASSERTING_INT_CAST(int, roi.height));
446 break;
447 default:
448 WLog_Print(av1->log, WLOG_ERROR, "Unsupoorted AV1 profile %" PRIu32,
449 av1->ecfg.g_profile);
450 return -1;
451 }
452 }
453 else
454#endif
455 {
456 switch (av1->ecfg.g_profile)
457 {
458 case 0:
459 img->fmt = AOM_IMG_FMT_I420;
460 img->x_chroma_shift = 1;
461 img->y_chroma_shift = 1;
462 rec = primitives->RGBToYUV420_8u_P3AC4R(pSrc, SrcFormat, nSrcStep, pDst,
463 av1->yuvStride, &roi);
464 break;
465 case 1:
466 img->fmt = AOM_IMG_FMT_I444;
467 rec =
468 primitives->RGBToI444_8u(pSrc, SrcFormat, nSrcStep, pDst, av1->yuvStride, &roi);
469 break;
470 default:
471 WLog_Print(av1->log, WLOG_ERROR, "Unsupoorted AV1 profile %" PRIu32,
472 av1->ecfg.g_profile);
473 return -1;
474 }
475 }
476 if (rec != 0)
477 {
478 WLog_Print(av1->log, WLOG_ERROR, "RGB to YUV conversion failed: %d", rec);
479 av1->fullFrameNext = true;
480 return -1;
481 }
482 av1->fullFrameNext = false;
483 img->bit_depth = 8;
484 img->d_w = img->r_w = img->w = nSrcWidth;
485 img->d_h = img->r_h = img->h = nSrcHeight;
486 img->stride[0] = WINPR_ASSERTING_INT_CAST(int, av1->yuvStride[0]);
487 img->stride[1] = WINPR_ASSERTING_INT_CAST(int, av1->yuvStride[1]);
488 img->stride[2] = WINPR_ASSERTING_INT_CAST(int, av1->yuvStride[2]);
489 img->planes[0] = av1->yuvdata[0];
490 img->planes[1] = av1->yuvdata[1];
491 img->planes[2] = av1->yuvdata[2];
492
493 const aom_codec_err_t rc = aom_codec_encode(&av1->ctx, img, ++av1->framecount, 1, av1->eflags);
494 aom_img_free(img);
495
496 if (rc != AOM_CODEC_OK)
497 {
498 WLog_Print(av1->log, WLOG_WARN, "aom_codec_encode: %s", aom_codec_err_to_string(rc));
499 return -1;
500 }
501
502 aom_codec_iter_t iter = nullptr;
503 const aom_codec_cx_pkt_t* pkt = nullptr;
504 while ((pkt = aom_codec_get_cx_data(&av1->ctx, &iter)) != nullptr)
505 {
506 if (pkt->kind != AOM_CODEC_CX_FRAME_PKT)
507 continue;
508
509 if (pkt->data.frame.sz > UINT32_MAX)
510 return -1;
511 *ppDstData = pkt->data.frame.buf;
512 *pDstSize = WINPR_ASSERTING_INT_CAST(UINT32, pkt->data.frame.sz);
513 }
514 if (*pDstSize == 0)
515 return 0;
516
517 av1_encoder_stats(av1, startNS, *pDstSize);
518
519 RECTANGLE_16* rect = calloc(1, sizeof(RECTANGLE_16));
520 if (rect)
521 {
522 rect->right = WINPR_ASSERTING_INT_CAST(UINT16, nSrcWidth);
523 rect->bottom = WINPR_ASSERTING_INT_CAST(UINT16, nSrcHeight);
524 }
525 return allocate_h264_metablock(10, rect, meta, 1);
526#else
527 WLog_Print(av1->log, WLOG_ERROR, "AV1 encoder requires libaom, recompile with '-DWITH_AOM=ON'");
528 WINPR_UNUSED(pSrcData);
529 WINPR_UNUSED(SrcFormat);
530 WINPR_UNUSED(nSrcStep);
531 WINPR_UNUSED(nSrcWidth);
532 WINPR_UNUSED(nSrcHeight);
533 WINPR_UNUSED(regionRect);
534 WINPR_UNUSED(meta);
535 return -1;
536#endif
537}
538
539static BOOL isRectValid(UINT32 width, UINT32 height, const RECTANGLE_16* rect)
540{
541 WINPR_ASSERT(rect);
542 if (rect->left > width)
543 return FALSE;
544 if (rect->right > width)
545 return FALSE;
546 if (rect->left >= rect->right)
547 return FALSE;
548 if (rect->top > height)
549 return FALSE;
550 if (rect->bottom > height)
551 return FALSE;
552 if (rect->top >= rect->bottom)
553 return FALSE;
554 return TRUE;
555}
556
557static BOOL areRectsValid(wLog* log, UINT32 width, UINT32 height, const RECTANGLE_16* rects,
558 UINT32 count)
559{
560 WINPR_ASSERT(rects || (count == 0));
561 for (size_t x = 0; x < count; x++)
562 {
563 const RECTANGLE_16* rect = &rects[x];
564 if (!isRectValid(width, height, rect))
565 {
566 char buffer[64] = WINPR_C_ARRAY_INIT;
567 WLog_Print(log, WLOG_WARN,
568 "Rectangle %" PRIuz " %s outside of bounding frame %" PRIu32 "x%" PRIu32, x,
569 rectangle_to_string(rect, buffer, sizeof(buffer)), width, height);
570 return FALSE;
571 }
572 }
573 return TRUE;
574}
575
576#if defined(WITH_DAV1D)
577static void av1_dav1d_data_free(WINPR_ATTR_UNUSED const uint8_t* buf,
578 WINPR_ATTR_UNUSED void* cookie)
579{
580 /* pSrcData is owned by the caller of freerdp_av1_decompress(), dav1d must not free it. */
581}
582
583static INT32 av1_dav1d_convert(FREERDP_AV1_CONTEXT* av1, const Dav1dPicture* pic, BYTE* pDstData,
584 DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth,
585 UINT32 nDstHeight, const RECTANGLE_16* regionRects,
586 UINT32 numRegionRect)
587{
588 if (pic->p.bpc != 8)
589 {
590 WLog_Print(av1->log, WLOG_ERROR, "dav1d picture bpc %d not supported", pic->p.bpc);
591 return -1;
592 }
593
594 /* The decoded frame may be smaller than the destination surface, so
595 * bound the converted region to it. Otherwise the colour conversion
596 * reads past the decoded image planes. */
597 const UINT32 dw = (UINT32)pic->p.w;
598 const UINT32 dh = (UINT32)pic->p.h;
599 const UINT32 nWidth = (dw < nDstWidth) ? dw : nDstWidth;
600 const UINT32 nHeight = (dh < nDstHeight) ? dh : nDstHeight;
601 const prim_size_t roi = { .width = nWidth, .height = nHeight };
602
603 if (!areRectsValid(av1->log, roi.width, roi.height, regionRects, numRegionRect))
604 return -2;
605
606 const UINT32 strides[] = { (UINT32)pic->stride[0], (UINT32)pic->stride[1],
607 (UINT32)pic->stride[1] };
608
609 pstatus_t rec = -1;
610#if defined(WITH_LIBYUV)
611 /* J420ToARGB()/J444ToARGB() always write pDstData as packed BGRA8888 regardless of
612 * DstFormat, so the fast path only applies for that layout; anything else must go
613 * through the format-aware primitives conversion below. */
614 if ((DstFormat == PIXEL_FORMAT_BGRA32) || (DstFormat == PIXEL_FORMAT_BGRX32))
615 {
616 const uint8_t* y = pic->data[0];
617 const uint8_t* u = pic->data[1];
618 const uint8_t* v = pic->data[2];
619
620 const int stride0 = WINPR_ASSERTING_INT_CAST(int, strides[0]);
621 const int stride1 = WINPR_ASSERTING_INT_CAST(int, strides[1]);
622 const int stride2 = WINPR_ASSERTING_INT_CAST(int, strides[2]);
623 const int iWidth = WINPR_ASSERTING_INT_CAST(int, nWidth);
624 const int iHeight = WINPR_ASSERTING_INT_CAST(int, nHeight);
625 const int iDstStep = WINPR_ASSERTING_INT_CAST(int, nDstStep);
626
627 switch (pic->p.layout)
628 {
629 case DAV1D_PIXEL_LAYOUT_I420:
630 rec = J420ToARGB(y, stride0, u, stride1, v, stride2, pDstData, iDstStep, iWidth,
631 iHeight);
632 break;
633 case DAV1D_PIXEL_LAYOUT_I444:
634 rec = J444ToARGB(y, stride0, u, stride1, v, stride2, pDstData, iDstStep, iWidth,
635 iHeight);
636 break;
637 default:
638 WLog_Print(av1->log, WLOG_ERROR, "dav1d picture layout %d not supported",
639 pic->p.layout);
640 return -1;
641 }
642 }
643 else
644#endif
645 {
646 primitives_t* primitives = primitives_get();
647 if (!primitives)
648 {
649 WLog_Print(av1->log, WLOG_ERROR, "primitives_get(): nullptr");
650 return -1;
651 }
652
653 const BYTE* pSrc[] = { pic->data[0], pic->data[1], pic->data[2] };
654 switch (pic->p.layout)
655 {
656 case DAV1D_PIXEL_LAYOUT_I420:
657 rec = primitives->YUV420ToRGB_8u_P3AC4R(pSrc, strides, pDstData, nDstStep,
658 DstFormat, &roi);
659 break;
660 case DAV1D_PIXEL_LAYOUT_I444:
661 rec = primitives->I444ToRGB_8u(pSrc, strides, pDstData, nDstStep, DstFormat, &roi);
662 break;
663 default:
664 WLog_Print(av1->log, WLOG_ERROR, "dav1d picture layout %u not supported",
665 pic->p.layout);
666 return -1;
667 }
668 }
669 if (rec != 0)
670 {
671 WLog_Print(av1->log, WLOG_ERROR, "RGB to YUV conversion failed: %d", rec);
672 return -1;
673 }
674 return TRUE;
675}
676
677/* Return: TRUE if a picture was converted, FALSE if none was ready (EAGAIN), <0 on error. */
678static INT32 av1_dav1d_drain_one(FREERDP_AV1_CONTEXT* av1, BYTE* pDstData, DWORD DstFormat,
679 UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight,
680 const RECTANGLE_16* regionRects, UINT32 numRegionRect)
681{
682 Dav1dPicture pic = WINPR_C_ARRAY_INIT;
683 const int prc = dav1d_get_picture(av1->dav1d, &pic);
684 if (prc == DAV1D_ERR(EAGAIN))
685 return FALSE;
686 if (prc < 0)
687 {
688 WLog_Print(av1->log, WLOG_WARN, "dav1d_get_picture: %d", prc);
689 return -1;
690 }
691
692 const INT32 rc = av1_dav1d_convert(av1, &pic, pDstData, DstFormat, nDstStep, nDstWidth,
693 nDstHeight, regionRects, numRegionRect);
694 dav1d_picture_unref(&pic);
695 return (rc == TRUE) ? TRUE : rc;
696}
697#endif
698
699INT32 freerdp_av1_decompress(FREERDP_AV1_CONTEXT* av1, const BYTE* pSrcData, UINT32 SrcSize,
700 BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth,
701 UINT32 nDstHeight, const RECTANGLE_16* regionRects,
702 UINT32 numRegionRect)
703{
704 WINPR_ASSERT(av1);
705 if (av1->encoder)
706 {
707 WLog_Print(av1->log, WLOG_ERROR, "av1->encoder: %d", av1->encoder);
708 return -1;
709 }
710
711 if (!areRectsValid(av1->log, nDstWidth, nDstHeight, regionRects, numRegionRect))
712 return -2;
713
714#if defined(WITH_AV1_WEBCODECS)
715 if (av1->webcodecs > 0)
716 {
717 const INT32 wrc = av1_wc_decode(av1->webcodecs, pSrcData, SrcSize, pDstData, DstFormat,
718 nDstStep, nDstWidth, nDstHeight);
719 if (wrc >= 0)
720 return TRUE;
721 /* fall back to the software decoder for the rest of the session */
722 if (wrc == AV1_WC_UNSUPPORTED)
723 WLog_Print(av1->log, WLOG_DEBUG, "surface format needs dav1d instead of WebCodecs");
724 else
725 WLog_Print(av1->log, WLOG_WARN,
726 "WebCodecs AV1 decode failed (%" PRId32 "), using dav1d", wrc);
727 av1_wc_close(av1->webcodecs);
728 av1->webcodecs = 0;
729 }
730#endif
731
732#if defined(WITH_DAV1D)
733 Dav1dData data = WINPR_C_ARRAY_INIT;
734 const int wrc = dav1d_data_wrap(&data, pSrcData, SrcSize, av1_dav1d_data_free, nullptr);
735 if (wrc < 0)
736 {
737 WLog_Print(av1->log, WLOG_WARN, "dav1d_data_wrap: %d", wrc);
738 return -1;
739 }
740
741 while (data.sz > 0)
742 {
743 const int src = dav1d_send_data(av1->dav1d, &data);
744 if ((src < 0) && (src != DAV1D_ERR(EAGAIN)))
745 {
746 WLog_Print(av1->log, WLOG_WARN, "dav1d_send_data: %d", src);
747 dav1d_data_unref(&data);
748 return -1;
749 }
750
751 const INT32 rc = av1_dav1d_drain_one(av1, pDstData, DstFormat, nDstStep, nDstWidth,
752 nDstHeight, regionRects, numRegionRect);
753 if ((rc != TRUE) && (rc != FALSE))
754 {
755 dav1d_data_unref(&data);
756 return rc;
757 }
758 }
759
760 /* Drain any picture the decoder buffered internally before it consumed the last input. */
761 for (;;)
762 {
763 const INT32 rc = av1_dav1d_drain_one(av1, pDstData, DstFormat, nDstStep, nDstWidth,
764 nDstHeight, regionRects, numRegionRect);
765 if (rc == FALSE)
766 break;
767 if (rc != TRUE)
768 return rc;
769 }
770
771 return TRUE;
772#elif defined(WITH_LIBAOM)
773 const aom_codec_err_t rc = aom_codec_decode(&av1->ctx, pSrcData, SrcSize, nullptr);
774 if (rc != AOM_CODEC_OK)
775 {
776 WLog_Print(av1->log, WLOG_WARN, "aom_codec_decode: %s", aom_codec_err_to_string(rc));
777 return -1;
778 }
779
780 aom_image_t* img = nullptr;
781 aom_codec_iter_t iter = nullptr;
782 while ((img = aom_codec_get_frame(&av1->ctx, &iter)) != nullptr)
783 {
784 /* The decoded frame may be smaller than the destination surface, so
785 * bound the converted region to it. Otherwise the colour conversion
786 * reads past the decoded image planes. */
787 const UINT32 nWidth = (img->d_w < nDstWidth) ? img->d_w : nDstWidth;
788 const UINT32 nHeight = (img->d_h < nDstHeight) ? img->d_h : nDstHeight;
789 const prim_size_t roi = { .width = nWidth, .height = nHeight };
790
791 if (!areRectsValid(av1->log, roi.width, roi.height, regionRects, numRegionRect))
792 return -2;
793
794 pstatus_t rec = -1;
795#if defined(WITH_LIBYUV)
796 /* J420ToARGB()/J444ToARGB() always write pDstData as packed BGRA8888 regardless of
797 * DstFormat, so the fast path only applies for that layout; anything else must go
798 * through the format-aware primitives conversion below. */
799 if ((DstFormat == PIXEL_FORMAT_BGRA32) || (DstFormat == PIXEL_FORMAT_BGRX32))
800 {
801 switch (img->fmt)
802 {
803 case AOM_IMG_FMT_I420:
804 rec = J420ToARGB(img->planes[0], img->stride[0], img->planes[1], img->stride[1],
805 img->planes[2], img->stride[2], pDstData, nDstStep, nWidth,
806 nHeight);
807 break;
808 case AOM_IMG_FMT_I444:
809 rec = J444ToARGB(img->planes[0], img->stride[0], img->planes[1], img->stride[1],
810 img->planes[2], img->stride[2], pDstData, nDstStep, nWidth,
811 nHeight);
812 break;
813 default:
814 WLog_Print(av1->log, WLOG_ERROR, "img->fmt %d not supported", img->fmt);
815 return -1;
816 }
817 }
818 else
819#endif
820 {
821 const BYTE* pSrc[] = { img->planes[0], img->planes[1], img->planes[2] };
822 const UINT32 strides[] = { img->stride[0], img->stride[1], img->stride[2] };
823
824 primitives_t* primitives = primitives_get();
825 if (!primitives)
826 {
827 WLog_Print(av1->log, WLOG_ERROR, "primitives_get(): nullptr");
828 return FALSE;
829 }
830
831 switch (img->fmt)
832 {
833 case AOM_IMG_FMT_I420:
834 rec = primitives->YUV420ToRGB_8u_P3AC4R(pSrc, strides, pDstData, nDstStep,
835 DstFormat, &roi);
836 break;
837 case AOM_IMG_FMT_I444:
838 rec = primitives->I444ToRGB_8u(pSrc, strides, pDstData, nDstStep, DstFormat,
839 &roi);
840 break;
841 default:
842 WLog_Print(av1->log, WLOG_ERROR, "img->fmt %d not supported", img->fmt);
843 return -1;
844 }
845 }
846 if (rec != 0)
847 {
848 WLog_Print(av1->log, WLOG_ERROR, "RGB to YUV conversion failed: %d", rec);
849 return -1;
850 }
851 }
852
853 return TRUE;
854#endif
855}
856
857BOOL freerdp_av1_context_reset(FREERDP_AV1_CONTEXT* av1, UINT32 width, UINT32 height)
858{
859 if (av1->encoder)
860 {
861#if defined(WITH_LIBAOM)
862 av1->ecfg.g_w = width;
863 av1->ecfg.g_h = height;
864
865 if (av1->initialized)
866 {
867 const aom_codec_err_t rc = aom_codec_destroy(&av1->ctx);
868 av1->initialized = false;
869 if (rc != AOM_CODEC_OK)
870 {
871 WLog_Print(av1->log, WLOG_WARN, "aom_codec_destroy: %s",
872 aom_codec_err_to_string(rc));
873 return FALSE;
874 }
875 }
876
877 const aom_codec_err_t rc =
878 aom_codec_enc_init(&av1->ctx, av1->iface, &av1->ecfg, av1->flags);
879 if (rc != AOM_CODEC_OK)
880 {
881 WLog_Print(av1->log, WLOG_WARN, "aom_codec_enc_init: %s", aom_codec_err_to_string(rc));
882 return FALSE;
883 }
884
885 av1->framecount = 0;
886 av1->eflags = 0; // AOM_EFLAG_FORCE_KF;
887 av1->initialized = true;
888 av1->fullFrameNext = true;
889
890 if (!av1_aom_apply_controls(av1))
891 return FALSE;
892#else
893 WLog_Print(av1->log, WLOG_ERROR,
894 "AV1 encoder requires libaom, recompile with '-DWITH_AOM=ON'");
895 return FALSE;
896#endif
897 }
898 else
899 {
900#if defined(WITH_AV1_WEBCODECS)
901 if (av1->webcodecs == 0)
902 {
903 av1->webcodecs = av1_wc_open();
904 WLog_Print(av1->log, WLOG_INFO, "AV1 decoder: %s",
905 av1->webcodecs > 0 ? "WebCodecs (browser), dav1d as fallback" : "dav1d");
906 }
907#endif
908#if defined(WITH_DAV1D)
909 if (av1->initialized)
910 {
911 dav1d_close(&av1->dav1d);
912 av1->initialized = false;
913 }
914
915 Dav1dSettings settings = WINPR_C_ARRAY_INIT;
916 dav1d_default_settings(&settings);
917 /* Single-threaded: freerdp_av1_decompress() needs its picture back synchronously,
918 * not held back by frame-parallel decoding. */
919 settings.n_threads = 1;
920 settings.max_frame_delay = 1;
921
922 const int rc = dav1d_open(&av1->dav1d, &settings);
923 if (rc < 0)
924 {
925 WLog_Print(av1->log, WLOG_WARN, "dav1d_open: %d", rc);
926 return FALSE;
927 }
928 av1->initialized = true;
929#elif defined(WITH_LIBAOM)
930 av1->dcfg.w = width;
931 av1->dcfg.h = height;
932 av1->dcfg.allow_lowbitdepth = 1;
933 const aom_codec_err_t rc =
934 aom_codec_dec_init(&av1->ctx, av1->iface, &av1->dcfg, av1->flags);
935 if (rc != AOM_CODEC_OK)
936 {
937 WLog_Print(av1->log, WLOG_WARN, "aom_codec_dec_init: %s", aom_codec_err_to_string(rc));
938 return FALSE;
939 }
940 av1->initialized = true;
941#else
942 WLog_Print(av1->log, WLOG_WARN,
943 "This build does not support AV1 decoding. Recompile with '-DWITH_DAV1D=ON' "
944 "or '-DWITH_AOM=ON'");
945 return FALSE;
946#endif
947 }
948
949 av1->yuvWidth = width;
950 av1->yuvStride[0] = width;
951
952 const size_t pad = av1->yuvStride[0] % 16;
953 if (pad != 0)
954 av1->yuvStride[0] += 16 - pad;
955 av1->yuvStride[0] *= 3;
956 av1->yuvStride[1] = width;
957 av1->yuvStride[2] = width;
958
959 av1->yuvHeight = height;
960 winpr_aligned_free(av1->yuvdata[0]);
961 winpr_aligned_free(av1->yuvdata[1]);
962 winpr_aligned_free(av1->yuvdata[2]);
963 av1->yuvdata[0] = winpr_aligned_malloc(1ull * av1->yuvStride[0] * av1->yuvHeight, 64);
964 av1->yuvdata[1] = winpr_aligned_malloc(1ull * av1->yuvStride[1] * av1->yuvHeight, 64);
965 av1->yuvdata[2] = winpr_aligned_malloc(1ull * av1->yuvStride[2] * av1->yuvHeight, 64);
966 return av1->yuvdata[0] != nullptr;
967}
968
969void freerdp_av1_context_free(FREERDP_AV1_CONTEXT* av1)
970{
971 if (!av1)
972 return;
973
974 if (av1->initialized)
975 {
976 if (av1->encoder)
977 {
978#if defined(WITH_LIBAOM)
979 av1_aom_destroy(av1);
980#endif
981 }
982 else
983 {
984#if defined(WITH_DAV1D)
985 dav1d_close(&av1->dav1d);
986#elif defined(WITH_LIBAOM)
987 av1_aom_destroy(av1);
988#endif
989 }
990 }
991#if defined(WITH_AV1_WEBCODECS)
992 av1_wc_close(av1->webcodecs);
993#endif
994 winpr_aligned_free(av1->yuvdata[0]);
995 winpr_aligned_free(av1->yuvdata[1]);
996 winpr_aligned_free(av1->yuvdata[2]);
997 free(av1);
998}
999
1000FREERDP_AV1_CONTEXT* freerdp_av1_context_new(BOOL Compressor)
1001{
1002 FREERDP_AV1_CONTEXT* ctx = calloc(1, sizeof(FREERDP_AV1_CONTEXT));
1003 if (!ctx)
1004 return nullptr;
1005 ctx->encoder = Compressor;
1006 ctx->log = WLog_Get(TAG);
1007 if (!ctx->log)
1008 goto fail;
1009
1010 if (Compressor)
1011 {
1012#if defined(WITH_LIBAOM)
1013 ctx->iface = aom_codec_av1_cx();
1014 if (!ctx->iface)
1015 {
1016 WLog_Print(ctx->log, WLOG_ERROR, "aom_codec_av1_cx() nullptr");
1017 goto fail;
1018 }
1019
1020 const aom_codec_err_t rc =
1021 aom_codec_enc_config_default(ctx->iface, &ctx->ecfg, AOM_USAGE_REALTIME);
1022 if (rc != AOM_CODEC_OK)
1023 {
1024 WLog_Print(ctx->log, WLOG_ERROR, "aom_codec_enc_config_default() %s",
1025 aom_codec_err_to_string(rc));
1026 goto fail;
1027 }
1028 ctx->ecfg.g_threads = av1_aom_threads();
1029#else
1030 WLog_Print(ctx->log, WLOG_ERROR,
1031 "AV1 encoder requires libaom, recompile with '-DWITH_AOM=ON'");
1032 goto fail;
1033#endif
1034 }
1035#if defined(WITH_LIBAOM) && !defined(WITH_DAV1D)
1036 else
1037 {
1038 ctx->iface = aom_codec_av1_dx();
1039 if (!ctx->iface)
1040 {
1041 WLog_Print(ctx->log, WLOG_ERROR, "aom_codec_av1_dx() nullptr");
1042 goto fail;
1043 }
1044 }
1045#endif
1046
1047 return ctx;
1048
1049fail:
1050 freerdp_av1_context_free(ctx);
1051 return nullptr;
1052}
1053#else
1054
1055struct S_FREERDP_AV1_CONTEXT
1056{
1057 BOOL Compressor;
1058 wLog* log;
1059};
1060
1061BOOL freerdp_av1_context_set_option(WINPR_ATTR_UNUSED FREERDP_AV1_CONTEXT* av1,
1062 WINPR_ATTR_UNUSED FREERDP_AV1_CONTEXT_OPTION option,
1063 WINPR_ATTR_UNUSED UINT32 value)
1064{
1065 return FALSE;
1066}
1067
1068UINT32 freerdp_av1_context_get_option(WINPR_ATTR_UNUSED FREERDP_AV1_CONTEXT* av1,
1069 WINPR_ATTR_UNUSED FREERDP_AV1_CONTEXT_OPTION option)
1070{
1071 return 0;
1072}
1073
1074INT32 freerdp_av1_compress(WINPR_ATTR_UNUSED FREERDP_AV1_CONTEXT* av1,
1075 WINPR_ATTR_UNUSED const BYTE* pSrcData,
1076 WINPR_ATTR_UNUSED DWORD SrcFormat, WINPR_ATTR_UNUSED UINT32 nSrcStep,
1077 WINPR_ATTR_UNUSED UINT32 nSrcWidth, WINPR_ATTR_UNUSED UINT32 nSrcHeight,
1078 WINPR_ATTR_UNUSED const RECTANGLE_16* regionRect,
1079 WINPR_ATTR_UNUSED BYTE** ppDstData, WINPR_ATTR_UNUSED UINT32* pDstSize,
1080 WINPR_ATTR_UNUSED RDPGFX_H264_METABLOCK* meta)
1081{
1082 WINPR_ASSERT(av1);
1083 WLog_Print(av1->log, WLOG_ERROR,
1084 "This build does not support AV1 codec. Recompile with '-DWITH_AOM=ON' or "
1085 "'-DWITH_DAV1D=ON'");
1086 return -1;
1087}
1088
1089INT32 freerdp_av1_decompress(WINPR_ATTR_UNUSED FREERDP_AV1_CONTEXT* av1,
1090 WINPR_ATTR_UNUSED const BYTE* pSrcData,
1091 WINPR_ATTR_UNUSED UINT32 SrcSize, WINPR_ATTR_UNUSED BYTE* pDstData,
1092 WINPR_ATTR_UNUSED DWORD DstFormat, WINPR_ATTR_UNUSED UINT32 nDstStep,
1093 WINPR_ATTR_UNUSED UINT32 nDstWidth,
1094 WINPR_ATTR_UNUSED UINT32 nDstHeight,
1095 WINPR_ATTR_UNUSED const RECTANGLE_16* regionRects,
1096 WINPR_ATTR_UNUSED UINT32 numRegionRect)
1097{
1098 WINPR_ASSERT(av1);
1099 WLog_Print(av1->log, WLOG_ERROR,
1100 "This build does not support AV1 codec. Recompile with '-DWITH_AOM=ON' or "
1101 "'-DWITH_DAV1D=ON'");
1102 return -1;
1103}
1104
1105BOOL freerdp_av1_context_reset(WINPR_ATTR_UNUSED FREERDP_AV1_CONTEXT* av1,
1106 WINPR_ATTR_UNUSED UINT32 width, WINPR_ATTR_UNUSED UINT32 height)
1107{
1108 WINPR_ASSERT(av1);
1109 WLog_Print(av1->log, WLOG_WARN,
1110 "This build does not support AV1 codec. Recompile with '-DWITH_AOM=ON' or "
1111 "'-DWITH_DAV1D=ON'");
1112 return FALSE;
1113}
1114
1115void freerdp_av1_context_free(WINPR_ATTR_UNUSED FREERDP_AV1_CONTEXT* av1)
1116{
1117 free(av1);
1118}
1119
1120FREERDP_AV1_CONTEXT* freerdp_av1_context_new(BOOL Compressor)
1121{
1122 FREERDP_AV1_CONTEXT* ctx = calloc(1, sizeof(FREERDP_AV1_CONTEXT));
1123 if (!ctx)
1124 return nullptr;
1125 ctx->Compressor = Compressor;
1126 ctx->log = WLog_Get(TAG);
1127 return ctx;
1128}
1129#endif
WINPR_ATTR_NODISCARD fn_RGBToYUV444_8u_P3AC4R_t RGBToI444_8u
Definition primitives.h:305
WINPR_ATTR_NODISCARD fn_YUV444ToRGB_8u_P3AC4R_t I444ToRGB_8u
Convert a plain YUV444 picture (e.g. decoded AV1) to RGB. Unlike YUV444ToRGB_8u_P3AC4R this does not ...
Definition primitives.h:310