CosyVoice2提供API接口,用于管理音频文件、创建语音合成等功能。本文为您介绍CosyVoice2支持的接口类型及调用方式。
准备工作
部署CosyVoice2 WebUI服务或Frontend/Backend分离式高性能服务,且需要挂载OSS或其他存储(用来保存上传的音频文件)。具体操作,请参见快速部署WebUI服务或快速部署Frontend/Backend分离式高性能服务。
获取服务访问地址和Token。
重要对于Frontend/Backend分离式高性能服务,API调用的是Frontend服务。
压测请使用vpc地址,相比公网可大幅提升处理速度。
单击CosyVoice2的WebUI服务或Frontend服务名称,在概览页面的基本信息区域,单击查看调用信息。
在调用信息配置面板的共享网关页签,获取服务访问地址(EAS_SERVICE_URL)和Token(EAS_TOKEN),并将访问地址末尾的
/删除。说明使用公网调用地址:调用客户端需支持访问公网。
使用VPC调用地址:调用客户端必须与服务位于同一个专有网络内。

准备音频文件。
本方案中使用的参考音频如下:
参考语音WAV文件:zero_shot_prompt.wav
参考语音文本:
希望你以后能够做得比我还好哟
接口列表
上传参考音频文件
调用方式
调用地址
<EAS_SERVICE_URL>/api/v1/audio/reference_audio请求方式
POST
请求HEADERS
Authorization: Bearer <EAS_TOKEN>请求参数
file:必填,表示需要上传的音频文件,支持MP3、WAV和PCM。类型:file,默认值:无。
text:必填,表示音频文件对应的文字内容。类型:string。
返回参数
返回一个reference audio object,详情请参见返回参数列表。
请求示例
cURL
# <EAS_SERVICE_URL>和<EAS_TOKEN>需分别替换为服务访问地址和Token。 curl -XPOST <EAS_SERVICE_URL>/api/v1/audio/reference_audio \ --header 'Authorization: Bearer <EAS_TOKEN>' \ --form 'file=@"/home/xxxx/zero_shot_prompt.wav"' \ --form 'text="希望你以后能够做得比我还好哟"'Python
import requests response = requests.post( "<EAS_SERVICE_URL>/api/v1/audio/reference_audio", # <EAS_SERVICE_URL>需替换为服务访问地址。 headers={ "Authorization": "Bearer <EAS_TOKEN>", # <EAS_TOKEN>需替换为服务Token。 }, files={ "file": open("./zero_shot_prompt.wav", "rb"), }, data={ "text": "希望你以后能够做得比我还好哟" } ) print(response.text)返回示例
{ "id": "50a5fdb9-c3ad-445a-adbb-3be32750****", "filename": "zero_shot_prompt.wav", "bytes": 111496, "created_at": 1748416005, "text": "希望你以后能够做得比我还好哟" }
查看参考音频文件列表
调用方式
调用地址
<EAS_SERVICE_URL>/api/v1/audio/reference_audio
请求方式
GET
请求HEADERS
Authorization: Bearer <EAS_TOKEN>请求参数
limit:选填,用于限制返回文件数。类型:integer,默认值:100。
order:选填,类型:string。按对象的created_at时间戳排序,取值如下:
asc:升序
desc(默认值):降序
返回参数
返回一个reference audio object的数组,详情请参见上传音频的返回参数列表。
请求示例
cURL
# <EAS_SERVICE_URL>和<EAS_TOKEN>需分别替换为服务访问地址和Token。 curl -XGET <EAS_SERVICE_URL>/api/v1/audio/reference_audio?limit=10&order=desc \ --header 'Authorization: Bearer <EAS_TOKEN>'Python
import requests response = requests.get( "<EAS_SERVICE_URL>/api/v1/audio/reference_audio", # <EAS_SERVICE_URL>需替换为服务访问地址。 headers={ "Authorization": "Bearer <EAS_TOKEN>", # <EAS_TOKEN>需替换为服务Token。 } ) print(response.text)返回示例
[ { "id": "50a5fdb9-c3ad-445a-adbb-3be32750****", "filename": "zero_shot_prompt.wav", "bytes": 111496, "created_at": 1748416005, "text": "希望你以后能够做得比我还好哟" } ]
查看指定参考音频文件
调用方式
调用地址
<EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id>请求方式
GET
请求HEADERS
Authorization: Bearer <EAS_TOKEN>路径参数
reference_audio_id:必填,表示参考音频ID,如何获取,请参见查看参考音频文件列表。类型:String,默认值:无。
返回参数
返回一个reference audio object,详情请参见返回参数列表。
请求示例
cURL
# <EAS_SERVICE_URL>和<EAS_TOKEN>需分别替换为服务访问地址和Token。 # <reference_audio_id>需替换为参考音频ID。 curl -XGET <EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id> \ --header 'Authorization: Bearer <EAS_TOKEN>'Python
import requests response = requests.get( "<EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id>", # <EAS_SERVICE_URL>需替换为服务访问地址。 headers={ "Authorization": "Bearer <EAS_TOKEN>", # <EAS_TOKEN>需替换为服务Token。 } ) print(response.text)返回示例
{ "id": "50a5fdb9-c3ad-445a-adbb-3be32750****", "filename": "zero_shot_prompt.wav", "bytes": 111496, "created_at": 1748416005, "text": "希望你以后能够做得比我还好哟" }
删除参考音频文件
调用方式
调用地址
<EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id>请求方式
DELETE
请求HEADERS
Authorization: Bearer <EAS_TOKEN>路径参数
reference_audio_id:必填,表示参考音频ID,如何获取,请参见查看参考音频文件列表。类型:String,默认值:无。
返回参数
返回一个reference audio object。
请求示例
cURL
# <EAS_SERVICE_URL>和<EAS_TOKEN>需分别替换为服务访问地址和Token。 # <reference_audio_id>需替换为参考音频ID。 curl -XDELETE <EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id> \ --header 'Authorization: Bearer <EAS_TOKEN>'Python
import requests response = requests.delete( "<EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id>", # <EAS_SERVICE_URL>需替换为服务访问地址。 headers={ "Authorization": "Bearer <EAS_TOKEN>", # <EAS_TOKEN>需替换为服务Token。 } ) print(response.text)返回示例
{ "code": "OK", "message": "reference audio: c0939ce0-308e-4073-918f-91ac88e3**** deleted.", "data": {} }
创建语音合成
调用方式
调用地址
<EAS_SERVICE_URL>/api/v1/audio/speech请求方式
POST
请求HEADERS
Authorization: Bearer <EAS_TOKEN>Content-Type: application/json
请求参数
model:必填,模型名称,目前仅支持CosyVoice2-0.5B。类型:string,默认值:无。
input:必填,类型:object,默认值:无。表示输入内容,取值如下:
mode:必填,类型:string。音频合成模式,取值如下:
fast_replication:快速复刻
cross_lingual_replication:跨语种复刻
natural_language_replication:自然语言复刻
text:必填,需要合成的文本。类型:string,默认值:无。
reference_audio_id:必填,表示参考音频ID,如何获取,请参见查看参考音频文件列表。类型:string,默认值:无。
instruct:选填,instruct文本,动态调整语音风格,例如语气、情感、语速等。仅模式选择natural_language_replication时生效。类型:string,默认值:无。
sample_rate:选填,音频采样率。默认值:24000。
bit_rate: 选填, 比特率。类型:string,默认值:192k。支持 16k、32k、48k、64k、128k、192k、256k、320k、384k。
volume: 选填,音量。 类型:float,默认值:1.0。例如,3.0就是三倍音量,0.8就是0.8倍音量。
speed:选填,输出语音的速度,取值范围:[0.5~2.0]。类型:float,默认值:1.0。
output_format:选填,输出音频格式。目前支持的格式:wav、mp3、pcm。默认值:wav。
stream:选填,是否输出流式。类型:boolean,默认值:true。
返回参数
流式返回speech chunk object,详情请参见返回参数列表。
请求示例
非流式调用
cURL
<EAS_SERVICE_URL>和<EAS_TOKEN>需分别替换为服务访问地址和Token。
<reference_audio_id>需替换为参考音频ID。
# <EAS_SERVICE_URL>和<EAS_TOKEN>需分别替换为服务访问地址和Token。 # <reference_audio_id>需替换为参考音频ID。 curl -XPOST <EAS_SERVICE_URL>/api/v1/audio/speech \ --header 'Authorization: Bearer <EAS_TOKEN>' \ --header 'Content-Type: application/json' \ --data '{ "model": "CosyVoice2-0.5B", "input": { "mode": "natural_language_replication", "reference_audio_id": "<reference_audio_id>", "text": "北京, 你好, 收到好友从远方寄来的生日礼物,那份意外的惊喜与深深的祝福让我心中充满了甜蜜的快乐,笑容如花儿般绽放。", "speed": 1.0, "instruct": "用四川话说或者用广东话说" }, "stream": false }'返回base64位编码结果:
{"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"}Python
import json import base64 import requests from packaging import version required_version = "2.32.3" if version.parse(requests.__version__) < version.parse(required_version): raise RuntimeError(f"requests version must >= {required_version}") with requests.post( "<EAS_SERVICE_URL>/api/v1/audio/speech", # <EAS_SERVICE_URL>需替换为服务访问地址。 headers={ "Authorization": "Bearer <EAS_TOKEN>", # <EAS_TOKEN>需替换为服务Token。 "Content-Type": "application/json", }, json={ "model": "CosyVoice2-0.5B", "input": { "mode": "natural_language_replication", "reference_audio_id": "<reference_audio_id>", # <reference_audio_id>需替换为参考音频ID。 "text": "北京, 你好, 收到好友从远方寄来的生日礼物,那份意外的惊喜与深深的祝福让我心中充满了甜蜜的快乐,笑容如花儿般绽放。", "speed": 1.0, "instruct": "用四川话说或者用广东话说" }, "stream": False }, timeout=10 ) as response: if response.status_code != 200: print(response.text) exit() data = json.loads(response.content) encode_buffer = data['output']['audio']['data'] decode_buffer = base64.b64decode(encode_buffer) with open('./http_non_stream.wav', 'wb') as wfile: wfile.write(decode_buffer)流式调用
cURL
<EAS_SERVICE_URL>和<EAS_TOKEN>需分别替换为服务访问地址和Token。
<reference_audio_id>需替换为参考音频ID。
# <EAS_SERVICE_URL>和<EAS_TOKEN>需分别替换为服务访问地址和Token。 # <reference_audio_id>需替换为参考音频ID。 curl -XPOST <EAS_SERVICE_URL>/api/v1/audio/speech \ --header 'Authorization: Bearer <EAS_TOKEN>' \ --header 'Content-Type: application/json' \ --data '{ "model": "CosyVoice2-0.5B", "input": { "mode": "natural_language_replication", "reference_audio_id": "<reference_audio_id>", "text": "北京, 你好, 收到好友从远方寄来的生日礼物,那份意外的惊喜与深深的祝福让我心中充满了甜蜜的快乐,笑容如花儿般绽放。", "speed": 1.0, "instruct": "用四川话说或者用广东话说" }, "stream": true }'返回base64位编码结果如下:
data: {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"} data: {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"} data: {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"} data: {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"}Python
需要安装python SSE客户端:
pip install requests==2.32.3 packaging==24.2 sseclient-py==1.8.0import io import json import base64 import wave import requests from sseclient import SSEClient # pip install sseclient-py from packaging import version required_version = "2.32.3" if version.parse(requests.__version__) < version.parse(required_version): raise RuntimeError(f"requests version must >= {required_version}") with requests.post( "<EAS_SERVICE_URL>/api/v1/audio/speech", # <EAS_SERVICE_URL>需替换为服务访问地址。 headers={ "Authorization": "Bearer <EAS_TOKEN>", # <EAS_TOKEN>需替换为服务Token。 "Content-Type": "application/json", }, json={ "model": "CosyVoice2-0.5B", "input": { "mode": "natural_language_replication", "reference_audio_id": "<reference_audio_id>", # <reference_audio_id>需替换为参考音频ID。 "text": "北京, 你好, 收到好友从远方寄来的生日礼物,那份意外的惊喜与深深的祝福让我心中充满了甜蜜的快乐,笑容如花儿般绽放。", "output_format": "wav", "sample_rate": 24000, "speed": 1.0, "instruct": "用四川话说或者用广东话说" }, "stream": True }, timeout=10 ) as response: if response.status_code != 200: print(response.text) exit() messages = SSEClient(response) with open('./http_stream.wav', 'wb') as f: for i, msg in enumerate(messages.events()): print(f"Event: {msg.event}, Data: {msg.data}") data = json.loads(msg.data) if data['error'] is not None: print(data['error']) break encode_buffer = data['output']['audio']['data'] decode_buffer = base64.b64decode(encode_buffer) f.write(decode_buffer)Websocket API
需要安装如下依赖:
pip install websocket-client==1.8.0#!/usr/bin/python # -*- coding: utf-8 -*- import base64 import json import logging import sys import time import uuid import websocket class TTSClient: def __init__(self, api_key, uri, params, log_level='INFO'): """ 初始化 TTSClient 实例 参数: api_key (str): 鉴权用的 API Key uri (str): WebSocket 服务地址 """ self._api_key = api_key # 替换为你的 API Key self._uri = uri # 替换为你的 WebSocket 地址 self._task_id = str(uuid.uuid4()) # 生成唯一任务 ID self._ws = None # WebSocketApp 实例 self._task_started = False # 是否收到 task-started self._task_finished = False # 是否收到 task-finished / task-failed self._check_params(params) self._params = params self._chunk_metrics = [] self._metrics = {} self._first_package_time = None self._last_time = None self._init_log(log_level) self.audio_data = b'' def _init_log(self, log_level): self._log = logging.getLogger("ws_client") log_formatter = logging.Formatter('%(asctime)s - Process(%(process)s) - %(levelname)s - %(message)s') stream_handler = logging.StreamHandler(stream=sys.stdout) stream_handler.setFormatter(log_formatter) self._log.addHandler(stream_handler) self._log.setLevel(log_level) def get_metrics(self): """获取合成结果性能指标""" return self._metrics def _check_params(self, params): assert 'mode' in params and params['mode'] in ['fast_replication', 'cross_lingual_replication', 'natural_language_replication'] assert 'reference_audio_id' in params assert 'output_format' in params and params['output_format'] in ['wav', 'mp3', 'pcm'] if params['mode'] == 'natural_language_replication': assert 'instruct' in params and params['instruct'] else: if 'instruct' in params: del params['instruct'] def on_open(self, ws): """ WebSocket 连接建立时回调函数 发送 run-task 指令开启语音合成任务 """ self._log.debug("WebSocket 已连接") # 构造 run-task 指令 run_task_cmd = { "header": { "action": "run-task", "task_id": self._task_id, "streaming": "duplex" }, "payload": { "task_group": "audio", "task": "tts", "function": "SpeechSynthesizer", "model": "cosyvoice-v2", "parameters": { "mode": self._params['mode'], "reference_audio_id": self._params['reference_audio_id'], "output_format": self._params.get('output_format', 'wav'), "sample_rate": self._params.get('sample_rate', 24000), "instruct": self._params.get('instruct', ''), "speed": self._params.get('speed', 1.0), "debug": True, }, "input": {} } } # 发送 run-task 指令 ws.send(json.dumps(run_task_cmd)) self._log.debug("已发送 run-task 指令") def on_message(self, ws, message): """ 接收到消息时的回调函数 区分文本和二进制消息处理 """ try: msg_json = json.loads(message) # self._log.debug(f"收到 JSON 消息: {msg_json}") self._log.debug(f"收到 JSON 消息: {msg_json['header']['event']}") if "header" in msg_json: header = msg_json["header"] if "event" in header: event = header["event"] if event == "task-started": self._log.debug("任务已启动") self._task_started = True # 发送 continue-task 指令 for text in self._params['texts']: self.send_continue_task(text) # 所有 continue-task 发送完成后发送 finish-task self.send_finish_task() self._last_time = time.time() elif event == "result-generated": metrics = msg_json['payload']['metrics'] cur_time = time.time() metrics['client_cost_time'] = cur_time - self._last_time self._last_time = cur_time encode_data = msg_json["payload"]["output"]["audio"]["data"] decode_data = base64.b64decode(encode_data) self._log.debug(f"收到音频数据,大小: {len(decode_data)} 字节") self.audio_data += decode_data metrics['client_rtf'] = metrics['client_cost_time'] / metrics['speech_len'] self._chunk_metrics.append(metrics) elif event == "task-finished": self._metrics = { 'client_first_package_time': self._chunk_metrics[0]['client_cost_time'], "client_rtf": sum([m["client_cost_time"] for m in self._chunk_metrics]) / sum([m["speech_len"] for m in self._chunk_metrics]), 'client_cost_time': sum([m["client_cost_time"] for m in self._chunk_metrics]), 'speech_len': sum([m["speech_len"] for m in self._chunk_metrics]), 'server_first_package_time': self._chunk_metrics[0]['server_cost_time'], 'server_rtf': sum([m["server_cost_time"] for m in self._chunk_metrics]) / sum([m["speech_len"] for m in self._chunk_metrics]), 'server_cost_time': sum([m["server_cost_time"] for m in self._chunk_metrics]), "generate_time": sum([m["generate_time"] for m in self._chunk_metrics]) } self._log.debug(f"任务已完成, 请求性能指标: client_first_package_time: {self._metrics['client_first_package_time']:.3f}, client_rtf: {self._metrics['client_rtf']:.3f}, client_cost_time: {self._metrics['client_cost_time']:.3f}, speech_len: {self._metrics['speech_len']:.3f}, server_cost_time: {self._metrics['server_cost_time']:.3f}, generate_time: {self._metrics['generate_time']:.3f}") self._task_finished = True self.close(ws) elif event == "task-failed": self._log.debug(f"任务失败: {msg_json}") self._task_finished = True self.close(ws) except json.JSONDecodeError as e: self._log.debug(f"JSON 解析失败: {str(e)}") def on_error(self, ws, error): """发生错误时的回调""" self._log.debug(f"WebSocket 出错: {error}") self._metrics = {'error': error} def on_close(self, ws, close_status_code, close_msg): """连接关闭时的回调""" self._log.debug(f"WebSocket 已关闭: {close_msg} ({close_status_code})") def send_continue_task(self, text): """发送 continue-task 指令,附带要合成的文本内容""" cmd = { "header": { "action": "continue-task", "task_id": self._task_id, "streaming": "duplex" }, "payload": { "input": { "text": text } } } self._ws.send(json.dumps(cmd)) self._log.debug(f"已发送 continue-task 指令,文本内容: {text}") def send_finish_task(self): """发送 finish-task 指令,结束语音合成任务""" cmd = { "header": { "action": "finish-task", "task_id": self._task_id, "streaming": "duplex" }, "payload": { "input": {} } } self._ws.send(json.dumps(cmd)) self._log.debug("已发送 finish-task 指令") def close(self, ws): """主动关闭连接""" if ws and ws.sock and ws.sock.connected: ws.close() self._log.debug("已主动关闭连接") def run(self): """启动 WebSocket 客户端""" # 设置请求头部(鉴权) header = { "Authorization": f"Bearer {self._api_key}", } # 创建 WebSocketApp 实例 self._ws = websocket.WebSocketApp( self._uri, header=header, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) self._log.debug("正在监听 WebSocket 消息...") self._ws.run_forever() # 启动长连接监听 # 示例使用方式 if __name__ == "__main__": # your-api-key需替换为服务Token。 API_KEY = 'your-api-key' # localhost:50000 需替换为服务访问地址。 SERVER_URI = "ws://localhost:50000/api-ws/v1/audio/speech" texts = [ # "床前明月光,疑是地上霜。", # "举头望明月,低头思故乡。" "北京, 你好, 收到好友从远方寄来的生日礼物,那份意外的惊喜与深深的祝福让我心中充满了甜蜜的快乐,笑容如花儿般绽放。" ] params = { 'mode': 'natural_language_replication', 'texts': texts, 'reference_audio_id': '<reference_audio_id>', 'speed': 1.0, 'output_format': 'wav', 'sample_rate': 24000, "instruct": "用冷静的语气说", } client = TTSClient(API_KEY, SERVER_URI, params, log_level='DEBUG') client.run() with open('./websocket_stream.wav', 'wb') as wfile: wfile.write(client.audio_data)