TTS通用播放庫技術設計

TTS音頻播放庫技術設計

目錄介紹

  • 01.整體介紹概述
    • 1.1 項目背景介紹
    • 1.2 遇到問題
    • 1.3 基礎概念介紹
    • 1.4 設計目標
    • 1.5 問題答疑和思考
  • 02.技術調研說明
    • 2.1 語音播放方案
    • 2.2 TTS技術分析
    • 2.3 語音合成技術
    • 2.4 方案選擇說明
    • 2.5 方案設計思路
    • 2.6 文本生成音頻
  • 03.系統TTS使用實踐
    • 3.1 如何播放文本
    • 3.2 如何循環播放
    • 3.3 如何添加播放監聽
    • 3.4 調整TTS音效
    • 3.5 語音合成文件在哪
  • 04.TTS功能庫設計
    • 4.1 設計語音播放通用接口
    • 4.2 設計語音播放實體
    • 4.3 設計語音播放順序
    • 4.4 設計語音播放分發器
    • 4.5 設計語音播放實現類
    • 4.6 設計語音播放服務
    • 4.7 設計語音播放監聽
    • 4.7 TTS功能庫API調用
  • 05.TTS功能庫實踐
    • 5.1 TTS播放功能實現
    • 5.2 TTS如何順序播放
    • 5.3 TTS如何實現優先級
    • 5.4 如何一鍵切換類型
    • 5.5 多線程播放實踐
    • 5.6 如何調整語速和語音
    • 5.7 如何播放長內容
    • 5.8 使用TTS注意要點
  • 06.TTS功能庫穩定性
    • 6.1 邊界異常設計
    • 6.2 播放異常設計
    • 6.3 其他穩定性設計
  • 07.系統TTS機制原理
    • 7.1 TextToSpeech機制
    • 7.2 Speech整體設計
    • 7.3 TTS初始化流程
    • 7.4 speak播報流程原理
    • 7.5 TextToSpeechService

01.整體介紹概述

1.1 項目背景介紹

  • 硬件設備需要通過tts語音向用戶傳遞信息

1.2 遇到問題

  • 問題1:使用系統TTS語音聲音聽起來可能存在差異
    • 不同的 TTS 引擎可能在發音和語調方面存在差異。這可能導致在不同設備上合成的語音聽起來不一致,或者某些單詞或短語的發音不準確。
  • 問題2:TTS在某些機型上不支持
    • 某些設備可能沒有預裝 TTS 引擎,或者用戶可能選擇禁用或卸載默認的 TTS 引擎。這可能導致應用程序無法使用 TextToSpeech 類進行語音合成。
  • 問題3:低端設備引擎效果不佳
    • 設備可能提供較低質量的引擎。這可能導致在不同設備上的語音合成質量和效果不一致。
  • 問題4:無法支持語音定制功能
    • 對于一些高級的定制需求,如更改語音合成的音色、速度、音量等,可能會受到限制。限制靈活性。

1.3 基礎概念介紹

  • TTS現狀和發展
    • 語音合成又稱文語轉換(Text to Speech,TTS)技術,是語音處理領域的一個重要的研究方向,旨在讓機器生成自然動聽的人類語音。
  • 系統TTS(Text-to-Speech)介紹
    • TTS 引擎(TTS Engine):TTS 引擎是實際執行文本到語音轉換的組件。Android 提供了默認的 TTS 引擎,即 Google Text-to-Speech 引擎。
    • TextToSpeech 類:TextToSpeech 類是 Android 提供的 API 類,用于與 TTS 引擎進行交互。它提供了一組方法,用于將文本轉換為語音,并控制語音的播放速度、音量等參數。
    • 初始化 TTS 引擎:在使用 TTS 功能之前,需要初始化 TTS 引擎。通過創建 TextToSpeech 對象,并傳遞初始化完成的監聽器,可以初始化 TTS 引擎。一旦引擎初始化完成,就可以開始使用 TTS 功能。
    • 語音合成:使用 TextToSpeech 類的 speak() 方法可以將文本轉換為語音。TTS 引擎將根據指定的參數將文本轉換為語音,并通過設備的揚聲器播放出來。
    • 語音監聽器:TextToSpeech 類的 setOnUtteranceProgressListener() 方法可以設置語音合成的監聽器。通過設置語音合成的監聽器,可以獲取語音合成的狀態和進度。
    • 支持的語言:TTS 引擎支持多種語言,可以通過 setLanguage() 方法設置要使用的語言。需要注意的是,不同的 TTS 引擎可能支持的語言范圍有所不同。

1.4 設計目標

  • 設計TTS功能庫的API目標
    • 1.開發者調用tts播放api簡單易用
    • 2.開發者可以自由切換不同資源播放tts
    • 3.可以設置優先級
    • 4.可以添加tts播放監聽,監聽周期
  • 代碼設計目標準則
    • 符合開閉原則:對外拓展是開放的,更改是封閉的。
    • 符合接口分離原則:不同層通過抽象接口隔離,針對不同TTS方案抽取通用接口方法,磨平差異性
    • 符合類指責分明:類的功能聚合,方便后期維護和迭代修改

1.5 問題答疑和思考

  • 關于TTS一些問題思考
    • TTS技術的工作原理是什么?可以簡要解釋一下TTS的基本流程嗎?
    • TTS系統中的文本預處理階段通常包括哪些步驟?聲碼器是什么?
    • TTS系統中的語音合成是否支持多種語言和語音風格?如何實現多樣性的語音輸出?
  • 高級難度大的問題
    • 針對一個長文本內容,如何對長內容進行tts播放。如果讓你設計,要注意什么問題?
    • 陸續添加10個tts,如何保證按照順序播放完成。如果中間某個播放異常,該如何處理?
    • 系統自帶TTS引擎,其核心原理是什么,是通過什么進行發出聲音?合成語音質量如何評估?

02.技術調研說明

2.1 語音播放方案

  • 調研后主要的語音播報方案有一下幾種:
    • 基于第三方的TTS SDK,如百度、思必馳、訊飛等;
    • 自研Native的TTS引擎+模型;
    • 基于云端的TTS方案;
    • 使用手機自帶的TTS引擎。
  • 不管是市面上那種tts方案,他們實現一般是以下:
    • 使用系統提供的 MediaPlayer 類:Android 提供了 MediaPlayer 類,可以用于播放音頻文件,包括語音文件。
    • 使用 Android TTS(Text-to-Speech)引擎:Android 提供了內置的 TTS 引擎,可以將文本轉換為語音并進行播放。
    • 使用第三方的 TTS 引擎,如Google的Text-to-Speech引擎(Google Text-to-Speech Engine)、MaryTTS、Flite 等。
    • 使用音頻流播放:如果您有原始的音頻數據,而不是語音文件或文本,您可以使用 Android 的 AudioTrack 類來播放音頻流。
    • 使用第三方音頻播放庫:包括 ExoPlayer、VLC Media Player等。這些庫提供了更多的功能和靈活性,可以滿足更復雜的音頻播放需求。

2.2 TTS技術分析

  • TTS技術主要分為兩種:
    • 通用TTS:適用于導航、語音播報、智能客服和大多數語音交互場景;
    • 個性化TTS:主要應用于對聲音質量較高的教育、長音頻、直播以及影視游戲配音等場景中。

2.3 語音合成技術

  • 語音合成模型經過長時間的發展
    • 由最初的基于拼接合成,到參數合成,逐漸達到了現階段感情充沛的基于端到端合成,最新一代端到端合成降低了對語言學知識的要求,可批量實現多語種的合成系統,語音自然程度高。
  • 語音合成技術內部分為前端和后端。
    • 前端主要負責文本的語音解析和處理,其處理內容主要包括語種、分詞、詞性預測、多音字處理、韻律預測、情感等。把文本上的發音的這些信息都預測出來之后,將信息送給TTS后端系統,后臺聲學系統融合這些信息后,將內容轉換為語音。
    • 后端聲學系統從第一代的語音拼接合成,到第二代的語音參數合成,到第三代端到端合成,后端聲學系統的智能化程度逐步增加,訓練素材需要標記的詳細程度和難度也在逐步減弱。

2.4 方案選擇說明

  • 客戶端實現有三種方案:
    • 外采:出于成本考慮,淘汰;
    • 自研引擎:語音團隊基于參數的合成引擎已完成開發,但是沒有人力支撐后續的調試,而播報的話術比較固定,并且對合成聲音的音質要求不是特別高,所以選擇了一種基于拼接的合成方案作為備選,語句的前部分和后部分使用完整的語音,中間變換部分通過逐字方式合成;
    • 手機自帶TTS引擎:Android系統已自帶了TTS引擎,但是并不是所有的手機都帶了中文引擎。
  • 語音方面的交互,Android SDK 提供多種方案
    • 語音交互的 VoiceInteraction 機制、語音識別的 Recognition 接口、語音播報的 TTS 接口。
  • TextToSpeech 機制的優點
    • 對于需要使用 TTS 的請求 App 而言: 無需關心 TTS 的具體實現,通過 TextToSpeech API 即用即有
    • 對于需要對外提供 TTS 能力的實現 Engine 而言,無需維護復雜的 TTS 時序和邏輯,按照 TextToSpeechService 框架的定義對接即可,無需關心系統如何將實現和請求進行銜接

2.5 方案設計思路

  • 針對TTS功能庫的設計
    • 不管是通過系統TTS,還是音頻Player,或者是外帶方案,這里創建播放tts語音的統一api接口。磨平不同技術實現的差異性!
  • 實現綜合性TTS播放功能庫
    • 支持系統TTS播放:使用系統自帶TextToSpeech來實現,主要是針對文本內容tts。
    • 支持系統音頻TTS播放:使用MediaPlayer來實現,主要是針對本地音頻文件,比如mp3等資源tts。
    • 支持網絡音頻TTS播放:使用谷歌ExoPlayer來實現,主要是針對網絡資源文件tts。

2.6 文本生成音頻

03.系統TTS使用實踐

3.1 如何播放文本

  • 如何進行播放?直接調用speak即可播放
    textToSpeech.speak(tts, TextToSpeech.QUEUE_FLUSH, null);
    

3.2 如何循環播放

  • 循環播放語音
    • 想讓他報個2-3遍或者循環播報的時候,我們來試一下
    for (int i=0 ; i<5 ; i++){
        textToSpeech.speak("簡單播放tts,"+i, TextToSpeech.QUEUE_FLUSH, null);
    }
    
    • 簡單的不行,但是問題來了,一段長的文字他只播報前面幾個字,然后又重新開始播報。
    • 這是因為textToSpeech.speak(tts, TextToSpeech.QUEUE_FLUSH, null);這個方法會自動關閉掉上面一個播報的內容,從而進行新一輪的播報。
  • 播放完成后再播放
    • 要等上一條播報完整了再進行播報,該如何操作呢?那么可以TTS有 isSpeaking() 這個方法
    for (int i=0 ; i<5 ; i++){
        if (!textToSpeech.isSpeaking()){
            textToSpeech.speak("簡單播放tts,"+i, TextToSpeech.QUEUE_FLUSH, null);
        }
    }
    
    • 這樣就可以播全了嘛? 非也,for循環飛快的跑只要發現在speaking那么直接跳過開始走下一個i
  • 如何正確循環播放。這樣就相當于在一個消息隊列然后進行循環的播報。
    for (int i=0 ; i<5 ; i++){
        textToSpeech.speak("簡單播放,"+i, TextToSpeech.QUEUE_ADD, null);
    }
    

3.3 如何添加播放監聽

  • 關于監聽tts狀態如下所示:
    private final class OnCompleteListener extends UtteranceProgressListener {
        /**
         * 播放完成。這個是播報完畢的時候 每一次播報完畢都會走
         */
        @Override
        public void onDone(final String utteranceId) {
        }
        /**
         * 播放異常
         */
        @Override
        public void onError(final String utteranceId) {
        }
        /**
         * 播放開始。這個是開始的時候。是先發聲之后才會走這里
         */
        @Override
        public void onStart(final String utteranceId) {
        }
    }
    

3.4 調整TTS音效

  • 任意 App 都可以方便地采用系統內置或第三方提供的 TTS Engine 進行播放鈴聲提示、語音提示的請求
    • Engine 可以由系統選擇默認的 provider 來執行操作,也可由 App 具體指定偏好的目標 Engine 來完成。
  • 可以在設置中選擇發音音色。在系統設置---輔助功能---無障礙---文本轉語音路徑
    • 可以調整語速,這個時候使用textToSpeech播放語速就會發生變化;
    • 可以調整音調,這個時候使用textToSpeech播放音調就會發生變化;

3.5 語音合成文件在哪

  • 先說結果,語音合成文件可以指定,如下代碼所示
    //指定文件地址
    wavPath = Environment.getExternalStorageDirectory() + "/temp.wav";
    //添加文本內容
    map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, content);
    //使用指定的參數將給定的文本合成為文件。該方法是異步的,即該方法只是將請求添加到TTS請求隊列中,然后返回。
    //當此方法返回時,合成可能還沒有完成(甚至還沒有開始!)。
    int r = mSpeech.synthesizeToFile(content, map, wavPath);
    
  • 如果是沒有指定,那么合成的tts文件究竟在哪里呢?
    • 系統TextToSpeech合成的TTS文件實際上是存儲在設備的內部存儲中的。通常存儲在以下路徑中:/data/data/com.google.android.tts/files/voices/
    • 其中,"/data/data/"是設備的內部存儲路徑,"com.google.android.tts"是Google Text-to-Speech引擎的包名。
    • 在這個目錄下,你可以找到已安裝的TTS語音包的文件,每個語音包通常由多個文件組成,包括聲音文件、配置文件等。

04.TTS功能庫設計

4.1 設計語音播放通用接口

  • 關于tts音頻播放,不管采用那種方式。這里可以設計通用接口
    • [圖片上傳失敗...(image-aa9e57-1711365940836)]

4.2 設計語音播放實體

  • 由于播放的音頻,可以為文本,可以是音頻,可以是連接。因此設置一個實體bean
    • 播放文本,是字符串tts內容;這個使用系統自帶TextToSpeech來實現。
    • 播放音頻,是本地資源文件rawId內容;這個使用MediaPlayer來實現。
    • 播放連接,是網絡資源url內容;這個使用谷歌ExoPlayer來實現。

4.3 設計語音播放順序

  • 添加了多條語音內容,如何讓語音按照順序播放,要做到這幾點
    • 如何添加多個音頻并且是有序的?這個可以設計一個鏈表,用來存儲音頻實體。鏈表很好保證遍歷有序,且高效!
    • 如何讓一個視頻播完后接著播放下一個?監聽語音播放完成的狀態,然后獲取鏈表下個數據,接著就開始play播放
  • 添加多條語音內容,如何播放最新的語音,要做到這一點
    • 如何做到播放內容永遠都是最新數據?保證播放器只有一條數據,每次都是先stop掉前面的內容,然后再play播放當前內容。

4.4 設計語音播放分發器

  • 為什么要設計分發器?
    • 主要是解決添加多條語音,讓它們按照先后順序播放。相當于一個任務分發器,每次添加數據,都是調用addTask添加一個任務。
  • 關于分發器,進行順序播放的流程圖如下所示:
    • [圖片上傳失敗...(image-5921e4-1711365940836)]

4.5 設計語音播放實現類

  • 不管是那種方案實現tts語音播放功能,都去繼承統一定義的接口
    • 系統TextToSpeech實現統一播放接口,具體可以看:DefaultTtsPlayer
    • 系統MediaPlayer來實現統一播放接口,具體可以看:MediaAudioPlayer
    • 谷歌ExoPlayer來實現統一播放接口,具體可以看:ExoAudioPlayer
  • 關于語音播放實現服務,其層次圖如下所示:
    • [圖片上傳失敗...(image-14df5f-1711365940836)]

4.6 設計語音播放服務

  • 播放服務是指,開發者調用語音播放api,對外方便簡單調用,對內屏蔽具體實現細節。
    • 設計AudioServiceProvider接口,里面包含:init初始化語音服務,stop停止播放,pause暫停播放,play播放數據,setPlayStateListener監聽播放狀態等api
    • 設計AudioService代理類,相當于一個中間層,以隔離訪問者和被訪問者的實現細節。起到一個解耦合的作用。
    • 設計AudioServiceImpl1實現類1做支持tts按照順序和優先級播放,AudioServiceImpl2實現類2做支持每次播放最新的tts(會覆蓋之前的),這兩個做具體實現邏輯。

4.7 設計語音播放監聽

  • 不管是哪一種語音播放,我都想知道什么時候播放完成,或者異常。這個時候就需要設計播放監聽
    • 第一步:設計通用播放監聽的接口,其中包含播放開始,完成,異常等api;
    • 第二步:在各種不同語音播放實現類中,在播放邏輯中,添加監聽接口調用方法;
    • 第三步:外部開發者添加監聽后,能否通過接口回調做業務邏輯處理。比如播放完成,做某某業務。

05.TTS功能庫實踐

5.1 TTS播放功能實現

  • 系統TextToSpeech實現統一播放接口,具體可以看DefaultTtsPlayer
    • 初始化:在初始化的時候,創建TextToSpeech對象,并且在初始化引擎回調的onInit方法中,設置朗讀語音操作。因為,在團餐機中有英文tts;
    • 播放:設置tts語音播放,在這個里面設置tts監聽listener,然后調用speak方法對內容進行播放;
    • 銷毀:在不需要使用 TTS 功能時,應及時停止語音合成并釋放相關資源,以避免資源浪費??梢允褂?stop() 方法停止當前的語音合成,使用 shutdown() 方法釋放 TTS 引擎。
  • 系統MediaPlayer來實現統一播放接口,具體可以看MediaAudioPlayer
    • 播放:在播放的時候,通過MediaPlayer的api加載本地資源。然后prepare準備,在準備監聽listener回調完成后,開始play播放音頻;
    • 銷毀:在不需要播放的時候,需要及時釋放音頻對象,避免資源浪費。

5.2 TTS如何順序播放

  • 比如添加數據,按照順序播放(先播放raw,然后播放tts文本,最后播放url資源,按照添加的先后順序播放),代碼如下所示
    AudioPlayData data = new AudioPlayData.Builder(AudioTtsPriority.HIGH_PRIORITY)
            .tts("我是一個混合的協議的語音播報")
            .rawId(R.raw.timeout)
            .tts("Hello TTS Service")
            .url("https://asraudio.cdnjtzy.com/eb93cfd82d0044a1a9ce047c3aeafb8c.mp3")
            .url("https://asraudio.cdnjtzy.com/52bdab34457e4d9ca14a5a7feee94a23.mp3")
            .url("https://asraudio.cdnjtzy.com/eb93cfd82d0044a1a9ce047c3aeafb8c.mp3")          
            .build();
    AudioService.getInstance().play(data);
    
  • 按照添加的順序,排序順序播放的實踐步驟
    • 設計一個AudioPlayData對象,用鏈表實現
  • 如何處理分析器中的消息隊列
    • AudioTaskDispatcher,在分發器中開啟線程死循環,不斷從鏈表中取出消息,處理消息。

5.3 TTS如何實現優先級

  • 這個可以給音頻bean添加類型標簽
    • 其設計思想可以借鑒:handler普通消息和屏障消息的設計思想,給消息打個標簽,先從消息隊列中取出所有的屏障消息處理,然后在從消息隊列中取出所有的普通消息做處理。
    • 消息機制的同步屏障核心思想:屏障消息就是為了確保異步消息的優先級,設置了屏障后,同步消息會被擋住。屏障消息優先級高于普通消息。

5.5 多線程播放實踐

  • 如何保證多線程播放安全,分別該如何選擇?
    • 對象鎖
    • lock鎖
    • synchronized
  • 三種鎖分別有不同的應用場景
    • 這里在分發器的while循環中,關于設置線程wait操作,使用synchronized鎖,用在代碼塊中。保證同一個對象在多線程中操作是線程安全的。
    • 關于addTask添加tts播放任務,這里用到了lock鎖,在開始添加調用lock(),在執行結束調用unlock釋放。

5.7 如何播放長內容

  • 在 Android 中,使用 TTS(Text-to-Speech)播放長內容可以通過以下步驟實現:
    • 將長內容分割為適當的片段:由于 TTS 引擎可能有長度限制,將長內容分割為適當的片段是必要的。可以根據需要的片段長度或其他標準將長內容分割為多個較短的文本片段。
    • 使用 speak() 方法逐個播放片段:使用 TextToSpeech 類的 speak() 方法逐個播放分割后的文本片段。可以在每個片段的朗讀完成后,通過監聽器的回調方法(如 onDone())來觸發播放下一個片段。
    • 控制播放速度和延遲:根據需要,可以使用 setSpeechRate() 方法設置播放速度,以控制 TTS 引擎的語音合成速度。此外,可以使用 setOnUtteranceProgressListener() 方法設置延遲,以確保在播放下一個片段之前有適當的間隔。
    • 處理播放順序和邏輯:根據需要,可以使用隊列或其他數據結構來管理要播放的文本片段的順序。可以在每個片段的朗讀完成后,根據播放邏輯決定下一個要播放的片段。
    • 監聽播放狀態和錯誤:使用 setOnUtteranceProgressListener() 方法設置監聽器,以便在播放過程中獲取播放狀態和錯誤信息。通過監聽器的回調方法,可以處理播放開始、完成和錯誤等
    • 異步處理:為了避免阻塞主線程,建議在后臺線程中執行 TTS 操作??梢允褂?AsyncTask 或其他異步機制來執行 TTS 操作,以確保長內容的播放不會影響應用程序的響應性能。
  • 處理播放長內容,是比較復雜的邏輯。
    • 將長內容分割為適當的片段,并使用 TTS 的播放方法逐個播放這些片段。同時,注意處理播放順序、播放狀態和錯誤,以及中斷和暫停播放的情況。

5.8 使用TTS注意要點

  • 對于 TTS 使用有幾點使用上的建議
    • TTS Request App 的 Activity 或 Service 生命周期銷毀的時候,比如 onDestroy() 等時候,需要調用 TextToSpeech 的 shutdown() 釋放連接、資源。
    • 可以通過 addSpeech() 指定固定文本的對應 audio 資源 (比如說語音里常用的幾套喚醒后的歡迎詞 audio),在后續的文本請求時直接播放該 audio,免去文本轉語音的過程、提高效率。

06.TTS功能庫穩定性

6.1 邊界異常設計

  • 比如:播放A,B,C三條TTS語音。B播放異常,C還會播放嗎?
    • B播放異常,C會播放的。B播放異常,在播放器異常監聽的回調中,處理邏輯是先停止播放,然后將異常信息暴露給開發者,最后回調播放完成的api。

6.2 播放異常設計

  • 不管是那種tts可能遇到播放異常,異常該如何處理呢
    • 將異常錯誤通過監聽接口,回調給外部開發者。方便排查問題。
    • 播放異常后,先調用stop停止player播放,最后再回調播放完成方法。
  • 子線程exo播放異常
    java.lang.IllegalStateException: Player is accessed on the wrong thread. See https://exoplayer.dev/issues/player-accessed-on-wrong-thread
    
    • 不管是哪一種tts播放方式,在調用play方法播放時,會判斷是否是主線程。代碼如下所示
    @Override
    public void playTts(final String tts) {
        if (null != this.mDelegate) {
            if (DelegateTaskExecutor.getInstance().isMainThread()) {
                this.mDelegate.playTts(tts);
            } else {
                DelegateTaskExecutor.getInstance().postToMainThread(() -> mDelegate.playTts(tts));
            }
        }
    }
    

6.3 其他穩定性設計

  • 先說一下兼容性問題
    • 如果出現speak failed: not bound to TTS engine并且是Android 11。則需要做兼容處理。
  • 兼容性考慮設計
    • 為兼容Android11系統手機,我們需要在應用程序AndroidManifest.xml文件中增加如下聲明:
    <queries>
        <intent>
            <action android:name="android.intent.action.TTS_SERVICE"/>
        </intent>
    </queries>
    

07.系統TTS機制原理

7.1 TextToSpeech機制

  • 語言 TextToSpeech 機制簡單介紹
    • 任意 App 都可以方便地采用系統內置或第三方提供的 TTS Engine 進行播放鈴聲提示、語音提示的請求,Engine 可以由系統選擇默認的 provider 來執行操作,也可由 App 具體指定偏好的目標 Engine 來完成。
    • 默認 TTS Engine 可以在設備設置的路徑中找到,亦可由用戶手動更改: Settings -> Accessibility -> Text-to-speech output -> preferred engine

7.2 Speech整體設計

  • 系統默認的 TTS(Text-to-Speech)引擎是 Google TTS 引擎,它基于語音合成技術實現文本到語音的轉換。
    • 文本處理:當應用程序調用 TTS 引擎時,首先將待合成的文本傳遞給引擎。引擎會對文本進行處理,包括分詞、標點符號處理、語法分析等,以便更好地理解和合成語音。
    • 文本轉音素:引擎將處理后的文本轉換為音素序列。音素是語音的最小單位,每個音素對應一個特定的發音。通過將文本轉換為音素序列,引擎可以更準確地控制語音的合成過程。
    • 合成語音:基于音素序列,引擎使用聲學模型和語音合成算法來生成語音波形。語音合成算法根據音素序列和聲學模型,生成合成語音的波形。
    • 參數調整:在語音合成過程中,引擎可以根據設置的參數進行調整。例如,可以調整語速、音調、音量等參數,以滿足不同的應用需求和用戶偏好。
    • 播放語音:生成的語音波形可以通過音頻輸出設備進行播放。引擎將合成的語音波形傳遞給音頻系統,然后由音頻系統輸出到揚聲器或耳機,供用戶聽取。
  • Speech整體設計流程圖
    • [圖片上傳失敗...(image-807163-1711365940836)]

7.3 TTS初始化流程

  • 初始化TTS其實就是創建TextToSpeech對象。傳遞初始化完成的監聽器,可以初始化 TTS 引擎。一旦引擎初始化完成,就可以開始使用 TTS 功能。
    • [圖片上傳失敗...(image-435a79-1711365940836)]
  • 然后看一下源碼流程
    • 第一步:TextToSpeech#TextToSpeech(),看一下構造方法,最后可以看到,創建了TtsEngines引擎對象然后調用initTts初始化引擎操作。
    • 第二步:TextToSpeech#initTts()初始化引擎工作
    • 第三步:TextToSpeech#connectToEngine()連接引擎后建立連接服務
    • 第四步:在connectToEngine方法中,回調最終連接結果
  • TextToSpeech#initTts(),其核心邏輯是查找需要連接到哪個 Engine 。
    • 如果構造 TTS 接口的實例時指定了目標 Engine 的 package,那么首選連接到該 Engine。
    • 否則,獲取設備設置的 default默認 Engine 并連接,設置來自于 TtsEngines 從系統設置數據 SettingsProvider 中讀取 TTS_DEFAULT_SYNTH 而來。
    • 如果 default 不存在或者沒有安裝的話,從 TtsEngines 獲取第一位的系統 Engine 并連接。第一位指的是從所有 TTS Service 實現 Engine 列表里獲得第一個屬于 system image 的 Engine
  • TextToSpeech#connectToEngine(),其核心邏輯就是通過bindService連接服務
    • 封裝 Action 為 INTENT_ACTION_TTS_SERVICE 的 Intent 進行 bindService(),后續由 AMS 執行和 Engine 的綁定。
    • 這里的Engine可能是RequestedEngine,或者是系統的defaultEngine。
    • 無論是哪種方式,在 connected 之后都需要將具體的 TTS Engine 的 ITextToSpeechService 接口實例暫存,同時將 Connection 實例暫存到 mServiceConnection,給外部類接收到 speak() 的時候使用。
    • 在TextToSpeech#onServiceConnected()中,還會啟動一個異步任務 SetupConnectionAsyncTask 將自己作為 Binder 接口 ITextToSpeechCallback 返回給 Engine 以處理完之后回調結果給 Request

7.4 speak播報流程原理

  • 將 speak() 對應的調用遠程接口的操作封裝為 Action 接口實例,并交給 init() 時暫存的已連接的 Connection 實例去調度。
    • [圖片上傳失敗...(image-1f2fa9-1711365940836)]
  • 然后看一下源碼流程
    • 第一步:TextToSpeech#speak(),看一下該方法,主要是傳遞一些參數到tts引擎中
    • 第二步:TextToSpeech#runAction(),繼續追蹤到TextToSpeech#ServiceConnection.runAction(),看最后的action.run(mService)
    • 第三步:最后回到TextToSpeech#speak(),從 mUtterances Map 里查找目標文本是否有設置過本地的 audio 資源,否則通過調用service.speak方法

7.5 TextToSpeechService

  • 跟著上面繼續分析,最終的實現細節在service中。這個具體看:TextToSpeechService
    • 有設置的話,調用 TTS Engine 的 playAudio() 直接播放;否則調用 text 轉 audio 的接口 speak()
    • [圖片上傳失敗...(image-4a751c-1711365940836)]
    public int speak(final CharSequence text,
                     final int queueMode,
                     final Bundle params,
                     final String utteranceId) {
        return runAction((ITextToSpeechService service) -> {
            Uri utteranceUri = mUtterances.get(text);
            if (utteranceUri != null) {
                return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
                        getParams(params), utteranceId);
            } else {
                return service.speak(getCallerIdentity(), text, queueMode, getParams(params),
                        utteranceId);
            }
        }, ERROR, "speak");
    }
    
  • TextToSpeechService 實現,它是一個Service服務。
    • 在onCreate方法中,創建SynthThread作為一個獨立線程,創建SynthHandler發送封裝的speak或者audio的消息。
    • 然后看一下,TextToSpeechService#ITextToSpeechService.speak()方法和TextToSpeechService#ITextToSpeechService.playAudio()方法。
    • speak 請求封裝給 Handler 的是 SynthesisSpeechItem;playAudio 請求封裝的是 AudioSpeechItem;它們通過SynthHandler發送并處理消息。
    • SynthHandler 拿到 SpeechItem 后根據 queueMode 的值決定是 stop() 還是繼續播放。播放的話,是封裝進一步 play 的操作 Message 給 Handler。
  • 思考一下,tts如何將文本轉化為audio文件播放,在TextToSpeechService是否能找到實現步驟?
    • TextToSpeechService.SynthHandler#enqueueSpeechItem(),根據 queueMode 的值決定是 stop() 還是繼續播放。播放的話,是封裝進一步 play 的操作 Message 給 Handler。
    • play() 具體是調用 playImpl() 繼續。這塊可以直接看AudioSpeechItem負責音頻播放,SynthesisSpeechItem負責文本播放,這兩個類都是繼承SpeechItem抽象類。
    • 對于 SynthesisSpeechItem 來說,將初始化時創建的 SynthesisRequest 實例和 SynthesisCallback 實例 (此處的實現是 PlaybackSynthesisCallback) 收集和調用 onSynthesizeText() 進一步處理,用于請求和回調結果。
    • onSynthesizeText() 是 abstract 方法,需要 Engine 復寫以將 text 合成 audio 數據,也是 TTS 功能里最核心的實現。

該庫地址:https://github.com/yangchong211/YCToolLib

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容