0. PhotoVoice 光子语音PhotonVoice | 光子引擎photonengine中文站

1. Vivox  来自 Vivox 的游戏内语音和文本聊天 SDK | Unity Multiplayer 服务

2. Agora 声网 声网 - 全球实时互动API平台开创者

3. Zego  HarmonyOS Java 实时音视频概述 - 开发者中心 - ZEGO即构科技

Vivox是什么

  1. Vivox是第三方语音和文本通信系统,常用于构建游戏项目的语音、实时聊天系统, 在 PC、移动或游戏主机平台上为玩家提供通信服务。

  1. Vivox被Unity收购,因此做为独立的第三方通信服务商的同时,Vivox成为了Unity产品矩阵中所提供的一项服务,可以为游戏项目提供语音通信服务。

https://unity.com/products

https://unity.com/products/vivox

二、如何接入

使用Unity的Vivox服务来构建游戏项目中的聊天系统:

  1. 配置Unity客户端:Unity商店中购买并安装插件

  1. 购买配置vivox服务端:https://support.unity.com/hc/en-us/articles/6380084154772-Vivox-How-do-I-get-started-with-Vivox-in-my-Unity-Project-

  1. 调用SDK API接入项目。

https://docs.vivox.com/v5/general/unity/15_1_180000/en-us/Default.htm#Unity/Unity.htm

  1. Service Relay

https://docs.unity.com/relay/introduction.html

Unity Relay 中继服务通过充当代理提供通用中继服务器连接,而无需维护第三方或专用游戏服务器 (DGS) 或担心架构点对点网络服务的复杂度。

中继服务有两个关键组件:the Relay servers 中继服务器and the Relay Allocations service中继分配服务。

  1. Service Lobby

https://docs.unity.com/lobby/unity-lobby-service-overview.html

Lobby 聊天大厅服务提供了两个主要功能:公共大厅和私人大厅,以供玩家创建和查找游戏会话。

三、Sample Chat Channel

演示 Vivox SDK 如何集成到 Unity 游戏中。

https://docs.vivox.com/v5/general/unity/15_1_180000/en-us/Default.htm#Unity/chat-channel-sample/chat-channel-sample-overview.htm

四、Sample Chat Lobby

演示大厅聊天频道的游戏集成

https://github.com/Unity-Technologies/com.unity.services.samples.game-lobby

Agora声网

文档:实现语音通话

https://docs.agora.io/cn/live-streaming-premium-legacy/start_call_audio_unity

文档:使用自定义的音视频源或渲染器,实现相关场景:

https://docs.agora.io/cn/voice-legacy/custom_audio_unity?platform=Unity

文档:采集音频原始数据:

https://docs.agora.io/cn/live-streaming-premium-legacy/raw_data_audio_unity?platform=Unity

采集实现方法

音频传输过程中,我们可以获取采集到的音频原始数据。

Agora Unity SDK 通过提供 AudioRawDataManager 类,实现采集原始音频数据功能。

在使用原始音频数据功能前,请确保你已在项目中完成基本的实时音频功能,详见实现音频通话实现音频直播

参考如下步骤,在你的项目中实现原始音频数据功能:

  1. 加入频道前调用 RegisterAudioRawDataObserver 方法注册音频观测器。

  1. 成功注册后,根据需求调用以下方法:

    1. 调用 SetOnRecordAudioFrameCallback 监听 OnRecordAudioFrameHandler 回调。SDK 会通过 OnRecordAudioFrameHandler 回调向用户发送采集到的原始音频数据。

    2. 调用 SetOnPlaybackAudioFrameCallback 监听 OnPlaybackAudioFrameHandler 回调。SDK 会通过 OnPlaybackAudioFrameHandler 回调向用户发送播放的原始音频数据,即所有远端用户混音后的音频数据。

    3. 调用 SetOnMixedAudioFrameCallback 监听 OnMixedAudioFrameHandler 回调。SDK 会通过 OnMixedAudioFrameHandler 回调向用户发送混音后的采集和播放的原始音频数据,即所有本地和远端用户混音后的音频数据。

    4. 调用 SetOnPlaybackAudioFrameBeforeMixingCallback 监听 OnPlaybackAudioFrameBeforeMixingHandler 回调。SDK 会通过 OnPlaybackAudioFrameBeforeMixingHandler 回调向用户发送指定远端用户混音前的原始音频数据。

  1. 用户拿到音频数据后,根据场景需要自行进行处理。以通过 Unity AudioSource 组件播放音频原始数据为例,大致流程如下:

    1. 创建一个有限队列。

    2. 在步骤 2 中调用任意回调后,会返回 buffer 数据。在该队列后端插入回调返回的 buffer 数据。

    3. 通过 AudioClip 组件的 setData 方法,从该队列前端按顺序取出 buffer 数据,并存入 AudioClip 组件。

    4. 通过 AudioSource 组件播放 AudioClip 组件中的数据。

  1. 离开频道后调用 UnRegisterAudioRawDataObserver 注销音频观测器。

API 调用时序

下图展示使用原始音频数据的 API 调用时序:

示例代码

你可以对照 API 时序图,参考下面的示例代码片段,在项目中实现原始音频数据功能:

void Start()
{
   // 初始化 IRtcEngine 对象。
   mRtcEngine = IRtcEngine.GetEngine(mVendorKey);
   // 获取 AudioRawDataManager 对象。
   AudioRawDataManager = AudioRawDataManager.GetInstance(mRtcEngine);
   // 注册音频观测器。
   mRtcEngine.RegisterAudioRawDataObserver();
   // 监听 OnRecordAudioFrameHandler delegate。
   AudioRawDataManager.SetOnRecordAudioFrameCallback(OnRecordAudioFrameHandler);
   // 监听 OnPlaybackAudioFrameHandler delegate。
   AudioRawDataManager.SetOnPlaybackAudioFrameCallback(OnPlaybackAudioFrameHandler);
   // 监听 OnMixedAudioFrameHandler delegate。
   AudioRawDataManager.SetOnMixedAudioFrameCallback(OnMixedAudioFrameHandler);
   // 监听 OnPlaybackAudioFrameBeforeMixingHandler delegate。
   AudioRawDataManager.SetOnPlaybackAudioFrameBeforeMixingCallback(OnPlaybackAudioFrameBeforeMixingHandler);
}

// 获取本地采集到的原始音频数据。
void OnRecordAudioFrameHandler(AudioFrame audioFrame)
{
   Debug.Log("OnRecordAudioFrameHandler");
}

// 获取从远端接收到的原始音频数据。
void OnPlaybackAudioFrameHandler(AudioFrame audioFrame)
{
   Debug.Log("OnPlaybackAudioFrameHandler");
}

// 获取本地和远端混音后的原始音频数据。
void OnMixedAudioFrameHandler(AudioFrame audioFrame)
{
   Debug.Log("OnMixedAudioFrameHandler");
}

// 获取指定本地或远端用户混音前的原始音频数据。
void OnPlaybackAudioFrameBeforeMixingHandler(uint uid, AudioFrame audioFrame)
{
   Debug.Log("OnPlaybackAudioFrameBeforeMixingHandler");
}

public enum AUDIO_FRAME_TYPE 
{
   // 0: PCM16
   FRAME_TYPE_PCM16 = 0,
};

public struct AudioFrame 
{
   // 音频帧类型。详见 #AUDIO_FRAME_TYPE 。
   public AUDIO_FRAME_TYPE type;
   // 每个声道的采样点数。
   public int samples; 
   // 每个采样点的字节数。通常为十六位,即两个字节。
   public int bytesPerSample; 
   // 声道数量(如果是立体声,数据是交叉的)
   // - 1: 单声道。
   // - 2: 双声道。
   public int channels; 
   // 采样率。
   public int samplesPerSec; 
   // 声音数据缓存区(如果是立体声,数据是交叉存储的)。缓存区数据大小:buffer = samples × channels × bytesPerSample。
   public byte[] buffer; 
   // 外部音频帧的渲染时间戳。你可以使用该时间戳还原音频帧顺序;在有视频的场景中(包含使用外部视频源的场景),该参数可以用于实现音视频同步。
   public long renderTimeMs;
   // 预留参数。
   public int avsync_type;
};

    public byte[] ConvertClipToBytes(AudioClip audioClip)
    {
        float[] samples = new float[audioClip.samples];
 
        audioClip.GetData(samples, 0);
 
        short[] intData = new short[samples.Length];
 
        byte[] bytesData = new byte[samples.Length * 2];
 
        int rescaleFactor = 32767;
 
        for (int i = 0; i < samples.Length; i++)
        {
            intData[i] = (short)(samples[i] * rescaleFactor);
            byte[] byteArr = new byte[2];  
            byteArr = BitConverter.GetBytes(intData[i]);
            byteArr.CopyTo(bytesData, i * 2);
        }
 
        return bytesData;
    }

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐