VirtualBox

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

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

Main: scm updates

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