From ffda6a51bbf1ccbeaea992fb77927900bfcea07f Mon Sep 17 00:00:00 2001 From: Andrey Nekrasov Date: Tue, 30 Mar 2021 18:27:42 +0300 Subject: [PATCH] [Tools] Add DShow webcam report tool (#10502) --- .github/actions/spell-check/expect.txt | 33 ++- .pipelines/build-tools.cmd | 1 + .../ci/templates/build-powertoys-steps.yml | 19 ++ .pipelines/pipeline.user.windows.yml | 2 + tools/WebcamReportTool/DirectShowUtils.cpp | 26 +++ tools/WebcamReportTool/DirectShowUtils.h | 22 ++ tools/WebcamReportTool/WebcamReportTool.sln | 25 ++ .../WebcamReportTool/WebcamReportTool.vcxproj | 69 ++++++ tools/WebcamReportTool/main.cpp | 219 ++++++++++++++++++ tools/WebcamReportTool/packages.config | 5 + 10 files changed, 414 insertions(+), 7 deletions(-) create mode 100644 tools/WebcamReportTool/DirectShowUtils.cpp create mode 100644 tools/WebcamReportTool/DirectShowUtils.h create mode 100644 tools/WebcamReportTool/WebcamReportTool.sln create mode 100644 tools/WebcamReportTool/WebcamReportTool.vcxproj create mode 100644 tools/WebcamReportTool/main.cpp create mode 100644 tools/WebcamReportTool/packages.config diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 8a4cb90321..64b329f0bb 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -49,6 +49,8 @@ amd Amicrosoft AMirror AModifier +AMPROPERTY +AMPROPSETID anges ansicolor antialiased @@ -175,7 +177,6 @@ BRIGHTGREEN Browsable bsd bstr -BText bti Btn BTNFACE @@ -204,10 +205,12 @@ CDATA CDBECF cdecl CDeclaration +CDEF cdpx cdpxwin CENTERALIGN cfg +cguid charconv charset chdir @@ -434,6 +437,8 @@ DEU Devagya devblogs devdocs +devenum +DEVMON df DFactory Dialpad @@ -486,6 +491,7 @@ drivedetectionwarning DRM dropdown dropref +dshow dst DSVG DText @@ -773,7 +779,6 @@ GNumber google GPTR grayscale -GText gui guiddef GUITHREADINFO @@ -880,6 +885,7 @@ IAppx IAsync IAuto IBackground +IBase IBeam IBind icase @@ -898,6 +904,7 @@ Iconset IContext ICONWARNING ICore +ICreate IData IDCANCEL IDD @@ -945,6 +952,7 @@ IInput IInspectable IItem IJson +IKs IList ILogon IMAGEHLP @@ -955,6 +963,7 @@ IMarkdown ime imeutil img +IMoniker IMonitor IMouse impl @@ -970,7 +979,7 @@ inheritdoc ini INITCOMMONCONTROLSEX INITDIALOG -INITGUID +initguid inl Inlines inorder @@ -992,7 +1001,6 @@ INSTALLLOCATION INSTALLLOGATTRIBUTES INSTALLLOGMODE INSTALLMESSAGE -installpath INSTALLPROPERTY INSTALLSTARTMENUSHORTCUT INSTALLSTATE @@ -1019,6 +1027,7 @@ ipc ipcmanager ipconfig IPersist +IPin IPlugin IPower ipp @@ -1027,6 +1036,7 @@ ipreviewhandlertranslateaccelerator ipreviewhandlervisualssetfont IPrincipal IProgram +IProperty IPublic IQuery IRead @@ -1241,6 +1251,7 @@ lzw mailto MAINICON Mainwindow +majortype makeappx MAKEINTRESOURCE MAKEINTRESOURCEW @@ -1267,6 +1278,8 @@ Mdb MDICHILD MDL mdpreviewhandler +MEDIASUBTYPE +MEDIATYPE Melman memcpy memset @@ -1293,6 +1306,7 @@ miniz MINMAXINFO Miracast mixin +MJPG mkdir MLogo MMI @@ -1568,6 +1582,7 @@ phwnd pici pid pidl +PINDIR pinfo pinvoke Pipelinhttps @@ -1781,13 +1796,12 @@ roslyn royvou rpc RRF -rshift RSHIFT +rshift Rsp rst Rstrtmgr RTB -RText rtf Rtl RTLREADING @@ -1999,6 +2013,7 @@ stringify STRINGIZE stringtable stringval +Strmiids strsafe strutil sttngs @@ -2244,6 +2259,7 @@ Versioning VFT vh vid +VIDEOINFOHEADER viewbox viewmodel virtualization @@ -2293,7 +2309,7 @@ wdp wdupenv weakme webapp -Webcam +webcam webclient webkit webp @@ -2346,6 +2362,7 @@ winspool winstore winui winxamlmanager +wistd withinrafael Withscript wix @@ -2435,6 +2452,8 @@ YLogo yml YOffset YStr +YUY +YUYV yy Zc ZEROINIT diff --git a/.pipelines/build-tools.cmd b/.pipelines/build-tools.cmd index 47da7a5c96..aa5f782932 100644 --- a/.pipelines/build-tools.cmd +++ b/.pipelines/build-tools.cmd @@ -7,4 +7,5 @@ set SolutionDir=%cd% popd SET IsPipeline=1 call msbuild ../tools/BugReportTool/BugReportTool.sln /p:Configuration=Release /p:Platform=x64 /p:CIBuild=true || exit /b 1 +call msbuild ../tools/WebcamReportTool/WebcamReportTool.sln /p:Configuration=Release /p:Platform=x64 /p:CIBuild=true || exit /b 1 diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml index fe149c755f..a053bf883d 100644 --- a/.pipelines/ci/templates/build-powertoys-steps.yml +++ b/.pipelines/ci/templates/build-powertoys-steps.yml @@ -55,6 +55,25 @@ steps: msbuildArgs: ${{ parameters.additionalBuildArguments }} maximumCpuCount: true +- task: NuGetCommand@2 + displayName: Restore NuGet packages for WebcamReportTool.sln + inputs: + command: restore + feedsToUse: config + configPath: NuGet.config + restoreSolution: tools\WebcamReportTool\WebcamReportTool.sln + restoreDirectory: '$(Build.SourcesDirectory)\tools\WebcamReportTool\packages' + +- task: VSBuild@1 + displayName: 'Build WebcamReportTool.sln' + inputs: + solution: '**\WebcamReportTool.sln' + vsVersion: 16.0 + platform: '$(BuildPlatform)' + configuration: '$(BuildConfiguration)' + msbuildArgs: ${{ parameters.additionalBuildArguments }} + maximumCpuCount: true + - task: NuGetCommand@2 displayName: Restore NuGet packages for PowerToysSetup.sln inputs: diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index d873aed929..5dcefed0eb 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -66,6 +66,7 @@ build: include: - 'action_runner.exe' - 'BugReportTool\BugReportTool.exe' + - 'WebcamReportTool\WebcamReportTool.exe' - 'modules\ColorPicker\ColorPicker.dll' - 'modules\ColorPicker\ColorPickerUI.dll' - 'modules\ColorPicker\ColorPickerUI.exe' @@ -168,6 +169,7 @@ build: to: 'Build_Output' include: - 'BugReportTool\BugReportTool.exe' + - 'WebcamReportTool\WebcamReportTool.exe' signing_options: sign_inline: true # This does signing a soon as this command completes - !!buildcommand diff --git a/tools/WebcamReportTool/DirectShowUtils.cpp b/tools/WebcamReportTool/DirectShowUtils.cpp new file mode 100644 index 0000000000..b7feffd71b --- /dev/null +++ b/tools/WebcamReportTool/DirectShowUtils.cpp @@ -0,0 +1,26 @@ +#include "DirectShowUtils.h" + +void FreeMediaTypeHelper(AM_MEDIA_TYPE& mt) +{ + if (mt.cbFormat != 0) + { + CoTaskMemFree(mt.pbFormat); + mt.cbFormat = 0; + mt.pbFormat = nullptr; + } + if (mt.pUnk != nullptr) + { + mt.pUnk->Release(); + mt.pUnk = nullptr; + } +} + +void DeleteMediaTypeHelper(AM_MEDIA_TYPE* pmt) +{ + if (!pmt) + { + return; + } + FreeMediaTypeHelper(*pmt); + CoTaskMemFree(const_cast(pmt)); +} diff --git a/tools/WebcamReportTool/DirectShowUtils.h b/tools/WebcamReportTool/DirectShowUtils.h new file mode 100644 index 0000000000..c230422238 --- /dev/null +++ b/tools/WebcamReportTool/DirectShowUtils.h @@ -0,0 +1,22 @@ +#pragma once +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include + +#include +#include + +std::ofstream& log(); + +#define TRACE log() << __FUNCTION__ << '\n'; +#define LOG(msg) log() << msg << '\n'; + +void DeleteMediaTypeHelper(AM_MEDIA_TYPE* pmt); + +using unique_media_type_ptr = + wistd::unique_ptr>; diff --git a/tools/WebcamReportTool/WebcamReportTool.sln b/tools/WebcamReportTool/WebcamReportTool.sln new file mode 100644 index 0000000000..88a5d0fcb1 --- /dev/null +++ b/tools/WebcamReportTool/WebcamReportTool.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31005.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WebcamReportTool", "WebcamReportTool.vcxproj", "{FC9599B4-68CD-E14B-71D7-456FDD8D0845}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FC9599B4-68CD-E14B-71D7-456FDD8D0845}.Debug|x64.ActiveCfg = Debug|x64 + {FC9599B4-68CD-E14B-71D7-456FDD8D0845}.Debug|x64.Build.0 = Debug|x64 + {FC9599B4-68CD-E14B-71D7-456FDD8D0845}.Release|x64.ActiveCfg = Release|x64 + {FC9599B4-68CD-E14B-71D7-456FDD8D0845}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {536C9E03-09C5-4CAA-AA5E-D62D34A5C09A} + EndGlobalSection +EndGlobal diff --git a/tools/WebcamReportTool/WebcamReportTool.vcxproj b/tools/WebcamReportTool/WebcamReportTool.vcxproj new file mode 100644 index 0000000000..9a8fec4aea --- /dev/null +++ b/tools/WebcamReportTool/WebcamReportTool.vcxproj @@ -0,0 +1,69 @@ + + + + + + + {FC9599B4-68CD-E14B-71D7-456FDD8D0845} + true + WebcamReportTool + WebcamReportTool + + + $(SolutionDir)..\..\$(Platform)\$(Configuration)\obj\$(ProjectName)\ + $(SolutionDir)..\..\$(Platform)\$(Configuration)\$(ProjectName)\ + + + + + + + + + + + + true + WebcamReportTool + .exe + include;$(IncludePath) + lib;$(LibraryPath) + + + false + WebcamReportTool + .exe + include;$(IncludePath) + lib;$(LibraryPath) + + + + NotUsing + + + Console + Windowsapp.lib;Strmiids.lib;%(AdditionalDependencies) + true + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/tools/WebcamReportTool/main.cpp b/tools/WebcamReportTool/main.cpp new file mode 100644 index 0000000000..06e25ab2f8 --- /dev/null +++ b/tools/WebcamReportTool/main.cpp @@ -0,0 +1,219 @@ +#include +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "DirectShowUtils.h" + +std::ofstream& log() +{ + static std::ofstream report = []{ + char buffer[MAX_PATH]{}; + if (SHGetSpecialFolderPathA(HWND_DESKTOP, buffer, CSIDL_DESKTOP, false)) + { + std::string path = buffer; + path += "\\WebcamReport.txt"; + return std::ofstream{ path }; + } + else + { + return std::ofstream{ "WebcamReport.txt" }; + } + }(); + return report; +} + +std::string GetMediaSubTypeString(const GUID& guid) +{ + if (guid == MEDIASUBTYPE_RGB24) + { + return "MEDIASUBTYPE_RGB24"; + } + else if (guid == MEDIASUBTYPE_RGB32) + { + return "MEDIASUBTYPE_RGB32"; + } + else if (guid == MEDIASUBTYPE_YUY2) + { + return "MEDIASUBTYPE_YUY2"; + } + else if (guid == MEDIASUBTYPE_MJPG) + { + return "MEDIASUBTYPE_MJPG"; + } + else if (guid == MEDIASUBTYPE_NV12) + { + return "MEDIASUBTYPE_NV12"; + } + else if (guid == MEDIASUBTYPE_NV11) + { + return "MEDIASUBTYPE_NV11"; + } + else if (guid == MEDIASUBTYPE_YV12) + { + return "MEDIASUBTYPE_YV12"; + } + else if (guid == MEDIASUBTYPE_YUYV) + { + return "MEDIASUBTYPE_YUYV"; + } + else + { + OLECHAR* guidString = nullptr; + StringFromCLSID(guid, &guidString); + if (guidString) + { + std::wstring_view wideView{guidString}; + std::string result; + for (const auto c :wideView) + { + result += static_cast(c); + } + ::CoTaskMemFree(guidString); + return result; + } + else + { + return "MEDIASUBTYPE_UNKNOWN"; + } + } +} + +void LogMediaTypes(wil::com_ptr_nothrow& pin) +{ + wil::com_ptr_nothrow mediaTypeEnum; + if (pin->EnumMediaTypes(&mediaTypeEnum); !mediaTypeEnum) + { + return; + } + ULONG _ = 0; + unique_media_type_ptr mt; + log() << "Supported formats:\n"; + while (mediaTypeEnum->Next(1, wil::out_param(mt), &_) == S_OK) + { + if (mt->majortype != MEDIATYPE_Video) + { + continue; + } + auto format = reinterpret_cast(mt->pbFormat); + if (!format->AvgTimePerFrame) + { + continue; + } + const auto formatAvgFPS = 10000000LL / format->AvgTimePerFrame; + log() << GetMediaSubTypeString(mt->subtype) << '\t' << format->bmiHeader.biWidth << "x" + << format->bmiHeader.biHeight << " - " << formatAvgFPS << "fps\n"; + } + log() << '\n'; +} + +void ReportAllWebcams() +{ + auto enumeratorFactory = wil::CoCreateInstanceNoThrow(CLSID_SystemDeviceEnum); + if (!enumeratorFactory) + { + LOG("Couldn't create devenum factory"); + return; + } + + wil::com_ptr_nothrow enumMoniker; + enumeratorFactory->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &enumMoniker, CDEF_DEVMON_PNP_DEVICE); + if (!enumMoniker) + { + LOG("Couldn't create class enumerator"); + return; + } + + ULONG _ = 0; + wil::com_ptr_nothrow moniker; + while (enumMoniker->Next(1, &moniker, &_) == S_OK) + { + wil::com_ptr_nothrow propertyData; + moniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, reinterpret_cast(&propertyData)); + if (!propertyData) + { + LOG("BindToStorage failed"); + continue; + } + + wil::unique_variant propVal; + propVal.vt = VT_BSTR; + + if (FAILED(propertyData->Read(L"FriendlyName", &propVal, nullptr))) + { + LOG("Couldn't obtain FriendlyName property"); + continue; + } + std::wstring wideFriendlyName = { propVal.bstrVal, SysStringLen(propVal.bstrVal) }; + std::string friendlyName; + for (wchar_t c : wideFriendlyName) + { + friendlyName += (char)c; + } + log() << "Webcam " << friendlyName << '\n'; + + propVal.reset(); + propVal.vt = VT_BSTR; + + if (FAILED(propertyData->Read(L"DevicePath", &propVal, nullptr))) + { + LOG("Couldn't obtain DevicePath property"); + continue; + } + wil::com_ptr_nothrow filter; + moniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, reinterpret_cast(&filter)); + if (!filter) + { + LOG("Couldn't BindToObject"); + continue; + } + + wil::com_ptr_nothrow pinsEnum; + if (FAILED(filter->EnumPins(&pinsEnum))) + { + LOG("BindToObject EnumPins"); + continue; + } + wil::com_ptr_nothrow pin; + + while (pinsEnum->Next(1, &pin, &_) == S_OK) + { + // Skip pins which do not belong to capture category + GUID category{}; + DWORD __; + if (auto props = pin.try_copy(); + !props || + FAILED(props->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, nullptr, 0, &category, sizeof(GUID), &__)) || + category != PIN_CATEGORY_CAPTURE) + { + continue; + } + + // Skip non-output pins + if (PIN_DIRECTION direction = {}; FAILED(pin->QueryDirection(&direction)) || direction != PINDIR_OUTPUT) + { + continue; + } + LogMediaTypes(pin); + } + } +} + +int main() +{ + auto comCtx = wil::CoInitializeEx(); + log() << "Report started\n"; + ReportAllWebcams(); + return 0; +} diff --git a/tools/WebcamReportTool/packages.config b/tools/WebcamReportTool/packages.config new file mode 100644 index 0000000000..fda08e21c1 --- /dev/null +++ b/tools/WebcamReportTool/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file