VirtualBox

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

Last change on this file since 108014 was 107496, checked in by vboxsync, 7 weeks ago

src/VBox/Main/src-client/Recording.cpp: Fixed warnings found by Parfait. jiraref:VBP-1424

  • 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: 42.4 KB
Line 
1/* $Id: Recording.cpp 107496 2025-01-08 10:57:35Z 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-2024 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 <iprt/cpp/utils.h>
53
54#include <VBox/err.h>
55#include <VBox/com/VirtualBox.h>
56
57#include "ConsoleImpl.h"
58#include "ProgressImpl.h"
59#include "Recording.h"
60#include "RecordingInternals.h"
61#include "RecordingStream.h"
62#include "RecordingUtils.h"
63#include "WebMWriter.h"
64#include "VirtualBoxErrorInfoImpl.h"
65
66using namespace com;
67
68#ifdef DEBUG_andy
69/** Enables dumping audio / video data for debugging reasons. */
70//# define VBOX_RECORDING_DUMP
71#endif
72
73
74
75RecordingCursorState::RecordingCursorState()
76 : m_fFlags(VBOX_RECORDING_CURSOR_F_NONE)
77{
78 m_Shape.Pos.x = UINT16_MAX;
79 m_Shape.Pos.y = UINT16_MAX;
80
81 RT_ZERO(m_Shape);
82}
83
84RecordingCursorState::~RecordingCursorState()
85{
86 Destroy();
87}
88
89/**
90 * Destroys a cursor state.
91 */
92void RecordingCursorState::Destroy(void)
93{
94 RecordingVideoFrameDestroy(&m_Shape);
95}
96
97/**
98 * Creates or updates the cursor shape.
99 *
100 * @returns VBox status code.
101 * @param fAlpha Whether the pixel data contains alpha channel information or not.
102 * @param uWidth Width (in pixel) of new cursor shape.
103 * @param uHeight Height (in pixel) of new cursor shape.
104 * @param pu8Shape Pixel data of new cursor shape.
105 * @param cbShape Bytes of \a pu8Shape.
106 */
107int RecordingCursorState::CreateOrUpdate(bool fAlpha, uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape)
108{
109 int vrc;
110
111 uint32_t fFlags = RECORDINGVIDEOFRAME_F_VISIBLE;
112
113 const uint8_t uBPP = 32; /* Seems to be fixed. */
114
115 uint32_t offShape;
116 if (fAlpha)
117 {
118 /* Calculate the offset to the actual pixel data. */
119 offShape = (uWidth + 7) / 8 * uHeight; /* size of the AND mask */
120 offShape = (offShape + 3) & ~3;
121 AssertReturn(offShape <= cbShape, VERR_INVALID_PARAMETER);
122 fFlags |= RECORDINGVIDEOFRAME_F_BLIT_ALPHA;
123 }
124 else
125 offShape = 0;
126
127 /* Cursor shape size has become bigger? Reallocate. */
128 if (cbShape > m_Shape.cbBuf)
129 {
130 RecordingVideoFrameDestroy(&m_Shape);
131 vrc = RecordingVideoFrameInit(&m_Shape, fFlags, uWidth, uHeight, 0 /* posX */, 0 /* posY */,
132 uBPP, RECORDINGPIXELFMT_BRGA32);
133 }
134 else /* Otherwise just zero out first. */
135 {
136 RecordingVideoFrameClear(&m_Shape);
137 vrc = VINF_SUCCESS;
138 }
139
140 if (RT_SUCCESS(vrc))
141 vrc = RecordingVideoFrameBlitRaw(&m_Shape, 0, 0, &pu8Shape[offShape], cbShape - offShape, 0, 0, uWidth, uHeight, uWidth * 4 /* BPP */, uBPP,
142 m_Shape.Info.enmPixelFmt);
143#if 0
144 RecordingUtilsDbgDumpVideoFrameEx(&m_Shape, "/tmp/recording", "cursor-update");
145#endif
146
147 return vrc;
148}
149
150/**
151 * Moves (sets) the cursor to a new position.
152 *
153 * @returns VBox status code.
154 * @retval VERR_NO_CHANGE if the cursor wasn't moved (set).
155 * @param iX New X position to set.
156 * @param iY New Y position to set.
157 */
158int RecordingCursorState::Move(int32_t iX, int32_t iY)
159{
160 /* No relative coordinates here. */
161 if ( iX < 0
162 || iY < 0)
163 return VERR_NO_CHANGE;
164
165 if ( m_Shape.Pos.x == (uint32_t)iX
166 && m_Shape.Pos.y == (uint32_t)iY)
167 return VERR_NO_CHANGE;
168
169 m_Shape.Pos.x = (uint16_t)iX;
170 m_Shape.Pos.y = (uint16_t)iY;
171
172 return VINF_SUCCESS;
173}
174
175
176//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
177
178
179/**
180 * Recording context constructor.
181 *
182 * @note Will throw vrc when unable to create.
183 */
184RecordingContext::RecordingContext(void)
185 : m_pConsole(NULL)
186 , m_enmState(RECORDINGSTS_UNINITIALIZED)
187 , m_WaitEvent(NIL_RTSEMEVENT)
188 , m_ulCurOp(0)
189 , m_cOps(0)
190 , m_fShutdown(false)
191 , m_Thread(NIL_RTTHREAD)
192 , m_cStreamsEnabled(0)
193 , m_tsStartMs(0)
194{
195 int vrc = RTCritSectInit(&m_CritSect);
196 if (RT_FAILURE(vrc))
197 throw vrc;
198}
199
200RecordingContext::~RecordingContext(void)
201{
202 destroyInternal();
203
204 if (RTCritSectIsInitialized(&m_CritSect))
205 RTCritSectDelete(&m_CritSect);
206}
207
208/**
209 * Returns whether the recording progress object has been canceled or not.
210 *
211 * @returns \c true if canceled, or \c false if not.
212 */
213bool RecordingContext::progressIsCanceled(void) const
214{
215 if (m_pProgress.isNull())
216 return true;
217
218 BOOL fCanceled;
219 HRESULT const hrc = m_pProgress->COMGETTER(Canceled(&fCanceled));
220 AssertComRC(hrc);
221 return RT_BOOL(fCanceled);
222}
223
224/**
225 * Returns whether the recording progress object has been completed or not.
226 *
227 * @returns \c true if completed, or \c false if not.
228 */
229bool RecordingContext::progressIsCompleted(void) const
230{
231 if (m_pProgress.isNull())
232 return true;
233
234 BOOL fCompleted;
235 HRESULT const hrc = m_pProgress->COMGETTER(Completed(&fCompleted));
236 AssertComRC(hrc);
237 return RT_BOOL(fCompleted);
238}
239
240/**
241 * Creates a progress object based on the given recording settings.
242 *
243 * @returns VBox status code.
244 * @param Settings Recording settings to use for creation.
245 * @param pProgress Where to return the created progress object on success.
246 */
247int RecordingContext::progressCreate(const settings::Recording &Settings, ComObjPtr<Progress> &pProgress)
248{
249 /* Determine the number of operations the recording progress has.
250 * We use the maximum time (in s) of each screen as the overall progress indicator.
251 * If one screen is configured to be recorded indefinitely (until manually stopped),
252 * the operation count gets reset to 1. */
253 ULONG cOperations = 1; /* Always start at 1. */
254 settings::RecordingScreenSettingsMap::const_iterator itScreen = Settings.mapScreens.begin();
255 while (itScreen != Settings.mapScreens.end())
256 {
257 settings::RecordingScreen const &screenSettings = itScreen->second;
258 if (screenSettings.ulMaxTimeS == 0)
259 {
260 cOperations = 1; /* Screen will be recorded indefinitely, reset operation count and bail out. */
261 break;
262 }
263 else
264 cOperations = RT_MAX(cOperations, screenSettings.ulMaxTimeS);
265 ++itScreen;
266 }
267
268 HRESULT hrc = pProgress.createObject();
269 if (SUCCEEDED(hrc))
270 {
271 hrc = pProgress->init(static_cast<IConsole *>(m_pConsole), Utf8Str("Recording"),
272 TRUE /* aCancelable */, cOperations, cOperations /* ulTotalOperationsWeight */,
273 Utf8Str("Starting"), 1 /* ulFirstOperationWeight */);
274 if (SUCCEEDED(hrc))
275 pProgress->i_setCancelCallback(RecordingContext::s_progressCancelCallback, this /* pvUser */);
276 }
277
278 return SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_COM_UNEXPECTED;
279}
280
281/**
282 * Sets the current progress based on the operation.
283 *
284 * @returns VBox status code.
285 * @param uOp Operation index to set (zero-based).
286 * @param strDesc Description of the operation.
287 */
288int RecordingContext::progressSet(uint32_t uOp, const Bstr &strDesc)
289{
290 if (m_pProgress.isNull())
291 return VINF_SUCCESS;
292
293 if ( uOp == m_ulCurOp /* No change? */
294 || uOp + 1 > m_cOps /* Done? */
295 || m_cOps == 1) /* Indefinitely recording until canceled? Skip. */
296 return VINF_SUCCESS;
297
298 Assert(uOp > m_ulCurOp);
299
300 ComPtr<IInternalProgressControl> pProgressControl(m_pProgress);
301 AssertReturn(!!pProgressControl, VERR_COM_UNEXPECTED);
302
303 /* hrc ignored */ pProgressControl->SetNextOperation(strDesc.raw(), 1 /* Weight */);
304 /* Might be E_FAIL if already canceled. */
305
306 m_ulCurOp = uOp;
307
308 return VINF_SUCCESS;
309}
310
311/**
312 * Sets the current progress based on a timestamp (PTS).
313 *
314 * @returns VBox status code.
315 * @param msTimestamp Timestamp to use (absolute, PTS).
316 */
317int RecordingContext::progressSet(uint64_t msTimestamp)
318{
319 /* Run until stopped / canceled? */
320 if (m_cOps == 1)
321 return VINF_SUCCESS;
322
323 ULONG const nextOp = (ULONG)msTimestamp / RT_MS_1SEC; /* Each operation equals 1s (same weight). */
324 if (nextOp <= m_ulCurOp) /* If next operation still is the current operation, bail out early. */
325 return VINF_SUCCESS;
326
327 /* Format the recording time as a human-readable time (HH:MM:SS) and set it as current progress operation text. */
328 char szDesc[32];
329 szDesc[0] = '\0';
330 char *psz = szDesc;
331 RTTIMESPEC TimeSpec;
332 RTTIME Time;
333 RTTimeExplode(&Time, RTTimeSpecSetMilli(&TimeSpec, msTimestamp));
334 psz += RTStrFormatNumber(psz, Time.u8Hour, 10, 2, 0, RTSTR_F_ZEROPAD);
335 *psz++ = ':';
336 psz += RTStrFormatNumber(psz, Time.u8Minute, 10, 2, 0, RTSTR_F_ZEROPAD);
337 *psz++ = ':';
338 psz += RTStrFormatNumber(psz, Time.u8Second, 10, 2, 0, RTSTR_F_ZEROPAD);
339
340 /* All operations have the same weight. */
341 uint8_t const uPercent = (100 * nextOp + m_cOps / 2) / m_cOps;
342
343 LogRel2(("Recording: Progress %s (%RU32 / %RU32) -- %RU8%%\n", szDesc, nextOp, m_cOps, uPercent));
344
345 psz += RTStrPrintf2(psz, psz - szDesc, " (%RU8%%)", uPercent);
346
347 return progressSet(nextOp, Bstr(szDesc));
348}
349
350/**
351 * Notifies the progress object about completion.
352 *
353 * @returns VBox status code.
354 * @param hrc Completion result to set.
355 * @param pErrorInfo Error info to set in case \a hrc indicates an error. Optional and can be NULL.
356 */
357int RecordingContext::progressNotifyComplete(HRESULT hrc /* = S_OK */, IVirtualBoxErrorInfo *pErrorInfo /* = NULL */)
358{
359 if (m_pProgress.isNull())
360 return VINF_SUCCESS;
361
362 BOOL fCompleted;
363 HRESULT hrc2 = m_pProgress->COMGETTER(Completed)(&fCompleted);
364 AssertComRC(hrc2);
365
366 if (!fCompleted)
367 {
368 ComPtr<IInternalProgressControl> pProgressControl(m_pProgress);
369 AssertReturn(!!pProgressControl, VERR_COM_UNEXPECTED);
370
371 pProgressControl->NotifyComplete(hrc, pErrorInfo);
372 }
373
374 return VINF_SUCCESS;
375}
376
377/**
378 * Reports an error condition to the recording context.
379 *
380 * @returns VBox status code.
381 * @param rc Error code to set.
382 * @param strText Error description to set.
383 */
384int RecordingContext::SetError(int rc, const com::Utf8Str &strText)
385{
386 lock();
387
388 if ( m_pProgress.isNull()
389 || !m_pConsole)
390 {
391 unlock();
392 return VINF_SUCCESS;
393 }
394
395 ComObjPtr<VirtualBoxErrorInfo> pErrorInfo;
396 HRESULT hrc = pErrorInfo.createObject();
397 AssertComRC(hrc);
398 hrc = pErrorInfo->initEx(VBOX_E_RECORDING_ERROR, (LONG)rc,
399 m_pConsole->getStaticClassIID(), m_pConsole->getStaticComponentName(), strText);
400 AssertComRC(hrc);
401
402 unlock();
403
404 LogRel(("Recording: An error occurred: %s (%Rrc)\n", strText.c_str(), rc));
405
406 hrc = m_pProgress->NotifyComplete(VBOX_E_RECORDING_ERROR, pErrorInfo);
407 AssertComRC(hrc);
408
409 return VINF_SUCCESS;
410}
411
412/**
413 * Worker thread for all streams of a recording context.
414 */
415DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
416{
417 RecordingContext *pThis = (RecordingContext *)pvUser;
418
419 /* Signal that we're up and rockin'. */
420 RTThreadUserSignal(hThreadSelf);
421
422 LogRel2(("Recording: Thread started\n"));
423
424 for (;;)
425 {
426 int vrcWait = RTSemEventWait(pThis->m_WaitEvent, RT_MS_1SEC);
427
428 if (ASMAtomicReadBool(&pThis->m_fShutdown))
429 {
430 LogRel2(("Recording: Thread is shutting down ...\n"));
431 break;
432 }
433
434 Log2Func(("Processing %zu streams (wait = %Rrc)\n", pThis->m_vecStreams.size(), vrcWait));
435
436 uint64_t const msTimestamp = pThis->GetCurrentPTS();
437
438 /* Set the overall progress. */
439 int vrc = pThis->progressSet(msTimestamp);
440 AssertRC(vrc);
441
442 /* Process common raw blocks (data which not has been encoded yet). */
443 vrc = pThis->processCommonData(pThis->m_mapBlocksRaw, 100 /* ms timeout */);
444
445 /** @todo r=andy This is inefficient -- as we already wake up this thread
446 * for every screen from Main, we here go again (on every wake up) through
447 * all screens. */
448 RecordingStreams::iterator itStream = pThis->m_vecStreams.begin();
449 while (itStream != pThis->m_vecStreams.end())
450 {
451 RecordingStream *pStream = (*itStream);
452
453 /* Hand-in common encoded blocks. */
454 vrc = pStream->ThreadMain(vrcWait, msTimestamp, pThis->m_mapBlocksEncoded);
455 if (RT_FAILURE(vrc))
456 {
457 LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), vrc));
458 break;
459 }
460
461 ++itStream;
462 }
463
464 if (RT_FAILURE(vrc))
465 LogRel(("Recording: Encoding thread failed (%Rrc)\n", vrc));
466
467 /* Keep going in case of errors. */
468
469 } /* for */
470
471 LogRel2(("Recording: Thread ended\n"));
472 return VINF_SUCCESS;
473}
474
475/**
476 * Notifies a recording context's encoding thread.
477 *
478 * @returns VBox status code.
479 */
480int RecordingContext::threadNotify(void)
481{
482 return RTSemEventSignal(m_WaitEvent);
483}
484
485/**
486 * Worker function for processing common block data.
487 *
488 * @returns VBox status code.
489 * @param mapCommon Common block map to handle.
490 * @param msTimeout Timeout to use for maximum time spending to process data.
491 * Use RT_INDEFINITE_WAIT for processing all data.
492 *
493 * @note Runs in recording thread.
494 */
495int RecordingContext::processCommonData(RecordingBlockMap &mapCommon, RTMSINTERVAL msTimeout)
496{
497 Log2Func(("Processing %zu common blocks (%RU32ms timeout)\n", mapCommon.size(), msTimeout));
498
499 int vrc = VINF_SUCCESS;
500
501 uint64_t const msStart = RTTimeMilliTS();
502 RecordingBlockMap::iterator itCommonBlocks = mapCommon.begin();
503 while (itCommonBlocks != mapCommon.end())
504 {
505 RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
506 while (itBlock != itCommonBlocks->second->List.end())
507 {
508 RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
509 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)pBlockCommon->pvData;
510 AssertPtr(pFrame);
511 switch (pFrame->enmType)
512 {
513#ifdef VBOX_WITH_AUDIO_RECORDING
514 case RECORDINGFRAME_TYPE_AUDIO:
515 {
516 vrc = recordingCodecEncodeFrame(&m_CodecAudio, pFrame, pFrame->msTimestamp, NULL /* pvUser */);
517 break;
518 }
519#endif /* VBOX_WITH_AUDIO_RECORDING */
520
521 default:
522 /* Skip unknown stuff. */
523 break;
524 }
525
526 itCommonBlocks->second->List.erase(itBlock);
527 delete pBlockCommon;
528 itBlock = itCommonBlocks->second->List.begin();
529
530 if (RT_FAILURE(vrc) || RTTimeMilliTS() > msStart + msTimeout)
531 break;
532 }
533
534 /* If no entries are left over in the block map, remove it altogether. */
535 if (itCommonBlocks->second->List.empty())
536 {
537 delete itCommonBlocks->second;
538 mapCommon.erase(itCommonBlocks);
539 itCommonBlocks = mapCommon.begin();
540 }
541 else
542 ++itCommonBlocks;
543
544 if (RT_FAILURE(vrc))
545 break;
546 }
547
548 return vrc;
549}
550
551/**
552 * Writes common block data (i.e. shared / the same) in all streams.
553 *
554 * The multiplexing is needed to supply all recorded (enabled) screens with the same
555 * data at the same given point in time.
556 *
557 * Currently this only is being used for audio data.
558 *
559 * @returns VBox status code.
560 * @param mapCommon Common block map to write data to.
561 * @param pCodec Pointer to codec instance which has written the data.
562 * @param pvData Pointer to written data (encoded).
563 * @param cbData Size (in bytes) of \a pvData.
564 * @param msTimestamp Absolute PTS (in ms) of the written data.
565 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
566 */
567int RecordingContext::writeCommonData(RecordingBlockMap &mapCommon, PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
568 uint64_t msTimestamp, uint32_t uFlags)
569{
570 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
571 AssertReturn(cbData, VERR_INVALID_PARAMETER);
572
573 LogFlowFunc(("pCodec=%p, cbData=%zu, msTimestamp=%zu, uFlags=%#x\n",
574 pCodec, cbData, msTimestamp, uFlags));
575
576 RECORDINGFRAME_TYPE const enmType = pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
577 ? RECORDINGFRAME_TYPE_AUDIO : RECORDINGFRAME_TYPE_INVALID;
578
579 AssertReturn(enmType != RECORDINGFRAME_TYPE_INVALID, VERR_NOT_SUPPORTED);
580
581 PRECORDINGFRAME pFrame = NULL;
582
583 switch (enmType)
584 {
585#ifdef VBOX_WITH_AUDIO_RECORDING
586 case RECORDINGFRAME_TYPE_AUDIO:
587 {
588 pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
589 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
590 pFrame->enmType = RECORDINGFRAME_TYPE_AUDIO;
591 pFrame->msTimestamp = msTimestamp;
592
593 PRECORDINGAUDIOFRAME pAudioFrame = &pFrame->u.Audio;
594 pAudioFrame->pvBuf = (uint8_t *)RTMemDup(pvData, cbData);
595 AssertPtrReturn(pAudioFrame->pvBuf, VERR_NO_MEMORY);
596 pAudioFrame->cbBuf = cbData;
597 break;
598 }
599#endif
600 default:
601 AssertFailed();
602 break;
603 }
604
605 if (!pFrame)
606 return VINF_SUCCESS;
607
608 lock();
609
610 int vrc;
611
612 RecordingBlock *pBlock = NULL;
613 try
614 {
615 pBlock = new RecordingBlock();
616
617 pBlock->pvData = pFrame;
618 pBlock->cbData = sizeof(RECORDINGFRAME);
619 pBlock->cRefs = m_cStreamsEnabled;
620 pBlock->msTimestamp = msTimestamp;
621 pBlock->uFlags = uFlags;
622
623 RecordingBlockMap::iterator itBlocks = mapCommon.find(msTimestamp);
624 if (itBlocks == mapCommon.end())
625 {
626 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
627 pRecordingBlocks->List.push_back(pBlock);
628
629 mapCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
630 }
631 else
632 itBlocks->second->List.push_back(pBlock);
633
634 vrc = VINF_SUCCESS;
635 }
636 catch (const std::exception &)
637 {
638 vrc = VERR_NO_MEMORY;
639 }
640
641 unlock();
642
643 if (RT_SUCCESS(vrc))
644 {
645 vrc = threadNotify();
646 }
647 else
648 {
649 if (pBlock)
650 delete pBlock;
651 RecordingFrameFree(pFrame);
652 }
653
654 return vrc;
655}
656
657#ifdef VBOX_WITH_AUDIO_RECORDING
658/**
659 * Callback function for writing encoded audio data into the common encoded block map.
660 *
661 * This is called by the audio codec when finishing encoding audio data.
662 *
663 * @copydoc RECORDINGCODECCALLBACKS::pfnWriteData
664 */
665/* static */
666DECLCALLBACK(int) RecordingContext::s_audioCodecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
667 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
668{
669 RecordingContext *pThis = (RecordingContext *)pvUser;
670 return pThis->writeCommonData(pThis->m_mapBlocksEncoded, pCodec, pvData, cbData, msAbsPTS, uFlags);
671}
672
673/**
674 * Initializes the audio codec for a (multiplexing) recording context.
675 *
676 * @returns VBox status code.
677 * @param screenSettings Reference to recording screen settings to use for initialization.
678 */
679int RecordingContext::audioInit(const settings::RecordingScreen &screenSettings)
680{
681 RecordingAudioCodec_T const enmCodec = screenSettings.Audio.enmCodec;
682
683 if (enmCodec == RecordingAudioCodec_None)
684 {
685 LogRel2(("Recording: No audio codec configured, skipping audio init\n"));
686 return VINF_SUCCESS;
687 }
688
689 RECORDINGCODECCALLBACKS Callbacks;
690 Callbacks.pvUser = this;
691 Callbacks.pfnWriteData = RecordingContext::s_audioCodecWriteDataCallback;
692
693 int vrc = recordingCodecCreateAudio(&m_CodecAudio, enmCodec);
694 if (RT_SUCCESS(vrc))
695 vrc = recordingCodecInit(&m_CodecAudio, &Callbacks, screenSettings);
696
697 return vrc;
698}
699#endif /* VBOX_WITH_AUDIO_RECORDING */
700
701/**
702 * Progress canceled callback.
703 *
704 * @param pvUser User-supplied pointer. Points to the RecordingContext instance.
705 */
706/* static */
707void RecordingContext::s_progressCancelCallback(void *pvUser)
708{
709 RecordingContext *pThis = (RecordingContext *)pvUser;
710
711 LogRel(("Recording: Canceled\n"));
712
713 if (pThis->m_pConsole)
714 {
715 ComPtr<IProgress> pProgressIgnored;
716 pThis->m_pConsole->i_onRecordingStateChange(FALSE /* Disable */, pProgressIgnored);
717 }
718}
719
720/** @copydoc RecordingContext::CALLBACKS::pfnStateChanged */
721DECLCALLBACK(void) RecordingContext::s_recordingStateChangedCallback(RecordingContext *pCtx,
722 RECORDINGSTS enmSts, uint32_t uScreen, int vrc, void *pvUser)
723{
724 RT_NOREF(vrc, pvUser);
725
726 Log2Func(("enmSts=%0x, uScreen=%RU32, vrc=%Rrc\n", enmSts, uScreen, vrc));
727
728 switch (enmSts)
729 {
730 case RECORDINGSTS_LIMIT_REACHED:
731 {
732 if (uScreen == UINT32_MAX) /* Limit for all screens reached? Disable recording. */
733 {
734 ComPtr<IProgress> pProgressIgnored;
735 pCtx->m_pConsole->i_onRecordingStateChange(FALSE /* Disable */, pProgressIgnored);
736
737 pCtx->lock();
738
739 /* Make sure to complete the progress object (if not already done so). */
740 pCtx->progressNotifyComplete(S_OK);
741
742 pCtx->unlock();
743 }
744 else if (pCtx->m_pConsole)
745 pCtx->m_pConsole->i_onRecordingScreenStateChange(FALSE /* Disable */, uScreen);
746 break;
747 }
748
749 default:
750 break;
751 }
752}
753
754/**
755 * Creates a recording context.
756 *
757 * @returns VBox status code.
758 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
759 * @param Settings Reference to recording settings to use for creation.
760 * @param pProgress Progress object returned on success.
761 */
762int RecordingContext::createInternal(Console *ptrConsole, const settings::Recording &Settings,
763 ComPtr<IProgress> &pProgress)
764{
765 int vrc = VINF_SUCCESS;
766
767 /* Copy the settings to our context. */
768 m_Settings = Settings;
769
770#ifdef VBOX_WITH_AUDIO_RECORDING
771 settings::RecordingScreenSettingsMap::const_iterator itScreen0 = m_Settings.mapScreens.begin();
772 AssertReturn(itScreen0 != m_Settings.mapScreens.end(), VERR_WRONG_ORDER);
773
774 /* We always use the audio settings from screen 0, as we multiplex the audio data anyway. */
775 settings::RecordingScreen const &screen0Settings = itScreen0->second;
776
777 vrc = this->audioInit(screen0Settings);
778 if (RT_FAILURE(vrc))
779 return vrc;
780#endif
781
782 m_pConsole = ptrConsole;
783 RT_ZERO(m_Callbacks);
784
785 settings::RecordingScreenSettingsMap::const_iterator itScreen = m_Settings.mapScreens.begin();
786 while (itScreen != m_Settings.mapScreens.end())
787 {
788 RecordingStream *pStream = NULL;
789 try
790 {
791 if (itScreen->second.fEnabled)
792 {
793 pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
794 m_vecStreams.push_back(pStream);
795 m_cStreamsEnabled++;
796 LogFlowFunc(("pStream=%p\n", pStream));
797 }
798 }
799 catch (std::bad_alloc &)
800 {
801 vrc = VERR_NO_MEMORY;
802 break;
803 }
804 catch (int vrc_thrown) /* Catch vrc thrown by constructor. */
805 {
806 vrc = vrc_thrown;
807 break;
808 }
809
810 ++itScreen;
811 }
812
813 ComObjPtr<Progress> pThisProgress;
814 vrc = progressCreate(m_Settings, pThisProgress);
815 if (RT_SUCCESS(vrc))
816 {
817 vrc = RTSemEventCreate(&m_WaitEvent);
818 AssertRCReturn(vrc, vrc);
819
820 RecordingContext::CALLBACKS Callbacks;
821 RT_ZERO(Callbacks);
822 Callbacks.pfnStateChanged = RecordingContext::s_recordingStateChangedCallback;
823
824 SetCallbacks(&Callbacks, this /* pvUser */);
825
826 reset();
827
828 unconst(m_pProgress) = pThisProgress;
829 pThisProgress.queryInterfaceTo(pProgress.asOutParam());
830 }
831
832 if (RT_FAILURE(vrc))
833 destroyInternal();
834
835 return vrc;
836}
837
838/**
839 * Sets the callback table for a recording context.
840 *
841 * @param pCallbacks Callback table to set.
842 * @param pvUser User-supplied pointer.
843 */
844void RecordingContext::SetCallbacks(RecordingContext::CALLBACKS *pCallbacks, void *pvUser)
845{
846 lock();
847
848 memcpy(&m_Callbacks, pCallbacks, sizeof(RecordingContext::CALLBACKS));
849 m_Callbacks.pvUser = pvUser;
850
851 unlock();
852}
853
854/**
855 * Resets a recording context.
856 */
857void RecordingContext::reset(void)
858{
859 m_tsStartMs = 0;
860 m_enmState = RECORDINGSTS_CREATED;
861 m_fShutdown = false;
862 m_cStreamsEnabled = 0;
863
864 unconst(m_pProgress).setNull();
865}
866
867/**
868 * Starts a recording context by creating its worker thread.
869 *
870 * @returns VBox status code.
871 */
872int RecordingContext::startInternal(void)
873{
874 if (m_enmState == RECORDINGSTS_STARTED)
875 return VINF_SUCCESS;
876
877 Assert(m_enmState == RECORDINGSTS_CREATED);
878
879 LogRel2(("Recording: Starting ...\n"));
880
881 m_tsStartMs = RTTimeMilliTS();
882
883 m_ulCurOp = 0;
884 if (m_pProgress.isNotNull())
885 {
886 HRESULT hrc = m_pProgress->COMGETTER(OperationCount)(&m_cOps);
887 AssertComRCReturn(hrc, VERR_COM_UNEXPECTED);
888 }
889
890 int vrc = RTThreadCreate(&m_Thread, RecordingContext::threadMain, (void *)this, 0,
891 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
892
893 if (RT_SUCCESS(vrc)) /* Wait for the thread to start. */
894 vrc = RTThreadUserWait(m_Thread, RT_MS_30SEC /* 30s timeout */);
895
896 if (RT_SUCCESS(vrc))
897 {
898 LogRel(("Recording: Started\n"));
899 m_enmState = RECORDINGSTS_STARTED;
900 }
901 else
902 LogRel(("Recording: Failed to start (%Rrc)\n", vrc));
903
904 return vrc;
905}
906
907/**
908 * Stops a recording context by telling the worker thread to stop and finalizing its operation.
909 *
910 * @returns VBox status code.
911 */
912int RecordingContext::stopInternal(void)
913{
914 if (m_enmState != RECORDINGSTS_STARTED)
915 return VINF_SUCCESS;
916
917 LogRel2(("Recording: Stopping ...\n"));
918
919 /* Set shutdown indicator. */
920 ASMAtomicWriteBool(&m_fShutdown, true);
921
922 /* Signal the thread and wait for it to shut down. */
923 int vrc = threadNotify();
924 if (RT_SUCCESS(vrc))
925 vrc = RTThreadWait(m_Thread, RT_MS_30SEC /* 30s timeout */, NULL);
926
927 lock();
928
929 if (RT_SUCCESS(vrc))
930 {
931 if (m_pProgress.isNotNull())
932 progressNotifyComplete();
933
934 LogRel(("Recording: Stopped\n"));
935
936 reset();
937 }
938 else
939 LogRel(("Recording: Failed to stop (%Rrc)\n", vrc));
940
941 unlock();
942
943 LogFlowThisFunc(("%Rrc\n", vrc));
944 return vrc;
945}
946
947/**
948 * Destroys a recording context, internal version.
949 */
950void RecordingContext::destroyInternal(void)
951{
952 lock();
953
954 if (m_enmState == RECORDINGSTS_UNINITIALIZED)
955 {
956 unlock();
957 return;
958 }
959
960 int vrc = stopInternal();
961 AssertRCReturnVoid(vrc);
962
963 vrc = RTSemEventDestroy(m_WaitEvent);
964 AssertRCReturnVoid(vrc);
965
966 m_WaitEvent = NIL_RTSEMEVENT;
967
968 RecordingStreams::iterator it = m_vecStreams.begin();
969 while (it != m_vecStreams.end())
970 {
971 RecordingStream *pStream = (*it);
972
973 vrc = pStream->Uninit();
974 AssertRC(vrc);
975
976 delete pStream;
977 pStream = NULL;
978
979 m_vecStreams.erase(it);
980 it = m_vecStreams.begin();
981 }
982
983 /* Sanity. */
984 Assert(m_vecStreams.empty());
985 Assert(m_mapBlocksRaw.size() == 0);
986 Assert(m_mapBlocksEncoded.size() == 0);
987
988 m_enmState = RECORDINGSTS_UNINITIALIZED;
989
990 unconst(m_pProgress).setNull();
991
992 unlock();
993}
994
995/**
996 * Returns a recording context's current settings.
997 *
998 * @returns The recording context's current settings.
999 */
1000const settings::Recording &RecordingContext::GetConfig(void) const
1001{
1002 return m_Settings;
1003}
1004
1005/**
1006 * Returns the recording stream for a specific screen.
1007 *
1008 * @returns Recording stream for a specific screen, or NULL if not found.
1009 * @param uScreen Screen ID to retrieve recording stream for.
1010 */
1011RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
1012{
1013 RecordingStream *pStream;
1014
1015 try
1016 {
1017 pStream = m_vecStreams.at(uScreen);
1018 }
1019 catch (std::out_of_range &)
1020 {
1021 pStream = NULL;
1022 }
1023
1024 return pStream;
1025}
1026
1027/**
1028 * Locks the recording context for serializing access.
1029 *
1030 * @returns VBox status code.
1031 */
1032int RecordingContext::lock(void)
1033{
1034 int vrc = RTCritSectEnter(&m_CritSect);
1035 AssertRC(vrc);
1036 return vrc;
1037}
1038
1039/**
1040 * Unlocks the recording context for serializing access.
1041 *
1042 * @returns VBox status code.
1043 */
1044int RecordingContext::unlock(void)
1045{
1046 int vrc = RTCritSectLeave(&m_CritSect);
1047 AssertRC(vrc);
1048 return vrc;
1049}
1050
1051/**
1052 * Retrieves a specific recording stream of a recording context.
1053 *
1054 * @returns Pointer to recording stream if found, or NULL if not found.
1055 * @param uScreen Screen number of recording stream to look up.
1056 */
1057RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
1058{
1059 return getStreamInternal(uScreen);
1060}
1061
1062/**
1063 * Returns the number of configured recording streams for a recording context.
1064 *
1065 * @returns Number of configured recording streams.
1066 */
1067size_t RecordingContext::GetStreamCount(void) const
1068{
1069 return m_vecStreams.size();
1070}
1071
1072/**
1073 * Creates a new recording context.
1074 *
1075 * @returns VBox status code.
1076 * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
1077 * @param Settings Reference to recording settings to use for creation.
1078 * @param pProgress Progress object returned on success.
1079 */
1080int RecordingContext::Create(Console *ptrConsole, const settings::Recording &Settings, ComPtr<IProgress> &pProgress)
1081{
1082 return createInternal(ptrConsole, Settings, pProgress);
1083}
1084
1085/**
1086 * Destroys a recording context.
1087 */
1088void RecordingContext::Destroy(void)
1089{
1090 destroyInternal();
1091}
1092
1093/**
1094 * Starts a recording context.
1095 *
1096 * @returns VBox status code.
1097 */
1098int RecordingContext::Start(void)
1099{
1100 return startInternal();
1101}
1102
1103/**
1104 * Stops a recording context.
1105 */
1106int RecordingContext::Stop(void)
1107{
1108 return stopInternal();
1109}
1110
1111/**
1112 * Returns the current PTS (presentation time stamp) for a recording context.
1113 *
1114 * @returns Current PTS.
1115 */
1116uint64_t RecordingContext::GetCurrentPTS(void) const
1117{
1118 return RTTimeMilliTS() - m_tsStartMs;
1119}
1120
1121/**
1122 * Returns if a specific recoding feature is enabled for at least one of the attached
1123 * recording streams or not.
1124 *
1125 * @returns @c true if at least one recording stream has this feature enabled, or @c false if
1126 * no recording stream has this feature enabled.
1127 * @param enmFeature Recording feature to check for.
1128 */
1129bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
1130{
1131 lock();
1132
1133 RecordingStreams::const_iterator itStream = m_vecStreams.begin();
1134 while (itStream != m_vecStreams.end())
1135 {
1136 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
1137 {
1138 unlock();
1139 return true;
1140 }
1141 ++itStream;
1142 }
1143
1144 unlock();
1145
1146 return false;
1147}
1148
1149/**
1150 * Returns if this recording context is ready to start recording.
1151 *
1152 * @returns @c true if recording context is ready, @c false if not.
1153 */
1154bool RecordingContext::IsReady(void)
1155{
1156 lock();
1157
1158 const bool fIsReady = m_enmState >= RECORDINGSTS_CREATED;
1159
1160 unlock();
1161
1162 return fIsReady;
1163}
1164
1165/**
1166 * Returns if a feature for a given stream is enabled or not.
1167 *
1168 * @returns @c true if the specified feature is enabled (running), @c false if not.
1169 * @param uScreen Screen ID.
1170 * @param enmFeature Feature of stream to check for.
1171 *
1172 * @note Implies that the stream is enabled (i.e. active).
1173 */
1174bool RecordingContext::IsFeatureEnabled(uint32_t uScreen, RecordingFeature_T enmFeature)
1175{
1176 lock();
1177
1178 bool fIsReady = false;
1179
1180 if (m_enmState == RECORDINGSTS_STARTED)
1181 {
1182 const RecordingStream *pStream = getStreamInternal(uScreen);
1183 if (pStream)
1184 fIsReady = pStream->IsFeatureEnabled(enmFeature);
1185
1186 /* Note: Do not check for other constraints like the video FPS rate here,
1187 * as this check then also would affect other (non-FPS related) stuff
1188 * like audio data. */
1189 }
1190
1191 unlock();
1192
1193 return fIsReady;
1194}
1195
1196/**
1197 * Returns whether a given recording context has been started or not.
1198 *
1199 * @returns @c true if started, @c false if not.
1200 */
1201bool RecordingContext::IsStarted(void)
1202{
1203 lock();
1204
1205 const bool fIsStarted = m_enmState == RECORDINGSTS_STARTED;
1206
1207 unlock();
1208
1209 return fIsStarted;
1210}
1211
1212/**
1213 * Checks if a specified limit for recording has been reached.
1214 *
1215 * @returns @c true if any limit has been reached, @c false if not.
1216 */
1217bool RecordingContext::IsLimitReached(void)
1218{
1219 lock();
1220
1221 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
1222
1223 const bool fLimitReached = m_cStreamsEnabled == 0;
1224
1225 unlock();
1226
1227 return fLimitReached;
1228}
1229
1230/**
1231 * Checks if a specified limit for recording has been reached.
1232 *
1233 * @returns @c true if any limit has been reached, @c false if not.
1234 * @param uScreen Screen ID.
1235 * @param msTimestamp Timestamp (PTS, in ms) to check for.
1236 */
1237bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
1238{
1239 lock();
1240
1241 bool fLimitReached = false;
1242
1243 const RecordingStream *pStream = getStreamInternal(uScreen);
1244 if ( !pStream
1245 || pStream->IsLimitReached(msTimestamp))
1246 {
1247 fLimitReached = true;
1248 }
1249
1250 unlock();
1251
1252 return fLimitReached;
1253}
1254
1255/**
1256 * Returns if a specific screen needs to be fed with an update or not.
1257 *
1258 * @returns @c true if an update is needed, @c false if not.
1259 * @param uScreen Screen ID to retrieve update stats for.
1260 * @param msTimestamp Timestamp (PTS, in ms).
1261 */
1262bool RecordingContext::NeedsUpdate(uint32_t uScreen, uint64_t msTimestamp)
1263{
1264 lock();
1265
1266 bool fNeedsUpdate = false;
1267
1268 if (m_enmState == RECORDINGSTS_STARTED)
1269 {
1270#ifdef VBOX_WITH_AUDIO_RECORDING
1271 if ( recordingCodecIsInitialized(&m_CodecAudio)
1272 && recordingCodecGetWritable(&m_CodecAudio, msTimestamp) > 0)
1273 {
1274 fNeedsUpdate = true;
1275 }
1276#endif /* VBOX_WITH_AUDIO_RECORDING */
1277
1278 if (!fNeedsUpdate)
1279 {
1280 const RecordingStream *pStream = getStreamInternal(uScreen);
1281 if (pStream)
1282 fNeedsUpdate = pStream->NeedsUpdate(msTimestamp);
1283 }
1284 }
1285
1286 unlock();
1287
1288 return fNeedsUpdate;
1289}
1290
1291/**
1292 * Gets called by a stream if its limit has been reached.
1293 *
1294 * @returns VBox status code.
1295 * @param uScreen The stream's ID (Screen ID).
1296 * @param vrc Result code of the limit operation.
1297 */
1298int RecordingContext::onLimitReached(uint32_t uScreen, int vrc)
1299{
1300 lock();
1301
1302 LogRel2(("Recording: Active streams: %RU16\n", m_cStreamsEnabled));
1303
1304 if (m_cStreamsEnabled)
1305 m_cStreamsEnabled--;
1306
1307 bool const fAllDisabled = m_cStreamsEnabled == 0;
1308
1309 if (fAllDisabled)
1310 LogRel(("Recording: All set limits have been reached\n"));
1311 else
1312 LogRel(("Recording: Set limit for screen #%RU32 has been reached\n", uScreen));
1313
1314 unlock(); /* Leave the lock before invoking callbacks. */
1315
1316 if (m_Callbacks.pfnStateChanged)
1317 m_Callbacks.pfnStateChanged(this, RECORDINGSTS_LIMIT_REACHED,
1318 fAllDisabled ? UINT32_MAX : uScreen, vrc, m_Callbacks.pvUser);
1319
1320 return VINF_SUCCESS;
1321}
1322
1323/**
1324 * Sends an audio frame to the recording thread.
1325 *
1326 * @returns VBox status code.
1327 * @param pvData Audio frame data to send.
1328 * @param cbData Size (in bytes) of (encoded) audio frame data.
1329 * @param msTimestamp Timestamp (PTS, in ms) of audio playback.
1330 */
1331int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
1332{
1333#ifdef VBOX_WITH_AUDIO_RECORDING
1334 return writeCommonData(m_mapBlocksRaw, &m_CodecAudio,
1335 pvData, cbData, msTimestamp, RECORDINGCODEC_ENC_F_BLOCK_IS_KEY);
1336#else
1337 RT_NOREF(pvData, cbData, msTimestamp);
1338 return VERR_NOT_SUPPORTED;
1339#endif
1340}
1341
1342/**
1343 * Sends a video frame to the recording thread.
1344 *
1345 * @thread EMT
1346 *
1347 * @returns VBox status code.
1348 * @param uScreen Screen number to send video frame to.
1349 * @param pFrame Video frame to send.
1350 * @param msTimestamp Timestamp (PTS, in ms).
1351 */
1352int RecordingContext::SendVideoFrame(uint32_t uScreen, PRECORDINGVIDEOFRAME pFrame, uint64_t msTimestamp)
1353{
1354 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
1355
1356 LogFlowFunc(("uScreen=%RU32, offX=%RU32, offY=%RU32, w=%RU32, h=%RU32 (%zu bytes), msTimestamp=%RU64\n",
1357 uScreen, pFrame->Pos.x, pFrame->Pos.y, pFrame->Info.uWidth, pFrame->Info.uHeight,
1358 pFrame->Info.uHeight * pFrame->Info.uWidth * (pFrame->Info.uBPP / 8), msTimestamp));
1359
1360 if (!pFrame->pau8Buf) /* Empty / invalid frame, skip. */
1361 return VINF_SUCCESS;
1362
1363 /* Sanity. */
1364 AssertReturn(pFrame->Info.uBPP, VERR_INVALID_PARAMETER);
1365 AssertReturn(pFrame->cbBuf, VERR_INVALID_PARAMETER);
1366 AssertReturn(pFrame->Info.uWidth * pFrame->Info.uHeight * (pFrame->Info.uBPP / 8) <= pFrame->cbBuf, VERR_INVALID_PARAMETER);
1367
1368 lock();
1369
1370 RecordingStream *pStream = getStreamInternal(uScreen);
1371 if (!pStream)
1372 {
1373 unlock();
1374 return VINF_SUCCESS;
1375 }
1376
1377 unlock();
1378
1379 int vrc = pStream->SendVideoFrame(pFrame, msTimestamp);
1380 if (vrc == VINF_SUCCESS) /* Might be VINF_RECORDING_THROTTLED or VINF_RECORDING_LIMIT_REACHED. */
1381 threadNotify();
1382
1383 return vrc;
1384}
1385
1386/**
1387 * Sends a cursor position change to the recording context.
1388 *
1389 * @returns VBox status code.
1390 * @param uScreen Screen number.
1391 * @param x X location within the guest.
1392 * @param y Y location within the guest.
1393 * @param msTimestamp Timestamp (PTS, in ms).
1394 */
1395int RecordingContext::SendCursorPositionChange(uint32_t uScreen, int32_t x, int32_t y, uint64_t msTimestamp)
1396{
1397 LogFlowFunc(("uScreen=%RU32, x=%RU32, y=%RU32\n", uScreen, x, y));
1398
1399 /* If no cursor shape is set yet, skip any cursor position changes. */
1400 if (!m_Cursor.m_Shape.pau8Buf)
1401 return VINF_SUCCESS;
1402
1403 int vrc = m_Cursor.Move(x, y);
1404 if (RT_SUCCESS(vrc))
1405 {
1406 lock();
1407
1408 RecordingStream *pStream = getStreamInternal(uScreen);
1409 if (!pStream)
1410 {
1411 unlock();
1412 return VINF_SUCCESS;
1413 }
1414
1415 unlock();
1416
1417 vrc = pStream->SendCursorPos(0 /* idCursor */, &m_Cursor.m_Shape.Pos, msTimestamp);
1418 if (vrc == VINF_SUCCESS) /* Might be VINF_RECORDING_THROTTLED or VINF_RECORDING_LIMIT_REACHED. */
1419 threadNotify();
1420 }
1421
1422 return vrc;
1423}
1424
1425/**
1426 * Sends a cursor shape change to the recording context.
1427 *
1428 * @returns VBox status code.
1429 * @param fVisible Whether the mouse cursor actually is visible or not.
1430 * @param fAlpha Whether the pixel data contains alpha channel information or not.
1431 * @param xHot X hot position (in pixel) of the new cursor.
1432 * @param yHot Y hot position (in pixel) of the new cursor.
1433 * @param uWidth Width (in pixel) of the new cursor.
1434 * @param uHeight Height (in pixel) of the new cursor.
1435 * @param pu8Shape Pixel data of the new cursor. Must be 32 BPP RGBA for now.
1436 * @param cbShape Size of \a pu8Shape (in bytes).
1437 * @param msTimestamp Timestamp (PTS, in ms).
1438 */
1439int RecordingContext::SendCursorShapeChange(bool fVisible, bool fAlpha, uint32_t xHot, uint32_t yHot,
1440 uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape,
1441 uint64_t msTimestamp)
1442{
1443 RT_NOREF(fAlpha, xHot, yHot);
1444
1445 LogFlowFunc(("fVisible=%RTbool, fAlpha=%RTbool, uWidth=%RU32, uHeight=%RU32\n", fVisible, fAlpha, uWidth, uHeight));
1446
1447 if ( !pu8Shape /* Might be NULL on saved state load. */
1448 || !fVisible)
1449 return VINF_SUCCESS;
1450
1451 AssertReturn(cbShape, VERR_INVALID_PARAMETER);
1452
1453 lock();
1454
1455 int vrc = m_Cursor.CreateOrUpdate(fAlpha, uWidth, uHeight, pu8Shape, cbShape);
1456
1457 RecordingStreams::iterator it = m_vecStreams.begin();
1458 while (it != m_vecStreams.end())
1459 {
1460 RecordingStream *pStream = (*it);
1461
1462 int vrc2 = pStream->SendCursorShape(0 /* idCursor */, &m_Cursor.m_Shape, msTimestamp);
1463 if (RT_SUCCESS(vrc))
1464 vrc = vrc2;
1465
1466 /* Bail out as soon as possible when the shutdown flag is set. */
1467 if (ASMAtomicReadBool(&m_fShutdown))
1468 break;
1469
1470 ++it;
1471 }
1472
1473 unlock();
1474
1475 if (vrc == VINF_SUCCESS) /* Might be VINF_RECORDING_THROTTLED or VINF_RECORDING_LIMIT_REACHED. */
1476 threadNotify();
1477
1478 return vrc;
1479}
1480
1481/**
1482 * Sends a screen change to a recording stream.
1483 *
1484 * @returns VBox status code.
1485 * @param uScreen Screen number.
1486 * @param pInfo Recording screen info to use.
1487 * @param msTimestamp Timestamp (PTS, in ms).
1488 */
1489int RecordingContext::SendScreenChange(uint32_t uScreen, PRECORDINGSURFACEINFO pInfo, uint64_t msTimestamp)
1490{
1491 lock();
1492
1493 RecordingStream *pStream = getStreamInternal(uScreen);
1494 if (!pStream)
1495 {
1496 unlock();
1497 return VINF_SUCCESS;
1498 }
1499
1500 unlock();
1501
1502 int const vrc = pStream->SendScreenChange(pInfo, msTimestamp);
1503
1504 return vrc;
1505}
1506
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