VirtualBox

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

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

Video Recording/Main: More optimizations for recording older guests which have a slightly different screen update logic. bugref:10650

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