文章目录
- 说明文档
- 框架图
- 介绍
-
- [RV1106 IPC 流程图](#RV1106 IPC 流程图)
- [编译 rkipc (由 RK_APP_TYPE=RKIPC_RV1106 决定)](#编译 rkipc (由 RK_APP_TYPE=RKIPC_RV1106 决定))
- [rkipc 上电时的参数(功能动态)](#rkipc 上电时的参数(功能动态))
- 代码结构
- rv1106_ipc的主程序分析注释
说明文档
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; // 程序正常退出
}
