VirtualBox

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

Last change on this file since 105006 was 105006, checked in by vboxsync, 5 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. 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 105006 2024-06-24 17:43:00Z 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 case RECORDINGFRAME_TYPE_AUDIO:
390 {
391 pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
392 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
393 pFrame->enmType = RECORDINGFRAME_TYPE_AUDIO;
394 pFrame->msTimestamp = msTimestamp;
395
396 PRECORDINGAUDIOFRAME pAudioFrame = &pFrame->u.Audio;
397 pAudioFrame->pvBuf = (uint8_t *)RTMemDup(pvData, cbData);
398 AssertPtrReturn(pAudioFrame->pvBuf, VERR_NO_MEMORY);
399 pAudioFrame->cbBuf = cbData;
400 break;
401 }
402
403 default:
404 AssertFailed();
405 break;
406 }
407
408 lock();
409
410 int vrc;
411
412 RecordingBlock *pBlock = NULL;
413 try
414 {
415 pBlock = new RecordingBlock();
416
417 pBlock->pvData = pFrame;
418 pBlock->cbData = sizeof(RECORDINGFRAME);
419 pBlock->cRefs = m_cStreamsEnabled;
420 pBlock->msTimestamp = msTimestamp;
421 pBlock->uFlags = uFlags;
422
423 RecordingBlockMap::iterator itBlocks = mapCommon.find(msTimestamp);
424 if (itBlocks == mapCommon.end())
425 {
426 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
427 pRecordingBlocks->List.push_back(pBlock);
428
429 mapCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
430 }
431 else
432 itBlocks->second->List.push_back(pBlock);
433
434 vrc = VINF_SUCCESS;
435 }
436 catch (const std::exception &)
437 {
438 vrc = VERR_NO_MEMORY;
439 }
440
441 unlock();
442
443 if (RT_SUCCESS(vrc))
444 {
445 vrc = threadNotify();
446 }
447 else
448 {
449 if (pBlock)
450 delete pBlock;
451 RecordingFrameFree(pFrame);
452 }
453
454 return vrc;
455}
456
457#ifdef VBOX_WITH_AUDIO_RECORDING
458/**
459 * Callback function for writing encoded audio data into the common encoded block map.
460 *
461 * This is called by the audio codec when finishing encoding audio data.
462 *
463 * @copydoc RECORDINGCODECCALLBACKS::pfnWriteData
464 */
465/* static */
466DECLCALLBACK(int) RecordingContext::audioCodecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
467 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
468{
469 RecordingContext *pThis = (RecordingContext *)pvUser;
470 return pThis->writeCommonData(pThis->m_mapBlocksEncoded, pCodec, pvData, cbData, msAbsPTS, uFlags);
471}
472
473/**
474 * Initializes the audio codec for a (multiplexing) recording context.
475 *
476 * @returns VBox status code.
477 * @param screenSettings Reference to recording screen settings to use for initialization.
478 */
479int RecordingContext::audioInit(const settings::RecordingScreenSettings &screenSettings)
480{
481 RecordingAudioCodec_T const enmCodec = screenSettings.Audio.enmCodec;
482
483 if (enmCodec == RecordingAudioCodec_None)
484 {
485 LogRel2(("Recording: No audio codec configured, skipping audio init\n"));
486 return VINF_SUCCESS;
487 }
488
489 RECORDINGCODECCALLBACKS Callbacks;
490 Callbacks.pvUser = this;
491 Callbacks.pfnWriteData = RecordingContext::audioCodecWriteDataCallback;
492
493 int vrc = recordingCodecCreateAudio(&m_CodecAudio, enmCodec);
494 if (RT_SUCCESS(vrc))
495 vrc = recordingCodecInit(&m_CodecAudio, &Callbacks, screenSettings);
496
497 return vrc;
498}
499#endif /* VBOX_WITH_AUDIO_RECORDING */
500
501/**
502 * Creates a recording context.
503 *
504 * @returns VBox status code.
505 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
506 * @param Settings Reference to recording settings to use for creation.
507 */
508int RecordingContext::createInternal(Console *ptrConsole, const settings::RecordingSettings &Settings)
509{
510 int vrc = VINF_SUCCESS;
511
512 /* Copy the settings to our context. */
513 m_Settings = Settings;
514
515#ifdef VBOX_WITH_AUDIO_RECORDING
516 settings::RecordingScreenSettingsMap::const_iterator itScreen0 = m_Settings.mapScreens.begin();
517 AssertReturn(itScreen0 != m_Settings.mapScreens.end(), VERR_WRONG_ORDER);
518
519 /* We always use the audio settings from screen 0, as we multiplex the audio data anyway. */
520 settings::RecordingScreenSettings const &screen0Settings = itScreen0->second;
521
522 vrc = this->audioInit(screen0Settings);
523 if (RT_FAILURE(vrc))
524 return vrc;
525#endif
526
527 m_pConsole = ptrConsole;
528
529 settings::RecordingScreenSettingsMap::const_iterator itScreen = m_Settings.mapScreens.begin();
530 while (itScreen != m_Settings.mapScreens.end())
531 {
532 RecordingStream *pStream = NULL;
533 try
534 {
535 pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
536 m_vecStreams.push_back(pStream);
537 if (itScreen->second.fEnabled)
538 m_cStreamsEnabled++;
539 LogFlowFunc(("pStream=%p\n", pStream));
540 }
541 catch (std::bad_alloc &)
542 {
543 vrc = VERR_NO_MEMORY;
544 break;
545 }
546 catch (int vrc_thrown) /* Catch vrc thrown by constructor. */
547 {
548 vrc = vrc_thrown;
549 break;
550 }
551
552 ++itScreen;
553 }
554
555 if (RT_SUCCESS(vrc))
556 {
557 m_tsStartMs = 0;
558 m_enmState = RECORDINGSTS_CREATED;
559 m_fShutdown = false;
560
561 vrc = RTSemEventCreate(&m_WaitEvent);
562 AssertRCReturn(vrc, vrc);
563 }
564
565 if (RT_FAILURE(vrc))
566 destroyInternal();
567
568 return vrc;
569}
570
571/**
572 * Starts a recording context by creating its worker thread.
573 *
574 * @returns VBox status code.
575 */
576int RecordingContext::startInternal(void)
577{
578 if (m_enmState == RECORDINGSTS_STARTED)
579 return VINF_SUCCESS;
580
581 Assert(m_enmState == RECORDINGSTS_CREATED);
582
583 m_tsStartMs = RTTimeMilliTS();
584
585 int vrc = RTThreadCreate(&m_Thread, RecordingContext::threadMain, (void *)this, 0,
586 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
587
588 if (RT_SUCCESS(vrc)) /* Wait for the thread to start. */
589 vrc = RTThreadUserWait(m_Thread, RT_MS_30SEC /* 30s timeout */);
590
591 if (RT_SUCCESS(vrc))
592 {
593 LogRel(("Recording: Started\n"));
594 m_enmState = RECORDINGSTS_STARTED;
595 }
596 else
597 Log(("Recording: Failed to start (%Rrc)\n", vrc));
598
599 return vrc;
600}
601
602/**
603 * Stops a recording context by telling the worker thread to stop and finalizing its operation.
604 *
605 * @returns VBox status code.
606 */
607int RecordingContext::stopInternal(void)
608{
609 if (m_enmState != RECORDINGSTS_STARTED)
610 return VINF_SUCCESS;
611
612 LogThisFunc(("Shutting down thread ...\n"));
613
614 /* Set shutdown indicator. */
615 ASMAtomicWriteBool(&m_fShutdown, true);
616
617 /* Signal the thread and wait for it to shut down. */
618 int vrc = threadNotify();
619 if (RT_SUCCESS(vrc))
620 vrc = RTThreadWait(m_Thread, RT_MS_30SEC /* 30s timeout */, NULL);
621
622 lock();
623
624 if (RT_SUCCESS(vrc))
625 {
626 LogRel(("Recording: Stopped\n"));
627 m_tsStartMs = 0;
628 m_enmState = RECORDINGSTS_CREATED;
629 }
630 else
631 Log(("Recording: Failed to stop (%Rrc)\n", vrc));
632
633 unlock();
634
635 LogFlowThisFunc(("%Rrc\n", vrc));
636 return vrc;
637}
638
639/**
640 * Destroys a recording context, internal version.
641 */
642void RecordingContext::destroyInternal(void)
643{
644 lock();
645
646 if (m_enmState == RECORDINGSTS_UNINITIALIZED)
647 {
648 unlock();
649 return;
650 }
651
652 int vrc = stopInternal();
653 AssertRCReturnVoid(vrc);
654
655 vrc = RTSemEventDestroy(m_WaitEvent);
656 AssertRCReturnVoid(vrc);
657
658 m_WaitEvent = NIL_RTSEMEVENT;
659
660 RecordingStreams::iterator it = m_vecStreams.begin();
661 while (it != m_vecStreams.end())
662 {
663 RecordingStream *pStream = (*it);
664
665 vrc = pStream->Uninit();
666 AssertRC(vrc);
667
668 delete pStream;
669 pStream = NULL;
670
671 m_vecStreams.erase(it);
672 it = m_vecStreams.begin();
673 }
674
675 /* Sanity. */
676 Assert(m_vecStreams.empty());
677 Assert(m_mapBlocksRaw.size() == 0);
678 Assert(m_mapBlocksEncoded.size() == 0);
679
680 m_enmState = RECORDINGSTS_UNINITIALIZED;
681
682 unlock();
683}
684
685/**
686 * Returns a recording context's current settings.
687 *
688 * @returns The recording context's current settings.
689 */
690const settings::RecordingSettings &RecordingContext::GetConfig(void) const
691{
692 return m_Settings;
693}
694
695/**
696 * Returns the recording stream for a specific screen.
697 *
698 * @returns Recording stream for a specific screen, or NULL if not found.
699 * @param uScreen Screen ID to retrieve recording stream for.
700 */
701RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
702{
703 RecordingStream *pStream;
704
705 try
706 {
707 pStream = m_vecStreams.at(uScreen);
708 }
709 catch (std::out_of_range &)
710 {
711 pStream = NULL;
712 }
713
714 return pStream;
715}
716
717/**
718 * Locks the recording context for serializing access.
719 *
720 * @returns VBox status code.
721 */
722int RecordingContext::lock(void)
723{
724 int vrc = RTCritSectEnter(&m_CritSect);
725 AssertRC(vrc);
726 return vrc;
727}
728
729/**
730 * Unlocks the recording context for serializing access.
731 *
732 * @returns VBox status code.
733 */
734int RecordingContext::unlock(void)
735{
736 int vrc = RTCritSectLeave(&m_CritSect);
737 AssertRC(vrc);
738 return vrc;
739}
740
741/**
742 * Retrieves a specific recording stream of a recording context.
743 *
744 * @returns Pointer to recording stream if found, or NULL if not found.
745 * @param uScreen Screen number of recording stream to look up.
746 */
747RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
748{
749 return getStreamInternal(uScreen);
750}
751
752/**
753 * Returns the number of configured recording streams for a recording context.
754 *
755 * @returns Number of configured recording streams.
756 */
757size_t RecordingContext::GetStreamCount(void) const
758{
759 return m_vecStreams.size();
760}
761
762/**
763 * Creates a new recording context.
764 *
765 * @returns VBox status code.
766 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
767 * @param Settings Reference to recording settings to use for creation.
768 */
769int RecordingContext::Create(Console *ptrConsole, const settings::RecordingSettings &Settings)
770{
771 return createInternal(ptrConsole, Settings);
772}
773
774/**
775 * Destroys a recording context.
776 */
777void RecordingContext::Destroy(void)
778{
779 destroyInternal();
780}
781
782/**
783 * Starts a recording context.
784 *
785 * @returns VBox status code.
786 */
787int RecordingContext::Start(void)
788{
789 return startInternal();
790}
791
792/**
793 * Stops a recording context.
794 */
795int RecordingContext::Stop(void)
796{
797 return stopInternal();
798}
799
800/**
801 * Returns the current PTS (presentation time stamp) for a recording context.
802 *
803 * @returns Current PTS.
804 */
805uint64_t RecordingContext::GetCurrentPTS(void) const
806{
807 return RTTimeMilliTS() - m_tsStartMs;
808}
809
810/**
811 * Returns if a specific recoding feature is enabled for at least one of the attached
812 * recording streams or not.
813 *
814 * @returns @c true if at least one recording stream has this feature enabled, or @c false if
815 * no recording stream has this feature enabled.
816 * @param enmFeature Recording feature to check for.
817 */
818bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
819{
820 lock();
821
822 RecordingStreams::const_iterator itStream = m_vecStreams.begin();
823 while (itStream != m_vecStreams.end())
824 {
825 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
826 {
827 unlock();
828 return true;
829 }
830 ++itStream;
831 }
832
833 unlock();
834
835 return false;
836}
837
838/**
839 * Returns if this recording context is ready to start recording.
840 *
841 * @returns @c true if recording context is ready, @c false if not.
842 */
843bool RecordingContext::IsReady(void)
844{
845 lock();
846
847 const bool fIsReady = m_enmState >= RECORDINGSTS_CREATED;
848
849 unlock();
850
851 return fIsReady;
852}
853
854/**
855 * Returns if this recording context is ready to accept new recording data for a given screen.
856 *
857 * @returns @c true if the specified screen is ready, @c false if not.
858 * @param uScreen Screen ID.
859 * @param msTimestamp Timestamp (PTS, in ms). Currently not being used.
860 */
861bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
862{
863 RT_NOREF(msTimestamp);
864
865 lock();
866
867 bool fIsReady = false;
868
869 if (m_enmState != RECORDINGSTS_STARTED)
870 {
871 const RecordingStream *pStream = getStreamInternal(uScreen);
872 if (pStream)
873 fIsReady = pStream->IsReady();
874
875 /* Note: Do not check for other constraints like the video FPS rate here,
876 * as this check then also would affect other (non-FPS related) stuff
877 * like audio data. */
878 }
879
880 unlock();
881
882 return fIsReady;
883}
884
885/**
886 * Returns whether a given recording context has been started or not.
887 *
888 * @returns true if active, false if not.
889 */
890bool RecordingContext::IsStarted(void)
891{
892 lock();
893
894 const bool fIsStarted = m_enmState == RECORDINGSTS_STARTED;
895
896 unlock();
897
898 return fIsStarted;
899}
900
901/**
902 * Checks if a specified limit for recording has been reached.
903 *
904 * @returns true if any limit has been reached.
905 */
906bool RecordingContext::IsLimitReached(void)
907{
908 lock();
909
910 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
911
912 const bool fLimitReached = m_cStreamsEnabled == 0;
913
914 unlock();
915
916 return fLimitReached;
917}
918
919/**
920 * Checks if a specified limit for recording has been reached.
921 *
922 * @returns true if any limit has been reached.
923 * @param uScreen Screen ID.
924 * @param msTimestamp Timestamp (PTS, in ms) to check for.
925 */
926bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
927{
928 lock();
929
930 bool fLimitReached = false;
931
932 const RecordingStream *pStream = getStreamInternal(uScreen);
933 if ( !pStream
934 || pStream->IsLimitReached(msTimestamp))
935 {
936 fLimitReached = true;
937 }
938
939 unlock();
940
941 return fLimitReached;
942}
943
944/**
945 * Returns if a specific screen needs to be fed with an update or not.
946 *
947 * @returns @c true if an update is needed, @c false if not.
948 * @param uScreen Screen ID to retrieve update stats for.
949 * @param msTimestamp Timestamp (PTS, in ms).
950 */
951bool RecordingContext::NeedsUpdate(uint32_t uScreen, uint64_t msTimestamp)
952{
953 lock();
954
955 bool fNeedsUpdate = false;
956
957 if (m_enmState == RECORDINGSTS_STARTED)
958 {
959#ifdef VBOX_WITH_AUDIO_RECORDING
960 if ( recordingCodecIsInitialized(&m_CodecAudio)
961 && recordingCodecGetWritable(&m_CodecAudio, msTimestamp) > 0)
962 {
963 fNeedsUpdate = true;
964 }
965#endif /* VBOX_WITH_AUDIO_RECORDING */
966
967 if (!fNeedsUpdate)
968 {
969 const RecordingStream *pStream = getStreamInternal(uScreen);
970 if (pStream)
971 fNeedsUpdate = pStream->NeedsUpdate(msTimestamp);
972 }
973 }
974
975 unlock();
976
977 return fNeedsUpdate;
978}
979
980DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int vrc)
981{
982 RT_NOREF(uScreen, vrc);
983 LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, vrc));
984
985 lock();
986
987 Assert(m_cStreamsEnabled);
988 m_cStreamsEnabled--;
989
990 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
991
992 unlock();
993
994 return VINF_SUCCESS;
995}
996
997/**
998 * Sends an audio frame to the recording thread.
999 *
1000 * @returns VBox status code.
1001 * @param pvData Audio frame data to send.
1002 * @param cbData Size (in bytes) of (encoded) audio frame data.
1003 * @param msTimestamp Timestamp (PTS, in ms) of audio playback.
1004 */
1005int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
1006{
1007#ifdef VBOX_WITH_AUDIO_RECORDING
1008 return writeCommonData(m_mapBlocksRaw, &m_CodecAudio,
1009 pvData, cbData, msTimestamp, RECORDINGCODEC_ENC_F_BLOCK_IS_KEY);
1010#else
1011 RT_NOREF(pvData, cbData, msTimestamp);
1012 return VERR_NOT_SUPPORTED;
1013#endif
1014}
1015
1016/**
1017 * Sends a video frame to the recording thread.
1018 *
1019 * @thread EMT
1020 *
1021 * @returns VBox status code.
1022 * @param uScreen Screen number to send video frame to.
1023 * @param pFrame Video frame to send.
1024 * @param msTimestamp Timestamp (PTS, in ms).
1025 */
1026int RecordingContext::SendVideoFrame(uint32_t uScreen, PRECORDINGVIDEOFRAME pFrame, uint64_t msTimestamp)
1027{
1028 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
1029
1030 LogFlowFunc(("uScreen=%RU32, offX=%RU32, offY=%RU32, w=%RU32, h=%RU32, msTimestamp=%RU64\n",
1031 uScreen, pFrame->Pos.x, pFrame->Pos.y, pFrame->Info.uWidth, pFrame->Info.uHeight, msTimestamp));
1032
1033 if (!pFrame->pau8Buf) /* Empty / invalid frame, skip. */
1034 return VINF_SUCCESS;
1035
1036 /* Sanity. */
1037 AssertReturn(pFrame->Info.uWidth * pFrame->Info.uHeight * (pFrame->Info.uBPP / 8) <= pFrame->cbBuf, VERR_INVALID_PARAMETER);
1038 AssertReturn(pFrame->Info.uHeight * pFrame->Info.uBytesPerLine <= pFrame->cbBuf, VERR_INVALID_PARAMETER);
1039
1040 lock();
1041
1042 RecordingStream *pStream = getStreamInternal(uScreen);
1043 if (!pStream)
1044 {
1045 unlock();
1046
1047 AssertFailed();
1048 return VERR_NOT_FOUND;
1049 }
1050
1051 int vrc = pStream->SendVideoFrame(pFrame, msTimestamp);
1052
1053 unlock();
1054
1055 if ( RT_SUCCESS(vrc)
1056 && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
1057 {
1058 threadNotify();
1059 }
1060
1061 return vrc;
1062}
1063
1064/**
1065 * Sends a cursor position change to the recording context.
1066 *
1067 * @returns VBox status code.
1068 * @param uScreen Screen number.
1069 * @param x X location within the guest.
1070 * @param y Y location within the guest.
1071 * @param msTimestamp Timestamp (PTS, in ms).
1072 */
1073int RecordingContext::SendCursorPositionChange(uint32_t uScreen, int32_t x, int32_t y, uint64_t msTimestamp)
1074{
1075 LogFlowFunc(("uScreen=%RU32, x=%RU32, y=%RU32\n", uScreen, x, y));
1076
1077 /* If no cursor shape is set yet, skip any cursor position changes. */
1078 if (!m_Cursor.m_Shape.pau8Buf)
1079 return VINF_SUCCESS;
1080
1081 if (uScreen == 0xFFFFFFFF /* SVGA_ID_INVALID */)
1082 uScreen = 0;
1083
1084 int vrc = m_Cursor.Move(x, y);
1085 if (RT_SUCCESS(vrc))
1086 {
1087 lock();
1088
1089 RecordingStream *pStream = getStreamInternal(uScreen);
1090 if (!pStream)
1091 {
1092 unlock();
1093
1094 AssertFailed();
1095 return VERR_NOT_FOUND;
1096 }
1097
1098 vrc = pStream->SendCursorPos(0 /* idCursor */, &m_Cursor.m_Shape.Pos, msTimestamp);
1099
1100 unlock();
1101
1102 if ( RT_SUCCESS(vrc)
1103 && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
1104 {
1105 threadNotify();
1106 }
1107 }
1108
1109 return vrc;
1110}
1111
1112/**
1113 * Sends a cursor shape change to the recording context.
1114 *
1115 * @returns VBox status code.
1116 * @param fVisible Whether the mouse cursor actually is visible or not.
1117 * @param fAlpha Whether the pixel data contains alpha channel information or not.
1118 * @param xHot X hot position (in pixel) of the new cursor.
1119 * @param yHot Y hot position (in pixel) of the new cursor.
1120 * @param uWidth Width (in pixel) of the new cursor.
1121 * @param uHeight Height (in pixel) of the new cursor.
1122 * @param pu8Shape Pixel data of the new cursor. Must be 32 BPP RGBA for now.
1123 * @param cbShape Size of \a pu8Shape (in bytes).
1124 * @param msTimestamp Timestamp (PTS, in ms).
1125 */
1126int RecordingContext::SendCursorShapeChange(bool fVisible, bool fAlpha, uint32_t xHot, uint32_t yHot,
1127 uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape,
1128 uint64_t msTimestamp)
1129{
1130 RT_NOREF(fAlpha, xHot, yHot);
1131
1132 LogFlowFunc(("fVisible=%RTbool, fAlpha=%RTbool, uWidth=%RU32, uHeight=%RU32\n", fVisible, fAlpha, uWidth, uHeight));
1133
1134 if ( !pu8Shape /* Might be NULL on saved state load. */
1135 || !fVisible)
1136 return VINF_SUCCESS;
1137
1138 AssertReturn(cbShape, VERR_INVALID_PARAMETER);
1139
1140 lock();
1141
1142 int vrc = m_Cursor.CreateOrUpdate(fAlpha, uWidth, uHeight, pu8Shape, cbShape);
1143
1144 RecordingStreams::iterator it = m_vecStreams.begin();
1145 while (it != m_vecStreams.end())
1146 {
1147 RecordingStream *pStream = (*it);
1148
1149 int vrc2 = pStream->SendCursorShape(0 /* idCursor */, &m_Cursor.m_Shape, msTimestamp);
1150 if (RT_SUCCESS(vrc))
1151 vrc = vrc2;
1152
1153 ++it;
1154 }
1155
1156 unlock();
1157
1158 if (RT_SUCCESS(vrc))
1159 threadNotify();
1160
1161 return vrc;
1162}
1163
1164/**
1165 * Sends a screen change to a recording stream.
1166 *
1167 * @returns VBox status code.
1168 * @param uScreen Screen number.
1169 * @param pInfo Recording screen info to use.
1170 * @param msTimestamp Timestamp (PTS, in ms).
1171 */
1172int RecordingContext::SendScreenChange(uint32_t uScreen, PRECORDINGSURFACEINFO pInfo, uint64_t msTimestamp)
1173{
1174 lock();
1175
1176 RecordingStream *pStream = getStreamInternal(uScreen);
1177 if (!pStream)
1178 {
1179 unlock();
1180
1181 AssertFailed();
1182 return VERR_NOT_FOUND;
1183 }
1184
1185 int const vrc = pStream->SendScreenChange(pInfo, msTimestamp);
1186
1187 unlock();
1188
1189 return vrc;
1190}
1191
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