VirtualBox

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

Last change on this file since 96229 was 96229, checked in by vboxsync, 2 years ago

Recording/Main: Decoupled the WebM writer class from codec dependencies. Various bugfixes. bugref:10275

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.9 KB
Line 
1/* $Id: RecordingStream.cpp 96229 2022-08-16 15:41:39Z vboxsync $ */
2/** @file
3 * Recording stream code.
4 */
5
6/*
7 * Copyright (C) 2012-2022 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_RECORDING
22#include "LoggingNew.h"
23
24#include <iprt/path.h>
25
26#ifdef VBOX_RECORDING_DUMP
27# include <iprt/formats/bmp.h>
28#endif
29
30#ifdef VBOX_WITH_AUDIO_RECORDING
31# include <VBox/vmm/pdmaudioinline.h>
32#endif
33
34#include "Recording.h"
35#include "RecordingUtils.h"
36#include "WebMWriter.h"
37
38
39RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
40 : enmState(RECORDINGSTREAMSTATE_UNINITIALIZED)
41{
42 int vrc2 = initInternal(a_pCtx, uScreen, Settings);
43 if (RT_FAILURE(vrc2))
44 throw vrc2;
45}
46
47RecordingStream::~RecordingStream(void)
48{
49 int vrc2 = uninitInternal();
50 AssertRC(vrc2);
51}
52
53/**
54 * Opens a recording stream.
55 *
56 * @returns IPRT status code.
57 * @param screenSettings Recording settings to use.
58 */
59int RecordingStream::open(const settings::RecordingScreenSettings &screenSettings)
60{
61 /* Sanity. */
62 Assert(screenSettings.enmDest != RecordingDestination_None);
63
64 int vrc;
65
66 switch (screenSettings.enmDest)
67 {
68 case RecordingDestination_File:
69 {
70 Assert(screenSettings.File.strName.isNotEmpty());
71
72 char *pszAbsPath = RTPathAbsDup(screenSettings.File.strName.c_str());
73 AssertPtrReturn(pszAbsPath, VERR_NO_MEMORY);
74
75 RTPathStripSuffix(pszAbsPath);
76
77 char *pszSuff = RTStrDup(".webm");
78 if (!pszSuff)
79 {
80 RTStrFree(pszAbsPath);
81 vrc = VERR_NO_MEMORY;
82 break;
83 }
84
85 char *pszFile = NULL;
86
87 vrc = RTStrAPrintf(&pszFile, "%s-%u%s", pszAbsPath, this->uScreenID, pszSuff);
88 if (RT_SUCCESS(vrc))
89 {
90#ifdef DEBUG_andy
91 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE;
92#else
93 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
94
95 /* Play safe: the file must not exist, overwriting is potentially
96 * hazardous as nothing prevents the user from picking a file name of some
97 * other important file, causing unintentional data loss. */
98 fOpen |= RTFILE_O_CREATE;
99#endif
100 RTFILE hFile;
101 vrc = RTFileOpen(&hFile, pszFile, fOpen);
102 if (vrc == VERR_ALREADY_EXISTS)
103 {
104 RTStrFree(pszFile);
105 pszFile = NULL;
106
107 RTTIMESPEC ts;
108 RTTimeNow(&ts);
109 RTTIME time;
110 RTTimeExplode(&time, &ts);
111
112 vrc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ-%u%s",
113 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
114 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
115 this->uScreenID, pszSuff);
116 if (RT_SUCCESS(vrc))
117 vrc = RTFileOpen(&hFile, pszFile, fOpen);
118 }
119
120 if (RT_FAILURE(vrc))
121 break;
122
123 LogRel2(("Recording: Opened file '%s'\n", pszFile));
124
125 try
126 {
127 Assert(File.pWEBM == NULL);
128 File.pWEBM = new WebMWriter();
129 }
130 catch (std::bad_alloc &)
131 {
132 vrc = VERR_NO_MEMORY;
133 }
134
135 if (RT_SUCCESS(vrc))
136 {
137 this->File.hFile = hFile;
138 this->ScreenSettings.File.strName = pszFile;
139 }
140 }
141
142 RTStrFree(pszSuff);
143 RTStrFree(pszAbsPath);
144
145 if (RT_FAILURE(vrc))
146 {
147 LogRel(("Recording: Failed to open file '%s' for screen %RU32, vrc=%Rrc\n",
148 pszFile ? pszFile : "<Unnamed>", this->uScreenID, vrc));
149 }
150
151 RTStrFree(pszFile);
152 break;
153 }
154
155 default:
156 vrc = VERR_NOT_IMPLEMENTED;
157 break;
158 }
159
160 LogFlowFuncLeaveRC(vrc);
161 return vrc;
162}
163
164/**
165 * Returns the recording stream's used configuration.
166 *
167 * @returns The recording stream's used configuration.
168 */
169const settings::RecordingScreenSettings &RecordingStream::GetConfig(void) const
170{
171 return this->ScreenSettings;
172}
173
174/**
175 * Checks if a specified limit for a recording stream has been reached, internal version.
176 *
177 * @returns true if any limit has been reached.
178 * @param msTimestamp Timestamp (in ms) to check for.
179 */
180bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const
181{
182 LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n",
183 msTimestamp, this->ScreenSettings.ulMaxTimeS, this->tsStartMs));
184
185 if ( this->ScreenSettings.ulMaxTimeS
186 && msTimestamp >= this->tsStartMs + (this->ScreenSettings.ulMaxTimeS * RT_MS_1SEC))
187 {
188 LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n",
189 this->uScreenID, this->ScreenSettings.ulMaxTimeS));
190 return true;
191 }
192
193 if (this->ScreenSettings.enmDest == RecordingDestination_File)
194 {
195 if (this->ScreenSettings.File.ulMaxSizeMB)
196 {
197 uint64_t sizeInMB = this->File.pWEBM->GetFileSize() / _1M;
198 if(sizeInMB >= this->ScreenSettings.File.ulMaxSizeMB)
199 {
200 LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n",
201 this->uScreenID, this->ScreenSettings.File.ulMaxSizeMB));
202 return true;
203 }
204 }
205
206 /* Check for available free disk space */
207 if ( this->File.pWEBM
208 && this->File.pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
209 {
210 LogRel(("Recording: Not enough free storage space available, stopping recording\n"));
211 return true;
212 }
213 }
214
215 return false;
216}
217
218/**
219 * Internal iteration main loop.
220 * Does housekeeping and recording context notification.
221 *
222 * @returns IPRT status code.
223 * @param msTimestamp Current timestamp (in ms).
224 */
225int RecordingStream::iterateInternal(uint64_t msTimestamp)
226{
227 if (!this->fEnabled)
228 return VINF_SUCCESS;
229
230 int vrc;
231
232 if (isLimitReachedInternal(msTimestamp))
233 {
234 vrc = VINF_RECORDING_LIMIT_REACHED;
235 }
236 else
237 vrc = VINF_SUCCESS;
238
239 AssertPtr(this->m_pCtx);
240
241 switch (vrc)
242 {
243 case VINF_RECORDING_LIMIT_REACHED:
244 {
245 this->fEnabled = false;
246
247 int vrc2 = this->m_pCtx->OnLimitReached(this->uScreenID, VINF_SUCCESS /* rc */);
248 AssertRC(vrc2);
249 break;
250 }
251
252 default:
253 break;
254 }
255
256 LogFlowFuncLeaveRC(vrc);
257 return vrc;
258}
259
260/**
261 * Checks if a specified limit for a recording stream has been reached.
262 *
263 * @returns true if any limit has been reached.
264 * @param msTimestamp Timestamp (in ms) to check for.
265 */
266bool RecordingStream::IsLimitReached(uint64_t msTimestamp) const
267{
268 if (!IsReady())
269 return true;
270
271 return isLimitReachedInternal(msTimestamp);
272}
273
274/**
275 * Returns whether a recording stream is ready (e.g. enabled and active) or not.
276 *
277 * @returns \c true if ready, \c false if not.
278 */
279bool RecordingStream::IsReady(void) const
280{
281 return this->fEnabled;
282}
283
284/**
285 * Processes a recording stream.
286 * This function takes care of the actual encoding and writing of a certain stream.
287 * As this can be very CPU intensive, this function usually is called from a separate thread.
288 *
289 * @returns IPRT status code.
290 * @param mapBlocksCommon Map of common block to process for this stream.
291 *
292 * @note Runs in recording thread.
293 */
294int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
295{
296 LogFlowFuncEnter();
297
298 lock();
299
300 if (!this->ScreenSettings.fEnabled)
301 {
302 unlock();
303 return VINF_SUCCESS;
304 }
305
306 int vrc = VINF_SUCCESS;
307
308 RecordingBlockMap::iterator itStreamBlocks = Blocks.Map.begin();
309 while (itStreamBlocks != Blocks.Map.end())
310 {
311 uint64_t const msTimestamp = itStreamBlocks->first;
312 RecordingBlocks *pBlocks = itStreamBlocks->second;
313
314 AssertPtr(pBlocks);
315
316 while (!pBlocks->List.empty())
317 {
318 RecordingBlock *pBlock = pBlocks->List.front();
319 AssertPtr(pBlock);
320
321 if (pBlock->enmType == RECORDINGBLOCKTYPE_VIDEO)
322 {
323 RECORDINGFRAME Frame;
324 Frame.VideoPtr = (PRECORDINGVIDEOFRAME)pBlock->pvData;
325 Frame.msTimestamp = msTimestamp;
326
327 int vrc2 = recordingCodecEncode(&this->CodecVideo, &Frame, NULL, NULL);
328 AssertRC(vrc2);
329 if (RT_SUCCESS(vrc))
330 vrc = vrc2;
331 }
332
333 pBlocks->List.pop_front();
334 delete pBlock;
335 }
336
337 Assert(pBlocks->List.empty());
338 delete pBlocks;
339
340 Blocks.Map.erase(itStreamBlocks);
341 itStreamBlocks = Blocks.Map.begin();
342 }
343
344#ifdef VBOX_WITH_AUDIO_RECORDING
345 /* Do we need to multiplex the common audio data to this stream? */
346 if (this->ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
347 {
348 /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
349 * written to the screen's assigned recording stream. */
350 RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin();
351 while (itCommonBlocks != mapBlocksCommon.end())
352 {
353 RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
354 while (itBlock != itCommonBlocks->second->List.end())
355 {
356 RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
357 switch (pBlockCommon->enmType)
358 {
359 case RECORDINGBLOCKTYPE_AUDIO:
360 {
361 PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
362 AssertPtr(pAudioFrame);
363 AssertPtr(pAudioFrame->pvBuf);
364 Assert(pAudioFrame->cbBuf);
365
366 AssertPtr(this->File.pWEBM);
367 int vrc2 = this->File.pWEBM->WriteBlock(this->uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags);
368 AssertRC(vrc2);
369 if (RT_SUCCESS(vrc))
370 vrc = vrc2;
371 break;
372 }
373
374 default:
375 AssertFailed();
376 break;
377 }
378
379 Assert(pBlockCommon->cRefs);
380 pBlockCommon->cRefs--;
381 if (pBlockCommon->cRefs == 0)
382 {
383 itCommonBlocks->second->List.erase(itBlock);
384 delete pBlockCommon;
385 itBlock = itCommonBlocks->second->List.begin();
386 }
387 else
388 ++itBlock;
389 }
390
391 /* If no entries are left over in the block map, remove it altogether. */
392 if (itCommonBlocks->second->List.empty())
393 {
394 delete itCommonBlocks->second;
395 mapBlocksCommon.erase(itCommonBlocks);
396 itCommonBlocks = mapBlocksCommon.begin();
397 }
398 else
399 ++itCommonBlocks;
400
401 LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
402 }
403 }
404#else
405 RT_NOREF(mapBlocksCommon);
406#endif /* VBOX_WITH_AUDIO_RECORDING */
407
408 unlock();
409
410 LogFlowFuncLeaveRC(vrc);
411 return vrc;
412}
413
414/**
415 * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
416 *
417 * @returns IPRT status code. Will return VINF_RECORDING_LIMIT_REACHED if the stream's recording
418 * limit has been reached or VINF_RECORDING_THROTTLED if the frame is too early for the current
419 * FPS setting.
420 * @param x Upper left (X) coordinate where the video frame starts.
421 * @param y Upper left (Y) coordinate where the video frame starts.
422 * @param uPixelFormat Pixel format of the video frame.
423 * @param uBPP Bits per pixel (BPP) of the video frame.
424 * @param uBytesPerLine Bytes per line of the video frame.
425 * @param uSrcWidth Width (in pixels) of the video frame.
426 * @param uSrcHeight Height (in pixels) of the video frame.
427 * @param puSrcData Actual pixel data of the video frame.
428 * @param msTimestamp Absolute PTS timestamp (in ms).
429 */
430int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
431 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
432{
433 lock();
434
435 LogFlowFunc(("tsAbsPTSMs=%RU64\n", msTimestamp));
436
437 PRECORDINGCODEC pCodec = &this->CodecVideo;
438
439 PRECORDINGVIDEOFRAME pFrame = NULL;
440
441 int vrc = iterateInternal(msTimestamp);
442 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
443 {
444 unlock();
445 return vrc;
446 }
447
448 do
449 {
450 if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.Video.uDelayMs)
451 {
452 vrc = VINF_RECORDING_THROTTLED; /* Respect maximum frames per second. */
453 break;
454 }
455
456 pCodec->State.tsLastWrittenMs = msTimestamp;
457
458 int xDiff = ((int)this->ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
459 uint32_t w = uSrcWidth;
460 if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
461 {
462 vrc = VERR_INVALID_PARAMETER;
463 break;
464 }
465
466 uint32_t destX;
467 if ((int)x < -xDiff)
468 {
469 w += xDiff + x;
470 x = -xDiff;
471 destX = 0;
472 }
473 else
474 destX = x + xDiff;
475
476 uint32_t h = uSrcHeight;
477 int yDiff = ((int)this->ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
478 if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
479 {
480 vrc = VERR_INVALID_PARAMETER;
481 break;
482 }
483
484 uint32_t destY;
485 if ((int)y < -yDiff)
486 {
487 h += yDiff + (int)y;
488 y = -yDiff;
489 destY = 0;
490 }
491 else
492 destY = y + yDiff;
493
494 if ( destX > this->ScreenSettings.Video.ulWidth
495 || destY > this->ScreenSettings.Video.ulHeight)
496 {
497 vrc = VERR_INVALID_PARAMETER; /* Nothing visible. */
498 break;
499 }
500
501 if (destX + w > this->ScreenSettings.Video.ulWidth)
502 w = this->ScreenSettings.Video.ulWidth - destX;
503
504 if (destY + h > this->ScreenSettings.Video.ulHeight)
505 h = this->ScreenSettings.Video.ulHeight - destY;
506
507 pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
508 AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY);
509
510 /* Calculate bytes per pixel and set pixel format. */
511 const unsigned uBytesPerPixel = uBPP / 8;
512 if (uPixelFormat == BitmapFormat_BGR)
513 {
514 switch (uBPP)
515 {
516 case 32:
517 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB32;
518 break;
519 case 24:
520 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB24;
521 break;
522 case 16:
523 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB565;
524 break;
525 default:
526 AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED);
527 break;
528 }
529 }
530 else
531 AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED);
532
533 const size_t cbRGBBuf = this->ScreenSettings.Video.ulWidth
534 * this->ScreenSettings.Video.ulHeight
535 * uBytesPerPixel;
536 AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER);
537
538 pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
539 AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY);
540 pFrame->cbRGBBuf = cbRGBBuf;
541 pFrame->uWidth = uSrcWidth;
542 pFrame->uHeight = uSrcHeight;
543
544 /* If the current video frame is smaller than video resolution we're going to encode,
545 * clear the frame beforehand to prevent artifacts. */
546 if ( uSrcWidth < this->ScreenSettings.Video.ulWidth
547 || uSrcHeight < this->ScreenSettings.Video.ulHeight)
548 {
549 RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
550 }
551
552 /* Calculate start offset in source and destination buffers. */
553 uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
554 uint32_t offDst = (destY * this->ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
555
556#ifdef VBOX_RECORDING_DUMP
557 BMPFILEHDR fileHdr;
558 RT_ZERO(fileHdr);
559
560 BMPWIN3XINFOHDR coreHdr;
561 RT_ZERO(coreHdr);
562
563 fileHdr.uType = BMP_HDR_MAGIC;
564 fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
565 fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
566
567 coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
568 coreHdr.uWidth = w;
569 coreHdr.uHeight = h;
570 coreHdr.cPlanes = 1;
571 coreHdr.cBits = uBPP;
572 coreHdr.uXPelsPerMeter = 5000;
573 coreHdr.uYPelsPerMeter = 5000;
574
575 char szFileName[RTPATH_MAX];
576 RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", this->uScreenID);
577
578 RTFILE fh;
579 int vrc2 = RTFileOpen(&fh, szFileName,
580 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
581 if (RT_SUCCESS(vrc2))
582 {
583 RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
584 RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
585 }
586#endif
587 Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
588
589 /* Do the copy. */
590 for (unsigned int i = 0; i < h; i++)
591 {
592 /* Overflow check. */
593 Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
594 Assert(offDst + w * uBytesPerPixel <= this->ScreenSettings.Video.ulHeight * this->ScreenSettings.Video.ulWidth * uBytesPerPixel);
595
596 memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
597
598#ifdef VBOX_RECORDING_DUMP
599 if (RT_SUCCESS(rc2))
600 RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
601#endif
602 offSrc += uBytesPerLine;
603 offDst += this->ScreenSettings.Video.ulWidth * uBytesPerPixel;
604 }
605
606#ifdef VBOX_RECORDING_DUMP
607 if (RT_SUCCESS(vrc2))
608 RTFileClose(fh);
609#endif
610
611 } while (0);
612
613 if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
614 {
615 RecordingBlock *pBlock = new RecordingBlock();
616 if (pBlock)
617 {
618 AssertPtr(pFrame);
619
620 pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
621 pBlock->pvData = pFrame;
622 pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
623
624 try
625 {
626 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
627 pRecordingBlocks->List.push_back(pBlock);
628
629 Assert(this->Blocks.Map.find(msTimestamp) == this->Blocks.Map.end());
630 this->Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
631 }
632 catch (const std::exception &ex)
633 {
634 RT_NOREF(ex);
635
636 delete pBlock;
637 vrc = VERR_NO_MEMORY;
638 }
639 }
640 else
641 vrc = VERR_NO_MEMORY;
642 }
643
644 if (RT_FAILURE(vrc))
645 RecordingVideoFrameFree(pFrame);
646
647 unlock();
648
649 return vrc;
650}
651
652/**
653 * Initializes a recording stream.
654 *
655 * @returns IPRT status code.
656 * @param pCtx Pointer to recording context.
657 * @param uScreen Screen number to use for this recording stream.
658 * @param Settings Recording screen configuration to use for initialization.
659 */
660int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
661{
662 return initInternal(pCtx, uScreen, Settings);
663}
664
665/**
666 * Initializes a recording stream, internal version.
667 *
668 * @returns IPRT status code.
669 * @param pCtx Pointer to recording context.
670 * @param uScreen Screen number to use for this recording stream.
671 * @param screenSettings Recording screen configuration to use for initialization.
672 */
673int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
674 const settings::RecordingScreenSettings &screenSettings)
675{
676 AssertReturn(enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
677
678 this->m_pCtx = pCtx;
679 this->uTrackAudio = UINT8_MAX;
680 this->uTrackVideo = UINT8_MAX;
681 this->tsStartMs = 0;
682 this->uScreenID = uScreen;
683#ifdef VBOX_WITH_AUDIO_RECORDING
684 /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
685 this->pCodecAudio = m_pCtx->GetCodecAudio();
686#endif
687 this->ScreenSettings = screenSettings;
688
689 settings::RecordingScreenSettings *pSettings = &this->ScreenSettings;
690
691 int vrc = RTCritSectInit(&this->CritSect);
692 if (RT_FAILURE(vrc))
693 return vrc;
694
695 this->File.pWEBM = NULL;
696 this->File.hFile = NIL_RTFILE;
697
698 vrc = open(*pSettings);
699 if (RT_FAILURE(vrc))
700 return vrc;
701
702 const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
703 const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
704
705 if (fVideoEnabled)
706 {
707 vrc = initVideo(*pSettings);
708 if (RT_FAILURE(vrc))
709 return vrc;
710 }
711
712 switch (pSettings->enmDest)
713 {
714 case RecordingDestination_File:
715 {
716 Assert(pSettings->File.strName.isNotEmpty());
717 const char *pszFile = pSettings->File.strName.c_str();
718
719 AssertPtr(File.pWEBM);
720 vrc = File.pWEBM->OpenEx(pszFile, &this->File.hFile,
721#ifdef VBOX_WITH_AUDIO_RECORDING
722 fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
723#else
724 RecordingAudioCodec_None,
725#endif
726 fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
727 if (RT_FAILURE(vrc))
728 {
729 LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
730 break;
731 }
732
733 if (fVideoEnabled)
734 {
735 vrc = this->File.pWEBM->AddVideoTrack(&this->CodecVideo,
736 pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
737 &this->uTrackVideo);
738 if (RT_FAILURE(vrc))
739 {
740 LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
741 break;
742 }
743
744 LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
745 this->uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
746 pSettings->Video.ulRate, pSettings->Video.ulFPS, this->uTrackVideo));
747 }
748
749#ifdef VBOX_WITH_AUDIO_RECORDING
750 if (fAudioEnabled)
751 {
752 AssertPtr(this->pCodecAudio);
753 vrc = this->File.pWEBM->AddAudioTrack(this->pCodecAudio,
754 pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
755 &this->uTrackAudio);
756 if (RT_FAILURE(vrc))
757 {
758 LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
759 break;
760 }
761
762 LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
763 this->uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
764 pSettings->Audio.cChannels ? "channels" : "channel", this->uTrackAudio));
765 }
766#endif
767
768 if ( fVideoEnabled
769#ifdef VBOX_WITH_AUDIO_RECORDING
770 || fAudioEnabled
771#endif
772 )
773 {
774 char szWhat[32] = { 0 };
775 if (fVideoEnabled)
776 RTStrCat(szWhat, sizeof(szWhat), "video");
777#ifdef VBOX_WITH_AUDIO_RECORDING
778 if (fAudioEnabled)
779 {
780 if (fVideoEnabled)
781 RTStrCat(szWhat, sizeof(szWhat), " + ");
782 RTStrCat(szWhat, sizeof(szWhat), "audio");
783 }
784#endif
785 LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, this->uScreenID, pszFile));
786 }
787
788 break;
789 }
790
791 default:
792 AssertFailed(); /* Should never happen. */
793 vrc = VERR_NOT_IMPLEMENTED;
794 break;
795 }
796
797 if (RT_SUCCESS(vrc))
798 {
799 this->enmState = RECORDINGSTREAMSTATE_INITIALIZED;
800 this->fEnabled = true;
801 this->tsStartMs = RTTimeProgramMilliTS();
802
803 return VINF_SUCCESS;
804 }
805
806 int vrc2 = uninitInternal();
807 AssertRC(vrc2);
808
809 LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
810 return vrc;
811}
812
813/**
814 * Closes a recording stream.
815 * Depending on the stream's recording destination, this function closes all associated handles
816 * and finalizes recording.
817 *
818 * @returns IPRT status code.
819 */
820int RecordingStream::close(void)
821{
822 int vrc = VINF_SUCCESS;
823
824 switch (this->ScreenSettings.enmDest)
825 {
826 case RecordingDestination_File:
827 {
828 if (this->File.pWEBM)
829 vrc = this->File.pWEBM->Close();
830 break;
831 }
832
833 default:
834 AssertFailed(); /* Should never happen. */
835 break;
836 }
837
838 this->Blocks.Clear();
839
840 LogRel(("Recording: Recording screen #%u stopped\n", this->uScreenID));
841
842 if (RT_FAILURE(vrc))
843 {
844 LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", this->uScreenID, vrc));
845 return vrc;
846 }
847
848 switch (this->ScreenSettings.enmDest)
849 {
850 case RecordingDestination_File:
851 {
852 if (RTFileIsValid(this->File.hFile))
853 {
854 vrc = RTFileClose(this->File.hFile);
855 if (RT_SUCCESS(vrc))
856 {
857 LogRel(("Recording: Closed file '%s'\n", this->ScreenSettings.File.strName.c_str()));
858 }
859 else
860 {
861 LogRel(("Recording: Error closing file '%s', rc=%Rrc\n", this->ScreenSettings.File.strName.c_str(), vrc));
862 break;
863 }
864 }
865
866 WebMWriter *pWebMWriter = this->File.pWEBM;
867 AssertPtr(pWebMWriter);
868
869 if (pWebMWriter)
870 {
871 /* If no clusters (= data) was written, delete the file again. */
872 if (pWebMWriter->GetClusters() == 0)
873 {
874 int vrc2 = RTFileDelete(this->ScreenSettings.File.strName.c_str());
875 AssertRC(vrc2); /* Ignore rc on non-debug builds. */
876 }
877
878 delete pWebMWriter;
879 pWebMWriter = NULL;
880
881 this->File.pWEBM = NULL;
882 }
883 break;
884 }
885
886 default:
887 vrc = VERR_NOT_IMPLEMENTED;
888 break;
889 }
890
891 LogFlowFuncLeaveRC(vrc);
892 return vrc;
893}
894
895/**
896 * Uninitializes a recording stream.
897 *
898 * @returns IPRT status code.
899 */
900int RecordingStream::Uninit(void)
901{
902 return uninitInternal();
903}
904
905/**
906 * Uninitializes a recording stream, internal version.
907 *
908 * @returns IPRT status code.
909 */
910int RecordingStream::uninitInternal(void)
911{
912 if (this->enmState != RECORDINGSTREAMSTATE_INITIALIZED)
913 return VINF_SUCCESS;
914
915 int vrc = close();
916 if (RT_FAILURE(vrc))
917 return vrc;
918
919#ifdef VBOX_WITH_AUDIO_RECORDING
920 this->pCodecAudio = NULL;
921#endif
922
923 if (this->ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
924 {
925 vrc = recordingCodecFinalize(&this->CodecVideo);
926 if (RT_SUCCESS(vrc))
927 vrc = recordingCodecDestroy(&this->CodecVideo);
928 }
929
930 if (RT_SUCCESS(vrc))
931 {
932 RTCritSectDelete(&this->CritSect);
933
934 this->enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
935 this->fEnabled = false;
936 }
937
938 return vrc;
939}
940
941/**
942 * Writes encoded data to a WebM file instance.
943 *
944 * @returns VBox status code.
945 * @param pCodec Codec which has encoded the data.
946 * @param pvData Encoded data to write.
947 * @param cbData Size (in bytes) of \a pvData.
948 * @param msAbsPTS Absolute PTS (in ms) of written data.
949 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
950 */
951int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
952 uint64_t msAbsPTS, uint32_t uFlags)
953{
954 AssertPtr(this->File.pWEBM);
955 AssertPtr(pvData);
956 Assert (cbData);
957
958 WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
959 if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE))
960 {
961 /* All set. */
962 }
963 else
964 {
965 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
966 blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
967 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
968 blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
969 }
970
971 return this->File.pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
972 ? this->uTrackAudio : this->uTrackVideo,
973 pvData, cbData, msAbsPTS, blockFlags);
974}
975
976/**
977 * Codec callback for writing encoded data to a recording stream.
978 *
979 * @returns VBox status code.
980 * @param pCodec Codec which has encoded the data.
981 * @param pvData Encoded data to write.
982 * @param cbData Size (in bytes) of \a pvData.
983 * @param msAbsPTS Absolute PTS (in ms) of written data.
984 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
985 */
986/* static */
987DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
988 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
989{
990 RecordingStream *pThis = (RecordingStream *)pvUser;
991 AssertPtr(pThis);
992
993 /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
994 return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
995}
996
997/**
998 * Initializes the video recording for a recording stream.
999 *
1000 * @returns VBox status code.
1001 * @param screenSettings Screen settings to use.
1002 */
1003int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings)
1004{
1005 /* Sanity. */
1006 AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
1007 AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
1008 AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
1009 AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
1010
1011 PRECORDINGCODEC pCodec = &this->CodecVideo;
1012
1013 RECORDINGCODECCALLBACKS Callbacks;
1014 Callbacks.pvUser = this;
1015 Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
1016
1017 int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
1018 if (RT_SUCCESS(vrc))
1019 vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
1020
1021 if (RT_FAILURE(vrc))
1022 LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
1023
1024 return vrc;
1025}
1026
1027/**
1028 * Locks a recording stream.
1029 */
1030void RecordingStream::lock(void)
1031{
1032 int vrc = RTCritSectEnter(&CritSect);
1033 AssertRC(vrc);
1034}
1035
1036/**
1037 * Unlocks a locked recording stream.
1038 */
1039void RecordingStream::unlock(void)
1040{
1041 int vrc = RTCritSectLeave(&CritSect);
1042 AssertRC(vrc);
1043}
1044
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