VirtualBox

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

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

VideoRec/Main: Split up code into more functions, separating VPX code from generic code.

  • 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: 64.6 KB
Line 
1/* $Id: VideoRec.cpp 74987 2018-10-23 08:46:05Z vboxsync $ */
2/** @file
3 * Video recording (with optional audio recording) code.
4 *
5 * This code employs a separate encoding thread per recording context
6 * to keep time spent in EMT as short as possible. Each configured VM display
7 * is represented by an own recording stream, which in turn has its own rendering
8 * queue. Common recording data across all recording streams is kept in a
9 * separate queue in the recording context to minimize data duplication and
10 * multiplexing overhead in EMT.
11 */
12
13/*
14 * Copyright (C) 2012-2018 Oracle Corporation
15 *
16 * This file is part of VirtualBox Open Source Edition (OSE), as
17 * available from http://www.virtualbox.org. This file is free software;
18 * you can redistribute it and/or modify it under the terms of the GNU
19 * General Public License (GPL) as published by the Free Software
20 * Foundation, in version 2 as it comes in the "COPYING" file of the
21 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
22 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
23 */
24
25#ifdef LOG_GROUP
26# undef LOG_GROUP
27#endif
28#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
29#include "LoggingNew.h"
30
31#include <stdexcept>
32#include <vector>
33
34#include <iprt/asm.h>
35#include <iprt/assert.h>
36#include <iprt/critsect.h>
37#include <iprt/path.h>
38#include <iprt/semaphore.h>
39#include <iprt/thread.h>
40#include <iprt/time.h>
41
42#include <VBox/err.h>
43#include <VBox/com/VirtualBox.h>
44
45#include "WebMWriter.h"
46#include "VideoRec.h"
47
48#ifdef VBOX_WITH_LIBVPX
49# define VPX_CODEC_DISABLE_COMPAT 1
50# include "vpx/vp8cx.h"
51# include "vpx/vpx_image.h"
52# include "vpx/vpx_encoder.h"
53#endif /* VBOX_WITH_LIBVPX */
54
55using namespace com;
56
57#ifdef DEBUG_andy
58/** Enables dumping audio / video data for debugging reasons. */
59//# define VBOX_VIDEOREC_DUMP
60#endif
61
62/**
63 * Enumeration for a video recording state.
64 */
65enum VIDEORECSTS
66{
67 /** Not initialized. */
68 VIDEORECSTS_UNINITIALIZED = 0,
69 /** Initialized. */
70 VIDEORECSTS_INITIALIZED = 1,
71 /** The usual 32-bit hack. */
72 VIDEORECSTS_32BIT_HACK = 0x7fffffff
73};
74
75/**
76 * Enumeration for supported pixel formats.
77 */
78enum VIDEORECPIXELFMT
79{
80 /** Unknown pixel format. */
81 VIDEORECPIXELFMT_UNKNOWN = 0,
82 /** RGB 24. */
83 VIDEORECPIXELFMT_RGB24 = 1,
84 /** RGB 24. */
85 VIDEORECPIXELFMT_RGB32 = 2,
86 /** RGB 565. */
87 VIDEORECPIXELFMT_RGB565 = 3,
88 /** The usual 32-bit hack. */
89 VIDEORECPIXELFMT_32BIT_HACK = 0x7fffffff
90};
91
92/**
93 * Structure for keeping specific video recording codec data.
94 */
95typedef struct VIDEORECVIDEOCODEC
96{
97 union
98 {
99#ifdef VBOX_WITH_LIBVPX
100 struct
101 {
102 /** VPX codec context. */
103 vpx_codec_ctx_t Ctx;
104 /** VPX codec configuration. */
105 vpx_codec_enc_cfg_t Cfg;
106 /** VPX image context. */
107 vpx_image_t RawImage;
108 /** Pointer to the codec's internal YUV buffer. */
109 uint8_t *pu8YuvBuf;
110 } VPX;
111#endif /* VBOX_WITH_LIBVPX */
112 };
113} VIDEORECVIDEOCODEC, *PVIDEORECVIDEOCODEC;
114
115/**
116 * Structure for keeping a single video recording video frame.
117 */
118typedef struct VIDEORECVIDEOFRAME
119{
120 /** X resolution of this frame. */
121 uint32_t uWidth;
122 /** Y resolution of this frame. */
123 uint32_t uHeight;
124 /** Pixel format of this frame. */
125 uint32_t uPixelFormat;
126 /** RGB buffer containing the unmodified frame buffer data from Main's display. */
127 uint8_t *pu8RGBBuf;
128 /** Size (in bytes) of the RGB buffer. */
129 size_t cbRGBBuf;
130} VIDEORECVIDEOFRAME, *PVIDEORECVIDEOFRAME;
131
132#ifdef VBOX_WITH_AUDIO_VIDEOREC
133/**
134 * Structure for keeping a single video recording audio frame.
135 */
136typedef struct VIDEORECAUDIOFRAME
137{
138 /** Pointer to audio data. */
139 uint8_t *pvBuf;
140 /** Size (in bytes) of audio data. */
141 size_t cbBuf;
142} VIDEORECAUDIOFRAME, *PVIDEORECAUDIOFRAME;
143#endif
144
145/**
146 * Enumeration for specifying a video recording block type.
147 */
148typedef enum VIDEORECBLOCKTYPE
149{
150 /** Uknown block type, do not use. */
151 VIDEORECBLOCKTYPE_UNKNOWN = 0,
152 /** The block is a video frame. */
153 VIDEORECBLOCKTYPE_VIDEO,
154#ifdef VBOX_WITH_AUDIO_VIDEOREC
155 /** The block is an audio frame. */
156 VIDEORECBLOCKTYPE_AUDIO
157#endif
158} VIDEORECBLOCKTYPE;
159
160/**
161 * Generic structure for keeping a single video recording (data) block.
162 */
163typedef struct VIDEORECBLOCK
164{
165 /** The block's type. */
166 VIDEORECBLOCKTYPE enmType;
167 /** Number of references held of this block. */
168 uint16_t cRefs;
169 /** The (absolute) time stamp (in ms, PTS) of this block. */
170 uint64_t uTimeStampMs;
171 /** Opaque data block to the actual block data, depending on the block's type. */
172 void *pvData;
173 /** Size (in bytes) of the (opaque) data block. */
174 size_t cbData;
175} VIDEORECBLOCK, *PVIDEORECBLOCK;
176
177/** List for keeping video recording (data) blocks. */
178typedef std::list<PVIDEORECBLOCK> VideoRecBlockList;
179
180static void videoRecBlockFree(PVIDEORECBLOCK pBlock);
181
182/** Structure for queuing all blocks bound to a single timecode.
183 * This can happen if multiple tracks are being involved. */
184struct VideoRecBlocks
185{
186 virtual ~VideoRecBlocks()
187 {
188 Clear();
189 }
190
191 /**
192 * Resets a video recording block list by removing (destroying)
193 * all current elements.
194 */
195 void Clear()
196 {
197 while (!List.empty())
198 {
199 PVIDEORECBLOCK pBlock = List.front();
200 videoRecBlockFree(pBlock);
201 List.pop_front();
202 }
203
204 Assert(List.size() == 0);
205 }
206
207 /** The actual block list for this timecode. */
208 VideoRecBlockList List;
209};
210
211/** A block map containing all currently queued blocks.
212 * The key specifies a unique timecode, whereas the value
213 * is a list of blocks which all correlate to the same key (timecode). */
214typedef std::map<uint64_t, VideoRecBlocks *> VideoRecBlockMap;
215
216/**
217 * Structure for holding a set of video recording (data) blocks.
218 */
219struct VideoRecBlockSet
220{
221 virtual ~VideoRecBlockSet()
222 {
223 Clear();
224 }
225
226 /**
227 * Resets a video recording block set by removing (destroying)
228 * all current elements.
229 */
230 void Clear()
231 {
232 VideoRecBlockMap::iterator it = Map.begin();
233 while (it != Map.end())
234 {
235 it->second->Clear();
236 delete it->second;
237 Map.erase(it);
238 it = Map.begin();
239 }
240
241 Assert(Map.size() == 0);
242 }
243
244 /** Timestamp (in ms) when this set was last processed. */
245 uint64_t tsLastProcessedMs;
246 /** All blocks related to this block set. */
247 VideoRecBlockMap Map;
248};
249
250/**
251 * Structure for maintaining a video recording stream.
252 */
253struct VIDEORECSTREAM
254{
255 /** Video recording context this stream is associated to. */
256 PVIDEORECCONTEXT pCtx;
257 /** Destination where to write the stream to. */
258 VIDEORECDEST enmDst;
259 union
260 {
261 struct
262 {
263 /** File handle to use for writing. */
264 RTFILE hFile;
265 /** File name being used for this stream. */
266 char *pszFile;
267 /** Pointer to WebM writer instance being used. */
268 WebMWriter *pWEBM;
269 } File;
270 };
271#ifdef VBOX_WITH_AUDIO_VIDEOREC
272 /** Track number of audio stream. */
273 uint8_t uTrackAudio;
274#endif
275 /** Track number of video stream. */
276 uint8_t uTrackVideo;
277 /** Screen ID. */
278 uint16_t uScreenID;
279 /** Whether video recording is enabled or not. */
280 bool fEnabled;
281 /** Critical section to serialize access. */
282 RTCRITSECT CritSect;
283
284 struct
285 {
286 /** Codec-specific data. */
287 VIDEORECVIDEOCODEC Codec;
288 /** Minimal delay (in ms) between two video frames.
289 * This value is based on the configured FPS rate. */
290 uint32_t uDelayMs;
291 /** Target X resolution (in pixels). */
292 uint32_t uWidth;
293 /** Target Y resolution (in pixels). */
294 uint32_t uHeight;
295 /** Time stamp (in ms) of the last video frame we encoded. */
296 uint64_t uLastTimeStampMs;
297 /** Number of failed attempts to encode the current video frame in a row. */
298 uint16_t cFailedEncodingFrames;
299 } Video;
300
301 /** Common set of video recording (data) blocks, needed for
302 * multiplexing to all recording streams. */
303 VideoRecBlockSet Blocks;
304};
305
306/** Vector of video recording streams. */
307typedef std::vector <PVIDEORECSTREAM> VideoRecStreams;
308
309/**
310 * Structure for keeping a video recording context.
311 */
312struct VIDEORECCONTEXT
313{
314 /** Used recording configuration. */
315 VIDEORECCFG Cfg;
316 /** The current state. */
317 uint32_t enmState;
318 /** Critical section to serialize access. */
319 RTCRITSECT CritSect;
320 /** Semaphore to signal the encoding worker thread. */
321 RTSEMEVENT WaitEvent;
322 /** Whether this conext is in started state or not. */
323 bool fStarted;
324 /** Shutdown indicator. */
325 bool fShutdown;
326 /** Worker thread. */
327 RTTHREAD Thread;
328 /** Vector of current recording streams.
329 * Per VM screen (display) one recording stream is being used. */
330 VideoRecStreams vecStreams;
331 /** Timestamp (in ms) of when recording has been started. */
332 uint64_t tsStartMs;
333 /** Block map of common blocks which need to get multiplexed
334 * to all recording streams. This common block maps should help
335 * reducing the time spent in EMT and avoid doing the (expensive)
336 * multiplexing work in there.
337 *
338 * For now this only affects audio, e.g. all recording streams
339 * need to have the same audio data at a specific point in time. */
340 VideoRecBlockMap mapBlocksCommon;
341};
342
343#ifdef VBOX_VIDEOREC_DUMP
344#pragma pack(push)
345#pragma pack(1)
346typedef struct
347{
348 uint16_t u16Magic;
349 uint32_t u32Size;
350 uint16_t u16Reserved1;
351 uint16_t u16Reserved2;
352 uint32_t u32OffBits;
353} VIDEORECBMPHDR, *PVIDEORECBMPHDR;
354AssertCompileSize(VIDEORECBMPHDR, 14);
355
356typedef struct
357{
358 uint32_t u32Size;
359 uint32_t u32Width;
360 uint32_t u32Height;
361 uint16_t u16Planes;
362 uint16_t u16BitCount;
363 uint32_t u32Compression;
364 uint32_t u32SizeImage;
365 uint32_t u32XPelsPerMeter;
366 uint32_t u32YPelsPerMeter;
367 uint32_t u32ClrUsed;
368 uint32_t u32ClrImportant;
369} VIDEORECBMPDIBHDR, *PVIDEORECBMPDIBHDR;
370AssertCompileSize(VIDEORECBMPDIBHDR, 40);
371
372#pragma pack(pop)
373#endif /* VBOX_VIDEOREC_DUMP */
374
375/**
376 * Iterator class for running through a BGRA32 image buffer and converting
377 * it to RGB.
378 */
379class ColorConvBGRA32Iter
380{
381private:
382 enum { PIX_SIZE = 4 };
383public:
384 ColorConvBGRA32Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
385 {
386 LogFlow(("width = %d height=%d aBuf=%lx\n", aWidth, aHeight, aBuf));
387 mPos = 0;
388 mSize = aWidth * aHeight * PIX_SIZE;
389 mBuf = aBuf;
390 }
391 /**
392 * Convert the next pixel to RGB.
393 * @returns true on success, false if we have reached the end of the buffer
394 * @param aRed where to store the red value
395 * @param aGreen where to store the green value
396 * @param aBlue where to store the blue value
397 */
398 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
399 {
400 bool rc = false;
401 if (mPos + PIX_SIZE <= mSize)
402 {
403 *aRed = mBuf[mPos + 2];
404 *aGreen = mBuf[mPos + 1];
405 *aBlue = mBuf[mPos ];
406 mPos += PIX_SIZE;
407 rc = true;
408 }
409 return rc;
410 }
411
412 /**
413 * Skip forward by a certain number of pixels
414 * @param aPixels how many pixels to skip
415 */
416 void skip(unsigned aPixels)
417 {
418 mPos += PIX_SIZE * aPixels;
419 }
420private:
421 /** Size of the picture buffer */
422 unsigned mSize;
423 /** Current position in the picture buffer */
424 unsigned mPos;
425 /** Address of the picture buffer */
426 uint8_t *mBuf;
427};
428
429/**
430 * Iterator class for running through an BGR24 image buffer and converting
431 * it to RGB.
432 */
433class ColorConvBGR24Iter
434{
435private:
436 enum { PIX_SIZE = 3 };
437public:
438 ColorConvBGR24Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
439 {
440 mPos = 0;
441 mSize = aWidth * aHeight * PIX_SIZE;
442 mBuf = aBuf;
443 }
444 /**
445 * Convert the next pixel to RGB.
446 * @returns true on success, false if we have reached the end of the buffer
447 * @param aRed where to store the red value
448 * @param aGreen where to store the green value
449 * @param aBlue where to store the blue value
450 */
451 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
452 {
453 bool rc = false;
454 if (mPos + PIX_SIZE <= mSize)
455 {
456 *aRed = mBuf[mPos + 2];
457 *aGreen = mBuf[mPos + 1];
458 *aBlue = mBuf[mPos ];
459 mPos += PIX_SIZE;
460 rc = true;
461 }
462 return rc;
463 }
464
465 /**
466 * Skip forward by a certain number of pixels
467 * @param aPixels how many pixels to skip
468 */
469 void skip(unsigned aPixels)
470 {
471 mPos += PIX_SIZE * aPixels;
472 }
473private:
474 /** Size of the picture buffer */
475 unsigned mSize;
476 /** Current position in the picture buffer */
477 unsigned mPos;
478 /** Address of the picture buffer */
479 uint8_t *mBuf;
480};
481
482/**
483 * Iterator class for running through an BGR565 image buffer and converting
484 * it to RGB.
485 */
486class ColorConvBGR565Iter
487{
488private:
489 enum { PIX_SIZE = 2 };
490public:
491 ColorConvBGR565Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
492 {
493 mPos = 0;
494 mSize = aWidth * aHeight * PIX_SIZE;
495 mBuf = aBuf;
496 }
497 /**
498 * Convert the next pixel to RGB.
499 * @returns true on success, false if we have reached the end of the buffer
500 * @param aRed where to store the red value
501 * @param aGreen where to store the green value
502 * @param aBlue where to store the blue value
503 */
504 bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
505 {
506 bool rc = false;
507 if (mPos + PIX_SIZE <= mSize)
508 {
509 unsigned uFull = (((unsigned) mBuf[mPos + 1]) << 8)
510 | ((unsigned) mBuf[mPos]);
511 *aRed = (uFull >> 8) & ~7;
512 *aGreen = (uFull >> 3) & ~3 & 0xff;
513 *aBlue = (uFull << 3) & ~7 & 0xff;
514 mPos += PIX_SIZE;
515 rc = true;
516 }
517 return rc;
518 }
519
520 /**
521 * Skip forward by a certain number of pixels
522 * @param aPixels how many pixels to skip
523 */
524 void skip(unsigned aPixels)
525 {
526 mPos += PIX_SIZE * aPixels;
527 }
528private:
529 /** Size of the picture buffer */
530 unsigned mSize;
531 /** Current position in the picture buffer */
532 unsigned mPos;
533 /** Address of the picture buffer */
534 uint8_t *mBuf;
535};
536
537#ifdef VBOX_WITH_AUDIO_VIDEOREC
538static void videoRecAudioFrameFree(PVIDEORECAUDIOFRAME pFrame);
539#endif
540static int videoRecRGBToYUV(uint32_t uPixelFormat,
541 uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
542 uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight);
543static int videoRecStreamClose(PVIDEORECSTREAM pStream);
544static int videoRecStreamOpen(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg);
545static int videoRecStreamUninit(PVIDEORECSTREAM pStream);
546static int videoRecStreamUnitVideo(PVIDEORECSTREAM pStream);
547static int videoRecStreamInitVideo(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg);
548#ifdef VBOX_WITH_LIBVPX
549static int videoRecStreamInitVideoVPX(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg);
550static int videoRecStreamUninitVideoVPX(PVIDEORECSTREAM pStream);
551static int videoRecStreamWriteVideoVPX(PVIDEORECSTREAM pStream, uint64_t uTimeStampMs, PVIDEORECVIDEOFRAME pFrame);
552#endif
553static void videoRecStreamLock(PVIDEORECSTREAM pStream);
554static void videoRecStreamUnlock(PVIDEORECSTREAM pStream);
555static void videoRecVideoFrameFree(PVIDEORECVIDEOFRAME pFrame);
556
557/**
558 * Convert an image to YUV420p format.
559 *
560 * @return true on success, false on failure.
561 * @param aDstBuf The destination image buffer.
562 * @param aDstWidth Width (in pixel) of destination buffer.
563 * @param aDstHeight Height (in pixel) of destination buffer.
564 * @param aSrcBuf The source image buffer.
565 * @param aSrcWidth Width (in pixel) of source buffer.
566 * @param aSrcHeight Height (in pixel) of source buffer.
567 */
568template <class T>
569inline bool colorConvWriteYUV420p(uint8_t *aDstBuf, unsigned aDstWidth, unsigned aDstHeight,
570 uint8_t *aSrcBuf, unsigned aSrcWidth, unsigned aSrcHeight)
571{
572 RT_NOREF(aDstWidth, aDstHeight);
573
574 AssertReturn(!(aSrcWidth & 1), false);
575 AssertReturn(!(aSrcHeight & 1), false);
576
577 bool fRc = true;
578 T iter1(aSrcWidth, aSrcHeight, aSrcBuf);
579 T iter2 = iter1;
580 iter2.skip(aSrcWidth);
581 unsigned cPixels = aSrcWidth * aSrcHeight;
582 unsigned offY = 0;
583 unsigned offU = cPixels;
584 unsigned offV = cPixels + cPixels / 4;
585 unsigned const cyHalf = aSrcHeight / 2;
586 unsigned const cxHalf = aSrcWidth / 2;
587 for (unsigned i = 0; i < cyHalf && fRc; ++i)
588 {
589 for (unsigned j = 0; j < cxHalf; ++j)
590 {
591 unsigned red, green, blue;
592 fRc = iter1.getRGB(&red, &green, &blue);
593 AssertReturn(fRc, false);
594 aDstBuf[offY] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
595 unsigned u = (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
596 unsigned v = (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
597
598 fRc = iter1.getRGB(&red, &green, &blue);
599 AssertReturn(fRc, false);
600 aDstBuf[offY + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
601 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
602 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
603
604 fRc = iter2.getRGB(&red, &green, &blue);
605 AssertReturn(fRc, false);
606 aDstBuf[offY + aSrcWidth] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
607 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
608 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
609
610 fRc = iter2.getRGB(&red, &green, &blue);
611 AssertReturn(fRc, false);
612 aDstBuf[offY + aSrcWidth + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
613 u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
614 v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
615
616 aDstBuf[offU] = u;
617 aDstBuf[offV] = v;
618 offY += 2;
619 ++offU;
620 ++offV;
621 }
622
623 iter1.skip(aSrcWidth);
624 iter2.skip(aSrcWidth);
625 offY += aSrcWidth;
626 }
627
628 return true;
629}
630
631/**
632 * Convert an image to RGB24 format
633 * @returns true on success, false on failure
634 * @param aWidth width of image
635 * @param aHeight height of image
636 * @param aDestBuf an allocated memory buffer large enough to hold the
637 * destination image (i.e. width * height * 12bits)
638 * @param aSrcBuf the source image as an array of bytes
639 */
640template <class T>
641inline bool colorConvWriteRGB24(unsigned aWidth, unsigned aHeight,
642 uint8_t *aDestBuf, uint8_t *aSrcBuf)
643{
644 enum { PIX_SIZE = 3 };
645 bool rc = true;
646 AssertReturn(0 == (aWidth & 1), false);
647 AssertReturn(0 == (aHeight & 1), false);
648 T iter(aWidth, aHeight, aSrcBuf);
649 unsigned cPixels = aWidth * aHeight;
650 for (unsigned i = 0; i < cPixels && rc; ++i)
651 {
652 unsigned red, green, blue;
653 rc = iter.getRGB(&red, &green, &blue);
654 if (rc)
655 {
656 aDestBuf[i * PIX_SIZE ] = red;
657 aDestBuf[i * PIX_SIZE + 1] = green;
658 aDestBuf[i * PIX_SIZE + 2] = blue;
659 }
660 }
661 return rc;
662}
663
664#ifdef VBOX_WITH_AUDIO_VIDEOREC
665/**
666 * Frees a previously allocated video recording audio frame.
667 *
668 * @param pFrame Audio frame to free. The pointer will be invalid after return.
669 */
670static void videoRecAudioFrameFree(PVIDEORECAUDIOFRAME pFrame)
671{
672 if (!pFrame)
673 return;
674
675 if (pFrame->pvBuf)
676 {
677 Assert(pFrame->cbBuf);
678 RTMemFree(pFrame->pvBuf);
679 }
680 RTMemFree(pFrame);
681 pFrame = NULL;
682}
683#endif
684
685/**
686 * Frees a video recording (data) block.
687 *
688 * @returns IPRT status code.
689 * @param pBlock Video recording (data) block to free. The pointer will be invalid after return.
690 */
691static void videoRecBlockFree(PVIDEORECBLOCK pBlock)
692{
693 if (!pBlock)
694 return;
695
696 switch (pBlock->enmType)
697 {
698 case VIDEORECBLOCKTYPE_VIDEO:
699 videoRecVideoFrameFree((PVIDEORECVIDEOFRAME)pBlock->pvData);
700 break;
701
702#ifdef VBOX_WITH_AUDIO_VIDEOREC
703 case VIDEORECBLOCKTYPE_AUDIO:
704 videoRecAudioFrameFree((PVIDEORECAUDIOFRAME)pBlock->pvData);
705 break;
706#endif
707 default:
708 AssertFailed();
709 break;
710 }
711
712 RTMemFree(pBlock);
713 pBlock = NULL;
714}
715
716/**
717 * Worker thread for all streams of a video recording context.
718 *
719 * For video frames, this also does the RGB/YUV conversion and encoding.
720 */
721static DECLCALLBACK(int) videoRecThread(RTTHREAD hThreadSelf, void *pvUser)
722{
723 PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)pvUser;
724
725 /* Signal that we're up and rockin'. */
726 RTThreadUserSignal(hThreadSelf);
727
728 LogFunc(("Thread started\n"));
729
730 for (;;)
731 {
732 int rc = RTSemEventWait(pCtx->WaitEvent, RT_INDEFINITE_WAIT);
733 AssertRCBreak(rc);
734
735 /** @todo r=andy This is inefficient -- as we already wake up this thread
736 * for every screen from Main, we here go again (on every wake up) through
737 * all screens. */
738 for (VideoRecStreams::iterator itStream = pCtx->vecStreams.begin(); itStream != pCtx->vecStreams.end(); itStream++)
739 {
740 PVIDEORECSTREAM pStream = (*itStream);
741
742 videoRecStreamLock(pStream);
743
744 if (!pStream->fEnabled)
745 {
746 videoRecStreamUnlock(pStream);
747 continue;
748 }
749
750 VideoRecBlockMap::iterator itBlockStream = pStream->Blocks.Map.begin();
751 while (itBlockStream != pStream->Blocks.Map.end())
752 {
753 const uint64_t uTimeStampMs = itBlockStream->first;
754 VideoRecBlocks *pBlocks = itBlockStream->second;
755
756 AssertPtr(pBlocks);
757
758 while (!pBlocks->List.empty())
759 {
760 PVIDEORECBLOCK pBlock = pBlocks->List.front();
761 AssertPtr(pBlock);
762
763#ifdef VBOX_WITH_LIBVPX
764 if (pBlock->enmType == VIDEORECBLOCKTYPE_VIDEO)
765 {
766 PVIDEORECVIDEOFRAME pVideoFrame = (PVIDEORECVIDEOFRAME)pBlock->pvData;
767
768 rc = videoRecRGBToYUV(pVideoFrame->uPixelFormat,
769 /* Destination */
770 pStream->Video.Codec.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
771 /* Source */
772 pVideoFrame->pu8RGBBuf, pStream->Video.uWidth, pStream->Video.uHeight);
773 if (RT_SUCCESS(rc))
774 rc = videoRecStreamWriteVideoVPX(pStream, uTimeStampMs, pVideoFrame);
775 }
776#endif
777 videoRecBlockFree(pBlock);
778 pBlock = NULL;
779
780 pBlocks->List.pop_front();
781 }
782
783#ifdef VBOX_WITH_AUDIO_VIDEOREC
784 /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
785 * written to the screen's assigned recording stream. */
786 VideoRecBlockMap::iterator itCommon = pCtx->mapBlocksCommon.begin();
787 while (itCommon != pCtx->mapBlocksCommon.end())
788 {
789 VideoRecBlockList::iterator itBlockCommon = itCommon->second->List.begin();
790 while (itBlockCommon != itCommon->second->List.end())
791 {
792 PVIDEORECBLOCK pBlockCommon = (PVIDEORECBLOCK)(*itBlockCommon);
793 switch (pBlockCommon->enmType)
794 {
795 case VIDEORECBLOCKTYPE_AUDIO:
796 {
797 PVIDEORECAUDIOFRAME pAudioFrame = (PVIDEORECAUDIOFRAME)pBlockCommon->pvData;
798 AssertPtr(pAudioFrame);
799 AssertPtr(pAudioFrame->pvBuf);
800 Assert(pAudioFrame->cbBuf);
801
802 WebMWriter::BlockData_Opus blockData = { pAudioFrame->pvBuf, pAudioFrame->cbBuf,
803 pBlockCommon->uTimeStampMs };
804 rc = pStream->File.pWEBM->WriteBlock(pStream->uTrackAudio, &blockData, sizeof(blockData));
805 break;
806 }
807
808 default:
809 AssertFailed();
810 break;
811 }
812
813 Assert(pBlockCommon->cRefs);
814 if (--pBlockCommon->cRefs == 0)
815 {
816 videoRecBlockFree(pBlockCommon);
817 itCommon->second->List.erase(itBlockCommon);
818 itBlockCommon = itCommon->second->List.begin();
819 }
820 else
821 ++itBlockCommon;
822 }
823
824 /* If no entries are left over in the block map, remove it altogether. */
825 if (itCommon->second->List.empty())
826 {
827 delete itCommon->second;
828 pCtx->mapBlocksCommon.erase(itCommon);
829 }
830
831 itCommon = pCtx->mapBlocksCommon.begin();
832
833 LogFunc(("Common blocks: %zu\n", pCtx->mapBlocksCommon.size()));
834 }
835#endif
836 ++itBlockStream;
837 }
838
839 videoRecStreamUnlock(pStream);
840 }
841
842 /* Keep going in case of errors. */
843
844 if (ASMAtomicReadBool(&pCtx->fShutdown))
845 {
846 LogFunc(("Thread is shutting down ...\n"));
847 break;
848 }
849
850 } /* for */
851
852 LogFunc(("Thread ended\n"));
853 return VINF_SUCCESS;
854}
855
856/**
857 * Notifies a recording context's encoding thread.
858 *
859 * @returns IPRT status code.
860 * @param pCtx Video recording context to notify thread for.
861 */
862static int videoRecThreadNotify(PVIDEORECCONTEXT pCtx)
863{
864 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
865
866 return RTSemEventSignal(pCtx->WaitEvent);
867}
868
869/**
870 * Creates a video recording context.
871 *
872 * @returns IPRT status code.
873 * @param cScreens Number of screens to create context for.
874 * @param pVideoRecCfg Pointer to video recording configuration to use.
875 * @param ppCtx Pointer to created video recording context on success.
876 */
877int VideoRecContextCreate(uint32_t cScreens, PVIDEORECCFG pVideoRecCfg, PVIDEORECCONTEXT *ppCtx)
878{
879 AssertReturn(cScreens, VERR_INVALID_PARAMETER);
880 AssertPtrReturn(pVideoRecCfg, VERR_INVALID_POINTER);
881 AssertPtrReturn(ppCtx, VERR_INVALID_POINTER);
882
883 VIDEORECCONTEXT *pCtx = NULL;
884 try
885 {
886 pCtx = new VIDEORECCONTEXT();
887 }
888 catch (std::bad_alloc &)
889 {
890 return VERR_NO_MEMORY;
891 }
892
893 int rc = RTCritSectInit(&pCtx->CritSect);
894 if (RT_FAILURE(rc))
895 {
896 delete pCtx;
897 return rc;
898 }
899
900 for (uint32_t uScreen = 0; uScreen < cScreens; uScreen++)
901 {
902 VIDEORECSTREAM *pStream = NULL;
903 try
904 {
905 pStream = new VIDEORECSTREAM();
906 }
907 catch (std::bad_alloc &)
908 {
909 rc = VERR_NO_MEMORY;
910 break;
911 }
912
913 rc = RTCritSectInit(&pStream->CritSect);
914 if (RT_FAILURE(rc))
915 break;
916
917 try
918 {
919 pStream->uScreenID = uScreen;
920
921 pCtx->vecStreams.push_back(pStream);
922
923 pStream->File.pWEBM = new WebMWriter();
924 }
925 catch (std::bad_alloc &)
926 {
927 rc = VERR_NO_MEMORY;
928 break;
929 }
930 }
931
932 if (RT_SUCCESS(rc))
933 {
934 pCtx->tsStartMs = RTTimeMilliTS();
935 pCtx->enmState = VIDEORECSTS_UNINITIALIZED;
936 pCtx->fStarted = false;
937 pCtx->fShutdown = false;
938
939 /* Copy the configuration to our context. */
940 pCtx->Cfg = *pVideoRecCfg;
941
942 rc = RTSemEventCreate(&pCtx->WaitEvent);
943 AssertRCReturn(rc, rc);
944
945 rc = RTThreadCreate(&pCtx->Thread, videoRecThread, (void *)pCtx, 0,
946 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "VideoRec");
947
948 if (RT_SUCCESS(rc)) /* Wait for the thread to start. */
949 rc = RTThreadUserWait(pCtx->Thread, 30 * RT_MS_1SEC /* 30s timeout */);
950
951 if (RT_SUCCESS(rc))
952 {
953 pCtx->enmState = VIDEORECSTS_INITIALIZED;
954 pCtx->fStarted = true;
955
956 if (ppCtx)
957 *ppCtx = pCtx;
958 }
959 }
960
961 if (RT_FAILURE(rc))
962 {
963 int rc2 = VideoRecContextDestroy(pCtx);
964 AssertRC(rc2);
965 }
966
967 return rc;
968}
969
970/**
971 * Destroys a video recording context.
972 *
973 * @param pCtx Video recording context to destroy.
974 */
975int VideoRecContextDestroy(PVIDEORECCONTEXT pCtx)
976{
977 if (!pCtx)
978 return VINF_SUCCESS;
979
980 int rc = VINF_SUCCESS;
981
982 if (pCtx->enmState == VIDEORECSTS_INITIALIZED)
983 {
984 LogFunc(("Shutting down thread ...\n"));
985
986 /* Set shutdown indicator. */
987 ASMAtomicWriteBool(&pCtx->fShutdown, true);
988
989 /* Signal the thread and wait for it to shut down. */
990 rc = videoRecThreadNotify(pCtx);
991 if (RT_SUCCESS(rc))
992 rc = RTThreadWait(pCtx->Thread, 10 * 1000 /* 10s timeout */, NULL);
993
994 if (RT_SUCCESS(rc))
995 {
996 /* Disable the context. */
997 ASMAtomicWriteBool(&pCtx->fStarted, false);
998
999 int rc2 = RTSemEventDestroy(pCtx->WaitEvent);
1000 AssertRC(rc2);
1001
1002 pCtx->WaitEvent = NIL_RTSEMEVENT;
1003 }
1004 }
1005
1006 if (RT_FAILURE(rc))
1007 {
1008 AssertRC(rc);
1009 return rc;
1010 }
1011
1012 rc = RTCritSectEnter(&pCtx->CritSect);
1013 if (RT_SUCCESS(rc))
1014 {
1015 VideoRecStreams::iterator it = pCtx->vecStreams.begin();
1016 while (it != pCtx->vecStreams.end())
1017 {
1018 PVIDEORECSTREAM pStream = (*it);
1019
1020 videoRecStreamLock(pStream);
1021
1022 int rc2 = videoRecStreamClose(pStream);
1023 if (RT_SUCCESS(rc))
1024 rc = rc2;
1025
1026 rc2 = videoRecStreamUninit(pStream);
1027 if (RT_SUCCESS(rc))
1028 rc = rc2;
1029
1030 pCtx->vecStreams.erase(it);
1031 it = pCtx->vecStreams.begin();
1032
1033 videoRecStreamUnlock(pStream);
1034
1035 RTCritSectDelete(&pStream->CritSect);
1036
1037 delete pStream;
1038 pStream = NULL;
1039 }
1040
1041 /* Sanity. */
1042 Assert(pCtx->vecStreams.empty());
1043 Assert(pCtx->mapBlocksCommon.size() == 0);
1044
1045 int rc2 = RTCritSectLeave(&pCtx->CritSect);
1046 AssertRC(rc2);
1047
1048 RTCritSectDelete(&pCtx->CritSect);
1049
1050 delete pCtx;
1051 pCtx = NULL;
1052 }
1053
1054 return rc;
1055}
1056
1057/**
1058 * Retrieves a specific recording stream of a recording context.
1059 *
1060 * @returns Pointer to recording stream if found, or NULL if not found.
1061 * @param pCtx Recording context to look up stream for.
1062 * @param uScreen Screen number of recording stream to look up.
1063 */
1064DECLINLINE(PVIDEORECSTREAM) videoRecStreamGet(PVIDEORECCONTEXT pCtx, uint32_t uScreen)
1065{
1066 AssertPtrReturn(pCtx, NULL);
1067
1068 PVIDEORECSTREAM pStream;
1069
1070 try
1071 {
1072 pStream = pCtx->vecStreams.at(uScreen);
1073 }
1074 catch (std::out_of_range &)
1075 {
1076 pStream = NULL;
1077 }
1078
1079 return pStream;
1080}
1081
1082/**
1083 * Locks a recording stream.
1084 *
1085 * @param pStream Recording stream to lock.
1086 */
1087static void videoRecStreamLock(PVIDEORECSTREAM pStream)
1088{
1089 int rc = RTCritSectEnter(&pStream->CritSect);
1090 AssertRC(rc);
1091}
1092
1093/**
1094 * Unlocks a locked recording stream.
1095 *
1096 * @param pStream Recording stream to unlock.
1097 */
1098static void videoRecStreamUnlock(PVIDEORECSTREAM pStream)
1099{
1100 int rc = RTCritSectLeave(&pStream->CritSect);
1101 AssertRC(rc);
1102}
1103
1104/**
1105 * Opens a recording stream.
1106 *
1107 * @returns IPRT status code.
1108 * @param pStream Recording stream to open.
1109 * @param pCfg Recording configuration to use.
1110 */
1111static int videoRecStreamOpen(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg)
1112{
1113 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1114 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
1115
1116 Assert(pStream->enmDst == VIDEORECDEST_INVALID);
1117
1118 int rc;
1119
1120 switch (pCfg->enmDst)
1121 {
1122 case VIDEORECDEST_FILE:
1123 {
1124 Assert(pCfg->File.strName.isNotEmpty());
1125
1126 char *pszAbsPath = RTPathAbsDup(com::Utf8Str(pCfg->File.strName).c_str());
1127 AssertPtrReturn(pszAbsPath, VERR_NO_MEMORY);
1128
1129 RTPathStripSuffix(pszAbsPath);
1130
1131 char *pszSuff = RTStrDup(".webm");
1132 if (!pszSuff)
1133 {
1134 RTStrFree(pszAbsPath);
1135 rc = VERR_NO_MEMORY;
1136 break;
1137 }
1138
1139 char *pszFile = NULL;
1140
1141 if (pCfg->aScreens.size() > 1)
1142 rc = RTStrAPrintf(&pszFile, "%s-%u%s", pszAbsPath, pStream->uScreenID + 1, pszSuff);
1143 else
1144 rc = RTStrAPrintf(&pszFile, "%s%s", pszAbsPath, pszSuff);
1145
1146 if (RT_SUCCESS(rc))
1147 {
1148 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
1149
1150 /* Play safe: the file must not exist, overwriting is potentially
1151 * hazardous as nothing prevents the user from picking a file name of some
1152 * other important file, causing unintentional data loss. */
1153 fOpen |= RTFILE_O_CREATE;
1154
1155 RTFILE hFile;
1156 rc = RTFileOpen(&hFile, pszFile, fOpen);
1157 if (rc == VERR_ALREADY_EXISTS)
1158 {
1159 RTStrFree(pszFile);
1160 pszFile = NULL;
1161
1162 RTTIMESPEC ts;
1163 RTTimeNow(&ts);
1164 RTTIME time;
1165 RTTimeExplode(&time, &ts);
1166
1167 if (pCfg->aScreens.size() > 1)
1168 rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ-%u%s",
1169 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
1170 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
1171 pStream->uScreenID + 1, pszSuff);
1172 else
1173 rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ%s",
1174 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
1175 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
1176 pszSuff);
1177
1178 if (RT_SUCCESS(rc))
1179 rc = RTFileOpen(&hFile, pszFile, fOpen);
1180 }
1181
1182 if (RT_SUCCESS(rc))
1183 {
1184 pStream->enmDst = VIDEORECDEST_FILE;
1185 pStream->File.hFile = hFile;
1186 pStream->File.pszFile = pszFile; /* Assign allocated string to our stream's config. */
1187 }
1188 }
1189
1190 RTStrFree(pszSuff);
1191 RTStrFree(pszAbsPath);
1192
1193 if (RT_FAILURE(rc))
1194 {
1195 LogRel(("VideoRec: Failed to open file '%s' for screen %RU32, rc=%Rrc\n",
1196 pszFile ? pszFile : "<Unnamed>", pStream->uScreenID, rc));
1197 RTStrFree(pszFile);
1198 }
1199
1200 break;
1201 }
1202
1203 default:
1204 rc = VERR_NOT_IMPLEMENTED;
1205 break;
1206 }
1207
1208 LogFlowFuncLeaveRC(rc);
1209 return rc;
1210}
1211
1212/**
1213 * VideoRec utility function to initialize video recording context.
1214 *
1215 * @returns IPRT status code.
1216 * @param pCtx Pointer to video recording context.
1217 * @param uScreen Screen number to record.
1218 */
1219int VideoRecStreamInit(PVIDEORECCONTEXT pCtx, uint32_t uScreen)
1220{
1221 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1222
1223 PVIDEORECCFG pCfg = &pCtx->Cfg;
1224
1225#ifdef VBOX_WITH_AUDIO_VIDEOREC
1226 if (pCfg->Audio.fEnabled)
1227 {
1228 /* Sanity. */
1229 AssertReturn(pCfg->Audio.uHz, VERR_INVALID_PARAMETER);
1230 AssertReturn(pCfg->Audio.cBits, VERR_INVALID_PARAMETER);
1231 AssertReturn(pCfg->Audio.cChannels, VERR_INVALID_PARAMETER);
1232 }
1233#endif
1234
1235 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1236 if (!pStream)
1237 return VERR_NOT_FOUND;
1238
1239 int rc = videoRecStreamOpen(pStream, pCfg);
1240 if (RT_FAILURE(rc))
1241 return rc;
1242
1243 pStream->pCtx = pCtx;
1244
1245 if (pCfg->Video.fEnabled)
1246 rc = videoRecStreamInitVideo(pStream, pCfg);
1247
1248 switch (pStream->enmDst)
1249 {
1250 case VIDEORECDEST_FILE:
1251 {
1252 rc = pStream->File.pWEBM->OpenEx(pStream->File.pszFile, &pStream->File.hFile,
1253#ifdef VBOX_WITH_AUDIO_VIDEOREC
1254 pCfg->Audio.fEnabled ? WebMWriter::AudioCodec_Opus : WebMWriter::AudioCodec_None,
1255#else
1256 WebMWriter::AudioCodec_None,
1257#endif
1258 pCfg->Video.fEnabled ? WebMWriter::VideoCodec_VP8 : WebMWriter::VideoCodec_None);
1259 if (RT_FAILURE(rc))
1260 {
1261 LogRel(("VideoRec: Failed to create the capture output file '%s' (%Rrc)\n", pStream->File.pszFile, rc));
1262 break;
1263 }
1264
1265 const char *pszFile = pStream->File.pszFile;
1266
1267 if (pCfg->Video.fEnabled)
1268 {
1269 rc = pStream->File.pWEBM->AddVideoTrack(pCfg->Video.uWidth, pCfg->Video.uHeight, pCfg->Video.uFPS,
1270 &pStream->uTrackVideo);
1271 if (RT_FAILURE(rc))
1272 {
1273 LogRel(("VideoRec: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, rc));
1274 break;
1275 }
1276
1277 LogRel(("VideoRec: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
1278 uScreen, pCfg->Video.uWidth, pCfg->Video.uHeight, pCfg->Video.uRate, pCfg->Video.uFPS,
1279 pStream->uTrackVideo));
1280 }
1281
1282#ifdef VBOX_WITH_AUDIO_VIDEOREC
1283 if (pCfg->Audio.fEnabled)
1284 {
1285 rc = pStream->File.pWEBM->AddAudioTrack(pCfg->Audio.uHz, pCfg->Audio.cChannels, pCfg->Audio.cBits,
1286 &pStream->uTrackAudio);
1287 if (RT_FAILURE(rc))
1288 {
1289 LogRel(("VideoRec: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, rc));
1290 break;
1291 }
1292
1293 LogRel(("VideoRec: Recording audio in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
1294 pCfg->Audio.uHz, pCfg->Audio.cBits, pCfg->Audio.cChannels, pCfg->Audio.cChannels ? "channels" : "channel",
1295 pStream->uTrackAudio));
1296 }
1297#endif
1298
1299 if ( pCfg->Video.fEnabled
1300#ifdef VBOX_WITH_AUDIO_VIDEOREC
1301 || pCfg->Audio.fEnabled
1302#endif
1303 )
1304 {
1305 char szWhat[32] = { 0 };
1306 if (pCfg->Video.fEnabled)
1307 RTStrCat(szWhat, sizeof(szWhat), "video");
1308#ifdef VBOX_WITH_AUDIO_VIDEOREC
1309 if (pCfg->Audio.fEnabled)
1310 {
1311 if (pCfg->Video.fEnabled)
1312 RTStrCat(szWhat, sizeof(szWhat), " + ");
1313 RTStrCat(szWhat, sizeof(szWhat), "audio");
1314 }
1315#endif
1316 LogRel(("VideoRec: Recording %s to '%s'\n", szWhat, pszFile));
1317 }
1318
1319 break;
1320 }
1321
1322 default:
1323 AssertFailed(); /* Should never happen. */
1324 rc = VERR_NOT_IMPLEMENTED;
1325 break;
1326 }
1327
1328 if (RT_FAILURE(rc))
1329 {
1330 int rc2 = videoRecStreamClose(pStream);
1331 AssertRC(rc2);
1332 return rc;
1333 }
1334
1335 pStream->fEnabled = true;
1336
1337 return VINF_SUCCESS;
1338}
1339
1340/**
1341 * Closes a recording stream.
1342 * Depending on the stream's recording destination, this function closes all associated handles
1343 * and finalizes recording.
1344 *
1345 * @returns IPRT status code.
1346 * @param pStream Recording stream to close.
1347 *
1348 */
1349static int videoRecStreamClose(PVIDEORECSTREAM pStream)
1350{
1351 int rc = VINF_SUCCESS;
1352
1353 if (pStream->fEnabled)
1354 {
1355 switch (pStream->enmDst)
1356 {
1357 case VIDEORECDEST_FILE:
1358 {
1359 if (pStream->File.pWEBM)
1360 rc = pStream->File.pWEBM->Close();
1361 break;
1362 }
1363
1364 default:
1365 AssertFailed(); /* Should never happen. */
1366 break;
1367 }
1368
1369 pStream->Blocks.Clear();
1370
1371 LogRel(("VideoRec: Recording screen #%u stopped\n", pStream->uScreenID));
1372 }
1373
1374 if (RT_FAILURE(rc))
1375 {
1376 LogRel(("VideoRec: Error stopping recording screen #%u, rc=%Rrc\n", pStream->uScreenID, rc));
1377 return rc;
1378 }
1379
1380 switch (pStream->enmDst)
1381 {
1382 case VIDEORECDEST_FILE:
1383 {
1384 AssertPtr(pStream->File.pszFile);
1385 if (RTFileIsValid(pStream->File.hFile))
1386 {
1387 rc = RTFileClose(pStream->File.hFile);
1388 if (RT_SUCCESS(rc))
1389 {
1390 LogRel(("VideoRec: Closed file '%s'\n", pStream->File.pszFile));
1391 }
1392 else
1393 {
1394 LogRel(("VideoRec: Error closing file '%s', rc=%Rrc\n", pStream->File.pszFile, rc));
1395 break;
1396 }
1397 }
1398
1399 RTStrFree(pStream->File.pszFile);
1400 pStream->File.pszFile = NULL;
1401
1402 if (pStream->File.pWEBM)
1403 {
1404 delete pStream->File.pWEBM;
1405 pStream->File.pWEBM = NULL;
1406 }
1407 break;
1408 }
1409
1410 default:
1411 rc = VERR_NOT_IMPLEMENTED;
1412 break;
1413 }
1414
1415 if (RT_SUCCESS(rc))
1416 {
1417 pStream->enmDst = VIDEORECDEST_INVALID;
1418 }
1419
1420 LogFlowFuncLeaveRC(rc);
1421 return rc;
1422}
1423
1424/**
1425 * Uninitializes a recording stream.
1426 *
1427 * @returns IPRT status code.
1428 * @param pStream Recording stream to uninitialize.
1429 */
1430static int videoRecStreamUninit(PVIDEORECSTREAM pStream)
1431{
1432 int rc = VINF_SUCCESS;
1433
1434 if (pStream->pCtx->Cfg.Video.fEnabled)
1435 {
1436 int rc2 = videoRecStreamUnitVideo(pStream);
1437 if (RT_SUCCESS(rc))
1438 rc = rc2;
1439 }
1440
1441 return rc;
1442}
1443
1444/**
1445 * Uninitializes video recording for a certain recording stream.
1446 *
1447 * @returns IPRT status code.
1448 * @param pStream Recording stream to uninitialize video recording for.
1449 */
1450static int videoRecStreamUnitVideo(PVIDEORECSTREAM pStream)
1451{
1452#ifdef VBOX_WITH_LIBVPX
1453 /* At the moment we only have VPX. */
1454 return videoRecStreamUninitVideoVPX(pStream);
1455#else
1456 return VERR_NOT_SUPPORTED;
1457#endif
1458}
1459
1460#ifdef VBOX_WITH_LIBVPX
1461/**
1462 * Uninitializes the VPX codec for a certain recording stream.
1463 *
1464 * @returns IPRT status code.
1465 * @param pStream Recording stream to uninitialize VPX codec for.
1466 */
1467static int videoRecStreamUninitVideoVPX(PVIDEORECSTREAM pStream)
1468{
1469 vpx_img_free(&pStream->Video.Codec.VPX.RawImage);
1470 vpx_codec_err_t rcv = vpx_codec_destroy(&pStream->Video.Codec.VPX.Ctx);
1471 Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
1472
1473 return VINF_SUCCESS;
1474}
1475#endif
1476
1477/**
1478 * Initializes the video recording for a certain recording stream.
1479 *
1480 * @returns IPRT status code.
1481 * @param pStream Recording stream to initialize video recording for.
1482 * @param pCfg Video recording configuration to use for initialization.
1483 */
1484static int videoRecStreamInitVideo(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg)
1485{
1486#ifdef VBOX_WITH_LIBVPX
1487 /* At the moment we only have VPX. */
1488 return videoRecStreamInitVideoVPX(pStream, pCfg);
1489#else
1490 return VERR_NOT_SUPPORTED;
1491#endif
1492}
1493
1494#ifdef VBOX_WITH_LIBVPX
1495/**
1496 * Initializes the VPX codec for a certain recording stream.
1497 *
1498 * @returns IPRT status code.
1499 * @param pStream Recording stream to initialize VPX codec for.
1500 * @param pCfg Video recording configuration to use for initialization.
1501 */
1502static int videoRecStreamInitVideoVPX(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg)
1503{
1504 pStream->Video.uWidth = pCfg->Video.uWidth;
1505 pStream->Video.uHeight = pCfg->Video.uHeight;
1506 pStream->Video.cFailedEncodingFrames = 0;
1507
1508 PVIDEORECVIDEOCODEC pVC = &pStream->Video.Codec;
1509
1510 pStream->Video.uDelayMs = RT_MS_1SEC / pCfg->Video.uFPS;
1511
1512# ifdef VBOX_WITH_LIBVPX_VP9
1513 vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx();
1514# else /* Default is using VP8. */
1515 vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx();
1516# endif
1517
1518 vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pVC->VPX.Cfg, 0 /* Reserved */);
1519 if (rcv != VPX_CODEC_OK)
1520 {
1521 LogRel(("VideoRec: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
1522 return VERR_AVREC_CODEC_INIT_FAILED;
1523 }
1524
1525 /* Target bitrate in kilobits per second. */
1526 pVC->VPX.Cfg.rc_target_bitrate = pCfg->Video.uRate;
1527 /* Frame width. */
1528 pVC->VPX.Cfg.g_w = pCfg->Video.uWidth;
1529 /* Frame height. */
1530 pVC->VPX.Cfg.g_h = pCfg->Video.uHeight;
1531 /* 1ms per frame. */
1532 pVC->VPX.Cfg.g_timebase.num = 1;
1533 pVC->VPX.Cfg.g_timebase.den = 1000;
1534 /* Disable multithreading. */
1535 pVC->VPX.Cfg.g_threads = 0;
1536
1537 /* Initialize codec. */
1538 rcv = vpx_codec_enc_init(&pVC->VPX.Ctx, pCodecIface, &pVC->VPX.Cfg, 0 /* Flags */);
1539 if (rcv != VPX_CODEC_OK)
1540 {
1541 LogRel(("VideoRec: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
1542 return VERR_AVREC_CODEC_INIT_FAILED;
1543 }
1544
1545 if (!vpx_img_alloc(&pVC->VPX.RawImage, VPX_IMG_FMT_I420, pCfg->Video.uWidth, pCfg->Video.uHeight, 1))
1546 {
1547 LogRel(("VideoRec: Failed to allocate image %RU32x%RU32\n", pCfg->Video.uWidth, pCfg->Video.uHeight));
1548 return VERR_NO_MEMORY;
1549 }
1550
1551 /* Save a pointer to the first raw YUV plane. */
1552 pStream->Video.Codec.VPX.pu8YuvBuf = pVC->VPX.RawImage.planes[0];
1553
1554 return VINF_SUCCESS;
1555}
1556#endif
1557
1558/**
1559 * Returns which recording features currently are enabled for a given configuration.
1560 *
1561 * @returns Enabled video recording features.
1562 * @param pCfg Pointer to recording configuration.
1563 */
1564VIDEORECFEATURES VideoRecGetFeatures(PVIDEORECCFG pCfg)
1565{
1566 if (!pCfg)
1567 return VIDEORECFEATURE_NONE;
1568
1569 VIDEORECFEATURES fFeatures = VIDEORECFEATURE_NONE;
1570
1571 if (pCfg->Video.fEnabled)
1572 fFeatures |= VIDEORECFEATURE_VIDEO;
1573
1574#ifdef VBOX_WITH_AUDIO_VIDEOREC
1575 if (pCfg->Audio.fEnabled)
1576 fFeatures |= VIDEORECFEATURE_AUDIO;
1577#endif
1578
1579 return fFeatures;
1580}
1581
1582/**
1583 * Checks if recording engine is ready to accept new recording data for a given screen.
1584 *
1585 * @returns true if recording engine is ready, false if not.
1586 * @param pCtx Pointer to video recording context.
1587 * @param uScreen Screen ID.
1588 * @param uTimeStampMs Current time stamp (in ms). Currently not being used.
1589 */
1590bool VideoRecIsReady(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t uTimeStampMs)
1591{
1592 AssertPtrReturn(pCtx, false);
1593 RT_NOREF(uTimeStampMs);
1594
1595 if (ASMAtomicReadU32(&pCtx->enmState) != VIDEORECSTS_INITIALIZED)
1596 return false;
1597
1598 bool fIsReady = false;
1599
1600 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1601 if (pStream)
1602 {
1603 videoRecStreamLock(pStream);
1604 fIsReady = pStream->fEnabled;
1605 videoRecStreamUnlock(pStream);
1606 }
1607
1608 /* Note: Do not check for other constraints like the video FPS rate here,
1609 * as this check then also would affect other (non-FPS related) stuff
1610 * like audio data. */
1611
1612 return fIsReady;
1613}
1614
1615/**
1616 * Returns whether a given recording context has been started or not.
1617 *
1618 * @returns true if active, false if not.
1619 * @param pCtx Pointer to video recording context.
1620 */
1621bool VideoRecIsStarted(PVIDEORECCONTEXT pCtx)
1622{
1623 if (!pCtx)
1624 return false;
1625
1626 return ASMAtomicReadBool(&pCtx->fStarted);
1627}
1628
1629/**
1630 * Checks if a specified limit for recording has been reached.
1631 *
1632 * @returns true if any limit has been reached.
1633 * @param pCtx Pointer to video recording context.
1634 * @param uScreen Screen ID.
1635 * @param tsNowMs Current time stamp (in ms).
1636 */
1637bool VideoRecIsLimitReached(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t tsNowMs)
1638{
1639 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1640 if ( !pStream
1641 || !pStream->fEnabled)
1642 {
1643 return false;
1644 }
1645
1646 const PVIDEORECCFG pCfg = &pCtx->Cfg;
1647
1648 if ( pCfg->uMaxTimeS
1649 && tsNowMs >= pCtx->tsStartMs + (pCfg->uMaxTimeS * RT_MS_1SEC))
1650 {
1651 return true;
1652 }
1653
1654 if (pCfg->enmDst == VIDEORECDEST_FILE)
1655 {
1656
1657 if (pCfg->File.uMaxSizeMB)
1658 {
1659 uint64_t sizeInMB = pStream->File.pWEBM->GetFileSize() / _1M;
1660 if(sizeInMB >= pCfg->File.uMaxSizeMB)
1661 return true;
1662 }
1663
1664 /* Check for available free disk space */
1665 if ( pStream->File.pWEBM
1666 && pStream->File.pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
1667 {
1668 LogRel(("VideoRec: Not enough free storage space available, stopping video capture\n"));
1669 return true;
1670 }
1671 }
1672
1673 return false;
1674}
1675
1676#ifdef VBOX_WITH_LIBVPX
1677/**
1678 * Encodes the source image and write the encoded image to the stream's destination.
1679 *
1680 * @returns IPRT status code.
1681 * @param pStream Stream to encode and submit to.
1682 * @param uTimeStampMs Absolute timestamp (PTS) of frame (in ms) to encode.
1683 * @param pFrame Frame to encode and submit.
1684 */
1685static int videoRecStreamWriteVideoVPX(PVIDEORECSTREAM pStream, uint64_t uTimeStampMs, PVIDEORECVIDEOFRAME pFrame)
1686{
1687 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1688 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
1689
1690 int rc;
1691
1692 AssertPtr(pStream->pCtx);
1693 PVIDEORECCFG pCfg = &pStream->pCtx->Cfg;
1694 PVIDEORECVIDEOCODEC pVC = &pStream->Video.Codec;
1695
1696 /* Presentation Time Stamp (PTS). */
1697 vpx_codec_pts_t pts = uTimeStampMs;
1698 vpx_codec_err_t rcv = vpx_codec_encode(&pVC->VPX.Ctx,
1699 &pVC->VPX.RawImage,
1700 pts /* Time stamp */,
1701 pStream->Video.uDelayMs /* How long to show this frame */,
1702 0 /* Flags */,
1703 pCfg->Video.Codec.VPX.uEncoderDeadline /* Quality setting */);
1704 if (rcv != VPX_CODEC_OK)
1705 {
1706 if (pStream->Video.cFailedEncodingFrames++ < 64)
1707 {
1708 LogRel(("VideoRec: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
1709 return VERR_GENERAL_FAILURE;
1710 }
1711 }
1712
1713 pStream->Video.cFailedEncodingFrames = 0;
1714
1715 vpx_codec_iter_t iter = NULL;
1716 rc = VERR_NO_DATA;
1717 for (;;)
1718 {
1719 const vpx_codec_cx_pkt_t *pPacket = vpx_codec_get_cx_data(&pVC->VPX.Ctx, &iter);
1720 if (!pPacket)
1721 break;
1722
1723 switch (pPacket->kind)
1724 {
1725 case VPX_CODEC_CX_FRAME_PKT:
1726 {
1727 WebMWriter::BlockData_VP8 blockData = { &pVC->VPX.Cfg, pPacket };
1728 rc = pStream->File.pWEBM->WriteBlock(pStream->uTrackVideo, &blockData, sizeof(blockData));
1729 break;
1730 }
1731
1732 default:
1733 AssertFailed();
1734 LogFunc(("Unexpected video packet type %ld\n", pPacket->kind));
1735 break;
1736 }
1737 }
1738
1739 return rc;
1740}
1741#endif /* VBOX_WITH_LIBVPX */
1742
1743/**
1744 * Converts a RGB to YUV buffer.
1745 *
1746 * @returns IPRT status code.
1747 * TODO
1748 */
1749static int videoRecRGBToYUV(uint32_t uPixelFormat,
1750 uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
1751 uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight)
1752{
1753 switch (uPixelFormat)
1754 {
1755 case VIDEORECPIXELFMT_RGB32:
1756 if (!colorConvWriteYUV420p<ColorConvBGRA32Iter>(paDst, uDstWidth, uDstHeight,
1757 paSrc, uSrcWidth, uSrcHeight))
1758 return VERR_INVALID_PARAMETER;
1759 break;
1760 case VIDEORECPIXELFMT_RGB24:
1761 if (!colorConvWriteYUV420p<ColorConvBGR24Iter>(paDst, uDstWidth, uDstHeight,
1762 paSrc, uSrcWidth, uSrcHeight))
1763 return VERR_INVALID_PARAMETER;
1764 break;
1765 case VIDEORECPIXELFMT_RGB565:
1766 if (!colorConvWriteYUV420p<ColorConvBGR565Iter>(paDst, uDstWidth, uDstHeight,
1767 paSrc, uSrcWidth, uSrcHeight))
1768 return VERR_INVALID_PARAMETER;
1769 break;
1770 default:
1771 AssertFailed();
1772 return VERR_NOT_SUPPORTED;
1773 }
1774 return VINF_SUCCESS;
1775}
1776
1777/**
1778 * Sends an audio frame to the video encoding thread.
1779 *
1780 * @thread EMT
1781 *
1782 * @returns IPRT status code.
1783 * @param pCtx Pointer to the video recording context.
1784 * @param pvData Audio frame data to send.
1785 * @param cbData Size (in bytes) of (encoded) audio frame data.
1786 * @param uTimeStampMs Time stamp (in ms) of audio playback.
1787 */
1788int VideoRecSendAudioFrame(PVIDEORECCONTEXT pCtx, const void *pvData, size_t cbData, uint64_t uTimeStampMs)
1789{
1790#ifdef VBOX_WITH_AUDIO_VIDEOREC
1791 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
1792 AssertReturn(cbData, VERR_INVALID_PARAMETER);
1793
1794 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
1795 *
1796 * The multiplexing is needed to supply all recorded (enabled) screens with the same
1797 * audio data at the same given point in time.
1798 */
1799 PVIDEORECBLOCK pBlock = (PVIDEORECBLOCK)RTMemAlloc(sizeof(VIDEORECBLOCK));
1800 AssertPtrReturn(pBlock, VERR_NO_MEMORY);
1801 pBlock->enmType = VIDEORECBLOCKTYPE_AUDIO;
1802
1803 PVIDEORECAUDIOFRAME pFrame = (PVIDEORECAUDIOFRAME)RTMemAlloc(sizeof(VIDEORECAUDIOFRAME));
1804 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
1805
1806 pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
1807 AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
1808 pFrame->cbBuf = cbData;
1809
1810 memcpy(pFrame->pvBuf, pvData, cbData);
1811
1812 pBlock->pvData = pFrame;
1813 pBlock->cbData = sizeof(VIDEORECAUDIOFRAME) + cbData;
1814 pBlock->cRefs = (uint16_t)pCtx->vecStreams.size(); /* All streams need the same audio data. */
1815 pBlock->uTimeStampMs = uTimeStampMs;
1816
1817 int rc = RTCritSectEnter(&pCtx->CritSect);
1818 if (RT_FAILURE(rc))
1819 return rc;
1820
1821 try
1822 {
1823 VideoRecBlockMap::iterator itBlocks = pCtx->mapBlocksCommon.find(uTimeStampMs);
1824 if (itBlocks == pCtx->mapBlocksCommon.end())
1825 {
1826 VideoRecBlocks *pVideoRecBlocks = new VideoRecBlocks();
1827 pVideoRecBlocks->List.push_back(pBlock);
1828
1829 pCtx->mapBlocksCommon.insert(std::make_pair(uTimeStampMs, pVideoRecBlocks));
1830 }
1831 else
1832 itBlocks->second->List.push_back(pBlock);
1833 }
1834 catch (const std::exception &ex)
1835 {
1836 RT_NOREF(ex);
1837 rc = VERR_NO_MEMORY;
1838 }
1839
1840 int rc2 = RTCritSectLeave(&pCtx->CritSect);
1841 AssertRC(rc2);
1842
1843 if (RT_SUCCESS(rc))
1844 rc = videoRecThreadNotify(pCtx);
1845
1846 return rc;
1847#else
1848 RT_NOREF(pCtx, pvData, cbData, uTimeStampMs);
1849 return VINF_SUCCESS;
1850#endif
1851}
1852
1853/**
1854 * Copies a source video frame to the intermediate RGB buffer.
1855 * This function is executed only once per time.
1856 *
1857 * @thread EMT
1858 *
1859 * @returns IPRT status code.
1860 * @param pCtx Pointer to the video recording context.
1861 * @param uScreen Screen number.
1862 * @param x Starting x coordinate of the video frame.
1863 * @param y Starting y coordinate of the video frame.
1864 * @param uPixelFormat Pixel format.
1865 * @param uBPP Bits Per Pixel (BPP).
1866 * @param uBytesPerLine Bytes per scanline.
1867 * @param uSrcWidth Width of the video frame.
1868 * @param uSrcHeight Height of the video frame.
1869 * @param puSrcData Pointer to video frame data.
1870 * @param uTimeStampMs Time stamp (in ms).
1871 */
1872int VideoRecSendVideoFrame(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint32_t x, uint32_t y,
1873 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
1874 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
1875 uint64_t uTimeStampMs)
1876{
1877 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1878 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
1879 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
1880 AssertReturn(puSrcData, VERR_INVALID_POINTER);
1881
1882 int rc = RTCritSectEnter(&pCtx->CritSect);
1883 AssertRC(rc);
1884
1885 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
1886 if (!pStream)
1887 {
1888 rc = RTCritSectLeave(&pCtx->CritSect);
1889 AssertRC(rc);
1890
1891 return VERR_NOT_FOUND;
1892 }
1893
1894 videoRecStreamLock(pStream);
1895
1896 PVIDEORECVIDEOFRAME pFrame = NULL;
1897
1898 do
1899 {
1900 if (!pStream->fEnabled)
1901 {
1902 rc = VINF_TRY_AGAIN; /* Not (yet) enabled. */
1903 break;
1904 }
1905
1906 if (uTimeStampMs < pStream->Video.uLastTimeStampMs + pStream->Video.uDelayMs)
1907 {
1908 rc = VINF_TRY_AGAIN; /* Respect maximum frames per second. */
1909 break;
1910 }
1911
1912 pStream->Video.uLastTimeStampMs = uTimeStampMs;
1913
1914 int xDiff = ((int)pStream->Video.uWidth - (int)uSrcWidth) / 2;
1915 uint32_t w = uSrcWidth;
1916 if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
1917 {
1918 rc = VERR_INVALID_PARAMETER;
1919 break;
1920 }
1921
1922 uint32_t destX;
1923 if ((int)x < -xDiff)
1924 {
1925 w += xDiff + x;
1926 x = -xDiff;
1927 destX = 0;
1928 }
1929 else
1930 destX = x + xDiff;
1931
1932 uint32_t h = uSrcHeight;
1933 int yDiff = ((int)pStream->Video.uHeight - (int)uSrcHeight) / 2;
1934 if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
1935 {
1936 rc = VERR_INVALID_PARAMETER;
1937 break;
1938 }
1939
1940 uint32_t destY;
1941 if ((int)y < -yDiff)
1942 {
1943 h += yDiff + (int)y;
1944 y = -yDiff;
1945 destY = 0;
1946 }
1947 else
1948 destY = y + yDiff;
1949
1950 if ( destX > pStream->Video.uWidth
1951 || destY > pStream->Video.uHeight)
1952 {
1953 rc = VERR_INVALID_PARAMETER; /* Nothing visible. */
1954 break;
1955 }
1956
1957 if (destX + w > pStream->Video.uWidth)
1958 w = pStream->Video.uWidth - destX;
1959
1960 if (destY + h > pStream->Video.uHeight)
1961 h = pStream->Video.uHeight - destY;
1962
1963 pFrame = (PVIDEORECVIDEOFRAME)RTMemAllocZ(sizeof(VIDEORECVIDEOFRAME));
1964 AssertBreakStmt(pFrame, rc = VERR_NO_MEMORY);
1965
1966 /* Calculate bytes per pixel and set pixel format. */
1967 const unsigned uBytesPerPixel = uBPP / 8;
1968 if (uPixelFormat == BitmapFormat_BGR)
1969 {
1970 switch (uBPP)
1971 {
1972 case 32:
1973 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB32;
1974 break;
1975 case 24:
1976 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB24;
1977 break;
1978 case 16:
1979 pFrame->uPixelFormat = VIDEORECPIXELFMT_RGB565;
1980 break;
1981 default:
1982 AssertMsgFailed(("Unknown color depth (%RU32)\n", uBPP));
1983 break;
1984 }
1985 }
1986 else
1987 AssertMsgFailed(("Unknown pixel format (%RU32)\n", uPixelFormat));
1988
1989 const size_t cbRGBBuf = pStream->Video.uWidth
1990 * pStream->Video.uHeight
1991 * uBytesPerPixel;
1992 AssertBreakStmt(cbRGBBuf, rc = VERR_INVALID_PARAMETER);
1993
1994 pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
1995 AssertBreakStmt(pFrame->pu8RGBBuf, rc = VERR_NO_MEMORY);
1996 pFrame->cbRGBBuf = cbRGBBuf;
1997 pFrame->uWidth = uSrcWidth;
1998 pFrame->uHeight = uSrcHeight;
1999
2000 /* If the current video frame is smaller than video resolution we're going to encode,
2001 * clear the frame beforehand to prevent artifacts. */
2002 if ( uSrcWidth < pStream->Video.uWidth
2003 || uSrcHeight < pStream->Video.uHeight)
2004 {
2005 RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
2006 }
2007
2008 /* Calculate start offset in source and destination buffers. */
2009 uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
2010 uint32_t offDst = (destY * pStream->Video.uWidth + destX) * uBytesPerPixel;
2011
2012#ifdef VBOX_VIDEOREC_DUMP
2013 VIDEORECBMPHDR bmpHdr;
2014 RT_ZERO(bmpHdr);
2015
2016 VIDEORECBMPDIBHDR bmpDIBHdr;
2017 RT_ZERO(bmpDIBHdr);
2018
2019 bmpHdr.u16Magic = 0x4d42; /* Magic */
2020 bmpHdr.u32Size = (uint32_t)(sizeof(VIDEORECBMPHDR) + sizeof(VIDEORECBMPDIBHDR) + (w * h * uBytesPerPixel));
2021 bmpHdr.u32OffBits = (uint32_t)(sizeof(VIDEORECBMPHDR) + sizeof(VIDEORECBMPDIBHDR));
2022
2023 bmpDIBHdr.u32Size = sizeof(VIDEORECBMPDIBHDR);
2024 bmpDIBHdr.u32Width = w;
2025 bmpDIBHdr.u32Height = h;
2026 bmpDIBHdr.u16Planes = 1;
2027 bmpDIBHdr.u16BitCount = uBPP;
2028 bmpDIBHdr.u32XPelsPerMeter = 5000;
2029 bmpDIBHdr.u32YPelsPerMeter = 5000;
2030
2031 char szFileName[RTPATH_MAX];
2032 RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", uScreen);
2033
2034 RTFILE fh;
2035 int rc2 = RTFileOpen(&fh, szFileName,
2036 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2037 if (RT_SUCCESS(rc2))
2038 {
2039 RTFileWrite(fh, &bmpHdr, sizeof(bmpHdr), NULL);
2040 RTFileWrite(fh, &bmpDIBHdr, sizeof(bmpDIBHdr), NULL);
2041 }
2042#endif
2043 Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
2044
2045 /* Do the copy. */
2046 for (unsigned int i = 0; i < h; i++)
2047 {
2048 /* Overflow check. */
2049 Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
2050 Assert(offDst + w * uBytesPerPixel <= pStream->Video.uHeight * pStream->Video.uWidth * uBytesPerPixel);
2051
2052 memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
2053
2054#ifdef VBOX_VIDEOREC_DUMP
2055 if (RT_SUCCESS(rc2))
2056 RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
2057#endif
2058 offSrc += uBytesPerLine;
2059 offDst += pStream->Video.uWidth * uBytesPerPixel;
2060 }
2061
2062#ifdef VBOX_VIDEOREC_DUMP
2063 if (RT_SUCCESS(rc2))
2064 RTFileClose(fh);
2065#endif
2066
2067 } while (0);
2068
2069 if (rc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
2070 {
2071 PVIDEORECBLOCK pBlock = (PVIDEORECBLOCK)RTMemAlloc(sizeof(VIDEORECBLOCK));
2072 if (pBlock)
2073 {
2074 AssertPtr(pFrame);
2075
2076 pBlock->enmType = VIDEORECBLOCKTYPE_VIDEO;
2077 pBlock->pvData = pFrame;
2078 pBlock->cbData = sizeof(VIDEORECVIDEOFRAME) + pFrame->cbRGBBuf;
2079
2080 try
2081 {
2082 VideoRecBlocks *pVideoRecBlocks = new VideoRecBlocks();
2083 pVideoRecBlocks->List.push_back(pBlock);
2084
2085 Assert(pStream->Blocks.Map.find(uTimeStampMs) == pStream->Blocks.Map.end());
2086 pStream->Blocks.Map.insert(std::make_pair(uTimeStampMs, pVideoRecBlocks));
2087 }
2088 catch (const std::exception &ex)
2089 {
2090 RT_NOREF(ex);
2091
2092 RTMemFree(pBlock);
2093 rc = VERR_NO_MEMORY;
2094 }
2095 }
2096 else
2097 rc = VERR_NO_MEMORY;
2098 }
2099
2100 if (RT_FAILURE(rc))
2101 videoRecVideoFrameFree(pFrame);
2102
2103 videoRecStreamUnlock(pStream);
2104
2105 int rc2 = RTCritSectLeave(&pCtx->CritSect);
2106 AssertRC(rc2);
2107
2108 if ( RT_SUCCESS(rc)
2109 && rc != VINF_TRY_AGAIN) /* Only signal the thread if operation was successful. */
2110 {
2111 videoRecThreadNotify(pCtx);
2112 }
2113
2114 return rc;
2115}
2116
2117/**
2118 * Frees a video recording video frame.
2119 *
2120 * @returns IPRT status code.
2121 * @param pFrame Pointer to video frame to free. The pointer will be invalid after return.
2122 */
2123static void videoRecVideoFrameFree(PVIDEORECVIDEOFRAME pFrame)
2124{
2125 if (!pFrame)
2126 return;
2127
2128 if (pFrame->pu8RGBBuf)
2129 {
2130 Assert(pFrame->cbRGBBuf);
2131 RTMemFree(pFrame->pu8RGBBuf);
2132 }
2133 RTMemFree(pFrame);
2134}
2135
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