赞
踩
1. 下载插件:motionverse官网地址:概述 · Motionverse 接口文档 (deepscience.cn)
2. 按照官方文档新建Unity工程:对接说明 · Motionverse 接口文档 (deepscience.cn)
3. 通过我们自己的ASR,将语音转换为文本,文本通过大语言模型(chatgpt、文心一言以及其他大语言模型)生成的结果,直接通过生成式AI技术,把文本转化为AI智能体的声音、动作和表情,和大语言模型完美连接,只需要在获取到文本的时候调用以下代码即可:
代码示例:
- DriveTask task = new DriveTask();
- task.player = player;
- task.text = question;
- NLPDrive.GetDrive(task);
其中,player即为挂载motionverse插件中Player脚本的对象,question即为大语言模型获取到的答案。
还可以自己生成语音,通过语音链接调用以下代码:
- DriveTask task = new DriveTask();
-
- task.player = player;
-
- task.text = audioUrl;
-
- AudioUrlDrive.GetDrive(task);
其中,player即为挂载motionverse插件中Player脚本的对象,audioUrl即为语音链接。
4. 新建脚本AskManager,并挂载到场景中(可新建空物体),脚本代码如下:
- using LitJson;
- using Motionverse;
- using MotionverseSDK;
- using System;
- using System.Collections;
- using System.Text;
- using UnityEngine;
- using UnityEngine.Networking;
- using UnityEngine.UI;
-
- public class AskManager : MonoBehaviour
- {
- public Player player;
- public Dropdown dropdown;
- public InputField inputField;
- public Button btnSend;
-
- public string chatGptKey = "";
- private string chatUrl = "https://chatgpt.kazava.io/v1/chat/completions";
-
- public string wenXinAPI = "";
- public string wenXinSECRET = "";
- private string wenXinToken = "";
- private int curSelectNLP = 0;
-
- // Start is called before the first frame update
- private void Awake()
- {
- for (int i = 0; i < Display.displays.Length; i++)
- {
- Display.displays[i].Activate();
- }
-
- }
- void Start()
- {
- dropdown.onValueChanged.AddListener((value) =>
- {
- curSelectNLP = value;
- });
- StartCoroutine(GetWenxinToken());
- btnSend.onClick.AddListener(() =>
- {
- if (string.IsNullOrEmpty(inputField.text))
- {
- Debug.Log("请输入内容!");
- }
- else
- {
- GetAnswer(inputField.text);
- }
- });
- }
-
- public void GetAnswer(string q)
- {
- StartCoroutine(RealAnswer(q));
- }
-
-
-
- public IEnumerator RealAnswer(string question)
- {
- switch (curSelectNLP)
- {
- case 0:
- DriveTask task = new DriveTask();
- task.player = player;
- task.text = question;
- NLPDrive.GetDrive(task);
- break;
- case 1:
- StartCoroutine(RequestChat(question));
- break;
- case 2:
- StartCoroutine(ChatCompletions(question));
- break;
- default:
- break;
- }
-
- Invoke("restartRecording", 1);
-
- yield return null;
-
- }
-
- IEnumerator GetWenxinToken()
- {
- string url =$"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={wenXinAPI}&client_secret={wenXinSECRET}";
- UnityWebRequest webRequest = UnityWebRequest.Get(url);
- webRequest.timeout = 5000;
-
- yield return webRequest.SendWebRequest();
-
- if (webRequest.result == UnityWebRequest.Result.Success)
- {
- string response = webRequest.downloadHandler.text;
- var result = JsonUtility.FromJson<AccessTokenResponse>(response);
- wenXinToken = result.access_token;
- }
- else
- {
- Debug.LogError("Failed to get access token: " + webRequest.error);
- }
- }
-
- IEnumerator ChatCompletions(string content)
- {
- string url = $"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token={wenXinToken}";
- WenXinPostData postData = new WenXinPostData();
- postData.messages = new WenXinMessage[1];
- postData.messages[0] = new WenXinMessage();
- postData.messages[0].content = content + "30字以内";
- string data = JsonMapper.ToJson(postData);
-
- UnityWebRequest webRequest = UnityWebRequest.Post(url, data, "application/json");
- webRequest.timeout = 5000;
- yield return webRequest.SendWebRequest();
-
- if (webRequest.result == UnityWebRequest.Result.Success)
- {
- string response = webRequest.downloadHandler.text;
- WenXinRequest requestData = JsonMapper.ToObject<WenXinRequest>(response);
- DriveTask task = new DriveTask();
- task.player = player;
- task.text = requestData.result;
- TextDrive.GetDrive(task);
- }
- else
- {
- Debug.LogError("Chat completions request failed: " + webRequest.error);
- }
- }
-
- private IEnumerator RequestChat(string content)
- {
- using (UnityWebRequest webRequest = new UnityWebRequest(chatUrl, "POST"))
- {
- webRequest.SetRequestHeader("Content-Type", "application/json");
- ChatGPTPostData postData = new ChatGPTPostData();
- postData.key = chatGptKey;
- postData.messages = new PostMessage[1];
- postData.messages[0] = new PostMessage();
- postData.messages[0].content = content + "30字以内"; ;
- string data = JsonMapper.ToJson(postData);
- byte[] jsonToSend = new UTF8Encoding().GetBytes(data);
- webRequest.uploadHandler = new UploadHandlerRaw(jsonToSend);
- webRequest.downloadHandler = new DownloadHandlerBuffer();
- yield return webRequest.SendWebRequest();
-
- if (webRequest.result != UnityWebRequest.Result.Success)
- {
- Debug.LogError("ChatGPT request error: " + content + webRequest.error);
- }
- else
- {
- string response = webRequest.downloadHandler.text;
- ChatGPTRequestData requestData = JsonMapper.ToObject<ChatGPTRequestData>(response);
- DriveTask task = new DriveTask();
- task.player = player;
- task.text = requestData.choices[0].message.content;
- TextDrive.GetDrive(task);
- }
- }
- }
- }
5. 新建脚本RealtimeAsrManager,代码如下:
- using System;
- using System.Collections;
- using UnityEngine;
- using UnityEngine.UI;
- using UnityWebSocket;
-
-
- namespace Motionverse
- {
- public class RealtimeAsrManager : MonoBehaviour
- {
- private IWebSocket webSocket;
- private Status status = Status.FirstFrame;
- private bool lockReconnect = false;
- [HideInInspector]
- public static Action<string> Asr;
-
- [SerializeField]
- private GameObject TextBG;
- //百度
- public int AsrAppId;
- public string AsrAppkey = "";
-
-
- void Start()
- {
- CreateWebSocket();
- RecorderManager.DataAvailable += OnDataAvailable;
- }
- private void OnDisable()
- {
- RecorderManager.DataAvailable -= OnDataAvailable;
- }
- void CreateWebSocket()
- {
- try
- {
- string uril = "wss://vop.baidu.com/realtime_asr?sn=" + Guid.NewGuid().ToString();
- webSocket = new WebSocket(uril);
- InitHandle();
- webSocket.ConnectAsync();
- }
- catch (Exception e)
- {
- Debug.Log("websocket连接异常:" + e.Message);
- ReConnect();
- }
-
- }
-
-
- private void InitHandle()
- {
- RemoveHandle();
- webSocket.OnOpen += OnOpen;
- webSocket.OnMessage += OnMessage;
- webSocket.OnClose += OnClose;
- webSocket.OnError += OnError;
-
-
- }
- void RemoveHandle()
- {
- webSocket.OnOpen -= OnOpen;
- webSocket.OnMessage -= OnMessage;
- webSocket.OnClose -= OnClose;
- webSocket.OnError -= OnError;
- }
- //请求开始
- private void OnDataAvailable(byte[] data)
- {
- if (webSocket == null || (webSocket != null && webSocket.ReadyState != WebSocketState.Open))
- return;
-
- switch (status)
- {
- case Status.FirstFrame://握手
- {
- var firstFrame = new FirstFrame();
- firstFrame.data.appid= AsrAppId;
- firstFrame.data.appkey = AsrAppkey;
-
- webSocket.SendAsync(JsonUtility.ToJson(firstFrame));
- status = Status.ContinueFrame;
- }
- break;
- case Status.ContinueFrame://开始发送
- {
- if (data.Length > 0)
- {
- webSocket.SendAsync(data);
- }
- }
- break;
- case Status.LastFrame://关闭
- {
- webSocket.SendAsync(JsonUtility.ToJson(new LastFrame()));
- }
- break;
- default:
- break;
- }
- }
-
- void ReConnect()
- {
- if (this.lockReconnect)
- return;
- this.lockReconnect = true;
- StartCoroutine(SetReConnect());
- }
-
- private IEnumerator SetReConnect()
- {
- yield return new WaitForSeconds(1);
- CreateWebSocket();
- lockReconnect = false;
- }
-
- #region WebSocket Event Handlers
-
- private void OnOpen(object sender, OpenEventArgs e)
- {
- status = Status.FirstFrame;
- }
-
- private void OnMessage(object sender, MessageEventArgs e)
- {
- var err_msg = Utils.GetJsonValue(e.Data, "err_msg");
- var type = Utils.GetJsonValue(e.Data, "type");
-
- if (err_msg == "OK" && type == "MID_TEXT")
- {
- var result = Utils.GetJsonValue(e.Data, "result");
- TextBG.GetComponentInChildren<Text>().text = result;
- }
-
- if (err_msg == "OK" && type == "FIN_TEXT")
- {
- var result = Utils.GetJsonValue(e.Data, "result");
- TextBG.GetComponentInChildren<Text>().text = result;
- if (result.Length > 1)
- {
- RecorderManager.Instance.EndRecording();
- Asr?.Invoke(result);
- }
-
- }
-
- }
-
- private void OnClose(object sender, CloseEventArgs e)
- {
- Debug.Log("websocket关闭," + string.Format("Closed: StatusCode: {0}, Reason: {1}", e.StatusCode, e.Reason));
- webSocket = null;
- ReConnect();
- }
-
- private void OnError(object sender, ErrorEventArgs e)
- {
- if (e != null)
- Debug.Log("websocket连接异常:" + e.Message);
- webSocket = null;
- ReConnect();
- }
-
- #endregion
- }
- }
6. 新建脚本RecorderManager,代码如下:
- using UnityEngine;
- using System;
- using System.Collections;
- using UnityEngine.UI;
- using Unity.VisualScripting;
- namespace Motionverse
- {
- public class RecorderManager : Singleton<RecorderManager>
- {
- //标记是否有麦克风
- private bool isHaveMic = false;
- //当前录音设备名称
- private string currentDeviceName = string.Empty;
- //表示录音的最大时长
- int recordMaxLength = 600;
- //录音频率,控制录音质量(16000)
- int recordFrequency = 16000;
- [HideInInspector]
- public static Action<byte[]> DataAvailable;
- [SerializeField]
- private GameObject micON;
- [SerializeField]
- private GameObject micOFF;
- [SerializeField]
- private Image micAmount;
- [SerializeField]
- private GameObject TextBG;
- private AudioClip saveAudioClip;
-
- int offsetSamples = 0;
- private void Start()
- {
- Debug.Log(Microphone.devices[0]);
- if (Microphone.devices.Length > 0)
- {
- isHaveMic = true;
- currentDeviceName = Microphone.devices[0];
- StartCoroutine(GetAudioFrames());
- StartRecording();
- }
-
- }
-
- /// <summary>
- /// 开始录音
- /// </summary>
- /// <returns></returns>
- public void StartRecording() //16000
- {
- if (isHaveMic == false || Microphone.IsRecording(currentDeviceName))
- {
- return;
- }
- micOFF.gameObject.SetActive(false);
- micON.gameObject.SetActive(true);
- TextBG.GetComponentInChildren<Text>().text = null;
- offsetSamples = 0;
- saveAudioClip = Microphone.Start(currentDeviceName, true, recordMaxLength, recordFrequency);
-
- }
- public void OnButtonClick()
- {
- if (Microphone.IsRecording(currentDeviceName))
- {
- EndRecording();
- }
- else
- {
- StartRecording();
- }
- }
- public void EndRecording()
- {
-
- if (isHaveMic == false || !Microphone.IsRecording(currentDeviceName))
- {
- return;
- }
- micOFF.gameObject.SetActive(true);
- micON.gameObject.SetActive(false);
- //结束录音
- Microphone.End(currentDeviceName);
- }
- public bool IsRecording()
- {
- return Microphone.IsRecording(currentDeviceName);
- }
- IEnumerator GetAudioFrames()
- {
-
- while (true)
- {
- if (Microphone.IsRecording(currentDeviceName))
- {
- int lenght = Microphone.GetPosition(currentDeviceName) * saveAudioClip.channels - offsetSamples;
- if (lenght > 0)
- {
- float[] samples = new float[lenght];
- saveAudioClip.GetData(samples, offsetSamples);
-
- var samplesShort = new short[samples.Length];
- for (var index = 0; index < samples.Length; index++)
- {
- samplesShort[index] = (short)(samples[index] * short.MaxValue);
- }
- byte[] binaryData = new byte[samplesShort.Length * 2];
- Buffer.BlockCopy(samplesShort, 0, binaryData, 0, binaryData.Length);
-
- offsetSamples += lenght;
- DataAvailable?.Invoke(binaryData);
- }
-
- yield return new WaitForSeconds(0.16f);
- }
- else
- {
- yield return new WaitForSeconds(0.16f);
- byte[] binaryData = new byte[2];
- DataAvailable?.Invoke(binaryData);
- }
-
- }
- }
-
- /// <summary>
- /// 获取毫秒级别的时间戳,用于计算按下录音时长
- /// </summary>
- /// <returns></returns>
- public double GetTimestampOfNowWithMillisecond()
- {
- return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000;
- }
-
- private void LateUpdate()
- {
- if (isHaveMic == true && Microphone.IsRecording(currentDeviceName))
- {
- micAmount.fillAmount = Volume;
- }
- }
- public float Volume
- {
- get
- {
- if (Microphone.IsRecording(currentDeviceName))
- {
- // 采样数
- int sampleSize = 128;
- float[] samples = new float[sampleSize];
- int startPosition = Microphone.GetPosition(currentDeviceName) - (sampleSize + 1);
- // 得到数据
- if (startPosition < 0)
- return 0;
-
- saveAudioClip.GetData(samples, startPosition);
-
- // Getting a peak on the last 128 samples
- float levelMax = 0;
- for (int i = 0; i < sampleSize; ++i)
- {
- float wavePeak = samples[i];
- if (levelMax < wavePeak)
- levelMax = wavePeak;
- }
-
- return levelMax;
- }
- return 0;
- }
- }
- void OnGUI()
- {
- GUIStyle guiStyle = GUIStyle.none;
- guiStyle.fontSize = 10;
- guiStyle.normal.textColor = Color.white;
- guiStyle.alignment = TextAnchor.UpperLeft;
- Rect tr = new Rect(0, 0, 100, 100);
- GUI.Label(tr, currentDeviceName, guiStyle);
- }
- }
- }
7. 新建空物体,挂载脚本RecorderManager和RealtimeAsrManager
8. 输入百度asr的appId和secretKey:
9. 输入GPTkey、问心一眼appId和secretKey:
10. 根据需求选择相应的NLP方式:
注意:如果缺少Singleton文件,可使用如下代码:
- using UnityEngine;
- namespace Motionverse
- {
- public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
- {
-
- private static T sInstance = null;
-
- public static T Instance
- {
- get
- {
- if (sInstance == null)
- {
- GameObject gameObject = new(typeof(T).FullName);
- sInstance = gameObject.AddComponent<T>();
- }
-
- return sInstance;
- }
- }
-
- public static void Clear()
- {
- sInstance = null;
- }
-
- protected virtual void Awake()
- {
- if (sInstance != null) Debug.LogError(name + "error: already initialized", this);
-
- sInstance = (T)this;
- }
- }
- }
若有收获,就点个赞吧
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。