在 USD 场景中编排媒体资产:usdMedia 域之 SpatialAudio 与 AssetPreviewsAPI 详解
【免费下载链接】OpenUSDUniversal Scene Description项目地址: https://gitcode.com/GitHub_Trending/ope/OpenUSD
UsdMedia是 USD(Universal Scene Description)中负责将音频、缩略图等媒体信息与资产关联的 schema 域,本文基于 OpenUSD 仓库中 docs/user_guides/schemas/usdMedia 下的官方 schema 文档展开。读完本文,你将掌握用SpatialAudio在 USD 场景中实现空间/非空间音频播放、用AssetPreviewsAPI为资产挂接多规格缩略图的方法,理解timecode属性与 layer offset 的相互作用,并能通过源码与测试用例验证其底层行为。
usdMedia 域能做什么
根据官方文档 overview.md 的定义,UsdMedia域为资产提供了两类媒体能力的关联方式:
SpatialAudio:允许环境(ambient)音频播放,或从场景中某个具体位置发出的空间音频播放,并可配置多种播放选项;AssetPreviewsAPI:允许为资产设置一个或多个缩略图。这些缩略图可以是资产的预渲染图像,既可用于 DCC 工具的资源浏览器(asset browser),也可用于操作系统级的文件浏览器图标(如 macOS Finder)。
两个 schema 可以同时使用:文档中的assetPreviewsApi.usda示例就同时挂接了AssetPreviewsAPI与两个SpatialAudioprim。下面先分别深入两个 schema,最后给出整合示例。
SpatialAudio:在场景中编排音频播放
Schema 层级与设计意图
从 schema.usda 可以看到,SpatialAudio是一个具体类型(concrete typed)schema,它继承自UsdGeomXformable:
class SpatialAudio "SpatialAudio" ( inherits = </Xformable> ... ) { ... }继承Xformable是有意为之的设计:SpatialAudio 既支持完整的空间音频,也支持非空间的单声道/立体声播放。由于它携带变换信息,一个或多个 SpatialAudio prim 可以放置在命名空间中的任意位置。官方 schema 注释特别建议:真正需要空间定位的音频 prim 应嵌套在声源模型之下,这样音频 prim 只需相对模型做变换,而不必复制模型的动画(详见 spatialAudio.h 的类注释)。
对应生成的 C++ 类UsdMediaSpatialAudio位于 spatialAudio.h,其schemaKind为UsdSchemaKind::ConcreteTyped,Python 侧则通过 wrapSpatialAudio.cpp 暴露为UsdMedia.SpatialAudio。
典型用法示例
官方文档 SpatialAudio.md 给出了同时包含Speech与Ambient两个音频 prim 的典型场景:
#usda 1.0 ( defaultPrim = "World" endTimeCode = 2400 startTimeCode = 0 timeCodesPerSecond = 24 ) def Xform "World" { def Cube "Cube" { double3 xformOp:translate = (1, 5, -2) uniform token[] xformOpOrder = ["xformOp:translate"] def SpatialAudio "Speech" { uniform token auralMode = "spatial" uniform timecode endTime = 480 uniform asset filePath = @mySpeech.mp3@ uniform token playbackMode = "onceFromStartToEnd" uniform timecode startTime = 240 } } def SpatialAudio "Ambient" { uniform token auralMode = "nonSpatial" uniform asset filePath = @myAmbientTrack.mp3@ uniform token playbackMode = "loopFromStage" } }从示例中可以观察出以下要点:
- 空间 vs 非空间:
Speech的auralMode = "spatial",从场景中 Cube 所在位置发声;Ambient的auralMode = "nonSpatial",无论听者位置如何,听起来都一样; - 嵌套于 gprim 之下:
Speech嵌套在Cube内部,当 Cube 被移动或动画化时,音频源会跟随其位置; - 播放时长换算:
Speech播放 10 秒,计算方式为(endTime - startTime) / timeCodesPerSecond = (480 - 240) / 24 = 10; - 音频文件内的时间窗:
mySpeech.mp3从文件第startTime / timeCodesPerSecond = 240 / 24 = 10秒处开始播放,到第480 / 24 = 20秒处结束; - 是否循环:
Speech因playbackMode = "onceFromStartToEnd"不循环;Ambient因playbackMode = "loopFromStage"循环播放; - 覆盖整个场景:
Ambient覆盖场景的全部 100 秒(stage 的startTimeCode = 0到endTimeCode = 2400,即 2400/24 = 100 秒)。
属性详解
SpatialAudio的 7 个自有属性定义于 schema.usda,以下参数说明均来自官方文档 SpatialAudio.md 的属性章节。
filePath
- USD 类型:
asset - 默认值:
@@(空资产路径) - 含义:音频文件路径。预期支持 m4a、mp3、wav 格式。
需要说明的是,USD 本身对音频格式的限制并不比图片格式更严格(参见 schema.usda 中filePath的 doc 注释);但usdz 有更严格的约束——基于 DMA(Digital Media Asset)规范及浏览器、消费级设备的格式支持,usdz 允许的音频文件类型按优先级依次为M4A、MP3、WAV。因此,若要打包进 usdz 资产,应优先选用 m4a。
auralMode
- USD 类型:
token - 默认值:
spatial - 允许值:
spatial、nonSpatial - 含义:决定音频如何播放:
spatial:如果设备支持空间音频,则在 3D 空间中播放,否则回退为单声道(mono)。适用于角色等需要声音始终从其位置发出的对象;nonSpatial:不考虑 SpatialAudio prim 的位置。如果媒体本身包含立体声或多声道内容,是否考虑听者位置由应用程序决定。官方期望nonSpatial用于环境音与背景音乐音轨。
值得注意,spatial与nonSpatial的枚举值定义在 tokens.h,通过UsdMediaTokens->spatial等 token 访问;auralMode 属性的 C++ 访问接口为GetAuralModeAttr()/CreateAuralModeAttr()(见 spatialAudio.h)。
playbackMode
- USD 类型:
token - 默认值:
onceFromStart - 允许值:
onceFromStart、onceFromStartToEnd、loopFromStart、loopFromStartToEnd、loopFromStage - 含义:决定音频播放的整体规则,用于指定何时开始、何时停止以及是否循环。
各取值与“是否循环 / 开始时间 / 结束时间”的关系见下表(原文档表格):
| Value | Audio Loops? | StartTime | EndTime |
|---|---|---|---|
onceFromStart | 否 | prim 的startTime | 音频文件末尾 |
onceFromStartToEnd | 否 | prim 的startTime | prim 的endTime,或音频文件末尾(取先到者) |
loopFromStart | 是 | prim 的startTime | stage 的endTimeCode |
loopFromStartToEnd | 是 | prim 的startTime | prim 的endTime |
loopFromStage | 是 | stage 的startTimeCode | stage 的endTimeCode |
startTime
- USD 类型:
timecode - 默认值:
0 - 含义:以 timeCode 为单位,从音频文件开头算起的开始播放偏移。
换算示例(来自文档):一个 10 秒的音频文件,stage 的timeCodesPerSecond为 24,startTime设为 24.0,则音频从文件第 1 秒处开始播放(24 / 24 = 1)。
startTime使用timecode而非普通 double 类型,是为了让 stage 在值解析时能正确应用 layer offset(详见下文“Layer Offsets”一节)。当playbackMode = "loopFromStage"时该值被忽略,因为此模式下音频总是从 stage 的startTimeCode开始(见 schema.usda)。
endTime
- USD 类型:
timecode - 默认值:
0 - 含义:以 timeCode 为单位,从音频文件开头算起的结束播放偏移。仅当引用的音频片段比期望播放时长更长时生效。
换算示例(来自文档):一个 10 秒的音频文件,stage 的timeCodesPerSecond为 24,endTime设为 48.0,则音频在文件第 2 秒处停止播放(48 / 24 = 2)。
endTime仅在playbackMode为onceFromStartToEnd或loopFromStartToEnd时生效,否则使用 stage 的endTimeCode代替。一个值得注意的边界行为:如果endTime小于startTime,预期音频将从endTime播放到startTime(即反向区间),详见 schema.usda。
mediaOffset
- USD 类型:
double - 默认值:
0.0 - 含义:以秒为单位,当 stage 播放到达该 prim 音频应开始的时刻时,从被引用音频文件开头算起的播放偏移量。
换算示例(来自文档):一个 10 秒的音频文件,mediaOffset为 3.0,则前 3 秒不播放,之后从音频文件开头开始播放。对于循环音频,该偏移仅作用于第一轮播放,第二轮及之后都从音频文件开头开始(见 schema.usda)。
与startTime/endTime不同,mediaOffset的单位是秒而非 timeCode,因此在 layer offset 作用于 timecode 属性时,mediaOffset不受影响——这一点在测试用例中有直接体现(见下文)。
gain
- USD 类型:
double - 默认值:
1.0 - 含义:音频信号的倍率。值为 0 时表示静音;负值会被钳制为 0(见 schema.usda)。
继承自 Xformable/Imageable 的属性
由于SpatialAudio继承自UsdGeomXformable(进而继承UsdGeomImageable),它还包含以下继承属性(见 SpatialAudio.md 的属性章节):
| 属性 | USD 类型 | 默认值 | 说明 |
|---|---|---|---|
xformOpOrder | token[] | — | 变换操作顺序(Xformable) |
proxyPrim | rel | — | 代理 prim(关系属性) |
purpose | token | default | prim 的用途(Imageable) |
visibility | token | inherited | 可见性(Imageable) |
SpatialAudio 与 Layer Offsets
这是SpatialAudio使用中最容易忽略也最重要的一个行为(原文档专门设有 "SpatialAudio and Layer Offsets" 一节):
- 当某 layer 带有layer offset时,该偏移信息会在**值解析(value resolution)**阶段被应用到
timecode类型的属性值上; - 由于
startTime和endTime都是timecode属性,这意味着SpatialAudio 的播放时间会被 layer offset 调整,从而可以与同一 layer 中时间采样(time sampled)的动画保持同步; - 重要限制:如果 layer offset 包含时间缩放(time scale),USD 不会对实际音频媒体做任何播放拉伸(playback dilation)。schema 的 doc 注释解释了原因(schema.usda):由于
startTime和endTime可以在具有不同时间缩放的不同 layer 中独立编写,通常无法定义用于计算拉伸的“原始时间框架”;即便能计算出组合后的拉伸,也不可能在将 stage 或 layer stack 拍平(flatten)为单个 layer 时保留组合后的音频拉伸效果。
该行为在测试用例 testUsdMediaSpatialAudio.py 中得到了直接验证:测试对RefAudio建立引用并施加layerOffset = Sdf.LayerOffset(scale=2.0, offset=10.0),随后在引用层分别设置startTime = 10、endTime = 200。组合解析后,外层 prim 的GetStartTimeAttr().Get()返回Gf.TimeCode(30)(10 × 2 + 10),GetEndTimeAttr().Get()返回Gf.TimeCode(410)(200 × 2 + 10)——偏移与缩放都被应用;而mediaOffset = 5.0保持不变,印证了其以秒为单位、不参与 timecode 偏移的特性。
AssetPreviewsAPI:为资产挂接缩略图预览
应用式 Schema 与 assetInfo 编码
与SpatialAudio不同,AssetPreviewsAPI是一个应用式(applied)API schema,类型为singleApply(见 schema.usda 中的customData.apiSchemaType = "singleApply"),继承自UsdAPISchemaBase。其核心特点:
- 它可以被应用到 stage 上任意数量的 prim;
- 要访问 stage 的“默认”预览,需查看 stage 的
defaultPrim; - 该 schema不定义任何属性或元数据回退值——预览信息完全编码在 prim 的
assetInfo元数据中; - 由于只涉及资产路径,直接消费返回数据的客户端需要通过会话的
ArAssetResolver获取ArAsset(见 assetPreviewsAPI.h)。
assetInfo中的字典键由 schema token 定义(schema.usda):
| Token | 值 | 含义 |
|---|---|---|
previews | previews | assetInfo 中预览子字典的键 |
thumbnails | thumbnails | previews 字典中缩略图子字典的键 |
defaultImage | defaultImage | 缩略图字典中默认图像的键 |
previewThumbnails | previews:thumbnails | 缩略图字典的完整路径键 |
previewThumbnailsDefault | previews:thumbnails:default | “默认”缩略图的完整路径键 |
缩略图字典结构与多规格支持
官方文档 AssetPreviewsAPI.md 给出的示例展示了一个资产可以同时关联多个缩略图,并指定其中一个为默认:
#usda 1.0 ( defaultPrim = "World" metersPerUnit = 0.01 upAxis = "Y" ) def Xform "World" ( prepend apiSchemas = ["AssetPreviewsAPI"] assetInfo = { dictionary previews = { dictionary thumbnails = { dictionary default = { asset defaultImage = @defaultThumbnail.jpg@ } dictionary highResolution = { asset defaultImage = @highResolution.jpg@ } dictionary wireFrame = { asset defaultImage = @wireFrame.jpg@ } } } } ) { def Cube "Cube" { } }要点:
AssetPreviewsAPI必须被应用(通过prepend apiSchemas = ["AssetPreviewsAPI"]),否则任何查询 API 都不会成功;- 缩略图按“命名子字典 + 统一的
defaultImage键”组织:上例中有default、highResolution、wireFrame三套缩略图,其中default是默认缩略图; - 这样应用就可以按需支持不同质量或风格的缩略图;
- 缩略图没有规定尺寸,但官方 schema 注释提醒:要注意缩略图的引入不应显著增大资产整体体积(例如打包进 USDZ 时),见 assetPreviewsAPI.h。
编程接口:Thumbnails 与 Get/Set/Clear
UsdMediaAssetPreviewsAPI类(生成头文件 assetPreviewsAPI.h)在生成代码之外还提供了自定义 API:
Thumbnails值类型:作为序列化/反序列化assetInfo["previews:thumbnails"]字典的辅助结构,目前持有defaultImage(SdfAssetPath类型)一个成员;GetDefaultThumbnails(Thumbnails*):读取默认缩略图数据,成功返回true。实现通过prim.GetAssetInfoByKey(UsdMediaTokens->previewThumbnailsDefault)取字典,再以defaultImagetoken 取图像路径(见 assetPreviewsAPI.cpp);SetDefaultThumbnails(const Thumbnails&):将缩略图数据写回assetInfo["previews:thumbnails:default"],内部使用prim.SetAssetInfoByKey(...)(assetPreviewsAPI.cpp);ClearDefaultThumbnails():在当前UsdEditTarget中删除默认缩略图的完整条目,内部调用prim.ClearAssetInfoByKey(...)(assetPreviewsAPI.cpp);- 静态工厂
GetAssetDefaultPreviews(layerPath / layer):以layerPath(或SdfLayerHandle)构造一个最小 stage,并返回针对该 stage 的defaultPrim的预览 schema 对象;通过字符串路径调用等价于GetAssetDefaultPreviews(SdfLayer::FindOrOpen(layerPath))。
上述 API 在 Python 侧通过 wrapAssetPreviewsAPI.cpp 暴露为UsdMedia.AssetPreviewsAPI。
测试用例验证的行为边界
测试 testUsdMediaAssetPreviewsAPI.py 验证了几个关键行为,值得在使用时注意:
- 未应用 schema 时一切查询失败:仅构造
UsdMedia.AssetPreviewsAPI(xform)并调用SetDefaultThumbnails后,GetDefaultThumbnails()仍返回False——因为 schema 尚未应用; Apply之后才生效:调用UsdMedia.AssetPreviewsAPI.Apply(xform.GetPrim())后,GetDefaultThumbnails()才能取回写入的defaultImage;GetAssetDefaultPreviews依赖defaultPrim:stage 未设置defaultPrim时,GetAssetDefaultPreviews(layer)返回失败;设置stage.SetDefaultPrim(...)后才成功;ClearDefaultThumbnails后数据为空:清除后GetDefaultThumbnails()返回None。
整合示例:同时使用两个 Schema
官方文档 overview.md 提供的assetPreviewsApi.usda将两个 schema 整合在同一个文件中——既为资产提供缩略图预览,又在场景中编排两段音频:
#usda 1.0 ( defaultPrim = "World" endTimeCode = 2400 startTimeCode = 0 timeCodesPerSecond = 24 ) def Xform "World"( prepend apiSchemas = ["AssetPreviewsAPI"] assetInfo = { dictionary previews = { dictionary thumbnails = { dictionary default = { asset defaultImage = @defaultThumbnail.jpg@ } } } } ) { def Cube "Cube" { double3 xformOp:translate = (1, 5, -2) uniform token[] xformOpOrder = ["xformOp:translate"] def SpatialAudio "Speech" { uniform token auralMode = "spatial" uniform timecode endTime = 480 uniform asset filePath = @mySpeech.mp3@ uniform token playbackMode = "onceFromStartToEnd" uniform timecode startTime = 240 } } def SpatialAudio "Ambient" { uniform token auralMode = "nonSpatial" uniform asset filePath = @myAmbientTrack.mp3@ uniform token playbackMode = "loopFromStage" } }从该示例中可以观察到:
- 空间与非空间音频的差异:
Speech(空间,跟随 Cube 位置)与Ambient(非空间,环境铺底)并存; - 缩略图的关联方式:
assetInfo.previews.thumbnails.default.defaultImage为资产提供了默认预览图,可被 DCC 或系统资源浏览器直接消费; - 嵌套的价值:
Speech嵌套在Cubegprim 之下,Cube 被移动或动画时,声源自动跟随。
文档还给出了缩略图信息的实际呈现效果:macOS 的 Finder 文件浏览器会把defaultThumbnail.jpg直接用作assetPreviewsApi.usda文件的图标(见上文配图,原图位于 docs/user_guides/schemas/usdMedia/usdMediaExample.jpg),这正是 AssetPreviewsAPI 在系统级资源浏览场景中的直观体现。
要点速查
- 两个 schema 分工明确:
SpatialAudio(concrete typed,继承UsdGeomXformable)负责场景音频播放;AssetPreviewsAPI(single-apply 应用式 API schema)负责把缩略图写进 prim 的assetInfo元数据,二者可同时应用于同一资产。 - SpatialAudio 属性速记:
filePath(m4a/mp3/wav,usdz 按 M4A、MP3、WAV 优先级)、auralMode(spatial/nonSpatial)、playbackMode(5 种取值,决定循环与起止)、startTime/endTime(timecode,受 layer offset 影响)、mediaOffset(秒,循环时仅首轮生效)、gain(倍率,负值钳为 0)。 - timecode 的双刃剑:
startTime/endTime使用timecode类型使音频与动画在 layer offset 下保持同步,但 layer offset 的时间缩放不会对音频媒体做拉伸。 - AssetPreviewsAPI 必须 Apply:无论 C++ 还是 Python,未应用 schema 的 prim 上所有预览查询都会失败;默认预览总是查 stage 的
defaultPrim。 - 深入阅读路径:schema 权威定义见 pxr/usd/usdMedia/schema.usda,生成 API 见 spatialAudio.h 与 assetPreviewsAPI.h,行为验证见 testUsdMediaSpatialAudio.py 与 testUsdMediaAssetPreviewsAPI.py。
【免费下载链接】OpenUSDUniversal Scene Description项目地址: https://gitcode.com/GitHub_Trending/ope/OpenUSD
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考