VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/RecordingUtils.cpp@ 105006

Last change on this file since 105006 was 105006, checked in by vboxsync, 5 months ago

Video Recording: Big revamp to improve overall performance. We now don't rely on the periodic display refresh callback anymore to render the entire framebuffer but now rely on delta updates ("dirty rectangles"). Also, we now only encode new frames when an area has changed. This also needed cursor position + change change notifications, as we render the cursor on the host side if mouse integration is enabled (requires 7.1 Guest Additions as of now). Optimized the BGRA32->YUV IV420 color space conversion as well as the overall amount of pixel data shuffled forth and back. Added a new testcase for the cropping/centering code. bugref:10650

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.7 KB
Line 
1/* $Id: RecordingUtils.cpp 105006 2024-06-24 17:43:00Z vboxsync $ */
2/** @file
3 * Recording utility code.
4 */
5
6/*
7 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#include "Recording.h"
29#include "RecordingUtils.h"
30
31#include <iprt/asm.h>
32#include <iprt/assert.h>
33#include <iprt/critsect.h>
34#include <iprt/path.h>
35#include <iprt/semaphore.h>
36#include <iprt/thread.h>
37#include <iprt/time.h>
38
39#ifdef DEBUG
40#include <iprt/file.h>
41#include <iprt/formats/bmp.h>
42#endif
43
44#include <VBox/log.h>
45
46
47/**
48 * Convert an image to YUV420p format.
49 *
50 * @return \c true on success, \c false on failure.
51 * @param aDstBuf The destination image buffer.
52 * @param aDstWidth Width (in pixel) of destination buffer.
53 * @param aDstHeight Height (in pixel) of destination buffer.
54 * @param aSrcBuf The source image buffer.
55 * @param aSrcWidth Width (in pixel) of source buffer.
56 * @param aSrcHeight Height (in pixel) of source buffer.
57 */
58template <class T>
59inline bool recordingUtilsColorConvWriteYUV420p(uint8_t *aDstBuf, unsigned aDstWidth, unsigned aDstHeight,
60 uint8_t *aSrcBuf, unsigned aSrcWidth, unsigned aSrcHeight)
61{
62 RT_NOREF(aDstWidth, aDstHeight);
63
64 size_t image_size = aSrcWidth * aSrcHeight;
65 size_t upos = image_size;
66 size_t vpos = upos + upos / 4;
67 size_t i = 0;
68
69#define CALC_Y(r, g, b) \
70 ((66 * r + 129 * g + 25 * b) >> 8) + 16
71#define CALC_U(r, g, b) \
72 ((-38 * r + -74 * g + 112 * b) >> 8) + 128
73#define CALC_V(r, g, b) \
74 ((112 * r + -94 * g + -18 * b) >> 8) + 128
75
76 for (size_t line = 0; line < aSrcHeight; ++line)
77 {
78 if ((line % 2) == 0)
79 {
80 for (size_t x = 0; x < aSrcWidth; x += 2)
81 {
82 uint8_t b = aSrcBuf[4 * i];
83 uint8_t g = aSrcBuf[4 * i + 1];
84 uint8_t r = aSrcBuf[4 * i + 2];
85
86 aDstBuf[i++] = CALC_Y(r, g, b);
87
88 aDstBuf[upos++] = CALC_U(r, g, b);
89 aDstBuf[vpos++] = CALC_V(r, g, b);
90
91 b = aSrcBuf[4 * i];
92 g = aSrcBuf[4 * i + 1];
93 r = aSrcBuf[4 * i + 2];
94
95 aDstBuf[i++] = CALC_Y(r, g, b);
96 }
97 }
98 else
99 {
100 for (size_t x = 0; x < aSrcWidth; x++)
101 {
102 uint8_t b = aSrcBuf[4 * i];
103 uint8_t g = aSrcBuf[4 * i + 1];
104 uint8_t r = aSrcBuf[4 * i + 2];
105
106 aDstBuf[i++] = CALC_Y(r, g, b);
107 }
108 }
109 }
110
111 return true;
112}
113
114/**
115 * Convert an image to RGB24 format.
116 *
117 * @returns true on success, false on failure.
118 * @param aWidth Width of image.
119 * @param aHeight Height of image.
120 * @param aDestBuf An allocated memory buffer large enough to hold the
121 * destination image (i.e. width * height * 12bits).
122 * @param aSrcBuf The source image as an array of bytes.
123 */
124template <class T>
125inline bool RecordingUtilsColorConvWriteRGB24(unsigned aWidth, unsigned aHeight,
126 uint8_t *aDestBuf, uint8_t *aSrcBuf)
127{
128 enum { PIX_SIZE = 3 };
129 bool fRc = true;
130 AssertReturn(0 == (aWidth & 1), false);
131 AssertReturn(0 == (aHeight & 1), false);
132 T iter(aWidth, aHeight, aSrcBuf);
133 unsigned cPixels = aWidth * aHeight;
134 for (unsigned i = 0; i < cPixels && fRc; ++i)
135 {
136 unsigned red, green, blue;
137 fRc = iter.getRGB(&red, &green, &blue);
138 if (fRc)
139 {
140 aDestBuf[i * PIX_SIZE ] = red;
141 aDestBuf[i * PIX_SIZE + 1] = green;
142 aDestBuf[i * PIX_SIZE + 2] = blue;
143 }
144 }
145 return fRc;
146}
147
148/**
149 * Converts an entire RGB BGRA32 buffer to a YUV I420 buffer.
150 *
151 * @param paDst Pointer to destination buffer.
152 * @param uDstWidth Width (X, in pixel) of destination buffer.
153 * @param uDstHeight Height (Y, in pixel) of destination buffer.
154 * @param paSrc Pointer to source buffer.
155 * @param uSrcWidth Width (X, in pixel) of source buffer.
156 * @param uSrcHeight Height (Y, in pixel) of source buffer.
157 */
158void RecordingUtilsConvBGRA32ToYUVI420(uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
159 uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight)
160{
161 RT_NOREF(uDstWidth, uDstHeight);
162
163 size_t const image_size = uSrcWidth * uSrcHeight;
164 size_t upos = image_size;
165 size_t vpos = upos + upos / 4;
166 size_t i = 0;
167
168#define CALC_Y(r, g, b) \
169 ((66 * r + 129 * g + 25 * b) >> 8) + 16
170#define CALC_U(r, g, b) \
171 ((-38 * r + -74 * g + 112 * b) >> 8) + 128
172#define CALC_V(r, g, b) \
173 ((112 * r + -94 * g + -18 * b) >> 8) + 128
174
175 for (size_t y = 0; y < uSrcHeight; y++)
176 {
177 if ((y % 2) == 0)
178 {
179 for (size_t x = 0; x < uSrcWidth; x += 2)
180 {
181 uint8_t b = paSrc[4 * i];
182 uint8_t g = paSrc[4 * i + 1];
183 uint8_t r = paSrc[4 * i + 2];
184
185 paDst[i++] = CALC_Y(r, g, b);
186
187 paDst[upos++] = CALC_U(r, g, b);
188 paDst[vpos++] = CALC_V(r, g, b);
189
190 b = paSrc[4 * i];
191 g = paSrc[4 * i + 1];
192 r = paSrc[4 * i + 2];
193
194 paDst[i++] = CALC_Y(r, g, b);
195 }
196 }
197 else
198 {
199 for (size_t x = 0; x < uSrcWidth; x++)
200 {
201 uint8_t const b = paSrc[4 * i];
202 uint8_t const g = paSrc[4 * i + 1];
203 uint8_t const r = paSrc[4 * i + 2];
204
205 paDst[i++] = CALC_Y(r, g, b);
206 }
207 }
208 }
209
210#undef CALC_Y
211#undef CALC_U
212#undef CALC_V
213}
214
215/**
216 * Converts a part of a RGB BGRA32 buffer to a YUV I420 buffer.
217 *
218 * @param paDst Pointer to destination buffer.
219 * @param uDstX X destination position (in pixel).
220 * @param uDstY Y destination position (in pixel).
221 * @param uDstWidth Width (X, in pixel) of destination buffer.
222 * @param uDstHeight Height (Y, in pixel) of destination buffer.
223 * @param paSrc Pointer to source buffer.
224 * @param uSrcX X source position (in pixel).
225 * @param uSrcY Y source position (in pixel).
226 * @param uSrcWidth Width (X, in pixel) of source buffer.
227 * @param uSrcHeight Height (Y, in pixel) of source buffer.
228 * @param uSrcStride Stride (in bytes) of source buffer.
229 * @param uSrcBPP Bits per pixel of source buffer.
230 */
231void RecordingUtilsConvBGRA32ToYUVI420Ex(uint8_t *paDst, uint32_t uDstX, uint32_t uDstY, uint32_t uDstWidth, uint32_t uDstHeight,
232 uint8_t *paSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
233 uint32_t uSrcStride, uint8_t uSrcBPP)
234{
235 Assert(uDstX < uDstWidth);
236 Assert(uDstX + uSrcWidth <= uDstWidth);
237 Assert(uDstY < uDstHeight);
238 Assert(uDstY + uSrcHeight <= uDstHeight);
239 Assert(uSrcBPP % 8 == 0);
240
241 RT_NOREF(uSrcHeight, uDstHeight);
242
243#define CALC_Y(r, g, b) \
244 (66 * r + 129 * g + 25 * b) >> 8
245#define CALC_U(r, g, b) \
246 ((-38 * r + -74 * g + 112 * b) >> 8) + 128
247#define CALC_V(r, g, b) \
248 ((112 * r + -94 * g + -18 * b) >> 8) + 128
249
250 uint32_t uDstXCur = uDstX;
251
252 const unsigned uSrcBytesPerPixel = uSrcBPP / 8;
253
254 size_t offSrc = (uSrcY * uSrcStride) + (uSrcX * uSrcBytesPerPixel);
255
256 for (size_t y = 0; y < uSrcHeight; y++)
257 {
258 for (size_t x = 0; x < uSrcWidth; x++)
259 {
260 size_t const offBGR = offSrc + (x * uSrcBytesPerPixel);
261
262 uint8_t const b = paSrc[offBGR];
263 uint8_t const g = paSrc[offBGR + 1];
264 uint8_t const r = paSrc[offBGR + 2];
265
266 size_t const offY = uDstY * uDstWidth + uDstXCur;
267 size_t const offUV = (uDstY / 2) * (uDstWidth / 2) + (uDstXCur / 2) + uDstWidth * uDstHeight;
268
269 paDst[offY] = CALC_Y(r, g, b);
270 paDst[offUV] = CALC_U(r, g, b);
271 paDst[offUV + uDstWidth * uDstHeight / 4] = CALC_V(r, g, b);
272
273 uDstXCur++;
274 }
275
276 offSrc += uSrcStride;
277
278 uDstXCur = uDstX;
279 uDstY++;
280 }
281
282#undef CALC_Y
283#undef CALC_U
284#undef CALC_V
285}
286
287/**
288 * Crops (centers) or centers coordinates of a given source.
289 *
290 * @returns VBox status code.
291 * @retval VWRN_RECORDING_ENCODING_SKIPPED if the source is not visible.
292 * @param pCodecParms Video codec parameters to use for cropping / centering.
293 * @param sx Input / output: X location (in pixel) of the source.
294 * @param sy Input / output: Y location (in pixel) of the source.
295 * @param sw Input / output: Width (in pixel) of the source.
296 * @param sh Input / output: Height (in pixel) of the source.
297 * @param dx Input / output: X location (in pixel) of the destination.
298 * @param dy Input / output: Y location (in pixel) of the destination.
299 *
300 * @note Used when no other scaling algorithm is being selected / available. See testcase.
301 */
302int RecordingUtilsCoordsCropCenter(PRECORDINGCODECPARMS pCodecParms,
303 int32_t *sx, int32_t *sy, int32_t *sw, int32_t *sh, int32_t *dx, int32_t *dy)
304{
305 int vrc = VINF_SUCCESS;
306
307 Log3Func(("Original: sx=%RI32 sy=%RI32 sw=%RI32 sh=%RI32 dx=%RI32 dy=%RI32\n",
308 *sx, *sy, *sw, *sh, *dx, *dy));
309
310 /*
311 * Do centered cropping or centering.
312 */
313
314 int32_t const uOriginX = (int32_t)pCodecParms->u.Video.Scaling.u.Crop.m_iOriginX;
315 int32_t const uOriginY = (int32_t)pCodecParms->u.Video.Scaling.u.Crop.m_iOriginY;
316
317 /* Apply cropping / centering values. */
318 *dx += uOriginX;
319 *dy += uOriginY;
320
321 if (*dx < 0)
322 {
323 *dx = 0;
324 *sx += RT_ABS(uOriginX);
325 *sw -= RT_ABS(uOriginX);
326 }
327
328 if (*dy < 0)
329 {
330 *dy = 0;
331 *sy += RT_ABS(uOriginY);
332 *sh -= RT_ABS(uOriginY);
333 }
334
335 Log3Func(("Crop0: sx=%RI32 sy=%RI32 sw=%RI32 sh=%RI32 dx=%RI32 dy=%RI32\n",
336 *sx, *sy, *sw, *sh, *dx, *dy));
337
338 if (*sw > (int32_t)pCodecParms->u.Video.uWidth)
339 *sw = (int32_t)pCodecParms->u.Video.uWidth;
340
341 if (*sh > (int32_t)pCodecParms->u.Video.uHeight)
342 *sh = pCodecParms->u.Video.uHeight;
343
344 if (*dx + *sw >= (int32_t)pCodecParms->u.Video.uWidth)
345 *sw = pCodecParms->u.Video.uWidth - *dx;
346
347 if (*dy + *sh >= (int32_t)pCodecParms->u.Video.uHeight)
348 *sh = pCodecParms->u.Video.uHeight - *dy;
349
350 if ( *dx + *sw < 1
351 || *dy + *sh < 1
352 || *dx > (int32_t)pCodecParms->u.Video.uWidth
353 || *dy > (int32_t)pCodecParms->u.Video.uHeight
354 || *sw < 1
355 || *sh < 1)
356 {
357 vrc = VWRN_RECORDING_ENCODING_SKIPPED; /* Not visible, skip encoding. */
358 }
359
360 Log3Func(("Crop1: sx=%RI32 sy=%RI32 sw=%RI32 sh=%RI32 dx=%RI32 dy=%RI32 -> vrc=%Rrc\n",
361 *sx, *sy, *sw, *sh, *dx, *dy, vrc));
362
363 return vrc;
364}
365
366#ifdef DEBUG
367/**
368 * Dumps image data to a bitmap (BMP) file.
369 *
370 * @returns VBox status code.
371 * @param pu8RGBBuf Pointer to actual RGB image data.
372 * @param cbRGBBuf Size (in bytes) of \a pu8RGBBuf.
373 * @param pszPath Absolute path to dump file to. Must exist.
374 * Specify NULL to use the system's temp directory as output directory.
375 * Existing files will be overwritten.
376 * @param pszWhat Hint of what to dump. Optional and can be NULL.
377 * @param uWidth Width (in pixel) to write.
378 * @param uHeight Height (in pixel) to write.
379 * @param uBytsPerLine Bytes per line.
380 * @param uBPP Bits in pixel.
381 */
382int RecordingUtilsDbgDumpImageData(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszWhat,
383 uint32_t uWidth, uint32_t uHeight, uint32_t uBytesPerLine, uint8_t uBPP)
384{
385 RT_NOREF(cbRGBBuf);
386
387 const uint8_t uBytesPerPixel = uBPP / 8 /* Bits */;
388 const size_t cbData = uWidth * uHeight * uBytesPerPixel;
389
390 if (!cbData) /* No data to write? Bail out early. */
391 return VINF_SUCCESS;
392
393 BMPFILEHDR fileHdr;
394 RT_ZERO(fileHdr);
395
396 BMPWIN3XINFOHDR coreHdr;
397 RT_ZERO(coreHdr);
398
399 fileHdr.uType = BMP_HDR_MAGIC;
400 fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + cbData);
401 fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
402
403 coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
404 coreHdr.uWidth = uWidth;
405 coreHdr.uHeight = uHeight;
406 coreHdr.cPlanes = 1;
407 coreHdr.cBits = uBPP;
408 coreHdr.uXPelsPerMeter = 5000;
409 coreHdr.uYPelsPerMeter = 5000;
410
411 static uint64_t s_iCount = 0;
412
413 /* Hardcoded path for now. */
414 char szPath[RTPATH_MAX];
415 if (!pszPath)
416 {
417 int rc2 = RTPathTemp(szPath, sizeof(szPath));
418 if (RT_FAILURE(rc2))
419 return rc2;
420 }
421
422 char szFileName[RTPATH_MAX];
423 if (RTStrPrintf2(szFileName, sizeof(szFileName), "%s/RecDump-%04RU64-%s-w%RU16h%RU16.bmp",
424 pszPath ? pszPath : szPath, s_iCount, pszWhat ? pszWhat : "Frame", uWidth, uHeight) <= 0)
425 return VERR_BUFFER_OVERFLOW;
426
427 s_iCount++;
428
429 RTFILE fh;
430 int vrc = RTFileOpen(&fh, szFileName,
431 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
432 if (RT_SUCCESS(vrc))
433 {
434 RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
435 RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
436
437 /* Bitmaps (DIBs) are stored upside-down (thanks, OS/2), so work from the bottom up. */
438 uint32_t offSrc = cbRGBBuf - uBytesPerLine;
439
440 /* Do the copy. */
441 for (unsigned int i = 0; i < uHeight; i++)
442 {
443 RTFileWrite(fh, pu8RGBBuf + offSrc, uBytesPerLine, NULL);
444 offSrc -= uBytesPerLine;
445 }
446
447 RTFileClose(fh);
448 }
449
450 return vrc;
451}
452
453/**
454 * Dumps a video recording frame to a bitmap (BMP) file, extended version.
455 *
456 * @returns VBox status code.
457 * @param pFrame Video frame to dump.
458 * @param pszPath Output directory.
459 * @param pszWhat Hint of what to dump. Optional and can be NULL.
460 */
461int RecordingUtilsDbgDumpVideoFrameEx(const PRECORDINGVIDEOFRAME pFrame, const char *pszPath, const char *pszWhat)
462{
463 return RecordingUtilsDbgDumpImageData(pFrame->pau8Buf, pFrame->cbBuf,
464 pszPath, pszWhat,
465 pFrame->Info.uWidth, pFrame->Info.uHeight, pFrame->Info.uBytesPerLine, pFrame->Info.uBPP);
466}
467
468/**
469 * Dumps a video recording frame to a bitmap (BMP) file.
470 *
471 * @returns VBox status code.
472 * @param pFrame Video frame to dump.
473 * @param pszWhat Hint of what to dump. Optional and can be NULL.
474 */
475int RecordingUtilsDbgDumpVideoFrame(const PRECORDINGVIDEOFRAME pFrame, const char *pszWhat)
476{
477 return RecordingUtilsDbgDumpVideoFrameEx(pFrame, NULL /* Use temp directory */, pszWhat);
478}
479#endif /* DEBUG */
480
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette