mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-20 00:51:42 +00:00
shared_from_this will assert if the shared pointer is in the middle of being destructed. Because we access shared_from_this in the message pump, this can easily occur. The solution is to grab the weak pointer, manually attempt to lock it, and only continue if that succeeds. The message pump is already synchronized to the usb camera being destructed, so this is a fine behavior.
1188 lines
35 KiB
C++
1188 lines
35 KiB
C++
// Copyright (c) FIRST and other WPILib contributors.
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
// the WPILib BSD license file in the root directory of this project.
|
|
|
|
#define _WINSOCKAPI_
|
|
#include "UsbCameraImpl.h"
|
|
|
|
#include <ks.h>
|
|
#include <ksmedia.h>
|
|
#include <mfapi.h>
|
|
#include <mferror.h>
|
|
#include <mfidl.h>
|
|
#include <shlwapi.h>
|
|
#include <windowsx.h>
|
|
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <Dbt.h>
|
|
#include <Dshow.h>
|
|
#include <Windows.h>
|
|
#include <opencv2/core/core.hpp>
|
|
#include <opencv2/highgui/highgui.hpp>
|
|
#include <opencv2/imgproc/imgproc.hpp>
|
|
#include <wpi/ConvertUTF.h>
|
|
#include <wpi/MemAlloc.h>
|
|
#include <wpi/SmallString.h>
|
|
#include <wpi/StringExtras.h>
|
|
#include <wpi/timestamp.h>
|
|
|
|
#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")
|
|
|
|
#pragma warning(disable : 4996 4018 26451)
|
|
|
|
static constexpr int NewImageMessage = 0x0400 + 4488;
|
|
static constexpr int SetCameraMessage = 0x0400 + 254;
|
|
static constexpr int WaitForStartupMessage = 0x0400 + 294;
|
|
static constexpr int PumpReadyMessage = 0x0400 + 330;
|
|
|
|
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(std::string_view name, wpi::Logger& logger,
|
|
Notifier& notifier, Telemetry& telemetry,
|
|
std::string_view path)
|
|
: SourceImpl{name, logger, notifier, telemetry}, m_path{path} {
|
|
wpi::SmallVector<wchar_t, 128> wideStorage;
|
|
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
|
|
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
|
|
m_deviceId = -1;
|
|
StartMessagePump();
|
|
}
|
|
|
|
UsbCameraImpl::UsbCameraImpl(std::string_view name, wpi::Logger& logger,
|
|
Notifier& notifier, Telemetry& telemetry,
|
|
int deviceId)
|
|
: SourceImpl{name, logger, notifier, telemetry}, m_deviceId(deviceId) {
|
|
StartMessagePump();
|
|
}
|
|
|
|
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<CS_Status, Message::Kind, Message*>(
|
|
SetCameraMessage, msg.kind, &msg);
|
|
*status = result;
|
|
}
|
|
void UsbCameraImpl::SetStringProperty(int property, std::string_view value,
|
|
CS_Status* status) {
|
|
Message msg{Message::kCmdSetPropertyStr};
|
|
msg.data[0] = property;
|
|
msg.dataStr = value;
|
|
auto result =
|
|
m_messagePump->SendWindowMessage<CS_Status, Message::Kind, Message*>(
|
|
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<CS_Status, Message::Kind, Message*>(
|
|
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<CS_Status, Message::Kind, Message*>(
|
|
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<CS_Status, Message::Kind, Message*>(
|
|
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<CS_Status, Message::Kind, Message*>(
|
|
SetCameraMessage, msg.kind, &msg);
|
|
*status = result;
|
|
return result == 0;
|
|
}
|
|
|
|
void UsbCameraImpl::NumSinksChanged() {
|
|
m_messagePump->PostWindowMessage<Message::Kind, Message*>(
|
|
SetCameraMessage, Message::kNumSinksChanged, nullptr);
|
|
}
|
|
void UsbCameraImpl::NumSinksEnabledChanged() {
|
|
m_messagePump->PostWindowMessage<Message::Kind, Message*>(
|
|
SetCameraMessage, Message::kNumSinksEnabledChanged, nullptr);
|
|
}
|
|
|
|
void UsbCameraImpl::SetPath(std::string_view path, CS_Status* status) {
|
|
Message msg{Message::kCmdSetPath};
|
|
msg.dataStr = path;
|
|
auto result =
|
|
m_messagePump->SendWindowMessage<CS_Status, Message::Kind, Message*>(
|
|
SetCameraMessage, msg.kind, &msg);
|
|
*status = result;
|
|
}
|
|
|
|
std::string UsbCameraImpl::GetPath() const {
|
|
std::scoped_lock lock(m_mutex);
|
|
return m_path;
|
|
}
|
|
|
|
void UsbCameraImpl::StartMessagePump() {
|
|
m_messagePump = std::make_unique<WindowsMessagePump>(
|
|
[this](HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
|
|
return this->PumpMain(hwnd, uiMsg, wParam, lParam);
|
|
});
|
|
}
|
|
|
|
void UsbCameraImpl::Start() {
|
|
m_messagePump->PostWindowMessage(PumpReadyMessage, nullptr, nullptr);
|
|
}
|
|
|
|
void UsbCameraImpl::PostRequestNewFrame() {
|
|
m_messagePump->PostWindowMessage(NewImageMessage, nullptr, nullptr);
|
|
}
|
|
|
|
bool UsbCameraImpl::CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
|
|
bool* connected) {
|
|
DEV_BROADCAST_DEVICEINTERFACE_A* 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<DEV_BROADCAST_DEVICEINTERFACE_A*>(pHdr);
|
|
|
|
if (wpi::equals_lower(m_path, pDi->dbcc_name)) {
|
|
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(std::string_view name) {
|
|
if (wpi::starts_with(name, "raw_"))
|
|
name = wpi::substr(name, 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<IMFMediaBuffer> 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;
|
|
}
|
|
|
|
BYTE* ptr = NULL;
|
|
LONG pitch = 0;
|
|
DWORD length = 0;
|
|
|
|
// First try to access using Lock2DSize, then try Lock2D, then fallback
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfmediabuffer-lock
|
|
|
|
ComPtr<IMF2DBuffer> buffer2d = buf.As<IMF2DBuffer>();
|
|
if (buffer2d) {
|
|
BYTE* scanline0 = nullptr;
|
|
HRESULT result;
|
|
ComPtr<IMF2DBuffer2> buffer2d2 = buf.As<IMF2DBuffer2>();
|
|
if (buffer2d2) {
|
|
BYTE* datastart;
|
|
result = buffer2d2->Lock2DSize(MF2DBuffer_LockFlags_Read, &scanline0,
|
|
&pitch, &datastart, &length);
|
|
} else {
|
|
result = buffer2d->Lock2D(&scanline0, &pitch);
|
|
}
|
|
if (SUCCEEDED(result)) {
|
|
BOOL isContiguous;
|
|
if (pitch > 0 && SUCCEEDED(buffer2d->IsContiguousFormat(&isContiguous)) &&
|
|
isContiguous &&
|
|
(length || SUCCEEDED(buffer2d->GetContiguousLength(&length)))) {
|
|
// Use the buffer pointer.
|
|
ptr = scanline0;
|
|
} else {
|
|
// Release the buffer and fall back to Lock().
|
|
buffer2d->Unlock2D();
|
|
}
|
|
}
|
|
}
|
|
if (ptr == NULL) {
|
|
buffer2d = nullptr;
|
|
DWORD maxsize = 0;
|
|
if (!SUCCEEDED(buf->Lock(&ptr, &maxsize, &length))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
cv::Mat tmpMat;
|
|
std::unique_ptr<Image> dest;
|
|
bool doFinalSet = true;
|
|
|
|
switch (mode.pixelFormat) {
|
|
case cs::VideoMode::PixelFormat::kMJPEG: {
|
|
// Special case
|
|
PutFrame(VideoMode::kMJPEG, mode.width, mode.height,
|
|
{reinterpret_cast<char*>(ptr), length}, 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 (buffer2d) {
|
|
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 PumpReadyMessage:
|
|
// Pump Created and ready to go
|
|
DeviceConnect();
|
|
break;
|
|
case WaitForStartupMessage:
|
|
DeviceConnect();
|
|
return CS_OK;
|
|
case WM_DEVICECHANGE: {
|
|
// Device potentially changed
|
|
PDEV_BROADCAST_HDR parameter =
|
|
reinterpret_cast<PDEV_BROADCAST_HDR>(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_A* pDi =
|
|
reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE_A*>(parameter);
|
|
m_path = pDi->dbcc_name;
|
|
wpi::SmallVector<wchar_t, 128> wideStorage;
|
|
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
|
|
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
|
|
} 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<Message*>(lParam);
|
|
Message::Kind msgKind = static_cast<Message::Kind>(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 (IsEqualGUID(guid, MFVideoFormat_NV12)) {
|
|
// GrayScale
|
|
return cs::VideoMode::PixelFormat::kGray;
|
|
} else if (IsEqualGUID(guid, MFVideoFormat_YUY2)) {
|
|
return cs::VideoMode::PixelFormat::kYUYV;
|
|
} else if (IsEqualGUID(guid, MFVideoFormat_RGB24)) {
|
|
return cs::VideoMode::PixelFormat::kBGR;
|
|
} else if (IsEqualGUID(guid, MFVideoFormat_MJPG)) {
|
|
return cs::VideoMode::PixelFormat::kMJPEG;
|
|
} else if (IsEqualGUID(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;
|
|
}
|
|
auto weakThis = weak_from_this();
|
|
auto sharedThis = weakThis.lock();
|
|
if (sharedThis) {
|
|
m_imageCallback = CreateSourceReaderCB(sharedThis, m_mode);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
m_sourceReader =
|
|
CreateSourceReader(m_mediaSource.Get(), m_imageCallback.Get());
|
|
|
|
if (!m_sourceReader) {
|
|
m_mediaSource.Reset();
|
|
return false;
|
|
}
|
|
|
|
CS_Status st = 0;
|
|
auto devices = EnumerateUsbCameras(&st);
|
|
|
|
for (auto&& device : devices) {
|
|
if (device.path == m_path) {
|
|
SetDescription(device.name);
|
|
}
|
|
}
|
|
|
|
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<PropertyImpl> UsbCameraImpl::CreateEmptyProperty(
|
|
std::string_view name) const {
|
|
return nullptr;
|
|
}
|
|
|
|
bool UsbCameraImpl::CacheProperties(CS_Status* status) const {
|
|
// Wait for Camera Thread to be started
|
|
auto result = m_messagePump->SendWindowMessage<CS_Status>(
|
|
WaitForStartupMessage, nullptr, nullptr);
|
|
*status = result;
|
|
if (*status != CS_OK) {
|
|
return false;
|
|
}
|
|
if (!m_properties_cached) {
|
|
*status = CS_SOURCE_IS_DISCONNECTED;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename TagProperty, typename IAM>
|
|
void UsbCameraImpl::DeviceAddProperty(std::string_view name_, TagProperty tag,
|
|
IAM* pProcAmp) {
|
|
// First see if properties exist
|
|
bool isValid = false;
|
|
auto property = std::make_unique<UsbCameraProperty>(name_, tag, false,
|
|
pProcAmp, &isValid);
|
|
if (isValid) {
|
|
DeviceCacheProperty(std::move(property), m_sourceReader.Get());
|
|
}
|
|
}
|
|
|
|
template void UsbCameraImpl::DeviceAddProperty(std::string_view name_,
|
|
tagVideoProcAmpProperty tag,
|
|
IAMVideoProcAmp* pProcAmp);
|
|
|
|
template void UsbCameraImpl::DeviceAddProperty(std::string_view name_,
|
|
tagCameraControlProperty tag,
|
|
IAMCameraControl* pProcAmp);
|
|
|
|
#define CREATEPROPERTY(val) \
|
|
DeviceAddProperty(#val, VideoProcAmp_##val, pProcAmp);
|
|
|
|
#define CREATECONTROLPROPERTY(val) \
|
|
DeviceAddProperty(#val, CameraControl_##val, pCamControl);
|
|
|
|
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();
|
|
}
|
|
|
|
IAMCameraControl* pCamControl = NULL;
|
|
|
|
if (SUCCEEDED(m_sourceReader->GetServiceForStream(
|
|
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
|
IID_PPV_ARGS(&pCamControl)))) {
|
|
CREATECONTROLPROPERTY(Pan)
|
|
CREATECONTROLPROPERTY(Tilt)
|
|
CREATECONTROLPROPERTY(Roll)
|
|
CREATECONTROLPROPERTY(Zoom)
|
|
CREATECONTROLPROPERTY(Exposure)
|
|
CREATECONTROLPROPERTY(Iris)
|
|
CREATECONTROLPROPERTY(Focus)
|
|
pCamControl->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<UsbCameraProperty> rawProp, IMFSourceReader* 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<UsbCameraProperty> perProp;
|
|
if (IsPercentageProperty(rawProp->name)) {
|
|
perProp =
|
|
std::make_unique<UsbCameraProperty>(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<UsbCameraProperty*>(GetProperty(*rawIndex));
|
|
|
|
int* perIndex = perProp ? &m_properties[perProp->name] : nullptr;
|
|
bool newPer = !perIndex || *perIndex == 0;
|
|
UsbCameraProperty* oldPerProp =
|
|
newPer ? nullptr
|
|
: static_cast<UsbCameraProperty*>(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 && perIndex)
|
|
NotifyPropertyCreated(*perIndex, *perPropPtr);
|
|
}
|
|
|
|
CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
|
|
std::unique_lock<wpi::mutex>& 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 if (msgKind == Message::kCmdSetPath) {
|
|
{
|
|
std::scoped_lock lock(m_mutex);
|
|
m_path = msg->dataStr;
|
|
wpi::SmallVector<wchar_t, 128> wideStorage;
|
|
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
|
|
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
|
|
}
|
|
DeviceDisconnect();
|
|
DeviceConnect();
|
|
return CS_OK;
|
|
} else {
|
|
return CS_OK;
|
|
}
|
|
}
|
|
|
|
CS_StatusValue UsbCameraImpl::DeviceCmdSetProperty(
|
|
std::unique_lock<wpi::mutex>& lock, const Message& msg) {
|
|
bool setString = (msg.kind == Message::kCmdSetPropertyStr);
|
|
int property = msg.data[0];
|
|
int value = msg.data[1];
|
|
std::string_view valueStr = msg.dataStr;
|
|
|
|
// Look up
|
|
auto prop = static_cast<UsbCameraProperty*>(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<UsbCameraProperty*>(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 {
|
|
if (!prop->DeviceSet(lock, m_sourceReader.Get())) {
|
|
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<IMFMediaType> UsbCameraImpl::DeviceCheckModeValid(
|
|
const VideoMode& toCheck) {
|
|
// Find the matching mode
|
|
auto match =
|
|
std::find_if(m_windowsVideoModes.begin(), m_windowsVideoModes.end(),
|
|
[&](std::pair<VideoMode, ComPtr<IMFMediaType>>& input) {
|
|
return input.first == toCheck;
|
|
});
|
|
|
|
if (match == m_windowsVideoModes.end()) {
|
|
return nullptr;
|
|
}
|
|
return match->second;
|
|
}
|
|
|
|
CS_StatusValue UsbCameraImpl::DeviceCmdSetMode(
|
|
std::unique_lock<wpi::mutex>& 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;
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 26110)
|
|
lock.unlock();
|
|
#pragma warning(pop)
|
|
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<VideoMode, ComPtr<IMFMediaType>>& 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::scoped_lock lock(m_mutex);
|
|
m_mode = firstSupported.first;
|
|
} else {
|
|
std::scoped_lock 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<VideoMode> modes;
|
|
m_windowsVideoModes.clear();
|
|
|
|
bool set = false;
|
|
|
|
ComPtr<IMFMediaType> 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<double>(dom));
|
|
}
|
|
|
|
VideoMode newMode = {format, static_cast<int>(width),
|
|
static_cast<int>(height), fps};
|
|
|
|
modes.emplace_back(newMode);
|
|
m_windowsVideoModes.emplace_back(newMode, nativeType);
|
|
count++;
|
|
}
|
|
{
|
|
std::scoped_lock lock(m_mutex);
|
|
m_videoModes.swap(modes);
|
|
}
|
|
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
|
|
}
|
|
|
|
static void ParseVidAndPid(std::string_view path, int* pid, int* vid) {
|
|
auto vidIndex = wpi::find_lower(path, "vid_");
|
|
auto pidIndex = wpi::find_lower(path, "pid_");
|
|
|
|
if (vidIndex != std::string_view::npos) {
|
|
auto vidSlice = wpi::slice(path, vidIndex + 4, vidIndex + 8);
|
|
if (auto v = wpi::parse_integer<uint16_t>(vidSlice, 16)) {
|
|
*vid = v.value();
|
|
}
|
|
}
|
|
|
|
if (pidIndex != std::string_view::npos) {
|
|
auto pidSlice = wpi::slice(path, pidIndex + 4, pidIndex + 8);
|
|
if (auto v = wpi::parse_integer<uint16_t>(pidSlice, 16)) {
|
|
*pid = v.value();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
|
std::vector<UsbCameraInfo> retval;
|
|
|
|
// Ensure we are initialized by grabbing the message pump
|
|
// GetMessagePump();
|
|
|
|
wpi::SmallString<128> storage;
|
|
WCHAR buf[512];
|
|
ComPtr<IMFAttributes> pAttributes;
|
|
IMFActivate** ppDevices = nullptr;
|
|
UINT32 count = 0;
|
|
|
|
// 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.
|
|
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;
|
|
|
|
UINT32 characters = 0;
|
|
ppDevices[i]->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, buf,
|
|
sizeof(buf) / sizeof(WCHAR), &characters);
|
|
storage.clear();
|
|
wpi::sys::windows::UTF16ToUTF8(buf, characters, storage);
|
|
info.name = storage.string();
|
|
ppDevices[i]->GetString(
|
|
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buf,
|
|
sizeof(buf) / sizeof(WCHAR), &characters);
|
|
storage.clear();
|
|
wpi::sys::windows::UTF16ToUTF8(buf, characters, storage);
|
|
info.path = storage.string();
|
|
|
|
// Try to parse path from symbolic link
|
|
ParseVidAndPid(info.path, &info.productId, &info.vendorId);
|
|
|
|
retval.emplace_back(std::move(info));
|
|
}
|
|
|
|
done:
|
|
pAttributes.Reset();
|
|
|
|
if (ppDevices) {
|
|
for (DWORD i = 0; i < count; i++) {
|
|
if (ppDevices[i]) {
|
|
ppDevices[i]->Release();
|
|
ppDevices[i] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
CoTaskMemFree(ppDevices);
|
|
return retval;
|
|
}
|
|
|
|
CS_Source CreateUsbCameraDev(std::string_view name, int dev,
|
|
CS_Status* status) {
|
|
// 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<UsbCameraImpl>(
|
|
name, inst.logger, inst.notifier, inst.telemetry, dev);
|
|
return inst.CreateSource(CS_SOURCE_USB, source);
|
|
}
|
|
|
|
CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path,
|
|
CS_Status* status) {
|
|
auto& inst = Instance::GetInstance();
|
|
auto source = std::make_shared<UsbCameraImpl>(
|
|
name, inst.logger, inst.notifier, inst.telemetry, path);
|
|
return inst.CreateSource(CS_SOURCE_USB, source);
|
|
}
|
|
|
|
void SetUsbCameraPath(CS_Source source, std::string_view path,
|
|
CS_Status* status) {
|
|
auto data = Instance::GetInstance().GetSource(source);
|
|
if (!data || data->kind != CS_SOURCE_USB) {
|
|
*status = CS_INVALID_HANDLE;
|
|
return;
|
|
}
|
|
static_cast<UsbCameraImpl&>(*data->source).SetPath(path, status);
|
|
}
|
|
|
|
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
|
auto data = Instance::GetInstance().GetSource(source);
|
|
if (!data || data->kind != CS_SOURCE_USB) {
|
|
*status = CS_INVALID_HANDLE;
|
|
return std::string{};
|
|
}
|
|
return static_cast<UsbCameraImpl&>(*data->source).GetPath();
|
|
}
|
|
|
|
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
|
UsbCameraInfo info;
|
|
auto data = Instance::GetInstance().GetSource(source);
|
|
if (!data || data->kind != CS_SOURCE_USB) {
|
|
*status = CS_INVALID_HANDLE;
|
|
return info;
|
|
}
|
|
|
|
info.path = static_cast<UsbCameraImpl&>(*data->source).GetPath();
|
|
wpi::SmallVector<char, 64> buf;
|
|
info.name = static_cast<UsbCameraImpl&>(*data->source).GetDescription(buf);
|
|
ParseVidAndPid(info.path, &info.productId, &info.vendorId);
|
|
info.dev = -1; // We have lost dev information by this point in time.
|
|
return info;
|
|
}
|
|
|
|
} // namespace cs
|