Files
oak-editor/docs/zh/modularization-plan/11-ipc-protocol.md
T

18 KiB
Raw Blame History

IPC 协议规范

本文件定义主进程与 olive-renderer 子进程之间的全部通信方式。由于采用**"用完即弃"模型,协议被设计为极简、无状态、单向请求-响应**。


1. 通信通道概览

通道 方向 用途 格式
命令行参数 主进程 → 子进程 传递渲染配置(模式、路径、参数) POSIX 风格长选项
stdin 主进程 → 子进程 传递节点图 XML(当 XML 过大超出命令行长度限制时) 原始 XML 字符串
stdout 子进程 → 主进程 返回渲染结果元数据 单行 JSON
stderr 子进程 → 主进程 日志和详细错误信息 纯文本
共享内存 / 内存映射文件 双向 传输大帧数据(像素/采样) 二进制(ShmHeader + 原始数据)
退出码 子进程 → 主进程 快速判断成功/失败/异常 整数 (0255)

重要:由于子进程渲染完成后立即退出,不存在长期状态同步心跳取消信号(主进程直接 kill 即可)。


2. 命令行参数

2.1 参数总表

参数 类型 必需 默认值 说明
--mode string frame 渲染模式:frame, batch, audio
--node-graph path 节点图 XML 文件路径
--node-graph-stdin flag false 从 stdin 读取节点图 XML,而非文件
--output-node string 自动探测 输出节点 ID
--time rational 条件 单帧时间点(--mode=frame 时必需)
--times csv 条件 多帧时间点列表(--mode=batch 时必需)
--start rational 条件 音频起始时间(--mode=audio 时必需)
--duration rational 条件 音频持续时间(--mode=audio 时必需)
--video-params json 条件 视频参数(--mode=frame/batch 时必需)
--audio-params json 条件 音频参数(--mode=audio 时必需)
--color-ref string 参考色彩空间名称
--color-display string 显示色彩空间名称
--force-size json 强制输出尺寸,如 {"width":1920,"height":1080}
--force-format string 强制像素格式,如 rgba32f
--output-shm string 输出共享内存名称或临时文件路径
--output-shm-size int 输出缓冲区大小(字节)
--output-stdout flag false 将帧数据 base64 编码输出到 stdout(仅小帧/测试)
--backend string opengl 渲染后端:opengl, dummy
--shader-path path <exe_dir>/shaders 着色器资源目录
--ocio-config path OCIO 配置文件路径
--verbose flag false 详细日志输出到 stderr
--version flag false 输出版本信息并退出
--help flag false 输出帮助信息并退出

2.2 参数值格式

Rational"<numerator>/<denominator>",如 "1001/30000", "0/1"

JSON:紧凑格式,键用双引号。例如:

--video-params='{"width":1920,"height":1080,"format":"rgba32f","channel_count":4,"depth":1}'

CSV:逗号分隔的有理数字符串。例如:

--times="0/24,1/24,2/24,3/24,4/24"

3. 标准输入(stdin

--node-graph-stdin 标志存在时,子进程从 stdin 读取节点图 XML,而不是从 --node-graph 指定的文件。

3.1 使用场景

  • 节点图 XML 非常大(> 100KB),超出命令行长度限制。
  • 主进程不想在磁盘上创建临时文件。
  • 安全考虑:敏感项目数据不写入磁盘。

3.2 协议

主进程          子进程
  │               │
  │── XML 数据 ──>│(子进程读取 stdin 直到 EOF)
  │               │

子进程读取 stdin 的全部内容,视为节点图 XML 字符串。XML 结束后不需要特殊分隔符(EOF 即结束)。

注意:由于子进程使用 QCoreApplication 且不使用 Qt 的事件循环读取 stdin,应使用阻塞式 QTextStream(stdin).readAll()std::cin


4. 标准输出(stdout

子进程将渲染结果以单行 JSON 输出到 stdout,以换行符 \n 结尾。主进程读取第一行后即视为响应完成。

4.1 单帧模式输出(--mode=frame

成功:

{
  "status": "ok",
  "mode": "frame",
  "time": "1001/30000",
  "width": 1920,
  "height": 1080,
  "format": "rgba32f",
  "pixel_format_id": 28,
  "channel_count": 4,
  "shm_name": "/olive_frame_abc123",
  "data_offset": 256,
  "data_size": 33177600,
  "linesize": 7680,
  "render_time_ms": 42
}

失败:

{
  "status": "error",
  "mode": "frame",
  "time": "1001/30000",
  "error_code": "decoder_failure",
  "message": "Failed to open decoder for footage 'clip001.mp4'",
  "render_time_ms": 5
}

4.2 批处理模式输出(--mode=batch

成功:

{
  "status": "ok",
  "mode": "batch",
  "frame_count": 5,
  "frames": [
    {"time": "0/24", "data_offset": 256, "data_size": 33177600, "render_time_ms": 45},
    {"time": "1/24", "data_offset": 33178056, "data_size": 33177600, "render_time_ms": 38},
    {"time": "2/24", "data_offset": 66356112, "data_size": 33177600, "render_time_ms": 41},
    {"time": "3/24", "data_offset": 99534168, "data_size": 33177600, "render_time_ms": 39},
    {"time": "4/24", "data_offset": 132712224, "data_size": 33177600, "render_time_ms": 42}
  ],
  "total_render_time_ms": 205
}

4.3 音频模式输出(--mode=audio

成功:

{
  "status": "ok",
  "mode": "audio",
  "start": "0/1",
  "duration": "48000/48000",
  "sample_rate": 48000,
  "channels": 2,
  "sample_count": 48000,
  "shm_name": "/olive_audio_abc123",
  "data_offset": 256,
  "data_size": 384000,
  "render_time_ms": 15
}

4.4 字段说明

字段 类型 出现条件 说明
status string 总是 "ok", "error", "cancelled"
mode string 总是 "frame", "batch", "audio"
time / times string frame/batch 渲染时间点
width int frame/batch 帧宽
height int frame/batch 帧高
format string frame/batch 像素格式名称
pixel_format_id int frame/batch 像素格式枚举值
channel_count int frame/batch 通道数
shm_name string 总是 共享内存名称
data_offset int 总是 实际数据在共享内存中的偏移(跳过 ShmHeader)
data_size int 总是 实际数据大小(字节)
linesize int frame/batch 每行字节数(含 padding
frame_count int batch 批处理帧数
frames array batch 每帧的元数据
sample_rate int audio 采样率
channels int audio 通道数
sample_count int audio 采样数
error_code string error 错误分类码
message string error 人类可读错误信息
render_time_ms int 总是 纯渲染耗时(不含进程启动)

5. 共享内存二进制布局

5.1 整体结构

┌─────────────────────────────────────────────────────────────────┐
│                        共享内存区域                              │
├────────────────────────┬────────────────────────────────────────┤
│    ShmHeader (256 B)   │            Payload Data                │
│                        │                                        │
│  magic                 │  帧像素数据 / 音频采样数据              │
│  version               │                                        │
│  data_offset           │  大小 = data_size                      │
│  data_size             │                                        │
│  width                 │                                        │
│  height                │                                        │
│  format                │                                        │
│  linesize              │                                        │
│  checksum              │                                        │
│  reserved[...]         │                                        │
└────────────────────────┴────────────────────────────────────────┘

5.2 ShmHeader 定义(C 结构)

#include <stdint.h>

#define OLIVE_SHM_MAGIC     0x4F4C4956  // 'OLIV' 大端序
#define OLIVE_SHM_VERSION   1
#define OLIVE_SHM_HEADER_SIZE 256

typedef struct {
    uint32_t magic;           // OLIVE_SHM_MAGIC
    uint32_t version;         // OLIVE_SHM_VERSION
    uint32_t data_offset;     // Payload 数据起始偏移(>= 256
    uint64_t data_size;       // Payload 实际数据大小(字节)
    uint32_t width;           // 帧宽 或 采样数
    uint32_t height;          // 帧高 或 0(音频)
    uint32_t depth;           // 3D 纹理深度(通常 1
    uint32_t channel_count;   // 通道数
    uint32_t pixel_format;    // OlivePixelFormat 枚举值
    uint32_t linesize;        // 每行字节数(视频)或 0(音频)
    uint64_t checksum;        // CRC64 校验和(可选,0 表示未校验)
    uint8_t  reserved[256 - 48];  // 填充至 256 字节,未来扩展用
} OliveShmHeader;

// 辅助:计算 CRC64
uint64_t olive_shm_checksum(const void* data, size_t size);

5.3 校验和(Checksum

  • 默认启用 CRC64 校验。
  • checksum 字段覆盖 Payload Data 区域(从 data_offset 开始的 data_size 字节)。
  • 若主进程设置 checksum = 0,表示跳过校验(用于调试或性能敏感场景)。

5.4 多帧批处理布局

批处理模式下,所有帧连续存储在同一块共享内存中:

Offset 0:      ShmHeader (256 B)
Offset 256:    Frame 0 data (frame_0_size B, 按 linesize 对齐)
Offset 256+N0: Frame 1 data (frame_1_size B)
Offset 256+N0+N1: Frame 2 data
...

每帧的 data_offset 在 stdout JSON 中单独指定。


6. 标准错误(stderr

6.1 日志级别

--verbose 启用时,stderr 输出结构化日志:

[2024-05-21T10:30:15.123Z] [INFO] 初始化 OpenGL 上下文
[2024-05-21T10:30:15.245Z] [INFO] OpenGL 版本: 4.6.0 NVIDIA 535.104
[2024-05-21T10:30:15.310Z] [INFO] 加载节点图: 42 个节点
[2024-05-21T10:30:15.412Z] [INFO] 开始渲染帧 @ 1001/30000
[2024-05-21T10:30:15.454Z] [INFO] 渲染完成, 耗时 42ms
[2024-05-21T10:30:15.455Z] [INFO] 写入共享内存: /olive_frame_abc123, 33177600 bytes

6.2 错误日志

错误同时输出到 stderr 和 stdout JSON

[2024-05-21T10:30:15.456Z] [ERROR] Decoder 初始化失败: codec not found for 'hevc'

7. 退出码

退出码 名称 含义 主进程应对
0 EXIT_OK 渲染成功 正常处理结果
1 EXIT_GENERIC_ERROR 通用错误 记录错误,丢弃该帧
2 EXIT_INVALID_ARGS 命令行参数无效 检查主进程参数组装逻辑
3 EXIT_INIT_FAILED 初始化失败(OpenGL/OCIO 尝试 dummy 后端或提示用户
4 EXIT_GRAPH_LOAD_FAILED 节点图加载/解析失败 检查 XML 序列化逻辑
5 EXIT_RENDER_FAILED 渲染过程中出错 记录具体错误,丢弃该帧
6 EXIT_OUTPUT_FAILED 输出写入失败(SHM 不足) 清理 SHM,重试或报错
130 EXIT_SIGINT 收到 SIGINTCtrl+C / kill -2 视为取消,正常丢弃
137 EXIT_SIGKILL 收到 SIGKILLkill -9 视为取消,正常丢弃
139 EXIT_SEGFAULT 段错误(未捕获信号) 视为崩溃,记录日志
其他 未知错误 记录日志,丢弃该帧

8. 主进程与子进程交互时序

8.1 单帧完整时序

时间轴 ──────────────────────────────────────────────────────────────>

主进程:     [准备参数]  [创建SHM]  [QProcess::start()]  [等待]  [读JSON]  [读SHM]  [unlink SHM]
                │           │            │                │        │        │        │
子进程:                  [启动]  [解析参数]  [加载XML]  [初始化GL]  [渲染]  [写SHM]  [写stdout]  [exit]
                             │        │        │           │        │        │          │
                             └────────┴────────┴───────────┴────────┴────────┴──────────┘
                                                   进程生命周期

8.2 异常情况时序

子进程崩溃(segfault

主进程: [start] ── [wait] ── [finished信号] ── [exitStatus == CrashExit] ── [忽略该帧]
子进程: [启动] ── [崩溃] ──────────────────── [操作系统回收资源]

主进程取消(kill

主进程: [start] ── [用户操作/超时] ── [QProcess::kill()] ── [finished信号] ── [unlink SHM]
子进程: [启动] ── [渲染中] ───────── [SIGKILL] ──────────── [立即终止]

9. 共享内存生命周期管理

9.1 创建

// 主进程创建
QString shm_name = "/olive_" + QUuid::createUuid().toString(QUuid::WithoutBraces);
size_t shm_size = CalculateFrameSize(params) + OLIVE_SHM_HEADER_SIZE;
void* shm_ptr = olive_shm_create(shm_name.toUtf8().constData(), shm_size);

9.2 命名规范

  • POSIX: /olive_<uuid>,必须以 / 开头,长度 < 255。
  • Windows: Local\\olive_<uuid>Global\\olive_<uuid>
  • 临时文件: /tmp/olive_<pid>_<uuid>.raw(内存映射临时文件回退方案)。

9.3 清理策略

正常路径

  1. 子进程成功渲染,写入数据,退出。
  2. 主进程读取数据。
  3. 主进程 olive_shm_close(ptr, size) + olive_shm_unlink(name)

异常路径(子进程崩溃)

  1. 主进程检测到 QProcess::CrashExit
  2. 主进程立即 olive_shm_unlink(name)(即使数据未读取)。

双重保险

  • 主进程在创建 SHM 时启动一个 QTimer5 秒后触发)。
  • 若 5 秒后 SHM 仍未被清理(异常路径未执行到 unlink),定时器自动 olive_shm_unlink(name)
  • 防止子进程崩溃后主进程也崩溃导致的 SHM 泄漏。

10. 平台差异

10.1 Linux

  • 共享内存:/dev/shm/ 下的 tmpfs 文件。
  • 最大名称长度:255 字节(含 null)。
  • 权限:shm_open 使用 0666,确保子进程可以打开。
  • 系统限制:/proc/sys/kernel/shmmax 通常足够大> 1GB)。

10.2 macOS

  • 共享内存:shm_open 创建的 POSIX 共享内存对象。
  • 注意:macOS 的 shm_open 名称长度限制为 31 字符(包括开头的 /)!
  • 解决方案:使用 内存映射临时文件 替代 POSIX shm。
// macOS 专用:使用临时文件替代 shm_open
int fd = mkstemp("/tmp/olive_XXXXXX.raw");
ftruncate(fd, size);
void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// 将文件路径传递给子进程

10.3 Windows

  • 共享内存:CreateFileMapping + MapViewOfFile
  • 名称:Local\\olive_<uuid>(用户会话内可见)。
  • 注意:句柄管理。子进程 MapViewOfFile 后需要保留 HANDLE 以便后续 UnmapViewOfFileCloseHandle
  • 简化方案:子进程只负责写入,不关闭映射。进程退出后操作系统自动回收。

11. 性能调优建议

11.1 减少进程启动开销

技术 效果 复杂度
静态链接 olive-renderer 避免动态库加载开销
预加载 Qt 插件 减少 QCoreApplication 初始化时间
批处理模式 摊销 OpenGL 上下文创建开销
使用 EGL 替代 GLX/WGL EGL 上下文创建更快

11.2 减少序列化开销

技术 效果 复杂度
节点图 XML 缓存 相同图只序列化一次
增量参数更新 仅发送变更的参数
二进制序列化格式 替代 XML,解析更快

11.3 减少共享内存开销

技术 效果 复杂度
共享内存池 预分配 N 块循环使用
内存映射临时文件 避免 shm_open 系统调用
零拷贝(GPU 纹理共享) 跨进程直接共享 GPU 纹理 高(平台相关)

12. 调试工具

12.1 手动运行子进程

# 直接运行 olive-renderer,独立于主进程
./olive-renderer \
  --mode=frame \
  --node-graph=/tmp/debug_graph.xml \
  --time=0/1 \
  --video-params='{"width":100,"height":100,"format":"rgba32f"}' \
  --output-shm=/olive_debug \
  --output-shm-size=160000 \
  --verbose

# 查看 stdout 输出
# 查看 stderr 日志
# 用另一个程序读取 /olive_debug 验证像素数据

12.2 环境变量

变量 作用
OLIVE_RENDERER_BACKEND=dummy 强制使用 dummy 后端(跳过 OpenGL
OLIVE_RENDERER_TIMEOUT=60000 子进程超时时间(毫秒)
OLIVE_RENDERER_KEEP_SHM=1 子进程退出后不删除共享内存(调试)
OLIVE_RENDERER_LOG_FILE=/path/to.log 将日志写入文件

12.3 重放渲染

将主进程发送给子进程的所有输入(命令行参数 + XML)保存到日志目录,可以精确重放某一帧的渲染:

# 主进程日志目录:~/.local/share/oak/renderer_logs/
# 每个渲染任务保存为:
#   frame_12345.params  (命令行参数)
#   frame_12345.xml     (节点图)

# 重放
./olive-renderer $(cat ~/.local/share/oak/renderer_logs/frame_12345.params)