VirtualBox

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

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

Recording/Main: Made recording file name construction more deterministic (also required for testcase handling in Validation Kit), by only letting the recording screen settings to construct + alter the actual file name(s). ​bugref:10275

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