VirtualBox

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

Last change on this file since 73768 was 73505, checked in by vboxsync, 6 years ago

Main,Config.kmk: GCC 8.2.0 fixes

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

© 2023 Oracle
ContactPrivacy policyTerms of Use