VirtualBox

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

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

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