请添加图片描述

1. 前言

上一篇,我们把雨声、翻书声和街角铃声整理到了同一条20秒时间轴上。三路素材都使用48 kHz、双声道、S16LE格式,从项目零点开始;短素材补静音,长素材保留前20秒,不自动循环。它们拥有相同的时间尺度,也保留着各自的身份。

现在可以开始摆放声音了。

用户把铃声图标拖到右边,期待铃声出现在右边。雨声应该仍在窗边,翻书声应该仍在面前。这个动作看起来只是一次参数更新,却对底层结构提出了要求:程序必须知道,哪一段声音属于这个图标,以及哪一个处理节点只影响它。

假如三路声音已经混成一路,再给这一路设置位置,移动的就是整个组合。界面仍然可以画出三个图标,但音频图已经失去了分别摆位的入口。

因此,本篇先解决一个结构问题:如何让每段声音拥有自己的空间参数。随后再解决一个体验问题:这些参数造成的差异,听者能否辨认。两者相关,却需要不同的证据。

请添加图片描述

图02-D:雨声、翻书声和铃声分别保留素材身份。拖动铃声时,更新应沿着它自己的绑定关系到达空间节点。

2. 功能定义

OHAudioSuite提供的空间渲染节点是EFFECT_NODE_TYPE_SPACE_RENDER,从API 23开始支持。我们在HarmonyOS 7.0工程中使用它,但不能把这个节点本身算作API 26新增能力。

理解空间参数,首先要确定观察者。这里以听者为原点,使用左手坐标系:X正向右,Y正向上,Z正向前。三个轴的范围都是[-5,5]米。这里的米是渲染参数单位,不是设备测量出的房间尺寸。

编辑界面采用固定朝向的俯视图。向右拖动增加X,向上拖动增加Z,Y保持为0。屏幕纵坐标通常向下增长,因此它与Z的方向相反。把屏幕上的纵坐标直接填进空间Y轴,会把“移到前面”变成“移到上面”。

请添加图片描述

图02-A:主图展示听者周围的X/Z平面,侧图补充Y轴。UI坐标经过应用映射后才能成为空间参数,两套坐标不能直接互换。

可以把这项约定收进一个不依赖音频引擎的小函数。以下C++代码接收编辑区域内的局部坐标;宽高必须大于零,区域之外的拖动被限制在边界上。

#include <algorithm>
#include <cmath>
#include <stdexcept>

struct Position {
    float x;
    float y;
    float z;
};

Position mapToSpace(float px, float py, float width, float height)
{
    if (!std::isfinite(px) || !std::isfinite(py) ||
        !std::isfinite(width) || !std::isfinite(height) ||
        width <= 0 || height <= 0) {
        throw std::invalid_argument("invalid editor coordinates");
    }
    const float u = std::clamp(px, 0.0f, width) / width;
    const float v = std::clamp(py, 0.0f, height) / height;
    return {10.0f * u - 5.0f, 0.0f, 5.0f - 10.0f * v};
}

这个函数规定编辑区域覆盖完整参数范围。区域中心对应原点,左上角对应(-5,0,5)。它不负责决定某个位置是否好听,也不需要知道用户拖动的是哪种声音。

空间节点还支持旋转和扩展。固定摆位描述一个位置;旋转增加环绕周期与方向;扩展描述半径和角度。它们分别有对应的参数设置接口。本文只用固定摆位建立比较基线,旋转的时间基准留到第06篇处理。

职责也应保持清楚:ArkUI接收操作,工程模型保存配置,C/C++侧的OHAudioSuite处理音频图,OHAudio负责播放。声源身份和坐标映射由应用维护,音频引擎不会从一个铃铛图标推断出这些关系。

3. 方案与实现

对于三段声音,有两种直接的组网方式。

组网方案处理顺序空间参数的作用对象
方案A三路输入→混音→空间渲染→输出混合后的整体声音
方案B各路输入→各自空间渲染→混音→输出对应输入的声音

方案A适合把一段组合声音放在某个位置。例如,把整段广播当作一个声源时,这种结构就很自然。声音明信片要求分别摆放雨声和铃声,因此选择方案B。

这个选择来自编辑需求。我们并不需要证明方案B在所有方面都更好,只需要确认它保留了应用要操作的边界。增加独立空间节点也会增加处理工作,实时成本仍需要测量。

请添加图片描述

图02-B:左侧只有一个空间参数入口,右侧为每路声音保留独立入口。高亮路径表示修改铃声参数时,参数实际作用于哪一个节点。

在代码中,这个边界可以先表现为一个稳定的声源ID。素材位置、空间位置和播放游标虽然都与声源有关,却不应该放在同一种状态里。

#include <cstdint>
#include <string>

enum class SourceId { Rain, Page, Bell };
enum class SpatialMode { Fixed };

struct SourceSpec {
    SourceId id;
    std::string assetKey;
    uint64_t beginFrame;
    uint64_t endFrame;
    float gain;
    SpatialMode mode;
    Position position;
};

struct InputState {
    uint64_t nextFrame = 0;
};

SourceSpec描述“要播放什么”,InputState描述“这次读到了哪里”。素材键关联上一章保存的格式说明、文件和哈希;每路样本范围使用[0,960000),末端不包含在内。每次准备播放时,为每路输入建立自己的读取状态。

这一区分会在下一篇变得更重要。两套配置可以引用相同素材,但不能通过共享一个可变游标轮流消耗数据。

我们为两套内容配置保留固定名字:试听版本A叫“窗边阅读”,试听版本B叫“街角散步”。它们都使用组网方案B。组网方案描述处理结构,试听版本描述空间安排,不能把这两组A/B混在一起。

声源ID素材窗边阅读(X,Y,Z)街角散步(X,Y,Z)
rain雨声(-2,0,1)(0,0,-2)
page翻书声(0,0,1)(-1,0,1)
bell街角铃声(2,0,2)(2,0,1)

这些位置是应用选择的起始配置。两版使用相同素材、时间范围和逐声源增益,只改变位置。“街角散步”是场景名称,不意味着已经加入脚步素材或运动轨迹。调整坐标后,配置版本、图示和听辨记录也要一起更新。

请添加图片描述

图02-E:两张俯视图共用听者朝向与声源编号,分别对应参数表中的窗边阅读和街角散步。变化的是位置,素材和时间范围保持一致。

接入引擎时,先使用OH_AudioSuiteEngine_IsNodeTypeSupported检查所需节点类型,再创建管线及节点。方案B需要三个输入节点、三个空间节点、一个混音节点和一个输出节点。每个输入只连接自己的空间节点,三路空间结果汇入混音,最后进入唯一的输出。

固定摆位通过OH_AudioSuiteEngine_SetSpaceRenderPositionParams提交。输入和输出设置明确的PCM格式,其他效果节点不设置格式。下面用应用层伪代码表达准备流程,具体SDK调用集中在适配层中。

prepare(config):
    validateAssetsAndPositions(config)
    requireSupported(Input, SpaceRender, Mixer, Output)
    graph = createPipeline()
    try:
        mixer = graph.createMixer()
        output = graph.createOutput(projectFormat)
        for source in config.sources:
            input = graph.createInput(source, new InputState())
            space = graph.createSpaceRender()
            graph.setFixedPosition(space, source.position)
            graph.connect(input, space)
            graph.connect(space, mixer)
            graph.bind(source.id, space)
        graph.connect(mixer, output)
        graph.start()
        return graph
    catch error:
        graph.releaseCreatedResources()
        throw error

其中每一步都要检查返回结果。创建成功不代表连接成功,连接成功也不代表参数已经设置成功。支持查询、建图、设置参数或启动失败时,应停止准备并记录失败阶段,不能把一个只完成部分配置的图交给用户试听。

输入回调根据各自游标提供数据。音频编创采用反向驱动:输出侧请求渲染,需求沿图向上游传递,最终触发输入读取。三路素材不需要各自启动一个播放器。

修改位置时,通过声源ID找到对应空间节点。位置必须是有限数且处于合法范围。UI拖动可以限制到边界,读取外部配置则应明确拒绝非法值,避免悄悄改变已保存的配方。参数提交成功后再更新已应用状态;失败时保留旧状态及错误信息,界面也应反映这次操作未生效。

实时预览允许修改效果参数,但不支持重新创建效果节点。需要改变组网结构时,应结束当前播放流程再重新准备。提交的线程约束和资源释放顺序要遵守所用SDK接口契约。

本篇先让两个版本分别从零播放,同一时刻只输出一个版本。连续拖动的参数提交策略留给第04篇。这样,独立摆位的问题不会被切换同步和实时负载掩盖。

4. 测试

测试从最便宜的检查开始。坐标映射不需要耳机,声源绑定也不需要听辨。把这些问题先排除,才能让听觉实验回答它真正擅长的问题。

检查操作观察点
坐标映射输入中心、四角及越界坐标X/Z符号正确,Y为0,边界处理一致
参数校验输入越界数值、无穷或非数值非法配置被拒绝,旧配置不被覆盖
独立绑定只修改bell的位置rain和page的参数保持原值
能力与生命周期节点不支持或准备过程失败记录失败阶段,不进入可听播放

独立绑定测试使用窗边阅读的副本,只把铃声从(2,0,2)改为(-2,0,2)。这使左右变化更容易比较,同时保留相同距离。它不是正式两版之间的切换,不能覆盖原来的窗边配方。

接下来先做单声源判断,再做三路混音判断。诊断素材使用分离的提示音、旁白和环境声,单独建立素材组,沿用相同格式和20秒规则。正式场景验收仍使用雨声、翻书声和街角铃声。诊断素材帮助定位问题,不会成为明信片的第四条轨道。

左右和前后应分开比较,每次只改变目标方向,保持素材、增益和距离一致。随后加入其他声音,观察混音后是否更难辨认。单路铃声可以被定位,并不能推出混合后的铃声仍然同样清楚。

试听需要固定设备、系统、SDK、输出路由、耳机或扬声器、音量以及额外音效开关。更换输出设备时另建测试组,否则设备变化会混入配置差异。

相同输入增益也不保证输出响度完全相同。空间处理后应检查峰值和响度;若需要制作响度匹配的听辨副本,保存原始输出,记录测量方法与整段缩放量。不要单独提高铃声增益,让“更响”代替“方向更清楚”。若发生削波,统一修订增益基线后重新比较两版。

匿名听辨时,听者只看到样本ID,不看到场景名称、坐标和图标。播放顺序随机,人数、重复次数在开始前确定。回答提供“不确定”选项;喜欢哪个版本另行记录,偏好不能替代方向判断。

请添加图片描述

图02-C:听者端仅展示匿名样本,实验者保留配置答案。结果按配置方向与判断方向汇总,并保留“不确定”;人数、次数和设备条件随记录一同保存。

一条记录可以采用下面的结构。期望方向由实验者保管,不传给听者界面。

sampleId, materialGroup, configRevision, graphScheme,
sourceId, expectedDirection, listenerId, playOrder,
device, outputRoute, volume, effectSettings,
parameterResult, judgedDirection, identifiable, notes

这里特意同时保留parameterResult和judgedDirection。前者回答接口是否接受参数,后者回答听者如何判断。如果参数设置成功,但方向判断集中在“不确定”,接下来应检查素材、输出条件和摆位设计,而不是把成功返回值写成体验结论。

两种组网也需要对照。方案A改变整体空间节点,方案B只改变铃声节点,比较时固定素材和增益。记录分别针对铃声、雨声和翻书声提问。即使后两路参数未变,铃声的变化仍可能影响注意力和掩蔽关系,因此不能要求主观描述逐字一致。

最终报告应分别呈现调用失败、左右判断、前后判断、三路可辨识性和不确定反馈。前后定位更困难、某些声音被掩蔽,都是需要保留的结果。没有足够记录时,先保留表头和用例;有了记录,再把结论限定在相应素材与设备条件内。

5. 结论

让雨声和铃声各就各位,需要在混音前保留独立的空间处理入口,并让声源身份贯穿界面、工程配置和音频节点。坐标映射决定操作表达了什么,组网结构决定参数能够影响什么,听辨记录决定用户实际辨认出了什么。

这三层各有自己的检查方式。把它们分开,错误就更容易定位:图标方向反了,检查坐标映射;移动铃声却改了整个组合,检查节点绑定;调用成功但方向难辨,检查素材和播放条件。

本篇留下的坐标约定、两套声源参数、组网对照和匿名记录字段,可以共同约束后续实现。它们描述固定摆位,不依赖头部追踪,也不保证不同耳机提供相同结果。动态运动、离线产物和分享封装仍有各自的问题要解决。

下一篇继续使用同一批20秒素材,以及“窗边阅读”和“街角散步”两套配置。新的问题是:当用户在第8秒切换版本时,怎样确保比较的仍然是素材的同一秒?

参考:
空间音频渲染
音频编创开发概述
实时预览
离线编辑

Logo

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

更多推荐