diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java index 06f0b4733a..cb6202d7c7 100644 --- a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java +++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java @@ -147,6 +147,8 @@ public class CameraServerJNI { public static native String getSinkDescription(int sink); public static native int getSinkProperty(int sink, String name); public static native int[] enumerateSinkProperties(int sink); + public static native boolean setSinkConfigJson(int sink, String config); + public static native String getSinkConfigJson(int sink); public static native void setSinkSource(int sink, int source); public static native int getSinkSourceProperty(int sink, String name); public static native int getSinkSource(int sink); diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoSink.java b/cscore/src/main/java/edu/wpi/cscore/VideoSink.java index 1301c9dffa..a59a952b93 100644 --- a/cscore/src/main/java/edu/wpi/cscore/VideoSink.java +++ b/cscore/src/main/java/edu/wpi/cscore/VideoSink.java @@ -133,6 +133,38 @@ public class VideoSink implements AutoCloseable { return rv; } + /** + * Set properties from a JSON configuration string. + * + *

The format of the JSON input is: + * + *

+   * {
+   *     "properties": [
+   *         {
+   *             "name": property name
+   *             "value": property value
+   *         }
+   *     ]
+   * }
+   * 
+ * + * @param config configuration + * @return True if set successfully + */ + public boolean setConfigJson(String config) { + return CameraServerJNI.setSinkConfigJson(m_handle, config); + } + + /** + * Get a JSON configuration string. + * + * @return JSON configuration string + */ + public String getConfigJson() { + return CameraServerJNI.getSinkConfigJson(m_handle); + } + /** * Configure which source should provide frames to this sink. Each sink * can accept frames from only a single source, but a single source can diff --git a/cscore/src/main/native/cpp/PropertyContainer.cpp b/cscore/src/main/native/cpp/PropertyContainer.cpp index d7fae628ab..46ea77dc7d 100644 --- a/cscore/src/main/native/cpp/PropertyContainer.cpp +++ b/cscore/src/main/native/cpp/PropertyContainer.cpp @@ -7,6 +7,11 @@ #include "PropertyContainer.h" +#include +#include +#include +#include + using namespace cs; int PropertyContainer::GetPropertyIndex(const wpi::Twine& name) const { @@ -204,3 +209,74 @@ bool PropertyContainer::CacheProperties(CS_Status* status) const { m_properties_cached = true; return true; } + +bool PropertyContainer::SetPropertiesJson(const wpi::json& config, + wpi::Logger& logger, + wpi::StringRef logName, + CS_Status* status) { + for (auto&& prop : config) { + std::string name; + try { + name = prop.at("name").get(); + } catch (const wpi::json::exception& e) { + WPI_WARNING(logger, + logName << ": 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(); + WPI_INFO(logger, logName << ": SetConfigJson: setting property '" + << name << "' to '" << val << '\''); + SetStringProperty(n, val, status); + } else if (v.is_boolean()) { + bool val = v.get(); + WPI_INFO(logger, logName << ": SetConfigJson: setting property '" + << name << "' to " << val); + SetProperty(n, val, status); + } else { + int val = v.get(); + WPI_INFO(logger, logName << ": SetConfigJson: setting property '" + << name << "' to " << val); + SetProperty(n, val, status); + } + } catch (const wpi::json::exception& e) { + WPI_WARNING(logger, + logName << ": SetConfigJson: could not read property value: " + << e.what()); + continue; + } + } + + return true; +} + +wpi::json PropertyContainer::GetPropertiesJsonObject(CS_Status* status) { + wpi::json j; + wpi::SmallVector 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(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; + } + j.emplace_back(prop); + } + + return j; +} diff --git a/cscore/src/main/native/cpp/PropertyContainer.h b/cscore/src/main/native/cpp/PropertyContainer.h index 4aeaf11152..19197b2659 100644 --- a/cscore/src/main/native/cpp/PropertyContainer.h +++ b/cscore/src/main/native/cpp/PropertyContainer.h @@ -24,6 +24,11 @@ #include "PropertyImpl.h" #include "cscore_cpp.h" +namespace wpi { +class Logger; +class json; +} // namespace wpi + namespace cs { class PropertyContainer { @@ -50,6 +55,10 @@ class PropertyContainer { std::vector GetEnumPropertyChoices(int property, CS_Status* status) const; + bool SetPropertiesJson(const wpi::json& config, wpi::Logger& logger, + wpi::StringRef logName, CS_Status* status); + wpi::json GetPropertiesJsonObject(CS_Status* status); + protected: // Get a property; must be called with m_mutex held. PropertyImpl* GetProperty(int property) { diff --git a/cscore/src/main/native/cpp/SinkImpl.cpp b/cscore/src/main/native/cpp/SinkImpl.cpp index 2ee057549a..637b407f28 100644 --- a/cscore/src/main/native/cpp/SinkImpl.cpp +++ b/cscore/src/main/native/cpp/SinkImpl.cpp @@ -7,6 +7,8 @@ #include "SinkImpl.h" +#include + #include "Instance.h" #include "Notifier.h" #include "SourceImpl.h" @@ -102,6 +104,43 @@ wpi::StringRef SinkImpl::GetError(wpi::SmallVectorImpl& buf) const { return wpi::StringRef{buf.data(), buf.size()}; } +bool SinkImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) { + wpi::json j; + try { + j = wpi::json::parse(config); + } catch (const 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 SinkImpl::SetConfigJson(const wpi::json& config, CS_Status* status) { + if (config.count("properties") != 0) + SetPropertiesJson(config.at("properties"), m_logger, GetName(), status); + + return true; +} + +std::string SinkImpl::GetConfigJson(CS_Status* status) { + std::string rv; + wpi::raw_string_ostream os(rv); + GetConfigJsonObject(status).dump(os, 4); + os.flush(); + return rv; +} + +wpi::json SinkImpl::GetConfigJsonObject(CS_Status* status) { + wpi::json j; + + wpi::json props = GetPropertiesJsonObject(status); + if (props.is_array()) j.emplace("properties", props); + + return j; +} + void SinkImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) { m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CREATED, prop.name, propIndex, prop.propKind, prop.value, diff --git a/cscore/src/main/native/cpp/SinkImpl.h b/cscore/src/main/native/cpp/SinkImpl.h index c51a7950b6..147268fe99 100644 --- a/cscore/src/main/native/cpp/SinkImpl.h +++ b/cscore/src/main/native/cpp/SinkImpl.h @@ -18,6 +18,10 @@ #include "SourceImpl.h" +namespace wpi { +class json; +} // namespace wpi + namespace cs { class Frame; @@ -51,6 +55,11 @@ class SinkImpl : public PropertyContainer { std::string GetError() const; wpi::StringRef GetError(wpi::SmallVectorImpl& buf) const; + 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); + protected: // PropertyContainer implementation void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) override; diff --git a/cscore/src/main/native/cpp/SourceImpl.cpp b/cscore/src/main/native/cpp/SourceImpl.cpp index 01b6e0f17a..e646eb6187 100644 --- a/cscore/src/main/native/cpp/SourceImpl.cpp +++ b/cscore/src/main/native/cpp/SourceImpl.cpp @@ -319,38 +319,8 @@ bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) { } // properties - if (config.count("properties") != 0) { - for (auto&& prop : config.at("properties")) { - std::string name; - try { - name = prop.at("name").get(); - } catch (const 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(); - SINFO("SetConfigJson: setting property '" << name << "' to '" << val - << '\''); - SetStringProperty(n, val, status); - } else if (v.is_boolean()) { - bool val = v.get(); - SINFO("SetConfigJson: setting property '" << name << "' to " << val); - SetProperty(n, val, status); - } else { - int val = v.get(); - SINFO("SetConfigJson: setting property '" << name << "' to " << val); - SetProperty(n, val, status); - } - } catch (const wpi::json::exception& e) { - SWARNING("SetConfigJson: could not read property value: " << e.what()); - continue; - } - } - } + if (config.count("properties") != 0) + SetPropertiesJson(config.at("properties"), m_logger, GetName(), status); return true; } @@ -401,28 +371,7 @@ wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) { // TODO: output brightness, white balance, and exposure? // properties - wpi::json props; - wpi::SmallVector 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(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); - } + wpi::json props = GetPropertiesJsonObject(status); if (props.is_array()) j.emplace("properties", props); return j; diff --git a/cscore/src/main/native/cpp/cscore_c.cpp b/cscore/src/main/native/cpp/cscore_c.cpp index 26d2c71075..1819d13835 100644 --- a/cscore/src/main/native/cpp/cscore_c.cpp +++ b/cscore/src/main/native/cpp/cscore_c.cpp @@ -277,6 +277,15 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count, return out; } +CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config, + CS_Status* status) { + return cs::SetSinkConfigJson(sink, config, status); +} + +char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status) { + return cs::ConvertToC(cs::GetSinkConfigJson(sink, status)); +} + void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) { return cs::SetSinkSource(sink, source, status); } diff --git a/cscore/src/main/native/cpp/cscore_cpp.cpp b/cscore/src/main/native/cpp/cscore_cpp.cpp index c40e9e26c0..83b4e8779c 100644 --- a/cscore/src/main/native/cpp/cscore_cpp.cpp +++ b/cscore/src/main/native/cpp/cscore_cpp.cpp @@ -564,6 +564,43 @@ wpi::ArrayRef EnumerateSinkProperties( return vec; } +bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->sink->SetConfigJson(config, status); +} + +bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config, + CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->sink->SetConfigJson(config, status); +} + +std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data) { + *status = CS_INVALID_HANDLE; + return std::string{}; + } + return data->sink->GetConfigJson(status); +} + +wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status) { + auto data = Instance::GetInstance().GetSink(sink); + if (!data) { + *status = CS_INVALID_HANDLE; + return wpi::json{}; + } + return data->sink->GetConfigJsonObject(status); +} + void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) { auto data = Instance::GetInstance().GetSink(sink); if (!data) { diff --git a/cscore/src/main/native/cpp/cscore_oo.cpp b/cscore/src/main/native/cpp/cscore_oo.cpp index cbb6b148ba..f455273eaa 100644 --- a/cscore/src/main/native/cpp/cscore_oo.cpp +++ b/cscore/src/main/native/cpp/cscore_oo.cpp @@ -16,6 +16,11 @@ wpi::json VideoSource::GetConfigJsonObject() const { return GetSourceConfigJsonObject(m_handle, &m_status); } +wpi::json VideoSink::GetConfigJsonObject() const { + m_status = 0; + return GetSinkConfigJsonObject(m_handle, &m_status); +} + std::vector VideoSource::EnumerateProperties() const { wpi::SmallVector handles_buf; CS_Status status = 0; diff --git a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp index fa455894d0..528d5e4110 100644 --- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp +++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp @@ -1292,6 +1292,36 @@ Java_edu_wpi_cscore_CameraServerJNI_enumerateSinkProperties return MakeJIntArray(env, arr); } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: setSinkConfigJson + * Signature: (ILjava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_cscore_CameraServerJNI_setSinkConfigJson + (JNIEnv* env, jclass, jint source, jstring config) +{ + CS_Status status = 0; + auto val = cs::SetSinkConfigJson(source, JStringRef{env, config}, &status); + CheckStatus(env, status); + return val; +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: getSinkConfigJson + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL +Java_edu_wpi_cscore_CameraServerJNI_getSinkConfigJson + (JNIEnv* env, jclass, jint source) +{ + CS_Status status = 0; + auto val = cs::GetSinkConfigJson(source, &status); + CheckStatus(env, status); + return MakeJString(env, val); +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: setSinkSource diff --git a/cscore/src/main/native/include/cscore_c.h b/cscore/src/main/native/include/cscore_c.h index 0cc354bb6a..182b9b2df9 100644 --- a/cscore/src/main/native/include/cscore_c.h +++ b/cscore/src/main/native/include/cscore_c.h @@ -393,6 +393,9 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count, void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status); CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name, CS_Status* status); +CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config, + CS_Status* status); +char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status); CS_Source CS_GetSinkSource(CS_Sink sink, CS_Status* status); CS_Sink CS_CopySink(CS_Sink sink, CS_Status* status); void CS_ReleaseSink(CS_Sink sink, CS_Status* status); diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h index 040b726fd2..a400b783b2 100644 --- a/cscore/src/main/native/include/cscore_cpp.h +++ b/cscore/src/main/native/include/cscore_cpp.h @@ -332,6 +332,11 @@ wpi::ArrayRef EnumerateSinkProperties( void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status); CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name, CS_Status* status); +bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status); +bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config, + CS_Status* status); +std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status); +wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status); CS_Source GetSinkSource(CS_Sink sink, CS_Status* status); CS_Sink CopySink(CS_Sink sink, CS_Status* status); void ReleaseSink(CS_Sink sink, CS_Status* status); diff --git a/cscore/src/main/native/include/cscore_oo.h b/cscore/src/main/native/include/cscore_oo.h index 9a8b3a0c1f..7e0cb9f43c 100644 --- a/cscore/src/main/native/include/cscore_oo.h +++ b/cscore/src/main/native/include/cscore_oo.h @@ -807,6 +807,49 @@ class VideoSink { */ std::vector EnumerateProperties() const; + /** + * Set properties from a JSON configuration string. + * + * The format of the JSON input is: + * + *
+   * {
+   *     "properties": [
+   *         {
+   *             "name": property name
+   *             "value": property value
+   *         }
+   *     ]
+   * }
+   * 
+ * + * @param config configuration + * @return True if set successfully + */ + bool SetConfigJson(wpi::StringRef config); + + /** + * Set properties from a JSON configuration object. + * + * @param config configuration + * @return True if set successfully + */ + bool SetConfigJson(const wpi::json& config); + + /** + * Get a JSON configuration string. + * + * @return JSON configuration string + */ + std::string GetConfigJson() const; + + /** + * Get a JSON configuration object. + * + * @return JSON configuration object + */ + wpi::json GetConfigJsonObject() const; + /** * Configure which source should provide frames to this sink. Each sink * can accept frames from only a single source, but a single source can diff --git a/cscore/src/main/native/include/cscore_oo.inl b/cscore/src/main/native/include/cscore_oo.inl index 1767452fae..5162e1db88 100644 --- a/cscore/src/main/native/include/cscore_oo.inl +++ b/cscore/src/main/native/include/cscore_oo.inl @@ -521,6 +521,21 @@ inline VideoProperty VideoSink::GetSourceProperty(const wpi::Twine& name) { return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)}; } +inline bool VideoSink::SetConfigJson(wpi::StringRef config) { + m_status = 0; + return SetSinkConfigJson(m_handle, config, &m_status); +} + +inline bool VideoSink::SetConfigJson(const wpi::json& config) { + m_status = 0; + return SetSinkConfigJson(m_handle, config, &m_status); +} + +inline std::string VideoSink::GetConfigJson() const { + m_status = 0; + return GetSinkConfigJson(m_handle, &m_status); +} + inline MjpegServer::MjpegServer(const wpi::Twine& name, const wpi::Twine& listenAddress, int port) { m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);