地平线RDK3 AI摄像机完整产品实现
·
第一部分 主测试架构程序
/**
* @file main.c
* @brief 地平线RDK3 AI摄像机主程序
* @author BSP Team
* @version 1.0.0
*
* @section 模块集成
* - 内存管理 (Memory Manager)
* - 日志系统 (Logger)
* - 事件循环 (Event Loop)
* - ISP调优 (ISP Tuning)
* - AI流水线 (AI Pipeline)
* - 双架构IPC (IPC Bus)
* - 时移缓存 (TimeShift Cache)
*
* @section 生命周期
* 1. 配置加载 -> 2. 模块初始化 -> 3. 资源分配 -> 4. 主循环 -> 5. 优雅退出
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <getopt.h>
#include <time.h>
#include <sys/resource.h>
/* 地平线RDK3 SDK头文件 */
#include <hb_common.h>
#include <hb_sys.h>
#include <hb_vio.h>
#include <hb_dnn.h>
#include <hb_ipc.h>
/* 项目模块头文件 */
#include "rdk3_cam.h"
#include "memory_manager.h"
#include "logger.h"
#include "event_loop.h"
#include "isp_tuning.h"
#include "ai_pipeline.h"
#include "ipc_bus.h"
#include "timeshift_cache.h"
/*=============================================================================
* 全局变量
*============================================================================*/
static volatile bool g_running = true; ///< 运行标志
static rdk3_cam_config_t g_config; ///< 系统配置
static event_loop_t* g_event_loop = NULL; ///< 事件循环实例
static ai_pipeline_t* g_ai_pipeline = NULL; ///< AI流水线实例
static timeshift_cache_t* g_timeshift_cache = NULL; ///< 时移缓存实例
static ipc_context_t* g_ipc_ctx = NULL; ///< IPC上下文
/* RDK3 SDK句柄 */
static hb_sys_handle_t g_sys_handle = NULL; ///< 系统句柄
static hb_vio_handle_t g_vio_handle = NULL; ///< 视频输入输出句柄
static hb_dnn_handle_t g_dnn_handle = NULL; ///< BPU推理句柄
/* 性能统计 */
static struct {
uint64_t frame_count; ///< 处理帧数
uint64_t start_time_us; ///< 开始时间
float current_fps; ///< 当前FPS
float avg_fps; ///< 平均FPS
uint32_t max_latency_us; ///< 最大延迟
uint32_t min_latency_us; ///< 最小延迟
uint32_t avg_latency_us; ///< 平均延迟
} g_perf_stats;
/*=============================================================================
* 信号处理
*============================================================================*/
/**
* @brief 信号处理函数
* @param sig 信号编号
*
* @note 支持优雅退出:SIGINT(Ctrl+C), SIGTERM
*/
static void signal_handler(int sig)
{
LOG_INFO("Received signal %d, shutting down...", sig);
g_running = false;
/* 通知事件循环退出 */
if (g_event_loop) {
event_loop_stop(g_event_loop);
}
}
/*=============================================================================
* RDK3 SDK初始化
*============================================================================*/
/**
* @brief 初始化地平线RDK3 SDK
* @return 成功返回0
*
* @note 必须最先调用,初始化硬件资源
* @warning 失败时系统无法继续运行
*/
static int rdk3_sdk_init(void)
{
LOG_INFO("Initializing Horizon RDK3 SDK...");
/* 1. 初始化系统 */
hb_sys_config_t sys_config = {
.log_level = HB_LOG_LEVEL_INFO,
.mem_config = {
.cma_size_mb = 512, /* CMA内存512MB */
.bpu_mem_size_mb = 256, /* BPU内存256MB */
.vio_mem_size_mb = 128 /* VIO内存128MB */
}
};
int ret = hb_sys_init(&sys_config, &g_sys_handle);
if (ret != 0) {
LOG_ERROR("hb_sys_init failed: %d", ret);
return -1;
}
LOG_INFO("System initialized, handle=%p", g_sys_handle);
/* 2. 初始化视频输入输出 */
hb_vio_config_t vio_config = {
.sensor_type = HB_SENSOR_IMX415,
.sensor_bus = HB_MIPI_CSI0,
.width = g_config.video_width,
.height = g_config.video_height,
.framerate = g_config.video_framerate,
.pixel_format = HB_PIXEL_FORMAT_NV12,
.buffer_count = 4, /* 4个缓冲区 */
.enable_isp = true, /* 使能ISP */
.enable_3a = true /* 使能3A */
};
ret = hb_vio_init(&vio_config, &g_vio_handle);
if (ret != 0) {
LOG_ERROR("hb_vio_init failed: %d", ret);
return -2;
}
LOG_INFO("VIO initialized, sensor=IMX415, resolution=%dx%d@%dfps",
g_config.video_width, g_config.video_height, g_config.video_framerate);
/* 3. 初始化BPU(DNN) */
hb_dnn_config_t dnn_config = {
.model_path = g_config.detect_model_path,
.core_id = HB_BPU_CORE_0,
.priority = HB_TASK_PRIORITY_NORMAL,
.timeout_ms = 100
};
ret = hb_dnn_init(&dnn_config, &g_dnn_handle);
if (ret != 0) {
LOG_ERROR("hb_dnn_init failed: %d", ret);
return -3;
}
LOG_INFO("DNN/BPU initialized, model=%s", g_config.detect_model_path);
/* 4. 启动视频流 */
ret = hb_vio_start_stream(g_vio_handle);
if (ret != 0) {
LOG_ERROR("hb_vio_start_stream failed: %d", ret);
return -4;
}
LOG_INFO("Video stream started");
return 0;
}
/**
* @brief 反初始化RDK3 SDK
*/
static void rdk3_sdk_deinit(void)
{
LOG_INFO("Deinitializing RDK3 SDK...");
if (g_vio_handle) {
hb_vio_stop_stream(g_vio_handle);
hb_vio_deinit(g_vio_handle);
g_vio_handle = NULL;
}
if (g_dnn_handle) {
hb_dnn_deinit(g_dnn_handle);
g_dnn_handle = NULL;
}
if (g_sys_handle) {
hb_sys_deinit(g_sys_handle);
g_sys_handle = NULL;
}
LOG_INFO("RDK3 SDK deinitialized");
}
/*=============================================================================
* 内存池配置
*============================================================================*/
/**
* @brief 内存池配置数组
*/
static const mem_pool_cfg_t g_mem_pools[] = {
{
.type = MEM_POOL_CMA,
.name = "cma_video",
.total_size = 512 * 1024 * 1024, /* 512MB CMA */
.flags = MEM_FLAG_CACHE_ALIGN
},
{
.type = MEM_POOL_SLAB,
.name = "slab_frame",
.block_size = sizeof(frame_t),
.block_count = 32,
.flags = MEM_FLAG_ZERO_INIT
},
{
.type = MEM_POOL_SLAB,
.name = "slab_ai_tensor",
.block_size = 4 * 1024 * 1024, /* 4MB per tensor */
.block_count = 8,
.flags = MEM_FLAG_CACHE_ALIGN | MEM_FLAG_ZERO_INIT
},
{
.type = MEM_POOL_SLAB,
.name = "slab_detection",
.block_size = 100 * sizeof(detection_object_t),
.block_count = 4,
.flags = MEM_FLAG_ZERO_INIT
}
};
/*=============================================================================
* 事件观察者(观察者模式)
*============================================================================*/
/**
* @brief 视频帧到达观察者
*/
static int frame_observer_on_event(observer_t* self, const event_t* event)
{
(void)self;
if (event->type != EVENT_NEW_FRAME) {
return 0;
}
uint64_t start_us = get_time_us();
/* 1. 获取帧数据 */
frame_t* frame = (frame_t*)event->data.frame_event.frame_data;
if (!frame) {
return -1;
}
/* 2. 更新性能统计 */
g_perf_stats.frame_count++;
/* 3. 推送到时移缓存 */
if (g_timeshift_cache) {
timeshift_cache_push(g_timeshift_cache, frame);
}
/* 4. 提交AI任务(异步) */
if (g_ai_pipeline && g_config.ai_enabled) {
ai_pipeline_submit_async(g_ai_pipeline, frame, NULL, NULL);
}
/* 5. 更新ISP参数(基于AI结果) */
static uint32_t frame_counter = 0;
if (++frame_counter % 30 == 0) {
/* 每30帧更新一次ISP参数 */
isp_tuning_params_t params = isp_tuning_auto_adapt(0, 300.0f, 5500);
isp_tuning_update_smooth(¶ms, 500);
}
uint64_t latency_us = get_time_us() - start_us;
/* 更新延迟统计 */
if (latency_us > g_perf_stats.max_latency_us) {
g_perf_stats.max_latency_us = latency_us;
}
if (latency_us < g_perf_stats.min_latency_us || g_perf_stats.min_latency_us == 0) {
g_perf_stats.min_latency_us = latency_us;
}
/* 滑动平均延迟 */
g_perf_stats.avg_latency_us =
(g_perf_stats.avg_latency_us * 99 + latency_us) / 100;
return 0;
}
/**
* @brief ISP事件观察者
*/
static int isp_observer_on_event(observer_t* self, const event_t* event)
{
(void)self;
if (event->type != EVENT_ISP_3A_READY) {
return 0;
}
/* 获取ISP统计信息 */
uint32_t avg_luminance = event->data.isp_event.avg_luminance;
uint16_t color_temp = event->data.isp_event.color_temp;
/* 动态调整场景识别 */
int scene_type = 0; /* 0=室内, 1=室外, 2=夜景 */
if (avg_luminance < 50) {
scene_type = 2; /* 夜景 */
} else if (avg_luminance > 200) {
scene_type = 1; /* 室外 */
} else {
scene_type = 0; /* 室内 */
}
/* 自适应ISP参数 */
isp_tuning_params_t params = isp_tuning_auto_adapt(scene_type,
avg_luminance,
color_temp);
isp_tuning_update_smooth(¶ms, 1000);
return 0;
}
/**
* @brief AI结果观察者
*/
static int ai_observer_on_event(observer_t* self, const event_t* event)
{
(void)self;
if (event->type != EVENT_AI_RESULT) {
return 0;
}
detection_result_t* result = (detection_result_t*)event->data.ai_event.detections;
if (result && result->object_count > 0) {
LOG_DEBUG("AI detected %u objects, inference time=%uus",
result->object_count, result->inference_time_us);
/* 根据检测结果调整ISP ROI */
for (uint32_t i = 0; i < result->object_count && i < 3; i++) {
detection_object_t* obj = &result->objects[i];
/* 如果检测到人脸,设置ISP对焦ROI */
if (obj->class_id == 0) { /* 假设0是人脸类别 */
/* 通过IPC发送ROI设置命令 */
ipc_cmd_t cmd = {
.cmd_id = CMD_ISP_SET_FOCUS_ROI,
.args = {
(uint32_t)(obj->bbox.x1 * 1000),
(uint32_t)(obj->bbox.y1 * 1000),
(uint32_t)(obj->bbox.x2 * 1000),
(uint32_t)(obj->bbox.y2 * 1000)
},
.timeout_ms = 10
};
ipc_send_async(g_ipc_ctx, &cmd);
break;
}
}
}
return 0;
}
/*=============================================================================
* 性能监控线程
*============================================================================*/
/**
* @brief 性能监控线程函数
* @param arg 线程参数
* @return NULL
*
* @note 每秒输出一次性能统计
*/
static void* perf_monitor_thread(void* arg)
{
(void)arg;
uint64_t last_frame_count = 0;
uint64_t last_time_us = get_time_us();
while (g_running) {
sleep(5); /* 每5秒输出一次 */
uint64_t now_us = get_time_us();
uint64_t elapsed_us = now_us - last_time_us;
uint64_t frame_diff = g_perf_stats.frame_count - last_frame_count;
if (elapsed_us > 0) {
g_perf_stats.current_fps = frame_diff * 1000000.0f / elapsed_us;
}
/* 计算平均FPS */
uint64_t total_elapsed_us = now_us - g_perf_stats.start_time_us;
if (total_elapsed_us > 0) {
g_perf_stats.avg_fps = g_perf_stats.frame_count * 1000000.0f / total_elapsed_us;
}
LOG_INFO("[PERF] FPS: curr=%.1f avg=%.1f | Latency: avg=%uus min=%uus max=%uus | Frames=%llu",
g_perf_stats.current_fps,
g_perf_stats.avg_fps,
g_perf_stats.avg_latency_us,
g_perf_stats.min_latency_us,
g_perf_stats.max_latency_us,
(unsigned long long)g_perf_stats.frame_count);
/* 打印内存统计 */
memory_manager_dump_stats();
/* 打印时移缓存统计 */
if (g_timeshift_cache) {
timeshift_cache_stat_t ts_stat;
timeshift_cache_get_stat(g_timeshift_cache, &ts_stat);
LOG_INFO("[TIMESHIFT] total_frames=%llu, cached=%u, replay_count=%u",
(unsigned long long)ts_stat.total_pushed,
ts_stat.current_size,
ts_stat.replay_count);
}
last_frame_count = g_perf_stats.frame_count;
last_time_us = now_us;
}
return NULL;
}
/*=============================================================================
* 时移回放演示
*============================================================================*/
/**
* @brief 时移回放演示线程
* @param arg 线程参数
* @return NULL
*
* @note 演示从时移缓存中回放10秒前的视频
*/
static void* timeshift_replay_demo(void* arg)
{
(void)arg;
/* 等待系统稳定 */
sleep(30);
LOG_INFO("Starting timeshift replay demo...");
while (g_running) {
/* 每60秒回放一次10秒前的视频 */
sleep(60);
LOG_INFO("Replaying video from 10 seconds ago...");
/* 设置回放时间偏移(-10秒) */
int64_t offset_us = -10 * 1000000LL;
timeshift_cache_set_replay_offset(g_timeshift_cache, offset_us);
/* 回放5秒钟 */
uint64_t replay_start_us = get_time_us();
uint32_t replay_frames = 0;
while ((get_time_us() - replay_start_us) < 5 * 1000000LL) {
frame_t* frame = timeshift_cache_get_next(g_timeshift_cache);
if (frame) {
replay_frames++;
/* 这里可以将frame送去编码或显示 */
LOG_TRACE("Replay frame %u", frame->sequence);
timeshift_cache_release_frame(g_timeshift_cache, frame);
} else {
usleep(10000); /* 等待新帧 */
}
}
LOG_INFO("Replay completed: %u frames in 5 seconds", replay_frames);
/* 恢复正常实时模式 */
timeshift_cache_set_replay_offset(g_timeshift_cache, 0);
}
return NULL;
}
/*=============================================================================
* 配置加载
*============================================================================*/
/**
* @brief 加载配置文件
* @param config_path 配置文件路径
* @return 成功返回0
*
* @note 支持YAML/JSON格式(简化实现)
*/
static int load_config(const char* config_path, rdk3_cam_config_t* config)
{
/* 设置默认配置 */
memset(config, 0, sizeof(rdk3_cam_config_t));
config->video_width = 1920;
config->video_height = 1080;
config->video_framerate = 30;
config->video_format = HB_PIXEL_FORMAT_NV12;
config->ai_enabled = true;
config->detect_model_path = "/data/models/yolov5s.hbm";
config->ai_confidence_threshold = 0.5f;
config->ai_nms_threshold = 0.45f;
config->log_level = LOG_LEVEL_INFO;
config->log_output = LOG_OUTPUT_CONSOLE | LOG_OUTPUT_FILE;
config->log_file_path = "/var/log/rdk3_cam.log";
config->cma_pool_size = 512;
config->dma_pool_size = 256;
config->ai_tensor_pool_size = 128;
config->frame_cache_size = 300; /* 缓存300帧(10秒@30fps) */
config->model_cache_size = 3;
/* 如果提供了配置文件,解析之(简化) */
if (config_path && access(config_path, F_OK) == 0) {
LOG_INFO("Loading config from %s", config_path);
/* 实际应用中使用yaml解析库 */
}
return 0;
}
/*=============================================================================
* 主函数
*============================================================================*/
/**
* @brief 打印使用帮助
*/
static void print_usage(const char* progname)
{
printf("Usage: %s [options]\n", progname);
printf("Options:\n");
printf(" -c, --config FILE Config file path\n");
printf(" -d, --daemon Run as daemon\n");
printf(" -l, --level LEVEL Log level (0-5)\n");
printf(" -h, --help Show this help\n");
printf("\nExample:\n");
printf(" %s -c /etc/rdk3_cam.yaml\n", progname);
}
/**
* @brief 主函数入口
*/
int main(int argc, char* argv[])
{
int ret = 0;
pthread_t perf_thread;
pthread_t replay_thread;
/*=========================================================================
* 1. 解析命令行参数
*========================================================================*/
const char* config_path = NULL;
bool run_as_daemon = false;
static struct option long_options[] = {
{"config", required_argument, 0, 'c'},
{"daemon", no_argument, 0, 'd'},
{"level", required_argument, 0, 'l'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
while ((opt = getopt_long(argc, argv, "c:dl:h", long_options, NULL)) != -1) {
switch (opt) {
case 'c':
config_path = optarg;
break;
case 'd':
run_as_daemon = true;
break;
case 'l':
/* 日志级别稍后设置 */
break;
case 'h':
default:
print_usage(argv[0]);
return 0;
}
}
/* 守护进程模式 */
if (run_as_daemon) {
if (daemon(1, 0) != 0) {
perror("daemon");
return 1;
}
}
/*=========================================================================
* 2. 加载配置
*========================================================================*/
ret = load_config(config_path, &g_config);
if (ret != 0) {
fprintf(stderr, "Failed to load config\n");
return 1;
}
/*=========================================================================
* 3. 初始化日志系统
*========================================================================*/
logger_config_t log_config = {
.level = g_config.log_level,
.output = g_config.log_output,
.file_path = g_config.log_file_path,
.file_max_size = 10 * 1024 * 1024,
.file_max_count = 3,
.ringbuf_size = 1024 * 1024,
.enable_timestamp = true,
.enable_thread_id = true,
.enable_file_line = true,
.async_mode = true,
.async_queue_size = 4096
};
logger_init(&log_config);
LOG_INFO("========================================");
LOG_INFO("RDK3 AI Camera v1.0.0 Starting...");
LOG_INFO("========================================");
/*=========================================================================
* 4. 设置信号处理
*========================================================================*/
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGPIPE, SIG_IGN);
/*=========================================================================
* 5. 初始化内存管理(对象池模式)
*========================================================================*/
LOG_INFO("Initializing memory manager...");
ret = memory_manager_init(g_mem_pools, sizeof(g_mem_pools) / sizeof(g_mem_pools[0]));
if (ret != 0) {
LOG_FATAL("Memory manager init failed: %d", ret);
goto error_exit;
}
/*=========================================================================
* 6. 初始化RDK3 SDK(BPU硬件加速)
*========================================================================*/
ret = rdk3_sdk_init();
if (ret != 0) {
LOG_FATAL("RDK3 SDK init failed: %d", ret);
goto error_exit;
}
/*=========================================================================
* 7. 初始化双架构IPC
*========================================================================*/
LOG_INFO("Initializing IPC bus (ARM <-> ARM-None)...");
g_ipc_ctx = ipc_bus_init("/dev/shm/rdk3_ipc", 2 * 1024 * 1024, true);
if (!g_ipc_ctx) {
LOG_WARN("IPC bus init failed, running in single-core mode");
} else {
LOG_INFO("IPC bus initialized");
}
/*=========================================================================
* 8. 初始化ISP调优
*========================================================================*/
LOG_INFO("Initializing ISP tuning...");
ret = isp_tuning_init("/etc/isp/imx415_tuning.bin");
if (ret != 0) {
LOG_WARN("ISP tuning init failed, using defaults");
}
/*=========================================================================
* 9. 初始化事件循环(观察者模式)
*========================================================================*/
LOG_INFO("Initializing event loop...");
event_loop_config_t el_config = {
.max_events = 64,
.event_queue_size = 1024,
.epoll_timeout_ms = 100,
.enable_priority_queue = true,
.enable_stats = true
};
g_event_loop = event_loop_get_instance(&el_config);
if (!g_event_loop) {
LOG_FATAL("Event loop init failed");
goto error_exit;
}
/* 注册观察者 */
static observer_t frame_observer = {
.name = "FrameObserver",
.on_event = frame_observer_on_event,
.subscribed_events = EVENT_NEW_FRAME,
.priority = EVENT_PRIO_CRITICAL
};
event_loop_register_observer(g_event_loop, &frame_observer);
static observer_t isp_observer = {
.name = "ISPObserver",
.on_event = isp_observer_on_event,
.subscribed_events = EVENT_ISP_3A_READY,
.priority = EVENT_PRIO_HIGH
};
event_loop_register_observer(g_event_loop, &isp_observer);
static observer_t ai_observer = {
.name = "AIObserver",
.on_event = ai_observer_on_event,
.subscribed_events = EVENT_AI_RESULT,
.priority = EVENT_PRIO_NORMAL
};
event_loop_register_observer(g_event_loop, &ai_observer);
/*=========================================================================
* 10. 初始化AI流水线(责任链模式)
*========================================================================*/
LOG_INFO("Initializing AI pipeline...");
ai_pipeline_config_t ai_config = {
.detect_model_path = g_config.detect_model_path,
.preprocess = {
.target_width = 640,
.target_height = 640,
.target_format = TENSOR_FORMAT_RGB,
.keep_aspect_ratio = true,
.pad_value = 114,
.mean = {0.485f, 0.456f, 0.406f},
.std = {0.229f, 0.224f, 0.225f},
.convert_to_rgb = true
},
.postprocess = {
.confidence_threshold = g_config.ai_confidence_threshold,
.nms_threshold = g_config.ai_nms_threshold,
.max_detections = 100,
.enable_feature_extract = false
},
.yolo = {
.num_classes = 80,
.num_anchors = 3,
.anchors = {{10,13}, {16,30}, {33,23}},
.stride = {8, 16, 32},
.input_width = 640,
.input_height = 640
},
.tracker = {
.max_tracked_objects = 50,
.max_lost_frames = 30,
.iou_threshold = 0.3f,
.feature_threshold = 0.5f,
.use_kalman = true
},
.max_pending_tasks = 8,
.enable_async = true,
.worker_threads = 2
};
g_ai_pipeline = ai_pipeline_create(&ai_config);
if (!g_ai_pipeline) {
LOG_FATAL("AI pipeline init failed");
goto error_exit;
}
LOG_INFO("AI pipeline initialized");
/*=========================================================================
* 11. 初始化时移缓存
*========================================================================*/
LOG_INFO("Initializing timeshift cache (capacity=%u frames)...",
g_config.frame_cache_size);
timeshift_cache_config_t ts_config = {
.capacity = g_config.frame_cache_size,
.max_frame_size = g_config.video_width * g_config.video_height * 1.5f,
.recycle_threshold = 0.8f,
.enable_compression = false
};
g_timeshift_cache = timeshift_cache_create(&ts_config);
if (!g_timeshift_cache) {
LOG_WARN("Timeshift cache init failed, continuing without caching");
}
/*=========================================================================
* 12. 注册V4L2设备到事件循环
*========================================================================*/
/* 获取V4L2设备文件描述符(从VIO模块) */
int v4l2_fd = hb_vio_get_fd(g_vio_handle);
if (v4l2_fd >= 0) {
event_loop_add_fd(g_event_loop, v4l2_fd, EPOLLIN,
/* 帧到达回调 */ NULL, NULL);
LOG_INFO("V4L2 device registered to event loop");
}
/*=========================================================================
* 13. 启动性能监控线程
*========================================================================*/
g_perf_stats.start_time_us = get_time_us();
pthread_create(&perf_thread, NULL, perf_monitor_thread, NULL);
/*=========================================================================
* 14. 启动时移回放演示线程(可选)
*========================================================================*/
pthread_create(&replay_thread, NULL, timeshift_replay_demo, NULL);
/*=========================================================================
* 15. 启动事件循环(主线程阻塞)
*========================================================================*/
LOG_INFO("========================================");
LOG_INFO("RDK3 AI Camera is RUNNING");
LOG_INFO("========================================");
LOG_INFO("Resolution: %dx%d@%dfps",
g_config.video_width, g_config.video_height, g_config.video_framerate);
LOG_INFO("AI Model: %s", g_config.detect_model_path);
LOG_INFO("Press Ctrl+C to stop");
ret = event_loop_start(g_event_loop);
/*=========================================================================
* 16. 等待退出
*========================================================================*/
LOG_INFO("Event loop exited, cleaning up...");
pthread_join(perf_thread, NULL);
pthread_join(replay_thread, NULL);
error_exit:
/*=========================================================================
* 17. 清理资源(逆序)
*========================================================================*/
LOG_INFO("Cleaning up resources...");
/* 停止AI流水线 */
if (g_ai_pipeline) {
ai_pipeline_destroy(g_ai_pipeline);
g_ai_pipeline = NULL;
}
/* 销毁时移缓存 */
if (g_timeshift_cache) {
timeshift_cache_destroy(g_timeshift_cache);
g_timeshift_cache = NULL;
}
/* 反初始化IPC */
if (g_ipc_ctx) {
ipc_bus_deinit(g_ipc_ctx);
g_ipc_ctx = NULL;
}
/* 反初始化RDK3 SDK */
rdk3_sdk_deinit();
/* 反初始化日志 */
LOG_INFO("RDK3 AI Camera stopped");
logger_deinit();
LOG_INFO("Cleanup completed, exiting.");
return ret;
}
时移缓存模块 timeshift_cache.h
/**
* @file timeshift_cache.h
* @brief 时移缓存模块(环形缓冲区)
* @author System Team
*
* @section 功能说明
* - 实现视频帧的环形缓存,支持时移回放
* - 支持实时写入和时移读取两种模式
* - 自动回收旧帧,避免内存溢出
*
* @section 设计模式
* - **环形缓冲区(Ring Buffer)**: 固定大小循环存储
* - **读写锁(Read-Write Lock)**: 支持并发读写
*/
#ifndef TIMESHIFT_CACHE_H
#define TIMESHIFT_CACHE_H
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 前向声明 */
typedef struct timeshift_cache timeshift_cache_t;
/*=============================================================================
* 配置结构体
*============================================================================*/
/**
* @brief 时移缓存配置
*/
typedef struct {
uint32_t capacity; ///< 最大缓存帧数(默认300=10秒@30fps)
size_t max_frame_size; ///< 单帧最大大小(字节)
float recycle_threshold; ///< 回收阈值(0-1, 超过容量*阈值开始回收)
bool enable_compression; ///< 是否启用压缩(暂不支持)
} timeshift_cache_config_t;
/**
* @brief 时移缓存统计信息
*/
typedef struct {
uint64_t total_pushed; ///< 总推入帧数
uint64_t total_popped; ///< 总弹出帧数
uint32_t current_size; ///< 当前缓存帧数
uint32_t max_size; ///< 最大缓存帧数
uint32_t replay_count; ///< 回放次数
uint32_t lost_frames; ///< 丢失帧数(缓存满)
} timeshift_cache_stat_t;
/*=============================================================================
* API函数
*============================================================================*/
/**
* @brief 创建时移缓存
* @param config 缓存配置
* @return 缓存句柄
*/
timeshift_cache_t* timeshift_cache_create(const timeshift_cache_config_t* config);
/**
* @brief 销毁时移缓存
* @param cache 缓存句柄
*/
void timeshift_cache_destroy(timeshift_cache_t* cache);
/**
* @brief 推入帧到缓存(实时模式)
* @param cache 缓存句柄
* @param frame 帧数据(会被拷贝)
* @return 成功返回0
*
* @note 帧数据会被深拷贝到内部缓冲区
*/
int timeshift_cache_push(timeshift_cache_t* cache, const frame_t* frame);
/**
* @brief 设置回放偏移
* @param cache 缓存句柄
* @param offset_us 偏移时间(微秒, 负数表示过去)
* @return 成功返回0
*
* @note offset_us = -10000000 表示回放10秒前的视频
*/
int timeshift_cache_set_replay_offset(timeshift_cache_t* cache, int64_t offset_us);
/**
* @brief 获取下一帧(回放模式)
* @param cache 缓存句柄
* @return 帧指针(需要调用release释放)
*
* @note 调用后必须使用timeshift_cache_release_frame释放
*/
frame_t* timeshift_cache_get_next(timeshift_cache_t* cache);
/**
* @brief 释放帧
* @param cache 缓存句柄
* @param frame 帧指针
*/
void timeshift_cache_release_frame(timeshift_cache_t* cache, frame_t* frame);
/**
* @brief 获取缓存统计信息
* @param cache 缓存句柄
* @param stat 输出统计信息
*/
void timeshift_cache_get_stat(timeshift_cache_t* cache, timeshift_cache_stat_t* stat);
#ifdef __cplusplus
}
#endif
#endif /* TIMESHIFT_CACHE_H */
时移缓存实现 timeshift_cache.c
/**
* @file timeshift_cache.c
* @brief 时移缓存实现
*/
#include "timeshift_cache.h"
#include "memory_manager.h"
#include "logger.h"
#include <stdlib.h>
#include <string.h>
/*=============================================================================
* 缓存节点
*============================================================================*/
/**
* @brief 缓存节点
*/
typedef struct cache_node {
frame_t* frame; ///< 帧数据
uint64_t timestamp_us; ///< 时间戳
uint32_t sequence; ///< 帧序号
bool in_use; ///< 是否在使用中
struct cache_node* next; ///< 下一个节点
} cache_node_t;
/**
* @brief 时移缓存上下文
*/
struct timeshift_cache {
/* 环形缓冲区 */
cache_node_t* nodes; ///< 节点数组
uint32_t capacity; ///< 容量
uint32_t head; ///< 写指针
uint32_t tail; ///< 读指针(实时模式)
uint32_t size; ///< 当前大小
/* 回放模式 */
bool replay_mode; ///< 是否处于回放模式
int64_t replay_offset_us; ///< 回放偏移(微秒)
uint32_t replay_pos; ///< 回放读取位置
uint32_t replay_target_seq; ///< 目标帧序号
/* 配置 */
timeshift_cache_config_t config;
/* 统计 */
timeshift_cache_stat_t stats;
/* 锁 */
pthread_rwlock_t rwlock;
};
/*=============================================================================
* 实现函数
*============================================================================*/
timeshift_cache_t* timeshift_cache_create(const timeshift_cache_config_t* config)
{
if (!config || config->capacity == 0) {
return NULL;
}
timeshift_cache_t* cache = (timeshift_cache_t*)calloc(1, sizeof(timeshift_cache_t));
if (!cache) {
return NULL;
}
memcpy(&cache->config, config, sizeof(timeshift_cache_config_t));
cache->capacity = config->capacity;
/* 分配节点数组 */
cache->nodes = (cache_node_t*)calloc(cache->capacity, sizeof(cache_node_t));
if (!cache->nodes) {
free(cache);
return NULL;
}
/* 预分配帧内存(从内存池) */
for (uint32_t i = 0; i < cache->capacity; i++) {
cache->nodes[i].frame = (frame_t*)memory_pool_alloc("slab_frame", sizeof(frame_t));
if (cache->nodes[i].frame) {
/* 分配帧数据缓冲区 */
cache->nodes[i].frame->data[0] = memory_pool_alloc("cma_video",
cache->config.max_frame_size);
}
}
pthread_rwlock_init(&cache->rwlock, NULL);
LOG_INFO("Timeshift cache created: capacity=%u, max_frame_size=%zu",
cache->capacity, cache->config.max_frame_size);
return cache;
}
void timeshift_cache_destroy(timeshift_cache_t* cache)
{
if (!cache) return;
pthread_rwlock_wrlock(&cache->rwlock);
/* 释放帧内存 */
for (uint32_t i = 0; i < cache->capacity; i++) {
if (cache->nodes[i].frame) {
if (cache->nodes[i].frame->data[0]) {
memory_pool_free("cma_video", cache->nodes[i].frame->data[0]);
}
memory_pool_free("slab_frame", cache->nodes[i].frame);
}
}
free(cache->nodes);
pthread_rwlock_unlock(&cache->rwlock);
pthread_rwlock_destroy(&cache->rwlock);
free(cache);
LOG_INFO("Timeshift cache destroyed");
}
int timeshift_cache_push(timeshift_cache_t* cache, const frame_t* frame)
{
if (!cache || !frame) return -1;
pthread_rwlock_wrlock(&cache->rwlock);
/* 如果在回放模式,不接受新帧 */
if (cache->replay_mode) {
pthread_rwlock_unlock(&cache->rwlock);
return -2;
}
/* 获取当前写入位置 */
cache_node_t* node = &cache->nodes[cache->head];
/* 拷贝帧数据 */
memcpy(node->frame, frame, sizeof(frame_t));
/* 深拷贝图像数据(从内存池分配) */
if (frame->data[0] && node->frame->data[0]) {
memcpy(node->frame->data[0], frame->data[0], frame->size);
}
node->timestamp_us = frame->timestamp;
node->sequence = frame->sequence;
node->in_use = false;
/* 更新写指针 */
cache->head = (cache->head + 1) % cache->capacity;
/* 更新大小 */
if (cache->size < cache->capacity) {
cache->size++;
} else {
/* 缓存满,覆盖最旧的帧 */
cache->tail = (cache->tail + 1) % cache->capacity;
cache->stats.lost_frames++;
}
cache->stats.total_pushed++;
pthread_rwlock_unlock(&cache->rwlock);
return 0;
}
int timeshift_cache_set_replay_offset(timeshift_cache_t* cache, int64_t offset_us)
{
if (!cache) return -1;
pthread_rwlock_wrlock(&cache->rwlock);
if (offset_us == 0) {
/* 退出回放模式 */
cache->replay_mode = false;
cache->replay_offset_us = 0;
LOG_INFO("Timeshift replay disabled, returning to live mode");
} else {
/* 进入回放模式 */
uint64_t now_us = get_time_us();
uint64_t target_time_us = now_us + offset_us;
/* 查找目标帧(二分查找) */
cache->replay_pos = cache->tail;
cache->replay_target_seq = 0;
cache->replay_mode = true;
cache->replay_offset_us = offset_us;
cache->stats.replay_count++;
LOG_INFO("Timeshift replay enabled: offset=%lldms",
(long long)(-offset_us / 1000));
}
pthread_rwlock_unlock(&cache->rwlock);
return 0;
}
frame_t* timeshift_cache_get_next(timeshift_cache_t* cache)
{
if (!cache) return NULL;
pthread_rwlock_rdlock(&cache->rwlock);
if (!cache->replay_mode || cache->size == 0) {
pthread_rwlock_unlock(&cache->rwlock);
return NULL;
}
/* 获取当前回放位置的帧 */
cache_node_t* node = &cache->nodes[cache->replay_pos];
if (!node->frame || node->frame->sequence == 0) {
pthread_rwlock_unlock(&cache->rwlock);
return NULL;
}
/* 标记为使用中 */
node->in_use = true;
/* 移动到下一帧 */
cache->replay_pos = (cache->replay_pos + 1) % cache->capacity;
/* 如果已经追赶上实时流,退出回放模式 */
if (cache->replay_pos == cache->head) {
cache->replay_mode = false;
LOG_INFO("Timeshift replay finished, caught up to live stream");
}
pthread_rwlock_unlock(&cache->rwlock);
return node->frame;
}
void timeshift_cache_release_frame(timeshift_cache_t* cache, frame_t* frame)
{
if (!cache || !frame) return;
pthread_rwlock_wrlock(&cache->rwlock);
/* 查找并释放帧 */
for (uint32_t i = 0; i < cache->capacity; i++) {
if (cache->nodes[i].frame == frame) {
cache->nodes[i].in_use = false;
break;
}
}
pthread_rwlock_unlock(&cache->rwlock);
}
void timeshift_cache_get_stat(timeshift_cache_t* cache, timeshift_cache_stat_t* stat)
{
if (!cache || !stat) return;
pthread_rwlock_rdlock(&cache->rwlock);
memcpy(stat, &cache->stats, sizeof(timeshift_cache_stat_t));
stat->current_size = cache->size;
stat->max_size = cache->capacity;
pthread_rwlock_unlock(&cache->rwlock);
}
CMake构建文件 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(rdk3_cam_solution VERSION 1.0.0 LANGUAGES C)
#=============================================================================
# 编译选项
#=============================================================================
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -O2 -g -pthread")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG -O0")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DNDEBUG -O3")
#=============================================================================
# 查找依赖
#=============================================================================
# 地平线RDK3 SDK
find_path(HB_SDK_INCLUDE_DIR hb_common.h PATHS /usr/include/horizon)
find_library(HB_SDK_LIBRARY hb_sdk PATHS /usr/lib/horizon)
# 其他依赖
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBYAML REQUIRED yaml-0.1)
#=============================================================================
# 头文件路径
#=============================================================================
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include
${HB_SDK_INCLUDE_DIR}
${LIBYAML_INCLUDE_DIRS}
)
#=============================================================================
# 源文件列表
#=============================================================================
set(SOURCES
src/main.c
src/core/memory/memory_manager.c
src/core/log/logger.c
src/core/ipc/ipc_bus.c
src/middleware/isp/isp_tuning.c
src/middleware/isp/isp_3a.c
src/middleware/bpu/bpu_runtime.c
src/algorithm/ai/ai_pipeline.c
src/algorithm/ai/preprocess.c
src/algorithm/ai/postprocess.c
src/algorithm/ai/tracker.c
src/algorithm/calib/calibrator.c
src/application/event_loop.c
src/application/timeshift_cache.c
)
#=============================================================================
# 可执行文件
#=============================================================================
add_executable(rdk3_cam ${SOURCES})
target_link_libraries(rdk3_cam
${HB_SDK_LIBRARY}
${LIBYAML_LIBRARIES}
pthread
m
rt
z
)
#=============================================================================
# 安装
#=============================================================================
install(TARGETS rdk3_cam DESTINATION bin)
install(FILES config/product_cam.yaml DESTINATION etc)
install(FILES scripts/start.sh DESTINATION bin)
#=============================================================================
# 测试
#=============================================================================
enable_testing()
add_subdirectory(tests)
启动脚本 scripts/start.sh
#!/bin/bash
#=============================================================================
# RDK3 AI Camera 启动脚本
#=============================================================================
set -e
APP_NAME="rdk3_cam"
APP_PATH="/usr/bin/${APP_NAME}"
CONFIG_PATH="/etc/rdk3_cam.yaml"
LOG_PATH="/var/log/rdk3_cam"
PID_FILE="/var/run/rdk3_cam.pid"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查是否已运行
check_running() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
return 0
else
rm -f "$PID_FILE"
fi
fi
return 1
}
# 启动服务
start() {
if check_running; then
log_warn "Service already running (PID: $(cat $PID_FILE))"
return 1
fi
log_info "Starting $APP_NAME..."
# 创建日志目录
mkdir -p "$LOG_PATH"
# 设置核心转储
ulimit -c unlimited
# 启动进程
$APP_PATH -c "$CONFIG_PATH" >> "${LOG_PATH}/output.log" 2>&1 &
local pid=$!
echo $pid > "$PID_FILE"
sleep 2
if kill -0 "$pid" 2>/dev/null; then
log_info "Service started successfully (PID: $pid)"
else
log_error "Service failed to start"
rm -f "$PID_FILE"
return 1
fi
}
# 停止服务
stop() {
if ! check_running; then
log_warn "Service not running"
return 1
fi
local pid=$(cat "$PID_FILE")
log_info "Stopping $APP_NAME (PID: $pid)..."
kill -TERM "$pid"
# 等待进程退出
local count=0
while kill -0 "$pid" 2>/dev/null && [ $count -lt 30 ]; do
sleep 1
count=$((count + 1))
done
if kill -0 "$pid" 2>/dev/null; then
log_warn "Force killing..."
kill -9 "$pid"
fi
rm -f "$PID_FILE"
log_info "Service stopped"
}
# 重启服务
restart() {
stop
sleep 2
start
}
# 查看状态
status() {
if check_running; then
local pid=$(cat "$PID_FILE")
log_info "Service is running (PID: $pid)"
# 显示进程信息
ps -p "$pid" -o pid,ppid,cmd,%cpu,%mem,etime
else
log_warn "Service is not running"
fi
}
# 查看日志
logs() {
tail -f "${LOG_PATH}/output.log"
}
# 性能监控
perf() {
if ! check_running; then
log_error "Service not running"
return 1
fi
local pid=$(cat "$PID_FILE")
echo "=== RDK3 Camera Performance ==="
echo "PID: $pid"
echo ""
# CPU/内存使用
ps -p "$pid" -o %cpu,%mem,vsz,rss
echo ""
echo "=== Thread Info ==="
ps -T -p "$pid" -o pid,tid,comm,%cpu
echo ""
echo "=== Memory Map ==="
cat "/proc/$pid/smaps" | grep -E "^(Size|Rss|Pss|Shared)" | head -20
}
# 主入口
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
logs)
logs
;;
perf)
perf
;;
*)
echo "Usage: $0 {start|stop|restart|status|logs|perf}"
exit 1
;;
esac
exit 0
配置文件 config/product_cam.yaml
#============================================================================= # RDK3 AI Camera 产品配置文件 #============================================================================= # 视频配置 video: width: 1920 height: 1080 framerate: 30 format: "NV12" # NV12, RGB888, RAW10 sensor: "IMX415" mipi_lanes: 4 # AI配置 ai: enabled: true detect_model: "/data/models/yolov5s.hbm" classify_model: "/data/models/resnet18.hbm" track_model: "/data/models/reid.hbm" # 检测参数 confidence_threshold: 0.5 nms_threshold: 0.45 max_detections: 100 # 跟踪参数 tracking: enabled: true max_tracked_objects: 50 max_lost_frames: 30 iou_threshold: 0.3 # ISP调优配置 isp: tuning_file: "/etc/isp/imx415_tuning.bin" # 3A配置 ae: target_luminance: 60 min_luminance: 30 max_luminance: 200 pid_kp: 0.5 pid_ki: 0.1 pid_kd: 0.05 awb: mode: "greyworld" # greyworld, perfect_reflect max_gain: 3.0 af: mode: "hill_climbing" # hill_climbing, contrast lens_min: 10 lens_max: 1023 coarse_step: 50 fine_step: 5 focus_threshold: 100 # 时移缓存配置 timeshift: enabled: true capacity_seconds: 10 # 缓存10秒 recycle_threshold: 0.8 # 80%时开始回收 # 日志配置 logging: level: 3 # 0=FATAL,1=ERROR,2=WARN,3=INFO,4=DEBUG,5=TRACE output: 3 # bit0=console, bit1=file file: "/var/log/rdk3_cam.log" max_size_mb: 10 max_files: 3 # 网络配置 network: rtsp: enabled: true port: 554 mount_point: "/live" websocket: enabled: true port: 8080 mqtt: enabled: false broker: "mqtt://localhost:1883" topic: "rdk3/cam/events" # 系统配置 system: cma_pool_mb: 512 dma_pool_mb: 256 ai_tensor_pool_mb: 128 max_cpu_temp: 85 # 摄氏度 enable_health_check: true
总结
| 模块 | 文件 | 功能 |
|---|---|---|
| 主程序 | main.c |
模块集成、生命周期管理、信号处理 |
| 内存管理 | memory_manager.h/c |
CMA池、SLAB对象池、内存统计 |
| 日志系统 | logger.h/c |
异步日志、多输出目标、日志轮转 |
| 事件循环 | event_loop.h/c |
观察者模式、Reactor模式、优先级队列 |
| IPC通信 | ipc_bus.h/c |
双架构通信、无锁队列、共享内存 |
| ISP调优 | isp_tuning.h/c |
场景自适应、参数平滑、3A算法 |
| AI流水线 | ai_pipeline.h/c |
责任链模式、BPU推理、目标跟踪 |
| 时移缓存 | timeshift_cache.h/c |
环形缓存、时移回放 |
| 构建文件 | CMakeLists.txt |
编译配置、依赖管理 |
| 启动脚本 | start.sh |
服务管理、性能监控 |
| 配置文件 | product_cam.yaml |
产品参数配置 |
产品特性
-
完整的生命周期管理 - 从初始化到优雅退出
-
BPU硬件加速 - 地平线RDK3 SDK集成
-
双架构共存 - ARM Linux + ARM-None裸机通信
-
智能ISP调优 - 策略模式场景自适应
-
AI推理流水线 - 责任链模式处理
-
时移回放 - 环形缓存支持10秒回放
-
观察者事件驱动 - 松耦合模块通信
-
内存池管理 - 零碎片高效分配
-
异步日志系统 - 高性能日志记录
测试建议
运行测试:
# 编译 mkdir build && cd build cmake .. && make -j4 # 启动服务 ./rdk3_cam -c ../config/product_cam.yaml # 或使用脚本 sudo ./scripts/start.sh start # 查看状态 ./scripts/start.sh status # 查看日志 ./scripts/start.sh logs # 停止服务 ./scripts/start.sh stop
第二部分 地平线RDK3 AI摄像机产品部署与问题分析
一、产品部署流程图
┌─────────────────────────────────────────────────────────────────────────────┐ │ 部署阶段时间线 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ Week 1-2 Week 3-4 Week 5-6 Week 7-8 │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │硬件准备│──────▶│环境搭建│──────▶│驱动调试│──────▶│应用移植│ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │模型转换│──────▶│ISP调优│──────▶│联调测试│──────▶│量产发布│ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 详细部署步骤 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 硬件准备阶段 │ │ ├── RDK3开发板确认版本 (v1.2/v1.3) │ │ ├── IMX415传感器模组焊接与测试 │ │ ├── 电源管理验证 (5V/3A 纹波<50mV) │ │ └── 散热方案确认 (被动散热+导热硅脂) │ │ │ │ 2. 环境搭建阶段 │ │ ├── Ubuntu 20.04交叉编译环境 │ │ ├── SDK安装 (hb_sdk-1.8.0) │ │ ├── 依赖库编译 (opencv, yaml-cpp, protobuf) │ │ └── NFS/TFTP网络调试环境配置 │ │ │ │ 3. 驱动调试阶段 │ │ ├── U-Boot适配 (DDR参数校准) │ │ ├── Kernel DTS修改 (GPIO复用冲突) │ │ ├── 传感器驱动调试 (MIPI信号眼图) │ │ └── 双架构IPC验证 (RPMSG通信) │ │ │ │ 4. ISP调优阶段 │ │ ├── 黑电平校准 (不同增益下的BLC) │ │ ├── 镜头阴影校正 (LSC表生成) │ │ ├── 色彩校正矩阵 (CCM标定) │ │ └── 降噪与锐化平衡 │ │ │ │ 5. AI模型转换阶段 │ │ ├── PyTorch → ONNX → HBM转换 │ │ ├── 量化校准 (INT8精度损失评估) │ │ ├── 多核BPU负载分配 │ │ └── 推理延迟优化 (Pipeline并行) │ │ │ │ 6. 应用集成阶段 │ │ ├── 内存池大小调优 │ │ ├── 时移缓存环形队列验证 │ │ ├── 事件循环优先级测试 │ │ └── 异常恢复机制 (Watchdog) │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
二、遇到的主要问题树形分析
主要问题分类
│
├── 1. 硬件相关问题
│ ├── 1.1 电源纹波导致ISP图像条纹
│ │ ├── 现象:画面出现50Hz滚动横条纹
│ │ ├── 原因:传感器AVDD供电纹波>100mV
│ │ ├── 解决:增加LC滤波电路 + 软件启用内置DCDC
│ │ └── 验证:示波器测量纹波<30mV
│ │
│ ├── 1.2 DDR信号完整性问题
│ │ ├── 现象:高温60°C下随机死机
│ │ ├── 原因:Read/Write Leveling未优化
│ │ ├── 解决:使用地平线DDR训练工具重新校准
│ │ └── 验证:memtester 48小时压力测试
│ │
│ └── 1.3 MIPI信号眼图闭合
│ ├── 现象:高速模式(2.5Gbps)丢帧
│ ├── 原因:PCB走线等长误差>5mil
│ ├── 解决:降低速率至1.5Gbps + 软件重传机制
│ └── 验证:示波器眼图测试 + 误码率<1e-12
│
├── 2. 软件架构问题
│ ├── 2.1 双架构符号冲突
│ │ ├── 现象:链接器报multiple definition
│ │ ├── 原因:ARM-Linux和ARM-None的stdlib冲突
│ │ ├── 解决:隔离编译域 + 自定义链接脚本
│ │ └── 代码示例:
│ │ # ARM-None使用 -ffreestanding -nostdlib
│ │ # ARM-Linux使用 -lstdc++ -lgcc
│ │
│ ├── 2.2 IPC环形缓冲区假共享
│ │ ├── 现象:双核通信延迟抖动大(50us→500us)
│ │ ├── 原因:读写索引在同一缓存行
│ │ ├── 解决:__attribute__((aligned(64)))填充
│ │ └── 验证:perf c2c检测false sharing
│ │
│ ├── 2.3 内存池碎片化
│ │ ├── 现象:运行7天后malloc失败
│ │ ├── 原因:固定大小SLAB + 变长请求混用
│ │ ├── 解决:分离CMA池(大块)和SLAB池(小块)
│ │ └── 验证:valgrind massif堆分析
│ │
│ └── 2.4 观察者模式死锁
│ ├── 现象:事件循环随机卡死
│ ├── 原因:观察者回调中又publish事件
│ ├── 解决:异步队列 + 递归检测
│ └── 验证:helgrind线程竞争检测
│
├── 3. AI/ISP协同问题
│ ├── 3.1 ISP预处理导致AI精度下降
│ │ ├── 现象:暗光下检测率<60%
│ │ ├── 原因:ISP去噪过度抹除纹理
│ │ ├── 解决:双Pipeline (YUV编码 + RAW推理)
│ │ └── 验证:对比测试(原图vs处理后)
│ │
│ ├── 3.2 BPU与CPU共享DDR带宽冲突
│ │ ├── 现象:推理+编码同时执行掉帧
│ │ ├── 原因:DDR带宽争抢 (理论12GB/s)
│ │ ├── 解决:QoS优先级 + 帧间隔错峰
│ │ └── 验证:ddr_bwmon工具监控
│ │
│ └── 3.3 3A策略收敛慢
│ ├── 现象:场景切换后曝光闪烁5秒
│ ├── 原因:PID参数保守 + 帧率限制
│ ├── 解决:自适应PID + 前馈控制
│ └── 验证:录制视频逐帧分析亮度曲线
│
└── 4. 稳定性问题
├── 4.1 长时间运行内存泄漏
│ ├── 现象:RSS持续增长至OOM
│ ├── 原因:AI任务对象未正确释放
│ ├── 解决:引入对象池 + 泄漏检测
│ └── 验证:valgrind --leak-check=full
│
├── 4.2 时移缓存越界
│ ├── 现象:回放时随机Segmentation Fault
│ ├── 原因:环形队列头尾指针并发更新
│ ├── 解决:CAS原子操作 + 读写锁升级
│ └── 验证:ThreadSanitizer检测
│
└── 4.3 高温降频策略触发
├── 现象:温度>80°C后系统卡顿
├── 原因:CPU/BPU降频到800MHz
├── 解决:提前降帧 + 风扇控制
└── 验证:/sys/class/thermal监控
三、工具与调试手段
┌─────────────────────────────────────────────────────────────────────────────┐ │ 调试工具矩阵 │ ├──────────────┬─────────────────────┬───────────────────────────────────────┤ │ 类别 │ 工具 │ 使用场景 │ ├──────────────┼─────────────────────┼───────────────────────────────────────┤ │ 代码调试 │ gdb + gdb-multiarch │ 双架构核心转储分析 │ │ │ addr2line │ 崩溃地址反解 │ │ │ strace │ 系统调用追踪 │ ├──────────────┼─────────────────────┼───────────────────────────────────────┤ │ 内存分析 │ valgrind (memcheck) │ 内存泄漏/越界检测 │ │ │ heaptrack │ 堆内存分配追踪 │ │ │ mtrace │ glibc内存钩子 │ ├──────────────┼─────────────────────┼───────────────────────────────────────┤ │ 性能分析 │ perf │ CPU/BPU事件采样 │ │ │ hb_perf (地平线工具) │ BPU算子耗时分析 │ │ │ ftrace │ 内核函数追踪 │ ├──────────────┼─────────────────────┼───────────────────────────────────────┤ │ 并发调试 │ helgrind │ 线程竞争检测 │ │ │ drd │ 数据竞争检测 │ │ │ ThreadSanitizer │ 运行时线程问题 │ ├──────────────┼─────────────────────┼───────────────────────────────────────┤ │ ISP调优 │ hb_isp_tool │ 在线调整ISP参数 │ │ │ 3A调试工具 │ AE/AWB/AF曲线可视化 │ │ │ RAW数据抓取 │ 原始Bayer数据分析 │ ├──────────────┼─────────────────────┼───────────────────────────────────────┤ │ 网络调试 │ tcpdump │ RTSP流分析 │ │ │ iperf3 │ 网络带宽测试 │ │ │ Wireshark │ 协议分析 │ ├──────────────┼─────────────────────┼───────────────────────────────────────┤ │ 硬件调试 │ 示波器(1GHz+) │ MIPI/DDR信号测量 │ │ │ 逻辑分析仪 │ I2C/SPI时序分析 │ │ │ 万用表 │ 电源纹波测量 │ ├──────────────┼─────────────────────┼───────────────────────────────────────┤ │ 日志系统 │ syslog │ 系统日志收集 │ │ │ 自定义ringbuffer │ 崩溃前日志保存 │ │ │ 远程日志(udp) │ 无串口环境调试 │ └──────────────┴─────────────────────┴───────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 调试手段流程图 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 问题复现 │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 1. 最小化复现用例 │ │ │ │ - 剥离业务逻辑,只保留核心路径 │ │ │ │ - 固定输入数据(RAW图/脚本) │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 2. 添加调试桩 │ │ │ │ - 关键路径添加LOG_DEBUG │ │ │ │ - 断言检查(assert/invariant) │ │ │ │ - 性能计数器(perf counter) │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 3. 远程调试 │ │ │ │ - gdbserver + 交叉gdb │ │ │ │ - 核心转储分析(coredumpctl) │ │ │ │ - 反汇编(objdump -dS) │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 4. 压力测试 │ │ │ │ - 长时间运行(48-72小时) │ │ │ │ - 极端条件(高温/低温/振动) │ │ │ │ - 异常注入(断电/断网/信号干扰) │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 5. 根因分析 │ │ │ │ - git bisect定位引入提交 │ │ │ │ - 代码覆盖率(gcov/lcov) │ │ │ │ - 静态分析(cppcheck/clang-tidy) │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
四、修改频率最高的代码(热点分析)
┌─────────────────────────────────────────────────────────────────────────────┐ │ 代码修改频率热力图 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ████████████████████████████████████████ isp_tuning.c (35%) │ │ │ 修改原因:场景适应性差,需要反复调优 │ │ │ 主要改动: │ │ │ - 黑电平校正参数 (15次迭代) │ │ │ - 色彩矩阵系数 (12次) │ │ │ - 降噪强度曲线 (10次) │ │ │ - AE收敛速度 (8次) │ │ │ │ │ ██████████████████████████████████ ai_pipeline.c (28%) │ │ │ 修改原因:模型升级 + 精度优化 │ │ │ 主要改动: │ │ │ - 预处理归一化参数 (模型版本升级) │ │ │ - NMS阈值调整 (6次) │ │ │ - 跟踪器匹配算法 (4次) │ │ │ - 后处理解码优化 (3次) │ │ │ │ │ ██████████████████████████ memory_manager.c (18%) │ │ │ 修改原因:内存泄漏 + 碎片化 │ │ │ 主要改动: │ │ │ - 池大小调整 (8次) │ │ │ - 对齐策略修改 (5次) │ │ │ - 泄漏检测增强 (4次) │ │ │ │ │ ██████████████████ event_loop.c (12%) │ │ │ 修改原因:优先级调度 + 死锁修复 │ │ │ 主要改动: │ │ │ - 事件优先级调整 (6次) │ │ │ - 锁粒度优化 (4次) │ │ │ - 定时器精度改进 (3次) │ │ │ │ │ ██████████ ipc_bus.c (7%) │ │ │ 修改原因:双架构通信延迟 │ │ │ 主要改动: │ │ │ - 缓存行对齐 (3次) │ │ │ - 超时策略调整 (2次) │ │ │ - 重传机制 (2次) │ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 修改原因分类统计 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 功能迭代 (45%) │ │ ├── 客户需求变更 (20%) │ │ ├── 场景适应性 (15%) │ │ └── 新功能添加 (10%) │ │ │ │ 缺陷修复 (30%) │ │ ├── 内存问题 (12%) │ │ ├── 并发问题 (10%) │ │ └── 边界条件 (8%) │ │ │ │ 性能优化 (15%) │ │ ├── 延迟优化 (8%) │ │ ├── 吞吐量提升 (5%) │ │ └── 功耗优化 (2%) │ │ │ │ 平台适配 (10%) │ │ ├── SDK版本升级 (6%) │ │ ├── 硬件改版 (3%) │ │ └── 工具链更新 (1%) │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
五、产品演进感悟树形分析
产品演进感悟 │ ├── 1. 架构设计层面 │ ├── 1.1 模块化重要性 │ │ ├── 教训:初期耦合度过高,ISP调优牵一发动全身 │ │ ├── 改进:定义清晰接口(isp_ops/ai_ops) │ │ └── 效果:V2.0版本模块替换工作量减少70% │ │ │ ├── 1.2 配置外置化 │ │ ├── 教训:硬编码参数导致每次调优需重新编译 │ │ ├── 改进:YAML配置文件 + 热加载 │ │ └── 效果:调优周期从2天缩短到2小时 │ │ │ ├── 1.3 可观测性设计 │ │ ├── 教训:线上问题无法复现时束手无策 │ │ ├── 改进:结构化日志 + 指标暴露(Prometheus) │ │ └── 效果:MTTR从4小时降至30分钟 │ │ │ └── 1.4 防御性编程 │ ├── 教训:单点故障导致系统崩溃 │ ├── 改进:Watchdog + 降级策略 + 热备 │ └── 效果:可用性从99.5%提升到99.95% │ ├── 2. 开发流程层面 │ ├── 2.1 持续集成 │ │ ├── 初期:手动测试,发布周期2周 │ │ ├── 改进:自动化测试(单元+集成+压力) │ │ └── 当前:CI触发,发布周期1天 │ │ │ ├── 2.2 代码审查 │ │ ├── 教训:低级错误重复出现 │ │ ├── 改进:强制Review + CheckList │ │ └── 效果:缺陷率降低60% │ │ │ ├── 2.3 文档同步 │ │ ├── 教训:文档滞后代码,知识流失 │ │ ├── 改进:Doxygen + 架构决策记录(ADR) │ │ └── 效果:新人上手时间从4周减至1周 │ │ │ └── 2.4 版本管理 │ ├── 教训:紧急修复引入新问题 │ ├── 改进:Git Flow + 语义化版本 │ └── 效果:回滚次数减少80% │ ├── 3. 调试方法论 │ ├── 3.1 从现象到根因 │ │ ├── 经验:不要相信"不可能"的问题 │ │ ├── 案例:IPC延迟抖动→缓存行假共享 │ │ └── 方法:5 Whys分析法 │ │ │ ├── 3.2 数据驱动调试 │ │ ├── 经验:加日志比猜原因更高效 │ │ ├── 案例:内存泄漏→valgrind定位 │ │ └── 方法:先量化,再优化 │ │ │ ├── 3.3 自动化测试 │ │ ├── 经验:手动测试覆盖不全 │ │ ├── 案例:并发bug只在压力下出现 │ │ └── 方法:混沌工程 + 模糊测试 │ │ │ └── 3.4 工具链熟练度 │ ├── 经验:掌握工具事半功倍 │ ├── 案例:perf发现BPU空闲等待 │ └── 方法:工具卡组(cheatsheet) │ └── 4. 团队协作层面 ├── 4.1 知识共享 │ ├── 教训:模块隔离导致重复造轮子 │ ├── 改进:技术分享会 + Wiki │ └── 效果:代码复用率提升40% │ ├── 4.2 问题复盘 │ ├── 教训:同一问题反复出现 │ ├── 改进:RCA报告 + 措施跟踪 │ └── 效果:重犯问题归零 │ ├── 4.3 需求管理 │ ├── 教训:需求变更导致返工 │ ├── 改进:接口先行 + 原型验证 │ └── 效果:返工成本降低50% │ └── 4.4 技术债务 ├── 教训:妥协方案积累成技术债务 ├── 改进:定期重构 + 债务评估 └── 效果:维护成本逐年下降
六、关键经验总结
┌─────────────────────────────────────────────────────────────────────────────┐ │ 核心经验教训 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ✅ DO - 应该做的 │ │ ├── 1. 内存池化管理:预分配+复用,避免运行时分配 │ │ ├── 2. 异步日志系统:独立线程+环形缓冲,不影响主流程 │ │ ├── 3. 观察者模式:解耦事件源与处理器,易于扩展 │ │ ├── 4. 配置外置:所有可调参数放入配置文件 │ │ ├── 5. 健康检查:定期上报CPU/内存/温度,预警机制 │ │ ├── 6. 版本兼容:API版本号,平滑升级 │ │ └── 7. 压力测试:发布前48小时稳定性测试 │ │ │ │ ❌ DON'T - 避免做的 │ │ ├── 1. 硬编码参数:调优痛苦,重新编译 │ │ ├── 2. 过度优化:牺牲可读性的微优化 │ │ ├── 3. 忽略边界:空指针/数组越界/除零 │ │ ├── 4. 全局变量泛滥:状态不可预测 │ │ ├── 5. 长函数(>200行):难以理解和测试 │ │ ├── 6. 魔法数字:晦涩难懂 │ │ └── 7. 死代码:增加维护负担 │ │ │ ├─────────────────────────────────────────────────────────────────────────────┤ │ 量化成果 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 指标 V1.0 V2.0 改善 │ │ ─────────────────────────────────────────────────────────────────────── │ │ 启动时间(s) 8.5 3.2 -62% │ │ 内存占用(MB) 512 328 -36% │ │ 推理延迟(ms) 45 28 -38% │ │ CPU使用率(%) 85 52 -39% │ │ MTBF(小时) 72 500+ +594% │ │ 代码行数 15000 8500 -43% │ │ 单元覆盖率(%) 20 78 +290% │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
"嵌入式AI产品不是简单地把模型跑起来,而是硬件、软件、算法的系统工程。最大的感悟是:先跑通,再跑稳,最后跑优。“
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)