VirtualBox

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

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

VideoRec: tsStartMs not needed.

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