/* $Id: RecordingStream.cpp 98278 2023-01-24 11:55:00Z vboxsync $ */ /** @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 . * * SPDX-License-Identifier: GPL-3.0-only */ #ifdef LOG_GROUP # undef LOG_GROUP #endif #define LOG_GROUP LOG_GROUP_RECORDING #include "LoggingNew.h" #include #ifdef VBOX_RECORDING_DUMP # include #endif #ifdef VBOX_WITH_AUDIO_RECORDING # include #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 : "", 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); }