VirtualBox

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

Last change on this file since 68808 was 68806, checked in by vboxsync, 7 years ago

Docs.

  • 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: 50.1 KB
Line 
1/* $Id: VideoRec.cpp 68806 2017-09-20 15:03:16Z 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_DISPLAY
19#include "LoggingNew.h"
20
21#include <stdexcept>
22#include <vector>
23
24#include <iprt/asm.h>
25#include <iprt/assert.h>
26#include <iprt/critsect.h>
27#include <iprt/path.h>
28#include <iprt/semaphore.h>
29#include <iprt/thread.h>
30#include <iprt/time.h>
31
32#include <VBox/log.h>
33#include <VBox/com/VirtualBox.h>
34#include <VBox/com/com.h>
35#include <VBox/com/string.h>
36
37#include "EbmlWriter.h"
38#include "VideoRec.h"
39
40#ifdef VBOX_WITH_LIBVPX
41# define VPX_CODEC_DISABLE_COMPAT 1
42# include <vpx/vp8cx.h>
43# include <vpx/vpx_image.h>
44
45/** Default VPX codec to use. */
46# define DEFAULTCODEC (vpx_codec_vp8_cx())
47#endif /* VBOX_WITH_LIBVPX */
48
49struct VIDEORECVIDEOFRAME;
50typedef struct VIDEORECVIDEOFRAME *PVIDEORECVIDEOFRAME;
51
52static int videoRecEncodeAndWrite(PVIDEORECSTREAM pStream, PVIDEORECVIDEOFRAME pFrame);
53static int videoRecRGBToYUV(uint32_t uPixelFormat,
54 uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
55 uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight);
56
57static int videoRecStreamCloseFile(PVIDEORECSTREAM pStream);
58static void videoRecStreamLock(PVIDEORECSTREAM pStream);
59static void videoRecStreamUnlock(PVIDEORECSTREAM pStream);
60
61using namespace com;
62
63#if 0
64/** Enables support for encoding multiple audio / video data frames at once. */
65#define VBOX_VIDEOREC_WITH_QUEUE
66#endif
67#ifdef DEBUG_andy
68/** Enables dumping audio / video data for debugging reasons. */
69# define VBOX_VIDEOREC_DUMP
70#endif
71
72/**
73 * Enumeration for a video recording state.
74 */
75enum VIDEORECSTS
76{
77 /** Not initialized. */
78 VIDEORECSTS_UNINITIALIZED = 0,
79 /** Initialized. */
80 VIDEORECSTS_INITIALIZED = 1,
81 /** The usual 32-bit hack. */
82 VIDEORECSTS_32BIT_HACK = 0x7fffffff
83};
84
85/**
86 * Enumeration for supported pixel formats.
87 */
88enum VIDEORECPIXELFMT
89{
90 /** Unknown pixel format. */
91 VIDEORECPIXELFMT_UNKNOWN = 0,
92 /** RGB 24. */
93 VIDEORECPIXELFMT_RGB24 = 1,
94 /** RGB 24. */
95 VIDEORECPIXELFMT_RGB32 = 2,
96 /** RGB 565. */
97 VIDEORECPIXELFMT_RGB565 = 3,
98 /** The usual 32-bit hack. */
99 VIDEORECPIXELFMT_32BIT_HACK = 0x7fffffff
100};
101
102/**
103 * Structure for keeping specific video recording codec data.
104 */
105typedef struct VIDEORECVIDEOCODEC
106{
107 union
108 {
109#ifdef VBOX_WITH_LIBVPX
110 struct
111 {
112 /** VPX codec context. */
113 vpx_codec_ctx_t Ctx;
114 /** VPX codec configuration. */
115 vpx_codec_enc_cfg_t Cfg;
116 /** VPX image context. */
117 vpx_image_t RawImage;
118 } VPX;
119#endif /* VBOX_WITH_LIBVPX */
120 };
121} VIDEORECVIDEOCODEC, *PVIDEORECVIDEOCODEC;
122
123/**
124 * Structure for keeping a single video recording video frame.
125 */
126typedef struct VIDEORECVIDEOFRAME
127{
128 /** X resolution of this frame. */
129 uint32_t uWidth;
130 /** Y resolution of this frame. */
131 uint32_t uHeight;
132 /** Pixel format of this frame. */
133 uint32_t uPixelFormat;
134 /** Time stamp (in ms). */
135 uint64_t uTimeStampMs;
136 /** RGB buffer containing the unmodified frame buffer data from Main's display. */
137 uint8_t *pu8RGBBuf;
138 /** Size (in bytes) of the RGB buffer. */
139 size_t cbRGBBuf;
140} VIDEORECVIDEOFRAME, *PVIDEORECVIDEOFRAME;
141
142#ifdef VBOX_WITH_AUDIO_VIDEOREC
143/**
144 * Structure for keeping a single video recording audio frame.
145 */
146typedef struct VIDEORECAUDIOFRAME
147{
148 uint8_t abBuf[_64K]; /** @todo Fix! */
149 size_t cbBuf;
150 /** Time stamp (in ms). */
151 uint64_t uTimeStampMs;
152} VIDEORECAUDIOFRAME, *PVIDEORECAUDIOFRAME;
153#endif
154
155/**
156 * Strucutre for maintaining a video recording stream.
157 */
158typedef struct VIDEORECSTREAM
159{
160 /** Video recording context this stream is associated to. */
161 PVIDEORECCONTEXT pCtx;
162 /** Destination where to write the stream to. */
163 VIDEORECDEST enmDst;
164 union
165 {
166 struct
167 {
168 /** File handle to use for writing. */
169 RTFILE hFile;
170 /** File name being used for this stream. */
171 char *pszFile;
172 /** Pointer to WebM writer instance being used. */
173 WebMWriter *pWEBM;
174 } File;
175 };
176#ifdef VBOX_WITH_AUDIO_VIDEOREC
177 /** Track number of audio stream. */
178 uint8_t uTrackAudio;
179#endif
180 /** Track number of video stream. */
181 uint8_t uTrackVideo;
182 /** Screen ID. */
183 uint16_t uScreenID;
184 /** Whether video recording is enabled or not. */
185 bool fEnabled;
186 /** Critical section to serialize access. */
187 RTCRITSECT CritSect;
188
189 struct
190 {
191 /** Codec-specific data. */
192 VIDEORECVIDEOCODEC Codec;
193 /** Minimal delay (in ms) between two frames. */
194 uint32_t uDelayMs;
195 /** Target X resolution (in pixels). */
196 uint32_t uWidth;
197 /** Target Y resolution (in pixels). */
198 uint32_t uHeight;
199 /** Time stamp (in ms) of the last video frame we encoded. */
200 uint64_t uLastTimeStampMs;
201 /** Pointer to the codec's internal YUV buffer. */
202 uint8_t *pu8YuvBuf;
203#ifdef VBOX_VIDEOREC_WITH_QUEUE
204# error "Implement me!"
205#else
206 VIDEORECVIDEOFRAME Frame;
207 bool fHasVideoData;
208#endif
209 } Video;
210} VIDEORECSTREAM, *PVIDEORECSTREAM;
211
212/** Vector of video recording streams. */
213typedef std::vector <PVIDEORECSTREAM> VideoRecStreams;
214
215/**
216 * Structure for keeping a video recording context.
217 */
218typedef struct VIDEORECCONTEXT
219{
220 /** Used recording configuration. */
221 VIDEORECCFG Cfg;
222 /** The current state. */
223 uint32_t enmState;
224 /** Critical section to serialize access. */
225 RTCRITSECT CritSect;
226 /** Semaphore to signal the encoding worker thread. */
227 RTSEMEVENT WaitEvent;
228 /** Whether this conext is enabled or not. */
229 bool fEnabled;
230 /** Shutdown indicator. */
231 bool fShutdown;
232 /** Worker thread. */
233 RTTHREAD Thread;
234 /** Vector of current recording stream contexts. */
235 VideoRecStreams vecStreams;
236 /** Timestamp (in ms) of when recording has been started. */
237 uint64_t tsStartMs;
238#ifdef VBOX_WITH_AUDIO_VIDEOREC
239 struct
240 {
241 bool fHasAudioData;
242 VIDEORECAUDIOFRAME Frame;
243 } Audio;
244#endif
245} VIDEORECCONTEXT, *PVIDEORECCONTEXT;
246
247#ifdef VBOX_VIDEOREC_DUMP
248#pragma pack(push)
249#pragma pack(1)
250typedef struct
251{
252 uint16_t u16Magic;
253 uint32_t u32Size;
254 uint16_t u16Reserved1;
255 uint16_t u16Reserved2;
256 uint32_t u32OffBits;
257} VIDEORECBMPHDR, *PVIDEORECBMPHDR;
258AssertCompileSize(VIDEORECBMPHDR, 14);
259
260typedef struct
261{
262 uint32_t u32Size;
263 uint32_t u32Width;
264 uint32_t u32Height;
265 uint16_t u16Planes;
266 uint16_t u16BitCount;
267 uint32_t u32Compression;
268 uint32_t u32SizeImage;
269 uint32_t u32XPelsPerMeter;
270 uint32_t u32YPelsPerMeter;
271 uint32_t u32ClrUsed;
272 uint32_t u32ClrImportant;
273} VIDEORECBMPDIBHDR, *PVIDEORECBMPDIBHDR;
274AssertCompileSize(VIDEORECBMPDIBHDR, 40);
275
276#pragma pack(pop)
277#endif /* VBOX_VIDEOREC_DUMP */
278
279/**
280 * Iterator class for running through a BGRA32 image buffer and converting
281 * it to RGB.
282 */
283class ColorConvBGRA32Iter
284{
285private:
286 enum { PIX_SIZE = 4 };
287public:
288 ColorConvBGRA32Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
289 {
290 LogFlow(("width = %d height=%d aBuf=%lx\n", aWidth, aHeight, aBuf));
291 mPos = 0;
292 mSize = aWidth * aHeight * PIX_SIZE;
293 mBuf = aBuf;
294 }
295 /**
296 * Convert the next pixel to RGB.
297 * @returns true on success, false if we have reached the end of the buffer
298 * @param aRed where to store the red value
299 * @param aGreen where to store the green value
300 * @param aBlue where to store the blue value
301 */
302 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
303 {
304 bool rc = false;
305 if (mPos + PIX_SIZE <= mSize)
306 {
307 *aRed = mBuf[mPos + 2];
308 *aGreen = mBuf[mPos + 1];
309 *aBlue = mBuf[mPos ];
310 mPos += PIX_SIZE;
311 rc = true;
312 }
313 return rc;
314 }
315
316 /**
317 * Skip forward by a certain number of pixels
318 * @param aPixels how many pixels to skip
319 */
320 void skip(unsigned aPixels)
321 {
322 mPos += PIX_SIZE * aPixels;
323 }
324private:
325 /** Size of the picture buffer */
326 unsigned mSize;
327 /** Current position in the picture buffer */
328 unsigned mPos;
329 /** Address of the picture buffer */
330 uint8_t *mBuf;
331};
332
333/**
334 * Iterator class for running through an BGR24 image buffer and converting
335 * it to RGB.
336 */
337class ColorConvBGR24Iter
338{
339private:
340 enum { PIX_SIZE = 3 };
341public:
342 ColorConvBGR24Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
343 {
344 mPos = 0;
345 mSize = aWidth * aHeight * PIX_SIZE;
346 mBuf = aBuf;
347 }
348 /**
349 * Convert the next pixel to RGB.
350 * @returns true on success, false if we have reached the end of the buffer
351 * @param aRed where to store the red value
352 * @param aGreen where to store the green value
353 * @param aBlue where to store the blue value
354 */
355 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
356 {
357 bool rc = false;
358 if (mPos + PIX_SIZE <= mSize)
359 {
360 *aRed = mBuf[mPos + 2];
361 *aGreen = mBuf[mPos + 1];
362 *aBlue = mBuf[mPos ];
363 mPos += PIX_SIZE;
364 rc = true;
365 }
366 return rc;
367 }
368
369 /**
370 * Skip forward by a certain number of pixels
371 * @param aPixels how many pixels to skip
372 */
373 void skip(unsigned aPixels)
374 {
375 mPos += PIX_SIZE * aPixels;
376 }
377private:
378 /** Size of the picture buffer */
379 unsigned mSize;
380 /** Current position in the picture buffer */
381 unsigned mPos;
382 /** Address of the picture buffer */
383 uint8_t *mBuf;
384};
385
386/**
387 * Iterator class for running through an BGR565 image buffer and converting
388 * it to RGB.
389 */
390class ColorConvBGR565Iter
391{
392private:
393 enum { PIX_SIZE = 2 };
394public:
395 ColorConvBGR565Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
396 {
397 mPos = 0;
398 mSize = aWidth * aHeight * PIX_SIZE;
399 mBuf = aBuf;
400 }
401 /**
402 * Convert the next pixel to RGB.
403 * @returns true on success, false if we have reached the end of the buffer
404 * @param aRed where to store the red value
405 * @param aGreen where to store the green value
406 * @param aBlue where to store the blue value
407 */
408 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
409 {
410 bool rc = false;
411 if (mPos + PIX_SIZE <= mSize)
412 {
413 unsigned uFull = (((unsigned) mBuf[mPos + 1]) << 8)
414 | ((unsigned) mBuf[mPos]);
415 *aRed = (uFull >> 8) & ~7;
416 *aGreen = (uFull >> 3) & ~3 & 0xff;
417 *aBlue = (uFull << 3) & ~7 & 0xff;
418 mPos += PIX_SIZE;
419 rc = true;
420 }
421 return rc;
422 }
423
424 /**
425 * Skip forward by a certain number of pixels
426 * @param aPixels how many pixels to skip
427 */
428 void skip(unsigned aPixels)
429 {
430 mPos += PIX_SIZE * aPixels;
431 }
432private:
433 /** Size of the picture buffer */
434 unsigned mSize;
435 /** Current position in the picture buffer */
436 unsigned mPos;
437 /** Address of the picture buffer */
438 uint8_t *mBuf;
439};
440
441/**
442 * Convert an image to YUV420p format.
443 *
444 * @return true on success, false on failure.
445 * @param aDstBuf The destination image buffer.
446 * @param aDstWidth Width (in pixel) of destination buffer.
447 * @param aDstHeight Height (in pixel) of destination buffer.
448 * @param aSrcBuf The source image buffer.
449 * @param aSrcWidth Width (in pixel) of source buffer.
450 * @param aSrcHeight Height (in pixel) of source buffer.
451 */
452template <class T>
453inline bool colorConvWriteYUV420p(uint8_t *aDstBuf, unsigned aDstWidth, unsigned aDstHeight,
454 uint8_t *aSrcBuf, unsigned aSrcWidth, unsigned aSrcHeight)
455{
456 RT_NOREF(aDstWidth, aDstHeight);
457
458 AssertReturn(!(aSrcWidth & 1), false);
459 AssertReturn(!(aSrcHeight & 1), false);
460
461 bool fRc = true;
462 T iter1(aSrcWidth, aSrcHeight, aSrcBuf);
463 T iter2 = iter1;
464 iter2.skip(aSrcWidth);
465 unsigned cPixels = aSrcWidth * aSrcHeight;
466 unsigned offY = 0;
467 unsigned offU = cPixels;
468 unsigned offV = cPixels + cPixels / 4;
469 unsigned const cyHalf = aSrcHeight / 2;
470 unsigned const cxHalf = aSrcWidth / 2;
471 for (unsigned i = 0; i < cyHalf && fRc; ++i)
472 {
473 for (unsigned j = 0; j < cxHalf; ++j)
474 {
475 unsigned red, green, blue;
476 fRc = iter1.getRGB(&red, &green, &blue);
477 AssertReturn(fRc, false);
478 aDstBuf[offY] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
479 unsigned u = (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
480 unsigned v = (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
481
482 fRc = iter1.getRGB(&red, &green, &blue);
483 AssertReturn(fRc, false);
484 aDstBuf[offY + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
485 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
486 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
487
488 fRc = iter2.getRGB(&red, &green, &blue);
489 AssertReturn(fRc, false);
490 aDstBuf[offY + aSrcWidth] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
491 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
492 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
493
494 fRc = iter2.getRGB(&red, &green, &blue);
495 AssertReturn(fRc, false);
496 aDstBuf[offY + aSrcWidth + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
497 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
498 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
499
500 aDstBuf[offU] = u;
501 aDstBuf[offV] = v;
502 offY += 2;
503 ++offU;
504 ++offV;
505 }
506
507 iter1.skip(aSrcWidth);
508 iter2.skip(aSrcWidth);
509 offY += aSrcWidth;
510 }
511
512 return true;
513}
514
515/**
516 * Convert an image to RGB24 format
517 * @returns true on success, false on failure
518 * @param aWidth width of image
519 * @param aHeight height of image
520 * @param aDestBuf an allocated memory buffer large enough to hold the
521 * destination image (i.e. width * height * 12bits)
522 * @param aSrcBuf the source image as an array of bytes
523 */
524template <class T>
525inline bool colorConvWriteRGB24(unsigned aWidth, unsigned aHeight,
526 uint8_t *aDestBuf, uint8_t *aSrcBuf)
527{
528 enum { PIX_SIZE = 3 };
529 bool rc = true;
530 AssertReturn(0 == (aWidth & 1), false);
531 AssertReturn(0 == (aHeight & 1), false);
532 T iter(aWidth, aHeight, aSrcBuf);
533 unsigned cPixels = aWidth * aHeight;
534 for (unsigned i = 0; i < cPixels && rc; ++i)
535 {
536 unsigned red, green, blue;
537 rc = iter.getRGB(&red, &green, &blue);
538 if (rc)
539 {
540 aDestBuf[i * PIX_SIZE ] = red;
541 aDestBuf[i * PIX_SIZE + 1] = green;
542 aDestBuf[i * PIX_SIZE + 2] = blue;
543 }
544 }
545 return rc;
546}
547
548/**
549 * Worker thread for all streams of a video recording context.
550 *
551 * Does RGB/YUV conversion and encoding.
552 */
553static DECLCALLBACK(int) videoRecThread(RTTHREAD hThreadSelf, void *pvUser)
554{
555 PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)pvUser;
556
557 /* Signal that we're up and rockin'. */
558 RTThreadUserSignal(hThreadSelf);
559
560 for (;;)
561 {
562 int rc = RTSemEventWait(pCtx->WaitEvent, RT_INDEFINITE_WAIT);
563 AssertRCBreak(rc);
564
565 if (ASMAtomicReadBool(&pCtx->fShutdown))
566 break;
567
568#ifdef VBOX_WITH_AUDIO_VIDEOREC
569 VIDEORECAUDIOFRAME audioFrame;
570 RT_ZERO(audioFrame);
571
572 int rc2 = RTCritSectEnter(&pCtx->CritSect);
573 AssertRC(rc2);
574
575 const bool fEncodeAudio = pCtx->Audio.fHasAudioData;
576 if (fEncodeAudio)
577 {
578 /*
579 * Every recording stream needs to get the same audio data at a certain point in time.
580 * Do the multiplexing here to not block EMT for too long.
581 *
582 * For now just doing a simple copy of the current audio frame should be good enough.
583 */
584 memcpy(&audioFrame, &pCtx->Audio.Frame, sizeof(VIDEORECAUDIOFRAME));
585
586 pCtx->Audio.fHasAudioData = false;
587 }
588
589 rc2 = RTCritSectLeave(&pCtx->CritSect);
590 AssertRC(rc2);
591#endif
592
593 /** @todo r=andy This is inefficient -- as we already wake up this thread
594 * for every screen from Main, we here go again (on every wake up) through
595 * all screens. */
596 for (VideoRecStreams::iterator it = pCtx->vecStreams.begin(); it != pCtx->vecStreams.end(); it++)
597 {
598 PVIDEORECSTREAM pStream = (*it);
599
600 videoRecStreamLock(pStream);
601
602 if (!pStream->fEnabled)
603 {
604 videoRecStreamUnlock(pStream);
605 continue;
606 }
607
608 PVIDEORECVIDEOFRAME pVideoFrame = &pStream->Video.Frame;
609 const bool fEncodeVideo = pStream->Video.fHasVideoData;
610
611 if (fEncodeVideo)
612 {
613 rc = videoRecRGBToYUV(pVideoFrame->uPixelFormat,
614 /* Destination */
615 pStream->Video.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
616 /* Source */
617 pVideoFrame->pu8RGBBuf, pStream->Video.uWidth, pStream->Video.uHeight);
618 if (RT_SUCCESS(rc))
619 rc = videoRecEncodeAndWrite(pStream, pVideoFrame);
620
621 pStream->Video.fHasVideoData = false;
622 }
623
624 videoRecStreamUnlock(pStream);
625
626 if (RT_FAILURE(rc))
627 {
628 static unsigned s_cErrEncVideo = 0;
629 if (s_cErrEncVideo < 32)
630 {
631 LogRel(("VideoRec: Error %Rrc encoding / writing video frame\n", rc));
632 s_cErrEncVideo++;
633 }
634 }
635
636#ifdef VBOX_WITH_AUDIO_VIDEOREC
637 /* Each (enabled) screen has to get the same audio data. */
638 if (fEncodeAudio)
639 {
640 Assert(audioFrame.cbBuf);
641 Assert(audioFrame.cbBuf <= _64K); /** @todo Fix. */
642
643 WebMWriter::BlockData_Opus blockData = { audioFrame.abBuf, audioFrame.cbBuf, audioFrame.uTimeStampMs };
644 rc = pStream->File.pWEBM->WriteBlock(pStream->uTrackAudio, &blockData, sizeof(blockData));
645 if (RT_FAILURE(rc))
646 {
647 static unsigned s_cErrEncAudio = 0;
648 if (s_cErrEncAudio < 32)
649 {
650 LogRel(("VideoRec: Error %Rrc encoding audio frame\n", rc));
651 s_cErrEncAudio++;
652 }
653 }
654 }
655#endif
656 }
657
658 /* Keep going in case of errors. */
659
660 } /* for */
661
662 return VINF_SUCCESS;
663}
664
665/**
666 * Creates a video recording context.
667 *
668 * @returns IPRT status code.
669 * @param cScreens Number of screens to create context for.
670 * @param pVideoRecCfg Pointer to video recording configuration to use.
671 * @param ppCtx Pointer to created video recording context on success.
672 */
673int VideoRecContextCreate(uint32_t cScreens, PVIDEORECCFG pVideoRecCfg, PVIDEORECCONTEXT *ppCtx)
674{
675 AssertReturn(cScreens, VERR_INVALID_PARAMETER);
676 AssertPtrReturn(pVideoRecCfg, VERR_INVALID_POINTER);
677 AssertPtrReturn(ppCtx, VERR_INVALID_POINTER);
678
679 PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)RTMemAllocZ(sizeof(VIDEORECCONTEXT));
680 if (!pCtx)
681 return VERR_NO_MEMORY;
682
683 int rc = RTCritSectInit(&pCtx->CritSect);
684 if (RT_FAILURE(rc))
685 {
686 RTMemFree(pCtx);
687 return rc;
688 }
689
690 for (uint32_t uScreen = 0; uScreen < cScreens; uScreen++)
691 {
692 PVIDEORECSTREAM pStream = (PVIDEORECSTREAM)RTMemAllocZ(sizeof(VIDEORECSTREAM));
693 if (!pStream)
694 {
695 rc = VERR_NO_MEMORY;
696 break;
697 }
698
699 rc = RTCritSectInit(&pStream->CritSect);
700 if (RT_FAILURE(rc))
701 break;
702
703 try
704 {
705 pStream->uScreenID = uScreen;
706
707 pCtx->vecStreams.push_back(pStream);
708
709 pStream->File.pWEBM = new WebMWriter();
710 }
711 catch (std::bad_alloc)
712 {
713 rc = VERR_NO_MEMORY;
714 break;
715 }
716 }
717
718 if (RT_SUCCESS(rc))
719 {
720 pCtx->tsStartMs = RTTimeMilliTS();
721 pCtx->enmState = VIDEORECSTS_UNINITIALIZED;
722 pCtx->fShutdown = false;
723
724 /* Copy the configuration to our context. */
725 pCtx->Cfg = *pVideoRecCfg;
726
727 rc = RTSemEventCreate(&pCtx->WaitEvent);
728 AssertRCReturn(rc, rc);
729
730 rc = RTThreadCreate(&pCtx->Thread, videoRecThread, (void *)pCtx, 0,
731 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "VideoRec");
732
733 if (RT_SUCCESS(rc)) /* Wait for the thread to start. */
734 rc = RTThreadUserWait(pCtx->Thread, 30 * 1000 /* 30s timeout */);
735
736 if (RT_SUCCESS(rc))
737 {
738 pCtx->enmState = VIDEORECSTS_INITIALIZED;
739 pCtx->fEnabled = true;
740
741 if (ppCtx)
742 *ppCtx = pCtx;
743 }
744 }
745
746 if (RT_FAILURE(rc))
747 {
748 int rc2 = VideoRecContextDestroy(pCtx);
749 AssertRC(rc2);
750 }
751
752 return rc;
753}
754
755/**
756 * Destroys a video recording context.
757 *
758 * @param pCtx Video recording context to destroy.
759 */
760int VideoRecContextDestroy(PVIDEORECCONTEXT pCtx)
761{
762 if (!pCtx)
763 return VINF_SUCCESS;
764
765 /* First, disable the context. */
766 ASMAtomicWriteBool(&pCtx->fEnabled, false);
767
768 if (pCtx->enmState == VIDEORECSTS_INITIALIZED)
769 {
770 /* Set shutdown indicator. */
771 ASMAtomicWriteBool(&pCtx->fShutdown, true);
772
773 /* Signal the thread. */
774 RTSemEventSignal(pCtx->WaitEvent);
775
776 int rc = RTThreadWait(pCtx->Thread, 10 * 1000 /* 10s timeout */, NULL);
777 if (RT_FAILURE(rc))
778 return rc;
779
780 rc = RTSemEventDestroy(pCtx->WaitEvent);
781 AssertRC(rc);
782
783 pCtx->WaitEvent = NIL_RTSEMEVENT;
784 }
785
786 int rc = RTCritSectEnter(&pCtx->CritSect);
787 if (RT_SUCCESS(rc))
788 {
789 VideoRecStreams::iterator it = pCtx->vecStreams.begin();
790 while (it != pCtx->vecStreams.end())
791 {
792 PVIDEORECSTREAM pStream = (*it);
793
794 videoRecStreamLock(pStream);
795
796 if (pStream->fEnabled)
797 {
798 switch (pStream->enmDst)
799 {
800 case VIDEORECDEST_FILE:
801 {
802 if (pStream->File.pWEBM)
803 pStream->File.pWEBM->Close();
804 break;
805 }
806
807 default:
808 AssertFailed(); /* Should never happen. */
809 break;
810 }
811
812 vpx_img_free(&pStream->Video.Codec.VPX.RawImage);
813 vpx_codec_err_t rcv = vpx_codec_destroy(&pStream->Video.Codec.VPX.Ctx);
814 Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
815
816#ifdef VBOX_VIDEOREC_WITH_QUEUE
817# error "Implement me!"
818#else
819 PVIDEORECVIDEOFRAME pFrame = &pStream->Video.Frame;
820#endif
821 if (pFrame->pu8RGBBuf)
822 {
823 Assert(pFrame->cbRGBBuf);
824
825 RTMemFree(pFrame->pu8RGBBuf);
826 pFrame->pu8RGBBuf = NULL;
827 }
828
829 pFrame->cbRGBBuf = 0;
830
831 LogRel(("VideoRec: Recording screen #%u stopped\n", pStream->uScreenID));
832 }
833
834 switch (pStream->enmDst)
835 {
836 case VIDEORECDEST_FILE:
837 {
838 int rc2 = videoRecStreamCloseFile(pStream);
839 AssertRC(rc2);
840
841 if (pStream->File.pWEBM)
842 {
843 delete pStream->File.pWEBM;
844 pStream->File.pWEBM = NULL;
845 }
846 break;
847 }
848
849 default:
850 AssertFailed(); /* Should never happen. */
851 break;
852 }
853
854 it = pCtx->vecStreams.erase(it);
855
856 videoRecStreamUnlock(pStream);
857
858 RTCritSectDelete(&pStream->CritSect);
859
860 RTMemFree(pStream);
861 pStream = NULL;
862 }
863
864 Assert(pCtx->vecStreams.empty());
865
866 int rc2 = RTCritSectLeave(&pCtx->CritSect);
867 AssertRC(rc2);
868
869 RTCritSectDelete(&pCtx->CritSect);
870
871 RTMemFree(pCtx);
872 pCtx = NULL;
873 }
874
875 return rc;
876}
877
878/**
879 * Retrieves a specific recording stream of a recording context.
880 *
881 * @returns Pointer to recording stream if found, or NULL if not found.
882 * @param pCtx Recording context to look up stream for.
883 * @param uScreen Screen number of recording stream to look up.
884 */
885DECLINLINE(PVIDEORECSTREAM) videoRecStreamGet(PVIDEORECCONTEXT pCtx, uint32_t uScreen)
886{
887 AssertPtrReturn(pCtx, NULL);
888
889 PVIDEORECSTREAM pStream;
890
891 try
892 {
893 pStream = pCtx->vecStreams.at(uScreen);
894 }
895 catch (std::out_of_range)
896 {
897 pStream = NULL;
898 }
899
900 return pStream;
901}
902
903/**
904 * Locks a recording stream.
905 *
906 * @param pStream Recording stream to lock.
907 */
908static void videoRecStreamLock(PVIDEORECSTREAM pStream)
909{
910 int rc = RTCritSectEnter(&pStream->CritSect);
911 AssertRC(rc);
912}
913
914/**
915 * Unlocks a locked recording stream.
916 *
917 * @param pStream Recording stream to unlock.
918 */
919static void videoRecStreamUnlock(PVIDEORECSTREAM pStream)
920{
921 int rc = RTCritSectLeave(&pStream->CritSect);
922 AssertRC(rc);
923}
924
925/**
926 * Opens a file for a given recording stream to capture to.
927 *
928 * @returns IPRT status code.
929 * @param pStream Recording stream to open file for.
930 * @param pCfg Recording configuration to use.
931 */
932int videoRecStreamOpenFile(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg)
933{
934 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
935 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
936
937 Assert(pStream->enmDst == VIDEORECDEST_INVALID);
938 Assert(pCfg->enmDst == VIDEORECDEST_FILE);
939
940 RTTIMESPEC ts;
941 RTTimeNow(&ts);
942
943 RTTIME time;
944 RTTimeExplode(&time, &ts);
945
946 char *pszAbsPath = RTPathAbsDup(com::Utf8Str(pCfg->File.strFile).c_str());
947 AssertPtrReturn(pszAbsPath, VERR_NO_MEMORY);
948
949 char *pszSuff = RTPathSuffix(pszAbsPath);
950 if (pszSuff)
951 pszSuff = RTStrDup(pszSuff);
952
953 RTPathStripSuffix(pszAbsPath);
954 AssertPtrReturn(pszAbsPath, VERR_INVALID_PARAMETER);
955
956 if (!pszSuff)
957 pszSuff = RTStrDup(".webm");
958
959 char *pszFile = NULL;
960
961 int rc;
962 if (pStream->uScreenID > 1)
963 rc = RTStrAPrintf(&pszFile, "%s-%u%s", pszAbsPath, pStream->uScreenID + 1, pszSuff);
964 else
965 rc = RTStrAPrintf(&pszFile, "%s%s", pszAbsPath, pszSuff);
966
967 if (RT_SUCCESS(rc))
968 {
969 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
970#ifdef DEBUG
971 fOpen |= RTFILE_O_CREATE_REPLACE;
972#else
973 /* Play safe: the file must not exist, overwriting is potentially
974 * hazardous as nothing prevents the user from picking a file name of some
975 * other important file, causing unintentional data loss. */
976 fOpen |= RTFILE_O_CREATE;
977#endif
978 RTFILE hFile;
979 rc = RTFileOpen(&hFile, pszFile, fOpen);
980 if (RT_SUCCESS(rc))
981 {
982 pStream->enmDst = VIDEORECDEST_FILE;
983 pStream->File.hFile = hFile;
984 pStream->File.pszFile = pszFile; /* Assign allocated string to our stream's config. */
985 }
986 else
987 RTStrFree(pszFile);
988 }
989
990 RTStrFree(pszSuff);
991 RTStrFree(pszAbsPath);
992
993 if (RT_FAILURE(rc))
994 LogRel(("VideoRec: Failed to open file for screen %RU32, rc=%Rrc\n", pStream->uScreenID, rc));
995
996 return rc;
997}
998
999/**
1000 * Closes a recording stream's file again.
1001 *
1002 * @returns IPRT status code.
1003 * @param pStream Recording stream to close file for.
1004 */
1005static int videoRecStreamCloseFile(PVIDEORECSTREAM pStream)
1006{
1007 Assert(pStream->enmDst == VIDEORECDEST_FILE);
1008
1009 pStream->enmDst = VIDEORECDEST_INVALID;
1010
1011 AssertPtr(pStream->File.pszFile);
1012
1013 if (RTFileIsValid(pStream->File.hFile))
1014 {
1015 RTFileClose(pStream->File.hFile);
1016 LogRel(("VideoRec: Closed file '%s'\n", pStream->File.pszFile));
1017 }
1018
1019 RTStrFree(pStream->File.pszFile);
1020 pStream->File.pszFile = NULL;
1021
1022 return VINF_SUCCESS;
1023}
1024
1025/**
1026 * VideoRec utility function to initialize video recording context.
1027 *
1028 * @returns IPRT status code.
1029 * @param pCtx Pointer to video recording context.
1030 * @param uScreen Screen number to record.
1031 */
1032int VideoRecStreamInit(PVIDEORECCONTEXT pCtx, uint32_t uScreen)
1033{
1034 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1035
1036 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1037 if (!pStream)
1038 return VERR_NOT_FOUND;
1039
1040 int rc = videoRecStreamOpenFile(pStream, &pCtx->Cfg);
1041 if (RT_FAILURE(rc))
1042 return rc;
1043
1044 PVIDEORECCFG pCfg = &pCtx->Cfg;
1045
1046 pStream->pCtx = pCtx;
1047 /** @todo Make the following parameters configurable on a per-stream basis? */
1048 pStream->Video.uWidth = pCfg->Video.uWidth;
1049 pStream->Video.uHeight = pCfg->Video.uHeight;
1050
1051#ifndef VBOX_VIDEOREC_WITH_QUEUE
1052 /* When not using a queue, we only use one frame per stream at once.
1053 * So do the initialization here. */
1054 PVIDEORECVIDEOFRAME pFrame = &pStream->Video.Frame;
1055
1056 const size_t cbRGBBuf = pStream->Video.uWidth
1057 * pStream->Video.uHeight
1058 * 4 /* 32 BPP maximum */;
1059 AssertReturn(cbRGBBuf, VERR_INVALID_PARAMETER);
1060
1061 pFrame->pu8RGBBuf = (uint8_t *)RTMemAllocZ(cbRGBBuf);
1062 AssertReturn(pFrame->pu8RGBBuf, VERR_NO_MEMORY);
1063 pFrame->cbRGBBuf = cbRGBBuf;
1064#endif
1065
1066 PVIDEORECVIDEOCODEC pVC = &pStream->Video.Codec;
1067
1068#ifdef VBOX_WITH_LIBVPX
1069 vpx_codec_err_t rcv = vpx_codec_enc_config_default(DEFAULTCODEC, &pVC->VPX.Cfg, 0);
1070 if (rcv != VPX_CODEC_OK)
1071 {
1072 LogRel(("VideoRec: Failed to get default configuration for VPX codec: %s\n", vpx_codec_err_to_string(rcv)));
1073 return VERR_INVALID_PARAMETER;
1074 }
1075#endif
1076
1077 pStream->Video.uDelayMs = 1000 / pCfg->Video.uFPS;
1078
1079 switch (pStream->enmDst)
1080 {
1081 case VIDEORECDEST_FILE:
1082 {
1083 rc = pStream->File.pWEBM->CreateEx(pStream->File.pszFile, &pStream->File.hFile,
1084#ifdef VBOX_WITH_AUDIO_VIDEOREC
1085 pCfg->Audio.fEnabled ? WebMWriter::AudioCodec_Opus : WebMWriter::AudioCodec_None,
1086#else
1087 WebMWriter::AudioCodec_None,
1088#endif
1089 pCfg->Video.fEnabled ? WebMWriter::VideoCodec_VP8 : WebMWriter::VideoCodec_None);
1090 if (RT_FAILURE(rc))
1091 {
1092 LogRel(("VideoRec: Failed to create the capture output file '%s' (%Rrc)\n", pStream->File.pszFile, rc));
1093 break;
1094 }
1095
1096 const char *pszFile = pStream->File.pszFile;
1097
1098 if (pCfg->Video.fEnabled)
1099 {
1100 rc = pStream->File.pWEBM->AddVideoTrack(pCfg->Video.uWidth, pCfg->Video.uHeight, pCfg->Video.uFPS,
1101 &pStream->uTrackVideo);
1102 if (RT_FAILURE(rc))
1103 {
1104 LogRel(("VideoRec: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, rc));
1105 break;
1106 }
1107
1108 LogRel(("VideoRec: Recording screen #%u with %RU32x%RU32 @ %RU32 kbps, %u FPS\n",
1109 uScreen, pCfg->Video.uWidth, pCfg->Video.uHeight, pCfg->Video.uRate, pCfg->Video.uFPS));
1110 }
1111
1112#ifdef VBOX_WITH_AUDIO_VIDEOREC
1113 if (pCfg->Audio.fEnabled)
1114 {
1115 rc = pStream->File.pWEBM->AddAudioTrack(pCfg->Audio.uHz, pCfg->Audio.cChannels, pCfg->Audio.cBits,
1116 &pStream->uTrackAudio);
1117 if (RT_FAILURE(rc))
1118 {
1119 LogRel(("VideoRec: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, rc));
1120 break;
1121 }
1122
1123 LogRel(("VideoRec: Recording audio in %RU16Hz, %RU8 bit, %RU8 %s\n",
1124 pCfg->Audio.uHz, pCfg->Audio.cBits, pCfg->Audio.cChannels, pCfg->Audio.cChannels ? "channel" : "channels"));
1125 }
1126#endif
1127
1128 if ( pCfg->Video.fEnabled
1129#ifdef VBOX_WITH_AUDIO_VIDEOREC
1130 || pCfg->Audio.fEnabled
1131#endif
1132 )
1133 {
1134 char szWhat[32] = { 0 };
1135 if (pCfg->Video.fEnabled)
1136 RTStrCat(szWhat, sizeof(szWhat), "video");
1137#ifdef VBOX_WITH_AUDIO_VIDEOREC
1138 if (pCfg->Audio.fEnabled)
1139 {
1140 if (pCfg->Video.fEnabled)
1141 RTStrCat(szWhat, sizeof(szWhat), " + ");
1142 RTStrCat(szWhat, sizeof(szWhat), "audio");
1143 }
1144#endif
1145 LogRel(("VideoRec: Recording %s to '%s'\n", szWhat, pszFile));
1146 }
1147
1148 break;
1149 }
1150
1151 default:
1152 AssertFailed(); /* Should never happen. */
1153 rc = VERR_NOT_IMPLEMENTED;
1154 break;
1155 }
1156
1157 if (RT_FAILURE(rc))
1158 return rc;
1159
1160#ifdef VBOX_WITH_LIBVPX
1161 /* Target bitrate in kilobits per second. */
1162 pVC->VPX.Cfg.rc_target_bitrate = pCfg->Video.uRate;
1163 /* Frame width. */
1164 pVC->VPX.Cfg.g_w = pCfg->Video.uWidth;
1165 /* Frame height. */
1166 pVC->VPX.Cfg.g_h = pCfg->Video.uHeight;
1167 /* 1ms per frame. */
1168 pVC->VPX.Cfg.g_timebase.num = 1;
1169 pVC->VPX.Cfg.g_timebase.den = 1000;
1170 /* Disable multithreading. */
1171 pVC->VPX.Cfg.g_threads = 0;
1172
1173 /* Initialize codec. */
1174 rcv = vpx_codec_enc_init(&pVC->VPX.Ctx, DEFAULTCODEC, &pVC->VPX.Cfg, 0);
1175 if (rcv != VPX_CODEC_OK)
1176 {
1177 LogFlow(("Failed to initialize VP8 encoder: %s\n", vpx_codec_err_to_string(rcv)));
1178 return VERR_INVALID_PARAMETER;
1179 }
1180
1181 if (!vpx_img_alloc(&pVC->VPX.RawImage, VPX_IMG_FMT_I420, pCfg->Video.uWidth, pCfg->Video.uHeight, 1))
1182 {
1183 LogFlow(("Failed to allocate image %RU32x%RU32\n", pCfg->Video.uWidth, pCfg->Video.uHeight));
1184 return VERR_NO_MEMORY;
1185 }
1186
1187 /* Save a pointer to the first raw YUV plane. */
1188 pStream->Video.pu8YuvBuf = pVC->VPX.RawImage.planes[0];
1189#endif
1190 pStream->fEnabled = true;
1191
1192 return VINF_SUCCESS;
1193}
1194
1195/**
1196 * Returns which recording features currently are enabled for a given configuration.
1197 *
1198 * @returns Enabled video recording features.
1199 * @param pCfg Pointer to recording configuration.
1200 */
1201VIDEORECFEATURES VideoRecGetEnabled(PVIDEORECCFG pCfg)
1202{
1203 if ( !pCfg
1204 || !pCfg->fEnabled)
1205 {
1206 return VIDEORECFEATURE_NONE;
1207 }
1208
1209 VIDEORECFEATURES fFeatures = VIDEORECFEATURE_NONE;
1210
1211 if (pCfg->Video.fEnabled)
1212 fFeatures |= VIDEORECFEATURE_VIDEO;
1213
1214#ifdef VBOX_WITH_AUDIO_VIDEOREC
1215 if (pCfg->Audio.fEnabled)
1216 fFeatures |= VIDEORECFEATURE_AUDIO;
1217#endif
1218
1219 return fFeatures;
1220}
1221
1222/**
1223 * Checks if recording engine is ready to accept a new frame for the given screen.
1224 *
1225 * @returns true if recording engine is ready.
1226 * @param pCtx Pointer to video recording context.
1227 * @param uScreen Screen ID.
1228 * @param uTimeStampMs Current time stamp (in ms).
1229 */
1230bool VideoRecIsReady(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t uTimeStampMs)
1231{
1232 AssertPtrReturn(pCtx, false);
1233
1234 if (ASMAtomicReadU32(&pCtx->enmState) != VIDEORECSTS_INITIALIZED)
1235 return false;
1236
1237 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1238 if ( !pStream
1239 || !pStream->fEnabled)
1240 {
1241 return false;
1242 }
1243
1244 PVIDEORECVIDEOFRAME pLastFrame = &pStream->Video.Frame;
1245
1246 if (uTimeStampMs < pLastFrame->uTimeStampMs + pStream->Video.uDelayMs)
1247 return false;
1248
1249 return true;
1250}
1251
1252/**
1253 * Returns whether video recording for a given recording context is active or not.
1254 *
1255 * @returns true if active, false if not.
1256 * @param pCtx Pointer to video recording context.
1257 */
1258bool VideoRecIsActive(PVIDEORECCONTEXT pCtx)
1259{
1260 if (!pCtx)
1261 return false;
1262
1263 return ASMAtomicReadBool(&pCtx->fEnabled);
1264}
1265
1266/**
1267 * Checks if a specified limit for recording has been reached.
1268 *
1269 * @returns true if any limit has been reached.
1270 * @param pCtx Pointer to video recording context.
1271 * @param uScreen Screen ID.
1272 * @param tsNowMs Current time stamp (in ms).
1273 */
1274bool VideoRecIsLimitReached(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t tsNowMs)
1275{
1276 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1277 if ( !pStream
1278 || !pStream->fEnabled)
1279 {
1280 return false;
1281 }
1282
1283 const PVIDEORECCFG pCfg = &pCtx->Cfg;
1284
1285 if ( pCfg->uMaxTimeS
1286 && tsNowMs >= pCtx->tsStartMs + (pCfg->uMaxTimeS * 1000))
1287 {
1288 return true;
1289 }
1290
1291 if (pCfg->enmDst == VIDEORECDEST_FILE)
1292 {
1293
1294 if (pCfg->File.uMaxSizeMB)
1295 {
1296 uint64_t sizeInMB = pStream->File.pWEBM->GetFileSize() / (1024 * 1024);
1297 if(sizeInMB >= pCfg->File.uMaxSizeMB)
1298 return true;
1299 }
1300
1301 /* Check for available free disk space */
1302 if ( pStream->File.pWEBM
1303 && pStream->File.pWEBM->GetAvailableSpace() < 0x100000) /**@todo r=andy WTF? Fix this. */
1304 {
1305 LogRel(("VideoRec: Not enough free storage space available, stopping video capture\n"));
1306 return true;
1307 }
1308 }
1309
1310 return false;
1311}
1312
1313/**
1314 * Encodes the source image and write the encoded image to the stream's destination.
1315 *
1316 * @returns IPRT status code.
1317 * @param pStream Stream to encode and submit to.
1318 * @param pFrame Frame to encode and submit.
1319 */
1320static int videoRecEncodeAndWrite(PVIDEORECSTREAM pStream, PVIDEORECVIDEOFRAME pFrame)
1321{
1322 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1323 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
1324
1325 int rc;
1326
1327 AssertPtr(pStream->pCtx);
1328 PVIDEORECCFG pCfg = &pStream->pCtx->Cfg;
1329 PVIDEORECVIDEOCODEC pVC = &pStream->Video.Codec;
1330#ifdef VBOX_WITH_LIBVPX
1331 /* Presentation Time Stamp (PTS). */
1332 vpx_codec_pts_t pts = pFrame->uTimeStampMs;
1333 vpx_codec_err_t rcv = vpx_codec_encode(&pVC->VPX.Ctx,
1334 &pVC->VPX.RawImage,
1335 pts /* Time stamp */,
1336 pStream->Video.uDelayMs /* How long to show this frame */,
1337 0 /* Flags */,
1338 pCfg->Video.Codec.VPX.uEncoderDeadline /* Quality setting */);
1339 if (rcv != VPX_CODEC_OK)
1340 {
1341 LogFunc(("Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
1342 return VERR_GENERAL_FAILURE;
1343 }
1344
1345 vpx_codec_iter_t iter = NULL;
1346 rc = VERR_NO_DATA;
1347 for (;;)
1348 {
1349 const vpx_codec_cx_pkt_t *pPacket = vpx_codec_get_cx_data(&pVC->VPX.Ctx, &iter);
1350 if (!pPacket)
1351 break;
1352
1353 switch (pPacket->kind)
1354 {
1355 case VPX_CODEC_CX_FRAME_PKT:
1356 {
1357 WebMWriter::BlockData_VP8 blockData = { &pVC->VPX.Cfg, pPacket };
1358 rc = pStream->File.pWEBM->WriteBlock(pStream->uTrackVideo, &blockData, sizeof(blockData));
1359 break;
1360 }
1361
1362 default:
1363 AssertFailed();
1364 LogFunc(("Unexpected video packet type %ld\n", pPacket->kind));
1365 break;
1366 }
1367 }
1368#else
1369 RT_NOREF(pStream);
1370 rc = VERR_NOT_SUPPORTED;
1371#endif /* VBOX_WITH_LIBVPX */
1372 return rc;
1373}
1374
1375/**
1376 * Converts a RGB to YUV buffer.
1377 *
1378 * @returns IPRT status code.
1379 * TODO
1380 */
1381static int videoRecRGBToYUV(uint32_t uPixelFormat,
1382 uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
1383 uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight)
1384{
1385 switch (uPixelFormat)
1386 {
1387 case VIDEORECPIXELFMT_RGB32:
1388 if (!colorConvWriteYUV420p<ColorConvBGRA32Iter>(paDst, uDstWidth, uDstHeight,
1389 paSrc, uSrcWidth, uSrcHeight))
1390 return VERR_INVALID_PARAMETER;
1391 break;
1392 case VIDEORECPIXELFMT_RGB24:
1393 if (!colorConvWriteYUV420p<ColorConvBGR24Iter>(paDst, uDstWidth, uDstHeight,
1394 paSrc, uSrcWidth, uSrcHeight))
1395 return VERR_INVALID_PARAMETER;
1396 break;
1397 case VIDEORECPIXELFMT_RGB565:
1398 if (!colorConvWriteYUV420p<ColorConvBGR565Iter>(paDst, uDstWidth, uDstHeight,
1399 paSrc, uSrcWidth, uSrcHeight))
1400 return VERR_INVALID_PARAMETER;
1401 break;
1402 default:
1403 AssertFailed();
1404 return VERR_NOT_SUPPORTED;
1405 }
1406 return VINF_SUCCESS;
1407}
1408
1409/**
1410 * Sends an audio frame to the video encoding thread.
1411 *
1412 * @thread EMT
1413 *
1414 * @returns IPRT status code.
1415 * @param pCtx Pointer to the video recording context.
1416 * @param pvData Audio frame data to send.
1417 * @param cbData Size (in bytes) of audio frame data.
1418 * @param uTimeStampMs Time stamp (in ms) of audio playback.
1419 */
1420int VideoRecSendAudioFrame(PVIDEORECCONTEXT pCtx, const void *pvData, size_t cbData, uint64_t uTimeStampMs)
1421{
1422#ifdef VBOX_WITH_AUDIO_VIDEOREC
1423 AssertReturn(cbData <= _64K, VERR_INVALID_PARAMETER);
1424
1425 int rc = RTCritSectEnter(&pCtx->CritSect);
1426 if (RT_FAILURE(rc))
1427 return rc;
1428
1429 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
1430 *
1431 * The multiplexing is needed to supply all recorded (enabled) screens with the same
1432 * audio data at the same given point in time.
1433 */
1434 PVIDEORECAUDIOFRAME pFrame = &pCtx->Audio.Frame;
1435
1436 memcpy(pFrame->abBuf, pvData, RT_MIN(_64K /** @todo Fix! */, cbData));
1437
1438 pFrame->cbBuf = cbData;
1439 pFrame->uTimeStampMs = uTimeStampMs;
1440
1441 pCtx->Audio.fHasAudioData = true;
1442
1443 rc = RTCritSectLeave(&pCtx->CritSect);
1444 if (RT_SUCCESS(rc))
1445 rc = RTSemEventSignal(pCtx->WaitEvent);
1446
1447 return rc;
1448#else
1449 RT_NOREF(pCtx, pvData, cbData, uTimeStampMs);
1450 return VINF_SUCCESS;
1451#endif
1452}
1453
1454/**
1455 * Copies a source video frame to the intermediate RGB buffer.
1456 * This function is executed only once per time.
1457 *
1458 * @thread EMT
1459 *
1460 * @returns IPRT status code.
1461 * @param pCtx Pointer to the video recording context.
1462 * @param uScreen Screen number.
1463 * @param x Starting x coordinate of the video frame.
1464 * @param y Starting y coordinate of the video frame.
1465 * @param uPixelFormat Pixel format.
1466 * @param uBPP Bits Per Pixel (BPP).
1467 * @param uBytesPerLine Bytes per scanline.
1468 * @param uSrcWidth Width of the video frame.
1469 * @param uSrcHeight Height of the video frame.
1470 * @param puSrcData Pointer to video frame data.
1471 * @param uTimeStampMs Time stamp (in ms).
1472 */
1473int VideoRecSendVideoFrame(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint32_t x, uint32_t y,
1474 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
1475 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
1476 uint64_t uTimeStampMs)
1477{
1478 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1479 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
1480 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
1481 AssertReturn(puSrcData, VERR_INVALID_POINTER);
1482
1483 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1484 if (!pStream)
1485 return VERR_NOT_FOUND;
1486
1487 videoRecStreamLock(pStream);
1488
1489 int rc = VINF_SUCCESS;
1490
1491 do
1492 {
1493 if (!pStream->fEnabled)
1494 {
1495 rc = VINF_TRY_AGAIN; /* Not (yet) enabled. */
1496 break;
1497 }
1498
1499 if (uTimeStampMs < pStream->Video.uLastTimeStampMs + pStream->Video.uDelayMs)
1500 {
1501 rc = VINF_TRY_AGAIN; /* Respect maximum frames per second. */
1502 break;
1503 }
1504
1505 pStream->Video.uLastTimeStampMs = uTimeStampMs;
1506
1507 int xDiff = ((int)pStream->Video.uWidth - (int)uSrcWidth) / 2;
1508 uint32_t w = uSrcWidth;
1509 if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
1510 {
1511 rc = VERR_INVALID_PARAMETER;
1512 break;
1513 }
1514
1515 uint32_t destX;
1516 if ((int)x < -xDiff)
1517 {
1518 w += xDiff + x;
1519 x = -xDiff;
1520 destX = 0;
1521 }
1522 else
1523 destX = x + xDiff;
1524
1525 uint32_t h = uSrcHeight;
1526 int yDiff = ((int)pStream->Video.uHeight - (int)uSrcHeight) / 2;
1527 if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
1528 {
1529 rc = VERR_INVALID_PARAMETER;
1530 break;
1531 }
1532
1533 uint32_t destY;
1534 if ((int)y < -yDiff)
1535 {
1536 h += yDiff + (int)y;
1537 y = -yDiff;
1538 destY = 0;
1539 }
1540 else
1541 destY = y + yDiff;
1542
1543 if ( destX > pStream->Video.uWidth
1544 || destY > pStream->Video.uHeight)
1545 {
1546 rc = VERR_INVALID_PARAMETER; /* Nothing visible. */
1547 break;
1548 }
1549
1550 if (destX + w > pStream->Video.uWidth)
1551 w = pStream->Video.uWidth - destX;
1552
1553 if (destY + h > pStream->Video.uHeight)
1554 h = pStream->Video.uHeight - destY;
1555
1556#ifdef VBOX_VIDEOREC_WITH_QUEUE
1557# error "Implement me!"
1558#else
1559 PVIDEORECVIDEOFRAME pFrame = &pStream->Video.Frame;
1560#endif
1561 /* Calculate bytes per pixel and set pixel format. */
1562 const unsigned uBytesPerPixel = uBPP / 8;
1563 if (uPixelFormat == BitmapFormat_BGR)
1564 {
1565 switch (uBPP)
1566 {
1567 case 32:
1568 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB32;
1569 break;
1570 case 24:
1571 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB24;
1572 break;
1573 case 16:
1574 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB565;
1575 break;
1576 default:
1577 AssertMsgFailed(("Unknown color depth (%RU32)\n", uBPP));
1578 break;
1579 }
1580 }
1581 else
1582 AssertMsgFailed(("Unknown pixel format (%RU32)\n", uPixelFormat));
1583
1584#ifndef VBOX_VIDEOREC_WITH_QUEUE
1585 /* If we don't use a queue then we have to compare the dimensions
1586 * of the current frame with the previous frame:
1587 *
1588 * If it's smaller than before then clear the entire buffer to prevent artifacts
1589 * from the previous frame. */
1590 if ( uSrcWidth < pFrame->uWidth
1591 || uSrcHeight < pFrame->uHeight)
1592 {
1593 /** @todo r=andy Only clear dirty areas. */
1594 RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
1595 }
1596#endif
1597 /* Calculate start offset in source and destination buffers. */
1598 uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
1599 uint32_t offDst = (destY * pStream->Video.uWidth + destX) * uBytesPerPixel;
1600
1601#ifdef VBOX_VIDEOREC_DUMP
1602 VIDEORECBMPHDR bmpHdr;
1603 RT_ZERO(bmpHdr);
1604
1605 VIDEORECBMPDIBHDR bmpDIBHdr;
1606 RT_ZERO(bmpDIBHdr);
1607
1608 bmpHdr.u16Magic = 0x4d42; /* Magic */
1609 bmpHdr.u32Size = (uint32_t)(sizeof(VIDEORECBMPHDR) + sizeof(VIDEORECBMPDIBHDR) + (w * h * uBytesPerPixel));
1610 bmpHdr.u32OffBits = (uint32_t)(sizeof(VIDEORECBMPHDR) + sizeof(VIDEORECBMPDIBHDR));
1611
1612 bmpDIBHdr.u32Size = sizeof(VIDEORECBMPDIBHDR);
1613 bmpDIBHdr.u32Width = w;
1614 bmpDIBHdr.u32Height = h;
1615 bmpDIBHdr.u16Planes = 1;
1616 bmpDIBHdr.u16BitCount = uBPP;
1617 bmpDIBHdr.u32XPelsPerMeter = 5000;
1618 bmpDIBHdr.u32YPelsPerMeter = 5000;
1619
1620 RTFILE fh;
1621 int rc2 = RTFileOpen(&fh, "/tmp/VideoRecFrame.bmp",
1622 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
1623 if (RT_SUCCESS(rc2))
1624 {
1625 RTFileWrite(fh, &bmpHdr, sizeof(bmpHdr), NULL);
1626 RTFileWrite(fh, &bmpDIBHdr, sizeof(bmpDIBHdr), NULL);
1627 }
1628#endif
1629 Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
1630
1631 /* Do the copy. */
1632 for (unsigned int i = 0; i < h; i++)
1633 {
1634 /* Overflow check. */
1635 Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
1636 Assert(offDst + w * uBytesPerPixel <= pStream->Video.uHeight * pStream->Video.uWidth * uBytesPerPixel);
1637
1638 memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
1639
1640#ifdef VBOX_VIDEOREC_DUMP
1641 if (RT_SUCCESS(rc2))
1642 RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
1643#endif
1644 offSrc += uBytesPerLine;
1645 offDst += pStream->Video.uWidth * uBytesPerPixel;
1646 }
1647
1648#ifdef VBOX_VIDEOREC_DUMP
1649 if (RT_SUCCESS(rc2))
1650 RTFileClose(fh);
1651#endif
1652 pFrame->uTimeStampMs = uTimeStampMs;
1653 pFrame->uWidth = uSrcWidth;
1654 pFrame->uHeight = uSrcHeight;
1655
1656 pStream->Video.fHasVideoData = true;
1657
1658 } while (0);
1659
1660 videoRecStreamUnlock(pStream);
1661
1662 if ( RT_SUCCESS(rc)
1663 && rc != VINF_TRY_AGAIN) /* Only signal the thread if operation was successful. */
1664 {
1665 int rc2 = RTSemEventSignal(pCtx->WaitEvent);
1666 AssertRC(rc2);
1667 }
1668
1669 return rc;
1670}
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