VirtualBox

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

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

Recording/Main: Optimization: Check if recording stream updates are needed at display implementation level already, should save quite a few unnecessary calls. bugref:10275

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.2 KB
Line 
1/* $Id: RecordingStream.cpp 96322 2022-08-19 07:45:57Z 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 VBox 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 \c true if any limit has been reached, \c false if not.
178 * @param msTimestamp Timestamp (PTS, 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 VBox status code.
223 * @param msTimestamp Timestamp (PTS, 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 \c true if any limit has been reached, \c false if not.
264 * @param msTimestamp Timestamp (PTS, 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 * Returns if a recording stream needs to be fed with an update or not.
286 *
287 * @returns @c true if an update is needed, @c false if not.
288 * @param msTimestamp Timestamp (PTS, in ms).
289 */
290bool RecordingStream::NeedsUpdate(uint64_t msTimestamp) const
291{
292 return recordingCodecGetWritable((const PRECORDINGCODEC)&CodecVideo, msTimestamp) > 0;
293}
294
295/**
296 * Processes a recording stream.
297 * This function takes care of the actual encoding and writing of a certain stream.
298 * As this can be very CPU intensive, this function usually is called from a separate thread.
299 *
300 * @returns VBox status code.
301 * @param mapBlocksCommon Map of common block to process for this stream.
302 *
303 * @note Runs in recording thread.
304 */
305int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
306{
307 LogFlowFuncEnter();
308
309 lock();
310
311 if (!this->ScreenSettings.fEnabled)
312 {
313 unlock();
314 return VINF_SUCCESS;
315 }
316
317 int vrc = VINF_SUCCESS;
318
319 RecordingBlockMap::iterator itStreamBlocks = Blocks.Map.begin();
320 while (itStreamBlocks != Blocks.Map.end())
321 {
322 uint64_t const msTimestamp = itStreamBlocks->first;
323 RecordingBlocks *pBlocks = itStreamBlocks->second;
324
325 AssertPtr(pBlocks);
326
327 while (!pBlocks->List.empty())
328 {
329 RecordingBlock *pBlock = pBlocks->List.front();
330 AssertPtr(pBlock);
331
332 switch (pBlock->enmType)
333 {
334 case RECORDINGBLOCKTYPE_VIDEO:
335 {
336 RECORDINGFRAME Frame;
337 Frame.VideoPtr = (PRECORDINGVIDEOFRAME)pBlock->pvData;
338 Frame.msTimestamp = msTimestamp;
339
340 int vrc2 = recordingCodecEncode(&this->CodecVideo, &Frame, NULL, NULL);
341 AssertRC(vrc2);
342 if (RT_SUCCESS(vrc))
343 vrc = vrc2;
344
345 break;
346 }
347
348 default:
349 /* Note: Audio data already is encoded. */
350 break;
351 }
352
353 pBlocks->List.pop_front();
354 delete pBlock;
355 }
356
357 Assert(pBlocks->List.empty());
358 delete pBlocks;
359
360 Blocks.Map.erase(itStreamBlocks);
361 itStreamBlocks = Blocks.Map.begin();
362 }
363
364#ifdef VBOX_WITH_AUDIO_RECORDING
365 /* Do we need to multiplex the common audio data to this stream? */
366 if (this->ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
367 {
368 /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
369 * written to the screen's assigned recording stream. */
370 RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin();
371 while (itCommonBlocks != mapBlocksCommon.end())
372 {
373 RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
374 while (itBlock != itCommonBlocks->second->List.end())
375 {
376 RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
377 switch (pBlockCommon->enmType)
378 {
379 case RECORDINGBLOCKTYPE_AUDIO:
380 {
381 PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
382 AssertPtr(pAudioFrame);
383 AssertPtr(pAudioFrame->pvBuf);
384 Assert(pAudioFrame->cbBuf);
385
386 AssertPtr(this->File.pWEBM);
387 int vrc2 = this->File.pWEBM->WriteBlock(this->uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags);
388 AssertRC(vrc2);
389 if (RT_SUCCESS(vrc))
390 vrc = vrc2;
391 break;
392 }
393
394 default:
395 AssertFailed();
396 break;
397 }
398
399 Assert(pBlockCommon->cRefs);
400 pBlockCommon->cRefs--;
401 if (pBlockCommon->cRefs == 0)
402 {
403 itCommonBlocks->second->List.erase(itBlock);
404 delete pBlockCommon;
405 itBlock = itCommonBlocks->second->List.begin();
406 }
407 else
408 ++itBlock;
409 }
410
411 /* If no entries are left over in the block map, remove it altogether. */
412 if (itCommonBlocks->second->List.empty())
413 {
414 delete itCommonBlocks->second;
415 mapBlocksCommon.erase(itCommonBlocks);
416 itCommonBlocks = mapBlocksCommon.begin();
417 }
418 else
419 ++itCommonBlocks;
420
421 LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
422 }
423 }
424#else
425 RT_NOREF(mapBlocksCommon);
426#endif /* VBOX_WITH_AUDIO_RECORDING */
427
428 unlock();
429
430 LogFlowFuncLeaveRC(vrc);
431 return vrc;
432}
433
434/**
435 * Sends a raw (e.g. not yet encoded) audio frame to the recording stream.
436 *
437 * @returns VBox status code.
438 * @param pvData Pointer to audio data.
439 * @param cbData Size (in bytes) of \a pvData.
440 * @param msTimestamp Timestamp (PTS, in ms).
441 */
442int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
443{
444 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
445 AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
446
447 Log3Func(("cbData=%zu, msTimestamp=%RU64\n", cbData, msTimestamp));
448
449 /* As audio data is common across all streams, re-route this to the recording context, where
450 * the data is being encoded and stored in the common blocks queue. */
451 return m_pCtx->SendAudioFrame(pvData, cbData, msTimestamp);
452}
453
454/**
455 * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
456 *
457 * @returns VBox status code. Will return VINF_RECORDING_LIMIT_REACHED if the stream's recording
458 * limit has been reached or VINF_RECORDING_THROTTLED if the frame is too early for the current
459 * FPS setting.
460 * @param x Upper left (X) coordinate where the video frame starts.
461 * @param y Upper left (Y) coordinate where the video frame starts.
462 * @param uPixelFormat Pixel format of the video frame.
463 * @param uBPP Bits per pixel (BPP) of the video frame.
464 * @param uBytesPerLine Bytes per line of the video frame.
465 * @param uSrcWidth Width (in pixels) of the video frame.
466 * @param uSrcHeight Height (in pixels) of the video frame.
467 * @param puSrcData Actual pixel data of the video frame.
468 * @param msTimestamp Timestamp (PTS, in ms).
469 */
470int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
471 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
472{
473 AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
474 AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
475
476 lock();
477
478 Log3Func(("[%RU32 %RU32 %RU32 %RU32] msTimestamp=%RU64\n", x , y, uSrcWidth, uSrcHeight, msTimestamp));
479
480 PRECORDINGVIDEOFRAME pFrame = NULL;
481
482 int vrc = iterateInternal(msTimestamp);
483 if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
484 {
485 unlock();
486 return vrc;
487 }
488
489 do
490 {
491 int xDiff = ((int)this->ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
492 uint32_t w = uSrcWidth;
493 if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
494 {
495 vrc = VERR_INVALID_PARAMETER;
496 break;
497 }
498
499 uint32_t destX;
500 if ((int)x < -xDiff)
501 {
502 w += xDiff + x;
503 x = -xDiff;
504 destX = 0;
505 }
506 else
507 destX = x + xDiff;
508
509 uint32_t h = uSrcHeight;
510 int yDiff = ((int)this->ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
511 if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
512 {
513 vrc = VERR_INVALID_PARAMETER;
514 break;
515 }
516
517 uint32_t destY;
518 if ((int)y < -yDiff)
519 {
520 h += yDiff + (int)y;
521 y = -yDiff;
522 destY = 0;
523 }
524 else
525 destY = y + yDiff;
526
527 if ( destX > this->ScreenSettings.Video.ulWidth
528 || destY > this->ScreenSettings.Video.ulHeight)
529 {
530 vrc = VERR_INVALID_PARAMETER; /* Nothing visible. */
531 break;
532 }
533
534 if (destX + w > this->ScreenSettings.Video.ulWidth)
535 w = this->ScreenSettings.Video.ulWidth - destX;
536
537 if (destY + h > this->ScreenSettings.Video.ulHeight)
538 h = this->ScreenSettings.Video.ulHeight - destY;
539
540 pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
541 AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY);
542
543 /* Calculate bytes per pixel and set pixel format. */
544 const unsigned uBytesPerPixel = uBPP / 8;
545 if (uPixelFormat == BitmapFormat_BGR)
546 {
547 switch (uBPP)
548 {
549 case 32:
550 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB32;
551 break;
552 case 24:
553 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB24;
554 break;
555 case 16:
556 pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB565;
557 break;
558 default:
559 AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED);
560 break;
561 }
562 }
563 else
564 AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED);
565
566 const size_t cbRGBBuf = this->ScreenSettings.Video.ulWidth
567 * this->ScreenSettings.Video.ulHeight
568 * uBytesPerPixel;
569 AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER);
570
571 pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
572 AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY);
573 pFrame->cbRGBBuf = cbRGBBuf;
574 pFrame->uWidth = uSrcWidth;
575 pFrame->uHeight = uSrcHeight;
576
577 /* If the current video frame is smaller than video resolution we're going to encode,
578 * clear the frame beforehand to prevent artifacts. */
579 if ( uSrcWidth < this->ScreenSettings.Video.ulWidth
580 || uSrcHeight < this->ScreenSettings.Video.ulHeight)
581 {
582 RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
583 }
584
585 /* Calculate start offset in source and destination buffers. */
586 uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
587 uint32_t offDst = (destY * this->ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
588
589#ifdef VBOX_RECORDING_DUMP
590 BMPFILEHDR fileHdr;
591 RT_ZERO(fileHdr);
592
593 BMPWIN3XINFOHDR coreHdr;
594 RT_ZERO(coreHdr);
595
596 fileHdr.uType = BMP_HDR_MAGIC;
597 fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
598 fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
599
600 coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
601 coreHdr.uWidth = w;
602 coreHdr.uHeight = h;
603 coreHdr.cPlanes = 1;
604 coreHdr.cBits = uBPP;
605 coreHdr.uXPelsPerMeter = 5000;
606 coreHdr.uYPelsPerMeter = 5000;
607
608 char szFileName[RTPATH_MAX];
609 RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", this->uScreenID);
610
611 RTFILE fh;
612 int vrc2 = RTFileOpen(&fh, szFileName,
613 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
614 if (RT_SUCCESS(vrc2))
615 {
616 RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
617 RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
618 }
619#endif
620 Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
621
622 /* Do the copy. */
623 for (unsigned int i = 0; i < h; i++)
624 {
625 /* Overflow check. */
626 Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
627 Assert(offDst + w * uBytesPerPixel <= this->ScreenSettings.Video.ulHeight * this->ScreenSettings.Video.ulWidth * uBytesPerPixel);
628
629 memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
630
631#ifdef VBOX_RECORDING_DUMP
632 if (RT_SUCCESS(rc2))
633 RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
634#endif
635 offSrc += uBytesPerLine;
636 offDst += this->ScreenSettings.Video.ulWidth * uBytesPerPixel;
637 }
638
639#ifdef VBOX_RECORDING_DUMP
640 if (RT_SUCCESS(vrc2))
641 RTFileClose(fh);
642#endif
643
644 } while (0);
645
646 if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
647 {
648 RecordingBlock *pBlock = new RecordingBlock();
649 if (pBlock)
650 {
651 AssertPtr(pFrame);
652
653 pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
654 pBlock->pvData = pFrame;
655 pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
656
657 try
658 {
659 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
660 pRecordingBlocks->List.push_back(pBlock);
661
662 Assert(this->Blocks.Map.find(msTimestamp) == this->Blocks.Map.end());
663 this->Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
664 }
665 catch (const std::exception &ex)
666 {
667 RT_NOREF(ex);
668
669 delete pBlock;
670 vrc = VERR_NO_MEMORY;
671 }
672 }
673 else
674 vrc = VERR_NO_MEMORY;
675 }
676
677 if (RT_FAILURE(vrc))
678 RecordingVideoFrameFree(pFrame);
679
680 unlock();
681
682 LogFlowFuncLeaveRC(vrc);
683 return vrc;
684}
685
686/**
687 * Initializes a recording stream.
688 *
689 * @returns VBox status code.
690 * @param pCtx Pointer to recording context.
691 * @param uScreen Screen number to use for this recording stream.
692 * @param Settings Recording screen configuration to use for initialization.
693 */
694int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
695{
696 return initInternal(pCtx, uScreen, Settings);
697}
698
699/**
700 * Initializes a recording stream, internal version.
701 *
702 * @returns VBox status code.
703 * @param pCtx Pointer to recording context.
704 * @param uScreen Screen number to use for this recording stream.
705 * @param screenSettings Recording screen configuration to use for initialization.
706 */
707int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
708 const settings::RecordingScreenSettings &screenSettings)
709{
710 AssertReturn(enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
711
712 this->m_pCtx = pCtx;
713 this->uTrackAudio = UINT8_MAX;
714 this->uTrackVideo = UINT8_MAX;
715 this->tsStartMs = 0;
716 this->uScreenID = uScreen;
717#ifdef VBOX_WITH_AUDIO_RECORDING
718 /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
719 this->pCodecAudio = m_pCtx->GetCodecAudio();
720#endif
721 this->ScreenSettings = screenSettings;
722
723 settings::RecordingScreenSettings *pSettings = &this->ScreenSettings;
724
725 int vrc = RTCritSectInit(&this->CritSect);
726 if (RT_FAILURE(vrc))
727 return vrc;
728
729 this->File.pWEBM = NULL;
730 this->File.hFile = NIL_RTFILE;
731
732 vrc = open(*pSettings);
733 if (RT_FAILURE(vrc))
734 return vrc;
735
736 const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
737 const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
738
739 if (fVideoEnabled)
740 {
741 vrc = initVideo(*pSettings);
742 if (RT_FAILURE(vrc))
743 return vrc;
744 }
745
746 switch (pSettings->enmDest)
747 {
748 case RecordingDestination_File:
749 {
750 Assert(pSettings->File.strName.isNotEmpty());
751 const char *pszFile = pSettings->File.strName.c_str();
752
753 AssertPtr(File.pWEBM);
754 vrc = File.pWEBM->OpenEx(pszFile, &this->File.hFile,
755 fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
756 fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
757 if (RT_FAILURE(vrc))
758 {
759 LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
760 break;
761 }
762
763 if (fVideoEnabled)
764 {
765 vrc = this->File.pWEBM->AddVideoTrack(&this->CodecVideo,
766 pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
767 &this->uTrackVideo);
768 if (RT_FAILURE(vrc))
769 {
770 LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
771 break;
772 }
773
774 LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
775 this->uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
776 pSettings->Video.ulRate, pSettings->Video.ulFPS, this->uTrackVideo));
777 }
778
779#ifdef VBOX_WITH_AUDIO_RECORDING
780 if (fAudioEnabled)
781 {
782 AssertPtr(this->pCodecAudio);
783 vrc = this->File.pWEBM->AddAudioTrack(this->pCodecAudio,
784 pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
785 &this->uTrackAudio);
786 if (RT_FAILURE(vrc))
787 {
788 LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
789 break;
790 }
791
792 LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
793 this->uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
794 pSettings->Audio.cChannels ? "channels" : "channel", this->uTrackAudio));
795 }
796#endif
797
798 if ( fVideoEnabled
799#ifdef VBOX_WITH_AUDIO_RECORDING
800 || fAudioEnabled
801#endif
802 )
803 {
804 char szWhat[32] = { 0 };
805 if (fVideoEnabled)
806 RTStrCat(szWhat, sizeof(szWhat), "video");
807#ifdef VBOX_WITH_AUDIO_RECORDING
808 if (fAudioEnabled)
809 {
810 if (fVideoEnabled)
811 RTStrCat(szWhat, sizeof(szWhat), " + ");
812 RTStrCat(szWhat, sizeof(szWhat), "audio");
813 }
814#endif
815 LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, this->uScreenID, pszFile));
816 }
817
818 break;
819 }
820
821 default:
822 AssertFailed(); /* Should never happen. */
823 vrc = VERR_NOT_IMPLEMENTED;
824 break;
825 }
826
827 if (RT_SUCCESS(vrc))
828 {
829 this->enmState = RECORDINGSTREAMSTATE_INITIALIZED;
830 this->fEnabled = true;
831 this->tsStartMs = RTTimeProgramMilliTS();
832
833 return VINF_SUCCESS;
834 }
835
836 int vrc2 = uninitInternal();
837 AssertRC(vrc2);
838
839 LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
840 return vrc;
841}
842
843/**
844 * Closes a recording stream.
845 * Depending on the stream's recording destination, this function closes all associated handles
846 * and finalizes recording.
847 *
848 * @returns VBox status code.
849 */
850int RecordingStream::close(void)
851{
852 int vrc = VINF_SUCCESS;
853
854 switch (this->ScreenSettings.enmDest)
855 {
856 case RecordingDestination_File:
857 {
858 if (this->File.pWEBM)
859 vrc = this->File.pWEBM->Close();
860 break;
861 }
862
863 default:
864 AssertFailed(); /* Should never happen. */
865 break;
866 }
867
868 this->Blocks.Clear();
869
870 LogRel(("Recording: Recording screen #%u stopped\n", this->uScreenID));
871
872 if (RT_FAILURE(vrc))
873 {
874 LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", this->uScreenID, vrc));
875 return vrc;
876 }
877
878 switch (this->ScreenSettings.enmDest)
879 {
880 case RecordingDestination_File:
881 {
882 if (RTFileIsValid(this->File.hFile))
883 {
884 vrc = RTFileClose(this->File.hFile);
885 if (RT_SUCCESS(vrc))
886 {
887 LogRel(("Recording: Closed file '%s'\n", this->ScreenSettings.File.strName.c_str()));
888 }
889 else
890 {
891 LogRel(("Recording: Error closing file '%s', rc=%Rrc\n", this->ScreenSettings.File.strName.c_str(), vrc));
892 break;
893 }
894 }
895
896 WebMWriter *pWebMWriter = this->File.pWEBM;
897 AssertPtr(pWebMWriter);
898
899 if (pWebMWriter)
900 {
901 /* If no clusters (= data) was written, delete the file again. */
902 if (pWebMWriter->GetClusters() == 0)
903 {
904 int vrc2 = RTFileDelete(this->ScreenSettings.File.strName.c_str());
905 AssertRC(vrc2); /* Ignore rc on non-debug builds. */
906 }
907
908 delete pWebMWriter;
909 pWebMWriter = NULL;
910
911 this->File.pWEBM = NULL;
912 }
913 break;
914 }
915
916 default:
917 vrc = VERR_NOT_IMPLEMENTED;
918 break;
919 }
920
921 LogFlowFuncLeaveRC(vrc);
922 return vrc;
923}
924
925/**
926 * Uninitializes a recording stream.
927 *
928 * @returns VBox status code.
929 */
930int RecordingStream::Uninit(void)
931{
932 return uninitInternal();
933}
934
935/**
936 * Uninitializes a recording stream, internal version.
937 *
938 * @returns VBox status code.
939 */
940int RecordingStream::uninitInternal(void)
941{
942 if (this->enmState != RECORDINGSTREAMSTATE_INITIALIZED)
943 return VINF_SUCCESS;
944
945 int vrc = close();
946 if (RT_FAILURE(vrc))
947 return vrc;
948
949#ifdef VBOX_WITH_AUDIO_RECORDING
950 this->pCodecAudio = NULL;
951#endif
952
953 if (this->ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
954 {
955 vrc = recordingCodecFinalize(&this->CodecVideo);
956 if (RT_SUCCESS(vrc))
957 vrc = recordingCodecDestroy(&this->CodecVideo);
958 }
959
960 if (RT_SUCCESS(vrc))
961 {
962 RTCritSectDelete(&this->CritSect);
963
964 this->enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
965 this->fEnabled = false;
966 }
967
968 return vrc;
969}
970
971/**
972 * Writes encoded data to a WebM file instance.
973 *
974 * @returns VBox status code.
975 * @param pCodec Codec which has encoded the data.
976 * @param pvData Encoded data to write.
977 * @param cbData Size (in bytes) of \a pvData.
978 * @param msAbsPTS Absolute PTS (in ms) of written data.
979 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
980 */
981int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
982 uint64_t msAbsPTS, uint32_t uFlags)
983{
984 AssertPtr(this->File.pWEBM);
985 AssertPtr(pvData);
986 Assert (cbData);
987
988 WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
989 if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE))
990 {
991 /* All set. */
992 }
993 else
994 {
995 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
996 blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
997 if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
998 blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
999 }
1000
1001 return this->File.pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
1002 ? this->uTrackAudio : this->uTrackVideo,
1003 pvData, cbData, msAbsPTS, blockFlags);
1004}
1005
1006/**
1007 * Codec callback for writing encoded data to a recording stream.
1008 *
1009 * @returns VBox status code.
1010 * @param pCodec Codec which has encoded the data.
1011 * @param pvData Encoded data to write.
1012 * @param cbData Size (in bytes) of \a pvData.
1013 * @param msAbsPTS Absolute PTS (in ms) of written data.
1014 * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
1015 * @param pvUser User-supplied pointer.
1016 */
1017/* static */
1018DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
1019 uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
1020{
1021 RecordingStream *pThis = (RecordingStream *)pvUser;
1022 AssertPtr(pThis);
1023
1024 /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
1025 return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
1026}
1027
1028/**
1029 * Initializes the video recording for a recording stream.
1030 *
1031 * @returns VBox status code.
1032 * @param screenSettings Screen settings to use.
1033 */
1034int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings)
1035{
1036 /* Sanity. */
1037 AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
1038 AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
1039 AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
1040 AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
1041
1042 PRECORDINGCODEC pCodec = &this->CodecVideo;
1043
1044 RECORDINGCODECCALLBACKS Callbacks;
1045 Callbacks.pvUser = this;
1046 Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
1047
1048 int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
1049 if (RT_SUCCESS(vrc))
1050 vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
1051
1052 if (RT_FAILURE(vrc))
1053 LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
1054
1055 return vrc;
1056}
1057
1058/**
1059 * Locks a recording stream.
1060 */
1061void RecordingStream::lock(void)
1062{
1063 int vrc = RTCritSectEnter(&CritSect);
1064 AssertRC(vrc);
1065}
1066
1067/**
1068 * Unlocks a locked recording stream.
1069 */
1070void RecordingStream::unlock(void)
1071{
1072 int vrc = RTCritSectLeave(&CritSect);
1073 AssertRC(vrc);
1074}
1075
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