VirtualBox

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

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

scm --update-copyright-year

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