VirtualBox

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

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

Recording/Main: Logging.

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