diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java index 3dc8fe2c5e..a2a4052bac 100644 --- a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java +++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java @@ -206,6 +206,8 @@ public class CameraServerJNI { public static native int getSinkKind(int sink); public static native String getSinkName(int sink); 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 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/VideoEvent.java b/cscore/src/main/java/edu/wpi/cscore/VideoEvent.java index 835550669e..6c7c1e6a5c 100644 --- a/cscore/src/main/java/edu/wpi/cscore/VideoEvent.java +++ b/cscore/src/main/java/edu/wpi/cscore/VideoEvent.java @@ -28,7 +28,10 @@ public class VideoEvent { kSinkEnabled(0x1000), kSinkDisabled(0x2000), kNetworkInterfacesChanged(0x4000), - kTelemetryUpdated(0x8000); + kTelemetryUpdated(0x8000), + kSinkPropertyCreated(0x10000), + kSinkPropertyValueUpdated(0x20000), + kSinkPropertyChoicesUpdated(0x40000); @SuppressWarnings("MemberName") private int value; @@ -66,6 +69,9 @@ public class VideoEvent { case 0x1000: return Kind.kSinkEnabled; case 0x2000: return Kind.kSinkDisabled; case 0x4000: return Kind.kNetworkInterfacesChanged; + case 0x10000: return Kind.kSinkPropertyCreated; + case 0x20000: return Kind.kSinkPropertyValueUpdated; + case 0x40000: return Kind.kSinkPropertyChoicesUpdated; default: return Kind.kUnknown; } } diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoSink.java b/cscore/src/main/java/edu/wpi/cscore/VideoSink.java index 4b187f148a..9c4b972b26 100644 --- a/cscore/src/main/java/edu/wpi/cscore/VideoSink.java +++ b/cscore/src/main/java/edu/wpi/cscore/VideoSink.java @@ -109,6 +109,29 @@ public class VideoSink implements AutoCloseable { return CameraServerJNI.getSinkDescription(m_handle); } + /** + * Get a property of the sink. + * @param name Property name + * @return Property (kind Property::kNone if no property with + * the given name exists) + */ + public VideoProperty getProperty(String name) { + return new VideoProperty(CameraServerJNI.getSinkProperty(m_handle, name)); + } + + /** + * Enumerate all properties of this sink. + */ + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + public VideoProperty[] enumerateProperties() { + int[] handles = CameraServerJNI.enumerateSinkProperties(m_handle); + VideoProperty[] rv = new VideoProperty[handles.length]; + for (int i = 0; i < handles.length; i++) { + rv[i] = new VideoProperty(handles[i]); + } + return rv; + } + /** * 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/CvSourceImpl.cpp b/cscore/src/main/native/cpp/CvSourceImpl.cpp index 622ecb45f8..8a0ba80526 100644 --- a/cscore/src/main/native/cpp/CvSourceImpl.cpp +++ b/cscore/src/main/native/cpp/CvSourceImpl.cpp @@ -31,57 +31,6 @@ CvSourceImpl::~CvSourceImpl() {} void CvSourceImpl::Start() {} -std::unique_ptr CvSourceImpl::CreateEmptyProperty( - wpi::StringRef name) const { - return wpi::make_unique(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 lock(m_mutex); - auto prop = static_cast(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, wpi::StringRef{}); -} - -void CvSourceImpl::SetStringProperty(int property, wpi::StringRef value, - CS_Status* status) { - std::lock_guard lock(m_mutex); - auto prop = static_cast(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) { @@ -176,22 +125,21 @@ int CvSourceImpl::CreateProperty(wpi::StringRef name, CS_PropertyKind kind, int minimum, int maximum, int step, int defaultValue, int value) { std::lock_guard lock(m_mutex); - int& ndx = m_properties[name]; - if (ndx == 0) { - // create a new index - ndx = m_propertyData.size() + 1; - m_propertyData.emplace_back(wpi::make_unique( - 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; - } + int ndx = CreateOrUpdateProperty(name, + [=] { + return wpi::make_unique( + name, kind, minimum, maximum, step, + defaultValue, value); + }, + [&](PropertyImpl& prop) { + // update all but value + 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, wpi::StringRef{}); diff --git a/cscore/src/main/native/cpp/CvSourceImpl.h b/cscore/src/main/native/cpp/CvSourceImpl.h index 91d0c6925c..a6f27bd741 100644 --- a/cscore/src/main/native/cpp/CvSourceImpl.h +++ b/cscore/src/main/native/cpp/CvSourceImpl.h @@ -25,11 +25,6 @@ class CvSourceImpl : public SourceImpl { void Start(); - // Property functions - void SetProperty(int property, int value, CS_Status* status) override; - void SetStringProperty(int property, wpi::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; @@ -56,30 +51,6 @@ class CvSourceImpl : public SourceImpl { void SetEnumPropertyChoices(int property, wpi::ArrayRef choices, CS_Status* status); - // Property data - class PropertyData : public PropertyImpl { - public: - PropertyData() = default; - explicit PropertyData(wpi::StringRef name_) : PropertyImpl{name_} {} - PropertyData(wpi::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 onChange; - }; - - protected: - std::unique_ptr CreateEmptyProperty( - wpi::StringRef name) const override; - - bool CacheProperties(CS_Status* status) const override; - private: std::atomic_bool m_connected{true}; }; diff --git a/cscore/src/main/native/cpp/Handle.h b/cscore/src/main/native/cpp/Handle.h index 45aa0e9afe..a6449029b7 100644 --- a/cscore/src/main/native/cpp/Handle.h +++ b/cscore/src/main/native/cpp/Handle.h @@ -29,7 +29,14 @@ class SourceImpl; class Handle { public: - enum Type { kUndefined = 0, kProperty = 0x40, kSource, kSink, kListener }; + enum Type { + kUndefined = 0, + kProperty = 0x40, + kSource, + kSink, + kListener, + kSinkProperty + }; enum { kIndexMax = 0xffff }; Handle(CS_Handle handle) : m_handle(handle) {} // NOLINT @@ -58,8 +65,9 @@ class Handle { 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(m_handle) >> 16) & 0xff - : -1; + return (IsType(Handle::kProperty) || IsType(Handle::kSinkProperty)) + ? (static_cast(m_handle) >> 16) & 0xff + : -1; } private: diff --git a/cscore/src/main/native/cpp/Notifier.cpp b/cscore/src/main/native/cpp/Notifier.cpp index b4f0047771..830537717e 100644 --- a/cscore/src/main/native/cpp/Notifier.cpp +++ b/cscore/src/main/native/cpp/Notifier.cpp @@ -217,6 +217,22 @@ void Notifier::NotifySinkSourceChanged(wpi::StringRef name, CS_Sink sink, thr->m_cond.notify_one(); } +void Notifier::NotifySinkProperty(const SinkImpl& sink, CS_EventKind kind, + wpi::StringRef propertyName, int property, + CS_PropertyKind propertyKind, int value, + wpi::StringRef valueStr) { + auto thr = m_owner.GetThread(); + if (!thr) return; + + auto handleData = Sinks::GetInstance().Find(sink); + + thr->m_notifications.emplace( + propertyName, handleData.first, static_cast(kind), + Handle{handleData.first, property, Handle::kSinkProperty}, propertyKind, + value, valueStr); + thr->m_cond.notify_one(); +} + void Notifier::NotifyNetworkInterfacesChanged() { auto thr = m_owner.GetThread(); if (!thr) return; diff --git a/cscore/src/main/native/cpp/Notifier.h b/cscore/src/main/native/cpp/Notifier.h index 05714ccf59..2de7754a90 100644 --- a/cscore/src/main/native/cpp/Notifier.h +++ b/cscore/src/main/native/cpp/Notifier.h @@ -53,6 +53,10 @@ class Notifier { void NotifySink(const SinkImpl& sink, CS_EventKind kind); void NotifySinkSourceChanged(wpi::StringRef name, CS_Sink sink, CS_Source source); + void NotifySinkProperty(const SinkImpl& sink, CS_EventKind kind, + wpi::StringRef propertyName, int property, + CS_PropertyKind propertyKind, int value, + wpi::StringRef valueStr); void NotifyNetworkInterfacesChanged(); void NotifyTelemetryUpdated(); diff --git a/cscore/src/main/native/cpp/PropertyContainer.cpp b/cscore/src/main/native/cpp/PropertyContainer.cpp new file mode 100644 index 0000000000..177b3f5579 --- /dev/null +++ b/cscore/src/main/native/cpp/PropertyContainer.cpp @@ -0,0 +1,205 @@ +/*----------------------------------------------------------------------------*/ +/* 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 "PropertyContainer.h" + +using namespace cs; + +int PropertyContainer::GetPropertyIndex(wpi::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 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; +} + +wpi::ArrayRef PropertyContainer::EnumerateProperties( + wpi::SmallVectorImpl& vec, CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) + return wpi::ArrayRef{}; + std::lock_guard lock(m_mutex); + for (int i = 0; i < static_cast(m_propertyData.size()); ++i) { + if (m_propertyData[i]) vec.push_back(i + 1); + } + return vec; +} + +CS_PropertyKind PropertyContainer::GetPropertyKind(int property) const { + CS_Status status = 0; + if (!m_properties_cached && !CacheProperties(&status)) return CS_PROP_NONE; + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) return CS_PROP_NONE; + return prop->propKind; +} + +wpi::StringRef PropertyContainer::GetPropertyName( + int property, wpi::SmallVectorImpl& buf, CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) return wpi::StringRef{}; + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) { + *status = CS_INVALID_PROPERTY; + return wpi::StringRef{}; + } + // safe to not copy because we never modify it after caching + return prop->name; +} + +int PropertyContainer::GetProperty(int property, CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) return 0; + std::lock_guard 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; +} + +void PropertyContainer::SetProperty(int property, int value, + CS_Status* status) { + std::lock_guard lock(m_mutex); + auto prop = 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, wpi::StringRef{}); +} + +int PropertyContainer::GetPropertyMin(int property, CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) return 0; + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) { + *status = CS_INVALID_PROPERTY; + return 0; + } + return prop->minimum; +} + +int PropertyContainer::GetPropertyMax(int property, CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) return 0; + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) { + *status = CS_INVALID_PROPERTY; + return 0; + } + return prop->maximum; +} + +int PropertyContainer::GetPropertyStep(int property, CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) return 0; + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) { + *status = CS_INVALID_PROPERTY; + return 0; + } + return prop->step; +} + +int PropertyContainer::GetPropertyDefault(int property, + CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) return 0; + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) { + *status = CS_INVALID_PROPERTY; + return 0; + } + return prop->defaultValue; +} + +wpi::StringRef PropertyContainer::GetStringProperty( + int property, wpi::SmallVectorImpl& buf, CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) return wpi::StringRef{}; + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) { + *status = CS_INVALID_PROPERTY; + return wpi::StringRef{}; + } + if (prop->propKind != CS_PROP_STRING) { + *status = CS_WRONG_PROPERTY_TYPE; + return wpi::StringRef{}; + } + buf.clear(); + buf.append(prop->valueStr.begin(), prop->valueStr.end()); + return wpi::StringRef(buf.data(), buf.size()); +} + +void PropertyContainer::SetStringProperty(int property, wpi::StringRef value, + CS_Status* status) { + std::lock_guard lock(m_mutex); + auto prop = 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); +} + +std::vector PropertyContainer::GetEnumPropertyChoices( + int property, CS_Status* status) const { + if (!m_properties_cached && !CacheProperties(status)) + return std::vector{}; + std::lock_guard lock(m_mutex); + auto prop = GetProperty(property); + if (!prop) { + *status = CS_INVALID_PROPERTY; + return std::vector{}; + } + if (prop->propKind != CS_PROP_ENUM) { + *status = CS_WRONG_PROPERTY_TYPE; + return std::vector{}; + } + return prop->enumChoices; +} + +std::unique_ptr PropertyContainer::CreateEmptyProperty( + wpi::StringRef name) const { + return wpi::make_unique(name); +} + +bool PropertyContainer::CacheProperties(CS_Status* status) const { + // Doesn't need to do anything. + m_properties_cached = true; + return true; +} diff --git a/cscore/src/main/native/cpp/PropertyContainer.h b/cscore/src/main/native/cpp/PropertyContainer.h new file mode 100644 index 0000000000..5dad43625a --- /dev/null +++ b/cscore/src/main/native/cpp/PropertyContainer.h @@ -0,0 +1,113 @@ +/*----------------------------------------------------------------------------*/ +/* 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_PROPERTYCONTAINER_H_ +#define CSCORE_PROPERTYCONTAINER_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "PropertyImpl.h" +#include "cscore_cpp.h" + +namespace cs { + +class PropertyContainer { + public: + virtual ~PropertyContainer() = default; + + int GetPropertyIndex(wpi::StringRef name) const; + wpi::ArrayRef EnumerateProperties(wpi::SmallVectorImpl& vec, + CS_Status* status) const; + CS_PropertyKind GetPropertyKind(int property) const; + wpi::StringRef GetPropertyName(int property, wpi::SmallVectorImpl& buf, + CS_Status* status) const; + int GetProperty(int property, CS_Status* status) const; + virtual void SetProperty(int property, int value, CS_Status* status); + 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; + wpi::StringRef GetStringProperty(int property, + wpi::SmallVectorImpl& buf, + CS_Status* status) const; + virtual void SetStringProperty(int property, wpi::StringRef value, + CS_Status* status); + std::vector GetEnumPropertyChoices(int property, + CS_Status* status) const; + + protected: + // Get a property; must be called with m_mutex held. + PropertyImpl* GetProperty(int property) { + if (property <= 0 || static_cast(property) > m_propertyData.size()) + return nullptr; + return m_propertyData[property - 1].get(); + } + const PropertyImpl* GetProperty(int property) const { + if (property <= 0 || static_cast(property) > m_propertyData.size()) + return nullptr; + return m_propertyData[property - 1].get(); + } + // Create or update a property; must be called with m_mutex held. + // @tparam NewFunc functor that returns a std::unique_ptr + // @tparam UpdateFunc functor that takes a PropertyImpl&. + template + int CreateOrUpdateProperty(wpi::StringRef name, NewFunc newFunc, + UpdateFunc updateFunc) { + int& ndx = m_properties[name]; + if (ndx == 0) { + // create a new index + ndx = m_propertyData.size() + 1; + m_propertyData.emplace_back(newFunc()); + } else { + // update existing + updateFunc(*GetProperty(ndx)); + } + return ndx; + } + + // 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. + // The default implementation simply creates a PropertyImpl object. + virtual std::unique_ptr CreateEmptyProperty( + wpi::StringRef name) const; + + // Cache properties. Implementations must return false and set status to + // CS_SOURCE_IS_DISCONNECTED if not possible to cache. + // The default implementation simply sets m_property_cached to true. + virtual bool CacheProperties(CS_Status* status) const; + + virtual void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) = 0; + + // Update property value; must be called with m_mutex held. + virtual void UpdatePropertyValue(int property, bool setString, int value, + wpi::StringRef valueStr) = 0; + + // 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; + + // Cached properties (protected with m_mutex) + mutable std::vector> m_propertyData; + mutable wpi::StringMap m_properties; +}; + +} // namespace cs + +#endif // CSCORE_PROPERTYCONTAINER_H_ diff --git a/cscore/src/main/native/cpp/PropertyImpl.cpp b/cscore/src/main/native/cpp/PropertyImpl.cpp new file mode 100644 index 0000000000..40c094080b --- /dev/null +++ b/cscore/src/main/native/cpp/PropertyImpl.cpp @@ -0,0 +1,64 @@ +/*----------------------------------------------------------------------------*/ +/* 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 "PropertyImpl.h" + +using namespace cs; + +PropertyImpl::PropertyImpl(wpi::StringRef name_) : name{name_} {} +PropertyImpl::PropertyImpl(wpi::StringRef name_, CS_PropertyKind kind_, + int step_, int defaultValue_, int value_) + : name{name_}, + propKind{kind_}, + step{step_}, + defaultValue{defaultValue_}, + value{value_} {} +PropertyImpl::PropertyImpl(wpi::StringRef name_, CS_PropertyKind kind_, + int minimum_, int maximum_, int step_, + int defaultValue_, int value_) + : name{name_}, + propKind{kind_}, + hasMinimum{true}, + hasMaximum{true}, + minimum{minimum_}, + maximum{maximum_}, + step{step_}, + defaultValue{defaultValue_}, + value{value_} {} + +void PropertyImpl::SetValue(int v) { + int oldValue = value; + if (hasMinimum && v < minimum) + value = minimum; + else if (hasMaximum && v > maximum) + value = maximum; + else + value = v; + bool wasValueSet = valueSet; + valueSet = true; + if (!wasValueSet || value != oldValue) changed(); +} + +void PropertyImpl::SetValue(wpi::StringRef v) { + bool valueChanged = false; + if (valueStr != v) { + valueStr = v; + valueChanged = true; + } + bool wasValueSet = valueSet; + valueSet = true; + if (!wasValueSet || valueChanged) changed(); +} + +void PropertyImpl::SetDefaultValue(int v) { + if (hasMinimum && v < minimum) + defaultValue = minimum; + else if (hasMaximum && v > maximum) + defaultValue = maximum; + else + defaultValue = v; +} diff --git a/cscore/src/main/native/cpp/PropertyImpl.h b/cscore/src/main/native/cpp/PropertyImpl.h index f2051339a1..daad8292f5 100644 --- a/cscore/src/main/native/cpp/PropertyImpl.h +++ b/cscore/src/main/native/cpp/PropertyImpl.h @@ -11,6 +11,7 @@ #include #include +#include #include #include "cscore_c.h" @@ -21,41 +22,18 @@ namespace cs { class PropertyImpl { public: PropertyImpl() = default; - explicit PropertyImpl(wpi::StringRef name_) : name{name_} {} + explicit PropertyImpl(wpi::StringRef name_); PropertyImpl(wpi::StringRef name_, CS_PropertyKind kind_, int step_, - int defaultValue_, int value_) - : name{name_}, - propKind{kind_}, - step{step_}, - defaultValue{defaultValue_}, - value{value_} {} + int defaultValue_, int value_); + PropertyImpl(wpi::StringRef name_, CS_PropertyKind kind_, int minimum_, + int maximum_, int step_, int defaultValue_, int 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(wpi::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; - } + void SetValue(int v); + void SetValue(wpi::StringRef v); + void SetDefaultValue(int v); std::string name; CS_PropertyKind propKind{CS_PROP_NONE}; @@ -69,6 +47,9 @@ class PropertyImpl { std::string valueStr; std::vector enumChoices; bool valueSet{false}; + + // emitted when value changes + wpi::sig::Signal<> changed; }; } // namespace cs diff --git a/cscore/src/main/native/cpp/SinkImpl.cpp b/cscore/src/main/native/cpp/SinkImpl.cpp index 3253b54f90..cdf4f9177b 100644 --- a/cscore/src/main/native/cpp/SinkImpl.cpp +++ b/cscore/src/main/native/cpp/SinkImpl.cpp @@ -96,4 +96,33 @@ wpi::StringRef SinkImpl::GetError(wpi::SmallVectorImpl& buf) const { return wpi::StringRef{buf.data(), buf.size()}; } +void SinkImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) { + auto& notifier = Notifier::GetInstance(); + notifier.NotifySinkProperty(*this, CS_SINK_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.NotifySinkProperty(*this, CS_SINK_PROPERTY_CHOICES_UPDATED, + prop.name, propIndex, prop.propKind, prop.value, + wpi::StringRef{}); +} + +void SinkImpl::UpdatePropertyValue(int property, bool setString, int value, + wpi::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().NotifySinkProperty( + *this, CS_SINK_PROPERTY_VALUE_UPDATED, prop->name, property, + prop->propKind, prop->value, prop->valueStr); +} + void SinkImpl::SetSourceImpl(std::shared_ptr source) {} diff --git a/cscore/src/main/native/cpp/SinkImpl.h b/cscore/src/main/native/cpp/SinkImpl.h index 8eef4d3f12..1080403e7f 100644 --- a/cscore/src/main/native/cpp/SinkImpl.h +++ b/cscore/src/main/native/cpp/SinkImpl.h @@ -20,7 +20,7 @@ namespace cs { class Frame; -class SinkImpl { +class SinkImpl : public PropertyContainer { public: explicit SinkImpl(wpi::StringRef name); virtual ~SinkImpl(); @@ -47,9 +47,12 @@ class SinkImpl { wpi::StringRef GetError(wpi::SmallVectorImpl& buf) const; protected: - virtual void SetSourceImpl(std::shared_ptr source); + // PropertyContainer implementation + void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) override; + void UpdatePropertyValue(int property, bool setString, int value, + wpi::StringRef valueStr) override; - mutable wpi::mutex m_mutex; + virtual void SetSourceImpl(std::shared_ptr source); private: std::string m_name; diff --git a/cscore/src/main/native/cpp/SourceImpl.cpp b/cscore/src/main/native/cpp/SourceImpl.cpp index 6ac553354d..6f6a85a855 100644 --- a/cscore/src/main/native/cpp/SourceImpl.cpp +++ b/cscore/src/main/native/cpp/SourceImpl.cpp @@ -94,150 +94,6 @@ void SourceImpl::Wakeup() { m_frameCv.notify_all(); } -int SourceImpl::GetPropertyIndex(wpi::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 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; -} - -wpi::ArrayRef SourceImpl::EnumerateProperties( - wpi::SmallVectorImpl& vec, CS_Status* status) const { - if (!m_properties_cached && !CacheProperties(status)) - return wpi::ArrayRef{}; - std::lock_guard lock(m_mutex); - for (int i = 0; i < static_cast(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 lock(m_mutex); - auto prop = GetProperty(property); - if (!prop) return CS_PROP_NONE; - return prop->propKind; -} - -wpi::StringRef SourceImpl::GetPropertyName(int property, - wpi::SmallVectorImpl& buf, - CS_Status* status) const { - if (!m_properties_cached && !CacheProperties(status)) return wpi::StringRef{}; - std::lock_guard lock(m_mutex); - auto prop = GetProperty(property); - if (!prop) { - *status = CS_INVALID_PROPERTY; - return wpi::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 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 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 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 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 lock(m_mutex); - auto prop = GetProperty(property); - if (!prop) { - *status = CS_INVALID_PROPERTY; - return 0; - } - return prop->defaultValue; -} - -wpi::StringRef SourceImpl::GetStringProperty(int property, - wpi::SmallVectorImpl& buf, - CS_Status* status) const { - if (!m_properties_cached && !CacheProperties(status)) return wpi::StringRef{}; - std::lock_guard lock(m_mutex); - auto prop = GetProperty(property); - if (!prop) { - *status = CS_INVALID_PROPERTY; - return wpi::StringRef{}; - } - if (prop->propKind != CS_PROP_STRING) { - *status = CS_WRONG_PROPERTY_TYPE; - return wpi::StringRef{}; - } - buf.clear(); - buf.append(prop->valueStr.begin(), prop->valueStr.end()); - return wpi::StringRef(buf.data(), buf.size()); -} - -std::vector SourceImpl::GetEnumPropertyChoices( - int property, CS_Status* status) const { - if (!m_properties_cached && !CacheProperties(status)) - return std::vector{}; - std::lock_guard lock(m_mutex); - auto prop = GetProperty(property); - if (!prop) { - *status = CS_INVALID_PROPERTY; - return std::vector{}; - } - if (prop->propKind != CS_PROP_ENUM) { - *status = CS_WRONG_PROPERTY_TYPE; - return std::vector{}; - } - return prop->enumChoices; -} - VideoMode SourceImpl::GetVideoMode(CS_Status* status) const { if (!m_properties_cached && !CacheProperties(status)) return VideoMode{}; std::lock_guard lock(m_mutex); diff --git a/cscore/src/main/native/cpp/SourceImpl.h b/cscore/src/main/native/cpp/SourceImpl.h index c1182bef4f..d6a1126a9f 100644 --- a/cscore/src/main/native/cpp/SourceImpl.h +++ b/cscore/src/main/native/cpp/SourceImpl.h @@ -15,19 +15,18 @@ #include #include -#include #include #include #include #include "Frame.h" #include "Image.h" -#include "PropertyImpl.h" +#include "PropertyContainer.h" #include "cscore_cpp.h" namespace cs { -class SourceImpl { +class SourceImpl : public PropertyContainer { friend class Frame; public: @@ -89,27 +88,6 @@ class SourceImpl { // Force a wakeup of all GetNextFrame() callers by sending an empty frame. void Wakeup(); - // Property functions - int GetPropertyIndex(wpi::StringRef name) const; - wpi::ArrayRef EnumerateProperties(wpi::SmallVectorImpl& vec, - CS_Status* status) const; - CS_PropertyKind GetPropertyKind(int property) const; - wpi::StringRef GetPropertyName(int property, wpi::SmallVectorImpl& 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; - wpi::StringRef GetStringProperty(int property, - wpi::SmallVectorImpl& buf, - CS_Status* status) const; - virtual void SetStringProperty(int property, wpi::StringRef value, - CS_Status* status) = 0; - std::vector 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; @@ -137,6 +115,10 @@ class SourceImpl { int width, int height, size_t size); protected: + void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) override; + void UpdatePropertyValue(int property, bool setString, int value, + wpi::StringRef valueStr) override; + void PutFrame(VideoMode::PixelFormat pixelFormat, int width, int height, wpi::StringRef data, Frame::Time time); void PutFrame(std::unique_ptr image, Frame::Time time); @@ -150,45 +132,10 @@ class SourceImpl { 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(property) > m_propertyData.size()) - return nullptr; - return m_propertyData[property - 1].get(); - } - const PropertyImpl* GetProperty(int property) const { - if (property <= 0 || static_cast(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 CreateEmptyProperty( - wpi::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, - wpi::StringRef valueStr); - - // Cached properties and video modes (protected with m_mutex) - mutable std::vector> m_propertyData; - mutable wpi::StringMap m_properties; + // Cached video modes (protected with m_mutex) mutable std::vector 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); diff --git a/cscore/src/main/native/cpp/cscore_c.cpp b/cscore/src/main/native/cpp/cscore_c.cpp index 467fa3c0fe..7abfcb5aa4 100644 --- a/cscore/src/main/native/cpp/cscore_c.cpp +++ b/cscore/src/main/native/cpp/cscore_c.cpp @@ -242,6 +242,22 @@ char* CS_GetSinkDescription(CS_Sink sink, CS_Status* status) { return cs::ConvertToC(str); } +CS_Property CS_GetSinkProperty(CS_Sink sink, const char* name, + CS_Status* status) { + return cs::GetSinkProperty(sink, name, status); +} + +CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count, + CS_Status* status) { + wpi::SmallVector buf; + auto vec = cs::EnumerateSinkProperties(sink, buf, status); + CS_Property* out = static_cast( + wpi::CheckedMalloc(vec.size() * sizeof(CS_Property))); + *count = vec.size(); + std::copy(vec.begin(), vec.end(), out); + return out; +} + 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 8c63cbd346..e35d213052 100644 --- a/cscore/src/main/native/cpp/cscore_cpp.cpp +++ b/cscore/src/main/native/cpp/cscore_cpp.cpp @@ -20,28 +20,39 @@ #include "Log.h" #include "NetworkListener.h" #include "Notifier.h" +#include "PropertyContainer.h" #include "SinkImpl.h" #include "SourceImpl.h" #include "Telemetry.h" using namespace cs; -static std::shared_ptr GetPropertySource(CS_Property propertyHandle, - int* propertyIndex, - CS_Status* status) { +static std::shared_ptr GetPropertyContainer( + CS_Property propertyHandle, int* propertyIndex, CS_Status* status) { + std::shared_ptr container; 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) { + if (handle.IsType(Handle::kProperty)) { + int i = handle.GetParentIndex(); + auto data = Sources::GetInstance().Get(Handle{i, Handle::kSource}); + if (!data) { + *status = CS_INVALID_HANDLE; + return nullptr; + } + container = data->source; + } else if (handle.IsType(Handle::kSinkProperty)) { + int i = handle.GetParentIndex(); + auto data = Sinks::GetInstance().Get(Handle{i, Handle::kSink}); + if (!data) { + *status = CS_INVALID_HANDLE; + return nullptr; + } + container = data->sink; + } else { *status = CS_INVALID_HANDLE; return nullptr; } *propertyIndex = handle.GetIndex(); - return data->source; + return container; } namespace cs { @@ -52,101 +63,101 @@ namespace cs { 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); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return CS_PROP_NONE; + return container->GetPropertyKind(propertyIndex); } std::string GetPropertyName(CS_Property property, CS_Status* status) { wpi::SmallString<128> buf; int propertyIndex; - auto source = GetPropertySource(property, &propertyIndex, status); - if (!source) return std::string{}; - return source->GetPropertyName(propertyIndex, buf, status); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return std::string{}; + return container->GetPropertyName(propertyIndex, buf, status); } wpi::StringRef GetPropertyName(CS_Property property, wpi::SmallVectorImpl& buf, CS_Status* status) { int propertyIndex; - auto source = GetPropertySource(property, &propertyIndex, status); - if (!source) return wpi::StringRef{}; - return source->GetPropertyName(propertyIndex, buf, status); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return wpi::StringRef{}; + return container->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); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return false; + return container->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); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return; + container->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); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return 0.0; + return container->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); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return 0.0; + return container->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); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return 0.0; + return container->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); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return 0.0; + return container->GetPropertyDefault(propertyIndex, status); } std::string GetStringProperty(CS_Property property, CS_Status* status) { wpi::SmallString<128> buf; int propertyIndex; - auto source = GetPropertySource(property, &propertyIndex, status); - if (!source) return std::string{}; - return source->GetStringProperty(propertyIndex, buf, status); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return std::string{}; + return container->GetStringProperty(propertyIndex, buf, status); } wpi::StringRef GetStringProperty(CS_Property property, wpi::SmallVectorImpl& buf, CS_Status* status) { int propertyIndex; - auto source = GetPropertySource(property, &propertyIndex, status); - if (!source) return wpi::StringRef{}; - return source->GetStringProperty(propertyIndex, buf, status); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return wpi::StringRef{}; + return container->GetStringProperty(propertyIndex, buf, status); } void SetStringProperty(CS_Property property, wpi::StringRef value, CS_Status* status) { int propertyIndex; - auto source = GetPropertySource(property, &propertyIndex, status); - if (!source) return; - source->SetStringProperty(propertyIndex, value, status); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return; + container->SetStringProperty(propertyIndex, value, status); } std::vector GetEnumPropertyChoices(CS_Property property, CS_Status* status) { int propertyIndex; - auto source = GetPropertySource(property, &propertyIndex, status); - if (!source) return std::vector{}; - return source->GetEnumPropertyChoices(propertyIndex, status); + auto container = GetPropertyContainer(property, &propertyIndex, status); + if (!container) return std::vector{}; + return container->GetEnumPropertyChoices(propertyIndex, status); } // @@ -478,6 +489,34 @@ wpi::StringRef GetSinkDescription(CS_Sink sink, wpi::SmallVectorImpl& buf, return data->sink->GetDescription(buf); } +CS_Property GetSinkProperty(CS_Sink sink, wpi::StringRef name, + CS_Status* status) { + auto data = Sinks::GetInstance().Get(sink); + if (!data) { + *status = CS_INVALID_HANDLE; + return 0; + } + int property = data->sink->GetPropertyIndex(name); + if (property < 0) { + *status = CS_INVALID_HANDLE; + return 0; + } + return Handle{sink, property, Handle::kSinkProperty}; +} + +wpi::ArrayRef EnumerateSinkProperties( + CS_Sink sink, wpi::SmallVectorImpl& vec, CS_Status* status) { + auto data = Sinks::GetInstance().Get(sink); + if (!data) { + *status = CS_INVALID_HANDLE; + return 0; + } + wpi::SmallVector properties_buf; + for (auto property : data->sink->EnumerateProperties(properties_buf, status)) + vec.push_back(Handle{sink, property, Handle::kSinkProperty}); + return vec; +} + void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) { auto data = Sinks::GetInstance().Get(sink); if (!data) { diff --git a/cscore/src/main/native/cpp/cscore_oo.cpp b/cscore/src/main/native/cpp/cscore_oo.cpp index 29213130fb..d41e07632a 100644 --- a/cscore/src/main/native/cpp/cscore_oo.cpp +++ b/cscore/src/main/native/cpp/cscore_oo.cpp @@ -43,6 +43,18 @@ std::vector VideoSource::EnumerateSources() { return sources; } +std::vector VideoSink::EnumerateProperties() const { + wpi::SmallVector handles_buf; + CS_Status status = 0; + auto handles = EnumerateSinkProperties(m_handle, handles_buf, &status); + + std::vector properties; + properties.reserve(handles.size()); + for (CS_Property handle : handles) + properties.emplace_back(VideoProperty{handle}); + return properties; +} + std::vector VideoSink::EnumerateSinks() { 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 693c7421f9..1ed75d3b1e 100644 --- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp +++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp @@ -1192,6 +1192,41 @@ Java_edu_wpi_cscore_CameraServerJNI_getSinkDescription return MakeJString(env, str); } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: getSinkProperty + * Signature: (ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_cscore_CameraServerJNI_getSinkProperty + (JNIEnv* env, jclass, jint sink, jstring name) +{ + if (!name) { + nullPointerEx.Throw(env, "name cannot be null"); + return 0; + } + CS_Status status = 0; + auto val = cs::GetSinkProperty(sink, JStringRef{env, name}, &status); + CheckStatus(env, status); + return val; +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: enumerateSinkProperties + * Signature: (I)[I + */ +JNIEXPORT jintArray JNICALL +Java_edu_wpi_cscore_CameraServerJNI_enumerateSinkProperties + (JNIEnv* env, jclass, jint source) +{ + CS_Status status = 0; + wpi::SmallVector buf; + auto arr = cs::EnumerateSinkProperties(source, buf, &status); + if (!CheckStatus(env, status)) return nullptr; + return MakeJIntArray(env, arr); +} + /* * 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 e4b2536524..182be3e7e5 100644 --- a/cscore/src/main/native/include/cscore_c.h +++ b/cscore/src/main/native/include/cscore_c.h @@ -154,7 +154,10 @@ enum CS_EventKind { CS_SINK_ENABLED = 0x1000, CS_SINK_DISABLED = 0x2000, CS_NETWORK_INTERFACES_CHANGED = 0x4000, - CS_TELEMETRY_UPDATED = 0x8000 + CS_TELEMETRY_UPDATED = 0x8000, + CS_SINK_PROPERTY_CREATED = 0x10000, + CS_SINK_PROPERTY_VALUE_UPDATED = 0x20000, + CS_SINK_PROPERTY_CHOICES_UPDATED = 0x40000 }; // @@ -315,6 +318,10 @@ CS_Sink CS_CreateCvSinkCallback(const char* name, void* data, enum CS_SinkKind CS_GetSinkKind(CS_Sink sink, CS_Status* status); char* CS_GetSinkName(CS_Sink sink, CS_Status* status); char* CS_GetSinkDescription(CS_Sink sink, CS_Status* status); +CS_Property CS_GetSinkProperty(CS_Sink sink, const char* name, + CS_Status* status); +CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count, + CS_Status* status); 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); diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h index 7aea1a744b..6462404043 100644 --- a/cscore/src/main/native/include/cscore_cpp.h +++ b/cscore/src/main/native/include/cscore_cpp.h @@ -85,7 +85,10 @@ struct RawEvent { kSinkEnabled = CS_SINK_ENABLED, kSinkDisabled = CS_SINK_DISABLED, kNetworkInterfacesChanged = CS_NETWORK_INTERFACES_CHANGED, - kTelemetryUpdated = CS_TELEMETRY_UPDATED + kTelemetryUpdated = CS_TELEMETRY_UPDATED, + kSinkPropertyCreated = CS_SINK_PROPERTY_CREATED, + kSinkPropertyValueUpdated = CS_SINK_PROPERTY_VALUE_UPDATED, + kSinkPropertyChoicesUpdated = CS_SINK_PROPERTY_CHOICES_UPDATED }; RawEvent() = default; @@ -265,6 +268,10 @@ wpi::StringRef GetSinkName(CS_Sink sink, wpi::SmallVectorImpl& buf, std::string GetSinkDescription(CS_Sink sink, CS_Status* status); wpi::StringRef GetSinkDescription(CS_Sink sink, wpi::SmallVectorImpl& buf, CS_Status* status); +CS_Property GetSinkProperty(CS_Sink sink, wpi::StringRef name, + CS_Status* status); +wpi::ArrayRef EnumerateSinkProperties( + CS_Sink sink, wpi::SmallVectorImpl& vec, CS_Status* status); void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status); CS_Property GetSinkSourceProperty(CS_Sink sink, wpi::StringRef name, CS_Status* status); diff --git a/cscore/src/main/native/include/cscore_oo.h b/cscore/src/main/native/include/cscore_oo.h index 65ea7f0d8b..acf914c5c4 100644 --- a/cscore/src/main/native/include/cscore_oo.h +++ b/cscore/src/main/native/include/cscore_oo.h @@ -502,6 +502,15 @@ class VideoSink { /// Get the sink description. This is sink-kind specific. std::string GetDescription() const; + /// Get a property of the sink. + /// @param name Property name + /// @return Property (kind Property::kNone if no property with + /// the given name exists) + VideoProperty GetProperty(wpi::StringRef name); + + /// Enumerate all properties of this sink. + std::vector EnumerateProperties() 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 /// provide frames to multiple clients. diff --git a/cscore/src/main/native/include/cscore_oo.inl b/cscore/src/main/native/include/cscore_oo.inl index 8c7fe7a104..b814d65057 100644 --- a/cscore/src/main/native/include/cscore_oo.inl +++ b/cscore/src/main/native/include/cscore_oo.inl @@ -459,6 +459,11 @@ inline std::string VideoSink::GetDescription() const { return GetSinkDescription(m_handle, &m_status); } +inline VideoProperty VideoSink::GetProperty(wpi::StringRef name) { + m_status = 0; + return VideoProperty{GetSinkProperty(m_handle, name, &m_status)}; +} + inline void VideoSink::SetSource(VideoSource source) { m_status = 0; if (!source)