VirtualBox

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

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

Recording/Main: Simplified code a bit; use the stream settings file name instead of having another file name defined.

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