即時語音合成通過 WebSocket 通訊協定將文本即時轉換為自然語音,支援流式輸入與輸出,具備聲音複刻、聲音設計及精細化音頻控制能力,適用於語音助手、有聲讀物、智能客服等情境。
概述
通過 WebSocket 雙向流式協議實現低延遲文字轉換語音。
前提條件
-
已配置 API Key並將其設定到環境變數。
-
如果通過 DashScope SDK 調用,需要安裝最新版SDK。
快速開始
以下是各模型的語音合成樣本。更多樣本和參數說明請參見各模型的API參考。
Qwen-Audio-TTS
以下樣本示範如何使用系統音色進行語音合成。
如需使用指令控制功能,請通過 instruction 參數設定指令。
Python
# coding=utf-8
import os
import dashscope
from dashscope.audio.tts_v2 import *
# 新加坡地區和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 若沒有配置環境變數,請用阿里雲百鍊API Key將下行替換為:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# 以下為新加坡地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'
# 模型
# qwen-audio-3.0-tts-flash/qwen-audio-3.0-tts-plus:使用longanlingxi等音色。
# 每個音色支援的語言不同,合成日語、韓語等非中文語言時,需選擇支援對應語言的音色。詳見音色列表。
model = "qwen-audio-3.0-tts-flash"
# 音色
voice = "longanlingxi"
# 執行個體化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 {
// 模型
// qwen-audio-3.0-tts-flash/qwen-audio-3.0-tts-plus:使用longanlingxi等音色。
// 每個音色支援的語言不同,合成日語、韓語等非中文語言時,需選擇支援對應語言的音色。詳見音色列表。
private static String model = "qwen-audio-3.0-tts-flash";
// 音色
private static String voice = "longanlingxi";
public static void streamAudioDataToSpeaker() {
// 請求參數
SpeechSynthesisParam param =
SpeechSynthesisParam.builder()
// 新加坡地區和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/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 串連,因此首包延遲會包含串連建立的耗時
// 注意:getFirstPackageDelay() 需要 dashscope-sdk-java 2.18.0 及以上版本
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) {
// 以下為新加坡地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
streamAudioDataToSpeaker();
System.exit(0);
}
}CosyVoice
以下樣本示範如何使用系統音色(參見CosyVoice音色列表)進行語音合成。
如需使用指令控制功能,請通過 instruction 參數設定指令。
Python
# coding=utf-8
import os
import dashscope
from dashscope.audio.tts_v2 import *
# 新加坡地區和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 若沒有配置環境變數,請用阿里雲百鍊API Key將下行替換為:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# 以下為新加坡地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.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://www.alibabacloud.com/help/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 串連,因此首包延遲會包含串連建立的耗時
// 注意:getFirstPackageDelay() 需要 dashscope-sdk-java 2.18.0 及以上版本
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) {
// 以下為新加坡地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
streamAudioDataToSpeaker();
System.exit(0);
}
}Qwen-TTS
以下樣本示範如何使用系統音色(參見支援的音色)進行語音合成。
如需使用指令控制功能,請將 model 替換為 qwen3-tts-instruct-flash-realtime,並通過 instructions 參數設定指令。
Python
server commit模式
import os
import base64
import threading
import time
import dashscope
from dashscope.audio.qwen_tts_realtime import *
qwen_tts_realtime: QwenTtsRealtime = None
text_to_synthesize = [
'對吧~我就特別喜歡這種超市,',
'尤其是過年的時候',
'去逛超市',
'就會覺得',
'超級超級開心!',
'想買好多好多的東西呢!'
]
DO_VIDEO_TEST = False
def init_dashscope_api_key():
"""
Set your DashScope API-key. More information:
https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
"""
# 新加坡和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
if 'DASHSCOPE_API_KEY' in os.environ:
dashscope.api_key = os.environ[
'DASHSCOPE_API_KEY'] # load API-key from environment variable DASHSCOPE_API_KEY
else:
dashscope.api_key = 'your-dashscope-api-key' # set API-key manually
class MyCallback(QwenTtsRealtimeCallback):
def __init__(self):
self.complete_event = threading.Event()
self.file = open('result_24k.pcm', 'wb')
def on_open(self) -> None:
print('connection opened, init player')
def on_close(self, close_status_code, close_msg) -> None:
self.file.close()
print('connection closed with code: {}, msg: {}, destroy player'.format(close_status_code, close_msg))
def on_event(self, response: str) -> None:
try:
global qwen_tts_realtime
type = response['type']
if 'session.created' == type:
print('start session: {}'.format(response['session']['id']))
if 'response.audio.delta' == type:
recv_audio_b64 = response['delta']
self.file.write(base64.b64decode(recv_audio_b64))
if 'response.done' == type:
print(f'response {qwen_tts_realtime.get_last_response_id()} done')
if 'session.finished' == type:
print('session finished')
self.complete_event.set()
except Exception as e:
print('[Error] {}'.format(e))
return
def wait_for_finished(self):
self.complete_event.wait()
if __name__ == '__main__':
init_dashscope_api_key()
print('Initializing ...')
callback = MyCallback()
qwen_tts_realtime = QwenTtsRealtime(
# 如需使用指令控制功能,請將model替換為qwen3-tts-instruct-flash-realtime
model='qwen3-tts-flash-realtime',
callback=callback,
# 以下為新加坡地區的配置。
url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
)
qwen_tts_realtime.connect()
qwen_tts_realtime.update_session(
voice = 'Cherry',
response_format = AudioFormat.PCM_24000HZ_MONO_16BIT,
# 如需使用指令控制功能,請取消下方注釋,並將model替換為qwen3-tts-instruct-flash-realtime
# instructions='語速較快,帶有明顯的上揚語調,適合介紹時尚產品。',
# optimize_instructions=True,
mode = 'server_commit'
)
for text_chunk in text_to_synthesize:
print(f'send text: {text_chunk}')
qwen_tts_realtime.append_text(text_chunk)
time.sleep(0.1)
qwen_tts_realtime.finish()
callback.wait_for_finished()
print('[Metric] session: {}, first audio delay: {}'.format(
qwen_tts_realtime.get_session_id(),
qwen_tts_realtime.get_first_audio_delay(),
))
commit模式
import base64
import os
import threading
import dashscope
from dashscope.audio.qwen_tts_realtime import *
qwen_tts_realtime: QwenTtsRealtime = None
text_to_synthesize = [
'這是第一句話。',
'這是第二句話。',
'這是第三句話。',
]
DO_VIDEO_TEST = False
def init_dashscope_api_key():
"""
Set your DashScope API-key. More information:
https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
"""
# 新加坡和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
if 'DASHSCOPE_API_KEY' in os.environ:
dashscope.api_key = os.environ[
'DASHSCOPE_API_KEY'] # load API-key from environment variable DASHSCOPE_API_KEY
else:
dashscope.api_key = 'your-dashscope-api-key' # set API-key manually
class MyCallback(QwenTtsRealtimeCallback):
def __init__(self):
super().__init__()
self.response_counter = 0
self.complete_event = threading.Event()
self.file = open(f'result_{self.response_counter}_24k.pcm', 'wb')
def reset_event(self):
self.response_counter += 1
self.file = open(f'result_{self.response_counter}_24k.pcm', 'wb')
self.complete_event = threading.Event()
def on_open(self) -> None:
print('connection opened, init player')
def on_close(self, close_status_code, close_msg) -> None:
print('connection closed with code: {}, msg: {}, destroy player'.format(close_status_code, close_msg))
def on_event(self, response: str) -> None:
try:
global qwen_tts_realtime
type = response['type']
if 'session.created' == type:
print('start session: {}'.format(response['session']['id']))
if 'response.audio.delta' == type:
recv_audio_b64 = response['delta']
self.file.write(base64.b64decode(recv_audio_b64))
if 'response.done' == type:
print(f'response {qwen_tts_realtime.get_last_response_id()} done')
self.complete_event.set()
self.file.close()
if 'session.finished' == type:
print('session finished')
self.complete_event.set()
except Exception as e:
print('[Error] {}'.format(e))
return
def wait_for_response_done(self):
self.complete_event.wait()
if __name__ == '__main__':
init_dashscope_api_key()
print('Initializing ...')
callback = MyCallback()
qwen_tts_realtime = QwenTtsRealtime(
# 如需使用指令控制功能,請將model替換為qwen3-tts-instruct-flash-realtime
model='qwen3-tts-flash-realtime',
callback=callback,
# 以下為新加坡地區的配置。
url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
)
qwen_tts_realtime.connect()
qwen_tts_realtime.update_session(
voice = 'Cherry',
response_format = AudioFormat.PCM_24000HZ_MONO_16BIT,
# 如需使用指令控制功能,請取消下方注釋,並將model替換為qwen3-tts-instruct-flash-realtime
# instructions='語速較快,帶有明顯的上揚語調,適合介紹時尚產品。',
# optimize_instructions=True,
mode = 'commit'
)
print(f'send text: {text_to_synthesize[0]}')
qwen_tts_realtime.append_text(text_to_synthesize[0])
qwen_tts_realtime.commit()
callback.wait_for_response_done()
callback.reset_event()
print(f'send text: {text_to_synthesize[1]}')
qwen_tts_realtime.append_text(text_to_synthesize[1])
qwen_tts_realtime.commit()
callback.wait_for_response_done()
callback.reset_event()
print(f'send text: {text_to_synthesize[2]}')
qwen_tts_realtime.append_text(text_to_synthesize[2])
qwen_tts_realtime.commit()
callback.wait_for_response_done()
qwen_tts_realtime.finish()
print('[Metric] session: {}, first audio delay: {}'.format(
qwen_tts_realtime.get_session_id(),
qwen_tts_realtime.get_first_audio_delay(),
))
Java
server commit模式
import com.alibaba.dashscope.audio.qwen_tts_realtime.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.AudioSystem;
import java.io.*;
import java.util.Base64;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class Main {
static String[] textToSynthesize = {
"對吧~我就特別喜歡這種超市",
"尤其是過年的時候",
"去逛超市",
"就會覺得",
"超級超級開心!",
"想買好多好多的東西呢!"
};
public static QwenTtsRealtimeAudioFormat ttsFormat = QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT;
// 即時PCM音頻播放器類
public static class RealtimePcmPlayer {
private int sampleRate;
private SourceDataLine line;
private AudioFormat audioFormat;
private Thread decoderThread;
private Thread playerThread;
private AtomicBoolean stopped = new AtomicBoolean(false);
private Queue<String> b64AudioBuffer = new ConcurrentLinkedQueue<>();
private Queue<byte[]> RawAudioBuffer = new ConcurrentLinkedQueue<>();
private ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream();
// 建構函式初始化音頻格式和音頻線路
public RealtimePcmPlayer(int sampleRate) throws LineUnavailableException {
this.sampleRate = sampleRate;
this.audioFormat = new AudioFormat(this.sampleRate, 16, 1, true, false);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
line.start();
decoderThread = new Thread(new Runnable() {
@Override
public void run() {
while (!stopped.get()) {
String b64Audio = b64AudioBuffer.poll();
if (b64Audio != null) {
byte[] rawAudio = Base64.getDecoder().decode(b64Audio);
RawAudioBuffer.add(rawAudio);
// 將音頻資料寫入 totalAudioStream
try {
totalAudioStream.write(rawAudio);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
playerThread = new Thread(new Runnable() {
@Override
public void run() {
while (!stopped.get()) {
byte[] rawAudio = RawAudioBuffer.poll();
if (rawAudio != null) {
try {
playChunk(rawAudio);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
decoderThread.start();
playerThread.start();
}
// 播放一個音頻塊並阻塞直到播放完成
private void playChunk(byte[] chunk) throws IOException, InterruptedException {
if (chunk == null || chunk.length == 0) return;
int bytesWritten = 0;
while (bytesWritten < chunk.length) {
bytesWritten += line.write(chunk, bytesWritten, chunk.length - bytesWritten);
}
int audioLength = chunk.length / (this.sampleRate*2/1000);
// 等待緩衝區中的音頻播放完成
Thread.sleep(audioLength - 10);
}
public void write(String b64Audio) {
b64AudioBuffer.add(b64Audio);
}
public void cancel() {
b64AudioBuffer.clear();
RawAudioBuffer.clear();
}
public void waitForComplete() throws InterruptedException {
while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) {
Thread.sleep(100);
}
line.drain();
}
public void shutdown() throws InterruptedException, IOException {
stopped.set(true);
decoderThread.join();
playerThread.join();
// 儲存完整音頻檔案
File file = new File("TotalAudio_"+ttsFormat.getSampleRate()+"."+ttsFormat.getFormat());
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(totalAudioStream.toByteArray());
}
if (line != null && line.isRunning()) {
line.drain();
line.close();
}
}
}
public static void main(String[] args) throws InterruptedException, LineUnavailableException, IOException {
QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
// 如需使用指令控制功能,請將model替換為qwen3-tts-instruct-flash-realtime
.model("qwen3-tts-flash-realtime")
// 以下為新加坡地區的配置。
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// 新加坡和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
.apikey(System.getenv("DASHSCOPE_API_KEY"))
.build();
AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));
final AtomicReference<QwenTtsRealtime> qwenTtsRef = new AtomicReference<>(null);
// 建立即時音頻播放器執行個體
RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);
QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
@Override
public void onOpen() {
// 串連建立時的處理
}
@Override
public void onEvent(JsonObject message) {
String type = message.get("type").getAsString();
switch(type) {
case "session.created":
// 會話建立時的處理
if (message.has("session")) {
String eventId = message.get("event_id").getAsString();
String sessionId = message.get("session").getAsJsonObject().get("id").getAsString();
System.out.println("[onEvent] session.created, session_id: "
+ sessionId + ", event_id: " + eventId);
}
break;
case "response.audio.delta":
String recvAudioB64 = message.get("delta").getAsString();
// 即時播放音頻
audioPlayer.write(recvAudioB64);
break;
case "response.done":
// 響應完成時的處理
break;
case "session.finished":
// 會話結束時的處理
completeLatch.get().countDown();
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
// 串連關閉時的處理
}
});
qwenTtsRef.set(qwenTtsRealtime);
try {
qwenTtsRealtime.connect();
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
}
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
.voice("Cherry")
.responseFormat(ttsFormat)
.mode("server_commit")
// 如需使用指令控制功能,請取消下方注釋,並將model替換為qwen3-tts-instruct-flash-realtime
// .instructions("")
// .optimizeInstructions(true)
.build();
qwenTtsRealtime.updateSession(config);
for (String text:textToSynthesize) {
qwenTtsRealtime.appendText(text);
Thread.sleep(100);
}
qwenTtsRealtime.finish();
completeLatch.get().await();
qwenTtsRealtime.close();
// 等待音頻播放完成並關閉播放器
audioPlayer.waitForComplete();
audioPlayer.shutdown();
System.exit(0);
}
}
commit模式
import com.alibaba.dashscope.audio.qwen_tts_realtime.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.AudioSystem;
import java.io.*;
import java.util.Base64;
import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class Main {
public static QwenTtsRealtimeAudioFormat ttsFormat = QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT;
// 即時PCM音頻播放器類
public static class RealtimePcmPlayer {
private int sampleRate;
private SourceDataLine line;
private AudioFormat audioFormat;
private Thread decoderThread;
private Thread playerThread;
private AtomicBoolean stopped = new AtomicBoolean(false);
private Queue<String> b64AudioBuffer = new ConcurrentLinkedQueue<>();
private Queue<byte[]> RawAudioBuffer = new ConcurrentLinkedQueue<>();
private ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream();
// 建構函式初始化音頻格式和音頻線路
public RealtimePcmPlayer(int sampleRate) throws LineUnavailableException {
this.sampleRate = sampleRate;
this.audioFormat = new AudioFormat(this.sampleRate, 16, 1, true, false);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
line.start();
decoderThread = new Thread(new Runnable() {
@Override
public void run() {
while (!stopped.get()) {
String b64Audio = b64AudioBuffer.poll();
if (b64Audio != null) {
byte[] rawAudio = Base64.getDecoder().decode(b64Audio);
RawAudioBuffer.add(rawAudio);
// 將音頻資料寫入 totalAudioStream
try {
totalAudioStream.write(rawAudio);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
playerThread = new Thread(new Runnable() {
@Override
public void run() {
while (!stopped.get()) {
byte[] rawAudio = RawAudioBuffer.poll();
if (rawAudio != null) {
try {
playChunk(rawAudio);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
decoderThread.start();
playerThread.start();
}
// 播放一個音頻塊並阻塞直到播放完成
private void playChunk(byte[] chunk) throws IOException, InterruptedException {
if (chunk == null || chunk.length == 0) return;
int bytesWritten = 0;
while (bytesWritten < chunk.length) {
bytesWritten += line.write(chunk, bytesWritten, chunk.length - bytesWritten);
}
int audioLength = chunk.length / (this.sampleRate*2/1000);
// 等待緩衝區中的音頻播放完成
Thread.sleep(audioLength - 10);
}
public void write(String b64Audio) {
b64AudioBuffer.add(b64Audio);
}
public void cancel() {
b64AudioBuffer.clear();
RawAudioBuffer.clear();
}
public void waitForComplete() throws InterruptedException {
// 等待所有緩衝區中的音頻資料播放完成
while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) {
Thread.sleep(100);
}
// 等待音頻線路播放完成
line.drain();
}
public void shutdown() throws InterruptedException {
stopped.set(true);
decoderThread.join();
playerThread.join();
// 儲存完整音頻檔案
File file = new File("TotalAudio_"+ttsFormat.getSampleRate()+"."+ttsFormat.getFormat());
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(totalAudioStream.toByteArray());
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (line != null && line.isRunning()) {
line.drain();
line.close();
}
}
}
public static void main(String[] args) throws InterruptedException, LineUnavailableException, FileNotFoundException {
Scanner scanner = new Scanner(System.in);
QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
// 如需使用指令控制功能,請將model替換為qwen3-tts-instruct-flash-realtime
.model("qwen3-tts-flash-realtime")
// 以下為新加坡地區的配置。
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// 新加坡和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
.apikey(System.getenv("DASHSCOPE_API_KEY"))
.build();
AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));
// 建立即時播放器執行個體
RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);
final AtomicReference<QwenTtsRealtime> qwenTtsRef = new AtomicReference<>(null);
QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
// File file = new File("result_24k.pcm");
// FileOutputStream fos = new FileOutputStream(file);
@Override
public void onOpen() {
System.out.println("connection opened");
System.out.println("輸入文本並按Enter發送,輸入'quit'退出程式");
}
@Override
public void onEvent(JsonObject message) {
String type = message.get("type").getAsString();
switch(type) {
case "session.created":
System.out.println("start session: " + message.get("session").getAsJsonObject().get("id").getAsString());
break;
case "response.audio.delta":
String recvAudioB64 = message.get("delta").getAsString();
byte[] rawAudio = Base64.getDecoder().decode(recvAudioB64);
// fos.write(rawAudio);
// 即時播放音頻
audioPlayer.write(recvAudioB64);
break;
case "response.done":
System.out.println("response done");
// 等待音頻播放完成
try {
audioPlayer.waitForComplete();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 為下一次輸入做準備
completeLatch.get().countDown();
break;
case "session.finished":
System.out.println("session finished");
if (qwenTtsRef.get() != null) {
System.out.println("[Metric] response: " + qwenTtsRef.get().getResponseId() +
", first audio delay: " + qwenTtsRef.get().getFirstAudioDelay() + " ms");
}
completeLatch.get().countDown();
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
System.out.println("connection closed code: " + code + ", reason: " + reason);
try {
// fos.close();
// 等待播放完成並關閉播放器
audioPlayer.waitForComplete();
audioPlayer.shutdown();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
qwenTtsRef.set(qwenTtsRealtime);
try {
qwenTtsRealtime.connect();
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
}
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
.voice("Cherry")
.responseFormat(ttsFormat)
.mode("commit")
// 如需使用指令控制功能,請取消下方注釋,並將model替換為qwen3-tts-instruct-flash-realtime
// .instructions("")
// .optimizeInstructions(true)
.build();
qwenTtsRealtime.updateSession(config);
// 迴圈讀取使用者輸入
while (true) {
System.out.print("請輸入要合成的文本: ");
String text = scanner.nextLine();
// 如果使用者輸入quit,則退出程式
if ("quit".equalsIgnoreCase(text.trim())) {
System.out.println("正在關閉串連...");
qwenTtsRealtime.finish();
completeLatch.get().await();
break;
}
// 如果使用者輸入為空白,跳過
if (text.trim().isEmpty()) {
continue;
}
// 重新初始化倒計時鎖存器
completeLatch.set(new CountDownLatch(1));
// 發送文本
qwenTtsRealtime.appendText(text);
qwenTtsRealtime.commit();
// 等待本次合成完成
completeLatch.get().await();
}
// 清理資源
audioPlayer.waitForComplete();
audioPlayer.shutdown();
scanner.close();
System.exit(0);
}
}
會話配置
Qwen-TTS 互動模式
Qwen-TTS Realtime API 提供兩種互動模式:
-
server_commit 模式:由服務端智能處理文本分段與合成時機,適合大段文本的連續合成情境。用戶端只需持續追加文本,無需關注分段和提交。
-
commit 模式:由用戶端主動提交文本緩衝區以觸發合成,適合需要精確控制合成時機的情境(如對話式 AI 逐輪合成)。
切換互動模式:
-
WebSocket:通過
session.update事件中的mode欄位設定。{ "type": "session.update", "session": { "mode": "server_commit" } } -
Python SDK:在
update_session方法中通過mode參數設定。qwen_tts_realtime.update_session( voice='Cherry', response_format=AudioFormat.PCM_24000HZ_MONO_16BIT, mode='server_commit' ) -
Java SDK:通過
QwenTtsRealtimeConfig.builder()設定mode參數。QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder() .voice("Cherry") .responseFormat(ttsFormat) .mode("server_commit") .build(); qwenTtsRealtime.updateSession(config);
完整的 SDK 程式碼範例請參見Python SDK和Java SDK。WebSocket 事件生命週期和串連複用說明請參見WebSocket API參考。
進階功能
指令控制
指令控制通過自然語言描述控制語音的音調、語速、情感和音色特點,無需調整複雜的音頻參數。
各模型指令規格:
Qwen-Audio-TTS
支援的模型:qwen-audio-3.0-tts-plus、qwen-audio-3.0-tts-flash
系統音色和聲音複刻音色:均可輸入任意指令。
CosyVoice
支援的模型:cosyvoice-v3.5-plus、cosyvoice-v3.5-flash、cosyvoice-v3-plus、cosyvoice-v3-flash
不同模型對指令的格式要求不同:
-
cosyvoice-v3.5-plus、cosyvoice-v3.5-flash:-
聲音複刻/設計音色:可輸入任意指令。
-
系統音色:v3.5不支援系統音色。
-
-
cosyvoice-v3-plus:-
聲音複刻/設計音色:不支援指令控制。
-
系統音色:指令必須使用固定格式和內容,參見CosyVoice音色列表。
-
-
cosyvoice-v3-flash:-
聲音複刻/設計音色:可輸入任意指令。
-
系統音色:指令必須使用固定格式和內容,參見CosyVoice音色列表。
-
使用方式:通過 instruction 參數指定指令內容。
指令文本支援的語言:
-
cosyvoice-v3.5-plus、cosyvoice-v3.5-flash:-
聲音複刻/設計音色:中文、英文、法語、德語、日語、韓語、俄語、葡萄牙語、泰語、印尼語、越南語。
-
系統音色:v3.5不支援系統音色。
-
-
cosyvoice-v3-plus:-
聲音複刻/設計音色:中文、英文、法語、德語、日語、韓語、俄語。
-
系統音色:指令必須使用固定格式和內容,參見CosyVoice音色列表。
-
-
cosyvoice-v3-flash:-
聲音複刻/設計音色:中文、英文、法語、德語、日語、韓語、俄語。
-
系統音色:中文。
-
指令文本長度限制:不超過 100 字元。漢字(包括簡體/繁體漢字、日文漢字和韓文漢字)按 2 個字元計算,其他字元(如標點符號、字母、數字、日韓文假名/諺文等)按 1 個字元計算。
Qwen-TTS
支援的模型:僅支援Qwen3-TTS-Instruct-Flash-Realtime系列模型。
使用方式:通過 instructions 參數指定指令內容。
指令文本支援的語言:僅支援中文和英文。
指令文本長度限制:不超過 1600 Token。
適用情境:
-
有聲書和廣播劇配音
-
廣告和宣傳片配音
-
遊戲角色和動畫配音
-
情感化的智能語音助手
-
紀錄片和新聞播報
如何編寫高品質的聲音描述:
-
核心原則:
-
具體而非模糊:使用描繪聲音特質的詞語,如“低沉”、“清脆”、“語速偏快”,避免“好聽”、“普通”等主觀或模糊的表述。
-
多維而非單一:好的描述通常涵蓋多個維度(如性別、年齡、情感等)。僅寫“女聲”過於寬泛,難以產生有特色的音色。
-
客觀而非主觀:聚焦聲音的物理和感知特徵。例如,用”音調偏高,帶有活力“代替”我最喜歡的聲音”。
-
原創而非模仿:描述聲音的特質,而非要求模仿特定人物(如名人、演員)。模型不支援模仿,且可能涉及著作權風險。
-
簡潔而非冗餘:確保每個詞都有明確作用,避免重複的同義字或無意義的修飾。
-
-
描述維度參考:
建議組合以下維度描述聲音,維度越豐富,產生效果越精準。
維度
描述樣本
性別
男性、女性、中性
年齡
兒童(5-12 歲)、青少年(13-18 歲)、青年(19-35 歲)、中年(36-55 歲)、老年(55 歲以上)
音調
高音、中音、低音、偏高、偏低
語速
快速、中速、緩慢、偏快、偏慢
情感
開朗、沉穩、溫柔、嚴肅、活潑、冷靜、治癒
特點
有磁性、清脆、沙啞、圓潤、甜美、渾厚、有力
用途
新聞播報、廣告配音、有聲書、動畫角色、語音助手、紀錄片解說
-
樣本:
-
標準播音風格:吐字清晰精準,字正腔圓
-
年輕活潑的女性聲音,語速較快,帶有明顯的上揚語調,適合介紹時尚產品
-
沉穩的中年男性,語速緩慢,音色低沉有磁性,適合朗讀新聞或紀錄片解說
-
溫柔知性的女性,30 歲左右,語調平和,適合有聲書朗讀
-
可愛的兒童聲音,大約 8 歲女孩,說話略帶稚氣,適合動畫角色配音
-
方言
本節介紹如何讓模型用中文方言(如河南話、四川話、粵語等)輸出語音。不同模型和音色類型的設定方式不同。
各模型方言設定方式:
Qwen-Audio-TTS
-
系統音色:在Qwen-Audio-TTS音色列表中選擇以下任一種音色:
-
支援方言的系統音色,無需額外設定即可輸出對應方言。
-
支援指令控制且可指定方言的音色,通過指令文本指定方言。
-
-
聲音複刻音色:通過指令控制功能設定,例如指令文本寫
請用河南話表達。
具體支援哪些方言:參見Qwen-Audio-TTS中各模型“支援的語言”。
CosyVoice
-
系統音色:在CosyVoice音色列表中選擇以下任一種音色:
-
支援方言的系統音色(例如
longshange_v3),無需額外設定即可輸出對應方言。 -
支援指令控制且可指定方言的音色(例如
longanhuan_v3),通過指令文本指定方言。
-
-
聲音複刻音色:通過指令控制功能設定,例如指令文本寫
請用河南話表達。 -
聲音設計音色:暫不支援方言。
具體支援哪些方言:參見CosyVoice中各模型“支援的語言”。
樣本:以 cosyvoice-v3-flash + longanhuan_v3 音色,通過指令文本 "請用河南話表達。" 輸出河南話語音。
# coding=utf-8
import os
import dashscope
from dashscope.audio.tts_v2 import *
# 新加坡地區和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 若沒有配置環境變數,請用阿里雲百鍊API Key將下行替換為:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# 以下為新加坡地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'
# 模型
# 不同模型版本需要使用對應版本的音色:
# cosyvoice-v3-flash/cosyvoice-v3-plus:使用longanyang等音色。
# cosyvoice-v2:使用longxiaochun_v2等音色。
# 不同語言選擇對應音色
model = "cosyvoice-v3-flash"
# 音色
voice = "longanhuan_v3"
# 執行個體化SpeechSynthesizer,並在構造方法中傳入模型(model)、音色(voice)等請求參數
synthesizer = SpeechSynthesizer(model=model, voice=voice, instruction="請用河南話表達。")
# 發送待合成文本,擷取二進位音頻
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)
Qwen-TTS
-
系統音色:使用支援方言的系統音色,參見支援的音色中的 Qwen-TTS音色列表。
-
聲音複刻音色:不支援方言。
-
聲音設計音色:不支援方言。
具體支援哪些方言:參見Qwen3-TTS中各模型“支援的語言”。
情感與富語言標籤
Qwen-Audio-TTS 系列模型支援在待合成文本(text 參數)中直接嵌入情感與富語言標籤,用於控制語音的情感表達或在指定位置插入擬聲效果(如笑聲、歎息等),無需調整複雜的音頻參數即可產生更具表現力的語音。
支援的模型:僅 qwen-audio-3.0-tts-plus 和 qwen-audio-3.0-tts-flash。
限制:僅支援單向流式模式。
控制類標籤
控制類標籤用於設定語音的情感或風格。將標籤寫在文本中,標籤會作用於其後的所有文本,直到遇到下一個控制類標籤,或因句子較長被自動切分為止。
|
標籤 |
說明 |
|
|
悲傷 |
|
|
驚歎 |
|
|
深沉大聲呐喊 |
|
|
顫抖 |
|
|
憤怒 |
|
|
興奮 |
|
|
諷刺 |
|
|
好奇 |
|
|
德古拉風格(低沉、陰森) |
|
|
無聊 |
|
|
疲憊 |
|
|
唱歌 |
|
|
輕蔑 |
|
|
大喊 |
|
|
ASMR 輕柔耳語 |
|
|
恐慌 |
|
|
調皮 |
|
|
共情 |
|
|
耳語 |
|
|
不情願 |
|
|
哭泣 |
|
|
嚴肅 |
|
|
非常緩慢地說話 |
|
|
非常快速地說話 |
富語言類標籤
富語言類標籤用於在文本的當前位置插入一段擬聲效果,不影響前後文本的情感風格。
|
標籤 |
說明 |
|
|
倒吸一口氣 |
|
|
歎息 |
|
|
清嗓 |
|
|
咯咯笑 |
|
|
大笑 |
|
|
咳嗽 |
|
|
哼聲、嗤笑 |
使用樣本
以下樣本展示如何在 text 參數中組合使用控制類標籤和富語言類標籤:
[excited]今天的天氣真不錯![laughing]我們一起出去玩吧!
上述文本中,[excited] 是控制類標籤,作用於其後的所有文本,使語音帶有興奮的情感;[laughing] 是富語言類標籤,在該位置插入一段笑聲效果後繼續合成後續文本。
您也可以在同一段文本中切換不同情感:
[serious]請注意安全事項。[excited]好了,現在讓我們開始吧!
其中 [serious] 控制第一句為嚴肅語氣,[excited] 從第二句起切換為興奮語氣。
WebSocket 原始協議調用
以下樣本展示如何通過 WebSocket 原始協議直連服務端,適用於不使用 DashScope SDK 的情境。此為最小可運行實現,WebSocket 通訊協定請參見各模型的 API 參考。
應用於生產環境
串連複用(WebSocket)
WebSocket 串連支援複用:一個合成任務結束後,無需重建立立串連即可開啟下一個任務。
複用流程:
-
Qwen-Audio-TTS / CosyVoice:用戶端發送
finish-task,服務端返回task-finished後,可重新發送run-task開啟新任務。 -
Qwen-TTS:用戶端發送
session.finish,服務端返回session.finished後,可建立新會話開啟下一個任務。
-
必須等服務端返回結束事件(
task-finished或session.finished)後才可發起新任務。 -
Qwen-Audio-TTS、CosyVoice 在複用串連中的不同任務需要使用不同的
task_id。 -
任務失敗時服務端返回錯誤事件並關閉串連,該串連不可複用。
-
任務結束後 60 秒無新任務,串連自動斷開。
各模型事件說明請參見對應的API參考。
高並發最佳實務
DashScope SDK 內建池化機制,可複用 WebSocket 串連和合成對象,避免頻繁建立銷毀帶來的開銷。
支援的模型與地區
新加坡
調用以下模型時,請選擇新加坡地區的API Key:
-
Qwen-Audio-TTS:qwen-audio-3.0-tts-plus、qwen-audio-3.0-tts-flash
-
CosyVoice:cosyvoice-v3-plus、cosyvoice-v3-flash
-
Qwen-TTS:
-
Qwen3-TTS-Instruct-Flash-Realtime:qwen3-tts-instruct-flash-realtime(穩定版,當前等同qwen3-tts-instruct-flash-realtime-2026-01-22)、qwen3-tts-instruct-flash-realtime-2026-01-22(最新快照版)
-
Qwen3-TTS-VD-Realtime:qwen3-tts-vd-realtime-2026-01-15(最新快照版)、qwen3-tts-vd-realtime-2025-12-16(快照版)
-
Qwen3-TTS-VC-Realtime:qwen3-tts-vc-realtime-2026-01-15(最新快照版)、qwen3-tts-vc-realtime-2025-11-27(快照版)
-
Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime(穩定版,當前等同qwen3-tts-flash-realtime-2025-11-27)、qwen3-tts-flash-realtime-2025-11-27(最新快照版)、qwen3-tts-flash-realtime-2025-09-18(快照版)
-
華北2(北京)
調用以下模型時,請選擇北京地區的API Key:
-
Qwen-Audio-TTS:qwen-audio-3.0-tts-plus、qwen-audio-3.0-tts-flash
-
CosyVoice:cosyvoice-v3.5-plus、cosyvoice-v3.5-flash、cosyvoice-v3-plus、cosyvoice-v3-flash、cosyvoice-v2
-
Qwen-TTS:
-
Qwen3-TTS-Instruct-Flash-Realtime:qwen3-tts-instruct-flash-realtime(穩定版,當前等同qwen3-tts-instruct-flash-realtime-2026-01-22)、qwen3-tts-instruct-flash-realtime-2026-01-22(最新快照版)
-
Qwen3-TTS-VD-Realtime:qwen3-tts-vd-realtime-2026-01-15(最新快照版)、qwen3-tts-vd-realtime-2025-12-16(快照版)
-
Qwen3-TTS-VC-Realtime:qwen3-tts-vc-realtime-2026-01-15(最新快照版)、qwen3-tts-vc-realtime-2025-11-27(快照版)
-
Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime(穩定版,當前等同qwen3-tts-flash-realtime-2025-11-27)、qwen3-tts-flash-realtime-2025-11-27(最新快照版)、qwen3-tts-flash-realtime-2025-09-18(快照版)
-
Qwen-TTS-Realtime:qwen-tts-realtime(穩定版,當前等同qwen-tts-realtime-2025-07-15)、qwen-tts-realtime-latest(最新版,當前等同qwen-tts-realtime-2025-07-15)、qwen-tts-realtime-2025-07-15(快照版)
-
支援的音色
不同模型支援的音色不同。將請求參數 voice 設為音色列表中 voice參數 列的值即可。
API參考
常見問題
Q:語音合成發音錯誤怎麼辦?多音字如何控制發音?
-
將多音字替換為同音的其他漢字,快速解決發音問題。
-
使用 SSML 標記語言控制發音。
Q:使用複刻音色產生的音頻無聲音如何排查?
-
確認音色狀態
調用CosyVoice聲音複刻/設計API介面,確認音色的
status是否為OK。 -
檢查模型版本一致性
確保複刻音色時使用的
target_model參數與語音合成時的model參數完全一致。例如:-
複刻時使用
cosyvoice-v3-plus -
合成時也必須使用
cosyvoice-v3-plus
-
-
驗證源音頻品質
檢查複刻音色時使用的源音頻是否符合CosyVoice聲音複刻/設計API:
-
音頻時間長度:10-20秒
-
音質清晰
-
無背景雜音
-
-
檢查請求參數
確認語音合成請求中的
voice參數已設定為複刻音色的 ID。
Q:聲音複刻後合成效果不穩定或語音不完整怎麼辦?
如果複刻音色後合成的語音出現以下問題:
-
語音播放不完整,唯讀出部分文字
-
合成效果不穩定,時好時壞
-
語音中包含異常停頓或靜音段
可能原因:源音頻品質不符合要求。
解決方案:請檢查源音頻是否符合錄音操作指南中的音頻要求,建議按照錄音指南重新錄製。
Q:為什麼語音合成的實際時間長度與 WAV 檔案顯示的時間長度不一致?
語音合成採用流式機制,邊合成邊返回資料,因此儲存的 WAV 檔案頭中的時間長度是預估值,存在一定誤差。如需精確時間長度,可將 format 設定為 pcm,待擷取完整合成結果後自行添加 WAV 檔案頭資訊。
Q:為什麼音頻無法播放?
請按以下情境逐一排查:
-
音頻儲存為完整檔案(如 xx.mp3)的情況
-
音頻格式一致性:請求參數中的音頻格式須與檔案尾碼一致(如參數為 wav 則檔案須為 .wav)。
-
播放器相容性:確認播放器支援該音訊格式和採樣率。
-
-
流式播放音訊情況
-
將音頻流儲存為完整檔案,嘗試用播放器播放。如果檔案無法播放,請參考情境 1 的排查方法。
-
如果檔案可正常播放,則問題在流式播放實現。請確認播放器支援流式播放(如 ffmpeg、pyaudio、AudioFormat、MediaSource 等)。
-
Q:為什麼音頻播放卡頓?
請按以下步驟逐一排查:
-
檢查文本發送速度:確保發送間隔合理,避免上段音頻播完後下段文本尚未到達。
-
檢查回呼函數效能:
-
確認回呼函數中無阻塞性商務邏輯。
-
回調運行在 WebSocket 線程,阻塞會影響資料接收。建議將音頻資料寫入獨立緩衝區,在其他線程中處理。
-
-
檢查網路穩定性:網路波動可能導致音頻傳輸中斷或延遲。
Q:語音合成耗時較長是什麼原因?
請按以下步驟排查:
-
檢查輸入間隔
如果是流式合成,確認文本發送間隔是否過長,過長會導致合成總時間長度增加。
-
分析效能指標
-
首包延遲:正常約 500ms。
-
RTF(即時率 = 合成總耗時 / 音頻時間長度):正常應小於 1.0。
-
Q:合成的音頻中讀出了文本裡的特殊符號怎麼辦?
Qwen-TTS 系列模型可能將文本中的部分特殊符號(如 Markdown 加粗標記 **)合成為語音。可通過以下方式處理:
-
調用前對文本進行預先處理,去除特殊符號。
-
改用 CosyVoice 模型。
Q:如何限制 API Key 僅用於語音合成服務(許可權隔離)?
通過建立業務空間並僅授權特定模型,可限制 API Key 的使用範圍。請參見業務空間管理。