讯飞文字转语音_unity3D

来源:互联网 发布:淘宝网首页官网电脑版 编辑:程序博客网 时间:2024/06/07 10:52

前言

    讯飞语音是国内的智能语音前沿,有语音合成及语音识别还有一些其他高级的语音服务。前面已经写过一篇unity中使用在线语音的方式,不过由于是国外的网站不稳定,速度相对也慢,目前貌似已经不能用了。所以看好讯飞在线免费使用的优点,同时也看靠c++的而不是直接通过http请求的方式,在这里简要分享下在unity3d开发环境下,基于windows平台的在线语音生成。值得说明的讯飞的语音选择面广,还支持部分方言!但是对于创业型公司来说,离线版本的收费还是比较昂贵的,也是为什么本文只会涉及在线语音生成。

准备SDK

    在讯飞语音官方网站(http://www.xfyun.cn/),可以下载到最新的SDK,不过要先注册,而注册后创建应用后生成的apiKey就是使用这些SDK的钥匙。解压后,应该能看到doc文件夹,如果你精通c语言,同时也熟练使用C#调用C的库,那么这篇文字对你意义不大,你需要的只是马上查看doc中的api去写在c#中实现c的接口了。当然因为本人对调用c的dll不是很熟练,所以才想把一些遇到的小问题记录下来,防止和我一样不熟悉的人会卡壳。在simple文件夹中有一些官方的demo,都是c写的,直接调用在bin中的msc.dll中的方法。在往下阅读之前,你或许可以先去学习下官方的例子。

提取接口

    查看doc中的iFlytek MSC Reference Manual.html网页,能直接看到msc.dll中所有的api。如果你只关心语音生成,那么我们直接跳到qtts.h部分,这里面一共就只有5个接口,也就是说我们的工作量并不大,只需要搞清楚这几个接口就好了。

将这些接口转换到C#中,应该看起来是这样的:

        [DllImport(mscdll, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]        public static extern IntPtr QTTSSessionBegin(string _params, ref int errorCode);        [DllImport(mscdll, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]        public static extern int QTTSTextPut(string sessionID, string textString, uint textLen, string _params);        [DllImport(mscdll, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]        public static extern IntPtr QTTSAudioGet(string sessionID, ref int audioLen, ref SynthStatus synthStatus, ref int errorCode);        [DllImport(mscdll, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]        public static extern int QTTSSessionEnd(string sessionID, string hints);        [DllImport(mscdll, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]        public static extern int QTTSGetParam(string sessionID, string paramName, string paramValue, ref uint valueLen);

值得注意的是,因为涉及到中文章
QTTSTextPut
接口中CharSet必须要设置为
CharSet.Unicode
这是因为,如果在C#中调用dll,如果不指定的话,默认应该就传入Ascii码了,这样就会出现接收端为乱码的问题。

调用接口

    在使用这几个接口的时候,可以参考官方demo中的tts.c文件,而这里实现的逻辑也和其中相差无几,唯一的区别就是,用的是C#无言也调用那几个接口。要说明的是本人很久没有用c++和c了,所以下面这段代码也不算原创,只是其中部分功能稍微调整了一下。

 public void Speak(string speekText, string szParams, string outWaveFlie)        {            byte[] bytes = null;            int ret = 0;            try            {                sessionID = Marshal.PtrToStringAuto(MSPAPI.QTTSSessionBegin(szParams, ref ret));                if (ret != 0)                {                    if (ttsSpeakErrorEvent != null) ttsSpeakErrorEvent.Invoke("初始化TTS引会话错误,错误代码:" + ret);                    return;                }                ret = MSPAPI.QTTSTextPut(sessionID, speekText, (uint)Encoding.Unicode.GetByteCount(speekText), string.Empty);                if (ret != 0)                {                    if (ttsSpeakErrorEvent != null) ttsSpeakErrorEvent.Invoke("向服务器发送数据,错误代码:" + ret);                    return;                }                IntPtr audio_data;                int audio_len = 0;                SynthStatus synth_status = SynthStatus.MSP_TTS_FLAG_STILL_HAVE_DATA;                using (MemoryStream ms = new MemoryStream())                {                    ms.Write(new byte[44], 0, 44);                    //写44字节的空文件头                    while (synth_status == SynthStatus.MSP_TTS_FLAG_STILL_HAVE_DATA)                    {                        audio_data = MSPAPI.QTTSAudioGet(sessionID, ref audio_len, ref synth_status, ref ret);                        if (audio_data != IntPtr.Zero)                        {                            byte[] data = new byte[audio_len];                            Marshal.Copy(audio_data, data, 0, audio_len);                            ms.Write(data, 0, data.Length);                            if (synth_status == SynthStatus.MSP_TTS_FLAG_DATA_END || ret != 0)                            {                                if (ret != 0)                                {                                    if (ttsSpeakErrorEvent != null) ttsSpeakErrorEvent.Invoke("下载TTS文件错误,错误代码:" + ret);                                    return;                                }                                break;                            }                        }                        Thread.Sleep(150);                    }                    System.Diagnostics.Debug.WriteLine("wav header");                    WAVE_Header header = getWave_Header((int)ms.Length - 44);     //创建wav文件头                    byte[] headerByte = StructToBytes(header);                         //把文件头结构转化为字节数组                      //写入文件头                    ms.Position = 0;                                                        //定位到文件头                    ms.Write(headerByte, 0, headerByte.Length);                             //写入文件头                    bytes = ms.ToArray();                    ms.Close();                }                if (outWaveFlie != null)                {                    if (File.Exists(outWaveFlie))                    {                        File.Delete(outWaveFlie);                    }                    File.WriteAllBytes(outWaveFlie, bytes);                }            }            catch (Exception ex)            {                if (ttsSpeakErrorEvent != null) ttsSpeakErrorEvent.Invoke("Error:" + ex.Message);                return;            }            finally            {                ret = MSPAPI.QTTSSessionEnd(sessionID, "");                if (ret != 0)                {                    if (ttsSpeakErrorEvent != null) ttsSpeakErrorEvent.Invoke("结束TTS会话错误,错误代码:" + ret);                }                else                {                    if (tts_SpeakFinishedEvent != null) tts_SpeakFinishedEvent.Invoke(speekText, bytes);                }            }        }
其中要注意的一点是,因为c的dll中返回char*,在c#中,接收到的只能是指针,所以用了一个指针转换为字符串的方法:
Marshal.PtrToStringAuto
在对于字符串的长度问题,也没有直接使用string.Length,而是使用
(uint)Encoding.Unicode.GetByteCount(speekText)

封装为Unity模块

    在上面已经实现了文字转语音功能的基础上,可以将这些功能封装为unity主线程中可以直接调用的一个模块,便于程序的使用。和前面一篇文章实现的接口是一样的,只是添加了一个批量下载的功能。当然你也可以自己去封装,毕竟这里的功能未必适合你的项目。下面是封装后预留的接口:

  public interface ITextToAudio    {        event UnityAction<string> onError;        IEnumerator GetAudioClip(string text, UnityAction<AudioClip> OnGet, Params param = null);        IEnumerator Downland(string[] text,UnityAction<float> onProgressChanged ,Params param = null);        void CleanUpCatchs();    }
其中,下载的时候都是使用协程,可以利用WWW直接将得到的AudioClip返回回来(值得注意的是,如果你的音频足够了解应该可以直接从byte中创建audioClip,就没有必要使用www了,但目前也就暂时这样使用着)。而Params就是对官方参数的解析类,可以自行定义。基于官方的字符串结构,这样生成比较理想(直接重写ToString):

  public override string ToString()    {        var fields = typeof(Params).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance);        var param = new string[fields.Length];        for (int i = 0; i < fields.Length; i++)        {            param[i] = fields[i].Name + "=" + fields[i].GetValue(this);        }        return string.Join(",",param);    }

源码及引用

    因为这部分资料在网上还是比较少的,而且多数开发都是移动端和对语音识别相对较高,对于还在windows端开发的我,也只能默默羡慕他们了,所以和我一样在window端的你,少遇到点坑算是我的小小贡献了。

   下面是github的地址:Text2Audio_Unity

    对于本文来说,这里面部分代码并不是来自于自己的脑子,下面的我引用的开源地址:

  c#(非unity环境)iflytek-csharp-demo

  unitySDK(多平台)IFlySDKForUnity

原创粉丝点击