初探機器學習之使用訊飛TTS服務實現在線語音合成


最近在調研使用各個雲平台提供的AI服務,有個語音合成的需求因此就使用了一下科大訊飛的TTS服務,也用.NET Core寫了一個小示例,下面就是這個小示例及其相關背景知識的介紹。

一、什么是語音合成(TTS)

1.1 What is 語音合成?

  

  將文字信息轉化為聲音信息,給應用配上“嘴巴”,這就是語音合成。

Note:語音合成和語音識別技術是實現人機語音通信,建立一個有聽和講能力的口語系統所必需的兩項關鍵技術。使電腦具有類似於人一樣的說話能力,是當今時代信息產業的重要競爭市場。和語音識別相比,語音合成的技術相對說來要成熟一些,並已開始向產業化方向成功邁進,大規模應用指日可待。

1.2 語音合成的應用場景

  目前,語音合成技術在我們生活中具有廣泛的應用,如機器人發聲、有聲讀物制作、語音播報等等,這些應用場景都離不開語音合成。

  

Note:在語音導航應用、新聞類 APP 中,語音合成可以快速生成高質量的播報音頻,實現在開車、走路等不方便閱讀消息的情況下,音頻消息的即時傳達。

1.3 語音合成的基本原理

  這里借用網易智能的一篇文章中的介紹如下:

  

  簡單來說語音合成分為文本分析、韻律分析和聲學分析三個部分。通過文本分析提取出文本特征,在此基礎上預測基頻、時長、節奏等多種韻律特征,然后通過聲學模型實現從前端參數到語音參數的映射,最后通過聲碼器合成語音。整個過程類似於“編碼、信息匹配,解碼的過程”。

  

  對語音合成有興趣的朋友,可以閱讀以下這篇文章《吳恩達盛贊的Deep Voice詳解教程,教你快速理解百度的語音合成原理》。

二、使用.NET Core調用訊飛API

2.1 科大訊飛TTS服務

  訊飛提供了眾多極具特色的發音人(音庫)供您選擇。其合成音在音色、自然度等方面的表現均接近甚至超過了人聲。這種語音合成體驗,達到了真正可商用的標准。在我對百度、阿里、騰訊及訊飛的TTS服務對比中,訊飛的TTS體驗好一些,且發音人(音庫)最為豐富,還有四川話(准確來說是成都話)音庫,碉堡了。大家可以去訊飛TTS服務網頁體驗一下。

2.2 .NET Core調用示例

  (1)首先得去訊飛開放平台注冊一個賬號,並申請一個勾選有“在線語音合成”的應用

  

  這里主要是拿到AppID及ApiKey,並且可以通過發音人管理增加發音庫,當然,高級版的音庫都是要單獨收費的。免費版本的每天有500次的免費API調用機會。

  (2)參考官方API文檔和C# DEMO

  PS:由於訊飛官方提供的C# DEMO是一個基於.NET Framework的比較老的DEMO,在.NET Core下無法正常使用,我將其簡單地改寫成了.NET Core版本,並封裝了一個XunFeiCloudTtsService類如下:(每個屬性都有注釋,不再贅述,只是對API調用需要的一些屬性的簡單封裝)

    public class XunFeiCloudTtsService
    {
        // Header Type : audio/mpeg
        private const string AUDIO_MPEG_TYPE = "audio/mpeg";
        // AppID, AppKey 從訊飛開放雲平台獲取
        public string AppID { get; set; } = "your AppID";
        public string ApiKey { get; set; } = "your ApiKey";
        // 要進行合成的文字
        public string TextToSpeech { get; set; } = "這是一段測試文字";
        // 訊飛TTS服務API地址
        public string ServiceUrl { get; set; } = "http://api.xfyun.cn/v1/service/v1/tts";
        // aue = raw, 音頻文件保存類型為 wav
        // aue = lame, 音頻文件保存類型為 mp3
        public string AUE { get; set; } = "raw";
        // 音頻采樣率,可選值:audio/L16;rate=8000,audio/L16;rate=16000
        public string AUF { get; set; } = "audio/L16;rate=16000";
        // 發音人,可選值:詳見控制台-我的應用-在線語音合成服務管理-發音人授權管理
        public string VoiceName { get; set; } = "xiaoyan";
        // 引擎類型,可選值:aisound(普通效果),intp65(中文),intp65_en(英文),
        // mtts(小語種,需配合小語種發音人使用),x(優化效果),
        // 默認為intp65
        public string EngineType { get; set; } = "intp65";
        // 語速,可選值:[0-100],默認為50
        public string Speed { get; set; } = "50";
        // 音量,可選值:[0-100],默認為50
        public string Volume { get; set; } = "50";
        // 音高,可選值:[0-100],默認為50
        public string Pitch { get; set; } = "50";
        // URL加密后的TextToSpeech
        public string Bodys { get; set; }
        // 要保存的文件夾路徑
        public string SavePath { get; set; } = "./Output/";
        // 要保存的文件名
        public string FileName { get; set; } = $"TTS-{ Guid.NewGuid().ToString() }";

        static XunFeiCloudTtsService()
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        }

        public XunFeiCloudTtsService()
        { }

        public XunFeiCloudTtsService(string appID, string apiKey, string serviceUrl)
        {
            AppID = appID;
            ApiKey = apiKey;
            ServiceUrl = serviceUrl;
        }

        public string GetTtsResult()
        {
            SetTextDataBodys();

            string param = "{\"aue\":\"" + AUE + "\",\"auf\":\"" + AUF + "\",\"voice_name\":\"" + VoiceName + "\",\"engine_type\":\""
                + EngineType + "\",\"speed\":\"" + Speed + "\",\"volume\":\"" + Volume + "\",\"pitch\":\"" + Pitch + "\"}";
            // 獲取十位的時間戳
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            string curTime = Convert.ToInt64(ts.TotalSeconds).ToString();
            // 對參數先utf-8然后用base64編碼
            byte[] paramData = Encoding.UTF8.GetBytes(param);
            string paraBase64 = Convert.ToBase64String(paramData);
            // 形成簽名
            string checkSum = Md5(ApiKey + curTime + paraBase64);
            // 設置HttpHeaders
            var ttsRequest = (HttpWebRequest)WebRequest.Create(ServiceUrl);
            ttsRequest.Method = "POST";
            ttsRequest.ContentType = "application/x-www-form-urlencoded";
            ttsRequest.Headers.Add("X-Param", paraBase64);
            ttsRequest.Headers.Add("X-CurTime", curTime);
            ttsRequest.Headers.Add("X-Appid", AppID);
            ttsRequest.Headers.Add("X-CheckSum", checkSum);

            using (Stream requestStream = ttsRequest.GetRequestStream())
            {
                using (StreamWriter streamWriter = new StreamWriter(requestStream, Encoding.GetEncoding("gb2312")))
                {
                    streamWriter.Write(Bodys);
                }
            }

            string responseText = string.Empty;
            HttpWebResponse ttsResponse = ttsRequest.GetResponse() as HttpWebResponse;
            using (Stream responseStream = ttsResponse.GetResponseStream())
            {
                using (StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("UTF-8")))
                {
                    string headerType = ttsResponse.Headers["Content-Type"];
                    if (headerType.Equals(AUDIO_MPEG_TYPE))
                    {
                        responseText = GetSuccessResponseText(ttsResponse);
                    }
                    else
                    {
                        responseText = streamReader.ReadToEnd();
                    }
                }
            }

            return responseText;
        }

        #region 私有輔助方法
        /// <summary>
        /// 獲取請求成功的響應文本
        /// </summary>
        /// <param name="ttsResponse">HttpWebResponse</param>
        /// <returns>響應Headers文本</returns>
        private string GetSuccessResponseText(HttpWebResponse ttsResponse)
        {
            string responseText = string.Empty;
            using (Stream stream = ttsResponse.GetResponseStream())
            {
                MemoryStream memoryStream = StreamToMemoryStream(stream);
                if (!Directory.Exists(SavePath))
                {
                    Directory.CreateDirectory(SavePath);
                }

                string fileType = string.Empty;
                switch (AUE.ToLower())
                {
                    case "raw":
                        fileType = "wav";
                        break;
                    case "lame":
                        fileType = "lame";
                        break;
                }

                File.WriteAllBytes($"{SavePath}{FileName}.{fileType}", streamTobyte(memoryStream));
                responseText = ttsResponse.Headers.ToString();
            }

            return responseText;
        }

        /// <summary>
        ///  對要合成語音的文字先用utf-8然后進行URL加密
        /// </summary>
        private void SetTextDataBodys()
        {
            byte[] textData = Encoding.UTF8.GetBytes(TextToSpeech);
            TextToSpeech = HttpUtility.UrlEncode(textData);
            Bodys = string.Format("text={0}", TextToSpeech);
        }

        /// <summary>
        /// 生成令牌 :X-CheckSum
        /// 計算方法:MD5(apiKey + curTime + param),三個值拼接的字符串,進行MD5哈希計算(32位小寫),其中apiKey由訊飛提供,調用方管理。
        /// </summary>
        /// <param name="token">apiKey + curTime + param</param>
        /// <returns>X-CheckSum</returns>
        private string Md5(string token)
        {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] bytes = Encoding.UTF8.GetBytes(token);
            bytes = md5.ComputeHash(bytes);
            md5.Clear();

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.Length; i++)
            {
                sb.Append(Convert.ToString(bytes[i], 16).PadLeft(2, '0'));
            }

            return sb.ToString().PadLeft(32, '0');
        }

        /// <summary>
        /// 將流轉換為緩存流
        /// </summary>
        /// <param name="instream">輸入流</param>
        /// <returns>輸出流</returns>
        private MemoryStream StreamToMemoryStream(Stream instream)
        {
            MemoryStream outstream = new MemoryStream();
            const int bufferLen = 4096;
            byte[] buffer = new byte[bufferLen];
            int count = 0;
            while ((count = instream.Read(buffer, 0, bufferLen)) > 0)
            {
                outstream.Write(buffer, 0, count);
            }

            return outstream;
        }

        /// <summary>
        /// 把緩存流轉換成字節組
        /// </summary>
        /// <param name="memoryStream">緩存劉</param>
        /// <returns>字節數組</returns>
        private byte[] streamTobyte(MemoryStream memoryStream)
        {
            byte[] buffer = new byte[memoryStream.Length];
            memoryStream.Seek(0, SeekOrigin.Begin);
            memoryStream.Read(buffer, 0, buffer.Length);

            return buffer;
        }
        #endregion
    }

  *.由於.NET Core下Encoding默認不支持GB2312,因此需要事先引入一個包:System.Text.Encoding.CodePages,然后在程序啟動時注冊一下,這里已經封裝靜態構造函數里面了。

NuGet>Install-Package System.Text.Encoding.CodePages

  客戶端調用:

    public class Program
    {
        public static void Main(string[] args)
        {
            XunFeiCloudTtsService ttsService = new XunFeiCloudTtsService();
            ttsService.AppID = "5c3806f12121";
            ttsService.ApiKey = "a99fff9fc537d883a181231231a37bf56dc8f28";
            ttsService.TextToSpeech = "大家好我是第一個語音合成!";
            ttsService.SavePath = "./TTS-Result/";
            ttsService.FileName = "Hello-TTS";
            ttsService.Speed = "60";

            var result = ttsService.GetTtsResult();
            Console.WriteLine(result);

            Console.ReadKey();
        }
    }

  *.這里AppID和ApiKey請使用你自己的,我博文這里是亂寫的。

  調用結果:

  (1)響應結果

  

  *.如果提示illegal client ip,請到控制台中加入白名單列表:

  

  (2)語音文件

  

  So,播放一下?

  

三、小結

  有了語音合成,我們可以在我們的業務系統或者App中有了更多的玩法,雖然我們不了解語音合成的具體實現原理。此文只是一個簡單的使用示例,無更多的內容,希望對你有幫助,本文的示例代碼可以點擊這里

參考資料

(1)科大訊飛開放雲平台,《在線語音合成服務

(2)科大訊飛TTS服務,API說明文檔

(3)網易智能,《讓機器說話更自然,語音合成還能干什么

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM