请添加图片描述

1. 前言

在“窗边阅读”里,雨声留在窗外,翻书声靠近听者,铃声从街角传来。现在,我们给铃声换上旋转模式。听完20秒,用户按下重播,期待铃声从相同方向出现,沿着相同顺序移动。保存之后,这个顺序也应留在文件里。

这个要求给上一篇的工程配方增加了一个问题:除了保存参数,我们还需要弄清楚这些参数从什么时候开始起作用。

固定摆位时,配方描述声源的位置。旋转之后,同一份素材在不同处理阶段可能呈现不同的方向。把输入游标归零,只能说明下一次读数从素材开头开始。效果节点是否也回到了相同状态,需要另找证据。

声音明信片继续使用rain、page、bell三路素材,工程长20秒,格式为48 kHz、双声道、S16LE,每路960,000个样本帧。短素材补静音,长素材裁切,不自动循环。A仍是“窗边阅读”,B仍是“街角散步”;两版共用素材和增益基线。

本篇从A的已确认配方派生旋转修订,只改变bell的空间模式,rain和page保持固定摆位。原来的固定版本继续保留。此前拖动铃声时保存的是最终位置,没有记录手势轨迹;这次的运动来自旋转模式。

我会先把完整重播做成一个可以检查的问题,再讨论第8秒接入。这样,每增加一种恢复操作,都有一份完整输出可供比较。

2. 功能定义

OHAudioSuite提供固定摆位、旋转和扩展三种空间模式。旋转模式通过 OH_AudioSuiteEngine_SetSpaceRenderRotationParams 配置单周环绕时间与时针方向。空间渲染节点始于API 23,属于这个HarmonyOS 7.0工程所使用的基础能力。

接入时需要核对目标SDK中的参数结构、单位、合法范围、方向枚举以及调用约束。指南说明了模式用途,并没有据此赋予应用任意设置或读取旋转相位的能力。固定摆位使用的坐标,也不能直接解释为旋转的初始相位。

坐标约定继续沿用听者中心的左手坐标系:X向右,Y向上,Z向前,每轴位置范围为[-5,5]米。这个范围只说明位置,不能拿来校验旋转周期。扩展模式有自己的半径与角度语义,本篇不再加入这组变量。

讨论恢复之前,先分清几种容易被称为“进度”的信息。

信息记录内容它回答的问题
素材位置每路输入的下一读取帧接下来从哪里取数
输出进度已生成的有效帧及块范围已经产生多少音频
管线事件创建、设参、启动、停止及时间戳处理经历了哪些操作
效果状态通过输出与听辨检查运动是否能够重复

48 kHz下,第8秒对应384,000帧。但输入读到这一帧,不代表扬声器正在播放这一帧;效果缓存和播放缓冲都可能使两者产生距离。旋转的内部进度也不能从输入游标直接推出来。

请添加图片描述

图06-A:素材时间、管线事件与效果状态分别记录。第8秒的对齐线用于提出检查问题,不表示三者已经同步。

官方管线规则说明,进入stopped状态后,各节点释放缓存。这让停止成为一个需要记录的边界,却不足以回答旋转相位是否归零。重新设参、停止后启动、销毁后重建,应当作为不同操作检查。

实时与离线还存在处理节奏的差别:实时路径向OHAudio交付播放数据,离线路径由应用主动请求渲染。生成20秒声音所花的现实时间可以不同。因此,导出用了多久,不能用来判断铃声绕到了哪里。

3. 方案与实现

我倾向于先保留上一篇的结构,只扩展它无法表达的部分。三路输入仍各自经过空间节点,再进入混音与单一输出。节点图没有因为铃声开始移动而改变。

A的初始位置是rain(-2,0,1)、page(0,0,1)、bell(2,0,2),单位为米。如果用户已经确认了新的位置,固定参考就采用那个修订。生成旋转配方时不能悄悄恢复初始表,也不能覆盖原来的配方。

上一篇的声源模型只有一个position字段。继续向它添加周期、方向和一组可空参数,很容易得到含义不清的组合:某个声源到底处于哪种模式?哪些字段应被使用?我更愿意让模式直接体现在类型里。

下面的C++17代码描述应用层模型。旋转配置引用一份独立的、按SDK契约校验的参数文档;它不直接复制SDK结构体的内存布局。

#include <cstdint>
#include <string>
#include <variant>

struct FixedPlacement {
    float x, y, z;
};

struct RotationConfigRef {
    std::string documentId;
    std::string contentHash;
    std::string sdkContract;
};

using SpatialRecipe =
    std::variant<FixedPlacement, RotationConfigRef>;

struct SourceRecipe {
    std::string id;
    std::string pcmHash;
    uint64_t beginFrame;
    uint64_t endFrame;
    float gain;
    SpatialRecipe spatial;
};

参数文档保存旋转模式所需的完整字段及单位;适配层负责范围检查和SDK参数映射。加载时同时验证文档内容哈希与契约版本,发现不匹配便拒绝准备。一个路径或文档名本身,不能保证下次读到相同配置。

工程配方继续保存基础版本、修订号、格式和节点图约定,并递增结构版本。旧配方中的position转换为固定摆位分支。项目零点仍由应用定义,它表示素材范围的开始,不是一个相位重置指令。

请添加图片描述

图06-D:配方保存可重建的参数,运行对象保存本次处理进度。A的固定修订与旋转修订分别保留,运行状态不回写配方。

配方冻结仍发生在最后一次参数提交成功之后。任务取得深拷贝快照,编辑器后续修改生成新修订。输入游标、管线实例和输出计数属于单次运行,每次播放或导出分别创建。

接下来比较两种恢复办法。

第一种尽量保留原管线,只让素材从头供数。它的改动较少,但保留了哪些效果历史并不明确。调整游标必须发生在API允许、且不会与输入回调竞争的边界;如果必须先停止管线,这个用例就已经包含停止操作,日志应如实记录。

第二种从冻结配方重新准备管线,三路输入从零开始。它需要额外的初始化时间,却让每轮处理过程更容易描述。我会先用独立重建建立基准,再检查复用管线是否能得到相同结果。重建是控制实验条件的办法,本身不证明相位必然恢复。

下面用应用层伪代码表达准备过程。各操作失败时都抛出错误,资源对象负责清理已经创建的部分。

prepareRun(snapshot, mode):
    assets = verifyAssets(snapshot)
    rotation = loadAndValidateRotation(snapshot)

    run = createOwnedRun(mode)
    try:
        run.buildGraph(snapshot, assets)
        run.applySpatialParameters(snapshot, rotation)
        run.initializeReadersAtZero()
        run.record("prepared", snapshot.revision)

        run.start()
        run.record("started")
        return run
    catch error:
        run.recordFailure(error)
        run.close()
        throw error

buildGraph包含节点支持查询、创建与连接检查;applySpatialParameters检查每次设参结果。失败后不继续播放半配置的图。创建、设参和启动事件都关联同一运行标识,才能把输出追溯到具体过程。

请添加图片描述

图06-B:两种恢复路径分别列出素材位置、参数和效果缓存的处理方式。停止重启与独立重建分开记录,再用输出判断重复性。

重播还涉及播放器。即使新管线从零输出,旧播放缓冲也可能混入上一轮尾音。控制层需要按OHAudio生命周期处理上一轮播放结束与下一轮启动,分别记录渲染零点、提交时刻和可听起点。只有软件日志时,就报告软件边界,不把它写成扬声器的实际时间。

基础实验按顺序执行:先生成实时参考,再做离线导出,同一时刻只有一个版本进入可听输出。采集数据通过有界缓冲交给非实时路径写盘;重建节点、长时间预滚和文件写入不进入播放回调。

离线任务读取同一旋转配方,独立初始化游标和节点。继续沿用上一篇的写入规则:检查返回码,只保存有效长度,处理最后一块数据后再判断完成。渲染完成、文件完整和动态一致性各有自己的状态,不能合并成一个success。

请添加图片描述

图06-E:同一冻结配方分别建立实时与离线运行,各自从项目零点供数。两条路径的处理耗时可以不同,输出仍需按相同项目范围进行检查。

如果用户要从第8秒进入,问题就多了一层。可以先从零不可听地渲染到目标输出范围,再将后续数据送入播放器,与完整参考的相同区间比较。这一过程叫预滚。目标落在一个输出块内部时,还需要按帧划分保留区间。

直接跳过384,000帧输入,没有补回效果的处理历史;等待8秒也没有完成这些处理。预滚是否值得采用,要同时看输出对应关系与准备成本。在它通过检查之前,界面可以明确提供“从头重播”,不必承诺任意位置恢复。

4. 测试

测试从一份冻结配方开始。保存设备、系统、SDK、素材哈希、输出路由、音量及额外音效设置,先检查单条管线,再考虑并发负载。固定摆位参考用于核对素材与播放链路;旋转的重复性则比较同一旋转配方的不同运行。

操作保持不变的条件主要观察
首次完整播放冻结旋转配方,三路从零供数开场方向、运动顺序与输出范围
完整重播素材、参数、设备不变分别比较游标恢复与重新准备
第8秒附近停止后从头播放记录实际停止边界复用管线与独立重建是否有差异
离线导出及重复导出同配方,逐轮独立初始化与实时参考及其他导出的对应关系
改变周期方向、素材和增益不变周期变化后的可辨识性与重复性
改变方向周期和其他参数不变运动顺序是否呈现对应变化
第8秒接入对照完整播放的相同区间定位、等待时间、空白及运动顺序
设参或写入失败保留失败阶段与返回码产物不被登记为可交付版本

周期从正式API合法范围内选择,先确认20秒内有足够的观察机会。一次只改变周期或方向,否则输出变化很难归因。

事件日志需要能够回答“第8秒”指什么。下面的结构分别记录目标、输入和输出;事件类型保留设参、启动与停止的区别。

#include <array>
#include <cstdint>

enum class EventKind : uint8_t {
    Prepared, ParametersApplied, Started, Stopped, Failed
};

struct RunEvent {
    uint64_t runId;
    uint64_t recipeRevision;
    uint64_t sequence;
    int64_t monotonicNs;
    EventKind kind;
    uint64_t targetFrame;
    std::array<uint64_t, 3> nextInputFrame; // rain/page/bell
    uint64_t outputFrames;
    int32_t resultCode;
};

这些字段不包含“当前相位”。日志能说明我们做过什么,方向判断则来自声音。实时记录写入预分配区域,另由后台保存;记录区满时统计丢失,避免采集反过来阻塞播放。

数据完整性仍需先检查。每帧4字节,20秒目标为3,840,000字节;长度异常时保留原始文件,检查供数、尾块与结束状态,不用裁切或补零修饰结果。总长度正确之后,还要排查重复块和遗漏块。

比较实时与离线PCM时,先核对配方身份、范围、通道与增益。需要时间对齐或响度调整时,生成单独副本并记录处理量。将两份文件平移后听起来更像,可以帮助分析差异,却不能证明原始开场相同。

请添加图片描述

图06-C:操作矩阵关联输出样本、事件记录和分时段方向判断。记录表保留“不确定”与失败原因,听辨结果不换算成内部相位。

听辨先完整播放,再检查对应区间。可以从0—5、5—10、10—15、15—20秒四个窗口开始;周期较短时细分窗口,记录方向变化的先后。切片应来自同一次完整输出,分段重新渲染会改变需要检查的处理历史。

随机样本编号隐藏首次、重播或离线来源,不占用工程A/B名称。人数、重复次数、播放顺序和判断标准在实验前确定。记录中保留“不确定”和“该区间铃声不可辨”;铃声静音时,无法仅凭听辨判断它的方向。

需要连续观察时,可以另建诊断素材组,只替换bell,保持三路结构与其他参数,单独记录哈希。诊断结果用于定位问题,最终体验仍回到原来的三路声音。接口调用成功、文件完整、运动可辨与重复性分别报告,偏好不能代替这些判断。

5. 结论

铃声能否记住起点,取决于配方与运行过程能否共同产生可重复的输出。素材游标只描述其中一部分。把它与管线事件、输出范围分开记录,才能知道差异来自供数、初始化还是播放链路。

当前实现选择先从冻结配方、项目零点和独立运行开始组织验证。完整重播通过,不自动意味着中途恢复可用;实时通过,也不能替离线导出签字。验收清单应分别保存这些结论及其适用条件。

工程资产按下面的关系保存,输出文件由清单绑定具体运行:

experiment-06/
  recipes/                  # 固定参考与旋转配方
  assets-manifest.json
  records/                  # 环境、事件、输出块与听辨记录
  output/
    format.json
    output-manifest.json
  failures/
  validated-versions.json
  README.md

能力不支持、参数失败、运动顺序无法重复或运行成本不满足要求时,保留失败记录,并选择第05篇对应的已验收固定摆位版本。这个选择需要明确的修订号与PCM哈希,不能把静态输出继续标为旋转成功。

下一篇接收经过验收的PCM、格式说明和版本清单,完成WAV封装与普通播放器验证。旋转版只有通过本篇检查后才进入交付;固定摆位版继续作为基础成果。可编辑的工程配方独立保存,方便以后重新生成声音。

Logo

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

更多推荐