VirtualBox

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

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

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

  • 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 105266 2024-07-11 07:49:37Z 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_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::RecordingScreenSettings &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::RecordingScreenSettings &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::RecordingScreenSettings *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::RecordingScreenSettings &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