diff --git a/include/cameraserver_c.h b/include/cameraserver_c.h index 64fbb8dda3..c82ea8b953 100644 --- a/include/cameraserver_c.h +++ b/include/cameraserver_c.h @@ -189,6 +189,8 @@ void CS_ReleaseSink(CS_Sink sink, CS_Status* status); // // OpenCV Sink Functions // +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); char* CS_GetSinkError(CS_Sink sink, CS_Status* status); void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status); diff --git a/include/cameraserver_cpp.h b/include/cameraserver_cpp.h index e7d4ce04bd..6f75104f17 100644 --- a/include/cameraserver_cpp.h +++ b/include/cameraserver_cpp.h @@ -182,6 +182,8 @@ void ReleaseSink(CS_Sink sink, CS_Status* status); // // OpenCV Sink Functions // +void SetSinkDescription(CS_Sink sink, llvm::StringRef description, + CS_Status* status); uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status); std::string GetSinkError(CS_Sink sink, CS_Status* status); llvm::StringRef GetSinkError(CS_Sink sink, llvm::SmallVectorImpl& buf, diff --git a/include/cameraserver_oo.h b/include/cameraserver_oo.h index 294163f8fb..0d3285b881 100644 --- a/include/cameraserver_oo.h +++ b/include/cameraserver_oo.h @@ -340,7 +340,7 @@ class CvSink : public VideoSink { /// WaitForFrame() must be called on the created sink to get each new /// image. /// @param name Source name (arbitrary unique identifier) - CvSink(llvm::StringRef name); + explicit CvSink(llvm::StringRef name); /// Create a sink for accepting OpenCV images in a separate thread. /// A thread will be created that calls WaitForFrame() and calls the @@ -352,6 +352,10 @@ class CvSink : public VideoSink { /// unusual circumstances) WaitForImage(). CvSink(llvm::StringRef name, std::function processFrame); + /// Set sink description. + /// @param description Description + void SetDescription(llvm::StringRef description); + /// Wait for the next frame and get the image. /// @return Frame time, or 0 on error (call GetError() to obtain the error /// message); diff --git a/include/cameraserver_oo.inl b/include/cameraserver_oo.inl index ced151783c..dd4998762f 100644 --- a/include/cameraserver_oo.inl +++ b/include/cameraserver_oo.inl @@ -290,6 +290,11 @@ inline CvSink::CvSink(llvm::StringRef name, m_handle = CreateCvSinkCallback(name, processFrame, &m_status); } +inline void CvSink::SetDescription(llvm::StringRef description) { + m_status = 0; + SetSinkDescription(m_handle, description, &m_status); +} + inline uint64_t CvSink::GrabFrame(cv::Mat& image) const { m_status = 0; return GrabSinkFrame(m_handle, image, &m_status); diff --git a/java/lib/CameraServerJNI.cpp b/java/lib/CameraServerJNI.cpp index 7deffbffbd..ca002e21bd 100644 --- a/java/lib/CameraServerJNI.cpp +++ b/java/lib/CameraServerJNI.cpp @@ -742,6 +742,19 @@ JNIEXPORT void JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_releaseSink CheckStatus(env, status); } +/* + * Class: edu_wpi_cameraserver_CameraServerJNI + * Method: setSinkDescription + * Signature: (ILjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_setSinkDescription + (JNIEnv *env, jclass, jint sink, jstring description) +{ + CS_Status status = 0; + cs::SetSinkDescription(sink, JStringRef{env, description}, &status); + CheckStatus(env, status); +} + /* * Class: edu_wpi_cameraserver_CameraServerJNI * Method: grabSinkFrame diff --git a/java/src/edu/wpi/cameraserver/CameraServerJNI.java b/java/src/edu/wpi/cameraserver/CameraServerJNI.java index 64a44e5c0d..13589b0312 100644 --- a/java/src/edu/wpi/cameraserver/CameraServerJNI.java +++ b/java/src/edu/wpi/cameraserver/CameraServerJNI.java @@ -148,6 +148,7 @@ public class CameraServerJNI { // // OpenCV Sink Functions // + public static native void setSinkDescription(int sink, String description); public static native long grabSinkFrame(int sink, long imageNativeObj); public static native String getSinkError(int sink); public static native void setSinkEnabled(int sink, boolean enabled); diff --git a/java/src/edu/wpi/cameraserver/CvSink.java b/java/src/edu/wpi/cameraserver/CvSink.java index 75b6a45f70..531cba4b79 100644 --- a/java/src/edu/wpi/cameraserver/CvSink.java +++ b/java/src/edu/wpi/cameraserver/CvSink.java @@ -32,6 +32,12 @@ 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. /// @return Frame time, or 0 on error (call GetError() to obtain the error /// message); diff --git a/src/CvSinkImpl.cpp b/src/CvSinkImpl.cpp new file mode 100644 index 0000000000..acb86cdcae --- /dev/null +++ b/src/CvSinkImpl.cpp @@ -0,0 +1,175 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "CvSinkImpl.h" + +#include "llvm/SmallString.h" +#include "opencv2/core/core.hpp" +#include "opencv2/highgui/highgui.hpp" + +#include "cameraserver_cpp.h" +#include "c_util.h" +#include "Handle.h" +#include "Log.h" + +using namespace cs; + +CvSinkImpl::CvSinkImpl(llvm::StringRef name) : SinkImpl{name} { + m_active = true; + // m_thread = std::thread(&CvSinkImpl::ThreadMain, this); +} + +CvSinkImpl::CvSinkImpl(llvm::StringRef name, + std::function processFrame) + : SinkImpl{name} {} + +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) return 0; + auto frame = source->GetNextFrame(); // blocks + if (!frame) return 0; // signal error + cv::imdecode(cv::InputArray{frame.data(), static_cast(frame.size())}, + cv::IMREAD_COLOR, &image); + return frame.time(); +} + +// 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; + } + DEBUG4("Cv: 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(llvm::StringRef name, CS_Status* status) { + auto sink = std::make_shared(name); + return Sinks::GetInstance().Allocate(SinkData::kCv, sink); +} + +CS_Sink CreateCvSinkCallback(llvm::StringRef name, + std::function processFrame, + CS_Status* status) { + auto sink = std::make_shared(name, processFrame); + return Sinks::GetInstance().Allocate(SinkData::kCv, sink); +} + +void SetSinkDescription(CS_Sink sink, llvm::StringRef description, + CS_Status* status) { + auto data = Sinks::GetInstance().Get(sink); + if (!data || data->type != SinkData::kCv) { + *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 = Sinks::GetInstance().Get(sink); + if (!data || data->type != SinkData::kCv) { + *status = CS_INVALID_HANDLE; + return 0; + } + return static_cast(*data->sink).GrabFrame(image); +} + +std::string GetSinkError(CS_Sink sink, CS_Status* status) { + auto data = Sinks::GetInstance().Get(sink); + if (!data || data->type != SinkData::kCv) { + *status = CS_INVALID_HANDLE; + return std::string{}; + } + return static_cast(*data->sink).GetError(); +} + +llvm::StringRef GetSinkError(CS_Sink sink, llvm::SmallVectorImpl& buf, + CS_Status* status) { + auto data = Sinks::GetInstance().Get(sink); + if (!data || data->type != SinkData::kCv) { + *status = CS_INVALID_HANDLE; + return llvm::StringRef{}; + } + return static_cast(*data->sink).GetError(buf); +} + +void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) { + auto data = Sinks::GetInstance().Get(sink); + if (!data || data->type != SinkData::kCv) { + *status = CS_INVALID_HANDLE; + return; + } + static_cast(*data->sink).SetEnabled(enabled); +} + +} // namespace cs + +extern "C" { + +CS_Sink CS_CreateCvSink(const char* name, CS_Status* status) { + return cs::CreateCvSink(name, status); +} + +CS_Sink CS_CreateCvSinkCallback(const char* name, void* data, + void (*processFrame)(void* data, uint64_t time), + CS_Status* status) { + return cs::CreateCvSinkCallback( + name, [=](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); +} + +uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, + CS_Status* status) { + auto mat = cv::cvarrToMat(image); + return cs::GrabSinkFrame(sink, mat, status); +} + +char* CS_GetSinkError(CS_Sink sink, CS_Status* status) { + llvm::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/src/CvSinkImpl.h b/src/CvSinkImpl.h new file mode 100644 index 0000000000..ed5091f2cd --- /dev/null +++ b/src/CvSinkImpl.h @@ -0,0 +1,53 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CAMERASERVER_CVSINKIMPL_H_ +#define CAMERASERVER_CVSINKIMPL_H_ + +#include +#include +#include +#include +#include +#include + +#include "llvm/raw_ostream.h" +#include "llvm/SmallVector.h" +#include "llvm/StringRef.h" +#include "support/raw_istream.h" +#include "support/raw_socket_ostream.h" +#include "tcpsockets/NetworkAcceptor.h" +#include "tcpsockets/NetworkStream.h" + +#include "SinkImpl.h" + +namespace cs { + +class SourceImpl; + +class CvSinkImpl : public SinkImpl { + public: + CvSinkImpl(llvm::StringRef name); + CvSinkImpl(llvm::StringRef name, + std::function processFrame); + ~CvSinkImpl() override; + + void Stop(); + + uint64_t GrabFrame(cv::Mat& image); + + private: + void ThreadMain(); + + std::atomic_bool m_active; // set to false to terminate threads + std::thread m_thread; + std::function m_processFrame; +}; + +} // namespace cs + +#endif // CAMERASERVER_CVSINKIMPL_H_ diff --git a/src/cameraserver_c.cpp b/src/cameraserver_c.cpp index e9b4d7cbac..69f8d28af3 100644 --- a/src/cameraserver_c.cpp +++ b/src/cameraserver_c.cpp @@ -176,17 +176,6 @@ void CS_ReleaseSource(CS_Source source, CS_Status* status) { return cs::ReleaseSource(source, status); } -CS_Sink CS_CreateCvSink(const char* name, CS_Status* status) { - return cs::CreateCvSink(name, status); -} - -CS_Sink CS_CreateCvSinkCallback(const char* name, void* data, - void (*processFrame)(void* data, uint64_t time), - CS_Status* status) { - return cs::CreateCvSinkCallback( - name, [=](uint64_t time) { processFrame(data, time); }, status); -} - char* CS_GetSinkName(CS_Sink sink, CS_Status* status) { llvm::SmallString<128> buf; auto str = cs::GetSinkName(sink, buf, status); @@ -222,23 +211,6 @@ void CS_ReleaseSink(CS_Sink sink, CS_Status* status) { return cs::ReleaseSink(sink, status); } -uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, - CS_Status* status) { - auto mat = cv::cvarrToMat(image); - return cs::GrabSinkFrame(sink, mat, status); -} - -char* CS_GetSinkError(CS_Sink sink, CS_Status* status) { - llvm::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); -} - CS_Listener CS_AddSourceListener(void* data, void (*callback)(void* data, const char* name, CS_Source source, int event), diff --git a/src/cameraserver_cpp.cpp b/src/cameraserver_cpp.cpp index 584fb445bb..f58c195565 100644 --- a/src/cameraserver_cpp.cpp +++ b/src/cameraserver_cpp.cpp @@ -320,23 +320,10 @@ void ReleaseSource(CS_Source source, CS_Status* status) { if (data->refCount-- == 0) inst.Free(source); } -// -// Sink Creation Functions -// - -CS_Sink CreateCvSink(llvm::StringRef name, CS_Status* status) { - return 0; // TODO -} - -CS_Sink CreateCvSinkCallback(llvm::StringRef name, - std::function processFrame, - CS_Status* status) { - return 0; // TODO -} - // // Sink Functions // + std::string GetSinkName(CS_Sink sink, CS_Status* status) { auto data = Sinks::GetInstance().Get(sink); if (!data) { @@ -433,27 +420,6 @@ void ReleaseSink(CS_Sink sink, CS_Status* status) { if (data->refCount-- == 0) inst.Free(sink); } -// -// OpenCV Sink Functions -// - -uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) { - return 0; // TODO -} - -std::string GetSinkError(CS_Sink sink, CS_Status* status) { - return std::string{}; // TODO -} - -llvm::StringRef GetSinkError(CS_Sink sink, llvm::SmallVectorImpl& buf, - CS_Status* status) { - return llvm::StringRef{}; // TODO -} - -void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) { - // TODO -} - // // Listener Functions //