diff --git a/cmake/Modules/FindWIL.cmake b/cmake/Modules/FindWIL.cmake new file mode 100644 index 000000000..153658c32 --- /dev/null +++ b/cmake/Modules/FindWIL.cmake @@ -0,0 +1,97 @@ +#[=======================================================================[.rst +FindWIL +------- + +FindModule for WIL and associated headers + +Imported Targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.0 + +This module defines the :prop_tgt:`IMPORTED` target ``WIL::WIL``. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module sets the following variables: + +``WIL_FOUND`` + True, if headers were found. +``WIL_VERSION`` + Detected version of found WIL headers. + +Cache variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``WIL_INCLUDE_DIR`` + Directory containing ``cppwinrt.h``. + +#]=======================================================================] + +# cmake-format: off +# cmake-lint: disable=C0103 +# cmake-lint: disable=C0301 +# cmake-format: on + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_search_module(_WIL QUIET wil) +endif() + +find_path( + WIL_INCLUDE_DIR + NAMES wil/cppwinrt.h + HINTS ${_WI_INCLUDE_DIRS} ${_WIL_INCLUDE_DIRS} + PATHS /usr/include /usr/local/include /opt/local/include /sw/include + DOC "WIL include directory") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + WIL + REQUIRED_VARS WIL_INCLUDE_DIR + VERSION_VAR WIL_VERSION REASON_FAILURE_MESSAGE "${WIL_ERROR_REASON}") +mark_as_advanced(WIL_INCLUDE_DIR) +unset(WIL_ERROR_REASON) + +if(EXISTS "${WIL_INCLUDE_DIR}/wil/cppwinrt.h") + file(STRINGS "${WIL_INCLUDE_DIR}/wil/cppwinrt.h" _version_string REGEX "^.*VERSION_(MAJOR|MINOR)[ \t]+[0-9]+[ \t]*$") + + string(REGEX REPLACE ".*VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" _version_major "${_version_string}") + string(REGEX REPLACE ".*VERSION_MINOR[ \t]+([0-9]+).*" "\\1" _version_minor "${_version_string}") + + set(WIL_VERSION "${_version_major}.${_version_minor}") + unset(_version_major) + unset(_version_minor) +else() + if(NOT WIL_FIND_QUIETLY) + message(AUTHOR_WARNING "Failed to find WIL version.") + endif() + set(WIL_VERSION 0.0.0) +endif() + +if(CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin|Windows") + set(WIL_ERROR_REASON "Ensure that obs-deps is provided as part of CMAKE_PREFIX_PATH.") +elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux|FreeBSD") + set(WIL_ERROR_REASON "Ensure WIL headers are available in local library paths.") +endif() + +if(WIL_FOUND) + set(WIL_INCLUDE_DIRS ${WIL_INCLUDE_DIR}) + + if(NOT TARGET WIL::WIL) + add_library(WIL::WIL INTERFACE IMPORTED) + set_target_properties(WIL::WIL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${WIL_INCLUDE_DIRS}") + + endif() +endif() + +include(FeatureSummary) +set_package_properties( + WIL PROPERTIES + URL "https://github.com/microsoft/wil.git" + DESCRIPTION + "The Windows Implementation Libraries (WIL) is a header-only C++ library created to make life easier for developers on Windows through readable type-safe C++ interfaces for common Windows coding patterns." +) diff --git a/cmake/finders/FindWIL.cmake b/cmake/finders/FindWIL.cmake new file mode 100644 index 000000000..153658c32 --- /dev/null +++ b/cmake/finders/FindWIL.cmake @@ -0,0 +1,97 @@ +#[=======================================================================[.rst +FindWIL +------- + +FindModule for WIL and associated headers + +Imported Targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.0 + +This module defines the :prop_tgt:`IMPORTED` target ``WIL::WIL``. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module sets the following variables: + +``WIL_FOUND`` + True, if headers were found. +``WIL_VERSION`` + Detected version of found WIL headers. + +Cache variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``WIL_INCLUDE_DIR`` + Directory containing ``cppwinrt.h``. + +#]=======================================================================] + +# cmake-format: off +# cmake-lint: disable=C0103 +# cmake-lint: disable=C0301 +# cmake-format: on + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_search_module(_WIL QUIET wil) +endif() + +find_path( + WIL_INCLUDE_DIR + NAMES wil/cppwinrt.h + HINTS ${_WI_INCLUDE_DIRS} ${_WIL_INCLUDE_DIRS} + PATHS /usr/include /usr/local/include /opt/local/include /sw/include + DOC "WIL include directory") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + WIL + REQUIRED_VARS WIL_INCLUDE_DIR + VERSION_VAR WIL_VERSION REASON_FAILURE_MESSAGE "${WIL_ERROR_REASON}") +mark_as_advanced(WIL_INCLUDE_DIR) +unset(WIL_ERROR_REASON) + +if(EXISTS "${WIL_INCLUDE_DIR}/wil/cppwinrt.h") + file(STRINGS "${WIL_INCLUDE_DIR}/wil/cppwinrt.h" _version_string REGEX "^.*VERSION_(MAJOR|MINOR)[ \t]+[0-9]+[ \t]*$") + + string(REGEX REPLACE ".*VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" _version_major "${_version_string}") + string(REGEX REPLACE ".*VERSION_MINOR[ \t]+([0-9]+).*" "\\1" _version_minor "${_version_string}") + + set(WIL_VERSION "${_version_major}.${_version_minor}") + unset(_version_major) + unset(_version_minor) +else() + if(NOT WIL_FIND_QUIETLY) + message(AUTHOR_WARNING "Failed to find WIL version.") + endif() + set(WIL_VERSION 0.0.0) +endif() + +if(CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin|Windows") + set(WIL_ERROR_REASON "Ensure that obs-deps is provided as part of CMAKE_PREFIX_PATH.") +elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux|FreeBSD") + set(WIL_ERROR_REASON "Ensure WIL headers are available in local library paths.") +endif() + +if(WIL_FOUND) + set(WIL_INCLUDE_DIRS ${WIL_INCLUDE_DIR}) + + if(NOT TARGET WIL::WIL) + add_library(WIL::WIL INTERFACE IMPORTED) + set_target_properties(WIL::WIL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${WIL_INCLUDE_DIRS}") + + endif() +endif() + +include(FeatureSummary) +set_package_properties( + WIL PROPERTIES + URL "https://github.com/microsoft/wil.git" + DESCRIPTION + "The Windows Implementation Libraries (WIL) is a header-only C++ library created to make life easier for developers on Windows through readable type-safe C++ interfaces for common Windows coding patterns." +) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8b653b053..6d94f7256 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -72,6 +72,7 @@ if(OBS_CMAKE_VERSION VERSION_GREATER_EQUAL 3.0.0) add_obs_plugin(vlc-video WITH_MESSAGE) add_obs_plugin(win-capture PLATFORMS WINDOWS) add_obs_plugin(win-dshow PLATFORMS WINDOWS) + add_obs_plugin(win-mf PLATFORMS WINDOWS) add_obs_plugin(win-wasapi PLATFORMS WINDOWS) return() @@ -94,6 +95,7 @@ endfunction() if(OS_WINDOWS) add_subdirectory(coreaudio-encoder) add_subdirectory(win-wasapi) + add_subdirectory(win-mf) add_subdirectory(win-dshow) add_subdirectory(win-capture) add_subdirectory(decklink) diff --git a/plugins/win-mf/CMakeLists.txt b/plugins/win-mf/CMakeLists.txt new file mode 100644 index 000000000..97fc163a3 --- /dev/null +++ b/plugins/win-mf/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.24...3.25) + +legacy_check() + +find_package(FFmpeg REQUIRED avcodec avutil) +find_package(WIL REQUIRED) + +add_library(win-mf MODULE) +add_library(OBS::mf ALIAS win-mf) + +target_sources(win-mf PUBLIC mf-plugin.cpp win-mf.cpp) + +add_library(libmfcapture INTERFACE) +add_library(OBS::libmfcapture ALIAS libmfcapture) + +target_include_directories(libmfcapture INTERFACE libmfcapture libmfcapture/source) + +target_sources(libmfcapture INTERFACE libmfcapture/source/DeviceControlChangeListener.cpp + libmfcapture/source/mfcapture.cpp libmfcapture/source/PhysicalCamera.cpp) + +configure_file(cmake/windows/obs-module.rc.in win-mf.rc) +target_sources(win-mf PRIVATE win-mf.rc) + +target_precompile_headers(win-mf PRIVATE ) + +target_link_libraries( + win-mf + PRIVATE OBS::libobs + OBS::w32-pthreads + OBS::winrt-headers + FFmpeg::avcodec + FFmpeg::avutil + strmiids + winmm + dxcore + dxguid + libmfcapture) + +# cmake-format: off +set_target_properties_obs(win-mf PROPERTIES FOLDER plugins/win-mf PREFIX "") +# cmake-format: on diff --git a/plugins/win-mf/cmake/legacy.cmake b/plugins/win-mf/cmake/legacy.cmake new file mode 100644 index 000000000..2977970c5 --- /dev/null +++ b/plugins/win-mf/cmake/legacy.cmake @@ -0,0 +1,64 @@ +project(win-mf) + +find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil) +find_package(WIL REQUIRED) + +add_library(win-mf MODULE) +add_library(OBS::mf ALIAS win-mf) + +target_sources(win-mf PRIVATE mf-plugin.cpp plugin-macros.generated.h win-mf.cpp) + +add_library(libmfcapture INTERFACE) +add_library(OBS::libmfcapture ALIAS libmfcapture) + +target_sources( + libmfcapture + INTERFACE libmfcapture/mfcapture.hpp + libmfcapture/source/DeviceControlChangeListener.cpp + libmfcapture/source/DeviceControlChangeListener.hpp + libmfcapture/source/mfcapture.cpp + libmfcapture/source/framework.hpp + libmfcapture/source/mfcapture.rc + libmfcapture/source/PhysicalCamera.cpp + libmfcapture/source/PhysicalCamera.hpp + libmfcapture/source/resource.hpp) + +target_include_directories(libmfcapture INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/libmfcapture) + +target_compile_definitions(libmfcapture INTERFACE _UP_WINDOWS=1) + +set(MODULE_DESCRIPTION "OBS MediaFoundation module") + +configure_file(${CMAKE_SOURCE_DIR}/cmake/bundle/windows/obs-module.rc.in win-mf.rc) + +target_sources(win-mf PRIVATE win-mf.rc) + +target_compile_definitions(win-mf PRIVATE UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_WARNINGS OBS_LEGACY) + +target_link_libraries( + win-mf + PRIVATE OBS::libobs + OBS::w32-pthreads + setupapi + strmiids + ksuser + winmm + wmcodecdspuuid + FFmpeg::avcodec + FFmpeg::avutil + dxcore + dxguid + libmfcapture) + +source_group( + "libmfcapture\\Source Files" + FILES libmfcapture/source/DeviceControlChangeListener.cpp libmfcapture/source/mfcapture.cpp + libmfcapture/source/mfcapture.rc libmfcapture/source/PhysicalCamera.cpp) +source_group( + "libmfcapture\\Header Files" + FILES libmfcapture/source/DeviceControlChangeListener.hpp libmfcapture/source/framework.hpp + libmfcapture/source/mfcapture.hpp libmfcapture/source/PhysicalCamera.hpp libmfcapture/source/resource.hpp) + +set_target_properties(win-mf PROPERTIES FOLDER "plugins/win-mf") + +setup_plugin_target(win-mf) diff --git a/plugins/win-mf/cmake/windows/obs-module.rc.in b/plugins/win-mf/cmake/windows/obs-module.rc.in new file mode 100644 index 000000000..c42d93c40 --- /dev/null +++ b/plugins/win-mf/cmake/windows/obs-module.rc.in @@ -0,0 +1,24 @@ +1 VERSIONINFO +FILEVERSION ${OBS_VERSION_MAJOR},${OBS_VERSION_MINOR},${OBS_VERSION_PATCH},0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "${OBS_COMPANY_NAME}" + VALUE "FileDescription", "OBS Media Foundation module" + VALUE "FileVersion", "${OBS_VERSION_CANONICAL}" + VALUE "ProductName", "${OBS_PRODUCT_NAME}" + VALUE "ProductVersion", "${OBS_VERSION_CANONICAL}" + VALUE "Comments", "${OBS_COMMENTS}" + VALUE "LegalCopyright", "${OBS_LEGAL_COPYRIGHT}" + VALUE "InternalName", "win-mf" + VALUE "OriginalFilename", "win-mf" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 0x04B0 + END +END diff --git a/plugins/win-mf/data/locale/en-US.ini b/plugins/win-mf/data/locale/en-US.ini new file mode 100644 index 000000000..2bd724caf --- /dev/null +++ b/plugins/win-mf/data/locale/en-US.ini @@ -0,0 +1,65 @@ +# media foundation video capture device text +VideoCaptureDevice="Video Capture Device 2" +Device="Device" +ColorSpace="Color Space" +ColorSpace.Default="Default" +ColorSpace.709="Rec. 709" +ColorSpace.601="Rec. 601" +ColorSpace.2100PQ="Rec. 2100 (PQ)" +ColorSpace.2100HLG="Rec. 2100 (HLG)" +ColorRange="Color Range" +ColorRange.Default="Default" +ColorRange.Partial="Limited" +ColorRange.Full="Full" +ConfigureAudio="Configure Audio" +ConfigureVideo="Video Effect Control" +ConfigureCrossbar="Configure Crossbar" +ResFPSType="Resolution/FPS Type" +ResFPSType.Custom="Custom" +ResFPSType.DevPreferred="Device Default" +FPS.Matching="Match Output FPS" +FPS.Highest="Highest FPS" +Resolution="Resolution" +VideoFormat="Video Format" +VideoFormat.Any="Any" +VideoFormat.Unknown="Unknown (%1)" +AudioOutputMode="Audio Output Mode" +AudioOutputMode.Capture="Capture audio only" +AudioOutputMode.DirectSound="Output desktop audio (DirectSound)" +AudioOutputMode.WaveOut="Output desktop audio (WaveOut)" +UseCustomAudioDevice="Use custom audio device" +AudioDevice="Audio Device" +Buffering="Buffering" +Buffering.ToolTip="When enabled, buffers video/audio data to ensure the smoothest and most\naccurate playback possible, but at the cost of increased latency. When using\nbuffering with a video capture card, it's recommended to set the card and the\nprogram to the same framerate for best results.\n\nWhen disabled, ensures lowest latency playback, but at the cost of frame\nplayback accuracy. This is ideal for face cameras, or when you want to use the\nprogram's preview window to play a console.\n\nAuto-detect (default) sets it to enabled if the device has delay, and disabled\nif it has no delay." +Buffering.AutoDetect="Auto-Detect" +Buffering.Enable="Enable" +Buffering.Disable="Disable" +Activate="Activate" +Deactivate="Deactivate" +FlipVertically="Flip Vertically" +Autorotation="Apply rotation data from camera (if any)" +HardwareDecode="Use hardware decoding when available" +DeactivateWhenNotShowing="Deactivate when not showing" +#if 0 +MEPFilters.Title="MEP Filters" +MEPFilters.Enabled="Enabled" +MEPFilters.Disabled="Disabled" +MEPFilters.BlurStandard="Blur (Standard)" +MEPFilters.BlurPortrait="Blur (Portrait)" +MEPFilters.AutoFrame="Auto Framing" +MEPFilters.EyeContact="Eye Contact" +#endif + +# Intel NPU text +IntelNPU.BackgroundBlur="Background Blur" +IntelNPU.BlurNone="None" +IntelNPU.BlurStandard="Standard Blur (Intel AI Boost)" +IntelNPU.Portrait="Portrait Blur (Intel AI Boost)" +IntelNPU.BackgroundRemoval="Background Removal (Intel AI Boost)" +IntelNPU.AutoFraming="Auto Framing (Intel AI Boost)" +IntelNPU.EyeGazeCorrection="Eye Contact Correction (Intel AI Boost)" + +# encoder text +Bitrate="Bitrate" +Encoder.C985="AVerMedia H.264 Encoder (c985)" +Encoder.C353="AVerMedia H.264 Encoder" diff --git a/plugins/win-mf/libmfcapture/mfcapture.hpp b/plugins/win-mf/libmfcapture/mfcapture.hpp new file mode 100644 index 000000000..805a08dfb --- /dev/null +++ b/plugins/win-mf/libmfcapture/mfcapture.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include + +#ifdef MFCAPTURE_EXPORTS +#define MFCAPTURE_EXPORTS __declspec(dllexport) +#else +#define MFCAPTURE_EXPORTS +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum MF_COLOR_FORMAT { + MF_COLOR_FORMAT_UNKNOWN, + MF_COLOR_FORMAT_NV12, + MF_COLOR_FORMAT_ARGB, + MF_COLOR_FORMAT_XRGB +}; + +typedef HANDLE CAPTURE_DEVICE_HANDLE; +typedef void(__stdcall *MF_VideoDataCallback)(void *pData, int Size, + long long llTimestamp, + void *pUserData); +typedef void(__stdcall *MF_EnumerateCameraCallback)(const wchar_t *Name, + const wchar_t *DevId, + void *pUserData); +typedef void(__stdcall *MF_EnumerateStreamCapabilitiesCallback)( + UINT32 Width, UINT32 Height, UINT32 FpsN, UINT32 FpsD, void *pUserData); + +MFCAPTURE_EXPORTS HRESULT MF_EnumerateCameras(MF_EnumerateCameraCallback cb, + void *pUserData); + +MFCAPTURE_EXPORTS CAPTURE_DEVICE_HANDLE MF_Create(const wchar_t *DevId); + +MFCAPTURE_EXPORTS void MF_Destroy(CAPTURE_DEVICE_HANDLE h); + +MFCAPTURE_EXPORTS HRESULT MF_Prepare(CAPTURE_DEVICE_HANDLE h); + +MFCAPTURE_EXPORTS HRESULT MF_EnumerateStreamCapabilities( + CAPTURE_DEVICE_HANDLE h, MF_EnumerateStreamCapabilitiesCallback cb, + void *pUserData); + +// if llInterval is 0, set the camera to us the max fps +// otherwise, set camera with matching fps. +MFCAPTURE_EXPORTS HRESULT MF_SetOutputResolution(CAPTURE_DEVICE_HANDLE h, + UINT32 uiWidth, + UINT32 uiHeight, + LONGLONG llInterval, + MF_COLOR_FORMAT fmt); + +// LONGLONG llInterval = 10000000ll * uiFpsD / uiFpsN; +MFCAPTURE_EXPORTS HRESULT MF_GetOutputResolution(CAPTURE_DEVICE_HANDLE h, + UINT32 *uiWidth, + UINT32 *uiHeight, + UINT32 *uiFpsN, + UINT32 *uiFpsD); + +MFCAPTURE_EXPORTS HRESULT MF_Start(CAPTURE_DEVICE_HANDLE h, + MF_VideoDataCallback cb, void *pUserData); + +MFCAPTURE_EXPORTS HRESULT MF_Stop(CAPTURE_DEVICE_HANDLE h); + +MFCAPTURE_EXPORTS HRESULT MF_GetBlur(CAPTURE_DEVICE_HANDLE h, bool &blur, + bool &shallowFocus, bool &mask); + +MFCAPTURE_EXPORTS HRESULT MF_SetBlur(CAPTURE_DEVICE_HANDLE h, BOOL blur, + BOOL shallowFocus, BOOL mask); + +MFCAPTURE_EXPORTS HRESULT MF_GetTransparent(CAPTURE_DEVICE_HANDLE h, + bool &enable); + +MFCAPTURE_EXPORTS HRESULT MF_GetAutoFraming(CAPTURE_DEVICE_HANDLE h, + bool &enable); + +MFCAPTURE_EXPORTS HRESULT MF_SetAutoFraming(CAPTURE_DEVICE_HANDLE h, + BOOL enable); + +MFCAPTURE_EXPORTS HRESULT MF_GetEyeGazeCorrection(CAPTURE_DEVICE_HANDLE h, + bool &enable); + +MFCAPTURE_EXPORTS HRESULT MF_SetEyeGazeCorrection(CAPTURE_DEVICE_HANDLE h, + BOOL enable); + +MFCAPTURE_EXPORTS HRESULT MF_RestoreDefaultSettings(CAPTURE_DEVICE_HANDLE h); + +#ifdef __cplusplus +} +#endif diff --git a/plugins/win-mf/libmfcapture/source/DeviceControlChangeListener.cpp b/plugins/win-mf/libmfcapture/source/DeviceControlChangeListener.cpp new file mode 100644 index 000000000..7eead3bac --- /dev/null +++ b/plugins/win-mf/libmfcapture/source/DeviceControlChangeListener.cpp @@ -0,0 +1,177 @@ +#include "DeviceControlChangeListener.hpp" + +DeviceControlChangeListener::DeviceControlChangeListener() {} + +DeviceControlChangeListener::~DeviceControlChangeListener() {} + +// IUnknown methods +HRESULT __stdcall DeviceControlChangeListener::QueryInterface(REFIID iid, + void **ppv) +{ + + if (iid == IID_IUnknown) { + *ppv = static_cast(this); + } else if (iid == IID_IMFCameraControlNotify) { + *ppv = static_cast(this); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + reinterpret_cast(*ppv)->AddRef(); + return S_OK; +} + +ULONG __stdcall DeviceControlChangeListener::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +ULONG __stdcall DeviceControlChangeListener::Release() +{ + + if (InterlockedDecrement(&m_cRef) == 0) { + delete this; + return 0; + } + return m_cRef; +} + +HRESULT +DeviceControlChangeListener::HandleControlSet_ExtendedCameraControl(UINT32 id) +{ + + if (id != KSPROPERTY_CAMERACONTROL_EXTENDED_EYEGAZECORRECTION && + id != KSPROPERTY_CAMERACONTROL_EXTENDED_BACKGROUNDSEGMENTATION && + id != KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW && + id != KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW_CONFIGCAPS && + id != KSPROPERTY_CAMERACONTROL_EXTENDED_VIDEOHDR) { + return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + } + + DWORD dwStreamIndex = KSCAMERA_EXTENDEDPROP_FILTERSCOPE; + wil::com_ptr spExtControl = nullptr; + RETURN_IF_FAILED(m_spExtCamController->GetExtendedCameraControl( + dwStreamIndex, id, &spExtControl)); + + ULONGLONG Flags = spExtControl->GetFlags(); + + WS_CONTROL_TYPE ctrlType = WS_EFFECT_UNKNOWN; + WS_CONTROL_VALUE ctrlValue = WS_EFFECT_OFF; + + if (id == KSPROPERTY_CAMERACONTROL_EXTENDED_EYEGAZECORRECTION) { + + bool effect = Flags & + KSCAMERA_EXTENDEDPROP_EYEGAZECORRECTION_ON; + + } else if (id == + KSPROPERTY_CAMERACONTROL_EXTENDED_BACKGROUNDSEGMENTATION) { + + bool blur = Flags & + KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_BLUR; + bool focus = + Flags & + KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_SHALLOWFOCUS; + bool mask = Flags & + KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_MASK; + + if (focus) { + blur = false; + } + + } else if (id == KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW) { + + bool effect = + Flags & + KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_AUTOFACEFRAMING; + } + + return S_OK; +} + +void PrintGuid(GUID guid) +{ + printf("{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}", + guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], + guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7]); +} + +void STDMETHODCALLTYPE +DeviceControlChangeListener::OnChange(_In_ REFGUID controlSet, _In_ UINT32 id) +{ + auto lock = m_lock.lock(); + + printf("Guid = "); + PrintGuid(controlSet); + printf(" Id = %d\n", id); + + if (IsEqualCLSID(controlSet, KSPROPERTYSETID_ANYCAMERACONTROL)) { + printf("KSPROPERTYSETID_ANYCAMERACONTROL\n"); + } else if (IsEqualCLSID(controlSet, PROPSETID_VIDCAP_VIDEOPROCAMP)) { + printf("PROPSETID_VIDCAP_VIDEOPROCAMP\n"); + } else if (IsEqualCLSID(controlSet, PROPSETID_VIDCAP_CAMERACONTROL)) { + printf("PROPSETID_VIDCAP_CAMERACONTROL\n"); + + } else if (IsEqualCLSID(controlSet, + KSPROPERTYSETID_ExtendedCameraControl)) { + printf("KSPROPERTYSETID_ExtendedCameraControl\n"); + HandleControlSet_ExtendedCameraControl(id); + } else { + printf("Unknown control set\n"); + } +} + +void STDMETHODCALLTYPE +DeviceControlChangeListener::OnError(_In_ HRESULT hrStatus) +{ + auto lock = m_lock.lock(); + printf("%s, hrStatus = 0x%x\n", __FUNCSIG__, hrStatus); +} + +HRESULT DeviceControlChangeListener::Start( + const wchar_t *devId, IMFExtendedCameraController *pExtCamController) +{ + { + auto lock = m_lock.lock(); + if (m_bStarted) { + return S_OK; + } + } + + wil::com_ptr_nothrow spMonitor; + RETURN_IF_FAILED(MFCreateCameraControlMonitor(devId, this, &spMonitor)); + RETURN_IF_FAILED(spMonitor->AddControlSubscription( + KSPROPERTYSETID_ANYCAMERACONTROL, 0)); + RETURN_IF_FAILED(spMonitor->Start()); + + { + auto lock = m_lock.lock(); + m_spMonitor = spMonitor; + } + + m_spExtCamController = pExtCamController; + + m_bStarted = true; + return S_OK; +} + +HRESULT DeviceControlChangeListener::Stop() +{ + + auto lock = m_lock.lock(); + if (!m_bStarted) { + return S_OK; + } + + if (m_spMonitor != nullptr) { + m_spMonitor->RemoveControlSubscription( + KSPROPERTYSETID_ANYCAMERACONTROL, 0); + (void)m_spMonitor->Shutdown(); + m_spMonitor = nullptr; + } + + m_spExtCamController = nullptr; + + m_bStarted = false; + return S_OK; +} diff --git a/plugins/win-mf/libmfcapture/source/DeviceControlChangeListener.hpp b/plugins/win-mf/libmfcapture/source/DeviceControlChangeListener.hpp new file mode 100644 index 000000000..5a49855bd --- /dev/null +++ b/plugins/win-mf/libmfcapture/source/DeviceControlChangeListener.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "framework.hpp" + +#include // must be before the first C++ WinRT header, ref:https://github.com/Microsoft/wil/wiki/Error-handling-helpers +#include +#include + +#include +#include + +typedef enum _WS_CONTROL_TYPE { + WS_EFFECT_UNKNOWN = -1, + WS_BACKGROUND_STANDARD = 0, + WS_BACKGROUND_PORTRAIT = 1, + WS_BACKGROUND_MASK = 2, + WS_BACKGROUND_NONE = 3, + WS_AUTO_FRAMING = 4, + WS_EYE_CONTACT = 5, +} WS_CONTROL_TYPE; + +typedef enum _WS_CONTROL_VALUE { + WS_EFFECT_OFF = 0, + WS_EFFECT_ON = 1, +} WS_CONTROL_VALUE; + +class DeviceControlChangeListener : public IMFCameraControlNotify { + long m_cRef = 1; + wil::critical_section m_lock; + + std::wstring m_wsDevId; + wil::com_ptr_nothrow m_spMonitor = nullptr; + wil::com_ptr_nothrow m_spKsControl = nullptr; + wil::com_ptr_nothrow m_spExtCamController = + nullptr; + void *m_cbdata = nullptr; + + bool m_bStarted = false; + +private: + HRESULT HandleControlSet_ExtendedCameraControl(UINT32 id); + +public: + DeviceControlChangeListener(); + ~DeviceControlChangeListener(); + + // IUnknown methods + IFACEMETHODIMP_(ULONG) AddRef(void) override; + IFACEMETHODIMP_(ULONG) Release(void) override; + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, + _Out_ LPVOID *ppvObject) override; + + // IMFCameraControlNotify methods + void STDMETHODCALLTYPE OnChange(_In_ REFGUID controlSet, + _In_ UINT32 id); + void STDMETHODCALLTYPE OnError(_In_ HRESULT hrStatus); + + HRESULT Start(const wchar_t *devId, + IMFExtendedCameraController *pExtCamController); + + HRESULT Stop(); +}; diff --git a/plugins/win-mf/libmfcapture/source/PhysicalCamera.cpp b/plugins/win-mf/libmfcapture/source/PhysicalCamera.cpp new file mode 100644 index 000000000..65c4a8815 --- /dev/null +++ b/plugins/win-mf/libmfcapture/source/PhysicalCamera.cpp @@ -0,0 +1,1418 @@ +#include "mfcapture.hpp" +#include "DeviceControlChangeListener.hpp" +#include "PhysicalCamera.hpp" +#include +#include +#include + +#pragma comment(lib, "mfuuid.lib") +#pragma comment(lib, "mf.lib") +#pragma comment(lib, "mfplat.lib") +#pragma comment(lib, "mfreadwrite.lib") +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "mfsensorgroup.lib") + +extern void ControlNotification(WS_CONTROL_TYPE type, WS_CONTROL_VALUE value, + void *pUserData); + +class VideoBufferLock { + +public: + VideoBufferLock(IMFMediaBuffer *pBuffer) + { + m_spBuffer = pBuffer; + m_spBuffer->QueryInterface(IID_PPV_ARGS(&m_sp2DBuffer)); + } + + ~VideoBufferLock() + { + UnlockBuffer(); + m_spBuffer = nullptr; + m_sp2DBuffer = nullptr; + } + + HRESULT LockBuffer(LONG lDefaultStride, DWORD dwHeightInPixels, + BYTE **ppbScanLine0, LONG *plStride) + { + HRESULT hr = S_OK; + if (m_sp2DBuffer) { + hr = m_sp2DBuffer->Lock2D(ppbScanLine0, plStride); + } else { + BYTE *pData = NULL; + hr = m_spBuffer->Lock(&pData, NULL, NULL); + if (SUCCEEDED(hr)) { + *plStride = lDefaultStride; + if (lDefaultStride < 0) { + *ppbScanLine0 = + pData + + abs(lDefaultStride) * + (dwHeightInPixels - 1); + } else { + *ppbScanLine0 = pData; + } + } + } + + m_bLocked = (SUCCEEDED(hr)); + return hr; + } + + void UnlockBuffer() + { + if (m_bLocked) { + if (m_sp2DBuffer) { + m_sp2DBuffer->Unlock2D(); + } else { + m_spBuffer->Unlock(); + } + m_bLocked = FALSE; + } + } + +private: + wil::com_ptr_nothrow m_spBuffer; + wil::com_ptr_nothrow m_sp2DBuffer; + + BOOL m_bLocked = FALSE; +}; + +PhysicalCamera::PhysicalCamera() {} + +PhysicalCamera::~PhysicalCamera() +{ + printf("%s, Begin\n", __FUNCSIG__); + Uninitialize(); + printf("%s, End\n", __FUNCSIG__); +} + +HRESULT __stdcall PhysicalCamera::QueryInterface(REFIID iid, void **ppv) +{ + + if (iid == IID_IUnknown) { + *ppv = static_cast(this); + } else if (iid == IID_IMFSourceReaderCallback) { + *ppv = static_cast(this); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + reinterpret_cast(*ppv)->AddRef(); + return S_OK; +} + +ULONG __stdcall PhysicalCamera::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +ULONG __stdcall PhysicalCamera::Release() +{ + + if (InterlockedDecrement(&m_cRef) == 0) { + delete this; + return 0; + } + return m_cRef; +} + +/* +* The function creates source reader and enables HW decode +*/ +HRESULT +PhysicalCamera::CreateSourceReader(IMFMediaSource *pSource, + IMFDXGIDeviceManager *pDxgiDevIManager, + IMFSourceReader **ppSourceReader) +{ + printf("%s, Begin\n", __FUNCSIG__); + HRESULT hr = S_OK; + + if (!pSource) { + return E_INVALIDARG; + } + + // Create attributes for source reader creation + UINT32 cInitialSize = 5; + + wil::com_ptr_nothrow spAttributes; + RETURN_IF_FAILED(MFCreateAttributes(&spAttributes, cInitialSize)); + + if (pDxgiDevIManager) { + RETURN_IF_FAILED(spAttributes->SetUnknown( + MF_SOURCE_READER_D3D_MANAGER, pDxgiDevIManager)); + RETURN_IF_FAILED(spAttributes->SetUINT32( + MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE)); + } + + RETURN_IF_FAILED(spAttributes->SetUINT32( + MF_READWRITE_DISABLE_CONVERTERS, FALSE)); + RETURN_IF_FAILED(spAttributes->SetUINT32( + MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE)); + RETURN_IF_FAILED(spAttributes->SetUnknown( + MF_SOURCE_READER_ASYNC_CALLBACK, this)); + + // Create source reader + RETURN_IF_FAILED(MFCreateSourceReaderFromMediaSource( + pSource, spAttributes.get(), ppSourceReader)); + + printf("%s, End\n", __FUNCSIG__); + return hr; +} + +HRESULT PhysicalCamera::Initialize(LPCWSTR pwszSymLink) +{ + printf("%s, Begin\n", __FUNCSIG__); + printf("%s, %ws\n", __FUNCSIG__, pwszSymLink); + + HRESULT hr = S_OK; + winrt::slim_lock_guard lock(m_Lock); + + // Create physical camera source + wil::com_ptr_nothrow spDeviceAttributes; + RETURN_IF_FAILED(MFCreateAttributes(&spDeviceAttributes, 3)); + RETURN_IF_FAILED(spDeviceAttributes->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)); + RETURN_IF_FAILED(spDeviceAttributes->SetString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, + pwszSymLink)); + + RETURN_IF_FAILED( + MFCreateDeviceSource(spDeviceAttributes.get(), &m_spDevSource)); + + wil::com_ptr_nothrow spGetService = nullptr; + RETURN_IF_FAILED(m_spDevSource->QueryInterface(&spGetService)); + RETURN_IF_FAILED(spGetService->GetService( + GUID_NULL, IID_PPV_ARGS(&m_spExtController))); + + m_pDevCtrlNotify = new DeviceControlChangeListener(); + if (!m_pDevCtrlNotify) { + return E_OUTOFMEMORY; + } + + hr = m_pDevCtrlNotify->Start(pwszSymLink, m_spExtController.get()); + if (FAILED(hr)) { + delete m_pDevCtrlNotify; + return hr; + } + + m_wsSymbolicName = pwszSymLink; + + printf("%s, End\n", __FUNCSIG__); + + return hr; +} + +HRESULT PhysicalCamera::Uninitialize() +{ + + printf("%s, Begin\n", __FUNCSIG__); + + Stop(); + { + winrt::slim_lock_guard lock(m_Lock); + + if (m_pDevCtrlNotify) { + m_pDevCtrlNotify->Stop(); + m_pDevCtrlNotify->Release(); + m_pDevCtrlNotify = nullptr; + } + m_spSourceReader = nullptr; + if (m_spDevSource.get() != nullptr) { + m_spDevSource->Shutdown(); + } + m_spDevSource = nullptr; + m_spDxgiDevManager = nullptr; + m_spExtController = nullptr; + } + + printf("%s, End\n", __FUNCSIG__); + return S_OK; +} + +HRESULT PhysicalCamera::SetD3dManager(IMFDXGIDeviceManager *pD3dManager) +{ + if (!pD3dManager) { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + winrt::slim_lock_guard lock(m_Lock); + + m_spDxgiDevManager = nullptr; + m_spDxgiDevManager = pD3dManager; + + return hr; +} + +HRESULT PhysicalCamera::Prepare() +{ + printf("%s, Begin\n", __FUNCSIG__); + + winrt::slim_lock_guard lock(m_Lock); + m_spSourceReader = nullptr; + RETURN_IF_FAILED(CreateSourceReader(m_spDevSource.get(), + m_spDxgiDevManager.get(), + &m_spSourceReader)); + + printf("%s, End\n", __FUNCSIG__); + return S_OK; +} + +HRESULT PhysicalCamera::Start(DWORD dwPhyStrmIndex, MF_VideoDataCallback cb, + void *pUserData) +{ + printf("%s, Begin\n", __FUNCSIG__); + + HRESULT hr = S_OK; + winrt::slim_lock_guard lock(m_Lock); + + if (!m_spSourceReader) { + return E_POINTER; + } + + // SetOutputResolution was not called + if (!m_uiWidth || !m_uiHeight) { + return E_UNEXPECTED; + } + + m_cbVideoData = cb; + m_pUserData = pUserData; + + hr = m_spSourceReader->ReadSample( + dwPhyStrmIndex, // (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, + 0, NULL, NULL, NULL, NULL); + + if (FAILED(hr)) { + m_spDevSource->Shutdown(); + } else { + hr = m_spSourceReader->SetStreamSelection(dwPhyStrmIndex, TRUE); + } + + printf("%s, End\n", __FUNCSIG__); + return hr; +} + +HRESULT PhysicalCamera::Stop() +{ + + printf("%s, Begin\n", __FUNCSIG__); + HRESULT hr = S_OK; + + SaveSettingsToDefault(); + + { + winrt::slim_lock_guard lock(m_Lock); + + m_spSourceReader = nullptr; + m_cbVideoData = nullptr; + m_pUserData = nullptr; + + m_uiWidth = 0; + m_uiHeight = 0; + m_lDefaultStride = 0; + m_fmt = MF_COLOR_FORMAT_UNKNOWN; + + if (m_pSegMaskBuf) { + delete[] m_pSegMaskBuf; + } + m_pSegMaskBuf = nullptr; + m_uiSegMaskBufSize = 0; + +#ifdef ARGB_SEG_MASK_WORKAROUND + if (m_pBufferArgbOut) { + delete[] m_pBufferArgbOut; + } + m_pBufferArgbOut = nullptr; +#endif + } + + printf("%s, End\n", __FUNCSIG__); + return hr; +} + +HRESULT PhysicalCamera::CreateInstance(PhysicalCamera **ppPhysicalCamera) +{ + if (!ppPhysicalCamera) { + return E_POINTER; + } + + *ppPhysicalCamera = nullptr; + PhysicalCamera *pPhysicalCamera = new (std::nothrow) PhysicalCamera; + if (!pPhysicalCamera) { + return E_OUTOFMEMORY; + } + + *ppPhysicalCamera = pPhysicalCamera; + + return S_OK; +} + +void resizeBilinearAlpha(BYTE *mask, int w, int h, DWORD *dest, int w2, int h2) +{ + int A, B, C, D, x, y, index, gray; + float x_ratio = ((float)(w - 1)) / w2; + float y_ratio = ((float)(h - 1)) / h2; + float x_diff, y_diff; + int offset = 0; + for (int i = 0; i < h2; i++) { + for (int j = 0; j < w2; j++) { + x = (int)(x_ratio * j); + y = (int)(y_ratio * i); + x_diff = (x_ratio * j) - x; + y_diff = (y_ratio * i) - y; + index = y * w + x; + + // range is 0 to 255 thus bitwise AND with 0xff + A = mask[index] & 0xff; + B = mask[index + 1] & 0xff; + C = mask[index + w] & 0xff; + D = mask[index + w + 1] & 0xff; + + // Y = A(1-w)(1-h) + B(w)(1-h) + C(h)(1-w) + Dwh + gray = (int)(A * (1 - x_diff) * (1 - y_diff) + + B * (x_diff) * (1 - y_diff) + + C * (y_diff) * (1 - x_diff) + + D * (x_diff * y_diff)); + + BYTE *destbyte = (BYTE *)&dest[offset]; + destbyte[3] = gray; + offset++; + } + } +} + +HRESULT PhysicalCamera::FillSegMask(IMFSample *pSample) +{ + + if (!pSample) { + return E_POINTER; + } + + wil::com_ptr_nothrow spMetadataAttri = nullptr; + RETURN_IF_FAILED(pSample->GetUnknown(MFSampleExtension_CaptureMetadata, + IID_PPV_ARGS(&spMetadataAttri))); + + UINT32 cbBlobSize; + RETURN_IF_FAILED(spMetadataAttri->GetBlobSize( + MF_CAPTURE_METADATA_FRAME_BACKGROUND_MASK, &cbBlobSize)); + + if (m_uiSegMaskBufSize < cbBlobSize) { + if (m_pSegMaskBuf) { + delete[] m_pSegMaskBuf; + m_pSegMaskBuf = nullptr; + } + } + + if (!m_pSegMaskBuf) { + m_pSegMaskBuf = new BYTE[cbBlobSize]; + if (!m_pSegMaskBuf) { + return E_OUTOFMEMORY; + } + m_uiSegMaskBufSize = cbBlobSize; + } + + UINT32 cbBlobSizeReal = 0; + RETURN_IF_FAILED(spMetadataAttri->GetBlob( + MF_CAPTURE_METADATA_FRAME_BACKGROUND_MASK, m_pSegMaskBuf, + cbBlobSize, &cbBlobSizeReal)); + + return S_OK; +} + +HRESULT PhysicalCamera::FillAlphaWithSegMask(DWORD *pData) +{ + KSCAMERA_METADATA_BACKGROUNDSEGMENTATIONMASK *mask = + (KSCAMERA_METADATA_BACKGROUNDSEGMENTATIONMASK *)m_pSegMaskBuf; + + if (!mask) { + return E_FAIL; + } + + resizeBilinearAlpha(mask->MaskData, mask->MaskResolution.cx, + mask->MaskResolution.cy, pData, m_uiWidth, + m_uiHeight); + + return S_OK; +} + +#ifdef ARGB_SEG_MASK_WORKAROUND +__forceinline BYTE Clip(int clr) +{ + return (BYTE)(clr < 0 ? 0 : (clr > 255 ? 255 : clr)); +} + +__forceinline RGBQUAD ConvertYCrCbToRGB(int y, int cr, int cb) +{ + RGBQUAD rgbq; + + int c = y - 16; + int d = cb - 128; + int e = cr - 128; + + rgbq.rgbRed = Clip((298 * c + 409 * e + 128) >> 8); + rgbq.rgbGreen = Clip((298 * c - 100 * d - 208 * e + 128) >> 8); + rgbq.rgbBlue = Clip((298 * c + 516 * d + 128) >> 8); + rgbq.rgbReserved = 0; + + return rgbq; +} + +void ConvertNv12ToArgb(BYTE *pDst, LONG dstStride, const BYTE *pSrc, + LONG srcStride, DWORD dwWidthInPixels, + DWORD dwHeightInPixels) +{ + const BYTE *lpBitsY = pSrc; + const BYTE *lpBitsCb = lpBitsY + (dwHeightInPixels * srcStride); + ; + const BYTE *lpBitsCr = lpBitsCb + 1; + + for (UINT y = 0; y < dwHeightInPixels; y += 2) { + const BYTE *lpLineY1 = lpBitsY; + const BYTE *lpLineY2 = lpBitsY + srcStride; + const BYTE *lpLineCr = lpBitsCr; + const BYTE *lpLineCb = lpBitsCb; + + LPBYTE lpDibLine1 = pDst; + LPBYTE lpDibLine2 = pDst + dstStride; + + for (UINT x = 0; x < dwWidthInPixels; x += 2) { + int y0 = (int)lpLineY1[0]; + int y1 = (int)lpLineY1[1]; + int y2 = (int)lpLineY2[0]; + int y3 = (int)lpLineY2[1]; + int cb = (int)lpLineCb[0]; + int cr = (int)lpLineCr[0]; + + RGBQUAD r = ConvertYCrCbToRGB(y0, cr, cb); + lpDibLine1[0] = r.rgbBlue; + lpDibLine1[1] = r.rgbGreen; + lpDibLine1[2] = r.rgbRed; + lpDibLine1[3] = 255; // Alpha + + r = ConvertYCrCbToRGB(y1, cr, cb); + lpDibLine1[4] = r.rgbBlue; + lpDibLine1[5] = r.rgbGreen; + lpDibLine1[6] = r.rgbRed; + lpDibLine1[7] = 255; // Alpha + + r = ConvertYCrCbToRGB(y2, cr, cb); + lpDibLine2[0] = r.rgbBlue; + lpDibLine2[1] = r.rgbGreen; + lpDibLine2[2] = r.rgbRed; + lpDibLine2[3] = 255; // Alpha + + r = ConvertYCrCbToRGB(y3, cr, cb); + lpDibLine2[4] = r.rgbBlue; + lpDibLine2[5] = r.rgbGreen; + lpDibLine2[6] = r.rgbRed; + lpDibLine2[7] = 255; // Alpha + + lpLineY1 += 2; + lpLineY2 += 2; + lpLineCr += 2; + lpLineCb += 2; + + lpDibLine1 += 8; + lpDibLine2 += 8; + } + + pDst += (2 * dstStride); + lpBitsY += (2 * srcStride); + lpBitsCr += srcStride; + lpBitsCb += srcStride; + } +} +#endif + +HRESULT PhysicalCamera::OnReadSample(HRESULT hrStatus, DWORD dwPhyStrmIndex, + DWORD dwStreamFlags, LONGLONG llTimestamp, + IMFSample *pSample // Can be NULL +) +{ + HRESULT hr = S_OK; + winrt::slim_lock_guard lock(m_Lock); + + if (FAILED(hrStatus)) { + hr = hrStatus; + } + + if (SUCCEEDED(hr)) { + + bool bHasMask = SUCCEEDED(FillSegMask(pSample)); + if (!bHasMask) { + printf("#"); + } + + if (pSample && m_cbVideoData && m_uiWidth && m_uiHeight) { + + wil::com_ptr_nothrow spBuffer = nullptr; + RETURN_IF_FAILED( + pSample->GetBufferByIndex(0, &spBuffer)); + + VideoBufferLock buffer(spBuffer.get()); + + LONG lStride = 0; + BYTE *pbScanline0 = NULL; + + BYTE *pBufferOut = nullptr; + RETURN_IF_FAILED( + buffer.LockBuffer(m_lDefaultStride, m_uiHeight, + &pbScanline0, &lStride)); + + pBufferOut = pbScanline0; + + int bufSize = 0; + if (m_fmt == MF_COLOR_FORMAT_NV12) { + bufSize = m_uiWidth * m_uiHeight * 3 / 2; + } else if (m_fmt == MF_COLOR_FORMAT_ARGB || + m_fmt == MF_COLOR_FORMAT_XRGB) { + bufSize = m_uiWidth * m_uiHeight * 4; + +#ifdef ARGB_SEG_MASK_WORKAROUND + pBufferOut = m_pBufferArgbOut; + ConvertNv12ToArgb(m_pBufferArgbOut, + m_uiWidth * 4, pbScanline0, + lStride, m_uiWidth, + m_uiHeight); +#endif + if (bHasMask && m_transparent) { + FillAlphaWithSegMask( + (DWORD *)pBufferOut); + } + } + + m_cbVideoData(pBufferOut, bufSize, llTimestamp, + m_pUserData); + } + } + + // Request the next frame. + if (SUCCEEDED(hr)) { + if (m_spSourceReader) { + hr = m_spSourceReader->ReadSample( + dwPhyStrmIndex, // (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, + 0, + NULL, // actual + NULL, // flags + NULL, // timestamp + NULL // sample + ); + } + } + + return hr; +} + +HRESULT +PhysicalCamera::GetPhysicalCameras(std::vector &cameras) +{ + HRESULT hr = S_OK; + UINT32 uiDeviceCount = 0; + IMFActivate **ppDevices = nullptr; + + UINT32 cch = 0; + wchar_t *wszSymbolicLink = NULL; + wchar_t *wszFriendlyName = NULL; + + wil::com_ptr_nothrow spAttributes = nullptr; + RETURN_IF_FAILED(MFCreateAttributes(&spAttributes, 1)); + RETURN_IF_FAILED(spAttributes->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)); + + RETURN_IF_FAILED(MFEnumDeviceSources(spAttributes.get(), &ppDevices, + &uiDeviceCount)); + if (!uiDeviceCount) { + hr = E_FAIL; + goto done; + } + + for (size_t idx = 0; idx < uiDeviceCount; idx++) { + + hr = ppDevices[idx]->GetAllocatedString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, + &wszSymbolicLink, &cch); + if (FAILED(hr)) { + goto done; + } + + CameraInformation info; + info.SymbolicLink = wszSymbolicLink; + CoTaskMemFree(wszSymbolicLink); + + hr = ppDevices[idx]->GetAllocatedString( + MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &wszFriendlyName, + &cch); + if (FAILED(hr)) { + goto done; + } + + info.FriendlyName = wszFriendlyName; + CoTaskMemFree(wszFriendlyName); + + cameras.push_back(info); + } + +done: + + for (DWORD i = 0; i < uiDeviceCount; i++) { + if (ppDevices[i]) { + ppDevices[i]->Release(); + } + } + CoTaskMemFree(ppDevices); + + return hr; +} + +HRESULT FindMatchingMediaType(IMFMediaType *pType, + IMFMediaTypeHandler *pPhyHandler, + bool bFindMJPGH264, bool bMatchAspectRatio, + IMFMediaType **ppMatchingType) +{ + if (!pType || !pPhyHandler || !ppMatchingType) { + return E_INVALIDARG; + } + + *ppMatchingType = nullptr; + + DWORD cTypes = 0; + RETURN_IF_FAILED(pPhyHandler->GetMediaTypeCount(&cTypes)); + + UINT32 _cx = 0, _cy = 0, _fpsN = 0, _fpsD = 0; + RETURN_IF_FAILED( + MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &_cx, &_cy)); + RETURN_IF_FAILED( + MFGetAttributeRatio(pType, MF_MT_FRAME_RATE, &_fpsN, &_fpsD)); + + bool found = false; + UINT32 cx = 0, cy = 0, xc = 0, yc = 0, fpsN = 0, fpsD = 0; + + GUID majorType, subType; + wil::com_ptr_nothrow spMatchingPhyType; + + long _area = _cx * _cy; + long _delta = LONG_MAX; + wil::com_ptr_nothrow spPhyType; + for (DWORD i = 0; i < cTypes; i++) { + RETURN_IF_FAILED( + pPhyHandler->GetMediaTypeByIndex(i, &spPhyType)); + RETURN_IF_FAILED( + spPhyType->GetGUID(MF_MT_MAJOR_TYPE, &majorType)); + + if (majorType != MFMediaType_Video) { + return E_UNEXPECTED; + } + + RETURN_IF_FAILED(spPhyType->GetGUID(MF_MT_SUBTYPE, &subType)); + + if (bFindMJPGH264 && (subType != MFVideoFormat_MJPG && + subType != MFVideoFormat_H264)) { + spPhyType.reset(); + continue; + } + + RETURN_IF_FAILED(MFGetAttributeRatio( + spPhyType.get(), MF_MT_FRAME_RATE, &fpsN, &fpsD)); + + if (fpsN != _fpsN && fpsD != _fpsD) { + spPhyType.reset(); + continue; + } + + RETURN_IF_FAILED(MFGetAttributeSize( + spPhyType.get(), MF_MT_FRAME_SIZE, &cx, &cy)); + + double aspect_ratio = (double)cx / (double)cy; + double _aspect_ratio = (double)_cx / (double)_cy; + if (bMatchAspectRatio && + fabs(aspect_ratio - _aspect_ratio) > 0.1) { + spPhyType.reset(); + continue; + } + + long area = cx * cy; + long delta = abs(_area - area); + if (delta < _delta) { + _delta = delta; + spMatchingPhyType = nullptr; + spMatchingPhyType = spPhyType; + xc = cx; + yc = cy; + } + + spPhyType.reset(); + } + + if (!spMatchingPhyType) { + return E_UNEXPECTED; + } + + printf("%d x %d : %d x %d - %d / %d : %d / %d", xc, yc, _cx, _cy, fpsN, + fpsD, _fpsN, _fpsD); + + wil::com_ptr_nothrow spMatchingPhyTypeClone; + RETURN_IF_FAILED(MFCreateMediaType(&spMatchingPhyTypeClone)); + RETURN_IF_FAILED( + spMatchingPhyType->CopyAllItems(spMatchingPhyTypeClone.get())); + *ppMatchingType = spMatchingPhyTypeClone.detach(); + + return S_OK; +} + +HRESULT CheckCompressedMediaType(IMFMediaTypeHandler *pPhyHandler, + bool &bHasMjpgType, bool &bHasH264Type) +{ + bHasMjpgType = false; + bHasH264Type = false; + + DWORD cTypes = 0; + RETURN_IF_FAILED(pPhyHandler->GetMediaTypeCount(&cTypes)); + + wil::com_ptr_nothrow spPhyType; + GUID majorType, subType; + for (DWORD i = 0; i < cTypes; i++) { + RETURN_IF_FAILED( + pPhyHandler->GetMediaTypeByIndex(i, &spPhyType)); + RETURN_IF_FAILED( + spPhyType->GetGUID(MF_MT_MAJOR_TYPE, &majorType)); + if (majorType != MFMediaType_Video) { + return E_UNEXPECTED; + } + + RETURN_IF_FAILED(spPhyType->GetGUID(MF_MT_SUBTYPE, &subType)); + if (subType == MFVideoFormat_MJPG) { + bHasMjpgType = true; + } else if (subType == MFVideoFormat_H264) { + bHasMjpgType = true; + } + + spPhyType.reset(); + } + + return S_OK; +} + +HRESULT PhysicalCamera::FindMatchingNativeMediaType( + DWORD dwPhyStrmIndex, UINT32 uiWidth, UINT32 uiHeight, UINT32 uiFpsN, + UINT32 uiFpsD, IMFMediaType **ppMatchingType) +{ + LONGLONG llInterval = 10000000ll * uiFpsD / uiFpsN; + return FindMatchingNativeMediaType(dwPhyStrmIndex, uiWidth, uiHeight, + llInterval, ppMatchingType); +} + +HRESULT PhysicalCamera::FindMatchingNativeMediaType( + DWORD dwPhyStrmIndex, UINT32 uiWidth, UINT32 uiHeight, + LONGLONG llInterval, IMFMediaType **ppMatchingType) +{ + printf("%s, Begin\n", __FUNCSIG__); + HRESULT hr = S_OK; + + GUID majorType; + wil::com_ptr_nothrow spPhyPresDesc = nullptr; + RETURN_IF_FAILED( + m_spDevSource->CreatePresentationDescriptor(&spPhyPresDesc)); + + DWORD dwPhyStrmDescCount = 0; + RETURN_IF_FAILED( + spPhyPresDesc->GetStreamDescriptorCount(&dwPhyStrmDescCount)); + + *ppMatchingType = nullptr; + wil::com_ptr_nothrow spPhyStrmDesc; + wil::com_ptr_nothrow spPhyHandler; + + printf("%s, To get stream descriptor\n", __FUNCSIG__); + BOOL fSelected; + RETURN_IF_FAILED(spPhyPresDesc->GetStreamDescriptorByIndex( + dwPhyStrmIndex, &fSelected, &spPhyStrmDesc)); + RETURN_IF_FAILED(spPhyStrmDesc->GetMediaTypeHandler(&spPhyHandler)); + + DWORD cTypes = 0; + RETURN_IF_FAILED(spPhyHandler->GetMediaTypeCount(&cTypes)); + + wil::com_ptr_nothrow spMatchingPhyType = nullptr; + wil::com_ptr_nothrow spPhyType = nullptr; + + double _maxfps = -1.0; + UINT32 fpsN = 0, fpsD = 0, cx = 0, cy = 0; + for (DWORD i = 0; i < cTypes; i++) { + RETURN_IF_FAILED( + spPhyHandler->GetMediaTypeByIndex(i, &spPhyType)); + RETURN_IF_FAILED( + spPhyType->GetGUID(MF_MT_MAJOR_TYPE, &majorType)); + + if (majorType != MFMediaType_Video) { + return E_UNEXPECTED; + } + + RETURN_IF_FAILED(MFGetAttributeRatio( + spPhyType.get(), MF_MT_FRAME_RATE, &fpsN, &fpsD)); + RETURN_IF_FAILED(MFGetAttributeSize( + spPhyType.get(), MF_MT_FRAME_SIZE, &cx, &cy)); + + if (cx == uiWidth && cy == uiHeight) { + if (llInterval == 0) { + double _fps = (double)fpsN / (double)fpsD; + if (_fps > _maxfps) { + spMatchingPhyType = nullptr; + spMatchingPhyType = spPhyType; + _maxfps = _fps; + } + } else { + double _fps = (double)fpsN / (double)fpsD; + double fps = + (double)10000000.0 / (double)llInterval; + if (fabs(fps - _fps) < 0.1) { + spMatchingPhyType = nullptr; + spMatchingPhyType = spPhyType; + break; + } + } + } + + spPhyType.reset(); + } + + if (!spMatchingPhyType) { + return E_UNEXPECTED; + } + + wil::com_ptr_nothrow spMatchingPhyTypeClone; + RETURN_IF_FAILED(MFCreateMediaType(&spMatchingPhyTypeClone)); + RETURN_IF_FAILED( + spMatchingPhyType->CopyAllItems(spMatchingPhyTypeClone.get())); + *ppMatchingType = spMatchingPhyTypeClone.detach(); + + printf("%s, End\n", __FUNCSIG__); + return hr; +} + +HRESULT +PhysicalCamera::GetStreamCapabilities(DWORD dwPhyStrmIndex, + std::vector &strmCaps) +{ + if (!m_spDevSource) { + return E_POINTER; + } + + wil::com_ptr_nothrow spPhyPresDesc = nullptr; + RETURN_IF_FAILED( + m_spDevSource->CreatePresentationDescriptor(&spPhyPresDesc)); + + DWORD dwPhyStrmDescCount = 0; + RETURN_IF_FAILED( + spPhyPresDesc->GetStreamDescriptorCount(&dwPhyStrmDescCount)); + + wil::com_ptr_nothrow spPhyStrmDesc; + wil::com_ptr_nothrow spPhyHandler; + + BOOL fSelected; + RETURN_IF_FAILED(spPhyPresDesc->GetStreamDescriptorByIndex( + dwPhyStrmIndex, &fSelected, &spPhyStrmDesc)); + RETURN_IF_FAILED(spPhyStrmDesc->GetMediaTypeHandler(&spPhyHandler)); + + DWORD cTypes = 0; + RETURN_IF_FAILED(spPhyHandler->GetMediaTypeCount(&cTypes)); + + wil::com_ptr_nothrow spPhyType; + GUID majorType, subType; + for (DWORD i = 0; i < cTypes; i++) { + RETURN_IF_FAILED( + spPhyHandler->GetMediaTypeByIndex(i, &spPhyType)); + RETURN_IF_FAILED( + spPhyType->GetGUID(MF_MT_MAJOR_TYPE, &majorType)); + if (majorType != MFMediaType_Video) { + return E_UNEXPECTED; + } + + RETURN_IF_FAILED(spPhyType->GetGUID(MF_MT_SUBTYPE, &subType)); + + UINT32 _cx, _cy, _fpsN, _fpsD; + RETURN_IF_FAILED(MFGetAttributeSize( + spPhyType.get(), MF_MT_FRAME_SIZE, &_cx, &_cy)); + RETURN_IF_FAILED(MFGetAttributeRatio( + spPhyType.get(), MF_MT_FRAME_RATE, &_fpsN, &_fpsD)); + + StreamInformation cap; + cap.guidSubtype = subType; + cap.uiWidth = _cx; + cap.uiHeight = _cy; + cap.uiFpsN = _fpsN; + cap.uiFpsD = _fpsD; + strmCaps.push_back(cap); + + spPhyType.reset(); + } + + return S_OK; +} + +HRESULT CreateMediaTypeFrom(IMFMediaType *pType, MF_COLOR_FORMAT fmt, + IMFMediaType **ppType) +{ + wil::com_ptr_nothrow spTypeClone; + RETURN_IF_FAILED(MFCreateMediaType(&spTypeClone)); + RETURN_IF_FAILED(pType->CopyAllItems(spTypeClone.get())); + + UINT32 uiWidth, uiHeight; + RETURN_IF_FAILED(MFGetAttributeSize(spTypeClone.get(), MF_MT_FRAME_SIZE, + &uiWidth, &uiHeight)); + + UINT32 uiFpsN, uiFpsD; + RETURN_IF_FAILED(MFGetAttributeRatio( + spTypeClone.get(), MF_MT_FRAME_RATE, &uiFpsN, &uiFpsD)); + + uint32_t bitrate; + GUID mffmt = GUID_NULL; + +#ifdef ARGB_SEG_MASK_WORKAROUND + mffmt = MFVideoFormat_NV12; + bitrate = (uint32_t)(uiHeight * 1.5 * uiWidth * 8 * uiFpsN / uiFpsD); +#else + if (fmt == MF_COLOR_FORMAT::MF_COLOR_FORMAT_NV12) { + mffmt = MFVideoFormat_NV12; + bitrate = (uint32_t)(uiHeight * 1.5 * uiWidth * 8 * uiFpsN / + uiFpsD); + } else if (fmt == MF_COLOR_FORMAT::MF_COLOR_FORMAT_ARGB) { + mffmt = MFVideoFormat_ARGB32; + bitrate = (uint32_t)(uiHeight * uiWidth * 8 * 4 * uiFpsN / + uiFpsD); + } else if (fmt == MF_COLOR_FORMAT::MF_COLOR_FORMAT_XRGB) { + mffmt = MFVideoFormat_RGB32; + bitrate = (uint32_t)(uiHeight * uiWidth * 8 * 4 * uiFpsN / + uiFpsD); + } else { + return E_INVALIDARG; + } +#endif + + RETURN_IF_FAILED(spTypeClone->SetGUID(MF_MT_SUBTYPE, mffmt)); + RETURN_IF_FAILED(spTypeClone->SetUINT32(MF_MT_INTERLACE_MODE, + MFVideoInterlace_Progressive)); + RETURN_IF_FAILED( + spTypeClone->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE)); + RETURN_IF_FAILED(MFSetAttributeRatio(spTypeClone.get(), + MF_MT_PIXEL_ASPECT_RATIO, 1, 1)); + + RETURN_IF_FAILED(spTypeClone->SetUINT32(MF_MT_AVG_BITRATE, bitrate)); + + *ppType = spTypeClone.detach(); + + return S_OK; +} + +HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride) +{ + LONG lStride = 0; + HRESULT hr = pType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32 *)&lStride); + if (FAILED(hr)) { + UINT32 cx = 0, cy = 0; + GUID subType = GUID_NULL; + RETURN_IF_FAILED(pType->GetGUID(MF_MT_SUBTYPE, &subType)); + RETURN_IF_FAILED( + MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &cx, &cy)); + RETURN_IF_FAILED(MFGetStrideForBitmapInfoHeader(subType.Data1, + cx, &lStride)); + } + *plStride = lStride; + return S_OK; +} + +HRESULT PhysicalCamera::SetOutputResolution(DWORD dwPhyStrmIndex, + UINT32 uiWidth, UINT32 uiHeight, + LONGLONG llInterval, + MF_COLOR_FORMAT fmt) +{ + printf("%s, Begin\n", __FUNCSIG__); + winrt::slim_lock_guard lock(m_Lock); + + if (!m_spSourceReader) { + return E_POINTER; + } + + wil::com_ptr_nothrow spMatchingType = nullptr; + + RETURN_IF_FAILED(FindMatchingNativeMediaType(dwPhyStrmIndex, uiWidth, + uiHeight, llInterval, + &spMatchingType)); + + wil::com_ptr_nothrow spOutputType = nullptr; + RETURN_IF_FAILED( + CreateMediaTypeFrom(spMatchingType.get(), fmt, &spOutputType)); + + DWORD dwStreamFlags; + wil::com_ptr_nothrow spSourceReaderEx; + RETURN_IF_FAILED(m_spSourceReader->QueryInterface(&spSourceReaderEx)); + RETURN_IF_FAILED(spSourceReaderEx->SetNativeMediaType( + dwPhyStrmIndex, spMatchingType.get(), &dwStreamFlags)); + RETURN_IF_FAILED(m_spSourceReader->SetCurrentMediaType( + dwPhyStrmIndex, NULL, spOutputType.get())); + RETURN_IF_FAILED( + m_spSourceReader->SetStreamSelection(dwPhyStrmIndex, TRUE)); + RETURN_IF_FAILED( + GetDefaultStride(spOutputType.get(), &m_lDefaultStride)); + + m_uiWidth = uiWidth; + m_uiHeight = uiHeight; + m_fmt = fmt; + +#ifdef ARGB_SEG_MASK_WORKAROUND + if (fmt == MF_COLOR_FORMAT::MF_COLOR_FORMAT_ARGB || + fmt == MF_COLOR_FORMAT::MF_COLOR_FORMAT_XRGB) { + m_pBufferArgbOut = new BYTE[m_uiWidth * m_uiHeight * 4 + 1024]; + if (!m_pBufferArgbOut) { + return E_OUTOFMEMORY; + } + } +#endif + + printf("%s, End\n", __FUNCSIG__); + return S_OK; +} + +HRESULT PhysicalCamera::GetCurrentStreamInformation(DWORD dwPhyStrmIndex, + StreamInformation &strmInfo) +{ + printf("%s, Begin\n", __FUNCSIG__); + + strmInfo = {0}; + + winrt::slim_lock_guard lock(m_Lock); + if (!m_spSourceReader) { + return E_POINTER; + } + + wil::com_ptr_nothrow spType; + RETURN_IF_FAILED( + m_spSourceReader->GetCurrentMediaType(dwPhyStrmIndex, &spType)); + + GUID majorType, subType; + RETURN_IF_FAILED(spType->GetGUID(MF_MT_MAJOR_TYPE, &majorType)); + if (majorType != MFMediaType_Video) { + printf("%s, Not Video Type\n", __FUNCSIG__); + return E_UNEXPECTED; + } + + RETURN_IF_FAILED(spType->GetGUID(MF_MT_SUBTYPE, &subType)); + + UINT32 _cx, _cy, _fpsN, _fpsD; + RETURN_IF_FAILED( + MFGetAttributeSize(spType.get(), MF_MT_FRAME_SIZE, &_cx, &_cy)); + RETURN_IF_FAILED(MFGetAttributeRatio(spType.get(), MF_MT_FRAME_RATE, + &_fpsN, &_fpsD)); + + strmInfo.guidSubtype = subType; + strmInfo.uiWidth = _cx; + strmInfo.uiHeight = _cy; + strmInfo.uiFpsN = _fpsN; + strmInfo.uiFpsD = _fpsD; + + printf("%s, End\n", __FUNCSIG__); + return S_OK; +} + +HRESULT PhysicalCamera::SetBlur(bool blur, bool shallowFocus, bool mask) +{ + + printf("%s, %d, Begin\n", __FUNCSIG__, __LINE__); + winrt::slim_lock_guard lock(m_Lock); + + ULONG ulPropertyId = + KSPROPERTY_CAMERACONTROL_EXTENDED_BACKGROUNDSEGMENTATION; + wil::com_ptr spExtControl = nullptr; + + ULONGLONG ulFlags = KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_OFF; + + HRESULT hr = m_spExtController->GetExtendedCameraControl( + KSCAMERA_EXTENDEDPROP_FILTERSCOPE, ulPropertyId, &spExtControl); + if (FAILED(hr)) { + printf("Failed to GetExtendedCameraControl, hr = 0x%x\n", hr); + goto done; + } + + if (blur) { + ulFlags = KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_BLUR; + if (shallowFocus) { + ulFlags |= + KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_SHALLOWFOCUS; + } + } + + if (mask) { + ulFlags |= KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_MASK; + } + + hr = spExtControl->SetFlags(ulFlags); + if (FAILED(hr)) { + printf("Failed to SetFlags, hr = 0x%x\n", hr); + goto done; + } + + hr = spExtControl->CommitSettings(); + if (FAILED(hr)) { + printf("Failed to CommitSettings, hr = 0x%x\n", hr); + goto done; + } + + for (auto &setting : s_MepSettings) { + if (setting.SymbolicLink == m_wsSymbolicName) { + setting.Blur = blur; + setting.ShallowFocus = shallowFocus; + setting.Mask = mask; + break; + } + } + +done: + + printf("%s, %d, End, hr = 0x%x\n", __FUNCSIG__, __LINE__, hr); + return hr; +} + +HRESULT PhysicalCamera::GetBlur(bool &blur, bool &shallowFocus, bool &mask) +{ + printf("%s, %d, Begin\n", __FUNCSIG__, __LINE__); + winrt::slim_lock_guard lock(m_Lock); + + ULONG ulPropertyId = + KSPROPERTY_CAMERACONTROL_EXTENDED_BACKGROUNDSEGMENTATION; + wil::com_ptr spExtControl = nullptr; + + ULONGLONG ulFlags = KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_OFF; + + HRESULT hr = m_spExtController->GetExtendedCameraControl( + KSCAMERA_EXTENDEDPROP_FILTERSCOPE, ulPropertyId, &spExtControl); + if (FAILED(hr)) { + printf("Failed to GetExtendedCameraControl, hr = 0x%x\n", hr); + return hr; + } + + ulFlags = spExtControl->GetFlags(); + + blur = ulFlags & KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_BLUR; + shallowFocus = + ulFlags & + KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_SHALLOWFOCUS; + mask = ulFlags & KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_MASK; + + printf("%s, %d, End, hr = 0x%x\n", __FUNCSIG__, __LINE__, hr); + return hr; +} + +HRESULT PhysicalCamera::SetAutoFraming(bool enable) +{ + printf("%s, %d, Begin\n", __FUNCSIG__, __LINE__); + winrt::slim_lock_guard lock(m_Lock); + + ULONG ulPropertyId = KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW; + wil::com_ptr spExtControl = nullptr; + + ULONGLONG ulFlags = KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_MANUAL; + + HRESULT hr = m_spExtController->GetExtendedCameraControl( + KSCAMERA_EXTENDEDPROP_FILTERSCOPE, ulPropertyId, &spExtControl); + if (FAILED(hr)) { + printf("Failed to GetExtendedCameraControl, hr = 0x%x\n", hr); + goto done; + } + + printf("%s, Succeeded to GetExtendedCameraControl\n", __FUNCSIG__); + + if (enable) { + ulFlags = KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_AUTOFACEFRAMING; + } + + hr = spExtControl->SetFlags(ulFlags); + if (FAILED(hr)) { + printf("Failed to SetFlags, hr = 0x%x\n", hr); + goto done; + } + + printf("%s, Succeeded to SetFlags\n", __FUNCSIG__); + + hr = spExtControl->CommitSettings(); + if (FAILED(hr)) { + printf("Failed to CommitSettings, hr = 0x%x\n", hr); + goto done; + } + + for (auto &setting : s_MepSettings) { + if (setting.SymbolicLink == m_wsSymbolicName) { + setting.AutoFraming = enable; + break; + } + } + + printf("%s, Succeeded to CommitSettings\n", __FUNCSIG__); + +done: + + printf("%s, %d, End, hr = 0x%x\n", __FUNCSIG__, __LINE__, hr); + return hr; +} + +HRESULT PhysicalCamera::GetAutoFraming(bool &enable) +{ + printf("%s, %d, Begin\n", __FUNCSIG__, __LINE__); + winrt::slim_lock_guard lock(m_Lock); + + ULONG ulPropertyId = KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW; + wil::com_ptr spExtControl = nullptr; + + ULONGLONG ulFlags = KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_MANUAL; + + HRESULT hr = m_spExtController->GetExtendedCameraControl( + KSCAMERA_EXTENDEDPROP_FILTERSCOPE, ulPropertyId, &spExtControl); + if (FAILED(hr)) { + printf("Failed to GetExtendedCameraControl, hr = 0x%x\n", hr); + return hr; + } + + enable = false; + ulFlags = spExtControl->GetFlags(); + if (ulFlags & KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_AUTOFACEFRAMING) { + enable = true; + } + + printf("%s, %d, End, hr = 0x%x\n", __FUNCSIG__, __LINE__, hr); + return hr; +} + +HRESULT PhysicalCamera::SetEyeGazeCorrection(bool enable) +{ + printf("%s, %d, Begin\n", __FUNCSIG__, __LINE__); + winrt::slim_lock_guard lock(m_Lock); + + ULONG ulPropertyId = + KSPROPERTY_CAMERACONTROL_EXTENDED_EYEGAZECORRECTION; + wil::com_ptr spExtControl = nullptr; + + ULONGLONG ulFlags = KSCAMERA_EXTENDEDPROP_EYEGAZECORRECTION_OFF; + + HRESULT hr = m_spExtController->GetExtendedCameraControl( + KSCAMERA_EXTENDEDPROP_FILTERSCOPE, ulPropertyId, &spExtControl); + if (FAILED(hr)) { + printf("Failed to GetExtendedCameraControl, hr = 0x%x\n", hr); + goto done; + } + + if (enable) { + ulFlags = KSCAMERA_EXTENDEDPROP_EYEGAZECORRECTION_ON; + } + + hr = spExtControl->SetFlags(ulFlags); + if (FAILED(hr)) { + printf("Failed to SetFlags, hr = 0x%x\n", hr); + goto done; + } + + hr = spExtControl->CommitSettings(); + if (FAILED(hr)) { + printf("Failed to CommitSettings, hr = 0x%x\n", hr); + goto done; + } + + for (auto &setting : s_MepSettings) { + if (setting.SymbolicLink == m_wsSymbolicName) { + setting.EyeGazeCorrection = enable; + break; + } + } + +done: + + printf("%s, %d, End, hr = 0x%x\n", __FUNCSIG__, __LINE__, hr); + return hr; +} + +HRESULT PhysicalCamera::GetEyeGazeCorrection(bool &enable) +{ + printf("%s, %d, Begin\n", __FUNCSIG__, __LINE__); + winrt::slim_lock_guard lock(m_Lock); + + ULONG ulPropertyId = + KSPROPERTY_CAMERACONTROL_EXTENDED_EYEGAZECORRECTION; + wil::com_ptr spExtControl = nullptr; + + ULONGLONG ulFlags = KSCAMERA_EXTENDEDPROP_EYEGAZECORRECTION_OFF; + + HRESULT hr = m_spExtController->GetExtendedCameraControl( + KSCAMERA_EXTENDEDPROP_FILTERSCOPE, ulPropertyId, &spExtControl); + if (FAILED(hr)) { + printf("Failed to GetExtendedCameraControl, hr = 0x%x\n", hr); + return hr; + } + + enable = false; + ulFlags = spExtControl->GetFlags(); + if (ulFlags & KSCAMERA_EXTENDEDPROP_EYEGAZECORRECTION_ON) { + enable = true; + } + + printf("%s, %d, End, hr = 0x%x\n", __FUNCSIG__, __LINE__, hr); + return hr; +} + +std::vector PhysicalCamera::s_MepSettings; +HRESULT PhysicalCamera::GetMepSetting(MepSetting &setting) +{ + + bool blur, shallowfocus, mask, autoframing, eyegaze; + + RETURN_IF_FAILED(GetBlur(blur, shallowfocus, mask)); + setting.Blur = blur; + setting.ShallowFocus = shallowfocus; + setting.Mask = mask; + + RETURN_IF_FAILED(GetAutoFraming(autoframing)); + setting.AutoFraming = autoframing; + + RETURN_IF_FAILED(GetEyeGazeCorrection(eyegaze)); + setting.EyeGazeCorrection = eyegaze; + + return S_OK; +} + +HRESULT PhysicalCamera::SetMepSetting(MepSetting &setting) +{ + + RETURN_IF_FAILED( + SetBlur(setting.Blur, setting.ShallowFocus, setting.Mask)); + RETURN_IF_FAILED(SetAutoFraming(setting.AutoFraming)); + RETURN_IF_FAILED(SetEyeGazeCorrection(setting.EyeGazeCorrection)); + return S_OK; +} + +HRESULT PhysicalCamera::SaveSettingsToDefault() +{ + bool found = false; + for (auto &setting : s_MepSettings) { + if (setting.SymbolicLink == m_wsSymbolicName) { + RETURN_IF_FAILED(GetMepSetting(setting)); + found = true; + break; + } + } + + if (!found) { + MepSetting setting; + setting.SymbolicLink = m_wsSymbolicName; + RETURN_IF_FAILED(GetMepSetting(setting)); + s_MepSettings.push_back(setting); + } + return S_OK; +} + +HRESULT PhysicalCamera::RestoreDefaultSettings() +{ + + for (auto &setting : s_MepSettings) { + if (setting.SymbolicLink == m_wsSymbolicName) { + RETURN_IF_FAILED(SetMepSetting(setting)); + break; + } + } + + return S_OK; +} diff --git a/plugins/win-mf/libmfcapture/source/PhysicalCamera.hpp b/plugins/win-mf/libmfcapture/source/PhysicalCamera.hpp new file mode 100644 index 000000000..f8f2dac72 --- /dev/null +++ b/plugins/win-mf/libmfcapture/source/PhysicalCamera.hpp @@ -0,0 +1,148 @@ +#pragma once + +#define ARGB_SEG_MASK_WORKAROUND + +struct CameraInformation { + std::wstring FriendlyName; + std::wstring SymbolicLink; +}; + +struct StreamInformation { + UINT32 uiWidth; + UINT32 uiHeight; + UINT32 uiFpsN; + UINT32 uiFpsD; + GUID guidSubtype; +}; + +struct MepSetting { + std::wstring SymbolicLink; + bool Blur = false; + bool ShallowFocus = false; + bool Mask = false; + bool AutoFraming = false; + bool EyeGazeCorrection = false; +}; + +class PhysicalCamera : public IMFSourceReaderCallback { + static std::vector s_MepSettings; + +private: + long m_cRef = 1; + winrt::slim_mutex m_Lock; + wil::com_ptr_nothrow m_spDevSource = nullptr; + wil::com_ptr_nothrow m_spSourceReader = nullptr; + wil::com_ptr_nothrow m_spDxgiDevManager = nullptr; + wil::com_ptr_nothrow m_spExtController = + nullptr; + + MF_VideoDataCallback m_cbVideoData = nullptr; + void *m_pUserData = nullptr; + + MF_COLOR_FORMAT m_fmt = MF_COLOR_FORMAT_UNKNOWN; + UINT32 m_uiWidth = 0; + UINT32 m_uiHeight = 0; + LONG m_lDefaultStride = 0; + + UINT32 m_uiSegMaskBufSize = 0; + BYTE *m_pSegMaskBuf = nullptr; + + bool m_transparent = false; + +#ifdef ARGB_SEG_MASK_WORKAROUND + BYTE *m_pBufferArgbOut = nullptr; +#endif + + std::wstring m_wsSymbolicName; + + DeviceControlChangeListener *m_pDevCtrlNotify = nullptr; + +private: + HRESULT CreateSourceReader(IMFMediaSource *pSource, + IMFDXGIDeviceManager *pDxgiDevIManager, + IMFSourceReader **ppSourceReader); + + HRESULT FindMatchingNativeMediaType(DWORD dwPhyStrmIndex, + UINT32 uiWidth, UINT32 uiHeight, + UINT32 uiFpsN, UINT32 uiFpsD, + IMFMediaType **ppMatchingType); + + HRESULT FindMatchingNativeMediaType(DWORD dwPhyStrmIndex, + UINT32 uiWidth, UINT32 uiHeight, + LONGLONG llInterval, + IMFMediaType **ppMatchingType); + + HRESULT GetMepSetting(MepSetting &setting); + HRESULT SetMepSetting(MepSetting &setting); + + HRESULT SaveSettingsToDefault(); + + HRESULT FillSegMask(IMFSample *pSample); + HRESULT FillAlphaWithSegMask(DWORD *pData); + +public: + PhysicalCamera(); + ~PhysicalCamera(); + + PhysicalCamera(const PhysicalCamera &) = delete; + PhysicalCamera &operator=(PhysicalCamera const &) = delete; + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID iid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IMFSourceReaderCallback methods + STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwPhyStrmIndex, + DWORD dwStreamFlags, LONGLONG llTimestamp, + IMFSample *pSample); + + STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) { return S_OK; } + + STDMETHODIMP OnFlush(DWORD) { return S_OK; } + + HRESULT Initialize(LPCWSTR pwszSymLink); + + HRESULT Uninitialize(); + + HRESULT SetD3dManager(IMFDXGIDeviceManager *pD3dManager); + + HRESULT Prepare(); + + HRESULT Start(DWORD dwPhyStrmIndex, MF_VideoDataCallback cb, + void *pUserData); + + HRESULT Stop(); + + HRESULT GetStreamCapabilities(DWORD dwPhyStrmIndex, + std::vector &strmCaps); + + void SetTransparent(bool flag) { m_transparent = flag; } + + bool GetTransparent() { return m_transparent; } + + // if llInterval is 0, set the camera to us the max fps + // otherwise, set camera with matching fps. + HRESULT SetOutputResolution(DWORD dwPhyStrmIndex, UINT32 uiWidth, + UINT32 uiHeight, LONGLONG llInterval, + MF_COLOR_FORMAT fmt); + + HRESULT GetCurrentStreamInformation(DWORD dwPhyStrmIndex, + StreamInformation &strmInfo); + + HRESULT SetBlur(bool blur, bool shallowFocus, bool mask); + HRESULT GetBlur(bool &blur, bool &shallowFocus, bool &mask); + + HRESULT SetAutoFraming(bool enable); + HRESULT GetAutoFraming(bool &enable); + + HRESULT SetEyeGazeCorrection(bool enable); + HRESULT GetEyeGazeCorrection(bool &enable); + + HRESULT RestoreDefaultSettings(); + + static HRESULT CreateInstance(PhysicalCamera **ppPhysicalCamera); + + static HRESULT + GetPhysicalCameras(std::vector &cameras); +}; diff --git a/plugins/win-mf/libmfcapture/source/framework.hpp b/plugins/win-mf/libmfcapture/source/framework.hpp new file mode 100644 index 000000000..c2085273b --- /dev/null +++ b/plugins/win-mf/libmfcapture/source/framework.hpp @@ -0,0 +1,27 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include + +#include "winrt\Windows.Foundation.h" +#include "winrt\Windows.ApplicationModel.h" + +#include // must be before the first C++ WinRT header, ref:https://github.com/Microsoft/wil/wiki/Error-handling-helpers +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include diff --git a/plugins/win-mf/libmfcapture/source/mfcapture.cpp b/plugins/win-mf/libmfcapture/source/mfcapture.cpp new file mode 100644 index 000000000..9a4971720 --- /dev/null +++ b/plugins/win-mf/libmfcapture/source/mfcapture.cpp @@ -0,0 +1,224 @@ +#include "mfcapture.hpp" +#include "DeviceControlChangeListener.hpp" +#include "PhysicalCamera.hpp" + +MFCAPTURE_EXPORTS HRESULT MF_EnumerateCameras(MF_EnumerateCameraCallback cb, + void *pUserData) +{ + std::vector camlist; + RETURN_IF_FAILED(PhysicalCamera::GetPhysicalCameras(camlist)); + + for (auto &cam : camlist) { + if (cb) { + cb(cam.FriendlyName.c_str(), cam.SymbolicLink.c_str(), + pUserData); + } + } + return S_OK; +} + +MFCAPTURE_EXPORTS CAPTURE_DEVICE_HANDLE MF_Create(const wchar_t *DevId) +{ + wil::com_ptr_nothrow spCapture = nullptr; + HRESULT hr = PhysicalCamera::CreateInstance(&spCapture); + if (FAILED(hr)) { + return NULL; + } + + hr = spCapture->Initialize(DevId); + if (FAILED(hr)) { + return NULL; + } + + return spCapture.detach(); +} + +MFCAPTURE_EXPORTS void MF_Destroy(CAPTURE_DEVICE_HANDLE h) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (p) { + p->Release(); + } +} + +MFCAPTURE_EXPORTS HRESULT MF_Prepare(CAPTURE_DEVICE_HANDLE h) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + return p->Prepare(); +} + +MFCAPTURE_EXPORTS HRESULT MF_EnumerateStreamCapabilities( + CAPTURE_DEVICE_HANDLE h, MF_EnumerateStreamCapabilitiesCallback cb, + void *pUserData) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + std::vector caplist; + HRESULT hr = p->GetStreamCapabilities(0, caplist); + if (FAILED(hr)) { + return hr; + } + + for (auto &cap : caplist) { + if (cb) { + cb(cap.uiWidth, cap.uiHeight, cap.uiFpsN, cap.uiFpsD, + pUserData); + } + } + + return S_OK; +} + +MFCAPTURE_EXPORTS HRESULT MF_SetOutputResolution(CAPTURE_DEVICE_HANDLE h, + UINT32 uiWidth, + UINT32 uiHeight, + LONGLONG llInterval, + MF_COLOR_FORMAT fmt) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + return p->SetOutputResolution(0, uiWidth, uiHeight, llInterval, fmt); +} + +MFCAPTURE_EXPORTS HRESULT MF_GetOutputResolution(CAPTURE_DEVICE_HANDLE h, + UINT32 *uiWidth, + UINT32 *uiHeight, + UINT32 *uiFpsN, UINT32 *uiFpsD) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + StreamInformation info; + RETURN_IF_FAILED(p->GetCurrentStreamInformation(0, info)); + + if (uiWidth && uiHeight && uiFpsN && uiFpsD) { + *uiWidth = info.uiWidth; + *uiHeight = info.uiHeight; + *uiFpsN = info.uiFpsN; + *uiFpsD = info.uiFpsD; + } + + return S_OK; +} + +MFCAPTURE_EXPORTS HRESULT MF_Start(CAPTURE_DEVICE_HANDLE h, + MF_VideoDataCallback cb, void *pUserData) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + return p->Start(0, cb, pUserData); +} + +MFCAPTURE_EXPORTS HRESULT MF_Stop(CAPTURE_DEVICE_HANDLE h) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + return p->Stop(); +} + +MFCAPTURE_EXPORTS HRESULT MF_GetBlur(CAPTURE_DEVICE_HANDLE h, bool &blur, + bool &shallowFocus, bool &mask) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + return p->GetBlur(blur, shallowFocus, mask); +} + +MFCAPTURE_EXPORTS HRESULT MF_SetBlur(CAPTURE_DEVICE_HANDLE h, BOOL blur, + BOOL shallowFocus, BOOL mask) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + p->SetTransparent(mask); + + return p->SetBlur(blur, shallowFocus, mask); +} + +MFCAPTURE_EXPORTS HRESULT MF_GetTransparent(CAPTURE_DEVICE_HANDLE h, + bool &enable) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + enable = p->GetTransparent(); + + return S_OK; +} + +MFCAPTURE_EXPORTS HRESULT MF_GetAutoFraming(CAPTURE_DEVICE_HANDLE h, + bool &enable) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + return p->GetAutoFraming(enable); +} + +MFCAPTURE_EXPORTS HRESULT MF_SetAutoFraming(CAPTURE_DEVICE_HANDLE h, + BOOL enable) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + return p->SetAutoFraming(enable); +} + +MFCAPTURE_EXPORTS HRESULT MF_GetEyeGazeCorrection(CAPTURE_DEVICE_HANDLE h, + bool &enable) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + return p->GetEyeGazeCorrection(enable); +} + +MFCAPTURE_EXPORTS HRESULT MF_SetEyeGazeCorrection(CAPTURE_DEVICE_HANDLE h, + BOOL enable) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + return p->SetEyeGazeCorrection(enable); +} + +MFCAPTURE_EXPORTS HRESULT MF_RestoreDefaultSettings(CAPTURE_DEVICE_HANDLE h) +{ + PhysicalCamera *p = (PhysicalCamera *)h; + if (!p) { + return E_INVALIDARG; + } + + return p->RestoreDefaultSettings(); +} diff --git a/plugins/win-mf/libmfcapture/source/mfcapture.rc b/plugins/win-mf/libmfcapture/source/mfcapture.rc new file mode 100644 index 000000000..a20625f42 --- /dev/null +++ b/plugins/win-mf/libmfcapture/source/mfcapture.rc @@ -0,0 +1,141 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.hpp" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG_EFFECT_CONTROL DIALOGEX 0, 0, 235, 144 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Effect Control" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,92,123,50,14 + CONTROL "Auto Framing",IDC_CHECK_AUTOFRAMING,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,34,65,59,10 + CONTROL "Eye Gaze Correction",IDC_CHECK_EYEGAZE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,34,82,81,10 + CONTROL "Portrait Blur",IDC_RADIO_BLUR_PORTRAIT,"Button",BS_AUTORADIOBUTTON,34,38,54,10 + CONTROL "Standard Blur",IDC_RADIO_BLUR_STANDARD,"Button",BS_AUTORADIOBUTTON,99,38,59,10 + GROUPBOX "Blur",IDC_STATIC,18,20,199,40 + CONTROL "None",IDC_RADIO_NOBLUR,"Button",BS_AUTORADIOBUTTON,169,38,33,10 + CONTROL "Background Transparent",IDC_CHECK_BK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,34,99,95,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG_EFFECT_CONTROL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 230 + TOPMARGIN, 7 + BOTTOMMARGIN, 137 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_DIALOG_EFFECT_CONTROL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 30,0,1,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Intel Corporation" + VALUE "FileDescription", "OBS Video Capture module" + VALUE "FileVersion", "30.0.1" + VALUE "ProductName", "OBS Studio" + VALUE "ProductVersion", "30.0.1" + VALUE "LegalCopyright", "Copyright (C) 2023, Intel Corporation" + VALUE "InternalName", "win-mf" + VALUE "OriginalFilename", "win-mf" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409,1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/win-mf/libmfcapture/source/resource.hpp b/plugins/win-mf/libmfcapture/source/resource.hpp new file mode 100644 index 000000000..c61edea3f --- /dev/null +++ b/plugins/win-mf/libmfcapture/source/resource.hpp @@ -0,0 +1,23 @@ +// {{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by libmfcapture.rc +// +#define IDD_DIALOG1 101 +#define IDD_DIALOG_EFFECT_CONTROL 101 +#define IDC_CHECK_BK 1002 +#define IDC_CHECK_AUTOFRAMING 1003 +#define IDC_CHECK_EYEGAZE 1004 +#define IDC_RADIO_BLUR_PORTRAIT 1005 +#define IDC_RADIO_BLUR_STANDARD 1006 +#define IDC_RADIO_NOBLUR 1007 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1008 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugins/win-mf/mf-plugin.cpp b/plugins/win-mf/mf-plugin.cpp new file mode 100644 index 000000000..972bf8a30 --- /dev/null +++ b/plugins/win-mf/mf-plugin.cpp @@ -0,0 +1,24 @@ +/* mf-plugin.cpp */ +#include +#include +#include +#include "plugin-macros.generated.h" + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") + +extern void RegisterMediaFoundationSource(); + +bool obs_module_load(void) +{ + RegisterMediaFoundationSource(); + obs_data_t *obs_settings = obs_data_create(); + obs_apply_private_data(obs_settings); + obs_data_release(obs_settings); + return true; +} + +void obs_module_unload() +{ + blog(LOG_INFO, "plugin unloaded"); +} diff --git a/plugins/win-mf/plugin-macros.generated.h b/plugins/win-mf/plugin-macros.generated.h new file mode 100644 index 000000000..558c903f5 --- /dev/null +++ b/plugins/win-mf/plugin-macros.generated.h @@ -0,0 +1,28 @@ +/* +Plugin Name +Copyright (C) + +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; either version 2 of the License, or +(at your option) any later version. + +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 +*/ + +#ifndef PLUGINNAME_H +#define PLUGINNAME_H + +#define PLUGIN_NAME "obs-mf-source" +#define PLUGIN_VERSION "2023.1.0.0" + +#define blog(level, msg, ...) \ + blog(level, "[" PLUGIN_NAME "] " msg, ##__VA_ARGS__) + +#endif // PLUGINNAME_H diff --git a/plugins/win-mf/plugin-macros.h.in b/plugins/win-mf/plugin-macros.h.in new file mode 100644 index 000000000..71f9ca21a --- /dev/null +++ b/plugins/win-mf/plugin-macros.h.in @@ -0,0 +1,27 @@ +/* +Plugin Name +Copyright (C) + +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; either version 2 of the License, or +(at your option) any later version. + +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 +*/ + +#ifndef PLUGINNAME_H +#define PLUGINNAME_H + +#define PLUGIN_NAME "@CMAKE_PROJECT_NAME@" +#define PLUGIN_VERSION "@CMAKE_PROJECT_VERSION@" + +#define blog(level, msg, ...) blog(level, "[" PLUGIN_NAME "] " msg, ##__VA_ARGS__) + +#endif // PLUGINNAME_H diff --git a/plugins/win-mf/win-mf.cpp b/plugins/win-mf/win-mf.cpp new file mode 100644 index 000000000..b4e05683e --- /dev/null +++ b/plugins/win-mf/win-mf.cpp @@ -0,0 +1,1591 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mfcapture.hpp" +#include + +#include +#include + +#pragma comment(lib, "mfplat.lib") +#pragma comment(lib, "mfuuid.lib") + +#undef min +#undef max + +using namespace std; + +/* settings defines that will cause errors if there are typos */ +#define VIDEO_DEVICE_ID "video_device_id" +#define RES_TYPE "res_type" +#define RESOLUTION "resolution" +#define FRAME_INTERVAL "frame_interval" +#define LAST_VIDEO_DEV_ID "last_video_device_id" +#define LAST_RESOLUTION "last_resolution" +#define BUFFERING_VAL "buffering" +#define FLIP_IMAGE "flip_vertically" +#define COLOR_SPACE "color_space" +#define COLOR_RANGE "color_range" +#define DEACTIVATE_WNS "deactivate_when_not_showing" +#define AUTOROTATION "autorotation" + +#define TEXT_INPUT_NAME obs_module_text("VideoCaptureDevice") +#define TEXT_DEVICE obs_module_text("Device") +#define TEXT_CONFIG_VIDEO obs_module_text("ConfigureVideo") +#define TEXT_RES_FPS_TYPE obs_module_text("ResFPSType") +#define TEXT_CUSTOM_RES obs_module_text("ResFPSType.Custom") +#define TEXT_PREFERRED_RES obs_module_text("ResFPSType.DevPreferred") +#define TEXT_FPS_MATCHING obs_module_text("FPS.Matching") +#define TEXT_FPS_HIGHEST obs_module_text("FPS.Highest") +#define TEXT_RESOLUTION obs_module_text("Resolution") +#define TEXT_BUFFERING obs_module_text("Buffering") +#define TEXT_BUFFERING_AUTO obs_module_text("Buffering.AutoDetect") +#define TEXT_BUFFERING_ON obs_module_text("Buffering.Enable") +#define TEXT_BUFFERING_OFF obs_module_text("Buffering.Disable") +#define TEXT_FLIP_IMAGE obs_module_text("FlipVertically") +#define TEXT_AUTOROTATION obs_module_text("Autorotation") +#define TEXT_ACTIVATE obs_module_text("Activate") +#define TEXT_DEACTIVATE obs_module_text("Deactivate") +#define TEXT_COLOR_SPACE obs_module_text("ColorSpace") +#define TEXT_COLOR_DEFAULT obs_module_text("ColorSpace.Default") +#define TEXT_COLOR_709 obs_module_text("ColorSpace.709") +#define TEXT_COLOR_601 obs_module_text("ColorSpace.601") +#define TEXT_COLOR_2100PQ obs_module_text("ColorSpace.2100PQ") +#define TEXT_COLOR_2100HLG obs_module_text("ColorSpace.2100HLG") +#define TEXT_COLOR_RANGE obs_module_text("ColorRange") +#define TEXT_RANGE_DEFAULT obs_module_text("ColorRange.Default") +#define TEXT_RANGE_PARTIAL obs_module_text("ColorRange.Partial") +#define TEXT_RANGE_FULL obs_module_text("ColorRange.Full") +#define TEXT_DWNS obs_module_text("DeactivateWhenNotShowing") + +enum ResType { + ResTypePreferred, + ResTypeCustom, +}; + +enum class BufferingType : int64_t { + Auto, + On, + Off, +}; + +class CriticalSection { + CRITICAL_SECTION mutex; + +public: + inline CriticalSection() { InitializeCriticalSection(&mutex); } + inline ~CriticalSection() { DeleteCriticalSection(&mutex); } + + inline operator CRITICAL_SECTION *() { return &mutex; } +}; + +class CriticalScope { + CriticalSection &mutex; + + CriticalScope() = delete; + CriticalScope &operator=(CriticalScope &cs) = delete; + +public: + inline CriticalScope(CriticalSection &mutex_) : mutex(mutex_) + { + EnterCriticalSection(mutex); + } + + inline ~CriticalScope() { LeaveCriticalSection(mutex); } +}; + +enum class Action { + None, + Activate, + ActivateBlock, + Deactivate, + Shutdown, + SaveSettings, + RestoreSettings +}; + +struct MediaFoundationVideoInfo { + int minCX; + int minCY; + int maxCX; + int maxCY; + int granularityCX; + int granularityCY; + long long minInterval; + long long maxInterval; +}; + +struct MediaFoundationDeviceId { + std::wstring name; + std::wstring path; +}; + +struct MediaFoundationVideoDevice : MediaFoundationDeviceId { + std::vector caps; +}; + +struct MediaFoundationVideoConfig { + + std::wstring name; + std::wstring path; + + /* Desired width/height of video. */ + int cx = 0; + int cyAbs = 0; + + /* Whether or not cy was negative. */ + bool cyFlip = false; + + /* Desired frame interval (in 100-nanosecond units) */ + long long frameInterval = 0; +}; + +struct MediaFoundationVideoDeviceProperty { + long property; + long flags; + long val; + long min; + long max; + long step; + long def; +}; + +static inline void encodeDstr(struct dstr *str) +{ + dstr_replace(str, "#", "#22"); + dstr_replace(str, ":", "#3A"); +} + +static inline void decodeDstr(struct dstr *str) +{ + dstr_replace(str, "#3A", ":"); + dstr_replace(str, "#22", "#"); +} + +static inline void EncodeDeviceId(struct dstr *encodedStr, + const wchar_t *nameStr, + const wchar_t *pathStr) +{ + DStr name; + DStr path; + + dstr_from_wcs(name, nameStr); + dstr_from_wcs(path, pathStr); + + encodeDstr(name); + encodeDstr(path); + + dstr_copy_dstr(encodedStr, name); + dstr_cat(encodedStr, ":"); + dstr_cat_dstr(encodedStr, path); +} + +static inline bool DecodeDeviceDStr(DStr &name, DStr &path, + const char *deviceId) +{ + const char *pathStr; + + if (!deviceId || !*deviceId) + return false; + + pathStr = strchr(deviceId, ':'); + if (!pathStr) + return false; + + dstr_copy(path, pathStr + 1); + dstr_copy(name, deviceId); + + size_t len = pathStr - deviceId; + name->array[len] = 0; + name->len = len; + + decodeDstr(name); + decodeDstr(path); + + return true; +} + +static inline bool DecodeDeviceId(MediaFoundationDeviceId &out, + const char *deviceId) +{ + DStr name, path; + + if (!DecodeDeviceDStr(name, path, deviceId)) + return false; + + BPtr wname = dstr_to_wcs(name); + out.name = wname; + + if (!dstr_is_empty(path)) { + BPtr wpath = dstr_to_wcs(path); + out.path = wpath; + } + + return true; +} + +static DWORD CALLBACK MediaFoundationSourceThread(LPVOID ptr); + +// ============================libmfcapture=============================== + +class DeviceEnumerator { + + std::vector _devices = {}; + +public: + DeviceEnumerator() {} + ~DeviceEnumerator() {} + + void static __stdcall EnumerateCameraCallback(const wchar_t *Name, + const wchar_t *DevId, + void *pUserData) + { + DeviceEnumerator *pThis = (DeviceEnumerator *)pUserData; + + printf("Name: %ws, DevId: %ws\n", Name, DevId); + MediaFoundationVideoDevice vd; + vd.name = Name; + vd.path = DevId; + + CAPTURE_DEVICE_HANDLE h = MF_Create(DevId); + if (h) { + HRESULT hr = MF_EnumerateStreamCapabilities( + h, EnumerateStreamCapabilitiesCallback, &vd); + if (SUCCEEDED(hr)) { + pThis->_devices.push_back(vd); + } + MF_Destroy(h); + } + } + + void static __stdcall EnumerateStreamCapabilitiesCallback( + UINT32 Width, UINT32 Height, UINT32 FpsN, UINT32 FpsD, + void *pUserData) + { + MediaFoundationVideoDevice *vd = + (MediaFoundationVideoDevice *)pUserData; + MediaFoundationVideoInfo vInfo; + vInfo.minCX = Width; + vInfo.maxCX = Width; + vInfo.minCY = Height; + vInfo.maxCY = Height; + vInfo.granularityCX = 1; + vInfo.granularityCY = 1; + vInfo.minInterval = 10000000ll * FpsD / FpsN; + vInfo.maxInterval = vInfo.minInterval; + vd->caps.push_back(vInfo); + } + + HRESULT Enumerate(std::vector &devices) + { + devices.clear(); + _devices.clear(); + HRESULT hr = MF_EnumerateCameras(EnumerateCameraCallback, this); + if (SUCCEEDED(hr)) { + devices = _devices; + } + return hr; + } +}; + +// ============================libmfcapture=============================== + +struct MediaFoundationSourceInput { + obs_source_t *source; + CAPTURE_DEVICE_HANDLE _mfcaptureDevice = nullptr; + HRESULT _mfcapturedialog = E_FAIL; + + bool _deactivateWhenNotShowing = false; + bool _flip = false; + bool _active = false; + bool _autorotation = true; + bool _firstframe = true; + + MediaFoundationVideoConfig _videoConfig; + + obs_source_frame2 _frame; + + WinHandle _semaphore; + WinHandle _activated_event; + WinHandle _saved_event; + WinHandle _thread; + CriticalSection _mutex; + vector _actions; + + inline void QueueAction(Action action) + { + CriticalScope scope(_mutex); + _actions.push_back(action); + ReleaseSemaphore(_semaphore, 1, nullptr); + } + + inline void QueueActivate(obs_data_t *settings) + { + bool block = + obs_data_get_bool(settings, "synchronous_activate"); + QueueAction(block ? Action::ActivateBlock : Action::Activate); + if (block) { + obs_data_erase(settings, "synchronous_activate"); + WaitForSingleObject(_activated_event, INFINITE); + } + } + + inline MediaFoundationSourceInput(obs_source_t *source_, + obs_data_t *settings) + : source(source_) + { + memset(&_frame, 0, sizeof(_frame)); + + _semaphore = CreateSemaphore(nullptr, 0, 0x7FFFFFFF, nullptr); + if (!_semaphore) + throw "Failed to create semaphore"; + + _activated_event = CreateEvent(nullptr, false, false, nullptr); + if (!_activated_event) + throw "Failed to create activated_event"; + + _saved_event = CreateEvent(nullptr, false, false, nullptr); + if (!_saved_event) + throw "Failed to create saved_event"; + + _thread = CreateThread(nullptr, 0, MediaFoundationSourceThread, + this, 0, nullptr); + if (!_thread) + throw "Failed to create thread"; + + _deactivateWhenNotShowing = + obs_data_get_bool(settings, DEACTIVATE_WNS); + + if (obs_data_get_bool(settings, "active")) { + bool showing = obs_source_showing(source); + if (!_deactivateWhenNotShowing || showing) + QueueActivate(settings); + + _active = true; + } + } + + inline ~MediaFoundationSourceInput() + { + { + CriticalScope scope(_mutex); + _actions.resize(1); + _actions[0] = Action::Shutdown; + } + + ReleaseSemaphore(_semaphore, 1, nullptr); + + WaitForSingleObject(_thread, INFINITE); + } + + void OnReactivate(); + bool UpdateVideoConfig(obs_data_t *settings); + bool UpdateVideoProperties(obs_data_t *settings); + void SaveVideoProperties(); + void SetActive(bool active); + inline enum video_colorspace GetColorSpace(obs_data_t *settings) const; + inline enum video_range_type GetColorRange(obs_data_t *settings) const; + inline bool Activate(obs_data_t *settings); + inline void Deactivate(); + + inline void SetupBuffering(obs_data_t *settings); + + void MediaFoundationSourceLoop(); + + void static __stdcall _OnVideoData(void *pData, int Size, + long long llTimestamp, + void *pUserData); + void OnVideoData(void *pData, int Size, long long llTimestamp); +}; + +static DWORD CALLBACK MediaFoundationSourceThread(LPVOID ptr) +{ + MediaFoundationSourceInput *input = (MediaFoundationSourceInput *)ptr; + os_set_thread_name("win-MediaFoundation: MediaFoundationSourceThread"); + if (SUCCEEDED(CoInitialize(nullptr))) { + if (SUCCEEDED(MFStartup(MF_VERSION))) { + input->MediaFoundationSourceLoop(); + MFShutdown(); + } + CoUninitialize(); + } + return 0; +} + +static inline void ProcessMessages() +{ + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void MediaFoundationSourceInput::MediaFoundationSourceLoop() +{ + while (true) { + DWORD ret = MsgWaitForMultipleObjects(1, &_semaphore, false, + INFINITE, QS_ALLINPUT); + if (ret == (WAIT_OBJECT_0 + 1)) { + ProcessMessages(); + continue; + } else if (ret != WAIT_OBJECT_0) { + break; + } + + Action action = Action::None; + { + CriticalScope scope(_mutex); + if (_actions.size()) { + action = _actions.front(); + _actions.erase(_actions.begin()); + } + } + + switch (action) { + case Action::Activate: + case Action::ActivateBlock: { + bool block = action == Action::ActivateBlock; + + obs_data_t *settings; + settings = obs_source_get_settings(source); + if (!Activate(settings)) { + obs_source_output_video2(source, nullptr); + } + if (block) + SetEvent(_activated_event); + obs_data_release(settings); + break; + } + + case Action::Deactivate: + Deactivate(); + break; + + case Action::Shutdown: + if (_mfcaptureDevice) { + MF_Stop(_mfcaptureDevice); + MF_Destroy(_mfcaptureDevice); + _mfcaptureDevice = nullptr; + } + return; + + case Action::SaveSettings: + SaveVideoProperties(); + break; + + case Action::RestoreSettings: + if (_mfcaptureDevice) { + MF_RestoreDefaultSettings(_mfcaptureDevice); + } + break; + case Action::None:; + } + } +} + +#define FPS_HIGHEST 0LL +#define FPS_MATCHING -1LL + +template +static bool between(T &&lower, U &&value, V &&upper) +{ + return value >= lower && value <= upper; +} + +static bool ResolutionAvailable(const MediaFoundationVideoInfo &cap, int cx, + int cy) +{ + return between(cap.minCX, cx, cap.maxCX) && + between(cap.minCY, cy, cap.maxCY); +} + +#define DEVICE_INTERVAL_DIFF_LIMIT 20 + +static bool FrameRateAvailable(const MediaFoundationVideoInfo &cap, + long long interval) +{ + return interval == FPS_HIGHEST || interval == FPS_MATCHING || + between(cap.minInterval - DEVICE_INTERVAL_DIFF_LIMIT, interval, + cap.maxInterval + DEVICE_INTERVAL_DIFF_LIMIT); +} + +static long long FrameRateInterval(const MediaFoundationVideoInfo &cap, + long long desired_interval) +{ + return desired_interval < cap.minInterval + ? cap.minInterval + : min(desired_interval, cap.maxInterval); +} + +void MediaFoundationSourceInput::OnReactivate() +{ + SetActive(true); +} + +void MediaFoundationSourceInput::OnVideoData(void *pData, int Size, + long long llTimestamp) +{ + if (_firstframe) { + QueueAction(Action::RestoreSettings); + _firstframe = false; + return; + } + + const int cx = _videoConfig.cx; + const int cyAbs = _videoConfig.cyAbs; + + _frame.timestamp = (uint64_t)llTimestamp * 100; + _frame.width = _videoConfig.cx; + _frame.height = cyAbs; + _frame.format = VIDEO_FORMAT_BGRA; + _frame.flip = _flip; + _frame.flags = OBS_SOURCE_FRAME_LINEAR_ALPHA; + + _frame.data[0] = (unsigned char *)pData; + _frame.linesize[0] = cx * 4; + + obs_source_output_video2(source, &_frame); +} + +struct PropertiesData { + MediaFoundationSourceInput *input; + vector devices; + + bool GetDevice(MediaFoundationVideoDevice &device, + const char *encoded_id) const + { + MediaFoundationDeviceId deviceId; + DecodeDeviceId(deviceId, encoded_id); + + for (const MediaFoundationVideoDevice &curDevice : devices) { + if (deviceId.name == curDevice.name && + deviceId.path == curDevice.path) { + device = curDevice; + return true; + } + } + + return false; + } +}; + +static inline bool ConvertRes(int &cx, int &cy, const char *res) +{ + return sscanf(res, "%dx%d", &cx, &cy) == 2; +} + +static inline bool ResolutionValid(const string &res, int &cx, int &cy) +{ + if (!res.size()) + return false; + + return ConvertRes(cx, cy, res.c_str()); +} + +static inline bool CapsMatch(const MediaFoundationVideoInfo &) +{ + return true; +} + +template +static bool CapsMatch(const MediaFoundationVideoDevice &dev, F... fs); + +template +static inline bool CapsMatch(const MediaFoundationVideoInfo &info, F &&f, + Fs... fs) +{ + return f(info) && CapsMatch(info, fs...); +} + +template +static bool CapsMatch(const MediaFoundationVideoDevice &dev, F... fs) +{ + // no early exit, trigger all side effects. + bool match = false; + for (const MediaFoundationVideoInfo &info : dev.caps) + if (CapsMatch(info, fs...)) + match = true; + return match; +} + +static inline bool +MatcherClosestFrameRateSelector(long long interval, long long &best_match, + const MediaFoundationVideoInfo &info) +{ + long long current = FrameRateInterval(info, interval); + if (llabs(interval - best_match) > llabs(interval - current)) + best_match = current; + return true; +} + +#define ResolutionMatcher(cx, cy) \ + [cx, cy](const MediaFoundationVideoInfo &info) -> bool { \ + return ResolutionAvailable(info, cx, cy); \ + } +#define FrameRateMatcher(interval) \ + [interval](const MediaFoundationVideoInfo &info) -> bool { \ + return FrameRateAvailable(info, interval); \ + } +#define ClosestFrameRateSelector(interval, best_match) \ + [interval, \ + &best_match](const MediaFoundationVideoInfo &info) mutable -> bool { \ + return MatcherClosestFrameRateSelector(interval, best_match, \ + info); \ + } + +static bool ResolutionAvailable(const MediaFoundationVideoDevice &dev, int cx, + int cy) +{ + return CapsMatch(dev, ResolutionMatcher(cx, cy)); +} + +static bool DetermineResolution(int &cx, int &cy, obs_data_t *settings, + MediaFoundationVideoDevice &dev) +{ + const char *res = obs_data_get_autoselect_string(settings, RESOLUTION); + if (obs_data_has_autoselect_value(settings, RESOLUTION) && + ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy)) + return true; + + res = obs_data_get_string(settings, RESOLUTION); + if (ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy)) + return true; + + res = obs_data_get_string(settings, LAST_RESOLUTION); + if (ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy)) + return true; + + return false; +} + +static long long GetOBSFPS(); + +static inline bool IsDecoupled(const MediaFoundationVideoConfig &config) +{ + return wstrstri(config.name.c_str(), L"GV-USB2") != NULL; +} + +inline void MediaFoundationSourceInput::SetupBuffering(obs_data_t *settings) +{ + BufferingType bufType; + bool useBuffering; + + bufType = (BufferingType)obs_data_get_int(settings, BUFFERING_VAL); + + if (bufType == BufferingType::Auto) + useBuffering = false; + else + useBuffering = bufType == BufferingType::On; + + obs_source_set_async_unbuffered(source, !useBuffering); + obs_source_set_async_decoupled(source, IsDecoupled(_videoConfig)); +} + +void MediaFoundationSourceInput::_OnVideoData(void *pData, int Size, + long long llTimestamp, + void *pUserData) +{ + MediaFoundationSourceInput *pThis = + (MediaFoundationSourceInput *)pUserData; + + if (pThis) { + pThis->OnVideoData(pData, Size, llTimestamp); + } +} + +bool MediaFoundationSourceInput::UpdateVideoConfig(obs_data_t *settings) +{ + string video_device_id = obs_data_get_string(settings, VIDEO_DEVICE_ID); + _deactivateWhenNotShowing = obs_data_get_bool(settings, DEACTIVATE_WNS); + _flip = obs_data_get_bool(settings, FLIP_IMAGE); + _autorotation = obs_data_get_bool(settings, AUTOROTATION); + + MediaFoundationDeviceId id; + if (!DecodeDeviceId(id, video_device_id.c_str())) { + blog(LOG_WARNING, "%s: DecodeDeviceId failed", + obs_source_get_name(source)); + return false; + } + + PropertiesData data; + DeviceEnumerator mfenum; + mfenum.Enumerate(data.devices); + + MediaFoundationVideoDevice dev; + if (!data.GetDevice(dev, video_device_id.c_str())) { + blog(LOG_WARNING, "%s: data.GetDevice failed", + obs_source_get_name(source)); + return false; + } + + int resType = (int)obs_data_get_int(settings, RES_TYPE); + int cx = 0, cy = 0; + long long interval = 0; + + if (resType == ResTypeCustom) { + bool has_autosel_val; + string resolution = obs_data_get_string(settings, RESOLUTION); + if (!ResolutionValid(resolution, cx, cy)) { + blog(LOG_WARNING, "%s: ResolutionValid failed", + obs_source_get_name(source)); + return false; + } + + has_autosel_val = + obs_data_has_autoselect_value(settings, FRAME_INTERVAL); + interval = has_autosel_val + ? obs_data_get_autoselect_int(settings, + FRAME_INTERVAL) + : obs_data_get_int(settings, FRAME_INTERVAL); + + if (interval == FPS_MATCHING) + interval = GetOBSFPS(); + + long long best_interval = numeric_limits::max(); + CapsMatch(dev, ResolutionMatcher(cx, cy), + ClosestFrameRateSelector(interval, best_interval), + FrameRateMatcher(interval)); + interval = best_interval; + } + + _videoConfig.name = id.name; + _videoConfig.path = id.path; + _videoConfig.cx = cx; + _videoConfig.cyAbs = abs(cy); + _videoConfig.cyFlip = cy < 0; + _videoConfig.frameInterval = interval; + + _mfcaptureDevice = MF_Create(dev.path.c_str()); + if (_mfcaptureDevice) { + HRESULT hr = MF_Prepare(_mfcaptureDevice); + UINT32 w, h, fpsd, fpsn; + if (SUCCEEDED(hr)) { + if (resType != ResTypeCustom) { + hr = MF_GetOutputResolution( + _mfcaptureDevice, &w, &h, &fpsn, &fpsd); + if (SUCCEEDED(hr)) { + interval = 10000000ll * fpsd / fpsn; + } + } else { + w = _videoConfig.cx; + h = _videoConfig.cyAbs; + interval = _videoConfig.frameInterval; + } + } + if (SUCCEEDED(hr)) { + hr = MF_SetOutputResolution( + _mfcaptureDevice, w, h, interval, + MF_COLOR_FORMAT::MF_COLOR_FORMAT_ARGB); + _videoConfig.cx = w; + _videoConfig.cyAbs = h; + _videoConfig.frameInterval = interval; + } + if (SUCCEEDED(hr)) { + _firstframe = true; + hr = MF_Start(_mfcaptureDevice, _OnVideoData, this); + } + + QueueAction(Action::NpuControl); + } + + SetupBuffering(settings); + + return true; +} + +bool MediaFoundationSourceInput::UpdateVideoProperties(obs_data_t *settings) +{ + OBSDataArrayAutoRelease cca = + obs_data_get_array(settings, "CameraControl"); + + if (cca) { + std::vector properties; + const auto count = obs_data_array_count(cca); + + for (size_t i = 0; i < count; i++) { + OBSDataAutoRelease item = obs_data_array_item(cca, i); + if (!item) + continue; + + MediaFoundationVideoDeviceProperty prop{}; + prop.property = + (long)obs_data_get_int(item, "property"); + prop.flags = (long)obs_data_get_int(item, "flags"); + prop.val = (long)obs_data_get_int(item, "val"); + properties.push_back(prop); + } + + if (!properties.empty()) { + } + } + + OBSDataArrayAutoRelease vpaa = + obs_data_get_array(settings, "VideoProcAmp"); + + if (vpaa) { + std::vector properties; + const auto count = obs_data_array_count(vpaa); + + for (size_t i = 0; i < count; i++) { + OBSDataAutoRelease item = obs_data_array_item(vpaa, i); + if (!item) + continue; + + MediaFoundationVideoDeviceProperty prop{}; + prop.property = + (long)obs_data_get_int(item, "property"); + prop.flags = (long)obs_data_get_int(item, "flags"); + prop.val = (long)obs_data_get_int(item, "val"); + properties.push_back(prop); + } + + if (!properties.empty()) { + } + } + + return true; +} + +void MediaFoundationSourceInput::SaveVideoProperties() +{ + OBSDataAutoRelease settings = obs_source_get_settings(source); + if (!settings) { + SetEvent(_saved_event); + return; + } + + std::vector properties; + OBSDataArrayAutoRelease ccp = obs_data_array_create(); + + obs_data_set_array(settings, "CameraControl", ccp); + properties.clear(); + + OBSDataArrayAutoRelease vpap = obs_data_array_create(); + + obs_data_set_array(settings, "VideoProcAmp", vpap); + + SetEvent(_saved_event); +} + +void MediaFoundationSourceInput::SetActive(bool active_) +{ + obs_data_t *settings = obs_source_get_settings(source); + QueueAction(active_ ? Action::Activate : Action::Deactivate); + obs_data_set_bool(settings, "active", active_); + _active = active_; + obs_data_release(settings); +} + +inline enum video_colorspace +MediaFoundationSourceInput::GetColorSpace(obs_data_t *settings) const +{ + const char *space = obs_data_get_string(settings, COLOR_SPACE); + + if (astrcmpi(space, "709") == 0) + return VIDEO_CS_709; + + if (astrcmpi(space, "601") == 0) + return VIDEO_CS_601; + + if (astrcmpi(space, "2100PQ") == 0) + return VIDEO_CS_2100_PQ; + + if (astrcmpi(space, "2100HLG") == 0) + return VIDEO_CS_2100_HLG; + + return VIDEO_CS_DEFAULT; +} + +inline enum video_range_type +MediaFoundationSourceInput::GetColorRange(obs_data_t *settings) const +{ + const char *range = obs_data_get_string(settings, COLOR_RANGE); + + if (astrcmpi(range, "full") == 0) + return VIDEO_RANGE_FULL; + if (astrcmpi(range, "partial") == 0) + return VIDEO_RANGE_PARTIAL; + return VIDEO_RANGE_DEFAULT; +} + +inline bool MediaFoundationSourceInput::Activate(obs_data_t *settings) +{ + + if (_mfcaptureDevice) { + MF_Stop(_mfcaptureDevice); + MF_Destroy(_mfcaptureDevice); + _mfcaptureDevice = nullptr; + } + + if (!UpdateVideoConfig(settings)) { + blog(LOG_WARNING, "%s: Video configuration failed", + obs_source_get_name(source)); + return false; + } + + if (!UpdateVideoProperties(settings)) { + blog(LOG_WARNING, "%s: Setting video device properties failed", + obs_source_get_name(source)); + } + + const enum video_colorspace cs = GetColorSpace(settings); + const enum video_range_type range = GetColorRange(settings); + + enum video_trc trc = VIDEO_TRC_DEFAULT; + switch (cs) { + case VIDEO_CS_DEFAULT: + case VIDEO_CS_601: + case VIDEO_CS_709: + case VIDEO_CS_SRGB: + trc = VIDEO_TRC_SRGB; + break; + case VIDEO_CS_2100_PQ: + trc = VIDEO_TRC_PQ; + break; + case VIDEO_CS_2100_HLG: + trc = VIDEO_TRC_HLG; + } + + _frame.range = range; + _frame.trc = trc; + + bool success = video_format_get_parameters_for_format( + cs, range, VIDEO_FORMAT_BGRA, _frame.color_matrix, + _frame.color_range_min, _frame.color_range_max); + if (!success) { + blog(LOG_ERROR, + "Failed to get video format parameters for " + "video format %u", + cs); + } + + return true; +} + +inline void MediaFoundationSourceInput::Deactivate() +{ + if (_mfcaptureDevice) { + MF_Stop(_mfcaptureDevice); + MF_Destroy(_mfcaptureDevice); + _mfcaptureDevice = nullptr; + } + obs_source_output_video2(source, nullptr); +} + +/* ------------------------------------------------------------------------- */ + +static const char *GetMediaFoundationSourceInputName(void *) +{ + return TEXT_INPUT_NAME; +} + +static void proc_activate(void *data, calldata_t *cd) +{ + bool activate = calldata_bool(cd, "active"); + MediaFoundationSourceInput *input = + reinterpret_cast(data); + input->SetActive(activate); +} + +static void *CreateMediaFoundationSourceInput(obs_data_t *settings, + obs_source_t *source) +{ + MediaFoundationSourceInput *input = nullptr; + + try { + input = new MediaFoundationSourceInput(source, settings); + proc_handler_t *ph = obs_source_get_proc_handler(source); + proc_handler_add(ph, "void activate(bool active)", + proc_activate, input); + } catch (const char *error) { + blog(LOG_ERROR, "Could not create device '%s': %s", + obs_source_get_name(source), error); + } + + return input; +} + +static void DestroyMediaFoundationSourceInput(void *data) +{ + delete reinterpret_cast(data); +} + +static void UpdateMediaFoundationSourceInput(void *data, obs_data_t *settings) +{ + MediaFoundationSourceInput *input = + reinterpret_cast(data); + if (input->_active) + input->QueueActivate(settings); +} + +static void SaveMediaFoundationSourceInput(void *data, obs_data_t *settings) +{ + MediaFoundationSourceInput *input = + reinterpret_cast(data); + if (!input->_active) + return; + + input->QueueAction(Action::SaveSettings); + WaitForSingleObject(input->_saved_event, INFINITE); +} + +static void GetMediaFoundationSourceDefaults(obs_data_t *settings) +{ + obs_data_set_default_int(settings, FRAME_INTERVAL, FPS_MATCHING); + obs_data_set_default_int(settings, RES_TYPE, ResTypePreferred); + obs_data_set_default_bool(settings, "active", true); + obs_data_set_default_string(settings, COLOR_SPACE, "default"); + obs_data_set_default_string(settings, COLOR_RANGE, "default"); + obs_data_set_default_bool(settings, AUTOROTATION, true); +} + +struct Resolution { + int cx, cy; + + inline Resolution(int cx, int cy) : cx(cx), cy(cy) {} +}; + +static void InsertResolution(vector &resolutions, int cx, int cy) +{ + int bestCY = 0; + size_t idx = 0; + + for (; idx < resolutions.size(); idx++) { + const Resolution &res = resolutions[idx]; + if (res.cx > cx) + break; + + if (res.cx == cx) { + if (res.cy == cy) + return; + + if (!bestCY) + bestCY = res.cy; + else if (res.cy > bestCY) + break; + } + } + + resolutions.insert(resolutions.begin() + idx, Resolution(cx, cy)); +} + +static inline void AddCap(vector &resolutions, + const MediaFoundationVideoInfo &cap) +{ + InsertResolution(resolutions, cap.minCX, cap.minCY); + InsertResolution(resolutions, cap.maxCX, cap.maxCY); +} + +#define MAKE_MEDIAFOUNDATION_FPS(fps) (10000000LL / (fps)) +#define MAKE_MEDIAFOUNDATION_FRACTIONAL_FPS(den, num) \ + ((num) * 10000000LL / (den)) + +static long long GetOBSFPS() +{ + obs_video_info ovi; + if (!obs_get_video_info(&ovi)) + return 0; + + return MAKE_MEDIAFOUNDATION_FRACTIONAL_FPS(ovi.fps_num, ovi.fps_den); +} + +struct FPSFormat { + const char *text; + long long interval; +}; + +static const FPSFormat validFPSFormats[] = { + {"60", MAKE_MEDIAFOUNDATION_FPS(60)}, + {"59.94 NTSC", MAKE_MEDIAFOUNDATION_FRACTIONAL_FPS(60000, 1001)}, + {"50", MAKE_MEDIAFOUNDATION_FPS(50)}, + {"48 film", MAKE_MEDIAFOUNDATION_FRACTIONAL_FPS(48000, 1001)}, + {"40", MAKE_MEDIAFOUNDATION_FPS(40)}, + {"30", MAKE_MEDIAFOUNDATION_FPS(30)}, + {"29.97 NTSC", MAKE_MEDIAFOUNDATION_FRACTIONAL_FPS(30000, 1001)}, + {"25", MAKE_MEDIAFOUNDATION_FPS(25)}, + {"24 film", MAKE_MEDIAFOUNDATION_FRACTIONAL_FPS(24000, 1001)}, + {"20", MAKE_MEDIAFOUNDATION_FPS(20)}, + {"15", MAKE_MEDIAFOUNDATION_FPS(15)}, + {"10", MAKE_MEDIAFOUNDATION_FPS(10)}, + {"5", MAKE_MEDIAFOUNDATION_FPS(5)}, + {"4", MAKE_MEDIAFOUNDATION_FPS(4)}, + {"3", MAKE_MEDIAFOUNDATION_FPS(3)}, + {"2", MAKE_MEDIAFOUNDATION_FPS(2)}, + {"1", MAKE_MEDIAFOUNDATION_FPS(1)}, +}; + +static bool DeviceIntervalChanged(obs_properties_t *props, obs_property_t *p, + obs_data_t *settings); + +static bool TryResolution(const MediaFoundationVideoDevice &dev, + const string &res) +{ + int cx, cy; + if (!ConvertRes(cx, cy, res.c_str())) + return false; + + return ResolutionAvailable(dev, cx, cy); +} + +static bool SetResolution(obs_properties_t *props, obs_data_t *settings, + const string &res, bool autoselect = false) +{ + if (autoselect) + obs_data_set_autoselect_string(settings, RESOLUTION, + res.c_str()); + else + obs_data_unset_autoselect_value(settings, RESOLUTION); + + DeviceIntervalChanged(props, obs_properties_get(props, FRAME_INTERVAL), + settings); + + if (!autoselect) + obs_data_set_string(settings, LAST_RESOLUTION, res.c_str()); + return true; +} + +static bool DeviceResolutionChanged(obs_properties_t *props, obs_property_t *p, + obs_data_t *settings) +{ + UNUSED_PARAMETER(p); + + PropertiesData *data = + (PropertiesData *)obs_properties_get_param(props); + const char *id; + MediaFoundationVideoDevice device; + + id = obs_data_get_string(settings, VIDEO_DEVICE_ID); + string res = obs_data_get_string(settings, RESOLUTION); + string last_res = obs_data_get_string(settings, LAST_RESOLUTION); + + if (!data->GetDevice(device, id)) + return false; + + if (TryResolution(device, res)) + return SetResolution(props, settings, res); + + if (TryResolution(device, last_res)) + return SetResolution(props, settings, last_res, true); + + return false; +} + +static bool ResTypeChanged(obs_properties_t *props, obs_property_t *p, + obs_data_t *settings); + +static size_t AddDevice(obs_property_t *device_list, const string &id) +{ + DStr name, path; + if (!DecodeDeviceDStr(name, path, id.c_str())) + return numeric_limits::max(); + + return obs_property_list_add_string(device_list, name, id.c_str()); +} + +static bool UpdateDeviceList(obs_property_t *list, const string &id) +{ + size_t size = obs_property_list_item_count(list); + bool found = false; + bool disabled_unknown_found = false; + + for (size_t i = 0; i < size; i++) { + if (obs_property_list_item_string(list, i) == id) { + found = true; + continue; + } + if (obs_property_list_item_disabled(list, i)) + disabled_unknown_found = true; + } + + if (!found && !disabled_unknown_found) { + size_t idx = AddDevice(list, id); + obs_property_list_item_disable(list, idx, true); + return true; + } + + if (found && !disabled_unknown_found) + return false; + + for (size_t i = 0; i < size;) { + if (obs_property_list_item_disabled(list, i)) { + obs_property_list_item_remove(list, i); + continue; + } + i += 1; + } + + return true; +} + +static bool DeviceSelectionChanged(obs_properties_t *props, obs_property_t *p, + obs_data_t *settings) +{ + PropertiesData *data = + (PropertiesData *)obs_properties_get_param(props); + MediaFoundationVideoDevice device; + + string id = obs_data_get_string(settings, VIDEO_DEVICE_ID); + string old_id = obs_data_get_string(settings, LAST_VIDEO_DEV_ID); + + bool device_list_updated = UpdateDeviceList(p, id); + + if (!data->GetDevice(device, id.c_str())) + return !device_list_updated; + + vector resolutions; + for (const MediaFoundationVideoInfo &cap : device.caps) + AddCap(resolutions, cap); + + p = obs_properties_get(props, RESOLUTION); + obs_property_list_clear(p); + + for (size_t idx = resolutions.size(); idx > 0; idx--) { + const Resolution &res = resolutions[idx - 1]; + + string strRes; + strRes += to_string(res.cx); + strRes += "x"; + strRes += to_string(res.cy); + + obs_property_list_add_string(p, strRes.c_str(), strRes.c_str()); + } + + /* only refresh properties if device legitimately changed */ + if (!id.size() || !old_id.size() || id != old_id) { + p = obs_properties_get(props, RES_TYPE); + ResTypeChanged(props, p, settings); + obs_data_set_string(settings, LAST_VIDEO_DEV_ID, id.c_str()); + } + + return true; +} + +static bool AddDevice(obs_property_t *device_list, + const MediaFoundationVideoDevice &device) +{ + DStr name, path, device_id; + + dstr_from_wcs(name, device.name.c_str()); + dstr_from_wcs(path, device.path.c_str()); + + encodeDstr(path); + + dstr_copy_dstr(device_id, name); + encodeDstr(device_id); + dstr_cat(device_id, ":"); + dstr_cat_dstr(device_id, path); + + obs_property_list_add_string(device_list, name, device_id); + + return true; +} + +static void PropertiesDataDestroy(void *data) +{ + delete reinterpret_cast(data); +} + +static bool ResTypeChanged(obs_properties_t *props, obs_property_t *p, + obs_data_t *settings) +{ + int val = (int)obs_data_get_int(settings, RES_TYPE); + bool enabled = (val != ResTypePreferred); + + p = obs_properties_get(props, RESOLUTION); + obs_property_set_enabled(p, enabled); + + p = obs_properties_get(props, FRAME_INTERVAL); + obs_property_set_enabled(p, enabled); + + if (val == ResTypeCustom) { + p = obs_properties_get(props, RESOLUTION); + DeviceResolutionChanged(props, p, settings); + } else { + obs_data_unset_autoselect_value(settings, FRAME_INTERVAL); + } + + return true; +} + +static DStr GetFPSName(long long interval) +{ + DStr name; + + if (interval == FPS_MATCHING) { + dstr_cat(name, TEXT_FPS_MATCHING); + return name; + } + + if (interval == FPS_HIGHEST) { + dstr_cat(name, TEXT_FPS_HIGHEST); + return name; + } + + for (const FPSFormat &format : validFPSFormats) { + if (format.interval != interval) + continue; + + dstr_cat(name, format.text); + return name; + } + + dstr_cat(name, to_string(10000000. / interval).c_str()); + return name; +} + +static void UpdateFPS(MediaFoundationVideoDevice &device, long long interval, + int cx, int cy, obs_properties_t *props) +{ + obs_property_t *list = obs_properties_get(props, FRAME_INTERVAL); + + obs_property_list_clear(list); + + obs_property_list_add_int(list, TEXT_FPS_MATCHING, FPS_MATCHING); + obs_property_list_add_int(list, TEXT_FPS_HIGHEST, FPS_HIGHEST); + + bool interval_added = interval == FPS_HIGHEST || + interval == FPS_MATCHING; + for (const FPSFormat &fps_format : validFPSFormats) { + long long format_interval = fps_format.interval; + + bool available = CapsMatch(device, ResolutionMatcher(cx, cy), + FrameRateMatcher(format_interval)); + + if (!available && interval != fps_format.interval) + continue; + + if (interval == fps_format.interval) + interval_added = true; + + size_t idx = obs_property_list_add_int(list, fps_format.text, + fps_format.interval); + obs_property_list_item_disable(list, idx, !available); + } + + if (interval_added) + return; + + size_t idx = + obs_property_list_add_int(list, GetFPSName(interval), interval); + obs_property_list_item_disable(list, idx, true); +} + +static bool UpdateFPS(long long interval, obs_property_t *list) +{ + size_t size = obs_property_list_item_count(list); + DStr name; + + for (size_t i = 0; i < size; i++) { + if (obs_property_list_item_int(list, i) != interval) + continue; + + obs_property_list_item_disable(list, i, true); + if (size == 1) + return false; + + dstr_cat(name, obs_property_list_item_name(list, i)); + break; + } + + obs_property_list_clear(list); + + if (!name->len) + name = GetFPSName(interval); + + obs_property_list_add_int(list, name, interval); + obs_property_list_item_disable(list, 0, true); + + return true; +} + +static bool DeviceIntervalChanged(obs_properties_t *props, obs_property_t *p, + obs_data_t *settings) +{ + long long val = obs_data_get_int(settings, FRAME_INTERVAL); + + PropertiesData *data = + (PropertiesData *)obs_properties_get_param(props); + const char *id = obs_data_get_string(settings, VIDEO_DEVICE_ID); + MediaFoundationVideoDevice device; + + if (!data->GetDevice(device, id)) + return UpdateFPS(val, p); + + int cx = 0, cy = 0; + if (!DetermineResolution(cx, cy, settings, device)) { + UpdateFPS(device, 0, 0, 0, props); + return true; + } + + int resType = (int)obs_data_get_int(settings, RES_TYPE); + if (resType != ResTypeCustom) + return true; + + if (val == FPS_MATCHING) + val = GetOBSFPS(); + + long long best_interval = numeric_limits::max(); + bool frameRateSupported = + CapsMatch(device, ResolutionMatcher(cx, cy), + ClosestFrameRateSelector(val, best_interval), + FrameRateMatcher(val)); + + if (!frameRateSupported && best_interval != val) { + long long listed_val = 0; + for (const FPSFormat &format : validFPSFormats) { + long long diff = llabs(format.interval - best_interval); + if (diff < DEVICE_INTERVAL_DIFF_LIMIT) { + listed_val = format.interval; + break; + } + } + + if (listed_val != val) { + obs_data_set_autoselect_int(settings, FRAME_INTERVAL, + listed_val); + val = listed_val; + } + + } else { + obs_data_unset_autoselect_value(settings, FRAME_INTERVAL); + } + + UpdateFPS(device, val, cx, cy, props); + + return true; +} + +static bool ActivateClicked(obs_properties_t *, obs_property_t *p, void *data) +{ + MediaFoundationSourceInput *input = + reinterpret_cast(data); + + if (input->_active) { + input->SetActive(false); + obs_property_set_description(p, TEXT_ACTIVATE); + } else { + input->SetActive(true); + obs_property_set_description(p, TEXT_DEACTIVATE); + } + + return true; +} + +static obs_properties_t *GetMediaFoundationSourceProperties(void *obj) +{ + MediaFoundationSourceInput *input = + reinterpret_cast(obj); + obs_properties_t *ppts = obs_properties_create(); + PropertiesData *data = new PropertiesData; + + data->input = input; + + obs_properties_set_param(ppts, data, PropertiesDataDestroy); + + obs_property_t *p = obs_properties_add_list(ppts, VIDEO_DEVICE_ID, + TEXT_DEVICE, + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + + obs_property_set_modified_callback(p, DeviceSelectionChanged); + + DeviceEnumerator mfenum; + mfenum.Enumerate(data->devices); + + for (const MediaFoundationVideoDevice &device : data->devices) + AddDevice(p, device); + + const char *activateText = TEXT_ACTIVATE; + if (input) { + if (input->_active) + activateText = TEXT_DEACTIVATE; + } + + obs_properties_add_button(ppts, "activate", activateText, + ActivateClicked); + + obs_properties_add_bool(ppts, DEACTIVATE_WNS, TEXT_DWNS); + + /* ------------------------------------- */ + /* video settings */ + + p = obs_properties_add_list(ppts, RES_TYPE, TEXT_RES_FPS_TYPE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + + obs_property_set_modified_callback(p, ResTypeChanged); + + obs_property_list_add_int(p, TEXT_PREFERRED_RES, ResTypePreferred); + obs_property_list_add_int(p, TEXT_CUSTOM_RES, ResTypeCustom); + + p = obs_properties_add_list(ppts, RESOLUTION, TEXT_RESOLUTION, + OBS_COMBO_TYPE_EDITABLE, + OBS_COMBO_FORMAT_STRING); + + obs_property_set_modified_callback(p, DeviceResolutionChanged); + + p = obs_properties_add_list(ppts, FRAME_INTERVAL, "FPS", + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + + obs_property_set_modified_callback(p, DeviceIntervalChanged); + + obs_property_set_long_description(p, + obs_module_text("Buffering.ToolTip")); + + obs_properties_add_bool(ppts, FLIP_IMAGE, TEXT_FLIP_IMAGE); + + return ppts; +} + +static void HideMediaFoundationSourceInput(void *data) +{ + MediaFoundationSourceInput *input = + reinterpret_cast(data); + + if (input->_deactivateWhenNotShowing && input->_active) + input->QueueAction(Action::Deactivate); +} + +static void ShowMediaFoundationSourceInput(void *data) +{ + MediaFoundationSourceInput *input = + reinterpret_cast(data); + + if (input->_deactivateWhenNotShowing && input->_active) + input->QueueAction(Action::Activate); +} + +void RegisterMediaFoundationSource() +{ + obs_source_info info = {}; + info.id = "MediaFoundationsource_input"; + info.type = OBS_SOURCE_TYPE_INPUT; + + info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_ASYNC | + OBS_SOURCE_DO_NOT_DUPLICATE; + + info.show = ShowMediaFoundationSourceInput; + info.hide = HideMediaFoundationSourceInput; + info.get_name = GetMediaFoundationSourceInputName; + info.create = CreateMediaFoundationSourceInput; + info.destroy = DestroyMediaFoundationSourceInput; + info.update = UpdateMediaFoundationSourceInput; + info.get_defaults = GetMediaFoundationSourceDefaults; + info.get_properties = GetMediaFoundationSourceProperties; + info.save = SaveMediaFoundationSourceInput; + info.icon_type = OBS_ICON_TYPE_CAMERA; + obs_register_source(&info); +}