From 9f0eac870f429208fd178f9721f2dbdec533e7c3 Mon Sep 17 00:00:00 2001 From: Maik Jurischka Date: Fri, 30 Jan 2026 16:29:19 +0100 Subject: [PATCH] debug shared_buffer stream --- fix_exposure.sh | 17 ++ include/vizionstreamer/StreamingEngine.h | 2 + service/README.md | 145 +++++++++++++++ service/install.sh | 73 ++++++++ service/uninstall.sh | 53 ++++++ service/vizionstreamer.service | 33 ++++ service/watchdog.sh | 119 ++++++++++++ src/CameraController.cpp | 24 ++- src/SharedMemoryWriter.cpp | 13 ++ src/StreamingEngine.cpp | 221 ++++++++++++++++++++++- src/main.cpp | 2 +- watchdog.sh | 119 ++++++++++++ 12 files changed, 805 insertions(+), 16 deletions(-) create mode 100755 fix_exposure.sh create mode 100644 service/README.md create mode 100755 service/install.sh create mode 100755 service/uninstall.sh create mode 100644 service/vizionstreamer.service create mode 100755 service/watchdog.sh create mode 100755 watchdog.sh diff --git a/fix_exposure.sh b/fix_exposure.sh new file mode 100755 index 0000000..83c18ee --- /dev/null +++ b/fix_exposure.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Script to fix camera exposure settings +# This stops the stream, enables auto-exposure, and restarts the stream + +SOCKET="/tmp/vizion_control.sock" + +echo "Stopping stream..." +echo '{"command":"stop_stream"}' | socat - UNIX-CONNECT:$SOCKET + +echo "Setting auto-exposure..." +echo '{"command":"set_exposure","params":{"mode":"auto","value":"0"}}' | socat - UNIX-CONNECT:$SOCKET + +echo "Starting stream..." +echo '{"command":"start_stream"}' | socat - UNIX-CONNECT:$SOCKET + +echo "Done! Camera should now show proper image." diff --git a/include/vizionstreamer/StreamingEngine.h b/include/vizionstreamer/StreamingEngine.h index ebd0838..9d8c41c 100644 --- a/include/vizionstreamer/StreamingEngine.h +++ b/include/vizionstreamer/StreamingEngine.h @@ -39,6 +39,7 @@ public: private: void acquisitionLoop(); + size_t convertToRGB(const uint8_t* src, uint8_t* dst, int width, int height, VX_IMAGE_FORMAT format); std::shared_ptr camera_; std::unique_ptr gstPipeline_; @@ -48,5 +49,6 @@ private: std::mutex mutex_; VxFormat currentFormat_; std::unique_ptr buffer_; + std::unique_ptr rgbBuffer_; // Buffer for RGB conversion size_t bufferSize_; }; diff --git a/service/README.md b/service/README.md new file mode 100644 index 0000000..d05251c --- /dev/null +++ b/service/README.md @@ -0,0 +1,145 @@ +# VizionStreamer Systemd Service + +Automatischer Start und Überwachung des VizionStreamer mit systemd. + +## Inhalt + +- `vizionstreamer.service` - Systemd service file +- `watchdog.sh` - Watchdog-Script (startet Streamer neu bei Crash) +- `install.sh` - Installations-Script +- `uninstall.sh` - Deinstallations-Script + +## Installation + +```bash +cd service +sudo ./install.sh +``` + +Das Script: +1. Kopiert die Service-Datei nach `/etc/systemd/system/` +2. Aktiviert den Service (Start beim Booten) +3. Startet den Service sofort + +## Deinstallation + +```bash +cd service +sudo ./uninstall.sh +``` + +## Befehle + +### Service verwalten + +```bash +# Service starten +sudo systemctl start vizionstreamer + +# Service stoppen +sudo systemctl stop vizionstreamer + +# Service neu starten +sudo systemctl restart vizionstreamer + +# Status anzeigen +sudo systemctl status vizionstreamer + +# Autostart deaktivieren +sudo systemctl disable vizionstreamer + +# Autostart aktivieren +sudo systemctl enable vizionstreamer +``` + +### Logs anzeigen + +```bash +# Live-Logs verfolgen +sudo journalctl -u vizionstreamer -f + +# Letzte 100 Zeilen +sudo journalctl -u vizionstreamer -n 100 + +# Logs seit heute +sudo journalctl -u vizionstreamer --since today + +# Alle Logs +sudo journalctl -u vizionstreamer --no-pager +``` + +## Funktionen + +✅ **Automatischer Start** beim Booten +✅ **Watchdog** startet Prozess bei Crash neu (max. 10 Versuche) +✅ **Systemd Integration** - Logs in journald +✅ **Graceful Shutdown** - Sauberes Beenden +✅ **Restart-Limit** - Verhindert endlose Restart-Loops + +## Konfiguration + +### Service-Einstellungen anpassen + +Service-Datei bearbeiten: +```bash +sudo systemctl edit vizionstreamer --full +``` + +Nach Änderungen: +```bash +sudo systemctl daemon-reload +sudo systemctl restart vizionstreamer +``` + +### Watchdog-Intervall ändern + +In `watchdog.sh`: +```bash +CHECK_INTERVAL=5 # Sekunden (Standard: 5) +``` + +## Troubleshooting + +### Service startet nicht + +1. Status prüfen: + ```bash + sudo systemctl status vizionstreamer + ``` + +2. Logs prüfen: + ```bash + sudo journalctl -u vizionstreamer -n 50 + ``` + +3. Manuell testen: + ```bash + ./watchdog.sh + ``` + +### Permissions + +Falls Permission-Probleme: +```bash +sudo usermod -a -G video maik +sudo usermod -a -G audio maik +``` + +### Service deaktivieren (temporär) + +```bash +sudo systemctl stop vizionstreamer +sudo systemctl disable vizionstreamer +``` + +## Systemanforderungen + +- Linux mit systemd +- Zugriff auf `/dev/video*` Geräte +- User `maik` muss Zugriff auf Kamera haben + +## Support + +Bei Problemen siehe: +- `sudo journalctl -u vizionstreamer -f` +- `tail -f ../watchdog.log` diff --git a/service/install.sh b/service/install.sh new file mode 100755 index 0000000..f544cd2 --- /dev/null +++ b/service/install.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Installation script for VizionStreamer systemd service + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +SERVICE_NAME="vizionstreamer.service" +SERVICE_FILE="$(pwd)/vizionstreamer.service" +SYSTEMD_DIR="/etc/systemd/system" + +echo -e "${GREEN}=== VizionStreamer Service Installation ===${NC}\n" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Error: This script must be run as root (use sudo)${NC}" + exit 1 +fi + +# Check if service file exists +if [ ! -f "$SERVICE_FILE" ]; then + echo -e "${RED}Error: Service file not found: $SERVICE_FILE${NC}" + exit 1 +fi + +# Check if vizionStreamer executable exists +if [ ! -f "/home/maik/CLionProjects/vizionStreamer/build/vizionStreamer" ]; then + echo -e "${YELLOW}Warning: vizionStreamer executable not found!${NC}" + echo "Please build the project first: cmake --build build" + read -p "Continue anyway? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +# Stop service if already running +if systemctl is-active --quiet $SERVICE_NAME; then + echo "Stopping existing service..." + systemctl stop $SERVICE_NAME +fi + +# Copy service file +echo "Installing service file to $SYSTEMD_DIR..." +cp "$SERVICE_FILE" "$SYSTEMD_DIR/" + +# Reload systemd +echo "Reloading systemd daemon..." +systemctl daemon-reload + +# Enable service +echo "Enabling service to start on boot..." +systemctl enable $SERVICE_NAME + +# Start service +echo "Starting service..." +systemctl start $SERVICE_NAME + +# Show status +echo -e "\n${GREEN}Installation complete!${NC}\n" +systemctl status $SERVICE_NAME --no-pager + +echo -e "\n${GREEN}=== Useful Commands ===${NC}" +echo " Start: sudo systemctl start $SERVICE_NAME" +echo " Stop: sudo systemctl stop $SERVICE_NAME" +echo " Restart: sudo systemctl restart $SERVICE_NAME" +echo " Status: sudo systemctl status $SERVICE_NAME" +echo " Logs: sudo journalctl -u $SERVICE_NAME -f" +echo " Disable: sudo systemctl disable $SERVICE_NAME" diff --git a/service/uninstall.sh b/service/uninstall.sh new file mode 100755 index 0000000..3bc9cad --- /dev/null +++ b/service/uninstall.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# Uninstallation script for VizionStreamer systemd service + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +SERVICE_NAME="vizionstreamer.service" +SYSTEMD_DIR="/etc/systemd/system" + +echo -e "${YELLOW}=== VizionStreamer Service Uninstallation ===${NC}\n" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Error: This script must be run as root (use sudo)${NC}" + exit 1 +fi + +# Check if service exists +if [ ! -f "$SYSTEMD_DIR/$SERVICE_NAME" ]; then + echo -e "${YELLOW}Service not installed.${NC}" + exit 0 +fi + +# Stop service +if systemctl is-active --quiet $SERVICE_NAME; then + echo "Stopping service..." + systemctl stop $SERVICE_NAME +fi + +# Disable service +if systemctl is-enabled --quiet $SERVICE_NAME; then + echo "Disabling service..." + systemctl disable $SERVICE_NAME +fi + +# Remove service file +echo "Removing service file..." +rm -f "$SYSTEMD_DIR/$SERVICE_NAME" + +# Reload systemd +echo "Reloading systemd daemon..." +systemctl daemon-reload + +# Reset failed state if any +systemctl reset-failed $SERVICE_NAME 2>/dev/null || true + +echo -e "\n${GREEN}Uninstallation complete!${NC}" +echo "Service '$SERVICE_NAME' has been removed." diff --git a/service/vizionstreamer.service b/service/vizionstreamer.service new file mode 100644 index 0000000..14f61f3 --- /dev/null +++ b/service/vizionstreamer.service @@ -0,0 +1,33 @@ +[Unit] +Description=VizionStreamer Camera Service with Watchdog +After=network.target +Wants=network-online.target + +[Service] +Type=simple +User=maik +Group=maik +WorkingDirectory=/home/maik/CLionProjects/vizionStreamer +ExecStart=/home/maik/CLionProjects/vizionStreamer/service/watchdog.sh +Restart=on-failure +RestartSec=10s + +# Environment +Environment="PATH=/usr/local/bin:/usr/bin:/bin" + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=vizionstreamer + +# Security settings (optional, kann bei Problemen auskommentiert werden) +# NoNewPrivileges=true +# PrivateTmp=true + +# Graceful shutdown +TimeoutStopSec=30 +KillMode=mixed +KillSignal=SIGTERM + +[Install] +WantedBy=multi-user.target diff --git a/service/watchdog.sh b/service/watchdog.sh new file mode 100755 index 0000000..0a79e95 --- /dev/null +++ b/service/watchdog.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# VizionStreamer Watchdog Script +# Automatically restarts the streamer if it crashes +# Usage: ./watchdog.sh + +STREAMER_PATH="./build/vizionStreamer" +CHECK_INTERVAL=5 # Seconds between checks +LOG_FILE="./watchdog.log" +PID_FILE="./vizionStreamer.pid" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to log messages +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +# Function to start the streamer +start_streamer() { + log "${GREEN}Starting VizionStreamer...${NC}" + $STREAMER_PATH & + STREAMER_PID=$! + echo $STREAMER_PID > "$PID_FILE" + log "VizionStreamer started with PID: $STREAMER_PID" +} + +# Function to check if process is running +is_running() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p $PID > /dev/null 2>&1; then + return 0 # Running + fi + fi + return 1 # Not running +} + +# Function to stop the streamer +stop_streamer() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p $PID > /dev/null 2>&1; then + log "${YELLOW}Stopping VizionStreamer (PID: $PID)...${NC}" + kill $PID + sleep 2 + # Force kill if still running + if ps -p $PID > /dev/null 2>&1; then + log "${RED}Force killing VizionStreamer...${NC}" + kill -9 $PID + fi + fi + rm -f "$PID_FILE" + fi +} + +# Trap SIGINT and SIGTERM to cleanly shutdown +cleanup() { + log "${YELLOW}Watchdog shutting down...${NC}" + stop_streamer + log "${GREEN}Watchdog stopped${NC}" + exit 0 +} + +trap cleanup SIGINT SIGTERM + +# Check if vizionStreamer executable exists +if [ ! -f "$STREAMER_PATH" ]; then + log "${RED}ERROR: VizionStreamer not found at $STREAMER_PATH${NC}" + log "Please build the project first: cmake --build build" + exit 1 +fi + +# Main watchdog loop +log "${GREEN}=== VizionStreamer Watchdog Started ===${NC}" +log "Check interval: ${CHECK_INTERVAL} seconds" +log "Press Ctrl+C to stop" + +RESTART_COUNT=0 + +# Start streamer initially +start_streamer +sleep 2 # Give it time to start + +while true; do + sleep $CHECK_INTERVAL + + if ! is_running; then + RESTART_COUNT=$((RESTART_COUNT + 1)) + log "${RED}VizionStreamer died! Restart count: $RESTART_COUNT${NC}" + + # Optional: limit restart attempts + if [ $RESTART_COUNT -gt 10 ]; then + log "${RED}ERROR: Too many restarts (10). Giving up.${NC}" + log "Check the logs for recurring errors." + exit 1 + fi + + # Clean up old PID file + rm -f "$PID_FILE" + + # Wait a bit before restarting to avoid rapid restart loops + sleep 2 + + # Restart + start_streamer + sleep 2 + else + # Still running - reset restart counter on successful check + if [ $RESTART_COUNT -gt 0 ]; then + RESTART_COUNT=0 + log "${GREEN}VizionStreamer stable again${NC}" + fi + fi +done diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 4295487..7b1040e 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -11,7 +11,7 @@ #include CameraController::CameraController(std::shared_ptr camera) - : camera_(camera), gstPipeline_("videoconvert ! autovideosink") { + : camera_(camera), gstPipeline_("fakesink") { streamingEngine_ = std::make_shared(camera); } @@ -154,8 +154,15 @@ std::string CameraController::handleSetExposure(const std::string& mode, const s const int flag = (mode == "auto") ? 1 : 0; const long expValue = value.empty() ? 0 : std::stol(value); - if (VxSetUVCImageProcessing(camera_, VX_UVC_IMAGE_PROPERTIES::UVC_IMAGE_EXPOSURE, - expValue, flag) != 0) { + std::cout << "[DEBUG] Setting exposure: mode=" << mode << " (flag=" << flag + << "), value=" << expValue << std::endl; + + const int result = VxSetUVCImageProcessing(camera_, VX_UVC_IMAGE_PROPERTIES::UVC_IMAGE_EXPOSURE, + expValue, flag); + + std::cout << "[DEBUG] VxSetUVCImageProcessing returned: " << result << std::endl; + + if (result != 0) { return createErrorResponse("Failed to set exposure"); } @@ -249,8 +256,15 @@ std::string CameraController::handleSetGamma(const std::string& value) { std::string CameraController::handleSetGain(const std::string& value) { try { const long val = std::stol(value); - if (VxSetUVCImageProcessing(camera_, VX_UVC_IMAGE_PROPERTIES::UVC_IMAGE_GAIN, - val, 0) != 0) { + + std::cout << "[DEBUG] Setting gain: value=" << val << std::endl; + + const int result = VxSetUVCImageProcessing(camera_, VX_UVC_IMAGE_PROPERTIES::UVC_IMAGE_GAIN, + val, 0); + + std::cout << "[DEBUG] VxSetUVCImageProcessing (gain) returned: " << result << std::endl; + + if (result != 0) { return createErrorResponse("Failed to set gain"); } return createSuccessResponse("Gain set successfully"); diff --git a/src/SharedMemoryWriter.cpp b/src/SharedMemoryWriter.cpp index ec23219..358ddf9 100644 --- a/src/SharedMemoryWriter.cpp +++ b/src/SharedMemoryWriter.cpp @@ -128,6 +128,19 @@ bool SharedMemoryWriter::writeFrame(const uint8_t* data, const size_t size, // 3. Copy frame data memcpy(frame_data, data, size); + // Debug: Log every 60 frames + static uint64_t debug_counter = 0; + if (++debug_counter % 60 == 0) { + std::cout << "[SHM] Frame #" << frame_counter_ + << " written. write_seq=" << header->write_sequence.load() + << " size=" << size + << " first 4 bytes: "; + for (int i = 0; i < 4 && i < size; i++) { + printf("%02x ", frame_data[i]); + } + std::cout << std::endl; + } + // 4. Increment sequence (even = write complete) header->write_sequence.fetch_add(1, std::memory_order_release); diff --git a/src/StreamingEngine.cpp b/src/StreamingEngine.cpp index ca64350..10c40a6 100644 --- a/src/StreamingEngine.cpp +++ b/src/StreamingEngine.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include StreamingEngine::StreamingEngine(std::shared_ptr camera) : camera_(std::move(camera)), running_(false), currentFormat_(), bufferSize_(0) { @@ -44,26 +46,41 @@ bool StreamingEngine::start(const std::string& gstPipeline) { return false; } - // Start camera streaming - if (VxStartStreaming(camera_) != 0) { - std::cerr << "Failed to start camera streaming" << std::endl; - return false; - } + // Ensure camera is not already streaming (clean slate) + VxStopStreaming(camera_); - // Get current format to allocate buffer + // Get current format to allocate buffer BEFORE starting streaming std::vector fmtList; if (VxGetFormatList(camera_, fmtList) != 0 || fmtList.empty()) { std::cerr << "Failed to get format list" << std::endl; - VxStopStreaming(camera_); return false; } currentFormat_ = fmtList[0]; + // Ensure format is properly set before streaming + if (VxSetFormat(camera_, currentFormat_) != 0) { + std::cerr << "Failed to set format before streaming" << std::endl; + return false; + } + + std::cout << "Starting stream with format: " << currentFormat_.width << "x" + << currentFormat_.height << " @ " << currentFormat_.framerate << " fps" << std::endl; + + // Start camera streaming + const int startResult = VxStartStreaming(camera_); + if (startResult != 0) { + std::cerr << "Failed to start camera streaming (error code: " << startResult << ")" << std::endl; + return false; + } + // Allocate buffer (assume worst case: uncompressed) const size_t calculatedBufferSize = currentFormat_.width * currentFormat_.height * 4; bufferSize_ = calculatedBufferSize; buffer_ = std::make_unique(bufferSize_); + // Allocate RGB conversion buffer for shared memory + rgbBuffer_ = std::make_unique(currentFormat_.width * currentFormat_.height * 3); + // Start GStreamer pipeline if configured if (useGStreamer) { gstPipeline_->setPipelineDescription(gstPipeline); @@ -130,6 +147,18 @@ void StreamingEngine::acquisitionLoop() { const VX_CAPTURE_RESULT result = VxGetImage(camera_, buffer_.get(), &dataSize, 1000); if (result == VX_CAPTURE_RESULT::VX_SUCCESS && dataSize > 0) { + // Debug: Check buffer immediately after VxGetImage (every 60 frames) + static uint64_t captureCount = 0; + captureCount++; + if (captureCount % 60 == 1) { + std::cout << "\n[RAW BUFFER Frame #" << captureCount << "] Right after VxGetImage:" << std::endl; + std::cout << "[RAW] First 16 bytes: "; + for (int i = 0; i < 16; i++) { + printf("%02x ", buffer_.get()[i]); + } + std::cout << std::endl; + } + // Get timestamp for frame const uint64_t timestamp_ns = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); @@ -147,6 +176,15 @@ void StreamingEngine::acquisitionLoop() { // Push frame to GStreamer pipeline if active if (gstPipeline_->isRunning()) { + // Debug: Check buffer before GStreamer push + if (captureCount % 60 == 1) { + std::cout << "[BEFORE GST] First 16 bytes: "; + for (int i = 0; i < 16; i++) { + printf("%02x ", buffer_.get()[i]); + } + std::cout << std::endl; + } + if (!gstPipeline_->pushBuffer(buffer_.get(), dataSize, currentFormat_.width, currentFormat_.height, formatStr)) { @@ -154,12 +192,68 @@ void StreamingEngine::acquisitionLoop() { } } - // Push frame to shared memory if enabled + // Push frame to shared memory if enabled (always as RGB) if (shmWriter_ && shmWriter_->isCreated()) { - if (!shmWriter_->writeFrame(buffer_.get(), dataSize, + static uint64_t shmFrameCount = 0; + shmFrameCount++; + + const uint8_t* frameData = buffer_.get(); + size_t frameSize = dataSize; + std::string outputFormat = "RGB"; + + // Debug: Print every 60 frames to see current data + if (shmFrameCount % 60 == 1) { + std::cout << "\n[DEBUG SHM Frame #" << shmFrameCount << "] Input format: " << formatStr + << " | Size: " << dataSize << std::endl; + std::cout << "[DEBUG] First 16 input bytes (hex): "; + for (int i = 0; i < 16 && i < dataSize; i++) { + printf("%02x ", buffer_.get()[i]); + } + std::cout << std::endl; + + // For UYVY, show interpretation + if (currentFormat_.format == VX_IMAGE_FORMAT::UYVY) { + std::cout << "[DEBUG] UYVY: U=" << (int)buffer_.get()[0] + << " Y0=" << (int)buffer_.get()[1] + << " V=" << (int)buffer_.get()[2] + << " Y1=" << (int)buffer_.get()[3] << std::endl; + } + } + + // Convert to RGB if needed + if (currentFormat_.format != VX_IMAGE_FORMAT::RGB) { + frameSize = convertToRGB(buffer_.get(), rgbBuffer_.get(), currentFormat_.width, currentFormat_.height, - formatStr, timestamp_ns)) { + currentFormat_.format); + frameData = rgbBuffer_.get(); + + // Debug: Print every 60 frames after conversion + if (shmFrameCount % 60 == 1) { + std::cout << "[DEBUG] RGB output - First 12 bytes: "; + for (int i = 0; i < 12 && i < frameSize; i++) { + printf("%02x ", frameData[i]); + } + std::cout << "\n[DEBUG] First 2 RGB pixels: R=" << (int)frameData[0] + << " G=" << (int)frameData[1] << " B=" << (int)frameData[2] + << " | R=" << (int)frameData[3] << " G=" << (int)frameData[4] + << " B=" << (int)frameData[5] << std::endl; + } + } + + if (!shmWriter_->writeFrame(frameData, frameSize, + currentFormat_.width, currentFormat_.height, + outputFormat, timestamp_ns)) { std::cerr << "Failed to write frame to shared memory" << std::endl; + } else { + // Debug: Print info every 30 frames + if (shmFrameCount % 30 == 0) { + std::cout << "[DEBUG] SHM: Written " << shmFrameCount << " frames. " + << "Last frame first 8 bytes: "; + for (int i = 0; i < 8 && i < frameSize; i++) { + printf("%02x ", frameData[i]); + } + std::cout << std::endl; + } } } @@ -223,3 +317,110 @@ void StreamingEngine::disableSharedMemory() { std::cout << "Shared memory disabled" << std::endl; } } + +size_t StreamingEngine::convertToRGB(const uint8_t* src, uint8_t* dst, const int width, const int height, const VX_IMAGE_FORMAT format) { + const size_t rgbSize = width * height * 3; + + // Helper lambda for safe clamping + auto clamp = [](int value) -> uint8_t { + if (value < 0) return 0; + if (value > 255) return 255; + return static_cast(value); + }; + + switch (format) { + case VX_IMAGE_FORMAT::BGR: { + // BGR to RGB: swap R and B channels + for (int i = 0; i < width * height; i++) { + dst[i * 3 + 0] = src[i * 3 + 2]; // R = B + dst[i * 3 + 1] = src[i * 3 + 1]; // G = G + dst[i * 3 + 2] = src[i * 3 + 0]; // B = R + } + return rgbSize; + } + + case VX_IMAGE_FORMAT::YUY2: { + // YUY2 (YUYV) to RGB conversion + // Format: Y0 U Y1 V (4 bytes = 2 pixels) + for (int i = 0; i < width * height / 2; i++) { + const int y0 = src[i * 4 + 0]; + const int u = src[i * 4 + 1]; + const int y1 = src[i * 4 + 2]; + const int v = src[i * 4 + 3]; + + const int c0 = y0 - 16; + const int c1 = y1 - 16; + const int d = u - 128; + const int e = v - 128; + + // First pixel (RGB) + const int r0 = (298 * c0 + 409 * e + 128) >> 8; + const int g0 = (298 * c0 - 100 * d - 208 * e + 128) >> 8; + const int b0 = (298 * c0 + 516 * d + 128) >> 8; + + dst[i * 6 + 0] = clamp(r0); + dst[i * 6 + 1] = clamp(g0); + dst[i * 6 + 2] = clamp(b0); + + // Second pixel (RGB) + const int r1 = (298 * c1 + 409 * e + 128) >> 8; + const int g1 = (298 * c1 - 100 * d - 208 * e + 128) >> 8; + const int b1 = (298 * c1 + 516 * d + 128) >> 8; + + dst[i * 6 + 3] = clamp(r1); + dst[i * 6 + 4] = clamp(g1); + dst[i * 6 + 5] = clamp(b1); + } + return rgbSize; + } + + case VX_IMAGE_FORMAT::UYVY: { + // UYVY to RGB conversion + // Format: U Y0 V Y1 (4 bytes = 2 pixels) + for (int i = 0; i < width * height / 2; i++) { + const int u = src[i * 4 + 0]; + const int y0 = src[i * 4 + 1]; + const int v = src[i * 4 + 2]; + const int y1 = src[i * 4 + 3]; + + const int c0 = y0 - 16; + const int c1 = y1 - 16; + const int d = u - 128; + const int e = v - 128; + + // First pixel (RGB) + const int r0 = (298 * c0 + 409 * e + 128) >> 8; + const int g0 = (298 * c0 - 100 * d - 208 * e + 128) >> 8; + const int b0 = (298 * c0 + 516 * d + 128) >> 8; + + dst[i * 6 + 0] = clamp(r0); + dst[i * 6 + 1] = clamp(g0); + dst[i * 6 + 2] = clamp(b0); + + // Second pixel (RGB) + const int r1 = (298 * c1 + 409 * e + 128) >> 8; + const int g1 = (298 * c1 - 100 * d - 208 * e + 128) >> 8; + const int b1 = (298 * c1 + 516 * d + 128) >> 8; + + dst[i * 6 + 3] = clamp(r1); + dst[i * 6 + 4] = clamp(g1); + dst[i * 6 + 5] = clamp(b1); + } + return rgbSize; + } + + case VX_IMAGE_FORMAT::RGB: { + // Already RGB, just copy + std::memcpy(dst, src, rgbSize); + return rgbSize; + } + + default: { + std::cerr << "Unsupported format for RGB conversion, copying as-is" << std::endl; + // Copy as-is for unsupported formats + const size_t copySize = std::min(rgbSize, static_cast(width * height * 4)); + std::memcpy(dst, src, copySize); + return copySize; + } + } +} diff --git a/src/main.cpp b/src/main.cpp index 430cbb3..8ae9bef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -98,7 +98,7 @@ int main() { std::cout << "License: CC BY-NC-SA 4.0 -> https://creativecommons.org/licenses/by-nc-sa/4.0/" << std::endl; std::cout << "========================================" << std::endl << std::endl; std::cout << "Control socket: " << socketPath << std::endl; - std::cout << "Default pipeline: videoconvert ! autovideosink" << std::endl; + std::cout << "Default pipeline: fakesink (no display)" << std::endl; std::cout << "\nQuick start:" << std::endl; std::cout << R"( echo '{"command":"start_stream"}' | socat - UNIX-CONNECT:)" << socketPath << std::endl; std::cout << "\nTo change pipeline before starting:" << std::endl; diff --git a/watchdog.sh b/watchdog.sh new file mode 100755 index 0000000..0a79e95 --- /dev/null +++ b/watchdog.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# VizionStreamer Watchdog Script +# Automatically restarts the streamer if it crashes +# Usage: ./watchdog.sh + +STREAMER_PATH="./build/vizionStreamer" +CHECK_INTERVAL=5 # Seconds between checks +LOG_FILE="./watchdog.log" +PID_FILE="./vizionStreamer.pid" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to log messages +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +# Function to start the streamer +start_streamer() { + log "${GREEN}Starting VizionStreamer...${NC}" + $STREAMER_PATH & + STREAMER_PID=$! + echo $STREAMER_PID > "$PID_FILE" + log "VizionStreamer started with PID: $STREAMER_PID" +} + +# Function to check if process is running +is_running() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p $PID > /dev/null 2>&1; then + return 0 # Running + fi + fi + return 1 # Not running +} + +# Function to stop the streamer +stop_streamer() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p $PID > /dev/null 2>&1; then + log "${YELLOW}Stopping VizionStreamer (PID: $PID)...${NC}" + kill $PID + sleep 2 + # Force kill if still running + if ps -p $PID > /dev/null 2>&1; then + log "${RED}Force killing VizionStreamer...${NC}" + kill -9 $PID + fi + fi + rm -f "$PID_FILE" + fi +} + +# Trap SIGINT and SIGTERM to cleanly shutdown +cleanup() { + log "${YELLOW}Watchdog shutting down...${NC}" + stop_streamer + log "${GREEN}Watchdog stopped${NC}" + exit 0 +} + +trap cleanup SIGINT SIGTERM + +# Check if vizionStreamer executable exists +if [ ! -f "$STREAMER_PATH" ]; then + log "${RED}ERROR: VizionStreamer not found at $STREAMER_PATH${NC}" + log "Please build the project first: cmake --build build" + exit 1 +fi + +# Main watchdog loop +log "${GREEN}=== VizionStreamer Watchdog Started ===${NC}" +log "Check interval: ${CHECK_INTERVAL} seconds" +log "Press Ctrl+C to stop" + +RESTART_COUNT=0 + +# Start streamer initially +start_streamer +sleep 2 # Give it time to start + +while true; do + sleep $CHECK_INTERVAL + + if ! is_running; then + RESTART_COUNT=$((RESTART_COUNT + 1)) + log "${RED}VizionStreamer died! Restart count: $RESTART_COUNT${NC}" + + # Optional: limit restart attempts + if [ $RESTART_COUNT -gt 10 ]; then + log "${RED}ERROR: Too many restarts (10). Giving up.${NC}" + log "Check the logs for recurring errors." + exit 1 + fi + + # Clean up old PID file + rm -f "$PID_FILE" + + # Wait a bit before restarting to avoid rapid restart loops + sleep 2 + + # Restart + start_streamer + sleep 2 + else + # Still running - reset restart counter on successful check + if [ $RESTART_COUNT -gt 0 ]; then + RESTART_COUNT=0 + log "${GREEN}VizionStreamer stable again${NC}" + fi + fi +done