VirtualBox

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

Last change on this file since 75344 was 75344, checked in by vboxsync, 6 years ago

Recording/Main: More file renaming.

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

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