音视频进阶教程|如何实现游戏中的实时语音

1 游戏实时语音功能简介

1.1 游戏实时语音概念解释

  • 范围:收听者接收音频的范围。
  • 方位:指收听者在游戏世界坐标中的位置和朝向,详情可参考 5.5 初始化设置 中的“步骤 1”。
  • 收听者:房间内接收音频的用户
  • 发声者:房间内发送音频的用户。

1.2 游戏实时语音功能描述

ZEGO Express SDK 从 2.11.0 版本起,新增游戏语音模块,主要包括:范围语音、3D 音效、小队语音。适用于吃鸡类游戏、元宇宙类场景。

在吃鸡类游戏中,小队语音提供编队功能,在游戏开始前和开始后都可以更换小队,开发者无需关注流分组以及推拉流的实现,直接实现小队语音功能。

在吃鸡游戏和元宇宙场景中,提供 3D 音效能力,在收听发声者音效时,有方向感距离感,让场景感受更真实。

实现游戏中的范围语音

房间内的收听者对音频的接收距离有范围限制,若发声者与自己的距离超过该范围,则无法听到声音。为保证语音清晰,附近超过 20 人发声时,只能听到离自己最近的 20 个发声者的声音。

假如设置音频接收距离的最大范围为 R,若发声者离收听者的距离为 r,则:

a. 当 r < R 时,表示发声者在正常范围内,收听者可以听到声音。

b. 当 r ≥ R 时,表示发声者超出了最大范围,收听者无法听到声音。

下图仅以范围语音模式为“全世界”时为例,更多不同模式组合关系下的声音可达情况请参考 5.9 (可选)设置小队语音功能 中的“步骤 2”。

下载 (1).png

游戏实时音视频的3D音效

声音有 3D 空间感且按距离衰减。

游戏小队模式语音

玩家可以选择加入小队,并支持在房间内自由切换“全世界”模式、“仅小队”模式、“隐秘小队”模式。

  • 全世界:玩家可与队友互相通话,同时能与范围内同为全世界模式的玩家互相通话。
  • 仅小队:玩家只能与队友互相通话。
  • 隐秘小队:玩家可与队友互相通话,但只能单向接收范围内全世界模式的玩家的语音。

2 游戏范围语音功能-示例源码下载

请参考 下载示例源码 获取源码。

相关源码请查看 “/ZegoExpressExample/Examples/AdvancedAudioProcessing/RangeAudio” 目录下的文件。

3 游戏范围语音功能实现的前提条件

在实现范围语音之前,请确保:

4 范围语音实现的注意事项

使用范围语音功能时请务必关注如下注意事项,以免影响接入。

如果您已经使用 ZEGO Express SDK 的实时音视频功能,需要注意以下事项:

  • 由于范围语音功能模块是基于 ZegoExpressEngine 的推拉流接口功能来实现的,使用时无需关注推拉流的概念。在范围语音场景下,推音频流的概念转变为“开启麦克风”,拉音频流的概念转变为“开启扬声器”。建议您不要在接入范围语音功能的同时再使用 startPublishingStreamstartPlayingStream 接口做推拉流操作,避免效果冲突。

5 实时音视频-范围语音SDK的使用步骤

5.1 范围语音SDK创建引擎

调用 createEngine 接口,将申请到到 AppID 和 AppSign 传入参数 “appID” 和 “appSign”,创建引擎单例对象。引擎当前只支持同时创建一个实例,超出后将返回 null。

/** 定义 SDK 引擎对象 */
ZegoExpressEngine engine;

ZegoEngineProfile profile = new ZegoEngineProfile();
/** 请通过官网注册获取,格式为 123456789L */
profile.appID = appID;
/** 64个字符,请通过官网注册获取,格式为"0123456789012345678901234567890123456789012345678901234567890123" */
profile.appSign = appSign;
/** 通用场景接入 */
profile.scenario = ZegoScenario.GENERAL;
/** 设置app的application 对象 */
profile.application = getApplication();
/** 创建引擎 */
engine = ZegoExpressEngine.createEngine(profile, null);

5.2 游戏语音创建范围语音模块

调用的 createRangeAudio 方法创建范围语音实例。

ZegoRangeAudio rangAudio = engine.createRangeAudio();
if (rangAudio == null) {
    printf("创建范围语音实例模块失败");
}

5.3 监听范围语音事件回调

可以根据需要调用 setEventHandler 接口为麦克风设置事件回调,用于监听麦克风的开启状态 onRangeAudioMicrophoneStateUpdate 通知。

// set range audio event handler
rangeAudio.setEventHandler(new IZegoRangeAudioEventHandler() {
    @Override
    public void onRangeAudioMicrophoneStateUpdate(ZegoRangeAudio rangeAudio, ZegoRangeAudioMicrophoneState state, int errorCode) {
        super.onRangeAudioMicrophoneStateUpdate(rangeAudio, state, errorCode);
        AppLogger.getInstance().callApi("microphone state update. state: %s, errorCode: %d", state, errorCode);
    }
});

5.4 登录房间

传入用户 ID 参数 userID 和 userName 创建 ZegoUser 用户对象后,调用 loginRoom 接口,传入房间 ID 参数 roomID 和用户参数 user,登录房间。

  • 同一个 AppID 内,需保证 roomID 全局唯一。
  • 同一个 AppID 内,需保证 userID 全局唯一,建议开发者将其设置成一个有意义的值,可将 userID 与自己业务账号系统进行关联。
  • userID 与 userName 不能为空,否则会导致登录房间失败。
/** 创建用户 */
ZegoUser user = new ZegoUser("user1");

/** 开始登录房间 */
engine.loginRoom("room1", user);

当用户已成功登录房间后,如果应用异常退出,在重启应用后,开发者需先调用 logoutRoom 接口退出房间,再调用 loginRoom 接口重新登录房间。

5.5 初始化设置

1. 设置听者自身所在位置和朝向

开发者可以通过调用 updateSelfPosition 接口,设置听者自身的所在位置和方位,或者在自身方位发生变化时更新自己在世界坐标系中的位置和朝向。

  • 在调用 enableSpeaker 打开扬声器之前如果没有调用该接口设置位置信息,则无法接收除小队以外其他人的声音。
  • 自身坐标系三个轴的坐标值可以通过第三方 3D 引擎的旋转角度换算矩阵得到。
微信截图_20221020161057.png
下载 (2).png
// 自身在世界坐标系中的坐标,顺序是前、右、上。
float[] position = new float[]{100.0, 100.0, 100.0};
// 自身坐标系前朝向的单位向量。
float[] axisForward = new float[]{1.0,0.0,0.0};
// 自身坐标系右朝向的单位向量。
float[] axisRight = new float[]{0.0,1.0,0.0};
// 自身坐标系上朝向的单位向量。
float[] axisUp = new float[]{0.0,0.0,1.0};

rangAudio.updateSelfPosition(position, axisForward, axisRight, axisUp);

2. (可选)设置音频接收距离的最大范围

调用 setAudioReceiveRange 接口设置听者接收音频距离的最大范围,即以自身为起点,3D 空间中以设置的距离为立体空间。设置该范围后,在开启 3D 音效的情况下,声音将会随距离的增加而衰减,直至超出所设置的范围,则不再有声音。小队内的语音,不会受到该值的限制,也不会有 3D 音效。

如果不设置,则表示只能接收本小队内的成员声音,无法接收小队外的所有声音。

rangAudio.setAudioReceiveRange(1000);

3. (可选)设置是否开启 3D 音效

调用 enableSpatializer 接口设置 3D 音效,enable 取值为 true 时表示开启 3D 音效,此时房间内非小队成员的音频,会随着发声者离自身的距离和方向的变化而产生空间感的变化,为 false 时表示关闭 3D 音效。(可随时开启或关闭)

该功能只对小队以外的人生效。

rangAudio.enableSpatializer(true);

5.6 添加或更新发声者位置信息

登录房间成功后,需要通过调用 updateAudioSource 接口,添加或更新发声者的位置信息。

  • 全世界模式下:需要更新房间内,收听者和所有发声者的位置。隐秘小队模式下:需要更新房间内,所有在音频接收范围内,且为全世界模式的发声者的位置。如果未设置发声者位置,或者发声者超出收听者范围,会出现听不到声音的情况。
  • 这里发声者指的是房间内其他人,收听者指的是自己。
  • userID:为房间内其他发声用户的 ID。
  • position:发声者在世界坐标系中的坐标,参数是长度为 3 的 float 数组,三个值依次表示前、右、上的坐标值。
// 用户在世界坐标系中的坐标,顺序是前、右、上。
float[] position = new float[]{100.0, 100.0, 100.0};
// 添加/更新用户位置
rangAudio.updateAudioSource("abc",position);

5.7 设置是否开启麦克风

登录房间成功后调用 enableMicrophone 接口设置是否开启麦克风,当 enable 取值为 true 时表示开启,此时 SDK 将会自动使用主通道推音频流,为 false 时表示关闭。(可随时开启或关闭)

需要通过监听 onRangeAudioMicrophoneStateUpdate 事件回调来获取麦克风更新后的状态。

rangAudio.enableMicrophone(true);

5.8 设置是否开启扬声器

登录房间成功后调用 enableSpeaker 接口设置是否开启扬声器,enable 取值为 true 时表示开启,此时将会自动拉取房间内的音频流,为 false 时表示关闭。(可随时开启或关闭)

当超过最大拉流数限制(目前为 20 路)时,会优先拉取小队内成员音频流(需设置小队模式),再拉取世界内距离自身范围最近的音频流。

rangAudio.enableSpeaker(true);

5.9 (可选)设置小队语音功能

1. 设置队伍 ID

调用 setTeamID 接口可根据需要设置想要加入的小队 ID(可随时变更 ID),设置 ID 后即可直接加入。加入小队后,与同一小队内队员之间的交流不受范围语音和 3D 音效的限制。

rangAudio.setTeamID("123");

2. 设置语音模式

调用 setRangeAudioMode) 接口设置范围语音模式(可随时切换模式),mode 参数取值为 ZegoRangeAudioModeWorld 或 ZegoRangeAudioSecretTeam 时表示可以听到所有处于世界模式的人的声音,取值为 ZegoRangeAudioModeTeam 时表示只能听到同一小队内其他成员的声音。

微信截图_20221020161158.png
rangeAudio.setRangeAudioMode(ZegoRangeAudioMode.WORLD);

不同范围语音模式下,发声者声音的可接收情况有所不同。

  • 假设 A 用户的模式为 “全世界”,则 B 用户在不同范围语音模式下的声音可接收情况如下:
微信截图_20221020161245.png
  • 假设 A 用户的模式为 “仅小队”,则 B 用户在不同范围语音模式下的声音可接收情况如下:
微信截图_20221020161309.png
  • 假设 A 用户的模式为 “隐秘小队”,则 B 用户在不同范围语音模式下的声音可接收情况如下:
微信截图_20221020161330.png

5.10 销毁范围语音模块

当不再使用范围语音模块时可调用 destroyRangeAudio 接口销毁,释放范围语音模块占用的资源。

engine.destroyRangeAudio(rangAudio);

5.11 退出房间

调用 logoutRoom 接口退出房间,退出后将自动关闭麦克风和扬声器(即无法发送自己的音频,也无法收听别人的声音),并清空发声者信息列表。

// 退出房间
engine.logoutRoom("roomID");

5 获取 范围语音 更多帮助

获取本文 实时音视频-范围语音 的开发文档、技术支持,访问即构文档中心开发文档页,适合对语音音效要求较高的小游戏,元宇宙类场景。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容