summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-client/Recording.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Main/src-client/Recording.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-client/Recording.cpp')
-rw-r--r--src/VBox/Main/src-client/Recording.cpp945
1 files changed, 945 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/Recording.cpp b/src/VBox/Main/src-client/Recording.cpp
new file mode 100644
index 00000000..ce475213
--- /dev/null
+++ b/src/VBox/Main/src-client/Recording.cpp
@@ -0,0 +1,945 @@
+/* $Id: Recording.cpp $ */
+/** @file
+ * Recording context code.
+ *
+ * This code employs a separate encoding thread per recording context
+ * to keep time spent in EMT as short as possible. Each configured VM display
+ * is represented by an own recording stream, which in turn has its own rendering
+ * queue. Common recording data across all recording streams is kept in a
+ * separate queue in the recording context to minimize data duplication and
+ * multiplexing overhead in EMT.
+ */
+
+/*
+ * 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 <stdexcept>
+#include <vector>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <VBox/err.h>
+#include <VBox/com/VirtualBox.h>
+
+#include "ConsoleImpl.h"
+#include "Recording.h"
+#include "RecordingInternals.h"
+#include "RecordingStream.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+using namespace com;
+
+#ifdef DEBUG_andy
+/** Enables dumping audio / video data for debugging reasons. */
+//# define VBOX_RECORDING_DUMP
+#endif
+
+
+/**
+ * Recording context constructor.
+ *
+ * @note Will throw vrc when unable to create.
+ */
+RecordingContext::RecordingContext(void)
+ : m_pConsole(NULL)
+ , m_enmState(RECORDINGSTS_UNINITIALIZED)
+ , m_cStreamsEnabled(0)
+{
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+}
+
+/**
+ * Recording context constructor.
+ *
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ *
+ * @note Will throw vrc when unable to create.
+ */
+RecordingContext::RecordingContext(Console *ptrConsole, const settings::RecordingSettings &Settings)
+ : m_pConsole(NULL)
+ , m_enmState(RECORDINGSTS_UNINITIALIZED)
+ , m_cStreamsEnabled(0)
+{
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ vrc = RecordingContext::createInternal(ptrConsole, Settings);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+}
+
+RecordingContext::~RecordingContext(void)
+{
+ destroyInternal();
+
+ if (RTCritSectIsInitialized(&m_CritSect))
+ RTCritSectDelete(&m_CritSect);
+}
+
+/**
+ * Worker thread for all streams of a recording context.
+ *
+ * For video frames, this also does the RGB/YUV conversion and encoding.
+ */
+DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RecordingContext *pThis = (RecordingContext *)pvUser;
+
+ /* Signal that we're up and rockin'. */
+ RTThreadUserSignal(hThreadSelf);
+
+ LogRel2(("Recording: Thread started\n"));
+
+ for (;;)
+ {
+ int vrc = RTSemEventWait(pThis->m_WaitEvent, RT_INDEFINITE_WAIT);
+ AssertRCBreak(vrc);
+
+ Log2Func(("Processing %zu streams\n", pThis->m_vecStreams.size()));
+
+ /* Process common raw blocks (data which not has been encoded yet). */
+ vrc = pThis->processCommonData(pThis->m_mapBlocksRaw, 100 /* ms timeout */);
+
+ /** @todo r=andy This is inefficient -- as we already wake up this thread
+ * for every screen from Main, we here go again (on every wake up) through
+ * all screens. */
+ RecordingStreams::iterator itStream = pThis->m_vecStreams.begin();
+ while (itStream != pThis->m_vecStreams.end())
+ {
+ RecordingStream *pStream = (*itStream);
+
+ /* Hand-in common encoded blocks. */
+ vrc = pStream->Process(pThis->m_mapBlocksEncoded);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), vrc));
+ break;
+ }
+
+ ++itStream;
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Encoding thread failed (%Rrc)\n", vrc));
+
+ /* Keep going in case of errors. */
+
+ if (ASMAtomicReadBool(&pThis->m_fShutdown))
+ {
+ LogFunc(("Thread is shutting down ...\n"));
+ break;
+ }
+
+ } /* for */
+
+ LogRel2(("Recording: Thread ended\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Notifies a recording context's encoding thread.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::threadNotify(void)
+{
+ return RTSemEventSignal(m_WaitEvent);
+}
+
+/**
+ * Worker function for processing common block data.
+ *
+ * @returns VBox status code.
+ * @param mapCommon Common block map to handle.
+ * @param msTimeout Timeout to use for maximum time spending to process data.
+ * Use RT_INDEFINITE_WAIT for processing all data.
+ *
+ * @note Runs in recording thread.
+ */
+int RecordingContext::processCommonData(RecordingBlockMap &mapCommon, RTMSINTERVAL msTimeout)
+{
+ Log2Func(("Processing %zu common blocks (%RU32ms timeout)\n", mapCommon.size(), msTimeout));
+
+ int vrc = VINF_SUCCESS;
+
+ uint64_t const msStart = RTTimeMilliTS();
+ RecordingBlockMap::iterator itCommonBlocks = mapCommon.begin();
+ while (itCommonBlocks != mapCommon.end())
+ {
+ RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
+ while (itBlock != itCommonBlocks->second->List.end())
+ {
+ RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
+ switch (pBlockCommon->enmType)
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
+
+ RECORDINGFRAME Frame;
+ Frame.msTimestamp = pBlockCommon->msTimestamp;
+ Frame.Audio.pvBuf = pAudioFrame->pvBuf;
+ Frame.Audio.cbBuf = pAudioFrame->cbBuf;
+
+ vrc = recordingCodecEncode(&m_CodecAudio, &Frame, NULL, NULL);
+ break;
+ }
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+ default:
+ /* Skip unknown stuff. */
+ break;
+ }
+
+ itCommonBlocks->second->List.erase(itBlock);
+ delete pBlockCommon;
+ itBlock = itCommonBlocks->second->List.begin();
+
+ if (RT_FAILURE(vrc) || RTTimeMilliTS() > msStart + msTimeout)
+ break;
+ }
+
+ /* If no entries are left over in the block map, remove it altogether. */
+ if (itCommonBlocks->second->List.empty())
+ {
+ delete itCommonBlocks->second;
+ mapCommon.erase(itCommonBlocks);
+ itCommonBlocks = mapCommon.begin();
+ }
+ else
+ ++itCommonBlocks;
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes common block data (i.e. shared / the same) in all streams.
+ *
+ * The multiplexing is needed to supply all recorded (enabled) screens with the same
+ * data at the same given point in time.
+ *
+ * Currently this only is being used for audio data.
+ *
+ * @returns VBox status code.
+ * @param mapCommon Common block map to write data to.
+ * @param pCodec Pointer to codec instance which has written the data.
+ * @param pvData Pointer to written data (encoded).
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msTimestamp Absolute PTS (in ms) of the written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ */
+int RecordingContext::writeCommonData(RecordingBlockMap &mapCommon, PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msTimestamp, uint32_t uFlags)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pCodec=%p, cbData=%zu, msTimestamp=%zu, uFlags=%#x\n",
+ pCodec, cbData, msTimestamp, uFlags));
+
+ /** @todo Optimize this! Three allocations in here! */
+
+ RECORDINGBLOCKTYPE const enmType = pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
+ ? RECORDINGBLOCKTYPE_AUDIO : RECORDINGBLOCKTYPE_UNKNOWN;
+
+ AssertReturn(enmType != RECORDINGBLOCKTYPE_UNKNOWN, VERR_NOT_SUPPORTED);
+
+ RecordingBlock *pBlock = new RecordingBlock();
+
+ switch (enmType)
+ {
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
+ AssertPtrReturn(pFrame, VERR_NO_MEMORY);
+
+ pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
+ AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
+ pFrame->cbBuf = cbData;
+
+ memcpy(pFrame->pvBuf, pvData, cbData);
+
+ pBlock->enmType = enmType;
+ pBlock->pvData = pFrame;
+ pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
+ pBlock->cRefs = m_cStreamsEnabled;
+ pBlock->msTimestamp = msTimestamp;
+ pBlock->uFlags = uFlags;
+
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ lock();
+
+ int vrc;
+
+ try
+ {
+ RecordingBlockMap::iterator itBlocks = mapCommon.find(msTimestamp);
+ if (itBlocks == mapCommon.end())
+ {
+ RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
+ pRecordingBlocks->List.push_back(pBlock);
+
+ mapCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
+ }
+ else
+ itBlocks->second->List.push_back(pBlock);
+
+ vrc = VINF_SUCCESS;
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+ vrc = VERR_NO_MEMORY;
+ }
+
+ unlock();
+
+ if (RT_SUCCESS(vrc))
+ vrc = threadNotify();
+
+ return vrc;
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Callback function for writing encoded audio data into the common encoded block map.
+ *
+ * This is called by the audio codec when finishing encoding audio data.
+ *
+ * @copydoc RECORDINGCODECCALLBACKS::pfnWriteData
+ */
+/* static */
+DECLCALLBACK(int) RecordingContext::audioCodecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
+{
+ RecordingContext *pThis = (RecordingContext *)pvUser;
+ return pThis->writeCommonData(pThis->m_mapBlocksEncoded, pCodec, pvData, cbData, msAbsPTS, uFlags);
+}
+
+/**
+ * Initializes the audio codec for a (multiplexing) recording context.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Reference to recording screen settings to use for initialization.
+ */
+int RecordingContext::audioInit(const settings::RecordingScreenSettings &screenSettings)
+{
+ RecordingAudioCodec_T const enmCodec = screenSettings.Audio.enmCodec;
+
+ if (enmCodec == RecordingAudioCodec_None)
+ {
+ LogRel2(("Recording: No audio codec configured, skipping audio init\n"));
+ return VINF_SUCCESS;
+ }
+
+ RECORDINGCODECCALLBACKS Callbacks;
+ Callbacks.pvUser = this;
+ Callbacks.pfnWriteData = RecordingContext::audioCodecWriteDataCallback;
+
+ int vrc = recordingCodecCreateAudio(&m_CodecAudio, enmCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecInit(&m_CodecAudio, &Callbacks, screenSettings);
+
+ return vrc;
+}
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+/**
+ * Creates a recording context.
+ *
+ * @returns VBox status code.
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ */
+int RecordingContext::createInternal(Console *ptrConsole, const settings::RecordingSettings &Settings)
+{
+ int vrc = VINF_SUCCESS;
+
+ /* Copy the settings to our context. */
+ m_Settings = Settings;
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ settings::RecordingScreenSettingsMap::const_iterator itScreen0 = m_Settings.mapScreens.begin();
+ AssertReturn(itScreen0 != m_Settings.mapScreens.end(), VERR_WRONG_ORDER);
+
+ /* We always use the audio settings from screen 0, as we multiplex the audio data anyway. */
+ settings::RecordingScreenSettings const &screen0Settings = itScreen0->second;
+
+ vrc = this->audioInit(screen0Settings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#endif
+
+ m_pConsole = ptrConsole;
+
+ settings::RecordingScreenSettingsMap::const_iterator itScreen = m_Settings.mapScreens.begin();
+ while (itScreen != m_Settings.mapScreens.end())
+ {
+ RecordingStream *pStream = NULL;
+ try
+ {
+ pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
+ m_vecStreams.push_back(pStream);
+ if (itScreen->second.fEnabled)
+ m_cStreamsEnabled++;
+ LogFlowFunc(("pStream=%p\n", pStream));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+ catch (int vrc_thrown) /* Catch vrc thrown by constructor. */
+ {
+ vrc = vrc_thrown;
+ break;
+ }
+
+ ++itScreen;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_tsStartMs = RTTimeMilliTS();
+ m_enmState = RECORDINGSTS_CREATED;
+ m_fShutdown = false;
+
+ vrc = RTSemEventCreate(&m_WaitEvent);
+ AssertRCReturn(vrc, vrc);
+ }
+
+ if (RT_FAILURE(vrc))
+ destroyInternal();
+
+ return vrc;
+}
+
+/**
+ * Starts a recording context by creating its worker thread.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::startInternal(void)
+{
+ if (m_enmState == RECORDINGSTS_STARTED)
+ return VINF_SUCCESS;
+
+ Assert(m_enmState == RECORDINGSTS_CREATED);
+
+ int vrc = RTThreadCreate(&m_Thread, RecordingContext::threadMain, (void *)this, 0,
+ RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
+
+ if (RT_SUCCESS(vrc)) /* Wait for the thread to start. */
+ vrc = RTThreadUserWait(m_Thread, RT_MS_30SEC /* 30s timeout */);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Started\n"));
+ m_enmState = RECORDINGSTS_STARTED;
+ }
+ else
+ Log(("Recording: Failed to start (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Stops a recording context by telling the worker thread to stop and finalizing its operation.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::stopInternal(void)
+{
+ if (m_enmState != RECORDINGSTS_STARTED)
+ return VINF_SUCCESS;
+
+ LogThisFunc(("Shutting down thread ...\n"));
+
+ /* Set shutdown indicator. */
+ ASMAtomicWriteBool(&m_fShutdown, true);
+
+ /* Signal the thread and wait for it to shut down. */
+ int vrc = threadNotify();
+ if (RT_SUCCESS(vrc))
+ vrc = RTThreadWait(m_Thread, RT_MS_30SEC /* 30s timeout */, NULL);
+
+ lock();
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Stopped\n"));
+ m_enmState = RECORDINGSTS_CREATED;
+ }
+ else
+ Log(("Recording: Failed to stop (%Rrc)\n", vrc));
+
+ unlock();
+
+ LogFlowThisFunc(("%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Destroys a recording context, internal version.
+ */
+void RecordingContext::destroyInternal(void)
+{
+ lock();
+
+ if (m_enmState == RECORDINGSTS_UNINITIALIZED)
+ {
+ unlock();
+ return;
+ }
+
+ int vrc = stopInternal();
+ AssertRCReturnVoid(vrc);
+
+ vrc = RTSemEventDestroy(m_WaitEvent);
+ AssertRCReturnVoid(vrc);
+
+ m_WaitEvent = NIL_RTSEMEVENT;
+
+ RecordingStreams::iterator it = m_vecStreams.begin();
+ while (it != m_vecStreams.end())
+ {
+ RecordingStream *pStream = (*it);
+
+ vrc = pStream->Uninit();
+ AssertRC(vrc);
+
+ delete pStream;
+ pStream = NULL;
+
+ m_vecStreams.erase(it);
+ it = m_vecStreams.begin();
+ }
+
+ /* Sanity. */
+ Assert(m_vecStreams.empty());
+ Assert(m_mapBlocksRaw.size() == 0);
+ Assert(m_mapBlocksEncoded.size() == 0);
+
+ m_enmState = RECORDINGSTS_UNINITIALIZED;
+
+ unlock();
+}
+
+/**
+ * Returns a recording context's current settings.
+ *
+ * @returns The recording context's current settings.
+ */
+const settings::RecordingSettings &RecordingContext::GetConfig(void) const
+{
+ return m_Settings;
+}
+
+/**
+ * Returns the recording stream for a specific screen.
+ *
+ * @returns Recording stream for a specific screen, or NULL if not found.
+ * @param uScreen Screen ID to retrieve recording stream for.
+ */
+RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
+{
+ RecordingStream *pStream;
+
+ try
+ {
+ pStream = m_vecStreams.at(uScreen);
+ }
+ catch (std::out_of_range &)
+ {
+ pStream = NULL;
+ }
+
+ return pStream;
+}
+
+/**
+ * Locks the recording context for serializing access.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::lock(void)
+{
+ int vrc = RTCritSectEnter(&m_CritSect);
+ AssertRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unlocks the recording context for serializing access.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::unlock(void)
+{
+ int vrc = RTCritSectLeave(&m_CritSect);
+ AssertRC(vrc);
+ return vrc;
+}
+
+/**
+ * Retrieves a specific recording stream of a recording context.
+ *
+ * @returns Pointer to recording stream if found, or NULL if not found.
+ * @param uScreen Screen number of recording stream to look up.
+ */
+RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
+{
+ return getStreamInternal(uScreen);
+}
+
+/**
+ * Returns the number of configured recording streams for a recording context.
+ *
+ * @returns Number of configured recording streams.
+ */
+size_t RecordingContext::GetStreamCount(void) const
+{
+ return m_vecStreams.size();
+}
+
+/**
+ * Creates a new recording context.
+ *
+ * @returns VBox status code.
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ */
+int RecordingContext::Create(Console *ptrConsole, const settings::RecordingSettings &Settings)
+{
+ return createInternal(ptrConsole, Settings);
+}
+
+/**
+ * Destroys a recording context.
+ */
+void RecordingContext::Destroy(void)
+{
+ destroyInternal();
+}
+
+/**
+ * Starts a recording context.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::Start(void)
+{
+ return startInternal();
+}
+
+/**
+ * Stops a recording context.
+ */
+int RecordingContext::Stop(void)
+{
+ return stopInternal();
+}
+
+/**
+ * Returns if a specific recoding feature is enabled for at least one of the attached
+ * recording streams or not.
+ *
+ * @returns @c true if at least one recording stream has this feature enabled, or @c false if
+ * no recording stream has this feature enabled.
+ * @param enmFeature Recording feature to check for.
+ */
+bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
+{
+ lock();
+
+ RecordingStreams::const_iterator itStream = m_vecStreams.begin();
+ while (itStream != m_vecStreams.end())
+ {
+ if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
+ {
+ unlock();
+ return true;
+ }
+ ++itStream;
+ }
+
+ unlock();
+
+ return false;
+}
+
+/**
+ * Returns if this recording context is ready to start recording.
+ *
+ * @returns @c true if recording context is ready, @c false if not.
+ */
+bool RecordingContext::IsReady(void)
+{
+ lock();
+
+ const bool fIsReady = m_enmState >= RECORDINGSTS_CREATED;
+
+ unlock();
+
+ return fIsReady;
+}
+
+/**
+ * Returns if this recording context is ready to accept new recording data for a given screen.
+ *
+ * @returns @c true if the specified screen is ready, @c false if not.
+ * @param uScreen Screen ID.
+ * @param msTimestamp Timestamp (PTS, in ms). Currently not being used.
+ */
+bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
+{
+ RT_NOREF(msTimestamp);
+
+ lock();
+
+ bool fIsReady = false;
+
+ if (m_enmState != RECORDINGSTS_STARTED)
+ {
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if (pStream)
+ fIsReady = pStream->IsReady();
+
+ /* Note: Do not check for other constraints like the video FPS rate here,
+ * as this check then also would affect other (non-FPS related) stuff
+ * like audio data. */
+ }
+
+ unlock();
+
+ return fIsReady;
+}
+
+/**
+ * Returns whether a given recording context has been started or not.
+ *
+ * @returns true if active, false if not.
+ */
+bool RecordingContext::IsStarted(void)
+{
+ lock();
+
+ const bool fIsStarted = m_enmState == RECORDINGSTS_STARTED;
+
+ unlock();
+
+ return fIsStarted;
+}
+
+/**
+ * Checks if a specified limit for recording has been reached.
+ *
+ * @returns true if any limit has been reached.
+ */
+bool RecordingContext::IsLimitReached(void)
+{
+ lock();
+
+ LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
+
+ const bool fLimitReached = m_cStreamsEnabled == 0;
+
+ unlock();
+
+ return fLimitReached;
+}
+
+/**
+ * Checks if a specified limit for recording has been reached.
+ *
+ * @returns true if any limit has been reached.
+ * @param uScreen Screen ID.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
+{
+ lock();
+
+ bool fLimitReached = false;
+
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if ( !pStream
+ || pStream->IsLimitReached(msTimestamp))
+ {
+ fLimitReached = true;
+ }
+
+ unlock();
+
+ return fLimitReached;
+}
+
+/**
+ * Returns if a specific screen needs to be fed with an update or not.
+ *
+ * @returns @c true if an update is needed, @c false if not.
+ * @param uScreen Screen ID to retrieve update stats for.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+bool RecordingContext::NeedsUpdate( uint32_t uScreen, uint64_t msTimestamp)
+{
+ lock();
+
+ bool fNeedsUpdate = false;
+
+ if (m_enmState == RECORDINGSTS_STARTED)
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if ( recordingCodecIsInitialized(&m_CodecAudio)
+ && recordingCodecGetWritable(&m_CodecAudio, msTimestamp) > 0)
+ {
+ fNeedsUpdate = true;
+ }
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+ if (!fNeedsUpdate)
+ {
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if (pStream)
+ fNeedsUpdate = pStream->NeedsUpdate(msTimestamp);
+ }
+ }
+
+ unlock();
+
+ return fNeedsUpdate;
+}
+
+DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int vrc)
+{
+ RT_NOREF(uScreen, vrc);
+ LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, vrc));
+
+ lock();
+
+ Assert(m_cStreamsEnabled);
+ m_cStreamsEnabled--;
+
+ LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
+
+ unlock();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sends an audio frame to the recording thread.
+ *
+ * @returns VBox status code.
+ * @param pvData Audio frame data to send.
+ * @param cbData Size (in bytes) of (encoded) audio frame data.
+ * @param msTimestamp Timestamp (PTS, in ms) of audio playback.
+ */
+int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
+{
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ return writeCommonData(m_mapBlocksRaw, &m_CodecAudio,
+ pvData, cbData, msTimestamp, RECORDINGCODEC_ENC_F_BLOCK_IS_KEY);
+#else
+ RT_NOREF(pvData, cbData, msTimestamp);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/**
+ * Sends a video frame to the recording thread.
+ *
+ * @thread EMT
+ *
+ * @returns VBox status code.
+ * @param uScreen Screen number to send video frame to.
+ * @param x Starting x coordinate of the video frame.
+ * @param y Starting y coordinate of the video frame.
+ * @param uPixelFormat Pixel format.
+ * @param uBPP Bits Per Pixel (BPP).
+ * @param uBytesPerLine Bytes per scanline.
+ * @param uSrcWidth Width of the video frame.
+ * @param uSrcHeight Height of the video frame.
+ * @param puSrcData Pointer to video frame data.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingContext::SendVideoFrame(uint32_t uScreen, 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)
+{
+ AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(puSrcData, VERR_INVALID_POINTER);
+
+ lock();
+
+ RecordingStream *pStream = getStreamInternal(uScreen);
+ if (!pStream)
+ {
+ unlock();
+
+ AssertFailed();
+ return VERR_NOT_FOUND;
+ }
+
+ int vrc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp);
+
+ unlock();
+
+ if ( RT_SUCCESS(vrc)
+ && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
+ {
+ threadNotify();
+ }
+
+ return vrc;
+}
+