diff --git a/include/utils/memory.h b/include/utils/memory.h index c6ec1a78..33b3e81e 100644 --- a/include/utils/memory.h +++ b/include/utils/memory.h @@ -60,4 +60,26 @@ int safe_strcat(char *dest, const char *src, size_t size); */ void secure_zero_memory(void *ptr, size_t size); -#endif /* MEMORY_UTILS_H */ \ No newline at end of file +/** + * Track memory allocations for debugging and leak detection + * + * @param size Size of memory being allocated or freed + * @param is_allocation True if allocating, false if freeing + */ +void track_memory_allocation(size_t size, bool is_allocation); + +/** + * Get the total amount of memory currently allocated + * + * @return Total memory allocated in bytes + */ +size_t get_total_memory_allocated(void); + +/** + * Get the peak memory usage since program start + * + * @return Peak memory allocated in bytes + */ +size_t get_peak_memory_allocated(void); + +#endif /* MEMORY_UTILS_H */ diff --git a/include/video/detection_buffer.h b/include/video/detection_buffer.h new file mode 100644 index 00000000..2ec2fb46 --- /dev/null +++ b/include/video/detection_buffer.h @@ -0,0 +1,76 @@ +#ifndef DETECTION_BUFFER_H +#define DETECTION_BUFFER_H + +#include +#include +#include +#include + +// Buffer pool item structure +typedef struct { + uint8_t *buffer; + size_t size; + bool in_use; + time_t last_used; // Track when the buffer was last used +} buffer_pool_item_t; + +/** + * Initialize the buffer pool + * This should be called at startup + * + * @return 0 on success, non-zero on failure + */ +int init_buffer_pool(void); + +/** + * Cleanup the buffer pool + * This should be called at shutdown + */ +void cleanup_buffer_pool(void); + +/** + * Get a buffer from the pool + * + * @param required_size The required buffer size + * @return Pointer to the buffer or NULL on failure + */ +uint8_t* get_buffer_from_pool(size_t required_size); + +/** + * Return a buffer to the pool + * + * @param buffer Pointer to the buffer + * @return 0 on success, non-zero on failure + */ +int return_buffer_to_pool(uint8_t *buffer); + +/** + * Get the number of active buffers + * + * @return Number of active buffers + */ +int get_active_buffer_count(void); + +/** + * Get the maximum number of buffers in the pool + * + * @return Maximum number of buffers + */ +int get_max_buffer_count(void); + +/** + * Emergency cleanup of buffer pool + * This frees all buffers that have been in use for too long + * Call this when buffer allocation fails to recover from leaks + */ +void emergency_buffer_pool_cleanup(void); + +/** + * Track memory allocations + * + * @param size Size of the allocation + * @param is_allocation True if allocating, false if freeing + */ +void track_memory_allocation(size_t size, bool is_allocation); + +#endif /* DETECTION_BUFFER_H */ diff --git a/include/video/detection_config.h b/include/video/detection_config.h new file mode 100644 index 00000000..5aedccd6 --- /dev/null +++ b/include/video/detection_config.h @@ -0,0 +1,68 @@ +#ifndef DETECTION_CONFIG_H +#define DETECTION_CONFIG_H + +#include +#include + +// Model types +#define MODEL_TYPE_SOD "sod" +#define MODEL_TYPE_SOD_REALNET "sod_realnet" +#define MODEL_TYPE_TFLITE "tflite" + +// System configuration +typedef struct { + // Memory constraints + int buffer_pool_size; // Maximum number of buffers in the pool + int concurrent_detections; // Maximum number of concurrent detections + int buffer_allocation_retries; // Number of retries for buffer allocation + + // Downscaling factors + int downscale_factor_default; // No downscaling by default + int downscale_factor_cnn; // Downscaling for CNN models + int downscale_factor_realnet; // Downscaling for RealNet models + + // Thresholds + float threshold_cnn; // Detection threshold for CNN models + float threshold_cnn_embedded; // Detection threshold for CNN models on embedded devices + float threshold_realnet; // Detection threshold for RealNet models + + // Debug options + bool save_frames_for_debug; // Enable/disable frame saving +} detection_config_t; + +// Default configurations +extern detection_config_t default_config; +extern detection_config_t embedded_config; + +/** + * Initialize detection configuration + * This should be called at startup + * + * @return 0 on success, non-zero on failure + */ +int init_detection_config(void); + +/** + * Get the current detection configuration + * + * @return Pointer to the current configuration + */ +detection_config_t* get_detection_config(void); + +/** + * Set custom detection configuration + * + * @param config The configuration to set + * @return 0 on success, non-zero on failure + */ +int set_detection_config(const detection_config_t* config); + +/** + * Get current memory usage statistics for detection + * + * @param total_memory Pointer to store total allocated memory + * @param peak_memory Pointer to store peak allocated memory + */ +void get_detection_memory_usage(size_t *total_memory, size_t *peak_memory); + +#endif /* DETECTION_CONFIG_H */ diff --git a/include/video/detection_embedded.h b/include/video/detection_embedded.h new file mode 100644 index 00000000..7797630b --- /dev/null +++ b/include/video/detection_embedded.h @@ -0,0 +1,59 @@ +#ifndef DETECTION_EMBEDDED_H +#define DETECTION_EMBEDDED_H + +#include +#include +#include + +/** + * Check if we're running on an embedded device + * + * @return true if running on an embedded device, false otherwise + */ +bool is_embedded_device(void); + +/** + * Get the appropriate downscale factor based on model type and device + * + * @param model_type The model type (MODEL_TYPE_SOD, MODEL_TYPE_SOD_REALNET, etc.) + * @return The downscale factor + */ +int get_downscale_factor(const char *model_type); + +/** + * Get the appropriate detection threshold based on model type and device + * + * @param model_type The model type (MODEL_TYPE_SOD, MODEL_TYPE_SOD_REALNET, etc.) + * @param configured_threshold The threshold configured by the user (0.0 for default) + * @return The detection threshold + */ +float get_detection_threshold(const char *model_type, float configured_threshold); + +/** + * Downscale a frame for detection + * + * @param src_data Source frame data + * @param src_width Source frame width + * @param src_height Source frame height + * @param src_channels Source frame channels + * @param dst_data Destination frame data (must be pre-allocated) + * @param dst_width Destination frame width + * @param dst_height Destination frame height + * @param dst_channels Destination frame channels + * @return 0 on success, non-zero on failure + */ +int downscale_frame(const uint8_t *src_data, int src_width, int src_height, int src_channels, + uint8_t *dst_data, int dst_width, int dst_height, int dst_channels); + +/** + * Calculate the memory requirements for detection + * + * @param width Frame width + * @param height Frame height + * @param channels Frame channels + * @param model_type The model type (MODEL_TYPE_SOD, MODEL_TYPE_SOD_REALNET, etc.) + * @return The memory requirements in bytes + */ +size_t calculate_detection_memory_requirements(int width, int height, int channels, const char *model_type); + +#endif /* DETECTION_EMBEDDED_H */ diff --git a/include/video/detection_integration.h b/include/video/detection_integration.h index 5ba9b7cb..5c583e0c 100644 --- a/include/video/detection_integration.h +++ b/include/video/detection_integration.h @@ -6,12 +6,13 @@ #include #include -// Maximum number of concurrent detections -#define MAX_CONCURRENT_DETECTIONS 3 - -// Expose detection state variables for memory-constrained optimization -extern pthread_mutex_t active_detections_mutex; -extern int active_detections; +/** + * Initialize the detection integration system + * This should be called at startup + * + * @return 0 on success, non-zero on failure + */ +int init_detection_integration(void); /** * Process a decoded frame for detection @@ -30,4 +31,26 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, */ void cleanup_detection_resources(void); +/** + * Get the number of active detections + * + * @return Number of active detections + */ +int get_active_detection_count(void); + +/** + * Get the maximum number of concurrent detections + * + * @return Maximum number of concurrent detections + */ +int get_max_detection_count(void); + +/** + * Check if a detection is already in progress for a specific stream + * + * @param stream_name The name of the stream to check + * @return true if detection is in progress, false otherwise + */ +bool is_detection_in_progress(const char *stream_name); + #endif /* DETECTION_INTEGRATION_H */ diff --git a/src/utils/memory.c b/src/utils/memory.c index 2b7b69f6..bf510a4e 100644 --- a/src/utils/memory.c +++ b/src/utils/memory.c @@ -1,10 +1,16 @@ #include #include #include +#include +#include #include "utils/memory.h" #include "core/logger.h" +// Memory tracking variables +static size_t total_memory_allocated = 0; +static size_t peak_memory_allocated = 0; + // Safe memory allocation void *safe_malloc(size_t size) { if (size == 0) { @@ -91,4 +97,33 @@ void secure_zero_memory(void *ptr, size_t size) { while (size--) { *p++ = 0; } -} \ No newline at end of file +} + +// Track memory allocations +void track_memory_allocation(size_t size, bool is_allocation) { + if (is_allocation) { + total_memory_allocated += size; + if (total_memory_allocated > peak_memory_allocated) { + peak_memory_allocated = total_memory_allocated; + } + } else { + if (size <= total_memory_allocated) { + total_memory_allocated -= size; + } else { + // This should not happen, but handle it gracefully + log_warn("Memory tracking inconsistency: trying to free %zu bytes when only %zu are tracked", + size, total_memory_allocated); + total_memory_allocated = 0; + } + } +} + +// Get total memory allocated +size_t get_total_memory_allocated(void) { + return total_memory_allocated; +} + +// Get peak memory allocated +size_t get_peak_memory_allocated(void) { + return peak_memory_allocated; +} diff --git a/src/video/detection_buffer.c b/src/video/detection_buffer.c new file mode 100644 index 00000000..006424eb --- /dev/null +++ b/src/video/detection_buffer.c @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include + +#include "../../include/core/logger.h" +#include "../../include/utils/memory.h" +#include "../../include/video/detection_config.h" +#include "../../include/video/detection_buffer.h" + +// Buffer pool +static buffer_pool_item_t *buffer_pool = NULL; +static int active_buffers = 0; +static int max_buffers = 0; + +// External declaration for memory tracking +extern void track_memory_allocation(size_t size, bool is_allocation); + +/** + * Initialize the buffer pool + */ +int init_buffer_pool(void) { + // Check if already initialized + if (buffer_pool) { + return 0; + } + + // Get configuration + detection_config_t *config = get_detection_config(); + if (!config) { + log_error("Failed to get detection configuration"); + return -1; + } + + // Allocate buffer pool + max_buffers = config->buffer_pool_size; + buffer_pool = (buffer_pool_item_t *)calloc(max_buffers, sizeof(buffer_pool_item_t)); + if (!buffer_pool) { + log_error("Failed to allocate buffer pool"); + return -1; + } + + log_info("Buffer pool initialized with %d buffers", max_buffers); + return 0; +} + +/** + * Cleanup the buffer pool + */ +void cleanup_buffer_pool(void) { + if (!buffer_pool) { + return; + } + + // Free all buffers + for (int i = 0; i < max_buffers; i++) { + if (buffer_pool[i].buffer) { + track_memory_allocation(buffer_pool[i].size, false); + free(buffer_pool[i].buffer); + buffer_pool[i].buffer = NULL; + buffer_pool[i].size = 0; + buffer_pool[i].in_use = false; + } + } + + // Free buffer pool + free(buffer_pool); + buffer_pool = NULL; + active_buffers = 0; + max_buffers = 0; + + log_info("Buffer pool cleaned up"); +} + +/** + * Get a buffer from the pool + */ +uint8_t* get_buffer_from_pool(size_t required_size) { + // Initialize if not already done + if (!buffer_pool) { + if (init_buffer_pool() != 0) { + log_error("Failed to initialize buffer pool"); + return NULL; + } + } + + // Get configuration + detection_config_t *config = get_detection_config(); + if (!config) { + log_error("Failed to get detection configuration"); + return NULL; + } + + // Try multiple times to get a buffer + for (int retry = 0; retry < config->buffer_allocation_retries; retry++) { + // First try to find an existing buffer in the pool + for (int i = 0; i < max_buffers; i++) { + if (!buffer_pool[i].in_use && buffer_pool[i].buffer && buffer_pool[i].size >= required_size) { + buffer_pool[i].in_use = true; + buffer_pool[i].last_used = time(NULL); + active_buffers++; + log_info("Reusing buffer from pool (index %d, size %zu, retry %d)", + i, buffer_pool[i].size, retry); + return buffer_pool[i].buffer; + } + } + + // If no suitable buffer found, try to allocate a new one + // Find an empty slot in the pool + int empty_slot = -1; + for (int i = 0; i < max_buffers; i++) { + if (!buffer_pool[i].buffer) { + empty_slot = i; + break; + } + } + + // If no empty slot, try to find the oldest unused buffer + if (empty_slot == -1) { + time_t oldest_time = time(NULL); + for (int i = 0; i < max_buffers; i++) { + if (!buffer_pool[i].in_use && buffer_pool[i].last_used < oldest_time) { + oldest_time = buffer_pool[i].last_used; + empty_slot = i; + } + } + } + + // If still no slot, we can't allocate a new buffer + if (empty_slot == -1) { + log_error("No available slots in buffer pool (retry %d)", retry); + // If this is the last retry, give up + if (retry == config->buffer_allocation_retries - 1) { + return NULL; + } + + // Wait a bit before retrying + usleep(100000); // 100ms + continue; + } + + // If there's an existing buffer but it's too small, free it + if (buffer_pool[empty_slot].buffer && buffer_pool[empty_slot].size < required_size) { + // Track memory before freeing + track_memory_allocation(buffer_pool[empty_slot].size, false); + free(buffer_pool[empty_slot].buffer); + buffer_pool[empty_slot].buffer = NULL; + } + + // Allocate a new buffer if needed + if (!buffer_pool[empty_slot].buffer) { + buffer_pool[empty_slot].buffer = (uint8_t *)safe_malloc(required_size); + if (!buffer_pool[empty_slot].buffer) { + log_error("Failed to allocate buffer for pool (size: %zu, retry %d)", + required_size, retry); + + // If this is the last retry, give up + if (retry == config->buffer_allocation_retries - 1) { + return NULL; + } + + // Wait a bit before retrying + usleep(100000); // 100ms + continue; + } + + // Track memory allocation + track_memory_allocation(required_size, true); + buffer_pool[empty_slot].size = required_size; + } + + buffer_pool[empty_slot].in_use = true; + buffer_pool[empty_slot].last_used = time(NULL); + active_buffers++; + log_info("Allocated buffer for pool (index %d, size %zu, retry %d)", + empty_slot, required_size, retry); + return buffer_pool[empty_slot].buffer; + } + + log_error("Failed to allocate buffer from pool"); + return NULL; +} + +/** + * Return a buffer to the pool + */ +int return_buffer_to_pool(uint8_t *buffer) { + if (!buffer) { + log_warn("Attempted to return NULL buffer to pool"); + return -1; + } + + if (!buffer_pool) { + log_warn("Buffer pool not initialized when returning buffer"); + // Free the buffer directly to prevent memory leak + free(buffer); + return -1; + } + + // Find the buffer in the pool + for (int i = 0; i < max_buffers; i++) { + if (buffer_pool[i].buffer == buffer) { + if (buffer_pool[i].in_use) { + buffer_pool[i].in_use = false; + buffer_pool[i].last_used = time(NULL); + if (active_buffers > 0) { + active_buffers--; + } + // Log memory tracking + track_memory_allocation(buffer_pool[i].size, false); + log_debug("Memory freed: %zu bytes, Total: %zu bytes, Peak: %zu bytes", + buffer_pool[i].size, get_total_memory_allocated(), get_peak_memory_allocated()); + log_info("Returned buffer to pool (index %d, active: %d/%d)", + i, active_buffers, max_buffers); + return 0; + } else { + log_warn("Buffer already marked as not in use (index %d)", i); + return 0; // Still consider this a success + } + } + } + + // CRITICAL FIX: If buffer not found in pool, free it directly to prevent memory leak + log_error("Buffer %p not found in pool, freeing directly", (void*)buffer); + free(buffer); + return -1; +} + +/** + * Emergency cleanup of buffer pool - free all buffers that have been in use for too long + * This helps recover from situations where buffers aren't properly returned + */ +void emergency_buffer_pool_cleanup(void) { + if (!buffer_pool) { + return; + } + + time_t current_time = time(NULL); + int freed_count = 0; + + log_warn("Performing emergency buffer pool cleanup"); + + for (int i = 0; i < max_buffers; i++) { + if (buffer_pool[i].in_use && buffer_pool[i].buffer) { + // If buffer has been in use for more than 30 seconds, assume it's leaked + if (current_time - buffer_pool[i].last_used > 30) { + log_warn("Freeing leaked buffer (index %d, in use for %ld seconds)", + i, current_time - buffer_pool[i].last_used); + + // Track memory before freeing + track_memory_allocation(buffer_pool[i].size, false); + free(buffer_pool[i].buffer); + buffer_pool[i].buffer = NULL; + buffer_pool[i].size = 0; + buffer_pool[i].in_use = false; + freed_count++; + + if (active_buffers > 0) { + active_buffers--; + } + } + } + } + + if (freed_count > 0) { + log_info("Emergency cleanup freed %d buffers, active buffers now: %d/%d", + freed_count, active_buffers, max_buffers); + } else { + log_info("Emergency cleanup found no leaked buffers"); + } +} + +/** + * Get the number of active buffers + */ +int get_active_buffer_count(void) { + return active_buffers; +} + +/** + * Get the maximum number of buffers in the pool + */ +int get_max_buffer_count(void) { + return max_buffers; +} diff --git a/src/video/detection_config.c b/src/video/detection_config.c new file mode 100644 index 00000000..d12f6c2d --- /dev/null +++ b/src/video/detection_config.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../include/core/logger.h" +#include "../../include/video/detection_config.h" + +// Default configuration for standard systems +detection_config_t default_config = { + // Memory constraints + .buffer_pool_size = 8, // 8 buffers in the pool + .concurrent_detections = 16, // 3 concurrent detections + .buffer_allocation_retries = 3, // 3 retries for buffer allocation + + // Downscaling factors + .downscale_factor_default = 1, // No downscaling by default + .downscale_factor_cnn = 1, // No downscaling for CNN models + .downscale_factor_realnet = 1, // No downscaling for RealNet models + + // Thresholds + .threshold_cnn = 0.3f, // Standard threshold for CNN models + .threshold_cnn_embedded = 0.3f, // Same threshold for embedded devices + .threshold_realnet = 5.0f, // Standard threshold for RealNet models + + // Debug options + .save_frames_for_debug = false // Disable frame saving +}; + +// Configuration for embedded systems (256MB RAM, 2 cores) +detection_config_t embedded_config = { + // Memory constraints + .buffer_pool_size = 8, // CRITICAL FIX: Increased from 4 to 8 buffers in the pool + .concurrent_detections = 1, // 1 concurrent detection + .buffer_allocation_retries = 3, // CRITICAL FIX: Increased from 3 to 5 retries for buffer allocation + + // Downscaling factors + .downscale_factor_default = 1, // No downscaling by default + .downscale_factor_cnn = 6, // downscaling for CNN models + .downscale_factor_realnet = 1, // No downscaling for RealNet models + + // Thresholds + .threshold_cnn = 0.3f, // Standard threshold for CNN models + .threshold_cnn_embedded = 0.4f, // Higher threshold for embedded devices + .threshold_realnet = 5.0f, // Standard threshold for RealNet models + + // Debug options + .save_frames_for_debug = false // Disable frame saving +}; + +// Current active configuration +static detection_config_t *current_config = NULL; + +// External memory tracking functions +extern void track_memory_allocation(size_t size, bool is_allocation); +extern size_t get_total_memory_allocated(void); +extern size_t get_peak_memory_allocated(void); + +/** + * Initialize detection configuration + */ +int init_detection_config(void) { + // Check if already initialized + if (current_config) { + return 0; + } + + // Default to standard configuration + current_config = &default_config; + + // Check if we're running on an embedded device + FILE *meminfo = fopen("/proc/meminfo", "r"); + if (meminfo) { + char line[256]; + unsigned long total_mem_kb = 0; + + while (fgets(line, sizeof(line), meminfo)) { + if (strncmp(line, "MemTotal:", 9) == 0) { + sscanf(line, "MemTotal: %lu", &total_mem_kb); + break; + } + } + fclose(meminfo); + + // If total memory is less than 512MB, use embedded configuration + if (total_mem_kb > 0 && total_mem_kb < 512 * 1024) { + log_info("Detected embedded device with %lu KB RAM, using embedded configuration", total_mem_kb); + current_config = &embedded_config; + } + } + + // Check number of CPU cores + int num_cores = sysconf(_SC_NPROCESSORS_ONLN); + if (num_cores > 0 && num_cores <= 2) { + log_info("Detected embedded device with %d CPU cores, using embedded configuration", num_cores); + current_config = &embedded_config; + } + + log_info("Detection configuration initialized"); + return 0; +} + +/** + * Get the current detection configuration + */ +detection_config_t* get_detection_config(void) { + // Initialize if not already done + if (!current_config) { + init_detection_config(); + } + + return current_config; +} + +/** + * Set custom detection configuration + */ +int set_detection_config(const detection_config_t* config) { + if (!config) { + log_error("Invalid configuration"); + return -1; + } + + // Validate configuration + if (config->buffer_pool_size <= 0 || + config->concurrent_detections <= 0 || + config->buffer_allocation_retries <= 0) { + log_error("Invalid configuration parameters"); + return -1; + } + + // Copy configuration + if (current_config == &default_config) { + memcpy(&default_config, config, sizeof(detection_config_t)); + } else if (current_config == &embedded_config) { + memcpy(&embedded_config, config, sizeof(detection_config_t)); + } else { + // Allocate new configuration if needed + if (!current_config) { + current_config = malloc(sizeof(detection_config_t)); + if (!current_config) { + log_error("Failed to allocate memory for configuration"); + return -1; + } + } + + memcpy(current_config, config, sizeof(detection_config_t)); + } + + log_info("Detection configuration updated"); + return 0; +} + +/** + * Get current memory usage statistics for detection + */ +void get_detection_memory_usage(size_t *total_memory, size_t *peak_memory) { + if (total_memory) { + *total_memory = get_total_memory_allocated(); + } + if (peak_memory) { + *peak_memory = get_peak_memory_allocated(); + } +} diff --git a/src/video/detection_embedded.c b/src/video/detection_embedded.c new file mode 100644 index 00000000..253f25d4 --- /dev/null +++ b/src/video/detection_embedded.c @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../include/core/logger.h" +#include "../../include/video/detection_config.h" +#include "../../include/video/detection_embedded.h" + +/** + * Check if we're running on an embedded device + */ +bool is_embedded_device(void) { + static bool checked = false; + static bool is_embedded = false; + + // Only check once + if (checked) { + return is_embedded; + } + + // Check available system memory + FILE *meminfo = fopen("/proc/meminfo", "r"); + if (meminfo) { + char line[256]; + unsigned long total_mem_kb = 0; + + while (fgets(line, sizeof(line), meminfo)) { + if (strncmp(line, "MemTotal:", 9) == 0) { + sscanf(line, "MemTotal: %lu", &total_mem_kb); + break; + } + } + fclose(meminfo); + + // If total memory is less than 512MB, consider it an embedded device + if (total_mem_kb > 0 && total_mem_kb < 512 * 1024) { + log_info("Detected embedded device with %lu KB RAM", total_mem_kb); + is_embedded = true; + checked = true; + return true; + } + } + + // Check number of CPU cores + int num_cores = sysconf(_SC_NPROCESSORS_ONLN); + if (num_cores > 0 && num_cores <= 2) { + log_info("Detected embedded device with %d CPU cores", num_cores); + is_embedded = true; + checked = true; + return true; + } + + // Not an embedded device + checked = true; + return false; +} + +/** + * Get the appropriate downscale factor based on model type and device + */ +int get_downscale_factor(const char *model_type) { + // Get configuration + detection_config_t *config = get_detection_config(); + if (!config) { + log_error("Failed to get detection configuration"); + return 1; // Default to no downscaling + } + + // Check if we're running on an embedded device + bool embedded = is_embedded_device(); + + // For non-embedded devices, use default factors + if (!embedded) { + return config->downscale_factor_default; + } + + // CRITICAL FIX: For embedded devices, use more aggressive downscaling for CNN models + if (strcmp(model_type, MODEL_TYPE_SOD) == 0) { + // For CNN models on embedded devices, use a higher downscale factor + // This significantly reduces memory usage and processing time + int downscale = config->downscale_factor_cnn; + + // Check available memory to determine if we need more aggressive downscaling + FILE *meminfo = fopen("/proc/meminfo", "r"); + if (meminfo) { + char line[256]; + unsigned long available_mem_kb = 0; + + while (fgets(line, sizeof(line), meminfo)) { + if (strncmp(line, "MemAvailable:", 13) == 0) { + sscanf(line, "MemAvailable: %lu", &available_mem_kb); + break; + } + } + fclose(meminfo); + + // If available memory is very low (less than 50MB), use more aggressive downscaling + if (available_mem_kb > 0 && available_mem_kb < 50 * 1024) { + log_warn("Very low memory available (%lu KB), using more aggressive downscaling", + available_mem_kb); + downscale = 4; // More aggressive downscaling + } + } + + log_info("Using downscale factor %d for CNN model on embedded device", downscale); + return downscale; + } else if (strcmp(model_type, MODEL_TYPE_SOD_REALNET) == 0) { + // RealNet models are already optimized for embedded devices + return config->downscale_factor_realnet; + } + + // Default for unknown model types + return config->downscale_factor_default; +} + +/** + * Get the appropriate detection threshold based on model type and device + */ +float get_detection_threshold(const char *model_type, float configured_threshold) { + // Get configuration + detection_config_t *config = get_detection_config(); + if (!config) { + log_error("Failed to get detection configuration"); + return 0.3f; // Default threshold + } + + // If threshold is configured, use it + if (configured_threshold > 0.0f) { + return configured_threshold; + } + + // Check if we're running on an embedded device + bool embedded = is_embedded_device(); + + // Use model-specific thresholds + if (strcmp(model_type, MODEL_TYPE_SOD_REALNET) == 0) { + return config->threshold_realnet; + } else if (strcmp(model_type, MODEL_TYPE_SOD) == 0) { + // For embedded devices, use a higher threshold + if (embedded) { + return config->threshold_cnn_embedded; + } else { + return config->threshold_cnn; + } + } + + // Default for unknown model types + return config->threshold_cnn; +} + +/** + * Downscale a frame for detection + * Simple bilinear interpolation implementation + */ +int downscale_frame(const uint8_t *src_data, int src_width, int src_height, int src_channels, + uint8_t *dst_data, int dst_width, int dst_height, int dst_channels) { + // Validate parameters + if (!src_data || !dst_data || src_width <= 0 || src_height <= 0 || dst_width <= 0 || dst_height <= 0 || + src_channels <= 0 || dst_channels <= 0) { + log_error("Invalid parameters for downscale_frame"); + return -1; + } + + // Check if source and destination channels match + if (src_channels != dst_channels) { + log_error("Source and destination channels must match"); + return -1; + } + + // Calculate scaling factors + float scale_x = (float)src_width / dst_width; + float scale_y = (float)src_height / dst_height; + + // Perform downscaling + for (int y = 0; y < dst_height; y++) { + for (int x = 0; x < dst_width; x++) { + // Calculate source coordinates + float src_x = x * scale_x; + float src_y = y * scale_y; + + // Calculate integer source coordinates + int src_x_int = (int)src_x; + int src_y_int = (int)src_y; + + // Calculate fractional parts + float src_x_frac = src_x - src_x_int; + float src_y_frac = src_y - src_y_int; + + // Ensure we don't go out of bounds + int src_x_int_p1 = (src_x_int + 1 < src_width) ? src_x_int + 1 : src_x_int; + int src_y_int_p1 = (src_y_int + 1 < src_height) ? src_y_int + 1 : src_y_int; + + // Process each channel + for (int c = 0; c < dst_channels; c++) { + // Get the four surrounding pixels + uint8_t p00 = src_data[(src_y_int * src_width + src_x_int) * src_channels + c]; + uint8_t p01 = src_data[(src_y_int * src_width + src_x_int_p1) * src_channels + c]; + uint8_t p10 = src_data[(src_y_int_p1 * src_width + src_x_int) * src_channels + c]; + uint8_t p11 = src_data[(src_y_int_p1 * src_width + src_x_int_p1) * src_channels + c]; + + // Bilinear interpolation + float top = p00 * (1 - src_x_frac) + p01 * src_x_frac; + float bottom = p10 * (1 - src_x_frac) + p11 * src_x_frac; + float value = top * (1 - src_y_frac) + bottom * src_y_frac; + + // Set the destination pixel + dst_data[(y * dst_width + x) * dst_channels + c] = (uint8_t)value; + } + } + } + + return 0; +} + +/** + * Calculate the memory requirements for detection + */ +size_t calculate_detection_memory_requirements(int width, int height, int channels, const char *model_type) { + // Get downscale factor + int downscale_factor = get_downscale_factor(model_type); + + // Calculate downscaled dimensions + int downscaled_width = width / downscale_factor; + int downscaled_height = height / downscale_factor; + + // Ensure dimensions are even + downscaled_width = (downscaled_width / 2) * 2; + downscaled_height = (downscaled_height / 2) * 2; + + // Calculate memory requirements + size_t frame_size = downscaled_width * downscaled_height * channels; + + // Add memory for model and other data structures + size_t model_size = 0; + if (strcmp(model_type, MODEL_TYPE_SOD) == 0) { + // CNN models are larger + model_size = 20 * 1024 * 1024; // 20MB for CNN model + } else if (strcmp(model_type, MODEL_TYPE_SOD_REALNET) == 0) { + // RealNet models are smaller + model_size = 5 * 1024 * 1024; // 5MB for RealNet model + } + + // Add memory for buffer pool + detection_config_t *config = get_detection_config(); + size_t buffer_pool_size = config ? config->buffer_pool_size * frame_size : 4 * frame_size; + + // Total memory requirements + return frame_size + model_size + buffer_pool_size; +} diff --git a/src/video/detection_integration.c b/src/video/detection_integration.c index f4f093fd..4cbfe91e 100644 --- a/src/video/detection_integration.c +++ b/src/video/detection_integration.c @@ -33,44 +33,54 @@ #include "video/sod_integration.h" #include "utils/memory.h" -// Define model types -#define MODEL_TYPE_SOD "sod" -#define MODEL_TYPE_SOD_REALNET "sod_realnet" -#define MODEL_TYPE_TFLITE "tflite" - -// Debug flag to enable/disable frame saving -static int save_frames_for_debug = 1; // Set to 1 to enable frame saving - -// Memory pool for packed buffers to avoid frequent allocations -#define MAX_BUFFER_POOL_SIZE 8 // Maximum number of buffers in the pool (increased from 4) -#define MAX_CONCURRENT_DETECTIONS 3 // Maximum number of concurrent detections (increased from 3) -#define BUFFER_ALLOCATION_RETRIES 3 // Number of retries for buffer allocation - -typedef struct { - uint8_t *buffer; - size_t size; - bool in_use; - time_t last_used; // Track when the buffer was last used -} buffer_pool_item_t; - -static buffer_pool_item_t buffer_pool[MAX_BUFFER_POOL_SIZE] = {0}; -int active_detections = 0; +// Include our new modules +#include "video/detection_config.h" +#include "video/detection_buffer.h" +#include "video/detection_embedded.h" +#include "video/detection_integration.h" // Track which streams are currently being processed for detection -static char active_detection_streams[MAX_CONCURRENT_DETECTIONS][MAX_STREAM_NAME] = {{0}}; +static char *active_detection_streams = NULL; +static int active_detections = 0; +static int max_detections = 0; -// Use the file_exists and detect_model_type functions from sod_integration.c +/** + * Initialize the detection integration system + */ +int init_detection_integration(void) { + // Initialize configuration + if (init_detection_config() != 0) { + log_error("Failed to initialize detection configuration"); + return -1; + } + + // Initialize buffer pool + if (init_buffer_pool() != 0) { + log_error("Failed to initialize buffer pool"); + return -1; + } + + // Get configuration + detection_config_t *config = get_detection_config(); + if (!config) { + log_error("Failed to get detection configuration"); + return -1; + } + + // Allocate active detection streams array + max_detections = config->concurrent_detections; + active_detection_streams = (char *)calloc(max_detections, MAX_STREAM_NAME); + if (!active_detection_streams) { + log_error("Failed to allocate active detection streams array"); + return -1; + } + + log_info("Detection integration initialized with %d max concurrent detections", max_detections); + return 0; +} /** * Process a decoded frame for detection - * This function should be called from the HLS streaming code with already decoded frames - * - * Revised to perform detection and pass results to process_frame_for_detection - * - * @param stream_name The name of the stream - * @param frame The decoded AVFrame - * @param detection_interval How often to process frames (e.g., every 10 frames) - * @return 0 on success, -1 on error */ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, int detection_interval) { // CRITICAL FIX: Add extra validation for all parameters @@ -91,6 +101,21 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, return -1; } + // Initialize if not already done + if (!active_detection_streams) { + if (init_detection_integration() != 0) { + log_error("Failed to initialize detection integration"); + return -1; + } + } + + // Get configuration + detection_config_t *config = get_detection_config(); + if (!config) { + log_error("Failed to get detection configuration"); + return -1; + } + static int frame_counters[MAX_STREAMS] = {0}; static char stream_names[MAX_STREAMS][MAX_STREAM_NAME] = {{0}}; @@ -100,6 +125,7 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, uint8_t *buffer = NULL; uint8_t *packed_buffer = NULL; int detect_ret = -1; + bool active_detection_added = false; // Find the stream's frame counter int stream_idx = -1; @@ -162,23 +188,23 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, return -1; } - stream_config_t config; - if (get_stream_config(stream_handle, &config) != 0) { + stream_config_t stream_config; + if (get_stream_config(stream_handle, &stream_config) != 0) { log_error("Failed to get stream config for %s", stream_name); return -1; } // Check if detection is enabled for this stream - if (!config.detection_based_recording || config.detection_model[0] == '\0') { + if (!stream_config.detection_based_recording || stream_config.detection_model[0] == '\0') { log_info("Detection not enabled for stream %s", stream_name); return 0; } - log_info("Detection enabled for stream %s with model %s", stream_name, config.detection_model); + log_info("Detection enabled for stream %s with model %s", stream_name, stream_config.detection_model); // Determine model type to use the correct image format - const char *model_type = detect_model_type(config.detection_model); - log_info("Detected model type: %s for model %s", model_type, config.detection_model); + const char *model_type = detect_model_type(stream_config.detection_model); + log_info("Detected model type: %s for model %s", model_type, stream_config.detection_model); // For RealNet models, we need grayscale images // For CNN models, we need RGB images @@ -196,10 +222,25 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, log_info("Using RGB format for non-RealNet model"); } - // Convert frame to the appropriate format for detection + // Determine if we should downscale the frame based on model type and device + int downscale_factor = get_downscale_factor(model_type); + log_info("Using downscale factor %d for model type %s", downscale_factor, model_type); + + // Calculate dimensions after downscaling + int target_width = frame->width / downscale_factor; + int target_height = frame->height / downscale_factor; + + // Ensure dimensions are even (required by some codecs) + target_width = (target_width / 2) * 2; + target_height = (target_height / 2) * 2; + + log_info("Original dimensions: %dx%d, Target dimensions: %dx%d (downscale factor: %d)", + frame->width, frame->height, target_width, target_height, downscale_factor); + + // Convert frame to the appropriate format for detection with downscaling if needed sws_ctx = sws_getContext( frame->width, frame->height, frame->format, - frame->width, frame->height, target_format, + target_width, target_height, target_format, SWS_BILINEAR, NULL, NULL, NULL); if (!sws_ctx) { @@ -215,7 +256,7 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, } // Allocate buffer for converted frame - ensure it's large enough - int buffer_size = av_image_get_buffer_size(target_format, frame->width, frame->height, 1); + int buffer_size = av_image_get_buffer_size(target_format, target_width, target_height, 1); buffer = (uint8_t *)av_malloc(buffer_size); if (!buffer) { log_error("Failed to allocate buffer for converted frame for stream %s", stream_name); @@ -224,134 +265,67 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, // Setup converted frame av_image_fill_arrays(converted_frame->data, converted_frame->linesize, buffer, - target_format, frame->width, frame->height, 1); + target_format, target_width, target_height, 1); - // Convert frame to target format + // Convert frame to target format with downscaling sws_scale(sws_ctx, (const uint8_t * const *)frame->data, frame->linesize, 0, frame->height, converted_frame->data, converted_frame->linesize); - log_info("Converted frame to %s format for stream %s", - (channels == 1) ? "grayscale" : "RGB", stream_name); + log_info("Converted frame to %s format for stream %s (dimensions: %dx%d)", + (channels == 1) ? "grayscale" : "RGB", stream_name, target_width, target_height); - // Check if this stream is already being processed + // CRITICAL FIX: Check if this stream is already being processed bool stream_already_active = false; - for (int i = 0; i < MAX_CONCURRENT_DETECTIONS; i++) { - if (strcmp(active_detection_streams[i], stream_name) == 0) { + for (int i = 0; i < max_detections; i++) { + char *active_stream = active_detection_streams + i * MAX_STREAM_NAME; + if (active_stream[0] != '\0' && strcmp(active_stream, stream_name) == 0) { stream_already_active = true; break; } } - // If this stream is already being processed, we can continue - // Otherwise, check if we have room for another stream - if (!stream_already_active) { - // If we're at the limit, log a warning but still try to process - // This allows all streams to get a chance at detection - if (active_detections >= MAX_CONCURRENT_DETECTIONS) { - log_warn("High detection load: %d concurrent detections (limit: %d), stream %s may experience delays", - active_detections, MAX_CONCURRENT_DETECTIONS, stream_name); - } + // If this stream is already being processed, skip this detection request + // This prevents multiple detections from running simultaneously for the same stream + if (stream_already_active) { + log_warn("Detection already in progress for stream %s, skipping this request", stream_name); + // Update the last detection time to prevent immediate retry + last_detection_times[stream_idx] = current_time; + return 0; // Skip this frame + } + + // Check if we have room for another detection + if (active_detections >= max_detections) { + log_warn("High detection load: %d concurrent detections (limit: %d), stream %s may experience delays", + active_detections, max_detections, stream_name); } - // Get a buffer from the pool or allocate a new one - size_t required_size = frame->width * frame->height * channels; - packed_buffer = NULL; + // Get a buffer from the pool + size_t required_size = target_width * target_height * channels; + packed_buffer = get_buffer_from_pool(required_size); - // Try multiple times to get a buffer (with retries) - for (int retry = 0; retry < BUFFER_ALLOCATION_RETRIES && !packed_buffer; retry++) { + if (!packed_buffer) { + log_error("Failed to allocate packed buffer for frame"); - // First try to find an existing buffer in the pool - for (int i = 0; i < MAX_BUFFER_POOL_SIZE; i++) { - if (!buffer_pool[i].in_use && buffer_pool[i].buffer && buffer_pool[i].size >= required_size) { - buffer_pool[i].in_use = true; - buffer_pool[i].last_used = time(NULL); - packed_buffer = buffer_pool[i].buffer; - log_info("Reusing buffer from pool (index %d, size %zu, retry %d)", - i, buffer_pool[i].size, retry); - break; - } - } + // CRITICAL FIX: Try emergency cleanup to recover from potential buffer leaks + log_warn("Attempting emergency buffer pool cleanup to recover from potential leaks"); + emergency_buffer_pool_cleanup(); - // If no suitable buffer found, try to allocate a new one + // Try one more time after cleanup + packed_buffer = get_buffer_from_pool(required_size); if (!packed_buffer) { - // Find an empty slot in the pool - int empty_slot = -1; - for (int i = 0; i < MAX_BUFFER_POOL_SIZE; i++) { - if (!buffer_pool[i].buffer) { - empty_slot = i; - break; - } - } - - // If no empty slot, try to find the oldest unused buffer - if (empty_slot == -1) { - time_t oldest_time = time(NULL); - for (int i = 0; i < MAX_BUFFER_POOL_SIZE; i++) { - if (!buffer_pool[i].in_use && buffer_pool[i].last_used < oldest_time) { - oldest_time = buffer_pool[i].last_used; - empty_slot = i; - } - } - } - - // If still no slot, we can't allocate a new buffer - if (empty_slot == -1) { - log_error("No available slots in buffer pool (retry %d)", retry); - // If this is the last retry, give up - if (retry == BUFFER_ALLOCATION_RETRIES - 1) { - goto cleanup; - } - - // Wait a bit before retrying - usleep(100000); // 100ms - continue; - } - - // If there's an existing buffer but it's too small, free it - if (buffer_pool[empty_slot].buffer && buffer_pool[empty_slot].size < required_size) { - free(buffer_pool[empty_slot].buffer); - buffer_pool[empty_slot].buffer = NULL; - } - - // Allocate a new buffer if needed - if (!buffer_pool[empty_slot].buffer) { - buffer_pool[empty_slot].buffer = (uint8_t *)safe_malloc(required_size); - if (!buffer_pool[empty_slot].buffer) { - log_error("Failed to allocate packed buffer for frame (size: %zu, retry %d)", - required_size, retry); - - // If this is the last retry, give up - if (retry == BUFFER_ALLOCATION_RETRIES - 1) { - goto cleanup; - } - - // Wait a bit before retrying - usleep(100000); // 100ms - continue; - } - - buffer_pool[empty_slot].size = required_size; - } - - buffer_pool[empty_slot].in_use = true; - buffer_pool[empty_slot].last_used = time(NULL); - packed_buffer = buffer_pool[empty_slot].buffer; - log_info("Allocated buffer for pool (index %d, size %zu, retry %d)", - empty_slot, required_size, retry); + log_error("Still failed to allocate packed buffer after emergency cleanup"); + goto cleanup; } - } - - if (!packed_buffer) { - log_error("Failed to allocate packed buffer for frame"); - goto cleanup; + + log_info("Successfully allocated buffer after emergency cleanup"); } // Copy each row, removing stride padding - for (int y = 0; y < frame->height; y++) { - memcpy(packed_buffer + y * frame->width * channels, + for (int y = 0; y < target_height; y++) { + memcpy(packed_buffer + y * target_width * channels, converted_frame->data[0] + y * converted_frame->linesize[0], - frame->width * channels); + target_width * channels); } // Increment active detections counter and track this stream @@ -359,11 +333,13 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, // Add this stream to the active list if it's not already there if (!stream_already_active) { bool added = false; - for (int i = 0; i < MAX_CONCURRENT_DETECTIONS; i++) { - if (active_detection_streams[i][0] == '\0') { - strncpy(active_detection_streams[i], stream_name, MAX_STREAM_NAME - 1); - active_detection_streams[i][MAX_STREAM_NAME - 1] = '\0'; + for (int i = 0; i < max_detections; i++) { + char *active_stream = active_detection_streams + i * MAX_STREAM_NAME; + if (active_stream[0] == '\0') { + strncpy(active_stream, stream_name, MAX_STREAM_NAME - 1); + active_stream[MAX_STREAM_NAME - 1] = '\0'; added = true; + active_detection_added = true; break; } } @@ -372,38 +348,27 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, // but don't track it (it will be treated as a one-off detection) if (!added) { log_warn("Detection tracking list full, processing stream %s as one-off detection", stream_name); + } else { + active_detections++; + log_info("Active detections: %d/%d for stream %s", active_detections, max_detections, stream_name); } } - - active_detections++; - log_info("Active detections: %d/%d for stream %s", active_detections, MAX_CONCURRENT_DETECTIONS, stream_name); - - // Log some debug info about the packed buffer - log_info("Packed buffer first 12 bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", - packed_buffer[0], packed_buffer[1], packed_buffer[2], packed_buffer[3], - packed_buffer[4], packed_buffer[5], packed_buffer[6], packed_buffer[7], - packed_buffer[8], packed_buffer[9], packed_buffer[10], packed_buffer[11]); // Get the appropriate threshold for the model type - float threshold = config.detection_threshold; - if (threshold <= 0.0f) { - if (strcmp(model_type, MODEL_TYPE_SOD_REALNET) == 0) { - threshold = 5.0f; // RealNet models typically use 5.0 - log_info("Using default threshold of 5.0 for RealNet model"); - } else { - threshold = 0.3f; // CNN models typically use 0.3 - log_info("Using default threshold of 0.3 for CNN model"); - } - } else { - log_info("Using configured threshold of %.2f for model", threshold); - } + float threshold = get_detection_threshold(model_type, stream_config.detection_threshold); + log_info("Using threshold %.2f for model %s", threshold, model_type); + + // CRITICAL FIX: Log more details about the detection configuration + log_info("DETECTION DEBUG: Stream=%s, Model=%s, Type=%s, Threshold=%.2f, Dimensions=%dx%d, Channels=%d", + stream_name, stream_config.detection_model, model_type, threshold, + target_width, target_height, channels); // Get global config to access models path extern config_t g_config; // Check if model_path is a relative path char full_model_path[MAX_PATH_LENGTH]; - if (config.detection_model[0] != '/') { + if (stream_config.detection_model[0] != '/') { // Construct full path using configured models path from INI if it exists if (g_config.models_path && strlen(g_config.models_path) > 0) { // Calculate available space for model name @@ -411,16 +376,16 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, size_t model_max_len = MAX_PATH_LENGTH - prefix_len - 1; // -1 for null terminator // Ensure model name isn't too long - size_t model_len = strlen(config.detection_model); + size_t model_len = strlen(stream_config.detection_model); if (model_len > model_max_len) { log_error("Model name too long: %s (max allowed: %zu chars)", - config.detection_model, model_max_len); + stream_config.detection_model, model_max_len); goto cleanup; } // Safe to use snprintf now int ret = snprintf(full_model_path, MAX_PATH_LENGTH, "%s/%s", - g_config.models_path, config.detection_model); + g_config.models_path, stream_config.detection_model); // Check for truncation if (ret < 0 || ret >= MAX_PATH_LENGTH) { @@ -435,16 +400,16 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, size_t model_max_len = MAX_PATH_LENGTH - prefix_len - 1; // -1 for null terminator // Ensure model name isn't too long - size_t model_len = strlen(config.detection_model); + size_t model_len = strlen(stream_config.detection_model); if (model_len > model_max_len) { log_error("Model name too long: %s (max allowed: %zu chars)", - config.detection_model, model_max_len); + stream_config.detection_model, model_max_len); goto cleanup; } // Safe to use snprintf now int ret = snprintf(full_model_path, MAX_PATH_LENGTH, "%s%s", - prefix, config.detection_model); + prefix, stream_config.detection_model); // Check for truncation if (ret < 0 || ret >= MAX_PATH_LENGTH) { @@ -473,17 +438,17 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, size_t model_max_len = MAX_PATH_LENGTH - prefix_len - 1; // -1 for null terminator // Ensure model name isn't too long - size_t model_len = strlen(config.detection_model); + size_t model_len = strlen(stream_config.detection_model); if (model_len > model_max_len) { log_error("Model name too long for alternative location: %s (max allowed: %zu chars)", - config.detection_model, model_max_len); + stream_config.detection_model, model_max_len); continue; // Try next location } // Safe to use snprintf now int ret = snprintf(alt_path, MAX_PATH_LENGTH, "%s%s", locations[i], - config.detection_model); + stream_config.detection_model); // Check for truncation if (ret < 0 || ret >= MAX_PATH_LENGTH) { @@ -500,7 +465,7 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, } } else { // Already an absolute path - strncpy(full_model_path, config.detection_model, MAX_PATH_LENGTH - 1); + strncpy(full_model_path, stream_config.detection_model, MAX_PATH_LENGTH - 1); } log_info("Using model path: %s", full_model_path); @@ -513,7 +478,7 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, time_t frame_time = time(NULL); // Check if we should use motion detection - bool use_motion_detection = (strcmp(config.detection_model, "motion") == 0); + bool use_motion_detection = (strcmp(stream_config.detection_model, "motion") == 0); // If the model is "motion", enable optimized motion detection if (use_motion_detection) { @@ -544,7 +509,7 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, // Run optimized motion detection only if it's properly enabled if (is_motion_detection_enabled(stream_name)) { - int motion_ret = detect_motion(stream_name, packed_buffer, frame->width, frame->height, + int motion_ret = detect_motion(stream_name, packed_buffer, target_width, target_height, channels, frame_time, &motion_result); if (motion_ret == 0 && motion_result.count > 0) { @@ -552,8 +517,8 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, stream_name, motion_result.detections[0].confidence); // Pass motion detection results to process_frame_for_recording - int ret = process_frame_for_recording(stream_name, packed_buffer, frame->width, - frame->height, channels, frame_time, &motion_result); + int ret = process_frame_for_recording(stream_name, packed_buffer, target_width, + target_height, channels, frame_time, &motion_result); if (ret != 0) { log_error("Failed to process optimized motion detection results for recording (error code: %d)", ret); @@ -695,11 +660,27 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, // Load the new model using the SOD integration module log_info("LOADING DETECTION MODEL: %s with threshold: %.2f", full_model_path, threshold); + // CRITICAL FIX: Check if SOD is enabled at compile time + #ifdef SOD_ENABLED + log_info("SOD is enabled at compile time"); + #else + log_warn("SOD is NOT enabled at compile time, will try dynamic loading"); + #endif + // Use the SOD integration function to load the model char resolved_path[MAX_PATH_LENGTH]; - model = load_sod_model_for_detection(config.detection_model, threshold, + log_info("Calling load_sod_model_for_detection with model=%s, threshold=%.2f", + stream_config.detection_model, threshold); + model = load_sod_model_for_detection(stream_config.detection_model, threshold, resolved_path, MAX_PATH_LENGTH); + if (!model) { + log_error("CRITICAL ERROR: Failed to load model %s", stream_config.detection_model); + } else { + log_info("Successfully loaded model %s (resolved to %s)", + stream_config.detection_model, resolved_path); + } + if (model) { // Update the full_model_path with the resolved path strncpy(full_model_path, resolved_path, MAX_PATH_LENGTH - 1); @@ -733,7 +714,7 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, } else { // Use our improved detect_objects function for ALL model types log_info("RUNNING DETECTION with unified detect_objects function"); - detect_ret = detect_objects(model, packed_buffer, frame->width, frame->height, channels, &result); + detect_ret = detect_objects(model, packed_buffer, target_width, target_height, channels, &result); if (detect_ret != 0) { log_error("Detection failed (error code: %d)", detect_ret); @@ -750,78 +731,119 @@ int process_decoded_frame_for_detection(const char *stream_name, AVFrame *frame, } // Pass detection results to process_frame_for_recording - int ret = process_frame_for_recording(stream_name, packed_buffer, frame->width, - frame->height, channels, frame_time, &result); + int ret = process_frame_for_recording(stream_name, packed_buffer, target_width, + target_height, channels, frame_time, &result); if (ret != 0) { log_error("Failed to process detection results for recording (error code: %d)", ret); } } - // Note: We don't unload the model here, it stays in the cache + // Note: We don't unload the model here, it's kept in the cache } } -cleanup: - // Cleanup - ensure all resources are properly freed - if (packed_buffer) { - // Return the buffer to the pool - for (int i = 0; i < MAX_BUFFER_POOL_SIZE; i++) { - if (buffer_pool[i].buffer == packed_buffer) { - buffer_pool[i].in_use = false; - buffer_pool[i].last_used = time(NULL); - log_info("Returned buffer to pool (index %d)", i); - break; - } - } - - // Decrement active detections counter and remove this stream from the active list - if (active_detections > 0) { - active_detections--; - } - - // Remove this stream from the active list - for (int i = 0; i < MAX_CONCURRENT_DETECTIONS; i++) { - if (strcmp(active_detection_streams[i], stream_name) == 0) { - active_detection_streams[i][0] = '\0'; - break; - } + // CRITICAL FIX: Remove this stream from the active list + for (int i = 0; i < max_detections; i++) { + char *active_stream = active_detection_streams + i * MAX_STREAM_NAME; + if (active_stream[0] != '\0' && strcmp(active_stream, stream_name) == 0) { + log_info("Removing stream %s from active detection list (slot %d)", stream_name, i); + active_stream[0] = '\0'; // Clear the slot + break; } - - log_info("Active detections: %d/%d after completing %s", active_detections, MAX_CONCURRENT_DETECTIONS, stream_name); } - if (buffer) { - av_free(buffer); + // Only decrement active detections counter if we incremented it + if (active_detection_added) { + active_detections--; + log_info("Decremented active detections to %d after successful detection", active_detections); + } + + // Return the detection result + return detect_ret; + +cleanup: + // Free resources + if (sws_ctx) { + sws_freeContext(sws_ctx); } if (converted_frame) { av_frame_free(&converted_frame); } - if (sws_ctx) { - sws_freeContext(sws_ctx); + if (buffer) { + av_free(buffer); } - - log_info("Finished processing frame %d for detection", frame_counter); - return (detect_ret == 0) ? 0 : -1; + + if (packed_buffer) { + return_buffer_to_pool(packed_buffer); + } + + // CRITICAL FIX: Remove this stream from the active list in case of error + for (int i = 0; i < max_detections; i++) { + char *active_stream = active_detection_streams + i * MAX_STREAM_NAME; + if (active_stream[0] != '\0' && strcmp(active_stream, stream_name) == 0) { + log_info("Removing stream %s from active detection list on error (slot %d)", stream_name, i); + active_stream[0] = '\0'; // Clear the slot + break; + } + } + + // Only decrement active detections counter if we incremented it + if (active_detection_added) { + active_detections--; + log_info("Decremented active detections to %d after error", active_detections); + } + + return -1; } /** * Cleanup detection resources when shutting down - * This should be called when the application is exiting */ void cleanup_detection_resources(void) { + // Free active detection streams array + if (active_detection_streams) { + free(active_detection_streams); + active_detection_streams = NULL; + } + + // Cleanup buffer pool + cleanup_buffer_pool(); + + log_info("Detection resources cleaned up"); +} + +/** + * Get the number of active detections + */ +int get_active_detection_count(void) { + return active_detections; +} + +/** + * Get the maximum number of concurrent detections + */ +int get_max_detection_count(void) { + return max_detections; +} + +/** + * Check if a detection is already in progress for a specific stream + */ +bool is_detection_in_progress(const char *stream_name) { + if (!stream_name || !active_detection_streams) { + return false; + } - // Free all buffers in the pool - for (int i = 0; i < MAX_BUFFER_POOL_SIZE; i++) { - if (buffer_pool[i].buffer) { - free(buffer_pool[i].buffer); - buffer_pool[i].buffer = NULL; - buffer_pool[i].size = 0; - buffer_pool[i].in_use = false; + // Check if this stream is in the active list + for (int i = 0; i < max_detections; i++) { + char *active_stream = active_detection_streams + i * MAX_STREAM_NAME; + if (active_stream[0] != '\0' && strcmp(active_stream, stream_name) == 0) { + return true; } } - log_info("Cleaned up detection resources"); + return false; } diff --git a/src/web/api_handlers_system.c b/src/web/api_handlers_system.c index 3881e961..fff19934 100644 --- a/src/web/api_handlers_system.c +++ b/src/web/api_handlers_system.c @@ -87,32 +87,47 @@ void mg_handle_get_system_info(struct mg_connection *c, struct mg_http_message * } } + // Get system-wide memory information first + struct sysinfo sys_info; + unsigned long long system_total = 0; + unsigned long long system_free = 0; + unsigned long long system_used = 0; + + if (sysinfo(&sys_info) == 0) { + // Calculate memory values in bytes + system_total = sys_info.totalram * sys_info.mem_unit; + system_free = sys_info.freeram * sys_info.mem_unit; + system_used = system_total - system_free; + } + // Get memory information for the LightNVR process cJSON *memory = cJSON_CreateObject(); if (memory) { // Get process memory usage using /proc/self/status FILE *fp = fopen("/proc/self/status", "r"); - unsigned long vm_size = 0; unsigned long vm_rss = 0; if (fp) { char line[256]; while (fgets(line, sizeof(line), fp)) { - if (strncmp(line, "VmSize:", 7) == 0) { - // VmSize is in kB - sscanf(line + 7, "%lu", &vm_size); - } else if (strncmp(line, "VmRSS:", 6) == 0) { - // VmRSS is in kB + if (strncmp(line, "VmRSS:", 6) == 0) { + // VmRSS is in kB - actual physical memory used sscanf(line + 6, "%lu", &vm_rss); + break; } } fclose(fp); } // Convert kB to bytes - unsigned long long total = vm_size * 1024; unsigned long long used = vm_rss * 1024; - unsigned long long free = total - used; + + // Use the system total memory as the total for LightNVR as well + // This makes it simpler to understand the memory usage + unsigned long long total = system_total; + + // Calculate free as the difference between total and used + unsigned long long free = (total > used) ? (total - used) : 0; cJSON_AddNumberToObject(memory, "total", total); cJSON_AddNumberToObject(memory, "used", used); @@ -125,17 +140,9 @@ void mg_handle_get_system_info(struct mg_connection *c, struct mg_http_message * // Get system-wide memory information cJSON *system_memory = cJSON_CreateObject(); if (system_memory) { - struct sysinfo sys_info; - if (sysinfo(&sys_info) == 0) { - // Calculate memory values in bytes - unsigned long long total = sys_info.totalram * sys_info.mem_unit; - unsigned long long free = sys_info.freeram * sys_info.mem_unit; - unsigned long long used = total - free; - - cJSON_AddNumberToObject(system_memory, "total", total); - cJSON_AddNumberToObject(system_memory, "used", used); - cJSON_AddNumberToObject(system_memory, "free", free); - } + cJSON_AddNumberToObject(system_memory, "total", system_total); + cJSON_AddNumberToObject(system_memory, "used", system_used); + cJSON_AddNumberToObject(system_memory, "free", system_free); // Add system memory object to info cJSON_AddItemToObject(info, "systemMemory", system_memory); @@ -433,8 +440,6 @@ void mg_handle_get_system_info(struct mg_connection *c, struct mg_http_message * } } - - cJSON_AddNumberToObject(streams_obj, "active", active_streams); cJSON_AddNumberToObject(streams_obj, "total", g_config.max_streams);