VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/VideoRec.cpp@ 65354

Last change on this file since 65354 was 65330, checked in by vboxsync, 8 years ago

VideoRec: Update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
  • Property svn:mergeinfo set to (toggle deleted branches)
    /branches/VBox-3.0/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp58652,​70973
    /branches/VBox-3.2/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp66309,​66318
    /branches/VBox-4.0/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp70873
    /branches/VBox-4.1/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp74233
    /branches/VBox-4.2/src/VBox/Main/src-client/VideoRec.cpp91503-91504,​91506-91508,​91510,​91514-91515,​91521
    /branches/VBox-4.3/src/VBox/Main/src-client/VideoRec.cpp91223
    /branches/VBox-4.3/trunk/src/VBox/Main/src-client/VideoRec.cpp91223
    /branches/dsen/gui/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp79076-79078,​79089,​79109-79110,​79112-79113,​79127-79130,​79134,​79141,​79151,​79155,​79157-79159,​79193,​79197
    /branches/dsen/gui2/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp79224,​79228,​79233,​79235,​79258,​79262-79263,​79273,​79341,​79345,​79354,​79357,​79387-79388,​79559-79569,​79572-79573,​79578,​79581-79582,​79590-79591,​79598-79599,​79602-79603,​79605-79606,​79632,​79635,​79637,​79644
    /branches/dsen/gui3/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp79645-79692
File size: 35.3 KB
Line 
1/* $Id: VideoRec.cpp 65330 2017-01-16 13:38:36Z vboxsync $ */
2/** @file
3 * Video capturing utility routines.
4 */
5
6/*
7 * Copyright (C) 2012-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_MAIN
19
20#include <vector>
21
22#include <VBox/log.h>
23#include <iprt/asm.h>
24#include <iprt/assert.h>
25#include <iprt/semaphore.h>
26#include <iprt/thread.h>
27#include <iprt/time.h>
28
29#include <VBox/com/VirtualBox.h>
30#include <VBox/com/com.h>
31#include <VBox/com/string.h>
32
33#include "EbmlWriter.h"
34#include "VideoRec.h"
35
36#ifdef VBOX_WITH_LIBVPX
37# define VPX_CODEC_DISABLE_COMPAT 1
38# include <vpx/vp8cx.h>
39# include <vpx/vpx_image.h>
40
41/** Default VPX codec to use. */
42# define DEFAULTCODEC (vpx_codec_vp8_cx())
43#endif /* VBOX_WITH_LIBVPX */
44
45static int videoRecEncodeAndWrite(PVIDEORECSTREAM pStrm);
46static int videoRecRGBToYUV(PVIDEORECSTREAM pStrm);
47
48using namespace com;
49
50/**
51 * Enumeration for a video recording state.
52 */
53enum VIDEORECSTS
54{
55 /** Not initialized. */
56 VIDEORECSTS_UNINITIALIZED = 0,
57 /** Initialized, idle. */
58 VIDEORECSTS_IDLE = 1,
59 /** Currently in VideoRecCopyToIntBuf(), delay termination. */
60 VIDEORECSTS_COPYING = 2,
61 /** Signal that we are terminating. */
62 VIDEORECSTS_TERMINATING = 3
63};
64
65/**
66 * Enumeration for supported pixel formats.
67 */
68enum VIDEORECPIXELFMT
69{
70 /** Unknown pixel format. */
71 VIDEORECPIXELFMT_UNKNOWN = 0,
72 /** RGB 24. */
73 VIDEORECPIXELFMT_RGB24 = 1,
74 /** RGB 24. */
75 VIDEORECPIXELFMT_RGB32 = 2,
76 /** RGB 565. */
77 VIDEORECPIXELFMT_RGB565 = 3
78};
79
80/* Must be always accessible and therefore cannot be part of VIDEORECCONTEXT */
81static uint32_t g_enmState = VIDEORECSTS_UNINITIALIZED;
82
83/**
84 * Structure for keeping specific video recording codec data.
85 */
86typedef struct VIDEORECCODEC
87{
88 union
89 {
90#ifdef VBOX_WITH_LIBVPX
91 struct
92 {
93 /** VPX codec context. */
94 vpx_codec_ctx_t CodecCtx;
95 /** VPX codec configuration. */
96 vpx_codec_enc_cfg_t Config;
97 /** VPX image context. */
98 vpx_image_t RawImage;
99 } VPX;
100#endif /* VBOX_WITH_LIBVPX */
101 };
102} VIDEORECCODEC, *PVIDEORECCODEC;
103
104/**
105 * Strucutre for maintaining a video recording stream.
106 */
107typedef struct VIDEORECSTREAM
108{
109 /** Container context. */
110 WebMWriter *pEBML;
111 /** Track number of video stream. */
112 uint8_t uTrackVideo;
113 /** Codec data. */
114 VIDEORECCODEC Codec;
115 /** Target X resolution (in pixels). */
116 uint32_t uTargetWidth;
117 /** Target Y resolution (in pixels). */
118 uint32_t uTargetHeight;
119 /** X resolution of the last encoded frame. */
120 uint32_t uLastSourceWidth;
121 /** Y resolution of the last encoded frame. */
122 uint32_t uLastSourceHeight;
123 /** Current frame number. */
124 uint64_t cFrame;
125 /** RGB buffer containing the most recent frame of the framebuffer. */
126 uint8_t *pu8RgbBuf;
127 /** YUV buffer the encode function fetches the frame from. */
128 uint8_t *pu8YuvBuf;
129 /** Whether video recording is enabled or not. */
130 bool fEnabled;
131 /** Whether the RGB buffer is filled or not. */
132 bool fRgbFilled;
133 /** Pixel format of the current frame. */
134 uint32_t u32PixelFormat;
135 /** Minimal delay between two frames. */
136 uint32_t uDelay;
137 /** Time stamp of the last frame we encoded. */
138 uint64_t u64LastTimeStamp;
139 /** Time stamp of the current frame. */
140 uint64_t u64TimeStamp;
141 /** Encoder deadline. */
142 unsigned int uEncoderDeadline;
143} VIDEORECSTREAM, *PVIDEORECSTREAM;
144
145/** Vector of video recording streams. */
146typedef std::vector <PVIDEORECSTREAM> VideoRecStreams;
147
148/**
149 * Structure for keeping a video recording context.
150 */
151typedef struct VIDEORECCONTEXT
152{
153 /** Semaphore to signal the encoding worker thread. */
154 RTSEMEVENT WaitEvent;
155 /** Semaphore required during termination. */
156 RTSEMEVENT TermEvent;
157 /** Whether video recording is enabled or not. */
158 bool fEnabled;
159 /** Worker thread. */
160 RTTHREAD Thread;
161 /** Maximal time stamp. */
162 uint64_t u64MaxTimeStamp;
163 /** Maximal file size in MB. */
164 uint32_t uMaxFileSize;
165 /** Vector of current video recording stream contexts. */
166 VideoRecStreams vecStreams;
167} VIDEORECCONTEXT, *PVIDEORECCONTEXT;
168
169
170/**
171 * Iterator class for running through a BGRA32 image buffer and converting
172 * it to RGB.
173 */
174class ColorConvBGRA32Iter
175{
176private:
177 enum { PIX_SIZE = 4 };
178public:
179 ColorConvBGRA32Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
180 {
181 LogFlow(("width = %d height=%d aBuf=%lx\n", aWidth, aHeight, aBuf));
182 mPos = 0;
183 mSize = aWidth * aHeight * PIX_SIZE;
184 mBuf = aBuf;
185 }
186 /**
187 * Convert the next pixel to RGB.
188 * @returns true on success, false if we have reached the end of the buffer
189 * @param aRed where to store the red value
190 * @param aGreen where to store the green value
191 * @param aBlue where to store the blue value
192 */
193 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
194 {
195 bool rc = false;
196 if (mPos + PIX_SIZE <= mSize)
197 {
198 *aRed = mBuf[mPos + 2];
199 *aGreen = mBuf[mPos + 1];
200 *aBlue = mBuf[mPos ];
201 mPos += PIX_SIZE;
202 rc = true;
203 }
204 return rc;
205 }
206
207 /**
208 * Skip forward by a certain number of pixels
209 * @param aPixels how many pixels to skip
210 */
211 void skip(unsigned aPixels)
212 {
213 mPos += PIX_SIZE * aPixels;
214 }
215private:
216 /** Size of the picture buffer */
217 unsigned mSize;
218 /** Current position in the picture buffer */
219 unsigned mPos;
220 /** Address of the picture buffer */
221 uint8_t *mBuf;
222};
223
224/**
225 * Iterator class for running through an BGR24 image buffer and converting
226 * it to RGB.
227 */
228class ColorConvBGR24Iter
229{
230private:
231 enum { PIX_SIZE = 3 };
232public:
233 ColorConvBGR24Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
234 {
235 mPos = 0;
236 mSize = aWidth * aHeight * PIX_SIZE;
237 mBuf = aBuf;
238 }
239 /**
240 * Convert the next pixel to RGB.
241 * @returns true on success, false if we have reached the end of the buffer
242 * @param aRed where to store the red value
243 * @param aGreen where to store the green value
244 * @param aBlue where to store the blue value
245 */
246 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
247 {
248 bool rc = false;
249 if (mPos + PIX_SIZE <= mSize)
250 {
251 *aRed = mBuf[mPos + 2];
252 *aGreen = mBuf[mPos + 1];
253 *aBlue = mBuf[mPos ];
254 mPos += PIX_SIZE;
255 rc = true;
256 }
257 return rc;
258 }
259
260 /**
261 * Skip forward by a certain number of pixels
262 * @param aPixels how many pixels to skip
263 */
264 void skip(unsigned aPixels)
265 {
266 mPos += PIX_SIZE * aPixels;
267 }
268private:
269 /** Size of the picture buffer */
270 unsigned mSize;
271 /** Current position in the picture buffer */
272 unsigned mPos;
273 /** Address of the picture buffer */
274 uint8_t *mBuf;
275};
276
277/**
278 * Iterator class for running through an BGR565 image buffer and converting
279 * it to RGB.
280 */
281class ColorConvBGR565Iter
282{
283private:
284 enum { PIX_SIZE = 2 };
285public:
286 ColorConvBGR565Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
287 {
288 mPos = 0;
289 mSize = aWidth * aHeight * PIX_SIZE;
290 mBuf = aBuf;
291 }
292 /**
293 * Convert the next pixel to RGB.
294 * @returns true on success, false if we have reached the end of the buffer
295 * @param aRed where to store the red value
296 * @param aGreen where to store the green value
297 * @param aBlue where to store the blue value
298 */
299 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
300 {
301 bool rc = false;
302 if (mPos + PIX_SIZE <= mSize)
303 {
304 unsigned uFull = (((unsigned) mBuf[mPos + 1]) << 8)
305 | ((unsigned) mBuf[mPos]);
306 *aRed = (uFull >> 8) & ~7;
307 *aGreen = (uFull >> 3) & ~3 & 0xff;
308 *aBlue = (uFull << 3) & ~7 & 0xff;
309 mPos += PIX_SIZE;
310 rc = true;
311 }
312 return rc;
313 }
314
315 /**
316 * Skip forward by a certain number of pixels
317 * @param aPixels how many pixels to skip
318 */
319 void skip(unsigned aPixels)
320 {
321 mPos += PIX_SIZE * aPixels;
322 }
323private:
324 /** Size of the picture buffer */
325 unsigned mSize;
326 /** Current position in the picture buffer */
327 unsigned mPos;
328 /** Address of the picture buffer */
329 uint8_t *mBuf;
330};
331
332/**
333 * Convert an image to YUV420p format
334 * @returns true on success, false on failure
335 * @param aWidth width of image
336 * @param aHeight height of image
337 * @param aDestBuf an allocated memory buffer large enough to hold the
338 * destination image (i.e. width * height * 12bits)
339 * @param aSrcBuf the source image as an array of bytes
340 */
341template <class T>
342inline bool colorConvWriteYUV420p(unsigned aWidth, unsigned aHeight, uint8_t *aDestBuf, uint8_t *aSrcBuf)
343{
344 AssertReturn(!(aWidth & 1), false);
345 AssertReturn(!(aHeight & 1), false);
346 bool fRc = true;
347 T iter1(aWidth, aHeight, aSrcBuf);
348 T iter2 = iter1;
349 iter2.skip(aWidth);
350 unsigned cPixels = aWidth * aHeight;
351 unsigned offY = 0;
352 unsigned offU = cPixels;
353 unsigned offV = cPixels + cPixels / 4;
354 unsigned const cyHalf = aHeight / 2;
355 unsigned const cxHalf = aWidth / 2;
356 for (unsigned i = 0; i < cyHalf && fRc; ++i)
357 {
358 for (unsigned j = 0; j < cxHalf; ++j)
359 {
360 unsigned red, green, blue;
361 fRc = iter1.getRGB(&red, &green, &blue);
362 AssertReturn(fRc, false);
363 aDestBuf[offY] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
364 unsigned u = (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
365 unsigned v = (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
366
367 fRc = iter1.getRGB(&red, &green, &blue);
368 AssertReturn(fRc, false);
369 aDestBuf[offY + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
370 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
371 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
372
373 fRc = iter2.getRGB(&red, &green, &blue);
374 AssertReturn(fRc, false);
375 aDestBuf[offY + aWidth] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
376 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
377 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
378
379 fRc = iter2.getRGB(&red, &green, &blue);
380 AssertReturn(fRc, false);
381 aDestBuf[offY + aWidth + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
382 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
383 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
384
385 aDestBuf[offU] = u;
386 aDestBuf[offV] = v;
387 offY += 2;
388 ++offU;
389 ++offV;
390 }
391
392 iter1.skip(aWidth);
393 iter2.skip(aWidth);
394 offY += aWidth;
395 }
396
397 return true;
398}
399
400/**
401 * Convert an image to RGB24 format
402 * @returns true on success, false on failure
403 * @param aWidth width of image
404 * @param aHeight height of image
405 * @param aDestBuf an allocated memory buffer large enough to hold the
406 * destination image (i.e. width * height * 12bits)
407 * @param aSrcBuf the source image as an array of bytes
408 */
409template <class T>
410inline bool colorConvWriteRGB24(unsigned aWidth, unsigned aHeight,
411 uint8_t *aDestBuf, uint8_t *aSrcBuf)
412{
413 enum { PIX_SIZE = 3 };
414 bool rc = true;
415 AssertReturn(0 == (aWidth & 1), false);
416 AssertReturn(0 == (aHeight & 1), false);
417 T iter(aWidth, aHeight, aSrcBuf);
418 unsigned cPixels = aWidth * aHeight;
419 for (unsigned i = 0; i < cPixels && rc; ++i)
420 {
421 unsigned red, green, blue;
422 rc = iter.getRGB(&red, &green, &blue);
423 if (rc)
424 {
425 aDestBuf[i * PIX_SIZE ] = red;
426 aDestBuf[i * PIX_SIZE + 1] = green;
427 aDestBuf[i * PIX_SIZE + 2] = blue;
428 }
429 }
430 return rc;
431}
432
433/**
434 * Worker thread for all streams of a video recording context.
435 *
436 * Does RGB/YUV conversion and encoding.
437 */
438static DECLCALLBACK(int) videoRecThread(RTTHREAD hThreadSelf, void *pvUser)
439{
440 RT_NOREF(hThreadSelf);
441 PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)pvUser;
442 for (;;)
443 {
444 int rc = RTSemEventWait(pCtx->WaitEvent, RT_INDEFINITE_WAIT);
445 AssertRCBreak(rc);
446
447 if (ASMAtomicReadU32(&g_enmState) == VIDEORECSTS_TERMINATING)
448 break;
449
450 for (VideoRecStreams::iterator it = pCtx->vecStreams.begin(); it != pCtx->vecStreams.end(); it++)
451 {
452 PVIDEORECSTREAM pStream = (*it);
453
454 if ( pStream->fEnabled
455 && ASMAtomicReadBool(&pStream->fRgbFilled))
456 {
457 rc = videoRecRGBToYUV(pStream);
458
459 ASMAtomicWriteBool(&pStream->fRgbFilled, false);
460
461 if (RT_SUCCESS(rc))
462 rc = videoRecEncodeAndWrite(pStream);
463
464 if (RT_FAILURE(rc))
465 {
466 static unsigned cErrors = 100;
467 if (cErrors > 0)
468 {
469 LogRel(("VideoRec: Error %Rrc encoding / writing video frame\n", rc));
470 cErrors--;
471 }
472 }
473 }
474 }
475 }
476
477 return VINF_SUCCESS;
478}
479
480/**
481 * Creates a video recording context.
482 *
483 * @returns IPRT status code.
484 * @param cScreens Number of screens to create context for.
485 * @param ppCtx Pointer to created video recording context on success.
486 */
487int VideoRecContextCreate(uint32_t cScreens, PVIDEORECCONTEXT *ppCtx)
488{
489 AssertReturn(cScreens, VERR_INVALID_PARAMETER);
490 AssertPtrReturn(ppCtx, VERR_INVALID_POINTER);
491
492 Assert(ASMAtomicReadU32(&g_enmState) == VIDEORECSTS_UNINITIALIZED);
493
494 int rc = VINF_SUCCESS;
495
496 PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)RTMemAllocZ(sizeof(VIDEORECCONTEXT));
497 if (!pCtx)
498 return VERR_NO_MEMORY;
499
500 for (uint32_t uScreen = 0; uScreen < cScreens; uScreen++)
501 {
502 PVIDEORECSTREAM pStream = (PVIDEORECSTREAM)RTMemAllocZ(sizeof(VIDEORECSTREAM));
503 if (!pStream)
504 {
505 rc = VERR_NO_MEMORY;
506 break;
507 }
508
509 try
510 {
511 pCtx->vecStreams.push_back(pStream);
512
513 pStream->pEBML = new WebMWriter();
514 }
515 catch (std::bad_alloc)
516 {
517 rc = VERR_NO_MEMORY;
518 break;
519 }
520 }
521
522 if (RT_SUCCESS(rc))
523 {
524 rc = RTSemEventCreate(&pCtx->WaitEvent);
525 AssertRCReturn(rc, rc);
526
527 rc = RTSemEventCreate(&pCtx->TermEvent);
528 AssertRCReturn(rc, rc);
529
530 rc = RTThreadCreate(&pCtx->Thread, videoRecThread, (void*)pCtx, 0,
531 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "VideoRec");
532 AssertRCReturn(rc, rc);
533
534 ASMAtomicWriteU32(&g_enmState, VIDEORECSTS_IDLE);
535
536 if (ppCtx)
537 *ppCtx = pCtx;
538 }
539 else
540 {
541 /* Roll back allocations on error. */
542 VideoRecStreams::iterator it = pCtx->vecStreams.begin();
543 while (it != pCtx->vecStreams.end())
544 {
545 PVIDEORECSTREAM pStream = (*it);
546
547 if (pStream->pEBML)
548 delete pStream->pEBML;
549
550 it = pCtx->vecStreams.erase(it);
551
552 RTMemFree(pStream);
553 pStream = NULL;
554 }
555
556 Assert(pCtx->vecStreams.empty());
557 }
558
559 return rc;
560}
561
562/**
563 * Destroys a video recording context.
564 *
565 * @param pCtx Video recording context to destroy.
566 */
567void VideoRecContextDestroy(PVIDEORECCONTEXT pCtx)
568{
569 if (!pCtx)
570 return;
571
572 uint32_t enmState = VIDEORECSTS_IDLE;
573
574 for (;;) /** @todo r=andy Remove busy waiting! */
575 {
576 if (ASMAtomicCmpXchgExU32(&g_enmState, VIDEORECSTS_TERMINATING, enmState, &enmState))
577 break;
578 if (enmState == VIDEORECSTS_UNINITIALIZED)
579 return;
580 }
581
582 if (enmState == VIDEORECSTS_COPYING)
583 {
584 int rc = RTSemEventWait(pCtx->TermEvent, RT_INDEFINITE_WAIT);
585 AssertRC(rc);
586 }
587
588 RTSemEventSignal(pCtx->WaitEvent);
589 RTThreadWait(pCtx->Thread, 10 * 1000, NULL);
590 RTSemEventDestroy(pCtx->WaitEvent);
591 RTSemEventDestroy(pCtx->TermEvent);
592
593 VideoRecStreams::iterator it = pCtx->vecStreams.begin();
594 while (it != pCtx->vecStreams.end())
595 {
596 PVIDEORECSTREAM pStream = (*it);
597
598 if (pStream->fEnabled)
599 {
600 AssertPtr(pStream->pEBML);
601 pStream->pEBML->Close();
602
603 vpx_img_free(&pStream->Codec.VPX.RawImage);
604 vpx_codec_err_t rcv = vpx_codec_destroy(&pStream->Codec.VPX.CodecCtx);
605 Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
606
607 if (pStream->pu8RgbBuf)
608 {
609 RTMemFree(pStream->pu8RgbBuf);
610 pStream->pu8RgbBuf = NULL;
611 }
612 }
613
614 if (pStream->pEBML)
615 {
616 delete pStream->pEBML;
617 pStream->pEBML = NULL;
618 }
619
620 it = pCtx->vecStreams.erase(it);
621
622 RTMemFree(pStream);
623 pStream = NULL;
624 }
625
626 Assert(pCtx->vecStreams.empty());
627 RTMemFree(pCtx);
628
629 ASMAtomicWriteU32(&g_enmState, VIDEORECSTS_UNINITIALIZED);
630}
631
632/**
633 * VideoRec utility function to initialize video recording context.
634 *
635 * @returns IPRT status code.
636 * @param pCtx Pointer to video recording context to initialize Framebuffer width.
637 * @param uScreen Screen number.
638 * @param pszFile File to save the recorded data
639 * @param uWidth Width of the target image in the video recoriding file (movie)
640 * @param uHeight Height of the target image in video recording file.
641 * @param uRate Rate.
642 * @param uFps FPS.
643 * @param uMaxTime
644 * @param uMaxFileSize
645 * @param pszOptions
646 */
647int VideoRecStreamInit(PVIDEORECCONTEXT pCtx, uint32_t uScreen, const char *pszFile,
648 uint32_t uWidth, uint32_t uHeight, uint32_t uRate, uint32_t uFps,
649 uint32_t uMaxTime, uint32_t uMaxFileSize, const char *pszOptions)
650{
651 AssertPtrReturn(pCtx, VERR_INVALID_PARAMETER);
652 AssertReturn(uScreen < pCtx->vecStreams.size(), VERR_INVALID_PARAMETER);
653
654 pCtx->u64MaxTimeStamp = (uMaxTime > 0 ? RTTimeProgramMilliTS() + uMaxTime * 1000 : 0);
655 pCtx->uMaxFileSize = uMaxFileSize;
656
657 PVIDEORECSTREAM pStream = pCtx->vecStreams.at(uScreen);
658
659 pStream->uTargetWidth = uWidth;
660 pStream->uTargetHeight = uHeight;
661 pStream->pu8RgbBuf = (uint8_t *)RTMemAllocZ(uWidth * uHeight * 4);
662 AssertReturn(pStream->pu8RgbBuf, VERR_NO_MEMORY);
663
664 /* Play safe: the file must not exist, overwriting is potentially
665 * hazardous as nothing prevents the user from picking a file name of some
666 * other important file, causing unintentional data loss. */
667
668#ifdef VBOX_WITH_LIBVPX
669 pStream->uEncoderDeadline = VPX_DL_REALTIME;
670
671 vpx_codec_err_t rcv = vpx_codec_enc_config_default(DEFAULTCODEC, &pStream->Codec.VPX.Config, 0);
672 if (rcv != VPX_CODEC_OK)
673 {
674 LogRel(("VideoRec: Failed to get default configuration for VPX codec: %s\n", vpx_codec_err_to_string(rcv)));
675 return VERR_INVALID_PARAMETER;
676 }
677#endif
678
679 com::Utf8Str options(pszOptions);
680 size_t pos = 0;
681
682 /* By default we enable everything (if available). */
683 bool fHasVideoTrack = true;
684#ifdef VBOX_WITH_AUDIO_VIDEOREC
685 bool fHasAudioTrack = true;
686#endif
687
688 com::Utf8Str key, value;
689 while ((pos = options.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
690 {
691 if (key.compare("vc_quality", Utf8Str::CaseInsensitive) == 0)
692 {
693 if (value.compare("realtime", Utf8Str::CaseInsensitive) == 0)
694 {
695#ifdef VBOX_WITH_LIBVPX
696 pStream->uEncoderDeadline = VPX_DL_REALTIME;
697#endif
698 }
699 else if (value.compare("good", Utf8Str::CaseInsensitive) == 0)
700 {
701 pStream->uEncoderDeadline = 1000000 / uFps;
702 }
703 else if (value.compare("best", Utf8Str::CaseInsensitive) == 0)
704 {
705#ifdef VBOX_WITH_LIBVPX
706 pStream->uEncoderDeadline = VPX_DL_BEST_QUALITY;
707#endif
708 }
709 else
710 {
711 LogRel(("VideoRec: Setting quality deadline to '%s'\n", value.c_str()));
712 pStream->uEncoderDeadline = value.toUInt32();
713 }
714 }
715 else if (key.compare("vc_enabled", Utf8Str::CaseInsensitive) == 0)
716 {
717#ifdef VBOX_WITH_AUDIO_VIDEOREC
718 if (value.compare("false", Utf8Str::CaseInsensitive) == 0) /* Disable audio. */
719 {
720 fHasVideoTrack = false;
721 LogRel(("VideoRec: Only audio will be recorded\n"));
722 }
723#endif
724 }
725 else if (key.compare("ac_enabled", Utf8Str::CaseInsensitive) == 0)
726 {
727#ifdef VBOX_WITH_AUDIO_VIDEOREC
728 if (value.compare("false", Utf8Str::CaseInsensitive)) /* Disable audio. */
729 {
730 fHasAudioTrack = false;
731 LogRel(("VideoRec: Only video will be recorded\n"));
732 }
733#endif
734 }
735 else
736 LogRel(("VideoRec: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
737
738 } /* while */
739
740 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
741#ifdef DEBUG
742 fOpen |= RTFILE_O_CREATE_REPLACE;
743#else
744 fOpen |= RTFILE_O_CREATE;
745#endif
746
747 int rc = pStream->pEBML->Create(pszFile, fOpen, WebMWriter::AudioCodec_Opus, WebMWriter::VideoCodec_VP8);
748 if (RT_FAILURE(rc))
749 {
750 LogRel(("VideoRec: Failed to create the video capture output file '%s' (%Rrc)\n", pszFile, rc));
751 return rc;
752 }
753
754 pStream->uDelay = 1000 / uFps;
755
756 if (fHasVideoTrack)
757 {
758 rc = pStream->pEBML->AddVideoTrack(uWidth, uHeight, uFps, &pStream->uTrackVideo);
759 if (RT_FAILURE(rc))
760 {
761 LogRel(("VideoRec: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, rc));
762 return rc;
763 }
764 }
765
766#ifdef VBOX_WITH_LIBVPX
767 /* target bitrate in kilobits per second */
768 pStream->Codec.VPX.Config.rc_target_bitrate = uRate;
769 /* frame width */
770 pStream->Codec.VPX.Config.g_w = uWidth;
771 /* frame height */
772 pStream->Codec.VPX.Config.g_h = uHeight;
773 /* 1ms per frame */
774 pStream->Codec.VPX.Config.g_timebase.num = 1;
775 pStream->Codec.VPX.Config.g_timebase.den = 1000;
776 /* disable multithreading */
777 pStream->Codec.VPX.Config.g_threads = 0;
778
779 /* Initialize codec. */
780 rcv = vpx_codec_enc_init(&pStream->Codec.VPX.CodecCtx, DEFAULTCODEC, &pStream->Codec.VPX.Config, 0);
781 if (rcv != VPX_CODEC_OK)
782 {
783 LogFlow(("Failed to initialize VP8 encoder %s", vpx_codec_err_to_string(rcv)));
784 return VERR_INVALID_PARAMETER;
785 }
786
787 if (!vpx_img_alloc(&pStream->Codec.VPX.RawImage, VPX_IMG_FMT_I420, uWidth, uHeight, 1))
788 {
789 LogFlow(("Failed to allocate image %dx%d", uWidth, uHeight));
790 return VERR_NO_MEMORY;
791 }
792
793 pStream->pu8YuvBuf = pStream->Codec.VPX.RawImage.planes[0];
794#endif
795
796 pCtx->fEnabled = true;
797 pStream->fEnabled = true;
798
799 return VINF_SUCCESS;
800}
801
802/**
803 * VideoRec utility function to check if recording is enabled.
804 *
805 * @returns true if recording is enabled
806 * @param pCtx Pointer to video recording context.
807 */
808bool VideoRecIsEnabled(PVIDEORECCONTEXT pCtx)
809{
810 RT_NOREF(pCtx);
811 uint32_t enmState = ASMAtomicReadU32(&g_enmState);
812 return ( enmState == VIDEORECSTS_IDLE
813 || enmState == VIDEORECSTS_COPYING);
814}
815
816/**
817 * VideoRec utility function to check if recording engine is ready to accept a new frame
818 * for the given screen.
819 *
820 * @returns true if recording engine is ready
821 * @param pCtx Pointer to video recording context.
822 * @param uScreen Screen ID.
823 * @param u64TimeStamp Current time stamp.
824 */
825bool VideoRecIsReady(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t u64TimeStamp)
826{
827 uint32_t enmState = ASMAtomicReadU32(&g_enmState);
828 if (enmState != VIDEORECSTS_IDLE)
829 return false;
830
831 PVIDEORECSTREAM pStream = pCtx->vecStreams.at(uScreen);
832 if (!pStream->fEnabled)
833 return false;
834
835 if (u64TimeStamp < pStream->u64LastTimeStamp + pStream->uDelay)
836 return false;
837
838 if (ASMAtomicReadBool(&pStream->fRgbFilled))
839 return false;
840
841 return true;
842}
843
844/**
845 * VideoRec utility function to check if the file size has reached
846 * specified limits (if any).
847 *
848 * @returns true if any limit has been reached.
849 * @param pCtx Pointer to video recording context.
850 * @param uScreen Screen ID.
851 * @param u64TimeStamp Current time stamp.
852 */
853
854bool VideoRecIsFull(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t u64TimeStamp)
855{
856 PVIDEORECSTREAM pStream = pCtx->vecStreams.at(uScreen);
857 if(!pStream->fEnabled)
858 return false;
859
860 if(pCtx->u64MaxTimeStamp > 0 && u64TimeStamp >= pCtx->u64MaxTimeStamp)
861 return true;
862
863 if (pCtx->uMaxFileSize > 0)
864 {
865 uint64_t sizeInMB = pStream->pEBML->GetFileSize() / (1024 * 1024);
866 if(sizeInMB >= pCtx->uMaxFileSize)
867 return true;
868 }
869 /* Check for available free disk space */
870 if (pStream->pEBML->GetAvailableSpace() < 0x100000)
871 {
872 LogRel(("VideoRec: Not enough free storage space available, stopping video capture\n"));
873 return true;
874 }
875
876 return false;
877}
878
879/**
880 * VideoRec utility function to encode the source image and write the encoded
881 * image to target file.
882 *
883 * @returns IPRT status code.
884 * @param pStream Stream to encode and write.
885 */
886static int videoRecEncodeAndWrite(PVIDEORECSTREAM pStream)
887{
888 int rc;
889
890#ifdef VBOX_WITH_LIBVPX
891 /* presentation time stamp */
892 vpx_codec_pts_t pts = pStream->u64TimeStamp;
893 vpx_codec_err_t rcv = vpx_codec_encode(&pStream->Codec.VPX.CodecCtx,
894 &pStream->Codec.VPX.RawImage,
895 pts /* time stamp */,
896 pStream->uDelay /* how long to show this frame */,
897 0 /* flags */,
898 pStream->uEncoderDeadline /* quality setting */);
899 if (rcv != VPX_CODEC_OK)
900 {
901 LogFlow(("Failed to encode:%s\n", vpx_codec_err_to_string(rcv)));
902 return VERR_GENERAL_FAILURE;
903 }
904
905 vpx_codec_iter_t iter = NULL;
906 rc = VERR_NO_DATA;
907 for (;;)
908 {
909 const vpx_codec_cx_pkt_t *pPacket = vpx_codec_get_cx_data(&pStream->Codec.VPX.CodecCtx, &iter);
910 if (!pPacket)
911 break;
912
913 switch (pPacket->kind)
914 {
915 case VPX_CODEC_CX_FRAME_PKT:
916 {
917 WebMWriter::BlockData_VP8 blockData = { &pStream->Codec.VPX.Config, pPacket };
918 rc = pStream->pEBML->WriteBlock(pStream->uTrackVideo, &blockData, sizeof(blockData));
919 break;
920 }
921
922 default:
923 LogFlow(("Unexpected CODEC packet kind %ld\n", pPacket->kind));
924 break;
925 }
926 }
927
928 pStream->cFrame++;
929#else
930 RT_NOREF(pStream);
931 rc = VERR_NOT_SUPPORTED;
932#endif /* VBOX_WITH_LIBVPX */
933 return rc;
934}
935
936/**
937 * VideoRec utility function to convert RGB to YUV.
938 *
939 * @returns IPRT status code.
940 * @param pStrm Strm.
941 */
942static int videoRecRGBToYUV(PVIDEORECSTREAM pStrm)
943{
944 switch (pStrm->u32PixelFormat)
945 {
946 case VIDEORECPIXELFMT_RGB32:
947 LogFlow(("32 bit\n"));
948 if (!colorConvWriteYUV420p<ColorConvBGRA32Iter>(pStrm->uTargetWidth,
949 pStrm->uTargetHeight,
950 pStrm->pu8YuvBuf,
951 pStrm->pu8RgbBuf))
952 return VERR_INVALID_PARAMETER;
953 break;
954 case VIDEORECPIXELFMT_RGB24:
955 LogFlow(("24 bit\n"));
956 if (!colorConvWriteYUV420p<ColorConvBGR24Iter>(pStrm->uTargetWidth,
957 pStrm->uTargetHeight,
958 pStrm->pu8YuvBuf,
959 pStrm->pu8RgbBuf))
960 return VERR_INVALID_PARAMETER;
961 break;
962 case VIDEORECPIXELFMT_RGB565:
963 LogFlow(("565 bit\n"));
964 if (!colorConvWriteYUV420p<ColorConvBGR565Iter>(pStrm->uTargetWidth,
965 pStrm->uTargetHeight,
966 pStrm->pu8YuvBuf,
967 pStrm->pu8RgbBuf))
968 return VERR_INVALID_PARAMETER;
969 break;
970 default:
971 return VERR_NOT_SUPPORTED;
972 }
973 return VINF_SUCCESS;
974}
975
976/**
977 * VideoRec utility function to copy a source image (FrameBuf) to the intermediate
978 * RGB buffer. This function is executed only once per time.
979 *
980 * @thread EMT
981 *
982 * @returns IPRT status code.
983 * @param pCtx Pointer to the video recording context.
984 * @param uScreen Screen number.
985 * @param x Starting x coordinate of the source buffer (Framebuffer).
986 * @param y Starting y coordinate of the source buffer (Framebuffer).
987 * @param uPixelFormat Pixel Format.
988 * @param uBitsPerPixel Bits Per Pixel
989 * @param uBytesPerLine Bytes per source scanlineName.
990 * @param uSourceWidth Width of the source image (framebuffer).
991 * @param uSourceHeight Height of the source image (framebuffer).
992 * @param pu8BufAddr Pointer to source image(framebuffer).
993 * @param u64TimeStamp Time stamp (milliseconds).
994 */
995int VideoRecCopyToIntBuf(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint32_t x, uint32_t y,
996 uint32_t uPixelFormat, uint32_t uBitsPerPixel, uint32_t uBytesPerLine,
997 uint32_t uSourceWidth, uint32_t uSourceHeight, uint8_t *pu8BufAddr,
998 uint64_t u64TimeStamp)
999{
1000 /* Do not execute during termination and guard against termination */
1001 if (!ASMAtomicCmpXchgU32(&g_enmState, VIDEORECSTS_COPYING, VIDEORECSTS_IDLE))
1002 return VINF_TRY_AGAIN;
1003
1004 int rc = VINF_SUCCESS;
1005 do
1006 {
1007 AssertPtrBreakStmt(pu8BufAddr, rc = VERR_INVALID_PARAMETER);
1008 AssertBreakStmt(uSourceWidth, rc = VERR_INVALID_PARAMETER);
1009 AssertBreakStmt(uSourceHeight, rc = VERR_INVALID_PARAMETER);
1010 AssertBreakStmt(uScreen < pCtx->vecStreams.size(), rc = VERR_INVALID_PARAMETER);
1011
1012 PVIDEORECSTREAM pStream = pCtx->vecStreams.at(uScreen);
1013 if (!pStream->fEnabled)
1014 {
1015 rc = VINF_TRY_AGAIN; /* not (yet) enabled */
1016 break;
1017 }
1018 if (u64TimeStamp < pStream->u64LastTimeStamp + pStream->uDelay)
1019 {
1020 rc = VINF_TRY_AGAIN; /* respect maximum frames per second */
1021 break;
1022 }
1023 if (ASMAtomicReadBool(&pStream->fRgbFilled))
1024 {
1025 rc = VERR_TRY_AGAIN; /* previous frame not yet encoded */
1026 break;
1027 }
1028
1029 pStream->u64LastTimeStamp = u64TimeStamp;
1030
1031 int xDiff = ((int)pStream->uTargetWidth - (int)uSourceWidth) / 2;
1032 uint32_t w = uSourceWidth;
1033 if ((int)w + xDiff + (int)x <= 0) /* nothing visible */
1034 {
1035 rc = VERR_INVALID_PARAMETER;
1036 break;
1037 }
1038
1039 uint32_t destX;
1040 if ((int)x < -xDiff)
1041 {
1042 w += xDiff + x;
1043 x = -xDiff;
1044 destX = 0;
1045 }
1046 else
1047 destX = x + xDiff;
1048
1049 uint32_t h = uSourceHeight;
1050 int yDiff = ((int)pStream->uTargetHeight - (int)uSourceHeight) / 2;
1051 if ((int)h + yDiff + (int)y <= 0) /* nothing visible */
1052 {
1053 rc = VERR_INVALID_PARAMETER;
1054 break;
1055 }
1056
1057 uint32_t destY;
1058 if ((int)y < -yDiff)
1059 {
1060 h += yDiff + (int)y;
1061 y = -yDiff;
1062 destY = 0;
1063 }
1064 else
1065 destY = y + yDiff;
1066
1067 if ( destX > pStream->uTargetWidth
1068 || destY > pStream->uTargetHeight)
1069 {
1070 rc = VERR_INVALID_PARAMETER; /* nothing visible */
1071 break;
1072 }
1073
1074 if (destX + w > pStream->uTargetWidth)
1075 w = pStream->uTargetWidth - destX;
1076
1077 if (destY + h > pStream->uTargetHeight)
1078 h = pStream->uTargetHeight - destY;
1079
1080 /* Calculate bytes per pixel */
1081 uint32_t bpp = 1;
1082 if (uPixelFormat == BitmapFormat_BGR)
1083 {
1084 switch (uBitsPerPixel)
1085 {
1086 case 32:
1087 pStream->u32PixelFormat = VIDEORECPIXELFMT_RGB32;
1088 bpp = 4;
1089 break;
1090 case 24:
1091 pStream->u32PixelFormat = VIDEORECPIXELFMT_RGB24;
1092 bpp = 3;
1093 break;
1094 case 16:
1095 pStream->u32PixelFormat = VIDEORECPIXELFMT_RGB565;
1096 bpp = 2;
1097 break;
1098 default:
1099 AssertMsgFailed(("Unknown color depth! mBitsPerPixel=%d\n", uBitsPerPixel));
1100 break;
1101 }
1102 }
1103 else
1104 AssertMsgFailed(("Unknown pixel format! mPixelFormat=%d\n", uPixelFormat));
1105
1106 /* One of the dimensions of the current frame is smaller than before so
1107 * clear the entire buffer to prevent artifacts from the previous frame */
1108 if ( uSourceWidth < pStream->uLastSourceWidth
1109 || uSourceHeight < pStream->uLastSourceHeight)
1110 memset(pStream->pu8RgbBuf, 0, pStream->uTargetWidth * pStream->uTargetHeight * 4);
1111
1112 pStream->uLastSourceWidth = uSourceWidth;
1113 pStream->uLastSourceHeight = uSourceHeight;
1114
1115 /* Calculate start offset in source and destination buffers */
1116 uint32_t offSrc = y * uBytesPerLine + x * bpp;
1117 uint32_t offDst = (destY * pStream->uTargetWidth + destX) * bpp;
1118 /* do the copy */
1119 for (unsigned int i = 0; i < h; i++)
1120 {
1121 /* Overflow check */
1122 Assert(offSrc + w * bpp <= uSourceHeight * uBytesPerLine);
1123 Assert(offDst + w * bpp <= pStream->uTargetHeight * pStream->uTargetWidth * bpp);
1124 memcpy(pStream->pu8RgbBuf + offDst, pu8BufAddr + offSrc, w * bpp);
1125 offSrc += uBytesPerLine;
1126 offDst += pStream->uTargetWidth * bpp;
1127 }
1128
1129 pStream->u64TimeStamp = u64TimeStamp;
1130
1131 ASMAtomicWriteBool(&pStream->fRgbFilled, true);
1132 RTSemEventSignal(pCtx->WaitEvent);
1133 } while (0);
1134
1135 if (!ASMAtomicCmpXchgU32(&g_enmState, VIDEORECSTS_IDLE, VIDEORECSTS_COPYING))
1136 {
1137 rc = RTSemEventSignal(pCtx->TermEvent);
1138 AssertRC(rc);
1139 }
1140
1141 return rc;
1142}
Note: See TracBrowser for help on using the repository browser.

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