VirtualBox

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

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

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