diff --git a/examples/usbstream/usbstream.cpp b/examples/usbstream/usbstream.cpp index d5f686eae6..f5bdb59f74 100644 --- a/examples/usbstream/usbstream.cpp +++ b/examples/usbstream/usbstream.cpp @@ -16,10 +16,18 @@ int main() { llvm::outs() << "IPv4 network addresses:\n"; for (const auto& addr : cs::GetNetworkInterfaces()) llvm::outs() << " " << addr << '\n'; - cs::UsbCamera camera{"usbcam", 1}; + cs::UsbCamera camera{"usbcam", 0}; camera.SetVideoMode(cs::VideoMode::kMJPEG, 320, 240, 30); cs::MjpegServer mjpegServer{"httpserver", 8081}; mjpegServer.SetSource(camera); + CS_Status status = 0; + cs::AddListener([&](const cs::RawEvent& event) { + llvm::outs() << "FPS=" << camera.GetActualFPS() + << " MBPS=" << (camera.GetActualDataRate() / + 1000000.0) << '\n'; + }, cs::RawEvent::kTelemetryUpdated, false, &status); + cs::SetTelemetryPeriod(1.0); + std::getchar(); } diff --git a/src/main/java/edu/wpi/cscore/CameraServerJNI.java b/src/main/java/edu/wpi/cscore/CameraServerJNI.java index 486718514c..4d34be0aa5 100644 --- a/src/main/java/edu/wpi/cscore/CameraServerJNI.java +++ b/src/main/java/edu/wpi/cscore/CameraServerJNI.java @@ -231,6 +231,34 @@ public class CameraServerJNI { public static native void removeListener(int handle); + // + // Telemetry Functions + // + public enum TelemetryKind { + kSourceBytesReceived(1), + kSourceFramesReceived(2); + + private int value; + + private TelemetryKind(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + public static native void setTelemetryPeriod(double seconds); + public static native double getTelemetryElapsedTime(); + public static native long getTelemetryValue(int handle, int kind); + public static long getTelemetryValue(int handle, TelemetryKind kind) { + return getTelemetryValue(handle, kind.getValue()); + } + public static native double getTelemetryAverageValue(int handle, int kind); + public static double getTelemetryAverageValue(int handle, TelemetryKind kind) { + return getTelemetryAverageValue(handle, kind.getValue()); + } + // // Logging Functions // diff --git a/src/main/java/edu/wpi/cscore/VideoEvent.java b/src/main/java/edu/wpi/cscore/VideoEvent.java index 5340f53304..a67299a3d4 100644 --- a/src/main/java/edu/wpi/cscore/VideoEvent.java +++ b/src/main/java/edu/wpi/cscore/VideoEvent.java @@ -27,7 +27,8 @@ public class VideoEvent { kSinkDestroyed(0x0800), kSinkEnabled(0x1000), kSinkDisabled(0x2000), - kNetworkInterfacesChanged(0x4000); + kNetworkInterfacesChanged(0x4000), + kTelemetryUpdated(0x8000); private int value; diff --git a/src/main/java/edu/wpi/cscore/VideoSource.java b/src/main/java/edu/wpi/cscore/VideoSource.java index e625962d9b..9ccf353ad8 100644 --- a/src/main/java/edu/wpi/cscore/VideoSource.java +++ b/src/main/java/edu/wpi/cscore/VideoSource.java @@ -180,6 +180,26 @@ public class VideoSource { return CameraServerJNI.setSourceFPS(m_handle, fps); } + /** + * Get the actual FPS. + * CameraServerJNI#setTelemetryPeriod() must be called for this to be valid + * (throws VisionException if telemetry is not enabled). + * @return Actual FPS averaged over the telemetry period. + */ + public double getActualFPS() { + return CameraServerJNI.getTelemetryAverageValue(m_handle, CameraServerJNI.TelemetryKind.kSourceFramesReceived); + } + + /** + * Get the data rate (in bytes per second). + * CameraServerJNI#setTelemetryPeriod() must be called for this to be valid + * (throws VisionException if telemetry is not enabled). + * @return Data rate averaged over the telemetry period. + */ + public double getActualDataRate() { + return CameraServerJNI.getTelemetryAverageValue(m_handle, CameraServerJNI.TelemetryKind.kSourceBytesReceived); + } + /** * Enumerate all known video modes for this source. */ diff --git a/src/main/native/cpp/HttpCameraImpl.cpp b/src/main/native/cpp/HttpCameraImpl.cpp index d0cc3852d5..52f2719024 100644 --- a/src/main/native/cpp/HttpCameraImpl.cpp +++ b/src/main/native/cpp/HttpCameraImpl.cpp @@ -15,6 +15,7 @@ #include "JpegUtil.h" #include "Log.h" #include "Notifier.h" +#include "Telemetry.h" #include "c_util.h" using namespace cs; diff --git a/src/main/native/cpp/Notifier.cpp b/src/main/native/cpp/Notifier.cpp index 03424dd69d..48e03a52e2 100644 --- a/src/main/native/cpp/Notifier.cpp +++ b/src/main/native/cpp/Notifier.cpp @@ -224,3 +224,11 @@ void Notifier::NotifyNetworkInterfacesChanged() { thr->m_notifications.emplace(RawEvent::kNetworkInterfacesChanged); thr->m_cond.notify_one(); } + +void Notifier::NotifyTelemetryUpdated() { + auto thr = m_owner.GetThread(); + if (!thr) return; + + thr->m_notifications.emplace(RawEvent::kTelemetryUpdated); + thr->m_cond.notify_one(); +} diff --git a/src/main/native/cpp/Notifier.h b/src/main/native/cpp/Notifier.h index ed144b89f5..98da430e5c 100644 --- a/src/main/native/cpp/Notifier.h +++ b/src/main/native/cpp/Notifier.h @@ -54,6 +54,7 @@ class Notifier { void NotifySinkSourceChanged(llvm::StringRef name, CS_Sink sink, CS_Source source); void NotifyNetworkInterfacesChanged(); + void NotifyTelemetryUpdated(); private: Notifier(); diff --git a/src/main/native/cpp/SourceImpl.cpp b/src/main/native/cpp/SourceImpl.cpp index 651728534a..59971de231 100644 --- a/src/main/native/cpp/SourceImpl.cpp +++ b/src/main/native/cpp/SourceImpl.cpp @@ -15,6 +15,7 @@ #include "Log.h" #include "Notifier.h" +#include "Telemetry.h" using namespace cs; @@ -326,6 +327,11 @@ void SourceImpl::PutFrame(VideoMode::PixelFormat pixelFormat, int width, } void SourceImpl::PutFrame(std::unique_ptr image, Frame::Time time) { + // Update telemetry + Telemetry::GetInstance().RecordSourceFrames(*this, 1); + Telemetry::GetInstance().RecordSourceBytes(*this, + static_cast(image->size())); + // Update frame { std::lock_guard lock{m_frameMutex}; diff --git a/src/main/native/cpp/Telemetry.cpp b/src/main/native/cpp/Telemetry.cpp new file mode 100644 index 0000000000..f8925294a9 --- /dev/null +++ b/src/main/native/cpp/Telemetry.cpp @@ -0,0 +1,137 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "Telemetry.h" + +#include +#include + +#include +#include + +#include "Handle.h" +#include "Notifier.h" +#include "Telemetry.h" +#include "cscore_cpp.h" + +using namespace cs; + +class Telemetry::Thread : public wpi::SafeThread { + public: + void Main(); + + llvm::DenseMap, int64_t> m_user; + llvm::DenseMap, int64_t> m_current; + double m_period = 0.0; + double m_elapsed = 0.0; + bool m_updated = false; + int64_t GetValue(CS_Handle handle, CS_TelemetryKind kind, CS_Status* status); +}; + +int64_t Telemetry::Thread::GetValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status) { + auto it = m_user.find(std::make_pair(handle, static_cast(kind))); + if (it == m_user.end()) { + *status = CS_EMPTY_VALUE; + return 0; + } + return it->getSecond(); +} + +Telemetry::Telemetry() {} + +Telemetry::~Telemetry() {} + +void Telemetry::Start() { + auto thr = m_owner.GetThread(); + if (!thr) m_owner.Start(new Thread); +} + +void Telemetry::Stop() { m_owner.Stop(); } + +void Telemetry::Thread::Main() { + std::unique_lock lock(m_mutex); + auto prevTime = std::chrono::steady_clock::now(); + while (m_active) { + double period = m_period; + if (period == 0) period = 1000.0; + auto timeoutTime = prevTime + std::chrono::duration(period); + while (m_active && !m_updated) { + if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout) + break; + } + if (!m_active) break; + if (m_updated) { + m_updated = false; + continue; + } + + // move to user and clear current, as we don't keep around old values + m_user = std::move(m_current); + m_current.clear(); + auto curTime = std::chrono::steady_clock::now(); + m_elapsed = std::chrono::duration(curTime - prevTime).count(); + prevTime = curTime; + + // notify + Notifier::GetInstance().NotifyTelemetryUpdated(); + } +} + +void Telemetry::SetPeriod(double seconds) { + auto thr = m_owner.GetThread(); + if (!thr) return; + if (thr->m_period == seconds) return; // no change + thr->m_period = seconds; + thr->m_updated = true; + thr->m_cond.notify_one(); +} + +double Telemetry::GetElapsedTime() { + auto thr = m_owner.GetThread(); + if (!thr) return 0; + return thr->m_elapsed; +} + +int64_t Telemetry::GetValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status) { + auto thr = m_owner.GetThread(); + if (!thr) { + *status = CS_TELEMETRY_NOT_ENABLED; + return 0; + } + return thr->GetValue(handle, kind, status); +} + +double Telemetry::GetAverageValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status) { + auto thr = m_owner.GetThread(); + if (!thr) { + *status = CS_TELEMETRY_NOT_ENABLED; + return 0; + } + if (thr->m_elapsed == 0) return 0.0; + return thr->GetValue(handle, kind, status) / thr->m_elapsed; +} + +void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) { + auto thr = m_owner.GetThread(); + if (!thr) return; + auto handleData = Sources::GetInstance().Find(source); + thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource}, + static_cast(CS_SOURCE_BYTES_RECEIVED))] + += quantity; +} + +void Telemetry::RecordSourceFrames(const SourceImpl& source, int quantity) { + auto thr = m_owner.GetThread(); + if (!thr) return; + auto handleData = Sources::GetInstance().Find(source); + thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource}, + static_cast(CS_SOURCE_FRAMES_RECEIVED))] + += quantity; +} diff --git a/src/main/native/cpp/Telemetry.h b/src/main/native/cpp/Telemetry.h new file mode 100644 index 0000000000..8995dce6bc --- /dev/null +++ b/src/main/native/cpp/Telemetry.h @@ -0,0 +1,52 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CSCORE_TELEMETRY_H_ +#define CSCORE_TELEMETRY_H_ + +#include + +#include "cscore_cpp.h" + +namespace cs { + +class SourceImpl; + +class Telemetry { + friend class TelemetryTest; + + public: + static Telemetry& GetInstance() { + static Telemetry instance; + return instance; + } + ~Telemetry(); + + void Start(); + void Stop(); + + // User interface + void SetPeriod(double seconds); + double GetElapsedTime(); + int64_t GetValue(CS_Handle handle, CS_TelemetryKind kind, CS_Status* status); + double GetAverageValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status); + + // Telemetry events + void RecordSourceBytes(const SourceImpl& source, int quantity); + void RecordSourceFrames(const SourceImpl& source, int quantity); + + private: + Telemetry(); + + class Thread; + wpi::SafeThreadOwner m_owner; +}; + +} // namespace cs + +#endif // CSCORE_TELEMETRY_H_ diff --git a/src/main/native/cpp/UsbCameraImpl.cpp b/src/main/native/cpp/UsbCameraImpl.cpp index 8a3c1fe20e..e2aa8f3f2a 100644 --- a/src/main/native/cpp/UsbCameraImpl.cpp +++ b/src/main/native/cpp/UsbCameraImpl.cpp @@ -34,6 +34,7 @@ #include "Handle.h" #include "Log.h" #include "Notifier.h" +#include "Telemetry.h" #include "UsbUtil.h" #include "c_util.h" #include "cscore_cpp.h" diff --git a/src/main/native/cpp/cscore_c.cpp b/src/main/native/cpp/cscore_c.cpp index 704a2db9b4..15634af813 100644 --- a/src/main/native/cpp/cscore_c.cpp +++ b/src/main/native/cpp/cscore_c.cpp @@ -296,6 +296,20 @@ void CS_RemoveListener(CS_Listener handle, CS_Status* status) { int CS_NotifierDestroyed(void) { return cs::NotifierDestroyed(); } +void CS_SetTelemetryPeriod(double seconds) { cs::SetTelemetryPeriod(seconds); } + +double CS_GetTelemetryElapsedTime() { return cs::GetTelemetryElapsedTime(); } + +int64_t CS_GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status) { + return cs::GetTelemetryValue(handle, kind, status); +} + +double CS_GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status) { + return cs::GetTelemetryAverageValue(handle, kind, status); +} + void CS_SetLogger(CS_LogFunc func, unsigned int min_level) { cs::SetLogger(func, min_level); } diff --git a/src/main/native/cpp/cscore_cpp.cpp b/src/main/native/cpp/cscore_cpp.cpp index dc7bb30641..b4a532a6db 100644 --- a/src/main/native/cpp/cscore_cpp.cpp +++ b/src/main/native/cpp/cscore_cpp.cpp @@ -22,6 +22,7 @@ #include "Notifier.h" #include "SinkImpl.h" #include "SourceImpl.h" +#include "Telemetry.h" using namespace cs; @@ -583,6 +584,28 @@ void RemoveListener(CS_Listener handle, CS_Status* status) { bool NotifierDestroyed() { return Notifier::destroyed(); } +// +// Telemetry Functions +// +void SetTelemetryPeriod(double seconds) { + Telemetry::GetInstance().Start(); + Telemetry::GetInstance().SetPeriod(seconds); +} + +double GetTelemetryElapsedTime() { + return Telemetry::GetInstance().GetElapsedTime(); +} + +int64_t GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status) { + return Telemetry::GetInstance().GetValue(handle, kind, status); +} + +double GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status) { + return Telemetry::GetInstance().GetAverageValue(handle, kind, status); +} + // // Logging Functions // diff --git a/src/main/native/cpp/jni/CameraServerJNI.cpp b/src/main/native/cpp/jni/CameraServerJNI.cpp index 148a124eab..e271cb61ce 100644 --- a/src/main/native/cpp/jni/CameraServerJNI.cpp +++ b/src/main/native/cpp/jni/CameraServerJNI.cpp @@ -161,6 +161,9 @@ static void ReportError(JNIEnv* env, CS_Status status) { case CS_BAD_URL: msg = "bad URL"; break; + case CS_TELEMETRY_NOT_ENABLED: + msg = "telemetry not enabled"; + break; default: { llvm::raw_svector_ostream oss{msg}; oss << "unknown error code=" << status; @@ -1373,6 +1376,59 @@ JNIEXPORT void JNICALL Java_edu_wpi_cscore_CameraServerJNI_removeListener CheckStatus(env, status); } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: setTelemetryPeriod + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_cscore_CameraServerJNI_setTelemetryPeriod + (JNIEnv* env, jclass, jdouble seconds) +{ + cs::SetTelemetryPeriod(seconds); +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: getTelemetryElapsedTime + * Signature: ()D + */ +JNIEXPORT jdouble JNICALL Java_edu_wpi_cscore_CameraServerJNI_getTelemetryElapsedTime + (JNIEnv* env, jclass) +{ + return cs::GetTelemetryElapsedTime(); +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: getTelemetryValue + * Signature: (II)J + */ +JNIEXPORT jlong JNICALL Java_edu_wpi_cscore_CameraServerJNI_getTelemetryValue + (JNIEnv* env, jclass, jint handle, jint kind) +{ + CS_Status status = 0; + auto val = cs::GetTelemetryValue(handle, static_cast(kind), + &status); + CheckStatus(env, status); + return val; +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: getTelemetryAverageValue + * Signature: (II)D + */ +JNIEXPORT jdouble JNICALL Java_edu_wpi_cscore_CameraServerJNI_getTelemetryAverageValue + (JNIEnv* env, jclass, jint handle, jint kind) +{ + CS_Status status = 0; + auto val = cs::GetTelemetryAverageValue(handle, + static_cast(kind), + &status); + CheckStatus(env, status); + return val; +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: enumerateUsbCameras diff --git a/src/main/native/include/cscore_c.h b/src/main/native/include/cscore_c.h index e63dae8c35..d58ed688f2 100644 --- a/src/main/native/include/cscore_c.h +++ b/src/main/native/include/cscore_c.h @@ -54,7 +54,8 @@ enum CS_StatusValue { CS_READ_FAILED = -2004, CS_SOURCE_IS_DISCONNECTED = -2005, CS_EMPTY_VALUE = -2006, - CS_BAD_URL = -2007 + CS_BAD_URL = -2007, + CS_TELEMETRY_NOT_ENABLED = -2008 }; // @@ -148,7 +149,16 @@ enum CS_EventKind { CS_SINK_DESTROYED = 0x0800, CS_SINK_ENABLED = 0x1000, CS_SINK_DISABLED = 0x2000, - CS_NETWORK_INTERFACES_CHANGED = 0x4000 + CS_NETWORK_INTERFACES_CHANGED = 0x4000, + CS_TELEMETRY_UPDATED = 0x8000 +}; + +// +// Telemetry kinds +// +enum CS_TelemetryKind { + CS_SOURCE_BYTES_RECEIVED = 1, + CS_SOURCE_FRAMES_RECEIVED = 2 }; // @@ -338,6 +348,16 @@ void CS_RemoveListener(CS_Listener handle, CS_Status* status); int CS_NotifierDestroyed(void); +// +// Telemetry Functions +// +void CS_SetTelemetryPeriod(double seconds); +double CS_GetTelemetryElapsedTime(); +int64_t CS_GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status); +double CS_GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status); + // // Logging Functions // diff --git a/src/main/native/include/cscore_cpp.h b/src/main/native/include/cscore_cpp.h index d9572bdf6f..99e847ba4a 100644 --- a/src/main/native/include/cscore_cpp.h +++ b/src/main/native/include/cscore_cpp.h @@ -84,7 +84,8 @@ struct RawEvent { kSinkDestroyed = CS_SINK_DESTROYED, kSinkEnabled = CS_SINK_ENABLED, kSinkDisabled = CS_SINK_DISABLED, - kNetworkInterfacesChanged = CS_NETWORK_INTERFACES_CHANGED + kNetworkInterfacesChanged = CS_NETWORK_INTERFACES_CHANGED, + kTelemetryUpdated = CS_TELEMETRY_UPDATED }; RawEvent() = default; @@ -306,6 +307,16 @@ void RemoveListener(CS_Listener handle, CS_Status* status); bool NotifierDestroyed(); +// +// Telemetry Functions +// +void SetTelemetryPeriod(double seconds); +double GetTelemetryElapsedTime(); +int64_t GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status); +double GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind, + CS_Status* status); + // // Logging Functions // diff --git a/src/main/native/include/cscore_oo.h b/src/main/native/include/cscore_oo.h index 69320647a7..7fc0d7fb91 100644 --- a/src/main/native/include/cscore_oo.h +++ b/src/main/native/include/cscore_oo.h @@ -170,6 +170,16 @@ class VideoSource { /// @return True if set successfully bool SetFPS(int fps); + /// Get the actual FPS. + /// SetTelemetryPeriod() must be called for this to be valid. + /// @return Actual FPS averaged over the telemetry period. + double GetActualFPS() const; + + /// Get the data rate (in bytes per second). + /// SetTelemetryPeriod() must be called for this to be valid. + /// @return Data rate averaged over the telemetry period. + double GetActualDataRate() const; + /// Enumerate all known video modes for this source. std::vector EnumerateVideoModes() const; diff --git a/src/main/native/include/cscore_oo.inl b/src/main/native/include/cscore_oo.inl index be31d376b9..d4181c6854 100644 --- a/src/main/native/include/cscore_oo.inl +++ b/src/main/native/include/cscore_oo.inl @@ -158,6 +158,18 @@ inline bool VideoSource::SetFPS(int fps) { return SetSourceFPS(m_handle, fps, &m_status); } +inline double VideoSource::GetActualFPS() const { + m_status = 0; + return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_FRAMES_RECEIVED, + &m_status); +} + +inline double VideoSource::GetActualDataRate() const { + m_status = 0; + return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_BYTES_RECEIVED, + &m_status); +} + inline std::vector VideoSource::EnumerateVideoModes() const { CS_Status status = 0; return EnumerateSourceVideoModes(m_handle, &status);