VirtualBox

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

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

Recording: Bugfixes for Main and FE/Qt.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette