VirtualBox

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

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

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