VirtualBox

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

Last change on this file since 82422 was 82422, checked in by vboxsync, 5 years ago

Main/Recording: Cleanup; greatly reduced the include maze.

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

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