VirtualBox

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

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

Recording: Renamed VERR_AVREC_XXX error codes to VERR_RECORDING_XXX. Added a few more.

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