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); }