Files
allwpilib/cscore/src/main/native/linux/UsbCameraImpl.cpp
Peter Johnson fe570e000c [wpiutil] Replace llvm filesystem with C++17 filesystem (#3401)
Use ghc::filesystem as fill on older GCC (e.g. RoboRIO).
This can be removed once all GCC platforms have upgraded to 8.1 or later.

File open functionality has been retained from LLVM but moved to "fs" namespace
and tweaked for improved consistency with std::filesystem (e.g. error_code is
passed by reference instead of returned).

Also update WPILibC's Filesystem functions to return std::string.
2021-06-01 21:50:35 -07:00

1709 lines
48 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.
#include "UsbCameraImpl.h"
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <sys/eventfd.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <memory>
#include <wpi/MemAlloc.h>
#include <wpi/SmallString.h>
#include <wpi/fs.h>
#include <wpi/raw_ostream.h>
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Instance.h"
#include "JpegUtil.h"
#include "Log.h"
#include "Notifier.h"
#include "Telemetry.h"
#include "UsbUtil.h"
#include "cscore_cpp.h"
using namespace cs;
static constexpr char const* kPropWbAuto = "white_balance_temperature_auto";
static constexpr char const* kPropWbValue = "white_balance_temperature";
static constexpr char const* kPropExAuto = "exposure_auto";
static constexpr char const* kPropExValue = "exposure_absolute";
static constexpr char const* kPropBrValue = "brightness";
static constexpr char const* kPropConnectVerbose = "connect_verbose";
static constexpr unsigned kPropConnectVerboseId = 0;
// Conversions v4l2_fract time per frame from/to frames per second (fps)
static inline int FractToFPS(const struct v4l2_fract& timeperframe) {
return (1.0 * timeperframe.denominator) / timeperframe.numerator;
}
static inline struct v4l2_fract FPSToFract(int fps) {
struct v4l2_fract timeperframe;
timeperframe.numerator = 1;
timeperframe.denominator = fps;
return timeperframe;
}
// Conversion from v4l2_format pixelformat to VideoMode::PixelFormat
static VideoMode::PixelFormat ToPixelFormat(__u32 pixelFormat) {
switch (pixelFormat) {
case V4L2_PIX_FMT_MJPEG:
return VideoMode::kMJPEG;
case V4L2_PIX_FMT_YUYV:
return VideoMode::kYUYV;
case V4L2_PIX_FMT_RGB565:
return VideoMode::kRGB565;
case V4L2_PIX_FMT_BGR24:
return VideoMode::kBGR;
case V4L2_PIX_FMT_GREY:
return VideoMode::kGray;
default:
return VideoMode::kUnknown;
}
}
// Conversion from VideoMode::PixelFormat to v4l2_format pixelformat
static __u32 FromPixelFormat(VideoMode::PixelFormat pixelFormat) {
switch (pixelFormat) {
case VideoMode::kMJPEG:
return V4L2_PIX_FMT_MJPEG;
case VideoMode::kYUYV:
return V4L2_PIX_FMT_YUYV;
case VideoMode::kRGB565:
return V4L2_PIX_FMT_RGB565;
case VideoMode::kBGR:
return V4L2_PIX_FMT_BGR24;
case VideoMode::kGray:
return V4L2_PIX_FMT_GREY;
default:
return 0;
}
}
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_absolute" || name == "exposure_time_absolute";
}
static constexpr const int quirkLifeCamHd3000[] = {
5, 10, 20, 39, 78, 156, 312, 625, 1250, 2500, 5000, 10000, 20000};
static constexpr char const* quirkPS3EyePropExAuto = "auto_exposure";
static constexpr char const* quirkPS3EyePropExValue = "exposure";
static constexpr const int quirkPS3EyePropExAutoOn = 0;
static constexpr const int quirkPS3EyePropExAutoOff = 1;
static constexpr char const* quirkPiCameraPropExAuto = "auto_exposure";
static constexpr char const* quirkPiCameraPropExValue =
"exposure_time_absolute";
static constexpr const int quirkPiCameraPropExAutoOn = 0;
static constexpr const int quirkPiCameraPropExAutoOff = 1;
int UsbCameraImpl::RawToPercentage(const UsbCameraProperty& rawProp,
int rawValue) {
// LifeCam exposure setting quirk
if (m_lifecam_exposure && rawProp.name == "raw_exposure_absolute" &&
rawProp.minimum == 5 && rawProp.maximum == 20000) {
int nelems = wpi::array_lengthof(quirkLifeCamHd3000);
for (int i = 0; i < nelems; ++i) {
if (rawValue < quirkLifeCamHd3000[i]) {
return 100.0 * i / nelems;
}
}
return 100;
}
return 100.0 * (rawValue - rawProp.minimum) /
(rawProp.maximum - rawProp.minimum);
}
int UsbCameraImpl::PercentageToRaw(const UsbCameraProperty& rawProp,
int percentValue) {
// LifeCam exposure setting quirk
if (m_lifecam_exposure && rawProp.name == "raw_exposure_absolute" &&
rawProp.minimum == 5 && rawProp.maximum == 20000) {
int nelems = wpi::array_lengthof(quirkLifeCamHd3000);
int ndx = nelems * percentValue / 100.0;
if (ndx < 0) {
ndx = 0;
}
if (ndx >= nelems) {
ndx = nelems - 1;
}
return quirkLifeCamHd3000[ndx];
}
return rawProp.minimum +
(rawProp.maximum - rawProp.minimum) * (percentValue / 100.0);
}
static bool GetVendorProduct(int dev, int* vendor, int* product) {
wpi::SmallString<64> ifpath;
{
wpi::raw_svector_ostream oss{ifpath};
oss << "/sys/class/video4linux/video" << dev << "/device/modalias";
}
int fd = open(ifpath.c_str(), O_RDONLY);
if (fd < 0) {
return false;
}
char readBuf[128];
ssize_t n = read(fd, readBuf, sizeof(readBuf));
close(fd);
if (n <= 0) {
return false;
}
wpi::StringRef readStr{readBuf};
if (readStr.substr(readStr.find('v'))
.substr(1, 4)
.getAsInteger(16, *vendor)) {
return false;
}
if (readStr.substr(readStr.find('p'))
.substr(1, 4)
.getAsInteger(16, *product)) {
return false;
}
return true;
}
static bool GetDescriptionSysV4L(int dev, std::string* desc) {
wpi::SmallString<64> ifpath;
{
wpi::raw_svector_ostream oss{ifpath};
oss << "/sys/class/video4linux/video" << dev << "/device/interface";
}
int fd = open(ifpath.c_str(), O_RDONLY);
if (fd < 0) {
return false;
}
char readBuf[128];
ssize_t n = read(fd, readBuf, sizeof(readBuf));
close(fd);
if (n <= 0) {
return false;
}
*desc = wpi::StringRef(readBuf, n).rtrim();
return true;
}
static bool GetDescriptionIoctl(const char* cpath, std::string* desc) {
int fd = open(cpath, O_RDWR);
if (fd < 0) {
return false;
}
struct v4l2_capability vcap;
std::memset(&vcap, 0, sizeof(vcap));
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) < 0) {
close(fd);
return false;
}
close(fd);
wpi::StringRef card{reinterpret_cast<const char*>(vcap.card)};
// try to convert "UVC Camera (0000:0000)" into a better name
int vendor = 0;
int product = 0;
if (card.startswith("UVC Camera (") &&
!card.substr(12, 4).getAsInteger(16, vendor) &&
!card.substr(17, 4).getAsInteger(16, product)) {
wpi::SmallString<64> card2Buf;
wpi::StringRef card2 = GetUsbNameFromId(vendor, product, card2Buf);
if (!card2.empty()) {
*desc = card2;
return true;
}
}
*desc = card;
return true;
}
static bool IsVideoCaptureDevice(const char* cpath) {
int fd = open(cpath, O_RDWR);
if (fd < 0) {
return false;
}
struct v4l2_capability vcap;
std::memset(&vcap, 0, sizeof(vcap));
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) < 0) {
close(fd);
return false;
}
close(fd);
return (vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE) != 0 &&
(vcap.capabilities & V4L2_CAP_STREAMING) != 0 &&
((vcap.capabilities & V4L2_CAP_DEVICE_CAPS) == 0 ||
((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) != 0 &&
(vcap.device_caps & V4L2_CAP_STREAMING) != 0));
}
static int GetDeviceNum(const char* cpath) {
fs::path path{cpath};
// it might be a symlink; if so, find the symlink target (e.g. /dev/videoN),
// add that to the list and make it the keypath
if (fs::is_symlink(path)) {
path = fs::canonical(path);
}
auto fn = path.filename();
if (!wpi::StringRef{fn}.startswith("video")) {
return -1;
}
int dev = -1;
if (wpi::StringRef{fn}.substr(5).getAsInteger(10, dev)) {
return -1;
}
return dev;
}
static std::string GetDescriptionImpl(const char* cpath) {
std::string rv;
int dev = GetDeviceNum(cpath);
if (dev >= 0) {
// Sometimes the /sys tree gives a better name.
if (GetDescriptionSysV4L(dev, &rv)) {
return rv;
}
}
// Otherwise use an ioctl to query the caps and get the card name
if (GetDescriptionIoctl(cpath, &rv)) {
return rv;
}
return std::string{};
}
UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
const wpi::Twine& path)
: SourceImpl{name, logger, notifier, telemetry},
m_fd{-1},
m_command_fd{eventfd(0, 0)},
m_active{true},
m_path{path.str()} {
SetDescription(GetDescriptionImpl(m_path.c_str()));
SetQuirks();
CreateProperty(kPropConnectVerbose, [] {
return std::make_unique<UsbCameraProperty>(kPropConnectVerbose,
kPropConnectVerboseId,
CS_PROP_INTEGER, 0, 1, 1, 1, 1);
});
}
UsbCameraImpl::~UsbCameraImpl() {
m_active = false;
// Just in case anyone is waiting...
m_responseCv.notify_all();
// Send message to wake up thread; select timeout will wake us up anyway,
// but this speeds shutdown.
Send(Message{Message::kNone});
// join camera thread
if (m_cameraThread.joinable()) {
m_cameraThread.join();
}
// close command fd
int fd = m_command_fd.exchange(-1);
if (fd >= 0) {
close(fd);
}
}
static inline void DoFdSet(int fd, fd_set* set, int* nfds) {
if (fd >= 0) {
FD_SET(fd, set);
if ((fd + 1) > *nfds) {
*nfds = fd + 1;
}
}
}
void UsbCameraImpl::Start() {
// Kick off the camera thread
m_cameraThread = std::thread(&UsbCameraImpl::CameraThreadMain, this);
}
void UsbCameraImpl::CameraThreadMain() {
// We want to be notified on file creation and deletion events in the device
// path. This is used to detect disconnects and reconnects.
std::unique_ptr<wpi::raw_fd_istream> notify_is;
int notify_fd = inotify_init();
if (notify_fd >= 0) {
// need to make a copy as dirname can modify it
wpi::SmallString<64> pathCopy{m_path};
pathCopy.push_back('\0');
if (inotify_add_watch(notify_fd, dirname(pathCopy.data()),
IN_CREATE | IN_DELETE) < 0) {
close(notify_fd);
notify_fd = -1;
} else {
notify_is = std::make_unique<wpi::raw_fd_istream>(
notify_fd, true, sizeof(struct inotify_event) + NAME_MAX + 1);
}
}
bool notified = (notify_fd < 0); // treat as always notified if cannot notify
// Get the basename for later notify use
wpi::SmallString<64> pathCopy{m_path};
pathCopy.push_back('\0');
wpi::SmallString<64> base{basename(pathCopy.data())};
// Used to restart streaming on reconnect
bool wasStreaming = false;
// Default to not streaming
m_streaming = false;
while (m_active) {
// If not connected, try to reconnect
if (m_fd < 0) {
DeviceConnect();
}
// Make copies of fd's in case they go away
int command_fd = m_command_fd.load();
int fd = m_fd.load();
if (!m_active) {
break;
}
// Reset notified flag and restart streaming if necessary
if (fd >= 0) {
notified = (notify_fd < 0);
if (wasStreaming && !m_streaming) {
DeviceStreamOn();
wasStreaming = false;
}
}
// Turn off streaming if not enabled, and turn it on if enabled
if (m_streaming && !IsEnabled()) {
DeviceStreamOff();
} else if (!m_streaming && IsEnabled()) {
DeviceStreamOn();
}
// The select timeout can be long unless we're trying to reconnect
struct timeval tv;
if (fd < 0 && notified) {
tv.tv_sec = 0;
tv.tv_usec = 300000;
} else {
tv.tv_sec = 2;
tv.tv_usec = 0;
}
// select on applicable read descriptors
int nfds = 0;
fd_set readfds;
FD_ZERO(&readfds);
DoFdSet(command_fd, &readfds, &nfds);
if (m_streaming) {
DoFdSet(fd, &readfds, &nfds);
}
DoFdSet(notify_fd, &readfds, &nfds);
if (select(nfds, &readfds, nullptr, nullptr, &tv) < 0) {
SERROR("select(): " << std::strerror(errno));
break; // XXX: is this the right thing to do here?
}
// Double-check to see if we're shutting down
if (!m_active) {
break;
}
// Handle notify events
if (notify_fd >= 0 && FD_ISSET(notify_fd, &readfds)) {
SDEBUG4("notify event");
struct inotify_event event;
do {
// Read the event structure
notify_is->read(&event, sizeof(event));
// Read the event name
wpi::SmallString<64> raw_name;
raw_name.resize(event.len);
notify_is->read(raw_name.data(), event.len);
// If the name is what we expect...
wpi::StringRef name{raw_name.c_str()};
SDEBUG4("got event on '" << name << "' (" << name.size()
<< ") compare to '" << base << "' ("
<< base.size() << ") mask " << event.mask);
if (name == base) {
if ((event.mask & IN_DELETE) != 0) {
wasStreaming = m_streaming;
DeviceStreamOff();
DeviceDisconnect();
} else if ((event.mask & IN_CREATE) != 0) {
notified = true;
}
}
} while (!notify_is->has_error() &&
notify_is->in_avail() >= sizeof(event));
continue;
}
// Handle commands
if (command_fd >= 0 && FD_ISSET(command_fd, &readfds)) {
SDEBUG4("got command");
// Read it to clear
eventfd_t val;
eventfd_read(command_fd, &val);
DeviceProcessCommands();
continue;
}
// Handle frames
if (m_streaming && fd >= 0 && FD_ISSET(fd, &readfds)) {
SDEBUG4("grabbing image");
// Dequeue buffer
struct v4l2_buffer buf;
std::memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (DoIoctl(fd, VIDIOC_DQBUF, &buf) != 0) {
SWARNING("could not dequeue buffer");
wasStreaming = m_streaming;
DeviceStreamOff();
DeviceDisconnect();
notified = true; // device wasn't deleted, just error'ed
continue; // will reconnect
}
if ((buf.flags & V4L2_BUF_FLAG_ERROR) == 0) {
SDEBUG4("got image size=" << buf.bytesused << " index=" << buf.index);
if (buf.index >= kNumBuffers || !m_buffers[buf.index].m_data) {
SWARNING("invalid buffer" << buf.index);
continue;
}
wpi::StringRef image{
static_cast<const char*>(m_buffers[buf.index].m_data),
static_cast<size_t>(buf.bytesused)};
int width = m_mode.width;
int height = m_mode.height;
bool good = true;
if (m_mode.pixelFormat == VideoMode::kMJPEG &&
!GetJpegSize(image, &width, &height)) {
SWARNING("invalid JPEG image received from camera");
good = false;
}
if (good) {
PutFrame(static_cast<VideoMode::PixelFormat>(m_mode.pixelFormat),
width, height, image, wpi::Now()); // TODO: time
}
}
// Requeue buffer
if (DoIoctl(fd, VIDIOC_QBUF, &buf) != 0) {
SWARNING("could not requeue buffer");
wasStreaming = m_streaming;
DeviceStreamOff();
DeviceDisconnect();
notified = true; // device wasn't deleted, just error'ed
continue; // will reconnect
}
}
}
// close camera connection
DeviceStreamOff();
DeviceDisconnect();
}
void UsbCameraImpl::DeviceDisconnect() {
int fd = m_fd.exchange(-1);
if (fd < 0) {
return; // already disconnected
}
// Unmap buffers
for (int i = 0; i < kNumBuffers; ++i) {
m_buffers[i] = UsbCameraBuffer{};
}
// Close device
close(fd);
// Notify
SetConnected(false);
}
void UsbCameraImpl::DeviceConnect() {
if (m_fd >= 0) {
return;
}
if (m_connectVerbose) {
SINFO("Connecting to USB camera on " << m_path);
}
// Try to open the device
SDEBUG3("opening device");
int fd = open(m_path.c_str(), O_RDWR);
if (fd < 0) {
return;
}
m_fd = fd;
// Get capabilities
SDEBUG3("getting capabilities");
struct v4l2_capability vcap;
std::memset(&vcap, 0, sizeof(vcap));
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) >= 0) {
m_capabilities = vcap.capabilities;
if (m_capabilities & V4L2_CAP_DEVICE_CAPS) {
m_capabilities = vcap.device_caps;
}
}
// Get or restore video mode
if (!m_properties_cached) {
SDEBUG3("caching properties");
DeviceCacheProperties();
DeviceCacheVideoModes();
DeviceCacheMode();
m_properties_cached = true;
} else {
SDEBUG3("restoring video mode");
DeviceSetMode();
DeviceSetFPS();
// Restore settings
SDEBUG3("restoring settings");
std::unique_lock lock2(m_mutex);
for (size_t i = 0; i < m_propertyData.size(); ++i) {
const auto prop =
static_cast<const UsbCameraProperty*>(m_propertyData[i].get());
if (!prop || !prop->valueSet || !prop->device || prop->percentage) {
continue;
}
if (!prop->DeviceSet(lock2, m_fd)) {
SWARNING("failed to set property " << prop->name);
}
}
}
// Request buffers
SDEBUG3("allocating buffers");
struct v4l2_requestbuffers rb;
std::memset(&rb, 0, sizeof(rb));
rb.count = kNumBuffers;
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
if (DoIoctl(fd, VIDIOC_REQBUFS, &rb) != 0) {
SWARNING("could not allocate buffers");
close(fd);
m_fd = -1;
return;
}
// Map buffers
SDEBUG3("mapping buffers");
for (int i = 0; i < kNumBuffers; ++i) {
struct v4l2_buffer buf;
std::memset(&buf, 0, sizeof(buf));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (DoIoctl(fd, VIDIOC_QUERYBUF, &buf) != 0) {
SWARNING("could not query buffer " << i);
close(fd);
m_fd = -1;
return;
}
SDEBUG4("buf " << i << " length=" << buf.length
<< " offset=" << buf.m.offset);
m_buffers[i] = UsbCameraBuffer(fd, buf.length, buf.m.offset);
if (!m_buffers[i].m_data) {
SWARNING("could not map buffer " << i);
// release other buffers
for (int j = 0; j < i; ++j) {
m_buffers[j] = UsbCameraBuffer{};
}
close(fd);
m_fd = -1;
return;
}
SDEBUG4("buf " << i << " address=" << m_buffers[i].m_data);
}
// Update description (as it may have changed)
SetDescription(GetDescriptionImpl(m_path.c_str()));
// Update quirks settings
SetQuirks();
// Notify
SetConnected(true);
}
bool UsbCameraImpl::DeviceStreamOn() {
if (m_streaming) {
return false; // ignore if already enabled
}
int fd = m_fd.load();
if (fd < 0) {
return false;
}
// Queue buffers
SDEBUG3("queuing buffers");
for (int i = 0; i < kNumBuffers; ++i) {
struct v4l2_buffer buf;
std::memset(&buf, 0, sizeof(buf));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (DoIoctl(fd, VIDIOC_QBUF, &buf) != 0) {
SWARNING("could not queue buffer " << i);
return false;
}
}
// Turn stream on
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (TryIoctl(fd, VIDIOC_STREAMON, &type) < 0) {
if (errno == ENOSPC) {
// indicates too much USB bandwidth requested
SERROR(
"could not start streaming due to USB bandwidth limitations; try a "
"lower resolution or a different pixel format (VIDIOC_STREAMON: "
"No space left on device)");
} else {
// some other error
SERROR("ioctl VIDIOC_STREAMON failed: " << std::strerror(errno));
}
return false;
}
SDEBUG4("enabled streaming");
m_streaming = true;
return true;
}
bool UsbCameraImpl::DeviceStreamOff() {
if (!m_streaming) {
return false; // ignore if already disabled
}
int fd = m_fd.load();
if (fd < 0) {
return false;
}
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (DoIoctl(fd, VIDIOC_STREAMOFF, &type) != 0) {
return false;
}
SDEBUG4("disabled streaming");
m_streaming = false;
return true;
}
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];
m_modeSetPixelFormat = true;
m_modeSetResolution = true;
m_modeSetFPS = true;
} else if (msg.kind == Message::kCmdSetPixelFormat) {
newMode = m_mode;
newMode.pixelFormat = msg.data[0];
m_modeSetPixelFormat = true;
} else if (msg.kind == Message::kCmdSetResolution) {
newMode = m_mode;
newMode.width = msg.data[0];
newMode.height = msg.data[1];
m_modeSetResolution = true;
} else if (msg.kind == Message::kCmdSetFPS) {
newMode = m_mode;
newMode.fps = msg.data[0];
m_modeSetFPS = true;
}
// If the pixel format or resolution changed, we need to disconnect and
// reconnect
if (newMode.pixelFormat != m_mode.pixelFormat ||
newMode.width != m_mode.width || newMode.height != m_mode.height) {
m_mode = newMode;
lock.unlock();
bool wasStreaming = m_streaming;
if (wasStreaming) {
DeviceStreamOff();
}
if (m_fd >= 0) {
DeviceDisconnect();
DeviceConnect();
}
if (wasStreaming) {
DeviceStreamOn();
}
m_notifier.NotifySourceVideoMode(*this, newMode);
lock.lock();
} else if (newMode.fps != m_mode.fps) {
m_mode = newMode;
lock.unlock();
// Need to stop streaming to set FPS
bool wasStreaming = m_streaming;
if (wasStreaming) {
DeviceStreamOff();
}
DeviceSetFPS();
if (wasStreaming) {
DeviceStreamOn();
}
m_notifier.NotifySourceVideoMode(*this, newMode);
lock.lock();
}
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];
wpi::StringRef 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_fd, value, valueStr)) {
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;
}
CS_StatusValue UsbCameraImpl::DeviceCmdSetPath(
std::unique_lock<wpi::mutex>& lock, const Message& msg) {
m_path = msg.dataStr;
lock.unlock();
// disconnect and reconnect
bool wasStreaming = m_streaming;
if (wasStreaming) {
DeviceStreamOff();
}
if (m_fd >= 0) {
DeviceDisconnect();
DeviceConnect();
}
if (wasStreaming) {
DeviceStreamOn();
}
lock.lock();
return CS_OK;
}
CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
std::unique_lock<wpi::mutex>& lock, const Message& msg) {
if (msg.kind == Message::kCmdSetMode ||
msg.kind == Message::kCmdSetPixelFormat ||
msg.kind == Message::kCmdSetResolution ||
msg.kind == Message::kCmdSetFPS) {
return DeviceCmdSetMode(lock, msg);
} else if (msg.kind == Message::kCmdSetProperty ||
msg.kind == Message::kCmdSetPropertyStr) {
return DeviceCmdSetProperty(lock, msg);
} else if (msg.kind == Message::kNumSinksChanged ||
msg.kind == Message::kNumSinksEnabledChanged) {
return CS_OK;
} else if (msg.kind == Message::kCmdSetPath) {
return DeviceCmdSetPath(lock, msg);
} else {
return CS_OK;
}
}
void UsbCameraImpl::DeviceProcessCommands() {
std::unique_lock lock(m_mutex);
if (m_commands.empty()) {
return;
}
while (!m_commands.empty()) {
auto msg = std::move(m_commands.back());
m_commands.pop_back();
CS_StatusValue status = DeviceProcessCommand(lock, msg);
if (msg.kind != Message::kNumSinksChanged &&
msg.kind != Message::kNumSinksEnabledChanged) {
m_responses.emplace_back(msg.from, status);
}
}
lock.unlock();
m_responseCv.notify_all();
}
void UsbCameraImpl::DeviceSetMode() {
int fd = m_fd.load();
if (fd < 0) {
return;
}
struct v4l2_format vfmt;
std::memset(&vfmt, 0, sizeof(vfmt));
#ifdef V4L2_CAP_EXT_PIX_FORMAT
vfmt.fmt.pix.priv = (m_capabilities & V4L2_CAP_EXT_PIX_FORMAT) != 0
? V4L2_PIX_FMT_PRIV_MAGIC
: 0;
#endif
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vfmt.fmt.pix.pixelformat =
FromPixelFormat(static_cast<VideoMode::PixelFormat>(m_mode.pixelFormat));
if (vfmt.fmt.pix.pixelformat == 0) {
SWARNING("could not set format " << m_mode.pixelFormat
<< ", defaulting to MJPEG");
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
}
vfmt.fmt.pix.width = m_mode.width;
vfmt.fmt.pix.height = m_mode.height;
vfmt.fmt.pix.field = V4L2_FIELD_ANY;
if (DoIoctl(fd, VIDIOC_S_FMT, &vfmt) != 0) {
SWARNING("could not set format " << m_mode.pixelFormat << " res "
<< m_mode.width << "x" << m_mode.height);
} else {
SINFO("set format " << m_mode.pixelFormat << " res " << m_mode.width << "x"
<< m_mode.height);
}
}
void UsbCameraImpl::DeviceSetFPS() {
int fd = m_fd.load();
if (fd < 0) {
return;
}
struct v4l2_streamparm parm;
std::memset(&parm, 0, sizeof(parm));
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (DoIoctl(fd, VIDIOC_G_PARM, &parm) != 0) {
return;
}
if ((parm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) == 0) {
return;
}
std::memset(&parm, 0, sizeof(parm));
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.timeperframe = FPSToFract(m_mode.fps);
if (DoIoctl(fd, VIDIOC_S_PARM, &parm) != 0) {
SWARNING("could not set FPS to " << m_mode.fps);
} else {
SINFO("set FPS to " << m_mode.fps);
}
}
void UsbCameraImpl::DeviceCacheMode() {
int fd = m_fd.load();
if (fd < 0) {
return;
}
// Get format
struct v4l2_format vfmt;
std::memset(&vfmt, 0, sizeof(vfmt));
#ifdef V4L2_CAP_EXT_PIX_FORMAT
vfmt.fmt.pix.priv = (m_capabilities & V4L2_CAP_EXT_PIX_FORMAT) != 0
? V4L2_PIX_FMT_PRIV_MAGIC
: 0;
#endif
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (DoIoctl(fd, VIDIOC_G_FMT, &vfmt) != 0) {
SERROR("could not read current video mode");
std::scoped_lock lock(m_mutex);
m_mode = VideoMode{VideoMode::kMJPEG, 320, 240, 30};
return;
}
VideoMode::PixelFormat pixelFormat = ToPixelFormat(vfmt.fmt.pix.pixelformat);
int width = vfmt.fmt.pix.width;
int height = vfmt.fmt.pix.height;
// Get FPS
int fps = 0;
struct v4l2_streamparm parm;
std::memset(&parm, 0, sizeof(parm));
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (TryIoctl(fd, VIDIOC_G_PARM, &parm) == 0) {
if (parm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) {
fps = FractToFPS(parm.parm.capture.timeperframe);
}
}
// Update format with user changes.
bool formatChanged = false;
if (m_modeSetPixelFormat) {
// User set pixel format
if (pixelFormat != m_mode.pixelFormat) {
formatChanged = true;
pixelFormat = static_cast<VideoMode::PixelFormat>(m_mode.pixelFormat);
}
} else {
// Default to MJPEG
if (pixelFormat != VideoMode::kMJPEG) {
formatChanged = true;
pixelFormat = VideoMode::kMJPEG;
}
}
if (m_modeSetResolution) {
// User set resolution
if (width != m_mode.width || height != m_mode.height) {
formatChanged = true;
width = m_mode.width;
height = m_mode.height;
}
} else {
// Default to lowest known resolution (based on number of total pixels)
int numPixels = width * height;
for (const auto& mode : m_videoModes) {
if (mode.pixelFormat != pixelFormat) {
continue;
}
int numPixelsHere = mode.width * mode.height;
if (numPixelsHere < numPixels) {
formatChanged = true;
numPixels = numPixelsHere;
width = mode.width;
height = mode.height;
}
}
}
// Update FPS with user changes
bool fpsChanged = false;
if (m_modeSetFPS && fps != m_mode.fps) {
fpsChanged = true;
fps = m_mode.fps;
}
// Save to global mode
{
std::scoped_lock lock(m_mutex);
m_mode.pixelFormat = pixelFormat;
m_mode.width = width;
m_mode.height = height;
m_mode.fps = fps;
}
if (formatChanged) {
DeviceSetMode();
}
if (fpsChanged) {
DeviceSetFPS();
}
m_notifier.NotifySourceVideoMode(*this, m_mode);
}
void UsbCameraImpl::DeviceCacheProperty(
std::unique_ptr<UsbCameraProperty> rawProp) {
// 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, m_fd)) {
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, m_fd)) {
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);
}
}
void UsbCameraImpl::DeviceCacheProperties() {
int fd = m_fd.load();
if (fd < 0) {
return;
}
#ifdef V4L2_CTRL_FLAG_NEXT_COMPOUND
constexpr __u32 nextFlags =
V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
#else
constexpr __u32 nextFlags = V4L2_CTRL_FLAG_NEXT_CTRL;
#endif
__u32 id = nextFlags;
while (auto prop = UsbCameraProperty::DeviceQuery(fd, &id)) {
DeviceCacheProperty(std::move(prop));
id |= nextFlags;
}
if (id == nextFlags) {
// try just enumerating standard...
for (id = V4L2_CID_BASE; id < V4L2_CID_LASTP1; ++id) {
if (auto prop = UsbCameraProperty::DeviceQuery(fd, &id)) {
DeviceCacheProperty(std::move(prop));
}
}
// ... and custom controls
std::unique_ptr<UsbCameraProperty> prop;
for (id = V4L2_CID_PRIVATE_BASE;
(prop = UsbCameraProperty::DeviceQuery(fd, &id)); ++id) {
DeviceCacheProperty(std::move(prop));
}
}
}
void UsbCameraImpl::DeviceCacheVideoModes() {
int fd = m_fd.load();
if (fd < 0) {
return;
}
std::vector<VideoMode> modes;
// Pixel formats
struct v4l2_fmtdesc fmt;
std::memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for (fmt.index = 0; TryIoctl(fd, VIDIOC_ENUM_FMT, &fmt) >= 0; ++fmt.index) {
VideoMode::PixelFormat pixelFormat = ToPixelFormat(fmt.pixelformat);
if (pixelFormat == VideoMode::kUnknown) {
continue;
}
// Frame sizes
struct v4l2_frmsizeenum frmsize;
std::memset(&frmsize, 0, sizeof(frmsize));
frmsize.pixel_format = fmt.pixelformat;
for (frmsize.index = 0; TryIoctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) >= 0;
++frmsize.index) {
if (frmsize.type != V4L2_FRMSIZE_TYPE_DISCRETE) {
continue;
}
// Frame intervals
struct v4l2_frmivalenum frmival;
std::memset(&frmival, 0, sizeof(frmival));
frmival.pixel_format = fmt.pixelformat;
frmival.width = frmsize.discrete.width;
frmival.height = frmsize.discrete.height;
for (frmival.index = 0;
TryIoctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) >= 0;
++frmival.index) {
if (frmival.type != V4L2_FRMIVAL_TYPE_DISCRETE) {
continue;
}
modes.emplace_back(pixelFormat,
static_cast<int>(frmsize.discrete.width),
static_cast<int>(frmsize.discrete.height),
FractToFPS(frmival.discrete));
}
}
}
// The Pi camera reports mode ranges, which we don't currently handle, so only
// provide a set of discrete modes; list based on
// https://picamera.readthedocs.io/en/release-1.10/fov.html
if (modes.empty() && m_picamera) {
for (VideoMode::PixelFormat pixelFormat :
{VideoMode::kYUYV, VideoMode::kMJPEG, VideoMode::kBGR}) {
modes.emplace_back(pixelFormat, 1920, 1080, 30);
modes.emplace_back(pixelFormat, 2592, 1944, 15);
modes.emplace_back(pixelFormat, 1296, 972, 42);
modes.emplace_back(pixelFormat, 1296, 730, 49);
modes.emplace_back(pixelFormat, 640, 480, 90);
modes.emplace_back(pixelFormat, 320, 240, 90);
modes.emplace_back(pixelFormat, 160, 120, 90);
modes.emplace_back(pixelFormat, 640, 480, 60);
modes.emplace_back(pixelFormat, 320, 240, 60);
modes.emplace_back(pixelFormat, 160, 120, 60);
}
}
{
std::scoped_lock lock(m_mutex);
m_videoModes.swap(modes);
}
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
}
CS_StatusValue UsbCameraImpl::SendAndWait(Message&& msg) const {
int fd = m_command_fd.load();
// exit early if not possible to signal
if (fd < 0) {
return CS_SOURCE_IS_DISCONNECTED;
}
auto from = msg.from;
// Add the message to the command queue
{
std::scoped_lock lock(m_mutex);
m_commands.emplace_back(std::move(msg));
}
// Signal the camera thread
if (eventfd_write(fd, 1) < 0) {
return CS_SOURCE_IS_DISCONNECTED;
}
std::unique_lock lock(m_mutex);
while (m_active) {
// Did we get a response to *our* request?
auto it =
std::find_if(m_responses.begin(), m_responses.end(),
[=](const std::pair<std::thread::id, CS_StatusValue>& r) {
return r.first == from;
});
if (it != m_responses.end()) {
// Yes, remove it from the vector and we're done.
auto rv = it->second;
m_responses.erase(it);
return rv;
}
// No, keep waiting for a response
m_responseCv.wait(lock);
}
return CS_SOURCE_IS_DISCONNECTED;
}
void UsbCameraImpl::Send(Message&& msg) const {
int fd = m_command_fd.load();
// exit early if not possible to signal
if (fd < 0) {
return;
}
// Add the message to the command queue
{
std::scoped_lock lock(m_mutex);
m_commands.emplace_back(std::move(msg));
}
// Signal the camera thread
eventfd_write(fd, 1);
}
std::unique_ptr<PropertyImpl> UsbCameraImpl::CreateEmptyProperty(
const wpi::Twine& name) const {
return std::make_unique<UsbCameraProperty>(name);
}
bool UsbCameraImpl::CacheProperties(CS_Status* status) const {
// Wake up camera thread; this will try to reconnect
*status = SendAndWait(Message{Message::kNone});
if (*status != CS_OK) {
return false;
}
if (!m_properties_cached) {
*status = CS_SOURCE_IS_DISCONNECTED;
return false;
}
return true;
}
void UsbCameraImpl::SetQuirks() {
wpi::SmallString<128> descbuf;
wpi::StringRef desc = GetDescription(descbuf);
m_lifecam_exposure =
desc.endswith("LifeCam HD-3000") || desc.endswith("LifeCam Cinema (TM)");
m_picamera = desc.startswith("mmal service");
int deviceNum = GetDeviceNum(m_path.c_str());
if (deviceNum >= 0) {
int vendorId, productId;
if (GetVendorProduct(deviceNum, &vendorId, &productId)) {
m_ps3eyecam_exposure = vendorId == 0x1415 && productId == 0x2000;
}
}
}
void UsbCameraImpl::SetProperty(int property, int value, CS_Status* status) {
Message msg{Message::kCmdSetProperty};
msg.data[0] = property;
msg.data[1] = value;
*status = SendAndWait(std::move(msg));
}
void UsbCameraImpl::SetStringProperty(int property, const wpi::Twine& value,
CS_Status* status) {
Message msg{Message::kCmdSetPropertyStr};
msg.data[0] = property;
msg.dataStr = value.str();
*status = SendAndWait(std::move(msg));
}
void UsbCameraImpl::SetBrightness(int brightness, CS_Status* status) {
if (brightness > 100) {
brightness = 100;
} else if (brightness < 0) {
brightness = 0;
}
SetProperty(GetPropertyIndex(kPropBrValue), brightness, status);
}
int UsbCameraImpl::GetBrightness(CS_Status* status) const {
return GetProperty(GetPropertyIndex(kPropBrValue), status);
}
void UsbCameraImpl::SetWhiteBalanceAuto(CS_Status* status) {
SetProperty(GetPropertyIndex(kPropWbAuto), 1, status); // auto
}
void UsbCameraImpl::SetWhiteBalanceHoldCurrent(CS_Status* status) {
SetProperty(GetPropertyIndex(kPropWbAuto), 0, status); // manual
}
void UsbCameraImpl::SetWhiteBalanceManual(int value, CS_Status* status) {
SetProperty(GetPropertyIndex(kPropWbAuto), 0, status); // manual
SetProperty(GetPropertyIndex(kPropWbValue), value, status);
}
void UsbCameraImpl::SetExposureAuto(CS_Status* status) {
// auto; this is an enum value
if (m_ps3eyecam_exposure) {
SetProperty(GetPropertyIndex(quirkPS3EyePropExAuto),
quirkPS3EyePropExAutoOn, status);
} else if (m_picamera) {
SetProperty(GetPropertyIndex(quirkPiCameraPropExAuto),
quirkPiCameraPropExAutoOn, status);
} else {
SetProperty(GetPropertyIndex(kPropExAuto), 3, status);
}
}
void UsbCameraImpl::SetExposureHoldCurrent(CS_Status* status) {
if (m_ps3eyecam_exposure) {
SetProperty(GetPropertyIndex(quirkPS3EyePropExAuto),
quirkPS3EyePropExAutoOff, status); // manual
} else if (m_picamera) {
SetProperty(GetPropertyIndex(quirkPiCameraPropExAuto),
quirkPiCameraPropExAutoOff, status); // manual
} else {
SetProperty(GetPropertyIndex(kPropExAuto), 1, status); // manual
}
}
void UsbCameraImpl::SetExposureManual(int value, CS_Status* status) {
if (m_ps3eyecam_exposure) {
SetProperty(GetPropertyIndex(quirkPS3EyePropExAuto),
quirkPS3EyePropExAutoOff, status); // manual
} else if (m_picamera) {
SetProperty(GetPropertyIndex(quirkPiCameraPropExAuto),
quirkPiCameraPropExAutoOff, status); // manual
} else {
SetProperty(GetPropertyIndex(kPropExAuto), 1, status); // manual
}
if (value > 100) {
value = 100;
} else if (value < 0) {
value = 0;
}
if (m_ps3eyecam_exposure) {
SetProperty(GetPropertyIndex(quirkPS3EyePropExValue), value, status);
} else if (m_picamera) {
SetProperty(GetPropertyIndex(quirkPiCameraPropExValue), value, status);
} else {
SetProperty(GetPropertyIndex(kPropExValue), value, status);
}
}
bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
Message msg{Message::kCmdSetMode};
msg.data[0] = mode.pixelFormat;
msg.data[1] = mode.width;
msg.data[2] = mode.height;
msg.data[3] = mode.fps;
*status = SendAndWait(std::move(msg));
return *status == CS_OK;
}
bool UsbCameraImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat,
CS_Status* status) {
Message msg{Message::kCmdSetPixelFormat};
msg.data[0] = pixelFormat;
*status = SendAndWait(std::move(msg));
return *status == CS_OK;
}
bool UsbCameraImpl::SetResolution(int width, int height, CS_Status* status) {
Message msg{Message::kCmdSetResolution};
msg.data[0] = width;
msg.data[1] = height;
*status = SendAndWait(std::move(msg));
return *status == CS_OK;
}
bool UsbCameraImpl::SetFPS(int fps, CS_Status* status) {
Message msg{Message::kCmdSetFPS};
msg.data[0] = fps;
*status = SendAndWait(std::move(msg));
return *status == CS_OK;
}
void UsbCameraImpl::NumSinksChanged() {
Send(Message{Message::kNumSinksChanged});
}
void UsbCameraImpl::NumSinksEnabledChanged() {
Send(Message{Message::kNumSinksEnabledChanged});
}
void UsbCameraImpl::SetPath(const wpi::Twine& path, CS_Status* status) {
Message msg{Message::kCmdSetPath};
msg.dataStr = path.str();
*status = SendAndWait(std::move(msg));
}
std::string UsbCameraImpl::GetPath() const {
std::scoped_lock lock(m_mutex);
return m_path;
}
namespace cs {
CS_Source CreateUsbCameraDev(const wpi::Twine& name, int dev,
CS_Status* status) {
wpi::SmallString<32> path;
wpi::raw_svector_ostream oss{path};
oss << "/dev/video" << dev;
return CreateUsbCameraPath(name, oss.str(), status);
}
CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSource(CS_SOURCE_USB, std::make_shared<UsbCameraImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, path));
}
void SetUsbCameraPath(CS_Source source, const wpi::Twine& 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();
}
static const char* symlinkDirs[] = {"/dev/v4l/by-id", "/dev/v4l/by-path"};
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;
}
std::string keypath = static_cast<UsbCameraImpl&>(*data->source).GetPath();
info.path = keypath;
// device number
info.dev = GetDeviceNum(keypath.c_str());
// description
info.name = GetDescriptionImpl(keypath.c_str());
// vendor/product id
GetVendorProduct(info.dev, &info.vendorId, &info.productId);
// look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to the
// keypath
wpi::SmallString<128> path;
for (auto symlinkDir : symlinkDirs) {
if (DIR* dp = ::opendir(symlinkDir)) {
while (struct dirent* ep = ::readdir(dp)) {
if (ep->d_type == DT_LNK) {
path = symlinkDir;
path += '/';
path += ep->d_name;
char* target = ::realpath(path.c_str(), nullptr);
if (target) {
if (keypath == target) {
info.otherPaths.emplace_back(path.str());
}
std::free(target);
}
}
}
::closedir(dp);
}
}
// eliminate any duplicates
std::sort(info.otherPaths.begin(), info.otherPaths.end());
info.otherPaths.erase(
std::unique(info.otherPaths.begin(), info.otherPaths.end()),
info.otherPaths.end());
return info;
}
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
std::vector<UsbCameraInfo> retval;
if (DIR* dp = ::opendir("/dev")) {
while (struct dirent* ep = ::readdir(dp)) {
wpi::StringRef fname{ep->d_name};
if (!fname.startswith("video")) {
continue;
}
unsigned int dev = 0;
if (fname.substr(5).getAsInteger(10, dev)) {
continue;
}
UsbCameraInfo info;
info.dev = dev;
wpi::SmallString<32> path{"/dev/"};
path += fname;
info.path = path.str();
if (!IsVideoCaptureDevice(path.c_str())) {
continue;
}
info.name = GetDescriptionImpl(path.c_str());
if (info.name.empty()) {
continue;
}
GetVendorProduct(dev, &info.vendorId, &info.productId);
if (dev >= retval.size()) {
retval.resize(info.dev + 1);
}
retval[info.dev] = std::move(info);
}
::closedir(dp);
} else {
// *status = ;
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
return retval;
}
// look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to
// /dev/videoN
wpi::SmallString<128> path;
for (auto symlinkDir : symlinkDirs) {
if (DIR* dp = ::opendir(symlinkDir)) {
while (struct dirent* ep = ::readdir(dp)) {
if (ep->d_type == DT_LNK) {
path = symlinkDir;
path += '/';
path += ep->d_name;
char* target = ::realpath(path.c_str(), nullptr);
if (target) {
std::string fname = fs::path{target}.filename();
unsigned int dev = 0;
if (wpi::StringRef{fname}.startswith("video") &&
!wpi::StringRef{fname}.substr(5).getAsInteger(10, dev) &&
dev < retval.size()) {
retval[dev].otherPaths.emplace_back(path.str());
}
std::free(target);
}
}
}
::closedir(dp);
}
}
// remove devices with empty names
retval.erase(
std::remove_if(retval.begin(), retval.end(),
[](const UsbCameraInfo& x) { return x.name.empty(); }),
retval.end());
return retval;
}
} // namespace cs