PC-VR 语音识别(百度智能云)

 注:应用VR(HTC)获取圆盘键,进行语音的回放、转化文字、保存语音等(确认百度智能云有可用资源)

 

using UnityEngine;
using UnityEngine.UI;
using System;
using System.IO;
using UnityEditor;
using System.Collections;
using UnityEngine.Networking;
using System.Text.RegularExpressions;

public class TestMicro : MonoBehaviour
{
    public static TestMicro _Instance;


    public AudioClip RecordedClip;//录音
    public AudioSource audioSource;//播放的音频
    public Text Infotxt;//提示信息
    public Text Adress;//音频保存地址
    private string fileName;//保存的文件名
    private byte[] data;
    /// <summary>
    /// VR手柄控制器
    /// </summary>
    public SteamVR_TrackedObject trackdeObjec;
    //是否开启
    public bool IsMicro = false;
    

    [Header("倒计时")]
    public Text time_add;
    private float number;
    private float time_offect;
    private bool stop;

    //---------------------------------------------------
    //百度语音识别相关key
    string appId = "18518840";
    string apiKey = "";              //填写自己的apiKey
    string secretKey = "";         //填写自己的secretKey

    //记录accesstoken令牌
    string accessToken = string.Empty;

    //录音频率,控制录音质量(8000,16000)
    int recordFrequency = 8000;
    //标记是否有麦克风
    private bool isHaveMic = false;

    //当前录音设备名称
    string currentDeviceName = string.Empty;


    [Header("显示结果的文本")]
    public Text textResult;


    //语音识别的结果
    string asrResult = string.Empty;
    //实际录音长度(由于unity的录音需先指定长度,导致识别上传时候会上传多余的无效字节)
    //通过该字段,获取有效录音长度,上传时候剪切到无效的字节数据即可
    int trueLength = 0;
    //表示录音的最大时长
    int recordMaxLength = 100;
    //上次按下时间戳
    double lastPressTimestamp = 0;
    private void Start()
    {
        _Instance = this;

        number = recordMaxLength;

        //获取麦克风设备,判断是否有麦克风设备
        if (Microphone.devices.Length > 0)
        {
            isHaveMic = true;
            currentDeviceName = Microphone.devices[0];
            Infotxt.text = "设备名称为:" + Microphone.devices[0].ToString() + "请点击触摸板上面开始录音!";
        }
        else
        {
            Infotxt.text = "缺少麦克风设备!";
        }

    }
    void Update()
    {
        if (IsMicro)
        {
            SteamVR_Controller.Device device = SteamVR_Controller.Input((int)trackdeObjec.index);
            //圆盘键分区
            if (device.GetPressDown(SteamVR_Controller.ButtonMask.Axis0))
            {
                Vector2 pad = device.GetAxis();

                Vector3 cross = Vector3.Cross(new Vector2(1, 0), pad);

                float angle = Vector2.Angle(new Vector2(1, 0), pad);

                float ang = cross.z > 0 ? -angle : angle;

                //下

                if (ang > 45 && ang < 135)
                {
                    stop = false;
                    //Stop();
                    EndRecording();
                    Debug.Log("下边");
                }
                //上  
                else if (ang < -45 && ang > -135)
                {
                    stop = true;
                    StartRecording(true);
                    //Begin();
                    Debug.Log("上边");
                }
                //左  
                else if ((ang < 180 && ang > 135) || (ang < -135 && ang > -180))
                {
                    Player();

                    Debug.Log("左边");
                }

                //右  
                else if ((ang > 0 && ang < 45) || (ang > -45 && ang < 0))
                {
                    Save();
                    Debug.Log("右边");
                }
            }
        }
        if (stop == true)
        {
            time_offect += Time.deltaTime;
            if (time_offect > 1)
            {
                number--;
                if (number <= 0)
                {
                    stop = false;
                    EndRecording();

                    number = 0;
                }
                time_add.text = "倒计时:\n" + number.ToString();
                time_offect -= 1;
            }
        }
        else
        {
            number = 60;
            time_add.text = "倒计时:\n" + number.ToString();
        }

    }
    #region 语音识别与录入
    /// <summary>
    /// 开始录音
    /// </summary>
    /// <param name="isLoop"></param>
    /// <param name="lengthSec"></param>
    /// <param name="frequency"></param>
    /// <returns></returns>
    public bool StartRecording(bool isLoop = false) //8000,16000
    {
        if (isHaveMic == false || Microphone.IsRecording(currentDeviceName))
        {
            Infotxt.text = "请确认麦克风设备是否已连接!";
            return false;
        }

        Infotxt.text = "开始录音!";

        //开始录音
        /*
         * public static AudioClip Start(string deviceName, bool loop, int lengthSec, int frequency);
         * deviceName   录音设备名称.
         * loop         如果达到长度,是否继续记录
         * lengthSec    指定录音的长度.
         * frequency    音频采样率   
         */

        lastPressTimestamp = GetTimestampOfNowWithMillisecond();

        RecordedClip = Microphone.Start(currentDeviceName, isLoop, recordMaxLength, recordFrequency);

        return true;
    }

    /// <summary>
    /// 录音结束,返回实际的录音时长
    /// </summary>
    /// <returns></returns>
    public int EndRecording()
    {
        if (isHaveMic == false || !Microphone.IsRecording(currentDeviceName))
        {
            Infotxt.text = "请确认麦克风设备是否已连接!";
            return 0;
        }
     
        //结束录音
        Microphone.End(currentDeviceName);

        data = GetRealAudio(ref RecordedClip);

        Infotxt.text = "录音结束!";

        trueLength = Mathf.CeilToInt((float)(GetTimestampOfNowWithMillisecond() - lastPressTimestamp) / 1000f);
        //向上取整,避免遗漏录音末尾
        return Mathf.CeilToInt((float)(GetTimestampOfNowWithMillisecond() - lastPressTimestamp) / 1000f);
    }
    /// <summary>
    /// 放开录音按钮
    /// </summary>
    /// <param name="eventData"></param>
    public void Player()
    {
        
       
        if (trueLength > 1)
        {
            Infotxt.text = "正在播放录音!";

            audioSource.PlayOneShot(RecordedClip);
            StartCoroutine(_StartBaiduYuYin());
        }
        else
        {
            textResult.text = "录音时长过短";

            Infotxt.text = "录音时长过短!";
        }
    }


    /// <summary>
    /// 获取毫秒级别的时间戳,用于计算按下录音时长
    /// </summary>
    /// <returns></returns>
    public double GetTimestampOfNowWithMillisecond()
    {
        return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000;
    }
    /// <summary>
    /// 获取真正大小的录音
    /// </summary>
    /// <param name="recordedClip"></param>
    /// <returns></returns>
    public static byte[] GetRealAudio(ref AudioClip recordedClip)
    {
        int position = Microphone.GetPosition(null);
        if (position <= 0 || position > recordedClip.samples)
        {
            position = recordedClip.samples;
        }
        float[] soundata = new float[position * recordedClip.channels];
        recordedClip.GetData(soundata, 0);
        recordedClip = AudioClip.Create(recordedClip.name, position,
        recordedClip.channels, recordedClip.frequency, false);
        recordedClip.SetData(soundata, 0);
        int rescaleFactor = 32767;
        byte[] outData = new byte[soundata.Length * 2];
        for (int i = 0; i < soundata.Length; i++)
        {
            short temshort = (short)(soundata[i] * rescaleFactor);
            byte[] temdata = BitConverter.GetBytes(temshort);
            outData[i * 2] = temdata[0];
            outData[i * 2 + 1] = temdata[1];
        }
        Debug.Log("position=" + position + "  outData.leng=" + outData.Length);
        return outData;
    }
    //#endregion
    /// <summary>
    /// 保存录音
    /// </summary>
    public void Save()
    {

        if (!Microphone.IsRecording(currentDeviceName) && RecordedClip != null)
        {
            fileName = DateTime.Now.ToString("yyyyMMddHHmmssffff");
            if (!fileName.ToLower().EndsWith(".wav"))
            {//如果不是“.wav”格式的,加上后缀
                fileName += ".wav";
            }
            string path = Path.Combine(Application.streamingAssetsPath, fileName);//录音保存路径
            Debug.Log(path);
            print(path);//输出路径
            Adress.text = path;
            using (FileStream fs = CreateEmpty(path))
            {
                fs.Write(data, 0, data.Length);
                WriteHeader(fs, RecordedClip); //wav文件头
            }
            Infotxt.text = "已经保存";
        }
        else
        {
            Infotxt.text = "正在录音中,请先停止录音!";
        }
    }

    /// <summary>
    /// 写文件头
    /// </summary>
    /// <param name="stream"></param>
    /// <param name="clip"></param>
    public static 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 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);
        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);
    }
    /// <summary>
    /// 创建wav格式文件头
    /// </summary>
    /// <param name="filepath"></param>
    /// <returns></returns>
    private FileStream CreateEmpty(string filepath)
    {
        FileStream fileStream = new FileStream(filepath, FileMode.Create);
        byte emptyByte = new byte();

        for (int i = 0; i < 44; i++) //为wav文件头留出空间
        {
            fileStream.WriteByte(emptyByte);
        }

        return fileStream;
    }

    /// <summary>
    /// 获取accessToken请求令牌
    /// </summary>
    /// <returns></returns>
    IEnumerator _GetAccessToken()
    {
        var uri =
            string.Format(
                "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id={0}&client_secret={1}",
                apiKey, secretKey);
        UnityWebRequest unityWebRequest = UnityWebRequest.Get(uri);
        yield return unityWebRequest.SendWebRequest();

        if (unityWebRequest.isDone)
        {

            print(unityWebRequest.downloadHandler.text);

            //这里可以考虑用Json,本人比较懒所以用正则匹配出accessToken
            Match match = Regex.Match(unityWebRequest.downloadHandler.text, @"access_token.:.(.*?).,");
            if (match.Success)
            {
                //表示正则匹配到了accessToken
                accessToken = match.Groups[1].ToString();
            }
            else
            {
                textResult.text = "验证错误,获取AccessToken失败!!!";
            }
        }
    }

    /// <summary>
    /// 发起语音识别请求
    /// </summary>
    /// <returns></returns>
    IEnumerator _StartBaiduYuYin()
    {
        if (string.IsNullOrEmpty(accessToken))
        {
            yield return _GetAccessToken();
        }

        asrResult = string.Empty;

        //处理当前录音数据为PCM16
        float[] samples = new float[recordFrequency * trueLength * RecordedClip.channels];
        RecordedClip.GetData(samples, 0);

        var samplesShort = new short[samples.Length];
        for (var index = 0; index < samples.Length; index++)
        {
            samplesShort[index] = (short)(samples[index] * short.MaxValue);
        }
        byte[] datas = new byte[samplesShort.Length * 2];
        Buffer.BlockCopy(samplesShort, 0, datas, 0, datas.Length);

        string url = string.Format("{0}?cuid={1}&token={2}", "https://vop.baidu.com/server_api", SystemInfo.deviceUniqueIdentifier, accessToken);

        WWWForm wwwForm = new WWWForm();
        wwwForm.AddBinaryData("audio", datas);

        UnityWebRequest unityWebRequest = UnityWebRequest.Post(url, wwwForm);

        unityWebRequest.SetRequestHeader("Content-Type", "audio/pcm;rate=" + recordFrequency);

        yield return unityWebRequest.SendWebRequest();

        if (string.IsNullOrEmpty(unityWebRequest.error))
        {
            asrResult = unityWebRequest.downloadHandler.text;
            if (Regex.IsMatch(asrResult, @"err_msg.:.success"))
            {
                Match match = Regex.Match(asrResult, "result.:..(.*?)..]");
                if (match.Success)
                {
                    asrResult = match.Groups[1].ToString();
                }
            }
            else
            {
                asrResult = "识别结果为空";
            }
            textResult.text = asrResult;
        }
    }
    #endregion
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值