リアルタイム音声合成は、WebSocket 接続を介してテキストを自然な音声に変換します。ストリーミング入出力、音声クローニング、音声デザイン、詳細な音声制御をサポートし、音声アシスタント、オーディオブック、インテリジェントカスタマーサービスなどのユースケースに利用できます。
概要
WebSocket を介した低レイテンシーのリアルタイム音声合成は、音声アシスタント、インテリジェントカスタマーサービス、ライブキャプションなど、即時応答が求められるシナリオ向けに構築されています。
-
ストリーミング入出力 (全二重 WebSocket) と初回音声受信までの時間 (TTFA) の短縮により、音声アシスタントやインテリジェントカスタマーサービスなどのリアルタイムな会話に最適です。
-
話速、ピッチ、音量、ビットレートを調整可能で、詳細な音声制御が可能です。
-
主要な音声フォーマット (PCM、WAV、MP3、Opus) と互換性があり、最大 48 kHz のサンプルレート出力をサポートします。
-
命令ベースの制御をサポートしており、自然言語による命令で音声の表現力を制御できます。
リアルタイム出力が不要な場合は、オーディオブックや教材の吹き替えなどのバッチシナリオに適した非リアルタイム音声合成 (HTTP API) を使用してください。モデル選択のガイダンスについては、「音声合成」をご参照ください。
前提条件
-
DashScope SDK を介してサービスを呼び出す場合は、最新の SDK をインストールしてください。
クイックスタート
以下の例は、各モデルの音声合成を示しています。その他の例とパラメーターの説明については、各モデルのAPI リファレンスをご参照ください。
CosyVoice
以下の例は、システム音声で音声を合成する方法を示しています (「CosyVoice 音声リスト」をご参照ください)。
命令ベースの制御を使用するには、instructions パラメーターを通じて命令を設定します。
Python
# coding=utf-8
import os
import dashscope
from dashscope.audio.tts_v2 import *
# API キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得:https://www.alibabacloud.com/help/model-studio/get-api-key
# 環境変数が設定されていない場合は、次の行を Model Studio API キーに置き換えてください:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
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 をインスタンス化し、コンストラクターでモデルや音声などのリクエストパラメーターを渡します
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# 合成するテキストを送信し、バイナリ音声を取得します
audio = synthesizer.call("How is the weather today?")
# 最初のテキスト送信では WebSocket 接続を確立する必要があるため、初回パケットレイテンシーには接続確立時間が含まれます
print('[Metric] requestId: {}, first package delay: {} ms'.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 キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得:https://www.alibabacloud.com/help/model-studio/get-api-key
// 環境変数が設定されていない場合は、次の行を Model Studio API キーに置き換えてください:.apiKey("sk-xxx")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model(model) // モデル
.voice(voice) // 音声
.build();
// 同期モード:コールバックを無効にします (2 番目のパラメーターは null)
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
ByteBuffer audio = null;
try {
// 音声が返されるまでブロックします
audio = synthesizer.call("How is the weather today?");
} 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()
+ ", first package delay (ms): "
+ 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。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
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 = [
'Right? I love supermarkets like this.',
'Especially during Chinese New Year,',
'I go shopping at supermarkets.',
'And I feel',
'absolutely thrilled!',
'I want to buy so many things!'
]
DO_VIDEO_TEST = False
def init_dashscope_api_key():
"""
DashScope API キーを設定します。詳細情報:
https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
"""
# API キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得:https://www.alibabacloud.com/help/model-studio/get-api-key
if 'DASHSCOPE_API_KEY' in os.environ:
dashscope.api_key = os.environ[
'DASHSCOPE_API_KEY'] # 環境変数 DASHSCOPE_API_KEY から API キーをロード
else:
dashscope.api_key = 'your-dashscope-api-key' # API キーを手動で設定
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(
# 命令ベースの制御を使用するには、モデルを qwen3-tts-instruct-flash-realtime に置き換えます
model='qwen3-tts-flash-realtime',
callback=callback,
# これはシンガポールリージョンの URL です。WorkspaceId を実際のワークスペース ID に置き換えてください。北京リージョンを使用する場合は、wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime に置き換えてください
url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime'
)
qwen_tts_realtime.connect()
qwen_tts_realtime.update_session(
voice = 'Cherry',
response_format = AudioFormat.PCM_24000HZ_MONO_16BIT,
# 命令ベースの制御を使用するには、以下の行のコメントを解除し、モデルを qwen3-tts-instruct-flash-realtime に置き換えます
# instructions='Speak quickly with a rising intonation, suitable for introducing fashion products.',
# 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 = [
'This is the first sentence.',
'This is the second sentence.',
'This is the third sentence.',
]
DO_VIDEO_TEST = False
def init_dashscope_api_key():
"""
DashScope API キーを設定します。詳細情報:
https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
"""
# API キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得:https://www.alibabacloud.com/help/model-studio/get-api-key
if 'DASHSCOPE_API_KEY' in os.environ:
dashscope.api_key = os.environ[
'DASHSCOPE_API_KEY'] # 環境変数 DASHSCOPE_API_KEY から API キーをロード
else:
dashscope.api_key = 'your-dashscope-api-key' # API キーを手動で設定
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(
# 命令ベースの制御を使用するには、モデルを qwen3-tts-instruct-flash-realtime に置き換えます
model='qwen3-tts-flash-realtime',
callback=callback,
# これはシンガポールリージョンの URL です。WorkspaceId を実際のワークスペース ID に置き換えてください。北京リージョンを使用する場合は、wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime に置き換えてください
url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime'
)
qwen_tts_realtime.connect()
qwen_tts_realtime.update_session(
voice = 'Cherry',
response_format = AudioFormat.PCM_24000HZ_MONO_16BIT,
# 命令ベースの制御を使用するには、以下の行のコメントを解除し、モデルを qwen3-tts-instruct-flash-realtime に置き換えます
# instructions='Speak quickly with a rising intonation, suitable for introducing fashion products.',
# 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 モード
appendText()
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 = {
"Right? I really love this kind of supermarket.",
"Especially during the Chinese New Year.",
"Going to the supermarket.",
"It just makes me feel.",
"Super, super happy!",
"I want to buy so many things!"
};
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()
// 命令ベースの制御を使用するには、モデルを qwen3-tts-instruct-flash-realtime に置き換えます。
.model("qwen3-tts-flash-realtime")
// シンガポールのエンドポイント。WorkspaceId を実際のワークスペース ID に置き換えてください。中国 (北京) の場合、wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/realtime を使用します。
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
// API キーはシンガポールと中国 (北京) で異なります。https://www.alibabacloud.com/help/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")
// 命令ベースの制御を使用するには、以下の行のコメントを解除し、モデルを 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 モード
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()
// 命令ベースの制御を使用するには、モデルを qwen3-tts-instruct-flash-realtime に置き換えます。
.model("qwen3-tts-flash-realtime")
// シンガポールのエンドポイント。WorkspaceId を実際のワークスペース ID に置き換えてください。中国 (北京) の場合、wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/realtime を使用します。
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
// API キーはシンガポールと中国 (北京) で異なります。https://www.alibabacloud.com/help/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() {
@Override
public void onOpen() {
System.out.println("connection opened");
System.out.println("Enter text and press Enter to send. Enter 'quit' to exit the program.");
}
@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);
// 音声をリアルタイムで再生します。
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 {
// 再生が完了するのを待ってから、プレーヤーをシャットダウンします。
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")
// 命令ベースの制御を使用するには、以下の行のコメントを解除し、モデルを qwen3-tts-instruct-flash-realtime に置き換えます。
// .instructions("")
// .optimizeInstructions(true)
.build();
qwenTtsRealtime.updateSession(config);
// ループでユーザー入力を読み取ります。
while (true) {
System.out.print("Enter the text to synthesize: ");
String text = scanner.nextLine();
// ユーザーが 'quit' を入力すると終了します。
if ("quit".equalsIgnoreCase(text.trim())) {
System.out.println("Closing the connection...");
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 は 2 つの対話モードを提供します:
-
`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 リファレンス」をご参照ください。
高度な機能
命令ベースの制御
命令ベースの制御により、複雑な音声パラメーターを調整することなく、自然言語の記述を通じてトーン、速度、感情、音色を形成できます。
モデルごとの命令の仕様:
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 音声リスト」をご参照ください。
-
使用方法:instructions パラメーターを通じて命令の内容を指定します。
命令テキストでサポートされている言語:
-
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 シリーズのモデルのみがサポートされています。
使用方法:instruction パラメーターを通じて命令の内容を指定します。
命令テキストでサポートされている言語:中国語と英語のみ。
命令テキストの長さ制限:最大 1,600 トークン。
利用シーン:
-
オーディオブックとラジオドラマのナレーション
-
広告・宣伝ナレーション
-
ゲームキャラクター・アニメーションの吹き替え
-
感情表現豊かな音声アシスタント
-
ドキュメンタリーナレーション・ニュース放送
高品質な音声記述を作成するためのヒント:
-
基本原則:
-
具体的であり、曖昧でないこと:「深い」「張りのある」「少し速い」など、具体的な声質を表す言葉を使用します。「良い」や「普通」のような主観的または曖昧な用語は避けてください。
-
多次元的であり、一面的でないこと:良い記述は複数の次元 (性別、年齢、感情など) をカバーします。「女性の声」とだけ書くのは、特徴的な音色を生み出すには広すぎます。
-
客観的であり、主観的でないこと:声の物理的および知覚的な性質に焦点を当てます。例えば、「私のお気に入りの声」ではなく、「少し高めのピッチでエネルギッシュ」のように記述します。
-
独創的であり、模倣でないこと:特定の公人 (有名人や俳優など) の模倣を要求するのではなく、希望する声質を記述してください。モデルは模倣をサポートしておらず、著作権リスクを伴う可能性があります。
-
簡潔であり、冗長でないこと:すべての言葉に意味を持たせます。同義語を繰り返したり、意味のない修飾子を重ねたりすることは避けてください。
-
-
記述の次元:
以下の次元を組み合わせることで、より正確な結果が得られます。記述する次元が多いほど、出力はより正確になります。
次元
記述例
性別
男性、女性、中性
年齢
子供 (5-12)、ティーンエイジャー (13-18)、若者 (19-35)、中年 (36-55)、高齢者 (55+)
ピッチ
高い、中間、低い、やや高い、やや低い
速度
速い、普通、遅い、やや速い、やや遅い
感情
陽気、穏やか、優しい、真面目、活発、落ち着いた、癒し系
音色
磁気的、張りのある、ハスキー、まろやか、甘い、豊か、力強い
利用シーン
ニュース放送、広告、オーディオブック、アニメキャラクター、音声アシスタント、ドキュメンタリーナレーション
-
例:
-
標準的な放送スタイル:標準的な発音で、明瞭かつ正確なアーティキュレーション
-
若々しく活発な女性の声、やや速いペースで、顕著な上昇イントネーションがあり、ファッション製品の紹介に適している
-
落ち着いた中年の男性の声、遅いペースで、深く磁気的な音色、ニュースの読み上げやドキュメンタリーのナレーションに適している
-
優しく知的な女性の声、30歳前後、落ち着いたトーンで、オーディオブックの読み聞かせに適している
-
かわいい子供の声、8歳くらいの女の子、少し子供っぽい話し方で、アニメキャラクターの吹き替えに適している
-
方言
モデルを使用して、河南、四川、広東など中国語の方言で音声を出力します。設定はモデルと音声タイプによって異なります。
モデルごとの方言設定:
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 キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得:https://www.alibabacloud.com/help/model-studio/get-api-key
# 環境変数が設定されていない場合は、次の行を Model Studio API キーに置き換えてください:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
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 をインスタンス化し、コンストラクターでモデル、音声、命令などのリクエストパラメーターを渡します
synthesizer = SpeechSynthesizer(model=model, voice=voice, instruction="请用河南话表达。")
# 合成するテキストを送信し、バイナリ音声を取得します
audio = synthesizer.call("叫你去买盐,你买回来一袋面,这不是弄啥嘞吗!")
# 最初のテキスト送信では WebSocket 接続を確立する必要があるため、初回パケットレイテンシーには接続確立時間が含まれます
print('[Metric] requestId: {}, first package delay: {} ms'.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」の各モデルの「サポートされている言語」エントリをご参照ください。
Raw WebSocket プロトコル
以下の例は、DashScope SDK を使用しないシナリオで、Raw WebSocket プロトコルを介してサーバーに直接接続する方法を示しています。これらは最小限の動作実装です。各モデルの WebSocket プロトコル仕様については、対応する API リファレンスをご参照ください。
本番デプロイメント
接続の再利用 (WebSocket)
WebSocket 接続は再利用をサポートしています:合成タスクが完了した後、再接続せずに同じ接続で次のタスクを開始できます。
再利用フロー:
-
CosyVoice:クライアントは
finish-taskを送信します。サーバーがtask-finishedイベントを返した後、クライアントはrun-taskイベントを送信して新しいタスクを開始できます。 -
Qwen-TTS:クライアントは
session.finishを送信します。サーバーがsession.finishedを返した後、クライアントは次のタスクのために新しいセッションを確立できます。
-
新しいタスクを開始する前に、サーバーが完了イベント (
task-finishedまたはsession.finished) を返すのを待ってください。 -
CosyVoice は、再利用された接続上の各タスクに異なる
task_idを必要とします。 -
タスクが失敗した場合、サーバーはエラーイベントを返し、接続を閉じます。接続は再利用できません。
-
60 秒以内に新しいタスクが開始されない場合、接続は自動的に切断されます。
イベントの詳細については、対応するCosyVoice API リファレンス、Qwen-TTS API リファレンスをご参照ください。
高い同時実行性のベストプラクティス
DashScope SDK には、WebSocket 接続と合成オブジェクトを再利用するための組み込みプーリングメカニズムが含まれており、頻繁な作成と破棄のオーバーヘッドを削減します。
サポートされているモデルとリージョン
シンガポール
以下のモデルを呼び出すには、シンガポールリージョンからAPI キーを選択してください:
-
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 (スナップショット)
-
中国 (北京)
以下のモデルを呼び出すには、北京リージョンから API キーを選択します:
-
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 リクエストパラメーターを音声リストの音声パラメーター列の値に設定してください。
API リファレンス
よくある質問
Q:音声合成で誤った発音を修正するにはどうすればよいですか?多音字の発音を制御するにはどうすればよいですか?
-
多音字を同音異義語に置き換えて、発音の問題を迅速に修正します。
-
SSML マークアップ言語を使用して発音を制御します。
Q:クローンした音声を使用すると無音になるのはなぜですか?
-
音声ステータスの確認
音声クローニング/デザイン API を呼び出し、音声の
statusがOKであることを確認します。 -
モデルバージョンの一貫性の確認
音声クローニング時に使用した
target_modelパラメーターが、音声合成時に使用したmodelパラメーターと一致していることを確認してください。例:-
クローニングに
cosyvoice-v3-plusを使用した場合 -
合成にも
cosyvoice-v3-plusを使用する必要があります
-
-
ソース音声の品質の確認
音声クローニングに使用したソース音声が音声要件とベストプラクティスを満たしているか確認します:
-
音声の長さ:10〜20 秒
-
クリアな音質
-
バックグラウンドノイズなし
-
-
リクエストパラメーターの確認
音声合成リクエストの
voiceパラメーターがクローンした音声 ID に設定されていることを確認します。
Q: クローン音声が不安定な、または不完全な音声を生成する場合、どうすればよいですか?
クローンした音声から合成された音声に次のいずれかの問題が見られる場合:
-
テキストの一部しか読み上げない不完全な再生
-
合成品質の不一致
-
音声に異常な間や無音部分がある
考えられる原因:ソース音声の品質が要件を満たしていません。
解決策:ソース音声が音声クローニングの録音ガイドの要件を満たしているか確認してください。録音ガイドラインに基づいて再録音することをお勧めします。
Q:実際の持続時間が WAV ファイルヘッダーに表示される持続時間と異なるのはなぜですか?
音声合成は、データが生成されるにつれて段階的にデータを返すストリーミングメカニズムを使用します。保存された WAV ファイルヘッダーの持続時間は推定値であり、不正確な場合があります。正確な持続時間を得るには、format を pcm に設定し、完全な合成結果を待ってから、自分で WAV ファイルヘッダーを追加してください。
Q:音声が再生されないのはなぜですか?
次のシナリオに基づいてトラブルシューティングを行ってください:
-
完全なファイルとして保存された音声 (xx.mp3 など)
-
音声フォーマットの一貫性:リクエストパラメーターの音声フォーマットは、ファイル拡張子と一致する必要があります (例:フォーマットが
wavに設定されている場合、ファイルは.wavとして保存する必要があります)。 -
プレーヤーの互換性:ご利用のプレーヤーが音声フォーマットとサンプルレートをサポートしていることを確認してください。
-
-
ストリーミング音声の再生
-
音声ストリームを完全なファイルとして保存し、メディアプレーヤーで再生してみてください。ファイルが再生されない場合は、シナリオ 1 のトラブルシューティング手順をご参照ください。
-
ファイルが正しく再生される場合は、ストリーミング再生の実装に問題があります。ご利用のプレーヤーがストリーミング再生 (ffmpeg、pyaudio、AudioFormat、MediaSource など) をサポートしていることを確認してください。
-
Q:音声再生がコマ落ちするのはなぜですか?
次の手順でトラブルシューティングを行ってください:
-
テキスト送信レートの確認:テキストセグメント間の間隔が、前の音声の再生が終了する前に次のセグメントが到着するのに十分短いことを確認してください。
-
コールバック関数のパフォーマンスの確認:
-
コールバック関数にブロッキングするビジネスロジックがないことを確認してください。
-
コールバックは WebSocket スレッドで実行されます。それをブロックすると、データ受信が遅延します。音声データを別のバッファーに書き込み、別のスレッドで処理してください。
-
-
ネットワークの安定性の確認:ネットワークの変動により、音声伝送の中断や遅延が発生する可能性があります。
Q:音声合成に時間がかかるのはなぜですか?
次の手順でトラブルシューティングを行ってください:
-
入力間隔の確認
ストリーミング合成の場合、テキストセグメント間の間隔が長すぎないことを確認してください。間隔が長いと、合計合成時間が増加します。
-
パフォーマンスメトリクスの分析
-
初回パケットレイテンシー:通常は約 500 ms です。
-
RTF (リアルタイム係数 = 合計合成時間 / 音声持続時間):通常の状態では 1.0 未満である必要があります。
-
Q:API キーを音声合成のみに制限するにはどうすればよいですか (権限隔離)?
新しいワークスペースを作成し、特定のモデルのみを承認して API キーの範囲を制限します。詳細については、「ワークスペースの管理」をご参照ください。