2016-10-26 23:37:00 -07:00
|
|
|
/*----------------------------------------------------------------------------*/
|
2018-01-02 09:16:20 -08:00
|
|
|
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
2016-10-26 23:37:00 -07:00
|
|
|
/* 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"
|
|
|
|
|
|
2017-08-25 17:48:06 -07:00
|
|
|
#include <opencv2/core/core.hpp>
|
|
|
|
|
#include <opencv2/highgui/highgui.hpp>
|
|
|
|
|
#include <opencv2/imgproc/imgproc.hpp>
|
2018-04-29 23:33:19 -07:00
|
|
|
#include <wpi/SmallString.h>
|
2016-10-26 23:37:00 -07:00
|
|
|
|
|
|
|
|
#include "Handle.h"
|
2018-10-31 20:22:58 -07:00
|
|
|
#include "Instance.h"
|
2016-10-26 23:37:00 -07:00
|
|
|
#include "Log.h"
|
2016-11-18 08:49:20 -08:00
|
|
|
#include "Notifier.h"
|
2017-08-25 17:48:06 -07:00
|
|
|
#include "c_util.h"
|
|
|
|
|
#include "cscore_cpp.h"
|
2016-10-26 23:37:00 -07:00
|
|
|
|
|
|
|
|
using namespace cs;
|
|
|
|
|
|
2018-10-31 20:22:58 -07:00
|
|
|
CvSinkImpl::CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
|
|
|
|
Notifier& notifier, Telemetry& telemetry)
|
|
|
|
|
: SinkImpl{name, logger, notifier, telemetry} {
|
2016-10-26 23:37:00 -07:00
|
|
|
m_active = true;
|
|
|
|
|
// m_thread = std::thread(&CvSinkImpl::ThreadMain, this);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 20:22:58 -07:00
|
|
|
CvSinkImpl::CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
|
|
|
|
Notifier& notifier, Telemetry& telemetry,
|
2016-10-26 23:37:00 -07:00
|
|
|
std::function<void(uint64_t time)> processFrame)
|
2018-10-31 20:22:58 -07:00
|
|
|
: SinkImpl{name, logger, notifier, telemetry} {}
|
2016-10-26 23:37:00 -07:00
|
|
|
|
|
|
|
|
CvSinkImpl::~CvSinkImpl() { Stop(); }
|
|
|
|
|
|
|
|
|
|
void CvSinkImpl::Stop() {
|
|
|
|
|
m_active = false;
|
|
|
|
|
|
|
|
|
|
// wake up any waiters by forcing an empty frame to be sent
|
2017-08-25 17:48:06 -07:00
|
|
|
if (auto source = GetSource()) source->Wakeup();
|
2016-10-26 23:37:00 -07:00
|
|
|
|
|
|
|
|
// join thread
|
|
|
|
|
if (m_thread.joinable()) m_thread.join();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image) {
|
|
|
|
|
SetEnabled(true);
|
2016-12-20 20:48:31 -08:00
|
|
|
|
2016-10-26 23:37:00 -07:00
|
|
|
auto source = GetSource();
|
2016-12-02 23:42:02 -08:00
|
|
|
if (!source) {
|
|
|
|
|
// Source disconnected; sleep for one second
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2016-12-20 20:48:31 -08:00
|
|
|
|
2016-10-26 23:37:00 -07:00
|
|
|
auto frame = source->GetNextFrame(); // blocks
|
2016-12-02 23:42:02 -08:00
|
|
|
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
|
|
|
|
|
}
|
2016-12-20 20:48:31 -08:00
|
|
|
|
|
|
|
|
if (!frame.GetCv(image)) {
|
|
|
|
|
// Shouldn't happen, but just in case...
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
|
|
|
return 0;
|
2016-11-10 00:00:20 -08:00
|
|
|
}
|
2016-12-20 20:48:31 -08:00
|
|
|
|
|
|
|
|
return frame.GetTime();
|
2016-10-26 23:37:00 -07:00
|
|
|
}
|
|
|
|
|
|
2017-02-17 01:17:12 -08:00
|
|
|
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)) {
|
|
|
|
|
// Shouldn't happen, but just in case...
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return frame.GetTime();
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 23:37:00 -07:00
|
|
|
// 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;
|
|
|
|
|
}
|
2016-12-10 23:36:35 -08:00
|
|
|
SDEBUG4("waiting for frame");
|
2016-10-26 23:37:00 -07:00
|
|
|
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 {
|
|
|
|
|
|
2018-07-29 12:53:41 -07:00
|
|
|
CS_Sink CreateCvSink(const wpi::Twine& name, CS_Status* status) {
|
2018-10-31 20:22:58 -07:00
|
|
|
auto& inst = Instance::GetInstance();
|
|
|
|
|
auto sink = std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
|
|
|
|
|
inst.telemetry);
|
|
|
|
|
auto handle = inst.sinks.Allocate(CS_SINK_CV, sink);
|
|
|
|
|
inst.notifier.NotifySink(name, handle, CS_SINK_CREATED);
|
2016-11-18 08:49:20 -08:00
|
|
|
return handle;
|
2016-10-26 23:37:00 -07:00
|
|
|
}
|
|
|
|
|
|
2018-07-29 12:53:41 -07:00
|
|
|
CS_Sink CreateCvSinkCallback(const wpi::Twine& name,
|
2016-10-26 23:37:00 -07:00
|
|
|
std::function<void(uint64_t time)> processFrame,
|
|
|
|
|
CS_Status* status) {
|
2018-10-31 20:22:58 -07:00
|
|
|
auto& inst = Instance::GetInstance();
|
|
|
|
|
auto sink = std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
|
|
|
|
|
inst.telemetry, processFrame);
|
|
|
|
|
auto handle = inst.sinks.Allocate(CS_SINK_CV, sink);
|
|
|
|
|
inst.notifier.NotifySink(name, handle, CS_SINK_CREATED);
|
2016-11-18 08:49:20 -08:00
|
|
|
return handle;
|
2016-10-26 23:37:00 -07:00
|
|
|
}
|
|
|
|
|
|
2018-07-29 12:53:41 -07:00
|
|
|
void SetSinkDescription(CS_Sink sink, const wpi::Twine& description,
|
2016-10-26 23:37:00 -07:00
|
|
|
CS_Status* status) {
|
2018-10-31 20:22:58 -07:00
|
|
|
auto data = Instance::GetInstance().sinks.Get(sink);
|
2016-11-15 23:54:50 -08:00
|
|
|
if (!data || data->kind != CS_SINK_CV) {
|
2016-10-26 23:37:00 -07:00
|
|
|
*status = CS_INVALID_HANDLE;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
static_cast<CvSinkImpl&>(*data->sink).SetDescription(description);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
|
2018-10-31 20:22:58 -07:00
|
|
|
auto data = Instance::GetInstance().sinks.Get(sink);
|
2016-11-15 23:54:50 -08:00
|
|
|
if (!data || data->kind != CS_SINK_CV) {
|
2016-10-26 23:37:00 -07:00
|
|
|
*status = CS_INVALID_HANDLE;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image);
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-17 01:17:12 -08:00
|
|
|
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
|
|
|
|
|
CS_Status* status) {
|
2018-10-31 20:22:58 -07:00
|
|
|
auto data = Instance::GetInstance().sinks.Get(sink);
|
2017-02-17 01:17:12 -08:00
|
|
|
if (!data || data->kind != CS_SINK_CV) {
|
|
|
|
|
*status = CS_INVALID_HANDLE;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image, timeout);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 23:37:00 -07:00
|
|
|
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
|
2018-10-31 20:22:58 -07:00
|
|
|
auto data = Instance::GetInstance().sinks.Get(sink);
|
2016-11-15 23:54:50 -08:00
|
|
|
if (!data || data->kind != CS_SINK_CV) {
|
2016-10-26 23:37:00 -07:00
|
|
|
*status = CS_INVALID_HANDLE;
|
|
|
|
|
return std::string{};
|
|
|
|
|
}
|
|
|
|
|
return static_cast<CvSinkImpl&>(*data->sink).GetError();
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 23:33:19 -07:00
|
|
|
wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
|
2018-05-13 17:09:56 -07:00
|
|
|
CS_Status* status) {
|
2018-10-31 20:22:58 -07:00
|
|
|
auto data = Instance::GetInstance().sinks.Get(sink);
|
2016-11-15 23:54:50 -08:00
|
|
|
if (!data || data->kind != CS_SINK_CV) {
|
2016-10-26 23:37:00 -07:00
|
|
|
*status = CS_INVALID_HANDLE;
|
2018-04-29 23:33:19 -07:00
|
|
|
return wpi::StringRef{};
|
2016-10-26 23:37:00 -07:00
|
|
|
}
|
|
|
|
|
return static_cast<CvSinkImpl&>(*data->sink).GetError(buf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
|
2018-10-31 20:22:58 -07:00
|
|
|
auto data = Instance::GetInstance().sinks.Get(sink);
|
2016-11-15 23:54:50 -08:00
|
|
|
if (!data || data->kind != CS_SINK_CV) {
|
2016-10-26 23:37:00 -07:00
|
|
|
*status = CS_INVALID_HANDLE;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
static_cast<CvSinkImpl&>(*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);
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-17 01:17:12 -08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-30 22:00:44 -08:00
|
|
|
uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status) {
|
2017-08-25 17:48:06 -07:00
|
|
|
return cs::GrabSinkFrame(sink, *image, status);
|
2016-11-30 22:00:44 -08:00
|
|
|
}
|
|
|
|
|
|
2017-02-17 01:17:12 -08:00
|
|
|
uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
|
|
|
|
|
double timeout, CS_Status* status) {
|
|
|
|
|
return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 23:37:00 -07:00
|
|
|
char* CS_GetSinkError(CS_Sink sink, CS_Status* status) {
|
2018-04-29 23:33:19 -07:00
|
|
|
wpi::SmallString<128> buf;
|
2016-10-26 23:37:00 -07:00
|
|
|
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"
|