diff --git a/include/cameraserver_c.h b/include/cameraserver_c.h index 89c455fd2f..f4a5c71726 100644 --- a/include/cameraserver_c.h +++ b/include/cameraserver_c.h @@ -50,10 +50,30 @@ enum CS_StatusValue { CS_WRONG_HANDLE_SUBTYPE = -2001, CS_INVALID_PROPERTY = -2002, CS_WRONG_PROPERTY_TYPE = -2003, - CS_PROPERTY_READ_FAILED = -2004, + CS_READ_FAILED = -2004, CS_SOURCE_IS_DISCONNECTED = -2005 }; +// +// Pixel formats +// +enum CS_PixelFormat { + CS_PIXFMT_UNKNOWN = 0, + CS_PIXFMT_MJPEG, + CS_PIXFMT_YUYV, + CS_PIXFMT_RGB565 +}; + +// +// Frame formats +// +typedef struct CS_VideoMode { + int pixelFormat; + int width; + int height; + int fps; +} CS_VideoMode; + // // Property Functions // @@ -89,7 +109,8 @@ CS_Source CS_CreateUSBSourcePath(const char* name, const char* path, CS_Status* status); CS_Source CS_CreateHTTPSource(const char* name, const char* url, CS_Status* status); -CS_Source CS_CreateCvSource(const char* name, CS_Status* status); +CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode, + CS_Status* status); // // Source Functions @@ -102,6 +123,22 @@ CS_Property CS_GetSourceProperty(CS_Source source, const char* name, CS_Status* status); CS_Property* CS_EnumerateSourceProperties(CS_Source source, int* count, CS_Status* status); +void CS_GetSourceVideoMode(CS_Source source, CS_VideoMode* mode, + CS_Status* status); +CS_Bool CS_SetSourceVideoMode(CS_Source source, const CS_VideoMode* mode, + CS_Status* status); +CS_Bool CS_SetSourceVideoModeDiscrete(CS_Source source, + enum CS_PixelFormat pixelFormat, + int width, int height, int fps, + CS_Status* status); +CS_Bool CS_SetSourcePixelFormat(CS_Source source, + enum CS_PixelFormat pixelFormat, + CS_Status* status); +CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height, + CS_Status* status); +CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status); +CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count, + CS_Status* status); CS_Source CS_CopySource(CS_Source source, CS_Status* status); void CS_ReleaseSource(CS_Source source, CS_Status* status); @@ -206,6 +243,7 @@ void CS_FreeString(char* str); void CS_FreeEnumPropertyChoices(char** choices, int count); void CS_FreeEnumeratedProperties(CS_Property* properties, int count); +void CS_FreeEnumeratedVideoModes(CS_VideoMode* modes, int count); #ifdef __cplusplus } diff --git a/include/cameraserver_cpp.h b/include/cameraserver_cpp.h index aa4a951b17..cb40a3252c 100644 --- a/include/cameraserver_cpp.h +++ b/include/cameraserver_cpp.h @@ -42,6 +42,29 @@ struct USBCameraInfo { std::string name; }; +/// Video mode +struct VideoMode : public CS_VideoMode { + enum PixelFormat { + kUnknown = CS_PIXFMT_UNKNOWN, + kMJPEG = CS_PIXFMT_MJPEG, + kYUYV = CS_PIXFMT_YUYV, + kRGB565 = CS_PIXFMT_RGB565 + }; + VideoMode() { + pixelFormat = 0; + width = 0; + height = 0; + fps = 0; + } + VideoMode(PixelFormat pixelFormat_, int width_, int height_, int fps_) { + pixelFormat = pixelFormat_; + width = width_; + height = height_; + fps = fps_; + } + explicit operator bool() const { return pixelFormat == kUnknown; } +}; + // // Property Functions // @@ -74,7 +97,8 @@ CS_Source CreateUSBSourcePath(llvm::StringRef name, llvm::StringRef path, CS_Status* status); CS_Source CreateHTTPSource(llvm::StringRef name, llvm::StringRef url, CS_Status* status); -CS_Source CreateCvSource(llvm::StringRef name, CS_Status* status); +CS_Source CreateCvSource(llvm::StringRef name, const VideoMode& mode, + CS_Status* status); // // Source Functions @@ -94,6 +118,16 @@ CS_Property GetSourceProperty(CS_Source source, llvm::StringRef name, llvm::ArrayRef EnumerateSourceProperties( CS_Source source, llvm::SmallVectorImpl& vec, CS_Status* status); +VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status); +bool SetSourceVideoMode(CS_Source source, const VideoMode& mode, + CS_Status* status); +bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat, + CS_Status* status); +bool SetSourceResolution(CS_Source source, int width, int height, + CS_Status* status); +bool SetSourceFPS(CS_Source source, int fps, CS_Status* status); +std::vector EnumerateSourceVideoModes(CS_Source source, + CS_Status* status); CS_Source CopySource(CS_Source source, CS_Status* status); void ReleaseSource(CS_Source source, CS_Status* status); diff --git a/include/cameraserver_oo.h b/include/cameraserver_oo.h index 47923f17e5..ae90f788b2 100644 --- a/include/cameraserver_oo.h +++ b/include/cameraserver_oo.h @@ -112,6 +112,41 @@ class VideoSource { /// Enumerate all properties of this source. std::vector EnumerateProperties() const; + /// Get the current video mode. + VideoMode GetVideoMode() const; + + /// Set the video mode. + /// @param mode Video mode + bool SetVideoMode(const VideoMode& mode); + + /// Set the video mode. + /// @param pixelFormat desired pixel format + /// @param width desired width + /// @param height desired height + /// @param fps desired FPS + /// @return True if set successfully + bool SetVideoMode(VideoMode::PixelFormat pixelFormat, int width, int height, + int fps); + + /// Set the pixel format. + /// @param pixelFormat desired pixel format + /// @return True if set successfully + bool SetPixelFormat(VideoMode::PixelFormat pixelFormat); + + /// Set the resolution. + /// @param width desired width + /// @param height desired height + /// @return True if set successfully + bool SetResolution(int width, int height); + + /// Set the frames per second (FPS). + /// @param fps desired FPS + /// @return True if set successfully + bool SetFPS(int fps); + + /// Enumerate all known video modes for this source. + std::vector EnumerateVideoModes() const; + CS_Status GetLastStatus() const { return m_status; } /// Enumerate all existing sources. @@ -163,7 +198,17 @@ class CvSource : public VideoSource { public: /// Create an OpenCV source. /// @param name Source name (arbitrary unique identifier) - CvSource(llvm::StringRef name); + /// @param mode Video mode being generated + CvSource(llvm::StringRef name, const VideoMode& mode); + + /// Create an OpenCV source. + /// @param name Source name (arbitrary unique identifier) + /// @param pixelFormat Pixel format + /// @param width width + /// @param height height + /// @param fps fps + CvSource(llvm::StringRef name, VideoMode::PixelFormat pixelFormat, int width, + int height, int fps); /// Put an OpenCV image and notify sinks. /// This is identical in behavior to calling PutImage(0, image) followed by diff --git a/include/cameraserver_oo.inl b/include/cameraserver_oo.inl index 3e6261ba71..856a0dbbf1 100644 --- a/include/cameraserver_oo.inl +++ b/include/cameraserver_oo.inl @@ -118,6 +118,43 @@ inline VideoProperty VideoSource::GetProperty(llvm::StringRef name) { return VideoProperty{GetSourceProperty(m_handle, name, &m_status)}; } +inline VideoMode VideoSource::GetVideoMode() const { + m_status = 0; + return GetSourceVideoMode(m_handle, &m_status); +} + +inline bool VideoSource::SetVideoMode(const VideoMode& mode) { + m_status = 0; + return SetSourceVideoMode(m_handle, mode, &m_status); +} + +inline bool VideoSource::SetVideoMode(VideoMode::PixelFormat pixelFormat, + int width, int height, int fps) { + m_status = 0; + return SetSourceVideoMode( + m_handle, VideoMode{pixelFormat, width, height, fps}, &m_status); +} + +inline bool VideoSource::SetPixelFormat(VideoMode::PixelFormat pixelFormat) { + m_status = 0; + return SetSourcePixelFormat(m_handle, pixelFormat, &m_status); +} + +inline bool VideoSource::SetResolution(int width, int height) { + m_status = 0; + return SetSourceResolution(m_handle, width, height, &m_status); +} + +inline bool VideoSource::SetFPS(int fps) { + m_status = 0; + return SetSourceFPS(m_handle, fps, &m_status); +} + +inline std::vector VideoSource::EnumerateVideoModes() const { + CS_Status status = 0; + return EnumerateSourceVideoModes(m_handle, &status); +} + inline USBCamera::USBCamera(llvm::StringRef name, int dev) { m_handle = CreateUSBSourceDev(name, dev, &m_status); } @@ -135,8 +172,14 @@ inline HTTPCamera::HTTPCamera(llvm::StringRef name, llvm::StringRef url) { m_handle = CreateHTTPSource(name, url, &m_status); } -inline CvSource::CvSource(llvm::StringRef name) { - m_handle = CreateCvSource(name, &m_status); +inline CvSource::CvSource(llvm::StringRef name, const VideoMode& mode) { + m_handle = CreateCvSource(name, mode, &m_status); +} + +inline CvSource::CvSource(llvm::StringRef name, VideoMode::PixelFormat format, + int width, int height, int fps) { + m_handle = + CreateCvSource(name, VideoMode{format, width, height, fps}, &m_status); } inline void CvSource::PutFrame(cv::Mat* image) { diff --git a/java/lib/CameraServerJNI.cpp b/java/lib/CameraServerJNI.cpp index d8ce91e6d7..e21718e9c3 100644 --- a/java/lib/CameraServerJNI.cpp +++ b/java/lib/CameraServerJNI.cpp @@ -21,6 +21,7 @@ using namespace wpi::java; // Used for callback. static JavaVM *jvm = nullptr; static jclass usbCameraInfoCls = nullptr; +static jclass videoModeCls = nullptr; extern "C" { @@ -40,6 +41,12 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { if (!usbCameraInfoCls) return JNI_ERR; env->DeleteLocalRef(local); + local = env->FindClass("edu/wpi/cameraserver/VideoMode"); + if (!local) return JNI_ERR; + videoModeCls = static_cast(env->NewGlobalRef(local)); + if (!videoModeCls) return JNI_ERR; + env->DeleteLocalRef(local); + return JNI_VERSION_1_6; } @@ -49,6 +56,7 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { return; // Delete global references if (usbCameraInfoCls) env->DeleteGlobalRef(usbCameraInfoCls); + if (videoModeCls) env->DeleteGlobalRef(videoModeCls); jvm = nullptr; } @@ -73,6 +81,15 @@ static jobject MakeJObject(JNIEnv *env, const cs::USBCameraInfo &info) { static_cast(info.dev), path.obj(), name.obj()); } +static jobject MakeJObject(JNIEnv *env, const cs::VideoMode &videoMode) { + static jmethodID constructor = + env->GetMethodID(videoModeCls, "", "(IIII)V"); + return env->NewObject( + videoModeCls, constructor, static_cast(videoMode.pixelFormat), + static_cast(videoMode.width), static_cast(videoMode.height), + static_cast(videoMode.fps)); +} + extern "C" { /* @@ -276,13 +293,19 @@ JNIEXPORT jint JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_createHTTPSourc /* * Class: edu_wpi_cameraserver_CameraServerJNI * Method: createCvSource - * Signature: (Ljava/lang/String;)I + * Signature: (Ljava/lang/String;IIII)I */ JNIEXPORT jint JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_createCvSource - (JNIEnv *env, jclass, jstring name) + (JNIEnv *env, jclass, jstring name, jint pixelFormat, jint width, jint height, + jint fps) { CS_Status status; - auto val = cs::CreateCvSource(JStringRef{env, name}, &status); + auto val = cs::CreateCvSource( + JStringRef{env, name}, + cs::VideoMode{static_cast(pixelFormat), + static_cast(width), static_cast(height), + static_cast(fps)}, + &status); CheckStatus(env, status); return val; } @@ -374,6 +397,103 @@ JNIEXPORT jintArray JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_enumerateS return MakeJIntArray(env, arr); } +/* + * Class: edu_wpi_cameraserver_CameraServerJNI + * Method: getSourceVideoMode + * Signature: (I)Ledu/wpi/cameraserver/VideoMode; + */ +JNIEXPORT jobject JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_getSourceVideoMode + (JNIEnv *env, jclass, jint source) +{ + CS_Status status; + auto val = cs::GetSourceVideoMode(source, &status); + if (!CheckStatus(env, status)) return nullptr; + return MakeJObject(env, val); +} + +/* + * Class: edu_wpi_cameraserver_CameraServerJNI + * Method: setSourceVideoMode + * Signature: (IIIII)Z + */ +JNIEXPORT jboolean JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_setSourceVideoMode + (JNIEnv *env, jclass, jint source, jint pixelFormat, jint width, jint height, + jint fps) +{ + CS_Status status; + auto val = cs::SetSourceVideoMode( + source, + cs::VideoMode(static_cast(pixelFormat), width, + height, fps), + &status); + CheckStatus(env, status); + return val; +} + +/* + * Class: edu_wpi_cameraserver_CameraServerJNI + * Method: setSourcePixelFormat + * Signature: (II)Z + */ +JNIEXPORT jboolean JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_setSourcePixelFormat + (JNIEnv *env, jclass, jint source, jint pixelFormat) +{ + CS_Status status; + auto val = cs::SetSourcePixelFormat( + source, static_cast(pixelFormat), &status); + CheckStatus(env, status); + return val; +} + +/* + * Class: edu_wpi_cameraserver_CameraServerJNI + * Method: setSourceResolution + * Signature: (III)Z + */ +JNIEXPORT jboolean JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_setSourceResolution + (JNIEnv *env, jclass, jint source, jint width, jint height) +{ + CS_Status status; + auto val = cs::SetSourceResolution(source, width, height, &status); + CheckStatus(env, status); + return val; +} + +/* + * Class: edu_wpi_cameraserver_CameraServerJNI + * Method: setSourceFPS + * Signature: (II)Z + */ +JNIEXPORT jboolean JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_setSourceFPS + (JNIEnv *env, jclass, jint source, jint fps) +{ + CS_Status status; + auto val = cs::SetSourceFPS(source, fps, &status); + CheckStatus(env, status); + return val; +} + +/* + * Class: edu_wpi_cameraserver_CameraServerJNI + * Method: enumerateSourceVideoModes + * Signature: (I)[Ledu/wpi/cameraserver/VideoMode; + */ +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_enumerateSourceVideoModes + (JNIEnv *env, jclass, jint source) +{ + CS_Status status; + auto arr = cs::EnumerateSourceVideoModes(source, &status); + if (!CheckStatus(env, status)) return nullptr; + jobjectArray jarr = + env->NewObjectArray(arr.size(), videoModeCls, nullptr); + if (!jarr) return nullptr; + for (size_t i = 0; i < arr.size(); ++i) { + JLocal jelem{env, MakeJObject(env, arr[i])}; + env->SetObjectArrayElement(jarr, i, jelem); + } + return jarr; +} + /* * Class: edu_wpi_cameraserver_CameraServerJNI * Method: copySource diff --git a/java/src/edu/wpi/cameraserver/CameraServerJNI.java b/java/src/edu/wpi/cameraserver/CameraServerJNI.java index 3345c0edd9..4642566760 100644 --- a/java/src/edu/wpi/cameraserver/CameraServerJNI.java +++ b/java/src/edu/wpi/cameraserver/CameraServerJNI.java @@ -93,7 +93,7 @@ public class CameraServerJNI { public static native int createUSBSourceDev(String name, int dev); public static native int createUSBSourcePath(String name, String path); public static native int createHTTPSource(String name, String url); - public static native int createCvSource(String name); + public static native int createCvSource(String name, int pixelFormat, int width, int height, int fps); // // Source Functions @@ -104,6 +104,12 @@ public class CameraServerJNI { public static native boolean isSourceConnected(int source); public static native int getSourceProperty(int source, String name); public static native int[] enumerateSourceProperties(int source); + public static native VideoMode getSourceVideoMode(int source); + public static native boolean setSourceVideoMode(int source, int pixelFormat, int width, int height, int fps); + public static native boolean setSourcePixelFormat(int source, int pixelFormat); + public static native boolean setSourceResolution(int source, int width, int height); + public static native boolean setSourceFPS(int source, int fps); + public static native VideoMode[] enumerateSourceVideoModes(int source); public static native int copySource(int source); public static native void releaseSource(int source); diff --git a/java/src/edu/wpi/cameraserver/CvSource.java b/java/src/edu/wpi/cameraserver/CvSource.java index 82cd2b156c..540ac8988b 100644 --- a/java/src/edu/wpi/cameraserver/CvSource.java +++ b/java/src/edu/wpi/cameraserver/CvSource.java @@ -11,8 +11,19 @@ package edu.wpi.cameraserver; public class CvSource extends VideoSource { /// Create an OpenCV source. /// @param name Source name (arbitrary unique identifier) - public CvSource(String name) { - super(CameraServerJNI.createCvSource(name)); + /// @param mode Video mode being generated + public CvSource(String name, VideoMode mode) { + super(CameraServerJNI.createCvSource(name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps)); + } + + /// Create an OpenCV source. + /// @param name Source name (arbitrary unique identifier) + /// @param pixelFormat Pixel format + /// @param width width + /// @param height height + /// @param fps fps + public CvSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) { + super(CameraServerJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps)); } /// Put an OpenCV image and notify sinks. diff --git a/java/src/edu/wpi/cameraserver/VideoMode.java b/java/src/edu/wpi/cameraserver/VideoMode.java new file mode 100644 index 0000000000..7a0697448f --- /dev/null +++ b/java/src/edu/wpi/cameraserver/VideoMode.java @@ -0,0 +1,41 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.cameraserver; + +/// Video mode +public class VideoMode { + public enum PixelFormat { + kUnknown(0), kMJPEG(1), kYUYV(2), kRGB565(3); + private int value; + + private PixelFormat(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + private static final PixelFormat[] m_pixelFormatValues = PixelFormat.values(); + + public VideoMode(int pixelFormat, int width, int height, int fps) { + this.pixelFormat = m_pixelFormatValues[pixelFormat]; + this.width = width; + this.height = height; + this.fps = fps; + } + + /// Pixel format + public PixelFormat pixelFormat; + /// Width in pixels + public int width; + /// Height in pixels + public int height; + /// Frames per second + public int fps; +} diff --git a/java/src/edu/wpi/cameraserver/VideoProperty.java b/java/src/edu/wpi/cameraserver/VideoProperty.java index 9e0c92f664..b5a71a1d9c 100644 --- a/java/src/edu/wpi/cameraserver/VideoProperty.java +++ b/java/src/edu/wpi/cameraserver/VideoProperty.java @@ -19,7 +19,7 @@ public class VideoProperty { public int getValue() { return value; } - }; + } private static final Type[] m_typeValues = Type.values(); public String getName() { diff --git a/java/src/edu/wpi/cameraserver/VideoSource.java b/java/src/edu/wpi/cameraserver/VideoSource.java index 5ab99c90de..7f416b88ad 100644 --- a/java/src/edu/wpi/cameraserver/VideoSource.java +++ b/java/src/edu/wpi/cameraserver/VideoSource.java @@ -65,6 +65,54 @@ public class VideoSource { return rv; } + /// Get the current video mode. + public VideoMode getVideoMode() { + return CameraServerJNI.getSourceVideoMode(m_handle); + } + + /// Set the video mode. + /// @param mode Video mode + public boolean setVideoMode(VideoMode mode) { + return CameraServerJNI.setSourceVideoMode(m_handle, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps); + } + + /// Set the video mode. + /// @param pixelFormat desired pixel format + /// @param width desired width + /// @param height desired height + /// @param fps desired FPS + /// @return True if set successfully + public boolean setVideoMode(VideoMode.PixelFormat pixelFormat, int width, int height, int fps) { + return CameraServerJNI.setSourceVideoMode(m_handle, pixelFormat.getValue(), width, height, fps); + } + + /// Set the pixel format. + /// @param pixelFormat desired pixel format + /// @return True if set successfully + public boolean setPixelFormat(VideoMode.PixelFormat pixelFormat) { + return CameraServerJNI.setSourcePixelFormat(m_handle, pixelFormat.getValue()); + } + + /// Set the resolution. + /// @param width desired width + /// @param height desired height + /// @return True if set successfully + public boolean setResolution(int width, int height) { + return CameraServerJNI.setSourceResolution(m_handle, width, height); + } + + /// Set the frames per second (FPS). + /// @param fps desired FPS + /// @return True if set successfully + public boolean setFPS(int fps) { + return CameraServerJNI.setSourceFPS(m_handle, fps); + } + + /// Enumerate all known video modes for this source. + public VideoMode[] enumerateVideoModes() { + return CameraServerJNI.enumerateSourceVideoModes(m_handle); + } + /// Enumerate all existing sources. /// @return Vector of sources. public static VideoSource[] enumerateSources() { diff --git a/src/SourceImpl.cpp b/src/SourceImpl.cpp index 04eb4f4d02..7138d90986 100644 --- a/src/SourceImpl.cpp +++ b/src/SourceImpl.cpp @@ -37,6 +37,29 @@ void SourceImpl::Wakeup() { m_frameCv.notify_all(); } +bool SourceImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat, + CS_Status* status) { + auto mode = GetVideoMode(status); + if (!mode) return false; + mode.pixelFormat = pixelFormat; + return SetVideoMode(mode, status); +} + +bool SourceImpl::SetResolution(int width, int height, CS_Status* status) { + auto mode = GetVideoMode(status); + if (!mode) return false; + mode.width = width; + mode.height = height; + return SetVideoMode(mode, status); +} + +bool SourceImpl::SetFPS(int fps, CS_Status* status) { + auto mode = GetVideoMode(status); + if (!mode) return false; + mode.fps = fps; + return SetVideoMode(mode, status); +} + void SourceImpl::StartFrame() { std::lock_guard lock{m_mutex}; if (m_frameData) return; diff --git a/src/SourceImpl.h b/src/SourceImpl.h index cbd33d7196..56591a5475 100644 --- a/src/SourceImpl.h +++ b/src/SourceImpl.h @@ -17,7 +17,7 @@ #include "llvm/ArrayRef.h" #include "llvm/StringRef.h" -#include "cameraserver_c.h" +#include "cameraserver_cpp.h" #include "Frame.h" namespace cs { @@ -99,6 +99,19 @@ class SourceImpl { virtual std::vector GetEnumPropertyChoices( int property, CS_Status* status) const = 0; + // Video mode functions + virtual VideoMode GetVideoMode(CS_Status* status) const = 0; + virtual bool SetVideoMode(const VideoMode& mode, CS_Status* status) = 0; + virtual std::vector EnumerateVideoModes( + CS_Status* status) const = 0; + + // These have default implementations but can be overridden for custom + // or optimized behavior. + virtual bool SetPixelFormat(VideoMode::PixelFormat pixelFormat, + CS_Status* status); + virtual bool SetResolution(int width, int height, CS_Status* status); + virtual bool SetFPS(int fps, CS_Status* status); + protected: void StartFrame(); Image& AddImage(std::size_t size); diff --git a/src/USBCameraImpl.cpp b/src/USBCameraImpl.cpp index 256c5746d3..1b78f4e5a6 100644 --- a/src/USBCameraImpl.cpp +++ b/src/USBCameraImpl.cpp @@ -36,6 +36,32 @@ using namespace cs; #ifdef __linux__ +// 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; + default: + return VideoMode::kUnknown; + } +} + // Removes non-alphanumeric characters and replaces spaces with underscores. // e.g. "Zoom, Absolute" -> "zoom_absolute", "Pan (Absolute)" -> "pan_absolute" static llvm::StringRef NormalizeName(llvm::StringRef name, @@ -286,7 +312,17 @@ USBCameraImpl::USBCameraImpl(llvm::StringRef name, llvm::StringRef path) m_description{GetDescriptionImpl(m_path.c_str())}, m_fd{open(m_path.c_str(), O_RDWR)}, m_active{false} { - if (m_fd >= 0) m_connected = true; + if (m_fd >= 0) { + m_connected = true; + struct v4l2_capability vcap; + if (DoIoctl(m_fd, VIDIOC_QUERYCAP, &vcap) >= 0) { + m_capabilities = vcap.capabilities; + if (m_capabilities & V4L2_CAP_DEVICE_CAPS) + m_capabilities = vcap.device_caps; + } + CS_Status status = 0; + m_mode = GetVideoMode(&status); + } } USBCameraImpl::~USBCameraImpl() { Stop(); } @@ -411,7 +447,7 @@ int USBCameraImpl::GetProperty(int property, CS_Status* status) const { int64_t value = 0; if (GetIntCtrlIoctl(fd, id, type, &value) < 0) { - *status = CS_PROPERTY_READ_FAILED; + *status = CS_READ_FAILED; return false; } return value; @@ -493,7 +529,7 @@ llvm::StringRef USBCameraImpl::GetStringProperty( ctrls.controls = &ctrl; int rc = DoIoctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls); if (rc < 0) { - *status = CS_PROPERTY_READ_FAILED; + *status = CS_READ_FAILED; return llvm::StringRef{}; } buf.append(ctrl.string, ctrl.string + std::strlen(ctrl.string)); @@ -587,6 +623,211 @@ std::vector USBCameraImpl::GetEnumPropertyChoices( return vec; } +VideoMode USBCameraImpl::GetVideoMode(CS_Status* status) const { + int fd = m_fd.load(); + if (fd < 0) { + *status = CS_SOURCE_IS_DISCONNECTED; + return VideoMode{}; + } + + // 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) { + *status = CS_READ_FAILED; + return VideoMode{}; + } + VideoMode::PixelFormat pixelFormat; + switch (vfmt.fmt.pix.pixelformat) { + case V4L2_PIX_FMT_MJPEG: + pixelFormat = VideoMode::kMJPEG; + break; + case V4L2_PIX_FMT_YUYV: + pixelFormat = VideoMode::kYUYV; + break; + case V4L2_PIX_FMT_RGB565: + pixelFormat = VideoMode::kRGB565; + break; + default: + pixelFormat = VideoMode::kUnknown; + break; + } + + // 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); + } + + VideoMode mode(pixelFormat, vfmt.fmt.pix.width, vfmt.fmt.pix.height, fps); + + // Re-cache + std::lock_guard lock(m_mutex); + m_mode = mode; + return mode; +} + +bool USBCameraImpl::SetVideoModePixRes(const VideoMode& mode, + CS_Status* status) { + int fd = m_fd.load(); + if (fd < 0) { + *status = CS_SOURCE_IS_DISCONNECTED; + return false; + } + + 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; + switch (mode.pixelFormat) { + case VideoMode::kMJPEG: + vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + break; + case VideoMode::kYUYV: + vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + break; + case VideoMode::kRGB565: + vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; + break; + default: + return false; + } + vfmt.fmt.pix.width = mode.width; + vfmt.fmt.pix.height = mode.height; + + if (DoIoctl(fd, VIDIOC_S_FMT, &vfmt) != 0) return false; + return true; +} + +bool USBCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) { + if (!SetVideoModePixRes(mode, status)) return false; + if (!SetFPS(mode.fps, status)) return false; + { + std::lock_guard lock(m_mutex); + m_mode = mode; + } + m_mode_changed.notify_one(); + return true; +} + +std::vector USBCameraImpl::EnumerateVideoModes( + CS_Status* status) const { + std::vector rv; + int fd = m_fd.load(); + if (fd < 0) { + *status = CS_SOURCE_IS_DISCONNECTED; + return rv; + } + + // Formats + struct v4l2_fmtdesc 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; + + struct v4l2_frmsizeenum 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; + + struct v4l2_frmivalenum 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; + + rv.emplace_back(pixelFormat, static_cast(frmsize.discrete.width), + static_cast(frmsize.discrete.height), + FractToFPS(frmival.discrete)); + } + } + } + + return rv; +} + +bool USBCameraImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat, + CS_Status* status) { + // Copy cached mode so we don't hold lock during ioctl + VideoMode mode; + { + std::lock_guard lock(m_mutex); + mode = m_mode; + } + mode.pixelFormat = pixelFormat; + if (!SetVideoModePixRes(mode, status)) return false; + { + std::lock_guard lock(m_mutex); + m_mode.pixelFormat = pixelFormat; + } + m_mode_changed.notify_one(); + return true; +} + +bool USBCameraImpl::SetResolution(int width, int height, CS_Status* status) { + // Copy cached mode so we don't hold lock during ioctl + VideoMode mode; + { + std::lock_guard lock(m_mutex); + mode = m_mode; + } + mode.width = width; + mode.height = height; + if (!SetVideoModePixRes(mode, status)) return false; + { + std::lock_guard lock(m_mutex); + m_mode.width = width; + m_mode.height = height; + } + m_mode_changed.notify_one(); + return true; +} + +bool USBCameraImpl::SetFPS(int fps, CS_Status* status) { + int fd = m_fd.load(); + if (fd < 0) { + *status = CS_SOURCE_IS_DISCONNECTED; + return false; + } + + 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 false; + if ((parm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) == 0) return false; + std::memset(&parm, 0, sizeof(parm)); + parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + parm.parm.capture.timeperframe = FPSToFract(fps); + if (DoIoctl(fd, VIDIOC_S_PARM, &parm) != 0) return false; + + { + std::lock_guard lock(m_mutex); + m_mode.fps = fps; + } + m_mode_changed.notify_one(); + return true; +} + void USBCameraImpl::Stop() { m_active = false; diff --git a/src/USBCameraImpl.h b/src/USBCameraImpl.h index 8d195f1133..6c6046a4e6 100644 --- a/src/USBCameraImpl.h +++ b/src/USBCameraImpl.h @@ -55,6 +55,14 @@ class USBCameraImpl : public SourceImpl { std::vector GetEnumPropertyChoices( int property, CS_Status* status) const override; + VideoMode GetVideoMode(CS_Status* status) const override; + bool SetVideoMode(const VideoMode& mode, CS_Status* status) override; + std::vector EnumerateVideoModes(CS_Status* status) const override; + bool SetPixelFormat(VideoMode::PixelFormat pixelFormat, + CS_Status* status) override; + bool SetResolution(int width, int height, CS_Status* status) override; + bool SetFPS(int fps, CS_Status* status) override; + void Stop(); struct PropertyData { @@ -85,6 +93,7 @@ class USBCameraImpl : public SourceImpl { void CacheProperties() const; bool GetPropertyTypeValueFd(int property, int propType, unsigned* id, int* type, int* fd, CS_Status* status) const; + bool SetVideoModePixRes(const VideoMode& mode, CS_Status* status); void CameraThreadMain(); @@ -96,7 +105,13 @@ class USBCameraImpl : public SourceImpl { std::atomic_bool m_active; // set to false to terminate threads std::thread m_cameraThread; + mutable VideoMode m_mode; +#ifdef __linux__ + unsigned m_capabilities = 0; +#endif + mutable std::mutex m_mutex; + mutable std::condition_variable m_mode_changed; }; } // namespace cs diff --git a/src/cameraserver_c.cpp b/src/cameraserver_c.cpp index a9237e977d..98eb51786f 100644 --- a/src/cameraserver_c.cpp +++ b/src/cameraserver_c.cpp @@ -79,8 +79,10 @@ CS_Source CS_CreateHTTPSource(const char* name, const char* url, return cs::CreateHTTPSource(name, url, status); } -CS_Source CS_CreateCvSource(const char* name, CS_Status* status) { - return cs::CreateCvSource(name, status); +CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode, + CS_Status* status) { + return cs::CreateCvSource(name, static_cast(*mode), + status); } char* CS_GetSourceName(CS_Source source, CS_Status* status) { @@ -121,6 +123,56 @@ CS_Property* CS_EnumerateSourceProperties(CS_Source source, int* count, return out; } +void CS_GetSourceVideoMode(CS_Source source, CS_VideoMode* mode, + CS_Status* status) { + *mode = cs::GetSourceVideoMode(source, status); +} + +CS_Bool CS_SetSourceVideoMode(CS_Source source, const CS_VideoMode* mode, + CS_Status* status) { + return cs::SetSourceVideoMode( + source, static_cast(*mode), status); +} + +CS_Bool CS_SetSourceVideoModeDiscrete(CS_Source source, + enum CS_PixelFormat pixelFormat, + int width, int height, int fps, + CS_Status* status) { + return cs::SetSourceVideoMode( + source, cs::VideoMode{static_cast( + static_cast(pixelFormat)), + width, height, fps}, + status); +} + +CS_Bool CS_SetSourcePixelFormat(CS_Source source, + enum CS_PixelFormat pixelFormat, + CS_Status* status) { + return cs::SetSourcePixelFormat( + source, + static_cast(static_cast(pixelFormat)), + status); +} + +CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height, + CS_Status* status) { + return cs::SetSourceResolution(source, width, height, status); +} + +CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status) { + return cs::SetSourceFPS(source, fps, status); +} + +CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count, + CS_Status* status) { + auto vec = cs::EnumerateSourceVideoModes(source, status); + CS_VideoMode* out = static_cast( + std::malloc(vec.size() * sizeof(CS_VideoMode))); + *count = vec.size(); + std::copy(vec.begin(), vec.end(), out); + return out; +} + CS_Source CS_CopySource(CS_Source source, CS_Status* status) { return cs::CopySource(source, status); } @@ -325,4 +377,8 @@ void CS_FreeEnumeratedProperties(CS_Property* properties, int count) { std::free(properties); } +void CS_FreeEnumeratedVideoModes(CS_VideoMode* modes, int count) { + std::free(modes); +} + } // extern "C" diff --git a/src/cameraserver_cpp.cpp b/src/cameraserver_cpp.cpp index 4dccbf90e6..aa92e863a1 100644 --- a/src/cameraserver_cpp.cpp +++ b/src/cameraserver_cpp.cpp @@ -147,7 +147,8 @@ CS_Source CreateHTTPSource(llvm::StringRef name, llvm::StringRef url, return 0; // TODO } -CS_Source CreateCvSource(llvm::StringRef name, CS_Status* status) { +CS_Source CreateCvSource(llvm::StringRef name, const VideoMode& mode, + CS_Status* status) { return 0; // TODO } @@ -238,6 +239,64 @@ llvm::ArrayRef EnumerateSourceProperties( return vec; } +VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status) { + auto data = Sources::GetInstance().Get(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return VideoMode{}; + } + return data->source->GetVideoMode(status); +} + +bool SetSourceVideoMode(CS_Source source, const VideoMode& mode, + CS_Status* status) { + auto data = Sources::GetInstance().Get(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->source->SetVideoMode(mode, status); +} + +bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat, + CS_Status* status) { + auto data = Sources::GetInstance().Get(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->source->SetPixelFormat(pixelFormat, status); +} + +bool SetSourceResolution(CS_Source source, int width, int height, + CS_Status* status) { + auto data = Sources::GetInstance().Get(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->source->SetResolution(width, height, status); +} + +bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) { + auto data = Sources::GetInstance().Get(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->source->SetFPS(fps, status); +} + +std::vector EnumerateSourceVideoModes(CS_Source source, + CS_Status* status) { + auto data = Sources::GetInstance().Get(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return std::vector{}; + } + return data->source->EnumerateVideoModes(status); +} + CS_Source CopySource(CS_Source source, CS_Status* status) { if (source == 0) return 0; auto data = Sources::GetInstance().Get(source);