diff --git a/cscore/examples/enum_usb/enum_usb.cpp b/cscore/examples/enum_usb/enum_usb.cpp
index 244ebf2ceb..866e2f1e0e 100644
--- a/cscore/examples/enum_usb/enum_usb.cpp
+++ b/cscore/examples/enum_usb/enum_usb.cpp
@@ -17,6 +17,12 @@ int main() {
for (const auto& caminfo : cs::EnumerateUsbCameras(&status)) {
wpi::outs() << caminfo.dev << ": " << caminfo.path << " (" << caminfo.name
<< ")\n";
+ if (!caminfo.otherPaths.empty()) {
+ wpi::outs() << "Other device paths:\n";
+ for (auto&& path : caminfo.otherPaths)
+ wpi::outs() << " " << path << '\n';
+ }
+
cs::UsbCamera camera{"usbcam", caminfo.dev};
wpi::outs() << "Properties:\n";
diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java
index 339694769f..06f0b4733a 100644
--- a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java
+++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java
@@ -112,6 +112,7 @@ public class CameraServerJNI {
// UsbCamera Source Functions
//
public static native String getUsbCameraPath(int source);
+ public static native UsbCameraInfo getUsbCameraInfo(int source);
//
// HttpCamera Source Functions
diff --git a/cscore/src/main/java/edu/wpi/cscore/UsbCamera.java b/cscore/src/main/java/edu/wpi/cscore/UsbCamera.java
index 9a99858731..fc80047eec 100644
--- a/cscore/src/main/java/edu/wpi/cscore/UsbCamera.java
+++ b/cscore/src/main/java/edu/wpi/cscore/UsbCamera.java
@@ -47,6 +47,13 @@ public class UsbCamera extends VideoCamera {
return CameraServerJNI.getUsbCameraPath(m_handle);
}
+ /**
+ * Get the full camera information for the device.
+ */
+ public UsbCameraInfo getInfo() {
+ return CameraServerJNI.getUsbCameraInfo(m_handle);
+ }
+
/**
* Set how verbose the camera connection messages are.
*
diff --git a/cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java b/cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java
index 08c90b2c5e..42ef4660c8 100644
--- a/cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java
+++ b/cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java
@@ -17,11 +17,14 @@ public class UsbCameraInfo {
* @param dev Device number (e.g. N in '/dev/videoN' on Linux)
* @param path Path to device if available (e.g. '/dev/video0' on Linux)
* @param name Vendor/model name of the camera as provided by the USB driver
+ * @param otherPaths Other path aliases to device
*/
- public UsbCameraInfo(int dev, String path, String name) {
+ @SuppressWarnings("PMD.ArrayIsStoredDirectly")
+ public UsbCameraInfo(int dev, String path, String name, String[] otherPaths) {
this.dev = dev;
this.path = path;
this.name = name;
+ this.otherPaths = otherPaths;
}
/**
@@ -41,4 +44,10 @@ public class UsbCameraInfo {
*/
@SuppressWarnings("MemberName")
public String name;
+
+ /**
+ * Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux).
+ */
+ @SuppressWarnings("MemberName")
+ public String[] otherPaths;
}
diff --git a/cscore/src/main/native/cpp/MjpegServerImpl.cpp b/cscore/src/main/native/cpp/MjpegServerImpl.cpp
index 73d312cdb3..cc0531033a 100644
--- a/cscore/src/main/native/cpp/MjpegServerImpl.cpp
+++ b/cscore/src/main/native/cpp/MjpegServerImpl.cpp
@@ -413,6 +413,15 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
}
}
+ status = 0;
+ auto info = GetUsbCameraInfo(Instance::GetInstance().FindSource(source).first,
+ &status);
+ if (status == CS_OK) {
+ os << "
USB device path: " << info.path << '\n';
+ for (auto&& path : info.otherPaths)
+ os << "
Alternate device path: " << path << '\n';
+ }
+
os << "
Supported Video Modes:
\n";
os << "\n";
os << "| Pixel Format | "
diff --git a/cscore/src/main/native/cpp/UsbCameraImplCommon.cpp b/cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
index 6308e80366..082e3982d5 100644
--- a/cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
+++ b/cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
@@ -12,6 +12,25 @@
using namespace cs;
+static void ConvertToC(CS_UsbCameraInfo* out, const UsbCameraInfo& in) {
+ out->dev = in.dev;
+ out->path = ConvertToC(in.path);
+ out->name = ConvertToC(in.name);
+ out->otherPaths = static_cast(
+ wpi::CheckedMalloc(in.otherPaths.size() * sizeof(char*)));
+ out->otherPathsCount = in.otherPaths.size();
+ for (size_t i = 0; i < in.otherPaths.size(); ++i)
+ out->otherPaths[i] = cs::ConvertToC(in.otherPaths[i]);
+}
+
+static void FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
+ std::free(info->path);
+ std::free(info->name);
+ for (int i = 0; i < info->otherPathsCount; ++i)
+ std::free(info->otherPaths[i]);
+ std::free(info->otherPaths);
+}
+
extern "C" {
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
@@ -27,26 +46,34 @@ char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
return ConvertToC(cs::GetUsbCameraPath(source, status));
}
+CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status) {
+ auto info = cs::GetUsbCameraInfo(source, status);
+ if (*status != CS_OK) return nullptr;
+ CS_UsbCameraInfo* out = static_cast(
+ wpi::CheckedMalloc(sizeof(CS_UsbCameraInfo)));
+ ConvertToC(out, info);
+ return out;
+}
+
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
auto cameras = cs::EnumerateUsbCameras(status);
CS_UsbCameraInfo* out = static_cast(
wpi::CheckedMalloc(cameras.size() * sizeof(CS_UsbCameraInfo)));
*count = cameras.size();
- for (size_t i = 0; i < cameras.size(); ++i) {
- out[i].dev = cameras[i].dev;
- out[i].path = ConvertToC(cameras[i].path);
- out[i].name = ConvertToC(cameras[i].name);
- }
+ for (size_t i = 0; i < cameras.size(); ++i) ConvertToC(&out[i], cameras[i]);
return out;
}
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count) {
if (!cameras) return;
- for (int i = 0; i < count; ++i) {
- std::free(cameras[i].path);
- std::free(cameras[i].name);
- }
+ for (int i = 0; i < count; ++i) FreeUsbCameraInfo(&cameras[i]);
std::free(cameras);
}
+void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
+ if (!info) return;
+ FreeUsbCameraInfo(info);
+ std::free(info);
+}
+
} // extern "C"
diff --git a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp
index d5aac6b7c0..fa455894d0 100644
--- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp
+++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp
@@ -185,11 +185,14 @@ static inline bool CheckStatus(JNIEnv* env, CS_Status status) {
static jobject MakeJObject(JNIEnv* env, const cs::UsbCameraInfo& info) {
static jmethodID constructor = env->GetMethodID(
- usbCameraInfoCls, "", "(ILjava/lang/String;Ljava/lang/String;)V");
+ usbCameraInfoCls, "",
+ "(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
JLocal path(env, MakeJString(env, info.path));
JLocal name(env, MakeJString(env, info.name));
+ JLocal otherPaths(env, MakeJStringArray(env, info.otherPaths));
return env->NewObject(usbCameraInfoCls, constructor,
- static_cast(info.dev), path.obj(), name.obj());
+ static_cast(info.dev), path.obj(), name.obj(),
+ otherPaths.obj());
}
static jobject MakeJObject(JNIEnv* env, const cs::VideoMode& videoMode) {
@@ -975,6 +978,21 @@ Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraPath
return MakeJString(env, str);
}
+/*
+ * Class: edu_wpi_cscore_CameraServerJNI
+ * Method: getUsbCameraInfo
+ * Signature: (I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraInfo
+ (JNIEnv* env, jclass, jint source)
+{
+ CS_Status status = 0;
+ auto info = cs::GetUsbCameraInfo(source, &status);
+ if (!CheckStatus(env, status)) return nullptr;
+ return MakeJObject(env, info);
+}
+
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getHttpCameraKind
diff --git a/cscore/src/main/native/include/cscore_c.h b/cscore/src/main/native/include/cscore_c.h
index b7b8789062..0cc354bb6a 100644
--- a/cscore/src/main/native/include/cscore_c.h
+++ b/cscore/src/main/native/include/cscore_c.h
@@ -223,6 +223,17 @@ struct CS_Event {
const char* valueStr;
};
+/**
+ * USB camera infomation
+ */
+typedef struct CS_UsbCameraInfo {
+ int dev;
+ char* path;
+ char* name;
+ int otherPathsCount;
+ char** otherPaths;
+} CS_UsbCameraInfo;
+
/**
* @defgroup cscore_property_cfunc Property Functions
* @{
@@ -322,6 +333,7 @@ void CS_SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
* @{
*/
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status);
+CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status);
/** @} */
/**
@@ -456,15 +468,6 @@ void CS_Shutdown(void);
* @{
*/
-/**
- * USB camera infomation
- */
-typedef struct CS_UsbCameraInfo {
- int dev;
- char* path;
- char* name;
-} CS_UsbCameraInfo;
-
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status);
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count);
@@ -476,6 +479,7 @@ void CS_ReleaseEnumeratedSinks(CS_Sink* sinks, int count);
void CS_FreeString(char* str);
void CS_FreeEnumPropertyChoices(char** choices, int count);
+void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info);
void CS_FreeHttpCameraUrls(char** urls, int count);
void CS_FreeEnumeratedProperties(CS_Property* properties, int count);
diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h
index 33e8f20339..040b726fd2 100644
--- a/cscore/src/main/native/include/cscore_cpp.h
+++ b/cscore/src/main/native/include/cscore_cpp.h
@@ -47,11 +47,13 @@ namespace cs {
*/
struct UsbCameraInfo {
/** Device number (e.g. N in '/dev/videoN' on Linux) */
- int dev;
+ int dev = -1;
/** Path to device if available (e.g. '/dev/video0' on Linux) */
std::string path;
/** Vendor/model name of the camera as provided by the USB driver */
std::string name;
+ /** Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux) */
+ std::vector otherPaths;
};
/**
@@ -267,6 +269,7 @@ void SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
* @{
*/
std::string GetUsbCameraPath(CS_Source source, CS_Status* status);
+UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
/** @} */
/**
diff --git a/cscore/src/main/native/include/cscore_oo.h b/cscore/src/main/native/include/cscore_oo.h
index f69d3d2e36..9a8b3a0c1f 100644
--- a/cscore/src/main/native/include/cscore_oo.h
+++ b/cscore/src/main/native/include/cscore_oo.h
@@ -449,6 +449,11 @@ class UsbCamera : public VideoCamera {
*/
std::string GetPath() const;
+ /**
+ * Get the full camera information for the device.
+ */
+ UsbCameraInfo GetInfo() const;
+
/**
* Set how verbose the camera connection messages are.
*
diff --git a/cscore/src/main/native/include/cscore_oo.inl b/cscore/src/main/native/include/cscore_oo.inl
index 0c818d1532..1767452fae 100644
--- a/cscore/src/main/native/include/cscore_oo.inl
+++ b/cscore/src/main/native/include/cscore_oo.inl
@@ -260,6 +260,11 @@ inline std::string UsbCamera::GetPath() const {
return ::cs::GetUsbCameraPath(m_handle, &m_status);
}
+inline UsbCameraInfo UsbCamera::GetInfo() const {
+ m_status = 0;
+ return ::cs::GetUsbCameraInfo(m_handle, &m_status);
+}
+
inline void UsbCamera::SetConnectVerbose(int level) {
m_status = 0;
SetProperty(GetSourceProperty(m_handle, "connect_verbose", &m_status), level,
diff --git a/cscore/src/main/native/linux/UsbCameraImpl.cpp b/cscore/src/main/native/linux/UsbCameraImpl.cpp
index a945f9bf77..125c9369f8 100644
--- a/cscore/src/main/native/linux/UsbCameraImpl.cpp
+++ b/cscore/src/main/native/linux/UsbCameraImpl.cpp
@@ -24,6 +24,8 @@
#include
+#include
+#include
#include
#include
#include
@@ -1278,17 +1280,79 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
return static_cast(*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(*data->source).GetPath();
+ info.path = keypath;
+
+ // 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 (wpi::sys::fs::is_symlink_file(keypath)) {
+ char* target = ::realpath(keypath.c_str(), nullptr);
+ if (target) {
+ keypath.assign(target);
+ info.otherPaths.emplace_back(keypath);
+ std::free(target);
+ }
+ }
+
+ // device number
+ wpi::StringRef fname = wpi::sys::path::filename(keypath);
+ if (fname.startswith("video")) fname.substr(5).getAsInteger(10, info.dev);
+
+ // description
+ info.name = GetDescriptionImpl(keypath.c_str());
+
+ // 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 EnumerateUsbCameras(CS_Status* status) {
std::vector retval;
- if (DIR* dp = opendir("/dev")) {
- while (struct dirent* ep = readdir(dp)) {
+ 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 = -1;
- fname.substr(5).getAsInteger(10, info.dev);
+ info.dev = dev;
+
wpi::SmallString<32> path{"/dev/"};
path += fname;
info.path = path.str();
@@ -1296,20 +1360,47 @@ std::vector EnumerateUsbCameras(CS_Status* status) {
info.name = GetDescriptionImpl(path.c_str());
if (info.name.empty()) continue;
- retval.emplace_back(std::move(info));
+ if (dev >= retval.size()) retval.resize(info.dev + 1);
+ retval[info.dev] = std::move(info);
}
- closedir(dp);
+ ::closedir(dp);
} else {
// *status = ;
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
return retval;
}
- // sort by device number
- std::sort(retval.begin(), retval.end(),
- [](const UsbCameraInfo& a, const UsbCameraInfo& b) {
- return a.dev < b.dev;
- });
+ // 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) {
+ wpi::StringRef fname = wpi::sys::path::filename(target);
+ unsigned int dev = 0;
+ if (fname.startswith("video") &&
+ !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;
}
diff --git a/cscore/src/main/native/osx/UsbCameraImpl.cpp b/cscore/src/main/native/osx/UsbCameraImpl.cpp
index e9a47c889b..a10fae4b6b 100644
--- a/cscore/src/main/native/osx/UsbCameraImpl.cpp
+++ b/cscore/src/main/native/osx/UsbCameraImpl.cpp
@@ -26,6 +26,11 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
return std::string{};
}
+UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
+ *status = CS_INVALID_HANDLE;
+ return UsbCameraInfo{};
+}
+
std::vector EnumerateUsbCameras(CS_Status* status) {
*status = CS_INVALID_HANDLE;
return std::vector{};
diff --git a/cscore/src/main/native/windows/UsbCameraImpl.cpp b/cscore/src/main/native/windows/UsbCameraImpl.cpp
index dc5c293ce9..0670dca43f 100644
--- a/cscore/src/main/native/windows/UsbCameraImpl.cpp
+++ b/cscore/src/main/native/windows/UsbCameraImpl.cpp
@@ -1020,4 +1020,17 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
return static_cast(*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(*data->source).GetPath();
+ // TODO: dev and name
+ return info;
+}
+
} // namespace cs
|---|