From 69cb53b51bcb421aedbc6c4b94d3812cb4950005 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 17 Nov 2018 23:16:35 -0800 Subject: [PATCH] Add support for USB Webcams on Windows (#1390) --- build.gradle | 1 + cscore/build.gradle | 8 +- cscore/src/main/native/include/cscore_c.h | 3 +- cscore/src/main/native/include/cscore_cpp.h | 7 + .../src/main/native/windows/COMCreators.cpp | 158 +++ cscore/src/main/native/windows/COMCreators.h | 58 + cscore/src/main/native/windows/ComPtr.h | 152 +++ .../main/native/windows/NetworkListener.cpp | 51 +- .../src/main/native/windows/NetworkUtil.cpp | 105 +- .../src/main/native/windows/UsbCameraImpl.cpp | 1013 ++++++++++++++++- .../src/main/native/windows/UsbCameraImpl.h | 184 +++ .../main/native/windows/UsbCameraProperty.cpp | 69 ++ .../main/native/windows/UsbCameraProperty.h | 72 ++ .../native/windows/WindowsMessagePump.cpp | 152 +++ .../main/native/windows/WindowsMessagePump.h | 66 ++ shared/jni/setupBuild.gradle | 4 + 16 files changed, 2082 insertions(+), 21 deletions(-) create mode 100644 cscore/src/main/native/windows/COMCreators.cpp create mode 100644 cscore/src/main/native/windows/COMCreators.h create mode 100644 cscore/src/main/native/windows/ComPtr.h create mode 100644 cscore/src/main/native/windows/UsbCameraImpl.h create mode 100644 cscore/src/main/native/windows/UsbCameraProperty.cpp create mode 100644 cscore/src/main/native/windows/UsbCameraProperty.h create mode 100644 cscore/src/main/native/windows/WindowsMessagePump.cpp create mode 100644 cscore/src/main/native/windows/WindowsMessagePump.h diff --git a/build.gradle b/build.gradle index 2d0d56a4b9..54fa394b23 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ plugins { id 'edu.wpi.first.GradleJni' version '0.3.1' id 'edu.wpi.first.GradleVsCode' version '0.6.1' id 'idea' + id 'visual-studio' id 'com.gradle.build-scan' version '1.15.1' id 'net.ltgt.errorprone' version '0.6' apply false } diff --git a/cscore/build.gradle b/cscore/build.gradle index 77331a6d3a..3162c8804b 100644 --- a/cscore/build.gradle +++ b/cscore/build.gradle @@ -29,6 +29,7 @@ ext { } exportedHeaders { srcDirs 'src/main/native/include' + include '**/*.h' } } cscoreMacCpp(CppSourceSet) { @@ -38,6 +39,7 @@ ext { } exportedHeaders { srcDirs 'src/main/native/include', 'src/main/native/cpp' + include '**/*.h' } } } @@ -50,6 +52,7 @@ ext { } exportedHeaders { srcDirs 'src/main/native/include', 'src/main/native/cpp' + include '**/*.h' } } } @@ -62,6 +65,7 @@ ext { } exportedHeaders { srcDirs 'src/main/native/include', 'src/main/native/cpp' + include '**/*.h' } } } @@ -94,11 +98,11 @@ model { x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure', '_CT??_R0?AVbad_cast', '_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure', - '_TI5?AVfailure'] + '_TI5?AVfailure', '=='] x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure', '_CT??_R0?AVbad_cast', '_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure', - '_TI5?AVfailure'] + '_TI5?AVfailure', '=='] } cscoreJNI(ExportsConfig) { x86SymbolFilter = { symbols -> diff --git a/cscore/src/main/native/include/cscore_c.h b/cscore/src/main/native/include/cscore_c.h index 7b3b1e0413..b7b8789062 100644 --- a/cscore/src/main/native/include/cscore_c.h +++ b/cscore/src/main/native/include/cscore_c.h @@ -69,7 +69,8 @@ enum CS_StatusValue { CS_SOURCE_IS_DISCONNECTED = -2005, CS_EMPTY_VALUE = -2006, CS_BAD_URL = -2007, - CS_TELEMETRY_NOT_ENABLED = -2008 + CS_TELEMETRY_NOT_ENABLED = -2008, + CS_UNSUPPORTED_MODE = -2009 }; /** diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h index 1156ba64b2..33e8f20339 100644 --- a/cscore/src/main/native/include/cscore_cpp.h +++ b/cscore/src/main/native/include/cscore_cpp.h @@ -79,6 +79,13 @@ struct VideoMode : public CS_VideoMode { fps = fps_; } explicit operator bool() const { return pixelFormat == kUnknown; } + + bool operator==(const VideoMode& other) const { + return pixelFormat == other.pixelFormat && width == other.width && + height == other.height && fps == other.fps; + } + + bool operator!=(const VideoMode& other) const { return !(*this == other); } }; /** diff --git a/cscore/src/main/native/windows/COMCreators.cpp b/cscore/src/main/native/windows/COMCreators.cpp new file mode 100644 index 0000000000..265f7d3844 --- /dev/null +++ b/cscore/src/main/native/windows/COMCreators.cpp @@ -0,0 +1,158 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include +#include +#include +#include + +#include + +#include "UsbCameraImpl.h" + +// https://github.com/opencv/opencv/blob/master/modules/videoio/src/cap_msmf.cpp + +#include +#include +#include +#include +#include +#include + +#include "COMCreators.h" +#include "ComPtr.h" + +#pragma comment(lib, "Mfplat.lib") +#pragma comment(lib, "Mf.lib") +#pragma comment(lib, "mfuuid.lib") +#pragma comment(lib, "Ole32.lib") +#pragma comment(lib, "User32.lib") +#pragma comment(lib, "Mfreadwrite.lib") +#pragma comment(lib, "Shlwapi.lib") + +namespace cs { + +SourceReaderCB::SourceReaderCB(std::weak_ptr source, + const cs::VideoMode& mode) + : m_nRefCount(1), m_source(source), m_mode{mode} {} + +// IUnknown methods +STDMETHODIMP SourceReaderCB::QueryInterface(REFIID iid, void** ppv) { + static const QITAB qit[] = { + QITABENT(SourceReaderCB, IMFSourceReaderCallback), + {0}, + }; + return QISearch(this, qit, iid, ppv); +} +STDMETHODIMP_(ULONG) SourceReaderCB::AddRef() { + return InterlockedIncrement(&m_nRefCount); +} +STDMETHODIMP_(ULONG) SourceReaderCB::Release() { + ULONG uCount = InterlockedDecrement(&m_nRefCount); + if (uCount == 0) { + delete this; + } + return uCount; +} + +STDMETHODIMP SourceReaderCB::OnEvent(DWORD, IMFMediaEvent*) { return S_OK; } + +STDMETHODIMP SourceReaderCB::OnFlush(DWORD) { return S_OK; } + +void SourceReaderCB::NotifyError(HRESULT hr) { + wprintf(L"Source Reader error: 0x%X\n", hr); +} + +STDMETHODIMP SourceReaderCB::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, + DWORD dwStreamFlags, + LONGLONG llTimestamp, + IMFSample* pSample // Can be NULL +) { + auto source = m_source.lock(); + if (!source) return S_OK; + if (SUCCEEDED(hrStatus)) { + if (pSample) { + // Prcoess sample + source->ProcessFrame(pSample, m_mode); + // DO NOT release the frame + } + } else { + // Streaming error. + NotifyError(hrStatus); + } + // Trigger asking for a new frame. + // This is piped through the message pump for concurrency reasons + source->PostRequestNewFrame(); + return S_OK; +} + +// Create a Source Reader COM Smart Object +ComPtr CreateSourceReaderCB( + std::weak_ptr source, const cs::VideoMode& mode) { + SourceReaderCB* ptr = new SourceReaderCB(source, mode); + ComPtr sourceReaderCB; + sourceReaderCB.Attach(ptr); + return sourceReaderCB; +} + +ComPtr CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink) { + ComPtr pAttributes; + ComPtr pSource; + + HRESULT hr = MFCreateAttributes(pAttributes.GetAddressOf(), 2); + + // Set the device type to video. + if (SUCCEEDED(hr)) { + hr = pAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + } + + // Set the symbolic link. + if (SUCCEEDED(hr)) { + hr = pAttributes->SetString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, + pszSymbolicLink); + } + + if (SUCCEEDED(hr)) { + hr = MFCreateDeviceSource(pAttributes.Get(), pSource.GetAddressOf()); + } + + // No need to check last HR, as the source would be null anyway. + return pSource; +} + +ComPtr CreateSourceReader(IMFMediaSource* mediaSource, + IMFSourceReaderCallback* callback) { + HRESULT hr = S_OK; + ComPtr pAttributes; + ComPtr sourceReader; + + hr = MFCreateAttributes(pAttributes.GetAddressOf(), 1); + if (FAILED(hr)) { + return nullptr; + } + + hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback); + if (FAILED(hr)) { + return nullptr; + } + + hr = pAttributes->SetUINT32( + MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN, TRUE); + if (FAILED(hr)) { + return nullptr; + } + + MFCreateSourceReaderFromMediaSource(mediaSource, pAttributes.Get(), + sourceReader.GetAddressOf()); + + // No need to check last HR, as the sourceReader would be null anyway. + return sourceReader; +} + +} // namespace cs diff --git a/cscore/src/main/native/windows/COMCreators.h b/cscore/src/main/native/windows/COMCreators.h new file mode 100644 index 0000000000..f89e6fdafe --- /dev/null +++ b/cscore/src/main/native/windows/COMCreators.h @@ -0,0 +1,58 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include +#include + +#include + +#include "ComPtr.h" +#include "cscore_cpp.h" + +namespace cs { + +class UsbCameraImpl; + +// Source callback used by the source reader. +// COM object, so it needs a to ref count itself. +class SourceReaderCB : public IMFSourceReaderCallback { + public: + explicit SourceReaderCB(std::weak_ptr source, + const cs::VideoMode& mode); + void SetVideoMode(const VideoMode& mode) { m_mode = mode; } + + STDMETHODIMP QueryInterface(REFIID iid, void** ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + STDMETHODIMP OnEvent(DWORD, IMFMediaEvent*); + STDMETHODIMP OnFlush(DWORD); + STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, + DWORD dwStreamFlags, LONGLONG llTimestamp, + IMFSample* pSample // Can be NULL + ); + + void InvalidateCapture() { m_source = std::weak_ptr(); } + + private: + // Destructor is private. Caller should call Release. + virtual ~SourceReaderCB() {} + void NotifyError(HRESULT hr); + + ULONG m_nRefCount; + std::weak_ptr m_source; + cs::VideoMode m_mode; +}; + +ComPtr CreateSourceReaderCB( + std::weak_ptr source, const cs::VideoMode& mode); +ComPtr CreateSourceReader(IMFMediaSource* mediaSource, + IMFSourceReaderCallback* callback); +ComPtr CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink); +} // namespace cs diff --git a/cscore/src/main/native/windows/ComPtr.h b/cscore/src/main/native/windows/ComPtr.h new file mode 100644 index 0000000000..22a330df0d --- /dev/null +++ b/cscore/src/main/native/windows/ComPtr.h @@ -0,0 +1,152 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include +#include // QISearch + +#include + +namespace cs { + +template +class RemoveAddRefRelease : public Interface { + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + virtual ~RemoveAddRefRelease(); +}; + +template +class ComPtr { + public: + template + friend class ComPtr; + + ComPtr(std::nullptr_t = nullptr) noexcept {} // NOLINT(runtime/explicit) + ComPtr(const ComPtr& other) noexcept : m_ptr(other.m_ptr) { + InternalAddRef(); + } + + template + ComPtr(const ComPtr& other) noexcept : m_ptr(other.m_ptr) { + InternalAddRef(); + } + + template + ComPtr(ComPtr&& other) noexcept : m_ptr(other.m_ptr) { + other.m_ptr = nullptr; + } + + ~ComPtr() noexcept { InternalRelease(); } + + ComPtr& operator=(const ComPtr& other) noexcept { + InternalCopy(other.m_ptr); + return *this; + } + + template + ComPtr& operator=(const ComPtr& other) noexcept { + InternalCopy(other.m_ptr); + return *this; + } + + template + ComPtr& operator=(ComPtr&& other) noexcept { + InternalMove(other); + return *this; + } + + void Swap(ComPtr& other) noexcept { + Interface* temp = m_ptr; + m_ptr = other.m_ptr; + other.m_ptr = temp; + } + + explicit operator bool() const noexcept { return nullptr != m_ptr; } + + void Reset() noexcept { InternalRelease(); } + + Interface* Get() const noexcept { return m_ptr; } + + Interface* Detach() noexcept { + Interface* temp = m_ptr; + m_ptr = nullptr; + return temp; + } + + void Copy(Interface* other) noexcept { InternalCopy(other); } + + void Attach(Interface* other) noexcept { + InternalRelease(); + m_ptr = other; + } + + Interface** GetAddressOf() noexcept { + assert(m_ptr == nullptr); + return &m_ptr; + } + + void CopyTo(Interface** other) const noexcept { + InternalAddRef(); + *other = m_ptr; + } + + template + ComPtr As() const noexcept { + ComPtr temp; + m_ptr->QueryInterface(temp.GetAddressOf()); + return temp; + } + + RemoveAddRefRelease* operator->() const noexcept { + return static_cast*>(m_ptr); + } + + private: + Interface* m_ptr = nullptr; + + void InternalAddRef() const noexcept { + if (m_ptr) { + m_ptr->AddRef(); + } + } + + void InternalRelease() noexcept { + Interface* temp = m_ptr; + if (temp) { + m_ptr = nullptr; + temp->Release(); + } + } + + void InternalCopy(Interface* other) noexcept { + if (m_ptr != other) { + InternalRelease(); + m_ptr = other; + InternalAddRef(); + } + } + + template + void InternalMove(ComPtr& other) noexcept { + if (m_ptr != other.m_ptr) { + InternalRelease(); + m_ptr = other.m_ptr; + other.m_ptr = nullptr; + } + } +}; + +template +void swap( + ComPtr& left, + ComPtr& right) noexcept { // NOLINT(build/include_what_you_use) + left.Swap(right); +} + +} // namespace cs diff --git a/cscore/src/main/native/windows/NetworkListener.cpp b/cscore/src/main/native/windows/NetworkListener.cpp index 0716789703..cd29e44163 100644 --- a/cscore/src/main/native/windows/NetworkListener.cpp +++ b/cscore/src/main/native/windows/NetworkListener.cpp @@ -7,14 +7,55 @@ #include "NetworkListener.h" +#include // NOLINT(build/include_order) + +#include // NOLINT(build/include_order) + +#include // NOLINT(build/include_order) + +#include // NOLINT(build/include_order) + +#include // NOLINT(build/include_order) + +#include // NOLINT(build/include_order) + +#include "Instance.h" +#include "Log.h" +#include "Notifier.h" + +#pragma comment(lib, "Iphlpapi.lib") + using namespace cs; -class NetworkListener::Impl {}; +class NetworkListener::Impl { + public: + Impl(wpi::Logger& logger, Notifier& notifier) + : m_logger(logger), m_notifier(notifier) {} + wpi::Logger& m_logger; + Notifier& m_notifier; + HANDLE eventHandle = 0; +}; -NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier) {} +// Static Callback function for NotifyIpInterfaceChange API. +static void WINAPI OnInterfaceChange(PVOID callerContext, + PMIB_IPINTERFACE_ROW row, + MIB_NOTIFICATION_TYPE notificationType) { + Notifier* notifier = reinterpret_cast(callerContext); + notifier->NotifyNetworkInterfacesChanged(); +} -NetworkListener::~NetworkListener() {} +NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier) + : m_impl(std::make_unique(logger, notifier)) {} -void NetworkListener::Start() {} +NetworkListener::~NetworkListener() { Stop(); } -void NetworkListener::Stop() {} +void NetworkListener::Start() { + NotifyIpInterfaceChange(AF_INET, OnInterfaceChange, &m_impl->m_notifier, true, + &m_impl->eventHandle); +} + +void NetworkListener::Stop() { + if (m_impl->eventHandle) { + CancelMibChangeNotify2(m_impl->eventHandle); + } +} diff --git a/cscore/src/main/native/windows/NetworkUtil.cpp b/cscore/src/main/native/windows/NetworkUtil.cpp index 935c21f6db..99e734a3ca 100644 --- a/cscore/src/main/native/windows/NetworkUtil.cpp +++ b/cscore/src/main/native/windows/NetworkUtil.cpp @@ -5,12 +5,115 @@ /* the project. */ /*----------------------------------------------------------------------------*/ +#include // NOLINT(build/include_order) + +#include // NOLINT(build/include_order) + +#include // NOLINT(build/include_order) + +#include + +#include + #include "cscore_cpp.h" +#pragma comment(lib, "IPHLPAPI.lib") +#pragma comment(lib, "Ws2_32.lib") + +#define WORKING_BUFFER_SIZE 15000 +#define MAX_TRIES 3 + +#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) +#define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) + namespace cs { std::vector GetNetworkInterfaces() { - return std::vector{}; // TODO + uv_interface_address_t* adrs; + int counts = 0; + + std::vector addresses{}; + + uv_interface_addresses(&adrs, &counts); + + char ip[50]; + + for (int i = 0; i < counts; i++) { + if (adrs[i].is_internal) continue; + std::cout << adrs[i].name << std::endl; + InetNtop(PF_INET, &(adrs[i].netmask.netmask4.sin_addr.s_addr), ip, + sizeof(ip) - 1); + ip[49] = '\0'; + std::cout << ip << std::endl; + InetNtop(PF_INET, &(adrs[i].address.address4.sin_addr.s_addr), ip, + sizeof(ip) - 1); + ip[49] = '\0'; + std::cout << ip << std::endl; + addresses.emplace_back(std::string{ip}); + } + + uv_free_interface_addresses(adrs, counts); + + std::cout << "finished\n"; + + return addresses; + + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + PIP_ADAPTER_ANYCAST_ADDRESS pAnycast = NULL; + PIP_ADAPTER_MULTICAST_ADDRESS pMulticast = NULL; + unsigned int i = 0; + + ULONG outBufLen = 0; + ULONG Iterations = 0; + DWORD dwRetVal = 0; + + // Allocate a 15 KB buffer to start with. + outBufLen = WORKING_BUFFER_SIZE; + + do { + pAddresses = reinterpret_cast(MALLOC(outBufLen)); + if (pAddresses == NULL) { + std::printf("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n"); + std::exit(1); + } + + dwRetVal = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, + pAddresses, &outBufLen); + + if (dwRetVal == ERROR_BUFFER_OVERFLOW) { + FREE(pAddresses); + pAddresses = NULL; + } else { + break; + } + + Iterations++; + } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES)); + + if (dwRetVal == NO_ERROR) { + pCurrAddresses = pAddresses; + while (pCurrAddresses) { + pUnicast = pCurrAddresses->FirstUnicastAddress; + while (pUnicast != NULL) { + sockaddr_in* address = + reinterpret_cast(pUnicast->Address.lpSockaddr); + InetNtop(PF_INET, &(address->sin_addr.s_addr), ip, sizeof(ip) - 1); + ip[49] = '\0'; + addresses.emplace_back(std::string{ip}); + pUnicast = pUnicast->Next; + } + + pCurrAddresses = pCurrAddresses->Next; + } + } + + if (pAddresses) { + FREE(pAddresses); + } + + return addresses; // TODO } } // namespace cs diff --git a/cscore/src/main/native/windows/UsbCameraImpl.cpp b/cscore/src/main/native/windows/UsbCameraImpl.cpp index e9a47c889b..dc5c293ce9 100644 --- a/cscore/src/main/native/windows/UsbCameraImpl.cpp +++ b/cscore/src/main/native/windows/UsbCameraImpl.cpp @@ -1,34 +1,1023 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ /*----------------------------------------------------------------------------*/ +#define _WINSOCKAPI_ +#include "UsbCameraImpl.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "COMCreators.h" +#include "ComPtr.h" +#include "Handle.h" +#include "Instance.h" +#include "JpegUtil.h" +#include "Log.h" +#include "Notifier.h" +#include "PropertyImpl.h" +#include "Telemetry.h" +#include "WindowsMessagePump.h" +#include "c_util.h" #include "cscore_cpp.h" +#pragma comment(lib, "Mfplat.lib") +#pragma comment(lib, "Mf.lib") +#pragma comment(lib, "mfuuid.lib") +#pragma comment(lib, "Ole32.lib") +#pragma comment(lib, "User32.lib") +#pragma comment(lib, "Mfreadwrite.lib") +#pragma comment(lib, "Shlwapi.lib") + +static constexpr int NewImageMessage = 0x0400 + 4488; +static constexpr int SetCameraMessage = 0x0400 + 254; +static constexpr int WaitForStartupMessage = 0x0400 + 294; + +static constexpr char const* kPropWbValue = "WhiteBalance"; +static constexpr char const* kPropExValue = "Exposure"; +static constexpr char const* kPropBrValue = "Brightness"; +static constexpr char const* kPropConnectVerbose = "connect_verbose"; + +static constexpr unsigned kPropConnectVerboseId = 0; + +using namespace cs; + namespace cs { +UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, + Notifier& notifier, Telemetry& telemetry, + const wpi::Twine& path) + : SourceImpl{name, logger, notifier, telemetry}, m_path{path.str()} { + std::wstring_convert> utf8_conv; + m_widePath = utf8_conv.from_bytes(m_path.c_str()); +} + +UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, + Notifier& notifier, Telemetry& telemetry, + int deviceId) + : SourceImpl{name, logger, notifier, telemetry}, m_deviceId(deviceId) {} + +UsbCameraImpl::~UsbCameraImpl() { m_messagePump = nullptr; } + +void UsbCameraImpl::SetProperty(int property, int value, CS_Status* status) { + Message msg{Message::kCmdSetProperty}; + msg.data[0] = property; + msg.data[1] = value; + auto result = + m_messagePump->SendWindowMessage( + SetCameraMessage, msg.kind, &msg); + *status = result; +} +void UsbCameraImpl::SetStringProperty(int property, const wpi::Twine& value, + CS_Status* status) { + Message msg{Message::kCmdSetPropertyStr}; + msg.data[0] = property; + msg.dataStr = value.str(); + auto result = + m_messagePump->SendWindowMessage( + SetCameraMessage, msg.kind, &msg); + *status = result; +} + +// Standard common camera properties +void UsbCameraImpl::SetBrightness(int brightness, CS_Status* status) { + SetProperty(GetPropertyIndex(kPropBrValue), brightness, status); +} +int UsbCameraImpl::GetBrightness(CS_Status* status) const { + return GetProperty(GetPropertyIndex(kPropBrValue), status); +} +void UsbCameraImpl::SetWhiteBalanceAuto(CS_Status* status) { + // TODO +} +void UsbCameraImpl::SetWhiteBalanceHoldCurrent(CS_Status* status) { + // TODO +} +void UsbCameraImpl::SetWhiteBalanceManual(int value, CS_Status* status) { + SetProperty(GetPropertyIndex(kPropWbValue), value, status); +} +void UsbCameraImpl::SetExposureAuto(CS_Status* status) { + // TODO +} +void UsbCameraImpl::SetExposureHoldCurrent(CS_Status* status) { + // TODO +} +void UsbCameraImpl::SetExposureManual(int value, CS_Status* status) { + SetProperty(GetPropertyIndex(kPropExValue), value, status); +} + +bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) { + if (mode.pixelFormat == VideoMode::kUnknown) { + *status = CS_UNSUPPORTED_MODE; + return false; + } + + Message msg{Message::kCmdSetMode}; + msg.data[0] = mode.pixelFormat; + msg.data[1] = mode.width; + msg.data[2] = mode.height; + msg.data[3] = mode.fps; + auto result = + m_messagePump->SendWindowMessage( + SetCameraMessage, msg.kind, &msg); + *status = result; + return result == 0; +} + +bool UsbCameraImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat, + CS_Status* status) { + if (pixelFormat == VideoMode::kUnknown) { + *status = CS_UNSUPPORTED_MODE; + return false; + } + Message msg{Message::kCmdSetPixelFormat}; + msg.data[0] = pixelFormat; + auto result = + m_messagePump->SendWindowMessage( + SetCameraMessage, msg.kind, &msg); + *status = result; + return result == 0; +} +bool UsbCameraImpl::SetResolution(int width, int height, CS_Status* status) { + Message msg{Message::kCmdSetResolution}; + msg.data[0] = width; + msg.data[1] = height; + auto result = + m_messagePump->SendWindowMessage( + SetCameraMessage, msg.kind, &msg); + *status = result; + return result == 0; +} +bool UsbCameraImpl::SetFPS(int fps, CS_Status* status) { + Message msg{Message::kCmdSetFPS}; + msg.data[0] = fps; + auto result = + m_messagePump->SendWindowMessage( + SetCameraMessage, msg.kind, &msg); + *status = result; + return result == 0; +} + +void UsbCameraImpl::NumSinksChanged() { + m_messagePump->PostWindowMessage( + SetCameraMessage, Message::kNumSinksChanged, nullptr); +} +void UsbCameraImpl::NumSinksEnabledChanged() { + m_messagePump->PostWindowMessage( + SetCameraMessage, Message::kNumSinksEnabledChanged, nullptr); +} + +void UsbCameraImpl::Start() { + m_messagePump = std::make_unique( + [this](HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) { + return this->PumpMain(hwnd, uiMsg, wParam, lParam); + }); +} + +void UsbCameraImpl::PostRequestNewFrame() { + m_messagePump->PostWindowMessage(NewImageMessage, nullptr, nullptr); +} + +bool UsbCameraImpl::CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr, + bool* connected) { + DEV_BROADCAST_DEVICEINTERFACE* pDi = NULL; + + *connected = false; + + if (pHdr == NULL) { + return false; + } + if (pHdr->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) { + return false; + } + + // Compare the device name with the symbolic link. + + pDi = reinterpret_cast(pHdr); + + if (_stricmp(m_path.c_str(), pDi->dbcc_name) == 0) { + if (wParam == DBT_DEVICEARRIVAL) { + *connected = true; + return true; + } else if (wParam == DBT_DEVICEREMOVECOMPLETE) { + *connected = false; + return true; + } + } + return false; +} + +void UsbCameraImpl::DeviceDisconnect() { + if (m_connectVerbose) SINFO("Disconnected from " << m_path); + m_sourceReader.Reset(); + m_mediaSource.Reset(); + if (m_imageCallback) { + m_imageCallback->InvalidateCapture(); + } + m_imageCallback.Reset(); + m_streaming = false; + SetConnected(false); +} + +static bool IsPercentageProperty(wpi::StringRef name) { + if (name.startswith("raw_")) name = name.substr(4); + return name == "Brightness" || name == "Contrast" || name == "Saturation" || + name == "Hue" || name == "Sharpness" || name == "Gain" || + name == "Exposure"; +} + +void UsbCameraImpl::ProcessFrame(IMFSample* videoSample, + const VideoMode& mode) { + if (!videoSample) return; + + ComPtr buf; + + if (!SUCCEEDED(videoSample->ConvertToContiguousBuffer(buf.GetAddressOf()))) { + DWORD bcnt = 0; + if (!SUCCEEDED(videoSample->GetBufferCount(&bcnt))) return; + if (bcnt == 0) return; + if (!SUCCEEDED(videoSample->GetBufferByIndex(0, buf.GetAddressOf()))) + return; + } + + bool lock2d = false; + BYTE* ptr = NULL; + LONG pitch = 0; + DWORD maxsize = 0, cursize = 0; + + // "For 2-D buffers, the Lock2D method is more efficient than the Lock + // method" see IMFMediaBuffer::Lock method documentation: + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb970366(v=vs.85).aspx + ComPtr buffer2d; + DWORD memLength2d = 0; + if (true) { + buffer2d = buf.As(); + if (buffer2d) { + buffer2d->GetContiguousLength(&memLength2d); + if (SUCCEEDED(buffer2d->Lock2D(&ptr, &pitch))) { + lock2d = true; + } + } + } + if (ptr == NULL) { + if (!SUCCEEDED(buf->Lock(&ptr, &maxsize, &cursize))) { + return; + } + } + if (!ptr) return; + + cv::Mat tmpMat; + std::unique_ptr dest; + bool doFinalSet = true; + + switch (mode.pixelFormat) { + case cs::VideoMode::PixelFormat::kMJPEG: { + // Special case + PutFrame(VideoMode::kMJPEG, mode.width, mode.height, + wpi::StringRef(reinterpret_cast(ptr), cursize), + wpi::Now()); + doFinalSet = false; + break; + } + case cs::VideoMode::PixelFormat::kGray: + tmpMat = cv::Mat(mode.height, mode.width, CV_8UC1, ptr, pitch); + dest = AllocImage(VideoMode::kGray, tmpMat.cols, tmpMat.rows, + tmpMat.total()); + tmpMat.copyTo(dest->AsMat()); + break; + case cs::VideoMode::PixelFormat::kBGR: + tmpMat = cv::Mat(mode.height, mode.width, CV_8UC3, ptr, pitch); + dest = AllocImage(VideoMode::kBGR, tmpMat.cols, tmpMat.rows, + tmpMat.total() * 3); + tmpMat.copyTo(dest->AsMat()); + break; + case cs::VideoMode::PixelFormat::kYUYV: + tmpMat = cv::Mat(mode.height, mode.width, CV_8UC2, ptr, pitch); + dest = AllocImage(VideoMode::kYUYV, tmpMat.cols, tmpMat.rows, + tmpMat.total() * 2); + tmpMat.copyTo(dest->AsMat()); + break; + default: + doFinalSet = false; + break; + } + + if (doFinalSet) { + PutFrame(std::move(dest), wpi::Now()); + } + + if (lock2d) + buffer2d->Unlock2D(); + else + buf->Unlock(); +} + +LRESULT UsbCameraImpl::PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam, + LPARAM lParam) { + switch (uiMsg) { + case WM_CLOSE: + m_sourceReader.Reset(); + m_mediaSource.Reset(); + if (m_imageCallback) { + m_imageCallback->InvalidateCapture(); + } + m_imageCallback.Reset(); + break; + case WM_CREATE: + // Pump Created and ready to go + DeviceConnect(); + break; + case WaitForStartupMessage: + return CS_OK; + case WM_DEVICECHANGE: { + // Device potentially changed + PDEV_BROADCAST_HDR parameter = + reinterpret_cast(lParam); + // Check if we're waiting on a device path, and this is a connection + if (m_path.empty() && wParam == DBT_DEVICEARRIVAL && + parameter->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + // If path is empty, we attempted to connect with a device ID. Enumerate + // and check + CS_Status status = 0; + auto devices = cs::EnumerateUsbCameras(&status); + if (devices.size() > m_deviceId) { + // If has device ID, use the device ID from the event + // because of windows bug + auto&& device = devices[m_deviceId]; + DEV_BROADCAST_DEVICEINTERFACE* pDi = + reinterpret_cast(parameter); + m_path = pDi->dbcc_name; + std::wstring_convert> utf8_conv; + m_widePath = utf8_conv.from_bytes(m_path.c_str()); + } else { + // This device not found + break; + } + } + bool connected = false; + if (CheckDeviceChange(wParam, parameter, &connected)) { + if (connected) { + DeviceConnect(); + } else { + // Disconnected + DeviceDisconnect(); + } + } + } break; + case NewImageMessage: { // New image + if (m_streaming && m_sourceReader) { + m_sourceReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, + NULL, NULL, NULL); + } + break; + } + case SetCameraMessage: { + { + Message* msg = reinterpret_cast(lParam); + Message::Kind msgKind = static_cast(wParam); + std::unique_lock lock(m_mutex); + auto retVal = DeviceProcessCommand(lock, msgKind, msg); + return retVal; + } + break; + } + } + return 0l; +} + +static cs::VideoMode::PixelFormat GetFromGUID(const GUID& guid) { + // Compare GUID to one of the supported ones + if (guid == MFVideoFormat_NV12) { + // GrayScale + return cs::VideoMode::PixelFormat::kGray; + } else if (guid == MFVideoFormat_YUY2) { + return cs::VideoMode::PixelFormat::kYUYV; + } else if (guid == MFVideoFormat_RGB24) { + return cs::VideoMode::PixelFormat::kBGR; + } else if (guid == MFVideoFormat_MJPG) { + return cs::VideoMode::PixelFormat::kMJPEG; + } else if (guid == MFVideoFormat_RGB565) { + return cs::VideoMode::PixelFormat::kRGB565; + } else { + return cs::VideoMode::PixelFormat::kUnknown; + } +} + +bool UsbCameraImpl::DeviceConnect() { + if (m_mediaSource && m_sourceReader) return true; + + if (m_connectVerbose) SINFO("Connecting to USB camera on " << m_path); + + SDEBUG3("opening device"); + + const wchar_t* path = m_widePath.c_str(); + m_mediaSource = CreateVideoCaptureDevice(path); + + if (!m_mediaSource) return false; + m_imageCallback = CreateSourceReaderCB(shared_from_this(), m_mode); + + m_sourceReader = + CreateSourceReader(m_mediaSource.Get(), m_imageCallback.Get()); + + if (!m_sourceReader) { + m_mediaSource.Reset(); + return false; + } + + if (!m_properties_cached) { + SDEBUG3("caching properties"); + DeviceCacheProperties(); + DeviceCacheVideoModes(); + DeviceCacheMode(); + m_properties_cached = true; + } else { + SDEBUG3("restoring video mode"); + DeviceSetMode(); + } + + SetConnected(true); + + // Turn off streaming if not enabled, and turn it on if enabled + if (IsEnabled()) { + DeviceStreamOn(); + } + return true; +} + +std::unique_ptr UsbCameraImpl::CreateEmptyProperty( + const wpi::Twine& name) const { + return nullptr; +} + +bool UsbCameraImpl::CacheProperties(CS_Status* status) const { + // Wait for Camera Thread to be started + auto result = m_messagePump->SendWindowMessage( + WaitForStartupMessage, nullptr, nullptr); + *status = result; + if (*status != CS_OK) return false; + if (!m_properties_cached) { + *status = CS_SOURCE_IS_DISCONNECTED; + return false; + } + return true; +} + +void UsbCameraImpl::DeviceAddProperty(const wpi::Twine& name_, + tagVideoProcAmpProperty tag, + IAMVideoProcAmp* pProcAmp) { + // First see if properties exist + bool isValid = false; + auto property = std::make_unique(name_, tag, false, + pProcAmp, &isValid); + if (isValid) { + DeviceCacheProperty(std::move(property), pProcAmp); + } +} + +#define CREATEPROPERTY(val) \ + DeviceAddProperty(#val, VideoProcAmp_##val, pProcAmp); + +void UsbCameraImpl::DeviceCacheProperties() { + if (!m_sourceReader) return; + + IAMVideoProcAmp* pProcAmp = NULL; + + if (SUCCEEDED(m_sourceReader->GetServiceForStream( + (DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL, + IID_PPV_ARGS(&pProcAmp)))) { + CREATEPROPERTY(Brightness) + CREATEPROPERTY(Contrast) + CREATEPROPERTY(Hue) + CREATEPROPERTY(Saturation) + CREATEPROPERTY(Sharpness) + CREATEPROPERTY(Gamma) + CREATEPROPERTY(ColorEnable) + CREATEPROPERTY(WhiteBalance) + CREATEPROPERTY(BacklightCompensation) + CREATEPROPERTY(Gain) + pProcAmp->Release(); + } +} + +int UsbCameraImpl::RawToPercentage(const UsbCameraProperty& rawProp, + int rawValue) { + return 100.0 * (rawValue - rawProp.minimum) / + (rawProp.maximum - rawProp.minimum); +} + +int UsbCameraImpl::PercentageToRaw(const UsbCameraProperty& rawProp, + int percentValue) { + return rawProp.minimum + + (rawProp.maximum - rawProp.minimum) * (percentValue / 100.0); +} + +void UsbCameraImpl::DeviceCacheProperty( + std::unique_ptr rawProp, IAMVideoProcAmp* pProcAmp) { + // For percentage properties, we want to cache both the raw and the + // percentage versions. This function is always called with prop being + // the raw property (as it's coming from the camera) so if required, we need + // to rename this one as well as create/cache the percentage version. + // + // This is complicated by the fact that either the percentage version or the + // the raw version may have been set previously. If both were previously set, + // the raw version wins. + std::unique_ptr perProp; + if (IsPercentageProperty(rawProp->name)) { + perProp = + wpi::make_unique(rawProp->name, 0, *rawProp, 0, 0); + rawProp->name = "raw_" + perProp->name; + } + + std::unique_lock lock(m_mutex); + int* rawIndex = &m_properties[rawProp->name]; + bool newRaw = *rawIndex == 0; + UsbCameraProperty* oldRawProp = + newRaw ? nullptr + : static_cast(GetProperty(*rawIndex)); + + int* perIndex = perProp ? &m_properties[perProp->name] : nullptr; + bool newPer = !perIndex || *perIndex == 0; + UsbCameraProperty* oldPerProp = + newPer ? nullptr + : static_cast(GetProperty(*perIndex)); + + if (oldRawProp && oldRawProp->valueSet) { + // Merge existing raw setting and set percentage from it + rawProp->SetValue(oldRawProp->value); + rawProp->valueStr = std::move(oldRawProp->valueStr); + + if (perProp) { + perProp->SetValue(RawToPercentage(*rawProp, rawProp->value)); + perProp->valueStr = rawProp->valueStr; // copy + } + } else if (oldPerProp && oldPerProp->valueSet) { + // Merge existing percentage setting and set raw from it + perProp->SetValue(oldPerProp->value); + perProp->valueStr = std::move(oldPerProp->valueStr); + + rawProp->SetValue(PercentageToRaw(*rawProp, perProp->value)); + rawProp->valueStr = perProp->valueStr; // copy + } else { + // Read current raw value and set percentage from it + if (!rawProp->DeviceGet(lock, pProcAmp)) + SWARNING("failed to get property " << rawProp->name); + + if (perProp) { + perProp->SetValue(RawToPercentage(*rawProp, rawProp->value)); + perProp->valueStr = rawProp->valueStr; // copy + } + } + + // Set value on device if user-configured + if (rawProp->valueSet) { + if (!rawProp->DeviceSet(lock, pProcAmp)) + SWARNING("failed to set property " << rawProp->name); + } + + // Update pointers since we released the lock + rawIndex = &m_properties[rawProp->name]; + perIndex = perProp ? &m_properties[perProp->name] : nullptr; + + // Get pointers before we move the std::unique_ptr values + auto rawPropPtr = rawProp.get(); + auto perPropPtr = perProp.get(); + + if (newRaw) { + // create a new index + *rawIndex = m_propertyData.size() + 1; + m_propertyData.emplace_back(std::move(rawProp)); + } else { + // update + m_propertyData[*rawIndex - 1] = std::move(rawProp); + } + + // Finish setting up percentage property + if (perProp) { + perProp->propPair = *rawIndex; + perProp->defaultValue = + RawToPercentage(*rawPropPtr, rawPropPtr->defaultValue); + + if (newPer) { + // create a new index + *perIndex = m_propertyData.size() + 1; + m_propertyData.emplace_back(std::move(perProp)); + } else if (perIndex) { + // update + m_propertyData[*perIndex - 1] = std::move(perProp); + } + + // Tell raw property where to find percentage property + rawPropPtr->propPair = *perIndex; + } + + NotifyPropertyCreated(*rawIndex, *rawPropPtr); + if (perPropPtr) NotifyPropertyCreated(*perIndex, *perPropPtr); +} + +CS_StatusValue UsbCameraImpl::DeviceProcessCommand( + std::unique_lock& lock, Message::Kind msgKind, + const Message* msg) { + if (msgKind == Message::kCmdSetMode || + msgKind == Message::kCmdSetPixelFormat || + msgKind == Message::kCmdSetResolution || msgKind == Message::kCmdSetFPS) { + return DeviceCmdSetMode(lock, *msg); + } else if (msgKind == Message::kCmdSetProperty || + msgKind == Message::kCmdSetPropertyStr) { + return DeviceCmdSetProperty(lock, *msg); + return CS_OK; + } else if (msgKind == Message::kNumSinksChanged || + msgKind == Message::kNumSinksEnabledChanged) { + // Turn On Streams + if (!IsEnabled()) { + DeviceStreamOff(); + } else if (!m_streaming && IsEnabled()) { + DeviceStreamOn(); + } + return CS_OK; + } else { + return CS_OK; + } +} + +CS_StatusValue UsbCameraImpl::DeviceCmdSetProperty( + std::unique_lock& lock, const Message& msg) { + bool setString = (msg.kind == Message::kCmdSetPropertyStr); + int property = msg.data[0]; + int value = msg.data[1]; + wpi::StringRef valueStr = msg.dataStr; + + // Look up + auto prop = static_cast(GetProperty(property)); + if (!prop) return CS_INVALID_PROPERTY; + + // If setting before we get, guess initial type based on set + if (prop->propKind == CS_PROP_NONE) { + if (setString) + prop->propKind = CS_PROP_STRING; + else + prop->propKind = CS_PROP_INTEGER; + } + + // Check kind match + if ((setString && prop->propKind != CS_PROP_STRING) || + (!setString && (prop->propKind & + (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) == 0)) + return CS_WRONG_PROPERTY_TYPE; + + // Handle percentage property + int percentageProperty = prop->propPair; + int percentageValue = value; + if (percentageProperty != 0) { + if (prop->percentage) { + std::swap(percentageProperty, property); + prop = static_cast(GetProperty(property)); + value = PercentageToRaw(*prop, percentageValue); + } else { + percentageValue = RawToPercentage(*prop, value); + } + } + + // Actually set the new value on the device (if possible) + if (!prop->device) { + if (prop->id == kPropConnectVerboseId) m_connectVerbose = value; + } else { + IAMVideoProcAmp* pProcAmp = NULL; + if (SUCCEEDED(m_sourceReader->GetServiceForStream( + (DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL, + IID_PPV_ARGS(&pProcAmp)))) { + if (!prop->DeviceSet(lock, pProcAmp, value)) { + pProcAmp->Release(); + return CS_PROPERTY_WRITE_FAILED; + } + pProcAmp->Release(); + } else { + return CS_PROPERTY_WRITE_FAILED; + } + } + + // Cache the set values + UpdatePropertyValue(property, setString, value, valueStr); + if (percentageProperty != 0) + UpdatePropertyValue(percentageProperty, setString, percentageValue, + valueStr); + + return CS_OK; +} + +ComPtr UsbCameraImpl::DeviceCheckModeValid( + const VideoMode& toCheck) { + // Find the matching mode + auto match = + std::find_if(m_windowsVideoModes.begin(), m_windowsVideoModes.end(), + [&](std::pair>& input) { + return input.first == toCheck; + }); + + if (match == m_windowsVideoModes.end()) { + return nullptr; + } + return match->second; +} + +CS_StatusValue UsbCameraImpl::DeviceCmdSetMode( + std::unique_lock& lock, const Message& msg) { + VideoMode newMode; + if (msg.kind == Message::kCmdSetMode) { + newMode.pixelFormat = msg.data[0]; + newMode.width = msg.data[1]; + newMode.height = msg.data[2]; + newMode.fps = msg.data[3]; + } else if (msg.kind == Message::kCmdSetPixelFormat) { + newMode = m_mode; + newMode.pixelFormat = msg.data[0]; + } else if (msg.kind == Message::kCmdSetResolution) { + newMode = m_mode; + newMode.width = msg.data[0]; + newMode.height = msg.data[1]; + } else if (msg.kind == Message::kCmdSetFPS) { + newMode = m_mode; + newMode.fps = msg.data[0]; + } + + // Check if the device is not connected, if not just apply and leave + if (!m_properties_cached) { + m_mode = newMode; + return CS_OK; + } + + // If the pixel format or resolution changed, we need to disconnect and + // reconnect + if (newMode != m_mode) { + // First check if the new mode is valid + auto newModeType = DeviceCheckModeValid(newMode); + if (!newModeType) { + return CS_UNSUPPORTED_MODE; + } + + m_currentMode = std::move(newModeType); + m_mode = newMode; + lock.unlock(); + if (m_sourceReader) { + DeviceDisconnect(); + DeviceConnect(); + } + m_notifier.NotifySourceVideoMode(*this, newMode); + lock.lock(); + } + + return CS_OK; +} + +bool UsbCameraImpl::DeviceStreamOn() { + if (m_streaming) return false; + if (!m_deviceValid) return false; + m_streaming = true; + m_sourceReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, NULL, + NULL, NULL); + return true; +} + +bool UsbCameraImpl::DeviceStreamOff() { + m_streaming = false; + return true; +} + +void UsbCameraImpl::DeviceCacheMode() { + if (!m_sourceReader) return; + + if (m_windowsVideoModes.size() == 0) return; + + if (!m_currentMode) { + // First, see if our set mode is valid + m_currentMode = DeviceCheckModeValid(m_mode); + if (!m_currentMode) { + if (FAILED(m_sourceReader->GetCurrentMediaType( + MF_SOURCE_READER_FIRST_VIDEO_STREAM, + m_currentMode.GetAddressOf()))) { + return; + } + // Find cached version + DWORD compare = MF_MEDIATYPE_EQUAL_MAJOR_TYPES | + MF_MEDIATYPE_EQUAL_FORMAT_TYPES | + MF_MEDIATYPE_EQUAL_FORMAT_DATA; + auto result = std::find_if( + m_windowsVideoModes.begin(), m_windowsVideoModes.end(), + [this, &compare](std::pair>& input) { + return input.second->IsEqual(m_currentMode.Get(), &compare) == S_OK; + }); + + if (result == m_windowsVideoModes.end()) { + // Default mode is not supported. Grab first supported image + auto&& firstSupported = m_windowsVideoModes[0]; + m_currentMode = firstSupported.second; + std::lock_guard lock(m_mutex); + m_mode = firstSupported.first; + } else { + std::lock_guard lock(m_mutex); + m_mode = result->first; + } + } + } + + DeviceSetMode(); + + m_notifier.NotifySourceVideoMode(*this, m_mode); +} + +CS_StatusValue UsbCameraImpl::DeviceSetMode() { + HRESULT setResult = m_sourceReader->SetCurrentMediaType( + MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, m_currentMode.Get()); + + m_deviceValid = SUCCEEDED(setResult); + + m_imageCallback->SetVideoMode(m_mode); + + switch (setResult) { + case S_OK: + return CS_OK; + break; + case MF_E_INVALIDMEDIATYPE: + break; + case MF_E_INVALIDREQUEST: + break; + case MF_E_INVALIDSTREAMNUMBER: + break; + case MF_E_TOPO_CODEC_NOT_FOUND: + break; + } + + return CS_UNSUPPORTED_MODE; +} + +void UsbCameraImpl::DeviceCacheVideoModes() { + if (!m_sourceReader) return; + + std::vector modes; + m_windowsVideoModes.clear(); + + bool set = false; + + ComPtr nativeType; + int count = 0; + while (true) { + nativeType.Reset(); + auto hr = m_sourceReader->GetNativeMediaType( + MF_SOURCE_READER_FIRST_VIDEO_STREAM, count, nativeType.GetAddressOf()); + if (FAILED(hr)) { + break; + } + GUID guid = {0}; + nativeType->GetGUID(MF_MT_SUBTYPE, &guid); + + auto format = GetFromGUID(guid); + if (format == VideoMode::kUnknown) { + count++; + // Don't put in unknowns + continue; + } + UINT32 width, height; + ::MFGetAttributeSize(nativeType.Get(), MF_MT_FRAME_SIZE, &width, &height); + + UINT32 num, dom; + ::MFGetAttributeRatio(nativeType.Get(), MF_MT_FRAME_RATE, &num, &dom); + + int fps = 30; + + if (dom != 0) { + fps = std::ceil(num / static_cast(dom)); + } + + VideoMode newMode = {format, static_cast(width), + static_cast(height), fps}; + + modes.emplace_back(newMode); + m_windowsVideoModes.emplace_back(newMode, nativeType); + count++; + } + { + std::lock_guard lock(m_mutex); + m_videoModes.swap(modes); + } + m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED); +} + +std::vector EnumerateUsbCameras(CS_Status* status) { + std::vector retval; + + // Ensure we are initialized by grabbing the message pump + // GetMessagePump(); + + ComPtr ppSource; + std::wstring_convert> utf8_conv; + ComPtr pSource; + ComPtr pAttributes; + IMFActivate** ppDevices = nullptr; + + // Create an attribute store to specify the enumeration parameters. + HRESULT hr = MFCreateAttributes(pAttributes.GetAddressOf(), 1); + if (FAILED(hr)) { + goto done; + } + + // Source type: video capture devices + hr = pAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (FAILED(hr)) { + goto done; + } + + // Enumerate devices. + UINT32 count; + hr = MFEnumDeviceSources(pAttributes.Get(), &ppDevices, &count); + if (FAILED(hr)) { + goto done; + } + + if (count == 0) { + hr = E_FAIL; + goto done; + } + + for (UINT32 i = 0; i < count; i++) { + UsbCameraInfo info; + info.dev = i; + WCHAR buf[512]; + ppDevices[i]->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, buf, + sizeof(buf), NULL); + info.name = utf8_conv.to_bytes(buf); + ppDevices[i]->GetString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buf, + sizeof(buf), NULL); + info.path = utf8_conv.to_bytes(buf); + retval.emplace_back(std::move(info)); + } + +done: + pAttributes.Reset(); + + for (DWORD i = 0; i < count; i++) { + if (ppDevices[i]) { + ppDevices[i]->Release(); + ppDevices[i] = nullptr; + } + } + CoTaskMemFree(ppDevices); + pSource.Reset(); + return retval; +} + CS_Source CreateUsbCameraDev(const wpi::Twine& name, int dev, CS_Status* status) { - *status = CS_INVALID_HANDLE; - return 0; + // First check if device exists + auto devices = cs::EnumerateUsbCameras(status); + if (devices.size() > dev) { + return CreateUsbCameraPath(name, devices[dev].path, status); + } + auto& inst = Instance::GetInstance(); + auto source = std::make_shared( + name, inst.logger, inst.notifier, inst.telemetry, dev); + return inst.CreateSource(CS_SOURCE_USB, source); } CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path, CS_Status* status) { - *status = CS_INVALID_HANDLE; - return 0; + auto& inst = Instance::GetInstance(); + auto source = std::make_shared( + name, inst.logger, inst.notifier, inst.telemetry, path); + return inst.CreateSource(CS_SOURCE_USB, source); } std::string GetUsbCameraPath(CS_Source source, CS_Status* status) { - *status = CS_INVALID_HANDLE; - return std::string{}; -} - -std::vector EnumerateUsbCameras(CS_Status* status) { - *status = CS_INVALID_HANDLE; - return std::vector{}; + auto data = Instance::GetInstance().GetSource(source); + if (!data || data->kind != CS_SOURCE_USB) { + *status = CS_INVALID_HANDLE; + return std::string{}; + } + return static_cast(*data->source).GetPath(); } } // namespace cs diff --git a/cscore/src/main/native/windows/UsbCameraImpl.h b/cscore/src/main/native/windows/UsbCameraImpl.h new file mode 100644 index 0000000000..5b730fb6f6 --- /dev/null +++ b/cscore/src/main/native/windows/UsbCameraImpl.h @@ -0,0 +1,184 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CSCORE_USBCAMERAIMPL_H_ +#define CSCORE_USBCAMERAIMPL_H_ + +#include +#include +#include + +#include // NOLINT(build/include_order) + +#include // NOLINT(build/include_order) + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ComCreators.h" +#include "ComPtr.h" +#include "SourceImpl.h" +#include "UsbCameraProperty.h" +#include "WindowsMessagePump.h" + +namespace cs { + +class UsbCameraImpl : public SourceImpl, + public std::enable_shared_from_this { + public: + UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier, + Telemetry& telemetry, const wpi::Twine& path); + UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier, + Telemetry& telemetry, int deviceId); + ~UsbCameraImpl() override; + + void Start(); + + // Property functions + void SetProperty(int property, int value, CS_Status* status) override; + void SetStringProperty(int property, const wpi::Twine& value, + CS_Status* status) override; + + // Standard common camera properties + void SetBrightness(int brightness, CS_Status* status) override; + int GetBrightness(CS_Status* status) const override; + void SetWhiteBalanceAuto(CS_Status* status) override; + void SetWhiteBalanceHoldCurrent(CS_Status* status) override; + void SetWhiteBalanceManual(int value, CS_Status* status) override; + void SetExposureAuto(CS_Status* status) override; + void SetExposureHoldCurrent(CS_Status* status) override; + void SetExposureManual(int value, CS_Status* status) override; + + bool SetVideoMode(const VideoMode& mode, CS_Status* status) override; + bool SetPixelFormat(VideoMode::PixelFormat pixelFormat, + CS_Status* status) override; + bool SetResolution(int width, int height, CS_Status* status) override; + bool SetFPS(int fps, CS_Status* status) override; + + void NumSinksChanged() override; + void NumSinksEnabledChanged() override; + + void ProcessFrame(IMFSample* sample, const VideoMode& mode); + void PostRequestNewFrame(); + + std::string GetPath() { return m_path; } + + // Messages passed to/from camera thread + struct Message { + enum Kind { + kNone = 0, + kCmdSetMode, + kCmdSetPixelFormat, + kCmdSetResolution, + kCmdSetFPS, + kCmdSetProperty, + kCmdSetPropertyStr, + kNumSinksChanged, // no response + kNumSinksEnabledChanged, // no response + // Responses + kOk, + kError + }; + + explicit Message(Kind kind_) + : kind(kind_), from(std::this_thread::get_id()) {} + + Kind kind; + int data[4]; + std::string dataStr; + std::thread::id from; + }; + + protected: + std::unique_ptr CreateEmptyProperty( + const wpi::Twine& name) const override; + + // Cache properties. Immediately successful if properties are already cached. + // If they are not, tries to connect to the camera to do so; returns false and + // sets status to CS_SOURCE_IS_DISCONNECTED if that too fails. + bool CacheProperties(CS_Status* status) const override; + + private: + // The camera processing thread + void CameraThreadMain(); + + LRESULT PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam); + + bool CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr, + bool* connected); + + // Functions used by CameraThreadMain() + void DeviceDisconnect(); + bool DeviceConnect(); + bool DeviceStreamOn(); + bool DeviceStreamOff(); + CS_StatusValue DeviceSetMode(); + void DeviceCacheMode(); + void DeviceCacheProperty(std::unique_ptr rawProp, + IAMVideoProcAmp* pProcAmp); + void DeviceCacheProperties(); + void DeviceCacheVideoModes(); + void DeviceAddProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag, + IAMVideoProcAmp* pProcAmp); + + ComPtr DeviceCheckModeValid(const VideoMode& toCheck); + + // Command helper functions + CS_StatusValue DeviceProcessCommand(std::unique_lock& lock, + Message::Kind msgKind, + const Message* msg); + CS_StatusValue DeviceCmdSetMode(std::unique_lock& lock, + const Message& msg); + CS_StatusValue DeviceCmdSetProperty(std::unique_lock& lock, + const Message& msg); + + // Property helper functions + int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue); + int PercentageToRaw(const UsbCameraProperty& rawProp, int percentValue); + + // + // Variables only used within camera thread + // + bool m_streaming{false}; + bool m_wasStreaming{false}; + bool m_modeSet{false}; + int m_connectVerbose{1}; + bool m_deviceValid{false}; + + ComPtr m_mediaSource; + ComPtr m_sourceReader; + ComPtr m_imageCallback; + std::unique_ptr m_messagePump; + ComPtr m_currentMode; + + // + // Path never changes, so not protected by mutex. + // + std::string m_path; + + std::wstring m_widePath; + int m_deviceId; + + std::vector>> m_windowsVideoModes; +}; + +} // namespace cs + +#endif // CSCORE_USBCAMERAIMPL_H_ diff --git a/cscore/src/main/native/windows/UsbCameraProperty.cpp b/cscore/src/main/native/windows/UsbCameraProperty.cpp new file mode 100644 index 0000000000..73bc6c01fd --- /dev/null +++ b/cscore/src/main/native/windows/UsbCameraProperty.cpp @@ -0,0 +1,69 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "UsbCameraProperty.h" + +using namespace cs; + +UsbCameraProperty::UsbCameraProperty(const wpi::Twine& name_, + tagVideoProcAmpProperty tag, bool autoProp, + IAMVideoProcAmp* pProcAmp, bool* isValid) + : PropertyImpl{autoProp ? name_ + "_auto" : name_} { + this->tag = tag; + this->isAutoProp = autoProp; + long paramVal, paramFlag; // NOLINT(runtime/int) + HRESULT hr; + long minVal, maxVal, stepVal; // NOLINT(runtime/int) + hr = pProcAmp->GetRange(tag, &minVal, &maxVal, &stepVal, ¶mVal, + ¶mFlag); // Unable to get the property, trying to + // return default value + if (SUCCEEDED(hr)) { + minimum = minVal; + maximum = maxVal; + hasMaximum = true; + hasMinimum = true; + defaultValue = paramVal; + step = stepVal; + value = paramVal; + propKind = CS_PropertyKind::CS_PROP_INTEGER; + *isValid = true; + } else { + *isValid = false; + } +} + +bool UsbCameraProperty::DeviceGet(std::unique_lock& lock, + IAMVideoProcAmp* pProcAmp) { + if (!pProcAmp) return true; + + lock.unlock(); + long newValue = 0, paramFlag = 0; // NOLINT(runtime/int) + if (SUCCEEDED(pProcAmp->Get(tag, &newValue, ¶mFlag))) { + lock.lock(); + value = newValue; + return true; + } + + return false; +} +bool UsbCameraProperty::DeviceSet(std::unique_lock& lock, + IAMVideoProcAmp* pProcAmp) const { + return DeviceSet(lock, pProcAmp, value); +} +bool UsbCameraProperty::DeviceSet(std::unique_lock& lock, + IAMVideoProcAmp* pProcAmp, + int newValue) const { + if (!pProcAmp) return true; + + lock.unlock(); + if (SUCCEEDED(pProcAmp->Set(tag, newValue, VideoProcAmp_Flags_Manual))) { + lock.lock(); + return true; + } + + return false; +} diff --git a/cscore/src/main/native/windows/UsbCameraProperty.h b/cscore/src/main/native/windows/UsbCameraProperty.h new file mode 100644 index 0000000000..5d63c73652 --- /dev/null +++ b/cscore/src/main/native/windows/UsbCameraProperty.h @@ -0,0 +1,72 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include + +#include +#include + +#include "PropertyImpl.h" + +namespace cs { + +// Property data +class UsbCameraProperty : public PropertyImpl { + public: + UsbCameraProperty() = default; + explicit UsbCameraProperty(const wpi::Twine& name_) : PropertyImpl{name_} {} + + // Software property constructor + UsbCameraProperty(const wpi::Twine& name_, unsigned id_, + CS_PropertyKind kind_, int minimum_, int maximum_, + int step_, int defaultValue_, int value_) + : PropertyImpl(name_, kind_, minimum_, maximum_, step_, defaultValue_, + value_), + device{false}, + id{id_} {} + + // Normalized property constructor + UsbCameraProperty(const wpi::Twine& name_, int rawIndex_, + const UsbCameraProperty& rawProp, int defaultValue_, + int value_) + : PropertyImpl(name_, rawProp.propKind, 1, defaultValue_, value_), + percentage{true}, + propPair{rawIndex_}, + id{rawProp.id}, + type{rawProp.type} { + hasMinimum = true; + minimum = 0; + hasMaximum = true; + maximum = 100; + } + + UsbCameraProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag, + bool autoProp, IAMVideoProcAmp* pProcAmp, bool* isValid); + + bool DeviceGet(std::unique_lock& lock, IAMVideoProcAmp* pProcAmp); + bool DeviceSet(std::unique_lock& lock, + IAMVideoProcAmp* pProcAmp) const; + bool DeviceSet(std::unique_lock& lock, IAMVideoProcAmp* pProcAmp, + int newValue) const; + + // If this is a device (rather than software) property + bool device{true}; + bool isAutoProp{true}; + tagVideoProcAmpProperty tag; + + // If this is a percentage (rather than raw) property + bool percentage{false}; + + // If not 0, index of corresponding raw/percentage property + int propPair{0}; + + unsigned id{0}; // implementation-level id + int type{0}; // implementation type, not CS_PropertyKind! +}; +} // namespace cs diff --git a/cscore/src/main/native/windows/WindowsMessagePump.cpp b/cscore/src/main/native/windows/WindowsMessagePump.cpp new file mode 100644 index 0000000000..0206bd0d33 --- /dev/null +++ b/cscore/src/main/native/windows/WindowsMessagePump.cpp @@ -0,0 +1,152 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "WindowsMessagePump.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#pragma comment(lib, "Mfplat.lib") +#pragma comment(lib, "Mf.lib") +#pragma comment(lib, "mfuuid.lib") +#pragma comment(lib, "Ole32.lib") +#pragma comment(lib, "User32.lib") + +namespace cs { + +static LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, + LPARAM lParam) { + WindowsMessagePump* pumpContainer; + // Our "this" parameter is passed only during WM_CREATE + // If it is create, store in our user parameter + // Otherwise grab from our user parameter + if (uiMsg == WM_CREATE) { + CREATESTRUCT* pCreate = reinterpret_cast(lParam); + pumpContainer = + reinterpret_cast(pCreate->lpCreateParams); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pumpContainer); + SetWindowPos(hwnd, HWND_MESSAGE, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } else { + pumpContainer = reinterpret_cast( + GetWindowLongPtr(hwnd, GWLP_USERDATA)); + } + + // Run the callback + bool hasCalledBack = false; + LRESULT result; + + if (pumpContainer) { + hasCalledBack = true; + result = pumpContainer->m_callback(hwnd, uiMsg, wParam, lParam); + } + + // Handle a close message + if (uiMsg == WM_CLOSE) { + return HANDLE_WM_CLOSE(hwnd, 0, 0, [](HWND) { PostQuitMessage(0); }); + } + + // Return message, otherwise return the base handler + if (hasCalledBack) { + return result; + } + return DefWindowProc(hwnd, uiMsg, wParam, lParam); +} + +namespace { +struct ClassHolder { + HINSTANCE current_instance; + WNDCLASSEX wx; + const char* class_name = "DUMMY_CLASS"; + ClassHolder() { + current_instance = (HINSTANCE)GetModuleHandle(NULL); + wx = {}; + wx.cbSize = sizeof(WNDCLASSEX); + wx.lpfnWndProc = pWndProc; // function which will handle messages + wx.hInstance = current_instance; + wx.lpszClassName = class_name; + RegisterClassEx(&wx); + } + ~ClassHolder() { UnregisterClass(class_name, current_instance); } +}; +} // namespace + +static std::shared_ptr GetClassHolder() { + static std::shared_ptr clsHolder = + std::make_shared(); + return clsHolder; +} + +WindowsMessagePump::WindowsMessagePump( + std::function callback) { + m_callback = callback; + auto handle = CreateEvent(NULL, true, false, NULL); + m_mainThread = std::thread([=]() { ThreadMain(handle); }); + auto waitResult = WaitForSingleObject(handle, 1000); + if (waitResult == WAIT_OBJECT_0) { + CloseHandle(handle); + } +} + +WindowsMessagePump::~WindowsMessagePump() { + auto res = SendMessage(hwnd, WM_CLOSE, NULL, NULL); + if (m_mainThread.joinable()) m_mainThread.join(); +} + +void WindowsMessagePump::ThreadMain(HANDLE eventHandle) { + // Initialize COM + CoInitializeEx(0, COINIT_MULTITHREADED); + // Initialize MF + MFStartup(MF_VERSION); + + auto classHolder = GetClassHolder(); + hwnd = CreateWindowEx(0, classHolder->class_name, "dummy_name", 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, NULL, this); + + // Register for device notifications + HDEVNOTIFY g_hdevnotify = NULL; + HDEVNOTIFY g_hdevnotify2 = NULL; + + DEV_BROADCAST_DEVICEINTERFACE di = {0}; + di.dbcc_size = sizeof(di); + di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + di.dbcc_classguid = KSCATEGORY_CAPTURE; + + g_hdevnotify = + RegisterDeviceNotification(hwnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE); + + DEV_BROADCAST_DEVICEINTERFACE di2 = {0}; + di2.dbcc_size = sizeof(di2); + di2.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + di2.dbcc_classguid = KSCATEGORY_VIDEO_CAMERA; + + g_hdevnotify2 = + RegisterDeviceNotification(hwnd, &di2, DEVICE_NOTIFY_WINDOW_HANDLE); + + SetEvent(eventHandle); + + MSG Msg; + while (GetMessage(&Msg, NULL, 0, 0) > 0) { + TranslateMessage(&Msg); + DispatchMessage(&Msg); + } + UnregisterDeviceNotification(g_hdevnotify); + UnregisterDeviceNotification(g_hdevnotify2); + + MFShutdown(); + CoUninitialize(); +} + +} // namespace cs diff --git a/cscore/src/main/native/windows/WindowsMessagePump.h b/cscore/src/main/native/windows/WindowsMessagePump.h new file mode 100644 index 0000000000..4638e4e923 --- /dev/null +++ b/cscore/src/main/native/windows/WindowsMessagePump.h @@ -0,0 +1,66 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include + +#include +#include + +namespace cs { +class WindowsMessagePump { + public: + WindowsMessagePump( + std::function callback); + ~WindowsMessagePump(); + + friend LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, + LPARAM lParam); + + template + RetVal SendWindowMessage(UINT msg, FirstParam wParam, SecondParam lParam) { + static_assert(sizeof(FirstParam) <= sizeof(WPARAM), + "First Parameter Does Not Fit"); + static_assert(sizeof(SecondParam) <= sizeof(LPARAM), + "Second Parameter Does Not Fit"); + static_assert(sizeof(RetVal) <= sizeof(LRESULT), + "Return Value Does Not Fit"); + WPARAM firstToSend = 0; + LPARAM secondToSend = 0; + std::memcpy(&firstToSend, &wParam, sizeof(FirstParam)); + std::memcpy(&secondToSend, &lParam, sizeof(SecondParam)); + LRESULT result = SendMessage(hwnd, msg, firstToSend, secondToSend); + RetVal toReturn; + std::memset(&toReturn, 0, sizeof(RetVal)); + std::memcpy(&toReturn, &result, sizeof(RetVal)); + return toReturn; + } + + template + BOOL PostWindowMessage(UINT msg, FirstParam wParam, SecondParam lParam) { + static_assert(sizeof(FirstParam) <= sizeof(WPARAM), + "First Parameter Does Not Fit"); + static_assert(sizeof(SecondParam) <= sizeof(LPARAM), + "Second Parameter Does Not Fit"); + WPARAM firstToSend = 0; + LPARAM secondToSend = 0; + std::memcpy(&firstToSend, &wParam, sizeof(FirstParam)); + std::memcpy(&secondToSend, &lParam, sizeof(SecondParam)); + return PostMessage(hwnd, msg, firstToSend, secondToSend); + } + + private: + void ThreadMain(HANDLE eventHandle); + + HWND hwnd; + std::function m_callback; + + std::thread m_mainThread; +}; +} // namespace cs diff --git a/shared/jni/setupBuild.gradle b/shared/jni/setupBuild.gradle index 5094414390..a22a67d185 100644 --- a/shared/jni/setupBuild.gradle +++ b/shared/jni/setupBuild.gradle @@ -60,6 +60,7 @@ model { if (project.hasProperty('generatedHeaders')) { srcDir generatedHeaders } + include '**/*.h' } } } @@ -118,7 +119,9 @@ model { if (project.hasProperty('generatedHeaders')) { srcDir generatedHeaders } + include '**/*.h' } + } } binaries.all { @@ -154,6 +157,7 @@ model { if (project.hasProperty('generatedHeaders')) { srcDir generatedHeaders } + include '**/*.h' } } }