diff --git a/cscore/src/main/java/edu/wpi/cscore/MjpegServer.java b/cscore/src/main/java/edu/wpi/cscore/MjpegServer.java
index d0cfecbf23..03aa55aeab 100644
--- a/cscore/src/main/java/edu/wpi/cscore/MjpegServer.java
+++ b/cscore/src/main/java/edu/wpi/cscore/MjpegServer.java
@@ -43,4 +43,60 @@ public class MjpegServer extends VideoSink {
public int getPort() {
return CameraServerJNI.getMjpegServerPort(m_handle);
}
+
+ /**
+ * Set the stream resolution for clients that don't specify it.
+ *
+ *
It is not necessary to set this if it is the same as the source
+ * resolution.
+ *
+ *
Setting this different than the source resolution will result in
+ * increased CPU usage, particularly for MJPEG source cameras, as it will
+ * decompress, resize, and recompress the image, instead of using the
+ * camera's MJPEG image directly.
+ *
+ * @param width width, 0 for unspecified
+ * @param height height, 0 for unspecified
+ */
+ void setResolution(int width, int height) {
+ CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "width"), width);
+ CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "height"), height);
+ }
+
+ /**
+ * Set the stream frames per second (FPS) for clients that don't specify it.
+ *
+ *
It is not necessary to set this if it is the same as the source FPS.
+ *
+ * @param fps FPS, 0 for unspecified
+ */
+ void setFPS(int fps) {
+ CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "fps"), fps);
+ }
+
+ /**
+ * Set the compression for clients that don't specify it.
+ *
+ *
Setting this will result in increased CPU usage for MJPEG source cameras
+ * as it will decompress and recompress the image instead of using the
+ * camera's MJPEG image directly.
+ *
+ * @param quality JPEG compression quality (0-100), -1 for unspecified
+ */
+ void setCompression(int quality) {
+ CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "compression"),
+ quality);
+ }
+
+ /**
+ * Set the default compression used for non-MJPEG sources. If not set,
+ * 80 is used. This function has no effect on MJPEG source cameras; use
+ * SetCompression() instead to force recompression of MJPEG source images.
+ *
+ * @param quality JPEG compression quality (0-100)
+ */
+ void setDefaultCompression(int quality) {
+ CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "default_compression"),
+ quality);
+ }
}
diff --git a/cscore/src/main/native/cpp/MjpegServerImpl.cpp b/cscore/src/main/native/cpp/MjpegServerImpl.cpp
index 6b8230b983..299419023f 100644
--- a/cscore/src/main/native/cpp/MjpegServerImpl.cpp
+++ b/cscore/src/main/native/cpp/MjpegServerImpl.cpp
@@ -89,6 +89,11 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
std::shared_ptr m_source;
bool m_streaming = false;
bool m_noStreaming = false;
+ int m_width = 0;
+ int m_height = 0;
+ int m_compression = -1;
+ int m_defaultCompression = 80;
+ int m_fps = 0;
private:
std::string m_name;
@@ -111,11 +116,6 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
m_source->DisableSink();
m_streaming = false;
}
-
- int m_width{0};
- int m_height{0};
- int m_compression{-1};
- int m_fps{0};
};
// Standard header to send along with other header information like mimetype.
@@ -293,7 +293,7 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(wpi::raw_ostream& os,
case CS_PROP_BOOLEAN:
case CS_PROP_INTEGER:
case CS_PROP_ENUM: {
- int val;
+ int val = 0;
if (value.getAsInteger(10, val)) {
response << param << ": \"invalid integer\"\r\n";
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
@@ -558,6 +558,25 @@ MjpegServerImpl::MjpegServerImpl(wpi::StringRef name,
desc << "HTTP Server on port " << port;
SetDescription(desc.str());
+ // Create properties
+ m_widthProp = CreateProperty("width", [] {
+ return std::make_unique("width", CS_PROP_INTEGER, 1, 0, 0);
+ });
+ m_heightProp = CreateProperty("height", [] {
+ return std::make_unique("height", CS_PROP_INTEGER, 1, 0, 0);
+ });
+ m_compressionProp = CreateProperty("compression", [] {
+ return std::make_unique("compression", CS_PROP_INTEGER, -1,
+ 100, 1, -1, -1);
+ });
+ m_defaultCompressionProp = CreateProperty("default_compression", [] {
+ return std::make_unique("default_compression",
+ CS_PROP_INTEGER, 0, 100, 1, 80, 80);
+ });
+ m_fpsProp = CreateProperty("fps", [] {
+ return std::make_unique("fps", CS_PROP_INTEGER, 1, 0, 0);
+ });
+
m_serverThread = std::thread(&MjpegServerImpl::ServerThreadMain, this);
}
@@ -636,7 +655,8 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
int width = m_width != 0 ? m_width : frame.GetOriginalWidth();
int height = m_height != 0 ? m_height : frame.GetOriginalHeight();
Image* image = frame.GetImageMJPEG(
- width, height, m_compression, m_compression == -1 ? 80 : m_compression);
+ width, height, m_compression,
+ m_compression == -1 ? m_defaultCompression : m_compression);
if (!image) {
// Shouldn't happen, but just in case...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
@@ -692,12 +712,6 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
wpi::raw_socket_istream is{*m_stream};
wpi::raw_socket_ostream os{*m_stream, true};
- // Reset per-request settings
- m_width = 0;
- m_height = 0;
- m_compression = -1;
- m_fps = 0;
-
// Read the request string from the stream
wpi::SmallString<128> reqBuf;
wpi::StringRef req = is.getline(reqBuf, 4096);
@@ -864,6 +878,11 @@ void MjpegServerImpl::ServerThreadMain() {
thr->m_stream = std::move(stream);
thr->m_source = source;
thr->m_noStreaming = nstreams >= 10;
+ thr->m_width = GetProperty(m_widthProp)->value;
+ thr->m_height = GetProperty(m_heightProp)->value;
+ thr->m_compression = GetProperty(m_compressionProp)->value;
+ thr->m_defaultCompression = GetProperty(m_defaultCompressionProp)->value;
+ thr->m_fps = GetProperty(m_fpsProp)->value;
thr->m_cond.notify_one();
}
diff --git a/cscore/src/main/native/cpp/MjpegServerImpl.h b/cscore/src/main/native/cpp/MjpegServerImpl.h
index 312ba768b8..16b7a71e81 100644
--- a/cscore/src/main/native/cpp/MjpegServerImpl.h
+++ b/cscore/src/main/native/cpp/MjpegServerImpl.h
@@ -55,6 +55,13 @@ class MjpegServerImpl : public SinkImpl {
std::thread m_serverThread;
std::vector> m_connThreads;
+
+ // property indices
+ int m_widthProp;
+ int m_heightProp;
+ int m_compressionProp;
+ int m_defaultCompressionProp;
+ int m_fpsProp;
};
} // namespace cs
diff --git a/cscore/src/main/native/cpp/PropertyContainer.h b/cscore/src/main/native/cpp/PropertyContainer.h
index 5dad43625a..210b27d3a7 100644
--- a/cscore/src/main/native/cpp/PropertyContainer.h
+++ b/cscore/src/main/native/cpp/PropertyContainer.h
@@ -78,6 +78,10 @@ class PropertyContainer {
}
return ndx;
}
+ template
+ int CreateProperty(wpi::StringRef name, NewFunc newFunc) {
+ return CreateOrUpdateProperty(name, newFunc, [](PropertyImpl&) {});
+ }
// Create an "empty" property. This is called by GetPropertyIndex to create
// properties that don't exist (as GetPropertyIndex can't fail).
diff --git a/cscore/src/main/native/include/cscore_oo.h b/cscore/src/main/native/include/cscore_oo.h
index acf914c5c4..a1342307e0 100644
--- a/cscore/src/main/native/include/cscore_oo.h
+++ b/cscore/src/main/native/include/cscore_oo.h
@@ -567,6 +567,43 @@ class MjpegServer : public VideoSink {
/// Get the port number of the server.
int GetPort() const;
+
+ /// Set the stream resolution for clients that don't specify it.
+ ///
+ /// It is not necessary to set this if it is the same as the source
+ /// resolution.
+ ///
+ /// Setting this different than the source resolution will result in
+ /// increased CPU usage, particularly for MJPEG source cameras, as it will
+ /// decompress, resize, and recompress the image, instead of using the
+ /// camera's MJPEG image directly.
+ ///
+ /// @param width width, 0 for unspecified
+ /// @param height height, 0 for unspecified
+ void SetResolution(int width, int height);
+
+ /// Set the stream frames per second (FPS) for clients that don't specify it.
+ ///
+ /// It is not necessary to set this if it is the same as the source FPS.
+ ///
+ /// @param fps FPS, 0 for unspecified
+ void SetFPS(int fps);
+
+ /// Set the compression for clients that don't specify it.
+ ///
+ /// Setting this will result in increased CPU usage for MJPEG source cameras
+ /// as it will decompress and recompress the image instead of using the
+ /// camera's MJPEG image directly.
+ ///
+ /// @param quality JPEG compression quality (0-100), -1 for unspecified
+ void SetCompression(int quality);
+
+ /// Set the default compression used for non-MJPEG sources. If not set,
+ /// 80 is used. This function has no effect on MJPEG source cameras; use
+ /// SetCompression() instead to force recompression of MJPEG source images.
+ ///
+ /// @param quality JPEG compression quality (0-100)
+ void SetDefaultCompression(int quality);
};
/// A sink for user code to accept video frames as OpenCV images.
diff --git a/cscore/src/main/native/include/cscore_oo.inl b/cscore/src/main/native/include/cscore_oo.inl
index b814d65057..68541a4d78 100644
--- a/cscore/src/main/native/include/cscore_oo.inl
+++ b/cscore/src/main/native/include/cscore_oo.inl
@@ -498,6 +498,30 @@ inline int MjpegServer::GetPort() const {
return cs::GetMjpegServerPort(m_handle, &m_status);
}
+inline void MjpegServer::SetResolution(int width, int height) {
+ m_status = 0;
+ SetProperty(GetSinkProperty(m_handle, "width", &m_status), width, &m_status);
+ SetProperty(GetSinkProperty(m_handle, "height", &m_status), height,
+ &m_status);
+}
+
+inline void MjpegServer::SetFPS(int fps) {
+ m_status = 0;
+ SetProperty(GetSinkProperty(m_handle, "fps", &m_status), fps, &m_status);
+}
+
+inline void MjpegServer::SetCompression(int quality) {
+ m_status = 0;
+ SetProperty(GetSinkProperty(m_handle, "compression", &m_status), quality,
+ &m_status);
+}
+
+inline void MjpegServer::SetDefaultCompression(int quality) {
+ m_status = 0;
+ SetProperty(GetSinkProperty(m_handle, "default_compression", &m_status),
+ quality, &m_status);
+}
+
inline CvSink::CvSink(wpi::StringRef name) {
m_handle = CreateCvSink(name, &m_status);
}