VirtualBox

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

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

Recording: Implemented support for a dedicated progress object, which is exposed to API clients. This can be used for better tracking the recording progress as well as for error reporting. The RecordingSettings API also now has a dedicated start() method to start recording, as well as support for attaching to an already ongoing recording by retrieving the progress object at a later time. Adapted FE/Qt (draft, see @todos), FE/VBoxManage and the Validation Kit testdriver to the new APIs. VBoxManage also can attach to an ongoing recording now. The recording progress object also will have multiple operations to get the recording progress for convenience [build fix]. bugref:10718

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