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 << "" 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
Pixel Format