全部產品
Search
文件中心

Alibaba Cloud Model Studio:Java SDK

更新時間:Jul 08, 2026

本文介紹 DashScope Java SDK 調用即時語音合成時的關鍵介面與請求參數。

使用者指南:關於模型介紹和選型建議請參見即時語音合成非即時語音合成

前期準備

DashScope Java SDK 版本需要不低於2.22.7。

快速開始

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);
    }
}

訪問github下載更多範例程式碼。

請求參數

下述請求參數可以通過QwenTtsRealtimeParam對象的鏈式方法或setter配置、之後作為參數傳入QwenTtsRealtime的構造方法完成配置。

參數

類型

是否必須

說明

model

String

模型名稱。參見支援的模型

url

String

華北2(北京)地區:wss://dashscope.aliyuncs.com/api-ws/v1/realtime

新加坡地區:wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime

下述請求參數可以通過QwenTtsRealtimeConfig對象的鏈式方法或setter配置、之後作為參數傳入updateSession介面完成配置。

參數

類型

是否必須

說明

voice

String

語音合成所使用的音色。參見支援的音色

支援系統音色和專屬音色:

  • 系統音色:僅限千問3-TTS-Instruct-Flash-Realtime、千問3-TTS-Flash-Realtime和千問-TTS-Realtime系列模型。音色效果請參見:支援的音色

  • 專屬音色

languageType

String

指定合成音訊語種,預設為 Auto

  • Auto:適用無法確定文本的語種或文本包含多種語言的情境,模型會自動為文本中的不同語言片段匹配各自的發音,但無法保證發音完全精準。

  • 指定語種:適用於文本為單一語種的情境,此時指定為具體語種,能顯著提升合成品質,效果通常優於 Auto。可選值包括:

    • Chinese

    • English

    • German

    • Italian

    • Portuguese

    • Spanish

    • Japanese

    • Korean

    • French

    • Russian

mode

String

互動模式,可選值:

  • server_commit(預設):服務端自動判斷合成時機,平衡延遲與品質,推薦大多數情境使用

  • commit:用戶端手動觸發合成,延遲最低,但需自行管理句子完整性

format

String

模型輸出音訊格式。

支援的格式:

  • pcm(預設)

  • wav

  • mp3

  • opus

千問-TTS-Realtime(參見支援的模型僅支援pcm

sampleRate

int

模型輸出音訊採樣率(Hz)。

支援的採樣率:

  • 8000

  • 16000

  • 24000(預設)

  • 48000

千問-TTS-Realtime(參見支援的模型僅支援24000。

speechRate

float

音訊語速。1.0為正常語速,小於1.0為慢速,大於1.0為快速。

預設值:1.0。

取值範圍:[0.5, 2.0]。

千問-TTS-Realtime(參見支援的模型不支援該參數。

volume

int

音訊音量。

預設值:50。

取值範圍:[0, 100]。

千問-TTS-Realtime(參見支援的模型不支援該參數。

pitchRate

float

合成音訊語調。

預設值:1.0。

取值範圍:[0.5, 2.0]。

千問-TTS-Realtime(參見支援的模型不支援該參數。

bitRate

int

指定音訊碼率(kbps)。碼率越大,音質越好,音頻檔案體積越大。僅在音頻格式(response_format)為opus時可用。

預設值:128。

取值範圍:[6, 510]。

千問-TTS-Realtime(參見支援的模型不支援該參數。

instructions

String

設定指令,參見指令控制

預設值:無預設值,不設定不生效。

長度限制:長度不得超過 1600 Token。

支援語言:僅支援中文和英文。

適用範圍:該功能僅適用於千問3-TTS-Instruct-Flash-Realtime系列模型。

optimizeInstructions

boolean

是否對 instructions 進行最佳化,以提升語音合成的自然度和表現力。

預設值:false。

行為說明:當設定為 true 時,系統將對 instructions 的內容進行語義增強與重寫,產生更適合語音合成的內部指令。

適用情境:推薦在追求高品質、精細化語音表達的情境下開啟。

依賴關係:此參數依賴於 instructions 參數被設定。如果 instructions 為空白,此參數不生效。

適用範圍:該功能僅適用於千問3-TTS-Instruct-Flash-Realtime系列模型。

關鍵介面

QwenTtsRealtime

引入方法:

import com.alibaba.dashscope.audio.qwen_tts_realtime.QwenTtsRealtime;

成員方法

方法簽名

服務端響應事件(通過回調下發)

說明

connect

public void connect() throws NoApiKeyException, InterruptedException

session.created

會話已建立

session.updated

會話配置已更新

和服務端建立串連。

updateSession

public void updateSession(QwenTtsRealtimeConfig config)

session.updated

會話配置已更新

更新本次會話互動的預設配置。參數配置請參考《請求參數》章節。

在您建立連結,服務端會及時返回用於此會話的預設輸出輸入配置。如果您需要更新預設會話配置,我們也推薦您總是在建立連結後即刻調用此介面。

服務端在收到session.update事件後,會進行參數校正,如果參數不合法則返回錯誤,否則更新服務端側的會話配置。

appendText

public void appendText(String text)

將文本片段追加到雲端輸入文本緩衝區。 緩衝區是你可以寫入並稍後提交的臨時儲存。

  • "server_commit"模式下,伺服器決定何時提交併合成文本緩衝區中的文本。

  • "commit"模式下,用戶端需要主動通過commit觸發語音合成。

clearAppendedText

public void clearAppendedText() 

input_text_buffer.cleared

清空服務端收到的文本

刪除當前雲端緩衝區的文本。

commit

public void commit()

input_text_buffer.committed

提交文本並觸發語音合成

response.output_item.added

響應時有新的輸出內容

response.content_part.added

新的輸出內容添加到assistant message 項

response.audio.delta

模型增量產生的音頻

response.audio.done

完成音頻產生

response.content_part.done

Assistant message 的音頻內容流式輸出完成

response.output_item.done

Assistant message 的整個輸出項串流完成

response.done

響應完成

提交之前通過append添加到雲端緩衝區的文本,並立刻合成所有文本。如果輸入的文本緩衝區為空白將產生錯誤。

  • "server_commit"模式下,用戶端不需要發送此事件,伺服器會自動認可文本緩衝區。

  • "commit"模式下,用戶端必須通過commit觸發語音合成。

finish

public void finish()

session.finished

響應完成

終止任務。

close

public void close()

關閉串連。

getSessionId

public String getSessionId()

擷取當前任務的session_id。

getResponseId

public String getResponseId() 

擷取最近一次response的response_id。

getFirstAudioDelay

public long getFirstAudioDelay()

擷取首包音頻延遲。

回調介面(QwenTtsRealtimeCallback

方法

參數

傳回值

描述

public void onOpen()

當和服務端建立串連完成後,該方法立刻被回調。

public abstract void onEvent(JsonObject message)

message:服務端響應事件。

包括對介面調用的回複響應和模型產生的文本和音頻。具體可以參考:服務端事件

public abstract void onClose(int code, String reason)

code:關閉WebSocket的狀態代碼。

reason:關閉WebSocket的關閉資訊。

當服務已經關閉串連後進行回調。