1.调用录音设备保存录音
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
public class MicroPhoneManager : MonoBehaviour
{
public int DeviceLength;
private void Awake()
{
DontDestroyOnLoad(this);
au= gameObject.GetComponent<AudioSource>();
if (au == null)
{
au = gameObject.AddComponent<AudioSource>();
}
}
private int Frequency = 16000; //录音频率
private int BitRate = 16; //比特率
private int MicSecond = 180; //每隔2秒,保存一下录音数据
public Button bStart;
public Button bStop;
public Button bPlay;
public Text tTip;
private AudioSource au;
private string[] devices;
private bool isHaveMicrophone = false;
void Start()
{
bStart.onClick.AddListener(OnStartClick);
bStop.onClick.AddListener(OnStopClick);
bPlay.onClick.AddListener(OnPlayClick);
devices = Microphone.devices;
DeviceLength = devices.Length;
if (devices.Length > 0)
{
isHaveMicrophone = true;
tTip.text = "设备有麦克风:" + devices[0];
}
else
{
isHaveMicrophone = false;
tTip.text = "设备没有麦克风";
}
}
void OnStartClick()
{
tTip.text += "\n开始录音....";
au.Stop();
au.loop = false;
au.mute = true;
//au.clip = Microphone.Start(devices[0], false, MicSecond, Frequency);
StartMicrophone();
}
void OnStopClick()
{
tTip.text += "\n停止录音!";
if (!Microphone.IsRecording(devices[0]))
return;
//Microphone.End(devices[0]);
StopMicrophone();
//au.Stop();
Mp3FromClip("d:/test.mp3", au.clip); //将录音保存为mp3
//WavFromClip(ClipPath, au.clip); //将录音保存为wav
}
string ClipPath
{
get
{
return Application.streamingAssetsPath + "/test.wav";
}
}
void OnPlayClick()
{
if (Microphone.IsRecording(null))
return;
if (au.clip == null)
return;
au.mute = false;
au.loop = false;
au.Play();
tTip.text += "\n播放录音....";
}
public delegate void AudioRecordHandle(AudioClip audioClip);
public AudioSource audioSource;
AudioClip micClip;
bool isMicRecordFinished = true;
List<float> micDataList = new List<float>();
float[] micDataTemp;
string micName;
public void StartMicrophone()
{
if (isHaveMicrophone)
{
tTip.text += "\n找不到设备....";
return;
}
StopCoroutine(StartMicrophone(null, PlayAudioRecord));
StartCoroutine(StartMicrophone(null, PlayAudioRecord));
}
IEnumerator StartMicrophone(string microphoneName, AudioRecordHandle audioRecordFinishedEvent)
{
Debug.Log("Start Mic");
micDataList = new List<float>();
micName = microphoneName;
micClip = Microphone.Start(micName, true, 2, 16000);
isMicRecordFinished = false;
int length = micClip.channels * micClip.samples;
bool isSaveFirstHalf = true;//将音频从中间分生两段,然后分段保存
int micPosition;
while (!isMicRecordFinished)
{
if (isSaveFirstHalf)
{
yield return new WaitUntil(() => { micPosition = Microphone.GetPosition(micName); return micPosition > length * 6 / 10 && micPosition < length; });//保存前半段
micDataTemp = new float[length / 2];
micClip.GetData(micDataTemp, 0);
micDataList.AddRange(micDataTemp);
isSaveFirstHalf = !isSaveFirstHalf;
}
else
{
yield return new WaitUntil(() => { micPosition = Microphone.GetPosition(micName); return micPosition > length / 10 && micPosition < length / 2; });//保存后半段
micDataTemp = new float[length / 2];
micClip.GetData(micDataTemp, length / 2);
micDataList.AddRange(micDataTemp);
isSaveFirstHalf = !isSaveFirstHalf;
}
}
micPosition = Microphone.GetPosition(micName);
if (micPosition <= length)//前半段
{
micDataTemp = new float[micPosition / 2];
micClip.GetData(micDataTemp, 0);
}
else
{
micDataTemp = new float[micPosition - length / 2];
micClip.GetData(micDataTemp, length / 2);
}
micDataList.AddRange(micDataTemp);
Microphone.End(micName);
AudioClip newAudioClip = AudioClip.Create("RecordClip", micDataList.Count, 1, 16000, false);
newAudioClip.SetData(micDataList.ToArray(), 0);
audioRecordFinishedEvent(newAudioClip);
Debug.Log("RecordEnd");
}
public void StopMicrophone()
{
Debug.Log("Stop mic");
isMicRecordFinished = true;
}
void PlayAudioRecord(AudioClip newAudioClip)
{
au.clip = newAudioClip;
au.Stop();
//Mp3FromClip("d:/test.mp3", au.clip); //将录音保存为mp3
WavFromClip(ClipPath, au.clip); //将录音保存为wav
}
public void WavFromClip(string WavPosition, AudioClip clip)
{
if (Microphone.IsRecording(null))
return;
Microphone.End(null);
using (FileStream fs = CreateEmpty(WavPosition))
{
ConvertAndWrite(fs, au.clip);
WriteHeader(fs, au.clip); //wav文件头
}
}
private FileStream CreateEmpty(string filepath)
{
FileStream fileStream = new FileStream(filepath, FileMode.OpenOrCreate);
byte emptyByte = new byte();
for (int i = 0; i < 44; i++) //为wav文件头留出空间
{
fileStream.WriteByte(emptyByte);
}
return fileStream;
}
private void ConvertAndWrite(FileStream fileStream, AudioClip clip)
{
float[] samples = new float[clip.samples];
clip.GetData(samples, 0);
Int16[] intData = new Int16[samples.Length];
Byte[] bytesData = new Byte[samples.Length * 2];
int rescaleFactor = 32767; //to convert float to Int16
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);
}
fileStream.Write(bytesData, 0, bytesData.Length);
}
private void WriteHeader(FileStream stream, AudioClip clip)
{
int hz = clip.frequency;
int channels = clip.channels;
int samples = clip.samples;
stream.Seek(0, SeekOrigin.Begin);
Byte[] riff = System.Text.Encoding.UTF8.GetBytes("RIFF");
stream.Write(riff, 0, 4);
Byte[] chunkSize = BitConverter.GetBytes(stream.Length - 8);
stream.Write(chunkSize, 0, 4);
Byte[] wave = System.Text.Encoding.UTF8.GetBytes("WAVE");
stream.Write(wave, 0, 4);
Byte[] fmt = System.Text.Encoding.UTF8.GetBytes("fmt ");
stream.Write(fmt, 0, 4);
Byte[] subChunk1 = BitConverter.GetBytes(16);
stream.Write(subChunk1, 0, 4);
UInt16 two = 2;
UInt16 one = 1;
Byte[] audioFormat = BitConverter.GetBytes(one);
stream.Write(audioFormat, 0, 2);
Byte[] numChannels = BitConverter.GetBytes(channels);
stream.Write(numChannels, 0, 2);
Byte[] sampleRate = BitConverter.GetBytes(hz);
stream.Write(sampleRate, 0, 4);
Byte[] byteRate = BitConverter.GetBytes(hz * channels * 2); // sampleRate * bytesPerSample*number of channels, here 44100*2*2
stream.Write(byteRate, 0, 4);
UInt16 blockAlign = (ushort)(channels * 2);
stream.Write(BitConverter.GetBytes(blockAlign), 0, 2);
UInt16 bps = 16;
Byte[] bitsPerSample = BitConverter.GetBytes(bps);
stream.Write(bitsPerSample, 0, 2);
Byte[] datastring = System.Text.Encoding.UTF8.GetBytes("data");
stream.Write(datastring, 0, 4);
Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2);
stream.Write(subChunk2, 0, 4);
}
}
2.数据上传平台进行评测
using Newtonsoft.Json;
using System;
using System.Globalization;
using System.IO;
using System.Net.WebSockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using UnityEngine;
namespace Ise
{
public class WebYYPCPlus : Web
{
private const string hostUrl = "ws://ise-api.xfyun.cn/v2/open-ise";//开放评测地址
private const string appid = " ";//控制台获取
private const string apiSecret = " ";//控制台获取
private const string apiKey = " ";//控制台获取
private const string sub = "ise";//服务类型sub,开放评测值为ise
private const string ent = "cn_vip";//语言标记参数 ent(cn_vip中文,en_vip英文)
private const string rst = "entirety";//string 限制只能使用精简版 只有总分 entirety plain
//题型、文本、音频要请注意做同步变更(如果是英文评测,请注意变更ent参数的值)
private const string category = "read_chapter";//题型
public const int StatusFirstFrame = 0;//第一帧
public const int StatusContinueFrame = 1;//中间帧
public const int StatusLastFrame = 2;//最后一帧
ClientWebSocket ws = new ClientWebSocket();
CancellationToken ct = new CancellationToken();
private bool isSocket = false;
//private IWebSocket socket;
string log = "";
public WebYYPCPlus(SetMessage value) : base(value)
{
//socket = new UnityWebSocket.WebSocket(getAuthUrl(hostUrl, apiKey, apiSecret));
//socket.OnOpen += Socket_OnOpen;
//socket.OnMessage += Socket_OnMessage;
//socket.OnClose += Socket_OnClose;
//socket.OnError += Socket_OnError;
//AddLog(string.Format("Connecting...\n"));
//socket.ConnectAsync();
}
public async void WebSocket()
{
try
{
//ClientWebSocket ws = new ClientWebSocket();
//CancellationToken ct = new CancellationToken();
string authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
Debug.Log(authUrl);
Uri url = new Uri(authUrl);
await ws.ConnectAsync(url, ct);
//await ws.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("hello")), WebSocketMessageType.Binary, true, ct); //发送数据
isSocket = true;
while (ws.State == System.Net.WebSockets.WebSocketState.Open)
{
var result = new byte[1024];
await ws.ReceiveAsync(new ArraySegment<byte>(result), new CancellationToken());//接受数据
var str = Encoding.UTF8.GetString(result);//, 0, result.Length);
onMessage(str);
}
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public override void Headers(string txtStr, string pathStr = "cn_chapter.wav")
{
//#if !UNITY_EDITOR && UNITY_WEBGL
//isSocket = true;
//#elif !NET_LEGACY
isSocket = false;
WebSocket();
//#else
// throw new Exception("Scripting Runtime Version should be .NET 4.x, via Menu:\nPlayerSettings -> Other Settings -> Script Runtime Version -> .Net 4.x Equivalent");
//#endif
onOpen(txtStr, pathStr);
}
public async void onOpen(string text, string pathStr = "cn_chapter.wav")
{
//连接成功,开始发送数据
int frameSize = 1280; //每一帧音频的大小,建议每 40ms 发送 1280B
int pcmCount = 0;
int pcmSize = 0;
int intervel = 10;
int status = 0; // 音频的状态
string path = Application.streamingAssetsPath + "/Audio/" + pathStr;
try
{
_last = false;
while (!isSocket)
{
await Task.Delay(intervel);
}
ssb(text);
byte[] arr = File.ReadAllBytes(path);
if (arr==null)
{
return;
}
pcmSize = arr.Length;
string cc = Convert.ToBase64String(arr);
while (true)
{
if (pcmSize <= 2 * frameSize)
{
frameSize = pcmSize;
status = StatusLastFrame;
}
if (frameSize <= 0)
{
break;
}
byte[] buffer = new byte[frameSize];
Array.Copy(arr, pcmCount, buffer, 0, frameSize);
pcmCount += frameSize;
pcmSize -= frameSize;
switch (status)
{
case StatusFirstFrame: // 第一帧音频status = 0
send(1, 1, Convert.ToBase64String(buffer));
status = StatusContinueFrame;//中间帧数
break;
case StatusContinueFrame: //中间帧status = 1
send(2, 1, Convert.ToBase64String(buffer));
break;
case StatusLastFrame: // 最后一帧音频status = 2 ,标志音频发送结束
send(4, 2, Convert.ToBase64String(buffer));
break;
}
await Task.Delay(intervel);
}
}
catch (FileNotFoundException e)
{
SetMessage(e.Message);
}
catch (IOException e)
{
SetMessage(e.Message);
}
catch (Exception e)
{
SetMessage(e.Message);
}
}
private void ssb(string text)
{
Root root = new Root()
{
common = new Common() { app_id = appid },
business = new Business() { sub = sub, ent = ent, category = category, aue = "raw", auf = "audio/L16;rate=16000", rstcd = "utf8", cmd = "ssb", text = "\ufeff" + text, tte = "utf-8", ttp_skip = true, rst = rst },// , extra_ability = "syll_phone_err_msg", ise_unite="1" },
data = new Data() { status = 0 }
};
var str = JsonConvert.SerializeObject(root);
onSend(str);
}
public void send(int aus, int status, string data)
{
string str = "{\"business\": {\"cmd\": \"auw\", \"aus\":" + aus + " },\"data\":{\"status\": " + status + ",\"data\":\"" + data + "\"}}";
onSend(str);
}
StringBuilder _json;
bool _last = false;
private void onMessage(string json)
{
Debug.Log(json);
if (_last)
{
if (!json.Contains("{"))
{
_json.Append(json);
}
}
if (!_last && !json.Contains("null"))
{
//第一条最后结果
_json = new StringBuilder(json);
_last = true;
string filePath = Application.streamingAssetsPath + "/StreamPlus.xml";
FileStream fs = new FileStream(xmlPath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
StreamWriter sw = new StreamWriter(fs);
fs.SetLength(0);//首先把文件清空了。
sw.Close();
}
if (_last && _json.ToString().Contains("}}"))
{
_json.Replace("\0", "");
json = _json.ToString();
var resp = JsonConvert.DeserializeObject<IseNewResponseData>(json);
if (resp.code != 0)
{
Debug.LogError(json);
}
if (resp.data.status == 2)
{
byte[] myByte = System.Convert.FromBase64String(resp.data.data);
string txt = Encoding.UTF8.GetString(myByte);
string filePath = Application.streamingAssetsPath + "/StreamPlus.xml";
File.AppendAllText(xmlPath, txt);
SetMessage(txt);
//AnalysisResultXML(xmlPath);
AudioManager.Ins.OnAnalysisResult(txt);
ws.Abort();
}
_last = false;
}
}
public void AnalysisResultXML(string path)
{
string[] read_chapter;
try
{
XmlDocument doc = new XmlDocument();
doc.Load(path);
//获取根节点---此处一定要添加双斜杠
XmlNodeList nodeList = doc.SelectNodes("//rec_paper");
//遍历输出.
foreach (XmlElement node in nodeList)
{
string str = node.InnerXml.Split('>')[0];
read_chapter = str.Split(' ');
Syll.SetDic(read_chapter, Overall);
//输出结果
Debug.Log(Overall["total_score"] + "--" + Overall["tone_score"] + "--" + Overall["phone_score"] + "--"+ Overall["integrity_score"]);
}
nodeList = doc.SelectNodes("//word");
//遍历解析.
foreach (XmlElement node in nodeList)
{
Syll syll = new Syll(node.InnerXml);
if (!syll.IsCorrect)
{
wroingList.Add(syll.txtData);
}
}
AudioManager.Ins.OnAnalysisResult(Overall, wroingList);
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
string xmlPath
{
get
{
return Application.streamingAssetsPath + "/StreamPlus.xml";
}
}
private void onSend(string json)
{
ws.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(json)), WebSocketMessageType.Binary, true, ct); //发送数据
}
public static string getAuthUrl(string hostUrl, string apiKey, string apiSecret)
{
Uri uri = new Uri(hostUrl);
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
string date = DateTime.UtcNow.ToUniversalTime().ToString(CultureInfo.CurrentCulture.DateTimeFormat.RFC1123Pattern, new System.Globalization.CultureInfo("en-us"));
StringBuilder builder = new StringBuilder("host: ").Append(uri.Host).Append("\n").//
Append("date: ").Append(date).Append("\n").//
Append("GET ").Append(uri.LocalPath).Append(" HTTP/1.1");
byte[] hexDigits;
string sha;
using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(apiSecret)))
{
hexDigits = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
sha = Convert.ToBase64String(hexDigits);
}
string authorization = string.Format("hmac username=\"{0}\", algorithm=\"{1}\", headers=\"{2}\", signature=\"{3}\"", apiKey, "hmac-sha256", "host date request-line", sha);
hostUrl += "?authorization=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(authorization)) + "&date=" + date + "&host=" + uri.Host;
return hostUrl;
}
private void AddLog(string str)
{
log += str;
// max log
if (log.Length > 32 * 1024)
{
log = log.Substring(16 * 1024);
}
}
//private void Socket_OnOpen(object sender, OpenEventArgs e)
//{
// SetMessage("打开" + sender.ToString());
//}
//private void Socket_OnMessage(object sender, MessageEventArgs e)
//{
// //SetMessage();
// //string
//}
//private void Socket_OnClose(object sender, CloseEventArgs e)
//{
//}
//private void Socket_OnError(object sender, UnityWebSocket.ErrorEventArgs e)
//{
// SetMessage("错误" + sender.ToString());
//}
[Serializable]
public class IseNewResponseData
{
public int code;
public string message;
public string sid;
public ResPonseData data;
}
[Serializable]
public class ResPonseData
{
public int status;
public string data;
}
}
public class Syll
{
public TxtData txtData = new TxtData();
public Syll(string xml)
{
Dictionary<string, string> syll = new Dictionary<string, string>();
Dictionary<string, string> phone = new Dictionary<string, string>();
Dictionary<string, string> phone1 = new Dictionary<string, string>();
List<string> sub_strs = new List<string>();
string[] strs = xml.Split('>');
for (int i = 0; i < strs.Length; i++)
{
if (strs[i].Contains("time_len"))
{
sub_strs.Add(strs[i]);
}
}
SetDic(sub_strs[0].Split(' '), syll);
SetDic(sub_strs[1].Split(' '), phone);
SetDic(sub_strs[2].Split(' '), phone1);
txtData.content = syll["content"];
txtData.symbol = syll["symbol"];
txtData.perr_msg = (Error)int.Parse(phone["perr_msg"]);
txtData.tone_perr_msg = (Error)int.Parse(phone1["perr_msg"]);
}
public static void SetDic(string[] strs, Dictionary<string, string> dic)
{
for (int i = 0; i < strs.Length; i++)
{
string str = strs[i];
if (str.Contains("="))
{
string[] sub_str = strs[i].Split('=');
dic.Add(sub_str[0].Replace("\"", string.Empty), sub_str[1].Replace("\"", string.Empty));
}
}
}
public bool IsCorrect
{
get
{
if (txtData.perr_msg != Error.Correct || txtData.tone_perr_msg != Error.Correct)
{
return false;
}
return true;
}
}
}
}
public struct TxtData
{
public string content;
public string symbol;
public Error perr_msg;
public Error tone_perr_msg;
}
public enum Error
{
Correct=0,
ReadMiss = 16,
AddRead= 32,
BackwardRead= 64,
Replace=128,
Unison = 1,
Pattern=2,
UnisonPattern =3,
}
}