diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java index 70814f4f04..2d67f3dee4 100644 --- a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java +++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java @@ -79,7 +79,9 @@ public class CameraServerJNI { public static native String getSourceName(int source); public static native String getSourceDescription(int source); public static native long getSourceLastFrameTime(int source); + public static native void setSourceConnectionStrategy(int source, int strategy); public static native boolean isSourceConnected(int source); + public static native boolean isSourceEnabled(int source); public static native int getSourceProperty(int source, String name); public static native int[] enumerateSourceProperties(int source); public static native VideoMode getSourceVideoMode(int source); diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoSource.java b/cscore/src/main/java/edu/wpi/cscore/VideoSource.java index dde32fbc02..b7d6bd522c 100644 --- a/cscore/src/main/java/edu/wpi/cscore/VideoSource.java +++ b/cscore/src/main/java/edu/wpi/cscore/VideoSource.java @@ -28,6 +28,40 @@ public class VideoSource implements AutoCloseable { } } + /** + * Connection strategy. + */ + public enum ConnectionStrategy { + /** + * Automatically connect or disconnect based on whether any sinks are + * connected to this source. This is the default behavior. + */ + kAutoManage(0), + + /** + * Try to keep the connection open regardless of whether any sinks are + * connected. + */ + kKeepOpen(1), + + /** + * Never open the connection. If this is set when the connection is open, + * close the connection. + */ + kForceClose(2); + + @SuppressWarnings("MemberName") + private final int value; + + ConnectionStrategy(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + /** * Convert from the numerical representation of kind to an enum type. * @@ -118,6 +152,20 @@ public class VideoSource implements AutoCloseable { return CameraServerJNI.getSourceLastFrameTime(m_handle); } + /** + * Sets the connection strategy. By default, the source will automatically + * connect or disconnect based on whether any sinks are connected. + * + *

This function is non-blocking; look for either a connection open or + * close event or call {@link #isConnected()} to determine the connection + * state. + * + * @param strategy connection strategy (auto, keep open, or force close) + */ + public void setConnectionStrategy(ConnectionStrategy strategy) { + CameraServerJNI.setSourceConnectionStrategy(m_handle, strategy.getValue()); + } + /** * Returns if the source currently connected to whatever is providing the images. */ @@ -125,6 +173,16 @@ public class VideoSource implements AutoCloseable { return CameraServerJNI.isSourceConnected(m_handle); } + /** + * Gets source enable status. This is determined with a combination of + * connection strategy and the number of sinks connected. + * + * @return True if enabled, false otherwise. + */ + public boolean isEnabled() { + return CameraServerJNI.isSourceEnabled(m_handle); + } + /** * Get a property. * diff --git a/cscore/src/main/native/cpp/HttpCameraImpl.cpp b/cscore/src/main/native/cpp/HttpCameraImpl.cpp index 17456ad2c3..6052cd47b7 100644 --- a/cscore/src/main/native/cpp/HttpCameraImpl.cpp +++ b/cscore/src/main/native/cpp/HttpCameraImpl.cpp @@ -69,13 +69,12 @@ void HttpCameraImpl::StreamThreadMain() { // sleep between retries std::this_thread::sleep_for(std::chrono::milliseconds(250)); - // disconnect if no one is listening - if (m_numSinksEnabled == 0) { + // disconnect if not enabled + if (!IsEnabled()) { std::unique_lock 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; }); + // Wait for enable + m_sinkEnabledCond.wait(lock, [=] { return !m_active || IsEnabled(); }); if (!m_active) return; } @@ -185,8 +184,8 @@ void HttpCameraImpl::DeviceStream(wpi::raw_istream& is, int numErrors = 0; // streaming loop - while (m_active && !is.has_error() && m_numSinksEnabled > 0 && - numErrors < 3 && !m_streamSettingsUpdated) { + while (m_active && !is.has_error() && IsEnabled() && numErrors < 3 && + !m_streamSettingsUpdated) { if (!FindMultipartBoundary(is, boundary, nullptr)) break; // Read the next two characters after the boundary (normally \r\n) diff --git a/cscore/src/main/native/cpp/SourceImpl.h b/cscore/src/main/native/cpp/SourceImpl.h index a6b50101ac..0eef6c2ca7 100644 --- a/cscore/src/main/native/cpp/SourceImpl.h +++ b/cscore/src/main/native/cpp/SourceImpl.h @@ -41,6 +41,15 @@ class SourceImpl : public PropertyContainer { void SetDescription(const wpi::Twine& description); wpi::StringRef GetDescription(wpi::SmallVectorImpl& buf) const; + void SetConnectionStrategy(CS_ConnectionStrategy strategy) { + m_strategy = static_cast(strategy); + } + bool IsEnabled() const { + return m_strategy == CS_CONNECTION_KEEP_OPEN || + (m_strategy == CS_CONNECTION_AUTO_MANAGE && m_numSinksEnabled > 0); + } + + // User-visible connection status void SetConnected(bool connected); bool IsConnected() const { return m_connected; } @@ -130,7 +139,6 @@ class SourceImpl : public PropertyContainer { virtual void NumSinksEnabledChanged() = 0; std::atomic_int m_numSinks{0}; - std::atomic_int m_numSinksEnabled{0}; protected: // Cached video modes (protected with m_mutex) @@ -146,6 +154,9 @@ class SourceImpl : public PropertyContainer { std::string m_name; std::string m_description; + std::atomic_int m_strategy{CS_CONNECTION_AUTO_MANAGE}; + std::atomic_int m_numSinksEnabled{0}; + wpi::mutex m_frameMutex; wpi::condition_variable m_frameCv; diff --git a/cscore/src/main/native/cpp/UsbCameraImpl.cpp b/cscore/src/main/native/cpp/UsbCameraImpl.cpp index c48b421b65..4d7363f79a 100644 --- a/cscore/src/main/native/cpp/UsbCameraImpl.cpp +++ b/cscore/src/main/native/cpp/UsbCameraImpl.cpp @@ -309,10 +309,10 @@ void UsbCameraImpl::CameraThreadMain() { } } - // Turn off streaming if no one is listening, and turn it on if there is. - if (m_streaming && m_numSinksEnabled == 0) { + // Turn off streaming if not enabled, and turn it on if enabled + if (m_streaming && !IsEnabled()) { DeviceStreamOff(); - } else if (!m_streaming && m_numSinksEnabled > 0) { + } else if (!m_streaming && IsEnabled()) { DeviceStreamOn(); } diff --git a/cscore/src/main/native/cpp/cscore_c.cpp b/cscore/src/main/native/cpp/cscore_c.cpp index 7abfcb5aa4..9ed8aaad22 100644 --- a/cscore/src/main/native/cpp/cscore_c.cpp +++ b/cscore/src/main/native/cpp/cscore_c.cpp @@ -99,10 +99,20 @@ uint64_t CS_GetSourceLastFrameTime(CS_Source source, CS_Status* status) { return cs::GetSourceLastFrameTime(source, status); } +void CS_SetSourceConnectionStrategy(CS_Source source, + CS_ConnectionStrategy strategy, + CS_Status* status) { + cs::SetSourceConnectionStrategy(source, strategy, status); +} + CS_Bool CS_IsSourceConnected(CS_Source source, CS_Status* status) { return cs::IsSourceConnected(source, status); } +CS_Bool CS_IsSourceEnabled(CS_Source source, CS_Status* status) { + return cs::IsSourceEnabled(source, status); +} + CS_Property CS_GetSourceProperty(CS_Source source, const char* name, CS_Status* status) { return cs::GetSourceProperty(source, name, status); diff --git a/cscore/src/main/native/cpp/cscore_cpp.cpp b/cscore/src/main/native/cpp/cscore_cpp.cpp index 52f9264b18..b8e699cae3 100644 --- a/cscore/src/main/native/cpp/cscore_cpp.cpp +++ b/cscore/src/main/native/cpp/cscore_cpp.cpp @@ -222,6 +222,17 @@ uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status) { return data->source->GetCurFrameTime(); } +void SetSourceConnectionStrategy(CS_Source source, + CS_ConnectionStrategy strategy, + CS_Status* status) { + auto data = Sources::GetInstance().Get(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return; + } + data->source->SetConnectionStrategy(strategy); +} + bool IsSourceConnected(CS_Source source, CS_Status* status) { auto data = Sources::GetInstance().Get(source); if (!data) { @@ -231,6 +242,15 @@ bool IsSourceConnected(CS_Source source, CS_Status* status) { return data->source->IsConnected(); } +bool IsSourceEnabled(CS_Source source, CS_Status* status) { + auto data = Sources::GetInstance().Get(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->source->IsEnabled(); +} + CS_Property GetSourceProperty(CS_Source source, const wpi::Twine& name, CS_Status* status) { auto data = Sources::GetInstance().Get(source); diff --git a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp index 49e091998c..128ca0059a 100644 --- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp +++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp @@ -599,6 +599,21 @@ Java_edu_wpi_cscore_CameraServerJNI_getSourceLastFrameTime return val; } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: setSourceConnectionStrategy + * Signature: (II)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_cscore_CameraServerJNI_setSourceConnectionStrategy + (JNIEnv* env, jclass, jint source, jint strategy) +{ + CS_Status status = 0; + cs::SetSourceConnectionStrategy( + source, static_cast(strategy), &status); + CheckStatus(env, status); +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: isSourceConnected @@ -614,6 +629,21 @@ Java_edu_wpi_cscore_CameraServerJNI_isSourceConnected return val; } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: isSourceEnabled + * Signature: (I)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_cscore_CameraServerJNI_isSourceEnabled + (JNIEnv* env, jclass, jint source) +{ + CS_Status status = 0; + auto val = cs::IsSourceEnabled(source, &status); + CheckStatus(env, status); + return val; +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: getSourceProperty diff --git a/cscore/src/main/native/include/cscore_c.h b/cscore/src/main/native/include/cscore_c.h index 345c011d7a..1163b3e734 100644 --- a/cscore/src/main/native/include/cscore_c.h +++ b/cscore/src/main/native/include/cscore_c.h @@ -178,6 +178,27 @@ enum CS_TelemetryKind { CS_SOURCE_FRAMES_RECEIVED = 2 }; +/** Connection strategy */ +enum CS_ConnectionStrategy { + /** + * Automatically connect or disconnect based on whether any sinks are + * connected to this source. This is the default behavior. + */ + CS_CONNECTION_AUTO_MANAGE = 0, + + /** + * Try to keep the connection open regardless of whether any sinks are + * connected. + */ + CS_CONNECTION_KEEP_OPEN, + + /** + * Never open the connection. If this is set when the connection is open, + * close the connection. + */ + CS_CONNECTION_FORCE_CLOSE +}; + /** * Listener event */ @@ -245,7 +266,11 @@ enum CS_SourceKind CS_GetSourceKind(CS_Source source, CS_Status* status); char* CS_GetSourceName(CS_Source source, CS_Status* status); char* CS_GetSourceDescription(CS_Source source, CS_Status* status); uint64_t CS_GetSourceLastFrameTime(CS_Source source, CS_Status* status); +void CS_SetSourceConnectionStrategy(CS_Source source, + enum CS_ConnectionStrategy strategy, + CS_Status* status); CS_Bool CS_IsSourceConnected(CS_Source source, CS_Status* status); +CS_Bool CS_IsSourceEnabled(CS_Source source, CS_Status* status); CS_Property CS_GetSourceProperty(CS_Source source, const char* name, CS_Status* status); CS_Property* CS_EnumerateSourceProperties(CS_Source source, int* count, diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h index 894a4106c6..a5c345d4de 100644 --- a/cscore/src/main/native/include/cscore_cpp.h +++ b/cscore/src/main/native/include/cscore_cpp.h @@ -203,7 +203,11 @@ wpi::StringRef GetSourceDescription(CS_Source source, wpi::SmallVectorImpl& buf, CS_Status* status); uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status); +void SetSourceConnectionStrategy(CS_Source source, + CS_ConnectionStrategy strategy, + CS_Status* status); bool IsSourceConnected(CS_Source source, CS_Status* status); +bool IsSourceEnabled(CS_Source source, CS_Status* status); CS_Property GetSourceProperty(CS_Source source, const wpi::Twine& name, CS_Status* status); wpi::ArrayRef EnumerateSourceProperties( diff --git a/cscore/src/main/native/include/cscore_oo.h b/cscore/src/main/native/include/cscore_oo.h index 0eb20507fa..8eec60a0e0 100644 --- a/cscore/src/main/native/include/cscore_oo.h +++ b/cscore/src/main/native/include/cscore_oo.h @@ -106,6 +106,27 @@ class VideoSource { kCv = CS_SOURCE_CV }; + /** Connection strategy. Used for SetConnectionStrategy(). */ + enum ConnectionStrategy { + /** + * Automatically connect or disconnect based on whether any sinks are + * connected to this source. This is the default behavior. + */ + kConnectionAutoManage = CS_CONNECTION_AUTO_MANAGE, + + /** + * Try to keep the connection open regardless of whether any sinks are + * connected. + */ + kConnectionKeepOpen = CS_CONNECTION_KEEP_OPEN, + + /** + * Never open the connection. If this is set when the connection is open, + * close the connection. + */ + kConnectionForceClose = CS_CONNECTION_FORCE_CLOSE + }; + VideoSource() noexcept : m_handle(0) {} VideoSource(const VideoSource& source); VideoSource(VideoSource&& other) noexcept; @@ -146,11 +167,30 @@ class VideoSource { */ uint64_t GetLastFrameTime() const; + /** + * Sets the connection strategy. By default, the source will automatically + * connect or disconnect based on whether any sinks are connected. + * + *

This function is non-blocking; look for either a connection open or + * close event or call IsConnected() to determine the connection state. + * + * @param strategy connection strategy (auto, keep open, or force close) + */ + void SetConnectionStrategy(ConnectionStrategy strategy); + /** * Is the source currently connected to whatever is providing the images? */ bool IsConnected() const; + /** + * Gets source enable status. This is determined with a combination of + * connection strategy and the number of sinks connected. + * + * @return True if enabled, false otherwise. + */ + bool IsEnabled() const; + /** Get a property. * * @param name Property name diff --git a/cscore/src/main/native/include/cscore_oo.inl b/cscore/src/main/native/include/cscore_oo.inl index 9693045069..74f19b0af8 100644 --- a/cscore/src/main/native/include/cscore_oo.inl +++ b/cscore/src/main/native/include/cscore_oo.inl @@ -116,11 +116,23 @@ inline uint64_t VideoSource::GetLastFrameTime() const { return GetSourceLastFrameTime(m_handle, &m_status); } +inline void VideoSource::SetConnectionStrategy(ConnectionStrategy strategy) { + m_status = 0; + SetSourceConnectionStrategy( + m_handle, static_cast(static_cast(strategy)), + &m_status); +} + inline bool VideoSource::IsConnected() const { m_status = 0; return IsSourceConnected(m_handle, &m_status); } +inline bool VideoSource::IsEnabled() const { + m_status = 0; + return IsSourceEnabled(m_handle, &m_status); +} + inline VideoProperty VideoSource::GetProperty(const wpi::Twine& name) { m_status = 0; return VideoProperty{GetSourceProperty(m_handle, name, &m_status)};