语音合成.md 30 KB

百炼平台语音合成 - Python SDK 调用指南

模型概览

CosyVoice 语音合成模型支持将文本转换为自然流畅的语音。

模型名称 单价 特点
cosyvoice-v3-plus $0.286706/万字符 高质量语音合成
cosyvoice-v3-flash $0.14335/万字符 快速语音合成
cosyvoice-v2 $0.286706/万字符 稳定版本

前提条件

  • 已获取并配置 API Key
  • 将 API Key 配置到环境变量 DASHSCOPE_API_KEY
  • 安装 DashScope SDK:pip install dashscope
  • 安装 pyaudio(用于音频播放):
    • macOS:brew install portaudio && pip install pyaudio
    • Debian/Ubuntu:sudo apt-get install python-pyaudio python3-pyaudio
    • CentOS:sudo yum install -y portaudio portaudio-devel && pip install pyaudio
    • Windows:python -m pip install pyaudio

调用方式

SpeechSynthesizer类提供了三种调用方式:

1. 非流式调用

提交单个语音合成任务,一次性获取完整结果。

import dashscope
from dashscope.audio.tts_v2 import *

# 若没有将API Key配置到环境变量中,需将your-api-key替换为自己的API Key
# dashscope.api_key = "your-api-key"

# 实例化SpeechSynthesizer
synthesizer = SpeechSynthesizer(
    model="cosyvoice-v3-flash",
    voice="longanyang"
)

# 调用call方法进行合成,返回二进制音频数据
audio_data = synthesizer.call("今天天气怎么样?")

# 保存音频文件
with open("output.mp3", "wb") as f:
    f.write(audio_data)

print("音频已保存到 output.mp3")

2. 单向流式调用

提交单个语音合成任务,通过回调方式流式输出中间结果。

import dashscope
from dashscope.audio.tts_v2 import *
from datetime import datetime

def get_timestamp():
    now = datetime.now()
    return now.strftime("[%Y-%m-%d %H:%M:%S.%f]")

# 定义回调接口
class Callback(ResultCallback):
    def on_open(self):
        print("连接建立:" + get_timestamp())
        self.file = open("output.mp3", "wb")

    def on_complete(self):
        print("语音合成完成:" + get_timestamp())

    def on_error(self, message: str):
        print(f"语音合成出现异常:{message}")

    def on_close(self):
        print("连接关闭:" + get_timestamp())
        self.file.close()

    def on_event(self, message):
        pass

    def on_data(self, data: bytes) -> None:
        print(get_timestamp() + " 二进制音频长度为:" + str(len(data)))
        self.file.write(data)

callback = Callback()

# 实例化SpeechSynthesizer,传入回调接口
synthesizer = SpeechSynthesizer(
    model="cosyvoice-v3-flash",
    voice="longanyang",
    callback=callback
)

# 调用call方法,通过回调接口实时获取合成结果
synthesizer.call("今天天气怎么样?")

# 获取首包延迟
print(f'[Metric] requestId为:{synthesizer.get_last_request_id()},首包延迟为:{synthesizer.get_first_package_delay()}毫秒')

3. 双向流式调用

在同一个语音合成任务中分多次提交文本,并通过回调方式实时获取合成结果。

import time
import pyaudio
import dashscope
from dashscope.audio.tts_v2 import *
from datetime import datetime

def get_timestamp():
    now = datetime.now()
    return now.strftime("[%Y-%m-%d %H:%M:%S.%f]")

# 定义回调接口
class Callback(ResultCallback):
    _player = None
    _stream = None

    def on_open(self):
        print("连接建立:" + get_timestamp())
        self._player = pyaudio.PyAudio()
        self._stream = self._player.open(
            format=pyaudio.paInt16, channels=1, rate=22050, output=True
        )

    def on_complete(self):
        print("语音合成完成,所有合成结果已被接收:" + get_timestamp())

    def on_error(self, message: str):
        print(f"语音合成出现异常:{message}")

    def on_close(self):
        print("连接关闭:" + get_timestamp())
        self._stream.stop_stream()
        self._stream.close()
        self._player.terminate()

    def on_event(self, message):
        pass

    def on_data(self, data: bytes) -> None:
        print(get_timestamp() + " 二进制音频长度为:" + str(len(data)))
        self._stream.write(data)

callback = Callback()

test_text = [
    "流式文本语音合成SDK,",
    "可以将输入的文本",
    "合成为语音二进制数据,",
    "相比于非流式语音合成,",
    "流式合成的优势在于实时性",
    "更强。用户在输入文本的同时",
    "可以听到接近同步的语音输出,",
    "极大地提升了交互体验,",
    "减少了用户等待时间。",
]

# 实例化SpeechSynthesizer
synthesizer = SpeechSynthesizer(
    model="cosyvoice-v3-flash",
    voice="longanyang",
    format=AudioFormat.PCM_22050HZ_MONO_16BIT,
    callback=callback
)

# 流式发送待合成文本
for text in test_text:
    synthesizer.streaming_call(text)
    time.sleep(0.1)

# 结束流式语音合成(必须调用)
synthesizer.streaming_complete()

# 获取首包延迟
print(f'[Metric] requestId为:{synthesizer.get_last_request_id()},首包延迟为:{synthesizer.get_first_package_delay()}毫秒')

参数说明

请求参数

通过SpeechSynthesizer类的构造方法进行设置。

参数 类型 必选 说明
model str 语音合成模型
- cosyvoice-v3-flash
- cosyvoice-v3-plus
- cosyvoice-v2
voice str 音色
系统音色:longanyang、longyingjing_v3等
复刻音色:通过声音复刻功能定制
format enum 音频编码格式及采样率
默认:22.05kHz,mp3格式
支持格式:WAV、MP3、PCM、OPUS
支持采样率:8kHz、16kHz、22.05kHz、24kHz、44.1kHz、48kHz
volume int 音量,范围[0, 100],默认50
speech_rate float 语速,范围[0.5, 2.0],默认1.0
pitch_rate float 音高,范围[0.5, 2.0],默认1.0
bit_rate int 音频码率(单位kbps),范围[6, 510],默认32
仅opus格式支持
通过additional_params设置
word_timestamp_enabled bool 是否开启字级别时间戳,默认False
通过additional_params设置
seed int 随机数种子,范围[0, 65535],默认0
language_hints list[str] 语言提示
- zh:中文
- en:英文
- fr:法语
- de:德语
- ja:日语
- ko:韩语
- ru:俄语
instruction str 设置指令
- 指定小语种:"你会用德语说出来。"
- 指定方言:"请用广东话表达。"
- 指定情感、场景等
enable_aigc_tag bool 是否添加AIGC隐性标识,默认False
通过additional_params设置
aigc_propagator str AIGC标识中的传播者字段
通过additional_params设置
aigc_propagate_id str AIGC标识中的传播ID字段
通过additional_params设置
callback ResultCallback 回调接口

音频格式示例

# WAV格式
AudioFormat.WAV_22050HZ_MONO_16BIT

# MP3格式
AudioFormat.MP3_22050HZ_MONO_256KBPS

# PCM格式
AudioFormat.PCM_22050HZ_MONO_16BIT

# OPUS格式
AudioFormat.OGG_OPUS_16KHZ_MONO_32KBPS

设置额外参数示例

# 设置码率
synthesizer = SpeechSynthesizer(
    model="cosyvoice-v3-flash",
    voice="longanyang",
    format=AudioFormat.OGG_OPUS_16KHZ_MONO_16KBPS,
    additional_params={"bit_rate": 32}
)

# 开启字级别时间戳
synthesizer = SpeechSynthesizer(
    model="cosyvoice-v3-flash",
    voice="longyingjing_v3",
    callback=callback,
    additional_params={'word_timestamp_enabled': True}
)

# 开启AIGC标识
synthesizer = SpeechSynthesizer(
    model="cosyvoice-v3-flash",
    voice="longanyang",
    additional_params={
        "enable_aigc_tag": True,
        "aigc_propagator": "xxxx",
        "aigc_propagate_id": "xxxx"
    }
)

关键接口

SpeechSynthesizer类方法

方法 参数 返回值 描述
call(text, timeout_millis) text:待合成文本
timeout_millis:超时时间(毫秒)
二进制音频数据或None 将整段文本转换为语音
未指定callback:返回音频数据
指定callback:返回None,通过回调获取
streaming_call(text) text:待合成文本片段 流式发送待合成文本
可多次调用,分片提交
streaming_complete(complete_timeout_millis) complete_timeout_millis:等待时间(毫秒) 结束流式语音合成
双向流式调用时必须调用
get_last_request_id() request_id 获取上一个任务的request_id
get_first_package_delay() 首包延迟(毫秒) 获取首包延迟
get_response() JSON数据 获取最后一次报文

ResultCallback回调接口

方法 参数 描述
on_open() 连接建立时回调
on_event(message) message:服务端返回的信息 服务有回复时回调
on_complete() 语音合成完成时回调
on_error(message) message:异常信息 发生异常时回调
on_data(data) data:二进制音频数据 有合成音频返回时回调
on_close() 连接关闭时回调

字级别时间戳示例

import dashscope
from dashscope.audio.tts_v2 import *
import json
from datetime import datetime

def get_timestamp():
    now = datetime.now()
    return now.strftime("[%Y-%m-%d %H:%M:%S.%f]")

class Callback(ResultCallback):
    def on_open(self):
        self.file = open("output.mp3", "wb")
        print("连接建立:" + get_timestamp())

    def on_complete(self):
        print("语音合成完成:" + get_timestamp())

    def on_error(self, message: str):
        print(f"语音合成出现异常:{message}")

    def on_close(self):
        print("连接关闭:" + get_timestamp())
        self.file.close()

    def on_event(self, message):
        json_data = json.loads(message)
        if json_data['payload'] and json_data['payload']['output'] and json_data['payload']['output']['sentence']:
            sentence = json_data['payload']['output']['sentence']
            print(f'sentence: {sentence}')
            words = sentence['words']
            if words:
                for word in words:
                    print(f'word: {word}')
                    # 示例值:word: {'text': '今', 'begin_index': 0, 'end_index': 1, 'begin_time': 80, 'end_time': 200}

    def on_data(self, data: bytes) -> None:
        print(get_timestamp() + " 二进制音频长度为:" + str(len(data)))
        self.file.write(data)

callback = Callback()

# 实例化SpeechSynthesizer,开启字级别时间戳
synthesizer = SpeechSynthesizer(
    model="cosyvoice-v3-flash",
    voice="longyingjing_v3",
    callback=callback,
    additional_params={'word_timestamp_enabled': True}
)

# 发送待合成文本
synthesizer.call("今天天气怎么样?")

print(f'[Metric] requestId为:{synthesizer.get_last_request_id()},首包延迟为:{synthesizer.get_first_package_delay()}毫秒')

文本限制与格式规范

文本长度限制

调用方式 单次限制 累计限制
call方法 不超过2000字符 -
streaming_call方法 每次不超过2000字符 累计不超过20万字符

字符计算规则

  • 编码格式:UTF-8
  • 计费字符:实际合成的有效字符数

数学表达式支持

cosyvoice-v2、cosyvoice-v3-flash和cosyvoice-v3-plus模型支持识别中小学常见的数学表达式。

SSML标记语言支持

cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型支持SSML功能,可用于:

  • 指定发音
  • 控制语速、音量、音高
  • 插入停顿
  • 等等

重要提示

  1. 双向流式调用必须调用streaming_complete():否则可能导致结尾部分的文本无法成功转换为语音
  2. 每次调用call方法前需要重新初始化SpeechSynthesizer实例
  3. 流式播放器选择:mp3/opus等压缩格式需使用流式播放器(如ffmpeg、pyaudio、AudioFormat、MediaSource)
  4. 首包延迟:首次发送文本时需建立WebSocket连接,因此首包延迟会包含连接建立的耗时
  5. 音频格式一致性:确保请求参数中设置的音频格式与文件后缀一致
  6. 复刻音色使用:使用复刻音色时,model参数必须与创建该音色时所用的模型版本完全一致

常见问题

Q:如何获取每次合成的文本长度?

A: 在回调接口的on_event方法中解析message,获取characters参数:

def on_event(self, message):
    json_data = json.loads(message)
    if 'payload' in json_data and 'usage' in json_data['payload']:
        characters = json_data['payload']['usage']['characters']
        print(f'计费字符数:{characters}')

Q:为什么音频无法播放?

A: 请检查:

  1. 音频格式与文件后缀是否一致
  2. 播放器是否支持该音频格式和采样率
  3. 流式播放是否使用了支持流式的播放器

Q:为什么音频播放卡顿?

A: 请检查:

  1. 文本发送速度是否合理
  2. 回调函数是否有耗时操作
  3. 网络连接是否稳定

Q:为什么没有返回语音?

A: 双向流式调用时,请确保调用了streaming_complete()方法。

Q:SSL证书校验失败如何处理?

A: 安装系统根证书:

# CentOS/RHEL
sudo yum install -y ca-certificates
sudo update-ca-trust enable

在代码中添加:

import os
os.environ["SSL_CERT_FILE"] = "/etc/ssl/certs/ca-bundle.crt"

高并发场景优化

CosyVoice 语音合成服务基于 WebSocket 协议,以支持流式实时通信。然而,在高并发场景下,为每个请求独立创建和销毁 WebSocket 连接会产生巨大的网络与系统资源开销,并引入显著的连接延迟。为优化性能并确保稳定性,DashScope SDK 内置了高效的资源复用机制(如连接池与对象池)。

Python SDK:对象池优化

Python SDK 通过 SpeechSynthesizerObjectPool 实现对象池优化,用于管理和复用 SpeechSynthesizer 对象。

对象池在初始化时会立即创建指定数量的 SpeechSynthesizer 实例并预先建立 WebSocket 连接。从池中获取对象时无需等待连接建立,可直接发起请求,有效降低首包延迟。当任务完成并将对象归还到对象池后,其 WebSocket 连接不会关闭,而是保持活跃状态等待下次任务复用。

前提条件

  • Python SDK:版本≥1.25.2(建议安装最新版)
  • 安装依赖:pip install -U dashscope

实现步骤

1. 创建并配置对象池

对象池大小需要通过 SpeechSynthesizerObjectPool 进行设置。推荐值:峰值并发数的 1.5 至 2 倍。对象池大小不应超过您账户的 QPS(每秒查询率)限制。

from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool

# 创建全局单例固定大小对象池
# 对象池在初始化时会立即创建指定数量的 SpeechSynthesizer 对象并建立 WebSocket 连接
synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20)

2. 从对象池中获取 SpeechSynthesizer 对象

如果当前未归还的对象数量已超过对象池的最大容量,系统会额外创建一个新的 SpeechSynthesizer 对象。此类新创建的对象需要重新进行初始化并建立 WebSocket 连接,无法利用对象池的既有连接资源,因此不具备复用效果。

speech_synthesizer = synthesizer_object_pool.borrow_synthesizer(
    model='cosyvoice-v3-flash',
    voice='longanyang',
    seed=12382,
    callback=synthesizer_callback
)

3. 进行语音合成

调用 SpeechSynthesizer 对象的 call 或 streaming_call 方法进行语音合成。

4. 归还 SpeechSynthesizer 对象

语音合成任务结束后,归还 SpeechSynthesizer 对象,以便后续任务可以复用该对象。

注意:不要归还未完成任务或任务失败的对象。

synthesizer_object_pool.return_synthesizer(speech_synthesizer)

完整代码示例

import os
import time
import threading
import dashscope
from dashscope.audio.tts_v2 import *

USE_CONNECTION_POOL = True

text_to_synthesize = [
    '第一句、欢迎使用阿里巴巴语音合成服务。',
    '第二句、欢迎使用阿里巴巴语音合成服务。',
    '第三句、欢迎使用阿里巴巴语音合成服务。',
]

connectionPool = None

if USE_CONNECTION_POOL:
    print('creating connection pool')
    start_time = time.time() * 1000
    connectionPool = SpeechSynthesizerObjectPool(max_size=3)
    end_time = time.time() * 1000
    print('connection pool created, cost: {} ms'.format(end_time - start_time))

def init_dashscope_api_key():
    if 'DASHSCOPE_API_KEY' in os.environ:
        dashscope.api_key = os.environ['DASHSCOPE_API_KEY']
    else:
        dashscope.api_key = '<your-dashscope-api-key>'

def synthesis_text_to_speech_and_play_by_streaming_mode(text, task_id):
    global USE_CONNECTION_POOL, connectionPool
    
    complete_event = threading.Event()
    
    class Callback(ResultCallback):
        def on_open(self):
            self.file = open(f'result_{task_id}.mp3', 'wb')
            print(f'[task_{task_id}] start')
        
        def on_complete(self):
            print(f'[task_{task_id}] speech synthesis task complete successfully.')
            complete_event.set()
        
        def on_error(self, message: str):
            print(f'[task_{task_id}] speech synthesis task failed, {message}')
        
        def on_close(self):
            print(f'[task_{task_id}] finished')
        
        def on_event(self, message):
            pass
        
        def on_data(self, data: bytes) -> None:
            self.file.write(data)
    
    synthesizer_callback = Callback()
    
    if USE_CONNECTION_POOL:
        speech_synthesizer = connectionPool.borrow_synthesizer(
            model='cosyvoice-v3-flash',
            voice='longanyang',
            seed=12382,
            callback=synthesizer_callback
        )
    else:
        speech_synthesizer = SpeechSynthesizer(
            model='cosyvoice-v3-flash',
            voice='longanyang',
            seed=12382,
            callback=synthesizer_callback
        )
    
    try:
        speech_synthesizer.call(text)
    except Exception as e:
        print(f'[task_{task_id}] speech synthesis task failed, {e}')
        if USE_CONNECTION_POOL:
            speech_synthesizer.close()
        return
    
    print('[task_{}] Synthesized text: {}'.format(task_id, text))
    complete_event.wait()
    
    print('[task_{}][Metric] requestId: {}, first package delay ms: {}'.format(
        task_id,
        speech_synthesizer.get_last_request_id(),
        speech_synthesizer.get_first_package_delay()
    ))
    
    if USE_CONNECTION_POOL:
        connectionPool.return_synthesizer(speech_synthesizer)

if __name__ == '__main__':
    init_dashscope_api_key()
    
    task_thread_list = []
    for task_id in range(3):
        thread = threading.Thread(
            target=synthesis_text_to_speech_and_play_by_streaming_mode,
            args=(text_to_synthesize[task_id], task_id)
        )
        task_thread_list.append(thread)
    
    for task_thread in task_thread_list:
        task_thread.start()
    
    for task_thread in task_thread_list:
        task_thread.join()
    
    if USE_CONNECTION_POOL:
        connectionPool.shutdown()

资源管理与异常处理

任务成功:

当语音合成任务正常完成时,必须调用 connectionPool.return_synthesizer(speech_synthesizer)SpeechSynthesizer 对象归还到池中,以便复用。

注意:不要归还未完成任务或任务失败的 SpeechSynthesizer 对象。

任务失败:

当 SDK 内部或业务逻辑抛出异常导致任务中断时,主动关闭底层的 WebSocket 连接:

speech_synthesizer.close()

关闭对象池:

在所有语音合成任务完成后,要通过如下方式关闭对象池:

connectionPool.shutdown()

注意:在服务出现 TaskFailed 报错时,不需要额外处理。

Java SDK:连接池与对象池优化

Java SDK 通过内置的连接池和自定义的对象池协同工作,实现最佳性能。

  • 连接池:SDK 内部集成的 OkHttp3 连接池,负责管理和复用底层的 WebSocket 连接,减少网络握手开销。此功能默认开启。
  • 对象池:基于 commons-pool2 实现,用于维护一组已预先建立好连接的 SpeechSynthesizer 对象。从池中获取对象可消除连接建立的延迟,显著降低首包延迟。

前提条件

  • Java SDK:版本≥2.16.6(建议安装最新版)

实现步骤

1. 添加依赖

根据项目构建工具,在依赖配置文件中添加 dashscope-sdk-java 和 commons-pool2。

Maven 配置:

pom.xml 文件的 <dependencies> 标签内添加:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dashscope-sdk-java</artifactId>
    <!-- 请将 'the-latest-version' 替换为2.16.6及以上版本 -->
    <version>the-latest-version</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <!-- 请将 'the-latest-version' 替换为最新版本 -->
    <version>the-latest-version</version>
</dependency>

保存后使用 Maven 命令更新项目依赖:mvn clean installmvn compile

Gradle 配置:

build.gradle 文件的 dependencies 块内添加:

dependencies {
    // 请将 'the-latest-version' 替换为2.16.6及以上版本
    implementation group: 'com.alibaba', name: 'dashscope-sdk-java', version: 'the-latest-version'
    // 请将 'the-latest-version' 替换为最新版本
    implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version'
}

保存后执行 Gradle 命令更新项目依赖:./gradlew build --refresh-dependencies(Windows 系统使用 gradlew build --refresh-dependencies

2. 配置连接池

通过环境变量配置连接池关键参数:

环境变量 描述
DASHSCOPE_CONNECTION_POOL_SIZE 连接池大小
推荐值:峰值并发数的 2 倍以上
默认值:32
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS 最大异步请求数
推荐值:与 DASHSCOPE_CONNECTION_POOL_SIZE 保持一致
默认值:32
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST 单主机最大异步请求数
推荐值:与 DASHSCOPE_CONNECTION_POOL_SIZE 保持一致
默认值:32

3. 配置对象池

通过环境变量配置对象池大小:

环境变量 描述
COSYVOICE_OBJECTPOOL_SIZE 对象池大小
推荐值:峰值并发数的 1.5 至 2 倍
默认值:500

重要提示:

  • 对象池的大小(COSYVOICE_OBJECTPOOL_SIZE)必须小于或等于连接池的大小(DASHSCOPE_CONNECTION_POOL_SIZE)。否则,当对象池请求对象时,若连接池已满,会导致调用线程阻塞,等待可用连接。
  • 对象池大小不应超过您账户的 QPS(每秒查询率)限制。

4. 创建对象池

class CosyvoiceObjectPool {
    public static GenericObjectPool<SpeechSynthesizer> synthesizerPool;
    public static String COSYVOICE_OBJECTPOOL_SIZE_ENV = "COSYVOICE_OBJECTPOOL_SIZE";
    public static int DEFAULT_OBJECT_POOL_SIZE = 500;
    private static Lock lock = new java.util.concurrent.locks.ReentrantLock();
    
    public static int getObjectivePoolSize() {
        try {
            Integer n = Integer.parseInt(System.getenv(COSYVOICE_OBJECTPOOL_SIZE_ENV));
            System.out.println("Using Object Pool Size In Env: "+ n);
            return n;
        } catch (NumberFormatException e) {
            System.out.println("Using Default Object Pool Size: "+ DEFAULT_OBJECT_POOL_SIZE);
            return DEFAULT_OBJECT_POOL_SIZE;
        }
    }
    
    public static GenericObjectPool<SpeechSynthesizer> getInstance() {
        lock.lock();
        if (synthesizerPool == null) {
            int objectPoolSize = getObjectivePoolSize();
            SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory = 
                new SpeechSynthesizerObjectFactory();
            GenericObjectPoolConfig<SpeechSynthesizer> config = 
                new GenericObjectPoolConfig<>();
            config.setMaxTotal(objectPoolSize);
            config.setMaxIdle(objectPoolSize);
            config.setMinIdle(objectPoolSize);
            synthesizerPool = 
                new GenericObjectPool<>(speechSynthesizerObjectFactory, config);
        }
        lock.unlock();
        return synthesizerPool;
    }
}

5. 从对象池中获取对象

synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();

6. 进行语音合成

调用 SpeechSynthesizer 对象的 call 或 streamingCall 方法进行语音合成。

7. 归还对象

CosyvoiceObjectPool.getInstance().returnObject(synthesizer);

推荐配置

以下配置基于在指定规格的阿里云服务器上仅运行 CosyVoice 语音合成服务的测试结果。过高的并发数可能导致任务处理延迟。

其中单机并发数指的是同一时刻正在运行的 CosyVoice 语音合成任务数,也可以理解为工作线程数。

机器配置(阿里云) 单机最大并发数 对象池大小 连接池大小
4核8GiB 100 500 2000
8核16GiB 150 500 2000
16核32GiB 200 500 2000

资源管理与异常处理

任务成功:

当语音合成任务正常完成时,必须调用 GenericObjectPool 的 returnObject 方法将 SpeechSynthesizer 对象归还到池中,以便复用。

CosyvoiceObjectPool.getInstance().returnObject(synthesizer);

注意:不要归还未完成任务或任务失败的 SpeechSynthesizer 对象。

任务失败:

当 SDK 内部或业务逻辑抛出异常导致任务中断时,必须执行以下两个操作:

  1. 主动关闭底层的 WebSocket 连接
  2. 从对象池中废弃该对象,防止被再次使用

    // 关闭连接
    synthesizer.getDuplexApi().close(1000, "bye");
    // 在对象池中废弃出现异常的synthesizer
    CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
    

注意:在服务出现 TaskFailed 报错时,不需要额外处理。

Java SDK 常见异常

异常 1:业务流量平稳,但是服务器 TCP 连接数持续上升

出错原因:

类型一:

每一个 SDK 对象创建时都会申请一个连接。如果没有使用对象池,每一次任务结束后对象都被析构。此时这一个连接将进入无引用状态,需要等待 61s 秒后服务端报错连接超时才会真正断开,这会导致这个连接在 61 秒内不可复用。

在高并发场景下,新的任务在发现没有可复用连接时会创建新连接,会造成如下后果:

  • 连接数持续上升
  • 由于连接数过多,服务器资源不足,服务器卡顿
  • 连接池被打满、新任务由于启动时需要等待可用连接而阻塞

类型二:

对象池配置的 MaxIdle 小于 MaxTotal,导致在对象闲置时,超过 MaxIdle 的对象被销毁,从而造成连接泄漏。泄漏的连接需要等待 61 秒超时后断连,同类型一造成连接数持续上升。

解决方法:

  • 对于类型一,使用对象池解决
  • 对于类型二,检查对象池配置参数,设置 MaxIdle 和 MaxTotal 相等,关闭对象池自动销毁策略解决

异常 2:任务耗时比正常调用多 60 秒

同"异常 1",连接池已经达到最大连接限制,新的任务需要等待无引用状态的连接 61 秒触发超时后才可以获得连接。

异常 3:服务启动时任务慢,之后慢慢恢复正常

出错原因:

在高并发调用时,同一个对象会复用同一个 WebSocket 连接,因此 WebSocket 连接只会在服务启动时创建。需要注意的是,任务启动阶段如果立刻开始较高并发调用,同时创建过多的 WebSocket 连接会导致阻塞。

解决方法:

启动服务后逐步提升并发量,或增加预热任务。

异常 4:服务端报错 Invalid action('run-task')! Please follow the protocol!

出错原因:

这是由于出现了客户端报错后,服务端不知道客户端出错,连接处于任务中状态。此时连接和对象被复用并开启下一个任务,导致流程错误,下一个任务失败。

解决方法:

在抛出异常后主动关闭 WebSocket 连接后归还对象池。

异常 5:业务流量平稳,调用量出现异常尖刺

出错原因:

同时创建过多 WebSocket 连接导致阻塞,但业务流量持续打进来,导致任务短时间积压,并且在阻塞后所有积压任务立刻调用。这会造成调用量尖刺,并且有可能造成瞬时超过账号的并发数限制导致部分任务失败、服务器卡顿等。

这种瞬间创建过多 WebSocket 的情况多发生于:

  • 服务启动阶段
  • 网络出现异常,大量 WebSocket 连接同时中断重连
  • 某一时刻出现大量服务端报错,导致大量 WebSocket 重连。常见报错如并发数超过账号限制("Requests rate limit exceeded, please try again later.")

解决方法:

  • 检查网络情况
  • 排查尖刺前是否出现大量其他服务端报错
  • 提高账号并发限制
  • 调小对象池和连接池大小,通过对象池上限限制最大并发数
  • 提升服务器配置或扩充机器数

异常 6:随着并发数提升,所有任务都变慢

解决方法:

  • 检查是否已经达到网络带宽上限
  • 检查实际并发数是否已经过高

参考文档