VirtualBox

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

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

scm --update-copyright-year

  • 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 93115 2022-01-01 11:31:46Z 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.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 "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 rc = RecordingContext::createInternal(a_Settings);
66 if (RT_FAILURE(rc))
67 throw rc;
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 rc = RTSemEventWait(pThis->WaitEvent, RT_INDEFINITE_WAIT);
92 AssertRCBreak(rc);
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 rc = pStream->Process(pThis->mapBlocksCommon);
105 if (RT_FAILURE(rc))
106 {
107 LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), rc));
108 break;
109 }
110
111 ++itStream;
112 }
113
114 if (RT_FAILURE(rc))
115 LogRel(("Recording: Encoding thread failed (%Rrc)\n", rc));
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 rc = RTCritSectInit(&this->CritSect);
150 if (RT_FAILURE(rc))
151 return rc;
152
153 settings::RecordingScreenMap::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 rc = VERR_NO_MEMORY;
167 break;
168 }
169
170 ++itScreen;
171 }
172
173 if (RT_SUCCESS(rc))
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 rc = RTSemEventCreate(&this->WaitEvent);
183 AssertRCReturn(rc, rc);
184 }
185
186 if (RT_FAILURE(rc))
187 destroyInternal();
188
189 return rc;
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 rc = RTThreadCreate(&this->Thread, RecordingContext::threadMain, (void *)this, 0,
205 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
206
207 if (RT_SUCCESS(rc)) /* Wait for the thread to start. */
208 rc = RTThreadUserWait(this->Thread, 30 * RT_MS_1SEC /* 30s timeout */);
209
210 if (RT_SUCCESS(rc))
211 {
212 LogRel(("Recording: Started\n"));
213 this->enmState = RECORDINGSTS_STARTED;
214 }
215 else
216 Log(("Recording: Failed to start (%Rrc)\n", rc));
217
218 return rc;
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 rc = threadNotify();
238 if (RT_SUCCESS(rc))
239 rc = RTThreadWait(this->Thread, 30 * 1000 /* 10s timeout */, NULL);
240
241 lock();
242
243 if (RT_SUCCESS(rc))
244 {
245 LogRel(("Recording: Stopped\n"));
246 this->enmState = RECORDINGSTS_CREATED;
247 }
248 else
249 Log(("Recording: Failed to stop (%Rrc)\n", rc));
250
251 unlock();
252
253 LogFlowThisFunc(("%Rrc\n", rc));
254 return rc;
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 rc = stopInternal();
266 AssertRCReturnVoid(rc);
267
268 lock();
269
270 rc = RTSemEventDestroy(this->WaitEvent);
271 AssertRCReturnVoid(rc);
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 rc = pStream->Uninit();
281 AssertRC(rc);
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 {
298 Assert(RTCritSectGetWaiters(&this->CritSect) == -1);
299 RTCritSectDelete(&this->CritSect);
300 }
301
302 this->enmState = RECORDINGSTS_UNINITIALIZED;
303}
304
305/**
306 * Returns a recording context's current settings.
307 *
308 * @returns The recording context's current settings.
309 */
310const settings::RecordingSettings &RecordingContext::GetConfig(void) const
311{
312 return this->Settings;
313}
314
315/**
316 * Returns the recording stream for a specific screen.
317 *
318 * @returns Recording stream for a specific screen, or NULL if not found.
319 * @param uScreen Screen ID to retrieve recording stream for.
320 */
321RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
322{
323 RecordingStream *pStream;
324
325 try
326 {
327 pStream = this->vecStreams.at(uScreen);
328 }
329 catch (std::out_of_range &)
330 {
331 pStream = NULL;
332 }
333
334 return pStream;
335}
336
337int RecordingContext::lock(void)
338{
339 int rc = RTCritSectEnter(&this->CritSect);
340 AssertRC(rc);
341 return rc;
342}
343
344int RecordingContext::unlock(void)
345{
346 int rc = RTCritSectLeave(&this->CritSect);
347 AssertRC(rc);
348 return rc;
349}
350
351/**
352 * Retrieves a specific recording stream of a recording context.
353 *
354 * @returns Pointer to recording stream if found, or NULL if not found.
355 * @param uScreen Screen number of recording stream to look up.
356 */
357RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
358{
359 return getStreamInternal(uScreen);
360}
361
362/**
363 * Returns the number of configured recording streams for a recording context.
364 *
365 * @returns Number of configured recording streams.
366 */
367size_t RecordingContext::GetStreamCount(void) const
368{
369 return this->vecStreams.size();
370}
371
372/**
373 * Creates a new recording context.
374 *
375 * @returns IPRT status code.
376 * @param a_Settings Recording settings to use for creation.
377 *
378 */
379int RecordingContext::Create(const settings::RecordingSettings &a_Settings)
380{
381 return createInternal(a_Settings);
382}
383
384/**
385 * Destroys a recording context.
386 */
387void RecordingContext::Destroy(void)
388{
389 destroyInternal();
390}
391
392/**
393 * Starts a recording context.
394 *
395 * @returns IPRT status code.
396 */
397int RecordingContext::Start(void)
398{
399 return startInternal();
400}
401
402/**
403 * Stops a recording context.
404 */
405int RecordingContext::Stop(void)
406{
407 return stopInternal();
408}
409
410/**
411 * Returns if a specific recoding feature is enabled for at least one of the attached
412 * recording streams or not.
413 *
414 * @returns \c true if at least one recording stream has this feature enabled, or \c false if
415 * no recording stream has this feature enabled.
416 * @param enmFeature Recording feature to check for.
417 */
418bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
419{
420 lock();
421
422 RecordingStreams::const_iterator itStream = this->vecStreams.begin();
423 while (itStream != this->vecStreams.end())
424 {
425 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
426 {
427 unlock();
428 return true;
429 }
430 ++itStream;
431 }
432
433 unlock();
434
435 return false;
436}
437
438/**
439 * Returns if this recording context is ready to start recording.
440 *
441 * @returns @c true if recording context is ready, @c false if not.
442 */
443bool RecordingContext::IsReady(void)
444{
445 lock();
446
447 const bool fIsReady = this->enmState >= RECORDINGSTS_CREATED;
448
449 unlock();
450
451 return fIsReady;
452}
453
454/**
455 * Returns if this recording context is ready to accept new recording data for a given screen.
456 *
457 * @returns @c true if the specified screen is ready, @c false if not.
458 * @param uScreen Screen ID.
459 * @param msTimestamp Current timestamp (in ms). Currently not being used.
460 */
461bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
462{
463 RT_NOREF(msTimestamp);
464
465 lock();
466
467 bool fIsReady = false;
468
469 if (this->enmState != RECORDINGSTS_STARTED)
470 {
471 const RecordingStream *pStream = GetStream(uScreen);
472 if (pStream)
473 fIsReady = pStream->IsReady();
474
475 /* Note: Do not check for other constraints like the video FPS rate here,
476 * as this check then also would affect other (non-FPS related) stuff
477 * like audio data. */
478 }
479
480 unlock();
481
482 return fIsReady;
483}
484
485/**
486 * Returns whether a given recording context has been started or not.
487 *
488 * @returns true if active, false if not.
489 */
490bool RecordingContext::IsStarted(void)
491{
492 lock();
493
494 const bool fIsStarted = this->enmState == RECORDINGSTS_STARTED;
495
496 unlock();
497
498 return fIsStarted;
499}
500
501/**
502 * Checks if a specified limit for recording has been reached.
503 *
504 * @returns true if any limit has been reached.
505 */
506bool RecordingContext::IsLimitReached(void)
507{
508 lock();
509
510 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", this->cStreamsEnabled));
511
512 const bool fLimitReached = this->cStreamsEnabled == 0;
513
514 unlock();
515
516 return fLimitReached;
517}
518
519/**
520 * Checks if a specified limit for recording has been reached.
521 *
522 * @returns true if any limit has been reached.
523 * @param uScreen Screen ID.
524 * @param msTimestamp Timestamp (in ms) to check for.
525 */
526bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
527{
528 lock();
529
530 bool fLimitReached = false;
531
532 const RecordingStream *pStream = getStreamInternal(uScreen);
533 if ( !pStream
534 || pStream->IsLimitReached(msTimestamp))
535 {
536 fLimitReached = true;
537 }
538
539 unlock();
540
541 return fLimitReached;
542}
543
544DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int rc)
545{
546 RT_NOREF(uScreen, rc);
547 LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, rc));
548
549 lock();
550
551 Assert(this->cStreamsEnabled);
552 this->cStreamsEnabled--;
553
554 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", cStreamsEnabled));
555
556 unlock();
557
558 return VINF_SUCCESS;
559}
560
561/**
562 * Sends an audio frame to the video encoding thread.
563 *
564 * @thread EMT
565 *
566 * @returns IPRT status code.
567 * @param pvData Audio frame data to send.
568 * @param cbData Size (in bytes) of (encoded) audio frame data.
569 * @param msTimestamp Timestamp (in ms) of audio playback.
570 */
571int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
572{
573#ifdef VBOX_WITH_AUDIO_RECORDING
574 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
575 AssertReturn(cbData, VERR_INVALID_PARAMETER);
576
577 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
578 *
579 * The multiplexing is needed to supply all recorded (enabled) screens with the same
580 * audio data at the same given point in time.
581 */
582 RecordingBlock *pBlock = new RecordingBlock();
583 pBlock->enmType = RECORDINGBLOCKTYPE_AUDIO;
584
585 PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
586 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
587
588 pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
589 AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
590 pFrame->cbBuf = cbData;
591
592 memcpy(pFrame->pvBuf, pvData, cbData);
593
594 pBlock->pvData = pFrame;
595 pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
596 pBlock->cRefs = this->cStreamsEnabled;
597 pBlock->msTimestamp = msTimestamp;
598
599 lock();
600
601 int rc;
602
603 try
604 {
605 RecordingBlockMap::iterator itBlocks = this->mapBlocksCommon.find(msTimestamp);
606 if (itBlocks == this->mapBlocksCommon.end())
607 {
608 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
609 pRecordingBlocks->List.push_back(pBlock);
610
611 this->mapBlocksCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
612 }
613 else
614 itBlocks->second->List.push_back(pBlock);
615
616 rc = VINF_SUCCESS;
617 }
618 catch (const std::exception &ex)
619 {
620 RT_NOREF(ex);
621 rc = VERR_NO_MEMORY;
622 }
623
624 unlock();
625
626 if (RT_SUCCESS(rc))
627 rc = threadNotify();
628
629 return rc;
630#else
631 RT_NOREF(pvData, cbData, msTimestamp);
632 return VINF_SUCCESS;
633#endif
634}
635
636/**
637 * Copies a source video frame to the intermediate RGB buffer.
638 * This function is executed only once per time.
639 *
640 * @thread EMT
641 *
642 * @returns IPRT status code.
643 * @param uScreen Screen number to send video frame to.
644 * @param x Starting x coordinate of the video frame.
645 * @param y Starting y coordinate of the video frame.
646 * @param uPixelFormat Pixel format.
647 * @param uBPP Bits Per Pixel (BPP).
648 * @param uBytesPerLine Bytes per scanline.
649 * @param uSrcWidth Width of the video frame.
650 * @param uSrcHeight Height of the video frame.
651 * @param puSrcData Pointer to video frame data.
652 * @param msTimestamp Timestamp (in ms).
653 */
654int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
655 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
656 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
657 uint64_t msTimestamp)
658{
659 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
660 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
661 AssertReturn(puSrcData, VERR_INVALID_POINTER);
662
663 lock();
664
665 RecordingStream *pStream = GetStream(uScreen);
666 if (!pStream)
667 {
668 unlock();
669
670 AssertFailed();
671 return VERR_NOT_FOUND;
672 }
673
674 int rc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp);
675
676 unlock();
677
678 if ( RT_SUCCESS(rc)
679 && rc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
680 {
681 threadNotify();
682 }
683
684 return rc;
685}
686
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