diff --git a/cameraserver/src/main/native/include/cameraserver/CameraServer.h b/cameraserver/src/main/native/include/cameraserver/CameraServer.h index 3e11febb5a..8f384fd60a 100644 --- a/cameraserver/src/main/native/include/cameraserver/CameraServer.h +++ b/cameraserver/src/main/native/include/cameraserver/CameraServer.h @@ -16,6 +16,7 @@ #include #include "cscore.h" +#include "cscore_cv.h" namespace frc { diff --git a/cameraserver/src/main/native/include/vision/VisionRunner.h b/cameraserver/src/main/native/include/vision/VisionRunner.h index a317f80876..610ac4d772 100644 --- a/cameraserver/src/main/native/include/vision/VisionRunner.h +++ b/cameraserver/src/main/native/include/vision/VisionRunner.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2016-2019 FIRST. 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. */ @@ -12,6 +12,7 @@ #include #include "cscore.h" +#include "cscore_cv.h" #include "vision/VisionPipeline.h" namespace frc { diff --git a/cscore/examples/httpcvstream/httpcvstream.cpp b/cscore/examples/httpcvstream/httpcvstream.cpp index db8e0d7b9c..90d61d57c3 100644 --- a/cscore/examples/httpcvstream/httpcvstream.cpp +++ b/cscore/examples/httpcvstream/httpcvstream.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2017-2019 FIRST. 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. */ @@ -11,6 +11,7 @@ #include #include "cscore.h" +#include "cscore_cv.h" int main() { cs::HttpCamera camera{"httpcam", "http://localhost:8081/?action=stream"}; diff --git a/cscore/examples/usbcvstream/usbcvstream.cpp b/cscore/examples/usbcvstream/usbcvstream.cpp index 68796b4557..9a4ab06c52 100644 --- a/cscore/examples/usbcvstream/usbcvstream.cpp +++ b/cscore/examples/usbcvstream/usbcvstream.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2017-2019 FIRST. 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. */ @@ -11,6 +11,7 @@ #include #include "cscore.h" +#include "cscore_cv.h" int main() { cs::UsbCamera camera{"usbcam", 0}; diff --git a/cscore/java-examples/RawCVMatSink.java b/cscore/java-examples/RawCVMatSink.java new file mode 100644 index 0000000000..d9557f718f --- /dev/null +++ b/cscore/java-examples/RawCVMatSink.java @@ -0,0 +1,96 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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.cscore; + +import java.nio.ByteBuffer; + +import org.opencv.core.CvType; +import org.opencv.core.Mat; + +import edu.wpi.cscore.VideoMode.PixelFormat; +import edu.wpi.cscore.raw.RawFrame; + +public class RawCVMatSink extends ImageSink { + RawFrame frame = new RawFrame(); + Mat tmpMat; + ByteBuffer origByteBuffer; + int width; + int height; + int pixelFormat; + int bgrValue = PixelFormat.kBGR.getValue(); + + private int getCVFormat(PixelFormat pixelFormat) { + int type = 0; + switch (pixelFormat) { + case kYUYV: + case kRGB565: + type = CvType.CV_8UC2; + break; + case kBGR: + type = CvType.CV_8UC3; + break; + case kGray: + case kMJPEG: + default: + type = CvType.CV_8UC1; + break; + } + return type; + } + + /** + * Create a sink for accepting OpenCV images. + * WaitForFrame() must be called on the created sink to get each new + * image. + * + * @param name Source name (arbitrary unique identifier) + */ + public RawCVMatSink(String name) { + super(CameraServerJNI.createRawSink(name)); + } + + /** + * Wait for the next frame and get the image. + * Times out (returning 0) after 0.225 seconds. + * The provided image will have three 3-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message) + */ + public long grabFrame(Mat image) { + return grabFrame(image, 0.225); + } + + /** + * Wait for the next frame and get the image. + * Times out (returning 0) after timeout seconds. + * The provided image will have three 3-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in 1 us increments. + */ + public long grabFrame(Mat image, double timeout) { + frame.setWidth(0); + frame.setHeight(0); + frame.setPixelFormat(bgrValue); + long rv = CameraServerJNI.grabSinkFrameTimeout(m_handle, frame, timeout); + if (rv <= 0) { + return rv; + } + + if (frame.getDataByteBuffer() != origByteBuffer || width != frame.getWidth() || height != frame.getHeight() || pixelFormat != frame.getPixelFormat()) { + origByteBuffer = frame.getDataByteBuffer(); + height = frame.getHeight(); + width = frame.getWidth(); + pixelFormat = frame.getPixelFormat(); + tmpMat = new Mat(frame.getHeight(), frame.getWidth(), getCVFormat(VideoMode.getPixelFormatFromInt(pixelFormat)), origByteBuffer); + } + tmpMat.copyTo(image); + return rv; + } +} diff --git a/cscore/java-examples/RawCVMatSource.java b/cscore/java-examples/RawCVMatSource.java new file mode 100644 index 0000000000..65bd7d2fa7 --- /dev/null +++ b/cscore/java-examples/RawCVMatSource.java @@ -0,0 +1,59 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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.cscore; + +import org.opencv.core.Mat; + +import edu.wpi.cscore.VideoMode.PixelFormat; + +public class RawCVMatSource extends ImageSource { + /** + * Create an OpenCV source. + * + * @param name Source name (arbitrary unique identifier) + * @param mode Video mode being generated + */ + public RawCVMatSource(String name, VideoMode mode) { + super(CameraServerJNI.createRawSource(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 RawCVMatSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) { + super(CameraServerJNI.createRawSource(name, pixelFormat.getValue(), width, height, fps)); + } + + /** + * Put an OpenCV image and notify sinks. + * + *

Only 8-bit single-channel or 3-channel (with BGR channel order) images + * are supported. If the format, depth or channel order is different, use + * Mat.convertTo() and/or cvtColor() to convert it first. + * + * @param image OpenCV image + */ + public void putFrame(Mat image) { + int channels = image.channels(); + if (channels != 1 && channels != 3) { + throw new VideoException("Unsupported Image Type"); + } + int imgType = channels == 1 ? PixelFormat.kGray.getValue() : PixelFormat.kBGR.getValue(); + CameraServerJNI.putRawSourceFrame(m_handle, image.dataAddr(), image.width(), image.height(), imgType, (int)image.total() * channels); + } +} diff --git a/cscore/src/dev/java/edu/wpi/cscore/DevMain.java b/cscore/src/dev/java/edu/wpi/cscore/DevMain.java index e7fd516062..51bfd2645b 100644 --- a/cscore/src/dev/java/edu/wpi/cscore/DevMain.java +++ b/cscore/src/dev/java/edu/wpi/cscore/DevMain.java @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2017-2019 FIRST. 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. */ diff --git a/cscore/src/dev/native/cpp/main.cpp b/cscore/src/dev/native/cpp/main.cpp index b95de474c7..f27f61fdae 100644 --- a/cscore/src/dev/native/cpp/main.cpp +++ b/cscore/src/dev/native/cpp/main.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2017-2019 FIRST. 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. */ diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerCvJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerCvJNI.java new file mode 100644 index 0000000000..d10d5b0ed7 --- /dev/null +++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerCvJNI.java @@ -0,0 +1,48 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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.cscore; + +import java.io.IOException; + +import org.opencv.core.Core; + +import edu.wpi.first.wpiutil.RuntimeLoader; + +public class CameraServerCvJNI { + static boolean cvLibraryLoaded = false; + + static RuntimeLoader cvLoader = null; + + static { + String opencvName = Core.NATIVE_LIBRARY_NAME; + if (!cvLibraryLoaded) { + CameraServerJNI.forceLoad(); + try { + cvLoader = new RuntimeLoader<>(opencvName, RuntimeLoader.getDefaultExtractionRoot(), Core.class); + cvLoader.loadLibraryHashed(); + } catch (IOException ex) { + ex.printStackTrace(); + System.exit(1); + } + cvLibraryLoaded = true; + } + } + + public static void forceLoad() {} + + public static native int createCvSource(String name, int pixelFormat, int width, int height, int fps); + + public static native void putSourceFrame(int source, long imageNativeObj); + + public static native int createCvSink(String name); + //public static native int createCvSinkCallback(String name, + // void (*processFrame)(long time)); + + public static native long grabSinkFrame(int sink, long imageNativeObj); + public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout); +} diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java index 0488c7743e..9f4fea6c36 100644 --- a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java +++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java @@ -8,18 +8,16 @@ package edu.wpi.cscore; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.function.Consumer; -import org.opencv.core.Core; - +import edu.wpi.cscore.raw.RawFrame; import edu.wpi.first.wpiutil.RuntimeLoader; public class CameraServerJNI { static boolean libraryLoaded = false; - static boolean cvLibraryLoaded = false; static RuntimeLoader loader = null; - static RuntimeLoader cvLoader = null; static { if (!libraryLoaded) { @@ -32,18 +30,6 @@ public class CameraServerJNI { } libraryLoaded = true; } - - String opencvName = Core.NATIVE_LIBRARY_NAME; - if (!cvLibraryLoaded) { - try { - cvLoader = new RuntimeLoader<>(opencvName, RuntimeLoader.getDefaultExtractionRoot(), Core.class); - cvLoader.loadLibraryHashed(); - } catch (IOException ex) { - ex.printStackTrace(); - System.exit(1); - } - cvLibraryLoaded = true; - } } public static void forceLoad() {} @@ -70,7 +56,7 @@ public class CameraServerJNI { public static native int createUsbCameraPath(String name, String path); public static native int createHttpCamera(String name, String url, int kind); public static native int createHttpCameraMulti(String name, String[] urls, int kind); - public static native int createCvSource(String name, int pixelFormat, int width, int height, int fps); + public static native int createRawSource(String name, int pixelFormat, int width, int height, int fps); // // Source Functions @@ -122,9 +108,13 @@ public class CameraServerJNI { public static native String[] getHttpCameraUrls(int source); // - // OpenCV Source Functions + // Image Source Functions // - public static native void putSourceFrame(int source, long imageNativeObj); + public static native void putRawSourceFrameBB(int source, ByteBuffer data, int width, int height, int pixelFormat, int totalData); + public static native void putRawSourceFrame(int source, long data, int width, int height, int pixelFormat, int totalData); + public static void putRawSourceFrame(int source, RawFrame raw) { + putRawSourceFrame(source, raw.getDataPtr(), raw.getWidth(), raw.getHeight(), raw.getPixelFormat(), raw.getTotalData()); + } public static native void notifySourceError(int source, String msg); public static native void setSourceConnected(int source, boolean connected); public static native void setSourceDescription(int source, String description); @@ -135,9 +125,8 @@ public class CameraServerJNI { // Sink Creation Functions // public static native int createMjpegServer(String name, String listenAddress, int port); - public static native int createCvSink(String name); - //public static native int createCvSinkCallback(String name, - // void (*processFrame)(long time)); + + public static native int createRawSink(String name); // // Sink Functions @@ -162,11 +151,19 @@ public class CameraServerJNI { public static native int getMjpegServerPort(int sink); // - // OpenCV Sink Functions + // Image Sink Functions // public static native void setSinkDescription(int sink, String description); - public static native long grabSinkFrame(int sink, long imageNativeObj); - public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout); + + private static native long grabRawSinkFrameImpl(int sink, RawFrame rawFrame, long rawFramePtr, ByteBuffer byteBuffer, int width, int height, int pixelFormat); + private static native long grabRawSinkFrameTimeoutImpl(int sink, RawFrame rawFrame, long rawFramePtr, ByteBuffer byteBuffer, int width, int height, int pixelFormat, double timeout); + + public static long grabSinkFrame(int sink, RawFrame rawFrame) { + return grabRawSinkFrameImpl(sink, rawFrame, rawFrame.getFramePtr(), rawFrame.getDataByteBuffer(), rawFrame.getWidth(), rawFrame.getHeight(), rawFrame.getPixelFormat()); + } + public static long grabSinkFrameTimeout(int sink, RawFrame rawFrame, double timeout) { + return grabRawSinkFrameTimeoutImpl(sink, rawFrame, rawFrame.getFramePtr(), rawFrame.getDataByteBuffer(), rawFrame.getWidth(), rawFrame.getHeight(), rawFrame.getPixelFormat(), timeout); + } public static native String getSinkError(int sink); public static native void setSinkEnabled(int sink, boolean enabled); @@ -228,4 +225,8 @@ public class CameraServerJNI { public static native String getHostname(); public static native String[] getNetworkInterfaces(); + + public static native long allocateRawFrame(); + + public static native void freeRawFrame(long frame); } diff --git a/cscore/src/main/java/edu/wpi/cscore/CvSink.java b/cscore/src/main/java/edu/wpi/cscore/CvSink.java index c2c7d0e51b..f12dcc7428 100644 --- a/cscore/src/main/java/edu/wpi/cscore/CvSink.java +++ b/cscore/src/main/java/edu/wpi/cscore/CvSink.java @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2016-2019 FIRST. 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. */ @@ -11,8 +11,11 @@ import org.opencv.core.Mat; /** * A sink for user code to accept video frames as OpenCV images. + * These sinks require the WPILib OpenCV builds. + * For an alternate OpenCV, see the documentation how to build your own + * with RawSink. */ -public class CvSink extends VideoSink { +public class CvSink extends ImageSink { /** * Create a sink for accepting OpenCV images. * WaitForFrame() must be called on the created sink to get each new @@ -21,7 +24,7 @@ public class CvSink extends VideoSink { * @param name Source name (arbitrary unique identifier) */ public CvSink(String name) { - super(CameraServerJNI.createCvSink(name)); + super(CameraServerCvJNI.createCvSink(name)); } /// Create a sink for accepting OpenCV images in a separate thread. @@ -37,15 +40,6 @@ public class CvSink extends VideoSink { // super(CameraServerJNI.createCvSinkCallback(name, processFrame)); //} - /** - * Set sink description. - * - * @param description Description - */ - public void setDescription(String description) { - CameraServerJNI.setSinkDescription(m_handle, description); - } - /** * Wait for the next frame and get the image. * Times out (returning 0) after 0.225 seconds. @@ -67,7 +61,7 @@ public class CvSink extends VideoSink { * message); the frame time is in 1 us increments. */ public long grabFrame(Mat image, double timeout) { - return CameraServerJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout); + return CameraServerCvJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout); } /** @@ -78,24 +72,6 @@ public class CvSink extends VideoSink { * message); the frame time is in 1 us increments. */ public long grabFrameNoTimeout(Mat image) { - return CameraServerJNI.grabSinkFrame(m_handle, image.nativeObj); - } - - /** - * Get error string. Call this if WaitForFrame() returns 0 to determine - * what the error is. - */ - public String getError() { - return CameraServerJNI.getSinkError(m_handle); - } - - /** - * Enable or disable getting new frames. - * Disabling will cause processFrame (for callback-based CvSinks) to not - * be called and WaitForFrame() to not return. This can be used to save - * processor resources when frames are not needed. - */ - public void setEnabled(boolean enabled) { - CameraServerJNI.setSinkEnabled(m_handle, enabled); + return CameraServerCvJNI.grabSinkFrame(m_handle, image.nativeObj); } } diff --git a/cscore/src/main/java/edu/wpi/cscore/CvSource.java b/cscore/src/main/java/edu/wpi/cscore/CvSource.java index 47cd409538..c04d197bf6 100644 --- a/cscore/src/main/java/edu/wpi/cscore/CvSource.java +++ b/cscore/src/main/java/edu/wpi/cscore/CvSource.java @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2016-2019 FIRST. 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. */ @@ -11,8 +11,11 @@ import org.opencv.core.Mat; /** * A source that represents a video camera. + * These sources require the WPILib OpenCV builds. + * For an alternate OpenCV, see the documentation how to build your own + * with RawSource. */ -public class CvSource extends VideoSource { +public class CvSource extends ImageSource { /** * Create an OpenCV source. * @@ -20,7 +23,7 @@ public class CvSource extends VideoSource { * @param mode Video mode being generated */ public CvSource(String name, VideoMode mode) { - super(CameraServerJNI.createCvSource(name, + super(CameraServerCvJNI.createCvSource(name, mode.pixelFormat.getValue(), mode.width, mode.height, @@ -37,7 +40,7 @@ public class CvSource extends VideoSource { * @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)); + super(CameraServerCvJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps)); } /** @@ -50,154 +53,7 @@ public class CvSource extends VideoSource { * @param image OpenCV image */ public void putFrame(Mat image) { - CameraServerJNI.putSourceFrame(m_handle, image.nativeObj); + CameraServerCvJNI.putSourceFrame(m_handle, image.nativeObj); } - /** - * Signal sinks that an error has occurred. This should be called instead - * of NotifyFrame when an error occurs. - */ - public void notifyError(String msg) { - CameraServerJNI.notifySourceError(m_handle, msg); - } - - /** - * Set source connection status. Defaults to true. - * - * @param connected True for connected, false for disconnected - */ - public void setConnected(boolean connected) { - CameraServerJNI.setSourceConnected(m_handle, connected); - } - - /** - * Set source description. - * - * @param description Description - */ - public void setDescription(String description) { - CameraServerJNI.setSourceDescription(m_handle, description); - } - - /** - * Create a property. - * - * @param name Property name - * @param kind Property kind - * @param minimum Minimum value - * @param maximum Maximum value - * @param step Step value - * @param defaultValue Default value - * @param value Current value - * @return Property - */ - public VideoProperty createProperty(String name, - VideoProperty.Kind kind, - int minimum, - int maximum, - int step, - int defaultValue, - int value) { - return new VideoProperty( - CameraServerJNI.createSourceProperty(m_handle, - name, - kind.getValue(), - minimum, - maximum, - step, - defaultValue, - value)); - } - - /** - * Create an integer property. - * - * @param name Property name - * @param minimum Minimum value - * @param maximum Maximum value - * @param step Step value - * @param defaultValue Default value - * @param value Current value - * @return Property - */ - public VideoProperty createIntegerProperty(String name, - int minimum, - int maximum, - int step, - int defaultValue, - int value) { - return new VideoProperty( - CameraServerJNI.createSourceProperty(m_handle, - name, - VideoProperty.Kind.kInteger.getValue(), - minimum, - maximum, - step, - defaultValue, - value)); - } - - /** - * Create a boolean property. - * - * @param name Property name - * @param defaultValue Default value - * @param value Current value - * @return Property - */ - public VideoProperty createBooleanProperty(String name, boolean defaultValue, boolean value) { - return new VideoProperty( - CameraServerJNI.createSourceProperty(m_handle, - name, - VideoProperty.Kind.kBoolean.getValue(), - 0, - 1, - 1, - defaultValue ? 1 : 0, - value ? 1 : 0)); - } - - /** - * Create a string property. - * - * @param name Property name - * @param value Current value - * @return Property - */ - public VideoProperty createStringProperty(String name, String value) { - VideoProperty prop = new VideoProperty( - CameraServerJNI.createSourceProperty(m_handle, - name, - VideoProperty.Kind.kString.getValue(), - 0, - 0, - 0, - 0, - 0)); - prop.setString(value); - return prop; - } - - /** - * Configure enum property choices. - * - * @param property Property - * @param choices Choices - */ - public void setEnumPropertyChoices(VideoProperty property, String[] choices) { - CameraServerJNI.setSourceEnumPropertyChoices(m_handle, property.m_handle, choices); - } - - /** - * Configure enum property choices. - * - * @param property Property - * @param choices Choices - * @deprecated Use {@code setEnumPropertyChoices} instead. - */ - @Deprecated - @SuppressWarnings("MethodName") - public void SetEnumPropertyChoices(VideoProperty property, String[] choices) { - setEnumPropertyChoices(property, choices); - } } diff --git a/cscore/src/main/java/edu/wpi/cscore/ImageSink.java b/cscore/src/main/java/edu/wpi/cscore/ImageSink.java new file mode 100644 index 0000000000..f755fb677b --- /dev/null +++ b/cscore/src/main/java/edu/wpi/cscore/ImageSink.java @@ -0,0 +1,41 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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.cscore; + +public abstract class ImageSink extends VideoSink { + protected ImageSink(int handle) { + super(handle); + } + + /** + * Set sink description. + * + * @param description Description + */ + public void setDescription(String description) { + CameraServerJNI.setSinkDescription(m_handle, description); + } + + /** + * Get error string. Call this if WaitForFrame() returns 0 to determine + * what the error is. + */ + public String getError() { + return CameraServerJNI.getSinkError(m_handle); + } + + /** + * Enable or disable getting new frames. + * Disabling will cause processFrame (for callback-based CvSinks) to not + * be called and WaitForFrame() to not return. This can be used to save + * processor resources when frames are not needed. + */ + public void setEnabled(boolean enabled) { + CameraServerJNI.setSinkEnabled(m_handle, enabled); + } +} diff --git a/cscore/src/main/java/edu/wpi/cscore/ImageSource.java b/cscore/src/main/java/edu/wpi/cscore/ImageSource.java new file mode 100644 index 0000000000..3787516dd6 --- /dev/null +++ b/cscore/src/main/java/edu/wpi/cscore/ImageSource.java @@ -0,0 +1,162 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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.cscore; + +public abstract class ImageSource extends VideoSource { + protected ImageSource(int handle) { + super(handle); + } + + /** + * Signal sinks that an error has occurred. This should be called instead + * of NotifyFrame when an error occurs. + */ + public void notifyError(String msg) { + CameraServerJNI.notifySourceError(m_handle, msg); + } + + /** + * Set source connection status. Defaults to true. + * + * @param connected True for connected, false for disconnected + */ + public void setConnected(boolean connected) { + CameraServerJNI.setSourceConnected(m_handle, connected); + } + + /** + * Set source description. + * + * @param description Description + */ + public void setDescription(String description) { + CameraServerJNI.setSourceDescription(m_handle, description); + } + + /** + * Create a property. + * + * @param name Property name + * @param kind Property kind + * @param minimum Minimum value + * @param maximum Maximum value + * @param step Step value + * @param defaultValue Default value + * @param value Current value + * @return Property + */ + public VideoProperty createProperty(String name, + VideoProperty.Kind kind, + int minimum, + int maximum, + int step, + int defaultValue, + int value) { + return new VideoProperty( + CameraServerJNI.createSourceProperty(m_handle, + name, + kind.getValue(), + minimum, + maximum, + step, + defaultValue, + value)); + } + + /** + * Create an integer property. + * + * @param name Property name + * @param minimum Minimum value + * @param maximum Maximum value + * @param step Step value + * @param defaultValue Default value + * @param value Current value + * @return Property + */ + public VideoProperty createIntegerProperty(String name, + int minimum, + int maximum, + int step, + int defaultValue, + int value) { + return new VideoProperty( + CameraServerJNI.createSourceProperty(m_handle, + name, + VideoProperty.Kind.kInteger.getValue(), + minimum, + maximum, + step, + defaultValue, + value)); + } + + /** + * Create a boolean property. + * + * @param name Property name + * @param defaultValue Default value + * @param value Current value + * @return Property + */ + public VideoProperty createBooleanProperty(String name, boolean defaultValue, boolean value) { + return new VideoProperty( + CameraServerJNI.createSourceProperty(m_handle, + name, + VideoProperty.Kind.kBoolean.getValue(), + 0, + 1, + 1, + defaultValue ? 1 : 0, + value ? 1 : 0)); + } + + /** + * Create a string property. + * + * @param name Property name + * @param value Current value + * @return Property + */ + public VideoProperty createStringProperty(String name, String value) { + VideoProperty prop = new VideoProperty( + CameraServerJNI.createSourceProperty(m_handle, + name, + VideoProperty.Kind.kString.getValue(), + 0, + 0, + 0, + 0, + 0)); + prop.setString(value); + return prop; + } + + /** + * Configure enum property choices. + * + * @param property Property + * @param choices Choices + */ + public void setEnumPropertyChoices(VideoProperty property, String[] choices) { + CameraServerJNI.setSourceEnumPropertyChoices(m_handle, property.m_handle, choices); + } + + /** + * Configure enum property choices. + * + * @param property Property + * @param choices Choices + * @deprecated Use {@code setEnumPropertyChoices} instead. + */ + @Deprecated + @SuppressWarnings("MethodName") + public void SetEnumPropertyChoices(VideoProperty property, String[] choices) { + setEnumPropertyChoices(property, choices); + } +} diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoSink.java b/cscore/src/main/java/edu/wpi/cscore/VideoSink.java index e494f446c2..8aa4199d7f 100644 --- a/cscore/src/main/java/edu/wpi/cscore/VideoSink.java +++ b/cscore/src/main/java/edu/wpi/cscore/VideoSink.java @@ -14,7 +14,7 @@ package edu.wpi.cscore; */ public class VideoSink implements AutoCloseable { public enum Kind { - kUnknown(0), kMjpeg(2), kCv(4); + kUnknown(0), kMjpeg(2), kCv(4), kRaw(8); @SuppressWarnings("MemberName") private final int value; diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoSource.java b/cscore/src/main/java/edu/wpi/cscore/VideoSource.java index cac344ced1..d301ad0566 100644 --- a/cscore/src/main/java/edu/wpi/cscore/VideoSource.java +++ b/cscore/src/main/java/edu/wpi/cscore/VideoSource.java @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2016-2019 FIRST. 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. */ @@ -14,7 +14,7 @@ package edu.wpi.cscore; */ public class VideoSource implements AutoCloseable { public enum Kind { - kUnknown(0), kUsb(1), kHttp(2), kCv(4); + kUnknown(0), kUsb(1), kHttp(2), kCv(4), kRaw(8); @SuppressWarnings("MemberName") private final int value; diff --git a/cscore/src/main/java/edu/wpi/cscore/raw/RawFrame.java b/cscore/src/main/java/edu/wpi/cscore/raw/RawFrame.java new file mode 100644 index 0000000000..0e7a9ce52d --- /dev/null +++ b/cscore/src/main/java/edu/wpi/cscore/raw/RawFrame.java @@ -0,0 +1,130 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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.cscore.raw; + +import java.nio.ByteBuffer; + +import edu.wpi.cscore.CameraServerJNI; + +/** + * Class for storing raw frame data between image read call. + * + *

Data is reused for each frame read, rather then reallocating every frame. + */ +public class RawFrame implements AutoCloseable { + private final long m_framePtr; + private ByteBuffer m_dataByteBuffer; + private long m_dataPtr; + private int m_totalData; + private int m_width; + private int m_height; + private int m_pixelFormat; + + /** + * Construct a new RawFrame. + */ + public RawFrame() { + m_framePtr = CameraServerJNI.allocateRawFrame(); + } + + /** + * Close the RawFrame, releasing native resources. + * Any images currently using the data will be invalidated. + */ + @Override + public void close() { + CameraServerJNI.freeRawFrame(m_framePtr); + } + + /** + * Called from JNI to set data in class. + */ + public void setData(ByteBuffer dataByteBuffer, long dataPtr, int totalData, + int width, int height, int pixelFormat) { + m_dataByteBuffer = dataByteBuffer; + m_dataPtr = dataPtr; + m_totalData = totalData; + m_width = width; + m_height = height; + m_pixelFormat = pixelFormat; + } + + /** + * Get the pointer to native representation of this frame. + */ + public long getFramePtr() { + return m_framePtr; + } + + /** + * Get a ByteBuffer pointing to the frame data. + * This ByteBuffer is backed by the frame directly. Its lifetime is controlled by + * the frame. If a new frame gets read, it will overwrite the current one. + */ + public ByteBuffer getDataByteBuffer() { + return m_dataByteBuffer; + } + + /** + * Get a long (is a char* in native code) pointing to the frame data. + * This pointer is backed by the frame directly. Its lifetime is controlled by + * the frame. If a new frame gets read, it will overwrite the current one. + */ + public long getDataPtr() { + return m_dataPtr; + } + + /** + * Get the total length of the data stored in the frame. + */ + public int getTotalData() { + return m_totalData; + } + + /** + * Get the width of the frame. + */ + public int getWidth() { + return m_width; + } + + /** + * Set the width of the frame. + */ + public void setWidth(int width) { + this.m_width = width; + } + + /** + * Get the height of the frame. + */ + public int getHeight() { + return m_height; + } + + /** + * Set the height of the frame. + */ + public void setHeight(int height) { + this.m_height = height; + } + + /** + * Get the PixelFormat of the frame. + */ + public int getPixelFormat() { + return m_pixelFormat; + } + + /** + * Set the PixelFormat of the frame. + */ + public void setPixelFormat(int pixelFormat) { + this.m_pixelFormat = pixelFormat; + } +} diff --git a/cscore/src/main/java/edu/wpi/cscore/raw/RawSink.java b/cscore/src/main/java/edu/wpi/cscore/raw/RawSink.java new file mode 100644 index 0000000000..535f356054 --- /dev/null +++ b/cscore/src/main/java/edu/wpi/cscore/raw/RawSink.java @@ -0,0 +1,68 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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.cscore.raw; + +import edu.wpi.cscore.CameraServerJNI; +import edu.wpi.cscore.ImageSink; + +/** + * A sink for user code to accept video frames as raw bytes. + * + *

This is a complex API, most cases should use CvSink. + */ +public class RawSink extends ImageSink { + /** + * Create a sink for accepting raw images. + * + *

grabFrame() must be called on the created sink to get each new + * image. + * + * @param name Source name (arbitrary unique identifier) + */ + public RawSink(String name) { + super(CameraServerJNI.createRawSink(name)); + } + + /** + * Wait for the next frame and get the image. + * Times out (returning 0) after 0.225 seconds. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call getError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + protected long grabFrame(RawFrame frame) { + return grabFrame(frame, 0.225); + } + + /** + * Wait for the next frame and get the image. + * Times out (returning 0) after timeout seconds. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call getError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + protected long grabFrame(RawFrame frame, double timeout) { + return CameraServerJNI.grabSinkFrameTimeout(m_handle, frame, timeout); + } + + /** + * Wait for the next frame and get the image. May block forever. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call getError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + protected long grabFrameNoTimeout(RawFrame frame) { + return CameraServerJNI.grabSinkFrame(m_handle, frame); + } +} diff --git a/cscore/src/main/java/edu/wpi/cscore/raw/RawSource.java b/cscore/src/main/java/edu/wpi/cscore/raw/RawSource.java new file mode 100644 index 0000000000..9dfb3f3452 --- /dev/null +++ b/cscore/src/main/java/edu/wpi/cscore/raw/RawSource.java @@ -0,0 +1,85 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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.cscore.raw; + +import edu.wpi.cscore.CameraServerJNI; +import edu.wpi.cscore.ImageSource; +import edu.wpi.cscore.VideoMode; + +/** + * A source for user code to provide video frames as raw bytes. + * + *

This is a complex API, most cases should use CvSource. + */ +public class RawSource extends ImageSource { + /** + * Create a raw frame source. + * + * @param name Source name (arbitrary unique identifier) + * @param mode Video mode being generated + */ + public RawSource(String name, VideoMode mode) { + super(CameraServerJNI.createRawSource(name, + mode.pixelFormat.getValue(), + mode.width, mode.height, + mode.fps)); + } + + /** + * Create a raw frame source. + * + * @param name Source name (arbitrary unique identifier) + * @param pixelFormat Pixel format + * @param width width + * @param height height + * @param fps fps + */ + public RawSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) { + super(CameraServerJNI.createRawSource(name, + pixelFormat.getValue(), + width, height, + fps)); + } + + /** + * Put a raw image and notify sinks. + * + * @param image raw frame image + */ + protected void putFrame(RawFrame image) { + CameraServerJNI.putRawSourceFrame(m_handle, image); + } + + /** + * Put a raw image and notify sinks. + * + * @param data raw frame data pointer + * @param width frame width + * @param height frame height + * @param pixelFormat pixel format + * @param totalData length of data in total + */ + protected void putFrame(long data, int width, int height, int pixelFormat, int totalData) { + CameraServerJNI.putRawSourceFrame(m_handle, data, width, height, pixelFormat, totalData); + } + + /** + * Put a raw image and notify sinks. + * + * @param data raw frame data pointer + * @param width frame width + * @param height frame height + * @param pixelFormat pixel format + * @param totalData length of data in total + */ + protected void putFrame(long data, int width, int height, VideoMode.PixelFormat pixelFormat, + int totalData) { + CameraServerJNI.putRawSourceFrame(m_handle, data, width, height, pixelFormat.getValue(), + totalData); + } +} diff --git a/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp b/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp new file mode 100644 index 0000000000..d4c4ecc827 --- /dev/null +++ b/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp @@ -0,0 +1,109 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#include "ConfigurableSourceImpl.h" + +#include + +#include "Handle.h" +#include "Instance.h" +#include "Log.h" +#include "Notifier.h" + +using namespace cs; + +ConfigurableSourceImpl::ConfigurableSourceImpl(const wpi::Twine& name, + wpi::Logger& logger, + Notifier& notifier, + Telemetry& telemetry, + const VideoMode& mode) + : SourceImpl{name, logger, notifier, telemetry} { + m_mode = mode; + m_videoModes.push_back(m_mode); +} + +ConfigurableSourceImpl::~ConfigurableSourceImpl() {} + +void ConfigurableSourceImpl::Start() { + m_notifier.NotifySource(*this, CS_SOURCE_CONNECTED); + m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED); + m_notifier.NotifySourceVideoMode(*this, m_mode); +} + +bool ConfigurableSourceImpl::SetVideoMode(const VideoMode& mode, + CS_Status* status) { + { + std::lock_guard lock(m_mutex); + m_mode = mode; + m_videoModes[0] = mode; + } + m_notifier.NotifySourceVideoMode(*this, mode); + return true; +} + +void ConfigurableSourceImpl::NumSinksChanged() { + // ignore +} + +void ConfigurableSourceImpl::NumSinksEnabledChanged() { + // ignore +} + +void ConfigurableSourceImpl::NotifyError(const wpi::Twine& msg) { + PutError(msg, wpi::Now()); +} + +int ConfigurableSourceImpl::CreateProperty(const wpi::Twine& name, + CS_PropertyKind kind, int minimum, + int maximum, int step, + int defaultValue, int value) { + std::lock_guard lock(m_mutex); + int ndx = CreateOrUpdateProperty(name, + [=] { + return wpi::make_unique( + name, kind, minimum, maximum, step, + defaultValue, value); + }, + [&](PropertyImpl& prop) { + // update all but value + prop.propKind = kind; + prop.minimum = minimum; + prop.maximum = maximum; + prop.step = step; + prop.defaultValue = defaultValue; + value = prop.value; + }); + m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx, + kind, value, wpi::Twine{}); + return ndx; +} + +int ConfigurableSourceImpl::CreateProperty( + const wpi::Twine& name, CS_PropertyKind kind, int minimum, int maximum, + int step, int defaultValue, int value, + std::function onChange) { + // TODO + return 0; +} + +void ConfigurableSourceImpl::SetEnumPropertyChoices( + int property, wpi::ArrayRef choices, CS_Status* status) { + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) { + *status = CS_INVALID_PROPERTY; + return; + } + if (prop->propKind != CS_PROP_ENUM) { + *status = CS_WRONG_PROPERTY_TYPE; + return; + } + prop->enumChoices = choices; + m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, + prop->name, property, CS_PROP_ENUM, + prop->value, wpi::Twine{}); +} diff --git a/cscore/src/main/native/cpp/ConfigurableSourceImpl.h b/cscore/src/main/native/cpp/ConfigurableSourceImpl.h new file mode 100644 index 0000000000..a10f27e1db --- /dev/null +++ b/cscore/src/main/native/cpp/ConfigurableSourceImpl.h @@ -0,0 +1,56 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2016-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CSCORE_CONFIGURABLESOURCEIMPL_H_ +#define CSCORE_CONFIGURABLESOURCEIMPL_H_ + +#include +#include +#include +#include +#include + +#include +#include + +#include "SourceImpl.h" + +namespace cs { + +class ConfigurableSourceImpl : public SourceImpl { + protected: + ConfigurableSourceImpl(const wpi::Twine& name, wpi::Logger& logger, + Notifier& notifier, Telemetry& telemetry, + const VideoMode& mode); + + public: + ~ConfigurableSourceImpl() override; + + void Start() override; + + bool SetVideoMode(const VideoMode& mode, CS_Status* status) override; + + void NumSinksChanged() override; + void NumSinksEnabledChanged() override; + + // OpenCV-specific functions + void NotifyError(const wpi::Twine& msg); + int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum, + int maximum, int step, int defaultValue, int value); + int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum, + int maximum, int step, int defaultValue, int value, + std::function onChange); + void SetEnumPropertyChoices(int property, wpi::ArrayRef choices, + CS_Status* status); + + private: + std::atomic_bool m_connected{true}; +}; + +} // namespace cs + +#endif // CSCORE_CONFIGURABLESOURCEIMPL_H_ diff --git a/cscore/src/main/native/cpp/CvSinkImpl.cpp b/cscore/src/main/native/cpp/CvSinkImpl.cpp index 17dbd18a74..0b012c3343 100644 --- a/cscore/src/main/native/cpp/CvSinkImpl.cpp +++ b/cscore/src/main/native/cpp/CvSinkImpl.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2016-2019 FIRST. 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. */ @@ -138,10 +138,12 @@ CS_Sink CreateCvSinkCallback(const wpi::Twine& name, inst.telemetry, processFrame)); } +static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW; + void SetSinkDescription(CS_Sink sink, const wpi::Twine& description, CS_Status* status) { auto data = Instance::GetInstance().GetSink(sink); - if (!data || data->kind != CS_SINK_CV) { + if (!data || (data->kind & SinkMask) == 0) { *status = CS_INVALID_HANDLE; return; } @@ -169,7 +171,7 @@ uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout, std::string GetSinkError(CS_Sink sink, CS_Status* status) { auto data = Instance::GetInstance().GetSink(sink); - if (!data || data->kind != CS_SINK_CV) { + if (!data || (data->kind & SinkMask) == 0) { *status = CS_INVALID_HANDLE; return std::string{}; } @@ -179,7 +181,7 @@ std::string GetSinkError(CS_Sink sink, CS_Status* status) { wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl& buf, CS_Status* status) { auto data = Instance::GetInstance().GetSink(sink); - if (!data || data->kind != CS_SINK_CV) { + if (!data || (data->kind & SinkMask) == 0) { *status = CS_INVALID_HANDLE; return wpi::StringRef{}; } @@ -188,7 +190,7 @@ wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl& buf, void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) { auto data = Instance::GetInstance().GetSink(sink); - if (!data || data->kind != CS_SINK_CV) { + if (!data || (data->kind & SinkMask) == 0) { *status = CS_INVALID_HANDLE; return; } diff --git a/cscore/src/main/native/cpp/CvSourceImpl.cpp b/cscore/src/main/native/cpp/CvSourceImpl.cpp index 449a9ee17d..9d69f5a62b 100644 --- a/cscore/src/main/native/cpp/CvSourceImpl.cpp +++ b/cscore/src/main/native/cpp/CvSourceImpl.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2016-2019 FIRST. 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. */ @@ -25,37 +25,10 @@ using namespace cs; CvSourceImpl::CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier, Telemetry& telemetry, const VideoMode& mode) - : SourceImpl{name, logger, notifier, telemetry} { - m_mode = mode; - m_videoModes.push_back(m_mode); -} + : ConfigurableSourceImpl{name, logger, notifier, telemetry, mode} {} CvSourceImpl::~CvSourceImpl() {} -void CvSourceImpl::Start() { - m_notifier.NotifySource(*this, CS_SOURCE_CONNECTED); - m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED); - m_notifier.NotifySourceVideoMode(*this, m_mode); -} - -bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) { - { - std::lock_guard lock(m_mutex); - m_mode = mode; - m_videoModes[0] = mode; - } - m_notifier.NotifySourceVideoMode(*this, mode); - return true; -} - -void CvSourceImpl::NumSinksChanged() { - // ignore -} - -void CvSourceImpl::NumSinksEnabledChanged() { - // ignore -} - void CvSourceImpl::PutFrame(cv::Mat& image) { // We only support 8-bit images; convert if necessary. cv::Mat finalImage; @@ -89,61 +62,6 @@ void CvSourceImpl::PutFrame(cv::Mat& image) { SourceImpl::PutFrame(std::move(dest), wpi::Now()); } -void CvSourceImpl::NotifyError(const wpi::Twine& msg) { - PutError(msg, wpi::Now()); -} - -int CvSourceImpl::CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, - int minimum, int maximum, int step, - int defaultValue, int value) { - std::lock_guard lock(m_mutex); - int ndx = CreateOrUpdateProperty(name, - [=] { - return wpi::make_unique( - name, kind, minimum, maximum, step, - defaultValue, value); - }, - [&](PropertyImpl& prop) { - // update all but value - prop.propKind = kind; - prop.minimum = minimum; - prop.maximum = maximum; - prop.step = step; - prop.defaultValue = defaultValue; - value = prop.value; - }); - m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx, - kind, value, wpi::Twine{}); - return ndx; -} - -int CvSourceImpl::CreateProperty( - const wpi::Twine& name, CS_PropertyKind kind, int minimum, int maximum, - int step, int defaultValue, int value, - std::function onChange) { - // TODO - return 0; -} - -void CvSourceImpl::SetEnumPropertyChoices(int property, - wpi::ArrayRef choices, - CS_Status* status) { - std::lock_guard lock(m_mutex); - auto prop = GetProperty(property); - if (!prop) { - *status = CS_INVALID_PROPERTY; - return; - } - if (prop->propKind != CS_PROP_ENUM) { - *status = CS_WRONG_PROPERTY_TYPE; - return; - } - prop->enumChoices = choices; - m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, - prop->name, property, CS_PROP_ENUM, - prop->value, wpi::Twine{}); -} - namespace cs { CS_Source CreateCvSource(const wpi::Twine& name, const VideoMode& mode, @@ -163,10 +81,12 @@ void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) { static_cast(*data->source).PutFrame(image); } +static constexpr unsigned SourceMask = CS_SINK_CV | CS_SINK_RAW; + void NotifySourceError(CS_Source source, const wpi::Twine& msg, CS_Status* status) { auto data = Instance::GetInstance().GetSource(source); - if (!data || data->kind != CS_SOURCE_CV) { + if (!data || (data->kind & SourceMask) == 0) { *status = CS_INVALID_HANDLE; return; } @@ -175,7 +95,7 @@ void NotifySourceError(CS_Source source, const wpi::Twine& msg, void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) { auto data = Instance::GetInstance().GetSource(source); - if (!data || data->kind != CS_SOURCE_CV) { + if (!data || (data->kind & SourceMask) == 0) { *status = CS_INVALID_HANDLE; return; } @@ -185,7 +105,7 @@ void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) { void SetSourceDescription(CS_Source source, const wpi::Twine& description, CS_Status* status) { auto data = Instance::GetInstance().GetSource(source); - if (!data || data->kind != CS_SOURCE_CV) { + if (!data || (data->kind & SourceMask) == 0) { *status = CS_INVALID_HANDLE; return; } @@ -197,7 +117,7 @@ CS_Property CreateSourceProperty(CS_Source source, const wpi::Twine& name, int step, int defaultValue, int value, CS_Status* status) { auto data = Instance::GetInstance().GetSource(source); - if (!data || data->kind != CS_SOURCE_CV) { + if (!data || (data->kind & SourceMask) == 0) { *status = CS_INVALID_HANDLE; return -1; } @@ -212,7 +132,7 @@ CS_Property CreateSourcePropertyCallback( int maximum, int step, int defaultValue, int value, std::function onChange, CS_Status* status) { auto data = Instance::GetInstance().GetSource(source); - if (!data || data->kind != CS_SOURCE_CV) { + if (!data || (data->kind & SourceMask) == 0) { *status = CS_INVALID_HANDLE; return -1; } @@ -226,7 +146,7 @@ void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property, wpi::ArrayRef choices, CS_Status* status) { auto data = Instance::GetInstance().GetSource(source); - if (!data || data->kind != CS_SOURCE_CV) { + if (!data || (data->kind & SourceMask) == 0) { *status = CS_INVALID_HANDLE; return; } diff --git a/cscore/src/main/native/cpp/CvSourceImpl.h b/cscore/src/main/native/cpp/CvSourceImpl.h index e1a9cd2cf0..978d0126b8 100644 --- a/cscore/src/main/native/cpp/CvSourceImpl.h +++ b/cscore/src/main/native/cpp/CvSourceImpl.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2016-2019 FIRST. 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. */ @@ -18,33 +18,19 @@ #include #include +#include "ConfigurableSourceImpl.h" #include "SourceImpl.h" namespace cs { -class CvSourceImpl : public SourceImpl { +class CvSourceImpl : public ConfigurableSourceImpl { public: CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier, Telemetry& telemetry, const VideoMode& mode); ~CvSourceImpl() override; - void Start() override; - - bool SetVideoMode(const VideoMode& mode, CS_Status* status) override; - - void NumSinksChanged() override; - void NumSinksEnabledChanged() override; - // OpenCV-specific functions void PutFrame(cv::Mat& image); - void NotifyError(const wpi::Twine& msg); - int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum, - int maximum, int step, int defaultValue, int value); - int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum, - int maximum, int step, int defaultValue, int value, - std::function onChange); - void SetEnumPropertyChoices(int property, wpi::ArrayRef choices, - CS_Status* status); private: std::atomic_bool m_connected{true}; diff --git a/cscore/src/main/native/cpp/RawSinkImpl.cpp b/cscore/src/main/native/cpp/RawSinkImpl.cpp new file mode 100644 index 0000000000..986378f848 --- /dev/null +++ b/cscore/src/main/native/cpp/RawSinkImpl.cpp @@ -0,0 +1,199 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#include "RawSinkImpl.h" + +#include "Instance.h" +#include "cscore.h" +#include "cscore_raw.h" + +using namespace cs; + +RawSinkImpl::RawSinkImpl(const wpi::Twine& name, wpi::Logger& logger, + Notifier& notifier, Telemetry& telemetry) + : SinkImpl{name, logger, notifier, telemetry} { + m_active = true; + // m_thread = std::thread(&RawSinkImpl::ThreadMain, this); +} + +RawSinkImpl::RawSinkImpl(const wpi::Twine& name, wpi::Logger& logger, + Notifier& notifier, Telemetry& telemetry, + std::function processFrame) + : SinkImpl{name, logger, notifier, telemetry} {} + +RawSinkImpl::~RawSinkImpl() { Stop(); } + +void RawSinkImpl::Stop() { + m_active = false; + + // wake up any waiters by forcing an empty frame to be sent + if (auto source = GetSource()) source->Wakeup(); + + // join thread + if (m_thread.joinable()) m_thread.join(); +} + +uint64_t RawSinkImpl::GrabFrame(CS_RawFrame& image) { + SetEnabled(true); + + auto source = GetSource(); + if (!source) { + // Source disconnected; sleep for one second + std::this_thread::sleep_for(std::chrono::seconds(1)); + return 0; + } + + auto frame = source->GetNextFrame(); // blocks + if (!frame) { + // Bad frame; sleep for 20 ms so we don't consume all processor time. + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + return 0; // signal error + } + + return GrabFrameImpl(image, frame); +} + +uint64_t RawSinkImpl::GrabFrame(CS_RawFrame& image, double timeout) { + SetEnabled(true); + + auto source = GetSource(); + if (!source) { + // Source disconnected; sleep for one second + std::this_thread::sleep_for(std::chrono::seconds(1)); + return 0; + } + + auto frame = source->GetNextFrame(timeout); // blocks + if (!frame) { + // Bad frame; sleep for 20 ms so we don't consume all processor time. + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + return 0; // signal error + } + + return GrabFrameImpl(image, frame); +} + +uint64_t RawSinkImpl::GrabFrameImpl(CS_RawFrame& rawFrame, + Frame& incomingFrame) { + Image* newImage = nullptr; + + if (rawFrame.pixelFormat == CS_PixelFormat::CS_PIXFMT_UNKNOWN) { + // Always get incoming image directly on unknown + newImage = incomingFrame.GetExistingImage(0); + } else { + // Format is known, ask for it + auto width = rawFrame.width; + auto height = rawFrame.height; + auto pixelFormat = + static_cast(rawFrame.pixelFormat); + if (width <= 0 || height <= 0) { + width = incomingFrame.GetOriginalWidth(); + height = incomingFrame.GetOriginalHeight(); + } + newImage = incomingFrame.GetImage(width, height, pixelFormat); + } + + if (!newImage) { + // Shouldn't happen, but just in case... + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + return 0; + } + + CS_AllocateRawFrameData(&rawFrame, newImage->size()); + rawFrame.height = newImage->height; + rawFrame.width = newImage->width; + rawFrame.pixelFormat = newImage->pixelFormat; + rawFrame.totalData = newImage->size(); + std::copy(newImage->data(), newImage->data() + rawFrame.totalData, + rawFrame.data); + + return incomingFrame.GetTime(); +} + +// Send HTTP response and a stream of JPG-frames +void RawSinkImpl::ThreadMain() { + Enable(); + while (m_active) { + auto source = GetSource(); + if (!source) { + // Source disconnected; sleep for one second + std::this_thread::sleep_for(std::chrono::seconds(1)); + continue; + } + SDEBUG4("waiting for frame"); + Frame frame = source->GetNextFrame(); // blocks + if (!m_active) break; + if (!frame) { + // Bad frame; sleep for 10 ms so we don't consume all processor time. + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + // TODO m_processFrame(); + } + Disable(); +} + +namespace cs { +CS_Sink CreateRawSink(const wpi::Twine& name, CS_Status* status) { + auto& inst = Instance::GetInstance(); + return inst.CreateSink(CS_SINK_RAW, + std::make_shared( + name, inst.logger, inst.notifier, inst.telemetry)); +} + +CS_Sink CreateRawSinkCallback(const wpi::Twine& name, + std::function processFrame, + CS_Status* status) { + auto& inst = Instance::GetInstance(); + return inst.CreateSink(CS_SINK_RAW, std::make_shared( + name, inst.logger, inst.notifier, + inst.telemetry, processFrame)); +} + +uint64_t GrabSinkFrame(CS_Sink sink, CS_RawFrame& image, CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data || data->kind != CS_SINK_RAW) { + *status = CS_INVALID_HANDLE; + return 0; + } + return static_cast(*data->sink).GrabFrame(image); +} + +uint64_t GrabSinkFrameTimeout(CS_Sink sink, CS_RawFrame& image, double timeout, + CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data || data->kind != CS_SINK_RAW) { + *status = CS_INVALID_HANDLE; + return 0; + } + return static_cast(*data->sink).GrabFrame(image, timeout); +} +} // namespace cs + +extern "C" { +CS_Sink CS_CreateRawSink(const char* name, CS_Status* status) { + return cs::CreateRawSink(name, status); +} + +CS_Sink CS_CreateRawSinkCallback(const char* name, void* data, + void (*processFrame)(void* data, + uint64_t time), + CS_Status* status) { + return cs::CreateRawSinkCallback( + name, [=](uint64_t time) { processFrame(data, time); }, status); +} + +uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct CS_RawFrame* image, + CS_Status* status) { + return cs::GrabSinkFrame(sink, *image, status); +} + +uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct CS_RawFrame* image, + double timeout, CS_Status* status) { + return cs::GrabSinkFrameTimeout(sink, *image, timeout, status); +} +} // extern "C" diff --git a/cscore/src/main/native/cpp/RawSinkImpl.h b/cscore/src/main/native/cpp/RawSinkImpl.h new file mode 100644 index 0000000000..3e694857c5 --- /dev/null +++ b/cscore/src/main/native/cpp/RawSinkImpl.h @@ -0,0 +1,52 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2016-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CSCORE_RAWSINKIMPL_H_ +#define CSCORE_RAWSINKIMPL_H_ + +#include + +#include +#include +#include + +#include +#include + +#include "Frame.h" +#include "SinkImpl.h" +#include "cscore_raw.h" + +namespace cs { +class SourceImpl; + +class RawSinkImpl : public SinkImpl { + public: + RawSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier, + Telemetry& telemetry); + RawSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier, + Telemetry& telemetry, + std::function processFrame); + ~RawSinkImpl() override; + + void Stop(); + + uint64_t GrabFrame(CS_RawFrame& frame); + uint64_t GrabFrame(CS_RawFrame& frame, double timeout); + + private: + void ThreadMain(); + + uint64_t GrabFrameImpl(CS_RawFrame& rawFrame, Frame& incomingFrame); + + std::atomic_bool m_active; // set to false to terminate threads + std::thread m_thread; + std::function m_processFrame; +}; +} // namespace cs + +#endif // CSCORE_RAWSINKIMPL_H_ diff --git a/cscore/src/main/native/cpp/RawSourceImpl.cpp b/cscore/src/main/native/cpp/RawSourceImpl.cpp new file mode 100644 index 0000000000..e0dba2dc49 --- /dev/null +++ b/cscore/src/main/native/cpp/RawSourceImpl.cpp @@ -0,0 +1,83 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#include "RawSourceImpl.h" + +#include + +#include "Handle.h" +#include "Instance.h" +#include "Log.h" +#include "Notifier.h" +#include "cscore_raw.h" + +using namespace cs; + +RawSourceImpl::RawSourceImpl(const wpi::Twine& name, wpi::Logger& logger, + Notifier& notifier, Telemetry& telemetry, + const VideoMode& mode) + : ConfigurableSourceImpl{name, logger, notifier, telemetry, mode} {} + +RawSourceImpl::~RawSourceImpl() {} + +void RawSourceImpl::PutFrame(const CS_RawFrame& image) { + int type; + switch (image.pixelFormat) { + case VideoMode::kYUYV: + case VideoMode::kRGB565: + type = CV_8UC2; + break; + case VideoMode::kBGR: + type = CV_8UC3; + break; + case VideoMode::kGray: + case VideoMode::kMJPEG: + default: + type = CV_8UC1; + break; + } + cv::Mat finalImage{image.height, image.width, type, image.data}; + std::unique_ptr dest = + AllocImage(static_cast(image.pixelFormat), + image.width, image.height, image.totalData); + finalImage.copyTo(dest->AsMat()); + + SourceImpl::PutFrame(std::move(dest), wpi::Now()); +} + +namespace cs { +CS_Source CreateRawSource(const wpi::Twine& name, const VideoMode& mode, + CS_Status* status) { + auto& inst = Instance::GetInstance(); + return inst.CreateSource(CS_SOURCE_RAW, std::make_shared( + name, inst.logger, inst.notifier, + inst.telemetry, mode)); +} + +void PutSourceFrame(CS_Source source, const CS_RawFrame& image, + CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || data->kind != CS_SOURCE_RAW) { + *status = CS_INVALID_HANDLE; + return; + } + static_cast(*data->source).PutFrame(image); +} +} // namespace cs + +extern "C" { +CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode, + CS_Status* status) { + return cs::CreateRawSource(name, static_cast(*mode), + status); +} + +void CS_PutRawSourceFrame(CS_Source source, const struct CS_RawFrame* image, + CS_Status* status) { + return cs::PutSourceFrame(source, *image, status); +} +} // extern "C" diff --git a/cscore/src/main/native/cpp/RawSourceImpl.h b/cscore/src/main/native/cpp/RawSourceImpl.h new file mode 100644 index 0000000000..1fdc749aa4 --- /dev/null +++ b/cscore/src/main/native/cpp/RawSourceImpl.h @@ -0,0 +1,41 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2016-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CSCORE_RAWSOURCEIMPL_H_ +#define CSCORE_RAWSOURCEIMPL_H_ + +#include +#include +#include +#include +#include + +#include +#include + +#include "ConfigurableSourceImpl.h" +#include "SourceImpl.h" +#include "cscore_raw.h" + +namespace cs { + +class RawSourceImpl : public ConfigurableSourceImpl { + public: + RawSourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier, + Telemetry& telemetry, const VideoMode& mode); + ~RawSourceImpl() override; + + // Raw-specific functions + void PutFrame(const CS_RawFrame& image); + + private: + std::atomic_bool m_connected{true}; +}; + +} // namespace cs + +#endif // CSCORE_RAWSOURCEIMPL_H_ diff --git a/cscore/src/main/native/cpp/cscore_c.cpp b/cscore/src/main/native/cpp/cscore_c.cpp index 468aa45fb1..321572e96b 100644 --- a/cscore/src/main/native/cpp/cscore_c.cpp +++ b/cscore/src/main/native/cpp/cscore_c.cpp @@ -16,6 +16,7 @@ #include "c_util.h" #include "cscore_cpp.h" +#include "cscore_raw.h" extern "C" { @@ -440,4 +441,23 @@ void CS_FreeNetworkInterfaces(char** interfaces, int count) { std::free(interfaces); } +void CS_AllocateRawFrameData(CS_RawFrame* frame, int requestedSize) { + if (frame->dataLength >= requestedSize) return; + if (frame->data) { + frame->data = + static_cast(wpi::safe_realloc(frame->data, requestedSize)); + } else { + frame->data = static_cast(wpi::safe_malloc(requestedSize)); + } + frame->dataLength = requestedSize; +} + +void CS_FreeRawFrameData(CS_RawFrame* frame) { + if (frame->data) { + std::free(frame->data); + frame->data = nullptr; + frame->dataLength = 0; + } +} + } // extern "C" diff --git a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp index 3f16d48906..a859b82e8b 100644 --- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp +++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp @@ -10,8 +10,14 @@ #include #include "cscore_cpp.h" +#include "cscore_cv.h" +#include "cscore_raw.h" #include "edu_wpi_cscore_CameraServerJNI.h" +namespace cv { +class Mat; +} // namespace cv + using namespace wpi::java; // @@ -23,6 +29,7 @@ static JavaVM* jvm = nullptr; static JClass usbCameraInfoCls; static JClass videoModeCls; static JClass videoEventCls; +static JClass rawFrameCls; static JException videoEx; static JException nullPointerEx; static JException unsupportedEx; @@ -32,7 +39,8 @@ static JNIEnv* listenerEnv = nullptr; static const JClassInit classes[] = { {"edu/wpi/cscore/UsbCameraInfo", &usbCameraInfoCls}, {"edu/wpi/cscore/VideoMode", &videoModeCls}, - {"edu/wpi/cscore/VideoEvent", &videoEventCls}}; + {"edu/wpi/cscore/VideoEvent", &videoEventCls}, + {"edu/wpi/cscore/raw/RawFrame", &rawFrameCls}}; static const JExceptionInit exceptions[] = { {"edu/wpi/cscore/VideoException", &videoEx}, @@ -506,12 +514,12 @@ Java_edu_wpi_cscore_CameraServerJNI_createHttpCameraMulti } /* - * Class: edu_wpi_cscore_CameraServerJNI + * Class: edu_wpi_cscore_CameraServerCvJNI * Method: createCvSource * Signature: (Ljava/lang/String;IIII)I */ JNIEXPORT jint JNICALL -Java_edu_wpi_cscore_CameraServerJNI_createCvSource +Java_edu_wpi_cscore_CameraServerCvJNI_createCvSource (JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height, jint fps) { @@ -530,6 +538,31 @@ Java_edu_wpi_cscore_CameraServerJNI_createCvSource return val; } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: createRawSource + * Signature: (Ljava/lang/String;IIII)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_cscore_CameraServerJNI_createRawSource + (JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height, + jint fps) +{ + if (!name) { + nullPointerEx.Throw(env, "name cannot be null"); + return 0; + } + CS_Status status = 0; + auto val = cs::CreateRawSource( + JStringRef{env, name}.str(), + cs::VideoMode{static_cast(pixelFormat), + static_cast(width), static_cast(height), + static_cast(fps)}, + &status); + CheckStatus(env, status); + return val; +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: getSourceKind @@ -1054,12 +1087,12 @@ Java_edu_wpi_cscore_CameraServerJNI_getHttpCameraUrls } /* - * Class: edu_wpi_cscore_CameraServerJNI + * Class: edu_wpi_cscore_CameraServerCvJNI * Method: putSourceFrame * Signature: (IJ)V */ JNIEXPORT void JNICALL -Java_edu_wpi_cscore_CameraServerJNI_putSourceFrame +Java_edu_wpi_cscore_CameraServerCvJNI_putSourceFrame (JNIEnv* env, jclass, jint source, jlong imageNativeObj) { cv::Mat& image = *((cv::Mat*)imageNativeObj); @@ -1068,6 +1101,51 @@ Java_edu_wpi_cscore_CameraServerJNI_putSourceFrame CheckStatus(env, status); } +// int width, int height, int pixelFormat, int totalData + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: putRawSourceFrameBB + * Signature: (ILjava/lang/Object;IIII)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_cscore_CameraServerJNI_putRawSourceFrameBB + (JNIEnv* env, jclass, jint source, jobject byteBuffer, jint width, + jint height, jint pixelFormat, jint totalData) +{ + CS_RawFrame rawFrame; + rawFrame.data = + reinterpret_cast(env->GetDirectBufferAddress(byteBuffer)); + rawFrame.totalData = totalData; + rawFrame.pixelFormat = pixelFormat; + rawFrame.width = width; + rawFrame.height = height; + CS_Status status = 0; + cs::PutSourceFrame(source, rawFrame, &status); + CheckStatus(env, status); +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: putRawSourceFrame + * Signature: (IJIIII)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_cscore_CameraServerJNI_putRawSourceFrame + (JNIEnv* env, jclass, jint source, jlong ptr, jint width, jint height, + jint pixelFormat, jint totalData) +{ + CS_RawFrame rawFrame; + rawFrame.data = reinterpret_cast(static_cast(ptr)); + rawFrame.totalData = totalData; + rawFrame.pixelFormat = pixelFormat; + rawFrame.width = width; + rawFrame.height = height; + CS_Status status = 0; + cs::PutSourceFrame(source, rawFrame, &status); + CheckStatus(env, status); +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: notifySourceError @@ -1192,12 +1270,12 @@ Java_edu_wpi_cscore_CameraServerJNI_createMjpegServer } /* - * Class: edu_wpi_cscore_CameraServerJNI + * Class: edu_wpi_cscore_CameraServerCvJNI * Method: createCvSink * Signature: (Ljava/lang/String;)I */ JNIEXPORT jint JNICALL -Java_edu_wpi_cscore_CameraServerJNI_createCvSink +Java_edu_wpi_cscore_CameraServerCvJNI_createCvSink (JNIEnv* env, jclass, jstring name) { if (!name) { @@ -1210,6 +1288,25 @@ Java_edu_wpi_cscore_CameraServerJNI_createCvSink return val; } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: createRawSink + * Signature: (Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_cscore_CameraServerJNI_createRawSink + (JNIEnv* env, jclass, jstring name) +{ + if (!name) { + nullPointerEx.Throw(env, "name cannot be null"); + return 0; + } + CS_Status status = 0; + auto val = cs::CreateRawSink(JStringRef{env, name}.str(), &status); + CheckStatus(env, status); + return val; +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: getSinkKind @@ -1449,12 +1546,12 @@ Java_edu_wpi_cscore_CameraServerJNI_setSinkDescription } /* - * Class: edu_wpi_cscore_CameraServerJNI + * Class: edu_wpi_cscore_CameraServerCvJNI * Method: grabSinkFrame * Signature: (IJ)J */ JNIEXPORT jlong JNICALL -Java_edu_wpi_cscore_CameraServerJNI_grabSinkFrame +Java_edu_wpi_cscore_CameraServerCvJNI_grabSinkFrame (JNIEnv* env, jclass, jint sink, jlong imageNativeObj) { cv::Mat& image = *((cv::Mat*)imageNativeObj); @@ -1465,12 +1562,12 @@ Java_edu_wpi_cscore_CameraServerJNI_grabSinkFrame } /* - * Class: edu_wpi_cscore_CameraServerJNI + * Class: edu_wpi_cscore_CameraServerCvJNI * Method: grabSinkFrameTimeout * Signature: (IJD)J */ JNIEXPORT jlong JNICALL -Java_edu_wpi_cscore_CameraServerJNI_grabSinkFrameTimeout +Java_edu_wpi_cscore_CameraServerCvJNI_grabSinkFrameTimeout (JNIEnv* env, jclass, jint sink, jlong imageNativeObj, jdouble timeout) { cv::Mat& image = *((cv::Mat*)imageNativeObj); @@ -1480,6 +1577,75 @@ Java_edu_wpi_cscore_CameraServerJNI_grabSinkFrameTimeout return rv; } +static void SetRawFrameData(JNIEnv* env, jobject rawFrameObj, + jobject byteBuffer, bool didChangeDataPtr, + const CS_RawFrame& frame) { + static jmethodID setMethod = + env->GetMethodID(rawFrameCls, "setData", "(Ljava/nio/ByteBuffer;JIIII)V"); + jlong framePtr = static_cast(reinterpret_cast(frame.data)); + + if (didChangeDataPtr) { + byteBuffer = env->NewDirectByteBuffer(frame.data, frame.dataLength); + } + + env->CallVoidMethod( + rawFrameObj, setMethod, byteBuffer, framePtr, + static_cast(frame.totalData), static_cast(frame.width), + static_cast(frame.height), static_cast(frame.pixelFormat)); +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: grabRawSinkFrameImpl + * Signature: (ILjava/lang/Object;JLjava/lang/Object;III)J + */ +JNIEXPORT jlong JNICALL +Java_edu_wpi_cscore_CameraServerJNI_grabRawSinkFrameImpl + (JNIEnv* env, jclass, jint sink, jobject rawFrameObj, jlong rawFramePtr, + jobject byteBuffer, jint width, jint height, jint pixelFormat) +{ + CS_RawFrame* ptr = + reinterpret_cast(static_cast(rawFramePtr)); + auto origDataPtr = ptr->data; + ptr->width = width; + ptr->height = height; + ptr->pixelFormat = pixelFormat; + CS_Status status = 0; + auto rv = cs::GrabSinkFrame(static_cast(sink), *ptr, &status); + if (!CheckStatus(env, status)) { + return 0; + } + SetRawFrameData(env, rawFrameObj, byteBuffer, origDataPtr != ptr->data, *ptr); + return rv; +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: grabRawSinkFrameTimeoutImpl + * Signature: (ILjava/lang/Object;JLjava/lang/Object;IIID)J + */ +JNIEXPORT jlong JNICALL +Java_edu_wpi_cscore_CameraServerJNI_grabRawSinkFrameTimeoutImpl + (JNIEnv* env, jclass, jint sink, jobject rawFrameObj, jlong rawFramePtr, + jobject byteBuffer, jint width, jint height, jint pixelFormat, + jdouble timeout) +{ + CS_RawFrame* ptr = + reinterpret_cast(static_cast(rawFramePtr)); + auto origDataPtr = ptr->data; + ptr->width = width; + ptr->height = height; + ptr->pixelFormat = pixelFormat; + CS_Status status = 0; + auto rv = cs::GrabSinkFrameTimeout(static_cast(sink), *ptr, timeout, + &status); + if (!CheckStatus(env, status)) { + return 0; + } + SetRawFrameData(env, rawFrameObj, byteBuffer, origDataPtr != ptr->data, *ptr); + return rv; +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: getSinkError @@ -1781,4 +1947,32 @@ Java_edu_wpi_cscore_CameraServerJNI_setLogger minLevel); } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: allocateRawFrame + * Signature: ()J + */ +JNIEXPORT jlong JNICALL +Java_edu_wpi_cscore_CameraServerJNI_allocateRawFrame + (JNIEnv*, jclass) +{ + cs::RawFrame* rawFrame = new cs::RawFrame{}; + intptr_t rawFrameIntPtr = reinterpret_cast(rawFrame); + return static_cast(rawFrameIntPtr); +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: freeRawFrame + * Signature: (J)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_cscore_CameraServerJNI_freeRawFrame + (JNIEnv*, jclass, jlong rawFrame) +{ + cs::RawFrame* ptr = + reinterpret_cast(static_cast(rawFrame)); + delete ptr; +} + } // extern "C" diff --git a/cscore/src/main/native/include/cscore_c.h b/cscore/src/main/native/include/cscore_c.h index 4312288f27..9bfbff003b 100644 --- a/cscore/src/main/native/include/cscore_c.h +++ b/cscore/src/main/native/include/cscore_c.h @@ -20,8 +20,6 @@ extern "C" { #endif -struct CvMat; - /** * @defgroup cscore_c_api cscore C API * @@ -128,7 +126,8 @@ enum CS_SourceKind { CS_SOURCE_UNKNOWN = 0, CS_SOURCE_USB = 1, CS_SOURCE_HTTP = 2, - CS_SOURCE_CV = 4 + CS_SOURCE_CV = 4, + CS_SOURCE_RAW = 8, }; /** @@ -144,7 +143,12 @@ enum CS_HttpCameraKind { /** * Sink kinds */ -enum CS_SinkKind { CS_SINK_UNKNOWN = 0, CS_SINK_MJPEG = 2, CS_SINK_CV = 4 }; +enum CS_SinkKind { + CS_SINK_UNKNOWN = 0, + CS_SINK_MJPEG = 2, + CS_SINK_CV = 4, + CS_SINK_RAW = 8 +}; /** * Listener event kinds @@ -351,8 +355,6 @@ char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status); * @defgroup cscore_opencv_source_cfunc OpenCV Source Functions * @{ */ -void CS_PutSourceFrame(CS_Source source, struct CvMat* image, - CS_Status* status); void CS_NotifySourceError(CS_Source source, const char* msg, CS_Status* status); void CS_SetSourceConnected(CS_Source source, CS_Bool connected, CS_Status* status); @@ -415,9 +417,6 @@ int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status); */ void CS_SetSinkDescription(CS_Sink sink, const char* description, CS_Status* status); -uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, CS_Status* status); -uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image, - double timeout, CS_Status* status); char* CS_GetSinkError(CS_Sink sink, CS_Status* status); void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status); /** @} */ diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h index 50b03f702c..5f71be6948 100644 --- a/cscore/src/main/native/include/cscore_cpp.h +++ b/cscore/src/main/native/include/cscore_cpp.h @@ -21,10 +21,6 @@ #include "cscore_c.h" -namespace cv { -class Mat; -} // namespace cv - namespace wpi { class json; } // namespace wpi @@ -286,7 +282,6 @@ std::vector GetHttpCameraUrls(CS_Source source, CS_Status* status); * @defgroup cscore_opencv_source_func OpenCV Source Functions * @{ */ -void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status); void NotifySourceError(CS_Source source, const wpi::Twine& msg, CS_Status* status); void SetSourceConnected(CS_Source source, bool connected, CS_Status* status); @@ -312,6 +307,7 @@ CS_Sink CreateCvSink(const wpi::Twine& name, CS_Status* status); CS_Sink CreateCvSinkCallback(const wpi::Twine& name, std::function processFrame, CS_Status* status); + /** @} */ /** @@ -356,9 +352,6 @@ int GetMjpegServerPort(CS_Sink sink, CS_Status* status); */ void SetSinkDescription(CS_Sink sink, const wpi::Twine& description, CS_Status* status); -uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status); -uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout, - CS_Status* status); std::string GetSinkError(CS_Sink sink, CS_Status* status); wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl& buf, CS_Status* status); @@ -429,18 +422,4 @@ std::vector GetNetworkInterfaces(); } // namespace cs -/** - * @defgroup cscore_cpp_opencv_special cscore C functions taking a cv::Mat* - * - * These are needed for specific interop implementations. - * @{ - */ -extern "C" { -uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status); -uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image, - double timeout, CS_Status* status); -void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status); -} // extern "C" -/** @} */ - #endif // CSCORE_CSCORE_CPP_H_ diff --git a/cscore/src/main/native/include/cscore_cv.h b/cscore/src/main/native/include/cscore_cv.h new file mode 100644 index 0000000000..6de3e0526c --- /dev/null +++ b/cscore/src/main/native/include/cscore_cv.h @@ -0,0 +1,205 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2015-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CSCORE_CSCORE_CV_H_ +#define CSCORE_CSCORE_CV_H_ + +#include "cscore_c.h" + +#ifdef CSCORE_CSCORE_RAW_CV_H_ +#error "Cannot include both cscore_cv.h and cscore_raw_cv.h in the same file" +#endif + +#ifdef __cplusplus +#include "cscore_oo.h" // NOLINT(build/include_order) + +#endif + +#ifdef __cplusplus +extern "C" { // NOLINT(build/include_order) +#endif + +struct CvMat; + +void CS_PutSourceFrame(CS_Source source, struct CvMat* image, + CS_Status* status); + +uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, CS_Status* status); +uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image, + double timeout, CS_Status* status); + +#ifdef __cplusplus +} // extern "C" +#endif + +#ifdef __cplusplus + +#include "cscore_oo.h" + +namespace cv { +class Mat; +} // namespace cv + +namespace cs { + +/** + * @defgroup cscore_cpp_opencv_special cscore C functions taking a cv::Mat* + * + * These are needed for specific interop implementations. + * @{ + */ +extern "C" { +uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status); +uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image, + double timeout, CS_Status* status); +void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status); +} // extern "C" +/** @} */ + +void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status); +uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status); +uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout, + CS_Status* status); + +/** + * A source for user code to provide OpenCV images as video frames. + * These sources require the WPILib OpenCV builds. + * For an alternate OpenCV, include "cscore_raw_cv.h" instead, and + * include your Mat header before that header. + */ +class CvSource : public ImageSource { + public: + CvSource() = default; + + /** + * Create an OpenCV source. + * + * @param name Source name (arbitrary unique identifier) + * @param mode Video mode being generated + */ + CvSource(const wpi::Twine& 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(const wpi::Twine& name, VideoMode::PixelFormat pixelFormat, + int width, int height, int fps); + + /** + * Put an OpenCV image and notify sinks. + * + *

Only 8-bit single-channel or 3-channel (with BGR channel order) images + * are supported. If the format, depth or channel order is different, use + * cv::Mat::convertTo() and/or cv::cvtColor() to convert it first. + * + * @param image OpenCV image + */ + void PutFrame(cv::Mat& image); +}; + +/** + * A sink for user code to accept video frames as OpenCV images. + * These sinks require the WPILib OpenCV builds. + * For an alternate OpenCV, include "cscore_raw_cv.h" instead, and + * include your Mat header before that header. + */ +class CvSink : public ImageSink { + public: + CvSink() = default; + + /** + * Create a sink for accepting OpenCV images. + * + *

WaitForFrame() must be called on the created sink to get each new + * image. + * + * @param name Source name (arbitrary unique identifier) + */ + explicit CvSink(const wpi::Twine& name); + + /** + * Create a sink for accepting OpenCV images in a separate thread. + * + *

A thread will be created that calls WaitForFrame() and calls the + * processFrame() callback each time a new frame arrives. + * + * @param name Source name (arbitrary unique identifier) + * @param processFrame Frame processing function; will be called with a + * time=0 if an error occurred. processFrame should call GetImage() + * or GetError() as needed, but should not call (except in very + * unusual circumstances) WaitForImage(). + */ + CvSink(const wpi::Twine& name, + std::function processFrame); + + /** + * Wait for the next frame and get the image. + * Times out (returning 0) after timeout seconds. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) const; + + /** + * Wait for the next frame and get the image. May block forever. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + uint64_t GrabFrameNoTimeout(cv::Mat& image) const; +}; + +inline CvSource::CvSource(const wpi::Twine& name, const VideoMode& mode) { + m_handle = CreateCvSource(name, mode, &m_status); +} + +inline CvSource::CvSource(const wpi::Twine& 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) { + m_status = 0; + PutSourceFrame(m_handle, image, &m_status); +} + +inline CvSink::CvSink(const wpi::Twine& name) { + m_handle = CreateCvSink(name, &m_status); +} + +inline CvSink::CvSink(const wpi::Twine& name, + std::function processFrame) { + m_handle = CreateCvSinkCallback(name, processFrame, &m_status); +} + +inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) const { + m_status = 0; + return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status); +} + +inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) const { + m_status = 0; + return GrabSinkFrame(m_handle, image, &m_status); +} + +} // namespace cs + +#endif + +#endif // CSCORE_CSCORE_CV_H_ diff --git a/cscore/src/main/native/include/cscore_oo.h b/cscore/src/main/native/include/cscore_oo.h index 766d3b7db8..70fd2a09cc 100644 --- a/cscore/src/main/native/include/cscore_oo.h +++ b/cscore/src/main/native/include/cscore_oo.h @@ -28,7 +28,7 @@ namespace cs { */ // Forward declarations so friend declarations work correctly -class CvSource; +class ImageSource; class VideoEvent; class VideoSink; class VideoSource; @@ -37,7 +37,7 @@ class VideoSource; * A source or sink property. */ class VideoProperty { - friend class CvSource; + friend class ImageSource; friend class VideoEvent; friend class VideoSink; friend class VideoSource; @@ -617,43 +617,13 @@ class AxisCamera : public HttpCamera { }; /** - * A source for user code to provide OpenCV images as video frames. + * A base class for single image providing sources. */ -class CvSource : public VideoSource { +class ImageSource : public VideoSource { + protected: + ImageSource() = default; + public: - CvSource() = default; - - /** - * Create an OpenCV source. - * - * @param name Source name (arbitrary unique identifier) - * @param mode Video mode being generated - */ - CvSource(const wpi::Twine& 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(const wpi::Twine& name, VideoMode::PixelFormat pixelFormat, - int width, int height, int fps); - - /** - * Put an OpenCV image and notify sinks. - * - *

Only 8-bit single-channel or 3-channel (with BGR channel order) images - * are supported. If the format, depth or channel order is different, use - * cv::Mat::convertTo() and/or cv::cvtColor() to convert it first. - * - * @param image OpenCV image - */ - void PutFrame(cv::Mat& image); - /** * Signal sinks that an error has occurred. This should be called instead * of NotifyFrame when an error occurs. @@ -979,37 +949,13 @@ class MjpegServer : public VideoSink { }; /** - * A sink for user code to accept video frames as OpenCV images. + * A base class for single image reading sinks. */ -class CvSink : public VideoSink { +class ImageSink : public VideoSink { + protected: + ImageSink() = default; + public: - CvSink() = default; - - /** - * Create a sink for accepting OpenCV images. - * - *

WaitForFrame() must be called on the created sink to get each new - * image. - * - * @param name Source name (arbitrary unique identifier) - */ - explicit CvSink(const wpi::Twine& name); - - /** - * Create a sink for accepting OpenCV images in a separate thread. - * - *

A thread will be created that calls WaitForFrame() and calls the - * processFrame() callback each time a new frame arrives. - * - * @param name Source name (arbitrary unique identifier) - * @param processFrame Frame processing function; will be called with a - * time=0 if an error occurred. processFrame should call GetImage() - * or GetError() as needed, but should not call (except in very - * unusual circumstances) WaitForImage(). - */ - CvSink(const wpi::Twine& name, - std::function processFrame); - /** * Set sink description. * @@ -1017,27 +963,6 @@ class CvSink : public VideoSink { */ void SetDescription(const wpi::Twine& description); - /** - * Wait for the next frame and get the image. - * Times out (returning 0) after timeout seconds. - * The provided image will have three 8-bit channels stored in BGR order. - * - * @return Frame time, or 0 on error (call GetError() to obtain the error - * message); the frame time is in the same time base as wpi::Now(), - * and is in 1 us increments. - */ - uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) const; - - /** - * Wait for the next frame and get the image. May block forever. - * The provided image will have three 8-bit channels stored in BGR order. - * - * @return Frame time, or 0 on error (call GetError() to obtain the error - * message); the frame time is in the same time base as wpi::Now(), - * and is in 1 us increments. - */ - uint64_t GrabFrameNoTimeout(cv::Mat& image) const; - /** * Get error string. Call this if WaitForFrame() returns 0 to determine * what the error is. diff --git a/cscore/src/main/native/include/cscore_oo.inl b/cscore/src/main/native/include/cscore_oo.inl index 5162e1db88..92ff5b77a8 100644 --- a/cscore/src/main/native/include/cscore_oo.inl +++ b/cscore/src/main/native/include/cscore_oo.inl @@ -378,37 +378,22 @@ inline AxisCamera::AxisCamera(const wpi::Twine& name, std::initializer_list hosts) : HttpCamera(name, HostToUrl(hosts), kAxis) {} -inline CvSource::CvSource(const wpi::Twine& name, const VideoMode& mode) { - m_handle = CreateCvSource(name, mode, &m_status); -} - -inline CvSource::CvSource(const wpi::Twine& 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) { - m_status = 0; - PutSourceFrame(m_handle, image, &m_status); -} - -inline void CvSource::NotifyError(const wpi::Twine& msg) { +inline void ImageSource::NotifyError(const wpi::Twine& msg) { m_status = 0; NotifySourceError(m_handle, msg, &m_status); } -inline void CvSource::SetConnected(bool connected) { +inline void ImageSource::SetConnected(bool connected) { m_status = 0; SetSourceConnected(m_handle, connected, &m_status); } -inline void CvSource::SetDescription(const wpi::Twine& description) { +inline void ImageSource::SetDescription(const wpi::Twine& description) { m_status = 0; SetSourceDescription(m_handle, description, &m_status); } -inline VideoProperty CvSource::CreateProperty(const wpi::Twine& name, +inline VideoProperty ImageSource::CreateProperty(const wpi::Twine& name, VideoProperty::Kind kind, int minimum, int maximum, int step, int defaultValue, @@ -419,7 +404,7 @@ inline VideoProperty CvSource::CreateProperty(const wpi::Twine& name, minimum, maximum, step, defaultValue, value, &m_status)}; } -inline VideoProperty CvSource::CreateIntegerProperty(const wpi::Twine& name, +inline VideoProperty ImageSource::CreateIntegerProperty(const wpi::Twine& name, int minimum, int maximum, int step, int defaultValue, int value) { @@ -429,7 +414,7 @@ inline VideoProperty CvSource::CreateIntegerProperty(const wpi::Twine& name, minimum, maximum, step, defaultValue, value, &m_status)}; } -inline VideoProperty CvSource::CreateBooleanProperty(const wpi::Twine& name, +inline VideoProperty ImageSource::CreateBooleanProperty(const wpi::Twine& name, bool defaultValue, bool value) { m_status = 0; @@ -438,7 +423,7 @@ inline VideoProperty CvSource::CreateBooleanProperty(const wpi::Twine& name, 0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0, &m_status)}; } -inline VideoProperty CvSource::CreateStringProperty(const wpi::Twine& name, +inline VideoProperty ImageSource::CreateStringProperty(const wpi::Twine& name, const wpi::Twine& value) { m_status = 0; auto prop = VideoProperty{CreateSourceProperty( @@ -449,14 +434,14 @@ inline VideoProperty CvSource::CreateStringProperty(const wpi::Twine& name, } -inline void CvSource::SetEnumPropertyChoices( +inline void ImageSource::SetEnumPropertyChoices( const VideoProperty& property, wpi::ArrayRef choices) { m_status = 0; SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status); } template -inline void CvSource::SetEnumPropertyChoices(const VideoProperty& property, +inline void ImageSource::SetEnumPropertyChoices(const VideoProperty& property, std::initializer_list choices) { std::vector vec; vec.reserve(choices.size()); @@ -575,36 +560,17 @@ inline void MjpegServer::SetDefaultCompression(int quality) { quality, &m_status); } -inline CvSink::CvSink(const wpi::Twine& name) { - m_handle = CreateCvSink(name, &m_status); -} - -inline CvSink::CvSink(const wpi::Twine& name, - std::function processFrame) { - m_handle = CreateCvSinkCallback(name, processFrame, &m_status); -} - -inline void CvSink::SetDescription(const wpi::Twine& description) { +inline void ImageSink::SetDescription(const wpi::Twine& description) { m_status = 0; SetSinkDescription(m_handle, description, &m_status); } -inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) const { - m_status = 0; - return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status); -} - -inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) const { - m_status = 0; - return GrabSinkFrame(m_handle, image, &m_status); -} - -inline std::string CvSink::GetError() const { +inline std::string ImageSink::GetError() const { m_status = 0; return GetSinkError(m_handle, &m_status); } -inline void CvSink::SetEnabled(bool enabled) { +inline void ImageSink::SetEnabled(bool enabled) { m_status = 0; SetSinkEnabled(m_handle, enabled, &m_status); } diff --git a/cscore/src/main/native/include/cscore_raw.h b/cscore/src/main/native/include/cscore_raw.h new file mode 100644 index 0000000000..902d90ea4b --- /dev/null +++ b/cscore/src/main/native/include/cscore_raw.h @@ -0,0 +1,234 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2016-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CSCORE_CSCORE_RAW_H_ +#define CSCORE_CSCORE_RAW_H_ + +#include "cscore_c.h" + +#ifdef __cplusplus +#include "cscore_oo.h" +#endif + +/** + * Raw Frame + */ +typedef struct CS_RawFrame { + char* data; + int dataLength; + int pixelFormat; + int width; + int height; + int totalData; +} CS_RawFrame; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup cscore_raw_cfunc Raw Image Functions + * @{ + */ +void CS_AllocateRawFrameData(CS_RawFrame* frame, int requestedSize); +void CS_FreeRawFrameData(CS_RawFrame* frame); + +uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct CS_RawFrame* rawImage, + CS_Status* status); +uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct CS_RawFrame* rawImage, + double timeout, CS_Status* status); + +CS_Sink CS_CreateRawSink(const char* name, CS_Status* status); + +CS_Sink CS_CreateRawSinkCallback(const char* name, void* data, + void (*processFrame)(void* data, + uint64_t time), + CS_Status* status); + +void CS_PutRawSourceFrame(CS_Source source, const struct CS_RawFrame* image, + CS_Status* status); + +CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode, + CS_Status* status); +/** @} */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#ifdef __cplusplus +namespace cs { + +struct RawFrame : public CS_RawFrame { + RawFrame() { + data = nullptr; + dataLength = 0; + pixelFormat = CS_PIXFMT_UNKNOWN; + width = 0; + height = 0; + totalData = 0; + } + + ~RawFrame() { CS_FreeRawFrameData(this); } +}; + +/** + * @defgroup cscore_raw_func Raw Image Functions + * @{ + */ + +CS_Source CreateRawSource(const wpi::Twine& name, const VideoMode& mode, + CS_Status* status); + +CS_Sink CreateRawSink(const wpi::Twine& name, CS_Status* status); +CS_Sink CreateRawSinkCallback(const wpi::Twine& name, + std::function processFrame, + CS_Status* status); + +void PutSourceFrame(CS_Source source, const CS_RawFrame& image, + CS_Status* status); +uint64_t GrabSinkFrame(CS_Sink sink, CS_RawFrame& image, CS_Status* status); +uint64_t GrabSinkFrameTimeout(CS_Sink sink, CS_RawFrame& image, double timeout, + CS_Status* status); + +/** + * A source for user code to provide video frames as raw bytes. + * + * This is a complex API, most cases should use CvSource. + */ +class RawSource : public ImageSource { + public: + RawSource() = default; + + /** + * Create a raw frame source. + * + * @param name Source name (arbitrary unique identifier) + * @param mode Video mode being generated + */ + RawSource(const wpi::Twine& name, const VideoMode& mode); + + /** + * Create a raw frame source. + * + * @param name Source name (arbitrary unique identifier) + * @param pixelFormat Pixel format + * @param width width + * @param height height + * @param fps fps + */ + RawSource(const wpi::Twine& name, VideoMode::PixelFormat pixelFormat, + int width, int height, int fps); + + protected: + /** + * Put a raw image and notify sinks. + * + * @param image raw frame image + */ + void PutFrame(RawFrame& image); +}; + +/** + * A sink for user code to accept video frames as raw bytes. + * + * This is a complex API, most cases should use CvSource. + */ +class RawSink : public ImageSink { + public: + RawSink() = default; + + /** + * Create a sink for accepting raw images. + * + *

GrabFrame() must be called on the created sink to get each new + * image. + * + * @param name Source name (arbitrary unique identifier) + */ + explicit RawSink(const wpi::Twine& name); + + /** + * Create a sink for accepting raws images in a separate thread. + * + *

A thread will be created that calls WaitForFrame() and calls the + * processFrame() callback each time a new frame arrives. + * + * @param name Source name (arbitrary unique identifier) + * @param processFrame Frame processing function; will be called with a + * time=0 if an error occurred. processFrame should call GetImage() + * or GetError() as needed, but should not call (except in very + * unusual circumstances) WaitForImage(). + */ + RawSink(const wpi::Twine& name, + std::function processFrame); + + protected: + /** + * Wait for the next frame and get the image. + * Times out (returning 0) after timeout seconds. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + uint64_t GrabFrame(RawFrame& image, double timeout = 0.225) const; + + /** + * Wait for the next frame and get the image. May block forever. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + uint64_t GrabFrameNoTimeout(RawFrame& image) const; +}; + +inline RawSource::RawSource(const wpi::Twine& name, const VideoMode& mode) { + m_handle = CreateRawSource(name, mode, &m_status); +} + +inline RawSource::RawSource(const wpi::Twine& name, + VideoMode::PixelFormat format, int width, + int height, int fps) { + m_handle = + CreateRawSource(name, VideoMode{format, width, height, fps}, &m_status); +} + +inline void RawSource::PutFrame(RawFrame& image) { + m_status = 0; + PutSourceFrame(m_handle, image, &m_status); +} + +inline RawSink::RawSink(const wpi::Twine& name) { + m_handle = CreateRawSink(name, &m_status); +} + +inline RawSink::RawSink(const wpi::Twine& name, + std::function processFrame) { + m_handle = CreateRawSinkCallback(name, processFrame, &m_status); +} + +inline uint64_t RawSink::GrabFrame(RawFrame& image, double timeout) const { + m_status = 0; + return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status); +} + +inline uint64_t RawSink::GrabFrameNoTimeout(RawFrame& image) const { + m_status = 0; + return GrabSinkFrame(m_handle, image, &m_status); +} + +} // namespace cs + +/** @} */ + +#endif + +#endif // CSCORE_CSCORE_RAW_H_ diff --git a/cscore/src/main/native/include/cscore_raw_cv.h b/cscore/src/main/native/include/cscore_raw_cv.h new file mode 100644 index 0000000000..ed4000621c --- /dev/null +++ b/cscore/src/main/native/include/cscore_raw_cv.h @@ -0,0 +1,218 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2015-2019 FIRST. 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. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CSCORE_CSCORE_RAW_CV_H_ +#define CSCORE_CSCORE_RAW_CV_H_ + +#ifdef CSCORE_CSCORE_CV_H_ +#error "Cannot include both cscore_cv.h and cscore_raw_cv.h in the same file" +#endif + +#include "cscore_raw.h" + +namespace cs { +/** + * A source for using the raw frame API to provide opencv images. + * + * If you are using the WPILib OpenCV builds, do not use this, and + * instead include "cscore_cv.h" to get a more performant version. + * + * This is not dependent on any opencv binary ABI, and can be used + * with versions of OpenCV that are not 3. If using OpenCV 3, use + * CvSource. + */ +class RawCvSource : public RawSource { + public: + RawCvSource() = default; + + /** + * Create a Raw OpenCV source. + * + * @param name Source name (arbitrary unique identifier) + * @param mode Video mode being generated + */ + RawCvSource(const wpi::Twine& name, const VideoMode& mode); + + /** + * Create a Raw OpenCV source. + * + * @param name Source name (arbitrary unique identifier) + * @param pixelFormat Pixel format + * @param width width + * @param height height + * @param fps fps + */ + RawCvSource(const wpi::Twine& name, VideoMode::PixelFormat pixelFormat, + int width, int height, int fps); + + /** + * Put an OpenCV image and notify sinks. + * + *

Only 8-bit single-channel or 3-channel (with BGR channel order) images + * are supported. If the format, depth or channel order is different, use + * cv::Mat::convertTo() and/or cv::cvtColor() to convert it first. + * + * @param image OpenCV image + */ + void PutFrame(cv::Mat& image); + + private: + RawFrame rawFrame; +}; + +/** + * A sink for user code to accept raw video frames as OpenCV images. + * + * If you are using the WPILib OpenCV builds, do not use this, and + * instead include "cscore_cv.h" to get a more performant version. + * + * This is not dependent on any opencv binary ABI, and can be used + * with versions of OpenCV that are not 3. If using OpenCV 3, use + * CvSink. + */ +class RawCvSink : public RawSink { + public: + RawCvSink() = default; + + /** + * Create a sink for accepting OpenCV images. + * + *

WaitForFrame() must be called on the created sink to get each new + * image. + * + * @param name Source name (arbitrary unique identifier) + */ + explicit RawCvSink(const wpi::Twine& name); + + /** + * Create a sink for accepting OpenCV images in a separate thread. + * + *

A thread will be created that calls WaitForFrame() and calls the + * processFrame() callback each time a new frame arrives. + * + * @param name Source name (arbitrary unique identifier) + * @param processFrame Frame processing function; will be called with a + * time=0 if an error occurred. processFrame should call GetImage() + * or GetError() as needed, but should not call (except in very + * unusual circumstances) WaitForImage(). + */ + RawCvSink(const wpi::Twine& name, + std::function processFrame); + + /** + * Wait for the next frame and get the image. + * Times out (returning 0) after timeout seconds. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225); + + /** + * Wait for the next frame and get the image. May block forever. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + uint64_t GrabFrameNoTimeout(cv::Mat& image); + + /** + * Wait for the next frame and get the image. + * Times out (returning 0) after timeout seconds. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + uint64_t GrabFrameDirect(cv::Mat& image, double timeout = 0.225); + + /** + * Wait for the next frame and get the image. May block forever. + * The provided image will have three 8-bit channels stored in BGR order. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error + * message); the frame time is in the same time base as wpi::Now(), + * and is in 1 us increments. + */ + uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image); + + private: + RawFrame rawFrame; +}; + +inline RawCvSource::RawCvSource(const wpi::Twine& name, const VideoMode& mode) + : RawSource{name, mode} {} + +inline RawCvSource::RawCvSource(const wpi::Twine& name, + VideoMode::PixelFormat format, int width, + int height, int fps) + : RawSource{name, format, width, height, fps} {} + +inline void RawCvSource::PutFrame(cv::Mat& image) { + m_status = 0; + rawFrame.data = reinterpret_cast(image.data); + rawFrame.width = image.cols; + rawFrame.height = image.rows; + rawFrame.totalData = image.total() * image.channels; + rawFrame.pixelFormat = image.channels == 3 ? CS_PIXFMT_BGR : CS_PIXFMT_GRAY; + PutSourceFrame(m_handle, rawFrame, &m_status); +} + +inline RawCvSink::RawCvSink(const wpi::Twine& name) : RawSink{name} {} + +inline RawCvSink::RawCvSink(const wpi::Twine& name, + std::function processFrame) + : RawSink{name, processFrame} {} + +inline uint64_t RawCvSink::GrabFrame(cv::Mat& image, double timeout) { + cv::Mat tmpMat; + auto retVal = GrabFrameDirect(tmpMat); + if (retVal <= 0) { + return retVal; + } + tmpMat.copyTo(image); + return retVal; +} + +inline uint64_t RawCvSink::GrabFrameNoTimeout(cv::Mat& image) { + cv::Mat tmpMat; + auto retVal = GrabFrameNoTimeoutDirect(tmpMat); + if (retVal <= 0) { + return retVal; + } + tmpMat.copyTo(image); + return retVal; +} + +inline uint64_t RawCvSink::GrabFrameDirect(cv::Mat& image, double timeout) { + rawFrame.height = 0; + rawFrame.width = 0; + rawFrame.pixelFormat = CS_PixelFormat::CS_PIXFMT_BGR; + m_status = RawSink::GrabFrame(rawFrame, timeout); + if (m_status <= 0) return m_status; + image = cv::Mat{rawFrame.height, rawFrame.width, CV_8UC3, rawFrame.data}; + return m_status; +} + +inline uint64_t RawCvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) { + rawFrame.height = 0; + rawFrame.width = 0; + rawFrame.pixelFormat = CS_PixelFormat::CS_PIXFMT_BGR; + m_status = RawSink::GrabFrameNoTimeout(rawFrame); + if (m_status <= 0) return m_status; + image = cv::Mat{rawFrame.height, rawFrame.width, CV_8UC3, rawFrame.data}; + return m_status; +} + +} // namespace cs + +#endif // CSCORE_CSCORE_RAW_CV_H_