空间音效
更新时间: 2024/09/18 16:26:13
空间音效也称 3D 音效,是通过在音频信号中添加空间信息,使得听众可以感受到声音来自于特定的位置和空间环境。它可以增强音频的真实感和沉浸感,让听众感受到更加真实的声音效果。
功能介绍
NERTC SDK 从 V5.4.0 开始支持空间音效。
空间音效可以将实时语音渲染成具有空间方位的效果。同时,空间音效具有房间混响,距离衰减和范围语音等属性,综合使用相关属性可以提升空间音效的真实感和沉浸感。
例如,在游戏中模拟真实世界的空间音效,玩家可以感知说话者的 3D 空间方位。同时,空间音效可以根据双方距离的远近实现语音音量的衰减,使近处的声音更响亮,远处的声音更低。这样可以让玩家更清晰地感受到游戏中的环境和位置信息,增强游戏的沉浸感和真实感。此外,空间音效还可以应用于虚拟现实和增强现实等领域,让用户更真实地感受虚拟环境中的声音效果。
功能原理
空间音效涉及到音频源和接收者两个对象。在RTC场景下,开启空间音效时,发送端会在语音中包含自身的坐标信息,远端接收该音频信息时,会将其作为音频源。接收者会基于自身坐标、接收到的音频及其位置,将音频渲染到特定方位,然后进行播放。
空间音效利用头部相关转换函数(HRTF)和声波空间卷积模仿自然声波的传播,使其仿佛来自三维空间中的一个点。空间音效通过方向、距离和环境三个关键因素实现空间感的形成。
空间音效的距离衰减和语音范围如下图所示。
注意事项
需要用户佩戴有线耳机体验空间音效功能,暂不支持蓝牙耳机。
API 调用时序
sequenceDiagram
participant 应用层
participant NERtcSDK
participant 云信服务器
Note over 应用层, 云信服务器: 启用并配置空间音效
rect rgb(191, 223, 255)
应用层->>NERtcSDK: initSpatializer
应用层->>NERtcSDK: enableSpatializer
NERtcSDK -->>应用层: return code
应用层->>NERtcSDK: setAudioRecvRange
NERtcSDK-->>应用层: return code
应用层->>NERtcSDK: setSpatializerRenderMode
NERtcSDK-->>应用层: return code
应用层->>NERtcSDK: setAudioProfile
NERtcSDK-->>应用层: return code
应用层->>NERtcSDK: setSpatializerRoomProperty
应用层->>NERtcSDK: enableSpatializerRoomEffects
end
Note over 应用层, 云信服务器: 加入房间
应用层->>NERtcSDK: joinChannel
NERtcSDK->>云信服务器: joinChannel
云信服务器->>NERtcSDK: onJoinChannel
NERtcSDK->>应用层: onJoinChannel
Note over 应用层, 云信服务器: 添加或更新位置信息
rect rgb(191, 223, 255)
应用层->>NERtcSDK: updateSelfPosition
NERtcSDK->>云信服务器: Voice data setting & rendering
end
实现方法
1. 启用空间音效
-
请在引擎初始化之后(
setUpEngineWithContext
),调用initSpatializer
方法初始化空间音效。 -
加入房间(
joinChannelWithToken
)之前调用enableSpatializer
方法以启用音频空间化, 如果您需要只针对本小队开启 3D 音效, 设置apply_to_team
为true
。
示例代码如下:
//打开空间音效功能
- (void)enable3DAudio {
//需要在执行完`setupEngineWithContext:`接口之后调用
[[NERtcEngine sharedEngine] initSpatializer];
[[NERtcEngine sharedEngine] enableSpatializer:YES applyToTeam:NO];
//audio profile must be stereo,2 channels
[[NERtcEngine sharedEngine] setAudioProfile:kNERtcAudioProfileHighQualityStereo scenario:kNERtcAudioScenarioMusic];
}
2. 设置距离衰减和语音范围
在加入房间前,调用 setAudioRecvRange
方法设置空间音效的距离衰减属性和语音范围,该属性可以让声音随着音频源和接收者之间距离的增加逐渐衰减音量。
- 距离衰减属性需要在加入房间之前设置,在通话过程中无法修改该属性,只有退出房间后才可重新设置。
- 空间音效房间内的成员需要都开启或都关闭,否则会无法听到对方声音。
参数描述如下表所示:
参数 | 描述 |
---|---|
audibleDistance | 监听器能接收到音频的最大距离,用户的声音在该范围内可被听见。取值范围:[1, max int) ,无默认值。 |
conversationalDistance | 监听器不对音频进行衰减的距离,在该距离范围内,扬声器音频保持其原始音量,超出该范围时,声音将会随距离的增加而衰减。默认值为 1。 |
rollOff |
距离衰减模式。一共有三种:
|
示例代码如下:
// 设置接收范围和声音衰减模型
-(void)setup3DAudioRecvRange {
//需要在`EnableSpatializer`执行成功之后调用。
[[NERtcEngine sharedEngine] setAudioRecvRange:50 conversationalDistance:1 rollOff:kNERtcDistanceRolloffNone];
// ...
}
3. 设置渲染模式
在加入房间前,调用 setSpatializerRenderMode
方法设置渲染模式,通过设置 mode
参数选择不同复杂程度的算法以实现不同的听觉效果。
其中 NERtcSpatializerRenderMode
类型的各字段说明如下表所示。
字段 | 描述 |
---|---|
kNERtcSpatializerRenderStereoPanning | 立体声 PANNing 方法 |
kNERtcSpatializerRenderBinauralLowQuality | 低复杂度双耳渲染方法 |
kNERtcSpatializerRenderBinauralMediumQuality | 中复杂度双耳渲染方法 |
kNERtcSpatializerRenderBinauralHighQuality | 高复杂度双耳渲染方法(推荐) |
kNERtcSpatializerRenderRoomEffectsOnly | 仅房间混响 |
示例代码如下:
// 设置空间语音渲染模式
-(void)setup3DAudioRenderMode {
//需要在`EnableSpatializer`执行成功之后调用。
[[NERtcEngine sharedEngine] setSpatializerRenderMode:kNERtcSpatializerRenderBinauralHighQuality];
// ...
}
4. 设置音频属性
在加入房间前,调用 setAudioProfile
方法将音频类型(NERtcAudioProfileType
)设置为 kNERtcAudioProfileMiddleQualityStereo
或者 kNERtcAudioProfileHighQualityStereo
,并将音频场景(NERtcAudioScenarioType
)设置为 kNERtcAudioScenarioMusic
。
// 设置音频属性
-(void)setup3DAudioProfile {
//需要在`setupEngineWithContext`执行成功之后,`JoinChannel`之前调用
[[NERtcEngine sharedEngine] setAudioProfile:kNERtcAudioProfileMiddleQualityStereo scenario:kNERtcAudioScenarioMusic];
// ...
}
5. 设置房间混响属性(可选)
- 调用
setSpatializerRoomProperty
方法设置roomProperty
参数预设的房间大小,混响时长,混响增益,音色亮度等参数,以调整房间混响效果。
其中 NERtcSpatializerRoomProperty
类型的各字段说明如下表所示。
参数 | 类型 | 描述 |
---|---|---|
roomCapacity | NERtcSpatializerRoomCapacity | 房间大小,默认值为 kNERtcSpatializerRoomCapacitySmall 。房间大小的枚举值请参见 NERtcSpatializerRoomCapacity |
material | NERtcSpatializerMaterialName | 房间材质,默认值为 kNERtcSpatializerMaterialTransparent 。更多的房间材质请参见 NERtcSpatializerMaterialName |
reflectionScalar | CGFloat | 混响反射比例因子,默认值为 1.0 |
reverbGain | CGFloat | 混响增益比例因子,默认值为 1.0 |
reverbTime | CGFloat | 混响时间比例因子,默认值为 1.0 |
reverbBrightness | CGFloat | 混响音色亮度,默认值为 1.0 |
-
调用
enableSpatializerRoomEffects
方法,设置enable
参数为YES
或NO
以开启或关闭空间音效的房间混响效果。 -
调用
enableSpatializer
方法,设置enable
参数为YES
或NO
以开启或关闭 3D 音效。3D 音效可以让声音有 3D 空间感且按距离衰减。
6. 添加或更新位置信息
在加入房间后,调用 updateSelfPosition
方法并设置 info
参数,以更新音频源或接收者的空间位置信息 NERtcPositionInfo
,从而实现空间音频定位特效。
- 通常在 3D 网络游戏开始后,都能获取地图上角色
GameObject
相互的坐标,此坐标即调用updateSelfPosition
方法时需要更新的角色坐标。 - 建议在一定间隔内更新角色位置信息,以保证画面和音频位置同步,推荐更新频率为 10 ~ 25次/秒。
- 发声者旋转角度的参数值暂时不起作用,保持默认值即可。
参数 | 类型 | 描述 |
---|---|---|
speakerPositionX | CGFloat | 发声坐标,表示左右,默认值为 0 |
speakerPositionY | CGFloat | 发声坐标,表示上下,默认值为 0 |
speakerPositionZ | CGFloat | 发声坐标,表示前后,默认值为 0 |
speakerQuaternionX | CGFloat | 发声旋转角度,4元素,默认值为 0 |
speakerQuaternionY | CGFloat | 发声旋转角度,4元素,默认值为 0 |
speakerQuaternionZ | CGFloat | 发声旋转角度,4元素,默认值为 0 |
speakerQuaternionW | CGFloat | 发声旋转角度,4元素,默认值为 0 |
headPositionX | CGFloat | 听觉坐标,表示左右,默认值为 0 |
headPositionY | CGFloat | 听觉坐标,表示上下,默认值为 0 |
headPositionZ | CGFloat | 听觉坐标,表示前后,默认值为 0 |
headQuaternionX | CGFloat | 听觉旋转角度,4元素,默认值为 0 |
headQuaternionY | CGFloat | 听觉旋转角度,4元素,默认值为 0 |
headQuaternionZ | CGFloat | 听觉旋转角度,4元素,默认值为 0 |
headQuaternionW | CGFloat | 听觉旋转角度,4元素,默认值为 0 |
示例代码如下:
//新建一个定时器,每200ms调用一次下面的函数,就会有3d音效旋转的效果
- (void)onRotationAStep {
NSMutableArray *position = [NSMutableArray arrayWithObjects:@9.0, @0.0, @9.0, nil]; // 初始化位置数组
NSMutableArray *queternion = [NSMutableArray arrayWithObjects:@0.0, @0.0, @0.0, @0.0, nil];// 初始化旋转数组
NSInteger size = [self.roomCapacityPickerView selectedRowInComponent:0];
if (size == 0) {
position[0] = @1.0;
position[2] = @1.0;
} else if (size == 1) {
position[0] = @2.0;
position[2] = @2.0;
} else if (size == 2) {
position[0] = @4.0;
position[2] = @4.0;
} else if (size == 3) {
position[0] = @9.0;
position[2] = @9.0;
} else {
position[0] = @9.0;
position[2] = @9.0;
}
_rotation_step++;
if (_rotation_step == 180) {
_rotation_step = 0;
}
// 计算旋转数组的值
queternion[0] = @0.0;
queternion[1] = @(sin((2.0 * M_PI * _rotation_step) / 180.0));
queternion[2] = @0.0;
queternion[3] = @(cos((2.0 * M_PI * _rotation_step) / 180.0));
[self updateSpatializerInfoUIWithPosition:position queternion:queternion]; //更新UI显示
NERtcPositionInfo *spatializerPositionInfo = [[NERtcPositionInfo alloc] init];
spatializerPositionInfo.speakerPositionX = [self.speakerPositionTextField0.text doubleValue];
spatializerPositionInfo.speakerPositionY = [self.speakerPositionTextField1.text doubleValue];
spatializerPositionInfo.speakerPositionZ = [self.speakerPositionTextField2.text doubleValue];
spatializerPositionInfo.speakerQuaternionX = [self.speakerQuaternionTextField0.text doubleValue];
spatializerPositionInfo.speakerQuaternionY = [self.speakerQuaternionTextField1.text doubleValue];
spatializerPositionInfo.speakerQuaternionZ = [self.speakerQuaternionTextField2.text doubleValue];
spatializerPositionInfo.speakerQuaternionW = [self.speakerQuaternionTextField3.text doubleValue];
spatializerPositionInfo.headPositionX = [self.headPositionTextField0.text doubleValue];
spatializerPositionInfo.headPositionY = [self.headPositionTextField1.text doubleValue];
spatializerPositionInfo.headPositionZ = [self.headPositionTextField2.text doubleValue];
spatializerPositionInfo.headQuaternionX = [self.headQuaternionTextField0.text doubleValue];
spatializerPositionInfo.headQuaternionY = [self.headQuaternionTextField1.text doubleValue];
spatializerPositionInfo.headQuaternionZ = [self.headQuaternionTextField2.text doubleValue];
spatializerPositionInfo.headQuaternionW = [self.headQuaternionTextField3.text doubleValue];
int result = 0;
if (self.channel) {
result = [_channel.channel updateSelfPosition:spatializerPositionInfo];
}
else{
result = [[[NTESDemoLogic sharedLogic] getCoreEngine] updateSelfPosition:spatializerPositionInfo];
}
NTESCheckResultAndReturn(result, "updateSelfPosition");
}