VirtualBox

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

最後變更 在這個檔案從105095是 105095,由 vboxsync 提交於 9 月 前

Video Recording/Main: More optimizations for recording older guests which have a slightly different screen update logic. bugref:10650

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 33.7 KB
 
1/* $Id: RecordingStream.cpp 105095 2024-07-02 10:06:48Z vboxsync $ */
2/** @file
3 * Recording stream code.
4 */
5
6/*
7 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.alldomusa.eu.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::RecordingScreenSettings &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::RecordingScreenSettings &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::RecordingScreenSettings &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 commonBlocks Common blocks multiplexed to all recording streams.
405 *
406 * @note Runs in encoding thread.
407 */
408int RecordingStream::ThreadMain(int rcWait, RecordingBlockMap &commonBlocks)
409{
410 Log3Func(("uScreenID=%RU16, rcWait=%Rrc\n", m_uScreenID, rcWait));
411
412 uint64_t const msTimestamp = m_pCtx->GetCurrentPTS();
413
414 /* No new data arrived within time? Feed the encoder with the last frame we built.
415 *
416 * This is necessary in order to render a video which has a consistent time line,
417 * as we only encode data when something has changed ("dirty areas"). */
418 if ( rcWait == VERR_TIMEOUT
419 && m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
420 {
421 return recordingCodecEncodeCurrent(&m_CodecVideo, msTimestamp);
422 }
423
424 int vrc = process(m_Blocks, commonBlocks);
425
426 /*
427 * Housekeeping.
428 *
429 * Here we delete all processed stream blocks of this stream.
430 * The common blocks will be deleted by the recording context (which owns those).
431 */
432 lock();
433
434 RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
435 while (itStreamBlocks != m_Blocks.Map.end())
436 {
437 RecordingBlocks *pBlocks = itStreamBlocks->second;
438 AssertPtr(pBlocks);
439 pBlocks->Clear();
440 Assert(pBlocks->List.empty());
441 delete pBlocks;
442
443 m_Blocks.Map.erase(itStreamBlocks);
444 itStreamBlocks = m_Blocks.Map.begin();
445 }
446 Assert(m_Blocks.Map.empty());
447
448 unlock();
449
450 return vrc;
451}
452
453/**
454 * Adds a recording frame to be fed to the encoder.
455 *
456 * @returns VBox status code.
457 * @param pFrame Recording frame to add.
458 * Ownership of the frame will be transferred to the encoder on success then.
459 * Must be free'd by the caller on failure.
460 * @param msTimestamp Timestamp (PTS, in ms).
461 *
462 * @note Caller needs to take the stream's lock.
463 */
464int RecordingStream::addFrame(PRECORDINGFRAME pFrame, uint64_t msTimestamp)
465{
466 int vrc;
467
468 Assert(pFrame->msTimestamp == msTimestamp); /* Sanity. */
469
470 try
471 {
472 RecordingBlock *pBlock = new RecordingBlock();
473
474 pBlock->pvData = pFrame;
475 pBlock->cbData = sizeof(RECORDINGFRAME);
476
477 try
478 {
479 RecordingBlocks *pRecordingBlocks;
480 RecordingBlockMap::const_iterator it = m_Blocks.Map.find(msTimestamp);
481 if (it == m_Blocks.Map.end())
482 {
483 pRecordingBlocks = new RecordingBlocks();
484 pRecordingBlocks->List.push_back(pBlock);
485 m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
486 }
487 else
488 {
489 pRecordingBlocks = it->second;
490 pRecordingBlocks->List.push_back(pBlock);
491 }
492
493 vrc = VINF_SUCCESS;
494 }
495 catch (const std::exception &)
496 {
497 delete pBlock;
498 vrc = VERR_NO_MEMORY;
499 }
500 }
501 catch (const std::exception &)
502 {
503 vrc = VERR_NO_MEMORY;
504 }
505
506 return vrc;
507}
508
509/**
510 * Sends a raw (e.g. not yet encoded) audio frame to the recording stream.
511 *
512 * @returns VBox status code.
513 * @param pvData Pointer to audio data.
514 * @param cbData Size (in bytes) of \a pvData.
515 * @param msTimestamp Timestamp (PTS, in ms).
516 */
517int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
518{
519 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
520
521 /* As audio data is common across all streams, re-route this to the recording context, where
522 * the data is being encoded and stored in the common blocks queue. */
523 return m_pCtx->SendAudioFrame(pvData, cbData, msTimestamp);
524}
525
526/**
527 * Sends a cursor position change to the recording stream.
528 *
529 * @returns VBox status code.
530 * @param idCursor Cursor ID. Currently unused and always set to 0.
531 * @param pPos Cursor information to send.
532 * @param msTimestamp Timestamp (PTS, in ms).
533 */
534int RecordingStream::SendCursorPos(uint8_t idCursor, PRECORDINGPOS pPos, uint64_t msTimestamp)
535{
536 RT_NOREF(idCursor);
537 AssertPtrReturn(pPos, VERR_INVALID_POINTER);
538
539 int vrc = iterateInternal(msTimestamp);
540 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
541 return vrc;
542
543 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
544 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
545 pFrame->enmType = RECORDINGFRAME_TYPE_CURSOR_POS;
546 pFrame->msTimestamp = msTimestamp;
547
548 pFrame->u.Cursor.Pos = *pPos;
549
550 lock();
551
552 vrc = addFrame(pFrame, msTimestamp);
553
554 unlock();
555
556 return vrc;
557}
558
559/**
560 * Sends a cursor shape change to the recording stream.
561 *
562 * @returns VBox status code.
563 * @param idCursor Cursor ID. Currently unused and always set to 0.
564 * @param pShape Cursor shape to send.
565 * @param msTimestamp Timestamp (PTS, in ms).
566 *
567 * @note Keep it as simple as possible, as this function might run on EMT.
568 * @thread EMT
569 */
570int RecordingStream::SendCursorShape(uint8_t idCursor, PRECORDINGVIDEOFRAME pShape, uint64_t msTimestamp)
571{
572 RT_NOREF(idCursor);
573 AssertPtrReturn(pShape, VERR_INVALID_POINTER);
574 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
575
576 int vrc = iterateInternal(msTimestamp);
577 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
578 return vrc;
579
580 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
581 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
582
583 pFrame->u.Video = *pShape;
584 /* Make a deep copy of the pixel data. */
585 pFrame->u.Video.pau8Buf = (uint8_t *)RTMemDup(pShape->pau8Buf, pShape->cbBuf);
586 AssertPtrReturnStmt(pFrame->u.Video.pau8Buf, RTMemFree(pFrame), VERR_NO_MEMORY);
587 pFrame->u.Video.cbBuf = pShape->cbBuf;
588
589 pFrame->enmType = RECORDINGFRAME_TYPE_CURSOR_SHAPE;
590 pFrame->msTimestamp = msTimestamp;
591
592 lock();
593
594 vrc = addFrame(pFrame, msTimestamp);
595
596 unlock();
597
598 if (RT_FAILURE(vrc))
599 {
600 RecordingVideoFrameDestroy(&pFrame->u.Video);
601 RecordingFrameFree(pFrame);
602 }
603
604 LogFlowFuncLeaveRC(vrc);
605 return vrc;
606}
607
608/**
609 * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
610 *
611 * @returns VBox status code.
612 * @retval VINF_RECORDING_LIMIT_REACHED if the stream's recording limit has been reached.
613 * @retval VINF_RECORDING_THROTTLED if the frame is too early for the current FPS setting.
614 * @param pVideoFrame Video frame to send.
615 * @param msTimestamp Timestamp (PTS, in ms).
616 *
617 * @note Keep it as simple as possible, as this function might run on EMT.
618 * @thread EMT
619 */
620int RecordingStream::SendVideoFrame(PRECORDINGVIDEOFRAME pVideoFrame, uint64_t msTimestamp)
621{
622 AssertPtrReturn(pVideoFrame, VERR_INVALID_POINTER);
623 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
624
625 int vrc = iterateInternal(msTimestamp);
626 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
627 return vrc;
628
629 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
630 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
631
632 pFrame->u.Video = *pVideoFrame;
633
634 /* Make a deep copy of the pixel data. */
635 pFrame->u.Video.pau8Buf = (uint8_t *)RTMemAlloc(pVideoFrame->cbBuf);
636 AssertPtrReturnStmt(pFrame->u.Video.pau8Buf, RTMemFree(pFrame), VERR_NO_MEMORY);
637 size_t offDst = 0;
638 size_t offSrc = 0;
639 size_t const cbDstBytesPerLine = pVideoFrame->Info.uWidth * (pVideoFrame->Info.uBPP / 8);
640 for (uint32_t h = 0; h < pFrame->u.Video.Info.uHeight; h++)
641 {
642 memcpy(pFrame->u.Video.pau8Buf + offDst, pVideoFrame->pau8Buf + offSrc, cbDstBytesPerLine);
643 offDst += cbDstBytesPerLine;
644 offSrc += pVideoFrame->Info.uBytesPerLine;
645 }
646 pFrame->u.Video.Info.uBytesPerLine = cbDstBytesPerLine;
647
648 pFrame->enmType = RECORDINGFRAME_TYPE_VIDEO;
649 pFrame->msTimestamp = msTimestamp;
650
651 lock();
652
653 vrc = addFrame(pFrame, msTimestamp);
654
655 unlock();
656
657 if (RT_FAILURE(vrc))
658 {
659 RecordingVideoFrameDestroy(&pFrame->u.Video);
660 RecordingFrameFree(pFrame);
661 }
662
663 LogFlowFuncLeaveRC(vrc);
664 return vrc;
665}
666
667/**
668 * Sends a screen size change to a recording stream.
669 *
670 * @returns VBox status code.
671 * @param pInfo Recording screen info to use.
672 * @param msTimestamp Timestamp (PTS, in ms).
673 * @param fForce Set to \c true to force a change, otherwise to \c false.
674 */
675int RecordingStream::SendScreenChange(PRECORDINGSURFACEINFO pInfo, uint64_t msTimestamp, bool fForce /* = false */)
676{
677 AssertPtrReturn(pInfo, VERR_INVALID_POINTER);
678
679 if ( !pInfo->uWidth
680 || !pInfo->uHeight)
681 return VINF_SUCCESS;
682
683 RT_NOREF(fForce);
684
685 LogRel(("Recording: Size of screen #%RU32 changed to %RU32x%RU32 (%RU8 BPP)\n",
686 m_uScreenID, pInfo->uWidth, pInfo->uHeight, pInfo->uBPP));
687
688 lock();
689
690 PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
691 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
692 pFrame->enmType = RECORDINGFRAME_TYPE_SCREEN_CHANGE;
693 pFrame->msTimestamp = msTimestamp;
694
695 pFrame->u.ScreenInfo = *pInfo;
696
697 int vrc = addFrame(pFrame, msTimestamp);
698
699 unlock();
700
701 LogFlowFuncLeaveRC(vrc);
702 return vrc;
703}
704
705/**
706 * Initializes a recording stream.
707 *
708 * @returns VBox status code.
709 * @param pCtx Pointer to recording context.
710 * @param uScreen Screen number to use for this recording stream.
711 * @param Settings Recording screen configuration to use for initialization.
712 */
713int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
714{
715 return initInternal(pCtx, uScreen, Settings);
716}
717
718/**
719 * Initializes a recording stream, internal version.
720 *
721 * @returns VBox status code.
722 * @param pCtx Pointer to recording context.
723 * @param uScreen Screen number to use for this recording stream.
724 * @param screenSettings Recording screen configuration to use for initialization.
725 */
726int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
727 const settings::RecordingScreenSettings &screenSettings)
728{
729 AssertReturn(m_enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
730
731 m_pCtx = pCtx;
732 m_uTrackAudio = UINT8_MAX;
733 m_uTrackVideo = UINT8_MAX;
734 m_tsStartMs = 0;
735 m_uScreenID = uScreen;
736#ifdef VBOX_WITH_AUDIO_RECORDING
737 /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
738 m_pCodecAudio = m_pCtx->GetCodecAudio();
739#endif
740 m_ScreenSettings = screenSettings;
741
742 settings::RecordingScreenSettings *pSettings = &m_ScreenSettings;
743
744 int vrc = RTCritSectInit(&m_CritSect);
745 if (RT_FAILURE(vrc))
746 return vrc;
747
748 this->File.m_pWEBM = NULL;
749 this->File.m_hFile = NIL_RTFILE;
750
751 vrc = open(*pSettings);
752 if (RT_FAILURE(vrc))
753 return vrc;
754
755 const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
756 const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
757
758 if (fVideoEnabled)
759 {
760 vrc = initVideo(*pSettings);
761 if (RT_FAILURE(vrc))
762 return vrc;
763 }
764
765 switch (pSettings->enmDest)
766 {
767 case RecordingDestination_File:
768 {
769 Assert(pSettings->File.strName.isNotEmpty());
770 const char *pszFile = pSettings->File.strName.c_str();
771
772 AssertPtr(File.m_pWEBM);
773 vrc = File.m_pWEBM->OpenEx(pszFile, &this->File.m_hFile,
774 fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
775 fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
776 if (RT_FAILURE(vrc))
777 {
778 LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
779 break;
780 }
781
782 if (fVideoEnabled)
783 {
784 vrc = this->File.m_pWEBM->AddVideoTrack(&m_CodecVideo,
785 pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
786 &m_uTrackVideo);
787 if (RT_FAILURE(vrc))
788 {
789 LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
790 break;
791 }
792
793 LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
794 m_uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
795 pSettings->Video.ulRate, pSettings->Video.ulFPS, m_uTrackVideo));
796 }
797
798#ifdef VBOX_WITH_AUDIO_RECORDING
799 if (fAudioEnabled)
800 {
801 AssertPtr(m_pCodecAudio);
802 vrc = this->File.m_pWEBM->AddAudioTrack(m_pCodecAudio,
803 pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
804 &m_uTrackAudio);
805 if (RT_FAILURE(vrc))
806 {
807 LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
808 break;
809 }
810
811 LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
812 m_uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
813 pSettings->Audio.cChannels ? "channels" : "channel", m_uTrackAudio));
814 }
815#endif
816
817 if ( fVideoEnabled
818#ifdef VBOX_WITH_AUDIO_RECORDING
819 || fAudioEnabled
820#endif
821 )
822 {
823 char szWhat[32] = { 0 };
824 if (fVideoEnabled)
825 RTStrCat(szWhat, sizeof(szWhat), "video");
826#ifdef VBOX_WITH_AUDIO_RECORDING
827 if (fAudioEnabled)
828 {
829 if (fVideoEnabled)
830 RTStrCat(szWhat, sizeof(szWhat), " + ");
831 RTStrCat(szWhat, sizeof(szWhat), "audio");
832 }
833#endif
834 LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, m_uScreenID, pszFile));
835 }
836
837 break;
838 }
839
840 default:
841 AssertFailed(); /* Should never happen. */
842 vrc = VERR_NOT_IMPLEMENTED;
843 break;
844 }
845
846 if (RT_SUCCESS(vrc))
847 {
848 m_enmState = RECORDINGSTREAMSTATE_INITIALIZED;
849 m_fEnabled = true;
850 m_tsStartMs = RTTimeMilliTS();
851
852 return VINF_SUCCESS;
853 }
854
855 int vrc2 = uninitInternal();
856 AssertRC(vrc2);
857
858 LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
859 return vrc;
860}
861
862/**
863 * Closes a recording stream.
864 * Depending on the stream's recording destination, this function closes all associated handles
865 * and finalizes recording.
866 *
867 * @returns VBox status code.
868 */
869int RecordingStream::close(void)
870{
871 int vrc = VINF_SUCCESS;
872
873 /* ignore rc */ recordingCodecFinalize(&m_CodecVideo);
874
875 switch (m_ScreenSettings.enmDest)
876 {
877 case RecordingDestination_File:
878 {
879 if (this->File.m_pWEBM)
880 vrc = this->File.m_pWEBM->Close();
881 break;
882 }
883
884 default:
885 AssertFailed(); /* Should never happen. */
886 break;
887 }
888
889 m_Blocks.Clear();
890
891 LogRel(("Recording: Recording screen #%u stopped\n", m_uScreenID));
892
893 if (RT_FAILURE(vrc))
894 {
895 LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", m_uScreenID, vrc));
896 return vrc;
897 }
898
899 switch (m_ScreenSettings.enmDest)
900 {
901 case RecordingDestination_File:
902 {
903 if (RTFileIsValid(this->File.m_hFile))
904 {
905 vrc = RTFileClose(this->File.m_hFile);
906 if (RT_SUCCESS(vrc))
907 {
908 LogRel(("Recording: Closed file '%s'\n", m_ScreenSettings.File.strName.c_str()));
909 }
910 else
911 {
912 LogRel(("Recording: Error closing file '%s', vrc=%Rrc\n", m_ScreenSettings.File.strName.c_str(), vrc));
913 break;
914 }
915 }
916
917 WebMWriter *pWebMWriter = this->File.m_pWEBM;
918 AssertPtr(pWebMWriter);
919
920 if (pWebMWriter)
921 {
922 /* If no clusters (= data) was written, delete the file again. */
923 if (pWebMWriter->GetClusters() == 0)
924 {
925 int vrc2 = RTFileDelete(m_ScreenSettings.File.strName.c_str());
926 AssertRC(vrc2); /* Ignore vrc on non-debug builds. */
927 }
928
929 delete pWebMWriter;
930 pWebMWriter = NULL;
931
932 this->File.m_pWEBM = NULL;
933 }
934 break;
935 }
936
937 default:
938 vrc = VERR_NOT_IMPLEMENTED;
939 break;
940 }
941
942 LogFlowFuncLeaveRC(vrc);
943 return vrc;
944}
945
946/**
947 * Uninitializes a recording stream.
948 *
949 * @returns VBox status code.
950 */
951int RecordingStream::Uninit(void)
952{
953 return uninitInternal();
954}
955
956/**
957 * Uninitializes a recording stream, internal version.
958 *
959 * @returns VBox status code.
960 */
961int RecordingStream::uninitInternal(void)
962{
963 if (m_enmState != RECORDINGSTREAMSTATE_INITIALIZED)
964 return VINF_SUCCESS;
965
966 int vrc = close();
967 if (RT_FAILURE(vrc))
968 return vrc;
969
970#ifdef VBOX_WITH_AUDIO_RECORDING
971 m_pCodecAudio = NULL;
972#endif
973
974 if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
975 vrc = recordingCodecDestroy(&m_CodecVideo);
976
977 if (RT_SUCCESS(vrc))
978 {
979 RTCritSectDelete(&m_CritSect);
980
981 m_enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
982 m_fEnabled = false;
983 }
984
985 return vrc;
986}
987
988/**
989 * Writes encoded data to a WebM file instance.
990 *
991 * @returns VBox status code.
992 * @param pCodec Codec which has encoded the data.
993 * @param pvData Encoded data to write.
994 * @param cbData Size (in bytes) of \a pvData.
995 * @param msAbsPTS Absolute PTS (in ms) of written data.
996 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
997 */
998int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
999 uint64_t msAbsPTS, uint32_t uFlags)
1000{
1001 AssertPtr(this->File.m_pWEBM);
1002 AssertPtr(pvData);
1003 Assert (cbData);
1004
1005 WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
1006 if (RT_LIKELY(uFlags == RECORDINGCODEC_ENC_F_NONE))
1007 {
1008 /* All set. */
1009 }
1010 else
1011 {
1012 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
1013 blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
1014 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
1015 blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
1016 }
1017
1018 return this->File.m_pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
1019 ? m_uTrackAudio : m_uTrackVideo,
1020 pvData, cbData, msAbsPTS, blockFlags);
1021}
1022
1023/**
1024 * Codec callback for writing encoded data to a recording stream.
1025 *
1026 * @returns VBox status code.
1027 * @param pCodec Codec which has encoded the data.
1028 * @param pvData Encoded data to write.
1029 * @param cbData Size (in bytes) of \a pvData.
1030 * @param msAbsPTS Absolute PTS (in ms) of written data.
1031 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
1032 * @param pvUser User-supplied pointer.
1033 */
1034/* static */
1035DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
1036 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
1037{
1038 RecordingStream *pThis = (RecordingStream *)pvUser;
1039 AssertPtr(pThis);
1040
1041 /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
1042 return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
1043}
1044
1045/**
1046 * Initializes the video recording for a recording stream.
1047 *
1048 * @returns VBox status code.
1049 * @param screenSettings Screen settings to use.
1050 */
1051int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings)
1052{
1053 /* Sanity. */
1054 AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
1055 AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
1056 AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
1057 AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
1058
1059 PRECORDINGCODEC pCodec = &m_CodecVideo;
1060
1061 RECORDINGCODECCALLBACKS Callbacks;
1062 Callbacks.pvUser = this;
1063 Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
1064
1065 RECORDINGSURFACEINFO ScreenInfo;
1066 ScreenInfo.uWidth = screenSettings.Video.ulWidth;
1067 ScreenInfo.uHeight = screenSettings.Video.ulHeight;
1068 ScreenInfo.uBPP = 32; /* We always start with 32 bit. */
1069
1070 int vrc = SendScreenChange(&ScreenInfo, true /* fForce */);
1071 if (RT_SUCCESS(vrc))
1072 {
1073 vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
1074 if (RT_SUCCESS(vrc))
1075 vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
1076 }
1077
1078 if (RT_FAILURE(vrc))
1079 LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
1080
1081 return vrc;
1082}
1083
1084/**
1085 * Locks a recording stream.
1086 */
1087void RecordingStream::lock(void)
1088{
1089 int vrc = RTCritSectEnter(&m_CritSect);
1090 AssertRC(vrc);
1091}
1092
1093/**
1094 * Unlocks a locked recording stream.
1095 */
1096void RecordingStream::unlock(void)
1097{
1098 int vrc = RTCritSectLeave(&m_CritSect);
1099 AssertRC(vrc);
1100}
1101
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette