cscore: Add JSON for source settings (#1423)

This allows save and restore of camera settings.  The restore is a bit
smarter than the save.

* Fix mime types in mjpeg server

* wpiutil: WPI_LOG: Make sure level is an unsigned int
This commit is contained in:
Peter Johnson
2018-11-10 20:30:02 -08:00
committed by GitHub
parent 43d188a429
commit 9bc998f4b0
14 changed files with 501 additions and 5 deletions

View File

@@ -67,7 +67,8 @@ static const char* startRootPage =
"</head><body>\n"
"<div class=\"stream\">\n"
"<img src=\"/stream.mjpg\" /><p />\n"
"<a href=\"/settings.json\">Settings JSON</a>\n"
"<a href=\"/settings.json\">Settings JSON</a> |\n"
"<a href=\"/config.json\">Source Config JSON</a>\n"
"</div>\n"
"<div class=\"settings\">\n";
static const char* endRootPage = "</div></body></html>";
@@ -340,7 +341,7 @@ void MjpegServerImpl::ConnThread::SendHTMLHeadTitle(
// Send the root html file with controls for all the settable properties.
void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
SourceImpl& source, bool header) {
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
if (header) SendHeader(os, 200, "OK", "text/html");
SendHTMLHeadTitle(os);
os << startRootPage;
@@ -453,7 +454,7 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
// Send a JSON file which is contains information about the source parameters.
void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
SourceImpl& source, bool header) {
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
if (header) SendHeader(os, 200, "OK", "application/json");
os << "{\n\"controls\": [\n";
wpi::SmallVector<int, 32> properties_vec;
@@ -728,7 +729,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
return;
}
enum { kCommand, kStream, kGetSettings, kRootPage } kind;
enum { kCommand, kStream, kGetSettings, kGetSourceConfig, kRootPage } kind;
wpi::StringRef parameters;
size_t pos;
@@ -748,6 +749,9 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
} else if (req.find("GET /settings") != wpi::StringRef::npos &&
req.find(".json") != wpi::StringRef::npos) {
kind = kGetSettings;
} else if (req.find("GET /config") != wpi::StringRef::npos &&
req.find(".json") != wpi::StringRef::npos) {
kind = kGetSourceConfig;
} else if (req.find("GET /input") != wpi::StringRef::npos &&
req.find(".json") != wpi::StringRef::npos) {
kind = kGetSettings;
@@ -806,6 +810,17 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
else
SendError(os, 404, "Resource not found");
break;
case kGetSourceConfig:
SDEBUG("request for JSON file");
if (auto source = GetSource()) {
SendHeader(os, 200, "OK", "application/json");
CS_Status status = CS_OK;
os << source->GetConfigJson(&status);
os.flush();
} else {
SendError(os, 404, "Resource not found");
}
break;
case kRootPage:
SDEBUG("request for root page");
SendHeader(os, 200, "OK", "text/html");

View File

@@ -11,6 +11,7 @@
#include <cstring>
#include <wpi/STLExtras.h>
#include <wpi/json.h>
#include <wpi/timestamp.h>
#include "Log.h"
@@ -161,6 +162,272 @@ bool SourceImpl::SetFPS(int fps, CS_Status* status) {
return SetVideoMode(mode, status);
}
bool SourceImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) {
wpi::json j;
try {
j = wpi::json::parse(config);
} catch (wpi::json::parse_error e) {
SWARNING("SetConfigJson: parse error at byte " << e.byte << ": "
<< e.what());
*status = CS_PROPERTY_WRITE_FAILED;
return false;
}
return SetConfigJson(j, status);
}
bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
VideoMode mode;
// pixel format
if (config.count("pixel format") != 0) {
try {
auto str = config.at("pixel format").get<std::string>();
wpi::StringRef s(str);
if (s.equals_lower("mjpeg")) {
mode.pixelFormat = cs::VideoMode::kMJPEG;
} else if (s.equals_lower("yuyv")) {
mode.pixelFormat = cs::VideoMode::kYUYV;
} else if (s.equals_lower("rgb565")) {
mode.pixelFormat = cs::VideoMode::kRGB565;
} else if (s.equals_lower("bgr")) {
mode.pixelFormat = cs::VideoMode::kBGR;
} else if (s.equals_lower("gray")) {
mode.pixelFormat = cs::VideoMode::kGray;
} else {
SWARNING("SetConfigJson: could not understand pixel format value '"
<< str << '\'');
}
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read pixel format: " << e.what());
}
}
// width
if (config.count("width") != 0) {
try {
mode.width = config.at("width").get<unsigned int>();
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read width: " << e.what());
}
}
// height
if (config.count("height") != 0) {
try {
mode.height = config.at("height").get<unsigned int>();
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read height: " << e.what());
}
}
// fps
if (config.count("fps") != 0) {
try {
mode.fps = config.at("fps").get<unsigned int>();
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read fps: " << e.what());
}
}
// if all of video mode is set, use SetVideoMode, otherwise piecemeal it
if (mode.pixelFormat != VideoMode::kUnknown && mode.width != 0 &&
mode.height != 0 && mode.fps != 0) {
SINFO("SetConfigJson: setting video mode to pixelFormat "
<< mode.pixelFormat << ", width " << mode.width << ", height "
<< mode.height << ", fps " << mode.fps);
SetVideoMode(mode, status);
} else {
if (mode.pixelFormat != cs::VideoMode::kUnknown) {
SINFO("SetConfigJson: setting pixelFormat " << mode.pixelFormat);
SetPixelFormat(static_cast<cs::VideoMode::PixelFormat>(mode.pixelFormat),
status);
}
if (mode.width != 0 && mode.height != 0) {
SINFO("SetConfigJson: setting width " << mode.width << ", height "
<< mode.height);
SetResolution(mode.width, mode.height, status);
}
if (mode.fps != 0) {
SINFO("SetConfigJson: setting fps " << mode.fps);
SetFPS(mode.fps, status);
}
}
// brightness
if (config.count("brightness") != 0) {
try {
int val = config.at("brightness").get<int>();
SINFO("SetConfigJson: setting brightness to " << val);
SetBrightness(val, status);
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read brightness: " << e.what());
}
}
// white balance
if (config.count("white balance") != 0) {
try {
auto& setting = config.at("white balance");
if (setting.is_string()) {
auto str = setting.get<std::string>();
wpi::StringRef s(str);
if (s.equals_lower("auto")) {
SINFO("SetConfigJson: setting white balance to auto");
SetWhiteBalanceAuto(status);
} else if (s.equals_lower("hold")) {
SINFO("SetConfigJson: setting white balance to hold current");
SetWhiteBalanceHoldCurrent(status);
} else {
SWARNING("SetConfigJson: could not understand white balance value '"
<< str << '\'');
}
} else {
int val = setting.get<int>();
SINFO("SetConfigJson: setting white balance to " << val);
SetWhiteBalanceManual(val, status);
}
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read white balance: " << e.what());
}
}
// exposure
if (config.count("exposure") != 0) {
try {
auto& setting = config.at("exposure");
if (setting.is_string()) {
auto str = setting.get<std::string>();
wpi::StringRef s(str);
if (s.equals_lower("auto")) {
SINFO("SetConfigJson: setting exposure to auto");
SetExposureAuto(status);
} else if (s.equals_lower("hold")) {
SINFO("SetConfigJson: setting exposure to hold current");
SetExposureHoldCurrent(status);
} else {
SWARNING("SetConfigJson: could not understand exposure value '"
<< str << '\'');
}
} else {
int val = setting.get<int>();
SINFO("SetConfigJson: setting exposure to " << val);
SetExposureManual(val, status);
}
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read exposure: " << e.what());
}
}
// properties
if (config.count("properties") != 0) {
for (auto&& prop : config.at("properties")) {
std::string name;
try {
name = prop.at("name").get<std::string>();
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read property name: " << e.what());
continue;
}
int n = GetPropertyIndex(name);
try {
auto& v = prop.at("value");
if (v.is_string()) {
std::string val = v.get<std::string>();
SINFO("SetConfigJson: setting property '" << name << "' to '" << val
<< '\'');
SetStringProperty(n, val, status);
} else if (v.is_boolean()) {
bool val = v.get<bool>();
SINFO("SetConfigJson: setting property '" << name << "' to " << val);
SetProperty(n, val, status);
} else {
int val = v.get<int>();
SINFO("SetConfigJson: setting property '" << name << "' to " << val);
SetProperty(n, val, status);
}
} catch (wpi::json::exception e) {
SWARNING("SetConfigJson: could not read property value: " << e.what());
continue;
}
}
}
return true;
}
std::string SourceImpl::GetConfigJson(CS_Status* status) {
std::string rv;
wpi::raw_string_ostream os(rv);
GetConfigJsonObject(status).dump(os, 4);
os.flush();
return rv;
}
wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
wpi::json j;
// pixel format
wpi::StringRef pixelFormat;
switch (m_mode.pixelFormat) {
case VideoMode::kMJPEG:
pixelFormat = "mjpeg";
break;
case VideoMode::kYUYV:
pixelFormat = "yuyv";
break;
case VideoMode::kRGB565:
pixelFormat = "rgb565";
break;
case VideoMode::kBGR:
pixelFormat = "bgr";
break;
case VideoMode::kGray:
pixelFormat = "gray";
break;
default:
break;
}
if (!pixelFormat.empty()) j.emplace("pixel format", pixelFormat);
// width
if (m_mode.width != 0) j.emplace("width", m_mode.width);
// height
if (m_mode.height != 0) j.emplace("height", m_mode.height);
// fps
if (m_mode.fps != 0) j.emplace("fps", m_mode.fps);
// TODO: output brightness, white balance, and exposure?
// properties
wpi::json props;
wpi::SmallVector<int, 32> propVec;
for (int p : EnumerateProperties(propVec, status)) {
wpi::json prop;
wpi::SmallString<128> strBuf;
prop.emplace("name", GetPropertyName(p, strBuf, status));
switch (GetPropertyKind(p)) {
case CS_PROP_BOOLEAN:
prop.emplace("value", static_cast<bool>(GetProperty(p, status)));
break;
case CS_PROP_INTEGER:
case CS_PROP_ENUM:
prop.emplace("value", GetProperty(p, status));
break;
case CS_PROP_STRING:
prop.emplace("value", GetStringProperty(p, strBuf, status));
break;
default:
continue;
}
props.emplace_back(prop);
}
if (props.is_array()) j.emplace("properties", props);
return j;
}
std::vector<VideoMode> SourceImpl::EnumerateVideoModes(
CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status))

View File

@@ -27,6 +27,10 @@
#include "PropertyContainer.h"
#include "cscore_cpp.h"
namespace wpi {
class json;
} // namespace wpi
namespace cs {
class Notifier;
@@ -127,6 +131,11 @@ class SourceImpl : public PropertyContainer {
virtual bool SetResolution(int width, int height, CS_Status* status);
virtual bool SetFPS(int fps, CS_Status* status);
bool SetConfigJson(wpi::StringRef config, CS_Status* status);
virtual bool SetConfigJson(const wpi::json& config, CS_Status* status);
std::string GetConfigJson(CS_Status* status);
virtual wpi::json GetConfigJsonObject(CS_Status* status);
std::vector<VideoMode> EnumerateVideoModes(CS_Status* status) const;
std::unique_ptr<Image> AllocImage(VideoMode::PixelFormat pixelFormat,

View File

@@ -170,6 +170,15 @@ CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
return cs::SetSourceFPS(source, fps, status);
}
CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config,
CS_Status* status) {
return cs::SetSourceConfigJson(source, config, status);
}
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status) {
return cs::ConvertToC(cs::GetSourceConfigJson(source, status));
}
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
CS_Status* status) {
auto vec = cs::EnumerateSourceVideoModes(source, status);

View File

@@ -9,6 +9,7 @@
#include <wpi/SmallString.h>
#include <wpi/hostname.h>
#include <wpi/json.h>
#include "Handle.h"
#include "Instance.h"
@@ -324,6 +325,44 @@ bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
return data->source->SetFPS(fps, status);
}
bool SetSourceConfigJson(CS_Source source, wpi::StringRef config,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->SetConfigJson(config, status);
}
bool SetSourceConfigJson(CS_Source source, const wpi::json& config,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->SetConfigJson(config, status);
}
std::string GetSourceConfigJson(CS_Source source, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return data->source->GetConfigJson(status);
}
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::json{};
}
return data->source->GetConfigJsonObject(status);
}
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);

View File

@@ -7,8 +7,15 @@
#include "cscore_oo.h"
#include <wpi/json.h>
using namespace cs;
wpi::json VideoSource::GetConfigJsonObject() const {
m_status = 0;
return GetSourceConfigJsonObject(m_handle, &m_status);
}
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
wpi::SmallVector<CS_Property, 32> handles_buf;
CS_Status status = 0;

View File

@@ -751,6 +751,36 @@ Java_edu_wpi_cscore_CameraServerJNI_setSourceFPS
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: setSourceConfigJson
* Signature: (ILjava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_cscore_CameraServerJNI_setSourceConfigJson
(JNIEnv* env, jclass, jint source, jstring config)
{
CS_Status status = 0;
auto val = cs::SetSourceConfigJson(source, JStringRef{env, config}, &status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getSourceConfigJson
* Signature: (I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_edu_wpi_cscore_CameraServerJNI_getSourceConfigJson
(JNIEnv* env, jclass, jint source)
{
CS_Status status = 0;
auto val = cs::GetSourceConfigJson(source, &status);
CheckStatus(env, status);
return MakeJString(env, val);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: enumerateSourceVideoModes