VirtualBox

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

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

VideoRec/Main: Factored out the WebMWriter class from the EBMLWriter one and got rid of the extra Impl class and instead derive WebMWriter from EBMLWriter. Saves quite a bit of code and should make the stuff a lot easier to read and maintain. No functional code changes.

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

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