mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-02 02:51:42 +00:00
MjpegServer: Add ability to set compression, etc in code (#1229)
This allows code to set the stream compression, resolution, and FPS used if not specified in HTTP parameters by the client.
This commit is contained in:
@@ -43,4 +43,60 @@ public class MjpegServer extends VideoSink {
|
|||||||
public int getPort() {
|
public int getPort() {
|
||||||
return CameraServerJNI.getMjpegServerPort(m_handle);
|
return CameraServerJNI.getMjpegServerPort(m_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the stream resolution for clients that don't specify it.
|
||||||
|
*
|
||||||
|
* <p>It is not necessary to set this if it is the same as the source
|
||||||
|
* resolution.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,11 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
|
|||||||
std::shared_ptr<SourceImpl> m_source;
|
std::shared_ptr<SourceImpl> m_source;
|
||||||
bool m_streaming = false;
|
bool m_streaming = false;
|
||||||
bool m_noStreaming = 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:
|
private:
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
@@ -111,11 +116,6 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
|
|||||||
m_source->DisableSink();
|
m_source->DisableSink();
|
||||||
m_streaming = false;
|
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.
|
// 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_BOOLEAN:
|
||||||
case CS_PROP_INTEGER:
|
case CS_PROP_INTEGER:
|
||||||
case CS_PROP_ENUM: {
|
case CS_PROP_ENUM: {
|
||||||
int val;
|
int val = 0;
|
||||||
if (value.getAsInteger(10, val)) {
|
if (value.getAsInteger(10, val)) {
|
||||||
response << param << ": \"invalid integer\"\r\n";
|
response << param << ": \"invalid integer\"\r\n";
|
||||||
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
|
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
|
||||||
@@ -558,6 +558,25 @@ MjpegServerImpl::MjpegServerImpl(wpi::StringRef name,
|
|||||||
desc << "HTTP Server on port " << port;
|
desc << "HTTP Server on port " << port;
|
||||||
SetDescription(desc.str());
|
SetDescription(desc.str());
|
||||||
|
|
||||||
|
// Create properties
|
||||||
|
m_widthProp = CreateProperty("width", [] {
|
||||||
|
return std::make_unique<PropertyImpl>("width", CS_PROP_INTEGER, 1, 0, 0);
|
||||||
|
});
|
||||||
|
m_heightProp = CreateProperty("height", [] {
|
||||||
|
return std::make_unique<PropertyImpl>("height", CS_PROP_INTEGER, 1, 0, 0);
|
||||||
|
});
|
||||||
|
m_compressionProp = CreateProperty("compression", [] {
|
||||||
|
return std::make_unique<PropertyImpl>("compression", CS_PROP_INTEGER, -1,
|
||||||
|
100, 1, -1, -1);
|
||||||
|
});
|
||||||
|
m_defaultCompressionProp = CreateProperty("default_compression", [] {
|
||||||
|
return std::make_unique<PropertyImpl>("default_compression",
|
||||||
|
CS_PROP_INTEGER, 0, 100, 1, 80, 80);
|
||||||
|
});
|
||||||
|
m_fpsProp = CreateProperty("fps", [] {
|
||||||
|
return std::make_unique<PropertyImpl>("fps", CS_PROP_INTEGER, 1, 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
m_serverThread = std::thread(&MjpegServerImpl::ServerThreadMain, this);
|
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 width = m_width != 0 ? m_width : frame.GetOriginalWidth();
|
||||||
int height = m_height != 0 ? m_height : frame.GetOriginalHeight();
|
int height = m_height != 0 ? m_height : frame.GetOriginalHeight();
|
||||||
Image* image = frame.GetImageMJPEG(
|
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) {
|
if (!image) {
|
||||||
// Shouldn't happen, but just in case...
|
// Shouldn't happen, but just in case...
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
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_istream is{*m_stream};
|
||||||
wpi::raw_socket_ostream os{*m_stream, true};
|
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
|
// Read the request string from the stream
|
||||||
wpi::SmallString<128> reqBuf;
|
wpi::SmallString<128> reqBuf;
|
||||||
wpi::StringRef req = is.getline(reqBuf, 4096);
|
wpi::StringRef req = is.getline(reqBuf, 4096);
|
||||||
@@ -864,6 +878,11 @@ void MjpegServerImpl::ServerThreadMain() {
|
|||||||
thr->m_stream = std::move(stream);
|
thr->m_stream = std::move(stream);
|
||||||
thr->m_source = source;
|
thr->m_source = source;
|
||||||
thr->m_noStreaming = nstreams >= 10;
|
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();
|
thr->m_cond.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ class MjpegServerImpl : public SinkImpl {
|
|||||||
std::thread m_serverThread;
|
std::thread m_serverThread;
|
||||||
|
|
||||||
std::vector<wpi::SafeThreadOwner<ConnThread>> m_connThreads;
|
std::vector<wpi::SafeThreadOwner<ConnThread>> m_connThreads;
|
||||||
|
|
||||||
|
// property indices
|
||||||
|
int m_widthProp;
|
||||||
|
int m_heightProp;
|
||||||
|
int m_compressionProp;
|
||||||
|
int m_defaultCompressionProp;
|
||||||
|
int m_fpsProp;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cs
|
} // namespace cs
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ class PropertyContainer {
|
|||||||
}
|
}
|
||||||
return ndx;
|
return ndx;
|
||||||
}
|
}
|
||||||
|
template <typename NewFunc>
|
||||||
|
int CreateProperty(wpi::StringRef name, NewFunc newFunc) {
|
||||||
|
return CreateOrUpdateProperty(name, newFunc, [](PropertyImpl&) {});
|
||||||
|
}
|
||||||
|
|
||||||
// Create an "empty" property. This is called by GetPropertyIndex to create
|
// Create an "empty" property. This is called by GetPropertyIndex to create
|
||||||
// properties that don't exist (as GetPropertyIndex can't fail).
|
// properties that don't exist (as GetPropertyIndex can't fail).
|
||||||
|
|||||||
@@ -567,6 +567,43 @@ class MjpegServer : public VideoSink {
|
|||||||
|
|
||||||
/// Get the port number of the server.
|
/// Get the port number of the server.
|
||||||
int GetPort() const;
|
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.
|
/// A sink for user code to accept video frames as OpenCV images.
|
||||||
|
|||||||
@@ -498,6 +498,30 @@ inline int MjpegServer::GetPort() const {
|
|||||||
return cs::GetMjpegServerPort(m_handle, &m_status);
|
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) {
|
inline CvSink::CvSink(wpi::StringRef name) {
|
||||||
m_handle = CreateCvSink(name, &m_status);
|
m_handle = CreateCvSink(name, &m_status);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user