VirtualBox

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

Last change on this file since 79294 was 76893, checked in by vboxsync, 6 years ago

Recording/Main: Use lock() and unlock() in more places.

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