VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/RecordingStream.cpp

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.8 KB
Line 
1/* $Id: RecordingStream.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * Recording stream code.
4 */
5
6/*
7 * Copyright (C) 2012-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#ifdef LOG_GROUP
29# undef LOG_GROUP
30#endif
31#define LOG_GROUP LOG_GROUP_RECORDING
32#include "LoggingNew.h"
33
34#include <iprt/path.h>
35
36#ifdef VBOX_RECORDING_DUMP
37# include <iprt/formats/bmp.h>
38#endif
39
40#ifdef VBOX_WITH_AUDIO_RECORDING
41# include <VBox/vmm/pdmaudioinline.h>
42#endif
43
44#include "Recording.h"
45#include "RecordingUtils.h"
46#include "WebMWriter.h"
47
48
49RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreen &Settings)
50 : m_enmState(RECORDINGSTREAMSTATE_UNINITIALIZED)
51{
52 int vrc2 = initInternal(a_pCtx, uScreen, Settings);
53 if (RT_FAILURE(vrc2))
54 throw vrc2;
55}
56
57RecordingStream::~RecordingStream(void)
58{
59 int vrc2 = uninitInternal();
60 AssertRC(vrc2);
61}
62
63/**
64 * Opens a recording stream.
65 *
66 * @returns VBox status code.
67 * @param screenSettings Recording settings to use.
68 */
69int RecordingStream::open(const settings::RecordingScreen &screenSettings)
70{
71 /* Sanity. */
72 Assert(screenSettings.enmDest != RecordingDestination_None);
73
74 int vrc;
75
76 switch (screenSettings.enmDest)
77 {
78 case RecordingDestination_File:
79 {
80 Assert(screenSettings.File.strName.isNotEmpty());
81
82 const char *pszFile = screenSettings.File.strName.c_str();
83
84 RTFILE hFile = NIL_RTFILE;
85 vrc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
86 if (RT_SUCCESS(vrc))
87 {
88 LogRel2(("Recording: Opened file '%s'\n", pszFile));
89
90 try
91 {
92 Assert(File.m_pWEBM == NULL);
93 File.m_pWEBM = new WebMWriter();
94 }
95 catch (std::bad_alloc &)
96 {
97 vrc = VERR_NO_MEMORY;
98 }
99
100 if (RT_SUCCESS(vrc))
101 {
102 this->File.m_hFile = hFile;
103 m_ScreenSettings.File.strName = pszFile;
104 }
105 }
106 else
107 LogRel(("Recording: Failed to open file '%s' for screen %RU32, vrc=%Rrc\n",
108 pszFile ? pszFile : "<Unnamed>", m_uScreenID, vrc));
109
110 if (RT_FAILURE(vrc))
111 {
112 if (hFile != NIL_RTFILE)
113 RTFileClose(hFile);
114 }
115
116 break;
117 }
118
119 default:
120 vrc = VERR_NOT_IMPLEMENTED;
121 break;
122 }
123
124 LogFlowFuncLeaveRC(vrc);
125 return vrc;
126}
127
128/**
129 * Returns the recording stream's used configuration.
130 *
131 * @returns The recording stream's used configuration.
132 */
133const settings::RecordingScreen &RecordingStream::GetConfig(void) const
134{
135 return m_ScreenSettings;
136}
137
138/**
139 * Checks if a specified limit for a recording stream has been reached, internal version.
140 *
141 * @returns @c true if any limit has been reached, @c false if not.
142 * @param msTimestamp Timestamp (PTS, in ms) to check for.
143 */
144bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const
145{
146 LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n",
147 msTimestamp, m_ScreenSettings.ulMaxTimeS, m_tsStartMs));
148
149 if ( m_ScreenSettings.ulMaxTimeS
150 && msTimestamp >= m_ScreenSettings.ulMaxTimeS * RT_MS_1SEC)
151 {
152 LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n",
153 m_uScreenID, m_ScreenSettings.ulMaxTimeS));
154 return true;
155 }
156
157 if (m_ScreenSettings.enmDest == RecordingDestination_File)
158 {
159 if (m_ScreenSettings.File.ulMaxSizeMB)
160 {
161 uint64_t sizeInMB = this->File.m_pWEBM->GetFileSize() / _1M;
162 if(sizeInMB >= m_ScreenSettings.File.ulMaxSizeMB)
163 {
164 LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n",
165 m_uScreenID, m_ScreenSettings.File.ulMaxSizeMB));
166 return true;
167 }
168 }
169
170 /* Check for available free disk space */
171 if ( this->File.m_pWEBM
172 && this->File.m_pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
173 {
174 LogRel(("Recording: Not enough free storage space available, stopping recording\n"));
175 return true;
176 }
177 }
178
179 return false;
180}
181
182/**
183 * Internal iteration main loop.
184 * Does housekeeping and recording context notification.
185 *
186 * @returns VBox status code.
187 * @retval VINF_RECORDING_LIMIT_REACHED if the stream's recording limit has been reached.
188 * @param msTimestamp Timestamp (PTS, in ms).
189 *
190 * @note Caller must *not* have the stream's lock (callbacks involved).
191 */
192int RecordingStream::iterateInternal(uint64_t msTimestamp)
193{
194 AssertReturn(!RTCritSectIsOwner(&m_CritSect), VERR_WRONG_ORDER);
195
196 if (!m_fEnabled)
197 return VINF_SUCCESS;
198
199 int vrc;
200
201 if (isLimitReachedInternal(msTimestamp))
202 {
203 vrc = VINF_RECORDING_LIMIT_REACHED;
204 }
205 else
206 vrc = VINF_SUCCESS;
207
208 AssertPtr(m_pCtx);
209
210 switch (vrc)
211 {
212 case VINF_RECORDING_LIMIT_REACHED:
213 {
214 m_fEnabled = false;
215
216 int vrc2 = m_pCtx->onLimitReached(m_uScreenID, VINF_SUCCESS /* vrc */);
217 AssertRC(vrc2);
218 break;
219 }
220
221 default:
222 break;
223 }
224
225 LogFlowFuncLeaveRC(vrc);
226 return vrc;
227}
228
229/**
230 * Checks if a specified limit for a recording stream has been reached.
231 *
232 * @returns @c true if any limit has been reached, @c false if not.
233 * @param msTimestamp Timestamp (PTS, in ms) to check for.
234 */
235bool RecordingStream::IsLimitReached(uint64_t msTimestamp) const
236{
237 if (!m_fEnabled)
238 return true;
239
240 return isLimitReachedInternal(msTimestamp);
241}
242
243/**
244 * Returns whether a feature for a recording stream is enabled or not.
245 *
246 * @returns @c true if ready, @c false if not.
247 * @param enmFeature Feature of stream to check enabled status for.
248 */
249bool RecordingStream::IsFeatureEnabled(RecordingFeature_T enmFeature) const
250{
251 return m_fEnabled && m_ScreenSettings.isFeatureEnabled(enmFeature);
252}
253
254/**
255 * Returns if a recording stream needs to be fed with an update or not.
256 *
257 * @returns @c true if an update is needed, @c false if not.
258 * @param msTimestamp Timestamp (PTS, in ms).
259 */
260bool RecordingStream::NeedsUpdate(uint64_t msTimestamp) const
261{
262 return recordingCodecGetWritable((const PRECORDINGCODEC)&m_CodecVideo, msTimestamp) > 0;
263}
264
265/**
266 * Processes a recording stream.
267 *
268 * This function takes care of the actual encoding and writing of a certain stream.
269 * As this can be very CPU intensive, this function usually is called from a separate thread.
270 *
271 * @returns VBox status code.
272 * @param streamBlocks Block set of stream to process.
273 * @param commonBlocks Block set of common blocks to process for this stream.
274 *
275 * @note Runs in recording thread.
276 */
277int RecordingStream::process(const RecordingBlockSet &streamBlocks, RecordingBlockMap &commonBlocks)
278{
279 LogFlowFuncEnter();
280
281 lock();
282
283 if (!m_ScreenSettings.fEnabled)
284 {
285 unlock();
286 return VINF_SUCCESS;
287 }
288
289 int vrc = VINF_SUCCESS;
290
291 RecordingBlockMap::const_iterator itStreamBlock = streamBlocks.Map.begin();
292 while (itStreamBlock != streamBlocks.Map.end())
293 {
294 uint64_t const msTimestamp = itStreamBlock->first; RT_NOREF(msTimestamp);
295 RecordingBlocks *pBlocks = itStreamBlock->second;
296
297 AssertPtr(pBlocks);
298
299 RecordingBlockList::const_iterator itBlockInList = pBlocks->List.cbegin();
300 while (itBlockInList != pBlocks->List.cend())
301 {
302 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)(*itBlockInList)->pvData;
303 AssertPtr(pFrame);
304 Assert(pFrame->msTimestamp == msTimestamp);
305
306 switch (pFrame->enmType)
307 {
308 case RECORDINGFRAME_TYPE_VIDEO:
309 RT_FALL_THROUGH();
310 case RECORDINGFRAME_TYPE_CURSOR_SHAPE:
311 RT_FALL_THROUGH();
312 case RECORDINGFRAME_TYPE_CURSOR_POS:
313 {
314 int vrc2 = recordingCodecEncodeFrame(&m_CodecVideo, pFrame, pFrame->msTimestamp, m_pCtx /* pvUser */);
315 AssertRC(vrc2);
316 if (RT_SUCCESS(vrc))
317 vrc = vrc2;
318 break;
319 }
320
321 case RECORDINGFRAME_TYPE_SCREEN_CHANGE:
322 {
323 /* ignore rc */ recordingCodecScreenChange(&m_CodecVideo, &pFrame->u.ScreenInfo);
324 break;
325 }
326
327 default:
328 break;
329 }
330
331 ++itBlockInList;
332 }
333
334 ++itStreamBlock;
335 }
336
337#ifdef VBOX_WITH_AUDIO_RECORDING
338 /* Do we need to multiplex the common audio data to this stream? */
339 if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
340 {
341 /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
342 * written to the screen's assigned recording stream. */
343 RecordingBlockMap::const_iterator itBlockMap = commonBlocks.begin();
344 while (itBlockMap != commonBlocks.end())
345 {
346 RecordingBlockList &blockList = itBlockMap->second->List;
347
348 RecordingBlockList::iterator itBlockList = blockList.begin();
349 while (itBlockList != blockList.end())
350 {
351 RecordingBlock *pBlock = (RecordingBlock *)(*itBlockList);
352
353 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)pBlock->pvData;
354 Assert(pFrame->enmType == RECORDINGFRAME_TYPE_AUDIO);
355 PRECORDINGAUDIOFRAME pAudioFrame = &pFrame->u.Audio;
356
357 int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlock->msTimestamp, pBlock->uFlags);
358 if (RT_SUCCESS(vrc))
359 vrc = vrc2;
360
361 Log3Func(("RECORDINGFRAME_TYPE_AUDIO: %zu bytes -> %Rrc\n", pAudioFrame->cbBuf, vrc2));
362
363 Assert(pBlock->cRefs);
364 pBlock->cRefs--;
365 if (pBlock->cRefs == 0)
366 {
367 blockList.erase(itBlockList);
368 delete pBlock;
369 itBlockList = blockList.begin();
370 }
371 else
372 ++itBlockList;
373 }
374
375 /* If no entries are left over in the block list, remove it altogether. */
376 if (blockList.empty())
377 {
378 delete itBlockMap->second;
379 commonBlocks.erase(itBlockMap);
380 itBlockMap = commonBlocks.begin();
381 }
382 else
383 ++itBlockMap;
384 }
385 }
386#else
387 RT_NOREF(commonBlocks);
388#endif /* VBOX_WITH_AUDIO_RECORDING */
389
390 unlock();
391
392 LogFlowFuncLeaveRC(vrc);
393 return vrc;
394}
395
396/**
397 * The stream's main routine called from the encoding thread.
398 *
399 * @returns VBox status code.
400 * @retval VINF_RECORDING_LIMIT_REACHED if the stream's recording limit has been reached.
401 * @param rcWait Result of the encoding thread's wait operation.
402 * Can be used for figuring out if the encoder has to perform some
403 * worked based on that result.
404 * @param msTimestamp Timestamp to use for PTS calculation (absolute).
405 * @param commonBlocks Common blocks multiplexed to all recording streams.
406 *
407 * @note Runs in encoding thread.
408 */
409int RecordingStream::ThreadMain(int rcWait, uint64_t msTimestamp, RecordingBlockMap &commonBlocks)
410{
411 Log3Func(("uScreenID=%RU16, msTimestamp=%RU64, rcWait=%Rrc\n", m_uScreenID, msTimestamp, rcWait));
412
413 /* No new data arrived within time? Feed the encoder with the last frame we built.
414 *
415 * This is necessary in order to render a video which has a consistent time line,
416 * as we only encode data when something has changed ("dirty areas"). */
417 if ( rcWait == VERR_TIMEOUT
418 && m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
419 {
420 return recordingCodecEncodeCurrent(&m_CodecVideo, msTimestamp);
421 }
422
423 int vrc = process(m_Blocks, commonBlocks);
424
425 /*
426 * Housekeeping.
427 *
428 * Here we delete all processed stream blocks of this stream.
429 * The common blocks will be deleted by the recording context (which owns those).
430 */
431 lock();
432
433 RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
434 while (itStreamBlocks != m_Blocks.Map.end())
435 {
436 RecordingBlocks *pBlocks = itStreamBlocks->second;
437 AssertPtr(pBlocks);
438 pBlocks->Clear();
439 Assert(pBlocks->List.empty());
440 delete pBlocks;
441
442 m_Blocks.Map.erase(itStreamBlocks);
443 itStreamBlocks = m_Blocks.Map.begin();
444 }
445 Assert(m_Blocks.Map.empty());
446
447 unlock();
448
449 return vrc;
450}
451
452/**
453 * Adds a recording frame to be fed to the encoder.
454 *
455 * @returns VBox status code.
456 * @param pFrame Recording frame to add.
457 * Ownership of the frame will be transferred to the encoder on success then.
458 * Must be free'd by the caller on failure.
459 * @param msTimestamp Timestamp (PTS, in ms).
460 *
461 * @note Caller needs to take the stream's lock.
462 */
463int RecordingStream::addFrame(PRECORDINGFRAME pFrame, uint64_t msTimestamp)
464{
465 int vrc;
466
467 Assert(pFrame->msTimestamp == msTimestamp); /* Sanity. */
468
469 try
470 {
471 RecordingBlock *pBlock = new RecordingBlock();
472
473 pBlock->pvData = pFrame;
474 pBlock->cbData = sizeof(RECORDINGFRAME);
475
476 try
477 {
478 RecordingBlocks *pRecordingBlocks;
479 RecordingBlockMap::const_iterator it = m_Blocks.Map.find(msTimestamp);
480 if (it == m_Blocks.Map.end())
481 {
482 pRecordingBlocks = new RecordingBlocks();
483 pRecordingBlocks->List.push_back(pBlock);
484 m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
485 }
486 else
487 {
488 pRecordingBlocks = it->second;
489 pRecordingBlocks->List.push_back(pBlock);
490 }
491
492 vrc = VINF_SUCCESS;
493 }
494 catch (const std::exception &)
495 {
496 delete pBlock;
497 vrc = VERR_NO_MEMORY;
498 }
499 }
500 catch (const std::exception &)
501 {
502 vrc = VERR_NO_MEMORY;
503 }
504
505 return vrc;
506}
507
508/**
509 * Sends a raw (e.g. not yet encoded) audio frame to the recording stream.
510 *
511 * @returns VBox status code.
512 * @param pvData Pointer to audio data.
513 * @param cbData Size (in bytes) of \a pvData.
514 * @param msTimestamp Timestamp (PTS, in ms).
515 */
516int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
517{
518 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
519
520 /* As audio data is common across all streams, re-route this to the recording context, where
521 * the data is being encoded and stored in the common blocks queue. */
522 return m_pCtx->SendAudioFrame(pvData, cbData, msTimestamp);
523}
524
525/**
526 * Sends a cursor position change to the recording stream.
527 *
528 * @returns VBox status code.
529 * @param idCursor Cursor ID. Currently unused and always set to 0.
530 * @param pPos Cursor information to send.
531 * @param msTimestamp Timestamp (PTS, in ms).
532 */
533int RecordingStream::SendCursorPos(uint8_t idCursor, PRECORDINGPOS pPos, uint64_t msTimestamp)
534{
535 RT_NOREF(idCursor);
536 AssertPtrReturn(pPos, VERR_INVALID_POINTER);
537
538 int vrc = iterateInternal(msTimestamp);
539 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
540 return vrc;
541
542 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
543 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
544 pFrame->enmType = RECORDINGFRAME_TYPE_CURSOR_POS;
545 pFrame->msTimestamp = msTimestamp;
546
547 pFrame->u.Cursor.Pos = *pPos;
548
549 lock();
550
551 vrc = addFrame(pFrame, msTimestamp);
552
553 unlock();
554
555 return vrc;
556}
557
558/**
559 * Sends a cursor shape change to the recording stream.
560 *
561 * @returns VBox status code.
562 * @param idCursor Cursor ID. Currently unused and always set to 0.
563 * @param pShape Cursor shape to send.
564 * @param msTimestamp Timestamp (PTS, in ms).
565 *
566 * @note Keep it as simple as possible, as this function might run on EMT.
567 * @thread EMT
568 */
569int RecordingStream::SendCursorShape(uint8_t idCursor, PRECORDINGVIDEOFRAME pShape, uint64_t msTimestamp)
570{
571 RT_NOREF(idCursor);
572 AssertPtrReturn(pShape, VERR_INVALID_POINTER);
573 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
574
575 int vrc = iterateInternal(msTimestamp);
576 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
577 return vrc;
578
579 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
580 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
581
582 pFrame->u.Video = *pShape;
583 /* Make a deep copy of the pixel data. */
584 pFrame->u.Video.pau8Buf = (uint8_t *)RTMemDup(pShape->pau8Buf, pShape->cbBuf);
585 AssertPtrReturnStmt(pFrame->u.Video.pau8Buf, RTMemFree(pFrame), VERR_NO_MEMORY);
586 pFrame->u.Video.cbBuf = pShape->cbBuf;
587
588 pFrame->enmType = RECORDINGFRAME_TYPE_CURSOR_SHAPE;
589 pFrame->msTimestamp = msTimestamp;
590
591 lock();
592
593 vrc = addFrame(pFrame, msTimestamp);
594
595 unlock();
596
597 if (RT_FAILURE(vrc))
598 {
599 RecordingVideoFrameDestroy(&pFrame->u.Video);
600 RecordingFrameFree(pFrame);
601 }
602
603 LogFlowFuncLeaveRC(vrc);
604 return vrc;
605}
606
607/**
608 * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
609 *
610 * @returns VBox status code.
611 * @retval VINF_RECORDING_LIMIT_REACHED if the stream's recording limit has been reached.
612 * @retval VINF_RECORDING_THROTTLED if the frame is too early for the current FPS setting.
613 * @param pVideoFrame Video frame to send.
614 * @param msTimestamp Timestamp (PTS, in ms).
615 *
616 * @note Keep it as simple as possible, as this function might run on EMT.
617 * @thread EMT
618 */
619int RecordingStream::SendVideoFrame(PRECORDINGVIDEOFRAME pVideoFrame, uint64_t msTimestamp)
620{
621 AssertPtrReturn(pVideoFrame, VERR_INVALID_POINTER);
622 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
623
624 int vrc = iterateInternal(msTimestamp);
625 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
626 return vrc;
627
628 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
629 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
630
631 pFrame->u.Video = *pVideoFrame;
632
633 /* Make a deep copy of the pixel data. */
634 pFrame->u.Video.pau8Buf = (uint8_t *)RTMemAlloc(pVideoFrame->cbBuf);
635 AssertPtrReturnStmt(pFrame->u.Video.pau8Buf, RTMemFree(pFrame), VERR_NO_MEMORY);
636 size_t offDst = 0;
637 size_t offSrc = 0;
638 size_t const cbDstBytesPerLine = pVideoFrame->Info.uWidth * (pVideoFrame->Info.uBPP / 8);
639 for (uint32_t h = 0; h < pFrame->u.Video.Info.uHeight; h++)
640 {
641 memcpy(pFrame->u.Video.pau8Buf + offDst, pVideoFrame->pau8Buf + offSrc, cbDstBytesPerLine);
642 offDst += cbDstBytesPerLine;
643 offSrc += pVideoFrame->Info.uBytesPerLine;
644 }
645 pFrame->u.Video.Info.uBytesPerLine = (uint32_t)cbDstBytesPerLine;
646
647 pFrame->enmType = RECORDINGFRAME_TYPE_VIDEO;
648 pFrame->msTimestamp = msTimestamp;
649
650 lock();
651
652 vrc = addFrame(pFrame, msTimestamp);
653
654 unlock();
655
656 if (RT_FAILURE(vrc))
657 {
658 RecordingVideoFrameDestroy(&pFrame->u.Video);
659 RecordingFrameFree(pFrame);
660 }
661
662 LogFlowFuncLeaveRC(vrc);
663 return vrc;
664}
665
666/**
667 * Sends a screen size change to a recording stream.
668 *
669 * @returns VBox status code.
670 * @param pInfo Recording screen info to use.
671 * @param msTimestamp Timestamp (PTS, in ms).
672 * @param fForce Set to \c true to force a change, otherwise to \c false.
673 */
674int RecordingStream::SendScreenChange(PRECORDINGSURFACEINFO pInfo, uint64_t msTimestamp, bool fForce /* = false */)
675{
676 AssertPtrReturn(pInfo, VERR_INVALID_POINTER);
677
678 if ( !pInfo->uWidth
679 || !pInfo->uHeight)
680 return VINF_SUCCESS;
681
682 RT_NOREF(fForce);
683
684 LogRel(("Recording: Size of screen #%RU32 changed to %RU32x%RU32 (%RU8 BPP)\n",
685 m_uScreenID, pInfo->uWidth, pInfo->uHeight, pInfo->uBPP));
686
687 lock();
688
689 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
690 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
691 pFrame->enmType = RECORDINGFRAME_TYPE_SCREEN_CHANGE;
692 pFrame->msTimestamp = msTimestamp;
693
694 pFrame->u.ScreenInfo = *pInfo;
695
696 int vrc = addFrame(pFrame, msTimestamp);
697
698 unlock();
699
700 LogFlowFuncLeaveRC(vrc);
701 return vrc;
702}
703
704/**
705 * Initializes a recording stream.
706 *
707 * @returns VBox status code.
708 * @param pCtx Pointer to recording context.
709 * @param uScreen Screen number to use for this recording stream.
710 * @param Settings Recording screen configuration to use for initialization.
711 */
712int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreen &Settings)
713{
714 return initInternal(pCtx, uScreen, Settings);
715}
716
717/**
718 * Initializes a recording stream, internal version.
719 *
720 * @returns VBox status code.
721 * @param pCtx Pointer to recording context.
722 * @param uScreen Screen number to use for this recording stream.
723 * @param screenSettings Recording screen configuration to use for initialization.
724 */
725int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
726 const settings::RecordingScreen &screenSettings)
727{
728 AssertReturn(m_enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
729
730 m_pCtx = pCtx;
731 m_uTrackAudio = UINT8_MAX;
732 m_uTrackVideo = UINT8_MAX;
733 m_tsStartMs = 0;
734 m_uScreenID = uScreen;
735#ifdef VBOX_WITH_AUDIO_RECORDING
736 /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
737 m_pCodecAudio = m_pCtx->GetCodecAudio();
738#endif
739 m_ScreenSettings = screenSettings;
740
741 settings::RecordingScreen *pSettings = &m_ScreenSettings;
742
743 int vrc = RTCritSectInit(&m_CritSect);
744 if (RT_FAILURE(vrc))
745 return vrc;
746
747 this->File.m_pWEBM = NULL;
748 this->File.m_hFile = NIL_RTFILE;
749
750 vrc = open(*pSettings);
751 if (RT_FAILURE(vrc))
752 return vrc;
753
754 const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
755 const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
756
757 if (fVideoEnabled)
758 {
759 vrc = initVideo(*pSettings);
760 if (RT_FAILURE(vrc))
761 return vrc;
762 }
763
764 switch (pSettings->enmDest)
765 {
766 case RecordingDestination_File:
767 {
768 Assert(pSettings->File.strName.isNotEmpty());
769 const char *pszFile = pSettings->File.strName.c_str();
770
771 AssertPtr(File.m_pWEBM);
772 vrc = File.m_pWEBM->OpenEx(pszFile, &this->File.m_hFile,
773 fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
774 fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
775 if (RT_FAILURE(vrc))
776 {
777 LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
778 break;
779 }
780
781 if (fVideoEnabled)
782 {
783 vrc = this->File.m_pWEBM->AddVideoTrack(&m_CodecVideo,
784 pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
785 &m_uTrackVideo);
786 if (RT_FAILURE(vrc))
787 {
788 LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
789 break;
790 }
791
792 LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
793 m_uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
794 pSettings->Video.ulRate, pSettings->Video.ulFPS, m_uTrackVideo));
795 }
796
797#ifdef VBOX_WITH_AUDIO_RECORDING
798 if (fAudioEnabled)
799 {
800 AssertPtr(m_pCodecAudio);
801 vrc = this->File.m_pWEBM->AddAudioTrack(m_pCodecAudio,
802 pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
803 &m_uTrackAudio);
804 if (RT_FAILURE(vrc))
805 {
806 LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
807 break;
808 }
809
810 LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
811 m_uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
812 pSettings->Audio.cChannels ? "channels" : "channel", m_uTrackAudio));
813 }
814#endif
815
816 if ( fVideoEnabled
817#ifdef VBOX_WITH_AUDIO_RECORDING
818 || fAudioEnabled
819#endif
820 )
821 {
822 char szWhat[32] = { 0 };
823 if (fVideoEnabled)
824 RTStrCat(szWhat, sizeof(szWhat), "video");
825#ifdef VBOX_WITH_AUDIO_RECORDING
826 if (fAudioEnabled)
827 {
828 if (fVideoEnabled)
829 RTStrCat(szWhat, sizeof(szWhat), " + ");
830 RTStrCat(szWhat, sizeof(szWhat), "audio");
831 }
832#endif
833 LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, m_uScreenID, pszFile));
834 }
835
836 break;
837 }
838
839 default:
840 AssertFailed(); /* Should never happen. */
841 vrc = VERR_NOT_IMPLEMENTED;
842 break;
843 }
844
845 if (RT_SUCCESS(vrc))
846 {
847 m_enmState = RECORDINGSTREAMSTATE_INITIALIZED;
848 m_fEnabled = true;
849 m_tsStartMs = RTTimeMilliTS();
850
851 return VINF_SUCCESS;
852 }
853
854 int vrc2 = uninitInternal();
855 AssertRC(vrc2);
856
857 LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
858 return vrc;
859}
860
861/**
862 * Closes a recording stream.
863 * Depending on the stream's recording destination, this function closes all associated handles
864 * and finalizes recording.
865 *
866 * @returns VBox status code.
867 */
868int RecordingStream::close(void)
869{
870 int vrc = VINF_SUCCESS;
871
872 /* ignore rc */ recordingCodecFinalize(&m_CodecVideo);
873
874 switch (m_ScreenSettings.enmDest)
875 {
876 case RecordingDestination_File:
877 {
878 if (this->File.m_pWEBM)
879 vrc = this->File.m_pWEBM->Close();
880 break;
881 }
882
883 default:
884 AssertFailed(); /* Should never happen. */
885 break;
886 }
887
888 m_Blocks.Clear();
889
890 LogRel(("Recording: Recording screen #%u stopped\n", m_uScreenID));
891
892 if (RT_FAILURE(vrc))
893 {
894 LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", m_uScreenID, vrc));
895 return vrc;
896 }
897
898 switch (m_ScreenSettings.enmDest)
899 {
900 case RecordingDestination_File:
901 {
902 if (RTFileIsValid(this->File.m_hFile))
903 {
904 vrc = RTFileClose(this->File.m_hFile);
905 if (RT_SUCCESS(vrc))
906 {
907 LogRel(("Recording: Closed file '%s'\n", m_ScreenSettings.File.strName.c_str()));
908 }
909 else
910 {
911 LogRel(("Recording: Error closing file '%s', vrc=%Rrc\n", m_ScreenSettings.File.strName.c_str(), vrc));
912 break;
913 }
914 }
915
916 WebMWriter *pWebMWriter = this->File.m_pWEBM;
917 AssertPtr(pWebMWriter);
918
919 if (pWebMWriter)
920 {
921 /* If no clusters (= data) was written, delete the file again. */
922 if (pWebMWriter->GetClusters() == 0)
923 {
924 int vrc2 = RTFileDelete(m_ScreenSettings.File.strName.c_str());
925 AssertRC(vrc2); /* Ignore vrc on non-debug builds. */
926 }
927
928 delete pWebMWriter;
929 pWebMWriter = NULL;
930
931 this->File.m_pWEBM = NULL;
932 }
933 break;
934 }
935
936 default:
937 vrc = VERR_NOT_IMPLEMENTED;
938 break;
939 }
940
941 LogFlowFuncLeaveRC(vrc);
942 return vrc;
943}
944
945/**
946 * Uninitializes a recording stream.
947 *
948 * @returns VBox status code.
949 */
950int RecordingStream::Uninit(void)
951{
952 return uninitInternal();
953}
954
955/**
956 * Uninitializes a recording stream, internal version.
957 *
958 * @returns VBox status code.
959 */
960int RecordingStream::uninitInternal(void)
961{
962 if (m_enmState != RECORDINGSTREAMSTATE_INITIALIZED)
963 return VINF_SUCCESS;
964
965 int vrc = close();
966 if (RT_FAILURE(vrc))
967 return vrc;
968
969#ifdef VBOX_WITH_AUDIO_RECORDING
970 m_pCodecAudio = NULL;
971#endif
972
973 if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
974 vrc = recordingCodecDestroy(&m_CodecVideo);
975
976 if (RT_SUCCESS(vrc))
977 {
978 RTCritSectDelete(&m_CritSect);
979
980 m_enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
981 m_fEnabled = false;
982 }
983
984 return vrc;
985}
986
987/**
988 * Writes encoded data to a WebM file instance.
989 *
990 * @returns VBox status code.
991 * @param pCodec Codec which has encoded the data.
992 * @param pvData Encoded data to write.
993 * @param cbData Size (in bytes) of \a pvData.
994 * @param msAbsPTS Absolute PTS (in ms) of written data.
995 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
996 */
997int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
998 uint64_t msAbsPTS, uint32_t uFlags)
999{
1000 AssertPtr(this->File.m_pWEBM);
1001 AssertPtr(pvData);
1002 Assert (cbData);
1003
1004 WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
1005 if (RT_LIKELY(uFlags == RECORDINGCODEC_ENC_F_NONE))
1006 {
1007 /* All set. */
1008 }
1009 else
1010 {
1011 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
1012 blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
1013 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
1014 blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
1015 }
1016
1017 return this->File.m_pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
1018 ? m_uTrackAudio : m_uTrackVideo,
1019 pvData, cbData, msAbsPTS, blockFlags);
1020}
1021
1022/**
1023 * Codec callback for writing encoded data to a recording stream.
1024 *
1025 * @returns VBox status code.
1026 * @param pCodec Codec which has encoded the data.
1027 * @param pvData Encoded data to write.
1028 * @param cbData Size (in bytes) of \a pvData.
1029 * @param msAbsPTS Absolute PTS (in ms) of written data.
1030 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
1031 * @param pvUser User-supplied pointer.
1032 */
1033/* static */
1034DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
1035 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
1036{
1037 RecordingStream *pThis = (RecordingStream *)pvUser;
1038 AssertPtr(pThis);
1039
1040 /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
1041 return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
1042}
1043
1044/**
1045 * Initializes the video recording for a recording stream.
1046 *
1047 * @returns VBox status code.
1048 * @param screenSettings Screen settings to use.
1049 */
1050int RecordingStream::initVideo(const settings::RecordingScreen &screenSettings)
1051{
1052 /* Sanity. */
1053 AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
1054 AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
1055 AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
1056 AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
1057
1058 PRECORDINGCODEC pCodec = &m_CodecVideo;
1059
1060 RECORDINGCODECCALLBACKS Callbacks;
1061 Callbacks.pvUser = this;
1062 Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
1063
1064 RECORDINGSURFACEINFO ScreenInfo;
1065 ScreenInfo.uWidth = screenSettings.Video.ulWidth;
1066 ScreenInfo.uHeight = screenSettings.Video.ulHeight;
1067 ScreenInfo.uBPP = 32; /* We always start with 32 bit. */
1068
1069 int vrc = SendScreenChange(&ScreenInfo, true /* fForce */);
1070 if (RT_SUCCESS(vrc))
1071 {
1072 vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
1073 if (RT_SUCCESS(vrc))
1074 vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
1075 }
1076
1077 if (RT_FAILURE(vrc))
1078 LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
1079
1080 return vrc;
1081}
1082
1083/**
1084 * Locks a recording stream.
1085 */
1086void RecordingStream::lock(void)
1087{
1088 int vrc = RTCritSectEnter(&m_CritSect);
1089 AssertRC(vrc);
1090}
1091
1092/**
1093 * Unlocks a locked recording stream.
1094 */
1095void RecordingStream::unlock(void)
1096{
1097 int vrc = RTCritSectLeave(&m_CritSect);
1098 AssertRC(vrc);
1099}
1100
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