3DGS 端侧重建的讨论往往集中在采集多少帧、重建速度和渲染效果。不过站在应用工程这一侧,还有一个不太起眼的问题:模型写出了文件,并不意味着这个任务就可以显示“交付完成”。文件放在哪里、目录是否属于应用沙箱、同名任务会不会覆盖旧结果,以及中途退出留下的半成品如何识别,都会影响最终交付。

本文以SceneLedger 展陈采集清单为例,只做输出路径规划与可恢复清单,不冒充完整重建流水线。演示任务号为 GS-021,对象名“石桥展廊”,候选输出位置为应用文件目录下 exports/GS-021/model。界面状态是 WAIT_VERIFY,表示已完成路径规划,但尚未验证模型文件的存在与完整性。

一、读到模型导出结构时,先注意那个路径约束

华为 Spatial Recon Kit 的 C 接口资料中,HMS_SpatialRecon_ModelWriteInfo 提供 modelFile、modelFormat、可选 audioFile 与经纬度等字段。文档明确 modelFile 是必填的输出文件名,并要求位于应用文件目录的子目录;modelFormat 则决定输出模型的格式和结构。这个约束足以让应用层提前规划路径,却不足以证明某个固定扩展名能适用于所有导出类型。

因此 SceneLedger 不写死 .ply 或其它后缀,也不会自己拼一个未核对的枚举值传给 SDK。本文的 model 只是候选文件名,真实调用时必须按所选 SDK 输出格式修正文件名、目录布局和校验逻辑。尤其当输出是多文件目录而非单个文件时,只检查一项 file_size() 并不能证明模型完整。

我把输出状态拆成三段:PLAN_OK 表示目录和任务号符合本地约束;OUTPUT_SEEN 表示按照实际格式找到了输出对象;READY 表示业务侧完成了格式、文件、元数据校验并发布清单。WAIT_VERIFY 是页面展示给用户的中间态,不等价于 SDK 返回成功,更不等价于模型可渲染。

二、路径只从受控任务号生成,不接收任意拼接字符串

演示用的 GS-021 是应用自己分配的短 ID。用户输入只允许影响场景名称,不能直接替换目录名。这样做并不是简单防止 ../,而是把模型写入目标固定在应用可以管理的一处。目录白名单比对每个危险字符做黑名单过滤更容易审计,迁移数据目录时也更清楚。

第一段 C++ 代码解决的是把合法任务号映射成应用文件目录下固定的子路径。appFiles 必须由宿主程序提供真实、可信的应用沙箱 files 路径,不能取自前端输入,也不能把示意路径直接带到产品中。

#include <filesystem>
#include <regex>
#include <stdexcept>
#include <string>
namespace fs = std::filesystem;

struct ExportPlan {
  std::string jobId;
  fs::path folder;
  fs::path modelPath;
};

ExportPlan planExport(const fs::path& appFiles, const std::string& id) {
  if (!std::regex_match(id, std::regex("GS-[0-9]{3}"))) {
    throw std::invalid_argument("INVALID_JOB_ID");
  }
  fs::path root = appFiles / "exports";
  fs::create_directories(root);
  fs::path folder = root / id;
  fs::create_directories(folder);
  if (fs::is_symlink(root) || fs::is_symlink(folder)) {
    throw std::runtime_error("SYMLINK_NOT_ALLOWED");
  }
  return { id, folder, folder / "model" };
}

regex 限制了命名空间,root / id 因而不会包含来自用户的路径分隔符;create_directories 用于建立新的输出位置。示例对直达目录进行了符号链接检查,但它并不是完整的安全沙箱实现:若父目录可被不可信参与方改写,还必须追加规范化路径比对、打开文件句柄后的 inode/目录身份检查,以及并发创建时的竞态处理。应用私有目录与外部共享路径也不能用一套信任假设。

三、把 SDK 写入参数的生命周期单独留出来

HMS_SpatialRecon_ModelWriteInfo::modelFile 是 const char*,它指向的字节不能在调用者还要读取时提前销毁。很多跨 C/C++ 接口故障不是参数值写错,而是局部 std::string 已经析构,指针被留下来继续使用。对于异步 API,尤其要区分“函数调用返回”和“SDK 已完成读取或拷贝参数”两个时刻。

下面的片段只展示如何准备已规划的路径及其稳定的字符串存储,故意不写某个未验证的“开始导出”函数。modelFormat 必须由实际集成时按官方枚举和设备能力配置;片段不等于完整 SDK 调用示例。

#include "spatial_recon_interface.h"
#include <string>

struct WriteRequest {
  std::string stablePath;
  HMS_SpatialRecon_ModelWriteInfo info{};

  explicit WriteRequest(const ExportPlan& plan)
    : stablePath(plan.modelPath.string()) {
    info.modelFile = stablePath.c_str();
    info.audioFile = nullptr;
    // info.modelFormat 按当前 SDK 文档与导出目标另行赋值。
  }
};

在这一层,WriteRequest 需要活到 SDK 已不再使用其成员为止,并且不应发生会改变 stablePath 存储地址的修改。另一个工程问题是路径约束:这段示例提供绝对路径候选值,但具体接口是否要求特定的子目录形式、扩展名和参数寿命,需要以当前接入版本的调用章节再次确认。不能只凭一个结构体字段文档推出完整 API 调用协议。

四、半成品不能用一个“存在”判断发布

SceneLedger 的清单不记录模型内部算法参数,而记录可追溯的交付事实:任务 ID、候选输出位置、选定的格式标识、验证项与发布时间。发布动作必须晚于校验动作,且保留失败状态。这里采用三道闸:任务 ID 和沙箱路径正确;按照真实格式检查模型输出;验证实际文件集合的可读性与完整性。只有三道都完成,才把业务状态从 WAIT_VERIFY 变成 READY。

如果输出目录里只有一份零字节文件,就不能写入“完成”;如果 SDK 导出的是多文件结构,只检查入口文件同样不够;如果文件刚写完而文件系统同步尚未落地,异常断电后的行为还需要单独验证。业务清单与文件数据是两个不同事务,不能假设一次 rename 可以跨文件系统提供原子更新。

第三段代码给出的是业务清单发布的最小安全边界:先写临时文本,检查流状态,再在同一目录内改名。它只负责清单,不负责替 SDK 判断模型格式,更不替代落盘同步与模型可读性检查。

#include <fstream>
#include <filesystem>
#include <stdexcept>
namespace fs = std::filesystem;

void publishManifest(const ExportPlan& plan, bool modelVerified) {
  if (!modelVerified) throw std::runtime_error("MODEL_NOT_VERIFIED");
  fs::path temp = plan.folder / "manifest.pending";
  fs::path finalPath = plan.folder / "manifest.txt";
  {
    std::ofstream out(temp, std::ios::trunc);
    if (!out) throw std::runtime_error("OPEN_MANIFEST_FAILED");
    out << "job=" << plan.jobId << "\nstatus=READY\n";
    out.flush();
    if (!out.good()) throw std::runtime_error("WRITE_MANIFEST_FAILED");
  }
  fs::rename(temp, finalPath);
}

modelVerified 必须来自真实的多项校验结果,绝不是 UI 按钮传入的布尔常量。rename 在同目录时可以降低读到部分清单的概率,但这里没有宣称全平台掉电原子性;是否需要 fsync、目录同步和失败回滚,应该结合具体平台文件系统及数据可靠性等级设计。真实工程还要为重复发布、校验后文件再次被替换和任务并发导出建立单写者策略。

五、让页面说“待确认”,而不是给演示进度造证据

示例 DevEco 图把 SceneLedger 工程中的 ExportPlan.cpp、ModelWriteBridge.cpp 和 ExportPage.ets 放在一个布局里。左侧目录用于说明模块职责,中间片段突出 GS-021 的路径白名单检查,右侧模拟器显示 WAIT_VERIFY,底部是计划阶段日志:PLAN_OK、FORMAT_PENDING、MODEL_NOT_VERIFIED。这些都属于预设数据,不是 SDK 真正运行后的日志。

对用户可见的手机页面,只给出实际业务能够确定的事实:项目“石桥展廊”,任务 GS-021,路径 exports/GS-021/model,1/3 检查项已完成;模型输出尚未核验,因此不能出现“已导出成功”或一张伪装成真实 3DGS 渲染的模型图。应用可以显示灰色占位预览,但不能在审核材料里当作设备生成结果。

六、异常恢复从目录清单开始,不从进度条开始

如果应用退出后重新进入,首先读取清单状态,再验证文件集合,而不是仅恢复上次页面的进度数字。遇到 manifest.pending 可以说明上次发布未完成,应该继续核验或清理;遇到 manifest.txt 也需要检查它指向的模型是否仍然有效。文件大小为正只能说明并非空文件,既不能证明格式正确,也不能证明渲染引擎能够打开它。

另一个容易忽略的点是会话生命周期。空间重建会话的真实创建、采集、写入与销毁必须以官方 Spatial Recon Kit 流程为准;本文没有调用 SDK 的会话函数,所以也没有资格给出“成功重建一座展廊”的结论。后续若接入真机,应把设备支持条件、所选输出格式、文件清单、失败码与渲染侧复验结果一起记录,而不是靠一张漂亮的成功页收尾。

这类工程更适合按“路径规划通过—输出被发现—结果被验证—清单被发布”的顺序验收。对 3DGS 项目而言,交付目录既是资源容器,也是错误恢复的边界。前面的模型计算再复杂,最后缺少确定的交付协议,应用仍然可能把一个未完成的结果交给下一环节。

资料核对:华为 Spatial Recon Kit C API 的 HMS_SpatialRecon_ModelWriteInfo 文档确认 modelFile 必填且受应用目录限制,并明确 modelFormat 控制输出结构;本文 C++ 代码只实现应用自己的路径与清单策略。演示图、任务号、进度及日志均非实机数据。

  • 官方资料:https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/api/capi-spatialrecon-hms-spatialrecon-modelwriteinfo
  • 官方资料:https://developer.huawei.com/consumer/cn/features/spatialization
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐