VirtualBox

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

Last change on this file since 106966 was 106061, checked in by vboxsync, 5 months ago

Copyright year updates by scm.

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