实时语音合成-CosyVoice/Sambert

更新时间:
复制为 MD 格式

语音合成,又称文本转语音(Text-to-Speech,TTS),是将文本转换为自然语音的技术。该技术基于机器学习算法,通过学习大量语音样本,掌握语言的韵律、语调和发音规则,从而在接收到文本输入时生成真人般自然的语音内容。

核心功能

  • 实时生成高保真语音,支持中英等多语种自然发声

  • 提供声音复刻与声音设计两种音色定制方式

  • 支持流式输入输出,低延迟响应实时交互场景

  • 可调节语速、语调、音量与码率,精细控制语音表现

  • 兼容主流音频格式,最高支持48kHz采样率输出

适用范围

支持的模型:

中国内地

中国内地部署模式下,接入点与数据存储均位于北京地域,模型推理计算资源仅限于中国内地。

调用以下模型时,请选择北京地域的API Key

  • CosyVoice:cosyvoice-v3.5-plus、cosyvoice-v3.5-flash、cosyvoice-v3-plus、cosyvoice-v3-flash、cosyvoice-v2、cosyvoice-v1

  • Sambert:sambert-zhinan-v1、sambert-zhiqi-v1、sambert-zhichu-v1、sambert-zhide-v1、sambert-zhijia-v1、sambert-zhiru-v1、sambert-zhiqian-v1、sambert-zhixiang-v1、sambert-zhiwei-v1、sambert-zhihao-v1、sambert-zhijing-v1、sambert-zhiming-v1、sambert-zhimo-v1、sambert-zhina-v1、sambert-zhishu-v1、sambert-zhistella-v1、sambert-zhiting-v1、sambert-zhixiao-v1、sambert-zhiya-v1、sambert-zhiye-v1、sambert-zhiying-v1、sambert-zhiyuan-v1、sambert-zhiyue-v1、sambert-zhigui-v1、sambert-zhishuo-v1、sambert-zhimiao-emo-v1、sambert-zhimao-v1、sambert-zhilun-v1、sambert-zhifei-v1、sambert-zhida-v1、sambert-camila-v1、sambert-perla-v1、sambert-indah-v1、sambert-clara-v1、sambert-hanna-v1、sambert-beth-v1、sambert-betty-v1、sambert-cally-v1、sambert-cindy-v1、sambert-eva-v1、sambert-donna-v1、sambert-brian-v1、sambert-waan-v1,详情请参见Sambert模型列表

国际

国际部署模式下,接入点与数据存储均位于新加坡地域,模型推理计算资源在全球范围内动态调度(不含中国内地)。

调用以下模型时,请选择新加坡地域的API Key

  • CosyVoice:cosyvoice-v3-plus、cosyvoice-v3-flash

更多信息请参见模型列表

模型选型

场景

推荐模型

理由

注意事项

品牌形象、专属声音、扩展系统音色等语音定制(基于文本描述)

cosyvoice-v3.5-plus

支持声音设计,无需音频样本,通过文本描述创建定制化音色,适合从零开始设计品牌专属声音

cosyvoice-v3.5-flash 仅在北京地域可用,且不支持系统音色

品牌形象、专属声音、扩展系统音色等语音定制(基于音频样本)

cosyvoice-v3.5-plus

支持声音复刻,基于真实音频样本快速复刻音色,打造拟人化品牌声纹,确保音色高度还原与一致性

cosyvoice-v3.5-flash 仅在北京地域可用,且不支持系统音色

智能客服 / 语音助手

cosyvoice-v3-flash、cosyvoice-v3.5-flash

plus模型成本低,支持流式交互、情感表达,响应快,性价比高

cosyvoice-v3.5-flash 仅在北京地域可用,且仅支持声音复刻模式

移动端嵌入式语音合成

CosyVoice全系列

SDK全覆盖,资源优化好,流式支持强,延迟可控

cosyvoice-v1不支持 SSML

方言广播系统

cosyvoice-v3-flash、cosyvoice-v3.5-plus

支持东北话、闽南语等多种方言,适合地方内容播报

cosyvoice-v3.5-plus仅在北京地域可用,且仅支持声音复刻模式

教育类应用(含公式朗读)

cosyvoice-v2、cosyvoice-v3-flash、cosyvoice-v3-plus

支持LaTeX公式转语音,适合数理化课程讲解

cosyvoice-v2cosyvoice-v3-plus成本较高(2元/万字符,cosyvoice-v2不支持设置情感

结构化语音播报(新闻/公告)

cosyvoice-v3-plus、cosyvoice-v3-flash、cosyvoice-v2

支持SSML控制语速、停顿、发音等,提升播报专业度

需额外开发 SSML 生成逻辑,不支持设置情感

语音与文本精准对齐(如字幕生成、教学回放、听写训练)

cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2/Sambert

支持时间戳输出,可实现合成语音与原文同步

需显式启用时间戳功能,默认关闭,cosyvoice-v2不支持设置情感,Sambert不支持流式输入

多语言出海产品

cosyvoice-v3-flash、cosyvoice-v3-plus、Sambert

支持多语种

Sambert不支持流式输入,价格高于cosyvoice-v3-flash

不同地域和模型的能力存在差异,请仔细阅读模型功能特性对比后再选择使用

快速开始

下面是调用API的示例代码。更多常用场景的代码示例,请参见GitHub

您需要已获取API Key配置API Key到环境变量。如果通过SDK调用,还需要安装DashScope SDK

CosyVoice

重要

cosyvoice-v3.5-plus 和 cosyvoice-v3.5-flash 模型目前仅在北京地域可用,且专门用于声音设计和声音复刻场景(无系统音色)。在使用它们进行语音合成之前,请先参见CosyVoice声音复刻API创建目标音色。创建完成后,只需将代码中的 voice 字段更新为您的音色 ID,并将 model 字段指定为对应模型,即可正常运行。

使用系统音色进行语音合成

以下示例演示如何使用系统音色(参见音色列表)进行语音合成。

将合成音频保存为文件

Python

# coding=utf-8

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

# 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
# 若没有配置环境变量,请用百炼API Key将下行替换为:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
dashscope.base_websocket_api_url='wss://dashscope.aliyuncs.com/api-ws/v1/inference'

# 模型
# 不同模型版本需要使用对应版本的音色:
# cosyvoice-v3-flash/cosyvoice-v3-plus:使用longanyang等音色。
# cosyvoice-v2:使用longxiaochun_v2等音色。
# 每个音色支持的语言不同,合成日语、韩语等非中文语言时,需选择支持对应语言的音色。详见CosyVoice音色列表。
model = "cosyvoice-v3-flash"
# 音色
voice = "longanyang"

# 实例化SpeechSynthesizer,并在构造方法中传入模型(model)、音色(voice)等请求参数
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# 发送待合成文本,获取二进制音频
audio = synthesizer.call("今天天气怎么样?")
# 首次发送文本时需建立 WebSocket 连接,因此首包延迟会包含连接建立的耗时
print('[Metric] requestId为:{},首包延迟为:{}毫秒'.format(
    synthesizer.get_last_request_id(),
    synthesizer.get_first_package_delay()))

# 将音频保存至本地
with open('output.mp3', 'wb') as f:
    f.write(audio)

Java

import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.utils.Constants;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

public class Main {
    // 模型
    // 不同模型版本需要使用对应版本的音色:
    // cosyvoice-v3-flash/cosyvoice-v3-plus:使用longanyang等音色。
    // cosyvoice-v2:使用longxiaochun_v2等音色。
    // 每个音色支持的语言不同,合成日语、韩语等非中文语言时,需选择支持对应语言的音色。详见CosyVoice音色列表。
    private static String model = "cosyvoice-v3-flash";
    // 音色
    private static String voice = "longanyang";

    public static void streamAudioDataToSpeaker() {
        // 请求参数
        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
                        // 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model) // 模型
                        .voice(voice) // 音色
                        .build();

        // 同步模式:禁用回调(第二个参数为null)
        SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
        ByteBuffer audio = null;
        try {
            // 阻塞直至音频返回
            audio = synthesizer.call("今天天气怎么样?");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 任务结束关闭websocket连接
            synthesizer.getDuplexApi().close(1000, "bye");
        }
        if (audio != null) {
            // 将音频数据保存到本地文件“output.mp3”中
            File file = new File("output.mp3");
            // 首次发送文本时需建立 WebSocket 连接,因此首包延迟会包含连接建立的耗时
            System.out.println(
                    "[Metric] requestId为:"
                            + synthesizer.getLastRequestId()
                            + "首包延迟(毫秒)为:"
                            + synthesizer.getFirstPackageDelay());
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(audio.array());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        // 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
        Constants.baseWebsocketApiUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference";
        streamAudioDataToSpeaker();
        System.exit(0);
    }
}

LLM生成的文本实时转成语音并通过扬声器播放

以下代码展示通过本地设备播放千问大语言模型(qwen-turbo)实时返回的文本内容。

Python

运行Python示例前,需要通过pip安装第三方音频播放库。

# coding=utf-8
# Installation instructions for pyaudio:
# APPLE Mac OS X
#   brew install portaudio
#   pip install pyaudio
# Debian/Ubuntu
#   sudo apt-get install python-pyaudio python3-pyaudio
#   or
#   pip install pyaudio
# CentOS
#   sudo yum install -y portaudio portaudio-devel && pip install pyaudio
# Microsoft Windows
#   python -m pip install pyaudio

import os
import pyaudio
import dashscope
from dashscope.audio.tts_v2 import *


from http import HTTPStatus
from dashscope import Generation

# 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
# 若没有配置环境变量,请用百炼API Key将下行替换为:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
dashscope.base_websocket_api_url='wss://dashscope.aliyuncs.com/api-ws/v1/inference'

# 不同模型版本需要使用对应版本的音色:
# cosyvoice-v3-flash/cosyvoice-v3-plus:使用longanyang等音色。
# cosyvoice-v2:使用longxiaochun_v2等音色。
# 每个音色支持的语言不同,合成日语、韩语等非中文语言时,需选择支持对应语言的音色。详见CosyVoice音色列表。
model = "cosyvoice-v3-flash"
voice = "longanyang"


class Callback(ResultCallback):
    _player = None
    _stream = None

    def on_open(self):
        print("websocket is open.")
        self._player = pyaudio.PyAudio()
        self._stream = self._player.open(
            format=pyaudio.paInt16, channels=1, rate=22050, output=True
        )

    def on_complete(self):
        print("speech synthesis task complete successfully.")

    def on_error(self, message: str):
        print(f"speech synthesis task failed, {message}")

    def on_close(self):
        print("websocket is closed.")
        # stop player
        self._stream.stop_stream()
        self._stream.close()
        self._player.terminate()

    def on_event(self, message):
        print(f"recv speech synthsis message {message}")

    def on_data(self, data: bytes) -> None:
        print("audio result length:", len(data))
        self._stream.write(data)


def synthesizer_with_llm():
    callback = Callback()
    synthesizer = SpeechSynthesizer(
        model=model,
        voice=voice,
        format=AudioFormat.PCM_22050HZ_MONO_16BIT,
        callback=callback,
    )

    messages = [{"role": "user", "content": "请介绍一下你自己"}]
    responses = Generation.call(
        model="qwen-turbo",
        messages=messages,
        result_format="message",  # set result format as 'message'
        stream=True,  # enable stream output
        incremental_output=True,  # enable incremental output 
    )
    for response in responses:
        if response.status_code == HTTPStatus.OK:
            print(response.output.choices[0]["message"]["content"], end="")
            synthesizer.streaming_call(response.output.choices[0]["message"]["content"])
        else:
            print(
                "Request id: %s, Status code: %s, error code: %s, error message: %s"
                % (
                    response.request_id,
                    response.status_code,
                    response.code,
                    response.message,
                )
            )
    synthesizer.streaming_complete()
    print('requestId: ', synthesizer.get_last_request_id())


if __name__ == "__main__":
    synthesizer_with_llm()

Java

import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.Constants;
import io.reactivex.Flowable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.*;

public class Main {
    // 不同模型版本需要使用对应版本的音色:
    // cosyvoice-v3-flash/cosyvoice-v3-plus:使用longanyang等音色。
    // cosyvoice-v2:使用longxiaochun_v2等音色。
    // 每个音色支持的语言不同,合成日语、韩语等非中文语言时,需选择支持对应语言的音色。详见CosyVoice音色列表。
    private static String model = "cosyvoice-v3-flash";
    private static String voice = "longanyang";
    public static void process() throws NoApiKeyException, InputRequiredException {
        // Playback thread
        class PlaybackRunnable implements Runnable {
            // Set the audio format. Please configure according to your actual device,
            // synthesized audio parameters, and platform choice Here it is set to
            // 22050Hz16bit single channel. It is recommended that customers choose other
            // sample rates and formats based on the model sample rate and device
            // compatibility.
            private AudioFormat af = new AudioFormat(22050, 16, 1, true, false);
            private DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
            private SourceDataLine targetSource = null;
            private AtomicBoolean runFlag = new AtomicBoolean(true);
            private ConcurrentLinkedQueue<ByteBuffer> queue =
                    new ConcurrentLinkedQueue<>();

            // Prepare the player
            public void prepare() throws LineUnavailableException {
                targetSource = (SourceDataLine) AudioSystem.getLine(info);
                targetSource.open(af, 4096);
                targetSource.start();
            }

            public void put(ByteBuffer buffer) {
                queue.add(buffer);
            }

            // Stop playback
            public void stop() {
                runFlag.set(false);
            }

            @Override
            public void run() {
                if (targetSource == null) {
                    return;
                }

                while (runFlag.get()) {
                    if (queue.isEmpty()) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }
                        continue;
                    }

                    ByteBuffer buffer = queue.poll();
                    if (buffer == null) {
                        continue;
                    }

                    byte[] data = buffer.array();
                    targetSource.write(data, 0, data.length);
                }

                // Play all remaining cache
                if (!queue.isEmpty()) {
                    ByteBuffer buffer = null;
                    while ((buffer = queue.poll()) != null) {
                        byte[] data = buffer.array();
                        targetSource.write(data, 0, data.length);
                    }
                }
                // Release the player
                targetSource.drain();
                targetSource.stop();
                targetSource.close();
            }
        }

        // Create a subclass inheriting from ResultCallback<SpeechSynthesisResult>
        // to implement the callback interface
        class ReactCallback extends ResultCallback<SpeechSynthesisResult> {
            private PlaybackRunnable playbackRunnable = null;

            public ReactCallback(PlaybackRunnable playbackRunnable) {
                this.playbackRunnable = playbackRunnable;
            }

            // Callback when the service side returns the streaming synthesis result
            @Override
            public void onEvent(SpeechSynthesisResult result) {
                // Get the binary data of the streaming result via getAudio
                if (result.getAudioFrame() != null) {
                    // Stream the data to the player
                    playbackRunnable.put(result.getAudioFrame());
                }
            }

            // Callback when the service side completes the synthesis
            @Override
            public void onComplete() {
                // Notify the playback thread to end
                playbackRunnable.stop();
            }

            // Callback when an error occurs
            @Override
            public void onError(Exception e) {
                // Tell the playback thread to end
                System.out.println(e);
                playbackRunnable.stop();
            }
        }

        PlaybackRunnable playbackRunnable = new PlaybackRunnable();
        try {
            playbackRunnable.prepare();
        } catch (LineUnavailableException e) {
            throw new RuntimeException(e);
        }
        Thread playbackThread = new Thread(playbackRunnable);
        // Start the playback thread
        playbackThread.start();
        /*******  Call the Generative AI Model to get streaming text *******/
        // Prepare for the LLM call
        Generation gen = new Generation();
        Message userMsg = Message.builder()
                .role(Role.USER.getValue())
                .content("请介绍一下你自己")
                .build();
        GenerationParam genParam =
                GenerationParam.builder()
                        // 若没有将API Key配置到环境变量中,需将下面这行代码注释放开,并将apiKey替换为自己的API Key
                        // .apiKey("apikey")
                        .model("qwen-turbo")
                        .messages(Arrays.asList(userMsg))
                        .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                        .topP(0.8)
                        .incrementalOutput(true)
                        .build();
        // Prepare the speech synthesis task
        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
                        // 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model)
                        .voice(voice)
                        .format(SpeechSynthesisAudioFormat
                                .PCM_22050HZ_MONO_16BIT)
                        .build();
        SpeechSynthesizer synthesizer =
                new SpeechSynthesizer(param, new ReactCallback(playbackRunnable));
        Flowable<GenerationResult> result = gen.streamCall(genParam);
        result.blockingForEach(message -> {
            String text =
                    message.getOutput().getChoices().get(0).getMessage().getContent().trim();
            if (text != null && !text.isEmpty()) {
                System.out.println("LLM output:" + text);
                synthesizer.streamingCall(text);
            }
        });
        synthesizer.streamingComplete();
        System.out.print("requestId: " + synthesizer.getLastRequestId());
        try {
            // Wait for the playback thread to finish playing all
            playbackThread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws NoApiKeyException, InputRequiredException {
        // 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
        Constants.baseWebsocketApiUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference";
        process();
        System.exit(0);
    }
}

使用声音复刻音色进行语音合成

image

声音复刻与语音合成是紧密关联的两个独立步骤,遵循“先创建,后使用”的流程:

  1. 准备录音文件

    将符合音频要求的音频文件上传至公网可访问的位置,如阿里云对象存储OSS,并确保URL可公开访问。

  2. 创建音色

    调用创建音色接口。此步骤必须指定target_model/targetModel,声明创建的音色将由哪个语音合成模型驱动。

    若已有创建好的音色(调用查询音色列表接口查看),可跳过这一步直接进行下一步。

  3. 使用音色进行语音合成

    使用创建音色接口创建音色成功后,系统会返回一个voice_id/voiceID

    • voice_id/voiceID 可直接作为语音合成接口或各语言 SDK 中的 voice 参数使用,用于后续的文本转语音。

    • 支持多种调用形态,包括非流式、单向流式以及双向流式合成。

    • 合成时指定的语音合成模型必须与创建音色时的 target_model/targetModel 保持一致,否则合成会失败。

示例代码:

import os
import time
import dashscope
from dashscope.audio.tts_v2 import VoiceEnrollmentService, SpeechSynthesizer

# 1. 环境准备
# 推荐通过环境变量配置API Key
# 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
# 若没有配置环境变量,请用百炼API Key将下行替换为:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
if not dashscope.api_key:
    raise ValueError("DASHSCOPE_API_KEY environment variable not set.")

# 以下为北京地域WebSocket url,若使用新加坡地域的模型,需将url替换为:wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
dashscope.base_websocket_api_url='wss://dashscope.aliyuncs.com/api-ws/v1/inference'
# 以下为北京地域HTTP url,若使用新加坡地域的模型,需将url替换为:https://dashscope-intl.aliyuncs.com/api/v1
dashscope.base_http_api_url = 'https://dashscope.aliyuncs.com/api/v1'


# 2. 定义复刻参数
TARGET_MODEL = "cosyvoice-v3.5-plus" 
# 为音色起一个有意义的前缀
VOICE_PREFIX = "myvoice" # 仅允许数字和小写字母,小于十个字符
# 公网可访问音频URL
AUDIO_URL = "https://dashscope.oss-cn-beijing.aliyuncs.com/samples/audio/cosyvoice/cosyvoice-zeroshot-sample.wav" # 示例URL,请替换为自己的

# 3. 创建音色 (异步任务)
print("--- Step 1: Creating voice enrollment ---")
service = VoiceEnrollmentService()
try:
    voice_id = service.create_voice(
        target_model=TARGET_MODEL,
        prefix=VOICE_PREFIX,
        url=AUDIO_URL
    )
    print(f"Voice enrollment submitted successfully. Request ID: {service.get_last_request_id()}")
    print(f"Generated Voice ID: {voice_id}")
except Exception as e:
    print(f"Error during voice creation: {e}")
    raise e
# 4. 轮询查询音色状态
print("\n--- Step 2: Polling for voice status ---")
max_attempts = 30
poll_interval = 10 # 秒
for attempt in range(max_attempts):
    try:
        voice_info = service.query_voice(voice_id=voice_id)
        status = voice_info.get("status")
        print(f"Attempt {attempt + 1}/{max_attempts}: Voice status is '{status}'")
        
        if status == "OK":
            print("Voice is ready for synthesis.")
            break
        elif status == "UNDEPLOYED":
            print(f"Voice processing failed with status: {status}. Please check audio quality or contact support.")
            raise RuntimeError(f"Voice processing failed with status: {status}")
        # 对于 "DEPLOYING" 等中间状态,继续等待
        time.sleep(poll_interval)
    except Exception as e:
        print(f"Error during status polling: {e}")
        time.sleep(poll_interval)
else:
    print("Polling timed out. The voice is not ready after several attempts.")
    raise RuntimeError("Polling timed out. The voice is not ready after several attempts.")

# 5. 使用复刻音色进行语音合成
print("\n--- Step 3: Synthesizing speech with the new voice ---")
try:
    synthesizer = SpeechSynthesizer(model=TARGET_MODEL, voice=voice_id)
    text_to_synthesize = "恭喜,已成功复刻并合成了属于自己的声音!"
    
    # call()方法返回二进制音频数据
    audio_data = synthesizer.call(text_to_synthesize)
    print(f"Speech synthesis successful. Request ID: {synthesizer.get_last_request_id()}")

    # 6. 保存音频文件
    output_file = "my_custom_voice_output.mp3"
    with open(output_file, "wb") as f:
        f.write(audio_data)
    print(f"Audio saved to {output_file}")

except Exception as e:
    print(f"Error during speech synthesis: {e}")

使用声音设计音色进行语音合成

image

声音设计与语音合成是紧密关联的两个独立步骤,遵循“先创建,后使用”的流程:

  1. 准备声音设计所需的声音描述与试听文本。

    • 声音描述(voice_prompt):定义目标音色的特征(关于如何编写请参见“CosyVoice声音复刻API”)。

    • 试听文本(preview_text):目标音色生成的预览音频朗读的内容(如“大家好,欢迎收听”)。

  2. 调用创建音色接口,创建一个专属音色,获取音色名和预览音频。

    此步骤必须指定target_model,声明创建的音色将由哪个语音合成模型驱动

    试听获取预览音频来判断是否符合预期;若符合要求,继续下一步,否则,重新设计。

    若已有创建好的音色(调用查询音色列表接口查看),可跳过这一步直接进行下一步。

  3. 使用音色进行语音合成

    使用创建音色接口创建音色成功后,系统会返回一个voice_id/voiceID

    • voice_id/voiceID 可直接作为语音合成接口或各语言 SDK 中的 voice 参数使用,用于后续的文本转语音。

    • 支持多种调用形态,包括非流式、单向流式以及双向流式合成。

    • 合成时指定的语音合成模型必须与创建音色时的 target_model/targetModel 保持一致,否则合成会失败。

示例代码:

  1. 生成专属音色并试听效果,若对效果满意,进行下一步;否则重新生成。

    Python

    import requests
    import base64
    import os
    
    def create_voice_and_play():
        # 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
        # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key = "sk-xxx"
        api_key = os.getenv("DASHSCOPE_API_KEY")
        
        if not api_key:
            print("错误: 未找到DASHSCOPE_API_KEY环境变量,请先设置API Key")
            return None, None, None
        
        # 准备请求数据
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        
        data = {
            "model": "voice-enrollment",
            "input": {
                "action": "create_voice",
                "target_model": "cosyvoice-v3.5-plus",
                "voice_prompt": "沉稳的中年男性播音员,音色低沉浑厚,富有磁性,语速平稳,吐字清晰,适合用于新闻播报或纪录片解说。",
                "preview_text": "各位听众朋友,大家好,欢迎收听晚间新闻。",
                "prefix": "announcer"
            },
            "parameters": {
                "sample_rate": 24000,
                "response_format": "wav"
            }
        }
        
        # 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:https://dashscope-intl.aliyuncs.com/api/v1/services/audio/tts/customization
        url = "https://dashscope.aliyuncs.com/api/v1/services/audio/tts/customization"
        
        try:
            # 发送请求
            response = requests.post(
                url,
                headers=headers,
                json=data,
                timeout=60  # 添加超时设置
            )
            
            if response.status_code == 200:
                result = response.json()
                
                # 获取音色ID
                voice_id = result["output"]["voice_id"]
                print(f"音色ID: {voice_id}")
                
                # 获取预览音频数据
                base64_audio = result["output"]["preview_audio"]["data"]
                
                # 解码Base64音频数据
                audio_bytes = base64.b64decode(base64_audio)
                
                # 保存音频文件到本地
                filename = f"{voice_id}_preview.wav"
                
                # 将音频数据写入本地文件
                with open(filename, 'wb') as f:
                    f.write(audio_bytes)
                
                print(f"音频已保存到本地文件: {filename}")
                print(f"文件路径: {os.path.abspath(filename)}")
                
                return voice_id, audio_bytes, filename
            else:
                print(f"请求失败,状态码: {response.status_code}")
                print(f"响应内容: {response.text}")
                return None, None, None
                
        except requests.exceptions.RequestException as e:
            print(f"网络请求发生错误: {e}")
            return None, None, None
        except KeyError as e:
            print(f"响应数据格式错误,缺少必要的字段: {e}")
            print(f"响应内容: {response.text if 'response' in locals() else 'No response'}")
            return None, None, None
        except Exception as e:
            print(f"发生未知错误: {e}")
            return None, None, None
    
    if __name__ == "__main__":
        print("开始创建语音...")
        voice_id, audio_data, saved_filename = create_voice_and_play()
        
        if voice_id:
            print(f"\n成功创建音色 '{voice_id}'")
            print(f"音频文件已保存: '{saved_filename}'")
            print(f"文件大小: {os.path.getsize(saved_filename)} 字节")
        else:
            print("\n音色创建失败")

    Java

    需要导入Gson依赖,若是使用Maven或者Gradle,添加依赖方式如下:

    Maven

    pom.xml中添加如下内容:

    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.13.1</version>
    </dependency>

    Gradle

    build.gradle中添加如下内容:

    // https://mvnrepository.com/artifact/com.google.code.gson/gson
    implementation("com.google.code.gson:gson:2.13.1")
    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.Base64;
    
    public class Main {
        public static void main(String[] args) {
            Main example = new Main();
            example.createVoice();
        }
    
        public void createVoice() {
            // 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
            // 若没有配置环境变量,请用百炼API Key将下行替换为:String apiKey = "sk-xxx"
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
    
            // 创建JSON请求体字符串
            String jsonBody = "{\n" +
                    "    \"model\": \"voice-enrollment\",\n" +
                    "    \"input\": {\n" +
                    "        \"action\": \"create_voice\",\n" +
                    "        \"target_model\": \"cosyvoice-v3.5-plus\",\n" +
                    "        \"voice_prompt\": \"沉稳的中年男性播音员,音色低沉浑厚,富有磁性,语速平稳,吐字清晰,适合用于新闻播报或纪录片解说。\",\n" +
                    "        \"preview_text\": \"各位听众朋友,大家好,欢迎收听晚间新闻。\",\n" +
                    "        \"prefix\": \"announcer\"\n" +
                    "    },\n" +
                    "    \"parameters\": {\n" +
                    "        \"sample_rate\": 24000,\n" +
                    "        \"response_format\": \"wav\"\n" +
                    "    }\n" +
                    "}";
    
            HttpURLConnection connection = null;
            try {
                // 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:https://dashscope-intl.aliyuncs.com/api/v1/services/audio/tts/customization
                URL url = new URL("https://dashscope.aliyuncs.com/api/v1/services/audio/tts/customization");
                connection = (HttpURLConnection) url.openConnection();
    
                // 设置请求方法和头部
                connection.setRequestMethod("POST");
                connection.setRequestProperty("Authorization", "Bearer " + apiKey);
                connection.setRequestProperty("Content-Type", "application/json");
                connection.setDoOutput(true);
                connection.setDoInput(true);
    
                // 发送请求体
                try (OutputStream os = connection.getOutputStream()) {
                    byte[] input = jsonBody.getBytes("UTF-8");
                    os.write(input, 0, input.length);
                    os.flush();
                }
    
                // 获取响应
                int responseCode = connection.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    // 读取响应内容
                    StringBuilder response = new StringBuilder();
                    try (BufferedReader br = new BufferedReader(
                            new InputStreamReader(connection.getInputStream(), "UTF-8"))) {
                        String responseLine;
                        while ((responseLine = br.readLine()) != null) {
                            response.append(responseLine.trim());
                        }
                    }
    
                    // 解析JSON响应
                    JsonObject jsonResponse = JsonParser.parseString(response.toString()).getAsJsonObject();
                    JsonObject outputObj = jsonResponse.getAsJsonObject("output");
                    JsonObject previewAudioObj = outputObj.getAsJsonObject("preview_audio");
    
                    // 获取音色名称
                    String voiceId = outputObj.get("voice_id").getAsString();
                    System.out.println("音色ID: " + voiceId);
    
                    // 获取Base64编码的音频数据
                    String base64Audio = previewAudioObj.get("data").getAsString();
    
                    // 解码Base64音频数据
                    byte[] audioBytes = Base64.getDecoder().decode(base64Audio);
    
                    // 保存音频到本地文件
                    String filename = voiceId + "_preview.wav";
                    saveAudioToFile(audioBytes, filename);
    
                    System.out.println("音频已保存到本地文件: " + filename);
    
                } else {
                    // 读取错误响应
                    StringBuilder errorResponse = new StringBuilder();
                    try (BufferedReader br = new BufferedReader(
                            new InputStreamReader(connection.getErrorStream(), "UTF-8"))) {
                        String responseLine;
                        while ((responseLine = br.readLine()) != null) {
                            errorResponse.append(responseLine.trim());
                        }
                    }
    
                    System.out.println("请求失败,状态码: " + responseCode);
                    System.out.println("错误响应: " + errorResponse.toString());
                }
    
            } catch (Exception e) {
                System.err.println("请求发生错误: " + e.getMessage());
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }
    
        private void saveAudioToFile(byte[] audioBytes, String filename) {
            try {
                File file = new File(filename);
                try (FileOutputStream fos = new FileOutputStream(file)) {
                    fos.write(audioBytes);
                }
                System.out.println("音频已保存到: " + file.getAbsolutePath());
            } catch (IOException e) {
                System.err.println("保存音频文件时发生错误: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }
  2. 使用上一步生成的专属音色进行语音合成。

    这里参考了非流式调用示例代码,将voice参数替换为声音设计生成的专属音色进行语音合成。

    关键原则:声音设计时使用的模型 (target_model) 必须与后续进行语音合成时使用的模型 (model) 保持一致,否则会导致合成失败。

    Python

    # coding=utf-8
    
    import dashscope
    from dashscope.audio.tts_v2 import *
    import os
    
    # 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
    # 若没有配置环境变量,请用百炼API Key将下行替换为:dashscope.api_key = "sk-xxx"
    dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
    
    # 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
    dashscope.base_websocket_api_url='wss://dashscope.aliyuncs.com/api-ws/v1/inference'
    
    # 声音设计、语音合成要使用相同的模型
    model = "cosyvoice-v3.5-plus"
    # 将voice参数替换为声音设计生成的专属音色
    voice = "your_voice"
    
    # 实例化SpeechSynthesizer,并在构造方法中传入模型(model)、音色(voice)等请求参数
    synthesizer = SpeechSynthesizer(model=model, voice=voice)
    # 发送待合成文本,获取二进制音频
    audio = synthesizer.call("今天天气怎么样?")
    # 首次发送文本时需建立 WebSocket 连接,因此首包延迟会包含连接建立的耗时
    print('[Metric] requestId为:{},首包延迟为:{}毫秒'.format(
        synthesizer.get_last_request_id(),
        synthesizer.get_first_package_delay()))
    
    # 将音频保存至本地
    with open('output.mp3', 'wb') as f:
        f.write(audio)

    Java

    import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
    import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
    import com.alibaba.dashscope.utils.Constants;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    
    public class Main {
        // 声音设计、语音合成要使用相同的模型
        private static String model = "cosyvoice-v3.5-plus";
        // 将voice参数替换为声音设计生成的专属音色
        private static String voice = "your_voice_id";
    
        public static void streamAudioDataToSpeaker() {
            // 请求参数
            SpeechSynthesisParam param =
                    SpeechSynthesisParam.builder()
                            // 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
                            // 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
                            .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                            .model(model) // 模型
                            .voice(voice) // 音色
                            .build();
    
            // 同步模式:禁用回调(第二个参数为null)
            SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
            ByteBuffer audio = null;
            try {
                // 阻塞直至音频返回
                audio = synthesizer.call("今天天气怎么样?");
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                // 任务结束关闭websocket连接
                synthesizer.getDuplexApi().close(1000, "bye");
            }
            if (audio != null) {
                // 将音频数据保存到本地文件“output.mp3”中
                File file = new File("output.mp3");
                // 首次发送文本时需建立 WebSocket 连接,因此首包延迟会包含连接建立的耗时
                System.out.println(
                        "[Metric] requestId为:"
                                + synthesizer.getLastRequestId()
                                + "首包延迟(毫秒)为:"
                                + synthesizer.getFirstPackageDelay());
                try (FileOutputStream fos = new FileOutputStream(file)) {
                    fos.write(audio.array());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        public static void main(String[] args) {
            // 以下为北京地域url,若使用新加坡地域的模型,需将url替换为:wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
            Constants.baseWebsocketApiUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference";
            streamAudioDataToSpeaker();
            System.exit(0);
        }
    }

Sambert

将合成音频保存为文件

import dashscope
from dashscope.audio.tts import SpeechSynthesizer

# 若没有将API Key配置到环境变量中,需将下面这行代码注释放开,并将apiKey替换为自己的API Key
# dashscope.api_key = "apiKey"
result = SpeechSynthesizer.call(model='sambert-zhichu-v1',
                                # 当text内容的语种发生变化时,请确认model是否匹配。不同model支持不同的语种,详情请参见Sambert音色列表中的“语言”列。
                                text='今天天气怎么样',
                                sample_rate=48000,
                                format='wav')
print('requestId: ', result.get_response()['request_id'])
if result.get_audio_data() is not None:
    with open('output.wav', 'wb') as f:
        f.write(result.get_audio_data())
print(' get response: %s' % (result.get_response()))

import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.common.Status;

import java.io.*;
import java.nio.ByteBuffer;

public class Main {

    public static void SyncAudioDataToFile() {
        SpeechSynthesizer synthesizer = new SpeechSynthesizer();
        SpeechSynthesisParam param = SpeechSynthesisParam.builder()
          // 若没有将API Key配置到环境变量中,需将下面这行代码注释放开,并将apiKey替换为自己的API Key
          // .apiKey(apikey)
          .model("sambert-zhichu-v1")
          // 当text内容的语种发生变化时,请确认model是否匹配。不同model支持不同的语种,详情请参见Sambert音色列表中的“语言”列。
          .text("今天天气怎么样")
          .sampleRate(48000)
          .format(SpeechSynthesisAudioFormat.WAV)
          .build();

        File file = new File("output.wav");
        // 调用call方法,传入param参数,获取合成音频
        ByteBuffer audio = synthesizer.call(param);
        System.out.println("requestId: " + synthesizer.getLastRequestId());
        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(audio.array());
            System.out.println("synthesis done!");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        SyncAudioDataToFile();
        System.exit(0);
    }
}

将合成的音频通过扬声器播放

合成语音后,通过本地设备播放实时返回的音频内容。

运行Python示例前,需要通过pip安装第三方音频播放库。

# coding=utf-8
#
# Installation instructions for pyaudio:
# APPLE Mac OS X
#   brew install portaudio
#   pip install pyaudio
# Debian/Ubuntu
#   sudo apt-get install python-pyaudio python3-pyaudio
#   or
#   pip install pyaudio
# CentOS
#   sudo yum install -y portaudio portaudio-devel && pip install pyaudio
# Microsoft Windows
#   python -m pip install pyaudio

import dashscope
import sys
import pyaudio
from dashscope.api_entities.dashscope_response import SpeechSynthesisResponse
from dashscope.audio.tts import ResultCallback, SpeechSynthesizer, SpeechSynthesisResult

# 若没有将API Key配置到环境变量中,需将下面这行代码注释放开,并将apiKey替换为自己的API Key
# dashscope.api_key = "apiKey"

class Callback(ResultCallback):
    _player = None
    _stream = None

    def on_open(self):
        print('Speech synthesizer is opened.')
        self._player = pyaudio.PyAudio()
        self._stream = self._player.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=48000,
            output=True)

    def on_complete(self):
        print('Speech synthesizer is completed.')

    def on_error(self, response: SpeechSynthesisResponse):
        print('Speech synthesizer failed, response is %s' % (str(response)))

    def on_close(self):
        print('Speech synthesizer is closed.')
        self._stream.stop_stream()
        self._stream.close()
        self._player.terminate()

    def on_event(self, result: SpeechSynthesisResult):
        if result.get_audio_frame() is not None:
            print('audio result length:', sys.getsizeof(result.get_audio_frame()))
            self._stream.write(result.get_audio_frame())

        if result.get_timestamp() is not None:
            print('timestamp result:', str(result.get_timestamp()))

callback = Callback()
result = SpeechSynthesizer.call(model='sambert-zhichu-v1',
                       text='今天天气怎么样',
                       sample_rate=48000,
                       format='pcm',
                       callback=callback)
print('requestId: ', result.get_response()['request_id'])
import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.common.ResultCallback;

import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.sound.sampled.*;

public class Main {

    public static void StreamAuidoDataToSpeaker() {
        CountDownLatch latch = new CountDownLatch(1);
        SpeechSynthesizer synthesizer = new SpeechSynthesizer();
        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // 若没有将API Key配置到环境变量中,需将下面这行代码注释放开,并将apiKey替换为自己的API Key
                        // .apiKey("apikey")
                        .text("今天天气怎么样")
                        .model("sambert-zhichu-v1")
                        .sampleRate(48000)
                        .format(SpeechSynthesisAudioFormat.PCM) // 流式合成使用PCM或者MP3
                        .build();

        // 播放线程
        class PlaybackRunnable implements Runnable {
            // 设置音频格式,请根据实际自身设备,合成音频参数和平台选择配置
            // 这里选择48k16bit单通道,建议客户根据选用的模型采样率情况和自身设备兼容性选择其他采样率和格式
            private AudioFormat af = new AudioFormat(48000, 16, 1, true, false);
            private DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
            private SourceDataLine targetSource = null;
            private AtomicBoolean runFlag = new AtomicBoolean(true);
            private ConcurrentLinkedQueue<ByteBuffer> queue = new ConcurrentLinkedQueue<>();

            // 准备播放器
            public void prepare() throws LineUnavailableException {
                targetSource = (SourceDataLine) AudioSystem.getLine(info);
                targetSource.open(af, 4096);
                targetSource.start();
            }

            public void put(ByteBuffer buffer) {
                queue.add(buffer);
            }

            // 停止播放
            public void stop() {
                runFlag.set(false);
            }

            @Override
            public void run() {
                if (targetSource == null) {
                    return;
                }

                while (runFlag.get()) {
                    if (queue.isEmpty()) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {

                        }
                        continue;
                    }

                    ByteBuffer buffer = queue.poll();
                    if (buffer == null) {
                        continue;
                    }

                    byte[] data = buffer.array();
                    targetSource.write(data, 0, data.length);
                }

                // 将缓存全部播放完
                if (!queue.isEmpty()) {
                    ByteBuffer buffer = null;
                    while ((buffer = queue.poll()) != null) {
                        byte[] data = buffer.array();
                        targetSource.write(data, 0, data.length);
                    }
                }

                // 释放播放器
                targetSource.drain();
                targetSource.stop();
                targetSource.close();
            }
        }

        // 创建一个继承自ResultCallback<SpeechSynthesisResult>的子类来实现回调接口
        class ReactCallback extends ResultCallback<SpeechSynthesisResult> {
            private PlaybackRunnable playbackRunnable = null;

            public ReactCallback(PlaybackRunnable playbackRunnable) {
                this.playbackRunnable = playbackRunnable;
            }

            // 当服务侧返回流式合成结果后回调
            @Override
            public void onEvent(SpeechSynthesisResult result) {
                // 通过getAudio获取流式结果二进制数据
                if (result.getAudioFrame() != null) {
                    // 将数据流式推给播放器
                    playbackRunnable.put(result.getAudioFrame());
                }
            }

            // 当服务侧完成合成后回调
            @Override
            public void onComplete() {
                // 告知播放线程结束
                playbackRunnable.stop();
                latch.countDown();
            }

            // 当出现错误时回调
            @Override
            public void onError(Exception e) {
                // 告诉播放线程结束
                System.out.println(e);
                playbackRunnable.stop();
                latch.countDown();
            }
        }

        PlaybackRunnable playbackRunnable = new PlaybackRunnable();
        try {
            playbackRunnable.prepare();
        } catch (LineUnavailableException e) {
            throw new RuntimeException(e);
        }
        Thread playbackThread = new Thread(playbackRunnable);
        // 启动播放线程
        playbackThread.start();
        // 带Callbackcall方法将不会阻塞当前线程
        synthesizer.call(param, new ReactCallback(playbackRunnable));
        System.out.println("requestId: " + synthesizer.getLastRequestId());
        // 等待合成完成
        try {
            latch.await();
            // 等待播放线程全部播放完
            playbackThread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        StreamAuidoDataToSpeaker();
        System.exit(0);
    }
}

声音复刻:输入音频格式

高质量的输入音频是获得优质复刻效果的基础。

项目

要求

支持格式

WAV (16bit), MP3, M4A

音频时长

推荐10~20秒,最长不得超过60

文件大小

≤ 10 MB

采样率

≥ 16 kHz

声道

单声道 / 双声道,双声道音频仅处理首声道,请确保首声道包含有效人声

内容

音频必须包含至少5秒连续清晰朗读(无背景音),其余部分仅允许短暂停顿(≤2秒);整段音频应避免背景音乐、噪音或其他人声,确保核心朗读内容质量;请使用正常说话音频作为输入,不要上传歌曲或唱歌音频,以确保复刻效果准确和可用。

声音设计:如何编写高质量的声音描述?

要求与限制

在编写声音描述(voice_prompt)时,请务必遵循以下技术约束:

  • 长度限制:voice_prompt 的内容长度不得超过 500 个字符。

  • 支持语言:描述文本仅支持中文和英文。

核心原则

高质量的声音描述(voice_prompt)是成功创建理想音色的关键。它如同声音设计的“蓝图”,直接指导模型生成具有特定特征的声音。

请遵循以下核心原则对声音进行描述:

  1. 具体而非模糊:使用能够描绘具体声音特质的词语,如“低沉”、“清脆”、“语速偏快”。避免使用“好听”、“普通”等主观且缺乏信息量的词汇。

  2. 多维而非单一:优秀的描述通常结合多个维度(如下文所述的性别、年龄、情感等)。单一维度的描述(如仅“女声”)过于宽泛,难以生成特色鲜明的音色。

  3. 客观而非主观:专注于声音本身的物理和感知特征,而不是个人的喜好。例如,用“音调偏高,带有活力”代替“我最喜欢的声音”。

  4. 原创而非模仿:请描述声音的特质,而不是要求模仿特定人物(如名人、演员)。此类请求涉及版权风险且模型不支持直接模仿。

  5. 简洁而非冗余:确保每个词都有其意义。避免重复使用同义词或无意义的强调词(如“非常非常棒的声音”)。

描述维度参考

维度

描述示例

性别

男性、女性、中性

年龄

儿童 (5-12岁)、青少年 (13-18岁)、青年 (19-35岁)、中年 (36-55岁)、老年 (55岁以上)

音调

高音、中音、低音、偏高、偏低

语速

快速、中速、缓慢、偏快、偏慢

情感

开朗、沉稳、温柔、严肃、活泼、冷静、治愈

特点

有磁性、清脆、沙哑、圆润、甜美、浑厚、有力

用途

新闻播报、广告配音、有声书、动画角色、语音助手、纪录片解说

示例对比

✅ 推荐示例

  • “年轻活泼的女性声音,语速较快,带有明显的上扬语调,适合介绍时尚产品。”

    分析:结合了年龄、性格、语速和语调,并指明了适用场景,形象立体。

  • “沉稳的中年男性,语速缓慢,音色低沉有磁性,适合朗读新闻或纪录片解说。”

    分析:清晰定义了性别、年龄段、语速、音色特点和应用领域。

  • “可爱的儿童声音,大约8岁女孩,说话略带稚气,适合动画角色配音。”

    分析:精确到具体年龄和声音特质(稚气),目标明确。

  • “温柔知性的女性,30岁左右,语调平和,适合有声书朗读。”

    分析:通过“知性”、“平和”等词汇,有效传递了声音的情感和风格。

❌ 不推荐示例与改进建议

不推荐示例

主要问题

改进建议

好听的声音

过于模糊,主观性强,缺乏可执行的特征。

添加具体维度,如:“声线清澈的青年女声,语调温柔”。

像某明星的声音

涉及版权风险,模型无法直接模仿。

提取其声音特质进行描述,如:“声音成熟、富有磁性、语速沉稳的男声”。

非常非常非常好听的女声

信息冗余,重复词汇无助于定义音色。

移除重复词,并增加有效描述,如:“一个20~24岁,语气轻快、音调活泼、音色甜美的女声”。

123456

无效输入,无法解析为声音特征。

请提供有意义的文本描述,参考上方的推荐示例。

API参考

模型应用上架及备案

参见应用合规备案

模型功能特性对比

中国内地

中国内地部署模式下,接入点与数据存储均位于北京地域,模型推理计算资源仅限于中国内地。

功能/特性

cosyvoice-v3.5-plus

cosyvoice-v3.5-flash

cosyvoice-v3-plus

cosyvoice-v3-flash

cosyvoice-v2

cosyvoice-v1

Sambert

支持语言

无系统音色复刻音色支持如下语言:中文(普通话、广东话、东北话、甘肃话、贵州话、河南话、湖北话、江西话、闽南话、宁夏话、山西话、陕西话、山东话、上海话、四川话、天津话、云南话)、英文、法语、德语、日语、韩语、俄语、葡萄牙语、泰语、印尼语、越南语

声音设计音色支持如下语言:中文、英文

系统音色(因音色而异):中文(普通话、东北话、闽南话、陕西话)、英文、日语、韩语

复刻音色:中文(普通话)、英文、法语、德语、日语、韩语、俄语

系统音色(因音色而异):中文(普通话)、英文

复刻音色:中文(普通话、广东话、东北话、甘肃话、贵州话、河南话、湖北话、江西话、闽南话、宁夏话、山西话、陕西话、山东话、上海话、四川话、天津话、云南话)、英文、法语、德语、日语、韩语、俄语、葡萄牙语、泰语、印尼语、越南语

系统音色(因音色而异):中文(普通话)、英文、韩语、日语

复刻音色:中文(普通话)、英文

系统音色(因音色而异):中文(普通话)、英文

复刻音色:中文(普通话)、英文

音色而异:中文、英文、美式英文、意大利语、西班牙语、印尼语、法语、德语、泰语

音频格式

pcm、wav、mp3、opus

pcm、wav、mp3

音频采样率

8kHz、16kHz、22.05kHz、24kHz、44.1kHz、48kHz

16kHz、48kHz

声音复刻

支持

使用方法请参见CosyVoice声音复刻API
声音复刻支持的语言如下:
cosyvoice-v1、cosyvoice-v2:中文(普通话)、英文
cosyvoice-v3.5-plus、cosyvoice-v3.5-flash、cosyvoice-v3-flash:中文(普通话、广东话、东北话、甘肃话、贵州话、河南话、湖北话、江西话、闽南话、宁夏话、山西话、陕西话、山东话、上海话、四川话、天津话、云南话)、英文、法语、德语、日语、韩语、俄语、葡萄牙语、泰语、印尼语、越南语
cosyvoice-v3-plus:中文(普通话)、英文、法语、德语、日语、韩语、俄语

不支持

声音设计

支持

使用方法请参见CosyVoice声音复刻API
声音复刻支持的语言:中文、英文

不支持

SSML

支持

该功能适用于复刻音色,以及音色列表中已标记为支持 SSML 的系统音色
使用方法请参见SSML标记语言介绍

不支持

支持

LaTeX

支持

使用方法请参见LaTeX 公式转语音

不支持

音量调节

支持

使用方法请参见请求参数volume

语速调节

支持

使用方法请参见请求参数speech_rate
在 Java SDK 中,该参数为speechRate

语调(音高)调节

支持

使用方法请参见请求参数pitch_rate
在 Java SDK 中,该参数为pitchRate

码率调节

支持

opus格式音频支持该功能
使用方法请参见请求参数bit_rate
在 Java SDK 中,该参数为pitchRate

不支持

时间戳

支持 默认关闭,可开启

该功能适用于复刻音色,以及音色列表中已标记为支持时间戳的系统音色
使用方法请参见请求参数word_timestamp_enabled
在 Java SDK 中,该参数为enableWordTimestamp

不支持

支持 默认关闭,可开启

指令控制(Instruct)

支持

该功能适用于复刻音色,以及音色列表中已标记为支持 Instruct 的系统音色
使用方法请参见请求参数instruction

不支持

支持

该功能适用于复刻音色,以及音色列表中已标记为支持 Instruct 的系统音色
使用方法请参见请求参数instruction

不支持

流式输入

支持

不支持

流式输出

支持

限流(RPS)

3

20

接入方式

Java/Python/Android/iOS SDK、WebSocket API

价格

1.5元/万字符

0.8元/万字符

2元/万字符

1元/万字符

2元/万字符

1元/万字符

国际

国际部署模式下,接入点与数据存储均位于新加坡地域,模型推理计算资源在全球范围内动态调度(不含中国内地)。

功能/特性

cosyvoice-v3-plus

cosyvoice-v3-flash

支持语言

系统音色而异:中文(普通话、东北话、闽南话、陕西话)、英文、日语、韩语

系统音色而异:中文(普通话)、英文

音频格式

pcm、wav、mp3、opus

音频采样率

8kHz、16kHz、22.05kHz、24kHz、44.1kHz、48kHz

声音复刻

不支持

声音设计

不支持

SSML

支持

该功能适用于复刻音色,以及音色列表中已标记为支持 SSML 的系统音色
使用方法请参见SSML标记语言介绍

LaTeX

支持

使用方法请参见LaTeX 公式转语音

音量调节

支持

使用方法请参见请求参数volume

语速调节

支持

使用方法请参见请求参数speech_rate
在 Java SDK 中,该参数为speechRate

语调(音高)调节

支持

使用方法请参见请求参数pitch_rate
在 Java SDK 中,该参数为pitchRate

码率调节

支持

opus格式音频支持该功能
使用方法请参见请求参数bit_rate
在 Java SDK 中,该参数为pitchRate

时间戳

支持 默认关闭,可开启

该功能适用于复刻音色,以及音色列表中已标记为支持时间戳的系统音色
使用方法请参见请求参数word_timestamp_enabled
在 Java SDK 中,该参数为enableWordTimestamp

指令控制(Instruct)

不支持

支持

该功能适用于音色列表中已标记为支持 Instruct 的系统音色
使用方法请参见请求参数instruction

流式输入

支持

流式输出

支持

限流(RPS)

3

接入方式

Java/Python/Android/iOS SDK、WebSocket API

价格

1.9082/万字符

0.9541/万字符

支持的系统音色

CosyVoice音色列表

Sambert音色列表

常见问题

Q:语音合成的发音读错怎么办?多音字如何控制发音?

  • 将多音字替换成同音的其他汉字,快速解决发音问题。

  • 使用SSML标记语言控制发音:SambertCosyvoice都支持SSML

Q:使用复刻音色生成的音频无声音如何排查?

  1. 确认音色状态

    调用查询指定音色接口,查看音色status是否为OK

  2. 检查模型版本一致性

    确保复刻音色时使用的target_model参数与语音合成时的model参数完全一致。例如:

    • 复刻时使用cosyvoice-v3-plus

    • 合成时也必须使用cosyvoice-v3-plus

  3. 验证源音频质量

    检查复刻音色时使用的源音频是否符合音频要求

    • 音频时长:10-20

    • 音质清晰

    • 无背景噪音

  4. 检查请求参数

    确认语音合成时请求参数voice设置为复刻音色的ID。

Q:声音复刻后合成效果不稳定或语音不完整如何处理?

如果复刻音色后合成的语音出现以下问题:

  • 语音播放不完整,只读出部分文字

  • 合成效果不稳定,时好时坏

  • 语音中包含异常停顿或静音段

可能原因:源音频质量不符合要求

解决方案:检查源音频是否符合如下要求,建议按照录音操作指南重新录制

  • 检查音频连续性:确保源音频中语音内容连续,避免长时间停顿或静音段(超过2秒)。如果音频中存在明显的空白段,会导致模型将静音或噪声作为音色特征的一部分,影响生成效果

  • 检查语音活动比例:确保有效语音占音频总时长的60%以上。如果背景噪声、非语音段过多,会干扰音色特征提取

  • 验证音频质量细节:

    • 音频时长:10-20秒(推荐15秒左右)

    • 发音清晰,语速平稳

    • 无背景噪音、回音、杂音

    • 语音能量集中,无长时间静音段