Flutter實現訊飛線上語音合成(WebSocket流式版)

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第2天,點選檢視活動詳情

前言

大家好,上次畫了一個會說話的粽子使用了訊飛語音技術,那麼今天這篇文章就帶你解析如何在Flutter跨平臺中讓粽子開口說話,無需整合任何SDK,從而非常輕量級的實現給你的應用裝上嘴巴的功能。

建立應用

首先我們需要到訊飛註冊賬號登入點選控制檯建立一個應用,填寫應用所屬資訊即可,非常簡單。

image.png

以我這裡建立了一個Flutter_Demo應用為例,點選語音合成之後,我們會得到右邊一共三個引數和API呼叫介面,這些引數在以後生成鑑權引數以及合成語音的時候會用到。

image.png

到這裡,應用就建立完成並且得到了專屬的引數,是不是很簡單。

建立webSocket連線

建立完應用之後,我們檢視文件,因為demo是沒有dart語言版本的,所以我們需要跟著文件步驟一步一步實現,我們可以看到主要是介面加密鑑權簽名這裡是比較麻煩的,其他符合要求即可。
image.png
鑑權規則:檢視文件可以看到,我們需要在API介面後面拼接這仨引數,前兩個都好說,第三個就是通過hmac-sha256規則加密在進行base64編碼一系列騷操作得出,反正就是為了介面的安全,有興趣的小夥伴可以到官網瞭解。 image.png

使用dart語言,那麼這裡加密鑑權我們需要引入以下兩個外掛輔助: ```yaml

格式時間戳

intl: ^0.17.0

加密

crypto: ^3.0.2 **鑑權核心程式碼:**dart ///獲取鑑權地址 String getAuthUrl(String host, String apiKey, String apiSecret) { final DateFormat df = DateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'"); String date = df.format(DateTime.now()); final String signatureOrigin = 'host: $host\ndate: $date\nGET /v2/tts HTTP/1.1'; final String signature = _hmacSha256Base64(signatureOrigin, apiSecret); final String authorizationOrigin = 'api_key="$apiKey", algorithm="hmac-sha256", headers="host date request-line", signature="$signature"'; final String authorization = base64.encode(authorizationOrigin.codeUnits); //對日期進行uri編碼 date = Uri.encodeComponent(date); final String url = 'wss://$host/v2/tts?authorization=$authorization&date=$date&host=$host'; print("鑑權 = $url"); return url; }

///hmacSha256加密後再進行base64編碼 String _hmacSha256Base64(String message, String secret) { List messageBytes = utf8.encode(message); List secretBytes = utf8.encode(secret); Hmac hmac = Hmac(sha256, secretBytes); Digest digest = hmac.convert(messageBytes); return base64.encode(digest.bytes); } 有了建立連線的`url`,接下來建立`WebSocket`連線,需要引入以下外掛:yaml

webSocket 連線

web_socket_channel: ^2.2.0 **核心程式碼:**dart /// 訊飛Socket class XfSocket { IOWebSocketChannel? _channel;

ValueChanged? onFilePath;

/// 建立連線 /// listen 轉化完成後的回撥 XfSocket.connect(String text, {this.onFilePath}) { String u = XfUtil.getAuthUrl(_host, _apiKey, _apiSecret); debugPrint('建立連線url = $u'); //關閉上一個連線 close(); _channel = IOWebSocketChannel.connect(u); //建立監聽 _channel?.stream.listen((message) { _onMessage(message); }, onError: (e) { debugPrint(" init error $e"); }); /// 傳送文字請求轉換語音 sendText(text); }

void _onMessage(dynamic data) { debugPrint("result = $data"); var xfRes = XfRes.fromJson(jsonDecode(data)); var decode = base64.decode(xfRes.data!.audio!); debugPrint("result222 = $decode");

_writeCounter(decode); } ///關閉連線 close([int status = status.goingAway]) { debugPrint('關閉連線'); _channel?.sink.close(status); } } ```

傳送文字資料

傳送訊息時,我們可以設定發音人、音訊格式、音調、音量、數字讀法等等,這些屬性一定不能有錯,有1個錯就可能出現莫名其妙的錯誤,具體說明官方文件說的很清楚,我這邊封裝了實體類,直接設定即可,發音人可以通過訊飛控制人進行新增。PS:好聽的聲音都是收費的。價格不一,自行體驗。☺ image.png 示例程式碼: ```dart sendText(String text) { XfTextReq xfTextReq = XfTextReq();

///設定appId Common common = Common(); common.appId = _appId; xfTextReq.common = common;

Business business = Business(); business.aue = "lame"; // 音訊編碼 mp3格式 business.tte = "UTF8"; business.pitch = 50; business.speed = 40; business.vcn = "x3_doudou";// 發音人 business.volume = 50; //音量 business.sfl = 0;// 是否開啟流式傳輸 =1會將一段文字音訊分成多段返回 business.auf = "audio/L16;rate=16000"; business.bgs = 0; business.reg = "0"; business.rdn = "0"; xfTextReq.business = business; DataReq dataReq = DataReq(); dataReq.status = 2; //固定 = 2 dataReq.text = base64.encode(utf8.encode(text)); xfTextReq.data = dataReq;

debugPrint("input == ${jsonEncode(xfTextReq)}");

/// 這裡一定要jsonEncode轉化成字串json formJson物件不可以 _channel?.sink.add(jsonEncode(xfTextReq)); } ```

這裡通過listen方法監聽回撥收到以下資訊,其中data中的audio就是我們需要聲音資料,通過base64進行解碼就可以得到我們需要的聲音位元組碼,然後將位元組碼寫入檔案即可。 image.png 比如我這裡以mp3為例,MP3的音訊編碼為lame,只需設定aue屬性即可,同時要注意檔名字尾,其他格式有興趣的小夥伴參照官網直接修改引數即可。

儲存音訊

儲存音訊就很簡單了,通過以下外掛將音訊儲存至儲存卡內,這樣我們下次播放就無須再次轉換了。 ```yaml

檔案路徑獲取

path_provider: ^2.0.1 **核心程式碼:**dart /// 獲取檔案路徑 Future _getLocalFileDir() async { Directory? tempDir = await getExternalStorageDirectory(); return tempDir!.path; }

/// 獲取檔案 Future _getLocalFile() async { String dir = await _getLocalFileDir(); return File("$dir/demo.mp3"); }

/// 寫入內容 void _writeCounter(Uint8List decode) async { File file = await _getLocalFile(); file.writeAsBytes(decode, mode: FileMode.write).then((value) { debugPrint("result寫入檔案 $value");
//儲存成功通知客戶端可以播放。 onFilePath?.call(value.path); }); } ```

具體用法

非常的easy。
播放音訊外掛:目前只支援Android, 以後可以換個同時支援iOS的。 ```yaml

音訊播放器

audioplayers: ^0.20.1 dart // text 轉換文字 XfSocket.connect(text, onFilePath: (path) { _playAudio(path); }); void _playAudio(String filePath) async { var i = await audioPlayer.play(filePath, isLocal: true); debugPrint("lxp $i"); } ``` 需要demo小夥伴的可以留言。稍後我會上傳到github。

總結

需要注意的點,傳送資料引數一定不能錯,要認真對照官網,一般出問題大部分都是這裡造成的,總體體驗來說,訊飛的發音機器人已經非常接近人聲了,甚至有的發音人閱讀文章根本分辨不出來是是機器人還是真人,但是是收費的~。,當然每天都有免費呼叫介面的額度,比如我們有時候看一本書,不想看,想聽,但是網上又沒有人聲版本,那麼用這個工具進行生成音訊還是不錯的,有時間再研究下語音識別,看了下文件,大同小異,有時間再分享了,那本篇文章就到這裡了,希望對你有所幫助~