O service de reconhecimento de fala em tempo real recebe um fluxo de áudio e o transcreve em texto pontuado instantaneamente. Utilize este recurso para legendas ao vivo, reuniões online, chat de voz, assistentes inteligentes e cenários semelhantes.
Visão geral
Este service processa fluxos de áudio e retorna texto transcrito com baixa latência.
- Oferece alta precisão no reconhecimento de mandarim, além de cantonês, sichuanês e outros dialetos.
- Opera em ambientes acústicos complexos, com detecção automática de idioma e filtragem inteligente de sons que não são fala.
- Identifica diversos estados emocionais, incluindo surpresa, calma, felicidade, tristeza, repulsa, raiva e medo.
- Permite o uso de hotwords personalizadas para aumentar a precisão no reconhecimento de termos específicos.
- Oferece aprimoramento de contexto para melhorar a precisão do reconhecimento mediante o envio de histórico de conversas ou termos de domínio.
- Gera timestamps para produzir resultados de reconhecimento estruturados.
- Aceita taxas de amostragem flexíveis e múltiplos formatos de áudio para se adaptar a diferentes ambientes de gravação.
Para cenários em lote, como transcrição de reuniões, análise de chamadas e geração de legendas, utilize Non-real-time speech recognition. Para obter orientações sobre como escolher um modelo, consulte Speech-to-text.
Pré-requisitos
- Uma chave de API foi Obtain an API key e set as an environment variable.
- Para chamar o service por meio do DashScope SDK, install the latest SDK.
Início rápido
Os exemplos a seguir demonstram como chamar o service de reconhecimento de fala em tempo real usando o DashScope SDK.
Qwen-Audio-3.0-ASR-Flash-Streaming/ Fun-ASR -Realtime
Além do WebSocket, este modelo também suporta o protocolo AOQ. Para integrações no lado do cliente que priorizam latência estável, resiliência em redes instáveis e supressão de ruído e cancelamento de eco full-duplex integrados, recomenda-se o uso do AOQ. Para comparar os protocolos, consulte Realtime API overview.
Reconhecer fala a partir de um microfone
Reconheça a fala captada por um microfone e exiba o texto em tempo real, permitindo que as palavras apareçam conforme o orador fala.
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 {
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your real workspace ID. Configurations differ by region.
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("qwen-audio-3.0-asr-flash-streaming")
// The API Key differs between the Singapore and Beijing regions. Get an API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Model Studio 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);
// Create the audio format
AudioFormat audioFormat = new AudioFormat(16000, 16, 1, true, false);
// Match the default recording device based on the format
TargetDataLine targetDataLine =
AudioSystem.getTargetDataLine(audioFormat);
targetDataLine.open(audioFormat);
// Start recording
targetDataLine.start();
ByteBuffer buffer = ByteBuffer.allocate(1024);
long start = System.currentTimeMillis();
// Record for 50s and perform real-time transcription
while (System.currentTimeMillis() - start < 50000) {
int read = targetDataLine.read(buffer.array(), 0, buffer.capacity());
if (read > 0) {
buffer.limit(read);
// Send the recorded audio data to the streaming recognition service
recognizer.sendAudioFrame(buffer);
buffer = ByteBuffer.allocate(1024);
// The recording rate is limited; sleep for a short while to prevent excessive CPU usage
Thread.sleep(20);
}
}
recognizer.stop();
} catch (Exception e) {
e.printStackTrace();
} finally {
// Close the WebSocket connection after the task is complete
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
Antes de executar o exemplo em Python, instale o toolkit de terceiros para captura e reprodução de áudio com 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.is_active():
stream.stop_stream()
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__':
# The API Key differs between the Singapore and Beijing regions. Get an API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# If you have not configured the environment variable, replace the following line with your Model Studio API Key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. The configuration differs by region.
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='qwen-audio-3.0-asr-flash-streaming',
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()
Reconhecer um arquivo de áudio local
Processe um arquivo de áudio local e obtenha o resultado da transcrição. Esta abordagem é ideal para cenários mais curtos e quase em tempo real, como conversas de chat, comandos de voz, métodos de entrada por voz e busca por voz.
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 {
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your real workspace ID. Configurations differ by region.
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
// In real applications, this method only needs to be executed once at the very beginning of the program; there is no need to execute it multiple times.
warmUp();
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new RealtimeRecognitionTask(Paths.get(System.getProperty("user.dir"), "{YOUR_AUDIO_FILE}")));
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("qwen-audio-3.0-asr-flash-streaming")
// The API Key differs between the Singapore and Beijing regions. Get an API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Model Studio 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 {
// Close the WebSocket connection after the task is complete
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());
}
}
import os
import time
import dashscope
from dashscope.audio.asr import *
# The API Key differs between the Singapore and Beijing regions. Get an API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# If you have not configured the environment variable, replace the following line with your Model Studio API Key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. The configuration differs by region.
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='qwen-audio-3.0-asr-flash-streaming',
format='wav',
sample_rate=16000,
callback=callback)
try:
audio_data: bytes = None
f = open("{YOUR_AUDIO_FILE}", 'rb')
if os.path.getsize("{YOUR_AUDIO_FILE}"):
# Read all the file data into the buffer at once
file_buffer = f.read()
f.close()
print("Start Recognition")
recognition.start()
# Send 3200 bytes from the buffer at a time
buffer_size = len(file_buffer)
offset = 0
chunk_size = 3200
while offset < buffer_size:
# Calculate the size of the data chunk to send this time
remaining_bytes = buffer_size - offset
current_chunk_size = min(chunk_size, remaining_bytes)
# Extract the current data chunk from the buffer
audio_data = file_buffer[offset:offset + current_chunk_size]
# Send the audio data frame
recognition.send_audio_frame(audio_data)
# Update the offset
offset += current_chunk_size
# Add a delay to simulate real-time transmission
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(),
))
Qwen3-ASR-Flash-Realtime
ObservaçãoO código de exemplo lê o arquivo your_audio_file.pcm (PCM16, 16 kHz, mono). Caso você possua apenas arquivos em MP3, WAV ou formatos similares, converta-os usando o ffmpeg:
ffmpeg -i your_audio.mp3 -ar 16000 -ac 1 -f s16le your_audio_file.pcm
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")
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio 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;
byte[] chunk = new byte[bytesRead];
System.arraycopy(audioBuffer, 0, chunk, 0, bytesRead);
String audioB64 = Base64.getEncoder().encodeToString(chunk);
// 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";
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():
"""Configure log output"""
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():
"""Initialize the API Key"""
# The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio 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):
"""Real-time recognition callback handler"""
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):
"""Read the audio file in chunks"""
with open(file_path, 'rb') as f:
while chunk := f.read(chunk_size):
yield chunk
def send_audio(conversation, file_path, delay=0.1):
"""Send audio data"""
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',
# The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime',
callback=callback,
)
callback.conversation = conversation # Inject conversation into the callback so its methods can be called within the callback
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
O código de exemplo do Paraformer é semelhante ao do Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime. Substitua o nome do modelo por um modelo Paraformer.
Configuração de reconhecimento
Modos de interação do Qwen3-ASR-Flash-Realtime
A API em tempo real do Qwen3-ASR-Flash-Realtime oferece dois modos de interação:
- Modo VAD (padrão): O servidor detecta automaticamente o início e o fim da fala (segmentação). Este modo é adequado para conversas em tempo real, anotações de reuniões e cenários semelhantes. Para ativá-lo, configure o parâmetro
session.turn_detection(ativado por padrão). - Modo manual: O cliente controla a segmentação enviando
input_audio_buffer.commit. Use este modo em cenários que exigem controle explícito sobre o envio de áudio, como o envio de mensagens de voz em aplicativos de chat. Para ativá-lo, definasession.turn_detectioncomo null.
Alternar modos de interação:
- WebSocket: Defina o campo
turn_detectionem um eventosession.update.
{
"type": "session.update",
"session": {
"turn_detection": null
}
}
- SDK Python: Configure o parâmetro
enable_turn_detectionno métodoupdate_session.
conversation.update_session(
enable_turn_detection=False
)
- SDK Java: Defina o parâmetro
enableTurnDetectionpor meio deOmniRealtimeConfig.builder().
OmniRealtimeConfig config = OmniRealtimeConfig.builder()
.enableTurnDetection(false)
.build();
conversation.updateSession(config);
Para exemplos completos de código dos SDKs, consulte Qwen-ASR-Realtime Python SDK - API reference e Java SDK. Para o ciclo de vida de eventos WebSocket, veja Event interaction flow.
Configuração de segmentação VAD
A Detecção de Atividade de Voz (VAD) determina quando um segmento contínuo de fala termina, o que aciona o evento de resultado final de reconhecimento. As três famílias de modelos habilitam o VAD no lado do servidor por padrão, mas seus nomes de parâmetros e granularidade de ajuste diferem:
- Qwen-Audio-3.0-ASR-Flash-Streaming / Fun-ASR-Realtime / Paraformer: Configurado via
max_sentence_silence(limiar de silêncio VAD para segmentação, em milissegundos). Quando o silêncio após um segmento de fala excede esse limiar, o sistema considera a frase concluída. - Qwen3-ASR-Flash-Realtime: Configurado por meio de
session.turn_detection, que incluisilence_duration_ms(limiar de duração do silêncio que encerra um turno quando excedido; padrão do servidor800, sendo400recomendado para cenários de conversa e chat que exigem segmentação rápida) ethreshold(sensibilidade de detecção VAD; padrão do servidor0.2). O Qwen3-ASR-Flash-Realtime também suporta o modo Manual, que desativa o VAD e utiliza confirmação no lado do cliente para segmentação. Para mais detalhes, consulte Qwen3-ASR-Flash-Realtime interaction modes acima.
Os nomes dos parâmetros variam conforme o protocolo: o mesmo conceito chama-se max_sentence_silence no Qwen-Audio-3.0-ASR-Flash-Streaming / Fun-ASR-Realtime / Paraformer, e silence_duration_ms no Qwen3-ASR-Flash-Realtime. Para as definições completas dos campos, veja API reference.
Recursos avançados
Melhorar a precisão com palavras-chave
Utilize palavras-chave para aumentar a precisão do reconhecimento de termos específicos, como nomes de marcas, nomes próprios e terminologia técnica.
Para obter detalhes sobre a configuração e o uso de palavras-chave, consulte Improve recognition accuracy.
Aumentar a precisão com aprimoramento de contexto
O aprimoramento de contexto fornece histórico de conversas ou terminologia de domínio ao modelo ASR, melhorando significativamente a precisão da transcrição de termos específicos. Para instruções detalhadas e exemplos de resultados, veja Context enhancement.
Obter carimbos de data/hora
As famílias de modelos Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime e Paraformer retornam carimbos de data/hora nos níveis de frase e de palavra por padrão. Isso permite alinhamento de legendas, destaque de palavras-chave, leitura acompanhada estilo karaokê e outros casos de uso. Atualmente, o Qwen3-ASR-Flash-Realtime (qwen3-asr-flash-realtime) não retorna carimbos de data/hora. Caso precise dessa funcionalidade, utilize Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime ou Paraformer. Para transcrição de arquivos, o modelo de transcrição de gravações Qwen ASR qwen3-asr-flash-filetrans suporta carimbos de data/hora no nível de palavra. Para mais informações, consulte Non-real-time speech recognition.
Os carimbos de data/hora são retornados em milissegundos em dois níveis:
- Nível de frase:
payload.output.sentence.begin_timeepayload.output.sentence.end_timemarcam o início e o fim de uma frase completa no áudio. Em resultados intermediários,end_timepode sernulle recebe o valor definitivo quando a frase termina (sentence_end = true). - Nível de palavra: O array
payload.output.sentence.words, onde cada elemento contémbegin_time,end_time,text(texto da palavra ou caractere) epunctuation(pontuação seguinte à palavra, ou string vazia se não houver).
O trecho abaixo ilustra a estrutura da resposta:
{
"payload": {
"output": {
"sentence": {
"begin_time": 170,
"end_time": 920,
"text": "OK, I got it",
"sentence_end": true,
"words": [
{ "begin_time": 170, "end_time": 295, "text": "OK", "punctuation": "," },
{ "begin_time": 295, "end_time": 503, "text": "I", "punctuation": "" },
{ "begin_time": 503, "end_time": 711, "text": "got", "punctuation": "" },
{ "begin_time": 711, "end_time": 920, "text": "it", "punctuation": "" }
]
}
}
}
}
Os nomes de campo acima seguem os caminhos JSON do WebSocket. Diferentes SDKs expõem esses campos com suas próprias convenções de nomenclatura (chaves de dicionário, propriedades de objeto, métodos getter, etc.). Para o mapeamento completo de campos, consulte a referência da API de cada SDK.
Para as definições completas dos campos, veja API reference.
Reconhecimento de emoções
O Qwen3-ASR-Flash-Realtime e alguns modelos Paraformer podem incluir o estado emocional do falante no resultado da transcrição, porém diferem na granularidade da saída e na forma de ativação do recurso.
Qwen3-ASR-Flash-Realtime (qwen3-asr-flash-realtime): Sempre ativo, sem necessidade de configuração. A emoção é retornada por meio de um campo emotion de nível superior tanto nos eventos conversation.item.input_audio_transcription.text quanto conversation.item.input_audio_transcription.completed. O valor corresponde a uma das sete emoções detalhadas: surprised, neutral, happy, sad, disgusted, angry e fearful.
{
"type": "conversation.item.input_audio_transcription.text",
"emotion": "neutral",
"text": "The weather is nice today",
"stash": ""
}
Paraformer (paraformer-realtime-8k-v2): Único modelo Paraformer com suporte a reconhecimento de emoções. O resultado é retornado via payload.output.sentence.emo_tag e payload.output.sentence.emo_confidence. O valor representa uma de três polaridades: positive (como feliz ou satisfeito), negative (como irritado ou abatido) e neutral (sem emoção clara). A confiança varia de 0,0 a 1,0.
O reconhecimento de emoções só é retornado quando todas as condições abaixo são atendidas:
- O modelo é
paraformer-realtime-8k-v2. - Segmentação semântica desativada:
semantic_punctuation_enabled = false(false é o padrão, portanto nenhuma configuração especial é necessária). - O resultado aparece apenas no evento de fim de frase, onde
sentence_end = true.
Para interromper o retorno dos campos de emoção, defina semantic_punctuation_enabled como true. Essa ação ativa a segmentação semântica e deixa de retornar os campos emo_tag e emo_confidence.
Os nomes de campo acima seguem os caminhos JSON do WebSocket. Diferentes SDKs expõem esses campos com suas próprias convenções de nomenclatura (chaves de dicionário, propriedades de objeto, métodos getter, etc.). Para o mapeamento completo de campos, consulte a referência da API de cada SDK.
Para definições completas dos campos, restrições de valores e exemplos, veja API reference.
Filtragem de palavras sensíveis
A filtragem de palavras sensíveis substitui ou remove termos sensíveis no resultado do reconhecimento. Aplique este recurso em inspeções de qualidade de call center, conformidade de conteúdo, revisão de legendas e situações afins.
Modelos suportados: Apenas Qwen-Audio-3.0-ASR-Flash-Streaming e Fun-ASR-Realtime.
Limite: É possível definir até 32 palavras sensíveis.
Comportamento padrão: Se o parâmetro special_word_filter não for enviado, nenhuma palavra sensível será filtrada.
Como configurar: special_word_filter é um objeto JSON com três subcampos:
filter_with_signed.word_list: Array de strings que lista as palavras sensíveis a serem substituídas por uma sequência de caracteres*de igual comprimento. Por exemplo, com["test"], "Help me test it" torna-se "Help me **** it".filter_with_empty.word_list: Array de strings que lista as palavras sensíveis a serem removidas completamente do resultado. Por exemplo, com["start"], "Is the game about to start" torna-se "Is the game about to".system_reserved_filter: Valor booleano cujo padrão éfalse. Determina se a filtragem de palavras sensíveis está ativada.
Exemplo de configuração:
{
"special_word_filter": {
"filter_with_signed": {
"word_list": ["test"]
},
"filter_with_empty": {
"word_list": ["start", "occur"]
},
"system_reserved_filter": true
}
}
Diferentes SDKs expõem esses parâmetros com suas próprias convenções de nomenclatura (chaves de dicionário, propriedades de objeto, métodos, etc.). Para o mapeamento completo de campos, consulte a referência da API.
Chamar o protocolo WebSocket nativo
Os exemplos a seguir demonstram como se conectar diretamente ao servidor usando o protocolo WebSocket nativo, destinados a cenários que não utilizam o DashScope SDK. Cada exemplo consiste em uma implementação mínima e executável. Para obter detalhes sobre o protocolo WebSocket, consulte a API reference de cada modelo.
Clique para visualizar exemplos do protocolo WebSocket nativo
Tab
Python
Antes de executar o exemplo, instale as dependências com os seguintes comandos:
pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
Não nomeie o arquivo do exemplo como websocket.py. Esse nome entra em conflito com a biblioteca websocket e causa o seguinte erro: AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?.
# pip install websocket-client
import os
import json
import time
import uuid
import threading
import websocket
# The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: api_key = "sk-xxx"
api_key = os.environ.get('DASHSCOPE_API_KEY')
# The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference' # WebSocket server address
audio_file = '{YOUR_AUDIO_FILE}' # Replace with the path to your audio file
# Generate a 32-character random ID
TASK_ID = uuid.uuid4().hex[:32]
task_started = False # Flag indicating whether the task has started
# Send the run-task instruction
def send_run_task(ws):
run_task_message = {
'header': {
'action': 'run-task',
'task_id': TASK_ID,
'streaming': 'duplex'
},
'payload': {
'task_group': 'audio',
'task': 'asr',
'function': 'recognition',
'model': 'qwen-audio-3.0-asr-flash-streaming',
'parameters': {
'sample_rate': 16000,
'format': 'wav'
},
'input': {}
}
}
ws.send(json.dumps(run_task_message))
# Send the finish-task instruction
def send_finish_task(ws):
finish_task_message = {
'header': {
'action': 'finish-task',
'task_id': TASK_ID,
'streaming': 'duplex'
},
'payload': {
'input': {}
}
}
ws.send(json.dumps(finish_task_message))
# Send the audio stream (send one binary chunk every 100ms)
def send_audio_stream(ws):
chunk_size = 3200 # 100ms @ 16kHz 16bit mono
try:
with open(audio_file, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
ws.send(chunk, opcode=websocket.ABNF.OPCODE_BINARY)
time.sleep(0.1)
print('Audio stream ended')
send_finish_task(ws)
except Exception as e:
print('Error reading audio file:', e)
ws.close()
# Send the run-task instruction when the connection opens
def on_open(ws):
print('Connected to server')
send_run_task(ws)
# Handle received messages
def on_message(ws, data):
global task_started
message = json.loads(data)
event = message['header']['event']
if event == 'task-started':
print('Task started')
task_started = True
threading.Thread(target=send_audio_stream, args=(ws,), daemon=True).start()
elif event == 'result-generated':
print('Recognition result:', message['payload']['output']['sentence']['text'])
if message['payload'].get('usage'):
print('Task billing duration (seconds):', message['payload']['usage']['duration'])
elif event == 'task-finished':
print('Task finished')
ws.close()
elif event == 'task-failed':
print('Task failed:', message['header'].get('error_message'))
ws.close()
else:
print('Unknown event:', event)
# Close the connection if the task-started event is not received
def on_close(ws, close_status_code, close_msg):
if not task_started:
print('Task not started, closing connection')
# Error handling
def on_error(ws, error):
print('WebSocket error:', error)
if __name__ == '__main__':
ws = websocket.WebSocketApp(
url,
header={'Authorization': f'bearer {api_key}'},
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
ws.run_forever()
Java
Antes de executar o exemplo, instale a dependência Java-WebSocket:
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
implementation 'org.java-websocket:Java-WebSocket:1.5.6'
implementation 'org.json:json:20240303'
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
public class FunASRRealtimeClient {
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: private static final String API_KEY = "sk-xxx";
private static final String API_KEY = System.getenv().getOrDefault("DASHSCOPE_API_KEY", "sk-xxx");
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
private static final String URL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
private static final String AUDIO_FILE = "{YOUR_AUDIO_FILE}"; // Replace with the path to your audio file
private static final String MODEL = "qwen-audio-3.0-asr-flash-streaming";
// Generate a 32-character random ID
private static final String TASK_ID = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
private static final AtomicBoolean taskStarted = new AtomicBoolean(false);
private static WebSocketClient client;
public static void main(String[] args) throws Exception {
client = new WebSocketClient(new URI(URL)) {
@Override
public void onOpen(ServerHandshake handshake) {
System.out.println("Connected to server");
sendRunTask();
}
@Override
public void onMessage(String data) {
JSONObject message = new JSONObject(data);
String event = message.getJSONObject("header").getString("event");
switch (event) {
case "task-started":
System.out.println("Task started");
taskStarted.set(true);
new Thread(FunASRRealtimeClient::sendAudioStream).start();
break;
case "result-generated":
JSONObject payload = message.getJSONObject("payload");
String text = payload.getJSONObject("output").getJSONObject("sentence").getString("text");
System.out.println("Recognition result: " + text);
if (payload.has("usage")) {
System.out.println("Task billing duration (seconds): " + payload.getJSONObject("usage").get("duration"));
}
break;
case "task-finished":
System.out.println("Task finished");
close();
break;
case "task-failed":
String errMsg = message.getJSONObject("header").optString("error_message");
System.err.println("Task failed: " + errMsg);
close();
break;
default:
System.out.println("Unknown event: " + event);
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
if (!taskStarted.get()) {
System.err.println("Task not started, closing connection");
}
}
@Override
public void onError(Exception ex) {
System.err.println("WebSocket error: " + ex.getMessage());
}
};
client.addHeader("Authorization", "bearer " + API_KEY);
client.connectBlocking();
}
// Send the run-task instruction
private static void sendRunTask() {
JSONObject runTask = new JSONObject()
.put("header", new JSONObject()
.put("action", "run-task")
.put("task_id", TASK_ID)
.put("streaming", "duplex"))
.put("payload", new JSONObject()
.put("task_group", "audio")
.put("task", "asr")
.put("function", "recognition")
.put("model", MODEL)
.put("parameters", new JSONObject()
.put("sample_rate", 16000)
.put("format", "wav"))
.put("input", new JSONObject()));
client.send(runTask.toString());
}
// Send the audio stream (send one binary chunk every 100ms)
private static void sendAudioStream() {
int chunkSize = 3200; // 100ms @ 16kHz 16bit mono
try {
byte[] audio = Files.readAllBytes(Paths.get(AUDIO_FILE));
int offset = 0;
while (offset < audio.length) {
int end = Math.min(offset + chunkSize, audio.length);
byte[] chunk = new byte[end - offset];
System.arraycopy(audio, offset, chunk, 0, end - offset);
client.send(ByteBuffer.wrap(chunk));
offset = end;
Thread.sleep(100);
}
System.out.println("Audio stream ended");
sendFinishTask();
} catch (Exception e) {
System.err.println("Error reading audio file: " + e.getMessage());
client.close();
}
}
// Send the finish-task instruction
private static void sendFinishTask() {
JSONObject finishTask = new JSONObject()
.put("header", new JSONObject()
.put("action", "finish-task")
.put("task_id", TASK_ID)
.put("streaming", "duplex"))
.put("payload", new JSONObject()
.put("input", new JSONObject()));
client.send(finishTask.toString());
}
}
Node.js
Instale as dependências necessárias:
npm install ws
npm install uuid
O código do exemplo é o seguinte:
const fs = require('fs');
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid'); // Used to generate a UUID
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: const apiKey = "sk-xxx"
const apiKey = process.env.DASHSCOPE_API_KEY;
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
const url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'; // WebSocket server address
const audioFile = '{YOUR_AUDIO_FILE}'; // Replace with the path to your audio file
// Generate a 32-character random ID
const TASK_ID = uuidv4().replace(/-/g, '').slice(0, 32);
// Create the WebSocket client
const ws = new WebSocket(url, {
headers: {
Authorization: `bearer ${apiKey}`
}
});
let taskStarted = false; // Flag indicating whether the task has started
// Send the run-task instruction when the connection opens
ws.on('open', () => {
console.log('Connected to server');
sendRunTask();
});
// Handle received messages
ws.on('message', (data) => {
const message = JSON.parse(data);
switch (message.header.event) {
case 'task-started':
console.log('Task started');
taskStarted = true;
sendAudioStream();
break;
case 'result-generated':
console.log('Recognition result:', message.payload.output.sentence.text);
if (message.payload.usage) {
console.log('Task billing duration (seconds):', message.payload.usage.duration);
}
break;
case 'task-finished':
console.log('Task finished');
ws.close();
break;
case 'task-failed':
console.error('Task failed:', message.header.error_message);
ws.close();
break;
default:
console.log('Unknown event:', message.header.event);
}
});
// Close the connection if the task-started event is not received
ws.on('close', () => {
if (!taskStarted) {
console.error('Task not started, closing connection');
}
});
// Send the run-task instruction
function sendRunTask() {
const runTaskMessage = {
header: {
action: 'run-task',
task_id: TASK_ID,
streaming: 'duplex'
},
payload: {
task_group: 'audio',
task: 'asr',
function: 'recognition',
model: 'qwen-audio-3.0-asr-flash-streaming',
parameters: {
sample_rate: 16000,
format: 'wav'
},
input: {}
}
};
ws.send(JSON.stringify(runTaskMessage));
}
// Send the audio stream
function sendAudioStream() {
const audioStream = fs.createReadStream(audioFile);
let chunkCount = 0;
function sendNextChunk() {
const chunk = audioStream.read();
if (chunk) {
ws.send(chunk);
chunkCount++;
setTimeout(sendNextChunk, 100); // Send once every 100ms
}
}
audioStream.on('readable', () => {
sendNextChunk();
});
audioStream.on('end', () => {
console.log('Audio stream ended');
sendFinishTask();
});
audioStream.on('error', (err) => {
console.error('Error reading audio file:', err);
ws.close();
});
}
// Send the finish-task instruction
function sendFinishTask() {
const finishTaskMessage = {
header: {
action: 'finish-task',
task_id: TASK_ID,
streaming: 'duplex'
},
payload: {
input: {}
}
};
ws.send(JSON.stringify(finishTaskMessage));
}
// Error handling
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
C#
O código do exemplo é o seguinte:
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
class Program {
private static ClientWebSocket _webSocket = new ClientWebSocket();
private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private static bool _taskStartedReceived = false;
private static bool _taskFinishedReceived = false;
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: private static readonly string ApiKey = "sk-xxx"
private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
private const string WebSocketUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
// Replace with the path to your audio file
private const string AudioFilePath = "{YOUR_AUDIO_FILE}";
static async Task Main(string[] args) {
// Establish the WebSocket connection and configure headers for authentication
_webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
await _webSocket.ConnectAsync(new Uri(WebSocketUrl), _cancellationTokenSource.Token);
// Start a thread to receive WebSocket messages asynchronously
var receiveTask = ReceiveMessagesAsync();
// Send the run-task instruction
string _taskId = Guid.NewGuid().ToString("N"); // Generate a 32-character random ID
var runTaskJson = GenerateRunTaskJson(_taskId);
await SendAsync(runTaskJson);
// Wait for the task-started event
while (!_taskStartedReceived) {
await Task.Delay(100, _cancellationTokenSource.Token);
}
// Read the local file and send the audio stream to be recognized to the server
await SendAudioStreamAsync(AudioFilePath);
// Send the finish-task instruction to end the task
var finishTaskJson = GenerateFinishTaskJson(_taskId);
await SendAsync(finishTaskJson);
// Wait for the task-finished event
while (!_taskFinishedReceived && !_cancellationTokenSource.IsCancellationRequested) {
try {
await Task.Delay(100, _cancellationTokenSource.Token);
} catch (OperationCanceledException) {
// The task has been canceled, exit the loop
break;
}
}
// Close the connection
if (!_cancellationTokenSource.IsCancellationRequested) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token);
}
_cancellationTokenSource.Cancel();
try {
await receiveTask;
} catch (OperationCanceledException) {
// Ignore the operation canceled exception
}
}
private static async Task ReceiveMessagesAsync() {
try {
while (_webSocket.State == WebSocketState.Open && !_cancellationTokenSource.IsCancellationRequested) {
var message = await ReceiveMessageAsync(_cancellationTokenSource.Token);
if (message != null) {
var eventValue = message["header"]?["event"]?.GetValue<string>();
switch (eventValue) {
case "task-started":
Console.WriteLine("Task started successfully");
_taskStartedReceived = true;
break;
case "result-generated":
Console.WriteLine($"Recognition result: {message["payload"]?["output"]?["sentence"]?["text"]?.GetValue<string>()}");
if (message["payload"]?["usage"] != null && message["payload"]?["usage"]?["duration"] != null) {
Console.WriteLine($"Task billing duration (seconds): {message["payload"]?["usage"]?["duration"]?.GetValue<int>()}");
}
break;
case "task-finished":
Console.WriteLine("Task finished");
_taskFinishedReceived = true;
_cancellationTokenSource.Cancel();
break;
case "task-failed":
Console.WriteLine($"Task failed: {message["header"]?["error_message"]?.GetValue<string>()}");
_cancellationTokenSource.Cancel();
break;
}
}
}
} catch (OperationCanceledException) {
// Ignore the operation canceled exception
}
}
private static async Task<JsonNode?> ReceiveMessageAsync(CancellationToken cancellationToken) {
var buffer = new byte[1024 * 4];
var segment = new ArraySegment<byte>(buffer);
var sb = new StringBuilder();
WebSocketReceiveResult result;
// Receive and concatenate fragments until EndOfMessage to avoid JSON parsing failures on long messages
do {
result = await _webSocket.ReceiveAsync(segment, cancellationToken);
if (result.MessageType == WebSocketMessageType.Close) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
return null;
}
sb.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
} while (!result.EndOfMessage);
return JsonNode.Parse(sb.ToString());
}
private static async Task SendAsync(string message) {
var buffer = Encoding.UTF8.GetBytes(message);
var segment = new ArraySegment<byte>(buffer);
await _webSocket.SendAsync(segment, WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
}
private static async Task SendAudioStreamAsync(string filePath) {
using (var audioStream = File.OpenRead(filePath)) {
var buffer = new byte[1024]; // Send 100ms of audio data each time
int bytesRead;
while ((bytesRead = await audioStream.ReadAsync(buffer, 0, buffer.Length)) > 0) {
var segment = new ArraySegment<byte>(buffer, 0, bytesRead);
await _webSocket.SendAsync(segment, WebSocketMessageType.Binary, true, _cancellationTokenSource.Token);
await Task.Delay(100); // 100ms interval
}
}
}
private static string GenerateRunTaskJson(string taskId) {
var runTask = new JsonObject {
["header"] = new JsonObject {
["action"] = "run-task",
["task_id"] = taskId,
["streaming"] = "duplex"
},
["payload"] = new JsonObject {
["task_group"] = "audio",
["task"] = "asr",
["function"] = "recognition",
["model"] = "qwen-audio-3.0-asr-flash-streaming",
["parameters"] = new JsonObject {
["format"] = "wav",
["sample_rate"] = 16000,
},
["input"] = new JsonObject()
}
};
return JsonSerializer.Serialize(runTask);
}
private static string GenerateFinishTaskJson(string taskId) {
var finishTask = new JsonObject {
["header"] = new JsonObject {
["action"] = "finish-task",
["task_id"] = taskId,
["streaming"] = "duplex"
},
["payload"] = new JsonObject {
["input"] = new JsonObject()
}
};
return JsonSerializer.Serialize(finishTask);
}
}
PHP
O projeto de exemplo possui a seguinte estrutura de diretórios:
my-php-project/
├── composer.json
├── vendor/
└── index.php
O conteúdo do arquivo composer.json está abaixo. Ajuste as versões das dependências conforme necessário:
{
"require": {
"react/event-loop": "^1.3",
"react/socket": "^1.11",
"react/stream": "^1.2",
"react/http": "^1.1",
"ratchet/pawl": "^0.4"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
O conteúdo do arquivo index.php é o seguinte:
<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;
use Ratchet\rfc6455\Messaging\Frame;
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: $api_key = "sk-xxx"
$api_key = getenv("DASHSCOPE_API_KEY");
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
$websocket_url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference';
$audio_file_path = '{YOUR_AUDIO_FILE}'; // Replace with the path to your audio file
$loop = Loop::get();
// Create a custom connector
$socketConnector = new SocketConnector($loop, [
'tcp' => [
'bindto' => '0.0.0.0:0',
],
// WARNING: Disabling TLS certificate verification exposes you to man-in-the-middle attacks. Use for local debugging only; always set verify_peer/verify_peer_name to true in production.
'tls' => [
'verify_peer' => false,
'verify_peer_name' => false,
],
]);
$connector = new Connector($loop, $socketConnector);
$headers = [
'Authorization' => 'bearer ' . $api_key
];
$connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $audio_file_path) {
echo "Connected to WebSocket server\n";
// Start a thread to receive WebSocket messages asynchronously
$conn->on('message', function($msg) use ($conn, $loop, $audio_file_path) {
$response = json_decode($msg, true);
if (isset($response['header']['event'])) {
handleEvent($conn, $response, $loop, $audio_file_path);
} else {
echo "Unknown message format\n";
}
});
// Listen for the connection close
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed\n";
if ($code !== null) {
echo "Close code: " . $code . "\n";
}
if ($reason !== null) {
echo "Close reason: " . $reason . "\n";
}
});
// Generate the task ID
$taskId = generateTaskId();
// Send the run-task instruction
sendRunTaskMessage($conn, $taskId);
}, function ($e) {
echo "Unable to connect: {$e->getMessage()}\n";
});
$loop->run();
/**
* Generate the task ID
* @return string
*/
function generateTaskId(): string {
return bin2hex(random_bytes(16));
}
/**
* Send the run-task instruction
* @param $conn
* @param $taskId
*/
function sendRunTaskMessage($conn, $taskId) {
$runTaskMessage = json_encode([
"header" => [
"action" => "run-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"task_group" => "audio",
"task" => "asr",
"function" => "recognition",
"model" => "qwen-audio-3.0-asr-flash-streaming",
"parameters" => [
"format" => "wav",
"sample_rate" => 16000
],
"input" => []
]
]);
echo "Preparing to send the run-task instruction: " . $runTaskMessage . "\n";
$conn->send($runTaskMessage);
echo "run-task instruction sent\n";
}
/**
* Read the audio file
* @param string $filePath
* @return bool|string
*/
function readAudioFile(string $filePath) {
$voiceData = file_get_contents($filePath);
if ($voiceData === false) {
echo "Unable to read the audio file\n";
}
return $voiceData;
}
/**
* Split the audio data
* @param string $data
* @param int $chunkSize
* @return array
*/
function splitAudioData(string $data, int $chunkSize): array {
return str_split($data, $chunkSize);
}
/**
* Send the finish-task instruction
* @param $conn
* @param $taskId
*/
function sendFinishTaskMessage($conn, $taskId) {
$finishTaskMessage = json_encode([
"header" => [
"action" => "finish-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"input" => []
]
]);
echo "Preparing to send the finish-task instruction: " . $finishTaskMessage . "\n";
$conn->send($finishTaskMessage);
echo "finish-task instruction sent\n";
}
/**
* Handle events
* @param $conn
* @param $response
* @param $loop
* @param $audio_file_path
*/
function handleEvent($conn, $response, $loop, $audio_file_path) {
static $taskId;
static $chunks;
static $allChunksSent = false;
if (is_null($taskId)) {
$taskId = generateTaskId();
}
switch ($response['header']['event']) {
case 'task-started':
echo "Task started, sending audio data...\n";
// Read the audio file
$voiceData = readAudioFile($audio_file_path);
if ($voiceData === false) {
echo "Unable to read the audio file\n";
$conn->close();
return;
}
// Split the audio data
$chunks = splitAudioData($voiceData, 1024);
// Define the send function
$sendChunk = function() use ($conn, &$chunks, $loop, &$sendChunk, &$allChunksSent, $taskId) {
if (!empty($chunks)) {
$chunk = array_shift($chunks);
$binaryMsg = new Frame($chunk, true, Frame::OP_BINARY);
$conn->send($binaryMsg);
// Send the next chunk after 100ms
$loop->addTimer(0.1, $sendChunk);
} else {
echo "All data chunks sent\n";
$allChunksSent = true;
// Send the finish-task instruction
sendFinishTaskMessage($conn, $taskId);
}
};
// Start sending audio data
$sendChunk();
break;
case 'result-generated':
$result = $response['payload']['output']['sentence'];
echo "Recognition result: " . $result['text'] . "\n";
if (isset($response['payload']['usage']['duration'])) {
echo "Task billing duration (seconds): " . $response['payload']['usage']['duration'] . "\n";
}
break;
case 'task-finished':
echo "Task finished\n";
$conn->close();
break;
case 'task-failed':
echo "Task failed\n";
echo "Error code: " . $response['header']['error_code'] . "\n";
echo "Error message: " . $response['header']['error_message'] . "\n";
$conn->close();
break;
case 'error':
echo "Error: " . $response['payload']['message'] . "\n";
break;
default:
echo "Unknown event: " . $response['header']['event'] . "\n";
break;
}
// If all data has been sent and the task has finished, close the connection
if ($allChunksSent && $response['header']['event'] == 'task-finished') {
// Wait 1 second to ensure all data has been transmitted
$loop->addTimer(1, function() use ($conn) {
$conn->close();
echo "Client closed the connection\n";
});
}
}
Go
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
const (
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
wsURL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference" // WebSocket server address
audioFile = "{YOUR_AUDIO_FILE}" // Replace with the path to your audio file
)
var dialer = websocket.DefaultDialer
func main() {
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: apiKey := "sk-xxx"
apiKey := os.Getenv("DASHSCOPE_API_KEY")
// Connect to the WebSocket service
conn, err := connectWebSocket(apiKey)
if err != nil {
log.Fatal("Failed to connect to WebSocket: ", err)
}
defer closeConnection(conn)
// Start a goroutine to receive results
taskStarted := make(chan bool)
taskDone := make(chan bool)
startResultReceiver(conn, taskStarted, taskDone)
// Send the run-task instruction
taskID, err := sendRunTaskCmd(conn)
if err != nil {
log.Fatal("Failed to send the run-task instruction: ", err)
}
// Wait for the task-started event
waitForTaskStarted(taskStarted)
// Send the audio file stream to be recognized
if err := sendAudioData(conn); err != nil {
log.Fatal("Failed to send audio: ", err)
}
// Send the finish-task instruction
if err := sendFinishTaskCmd(conn, taskID); err != nil {
log.Fatal("Failed to send the finish-task instruction: ", err)
}
// Wait for the task to finish or fail
<-taskDone
}
// Define structs to represent the JSON data
type Header struct {
Action string `json:"action"`
TaskID string `json:"task_id"`
Streaming string `json:"streaming"`
Event string `json:"event"`
ErrorCode string `json:"error_code,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
Attributes map[string]interface{} `json:"attributes"`
}
type Output struct {
Sentence struct {
BeginTime int64 `json:"begin_time"`
EndTime *int64 `json:"end_time"`
Text string `json:"text"`
Words []struct {
BeginTime int64 `json:"begin_time"`
EndTime *int64 `json:"end_time"`
Text string `json:"text"`
Punctuation string `json:"punctuation"`
} `json:"words"`
} `json:"sentence"`
}
type Payload struct {
TaskGroup string `json:"task_group"`
Task string `json:"task"`
Function string `json:"function"`
Model string `json:"model"`
Parameters Params `json:"parameters"`
Input Input `json:"input"`
Output Output `json:"output,omitempty"`
Usage *struct {
Duration int `json:"duration"`
} `json:"usage,omitempty"`
}
type Params struct {
Format string `json:"format"`
SampleRate int `json:"sample_rate"`
DisfluencyRemovalEnabled bool `json:"disfluency_removal_enabled"`
}
type Input struct {
}
type Event struct {
Header Header `json:"header"`
Payload Payload `json:"payload"`
}
// Connect to the WebSocket service
func connectWebSocket(apiKey string) (*websocket.Conn, error) {
header := make(http.Header)
header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))
conn, _, err := dialer.Dial(wsURL, header)
return conn, err
}
// Start a goroutine to receive WebSocket messages asynchronously
func startResultReceiver(conn *websocket.Conn, taskStarted chan<- bool, taskDone chan<- bool) {
go func() {
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("Failed to parse server message: ", err)
return
}
var event Event
err = json.Unmarshal(message, &event)
if err != nil {
log.Println("Failed to parse event: ", err)
continue
}
if handleEvent(conn, event, taskStarted, taskDone) {
return
}
}
}()
}
// Send the run-task instruction
func sendRunTaskCmd(conn *websocket.Conn) (string, error) {
runTaskCmd, taskID, err := generateRunTaskCmd()
if err != nil {
return "", err
}
err = conn.WriteMessage(websocket.TextMessage, []byte(runTaskCmd))
return taskID, err
}
// Generate the run-task instruction
func generateRunTaskCmd() (string, string, error) {
taskID := uuid.New().String()
runTaskCmd := Event{
Header: Header{
Action: "run-task",
TaskID: taskID,
Streaming: "duplex",
},
Payload: Payload{
TaskGroup: "audio",
Task: "asr",
Function: "recognition",
Model: "qwen-audio-3.0-asr-flash-streaming",
Parameters: Params{
Format: "wav",
SampleRate: 16000,
},
Input: Input{},
},
}
runTaskCmdJSON, err := json.Marshal(runTaskCmd)
return string(runTaskCmdJSON), taskID, err
}
// Wait for the task-started event
func waitForTaskStarted(taskStarted chan bool) {
select {
case <-taskStarted:
fmt.Println("Task started successfully")
case <-time.After(10 * time.Second):
log.Fatal("Timed out waiting for task-started; failed to start the task")
}
}
// Send audio data
func sendAudioData(conn *websocket.Conn) error {
file, err := os.Open(audioFile)
if err != nil {
return err
}
defer file.Close()
buf := make([]byte, 1024)
for {
n, err := file.Read(buf)
if n == 0 {
break
}
if err != nil && err != io.EOF {
return err
}
err = conn.WriteMessage(websocket.BinaryMessage, buf[:n])
if err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
// Send the finish-task instruction
func sendFinishTaskCmd(conn *websocket.Conn, taskID string) error {
finishTaskCmd, err := generateFinishTaskCmd(taskID)
if err != nil {
return err
}
err = conn.WriteMessage(websocket.TextMessage, []byte(finishTaskCmd))
return err
}
// Generate the finish-task instruction
func generateFinishTaskCmd(taskID string) (string, error) {
finishTaskCmd := Event{
Header: Header{
Action: "finish-task",
TaskID: taskID,
Streaming: "duplex",
},
Payload: Payload{
Input: Input{},
},
}
finishTaskCmdJSON, err := json.Marshal(finishTaskCmd)
return string(finishTaskCmdJSON), err
}
// Handle events
func handleEvent(conn *websocket.Conn, event Event, taskStarted chan<- bool, taskDone chan<- bool) bool {
switch event.Header.Event {
case "task-started":
fmt.Println("Received the task-started event")
taskStarted <- true
case "result-generated":
if event.Payload.Output.Sentence.Text != "" {
fmt.Println("Recognition result: ", event.Payload.Output.Sentence.Text)
}
if event.Payload.Usage != nil {
fmt.Println("Task billing duration (seconds): ", event.Payload.Usage.Duration)
}
case "task-finished":
fmt.Println("Task finished")
taskDone <- true
return true
case "task-failed":
handleTaskFailed(event, conn)
taskDone <- true
return true
default:
log.Printf("Unexpected event: %v", event)
}
return false
}
// Handle the task-failed event
func handleTaskFailed(event Event, conn *websocket.Conn) {
if event.Header.ErrorMessage != "" {
log.Fatalf("Task failed: %s", event.Header.ErrorMessage)
} else {
log.Fatal("The task failed for an unknown reason")
}
}
// Close the connection
func closeConnection(conn *websocket.Conn) {
if conn != nil {
conn.Close()
}
}
Qwen3-ASR-Flash-Realtime
ObservaçãoO código de exemplo lê your_audio_file.pcm (PCM16, 16 kHz, mono). Caso você tenha apenas um arquivo MP3, WAV ou formato similar, converta-o com o ffmpeg:
ffmpeg -i your_audio.mp3 -ar 16000 -ac 1 -f s16le your_audio_file.pcm
ObservaçãoNo modo não-VAD (Manual), recomendamos que a duração acumulada do áudio enviado em uma única sessão não ultrapasse 60 segundos.
Python
Antes de executar o exemplo, instale as dependências com os seguintes comandos:
pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
Não nomeie o arquivo de exemplo como websocket.py. Esse nome entra em conflito com a biblioteca websocket e causa o seguinte erro: AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?.
# pip install websocket-client
import os
import time
import json
import threading
import base64
import websocket
import logging
import logging.handlers
from datetime import datetime
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: API_KEY="sk-xxx"
API_KEY = os.environ.get("DASHSCOPE_API_KEY", "sk-xxx")
QWEN_MODEL = "qwen3-asr-flash-realtime"
# The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
baseUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime"
url = f"{baseUrl}?model={QWEN_MODEL}"
print(f"Connecting to server: {url}")
# Note: In non-VAD mode, it is recommended that the cumulative duration of continuously sent audio does not exceed 60s
enableServerVad = True
is_running = True # Add a running flag
headers = [
"Authorization: Bearer " + API_KEY,
"OpenAI-Beta: realtime=v1"
]
def init_logger():
formatter = logging.Formatter('%(asctime)s|%(levelname)s|%(message)s')
f_handler = logging.handlers.RotatingFileHandler(
"omni_tester.log", maxBytes=100 * 1024 * 1024, backupCount=3
)
f_handler.setLevel(logging.DEBUG)
f_handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
console.setFormatter(formatter)
logger.addHandler(f_handler)
logger.addHandler(console)
def on_open(ws):
logger.info("Connected to server.")
# Session update event
event_manual = {
"event_id": "event_123",
"type": "session.update",
"session": {
"modalities": ["text"],
"input_audio_format": "pcm",
"sample_rate": 16000,
# "input_audio_transcription": {
# # Language identifier, optional; recommended to set it if the language is known
# "language": "zh"
# },
"turn_detection": None
}
}
event_vad = {
"event_id": "event_123",
"type": "session.update",
"session": {
"modalities": ["text"],
"input_audio_format": "pcm",
"sample_rate": 16000,
# "input_audio_transcription": {
# "language": "zh"
# },
"turn_detection": {
"type": "server_vad",
"threshold": 0.2,
"silence_duration_ms": 400
}
}
}
if enableServerVad:
logger.info(f"Sending event: {json.dumps(event_vad, indent=2)}")
ws.send(json.dumps(event_vad))
else:
logger.info(f"Sending event: {json.dumps(event_manual, indent=2)}")
ws.send(json.dumps(event_manual))
def on_message(ws, message):
global is_running
try:
data = json.loads(message)
logger.info(f"Received event: {json.dumps(data, ensure_ascii=False, indent=2)}")
if data.get("type") == "conversation.item.input_audio_transcription.completed":
logger.info(f"Final transcript: {data.get('transcript')}")
elif data.get("type") == "session.finished":
logger.info("Closing WebSocket connection after session finished...")
is_running = False # Stop the audio sending thread
ws.close()
except json.JSONDecodeError:
logger.error(f"Failed to parse message: {message}")
def on_error(ws, error):
logger.error(f"Error: {error}")
def on_close(ws, close_status_code, close_msg):
logger.info(f"Connection closed: {close_status_code} - {close_msg}")
def send_audio(ws, local_audio_path):
time.sleep(3) # Wait for the session update to complete
global is_running
with open(local_audio_path, 'rb') as audio_file:
logger.info(f"File reading started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}")
while is_running:
audio_data = audio_file.read(3200) # ~0.1s PCM16/16kHz
if not audio_data:
logger.info(f"File reading finished: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}")
if ws.sock and ws.sock.connected:
if not enableServerVad:
commit_event = {
"event_id": "event_789",
"type": "input_audio_buffer.commit"
}
ws.send(json.dumps(commit_event))
finish_event = {
"event_id": "event_987",
"type": "session.finish"
}
ws.send(json.dumps(finish_event))
break
if not ws.sock or not ws.sock.connected:
logger.info("WebSocket is closed, stopping audio sending.")
break
encoded_data = base64.b64encode(audio_data).decode('utf-8')
eventd = {
"event_id": f"event_{int(time.time() * 1000)}",
"type": "input_audio_buffer.append",
"audio": encoded_data
}
ws.send(json.dumps(eventd))
logger.info(f"Sending audio event: {eventd['event_id']}")
time.sleep(0.1) # Simulate real-time capture
# Initialize logging
init_logger()
logger.info(f"Connecting to WebSocket server at {url}...")
local_audio_path = "your_audio_file.pcm"
ws = websocket.WebSocketApp(
url,
header=headers,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
thread = threading.Thread(target=send_audio, args=(ws, local_audio_path))
thread.start()
ws.run_forever()
Java
Antes de executar o exemplo, instale a dependência Java-WebSocket:
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.6</version>
</dependency>
implementation 'org.java-websocket:Java-WebSocket:1.5.6'
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.*;
public class QwenASRRealtimeClient {
private static final Logger logger = Logger.getLogger(QwenASRRealtimeClient.class.getName());
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: private static final String API_KEY = "sk-xxx"
private static final String API_KEY = System.getenv().getOrDefault("DASHSCOPE_API_KEY", "sk-xxx");
private static final String MODEL = "qwen3-asr-flash-realtime";
// Controls whether to use VAD mode
private static final boolean enableServerVad = true;
private static final AtomicBoolean isRunning = new AtomicBoolean(true);
private static WebSocketClient client;
public static void main(String[] args) throws Exception {
initLogger();
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
String baseUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime";
String url = baseUrl + "?model=" + MODEL;
logger.info("Connecting to server: " + url);
client = new WebSocketClient(new URI(url)) {
@Override
public void onOpen(ServerHandshake handshake) {
logger.info("Connected to server.");
sendSessionUpdate();
}
@Override
public void onMessage(String message) {
try {
JSONObject data = new JSONObject(message);
String eventType = data.optString("type");
logger.info("Received event: " + data.toString(2));
// The final recognition result is in the transcription.completed event
if ("conversation.item.input_audio_transcription.completed".equals(eventType)) {
logger.info("Final transcript: " + data.optString("transcript"));
}
// On receiving the finished event -> stop the sending thread and close the connection
if ("session.finished".equals(eventType)) {
logger.info("Closing WebSocket connection after session finished...");
isRunning.set(false); // Stop the audio sending thread
if (this.isOpen()) {
this.close(1000, "ASR finished");
}
}
} catch (Exception e) {
logger.severe("Failed to parse message: " + message);
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
logger.info("Connection closed: " + code + " - " + reason);
}
@Override
public void onError(Exception ex) {
logger.severe("Error: " + ex.getMessage());
}
};
// Add request headers
client.addHeader("Authorization", "Bearer " + API_KEY);
client.addHeader("OpenAI-Beta", "realtime=v1");
client.connectBlocking(); // Block until the connection is established
// Replace with the path to the audio file to be recognized
String localAudioPath = "your_audio_file.pcm";
Thread audioThread = new Thread(() -> {
try {
sendAudio(localAudioPath);
} catch (Exception e) {
logger.severe("Audio sending thread error: " + e.getMessage());
}
});
audioThread.start();
}
/** Session update event (enable/disable VAD) */
private static void sendSessionUpdate() {
JSONObject eventNoVad = new JSONObject()
.put("event_id", "event_123")
.put("type", "session.update")
.put("session", new JSONObject()
.put("modalities", new String[]{"text"})
.put("input_audio_format", "pcm")
.put("sample_rate", 16000)
// .put("input_audio_transcription", new JSONObject()
// .put("language", "zh"))
.put("turn_detection", JSONObject.NULL) // Manual mode
);
JSONObject eventVad = new JSONObject()
.put("event_id", "event_123")
.put("type", "session.update")
.put("session", new JSONObject()
.put("modalities", new String[]{"text"})
.put("input_audio_format", "pcm")
.put("sample_rate", 16000)
// .put("input_audio_transcription", new JSONObject()
// .put("language", "zh"))
.put("turn_detection", new JSONObject()
.put("type", "server_vad")
.put("threshold", 0.2)
.put("silence_duration_ms", 400))
);
if (enableServerVad) {
logger.info("Sending event (VAD):\n" + eventVad.toString(2));
client.send(eventVad.toString());
} else {
logger.info("Sending event (Manual):\n" + eventNoVad.toString(2));
client.send(eventNoVad.toString());
}
}
/** Send the audio file stream */
private static void sendAudio(String localAudioPath) throws Exception {
Thread.sleep(3000); // Wait for the session to be ready
byte[] allBytes = Files.readAllBytes(Paths.get(localAudioPath));
logger.info("File reading started");
int offset = 0;
while (isRunning.get() && offset < allBytes.length) {
int chunkSize = Math.min(3200, allBytes.length - offset);
byte[] chunk = new byte[chunkSize];
System.arraycopy(allBytes, offset, chunk, 0, chunkSize);
offset += chunkSize;
if (client != null && client.isOpen()) {
String encoded = Base64.getEncoder().encodeToString(chunk);
JSONObject eventd = new JSONObject()
.put("event_id", "event_" + System.currentTimeMillis())
.put("type", "input_audio_buffer.append")
.put("audio", encoded);
client.send(eventd.toString());
logger.info("Sending audio event: " + eventd.getString("event_id"));
} else {
break; // Avoid continuing to send after disconnection
}
Thread.sleep(100); // Simulate real-time sending
}
logger.info("File reading finished");
if (client != null && client.isOpen()) {
// Commit is required in non-VAD mode
if (!enableServerVad) {
JSONObject commitEvent = new JSONObject()
.put("event_id", "event_789")
.put("type", "input_audio_buffer.commit");
client.send(commitEvent.toString());
logger.info("Sent commit event for manual mode.");
}
JSONObject finishEvent = new JSONObject()
.put("event_id", "event_987")
.put("type", "session.finish");
client.send(finishEvent.toString());
logger.info("Sent finish event.");
}
}
/** Initialize logging */
private static void initLogger() {
logger.setLevel(Level.ALL);
Logger rootLogger = Logger.getLogger("");
for (Handler h : rootLogger.getHandlers()) {
rootLogger.removeHandler(h);
}
Handler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFormatter(new SimpleFormatter());
logger.addHandler(consoleHandler);
}
}
Node.js
Antes de executar o exemplo, instale as dependências com o seguinte comando:
npm install ws
/**
* Qwen-ASR Realtime WebSocket client (Node.js version)
* Features:
* - Supports VAD mode and Manual mode
* - Sends session.update to start the session
* - Continuously sends audio chunks via input_audio_buffer.append
* - In Manual mode, sends input_audio_buffer.commit
* - Sends the session.finish event
* - Closes the connection after receiving the session.finished event
*/
import WebSocket from 'ws';
import fs from 'fs';
// ===== Configuration =====
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: const API_KEY = "sk-xxx"
const API_KEY = process.env.DASHSCOPE_API_KEY || 'sk-xxx';
const MODEL = 'qwen3-asr-flash-realtime';
const enableServerVad = true; // true for VAD mode, false for Manual mode
const localAudioPath = 'your_audio_file.pcm'; // Path to the PCM16, 16kHz audio file
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
const baseUrl = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime';
const url = `${baseUrl}?model=${MODEL}`;
console.log(`Connecting to server: ${url}`);
// ===== State control =====
let isRunning = true;
// ===== Establish the connection =====
const ws = new WebSocket(url, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'OpenAI-Beta': 'realtime=v1'
}
});
// ===== Event bindings =====
ws.on('open', () => {
console.log('[WebSocket] Connected to server.');
sendSessionUpdate();
// Start the audio sending thread
sendAudio(localAudioPath);
});
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
console.log('[Received Event]:', JSON.stringify(data, null, 2));
// The final recognition result is in the transcription.completed event
if (data.type === 'conversation.item.input_audio_transcription.completed') {
console.log(`[Final Transcript] ${data.transcript}`);
}
// Received the finished event
if (data.type === 'session.finished') {
console.log('[Action] Closing WebSocket connection after session finished...');
if (ws.readyState === WebSocket.OPEN) {
ws.close(1000, 'ASR finished');
}
}
} catch (e) {
console.error('[Error] Failed to parse message:', message);
}
});
ws.on('close', (code, reason) => {
console.log(`[WebSocket] Connection closed: ${code} - ${reason}`);
});
ws.on('error', (err) => {
console.error('[WebSocket Error]', err);
});
// ===== Session update =====
function sendSessionUpdate() {
const eventNoVad = {
event_id: 'event_123',
type: 'session.update',
session: {
modalities: ['text'],
input_audio_format: 'pcm',
sample_rate: 16000,
// input_audio_transcription: {
// language: 'zh'
// },
turn_detection: null
}
};
const eventVad = {
event_id: 'event_123',
type: 'session.update',
session: {
modalities: ['text'],
input_audio_format: 'pcm',
sample_rate: 16000,
// input_audio_transcription: {
// language: 'zh'
// },
turn_detection: {
type: 'server_vad',
threshold: 0.2,
silence_duration_ms: 400
}
}
};
if (enableServerVad) {
console.log('[Send Event] VAD Mode:\n', JSON.stringify(eventVad, null, 2));
ws.send(JSON.stringify(eventVad));
} else {
console.log('[Send Event] Manual Mode:\n', JSON.stringify(eventNoVad, null, 2));
ws.send(JSON.stringify(eventNoVad));
}
}
// ===== Send the audio file stream =====
function sendAudio(audioPath) {
setTimeout(() => {
console.log(`[File Read Start] ${audioPath}`);
const buffer = fs.readFileSync(audioPath);
let offset = 0;
const chunkSize = 3200; // About 0.1s of PCM16 audio
function sendChunk() {
if (!isRunning) return;
if (offset >= buffer.length) {
isRunning = false; // Stop sending audio
console.log('[File Read End]');
if (ws.readyState === WebSocket.OPEN) {
if (!enableServerVad) {
const commitEvent = {
event_id: 'event_789',
type: 'input_audio_buffer.commit'
};
ws.send(JSON.stringify(commitEvent));
console.log('[Send Commit Event]');
}
const finishEvent = {
event_id: 'event_987',
type: 'session.finish'
};
ws.send(JSON.stringify(finishEvent));
console.log('[Send Finish Event]');
}
return;
}
if (ws.readyState !== WebSocket.OPEN) {
console.log('[Stop] WebSocket is not open.');
return;
}
const chunk = buffer.slice(offset, offset + chunkSize);
offset += chunkSize;
const encoded = chunk.toString('base64');
const appendEvent = {
event_id: `event_${Date.now()}`,
type: 'input_audio_buffer.append',
audio: encoded
};
ws.send(JSON.stringify(appendEvent));
console.log(`[Send Audio Event] ${appendEvent.event_id}`);
setTimeout(sendChunk, 100); // Simulate real-time sending
}
sendChunk();
}, 3000); // Wait for the session configuration to complete
}
C#
O código de exemplo é o seguinte:
using System.Net.WebSockets;
using System.Text;
using System.Text.Json.Nodes;
class Program {
private static ClientWebSocket _webSocket = new ClientWebSocket();
private static CancellationTokenSource _cts = new CancellationTokenSource();
private static bool _sessionFinished = false;
private static bool _isRunning = true;
// Controls whether to use VAD mode
private const bool EnableServerVad = true;
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: private static readonly string ApiKey = "sk-xxx"
private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");
private const string Model = "qwen3-asr-flash-realtime";
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
private const string BaseUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime";
private const string AudioFilePath = "your_audio_file.pcm"; // Replace with the path to your PCM audio file
static async Task Main(string[] args) {
var url = $"{BaseUrl}?model={Model}";
Console.WriteLine($"Connecting to server: {url}");
// Set authentication headers
_webSocket.Options.SetRequestHeader("Authorization", $"Bearer {ApiKey}");
_webSocket.Options.SetRequestHeader("OpenAI-Beta", "realtime=v1");
await _webSocket.ConnectAsync(new Uri(url), _cts.Token);
Console.WriteLine("Connected to server.");
// Start the message receiving task
var receiveTask = ReceiveMessagesAsync();
// Send the session.update configuration
await SendSessionUpdateAsync();
// Send the audio stream
await SendAudioStreamAsync();
// Wait for the session.finished event
while (!_sessionFinished && !_cts.IsCancellationRequested) {
await Task.Delay(100);
}
if (_webSocket.State == WebSocketState.Open) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "ASR finished", _cts.Token);
}
}
private static async Task SendAsync(string text) {
var bytes = Encoding.UTF8.GetBytes(text);
await _webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, _cts.Token);
}
// Send the session.update event
private static async Task SendSessionUpdateAsync() {
var session = new JsonObject {
["modalities"] = new JsonArray { "text" },
["input_audio_format"] = "pcm",
["sample_rate"] = 16000,
// ["input_audio_transcription"] = new JsonObject { ["language"] = "zh" }
};
if (EnableServerVad) {
session["turn_detection"] = new JsonObject {
["type"] = "server_vad",
["threshold"] = 0.2,
["silence_duration_ms"] = 400
};
} else {
session["turn_detection"] = null;
}
var payload = new JsonObject {
["event_id"] = "event_123",
["type"] = "session.update",
["session"] = session
};
Console.WriteLine($"Sending session.update: {payload.ToJsonString()}");
await SendAsync(payload.ToJsonString());
}
// Send the audio stream (send one PCM chunk every 100ms)
private static async Task SendAudioStreamAsync() {
await Task.Delay(3000); // Wait for the session configuration to complete
const int chunkSize = 3200; // 100ms @ 16kHz 16bit mono
using var fs = new FileStream(AudioFilePath, FileMode.Open, FileAccess.Read);
var buffer = new byte[chunkSize];
int read;
while (_isRunning && (read = await fs.ReadAsync(buffer, 0, chunkSize)) > 0) {
if (_webSocket.State != WebSocketState.Open) break;
string b64 = Convert.ToBase64String(buffer, 0, read);
var append = new JsonObject {
["event_id"] = $"event_{DateTimeOffset.Now.ToUnixTimeMilliseconds()}",
["type"] = "input_audio_buffer.append",
["audio"] = b64
};
await SendAsync(append.ToJsonString());
await Task.Delay(100);
}
Console.WriteLine("File read end.");
if (_webSocket.State == WebSocketState.Open) {
if (!EnableServerVad) {
var commit = new JsonObject {
["event_id"] = "event_789",
["type"] = "input_audio_buffer.commit"
};
await SendAsync(commit.ToJsonString());
}
var finish = new JsonObject {
["event_id"] = "event_987",
["type"] = "session.finish"
};
await SendAsync(finish.ToJsonString());
}
}
// Receive and handle server-side events
private static async Task ReceiveMessagesAsync() {
var buffer = new byte[16384];
var sb = new StringBuilder();
while (_webSocket.State == WebSocketState.Open && !_cts.IsCancellationRequested) {
try {
var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token);
if (result.MessageType == WebSocketMessageType.Close) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cts.Token);
break;
}
sb.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
if (!result.EndOfMessage) continue;
string text = sb.ToString();
sb.Clear();
var data = JsonNode.Parse(text);
string? type = data?["type"]?.GetValue<string>();
Console.WriteLine($"Received event: {type}");
if (type == "conversation.item.input_audio_transcription.completed") {
Console.WriteLine($"Final transcript: {data!["transcript"]}");
} else if (type == "session.finished") {
Console.WriteLine("Session finished, closing...");
_sessionFinished = true;
_isRunning = false;
break;
}
} catch (Exception ex) {
Console.WriteLine($"Receive error: {ex.Message}");
break;
}
}
}
}
PHP
O projeto de exemplo possui a seguinte estrutura de diretórios:
my-php-project/
├── composer.json
├── vendor/
└── index.php
O conteúdo do arquivo composer.json é apresentado a seguir. Ajuste as versões das dependências conforme necessário:
{
"require": {
"react/event-loop": "^1.3",
"react/socket": "^1.11",
"ratchet/pawl": "^0.4"
}
}
O conteúdo do arquivo index.php é o seguinte:
<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: $api_key = "sk-xxx"
$api_key = getenv("DASHSCOPE_API_KEY");
$model = 'qwen3-asr-flash-realtime';
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
$base_url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime';
$websocket_url = $base_url . '?model=' . $model;
$audio_file_path = 'your_audio_file.pcm'; // Replace with the path to your PCM audio file
// Controls whether to use VAD mode
$enable_server_vad = true;
$loop = Loop::get();
$socketConnector = new SocketConnector($loop, [
// WARNING: Disabling TLS certificate verification exposes you to man-in-the-middle attacks. Use for local debugging only; always set verify_peer/verify_peer_name to true in production.
'tls' => ['verify_peer' => false, 'verify_peer_name' => false],
]);
$connector = new Connector($loop, $socketConnector);
$headers = [
'Authorization' => 'Bearer ' . $api_key,
'OpenAI-Beta' => 'realtime=v1',
];
$is_running = true;
$connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $audio_file_path, $enable_server_vad, &$is_running) {
echo "Connected to WebSocket server\n";
// Listen for server-side events
$conn->on('message', function($msg) use ($conn, &$is_running) {
$event = json_decode($msg, true);
if (!isset($event['type'])) {
return;
}
echo "Received event: {$event['type']}\n";
if ($event['type'] === 'conversation.item.input_audio_transcription.completed') {
echo "Final transcript: {$event['transcript']}\n";
} elseif ($event['type'] === 'session.finished') {
echo "Session finished, closing...\n";
$is_running = false;
$conn->close();
}
});
$conn->on('close', function() {
echo "Connection closed\n";
});
// Send the session.update event
sendSessionUpdate($conn, $enable_server_vad);
// Start sending audio after the session configuration completes
$loop->addTimer(3, function () use ($conn, $audio_file_path, $enable_server_vad, $loop, &$is_running) {
sendAudioStream($conn, $audio_file_path, $enable_server_vad, $loop, $is_running);
});
}, function ($e) {
echo "Unable to connect: {$e->getMessage()}\n";
});
$loop->run();
// Send the session.update event
function sendSessionUpdate($conn, $enable_server_vad) {
$session = [
'modalities' => ['text'],
'input_audio_format' => 'pcm',
'sample_rate' => 16000,
// 'input_audio_transcription' => ['language' => 'zh'],
'turn_detection' => $enable_server_vad ? [
'type' => 'server_vad',
'threshold' => 0.2,
'silence_duration_ms' => 400,
] : null,
];
$event = [
'event_id' => 'event_123',
'type' => 'session.update',
'session' => $session,
];
$conn->send(json_encode($event));
echo "Sent session.update\n";
}
// Send the audio stream (send one PCM chunk every 100ms)
function sendAudioStream($conn, $audio_file_path, $enable_server_vad, $loop, &$is_running) {
$fp = fopen($audio_file_path, 'rb');
if (!$fp) {
echo "Unable to open the audio file\n";
return;
}
$send_chunk = function() use ($conn, $fp, $enable_server_vad, $loop, &$send_chunk, &$is_running) {
if (!$is_running) {
fclose($fp);
return;
}
$chunk = fread($fp, 3200); // 100ms @ 16kHz 16bit mono
if ($chunk === false || strlen($chunk) === 0) {
fclose($fp);
echo "Audio stream ended\n";
if (!$enable_server_vad) {
$conn->send(json_encode([
'event_id' => 'event_789',
'type' => 'input_audio_buffer.commit',
]));
}
$conn->send(json_encode([
'event_id' => 'event_987',
'type' => 'session.finish',
]));
return;
}
$append = [
'event_id' => 'event_' . round(microtime(true) * 1000),
'type' => 'input_audio_buffer.append',
'audio' => base64_encode($chunk),
];
$conn->send(json_encode($append));
$loop->addTimer(0.1, $send_chunk);
};
$send_chunk();
}
Go
Antes de executar o exemplo, instale a dependência necessária:
go get github.com/gorilla/websocket
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
"github.com/gorilla/websocket"
)
const (
// The following is the configuration for the Singapore region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
baseURL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime"
model = "qwen3-asr-flash-realtime"
audioFile = "your_audio_file.pcm" // Replace with the path to your PCM audio file
enableServerVad = true // Controls whether to use VAD mode
)
// Server-side event structure
type ServerEvent struct {
Type string `json:"type"`
Transcript string `json:"transcript,omitempty"`
}
func main() {
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// If you have not configured the environment variable, replace the following line with your Alibaba Cloud Model Studio API Key: apiKey := "sk-xxx"
apiKey := os.Getenv("DASHSCOPE_API_KEY")
url := baseURL + "?model=" + model
log.Printf("Connecting to server: %s", url)
conn, err := connect(url, apiKey)
if err != nil {
log.Fatal("Failed to connect to WebSocket: ", err)
}
defer conn.Close()
// Start a goroutine to receive messages
sessionFinished := make(chan bool, 1)
go receiveMessages(conn, sessionFinished)
// Send session.update
if err := sendSessionUpdate(conn); err != nil {
log.Fatal("Failed to send session.update: ", err)
}
// Wait for the session configuration to complete
time.Sleep(3 * time.Second)
// Send the audio stream
if err := sendAudioStream(conn); err != nil {
log.Fatal("Failed to send audio: ", err)
}
// Wait for session.finished
<-sessionFinished
}
// Establish the WebSocket connection
func connect(url, apiKey string) (*websocket.Conn, error) {
headers := http.Header{}
headers.Set("Authorization", "Bearer "+apiKey)
headers.Set("OpenAI-Beta", "realtime=v1")
conn, _, err := websocket.DefaultDialer.Dial(url, headers)
return conn, err
}
// Send the session.update event
func sendSessionUpdate(conn *websocket.Conn) error {
session := map[string]interface{}{
"modalities": []string{"text"},
"input_audio_format": "pcm",
"sample_rate": 16000,
// "input_audio_transcription": map[string]interface{}{
// "language": "zh",
// },
}
if enableServerVad {
session["turn_detection"] = map[string]interface{}{
"type": "server_vad",
"threshold": 0.2,
"silence_duration_ms": 400,
}
} else {
session["turn_detection"] = nil
}
event := map[string]interface{}{
"event_id": "event_123",
"type": "session.update",
"session": session,
}
payload, _ := json.Marshal(event)
log.Printf("Sending session.update: %s", string(payload))
return conn.WriteMessage(websocket.TextMessage, payload)
}
// Send the audio stream (send one PCM chunk every 100ms)
func sendAudioStream(conn *websocket.Conn) error {
f, err := os.Open(audioFile)
if err != nil {
return err
}
defer f.Close()
chunk := make([]byte, 3200) // 100ms @ 16kHz 16bit mono
for {
n, err := f.Read(chunk)
if n > 0 {
event := map[string]interface{}{
"event_id": fmt.Sprintf("event_%d", time.Now().UnixMilli()),
"type": "input_audio_buffer.append",
"audio": base64.StdEncoding.EncodeToString(chunk[:n]),
}
payload, _ := json.Marshal(event)
if err := conn.WriteMessage(websocket.TextMessage, payload); err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
log.Println("Audio stream ended")
if !enableServerVad {
commitEvt := map[string]interface{}{
"event_id": "event_789",
"type": "input_audio_buffer.commit",
}
payload, _ := json.Marshal(commitEvt)
if err := conn.WriteMessage(websocket.TextMessage, payload); err != nil {
return err
}
}
finishEvt := map[string]interface{}{
"event_id": "event_987",
"type": "session.finish",
}
payload, _ := json.Marshal(finishEvt)
return conn.WriteMessage(websocket.TextMessage, payload)
}
// Receive and handle server-side events
func receiveMessages(conn *websocket.Conn, sessionFinished chan<- bool) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
log.Println("Error reading message: ", err)
sessionFinished <- true
return
}
var evt ServerEvent
if err := json.Unmarshal(msg, &evt); err != nil {
log.Println("Error parsing message: ", err)
continue
}
log.Printf("Received event: %s", evt.Type)
switch evt.Type {
case "conversation.item.input_audio_transcription.completed":
log.Printf("Final transcript: %s", evt.Transcript)
case "session.finished":
log.Println("Session finished")
sessionFinished <- true
return
}
}
}
Paraformer
O código de exemplo do Paraformer é semelhante ao do Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime. Substitua o nome do modelo por um modelo Paraformer.
Aplicação em produção
Reutilização de conexões (WebSocket)
As conexões WebSocket para Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime e Paraformer permitem reutilização. Após a conclusão de uma tarefa de reconhecimento, é possível iniciar a próxima sem restabelecer a conexão.
Fluxo de reutilização: O cliente envia finish-task. Depois que o servidor retorna task-finished, o cliente pode enviar run-task novamente para começar uma nova tarefa.
Importante
- Aguarde o evento
task-finisheddo servidor antes de iniciar outra tarefa. - Tarefas distintas na mesma conexão reutilizada devem usar valores diferentes de
task_id. - Em caso de falha na tarefa, o servidor retorna um evento de erro e encerra a conexão, impedindo sua reutilização.
- Se nenhuma nova tarefa for iniciada dentro de 60 segundos após o término da anterior, a conexão será fechada automaticamente.
O modelo Qwen3-ASR-Flash-Realtime opera com um modelo de sessão e não oferece suporte à reutilização de conexões. Feche a conexão sempre que uma sessão terminar.
Para consultar os eventos de cada modelo, acesse API reference.
Melhores práticas para alta concorrência
O DashScope SDK possui um mecanismo interno de pool que reutiliza conexões WebSocket e objetos de reconhecimento, evitando a sobrecarga causada pela criação e destruição frequentes de recursos.
ImportanteAtualmente, apenas o Java SDK do Paraformer oferece suporte a esse recurso.
Clique para visualizar as melhores práticas de alta concorrência
Pré-requisitos
- Obtain an API key
- DashScope SDK instalado e compatível com a versão exigida. Recomendamos que você install the latest version: Java SDK versão 2.16.9 ou superior.
O Java SDK combina um pool de conexões integrado a um pool de objetos personalizado para garantir desempenho ideal:
- Pool de conexões: O pool OkHttp3, integrado ao SDK, gerencia e reutiliza as conexões WebSocket subjacentes, reduzindo a sobrecarga de handshakes de rede. Esse recurso vem ativado por padrão.
- Pool de objetos: Construído sobre
commons-pool2, este pool mantém um conjunto de objetosRecognitioncom conexões já estabelecidas. Ao obter um objeto do pool, elimina-se a latência de configuração de conexão, reduzindo significativamente a latência do primeiro pacote.
Etapas de implementação
-
Adicione as dependências
Inclua dashscope-sdk-java e commons-pool2 no arquivo de configuração de dependências, conforme a ferramenta de build do seu projeto.
Os exemplos abaixo mostram a configuração para Maven e Gradle:
Maven
- Abra o arquivo
pom.xmldo seu projeto Maven. - Insira as seguintes dependências dentro da tag
<dependencies>.
<dependency> <groupId>com.alibaba</groupId> <artifactId>dashscope-sdk-java</artifactId> <!-- Replace 'the-latest-version' with version 2.16.9 or later. You can look up version numbers at: https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java --> <version>the-latest-version</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <!-- Replace 'the-latest-version' with the latest version. You can look up version numbers at: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <version>the-latest-version</version> </dependency>- Salve o arquivo
pom.xml. - Execute um comando Maven (como
mvn clean installoumvn compile) para atualizar as dependências do projeto.
Gradle
- Abra o arquivo
build.gradledo seu projeto Gradle. - Adicione as dependências abaixo no bloco
dependencies.
dependencies { // Replace 'the-latest-version' with version 2.16.9 or later. You can look up version numbers at: https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java implementation group: 'com.alibaba', name: 'dashscope-sdk-java', version: 'the-latest-version' // Replace 'the-latest-version' with the latest version. You can look up version numbers at: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version' }- Salve o arquivo
build.gradle. - No terminal, navegue até o diretório raiz do projeto e execute o comando Gradle a seguir para atualizar as dependências.
./gradlew build --refresh-dependenciesNo Windows, utilize o seguinte comando:
gradlew build --refresh-dependencies - Abra o arquivo
-
Configure o pool de conexões
Defina os principais parâmetros do pool de conexões por meio de variáveis de ambiente:
Variável de ambiente
Descrição
DASHSCOPE_CONNECTION_POOL_SIZE
Tamanho do pool de conexões.
Valor recomendado: pelo menos o dobro da concorrência de pico.
Valor padrão: 32.
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS
Número máximo de requisições assíncronas.
Valor recomendado: igual a
DASHSCOPE_CONNECTION_POOL_SIZE.Valor padrão: 32.
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST
Limite de requisições assíncronas por host.
Valor recomendado: igual a
DASHSCOPE_CONNECTION_POOL_SIZE.Valor padrão: 32.
-
Configure o pool de objetos
Ajuste o tamanho do pool de objetos usando uma variável de ambiente:
Variável de ambiente
Descrição
RECOGNITION_OBJECTPOOL_SIZE
Capacidade do pool de objetos.
Valor recomendado: entre 1,5 e 2 vezes a concorrência de pico.
Valor padrão: 500.
Importante
- O tamanho do pool de objetos (
RECOGNITION_OBJECTPOOL_SIZE) deve ser menor ou igual ao tamanho do pool de conexões (DASHSCOPE_CONNECTION_POOL_SIZE). Caso contrário, se o pool de objetos solicitar um item enquanto o pool de conexões estiver cheio, a thread chamante ficará bloqueada. - A capacidade do pool de objetos não pode ultrapassar o limite de consultas por segundo (QPS) da sua conta.
Crie o pool de objetos com o código abaixo:
- O tamanho do pool de objetos (
class RecognitionObjectPool {
// ... For the full example, see the complete code.
public static GenericObjectPool<Recognition> getInstance() {
lock.lock();
if (recognitionGenericObjectPool == null) {
int objectPoolSize = getObjectivePoolSize();
RecognitionObjectFactory recognitionObjectFactory =
new RecognitionObjectFactory();
GenericObjectPoolConfig<Recognition> config =
new GenericObjectPoolConfig<>();
config.setMaxTotal(objectPoolSize);
config.setMaxIdle(objectPoolSize);
config.setMinIdle(objectPoolSize);
recognitionGenericObjectPool =
new GenericObjectPool<>(recognitionObjectFactory, config);
}
lock.unlock();
return recognitionGenericObjectPool;
}
}
-
Obtenha um objeto Recognition do pool
Se a quantidade de objetos não devolvidos exceder o limite do pool, o sistema criará novos objetos
Recognition. Esses novos objetos precisarão restabelecer a conexão WebSocket e não poderão ser reutilizados imediatamente.
recognizer = RecognitionObjectPool.getInstance().borrowObject();
-
Execute o reconhecimento de fala
Invoque o método call ou streamCall do objeto
Recognitionpara processar o áudio. -
Devolva o objeto Recognition
Ao concluir a tarefa de reconhecimento, devolva o objeto Recognition para permitir sua reutilização. Não devolva objetos associados a tarefas incompletas ou com falha.
RecognitionObjectPool.getInstance().returnObject(recognizer);
Código completo
package org.alibaba.bailian.example.examples;
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.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.ApiKey;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import com.alibaba.dashscope.utils.Constants;
public class Main {
public static void checkoutEnv(String envName, int defaultSize) {
if (System.getenv(envName) != null) {
System.out.println("[ENV CHECK]: " + envName + " "
+ System.getenv(envName));
} else {
System.out.println("[ENV CHECK]: " + envName
+ " Using Default which is " + defaultSize);
}
}
public static void main(String[] args)
throws NoApiKeyException, InterruptedException {
// The following is the configuration for the China (Beijing) region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
Constants.baseHttpApiUrl = "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1";
checkoutEnv("DASHSCOPE_CONNECTION_POOL_SIZE", 32);
checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS", 32);
checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST", 32);
checkoutEnv(RecognitionObjectPool.RECOGNITION_OBJECTPOOL_SIZE_ENV,
RecognitionObjectPool.DEFAULT_OBJECT_POOL_SIZE);
int threadNums = 3;
String currentDir = System.getProperty("user.dir");
Path[] filePaths = {
Paths.get(currentDir, "{YOUR_AUDIO_FILE}"),
Paths.get(currentDir, "{YOUR_AUDIO_FILE}"),
Paths.get(currentDir, "{YOUR_AUDIO_FILE}"),
};
ExecutorService executorService = Executors.newFixedThreadPool(threadNums);
for (int i = 0; i < threadNums; i++) {
executorService.submit(new RealtimeRecognizeTask(filePaths));
}
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.MINUTES);
System.exit(0);
}
}
class RecognitionObjectFactory extends BasePooledObjectFactory<Recognition> {
public RecognitionObjectFactory() {
super();
}
@Override
public Recognition create() throws Exception {
return new Recognition();
}
@Override
public PooledObject<Recognition> wrap(Recognition obj) {
return new DefaultPooledObject<>(obj);
}
}
class RecognitionObjectPool {
public static GenericObjectPool<Recognition> recognitionGenericObjectPool;
public static String RECOGNITION_OBJECTPOOL_SIZE_ENV =
"RECOGNITION_OBJECTPOOL_SIZE";
public static int DEFAULT_OBJECT_POOL_SIZE = 500;
private static Lock lock = new java.util.concurrent.locks.ReentrantLock();
public static int getObjectivePoolSize() {
try {
Integer n = Integer.parseInt(
System.getenv(RECOGNITION_OBJECTPOOL_SIZE_ENV));
return n;
} catch (NumberFormatException e) {
return DEFAULT_OBJECT_POOL_SIZE;
}
}
public static GenericObjectPool<Recognition> getInstance() {
lock.lock();
if (recognitionGenericObjectPool == null) {
int objectPoolSize = getObjectivePoolSize();
System.out.println("RECOGNITION_OBJECTPOOL_SIZE: "
+ objectPoolSize);
RecognitionObjectFactory recognitionObjectFactory =
new RecognitionObjectFactory();
GenericObjectPoolConfig<Recognition> config =
new GenericObjectPoolConfig<>();
config.setMaxTotal(objectPoolSize);
config.setMaxIdle(objectPoolSize);
config.setMinIdle(objectPoolSize);
recognitionGenericObjectPool =
new GenericObjectPool<>(recognitionObjectFactory, config);
}
lock.unlock();
return recognitionGenericObjectPool;
}
}
class RealtimeRecognizeTask implements Runnable {
private static final Object lock = new Object();
private Path[] filePaths;
public RealtimeRecognizeTask(Path[] filePaths) {
this.filePaths = filePaths;
}
private static String getDashScopeApiKey() throws NoApiKeyException {
String dashScopeApiKey = null;
try {
ApiKey apiKey = new ApiKey();
dashScopeApiKey = ApiKey.getApiKey(null);
} catch (NoApiKeyException e) {
System.out.println("No API key found in environment.");
}
if (dashScopeApiKey == null) {
dashScopeApiKey = "your-dashscope-apikey";
}
return dashScopeApiKey;
}
public void runCallback() {
for (Path filePath : filePaths) {
RecognitionParam param = null;
try {
param = RecognitionParam.builder()
.model("paraformer-realtime-v2")
.format("pcm")
.sampleRate(16000)
.apiKey(getDashScopeApiKey())
.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
Recognition recognizer = null;
final boolean[] hasError = {false};
try {
recognizer = RecognitionObjectPool.getInstance().borrowObject();
String threadName = Thread.currentThread().getName();
ResultCallback<RecognitionResult> callback =
new ResultCallback<RecognitionResult>() {
@Override
public void onEvent(RecognitionResult message) {
synchronized (lock) {
if (message.isSentenceEnd()) {
System.out.println("[process " + threadName
+ "] Fix:" + message.getSentence().getText());
} else {
System.out.println("[process " + threadName
+ "] Result: " + message.getSentence().getText());
}
}
}
@Override
public void onComplete() {
System.out.println("[" + threadName
+ "] Recognition complete");
}
@Override
public void onError(Exception e) {
System.out.println("[" + threadName
+ "] RecognitionCallback error: " + e.getMessage());
hasError[0] = true;
}
};
System.out.println("[" + threadName
+ "] Input file_path is: " + filePath);
FileInputStream fis = null;
try {
fis = new FileInputStream(filePath.toFile());
} catch (Exception e) {
System.out.println("Error when loading file: " + filePath);
e.printStackTrace();
}
recognizer.call(param, callback);
// chunk size set to 100 ms for 16KHz sample rate
byte[] buffer = new byte[3200];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
ByteBuffer byteBuffer;
if (bytesRead < buffer.length) {
byteBuffer = ByteBuffer.wrap(buffer, 0, bytesRead);
} else {
byteBuffer = ByteBuffer.wrap(buffer);
}
recognizer.sendAudioFrame(byteBuffer);
Thread.sleep(100);
buffer = new byte[3200];
}
System.out.println("[" + threadName + "] send audio done");
recognizer.stop();
System.out.println("[" + threadName + "] asr task finished");
} catch (Exception e) {
e.printStackTrace();
hasError[0] = true;
}
if (recognizer != null) {
try {
if (hasError[0] == true) {
recognizer.getDuplexApi().close(1000, "bye");
RecognitionObjectPool.getInstance()
.invalidateObject(recognizer);
} else {
RecognitionObjectPool.getInstance()
.returnObject(recognizer);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public void run() {
runCallback();
}
}
Configuração recomendada
As configurações a seguir baseiam-se em testes executados exclusivamente com o serviço de reconhecimento de fala em tempo real Paraformer, rodando em servidores Alibaba Cloud das especificações indicadas. A concorrência por máquina refere-se ao número de tarefas simultâneas de reconhecimento (ou seja, a quantidade de threads de trabalho).
Especificação da máquina (Alibaba Cloud) | Concorrência máxima por máquina | Tamanho do pool de objetos | Tamanho do pool de conexões |
|---|---|---|---|
4 vCPUs, 8 GiB | 100 | 500 | 2000 |
8 vCPUs, 16 GiB | 200 | 500 | 2000 |
16 vCPUs, 32 GiB | 400 | 500 | 2000 |
Gestão de recursos e tratamento de erros
-
Sucesso na tarefa: Invoque
GenericObjectPool.returnObject()para devolver o objeto Recognition ao pool, permitindo seu reaproveitamento.ImportanteJamais devolva objetos Recognition cujas tarefas estejam incompletas ou tenham falhado.
-
Falha na tarefa: Se uma exceção lançada pelo SDK ou pela lógica de negócio interromper a execução, realize as duas ações abaixo:
- Feche ativamente a conexão WebSocket subjacente.
- Invalidade o objeto no pool para impedir que seja reutilizado.
// Close the connection.
recognizer.getDuplexApi().close(1000, "bye");
// Invalidate the failed recognizer in the object pool.
RecognitionObjectPool.getInstance().invalidateObject(recognizer);
- Caso o serviço retorne um erro TaskFailed, nenhuma ação adicional é necessária.
Warm-up e medição de latência
Ao avaliar métricas de desempenho como a latência de chamadas concorrentes no DashScope Java SDK, recomenda-se executar um aquecimento adequado antes dos testes oficiais.
Mecanismo de reutilização de conexões
O DashScope Java SDK gerencia e reutiliza conexões WebSocket através de um pool global singleton. Esse mecanismo funciona da seguinte forma:
-
Criação sob demanda: O SDK não pré-cria conexões WebSocket na inicialização. Em vez disso, ele estabelece as conexões conforme necessário durante a primeira chamada.
-
Reutilização temporária: Após a conclusão de uma requisição, a conexão permanece disponível no pool por até 60 segundos.
- Se uma nova requisição chegar dentro desse período, o SDK aproveita a conexão existente, evitando a sobrecarga de um novo handshake.
- Conexões ociosas por mais de 60 segundos são fechadas automaticamente para liberar recursos.
Importância do warm-up
Nos cenários abaixo, o pool pode não ter conexões ativas disponíveis, obrigando a requisição a criar uma nova:
- A aplicação acabou de iniciar e ainda não realizou chamadas.
- O serviço ficou inativo por mais de 60 segundos, causando o fechamento das conexões do pool por timeout.
Nessas situações, as primeiras requisições acionam todo o processo de conexão WebSocket (incluindo handshake TCP, negociação TLS e upgrade de protocolo). Consequentemente, a latência ponta a ponta será significativamente maior comparada às requisições subsequentes que reutilizam conexões.
Abordagem recomendada
Antes de iniciar testes de carga formais ou medir latência, siga estas etapas de aquecimento:
- Simule o nível de concorrência do teste oficial enviando chamadas antecipadamente (por exemplo, durante 1 a 2 minutos) para preencher completamente o pool de conexões.
- Após confirmar que o pool estabeleceu e manteve conexões ativas suficientes, comece a coletar os dados de desempenho oficiais.
Melhoria da precisão do reconhecimento
- Escolha um modelo compatível com a taxa de amostragem: Para áudio telefônico de 8 kHz, utilize diretamente um modelo de 8 kHz. Isso previne a perda de informações causada pelo upsampling para 16 kHz.
- Otimize a qualidade do áudio de entrada: Utilize microfones de alta qualidade e grave em ambientes com boa relação sinal-ruído e ausência de eco. Na camada de aplicação, integre algoritmos de pré-processamento como redução de ruído (ex: RNNoise) e cancelamento de eco acústico (AEC).
Configuração de estratégia de tolerância a falhas
-
Reconexão no lado do cliente: Implemente reconexão automática no cliente para lidar com instabilidades de rede. Abaixo está uma implementação de referência para o Python SDK:
- Captura de exceções: Implemente o método
on_errorna classeCallback. O SDKdashscopeinvoca esse método ao detectar erros de rede ou outros problemas. - Sinalização de estado: Quando
on_errorfor acionado, defina um sinal de reconexão. Em Python, usethreading.Event, um sinalizador thread-safe. - Loop de reconexão: Envolva a lógica principal em um loop
for(por exemplo, tente 3 vezes). Ao detectar o sinal de reconexão, interrompa a rodada atual de reconhecimento, limpe os recursos e, após alguns segundos, reinicie o loop para estabelecer uma conexão totalmente nova.
- Captura de exceções: Implemente o método
-
Configure heartbeat para manter a conexão ativa: Para preservar conexões persistentes com o servidor, defina o parâmetro heartbeat como
true. Assim, a conexão permanecerá aberta mesmo durante longos períodos de silêncio no áudio. -
Limites de taxa do modelo: Ao chamar a API do modelo, observe as regras de Rate limiting.
Modelos e regiões suportados
Singapore
Para chamar os modelos abaixo, utilize uma API Key da região Singapore:
- Qwen-Audio-3.0-ASR-Flash-Streaming: qwen-audio-3.0-asr-flash-streaming
- Fun-ASR-Realtime: fun-asr-realtime (versão estável, atualmente equivalente a fun-asr-realtime-2025-11-07), fun-asr-realtime-2025-11-07 (versão snapshot)
- Qwen3-ASR-Flash-Realtime: qwen3-asr-flash-realtime (versão estável, atualmente equivalente a qwen3-asr-flash-realtime-2025-10-27), qwen3-asr-flash-realtime-2026-02-10 (versão snapshot mais recente), qwen3-asr-flash-realtime-2025-10-27 (versão snapshot)
China (Beijing)
Para chamar os modelos abaixo, utilize uma API Key da região China (Beijing):
-
Qwen-Audio-3.0-ASR-Flash-Streaming: qwen-audio-3.0-asr-flash-streaming
-
Fun-ASR-Realtime: fun-asr-realtime (versão estável, atualmente equivalente a fun-asr-realtime-2025-11-07), fun-asr-realtime-2026-02-28 (versão snapshot mais recente), fun-asr-realtime-2025-11-07 (versão snapshot), fun-asr-realtime-2025-09-15 (versão snapshot)
- fun-asr-flash-8k-realtime (versão estável, atualmente equivalente a fun-asr-flash-8k-realtime-2026-01-28), fun-asr-flash-8k-realtime-2026-01-28
-
Qwen3-ASR-Flash-Realtime: qwen3-asr-flash-realtime (versão estável, atualmente equivalente a qwen3-asr-flash-realtime-2025-10-27), qwen3-asr-flash-realtime-2026-02-10 (versão snapshot mais recente), qwen3-asr-flash-realtime-2025-10-27 (versão snapshot)
-
Paraformer: paraformer-realtime-v2, paraformer-realtime-v1, paraformer-realtime-8k-v2, paraformer-realtime-8k-v1
Referência da API
- Real-time speech recognition - Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime API reference
- Real-time speech recognition - Qwen3-ASR-Flash-Realtime API reference
- Real-time speech recognition - Paraformer API reference
- AOQ client SDK (para Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime)
FAQ
Quais formatos de áudio são suportados pelo reconhecimento de fala em tempo real?
Os modelos Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime e Paraformer aceitam os formatos pcm, wav, mp3, opus, speex, aac e amr. Para o modelo Qwen3-ASR-Flash-Realtime, recomendamos os formatos pcm ou opus. Outros formatos (como wav, aac e amr) passam pela validação de session.update, mas podem falhar na decodificação do servidor. Certifique-se de que o fluxo de áudio utiliza um formato recomendado antes de enviá-lo.
Qual a diferença entre o SDK e a API WebSocket, e qual devo escolher?
O DashScope SDK abstrai detalhes como gerenciamento de conexões WebSocket, autenticação e reconexão, sendo ideal para integrações rápidas. Já a conexão direta via API WebSocket oferece controle mais granular, atendendo linguagens não cobertas pelo SDK ou cenários que exigem gestão personalizada de conexões. Recomendamos começar pelo SDK.
Como melhorar a precisão no reconhecimento de substantivos próprios?
Utilize hotwords ou aprimoramento de contexto. Para métodos detalhados de configuração e notas de uso, consulte Improve recognition accuracy.
O que fazer quando a conexão cai frequentemente?
Implemente reconexão no lado do cliente e ative o parâmetro heartbeat (heartbeat=true) para evitar quedas durante longos períodos sem áudio. Para estratégias detalhadas de tolerância a falhas, veja Apply in production.