diff --git a/SpoutCamDX.vcxproj b/SpoutCamDX.vcxproj index 394c5e2..5b3c6b2 100644 --- a/SpoutCamDX.vcxproj +++ b/SpoutCamDX.vcxproj @@ -28,23 +28,23 @@ DynamicLibrary - MultiByte + Unicode v141 DynamicLibrary - MultiByte + Unicode v141 DynamicLibrary v141 - MultiByte + Unicode DynamicLibrary v141 - MultiByte + Unicode @@ -141,6 +141,8 @@ copy /y $(TargetPath) release\win32\SpoutCam32.ax + + @@ -167,6 +169,9 @@ copy /y $(TargetPath) release\x64\SpoutCam64.ax + + PerMonitorHighDPIAware + diff --git a/SpoutDX/winproj2017/SpoutDX.vcxproj b/SpoutDX/winproj2017/SpoutDX.vcxproj index b70aca5..2c25244 100644 --- a/SpoutDX/winproj2017/SpoutDX.vcxproj +++ b/SpoutDX/winproj2017/SpoutDX.vcxproj @@ -50,27 +50,27 @@ StaticLibrary true - MultiByte + Unicode v141 StaticLibrary true - MultiByte + Unicode v141 StaticLibrary false true - MultiByte + Unicode v141 StaticLibrary false true - MultiByte + Unicode v141 diff --git a/baseclasses/winproj2017/BaseClasses.vcxproj b/baseclasses/winproj2017/BaseClasses.vcxproj index 3a67e78..bf413f6 100644 --- a/baseclasses/winproj2017/BaseClasses.vcxproj +++ b/baseclasses/winproj2017/BaseClasses.vcxproj @@ -43,6 +43,7 @@ StaticLibrary v141 + Unicode diff --git a/release/win32/SpoutCam32.ax b/release/win32/SpoutCam32.ax index 8399934..2109a5f 100644 Binary files a/release/win32/SpoutCam32.ax and b/release/win32/SpoutCam32.ax differ diff --git a/release/win32/SpoutCamProperties.cmd b/release/win32/SpoutCamProperties.cmd new file mode 100644 index 0000000..7e6ca20 --- /dev/null +++ b/release/win32/SpoutCamProperties.cmd @@ -0,0 +1,3 @@ +@echo off +cd /d "%~dp0" +START C:\Windows\System32\rundll32.exe SpoutCam32.ax,Configure diff --git a/release/win32/SpoutCamSettings.cmd b/release/win32/SpoutCamSettings.cmd deleted file mode 100644 index 75eff6b..0000000 --- a/release/win32/SpoutCamSettings.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -cd /d "%~dp0" -C:\Windows\System32\rundll32.exe SpoutCam32.ax,Configure diff --git a/release/x64/SpoutCam64.ax b/release/x64/SpoutCam64.ax index cfc2ca8..bcc7932 100644 Binary files a/release/x64/SpoutCam64.ax and b/release/x64/SpoutCam64.ax differ diff --git a/release/x64/SpoutCamProperties.cmd b/release/x64/SpoutCamProperties.cmd new file mode 100644 index 0000000..e2b88f7 --- /dev/null +++ b/release/x64/SpoutCamProperties.cmd @@ -0,0 +1,3 @@ +@echo off +cd /d "%~dp0" +START C:\Windows\System32\rundll32.exe SpoutCam64.ax,Configure diff --git a/release/x64/SpoutCamSettings.cmd b/release/x64/SpoutCamSettings.cmd deleted file mode 100644 index a6aaf16..0000000 --- a/release/x64/SpoutCamSettings.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -cd /d "%~dp0" -C:\Windows\System32\rundll32.exe SpoutCam64.ax,Configure diff --git a/source/cam.aps b/source/cam.aps new file mode 100644 index 0000000..a67f5b9 Binary files /dev/null and b/source/cam.aps differ diff --git a/source/cam.cpp b/source/cam.cpp index d74feee..b91f1ed 100644 --- a/source/cam.cpp +++ b/source/cam.cpp @@ -241,25 +241,46 @@ Add options for image mirror, flip and swap (RGB <> BGR) Requires SpoutCamSettings - Version 2.005 or greater Verson 2.017 + 09.10.20 Valentin Schmidt: Various changes to add a settings dialog + Verson 2.018 + 09.10.20 Valentin Schmidt: added Mirror/Flip/Swap options to dialog + Verson 2.019 + 12.10.20 CVCam::CreateInstance - SetProcessDPIAware() for clear options dialog + put_Settings simplify code for flags + Introduce ReleaseCamReceiver() + Remove connect/disconnect test + Showstatic if user and filter resolutions are different + Update verison.h - Version 2.020 + Correct CSpoutCamProperties::OnApplyChanges() + for handle to fps and resolution controls for old comparison + 13.10.20 CSpoutCamProperties::OnApplyChanges() to restore + fps & resolution list items on response to no change + Correct pvscc->InputSize.cx/cy in GetStreamCaps + Clean up unused code + Version 2.021 + 13.10.20 Correct Flip true by default + Made seed for xorshiftRand static to prevent frozen image + Returned to timing by RED5 + Conditional receive if DirectX initialization failed + Version 2.022 + 16.10.20 FillBuffer correct cast for TimeGetTime calculations + Use TimeBeginPeriod for maximum precision of TimeGetTime and Sleep + DirectX device not required - in SpoutDX class. + Do not go to static on first DirectX initialize. + 17.10.20 Change to Unicode for WCHAR folder name support + Verson 2.023 */ #pragma warning(disable:4244) #pragma warning(disable:4711) -#include -#include -#include -#include - #include "cam.h" -static HWND hwndButton = NULL; // dummy window for opengl context - // This is just a fast rand so that the static image isn't too slow for higher resolutions // Based on Marsaglia's xorshift generator (http://www.jstatsoft.org/v08/i14/paper) // and copied from (https://excamera.com/sphinx/article-xorshift.html) -uint32_t seed = 7; // 100% random seed value +static uint32_t seed = 7; // 100% random seed value static uint32_t xorshiftRand() { seed ^= seed << 13; @@ -268,7 +289,6 @@ static uint32_t xorshiftRand() return seed; } - ////////////////////////////////////////////////////////////////////////// // CVCam is the source filter which masquerades as a capture device ////////////////////////////////////////////////////////////////////////// @@ -279,7 +299,10 @@ CUnknown * WINAPI CVCam::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr) // debug console window // OpenSpoutConsole(); // Empty console // EnableSpoutLog(); // Show error logs - // printf("SpoutCamDX ~~ Vers 2.017\n"); + // printf("SpoutCamDX ~~ Vers 2.023\n"); + + // For clear options dialog for scaled display + SetProcessDPIAware(); CUnknown *punk = new CVCam(lpunk, phr); @@ -287,7 +310,7 @@ CUnknown * WINAPI CVCam::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr) } CVCam::CVCam(LPUNKNOWN lpunk, HRESULT *phr) : - CSource(NAME(SpoutCamName), lpunk, CLSID_SpoutCam) + CSource(NAME(SPOUTCAMNAME), lpunk, CLSID_SpoutCam) //VS: replaced SpoutCamName with makro SPOUTCAMNAME, NAME() expects LPCTSTR { ASSERT(phr); @@ -297,6 +320,7 @@ CVCam::CVCam(LPUNKNOWN lpunk, HRESULT *phr) : m_paStreams = (CSourceStream **)new CVCamStream*[1]; m_paStreams[0] = new CVCamStream(phr, this, (LPCWSTR)&SpoutCamName); + if (phr) *phr = S_OK; //VS } // Retrieves pointers to the supported interfaces on an object. @@ -308,6 +332,12 @@ HRESULT CVCam::QueryInterface(REFIID riid, void **ppv) riid == _uuidof(IAMDroppedFrames) || riid == _uuidof(IKsPropertySet)) return m_paStreams[0]->QueryInterface(riid, ppv); + //<==================== VS-START ====================> + else if (riid == IID_ICamSettings) + return GetInterface((ICamSettings *)this, ppv); + else if (riid == IID_ISpecifyPropertyPages) + return GetInterface((ISpecifyPropertyPages *)this, ppv); + //<==================== VS-END ======================> else return CSource::QueryInterface(riid, ppv); } @@ -332,6 +362,33 @@ STDMETHODIMP CVCam::JoinFilterGraph( return hr; } +//<==================== VS-START ====================> + +////////////////////////////////////////////////////////////////////////// +// ISpecifyPropertyPages interface +////////////////////////////////////////////////////////////////////////// + +// GetPages +// Returns the clsid's of the property pages we support +STDMETHODIMP CVCam::GetPages(CAUUID *pPages) +{ + CheckPointer(pPages, E_POINTER); + pPages->cElems = 1; + pPages->pElems = (GUID *)CoTaskMemAlloc(sizeof(GUID)); + if (pPages->pElems == NULL) + return E_OUTOFMEMORY; + pPages->pElems[0] = CLSID_SpoutCamPropertyPage; + return S_OK; +} + +STDMETHODIMP CVCam::put_Settings(DWORD dwFps, DWORD dwResolution, DWORD dwMirror, DWORD dwSwap, DWORD dwFlip) +{ + ((CVCamStream *)m_paStreams[0])->put_Settings(dwFps, dwResolution, dwMirror, dwSwap, dwFlip); + return S_OK; +} + +//<==================== VS-END ======================> + /////////////////////////////////////////////////////////// // all inherited virtual functions /////////////////////////////////////////////////////////// @@ -398,7 +455,6 @@ HRESULT STDMETHODCALLTYPE CVCam::Run(REFERENCE_TIME tStart) return hr; } - HRESULT STDMETHODCALLTYPE CVCam::SetSyncSource(__in_opt IReferenceClock *pClock) { return CSource::SetSyncSource(pClock); @@ -435,25 +491,45 @@ HRESULT STDMETHODCALLTYPE CVCam::Unregister( void) } ////////////////////////////////////////////////////////////////////////// +//<==================== VS-START ====================> +HRESULT CVCamStream::put_Settings(DWORD dwFps, DWORD dwResolution, DWORD dwMirror, DWORD dwSwap, DWORD dwFlip) +{ + // Calling Disconnect() would disconnect SpoutCam with a connected filter (e.g. Color Space Converter) in GraphStudioNext, + // but something is still missing, you can't reconnect the existing filters with the new cam settings. + // For now, a better solution is to let the user select "Disconnect all Filters" from the "Graph" menu after SpoutCam's settings + // were changed by its Properties dialog, then rendering SpoutCam's output pin works fine and all filters are successfully reconnected, + // taking into account the new fps/resolution settings. + SetFps(dwFps); + SetResolution(dwResolution); + receiver.m_bMirror = (dwMirror > 0); + receiver.m_bSwapRB = (dwSwap > 0); + // Flip is true by default - so false will appear inverted + bInvert = !(dwFlip > 0); + + return GetMediaType(0, &m_mt); +} +//<==================== VS-END ======================> ////////////////////////////////////////////////////////////////////////// // CVCamStream is the one and only output pin of CVCam which handles // all the stuff. ////////////////////////////////////////////////////////////////////////// CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) : - CSourceStream(NAME(SpoutCamName), phr, pParent, pPinName), m_pParent(pParent) + CSourceStream(NAME(SPOUTCAMNAME), phr, pParent, pPinName), m_pParent(pParent) //VS: replaced SpoutCamName with makro SPOUTCAMNAME, NAME() expects LPCTSTR { - g_pd3dDevice = nullptr; bDXinitialized = false; // DirectX bMemoryMode = false; // Default mode is texture, true means memoryshare bInvert = true; // Not currently used bInitialized = false; // Spoutcam receiver - bDisconnected = false; // Has to connect before can disconnect or it will never connect g_Width = 640; // give it an initial size - this will be changed if a sender is running at start g_Height = 480; g_SenderName[0] = 0; g_ActiveSender[0] = 0; + + // Maximum precision of timeGetTime used in FillBuffer + timeGetDevCaps(&g_caps, sizeof(g_caps)); + timeBeginPeriod(g_caps.wPeriodMin); // // Retrieve fps and resolution from registry "SpoutCamConfig" @@ -466,13 +542,9 @@ CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) : // 50 4 // 60 5 // - dwFps = 3; // Fps from SpoutCamConfig (default 3 = 30) + // Fps from SpoutCamConfig (default 3 = 30) if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "fps", &dwFps)) { dwFps = 3; - g_FrameTime = 333333; - } - else { - SetFps(dwFps); } // o Resolution @@ -488,28 +560,10 @@ CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) : // 1280 x 1024 9 // 1920 x 1080 10 // - dwResolution = 0; // Resolution from SpoutCamConfig (default 0 = active sender) - ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", &dwResolution); - - // If there is no Sender, getmediatype will use the resolution set by the user - - // Resolution index 0 means that the user has selected "Active sender" - if(dwResolution == 0) { - // Use the resolution of the active sender if one is running - if(receiver.GetActiveSender(g_SenderName)) { - unsigned int width, height; - HANDLE sharehandle; - DWORD format; - if (receiver.GetSenderInfo(g_SenderName, width, height, sharehandle, format)) { - // If not fixed to the a selected resolution, use the sender width and height - g_Width = width; - g_Height = height; - } - } - } - else { - // Set g_Width and g_Height according to the registry setting - SetResolution(dwResolution); + // Resolution from SpoutCamConfig (default 0 = active sender) + if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", &dwResolution)) + { + dwResolution = 0; } // Find out whether memoryshare mode is selected by SpoutSettings @@ -520,40 +574,30 @@ CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) : // Options in SpoutCamSettings // - DWORD dwMode = 0; // Mirror image - ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "mirror", &dwMode); - if(dwMode > 0) - receiver.m_bMirror = true; - else - receiver.m_bMirror = false; + DWORD dwMirror = 0; + ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "mirror", &dwMirror); // RGB <> BGR - ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "swap", &dwMode); - if (dwMode > 0) - receiver.m_bSwapRB = true; - else - receiver.m_bSwapRB = false; + DWORD dwSwap = 0; + ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "swap", &dwSwap); // Flip image // Default is flipped due to upside down Windows bitmap // If set false, the result comes out inverted // bInvert = false; - ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "flip", &dwMode); - if (dwMode > 0) - bInvert = false; - else - bInvert = true; + DWORD dwFlip = 0; + ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "flip", &dwFlip); - m_Fps = dwFps; - m_Resolution = dwResolution; - - // Set mediatype to active sender width and height, or user defaults - GetMediaType(0, &m_mt); + //<==================== VS-START ====================> + // Now always the new function put_Settings is called, which in turn calls + // SetFps and SetResolution. Dealing with resolution 0 (Sender) was moved to + // SetResolution. + put_Settings(dwFps, dwResolution, dwMirror, dwSwap, dwFlip); + //<==================== VS-END ======================> NumDroppedFrames = 0LL; NumFrames = 0LL; - hwndButton = NULL; // ensure NULL of static variable for the OpenGL window handle // // Special purpose option : Lock to a specific sender @@ -601,11 +645,33 @@ void CVCamStream::SetFps(DWORD dwFps) } } - void CVCamStream::SetResolution(DWORD dwResolution) { switch(dwResolution) { + + //<==================== VS-START ====================> // Case 0 - use the active sender or default + case 0: + { + // If there is no Sender, getmediatype will use the resolution set by the user + // Resolution index 0 means that the user has selected "Active sender" + // Use the resolution of the active sender if one is running + if (receiver.GetActiveSender(g_SenderName)) + { + unsigned int width, height; + HANDLE sharehandle; + DWORD format; + if (receiver.GetSenderInfo(g_SenderName, width, height, sharehandle, format)) + { + // If not fixed to the a selected resolution, use the sender width and height + g_Width = width; + g_Height = height; + } + } + } + break; + //<==================== VS-END ======================> + case 1 : g_Width = 320; // 1 g_Height = 240; @@ -661,6 +727,9 @@ CVCamStream::~CVCamStream() if (bDXinitialized) receiver.CleanupDX11(); + // End timer precision + timeEndPeriod(g_caps.wPeriodMin); + } HRESULT CVCamStream::QueryInterface(REFIID riid, void **ppv) @@ -697,7 +766,7 @@ HRESULT CVCamStream::FillBuffer(IMediaSample *pms) HRESULT hr = S_OK; BYTE *pData = nullptr; - VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) m_mt.Format(); + VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)m_mt.Format(); // If graph is inactive stop cueing samples if(!m_pParent->IsActive()) { @@ -707,116 +776,130 @@ HRESULT CVCamStream::FillBuffer(IMediaSample *pms) // Memory share mode not supported by DirectX if (bMemoryMode) return FALSE; - + // - // Simple timing modifed from original Vcam - // https://github.com/johnmaccormick/MultiCam/blob/master/vcam/Filters.cpp + // Timing - modified from Red5 method // // Set the timestamps that will govern playback frame rate. // The current time is the sample's start. REFERENCE_TIME rtNow = m_rtLastTime; - REFERENCE_TIME avgFrameTime = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame; + REFERENCE_TIME avgFrameTime = g_FrameTime; // Desired average frame time is set by the user + + // Create some working info + REFERENCE_TIME rtDelta, rtDelta2 = 0LL; // delta for dropped, delta 2 for sleep. + + // + // What time is it REALLY ??? + // + m_pParent->GetSyncSource(&m_pClock); + if (m_pClock) { + m_pClock->GetTime(&refSync1); + m_pClock->Release(); + } + else { + // Some programs do not implement the DirectShow clock and can crash if assumed + // so we can use TimeGetTime instead. Only millisecond precision, but so is Sleep. + refSync1 = (REFERENCE_TIME)timeGetTime() * 10000LL; // Cast before the multiply to avoid overflow + } + + if (NumFrames <= 1) { + // initiate values + refStart = refSync1; // FirstFrame No Drop + refSync2 = 0; + } + rtNow = m_rtLastTime; - // Sleep to meet the frame rate. - // elapsed will be zero for the first call. - REFERENCE_TIME elapsed = (REFERENCE_TIME)EndTiming() / 1000LL; // Sleep is msec precision - if (elapsed < avgFrameTime / 10000LL) { - DWORD dwSleep = (DWORD)(avgFrameTime / 10000LL - elapsed); - Sleep(dwSleep); + m_rtLastTime = avgFrameTime + m_rtLastTime; + + // IAMDropppedFrame. We only have avgFrameTime to generate image. + // Find generated stream time and compare to real elapsed time. + rtDelta = ((refSync1 - refStart) - (((NumFrames)*avgFrameTime) - avgFrameTime)); + if (rtDelta - refSync2 < 0) { + // we are early + rtDelta2 = rtDelta - refSync2; + DWORD dwSleep = (DWORD)abs(rtDelta2 / 10000LL); + if (dwSleep >= 1) + Sleep(dwSleep); } - // Look for dropped frames and adjust. - REFERENCE_TIME droppedFrames = elapsed / (avgFrameTime / 10000LL); - if (droppedFrames > 0) { + else if (rtDelta / avgFrameTime > NumDroppedFrames) { + // new dropped frame + NumDroppedFrames = rtDelta / avgFrameTime; + // Figure new RT for sleeping + refSync2 = NumDroppedFrames * avgFrameTime; // Our time stamping needs adjustment. - // Find total real stream. - rtNow = rtNow + droppedFrames * avgFrameTime; + // Find total real stream time from start time. + rtNow = refSync1 - refStart; m_rtLastTime = rtNow + avgFrameTime; pms->SetDiscontinuity(true); } - else { - m_rtLastTime += avgFrameTime; - } // The SetTime method sets the stream times when this sample should begin and finish. - pms->SetTime(&rtNow, &m_rtLastTime); + hr = pms->SetTime(&rtNow, &m_rtLastTime); // Set true on every sample for uncompressed frames - pms->SetSyncPoint(TRUE); - - // Reset timer for sleep calculations - StartTiming(); + hr = pms->SetSyncPoint(true); + // ============== END OF INITIAL TIMING ============ // Check access to the sample's data buffer pms->GetPointer(&pData); - if(pData == NULL) + if (pData == NULL) { return NOERROR; + } // Get the current frame size for texture transfers imagesize = (unsigned int)pvi->bmiHeader.biSizeImage; width = (unsigned int)pvi->bmiHeader.biWidth; height = (unsigned int)pvi->bmiHeader.biHeight; - if(width == 0 || height == 0) + if (width == 0 || height == 0) { return NOERROR; + } - // Don't do anything if disconnected because it will already have connected - // previously and something has changed. It can only disconnect after it has connected. - if(!bDisconnected) { - - // If connected, sizes should be OK, but check again - unsigned int size = (unsigned int)pms->GetSize(); - imagesize = width*height*3; // Retrieved above - if(size != imagesize) { - receiver.ReleaseReceiver(); - bDisconnected = true; // don't try again - return NOERROR; - } - - // Quit if nothing running at all - if(!receiver.GetActiveSender(g_ActiveSender)) { - receiver.ReleaseReceiver(); - goto ShowStatic; - } + // Sizes should be OK, but check again + unsigned int size = (unsigned int)pms->GetSize(); + // TODO : check imagesize = width*height*3; + if(size != imagesize) { // imagesize retrieved above + ReleaseCamReceiver(); + goto ShowStatic; + } - // Initialize DirectX if is has not been done - if(!bDXinitialized) { - if (receiver.OpenDirectX11()) { - g_pd3dDevice = receiver.GetDevice(); - } - else { - bDXinitialized = false; - bDisconnected = true; // don't try again - return NOERROR; - } - } // endif !bDXinitialized - bDXinitialized = true; + // Quit if nothing running at all + if(!receiver.GetActiveSender(g_ActiveSender)) { + ReleaseCamReceiver(); + goto ShowStatic; + } - // Get bgr pixels from the sender bgra shared texture - // ReceiveImage handles sender detection, connection and copy of pixels - if (receiver.ReceiveImage(pData, g_Width, g_Height, true, bInvert)) { - // rgb(not rgba) = true, invert = true - // If IsUpdated() returns true, the sender has changed - if (receiver.IsUpdated()) { - if (strcmp(g_SenderName, receiver.GetSenderName()) != 0) { - // Only test for change of sender name. - // The pixel buffer (pData) remains the same size and - // ReceiveImage uses resampling for a different texture size - strcpy_s(g_SenderName, 256, receiver.GetSenderName()); - // Set the sender name to the registry for SpoutCamSettings - WritePathToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "sendername", g_SenderName); - } - } - bInitialized = true; - NumFrames++; + // Initialize DirectX if is has not been done + // TODO : prevent retries + if(!bDXinitialized) { + if (!receiver.OpenDirectX11()) { return NOERROR; } - else { - if (bInitialized) { - receiver.ReleaseReceiver(); - bInitialized = false; + bDXinitialized = true; + } // endif !bDXinitialized + + // DirectX is initialized OK + // Get bgr pixels from the sender bgra shared texture + // ReceiveImage handles sender detection, connection and copy of pixels + if (receiver.ReceiveImage(pData, g_Width, g_Height, true, bInvert)) { + // rgb(not rgba) = true, invert = true + // If IsUpdated() returns true, the sender has changed + if (receiver.IsUpdated()) { + if (strcmp(g_SenderName, receiver.GetSenderName()) != 0) { + // Only test for change of sender name. + // The pixel buffer (pData) remains the same size and + // ReceiveImage uses resampling for a different texture size + strcpy_s(g_SenderName, 256, receiver.GetSenderName()); + // Set the sender name to the registry for SpoutCamSettings + WritePathToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "sendername", g_SenderName); } } - - } // endif not disconnected + bInitialized = true; + NumFrames++; + return NOERROR; + } + else { + ReleaseCamReceiver(); + } ShowStatic : @@ -833,6 +916,16 @@ ShowStatic : } // FillBuffer +// Conditionally release receiver and reset flag +void CVCamStream::ReleaseCamReceiver() +{ + if (bInitialized) { + receiver.ReleaseReceiver(); + bInitialized = false; + } +} + + // // Notify // Ignore quality management messages sent from the downstream filter @@ -867,7 +960,6 @@ HRESULT CVCamStream::GetMediaType(int iPosition, CMediaType *pmt) if (iPosition > 1) { return VFW_S_NO_MORE_ITEMS; } - DECLARE_PTR(VIDEOINFOHEADER, pvi, pmt->AllocFormatBuffer(sizeof(VIDEOINFOHEADER))); ZeroMemory(pvi, sizeof(VIDEOINFOHEADER)); @@ -911,7 +1003,6 @@ HRESULT CVCamStream::GetMediaType(int iPosition, CMediaType *pmt) const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader); pmt->SetSubtype(&SubTypeGUID); pmt->SetVariableSize(); // LJ - to be checked - pmt->SetSampleSize(pvi->bmiHeader.biSizeImage); return NOERROR; @@ -1061,8 +1152,8 @@ HRESULT STDMETHODCALLTYPE CVCamStream::GetStreamCaps(int iIndex, AM_MEDIA_TYPE * // For a capture filter, the size is the largest signal the filter // can digitize with every pixel remaining unique. // Note Deprecated. - pvscc->InputSize.cx = 1920; - pvscc->InputSize.cy = 1080; + pvscc->InputSize.cx = (LONG)width; // 1920; + pvscc->InputSize.cy = (LONG)height; // 1080; pvscc->MinCroppingSize.cx = 0; // LJ was 80 but we don't want to limit it pvscc->MinCroppingSize.cy = 0; // was 60 pvscc->MaxCroppingSize.cx = 1920; diff --git a/source/cam.def b/source/cam.def index 591a4dd..b562402 100644 --- a/source/cam.def +++ b/source/cam.def @@ -5,3 +5,4 @@ EXPORTS DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE + Configure PRIVATE diff --git a/source/cam.h b/source/cam.h index 19e3de2..08b3cda 100644 --- a/source/cam.h +++ b/source/cam.h @@ -8,6 +8,7 @@ // 28.09.15 - updated with modifications by John MacCormick, 2012. // 10.07.16 - Modified for "SpoutCamConfig" for starting fps and resolution // 17.10.19 - Clean up for DirectX methods +// 13.10.20 - Clean up unused variables // #pragma once @@ -16,7 +17,6 @@ #define DECLARE_PTR(type, ptr, expr) type* ptr = (type*)(expr); - // leak checking // http://www.codeproject.com/Articles/9815/Visual-Leak-Detector-Enhanced-Memory-Leak-Detectio // @@ -31,11 +31,41 @@ #include "..\SpoutDX\source\SpoutDX.h" #include +// LJ DEBUG +#include +#include +using namespace std::chrono_literals; + +//<==================== VS-START ====================> +#include "dshowutil.h" + +// we need a LPCTSTR for the NAME() makros in debug mode +#define SPOUTCAMNAME "SpoutCam" + +extern "C" { + DECLARE_INTERFACE_(ICamSettings, IUnknown) + { + STDMETHOD(put_Settings) (THIS_ + DWORD dwFps, + DWORD dwResolution, + DWORD dwMirror, + DWORD dwSwap, + DWORD dwFlip + ) PURE; + }; +} + +EXTERN_C const GUID CLSID_SpoutCamPropertyPage; +EXTERN_C const GUID IID_ICamSettings; +//<==================== VS-END ======================> + EXTERN_C const GUID CLSID_SpoutCam; EXTERN_C const WCHAR SpoutCamName[MAX_PATH]; class CVCamStream; -class CVCam : public CSource +class CVCam : public CSource, + public ISpecifyPropertyPages,//VS + public ICamSettings//VS { public: ////////////////////////////////////////////////////////////////////////// @@ -51,6 +81,14 @@ class CVCam : public CSource STDMETHODIMP JoinFilterGraph(__inout_opt IFilterGraph * pGraph, __in_opt LPCWSTR pName); + //<==================== VS-START ====================> + // ISpecifyPropertyPages interface + STDMETHODIMP GetPages(CAUUID *pPages); + + // ICamSettings interface + STDMETHODIMP put_Settings(DWORD dwFps, DWORD dwResolution, DWORD dwMirror, DWORD dwSwap, DWORD dwFlip); + //<==================== VS-END ======================> + private: CVCam(LPUNKNOWN lpunk, HRESULT *phr); @@ -162,13 +200,11 @@ class CVCamStream : public CSourceStream, public IAMStreamConfig, public IKsProp HRESULT GetMediaType(int iPosition, CMediaType *pmt); HRESULT SetMediaType(const CMediaType *pmt); HRESULT OnThreadCreate(void); - - int m_Fps; - int m_Resolution; - bool m_bLock; + HRESULT put_Settings(DWORD dwFps, DWORD dwResolution, DWORD dwMirror, DWORD dwSwap, DWORD dwFlip); //VS void SetFps(DWORD dwFps); void SetResolution(DWORD dwResolution); + void ReleaseCamReceiver(); // ============== IPC functions ============== // @@ -179,23 +215,18 @@ class CVCamStream : public CSourceStream, public IAMStreamConfig, public IKsProp char g_SenderName[256]; char g_ActiveSender[256]; // The name of any Spout sender being received - ID3D11Device* g_pd3dDevice; // DirectX 11.0 device pointer bool bMemoryMode; // true = memory, false = texture bool bInvert; - bool bDebug; bool bInitialized; bool bDXinitialized; - bool bDisconnected; // Sender had started but it has stopped or changed image size - - GLenum glBGRmode; unsigned int g_Width; // The global filter image width unsigned int g_Height; // The global filter image height DWORD dwFps; // Fps from SpoutCamConfig DWORD dwResolution; // Resolution from SpoutCamConfig - DWORD dwLock; // Fix to the selected resolution int g_FrameTime; // Frame time to use based on fps selection + TIMECAPS g_caps; // Timer capability for Sleep precision private: diff --git a/source/camprops.cpp b/source/camprops.cpp new file mode 100644 index 0000000..f3f2d48 --- /dev/null +++ b/source/camprops.cpp @@ -0,0 +1,268 @@ +#include +#include +#include +#include + +#include "cam.h" + +#include "camprops.h" +#include "resource.h" +#include "version.h" + +// CreateInstance +// Used by the DirectShow base classes to create instances +CUnknown *CSpoutCamProperties::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr) +{ + ASSERT(phr); + CUnknown *punk = new CSpoutCamProperties(lpunk, phr); + if (punk == NULL) + { + if (phr) + *phr = E_OUTOFMEMORY; + } + return punk; +} // CreateInstance + +// Constructor +CSpoutCamProperties::CSpoutCamProperties(LPUNKNOWN pUnk, HRESULT *phr) : + CBasePropertyPage("Settings", pUnk, IDD_SPOUTCAMBOX, IDS_TITLE), + m_pCamSettings(NULL), + m_bIsInitialized(FALSE) +{ + ASSERT(phr); + if (phr) + *phr = S_OK; +} + +//OnReceiveMessage is called when the dialog receives a window message. +INT_PTR CSpoutCamProperties::OnReceiveMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + { + if (m_bIsInitialized) + { + m_bDirty = TRUE; + if (m_pPageSite) + { + m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY); + } + } + return (LRESULT)1; + } + } + + // Let the parent class handle the message. + return CBasePropertyPage::OnReceiveMessage(hwnd, uMsg, wParam, lParam); +} + +//OnConnect is called when the client creates the property page. It sets the IUnknown pointer to the filter. +HRESULT CSpoutCamProperties::OnConnect(IUnknown *pUnknown) +{ + TRACE("OnConnect"); + CheckPointer(pUnknown, E_POINTER); + ASSERT(m_pCamSettings == NULL); + + HRESULT hr = pUnknown->QueryInterface(IID_ICamSettings, (void **)&m_pCamSettings); + if (FAILED(hr)) + { + return E_NOINTERFACE; + } + + // Get the initial image FX property + CheckPointer(m_pCamSettings, E_FAIL); + + m_bIsInitialized = FALSE; + + return S_OK; +} + +//OnDisconnect is called when the user dismisses the property sheet. +HRESULT CSpoutCamProperties::OnDisconnect() +{ + TRACE("OnDisconnect"); + // Release of Interface after setting the appropriate old effect value + if (m_pCamSettings) + { + m_pCamSettings->Release(); + m_pCamSettings = NULL; + } + + return S_OK; +} + +//OnActivate is called when the dialog is created. +HRESULT CSpoutCamProperties::OnActivate() +{ + TRACE("OnActivate"); + + HWND hwndCtl; + DWORD dwValue; + + //////////////////////////////////////// + // Fps + //////////////////////////////////////// + + // Retrieve fps from registry + if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "fps", &dwValue) || dwValue > 5) + { + dwValue = 3; // default 3 = 30 fps + } + + hwndCtl = GetDlgItem(this->m_Dlg, IDC_FPS); + + WCHAR fps_values[6][3] = + { + L"10", + L"15", + L"25", + L"30", // default + L"50", + L"60" + }; + + WCHAR fps[3]; + int k = 0; + + memset(&fps, 0, sizeof(fps)); + for (k = 0; k <= 5; k += 1) + { + wcscpy_s(fps, sizeof(fps) / sizeof(WCHAR), fps_values[k]); + ComboBox_AddString(hwndCtl, fps); + } + + // Select default + ComboBox_SetCurSel(hwndCtl, dwValue); + + //////////////////////////////////////// + // Resolution + //////////////////////////////////////// + + // Retrieve resolution from registry "SpoutCamConfig" + if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", &dwValue) || dwValue > 10) + { + dwValue = 0; // default 0 = active sender + } + + hwndCtl = GetDlgItem(this->m_Dlg, IDC_RESOLUTION); + + WCHAR res_values[11][14] = + { + L"Active Sender", + L"320 x 240", + L"640 x 360", + L"640 x 480", // default if no sender + L"800 x 600", + L"1024 x 720", + L"1024 x 768", + L"1280 x 720", + L"1280 x 960", + L"1280 x 1024", + L"1920 x 1080" + }; + + WCHAR res[14]; + memset(&res, 0, sizeof(res)); + for (k = 0; k <= 10; k += 1) + { + wcscpy_s(res, sizeof(res) / sizeof(WCHAR), res_values[k]); + ComboBox_AddString(hwndCtl, res); + } + + // Select current value + ComboBox_SetCurSel(hwndCtl, dwValue); + + //////////////////////////////////////// + // Mirror image + //////////////////////////////////////// + if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "mirror", &dwValue)) + { + dwValue = 0; + } + hwndCtl = GetDlgItem(this->m_Dlg, IDC_MIRROR); + Button_SetCheck(hwndCtl, dwValue); + + //////////////////////////////////////// + // Swap RGB <> BGR + //////////////////////////////////////// + if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "swap", &dwValue)) + { + dwValue = 0; + } + hwndCtl = GetDlgItem(this->m_Dlg, IDC_SWAP); + Button_SetCheck(hwndCtl, dwValue); + + //////////////////////////////////////// + // Flip image + // Default is flipped due to upside down Windows bitmap + // If set false, the result comes out inverted + // bInvert = false; + //////////////////////////////////////// + if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "flip", &dwValue)) + { + dwValue = 0; + } + hwndCtl = GetDlgItem(this->m_Dlg, IDC_FLIP); + Button_SetCheck(hwndCtl, dwValue); + + // Show SpoutCam version + hwndCtl = GetDlgItem(this->m_Dlg, IDC_VERS); + Static_SetText(hwndCtl, L"Version: " _VER_VERSION_STRING); + + m_bIsInitialized = TRUE; + + if (m_pPageSite) + { + m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY); + } + + return S_OK; +} + +//OnApplyChanges is called when the user commits the property changes by clicking the OK or Apply button. +HRESULT CSpoutCamProperties::OnApplyChanges() +{ + TRACE("OnApplyChanges"); + + DWORD dwFps, dwResolution, dwMirror, dwSwap, dwFlip; + + // ================================= + // Get old fps and resolution for user warning + DWORD dwOldFps, dwOldResolution; + ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "fps", &dwOldFps); + ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", &dwOldResolution); + dwFps = ComboBox_GetCurSel(GetDlgItem(this->m_Dlg, IDC_FPS)); + dwResolution = ComboBox_GetCurSel(GetDlgItem(this->m_Dlg, IDC_RESOLUTION)); + if (dwOldFps != dwFps || dwOldResolution != dwResolution) { + if (MessageBoxA(NULL, "For change of resolution or fps, you\nhave to stop and re-start SpoutCam\nDo you want to change ? ", "Warning", MB_YESNO | MB_TOPMOST | MB_ICONQUESTION) == IDNO) { + if (dwOldFps != dwFps) + ComboBox_SetCurSel(GetDlgItem(this->m_Dlg, IDC_FPS), dwOldFps); + if (dwOldResolution != dwResolution) + ComboBox_SetCurSel(GetDlgItem(this->m_Dlg, IDC_RESOLUTION), dwOldResolution); + return -1; + } + } + + // ================================= + + WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "fps", dwFps); + WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", dwResolution); + + HWND hwndCtl = GetDlgItem(this->m_Dlg, IDC_MIRROR); + dwMirror = Button_GetCheck(hwndCtl); + WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "mirror", dwMirror); + + hwndCtl = GetDlgItem(this->m_Dlg, IDC_SWAP); + dwSwap = Button_GetCheck(hwndCtl); + WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "swap", dwSwap); + + hwndCtl = GetDlgItem(this->m_Dlg, IDC_FLIP); + dwFlip = Button_GetCheck(hwndCtl); + WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "flip", dwFlip); + + if (m_pCamSettings) + m_pCamSettings->put_Settings(dwFps, dwResolution, dwMirror, dwSwap, dwFlip); + + return S_OK; +} diff --git a/source/camprops.h b/source/camprops.h new file mode 100644 index 0000000..951dca8 --- /dev/null +++ b/source/camprops.h @@ -0,0 +1,24 @@ +#include + +class CSpoutCamProperties : public CBasePropertyPage +{ + +public: + + static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr); + +private: + + INT_PTR OnReceiveMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + HRESULT OnConnect(IUnknown *pUnknown); + HRESULT OnDisconnect(); + HRESULT OnActivate(); + //HRESULT OnDeactivate(); + HRESULT OnApplyChanges(); + + CSpoutCamProperties(LPUNKNOWN lpunk, HRESULT *phr); + + BOOL m_bIsInitialized; // Used to ignore startup messages + ICamSettings *m_pCamSettings; // The custom interface on the filter +}; + diff --git a/source/dialog.aps b/source/dialog.aps new file mode 100644 index 0000000..0d50eab Binary files /dev/null and b/source/dialog.aps differ diff --git a/source/dialog.rc b/source/dialog.rc new file mode 100644 index 0000000..4121fa8 --- /dev/null +++ b/source/dialog.rc @@ -0,0 +1,38 @@ +// Generated by ResEdit 1.6.6 +// Copyright (C) 2006-2015 +// http://www.resedit.net + +#include +#include "resource.h" + +// +// Dialog resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SPOUTCAMBOX DIALOG 0, 0, 211, 140 +STYLE DS_CENTER | DS_SHELLFONT | WS_CHILD +FONT 8, "Ms Shell Dlg" +{ + GROUPBOX "Frame Rate (fps)", -1, 13, 7, 185, 32, 0, WS_EX_LEFT + COMBOBOX IDC_FPS, 28, 19, 100, 100, WS_TABSTOP | CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_LEFT + + GROUPBOX "Starting Resolution", -1, 13, 44, 185, 32, 0, WS_EX_LEFT + COMBOBOX IDC_RESOLUTION, 28, 56, 100, 120, WS_TABSTOP | CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_LEFT + + GROUPBOX "Options", -1, 13, 81, 185, 28, 0, WS_EX_LEFT + AUTOCHECKBOX "Mirror", IDC_MIRROR, 28, 93, 36, 8, 0, WS_EX_LEFT + AUTOCHECKBOX "Flip", IDC_FLIP, 65, 93, 36, 8, 0, WS_EX_LEFT + AUTOCHECKBOX "Swap", IDC_SWAP, 102, 93, 36, 8, 0, WS_EX_LEFT + + LTEXT "Version:", IDC_VERS, 15, 120, 130, 9, SS_LEFT, WS_EX_LEFT +} + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_TITLE "Settings" +END diff --git a/source/dll.cpp b/source/dll.cpp index 46613fe..84c8638 100644 --- a/source/dll.cpp +++ b/source/dll.cpp @@ -13,32 +13,33 @@ #pragma comment(lib, "ole32") #pragma comment(lib, "oleaut32") -////////////////////////////////////////////////////////////////////////// -// Valentin Schmidt 2018-07-24: this project uses a version called "BaseClasses.lib" instead -////////////////////////////////////////////////////////////////////////// +#include "cam.h" -//#ifdef _DEBUG -// #pragma comment(lib, "strmbasd") -//#else -// #pragma comment(lib, "strmbase") -//#endif +//<==================== VS-START ====================> +#ifdef DIALOG_WITHOUT_REGISTRATION +#pragma comment(lib, "Comctl32.lib") +#endif -#include "cam.h" +#include "camprops.h" +//<==================== VS-END ======================> #include #include #include - #define CreateComObject(clsid, iid, var) CoCreateInstance( clsid, NULL, CLSCTX_INPROC_SERVER, iid, (void **)&var); -STDAPI AMovieSetupRegisterServer( CLSID clsServer, LPCWSTR szDescription, LPCWSTR szFileName, LPCWSTR szThreadingModel = L"Both", LPCWSTR szServerType = L"InprocServer32" ); +STDAPI AMovieSetupRegisterServer( CLSID clsServer, LPCWSTR szDescription, LPCWSTR szFileName, LPCWSTR szThreadingModel = L"Both", LPCWSTR szServerType = L"InprocServer32" ); STDAPI AMovieSetupUnregisterServer( CLSID clsServer ); // // The NAME OF THE CAMERA CAN BE CHANGED HERE -// -const WCHAR SpoutCamName[] = L"SpoutCam"; +//<==================== VS-START ====================> +// Actual name now defined as LPCTSTR literal makro in cam.h. +// This was needed because the NAME() makros in debug mode need a LPCTSTR, +// and therefor the debug version previously didn't compile. +const WCHAR SpoutCamName[] = L"" SPOUTCAMNAME; +//<==================== VS-END ======================> // // THE CLSID CAN BE CHANGED HERE @@ -64,16 +65,17 @@ const WCHAR SpoutCamName[] = L"SpoutCam"; // DEFINE_GUID(CLSID_SpoutCam, 0x8e14549a, 0xdb61, 0x4309, 0xaf, 0xa1, 0x35, 0x78, 0xe9, 0x27, 0xe9, 0x33); +//<==================== VS-START ====================> +// And the property page we support -// -// TODO -// -// SpoutCam filter property page -// https://msdn.microsoft.com/en-us/library/dd377627%28v=vs.85%29.aspx -// -// F0B09553-7B71-49D5-9E04-9DE0A0987144 -// DEFINE_GUID(CLSID_SaturationProp, 0xF0B09553, 0x7B71, 0x49D5, 0x9E, 0x04, 0x9D, 0xE0, 0xA0, 0x98, 0x71, 0x44); +// {CD7780B7-40D2-4F33-80E2-B02E009CE63F} +DEFINE_GUID(CLSID_SpoutCamPropertyPage, + 0xcd7780b7, 0x40d2, 0x4f33, 0x80, 0xe2, 0xb0, 0x2e, 0x0, 0x9c, 0xe6, 0x3f); +// {6CD0D97B-C242-48DA-916D-28856D00754B} +DEFINE_GUID(IID_ICamSettings, + 0x6cd0d97b, 0xc242, 0x48da, 0x91, 0x6d, 0x28, 0x85, 0x6d, 0x00, 0x75, 0x4b); +//<==================== VS-END ======================> const AMOVIESETUP_MEDIATYPE AMSMediaTypesVCam = { @@ -113,7 +115,14 @@ CFactoryTemplate g_Templates[] = NULL, &AMSFilterVCam } - + //<==================== VS-START ====================> + , + { + L"Settings", + &CLSID_SpoutCamPropertyPage, + CSpoutCamProperties::CreateInstance + } + //<==================== VS-END ======================> }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); @@ -131,21 +140,23 @@ HRESULT RegisterFilter( STDAPI RegisterFilters( BOOL bRegister ) { + ASSERT(g_hInst != 0); + HRESULT hr = NOERROR; WCHAR achFileName[MAX_PATH]; - char achTemp[MAX_PATH]; - ASSERT(g_hInst != 0); - - if( 0 == GetModuleFileNameA(g_hInst, achTemp, sizeof(achTemp))) - return AmHresultFromWin32(GetLastError()); + + //<==================== VS-START ====================> + if (0 == GetModuleFileNameW(g_hInst, achFileName, sizeof(achFileName))) + { + return AmHresultFromWin32(GetLastError()); + } + //<==================== VS-END ====================> - MultiByteToWideChar(CP_ACP, 0L, achTemp, lstrlenA(achTemp) + 1, - achFileName, NUMELMS(achFileName)); - hr = CoInitialize(0); if(bRegister) { hr = AMovieSetupRegisterServer(CLSID_SpoutCam, SpoutCamName, achFileName, L"Both", L"InprocServer32"); + hr = AMovieSetupRegisterServer(CLSID_SpoutCamPropertyPage, L"Settings", achFileName, L"Both", L"InprocServer32"); //VS } if( SUCCEEDED(hr) ) @@ -171,13 +182,15 @@ STDAPI RegisterFilters( BOOL bRegister ) } // release interface - // if(fm) fm->Release(); } - if( SUCCEEDED(hr) && !bRegister ) - hr = AMovieSetupUnregisterServer( CLSID_SpoutCam ); + if (SUCCEEDED(hr) && !bRegister) + { + hr = AMovieSetupUnregisterServer(CLSID_SpoutCam); + hr = AMovieSetupUnregisterServer(CLSID_SpoutCamPropertyPage); //VS + } CoFreeUnusedLibraries(); CoUninitialize(); @@ -188,13 +201,80 @@ STDAPI RegisterFilters( BOOL bRegister ) // see http://msdn.microsoft.com/en-us/library/windows/desktop/dd376682%28v=vs.85%29.aspx STDAPI DllRegisterServer() { - return RegisterFilters(TRUE); // && AMovieDllRegisterServer2( TRUE ); (from screencapturerecorder) + return RegisterFilters(TRUE); } STDAPI DllUnregisterServer() { - return RegisterFilters(FALSE); // && AMovieDllRegisterServer2( TRUE ); (from screencapturerecorder) + return RegisterFilters(FALSE); +} + +//<==================== VS-START ====================> +#ifdef DIALOG_WITHOUT_REGISTRATION + +extern "C" { + HRESULT WINAPI OleCreatePropertyFrameDirect( + HWND hwndOwner, + LPCOLESTR lpszCaption, + LPUNKNOWN* ppUnk, + IPropertyPage * page); +} + +HRESULT ShowFilterPropertyPageDirect(IBaseFilter *pFilter, HWND hwndParent) +{ + TRACE("ShowFilterPropertyPageDirect"); + HRESULT hr; + if (!pFilter) + return E_POINTER; + CSpoutCamProperties * pPage = (CSpoutCamProperties *)CSpoutCamProperties::CreateInstance(pFilter, &hr); + if (SUCCEEDED(hr)) + { + hr = OleCreatePropertyFrameDirect( + hwndParent, // Parent window + SpoutCamName, // Caption for the dialog box + (IUnknown **)&pFilter, // Pointer to the filter + pPage + ); + pPage->Release(); + } + return hr; +} +#endif + +void CALLBACK Configure() +{ + HRESULT hr; + IBaseFilter *pFilter; + CUnknown *pInstance; + + CoInitialize(nullptr); + + // Obtain the filter's IBaseFilter interface. + pInstance = CVCam::CreateInstance(nullptr, &hr); + if (SUCCEEDED(hr)) + { + hr = pInstance->NonDelegatingQueryInterface(IID_IBaseFilter, (void **)&pFilter); + if (SUCCEEDED(hr)) + { + // If the filter is registered, this will open the settings dialog. + hr = ShowFilterPropertyPage(pFilter, GetDesktopWindow()); + +#ifdef DIALOG_WITHOUT_REGISTRATION + if (FAILED(hr)) + { + // The filter propably isn't registered in the system; + // This will open the settings dialog anyway. + hr = ShowFilterPropertyPageDirect(pFilter, GetDesktopWindow()); + } +#endif + pFilter->Release(); + } + delete pInstance; + } + + CoUninitialize(); } +//<==================== VS-END ======================> extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID); diff --git a/source/dshowutil.h b/source/dshowutil.h new file mode 100644 index 0000000..366860e --- /dev/null +++ b/source/dshowutil.h @@ -0,0 +1,101 @@ +////////////////////////////////////////////////////////////////////////// +// DSUtil.h: DirectShow helper functions. +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +////////////////////////////////////////////////////////////////////////// + +// Conventions: +// +// Functions named "IsX" return true or false. +// +// Functions named "FindX" enumerate over a collection and return the first +// matching instance. + +#pragma once + +#include +#include +#include + +#ifndef ASSERT +#define ASSERT assert +#endif + +#ifndef SAFE_RELEASE +#define SAFE_RELEASE(x) if (x) { x->Release(); x = NULL; } +#endif + +#ifndef CHECK_HR +#define CHECK_HR(hr) if (FAILED(hr)) { goto done; } +#endif + +#define TRACE(_x_) ((void)0) + +//////////////////////////////////////// +// uncomment the below to trace to debug log, monitor with Sysinternals' DebugView +//////////////////////////////////////// +//inline void OutputDebugStringf(const char * format, ...) +//{ +// char buffer[512]; +// va_list args; +// va_start(args, format); +// vsnprintf(buffer, 512, format, args); +// OutputDebugStringA(buffer); +// va_end(args); +//} +//#define TRACE(...) OutputDebugStringf(__VA_ARGS__); + +/////////////////////////////////////////////////////////////////////// +// Name: ShowFilterPropertyPage +// Desc: Show a filter's property page. +// +// hwndParent: Owner window for the property page. +/////////////////////////////////////////////////////////////////////// +inline HRESULT ShowFilterPropertyPage(IBaseFilter *pFilter, HWND hwndParent) +{ + HRESULT hr; + + ISpecifyPropertyPages *pSpecify = NULL; + FILTER_INFO FilterInfo; + CAUUID caGUID; + + if (!pFilter) + { + return E_POINTER; + } + + ZeroMemory(&FilterInfo, sizeof(FilterInfo)); + ZeroMemory(&caGUID, sizeof(caGUID)); + + // Discover if this filter contains a property page + CHECK_HR(hr = pFilter->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpecify)); + CHECK_HR(hr = pFilter->QueryFilterInfo(&FilterInfo)); + CHECK_HR(hr = pSpecify->GetPages(&caGUID)); + + // Display the filter's property page + CHECK_HR(hr = OleCreatePropertyFrame( + hwndParent, // Parent window + 0, // x (Reserved) + 0, // y (Reserved) + FilterInfo.achName, // Caption for the dialog box + 1, // Number of filters + (IUnknown **)&pFilter, // Pointer to the filter + caGUID.cElems, // Number of property pages + caGUID.pElems, // Pointer to property page CLSIDs + 0, // Locale identifier + 0, // Reserved + NULL // Reserved + )); + +done: + CoTaskMemFree(caGUID.pElems); + SAFE_RELEASE(FilterInfo.pGraph); + SAFE_RELEASE(pSpecify); + return hr; +} diff --git a/source/olepropframe.c b/source/olepropframe.c new file mode 100644 index 0000000..6357d0c --- /dev/null +++ b/source/olepropframe.c @@ -0,0 +1,313 @@ +/* + * Valentin Schmidt 2020 + * + * Based on Wine code (https://dl.winehq.org/wine/source/) and this suggested extension/fix by + * Jason Winter: https://www.winehq.org/pipermail/wine-bugs/2015-July/417234.html + */ +#ifdef DIALOG_WITHOUT_REGISTRATION +#include + +#define COBJMACROS +#define NONAMELESSUNION + +#include +#include "ole2.h" +#include "olectl.h" +#include "oledlg.h" + +#define DWL_MSGRESULT 0 +static HWND hwndFrom = NULL; +static HWND hwndFor = NULL; + +typedef struct +{ + IPropertyPageSite IPropertyPageSite_iface; + LCID lcid; + LONG ref; +} PropertyPageSite; + +static inline PropertyPageSite *impl_from_IPropertyPageSite(IPropertyPageSite *iface) +{ + return CONTAINING_RECORD(iface, PropertyPageSite, IPropertyPageSite_iface); +} + +static INT_PTR CALLBACK property_sheet_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + IPropertyPage *property_page = (IPropertyPage*)GetWindowLongPtrW(hwnd, DWLP_USER); + + switch(msg) + { + case WM_INITDIALOG: + { + RECT rect; + property_page = (IPropertyPage*)((LPPROPSHEETPAGEW)lparam)->lParam; + GetClientRect(hwnd, &rect); + IPropertyPage_Activate(property_page, hwnd, &rect, TRUE); + IPropertyPage_Show(property_page, SW_SHOW); + SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR)property_page); + return FALSE; + } + + case WM_NOTIFY: + switch (((NMHDR FAR *)lparam)->code) + { + case PSN_APPLY: + if (IPropertyPage_Apply(property_page) == NOERROR) + { + SetWindowLong(hwnd, DWL_MSGRESULT, PSNRET_NOERROR); + PropSheet_UnChanged(((NMHDR FAR *)lparam)->hwndFrom, hwnd); // Reset Apply button + } + else + SetWindowLong(hwnd, DWL_MSGRESULT, PSNRET_INVALID); + break; + case PSN_SETACTIVE: + hwndFrom = ((NMHDR FAR *)lparam)->hwndFrom; + hwndFor = hwnd; + break; + case PSN_KILLACTIVE: + SetWindowLong(hwnd, DWL_MSGRESULT, FALSE); + break; + } + return FALSE; + + case WM_DESTROY: + IPropertyPage_Show(property_page, SW_HIDE); + IPropertyPage_Deactivate(property_page); + hwndFrom = NULL; + hwndFor = NULL; + return FALSE; + + default: + return FALSE; + } +} + +static HRESULT WINAPI PropertyPageSite_QueryInterface(IPropertyPageSite* iface, + REFIID riid, void** ppv) +{ + if(IsEqualGUID(&IID_IUnknown, riid) + || IsEqualGUID(&IID_IPropertyPageSite, riid)) + *ppv = iface; + else { + *ppv = NULL; + return E_NOINTERFACE; + } + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI PropertyPageSite_AddRef(IPropertyPageSite* iface) +{ + PropertyPageSite *this = impl_from_IPropertyPageSite(iface); + LONG ref = InterlockedIncrement(&this->ref); + return ref; +} + +static ULONG WINAPI PropertyPageSite_Release(IPropertyPageSite* iface) +{ + PropertyPageSite *this = impl_from_IPropertyPageSite(iface); + LONG ref = InterlockedDecrement(&this->ref); + if(!ref) + HeapFree(GetProcessHeap(), 0, this); + return ref; +} + +static HRESULT WINAPI PropertyPageSite_OnStatusChange( + IPropertyPageSite *iface, DWORD dwFlags) +{ + IPropertyPage *property_page; + if ((hwndFrom) && (hwndFor)) + { + // Were they set? + if ((dwFlags & PROPPAGESTATUS_DIRTY) == PROPPAGESTATUS_DIRTY) + PropSheet_Changed(hwndFrom, hwndFor); // Enable Apply button + + if ((dwFlags & PROPPAGESTATUS_VALIDATE) == PROPPAGESTATUS_VALIDATE) + { + property_page = (IPropertyPage*)GetWindowLongPtrW(hwndFor, + DWLP_USER); + if ((property_page) && + (IPropertyPage_Apply(property_page) == NOERROR)) + PropSheet_UnChanged(hwndFrom, hwndFor); // Reset Apply button + } + + if ((dwFlags & PROPPAGESTATUS_CLEAN) == PROPPAGESTATUS_CLEAN) + PropSheet_UnChanged(hwndFrom, hwndFor); // Reset Apply button + } + return S_OK; +} + +static HRESULT WINAPI PropertyPageSite_GetLocaleID( + IPropertyPageSite *iface, LCID *pLocaleID) +{ + PropertyPageSite *this = impl_from_IPropertyPageSite(iface); + *pLocaleID = this->lcid; + return S_OK; +} + +static HRESULT WINAPI PropertyPageSite_GetPageContainer( + IPropertyPageSite* iface, IUnknown** ppUnk) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI PropertyPageSite_TranslateAccelerator( + IPropertyPageSite* iface, MSG *pMsg) +{ + return E_NOTIMPL; +} + +static IPropertyPageSiteVtbl PropertyPageSiteVtbl = { + PropertyPageSite_QueryInterface, + PropertyPageSite_AddRef, + PropertyPageSite_Release, + PropertyPageSite_OnStatusChange, + PropertyPageSite_GetLocaleID, + PropertyPageSite_GetPageContainer, + PropertyPageSite_TranslateAccelerator +}; + +LONG WINAPI GdiGetCharDimensions(HDC hdc, LPTEXTMETRICW lptm, LONG *height) +{ + SIZE sz; + static const WCHAR alphabet[] = { + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q', + 'r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H', + 'I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',0 }; + if (lptm && !GetTextMetricsW(hdc, lptm)) return 0; + if (!GetTextExtentPointW(hdc, alphabet, 52, &sz)) return 0; + if (height) *height = sz.cy; + return (sz.cx / 26 + 1) / 2; +} + +/*********************************************************************** + * Custom function by Valentin Schmidt, based on Wine's OleCreatePropertyFrame implementation + */ +HRESULT WINAPI OleCreatePropertyFrameDirect( + HWND hwndOwner, + LPCOLESTR lpszCaption, + LPUNKNOWN* ppUnk, + IPropertyPage * page) +{ + static const WCHAR comctlW[] = { 'c','o','m','c','t','l','3','2','.','d','l','l',0 }; + PROPSHEETHEADERW property_sheet; + PROPSHEETPAGEW property_sheet_page; + struct + { + DLGTEMPLATE template; + WORD menu; + WORD class; + WORD title; + } *dialogs; + PropertyPageSite *property_page_site; + HRESULT res; + HMODULE hcomctl; + HRSRC property_sheet_dialog_find = NULL; + HGLOBAL property_sheet_dialog_load = NULL; + WCHAR *property_sheet_dialog_data = NULL; + HDC hdc; + LOGFONTW font_desc; + HFONT hfont; + LONG font_width = 4, font_height = 8; + hdc = GetDC(NULL); + hcomctl = LoadLibraryW(comctlW); + if (hcomctl) + property_sheet_dialog_find = FindResourceW(hcomctl, + MAKEINTRESOURCEW(1006 /*IDD_PROPSHEET*/), (LPWSTR)RT_DIALOG); + if (property_sheet_dialog_find) + property_sheet_dialog_load = LoadResource(hcomctl, property_sheet_dialog_find); + if (property_sheet_dialog_load) + property_sheet_dialog_data = LockResource(property_sheet_dialog_load); + + if (property_sheet_dialog_data) + { + if (property_sheet_dialog_data[1] == 0xffff) + { + FreeLibrary(hcomctl); + return E_OUTOFMEMORY; + } + + property_sheet_dialog_data += sizeof(DLGTEMPLATE) / sizeof(WCHAR); + /* Skip menu, class and title */ + property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data) + 1; + property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data) + 1; + property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data) + 1; + + memset(&font_desc, 0, sizeof(LOGFONTW)); + /* Calculate logical height */ + font_desc.lfHeight = -MulDiv(property_sheet_dialog_data[0], + GetDeviceCaps(hdc, LOGPIXELSY), 72); + font_desc.lfCharSet = DEFAULT_CHARSET; + memcpy(font_desc.lfFaceName, property_sheet_dialog_data + 1, + sizeof(WCHAR)*(lstrlenW(property_sheet_dialog_data + 1) + 1)); + hfont = CreateFontIndirectW(&font_desc); + + if (hfont) + { + hfont = SelectObject(hdc, hfont); + font_width = GdiGetCharDimensions(hdc, NULL, &font_height); + SelectObject(hdc, hfont); + } + } + if (hcomctl) + FreeLibrary(hcomctl); + ReleaseDC(NULL, hdc); + + memset(&property_sheet, 0, sizeof(property_sheet)); + property_sheet.dwSize = sizeof(property_sheet); + if (lpszCaption) + { + property_sheet.dwFlags = PSH_PROPTITLE; + property_sheet.pszCaption = lpszCaption; + } + + property_sheet.u3.phpage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HPROPSHEETPAGE)); + + dialogs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*dialogs)); + if (!property_sheet.u3.phpage || !dialogs) + { + HeapFree(GetProcessHeap(), 0, property_sheet.u3.phpage); + HeapFree(GetProcessHeap(), 0, dialogs); + return E_OUTOFMEMORY; + } + + memset(&property_sheet_page, 0, sizeof(PROPSHEETPAGEW)); + property_sheet_page.dwSize = sizeof(PROPSHEETPAGEW); + property_sheet_page.dwFlags = PSP_DLGINDIRECT | PSP_USETITLE; + + property_sheet_page.pfnDlgProc = property_sheet_proc; + + PROPPAGEINFO page_info; + property_page_site = HeapAlloc(GetProcessHeap(), 0, sizeof(PropertyPageSite)); + if (!property_page_site) + goto done; + + property_page_site->IPropertyPageSite_iface.lpVtbl = &PropertyPageSiteVtbl; + property_page_site->ref = 1; + property_page_site->lcid = 0; + + res = IPropertyPage_SetPageSite(page, &property_page_site->IPropertyPageSite_iface); + IPropertyPageSite_Release(&property_page_site->IPropertyPageSite_iface); + if (FAILED(res)) + goto done; + + res = IPropertyPage_SetObjects(page, 1, ppUnk); + res = IPropertyPage_GetPageInfo(page, &page_info); + if (FAILED(res)) + goto done; + + dialogs[0].template.cx = MulDiv(page_info.size.cx, 4, font_width); + dialogs[0].template.cy = MulDiv(page_info.size.cy, 8, font_height); + + property_sheet_page.u.pResource = &dialogs[0].template; + property_sheet_page.lParam = (LPARAM)page; + property_sheet_page.pszTitle = page_info.pszTitle; + property_sheet.u3.phpage[property_sheet.nPages++] = CreatePropertySheetPageW(&property_sheet_page); + PropertySheetW(&property_sheet); + +done: + HeapFree(GetProcessHeap(), 0, dialogs); + HeapFree(GetProcessHeap(), 0, property_sheet.u3.phpage); + return S_OK; +} +#endif diff --git a/source/resource.h b/source/resource.h new file mode 100644 index 0000000..4f9e5c4 --- /dev/null +++ b/source/resource.h @@ -0,0 +1,12 @@ +#ifndef IDC_STATIC +#define IDC_STATIC (-1) +#endif + +#define IDD_SPOUTCAMBOX 401 +#define IDC_FPS 402 +#define IDC_RESOLUTION 403 +#define IDC_MIRROR 404 +#define IDC_FLIP 405 +#define IDC_SWAP 406 +#define IDC_VERS 407 +#define IDS_TITLE 1015 diff --git a/source/version.h b/source/version.h index 5e352fa..fb2043c 100644 --- a/source/version.h +++ b/source/version.h @@ -9,7 +9,7 @@ #define _VER_MAJORVERSION_STRING "2" #define _VER_MINORVERSION 1 -#define _VER_MINORVERSION_STRING ".017" +#define _VER_MINORVERSION_STRING "023" #define _VER_BUGFIXVERSION 0 #define _VER_BUGFIXVERSION_STRING "0"