VirtualBox

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

Last change on this file since 96176 was 96175, checked in by vboxsync, 3 years ago

Recording: Implemented support for Vorbis codec (provided by libvorbis, not enabled by default yet). This also makes all the codec handling more abstract by using a simple codec wrapper, to keep other places free from codec-specific as much as possible. Initial implementation works and output files are being recognized by media players, but there still are some timing bugs to resolve, as well as optimizing the performance. bugref:10275

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

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