diff --git a/cameraserver/src/main/java/edu/wpi/first/cameraserver/CameraServer.java b/cameraserver/src/main/java/edu/wpi/first/cameraserver/CameraServer.java index 5597f8002e..630943ec5d 100644 --- a/cameraserver/src/main/java/edu/wpi/first/cameraserver/CameraServer.java +++ b/cameraserver/src/main/java/edu/wpi/first/cameraserver/CameraServer.java @@ -366,6 +366,8 @@ public final class CameraServer { } case kCv: return "cv:"; + case kRaw: + return "raw:"; default: return "unknown:"; } diff --git a/cameraserver/src/main/native/cpp/cameraserver/CameraServer.cpp b/cameraserver/src/main/native/cpp/cameraserver/CameraServer.cpp index 97dba02dab..58394b465e 100644 --- a/cameraserver/src/main/native/cpp/cameraserver/CameraServer.cpp +++ b/cameraserver/src/main/native/cpp/cameraserver/CameraServer.cpp @@ -113,6 +113,8 @@ static std::string_view MakeSourceValue(CS_Source source, } case CS_SOURCE_CV: return "cv:"; + case CS_SOURCE_RAW: + return "raw:"; default: return "unknown:"; } diff --git a/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java index d3b28cfb29..0d874a7dd8 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java @@ -212,6 +212,7 @@ public class CameraServerJNI { * Creates a raw source. * * @param name Source name. + * @param isCv true for a Cv source. * @param pixelFormat Pixel format. * @param width Image width. * @param height Image height. @@ -219,7 +220,7 @@ public class CameraServerJNI { * @return Raw source handle. */ public static native int createRawSource( - String name, int pixelFormat, int width, int height, int fps); + String name, boolean isCv, int pixelFormat, int width, int height, int fps); // // Source Functions @@ -630,9 +631,10 @@ public class CameraServerJNI { * Creates a raw sink. * * @param name Sink name. + * @param isCv true for a Cv source. * @return Raw sink handle. */ - public static native int createRawSink(String name); + public static native int createRawSink(String name, boolean isCv); // // Sink Functions diff --git a/cscore/src/main/java/edu/wpi/first/cscore/CvSink.java b/cscore/src/main/java/edu/wpi/first/cscore/CvSink.java index af390e5967..922bd38abb 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/CvSink.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/CvSink.java @@ -5,6 +5,9 @@ package edu.wpi.first.cscore; import edu.wpi.first.util.PixelFormat; +import edu.wpi.first.util.RawFrame; +import java.nio.ByteBuffer; +import org.opencv.core.CvType; import org.opencv.core.Mat; /** @@ -12,15 +15,52 @@ import org.opencv.core.Mat; * OpenCV builds. For an alternate OpenCV, see the documentation how to build your own with RawSink. */ public class CvSink extends ImageSink { + private final RawFrame m_frame = new RawFrame(); + private Mat m_tmpMat; + private ByteBuffer m_origByteBuffer; + private int m_width; + private int m_height; + private PixelFormat m_pixelFormat; + + @Override + public void close() { + if (m_tmpMat != null) { + m_tmpMat.release(); + } + m_frame.close(); + super.close(); + } + + 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 + * Create a sink for accepting OpenCV images. grabFrame() must be called on the created sink to * get each new image. * * @param name Source name (arbitrary unique identifier) * @param pixelFormat Source pixel format */ public CvSink(String name, PixelFormat pixelFormat) { - super(CameraServerCvJNI.createCvSink(name, pixelFormat.getValue())); + super(CameraServerJNI.createRawSink(name, true)); + m_pixelFormat = pixelFormat; + OpenCvLoader.forceStaticLoad(); } /** @@ -33,22 +73,9 @@ public class CvSink extends ImageSink { this(name, PixelFormat.kBGR); } - /// 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(). - // public CvSink(wpi::StringRef name, - // std::function processFrame) { - // super(CameraServerJNI.createCvSinkCallback(name, processFrame)); - // } - /** * 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. + * provided image will have the pixelFormat this class was constructed with. * * @param image Where to store the image. * @return Frame time, or 0 on error (call GetError() to obtain the error message) @@ -59,7 +86,7 @@ public class CvSink extends ImageSink { /** * 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. + * provided image will have the pixelFormat this class was constructed with. * * @param image Where to store the image. * @param timeout Retrieval timeout in seconds. @@ -67,18 +94,140 @@ public class CvSink extends ImageSink { * is in 1 us increments. */ public long grabFrame(Mat image, double timeout) { - return CameraServerCvJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout); + long rv = grabFrameDirect(timeout); + if (rv <= 0) { + return rv; + } + m_tmpMat.copyTo(image); + return rv; } /** - * Wait for the next frame and get the image. May block forever. The provided image will have - * three 3-bit channels stored in BGR order. + * Wait for the next frame and get the image. May block forever. The provided image will have the + * pixelFormat this class was constructed with. * * @param image Where to store the image. * @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time * is in 1 us increments. */ public long grabFrameNoTimeout(Mat image) { - return CameraServerCvJNI.grabSinkFrame(m_handle, image.nativeObj); + long rv = grabFrameNoTimeoutDirect(); + if (rv <= 0) { + return rv; + } + m_tmpMat.copyTo(image); + return rv; + } + + /** + * Get the direct backing mat for this sink. + * + *

This mat can be invalidated any time any of the grab* methods are called, or when the CvSink + * is closed. + * + * @return The backing mat. + */ + public Mat getDirectMat() { + return m_tmpMat; + } + + /** + * Wait for the next frame and store the image. Times out (returning 0) after 0.225 seconds. The + * provided image will have the pixelFormat this class was constructed with. Use getDirectMat() to + * grab the image. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error message) + */ + public long grabFrameDirect() { + return grabFrameDirect(0.225); + } + + /** + * Wait for the next frame and store the image. Times out (returning 0) after timeout seconds. The + * provided image will have the pixelFormat this class was constructed with. Use getDirectMat() to + * grab the image. + * + * @param timeout Retrieval timeout in seconds. + * @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time + * is in 1 us increments. + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public long grabFrameDirect(double timeout) { + m_frame.setInfo(0, 0, 0, m_pixelFormat); + long rv = + CameraServerJNI.grabRawSinkFrameTimeout(m_handle, m_frame, m_frame.getNativeObj(), timeout); + if (rv <= 0) { + return rv; + } + + if (m_frame.getData() != m_origByteBuffer + || m_width != m_frame.getWidth() + || m_height != m_frame.getHeight() + || m_pixelFormat != m_frame.getPixelFormat()) { + m_origByteBuffer = m_frame.getData(); + m_height = m_frame.getHeight(); + m_width = m_frame.getWidth(); + m_pixelFormat = m_frame.getPixelFormat(); + if (m_frame.getStride() == 0) { + m_tmpMat = + new Mat( + m_frame.getHeight(), + m_frame.getWidth(), + getCVFormat(m_pixelFormat), + m_origByteBuffer); + } else { + m_tmpMat = + new Mat( + m_frame.getHeight(), + m_frame.getWidth(), + getCVFormat(m_pixelFormat), + m_origByteBuffer, + m_frame.getStride()); + } + } + return rv; + } + + /** + * Wait for the next frame and store the image. May block forever. The provided image will have + * the pixelFormat this class was constructed with. Use getDirectMat() to grab the image. + * + * @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time + * is in 1 us increments. + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public long grabFrameNoTimeoutDirect() { + m_frame.setInfo(0, 0, 0, m_pixelFormat); + long rv = CameraServerJNI.grabRawSinkFrame(m_handle, m_frame, m_frame.getNativeObj()); + if (rv <= 0) { + return rv; + } + + if (m_frame.getData() != m_origByteBuffer + || m_width != m_frame.getWidth() + || m_height != m_frame.getHeight() + || m_pixelFormat != m_frame.getPixelFormat()) { + m_origByteBuffer = m_frame.getData(); + m_height = m_frame.getHeight(); + m_width = m_frame.getWidth(); + m_pixelFormat = m_frame.getPixelFormat(); + if (m_frame.getStride() == 0) { + m_tmpMat = + new Mat( + m_frame.getHeight(), + m_frame.getWidth(), + getCVFormat(m_pixelFormat), + m_origByteBuffer); + } else { + m_tmpMat = + new Mat( + m_frame.getHeight(), + m_frame.getWidth(), + getCVFormat(m_pixelFormat), + m_origByteBuffer, + m_frame.getStride()); + } + } + return rv; } } diff --git a/cscore/src/main/java/edu/wpi/first/cscore/CvSource.java b/cscore/src/main/java/edu/wpi/first/cscore/CvSource.java index 4c29ffc482..1fcf5ab21f 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/CvSource.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/CvSource.java @@ -20,8 +20,9 @@ public class CvSource extends ImageSource { */ public CvSource(String name, VideoMode mode) { super( - CameraServerCvJNI.createCvSource( - name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps)); + CameraServerJNI.createRawSource( + name, true, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps)); + OpenCvLoader.forceStaticLoad(); } /** @@ -34,7 +35,8 @@ public class CvSource extends ImageSource { * @param fps fps */ public CvSource(String name, PixelFormat pixelFormat, int width, int height, int fps) { - super(CameraServerCvJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps)); + super(CameraServerJNI.createRawSource(name, true, pixelFormat.getValue(), width, height, fps)); + OpenCvLoader.forceStaticLoad(); } /** @@ -47,6 +49,43 @@ public class CvSource extends ImageSource { * @param image OpenCV image */ public void putFrame(Mat image) { - CameraServerCvJNI.putSourceFrame(m_handle, image.nativeObj); + // We only support 8-bit images, convert if necessary + boolean cleanupRequired = false; + Mat finalImage; + if (image.depth() == 0) { + finalImage = image; + } else { + finalImage = new Mat(); + image.convertTo(finalImage, 0); + cleanupRequired = true; + } + + try { + int channels = finalImage.channels(); + PixelFormat format; + if (channels == 1) { + format = PixelFormat.kGray; + } else if (channels == 3) { + format = PixelFormat.kBGR; + } else { + throw new VideoException("Unsupported image type"); + } + // TODO old code supported BGRA, but the only way I can support that is slow. + // Update cscore to support BGRA for raw frames + + CameraServerJNI.putRawSourceFrameData( + m_handle, + finalImage.dataAddr(), + (int) finalImage.total() * channels, + finalImage.width(), + finalImage.height(), + image.width(), + format.getValue()); + + } finally { + if (cleanupRequired) { + finalImage.release(); + } + } } } diff --git a/cscore/src/main/java/edu/wpi/first/cscore/CameraServerCvJNI.java b/cscore/src/main/java/edu/wpi/first/cscore/OpenCvLoader.java similarity index 51% rename from cscore/src/main/java/edu/wpi/first/cscore/CameraServerCvJNI.java rename to cscore/src/main/java/edu/wpi/first/cscore/OpenCvLoader.java index 26933d41bb..0ca098c282 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/CameraServerCvJNI.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/OpenCvLoader.java @@ -9,11 +9,13 @@ import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import org.opencv.core.Core; -/** CameraServer CV JNI. */ -public class CameraServerCvJNI { - static boolean libraryLoaded = false; +/** OpenCV Native Loader. */ +public final class OpenCvLoader { + @SuppressWarnings("PMD.MutableStaticState") + static boolean libraryLoaded; - static RuntimeLoader loader = null; + @SuppressWarnings("PMD.MutableStaticState") + static RuntimeLoader loader; /** Sets whether JNI should be loaded in the static block. */ public static class Helper { @@ -45,7 +47,6 @@ public class CameraServerCvJNI { String opencvName = Core.NATIVE_LIBRARY_NAME; if (Helper.getExtractOnStaticLoad()) { try { - CameraServerJNI.forceLoad(); loader = new RuntimeLoader<>(opencvName, RuntimeLoader.getDefaultExtractionRoot(), Core.class); loader.loadLibraryHashed(); @@ -57,6 +58,15 @@ public class CameraServerCvJNI { } } + /** + * Forces a static load. + * + * @return a garbage value + */ + public static int forceStaticLoad() { + return libraryLoaded ? 1 : 0; + } + /** * Force load the library. * @@ -66,7 +76,6 @@ public class CameraServerCvJNI { if (libraryLoaded) { return; } - CameraServerJNI.forceLoad(); loader = new RuntimeLoader<>( Core.NATIVE_LIBRARY_NAME, RuntimeLoader.getDefaultExtractionRoot(), Core.class); @@ -74,64 +83,6 @@ public class CameraServerCvJNI { libraryLoaded = true; } - /** - * Creates a CV source. - * - * @param name Name. - * @param pixelFormat OpenCV pixel format. - * @param width Image width. - * @param height Image height. - * @param fps Frames per second. - * @return CV source. - */ - public static native int createCvSource( - String name, int pixelFormat, int width, int height, int fps); - - /** - * Put source frame. - * - * @param source Source handle. - * @param imageNativeObj Image native object handle. - */ - public static native void putSourceFrame(int source, long imageNativeObj); - - /** - * Creates a CV sink. - * - * @param name Name. - * @param pixelFormat OpenCV pixel format. - * @return CV sink handle. - */ - public static native int createCvSink(String name, int pixelFormat); - - // /** - // * Creates a CV sink callback. - // * - // * @param name Name. - // * @param processFrame Process frame callback. - // */ - // public static native int createCvSinkCallback(String name, - // void (*processFrame)(long time)); - - /** - * Returns sink frame handle. - * - * @param sink Sink handle. - * @param imageNativeObj Image native object handle. - * @return Sink frame handle. - */ - public static native long grabSinkFrame(int sink, long imageNativeObj); - - /** - * Returns sink frame timeout in microseconds. - * - * @param sink Sink handle. - * @param imageNativeObj Image native object handle. - * @param timeout Timeout in seconds. - * @return Sink frame timeout in microseconds. - */ - public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout); - /** Utility class. */ - private CameraServerCvJNI() {} + private OpenCvLoader() {} } diff --git a/cscore/src/main/java/edu/wpi/first/cscore/VideoSink.java b/cscore/src/main/java/edu/wpi/first/cscore/VideoSink.java index 661ae9adb3..5eced4c622 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/VideoSink.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/VideoSink.java @@ -48,6 +48,8 @@ public class VideoSink implements AutoCloseable { return Kind.kMjpeg; case 4: return Kind.kCv; + case 8: + return Kind.kRaw; default: return Kind.kUnknown; } diff --git a/cscore/src/main/java/edu/wpi/first/cscore/VideoSource.java b/cscore/src/main/java/edu/wpi/first/cscore/VideoSource.java index 2356940ef3..7657eafa56 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/VideoSource.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/VideoSource.java @@ -86,6 +86,8 @@ public class VideoSource implements AutoCloseable { return Kind.kHttp; case 4: return Kind.kCv; + case 8: + return Kind.kRaw; default: return Kind.kUnknown; } diff --git a/cscore/src/main/java/edu/wpi/first/cscore/raw/RawSink.java b/cscore/src/main/java/edu/wpi/first/cscore/raw/RawSink.java index babb0fdede..ba567a98e3 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/raw/RawSink.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/raw/RawSink.java @@ -22,7 +22,7 @@ public class RawSink extends ImageSink { * @param name Source name (arbitrary unique identifier) */ public RawSink(String name) { - super(CameraServerJNI.createRawSink(name)); + super(CameraServerJNI.createRawSink(name, false)); } /** diff --git a/cscore/src/main/java/edu/wpi/first/cscore/raw/RawSource.java b/cscore/src/main/java/edu/wpi/first/cscore/raw/RawSource.java index 1c8bc23709..f1dafd9a01 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/raw/RawSource.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/raw/RawSource.java @@ -26,7 +26,7 @@ public class RawSource extends ImageSource { public RawSource(String name, VideoMode mode) { super( CameraServerJNI.createRawSource( - name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps)); + name, false, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps)); } /** @@ -39,7 +39,7 @@ public class RawSource extends ImageSource { * @param fps fps */ public RawSource(String name, PixelFormat pixelFormat, int width, int height, int fps) { - super(CameraServerJNI.createRawSource(name, pixelFormat.getValue(), width, height, fps)); + super(CameraServerJNI.createRawSource(name, false, pixelFormat.getValue(), width, height, fps)); } /** diff --git a/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp b/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp index 48677a6319..9f09eff050 100644 --- a/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp +++ b/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp @@ -104,3 +104,140 @@ void ConfigurableSourceImpl::SetEnumPropertyChoices( prop->name, property, CS_PROP_ENUM, prop->value, {}); } + +namespace cs { +static constexpr unsigned SourceMask = CS_SOURCE_CV | CS_SOURCE_RAW; + +void NotifySourceError(CS_Source source, std::string_view msg, + CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || (data->kind & SourceMask) == 0) { + *status = CS_INVALID_HANDLE; + return; + } + static_cast(*data->source).NotifyError(msg); +} + +void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || (data->kind & SourceMask) == 0) { + *status = CS_INVALID_HANDLE; + return; + } + static_cast(*data->source).SetConnected(connected); +} + +void SetSourceDescription(CS_Source source, std::string_view description, + CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || (data->kind & SourceMask) == 0) { + *status = CS_INVALID_HANDLE; + return; + } + static_cast(*data->source) + .SetDescription(description); +} + +CS_Property CreateSourceProperty(CS_Source source, std::string_view name, + CS_PropertyKind kind, int minimum, int maximum, + int step, int defaultValue, int value, + CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || (data->kind & SourceMask) == 0) { + *status = CS_INVALID_HANDLE; + return -1; + } + int property = static_cast(*data->source) + .CreateProperty(name, kind, minimum, maximum, step, + defaultValue, value); + return Handle{source, property, Handle::kProperty}; +} + +CS_Property CreateSourcePropertyCallback( + CS_Source source, std::string_view name, CS_PropertyKind kind, int minimum, + int maximum, int step, int defaultValue, int value, + std::function onChange, CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || (data->kind & SourceMask) == 0) { + *status = CS_INVALID_HANDLE; + return -1; + } + int property = static_cast(*data->source) + .CreateProperty(name, kind, minimum, maximum, step, + defaultValue, value, onChange); + return Handle{source, property, Handle::kProperty}; +} + +void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property, + std::span choices, + CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || (data->kind & SourceMask) == 0) { + *status = CS_INVALID_HANDLE; + return; + } + + // Get property index; also validate the source owns this property + Handle handle{property}; + int i = handle.GetParentIndex(); + if (i < 0) { + *status = CS_INVALID_HANDLE; + return; + } + auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource}); + if (!data2 || data->source.get() != data2->source.get()) { + *status = CS_INVALID_HANDLE; + return; + } + int propertyIndex = handle.GetIndex(); + static_cast(*data->source) + .SetEnumPropertyChoices(propertyIndex, choices, status); +} + +} // namespace cs + +extern "C" { +void CS_NotifySourceError(CS_Source source, const char* msg, + CS_Status* status) { + return cs::NotifySourceError(source, msg, status); +} + +void CS_SetSourceConnected(CS_Source source, CS_Bool connected, + CS_Status* status) { + return cs::SetSourceConnected(source, connected, status); +} + +void CS_SetSourceDescription(CS_Source source, const char* description, + CS_Status* status) { + return cs::SetSourceDescription(source, description, status); +} + +CS_Property CS_CreateSourceProperty(CS_Source source, const char* name, + enum CS_PropertyKind kind, int minimum, + int maximum, int step, int defaultValue, + int value, CS_Status* status) { + return cs::CreateSourceProperty(source, name, kind, minimum, maximum, step, + defaultValue, value, status); +} + +CS_Property CS_CreateSourcePropertyCallback( + CS_Source source, const char* name, enum CS_PropertyKind kind, int minimum, + int maximum, int step, int defaultValue, int value, void* data, + void (*onChange)(void* data, CS_Property property), CS_Status* status) { + return cs::CreateSourcePropertyCallback( + source, name, kind, minimum, maximum, step, defaultValue, value, + [=](CS_Property property) { onChange(data, property); }, status); +} + +void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property, + const char** choices, int count, + CS_Status* status) { + wpi::SmallVector vec; + vec.reserve(count); + for (int i = 0; i < count; ++i) { + vec.push_back(choices[i]); + } + return cs::SetSourceEnumPropertyChoices(source, property, vec, status); +} + +} // extern "C" diff --git a/cscore/src/main/native/cpp/ConfigurableSourceImpl.h b/cscore/src/main/native/cpp/ConfigurableSourceImpl.h index 31236f5317..570ab21ead 100644 --- a/cscore/src/main/native/cpp/ConfigurableSourceImpl.h +++ b/cscore/src/main/native/cpp/ConfigurableSourceImpl.h @@ -33,7 +33,7 @@ class ConfigurableSourceImpl : public SourceImpl { void NumSinksChanged() override; void NumSinksEnabledChanged() override; - // OpenCV-specific functions + // Frame based specific functions void NotifyError(std::string_view msg); int CreateProperty(std::string_view name, CS_PropertyKind kind, int minimum, int maximum, int step, int defaultValue, int value); diff --git a/cscore/src/main/native/cpp/CvSinkImpl.cpp b/cscore/src/main/native/cpp/CvSinkImpl.cpp deleted file mode 100644 index a5447cccfe..0000000000 --- a/cscore/src/main/native/cpp/CvSinkImpl.cpp +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#include "CvSinkImpl.h" - -#include -#include -#include -#include - -#include "Handle.h" -#include "Instance.h" -#include "Log.h" -#include "Notifier.h" -#include "c_util.h" -#include "cscore_cpp.h" - -using namespace cs; - -CvSinkImpl::CvSinkImpl(std::string_view name, wpi::Logger& logger, - Notifier& notifier, Telemetry& telemetry, - VideoMode::PixelFormat pixelFormat) - : SinkImpl{name, logger, notifier, telemetry}, m_pixelFormat{pixelFormat} { - m_active = true; - // m_thread = std::thread(&CvSinkImpl::ThreadMain, this); -} - -CvSinkImpl::CvSinkImpl(std::string_view name, wpi::Logger& logger, - Notifier& notifier, Telemetry& telemetry, - VideoMode::PixelFormat pixelFormat, - std::function processFrame) - : SinkImpl{name, logger, notifier, telemetry}, m_pixelFormat{pixelFormat} {} - -CvSinkImpl::~CvSinkImpl() { - Stop(); -} - -void CvSinkImpl::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 CvSinkImpl::GrabFrame(cv::Mat& 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 - } - - if (!frame.GetCv(image, m_pixelFormat)) { - // Shouldn't happen, but just in case... - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - return 0; - } - - return frame.GetTime(); -} - -uint64_t CvSinkImpl::GrabFrame(cv::Mat& 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 - } - - if (!frame.GetCv(image, m_pixelFormat)) { - // Shouldn't happen, but just in case... - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - return 0; - } - - return frame.GetTime(); -} - -// Send HTTP response and a stream of JPG-frames -void CvSinkImpl::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 CreateCvSink(std::string_view name, VideoMode::PixelFormat pixelFormat, - CS_Status* status) { - auto& inst = Instance::GetInstance(); - return inst.CreateSink( - CS_SINK_CV, std::make_shared(name, inst.logger, inst.notifier, - inst.telemetry, pixelFormat)); -} - -CS_Sink CreateCvSinkCallback(std::string_view name, - VideoMode::PixelFormat pixelFormat, - std::function processFrame, - CS_Status* status) { - auto& inst = Instance::GetInstance(); - return inst.CreateSink( - CS_SINK_CV, - std::make_shared(name, inst.logger, inst.notifier, - inst.telemetry, pixelFormat, processFrame)); -} - -static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW; - -void SetSinkDescription(CS_Sink sink, std::string_view description, - CS_Status* status) { - auto data = Instance::GetInstance().GetSink(sink); - if (!data || (data->kind & SinkMask) == 0) { - *status = CS_INVALID_HANDLE; - return; - } - static_cast(*data->sink).SetDescription(description); -} - -uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) { - auto data = Instance::GetInstance().GetSink(sink); - if (!data || data->kind != CS_SINK_CV) { - *status = CS_INVALID_HANDLE; - return 0; - } - return static_cast(*data->sink).GrabFrame(image); -} - -uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout, - CS_Status* status) { - auto data = Instance::GetInstance().GetSink(sink); - if (!data || data->kind != CS_SINK_CV) { - *status = CS_INVALID_HANDLE; - return 0; - } - return static_cast(*data->sink).GrabFrame(image, timeout); -} - -std::string GetSinkError(CS_Sink sink, CS_Status* status) { - auto data = Instance::GetInstance().GetSink(sink); - if (!data || (data->kind & SinkMask) == 0) { - *status = CS_INVALID_HANDLE; - return std::string{}; - } - return static_cast(*data->sink).GetError(); -} - -std::string_view GetSinkError(CS_Sink sink, wpi::SmallVectorImpl& buf, - CS_Status* status) { - auto data = Instance::GetInstance().GetSink(sink); - if (!data || (data->kind & SinkMask) == 0) { - *status = CS_INVALID_HANDLE; - return {}; - } - return static_cast(*data->sink).GetError(buf); -} - -void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) { - auto data = Instance::GetInstance().GetSink(sink); - if (!data || (data->kind & SinkMask) == 0) { - *status = CS_INVALID_HANDLE; - return; - } - static_cast(*data->sink).SetEnabled(enabled); -} - -} // namespace cs - -extern "C" { - -CS_Sink CS_CreateCvSink(const char* name, enum WPI_PixelFormat pixelFormat, - CS_Status* status) { - return cs::CreateCvSink( - name, static_cast(pixelFormat), status); -} - -CS_Sink CS_CreateCvSinkCallback(const char* name, - enum WPI_PixelFormat pixelFormat, void* data, - void (*processFrame)(void* data, uint64_t time), - CS_Status* status) { - return cs::CreateCvSinkCallback( - name, static_cast(pixelFormat), - [=](uint64_t time) { processFrame(data, time); }, status); -} - -void CS_SetSinkDescription(CS_Sink sink, const char* description, - CS_Status* status) { - return cs::SetSinkDescription(sink, description, status); -} - -#if CV_VERSION_MAJOR < 4 -uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, - CS_Status* status) { - auto mat = cv::cvarrToMat(image); - return cs::GrabSinkFrame(sink, mat, status); -} - -uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image, - double timeout, CS_Status* status) { - auto mat = cv::cvarrToMat(image); - return cs::GrabSinkFrameTimeout(sink, mat, timeout, status); -} -#endif // CV_VERSION_MAJOR < 4 - -uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status) { - return cs::GrabSinkFrame(sink, *image, status); -} - -uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image, - double timeout, CS_Status* status) { - return cs::GrabSinkFrameTimeout(sink, *image, timeout, status); -} - -char* CS_GetSinkError(CS_Sink sink, CS_Status* status) { - wpi::SmallString<128> buf; - auto str = cs::GetSinkError(sink, buf, status); - if (*status != 0) { - return nullptr; - } - return cs::ConvertToC(str); -} - -void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) { - return cs::SetSinkEnabled(sink, enabled, status); -} - -} // extern "C" diff --git a/cscore/src/main/native/cpp/CvSinkImpl.h b/cscore/src/main/native/cpp/CvSinkImpl.h deleted file mode 100644 index da9392f39c..0000000000 --- a/cscore/src/main/native/cpp/CvSinkImpl.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#ifndef CSCORE_CVSINKIMPL_H_ -#define CSCORE_CVSINKIMPL_H_ - -#include - -#include -#include -#include -#include - -#include -#include - -#include "Frame.h" -#include "SinkImpl.h" - -namespace cs { - -class SourceImpl; - -class CvSinkImpl : public SinkImpl { - public: - CvSinkImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier, - Telemetry& telemetry, VideoMode::PixelFormat pixelFormat); - CvSinkImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier, - Telemetry& telemetry, VideoMode::PixelFormat pixelFormat, - std::function processFrame); - ~CvSinkImpl() override; - - void Stop(); - - uint64_t GrabFrame(cv::Mat& image); - uint64_t GrabFrame(cv::Mat& image, double timeout); - - private: - void ThreadMain(); - - std::atomic_bool m_active; // set to false to terminate threads - std::thread m_thread; - std::function m_processFrame; - VideoMode::PixelFormat m_pixelFormat; -}; - -} // namespace cs - -#endif // CSCORE_CVSINKIMPL_H_ diff --git a/cscore/src/main/native/cpp/CvSourceImpl.cpp b/cscore/src/main/native/cpp/CvSourceImpl.cpp deleted file mode 100644 index 511cf6ceb2..0000000000 --- a/cscore/src/main/native/cpp/CvSourceImpl.cpp +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#include "CvSourceImpl.h" - -#include -#include -#include -#include - -#include "Handle.h" -#include "Instance.h" -#include "Log.h" -#include "Notifier.h" -#include "c_util.h" -#include "cscore_cpp.h" - -using namespace cs; - -CvSourceImpl::CvSourceImpl(std::string_view name, wpi::Logger& logger, - Notifier& notifier, Telemetry& telemetry, - const VideoMode& mode) - : ConfigurableSourceImpl{name, logger, notifier, telemetry, mode} {} - -CvSourceImpl::~CvSourceImpl() = default; - -void CvSourceImpl::PutFrame(cv::Mat& image) { - // We only support 8-bit images; convert if necessary. - cv::Mat finalImage; - if (image.depth() == CV_8U) { - finalImage = image; - } else { - image.convertTo(finalImage, CV_8U); - } - - std::unique_ptr dest; - switch (image.channels()) { - case 1: - dest = - AllocImage(VideoMode::kGray, image.cols, image.rows, image.total()); - finalImage.copyTo(dest->AsMat()); - break; - case 3: - dest = AllocImage(VideoMode::kBGR, image.cols, image.rows, - image.total() * 3); - finalImage.copyTo(dest->AsMat()); - break; - case 4: - dest = AllocImage(VideoMode::kBGR, image.cols, image.rows, - image.total() * 3); - cv::cvtColor(finalImage, dest->AsMat(), cv::COLOR_BGRA2BGR); - break; - default: - SERROR("PutFrame: {}-channel images not supported", image.channels()); - return; - } - SourceImpl::PutFrame(std::move(dest), wpi::Now()); -} - -namespace cs { - -CS_Source CreateCvSource(std::string_view name, const VideoMode& mode, - CS_Status* status) { - auto& inst = Instance::GetInstance(); - return inst.CreateSource(CS_SOURCE_CV, std::make_shared( - name, inst.logger, inst.notifier, - inst.telemetry, mode)); -} - -void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) { - auto data = Instance::GetInstance().GetSource(source); - if (!data || data->kind != CS_SOURCE_CV) { - *status = CS_INVALID_HANDLE; - return; - } - static_cast(*data->source).PutFrame(image); -} - -static constexpr unsigned SourceMask = CS_SINK_CV | CS_SINK_RAW; - -void NotifySourceError(CS_Source source, std::string_view msg, - CS_Status* status) { - auto data = Instance::GetInstance().GetSource(source); - if (!data || (data->kind & SourceMask) == 0) { - *status = CS_INVALID_HANDLE; - return; - } - static_cast(*data->source).NotifyError(msg); -} - -void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) { - auto data = Instance::GetInstance().GetSource(source); - if (!data || (data->kind & SourceMask) == 0) { - *status = CS_INVALID_HANDLE; - return; - } - static_cast(*data->source).SetConnected(connected); -} - -void SetSourceDescription(CS_Source source, std::string_view description, - CS_Status* status) { - auto data = Instance::GetInstance().GetSource(source); - if (!data || (data->kind & SourceMask) == 0) { - *status = CS_INVALID_HANDLE; - return; - } - static_cast(*data->source).SetDescription(description); -} - -CS_Property CreateSourceProperty(CS_Source source, std::string_view name, - CS_PropertyKind kind, int minimum, int maximum, - int step, int defaultValue, int value, - CS_Status* status) { - auto data = Instance::GetInstance().GetSource(source); - if (!data || (data->kind & SourceMask) == 0) { - *status = CS_INVALID_HANDLE; - return -1; - } - int property = static_cast(*data->source) - .CreateProperty(name, kind, minimum, maximum, step, - defaultValue, value); - return Handle{source, property, Handle::kProperty}; -} - -CS_Property CreateSourcePropertyCallback( - CS_Source source, std::string_view name, CS_PropertyKind kind, int minimum, - int maximum, int step, int defaultValue, int value, - std::function onChange, CS_Status* status) { - auto data = Instance::GetInstance().GetSource(source); - if (!data || (data->kind & SourceMask) == 0) { - *status = CS_INVALID_HANDLE; - return -1; - } - int property = static_cast(*data->source) - .CreateProperty(name, kind, minimum, maximum, step, - defaultValue, value, onChange); - return Handle{source, property, Handle::kProperty}; -} - -void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property, - std::span choices, - CS_Status* status) { - auto data = Instance::GetInstance().GetSource(source); - if (!data || (data->kind & SourceMask) == 0) { - *status = CS_INVALID_HANDLE; - return; - } - - // Get property index; also validate the source owns this property - Handle handle{property}; - int i = handle.GetParentIndex(); - if (i < 0) { - *status = CS_INVALID_HANDLE; - return; - } - auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource}); - if (!data2 || data->source.get() != data2->source.get()) { - *status = CS_INVALID_HANDLE; - return; - } - int propertyIndex = handle.GetIndex(); - static_cast(*data->source) - .SetEnumPropertyChoices(propertyIndex, choices, status); -} - -} // namespace cs - -extern "C" { - -CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode, - CS_Status* status) { - return cs::CreateCvSource(name, static_cast(*mode), - status); -} - -#if CV_VERSION_MAJOR < 4 -void CS_PutSourceFrame(CS_Source source, struct CvMat* image, - CS_Status* status) { - auto mat = cv::cvarrToMat(image); - return cs::PutSourceFrame(source, mat, status); -} -#endif // CV_VERSION_MAJOR < 4 - -void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status) { - return cs::PutSourceFrame(source, *image, status); -} - -void CS_NotifySourceError(CS_Source source, const char* msg, - CS_Status* status) { - return cs::NotifySourceError(source, msg, status); -} - -void CS_SetSourceConnected(CS_Source source, CS_Bool connected, - CS_Status* status) { - return cs::SetSourceConnected(source, connected, status); -} - -void CS_SetSourceDescription(CS_Source source, const char* description, - CS_Status* status) { - return cs::SetSourceDescription(source, description, status); -} - -CS_Property CS_CreateSourceProperty(CS_Source source, const char* name, - enum CS_PropertyKind kind, int minimum, - int maximum, int step, int defaultValue, - int value, CS_Status* status) { - return cs::CreateSourceProperty(source, name, kind, minimum, maximum, step, - defaultValue, value, status); -} - -CS_Property CS_CreateSourcePropertyCallback( - CS_Source source, const char* name, enum CS_PropertyKind kind, int minimum, - int maximum, int step, int defaultValue, int value, void* data, - void (*onChange)(void* data, CS_Property property), CS_Status* status) { - return cs::CreateSourcePropertyCallback( - source, name, kind, minimum, maximum, step, defaultValue, value, - [=](CS_Property property) { onChange(data, property); }, status); -} - -void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property, - const char** choices, int count, - CS_Status* status) { - wpi::SmallVector vec; - vec.reserve(count); - for (int i = 0; i < count; ++i) { - vec.push_back(choices[i]); - } - return cs::SetSourceEnumPropertyChoices(source, property, vec, status); -} - -} // extern "C" diff --git a/cscore/src/main/native/cpp/CvSourceImpl.h b/cscore/src/main/native/cpp/CvSourceImpl.h deleted file mode 100644 index fba7131b43..0000000000 --- a/cscore/src/main/native/cpp/CvSourceImpl.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#ifndef CSCORE_CVSOURCEIMPL_H_ -#define CSCORE_CVSOURCEIMPL_H_ - -#include -#include -#include -#include -#include -#include - -#include - -#include "ConfigurableSourceImpl.h" -#include "SourceImpl.h" - -namespace cs { - -class CvSourceImpl : public ConfigurableSourceImpl { - public: - CvSourceImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier, - Telemetry& telemetry, const VideoMode& mode); - ~CvSourceImpl() override; - - // OpenCV-specific functions - void PutFrame(cv::Mat& image); - - private: - std::atomic_bool m_connected{true}; -}; - -} // namespace cs - -#endif // CSCORE_CVSOURCEIMPL_H_ diff --git a/cscore/src/main/native/cpp/RawSinkImpl.cpp b/cscore/src/main/native/cpp/RawSinkImpl.cpp index 77116416d8..74e7359093 100644 --- a/cscore/src/main/native/cpp/RawSinkImpl.cpp +++ b/cscore/src/main/native/cpp/RawSinkImpl.cpp @@ -143,25 +143,28 @@ void RawSinkImpl::ThreadMain() { } namespace cs { -CS_Sink CreateRawSink(std::string_view name, CS_Status* status) { +static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW; + +CS_Sink CreateRawSink(std::string_view name, bool isCv, CS_Status* status) { auto& inst = Instance::GetInstance(); - return inst.CreateSink(CS_SINK_RAW, + return inst.CreateSink(isCv ? CS_SINK_CV : CS_SINK_RAW, std::make_shared( name, inst.logger, inst.notifier, inst.telemetry)); } -CS_Sink CreateRawSinkCallback(std::string_view name, +CS_Sink CreateRawSinkCallback(std::string_view name, bool isCv, 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)); + return inst.CreateSink( + isCv ? CS_SINK_CV : CS_SINK_RAW, + std::make_shared(name, inst.logger, inst.notifier, + inst.telemetry, processFrame)); } uint64_t GrabSinkFrame(CS_Sink sink, WPI_RawFrame& image, CS_Status* status) { auto data = Instance::GetInstance().GetSink(sink); - if (!data || data->kind != CS_SINK_RAW) { + if (!data || (data->kind & SinkMask) == 0) { *status = CS_INVALID_HANDLE; return 0; } @@ -171,25 +174,26 @@ uint64_t GrabSinkFrame(CS_Sink sink, WPI_RawFrame& image, CS_Status* status) { uint64_t GrabSinkFrameTimeout(CS_Sink sink, WPI_RawFrame& image, double timeout, CS_Status* status) { auto data = Instance::GetInstance().GetSink(sink); - if (!data || data->kind != CS_SINK_RAW) { + if (!data || (data->kind & SinkMask) == 0) { *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_CreateRawSink(const char* name, CS_Bool isCv, CS_Status* status) { + return cs::CreateRawSink(name, isCv, status); } -CS_Sink CS_CreateRawSinkCallback(const char* name, void* data, +CS_Sink CS_CreateRawSinkCallback(const char* name, CS_Bool isCv, void* data, void (*processFrame)(void* data, uint64_t time), CS_Status* status) { return cs::CreateRawSinkCallback( - name, [=](uint64_t time) { processFrame(data, time); }, status); + name, isCv, [=](uint64_t time) { processFrame(data, time); }, status); } uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct WPI_RawFrame* image, @@ -201,4 +205,5 @@ uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct WPI_RawFrame* image, double timeout, CS_Status* status) { return cs::GrabSinkFrameTimeout(sink, *image, timeout, status); } + } // extern "C" diff --git a/cscore/src/main/native/cpp/RawSourceImpl.cpp b/cscore/src/main/native/cpp/RawSourceImpl.cpp index 0d53f8993b..b5c27803fe 100644 --- a/cscore/src/main/native/cpp/RawSourceImpl.cpp +++ b/cscore/src/main/native/cpp/RawSourceImpl.cpp @@ -50,34 +50,39 @@ void RawSourceImpl::PutFrame(const WPI_RawFrame& image) { } namespace cs { -CS_Source CreateRawSource(std::string_view name, const VideoMode& mode, - CS_Status* status) { +static constexpr unsigned SourceMask = CS_SOURCE_CV | CS_SOURCE_RAW; + +CS_Source CreateRawSource(std::string_view name, bool isCv, + 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)); + return inst.CreateSource( + isCv ? CS_SOURCE_CV : CS_SOURCE_RAW, + std::make_shared(name, inst.logger, inst.notifier, + inst.telemetry, mode)); } void PutSourceFrame(CS_Source source, const WPI_RawFrame& image, CS_Status* status) { auto data = Instance::GetInstance().GetSource(source); - if (!data || data->kind != CS_SOURCE_RAW) { + if (!data || (data->kind & SourceMask) == 0) { *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); +CS_Source CS_CreateRawSource(const char* name, CS_Bool isCv, + const CS_VideoMode* mode, CS_Status* status) { + return cs::CreateRawSource(name, isCv, + static_cast(*mode), status); } void CS_PutRawSourceFrame(CS_Source source, const struct WPI_RawFrame* image, CS_Status* status) { return cs::PutSourceFrame(source, *image, status); } + } // extern "C" diff --git a/cscore/src/main/native/cpp/SinkImpl.cpp b/cscore/src/main/native/cpp/SinkImpl.cpp index 93a625f814..3d4e70cf9a 100644 --- a/cscore/src/main/native/cpp/SinkImpl.cpp +++ b/cscore/src/main/native/cpp/SinkImpl.cpp @@ -4,11 +4,13 @@ #include "SinkImpl.h" +#include #include #include "Instance.h" #include "Notifier.h" #include "SourceImpl.h" +#include "c_util.h" using namespace cs; @@ -195,3 +197,67 @@ void SinkImpl::UpdatePropertyValue(int property, bool setString, int value, } void SinkImpl::SetSourceImpl(std::shared_ptr source) {} + +namespace cs { +static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW; + +void SetSinkDescription(CS_Sink sink, std::string_view description, + CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data || (data->kind & SinkMask) == 0) { + *status = CS_INVALID_HANDLE; + return; + } + data->sink->SetDescription(description); +} + +std::string GetSinkError(CS_Sink sink, CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data || (data->kind & SinkMask) == 0) { + *status = CS_INVALID_HANDLE; + return std::string{}; + } + return data->sink->GetError(); +} + +std::string_view GetSinkError(CS_Sink sink, wpi::SmallVectorImpl& buf, + CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data || (data->kind & SinkMask) == 0) { + *status = CS_INVALID_HANDLE; + return {}; + } + return data->sink->GetError(buf); +} + +void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data || (data->kind & SinkMask) == 0) { + *status = CS_INVALID_HANDLE; + return; + } + data->sink->SetEnabled(enabled); +} + +} // namespace cs + +extern "C" { +void CS_SetSinkDescription(CS_Sink sink, const char* description, + CS_Status* status) { + return cs::SetSinkDescription(sink, description, status); +} + +char* CS_GetSinkError(CS_Sink sink, CS_Status* status) { + wpi::SmallString<128> buf; + auto str = cs::GetSinkError(sink, buf, status); + if (*status != 0) { + return nullptr; + } + return cs::ConvertToC(str); +} + +void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) { + return cs::SetSinkEnabled(sink, enabled, status); +} + +} // extern "C" diff --git a/cscore/src/main/native/cpp/cscore_c.cpp b/cscore/src/main/native/cpp/cscore_c.cpp index 45583acc57..4670dd4c8e 100644 --- a/cscore/src/main/native/cpp/cscore_c.cpp +++ b/cscore/src/main/native/cpp/cscore_c.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include diff --git a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp index d7a9b4537e..e65e9ac6b5 100644 --- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp +++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp @@ -6,7 +6,6 @@ #include #include -#include #define WPI_RAWFRAME_JNI #include @@ -82,32 +81,6 @@ static void ListenerOnExit() { jvm->DetachCurrentThread(); } -/// throw java exception -static void ThrowJavaException(JNIEnv* env, const std::exception* e) { - wpi::SmallString<128> what; - jclass je = nullptr; - - if (e) { - const char* exception_type = "std::exception"; - - if (dynamic_cast(e)) { - exception_type = "cv::Exception"; - je = env->FindClass("org/opencv/core/CvException"); - } - - what = exception_type; - what += ": "; - what += e->what(); - } else { - what = "unknown exception"; - } - - if (!je) { - je = exceptionEx; - } - env->ThrowNew(je, what.c_str()); -} - extern "C" { JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { @@ -593,40 +566,15 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createHttpCameraMulti return val; } -/* - * Class: edu_wpi_first_cscore_CameraServerCvJNI - * Method: createCvSource - * Signature: (Ljava/lang/String;IIII)I - */ -JNIEXPORT jint JNICALL -Java_edu_wpi_first_cscore_CameraServerCvJNI_createCvSource - (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::CreateCvSource( - 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_first_cscore_CameraServerJNI * Method: createRawSource - * Signature: (Ljava/lang/String;IIII)I + * Signature: (Ljava/lang/String;ZIIII)I */ JNIEXPORT jint JNICALL Java_edu_wpi_first_cscore_CameraServerJNI_createRawSource - (JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height, - jint fps) + (JNIEnv* env, jclass, jstring name, jboolean isCv, jint pixelFormat, + jint width, jint height, jint fps) { if (!name) { nullPointerEx.Throw(env, "name cannot be null"); @@ -634,7 +582,7 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createRawSource } CS_Status status = 0; auto val = cs::CreateRawSource( - JStringRef{env, name}.str(), + JStringRef{env, name}.str(), isCv, cs::VideoMode{static_cast(pixelFormat), static_cast(width), static_cast(height), static_cast(fps)}, @@ -1202,27 +1150,6 @@ Java_edu_wpi_first_cscore_CameraServerJNI_getHttpCameraUrls return MakeJStringArray(env, arr); } -/* - * Class: edu_wpi_first_cscore_CameraServerCvJNI - * Method: putSourceFrame - * Signature: (IJ)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_cscore_CameraServerCvJNI_putSourceFrame - (JNIEnv* env, jclass, jint source, jlong imageNativeObj) -{ - try { - cv::Mat& image = *((cv::Mat*)imageNativeObj); - CS_Status status = 0; - cs::PutSourceFrame(source, image, &status); - CheckStatus(env, status); - } catch (const std::exception& e) { - ThrowJavaException(env, &e); - } catch (...) { - ThrowJavaException(env, nullptr); - } -} - /* * Class: edu_wpi_first_cscore_CameraServerJNI * Method: putRawSourceFrame @@ -1421,42 +1348,21 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createMjpegServer return val; } -/* - * Class: edu_wpi_first_cscore_CameraServerCvJNI - * Method: createCvSink - * Signature: (Ljava/lang/String;I)I - */ -JNIEXPORT jint JNICALL -Java_edu_wpi_first_cscore_CameraServerCvJNI_createCvSink - (JNIEnv* env, jclass, jstring name, jint pixelFormat) -{ - if (!name) { - nullPointerEx.Throw(env, "name cannot be null"); - return 0; - } - CS_Status status = 0; - auto val = cs::CreateCvSink( - JStringRef{env, name}.str(), - static_cast(pixelFormat), &status); - CheckStatus(env, status); - return val; -} - /* * Class: edu_wpi_first_cscore_CameraServerJNI * Method: createRawSink - * Signature: (Ljava/lang/String;)I + * Signature: (Ljava/lang/String;Z)I */ JNIEXPORT jint JNICALL Java_edu_wpi_first_cscore_CameraServerJNI_createRawSink - (JNIEnv* env, jclass, jstring name) + (JNIEnv* env, jclass, jstring name, jboolean isCv) { if (!name) { nullPointerEx.Throw(env, "name cannot be null"); return 0; } CS_Status status = 0; - auto val = cs::CreateRawSink(JStringRef{env, name}.str(), &status); + auto val = cs::CreateRawSink(JStringRef{env, name}.str(), isCv, &status); CheckStatus(env, status); return val; } @@ -1707,54 +1613,6 @@ Java_edu_wpi_first_cscore_CameraServerJNI_setSinkDescription CheckStatus(env, status); } -/* - * Class: edu_wpi_first_cscore_CameraServerCvJNI - * Method: grabSinkFrame - * Signature: (IJ)J - */ -JNIEXPORT jlong JNICALL -Java_edu_wpi_first_cscore_CameraServerCvJNI_grabSinkFrame - (JNIEnv* env, jclass, jint sink, jlong imageNativeObj) -{ - try { - cv::Mat& image = *((cv::Mat*)imageNativeObj); - CS_Status status = 0; - auto rv = cs::GrabSinkFrame(sink, image, &status); - CheckStatus(env, status); - return rv; - } catch (const std::exception& e) { - ThrowJavaException(env, &e); - return 0; - } catch (...) { - ThrowJavaException(env, nullptr); - return 0; - } -} - -/* - * Class: edu_wpi_first_cscore_CameraServerCvJNI - * Method: grabSinkFrameTimeout - * Signature: (IJD)J - */ -JNIEXPORT jlong JNICALL -Java_edu_wpi_first_cscore_CameraServerCvJNI_grabSinkFrameTimeout - (JNIEnv* env, jclass, jint sink, jlong imageNativeObj, jdouble timeout) -{ - try { - cv::Mat& image = *((cv::Mat*)imageNativeObj); - CS_Status status = 0; - auto rv = cs::GrabSinkFrameTimeout(sink, image, timeout, &status); - CheckStatus(env, status); - return rv; - } catch (const std::exception& e) { - ThrowJavaException(env, &e); - return 0; - } catch (...) { - ThrowJavaException(env, nullptr); - return 0; - } -} - /* * Class: edu_wpi_first_cscore_CameraServerJNI * Method: grabRawSinkFrame diff --git a/cscore/src/main/native/include/cscore_c.h b/cscore/src/main/native/include/cscore_c.h index 277d0ffc03..45a334269a 100644 --- a/cscore/src/main/native/include/cscore_c.h +++ b/cscore/src/main/native/include/cscore_c.h @@ -347,7 +347,7 @@ char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status); /** @} */ /** - * @defgroup cscore_opencv_source_cfunc OpenCV Source Functions + * @defgroup cscore_frame_source_cfunc Frame Source Functions * @{ */ void CS_NotifySourceError(CS_Source source, const char* msg, CS_Status* status); @@ -409,7 +409,7 @@ int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status); /** @} */ /** - * @defgroup cscore_opencv_sink_cfunc OpenCV Sink Functions + * @defgroup cscore_frame_sink_cfunc Frame Sink Functions * @{ */ void CS_SetSinkDescription(CS_Sink sink, const char* description, diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h index 1d15039018..76c37fed91 100644 --- a/cscore/src/main/native/include/cscore_cpp.h +++ b/cscore/src/main/native/include/cscore_cpp.h @@ -294,7 +294,7 @@ std::vector GetHttpCameraUrls(CS_Source source, CS_Status* status); /** @} */ /** - * @defgroup cscore_opencv_source_func OpenCV Source Functions + * @defgroup cscore_frame_source_func Frame Source Functions * @{ */ void NotifySourceError(CS_Source source, std::string_view msg, @@ -365,7 +365,7 @@ int GetMjpegServerPort(CS_Sink sink, CS_Status* status); /** @} */ /** - * @defgroup cscore_opencv_sink_func OpenCV Sink Functions + * @defgroup cscore_frame_sink_func Frame Sink Functions * @{ */ void SetSinkDescription(CS_Sink sink, std::string_view description, diff --git a/cscore/src/main/native/include/cscore_cv.h b/cscore/src/main/native/include/cscore_cv.h index 6fdfdfb95d..e623f6aea3 100644 --- a/cscore/src/main/native/include/cscore_cv.h +++ b/cscore/src/main/native/include/cscore_cv.h @@ -7,72 +7,17 @@ #include -#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 - -#if CV_VERSION_MAJOR < 4 - -#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 - -#endif // CV_VERSION_MAJOR < 4 - -#ifdef __cplusplus +#include #include "cscore_oo.h" - -namespace cv { -class Mat; -} // namespace cv +#include "cscore_raw.h" 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. + * + * This is not dependent on any opencv binary ABI, and can be used + * with versions of most versions of OpenCV. */ class CvSource : public ImageSource { public: @@ -87,7 +32,7 @@ class CvSource : public ImageSource { CvSource(std::string_view name, const VideoMode& mode); /** - * Create an OpenCV source. + * Create an OpenCV source. * * @param name Source name (arbitrary unique identifier) * @param pixelFormat Pixel format @@ -111,14 +56,15 @@ class CvSource : public ImageSource { }; /** - * 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. + * A source for user code to accept video frames as OpenCV images. + * + * This is not dependent on any opencv binary ABI, and can be used + * with versions of most versions of OpenCV. */ class CvSink : public ImageSink { public: CvSink() = default; + CvSink(const CvSink& sink); /** * Create a sink for accepting OpenCV images. @@ -127,89 +73,191 @@ class CvSink : public ImageSink { * image. * * @param name Source name (arbitrary unique identifier) - * @param pixelFormat Source pixel format + * @param pixelFormat The pixel format to read */ explicit CvSink(std::string_view name, VideoMode::PixelFormat pixelFormat = VideoMode::PixelFormat::kBGR); /** - * Create a sink for accepting OpenCV images in a separate thread. + * Wait for the next frame and get the image. + * Times out (returning 0) after timeout seconds. + * The provided image will have the pixelFormat this class was constructed + * with. * - *

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(). - * @param pixelFormat Source pixel format + * @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. */ - CvSink(std::string_view name, std::function processFrame, - VideoMode::PixelFormat pixelFormat = VideoMode::PixelFormat::kBGR); + [[nodiscard]] + 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 the pixelFormat this class was constructed + * with. + * + * @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. + */ + [[nodiscard]] + 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. + * The provided image will have the pixelFormat this class was constructed + * with. The data is backed by data in the CvSink. It will be invalidated by + * any grabFrame*() call on the sink. * * @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. */ [[nodiscard]] - uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) const; + 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. + * The provided image will have the pixelFormat this class was constructed + * with. The data is backed by data in the CvSink. It will be invalidated by + * any grabFrame*() call on the sink. * * @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. */ [[nodiscard]] - uint64_t GrabFrameNoTimeout(cv::Mat& image) const; + uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image); + + private: + wpi::RawFrame rawFrame; + VideoMode::PixelFormat pixelFormat; }; inline CvSource::CvSource(std::string_view name, const VideoMode& mode) { - m_handle = CreateCvSource(name, mode, &m_status); + m_handle = CreateRawSource(name, true, mode, &m_status); } inline CvSource::CvSource(std::string_view name, VideoMode::PixelFormat format, int width, int height, int fps) { - m_handle = - CreateCvSource(name, VideoMode{format, width, height, fps}, &m_status); + m_handle = CreateRawSource(name, true, VideoMode{format, width, height, fps}, + &m_status); } inline void CvSource::PutFrame(cv::Mat& image) { + // We only support 8-bit images; convert if necessary. + cv::Mat finalImage; + if (image.depth() == CV_8U) { + finalImage = image; + } else { + image.convertTo(finalImage, CV_8U); + } + + int channels = finalImage.channels(); + WPI_PixelFormat format; + if (channels == 1) { + format = WPI_PIXFMT_GRAY; + } else if (channels == 3) { + format = WPI_PIXFMT_BGR; + } else { + // TODO Error + return; + } + // TODO old code supported BGRA, but the only way I can support that is slow. + // Update cscore to support BGRA for raw frames + + WPI_RawFrame frame; // use WPI_Frame because we don't want the destructor + frame.data = finalImage.data; + frame.freeFunc = nullptr; + frame.freeCbData = nullptr; + frame.size = finalImage.total() * channels; + frame.width = finalImage.cols; + frame.height = finalImage.rows; + frame.stride = finalImage.cols; + frame.pixelFormat = format; m_status = 0; - PutSourceFrame(m_handle, image, &m_status); + PutSourceFrame(m_handle, frame, &m_status); } inline CvSink::CvSink(std::string_view name, VideoMode::PixelFormat pixelFormat) { - m_handle = CreateCvSink(name, pixelFormat, &m_status); + m_handle = CreateRawSink(name, true, &m_status); + this->pixelFormat = pixelFormat; } -inline CvSink::CvSink(std::string_view name, - std::function processFrame, - VideoMode::PixelFormat pixelFormat) { - m_handle = CreateCvSinkCallback(name, pixelFormat, processFrame, &m_status); +inline CvSink::CvSink(const CvSink& sink) + : ImageSink{sink}, pixelFormat{sink.pixelFormat} {} + +inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) { + cv::Mat tmpnam; + auto retVal = GrabFrameDirect(tmpnam); + if (retVal <= 0) { + return retVal; + } + tmpnam.copyTo(image); + return retVal; } -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) { + cv::Mat tmpnam; + auto retVal = GrabFrameNoTimeoutDirect(tmpnam); + if (retVal <= 0) { + return retVal; + } + tmpnam.copyTo(image); + return retVal; } -inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) const { - m_status = 0; - return GrabSinkFrame(m_handle, image, &m_status); +inline constexpr int GetCvFormat(WPI_PixelFormat pixelFormat) { + int type = 0; + switch (pixelFormat) { + case WPI_PIXFMT_YUYV: + case WPI_PIXFMT_RGB565: + type = CV_8UC2; + break; + case WPI_PIXFMT_BGR: + type = CV_8UC3; + break; + case WPI_PIXFMT_GRAY: + case WPI_PIXFMT_MJPEG: + default: + type = CV_8UC1; + break; + } + return type; +} + +inline uint64_t CvSink::GrabFrameDirect(cv::Mat& image, double timeout) { + rawFrame.height = 0; + rawFrame.width = 0; + rawFrame.pixelFormat = pixelFormat; + m_status = GrabSinkFrameTimeout(m_handle, rawFrame, timeout, &m_status); + if (m_status <= 0) { + return m_status; + } + image = + cv::Mat{rawFrame.height, rawFrame.width, + GetCvFormat(static_cast(rawFrame.pixelFormat)), + rawFrame.data}; + return m_status; +} + +inline uint64_t CvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) { + rawFrame.height = 0; + rawFrame.width = 0; + rawFrame.pixelFormat = pixelFormat; + m_status = GrabSinkFrame(m_handle, rawFrame, &m_status); + if (m_status <= 0) { + return m_status; + } + image = + cv::Mat{rawFrame.height, rawFrame.width, + GetCvFormat(static_cast(rawFrame.pixelFormat)), + rawFrame.data}; + return 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 3a55d86376..1f96476742 100644 --- a/cscore/src/main/native/include/cscore_oo.h +++ b/cscore/src/main/native/include/cscore_oo.h @@ -221,7 +221,9 @@ class VideoSource { /// HTTP video source. kHttp = CS_SOURCE_HTTP, /// CV video source. - kCv = CS_SOURCE_CV + kCv = CS_SOURCE_CV, + /// Raw video source. + kRaw = CS_SOURCE_RAW, }; /** Connection strategy. Used for SetConnectionStrategy(). */ @@ -856,7 +858,9 @@ class VideoSink { /// MJPEG video sink. kMjpeg = CS_SINK_MJPEG, /// CV video sink. - kCv = CS_SINK_CV + kCv = CS_SINK_CV, + /// Raw video sink. + kRaw = CS_SINK_RAW, }; VideoSink() noexcept = default; diff --git a/cscore/src/main/native/include/cscore_raw.h b/cscore/src/main/native/include/cscore_raw.h index 6d49612218..9367c26c6b 100644 --- a/cscore/src/main/native/include/cscore_raw.h +++ b/cscore/src/main/native/include/cscore_raw.h @@ -28,9 +28,9 @@ uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct WPI_RawFrame* rawImage, uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct WPI_RawFrame* rawImage, double timeout, CS_Status* status); -CS_Sink CS_CreateRawSink(const char* name, CS_Status* status); +CS_Sink CS_CreateRawSink(const char* name, CS_Bool isCv, CS_Status* status); -CS_Sink CS_CreateRawSinkCallback(const char* name, void* data, +CS_Sink CS_CreateRawSinkCallback(const char* name, CS_Bool isCv, void* data, void (*processFrame)(void* data, uint64_t time), CS_Status* status); @@ -38,8 +38,8 @@ CS_Sink CS_CreateRawSinkCallback(const char* name, void* data, void CS_PutRawSourceFrame(CS_Source source, const struct WPI_RawFrame* image, CS_Status* status); -CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode, - CS_Status* status); +CS_Source CS_CreateRawSource(const char* name, CS_Bool isCv, + const CS_VideoMode* mode, CS_Status* status); /** @} */ #ifdef __cplusplus @@ -54,11 +54,11 @@ namespace cs { * @{ */ -CS_Source CreateRawSource(std::string_view name, const VideoMode& mode, - CS_Status* status); +CS_Source CreateRawSource(std::string_view name, bool isCv, + const VideoMode& mode, CS_Status* status); -CS_Sink CreateRawSink(std::string_view name, CS_Status* status); -CS_Sink CreateRawSinkCallback(std::string_view name, +CS_Sink CreateRawSink(std::string_view name, bool isCv, CS_Status* status); +CS_Sink CreateRawSinkCallback(std::string_view name, bool isCv, std::function processFrame, CS_Status* status); @@ -166,14 +166,14 @@ class RawSink : public ImageSink { }; inline RawSource::RawSource(std::string_view name, const VideoMode& mode) { - m_handle = CreateRawSource(name, mode, &m_status); + m_handle = CreateRawSource(name, false, mode, &m_status); } inline RawSource::RawSource(std::string_view name, VideoMode::PixelFormat format, int width, int height, int fps) { - m_handle = - CreateRawSource(name, VideoMode{format, width, height, fps}, &m_status); + m_handle = CreateRawSource(name, false, VideoMode{format, width, height, fps}, + &m_status); } inline void RawSource::PutFrame(wpi::RawFrame& image) { @@ -182,12 +182,12 @@ inline void RawSource::PutFrame(wpi::RawFrame& image) { } inline RawSink::RawSink(std::string_view name) { - m_handle = CreateRawSink(name, &m_status); + m_handle = CreateRawSink(name, false, &m_status); } inline RawSink::RawSink(std::string_view name, std::function processFrame) { - m_handle = CreateRawSinkCallback(name, processFrame, &m_status); + m_handle = CreateRawSinkCallback(name, false, processFrame, &m_status); } inline uint64_t RawSink::GrabFrame(wpi::RawFrame& image, double timeout) const { diff --git a/cscore/src/main/native/include/cscore_raw_cv.h b/cscore/src/main/native/include/cscore_raw_cv.h deleted file mode 100644 index 7b687f8f90..0000000000 --- a/cscore/src/main/native/include/cscore_raw_cv.h +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#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 - -#include - -#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(std::string_view 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(std::string_view 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: - wpi::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(std::string_view 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(std::string_view 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. - */ - [[nodiscard]] - 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. - */ - [[nodiscard]] - 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. - */ - [[nodiscard]] - 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. - */ - [[nodiscard]] - uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image); - - private: - wpi::RawFrame rawFrame; -}; - -inline RawCvSource::RawCvSource(std::string_view name, const VideoMode& mode) - : RawSource{name, mode} {} - -inline RawCvSource::RawCvSource(std::string_view 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 ? WPI_PIXFMT_BGR : WPI_PIXFMT_GRAY; - PutSourceFrame(m_handle, rawFrame, &m_status); -} - -inline RawCvSink::RawCvSink(std::string_view name) : RawSink{name} {} - -inline RawCvSink::RawCvSink(std::string_view name, - std::function processFrame) - : RawSink{name, processFrame} {} - -inline uint64_t RawCvSink::GrabFrame(cv::Mat& image, double timeout) { - cv::Mat tmpnam; - auto retVal = GrabFrameDirect(tmpnam); - if (retVal <= 0) { - return retVal; - } - tmpnam.copyTo(image); - return retVal; -} - -inline uint64_t RawCvSink::GrabFrameNoTimeout(cv::Mat& image) { - cv::Mat tmpnam; - auto retVal = GrabFrameNoTimeoutDirect(tmpnam); - if (retVal <= 0) { - return retVal; - } - tmpnam.copyTo(image); - return retVal; -} - -inline uint64_t RawCvSink::GrabFrameDirect(cv::Mat& image, double timeout) { - rawFrame.height = 0; - rawFrame.width = 0; - rawFrame.pixelFormat = WPI_PixelFormat::WPI_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 = WPI_PixelFormat::WPI_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_ diff --git a/styleguide/spotbugs-exclude.xml b/styleguide/spotbugs-exclude.xml index ba4feb2ffd..e015399a7f 100644 --- a/styleguide/spotbugs-exclude.xml +++ b/styleguide/spotbugs-exclude.xml @@ -155,4 +155,13 @@ + + + +