VirtualBox

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

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

VideoRec: Don't try to free the filename's suffix if it's not allocated by us.

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