VirtualBox

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

Last change on this file since 105010 was 105010, checked in by vboxsync, 8 months ago

Video Recording: Big revamp to improve overall performance. We now don't rely on the periodic display refresh callback anymore to render the entire framebuffer but now rely on delta updates ("dirty rectangles"). Also, we now only encode new frames when an area has changed. This also needed cursor position + change change notifications, as we render the cursor on the host side if mouse integration is enabled (requires 7.1 Guest Additions as of now). Optimized the BGRA32->YUV IV420 color space conversion as well as the overall amount of pixel data shuffled forth and back. Added a new testcase for the cropping/centering code [build fixes]. bugref:10650

  • 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: 32.3 KB
Line 
1/* $Id: Recording.cpp 105010 2024-06-24 18:47:56Z 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-2023 Oracle and/or its affiliates.
15 *
16 * This file is part of VirtualBox base platform packages, as
17 * available from https://www.virtualbox.org.
18 *
19 * This program is free software; you can redistribute it and/or
20 * modify it under the terms of the GNU General Public License
21 * as published by the Free Software Foundation, in version 3 of the
22 * License.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, see <https://www.gnu.org/licenses>.
31 *
32 * SPDX-License-Identifier: GPL-3.0-only
33 */
34
35#ifdef LOG_GROUP
36# undef LOG_GROUP
37#endif
38#define LOG_GROUP LOG_GROUP_RECORDING
39#include "LoggingNew.h"
40
41#include <stdexcept>
42#include <vector>
43
44#include <iprt/asm.h>
45#include <iprt/assert.h>
46#include <iprt/critsect.h>
47#include <iprt/path.h>
48#include <iprt/semaphore.h>
49#include <iprt/thread.h>
50#include <iprt/time.h>
51
52#include <VBox/err.h>
53#include <VBox/com/VirtualBox.h>
54
55#include "ConsoleImpl.h"
56#include "Recording.h"
57#include "RecordingInternals.h"
58#include "RecordingStream.h"
59#include "RecordingUtils.h"
60#include "WebMWriter.h"
61
62using namespace com;
63
64#ifdef DEBUG_andy
65/** Enables dumping audio / video data for debugging reasons. */
66//# define VBOX_RECORDING_DUMP
67#endif
68
69
70
71RecordingCursorState::RecordingCursorState()
72 : m_fFlags(VBOX_RECORDING_CURSOR_F_NONE)
73{
74 m_Shape.Pos.x = UINT16_MAX;
75 m_Shape.Pos.y = UINT16_MAX;
76
77 RT_ZERO(m_Shape);
78}
79
80RecordingCursorState::~RecordingCursorState()
81{
82 Destroy();
83}
84
85/**
86 * Destroys a cursor state.
87 */
88void RecordingCursorState::Destroy(void)
89{
90 RecordingVideoFrameDestroy(&m_Shape);
91}
92
93/**
94 * Creates or updates the cursor shape.
95 *
96 * @returns VBox status code.
97 * @param fAlpha Whether the pixel data contains alpha channel information or not.
98 * @param uWidth Width (in pixel) of new cursor shape.
99 * @param uHeight Height (in pixel) of new cursor shape.
100 * @param pu8Shape Pixel data of new cursor shape.
101 * @param cbShape Bytes of \a pu8Shape.
102 */
103int RecordingCursorState::CreateOrUpdate(bool fAlpha, uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape)
104{
105 int vrc;
106
107 uint32_t fFlags = RECORDINGVIDEOFRAME_F_VISIBLE;
108
109 const uint8_t uBPP = 32; /* Seems to be fixed. */
110
111 uint32_t offShape;
112 if (fAlpha)
113 {
114 /* Calculate the offset to the actual pixel data. */
115 offShape = (uWidth + 7) / 8 * uHeight; /* size of the AND mask */
116 offShape = (offShape + 3) & ~3;
117 AssertReturn(offShape <= cbShape, VERR_INVALID_PARAMETER);
118 fFlags |= RECORDINGVIDEOFRAME_F_BLIT_ALPHA;
119 }
120 else
121 offShape = 0;
122
123 /* Cursor shape size has become bigger? Reallocate. */
124 if (cbShape > m_Shape.cbBuf)
125 {
126 RecordingVideoFrameDestroy(&m_Shape);
127 vrc = RecordingVideoFrameInit(&m_Shape, fFlags, uWidth, uHeight, 0 /* posX */, 0 /* posY */,
128 uBPP, RECORDINGPIXELFMT_BRGA32);
129 }
130 else /* Otherwise just zero out first. */
131 {
132 RecordingVideoFrameClear(&m_Shape);
133 vrc = VINF_SUCCESS;
134 }
135
136 if (RT_SUCCESS(vrc))
137 vrc = RecordingVideoFrameBlitRaw(&m_Shape, 0, 0, &pu8Shape[offShape], cbShape - offShape, 0, 0, uWidth, uHeight, uWidth * 4 /* BPP */, uBPP,
138 m_Shape.Info.enmPixelFmt);
139#ifdef DEBUG_andy_disabled
140 RecordingUtilsDbgDumpVideoFrameEx(&m_Shape, "/tmp/recording", "cursor-update");
141#endif
142
143 return vrc;
144}
145
146/**
147 * Moves (sets) the cursor to a new position.
148 *
149 * @returns VBox status code.
150 * @retval VERR_NO_CHANGE if the cursor wasn't moved (set).
151 * @param iX New X position to set.
152 * @param iY New Y position to set.
153 */
154int RecordingCursorState::Move(int32_t iX, int32_t iY)
155{
156 /* No relative coordinates here. */
157 if ( iX < 0
158 || iY < 0)
159 return VERR_NO_CHANGE;
160
161 if ( m_Shape.Pos.x == (uint32_t)iX
162 && m_Shape.Pos.y == (uint32_t)iY)
163 return VERR_NO_CHANGE;
164
165 m_Shape.Pos.x = (uint16_t)iX;
166 m_Shape.Pos.y = (uint16_t)iY;
167
168 return VINF_SUCCESS;
169}
170
171
172//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
173
174
175/**
176 * Recording context constructor.
177 *
178 * @note Will throw vrc when unable to create.
179 */
180RecordingContext::RecordingContext(void)
181 : m_pConsole(NULL)
182 , m_enmState(RECORDINGSTS_UNINITIALIZED)
183 , m_cStreamsEnabled(0)
184{
185 int vrc = RTCritSectInit(&m_CritSect);
186 if (RT_FAILURE(vrc))
187 throw vrc;
188}
189
190/**
191 * Recording context constructor.
192 *
193 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
194 * @param Settings Reference to recording settings to use for creation.
195 *
196 * @note Will throw vrc when unable to create.
197 */
198RecordingContext::RecordingContext(Console *ptrConsole, const settings::RecordingSettings &Settings)
199 : m_pConsole(NULL)
200 , m_enmState(RECORDINGSTS_UNINITIALIZED)
201 , m_cStreamsEnabled(0)
202{
203 int vrc = RTCritSectInit(&m_CritSect);
204 if (RT_FAILURE(vrc))
205 throw vrc;
206
207 vrc = RecordingContext::createInternal(ptrConsole, Settings);
208 if (RT_FAILURE(vrc))
209 throw vrc;
210}
211
212RecordingContext::~RecordingContext(void)
213{
214 destroyInternal();
215
216 if (RTCritSectIsInitialized(&m_CritSect))
217 RTCritSectDelete(&m_CritSect);
218}
219
220/**
221 * Worker thread for all streams of a recording context.
222 *
223 * For video frames, this also does the RGB/YUV conversion and encoding.
224 */
225DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
226{
227 RecordingContext *pThis = (RecordingContext *)pvUser;
228
229 /* Signal that we're up and rockin'. */
230 RTThreadUserSignal(hThreadSelf);
231
232 LogRel2(("Recording: Thread started\n"));
233
234 for (;;)
235 {
236 int vrcWait = RTSemEventWait(pThis->m_WaitEvent, RT_MS_1SEC);
237
238 Log2Func(("Processing %zu streams (wait = %Rrc)\n", pThis->m_vecStreams.size(), vrcWait));
239
240 /* Process common raw blocks (data which not has been encoded yet). */
241 int vrc = pThis->processCommonData(pThis->m_mapBlocksRaw, 100 /* ms timeout */);
242
243 /** @todo r=andy This is inefficient -- as we already wake up this thread
244 * for every screen from Main, we here go again (on every wake up) through
245 * all screens. */
246 RecordingStreams::iterator itStream = pThis->m_vecStreams.begin();
247 while (itStream != pThis->m_vecStreams.end())
248 {
249 RecordingStream *pStream = (*itStream);
250
251 /* Hand-in common encoded blocks. */
252 vrc = pStream->ThreadMain(vrcWait, pThis->m_mapBlocksEncoded);
253 if (RT_FAILURE(vrc))
254 {
255 LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), vrc));
256 break;
257 }
258
259 ++itStream;
260 }
261
262 if (RT_FAILURE(vrc))
263 LogRel(("Recording: Encoding thread failed (%Rrc)\n", vrc));
264
265 /* Keep going in case of errors. */
266
267 if (ASMAtomicReadBool(&pThis->m_fShutdown))
268 {
269 LogFunc(("Thread is shutting down ...\n"));
270 break;
271 }
272
273 } /* for */
274
275 LogRel2(("Recording: Thread ended\n"));
276 return VINF_SUCCESS;
277}
278
279/**
280 * Notifies a recording context's encoding thread.
281 *
282 * @returns VBox status code.
283 */
284int RecordingContext::threadNotify(void)
285{
286 return RTSemEventSignal(m_WaitEvent);
287}
288
289/**
290 * Worker function for processing common block data.
291 *
292 * @returns VBox status code.
293 * @param mapCommon Common block map to handle.
294 * @param msTimeout Timeout to use for maximum time spending to process data.
295 * Use RT_INDEFINITE_WAIT for processing all data.
296 *
297 * @note Runs in recording thread.
298 */
299int RecordingContext::processCommonData(RecordingBlockMap &mapCommon, RTMSINTERVAL msTimeout)
300{
301 Log2Func(("Processing %zu common blocks (%RU32ms timeout)\n", mapCommon.size(), msTimeout));
302
303 int vrc = VINF_SUCCESS;
304
305 uint64_t const msStart = RTTimeMilliTS();
306 RecordingBlockMap::iterator itCommonBlocks = mapCommon.begin();
307 while (itCommonBlocks != mapCommon.end())
308 {
309 RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
310 while (itBlock != itCommonBlocks->second->List.end())
311 {
312 RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
313 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)pBlockCommon->pvData;
314 AssertPtr(pFrame);
315 switch (pFrame->enmType)
316 {
317#ifdef VBOX_WITH_AUDIO_RECORDING
318 case RECORDINGFRAME_TYPE_AUDIO:
319 {
320 vrc = recordingCodecEncodeFrame(&m_CodecAudio, pFrame, pFrame->msTimestamp, NULL /* pvUser */);
321 break;
322 }
323#endif /* VBOX_WITH_AUDIO_RECORDING */
324
325 default:
326 /* Skip unknown stuff. */
327 break;
328 }
329
330 itCommonBlocks->second->List.erase(itBlock);
331 delete pBlockCommon;
332 itBlock = itCommonBlocks->second->List.begin();
333
334 if (RT_FAILURE(vrc) || RTTimeMilliTS() > msStart + msTimeout)
335 break;
336 }
337
338 /* If no entries are left over in the block map, remove it altogether. */
339 if (itCommonBlocks->second->List.empty())
340 {
341 delete itCommonBlocks->second;
342 mapCommon.erase(itCommonBlocks);
343 itCommonBlocks = mapCommon.begin();
344 }
345 else
346 ++itCommonBlocks;
347
348 if (RT_FAILURE(vrc))
349 break;
350 }
351
352 return vrc;
353}
354
355/**
356 * Writes common block data (i.e. shared / the same) in all streams.
357 *
358 * The multiplexing is needed to supply all recorded (enabled) screens with the same
359 * data at the same given point in time.
360 *
361 * Currently this only is being used for audio data.
362 *
363 * @returns VBox status code.
364 * @param mapCommon Common block map to write data to.
365 * @param pCodec Pointer to codec instance which has written the data.
366 * @param pvData Pointer to written data (encoded).
367 * @param cbData Size (in bytes) of \a pvData.
368 * @param msTimestamp Absolute PTS (in ms) of the written data.
369 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
370 */
371int RecordingContext::writeCommonData(RecordingBlockMap &mapCommon, PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
372 uint64_t msTimestamp, uint32_t uFlags)
373{
374 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
375 AssertReturn(cbData, VERR_INVALID_PARAMETER);
376
377 LogFlowFunc(("pCodec=%p, cbData=%zu, msTimestamp=%zu, uFlags=%#x\n",
378 pCodec, cbData, msTimestamp, uFlags));
379
380 RECORDINGFRAME_TYPE const enmType = pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
381 ? RECORDINGFRAME_TYPE_AUDIO : RECORDINGFRAME_TYPE_INVALID;
382
383 AssertReturn(enmType != RECORDINGFRAME_TYPE_INVALID, VERR_NOT_SUPPORTED);
384
385 PRECORDINGFRAME pFrame = NULL;
386
387 switch (enmType)
388 {
389#ifdef VBOX_WITH_AUDIO_RECORDING
390 case RECORDINGFRAME_TYPE_AUDIO:
391 {
392 pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
393 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
394 pFrame->enmType = RECORDINGFRAME_TYPE_AUDIO;
395 pFrame->msTimestamp = msTimestamp;
396
397 PRECORDINGAUDIOFRAME pAudioFrame = &pFrame->u.Audio;
398 pAudioFrame->pvBuf = (uint8_t *)RTMemDup(pvData, cbData);
399 AssertPtrReturn(pAudioFrame->pvBuf, VERR_NO_MEMORY);
400 pAudioFrame->cbBuf = cbData;
401 break;
402 }
403#endif
404 default:
405 AssertFailed();
406 break;
407 }
408
409 if (!pFrame)
410 return VINF_SUCCESS;
411
412 lock();
413
414 int vrc;
415
416 RecordingBlock *pBlock = NULL;
417 try
418 {
419 pBlock = new RecordingBlock();
420
421 pBlock->pvData = pFrame;
422 pBlock->cbData = sizeof(RECORDINGFRAME);
423 pBlock->cRefs = m_cStreamsEnabled;
424 pBlock->msTimestamp = msTimestamp;
425 pBlock->uFlags = uFlags;
426
427 RecordingBlockMap::iterator itBlocks = mapCommon.find(msTimestamp);
428 if (itBlocks == mapCommon.end())
429 {
430 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
431 pRecordingBlocks->List.push_back(pBlock);
432
433 mapCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
434 }
435 else
436 itBlocks->second->List.push_back(pBlock);
437
438 vrc = VINF_SUCCESS;
439 }
440 catch (const std::exception &)
441 {
442 vrc = VERR_NO_MEMORY;
443 }
444
445 unlock();
446
447 if (RT_SUCCESS(vrc))
448 {
449 vrc = threadNotify();
450 }
451 else
452 {
453 if (pBlock)
454 delete pBlock;
455 RecordingFrameFree(pFrame);
456 }
457
458 return vrc;
459}
460
461#ifdef VBOX_WITH_AUDIO_RECORDING
462/**
463 * Callback function for writing encoded audio data into the common encoded block map.
464 *
465 * This is called by the audio codec when finishing encoding audio data.
466 *
467 * @copydoc RECORDINGCODECCALLBACKS::pfnWriteData
468 */
469/* static */
470DECLCALLBACK(int) RecordingContext::audioCodecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
471 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
472{
473 RecordingContext *pThis = (RecordingContext *)pvUser;
474 return pThis->writeCommonData(pThis->m_mapBlocksEncoded, pCodec, pvData, cbData, msAbsPTS, uFlags);
475}
476
477/**
478 * Initializes the audio codec for a (multiplexing) recording context.
479 *
480 * @returns VBox status code.
481 * @param screenSettings Reference to recording screen settings to use for initialization.
482 */
483int RecordingContext::audioInit(const settings::RecordingScreenSettings &screenSettings)
484{
485 RecordingAudioCodec_T const enmCodec = screenSettings.Audio.enmCodec;
486
487 if (enmCodec == RecordingAudioCodec_None)
488 {
489 LogRel2(("Recording: No audio codec configured, skipping audio init\n"));
490 return VINF_SUCCESS;
491 }
492
493 RECORDINGCODECCALLBACKS Callbacks;
494 Callbacks.pvUser = this;
495 Callbacks.pfnWriteData = RecordingContext::audioCodecWriteDataCallback;
496
497 int vrc = recordingCodecCreateAudio(&m_CodecAudio, enmCodec);
498 if (RT_SUCCESS(vrc))
499 vrc = recordingCodecInit(&m_CodecAudio, &Callbacks, screenSettings);
500
501 return vrc;
502}
503#endif /* VBOX_WITH_AUDIO_RECORDING */
504
505/**
506 * Creates a recording context.
507 *
508 * @returns VBox status code.
509 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
510 * @param Settings Reference to recording settings to use for creation.
511 */
512int RecordingContext::createInternal(Console *ptrConsole, const settings::RecordingSettings &Settings)
513{
514 int vrc = VINF_SUCCESS;
515
516 /* Copy the settings to our context. */
517 m_Settings = Settings;
518
519#ifdef VBOX_WITH_AUDIO_RECORDING
520 settings::RecordingScreenSettingsMap::const_iterator itScreen0 = m_Settings.mapScreens.begin();
521 AssertReturn(itScreen0 != m_Settings.mapScreens.end(), VERR_WRONG_ORDER);
522
523 /* We always use the audio settings from screen 0, as we multiplex the audio data anyway. */
524 settings::RecordingScreenSettings const &screen0Settings = itScreen0->second;
525
526 vrc = this->audioInit(screen0Settings);
527 if (RT_FAILURE(vrc))
528 return vrc;
529#endif
530
531 m_pConsole = ptrConsole;
532
533 settings::RecordingScreenSettingsMap::const_iterator itScreen = m_Settings.mapScreens.begin();
534 while (itScreen != m_Settings.mapScreens.end())
535 {
536 RecordingStream *pStream = NULL;
537 try
538 {
539 pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
540 m_vecStreams.push_back(pStream);
541 if (itScreen->second.fEnabled)
542 m_cStreamsEnabled++;
543 LogFlowFunc(("pStream=%p\n", pStream));
544 }
545 catch (std::bad_alloc &)
546 {
547 vrc = VERR_NO_MEMORY;
548 break;
549 }
550 catch (int vrc_thrown) /* Catch vrc thrown by constructor. */
551 {
552 vrc = vrc_thrown;
553 break;
554 }
555
556 ++itScreen;
557 }
558
559 if (RT_SUCCESS(vrc))
560 {
561 m_tsStartMs = 0;
562 m_enmState = RECORDINGSTS_CREATED;
563 m_fShutdown = false;
564
565 vrc = RTSemEventCreate(&m_WaitEvent);
566 AssertRCReturn(vrc, vrc);
567 }
568
569 if (RT_FAILURE(vrc))
570 destroyInternal();
571
572 return vrc;
573}
574
575/**
576 * Starts a recording context by creating its worker thread.
577 *
578 * @returns VBox status code.
579 */
580int RecordingContext::startInternal(void)
581{
582 if (m_enmState == RECORDINGSTS_STARTED)
583 return VINF_SUCCESS;
584
585 Assert(m_enmState == RECORDINGSTS_CREATED);
586
587 m_tsStartMs = RTTimeMilliTS();
588
589 int vrc = RTThreadCreate(&m_Thread, RecordingContext::threadMain, (void *)this, 0,
590 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
591
592 if (RT_SUCCESS(vrc)) /* Wait for the thread to start. */
593 vrc = RTThreadUserWait(m_Thread, RT_MS_30SEC /* 30s timeout */);
594
595 if (RT_SUCCESS(vrc))
596 {
597 LogRel(("Recording: Started\n"));
598 m_enmState = RECORDINGSTS_STARTED;
599 }
600 else
601 Log(("Recording: Failed to start (%Rrc)\n", vrc));
602
603 return vrc;
604}
605
606/**
607 * Stops a recording context by telling the worker thread to stop and finalizing its operation.
608 *
609 * @returns VBox status code.
610 */
611int RecordingContext::stopInternal(void)
612{
613 if (m_enmState != RECORDINGSTS_STARTED)
614 return VINF_SUCCESS;
615
616 LogThisFunc(("Shutting down thread ...\n"));
617
618 /* Set shutdown indicator. */
619 ASMAtomicWriteBool(&m_fShutdown, true);
620
621 /* Signal the thread and wait for it to shut down. */
622 int vrc = threadNotify();
623 if (RT_SUCCESS(vrc))
624 vrc = RTThreadWait(m_Thread, RT_MS_30SEC /* 30s timeout */, NULL);
625
626 lock();
627
628 if (RT_SUCCESS(vrc))
629 {
630 LogRel(("Recording: Stopped\n"));
631 m_tsStartMs = 0;
632 m_enmState = RECORDINGSTS_CREATED;
633 }
634 else
635 Log(("Recording: Failed to stop (%Rrc)\n", vrc));
636
637 unlock();
638
639 LogFlowThisFunc(("%Rrc\n", vrc));
640 return vrc;
641}
642
643/**
644 * Destroys a recording context, internal version.
645 */
646void RecordingContext::destroyInternal(void)
647{
648 lock();
649
650 if (m_enmState == RECORDINGSTS_UNINITIALIZED)
651 {
652 unlock();
653 return;
654 }
655
656 int vrc = stopInternal();
657 AssertRCReturnVoid(vrc);
658
659 vrc = RTSemEventDestroy(m_WaitEvent);
660 AssertRCReturnVoid(vrc);
661
662 m_WaitEvent = NIL_RTSEMEVENT;
663
664 RecordingStreams::iterator it = m_vecStreams.begin();
665 while (it != m_vecStreams.end())
666 {
667 RecordingStream *pStream = (*it);
668
669 vrc = pStream->Uninit();
670 AssertRC(vrc);
671
672 delete pStream;
673 pStream = NULL;
674
675 m_vecStreams.erase(it);
676 it = m_vecStreams.begin();
677 }
678
679 /* Sanity. */
680 Assert(m_vecStreams.empty());
681 Assert(m_mapBlocksRaw.size() == 0);
682 Assert(m_mapBlocksEncoded.size() == 0);
683
684 m_enmState = RECORDINGSTS_UNINITIALIZED;
685
686 unlock();
687}
688
689/**
690 * Returns a recording context's current settings.
691 *
692 * @returns The recording context's current settings.
693 */
694const settings::RecordingSettings &RecordingContext::GetConfig(void) const
695{
696 return m_Settings;
697}
698
699/**
700 * Returns the recording stream for a specific screen.
701 *
702 * @returns Recording stream for a specific screen, or NULL if not found.
703 * @param uScreen Screen ID to retrieve recording stream for.
704 */
705RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
706{
707 RecordingStream *pStream;
708
709 try
710 {
711 pStream = m_vecStreams.at(uScreen);
712 }
713 catch (std::out_of_range &)
714 {
715 pStream = NULL;
716 }
717
718 return pStream;
719}
720
721/**
722 * Locks the recording context for serializing access.
723 *
724 * @returns VBox status code.
725 */
726int RecordingContext::lock(void)
727{
728 int vrc = RTCritSectEnter(&m_CritSect);
729 AssertRC(vrc);
730 return vrc;
731}
732
733/**
734 * Unlocks the recording context for serializing access.
735 *
736 * @returns VBox status code.
737 */
738int RecordingContext::unlock(void)
739{
740 int vrc = RTCritSectLeave(&m_CritSect);
741 AssertRC(vrc);
742 return vrc;
743}
744
745/**
746 * Retrieves a specific recording stream of a recording context.
747 *
748 * @returns Pointer to recording stream if found, or NULL if not found.
749 * @param uScreen Screen number of recording stream to look up.
750 */
751RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
752{
753 return getStreamInternal(uScreen);
754}
755
756/**
757 * Returns the number of configured recording streams for a recording context.
758 *
759 * @returns Number of configured recording streams.
760 */
761size_t RecordingContext::GetStreamCount(void) const
762{
763 return m_vecStreams.size();
764}
765
766/**
767 * Creates a new recording context.
768 *
769 * @returns VBox status code.
770 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
771 * @param Settings Reference to recording settings to use for creation.
772 */
773int RecordingContext::Create(Console *ptrConsole, const settings::RecordingSettings &Settings)
774{
775 return createInternal(ptrConsole, Settings);
776}
777
778/**
779 * Destroys a recording context.
780 */
781void RecordingContext::Destroy(void)
782{
783 destroyInternal();
784}
785
786/**
787 * Starts a recording context.
788 *
789 * @returns VBox status code.
790 */
791int RecordingContext::Start(void)
792{
793 return startInternal();
794}
795
796/**
797 * Stops a recording context.
798 */
799int RecordingContext::Stop(void)
800{
801 return stopInternal();
802}
803
804/**
805 * Returns the current PTS (presentation time stamp) for a recording context.
806 *
807 * @returns Current PTS.
808 */
809uint64_t RecordingContext::GetCurrentPTS(void) const
810{
811 return RTTimeMilliTS() - m_tsStartMs;
812}
813
814/**
815 * Returns if a specific recoding feature is enabled for at least one of the attached
816 * recording streams or not.
817 *
818 * @returns @c true if at least one recording stream has this feature enabled, or @c false if
819 * no recording stream has this feature enabled.
820 * @param enmFeature Recording feature to check for.
821 */
822bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
823{
824 lock();
825
826 RecordingStreams::const_iterator itStream = m_vecStreams.begin();
827 while (itStream != m_vecStreams.end())
828 {
829 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
830 {
831 unlock();
832 return true;
833 }
834 ++itStream;
835 }
836
837 unlock();
838
839 return false;
840}
841
842/**
843 * Returns if this recording context is ready to start recording.
844 *
845 * @returns @c true if recording context is ready, @c false if not.
846 */
847bool RecordingContext::IsReady(void)
848{
849 lock();
850
851 const bool fIsReady = m_enmState >= RECORDINGSTS_CREATED;
852
853 unlock();
854
855 return fIsReady;
856}
857
858/**
859 * Returns if this recording context is ready to accept new recording data for a given screen.
860 *
861 * @returns @c true if the specified screen is ready, @c false if not.
862 * @param uScreen Screen ID.
863 * @param msTimestamp Timestamp (PTS, in ms). Currently not being used.
864 */
865bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
866{
867 RT_NOREF(msTimestamp);
868
869 lock();
870
871 bool fIsReady = false;
872
873 if (m_enmState != RECORDINGSTS_STARTED)
874 {
875 const RecordingStream *pStream = getStreamInternal(uScreen);
876 if (pStream)
877 fIsReady = pStream->IsReady();
878
879 /* Note: Do not check for other constraints like the video FPS rate here,
880 * as this check then also would affect other (non-FPS related) stuff
881 * like audio data. */
882 }
883
884 unlock();
885
886 return fIsReady;
887}
888
889/**
890 * Returns whether a given recording context has been started or not.
891 *
892 * @returns true if active, false if not.
893 */
894bool RecordingContext::IsStarted(void)
895{
896 lock();
897
898 const bool fIsStarted = m_enmState == RECORDINGSTS_STARTED;
899
900 unlock();
901
902 return fIsStarted;
903}
904
905/**
906 * Checks if a specified limit for recording has been reached.
907 *
908 * @returns true if any limit has been reached.
909 */
910bool RecordingContext::IsLimitReached(void)
911{
912 lock();
913
914 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
915
916 const bool fLimitReached = m_cStreamsEnabled == 0;
917
918 unlock();
919
920 return fLimitReached;
921}
922
923/**
924 * Checks if a specified limit for recording has been reached.
925 *
926 * @returns true if any limit has been reached.
927 * @param uScreen Screen ID.
928 * @param msTimestamp Timestamp (PTS, in ms) to check for.
929 */
930bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
931{
932 lock();
933
934 bool fLimitReached = false;
935
936 const RecordingStream *pStream = getStreamInternal(uScreen);
937 if ( !pStream
938 || pStream->IsLimitReached(msTimestamp))
939 {
940 fLimitReached = true;
941 }
942
943 unlock();
944
945 return fLimitReached;
946}
947
948/**
949 * Returns if a specific screen needs to be fed with an update or not.
950 *
951 * @returns @c true if an update is needed, @c false if not.
952 * @param uScreen Screen ID to retrieve update stats for.
953 * @param msTimestamp Timestamp (PTS, in ms).
954 */
955bool RecordingContext::NeedsUpdate(uint32_t uScreen, uint64_t msTimestamp)
956{
957 lock();
958
959 bool fNeedsUpdate = false;
960
961 if (m_enmState == RECORDINGSTS_STARTED)
962 {
963#ifdef VBOX_WITH_AUDIO_RECORDING
964 if ( recordingCodecIsInitialized(&m_CodecAudio)
965 && recordingCodecGetWritable(&m_CodecAudio, msTimestamp) > 0)
966 {
967 fNeedsUpdate = true;
968 }
969#endif /* VBOX_WITH_AUDIO_RECORDING */
970
971 if (!fNeedsUpdate)
972 {
973 const RecordingStream *pStream = getStreamInternal(uScreen);
974 if (pStream)
975 fNeedsUpdate = pStream->NeedsUpdate(msTimestamp);
976 }
977 }
978
979 unlock();
980
981 return fNeedsUpdate;
982}
983
984DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int vrc)
985{
986 RT_NOREF(uScreen, vrc);
987 LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, vrc));
988
989 lock();
990
991 Assert(m_cStreamsEnabled);
992 m_cStreamsEnabled--;
993
994 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
995
996 unlock();
997
998 return VINF_SUCCESS;
999}
1000
1001/**
1002 * Sends an audio frame to the recording thread.
1003 *
1004 * @returns VBox status code.
1005 * @param pvData Audio frame data to send.
1006 * @param cbData Size (in bytes) of (encoded) audio frame data.
1007 * @param msTimestamp Timestamp (PTS, in ms) of audio playback.
1008 */
1009int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
1010{
1011#ifdef VBOX_WITH_AUDIO_RECORDING
1012 return writeCommonData(m_mapBlocksRaw, &m_CodecAudio,
1013 pvData, cbData, msTimestamp, RECORDINGCODEC_ENC_F_BLOCK_IS_KEY);
1014#else
1015 RT_NOREF(pvData, cbData, msTimestamp);
1016 return VERR_NOT_SUPPORTED;
1017#endif
1018}
1019
1020/**
1021 * Sends a video frame to the recording thread.
1022 *
1023 * @thread EMT
1024 *
1025 * @returns VBox status code.
1026 * @param uScreen Screen number to send video frame to.
1027 * @param pFrame Video frame to send.
1028 * @param msTimestamp Timestamp (PTS, in ms).
1029 */
1030int RecordingContext::SendVideoFrame(uint32_t uScreen, PRECORDINGVIDEOFRAME pFrame, uint64_t msTimestamp)
1031{
1032 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
1033
1034 LogFlowFunc(("uScreen=%RU32, offX=%RU32, offY=%RU32, w=%RU32, h=%RU32, msTimestamp=%RU64\n",
1035 uScreen, pFrame->Pos.x, pFrame->Pos.y, pFrame->Info.uWidth, pFrame->Info.uHeight, msTimestamp));
1036
1037 if (!pFrame->pau8Buf) /* Empty / invalid frame, skip. */
1038 return VINF_SUCCESS;
1039
1040 /* Sanity. */
1041 AssertReturn(pFrame->Info.uWidth * pFrame->Info.uHeight * (pFrame->Info.uBPP / 8) <= pFrame->cbBuf, VERR_INVALID_PARAMETER);
1042 AssertReturn(pFrame->Info.uHeight * pFrame->Info.uBytesPerLine <= pFrame->cbBuf, VERR_INVALID_PARAMETER);
1043
1044 lock();
1045
1046 RecordingStream *pStream = getStreamInternal(uScreen);
1047 if (!pStream)
1048 {
1049 unlock();
1050
1051 AssertFailed();
1052 return VERR_NOT_FOUND;
1053 }
1054
1055 int vrc = pStream->SendVideoFrame(pFrame, msTimestamp);
1056
1057 unlock();
1058
1059 if ( RT_SUCCESS(vrc)
1060 && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
1061 {
1062 threadNotify();
1063 }
1064
1065 return vrc;
1066}
1067
1068/**
1069 * Sends a cursor position change to the recording context.
1070 *
1071 * @returns VBox status code.
1072 * @param uScreen Screen number.
1073 * @param x X location within the guest.
1074 * @param y Y location within the guest.
1075 * @param msTimestamp Timestamp (PTS, in ms).
1076 */
1077int RecordingContext::SendCursorPositionChange(uint32_t uScreen, int32_t x, int32_t y, uint64_t msTimestamp)
1078{
1079 LogFlowFunc(("uScreen=%RU32, x=%RU32, y=%RU32\n", uScreen, x, y));
1080
1081 /* If no cursor shape is set yet, skip any cursor position changes. */
1082 if (!m_Cursor.m_Shape.pau8Buf)
1083 return VINF_SUCCESS;
1084
1085 if (uScreen == 0xFFFFFFFF /* SVGA_ID_INVALID */)
1086 uScreen = 0;
1087
1088 int vrc = m_Cursor.Move(x, y);
1089 if (RT_SUCCESS(vrc))
1090 {
1091 lock();
1092
1093 RecordingStream *pStream = getStreamInternal(uScreen);
1094 if (!pStream)
1095 {
1096 unlock();
1097
1098 AssertFailed();
1099 return VERR_NOT_FOUND;
1100 }
1101
1102 vrc = pStream->SendCursorPos(0 /* idCursor */, &m_Cursor.m_Shape.Pos, msTimestamp);
1103
1104 unlock();
1105
1106 if ( RT_SUCCESS(vrc)
1107 && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
1108 {
1109 threadNotify();
1110 }
1111 }
1112
1113 return vrc;
1114}
1115
1116/**
1117 * Sends a cursor shape change to the recording context.
1118 *
1119 * @returns VBox status code.
1120 * @param fVisible Whether the mouse cursor actually is visible or not.
1121 * @param fAlpha Whether the pixel data contains alpha channel information or not.
1122 * @param xHot X hot position (in pixel) of the new cursor.
1123 * @param yHot Y hot position (in pixel) of the new cursor.
1124 * @param uWidth Width (in pixel) of the new cursor.
1125 * @param uHeight Height (in pixel) of the new cursor.
1126 * @param pu8Shape Pixel data of the new cursor. Must be 32 BPP RGBA for now.
1127 * @param cbShape Size of \a pu8Shape (in bytes).
1128 * @param msTimestamp Timestamp (PTS, in ms).
1129 */
1130int RecordingContext::SendCursorShapeChange(bool fVisible, bool fAlpha, uint32_t xHot, uint32_t yHot,
1131 uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape,
1132 uint64_t msTimestamp)
1133{
1134 RT_NOREF(fAlpha, xHot, yHot);
1135
1136 LogFlowFunc(("fVisible=%RTbool, fAlpha=%RTbool, uWidth=%RU32, uHeight=%RU32\n", fVisible, fAlpha, uWidth, uHeight));
1137
1138 if ( !pu8Shape /* Might be NULL on saved state load. */
1139 || !fVisible)
1140 return VINF_SUCCESS;
1141
1142 AssertReturn(cbShape, VERR_INVALID_PARAMETER);
1143
1144 lock();
1145
1146 int vrc = m_Cursor.CreateOrUpdate(fAlpha, uWidth, uHeight, pu8Shape, cbShape);
1147
1148 RecordingStreams::iterator it = m_vecStreams.begin();
1149 while (it != m_vecStreams.end())
1150 {
1151 RecordingStream *pStream = (*it);
1152
1153 int vrc2 = pStream->SendCursorShape(0 /* idCursor */, &m_Cursor.m_Shape, msTimestamp);
1154 if (RT_SUCCESS(vrc))
1155 vrc = vrc2;
1156
1157 ++it;
1158 }
1159
1160 unlock();
1161
1162 if (RT_SUCCESS(vrc))
1163 threadNotify();
1164
1165 return vrc;
1166}
1167
1168/**
1169 * Sends a screen change to a recording stream.
1170 *
1171 * @returns VBox status code.
1172 * @param uScreen Screen number.
1173 * @param pInfo Recording screen info to use.
1174 * @param msTimestamp Timestamp (PTS, in ms).
1175 */
1176int RecordingContext::SendScreenChange(uint32_t uScreen, PRECORDINGSURFACEINFO pInfo, uint64_t msTimestamp)
1177{
1178 lock();
1179
1180 RecordingStream *pStream = getStreamInternal(uScreen);
1181 if (!pStream)
1182 {
1183 unlock();
1184
1185 AssertFailed();
1186 return VERR_NOT_FOUND;
1187 }
1188
1189 int const vrc = pStream->SendScreenChange(pInfo, msTimestamp);
1190
1191 unlock();
1192
1193 return vrc;
1194}
1195
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