在语聊房、K 歌房等实时音频场景中,我们经常能看到随着用户说话或唱歌,界面上会出现动态的声浪波形或音量柱状图。这种视觉反馈不仅让用户感知到音频正在传输,还能增强互动体验。那么,这种声浪效果是如何实现的呢?本文将基于ZEGO 实时音视频 SDK(ZEGO Express SDK),以 Android 平台为例详细讲解声浪效果的技术实现。

一、什么是声浪效果
1.1 什么是声浪?
声浪效果是指根据音频信号的音量大小,实时在界面上渲染出动态的视觉效果。常见的表现形式包括:
- 音量波动动画:头像周围的圆环随音量大小变化
- 柱状图频谱:类似音乐播放器的频谱可视化
- 波形动画:模拟声波传播的动态效果
1.2 声浪常见的应用场景
- 语聊房:显示正在说话的用户
- K 歌房:展示主播的音调与音量变化
- 视频会议:标识当前发言人
- 在线教育:提示学生发言状态
1.3 声浪实现的技术原理
声浪效果的实现流程可以概括为三个步骤:
音频采集 → 音量计算 → UI 渲染
ZEGO Express SDK 负责音频采集和音量计算,开发者只需监听回调并将数据渲染到 UI 上。
二、ZEGO SDK 提供的声浪能力
ZEGO Express SDK 提供了完整的声浪监控能力,包括声浪(音量)和音频频谱两种数据类型。
2.1 核心 API 介绍
声浪监控 API
// 启动声浪监控(支持版本:1.1.0+)
public void startSoundLevelMonitor(int millisecond)
// 停止声浪监控
public void stopSoundLevelMonitor()
参数说明:
millisecond:声浪回调周期,单位毫秒,取值范围 [100, 3000],默认 100ms
音频频谱监控 API
// 启动音频频谱监控(支持版本:1.1.0+)
public void startAudioSpectrumMonitor()
// 停止音频频谱监控
public void stopAudioSpectrumMonitor()
特点:
- 固定回调周期:100ms
- 适用于 K 歌场景的音调与音量变化动画
2.2 回调接口说明
ZEGO SDK 通过 IZegoEventHandler 提供四个回调接口:
本地音频回调
// 本地采集的声浪回调
public void onCapturedSoundLevelUpdate(double soundLevel)
// 本地采集的音频频谱回调
public void onCapturedAudioSpectrumUpdate(float[] audioSpectrum)
远端音频回调
// 远端拉流的声浪回调
public void onRemoteSoundLevelUpdate(HashMap<String, Double> soundLevels)
// 远端拉流的音频频谱回调
public void onRemoteAudioSpectrumUpdate(HashMap<String, float[]> audioSpectrums)
关键点:
- 远端回调使用
HashMap结构,key为流 ID,value为对应的声浪/频谱数据 - 这样设计是因为语聊房中可能同时存在多路远端音频流
三、实现步骤详解
3.1 前置准备
在使用声浪监控功能前,需要完成以下准备工作:
// 1. 创建 ZEGO Express Engine 实例
ZegoExpressEngine engine = ZegoExpressEngine.createEngine(
appID, appSign, true, ZegoScenario.GENERAL, application, null
);
// 2. 加入房间
engine.loginRoom(roomID, user);
// 3. 推流(本地音频)
engine.startPublishingStream(streamID);
// 4. 拉流(远端音频)
engine.startPlayingStream(streamID);
3.2 监听声浪回调
创建自定义的事件处理器,实现声浪回调接口:
class MyEventHandler extends IZegoEventHandler {
@Override
public void onCapturedSoundLevelUpdate(double soundLevel) {
// 处理本地声浪数据
// soundLevel 范围:0.0 ~ 100.0
updateLocalSoundLevelUI(soundLevel);
}
@Override
public void onRemoteSoundLevelUpdate(HashMap<String, Double> soundLevels) {
// 处理远端声浪数据
for (Map.Entry<String, Double> entry : soundLevels.entrySet()) {
String streamID = entry.getKey();
Double soundLevel = entry.getValue();
updateRemoteSoundLevelUI(streamID, soundLevel);
}
}
@Override
public void onCapturedAudioSpectrumUpdate(float[] audioSpectrum) {
// 处理本地音频频谱数据
// audioSpectrum 为频谱值数组,范围 [0, 2^30]
updateLocalSpectrumUI(audioSpectrum);
}
@Override
public void onRemoteAudioSpectrumUpdate(HashMap<String, float[]> audioSpectrums) {
// 处理远端音频频谱数据
for (Map.Entry<String, float[]> entry : audioSpectrums.entrySet()) {
String streamID = entry.getKey();
float[] spectrum = entry.getValue();
updateRemoteSpectrumUI(streamID, spectrum);
}
}
}
3.3 启动声浪监控
// 设置事件回调
engine.setEventHandler(new MyEventHandler());
// 启动声浪监控,设置 100ms 回调周期
engine.startSoundLevelMonitor(100);
注意事项:
- 调用
startSoundLevelMonitor后,onCapturedSoundLevelUpdate会立即触发 - 未推流且未预览时,本地声浪回调值为 0
- 远端声浪需要在
startPlayingStream之后才会回调
3.4 处理远端多路流数据
在语聊房场景中,房间内可能有多个用户同时推流。为了正确处理每路流的声浪数据,需要先获取流列表:
class MyEventHandler extends IZegoEventHandler {
// 保存房间内的流列表
private List<ZegoStream> streamList = new ArrayList<>();
@Override
public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType,
ArrayList<ZegoStream> streamList,
JSONObject extendedData) {
if (updateType == ZegoUpdateType.ADD) {
// 有新流加入
this.streamList.addAll(streamList);
} else {
// 有流移除
this.streamList.removeAll(streamList);
}
}
@Override
public void onRemoteSoundLevelUpdate(HashMap<String, Double> soundLevels) {
// 遍历保存的流列表,获取对应的声浪数据
for (ZegoStream stream : streamList) {
String streamID = stream.streamID;
if (soundLevels.containsKey(streamID)) {
Double soundLevel = soundLevels.get(streamID);
// 根据流 ID 更新对应用户的 UI
updateUserSoundLevelUI(stream.user.userID, soundLevel);
}
}
}
}
四、进阶:音频频谱可视化
如果需要实现更炫酷的音频可视化效果(如 K 歌房的频谱动画),可以使用音频频谱监控功能。
4.1 频谱数据特点
- 回调周期:固定 100ms
- 数据格式:
float[]数组,每个元素代表一个频段的能量值 - 数值范围:[0, 2^30]
- 适用场景:主播 K 歌场景,让主播或观众看到音调与音量变化的动画
4.2 启动频谱监控
// 启动音频频谱监控
engine.startAudioSpectrumMonitor();
4.3 频谱数据渲染
将频谱数组映射到 UI 柱状图:
@Override
public void onCapturedAudioSpectrumUpdate(float[] audioSpectrum) {
// audioSpectrum 长度通常为 512 或 1024
// 可以根据需要进行降采样,例如只显示 32 个柱状图
int barCount = 32;
float[] displaySpectrum = new float[barCount];
// 将频谱数据分组求平均
int groupSize = audioSpectrum.length / barCount;
for (int i = 0; i < barCount; i++) {
float sum = 0;
for (int j = 0; j < groupSize; j++) {
sum += audioSpectrum[i * groupSize + j];
}
displaySpectrum[i] = sum / groupSize;
}
// 更新 UI(自定义 View 绘制柱状图)
spectrumView.updateSpectrum(displaySpectrum);
}
五、实战技巧与注意事项
5.1 性能优化
合理设置回调周期
// 对于普通语聊房,200ms 的回调周期足够
engine.startSoundLevelMonitor(200);
// 对于 K 歌房等对实时性要求高的场景,使用 100ms
engine.startSoundLevelMonitor(100);
UI 渲染使用节流策略
private long lastUpdateTime = 0;
private static final long UPDATE_INTERVAL = 50; // 50ms 更新一次 UI
@Override
public void onCapturedSoundLevelUpdate(double soundLevel) {
long currentTime = System.currentTimeMillis();
if (currentTime - lastUpdateTime >= UPDATE_INTERVAL) {
updateUI(soundLevel);
lastUpdateTime = currentTime;
}
}
5.2 常见问题
问题 1:未推流时声浪回调值为 0
原因:启动声浪监控后,即使未启动本地音频采集,onCapturedSoundLevelUpdate 也会有回调,但声浪值为 0。
解决方案:在推流或预览后再处理声浪数据。
问题 2:远端声浪没有回调
原因:onRemoteSoundLevelUpdate 需要在拉流 startPlayingStream 之后才会回调。
解决方案:确保已成功拉流,可以通过 onPlayerStateUpdate 回调确认拉流状态。
问题 3:声浪数据跳变明显
原因:音频信号本身存在波动,直接渲染会导致 UI 抖动。
解决方案:对声浪数据进行平滑处理:
private double smoothedSoundLevel = 0;
private static final double SMOOTH_FACTOR = 0.3;
@Override
public void onCapturedSoundLevelUpdate(double soundLevel) {
// 指数移动平均
smoothedSoundLevel = smoothedSoundLevel * (1 - SMOOTH_FACTOR)
+ soundLevel * SMOOTH_FACTOR;
updateUI(smoothedSoundLevel);
}
5.3 混流场景处理
在混流场景下,如果需要获取混流后的声浪数据,可以通过拉取混流后的流来获取:
// 拉取混流后的流
engine.startPlayingStream(mixStreamID);
// 在 onRemoteSoundLevelUpdate 中获取混流的声浪
@Override
public void onRemoteSoundLevelUpdate(HashMap<String, Double> soundLevels) {
if (soundLevels.containsKey(mixStreamID)) {
Double mixedSoundLevel = soundLevels.get(mixStreamID);
updateMixedSoundLevelUI(mixedSoundLevel);
}
}
六、完整示例代码
以下是一个完整的语聊房声浪效果实现示例:
public class VoiceChatActivity extends AppCompatActivity {
private ZegoExpressEngine engine;
private String roomID = "room_001";
private String userID = "user_001";
private String streamID = "stream_001";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_voice_chat);
initZegoEngine();
joinRoom();
startPublishing();
startSoundLevelMonitor();
}
private void initZegoEngine() {
// 创建引擎
engine = ZegoExpressEngine.createEngine(
YOUR_APP_ID,
YOUR_APP_SIGN,
true,
ZegoScenario.GENERAL,
getApplication(),
null
);
// 设置事件回调
engine.setEventHandler(new IZegoEventHandler() {
@Override
public void onCapturedSoundLevelUpdate(double soundLevel) {
runOnUiThread(() -> {
// 更新本地用户的声浪 UI
updateLocalSoundLevel(soundLevel);
});
}
@Override
public void onRemoteSoundLevelUpdate(HashMap<String, Double> soundLevels) {
runOnUiThread(() -> {
// 更新远端用户的声浪 UI
for (Map.Entry<String, Double> entry : soundLevels.entrySet()) {
updateRemoteSoundLevel(entry.getKey(), entry.getValue());
}
});
}
@Override
public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType,
ArrayList<ZegoStream> streamList,
JSONObject extendedData) {
if (updateType == ZegoUpdateType.ADD) {
// 有新流加入,开始拉流
for (ZegoStream stream : streamList) {
engine.startPlayingStream(stream.streamID);
}
}
}
});
}
private void joinRoom() {
ZegoUser user = new ZegoUser(userID);
engine.loginRoom(roomID, user);
}
private void startPublishing() {
engine.startPublishingStream(streamID);
}
private void startSoundLevelMonitor() {
// 启动声浪监控,100ms 回调一次
engine.startSoundLevelMonitor(100);
}
private void updateLocalSoundLevel(double soundLevel) {
// 更新本地用户头像的声浪动画
// 例如:根据 soundLevel 调整圆环的大小或透明度
localUserView.setSoundLevel(soundLevel);
}
private void updateRemoteSoundLevel(String streamID, double soundLevel) {
// 更新远端用户头像的声浪动画
RemoteUserView userView = findUserViewByStreamID(streamID);
if (userView != null) {
userView.setSoundLevel(soundLevel);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 停止声浪监控
engine.stopSoundLevelMonitor();
// 登出房间
engine.logoutRoom(roomID);
// 销毁引擎
ZegoExpressEngine.destroyEngine(null);
}
}
七、总结
声浪效果的实现核心流程可以总结为:
- 初始化 SDK:创建 ZEGO Express Engine 实例
- 设置回调:实现
IZegoEventHandler中的声浪回调接口 - 启动监控:调用
startSoundLevelMonitor开启声浪监控 - 处理数据:在回调中获取声浪数据并渲染到 UI
- 性能优化:合理设置回调周期,对数据进行平滑处理
ZEGO Express SDK 的优势在于:
- 低延迟:声浪数据实时回调,延迟低至 100ms
- 高精度:提供精确的音量值和频谱数据
- 易集成:API 简洁,几行代码即可实现
- 跨平台:支持 Android、iOS、Web、Flutter 等多平台
参考资料
希望这篇文章能帮助您理解语聊房声浪效果的实现原理。如果您有任何问题,欢迎查阅 ZEGO 官方文档或联系技术支持。

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/yinshipin/65376.html