题记
本文将根据一种具体业务场景:语音播报(将一篇ai撰写的文章异步转换成语音文件进行播报)为案例演示华为云语音交互SIS的集成使用。
一 、语音交互服务(Speech Interaction Service,简称SIS)
语音交互服务(Speech Interaction Service,简称SIS)是一种人机交互方式,用户通过实时访问和调用API(Application Programming Interface,应用程序编程接口)将语音识别成文字或者将文本转换成逼真的语音等。
常用的应用场景参看官网:应用场景
二、功能介绍
Tip:根据你的需求场景,是否实时、大小、时长、是语音转文字,还是文字转语音等等评估应该使用下边哪种功能。
1、实时语音识别
实时语音识别服务
,用户通过实时访问和调用API获取实时语音识别结果,支持的语言包含中文普通话、方言和英语,方言当前支持四川话、粤语和上海话。
-
文本时间戳
为音频转换结果生成特定的时间戳,从而通过搜索文本即可快速找到对应的原始音频。 -
智能断句
通过提取上下文相关语义特征,并结合语音特征,智能划分断句及添加标点符号,提升输出文本的可阅读性。 -
中英文混合识别
支持在中文句子识别中夹带英文字母、数字等,从而实现中、英文以及数字的混合识别。 -
即时输出识别结果
连续识别语音流内容,即时输出结果,并可根据上下文语言模型自动校正。 -
自动静音检测
对输入语音流进行静音检测,识别效率和准确率更高。
2、一句话识别
可以实现1分钟以内音频到文字的转换
。对于用户上传的二进制音频格式数据,系统经过处理,生成语音对应的文字,支持的语言包含中文普通话、方言以及英语。方言当前支持四川话、粤语和上海话。
3、录音文件识别
对于录制的长语音进行识别,转写成文字
,提供不同领域模型,具备良好的可扩展性,支持热词定制。
4、语音合成
文本转成语音
,语音合成支持多种音色,可调节语调,语速,音量。
这里我将使用【4、语音合成】功能实现开篇提到的文章转语音播报的目的。
三、约束与限制
明确了要使用的功能,接下来看有哪些约束限制,是否与需求契合。使用【语音合成】功能的注意点:
- 支持
“华北-北京四”、“华东-上海一”
区域。 - 支持中文、英文、中英文,文本
不长于500个字符
。 - 支持合成采样率8kHz、16kHz。
Tip:由上可知,如果文本大于500字符就需要切割再合并问题。
以上了解了需求场景能不能使用,接下来就看怎么用啦~
四、使用
主要有两种接入方式:API
或SDK
。
1、API
SIS服务提供了两种接口,包含REST(Representational State Transfer)API,支持您通过HTTPS请求
调用。也包含WebSocket接口,支持Websocket协议
。参看:API文档
本文使用SDK方式接入,API方式不过多赘述,可参考文档使用。
2、SDK
最新的sdk目前是3.1.128版本。
注意该SDK暂不支持websocket方法。
如果需要使用实时语音识别,可考虑使用替代SDK,当前支持Java SDK、Python SDK、CPP SDK、iOS SDK、Android SDK。
这里我不需要实时的,可以直接使用上边的最新sdk的方式。
五、项目集成
由于我的项目本身有华为云其他产品,为了兼容使用了3.1.116版本,以及排除了一些依赖。
1、引入pom依赖
<dependency>
<groupId>com.huaweicloud.sdk</groupId>
<artifactId>huaweicloud-sdk-sis</artifactId>
<version>3.1.116</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
2、初始化 Client
注意:官方文档上显示的客户端client可能是未更新的或者和你本地引入的依赖里的客户端不匹配,根据实际情况使用你依赖里的客户端去处理就好,以及封装的请求对象。
【我这里依赖里的客户端是:SisClient,请求类:RunTtsRequest】
1)准备参数
首先需要一些认证信息、配置信息,可参考官网获取方式:
请求参数:
目前SDK仅支持AK/SK认证方式。
2)nacos配置
我们将上边的信息以及可以调整的参数统一提取出来配置化,避免硬编码,这里我统一放到nacos中配置。
nacos配置文件内容:
#支持多租户分桶的文件服务配置,目前支持阿里云oss、亚马逊s3、华为云obs、NAS网络存储、微软云blob。
common:
clients:
#文件权限范围; default:平台, 租户code eg:100001
- bucketOwner: default
#桶类型; public:公有, private:私有; 其他自定义只作为备用桶, 需以_public或_private结尾
bucketType: public
#存储云类型;
cloudType: huaweiyun
#桶名称
bucketName: obs-group-test-xxxxx
#oss提供的内网访问域名
endpoint: https://obs.cn-north-4.myhuaweicloud.com
accessKeyId: YL6BxxxxxxxxxxxxxxxKL
accessKeySecret: w0pTVxxxxxxxxxxxxxx1hXnH
projectId: 0744xxxxxxxxxxxxxd9a
region: cn-north-4
default:
#默认的私有桶url有效时间,单位:秒。
expiration: 3600
#租户备用桶设置(只支持读取)
buckets:
#租户code
- tenantCode: test
#{bucketOwner}_{bucketType},根据bucketOwner和bucketType映射到上面配置的桶
spareBucket: test_public
#华为云语音合成音色设置
sis-client:
#语音格式头:wav、mp3、pcm 默认:wav
audioFormat: wav
#采样率:16000、8000赫兹 默认:8000
sampleRate: 8000
#语音合成特征字符串
property: chinese_huaxiaodong_common
#语速
speed: 0
#音高
pitch: 43
#音量默认50
volume: 44
3)配置类-CommonClientsProperties.java
CommonClientsProperties.java
@ConfigurationProperties(prefix = "common")
public class CommonClientsProperties {
private List<Properties> clients = new ArrayList<>();
public List<Properties> getClients() {
return clients;
}
public void setClients(List<Properties> clients) {
this.clients = clients;
}
@Data
public static class Properties {
private String bucketOwner;
private String bucketType;
private String cloudType;
private String bucketName;
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String region;
private Integer expiration;
private String baseDir;
private String connectStr;
private String projectId;
}
}
4)初始化客户端配置-CommonClientsCache.java
这里可以做的通用一些,将每个平台自家的产品的客户端都单独封装在一起,比如华为云的obs、语音、视频等封装成华为云的客户端;阿里的oss、语音等等封装成阿里的客户端;统一给外层调用。
另外accessKey可能涉及到加解密等注意处理即可。
这里我们将生成的语音文件上传到华为云obs,所以一并将obs客户端、http的也初始化了。
/**
* 文件客户端初始化
*/
@Slf4j
public class CommonClientsCache {
@Resource
CommonClientsProperties commonClientsProperties;
private final Map<String, CommonClientBean> cache = new HashMap<>();
@PostConstruct
public void init() {
List<CommonClientsProperties.Properties> clientParams = commonClientsProperties.getClients();
clientParams.forEach(properties -> {
String key = String.format("%s_%s", properties.getBucketOwner(), properties.getBucketType());
cache.put(key, buildCommonClientBean(properties));
});
}
private CommonClientBean buildCommonClientBean(CommonClientsProperties.Properties properties) {
String endpoint = properties.getEndpoint();
String accessKeySecret = decode(properties.getAccessKeySecret());
String bucketName = properties.getBucketName();
CloudTypeEnum cloudType = CloudTypeEnum.valueOfType(properties.getCloudType());
if (StringUtils.isBlank(bucketName) && StringUtils.isBlank(properties.getConnectStr())) {
log.info("file client configuration missing");
return null;
}
try {
log.info("file client init start, endpoint:{},bucketName:{}", endpoint, bucketName);
switch (Objects.requireNonNull(cloudType)) {
case HUAWEIYUN:
return getHuaWeiClientBean(properties, accessKeySecret);
default:
throw new FileBizException("cloud type is error");
}
} catch (Exception e) {
log.error("file client init failed", e);
return null;
}
}
private String decode(String accessKey) {
// 使用加密AK秘钥
try {
if (StringUtils.isNotEmpty(accessKey) && accessKey.contains(CoreConstants.ZAEC)) {
accessKey = Zaenc.decryptData(accessKey);
}
} catch (Exception e) {
log.error(" access key decrypt fail", e);
}
return accessKey;
}
private CommonClientBean getHuaWeiClientBean(CommonClientsProperties.Properties properties, String accessKeySecret) {
ObsClient obsClient = new ObsClient(properties.getAccessKeyId(), accessKeySecret, properties.getEndpoint());
HttpConfig httpConfig = HttpConfig.getDefaultHttpConfig().withIgnoreSSLVerification(true).withTimeout(10);
ICredential auth = new BasicCredentials()
.withAk(properties.getAccessKeyId())
.withSk(accessKeySecret)
.withProjectId(properties.getProjectId());
SisClient sisClient = SisClient.newBuilder().withCredential(auth)
.withHttpConfig(httpConfig)
.withRegion(SisRegion.valueOf(properties.getRegion()))
.build();
OkHttpClient okHttpClient = new