Skip to main content
语音识别

实时语音识别

实时语音识别服务接收音频流并实时转写为带标点的文本,适用于直播字幕、在线会议、语音聊天、智能助手等场景。

概述

实现低延迟音频到文本转换。
  • 支持普通话及粤语、四川话等多种方言的高精度语音识别
  • 具备应对复杂声学环境的能力,支持自动语种检测与智能非人声过滤
  • 支持惊讶、平静、愉快、悲伤、厌恶、愤怒、恐惧等多种情绪状态识别
  • 支持热词定制,可提升特定词汇的识别准确率
  • 支持上下文增强,通过配置上下文提高识别准确率
  • 支持时间戳输出,生成结构化识别结果
  • 灵活采样率与多种音频格式,适配不同录音环境
批量场景(会议转写、通话分析、字幕生成等)可使用非实时语音识别。各模型选型建议请参见语音识别

前提条件

快速开始

以下示例展示如何通过 DashScope SDK 快速调用实时语音识别服务。
  • Qwen-Audio-3.0-ASR-Flash-Streaming/ Fun-ASR -Realtime
  • Qwen3-ASR-Flash-Realtime
  • Paraformer
该模型除 WebSocket 协议外,还支持通过 AOQ 协议接入;如果是客户端对接,且更看重稳定的延迟、弱网下的交互能力、实时双工的降噪与回声消除,可优先考虑 AOQ,协议对比与选型请参见模型/应用支持力度
  • 识别传入麦克风的语音
  • 识别本地音频文件
识别麦克风传入的语音并实时输出文本,实现"边说边出字"的效果。
  • Java
  • Python
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 {
        // 以下为华北2(北京)地域的配置,调用时请将"{WorkspaceId}"替换为真实的业务空间ID,各地域的配置不同。
        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")
                // 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
                // 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                .format("pcm")
                .sampleRate(16000)
                .build();
        Recognition recognizer = new Recognition();

        ResultCallback<RecognitionResult> callback = new ResultCallback<RecognitionResult>() {
            @Override
            public void onEvent(RecognitionResult result) {
                if (result.isSentenceEnd()) {
                    System.out.println("Final Result: " + result.getSentence().getText());
                } else {
                    System.out.println("Intermediate Result: " + result.getSentence().getText());
                }
            }

            @Override
            public void onComplete() {
                System.out.println("Recognition complete");
            }

            @Override
            public void onError(Exception e) {
                System.out.println("RecognitionCallback error: " + e.getMessage());
            }
        };
        try {
            recognizer.call(param, callback);
            // 创建音频格式
            AudioFormat audioFormat = new AudioFormat(16000, 16, 1, true, false);
            // 根据格式匹配默认录音设备
            TargetDataLine targetDataLine =
                    AudioSystem.getTargetDataLine(audioFormat);
            targetDataLine.open(audioFormat);
            // 开始录音
            targetDataLine.start();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            long start = System.currentTimeMillis();
            // 录音50s并进行实时转写
            while (System.currentTimeMillis() - start < 50000) {
                int read = targetDataLine.read(buffer.array(), 0, buffer.capacity());
                if (read > 0) {
                    buffer.limit(read);
                    // 将录音音频数据发送给流式识别服务
                    recognizer.sendAudioFrame(buffer);
                    buffer = ByteBuffer.allocate(1024);
                    // 录音速率有限,防止cpu占用过高,休眠一小会儿
                    Thread.sleep(20);
                }
            }
            recognizer.stop();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 任务结束后关闭 Websocket 连接
            recognizer.getDuplexApi().close(1000, "bye");
        }

        System.out.println(
                "[Metric] requestId: "
                        + recognizer.getLastRequestId()
                        + ", first package delay ms: "
                        + recognizer.getFirstPackageDelay()
                        + ", last package delay ms: "
                        + recognizer.getLastPackageDelay());
    }
}

识别配置

Qwen3-ASR-Flash-Realtime 交互模式

Qwen3-ASR-Flash-Realtime Realtime API 提供两种交互模式:
  • VAD 模式(默认):服务端自动检测语音的起点和终点(断句),适用于实时对话、会议记录等场景。启用方式:配置 session.turn_detection 参数(默认启用)。
  • Manual 模式:由客户端通过发送 input_audio_buffer.commit 控制断句,适用于需要明确控制发送时机的场景(如聊天软件发送语音)。启用方式:将 session.turn_detection 设为 null。
切换交互模式
  • WebSocket:通过 session.update 事件中的 turn_detection 字段设置。
{
    "type": "session.update",
    "session": {
        "turn_detection": null
    }
}
  • Python SDK:在 update_session 方法中通过 enable_turn_detection 参数设置。
conversation.update_session(
    enable_turn_detection=False
)
  • Java SDK:通过 OmniRealtimeConfig.builder() 设置 enableTurnDetection 参数。
OmniRealtimeConfig config = OmniRealtimeConfig.builder()
        .enableTurnDetection(false)
        .build();
conversation.updateSession(config);
完整的 SDK 代码示例请参见Python SDKJava SDK。WebSocket 事件生命周期请参见事件交互流程

VAD 断句配置

VAD(Voice Activity Detection,语音活动检测)用于判定一段连续语音何时结束,从而触发"最终识别结果"事件。三类模型均默认启用服务端 VAD,但参数命名与可调粒度不同:
  • Qwen-Audio-3.0-ASR-Flash-Streaming / Fun-ASR-Realtime / Paraformer:通过 max_sentence_silence(VAD 断句静音阈值,毫秒)配置。当一段语音后的静音时长超过该阈值时,系统判定该句子已结束。
  • Qwen3-ASR-Flash-Realtime:通过 session.turn_detection 配置,含 silence_duration_ms(静音持续时长阈值,超过则判定 turn 结束,服务端默认 800,对话和聊天等需快速断句的场景推荐设为 400)与 threshold(VAD 检测灵敏度,服务端默认 0.2)。Qwen3-ASR-Flash-Realtime 还支持关闭 VAD 改用客户端 commit 控制断句的 Manual 模式,详见上文 Qwen3-ASR-Flash-Realtime 交互模式
参数名因协议而异(同一含义在 Qwen-Audio-3.0-ASR-Flash-Streaming / Fun-ASR-Realtime / Paraformer 中称 max_sentence_silence,在 Qwen3-ASR-Flash-Realtime 中称 silence_duration_ms)。完整字段定义请参见API参考

进阶功能

使用热词提升准确率

支持通过热词提升特定词汇(品牌名、人名、专有术语等)的识别准确率。 详细的热词配置方法和使用说明,请参见提升识别准确率

使用上下文增强提升准确率

支持上下文增强功能,可将对话历史或领域术语传入 ASR 模型,显著提升专有词汇的转写准确率。详细的使用方法和效果示例,请参见上下文增强

获取时间戳

Qwen-Audio-3.0-ASR-Flash-Streaming、Fun-ASR-Realtime 和 Paraformer 系列模型默认输出句级字级两种粒度的时间戳,便于字幕对齐、关键词高亮、卡拉 OK 跟读等场景。Qwen3-ASR-Flash-Realtime(qwen3-asr-flash-realtime)当前不返回时间戳信息,如需时间戳请使用 Qwen-Audio-3.0-ASR-Flash-Streaming、Fun-ASR-Realtime 或 Paraformer。Qwen ASR 的录音文件转写模型 qwen3-asr-flash-filetrans 支持字级时间戳,详见非实时语音识别 时间戳单位均为毫秒,分两个层级返回:
  • 句级payload.output.sentence.begin_timepayload.output.sentence.end_time,标识整句在音频中的起止时刻。中间结果中 end_time 可能为 null,待句子结束(sentence_end = true)时填充最终值。
  • 字级payload.output.sentence.words 数组,每个元素包含 begin_timeend_timetext(该字/词文本)以及 punctuation(该字后跟随的标点,无则为空串)。
返回结构示例(节选):
{
  "payload": {
    "output": {
      "sentence": {
        "begin_time": 170,
        "end_time": 920,
        "text": "好,我知道了",
        "sentence_end": true,
        "words": [
          { "begin_time": 170, "end_time": 295, "text": "好", "punctuation": "," },
          { "begin_time": 295, "end_time": 503, "text": "我", "punctuation": "" },
          { "begin_time": 503, "end_time": 711, "text": "知道", "punctuation": "" },
          { "begin_time": 711, "end_time": 920, "text": "了", "punctuation": "" }
        ]
      }
    }
  }
}
以上字段名以 WebSocket JSON 路径为准。不同 SDK 暴露上述字段的命名习惯不同(如字典 key、对象属性、getter 方法等),完整字段对照请参见各 SDK 的 API 参考。 完整字段定义请参见API参考

情感识别

Qwen3-ASR-Flash-Realtime 与 Paraformer 部分模型可在转写结果中附带说话人的情绪状态,但两者输出粒度与开启方式不同。 Qwen3-ASR-Flash-Realtime(qwen3-asr-flash-realtime):固定开启,无需配置。在 conversation.item.input_audio_transcription.textconversation.item.input_audio_transcription.completed 事件中均通过顶层 emotion 字段返回,取值为 7 类细粒度情绪:surprised(惊讶)、neutral(平静)、happy(愉快)、sad(悲伤)、disgusted(厌恶)、angry(愤怒)、fearful(恐惧)。
{
  "type": "conversation.item.input_audio_transcription.text",
  "emotion": "neutral",
  "text": "今天天气不错",
  "stash": ""
}
Paraformer(paraformer-realtime-8k-v2):仅此一款 Paraformer 模型支持情感识别,结果通过 payload.output.sentence.emo_tagpayload.output.sentence.emo_confidence 返回,取值为 3 类极性:positive(正面,如开心、满意)、negative(负面,如愤怒、沉闷)、neutral(无明显情感),置信度范围 [0.0, 1.0]。 情感识别需同时满足以下条件才会输出:
  • 模型为 paraformer-realtime-8k-v2
  • 语义断句关闭:semantic_punctuation_enabled = false(默认即为 false,无需特别设置)。
  • 仅在 sentence_end = true 的句子结束事件中返回。
如不希望返回情感识别字段,可将 semantic_punctuation_enabled 设为 true,此时将启用语义断句、不再返回 emo_tagemo_confidence 字段。 以上字段名以 WebSocket JSON 路径为准。不同 SDK 暴露上述字段的命名习惯不同(如字典 key、对象属性、getter 方法等),完整字段对照请参见各 SDK 的 API 参考。 完整字段定义、取值约束与示例请参见API参考

敏感词过滤

敏感词过滤可对识别结果中的敏感词执行替换或移除,适用于客服质检、内容合规、字幕审核等场景。 支持范围:仅Qwen-Audio-3.0-ASR-Flash-Streaming和Fun-ASR-Realtime。 使用限制:最多支持设置32个敏感词。 默认行为:未传入 special_word_filter 参数时,不会对敏感词进行过滤。 如何配置:special_word_filter 是 JSON 对象,包含三个子字段:
  • filter_with_signed.word_list:字符串数组,列出需要被替换为等长 * 的敏感词。例如 ["测试"],「帮我测试一下」会变成「帮我**一下」。
  • filter_with_empty.word_list:字符串数组,列出需要从结果中完全移除的敏感词。例如 ["开始"],「比赛这就要开始了吗」会变成「比赛这就要了吗」。
  • system_reserved_filter:布尔值,默认 false。是否启用敏感词过滤功能。
配置示例:
{
  "special_word_filter": {
    "filter_with_signed": {
      "word_list": ["测试"]
    },
    "filter_with_empty": {
      "word_list": ["开始", "发生"]
    },
    "system_reserved_filter": true
  }
}
不同 SDK 暴露上述参数的命名习惯不同(如字典 key、对象属性、方法等),完整字段对照请参见 API参考。

WebSocket 原始协议调用

以下示例展示如何通过 WebSocket 原始协议直连服务端,适用于不使用 DashScope SDK 的场景。此为最小可运行实现,WebSocket 协议请参见各模型的 API参考
  • Qwen-Audio-3.0-ASR-Flash-Streaming/ Fun-ASR-Realtime
  • Qwen3-ASR-Flash-Realtime
  • Paraformer
  • Python
  • Java
  • Node.js
  • C#
  • PHP
  • Go
在运行示例前,请确保已使用以下命令安装依赖:
pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
请不要将示例代码文件命名为 websocket.py,这会与 websocket 库产生命名冲突,导致如下错误: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

# 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
# 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:api_key = "sk-xxx"
api_key = os.environ.get('DASHSCOPE_API_KEY')
# 以下为华北2(北京)地域的配置,调用时请将"{WorkspaceId}"替换为真实的业务空间ID,各地域的配置不同。
url = 'wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference'  # WebSocket服务器地址
audio_file = '{YOUR_AUDIO_FILE}'  # 替换为您的音频文件路径

# 生成32位随机ID
TASK_ID = uuid.uuid4().hex[:32]

task_started = False  # 标记任务是否已启动

# 发送run-task指令
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))

# 发送finish-task指令
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))

# 发送音频流(每100ms发送一个二进制chunk)
def send_audio_stream(ws):
    chunk_size = 3200  # 100ms @ 16kHz 16bit 单声道
    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('音频流结束')
        send_finish_task(ws)
    except Exception as e:
        print('读取音频文件错误:', e)
        ws.close()

# 连接打开时发送run-task指令
def on_open(ws):
    print('连接到服务器')
    send_run_task(ws)

# 接收消息处理
def on_message(ws, data):
    global task_started
    message = json.loads(data)
    event = message['header']['event']
    if event == 'task-started':
        print('任务开始')
        task_started = True
        threading.Thread(target=send_audio_stream, args=(ws,), daemon=True).start()
    elif event == 'result-generated':
        print('识别结果:', message['payload']['output']['sentence']['text'])
        if message['payload'].get('usage'):
            print('任务计费时长(秒):', message['payload']['usage']['duration'])
    elif event == 'task-finished':
        print('任务完成')
        ws.close()
    elif event == 'task-failed':
        print('任务失败:', message['header'].get('error_message'))
        ws.close()
    else:
        print('未知事件:', event)

# 如果没有收到task-started事件,关闭连接
def on_close(ws, close_status_code, close_msg):
    if not task_started:
        print('任务未启动,关闭连接')

# 错误处理
def on_error(ws, error):
    print('WebSocket错误:', 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()

应用于生产环境

连接复用(WebSocket)

Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime 和 Paraformer 的 WebSocket 连接支持复用:一个识别任务结束后,无需重新建立连接即可开启下一个任务。 复用流程:客户端发送 finish-task,服务端返回 task-finished 后,可重新发送 run-task 开启新任务。
  1. 必须等服务端返回 task-finished 事件后才可发起新任务。
  2. 复用连接中的不同任务需要使用不同的 task_id
  3. 任务失败时服务端返回错误事件并关闭连接,该连接不可复用。
  4. 任务结束后 60 秒无新任务,连接自动断开。
Qwen3-ASR-Flash-Realtime 采用会话模式,每次会话结束后需主动断开连接,不支持连接复用。 各模型事件说明请参见对应的API参考

高并发最佳实践

DashScope SDK 内置池化机制,可复用 WebSocket 连接和识别对象,避免频繁创建销毁带来的开销。
目前仅 Java SDK 支持此功能。

前提条件

Java SDK 通过内置的连接池和自定义的对象池协同工作,实现最佳性能:
  • 连接池:SDK 内部集成的 OkHttp3 连接池,负责管理和复用底层的 WebSocket 连接,减少网络握手开销。此功能默认开启。
  • 对象池:基于 commons-pool2 实现,用于维护一组已预先建立好连接的 Recognition 对象。从池中获取对象可消除连接建立的延迟,显著降低首包延迟。

实现步骤

  1. 添加依赖 根据项目构建工具,在依赖配置文件中添加 dashscope-sdk-java 和 commons-pool2。 以 Maven 和 Gradle 为例,配置如下:
    • Maven
    • Gradle
    1. 打开 Maven 项目的 pom.xml 文件。
    2. <dependencies> 标签内添加以下依赖信息。
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>dashscope-sdk-java</artifactId>
        <!-- 请将 'the-latest-version' 替换为2.16.9及以上版本,可在如下链接查询相关版本号: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>
        <!-- 请将 'the-latest-version' 替换为最新版本,可在如下链接查询相关版本号:https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <version>the-latest-version</version>
    </dependency>
    
    1. 保存 pom.xml 文件。
    2. 使用 Maven 命令(如 mvn clean installmvn compile)来更新项目依赖。
  2. 配置连接池 通过环境变量配置连接池关键参数:

    环境变量

    描述

    DASHSCOPE_CONNECTION_POOL_SIZE

    连接池大小。

    推荐值:峰值并发数的 2 倍以上。

    默认值:32。

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS

    最大异步请求数。

    推荐值:与 DASHSCOPE_CONNECTION_POOL_SIZE 保持一致。

    默认值:32。

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST

    单主机最大异步请求数。

    推荐值:与 DASHSCOPE_CONNECTION_POOL_SIZE 保持一致。

    默认值:32。

  3. 配置对象池 通过环境变量配置对象池大小:

    环境变量

    描述

    RECOGNITION_OBJECTPOOL_SIZE

    对象池大小。

    推荐值:峰值并发数的 1.5 至 2 倍。

    默认值:500。

    • 对象池的大小(RECOGNITION_OBJECTPOOL_SIZE)必须小于或等于连接池的大小(DASHSCOPE_CONNECTION_POOL_SIZE)。否则,当对象池请求对象时,若连接池已满,会导致调用线程阻塞。
    • 对象池大小不应超过账户的 QPS(每秒查询率)限制。
    通过如下代码创建对象池:
class RecognitionObjectPool {
    // ……完整示例请参见完整代码
    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;
    }
}
  1. 从对象池中获取 Recognition 对象 未归还的对象数量超过对象池上限时,系统会额外创建新的 Recognition 对象。这类新对象需重新建立 WebSocket 连接,无法复用。
recognizer = RecognitionObjectPool.getInstance().borrowObject();
  1. 进行语音识别 调用 Recognition 对象的 call 或 streamCall 方法进行语音识别。
  2. 归还 Recognition 对象 语音识别任务结束后,归还 Recognition 对象以供复用。不要归还未完成任务或任务失败的对象。
RecognitionObjectPool.getInstance().returnObject(recognizer);

完整代码

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 {
        // 以下为华北2(北京)地域的配置,调用时请将"{WorkspaceId}"替换为真实的业务空间ID,各地域的配置不同。
        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();
    }
}

推荐配置

以下配置基于在指定规格的阿里云服务器上仅运行 Paraformer 实时语音识别服务的测试结果。其中单机并发数指的是同一时刻正在运行的 Paraformer 实时语音识别任务数(即工作线程数)。

机器配置(阿里云)

单机最大并发数

对象池大小

连接池大小

4核8GiB

100

500

2000

8核16GiB

200

500

2000

16核32GiB

400

500

2000

资源管理与异常处理

  • 任务成功:必须调用 GenericObjectPool.returnObject() 将 Recognition 对象归还到池中以便复用。
    不要归还未完成任务或任务失败的 Recognition 对象。
  • 任务失败:当 SDK 内部或业务逻辑抛出异常导致任务中断时,必须执行以下两个操作:
    1. 主动关闭底层的 WebSocket 连接
    2. 从对象池中废弃该对象,防止被再次使用
// 关闭连接
recognizer.getDuplexApi().close(1000, "bye");
// 在对象池中废弃出现异常的 recognizer
RecognitionObjectPool.getInstance().invalidateObject(recognizer);
  • 在服务出现 TaskFailed 报错时,不需要额外处理。

调用预热与耗时统计

在对 DashScope Java SDK 进行并发调用延迟等性能评估时,建议在正式测试前执行充分的预热操作。
连接复用机制
DashScope Java SDK 通过全局单例的连接池管理和复用 WebSocket 连接。该机制的工作特点如下:
  • 按需创建:SDK 不会在服务启动时预创建 WebSocket 连接,而是在首次调用时按需建立。
  • 限时复用:请求完成后,连接将在池中保留最多 60 秒以备复用。
    • 若 60 秒内有新请求,将复用现有连接,避免重复握手开销。
    • 若连接空闲超过 60 秒,将被自动关闭以释放资源。
预热的重要性
在以下场景中,连接池中可能没有可复用的活跃连接,导致请求需要新建连接:
  • 应用刚启动,尚未发起任何调用。
  • 服务空闲时间超过 60 秒,池中连接已因超时而关闭。
在这些场景下,首次或初期请求会触发完整的 WebSocket 建连过程(包括 TCP 握手、TLS 加密协商和协议升级),其端到端延迟会显著高于后续复用连接的请求。
推荐做法
在正式进行性能压测或延迟统计前,请遵循以下预热步骤:
  1. 模拟正式测试的并发级别,提前发起一定数量的调用(例如,持续 1~2 分钟),以充分填充连接池。
  2. 确认连接池已建立并维持足够的活跃连接后,再开始正式的性能数据采集。

提升识别效果

  • 选择匹配采样率的模型:8kHz 电话音频直接使用 8kHz 模型,避免升采样到 16kHz 造成的信息失真。
  • 优化输入音频质量:使用高质量麦克风,确保录音环境信噪比高、无回声。可在应用层集成降噪(如 RNNoise)、回声消除(AEC)等算法做预处理。

设置容错策略

  • 客户端重连:客户端应实现断线自动重连机制,以应对网络抖动。Python SDK 参考实现如下:
    1. 捕获异常:在Callback类中实现on_error方法。当dashscope SDK遇到网络错误或其他问题时,会调用该方法。
    2. 状态通知:当on_error被触发时,设置重连信号。在Python中可以使用threading.Event,它是一种线程安全的信号标志。
    3. 重连循环:将主逻辑包裹在一个for循环中(例如重试3次)。当检测到重连信号后,当前轮次的识别会中断,清理资源,然后等待几秒钟,再次进入循环,创建一个全新的连接。
  • 设置心跳防止连接断开:当需要与服务端保持长连接时,可将参数heartbeat设置为true,即使音频中长时间没有声音,与服务端的连接也不会中断。
  • 模型限流:在调用模型接口时请注意模型的限流规则。

支持的模型与地域

  • 华北2(北京)
  • 新加坡
调用以下模型时,请选择北京地域的API Key
  • Qwen-Audio-3.0-ASR-Flash-Streaming:qwen-audio-3.0-asr-flash-streaming
  • Fun-ASR-Realtime
    • fun-asr-realtime(稳定版,当前等同fun-asr-realtime-2025-11-07)、fun-asr-realtime-2026-02-28(最新快照版)、fun-asr-realtime-2025-11-07(快照版)、fun-asr-realtime-2025-09-15(快照版)
    • fun-asr-flash-8k-realtime(稳定版,当前等同fun-asr-flash-8k-realtime-2026-01-28)、fun-asr-flash-8k-realtime-2026-01-28
  • Qwen3-ASR-Flash-Realtime:qwen3-asr-flash-realtime(稳定版,当前等同qwen3-asr-flash-realtime-2025-10-27)、qwen3-asr-flash-realtime-2026-02-10(最新快照版)、qwen3-asr-flash-realtime-2025-10-27(快照版)
  • Paraformer:paraformer-realtime-v2、paraformer-realtime-v1、paraformer-realtime-8k-v2、paraformer-realtime-8k-v1

API参考

常见问题

实时语音识别支持哪些音频格式?

Qwen-Audio-3.0-ASR-Flash-Streaming、Fun-ASR-Realtime 和 Paraformer 模型支持 pcm、wav、mp3、opus、speex、aac、amr 格式。Qwen3-ASR-Flash-Realtime 模型推荐使用 pcm 或 opus 格式;其他格式(如 wav、aac、amr)虽然在 session.update 校验层会被接受,但服务端实际解码可能失败,请务必确认音频流为推荐格式后再发送。

SDK 和 WebSocket API 有什么区别?该如何选择?

DashScope SDK 封装了 WebSocket 连接管理、鉴权、重连等细节,适合快速集成。WebSocket API 直连提供更细粒度的控制能力,适用于 SDK 未覆盖的编程语言或需要自定义连接管理的场景。推荐优先使用 SDK。

如何提升专有名词的识别准确率?

使用热词或上下文增强。详细的配置方法和使用说明,请参见提升识别准确率

连接经常断开怎么办?

建议实现客户端重连机制,并开启心跳参数(heartbeat=true)防止长时间无音频导致连接断开。详细的容错策略请参见应用于生产环境

模型应用上架及备案

参见应用合规备案
Token Plan
模型体验
模型调优
模型压缩目录节点
用量统计与性能监控
资产中心
服务支持