From 9c9f822f35fe44ca858fcc801a56f5784245dce5 Mon Sep 17 00:00:00 2001 From: Maik Jurischka Date: Fri, 12 Dec 2025 14:37:57 +0100 Subject: [PATCH] stream optimizations --- GStreamerPipeline.cpp | 16 ++--- GStreamerPipeline.h | 4 +- SocketServer.cpp | 13 ++-- StreamingEngine.cpp | 3 +- StreamingEngine.h | 4 +- main.cpp | 5 +- scripts/mjpeg_http_proxy.sh | 24 +++++++ scripts/mjpeg_http_server.py | 79 +++++++++++++++++++++++ scripts/set_pipeline_mjpeg.sh | 12 +++- scripts/set_pipeline_mjpeg_passthrough.sh | 32 +++++++++ 10 files changed, 168 insertions(+), 24 deletions(-) create mode 100755 scripts/mjpeg_http_proxy.sh create mode 100755 scripts/mjpeg_http_server.py create mode 100755 scripts/set_pipeline_mjpeg_passthrough.sh diff --git a/GStreamerPipeline.cpp b/GStreamerPipeline.cpp index 6cdf6c0..bc7624d 100644 --- a/GStreamerPipeline.cpp +++ b/GStreamerPipeline.cpp @@ -1,10 +1,11 @@ #include "GStreamerPipeline.h" #include #include +#include -GStreamerPipeline::GStreamerPipeline(const std::string& pipelineDescription) +GStreamerPipeline::GStreamerPipeline(std::string pipelineDescription) : pipeline_(nullptr), appsrc_(nullptr), bus_(nullptr), running_(false), - pipelineDescription_(pipelineDescription), width_(0), height_(0) { + pipelineDescription_(std::move(pipelineDescription)), width_(0), height_(0) { gst_init(nullptr, nullptr); } @@ -30,7 +31,7 @@ bool GStreamerPipeline::start() { } GError* error = nullptr; - std::string fullPipeline = "appsrc name=source ! " + pipelineDescription_; + const std::string fullPipeline = "appsrc name=source ! " + pipelineDescription_; pipeline_ = gst_parse_launch(fullPipeline.c_str(), &error); if (error) { @@ -106,7 +107,7 @@ void GStreamerPipeline::stop() { std::cout << "GStreamer pipeline stopped" << std::endl; } -bool GStreamerPipeline::pushBuffer(uint8_t* data, size_t size, int width, int height, const std::string& format) { +bool GStreamerPipeline::pushBuffer(const uint8_t* data, const size_t size, const int width, const int height, const std::string& format) { if (!running_ || !appsrc_) { return false; } @@ -119,16 +120,13 @@ bool GStreamerPipeline::pushBuffer(uint8_t* data, size_t size, int width, int he // Set caps based on format std::string capsStr; - if (format == "YUY2" || format == "UYVY") { + if (format == "YUY2" || format == "UYVY" || format == "BGR" || format == "RGB") { capsStr = "video/x-raw,format=" + format + ",width=" + std::to_string(width) + ",height=" + std::to_string(height) + ",framerate=30/1"; } else if (format == "MJPG") { capsStr = "image/jpeg,width=" + std::to_string(width) + ",height=" + std::to_string(height) + ",framerate=30/1"; - } else if (format == "BGR" || format == "RGB") { - capsStr = "video/x-raw,format=" + format + ",width=" + std::to_string(width) + - ",height=" + std::to_string(height) + ",framerate=30/1"; - } else { + } else { capsStr = "video/x-raw,width=" + std::to_string(width) + ",height=" + std::to_string(height) + ",framerate=30/1"; } diff --git a/GStreamerPipeline.h b/GStreamerPipeline.h index 3029232..1139536 100644 --- a/GStreamerPipeline.h +++ b/GStreamerPipeline.h @@ -7,12 +7,12 @@ class GStreamerPipeline { public: - explicit GStreamerPipeline(const std::string& pipelineDescription); + explicit GStreamerPipeline(std::string pipelineDescription); ~GStreamerPipeline(); bool start(); void stop(); - bool pushBuffer(uint8_t* data, size_t size, int width, int height, const std::string& format); + bool pushBuffer(const uint8_t* data, size_t size, int width, int height, const std::string& format); bool isRunning() const { return running_; } void setPipelineDescription(const std::string& description); diff --git a/SocketServer.cpp b/SocketServer.cpp index 0a93727..a36ca19 100644 --- a/SocketServer.cpp +++ b/SocketServer.cpp @@ -17,7 +17,7 @@ bool SocketServer::start(CommandCallback callback) { return false; } - commandCallback_ = callback; + commandCallback_ = std::move(callback); // Remove existing socket file if it exists unlink(socketPath_.c_str()); @@ -30,12 +30,11 @@ bool SocketServer::start(CommandCallback callback) { } // Bind socket - struct sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); + struct sockaddr_un addr = {}; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socketPath_.c_str(), sizeof(addr.sun_path) - 1); - if (bind(serverFd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + if (bind(serverFd_, reinterpret_cast(&addr), sizeof(addr)) < 0) { std::cerr << "Failed to bind socket: " << strerror(errno) << std::endl; close(serverFd_); return false; @@ -94,16 +93,16 @@ void SocketServer::serverLoop() { } } -void SocketServer::handleClient(int clientFd) { +void SocketServer::handleClient(const int clientFd) { char buffer[4096]; ssize_t bytesRead = recv(clientFd, buffer, sizeof(buffer) - 1, 0); if (bytesRead > 0) { buffer[bytesRead] = '\0'; - std::string command(buffer); + const std::string command(buffer); // Call the command callback - std::string response = commandCallback_(command); + const std::string response = commandCallback_(command); // Send response back to client send(clientFd, response.c_str(), response.length(), 0); diff --git a/StreamingEngine.cpp b/StreamingEngine.cpp index a2106a3..483c79d 100644 --- a/StreamingEngine.cpp +++ b/StreamingEngine.cpp @@ -1,9 +1,10 @@ #include "StreamingEngine.h" #include #include +#include StreamingEngine::StreamingEngine(std::shared_ptr camera) - : camera_(camera), running_(false), bufferSize_(0) { + : camera_(std::move(camera)), running_(false), bufferSize_(0) { gstPipeline_ = std::make_unique(""); } diff --git a/StreamingEngine.h b/StreamingEngine.h index e13360f..5367577 100644 --- a/StreamingEngine.h +++ b/StreamingEngine.h @@ -14,10 +14,10 @@ public: bool start(const std::string& gstPipeline); void stop(); - bool isRunning() const { return running_; } + [[nodiscard]] bool isRunning() const { return running_; } void setFormat(const VxFormat& format); - VxFormat getCurrentFormat() const { return currentFormat_; } + [[nodiscard]] VxFormat getCurrentFormat() const { return currentFormat_; } void setPipelineDescription(const std::string& pipeline); diff --git a/main.cpp b/main.cpp index a4a9271..56cc634 100644 --- a/main.cpp +++ b/main.cpp @@ -73,13 +73,14 @@ int main() { std::cout << "\n========================================" << std::endl; std::cout << "VizionStreamer Ready" << std::endl; + std::cout << "Author: Maik Jurischka 1 else 8081 + http_port = int(sys.argv[2]) if len(sys.argv) > 2 else 8080 + + MJPEGProxyHandler.tcp_port = tcp_port + + server = ThreadedHTTPServer(('0.0.0.0', http_port), MJPEGProxyHandler) + + print(f"MJPEG HTTP Proxy Server") + print(f"========================") + print(f"Proxying TCP stream from localhost:{tcp_port}") + print(f"HTTP server listening on port {http_port}") + print(f"") + print(f"Configure vizionStreamer with:") + print(f' ./scripts/set_pipeline_mjpeg.sh {tcp_port}') + print(f"") + print(f"Access stream at: http://localhost:{http_port}") + print(f"") + + try: + server.serve_forever() + except KeyboardInterrupt: + print("\nShutting down...") + server.shutdown() diff --git a/scripts/set_pipeline_mjpeg.sh b/scripts/set_pipeline_mjpeg.sh index 6878b21..35665bb 100755 --- a/scripts/set_pipeline_mjpeg.sh +++ b/scripts/set_pipeline_mjpeg.sh @@ -4,8 +4,18 @@ SOCKET="/tmp/vizion_control.sock" PORT="${1:-8080}" +# Check if gst-plugins-good with souphttpsink is available +if gst-inspect-1.0 souphttpsink &>/dev/null; then + echo "Using souphttpsink for HTTP server..." + PIPELINE="videoconvert ! jpegenc quality=85 ! multipartmux boundary=\"--videoboundary\" ! souphttpsink port=$PORT" +else + echo "WARNING: souphttpsink not found. Using tcpserversink (may not work in browsers)." + echo "Install gst-plugins-good: sudo apt install gstreamer1.0-plugins-good" + PIPELINE="videoconvert ! jpegenc quality=85 ! multipartmux ! tcpserversink host=0.0.0.0 port=$PORT" +fi + echo "Setting MJPEG HTTP streaming pipeline on port $PORT..." -echo "{\"command\":\"set_pipeline\",\"params\":{\"pipeline\":\"videoconvert ! jpegenc ! multipartmux ! tcpserversink host=0.0.0.0 port=$PORT\"}}" | socat - UNIX-CONNECT:$SOCKET +echo "{\"command\":\"set_pipeline\",\"params\":{\"pipeline\":\"$PIPELINE\"}}" | socat - UNIX-CONNECT:$SOCKET echo "" echo "Pipeline set. Start streaming with start_stream.sh" diff --git a/scripts/set_pipeline_mjpeg_passthrough.sh b/scripts/set_pipeline_mjpeg_passthrough.sh new file mode 100755 index 0000000..64468ca --- /dev/null +++ b/scripts/set_pipeline_mjpeg_passthrough.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Optimized pipeline for cameras that already output MJPEG format +# Passes through MJPEG data without re-encoding + +SOCKET="/tmp/vizion_control.sock" +PORT="${1:-8080}" + +# Check if gst-plugins-good with souphttpsink is available +if gst-inspect-1.0 souphttpsink &>/dev/null; then + echo "Using souphttpsink for HTTP server (no re-encoding)..." + PIPELINE="multipartmux boundary=\"--videoboundary\" ! souphttpsink port=$PORT" +else + echo "WARNING: souphttpsink not found. Using tcpserversink (requires HTTP proxy)." + echo "Install gst-plugins-good: sudo apt install gstreamer1.0-plugins-good" + echo "Or use: ./scripts/mjpeg_http_server.py $PORT 8080" + PIPELINE="multipartmux ! tcpserversink host=0.0.0.0 port=$PORT" +fi + +echo "Setting optimized MJPEG passthrough pipeline on port $PORT..." +echo "NOTE: This pipeline is optimized for cameras with native MJPEG output." +echo "" + +echo "{\"command\":\"set_pipeline\",\"params\":{\"pipeline\":\"$PIPELINE\"}}" | socat - UNIX-CONNECT:$SOCKET + +echo "" +echo "Pipeline set. Start streaming with start_stream.sh" +if gst-inspect-1.0 souphttpsink &>/dev/null; then + echo "View stream in browser: http://localhost:$PORT" +else + echo "Start HTTP proxy first: ./scripts/mjpeg_http_server.py $PORT 8080" + echo "Then view stream: http://localhost:8080" +fi