VirtualBox

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

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

Capturing: Build fix.

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