VirtualBox

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

Last change on this file was 98278, checked in by vboxsync, 16 months ago

Main/src-client: Some more rc -> hrc/vrc stuff found by grep. A build fix. bugref:10223

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.6 KB
RevLine 
[74992]1/* $Id: RecordingStream.cpp 98278 2023-01-24 11:55:00Z vboxsync $ */
2/** @file
[75344]3 * Recording stream code.
[74992]4 */
5
6/*
[98103]7 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
[74992]8 *
[96407]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
[74992]26 */
27
28#ifdef LOG_GROUP
29# undef LOG_GROUP
30#endif
[96140]31#define LOG_GROUP LOG_GROUP_RECORDING
[74992]32#include "LoggingNew.h"
33
34#include <iprt/path.h>
35
[85858]36#ifdef VBOX_RECORDING_DUMP
[85856]37# include <iprt/formats/bmp.h>
38#endif
39
[96175]40#ifdef VBOX_WITH_AUDIO_RECORDING
41# include <VBox/vmm/pdmaudioinline.h>
42#endif
43
[75344]44#include "Recording.h"
45#include "RecordingUtils.h"
[74995]46#include "WebMWriter.h"
[74992]47
[82422]48
[75361]49RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
[96324]50 : m_enmState(RECORDINGSTREAMSTATE_UNINITIALIZED)
[74992]51{
[94966]52 int vrc2 = initInternal(a_pCtx, uScreen, Settings);
53 if (RT_FAILURE(vrc2))
54 throw vrc2;
[74992]55}
56
[75354]57RecordingStream::~RecordingStream(void)
[75251]58{
[94966]59 int vrc2 = uninitInternal();
60 AssertRC(vrc2);
[75251]61}
62
[74992]63/**
64 * Opens a recording stream.
65 *
[96230]66 * @returns VBox status code.
[96229]67 * @param screenSettings Recording settings to use.
[74992]68 */
[96229]69int RecordingStream::open(const settings::RecordingScreenSettings &screenSettings)
[74992]70{
[75307]71 /* Sanity. */
[96229]72 Assert(screenSettings.enmDest != RecordingDestination_None);
[74992]73
[94966]74 int vrc;
[74992]75
[96229]76 switch (screenSettings.enmDest)
[74992]77 {
[75361]78 case RecordingDestination_File:
[74992]79 {
[96229]80 Assert(screenSettings.File.strName.isNotEmpty());
[74992]81
[96545]82 const char *pszFile = screenSettings.File.strName.c_str();
[74992]83
[96545]84 RTFILE hFile = NIL_RTFILE;
85 vrc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
[94966]86 if (RT_SUCCESS(vrc))
[74992]87 {
[96229]88 LogRel2(("Recording: Opened file '%s'\n", pszFile));
89
[75307]90 try
91 {
[96324]92 Assert(File.m_pWEBM == NULL);
93 File.m_pWEBM = new WebMWriter();
[75307]94 }
95 catch (std::bad_alloc &)
[96229]96 {
[94966]97 vrc = VERR_NO_MEMORY;
[75307]98 }
99
[94966]100 if (RT_SUCCESS(vrc))
[74992]101 {
[96324]102 this->File.m_hFile = hFile;
103 m_ScreenSettings.File.strName = pszFile;
[74992]104 }
105 }
[96545]106 else
107 LogRel(("Recording: Failed to open file '%s' for screen %RU32, vrc=%Rrc\n",
108 pszFile ? pszFile : "<Unnamed>", m_uScreenID, vrc));
[74992]109
[94966]110 if (RT_FAILURE(vrc))
[74992]111 {
[96545]112 if (hFile != NIL_RTFILE)
113 RTFileClose(hFile);
[74992]114 }
115
116 break;
117 }
118
119 default:
[94966]120 vrc = VERR_NOT_IMPLEMENTED;
[74992]121 break;
122 }
123
[94966]124 LogFlowFuncLeaveRC(vrc);
125 return vrc;
[74992]126}
127
[75392]128/**
129 * Returns the recording stream's used configuration.
130 *
131 * @returns The recording stream's used configuration.
132 */
[75361]133const settings::RecordingScreenSettings &RecordingStream::GetConfig(void) const
[75251]134{
[96324]135 return m_ScreenSettings;
[75251]136}
137
[74992]138/**
[75488]139 * Checks if a specified limit for a recording stream has been reached, internal version.
[75251]140 *
[96323]141 * @returns @c true if any limit has been reached, @c false if not.
[96284]142 * @param msTimestamp Timestamp (PTS, in ms) to check for.
[75251]143 */
[75499]144bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const
[75251]145{
[75499]146 LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n",
[96324]147 msTimestamp, m_ScreenSettings.ulMaxTimeS, m_tsStartMs));
[75251]148
[96324]149 if ( m_ScreenSettings.ulMaxTimeS
150 && msTimestamp >= m_tsStartMs + (m_ScreenSettings.ulMaxTimeS * RT_MS_1SEC))
[75251]151 {
[75441]152 LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n",
[96324]153 m_uScreenID, m_ScreenSettings.ulMaxTimeS));
[75251]154 return true;
155 }
156
[96324]157 if (m_ScreenSettings.enmDest == RecordingDestination_File)
[75251]158 {
[96324]159 if (m_ScreenSettings.File.ulMaxSizeMB)
[75251]160 {
[96324]161 uint64_t sizeInMB = this->File.m_pWEBM->GetFileSize() / _1M;
162 if(sizeInMB >= m_ScreenSettings.File.ulMaxSizeMB)
[75441]163 {
164 LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n",
[96324]165 m_uScreenID, m_ScreenSettings.File.ulMaxSizeMB));
[75251]166 return true;
[75441]167 }
[75251]168 }
169
170 /* Check for available free disk space */
[96324]171 if ( this->File.m_pWEBM
172 && this->File.m_pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
[75251]173 {
[75441]174 LogRel(("Recording: Not enough free storage space available, stopping recording\n"));
[75251]175 return true;
176 }
177 }
178
179 return false;
180}
181
[75392]182/**
[75488]183 * Internal iteration main loop.
184 * Does housekeeping and recording context notification.
185 *
[96230]186 * @returns VBox status code.
[96284]187 * @param msTimestamp Timestamp (PTS, in ms).
[75488]188 */
[75499]189int RecordingStream::iterateInternal(uint64_t msTimestamp)
[75488]190{
[96324]191 if (!m_fEnabled)
[75488]192 return VINF_SUCCESS;
193
[94966]194 int vrc;
[75488]195
[75499]196 if (isLimitReachedInternal(msTimestamp))
[75488]197 {
[94966]198 vrc = VINF_RECORDING_LIMIT_REACHED;
[75488]199 }
200 else
[94966]201 vrc = VINF_SUCCESS;
[75488]202
[96324]203 AssertPtr(m_pCtx);
[75488]204
[94966]205 switch (vrc)
[75488]206 {
207 case VINF_RECORDING_LIMIT_REACHED:
208 {
[96324]209 m_fEnabled = false;
[75488]210
[98278]211 int vrc2 = m_pCtx->OnLimitReached(m_uScreenID, VINF_SUCCESS /* vrc */);
[94966]212 AssertRC(vrc2);
[75488]213 break;
214 }
215
216 default:
217 break;
218 }
219
[94966]220 LogFlowFuncLeaveRC(vrc);
221 return vrc;
[75488]222}
223
224/**
225 * Checks if a specified limit for a recording stream has been reached.
226 *
[96323]227 * @returns @c true if any limit has been reached, @c false if not.
[96284]228 * @param msTimestamp Timestamp (PTS, in ms) to check for.
[75488]229 */
[75499]230bool RecordingStream::IsLimitReached(uint64_t msTimestamp) const
[75488]231{
232 if (!IsReady())
233 return true;
234
[75499]235 return isLimitReachedInternal(msTimestamp);
[75488]236}
237
238/**
[75392]239 * Returns whether a recording stream is ready (e.g. enabled and active) or not.
240 *
[96323]241 * @returns @c true if ready, @c false if not.
[75392]242 */
[75354]243bool RecordingStream::IsReady(void) const
[75251]244{
[96324]245 return m_fEnabled;
[75251]246}
247
248/**
[96322]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{
[96324]256 return recordingCodecGetWritable((const PRECORDINGCODEC)&m_CodecVideo, msTimestamp) > 0;
[96322]257}
258
259/**
[75040]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 *
[96230]264 * @returns VBox status code.
[75251]265 * @param mapBlocksCommon Map of common block to process for this stream.
[96229]266 *
267 * @note Runs in recording thread.
[75040]268 */
[75354]269int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
[75040]270{
[75441]271 LogFlowFuncEnter();
272
[75251]273 lock();
[75040]274
[96324]275 if (!m_ScreenSettings.fEnabled)
[75040]276 {
[75251]277 unlock();
[75040]278 return VINF_SUCCESS;
279 }
280
[94966]281 int vrc = VINF_SUCCESS;
[75040]282
[96324]283 RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
284 while (itStreamBlocks != m_Blocks.Map.end())
[75040]285 {
[75499]286 uint64_t const msTimestamp = itStreamBlocks->first;
287 RecordingBlocks *pBlocks = itStreamBlocks->second;
[75040]288
289 AssertPtr(pBlocks);
290
291 while (!pBlocks->List.empty())
292 {
[75441]293 RecordingBlock *pBlock = pBlocks->List.front();
[75040]294 AssertPtr(pBlock);
295
[96260]296 switch (pBlock->enmType)
[75040]297 {
[96260]298 case RECORDINGBLOCKTYPE_VIDEO:
299 {
300 RECORDINGFRAME Frame;
301 Frame.VideoPtr = (PRECORDINGVIDEOFRAME)pBlock->pvData;
302 Frame.msTimestamp = msTimestamp;
[75040]303
[96324]304 int vrc2 = recordingCodecEncode(&m_CodecVideo, &Frame, NULL, NULL);
[96260]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;
[75040]315 }
[96175]316
[75040]317 pBlocks->List.pop_front();
[75441]318 delete pBlock;
[75040]319 }
320
[75492]321 Assert(pBlocks->List.empty());
322 delete pBlocks;
323
[96324]324 m_Blocks.Map.erase(itStreamBlocks);
325 itStreamBlocks = m_Blocks.Map.begin();
[75040]326 }
327
[75346]328#ifdef VBOX_WITH_AUDIO_RECORDING
[96229]329 /* Do we need to multiplex the common audio data to this stream? */
[96324]330 if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
[75040]331 {
[96229]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())
[75040]336 {
[96229]337 RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
338 while (itBlock != itCommonBlocks->second->List.end())
[75040]339 {
[96229]340 RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
341 switch (pBlockCommon->enmType)
[75040]342 {
[96229]343 case RECORDINGBLOCKTYPE_AUDIO:
344 {
345 PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
346 AssertPtr(pAudioFrame);
347 AssertPtr(pAudioFrame->pvBuf);
348 Assert(pAudioFrame->cbBuf);
[75040]349
[96324]350 AssertPtr(this->File.m_pWEBM);
351 int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags);
[96229]352 AssertRC(vrc2);
353 if (RT_SUCCESS(vrc))
354 vrc = vrc2;
355 break;
356 }
357
358 default:
359 AssertFailed();
360 break;
[75040]361 }
362
[96229]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;
[75040]373 }
374
[96229]375 /* If no entries are left over in the block map, remove it altogether. */
376 if (itCommonBlocks->second->List.empty())
[75040]377 {
[96229]378 delete itCommonBlocks->second;
379 mapBlocksCommon.erase(itCommonBlocks);
380 itCommonBlocks = mapBlocksCommon.begin();
[75040]381 }
382 else
[96229]383 ++itCommonBlocks;
[75040]384
[96229]385 LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
[75040]386 }
387 }
[95750]388#else
389 RT_NOREF(mapBlocksCommon);
390#endif /* VBOX_WITH_AUDIO_RECORDING */
[75040]391
[75251]392 unlock();
[75040]393
[94966]394 LogFlowFuncLeaveRC(vrc);
395 return vrc;
[75040]396}
397
[75392]398/**
[96260]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.
[96284]404 * @param msTimestamp Timestamp (PTS, in ms).
[96260]405 */
406int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
407{
[96284]408 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
[96322]409 AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
[96284]410
411 Log3Func(("cbData=%zu, msTimestamp=%RU64\n", cbData, msTimestamp));
412
[96260]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/**
[75392]419 * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
420 *
[96230]421 * @returns VBox status code. Will return VINF_RECORDING_LIMIT_REACHED if the stream's recording
[75488]422 * limit has been reached or VINF_RECORDING_THROTTLED if the frame is too early for the current
423 * FPS setting.
[75392]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.
[96284]432 * @param msTimestamp Timestamp (PTS, in ms).
[75392]433 */
[75354]434int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
[75499]435 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
[75251]436{
[96322]437 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
438 AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
439
[75251]440 lock();
441
[96284]442 Log3Func(("[%RU32 %RU32 %RU32 %RU32] msTimestamp=%RU64\n", x , y, uSrcWidth, uSrcHeight, msTimestamp));
[75488]443
[75354]444 PRECORDINGVIDEOFRAME pFrame = NULL;
[75251]445
[94966]446 int vrc = iterateInternal(msTimestamp);
447 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
[75488]448 {
449 unlock();
[94966]450 return vrc;
[75488]451 }
[75251]452
453 do
454 {
[96324]455 int xDiff = ((int)m_ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
[75251]456 uint32_t w = uSrcWidth;
457 if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
458 {
[94966]459 vrc = VERR_INVALID_PARAMETER;
[75251]460 break;
461 }
462
463 uint32_t destX;
464 if ((int)x < -xDiff)
465 {
466 w += xDiff + x;
467 x = -xDiff;
468 destX = 0;
469 }
470 else
471 destX = x + xDiff;
472
473 uint32_t h = uSrcHeight;
[96324]474 int yDiff = ((int)m_ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
[75251]475 if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
476 {
[94966]477 vrc = VERR_INVALID_PARAMETER;
[75251]478 break;
479 }
480
481 uint32_t destY;
482 if ((int)y < -yDiff)
483 {
484 h += yDiff + (int)y;
485 y = -yDiff;
486 destY = 0;
487 }
488 else
489 destY = y + yDiff;
490
[96324]491 if ( destX > m_ScreenSettings.Video.ulWidth
492 || destY > m_ScreenSettings.Video.ulHeight)
[75251]493 {
[94966]494 vrc = VERR_INVALID_PARAMETER; /* Nothing visible. */
[75251]495 break;
496 }
497
[96324]498 if (destX + w > m_ScreenSettings.Video.ulWidth)
499 w = m_ScreenSettings.Video.ulWidth - destX;
[75251]500
[96324]501 if (destY + h > m_ScreenSettings.Video.ulHeight)
502 h = m_ScreenSettings.Video.ulHeight - destY;
[75251]503
[75354]504 pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
[94966]505 AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY);
[75251]506
507 /* Calculate bytes per pixel and set pixel format. */
508 const unsigned uBytesPerPixel = uBPP / 8;
509 if (uPixelFormat == BitmapFormat_BGR)
510 {
511 switch (uBPP)
512 {
513 case 32:
[96482]514 pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB32;
[75251]515 break;
516 case 24:
[96482]517 pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB24;
[75251]518 break;
519 case 16:
[96482]520 pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB565;
[75251]521 break;
522 default:
[94966]523 AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED);
[75251]524 break;
525 }
526 }
527 else
[94966]528 AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED);
[75251]529
[96324]530 const size_t cbRGBBuf = m_ScreenSettings.Video.ulWidth
531 * m_ScreenSettings.Video.ulHeight
[75251]532 * uBytesPerPixel;
[94966]533 AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER);
[75251]534
535 pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
[94966]536 AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY);
[75251]537 pFrame->cbRGBBuf = cbRGBBuf;
538 pFrame->uWidth = uSrcWidth;
539 pFrame->uHeight = uSrcHeight;
540
541 /* If the current video frame is smaller than video resolution we're going to encode,
542 * clear the frame beforehand to prevent artifacts. */
[96324]543 if ( uSrcWidth < m_ScreenSettings.Video.ulWidth
544 || uSrcHeight < m_ScreenSettings.Video.ulHeight)
[75251]545 {
546 RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
547 }
548
549 /* Calculate start offset in source and destination buffers. */
550 uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
[96324]551 uint32_t offDst = (destY * m_ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
[75251]552
[75354]553#ifdef VBOX_RECORDING_DUMP
[85864]554 BMPFILEHDR fileHdr;
555 RT_ZERO(fileHdr);
[75251]556
[85864]557 BMPWIN3XINFOHDR coreHdr;
558 RT_ZERO(coreHdr);
[75251]559
[85864]560 fileHdr.uType = BMP_HDR_MAGIC;
561 fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
562 fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
[75251]563
[85864]564 coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
565 coreHdr.uWidth = w;
566 coreHdr.uHeight = h;
567 coreHdr.cPlanes = 1;
568 coreHdr.cBits = uBPP;
569 coreHdr.uXPelsPerMeter = 5000;
570 coreHdr.uYPelsPerMeter = 5000;
[75251]571
572 char szFileName[RTPATH_MAX];
[96324]573 RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", m_uScreenID);
[75251]574
575 RTFILE fh;
[94966]576 int vrc2 = RTFileOpen(&fh, szFileName,
577 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
578 if (RT_SUCCESS(vrc2))
[75251]579 {
[85864]580 RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
581 RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
[75251]582 }
583#endif
584 Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
585
586 /* Do the copy. */
587 for (unsigned int i = 0; i < h; i++)
588 {
589 /* Overflow check. */
590 Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
[96324]591 Assert(offDst + w * uBytesPerPixel <= m_ScreenSettings.Video.ulHeight * m_ScreenSettings.Video.ulWidth * uBytesPerPixel);
[75251]592
593 memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
594
[75354]595#ifdef VBOX_RECORDING_DUMP
[75251]596 if (RT_SUCCESS(rc2))
597 RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
598#endif
599 offSrc += uBytesPerLine;
[96324]600 offDst += m_ScreenSettings.Video.ulWidth * uBytesPerPixel;
[75251]601 }
602
[75354]603#ifdef VBOX_RECORDING_DUMP
[94966]604 if (RT_SUCCESS(vrc2))
[75251]605 RTFileClose(fh);
606#endif
607
608 } while (0);
609
[94966]610 if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
[75251]611 {
[75441]612 RecordingBlock *pBlock = new RecordingBlock();
[75251]613 if (pBlock)
614 {
615 AssertPtr(pFrame);
616
[75354]617 pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
[75251]618 pBlock->pvData = pFrame;
[75354]619 pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
[75251]620
621 try
622 {
[75490]623 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
624 pRecordingBlocks->List.push_back(pBlock);
[75251]625
[96324]626 Assert(m_Blocks.Map.find(msTimestamp) == m_Blocks.Map.end());
627 m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
[75251]628 }
629 catch (const std::exception &ex)
630 {
631 RT_NOREF(ex);
632
[75491]633 delete pBlock;
[94966]634 vrc = VERR_NO_MEMORY;
[75251]635 }
636 }
637 else
[94966]638 vrc = VERR_NO_MEMORY;
[75251]639 }
640
[94966]641 if (RT_FAILURE(vrc))
[75354]642 RecordingVideoFrameFree(pFrame);
[75251]643
[75313]644 unlock();
[75251]645
[96284]646 LogFlowFuncLeaveRC(vrc);
[94966]647 return vrc;
[75251]648}
649
[75313]650/**
651 * Initializes a recording stream.
652 *
[96230]653 * @returns VBox status code.
[96229]654 * @param pCtx Pointer to recording context.
[75313]655 * @param uScreen Screen number to use for this recording stream.
[75441]656 * @param Settings Recording screen configuration to use for initialization.
[75313]657 */
[96229]658int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
[75251]659{
[96229]660 return initInternal(pCtx, uScreen, Settings);
[75251]661}
662
[75040]663/**
[75313]664 * Initializes a recording stream, internal version.
[74992]665 *
[96230]666 * @returns VBox status code.
[96229]667 * @param pCtx Pointer to recording context.
[75251]668 * @param uScreen Screen number to use for this recording stream.
[96229]669 * @param screenSettings Recording screen configuration to use for initialization.
[74992]670 */
[96229]671int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
672 const settings::RecordingScreenSettings &screenSettings)
[74992]673{
[96324]674 AssertReturn(m_enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
[96175]675
[96324]676 m_pCtx = pCtx;
677 m_uTrackAudio = UINT8_MAX;
678 m_uTrackVideo = UINT8_MAX;
679 m_tsStartMs = 0;
680 m_uScreenID = uScreen;
[96229]681#ifdef VBOX_WITH_AUDIO_RECORDING
682 /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
[96324]683 m_pCodecAudio = m_pCtx->GetCodecAudio();
[96229]684#endif
[96324]685 m_ScreenSettings = screenSettings;
[75417]686
[96324]687 settings::RecordingScreenSettings *pSettings = &m_ScreenSettings;
[75441]688
[96324]689 int vrc = RTCritSectInit(&m_CritSect);
[94966]690 if (RT_FAILURE(vrc))
691 return vrc;
[74992]692
[96324]693 this->File.m_pWEBM = NULL;
694 this->File.m_hFile = NIL_RTFILE;
[96175]695
696 vrc = open(*pSettings);
[94966]697 if (RT_FAILURE(vrc))
698 return vrc;
[74992]699
[75441]700 const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
701 const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
[74992]702
[75251]703 if (fVideoEnabled)
[75391]704 {
[96175]705 vrc = initVideo(*pSettings);
[94966]706 if (RT_FAILURE(vrc))
707 return vrc;
[75391]708 }
[75251]709
[96175]710 switch (pSettings->enmDest)
[74992]711 {
[75361]712 case RecordingDestination_File:
[74992]713 {
[75441]714 Assert(pSettings->File.strName.isNotEmpty());
715 const char *pszFile = pSettings->File.strName.c_str();
[75251]716
[96324]717 AssertPtr(File.m_pWEBM);
718 vrc = File.m_pWEBM->OpenEx(pszFile, &this->File.m_hFile,
[96175]719 fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
720 fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
[94966]721 if (RT_FAILURE(vrc))
[74992]722 {
[94966]723 LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
[74992]724 break;
725 }
726
[75251]727 if (fVideoEnabled)
[74992]728 {
[96324]729 vrc = this->File.m_pWEBM->AddVideoTrack(&m_CodecVideo,
[96175]730 pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
[96324]731 &m_uTrackVideo);
[94966]732 if (RT_FAILURE(vrc))
[74992]733 {
[94966]734 LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
[74992]735 break;
736 }
737
[75344]738 LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
[96324]739 m_uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
740 pSettings->Video.ulRate, pSettings->Video.ulFPS, m_uTrackVideo));
[74992]741 }
742
[75346]743#ifdef VBOX_WITH_AUDIO_RECORDING
[75251]744 if (fAudioEnabled)
[74992]745 {
[96324]746 AssertPtr(m_pCodecAudio);
747 vrc = this->File.m_pWEBM->AddAudioTrack(m_pCodecAudio,
[96229]748 pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
[96324]749 &m_uTrackAudio);
[94966]750 if (RT_FAILURE(vrc))
[74992]751 {
[94966]752 LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
[74992]753 break;
754 }
755
[75391]756 LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
[96324]757 m_uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
758 pSettings->Audio.cChannels ? "channels" : "channel", m_uTrackAudio));
[74992]759 }
760#endif
761
[75251]762 if ( fVideoEnabled
[75346]763#ifdef VBOX_WITH_AUDIO_RECORDING
[75251]764 || fAudioEnabled
[74992]765#endif
766 )
767 {
768 char szWhat[32] = { 0 };
[75251]769 if (fVideoEnabled)
[74992]770 RTStrCat(szWhat, sizeof(szWhat), "video");
[75346]771#ifdef VBOX_WITH_AUDIO_RECORDING
[75251]772 if (fAudioEnabled)
[74992]773 {
[75251]774 if (fVideoEnabled)
[74992]775 RTStrCat(szWhat, sizeof(szWhat), " + ");
776 RTStrCat(szWhat, sizeof(szWhat), "audio");
777 }
778#endif
[96324]779 LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, m_uScreenID, pszFile));
[74992]780 }
781
782 break;
783 }
784
785 default:
786 AssertFailed(); /* Should never happen. */
[94966]787 vrc = VERR_NOT_IMPLEMENTED;
[74992]788 break;
789 }
790
[94966]791 if (RT_SUCCESS(vrc))
[74992]792 {
[96324]793 m_enmState = RECORDINGSTREAMSTATE_INITIALIZED;
794 m_fEnabled = true;
795 m_tsStartMs = RTTimeProgramMilliTS();
[96229]796
797 return VINF_SUCCESS;
[75069]798 }
[74992]799
[96229]800 int vrc2 = uninitInternal();
801 AssertRC(vrc2);
802
803 LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
804 return vrc;
[74992]805}
806
807/**
808 * Closes a recording stream.
809 * Depending on the stream's recording destination, this function closes all associated handles
810 * and finalizes recording.
811 *
[96230]812 * @returns VBox status code.
[74992]813 */
[75354]814int RecordingStream::close(void)
[74992]815{
[94966]816 int vrc = VINF_SUCCESS;
[74992]817
[96324]818 switch (m_ScreenSettings.enmDest)
[74992]819 {
[75488]820 case RecordingDestination_File:
[74992]821 {
[96324]822 if (this->File.m_pWEBM)
823 vrc = this->File.m_pWEBM->Close();
[75488]824 break;
[74992]825 }
826
[75488]827 default:
828 AssertFailed(); /* Should never happen. */
829 break;
[74992]830 }
831
[96324]832 m_Blocks.Clear();
[75488]833
[96324]834 LogRel(("Recording: Recording screen #%u stopped\n", m_uScreenID));
[75488]835
[94966]836 if (RT_FAILURE(vrc))
[74992]837 {
[96324]838 LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", m_uScreenID, vrc));
[94966]839 return vrc;
[74992]840 }
841
[96324]842 switch (m_ScreenSettings.enmDest)
[74992]843 {
[75361]844 case RecordingDestination_File:
[74992]845 {
[96324]846 if (RTFileIsValid(this->File.m_hFile))
[74992]847 {
[96324]848 vrc = RTFileClose(this->File.m_hFile);
[94966]849 if (RT_SUCCESS(vrc))
[74992]850 {
[96324]851 LogRel(("Recording: Closed file '%s'\n", m_ScreenSettings.File.strName.c_str()));
[74992]852 }
853 else
854 {
[98278]855 LogRel(("Recording: Error closing file '%s', vrc=%Rrc\n", m_ScreenSettings.File.strName.c_str(), vrc));
[74992]856 break;
857 }
858 }
859
[96324]860 WebMWriter *pWebMWriter = this->File.m_pWEBM;
[95665]861 AssertPtr(pWebMWriter);
862
863 if (pWebMWriter)
[74992]864 {
[95665]865 /* If no clusters (= data) was written, delete the file again. */
866 if (pWebMWriter->GetClusters() == 0)
867 {
[96324]868 int vrc2 = RTFileDelete(m_ScreenSettings.File.strName.c_str());
[98278]869 AssertRC(vrc2); /* Ignore vrc on non-debug builds. */
[95665]870 }
871
872 delete pWebMWriter;
873 pWebMWriter = NULL;
874
[96324]875 this->File.m_pWEBM = NULL;
[74992]876 }
877 break;
878 }
879
880 default:
[94966]881 vrc = VERR_NOT_IMPLEMENTED;
[74992]882 break;
883 }
884
[94966]885 LogFlowFuncLeaveRC(vrc);
886 return vrc;
[74992]887}
888
889/**
890 * Uninitializes a recording stream.
891 *
[96230]892 * @returns VBox status code.
[74992]893 */
[75354]894int RecordingStream::Uninit(void)
[74992]895{
[75251]896 return uninitInternal();
897}
[74992]898
[75392]899/**
900 * Uninitializes a recording stream, internal version.
901 *
[96230]902 * @returns VBox status code.
[75392]903 */
[75354]904int RecordingStream::uninitInternal(void)
[75251]905{
[96324]906 if (m_enmState != RECORDINGSTREAMSTATE_INITIALIZED)
[75313]907 return VINF_SUCCESS;
908
[94966]909 int vrc = close();
910 if (RT_FAILURE(vrc))
911 return vrc;
[75251]912
[96229]913#ifdef VBOX_WITH_AUDIO_RECORDING
[96324]914 m_pCodecAudio = NULL;
[96229]915#endif
916
[96324]917 if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
[96229]918 {
[96324]919 vrc = recordingCodecFinalize(&m_CodecVideo);
[96229]920 if (RT_SUCCESS(vrc))
[96324]921 vrc = recordingCodecDestroy(&m_CodecVideo);
[96229]922 }
[96175]923
924 if (RT_SUCCESS(vrc))
[74992]925 {
[96324]926 RTCritSectDelete(&m_CritSect);
[96175]927
[96324]928 m_enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
929 m_fEnabled = false;
[74992]930 }
931
[94966]932 return vrc;
[74992]933}
934
[96229]935/**
936 * Writes encoded data to a WebM file instance.
937 *
938 * @returns VBox status code.
939 * @param pCodec Codec which has encoded the data.
940 * @param pvData Encoded data to write.
941 * @param cbData Size (in bytes) of \a pvData.
942 * @param msAbsPTS Absolute PTS (in ms) of written data.
943 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
944 */
945int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
946 uint64_t msAbsPTS, uint32_t uFlags)
[74992]947{
[96324]948 AssertPtr(this->File.m_pWEBM);
[96229]949 AssertPtr(pvData);
950 Assert (cbData);
[74992]951
[96229]952 WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
953 if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE))
954 {
955 /* All set. */
956 }
957 else
958 {
959 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
960 blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
961 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
962 blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
963 }
[75313]964
[96324]965 return this->File.m_pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
966 ? m_uTrackAudio : m_uTrackVideo,
[96229]967 pvData, cbData, msAbsPTS, blockFlags);
[74992]968}
969
970/**
[96229]971 * Codec callback for writing encoded data to a recording stream.
[74992]972 *
[96175]973 * @returns VBox status code.
[96229]974 * @param pCodec Codec which has encoded the data.
975 * @param pvData Encoded data to write.
976 * @param cbData Size (in bytes) of \a pvData.
977 * @param msAbsPTS Absolute PTS (in ms) of written data.
978 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
[96243]979 * @param pvUser User-supplied pointer.
[74992]980 */
[96229]981/* static */
982DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
983 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
[74992]984{
[96229]985 RecordingStream *pThis = (RecordingStream *)pvUser;
986 AssertPtr(pThis);
[75251]987
[96229]988 /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
989 return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
[74992]990}
991
992/**
[96229]993 * Initializes the video recording for a recording stream.
[74992]994 *
[96175]995 * @returns VBox status code.
[96229]996 * @param screenSettings Screen settings to use.
[74992]997 */
[96229]998int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings)
[74992]999{
[96229]1000 /* Sanity. */
1001 AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
1002 AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
1003 AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
1004 AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
[74992]1005
[96324]1006 PRECORDINGCODEC pCodec = &m_CodecVideo;
[75251]1007
[96175]1008 RECORDINGCODECCALLBACKS Callbacks;
1009 Callbacks.pvUser = this;
1010 Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
[74992]1011
[96229]1012 int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
[96175]1013 if (RT_SUCCESS(vrc))
[96229]1014 vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
[74992]1015
[96175]1016 if (RT_FAILURE(vrc))
[96229]1017 LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
[74992]1018
[94966]1019 return vrc;
[74992]1020}
1021
[75251]1022/**
1023 * Locks a recording stream.
1024 */
[75354]1025void RecordingStream::lock(void)
[75251]1026{
[96324]1027 int vrc = RTCritSectEnter(&m_CritSect);
[94966]1028 AssertRC(vrc);
[75251]1029}
1030
1031/**
1032 * Unlocks a locked recording stream.
1033 */
[75354]1034void RecordingStream::unlock(void)
[75251]1035{
[96324]1036 int vrc = RTCritSectLeave(&m_CritSect);
[94966]1037 AssertRC(vrc);
[75251]1038}
1039
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use