博 客 - 正文

声音克隆API_V3开发教程

来源:创客API 分类:代码示例 SUPERADMIN 阅读(45)

声音克隆 API V3 接入教程

在线效果体验:https://ai.hihookeji.com

接口地址:https://api.hihookeji.com/api/tts/clonevoicev3

返回格式:application/json

请求方式:HTTP POST


一、接口概述

本接口根据一段 5~14 秒 的参考音频(建议 MP3)与指定文案,克隆音色并合成目标文本语音。

项目说明
接口地址https://api.hihookeji.com/api/tts/clonevoicev3
认证方式请求体字段 key(你的 API 密钥,与业务参数一并提交)
请求示例https://api.hihookeji.com/api/tts/clonevoicev3
Content-Typeapplication/json(推荐)或 application/x-www-form-urlencoded
业务判断统一用响应字段 code200 成功
HTTP 状态码请以响应体 code 为准

二、请求参数

参数名类型必填描述示例
keystringAPI 密钥,放在请求体中与其它参数一起提交你的密钥
source_audio_urlstring参考音频公网 URL,须为标准 mp3 / wav,且 URL .mp3.wav 结尾;时长 5~14 秒;音频中读的文案必须为下方指定文案https://a.b.c/1.mp3
target_textstring要合成的目标文本,单次不超过 2000 字;超过 1000 字建议用异步(type=1)我是克隆的文字
notify_urlstring合成结果通知地址(异步回调接收地址,需公网可达)见下方「回调测试」
ref_textstring参考音频文案,必须为下方两种之一见下方说明
speedfloat语速,默认 1.01
pitchfloat语调,默认 1.01
typeint结果返回类型,默认异步:1 异步文件回调;2 同步返回 Base64 音频1
mtypeint1 单人;2 多角色1

回调测试:联调异步回调时,建议先打开 https://webhook.site/ 获取自己的唯一 URL,将该地址填入 notify_url。提交任务后可在网页上直接查看服务端推送的表单字段与 target_file 文件,无需先部署自己的回调服务。


三、参考音频文案(必须严格一致)

source_audio_url 对应音频中念出的内容,以及参数 ref_text必须与下列文案完全一致,否则克隆会失败:

  1. 中文:我的声音将用于平台克隆,并合法使用,为自己的行为负责
  1. 英文:My voice will be used for platform cloning , and I take responsibility for my actions

四、文本标签能力

可在 target_text 中使用下列标签。

4.1 停顿标签

支持秒(s)与毫秒(ms),单次停顿 最大不超过 10 秒,否则失败。


如果每天都是全新的一天。

表示停顿50毫秒

4.2 多音字 / 发音矫正


每天早起照镜子都崩溃。

多音字:重要处理。

ph 中音标与声调之间有空格,如 wan 3gan 4chong 3

4.3 读法标签(say-as)

interpret-as含义
number数字读法
time时间读法
date日期读法
value数值读法
telephone手机号 / 电话读法

4.4 完整示例


金额123436511.562254,

数值是-1204455,

停顿5秒,

多音字:重要处理。

现在的时间是:14:05,

Today is 25-02-01.

我的电话: (888) 555-1212

五、返回结果说明

5.1 异步提交成功(type=1)

立即返回任务受理结果;data.audio_base64null,音频通过 notify_url 回调。

字段说明
code业务状态码,200 成功
msg说明信息
data.taskid任务 ID,回调关联用
data.consume消耗点数
data.audio_base64异步为 null
exec_time接口耗时(秒)
ip请求方 IP

{

"code": 200,

"msg": "success",

"data": {

"taskid": "1784017039696108",

"consume": 20,

"audio_base64": null

},

"exec_time": 0.097585,

"ip": "14.19.93.116"

}

5.2 同步返回(type=2)

同步成功时 data.audio_base64 为音频 Base64 字符串。必须先 Base64 解码再保存为文件。


{

"code": 200,

"msg": "success",

"data": {

"taskid": "1784017039913816",

"consume": 20,

"audio_base64": "SUQzBAAAAAALFVRTo......="

},

"exec_time": 0.936593,

"ip": "14.19.93.116"

}

5.3 异步回调 Notify(type=1)

服务端合成完成后,以 表单 POSTnotify_url 推送(成功带文件时为 multipart/form-data,失败多为 application/x-www-form-urlencoded),不是 JSON Body。

字段说明
errcode0 成功1001 失败(与接口提交响应的 code: 200 不同,回调请按此处判断)
taskid任务 ID
msg说明文案
target_file音频文件(mp3/wav),PHP 可用 $_FILES['target_file'] 接收;失败时无此字段

六、接入流程建议

  1. 准备合规参考音频(5~14 秒,MP3),念上述指定文案之一,公网可访问 URL。
  1. 申请 API Key,作为请求体字段 key 与其它参数一起提交。
  1. 字数 ≤1000 且需立即拿结果:可用 type=2 同步;字数较多或需稳定投递:用 type=1 异步。联调回调时可先用 webhook.site 获取唯一接收地址填入 notify_url
  1. 异步场景下用 data.taskid 关联提交与回调结果。
  1. 同步场景对 data.audio_base64 解码后落盘。

七、Python 完整代码

依赖:requestspip install requests


# -*- coding: utf-8 -*-

"""

声音克隆 API V3 - Python 完整示例

接口: https://api.hihookeji.com/api/tts/clonevoicev3

"""

import os

import base64

import json

from datetime import datetime

import requests

API_URL = "https://api.hihookeji.com/api/tts/clonevoicev3"

API_KEY = "你的密钥"  # 替换为真实密钥

# 参考音频文案(必须与音频内容一致,二选一)

REF_TEXT_CN = "我的声音将用于平台克隆,并合法使用,为自己的行为负责"

REF_TEXT_EN = (

"My voice will be used for platform cloning , "

"and I take responsibility for my actions"

)

def clone_voice(

source_audio_url,

target_text,

notify_url,

ref_text=REF_TEXT_CN,

speed=1.0,

pitch=1.0,

result_type=1,

mtype=1,

timeout=120,

):

"""

调用声音克隆 V3 接口。

:param source_audio_url: 参考音频公网 URL(mp3,5~14 秒)

:param target_text: 目标合成文本(≤2000 字)

:param notify_url: 异步回调地址(type=1 必填且需可公网访问)

:param ref_text: 参考音频文案(必须为指定文案)

:param speed: 语速,默认 1.0

:param pitch: 语调,默认 1.0

:param result_type: 1=异步回调,2=同步 Base64

:param mtype: 1=单人,2=多角色

:param timeout: HTTP 超时秒数(同步建议调大)

:return: 接口 JSON 字典

"""

url = API_URL

payload = {

"key": API_KEY,

"source_audio_url": source_audio_url,

"target_text": target_text,

"notify_url": notify_url,

"ref_text": ref_text,

"speed": speed,

"pitch": pitch,

"type": result_type,

"mtype": mtype,

}

resp = requests.post(

url,

json=payload,

headers={"Content-Type": "application/json"},

timeout=timeout,

)

resp.raise_for_status()

return resp.json()

def save_base64_audio(result, save_dir="./output"):

"""

将 type=2 同步返回的 Base64 音频解码并保存为 mp3。

"""

if int(result.get("code") or 0) != 200:

raise RuntimeError(f"合成失败: {result.get('msg')}")

data = result.get("data") or {}

audio_b64 = data.get("audio_base64")

if not audio_b64:

raise RuntimeError("响应中缺少 data.audio_base64")

os.makedirs(save_dir, exist_ok=True)

audio_bytes = base64.b64decode(audio_b64)

timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

taskid = data.get("taskid") or timestamp

save_path = os.path.join(save_dir, f"{taskid}.mp3")

with open(save_path, "wb") as f:

f.write(audio_bytes)

return save_path

# ---------------------------------------------------------------------------

# Flask 异步回调接收示例(可选,部署到公网后作为 notify_url)

# 安装: pip install flask

# ---------------------------------------------------------------------------

def create_notify_app(save_dir="./callback_audio"):

from flask import Flask, request, jsonify

app = Flask(__name__)

os.makedirs(save_dir, exist_ok=True)

@app.route("/Notify", methods=["POST"])

def notify():

# 回调为表单字段(有文件时是 multipart),不是 JSON Body

taskid = request.form.get("taskid")

msg = request.form.get("msg")

errcode = request.form.get("errcode")  # 成功 0,失败 1001

if "target_file" in request.files:

f = request.files["target_file"]

if f and f.filename:

ext = os.path.splitext(f.filename)[1] or ".mp3"

path = os.path.join(save_dir, f"{taskid}{ext}")

f.save(path)

return jsonify({"ok": True, "taskid": taskid, "saved": path, "errcode": errcode})

return jsonify({"ok": False, "taskid": taskid, "msg": msg, "errcode": errcode})

return app

if __name__ == "__main__":

# ---------- 示例 1:异步提交(type=1)----------

async_result = clone_voice(

source_audio_url="https://a.b.c/1.mp3",

target_text='你好,这是克隆语音。今天天气不错。',

notify_url="https://webhook.site/你的唯一ID",

ref_text=REF_TEXT_CN,

result_type=1,

mtype=1,

)

print("异步提交:", json.dumps(async_result, ensure_ascii=False, indent=2))

# 成功时 code==200,记录 data.taskid;回调可在 webhook.site 页面查看

# ---------- 示例 2:同步返回 Base64(type=2)----------

sync_result = clone_voice(

source_audio_url="https://a.b.c/1.mp3",

target_text="我是克隆的文字",

notify_url="https://webhook.site/你的唯一ID",  # 同步模式仍按文档传 notify_url

ref_text=REF_TEXT_CN,

result_type=2,

timeout=180,

)

print("同步响应 code:", sync_result.get("code"), "type:", type(sync_result.get("code")), "msg:", sync_result.get("msg"))

print("data keys:", list((sync_result.get("data") or {}).keys()))

print("audio_base64 是否有值:", bool((sync_result.get("data") or {}).get("audio_base64")))

if int(sync_result.get("code") or 0) == 200:

try:

path = save_base64_audio(sync_result)

print("音频已保存:", path)

except Exception as e:

print("保存失败:", repr(e))

print("完整 data:", json.dumps(sync_result.get("data"), ensure_ascii=False)[:500])

# ---------- 启动回调服务(需要时取消注释)----------

# app = create_notify_app()

# app.run(host="0.0.0.0", port=8080)

九、Java 完整代码

依赖:JDK 8+,使用内置 HttpURLConnection,无需第三方库。

若使用 Maven,也可自行替换为 OkHttp / Apache HttpClient。


import java.io.*;

import java.net.HttpURLConnection;

import java.net.URL;

import java.nio.charset.StandardCharsets;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.time.LocalDateTime;

import java.time.format.DateTimeFormatter;

import java.util.Base64;

/**

* 声音克隆 API V3 - Java 完整示例

* 接口: https://api.hihookeji.com/api/tts/clonevoicev3

*

* 说明:为减少依赖,本示例用字符串拼 JSON,并用简单方式解析 code / audio_base64。

* 生产环境建议改用 Jackson / Gson 等库。

*/

public class CloneVoiceV3Demo {

private static final String API_URL = "https://api.hihookeji.com/api/tts/clonevoicev3";

private static final String API_KEY = "你的密钥"; // 替换为真实密钥

public static final String REF_TEXT_CN =

"我的声音将用于平台克隆,并合法使用,为自己的行为负责";

public static final String REF_TEXT_EN =

"My voice will be used for platform cloning , and I take responsibility for my actions";

public static class CloneRequest {

public String sourceAudioUrl;

public String targetText;

public String notifyUrl;

public String refText = REF_TEXT_CN;

public double speed = 1.0;

public double pitch = 1.0;

public int type = 1;      // 1 异步,2 同步 Base64

public int mtype = 1;     // 1 单人,2 多角色

}

/**

* 调用声音克隆 V3

*/

public static String cloneVoice(CloneRequest req, int timeoutMs) throws IOException {

String urlStr = API_URL;

URL url = new URL(urlStr);

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("POST");

conn.setConnectTimeout(15000);

conn.setReadTimeout(timeoutMs);

conn.setDoOutput(true);

conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");

conn.setRequestProperty("Accept", "application/json");

String json = toJson(req);

byte[] body = json.getBytes(StandardCharsets.UTF_8);

conn.setRequestProperty("Content-Length", String.valueOf(body.length));

try (OutputStream os = conn.getOutputStream()) {

os.write(body);

}

int code = conn.getResponseCode();

InputStream is = (code >= 200 && code < 300)

? conn.getInputStream()

: conn.getErrorStream();

String resp = readFully(is);

conn.disconnect();

return resp;

}

/**

* 将 type=2 的 audio_base64 解码保存为 mp3

*/

public static Path saveBase64Audio(String responseJson, String saveDir) throws IOException {

int code = extractInt(responseJson, "code");

if (code != 200) {

String msg = extractString(responseJson, "msg");

throw new IOException("合成失败: " + msg);

}

String audioB64 = extractNestedString(responseJson, "audio_base64");

if (audioB64 == null || audioB64.isEmpty() || "null".equals(audioB64)) {

throw new IOException("响应中缺少 data.audio_base64");

}

Path dir = Paths.get(saveDir);

if (!Files.exists(dir)) {

Files.createDirectories(dir);

}

byte[] audioBytes = Base64.getDecoder().decode(audioB64);

String taskid = extractNestedString(responseJson, "taskid");

String name = (taskid != null && !taskid.isEmpty())

? taskid + ".mp3"

: LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".mp3";

Path path = dir.resolve(name);

Files.write(path, audioBytes);

return path;

}

// --------------------- JSON 构造 / 简易解析 ---------------------

private static String toJson(CloneRequest r) {

StringBuilder sb = new StringBuilder();

sb.append("{");

sb.append(""key":").append(quote(API_KEY)).append(",");

sb.append(""source_audio_url":").append(quote(r.sourceAudioUrl)).append(",");

sb.append(""target_text":").append(quote(r.targetText)).append(",");

sb.append(""notify_url":").append(quote(r.notifyUrl)).append(",");

sb.append(""ref_text":").append(quote(r.refText)).append(",");

sb.append(""speed":").append(r.speed).append(",");

sb.append(""pitch":").append(r.pitch).append(",");

sb.append(""type":").append(r.type).append(",");

sb.append(""mtype":").append(r.mtype);

sb.append("}");

return sb.toString();

}

private static String quote(String s) {

if (s == null) {

return "null";

}

String escaped = s

.replace("\", "\\")

.replace(""", "\"")

.replace("n", "\n")

.replace("r", "\r")

.replace("t", "\t");

return """ + escaped + """;

}

private static String urlEncode(String s) {

try {

return java.net.URLEncoder.encode(s, "UTF-8");

} catch (UnsupportedEncodingException e) {

return s;

}

}

private static String readFully(InputStream is) throws IOException {

if (is == null) {

return "";

}

ByteArrayOutputStream bos = new ByteArrayOutputStream();

byte[] buf = new byte[4096];

int n;

while ((n = is.read(buf)) != -1) {

bos.write(buf, 0, n);

}

return new String(bos.toByteArray(), StandardCharsets.UTF_8);

}

private static int extractInt(String json, String key) {

String v = extractString(json, key);

try {

return Integer.parseInt(v);

} catch (Exception e) {

return -1;

}

}

private static String extractString(String json, String key) {

String pattern = """ + key + """;

int i = json.indexOf(pattern);

if (i < 0) {

return null;

}

int colon = json.indexOf(':', i + pattern.length());

if (colon < 0) {

return null;

}

int start = colon + 1;

while (start < json.length() && Character.isWhitespace(json.charAt(start))) {

start++;

}

if (start >= json.length()) {

return null;

}

if (json.charAt(start) == '"') {

int end = start + 1;

StringBuilder sb = new StringBuilder();

while (end < json.length()) {

char c = json.charAt(end);

if (c == '\' && end + 1 < json.length()) {

sb.append(json.charAt(end + 1));

end += 2;

continue;

}

if (c == '"') {

break;

}

sb.append(c);

end++;

}

return sb.toString();

}

int end = start;

while (end < json.length()) {

char c = json.charAt(end);

if (c == ',' || c == '}' || c == ']') {

break;

}

end++;

}

return json.substring(start, end).trim();

}

private static String extractNestedString(String json, String key) {

return extractString(json, key);

}

// --------------------- 异步回调 Servlet 示例(可选) ---------------------

/*

* 若使用 Servlet 容器,可参考下列伪代码:

*

* @WebServlet("/Notify")

* public class NotifyServlet extends HttpServlet {

*   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {

*     // 表单字段: taskid / msg / errcode(成功0,失败1001)

*     // multipart: Part file = req.getPart("target_file");

*   }

* }

*/

public static void main(String[] args) throws Exception {

// 示例 1:异步 type=1

CloneRequest asyncReq = new CloneRequest();

asyncReq.sourceAudioUrl = "https://a.b.c/1.mp3";

asyncReq.targetText = "你好,这是克隆语音。今天天气不错。";

asyncReq.notifyUrl = "https://webhook.site/你的唯一ID";

asyncReq.refText = REF_TEXT_CN;

asyncReq.type = 1;

asyncReq.mtype = 1;

String asyncResp = cloneVoice(asyncReq, 60000);

System.out.println("异步提交: " + asyncResp);

// 示例 2:同步 type=2

CloneRequest syncReq = new CloneRequest();

syncReq.sourceAudioUrl = "https://a.b.c/1.mp3";

syncReq.targetText = "我是克隆的文字";

syncReq.notifyUrl = "https://webhook.site/你的唯一ID";

syncReq.refText = REF_TEXT_CN;

syncReq.type = 2;

syncReq.mtype = 1;

String syncResp = cloneVoice(syncReq, 180000);

System.out.println("同步响应: " + syncResp);

if (extractInt(syncResp, "code") == 200) {

Path path = saveBase64Audio(syncResp, "./output");

System.out.println("音频已保存: " + path.toAbsolutePath());

}

}

}

十、常见问题

问题处理建议
克隆效果异常 / 失败检查参考音频是否念指定文案,且 ref_text 与音频内容完全一致
参考音频格式不对source_audio_url 必须是标准 mp3wav;提示失败时可先用 ffmpeg 转码后再上传,例如:ffmpeg -i 输入文件 -acodec libmp3lame -ar 44100 输出.mp3
参考音频链接后缀不对URL 必须以 .mp3.wav 结尾;常见 OSS 带鉴权参数的长链接(不以 .mp3/.wav 结尾)会失败,需换成可访问且后缀正确的直链,或转存后再提交
参考音频时长必须在 5~14 秒;URL 需公网可访问
长文本超过 1000 字建议 type=1 异步;单次最多 2000 字
停顿过长`` 单次最大 10 秒
只看 HTTP 200不够,必须判断响应体 code == 200
同步音频无法播放data.audio_base64 解码后再写文件,不要直接把 Base64 字符串当二进制保存
异步收不到回调确认 notify_url 公网可达、支持 POST;联调可先用 webhook.site 唯一地址验证是否已推送;正式环境再换成自己的接收服务
回调当成 JSON 解析失败回调是表单 POST,用 form/$_POST 取值,不要按 JSON Body 解析
回调成功却判失败回调成功 errcode0(不是提交接口的 code: 200),失败为 1001
旧接口标签无效请改用本教程新地址 .../clonevoicev3

十一、快速对照

模式type立即响应音频获取方式
异步1(默认)code:200data.taskidaudio_base64: nullnotify_url 回调 multipart 文件 target_file
同步2code:200data.audio_base64 有值本地 Base64 解码落盘

数据驱动未来

立即注册

客服微信

请打开手机微信,扫一扫联系我们

返回顶部