VirtualBox

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

Last change on this file since 104751 was 104751, checked in by vboxsync, 6 months ago

Main/Recording: Don't (debug) assert in RecordingStream::SendVideoFrame() when sending a video frame too early or redundantly; just return VINF_RECORDING_THROTTLED. Can happen when debugging VBoxSVC or when invalid settings are being used. bugref:10603

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.5 KB
Line 
1/* $Id: RecordingStream.cpp 104751 2024-05-22 09:46:55Z 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 * This function takes care of the actual encoding and writing of a certain stream.
262 * As this can be very CPU intensive, this function usually is called from a separate thread.
263 *
264 * @returns VBox status code.
265 * @param mapBlocksCommon Map of common block to process for this stream.
266 *
267 * @note Runs in recording thread.
268 */
269int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
270{
271 LogFlowFuncEnter();
272
273 lock();
274
275 if (!m_ScreenSettings.fEnabled)
276 {
277 unlock();
278 return VINF_SUCCESS;
279 }
280
281 int vrc = VINF_SUCCESS;
282
283 RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
284 while (itStreamBlocks != m_Blocks.Map.end())
285 {
286 uint64_t const msTimestamp = itStreamBlocks->first;
287 RecordingBlocks *pBlocks = itStreamBlocks->second;
288
289 AssertPtr(pBlocks);
290
291 while (!pBlocks->List.empty())
292 {
293 RecordingBlock *pBlock = pBlocks->List.front();
294 AssertPtr(pBlock);
295
296 switch (pBlock->enmType)
297 {
298 case RECORDINGBLOCKTYPE_VIDEO:
299 {
300 RECORDINGFRAME Frame;
301 Frame.VideoPtr = (PRECORDINGVIDEOFRAME)pBlock->pvData;
302 Frame.msTimestamp = msTimestamp;
303
304 int vrc2 = recordingCodecEncode(&m_CodecVideo, &Frame, NULL, NULL);
305 AssertRC(vrc2);
306 if (RT_SUCCESS(vrc))
307 vrc = vrc2;
308
309 break;
310 }
311
312 default:
313 /* Note: Audio data already is encoded. */
314 break;
315 }
316
317 pBlocks->List.pop_front();
318 delete pBlock;
319 }
320
321 Assert(pBlocks->List.empty());
322 delete pBlocks;
323
324 m_Blocks.Map.erase(itStreamBlocks);
325 itStreamBlocks = m_Blocks.Map.begin();
326 }
327
328#ifdef VBOX_WITH_AUDIO_RECORDING
329 /* Do we need to multiplex the common audio data to this stream? */
330 if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
331 {
332 /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
333 * written to the screen's assigned recording stream. */
334 RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin();
335 while (itCommonBlocks != mapBlocksCommon.end())
336 {
337 RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
338 while (itBlock != itCommonBlocks->second->List.end())
339 {
340 RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
341 switch (pBlockCommon->enmType)
342 {
343 case RECORDINGBLOCKTYPE_AUDIO:
344 {
345 PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
346 AssertPtr(pAudioFrame);
347 AssertPtr(pAudioFrame->pvBuf);
348 Assert(pAudioFrame->cbBuf);
349
350 AssertPtr(this->File.m_pWEBM);
351 int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags);
352 AssertRC(vrc2);
353 if (RT_SUCCESS(vrc))
354 vrc = vrc2;
355 break;
356 }
357
358 default:
359 AssertFailed();
360 break;
361 }
362
363 Assert(pBlockCommon->cRefs);
364 pBlockCommon->cRefs--;
365 if (pBlockCommon->cRefs == 0)
366 {
367 itCommonBlocks->second->List.erase(itBlock);
368 delete pBlockCommon;
369 itBlock = itCommonBlocks->second->List.begin();
370 }
371 else
372 ++itBlock;
373 }
374
375 /* If no entries are left over in the block map, remove it altogether. */
376 if (itCommonBlocks->second->List.empty())
377 {
378 delete itCommonBlocks->second;
379 mapBlocksCommon.erase(itCommonBlocks);
380 itCommonBlocks = mapBlocksCommon.begin();
381 }
382 else
383 ++itCommonBlocks;
384
385 LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
386 }
387 }
388#else
389 RT_NOREF(mapBlocksCommon);
390#endif /* VBOX_WITH_AUDIO_RECORDING */
391
392 unlock();
393
394 LogFlowFuncLeaveRC(vrc);
395 return vrc;
396}
397
398/**
399 * Sends a raw (e.g. not yet encoded) audio frame to the recording stream.
400 *
401 * @returns VBox status code.
402 * @param pvData Pointer to audio data.
403 * @param cbData Size (in bytes) of \a pvData.
404 * @param msTimestamp Timestamp (PTS, in ms).
405 */
406int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
407{
408 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
409 AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
410
411 Log3Func(("cbData=%zu, msTimestamp=%RU64\n", cbData, msTimestamp));
412
413 /* As audio data is common across all streams, re-route this to the recording context, where
414 * the data is being encoded and stored in the common blocks queue. */
415 return m_pCtx->SendAudioFrame(pvData, cbData, msTimestamp);
416}
417
418/**
419 * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
420 *
421 * @returns VBox status code.
422 * @retval VINF_RECORDING_LIMIT_REACHED if the stream's recording limit has been reached.
423 * @retval VINF_RECORDING_THROTTLED if the frame is too early for the current FPS setting.
424 * @param x Upper left (X) coordinate where the video frame starts.
425 * @param y Upper left (Y) coordinate where the video frame starts.
426 * @param uPixelFormat Pixel format of the video frame.
427 * @param uBPP Bits per pixel (BPP) of the video frame.
428 * @param uBytesPerLine Bytes per line of the video frame.
429 * @param uSrcWidth Width (in pixels) of the video frame.
430 * @param uSrcHeight Height (in pixels) of the video frame.
431 * @param puSrcData Actual pixel data of the video frame.
432 * @param msTimestamp Timestamp (PTS, in ms).
433 */
434int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
435 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
436{
437 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
438
439 if RT_UNLIKELY(!NeedsUpdate(msTimestamp))
440 return VINF_RECORDING_THROTTLED;
441
442 lock();
443
444 Log3Func(("[%RU32 %RU32 %RU32 %RU32] msTimestamp=%RU64\n", x , y, uSrcWidth, uSrcHeight, msTimestamp));
445
446 PRECORDINGVIDEOFRAME pFrame = NULL;
447
448 int vrc = iterateInternal(msTimestamp);
449 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
450 {
451 unlock();
452 return vrc;
453 }
454
455 do
456 {
457 int xDiff = ((int)m_ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
458 uint32_t w = uSrcWidth;
459 if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
460 {
461 vrc = VERR_INVALID_PARAMETER;
462 break;
463 }
464
465 uint32_t destX;
466 if ((int)x < -xDiff)
467 {
468 w += xDiff + x;
469 x = -xDiff;
470 destX = 0;
471 }
472 else
473 destX = x + xDiff;
474
475 uint32_t h = uSrcHeight;
476 int yDiff = ((int)m_ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
477 if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
478 {
479 vrc = VERR_INVALID_PARAMETER;
480 break;
481 }
482
483 uint32_t destY;
484 if ((int)y < -yDiff)
485 {
486 h += yDiff + (int)y;
487 y = -yDiff;
488 destY = 0;
489 }
490 else
491 destY = y + yDiff;
492
493 if ( destX > m_ScreenSettings.Video.ulWidth
494 || destY > m_ScreenSettings.Video.ulHeight)
495 {
496 vrc = VERR_INVALID_PARAMETER; /* Nothing visible. */
497 break;
498 }
499
500 if (destX + w > m_ScreenSettings.Video.ulWidth)
501 w = m_ScreenSettings.Video.ulWidth - destX;
502
503 if (destY + h > m_ScreenSettings.Video.ulHeight)
504 h = m_ScreenSettings.Video.ulHeight - destY;
505
506 pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
507 AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY);
508
509 /* Calculate bytes per pixel and set pixel format. */
510 const unsigned uBytesPerPixel = uBPP / 8;
511 if (uPixelFormat == BitmapFormat_BGR)
512 {
513 switch (uBPP)
514 {
515 case 32:
516 pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB32;
517 break;
518 case 24:
519 pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB24;
520 break;
521 case 16:
522 pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB565;
523 break;
524 default:
525 AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED);
526 break;
527 }
528 }
529 else
530 AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED);
531
532 const size_t cbRGBBuf = m_ScreenSettings.Video.ulWidth
533 * m_ScreenSettings.Video.ulHeight
534 * uBytesPerPixel;
535 AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER);
536
537 pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
538 AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY);
539 pFrame->cbRGBBuf = cbRGBBuf;
540 pFrame->uWidth = uSrcWidth;
541 pFrame->uHeight = uSrcHeight;
542
543 /* If the current video frame is smaller than video resolution we're going to encode,
544 * clear the frame beforehand to prevent artifacts. */
545 if ( uSrcWidth < m_ScreenSettings.Video.ulWidth
546 || uSrcHeight < m_ScreenSettings.Video.ulHeight)
547 {
548 RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
549 }
550
551 /* Calculate start offset in source and destination buffers. */
552 uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
553 uint32_t offDst = (destY * m_ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
554
555#ifdef VBOX_RECORDING_DUMP
556 BMPFILEHDR fileHdr;
557 RT_ZERO(fileHdr);
558
559 BMPWIN3XINFOHDR coreHdr;
560 RT_ZERO(coreHdr);
561
562 fileHdr.uType = BMP_HDR_MAGIC;
563 fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
564 fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
565
566 coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
567 coreHdr.uWidth = w;
568 coreHdr.uHeight = h;
569 coreHdr.cPlanes = 1;
570 coreHdr.cBits = uBPP;
571 coreHdr.uXPelsPerMeter = 5000;
572 coreHdr.uYPelsPerMeter = 5000;
573
574 char szFileName[RTPATH_MAX];
575 RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", m_uScreenID);
576
577 RTFILE fh;
578 int vrc2 = RTFileOpen(&fh, szFileName,
579 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
580 if (RT_SUCCESS(vrc2))
581 {
582 RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
583 RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
584 }
585#endif
586 Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
587
588 /* Do the copy. */
589 for (unsigned int i = 0; i < h; i++)
590 {
591 /* Overflow check. */
592 Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
593 Assert(offDst + w * uBytesPerPixel <= m_ScreenSettings.Video.ulHeight * m_ScreenSettings.Video.ulWidth * uBytesPerPixel);
594
595 memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
596
597#ifdef VBOX_RECORDING_DUMP
598 if (RT_SUCCESS(rc2))
599 RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
600#endif
601 offSrc += uBytesPerLine;
602 offDst += m_ScreenSettings.Video.ulWidth * uBytesPerPixel;
603 }
604
605#ifdef VBOX_RECORDING_DUMP
606 if (RT_SUCCESS(vrc2))
607 RTFileClose(fh);
608#endif
609
610 } while (0);
611
612 if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
613 {
614 RecordingBlock *pBlock = new RecordingBlock();
615 if (pBlock)
616 {
617 AssertPtr(pFrame);
618
619 pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
620 pBlock->pvData = pFrame;
621 pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
622
623 try
624 {
625 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
626 pRecordingBlocks->List.push_back(pBlock);
627
628 Assert(m_Blocks.Map.find(msTimestamp) == m_Blocks.Map.end());
629 m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
630 }
631 catch (const std::exception &ex)
632 {
633 RT_NOREF(ex);
634
635 delete pBlock;
636 vrc = VERR_NO_MEMORY;
637 }
638 }
639 else
640 vrc = VERR_NO_MEMORY;
641 }
642
643 if (RT_FAILURE(vrc))
644 RecordingVideoFrameFree(pFrame);
645
646 unlock();
647
648 LogFlowFuncLeaveRC(vrc);
649 return vrc;
650}
651
652/**
653 * Initializes a recording stream.
654 *
655 * @returns VBox status code.
656 * @param pCtx Pointer to recording context.
657 * @param uScreen Screen number to use for this recording stream.
658 * @param Settings Recording screen configuration to use for initialization.
659 */
660int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
661{
662 return initInternal(pCtx, uScreen, Settings);
663}
664
665/**
666 * Initializes a recording stream, internal version.
667 *
668 * @returns VBox status code.
669 * @param pCtx Pointer to recording context.
670 * @param uScreen Screen number to use for this recording stream.
671 * @param screenSettings Recording screen configuration to use for initialization.
672 */
673int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
674 const settings::RecordingScreenSettings &screenSettings)
675{
676 AssertReturn(m_enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
677
678 m_pCtx = pCtx;
679 m_uTrackAudio = UINT8_MAX;
680 m_uTrackVideo = UINT8_MAX;
681 m_tsStartMs = 0;
682 m_uScreenID = uScreen;
683#ifdef VBOX_WITH_AUDIO_RECORDING
684 /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
685 m_pCodecAudio = m_pCtx->GetCodecAudio();
686#endif
687 m_ScreenSettings = screenSettings;
688
689 settings::RecordingScreenSettings *pSettings = &m_ScreenSettings;
690
691 int vrc = RTCritSectInit(&m_CritSect);
692 if (RT_FAILURE(vrc))
693 return vrc;
694
695 this->File.m_pWEBM = NULL;
696 this->File.m_hFile = NIL_RTFILE;
697
698 vrc = open(*pSettings);
699 if (RT_FAILURE(vrc))
700 return vrc;
701
702 const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
703 const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
704
705 if (fVideoEnabled)
706 {
707 vrc = initVideo(*pSettings);
708 if (RT_FAILURE(vrc))
709 return vrc;
710 }
711
712 switch (pSettings->enmDest)
713 {
714 case RecordingDestination_File:
715 {
716 Assert(pSettings->File.strName.isNotEmpty());
717 const char *pszFile = pSettings->File.strName.c_str();
718
719 AssertPtr(File.m_pWEBM);
720 vrc = File.m_pWEBM->OpenEx(pszFile, &this->File.m_hFile,
721 fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
722 fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
723 if (RT_FAILURE(vrc))
724 {
725 LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
726 break;
727 }
728
729 if (fVideoEnabled)
730 {
731 vrc = this->File.m_pWEBM->AddVideoTrack(&m_CodecVideo,
732 pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
733 &m_uTrackVideo);
734 if (RT_FAILURE(vrc))
735 {
736 LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
737 break;
738 }
739
740 LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
741 m_uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
742 pSettings->Video.ulRate, pSettings->Video.ulFPS, m_uTrackVideo));
743 }
744
745#ifdef VBOX_WITH_AUDIO_RECORDING
746 if (fAudioEnabled)
747 {
748 AssertPtr(m_pCodecAudio);
749 vrc = this->File.m_pWEBM->AddAudioTrack(m_pCodecAudio,
750 pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
751 &m_uTrackAudio);
752 if (RT_FAILURE(vrc))
753 {
754 LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
755 break;
756 }
757
758 LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
759 m_uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
760 pSettings->Audio.cChannels ? "channels" : "channel", m_uTrackAudio));
761 }
762#endif
763
764 if ( fVideoEnabled
765#ifdef VBOX_WITH_AUDIO_RECORDING
766 || fAudioEnabled
767#endif
768 )
769 {
770 char szWhat[32] = { 0 };
771 if (fVideoEnabled)
772 RTStrCat(szWhat, sizeof(szWhat), "video");
773#ifdef VBOX_WITH_AUDIO_RECORDING
774 if (fAudioEnabled)
775 {
776 if (fVideoEnabled)
777 RTStrCat(szWhat, sizeof(szWhat), " + ");
778 RTStrCat(szWhat, sizeof(szWhat), "audio");
779 }
780#endif
781 LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, m_uScreenID, pszFile));
782 }
783
784 break;
785 }
786
787 default:
788 AssertFailed(); /* Should never happen. */
789 vrc = VERR_NOT_IMPLEMENTED;
790 break;
791 }
792
793 if (RT_SUCCESS(vrc))
794 {
795 m_enmState = RECORDINGSTREAMSTATE_INITIALIZED;
796 m_fEnabled = true;
797 m_tsStartMs = RTTimeProgramMilliTS();
798
799 return VINF_SUCCESS;
800 }
801
802 int vrc2 = uninitInternal();
803 AssertRC(vrc2);
804
805 LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
806 return vrc;
807}
808
809/**
810 * Closes a recording stream.
811 * Depending on the stream's recording destination, this function closes all associated handles
812 * and finalizes recording.
813 *
814 * @returns VBox status code.
815 */
816int RecordingStream::close(void)
817{
818 int vrc = VINF_SUCCESS;
819
820 switch (m_ScreenSettings.enmDest)
821 {
822 case RecordingDestination_File:
823 {
824 if (this->File.m_pWEBM)
825 vrc = this->File.m_pWEBM->Close();
826 break;
827 }
828
829 default:
830 AssertFailed(); /* Should never happen. */
831 break;
832 }
833
834 m_Blocks.Clear();
835
836 LogRel(("Recording: Recording screen #%u stopped\n", m_uScreenID));
837
838 if (RT_FAILURE(vrc))
839 {
840 LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", m_uScreenID, vrc));
841 return vrc;
842 }
843
844 switch (m_ScreenSettings.enmDest)
845 {
846 case RecordingDestination_File:
847 {
848 if (RTFileIsValid(this->File.m_hFile))
849 {
850 vrc = RTFileClose(this->File.m_hFile);
851 if (RT_SUCCESS(vrc))
852 {
853 LogRel(("Recording: Closed file '%s'\n", m_ScreenSettings.File.strName.c_str()));
854 }
855 else
856 {
857 LogRel(("Recording: Error closing file '%s', vrc=%Rrc\n", m_ScreenSettings.File.strName.c_str(), vrc));
858 break;
859 }
860 }
861
862 WebMWriter *pWebMWriter = this->File.m_pWEBM;
863 AssertPtr(pWebMWriter);
864
865 if (pWebMWriter)
866 {
867 /* If no clusters (= data) was written, delete the file again. */
868 if (pWebMWriter->GetClusters() == 0)
869 {
870 int vrc2 = RTFileDelete(m_ScreenSettings.File.strName.c_str());
871 AssertRC(vrc2); /* Ignore vrc on non-debug builds. */
872 }
873
874 delete pWebMWriter;
875 pWebMWriter = NULL;
876
877 this->File.m_pWEBM = NULL;
878 }
879 break;
880 }
881
882 default:
883 vrc = VERR_NOT_IMPLEMENTED;
884 break;
885 }
886
887 LogFlowFuncLeaveRC(vrc);
888 return vrc;
889}
890
891/**
892 * Uninitializes a recording stream.
893 *
894 * @returns VBox status code.
895 */
896int RecordingStream::Uninit(void)
897{
898 return uninitInternal();
899}
900
901/**
902 * Uninitializes a recording stream, internal version.
903 *
904 * @returns VBox status code.
905 */
906int RecordingStream::uninitInternal(void)
907{
908 if (m_enmState != RECORDINGSTREAMSTATE_INITIALIZED)
909 return VINF_SUCCESS;
910
911 int vrc = close();
912 if (RT_FAILURE(vrc))
913 return vrc;
914
915#ifdef VBOX_WITH_AUDIO_RECORDING
916 m_pCodecAudio = NULL;
917#endif
918
919 if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
920 {
921 vrc = recordingCodecFinalize(&m_CodecVideo);
922 if (RT_SUCCESS(vrc))
923 vrc = recordingCodecDestroy(&m_CodecVideo);
924 }
925
926 if (RT_SUCCESS(vrc))
927 {
928 RTCritSectDelete(&m_CritSect);
929
930 m_enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
931 m_fEnabled = false;
932 }
933
934 return vrc;
935}
936
937/**
938 * Writes encoded data to a WebM file instance.
939 *
940 * @returns VBox status code.
941 * @param pCodec Codec which has encoded the data.
942 * @param pvData Encoded data to write.
943 * @param cbData Size (in bytes) of \a pvData.
944 * @param msAbsPTS Absolute PTS (in ms) of written data.
945 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
946 */
947int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
948 uint64_t msAbsPTS, uint32_t uFlags)
949{
950 AssertPtr(this->File.m_pWEBM);
951 AssertPtr(pvData);
952 Assert (cbData);
953
954 WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
955 if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE))
956 {
957 /* All set. */
958 }
959 else
960 {
961 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
962 blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
963 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
964 blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
965 }
966
967 return this->File.m_pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
968 ? m_uTrackAudio : m_uTrackVideo,
969 pvData, cbData, msAbsPTS, blockFlags);
970}
971
972/**
973 * Codec callback for writing encoded data to a recording stream.
974 *
975 * @returns VBox status code.
976 * @param pCodec Codec which has encoded the data.
977 * @param pvData Encoded data to write.
978 * @param cbData Size (in bytes) of \a pvData.
979 * @param msAbsPTS Absolute PTS (in ms) of written data.
980 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
981 * @param pvUser User-supplied pointer.
982 */
983/* static */
984DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
985 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
986{
987 RecordingStream *pThis = (RecordingStream *)pvUser;
988 AssertPtr(pThis);
989
990 /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
991 return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
992}
993
994/**
995 * Initializes the video recording for a recording stream.
996 *
997 * @returns VBox status code.
998 * @param screenSettings Screen settings to use.
999 */
1000int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings)
1001{
1002 /* Sanity. */
1003 AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
1004 AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
1005 AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
1006 AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
1007
1008 PRECORDINGCODEC pCodec = &m_CodecVideo;
1009
1010 RECORDINGCODECCALLBACKS Callbacks;
1011 Callbacks.pvUser = this;
1012 Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
1013
1014 int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
1015 if (RT_SUCCESS(vrc))
1016 vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
1017
1018 if (RT_FAILURE(vrc))
1019 LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
1020
1021 return vrc;
1022}
1023
1024/**
1025 * Locks a recording stream.
1026 */
1027void RecordingStream::lock(void)
1028{
1029 int vrc = RTCritSectEnter(&m_CritSect);
1030 AssertRC(vrc);
1031}
1032
1033/**
1034 * Unlocks a locked recording stream.
1035 */
1036void RecordingStream::unlock(void)
1037{
1038 int vrc = RTCritSectLeave(&m_CritSect);
1039 AssertRC(vrc);
1040}
1041
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