持续创作,加速成长!这是我参与「日新计划 6 月更文挑战」的第2天,点击查看活动详情。
前言
大家好,上次画了一个会说话的粽子使用了讯飞语音技术,那么今天这篇文章就带你解析如何在Flutter
跨平台中让粽子开口说话,无需集成任何SDK,从而非常轻量级的实现给你的应用装上嘴巴的功能。
创建应用
首先我们需要到讯飞注册账号登录点击控制台创建一个应用,填写ios应用商店应用所approve属信息即可,非常简单。
以我这里创建了一个Flutter_Demo应用为例,点击语音合成之后,我们会得到右边一共三个参数和API调用接口,这些参数在以后生成鉴权参数以及合成语音的时候会用到。
到这里,应用就创建完成并且得到了专属的参数,是不是很简单。
创建webSocket连接
创建完应用之后,我们查看文档,因为demo是没有dart
语言版本的,所以我们需要跟着文档步https和http的区别骤一步一步实现,我们可以看到主要是接口加密鉴权签名这里是比较麻烦的,其他符合要求即可。
鉴权规则:查看文档可以看到,我们需要在API接口后面拼接这仨参数http协议,前两个都好说,第三个就是通过hmac-sha256
规则加密在进行base64
编码一系列骚操作得出,反正就是为了接口的安全,有兴趣的小伙伴可以到官网了解。
使用dart
语言,那么这里加密鉴权我们实体类型需要引入以下两个插件辅助:
# 格式时间戳
intl: ^0.17.0
# 加密
crypto: ^3.0.2
鉴权核心代码:
///获取鉴权地址
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: $hostndate: $datenGET /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<int> messageBytes = utf8.encode(message);
List<int> secretBytes = utf8.encode(secret);
Hmac hmac = Hmac(sha256, secretBytes);
Digest digest = hmac.convert(messageBytes);
return base64.encode(digest.bytes);
}
有了创建连接的url
,接下来创建WebSocket
连接application,需要引入以下插appearance件:
# webSocket 连接
web_socket_channel: ^2.2.0
核心代码:
/// 讯飞Socket
class XfSocket {
IOWebSocketChannel? _channel;
ValueChanged<String>? 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);
}
}
发送文本数据
发送消息时,我们可以设置发音人、音频格式、音调、音量、数字读法等等,这些属github开放私库性一http代理定不能有错,有application1个错就可能出现莫名其妙的错误,具体说明官方文档说的很清楚,我这边封装了实体类,直接设置即可,发音人可以通过讯飞控制人进行添加。PS:github中文社区好听的声音都是收费的。价格不appointment一,自行体验。☺ 示例代码:
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
方法监听回调收到ios15以下信息,其中data中的audio就是我们需要声音数据,通过base64
进行解码就可以得到我们需要的声音字节码,然后将字节码写入文appstore件即可。
比如我这里appear以mp3为例,MP3的音频编码为lame,只需设置aue属性即可,同时要注意文件github汤姆名后缀,其他格式有兴趣的小伙伴参照官网直接修改参数即可。
保存音频
保存音频就很简单了,通过以下插件将音频保存至存储卡内,这样我们下次播放就无须再次转换了。
# 文件路径获取
path_provider: ^2.0.1
核心代码:
/// 获取文件路径
Future<String> _getLocalFileDir() async {
Directory? tempDir = await getExternalStorageDirectory();
return tempDir!.path;
}
/// 获取文件
Future<File> _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。
播放音频插件:目http://www.baidu.com前只支持Androgithub中文社区id, 以后可以换个同时支持iOS的。
# 音频播放器
audioplayers: ^0.20.1
// text 转换文本
XfSocket.connect(text, onFilePath: (path) {
_playAudio(path);
});
void _playAudio(String filePath) async {
var i = await audioPlayer.play(filePath, isLocal: true);
debugPrint("lxp $i");
}
需HTTP要demo小伙伴的可以留言。稍后我会上传到github。
总结
需要注意的点,发送数据参数一定不能错,要认真对照官网,一般出问题大部分都是这里造成的,总体体验来说,讯飞的发音github是什么机器人已经非常接近人声了,甚至有的发音人阅读文章根本分辨不出来是是机器人github永久回家地址还是真人,但是是收费的~。,当然每天都有免费调用接口的额度,比如我们有时候看一本书http 302,不想看,想听,但是网上又没有人声版appstore本,那么用这个工具进行生成音频还是不错的,有时间再研究下ios应用商店语音实体类的定义识别,看了下文档,大同小异,有时间再分享了,那本篇文章就到这http 500里了,希望对你有所帮助~