VirtualBox

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

Last change on this file since 75393 was 75393, checked in by vboxsync, 7 years ago

Recording/Main: Renaming, docs.

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

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