API Path
/aipaas/voice/v1/audio/synthesis
请求协议
WS
建立连接时的请求参数(websocket open) :
请求头部 :
| 头部标签 | 必填 | 说明 | 类型 | 数据字典 | 限制 | 头部内容 | 示例 |
|---|---|---|---|---|---|---|---|
| Content-Type | 是 | application/json | [string] | application/json | application/json | ||
| X-APP-ID | 是 | 控制台-应用管理-创建应用-AppID | [string] | ||||
| Device-Uuid | 否 | 设备管理-设备uuid | [string] | ||||
| Authorization | 是 | 鉴权信息 | [string] |
请求参数 Json:
| 参数名 | 说明 | 必填 | 类型 | 数据字典 | 限制 | 示例 |
|---|---|---|---|---|---|---|
| req_id | 请求ID,记录该值便于排查问题 | 是 | [string] | |||
| speaker_id | 全局唯一音色代号 | 是 | [string] | |||
| text | 待合成的文本,需要为 UTF-8 编码 | 是 | [string] | |||
| format | 音频编码格式,支持 PCM 格式,默认值:PCM | 否 | [string] | |||
| sampling_rate | 音频采样率,默认值:22050 Hz | 否 | [int] |
返回结果
> 成功 (200)
> Json
> Object
| 参数名 | 说明 | 必填 | 类型 | 数据字典 | 限制 | 示例 |
|---|---|---|---|---|---|---|
| code | 状态码 | 是 | [string] | |||
| message | 状态说明 | 是 | [string] | |||
| sid | 会话ID,用于记录本次会话,仅建立连接时返回 | 否 | [string] | |||
| result | 合成结果,建立连接后返回 | 否 | [object] | |||
| result>>audio | 合成音频数据,经过 base64 编码 | 否 | [string] | |||
| result>>audio_len | 合成音频长度,单位:ms | 否 | [string] | |||
| result>>is_end | 标志位,true:最后一个合成片段,false:中间合成片段 | 否 | [string] |
流式声音复刻提供对用户音色、说话风格、口音和声学环境音的复刻的功能。支持输出格式:PCM 编码;支持设置采样率:8000 Hz,16000 Hz,22050 Hz;支持一次性合成 500 字符以内的文字,其中 1 个汉字、1 个英文字母、1 个标点或 1 个句子中间空格均算作 1 个字符,超过 500 个字符的内容将会截断;仅支持采用 UTF-8 编码的文本输入;调用该模型前需要先调用声音复刻-音色注册。
服务接口调用时需要严格遵循服务鉴权规则,服务调用鉴权规则请参见:开发指南 - 接口签名认证。
| 返回码 | 解释 | 说明 | 解决方法 |
|---|---|---|---|
| 10301 | Required parameter miss | 参数错误 | 请检查参数是否正确 |
| 10304 | Parse request body fail | 请求格式错误 | 查看请求的 URL body 格式是否正确,参考接口文档 |
| 10503 | Server connection time out | 服务连接超时 | 联系技术人员 |
| 10601 | Speaker feature illegal | 说话人特征不合法 | 联系技术人员 |
| 10903 | Synthesis failed | 合成失败 | 联系技术人员 |
| 10000 | Success | 成功 | 执行下一步操作 |
通用状态码请参考【状态码】中的【网关认证】
{
"req_id": "3a87fe9793c9-4ebd-95d4-4ce2-a80c054b",
"speaker_id": "e0639793-4119-8482-d0ab-01471575c327",
"text": "yourText"
}
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;
import org.json.JSONException;
import javax.xml.bind.DatatypeConverter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class WebSocketTTSClient {
private static final Logger logger = Logger.getLogger(WebSocketTTSClient.class.getName());
private String url;
private String xAppId;
private String authorization;
private long startTime;
private long connectionEstablishedTime;
private FileOutputStream fileOutputStream;
static {
// 配置日志格式
System.setProperty("java.util.logging.SimpleFormatter.format",
"[%1$tF %1$tT] [%4$-7s] %5$s %n");
}
public WebSocketTTSClient(String url, String xAppId, String authorization) {
this.url = url;
this.xAppId = xAppId;
this.authorization = authorization;
}
public void call() throws Exception {
this.startTime = System.currentTimeMillis();
logger.info(String.format("开始建立WebSocket连接,获取当前时间:%d", startTime));
// 准备输出文件
String outputPath = "test.pcm";
try {
fileOutputStream = new FileOutputStream(outputPath);
} catch (IOException e) {
logger.severe("创建输出文件失败: " + e.getMessage());
throw e;
}
// 创建WebSocket连接
Map headers = new HashMap();
headers.put("Content-Type", "application/json");
headers.put("X-APP-ID", xAppId);
headers.put("Authorization", authorization);
CountDownLatch latch = new CountDownLatch(1);
WebSocketClient client = new WebSocketClient(URI.create(url), headers) {
@Override
public void onOpen(ServerHandshake handshakedata) {
logger.info("WebSocket连接已建立");
sendInitialRequest();
}
@Override
public void onMessage(String message) {
try {
handleMessage(message);
} catch (Exception e) {
logger.severe("处理消息时发生错误: " + e.getMessage());
close();
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
logger.info("WebSocket连接已关闭: " + reason);
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
logger.warning("关闭文件输出流时发生错误: " + e.getMessage());
}
latch.countDown();
}
@Override
public void onError(Exception ex) {
logger.severe("WebSocket错误: " + ex.getMessage());
latch.countDown();
}
};
try {
client.connect();
// 等待连接完成或超时
if (!latch.await(30, TimeUnit.SECONDS)) {
logger.warning("连接超时");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.severe("连接过程被中断: " + e.getMessage());
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
logger.warning("关闭文件输出流时发生错误: " + e.getMessage());
}
}
}
}
private void sendInitialRequest() {
try {
JSONObject startJson = new JSONObject();
startJson.put("req_id", "3a87fe9793c9-4ebd-95d4-4ce2-a80c054b");
startJson.put("speaker_id", "e0639793-4119-8482-d0ab-01471575c327");
startJson.put("text", "yourText");
logger.info("=================================请求参数start_json===============================");
logger.info("请求参数:" + startJson.toString());
send(startJson.toString());
} catch (JSONException e) {
logger.severe("构建请求参数时发生JSON错误: " + e.getMessage());
}
}
private void handleMessage(String message) {
try {
JSONObject respData = new JSONObject(message);
if (connectionEstablishedTime == 0) {
// 第一个响应,记录连接建立时间
if (respData.getInt("status") == 10000) {
connectionEstablishedTime = System.currentTimeMillis();
logger.info("Received initial response: " + message);
logger.info(String.format("WebSocket connection established in %.2f seconds",
(connectionEstablishedTime - startTime) / 1000.0));
}
} else {
// 处理音频数据响应
if (respData.getInt("status") != 10000) {
logger.severe("Received non-success status: " + respData);
close();
return;
}
if (respData.has("result")) {
JSONObject result = respData.getJSONObject("result");
logger.info("Received synthesis info: " + result);
// 解码音频数据并写入文件
String audioBase64 = result.getString("audio");
byte[] audioData = DatatypeConverter.parseBase64Binary(audioBase64);
fileOutputStream.write(audioData);
boolean isEnd = result.getBoolean("is_end");
if (isEnd) {
long totalDuration = System.currentTimeMillis() - startTime;
logger.info(String.format("Total interaction duration: %.2f seconds",
totalDuration / 1000.0));
close();
}
} else {
logger.severe("No result in response: " + respData);
close();
}
}
} catch (JSONException e) {
logger.severe("解析JSON响应时发生错误: " + e.getMessage());
} catch (IOException e) {
logger.severe("写入音频文件时发生错误: " + e.getMessage());
}
}
public static void main(String[] args) {
// 设置参数(实际使用时可以从命令行参数获取)
String url = "算法调用地址";
String xAppId = "yourAppId";
String authorization = "yourAuthorization";
if (args.length >= 3) {
url = args[0];
xAppId = args[1];
authorization = args[2];
}
WebSocketTTSClient client = new WebSocketTTSClient(url, xAppId, authorization);
try {
client.call();
} catch (Exception e) {
logger.severe("执行过程中发生错误: " + e.getMessage());
e.printStackTrace();
}
}
}
import argparse
import asyncio
import websockets
import json
import base64
import logging
import time
# 配置日志,设置日志级别为INFO,并指定日志格式
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger()
async def uws_call(args):
# 获取命令行参数中的URL
url = args.url
# 记录开始时间
start_time = time.time()
logger.info(f"开始建立websocket连接,获取当前时间:{start_time}")
header = {
"Content-Type": "application/json",
'X-APP-ID': args.X_APP_ID,
'Authorization': args.Authorization
}
# 建立WebSocket连接
async with websockets.connect(url, extra_headers=header) as websocket:
logger.info("=================================请求参数start_json===============================")
# 构建请求参数
start_json = {
"req_id": "3a87fe9793c9-4ebd-95d4-4ce2-a80c054b",
"speaker_id": "e0639793-4119-8482-d0ab-01471575c327",
"text": "yourText"
}
logger.info(f"请求参数:{start_json}")
# 发送请求参数
await websocket.send(json.dumps(start_json))
try:
# 接收响应
resp_str = await websocket.recv()
resp_data = json.loads(resp_str)
if resp_data['status'] == 10000:
logger.info('Received initial response: %s', resp_str)
# 记录连接建立(即收到第一个响应)的时间
connection_established_time = time.time()
logger.info('WebSocket connection established in %.2f seconds',
connection_established_time - start_time)
is_end = False
# 打开文件以写入音频数据
with open("test.pcm", "wb") as f:
while not is_end:
# 接收音频数据
resp_audio_str = await websocket.recv()
resp_audio_data = json.loads(resp_audio_str)
if resp_audio_data['status'] != 10000:
logger.error('Received non-success status: %s', resp_audio_data)
break
if 'result' in resp_audio_data:
synth_info = resp_audio_data['result']
logger.info('Received synthesis info: %s', synth_info)
# 解码音频数据并写入文件
audio_wav = base64.b64decode(synth_info['audio'])
f.write(audio_wav)
is_end = synth_info['is_end']
else:
logger.error('No result in response: %s', resp_audio_data)
break
# 计算整个交互的耗时(从尝试连接到接收完所有消息)
total_duration = time.time() - start_time
logger.info('Total interaction duration: %.2f seconds', total_duration)
except websockets.exceptions.ConnectionClosed as e:
logger.error('WebSocket connection closed: %s', e)
# 如果连接关闭了,我们仍然可以记录到目前为止的耗时
logger.info('Duration until connection closed: %.2f seconds', time.time() - start_time)
except Exception as e:
logger.error('An error occurred: %s', e)
# 如果发生错误,我们同样记录到目前为止的耗时
logger.info('Duration until error occurred: %.2f seconds', time.time() - start_time)
finally:
# 关闭WebSocket连接
await websocket.close()
def main(args):
# 运行异步函数
asyncio.get_event_loop().run_until_complete(uws_call(args))
if __name__ == '__main__':
# 解析命令行参数
parser = argparse.ArgumentParser()
parser.add_argument('--url', type=str, default='算法调用地址')
parser.add_argument('--X_APP_ID', type=str, default='yourAppId')
parser.add_argument('--Authorization', type=str, default='yourAuthorization')
args = parser.parse_args()
# 调用主函数
main(args)