The real-time speech recognition service receives an audio stream and transcribes it into punctuated text in real time. Use it for live captioning, online meetings, voice chat, smart assistants, and similar scenarios.
Overview
The service streams audio and returns transcribed text with low latency.
-
Recognizes Mandarin Chinese with high accuracy, plus Cantonese, Sichuanese, and other dialects.
-
Handles complex acoustic environments, with automatic language detection and intelligent filtering of non-speech audio.
-
Recognizes a range of emotional states, including surprise, calm, happiness, sadness, disgust, anger, and fear.
-
Supports custom hotwords to improve recognition accuracy for specific terms.
-
Supports context enhancement to improve recognition accuracy by passing in conversation history or domain terms.
-
Outputs timestamps to produce structured recognition results.
-
Accepts flexible sample rates and multiple audio formats to fit different recording environments.
For batch scenarios such as meeting transcription, call analysis, and subtitle generation, use Non-real-time speech recognition. For guidance on choosing a model, see Speech-to-text.
Prerequisites
-
An API key is Obtain an API key and set as an environment variable.
-
To call the service through the DashScope SDK, install the latest SDK.
Quick start
The following examples show how to call the real-time speech recognition service through the DashScope SDK.
Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime
In addition to WebSocket, this model also supports the AOQ protocol. For client-side integration that prioritizes stable latency, resilience on weak networks, and built-in full-duplex noise suppression and echo cancellation, AOQ is recommended. For a protocol comparison, see Realtime API overview.
Recognize speech from a microphone
Recognize speech from a microphone and output text in real time, so words appear as the speaker talks.
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 China (Beijing) region. When calling, replace "{WorkspaceId}" with your real workspace ID. Configurations differ by region.
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.cn-beijing.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://help.aliyun.com/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
Before you run the Python example, install the third-party audio playback and capture toolkit with pip install pyaudio.
import os
import signal # for keyboard events handling (press "Ctrl+C" to terminate recording)
import sys
import dashscope
import pyaudio
from dashscope.audio.asr import *
mic = None
stream = None
# Set recording parameters
sample_rate = 16000 # sampling rate (Hz)
channels = 1 # mono channel
dtype = 'int16' # data type
format_pcm = 'pcm' # the format of the audio data
block_size = 3200 # number of frames per buffer
# Real-time speech recognition callback
class Callback(RecognitionCallback):
def on_open(self) -> None:
global mic
global stream
print('RecognitionCallback open.')
mic = pyaudio.PyAudio()
stream = mic.open(format=pyaudio.paInt16,
channels=1,
rate=16000,
input=True)
def on_close(self) -> None:
global mic
global stream
print('RecognitionCallback close.')
stream.stop_stream()
stream.close()
mic.terminate()
stream = None
mic = None
def on_complete(self) -> None:
print('RecognitionCallback completed.') # recognition completed
def on_error(self, message) -> None:
print('RecognitionCallback task_id: ', message.request_id)
print('RecognitionCallback error: ', message.message)
# Stop and close the audio stream if it is running
if 'stream' in globals() and stream.active:
stream.stop()
stream.close()
# Forcefully exit the program
sys.exit(1)
def on_event(self, result: RecognitionResult) -> None:
sentence = result.get_sentence()
if 'text' in sentence:
print('RecognitionCallback text: ', sentence['text'])
if RecognitionResult.is_sentence_end(sentence):
print(
'RecognitionCallback sentence end, request_id:%s, usage:%s'
% (result.get_request_id(), result.get_usage(sentence)))
def signal_handler(sig, frame):
print('Ctrl+C pressed, stop recognition ...')
# Stop recognition
recognition.stop()
print('Recognition stopped.')
print(
'[Metric] requestId: {}, first package delay ms: {}, last package delay ms: {}'
.format(
recognition.get_last_request_id(),
recognition.get_first_package_delay(),
recognition.get_last_package_delay(),
))
# Forcefully exit the program
sys.exit(0)
# main function
if __name__ == '__main__':
# The API Key differs between the Singapore and Beijing regions. Get an API Key: https://help.aliyun.com/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 China (Beijing) region. When calling, replace "{WorkspaceId}" with your actual workspace ID. The configuration differs by region.
dashscope.base_websocket_api_url='wss://{WorkspaceId}.cn-beijing.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()Recognize a local audio file
Recognize a local audio file and output the result. This suits shorter, near-real-time scenarios such as chat conversations, voice commands, voice input methods, and voice search.
Java
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 China (Beijing) region. When calling, replace "{WorkspaceId}" with your real workspace ID. Configurations differ by region.
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.cn-beijing.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://help.aliyun.com/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());
}
}Python
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://help.aliyun.com/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 China (Beijing) region. When calling, replace "{WorkspaceId}" with your actual workspace ID. The configuration differs by region.
dashscope.base_websocket_api_url = 'wss://{WorkspaceId}.cn-beijing.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
The example code reads your_audio_file.pcm (PCM16, 16 kHz, mono). If you only have an MP3, WAV, or similar format, convert it with ffmpeg:
ffmpeg -i your_audio.mp3 -ar 16000 -ac 1 -f s16le your_audio_file.pcm
Java
import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.LineUnavailableException;
import java.io.File;
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
public class Qwen3AsrRealtimeUsage {
private static final Logger log = LoggerFactory.getLogger(Qwen3AsrRealtimeUsage.class);
private static final int AUDIO_CHUNK_SIZE = 1024; // Audio chunk size in bytes
private static final int SLEEP_INTERVAL_MS = 30; // Sleep interval in milliseconds
public static void main(String[] args) throws InterruptedException, LineUnavailableException {
CountDownLatch finishLatch = new CountDownLatch(1);
OmniRealtimeParam param = OmniRealtimeParam.builder()
.model("qwen3-asr-flash-realtime")
// The following is the configuration for the China (Beijing) region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
.url("wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/realtime")
// The API Key differs between the Singapore and Beijing regions. Get your API Key: https://help.aliyun.com/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;
String audioB64 = Base64.getEncoder().encodeToString(audioBuffer);
// Send audio chunk to conversation
conversation.appendAudio(audioB64);
// Add small delay to simulate real-time audio streaming
Thread.sleep(SLEEP_INTERVAL_MS);
}
log.info("Finished sending audio data. Total bytes sent: {}", totalBytesRead);
} catch (Exception e) {
log.error("Error sending audio from file: {}", filePath, e);
}
//send session.finish and wait for finish and close
conversation.endSession();
log.info("task finished");
System.exit(0);
}
}
Python
import logging
import os
import base64
import signal
import sys
import time
import dashscope
from dashscope.audio.qwen_omni import *
from dashscope.audio.qwen_omni.omni_realtime import TranscriptionParams
def setup_logging():
"""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://help.aliyun.com/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 China (Beijing) region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
url='wss://{WorkspaceId}.cn-beijing.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
The Paraformer example code is similar to that of Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime. Replace the model name with a Paraformer model.
Recognition configuration
Qwen3-ASR-Flash-Realtime interaction modes
The Qwen3-ASR-Flash-Realtime Realtime API offers two interaction modes:
-
VAD mode (default): The server automatically detects the start and end of speech (segmentation). This mode suits real-time conversations, meeting notes, and similar scenarios. To enable it, configure the
session.turn_detectionparameter (enabled by default). -
Manual mode: The client controls segmentation by sending
input_audio_buffer.commit. This mode suits scenarios that require explicit control over when audio is sent, such as sending a voice message in a chat app. To enable it, setsession.turn_detectionto null.
Switch interaction modes:
-
WebSocket: Set the
turn_detectionfield in asession.updateevent.{ "type": "session.update", "session": { "turn_detection": null } } -
Python SDK: Set the
enable_turn_detectionparameter in theupdate_sessionmethod.conversation.update_session( enable_turn_detection=False ) -
Java SDK: Set the
enableTurnDetectionparameter throughOmniRealtimeConfig.builder().OmniRealtimeConfig config = OmniRealtimeConfig.builder() .enableTurnDetection(false) .build(); conversation.updateSession(config);
For complete SDK code examples, see Qwen-ASR-Realtime Python SDK - API reference and Java SDK. For the WebSocket event lifecycle, see Event interaction flow.
VAD segmentation configuration
Voice Activity Detection (VAD) determines when a continuous segment of speech ends, which triggers the final recognition result event. All three model families enable server-side VAD by default, but their parameter names and tuning granularity differ:
-
Qwen-Audio-3.0-ASR-Flash-Streaming / Fun-ASR-Realtime / Paraformer: Configured through
max_sentence_silence(the VAD silence threshold for segmentation, in milliseconds). When the silence after a segment of speech exceeds this threshold, the system treats the sentence as complete. -
Qwen3-ASR-Flash-Realtime: Configured through
session.turn_detection, which includessilence_duration_ms(the silence duration threshold that ends a turn when exceeded; server default800, with400recommended for conversation and chat scenarios that need fast segmentation) andthreshold(VAD detection sensitivity; server default0.2). Qwen3-ASR-Flash-Realtime also supports Manual mode, which disables VAD and uses client-side commit for segmentation. For details, see Qwen3-ASR-Flash-Realtime interaction modes above.
Parameter names vary by protocol: the same concept is called max_sentence_silence in Qwen-Audio-3.0-ASR-Flash-Streaming / Fun-ASR-Realtime / Paraformer, and silence_duration_ms in Qwen3-ASR-Flash-Realtime. For the full field definitions, see API reference.
Advanced features
Improve accuracy with hotwords
Use hotwords to improve recognition accuracy for specific terms, such as brand names, personal names, and proper terminology.
For detailed hotword configuration and usage, see Improve recognition accuracy.
Improve accuracy with context enhancement
Context enhancement passes conversation history or domain terminology to the ASR model to significantly improve transcription accuracy for proper terms. For detailed usage and result examples, see Context enhancement.
Get timestamps
The Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime, and Paraformer model families output timestamps at both the sentence level and the word level by default, which supports subtitle alignment, keyword highlighting, karaoke-style read-along, and similar scenarios. Qwen3-ASR-Flash-Realtime (qwen3-asr-flash-realtime) does not currently return timestamps. If you need timestamps, use Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime, or Paraformer. For file transcription, the Qwen ASR recording-file transcription model qwen3-asr-flash-filetrans supports word-level timestamps. For details, see Non-real-time speech recognition.
Timestamps are returned in milliseconds at two levels:
-
Sentence level:
payload.output.sentence.begin_timeandpayload.output.sentence.end_timemark the start and end of a full sentence in the audio. In an intermediate result,end_timemay benulland is filled with the final value when the sentence ends (sentence_end = true). -
Word level: The
payload.output.sentence.wordsarray, where each element containsbegin_time,end_time,text(the word or character text), andpunctuation(the punctuation that follows the word, or an empty string if none).
The following excerpt shows the response structure:
{
"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": "" }
]
}
}
}
}
The field names above follow the WebSocket JSON paths. Different SDKs expose these fields with their own naming conventions (dictionary keys, object properties, getter methods, and so on). For the complete field mapping, see the API reference for each SDK.
For the full field definitions, see API reference.
Emotion recognition
Qwen3-ASR-Flash-Realtime and some Paraformer models can include the speaker's emotional state in the transcription result, but the two differ in output granularity and in how the feature is enabled.
Qwen3-ASR-Flash-Realtime (qwen3-asr-flash-realtime): Always on, no configuration required. The emotion is returned through a top-level emotion field in both the conversation.item.input_audio_transcription.text and conversation.item.input_audio_transcription.completed events. The value is one of seven fine-grained emotions: surprised, neutral, happy, sad, disgusted, angry, and fearful.
{
"type": "conversation.item.input_audio_transcription.text",
"emotion": "neutral",
"text": "The weather is nice today",
"stash": ""
}
Paraformer (paraformer-realtime-8k-v2): This is the only Paraformer model that supports emotion recognition. The result is returned through payload.output.sentence.emo_tag and payload.output.sentence.emo_confidence. The value is one of three polarities: positive (such as happy or satisfied), negative (such as angry or subdued), and neutral (no clear emotion). The confidence ranges from 0.0 to 1.0.
Emotion recognition is returned only when all of the following conditions are met:
-
The model is
paraformer-realtime-8k-v2. -
Semantic segmentation is off:
semantic_punctuation_enabled = false(false is the default, so no special setting is needed). -
The result is returned only in the sentence-end event, where
sentence_end = true.
To stop returning the emotion fields, set semantic_punctuation_enabled to true. This enables semantic segmentation and no longer returns the emo_tag and emo_confidence fields.
The field names above follow the WebSocket JSON paths. Different SDKs expose these fields with their own naming conventions (dictionary keys, object properties, getter methods, and so on). For the complete field mapping, see the API reference for each SDK.
For the full field definitions, value constraints, and examples, see API reference.
Sensitive word filtering
Sensitive word filtering replaces or removes sensitive words in the recognition result. Use it for call-center quality inspection, content compliance, subtitle review, and similar scenarios.
Supported models: Qwen-Audio-3.0-ASR-Flash-Streaming and Fun-ASR-Realtime only.
Limit: You can set up to 32 sensitive words.
Default behavior: When the special_word_filter parameter is not passed, no sensitive words are filtered.
How to configure: special_word_filter is a JSON object with three subfields:
-
filter_with_signed.word_list: A string array that lists the sensitive words to replace with an equal-length string of*characters. For example, with["test"], "Help me test it" becomes "Help me **** it". -
filter_with_empty.word_list: A string array that lists the sensitive words to remove entirely from the result. For example, with["start"], "Is the game about to start" becomes "Is the game about to". -
system_reserved_filter: A boolean that defaults tofalse. It determines whether sensitive word filtering is enabled.
Configuration example:
{
"special_word_filter": {
"filter_with_signed": {
"word_list": ["test"]
},
"filter_with_empty": {
"word_list": ["start", "occur"]
},
"system_reserved_filter": true
}
}
Different SDKs expose these parameters with their own naming conventions (dictionary keys, object properties, methods, and so on). For the complete field mapping, see the API reference.
Call the raw WebSocket protocol
The following examples show how to connect directly to the server over the raw WebSocket protocol, for scenarios that do not use the DashScope SDK. Each example is a minimal, runnable implementation. For the WebSocket protocol, see the API reference of each model.
Apply in production
Reuse connections (WebSocket)
The WebSocket connections for Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime and Paraformer support reuse: after one recognition task finishes, you can start the next task without reestablishing the connection.
Reuse flow: The client sends finish-task. After the server returns task-finished, the client can send run-task again to start a new task.
-
Wait for the server to return the
task-finishedevent before starting a new task. -
Different tasks over a reused connection must use different
task_idvalues. -
When a task fails, the server returns an error event and closes the connection. That connection cannot be reused.
-
If no new task starts within 60 seconds after a task ends, the connection closes automatically.
Qwen3-ASR-Flash-Realtime uses a session model and does not support connection reuse. Close the connection after each session ends.
For the events of each model, see the corresponding API reference.
High-concurrency best practices
The DashScope SDK includes a built-in pooling mechanism that reuses WebSocket connections and recognition objects, which avoids the overhead of frequent creation and destruction.
Currently, only the Paraformer Java SDK supports this feature.
Improve recognition accuracy
-
Choose a model that matches the sample rate: For 8 kHz telephone audio, use an 8 kHz model directly. This avoids the information loss caused by upsampling to 16 kHz.
-
Improve the input audio quality: Use a high-quality microphone and record in an environment with a high signal-to-noise ratio and no echo. At the application layer, you can integrate algorithms such as noise reduction (for example, RNNoise) and acoustic echo cancellation (AEC) for preprocessing.
Set up a fault-tolerance strategy
-
Client-side reconnection: The client should implement automatic reconnection to handle network jitter. The following is a reference implementation for the Python SDK:
-
Catch exceptions: Implement the
on_errormethod in theCallbackclass. ThedashscopeSDK calls this method when it encounters a network error or another issue. -
Signal the state: When
on_erroris triggered, set a reconnection signal. In Python, you can usethreading.Event, a thread-safe signal flag. -
Reconnection loop: Wrap the main logic in a
forloop (for example, retry 3 times). When the reconnection signal is detected, the current recognition round is interrupted, resources are cleaned up, and after a few seconds the loop runs again to create a brand-new connection.
-
-
Set a heartbeat to keep the connection alive: To maintain a long-lived connection with the server, set the heartbeat parameter to
true. The connection to the server then stays open even when the audio contains no sound for a long time. -
Model rate limits: When you call the model API, note the model's Rate limiting rules.
Supported models and regions
China (Beijing)
To call the following models, use an API Key for the China (Beijing) region:
-
Qwen-Audio-3.0-ASR-Flash-Streaming: qwen-audio-3.0-asr-flash-streaming
-
Fun-ASR-Realtime:
-
fun-asr-realtime (stable version, currently equivalent to fun-asr-realtime-2025-11-07), fun-asr-realtime-2026-02-28 (latest snapshot version), fun-asr-realtime-2025-11-07 (snapshot version), fun-asr-realtime-2025-09-15 (snapshot version)
-
fun-asr-flash-8k-realtime (stable version, currently equivalent to fun-asr-flash-8k-realtime-2026-01-28), fun-asr-flash-8k-realtime-2026-01-28
-
-
Qwen3-ASR-Flash-Realtime: qwen3-asr-flash-realtime (stable version, currently equivalent to qwen3-asr-flash-realtime-2025-10-27), qwen3-asr-flash-realtime-2026-02-10 (latest snapshot version), qwen3-asr-flash-realtime-2025-10-27 (snapshot version)
-
Paraformer: paraformer-realtime-v2, paraformer-realtime-v1, paraformer-realtime-8k-v2, paraformer-realtime-8k-v1
Singapore
To call the following models, use an API Key for the Singapore region:
-
Qwen-Audio-3.0-ASR-Flash-Streaming: qwen-audio-3.0-asr-flash-streaming
-
Fun-ASR-Realtime: fun-asr-realtime (stable version, currently equivalent to fun-asr-realtime-2025-11-07), fun-asr-realtime-2025-11-07 (snapshot version)
-
Qwen3-ASR-Flash-Realtime: qwen3-asr-flash-realtime (stable version, currently equivalent to qwen3-asr-flash-realtime-2025-10-27), qwen3-asr-flash-realtime-2026-02-10 (latest snapshot version), qwen3-asr-flash-realtime-2025-10-27 (snapshot version)
API reference
-
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
-
AOQ Client SDK (for Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime)
FAQ
Which audio formats does real-time speech recognition support?
The Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime, and Paraformer models support the pcm, wav, mp3, opus, speex, aac, and amr formats. For the Qwen3-ASR-Flash-Realtime model, we recommend the pcm or opus format. Other formats (such as wav, aac, and amr) are accepted by the session.update validation layer, but the server-side decoding might fail. Confirm that the audio stream uses a recommended format before you send it.
What's the difference between the SDK and the WebSocket API, and how do I choose?
The DashScope SDK encapsulates details such as WebSocket connection management, authentication, and reconnection, which makes it a good fit for quick integration. Connecting directly to the WebSocket API provides finer-grained control and suits programming languages that the SDK does not cover or scenarios that require custom connection management. We recommend that you use the SDK first.
How do I improve recognition accuracy for proper nouns?
Use hotwords or context enhancement. For detailed configuration methods and usage notes, see Improve recognition accuracy.
What should I do when the connection drops frequently?
Implement client-side reconnection and enable the heartbeat parameter (heartbeat=true) to prevent the connection from dropping when there is no audio for a long time. For detailed fault-tolerance strategies, see Apply in production.