即時語音辨識
即時語音辨識服務通過 WebSocket 接收音頻流並即時轉寫為帶標點的文本,適用於直播字幕、線上會議、語音交談、智能助手等情境。
概述
通過 WebSocket 流式協議實現低延遲音頻到文本轉換。
-
支援普通話及粵語、四川話等多種方言的高精度語音辨識
-
具備應對複雜聲學環境的能力,支援自動語種檢測與智能非人聲過濾
-
支援驚訝、平靜、愉快、悲傷、厭惡、憤怒、恐懼等多種情緒狀態識別
-
支援熱詞定製,可提升特定詞彙的識別準確率
-
支援時間戳記輸出,產生結構化識別結果
-
靈活採樣率與多種音頻格式,適配不同錄音環境
前提條件
快速開始
以下樣本展示如何通過 DashScope SDK 快速調用即時語音辨識服務。
Fun-ASR
識別傳入麥克風的語音
識別麥克風傳入的語音並即時輸出文本,實現"邊說邊出字"的效果。
Java
import com.alibaba.dashscope.audio.asr.recognition.Recognition;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionParam;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionResult;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.utils.Constants;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.TargetDataLine;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
// 以下為新加坡地區URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new RealtimeRecognitionTask());
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES);
System.exit(0);
}
}
class RealtimeRecognitionTask implements Runnable {
@Override
public void run() {
RecognitionParam param = RecognitionParam.builder()
.model("fun-asr-realtime")
// 新加坡地區和北京地區的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"))
.format("pcm")
.sampleRate(16000)
.build();
Recognition recognizer = new Recognition();
ResultCallback<RecognitionResult> callback = new ResultCallback<RecognitionResult>() {
@Override
public void onEvent(RecognitionResult result) {
if (result.isSentenceEnd()) {
System.out.println("Final Result: " + result.getSentence().getText());
} else {
System.out.println("Intermediate Result: " + result.getSentence().getText());
}
}
@Override
public void onComplete() {
System.out.println("Recognition complete");
}
@Override
public void onError(Exception e) {
System.out.println("RecognitionCallback error: " + e.getMessage());
}
};
try {
recognizer.call(param, callback);
// 建立音頻格式
AudioFormat audioFormat = new AudioFormat(16000, 16, 1, true, false);
// 根據格式匹配預設錄音裝置
TargetDataLine targetDataLine =
AudioSystem.getTargetDataLine(audioFormat);
targetDataLine.open(audioFormat);
// 開始錄音
targetDataLine.start();
ByteBuffer buffer = ByteBuffer.allocate(1024);
long start = System.currentTimeMillis();
// 錄音50s並進行即時轉寫
while (System.currentTimeMillis() - start < 50000) {
int read = targetDataLine.read(buffer.array(), 0, buffer.capacity());
if (read > 0) {
buffer.limit(read);
// 將錄音音頻資料發送給流式識別服務
recognizer.sendAudioFrame(buffer);
buffer = ByteBuffer.allocate(1024);
// 錄音速率有限,防止cpu佔用過高,休眠一小會兒
Thread.sleep(20);
}
}
recognizer.stop();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 任務結束後關閉 Websocket 串連
recognizer.getDuplexApi().close(1000, "bye");
}
System.out.println(
"[Metric] requestId: "
+ recognizer.getLastRequestId()
+ ", first package delay ms: "
+ recognizer.getFirstPackageDelay()
+ ", last package delay ms: "
+ recognizer.getLastPackageDelay());
}
}Python
運行Python樣本前,需要通過pip install pyaudio命令安裝第三方音頻播放與採集套件。
import os
import signal # for keyboard events handling (press "Ctrl+C" to terminate recording)
import sys
import dashscope
import pyaudio
from dashscope.audio.asr import *
mic = None
stream = None
# Set recording parameters
sample_rate = 16000 # sampling rate (Hz)
channels = 1 # mono channel
dtype = 'int16' # data type
format_pcm = 'pcm' # the format of the audio data
block_size = 3200 # number of frames per buffer
# Real-time speech recognition callback
class Callback(RecognitionCallback):
def on_open(self) -> None:
global mic
global stream
print('RecognitionCallback open.')
mic = pyaudio.PyAudio()
stream = mic.open(format=pyaudio.paInt16,
channels=1,
rate=16000,
input=True)
def on_close(self) -> None:
global mic
global stream
print('RecognitionCallback close.')
stream.stop_stream()
stream.close()
mic.terminate()
stream = None
mic = None
def on_complete(self) -> None:
print('RecognitionCallback completed.') # recognition completed
def on_error(self, message) -> None:
print('RecognitionCallback task_id: ', message.request_id)
print('RecognitionCallback error: ', message.message)
# Stop and close the audio stream if it is running
if 'stream' in globals() and stream.active:
stream.stop()
stream.close()
# Forcefully exit the program
sys.exit(1)
def on_event(self, result: RecognitionResult) -> None:
sentence = result.get_sentence()
if 'text' in sentence:
print('RecognitionCallback text: ', sentence['text'])
if RecognitionResult.is_sentence_end(sentence):
print(
'RecognitionCallback sentence end, request_id:%s, usage:%s'
% (result.get_request_id(), result.get_usage(sentence)))
def signal_handler(sig, frame):
print('Ctrl+C pressed, stop recognition ...')
# Stop recognition
recognition.stop()
print('Recognition stopped.')
print(
'[Metric] requestId: {}, first package delay ms: {}, last package delay ms: {}'
.format(
recognition.get_last_request_id(),
recognition.get_first_package_delay(),
recognition.get_last_package_delay(),
))
# Forcefully exit the program
sys.exit(0)
# main function
if __name__ == '__main__':
# 新加坡地區和北京地區的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')
# 以下為新加坡地區URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'
# Create the recognition callback
callback = Callback()
# Call recognition service by async mode, you can customize the recognition parameters, like model, format,
# sample_rate
recognition = Recognition(
model='fun-asr-realtime',
format=format_pcm,
# 'pcm'、'wav'、'opus'、'speex'、'aac'、'amr', you can check the supported formats in the document
sample_rate=sample_rate,
# support 8000, 16000
semantic_punctuation_enabled=False,
callback=callback)
# Start recognition
recognition.start()
signal.signal(signal.SIGINT, signal_handler)
print("Press 'Ctrl+C' to stop recording and recognition...")
# Create a keyboard listener until "Ctrl+C" is pressed
while True:
if stream:
data = stream.read(3200, exception_on_overflow=False)
recognition.send_audio_frame(data)
else:
break
recognition.stop()識別本地音頻檔案
識別本地音頻檔案並輸出結果,適用於對話聊天、控制口令、語音輸入法、語音搜尋等較短的准即時情境。
Java
樣本中用到的音頻為:asr_example.wav。
import com.alibaba.dashscope.api.GeneralApi;
import com.alibaba.dashscope.audio.asr.recognition.Recognition;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionParam;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionResult;
import com.alibaba.dashscope.base.HalfDuplexParamBase;
import com.alibaba.dashscope.common.GeneralListParam;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.protocol.GeneralServiceOption;
import com.alibaba.dashscope.protocol.HttpMethod;
import com.alibaba.dashscope.protocol.Protocol;
import com.alibaba.dashscope.protocol.StreamingMode;
import com.alibaba.dashscope.utils.Constants;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class TimeUtils {
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
public static String getTimestamp() {
return LocalDateTime.now().format(formatter);
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
// 以下為新加坡地區URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
// 實際應用中,該方法僅在程式最開始執行一次即可,不必多次執行該方法。
warmUp();
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new RealtimeRecognitionTask(Paths.get(System.getProperty("user.dir"), "asr_example.wav")));
executorService.shutdown();
// wait for all tasks to complete
executorService.awaitTermination(1, TimeUnit.MINUTES);
System.exit(0);
}
public static void warmUp() {
try {
// Lightweight GET request to establish connection
GeneralServiceOption warmupOption = GeneralServiceOption.builder()
.protocol(Protocol.HTTP)
.httpMethod(HttpMethod.GET)
.streamingMode(StreamingMode.OUT)
.path("assistants")
.build();
warmupOption.setBaseHttpUrl(Constants.baseHttpApiUrl);
GeneralApi<HalfDuplexParamBase> api = new GeneralApi<>();
api.get(GeneralListParam.builder().limit(1L).build(), warmupOption);
} catch (Exception e) {
// Reset flag to allow retry if pre-warming failed
}
}
}
class RealtimeRecognitionTask implements Runnable {
private Path filepath;
public RealtimeRecognitionTask(Path filepath) {
this.filepath = filepath;
}
@Override
public void run() {
RecognitionParam param = RecognitionParam.builder()
.model("fun-asr-realtime")
// 新加坡地區和北京地區的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"))
.format("wav")
.sampleRate(16000)
.build();
Recognition recognizer = new Recognition();
String threadName = Thread.currentThread().getName();
ResultCallback<RecognitionResult> callback = new ResultCallback<RecognitionResult>() {
@Override
public void onEvent(RecognitionResult message) {
if (message.isSentenceEnd()) {
System.out.println(TimeUtils.getTimestamp()+" "+
"[process " + threadName + "] Final Result:" + message.getSentence().getText());
} else {
System.out.println(TimeUtils.getTimestamp()+" "+
"[process " + threadName + "] Intermediate Result: " + message.getSentence().getText());
}
}
@Override
public void onComplete() {
System.out.println(TimeUtils.getTimestamp()+" "+"[" + threadName + "] Recognition complete");
}
@Override
public void onError(Exception e) {
System.out.println(TimeUtils.getTimestamp()+" "+
"[" + threadName + "] RecognitionCallback error: " + e.getMessage());
}
};
try {
recognizer.call(param, callback);
// Please replace the path with your audio file path
System.out.println(TimeUtils.getTimestamp()+" "+"[" + threadName + "] Input file_path is: " + this.filepath);
// Read file and send audio by chunks
FileInputStream fis = new FileInputStream(this.filepath.toFile());
byte[] allData = new byte[fis.available()];
int ret = fis.read(allData);
fis.close();
int sendFrameLength = 3200;
for (int i = 0; i * sendFrameLength < allData.length; i ++) {
int start = i * sendFrameLength;
int end = Math.min(start + sendFrameLength, allData.length);
ByteBuffer byteBuffer = ByteBuffer.wrap(allData, start, end - start);
recognizer.sendAudioFrame(byteBuffer);
Thread.sleep(100);
}
System.out.println(TimeUtils.getTimestamp()+" "+LocalDateTime.now());
recognizer.stop();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 任務結束後關閉 Websocket 串連
recognizer.getDuplexApi().close(1000, "bye");
}
System.out.println(
"["
+ threadName
+ "][Metric] requestId: "
+ recognizer.getLastRequestId()
+ ", first package delay ms: "
+ recognizer.getFirstPackageDelay()
+ ", last package delay ms: "
+ recognizer.getLastPackageDelay());
}
}Python
樣本中用到的音頻為:asr_example.wav。
import os
import time
import dashscope
from dashscope.audio.asr 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')
# 以下為新加坡地區URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'
from datetime import datetime
def get_timestamp():
now = datetime.now()
formatted_timestamp = now.strftime("[%Y-%m-%d %H:%M:%S.%f]")
return formatted_timestamp
class Callback(RecognitionCallback):
def on_complete(self) -> None:
print(get_timestamp() + ' Recognition completed') # recognition complete
def on_error(self, result: RecognitionResult) -> None:
print('Recognition task_id: ', result.request_id)
print('Recognition error: ', result.message)
exit(0)
def on_event(self, result: RecognitionResult) -> None:
sentence = result.get_sentence()
if 'text' in sentence:
print(get_timestamp() + ' RecognitionCallback text: ', sentence['text'])
if RecognitionResult.is_sentence_end(sentence):
print(get_timestamp() +
'RecognitionCallback sentence end, request_id:%s, usage:%s'
% (result.get_request_id(), result.get_usage(sentence)))
callback = Callback()
recognition = Recognition(model='fun-asr-realtime',
format='wav',
sample_rate=16000,
callback=callback)
try:
audio_data: bytes = None
f = open("asr_example.wav", 'rb')
if os.path.getsize("asr_example.wav"):
# 一次性將檔案資料全部讀入buffer
file_buffer = f.read()
f.close()
print("Start Recognition")
recognition.start()
# 從buffer中間隔3200位元組發送一次
buffer_size = len(file_buffer)
offset = 0
chunk_size = 3200
while offset < buffer_size:
# 計算本次要發送的資料區塊大小
remaining_bytes = buffer_size - offset
current_chunk_size = min(chunk_size, remaining_bytes)
# 從buffer中提取當前資料區塊
audio_data = file_buffer[offset:offset + current_chunk_size]
# 發送音頻資料幀
recognition.send_audio_frame(audio_data)
# 更新位移量
offset += current_chunk_size
# 添加延遲類比即時傳輸
time.sleep(0.1)
recognition.stop()
else:
raise Exception(
'The supplied file was empty (zero bytes long)')
except Exception as e:
raise e
print(
'[Metric] requestId: {}, first package delay ms: {}, last package delay ms: {}'
.format(
recognition.get_last_request_id(),
recognition.get_first_package_delay(),
recognition.get_last_package_delay(),
))Qwen-ASR
範例程式碼讀取 your_audio_file.pcm(PCM16、16 kHz、單聲道)。如僅有 MP3/WAV 等格式,可使用 ffmpeg 轉換:
ffmpeg -i your_audio.mp3 -ar 16000 -ac 1 -f s16le your_audio_file.pcm
Java
import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.LineUnavailableException;
import java.io.File;
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
public class Qwen3AsrRealtimeUsage {
private static final Logger log = LoggerFactory.getLogger(Qwen3AsrRealtimeUsage.class);
private static final int AUDIO_CHUNK_SIZE = 1024; // Audio chunk size in bytes
private static final int SLEEP_INTERVAL_MS = 30; // Sleep interval in milliseconds
public static void main(String[] args) throws InterruptedException, LineUnavailableException {
CountDownLatch finishLatch = new CountDownLatch(1);
OmniRealtimeParam param = OmniRealtimeParam.builder()
.model("qwen3-asr-flash-realtime")
// 以下為新加坡地區WebSocket URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
// 新加坡和北京地區的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"))
.build();
OmniRealtimeConversation conversation = null;
final AtomicReference<OmniRealtimeConversation> conversationRef = new AtomicReference<>(null);
conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() {
@Override
public void onOpen() {
System.out.println("connection opened");
}
@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 "conversation.item.input_audio_transcription.completed":
System.out.println("transcription: " + message.get("transcript").getAsString());
finishLatch.countDown();
break;
case "input_audio_buffer.speech_started":
System.out.println("======VAD Speech Start======");
break;
case "input_audio_buffer.speech_stopped":
System.out.println("======VAD Speech Stop======");
break;
case "conversation.item.input_audio_transcription.text":
System.out.println("transcription: " + message.get("text").getAsString() + message.get("stash").getAsString());
break;
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
System.out.println("connection closed code: " + code + ", reason: " + reason);
}
});
conversationRef.set(conversation);
try {
conversation.connect();
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
}
OmniRealtimeTranscriptionParam transcriptionParam = new OmniRealtimeTranscriptionParam();
transcriptionParam.setLanguage("zh");
transcriptionParam.setInputAudioFormat("pcm");
transcriptionParam.setInputSampleRate(16000);
OmniRealtimeConfig config = OmniRealtimeConfig.builder()
.modalities(Collections.singletonList(OmniRealtimeModality.TEXT))
.transcriptionConfig(transcriptionParam)
.build();
conversation.updateSession(config);
String filePath = "your_audio_file.pcm";
File audioFile = new File(filePath);
if (!audioFile.exists()) {
log.error("Audio file not found: {}", filePath);
return;
}
try (FileInputStream audioInputStream = new FileInputStream(audioFile)) {
byte[] audioBuffer = new byte[AUDIO_CHUNK_SIZE];
int bytesRead;
int totalBytesRead = 0;
log.info("Starting to send audio data from: {}", filePath);
// Read and send audio data in chunks
while ((bytesRead = audioInputStream.read(audioBuffer)) != -1) {
totalBytesRead += bytesRead;
String audioB64 = Base64.getEncoder().encodeToString(audioBuffer);
// Send audio chunk to conversation
conversation.appendAudio(audioB64);
// Add small delay to simulate real-time audio streaming
Thread.sleep(SLEEP_INTERVAL_MS);
}
log.info("Finished sending audio data. Total bytes sent: {}", totalBytesRead);
} catch (Exception e) {
log.error("Error sending audio from file: {}", filePath, e);
}
//send session.finish and wait for finish and close
conversation.endSession();
log.info("task finished");
System.exit(0);
}
}
Constants.baseHttpApiUrl = "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1";Python
import logging
import os
import base64
import signal
import sys
import time
import dashscope
from dashscope.audio.qwen_omni import *
from dashscope.audio.qwen_omni.omni_realtime import TranscriptionParams
def setup_logging():
"""配置日誌輸出"""
logger = logging.getLogger('dashscope')
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.propagate = False
return logger
def init_api_key():
"""初始化 API Key"""
# 新加坡和北京地區的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', 'YOUR_API_KEY')
if dashscope.api_key == 'YOUR_API_KEY':
print('[Warning] Using placeholder API key, set DASHSCOPE_API_KEY environment variable.')
class MyCallback(OmniRealtimeCallback):
"""即時識別回調處理"""
def __init__(self, conversation):
self.conversation = conversation
self.handlers = {
'session.created': self._handle_session_created,
'conversation.item.input_audio_transcription.completed': self._handle_final_text,
'conversation.item.input_audio_transcription.text': self._handle_transcription_text,
'input_audio_buffer.speech_started': lambda r: print('======Speech Start======'),
'input_audio_buffer.speech_stopped': lambda r: print('======Speech Stop======')
}
def on_open(self):
print('Connection opened')
def on_close(self, code, msg):
print(f'Connection closed, code: {code}, msg: {msg}')
def on_event(self, response):
try:
handler = self.handlers.get(response['type'])
if handler:
handler(response)
except Exception as e:
print(f'[Error] {e}')
def _handle_session_created(self, response):
print(f"Start session: {response['session']['id']}")
def _handle_final_text(self, response):
print(f"Final recognized text: {response['transcript']}")
def _handle_transcription_text(self, response):
print(f"Got transcription result: {response['text'] + response['stash']}")
def read_audio_chunks(file_path, chunk_size=3200):
"""按塊讀取音頻檔案"""
with open(file_path, 'rb') as f:
while chunk := f.read(chunk_size):
yield chunk
def send_audio(conversation, file_path, delay=0.1):
"""發送音頻資料"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"Audio file {file_path} does not exist.")
print("Processing audio file... Press 'Ctrl+C' to stop.")
for chunk in read_audio_chunks(file_path):
audio_b64 = base64.b64encode(chunk).decode('ascii')
conversation.append_audio(audio_b64)
time.sleep(delay)
def main():
setup_logging()
init_api_key()
audio_file_path = "./your_audio_file.pcm"
callback = MyCallback(conversation=None)
conversation = OmniRealtimeConversation(
model='qwen3-asr-flash-realtime',
# 以下為新加坡地區WebSocket URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime',
callback=callback,
)
callback.conversation = conversation # 把 conversation 注入回調,用於回調中調用其方法
def handle_exit(sig, frame):
print('Ctrl+C pressed, exiting...')
conversation.close()
sys.exit(0)
signal.signal(signal.SIGINT, handle_exit)
conversation.connect()
transcription_params = TranscriptionParams(
language='zh',
sample_rate=16000,
input_audio_format="pcm"
)
conversation.update_session(
output_modalities=[MultiModality.TEXT],
enable_input_audio_transcription=True,
transcription_params=transcription_params
)
try:
send_audio(conversation, audio_file_path)
# send session.finish and wait for finished and close
conversation.end_session()
except Exception as e:
print(f"Error occurred: {e}")
finally:
conversation.close()
print("Audio processing completed.")
if __name__ == '__main__':
main()
Paraformer
Paraformer範例程式碼和Fun-ASR相似,將model替換成Paraformer模型名即可。
識別配置
Qwen-ASR 互動模式
Qwen-ASR Realtime API 提供兩種互動模式:
-
VAD 模式(預設):服務端自動檢測語音的起點和終點(斷句),適用於即時對話、會議記錄等情境。啟用方式:配置
session.turn_detection參數(預設啟用)。 -
Manual 模式:由用戶端通過發送
input_audio_buffer.commit控制斷句,適用於需要明確控制發送時機的情境(如聊天軟體發送語音)。啟用方式:將session.turn_detection設為 null。
切換互動模式:
-
WebSocket:通過
session.update事件中的turn_detection欄位設定。{ "type": "session.update", "session": { "turn_detection": null } } -
Python SDK:在
update_session方法中通過enable_turn_detection參數設定。conversation.update_session( enable_turn_detection=False ) -
Java SDK:通過
OmniRealtimeConfig.builder()設定enableTurnDetection參數。OmniRealtimeConfig config = OmniRealtimeConfig.builder() .enableTurnDetection(false) .build(); conversation.updateSession(config);
完整的 SDK 程式碼範例請參見Python SDK和Java SDK。WebSocket 事件生命週期請參見事件互動流程。
VAD 斷句配置
VAD(Voice Activity Detection,語音活動檢測)用於判定一段連續語音何時結束,從而觸發"最終識別結果"事件。三類模型均預設啟用服務端 VAD,但參數命名與可調粒度不同:
-
Qwen-ASR:通過
session.turn_detection配置,含silence_duration_ms(靜音持續時間長度閾值,超過則判定 turn 結束,服務端預設800,對話和聊天等需快速斷句的情境推薦設為400)與threshold(VAD 檢測靈敏度,服務端預設0.2)。Qwen-ASR 還支援關閉 VAD 改用用戶端 commit 控制斷句的 Manual 模式,詳見上文 Qwen-ASR 互動模式。 -
Fun-ASR / Paraformer:通過
max_sentence_silence(VAD 斷句靜音閾值,毫秒)配置。當一段語音後的靜音時間長度超過該閾值時,系統判定該句子已結束。
參數名因協議而異(同一含義在 Qwen-ASR 中稱 silence_duration_ms,在 Fun-ASR / Paraformer 中稱 max_sentence_silence)。完整欄位定義請參見API參考。
進階功能
使用熱詞提升準確率
Fun-ASR 和 Paraformer 系列支援通過熱詞提升特定詞彙(品牌名、人名、專有術語等)的識別準確率。
詳細的熱詞配置方法和使用說明,請參見提升識別準確率。
擷取時間戳記
Fun-ASR 和 Paraformer 系列模型預設輸出句級與字級兩種粒度的時間戳記,便於字幕對齊、關鍵詞高亮、卡拉 OK 跟讀等情境。Qwen-ASR Realtime(qwen3-asr-flash-realtime)當前不返回時間戳記資訊,如需時間戳記請使用 Fun-ASR 或 Paraformer。Qwen-ASR 的錄音檔案轉寫模型 qwen3-asr-flash-filetrans 支援字級時間戳記,詳見非即時語音辨識。
時間戳記單位均為毫秒,分兩個層級返回:
-
句級:
payload.output.sentence.begin_time與payload.output.sentence.end_time,標識整句在音頻中的起止時刻。中間結果中end_time可能為null,待句子結束(sentence_end = true)時填充最終值。 -
字級:
payload.output.sentence.words數組,每個元素包含begin_time、end_time、text(該字/詞文本)以及punctuation(該字後跟隨的標點,無則為空白串)。
返回結構樣本(節選):
{
"payload": {
"output": {
"sentence": {
"begin_time": 170,
"end_time": 920,
"text": "好,我知道了",
"sentence_end": true,
"words": [
{ "begin_time": 170, "end_time": 295, "text": "好", "punctuation": "," },
{ "begin_time": 295, "end_time": 503, "text": "我", "punctuation": "" },
{ "begin_time": 503, "end_time": 711, "text": "知道", "punctuation": "" },
{ "begin_time": 711, "end_time": 920, "text": "了", "punctuation": "" }
]
}
}
}
}
以上欄位名以 WebSocket JSON 路徑為準。不同 SDK 暴露上述欄位的命名習慣不同(如字典 key、對象屬性、getter 方法等),完整欄位對照請參見各 SDK 的 API 參考。
完整欄位定義請參見API參考。
情感識別
Qwen-ASR 與 Paraformer 部分模型可在轉寫結果中附帶說話人的情緒狀態,但兩者輸出粒度與開啟方式不同。
Qwen-ASR(qwen3-asr-flash-realtime):固定開啟,無需配置。在 conversation.item.input_audio_transcription.text 與 conversation.item.input_audio_transcription.completed 事件中均通過頂層 emotion 欄位返回,取值為 7 類細粒度情緒:surprised(驚訝)、neutral(平靜)、happy(愉快)、sad(悲傷)、disgusted(厭惡)、angry(憤怒)、fearful(恐懼)。
{
"type": "conversation.item.input_audio_transcription.text",
"emotion": "neutral",
"text": "今天天氣不錯",
"stash": ""
}
Paraformer(paraformer-realtime-8k-v2):僅此一款 Paraformer 模型支援情感識別,結果通過 payload.output.sentence.emo_tag 與 payload.output.sentence.emo_confidence 返回,取值為 3 類極性:positive(正面,如開心、滿意)、negative(負面,如憤怒、沉悶)、neutral(無明顯情感),信賴度範圍 [0.0, 1.0]。
情感識別需同時滿足以下條件才會輸出:
-
模型為
paraformer-realtime-8k-v2。 -
語義斷句關閉:
semantic_punctuation_enabled = false(預設即為 false,無需特別設定)。 -
僅在
sentence_end = true的句子結束事件中返回。
如不希望返回情感識別欄位,可將 semantic_punctuation_enabled 設為 true,此時將啟用語義斷句、不再返回 emo_tag 與 emo_confidence 欄位。
以上欄位名以 WebSocket JSON 路徑為準。不同 SDK 暴露上述欄位的命名習慣不同(如字典 key、對象屬性、getter 方法等),完整欄位對照請參見各 SDK 的 API 參考。
完整欄位定義、取值約束與樣本請參見API參考。
WebSocket 原始協議調用
以下樣本展示如何通過 WebSocket 原始協議直連服務端,適用於不使用 DashScope SDK 的情境。此為最小可運行實現,WebSocket 通訊協定請參見各模型的 API參考。
應用於生產環境
串連複用(WebSocket)
Fun-ASR 和 Paraformer 的 WebSocket 串連支援複用:一個識別任務結束後,無需重建立立串連即可開啟下一個任務。
複用流程:用戶端發送 finish-task,服務端返回 task-finished 後,可重新發送 run-task 開啟新任務。
-
必須等服務端返回
task-finished事件後才可發起新任務。 -
複用串連中的不同任務需要使用不同的
task_id。 -
任務失敗時服務端返回錯誤事件並關閉串連,該串連不可複用。
-
任務結束後 60 秒無新任務,串連自動斷開。
Qwen-ASR Realtime 採用會話模式,每次會話結束後需主動中斷連線,不支援串連複用。
各模型事件說明請參見對應的API參考。
高並發最佳實務
DashScope SDK 內建池化機制,可複用 WebSocket 串連和識別對象,避免頻繁建立銷毀帶來的開銷。目前僅 Paraformer Java SDK 支援此功能。
提升識別效果
-
選擇匹配採樣率的模型:8kHz 電話音頻直接使用 8kHz 模型,避免升採樣到 16kHz 造成的資訊失真。
-
最佳化輸入音頻品質:使用高品質麥克風,確保錄音環境信噪比高、無回聲。可在應用程式層整合降噪(如 RNNoise)、回聲消除(AEC)等演算法做預先處理。
設定容錯策略
-
用戶端重連:用戶端應實現斷線自動重連機制,以應對網路抖動。Python SDK 參考實現如下:
-
捕獲異常:在
Callback類中實現on_error方法。當dashscopeSDK遇到網路錯誤或其他問題時,會調用該方法。 -
狀態通知:當
on_error被觸發時,設定重連訊號。在Python中可以使用threading.Event,它是一種安全執行緒的訊號標誌。 -
重連迴圈:將主邏輯包裹在一個
for迴圈中(例如重試3次)。當檢測到重連訊號後,當前輪次的識別會中斷,清理資源,然後等待幾秒鐘,再次進入迴圈,建立一個全新的串連。
-
-
設定心跳防止串連斷開:當需要與服務端保持長串連時,可將參數heartbeat設定為
true,即使音頻中長時間沒有聲音,與服務端的串連也不會中斷。 -
模型限流:在調用模型介面時請注意模型的限流規則。
支援的模型與地區
新加坡
調用以下模型時,請選擇新加坡地區的API Key:
-
Fun-ASR:fun-asr-realtime(穩定版,當前等同fun-asr-realtime-2025-11-07)、fun-asr-realtime-2025-11-07(快照版)
-
Qwen3-ASR-Flash-Realtime:qwen3-asr-flash-realtime(穩定版,當前等同qwen3-asr-flash-realtime-2025-10-27)、qwen3-asr-flash-realtime-2026-02-10(最新快照版)、qwen3-asr-flash-realtime-2025-10-27(快照版)
華北2(北京)
調用以下模型時,請選擇北京地區的API Key:
-
Fun-ASR:fun-asr-realtime(穩定版,當前等同fun-asr-realtime-2025-11-07)、fun-asr-realtime-2026-02-28(最新快照版)、fun-asr-realtime-2025-11-07(快照版)、fun-asr-realtime-2025-09-15(快照版)
-
fun-asr-flash-8k-realtime(穩定版,當前等同fun-asr-flash-8k-realtime-2026-01-28)、fun-asr-flash-8k-realtime-2026-01-28
-
-
Qwen3-ASR-Flash-Realtime:qwen3-asr-flash-realtime(穩定版,當前等同qwen3-asr-flash-realtime-2025-10-27)、qwen3-asr-flash-realtime-2026-02-10(最新快照版)、qwen3-asr-flash-realtime-2025-10-27(快照版)
-
Paraformer:paraformer-realtime-v2、paraformer-realtime-v1、paraformer-realtime-8k-v2、paraformer-realtime-8k-v1
API參考
常見問題
即時語音辨識支援哪些音頻格式?
Fun-ASR 和 Paraformer 模型支援 pcm、wav、mp3、opus、speex、aac、amr 格式。Qwen-ASR 模型推薦使用 pcm 或 opus 格式;其他格式(如 wav、aac、amr)雖然在 session.update 校正層會被接受,但服務端實際解碼可能失敗,請務必確認音頻流為推薦格式後再發送。
SDK 和 WebSocket API 有什麼區別?該如何選擇?
DashScope SDK 封裝了 WebSocket 串連管理、鑒權、重連等細節,適合快速整合。WebSocket API 直連提供更細粒度的控制能力,適用於 SDK 未覆蓋的程式設計語言或需要自訂串連管理的情境。推薦優先使用 SDK。
如何提升專有名詞的識別準確率?
使用熱詞(Fun-ASR、Paraformer 支援)。詳細的熱詞配置方法和使用說明,請參見提升識別準確率。
串連經常斷開怎麼辦?
建議實現用戶端重連機制,並開啟心跳參數(heartbeat=true)防止長時間無音頻導致串連斷開。詳細的容錯策略請參見應用於生產環境。