VirtualBox

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

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

Copyright year updates by scm.

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