VirtualBox

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

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

Capturing: Build fix.

  • 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: 15.0 KB
Line 
1/* $Id: VideoRec.cpp 75254 2018-11-05 18:35:21Z 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 "ConsoleImpl.h"
46#include "VideoRec.h"
47#include "VideoRecInternals.h"
48#include "VideoRecStream.h"
49#include "VideoRecUtils.h"
50#include "WebMWriter.h"
51
52using namespace com;
53
54#ifdef DEBUG_andy
55/** Enables dumping audio / video data for debugging reasons. */
56//# define VBOX_VIDEOREC_DUMP
57#endif
58
59/**
60 * Enumeration for a video recording state.
61 */
62enum VIDEORECSTS
63{
64 /** Not initialized. */
65 VIDEORECSTS_UNINITIALIZED = 0,
66 /** Initialized. */
67 VIDEORECSTS_INITIALIZED = 1,
68 /** The usual 32-bit hack. */
69 VIDEORECSTS_32BIT_HACK = 0x7fffffff
70};
71
72
73#ifdef VBOX_VIDEOREC_DUMP
74#pragma pack(push)
75#pragma pack(1)
76typedef struct
77{
78 uint16_t u16Magic;
79 uint32_t u32Size;
80 uint16_t u16Reserved1;
81 uint16_t u16Reserved2;
82 uint32_t u32OffBits;
83} VIDEORECBMPHDR, *PVIDEORECBMPHDR;
84AssertCompileSize(VIDEORECBMPHDR, 14);
85
86typedef struct
87{
88 uint32_t u32Size;
89 uint32_t u32Width;
90 uint32_t u32Height;
91 uint16_t u16Planes;
92 uint16_t u16BitCount;
93 uint32_t u32Compression;
94 uint32_t u32SizeImage;
95 uint32_t u32XPelsPerMeter;
96 uint32_t u32YPelsPerMeter;
97 uint32_t u32ClrUsed;
98 uint32_t u32ClrImportant;
99} VIDEORECBMPDIBHDR, *PVIDEORECBMPDIBHDR;
100AssertCompileSize(VIDEORECBMPDIBHDR, 40);
101
102#pragma pack(pop)
103#endif /* VBOX_VIDEOREC_DUMP */
104
105
106CaptureContext::CaptureContext(Console *a_pConsole)
107 : pConsole(a_pConsole) { }
108
109CaptureContext::CaptureContext(Console *a_pConsole, const settings::CaptureSettings &a_Settings)
110 : pConsole(a_pConsole)
111{
112 int rc = CaptureContext::createInternal(a_Settings);
113 if (RT_FAILURE(rc))
114 throw rc;
115}
116
117CaptureContext::~CaptureContext(void)
118{
119 destroyInternal();
120}
121
122/**
123 * Worker thread for all streams of a video recording context.
124 *
125 * For video frames, this also does the RGB/YUV conversion and encoding.
126 */
127DECLCALLBACK(int) CaptureContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
128{
129 CaptureContext *pThis = (CaptureContext *)pvUser;
130
131 /* Signal that we're up and rockin'. */
132 RTThreadUserSignal(hThreadSelf);
133
134 LogFunc(("Thread started\n"));
135
136 for (;;)
137 {
138 int rc = RTSemEventWait(pThis->WaitEvent, RT_INDEFINITE_WAIT);
139 AssertRCBreak(rc);
140
141 Log2Func(("Processing %zu streams\n", pThis->vecStreams.size()));
142
143 /** @todo r=andy This is inefficient -- as we already wake up this thread
144 * for every screen from Main, we here go again (on every wake up) through
145 * all screens. */
146 VideoRecStreams::iterator itStream = pThis->vecStreams.begin();
147 while (itStream != pThis->vecStreams.end())
148 {
149 CaptureStream *pStream = (*itStream);
150
151 rc = pStream->Process(pThis->mapBlocksCommon);
152 if (RT_FAILURE(rc))
153 break;
154
155 ++itStream;
156 }
157
158 if (RT_FAILURE(rc))
159 LogRel(("VideoRec: Encoding thread failed with rc=%Rrc\n", rc));
160
161 /* Keep going in case of errors. */
162
163 if (ASMAtomicReadBool(&pThis->fShutdown))
164 {
165 LogFunc(("Thread is shutting down ...\n"));
166 break;
167 }
168
169 } /* for */
170
171 LogFunc(("Thread ended\n"));
172 return VINF_SUCCESS;
173}
174
175/**
176 * Notifies a recording context's encoding thread.
177 *
178 * @returns IPRT status code.
179 */
180int CaptureContext::threadNotify(void)
181{
182 return RTSemEventSignal(this->WaitEvent);
183}
184
185/**
186 * Creates a video recording context.
187 *
188 * @returns IPRT status code.
189 * @param a_Settings Capture settings to use for context creation.
190 */
191int CaptureContext::createInternal(const settings::CaptureSettings &a_Settings)
192{
193 int rc = RTCritSectInit(&this->CritSect);
194 if (RT_FAILURE(rc))
195 return rc;
196
197 settings::CaptureScreenMap::const_iterator itScreen = a_Settings.mapScreens.begin();
198 while (itScreen != a_Settings.mapScreens.end())
199 {
200 CaptureStream *pStream = NULL;
201 try
202 {
203 pStream = new CaptureStream(itScreen->first /* Screen ID */, itScreen->second);
204 this->vecStreams.push_back(pStream);
205 }
206 catch (std::bad_alloc &)
207 {
208 rc = VERR_NO_MEMORY;
209 break;
210 }
211 }
212
213 if (RT_SUCCESS(rc))
214 {
215 this->tsStartMs = RTTimeMilliTS();
216 this->enmState = VIDEORECSTS_UNINITIALIZED;
217 this->fStarted = false;
218 this->fShutdown = false;
219
220 /* Copy the settings to our context. */
221 this->Settings = a_Settings;
222
223 rc = RTSemEventCreate(&this->WaitEvent);
224 AssertRCReturn(rc, rc);
225
226 rc = RTThreadCreate(&this->Thread, CaptureContext::threadMain, (void *)this, 0,
227 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "VideoRec");
228
229 if (RT_SUCCESS(rc)) /* Wait for the thread to start. */
230 rc = RTThreadUserWait(this->Thread, 30 * RT_MS_1SEC /* 30s timeout */);
231
232 if (RT_SUCCESS(rc))
233 {
234 this->enmState = VIDEORECSTS_INITIALIZED;
235 this->fStarted = true;
236 }
237 }
238
239 if (RT_FAILURE(rc))
240 {
241 int rc2 = destroyInternal();
242 AssertRC(rc2);
243 }
244
245 return rc;
246}
247
248/**
249 * Destroys a video recording context.
250 */
251int CaptureContext::destroyInternal(void)
252{
253 int rc = VINF_SUCCESS;
254
255 if (this->enmState == VIDEORECSTS_INITIALIZED)
256 {
257 LogFunc(("Shutting down thread ...\n"));
258
259 /* Set shutdown indicator. */
260 ASMAtomicWriteBool(&this->fShutdown, true);
261
262 /* Signal the thread and wait for it to shut down. */
263 rc = threadNotify();
264 if (RT_SUCCESS(rc))
265 rc = RTThreadWait(this->Thread, 30 * 1000 /* 10s timeout */, NULL);
266
267 if (RT_SUCCESS(rc))
268 {
269 /* Disable the context. */
270 ASMAtomicWriteBool(&this->fStarted, false);
271
272 int rc2 = RTSemEventDestroy(this->WaitEvent);
273 AssertRC(rc2);
274
275 this->WaitEvent = NIL_RTSEMEVENT;
276 }
277 }
278
279 if (RT_FAILURE(rc))
280 {
281 AssertRC(rc);
282 return rc;
283 }
284
285 rc = RTCritSectEnter(&this->CritSect);
286 if (RT_SUCCESS(rc))
287 {
288 VideoRecStreams::iterator it = this->vecStreams.begin();
289 while (it != this->vecStreams.end())
290 {
291 CaptureStream *pStream = (*it);
292
293 int rc2 = pStream->Uninit();
294 if (RT_SUCCESS(rc))
295 rc = rc2;
296
297 delete pStream;
298 pStream = NULL;
299
300 this->vecStreams.erase(it);
301 it = this->vecStreams.begin();
302
303 delete pStream;
304 pStream = NULL;
305 }
306
307 /* Sanity. */
308 Assert(this->vecStreams.empty());
309 Assert(this->mapBlocksCommon.size() == 0);
310
311 int rc2 = RTCritSectLeave(&this->CritSect);
312 AssertRC(rc2);
313
314 RTCritSectDelete(&this->CritSect);
315 }
316
317 return rc;
318}
319
320const settings::CaptureSettings &CaptureContext::GetConfig(void) const
321{
322 return this->Settings;
323}
324
325CaptureStream *CaptureContext::getStreamInternal(unsigned uScreen) const
326{
327 CaptureStream *pStream;
328
329 try
330 {
331 pStream = this->vecStreams.at(uScreen);
332 }
333 catch (std::out_of_range &)
334 {
335 pStream = NULL;
336 }
337
338 return pStream;
339}
340
341/**
342 * Retrieves a specific recording stream of a recording context.
343 *
344 * @returns Pointer to recording stream if found, or NULL if not found.
345 * @param uScreen Screen number of recording stream to look up.
346 */
347CaptureStream *CaptureContext::GetStream(unsigned uScreen) const
348{
349 return getStreamInternal(uScreen);
350}
351
352size_t CaptureContext::GetStreamCount(void) const
353{
354 return this->vecStreams.size();
355}
356
357int CaptureContext::Create(const settings::CaptureSettings &a_Settings)
358{
359 return createInternal(a_Settings);
360}
361
362int CaptureContext::Destroy(void)
363{
364 return destroyInternal();
365}
366
367bool CaptureContext::IsFeatureEnabled(CaptureFeature_T enmFeature) const
368{
369 VideoRecStreams::const_iterator itStream = this->vecStreams.begin();
370 while (itStream != this->vecStreams.end())
371 {
372 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
373 return true;
374 ++itStream;
375 }
376
377 return false;
378}
379
380bool CaptureContext::IsReady(void) const
381{
382 return this->fStarted;
383}
384
385/**
386 * Checks if recording engine is ready to accept new recording data for a given screen.
387 *
388 * @returns true if recording engine is ready, false if not.
389 * @param uScreen Screen ID.
390 * @param uTimeStampMs Current time stamp (in ms). Currently not being used.
391 */
392bool CaptureContext::IsReady(uint32_t uScreen, uint64_t uTimeStampMs) const
393{
394 RT_NOREF(uTimeStampMs);
395
396 if (this->enmState != VIDEORECSTS_INITIALIZED)
397 return false;
398
399 bool fIsReady = false;
400
401 const CaptureStream *pStream = GetStream(uScreen);
402 if (pStream)
403 fIsReady = pStream->IsReady();
404
405 /* Note: Do not check for other constraints like the video FPS rate here,
406 * as this check then also would affect other (non-FPS related) stuff
407 * like audio data. */
408
409 return fIsReady;
410}
411
412/**
413 * Returns whether a given recording context has been started or not.
414 *
415 * @returns true if active, false if not.
416 */
417bool CaptureContext::IsStarted(void) const
418{
419 return this->fStarted;
420}
421
422/**
423 * Checks if a specified limit for recording has been reached.
424 *
425 * @returns true if any limit has been reached.
426 * @param uScreen Screen ID.
427 * @param tsNowMs Current time stamp (in ms).
428 */
429bool CaptureContext::IsLimitReached(uint32_t uScreen, uint64_t tsNowMs) const
430{
431 const CaptureStream *pStream = GetStream(uScreen);
432 if ( !pStream
433 || pStream->IsLimitReached(tsNowMs))
434 {
435 return true;
436 }
437
438 return false;
439}
440
441/**
442 * Sends an audio frame to the video encoding thread.
443 *
444 * @thread EMT
445 *
446 * @returns IPRT status code.
447 * @param pvData Audio frame data to send.
448 * @param cbData Size (in bytes) of (encoded) audio frame data.
449 * @param uTimeStampMs Time stamp (in ms) of audio playback.
450 */
451int CaptureContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t uTimeStampMs)
452{
453#ifdef VBOX_WITH_AUDIO_VIDEOREC
454 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
455 AssertReturn(cbData, VERR_INVALID_PARAMETER);
456
457 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
458 *
459 * The multiplexing is needed to supply all recorded (enabled) screens with the same
460 * audio data at the same given point in time.
461 */
462 PVIDEORECBLOCK pBlock = (PVIDEORECBLOCK)RTMemAlloc(sizeof(VIDEORECBLOCK));
463 AssertPtrReturn(pBlock, VERR_NO_MEMORY);
464 pBlock->enmType = VIDEORECBLOCKTYPE_AUDIO;
465
466 PVIDEORECAUDIOFRAME pFrame = (PVIDEORECAUDIOFRAME)RTMemAlloc(sizeof(VIDEORECAUDIOFRAME));
467 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
468
469 pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
470 AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
471 pFrame->cbBuf = cbData;
472
473 memcpy(pFrame->pvBuf, pvData, cbData);
474
475 pBlock->pvData = pFrame;
476 pBlock->cbData = sizeof(VIDEORECAUDIOFRAME) + cbData;
477 pBlock->cRefs = (uint16_t)this->vecStreams.size(); /* All streams need the same audio data. */
478 pBlock->uTimeStampMs = uTimeStampMs;
479
480 int rc = RTCritSectEnter(&this->CritSect);
481 if (RT_FAILURE(rc))
482 return rc;
483
484 try
485 {
486 VideoRecBlockMap::iterator itBlocks = this->mapBlocksCommon.find(uTimeStampMs);
487 if (itBlocks == this->mapBlocksCommon.end())
488 {
489 CaptureBlocks *pVideoRecBlocks = new CaptureBlocks();
490 pVideoRecBlocks->List.push_back(pBlock);
491
492 this->mapBlocksCommon.insert(std::make_pair(uTimeStampMs, pVideoRecBlocks));
493 }
494 else
495 itBlocks->second->List.push_back(pBlock);
496 }
497 catch (const std::exception &ex)
498 {
499 RT_NOREF(ex);
500 rc = VERR_NO_MEMORY;
501 }
502
503 int rc2 = RTCritSectLeave(&this->CritSect);
504 AssertRC(rc2);
505
506 if (RT_SUCCESS(rc))
507 rc = threadNotify();
508
509 return rc;
510#else
511 RT_NOREF(pCtx, pvData, cbData, uTimeStampMs);
512 return VINF_SUCCESS;
513#endif
514}
515
516/**
517 * Copies a source video frame to the intermediate RGB buffer.
518 * This function is executed only once per time.
519 *
520 * @thread EMT
521 *
522 * @returns IPRT status code.
523 * @param uScreen Screen number to send video frame to.
524 * @param x Starting x coordinate of the video frame.
525 * @param y Starting y coordinate of the video frame.
526 * @param uPixelFormat Pixel format.
527 * @param uBPP Bits Per Pixel (BPP).
528 * @param uBytesPerLine Bytes per scanline.
529 * @param uSrcWidth Width of the video frame.
530 * @param uSrcHeight Height of the video frame.
531 * @param puSrcData Pointer to video frame data.
532 * @param uTimeStampMs Time stamp (in ms).
533 */
534int CaptureContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
535 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
536 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
537 uint64_t uTimeStampMs)
538{
539 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
540 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
541 AssertReturn(puSrcData, VERR_INVALID_POINTER);
542
543 int rc = RTCritSectEnter(&this->CritSect);
544 AssertRC(rc);
545
546 CaptureStream *pStream = GetStream(uScreen);
547 if (!pStream)
548 {
549 rc = RTCritSectLeave(&this->CritSect);
550 AssertRC(rc);
551
552 return VERR_NOT_FOUND;
553 }
554
555 rc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, uTimeStampMs);
556
557 int rc2 = RTCritSectLeave(&this->CritSect);
558 AssertRC(rc2);
559
560 if ( RT_SUCCESS(rc)
561 && rc != VINF_TRY_AGAIN) /* Only signal the thread if operation was successful. */
562 {
563 threadNotify();
564 }
565
566 return rc;
567}
568
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