VirtualBox

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

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

VideoRec: Got rid of the static codec interface define and prepare for also getting/using other codec interface (like VP9, not used yet).

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