【RV1106】rkipc:分析(一)

文章目录

说明文档

  • rkipc/docs/Rockchip_Developer_Guide_Linux_RKIPC_CN.md

框架图

介绍

bash 复制代码
| 源码目录    |    依赖外部库 | 功能                                                         |
| ---------- | ------------- | ------------------------------------------------------------ |
| rv1106_ipc | rockit、rkaiq | 针对RV1106平台的IPC产品,支持网页和rtsp/rtmp预览,参数动态修改,关闭卷绕。 |

RV1106 IPC 流程图

bind
get
send
bind
get and sned
bind
AI
AENC
MUXER
MP4
VI_0
VENC_0_and_OSD_draw_NN_result
TDE_process
VENC_JPEG
RTSP_RTMP_0
VI_1
VENC_1
RTSP_RTMP_1
VI_2
NPU
IVS_for_OD_MD

编译 rkipc (由 RK_APP_TYPE=RKIPC_RV1106 决定)

  • 仅编译app: ./build.sh app
  • 仅清空app: ./build.sh clean app 会删除掉两处 app_out 文件夹。
bash 复制代码
function build_app() {
	check_config RK_APP_TYPE || return 0

	echo "============Start building app============"
	echo "TARGET_APP_CONFIG=$RK_APP_DEFCONFIG $RK_APP_DEFCONFIG_FRAGMENT $RK_APP_TYPE"
	echo "========================================="

	build_meta --export --media_dir $RK_PROJECT_PATH_MEDIA
	test -d ${SDK_APP_DIR} && make -C ${SDK_APP_DIR}

	finish_build
}

RK_APP_TYPE 决定了编译的APP,这个在 BoardConfig.mk文件里面有定义

bash 复制代码
#################################################
# 	Board Config
#################################################
export LF_ORIGIN_BOARD_CONFIG=BoardConfig-EMMC-Buildroot-RV1106_Luckfox_Pico_Ultra_W-IPC.mk
# Target CHIP
export RK_CHIP=rv1106

# app config
export RK_APP_TYPE=RKIPC_RV1106

rkipc 上电时的参数(功能动态)

参数系统函数分析

  • 参数系统初始化:int rk_param_init(char *ini_path);
  • 获取参数:rk_param_get_int()
c 复制代码
int rk_param_init(char *ini_path) {
  LOG_INFO("%s\n", __func__);
  pthread_mutex_lock(&g_param_mutex);
  g_ini_d_ = NULL;
  if (ini_path)
    memcpy(g_ini_path_, ini_path, strlen(ini_path));
  else
    memcpy(g_ini_path_, "/usr/share/rkuvc.ini", strlen("/usr/share/rkuvc.ini"));
  LOG_INFO("g_ini_path_ is %s\n", g_ini_path_);

  g_ini_d_ = iniparser_load(g_ini_path_);
  if (g_ini_d_ == NULL) {
    LOG_ERROR("iniparser_load error!\n");
    return -1;
  }
  rk_param_dump();
  pthread_mutex_unlock(&g_param_mutex);

  return 0;
}
c 复制代码
const char * dictionary_get(const dictionary * d, const char * key, const char * def)
{
    unsigned    hash ;
    ssize_t      i ;

    hash = dictionary_hash(key);
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        /* Compare hash */
        if (hash==d->hash[i]) {
            /* Compare string, to avoid hash collisions */
            if (!strcmp(key, d->key[i])) {
                return d->val[i] ;
            }
        }
    }
    return def ;
}
...
static const char * strlwc(const char * in, char *out, unsigned len)
{
    unsigned i ;

    if (in==NULL || out == NULL || len==0) return NULL ;
    i=0 ;
    while (in[i] != '\0' && i < len-1) {
        out[i] = (char)tolower((int)in[i]);
        i++ ;
    }
    out[i] = '\0';
    return out ;
}

...
const char * iniparser_getstring(const dictionary * d, const char * key, const char * def)
{
    const char * lc_key ;
    const char * sval ;
    char tmp_str[ASCIILINESZ+1];

    if (d==NULL || key==NULL)
        return def ;

    lc_key = strlwc(key, tmp_str, sizeof(tmp_str));
    sval = dictionary_get(d, lc_key, def);
    return sval ;
}

...
long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound)
{
    const char * str ;

    str = iniparser_getstring(d, key, INI_INVALID_KEY);
    if (str==INI_INVALID_KEY) return notfound ;
    return strtol(str, NULL, 0);
}
...
int iniparser_getint(const dictionary * d, const char * key, int notfound)
{
    return (int)iniparser_getlongint(d, key, notfound);
}
...

int rk_param_get_int(const char *entry, int default_val) {
  int ret;
  pthread_mutex_lock(&g_param_mutex);
  ret = iniparser_getint(g_ini_d_, entry, default_val);
  pthread_mutex_unlock(&g_param_mutex);

  return ret;
}

实例:rk_param_get_int("video.source:enable_ivs", 0);

xxx.ini文件
当程序读取时,你的代码就会从 video.source 段中找到 enable_ivs,并返回其值 。如果找不到,则返回默认值 0。

上电时的打印系统:

c 复制代码
[rkipc.c][main]:rkipc_ini_path_ is (null), rkipc_iq_file_path_ is (null), rkipc_log_level is 3
[param.c][rk_param_init]:rk_param_init
[param.c][rk_param_init]:g_ini_path_ is /userdata/rkipc.ini

源码:

c 复制代码
char *rkipc_ini_path_ = NULL;

...
	LOG_INFO("rkipc_ini_path_ is %s, rkipc_iq_file_path_ is %s, rkipc_log_level "
	         "is %d\n",
	         rkipc_ini_path_, rkipc_iq_file_path_, rkipc_log_level);

	// init
	rk_param_init(rkipc_ini_path_);
...

确定了上电时加载的参数系统ini文件为:/userdata/rkipc.ini

代码结构

bash 复制代码
├── CMakeLists.txt
├── common # 通用模块
│   ├── common.h # 一些通用函数功能
│   ├── event # 事件处理模块
│   ├── isp # 图像处理模块
│   │   ├── rk3588
│   │   ├── rv1106
│   │   └── rv1126
│   ├── log.h # 日志管理
│   ├── network # 网络模块
│   ├── osd # OSD模块
│   │   ├── image.bmp # logo图片
│   │   └── simsun_en.ttf # 字体库
│   ├── param # 参数管理模块
│   ├── rkbar # 二维码识别模块
│   ├── rockiva # 周界算法模块,人脸人形识别
│   ├── rtmp # rtmp推流模块
│   ├── rtsp # rtsp推流模块
│   ├── storage # 存储模块
│   └── system # 系统管理模块
│   └── tuya_ipc # 涂鸦IPC模块
├── format.sh # 格式化脚本
├── lib # 存放32/64位版本,不同工具链的预编译库
│   ├── aarch64-rockchip1031-linux-gnu
│   └── arm-rockchip830-linux-gnueabihf
├── LICENSE # 版权声明
└── src
    ├── low_memory_ipc
    ├── rk3588_ipc
    │   ├── audio # 音频业务逻辑
    │   ├── CMakeLists.txt
    │   ├── main.c
    │   ├── rkipc.ini # 参数文件
    │   ├── server # socket服务端
    │   └── video # 视频业务逻辑
    │       ├── video.c
    │       └── video.h
    ├── rk3588_muliti_ipc
    ├── rv1126_battery_ipc
    ├── rv1126_ipc_rkmedia
    ├── rv1126_ipc_rockit
    └── rv1126_snapshot

rv1106_ipc的主程序分析注释

c 复制代码
// 包含头文件
#include <getopt.h>           // 命令行参数解析
#include "audio.h"           // 音频处理模块
#include "common.h"          // 公共定义
#include "isp.h"             // 图像信号处理模块(Image Signal Processor)
#include "log.h"             // 日志模块
#include "network.h"         // 网络模块
#include "param.h"           // 参数管理模块
#include "rockiva.h"         // 瑞芯微AI视觉分析模块
#include "server.h"          // 服务器/服务模块
#include "storage.h"         // 存储模块(SD卡/NAS等)
#include "system.h"          // 系统管理模块
#include "video.h"           // 视频处理模块
#include <linux/input.h>     // Linux输入子系统(用于按键检测)

// 重新定义日志标签
#ifdef LOG_TAG
#undef LOG_TAG
#endif
#define LOG_TAG "rkipc.c"   // 标识这是IPC主程序的日志

// 定义日志级别枚举
enum { LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG };

// 全局变量定义
int enable_minilog = 0;                 // 迷你日志使能标志(未使用)
int rkipc_log_level = LOG_INFO;        // 全局日志级别,默认INFO
static int g_main_run_ = 1;            // 主循环运行标志,控制程序退出
char *rkipc_ini_path_ = NULL;          // 配置文件路径指针
char *rkipc_iq_file_path_ = NULL;      // IQ调校文件路径指针

// 信号处理函数 - 用于优雅退出
static void sig_proc(int signo) {
    LOG_INFO("received signo %d \n", signo);  // 打印收到的信号
    g_main_run_ = 0;  // 设置退出标志,通知主循环结束
}

// 命令行参数定义
static const char short_options[] = "c:a:l:";  // 短参数:-c -a -l
static const struct option long_options[] = {  // 长参数定义
    {"config", required_argument, NULL, 'c'},      // 配置文件路径
    {"aiq_file", no_argument, NULL, 'a'},          // AIQ文件目录
    {"log_level", no_argument, NULL, 'l'},         // 日志级别
    {"help", no_argument, NULL, 'h'},              // 帮助信息
    {0, 0, 0, 0}                                   // 结束标记
};

// 使用说明提示函数
static void usage_tip(FILE *fp, int argc, char **argv) {
    fprintf(fp,
            "Usage: %s [options]\n"                     // 用法
            "Version %s\n"                             // 版本号
            "Options:\n"
            "-c | --config      rkipc ini file, default is "  // 配置文件
            "/userdata/rkipc.ini, need to be writable\n"
            "-a | --aiq_file    aiq file dir path, default is /etc/iqfiles\n" // IQ文件路径
            "-l | --log_level   log_level [0/1/2/3], default is 2\n"  // 日志级别
            "-h | --help        for help \n\n"          // 帮助
            "\n",
            argv[0], "V1.0");                           // 程序名和版本
}

// 命令行参数解析函数
void rkipc_get_opt(int argc, char *argv[]) {
    for (;;) {  // 无限循环,直到所有参数解析完毕
        int idx;
        int c;
        c = getopt_long(argc, argv, short_options, long_options, &idx);
        if (-1 == c)  // getopt_long返回-1表示解析完成
            break;
        switch (c) {
        case 0: /* getopt_long() flag */
            break;
        case 'c':  // 处理-c参数:配置文件路径
            rkipc_ini_path_ = optarg;  // optarg指向参数值
            break;
        case 'a':  // 处理-a参数:IQ文件路径
            rkipc_iq_file_path_ = optarg;
            break;
        case 'l':  // 处理-l参数:日志级别
            rkipc_log_level = atoi(optarg);  // 字符串转整数
            break;
        case 'h':  // 处理-h参数:帮助信息
            usage_tip(stdout, argc, argv);
            exit(EXIT_SUCCESS);  // 显示帮助后直接退出
        default:   // 未知参数
            usage_tip(stderr, argc, argv);
            exit(EXIT_FAILURE);  // 显示错误用法后退出
        }
    }
}

// 音频文件读取缓冲区大小(4KB)
#define AO_FREAD_SIZE 1024 * 4

// 按键事件监听线程函数
static void *wait_key_event(void *arg) {
    int key_fd;
    // 打开输入设备(通常是硬件按键)
    key_fd = open("/dev/input/event0", O_RDONLY);
    if (key_fd < 0) {
        LOG_ERROR("can't open /dev/input/event0\n");
        return NULL;
    }
    
    // 文件描述符集合和超时设置
    fd_set rfds;
    int nfds = key_fd + 1;
    struct timeval timeout;
    struct input_event key_event;  // 输入事件结构体
    
    // 主监听循环
    while (g_main_run_) {
        // rfds集合必须每次清空,否则无法检测描述符变化
        timeout.tv_sec = 1;  // 1秒超时
        FD_ZERO(&rfds);      // 清空文件描述符集合
        FD_SET(key_fd, &rfds);  // 将按键设备描述符加入集合
        
        // 使用select监听输入事件
        select(nfds, &rfds, NULL, NULL, &timeout);
        
        // 检查是否有按键事件发生
        if (FD_ISSET(key_fd, &rfds)) {
            // 读取按键事件
            read(key_fd, &key_event, sizeof(key_event));
            LOG_INFO("[timeval:sec:%d,usec:%d,type:%d,code:%d,value:%d]\n", 
                    key_event.time.tv_sec, key_event.time.tv_usec, 
                    key_event.type, key_event.code, key_event.value);
            
            // 处理音量减键(硬件测试功能)
            if ((key_event.code == KEY_VOLUMEDOWN) && key_event.value) {
                LOG_INFO("get KEY_VOLUMEDOWN\n");
                
                // 音频系统初始化
                rkipc_ao_init();
                
                // 打开测试音频文件
                FILE *fp = fopen("/oem/usr/share/speaker_test.wav", "rb");
                int size = AO_FREAD_SIZE;
                char *tmp_data;
                tmp_data = malloc(AO_FREAD_SIZE);  // 分配缓冲区
                
                // 读取并播放音频文件
                while (size > 0) {
                    memset((void *)tmp_data, 0, AO_FREAD_SIZE);  // 清空缓冲区
                    size = fread(tmp_data, 1, AO_FREAD_SIZE, fp);  // 读取音频数据
                    rkipc_ao_write(tmp_data, AO_FREAD_SIZE);  // 写入音频输出
                }
                
                // 发送结束标记(0长度)
                rkipc_ao_write(tmp_data, 0);
                
                // 清理资源
                free(tmp_data);
                fclose(fp);
                rkipc_ao_deinit();  // 音频系统反初始化
            }
            
            // 处理音量加键(预留功能)
            if ((key_event.code == KEY_VOLUMEUP) && key_event.value) {
                LOG_INFO("get KEY_VOLUMEUP\n");
                // 这里可以添加其他功能,如拍照、录像等
            }
        }
    }
    
    // 清理资源
    if (key_fd) {
        close(key_fd);
        key_fd = 0;
    }
    LOG_DEBUG("wait key event out\n");
    return NULL;
}

// 主函数 - 程序入口点
int main(int argc, char **argv) {
    pthread_t key_chk;  // 按键监听线程句柄
    
    LOG_DEBUG("main begin\n");
    rkipc_version_dump();  // 打印版本信息
    
    // 注册信号处理函数
    signal(SIGINT, sig_proc);   // Ctrl+C中断信号
    signal(SIGTERM, sig_proc);  // 终止信号
    
    // 解析命令行参数
    rkipc_get_opt(argc, argv);
    LOG_INFO("rkipc_ini_path_ is %s, rkipc_iq_file_path_ is %s, rkipc_log_level is %d\n",
             rkipc_ini_path_, rkipc_iq_file_path_, rkipc_log_level);
    
    // ==================== 初始化阶段 ====================
    // 1. 参数系统初始化(读取配置文件)
    rk_param_init(rkipc_ini_path_);
    
    // 2. 网络模块初始化
    rk_network_init(NULL);
    
    // 3. 系统模块初始化
    rk_system_init();
    
    // 4. AI视觉模块初始化(根据配置决定是否启用)
    if (rk_param_get_int("video.source:enable_npu", 0))
        rkipc_rockiva_init();
    
    // 5. ISP图像处理模块初始化(根据配置决定是否启用AIQ)
    if (rk_param_get_int("video.source:enable_aiq", 1)) {
        rk_isp_init(0, rkipc_iq_file_path_);  // 初始化ISP,传入IQ文件路径
        if (rk_param_get_int("isp:init_form_ini", 1))
            rk_isp_set_from_ini(0);  // 从INI文件加载ISP参数
    }
    
    // 6. 瑞芯微MPI系统初始化(媒体处理接口)
    RK_MPI_SYS_Init();
    
    // 7. 视频模块初始化
    rk_video_init();
    
    // 8. 音频模块初始化(根据配置决定是否启用)
    if (rk_param_get_int("audio.0:enable", 0))
        rkipc_audio_init();
    
    // 9. 服务器模块初始化(Web服务/RTSP服务等)
    rkipc_server_init();
    
    // 10. 存储模块初始化(SD卡录像等)
    rk_storage_init();
    
    // 11. 创建按键监听线程
    pthread_create(&key_chk, NULL, wait_key_event, NULL);
    
    // ==================== 主运行循环 ====================
    // 简单的主循环,每秒检查一次退出标志
    while (g_main_run_) {
        usleep(1000 * 1000);  // 睡眠1秒(1000毫秒)
        // 注意:这里可以添加其他周期性任务,如状态检查、心跳包等
    }
    
    // ==================== 反初始化阶段(逆序) ====================
    // 1. 等待按键线程结束
    pthread_join(key_chk, NULL);
    
    // 2. 存储模块反初始化
    rk_storage_deinit();
    
    // 3. 服务器模块反初始化
    rkipc_server_deinit();
    
    // 4. 系统模块反初始化
    rk_system_deinit();
    
    // 5. 视频模块反初始化
    rk_video_deinit();
    
    // 6. ISP模块反初始化(如果之前初始化了)
    if (rk_param_get_int("video.source:enable_aiq", 1))
        rk_isp_deinit(0);
    
    // 7. 音频模块反初始化(如果之前初始化了)
    if (rk_param_get_int("audio.0:enable", 0))
        rkipc_audio_deinit();
    
    // 8. 瑞芯微MPI系统退出
    RK_MPI_SYS_Exit();
    
    // 9. AI视觉模块反初始化(如果之前初始化了)
    if (rk_param_get_int("video.source:enable_npu", 0))
        rkipc_rockiva_deinit();
    
    // 10. 网络模块反初始化
    rk_network_deinit();
    
    // 11. 参数系统反初始化
    rk_param_deinit();
    
    return 0;  // 程序正常退出
}
相关推荐
微帧Visionular2 小时前
AI视频单帧清晰,播放时却闪烁,原因是什么?
人工智能·深度学习·音视频
辰辰观影3 小时前
Windows网盘视频播放器推荐:无广告4K HDR怎么选
音视频
2601_966827455 小时前
电视端云盘在线播放怎么选?支持4K HDR的应用推荐
音视频
开开心心就好6 小时前
音频降噪剪辑工具,音量裁剪一键处理
运维·服务器·前端·spring·ocr·音视频·1024程序员节
AI视频工具指南9 小时前
AI视频的声音和画面怎么配合?先分清谁在传递信息
人工智能·aigc·音视频
辰辰观影12 小时前
无广告播放器推荐来啦
音视频
TrueDei12 小时前
GStreamer 使用 valve 分别控制音视频流的暂停与恢复
音视频
一条破秋裤13 小时前
基于 Hi3516CV610 的立定跳远视频测量系统调研与实施方案
arm开发·音视频
2601_9668274513 小时前
视频里的特效字幕怎么翻译?外挂字幕与AI字幕一键处理教程
音视频
八月的雨季 最後的冰吻14 小时前
音视频面试--延迟优化
面试·职场和发展·音视频