VirtualBox

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

Last change on this file since 107402 was 107188, checked in by vboxsync, 7 weeks ago

src/VBox/Main/include/Recording: Fixed missing constructor intializers, found by Parfait.

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