summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-client/RecordingStream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-client/RecordingStream.cpp')
-rw-r--r--src/VBox/Main/src-client/RecordingStream.cpp1039
1 files changed, 1039 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/RecordingStream.cpp b/src/VBox/Main/src-client/RecordingStream.cpp
new file mode 100644
index 00000000..dad3e5c5
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingStream.cpp
@@ -0,0 +1,1039 @@
+/* $Id: RecordingStream.cpp $ */
+/** @file
+ * Recording stream code.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <iprt/path.h>
+
+#ifdef VBOX_RECORDING_DUMP
+# include <iprt/formats/bmp.h>
+#endif
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include <VBox/vmm/pdmaudioinline.h>
+#endif
+
+#include "Recording.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+
+RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
+ : m_enmState(RECORDINGSTREAMSTATE_UNINITIALIZED)
+{
+ int vrc2 = initInternal(a_pCtx, uScreen, Settings);
+ if (RT_FAILURE(vrc2))
+ throw vrc2;
+}
+
+RecordingStream::~RecordingStream(void)
+{
+ int vrc2 = uninitInternal();
+ AssertRC(vrc2);
+}
+
+/**
+ * Opens a recording stream.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Recording settings to use.
+ */
+int RecordingStream::open(const settings::RecordingScreenSettings &screenSettings)
+{
+ /* Sanity. */
+ Assert(screenSettings.enmDest != RecordingDestination_None);
+
+ int vrc;
+
+ switch (screenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ Assert(screenSettings.File.strName.isNotEmpty());
+
+ const char *pszFile = screenSettings.File.strName.c_str();
+
+ RTFILE hFile = NIL_RTFILE;
+ vrc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Recording: Opened file '%s'\n", pszFile));
+
+ try
+ {
+ Assert(File.m_pWEBM == NULL);
+ File.m_pWEBM = new WebMWriter();
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ this->File.m_hFile = hFile;
+ m_ScreenSettings.File.strName = pszFile;
+ }
+ }
+ else
+ LogRel(("Recording: Failed to open file '%s' for screen %RU32, vrc=%Rrc\n",
+ pszFile ? pszFile : "<Unnamed>", m_uScreenID, vrc));
+
+ if (RT_FAILURE(vrc))
+ {
+ if (hFile != NIL_RTFILE)
+ RTFileClose(hFile);
+ }
+
+ break;
+ }
+
+ default:
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the recording stream's used configuration.
+ *
+ * @returns The recording stream's used configuration.
+ */
+const settings::RecordingScreenSettings &RecordingStream::GetConfig(void) const
+{
+ return m_ScreenSettings;
+}
+
+/**
+ * Checks if a specified limit for a recording stream has been reached, internal version.
+ *
+ * @returns @c true if any limit has been reached, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const
+{
+ LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n",
+ msTimestamp, m_ScreenSettings.ulMaxTimeS, m_tsStartMs));
+
+ if ( m_ScreenSettings.ulMaxTimeS
+ && msTimestamp >= m_tsStartMs + (m_ScreenSettings.ulMaxTimeS * RT_MS_1SEC))
+ {
+ LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n",
+ m_uScreenID, m_ScreenSettings.ulMaxTimeS));
+ return true;
+ }
+
+ if (m_ScreenSettings.enmDest == RecordingDestination_File)
+ {
+ if (m_ScreenSettings.File.ulMaxSizeMB)
+ {
+ uint64_t sizeInMB = this->File.m_pWEBM->GetFileSize() / _1M;
+ if(sizeInMB >= m_ScreenSettings.File.ulMaxSizeMB)
+ {
+ LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n",
+ m_uScreenID, m_ScreenSettings.File.ulMaxSizeMB));
+ return true;
+ }
+ }
+
+ /* Check for available free disk space */
+ if ( this->File.m_pWEBM
+ && this->File.m_pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
+ {
+ LogRel(("Recording: Not enough free storage space available, stopping recording\n"));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Internal iteration main loop.
+ * Does housekeeping and recording context notification.
+ *
+ * @returns VBox status code.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::iterateInternal(uint64_t msTimestamp)
+{
+ if (!m_fEnabled)
+ return VINF_SUCCESS;
+
+ int vrc;
+
+ if (isLimitReachedInternal(msTimestamp))
+ {
+ vrc = VINF_RECORDING_LIMIT_REACHED;
+ }
+ else
+ vrc = VINF_SUCCESS;
+
+ AssertPtr(m_pCtx);
+
+ switch (vrc)
+ {
+ case VINF_RECORDING_LIMIT_REACHED:
+ {
+ m_fEnabled = false;
+
+ int vrc2 = m_pCtx->OnLimitReached(m_uScreenID, VINF_SUCCESS /* vrc */);
+ AssertRC(vrc2);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Checks if a specified limit for a recording stream has been reached.
+ *
+ * @returns @c true if any limit has been reached, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingStream::IsLimitReached(uint64_t msTimestamp) const
+{
+ if (!IsReady())
+ return true;
+
+ return isLimitReachedInternal(msTimestamp);
+}
+
+/**
+ * Returns whether a recording stream is ready (e.g. enabled and active) or not.
+ *
+ * @returns @c true if ready, @c false if not.
+ */
+bool RecordingStream::IsReady(void) const
+{
+ return m_fEnabled;
+}
+
+/**
+ * Returns if a recording stream needs to be fed with an update or not.
+ *
+ * @returns @c true if an update is needed, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+bool RecordingStream::NeedsUpdate(uint64_t msTimestamp) const
+{
+ return recordingCodecGetWritable((const PRECORDINGCODEC)&m_CodecVideo, msTimestamp) > 0;
+}
+
+/**
+ * Processes a recording stream.
+ * This function takes care of the actual encoding and writing of a certain stream.
+ * As this can be very CPU intensive, this function usually is called from a separate thread.
+ *
+ * @returns VBox status code.
+ * @param mapBlocksCommon Map of common block to process for this stream.
+ *
+ * @note Runs in recording thread.
+ */
+int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
+{
+ LogFlowFuncEnter();
+
+ lock();
+
+ if (!m_ScreenSettings.fEnabled)
+ {
+ unlock();
+ return VINF_SUCCESS;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
+ while (itStreamBlocks != m_Blocks.Map.end())
+ {
+ uint64_t const msTimestamp = itStreamBlocks->first;
+ RecordingBlocks *pBlocks = itStreamBlocks->second;
+
+ AssertPtr(pBlocks);
+
+ while (!pBlocks->List.empty())
+ {
+ RecordingBlock *pBlock = pBlocks->List.front();
+ AssertPtr(pBlock);
+
+ switch (pBlock->enmType)
+ {
+ case RECORDINGBLOCKTYPE_VIDEO:
+ {
+ RECORDINGFRAME Frame;
+ Frame.VideoPtr = (PRECORDINGVIDEOFRAME)pBlock->pvData;
+ Frame.msTimestamp = msTimestamp;
+
+ int vrc2 = recordingCodecEncode(&m_CodecVideo, &Frame, NULL, NULL);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ break;
+ }
+
+ default:
+ /* Note: Audio data already is encoded. */
+ break;
+ }
+
+ pBlocks->List.pop_front();
+ delete pBlock;
+ }
+
+ Assert(pBlocks->List.empty());
+ delete pBlocks;
+
+ m_Blocks.Map.erase(itStreamBlocks);
+ itStreamBlocks = m_Blocks.Map.begin();
+ }
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /* Do we need to multiplex the common audio data to this stream? */
+ if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
+ {
+ /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
+ * written to the screen's assigned recording stream. */
+ RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin();
+ while (itCommonBlocks != mapBlocksCommon.end())
+ {
+ RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
+ while (itBlock != itCommonBlocks->second->List.end())
+ {
+ RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
+ switch (pBlockCommon->enmType)
+ {
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
+ AssertPtr(pAudioFrame);
+ AssertPtr(pAudioFrame->pvBuf);
+ Assert(pAudioFrame->cbBuf);
+
+ AssertPtr(this->File.m_pWEBM);
+ int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ Assert(pBlockCommon->cRefs);
+ pBlockCommon->cRefs--;
+ if (pBlockCommon->cRefs == 0)
+ {
+ itCommonBlocks->second->List.erase(itBlock);
+ delete pBlockCommon;
+ itBlock = itCommonBlocks->second->List.begin();
+ }
+ else
+ ++itBlock;
+ }
+
+ /* If no entries are left over in the block map, remove it altogether. */
+ if (itCommonBlocks->second->List.empty())
+ {
+ delete itCommonBlocks->second;
+ mapBlocksCommon.erase(itCommonBlocks);
+ itCommonBlocks = mapBlocksCommon.begin();
+ }
+ else
+ ++itCommonBlocks;
+
+ LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
+ }
+ }
+#else
+ RT_NOREF(mapBlocksCommon);
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+ unlock();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends a raw (e.g. not yet encoded) audio frame to the recording stream.
+ *
+ * @returns VBox status code.
+ * @param pvData Pointer to audio data.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
+{
+ AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
+ AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
+
+ Log3Func(("cbData=%zu, msTimestamp=%RU64\n", cbData, msTimestamp));
+
+ /* As audio data is common across all streams, re-route this to the recording context, where
+ * the data is being encoded and stored in the common blocks queue. */
+ return m_pCtx->SendAudioFrame(pvData, cbData, msTimestamp);
+}
+
+/**
+ * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
+ *
+ * @returns VBox status code. Will return VINF_RECORDING_LIMIT_REACHED if the stream's recording
+ * limit has been reached or VINF_RECORDING_THROTTLED if the frame is too early for the current
+ * FPS setting.
+ * @param x Upper left (X) coordinate where the video frame starts.
+ * @param y Upper left (Y) coordinate where the video frame starts.
+ * @param uPixelFormat Pixel format of the video frame.
+ * @param uBPP Bits per pixel (BPP) of the video frame.
+ * @param uBytesPerLine Bytes per line of the video frame.
+ * @param uSrcWidth Width (in pixels) of the video frame.
+ * @param uSrcHeight Height (in pixels) of the video frame.
+ * @param puSrcData Actual pixel data of the video frame.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
+ uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
+{
+ AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
+ AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
+
+ lock();
+
+ Log3Func(("[%RU32 %RU32 %RU32 %RU32] msTimestamp=%RU64\n", x , y, uSrcWidth, uSrcHeight, msTimestamp));
+
+ PRECORDINGVIDEOFRAME pFrame = NULL;
+
+ int vrc = iterateInternal(msTimestamp);
+ if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
+ {
+ unlock();
+ return vrc;
+ }
+
+ do
+ {
+ int xDiff = ((int)m_ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
+ uint32_t w = uSrcWidth;
+ if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ uint32_t destX;
+ if ((int)x < -xDiff)
+ {
+ w += xDiff + x;
+ x = -xDiff;
+ destX = 0;
+ }
+ else
+ destX = x + xDiff;
+
+ uint32_t h = uSrcHeight;
+ int yDiff = ((int)m_ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
+ if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ uint32_t destY;
+ if ((int)y < -yDiff)
+ {
+ h += yDiff + (int)y;
+ y = -yDiff;
+ destY = 0;
+ }
+ else
+ destY = y + yDiff;
+
+ if ( destX > m_ScreenSettings.Video.ulWidth
+ || destY > m_ScreenSettings.Video.ulHeight)
+ {
+ vrc = VERR_INVALID_PARAMETER; /* Nothing visible. */
+ break;
+ }
+
+ if (destX + w > m_ScreenSettings.Video.ulWidth)
+ w = m_ScreenSettings.Video.ulWidth - destX;
+
+ if (destY + h > m_ScreenSettings.Video.ulHeight)
+ h = m_ScreenSettings.Video.ulHeight - destY;
+
+ pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
+ AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY);
+
+ /* Calculate bytes per pixel and set pixel format. */
+ const unsigned uBytesPerPixel = uBPP / 8;
+ if (uPixelFormat == BitmapFormat_BGR)
+ {
+ switch (uBPP)
+ {
+ case 32:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB32;
+ break;
+ case 24:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB24;
+ break;
+ case 16:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB565;
+ break;
+ default:
+ AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED);
+ break;
+ }
+ }
+ else
+ AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED);
+
+ const size_t cbRGBBuf = m_ScreenSettings.Video.ulWidth
+ * m_ScreenSettings.Video.ulHeight
+ * uBytesPerPixel;
+ AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER);
+
+ pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
+ AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY);
+ pFrame->cbRGBBuf = cbRGBBuf;
+ pFrame->uWidth = uSrcWidth;
+ pFrame->uHeight = uSrcHeight;
+
+ /* If the current video frame is smaller than video resolution we're going to encode,
+ * clear the frame beforehand to prevent artifacts. */
+ if ( uSrcWidth < m_ScreenSettings.Video.ulWidth
+ || uSrcHeight < m_ScreenSettings.Video.ulHeight)
+ {
+ RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
+ }
+
+ /* Calculate start offset in source and destination buffers. */
+ uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
+ uint32_t offDst = (destY * m_ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
+
+#ifdef VBOX_RECORDING_DUMP
+ BMPFILEHDR fileHdr;
+ RT_ZERO(fileHdr);
+
+ BMPWIN3XINFOHDR coreHdr;
+ RT_ZERO(coreHdr);
+
+ fileHdr.uType = BMP_HDR_MAGIC;
+ fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
+ fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
+
+ coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
+ coreHdr.uWidth = w;
+ coreHdr.uHeight = h;
+ coreHdr.cPlanes = 1;
+ coreHdr.cBits = uBPP;
+ coreHdr.uXPelsPerMeter = 5000;
+ coreHdr.uYPelsPerMeter = 5000;
+
+ char szFileName[RTPATH_MAX];
+ RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", m_uScreenID);
+
+ RTFILE fh;
+ int vrc2 = RTFileOpen(&fh, szFileName,
+ RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc2))
+ {
+ RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
+ RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
+ }
+#endif
+ Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
+
+ /* Do the copy. */
+ for (unsigned int i = 0; i < h; i++)
+ {
+ /* Overflow check. */
+ Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
+ Assert(offDst + w * uBytesPerPixel <= m_ScreenSettings.Video.ulHeight * m_ScreenSettings.Video.ulWidth * uBytesPerPixel);
+
+ memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
+
+#ifdef VBOX_RECORDING_DUMP
+ if (RT_SUCCESS(rc2))
+ RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
+#endif
+ offSrc += uBytesPerLine;
+ offDst += m_ScreenSettings.Video.ulWidth * uBytesPerPixel;
+ }
+
+#ifdef VBOX_RECORDING_DUMP
+ if (RT_SUCCESS(vrc2))
+ RTFileClose(fh);
+#endif
+
+ } while (0);
+
+ if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
+ {
+ RecordingBlock *pBlock = new RecordingBlock();
+ if (pBlock)
+ {
+ AssertPtr(pFrame);
+
+ pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
+ pBlock->pvData = pFrame;
+ pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
+
+ try
+ {
+ RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
+ pRecordingBlocks->List.push_back(pBlock);
+
+ Assert(m_Blocks.Map.find(msTimestamp) == m_Blocks.Map.end());
+ m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+
+ delete pBlock;
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ RecordingVideoFrameFree(pFrame);
+
+ unlock();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Initializes a recording stream.
+ *
+ * @returns VBox status code.
+ * @param pCtx Pointer to recording context.
+ * @param uScreen Screen number to use for this recording stream.
+ * @param Settings Recording screen configuration to use for initialization.
+ */
+int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
+{
+ return initInternal(pCtx, uScreen, Settings);
+}
+
+/**
+ * Initializes a recording stream, internal version.
+ *
+ * @returns VBox status code.
+ * @param pCtx Pointer to recording context.
+ * @param uScreen Screen number to use for this recording stream.
+ * @param screenSettings Recording screen configuration to use for initialization.
+ */
+int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
+ const settings::RecordingScreenSettings &screenSettings)
+{
+ AssertReturn(m_enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
+
+ m_pCtx = pCtx;
+ m_uTrackAudio = UINT8_MAX;
+ m_uTrackVideo = UINT8_MAX;
+ m_tsStartMs = 0;
+ m_uScreenID = uScreen;
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
+ m_pCodecAudio = m_pCtx->GetCodecAudio();
+#endif
+ m_ScreenSettings = screenSettings;
+
+ settings::RecordingScreenSettings *pSettings = &m_ScreenSettings;
+
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ this->File.m_pWEBM = NULL;
+ this->File.m_hFile = NIL_RTFILE;
+
+ vrc = open(*pSettings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
+ const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
+
+ if (fVideoEnabled)
+ {
+ vrc = initVideo(*pSettings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ switch (pSettings->enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ Assert(pSettings->File.strName.isNotEmpty());
+ const char *pszFile = pSettings->File.strName.c_str();
+
+ AssertPtr(File.m_pWEBM);
+ vrc = File.m_pWEBM->OpenEx(pszFile, &this->File.m_hFile,
+ fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
+ fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ if (fVideoEnabled)
+ {
+ vrc = this->File.m_pWEBM->AddVideoTrack(&m_CodecVideo,
+ pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
+ &m_uTrackVideo);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
+ m_uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
+ pSettings->Video.ulRate, pSettings->Video.ulFPS, m_uTrackVideo));
+ }
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if (fAudioEnabled)
+ {
+ AssertPtr(m_pCodecAudio);
+ vrc = this->File.m_pWEBM->AddAudioTrack(m_pCodecAudio,
+ pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
+ &m_uTrackAudio);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
+ m_uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
+ pSettings->Audio.cChannels ? "channels" : "channel", m_uTrackAudio));
+ }
+#endif
+
+ if ( fVideoEnabled
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ || fAudioEnabled
+#endif
+ )
+ {
+ char szWhat[32] = { 0 };
+ if (fVideoEnabled)
+ RTStrCat(szWhat, sizeof(szWhat), "video");
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if (fAudioEnabled)
+ {
+ if (fVideoEnabled)
+ RTStrCat(szWhat, sizeof(szWhat), " + ");
+ RTStrCat(szWhat, sizeof(szWhat), "audio");
+ }
+#endif
+ LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, m_uScreenID, pszFile));
+ }
+
+ break;
+ }
+
+ default:
+ AssertFailed(); /* Should never happen. */
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_enmState = RECORDINGSTREAMSTATE_INITIALIZED;
+ m_fEnabled = true;
+ m_tsStartMs = RTTimeProgramMilliTS();
+
+ return VINF_SUCCESS;
+ }
+
+ int vrc2 = uninitInternal();
+ AssertRC(vrc2);
+
+ LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
+ return vrc;
+}
+
+/**
+ * Closes a recording stream.
+ * Depending on the stream's recording destination, this function closes all associated handles
+ * and finalizes recording.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::close(void)
+{
+ int vrc = VINF_SUCCESS;
+
+ switch (m_ScreenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ if (this->File.m_pWEBM)
+ vrc = this->File.m_pWEBM->Close();
+ break;
+ }
+
+ default:
+ AssertFailed(); /* Should never happen. */
+ break;
+ }
+
+ m_Blocks.Clear();
+
+ LogRel(("Recording: Recording screen #%u stopped\n", m_uScreenID));
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", m_uScreenID, vrc));
+ return vrc;
+ }
+
+ switch (m_ScreenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ if (RTFileIsValid(this->File.m_hFile))
+ {
+ vrc = RTFileClose(this->File.m_hFile);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Closed file '%s'\n", m_ScreenSettings.File.strName.c_str()));
+ }
+ else
+ {
+ LogRel(("Recording: Error closing file '%s', vrc=%Rrc\n", m_ScreenSettings.File.strName.c_str(), vrc));
+ break;
+ }
+ }
+
+ WebMWriter *pWebMWriter = this->File.m_pWEBM;
+ AssertPtr(pWebMWriter);
+
+ if (pWebMWriter)
+ {
+ /* If no clusters (= data) was written, delete the file again. */
+ if (pWebMWriter->GetClusters() == 0)
+ {
+ int vrc2 = RTFileDelete(m_ScreenSettings.File.strName.c_str());
+ AssertRC(vrc2); /* Ignore vrc on non-debug builds. */
+ }
+
+ delete pWebMWriter;
+ pWebMWriter = NULL;
+
+ this->File.m_pWEBM = NULL;
+ }
+ break;
+ }
+
+ default:
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Uninitializes a recording stream.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::Uninit(void)
+{
+ return uninitInternal();
+}
+
+/**
+ * Uninitializes a recording stream, internal version.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::uninitInternal(void)
+{
+ if (m_enmState != RECORDINGSTREAMSTATE_INITIALIZED)
+ return VINF_SUCCESS;
+
+ int vrc = close();
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ m_pCodecAudio = NULL;
+#endif
+
+ if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
+ {
+ vrc = recordingCodecFinalize(&m_CodecVideo);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecDestroy(&m_CodecVideo);
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ RTCritSectDelete(&m_CritSect);
+
+ m_enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
+ m_fEnabled = false;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes encoded data to a WebM file instance.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec which has encoded the data.
+ * @param pvData Encoded data to write.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msAbsPTS Absolute PTS (in ms) of written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ */
+int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags)
+{
+ AssertPtr(this->File.m_pWEBM);
+ AssertPtr(pvData);
+ Assert (cbData);
+
+ WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
+ if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE))
+ {
+ /* All set. */
+ }
+ else
+ {
+ if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
+ blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
+ if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
+ blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
+ }
+
+ return this->File.m_pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
+ ? m_uTrackAudio : m_uTrackVideo,
+ pvData, cbData, msAbsPTS, blockFlags);
+}
+
+/**
+ * Codec callback for writing encoded data to a recording stream.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec which has encoded the data.
+ * @param pvData Encoded data to write.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msAbsPTS Absolute PTS (in ms) of written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ * @param pvUser User-supplied pointer.
+ */
+/* static */
+DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
+{
+ RecordingStream *pThis = (RecordingStream *)pvUser;
+ AssertPtr(pThis);
+
+ /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
+ return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
+}
+
+/**
+ * Initializes the video recording for a recording stream.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Screen settings to use.
+ */
+int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings)
+{
+ /* Sanity. */
+ AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
+
+ PRECORDINGCODEC pCodec = &m_CodecVideo;
+
+ RECORDINGCODECCALLBACKS Callbacks;
+ Callbacks.pvUser = this;
+ Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
+
+ int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Locks a recording stream.
+ */
+void RecordingStream::lock(void)
+{
+ int vrc = RTCritSectEnter(&m_CritSect);
+ AssertRC(vrc);
+}
+
+/**
+ * Unlocks a locked recording stream.
+ */
+void RecordingStream::unlock(void)
+{
+ int vrc = RTCritSectLeave(&m_CritSect);
+ AssertRC(vrc);
+}
+