跳转到主要内容
Qwen3-TTS-Realtime

WebSocket 接入指南

本文介绍通过 WebSocket 连接访问 Qwen3-TTS-Realtime 实时语音合成服务的接口地址、请求头和交互流程。

用户指南:关于模型介绍和选型建议请参见实时语音合成 Qwen3-TTS Realtime API 基于 WebSocket 协议。Java 和 Python 推荐通过 DashScope SDK 调用,可免去处理 WebSocket 细节;其他语言可使用 WebSocket 库直接连接。

接口地址

WebSocket URL 固定如下,通过查询参数 model 指定要调用的模型名称: WebSocket URL:wss://maas.qianwenaiapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime
URL 必须使用 wss:// 协议。Authorization 在请求头中设置(参见请求头),模型通过 URL 查询参数 model 指定。

请求头

请求头中需添加如下信息:
参数类型是否必选说明
Authorizationstring是鉴权令牌,格式为 Bearer $DASHSCOPE_API_KEY,将 $DASHSCOPE_API_KEY 替换为实际的 API Key。
user-agentstring否客户端标识,便于服务端追踪来源。
X-DashScope-WorkSpacestring否千问AI平台业务空间ID。
Authorization 鉴权在 WebSocket 握手阶段验证。如果 API Key 无效或缺失,握手将失败并返回 HTTP 401/403 错误。

交互流程

客户端事件和服务端事件的详细说明,请参见客户端事件和服务端事件。 支持两种使用模式:
  • ServerCommit 模式:服务端智能判断文本分段与合成时机,开发者无需关心内部状态切分。
  • Commit 模式:客户端控制每一段文本的提交时间,需显式调用 input_text_buffer.commit 触发合成。
模式说明:
  • ServerCommit 模式下调用 input_text_buffer.append 多次,系统根据内部规则判断合成起点。
  • 若在 ServerCommit 模式中主动调用 input_text_buffer.commit,表示立即合成当前缓冲内容,后续仍维持 ServerCommit 模式。
  • Commit 模式下仅调用 input_text_buffer.append 不会触发合成,需明确调用 input_text_buffer.commit。
qwen3-tts
关键流程说明:
  1. 连接阶段:客户端发起 WebSocket 连接,服务端返回 session.created,表示会话已初始化。
  2. 配置会话:客户端发送 session.update 事件设置音色、格式、模式等参数。
  3. 文本输入阶段:客户端通过多次发送 input_text_buffer.append 添加文本到缓冲区。
  4. 触发合成阶段:
    • ServerCommit 模式中系统自动判断合成时机,或客户端手动调用 input_text_buffer.commit 强制触发。
    • Commit 模式中仅 input_text_buffer.commit 操作才会真正触发语音合成流程。
  5. 音频生成阶段:服务端发出 response.created 表示任务已启动,随后分片返回音频 response.audio.delta(base64 编码),直到 response.audio.done。
  6. 会话结束阶段:客户端显式调用 session.finish 通知服务端清理状态,服务端返回 session.finished 后关闭连接。
连接建立后,服务端返回如下 session.created 事件:
{
    "event_id": "event_xxx",
    "type": "session.created",
    "session": {
        "object": "realtime.session",
        "mode": "server_commit",
        "model": "qwen3-tts-flash-realtime",
        "voice": "Cherry",
        "response_format": "pcm",
        "sample_rate": 24000,
        "id": "sess_xxx"
    }
}

示例代码

以下示例提供 Python、Java 客户端以及两种提交模式。先配置 DASHSCOPE_API_KEY。Python 需要 websockets、pyaudio;Java 需要 Java-WebSocket、Gson,并使用 Java Sound 播放音频。每个客户端事件使用独立 UUID。
Java
<dependencies>
    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>1.5.3</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>
</dependencies>
  1. 创建客户端
  • Java
  • Python
在本地新建 Java 文件,命名为TTSRealtimeClient.java并复制以下代码到文件中:
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;

/**
 * 与 TTS Realtime API 交互的客户端。
 *
 * 该类提供了连接 TTS Realtime API、发送文本数据、获取音频输出以及管理 WebSocket 连接的相关方法。
 */
public class TTSRealtimeClient {

    public enum SessionMode {
        SERVER_COMMIT("server_commit"),
        COMMIT("commit");
        private final String value;
        SessionMode(String value) { this.value = value; }
        public String getValue() { return value; }
    }

    /**
     * 音频回调接口
     */
    public interface AudioCallback {
        void onAudio(byte[] audioData);
    }

    private final String baseUrl;
    private final String apiKey;
    private final String voice;
    private final SessionMode mode;
    private final String languageType;
    private final AudioCallback audioCallback;
    private final Gson gson = new Gson();

    private WebSocketClient ws;
    private CountDownLatch responseDoneLatch;
    private CountDownLatch sessionFinishedLatch;

    public TTSRealtimeClient(String baseUrl, String apiKey, String voice,
                             SessionMode mode, AudioCallback audioCallback,
                             String languageType) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
        this.voice = voice;
        this.mode = mode;
        this.audioCallback = audioCallback;
        this.languageType = languageType;
    }

    public TTSRealtimeClient(String baseUrl, String apiKey, String voice,
                             SessionMode mode, AudioCallback audioCallback) {
        this(baseUrl, apiKey, voice, mode, audioCallback, "Auto");
    }

    /**
     * 与 TTS Realtime API 建立 WebSocket 连接。
     */
    public void connect() throws Exception {
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "Bearer " + apiKey);

        responseDoneLatch = new CountDownLatch(0);
        sessionFinishedLatch = new CountDownLatch(1);

        ws = new WebSocketClient(new URI(baseUrl), headers) {
            @Override
            public void onOpen(ServerHandshake handshake) {
                System.out.println("WebSocket 连接已建立");
                // 发送默认会话配置
                JsonObject session = new JsonObject();
                session.addProperty("mode", mode.getValue());
                session.addProperty("voice", TTSRealtimeClient.this.voice);
                session.addProperty("language_type", languageType);
                session.addProperty("response_format", "pcm");
                session.addProperty("sample_rate", 24000);
                updateSession(session);
            }

            @Override
            public void onMessage(String message) {
                JsonObject event = gson.fromJson(message, JsonObject.class);
                String eventType = event.has("type") ? event.get("type").getAsString() : "";

                if (!"response.audio.delta".equals(eventType)) {
                    System.out.println("收到事件: " + eventType);
                }

                switch (eventType) {
                    case "error":
                        System.err.println("错误: " + event.get("error"));
                        break;
                    case "session.created":
                        System.out.println("会话创建,ID: " +
                            event.getAsJsonObject("session").get("id").getAsString());
                        break;
                    case "session.updated":
                        System.out.println("会话更新,ID: " +
                            event.getAsJsonObject("session").get("id").getAsString());
                        break;
                    case "input_text_buffer.committed":
                        System.out.println("文本缓冲区已提交,项目ID: " + event.get("item_id"));
                        break;
                    case "input_text_buffer.cleared":
                        System.out.println("文本缓冲区已清除");
                        break;
                    case "response.created":
                        System.out.println("响应已创建,ID: " +
                            event.getAsJsonObject("response").get("id").getAsString());
                        responseDoneLatch = new CountDownLatch(1);
                        break;
                    case "response.output_item.added":
                        System.out.println("输出项已添加,ID: " +
                            event.getAsJsonObject("item").get("id").getAsString());
                        break;
                    case "response.audio.delta":
                        if (audioCallback != null) {
                            byte[] audioBytes = Base64.getDecoder().decode(
                                event.get("delta").getAsString());
                            audioCallback.onAudio(audioBytes);
                        }
                        break;
                    case "response.audio.done":
                        System.out.println("音频生成完成");
                        break;
                    case "response.done":
                        System.out.println("响应完成");
                        responseDoneLatch.countDown();
                        break;
                    case "session.finished":
                        System.out.println("会话已结束");
                        sessionFinishedLatch.countDown();
                        break;
                }
            }

            @Override
            public void onClose(int code, String reason, boolean remote) {
                System.out.println("连接已关闭: " + reason);
            }

            @Override
            public void onError(Exception ex) {
                System.err.println("WebSocket 错误: " + ex.getMessage());
            }
        };
        ws.connectBlocking();
    }

    /**
     * 发送事件到服务器。
     */
    public void sendEvent(JsonObject event) {
        String eventId = "event_" + java.util.UUID.randomUUID().toString();
        event.addProperty("event_id", eventId);
        System.out.println("发送事件: type=" + event.get("type").getAsString()
            + ", event_id=" + eventId);
        ws.send(gson.toJson(event));
    }

    /**
     * 更新会话配置。
     */
    public void updateSession(JsonObject config) {
        JsonObject event = new JsonObject();
        event.addProperty("type", "session.update");
        event.add("session", config);
        System.out.println("更新会话配置: " + event);
        sendEvent(event);
    }

    /**
     * 向 API 发送文本数据。
     */
    public void appendText(String text) {
        JsonObject event = new JsonObject();
        event.addProperty("type", "input_text_buffer.append");
        event.addProperty("text", text);
        sendEvent(event);
    }

    /**
     * 提交文本缓冲区以触发处理。
     */
    public void commitTextBuffer() {
        JsonObject event = new JsonObject();
        event.addProperty("type", "input_text_buffer.commit");
        sendEvent(event);
    }

    /**
     * 清除文本缓冲区。
     */
    public void clearTextBuffer() {
        JsonObject event = new JsonObject();
        event.addProperty("type", "input_text_buffer.clear");
        sendEvent(event);
    }

    /**
     * 结束会话。
     */
    public void finishSession() {
        JsonObject event = new JsonObject();
        event.addProperty("type", "session.finish");
        sendEvent(event);
    }

    /**
     * 等待 response.done 事件。
     */
    public void waitForResponseDone() throws InterruptedException {
        responseDoneLatch.await();
    }

    /**
     * 等待 session.finished 事件。
     */
    public void waitForSessionFinished() throws InterruptedException {
        sessionFinishedLatch.await();
    }

    /**
     * 关闭 WebSocket 连接。
     */
    public void close() {
        if (ws != null) {
            ws.close();
        }
    }
}
  1. 选择语音合成模式 Realtime API支持以下两种模式:
    • server_commit 模式:服务端智能判断文本分段与合成时机,客户端只需发送文本。适合低延迟场景(如 GPS 导航)。
    • commit 模式:客户端将文本添加至缓冲区后主动触发合成。适合精细控制断句的场景(如新闻播报)。
  • server_commit 模式
  • commit 模式
  • Java
  • Python
在TTSRealtimeClient.java的同级目录下新建另一个 Java 文件,命名为ServerCommit.java,并将以下代码复制进文件中:
import javax.sound.sampled.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class ServerCommit {
        private static final String URL = "wss://maas.qianwenaiapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime";
    // 获取API Key:https://platform.qianwenai.com/home/api-keys
    // 若没有配置环境变量,请用千问AI平台API Key将下行替换为:private static final String API_KEY = "sk-xxx";
    private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY");
    private static final int SAMPLE_RATE = 24000;

    // 音频数据缓存
    private static final List<byte[]> audioChunks = new ArrayList<>();
    // 实时播放队列
    private static final ConcurrentLinkedQueue<byte[]> playbackQueue = new ConcurrentLinkedQueue<>();
    private static final AtomicBoolean playing = new AtomicBoolean(true);

    public static void main(String[] args) throws Exception {
        if (API_KEY == null || API_KEY.isEmpty()) {
            throw new IllegalStateException("请设置 DASHSCOPE_API_KEY 环境变量");
        }

        // 初始化音频播放
        AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
        SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info);
        audioLine.open(format);
        audioLine.start();

        // 启动播放线程
        Thread playerThread = new Thread(() -> {
            while (playing.get() || !playbackQueue.isEmpty()) {
                byte[] chunk = playbackQueue.poll();
                if (chunk != null) {
                    audioLine.write(chunk, 0, chunk.length);
                } else {
                    try { Thread.sleep(10); } catch (InterruptedException ignored) {}
                }
            }
        });
        playerThread.start();

        // 创建 TTS 客户端
        TTSRealtimeClient client = new TTSRealtimeClient(
            URL, API_KEY, "Cherry",
            TTSRealtimeClient.SessionMode.SERVER_COMMIT,
            audioData -> {
                playbackQueue.add(audioData);
                audioChunks.add(audioData);
                System.out.println("收到音频数据: " + audioData.length + " bytes");
            }
        );

        client.connect();

        // 发送文本片段
        String[] textFragments = {
            "千问AI平台是一站式的大模型开发及应用构建平台。",
            "不论是开发者还是业务人员,都能深入参与大模型应用的设计和构建。",
            "您可以通过简单的界面操作,在5分钟内开发出一款大模型应用,",
            "或在几小时内训练出一个专属模型,从而将更多精力专注于应用创新。"
        };

        System.out.println("开始发送文本...");
        for (String text : textFragments) {
            System.out.println("发送片段: " + text);
            client.appendText(text);
            Thread.sleep(100);
        }

        Thread.sleep(1000);
        client.finishSession();

        // 等待响应完成
        client.waitForResponseDone();
        client.waitForSessionFinished();
        client.close();

        // 等待播放完成
        playing.set(false);
        playerThread.join();
        audioLine.drain();
        audioLine.close();

        // 保存音频文件
        saveWav("output.wav");
        System.out.println("完成");
    }

    private static void saveWav(String filename) throws IOException {
        if (audioChunks.isEmpty()) {
            System.out.println("没有音频数据可保存");
            return;
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        for (byte[] chunk : audioChunks) {
            bos.write(chunk);
        }
        byte[] allAudio = bos.toByteArray();
        AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
        AudioInputStream ais = new AudioInputStream(
            new ByteArrayInputStream(allAudio), format, allAudio.length / 2);
        new File("outputs").mkdirs();
        AudioSystem.write(ais, AudioFileFormat.Type.WAVE,
            new File("outputs/" + filename));
        System.out.println("音频已保存到: outputs/" + filename);
    }
}
编译并运行ServerCommit.java,即可听到 Realtime API实时生成的音频。