[声网音频SDK实战教程] 如何实现语音房麦位上嘉宾说话时的水波纹效果

简介

通过本篇教程, 您将学习到如何实现 语音房内 麦位上嘉宾说话时的水波纹效果.

1] 加入声网频道前的参数设置 :

在调用 joinChannel 之前, 需要进行如下配置 :

/**
             * 启用用户音量提示。
             *
             * 该方法允许 SDK 定期向 app 报告本地发流用户和瞬时音量最高的远端用户(最多 3 位)的音量相关信息。
             * 启用该方法后,只要频道内有发流用户,SDK 会在加入频道后按设置的时间间隔触发 onAudioVolumeIndication 回调。
             *
             * 参数
             * interval    指定音量提示的时间间隔:
             *      ≤ 0:禁用音量提示功能。
             *      > 0:返回音量提示的间隔,单位为毫秒。建议设置到大于 200 毫秒。最小不得少于 10 毫秒,否则会收不到 onAudioVolumeIndication 回调。
             * smooth    平滑系数,指定音量提示的灵敏度。取值范围为 [0, 10],建议值为 3,数字越大,波动越灵敏;数字越小,波动越平滑。
             * report_vad    是否开启人声检测
             *      true: 开启本地人声检测功能。开启后,onAudioVolumeIndication 回调的 vad 参数会报告是否在本地检测到人声。
             *      false: (默认)关闭本地人声检测功能。除引擎自动进行本地人声检测的场景外,onAudioVolumeIndication 回调的 vad 参数不会报告是否在本地检测到人声。
             * 返回
             *      0: 方法调用成功。
             *      < 0: 方法调用失败。
             *
             *
             * 实测发现enableAudioVolumeIndication   interval设置的间隔时间。
             * 我们在间隔时间开始内说话。是没有给我们回调的。
             * 比如说:设置回调是10s,如果我们在3s的时间节点说一句话,在10s前说话结束,那我们是没有收到onAudioVolumeIndication 回调的。
             */
            resultCode = rtcEngine.enableAudioVolumeIndication(500, 3, false); 


2] 接收水波纹的声网回调(其实是用户音量回调) :

/**
     * 用户音量提示回调
     * 该回调默认禁用。可以通过 enableAudioVolumeIndication 方法开启。
     * 开启后,只要频道内有发流用户,SDK 会在加入频道后按 enableAudioVolumeIndication 中设置的时间间隔触发 onAudioVolumeIndication 回调。
     * 每次会触发两个 onAudioVolumeIndication 回调,一个报告本地发流用户的音量相关信息,另一个报告瞬时音量最高的远端用户(最多 3 位)的音量相关信息。
     * <p>
     * 注解
     * 启用该功能后,如果有用户将自己静音(调用了 muteLocalAudioStream),SDK 行为会受如下影响:
     * 1] 本地用户静音后,SDK 立即停止报告本地用户的音量提示回调。
     * 2] 瞬时音量最高的远端用户静音后 20 秒,远端的音量提示回调中将不再包含该用户;如果远端所有用户都将自己静音,20 秒后 SDK 停止报告远端用户的音量提示回调。
     * <p>
     * 参数
     *
     * @param speakerArray 用户音量信息,详见 AudioVolumeInfo 数组。如果 speakers 为空,则表示远端用户不发流或没有远端用户。
     * @param totalVolume  混音后的总音量,取值范围为 [0,255]。
     *                     1] 在本地用户的回调中,totalVolume 为本地发流用户的音量。
     *                     2] 在远端用户的回调中,totalVolume 为瞬时音量最高的远端用户(最多 3 位)混音后的总音量。
     *                     如果用户调用了 startAudioMixing,则 totalVolume 为音乐文件和用户声音的总音量。
     */
    @Override
    public void onAudioVolumeIndication(final AudioVolumeInfo[] speakerArray, final int totalVolume) 

经验积累 :

1] 本端 和 远端 都通过此回调返回;

2] 在用户设置 "静音" 之后, 就不会回调了; 注意 : 不会在点击 "静音" 之后, 给客户端一个回调, 告知客户端本端没声音了.

3] 本端用户只要没设置 "静音", 就会一直根据配置的回调周期, 进行回调;

4] 他端如果没有人说话时, speakerInfos 数组会返回 "空数组", 注意 : 当返回 "空数组" 时, 一定是他端没人说话, 本端不说话时, 也会执行本回调, 并且 speakerInfos 不为空., 注意: 他端没人说话时, 会一直有 speakerInfos 为空的回调返回;

5] 最多支持 返回3个正在说话的嘉宾;

6] 如果使用 [频道转发技术] 来实现跨房PK, 那么对方主播的说话时的水波纹, 也通过此回调来实现;

7] 注意 : 这个回调在子线程中运行


核心代码演示 :

@Override
public void onAudioVolumeIndication(final AudioVolumeInfo[] speakerArray, final int totalVolume) {
    handler.post(new Runnable() {
            @Override
            public void run() {
                // 注意 : 要先切换到主线程
                           
                // 数组不好操作, 转成 List
                final List<AudioVolumeInfo> speakerList = new ArrayList<>();
                if (speakerArray != null && speakerArray.length > 0) {
                    Collections.addAll(speakerList, speakerArray);
                }

                final List<Integer> speakingSeatIndexList = new ArrayList<>();
                final int localSeatIndex = vcrSdk.getSeatIndexForCurrentLoginUser();// 获取当前登录用户的麦位索引(包括 主播位), 如果当前登录用户不在麦位上, 就返回 -1
                boolean isLocalSeat = false;

                if (speakerList.size() == 1 && speakerList.get(0).uid == 0) {
                    // "本端”
                    // getVoiceChatRoomUserRoleType() 获取语音房用户角色(主播 / 嘉宾 / 观众)
                    if (vcrSdk.getVoiceChatRoomUserRoleType() == GlobalConstant.VoiceChatRoomUserRoleType.AUDIENCE) {
                        // 本端用户是 "观众", 不在麦上, 直接返回.
                        return;
                    }

                    isLocalSeat = true;

                    // 本端用户在麦上
                    if (speakerList.get(0).volume >= 40) {
                        // 产品需求声音音量小于一个数量值,不用提示水波纹。
                        // 声网推荐的是这个回调音量80~100,是比较正常的过滤音量
                        // 80有点大,在操作的办公室。水波纹偶现。
                        // 最终实测得到的经验值是 40
                        speakingSeatIndexList.add(localSeatIndex);
                    }
                } else {
                    // "他端"
                    isLocalSeat = false;

                    // 标识 [单人 - 跨房PK] 对方主播 是否正在说话中...
                    boolean isSingleDifferentRoomPKOtherAnchorSpeaking = false;
                    for (AudioVolumeInfo speakerInfo : speakerList) {
                        if (speakerInfo.volume < 40) {
                            continue;
                        }

                        if (vcrSdk.isSingleDifferentRoomPKStarting()) {
                            // 单人跨房PK正在进行中…
                            // getSingleDifferentRoomPKOtherAnchorInfo() : 获取 [单人 - 跨房PK] 对方主播信息
                            if (vcrSdk.getSingleDifferentRoomPKOtherAnchorInfo().getAgoraId() == speakerInfo.uid) {
                                isSingleDifferentRoomPKOtherAnchorSpeaking = true;
                                continue;
                            }
                        }

                        // vcrSdk.seatCache 麦位列表缓存(全部麦位, 包括主播麦位, 主播麦位索引定死是 0 )
                        for (Map.Entry<Integer, VoiceChatRoomSeatInfo> entry : vcrSdk.seatCache.entrySet()) {
                            final VoiceChatRoomSeatInfo seatInfo = entry.getValue();
                            if (seatInfo.getSeatStatus() != GlobalConstant.VoiceChatRoomSeatStatus.ON) {
                                // 麦位上没人
                                continue;
                            }

                            if (seatInfo.getSeatOwner().getAgoraId() == speakerInfo.uid) {
                                speakingSeatIndexList.add(seatInfo.getSeatIndex());

                                // 找到说话人所在的麦位索引
                                break;
                            }
                        }
                    }

                    if (vcrSdk.isSingleDifferentRoomPKStarting()) {
                         // 单人跨房PK正在进行中…
                        try {

                            /**
                             * [单人-跨房PK] 对方主播是否正在在说话
                             *
                             * @param isSpeaking true : 在说话, false : 没有说话
                             */
                            vcrSdk.voiceChatRoomSdkCallback.onSingleDifferentRoomPKOtherAnchorSpeaking(isSingleDifferentRoomPKOtherAnchorSpeaking);
                        } catch (Exception e) {
                            e.printStackTrace();
                            
                        }
                    }
                }


                try {
                    /**
                     * 有麦主(包括 主播 和 嘉宾)说话时的回调, "本端" 和 "他端" 都通过此回调通知业务层
                     *
                     * @param speakingSeatIndexList 正在说话的麦位索引列表
                     *       如果 isLocalSeat 等于 true, 如果 "本端" 用户在说话, speakingSeatIndexList中就包含了 "本端" 用户所在麦位索引, 如果 "本端" 用户没在说话, speakingSeatIndexList 为空.
                     *      如果 isLocalSeat 等于 false, speakingSeatIndexList 里面存放着, 他端正在说话的麦位索引
                     * @param localSeatIndex        本地登录用户所在麦位的索引, 只有本地登录用户在麦时, 这个参数才有效果, 否则返回 -1
                     * @param isLocalSeat           本次数据变化, 属于 "本端" 还是 "他端" , true : 本端, false : 他端
                     */
                            vcrSdk.voiceChatRoomSdkCallback.onSeatOwnerSpeaking(speakingSeatIndexList, localSeatIndex, isLocalSeat);
                } catch (Exception e) {
                    e.printStackTrace();
                     
                }
         }
 }



推荐阅读
相关专栏
SDK 教程
164 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。