Add support for USB Webcams on Windows (#1390)

This commit is contained in:
Thad House
2018-11-17 23:16:35 -08:00
committed by Peter Johnson
parent 70a66fc943
commit 69cb53b51b
16 changed files with 2082 additions and 21 deletions

View File

@@ -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
}

View File

@@ -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 ->

View File

@@ -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
};
/**

View File

@@ -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); }
};
/**

View File

@@ -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 <mfapi.h>
#include <mfidl.h>
#include <shlwapi.h>
#include <windowsx.h>
#include <Windows.h>
#include "UsbCameraImpl.h"
// https://github.com/opencv/opencv/blob/master/modules/videoio/src/cap_msmf.cpp
#include <mfidl.h>
#include <mfapi.h>
#include <Dbt.h>
#include <ks.h>
#include <ksmedia.h>
#include <mfreadwrite.h>
#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<cs::UsbCameraImpl> 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<SourceReaderCB> CreateSourceReaderCB(
std::weak_ptr<cs::UsbCameraImpl> source, const cs::VideoMode& mode) {
SourceReaderCB* ptr = new SourceReaderCB(source, mode);
ComPtr<SourceReaderCB> sourceReaderCB;
sourceReaderCB.Attach(ptr);
return sourceReaderCB;
}
ComPtr<IMFMediaSource> CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink) {
ComPtr<IMFAttributes> pAttributes;
ComPtr<IMFMediaSource> 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<IMFSourceReader> CreateSourceReader(IMFMediaSource* mediaSource,
IMFSourceReaderCallback* callback) {
HRESULT hr = S_OK;
ComPtr<IMFAttributes> pAttributes;
ComPtr<IMFSourceReader> 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

View File

@@ -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 <mfidl.h>
#include <mfreadwrite.h>
#include <memory>
#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<cs::UsbCameraImpl> 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<cs::UsbCameraImpl>(); }
private:
// Destructor is private. Caller should call Release.
virtual ~SourceReaderCB() {}
void NotifyError(HRESULT hr);
ULONG m_nRefCount;
std::weak_ptr<cs::UsbCameraImpl> m_source;
cs::VideoMode m_mode;
};
ComPtr<SourceReaderCB> CreateSourceReaderCB(
std::weak_ptr<cs::UsbCameraImpl> source, const cs::VideoMode& mode);
ComPtr<IMFSourceReader> CreateSourceReader(IMFMediaSource* mediaSource,
IMFSourceReaderCallback* callback);
ComPtr<IMFMediaSource> CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink);
} // namespace cs

View File

@@ -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 <comdef.h>
#include <shlwapi.h> // QISearch
#include <cassert>
namespace cs {
template <typename Interface>
class RemoveAddRefRelease : public Interface {
ULONG __stdcall AddRef();
ULONG __stdcall Release();
virtual ~RemoveAddRefRelease();
};
template <typename Interface>
class ComPtr {
public:
template <typename T>
friend class ComPtr;
ComPtr(std::nullptr_t = nullptr) noexcept {} // NOLINT(runtime/explicit)
ComPtr(const ComPtr& other) noexcept : m_ptr(other.m_ptr) {
InternalAddRef();
}
template <typename T>
ComPtr(const ComPtr<T>& other) noexcept : m_ptr(other.m_ptr) {
InternalAddRef();
}
template <typename T>
ComPtr(ComPtr<T>&& 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 <typename T>
ComPtr& operator=(const ComPtr<T>& other) noexcept {
InternalCopy(other.m_ptr);
return *this;
}
template <typename T>
ComPtr& operator=(ComPtr<T>&& 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 <typename T>
ComPtr<T> As() const noexcept {
ComPtr<T> temp;
m_ptr->QueryInterface(temp.GetAddressOf());
return temp;
}
RemoveAddRefRelease<Interface>* operator->() const noexcept {
return static_cast<RemoveAddRefRelease<Interface>*>(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 <typename T>
void InternalMove(ComPtr<T>& other) noexcept {
if (m_ptr != other.m_ptr) {
InternalRelease();
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
}
};
template <typename Interface>
void swap(
ComPtr<Interface>& left,
ComPtr<Interface>& right) noexcept { // NOLINT(build/include_what_you_use)
left.Swap(right);
}
} // namespace cs

View File

@@ -7,14 +7,55 @@
#include "NetworkListener.h"
#include <winsock2.h> // NOLINT(build/include_order)
#include <windows.h> // NOLINT(build/include_order)
#include <ws2def.h> // NOLINT(build/include_order)
#include <ws2ipdef.h> // NOLINT(build/include_order)
#include <iphlpapi.h> // NOLINT(build/include_order)
#include <netioapi.h> // 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<Notifier*>(callerContext);
notifier->NotifyNetworkInterfacesChanged();
}
NetworkListener::~NetworkListener() {}
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier)
: m_impl(std::make_unique<Impl>(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);
}
}

View File

@@ -5,12 +5,115 @@
/* the project. */
/*----------------------------------------------------------------------------*/
#include <winsock2.h> // NOLINT(build/include_order)
#include <iphlpapi.h> // NOLINT(build/include_order)
#include <ws2tcpip.h> // NOLINT(build/include_order)
#include <uv.h>
#include <iostream>
#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<std::string> GetNetworkInterfaces() {
return std::vector<std::string>{}; // TODO
uv_interface_address_t* adrs;
int counts = 0;
std::vector<std::string> 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<IP_ADAPTER_ADDRESSES*>(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<sockaddr_in*>(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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <ks.h> // NOLINT(build/include_order)
#include <ksmedia.h> // NOLINT(build/include_order)
#include <atomic>
#include <memory>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <Dbt.h>
#include <wpi/STLExtras.h>
#include <wpi/SmallVector.h>
#include <wpi/Twine.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#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<UsbCameraImpl> {
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<PropertyImpl> 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<UsbCameraProperty> rawProp,
IAMVideoProcAmp* pProcAmp);
void DeviceCacheProperties();
void DeviceCacheVideoModes();
void DeviceAddProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag,
IAMVideoProcAmp* pProcAmp);
ComPtr<IMFMediaType> DeviceCheckModeValid(const VideoMode& toCheck);
// Command helper functions
CS_StatusValue DeviceProcessCommand(std::unique_lock<wpi::mutex>& lock,
Message::Kind msgKind,
const Message* msg);
CS_StatusValue DeviceCmdSetMode(std::unique_lock<wpi::mutex>& lock,
const Message& msg);
CS_StatusValue DeviceCmdSetProperty(std::unique_lock<wpi::mutex>& 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<IMFMediaSource> m_mediaSource;
ComPtr<IMFSourceReader> m_sourceReader;
ComPtr<SourceReaderCB> m_imageCallback;
std::unique_ptr<cs::WindowsMessagePump> m_messagePump;
ComPtr<IMFMediaType> m_currentMode;
//
// Path never changes, so not protected by mutex.
//
std::string m_path;
std::wstring m_widePath;
int m_deviceId;
std::vector<std::pair<VideoMode, ComPtr<IMFMediaType>>> m_windowsVideoModes;
};
} // namespace cs
#endif // CSCORE_USBCAMERAIMPL_H_

View File

@@ -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, &paramVal,
&paramFlag); // 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<wpi::mutex>& lock,
IAMVideoProcAmp* pProcAmp) {
if (!pProcAmp) return true;
lock.unlock();
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
if (SUCCEEDED(pProcAmp->Get(tag, &newValue, &paramFlag))) {
lock.lock();
value = newValue;
return true;
}
return false;
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
IAMVideoProcAmp* pProcAmp) const {
return DeviceSet(lock, pProcAmp, value);
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& 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;
}

View File

@@ -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 <memory>
#include <Dshow.h>
#include <wpi/mutex.h>
#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<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp);
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
IAMVideoProcAmp* pProcAmp) const;
bool DeviceSet(std::unique_lock<wpi::mutex>& 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

View File

@@ -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 <ks.h>
#include <ksmedia.h>
#include <mfapi.h>
#include <mfidl.h>
#include <windows.h>
#include <windowsx.h>
#include <memory>
#include <Dbt.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")
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<CREATESTRUCT*>(lParam);
pumpContainer =
reinterpret_cast<WindowsMessagePump*>(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<WindowsMessagePump*>(
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<ClassHolder> GetClassHolder() {
static std::shared_ptr<ClassHolder> clsHolder =
std::make_shared<ClassHolder>();
return clsHolder;
}
WindowsMessagePump::WindowsMessagePump(
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> 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

View File

@@ -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 <windows.h>
#include <functional>
#include <thread>
namespace cs {
class WindowsMessagePump {
public:
WindowsMessagePump(
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback);
~WindowsMessagePump();
friend LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
LPARAM lParam);
template <typename RetVal = LRESULT, typename FirstParam = WPARAM,
typename SecondParam = LPARAM>
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 <typename FirstParam = WPARAM, typename SecondParam = LPARAM>
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<LRESULT(HWND, UINT, WPARAM, LPARAM)> m_callback;
std::thread m_mainThread;
};
} // namespace cs

View File

@@ -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'
}
}
}