03 最后停在:

progress=100%
stage=FINISHED
status=RECON_FINISHED

这一步只说明 3DGS 重建已经完成。

对用户来说,真正能用的东西还没有出现。

要进入后续渲染、分享或归档,必须把 Session 里的结果写成文件。

Spatial Recon Kit 当前 HMS_SpatialRecon_ModelWriteInfo 提供了结果写入配置:modelFile 是必填的输出文件,modelFormat 指定输出格式;当前 HMS_SpatialReconOutputFormat 包含 PLY 与 MP4。audioFile 是可选 MP3 路径,longitude / latitude 则用于地理参考。HMS_SpatialRecon_SaveResultToFile 是异步保存接口,保存过程中再调用 GetProgress 时,进度已经表示保存进度。

SceneForge 04 就围绕这个阶段展开。

这次不换 Scene,也不重建第二遍。

直接继续 03 已完成的:

sessionId:
sf_session_20261003_03

先保存 PLY,再串行保存 MP4。

本轮统一数据:

taskId:
recon_20261003_04

sessionId:
sf_session_20261003_03

scene:
tabletop_tea_set_02

stageBeforeSave:
FINISHED

resultDir:
/data/storage/el2/base/files/
sceneforge/recon_03/results/

plyFile:
tabletop_tea_set_02_3dgs.ply

plySize:
86.4MB

plySaveCost:
1280ms

mp4File:
tabletop_tea_set_02_orbit.mp4

mp4Size:
18.7MB

mp4SaveCost:
2460ms

audioFile:
nullptr

geoTag:
false

saveQueueDepth:
1

concurrentSaveBlocked:
1

saveCallbackSuccess:
2 / 2

saveProgressSamples:
9

manifestWritten:
true

finalFiles:
2

destroyAfterSave:
true

status:
RESULT_READY

一、为什么 03 不直接把 writeInfo 传给 StartSession

HMS_SpatialRecon_StartSession 当前支持传:

HMS_SpatialRecon_ModelWriteInfo*

如果不为空,重建完成后可以继续执行写入。

SceneForge 没这么做。

原因不是接口不能用,而是这个系列想把:

RECONSTRUCT

和:

SAVE

拆成两个可独立观察的阶段。

03 只验收重建生命周期。

04 才开始保存。

这样:

94.8s

明确是重建耗时,

1280ms
2460ms

明确是两个保存任务的耗时。

性能口径不会混在一起。

二、保存之前第一条判断必须是 stage=FINISHED

当前官方状态码里有:

SPATIAL_RECON_STATUS_STAGE_NOT_FINISHED

所以项目层不等接口报错才处理。

保存入口先检查 03 的终态。

bool ResultSaveCoordinator::CanSave() const
{
    return session_ != nullptr &&
        snapshot_.stage ==
            SPATIAL_RECON_STAGE_FINISHED &&
        !isSaving_;
}

只要还在:

BUILDING
PAUSED
SAVING

就不允许新建保存任务。

三、ModelWriteInfo 里最关键的是 modelFile 与 modelFormat

PLY 保存配置:

HMS_SpatialRecon_ModelWriteInfo BuildPlyInfo()
{
    HMS_SpatialRecon_ModelWriteInfo info {};

    info.modelFile =
        "/data/storage/el2/base/files/"
        "sceneforge/recon_03/results/"
        "tabletop_tea_set_02_3dgs.ply";

    info.modelFormat =
        SPATIAL_RECON_OUTPUT_FORMAT_PLY;

    info.audioFile = nullptr;
    info.longitude = 0.0f;
    info.latitude = 0.0f;

    return info;
}

本轮没有音频,也不做地理标记:

audioFile=nullptr
geoTag=false

所以经纬度保留默认 0。

这两个字段不能拿来“随便填一个位置”。

没有业务需求就不要伪造地理信息。

四、PLY 和 MP4 是两种完全不同的结果用途

SceneForge 当前把它们定义成:

PLY
后续 3DGS 加载、渲染、编辑的核心模型结果;

MP4
给用户快速预览和分享的环绕视频。

当前官方输出枚举就是:

SPATIAL_RECON_OUTPUT_FORMAT_PLY

SPATIAL_RECON_OUTPUT_FORMAT_MP4

所以 04 不在文章里虚构第三种自定义格式。

后面 05 的 spatialRender.GSPlugin 主线会使用模型结果进入 ArkGraphics 3D 渲染。

五、保存入口主动串行,不让两个 Save 同时跑

我第一次写的时候,PLY 保存刚开始,就让用户又点了:

导出 MP4

工程上没必要冒这个险。

SceneForge 当前强制:

saveQueueDepth=1

保存 PLY 时,新的保存请求直接:

concurrentSaveBlocked++

等 PLY 回调成功后,再开始 MP4。

HMS_SpatialReconStatus SaveOne(
    HMS_SpatialRecon_ModelWriteInfo* info,
    HMS_SpatialReconCallbackFunc callback)
{
    if (isSaving_) {
        concurrentSaveBlocked_++;
        return SPATIAL_RECON_STATUS_STAGE_BUILDING;
    }

    isSaving_ = true;

    return HMS_SpatialRecon_SaveResultToFile(
        session_,
        info,
        callback);
}

这里用项目状态先做互斥。

不依赖“多次并发保存究竟会返回什么”来保证正确性。

六、GetProgress 在保存阶段必须换标签

03 里:

progress=46.2%

表示重建进度。

04 调用 SaveResultToFile 后,官方文档明确说:

GetProgress
只反映当前文件保存进度。

所以 Snapshot 变成:

operation=SAVING_PLY
saveProgress=...

而不是继续写:

reconProgress

本轮 PLY 和 MP4 都最终采样到:

100%

累计:

saveProgressSamples=9

七、PLY 保存耗时 1280ms,文件 86.4MB

本轮第一份结果:

tabletop_tea_set_02_3dgs.ply

86.4MB

1280ms

回调:

SUCCESS

只有保存回调成功以后,Result Manifest 才把:

plyReady=true

写入。

不能看到文件名出现就提前宣布成功。

八、MP4 保存耗时更长,单独记录

第二份:

tabletop_tea_set_02_orbit.mp4

18.7MB

2460ms

文件更小,但耗时更长并不矛盾。

MP4 可能包含额外的路径生成和视频编码过程。

SceneForge 只记录实际耗时,不用文件大小直接推断时间。

这两条性能基线以后会进 06 回归。

九、保存回调要把“成功状态”和“文件证据”一起检查

只有回调 SUCCESS 还不够。

SceneForge 保存完成后继续验证:

文件存在
size > 0
扩展名正确
Manifest 路径一致

如果回调成功但文件找不到:

SAVE_RESULT_MISSING

不会进入 RESULT_READY。

项目不把一个 callback 当成全部证据。

十、结果清单必须和两个文件一起落盘

保存完成后生成:

manifest.json

示意:

{
  "taskId": "recon_20261003_04",
  "sessionId": "sf_session_20261003_03",
  "scene": "tabletop_tea_set_02",
  "files": [
    {
      "type": "PLY",
      "name": "tabletop_tea_set_02_3dgs.ply",
      "sizeMb": 86.4
    },
    {
      "type": "MP4",
      "name": "tabletop_tea_set_02_orbit.mp4",
      "sizeMb": 18.7
    }
  ]
}

这段 Manifest 不是 Spatial Recon Kit 官方格式。

它是 SceneForge 自己的结果索引。

05 加载模型时,不再到目录里“猜哪个 PLY 是最新的”,而是直接读取 Manifest。

十一、文件目录要按任务隔离,不让结果互相覆盖

当前结果目录:

/data/storage/el2/base/files/
sceneforge/recon_03/results/

里面只放当前 Scene 这次任务的结果。

如果下一轮又重建:

tabletop_tea_set_02

会创建新任务目录,而不是覆盖旧文件。

这样可以保留:

版本
性能数据
对比模型

也避免结果缓存和渲染加载拿错文件。

十二、保存过程中不能 DestroySession

HMS_SpatialRecon_DestroySession 当前说明非常明确:

销毁以后 Session 失效,未保存的数据会丢失;如果需要持久化,必须在销毁前先保存。

所以 04 的资源收口顺序固定:

FINISHED

→ Save PLY

→ PLY callback success

→ Save MP4

→ MP4 callback success

→ Manifest

→ DestroySession

不是:

Start Save
→ 立刻 Destroy

十三、Destroy 以后 session 指针立即视为不可用

SceneForge 的 Session Owner 在 Destroy 后:

session_ = nullptr

并把:

destroyAfterSave=true

写进最终 Snapshot。

从这一刻起:

GetProgress
Pause
Resume
SaveResult

全部禁止。

后续渲染只依赖文件,不再依赖重建 Session。

这个“从 Session 到文件”的切换就是 04 的真正完成点。

十四、DevEco 图重点看“先 PLY,再 MP4,再 Destroy”

开发图:

HiLog:

taskId=
recon_20261003_04

sessionId=
sf_session_20261003_03

stage=
FINISHED

saveQueueDepth=
1

SaveResultToFile PLY

progress=
100%

ply=
86.4MB

cost=
1280ms

block concurrent save=
1

SaveResultToFile MP4

progress=
100%

mp4=
18.7MB

cost=
2460ms

callbacks=
2/2

manifestWritten=
true

finalFiles=
2

destroySession=
true

status=
RESULT_READY

整个保存链一眼就能看清。

十五、手机运行图只展示“可以用的结果”,不再展示 Session 细节

最终运行图:

顶部:

tabletop_tea_set_02

RESULT_READY

两个文件:

PLY
86.4MB
100%

MP4
18.7MB
100%

底部检查:

manifestWritten=true

save callback=2/2

destroyAfterSave=true

finalFiles=2

到这一篇,SceneForge 第一次真正拥有了:

可加载
可预览
可分享

的输出资产。

十六、为什么 MP4 也放在 04,而不是渲染篇

MP4 在这里是:

重建结果保存格式

05 的渲染主线则是:

把 3DGS 模型真正加载进 ArkGraphics 3D
并让用户交互浏览。

两个概念不能混。

一个是“导出视频文件”,另一个是“实时 3D 渲染”。

所以 04 保存 MP4,05 不再围绕视频播放,而会进入 PLY / GS 模型的实时加载。

十七、保存失败时不要马上 Destroy,允许项目层重试

如果 PLY 保存失败:

Session 仍然 FINISHED
结果仍在内存 / 工作目录上下文里

SceneForge 会:

保持 Session
标记 SAVE_FAILED
允许用户重试

只有:

用户主动放弃
或
保存成功

才 Destroy。

如果第一次失败就把 Session 销毁,恢复机会也一起丢了。

十八、04 最后固定八组保存测试

第一组,FINISHED 后保存 PLY 成功。

第二组,PLY 保存中重复点保存,项目层阻断。

第三组,PLY 完成后保存 MP4 成功。

第四组,保存阶段 GetProgress 口径切成 save progress。

第五组,modelFile 路径无效,状态不进入 RESULT_READY。

第六组,回调成功但文件不存在,结果校验失败。

第七组,两个文件都完成后 Manifest 正确。

第八组,DestroySession 后任何 Session API 都不再执行。

全部通过以后:

RESULT_READY

才成立。

十九、下一篇:文件有了,真正的 3D 浏览还没开始

04 最终拿到:

PLY
MP4
Manifest

但用户要的不是“结果目录”。

下一篇会继续:

spatialRender
GSPlugin
ArkGraphics 3D Scene
GSNode
Camera
RenderContext

把真正的 3DGS 结果加载到页面里。

也就是说,05 会正式从:

Spatial Recon

切到:

Spatial Render

但仍然使用同一个 SceneForge 项目和 04 生成的结果资产。

二十、保存前要先做磁盘空间预检

PLY 最终:

86.4MB

MP4:

18.7MB

真实保存过程还需要临时空间。

如果结果目录只剩:

20MB

直接调用 Save 没有意义。

SceneForge 在进入保存队列前先做:

availableBytes

检查。

项目不会写一个固定:

至少 200MB

的系统结论。

而是按当前任务估算:

预计 PLY
预计 MP4
安全余量

判断。

空间不足:

RESULT_STORAGE_NOT_ENOUGH

保持 Session,不 Destroy,让用户清理空间后重试。

二十一、保存进度要和当前文件绑定

PLY 与 MP4 都会触发:

stage=SAVING
progress=0~100%

如果 Snapshot 只有:

saveProgress=72%

UI 不知道这是哪个文件。

所以 04 保存状态增加:

saveTarget=PLY

或

saveTarget=MP4

本轮流程:

PLY 0 → 100

然后
MP4 0 → 100

而不是一条:

0 → 200%

二十二、第二次保存开始前必须确认第一次已经真正结束

项目层不只看:

isSaving=false

还要确认:

PLY callback success
PLY 文件验证通过
Manifest 内 PLY 状态 ready

三项都满足以后才启动 MP4。

否则第一份结果可能还在文件系统收口,第二个 Save 已经进入 Session。

本轮:

saveQueueDepth=1
concurrentSaveBlocked=1

就是为了验证这个边界。

二十三、ModelWriteInfo 的路径也要绑定 taskId

如果固定输出:

result.ply

下一次重建很容易覆盖上一版。

SceneForge 路径层级:

sceneforge/
recon_03/
results/

文件名再包含:

scene
结果类型

例如:

tabletop_tea_set_02_3dgs.ply

Manifest 则额外保存:

taskId
sessionId
scene

文件路径和业务身份有两层绑定。

二十四、audioFile 为 nullptr 也要明确记录

ModelWriteInfo 当前支持可选:

audioFile

要求是 MP3 路径。

SceneForge 这次没有空间音频或讲解音轨,因此明确:

audioFile=nullptr

而不是:

传一个不存在的空字符串路径

项目层把“没有音频”作为合法状态。

未来如果文旅场景要加讲解音轨,再重新验证:

音频文件存在
扩展名
生命周期

二十五、longitude / latitude 不应该用设备当前位置自动填充

ModelWriteInfo 还支持:

longitude
latitude

但 SceneForge 的茶具桌面场景和地理位置无关。

所以:

geoTag=false

经纬度保留默认。

即使应用拿到了 Location 权限,也不能因为“字段有位置”就自动把用户位置写进模型结果。

只有明确业务场景、用户预期和隐私设计都成立时,才应该启用地理标记。

二十六、结果文件最好再记录 hash

文件:

86.4MB
18.7MB

保存完成后,SceneForge 的后台索引还会计算:

fileSize
hash
createdAt

Manifest 只写:

路径 + size

已经够完成本篇验收。

真实产品则建议再保存完整 hash。

这样 05 加载模型时可以确认:

文件没有被替换
Manifest 没指错版本

二十七、MP4 失败不应该把已经成功的 PLY 删除

保存策略必须允许“部分成功”。

假设:

PLY success
MP4 failed

SceneForge 结果状态应该是:

PLY_READY
MP4_SAVE_FAILED
RESULT_PARTIAL

而不是回滚删除 86.4MB PLY。

因为 PLY 已经是有价值的核心模型资产。

用户可以稍后只重试 MP4。

本轮两个回调都成功,所以:

RESULT_READY

二十八、保存重试必须复用 FINISHED Session,而不是重新重建

保存失败以后,最昂贵的错误处理是:

重新 Push 54 帧
重新重建 94.8s

实际上只要 Session 仍然存在且 Stage 允许,项目应该重试:

SaveResultToFile

而不是重新构建模型。

这也是为什么保存失败时暂不 Destroy Session。

真正 Destroy 是一个不可逆边界。

二十九、DestroySession 需要放在统一 Owner 里

如果:

页面关闭
保存完成回调
用户取消
应用退出

四个地方都能直接 Destroy,很容易重复销毁。

SceneForge 继续沿用一个 Native Owner:

SpatialReconSessionManager

只有 Manager 可以执行:

DestroySession

其他模块只提交:

DESTROY_REQUEST

Manager 判断:

是否正在保存
是否还有待重试结果
是否已经 Destroy

再真正释放。

三十、Manifest 写入失败也不能静默宣布 RESULT_READY

如果:

PLY / MP4 都成功

但 manifest.json 写失败,

页面如果仍然显示:

RESULT_READY

下次 05 加载模型就找不到可靠索引。

所以本轮最终状态条件是:

PLY ready
MP4 ready
Manifest ready
Session destroyed

四项全部完成。

当前:

manifestWritten=true
destroyAfterSave=true

因此最终文件状态是完整的。

三十一、保存结果最好和重建性能日志分文件归档

03 的日志关注:

build cost
pause
mode switch
progress

04 的日志关注:

save progress
file size
save cost
callback
manifest

如果全部混在一个巨大文本里,后续很难做性能对比。

SceneForge 会生成:

recon_metrics.json
result_manifest.json

两个文件。

06 最终回归再把它们汇总到一份 Acceptance Snapshot。

三十二、04 真正完成的是“Session 资产化”

在 03 结束时,3DGS 结果只存在于:

HMS_SpatialRecon_Session

它是运行时状态。

04 结束以后,结果变成:

PLY
MP4
Manifest

这三种可以持久化、索引、分享、加载的资产。

从这一刻起,SceneForge 的业务重心才真正从:

Recon Session

切到:

Scene Asset

下一篇的 spatialRender 也正是建立在这个边界上。

三十三、结果目录也要防止“同名文件半覆盖”

如果 final path 已经存在,SceneForge 不会直接:

open + truncate

而是先判断:

Manifest 是否属于同一 taskId;
旧结果是否允许覆盖;
当前是否正在被渲染页使用。

默认策略是给新任务使用新的 task 目录。

这样即使同一个 scene 连续重建两次,也不会因为文件名相同把旧结果覆盖掉。

结果可追溯性比“目录看起来干净”更重要。

三十四、RESULT_READY 之后的第一消费方不是分享,而是 05 的渲染器

04 最终得到的 PLY 并不会立刻上传或分享。

下一篇 spatialRender.GSPlugin 会先用 Manifest 取到模型 URI,加载进 ArkGraphics 3D Scene,再验证:

GSNode 能创建;
相机可围绕观察;
模型缩放和中心点合理;
RenderContext 资源可释放。

只有“保存出的模型真的能被加载”以后,结果文件才完成从重建资产到交互资产的转换。

所以 04 的 RESULT_READY 是“文件层就绪”,05 才会进入“渲染层就绪”。这两个 Ready 状态继续保持清晰边界。

参考资料

  • HMS_SpatialRecon_ModelWriteInfo
    https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/api/capi-spatialrecon-hms-spatialrecon-modelwriteinfo
  • spatial_recon_interface.h
    https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-spatial-recon-interface-h
  • Spatial Recon Kit 空间计算能力
    https://developer.huawei.com/consumer/cn/features/spatialization
Logo

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

更多推荐