VirtualBox

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

Last change on this file since 94521 was 93115, checked in by vboxsync, 2 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.0 KB
Line 
1/* $Id: RecordingStream.cpp 93115 2022-01-01 11:31:46Z 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_MAIN_DISPLAY
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#include "Recording.h"
31#include "RecordingUtils.h"
32#include "WebMWriter.h"
33
34
35RecordingStream::RecordingStream(RecordingContext *a_pCtx)
36 : pCtx(a_pCtx)
37 , enmState(RECORDINGSTREAMSTATE_UNINITIALIZED)
38 , tsStartMs(0)
39{
40 File.pWEBM = NULL;
41 File.hFile = NIL_RTFILE;
42}
43
44RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
45 : enmState(RECORDINGSTREAMSTATE_UNINITIALIZED)
46 , tsStartMs(0)
47{
48 File.pWEBM = NULL;
49 File.hFile = NIL_RTFILE;
50
51 int rc2 = initInternal(a_pCtx, uScreen, Settings);
52 if (RT_FAILURE(rc2))
53 throw rc2;
54}
55
56RecordingStream::~RecordingStream(void)
57{
58 int rc2 = uninitInternal();
59 AssertRC(rc2);
60}
61
62/**
63 * Opens a recording stream.
64 *
65 * @returns IPRT status code.
66 */
67int RecordingStream::open(const settings::RecordingScreenSettings &Settings)
68{
69 /* Sanity. */
70 Assert(Settings.enmDest != RecordingDestination_None);
71
72 int rc;
73
74 switch (Settings.enmDest)
75 {
76 case RecordingDestination_File:
77 {
78 Assert(Settings.File.strName.isNotEmpty());
79
80 char *pszAbsPath = RTPathAbsDup(Settings.File.strName.c_str());
81 AssertPtrReturn(pszAbsPath, VERR_NO_MEMORY);
82
83 RTPathStripSuffix(pszAbsPath);
84
85 char *pszSuff = RTStrDup(".webm");
86 if (!pszSuff)
87 {
88 RTStrFree(pszAbsPath);
89 rc = VERR_NO_MEMORY;
90 break;
91 }
92
93 char *pszFile = NULL;
94
95 if (this->uScreenID > 0)
96 rc = RTStrAPrintf(&pszFile, "%s-%u%s", pszAbsPath, this->uScreenID + 1, pszSuff);
97 else
98 rc = RTStrAPrintf(&pszFile, "%s%s", pszAbsPath, pszSuff);
99
100 if (RT_SUCCESS(rc))
101 {
102 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
103
104 /* Play safe: the file must not exist, overwriting is potentially
105 * hazardous as nothing prevents the user from picking a file name of some
106 * other important file, causing unintentional data loss. */
107 fOpen |= RTFILE_O_CREATE;
108
109 RTFILE hFile;
110 rc = RTFileOpen(&hFile, pszFile, fOpen);
111 if (rc == VERR_ALREADY_EXISTS)
112 {
113 RTStrFree(pszFile);
114 pszFile = NULL;
115
116 RTTIMESPEC ts;
117 RTTimeNow(&ts);
118 RTTIME time;
119 RTTimeExplode(&time, &ts);
120
121 if (this->uScreenID > 0)
122 rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ-%u%s",
123 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
124 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
125 this->uScreenID + 1, pszSuff);
126 else
127 rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ%s",
128 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
129 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
130 pszSuff);
131
132 if (RT_SUCCESS(rc))
133 rc = RTFileOpen(&hFile, pszFile, fOpen);
134 }
135
136 try
137 {
138 Assert(File.pWEBM == NULL);
139 File.pWEBM = new WebMWriter();
140 }
141 catch (std::bad_alloc &)
142 {
143 rc = VERR_NO_MEMORY;
144 }
145
146 if (RT_SUCCESS(rc))
147 {
148 this->File.hFile = hFile;
149 this->ScreenSettings.File.strName = pszFile;
150 }
151 }
152
153 RTStrFree(pszSuff);
154 RTStrFree(pszAbsPath);
155
156 if (RT_FAILURE(rc))
157 {
158 LogRel(("Recording: Failed to open file '%s' for screen %RU32, rc=%Rrc\n",
159 pszFile ? pszFile : "<Unnamed>", this->uScreenID, rc));
160 }
161
162 RTStrFree(pszFile);
163 break;
164 }
165
166 default:
167 rc = VERR_NOT_IMPLEMENTED;
168 break;
169 }
170
171 LogFlowFuncLeaveRC(rc);
172 return rc;
173}
174
175/**
176 * Parses an options string to configure advanced / hidden / experimental features of a recording stream.
177 * Unknown values will be skipped.
178 *
179 * @returns IPRT status code.
180 * @param strOptions Options string to parse.
181 */
182int RecordingStream::parseOptionsString(const com::Utf8Str &strOptions)
183{
184 size_t pos = 0;
185 com::Utf8Str key, value;
186 while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
187 {
188 if (key.compare("vc_quality", com::Utf8Str::CaseInsensitive) == 0)
189 {
190#ifdef VBOX_WITH_LIBVPX
191 Assert(this->ScreenSettings.Video.ulFPS);
192 if (value.compare("realtime", com::Utf8Str::CaseInsensitive) == 0)
193 this->Video.Codec.VPX.uEncoderDeadline = VPX_DL_REALTIME;
194 else if (value.compare("good", com::Utf8Str::CaseInsensitive) == 0)
195 this->Video.Codec.VPX.uEncoderDeadline = 1000000 / this->ScreenSettings.Video.ulFPS;
196 else if (value.compare("best", com::Utf8Str::CaseInsensitive) == 0)
197 this->Video.Codec.VPX.uEncoderDeadline = VPX_DL_BEST_QUALITY;
198 else
199 {
200 this->Video.Codec.VPX.uEncoderDeadline = value.toUInt32();
201#endif
202 }
203 }
204 else if (key.compare("vc_enabled", com::Utf8Str::CaseInsensitive) == 0)
205 {
206 if (value.compare("false", com::Utf8Str::CaseInsensitive) == 0)
207 this->ScreenSettings.featureMap[RecordingFeature_Video] = false;
208 }
209 else if (key.compare("ac_enabled", com::Utf8Str::CaseInsensitive) == 0)
210 {
211#ifdef VBOX_WITH_AUDIO_RECORDING
212 if (value.compare("true", com::Utf8Str::CaseInsensitive) == 0)
213 this->ScreenSettings.featureMap[RecordingFeature_Audio] = true;
214#endif
215 }
216 else if (key.compare("ac_profile", com::Utf8Str::CaseInsensitive) == 0)
217 {
218#ifdef VBOX_WITH_AUDIO_RECORDING
219 if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0)
220 {
221 this->ScreenSettings.Audio.uHz = 8000;
222 this->ScreenSettings.Audio.cBits = 16;
223 this->ScreenSettings.Audio.cChannels = 1;
224 }
225 else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0)
226 {
227 /* Stay with the default set above. */
228 }
229 else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0)
230 {
231 this->ScreenSettings.Audio.uHz = 48000;
232 this->ScreenSettings.Audio.cBits = 16;
233 this->ScreenSettings.Audio.cChannels = 2;
234 }
235#endif
236 }
237 else
238 LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
239
240 } /* while */
241
242 return VINF_SUCCESS;
243}
244
245/**
246 * Returns the recording stream's used configuration.
247 *
248 * @returns The recording stream's used configuration.
249 */
250const settings::RecordingScreenSettings &RecordingStream::GetConfig(void) const
251{
252 return this->ScreenSettings;
253}
254
255/**
256 * Checks if a specified limit for a recording stream has been reached, internal version.
257 *
258 * @returns true if any limit has been reached.
259 * @param msTimestamp Timestamp (in ms) to check for.
260 */
261bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const
262{
263 LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n",
264 msTimestamp, this->ScreenSettings.ulMaxTimeS, this->tsStartMs));
265
266 if ( this->ScreenSettings.ulMaxTimeS
267 && msTimestamp >= this->tsStartMs + (this->ScreenSettings.ulMaxTimeS * RT_MS_1SEC))
268 {
269 LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n",
270 this->uScreenID, this->ScreenSettings.ulMaxTimeS));
271 return true;
272 }
273
274 if (this->ScreenSettings.enmDest == RecordingDestination_File)
275 {
276 if (this->ScreenSettings.File.ulMaxSizeMB)
277 {
278 uint64_t sizeInMB = this->File.pWEBM->GetFileSize() / _1M;
279 if(sizeInMB >= this->ScreenSettings.File.ulMaxSizeMB)
280 {
281 LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n",
282 this->uScreenID, this->ScreenSettings.File.ulMaxSizeMB));
283 return true;
284 }
285 }
286
287 /* Check for available free disk space */
288 if ( this->File.pWEBM
289 && this->File.pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
290 {
291 LogRel(("Recording: Not enough free storage space available, stopping recording\n"));
292 return true;
293 }
294 }
295
296 return false;
297}
298
299/**
300 * Internal iteration main loop.
301 * Does housekeeping and recording context notification.
302 *
303 * @returns IPRT status code.
304 * @param msTimestamp Current timestamp (in ms).
305 */
306int RecordingStream::iterateInternal(uint64_t msTimestamp)
307{
308 if (!this->fEnabled)
309 return VINF_SUCCESS;
310
311 int rc;
312
313 if (isLimitReachedInternal(msTimestamp))
314 {
315 rc = VINF_RECORDING_LIMIT_REACHED;
316 }
317 else
318 rc = VINF_SUCCESS;
319
320 AssertPtr(this->pCtx);
321
322 switch (rc)
323 {
324 case VINF_RECORDING_LIMIT_REACHED:
325 {
326 this->fEnabled = false;
327
328 int rc2 = this->pCtx->OnLimitReached(this->uScreenID, VINF_SUCCESS /* rc */);
329 AssertRC(rc2);
330 break;
331 }
332
333 default:
334 break;
335 }
336
337 LogFlowFuncLeaveRC(rc);
338 return rc;
339}
340
341/**
342 * Checks if a specified limit for a recording stream has been reached.
343 *
344 * @returns true if any limit has been reached.
345 * @param msTimestamp Timestamp (in ms) to check for.
346 */
347bool RecordingStream::IsLimitReached(uint64_t msTimestamp) const
348{
349 if (!IsReady())
350 return true;
351
352 return isLimitReachedInternal(msTimestamp);
353}
354
355/**
356 * Returns whether a recording stream is ready (e.g. enabled and active) or not.
357 *
358 * @returns \c true if ready, \c false if not.
359 */
360bool RecordingStream::IsReady(void) const
361{
362 return this->fEnabled;
363}
364
365/**
366 * Processes a recording stream.
367 * This function takes care of the actual encoding and writing of a certain stream.
368 * As this can be very CPU intensive, this function usually is called from a separate thread.
369 *
370 * @returns IPRT status code.
371 * @param mapBlocksCommon Map of common block to process for this stream.
372 */
373int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
374{
375 LogFlowFuncEnter();
376
377 lock();
378
379 if (!this->ScreenSettings.fEnabled)
380 {
381 unlock();
382 return VINF_SUCCESS;
383 }
384
385 int rc = VINF_SUCCESS;
386
387 RecordingBlockMap::iterator itStreamBlocks = Blocks.Map.begin();
388 while (itStreamBlocks != Blocks.Map.end())
389 {
390 uint64_t const msTimestamp = itStreamBlocks->first;
391 RecordingBlocks *pBlocks = itStreamBlocks->second;
392
393 AssertPtr(pBlocks);
394
395 while (!pBlocks->List.empty())
396 {
397 RecordingBlock *pBlock = pBlocks->List.front();
398 AssertPtr(pBlock);
399
400#ifdef VBOX_WITH_LIBVPX
401 if (pBlock->enmType == RECORDINGBLOCKTYPE_VIDEO)
402 {
403 PRECORDINGVIDEOFRAME pVideoFrame = (PRECORDINGVIDEOFRAME)pBlock->pvData;
404
405 int rc2 = RecordingUtilsRGBToYUV(pVideoFrame->uPixelFormat,
406 /* Destination */
407 this->Video.Codec.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
408 /* Source */
409 pVideoFrame->pu8RGBBuf, this->ScreenSettings.Video.ulWidth, this->ScreenSettings.Video.ulHeight);
410 if (RT_SUCCESS(rc2))
411 {
412 rc2 = writeVideoVPX(msTimestamp, pVideoFrame);
413 AssertRC(rc2);
414 if (RT_SUCCESS(rc))
415 rc = rc2;
416 }
417 }
418#endif
419 pBlocks->List.pop_front();
420 delete pBlock;
421 }
422
423 Assert(pBlocks->List.empty());
424 delete pBlocks;
425
426 Blocks.Map.erase(itStreamBlocks);
427 itStreamBlocks = Blocks.Map.begin();
428 }
429
430#ifdef VBOX_WITH_AUDIO_RECORDING
431 AssertPtr(pCtx);
432
433 /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
434 * written to the screen's assigned recording stream. */
435 RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin();
436 while (itCommonBlocks != mapBlocksCommon.end())
437 {
438 RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
439 while (itBlock != itCommonBlocks->second->List.end())
440 {
441 RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
442 switch (pBlockCommon->enmType)
443 {
444 case RECORDINGBLOCKTYPE_AUDIO:
445 {
446 PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
447 AssertPtr(pAudioFrame);
448 AssertPtr(pAudioFrame->pvBuf);
449 Assert(pAudioFrame->cbBuf);
450
451 WebMWriter::BlockData_Opus blockData = { pAudioFrame->pvBuf, pAudioFrame->cbBuf,
452 pBlockCommon->msTimestamp };
453 AssertPtr(this->File.pWEBM);
454 int rc2 = this->File.pWEBM->WriteBlock(this->uTrackAudio, &blockData, sizeof(blockData));
455 AssertRC(rc2);
456 if (RT_SUCCESS(rc))
457 rc = rc2;
458 break;
459 }
460
461 default:
462 AssertFailed();
463 break;
464 }
465
466 Assert(pBlockCommon->cRefs);
467 pBlockCommon->cRefs--;
468 if (pBlockCommon->cRefs == 0)
469 {
470 itCommonBlocks->second->List.erase(itBlock);
471 delete pBlockCommon;
472 itBlock = itCommonBlocks->second->List.begin();
473 }
474 else
475 ++itBlock;
476 }
477
478 /* If no entries are left over in the block map, remove it altogether. */
479 if (itCommonBlocks->second->List.empty())
480 {
481 delete itCommonBlocks->second;
482 mapBlocksCommon.erase(itCommonBlocks);
483 itCommonBlocks = mapBlocksCommon.begin();
484 }
485 else
486 ++itCommonBlocks;
487
488 LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
489 }
490#endif
491
492 unlock();
493
494 LogFlowFuncLeaveRC(rc);
495 return rc;
496}
497
498/**
499 * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
500 *
501 * @returns IPRT status code. Will return VINF_RECORDING_LIMIT_REACHED if the stream's recording
502 * limit has been reached or VINF_RECORDING_THROTTLED if the frame is too early for the current
503 * FPS setting.
504 * @param x Upper left (X) coordinate where the video frame starts.
505 * @param y Upper left (Y) coordinate where the video frame starts.
506 * @param uPixelFormat Pixel format of the video frame.
507 * @param uBPP Bits per pixel (BPP) of the video frame.
508 * @param uBytesPerLine Bytes per line of the video frame.
509 * @param uSrcWidth Width (in pixels) of the video frame.
510 * @param uSrcHeight Height (in pixels) of the video frame.
511 * @param puSrcData Actual pixel data of the video frame.
512 * @param msTimestamp Timestamp (in ms) as PTS.
513 */
514int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
515 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
516{
517 lock();
518
519 LogFlowFunc(("msTimestamp=%RU64\n", msTimestamp));
520
521 PRECORDINGVIDEOFRAME pFrame = NULL;
522
523 int rc = iterateInternal(msTimestamp);
524 if (rc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
525 {
526 unlock();
527 return rc;
528 }
529
530 do
531 {
532 if (msTimestamp < this->Video.uLastTimeStampMs + this->Video.uDelayMs)
533 {
534 rc = VINF_RECORDING_THROTTLED; /* Respect maximum frames per second. */
535 break;
536 }
537
538 this->Video.uLastTimeStampMs = msTimestamp;
539
540 int xDiff = ((int)this->ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
541 uint32_t w = uSrcWidth;
542 if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
543 {
544 rc = VERR_INVALID_PARAMETER;
545 break;
546 }
547
548 uint32_t destX;
549 if ((int)x < -xDiff)
550 {
551 w += xDiff + x;
552 x = -xDiff;
553 destX = 0;
554 }
555 else
556 destX = x + xDiff;
557
558 uint32_t h = uSrcHeight;
559 int yDiff = ((int)this->ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
560 if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
561 {
562 rc = VERR_INVALID_PARAMETER;
563 break;
564 }
565
566 uint32_t destY;
567 if ((int)y < -yDiff)
568 {
569 h += yDiff + (int)y;
570 y = -yDiff;
571 destY = 0;
572 }
573 else
574 destY = y + yDiff;
575
576 if ( destX > this->ScreenSettings.Video.ulWidth
577 || destY > this->ScreenSettings.Video.ulHeight)
578 {
579 rc = VERR_INVALID_PARAMETER; /* Nothing visible. */
580 break;
581 }
582
583 if (destX + w > this->ScreenSettings.Video.ulWidth)
584 w = this->ScreenSettings.Video.ulWidth - destX;
585
586 if (destY + h > this->ScreenSettings.Video.ulHeight)
587 h = this->ScreenSettings.Video.ulHeight - destY;
588
589 pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
590 AssertBreakStmt(pFrame, rc = VERR_NO_MEMORY);
591
592 /* Calculate bytes per pixel and set pixel format. */
593 const unsigned uBytesPerPixel = uBPP / 8;
594 if (uPixelFormat == BitmapFormat_BGR)
595 {
596 switch (uBPP)
597 {
598 case 32:
599 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB32;
600 break;
601 case 24:
602 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB24;
603 break;
604 case 16:
605 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB565;
606 break;
607 default:
608 AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), rc = VERR_NOT_SUPPORTED);
609 break;
610 }
611 }
612 else
613 AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), rc = VERR_NOT_SUPPORTED);
614
615 const size_t cbRGBBuf = this->ScreenSettings.Video.ulWidth
616 * this->ScreenSettings.Video.ulHeight
617 * uBytesPerPixel;
618 AssertBreakStmt(cbRGBBuf, rc = VERR_INVALID_PARAMETER);
619
620 pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
621 AssertBreakStmt(pFrame->pu8RGBBuf, rc = VERR_NO_MEMORY);
622 pFrame->cbRGBBuf = cbRGBBuf;
623 pFrame->uWidth = uSrcWidth;
624 pFrame->uHeight = uSrcHeight;
625
626 /* If the current video frame is smaller than video resolution we're going to encode,
627 * clear the frame beforehand to prevent artifacts. */
628 if ( uSrcWidth < this->ScreenSettings.Video.ulWidth
629 || uSrcHeight < this->ScreenSettings.Video.ulHeight)
630 {
631 RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
632 }
633
634 /* Calculate start offset in source and destination buffers. */
635 uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
636 uint32_t offDst = (destY * this->ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
637
638#ifdef VBOX_RECORDING_DUMP
639 BMPFILEHDR fileHdr;
640 RT_ZERO(fileHdr);
641
642 BMPWIN3XINFOHDR coreHdr;
643 RT_ZERO(coreHdr);
644
645 fileHdr.uType = BMP_HDR_MAGIC;
646 fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
647 fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
648
649 coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
650 coreHdr.uWidth = w;
651 coreHdr.uHeight = h;
652 coreHdr.cPlanes = 1;
653 coreHdr.cBits = uBPP;
654 coreHdr.uXPelsPerMeter = 5000;
655 coreHdr.uYPelsPerMeter = 5000;
656
657 char szFileName[RTPATH_MAX];
658 RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", this->uScreenID);
659
660 RTFILE fh;
661 int rc2 = RTFileOpen(&fh, szFileName,
662 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
663 if (RT_SUCCESS(rc2))
664 {
665 RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
666 RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
667 }
668#endif
669 Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
670
671 /* Do the copy. */
672 for (unsigned int i = 0; i < h; i++)
673 {
674 /* Overflow check. */
675 Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
676 Assert(offDst + w * uBytesPerPixel <= this->ScreenSettings.Video.ulHeight * this->ScreenSettings.Video.ulWidth * uBytesPerPixel);
677
678 memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
679
680#ifdef VBOX_RECORDING_DUMP
681 if (RT_SUCCESS(rc2))
682 RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
683#endif
684 offSrc += uBytesPerLine;
685 offDst += this->ScreenSettings.Video.ulWidth * uBytesPerPixel;
686 }
687
688#ifdef VBOX_RECORDING_DUMP
689 if (RT_SUCCESS(rc2))
690 RTFileClose(fh);
691#endif
692
693 } while (0);
694
695 if (rc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
696 {
697 RecordingBlock *pBlock = new RecordingBlock();
698 if (pBlock)
699 {
700 AssertPtr(pFrame);
701
702 pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
703 pBlock->pvData = pFrame;
704 pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
705
706 try
707 {
708 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
709 pRecordingBlocks->List.push_back(pBlock);
710
711 Assert(this->Blocks.Map.find(msTimestamp) == this->Blocks.Map.end());
712 this->Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
713 }
714 catch (const std::exception &ex)
715 {
716 RT_NOREF(ex);
717
718 delete pBlock;
719 rc = VERR_NO_MEMORY;
720 }
721 }
722 else
723 rc = VERR_NO_MEMORY;
724 }
725
726 if (RT_FAILURE(rc))
727 RecordingVideoFrameFree(pFrame);
728
729 unlock();
730
731 return rc;
732}
733
734/**
735 * Initializes a recording stream.
736 *
737 * @returns IPRT status code.
738 * @param a_pCtx Pointer to recording context.
739 * @param uScreen Screen number to use for this recording stream.
740 * @param Settings Recording screen configuration to use for initialization.
741 */
742int RecordingStream::Init(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
743{
744 return initInternal(a_pCtx, uScreen, Settings);
745}
746
747/**
748 * Initializes a recording stream, internal version.
749 *
750 * @returns IPRT status code.
751 * @param a_pCtx Pointer to recording context.
752 * @param uScreen Screen number to use for this recording stream.
753 * @param Settings Recording screen configuration to use for initialization.
754 */
755int RecordingStream::initInternal(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
756{
757 this->pCtx = a_pCtx;
758 this->uScreenID = uScreen;
759 this->ScreenSettings = Settings;
760
761 int rc = parseOptionsString(this->ScreenSettings.strOptions);
762 if (RT_FAILURE(rc))
763 return rc;
764
765 settings::RecordingScreenSettings *pSettings = &this->ScreenSettings;
766
767 rc = RTCritSectInit(&this->CritSect);
768 if (RT_FAILURE(rc))
769 return rc;
770
771 rc = open(this->ScreenSettings);
772 if (RT_FAILURE(rc))
773 return rc;
774
775 const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
776 const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
777
778 if (fVideoEnabled)
779 {
780 rc = initVideo();
781 if (RT_FAILURE(rc))
782 return rc;
783 }
784
785 if (fAudioEnabled)
786 {
787 rc = initAudio();
788 if (RT_FAILURE(rc))
789 return rc;
790 }
791
792 switch (this->ScreenSettings.enmDest)
793 {
794 case RecordingDestination_File:
795 {
796 Assert(pSettings->File.strName.isNotEmpty());
797 const char *pszFile = pSettings->File.strName.c_str();
798
799 AssertPtr(File.pWEBM);
800 rc = File.pWEBM->OpenEx(pszFile, &this->File.hFile,
801#ifdef VBOX_WITH_AUDIO_RECORDING
802 fAudioEnabled ? WebMWriter::AudioCodec_Opus : WebMWriter::AudioCodec_None,
803#else
804 WebMWriter::AudioCodec_None,
805#endif
806 fVideoEnabled ? WebMWriter::VideoCodec_VP8 : WebMWriter::VideoCodec_None);
807 if (RT_FAILURE(rc))
808 {
809 LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, rc));
810 break;
811 }
812
813 if (fVideoEnabled)
814 {
815 rc = this->File.pWEBM->AddVideoTrack(pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
816 &this->uTrackVideo);
817 if (RT_FAILURE(rc))
818 {
819 LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, rc));
820 break;
821 }
822
823 LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
824 this->uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
825 pSettings->Video.ulRate, pSettings->Video.ulFPS, this->uTrackVideo));
826 }
827
828#ifdef VBOX_WITH_AUDIO_RECORDING
829 if (fAudioEnabled)
830 {
831 rc = this->File.pWEBM->AddAudioTrack(pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
832 &this->uTrackAudio);
833 if (RT_FAILURE(rc))
834 {
835 LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, rc));
836 break;
837 }
838
839 LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
840 this->uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
841 pSettings->Audio.cChannels ? "channels" : "channel", this->uTrackAudio));
842 }
843#endif
844
845 if ( fVideoEnabled
846#ifdef VBOX_WITH_AUDIO_RECORDING
847 || fAudioEnabled
848#endif
849 )
850 {
851 char szWhat[32] = { 0 };
852 if (fVideoEnabled)
853 RTStrCat(szWhat, sizeof(szWhat), "video");
854#ifdef VBOX_WITH_AUDIO_RECORDING
855 if (fAudioEnabled)
856 {
857 if (fVideoEnabled)
858 RTStrCat(szWhat, sizeof(szWhat), " + ");
859 RTStrCat(szWhat, sizeof(szWhat), "audio");
860 }
861#endif
862 LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, this->uScreenID, pszFile));
863 }
864
865 break;
866 }
867
868 default:
869 AssertFailed(); /* Should never happen. */
870 rc = VERR_NOT_IMPLEMENTED;
871 break;
872 }
873
874 if (RT_SUCCESS(rc))
875 {
876 this->enmState = RECORDINGSTREAMSTATE_INITIALIZED;
877 this->fEnabled = true;
878 this->tsStartMs = RTTimeProgramMilliTS();
879 }
880 else
881 {
882 int rc2 = uninitInternal();
883 AssertRC(rc2);
884 return rc;
885 }
886
887 return VINF_SUCCESS;
888}
889
890/**
891 * Closes a recording stream.
892 * Depending on the stream's recording destination, this function closes all associated handles
893 * and finalizes recording.
894 *
895 * @returns IPRT status code.
896 */
897int RecordingStream::close(void)
898{
899 int rc = VINF_SUCCESS;
900
901 switch (this->ScreenSettings.enmDest)
902 {
903 case RecordingDestination_File:
904 {
905 if (this->File.pWEBM)
906 rc = this->File.pWEBM->Close();
907 break;
908 }
909
910 default:
911 AssertFailed(); /* Should never happen. */
912 break;
913 }
914
915 this->Blocks.Clear();
916
917 LogRel(("Recording: Recording screen #%u stopped\n", this->uScreenID));
918
919 if (RT_FAILURE(rc))
920 {
921 LogRel(("Recording: Error stopping recording screen #%u, rc=%Rrc\n", this->uScreenID, rc));
922 return rc;
923 }
924
925 switch (this->ScreenSettings.enmDest)
926 {
927 case RecordingDestination_File:
928 {
929 if (RTFileIsValid(this->File.hFile))
930 {
931 rc = RTFileClose(this->File.hFile);
932 if (RT_SUCCESS(rc))
933 {
934 LogRel(("Recording: Closed file '%s'\n", this->ScreenSettings.File.strName.c_str()));
935 }
936 else
937 {
938 LogRel(("Recording: Error closing file '%s', rc=%Rrc\n", this->ScreenSettings.File.strName.c_str(), rc));
939 break;
940 }
941 }
942
943 if (this->File.pWEBM)
944 {
945 delete this->File.pWEBM;
946 this->File.pWEBM = NULL;
947 }
948 break;
949 }
950
951 default:
952 rc = VERR_NOT_IMPLEMENTED;
953 break;
954 }
955
956 LogFlowFuncLeaveRC(rc);
957 return rc;
958}
959
960/**
961 * Uninitializes a recording stream.
962 *
963 * @returns IPRT status code.
964 */
965int RecordingStream::Uninit(void)
966{
967 return uninitInternal();
968}
969
970/**
971 * Uninitializes a recording stream, internal version.
972 *
973 * @returns IPRT status code.
974 */
975int RecordingStream::uninitInternal(void)
976{
977 if (this->enmState != RECORDINGSTREAMSTATE_INITIALIZED)
978 return VINF_SUCCESS;
979
980 int rc = close();
981 if (RT_FAILURE(rc))
982 return rc;
983
984 if (this->ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
985 {
986 int rc2 = unitVideo();
987 if (RT_SUCCESS(rc))
988 rc = rc2;
989 }
990
991 RTCritSectDelete(&this->CritSect);
992
993 this->enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
994 this->fEnabled = false;
995
996 return rc;
997}
998
999/**
1000 * Uninitializes video recording for a recording stream.
1001 *
1002 * @returns IPRT status code.
1003 */
1004int RecordingStream::unitVideo(void)
1005{
1006#ifdef VBOX_WITH_LIBVPX
1007 /* At the moment we only have VPX. */
1008 return uninitVideoVPX();
1009#else
1010 return VERR_NOT_SUPPORTED;
1011#endif
1012}
1013
1014#ifdef VBOX_WITH_LIBVPX
1015/**
1016 * Uninitializes the VPX codec for a recording stream.
1017 *
1018 * @returns IPRT status code.
1019 */
1020int RecordingStream::uninitVideoVPX(void)
1021{
1022 PRECORDINGVIDEOCODEC pCodec = &this->Video.Codec;
1023 vpx_img_free(&pCodec->VPX.RawImage);
1024 pCodec->VPX.pu8YuvBuf = NULL; /* Was pointing to VPX.RawImage. */
1025
1026 vpx_codec_err_t rcv = vpx_codec_destroy(&this->Video.Codec.VPX.Ctx);
1027 Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
1028
1029 return VINF_SUCCESS;
1030}
1031#endif
1032
1033/**
1034 * Initializes the video recording for a recording stream.
1035 *
1036 * @returns IPRT status code.
1037 */
1038int RecordingStream::initVideo(void)
1039{
1040 /* Sanity. */
1041 AssertReturn(this->ScreenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
1042 AssertReturn(this->ScreenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
1043 AssertReturn(this->ScreenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
1044 AssertReturn(this->ScreenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
1045
1046 this->Video.cFailedEncodingFrames = 0;
1047 this->Video.uLastTimeStampMs = 0;
1048 this->Video.uDelayMs = RT_MS_1SEC / this->ScreenSettings.Video.ulFPS;
1049
1050 int rc;
1051
1052#ifdef VBOX_WITH_LIBVPX
1053 /* At the moment we only have VPX. */
1054 rc = initVideoVPX();
1055#else
1056 rc = VERR_NOT_SUPPORTED;
1057#endif
1058
1059 if (RT_FAILURE(rc))
1060 LogRel(("Recording: Failed to initialize video encoding (%Rrc)\n", rc));
1061
1062 return rc;
1063}
1064
1065#ifdef VBOX_WITH_LIBVPX
1066/**
1067 * Initializes the VPX codec for a recording stream.
1068 *
1069 * @returns IPRT status code.
1070 */
1071int RecordingStream::initVideoVPX(void)
1072{
1073# ifdef VBOX_WITH_LIBVPX_VP9
1074 vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx();
1075# else /* Default is using VP8. */
1076 vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx();
1077# endif
1078
1079 PRECORDINGVIDEOCODEC pCodec = &this->Video.Codec;
1080
1081 vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pCodec->VPX.Cfg, 0 /* Reserved */);
1082 if (rcv != VPX_CODEC_OK)
1083 {
1084 LogRel(("Recording: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
1085 return VERR_RECORDING_CODEC_INIT_FAILED;
1086 }
1087
1088 /* Target bitrate in kilobits per second. */
1089 pCodec->VPX.Cfg.rc_target_bitrate = this->ScreenSettings.Video.ulRate;
1090 /* Frame width. */
1091 pCodec->VPX.Cfg.g_w = this->ScreenSettings.Video.ulWidth;
1092 /* Frame height. */
1093 pCodec->VPX.Cfg.g_h = this->ScreenSettings.Video.ulHeight;
1094 /* 1ms per frame. */
1095 pCodec->VPX.Cfg.g_timebase.num = 1;
1096 pCodec->VPX.Cfg.g_timebase.den = 1000;
1097 /* Disable multithreading. */
1098 pCodec->VPX.Cfg.g_threads = 0;
1099
1100 /* Initialize codec. */
1101 rcv = vpx_codec_enc_init(&pCodec->VPX.Ctx, pCodecIface, &pCodec->VPX.Cfg, 0 /* Flags */);
1102 if (rcv != VPX_CODEC_OK)
1103 {
1104 LogRel(("Recording: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
1105 return VERR_RECORDING_CODEC_INIT_FAILED;
1106 }
1107
1108 if (!vpx_img_alloc(&pCodec->VPX.RawImage, VPX_IMG_FMT_I420,
1109 this->ScreenSettings.Video.ulWidth, this->ScreenSettings.Video.ulHeight, 1))
1110 {
1111 LogRel(("Recording: Failed to allocate image %RU32x%RU32\n",
1112 this->ScreenSettings.Video.ulWidth, this->ScreenSettings.Video.ulHeight));
1113 return VERR_NO_MEMORY;
1114 }
1115
1116 /* Save a pointer to the first raw YUV plane. */
1117 pCodec->VPX.pu8YuvBuf = pCodec->VPX.RawImage.planes[0];
1118
1119 return VINF_SUCCESS;
1120}
1121#endif
1122
1123/**
1124 * Initializes the audio part of a recording stream,
1125 *
1126 * @returns IPRT status code.
1127 */
1128int RecordingStream::initAudio(void)
1129{
1130#ifdef VBOX_WITH_AUDIO_RECORDING
1131 if (this->ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
1132 {
1133 /* Sanity. */
1134 AssertReturn(this->ScreenSettings.Audio.uHz, VERR_INVALID_PARAMETER);
1135 AssertReturn(this->ScreenSettings.Audio.cBits, VERR_INVALID_PARAMETER);
1136 AssertReturn(this->ScreenSettings.Audio.cChannels, VERR_INVALID_PARAMETER);
1137 }
1138#endif
1139
1140 return VINF_SUCCESS;
1141}
1142
1143#ifdef VBOX_WITH_LIBVPX
1144/**
1145 * Encodes the source image and write the encoded image to the stream's destination.
1146 *
1147 * @returns IPRT status code.
1148 * @param msTimestamp Absolute timestamp (PTS) of frame (in ms) to encode.
1149 * @param pFrame Frame to encode and submit.
1150 */
1151int RecordingStream::writeVideoVPX(uint64_t msTimestamp, PRECORDINGVIDEOFRAME pFrame)
1152{
1153 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
1154
1155 int rc;
1156
1157 PRECORDINGVIDEOCODEC pCodec = &this->Video.Codec;
1158
1159 /* Presentation TimeStamp (PTS). */
1160 vpx_codec_pts_t pts = msTimestamp;
1161 vpx_codec_err_t rcv = vpx_codec_encode(&pCodec->VPX.Ctx,
1162 &pCodec->VPX.RawImage,
1163 pts /* Timestamp */,
1164 this->Video.uDelayMs /* How long to show this frame */,
1165 0 /* Flags */,
1166 pCodec->VPX.uEncoderDeadline /* Quality setting */);
1167 if (rcv != VPX_CODEC_OK)
1168 {
1169 if (this->Video.cFailedEncodingFrames++ < 64) /** @todo Make this configurable. */
1170 {
1171 LogRel(("Recording: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
1172 return VERR_GENERAL_FAILURE;
1173 }
1174 }
1175
1176 this->Video.cFailedEncodingFrames = 0;
1177
1178 vpx_codec_iter_t iter = NULL;
1179 rc = VERR_NO_DATA;
1180 for (;;)
1181 {
1182 const vpx_codec_cx_pkt_t *pPacket = vpx_codec_get_cx_data(&pCodec->VPX.Ctx, &iter);
1183 if (!pPacket)
1184 break;
1185
1186 switch (pPacket->kind)
1187 {
1188 case VPX_CODEC_CX_FRAME_PKT:
1189 {
1190 WebMWriter::BlockData_VP8 blockData = { &pCodec->VPX.Cfg, pPacket };
1191 rc = this->File.pWEBM->WriteBlock(this->uTrackVideo, &blockData, sizeof(blockData));
1192 break;
1193 }
1194
1195 default:
1196 AssertFailed();
1197 LogFunc(("Unexpected video packet type %ld\n", pPacket->kind));
1198 break;
1199 }
1200 }
1201
1202 return rc;
1203}
1204#endif /* VBOX_WITH_LIBVPX */
1205
1206/**
1207 * Locks a recording stream.
1208 */
1209void RecordingStream::lock(void)
1210{
1211 int rc = RTCritSectEnter(&CritSect);
1212 AssertRC(rc);
1213}
1214
1215/**
1216 * Unlocks a locked recording stream.
1217 */
1218void RecordingStream::unlock(void)
1219{
1220 int rc = RTCritSectLeave(&CritSect);
1221 AssertRC(rc);
1222}
1223
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use