VirtualBox

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

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

Video Recording: Big revamp to improve overall performance. We now don't rely on the periodic display refresh callback anymore to render the entire framebuffer but now rely on delta updates ("dirty rectangles"). Also, we now only encode new frames when an area has changed. This also needed cursor position + change change notifications, as we render the cursor on the host side if mouse integration is enabled (requires 7.1 Guest Additions as of now). Optimized the BGRA32->YUV IV420 color space conversion as well as the overall amount of pixel data shuffled forth and back. Added a new testcase for the cropping/centering code. bugref:10650

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