VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/Recording.cpp@ 95645

Last change on this file since 95645 was 95645, checked in by vboxsync, 3 years ago

Recording/Main: Simplified and got rid of some destruction races by not dynamically (re-)allocating the recording context. Added docs, a bit of renaming. bugref:9286

  • 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: 18.0 KB
Line 
1/* $Id: Recording.cpp 95645 2022-07-14 10:33:14Z vboxsync $ */
2/** @file
3 * Recording context 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-2022 Oracle Corporation
15 *
16 * This file is part of VirtualBox Open Source Edition (OSE), as
17 * available from http://www.215389.xyz. 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 "Recording.h"
47#include "RecordingInternals.h"
48#include "RecordingStream.h"
49#include "RecordingUtils.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_RECORDING_DUMP
57#endif
58
59
60/**
61 * Recording context constructor.
62 *
63 * @note Will throw when unable to create.
64 */
65RecordingContext::RecordingContext(void)
66 : pConsole(NULL)
67 , enmState(RECORDINGSTS_UNINITIALIZED)
68 , cStreamsEnabled(0)
69{
70 int vrc = RTCritSectInit(&this->CritSect);
71 if (RT_FAILURE(vrc))
72 throw vrc;
73}
74
75/**
76 * Recording context constructor.
77 *
78 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
79 * @param settings Reference to recording settings to use for creation.
80 *
81 * @note Will throw when unable to create.
82 */
83RecordingContext::RecordingContext(Console *ptrConsole, const settings::RecordingSettings &settings)
84 : pConsole(NULL)
85 , enmState(RECORDINGSTS_UNINITIALIZED)
86 , cStreamsEnabled(0)
87{
88 int vrc = RTCritSectInit(&this->CritSect);
89 if (RT_FAILURE(vrc))
90 throw vrc;
91
92 vrc = RecordingContext::createInternal(ptrConsole, settings);
93 if (RT_FAILURE(vrc))
94 throw vrc;
95}
96
97RecordingContext::~RecordingContext(void)
98{
99 destroyInternal();
100
101 if (RTCritSectIsInitialized(&this->CritSect))
102 RTCritSectDelete(&this->CritSect);
103}
104
105/**
106 * Worker thread for all streams of a recording context.
107 *
108 * For video frames, this also does the RGB/YUV conversion and encoding.
109 */
110DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
111{
112 RecordingContext *pThis = (RecordingContext *)pvUser;
113
114 /* Signal that we're up and rockin'. */
115 RTThreadUserSignal(hThreadSelf);
116
117 LogFunc(("Thread started\n"));
118
119 for (;;)
120 {
121 int vrc = RTSemEventWait(pThis->WaitEvent, RT_INDEFINITE_WAIT);
122 AssertRCBreak(vrc);
123
124 Log2Func(("Processing %zu streams\n", pThis->vecStreams.size()));
125
126 /** @todo r=andy This is inefficient -- as we already wake up this thread
127 * for every screen from Main, we here go again (on every wake up) through
128 * all screens. */
129 RecordingStreams::iterator itStream = pThis->vecStreams.begin();
130 while (itStream != pThis->vecStreams.end())
131 {
132 RecordingStream *pStream = (*itStream);
133
134 vrc = pStream->Process(pThis->mapBlocksCommon);
135 if (RT_FAILURE(vrc))
136 {
137 LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), vrc));
138 break;
139 }
140
141 ++itStream;
142 }
143
144 if (RT_FAILURE(vrc))
145 LogRel(("Recording: Encoding thread failed (%Rrc)\n", vrc));
146
147 /* Keep going in case of errors. */
148
149 if (ASMAtomicReadBool(&pThis->fShutdown))
150 {
151 LogFunc(("Thread is shutting down ...\n"));
152 break;
153 }
154
155 } /* for */
156
157 LogFunc(("Thread ended\n"));
158 return VINF_SUCCESS;
159}
160
161/**
162 * Notifies a recording context's encoding thread.
163 *
164 * @returns IPRT status code.
165 */
166int RecordingContext::threadNotify(void)
167{
168 return RTSemEventSignal(this->WaitEvent);
169}
170
171/**
172 * Creates a recording context.
173 *
174 * @returns IPRT status code.
175 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
176 * @param settings Reference to recording settings to use for creation.
177 */
178int RecordingContext::createInternal(Console *ptrConsole, const settings::RecordingSettings &settings)
179{
180 int vrc = VINF_SUCCESS;
181
182 this->pConsole = ptrConsole;
183
184 settings::RecordingScreenSettingsMap::const_iterator itScreen = settings.mapScreens.begin();
185 while (itScreen != settings.mapScreens.end())
186 {
187 RecordingStream *pStream = NULL;
188 try
189 {
190 pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
191 this->vecStreams.push_back(pStream);
192 if (itScreen->second.fEnabled)
193 this->cStreamsEnabled++;
194 }
195 catch (std::bad_alloc &)
196 {
197 vrc = VERR_NO_MEMORY;
198 break;
199 }
200
201 ++itScreen;
202 }
203
204 if (RT_SUCCESS(vrc))
205 {
206 this->tsStartMs = RTTimeMilliTS();
207 this->enmState = RECORDINGSTS_CREATED;
208 this->fShutdown = false;
209
210 /* Copy the settings to our context. */
211 this->Settings = settings;
212
213 vrc = RTSemEventCreate(&this->WaitEvent);
214 AssertRCReturn(vrc, vrc);
215 }
216
217 if (RT_FAILURE(vrc))
218 destroyInternal();
219
220 return vrc;
221}
222
223/**
224 * Starts a recording context by creating its worker thread.
225 *
226 * @returns IPRT status code.
227 */
228int RecordingContext::startInternal(void)
229{
230 if (this->enmState == RECORDINGSTS_STARTED)
231 return VINF_SUCCESS;
232
233 Assert(this->enmState == RECORDINGSTS_CREATED);
234
235 int vrc = RTThreadCreate(&this->Thread, RecordingContext::threadMain, (void *)this, 0,
236 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
237
238 if (RT_SUCCESS(vrc)) /* Wait for the thread to start. */
239 vrc = RTThreadUserWait(this->Thread, RT_MS_30SEC /* 30s timeout */);
240
241 if (RT_SUCCESS(vrc))
242 {
243 LogRel(("Recording: Started\n"));
244 this->enmState = RECORDINGSTS_STARTED;
245 }
246 else
247 Log(("Recording: Failed to start (%Rrc)\n", vrc));
248
249 return vrc;
250}
251
252/**
253 * Stops a recording context by telling the worker thread to stop and finalizing its operation.
254 *
255 * @returns IPRT status code.
256 */
257int RecordingContext::stopInternal(void)
258{
259 if (this->enmState != RECORDINGSTS_STARTED)
260 return VINF_SUCCESS;
261
262 LogThisFunc(("Shutting down thread ...\n"));
263
264 /* Set shutdown indicator. */
265 ASMAtomicWriteBool(&this->fShutdown, true);
266
267 /* Signal the thread and wait for it to shut down. */
268 int vrc = threadNotify();
269 if (RT_SUCCESS(vrc))
270 vrc = RTThreadWait(this->Thread, RT_MS_30SEC /* 30s timeout */, NULL);
271
272 lock();
273
274 if (RT_SUCCESS(vrc))
275 {
276 LogRel(("Recording: Stopped\n"));
277 this->enmState = RECORDINGSTS_CREATED;
278 }
279 else
280 Log(("Recording: Failed to stop (%Rrc)\n", vrc));
281
282 unlock();
283
284 LogFlowThisFunc(("%Rrc\n", vrc));
285 return vrc;
286}
287
288/**
289 * Destroys a recording context, internal version.
290 */
291void RecordingContext::destroyInternal(void)
292{
293 lock();
294
295 if (this->enmState == RECORDINGSTS_UNINITIALIZED)
296 {
297 unlock();
298 return;
299 }
300
301 int vrc = stopInternal();
302 AssertRCReturnVoid(vrc);
303
304 vrc = RTSemEventDestroy(this->WaitEvent);
305 AssertRCReturnVoid(vrc);
306
307 this->WaitEvent = NIL_RTSEMEVENT;
308
309 RecordingStreams::iterator it = this->vecStreams.begin();
310 while (it != this->vecStreams.end())
311 {
312 RecordingStream *pStream = (*it);
313
314 vrc = pStream->Uninit();
315 AssertRC(vrc);
316
317 delete pStream;
318 pStream = NULL;
319
320 this->vecStreams.erase(it);
321 it = this->vecStreams.begin();
322 }
323
324 /* Sanity. */
325 Assert(this->vecStreams.empty());
326 Assert(this->mapBlocksCommon.size() == 0);
327
328 this->enmState = RECORDINGSTS_UNINITIALIZED;
329
330 unlock();
331}
332
333/**
334 * Returns a recording context's current settings.
335 *
336 * @returns The recording context's current settings.
337 */
338const settings::RecordingSettings &RecordingContext::GetConfig(void) const
339{
340 return this->Settings;
341}
342
343/**
344 * Returns the recording stream for a specific screen.
345 *
346 * @returns Recording stream for a specific screen, or NULL if not found.
347 * @param uScreen Screen ID to retrieve recording stream for.
348 */
349RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
350{
351 RecordingStream *pStream;
352
353 try
354 {
355 pStream = this->vecStreams.at(uScreen);
356 }
357 catch (std::out_of_range &)
358 {
359 pStream = NULL;
360 }
361
362 return pStream;
363}
364
365int RecordingContext::lock(void)
366{
367 int vrc = RTCritSectEnter(&this->CritSect);
368 AssertRC(vrc);
369 return vrc;
370}
371
372int RecordingContext::unlock(void)
373{
374 int vrc = RTCritSectLeave(&this->CritSect);
375 AssertRC(vrc);
376 return vrc;
377}
378
379/**
380 * Retrieves a specific recording stream of a recording context.
381 *
382 * @returns Pointer to recording stream if found, or NULL if not found.
383 * @param uScreen Screen number of recording stream to look up.
384 */
385RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
386{
387 return getStreamInternal(uScreen);
388}
389
390/**
391 * Returns the number of configured recording streams for a recording context.
392 *
393 * @returns Number of configured recording streams.
394 */
395size_t RecordingContext::GetStreamCount(void) const
396{
397 return this->vecStreams.size();
398}
399
400/**
401 * Creates a new recording context.
402 *
403 * @returns IPRT status code.
404 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
405 * @param settings Reference to recording settings to use for creation.
406 */
407int RecordingContext::Create(Console *ptrConsole, const settings::RecordingSettings &settings)
408{
409 return createInternal(ptrConsole, settings);
410}
411
412/**
413 * Destroys a recording context.
414 */
415void RecordingContext::Destroy(void)
416{
417 destroyInternal();
418}
419
420/**
421 * Starts a recording context.
422 *
423 * @returns IPRT status code.
424 */
425int RecordingContext::Start(void)
426{
427 return startInternal();
428}
429
430/**
431 * Stops a recording context.
432 */
433int RecordingContext::Stop(void)
434{
435 return stopInternal();
436}
437
438/**
439 * Returns if a specific recoding feature is enabled for at least one of the attached
440 * recording streams or not.
441 *
442 * @returns \c true if at least one recording stream has this feature enabled, or \c false if
443 * no recording stream has this feature enabled.
444 * @param enmFeature Recording feature to check for.
445 */
446bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
447{
448 lock();
449
450 RecordingStreams::const_iterator itStream = this->vecStreams.begin();
451 while (itStream != this->vecStreams.end())
452 {
453 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
454 {
455 unlock();
456 return true;
457 }
458 ++itStream;
459 }
460
461 unlock();
462
463 return false;
464}
465
466/**
467 * Returns if this recording context is ready to start recording.
468 *
469 * @returns @c true if recording context is ready, @c false if not.
470 */
471bool RecordingContext::IsReady(void)
472{
473 lock();
474
475 const bool fIsReady = this->enmState >= RECORDINGSTS_CREATED;
476
477 unlock();
478
479 return fIsReady;
480}
481
482/**
483 * Returns if this recording context is ready to accept new recording data for a given screen.
484 *
485 * @returns @c true if the specified screen is ready, @c false if not.
486 * @param uScreen Screen ID.
487 * @param msTimestamp Current timestamp (in ms). Currently not being used.
488 */
489bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
490{
491 RT_NOREF(msTimestamp);
492
493 lock();
494
495 bool fIsReady = false;
496
497 if (this->enmState != RECORDINGSTS_STARTED)
498 {
499 const RecordingStream *pStream = GetStream(uScreen);
500 if (pStream)
501 fIsReady = pStream->IsReady();
502
503 /* Note: Do not check for other constraints like the video FPS rate here,
504 * as this check then also would affect other (non-FPS related) stuff
505 * like audio data. */
506 }
507
508 unlock();
509
510 return fIsReady;
511}
512
513/**
514 * Returns whether a given recording context has been started or not.
515 *
516 * @returns true if active, false if not.
517 */
518bool RecordingContext::IsStarted(void)
519{
520 lock();
521
522 const bool fIsStarted = this->enmState == RECORDINGSTS_STARTED;
523
524 unlock();
525
526 return fIsStarted;
527}
528
529/**
530 * Checks if a specified limit for recording has been reached.
531 *
532 * @returns true if any limit has been reached.
533 */
534bool RecordingContext::IsLimitReached(void)
535{
536 lock();
537
538 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", this->cStreamsEnabled));
539
540 const bool fLimitReached = this->cStreamsEnabled == 0;
541
542 unlock();
543
544 return fLimitReached;
545}
546
547/**
548 * Checks if a specified limit for recording has been reached.
549 *
550 * @returns true if any limit has been reached.
551 * @param uScreen Screen ID.
552 * @param msTimestamp Timestamp (in ms) to check for.
553 */
554bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
555{
556 lock();
557
558 bool fLimitReached = false;
559
560 const RecordingStream *pStream = getStreamInternal(uScreen);
561 if ( !pStream
562 || pStream->IsLimitReached(msTimestamp))
563 {
564 fLimitReached = true;
565 }
566
567 unlock();
568
569 return fLimitReached;
570}
571
572DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int rc)
573{
574 RT_NOREF(uScreen, rc);
575 LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, rc));
576
577 lock();
578
579 Assert(this->cStreamsEnabled);
580 this->cStreamsEnabled--;
581
582 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", cStreamsEnabled));
583
584 unlock();
585
586 return VINF_SUCCESS;
587}
588
589/**
590 * Sends an audio frame to the video encoding thread.
591 *
592 * @thread EMT
593 *
594 * @returns IPRT status code.
595 * @param pvData Audio frame data to send.
596 * @param cbData Size (in bytes) of (encoded) audio frame data.
597 * @param msTimestamp Timestamp (in ms) of audio playback.
598 */
599int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
600{
601#ifdef VBOX_WITH_AUDIO_RECORDING
602 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
603 AssertReturn(cbData, VERR_INVALID_PARAMETER);
604
605 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
606 *
607 * The multiplexing is needed to supply all recorded (enabled) screens with the same
608 * audio data at the same given point in time.
609 */
610 RecordingBlock *pBlock = new RecordingBlock();
611 pBlock->enmType = RECORDINGBLOCKTYPE_AUDIO;
612
613 PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
614 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
615
616 pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
617 AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
618 pFrame->cbBuf = cbData;
619
620 memcpy(pFrame->pvBuf, pvData, cbData);
621
622 pBlock->pvData = pFrame;
623 pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
624 pBlock->cRefs = this->cStreamsEnabled;
625 pBlock->msTimestamp = msTimestamp;
626
627 lock();
628
629 int vrc;
630
631 try
632 {
633 RecordingBlockMap::iterator itBlocks = this->mapBlocksCommon.find(msTimestamp);
634 if (itBlocks == this->mapBlocksCommon.end())
635 {
636 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
637 pRecordingBlocks->List.push_back(pBlock);
638
639 this->mapBlocksCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
640 }
641 else
642 itBlocks->second->List.push_back(pBlock);
643
644 vrc = VINF_SUCCESS;
645 }
646 catch (const std::exception &ex)
647 {
648 RT_NOREF(ex);
649 vrc = VERR_NO_MEMORY;
650 }
651
652 unlock();
653
654 if (RT_SUCCESS(vrc))
655 vrc = threadNotify();
656
657 return vrc;
658#else
659 RT_NOREF(pvData, cbData, msTimestamp);
660 return VINF_SUCCESS;
661#endif
662}
663
664/**
665 * Copies a source video frame to the intermediate RGB buffer.
666 * This function is executed only once per time.
667 *
668 * @thread EMT
669 *
670 * @returns IPRT status code.
671 * @param uScreen Screen number to send video frame to.
672 * @param x Starting x coordinate of the video frame.
673 * @param y Starting y coordinate of the video frame.
674 * @param uPixelFormat Pixel format.
675 * @param uBPP Bits Per Pixel (BPP).
676 * @param uBytesPerLine Bytes per scanline.
677 * @param uSrcWidth Width of the video frame.
678 * @param uSrcHeight Height of the video frame.
679 * @param puSrcData Pointer to video frame data.
680 * @param msTimestamp Timestamp (in ms).
681 */
682int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
683 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
684 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
685 uint64_t msTimestamp)
686{
687 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
688 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
689 AssertReturn(puSrcData, VERR_INVALID_POINTER);
690
691 lock();
692
693 RecordingStream *pStream = GetStream(uScreen);
694 if (!pStream)
695 {
696 unlock();
697
698 AssertFailed();
699 return VERR_NOT_FOUND;
700 }
701
702 int vrc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp);
703
704 unlock();
705
706 if ( RT_SUCCESS(vrc)
707 && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
708 {
709 threadNotify();
710 }
711
712 return vrc;
713}
714
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