VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/VideoRecStream.cpp@ 75307

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

Recording: Bugfixes for Main and FE/Qt.

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