mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-20 00:51:42 +00:00
Prepare cscore for merge into allwpilib.
This commit is contained in:
246
cscore/src/main/native/cpp/CvSinkImpl.cpp
Normal file
246
cscore/src/main/native/cpp/CvSinkImpl.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "CvSinkImpl.h"
|
||||
|
||||
#include <llvm/SmallString.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "c_util.h"
|
||||
#include "cscore_cpp.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<void(uint64_t time)> 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) {
|
||||
// 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)) {
|
||||
// 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)) {
|
||||
// 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(llvm::StringRef name, CS_Status* status) {
|
||||
auto sink = std::make_shared<CvSinkImpl>(name);
|
||||
auto handle = Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
|
||||
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
|
||||
return handle;
|
||||
}
|
||||
|
||||
CS_Sink CreateCvSinkCallback(llvm::StringRef name,
|
||||
std::function<void(uint64_t time)> processFrame,
|
||||
CS_Status* status) {
|
||||
auto sink = std::make_shared<CvSinkImpl>(name, processFrame);
|
||||
auto handle = Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
|
||||
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void SetSinkDescription(CS_Sink sink, llvm::StringRef description,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSinkImpl&>(*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->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image);
|
||||
}
|
||||
|
||||
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image, timeout);
|
||||
}
|
||||
|
||||
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return static_cast<CvSinkImpl&>(*data->sink).GetError();
|
||||
}
|
||||
|
||||
llvm::StringRef GetSinkError(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
return static_cast<CvSinkImpl&>(*data->sink).GetError(buf);
|
||||
}
|
||||
|
||||
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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"
|
||||
52
cscore/src/main/native/cpp/CvSinkImpl.h
Normal file
52
cscore/src/main/native/cpp/CvSinkImpl.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_CVSINKIMPL_H_
|
||||
#define CSCORE_CVSINKIMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/SmallVector.h>
|
||||
#include <llvm/StringRef.h>
|
||||
#include <llvm/raw_ostream.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:
|
||||
explicit CvSinkImpl(llvm::StringRef name);
|
||||
CvSinkImpl(llvm::StringRef name,
|
||||
std::function<void(uint64_t time)> 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<void(uint64_t time)> m_processFrame;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_CVSINKIMPL_H_
|
||||
399
cscore/src/main/native/cpp/CvSourceImpl.cpp
Normal file
399
cscore/src/main/native/cpp/CvSourceImpl.cpp
Normal file
@@ -0,0 +1,399 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "CvSourceImpl.h"
|
||||
|
||||
#include <llvm/STLExtras.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
#include <support/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "c_util.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
CvSourceImpl::CvSourceImpl(llvm::StringRef name, const VideoMode& mode)
|
||||
: SourceImpl{name} {
|
||||
m_mode = mode;
|
||||
m_videoModes.push_back(m_mode);
|
||||
}
|
||||
|
||||
CvSourceImpl::~CvSourceImpl() {}
|
||||
|
||||
void CvSourceImpl::Start() {}
|
||||
|
||||
std::unique_ptr<PropertyImpl> CvSourceImpl::CreateEmptyProperty(
|
||||
llvm::StringRef name) const {
|
||||
return llvm::make_unique<PropertyData>(name);
|
||||
}
|
||||
|
||||
bool CvSourceImpl::CacheProperties(CS_Status* status) const {
|
||||
// Doesn't need to do anything.
|
||||
m_properties_cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetProperty(int property, int value, CS_Status* status) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = static_cast<PropertyData*>(GetProperty(property));
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
// Guess it's integer if we've set before get
|
||||
if (prop->propKind == CS_PROP_NONE) prop->propKind = CS_PROP_INTEGER;
|
||||
|
||||
if ((prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) ==
|
||||
0) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePropertyValue(property, false, value, llvm::StringRef{});
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetStringProperty(int property, llvm::StringRef value,
|
||||
CS_Status* status) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = static_cast<PropertyData*>(GetProperty(property));
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
// Guess it's string if we've set before get
|
||||
if (prop->propKind == CS_PROP_NONE) prop->propKind = CS_PROP_STRING;
|
||||
|
||||
if (prop->propKind != CS_PROP_STRING) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePropertyValue(property, true, 0, value);
|
||||
}
|
||||
|
||||
// These are only valid for cameras (should never get called)
|
||||
|
||||
void CvSourceImpl::SetBrightness(int brightness, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
int CvSourceImpl::GetBrightness(CS_Status* status) const {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetWhiteBalanceAuto(CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetWhiteBalanceHoldCurrent(CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetWhiteBalanceManual(int value, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetExposureAuto(CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetExposureHoldCurrent(CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetExposureManual(int value, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_mode = mode;
|
||||
m_videoModes[0] = mode;
|
||||
}
|
||||
Notifier::GetInstance().NotifySourceVideoMode(*this, mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CvSourceImpl::NumSinksChanged() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
void CvSourceImpl::NumSinksEnabledChanged() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
void CvSourceImpl::PutFrame(cv::Mat& image) {
|
||||
// We only support 8-bit images; convert if necessary.
|
||||
cv::Mat finalImage;
|
||||
if (image.depth() == CV_8U)
|
||||
finalImage = image;
|
||||
else
|
||||
image.convertTo(finalImage, CV_8U);
|
||||
|
||||
std::unique_ptr<Image> 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: " << image.channels()
|
||||
<< "-channel images not supported");
|
||||
return;
|
||||
}
|
||||
SourceImpl::PutFrame(std::move(dest), wpi::Now());
|
||||
}
|
||||
|
||||
void CvSourceImpl::NotifyError(llvm::StringRef msg) {
|
||||
PutError(msg, wpi::Now());
|
||||
}
|
||||
|
||||
int CvSourceImpl::CreateProperty(llvm::StringRef name, CS_PropertyKind kind,
|
||||
int minimum, int maximum, int step,
|
||||
int defaultValue, int value) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
int& ndx = m_properties[name];
|
||||
if (ndx == 0) {
|
||||
// create a new index
|
||||
ndx = m_propertyData.size() + 1;
|
||||
m_propertyData.emplace_back(llvm::make_unique<PropertyData>(
|
||||
name, kind, minimum, maximum, step, defaultValue, value));
|
||||
} else {
|
||||
// update all but value
|
||||
auto prop = GetProperty(ndx);
|
||||
prop->propKind = kind;
|
||||
prop->minimum = minimum;
|
||||
prop->maximum = maximum;
|
||||
prop->step = step;
|
||||
prop->defaultValue = defaultValue;
|
||||
value = prop->value;
|
||||
}
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CREATED, name, ndx, kind, value,
|
||||
llvm::StringRef{});
|
||||
return ndx;
|
||||
}
|
||||
|
||||
int CvSourceImpl::CreateProperty(
|
||||
llvm::StringRef name, CS_PropertyKind kind, int minimum, int maximum,
|
||||
int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange) {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetEnumPropertyChoices(int property,
|
||||
llvm::ArrayRef<std::string> choices,
|
||||
CS_Status* status) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
if (prop->propKind != CS_PROP_ENUM) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return;
|
||||
}
|
||||
prop->enumChoices = choices;
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, prop->name, property,
|
||||
CS_PROP_ENUM, prop->value, llvm::StringRef{});
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Source CreateCvSource(llvm::StringRef name, const VideoMode& mode,
|
||||
CS_Status* status) {
|
||||
auto source = std::make_shared<CvSourceImpl>(name, mode);
|
||||
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_CV, source);
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
|
||||
// Generate initial events here so they come after the source created event
|
||||
source->Start(); // causes a property event
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_CONNECTED);
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_VIDEOMODES_UPDATED);
|
||||
notifier.NotifySourceVideoMode(*source, mode);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSourceImpl&>(*data->source).PutFrame(image);
|
||||
}
|
||||
|
||||
void NotifySourceError(CS_Source source, llvm::StringRef msg,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSourceImpl&>(*data->source).NotifyError(msg);
|
||||
}
|
||||
|
||||
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSourceImpl&>(*data->source).SetConnected(connected);
|
||||
}
|
||||
|
||||
void SetSourceDescription(CS_Source source, llvm::StringRef description,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSourceImpl&>(*data->source).SetDescription(description);
|
||||
}
|
||||
|
||||
CS_Property CreateSourceProperty(CS_Source source, llvm::StringRef name,
|
||||
CS_PropertyKind kind, int minimum, int maximum,
|
||||
int step, int defaultValue, int value,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return -1;
|
||||
}
|
||||
int property = static_cast<CvSourceImpl&>(*data->source)
|
||||
.CreateProperty(name, kind, minimum, maximum, step,
|
||||
defaultValue, value);
|
||||
return Handle{source, property, Handle::kProperty};
|
||||
}
|
||||
|
||||
CS_Property CreateSourcePropertyCallback(
|
||||
CS_Source source, llvm::StringRef name, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return -1;
|
||||
}
|
||||
int property = static_cast<CvSourceImpl&>(*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,
|
||||
llvm::ArrayRef<std::string> choices,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*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 = Sources::GetInstance().Get(Handle{i, Handle::kSource});
|
||||
if (!data2 || data->source.get() != data2->source.get()) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
int propertyIndex = handle.GetIndex();
|
||||
static_cast<CvSourceImpl&>(*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<const cs::VideoMode&>(*mode),
|
||||
status);
|
||||
}
|
||||
|
||||
void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
|
||||
CS_Status* status) {
|
||||
auto mat = cv::cvarrToMat(image);
|
||||
return cs::PutSourceFrame(source, mat, status);
|
||||
}
|
||||
|
||||
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) {
|
||||
llvm::SmallVector<std::string, 8> 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"
|
||||
89
cscore/src/main/native/cpp/CvSourceImpl.h
Normal file
89
cscore/src/main/native/cpp/CvSourceImpl.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_CVSOURCEIMPL_H_
|
||||
#define CSCORE_CVSOURCEIMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class CvSourceImpl : public SourceImpl {
|
||||
public:
|
||||
CvSourceImpl(llvm::StringRef name, const VideoMode& mode);
|
||||
~CvSourceImpl() override;
|
||||
|
||||
void Start();
|
||||
|
||||
// Property functions
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, llvm::StringRef value,
|
||||
CS_Status* status) override;
|
||||
|
||||
// Standard common camera properties
|
||||
void SetBrightness(int brightness, CS_Status* status) override;
|
||||
int GetBrightness(CS_Status* status) const override;
|
||||
void SetWhiteBalanceAuto(CS_Status* status) override;
|
||||
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
|
||||
void SetWhiteBalanceManual(int value, CS_Status* status) override;
|
||||
void SetExposureAuto(CS_Status* status) override;
|
||||
void SetExposureHoldCurrent(CS_Status* status) override;
|
||||
void SetExposureManual(int value, CS_Status* status) override;
|
||||
|
||||
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
|
||||
|
||||
void NumSinksChanged() override;
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
// OpenCV-specific functions
|
||||
void PutFrame(cv::Mat& image);
|
||||
void NotifyError(llvm::StringRef msg);
|
||||
int CreateProperty(llvm::StringRef name, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value);
|
||||
int CreateProperty(llvm::StringRef name, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange);
|
||||
void SetEnumPropertyChoices(int property, llvm::ArrayRef<std::string> choices,
|
||||
CS_Status* status);
|
||||
|
||||
// Property data
|
||||
class PropertyData : public PropertyImpl {
|
||||
public:
|
||||
PropertyData() = default;
|
||||
explicit PropertyData(llvm::StringRef name_) : PropertyImpl{name_} {}
|
||||
PropertyData(llvm::StringRef name_, CS_PropertyKind kind_, int minimum_,
|
||||
int maximum_, int step_, int defaultValue_, int value_)
|
||||
: PropertyImpl{name_, kind_, step_, defaultValue_, value_} {
|
||||
hasMinimum = true;
|
||||
minimum = minimum_;
|
||||
hasMaximum = true;
|
||||
maximum = maximum_;
|
||||
}
|
||||
~PropertyData() override = default;
|
||||
|
||||
std::function<void(CS_Property property)> onChange;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
|
||||
llvm::StringRef name) const override;
|
||||
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
|
||||
private:
|
||||
std::atomic_bool m_connected{true};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_CVSOURCEIMPL_H_
|
||||
480
cscore/src/main/native/cpp/Frame.cpp
Normal file
480
cscore/src/main/native/cpp/Frame.cpp
Normal file
@@ -0,0 +1,480 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Frame.h"
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
|
||||
#include "Log.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
Frame::Frame(SourceImpl& source, llvm::StringRef error, Time time)
|
||||
: m_impl{source.AllocFrameImpl().release()} {
|
||||
m_impl->refcount = 1;
|
||||
m_impl->error = error;
|
||||
m_impl->time = time;
|
||||
}
|
||||
|
||||
Frame::Frame(SourceImpl& source, std::unique_ptr<Image> image, Time time)
|
||||
: m_impl{source.AllocFrameImpl().release()} {
|
||||
m_impl->refcount = 1;
|
||||
m_impl->error.resize(0);
|
||||
m_impl->time = time;
|
||||
m_impl->images.push_back(image.release());
|
||||
}
|
||||
|
||||
Image* Frame::GetNearestImage(int width, int height) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
Image* found = nullptr;
|
||||
|
||||
// Ideally we want the smallest image at least width/height in size
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->IsLarger(width, height) && (!found || (i->IsSmaller(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// Find the largest image (will be less than width/height)
|
||||
for (auto i : m_impl->images) {
|
||||
if (!found || (i->IsLarger(*found))) found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// Shouldn't reach this, but just in case...
|
||||
return m_impl->images.empty() ? nullptr : m_impl->images[0];
|
||||
}
|
||||
|
||||
Image* Frame::GetNearestImage(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
Image* found = nullptr;
|
||||
|
||||
// We want the smallest image at least width/height (or the next largest),
|
||||
// but the primary search order is in order of conversion cost.
|
||||
// If we don't find exactly what we want, we prefer non-JPEG source images
|
||||
// (because JPEG source images require a decompression step).
|
||||
// While the searching takes a little time, it pales in comparison to the
|
||||
// image processing to come, so it's worth spending a little extra time
|
||||
// looking for the most efficient conversion.
|
||||
|
||||
// 1) Same width, height, and pixelFormat (e.g. exactly what we want)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, pixelFormat)) return i;
|
||||
}
|
||||
|
||||
// 2) Same width, height, different (but non-JPEG) pixelFormat (color conv)
|
||||
// 2a) If we want JPEG output, prefer BGR over other pixel formats
|
||||
if (pixelFormat == VideoMode::kMJPEG) {
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, VideoMode::kBGR)) return i;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height) && i->pixelFormat != VideoMode::kMJPEG) return i;
|
||||
}
|
||||
|
||||
// 3) Different width, height, same pixelFormat (only if non-JPEG) (resample)
|
||||
if (pixelFormat != VideoMode::kMJPEG) {
|
||||
// 3a) Smallest image at least width/height in size
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->IsLarger(width, height) && i->pixelFormat == pixelFormat &&
|
||||
(!found || (i->IsSmaller(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 3b) Largest image (less than width/height)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->pixelFormat == pixelFormat && (!found || (i->IsLarger(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
}
|
||||
|
||||
// 4) Different width, height, different (but non-JPEG) pixelFormat
|
||||
// (color conversion + resample)
|
||||
// 4a) Smallest image at least width/height in size
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->IsLarger(width, height) && i->pixelFormat != VideoMode::kMJPEG &&
|
||||
(!found || (i->IsSmaller(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 4b) Largest image (less than width/height)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->pixelFormat != VideoMode::kMJPEG &&
|
||||
(!found || (i->IsLarger(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 5) Same width, height, JPEG pixelFormat (decompression)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, VideoMode::kMJPEG)) return i;
|
||||
}
|
||||
|
||||
// 6) Different width, height, JPEG pixelFormat (decompression)
|
||||
// 6a) Smallest image at least width/height in size
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->IsLarger(width, height) && i->pixelFormat == VideoMode::kMJPEG &&
|
||||
(!found || (i->IsSmaller(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 6b) Largest image (less than width/height)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->pixelFormat != VideoMode::kMJPEG &&
|
||||
(!found || (i->IsLarger(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// Shouldn't reach this, but just in case...
|
||||
return m_impl->images.empty() ? nullptr : m_impl->images[0];
|
||||
}
|
||||
|
||||
Image* Frame::Convert(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
int jpegQuality) {
|
||||
if (!image || image->pixelFormat == pixelFormat) return image;
|
||||
Image* cur = image;
|
||||
|
||||
// If the source image is a JPEG, we need to decode it before we can do
|
||||
// anything else with it. Note that if the destination format is JPEG, we
|
||||
// still need to do this (unless it was already a JPEG, in which case we
|
||||
// would have returned above).
|
||||
if (cur->pixelFormat == VideoMode::kMJPEG) {
|
||||
cur = ConvertMJPEGToBGR(cur);
|
||||
if (pixelFormat == VideoMode::kBGR) return cur;
|
||||
}
|
||||
|
||||
// Color convert; if ultimate destination is JPEG, we need to convert to BGR
|
||||
switch (pixelFormat) {
|
||||
case VideoMode::kRGB565:
|
||||
// If source is YUYV or Gray, need to convert to BGR first
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
||||
cur = newImage;
|
||||
else
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kGray) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
||||
cur = newImage;
|
||||
else
|
||||
cur = ConvertGrayToBGR(cur);
|
||||
}
|
||||
return ConvertBGRToRGB565(cur);
|
||||
case VideoMode::kGray:
|
||||
// If source is YUYV or RGB565, need to convert to BGR first
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
||||
cur = newImage;
|
||||
else
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kRGB565) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
||||
cur = newImage;
|
||||
else
|
||||
cur = ConvertRGB565ToBGR(cur);
|
||||
}
|
||||
return ConvertBGRToGray(cur);
|
||||
case VideoMode::kBGR:
|
||||
case VideoMode::kMJPEG:
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kRGB565) {
|
||||
cur = ConvertRGB565ToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kGray) {
|
||||
if (pixelFormat == VideoMode::kBGR)
|
||||
return ConvertGrayToBGR(cur);
|
||||
else
|
||||
return ConvertGrayToMJPEG(cur, jpegQuality);
|
||||
}
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
default:
|
||||
return nullptr; // Unsupported
|
||||
}
|
||||
|
||||
// Compress if destination is JPEG
|
||||
if (pixelFormat == VideoMode::kMJPEG)
|
||||
cur = ConvertBGRToMJPEG(cur, jpegQuality);
|
||||
|
||||
return cur;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertMJPEGToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kMJPEG) return nullptr;
|
||||
|
||||
// Allocate an BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Decode
|
||||
cv::Mat newMat = newImage->AsMat();
|
||||
cv::imdecode(image->AsInputArray(), cv::IMREAD_COLOR, &newMat);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertMJPEGToGray(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kMJPEG) return nullptr;
|
||||
|
||||
// Allocate an grayscale image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
||||
image->width * image->height);
|
||||
|
||||
// Decode
|
||||
cv::Mat newMat = newImage->AsMat();
|
||||
cv::imdecode(image->AsInputArray(), cv::IMREAD_GRAYSCALE, &newMat);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertYUYVToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kYUYV) return nullptr;
|
||||
|
||||
// Allocate a BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2BGR_YUYV);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertBGRToRGB565(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
||||
|
||||
// Allocate a RGB565 image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kRGB565, image->width, image->height,
|
||||
image->width * image->height * 2);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_RGB2BGR565);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertRGB565ToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kRGB565) return nullptr;
|
||||
|
||||
// Allocate a BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR5652RGB);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertBGRToGray(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
||||
|
||||
// Allocate a Grayscale image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
||||
image->width * image->height);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR2GRAY);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertGrayToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
||||
|
||||
// Allocate a BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_GRAY2BGR);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertBGRToMJPEG(Image* image, int quality) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
|
||||
// Allocate a JPEG image. We don't actually know what the resulting size
|
||||
// will be; while the destination will automatically grow, doing so will
|
||||
// cause an extra malloc, so we don't want to be too conservative here.
|
||||
// Per Wikipedia, Q=100 on a sample image results in 8.25 bits per pixel,
|
||||
// this is a little bit more conservative in assuming 50% space savings over
|
||||
// the equivalent BGR image.
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kMJPEG, image->width, image->height,
|
||||
image->width * image->height * 1.5);
|
||||
|
||||
// Compress
|
||||
if (m_impl->compressionParams.empty()) {
|
||||
m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
|
||||
m_impl->compressionParams.push_back(quality);
|
||||
} else {
|
||||
m_impl->compressionParams[1] = quality;
|
||||
}
|
||||
cv::imencode(".jpg", image->AsMat(), newImage->vec(),
|
||||
m_impl->compressionParams);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
m_impl->images.push_back(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
|
||||
if (!image || image->pixelFormat != VideoMode::kGray) return nullptr;
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
|
||||
// Allocate a JPEG image. We don't actually know what the resulting size
|
||||
// will be; while the destination will automatically grow, doing so will
|
||||
// cause an extra malloc, so we don't want to be too conservative here.
|
||||
// Per Wikipedia, Q=100 on a sample image results in 8.25 bits per pixel,
|
||||
// this is a little bit more conservative in assuming 25% space savings over
|
||||
// the equivalent grayscale image.
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kMJPEG, image->width, image->height,
|
||||
image->width * image->height * 0.75);
|
||||
|
||||
// Compress
|
||||
if (m_impl->compressionParams.empty()) {
|
||||
m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
|
||||
m_impl->compressionParams.push_back(quality);
|
||||
} else {
|
||||
m_impl->compressionParams[1] = quality;
|
||||
}
|
||||
cv::imencode(".jpg", image->AsMat(), newImage->vec(),
|
||||
m_impl->compressionParams);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
m_impl->images.push_back(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::GetImage(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat, int jpegQuality) {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
Image* cur = GetNearestImage(width, height, pixelFormat);
|
||||
if (!cur || cur->Is(width, height, pixelFormat)) return cur;
|
||||
|
||||
DEBUG4("converting image from "
|
||||
<< cur->width << "x" << cur->height << " type " << cur->pixelFormat
|
||||
<< " to " << width << "x" << height << " type " << pixelFormat);
|
||||
|
||||
// If the source image is a JPEG, we need to decode it before we can do
|
||||
// anything else with it. Note that if the destination format is JPEG, we
|
||||
// still need to do this (unless the width/height were the same, in which
|
||||
// case we already returned the existing JPEG above).
|
||||
if (cur->pixelFormat == VideoMode::kMJPEG) cur = ConvertMJPEGToBGR(cur);
|
||||
|
||||
// Resize
|
||||
if (!cur->Is(width, height)) {
|
||||
// Allocate an image.
|
||||
auto newImage = m_impl->source.AllocImage(
|
||||
cur->pixelFormat, width, height,
|
||||
width * height * (cur->size() / (cur->width * cur->height)));
|
||||
|
||||
// Resize
|
||||
cv::Mat newMat = newImage->AsMat();
|
||||
cv::resize(cur->AsMat(), newMat, newMat.size(), 0, 0);
|
||||
|
||||
// Save the result
|
||||
cur = newImage.release();
|
||||
m_impl->images.push_back(cur);
|
||||
}
|
||||
|
||||
// Convert to output format
|
||||
return Convert(cur, pixelFormat, jpegQuality);
|
||||
}
|
||||
|
||||
bool Frame::GetCv(cv::Mat& image, int width, int height) {
|
||||
Image* rawImage = GetImage(width, height, VideoMode::kBGR);
|
||||
if (!rawImage) return false;
|
||||
rawImage->AsMat().copyTo(image);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Frame::ReleaseFrame() {
|
||||
for (auto image : m_impl->images)
|
||||
m_impl->source.ReleaseImage(std::unique_ptr<Image>(image));
|
||||
m_impl->images.clear();
|
||||
m_impl->source.ReleaseFrameImpl(std::unique_ptr<Impl>(m_impl));
|
||||
m_impl = nullptr;
|
||||
}
|
||||
162
cscore/src/main/native/cpp/Frame.h
Normal file
162
cscore/src/main/native/cpp/Frame.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_FRAME_H_
|
||||
#define CSCORE_FRAME_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/SmallVector.h>
|
||||
#include <support/mutex.h>
|
||||
|
||||
#include "Image.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SourceImpl;
|
||||
|
||||
class Frame {
|
||||
friend class SourceImpl;
|
||||
|
||||
public:
|
||||
typedef uint64_t Time;
|
||||
|
||||
private:
|
||||
struct Impl {
|
||||
explicit Impl(SourceImpl& source_) : source(source_) {}
|
||||
|
||||
wpi::recursive_mutex mutex;
|
||||
std::atomic_int refcount{0};
|
||||
Time time{0};
|
||||
SourceImpl& source;
|
||||
std::string error;
|
||||
llvm::SmallVector<Image*, 4> images;
|
||||
std::vector<int> compressionParams;
|
||||
};
|
||||
|
||||
public:
|
||||
Frame() noexcept : m_impl{nullptr} {}
|
||||
|
||||
Frame(SourceImpl& source, llvm::StringRef error, Time time);
|
||||
|
||||
Frame(SourceImpl& source, std::unique_ptr<Image> image, Time time);
|
||||
|
||||
Frame(const Frame& frame) noexcept : m_impl{frame.m_impl} {
|
||||
if (m_impl) ++m_impl->refcount;
|
||||
}
|
||||
|
||||
Frame(Frame&& other) noexcept : Frame() { swap(*this, other); }
|
||||
|
||||
~Frame() { DecRef(); }
|
||||
|
||||
Frame& operator=(Frame other) noexcept {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return m_impl && m_impl->error.empty(); }
|
||||
|
||||
friend void swap(Frame& first, Frame& second) noexcept {
|
||||
using std::swap;
|
||||
swap(first.m_impl, second.m_impl);
|
||||
}
|
||||
|
||||
Time GetTime() const { return m_impl ? m_impl->time : 0; }
|
||||
|
||||
llvm::StringRef GetError() const {
|
||||
if (!m_impl) return llvm::StringRef{};
|
||||
return m_impl->error;
|
||||
}
|
||||
|
||||
int GetOriginalWidth() const {
|
||||
if (!m_impl) return 0;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (m_impl->images.empty()) return 0;
|
||||
return m_impl->images[0]->width;
|
||||
}
|
||||
|
||||
int GetOriginalHeight() const {
|
||||
if (!m_impl) return 0;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (m_impl->images.empty()) return 0;
|
||||
return m_impl->images[0]->height;
|
||||
}
|
||||
|
||||
int GetOriginalPixelFormat() const {
|
||||
if (!m_impl) return 0;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (m_impl->images.empty()) return 0;
|
||||
return m_impl->images[0]->pixelFormat;
|
||||
}
|
||||
|
||||
Image* GetExistingImage(size_t i = 0) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (i >= m_impl->images.size()) return nullptr;
|
||||
return m_impl->images[i];
|
||||
}
|
||||
|
||||
Image* GetExistingImage(int width, int height) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height)) return i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Image* GetExistingImage(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, pixelFormat)) return i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Image* GetNearestImage(int width, int height) const;
|
||||
Image* GetNearestImage(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat) const;
|
||||
|
||||
Image* Convert(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
int jpegQuality = 80);
|
||||
Image* ConvertMJPEGToBGR(Image* image);
|
||||
Image* ConvertMJPEGToGray(Image* image);
|
||||
Image* ConvertYUYVToBGR(Image* image);
|
||||
Image* ConvertBGRToRGB565(Image* image);
|
||||
Image* ConvertRGB565ToBGR(Image* image);
|
||||
Image* ConvertBGRToGray(Image* image);
|
||||
Image* ConvertGrayToBGR(Image* image);
|
||||
Image* ConvertBGRToMJPEG(Image* image, int quality);
|
||||
Image* ConvertGrayToMJPEG(Image* image, int quality);
|
||||
|
||||
Image* GetImage(int width, int height, VideoMode::PixelFormat pixelFormat,
|
||||
int jpegQuality = 80);
|
||||
|
||||
bool GetCv(cv::Mat& image) {
|
||||
return GetCv(image, GetOriginalWidth(), GetOriginalHeight());
|
||||
}
|
||||
bool GetCv(cv::Mat& image, int width, int height);
|
||||
|
||||
private:
|
||||
void DecRef() {
|
||||
if (m_impl && --(m_impl->refcount) == 0) ReleaseFrame();
|
||||
}
|
||||
void ReleaseFrame();
|
||||
|
||||
Impl* m_impl;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_FRAME_H_
|
||||
124
cscore/src/main/native/cpp/Handle.h
Normal file
124
cscore/src/main/native/cpp/Handle.h
Normal file
@@ -0,0 +1,124 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_HANDLE_H_
|
||||
#define CSCORE_HANDLE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <llvm/StringRef.h>
|
||||
|
||||
#include "UnlimitedHandleResource.h"
|
||||
#include "cscore_c.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SinkImpl;
|
||||
class SourceImpl;
|
||||
|
||||
// Handle data layout:
|
||||
// Bits 0-15: Handle index
|
||||
// Bits 16-23: Parent index (property only)
|
||||
// Bits 24-30: Type
|
||||
|
||||
class Handle {
|
||||
public:
|
||||
enum Type { kUndefined = 0, kProperty = 0x40, kSource, kSink, kListener };
|
||||
enum { kIndexMax = 0xffff };
|
||||
|
||||
Handle(CS_Handle handle) : m_handle(handle) {} // NOLINT
|
||||
operator CS_Handle() const { return m_handle; }
|
||||
|
||||
Handle(int index, Type type) {
|
||||
if (index < 0) {
|
||||
m_handle = 0;
|
||||
return;
|
||||
}
|
||||
m_handle = ((static_cast<int>(type) & 0x7f) << 24) | (index & 0xffff);
|
||||
}
|
||||
Handle(int index, int property, Type type) {
|
||||
if (index < 0 || property < 0) {
|
||||
m_handle = 0;
|
||||
return;
|
||||
}
|
||||
m_handle = ((static_cast<int>(type) & 0x7f) << 24) |
|
||||
((index & 0xff) << 16) | (property & 0xffff);
|
||||
}
|
||||
|
||||
int GetIndex() const { return static_cast<int>(m_handle) & 0xffff; }
|
||||
Type GetType() const {
|
||||
return static_cast<Type>((static_cast<int>(m_handle) >> 24) & 0xff);
|
||||
}
|
||||
bool IsType(Type type) const { return type == GetType(); }
|
||||
int GetTypedIndex(Type type) const { return IsType(type) ? GetIndex() : -1; }
|
||||
int GetParentIndex() const {
|
||||
return IsType(Handle::kProperty) ? (static_cast<int>(m_handle) >> 16) & 0xff
|
||||
: -1;
|
||||
}
|
||||
|
||||
private:
|
||||
CS_Handle m_handle;
|
||||
};
|
||||
|
||||
struct SourceData {
|
||||
SourceData(CS_SourceKind kind_, std::shared_ptr<SourceImpl> source_)
|
||||
: kind{kind_}, refCount{0}, source{source_} {}
|
||||
|
||||
CS_SourceKind kind;
|
||||
std::atomic_int refCount;
|
||||
std::shared_ptr<SourceImpl> source;
|
||||
};
|
||||
|
||||
class Sources
|
||||
: public UnlimitedHandleResource<Handle, SourceData, Handle::kSource> {
|
||||
public:
|
||||
static Sources& GetInstance() {
|
||||
static Sources instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::pair<CS_Source, std::shared_ptr<SourceData>> Find(
|
||||
const SourceImpl& source) {
|
||||
return FindIf(
|
||||
[&](const SourceData& data) { return data.source.get() == &source; });
|
||||
}
|
||||
|
||||
private:
|
||||
Sources() = default;
|
||||
};
|
||||
|
||||
struct SinkData {
|
||||
explicit SinkData(CS_SinkKind kind_, std::shared_ptr<SinkImpl> sink_)
|
||||
: kind{kind_}, refCount{0}, sourceHandle{0}, sink{sink_} {}
|
||||
|
||||
CS_SinkKind kind;
|
||||
std::atomic_int refCount;
|
||||
std::atomic<CS_Source> sourceHandle;
|
||||
std::shared_ptr<SinkImpl> sink;
|
||||
};
|
||||
|
||||
class Sinks : public UnlimitedHandleResource<Handle, SinkData, Handle::kSink> {
|
||||
public:
|
||||
static Sinks& GetInstance() {
|
||||
static Sinks instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::pair<CS_Sink, std::shared_ptr<SinkData>> Find(const SinkImpl& sink) {
|
||||
return FindIf(
|
||||
[&](const SinkData& data) { return data.sink.get() == &sink; });
|
||||
}
|
||||
|
||||
private:
|
||||
Sinks() = default;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_HANDLE_H_
|
||||
589
cscore/src/main/native/cpp/HttpCameraImpl.cpp
Normal file
589
cscore/src/main/native/cpp/HttpCameraImpl.cpp
Normal file
@@ -0,0 +1,589 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "HttpCameraImpl.h"
|
||||
|
||||
#include <llvm/STLExtras.h>
|
||||
#include <support/timestamp.h>
|
||||
#include <tcpsockets/TCPConnector.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "JpegUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "Telemetry.h"
|
||||
#include "c_util.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
HttpCameraImpl::HttpCameraImpl(llvm::StringRef name, CS_HttpCameraKind kind)
|
||||
: SourceImpl{name}, m_kind{kind} {}
|
||||
|
||||
HttpCameraImpl::~HttpCameraImpl() {
|
||||
m_active = false;
|
||||
|
||||
// Close file if it's open
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_streamConn) m_streamConn->stream->close();
|
||||
if (m_settingsConn) m_settingsConn->stream->close();
|
||||
}
|
||||
|
||||
// force wakeup of camera thread in case it's waiting on cv
|
||||
m_sinkEnabledCond.notify_one();
|
||||
|
||||
// join camera thread
|
||||
if (m_streamThread.joinable()) m_streamThread.join();
|
||||
|
||||
// force wakeup of settings thread
|
||||
m_settingsCond.notify_one();
|
||||
|
||||
// join settings thread
|
||||
if (m_settingsThread.joinable()) m_settingsThread.join();
|
||||
}
|
||||
|
||||
void HttpCameraImpl::Start() {
|
||||
// Kick off the stream and settings threads
|
||||
m_streamThread = std::thread(&HttpCameraImpl::StreamThreadMain, this);
|
||||
m_settingsThread = std::thread(&HttpCameraImpl::SettingsThreadMain, this);
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static inline void DoFdSet(int fd, fd_set* set, int* nfds) {
|
||||
if (fd >= 0) {
|
||||
FD_SET(fd, set);
|
||||
if ((fd + 1) > *nfds) *nfds = fd + 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void HttpCameraImpl::StreamThreadMain() {
|
||||
while (m_active) {
|
||||
SetConnected(false);
|
||||
|
||||
// sleep between retries
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
|
||||
// disconnect if no one is listening
|
||||
if (m_numSinksEnabled == 0) {
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
if (m_streamConn) m_streamConn->stream->close();
|
||||
// Wait for a sink to enable
|
||||
m_sinkEnabledCond.wait(
|
||||
lock, [=] { return !m_active || m_numSinksEnabled != 0; });
|
||||
if (!m_active) return;
|
||||
}
|
||||
|
||||
// connect
|
||||
llvm::SmallString<64> boundary;
|
||||
wpi::HttpConnection* conn = DeviceStreamConnect(boundary);
|
||||
|
||||
if (!m_active) break;
|
||||
|
||||
// keep retrying
|
||||
if (!conn) continue;
|
||||
|
||||
// update connected since we're actually connected
|
||||
SetConnected(true);
|
||||
|
||||
// stream
|
||||
DeviceStream(conn->is, boundary);
|
||||
}
|
||||
|
||||
SDEBUG("Camera Thread exiting");
|
||||
SetConnected(false);
|
||||
}
|
||||
|
||||
wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
llvm::SmallVectorImpl<char>& boundary) {
|
||||
// Build the request
|
||||
wpi::HttpRequest req;
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_locations.empty()) {
|
||||
SERROR("locations array is empty!?");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return nullptr;
|
||||
}
|
||||
if (m_nextLocation >= m_locations.size()) m_nextLocation = 0;
|
||||
req = wpi::HttpRequest{m_locations[m_nextLocation++], m_streamSettings};
|
||||
m_streamSettingsUpdated = false;
|
||||
}
|
||||
|
||||
// Try to connect
|
||||
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
|
||||
Logger::GetInstance(), 1);
|
||||
|
||||
if (!m_active || !stream) return nullptr;
|
||||
|
||||
auto connPtr = llvm::make_unique<wpi::HttpConnection>(std::move(stream), 1);
|
||||
wpi::HttpConnection* conn = connPtr.get();
|
||||
|
||||
// update m_streamConn
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = std::move(connPtr);
|
||||
}
|
||||
|
||||
std::string warn;
|
||||
if (!conn->Handshake(req, &warn)) {
|
||||
SWARNING(GetName() << ": " << warn);
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Parse Content-Type header to get the boundary
|
||||
llvm::StringRef mediaType, contentType;
|
||||
std::tie(mediaType, contentType) = conn->contentType.str().split(';');
|
||||
mediaType = mediaType.trim();
|
||||
if (mediaType != "multipart/x-mixed-replace") {
|
||||
SWARNING("\"" << req.host << "\": unrecognized Content-Type \"" << mediaType
|
||||
<< "\"");
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// media parameters
|
||||
boundary.clear();
|
||||
while (!contentType.empty()) {
|
||||
llvm::StringRef keyvalue;
|
||||
std::tie(keyvalue, contentType) = contentType.split(';');
|
||||
contentType = contentType.ltrim();
|
||||
llvm::StringRef key, value;
|
||||
std::tie(key, value) = keyvalue.split('=');
|
||||
if (key.trim() == "boundary") {
|
||||
value = value.trim().trim('"'); // value may be quoted
|
||||
boundary.append(value.begin(), value.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (boundary.empty()) {
|
||||
SWARNING("\"" << req.host
|
||||
<< "\": empty multi-part boundary or no Content-Type");
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::DeviceStream(wpi::raw_istream& is,
|
||||
llvm::StringRef boundary) {
|
||||
// Stored here so we reuse it from frame to frame
|
||||
std::string imageBuf;
|
||||
|
||||
// keep track of number of bad images received; if we receive 3 bad images
|
||||
// in a row, we reconnect
|
||||
int numErrors = 0;
|
||||
|
||||
// streaming loop
|
||||
while (m_active && !is.has_error() && m_numSinksEnabled > 0 &&
|
||||
numErrors < 3 && !m_streamSettingsUpdated) {
|
||||
if (!FindMultipartBoundary(is, boundary, nullptr)) break;
|
||||
|
||||
// Read the next two characters after the boundary (normally \r\n)
|
||||
char eol[2];
|
||||
is.read(eol, 2);
|
||||
if (!m_active || is.has_error()) break;
|
||||
// End-of-stream is indicated with trailing --
|
||||
if (eol[0] == '-' && eol[1] == '-') break;
|
||||
|
||||
if (!DeviceStreamFrame(is, imageBuf))
|
||||
++numErrors;
|
||||
else
|
||||
numErrors = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
std::string& imageBuf) {
|
||||
// Read the headers
|
||||
llvm::SmallString<64> contentTypeBuf;
|
||||
llvm::SmallString<64> contentLengthBuf;
|
||||
if (!ParseHttpHeaders(is, &contentTypeBuf, &contentLengthBuf)) {
|
||||
SWARNING("disconnected during headers");
|
||||
PutError("disconnected during headers", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the content type (if present)
|
||||
if (!contentTypeBuf.str().empty() &&
|
||||
!contentTypeBuf.str().startswith("image/jpeg")) {
|
||||
llvm::SmallString<64> errBuf;
|
||||
llvm::raw_svector_ostream errMsg{errBuf};
|
||||
errMsg << "received unknown Content-Type \"" << contentTypeBuf << "\"";
|
||||
SWARNING(errMsg.str());
|
||||
PutError(errMsg.str(), wpi::Now());
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int contentLength = 0;
|
||||
if (contentLengthBuf.str().getAsInteger(10, contentLength)) {
|
||||
// Ugh, no Content-Length? Read the blocks of the JPEG file.
|
||||
int width, height;
|
||||
if (!ReadJpeg(is, imageBuf, &width, &height)) {
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
|
||||
wpi::Now());
|
||||
return true;
|
||||
}
|
||||
|
||||
// We know how big it is! Just get a frame of the right size and read
|
||||
// the data directly into it.
|
||||
auto image = AllocImage(VideoMode::PixelFormat::kMJPEG, 0, 0, contentLength);
|
||||
is.read(image->data(), contentLength);
|
||||
if (!m_active || is.has_error()) return false;
|
||||
int width, height;
|
||||
if (!GetJpegSize(image->str(), &width, &height)) {
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
PutFrame(std::move(image), wpi::Now());
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SettingsThreadMain() {
|
||||
for (;;) {
|
||||
wpi::HttpRequest req;
|
||||
{
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
m_settingsCond.wait(lock, [=] {
|
||||
return !m_active || (m_prefLocation != -1 && !m_settings.empty());
|
||||
});
|
||||
if (!m_active) break;
|
||||
|
||||
// Build the request
|
||||
req = wpi::HttpRequest{m_locations[m_prefLocation], m_settings};
|
||||
}
|
||||
|
||||
DeviceSendSettings(req);
|
||||
}
|
||||
|
||||
SDEBUG("Settings Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
|
||||
// Try to connect
|
||||
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
|
||||
Logger::GetInstance(), 1);
|
||||
|
||||
if (!m_active || !stream) return;
|
||||
|
||||
auto connPtr = llvm::make_unique<wpi::HttpConnection>(std::move(stream), 1);
|
||||
wpi::HttpConnection* conn = connPtr.get();
|
||||
|
||||
// update m_settingsConn
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_settingsConn = std::move(connPtr);
|
||||
}
|
||||
|
||||
// Just need a handshake as settings are sent via GET parameters
|
||||
std::string warn;
|
||||
if (!conn->Handshake(req, &warn)) SWARNING(GetName() << ": " << warn);
|
||||
|
||||
conn->stream->close();
|
||||
}
|
||||
|
||||
CS_HttpCameraKind HttpCameraImpl::GetKind() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::SetUrls(llvm::ArrayRef<std::string> urls,
|
||||
CS_Status* status) {
|
||||
std::vector<wpi::HttpLocation> locations;
|
||||
for (const auto& url : urls) {
|
||||
bool error = false;
|
||||
std::string errorMsg;
|
||||
locations.emplace_back(url, &error, &errorMsg);
|
||||
if (error) {
|
||||
SERROR(GetName() << ": " << errorMsg);
|
||||
*status = CS_BAD_URL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_locations.swap(locations);
|
||||
m_nextLocation = 0;
|
||||
m_streamSettingsUpdated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> HttpCameraImpl::GetUrls() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
std::vector<std::string> urls;
|
||||
for (const auto& loc : m_locations) urls.push_back(loc.url);
|
||||
return urls;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::CreateProperty(llvm::StringRef name,
|
||||
llvm::StringRef httpParam, bool viaSettings,
|
||||
CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue,
|
||||
int value) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_propertyData.emplace_back(llvm::make_unique<PropertyData>(
|
||||
name, httpParam, viaSettings, kind, minimum, maximum, step, defaultValue,
|
||||
value));
|
||||
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1, kind,
|
||||
value, llvm::StringRef{});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void HttpCameraImpl::CreateEnumProperty(
|
||||
llvm::StringRef name, llvm::StringRef httpParam, bool viaSettings,
|
||||
int defaultValue, int value, std::initializer_list<T> choices) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_propertyData.emplace_back(llvm::make_unique<PropertyData>(
|
||||
name, httpParam, viaSettings, CS_PROP_ENUM, 0, choices.size() - 1, 1,
|
||||
defaultValue, value));
|
||||
|
||||
auto& enumChoices = m_propertyData.back()->enumChoices;
|
||||
enumChoices.clear();
|
||||
for (const auto& choice : choices) enumChoices.emplace_back(choice);
|
||||
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1,
|
||||
CS_PROP_ENUM, value, llvm::StringRef{});
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, name,
|
||||
m_propertyData.size() + 1, CS_PROP_ENUM, value, llvm::StringRef{});
|
||||
}
|
||||
|
||||
std::unique_ptr<PropertyImpl> HttpCameraImpl::CreateEmptyProperty(
|
||||
llvm::StringRef name) const {
|
||||
return llvm::make_unique<PropertyData>(name);
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::CacheProperties(CS_Status* status) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
|
||||
// Pretty typical set of video modes
|
||||
m_videoModes.clear();
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 640, 480, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 320, 240, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 160, 120, 30);
|
||||
|
||||
m_properties_cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetProperty(int property, int value, CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetStringProperty(int property, llvm::StringRef value,
|
||||
CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetBrightness(int brightness, CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
int HttpCameraImpl::GetBrightness(CS_Status* status) const {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetWhiteBalanceAuto(CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetWhiteBalanceHoldCurrent(CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetWhiteBalanceManual(int value, CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetExposureAuto(CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetExposureHoldCurrent(CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetExposureManual(int value, CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
|
||||
if (mode.pixelFormat != VideoMode::kMJPEG) return false;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_mode = mode;
|
||||
m_streamSettingsUpdated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::NumSinksChanged() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
void HttpCameraImpl::NumSinksEnabledChanged() {
|
||||
m_sinkEnabledCond.notify_one();
|
||||
}
|
||||
|
||||
bool AxisCameraImpl::CacheProperties(CS_Status* status) const {
|
||||
CreateProperty("brightness", "ImageSource.I0.Sensor.Brightness", true,
|
||||
CS_PROP_INTEGER, 0, 100, 1, 50, 50);
|
||||
CreateEnumProperty("white_balance", "ImageSource.I0.Sensor.WhiteBalance",
|
||||
true, 0, 0,
|
||||
{"auto", "hold", "fixed_outdoor1", "fixed_outdoor2",
|
||||
"fixed_indoor", "fixed_fluor1", "fixed_fluor2"});
|
||||
CreateProperty("color_level", "ImageSource.I0.Sensor.ColorLevel", true,
|
||||
CS_PROP_INTEGER, 0, 100, 1, 50, 50);
|
||||
CreateEnumProperty("exposure", "ImageSource.I0.Sensor.Exposure", true, 0, 0,
|
||||
{"auto", "hold", "flickerfree50", "flickerfree60"});
|
||||
CreateProperty("exposure_priority", "ImageSource.I0.Sensor.ExposurePriority",
|
||||
true, CS_PROP_INTEGER, 0, 100, 1, 50, 50);
|
||||
|
||||
// TODO: get video modes from device
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_videoModes.clear();
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 640, 480, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 480, 360, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 320, 240, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 240, 180, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 176, 144, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 160, 120, 30);
|
||||
|
||||
m_properties_cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Source CreateHttpCamera(llvm::StringRef name, llvm::StringRef url,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
std::shared_ptr<HttpCameraImpl> source;
|
||||
switch (kind) {
|
||||
case CS_HTTP_AXIS:
|
||||
source = std::make_shared<AxisCameraImpl>(name);
|
||||
break;
|
||||
default:
|
||||
source = std::make_shared<HttpCameraImpl>(name, kind);
|
||||
break;
|
||||
}
|
||||
std::string urlCopy{url};
|
||||
if (!source->SetUrls(urlCopy, status)) return 0;
|
||||
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_HTTP, source);
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
|
||||
source->Start();
|
||||
return handle;
|
||||
}
|
||||
|
||||
CS_Source CreateHttpCamera(llvm::StringRef name,
|
||||
llvm::ArrayRef<std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
if (urls.empty()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
return 0;
|
||||
}
|
||||
auto source = std::make_shared<HttpCameraImpl>(name, kind);
|
||||
if (!source->SetUrls(urls, status)) return 0;
|
||||
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_HTTP, source);
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
|
||||
source->Start();
|
||||
return handle;
|
||||
}
|
||||
|
||||
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return CS_HTTP_UNKNOWN;
|
||||
}
|
||||
return static_cast<HttpCameraImpl&>(*data->source).GetKind();
|
||||
}
|
||||
|
||||
void SetHttpCameraUrls(CS_Source source, llvm::ArrayRef<std::string> urls,
|
||||
CS_Status* status) {
|
||||
if (urls.empty()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
return;
|
||||
}
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<HttpCameraImpl&>(*data->source).SetUrls(urls, status);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetHttpCameraUrls(CS_Source source,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::vector<std::string>{};
|
||||
}
|
||||
return static_cast<HttpCameraImpl&>(*data->source).GetUrls();
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Source CS_CreateHttpCamera(const char* name, const char* url,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
return cs::CreateHttpCamera(name, url, kind, status);
|
||||
}
|
||||
|
||||
CS_Source CS_CreateHttpCameraMulti(const char* name, const char** urls,
|
||||
int count, CS_HttpCameraKind kind,
|
||||
CS_Status* status) {
|
||||
llvm::SmallVector<std::string, 4> vec;
|
||||
vec.reserve(count);
|
||||
for (int i = 0; i < count; ++i) vec.push_back(urls[i]);
|
||||
return cs::CreateHttpCamera(name, vec, kind, status);
|
||||
}
|
||||
|
||||
CS_HttpCameraKind CS_GetHttpCameraKind(CS_Source source, CS_Status* status) {
|
||||
return cs::GetHttpCameraKind(source, status);
|
||||
}
|
||||
|
||||
void CS_SetHttpCameraUrls(CS_Source source, const char** urls, int count,
|
||||
CS_Status* status) {
|
||||
llvm::SmallVector<std::string, 4> vec;
|
||||
vec.reserve(count);
|
||||
for (int i = 0; i < count; ++i) vec.push_back(urls[i]);
|
||||
cs::SetHttpCameraUrls(source, vec, status);
|
||||
}
|
||||
|
||||
char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status) {
|
||||
auto urls = cs::GetHttpCameraUrls(source, status);
|
||||
char** out = static_cast<char**>(std::malloc(urls.size() * sizeof(char*)));
|
||||
*count = urls.size();
|
||||
for (size_t i = 0; i < urls.size(); ++i) out[i] = cs::ConvertToC(urls[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
void CS_FreeHttpCameraUrls(char** urls, int count) {
|
||||
if (!urls) return;
|
||||
for (int i = 0; i < count; ++i) std::free(urls[i]);
|
||||
std::free(urls);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
155
cscore/src/main/native/cpp/HttpCameraImpl.h
Normal file
155
cscore/src/main/native/cpp/HttpCameraImpl.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_HTTPCAMERAIMPL_H_
|
||||
#define CSCORE_HTTPCAMERAIMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/SmallString.h>
|
||||
#include <llvm/StringMap.h>
|
||||
#include <support/HttpUtil.h>
|
||||
#include <support/condition_variable.h>
|
||||
#include <support/raw_istream.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class HttpCameraImpl : public SourceImpl {
|
||||
public:
|
||||
HttpCameraImpl(llvm::StringRef name, CS_HttpCameraKind kind);
|
||||
~HttpCameraImpl() override;
|
||||
|
||||
void Start();
|
||||
|
||||
// Property functions
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, llvm::StringRef value,
|
||||
CS_Status* status) override;
|
||||
|
||||
// Standard common camera properties
|
||||
void SetBrightness(int brightness, CS_Status* status) override;
|
||||
int GetBrightness(CS_Status* status) const override;
|
||||
void SetWhiteBalanceAuto(CS_Status* status) override;
|
||||
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
|
||||
void SetWhiteBalanceManual(int value, CS_Status* status) override;
|
||||
void SetExposureAuto(CS_Status* status) override;
|
||||
void SetExposureHoldCurrent(CS_Status* status) override;
|
||||
void SetExposureManual(int value, CS_Status* status) override;
|
||||
|
||||
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
|
||||
|
||||
void NumSinksChanged() override;
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
CS_HttpCameraKind GetKind() const;
|
||||
bool SetUrls(llvm::ArrayRef<std::string> urls, CS_Status* status);
|
||||
std::vector<std::string> GetUrls() const;
|
||||
|
||||
// Property data
|
||||
class PropertyData : public PropertyImpl {
|
||||
public:
|
||||
PropertyData() = default;
|
||||
explicit PropertyData(llvm::StringRef name_) : PropertyImpl{name_} {}
|
||||
PropertyData(llvm::StringRef name_, llvm::StringRef httpParam_,
|
||||
bool viaSettings_, CS_PropertyKind kind_, int minimum_,
|
||||
int maximum_, int step_, int defaultValue_, int value_)
|
||||
: PropertyImpl(name_, kind_, step_, defaultValue_, value_),
|
||||
viaSettings(viaSettings_),
|
||||
httpParam(httpParam_) {
|
||||
hasMinimum = true;
|
||||
minimum = minimum_;
|
||||
hasMaximum = true;
|
||||
maximum = maximum_;
|
||||
}
|
||||
~PropertyData() override = default;
|
||||
|
||||
bool viaSettings{false};
|
||||
std::string httpParam;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
|
||||
llvm::StringRef name) const override;
|
||||
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
|
||||
void CreateProperty(llvm::StringRef name, llvm::StringRef httpParam,
|
||||
bool viaSettings, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value) const;
|
||||
|
||||
template <typename T>
|
||||
void CreateEnumProperty(llvm::StringRef name, llvm::StringRef httpParam,
|
||||
bool viaSettings, int defaultValue, int value,
|
||||
std::initializer_list<T> choices) const;
|
||||
|
||||
private:
|
||||
// The camera streaming thread
|
||||
void StreamThreadMain();
|
||||
|
||||
// Functions used by StreamThreadMain()
|
||||
wpi::HttpConnection* DeviceStreamConnect(
|
||||
llvm::SmallVectorImpl<char>& boundary);
|
||||
void DeviceStream(wpi::raw_istream& is, llvm::StringRef boundary);
|
||||
bool DeviceStreamFrame(wpi::raw_istream& is, std::string& imageBuf);
|
||||
|
||||
// The camera settings thread
|
||||
void SettingsThreadMain();
|
||||
void DeviceSendSettings(wpi::HttpRequest& req);
|
||||
|
||||
std::atomic_bool m_connected{false};
|
||||
std::atomic_bool m_active{true}; // set to false to terminate thread
|
||||
std::thread m_streamThread;
|
||||
std::thread m_settingsThread;
|
||||
|
||||
//
|
||||
// Variables protected by m_mutex
|
||||
//
|
||||
|
||||
// The camera connections
|
||||
std::unique_ptr<wpi::HttpConnection> m_streamConn;
|
||||
std::unique_ptr<wpi::HttpConnection> m_settingsConn;
|
||||
|
||||
CS_HttpCameraKind m_kind;
|
||||
|
||||
std::vector<wpi::HttpLocation> m_locations;
|
||||
size_t m_nextLocation{0};
|
||||
int m_prefLocation{-1}; // preferred location
|
||||
|
||||
wpi::condition_variable m_sinkEnabledCond;
|
||||
|
||||
llvm::StringMap<llvm::SmallString<16>> m_settings;
|
||||
wpi::condition_variable m_settingsCond;
|
||||
|
||||
llvm::StringMap<llvm::SmallString<16>> m_streamSettings;
|
||||
std::atomic_bool m_streamSettingsUpdated{false};
|
||||
};
|
||||
|
||||
class AxisCameraImpl : public HttpCameraImpl {
|
||||
public:
|
||||
explicit AxisCameraImpl(llvm::StringRef name)
|
||||
: HttpCameraImpl{name, CS_HTTP_AXIS} {}
|
||||
#if 0
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, llvm::StringRef value,
|
||||
CS_Status* status) override;
|
||||
#endif
|
||||
protected:
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_HTTPCAMERAIMPL_H_
|
||||
102
cscore/src/main/native/cpp/Image.h
Normal file
102
cscore/src/main/native/cpp/Image.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_IMAGE_H_
|
||||
#define CSCORE_IMAGE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/StringRef.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
#include "default_init_allocator.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Frame;
|
||||
|
||||
class Image {
|
||||
friend class Frame;
|
||||
|
||||
public:
|
||||
#ifndef __linux__
|
||||
explicit Image(size_t capacity) { m_data.reserve(capacity); }
|
||||
#else
|
||||
explicit Image(size_t capacity)
|
||||
: m_data{capacity, default_init_allocator<uchar>{}} {
|
||||
m_data.resize(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
Image(const Image&) = delete;
|
||||
Image& operator=(const Image&) = delete;
|
||||
|
||||
// Getters
|
||||
operator llvm::StringRef() const { return str(); }
|
||||
llvm::StringRef str() const { return llvm::StringRef(data(), size()); }
|
||||
size_t capacity() const { return m_data.capacity(); }
|
||||
const char* data() const {
|
||||
return reinterpret_cast<const char*>(m_data.data());
|
||||
}
|
||||
char* data() { return reinterpret_cast<char*>(m_data.data()); }
|
||||
size_t size() const { return m_data.size(); }
|
||||
|
||||
const std::vector<uchar>& vec() const { return m_data; }
|
||||
std::vector<uchar>& vec() { return m_data; }
|
||||
|
||||
void resize(size_t size) { m_data.resize(size); }
|
||||
void SetSize(size_t size) { m_data.resize(size); }
|
||||
|
||||
cv::Mat AsMat() {
|
||||
int type;
|
||||
switch (pixelFormat) {
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kRGB565:
|
||||
type = CV_8UC2;
|
||||
break;
|
||||
case VideoMode::kBGR:
|
||||
type = CV_8UC3;
|
||||
break;
|
||||
case VideoMode::kGray:
|
||||
case VideoMode::kMJPEG:
|
||||
default:
|
||||
type = CV_8UC1;
|
||||
break;
|
||||
}
|
||||
return cv::Mat{height, width, type, m_data.data()};
|
||||
}
|
||||
|
||||
cv::_InputArray AsInputArray() { return cv::_InputArray{m_data}; }
|
||||
|
||||
bool Is(int width_, int height_) {
|
||||
return width == width_ && height == height_;
|
||||
}
|
||||
bool Is(int width_, int height_, VideoMode::PixelFormat pixelFormat_) {
|
||||
return width == width_ && height == height_ && pixelFormat == pixelFormat_;
|
||||
}
|
||||
bool IsLarger(int width_, int height_) {
|
||||
return width >= width_ && height >= height_;
|
||||
}
|
||||
bool IsLarger(const Image& oth) {
|
||||
return width >= oth.width && height >= oth.height;
|
||||
}
|
||||
bool IsSmaller(int width_, int height_) { return !IsLarger(width_, height_); }
|
||||
bool IsSmaller(const Image& oth) { return !IsLarger(oth); }
|
||||
|
||||
private:
|
||||
std::vector<uchar> m_data;
|
||||
|
||||
public:
|
||||
VideoMode::PixelFormat pixelFormat{VideoMode::kUnknown};
|
||||
int width{0};
|
||||
int height{0};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_IMAGE_H_
|
||||
197
cscore/src/main/native/cpp/JpegUtil.cpp
Normal file
197
cscore/src/main/native/cpp/JpegUtil.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "JpegUtil.h"
|
||||
|
||||
#include <support/raw_istream.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
// DHT data for MJPEG images that don't have it.
|
||||
static const unsigned char dhtData[] = {
|
||||
0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
|
||||
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x03,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
|
||||
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04,
|
||||
0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
|
||||
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15,
|
||||
0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95,
|
||||
0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
|
||||
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
|
||||
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
|
||||
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
|
||||
0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
|
||||
0xfa, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
|
||||
0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
|
||||
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
|
||||
0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33,
|
||||
0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25,
|
||||
0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
|
||||
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
|
||||
0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
|
||||
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
|
||||
0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
|
||||
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
|
||||
0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa};
|
||||
|
||||
bool IsJpeg(llvm::StringRef data) {
|
||||
if (data.size() < 11) return false;
|
||||
|
||||
// Check for valid SOI
|
||||
auto bytes = data.bytes_begin();
|
||||
if (bytes[0] != 0xff || bytes[1] != 0xd8) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetJpegSize(llvm::StringRef data, int* width, int* height) {
|
||||
if (!IsJpeg(data)) return false;
|
||||
|
||||
data = data.substr(2); // Get to the first block
|
||||
auto bytes = data.bytes_begin();
|
||||
for (;;) {
|
||||
if (data.size() < 4) return false; // EOF
|
||||
bytes = data.bytes_begin();
|
||||
if (bytes[0] != 0xff) return false; // not a tag
|
||||
if (bytes[1] == 0xd9) return false; // EOI without finding SOF?
|
||||
if (bytes[1] == 0xda) return false; // SOS without finding SOF?
|
||||
if (bytes[1] == 0xc0) {
|
||||
// SOF contains the file size
|
||||
if (data.size() < 9) return false;
|
||||
*height = bytes[5] * 256 + bytes[6];
|
||||
*width = bytes[7] * 256 + bytes[8];
|
||||
return true;
|
||||
}
|
||||
// Go to the next block
|
||||
data = data.substr(bytes[2] * 256 + bytes[3] + 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF) {
|
||||
llvm::StringRef sdata(data, *size);
|
||||
if (!IsJpeg(sdata)) return false;
|
||||
|
||||
*locSOF = *size;
|
||||
|
||||
// Search until SOS for DHT tag
|
||||
sdata = sdata.substr(2); // Get to the first block
|
||||
auto bytes = sdata.bytes_begin();
|
||||
for (;;) {
|
||||
if (sdata.size() < 4) return false; // EOF
|
||||
bytes = sdata.bytes_begin();
|
||||
if (bytes[0] != 0xff) return false; // not a tag
|
||||
if (bytes[1] == 0xda) break; // SOS
|
||||
if (bytes[1] == 0xc4) return false; // DHT
|
||||
if (bytes[1] == 0xc0) *locSOF = sdata.data() - data; // SOF
|
||||
// Go to the next block
|
||||
sdata = sdata.substr(bytes[2] * 256 + bytes[3] + 2);
|
||||
}
|
||||
|
||||
// Only add DHT if we also found SOF (insertion point)
|
||||
if (*locSOF != *size) {
|
||||
*size += sizeof(dhtData);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
llvm::StringRef JpegGetDHT() {
|
||||
return llvm::StringRef(reinterpret_cast<const char*>(dhtData),
|
||||
sizeof(dhtData));
|
||||
}
|
||||
|
||||
static inline void ReadInto(wpi::raw_istream& is, std::string& buf,
|
||||
size_t len) {
|
||||
size_t oldSize = buf.size();
|
||||
buf.resize(oldSize + len);
|
||||
is.read(&(*buf.begin()) + oldSize, len);
|
||||
}
|
||||
|
||||
bool ReadJpeg(wpi::raw_istream& is, std::string& buf, int* width, int* height) {
|
||||
// in case we don't get a SOF
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
|
||||
// read SOI and first marker
|
||||
buf.resize(4);
|
||||
is.read(&(*buf.begin()), 4);
|
||||
if (is.has_error()) return false;
|
||||
|
||||
// Check for valid SOI
|
||||
auto bytes = reinterpret_cast<const unsigned char*>(buf.data());
|
||||
if (bytes[0] != 0xff || bytes[1] != 0xd8) return false;
|
||||
size_t pos = 2; // point to first marker
|
||||
for (;;) {
|
||||
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
|
||||
if (bytes[0] != 0xff) return false; // not a marker
|
||||
unsigned char marker = bytes[1];
|
||||
|
||||
if (marker == 0xd9) return true; // EOI, we're done
|
||||
|
||||
if (marker == 0xda) {
|
||||
// SOS: need to keep reading until we reach a normal marker.
|
||||
// Byte stuffing ensures we don't get false markers.
|
||||
// Have to read a byte at a time as we don't want to overread.
|
||||
pos += 2; // point after SOS marker
|
||||
bool maybeMarker = false;
|
||||
for (;;) {
|
||||
ReadInto(is, buf, 1);
|
||||
if (is.has_error()) return false;
|
||||
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
|
||||
if (maybeMarker) {
|
||||
if (bytes[0] != 0x00 && bytes[0] != 0xff &&
|
||||
(bytes[0] < 0xd0 || bytes[0] > 0xd7))
|
||||
break;
|
||||
maybeMarker = false;
|
||||
} else if (bytes[0] == 0xff) {
|
||||
maybeMarker = true;
|
||||
}
|
||||
++pos; // point after byte we finished reading
|
||||
}
|
||||
--pos; // point back to start of marker
|
||||
continue;
|
||||
}
|
||||
|
||||
// A normal block. Read the length
|
||||
ReadInto(is, buf, 2); // read length
|
||||
if (is.has_error()) return false;
|
||||
|
||||
// Point to length
|
||||
pos += 2;
|
||||
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
|
||||
|
||||
// Read the block and the next marker
|
||||
size_t blockLength = bytes[0] * 256 + bytes[1];
|
||||
ReadInto(is, buf, blockLength);
|
||||
if (is.has_error()) return false;
|
||||
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
|
||||
|
||||
// Special block processing
|
||||
if (marker == 0xc0) {
|
||||
// SOF: contains the file size; make sure we actually read enough bytes
|
||||
if (blockLength >= 7) {
|
||||
*height = bytes[3] * 256 + bytes[4];
|
||||
*width = bytes[5] * 256 + bytes[6];
|
||||
}
|
||||
}
|
||||
|
||||
// Point to next marker
|
||||
pos += blockLength;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
33
cscore/src/main/native/cpp/JpegUtil.h
Normal file
33
cscore/src/main/native/cpp/JpegUtil.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_JPEGUTIL_H_
|
||||
#define CSCORE_JPEGUTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <llvm/StringRef.h>
|
||||
|
||||
namespace wpi {
|
||||
class raw_istream;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
bool IsJpeg(llvm::StringRef data);
|
||||
|
||||
bool GetJpegSize(llvm::StringRef data, int* width, int* height);
|
||||
|
||||
bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF);
|
||||
|
||||
llvm::StringRef JpegGetDHT();
|
||||
|
||||
bool ReadJpeg(wpi::raw_istream& is, std::string& buf, int* width, int* height);
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_JPEGUTIL_H_
|
||||
45
cscore/src/main/native/cpp/Log.cpp
Normal file
45
cscore/src/main/native/cpp/Log.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include <llvm/Path.h>
|
||||
#include <llvm/SmallString.h>
|
||||
#include <llvm/StringRef.h>
|
||||
#include <llvm/raw_ostream.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
static void def_log_func(unsigned int level, const char* file,
|
||||
unsigned int line, const char* msg) {
|
||||
llvm::SmallString<128> buf;
|
||||
llvm::raw_svector_ostream oss(buf);
|
||||
if (level == 20) {
|
||||
oss << "CS: " << msg << '\n';
|
||||
llvm::errs() << oss.str();
|
||||
return;
|
||||
}
|
||||
|
||||
llvm::StringRef levelmsg;
|
||||
if (level >= 50)
|
||||
levelmsg = "CRITICAL: ";
|
||||
else if (level >= 40)
|
||||
levelmsg = "ERROR: ";
|
||||
else if (level >= 30)
|
||||
levelmsg = "WARNING: ";
|
||||
else
|
||||
return;
|
||||
oss << "CS: " << levelmsg << msg << " (" << llvm::sys::path::filename(file)
|
||||
<< ':' << line << ")\n";
|
||||
llvm::errs() << oss.str();
|
||||
}
|
||||
|
||||
Logger::Logger() { SetDefaultLogger(); }
|
||||
|
||||
Logger::~Logger() {}
|
||||
|
||||
void Logger::SetDefaultLogger() { SetLogger(def_log_func); }
|
||||
54
cscore/src/main/native/cpp/Log.h
Normal file
54
cscore/src/main/native/cpp/Log.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_LOG_H_
|
||||
#define CSCORE_LOG_H_
|
||||
|
||||
#include <support/Logger.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Logger : public wpi::Logger {
|
||||
public:
|
||||
static Logger& GetInstance() {
|
||||
static Logger instance;
|
||||
return instance;
|
||||
}
|
||||
~Logger();
|
||||
|
||||
void SetDefaultLogger();
|
||||
|
||||
private:
|
||||
Logger();
|
||||
};
|
||||
|
||||
#define LOG(level, x) WPI_LOG(cs::Logger::GetInstance(), level, x)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(x) WPI_ERROR(cs::Logger::GetInstance(), x)
|
||||
#define WARNING(x) WPI_WARNING(cs::Logger::GetInstance(), x)
|
||||
#define INFO(x) WPI_INFO(cs::Logger::GetInstance(), x)
|
||||
|
||||
#define DEBUG(x) WPI_DEBUG(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG1(x) WPI_DEBUG1(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG2(x) WPI_DEBUG2(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG3(x) WPI_DEBUG3(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG4(x) WPI_DEBUG4(cs::Logger::GetInstance(), x)
|
||||
|
||||
#define SERROR(x) ERROR(GetName() << ": " << x)
|
||||
#define SWARNING(x) WARNING(GetName() << ": " << x)
|
||||
#define SINFO(x) INFO(GetName() << ": " << x)
|
||||
|
||||
#define SDEBUG(x) DEBUG(GetName() << ": " << x)
|
||||
#define SDEBUG1(x) DEBUG1(GetName() << ": " << x)
|
||||
#define SDEBUG2(x) DEBUG2(GetName() << ": " << x)
|
||||
#define SDEBUG3(x) DEBUG3(GetName() << ": " << x)
|
||||
#define SDEBUG4(x) DEBUG4(GetName() << ": " << x)
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_LOG_H_
|
||||
938
cscore/src/main/native/cpp/MjpegServerImpl.cpp
Normal file
938
cscore/src/main/native/cpp/MjpegServerImpl.cpp
Normal file
@@ -0,0 +1,938 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "MjpegServerImpl.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <llvm/SmallString.h>
|
||||
#include <support/HttpUtil.h>
|
||||
#include <support/raw_socket_istream.h>
|
||||
#include <support/raw_socket_ostream.h>
|
||||
#include <tcpsockets/TCPAcceptor.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "JpegUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "SourceImpl.h"
|
||||
#include "c_util.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
// The boundary used for the M-JPEG stream.
|
||||
// It separates the multipart stream of pictures
|
||||
#define BOUNDARY "boundarydonotcross"
|
||||
|
||||
// A bare-bones HTML webpage for user friendliness.
|
||||
static const char* emptyRootPage =
|
||||
"</head><body>"
|
||||
"<img src=\"/stream.mjpg\" /><p />"
|
||||
"<a href=\"/settings.json\">Settings JSON</a>"
|
||||
"</body></html>";
|
||||
|
||||
// An HTML page to be sent when a source exists
|
||||
static const char* startRootPage =
|
||||
"<script>\n"
|
||||
"function httpGetAsync(name, val)\n"
|
||||
"{\n"
|
||||
" var host = location.protocol + '//' + location.host + "
|
||||
"'/?action=command&' + name + '=' + val;\n"
|
||||
" var xmlHttp = new XMLHttpRequest();\n"
|
||||
" xmlHttp.open(\"GET\", host, true);\n"
|
||||
" xmlHttp.send(null);\n"
|
||||
"}\n"
|
||||
"function updateInt(prop, name, val) {\n"
|
||||
" document.querySelector(prop).value = val;\n"
|
||||
" httpGetAsync(name, val);\n"
|
||||
"}\n"
|
||||
"function update(name, val) {\n"
|
||||
" httpGetAsync(name, val);\n"
|
||||
"}\n"
|
||||
"</script>\n"
|
||||
"<style>\n"
|
||||
"table, th, td {\n"
|
||||
" border: 1px solid black;\n"
|
||||
" border-collapse: collapse;\n"
|
||||
"}\n"
|
||||
".settings { float: left; }\n"
|
||||
".stream { display: inline-block; margin-left: 10px; }\n"
|
||||
"</style>\n"
|
||||
"</head><body>\n"
|
||||
"<div class=\"stream\">\n"
|
||||
"<img src=\"/stream.mjpg\" /><p />\n"
|
||||
"<a href=\"/settings.json\">Settings JSON</a>\n"
|
||||
"</div>\n"
|
||||
"<div class=\"settings\">\n";
|
||||
static const char* endRootPage = "</div></body></html>";
|
||||
|
||||
class MjpegServerImpl::ConnThread : public wpi::SafeThread {
|
||||
public:
|
||||
explicit ConnThread(llvm::StringRef name) : m_name(name) {}
|
||||
|
||||
void Main();
|
||||
|
||||
bool ProcessCommand(llvm::raw_ostream& os, SourceImpl& source,
|
||||
llvm::StringRef parameters, bool respond);
|
||||
void SendJSON(llvm::raw_ostream& os, SourceImpl& source, bool header);
|
||||
void SendHTMLHeadTitle(llvm::raw_ostream& os) const;
|
||||
void SendHTML(llvm::raw_ostream& os, SourceImpl& source, bool header);
|
||||
void SendStream(wpi::raw_socket_ostream& os);
|
||||
void ProcessRequest();
|
||||
|
||||
std::unique_ptr<wpi::NetworkStream> m_stream;
|
||||
std::shared_ptr<SourceImpl> m_source;
|
||||
bool m_streaming = false;
|
||||
bool m_noStreaming = false;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
|
||||
llvm::StringRef GetName() { return m_name; }
|
||||
|
||||
std::shared_ptr<SourceImpl> GetSource() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
return m_source;
|
||||
}
|
||||
|
||||
void StartStream() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_source->EnableSink();
|
||||
m_streaming = true;
|
||||
}
|
||||
|
||||
void StopStream() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_source->DisableSink();
|
||||
m_streaming = false;
|
||||
}
|
||||
|
||||
int m_width{0};
|
||||
int m_height{0};
|
||||
int m_compression{80};
|
||||
int m_fps{0};
|
||||
};
|
||||
|
||||
// Standard header to send along with other header information like mimetype.
|
||||
//
|
||||
// The parameters should ensure the browser does not cache our answer.
|
||||
// A browser should connect for each file and not serve files from its cache.
|
||||
// Using cached pictures would lead to showing old/outdated pictures.
|
||||
// Many browsers seem to ignore, or at least not always obey, those headers.
|
||||
static void SendHeader(llvm::raw_ostream& os, int code,
|
||||
llvm::StringRef codeText, llvm::StringRef contentType,
|
||||
llvm::StringRef extra = llvm::StringRef{}) {
|
||||
os << "HTTP/1.0 " << code << ' ' << codeText << "\r\n";
|
||||
os << "Connection: close\r\n"
|
||||
"Server: CameraServer/1.0\r\n"
|
||||
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
|
||||
"post-check=0, max-age=0\r\n"
|
||||
"Pragma: no-cache\r\n"
|
||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n";
|
||||
os << "Content-Type: " << contentType << "\r\n";
|
||||
os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
|
||||
if (!extra.empty()) os << extra << "\r\n";
|
||||
os << "\r\n"; // header ends with a blank line
|
||||
}
|
||||
|
||||
// Send error header and message
|
||||
// @param code HTTP error code (e.g. 404)
|
||||
// @param message Additional message text
|
||||
static void SendError(llvm::raw_ostream& os, int code,
|
||||
llvm::StringRef message) {
|
||||
llvm::StringRef codeText, extra, baseMessage;
|
||||
switch (code) {
|
||||
case 401:
|
||||
codeText = "Unauthorized";
|
||||
extra = "WWW-Authenticate: Basic realm=\"CameraServer\"";
|
||||
baseMessage = "401: Not Authenticated!";
|
||||
break;
|
||||
case 404:
|
||||
codeText = "Not Found";
|
||||
baseMessage = "404: Not Found!";
|
||||
break;
|
||||
case 500:
|
||||
codeText = "Internal Server Error";
|
||||
baseMessage = "500: Internal Server Error!";
|
||||
break;
|
||||
case 400:
|
||||
codeText = "Bad Request";
|
||||
baseMessage = "400: Not Found!";
|
||||
break;
|
||||
case 403:
|
||||
codeText = "Forbidden";
|
||||
baseMessage = "403: Forbidden!";
|
||||
break;
|
||||
case 503:
|
||||
codeText = "Service Unavailable";
|
||||
baseMessage = "503: Service Unavailable";
|
||||
break;
|
||||
default:
|
||||
code = 501;
|
||||
codeText = "Not Implemented";
|
||||
baseMessage = "501: Not Implemented!";
|
||||
break;
|
||||
}
|
||||
SendHeader(os, code, codeText, "text/plain", extra);
|
||||
os << baseMessage << "\r\n" << message;
|
||||
}
|
||||
|
||||
// Perform a command specified by HTTP GET parameters.
|
||||
bool MjpegServerImpl::ConnThread::ProcessCommand(llvm::raw_ostream& os,
|
||||
SourceImpl& source,
|
||||
llvm::StringRef parameters,
|
||||
bool respond) {
|
||||
llvm::SmallString<256> responseBuf;
|
||||
llvm::raw_svector_ostream response{responseBuf};
|
||||
// command format: param1=value1¶m2=value2...
|
||||
while (!parameters.empty()) {
|
||||
// split out next param and value
|
||||
llvm::StringRef rawParam, rawValue;
|
||||
std::tie(rawParam, parameters) = parameters.split('&');
|
||||
if (rawParam.empty()) continue; // ignore "&&"
|
||||
std::tie(rawParam, rawValue) = rawParam.split('=');
|
||||
if (rawParam.empty() || rawValue.empty()) continue; // ignore "param="
|
||||
SDEBUG4("HTTP parameter \"" << rawParam << "\" value \"" << rawValue
|
||||
<< "\"");
|
||||
|
||||
// unescape param
|
||||
bool error = false;
|
||||
llvm::SmallString<64> paramBuf;
|
||||
llvm::StringRef param = wpi::UnescapeURI(rawParam, paramBuf, &error);
|
||||
if (error) {
|
||||
llvm::SmallString<128> error;
|
||||
llvm::raw_svector_ostream oss{error};
|
||||
oss << "could not unescape parameter \"" << rawParam << "\"";
|
||||
SendError(os, 500, error.str());
|
||||
SDEBUG(error.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// unescape value
|
||||
llvm::SmallString<64> valueBuf;
|
||||
llvm::StringRef value = wpi::UnescapeURI(rawValue, valueBuf, &error);
|
||||
if (error) {
|
||||
llvm::SmallString<128> error;
|
||||
llvm::raw_svector_ostream oss{error};
|
||||
oss << "could not unescape value \"" << rawValue << "\"";
|
||||
SendError(os, 500, error.str());
|
||||
SDEBUG(error.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle resolution, compression, and FPS. These are handled locally
|
||||
// rather than passed to the source.
|
||||
if (param == "resolution") {
|
||||
llvm::StringRef widthStr, heightStr;
|
||||
std::tie(widthStr, heightStr) = value.split('x');
|
||||
int width, height;
|
||||
if (widthStr.getAsInteger(10, width)) {
|
||||
response << param << ": \"width is not an integer\"\r\n";
|
||||
SWARNING("HTTP parameter \"" << param << "\" width \"" << widthStr
|
||||
<< "\" is not an integer");
|
||||
continue;
|
||||
}
|
||||
if (heightStr.getAsInteger(10, height)) {
|
||||
response << param << ": \"height is not an integer\"\r\n";
|
||||
SWARNING("HTTP parameter \"" << param << "\" height \"" << heightStr
|
||||
<< "\" is not an integer");
|
||||
continue;
|
||||
}
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
response << param << ": \"ok\"\r\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == "fps") {
|
||||
int fps;
|
||||
if (value.getAsInteger(10, fps)) {
|
||||
response << param << ": \"invalid integer\"\r\n";
|
||||
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
|
||||
<< "\" is not an integer");
|
||||
continue;
|
||||
} else {
|
||||
m_fps = fps;
|
||||
response << param << ": \"ok\"\r\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == "compression") {
|
||||
int compression;
|
||||
if (value.getAsInteger(10, compression)) {
|
||||
response << param << ": \"invalid integer\"\r\n";
|
||||
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
|
||||
<< "\" is not an integer");
|
||||
continue;
|
||||
} else {
|
||||
m_compression = compression;
|
||||
response << param << ": \"ok\"\r\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore name parameter
|
||||
if (param == "name") continue;
|
||||
|
||||
// try to assign parameter
|
||||
auto prop = source.GetPropertyIndex(param);
|
||||
if (!prop) {
|
||||
response << param << ": \"ignored\"\r\n";
|
||||
SWARNING("ignoring HTTP parameter \"" << param << "\"");
|
||||
continue;
|
||||
}
|
||||
|
||||
CS_Status status = 0;
|
||||
auto kind = source.GetPropertyKind(prop);
|
||||
switch (kind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM: {
|
||||
int val;
|
||||
if (value.getAsInteger(10, val)) {
|
||||
response << param << ": \"invalid integer\"\r\n";
|
||||
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
|
||||
<< "\" is not an integer");
|
||||
} else {
|
||||
response << param << ": " << val << "\r\n";
|
||||
SDEBUG4("HTTP parameter \"" << param << "\" value " << value);
|
||||
source.SetProperty(prop, val, &status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CS_PROP_STRING: {
|
||||
response << param << ": \"ok\"\r\n";
|
||||
SDEBUG4("HTTP parameter \"" << param << "\" value \"" << value << "\"");
|
||||
source.SetStringProperty(prop, value, &status);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send HTTP response
|
||||
if (respond) {
|
||||
SendHeader(os, 200, "OK", "text/plain");
|
||||
os << response.str() << "\r\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MjpegServerImpl::ConnThread::SendHTMLHeadTitle(
|
||||
llvm::raw_ostream& os) const {
|
||||
os << "<html><head><title>" << m_name << " CameraServer</title>";
|
||||
}
|
||||
|
||||
// Send the root html file with controls for all the settable properties.
|
||||
void MjpegServerImpl::ConnThread::SendHTML(llvm::raw_ostream& os,
|
||||
SourceImpl& source, bool header) {
|
||||
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
|
||||
|
||||
SendHTMLHeadTitle(os);
|
||||
os << startRootPage;
|
||||
llvm::SmallVector<int, 32> properties_vec;
|
||||
CS_Status status = 0;
|
||||
for (auto prop : source.EnumerateProperties(properties_vec, &status)) {
|
||||
llvm::SmallString<128> name_buf;
|
||||
auto name = source.GetPropertyName(prop, name_buf, &status);
|
||||
if (name.startswith("raw_")) continue;
|
||||
auto kind = source.GetPropertyKind(prop);
|
||||
os << "<p />"
|
||||
<< "<label for=\"" << name << "\">" << name << "</label>\n";
|
||||
switch (kind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
os << "<input id=\"" << name
|
||||
<< "\" type=\"checkbox\" onclick=\"update('" << name
|
||||
<< "', this.checked ? 1 : 0)\" ";
|
||||
if (source.GetProperty(prop, &status) != 0)
|
||||
os << "checked />\n";
|
||||
else
|
||||
os << " />\n";
|
||||
break;
|
||||
case CS_PROP_INTEGER: {
|
||||
auto valI = source.GetProperty(prop, &status);
|
||||
auto min = source.GetPropertyMin(prop, &status);
|
||||
auto max = source.GetPropertyMax(prop, &status);
|
||||
auto step = source.GetPropertyStep(prop, &status);
|
||||
os << "<input type=\"range\" min=\"" << min << "\" max=\"" << max
|
||||
<< "\" value=\"" << valI << "\" id=\"" << name << "\" step=\""
|
||||
<< step << "\" oninput=\"updateInt('#" << name << "op', '" << name
|
||||
<< "', value)\" />\n";
|
||||
os << "<output for=\"" << name << "\" id=\"" << name << "op\">" << valI
|
||||
<< "</output>\n";
|
||||
break;
|
||||
}
|
||||
case CS_PROP_ENUM: {
|
||||
auto valE = source.GetProperty(prop, &status);
|
||||
auto choices = source.GetEnumPropertyChoices(prop, &status);
|
||||
int j = 0;
|
||||
for (auto choice = choices.begin(), end = choices.end(); choice != end;
|
||||
++j, ++choice) {
|
||||
if (choice->empty()) continue; // skip empty choices
|
||||
// replace any non-printable characters in name with spaces
|
||||
llvm::SmallString<128> ch_name;
|
||||
for (char ch : *choice)
|
||||
ch_name.push_back(std::isprint(ch) ? ch : ' ');
|
||||
os << "<input id=\"" << name << j << "\" type=\"radio\" name=\""
|
||||
<< name << "\" value=\"" << ch_name << "\" onclick=\"update('"
|
||||
<< name << "', " << j << ")\"";
|
||||
if (j == valE) {
|
||||
os << " checked";
|
||||
}
|
||||
os << " /><label for=\"" << name << j << "\">" << ch_name
|
||||
<< "</label>\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CS_PROP_STRING: {
|
||||
llvm::SmallString<128> strval_buf;
|
||||
os << "<input type=\"text\" id=\"" << name << "box\" name=\"" << name
|
||||
<< "\" value=\""
|
||||
<< source.GetStringProperty(prop, strval_buf, &status) << "\" />\n";
|
||||
os << "<input type=\"button\" value =\"Submit\" onclick=\"update('"
|
||||
<< name << "', " << name << "box.value)\" />\n";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
os << "<p>Supported Video Modes:</p>\n";
|
||||
os << "<table cols=\"4\" style=\"border: 1px solid black\">\n";
|
||||
os << "<tr><th>Pixel Format</th>"
|
||||
<< "<th>Width</th>"
|
||||
<< "<th>Height</th>"
|
||||
<< "<th>FPS</th></tr>";
|
||||
for (auto mode : source.EnumerateVideoModes(&status)) {
|
||||
os << "<tr><td>";
|
||||
switch (mode.pixelFormat) {
|
||||
case VideoMode::kMJPEG:
|
||||
os << "MJPEG";
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
os << "YUYV";
|
||||
break;
|
||||
case VideoMode::kRGB565:
|
||||
os << "RGB565";
|
||||
break;
|
||||
case VideoMode::kBGR:
|
||||
os << "BGR";
|
||||
break;
|
||||
case VideoMode::kGray:
|
||||
os << "gray";
|
||||
break;
|
||||
default:
|
||||
os << "unknown";
|
||||
break;
|
||||
}
|
||||
os << "</td><td>" << mode.width;
|
||||
os << "</td><td>" << mode.height;
|
||||
os << "</td><td>" << mode.fps;
|
||||
os << "</td></tr>";
|
||||
}
|
||||
os << "</table>\n";
|
||||
os << endRootPage << "\r\n";
|
||||
os.flush();
|
||||
}
|
||||
|
||||
// Send a JSON file which is contains information about the source parameters.
|
||||
void MjpegServerImpl::ConnThread::SendJSON(llvm::raw_ostream& os,
|
||||
SourceImpl& source, bool header) {
|
||||
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
|
||||
|
||||
os << "{\n\"controls\": [\n";
|
||||
llvm::SmallVector<int, 32> properties_vec;
|
||||
bool first = true;
|
||||
CS_Status status = 0;
|
||||
for (auto prop : source.EnumerateProperties(properties_vec, &status)) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
os << ",\n";
|
||||
os << '{';
|
||||
llvm::SmallString<128> name_buf;
|
||||
auto name = source.GetPropertyName(prop, name_buf, &status);
|
||||
auto kind = source.GetPropertyKind(prop);
|
||||
os << "\n\"name\": \"" << name << '"';
|
||||
os << ",\n\"id\": \"" << prop << '"';
|
||||
os << ",\n\"type\": \"" << kind << '"';
|
||||
os << ",\n\"min\": \"" << source.GetPropertyMin(prop, &status) << '"';
|
||||
os << ",\n\"max\": \"" << source.GetPropertyMax(prop, &status) << '"';
|
||||
os << ",\n\"step\": \"" << source.GetPropertyStep(prop, &status) << '"';
|
||||
os << ",\n\"default\": \"" << source.GetPropertyDefault(prop, &status)
|
||||
<< '"';
|
||||
os << ",\n\"value\": \"";
|
||||
switch (kind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM:
|
||||
os << source.GetProperty(prop, &status);
|
||||
break;
|
||||
case CS_PROP_STRING: {
|
||||
llvm::SmallString<128> strval_buf;
|
||||
os << source.GetStringProperty(prop, strval_buf, &status);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
os << '"';
|
||||
// os << ",\n\"dest\": \"0\"";
|
||||
// os << ",\n\"flags\": \"" << param->flags << '"';
|
||||
// os << ",\n\"group\": \"" << param->group << '"';
|
||||
|
||||
// append the menu object to the menu typecontrols
|
||||
if (source.GetPropertyKind(prop) == CS_PROP_ENUM) {
|
||||
os << ",\n\"menu\": {";
|
||||
auto choices = source.GetEnumPropertyChoices(prop, &status);
|
||||
int j = 0;
|
||||
for (auto choice = choices.begin(), end = choices.end(); choice != end;
|
||||
++j, ++choice) {
|
||||
if (j != 0) os << ", ";
|
||||
// replace any non-printable characters in name with spaces
|
||||
llvm::SmallString<128> ch_name;
|
||||
for (char ch : *choice) ch_name.push_back(std::isprint(ch) ? ch : ' ');
|
||||
os << '"' << j << "\": \"" << ch_name << '"';
|
||||
}
|
||||
os << "}\n";
|
||||
}
|
||||
os << '}';
|
||||
}
|
||||
os << "\n],\n\"modes\": [\n";
|
||||
first = true;
|
||||
for (auto mode : source.EnumerateVideoModes(&status)) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
os << ",\n";
|
||||
os << '{';
|
||||
os << "\n\"pixelFormat\": \"";
|
||||
switch (mode.pixelFormat) {
|
||||
case VideoMode::kMJPEG:
|
||||
os << "MJPEG";
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
os << "YUYV";
|
||||
break;
|
||||
case VideoMode::kRGB565:
|
||||
os << "RGB565";
|
||||
break;
|
||||
case VideoMode::kBGR:
|
||||
os << "BGR";
|
||||
break;
|
||||
case VideoMode::kGray:
|
||||
os << "gray";
|
||||
break;
|
||||
default:
|
||||
os << "unknown";
|
||||
break;
|
||||
}
|
||||
os << "\",\n\"width\": \"" << mode.width << '"';
|
||||
os << ",\n\"height\": \"" << mode.height << '"';
|
||||
os << ",\n\"fps\": \"" << mode.fps << '"';
|
||||
os << '}';
|
||||
}
|
||||
os << "\n]\n}\n";
|
||||
os.flush();
|
||||
}
|
||||
|
||||
MjpegServerImpl::MjpegServerImpl(llvm::StringRef name,
|
||||
llvm::StringRef listenAddress, int port,
|
||||
std::unique_ptr<wpi::NetworkAcceptor> acceptor)
|
||||
: SinkImpl{name},
|
||||
m_listenAddress(listenAddress),
|
||||
m_port(port),
|
||||
m_acceptor{std::move(acceptor)} {
|
||||
m_active = true;
|
||||
|
||||
llvm::SmallString<128> descBuf;
|
||||
llvm::raw_svector_ostream desc{descBuf};
|
||||
desc << "HTTP Server on port " << port;
|
||||
SetDescription(desc.str());
|
||||
|
||||
m_serverThread = std::thread(&MjpegServerImpl::ServerThreadMain, this);
|
||||
}
|
||||
|
||||
MjpegServerImpl::~MjpegServerImpl() { Stop(); }
|
||||
|
||||
void MjpegServerImpl::Stop() {
|
||||
m_active = false;
|
||||
|
||||
// wake up server thread by shutting down the socket
|
||||
m_acceptor->shutdown();
|
||||
|
||||
// join server thread
|
||||
if (m_serverThread.joinable()) m_serverThread.join();
|
||||
|
||||
// close streams
|
||||
for (auto& connThread : m_connThreads) {
|
||||
if (auto thr = connThread.GetThread()) {
|
||||
if (thr->m_stream) thr->m_stream->close();
|
||||
}
|
||||
connThread.Stop();
|
||||
}
|
||||
|
||||
// wake up connection threads by forcing an empty frame to be sent
|
||||
if (auto source = GetSource()) source->Wakeup();
|
||||
}
|
||||
|
||||
// Send HTTP response and a stream of JPG-frames
|
||||
void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
if (m_noStreaming) {
|
||||
SERROR("Too many simultaneous client streams");
|
||||
SendError(os, 503, "Too many simultaneous streams");
|
||||
return;
|
||||
}
|
||||
|
||||
os.SetUnbuffered();
|
||||
|
||||
llvm::SmallString<256> header;
|
||||
llvm::raw_svector_ostream oss{header};
|
||||
|
||||
SendHeader(oss, 200, "OK", "multipart/x-mixed-replace;boundary=" BOUNDARY);
|
||||
os << oss.str();
|
||||
|
||||
SDEBUG("Headers send, sending stream now");
|
||||
|
||||
Frame::Time lastFrameTime = 0;
|
||||
Frame::Time timePerFrame = 0;
|
||||
if (m_fps != 0) timePerFrame = 1000000.0 / m_fps;
|
||||
// Allow fudge factor of 1 ms in frame rate
|
||||
if (timePerFrame >= 1000) timePerFrame -= 1000;
|
||||
|
||||
StartStream();
|
||||
while (m_active && !os.has_error()) {
|
||||
auto source = GetSource();
|
||||
if (!source) {
|
||||
// Source disconnected; sleep so we don't consume all processor time.
|
||||
os << "\r\n"; // Keep connection alive
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(0.225); // blocks
|
||||
if (!m_active) break;
|
||||
if (!frame) {
|
||||
// Bad frame; sleep for 20 ms so we don't consume all processor time.
|
||||
os << "\r\n"; // Keep connection alive
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.GetTime() < (lastFrameTime + timePerFrame)) {
|
||||
// Limit FPS; sleep for 10 ms so we don't consume all processor time
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
int width = m_width != 0 ? m_width : frame.GetOriginalWidth();
|
||||
int height = m_height != 0 ? m_height : frame.GetOriginalHeight();
|
||||
Image* image =
|
||||
frame.GetImage(width, height, VideoMode::kMJPEG, m_compression);
|
||||
if (!image) {
|
||||
// Shouldn't happen, but just in case...
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* data = image->data();
|
||||
size_t size = image->size();
|
||||
bool addDHT = false;
|
||||
size_t locSOF = size;
|
||||
switch (image->pixelFormat) {
|
||||
case VideoMode::kMJPEG:
|
||||
// Determine if we need to add DHT to it, and allocate enough space
|
||||
// for adding it if required.
|
||||
addDHT = JpegNeedsDHT(data, &size, &locSOF);
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kRGB565:
|
||||
default:
|
||||
// Bad frame; sleep for 10 ms so we don't consume all processor time.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
SDEBUG4("sending frame size=" << size << " addDHT=" << addDHT);
|
||||
|
||||
// print the individual mimetype and the length
|
||||
// sending the content-length fixes random stream disruption observed
|
||||
// with firefox
|
||||
lastFrameTime = frame.GetTime();
|
||||
double timestamp = lastFrameTime / 1000000.0;
|
||||
header.clear();
|
||||
oss << "\r\n--" BOUNDARY "\r\n"
|
||||
<< "Content-Type: image/jpeg\r\n"
|
||||
<< "Content-Length: " << size << "\r\n"
|
||||
<< "X-Timestamp: " << timestamp << "\r\n"
|
||||
<< "\r\n";
|
||||
os << oss.str();
|
||||
if (addDHT) {
|
||||
// Insert DHT data immediately before SOF
|
||||
os << llvm::StringRef(data, locSOF);
|
||||
os << JpegGetDHT();
|
||||
os << llvm::StringRef(data + locSOF, image->size() - locSOF);
|
||||
} else {
|
||||
os << llvm::StringRef(data, size);
|
||||
}
|
||||
// os.flush();
|
||||
}
|
||||
StopStream();
|
||||
}
|
||||
|
||||
void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
wpi::raw_socket_istream is{*m_stream};
|
||||
wpi::raw_socket_ostream os{*m_stream, true};
|
||||
|
||||
// Reset per-request settings
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_compression = 80;
|
||||
m_fps = 0;
|
||||
|
||||
// Read the request string from the stream
|
||||
llvm::SmallString<128> reqBuf;
|
||||
llvm::StringRef req = is.getline(reqBuf, 4096);
|
||||
if (is.has_error()) {
|
||||
SDEBUG("error getting request string");
|
||||
return;
|
||||
}
|
||||
|
||||
enum { kCommand, kStream, kGetSettings, kRootPage } kind;
|
||||
llvm::StringRef parameters;
|
||||
size_t pos;
|
||||
|
||||
SDEBUG("HTTP request: '" << req << "'\n");
|
||||
|
||||
// Determine request kind. Most of these are for mjpgstreamer
|
||||
// compatibility, others are for Axis camera compatibility.
|
||||
if ((pos = req.find("POST /stream")) != llvm::StringRef::npos) {
|
||||
kind = kStream;
|
||||
parameters = req.substr(req.find('?', pos + 12)).substr(1);
|
||||
} else if ((pos = req.find("GET /?action=stream")) != llvm::StringRef::npos) {
|
||||
kind = kStream;
|
||||
parameters = req.substr(req.find('&', pos + 19)).substr(1);
|
||||
} else if ((pos = req.find("GET /stream.mjpg")) != llvm::StringRef::npos) {
|
||||
kind = kStream;
|
||||
parameters = req.substr(req.find('?', pos + 16)).substr(1);
|
||||
} else if (req.find("GET /settings") != llvm::StringRef::npos &&
|
||||
req.find(".json") != llvm::StringRef::npos) {
|
||||
kind = kGetSettings;
|
||||
} else if (req.find("GET /input") != llvm::StringRef::npos &&
|
||||
req.find(".json") != llvm::StringRef::npos) {
|
||||
kind = kGetSettings;
|
||||
} else if (req.find("GET /output") != llvm::StringRef::npos &&
|
||||
req.find(".json") != llvm::StringRef::npos) {
|
||||
kind = kGetSettings;
|
||||
} else if ((pos = req.find("GET /?action=command")) !=
|
||||
llvm::StringRef::npos) {
|
||||
kind = kCommand;
|
||||
parameters = req.substr(req.find('&', pos + 20)).substr(1);
|
||||
} else if (req.find("GET / ") != llvm::StringRef::npos || req == "GET /\n") {
|
||||
kind = kRootPage;
|
||||
} else {
|
||||
SDEBUG("HTTP request resource not found");
|
||||
SendError(os, 404, "Resource not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parameter can only be certain characters. This also strips the EOL.
|
||||
pos = parameters.find_first_not_of(
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
|
||||
"-=&1234567890%./");
|
||||
parameters = parameters.substr(0, pos);
|
||||
SDEBUG("command parameters: \"" << parameters << "\"");
|
||||
|
||||
// Read the rest of the HTTP request.
|
||||
// The end of the request is marked by a single, empty line
|
||||
llvm::SmallString<128> lineBuf;
|
||||
for (;;) {
|
||||
if (is.getline(lineBuf, 4096).startswith("\n")) break;
|
||||
if (is.has_error()) return;
|
||||
}
|
||||
|
||||
// Send response
|
||||
switch (kind) {
|
||||
case kStream:
|
||||
if (auto source = GetSource()) {
|
||||
SDEBUG("request for stream " << source->GetName());
|
||||
if (!ProcessCommand(os, *source, parameters, false)) return;
|
||||
}
|
||||
SendStream(os);
|
||||
break;
|
||||
case kCommand:
|
||||
if (auto source = GetSource()) {
|
||||
ProcessCommand(os, *source, parameters, true);
|
||||
} else {
|
||||
SendHeader(os, 200, "OK", "text/plain");
|
||||
os << "Ignored due to no connected source."
|
||||
<< "\r\n";
|
||||
SDEBUG("Ignored due to no connected source.");
|
||||
}
|
||||
break;
|
||||
case kGetSettings:
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource())
|
||||
SendJSON(os, *source, true);
|
||||
else
|
||||
SendError(os, 404, "Resource not found");
|
||||
break;
|
||||
case kRootPage:
|
||||
SDEBUG("request for root page");
|
||||
SendHeader(os, 200, "OK", "text/html");
|
||||
if (auto source = GetSource()) {
|
||||
SendHTML(os, *source, false);
|
||||
} else {
|
||||
SendHTMLHeadTitle(os);
|
||||
os << emptyRootPage << "\r\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SDEBUG("leaving HTTP client thread");
|
||||
}
|
||||
|
||||
// worker thread for clients that connected to this server
|
||||
void MjpegServerImpl::ConnThread::Main() {
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
while (m_active) {
|
||||
while (!m_stream) {
|
||||
m_cond.wait(lock);
|
||||
if (!m_active) return;
|
||||
}
|
||||
lock.unlock();
|
||||
ProcessRequest();
|
||||
lock.lock();
|
||||
m_stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Main server thread
|
||||
void MjpegServerImpl::ServerThreadMain() {
|
||||
if (m_acceptor->start() != 0) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
SDEBUG("waiting for clients to connect");
|
||||
while (m_active) {
|
||||
auto stream = m_acceptor->accept();
|
||||
if (!stream) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
if (!m_active) return;
|
||||
|
||||
SDEBUG("client connection from " << stream->getPeerIP());
|
||||
|
||||
auto source = GetSource();
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
// Find unoccupied worker thread, or create one if necessary
|
||||
auto it = std::find_if(m_connThreads.begin(), m_connThreads.end(),
|
||||
[](const wpi::SafeThreadOwner<ConnThread>& owner) {
|
||||
auto thr = owner.GetThread();
|
||||
return !thr || !thr->m_stream;
|
||||
});
|
||||
if (it == m_connThreads.end()) {
|
||||
m_connThreads.emplace_back();
|
||||
it = std::prev(m_connThreads.end());
|
||||
}
|
||||
|
||||
// Start it if not already started
|
||||
{
|
||||
auto thr = it->GetThread();
|
||||
if (!thr) it->Start(new ConnThread{GetName()});
|
||||
}
|
||||
|
||||
auto nstreams =
|
||||
std::count_if(m_connThreads.begin(), m_connThreads.end(),
|
||||
[](const wpi::SafeThreadOwner<ConnThread>& owner) {
|
||||
auto thr = owner.GetThread();
|
||||
return thr && thr->m_streaming;
|
||||
});
|
||||
|
||||
// Hand off connection to it
|
||||
auto thr = it->GetThread();
|
||||
thr->m_stream = std::move(stream);
|
||||
thr->m_source = source;
|
||||
thr->m_noStreaming = nstreams >= 10;
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
SDEBUG("leaving server thread");
|
||||
}
|
||||
|
||||
void MjpegServerImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
for (auto& connThread : m_connThreads) {
|
||||
if (auto thr = connThread.GetThread()) {
|
||||
if (thr->m_source != source) {
|
||||
bool streaming = thr->m_streaming;
|
||||
if (thr->m_source && streaming) thr->m_source->DisableSink();
|
||||
thr->m_source = source;
|
||||
if (source && streaming) thr->m_source->EnableSink();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Sink CreateMjpegServer(llvm::StringRef name, llvm::StringRef listenAddress,
|
||||
int port, CS_Status* status) {
|
||||
llvm::SmallString<128> str{listenAddress};
|
||||
auto sink = std::make_shared<MjpegServerImpl>(
|
||||
name, listenAddress, port,
|
||||
std::unique_ptr<wpi::NetworkAcceptor>(
|
||||
new wpi::TCPAcceptor(port, str.c_str(), Logger::GetInstance())));
|
||||
auto handle = Sinks::GetInstance().Allocate(CS_SINK_MJPEG, sink);
|
||||
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
|
||||
return handle;
|
||||
}
|
||||
|
||||
std::string GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data || data->kind != CS_SINK_MJPEG) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return static_cast<MjpegServerImpl&>(*data->sink).GetListenAddress();
|
||||
}
|
||||
|
||||
int GetMjpegServerPort(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data || data->kind != CS_SINK_MJPEG) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return static_cast<MjpegServerImpl&>(*data->sink).GetPort();
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Sink CS_CreateMjpegServer(const char* name, const char* listenAddress,
|
||||
int port, CS_Status* status) {
|
||||
return cs::CreateMjpegServer(name, listenAddress, port, status);
|
||||
}
|
||||
|
||||
char* CS_GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
|
||||
return ConvertToC(cs::GetMjpegServerListenAddress(sink, status));
|
||||
}
|
||||
|
||||
int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status) {
|
||||
return cs::GetMjpegServerPort(sink, status);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
62
cscore/src/main/native/cpp/MjpegServerImpl.h
Normal file
62
cscore/src/main/native/cpp/MjpegServerImpl.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_MJPEGSERVERIMPL_H_
|
||||
#define CSCORE_MJPEGSERVERIMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/SmallVector.h>
|
||||
#include <llvm/StringRef.h>
|
||||
#include <llvm/raw_ostream.h>
|
||||
#include <support/SafeThread.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 MjpegServerImpl : public SinkImpl {
|
||||
public:
|
||||
MjpegServerImpl(llvm::StringRef name, llvm::StringRef listenAddress, int port,
|
||||
std::unique_ptr<wpi::NetworkAcceptor> acceptor);
|
||||
~MjpegServerImpl() override;
|
||||
|
||||
void Stop();
|
||||
std::string GetListenAddress() { return m_listenAddress; }
|
||||
int GetPort() { return m_port; }
|
||||
|
||||
private:
|
||||
void SetSourceImpl(std::shared_ptr<SourceImpl> source) override;
|
||||
|
||||
void ServerThreadMain();
|
||||
|
||||
class ConnThread;
|
||||
|
||||
// Never changed, so not protected by mutex
|
||||
std::string m_listenAddress;
|
||||
int m_port;
|
||||
|
||||
std::unique_ptr<wpi::NetworkAcceptor> m_acceptor;
|
||||
std::atomic_bool m_active; // set to false to terminate threads
|
||||
std::thread m_serverThread;
|
||||
|
||||
std::vector<wpi::SafeThreadOwner<ConnThread>> m_connThreads;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_MJPEGSERVERIMPL_H_
|
||||
136
cscore/src/main/native/cpp/NetworkListener.cpp
Normal file
136
cscore/src/main/native/cpp/NetworkListener.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "NetworkListener.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class NetworkListener::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
void Main();
|
||||
|
||||
#ifdef __linux__
|
||||
int m_command_fd = -1;
|
||||
#endif
|
||||
};
|
||||
|
||||
NetworkListener::~NetworkListener() { Stop(); }
|
||||
|
||||
void NetworkListener::Start() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) m_owner.Start();
|
||||
}
|
||||
|
||||
void NetworkListener::Stop() {
|
||||
// Wake up thread
|
||||
if (auto thr = m_owner.GetThread()) {
|
||||
thr->m_active = false;
|
||||
#ifdef __linux__
|
||||
if (thr->m_command_fd >= 0) eventfd_write(thr->m_command_fd, 1);
|
||||
#endif
|
||||
}
|
||||
m_owner.Stop();
|
||||
}
|
||||
|
||||
void NetworkListener::Thread::Main() {
|
||||
#ifdef __linux__
|
||||
// Create event socket so we can be shut down
|
||||
m_command_fd = ::eventfd(0, 0);
|
||||
if (m_command_fd < 0) {
|
||||
ERROR(
|
||||
"NetworkListener: could not create eventfd: " << std::strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create netlink socket
|
||||
int sd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
if (sd < 0) {
|
||||
ERROR("NetworkListener: could not create socket: " << std::strerror(errno));
|
||||
::close(m_command_fd);
|
||||
m_command_fd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind to netlink socket
|
||||
struct sockaddr_nl addr;
|
||||
std::memset(&addr, 0, sizeof(addr));
|
||||
addr.nl_family = AF_NETLINK;
|
||||
addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
|
||||
if (bind(sd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0) {
|
||||
ERROR("NetworkListener: could not create socket: " << std::strerror(errno));
|
||||
::close(sd);
|
||||
::close(m_command_fd);
|
||||
m_command_fd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
|
||||
while (m_active) {
|
||||
// select timeout
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 10;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
// select on applicable read descriptors
|
||||
fd_set readfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(m_command_fd, &readfds);
|
||||
FD_SET(sd, &readfds);
|
||||
int nfds = std::max(m_command_fd, sd) + 1;
|
||||
|
||||
if (::select(nfds, &readfds, nullptr, nullptr, &tv) < 0) {
|
||||
ERROR("NetworkListener: select(): " << std::strerror(errno));
|
||||
break; // XXX: is this the right thing to do here?
|
||||
}
|
||||
|
||||
// Double-check to see if we're shutting down
|
||||
if (!m_active) break;
|
||||
|
||||
if (!FD_ISSET(sd, &readfds)) continue;
|
||||
|
||||
std::memset(&addr, 0, sizeof(addr));
|
||||
struct iovec iov = {buf, sizeof(buf)};
|
||||
struct msghdr msg = {&addr, sizeof(addr), &iov, 1, nullptr, 0, 0};
|
||||
int len = ::recvmsg(sd, &msg, 0);
|
||||
if (len < 0) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) continue;
|
||||
ERROR(
|
||||
"NetworkListener: could not read netlink: " << std::strerror(errno));
|
||||
break; // XXX: is this the right thing to do here?
|
||||
}
|
||||
if (len == 0) continue; // EOF?
|
||||
for (struct nlmsghdr* nh = reinterpret_cast<struct nlmsghdr*>(buf);
|
||||
NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
|
||||
if (nh->nlmsg_type == NLMSG_DONE) break;
|
||||
if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK ||
|
||||
nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR) {
|
||||
Notifier::GetInstance().NotifyNetworkInterfacesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
::close(sd);
|
||||
::close(m_command_fd);
|
||||
m_command_fd = -1;
|
||||
#endif
|
||||
}
|
||||
35
cscore/src/main/native/cpp/NetworkListener.h
Normal file
35
cscore/src/main/native/cpp/NetworkListener.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_NETWORKLISTENER_H_
|
||||
#define CSCORE_NETWORKLISTENER_H_
|
||||
|
||||
#include <support/SafeThread.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
class NetworkListener {
|
||||
public:
|
||||
static NetworkListener& GetInstance() {
|
||||
static NetworkListener instance;
|
||||
return instance;
|
||||
}
|
||||
~NetworkListener();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
NetworkListener() = default;
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_NETWORKLISTENER_H_
|
||||
234
cscore/src/main/native/cpp/Notifier.cpp
Normal file
234
cscore/src/main/native/cpp/Notifier.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Notifier.h"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "SinkImpl.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
bool Notifier::s_destroyed = false;
|
||||
|
||||
namespace {
|
||||
// Vector which provides an integrated freelist for removal and reuse of
|
||||
// individual elements.
|
||||
template <typename T>
|
||||
class UidVector {
|
||||
public:
|
||||
typedef typename std::vector<T>::size_type size_type;
|
||||
|
||||
size_type size() const { return m_vector.size(); }
|
||||
T& operator[](size_type i) { return m_vector[i]; }
|
||||
const T& operator[](size_type i) const { return m_vector[i]; }
|
||||
|
||||
// Add a new T to the vector. If there are elements on the freelist,
|
||||
// reuses the last one; otherwise adds to the end of the vector.
|
||||
// Returns the resulting element index (+1).
|
||||
template <class... Args>
|
||||
unsigned int emplace_back(Args&&... args) {
|
||||
unsigned int uid;
|
||||
if (m_free.empty()) {
|
||||
uid = m_vector.size();
|
||||
m_vector.emplace_back(std::forward<Args>(args)...);
|
||||
} else {
|
||||
uid = m_free.back();
|
||||
m_free.pop_back();
|
||||
m_vector[uid] = T(std::forward<Args>(args)...);
|
||||
}
|
||||
return uid + 1;
|
||||
}
|
||||
|
||||
// Removes the identified element by replacing it with a default-constructed
|
||||
// one. The element is added to the freelist for later reuse.
|
||||
void erase(unsigned int uid) {
|
||||
--uid;
|
||||
if (uid >= m_vector.size() || !m_vector[uid]) return;
|
||||
m_free.push_back(uid);
|
||||
m_vector[uid] = T();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> m_vector;
|
||||
std::vector<unsigned int> m_free;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class Notifier::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
Thread(std::function<void()> on_start, std::function<void()> on_exit)
|
||||
: m_on_start(on_start), m_on_exit(on_exit) {}
|
||||
|
||||
void Main();
|
||||
|
||||
struct Listener {
|
||||
Listener() = default;
|
||||
Listener(std::function<void(const RawEvent& event)> callback_,
|
||||
int eventMask_)
|
||||
: callback(callback_), eventMask(eventMask_) {}
|
||||
|
||||
explicit operator bool() const { return static_cast<bool>(callback); }
|
||||
|
||||
std::string prefix;
|
||||
std::function<void(const RawEvent& event)> callback;
|
||||
int eventMask;
|
||||
};
|
||||
UidVector<Listener> m_listeners;
|
||||
|
||||
std::queue<RawEvent> m_notifications;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
};
|
||||
|
||||
Notifier::Notifier() { s_destroyed = false; }
|
||||
|
||||
Notifier::~Notifier() { s_destroyed = true; }
|
||||
|
||||
void Notifier::Start() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) m_owner.Start(new Thread(m_on_start, m_on_exit));
|
||||
}
|
||||
|
||||
void Notifier::Stop() { m_owner.Stop(); }
|
||||
|
||||
void Notifier::Thread::Main() {
|
||||
if (m_on_start) m_on_start();
|
||||
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
while (m_active) {
|
||||
while (m_notifications.empty()) {
|
||||
m_cond.wait(lock);
|
||||
if (!m_active) goto done;
|
||||
}
|
||||
|
||||
while (!m_notifications.empty()) {
|
||||
if (!m_active) goto done;
|
||||
auto item = std::move(m_notifications.front());
|
||||
m_notifications.pop();
|
||||
|
||||
// Use index because iterator might get invalidated.
|
||||
for (size_t i = 0; i < m_listeners.size(); ++i) {
|
||||
if (!m_listeners[i]) continue; // removed
|
||||
|
||||
// Event type must be within requested set for this listener.
|
||||
if ((item.kind & m_listeners[i].eventMask) == 0) continue;
|
||||
|
||||
// make a copy of the callback so we can safely release the mutex
|
||||
auto callback = m_listeners[i].callback;
|
||||
|
||||
// Don't hold mutex during callback execution!
|
||||
lock.unlock();
|
||||
callback(item);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (m_on_exit) m_on_exit();
|
||||
}
|
||||
|
||||
int Notifier::AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask) {
|
||||
Start();
|
||||
auto thr = m_owner.GetThread();
|
||||
return thr->m_listeners.emplace_back(callback, eventMask);
|
||||
}
|
||||
|
||||
void Notifier::RemoveListener(int uid) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_listeners.erase(uid);
|
||||
}
|
||||
|
||||
void Notifier::NotifySource(llvm::StringRef name, CS_Source source,
|
||||
CS_EventKind kind) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_notifications.emplace(name, source, static_cast<RawEvent::Kind>(kind));
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySource(const SourceImpl& source, CS_EventKind kind) {
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
NotifySource(source.GetName(), handleData.first, kind);
|
||||
}
|
||||
|
||||
void Notifier::NotifySourceVideoMode(const SourceImpl& source,
|
||||
const VideoMode& mode) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
|
||||
thr->m_notifications.emplace(source.GetName(), handleData.first, mode);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
|
||||
llvm::StringRef propertyName, int property,
|
||||
CS_PropertyKind propertyKind, int value,
|
||||
llvm::StringRef valueStr) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
|
||||
thr->m_notifications.emplace(
|
||||
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
|
||||
Handle{handleData.first, property, Handle::kProperty}, propertyKind,
|
||||
value, valueStr);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySink(llvm::StringRef name, CS_Sink sink,
|
||||
CS_EventKind kind) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
thr->m_notifications.emplace(name, sink, static_cast<RawEvent::Kind>(kind));
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySink(const SinkImpl& sink, CS_EventKind kind) {
|
||||
auto handleData = Sinks::GetInstance().Find(sink);
|
||||
NotifySink(sink.GetName(), handleData.first, kind);
|
||||
}
|
||||
|
||||
void Notifier::NotifySinkSourceChanged(llvm::StringRef name, CS_Sink sink,
|
||||
CS_Source source) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
RawEvent event{name, sink, RawEvent::kSinkSourceChanged};
|
||||
event.sourceHandle = source;
|
||||
|
||||
thr->m_notifications.emplace(std::move(event));
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifyNetworkInterfacesChanged() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
thr->m_notifications.emplace(RawEvent::kNetworkInterfacesChanged);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifyTelemetryUpdated() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
thr->m_notifications.emplace(RawEvent::kTelemetryUpdated);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
72
cscore/src/main/native/cpp/Notifier.h
Normal file
72
cscore/src/main/native/cpp/Notifier.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_NOTIFIER_H_
|
||||
#define CSCORE_NOTIFIER_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <support/SafeThread.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SinkImpl;
|
||||
class SourceImpl;
|
||||
|
||||
class Notifier {
|
||||
friend class NotifierTest;
|
||||
|
||||
public:
|
||||
static Notifier& GetInstance() {
|
||||
static Notifier instance;
|
||||
return instance;
|
||||
}
|
||||
~Notifier();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
static bool destroyed() { return s_destroyed; }
|
||||
|
||||
void SetOnStart(std::function<void()> on_start) { m_on_start = on_start; }
|
||||
void SetOnExit(std::function<void()> on_exit) { m_on_exit = on_exit; }
|
||||
|
||||
int AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask);
|
||||
void RemoveListener(int uid);
|
||||
|
||||
// Notification events
|
||||
void NotifySource(llvm::StringRef name, CS_Source source, CS_EventKind kind);
|
||||
void NotifySource(const SourceImpl& source, CS_EventKind kind);
|
||||
void NotifySourceVideoMode(const SourceImpl& source, const VideoMode& mode);
|
||||
void NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
|
||||
llvm::StringRef propertyName, int property,
|
||||
CS_PropertyKind propertyKind, int value,
|
||||
llvm::StringRef valueStr);
|
||||
void NotifySink(llvm::StringRef name, CS_Sink sink, CS_EventKind kind);
|
||||
void NotifySink(const SinkImpl& sink, CS_EventKind kind);
|
||||
void NotifySinkSourceChanged(llvm::StringRef name, CS_Sink sink,
|
||||
CS_Source source);
|
||||
void NotifyNetworkInterfacesChanged();
|
||||
void NotifyTelemetryUpdated();
|
||||
|
||||
private:
|
||||
Notifier();
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
static bool s_destroyed;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_NOTIFIER_H_
|
||||
76
cscore/src/main/native/cpp/PropertyImpl.h
Normal file
76
cscore/src/main/native/cpp/PropertyImpl.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_PROPERTYIMPL_H_
|
||||
#define CSCORE_PROPERTYIMPL_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/StringRef.h>
|
||||
|
||||
#include "cscore_c.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
// Property data
|
||||
class PropertyImpl {
|
||||
public:
|
||||
PropertyImpl() = default;
|
||||
explicit PropertyImpl(llvm::StringRef name_) : name{name_} {}
|
||||
PropertyImpl(llvm::StringRef name_, CS_PropertyKind kind_, int step_,
|
||||
int defaultValue_, int value_)
|
||||
: name{name_},
|
||||
propKind{kind_},
|
||||
step{step_},
|
||||
defaultValue{defaultValue_},
|
||||
value{value_} {}
|
||||
virtual ~PropertyImpl() = default;
|
||||
PropertyImpl(const PropertyImpl& oth) = delete;
|
||||
PropertyImpl& operator=(const PropertyImpl& oth) = delete;
|
||||
|
||||
void SetValue(int v) {
|
||||
if (hasMinimum && v < minimum)
|
||||
value = minimum;
|
||||
else if (hasMaximum && v > maximum)
|
||||
value = maximum;
|
||||
else
|
||||
value = v;
|
||||
valueSet = true;
|
||||
}
|
||||
|
||||
void SetValue(llvm::StringRef v) {
|
||||
valueStr = v;
|
||||
valueSet = true;
|
||||
}
|
||||
|
||||
void SetDefaultValue(int v) {
|
||||
if (hasMinimum && v < minimum)
|
||||
defaultValue = minimum;
|
||||
else if (hasMaximum && v > maximum)
|
||||
defaultValue = maximum;
|
||||
else
|
||||
defaultValue = v;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
CS_PropertyKind propKind{CS_PROP_NONE};
|
||||
bool hasMinimum{false};
|
||||
bool hasMaximum{false};
|
||||
int minimum{0};
|
||||
int maximum{100};
|
||||
int step{1};
|
||||
int defaultValue{0};
|
||||
int value{0};
|
||||
std::string valueStr;
|
||||
std::vector<std::string> enumChoices;
|
||||
bool valueSet{false};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_PROPERTYIMPL_H_
|
||||
100
cscore/src/main/native/cpp/SinkImpl.cpp
Normal file
100
cscore/src/main/native/cpp/SinkImpl.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "SinkImpl.h"
|
||||
|
||||
#include "Notifier.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
SinkImpl::SinkImpl(llvm::StringRef name) : m_name{name} {}
|
||||
|
||||
SinkImpl::~SinkImpl() {
|
||||
if (m_source) {
|
||||
if (m_enabledCount > 0) m_source->DisableSink();
|
||||
m_source->RemoveSink();
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::SetDescription(llvm::StringRef description) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_description = description;
|
||||
}
|
||||
|
||||
llvm::StringRef SinkImpl::GetDescription(
|
||||
llvm::SmallVectorImpl<char>& buf) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
buf.append(m_description.begin(), m_description.end());
|
||||
return llvm::StringRef{buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
void SinkImpl::Enable() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
++m_enabledCount;
|
||||
if (m_enabledCount == 1) {
|
||||
if (m_source) m_source->EnableSink();
|
||||
Notifier::GetInstance().NotifySink(*this, CS_SINK_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::Disable() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
--m_enabledCount;
|
||||
if (m_enabledCount == 0) {
|
||||
if (m_source) m_source->DisableSink();
|
||||
Notifier::GetInstance().NotifySink(*this, CS_SINK_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::SetEnabled(bool enabled) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (enabled && m_enabledCount == 0) {
|
||||
if (m_source) m_source->EnableSink();
|
||||
m_enabledCount = 1;
|
||||
Notifier::GetInstance().NotifySink(*this, CS_SINK_ENABLED);
|
||||
} else if (!enabled && m_enabledCount > 0) {
|
||||
if (m_source) m_source->DisableSink();
|
||||
m_enabledCount = 0;
|
||||
Notifier::GetInstance().NotifySink(*this, CS_SINK_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::SetSource(std::shared_ptr<SourceImpl> source) {
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_source == source) return;
|
||||
if (m_source) {
|
||||
if (m_enabledCount > 0) m_source->DisableSink();
|
||||
m_source->RemoveSink();
|
||||
}
|
||||
m_source = source;
|
||||
if (m_source) {
|
||||
m_source->AddSink();
|
||||
if (m_enabledCount > 0) m_source->EnableSink();
|
||||
}
|
||||
}
|
||||
SetSourceImpl(source);
|
||||
}
|
||||
|
||||
std::string SinkImpl::GetError() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (!m_source) return "no source connected";
|
||||
return m_source->GetCurFrame().GetError();
|
||||
}
|
||||
|
||||
llvm::StringRef SinkImpl::GetError(llvm::SmallVectorImpl<char>& buf) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (!m_source) return "no source connected";
|
||||
// Make a copy as it's shared data
|
||||
llvm::StringRef error = m_source->GetCurFrame().GetError();
|
||||
buf.clear();
|
||||
buf.append(error.data(), error.data() + error.size());
|
||||
return llvm::StringRef{buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
void SinkImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {}
|
||||
63
cscore/src/main/native/cpp/SinkImpl.h
Normal file
63
cscore/src/main/native/cpp/SinkImpl.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_SINKIMPL_H_
|
||||
#define CSCORE_SINKIMPL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <llvm/StringRef.h>
|
||||
#include <support/mutex.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Frame;
|
||||
|
||||
class SinkImpl {
|
||||
public:
|
||||
explicit SinkImpl(llvm::StringRef name);
|
||||
virtual ~SinkImpl();
|
||||
SinkImpl(const SinkImpl& queue) = delete;
|
||||
SinkImpl& operator=(const SinkImpl& queue) = delete;
|
||||
|
||||
llvm::StringRef GetName() const { return m_name; }
|
||||
|
||||
void SetDescription(llvm::StringRef description);
|
||||
llvm::StringRef GetDescription(llvm::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
void Enable();
|
||||
void Disable();
|
||||
void SetEnabled(bool enabled);
|
||||
|
||||
void SetSource(std::shared_ptr<SourceImpl> source);
|
||||
|
||||
std::shared_ptr<SourceImpl> GetSource() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
return m_source;
|
||||
}
|
||||
|
||||
std::string GetError() const;
|
||||
llvm::StringRef GetError(llvm::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
protected:
|
||||
virtual void SetSourceImpl(std::shared_ptr<SourceImpl> source);
|
||||
|
||||
mutable wpi::mutex m_mutex;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string m_description;
|
||||
std::shared_ptr<SourceImpl> m_source;
|
||||
int m_enabledCount{0};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_SINKIMPL_H_
|
||||
421
cscore/src/main/native/cpp/SourceImpl.cpp
Normal file
421
cscore/src/main/native/cpp/SourceImpl.cpp
Normal file
@@ -0,0 +1,421 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <llvm/STLExtras.h>
|
||||
#include <support/timestamp.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "Telemetry.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
static constexpr size_t kMaxImagesAvail = 32;
|
||||
|
||||
SourceImpl::SourceImpl(llvm::StringRef name) : m_name{name} {
|
||||
m_frame = Frame{*this, llvm::StringRef{}, 0};
|
||||
}
|
||||
|
||||
SourceImpl::~SourceImpl() {
|
||||
// Wake up anyone who is waiting. This also clears the current frame,
|
||||
// which is good because its destructor will call back into the class.
|
||||
Wakeup();
|
||||
// Set a flag so ReleaseFrame() doesn't re-add them to m_framesAvail.
|
||||
// Put in a block so we destroy before the destructor ends.
|
||||
{
|
||||
m_destroyFrames = true;
|
||||
auto frames = std::move(m_framesAvail);
|
||||
}
|
||||
// Everything else can clean up itself.
|
||||
}
|
||||
|
||||
void SourceImpl::SetDescription(llvm::StringRef description) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_description = description;
|
||||
}
|
||||
|
||||
llvm::StringRef SourceImpl::GetDescription(
|
||||
llvm::SmallVectorImpl<char>& buf) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
buf.append(m_description.begin(), m_description.end());
|
||||
return llvm::StringRef{buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
void SourceImpl::SetConnected(bool connected) {
|
||||
bool wasConnected = m_connected.exchange(connected);
|
||||
if (wasConnected && !connected)
|
||||
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_DISCONNECTED);
|
||||
else if (!wasConnected && connected)
|
||||
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_CONNECTED);
|
||||
}
|
||||
|
||||
uint64_t SourceImpl::GetCurFrameTime() {
|
||||
std::unique_lock<wpi::mutex> lock{m_frameMutex};
|
||||
return m_frame.GetTime();
|
||||
}
|
||||
|
||||
Frame SourceImpl::GetCurFrame() {
|
||||
std::unique_lock<wpi::mutex> lock{m_frameMutex};
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
Frame SourceImpl::GetNextFrame() {
|
||||
std::unique_lock<wpi::mutex> lock{m_frameMutex};
|
||||
auto oldTime = m_frame.GetTime();
|
||||
m_frameCv.wait(lock, [=] { return m_frame.GetTime() != oldTime; });
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
Frame SourceImpl::GetNextFrame(double timeout) {
|
||||
std::unique_lock<wpi::mutex> lock{m_frameMutex};
|
||||
auto oldTime = m_frame.GetTime();
|
||||
if (!m_frameCv.wait_for(
|
||||
lock, std::chrono::milliseconds(static_cast<int>(timeout * 1000)),
|
||||
[=] { return m_frame.GetTime() != oldTime; })) {
|
||||
m_frame = Frame{*this, "timed out getting frame", wpi::Now()};
|
||||
}
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
void SourceImpl::Wakeup() {
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock{m_frameMutex};
|
||||
m_frame = Frame{*this, llvm::StringRef{}, 0};
|
||||
}
|
||||
m_frameCv.notify_all();
|
||||
}
|
||||
|
||||
int SourceImpl::GetPropertyIndex(llvm::StringRef name) const {
|
||||
// We can't fail, so instead we create a new index if caching fails.
|
||||
CS_Status status = 0;
|
||||
if (!m_properties_cached) CacheProperties(&status);
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
int& ndx = m_properties[name];
|
||||
if (ndx == 0) {
|
||||
// create a new index
|
||||
ndx = m_propertyData.size() + 1;
|
||||
m_propertyData.emplace_back(CreateEmptyProperty(name));
|
||||
}
|
||||
return ndx;
|
||||
}
|
||||
|
||||
llvm::ArrayRef<int> SourceImpl::EnumerateProperties(
|
||||
llvm::SmallVectorImpl<int>& vec, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
return llvm::ArrayRef<int>{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
for (int i = 0; i < static_cast<int>(m_propertyData.size()); ++i) {
|
||||
if (m_propertyData[i]) vec.push_back(i + 1);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
CS_PropertyKind SourceImpl::GetPropertyKind(int property) const {
|
||||
CS_Status status = 0;
|
||||
if (!m_properties_cached && !CacheProperties(&status)) return CS_PROP_NONE;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) return CS_PROP_NONE;
|
||||
return prop->propKind;
|
||||
}
|
||||
|
||||
llvm::StringRef SourceImpl::GetPropertyName(int property,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
return llvm::StringRef{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
// safe to not copy because we never modify it after caching
|
||||
return prop->name;
|
||||
}
|
||||
|
||||
int SourceImpl::GetProperty(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
if ((prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) ==
|
||||
0) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return 0;
|
||||
}
|
||||
return prop->value;
|
||||
}
|
||||
|
||||
int SourceImpl::GetPropertyMin(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
return prop->minimum;
|
||||
}
|
||||
|
||||
int SourceImpl::GetPropertyMax(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
return prop->maximum;
|
||||
}
|
||||
|
||||
int SourceImpl::GetPropertyStep(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
return prop->step;
|
||||
}
|
||||
|
||||
int SourceImpl::GetPropertyDefault(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
return prop->defaultValue;
|
||||
}
|
||||
|
||||
llvm::StringRef SourceImpl::GetStringProperty(int property,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
return llvm::StringRef{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
if (prop->propKind != CS_PROP_STRING) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
buf.clear();
|
||||
buf.append(prop->valueStr.begin(), prop->valueStr.end());
|
||||
return llvm::StringRef(buf.data(), buf.size());
|
||||
}
|
||||
|
||||
std::vector<std::string> SourceImpl::GetEnumPropertyChoices(
|
||||
int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
return std::vector<std::string>{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return std::vector<std::string>{};
|
||||
}
|
||||
if (prop->propKind != CS_PROP_ENUM) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return std::vector<std::string>{};
|
||||
}
|
||||
return prop->enumChoices;
|
||||
}
|
||||
|
||||
VideoMode SourceImpl::GetVideoMode(CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return VideoMode{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
return m_mode;
|
||||
}
|
||||
|
||||
bool SourceImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat,
|
||||
CS_Status* status) {
|
||||
auto mode = GetVideoMode(status);
|
||||
if (!mode) return false;
|
||||
mode.pixelFormat = pixelFormat;
|
||||
return SetVideoMode(mode, status);
|
||||
}
|
||||
|
||||
bool SourceImpl::SetResolution(int width, int height, CS_Status* status) {
|
||||
auto mode = GetVideoMode(status);
|
||||
if (!mode) return false;
|
||||
mode.width = width;
|
||||
mode.height = height;
|
||||
return SetVideoMode(mode, status);
|
||||
}
|
||||
|
||||
bool SourceImpl::SetFPS(int fps, CS_Status* status) {
|
||||
auto mode = GetVideoMode(status);
|
||||
if (!mode) return false;
|
||||
mode.fps = fps;
|
||||
return SetVideoMode(mode, status);
|
||||
}
|
||||
|
||||
std::vector<VideoMode> SourceImpl::EnumerateVideoModes(
|
||||
CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
return std::vector<VideoMode>{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
return m_videoModes;
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> SourceImpl::AllocImage(
|
||||
VideoMode::PixelFormat pixelFormat, int width, int height, size_t size) {
|
||||
std::unique_ptr<Image> image;
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock{m_poolMutex};
|
||||
// find the smallest existing frame that is at least big enough.
|
||||
int found = -1;
|
||||
for (size_t i = 0; i < m_imagesAvail.size(); ++i) {
|
||||
// is it big enough?
|
||||
if (m_imagesAvail[i] && m_imagesAvail[i]->capacity() >= size) {
|
||||
// is it smaller than the last found?
|
||||
if (found < 0 ||
|
||||
m_imagesAvail[i]->capacity() < m_imagesAvail[found]->capacity()) {
|
||||
// yes, update
|
||||
found = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if nothing found, allocate a new buffer
|
||||
if (found < 0)
|
||||
image.reset(new Image{size});
|
||||
else
|
||||
image = std::move(m_imagesAvail[found]);
|
||||
}
|
||||
|
||||
// Initialize image
|
||||
image->SetSize(size);
|
||||
image->pixelFormat = pixelFormat;
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void SourceImpl::PutFrame(VideoMode::PixelFormat pixelFormat, int width,
|
||||
int height, llvm::StringRef data, Frame::Time time) {
|
||||
auto image = AllocImage(pixelFormat, width, height, data.size());
|
||||
|
||||
// Copy in image data
|
||||
SDEBUG4("Copying data to "
|
||||
<< reinterpret_cast<const void*>(image->data()) << " from "
|
||||
<< reinterpret_cast<const void*>(data.data()) << " (" << data.size()
|
||||
<< " bytes)");
|
||||
std::memcpy(image->data(), data.data(), data.size());
|
||||
|
||||
PutFrame(std::move(image), time);
|
||||
}
|
||||
|
||||
void SourceImpl::PutFrame(std::unique_ptr<Image> image, Frame::Time time) {
|
||||
// Update telemetry
|
||||
Telemetry::GetInstance().RecordSourceFrames(*this, 1);
|
||||
Telemetry::GetInstance().RecordSourceBytes(*this,
|
||||
static_cast<int>(image->size()));
|
||||
|
||||
// Update frame
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock{m_frameMutex};
|
||||
m_frame = Frame{*this, std::move(image), time};
|
||||
}
|
||||
|
||||
// Signal listeners
|
||||
m_frameCv.notify_all();
|
||||
}
|
||||
|
||||
void SourceImpl::PutError(llvm::StringRef msg, Frame::Time time) {
|
||||
// Update frame
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock{m_frameMutex};
|
||||
m_frame = Frame{*this, msg, time};
|
||||
}
|
||||
|
||||
// Signal listeners
|
||||
m_frameCv.notify_all();
|
||||
}
|
||||
|
||||
void SourceImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) {
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, prop.name,
|
||||
propIndex, prop.propKind, prop.value,
|
||||
prop.valueStr);
|
||||
// also notify choices updated event for enum types
|
||||
if (prop.propKind == CS_PROP_ENUM)
|
||||
notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
|
||||
prop.name, propIndex, prop.propKind,
|
||||
prop.value, llvm::StringRef{});
|
||||
}
|
||||
|
||||
void SourceImpl::UpdatePropertyValue(int property, bool setString, int value,
|
||||
llvm::StringRef valueStr) {
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) return;
|
||||
|
||||
if (setString)
|
||||
prop->SetValue(valueStr);
|
||||
else
|
||||
prop->SetValue(value);
|
||||
|
||||
// Only notify updates after we've notified created
|
||||
if (m_properties_cached)
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_VALUE_UPDATED, prop->name, property,
|
||||
prop->propKind, prop->value, prop->valueStr);
|
||||
}
|
||||
|
||||
void SourceImpl::ReleaseImage(std::unique_ptr<Image> image) {
|
||||
std::lock_guard<wpi::mutex> lock{m_poolMutex};
|
||||
if (m_destroyFrames) return;
|
||||
// Return the frame to the pool. First try to find an empty slot, otherwise
|
||||
// add it to the end.
|
||||
auto it = std::find(m_imagesAvail.begin(), m_imagesAvail.end(), nullptr);
|
||||
if (it != m_imagesAvail.end()) {
|
||||
*it = std::move(image);
|
||||
} else if (m_imagesAvail.size() > kMaxImagesAvail) {
|
||||
// Replace smallest buffer; don't need to check for null because the above
|
||||
// find would have found it.
|
||||
auto it2 = std::min_element(
|
||||
m_imagesAvail.begin(), m_imagesAvail.end(),
|
||||
[](const std::unique_ptr<Image>& a, const std::unique_ptr<Image>& b) {
|
||||
return a->capacity() < b->capacity();
|
||||
});
|
||||
if ((*it2)->capacity() < image->capacity()) *it2 = std::move(image);
|
||||
} else {
|
||||
m_imagesAvail.emplace_back(std::move(image));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Frame::Impl> SourceImpl::AllocFrameImpl() {
|
||||
std::lock_guard<wpi::mutex> lock{m_poolMutex};
|
||||
|
||||
if (m_framesAvail.empty()) return llvm::make_unique<Frame::Impl>(*this);
|
||||
|
||||
auto impl = std::move(m_framesAvail.back());
|
||||
m_framesAvail.pop_back();
|
||||
return impl;
|
||||
}
|
||||
|
||||
void SourceImpl::ReleaseFrameImpl(std::unique_ptr<Frame::Impl> impl) {
|
||||
std::lock_guard<wpi::mutex> lock{m_poolMutex};
|
||||
if (m_destroyFrames) return;
|
||||
m_framesAvail.push_back(std::move(impl));
|
||||
}
|
||||
223
cscore/src/main/native/cpp/SourceImpl.h
Normal file
223
cscore/src/main/native/cpp/SourceImpl.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_SOURCEIMPL_H_
|
||||
#define CSCORE_SOURCEIMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/ArrayRef.h>
|
||||
#include <llvm/StringMap.h>
|
||||
#include <llvm/StringRef.h>
|
||||
#include <support/condition_variable.h>
|
||||
#include <support/mutex.h>
|
||||
|
||||
#include "Frame.h"
|
||||
#include "Image.h"
|
||||
#include "PropertyImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SourceImpl {
|
||||
friend class Frame;
|
||||
|
||||
public:
|
||||
explicit SourceImpl(llvm::StringRef name);
|
||||
virtual ~SourceImpl();
|
||||
SourceImpl(const SourceImpl& oth) = delete;
|
||||
SourceImpl& operator=(const SourceImpl& oth) = delete;
|
||||
|
||||
llvm::StringRef GetName() const { return m_name; }
|
||||
|
||||
void SetDescription(llvm::StringRef description);
|
||||
llvm::StringRef GetDescription(llvm::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
void SetConnected(bool connected);
|
||||
bool IsConnected() const { return m_connected; }
|
||||
|
||||
// Functions to keep track of the overall number of sinks connected to this
|
||||
// source. Primarily used by sinks to determine if other sinks are using
|
||||
// the same source.
|
||||
int GetNumSinks() const { return m_numSinks; }
|
||||
void AddSink() {
|
||||
++m_numSinks;
|
||||
NumSinksChanged();
|
||||
}
|
||||
void RemoveSink() {
|
||||
--m_numSinks;
|
||||
NumSinksChanged();
|
||||
}
|
||||
|
||||
// Functions to keep track of the number of sinks connected to this source
|
||||
// that are "enabled", in other words, listening for new images. Primarily
|
||||
// used by sources to determine whether they should actually bother trying
|
||||
// to get source frames.
|
||||
int GetNumSinksEnabled() const { return m_numSinksEnabled; }
|
||||
|
||||
void EnableSink() {
|
||||
++m_numSinksEnabled;
|
||||
NumSinksEnabledChanged();
|
||||
}
|
||||
|
||||
void DisableSink() {
|
||||
--m_numSinksEnabled;
|
||||
NumSinksEnabledChanged();
|
||||
}
|
||||
|
||||
// Gets the current frame time (without waiting for a new one).
|
||||
uint64_t GetCurFrameTime();
|
||||
|
||||
// Gets the current frame (without waiting for a new one).
|
||||
Frame GetCurFrame();
|
||||
|
||||
// Blocking function that waits for the next frame and returns it.
|
||||
Frame GetNextFrame();
|
||||
|
||||
// Blocking function that waits for the next frame and returns it (with
|
||||
// timeout in seconds). If timeout expires, returns empty frame.
|
||||
Frame GetNextFrame(double timeout);
|
||||
|
||||
// Force a wakeup of all GetNextFrame() callers by sending an empty frame.
|
||||
void Wakeup();
|
||||
|
||||
// Property functions
|
||||
int GetPropertyIndex(llvm::StringRef name) const;
|
||||
llvm::ArrayRef<int> EnumerateProperties(llvm::SmallVectorImpl<int>& vec,
|
||||
CS_Status* status) const;
|
||||
CS_PropertyKind GetPropertyKind(int property) const;
|
||||
llvm::StringRef GetPropertyName(int property,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) const;
|
||||
int GetProperty(int property, CS_Status* status) const;
|
||||
virtual void SetProperty(int property, int value, CS_Status* status) = 0;
|
||||
int GetPropertyMin(int property, CS_Status* status) const;
|
||||
int GetPropertyMax(int property, CS_Status* status) const;
|
||||
int GetPropertyStep(int property, CS_Status* status) const;
|
||||
int GetPropertyDefault(int property, CS_Status* status) const;
|
||||
llvm::StringRef GetStringProperty(int property,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) const;
|
||||
virtual void SetStringProperty(int property, llvm::StringRef value,
|
||||
CS_Status* status) = 0;
|
||||
std::vector<std::string> GetEnumPropertyChoices(int property,
|
||||
CS_Status* status) const;
|
||||
|
||||
// Standard common camera properties
|
||||
virtual void SetBrightness(int brightness, CS_Status* status) = 0;
|
||||
virtual int GetBrightness(CS_Status* status) const = 0;
|
||||
virtual void SetWhiteBalanceAuto(CS_Status* status) = 0;
|
||||
virtual void SetWhiteBalanceHoldCurrent(CS_Status* status) = 0;
|
||||
virtual void SetWhiteBalanceManual(int value, CS_Status* status) = 0;
|
||||
virtual void SetExposureAuto(CS_Status* status) = 0;
|
||||
virtual void SetExposureHoldCurrent(CS_Status* status) = 0;
|
||||
virtual void SetExposureManual(int value, CS_Status* status) = 0;
|
||||
|
||||
// Video mode functions
|
||||
VideoMode GetVideoMode(CS_Status* status) const;
|
||||
virtual bool SetVideoMode(const VideoMode& mode, CS_Status* status) = 0;
|
||||
|
||||
// These have default implementations but can be overridden for custom
|
||||
// or optimized behavior.
|
||||
virtual bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
|
||||
CS_Status* status);
|
||||
virtual bool SetResolution(int width, int height, CS_Status* status);
|
||||
virtual bool SetFPS(int fps, CS_Status* status);
|
||||
|
||||
std::vector<VideoMode> EnumerateVideoModes(CS_Status* status) const;
|
||||
|
||||
std::unique_ptr<Image> AllocImage(VideoMode::PixelFormat pixelFormat,
|
||||
int width, int height, size_t size);
|
||||
|
||||
protected:
|
||||
void PutFrame(VideoMode::PixelFormat pixelFormat, int width, int height,
|
||||
llvm::StringRef data, Frame::Time time);
|
||||
void PutFrame(std::unique_ptr<Image> image, Frame::Time time);
|
||||
void PutError(llvm::StringRef msg, Frame::Time time);
|
||||
|
||||
// Notification functions for corresponding atomics
|
||||
virtual void NumSinksChanged() = 0;
|
||||
virtual void NumSinksEnabledChanged() = 0;
|
||||
|
||||
std::atomic_int m_numSinks{0};
|
||||
std::atomic_int m_numSinksEnabled{0};
|
||||
|
||||
protected:
|
||||
// Get a property; must be called with m_mutex held.
|
||||
PropertyImpl* GetProperty(int property) {
|
||||
if (property <= 0 || static_cast<size_t>(property) > m_propertyData.size())
|
||||
return nullptr;
|
||||
return m_propertyData[property - 1].get();
|
||||
}
|
||||
const PropertyImpl* GetProperty(int property) const {
|
||||
if (property <= 0 || static_cast<size_t>(property) > m_propertyData.size())
|
||||
return nullptr;
|
||||
return m_propertyData[property - 1].get();
|
||||
}
|
||||
|
||||
// Create an "empty" property. This is called by GetPropertyIndex to create
|
||||
// properties that don't exist (as GetPropertyIndex can't fail).
|
||||
// Note: called with m_mutex held.
|
||||
virtual std::unique_ptr<PropertyImpl> CreateEmptyProperty(
|
||||
llvm::StringRef name) const = 0;
|
||||
|
||||
// Cache properties. Implementations must return false and set status to
|
||||
// CS_SOURCE_IS_DISCONNECTED if not possible to cache.
|
||||
virtual bool CacheProperties(CS_Status* status) const = 0;
|
||||
|
||||
void NotifyPropertyCreated(int propIndex, PropertyImpl& prop);
|
||||
|
||||
// Update property value; must be called with m_mutex held.
|
||||
void UpdatePropertyValue(int property, bool setString, int value,
|
||||
llvm::StringRef valueStr);
|
||||
|
||||
// Cached properties and video modes (protected with m_mutex)
|
||||
mutable std::vector<std::unique_ptr<PropertyImpl>> m_propertyData;
|
||||
mutable llvm::StringMap<int> m_properties;
|
||||
mutable std::vector<VideoMode> m_videoModes;
|
||||
// Current video mode
|
||||
mutable VideoMode m_mode;
|
||||
// Whether CacheProperties() has been successful at least once (and thus
|
||||
// should not be called again)
|
||||
mutable std::atomic_bool m_properties_cached{false};
|
||||
|
||||
mutable wpi::mutex m_mutex;
|
||||
|
||||
private:
|
||||
void ReleaseImage(std::unique_ptr<Image> image);
|
||||
std::unique_ptr<Frame::Impl> AllocFrameImpl();
|
||||
void ReleaseFrameImpl(std::unique_ptr<Frame::Impl> data);
|
||||
|
||||
std::string m_name;
|
||||
std::string m_description;
|
||||
|
||||
wpi::mutex m_frameMutex;
|
||||
wpi::condition_variable m_frameCv;
|
||||
|
||||
bool m_destroyFrames{false};
|
||||
|
||||
// Pool of frames/images to reduce malloc traffic.
|
||||
wpi::mutex m_poolMutex;
|
||||
std::vector<std::unique_ptr<Frame::Impl>> m_framesAvail;
|
||||
std::vector<std::unique_ptr<Image>> m_imagesAvail;
|
||||
|
||||
std::atomic_bool m_connected{false};
|
||||
|
||||
// Most recent frame (returned to callers of GetNextFrame)
|
||||
// Access protected by m_frameMutex.
|
||||
// MUST be located below m_poolMutex as the Frame destructor calls back
|
||||
// into SourceImpl::ReleaseImage, which locks m_poolMutex.
|
||||
Frame m_frame;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_SOURCEIMPL_H_
|
||||
136
cscore/src/main/native/cpp/Telemetry.cpp
Normal file
136
cscore/src/main/native/cpp/Telemetry.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Telemetry.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
|
||||
#include <llvm/DenseMap.h>
|
||||
#include <support/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Notifier.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class Telemetry::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
void Main();
|
||||
|
||||
llvm::DenseMap<std::pair<CS_Handle, int>, int64_t> m_user;
|
||||
llvm::DenseMap<std::pair<CS_Handle, int>, int64_t> m_current;
|
||||
double m_period = 0.0;
|
||||
double m_elapsed = 0.0;
|
||||
bool m_updated = false;
|
||||
int64_t GetValue(CS_Handle handle, CS_TelemetryKind kind, CS_Status* status);
|
||||
};
|
||||
|
||||
int64_t Telemetry::Thread::GetValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
auto it = m_user.find(std::make_pair(handle, static_cast<int>(kind)));
|
||||
if (it == m_user.end()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
return 0;
|
||||
}
|
||||
return it->getSecond();
|
||||
}
|
||||
|
||||
Telemetry::Telemetry() {}
|
||||
|
||||
Telemetry::~Telemetry() {}
|
||||
|
||||
void Telemetry::Start() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) m_owner.Start(new Thread);
|
||||
}
|
||||
|
||||
void Telemetry::Stop() { m_owner.Stop(); }
|
||||
|
||||
void Telemetry::Thread::Main() {
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
auto prevTime = std::chrono::steady_clock::now();
|
||||
while (m_active) {
|
||||
double period = m_period;
|
||||
if (period == 0) period = 1000.0;
|
||||
auto timeoutTime = prevTime + std::chrono::duration<double>(period);
|
||||
while (m_active && !m_updated) {
|
||||
if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout)
|
||||
break;
|
||||
}
|
||||
if (!m_active) break;
|
||||
if (m_updated) {
|
||||
m_updated = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// move to user and clear current, as we don't keep around old values
|
||||
m_user = std::move(m_current);
|
||||
m_current.clear();
|
||||
auto curTime = std::chrono::steady_clock::now();
|
||||
m_elapsed = std::chrono::duration<double>(curTime - prevTime).count();
|
||||
prevTime = curTime;
|
||||
|
||||
// notify
|
||||
Notifier::GetInstance().NotifyTelemetryUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void Telemetry::SetPeriod(double seconds) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
if (thr->m_period == seconds) return; // no change
|
||||
thr->m_period = seconds;
|
||||
thr->m_updated = true;
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
double Telemetry::GetElapsedTime() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return 0;
|
||||
return thr->m_elapsed;
|
||||
}
|
||||
|
||||
int64_t Telemetry::GetValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
*status = CS_TELEMETRY_NOT_ENABLED;
|
||||
return 0;
|
||||
}
|
||||
return thr->GetValue(handle, kind, status);
|
||||
}
|
||||
|
||||
double Telemetry::GetAverageValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
*status = CS_TELEMETRY_NOT_ENABLED;
|
||||
return 0;
|
||||
}
|
||||
if (thr->m_elapsed == 0) return 0.0;
|
||||
return thr->GetValue(handle, kind, status) / thr->m_elapsed;
|
||||
}
|
||||
|
||||
void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
|
||||
static_cast<int>(CS_SOURCE_BYTES_RECEIVED))] +=
|
||||
quantity;
|
||||
}
|
||||
|
||||
void Telemetry::RecordSourceFrames(const SourceImpl& source, int quantity) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
|
||||
static_cast<int>(CS_SOURCE_FRAMES_RECEIVED))] +=
|
||||
quantity;
|
||||
}
|
||||
52
cscore/src/main/native/cpp/Telemetry.h
Normal file
52
cscore/src/main/native/cpp/Telemetry.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_TELEMETRY_H_
|
||||
#define CSCORE_TELEMETRY_H_
|
||||
|
||||
#include <support/SafeThread.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SourceImpl;
|
||||
|
||||
class Telemetry {
|
||||
friend class TelemetryTest;
|
||||
|
||||
public:
|
||||
static Telemetry& GetInstance() {
|
||||
static Telemetry instance;
|
||||
return instance;
|
||||
}
|
||||
~Telemetry();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
// User interface
|
||||
void SetPeriod(double seconds);
|
||||
double GetElapsedTime();
|
||||
int64_t GetValue(CS_Handle handle, CS_TelemetryKind kind, CS_Status* status);
|
||||
double GetAverageValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status);
|
||||
|
||||
// Telemetry events
|
||||
void RecordSourceBytes(const SourceImpl& source, int quantity);
|
||||
void RecordSourceFrames(const SourceImpl& source, int quantity);
|
||||
|
||||
private:
|
||||
Telemetry();
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_TELEMETRY_H_
|
||||
182
cscore/src/main/native/cpp/UnlimitedHandleResource.h
Normal file
182
cscore/src/main/native/cpp/UnlimitedHandleResource.h
Normal file
@@ -0,0 +1,182 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_UNLIMITEDHANDLERESOURCE_H_
|
||||
#define CSCORE_UNLIMITEDHANDLERESOURCE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/ArrayRef.h>
|
||||
#include <llvm/SmallVector.h>
|
||||
#include <support/mutex.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
// The UnlimitedHandleResource class is a way to track handles. This version
|
||||
// allows an unlimted number of handles that are allocated sequentially. When
|
||||
// possible, indices are reused to save memory usage and keep the array length
|
||||
// down.
|
||||
// However, automatic array management has not been implemented, but might be in
|
||||
// the future.
|
||||
// Because we have to loop through the allocator, we must use a global mutex.
|
||||
//
|
||||
// THandle needs to have the following attributes:
|
||||
// Type : enum or typedef
|
||||
// kIndexMax : static, constexpr, or enum value for the maximum index value
|
||||
// int GetTypedIndex() const : function that returns the index of the handle
|
||||
// THandle(int index, HandleType[int] type) : constructor for index and type
|
||||
//
|
||||
// @tparam THandle The Handle Type
|
||||
// @tparam TStruct The struct type held by this resource
|
||||
// @tparam typeValue The type value stored in the handle
|
||||
// @tparam TMutex The mutex type to use
|
||||
template <typename THandle, typename TStruct, int typeValue,
|
||||
typename TMutex = wpi::mutex>
|
||||
class UnlimitedHandleResource {
|
||||
public:
|
||||
UnlimitedHandleResource(const UnlimitedHandleResource&) = delete;
|
||||
UnlimitedHandleResource operator=(const UnlimitedHandleResource&) = delete;
|
||||
UnlimitedHandleResource() = default;
|
||||
|
||||
template <typename... Args>
|
||||
THandle Allocate(Args&&... args);
|
||||
THandle Allocate(std::shared_ptr<THandle> structure);
|
||||
|
||||
std::shared_ptr<TStruct> Get(THandle handle);
|
||||
|
||||
void Free(THandle handle);
|
||||
|
||||
template <typename T>
|
||||
llvm::ArrayRef<T> GetAll(llvm::SmallVectorImpl<T>& vec);
|
||||
|
||||
// @param func functor with (THandle, const TStruct&) parameters
|
||||
template <typename F>
|
||||
void ForEach(F func);
|
||||
|
||||
// @pram func functor with (const TStruct&) parameter and bool return value
|
||||
template <typename F>
|
||||
std::pair<THandle, std::shared_ptr<TStruct>> FindIf(F func);
|
||||
|
||||
private:
|
||||
THandle MakeHandle(size_t i) {
|
||||
return THandle{static_cast<int>(i),
|
||||
static_cast<typename THandle::Type>(typeValue)};
|
||||
}
|
||||
std::vector<std::shared_ptr<TStruct>> m_structures;
|
||||
TMutex m_handleMutex;
|
||||
};
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
template <typename... Args>
|
||||
THandle UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Allocate(
|
||||
Args&&... args) {
|
||||
std::lock_guard<TMutex> sync(m_handleMutex);
|
||||
size_t i;
|
||||
for (i = 0; i < m_structures.size(); i++) {
|
||||
if (m_structures[i] == nullptr) {
|
||||
m_structures[i] = std::make_shared<TStruct>(std::forward<Args>(args)...);
|
||||
return MakeHandle(i);
|
||||
}
|
||||
}
|
||||
if (i >= THandle::kIndexMax) return 0;
|
||||
|
||||
m_structures.emplace_back(
|
||||
std::make_shared<TStruct>(std::forward<Args>(args)...));
|
||||
return MakeHandle(i);
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
THandle UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Allocate(
|
||||
std::shared_ptr<THandle> structure) {
|
||||
std::lock_guard<TMutex> sync(m_handleMutex);
|
||||
size_t i;
|
||||
for (i = 0; i < m_structures.size(); i++) {
|
||||
if (m_structures[i] == nullptr) {
|
||||
m_structures[i] = structure;
|
||||
return MakeHandle(i);
|
||||
}
|
||||
}
|
||||
if (i >= THandle::kIndexMax) return 0;
|
||||
|
||||
m_structures.push_back(structure);
|
||||
return MakeHandle(i);
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
inline std::shared_ptr<TStruct>
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Get(
|
||||
THandle handle) {
|
||||
auto index =
|
||||
handle.GetTypedIndex(static_cast<typename THandle::Type>(typeValue));
|
||||
if (index < 0) return nullptr;
|
||||
std::lock_guard<TMutex> sync(m_handleMutex);
|
||||
if (index >= static_cast<int>(m_structures.size())) return nullptr;
|
||||
return m_structures[index];
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
inline void UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
|
||||
THandle handle) {
|
||||
auto index =
|
||||
handle.GetTypedIndex(static_cast<typename THandle::Type>(typeValue));
|
||||
if (index < 0) return;
|
||||
std::lock_guard<TMutex> sync(m_handleMutex);
|
||||
if (index >= static_cast<int>(m_structures.size())) return;
|
||||
m_structures[index].reset();
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
template <typename T>
|
||||
inline llvm::ArrayRef<T>
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
|
||||
llvm::SmallVectorImpl<T>& vec) {
|
||||
ForEach([&](THandle handle, const TStruct& data) { vec.push_back(handle); });
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
template <typename F>
|
||||
inline void
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::ForEach(F func) {
|
||||
std::lock_guard<TMutex> sync(m_handleMutex);
|
||||
for (size_t i = 0; i < m_structures.size(); i++) {
|
||||
if (m_structures[i] != nullptr) func(MakeHandle(i), *(m_structures[i]));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
template <typename F>
|
||||
inline std::pair<THandle, std::shared_ptr<TStruct>>
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FindIf(F func) {
|
||||
std::lock_guard<TMutex> sync(m_handleMutex);
|
||||
for (size_t i = 0; i < m_structures.size(); i++) {
|
||||
auto& structure = m_structures[i];
|
||||
if (structure != nullptr && func(*structure))
|
||||
return std::make_pair(MakeHandle(i), structure);
|
||||
}
|
||||
return std::make_pair(0, nullptr);
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue,
|
||||
typename TMutex = wpi::mutex>
|
||||
class StaticUnlimitedHandleResource
|
||||
: public UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex> {
|
||||
public:
|
||||
static StaticUnlimitedHandleResource& GetInstance() {
|
||||
static StaticUnlimitedHandleResource instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private:
|
||||
StaticUnlimitedHandleResource() = default;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_UNLIMITEDHANDLERESOURCE_H_
|
||||
60
cscore/src/main/native/cpp/UsbCameraBuffer.h
Normal file
60
cscore/src/main/native/cpp/UsbCameraBuffer.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_USBCAMERABUFFER_H_
|
||||
#define CSCORE_USBCAMERABUFFER_H_
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace cs {
|
||||
|
||||
class UsbCameraBuffer {
|
||||
public:
|
||||
UsbCameraBuffer() noexcept : m_data{nullptr}, m_length{0} {}
|
||||
UsbCameraBuffer(UsbCameraBuffer&& other) noexcept : UsbCameraBuffer() {
|
||||
swap(*this, other);
|
||||
}
|
||||
UsbCameraBuffer& operator=(UsbCameraBuffer&& other) noexcept {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
UsbCameraBuffer(const UsbCameraBuffer&) = delete;
|
||||
UsbCameraBuffer& operator=(const UsbCameraBuffer&) = delete;
|
||||
|
||||
#ifdef __linux__
|
||||
UsbCameraBuffer(int fd, size_t length, off_t offset) noexcept
|
||||
: m_length{length} {
|
||||
m_data =
|
||||
mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
|
||||
if (m_data == MAP_FAILED) {
|
||||
m_data = nullptr;
|
||||
m_length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
~UsbCameraBuffer() {
|
||||
if (m_data) munmap(m_data, m_length);
|
||||
}
|
||||
#endif
|
||||
|
||||
friend void swap(UsbCameraBuffer& first, UsbCameraBuffer& second) noexcept {
|
||||
using std::swap;
|
||||
swap(first.m_data, second.m_data);
|
||||
swap(first.m_length, second.m_length);
|
||||
}
|
||||
|
||||
void* m_data;
|
||||
size_t m_length;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_USBCAMERABUFFER_H_
|
||||
1368
cscore/src/main/native/cpp/UsbCameraImpl.cpp
Normal file
1368
cscore/src/main/native/cpp/UsbCameraImpl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
183
cscore/src/main/native/cpp/UsbCameraImpl.h
Normal file
183
cscore/src/main/native/cpp/UsbCameraImpl.h
Normal file
@@ -0,0 +1,183 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_USBCAMERAIMPL_H_
|
||||
#define CSCORE_USBCAMERAIMPL_H_
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/videodev2.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/STLExtras.h>
|
||||
#include <llvm/SmallVector.h>
|
||||
#include <llvm/raw_ostream.h>
|
||||
#include <support/condition_variable.h>
|
||||
#include <support/mutex.h>
|
||||
#include <support/raw_istream.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
#include "UsbCameraBuffer.h"
|
||||
#include "UsbCameraProperty.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class UsbCameraImpl : public SourceImpl {
|
||||
public:
|
||||
UsbCameraImpl(llvm::StringRef name, llvm::StringRef path);
|
||||
~UsbCameraImpl() override;
|
||||
|
||||
void Start();
|
||||
|
||||
// Property functions
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, llvm::StringRef value,
|
||||
CS_Status* status) override;
|
||||
|
||||
// Standard common camera properties
|
||||
void SetBrightness(int brightness, CS_Status* status) override;
|
||||
int GetBrightness(CS_Status* status) const override;
|
||||
void SetWhiteBalanceAuto(CS_Status* status) override;
|
||||
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
|
||||
void SetWhiteBalanceManual(int value, CS_Status* status) override;
|
||||
void SetExposureAuto(CS_Status* status) override;
|
||||
void SetExposureHoldCurrent(CS_Status* status) override;
|
||||
void SetExposureManual(int value, CS_Status* status) override;
|
||||
|
||||
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
|
||||
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
|
||||
CS_Status* status) override;
|
||||
bool SetResolution(int width, int height, CS_Status* status) override;
|
||||
bool SetFPS(int fps, CS_Status* status) override;
|
||||
|
||||
void NumSinksChanged() override;
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
std::string GetPath() { return m_path; }
|
||||
|
||||
// Messages passed to/from camera thread
|
||||
struct Message {
|
||||
enum Kind {
|
||||
kNone = 0,
|
||||
kCmdSetMode,
|
||||
kCmdSetPixelFormat,
|
||||
kCmdSetResolution,
|
||||
kCmdSetFPS,
|
||||
kCmdSetProperty,
|
||||
kCmdSetPropertyStr,
|
||||
kNumSinksChanged, // no response
|
||||
kNumSinksEnabledChanged, // no response
|
||||
// Responses
|
||||
kOk,
|
||||
kError
|
||||
};
|
||||
|
||||
explicit Message(Kind kind_)
|
||||
: kind(kind_), from(std::this_thread::get_id()) {}
|
||||
|
||||
Kind kind;
|
||||
int data[4];
|
||||
std::string dataStr;
|
||||
std::thread::id from;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
|
||||
llvm::StringRef name) const override;
|
||||
|
||||
// Cache properties. Immediately successful if properties are already cached.
|
||||
// If they are not, tries to connect to the camera to do so; returns false and
|
||||
// sets status to CS_SOURCE_IS_DISCONNECTED if that too fails.
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
|
||||
private:
|
||||
// Send a message to the camera thread and wait for a response (generic)
|
||||
CS_StatusValue SendAndWait(Message&& msg) const;
|
||||
// Send a message to the camera thread with no response
|
||||
void Send(Message&& msg) const;
|
||||
|
||||
// The camera processing thread
|
||||
void CameraThreadMain();
|
||||
|
||||
// Functions used by CameraThreadMain()
|
||||
void DeviceDisconnect();
|
||||
void DeviceConnect();
|
||||
bool DeviceStreamOn();
|
||||
bool DeviceStreamOff();
|
||||
void DeviceProcessCommands();
|
||||
void DeviceSetMode();
|
||||
void DeviceSetFPS();
|
||||
void DeviceCacheMode();
|
||||
void DeviceCacheProperty(std::unique_ptr<UsbCameraProperty> rawProp);
|
||||
void DeviceCacheProperties();
|
||||
void DeviceCacheVideoModes();
|
||||
|
||||
// Command helper functions
|
||||
CS_StatusValue DeviceProcessCommand(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
CS_StatusValue DeviceCmdSetMode(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
CS_StatusValue DeviceCmdSetProperty(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
|
||||
// Property helper functions
|
||||
int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue);
|
||||
int PercentageToRaw(const UsbCameraProperty& rawProp, int percentValue);
|
||||
|
||||
void SetQuirks();
|
||||
|
||||
//
|
||||
// Variables only used within camera thread
|
||||
//
|
||||
bool m_streaming;
|
||||
bool m_modeSetPixelFormat{false};
|
||||
bool m_modeSetResolution{false};
|
||||
bool m_modeSetFPS{false};
|
||||
#ifdef __linux__
|
||||
unsigned m_capabilities = 0;
|
||||
#endif
|
||||
// Number of buffers to ask OS for
|
||||
static constexpr int kNumBuffers = 4;
|
||||
#ifdef __linux__
|
||||
std::array<UsbCameraBuffer, kNumBuffers> m_buffers;
|
||||
#endif
|
||||
|
||||
//
|
||||
// Path never changes, so not protected by mutex.
|
||||
//
|
||||
std::string m_path;
|
||||
|
||||
#ifdef __linux__
|
||||
std::atomic_int m_fd;
|
||||
std::atomic_int m_command_fd; // for command eventfd
|
||||
#endif
|
||||
|
||||
std::atomic_bool m_active; // set to false to terminate thread
|
||||
std::thread m_cameraThread;
|
||||
|
||||
// Quirks
|
||||
bool m_lifecam_exposure{false}; // Microsoft LifeCam exposure
|
||||
|
||||
//
|
||||
// Variables protected by m_mutex
|
||||
//
|
||||
|
||||
// Message queues
|
||||
mutable std::vector<Message> m_commands;
|
||||
mutable std::vector<std::pair<std::thread::id, CS_StatusValue>> m_responses;
|
||||
mutable wpi::condition_variable m_responseCv;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_USBCAMERAIMPL_H_
|
||||
320
cscore/src/main/native/cpp/UsbCameraProperty.cpp
Normal file
320
cscore/src/main/native/cpp/UsbCameraProperty.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "UsbCameraProperty.h"
|
||||
|
||||
#include <llvm/STLExtras.h>
|
||||
#include <llvm/SmallString.h>
|
||||
|
||||
#include "UsbUtil.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
static int GetIntCtrlIoctl(int fd, unsigned id, int type, int64_t* value) {
|
||||
unsigned ctrl_class = V4L2_CTRL_ID2CLASS(id);
|
||||
if (type == V4L2_CTRL_TYPE_INTEGER64 || V4L2_CTRL_DRIVER_PRIV(id) ||
|
||||
(ctrl_class != V4L2_CTRL_CLASS_USER &&
|
||||
ctrl_class != V4L2_CID_PRIVATE_BASE)) {
|
||||
// Use extended control
|
||||
struct v4l2_ext_control ctrl;
|
||||
struct v4l2_ext_controls ctrls;
|
||||
std::memset(&ctrl, 0, sizeof(ctrl));
|
||||
std::memset(&ctrls, 0, sizeof(ctrls));
|
||||
ctrl.id = id;
|
||||
ctrls.ctrl_class = ctrl_class;
|
||||
ctrls.count = 1;
|
||||
ctrls.controls = &ctrl;
|
||||
int rc = DoIoctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls);
|
||||
if (rc < 0) return rc;
|
||||
*value = ctrl.value;
|
||||
} else {
|
||||
// Use normal control
|
||||
struct v4l2_control ctrl;
|
||||
std::memset(&ctrl, 0, sizeof(ctrl));
|
||||
ctrl.id = id;
|
||||
int rc = DoIoctl(fd, VIDIOC_G_CTRL, &ctrl);
|
||||
if (rc < 0) return rc;
|
||||
*value = ctrl.value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SetIntCtrlIoctl(int fd, unsigned id, int type, int64_t value) {
|
||||
unsigned ctrl_class = V4L2_CTRL_ID2CLASS(id);
|
||||
if (type == V4L2_CTRL_TYPE_INTEGER64 || V4L2_CTRL_DRIVER_PRIV(id) ||
|
||||
(ctrl_class != V4L2_CTRL_CLASS_USER &&
|
||||
ctrl_class != V4L2_CID_PRIVATE_BASE)) {
|
||||
// Use extended control
|
||||
struct v4l2_ext_control ctrl;
|
||||
struct v4l2_ext_controls ctrls;
|
||||
std::memset(&ctrl, 0, sizeof(ctrl));
|
||||
std::memset(&ctrls, 0, sizeof(ctrls));
|
||||
ctrl.id = id;
|
||||
if (type == V4L2_CTRL_TYPE_INTEGER64)
|
||||
ctrl.value64 = value;
|
||||
else
|
||||
ctrl.value = static_cast<__s32>(value);
|
||||
ctrls.ctrl_class = ctrl_class;
|
||||
ctrls.count = 1;
|
||||
ctrls.controls = &ctrl;
|
||||
return DoIoctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls);
|
||||
} else {
|
||||
// Use normal control
|
||||
struct v4l2_control ctrl;
|
||||
ctrl.id = id;
|
||||
ctrl.value = static_cast<__s32>(value);
|
||||
return DoIoctl(fd, VIDIOC_S_CTRL, &ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
static int GetStringCtrlIoctl(int fd, int id, int maximum, std::string* value) {
|
||||
struct v4l2_ext_control ctrl;
|
||||
struct v4l2_ext_controls ctrls;
|
||||
std::memset(&ctrl, 0, sizeof(ctrl));
|
||||
std::memset(&ctrls, 0, sizeof(ctrls));
|
||||
ctrl.id = id;
|
||||
ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(id);
|
||||
ctrls.count = 1;
|
||||
ctrls.controls = &ctrl;
|
||||
int rc = DoIoctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls);
|
||||
if (rc < 0) {
|
||||
value->clear();
|
||||
return rc;
|
||||
}
|
||||
value->assign(ctrl.string, std::strlen(ctrl.string));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SetStringCtrlIoctl(int fd, int id, int maximum,
|
||||
llvm::StringRef value) {
|
||||
llvm::SmallString<64> str{
|
||||
value.substr(0, std::min(value.size(), static_cast<size_t>(maximum)))};
|
||||
|
||||
struct v4l2_ext_control ctrl;
|
||||
struct v4l2_ext_controls ctrls;
|
||||
std::memset(&ctrl, 0, sizeof(ctrl));
|
||||
std::memset(&ctrls, 0, sizeof(ctrls));
|
||||
ctrl.id = id;
|
||||
ctrl.size = str.size();
|
||||
ctrl.string = const_cast<char*>(str.c_str());
|
||||
ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(id);
|
||||
ctrls.count = 1;
|
||||
ctrls.controls = &ctrl;
|
||||
return DoIoctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls);
|
||||
}
|
||||
|
||||
// Removes non-alphanumeric characters and replaces spaces with underscores.
|
||||
// e.g. "Zoom, Absolute" -> "zoom_absolute", "Pan (Absolute)" -> "pan_absolute"
|
||||
static llvm::StringRef NormalizeName(llvm::StringRef name,
|
||||
llvm::SmallVectorImpl<char>& buf) {
|
||||
bool newWord = false;
|
||||
for (auto ch : name) {
|
||||
if (std::isalnum(ch)) {
|
||||
if (newWord) buf.push_back('_');
|
||||
newWord = false;
|
||||
buf.push_back(std::tolower(ch));
|
||||
} else if (!buf.empty()) {
|
||||
newWord = true;
|
||||
}
|
||||
}
|
||||
return llvm::StringRef(buf.data(), buf.size());
|
||||
}
|
||||
|
||||
#ifdef VIDIOC_QUERY_EXT_CTRL
|
||||
UsbCameraProperty::UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl)
|
||||
: PropertyImpl(llvm::StringRef{}, CS_PROP_NONE, ctrl.step,
|
||||
ctrl.default_value, 0),
|
||||
id(ctrl.id & V4L2_CTRL_ID_MASK),
|
||||
type(ctrl.type) {
|
||||
hasMinimum = true;
|
||||
minimum = ctrl.minimum;
|
||||
hasMaximum = true;
|
||||
maximum = ctrl.maximum;
|
||||
|
||||
// propKind
|
||||
switch (ctrl.type) {
|
||||
case V4L2_CTRL_TYPE_INTEGER:
|
||||
case V4L2_CTRL_TYPE_INTEGER64:
|
||||
propKind = CS_PROP_INTEGER;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_BOOLEAN:
|
||||
propKind = CS_PROP_BOOLEAN;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_INTEGER_MENU:
|
||||
case V4L2_CTRL_TYPE_MENU:
|
||||
propKind = CS_PROP_ENUM;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_STRING:
|
||||
propKind = CS_PROP_STRING;
|
||||
break;
|
||||
default:
|
||||
return; // others unsupported
|
||||
}
|
||||
|
||||
// name
|
||||
size_t len = 0;
|
||||
while (len < sizeof(ctrl.name) && ctrl.name[len] != '\0') ++len;
|
||||
llvm::SmallString<64> name_buf;
|
||||
name = NormalizeName(llvm::StringRef(ctrl.name, len), name_buf);
|
||||
}
|
||||
#endif
|
||||
|
||||
UsbCameraProperty::UsbCameraProperty(const struct v4l2_queryctrl& ctrl)
|
||||
: PropertyImpl(llvm::StringRef{}, CS_PROP_NONE, ctrl.step,
|
||||
ctrl.default_value, 0),
|
||||
id(ctrl.id & V4L2_CTRL_ID_MASK),
|
||||
type(ctrl.type) {
|
||||
hasMinimum = true;
|
||||
minimum = ctrl.minimum;
|
||||
hasMaximum = true;
|
||||
maximum = ctrl.maximum;
|
||||
|
||||
// propKind
|
||||
switch (ctrl.type) {
|
||||
case V4L2_CTRL_TYPE_INTEGER:
|
||||
case V4L2_CTRL_TYPE_INTEGER64:
|
||||
propKind = CS_PROP_INTEGER;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_BOOLEAN:
|
||||
propKind = CS_PROP_BOOLEAN;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_INTEGER_MENU:
|
||||
case V4L2_CTRL_TYPE_MENU:
|
||||
propKind = CS_PROP_ENUM;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_STRING:
|
||||
propKind = CS_PROP_STRING;
|
||||
break;
|
||||
default:
|
||||
return; // others unsupported
|
||||
}
|
||||
|
||||
// name
|
||||
size_t len = 0;
|
||||
while (len < sizeof(ctrl.name) && ctrl.name[len] != '\0') ++len;
|
||||
llvm::SmallString<64> name_buf;
|
||||
name = NormalizeName(
|
||||
llvm::StringRef(reinterpret_cast<const char*>(ctrl.name), len), name_buf);
|
||||
}
|
||||
|
||||
std::unique_ptr<UsbCameraProperty> UsbCameraProperty::DeviceQuery(int fd,
|
||||
__u32* id) {
|
||||
int rc;
|
||||
std::unique_ptr<UsbCameraProperty> prop;
|
||||
#ifdef VIDIOC_QUERY_EXT_CTRL
|
||||
v4l2_query_ext_ctrl qc_ext;
|
||||
std::memset(&qc_ext, 0, sizeof(qc_ext));
|
||||
qc_ext.id = *id;
|
||||
rc = TryIoctl(fd, VIDIOC_QUERY_EXT_CTRL, &qc_ext);
|
||||
if (rc == 0) {
|
||||
*id = qc_ext.id; // copy back
|
||||
// We don't support array types
|
||||
if (qc_ext.elems > 1 || qc_ext.nr_of_dims > 0) return nullptr;
|
||||
prop = llvm::make_unique<UsbCameraProperty>(qc_ext);
|
||||
}
|
||||
#endif
|
||||
if (!prop) {
|
||||
// Fall back to normal QUERYCTRL
|
||||
struct v4l2_queryctrl qc;
|
||||
std::memset(&qc, 0, sizeof(qc));
|
||||
qc.id = *id;
|
||||
rc = TryIoctl(fd, VIDIOC_QUERYCTRL, &qc);
|
||||
*id = qc.id; // copy back
|
||||
if (rc != 0) return nullptr;
|
||||
prop = llvm::make_unique<UsbCameraProperty>(qc);
|
||||
}
|
||||
|
||||
// Cache enum property choices
|
||||
if (prop->propKind == CS_PROP_ENUM) {
|
||||
prop->enumChoices.resize(prop->maximum + 1);
|
||||
v4l2_querymenu qmenu;
|
||||
std::memset(&qmenu, 0, sizeof(qmenu));
|
||||
qmenu.id = *id;
|
||||
for (int i = prop->minimum; i <= prop->maximum; ++i) {
|
||||
qmenu.index = static_cast<__u32>(i);
|
||||
if (TryIoctl(fd, VIDIOC_QUERYMENU, &qmenu) != 0) continue;
|
||||
prop->enumChoices[i] = reinterpret_cast<const char*>(qmenu.name);
|
||||
}
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock, int fd) {
|
||||
if (fd < 0) return true;
|
||||
unsigned idCopy = id;
|
||||
int rv = 0;
|
||||
|
||||
switch (propKind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM: {
|
||||
int typeCopy = type;
|
||||
int64_t newValue = 0;
|
||||
lock.unlock();
|
||||
rv = GetIntCtrlIoctl(fd, idCopy, typeCopy, &newValue);
|
||||
lock.lock();
|
||||
if (rv >= 0) value = newValue;
|
||||
break;
|
||||
}
|
||||
case CS_PROP_STRING: {
|
||||
int maximumCopy = maximum;
|
||||
std::string newValueStr;
|
||||
lock.unlock();
|
||||
rv = GetStringCtrlIoctl(fd, idCopy, maximumCopy, &newValueStr);
|
||||
lock.lock();
|
||||
if (rv >= 0) valueStr = std::move(newValueStr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rv >= 0;
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
int fd) const {
|
||||
// Make a copy of the string as we're about to release the lock
|
||||
llvm::SmallString<128> valueStrCopy{valueStr};
|
||||
return DeviceSet(lock, fd, value, valueStrCopy);
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd,
|
||||
int newValue,
|
||||
llvm::StringRef newValueStr) const {
|
||||
if (fd < 0) return true;
|
||||
unsigned idCopy = id;
|
||||
int rv = 0;
|
||||
|
||||
switch (propKind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM: {
|
||||
int typeCopy = type;
|
||||
lock.unlock();
|
||||
rv = SetIntCtrlIoctl(fd, idCopy, typeCopy, newValue);
|
||||
lock.lock();
|
||||
break;
|
||||
}
|
||||
case CS_PROP_STRING: {
|
||||
int maximumCopy = maximum;
|
||||
lock.unlock();
|
||||
rv = SetStringCtrlIoctl(fd, idCopy, maximumCopy, newValueStr);
|
||||
lock.lock();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rv >= 0;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
70
cscore/src/main/native/cpp/UsbCameraProperty.h
Normal file
70
cscore/src/main/native/cpp/UsbCameraProperty.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_USBCAMERAPROPERTY_H_
|
||||
#define CSCORE_USBCAMERAPROPERTY_H_
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/videodev2.h>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <support/mutex.h>
|
||||
|
||||
#include "PropertyImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
// Property data
|
||||
class UsbCameraProperty : public PropertyImpl {
|
||||
public:
|
||||
UsbCameraProperty() = default;
|
||||
explicit UsbCameraProperty(llvm::StringRef name_) : PropertyImpl{name_} {}
|
||||
|
||||
// Normalized property constructor
|
||||
UsbCameraProperty(llvm::StringRef name_, int rawIndex_,
|
||||
const UsbCameraProperty& rawProp, int defaultValue_,
|
||||
int value_)
|
||||
: PropertyImpl(name_, rawProp.propKind, 1, defaultValue_, value_),
|
||||
percentage{true},
|
||||
propPair{rawIndex_},
|
||||
id{rawProp.id},
|
||||
type{rawProp.type} {
|
||||
hasMinimum = true;
|
||||
minimum = 0;
|
||||
hasMaximum = true;
|
||||
maximum = 100;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#ifdef VIDIOC_QUERY_EXT_CTRL
|
||||
explicit UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl);
|
||||
#endif
|
||||
explicit UsbCameraProperty(const struct v4l2_queryctrl& ctrl);
|
||||
|
||||
static std::unique_ptr<UsbCameraProperty> DeviceQuery(int fd, __u32* id);
|
||||
|
||||
bool DeviceGet(std::unique_lock<wpi::mutex>& lock, int fd);
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd) const;
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd, int newValue,
|
||||
llvm::StringRef newValueStr) const;
|
||||
#endif
|
||||
|
||||
// If this is a percentage (rather than raw) property
|
||||
bool percentage{false};
|
||||
|
||||
// If not 0, index of corresponding raw/percentage property
|
||||
int propPair{0};
|
||||
|
||||
unsigned id{0}; // implementation-level id
|
||||
int type{0}; // implementation type, not CS_PropertyKind!
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_USBCAMERAPROPERTY_H_
|
||||
166
cscore/src/main/native/cpp/UsbUtil.cpp
Normal file
166
cscore/src/main/native/cpp/UsbUtil.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "UsbUtil.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
#include <llvm/Format.h>
|
||||
#include <llvm/SmallString.h>
|
||||
#include <llvm/raw_ostream.h>
|
||||
#include <support/raw_istream.h>
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
static llvm::StringRef GetUsbNameFromFile(int vendor, int product,
|
||||
llvm::SmallVectorImpl<char>& buf) {
|
||||
int fd = open("/var/lib/usbutils/usb.ids", O_RDONLY);
|
||||
if (fd < 0) return llvm::StringRef{};
|
||||
|
||||
llvm::raw_svector_ostream os{buf};
|
||||
wpi::raw_fd_istream is{fd, true};
|
||||
|
||||
// build vendor and product 4-char hex strings
|
||||
llvm::SmallString<16> vendorStr, productStr;
|
||||
llvm::raw_svector_ostream vendorOs{vendorStr}, productOs{productStr};
|
||||
vendorOs << llvm::format_hex_no_prefix(vendor, 4);
|
||||
productOs << llvm::format_hex_no_prefix(product, 4);
|
||||
|
||||
// scan file
|
||||
llvm::SmallString<128> lineBuf;
|
||||
bool foundVendor = false;
|
||||
for (;;) {
|
||||
auto line = is.getline(lineBuf, 4096);
|
||||
if (is.has_error()) break;
|
||||
|
||||
if (line.empty()) continue;
|
||||
|
||||
// look for vendor at start of line
|
||||
if (line.startswith(vendorStr)) {
|
||||
foundVendor = true;
|
||||
os << line.substr(5).trim() << ' ';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (foundVendor) {
|
||||
// next vendor, but didn't match product?
|
||||
if (line[0] != '\t') {
|
||||
os << "Unknown";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
// look for product
|
||||
if (line.substr(1).startswith(productStr)) {
|
||||
os << line.substr(6).trim();
|
||||
return os.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
|
||||
llvm::StringRef GetUsbNameFromId(int vendor, int product,
|
||||
llvm::SmallVectorImpl<char>& buf) {
|
||||
// try reading usb.ids
|
||||
llvm::StringRef rv = GetUsbNameFromFile(vendor, product, buf);
|
||||
if (!rv.empty()) return rv;
|
||||
|
||||
// Fall back to internal database
|
||||
llvm::raw_svector_ostream os{buf};
|
||||
switch (vendor) {
|
||||
case 0x046d:
|
||||
os << "Logitech, Inc. ";
|
||||
switch (product) {
|
||||
case 0x0802:
|
||||
os << "Webcam C200";
|
||||
break;
|
||||
case 0x0804:
|
||||
os << "Webcam C250";
|
||||
break;
|
||||
case 0x0805:
|
||||
os << "Webcam C300";
|
||||
break;
|
||||
case 0x0807:
|
||||
os << "Webcam B500";
|
||||
break;
|
||||
case 0x0808:
|
||||
os << "Webcam C600";
|
||||
break;
|
||||
case 0x0809:
|
||||
os << "Webcam Pro 9000";
|
||||
break;
|
||||
case 0x080a:
|
||||
os << "Portable Webcam C905";
|
||||
break;
|
||||
case 0x080f:
|
||||
os << "Webcam C120";
|
||||
break;
|
||||
case 0x0819:
|
||||
os << "Webcam C210";
|
||||
break;
|
||||
case 0x081b:
|
||||
os << "Webcam C310";
|
||||
break;
|
||||
case 0x081d:
|
||||
os << "HD Webcam C510";
|
||||
break;
|
||||
case 0x0821:
|
||||
os << "HD Webcam C910";
|
||||
break;
|
||||
case 0x0825:
|
||||
os << "Webcam C270";
|
||||
break;
|
||||
case 0x0826:
|
||||
os << "HD Webcam C525";
|
||||
break;
|
||||
case 0x0828:
|
||||
os << "HD Webcam B990";
|
||||
break;
|
||||
case 0x082b:
|
||||
os << "Webcam C170";
|
||||
break;
|
||||
case 0x082d:
|
||||
os << "HD Pro Webcam C920";
|
||||
break;
|
||||
case 0x0836:
|
||||
os << "B525 HD Webcam";
|
||||
break;
|
||||
case 0x0843:
|
||||
os << "Webcam C930e";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int CheckedIoctl(int fd, unsigned long req, void* data, // NOLINT(runtime/int)
|
||||
const char* name, const char* file, int line, bool quiet) {
|
||||
int retval = ioctl(fd, req, data);
|
||||
if (!quiet && retval < 0) {
|
||||
llvm::SmallString<64> localfile{file};
|
||||
localfile.push_back('\0');
|
||||
ERROR("ioctl " << name << " failed at " << basename(localfile.data()) << ":"
|
||||
<< line << ": " << std::strerror(errno));
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
} // namespace cs
|
||||
35
cscore/src/main/native/cpp/UsbUtil.h
Normal file
35
cscore/src/main/native/cpp/UsbUtil.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_USBUTIL_H_
|
||||
#define CSCORE_USBUTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <llvm/SmallVector.h>
|
||||
#include <llvm/StringRef.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
llvm::StringRef GetUsbNameFromId(int vendor, int product,
|
||||
llvm::SmallVectorImpl<char>& buf);
|
||||
|
||||
int CheckedIoctl(int fd, unsigned long req, void* data, // NOLINT(runtime/int)
|
||||
const char* name, const char* file, int line, bool quiet);
|
||||
|
||||
#define DoIoctl(fd, req, data) \
|
||||
CheckedIoctl(fd, req, data, #req, __FILE__, __LINE__, false)
|
||||
#define TryIoctl(fd, req, data) \
|
||||
CheckedIoctl(fd, req, data, #req, __FILE__, __LINE__, true)
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_USBUTIL_H_
|
||||
27
cscore/src/main/native/cpp/c_util.h
Normal file
27
cscore/src/main/native/cpp/c_util.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_C_UTIL_H_
|
||||
#define CSCORE_C_UTIL_H_
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <llvm/StringRef.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
inline char* ConvertToC(llvm::StringRef in) {
|
||||
char* out = static_cast<char*>(std::malloc(in.size() + 1));
|
||||
std::memmove(out, in.data(), in.size());
|
||||
out[in.size()] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_C_UTIL_H_
|
||||
395
cscore/src/main/native/cpp/cscore_c.cpp
Normal file
395
cscore/src/main/native/cpp/cscore_c.cpp
Normal file
@@ -0,0 +1,395 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "cscore_c.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <llvm/SmallString.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
|
||||
#include "c_util.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_PropertyKind CS_GetPropertyKind(CS_Property property, CS_Status* status) {
|
||||
return cs::GetPropertyKind(property, status);
|
||||
}
|
||||
|
||||
char* CS_GetPropertyName(CS_Property property, CS_Status* status) {
|
||||
llvm::SmallString<128> buf;
|
||||
auto str = cs::GetPropertyName(property, buf, status);
|
||||
if (*status != 0) return nullptr;
|
||||
return cs::ConvertToC(str);
|
||||
}
|
||||
|
||||
int CS_GetProperty(CS_Property property, CS_Status* status) {
|
||||
return cs::GetProperty(property, status);
|
||||
}
|
||||
|
||||
void CS_SetProperty(CS_Property property, int value, CS_Status* status) {
|
||||
return cs::SetProperty(property, value, status);
|
||||
}
|
||||
|
||||
int CS_GetPropertyMin(CS_Property property, CS_Status* status) {
|
||||
return cs::GetPropertyMin(property, status);
|
||||
}
|
||||
|
||||
int CS_GetPropertyMax(CS_Property property, CS_Status* status) {
|
||||
return cs::GetPropertyMax(property, status);
|
||||
}
|
||||
|
||||
int CS_GetPropertyStep(CS_Property property, CS_Status* status) {
|
||||
return cs::GetPropertyStep(property, status);
|
||||
}
|
||||
|
||||
int CS_GetPropertyDefault(CS_Property property, CS_Status* status) {
|
||||
return cs::GetPropertyDefault(property, status);
|
||||
}
|
||||
|
||||
char* CS_GetStringProperty(CS_Property property, CS_Status* status) {
|
||||
llvm::SmallString<128> buf;
|
||||
auto str = cs::GetStringProperty(property, buf, status);
|
||||
if (*status != 0) return nullptr;
|
||||
return cs::ConvertToC(str);
|
||||
}
|
||||
|
||||
void CS_SetStringProperty(CS_Property property, const char* value,
|
||||
CS_Status* status) {
|
||||
return cs::SetStringProperty(property, value, status);
|
||||
}
|
||||
|
||||
char** CS_GetEnumPropertyChoices(CS_Property property, int* count,
|
||||
CS_Status* status) {
|
||||
auto choices = cs::GetEnumPropertyChoices(property, status);
|
||||
char** out = static_cast<char**>(std::malloc(choices.size() * sizeof(char*)));
|
||||
*count = choices.size();
|
||||
for (size_t i = 0; i < choices.size(); ++i)
|
||||
out[i] = cs::ConvertToC(choices[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
CS_SourceKind CS_GetSourceKind(CS_Source source, CS_Status* status) {
|
||||
return cs::GetSourceKind(source, status);
|
||||
}
|
||||
|
||||
char* CS_GetSourceName(CS_Source source, CS_Status* status) {
|
||||
llvm::SmallString<128> buf;
|
||||
auto str = cs::GetSourceName(source, buf, status);
|
||||
if (*status != 0) return nullptr;
|
||||
return cs::ConvertToC(str);
|
||||
}
|
||||
|
||||
char* CS_GetSourceDescription(CS_Source source, CS_Status* status) {
|
||||
llvm::SmallString<128> buf;
|
||||
auto str = cs::GetSourceDescription(source, buf, status);
|
||||
if (*status != 0) return nullptr;
|
||||
return cs::ConvertToC(str);
|
||||
}
|
||||
|
||||
uint64_t CS_GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
|
||||
return cs::GetSourceLastFrameTime(source, status);
|
||||
}
|
||||
|
||||
CS_Bool CS_IsSourceConnected(CS_Source source, CS_Status* status) {
|
||||
return cs::IsSourceConnected(source, status);
|
||||
}
|
||||
|
||||
CS_Property CS_GetSourceProperty(CS_Source source, const char* name,
|
||||
CS_Status* status) {
|
||||
return cs::GetSourceProperty(source, name, status);
|
||||
}
|
||||
|
||||
CS_Property* CS_EnumerateSourceProperties(CS_Source source, int* count,
|
||||
CS_Status* status) {
|
||||
llvm::SmallVector<CS_Property, 32> buf;
|
||||
auto vec = cs::EnumerateSourceProperties(source, buf, status);
|
||||
CS_Property* out =
|
||||
static_cast<CS_Property*>(std::malloc(vec.size() * sizeof(CS_Property)));
|
||||
*count = vec.size();
|
||||
std::copy(vec.begin(), vec.end(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
void CS_GetSourceVideoMode(CS_Source source, CS_VideoMode* mode,
|
||||
CS_Status* status) {
|
||||
*mode = cs::GetSourceVideoMode(source, status);
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSourceVideoMode(CS_Source source, const CS_VideoMode* mode,
|
||||
CS_Status* status) {
|
||||
return cs::SetSourceVideoMode(
|
||||
source, static_cast<const cs::VideoMode&>(*mode), status);
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSourceVideoModeDiscrete(CS_Source source,
|
||||
enum CS_PixelFormat pixelFormat,
|
||||
int width, int height, int fps,
|
||||
CS_Status* status) {
|
||||
return cs::SetSourceVideoMode(
|
||||
source,
|
||||
cs::VideoMode{static_cast<cs::VideoMode::PixelFormat>(
|
||||
static_cast<int>(pixelFormat)),
|
||||
width, height, fps},
|
||||
status);
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSourcePixelFormat(CS_Source source,
|
||||
enum CS_PixelFormat pixelFormat,
|
||||
CS_Status* status) {
|
||||
return cs::SetSourcePixelFormat(
|
||||
source,
|
||||
static_cast<cs::VideoMode::PixelFormat>(static_cast<int>(pixelFormat)),
|
||||
status);
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height,
|
||||
CS_Status* status) {
|
||||
return cs::SetSourceResolution(source, width, height, status);
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
|
||||
return cs::SetSourceFPS(source, fps, status);
|
||||
}
|
||||
|
||||
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
|
||||
CS_Status* status) {
|
||||
auto vec = cs::EnumerateSourceVideoModes(source, status);
|
||||
CS_VideoMode* out = static_cast<CS_VideoMode*>(
|
||||
std::malloc(vec.size() * sizeof(CS_VideoMode)));
|
||||
*count = vec.size();
|
||||
std::copy(vec.begin(), vec.end(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
|
||||
CS_Status* status) {
|
||||
llvm::SmallVector<CS_Sink, 32> buf;
|
||||
auto handles = cs::EnumerateSourceSinks(source, buf, status);
|
||||
CS_Sink* sinks =
|
||||
static_cast<CS_Sink*>(std::malloc(handles.size() * sizeof(CS_Sink)));
|
||||
*count = handles.size();
|
||||
std::copy(handles.begin(), handles.end(), sinks);
|
||||
return sinks;
|
||||
}
|
||||
|
||||
CS_Source CS_CopySource(CS_Source source, CS_Status* status) {
|
||||
return cs::CopySource(source, status);
|
||||
}
|
||||
|
||||
void CS_ReleaseSource(CS_Source source, CS_Status* status) {
|
||||
return cs::ReleaseSource(source, status);
|
||||
}
|
||||
|
||||
void CS_SetCameraBrightness(CS_Source source, int brightness,
|
||||
CS_Status* status) {
|
||||
return cs::SetCameraBrightness(source, brightness, status);
|
||||
}
|
||||
|
||||
int CS_GetCameraBrightness(CS_Source source, CS_Status* status) {
|
||||
return cs::GetCameraBrightness(source, status);
|
||||
}
|
||||
|
||||
void CS_SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status) {
|
||||
return cs::SetCameraWhiteBalanceAuto(source, status);
|
||||
}
|
||||
|
||||
void CS_SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status) {
|
||||
return cs::SetCameraWhiteBalanceHoldCurrent(source, status);
|
||||
}
|
||||
|
||||
void CS_SetCameraWhiteBalanceManual(CS_Source source, int value,
|
||||
CS_Status* status) {
|
||||
return cs::SetCameraWhiteBalanceManual(source, value, status);
|
||||
}
|
||||
|
||||
void CS_SetCameraExposureAuto(CS_Source source, CS_Status* status) {
|
||||
return cs::SetCameraExposureAuto(source, status);
|
||||
}
|
||||
|
||||
void CS_SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status) {
|
||||
return cs::SetCameraExposureHoldCurrent(source, status);
|
||||
}
|
||||
|
||||
void CS_SetCameraExposureManual(CS_Source source, int value,
|
||||
CS_Status* status) {
|
||||
return cs::SetCameraExposureManual(source, value, status);
|
||||
}
|
||||
|
||||
CS_SinkKind CS_GetSinkKind(CS_Sink sink, CS_Status* status) {
|
||||
return cs::GetSinkKind(sink, status);
|
||||
}
|
||||
|
||||
char* CS_GetSinkName(CS_Sink sink, CS_Status* status) {
|
||||
llvm::SmallString<128> buf;
|
||||
auto str = cs::GetSinkName(sink, buf, status);
|
||||
if (*status != 0) return nullptr;
|
||||
return cs::ConvertToC(str);
|
||||
}
|
||||
|
||||
char* CS_GetSinkDescription(CS_Sink sink, CS_Status* status) {
|
||||
llvm::SmallString<128> buf;
|
||||
auto str = cs::GetSinkDescription(sink, buf, status);
|
||||
if (*status != 0) return nullptr;
|
||||
return cs::ConvertToC(str);
|
||||
}
|
||||
|
||||
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
|
||||
return cs::SetSinkSource(sink, source, status);
|
||||
}
|
||||
|
||||
CS_Source CS_GetSinkSource(CS_Sink sink, CS_Status* status) {
|
||||
return cs::GetSinkSource(sink, status);
|
||||
}
|
||||
|
||||
CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name,
|
||||
CS_Status* status) {
|
||||
return cs::GetSinkSourceProperty(sink, name, status);
|
||||
}
|
||||
|
||||
CS_Sink CS_CopySink(CS_Sink sink, CS_Status* status) {
|
||||
return cs::CopySink(sink, status);
|
||||
}
|
||||
|
||||
void CS_ReleaseSink(CS_Sink sink, CS_Status* status) {
|
||||
return cs::ReleaseSink(sink, status);
|
||||
}
|
||||
|
||||
void CS_SetListenerOnStart(void (*onStart)(void* data), void* data) {
|
||||
cs::SetListenerOnStart([=]() { onStart(data); });
|
||||
}
|
||||
|
||||
void CS_SetListenerOnExit(void (*onExit)(void* data), void* data) {
|
||||
cs::SetListenerOnExit([=]() { onExit(data); });
|
||||
}
|
||||
|
||||
CS_Listener CS_AddListener(void* data,
|
||||
void (*callback)(void* data, const CS_Event* event),
|
||||
int eventMask, int immediateNotify,
|
||||
CS_Status* status) {
|
||||
return cs::AddListener(
|
||||
[=](const cs::RawEvent& rawEvent) {
|
||||
CS_Event event;
|
||||
event.kind = static_cast<CS_EventKind>(static_cast<int>(rawEvent.kind));
|
||||
event.source = rawEvent.sourceHandle;
|
||||
event.sink = rawEvent.sinkHandle;
|
||||
event.name = rawEvent.name.c_str();
|
||||
event.mode = rawEvent.mode;
|
||||
event.property = rawEvent.propertyHandle;
|
||||
event.propertyKind = rawEvent.propertyKind;
|
||||
event.value = rawEvent.value;
|
||||
event.valueStr = rawEvent.valueStr.c_str();
|
||||
callback(data, &event);
|
||||
},
|
||||
eventMask, immediateNotify, status);
|
||||
}
|
||||
|
||||
void CS_RemoveListener(CS_Listener handle, CS_Status* status) {
|
||||
return cs::RemoveListener(handle, status);
|
||||
}
|
||||
|
||||
int CS_NotifierDestroyed(void) { return cs::NotifierDestroyed(); }
|
||||
|
||||
void CS_SetTelemetryPeriod(double seconds) { cs::SetTelemetryPeriod(seconds); }
|
||||
|
||||
double CS_GetTelemetryElapsedTime(void) {
|
||||
return cs::GetTelemetryElapsedTime();
|
||||
}
|
||||
|
||||
int64_t CS_GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
return cs::GetTelemetryValue(handle, kind, status);
|
||||
}
|
||||
|
||||
double CS_GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
return cs::GetTelemetryAverageValue(handle, kind, status);
|
||||
}
|
||||
|
||||
void CS_SetLogger(CS_LogFunc func, unsigned int min_level) {
|
||||
cs::SetLogger(func, min_level);
|
||||
}
|
||||
|
||||
void CS_SetDefaultLogger(unsigned int min_level) {
|
||||
cs::SetDefaultLogger(min_level);
|
||||
}
|
||||
|
||||
CS_Source* CS_EnumerateSources(int* count, CS_Status* status) {
|
||||
llvm::SmallVector<CS_Source, 32> buf;
|
||||
auto handles = cs::EnumerateSourceHandles(buf, status);
|
||||
CS_Source* sources =
|
||||
static_cast<CS_Source*>(std::malloc(handles.size() * sizeof(CS_Source)));
|
||||
*count = handles.size();
|
||||
std::copy(handles.begin(), handles.end(), sources);
|
||||
return sources;
|
||||
}
|
||||
|
||||
void CS_ReleaseEnumeratedSources(CS_Source* sources, int count) {
|
||||
if (!sources) return;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
CS_Status status = 0;
|
||||
if (sources[i] != 0) cs::ReleaseSource(sources[i], &status);
|
||||
}
|
||||
std::free(sources);
|
||||
}
|
||||
|
||||
CS_Sink* CS_EnumerateSinks(int* count, CS_Status* status) {
|
||||
llvm::SmallVector<CS_Sink, 32> buf;
|
||||
auto handles = cs::EnumerateSinkHandles(buf, status);
|
||||
CS_Sink* sinks =
|
||||
static_cast<CS_Sink*>(std::malloc(handles.size() * sizeof(CS_Sink)));
|
||||
*count = handles.size();
|
||||
std::copy(handles.begin(), handles.end(), sinks);
|
||||
return sinks;
|
||||
}
|
||||
|
||||
void CS_ReleaseEnumeratedSinks(CS_Sink* sinks, int count) {
|
||||
if (!sinks) return;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
CS_Status status = 0;
|
||||
if (sinks[i] != 0) cs::ReleaseSink(sinks[i], &status);
|
||||
}
|
||||
std::free(sinks);
|
||||
}
|
||||
|
||||
void CS_FreeString(char* str) { std::free(str); }
|
||||
|
||||
void CS_FreeEnumPropertyChoices(char** choices, int count) {
|
||||
if (!choices) return;
|
||||
for (int i = 0; i < count; ++i) std::free(choices[i]);
|
||||
std::free(choices);
|
||||
}
|
||||
|
||||
void CS_FreeEnumeratedProperties(CS_Property* properties, int count) {
|
||||
std::free(properties);
|
||||
}
|
||||
|
||||
void CS_FreeEnumeratedVideoModes(CS_VideoMode* modes, int count) {
|
||||
std::free(modes);
|
||||
}
|
||||
|
||||
char* CS_GetHostname() { return cs::ConvertToC(cs::GetHostname()); }
|
||||
|
||||
char** CS_GetNetworkInterfaces(int* count) {
|
||||
auto interfaces = cs::GetNetworkInterfaces();
|
||||
char** out =
|
||||
static_cast<char**>(std::malloc(interfaces.size() * sizeof(char*)));
|
||||
*count = interfaces.size();
|
||||
for (size_t i = 0; i < interfaces.size(); ++i)
|
||||
out[i] = cs::ConvertToC(interfaces[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
void CS_FreeNetworkInterfaces(char** interfaces, int count) {
|
||||
if (!interfaces) return;
|
||||
for (int i = 0; i < count; ++i) std::free(interfaces[i]);
|
||||
std::free(interfaces);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
673
cscore/src/main/native/cpp/cscore_cpp.cpp
Normal file
673
cscore/src/main/native/cpp/cscore_cpp.cpp
Normal file
@@ -0,0 +1,673 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <llvm/SmallString.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Log.h"
|
||||
#include "NetworkListener.h"
|
||||
#include "Notifier.h"
|
||||
#include "SinkImpl.h"
|
||||
#include "SourceImpl.h"
|
||||
#include "Telemetry.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
static std::shared_ptr<SourceImpl> GetPropertySource(CS_Property propertyHandle,
|
||||
int* propertyIndex,
|
||||
CS_Status* status) {
|
||||
Handle handle{propertyHandle};
|
||||
int i = handle.GetParentIndex();
|
||||
if (i < 0) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return nullptr;
|
||||
}
|
||||
auto data = Sources::GetInstance().Get(Handle{i, Handle::kSource});
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return nullptr;
|
||||
}
|
||||
*propertyIndex = handle.GetIndex();
|
||||
return data->source;
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
//
|
||||
// Property Functions
|
||||
//
|
||||
|
||||
CS_PropertyKind GetPropertyKind(CS_Property property, CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return CS_PROP_NONE;
|
||||
return source->GetPropertyKind(propertyIndex);
|
||||
}
|
||||
|
||||
std::string GetPropertyName(CS_Property property, CS_Status* status) {
|
||||
llvm::SmallString<128> buf;
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return std::string{};
|
||||
return source->GetPropertyName(propertyIndex, buf, status);
|
||||
}
|
||||
|
||||
llvm::StringRef GetPropertyName(CS_Property property,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return llvm::StringRef{};
|
||||
return source->GetPropertyName(propertyIndex, buf, status);
|
||||
}
|
||||
|
||||
int GetProperty(CS_Property property, CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return false;
|
||||
return source->GetProperty(propertyIndex, status);
|
||||
}
|
||||
|
||||
void SetProperty(CS_Property property, int value, CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return;
|
||||
source->SetProperty(propertyIndex, value, status);
|
||||
}
|
||||
|
||||
int GetPropertyMin(CS_Property property, CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return 0.0;
|
||||
return source->GetPropertyMin(propertyIndex, status);
|
||||
}
|
||||
|
||||
int GetPropertyMax(CS_Property property, CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return 0.0;
|
||||
return source->GetPropertyMax(propertyIndex, status);
|
||||
}
|
||||
|
||||
int GetPropertyStep(CS_Property property, CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return 0.0;
|
||||
return source->GetPropertyStep(propertyIndex, status);
|
||||
}
|
||||
|
||||
int GetPropertyDefault(CS_Property property, CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return 0.0;
|
||||
return source->GetPropertyDefault(propertyIndex, status);
|
||||
}
|
||||
|
||||
std::string GetStringProperty(CS_Property property, CS_Status* status) {
|
||||
llvm::SmallString<128> buf;
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return std::string{};
|
||||
return source->GetStringProperty(propertyIndex, buf, status);
|
||||
}
|
||||
|
||||
llvm::StringRef GetStringProperty(CS_Property property,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return llvm::StringRef{};
|
||||
return source->GetStringProperty(propertyIndex, buf, status);
|
||||
}
|
||||
|
||||
void SetStringProperty(CS_Property property, llvm::StringRef value,
|
||||
CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return;
|
||||
source->SetStringProperty(propertyIndex, value, status);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetEnumPropertyChoices(CS_Property property,
|
||||
CS_Status* status) {
|
||||
int propertyIndex;
|
||||
auto source = GetPropertySource(property, &propertyIndex, status);
|
||||
if (!source) return std::vector<std::string>{};
|
||||
return source->GetEnumPropertyChoices(propertyIndex, status);
|
||||
}
|
||||
|
||||
//
|
||||
// Source Functions
|
||||
//
|
||||
|
||||
CS_SourceKind GetSourceKind(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return CS_SOURCE_UNKNOWN;
|
||||
}
|
||||
return data->kind;
|
||||
}
|
||||
|
||||
std::string GetSourceName(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return data->source->GetName();
|
||||
}
|
||||
|
||||
llvm::StringRef GetSourceName(CS_Source source,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
return data->source->GetName();
|
||||
}
|
||||
|
||||
std::string GetSourceDescription(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
llvm::SmallString<128> buf;
|
||||
return data->source->GetDescription(buf);
|
||||
}
|
||||
|
||||
llvm::StringRef GetSourceDescription(CS_Source source,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
return data->source->GetDescription(buf);
|
||||
}
|
||||
|
||||
uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return data->source->GetCurFrameTime();
|
||||
}
|
||||
|
||||
bool IsSourceConnected(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->IsConnected();
|
||||
}
|
||||
|
||||
CS_Property GetSourceProperty(CS_Source source, llvm::StringRef name,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
int property = data->source->GetPropertyIndex(name);
|
||||
if (property < 0) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return Handle{source, property, Handle::kProperty};
|
||||
}
|
||||
|
||||
llvm::ArrayRef<CS_Property> EnumerateSourceProperties(
|
||||
CS_Source source, llvm::SmallVectorImpl<CS_Property>& vec,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
llvm::SmallVector<int, 32> properties_buf;
|
||||
for (auto property :
|
||||
data->source->EnumerateProperties(properties_buf, status))
|
||||
vec.push_back(Handle{source, property, Handle::kProperty});
|
||||
return vec;
|
||||
}
|
||||
|
||||
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return VideoMode{};
|
||||
}
|
||||
return data->source->GetVideoMode(status);
|
||||
}
|
||||
|
||||
bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->SetVideoMode(mode, status);
|
||||
}
|
||||
|
||||
bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->SetPixelFormat(pixelFormat, status);
|
||||
}
|
||||
|
||||
bool SetSourceResolution(CS_Source source, int width, int height,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->SetResolution(width, height, status);
|
||||
}
|
||||
|
||||
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->SetFPS(fps, status);
|
||||
}
|
||||
|
||||
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::vector<VideoMode>{};
|
||||
}
|
||||
return data->source->EnumerateVideoModes(status);
|
||||
}
|
||||
|
||||
llvm::ArrayRef<CS_Sink> EnumerateSourceSinks(
|
||||
CS_Source source, llvm::SmallVectorImpl<CS_Sink>& vec, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return llvm::ArrayRef<CS_Sink>{};
|
||||
}
|
||||
vec.clear();
|
||||
Sinks::GetInstance().ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
|
||||
if (source == data.sourceHandle.load()) vec.push_back(sinkHandle);
|
||||
});
|
||||
return vec;
|
||||
}
|
||||
|
||||
CS_Source CopySource(CS_Source source, CS_Status* status) {
|
||||
if (source == 0) return 0;
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
data->refCount++;
|
||||
return source;
|
||||
}
|
||||
|
||||
void ReleaseSource(CS_Source source, CS_Status* status) {
|
||||
if (source == 0) return;
|
||||
auto& inst = Sources::GetInstance();
|
||||
auto data = inst.Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
if (data->refCount-- == 0) {
|
||||
Notifier::GetInstance().NotifySource(data->source->GetName(), source,
|
||||
CS_SOURCE_DESTROYED);
|
||||
inst.Free(source);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Camera Source Common Property Fuctions
|
||||
//
|
||||
|
||||
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
data->source->SetBrightness(brightness, status);
|
||||
}
|
||||
|
||||
int GetCameraBrightness(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return data->source->GetBrightness(status);
|
||||
}
|
||||
|
||||
void SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
data->source->SetWhiteBalanceAuto(status);
|
||||
}
|
||||
|
||||
void SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
data->source->SetWhiteBalanceHoldCurrent(status);
|
||||
}
|
||||
|
||||
void SetCameraWhiteBalanceManual(CS_Source source, int value,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
data->source->SetWhiteBalanceManual(value, status);
|
||||
}
|
||||
|
||||
void SetCameraExposureAuto(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
data->source->SetExposureAuto(status);
|
||||
}
|
||||
|
||||
void SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
data->source->SetExposureHoldCurrent(status);
|
||||
}
|
||||
|
||||
void SetCameraExposureManual(CS_Source source, int value, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
data->source->SetExposureManual(value, status);
|
||||
}
|
||||
|
||||
//
|
||||
// Sink Functions
|
||||
//
|
||||
|
||||
CS_SinkKind GetSinkKind(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return CS_SINK_UNKNOWN;
|
||||
}
|
||||
return data->kind;
|
||||
}
|
||||
|
||||
std::string GetSinkName(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return data->sink->GetName();
|
||||
}
|
||||
|
||||
llvm::StringRef GetSinkName(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
return data->sink->GetName();
|
||||
}
|
||||
|
||||
std::string GetSinkDescription(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
llvm::SmallString<128> buf;
|
||||
return data->sink->GetDescription(buf);
|
||||
}
|
||||
|
||||
llvm::StringRef GetSinkDescription(CS_Sink sink,
|
||||
llvm::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return llvm::StringRef{};
|
||||
}
|
||||
return data->sink->GetDescription(buf);
|
||||
}
|
||||
|
||||
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
if (source == 0) {
|
||||
data->sink->SetSource(nullptr);
|
||||
} else {
|
||||
auto sourceData = Sources::GetInstance().Get(source);
|
||||
if (!sourceData) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
data->sink->SetSource(sourceData->source);
|
||||
}
|
||||
data->sourceHandle.store(source);
|
||||
Notifier::GetInstance().NotifySinkSourceChanged(data->sink->GetName(), sink,
|
||||
source);
|
||||
}
|
||||
|
||||
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return data->sourceHandle.load();
|
||||
}
|
||||
|
||||
CS_Property GetSinkSourceProperty(CS_Sink sink, llvm::StringRef name,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return GetSourceProperty(data->sourceHandle.load(), name, status);
|
||||
}
|
||||
|
||||
CS_Sink CopySink(CS_Sink sink, CS_Status* status) {
|
||||
if (sink == 0) return 0;
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
data->refCount++;
|
||||
return sink;
|
||||
}
|
||||
|
||||
void ReleaseSink(CS_Sink sink, CS_Status* status) {
|
||||
if (sink == 0) return;
|
||||
auto& inst = Sinks::GetInstance();
|
||||
auto data = inst.Get(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
if (data->refCount-- == 0) {
|
||||
Notifier::GetInstance().NotifySink(data->sink->GetName(), sink,
|
||||
CS_SINK_DESTROYED);
|
||||
inst.Free(sink);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listener Functions
|
||||
//
|
||||
|
||||
void SetListenerOnStart(std::function<void()> onStart) {
|
||||
Notifier::GetInstance().SetOnStart(onStart);
|
||||
}
|
||||
|
||||
void SetListenerOnExit(std::function<void()> onExit) {
|
||||
Notifier::GetInstance().SetOnExit(onExit);
|
||||
}
|
||||
|
||||
CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask, bool immediateNotify,
|
||||
CS_Status* status) {
|
||||
int uid = Notifier::GetInstance().AddListener(callback, eventMask);
|
||||
if ((eventMask & CS_NETWORK_INTERFACES_CHANGED) != 0) {
|
||||
// start network interface event listener
|
||||
NetworkListener::GetInstance().Start();
|
||||
if (immediateNotify)
|
||||
Notifier::GetInstance().NotifyNetworkInterfacesChanged();
|
||||
}
|
||||
if (immediateNotify) {
|
||||
// TODO
|
||||
}
|
||||
return Handle{uid, Handle::kListener};
|
||||
}
|
||||
|
||||
void RemoveListener(CS_Listener handle, CS_Status* status) {
|
||||
int uid = Handle{handle}.GetTypedIndex(Handle::kListener);
|
||||
if (uid < 0) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
Notifier::GetInstance().RemoveListener(uid);
|
||||
}
|
||||
|
||||
bool NotifierDestroyed() { return Notifier::destroyed(); }
|
||||
|
||||
//
|
||||
// Telemetry Functions
|
||||
//
|
||||
void SetTelemetryPeriod(double seconds) {
|
||||
Telemetry::GetInstance().Start();
|
||||
Telemetry::GetInstance().SetPeriod(seconds);
|
||||
}
|
||||
|
||||
double GetTelemetryElapsedTime() {
|
||||
return Telemetry::GetInstance().GetElapsedTime();
|
||||
}
|
||||
|
||||
int64_t GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
return Telemetry::GetInstance().GetValue(handle, kind, status);
|
||||
}
|
||||
|
||||
double GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
return Telemetry::GetInstance().GetAverageValue(handle, kind, status);
|
||||
}
|
||||
|
||||
//
|
||||
// Logging Functions
|
||||
//
|
||||
void SetLogger(LogFunc func, unsigned int min_level) {
|
||||
Logger& logger = Logger::GetInstance();
|
||||
logger.SetLogger(func);
|
||||
logger.set_min_level(min_level);
|
||||
}
|
||||
|
||||
void SetDefaultLogger(unsigned int min_level) {
|
||||
Logger& logger = Logger::GetInstance();
|
||||
logger.SetDefaultLogger();
|
||||
logger.set_min_level(min_level);
|
||||
}
|
||||
|
||||
//
|
||||
// Utility Functions
|
||||
//
|
||||
|
||||
llvm::ArrayRef<CS_Source> EnumerateSourceHandles(
|
||||
llvm::SmallVectorImpl<CS_Source>& vec, CS_Status* status) {
|
||||
return Sources::GetInstance().GetAll(vec);
|
||||
}
|
||||
|
||||
llvm::ArrayRef<CS_Sink> EnumerateSinkHandles(
|
||||
llvm::SmallVectorImpl<CS_Sink>& vec, CS_Status* status) {
|
||||
return Sinks::GetInstance().GetAll(vec);
|
||||
}
|
||||
|
||||
std::string GetHostname() {
|
||||
#ifdef __linux__
|
||||
char name[256];
|
||||
if (::gethostname(name, sizeof(name)) != 0) return "";
|
||||
name[255] = '\0'; // Per POSIX, may not be null terminated if too long
|
||||
return name;
|
||||
#else
|
||||
return ""; // TODO
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::string> GetNetworkInterfaces() {
|
||||
#ifdef __linux__
|
||||
struct ifaddrs* ifa;
|
||||
if (::getifaddrs(&ifa) != 0) return std::vector<std::string>{};
|
||||
|
||||
std::vector<std::string> rv;
|
||||
char buf[256];
|
||||
for (struct ifaddrs* i = ifa; i; i = i->ifa_next) {
|
||||
if (!i->ifa_addr) continue; // no address
|
||||
if (i->ifa_addr->sa_family != AF_INET) continue; // only return IPv4
|
||||
struct sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
|
||||
const char* addr =
|
||||
::inet_ntop(addr_in->sin_family, &addr_in->sin_addr, buf, sizeof(buf));
|
||||
if (!addr) continue; // error converting address
|
||||
rv.emplace_back(addr);
|
||||
}
|
||||
|
||||
::freeifaddrs(ifa);
|
||||
return rv;
|
||||
#else
|
||||
return std::vector<std::string>{}; // TODO
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
55
cscore/src/main/native/cpp/cscore_oo.cpp
Normal file
55
cscore/src/main/native/cpp/cscore_oo.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "cscore_oo.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
|
||||
llvm::SmallVector<CS_Property, 32> handles_buf;
|
||||
CS_Status status = 0;
|
||||
auto handles = EnumerateSourceProperties(m_handle, handles_buf, &status);
|
||||
|
||||
std::vector<VideoProperty> properties;
|
||||
properties.reserve(handles.size());
|
||||
for (CS_Property handle : handles)
|
||||
properties.emplace_back(VideoProperty{handle});
|
||||
return properties;
|
||||
}
|
||||
|
||||
std::vector<VideoSink> VideoSource::EnumerateSinks() {
|
||||
llvm::SmallVector<CS_Sink, 16> handles_buf;
|
||||
CS_Status status = 0;
|
||||
auto handles = EnumerateSourceSinks(m_handle, handles_buf, &status);
|
||||
|
||||
std::vector<VideoSink> sinks;
|
||||
sinks.reserve(handles.size());
|
||||
for (int handle : handles) sinks.emplace_back(VideoSink{handle});
|
||||
return sinks;
|
||||
}
|
||||
|
||||
std::vector<VideoSource> VideoSource::EnumerateSources() {
|
||||
llvm::SmallVector<CS_Source, 16> handles_buf;
|
||||
CS_Status status = 0;
|
||||
auto handles = ::cs::EnumerateSourceHandles(handles_buf, &status);
|
||||
|
||||
std::vector<VideoSource> sources;
|
||||
sources.reserve(handles.size());
|
||||
for (int handle : handles) sources.emplace_back(VideoSource{handle});
|
||||
return sources;
|
||||
}
|
||||
|
||||
std::vector<VideoSink> VideoSink::EnumerateSinks() {
|
||||
llvm::SmallVector<CS_Sink, 16> handles_buf;
|
||||
CS_Status status = 0;
|
||||
auto handles = ::cs::EnumerateSinkHandles(handles_buf, &status);
|
||||
|
||||
std::vector<VideoSink> sinks;
|
||||
sinks.reserve(handles.size());
|
||||
for (int handle : handles) sinks.emplace_back(VideoSink{handle});
|
||||
return sinks;
|
||||
}
|
||||
41
cscore/src/main/native/cpp/default_init_allocator.h
Normal file
41
cscore/src/main/native/cpp/default_init_allocator.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// From:
|
||||
// http://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container
|
||||
// Credits: Casey and Howard Hinnant
|
||||
|
||||
#ifndef CSCORE_DEFAULT_INIT_ALLOCATOR_H_
|
||||
#define CSCORE_DEFAULT_INIT_ALLOCATOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace cs {
|
||||
|
||||
// Allocator adaptor that interposes construct() calls to
|
||||
// convert value initialization into default initialization.
|
||||
template <typename T, typename A = std::allocator<T>>
|
||||
class default_init_allocator : public A {
|
||||
typedef std::allocator_traits<A> a_t;
|
||||
|
||||
public:
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other =
|
||||
default_init_allocator<U, typename a_t::template rebind_alloc<U>>;
|
||||
};
|
||||
|
||||
using A::A;
|
||||
|
||||
template <typename U>
|
||||
void construct(U* ptr) noexcept(
|
||||
std::is_nothrow_default_constructible<U>::value) {
|
||||
::new (static_cast<void*>(ptr)) U;
|
||||
}
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* ptr, Args&&... args) {
|
||||
a_t::construct(static_cast<A&>(*this), ptr, std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_DEFAULT_INIT_ALLOCATOR_H_
|
||||
1576
cscore/src/main/native/cpp/jni/CameraServerJNI.cpp
Normal file
1576
cscore/src/main/native/cpp/jni/CameraServerJNI.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user