VirtualBox

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

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

VideoRec: Added timestamping output files if an output file already exists (never overwrite).

  • 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 71137 2018-02-27 14:47:38Z 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/com/VirtualBox.h>
36
37#include "WebMWriter.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 /** Number of failed attempts to encode the current video frame in a row. */
210 uint16_t cFailedEncodingFrames;
211 } Video;
212} VIDEORECSTREAM, *PVIDEORECSTREAM;
213
214/** Vector of video recording streams. */
215typedef std::vector <PVIDEORECSTREAM> VideoRecStreams;
216
217/**
218 * Structure for keeping a video recording context.
219 */
220typedef struct VIDEORECCONTEXT
221{
222 /** Used recording configuration. */
223 VIDEORECCFG Cfg;
224 /** The current state. */
225 uint32_t enmState;
226 /** Critical section to serialize access. */
227 RTCRITSECT CritSect;
228 /** Semaphore to signal the encoding worker thread. */
229 RTSEMEVENT WaitEvent;
230 /** Whether this conext is enabled or not. */
231 bool fEnabled;
232 /** Shutdown indicator. */
233 bool fShutdown;
234 /** Worker thread. */
235 RTTHREAD Thread;
236 /** Vector of current recording stream contexts. */
237 VideoRecStreams vecStreams;
238 /** Timestamp (in ms) of when recording has been started. */
239 uint64_t tsStartMs;
240#ifdef VBOX_WITH_AUDIO_VIDEOREC
241 struct
242 {
243 bool fHasAudioData;
244 VIDEORECAUDIOFRAME Frame;
245 } Audio;
246#endif
247} VIDEORECCONTEXT, *PVIDEORECCONTEXT;
248
249#ifdef VBOX_VIDEOREC_DUMP
250#pragma pack(push)
251#pragma pack(1)
252typedef struct
253{
254 uint16_t u16Magic;
255 uint32_t u32Size;
256 uint16_t u16Reserved1;
257 uint16_t u16Reserved2;
258 uint32_t u32OffBits;
259} VIDEORECBMPHDR, *PVIDEORECBMPHDR;
260AssertCompileSize(VIDEORECBMPHDR, 14);
261
262typedef struct
263{
264 uint32_t u32Size;
265 uint32_t u32Width;
266 uint32_t u32Height;
267 uint16_t u16Planes;
268 uint16_t u16BitCount;
269 uint32_t u32Compression;
270 uint32_t u32SizeImage;
271 uint32_t u32XPelsPerMeter;
272 uint32_t u32YPelsPerMeter;
273 uint32_t u32ClrUsed;
274 uint32_t u32ClrImportant;
275} VIDEORECBMPDIBHDR, *PVIDEORECBMPDIBHDR;
276AssertCompileSize(VIDEORECBMPDIBHDR, 40);
277
278#pragma pack(pop)
279#endif /* VBOX_VIDEOREC_DUMP */
280
281/**
282 * Iterator class for running through a BGRA32 image buffer and converting
283 * it to RGB.
284 */
285class ColorConvBGRA32Iter
286{
287private:
288 enum { PIX_SIZE = 4 };
289public:
290 ColorConvBGRA32Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
291 {
292 LogFlow(("width = %d height=%d aBuf=%lx\n", aWidth, aHeight, aBuf));
293 mPos = 0;
294 mSize = aWidth * aHeight * PIX_SIZE;
295 mBuf = aBuf;
296 }
297 /**
298 * Convert the next pixel to RGB.
299 * @returns true on success, false if we have reached the end of the buffer
300 * @param aRed where to store the red value
301 * @param aGreen where to store the green value
302 * @param aBlue where to store the blue value
303 */
304 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
305 {
306 bool rc = false;
307 if (mPos + PIX_SIZE <= mSize)
308 {
309 *aRed = mBuf[mPos + 2];
310 *aGreen = mBuf[mPos + 1];
311 *aBlue = mBuf[mPos ];
312 mPos += PIX_SIZE;
313 rc = true;
314 }
315 return rc;
316 }
317
318 /**
319 * Skip forward by a certain number of pixels
320 * @param aPixels how many pixels to skip
321 */
322 void skip(unsigned aPixels)
323 {
324 mPos += PIX_SIZE * aPixels;
325 }
326private:
327 /** Size of the picture buffer */
328 unsigned mSize;
329 /** Current position in the picture buffer */
330 unsigned mPos;
331 /** Address of the picture buffer */
332 uint8_t *mBuf;
333};
334
335/**
336 * Iterator class for running through an BGR24 image buffer and converting
337 * it to RGB.
338 */
339class ColorConvBGR24Iter
340{
341private:
342 enum { PIX_SIZE = 3 };
343public:
344 ColorConvBGR24Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
345 {
346 mPos = 0;
347 mSize = aWidth * aHeight * PIX_SIZE;
348 mBuf = aBuf;
349 }
350 /**
351 * Convert the next pixel to RGB.
352 * @returns true on success, false if we have reached the end of the buffer
353 * @param aRed where to store the red value
354 * @param aGreen where to store the green value
355 * @param aBlue where to store the blue value
356 */
357 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
358 {
359 bool rc = false;
360 if (mPos + PIX_SIZE <= mSize)
361 {
362 *aRed = mBuf[mPos + 2];
363 *aGreen = mBuf[mPos + 1];
364 *aBlue = mBuf[mPos ];
365 mPos += PIX_SIZE;
366 rc = true;
367 }
368 return rc;
369 }
370
371 /**
372 * Skip forward by a certain number of pixels
373 * @param aPixels how many pixels to skip
374 */
375 void skip(unsigned aPixels)
376 {
377 mPos += PIX_SIZE * aPixels;
378 }
379private:
380 /** Size of the picture buffer */
381 unsigned mSize;
382 /** Current position in the picture buffer */
383 unsigned mPos;
384 /** Address of the picture buffer */
385 uint8_t *mBuf;
386};
387
388/**
389 * Iterator class for running through an BGR565 image buffer and converting
390 * it to RGB.
391 */
392class ColorConvBGR565Iter
393{
394private:
395 enum { PIX_SIZE = 2 };
396public:
397 ColorConvBGR565Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
398 {
399 mPos = 0;
400 mSize = aWidth * aHeight * PIX_SIZE;
401 mBuf = aBuf;
402 }
403 /**
404 * Convert the next pixel to RGB.
405 * @returns true on success, false if we have reached the end of the buffer
406 * @param aRed where to store the red value
407 * @param aGreen where to store the green value
408 * @param aBlue where to store the blue value
409 */
410 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
411 {
412 bool rc = false;
413 if (mPos + PIX_SIZE <= mSize)
414 {
415 unsigned uFull = (((unsigned) mBuf[mPos + 1]) << 8)
416 | ((unsigned) mBuf[mPos]);
417 *aRed = (uFull >> 8) & ~7;
418 *aGreen = (uFull >> 3) & ~3 & 0xff;
419 *aBlue = (uFull << 3) & ~7 & 0xff;
420 mPos += PIX_SIZE;
421 rc = true;
422 }
423 return rc;
424 }
425
426 /**
427 * Skip forward by a certain number of pixels
428 * @param aPixels how many pixels to skip
429 */
430 void skip(unsigned aPixels)
431 {
432 mPos += PIX_SIZE * aPixels;
433 }
434private:
435 /** Size of the picture buffer */
436 unsigned mSize;
437 /** Current position in the picture buffer */
438 unsigned mPos;
439 /** Address of the picture buffer */
440 uint8_t *mBuf;
441};
442
443/**
444 * Convert an image to YUV420p format.
445 *
446 * @return true on success, false on failure.
447 * @param aDstBuf The destination image buffer.
448 * @param aDstWidth Width (in pixel) of destination buffer.
449 * @param aDstHeight Height (in pixel) of destination buffer.
450 * @param aSrcBuf The source image buffer.
451 * @param aSrcWidth Width (in pixel) of source buffer.
452 * @param aSrcHeight Height (in pixel) of source buffer.
453 */
454template <class T>
455inline bool colorConvWriteYUV420p(uint8_t *aDstBuf, unsigned aDstWidth, unsigned aDstHeight,
456 uint8_t *aSrcBuf, unsigned aSrcWidth, unsigned aSrcHeight)
457{
458 RT_NOREF(aDstWidth, aDstHeight);
459
460 AssertReturn(!(aSrcWidth & 1), false);
461 AssertReturn(!(aSrcHeight & 1), false);
462
463 bool fRc = true;
464 T iter1(aSrcWidth, aSrcHeight, aSrcBuf);
465 T iter2 = iter1;
466 iter2.skip(aSrcWidth);
467 unsigned cPixels = aSrcWidth * aSrcHeight;
468 unsigned offY = 0;
469 unsigned offU = cPixels;
470 unsigned offV = cPixels + cPixels / 4;
471 unsigned const cyHalf = aSrcHeight / 2;
472 unsigned const cxHalf = aSrcWidth / 2;
473 for (unsigned i = 0; i < cyHalf && fRc; ++i)
474 {
475 for (unsigned j = 0; j < cxHalf; ++j)
476 {
477 unsigned red, green, blue;
478 fRc = iter1.getRGB(&red, &green, &blue);
479 AssertReturn(fRc, false);
480 aDstBuf[offY] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
481 unsigned u = (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
482 unsigned v = (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
483
484 fRc = iter1.getRGB(&red, &green, &blue);
485 AssertReturn(fRc, false);
486 aDstBuf[offY + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
487 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
488 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
489
490 fRc = iter2.getRGB(&red, &green, &blue);
491 AssertReturn(fRc, false);
492 aDstBuf[offY + aSrcWidth] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
493 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
494 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
495
496 fRc = iter2.getRGB(&red, &green, &blue);
497 AssertReturn(fRc, false);
498 aDstBuf[offY + aSrcWidth + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
499 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
500 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
501
502 aDstBuf[offU] = u;
503 aDstBuf[offV] = v;
504 offY += 2;
505 ++offU;
506 ++offV;
507 }
508
509 iter1.skip(aSrcWidth);
510 iter2.skip(aSrcWidth);
511 offY += aSrcWidth;
512 }
513
514 return true;
515}
516
517/**
518 * Convert an image to RGB24 format
519 * @returns true on success, false on failure
520 * @param aWidth width of image
521 * @param aHeight height of image
522 * @param aDestBuf an allocated memory buffer large enough to hold the
523 * destination image (i.e. width * height * 12bits)
524 * @param aSrcBuf the source image as an array of bytes
525 */
526template <class T>
527inline bool colorConvWriteRGB24(unsigned aWidth, unsigned aHeight,
528 uint8_t *aDestBuf, uint8_t *aSrcBuf)
529{
530 enum { PIX_SIZE = 3 };
531 bool rc = true;
532 AssertReturn(0 == (aWidth & 1), false);
533 AssertReturn(0 == (aHeight & 1), false);
534 T iter(aWidth, aHeight, aSrcBuf);
535 unsigned cPixels = aWidth * aHeight;
536 for (unsigned i = 0; i < cPixels && rc; ++i)
537 {
538 unsigned red, green, blue;
539 rc = iter.getRGB(&red, &green, &blue);
540 if (rc)
541 {
542 aDestBuf[i * PIX_SIZE ] = red;
543 aDestBuf[i * PIX_SIZE + 1] = green;
544 aDestBuf[i * PIX_SIZE + 2] = blue;
545 }
546 }
547 return rc;
548}
549
550/**
551 * Worker thread for all streams of a video recording context.
552 *
553 * Does RGB/YUV conversion and encoding.
554 */
555static DECLCALLBACK(int) videoRecThread(RTTHREAD hThreadSelf, void *pvUser)
556{
557 PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)pvUser;
558
559 /* Signal that we're up and rockin'. */
560 RTThreadUserSignal(hThreadSelf);
561
562 for (;;)
563 {
564 int rc = RTSemEventWait(pCtx->WaitEvent, RT_INDEFINITE_WAIT);
565 AssertRCBreak(rc);
566
567 if (ASMAtomicReadBool(&pCtx->fShutdown))
568 break;
569
570#ifdef VBOX_WITH_AUDIO_VIDEOREC
571 VIDEORECAUDIOFRAME audioFrame;
572 RT_ZERO(audioFrame);
573
574 int rc2 = RTCritSectEnter(&pCtx->CritSect);
575 AssertRC(rc2);
576
577 const bool fEncodeAudio = pCtx->Audio.fHasAudioData;
578 if (fEncodeAudio)
579 {
580 /*
581 * Every recording stream needs to get the same audio data at a certain point in time.
582 * Do the multiplexing here to not block EMT for too long.
583 *
584 * For now just doing a simple copy of the current audio frame should be good enough.
585 */
586 memcpy(&audioFrame, &pCtx->Audio.Frame, sizeof(VIDEORECAUDIOFRAME));
587
588 pCtx->Audio.fHasAudioData = false;
589 }
590
591 rc2 = RTCritSectLeave(&pCtx->CritSect);
592 AssertRC(rc2);
593#endif
594
595 /** @todo r=andy This is inefficient -- as we already wake up this thread
596 * for every screen from Main, we here go again (on every wake up) through
597 * all screens. */
598 for (VideoRecStreams::iterator it = pCtx->vecStreams.begin(); it != pCtx->vecStreams.end(); it++)
599 {
600 PVIDEORECSTREAM pStream = (*it);
601
602 videoRecStreamLock(pStream);
603
604 if (!pStream->fEnabled)
605 {
606 videoRecStreamUnlock(pStream);
607 continue;
608 }
609
610 PVIDEORECVIDEOFRAME pVideoFrame = &pStream->Video.Frame;
611 const bool fEncodeVideo = pStream->Video.fHasVideoData;
612
613 if (fEncodeVideo)
614 {
615 rc = videoRecRGBToYUV(pVideoFrame->uPixelFormat,
616 /* Destination */
617 pStream->Video.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
618 /* Source */
619 pVideoFrame->pu8RGBBuf, pStream->Video.uWidth, pStream->Video.uHeight);
620 if (RT_SUCCESS(rc))
621 rc = videoRecEncodeAndWrite(pStream, pVideoFrame);
622
623 pStream->Video.fHasVideoData = false;
624 }
625
626 videoRecStreamUnlock(pStream);
627
628 if (RT_FAILURE(rc))
629 {
630 static unsigned s_cErrEncVideo = 0;
631 if (s_cErrEncVideo < 32)
632 {
633 LogRel(("VideoRec: Error %Rrc encoding / writing video frame\n", rc));
634 s_cErrEncVideo++;
635 }
636 }
637
638#ifdef VBOX_WITH_AUDIO_VIDEOREC
639 /* Each (enabled) screen has to get the same audio data. */
640 if (fEncodeAudio)
641 {
642 Assert(audioFrame.cbBuf);
643 Assert(audioFrame.cbBuf <= _64K); /** @todo Fix. */
644
645 WebMWriter::BlockData_Opus blockData = { audioFrame.abBuf, audioFrame.cbBuf, audioFrame.uTimeStampMs };
646 rc = pStream->File.pWEBM->WriteBlock(pStream->uTrackAudio, &blockData, sizeof(blockData));
647 if (RT_FAILURE(rc))
648 {
649 static unsigned s_cErrEncAudio = 0;
650 if (s_cErrEncAudio < 32)
651 {
652 LogRel(("VideoRec: Error %Rrc encoding audio frame\n", rc));
653 s_cErrEncAudio++;
654 }
655 }
656 }
657#endif
658 }
659
660 /* Keep going in case of errors. */
661
662 } /* for */
663
664 return VINF_SUCCESS;
665}
666
667/**
668 * Creates a video recording context.
669 *
670 * @returns IPRT status code.
671 * @param cScreens Number of screens to create context for.
672 * @param pVideoRecCfg Pointer to video recording configuration to use.
673 * @param ppCtx Pointer to created video recording context on success.
674 */
675int VideoRecContextCreate(uint32_t cScreens, PVIDEORECCFG pVideoRecCfg, PVIDEORECCONTEXT *ppCtx)
676{
677 AssertReturn(cScreens, VERR_INVALID_PARAMETER);
678 AssertPtrReturn(pVideoRecCfg, VERR_INVALID_POINTER);
679 AssertPtrReturn(ppCtx, VERR_INVALID_POINTER);
680
681 PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)RTMemAllocZ(sizeof(VIDEORECCONTEXT));
682 if (!pCtx)
683 return VERR_NO_MEMORY;
684
685 int rc = RTCritSectInit(&pCtx->CritSect);
686 if (RT_FAILURE(rc))
687 {
688 RTMemFree(pCtx);
689 return rc;
690 }
691
692 for (uint32_t uScreen = 0; uScreen < cScreens; uScreen++)
693 {
694 PVIDEORECSTREAM pStream = (PVIDEORECSTREAM)RTMemAllocZ(sizeof(VIDEORECSTREAM));
695 if (!pStream)
696 {
697 rc = VERR_NO_MEMORY;
698 break;
699 }
700
701 rc = RTCritSectInit(&pStream->CritSect);
702 if (RT_FAILURE(rc))
703 break;
704
705 try
706 {
707 pStream->uScreenID = uScreen;
708
709 pCtx->vecStreams.push_back(pStream);
710
711 pStream->File.pWEBM = new WebMWriter();
712 }
713 catch (std::bad_alloc)
714 {
715 rc = VERR_NO_MEMORY;
716 break;
717 }
718 }
719
720 if (RT_SUCCESS(rc))
721 {
722 pCtx->tsStartMs = RTTimeMilliTS();
723 pCtx->enmState = VIDEORECSTS_UNINITIALIZED;
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->fEnabled = 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 /* First, disable the context. */
768 ASMAtomicWriteBool(&pCtx->fEnabled, false);
769
770 if (pCtx->enmState == VIDEORECSTS_INITIALIZED)
771 {
772 /* Set shutdown indicator. */
773 ASMAtomicWriteBool(&pCtx->fShutdown, true);
774
775 /* Signal the thread. */
776 RTSemEventSignal(pCtx->WaitEvent);
777
778 int rc = RTThreadWait(pCtx->Thread, 10 * 1000 /* 10s timeout */, NULL);
779 if (RT_FAILURE(rc))
780 return rc;
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 AssertPtrReturn(pszAbsPath, VERR_INVALID_PARAMETER);
949
950 char *pszSuff = RTPathSuffix(pszAbsPath);
951 if (!pszSuff)
952 pszSuff = RTStrDup(".webm");
953
954 if (!pszSuff)
955 {
956 RTStrFree(pszAbsPath);
957 return VERR_NO_MEMORY;
958 }
959
960 char *pszFile = NULL;
961
962 int rc;
963 if (pCfg->aScreens.size() > 1)
964 rc = RTStrAPrintf(&pszFile, "%s-%u%s", pszAbsPath, pStream->uScreenID + 1, pszSuff);
965 else
966 rc = RTStrAPrintf(&pszFile, "%s%s", pszAbsPath, pszSuff);
967
968 if (RT_SUCCESS(rc))
969 {
970 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
971
972 /* Play safe: the file must not exist, overwriting is potentially
973 * hazardous as nothing prevents the user from picking a file name of some
974 * other important file, causing unintentional data loss. */
975 fOpen |= RTFILE_O_CREATE;
976
977 RTFILE hFile;
978 rc = RTFileOpen(&hFile, pszFile, fOpen);
979 if (rc == VERR_ALREADY_EXISTS)
980 {
981 RTStrFree(pszFile);
982 pszFile = NULL;
983
984 RTTIMESPEC ts;
985 RTTimeNow(&ts);
986 RTTIME time;
987 RTTimeExplode(&time, &ts);
988
989 if (pCfg->aScreens.size() > 1)
990 rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ-%u%s",
991 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
992 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
993 pStream->uScreenID + 1, pszSuff);
994 else
995 rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ%s",
996 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
997 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
998 pszSuff);
999
1000 if (RT_SUCCESS(rc))
1001 rc = RTFileOpen(&hFile, pszFile, fOpen);
1002 }
1003
1004 if (RT_SUCCESS(rc))
1005 {
1006 pStream->enmDst = VIDEORECDEST_FILE;
1007 pStream->File.hFile = hFile;
1008 pStream->File.pszFile = pszFile; /* Assign allocated string to our stream's config. */
1009 }
1010 }
1011
1012 RTStrFree(pszSuff);
1013 RTStrFree(pszAbsPath);
1014
1015 if (RT_FAILURE(rc))
1016 {
1017 LogRel(("VideoRec: Failed to open file '%s' for screen %RU32, rc=%Rrc\n",
1018 pszFile ? pszFile : "<Unnamed>", pStream->uScreenID, rc));
1019 RTStrFree(pszFile);
1020 }
1021
1022 return rc;
1023}
1024
1025/**
1026 * Closes a recording stream's file again.
1027 *
1028 * @returns IPRT status code.
1029 * @param pStream Recording stream to close file for.
1030 */
1031static int videoRecStreamCloseFile(PVIDEORECSTREAM pStream)
1032{
1033 Assert(pStream->enmDst == VIDEORECDEST_FILE);
1034
1035 pStream->enmDst = VIDEORECDEST_INVALID;
1036
1037 AssertPtr(pStream->File.pszFile);
1038
1039 if (RTFileIsValid(pStream->File.hFile))
1040 {
1041 RTFileClose(pStream->File.hFile);
1042 LogRel(("VideoRec: Closed file '%s'\n", pStream->File.pszFile));
1043 }
1044
1045 RTStrFree(pStream->File.pszFile);
1046 pStream->File.pszFile = NULL;
1047
1048 return VINF_SUCCESS;
1049}
1050
1051/**
1052 * VideoRec utility function to initialize video recording context.
1053 *
1054 * @returns IPRT status code.
1055 * @param pCtx Pointer to video recording context.
1056 * @param uScreen Screen number to record.
1057 */
1058int VideoRecStreamInit(PVIDEORECCONTEXT pCtx, uint32_t uScreen)
1059{
1060 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1061
1062 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1063 if (!pStream)
1064 return VERR_NOT_FOUND;
1065
1066 int rc = videoRecStreamOpenFile(pStream, &pCtx->Cfg);
1067 if (RT_FAILURE(rc))
1068 return rc;
1069
1070 PVIDEORECCFG pCfg = &pCtx->Cfg;
1071
1072 pStream->pCtx = pCtx;
1073
1074 /** @todo Make the following parameters configurable on a per-stream basis? */
1075 pStream->Video.uWidth = pCfg->Video.uWidth;
1076 pStream->Video.uHeight = pCfg->Video.uHeight;
1077 pStream->Video.cFailedEncodingFrames = 0;
1078
1079#ifndef VBOX_VIDEOREC_WITH_QUEUE
1080 /* When not using a queue, we only use one frame per stream at once.
1081 * So do the initialization here. */
1082 PVIDEORECVIDEOFRAME pFrame = &pStream->Video.Frame;
1083
1084 const size_t cbRGBBuf = pStream->Video.uWidth
1085 * pStream->Video.uHeight
1086 * 4 /* 32 BPP maximum */;
1087 AssertReturn(cbRGBBuf, VERR_INVALID_PARAMETER);
1088
1089 pFrame->pu8RGBBuf = (uint8_t *)RTMemAllocZ(cbRGBBuf);
1090 AssertReturn(pFrame->pu8RGBBuf, VERR_NO_MEMORY);
1091 pFrame->cbRGBBuf = cbRGBBuf;
1092#endif
1093
1094 PVIDEORECVIDEOCODEC pVC = &pStream->Video.Codec;
1095
1096#ifdef VBOX_WITH_LIBVPX
1097 vpx_codec_err_t rcv = vpx_codec_enc_config_default(DEFAULTCODEC, &pVC->VPX.Cfg, 0);
1098 if (rcv != VPX_CODEC_OK)
1099 {
1100 LogRel(("VideoRec: Failed to get default configuration for VPX codec: %s\n", vpx_codec_err_to_string(rcv)));
1101 return VERR_INVALID_PARAMETER;
1102 }
1103#endif
1104
1105 pStream->Video.uDelayMs = 1000 / pCfg->Video.uFPS;
1106
1107 switch (pStream->enmDst)
1108 {
1109 case VIDEORECDEST_FILE:
1110 {
1111 rc = pStream->File.pWEBM->OpenEx(pStream->File.pszFile, &pStream->File.hFile,
1112#ifdef VBOX_WITH_AUDIO_VIDEOREC
1113 pCfg->Audio.fEnabled ? WebMWriter::AudioCodec_Opus : WebMWriter::AudioCodec_None,
1114#else
1115 WebMWriter::AudioCodec_None,
1116#endif
1117 pCfg->Video.fEnabled ? WebMWriter::VideoCodec_VP8 : WebMWriter::VideoCodec_None);
1118 if (RT_FAILURE(rc))
1119 {
1120 LogRel(("VideoRec: Failed to create the capture output file '%s' (%Rrc)\n", pStream->File.pszFile, rc));
1121 break;
1122 }
1123
1124 const char *pszFile = pStream->File.pszFile;
1125
1126 if (pCfg->Video.fEnabled)
1127 {
1128 rc = pStream->File.pWEBM->AddVideoTrack(pCfg->Video.uWidth, pCfg->Video.uHeight, pCfg->Video.uFPS,
1129 &pStream->uTrackVideo);
1130 if (RT_FAILURE(rc))
1131 {
1132 LogRel(("VideoRec: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, rc));
1133 break;
1134 }
1135
1136 LogRel(("VideoRec: Recording screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS\n",
1137 uScreen, pCfg->Video.uWidth, pCfg->Video.uHeight, pCfg->Video.uRate, pCfg->Video.uFPS));
1138 }
1139
1140#ifdef VBOX_WITH_AUDIO_VIDEOREC
1141 if (pCfg->Audio.fEnabled)
1142 {
1143 rc = pStream->File.pWEBM->AddAudioTrack(pCfg->Audio.uHz, pCfg->Audio.cChannels, pCfg->Audio.cBits,
1144 &pStream->uTrackAudio);
1145 if (RT_FAILURE(rc))
1146 {
1147 LogRel(("VideoRec: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, rc));
1148 break;
1149 }
1150
1151 LogRel(("VideoRec: Recording audio in %RU16Hz, %RU8 bit, %RU8 %s\n",
1152 pCfg->Audio.uHz, pCfg->Audio.cBits, pCfg->Audio.cChannels, pCfg->Audio.cChannels ? "channel" : "channels"));
1153 }
1154#endif
1155
1156 if ( pCfg->Video.fEnabled
1157#ifdef VBOX_WITH_AUDIO_VIDEOREC
1158 || pCfg->Audio.fEnabled
1159#endif
1160 )
1161 {
1162 char szWhat[32] = { 0 };
1163 if (pCfg->Video.fEnabled)
1164 RTStrCat(szWhat, sizeof(szWhat), "video");
1165#ifdef VBOX_WITH_AUDIO_VIDEOREC
1166 if (pCfg->Audio.fEnabled)
1167 {
1168 if (pCfg->Video.fEnabled)
1169 RTStrCat(szWhat, sizeof(szWhat), " + ");
1170 RTStrCat(szWhat, sizeof(szWhat), "audio");
1171 }
1172#endif
1173 LogRel(("VideoRec: Recording %s to '%s'\n", szWhat, pszFile));
1174 }
1175
1176 break;
1177 }
1178
1179 default:
1180 AssertFailed(); /* Should never happen. */
1181 rc = VERR_NOT_IMPLEMENTED;
1182 break;
1183 }
1184
1185 if (RT_FAILURE(rc))
1186 return rc;
1187
1188#ifdef VBOX_WITH_LIBVPX
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, DEFAULTCODEC, &pVC->VPX.Cfg, 0);
1203 if (rcv != VPX_CODEC_OK)
1204 {
1205 LogRel(("VideoRec: Failed to initialize VP8 encoder: %s\n", vpx_codec_err_to_string(rcv)));
1206 return VERR_INVALID_PARAMETER;
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 VideoRecGetEnabled(PVIDEORECCFG pCfg)
1230{
1231 if ( !pCfg
1232 || !pCfg->fEnabled)
1233 {
1234 return VIDEORECFEATURE_NONE;
1235 }
1236
1237 VIDEORECFEATURES fFeatures = VIDEORECFEATURE_NONE;
1238
1239 if (pCfg->Video.fEnabled)
1240 fFeatures |= VIDEORECFEATURE_VIDEO;
1241
1242#ifdef VBOX_WITH_AUDIO_VIDEOREC
1243 if (pCfg->Audio.fEnabled)
1244 fFeatures |= VIDEORECFEATURE_AUDIO;
1245#endif
1246
1247 return fFeatures;
1248}
1249
1250/**
1251 * Checks if recording engine is ready to accept a new frame for the given screen.
1252 *
1253 * @returns true if recording engine is ready.
1254 * @param pCtx Pointer to video recording context.
1255 * @param uScreen Screen ID.
1256 * @param uTimeStampMs Current time stamp (in ms).
1257 */
1258bool VideoRecIsReady(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t uTimeStampMs)
1259{
1260 AssertPtrReturn(pCtx, false);
1261
1262 if (ASMAtomicReadU32(&pCtx->enmState) != VIDEORECSTS_INITIALIZED)
1263 return false;
1264
1265 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1266 if ( !pStream
1267 || !pStream->fEnabled)
1268 {
1269 return false;
1270 }
1271
1272 PVIDEORECVIDEOFRAME pLastFrame = &pStream->Video.Frame;
1273
1274 if (uTimeStampMs < pLastFrame->uTimeStampMs + pStream->Video.uDelayMs)
1275 return false;
1276
1277 return true;
1278}
1279
1280/**
1281 * Returns whether video recording for a given recording context is active or not.
1282 *
1283 * @returns true if active, false if not.
1284 * @param pCtx Pointer to video recording context.
1285 */
1286bool VideoRecIsActive(PVIDEORECCONTEXT pCtx)
1287{
1288 if (!pCtx)
1289 return false;
1290
1291 return ASMAtomicReadBool(&pCtx->fEnabled);
1292}
1293
1294/**
1295 * Checks if a specified limit for recording has been reached.
1296 *
1297 * @returns true if any limit has been reached.
1298 * @param pCtx Pointer to video recording context.
1299 * @param uScreen Screen ID.
1300 * @param tsNowMs Current time stamp (in ms).
1301 */
1302bool VideoRecIsLimitReached(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t tsNowMs)
1303{
1304 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1305 if ( !pStream
1306 || !pStream->fEnabled)
1307 {
1308 return false;
1309 }
1310
1311 const PVIDEORECCFG pCfg = &pCtx->Cfg;
1312
1313 if ( pCfg->uMaxTimeS
1314 && tsNowMs >= pCtx->tsStartMs + (pCfg->uMaxTimeS * 1000))
1315 {
1316 return true;
1317 }
1318
1319 if (pCfg->enmDst == VIDEORECDEST_FILE)
1320 {
1321
1322 if (pCfg->File.uMaxSizeMB)
1323 {
1324 uint64_t sizeInMB = pStream->File.pWEBM->GetFileSize() / (1024 * 1024);
1325 if(sizeInMB >= pCfg->File.uMaxSizeMB)
1326 return true;
1327 }
1328
1329 /* Check for available free disk space */
1330 if ( pStream->File.pWEBM
1331 && pStream->File.pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
1332 {
1333 LogRel(("VideoRec: Not enough free storage space available, stopping video capture\n"));
1334 return true;
1335 }
1336 }
1337
1338 return false;
1339}
1340
1341/**
1342 * Encodes the source image and write the encoded image to the stream's destination.
1343 *
1344 * @returns IPRT status code.
1345 * @param pStream Stream to encode and submit to.
1346 * @param pFrame Frame to encode and submit.
1347 */
1348static int videoRecEncodeAndWrite(PVIDEORECSTREAM pStream, PVIDEORECVIDEOFRAME pFrame)
1349{
1350 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1351 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
1352
1353 int rc;
1354
1355 AssertPtr(pStream->pCtx);
1356 PVIDEORECCFG pCfg = &pStream->pCtx->Cfg;
1357 PVIDEORECVIDEOCODEC pVC = &pStream->Video.Codec;
1358#ifdef VBOX_WITH_LIBVPX
1359 /* Presentation Time Stamp (PTS). */
1360 vpx_codec_pts_t pts = pFrame->uTimeStampMs;
1361 vpx_codec_err_t rcv = vpx_codec_encode(&pVC->VPX.Ctx,
1362 &pVC->VPX.RawImage,
1363 pts /* Time stamp */,
1364 pStream->Video.uDelayMs /* How long to show this frame */,
1365 0 /* Flags */,
1366 pCfg->Video.Codec.VPX.uEncoderDeadline /* Quality setting */);
1367 if (rcv != VPX_CODEC_OK)
1368 {
1369 if (pStream->Video.cFailedEncodingFrames++ < 64)
1370 {
1371 LogRel(("VideoRec: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
1372 return VERR_GENERAL_FAILURE;
1373 }
1374 }
1375
1376 pStream->Video.cFailedEncodingFrames = 0;
1377
1378 vpx_codec_iter_t iter = NULL;
1379 rc = VERR_NO_DATA;
1380 for (;;)
1381 {
1382 const vpx_codec_cx_pkt_t *pPacket = vpx_codec_get_cx_data(&pVC->VPX.Ctx, &iter);
1383 if (!pPacket)
1384 break;
1385
1386 switch (pPacket->kind)
1387 {
1388 case VPX_CODEC_CX_FRAME_PKT:
1389 {
1390 WebMWriter::BlockData_VP8 blockData = { &pVC->VPX.Cfg, pPacket };
1391 rc = pStream->File.pWEBM->WriteBlock(pStream->uTrackVideo, &blockData, sizeof(blockData));
1392 break;
1393 }
1394
1395 default:
1396 AssertFailed();
1397 LogFunc(("Unexpected video packet type %ld\n", pPacket->kind));
1398 break;
1399 }
1400 }
1401#else
1402 RT_NOREF(pStream);
1403 rc = VERR_NOT_SUPPORTED;
1404#endif /* VBOX_WITH_LIBVPX */
1405 return rc;
1406}
1407
1408/**
1409 * Converts a RGB to YUV buffer.
1410 *
1411 * @returns IPRT status code.
1412 * TODO
1413 */
1414static int videoRecRGBToYUV(uint32_t uPixelFormat,
1415 uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
1416 uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight)
1417{
1418 switch (uPixelFormat)
1419 {
1420 case VIDEORECPIXELFMT_RGB32:
1421 if (!colorConvWriteYUV420p<ColorConvBGRA32Iter>(paDst, uDstWidth, uDstHeight,
1422 paSrc, uSrcWidth, uSrcHeight))
1423 return VERR_INVALID_PARAMETER;
1424 break;
1425 case VIDEORECPIXELFMT_RGB24:
1426 if (!colorConvWriteYUV420p<ColorConvBGR24Iter>(paDst, uDstWidth, uDstHeight,
1427 paSrc, uSrcWidth, uSrcHeight))
1428 return VERR_INVALID_PARAMETER;
1429 break;
1430 case VIDEORECPIXELFMT_RGB565:
1431 if (!colorConvWriteYUV420p<ColorConvBGR565Iter>(paDst, uDstWidth, uDstHeight,
1432 paSrc, uSrcWidth, uSrcHeight))
1433 return VERR_INVALID_PARAMETER;
1434 break;
1435 default:
1436 AssertFailed();
1437 return VERR_NOT_SUPPORTED;
1438 }
1439 return VINF_SUCCESS;
1440}
1441
1442/**
1443 * Sends an audio frame to the video encoding thread.
1444 *
1445 * @thread EMT
1446 *
1447 * @returns IPRT status code.
1448 * @param pCtx Pointer to the video recording context.
1449 * @param pvData Audio frame data to send.
1450 * @param cbData Size (in bytes) of audio frame data.
1451 * @param uTimeStampMs Time stamp (in ms) of audio playback.
1452 */
1453int VideoRecSendAudioFrame(PVIDEORECCONTEXT pCtx, const void *pvData, size_t cbData, uint64_t uTimeStampMs)
1454{
1455#ifdef VBOX_WITH_AUDIO_VIDEOREC
1456 AssertReturn(cbData <= _64K, VERR_INVALID_PARAMETER);
1457
1458 int rc = RTCritSectEnter(&pCtx->CritSect);
1459 if (RT_FAILURE(rc))
1460 return rc;
1461
1462 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
1463 *
1464 * The multiplexing is needed to supply all recorded (enabled) screens with the same
1465 * audio data at the same given point in time.
1466 */
1467 PVIDEORECAUDIOFRAME pFrame = &pCtx->Audio.Frame;
1468
1469 memcpy(pFrame->abBuf, pvData, RT_MIN(_64K /** @todo Fix! */, cbData));
1470
1471 pFrame->cbBuf = cbData;
1472 pFrame->uTimeStampMs = uTimeStampMs;
1473
1474 pCtx->Audio.fHasAudioData = true;
1475
1476 rc = RTCritSectLeave(&pCtx->CritSect);
1477 if (RT_SUCCESS(rc))
1478 rc = RTSemEventSignal(pCtx->WaitEvent);
1479
1480 return rc;
1481#else
1482 RT_NOREF(pCtx, pvData, cbData, uTimeStampMs);
1483 return VINF_SUCCESS;
1484#endif
1485}
1486
1487/**
1488 * Copies a source video frame to the intermediate RGB buffer.
1489 * This function is executed only once per time.
1490 *
1491 * @thread EMT
1492 *
1493 * @returns IPRT status code.
1494 * @param pCtx Pointer to the video recording context.
1495 * @param uScreen Screen number.
1496 * @param x Starting x coordinate of the video frame.
1497 * @param y Starting y coordinate of the video frame.
1498 * @param uPixelFormat Pixel format.
1499 * @param uBPP Bits Per Pixel (BPP).
1500 * @param uBytesPerLine Bytes per scanline.
1501 * @param uSrcWidth Width of the video frame.
1502 * @param uSrcHeight Height of the video frame.
1503 * @param puSrcData Pointer to video frame data.
1504 * @param uTimeStampMs Time stamp (in ms).
1505 */
1506int VideoRecSendVideoFrame(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint32_t x, uint32_t y,
1507 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
1508 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
1509 uint64_t uTimeStampMs)
1510{
1511 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1512 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
1513 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
1514 AssertReturn(puSrcData, VERR_INVALID_POINTER);
1515
1516 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1517 if (!pStream)
1518 return VERR_NOT_FOUND;
1519
1520 videoRecStreamLock(pStream);
1521
1522 int rc = VINF_SUCCESS;
1523
1524 do
1525 {
1526 if (!pStream->fEnabled)
1527 {
1528 rc = VINF_TRY_AGAIN; /* Not (yet) enabled. */
1529 break;
1530 }
1531
1532 if (uTimeStampMs < pStream->Video.uLastTimeStampMs + pStream->Video.uDelayMs)
1533 {
1534 rc = VINF_TRY_AGAIN; /* Respect maximum frames per second. */
1535 break;
1536 }
1537
1538 pStream->Video.uLastTimeStampMs = uTimeStampMs;
1539
1540 int xDiff = ((int)pStream->Video.uWidth - (int)uSrcWidth) / 2;
1541 uint32_t w = uSrcWidth;
1542 if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
1543 {
1544 rc = VERR_INVALID_PARAMETER;
1545 break;
1546 }
1547
1548 uint32_t destX;
1549 if ((int)x < -xDiff)
1550 {
1551 w += xDiff + x;
1552 x = -xDiff;
1553 destX = 0;
1554 }
1555 else
1556 destX = x + xDiff;
1557
1558 uint32_t h = uSrcHeight;
1559 int yDiff = ((int)pStream->Video.uHeight - (int)uSrcHeight) / 2;
1560 if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
1561 {
1562 rc = VERR_INVALID_PARAMETER;
1563 break;
1564 }
1565
1566 uint32_t destY;
1567 if ((int)y < -yDiff)
1568 {
1569 h += yDiff + (int)y;
1570 y = -yDiff;
1571 destY = 0;
1572 }
1573 else
1574 destY = y + yDiff;
1575
1576 if ( destX > pStream->Video.uWidth
1577 || destY > pStream->Video.uHeight)
1578 {
1579 rc = VERR_INVALID_PARAMETER; /* Nothing visible. */
1580 break;
1581 }
1582
1583 if (destX + w > pStream->Video.uWidth)
1584 w = pStream->Video.uWidth - destX;
1585
1586 if (destY + h > pStream->Video.uHeight)
1587 h = pStream->Video.uHeight - destY;
1588
1589#ifdef VBOX_VIDEOREC_WITH_QUEUE
1590# error "Implement me!"
1591#else
1592 PVIDEORECVIDEOFRAME pFrame = &pStream->Video.Frame;
1593#endif
1594 /* Calculate bytes per pixel and set pixel format. */
1595 const unsigned uBytesPerPixel = uBPP / 8;
1596 if (uPixelFormat == BitmapFormat_BGR)
1597 {
1598 switch (uBPP)
1599 {
1600 case 32:
1601 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB32;
1602 break;
1603 case 24:
1604 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB24;
1605 break;
1606 case 16:
1607 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB565;
1608 break;
1609 default:
1610 AssertMsgFailed(("Unknown color depth (%RU32)\n", uBPP));
1611 break;
1612 }
1613 }
1614 else
1615 AssertMsgFailed(("Unknown pixel format (%RU32)\n", uPixelFormat));
1616
1617#ifndef VBOX_VIDEOREC_WITH_QUEUE
1618 /* If we don't use a queue then we have to compare the dimensions
1619 * of the current frame with the previous frame:
1620 *
1621 * If it's smaller than before then clear the entire buffer to prevent artifacts
1622 * from the previous frame. */
1623 if ( uSrcWidth < pFrame->uWidth
1624 || uSrcHeight < pFrame->uHeight)
1625 {
1626 /** @todo r=andy Only clear dirty areas. */
1627 RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
1628 }
1629#endif
1630 /* Calculate start offset in source and destination buffers. */
1631 uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
1632 uint32_t offDst = (destY * pStream->Video.uWidth + destX) * uBytesPerPixel;
1633
1634#ifdef VBOX_VIDEOREC_DUMP
1635 VIDEORECBMPHDR bmpHdr;
1636 RT_ZERO(bmpHdr);
1637
1638 VIDEORECBMPDIBHDR bmpDIBHdr;
1639 RT_ZERO(bmpDIBHdr);
1640
1641 bmpHdr.u16Magic = 0x4d42; /* Magic */
1642 bmpHdr.u32Size = (uint32_t)(sizeof(VIDEORECBMPHDR) + sizeof(VIDEORECBMPDIBHDR) + (w * h * uBytesPerPixel));
1643 bmpHdr.u32OffBits = (uint32_t)(sizeof(VIDEORECBMPHDR) + sizeof(VIDEORECBMPDIBHDR));
1644
1645 bmpDIBHdr.u32Size = sizeof(VIDEORECBMPDIBHDR);
1646 bmpDIBHdr.u32Width = w;
1647 bmpDIBHdr.u32Height = h;
1648 bmpDIBHdr.u16Planes = 1;
1649 bmpDIBHdr.u16BitCount = uBPP;
1650 bmpDIBHdr.u32XPelsPerMeter = 5000;
1651 bmpDIBHdr.u32YPelsPerMeter = 5000;
1652
1653 RTFILE fh;
1654 int rc2 = RTFileOpen(&fh, "/tmp/VideoRecFrame.bmp",
1655 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
1656 if (RT_SUCCESS(rc2))
1657 {
1658 RTFileWrite(fh, &bmpHdr, sizeof(bmpHdr), NULL);
1659 RTFileWrite(fh, &bmpDIBHdr, sizeof(bmpDIBHdr), NULL);
1660 }
1661#endif
1662 Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
1663
1664 /* Do the copy. */
1665 for (unsigned int i = 0; i < h; i++)
1666 {
1667 /* Overflow check. */
1668 Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
1669 Assert(offDst + w * uBytesPerPixel <= pStream->Video.uHeight * pStream->Video.uWidth * uBytesPerPixel);
1670
1671 memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
1672
1673#ifdef VBOX_VIDEOREC_DUMP
1674 if (RT_SUCCESS(rc2))
1675 RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
1676#endif
1677 offSrc += uBytesPerLine;
1678 offDst += pStream->Video.uWidth * uBytesPerPixel;
1679 }
1680
1681#ifdef VBOX_VIDEOREC_DUMP
1682 if (RT_SUCCESS(rc2))
1683 RTFileClose(fh);
1684#endif
1685 pFrame->uTimeStampMs = uTimeStampMs;
1686 pFrame->uWidth = uSrcWidth;
1687 pFrame->uHeight = uSrcHeight;
1688
1689 pStream->Video.fHasVideoData = true;
1690
1691 } while (0);
1692
1693 videoRecStreamUnlock(pStream);
1694
1695 if ( RT_SUCCESS(rc)
1696 && rc != VINF_TRY_AGAIN) /* Only signal the thread if operation was successful. */
1697 {
1698 int rc2 = RTSemEventSignal(pCtx->WaitEvent);
1699 AssertRC(rc2);
1700 }
1701
1702 return rc;
1703}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use