diff --git a/dll/ApiHooks.cpp b/dll/ApiHooks.cpp index 96c3df1..cdc4f7b 100644 --- a/dll/ApiHooks.cpp +++ b/dll/ApiHooks.cpp @@ -431,6 +431,7 @@ bool WINAPI MsRdpEx_CaptureBlt( bool outputMirrorEnabled = false; bool videoRecordingEnabled = false; uint32_t videoRecordingQuality = 5; + uint32_t videoRecordingFrameRate = 0; bool dumpBitmapUpdates = false; IMsRdpExInstance* instance = NULL; MsRdpEx_OutputMirror* outputMirror = NULL; @@ -457,6 +458,7 @@ bool WINAPI MsRdpEx_CaptureBlt( outputMirrorEnabled = pExtendedSettings->GetOutputMirrorEnabled(); videoRecordingEnabled = pExtendedSettings->GetVideoRecordingEnabled(); videoRecordingQuality = pExtendedSettings->GetVideoRecordingQuality(); + videoRecordingFrameRate = pExtendedSettings->GetVideoRecordingFrameRate(); dumpBitmapUpdates = pExtendedSettings->GetDumpBitmapUpdates(); if (!outputMirrorEnabled) @@ -476,6 +478,7 @@ bool WINAPI MsRdpEx_CaptureBlt( MsRdpEx_OutputMirror_SetDumpBitmapUpdates(outputMirror, dumpBitmapUpdates); MsRdpEx_OutputMirror_SetVideoRecordingEnabled(outputMirror, videoRecordingEnabled); MsRdpEx_OutputMirror_SetVideoQualityLevel(outputMirror, videoRecordingQuality); + MsRdpEx_OutputMirror_SetVideoFrameRate(outputMirror, videoRecordingFrameRate); char* recordingPath = pExtendedSettings->GetRecordingPath(); if (recordingPath) { diff --git a/dll/OutputMirror.c b/dll/OutputMirror.c index ae20ad4..3212cef 100644 --- a/dll/OutputMirror.c +++ b/dll/OutputMirror.c @@ -34,6 +34,8 @@ struct _MsRdpEx_OutputMirror MsRdpEx_RecordingManifest* manifest; FILE* frameMetadataFile; + uint32_t videoFrameRate; + CRITICAL_SECTION lock; }; @@ -83,9 +85,9 @@ bool MsRdpEx_OutputMirror_DumpFrame(MsRdpEx_OutputMirror* ctx) captureTime = GetTickCount64() - ctx->captureBaseTime; if (ctx->videoRecordingEnabled && ctx->videoRecorder) { + // Submit every paint; cadeau caps the encode rate. A per-paint Timeout would force-encode and bypass that cap. MsRdpEx_VideoRecorder_UpdateFrame(ctx->videoRecorder, ctx->bitmapData, 0, 0, ctx->bitmapWidth, ctx->bitmapHeight, ctx->bitmapStep); - MsRdpEx_VideoRecorder_Timeout(ctx->videoRecorder); } if (ctx->dumpBitmapUpdates) { @@ -121,6 +123,11 @@ void MsRdpEx_OutputMirror_SetVideoQualityLevel(MsRdpEx_OutputMirror* ctx, uint32 ctx->videoQualityLevel = videoQualityLevel; } +void MsRdpEx_OutputMirror_SetVideoFrameRate(MsRdpEx_OutputMirror* ctx, uint32_t videoFrameRate) +{ + ctx->videoFrameRate = videoFrameRate; +} + void MsRdpEx_OutputMirror_SetRecordingPath(MsRdpEx_OutputMirror* ctx, const char* recordingPath) { strcpy_s(ctx->recordingPath, MSRDPEX_MAX_PATH, recordingPath); @@ -214,6 +221,10 @@ bool MsRdpEx_OutputMirror_Init(MsRdpEx_OutputMirror* ctx) MsRdpEx_VideoRecorder_SetFileName(ctx->videoRecorder, filename); MsRdpEx_VideoRecorder_SetVideoQuality(ctx->videoRecorder, ctx->videoQualityLevel); + if (ctx->videoFrameRate > 0) { + MsRdpEx_VideoRecorder_SetFrameRate(ctx->videoRecorder, ctx->videoFrameRate); + } + if (!MsRdpEx_StringIsNullOrEmpty(ctx->recordingPipeName)) { MsRdpEx_VideoRecorder_SetPipeName(ctx->videoRecorder, ctx->recordingPipeName); } diff --git a/dll/RdpSettings.cpp b/dll/RdpSettings.cpp index 2ff1d20..f0f3c1b 100644 --- a/dll/RdpSettings.cpp +++ b/dll/RdpSettings.cpp @@ -19,6 +19,8 @@ extern "C" const GUID IID_ITSPropertySet; extern MsRdpEx_mstscax g_mstscax; extern MsRdpEx_rdclientax g_rdclientax; +#define MSRDPEX_VIDEO_RECORDING_MAX_FRAME_RATE 30 + static bool g_TSPropertySet_Hooked = false; static ITSPropertySet_SetBoolProperty Real_ITSPropertySet_SetBoolProperty = NULL; @@ -826,6 +828,21 @@ HRESULT __stdcall CMsRdpExtendedSettings::put_Property(BSTR bstrPropertyName, VA hr = S_OK; } + else if (MsRdpEx_StringEquals(propName, "VideoRecordingFrameRate")) + { + if ((pValue->vt != VT_UI4) && (pValue->vt != VT_I4)) + goto end; + + if (pValue->vt == VT_I4) + m_VideoRecordingFrameRate = (pValue->intVal > 0) ? (uint32_t)pValue->intVal : 0; + else + m_VideoRecordingFrameRate = pValue->uintVal; + + if (m_VideoRecordingFrameRate > MSRDPEX_VIDEO_RECORDING_MAX_FRAME_RATE) + m_VideoRecordingFrameRate = MSRDPEX_VIDEO_RECORDING_MAX_FRAME_RATE; + + hr = S_OK; + } else if (MsRdpEx_StringEquals(propName, "RecordingPath")) { if (pValue->vt != VT_BSTR) @@ -971,6 +988,11 @@ HRESULT __stdcall CMsRdpExtendedSettings::get_Property(BSTR bstrPropertyName, VA pValue->intVal = (INT)m_VideoRecordingQuality; hr = S_OK; } + else if (MsRdpEx_StringEquals(propName, "VideoRecordingFrameRate")) { + pValue->vt = VT_I4; + pValue->intVal = (INT)m_VideoRecordingFrameRate; + hr = S_OK; + } else if (MsRdpEx_StringEquals(propName, "RecordingPath")) { pValue->vt = VT_BSTR; const char* recordingPath = m_RecordingPath ? m_RecordingPath : ""; @@ -1384,6 +1406,12 @@ HRESULT CMsRdpExtendedSettings::ApplyRdpFile(void* rdpFilePtr) pMsRdpExtendedSettings->put_Property(propName, &value); } } + else if (MsRdpEx_RdpFileEntry_IsMatch(entry, 'i', "VideoRecordingFrameRate")) { + if (MsRdpEx_RdpFileEntry_GetIntValue(entry, &value)) { + bstr_t propName = _com_util::ConvertStringToBSTR(entry->name); + pMsRdpExtendedSettings->put_Property(propName, &value); + } + } else if (MsRdpEx_RdpFileEntry_IsMatch(entry, 's', "RecordingPath")) { bstr_t propName = _com_util::ConvertStringToBSTR(entry->name); bstr_t propValue = _com_util::ConvertStringToBSTR(entry->value); @@ -1635,6 +1663,11 @@ uint32_t CMsRdpExtendedSettings::GetVideoRecordingQuality() return m_VideoRecordingQuality; } +uint32_t CMsRdpExtendedSettings::GetVideoRecordingFrameRate() +{ + return m_VideoRecordingFrameRate; +} + char* CMsRdpExtendedSettings::GetRecordingPath() { if (m_RecordingPath) diff --git a/include/MsRdpEx/OutputMirror.h b/include/MsRdpEx/OutputMirror.h index 121131c..16d6d21 100644 --- a/include/MsRdpEx/OutputMirror.h +++ b/include/MsRdpEx/OutputMirror.h @@ -22,6 +22,7 @@ bool MsRdpEx_OutputMirror_DumpFrame(MsRdpEx_OutputMirror* ctx); void MsRdpEx_OutputMirror_SetDumpBitmapUpdates(MsRdpEx_OutputMirror* ctx, bool dumpBitmapUpdates); void MsRdpEx_OutputMirror_SetVideoRecordingEnabled(MsRdpEx_OutputMirror* ctx, bool videoRecordingEnabled); void MsRdpEx_OutputMirror_SetVideoQualityLevel(MsRdpEx_OutputMirror* ctx, uint32_t videoQualityLevel); +void MsRdpEx_OutputMirror_SetVideoFrameRate(MsRdpEx_OutputMirror* ctx, uint32_t videoFrameRate); void MsRdpEx_OutputMirror_SetRecordingPath(MsRdpEx_OutputMirror* ctx, const char* recordingPath); void MsRdpEx_OutputMirror_SetRecordingPipeName(MsRdpEx_OutputMirror* ctx, const char* recordingPipeName); void MsRdpEx_OutputMirror_SetSessionId(MsRdpEx_OutputMirror* ctx, const char* sessionId); diff --git a/include/MsRdpEx/RdpSettings.h b/include/MsRdpEx/RdpSettings.h index 3e75f68..3e99258 100644 --- a/include/MsRdpEx/RdpSettings.h +++ b/include/MsRdpEx/RdpSettings.h @@ -62,6 +62,7 @@ class CMsRdpExtendedSettings : public IMsRdpExtendedSettings bool GetOutputMirrorEnabled(); bool GetVideoRecordingEnabled(); uint32_t GetVideoRecordingQuality(); + uint32_t GetVideoRecordingFrameRate(); char* GetRecordingPath(); char* GetRecordingSessionId(); char* GetRecordingPipeName(); @@ -88,6 +89,7 @@ class CMsRdpExtendedSettings : public IMsRdpExtendedSettings bool m_OutputMirrorEnabled = false; bool m_VideoRecordingEnabled = false; uint32_t m_VideoRecordingQuality = 5; + uint32_t m_VideoRecordingFrameRate = 0; char* m_RecordingPath = NULL; char* m_RecordingSessionId = NULL; char* m_RecordingPipeName = NULL;