diff options
Diffstat (limited to 'src/VBox/Main/src-client/RecordingStream.cpp')
-rw-r--r-- | src/VBox/Main/src-client/RecordingStream.cpp | 1039 |
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); +} + |