
# 语音合成(http版)
#### 前提条件
- 确保已经按照配置好Android开发环境。
- 请参考[SDK（websocket）](https://support.huaweicloud.com/sdkreference-sis/sis_05_0004.html)获取最新版本SDK包。
 
#### 初始化Client
初始化SisClient,详细信息如下。
1. 配置客户端连接参数。
   - 默认配置
     ```
     // 使用默认配置
     HttpConfig config = HttpConfig.getDefaultHttpConfig();
     ```
     
   
   - 网络代理（可选）
     ```
     // 根据需要配置网络代理，网络代理默认的协议为 `http` 协议
     config.withProxyHost("proxy.huaweicloud.com")
         .withProxyPort(8080)
         .withProxyUsername("test")
         .withProxyPassword("test");
     ```
     
   
   - 超时配置（可选）
     ```
     // 默认连接超时时间为60秒，可根据需要调整
     config.withTimeout(60);
     ```
     
   
   - SSL配置（可选）
     ```
     // 根据需要配置是否跳过SSL证书验证
     config.withIgnoreSSLVerification(true);
     ```
     
    
2. 配置认证信息。 配置AK、SK、project_id信息。华为云通过AK识别用户的身份，通过SK对请求数据进行签名验证，用于确保请求的机密性、完整性和请求者身份的正确性。
   - 使用永久AK和SK
     ```
     BasicCredentials basicCredentials = new BasicCredentials()
         .withAk(ak)
         .withSk(sk)
         .withProjectId(projectId);
     ```
     
   
   - 使用临时AK和SK
     ```
     BasicCredentials basicCredentials = new BasicCredentials()
         .withAk(ak)
         .withSk(sk)
         .withSecurityToken(securityToken)
         .withProjectId(projectId)
     ```
     
   
   
   认证参数说明：
   - ak、sk：访问密钥信息，获取方法请参考[AK/SK认证](https://support.huaweicloud.com/sdkreference-sis/sis_05_0003.html)。
   
   - projectId：华为云项目ID，获取方法请参考[获取项目ID](https://support.huaweicloud.com/api-sis/sis_03_0008.html)。
   
   - securityToken：采用临时AK、SK 认证场景下的安全票据，可以[通过token获取](https://support.huaweicloud.com/api-iam/iam_04_0002.html)或者[通过委托授权获取](https://support.huaweicloud.com/api-iam/iam_04_0101.html)。
    
3. 初始化客户端（region和指定云服务endpoint二选一即可）。
   - 指定region方式（强烈推荐）
     ```
     // 初始化客户端认证信息，使用当前客户端初始化方式可不填 projectId/domainId，以初始化 BasicCredentials 为例
     BasicCredentials basicCredentials = new BasicCredentials()
         .withAk(ak)
         .withSk(sk);
     // 初始化指定云服务的客户端 {Service}Client ，以初始化 Region 级服务SIS的 SisClient 为例
     SisClient client = SisClient.newBuilder()
         .withHttpConfig(config)
         .withCredential(basicCredentials)
         .withRegion(SisRegion.valueOf("cn-north-4"))
         .build();
     ```
     
   
   - 指定云服务endpoint方式（可选）
     ```
     // 指定终端节点，以SIS服务北京四的 endpoint 为例
     String endpoint = "https://sis-ext.cn-north-4.myhuaweicloud.com";
     // 初始化客户端认证信息，需要填写相应 projectId/domainId，以初始化 BasicCredentials 为例
     BasicCredentials basicCredentials = new BasicCredentials()
         .withAk(ak)
         .withSk(sk)
         .withProjectId(projectId);
     // 初始化指定云服务的客户端 {Service}Client ，以初始化 Region 级服务SIS的 CbsClient 为例
     SisClient client = SisClient.newBuilder()
         .withHttpConfig(config)
         .withCredential(basicCredentials)
         .withEndpoint(endpoint)
         .build();
     ```
     endpoint是华为云各服务应用区域和各服务的终端节点，详情请查看 [地区和终端节点](https://developer.huaweicloud.com/endpoint?SIS)。
     
    
 
#### 请求参数
1. 请求类为RunTtsRequest,其中包含参数类PostCustomTTSReq，该类包含两个参数text(待合成文本)和TtsConfig，详见TtsConfig。
   表1TtsConfig 
   | 参数名称         | 是否必选 | 参数类型    | 描述                                                                                                                                             |
   |:---|:---|:---|:---|
   | audio_format | 否    | String  | 待合成的音频格式，可选mp3，wav等，默认wav。具体信息请参见《API参考》中[语音合成](https://support.huaweicloud.com/api-sis/sis_03_0111.html)章节。                                   |
   | pitch        | 否    | Integer | 音高，\[-500,500\] ，默认是0。                                                                                                                         |
   | speed        | 否    | Integer | 语速，\[-500,500\] ，默认是0。                                                                                                                         |
   | volume       | 否    | Integer | 音量，\[0,100\]，默认是50。                                                                                                                            |
   | sample_rate  | 否    | String  | 采样率，支持"8000"、"16000"，默认"8000"。                                                                                                                 |
   | property     | 否    | String  | 特征字符串，{language}_{speaker}_{domain}，默认chinese_xiaoqi_common。具体信息请参见《API参考》中[语音合成](https://support.huaweicloud.com/api-sis/sis_03_0111.html)章节。 |
      
   
2. 伪代码
   ```
   TtsConfig configbody = new TtsConfig();
   configbody.setAudioFormat(TtsConfig.AudioFormatEnum.fromValue("wav"));
   configbody.setSampleRate(TtsConfig.SampleRateEnum.fromValue("8000"));
   configbody.setProperty(TtsConfig.PropertyEnum.fromValue("chinese_huaxiaomei_common"));
   RunTtsRequest request = new RunTtsRequest();
   PostCustomTTSReq body = new PostCustomTTSReq();
   body.withConfig(configbody);
   if (!StringUtils.isEmpty(text.getText().toString())) {
   body.withText(text.getText().toString());
   } else {
   body.withText("请输入合成文本");
   }
   request.withBody(body);
   return request;
   ```
   
 
#### 响应参数
响应类为RunTtsResponse，详见下表。调用失败处理方法请参见[错误码](https://support.huaweicloud.com/api-sis/sis_03_0045.html)。
表2RunTtsResponse 
| 参数名      | 是否必选 | 参数类型   | 说明                                                                                                                                           |
|:---|:---|:---|:---|
| trace_id | 是    | String | 服务内部的令牌，可用于在日志中追溯具体流程，调用失败无此字段。 在某些错误情况下可能没有此令牌字符串。                                           |
| result   | 是    | Object | 调用成功时为合成语音内容，请参考[表3]。 调用失败时无此字段。 |
   
 表3Result 
| 参数名  | 是否必选 | 参数类型   | 说明                                                                |
|:---|:---|:---|:---|
| data | 是    | String | 合成后生成的语音数据，以Base64编码格式返回。用户如需生成音频，需要将Base64编码解码成byte数组，再保存为wav音频。 |
   
#### 示例代码
如下示例仅供参考，最新代码请前往[SDK（websocket）](https://support.huaweicloud.com/sdkreference-sis/sis_05_0004.html)章节获取并运行。
```
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */
package com.huaweicloud.sis.android.demo.tts;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.huaweicloud.sdk.core.auth.BasicCredentials;
import com.huaweicloud.sdk.core.exception.ConnectionException;
import com.huaweicloud.sdk.core.exception.RequestTimeoutException;
import com.huaweicloud.sdk.core.exception.ServiceResponseException;
import com.huaweicloud.sdk.core.http.HttpConfig;
import com.huaweicloud.sdk.core.utils.StringUtils;
import com.huaweicloud.sdk.sis.v1.SisClient;
import com.huaweicloud.sdk.sis.v1.model.PostCustomTTSReq;
import com.huaweicloud.sdk.sis.v1.model.RunTtsRequest;
import com.huaweicloud.sdk.sis.v1.model.RunTtsResponse;
import com.huaweicloud.sdk.sis.v1.model.TtsConfig;
import com.huaweicloud.sdk.sis.v1.region.SisRegion;
import com.huaweicloud.sis.android.demo.R;
import com.huaweicloud.sis.android.demo.service.MediaPlayerService;
import com.huaweicloud.sis.android.demo.Config;
/**
 * 功能描述
 * 语音合成 http
 *
 * @since 2022-07-18
 */
public class SttsActivity extends AppCompatActivity {
private EditText text;
private TextView outResult;
private Button startSoundRecording;
private Button startPlay;
private Handler handler;
// 保存合成的base64字符串
private String base64Data;
// 合成音频路径
private String createFilePath;
// 客户端请求
private SisClient client;
private MediaPlayerService mediaPlayerService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.stts);
}
@Override
protected void onStart() {
super.onStart();
initView();
initResoureces();
}
@Override
protected void onDestroy() {
mediaPlayerService.stopMyPlayer(createFilePath);
super.onDestroy();
}
// 初始化界面
private void initView() {
text = findViewById(R.id.input_text);
outResult = findViewById(R.id.out_result);
startSoundRecording = findViewById(R.id.start);
startPlay = findViewById(R.id.startplay);
startSoundRecording.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Runnable runnable = new Runnable() {
@Override
public void run() {
String sttsRequestText = getSttsResponse();
Message message = new Message();
Bundle mBundle = new Bundle();
mBundle.putString("result", sttsRequestText);
message.setData(mBundle);
handler.sendMessage(message);
}
};
new Thread(runnable).start();
}
});
startPlay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!StringUtils.isEmpty(base64Data)) {
createFilePath = mediaPlayerService.createAudioFile(getBaseContext(), "wav", base64Data);
mediaPlayerService.startPlay(createFilePath);
}
}
});
}
/**
 * 初始化资源
 *
 * @return
 */
private void initResoureces() {
BasicCredentials auth = new BasicCredentials()
                        .withAk(this.getString(R.string.HUAWEICLOUD_SDK_AK))
                        .withSk(this.getString(R.string.HUAWEICLOUD_SDK_SK))
.withProjectId(Config.PROJECT_ID);
HttpConfig config = HttpConfig.getDefaultHttpConfig();
config.withIgnoreSSLVerification(true);
client = SisClient.newBuilder()
.withHttpConfig(config)
.withCredential(auth)
.withRegion(SisRegion.valueOf(Config.REGION))
.build();
handler = new Handler(getMainLooper()) {
@Override
public void handleMessage(@NonNull Message message) {
super.handleMessage(message);
switch (message.what) {
case 0:
Bundle bundle = message.getData();
String rstr = bundle.getString("result");
outResult.setText(rstr);
break;
default:
Log.e("Unexpected value: ", String.valueOf(message.what));
}
}
};
mediaPlayerService = new MediaPlayerService();
}
// 设置请求体
private RunTtsRequest getRunTtsRequest() {
TtsConfig configbody = new TtsConfig();
configbody.setAudioFormat(TtsConfig.AudioFormatEnum.fromValue("wav"));
configbody.setSampleRate(TtsConfig.SampleRateEnum.fromValue("8000"));
configbody.setProperty(TtsConfig.PropertyEnum.fromValue("chinese_huaxiaomei_common"));
RunTtsRequest request = new RunTtsRequest();
PostCustomTTSReq body = new PostCustomTTSReq();
body.withConfig(configbody);
if (!StringUtils.isEmpty(text.getText().toString())) {
body.withText(text.getText().toString());
} else {
body.withText("请输入合成文本");
}
request.withBody(body);
return request;
}
// 发送请求
private String getSttsResponse() {
RunTtsRequest request = getRunTtsRequest();
String ttsString = "";
try {
RunTtsResponse response = client.runTts(request);
if (response.getResult().getData() != null) {
base64Data = response.getResult().getData();
ttsString = "合成成功";
} else {
ttsString = "合成失败";
}
Log.i("info", ttsString);
} catch (ConnectionException | RequestTimeoutException | ServiceResponseException e) {
Log.e("error", e.toString());
} catch (Exception e) {
Log.e("error", e.toString());
}
return ttsString;
}
@Override
protected void onPause() {
mediaPlayerService.stopMyPlayer(createFilePath);
super.onPause();
}
}
```
#### Jar包冲突报错
- **Jar包引入不规范**：通过 libs 目录手动引入的 Jar 文件被 Gradle 视为原子性的二进制文件。Gradle 无法读取本地 Jar 包内部的元数据（如 POM 文件），因此无法自动识别或排除其内部包含的第三方库（Transitive Dependencies），导致 exclude 语法对本地文件失效。
- **第三方库隐式捆绑**：SIS SDK 的发布包中包含了通用的第三方基础库（如 OkHttp、Jackson、Log4j 等）。若宿主项目本身已通过 Maven/Gradle 引入了这些库的其他版本，构建时会检测到全限定名相同的类文件，从而引发 Duplicate class 编译错误。
- **版本一致性冲突**：SDK 依赖的华为云核心组件（huaweicloud-sdk-core）版本可能与项目中引入的其他华为云服务 SDK（如 OBS、VOD）所依赖的核心组件版本不一致。多版本共存可能导致运行时出现 NoSuchMethodError 或 ClassNotFoundException。
- **构建缓存残留**：Android Studio 或 Gradle 的构建缓存（Build Cache）可能保留了旧的依赖解析树，导致配置文件修改后，构建系统并未完全重新加载新的依赖关系。
