VirtualBox

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

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

Main/VideoRec: Logging nit.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use