Skip to main content

实时语音识别

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

概述

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

前提条件

快速开始

以下示例展示如何通过 DashScope SDK 快速调用实时语音识别服务。
  • Qwen-Audio-ASR-Message
  • Qwen-Audio-ASR-Streaming / Fun-ASR-Realtime
  • Paraformer
接入方式请参见 模型接入方式。
import os
from http import HTTPStatus

import dashscope
from dashscope.audio.asr import Recognition

dashscope.api_key = os.environ["DASHSCOPE_API_KEY"]
# 将 {WorkspaceId} 替换为北京地域的业务空间 ID。
dashscope.base_websocket_api_url = "wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference"

recognition = Recognition(
    model="qwen-audio-3.1-asr-flash-message",
    format="wav",
    sample_rate=16000,
    callback=None,
)
# audio.wav:16 kHz、单声道、16-bit PCM 编码的 WAV 文件。
result = recognition.call("audio.wav")
if result.status_code == HTTPStatus.OK:
    print(result.get_sentence())
else:
    raise RuntimeError(result.message)

进阶功能

中间结果与文本润色

qwen-audio-3.1-asr-flash-message 的 intermediate_result_enabled 控制中间结果,disfluency_removal_enabled 控制文本润色,两项默认均为 false。 开启中间结果后,按 sentence_id 更新对应句子的显示文本;中间结果可能修正,不应逐条拼接。收到 sentence_end=true 后,用最终结果替换该句文本。开启润色后,最终结果可去除语气词、重复和自我纠正,并整理表达。 客户端事件 · 服务端事件

VAD 断句配置

VAD(Voice Activity Detection,语音活动检测)用于判定一段连续语音何时结束,从而触发"最终识别结果"事件。以下模型均默认启用服务端 VAD,但参数命名与可调粒度不同:
  • qwen-audio-3.1-asr-flash-message:通过 vad_model 选择 near_meeting_16k(近场)或 far_field_meeting_16k(远场,默认值)。参数定义请参见 客户端事件。
  • qwen-audio-3.1-asr-flash-streaming:通过 vad_model 选择 VAD 模型,可选值为 near_meeting_16k(近场)和 far_field_meeting_16k(远场,默认值)。参数定义请参见客户端事件。
  • 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.1-asr-flash-message 同样在最终结果中返回句级与字级时间戳,字段定义请参见 服务端事件。 Qwen-Audio-ASR-Streaming、Fun-ASR-Realtime 和 Paraformer 系列模型默认输出句级与字级两种粒度的时间戳,便于字幕对齐、关键词高亮、卡拉 OK 跟读等场景。Qwen3-ASR-Flash-Realtime(qwen3-asr-flash-realtime)当前不返回时间戳信息,如需时间戳请使用 Qwen-Audio-ASR-Streaming、Fun-ASR-Realtime 或 Paraformer。Qwen ASR 的录音文件转写模型 qwen3-asr-flash-filetrans 支持字级时间戳,详见非实时语音识别。 时间戳单位均为毫秒,分两个层级返回:
  • 句级:payload.output.sentence.begin_time 与 payload.output.sentence.end_time,标识整句在音频中的起止时刻。中间结果中 end_time 可能为 null,待句子结束(sentence_end = true)时填充最终值。
  • 字级:payload.output.sentence.words 数组,每个元素包含 begin_time、end_time、text(该字/词文本)以及 punctuation(该字后跟随的标点,无则为空串)。
返回结构示例(节选):
{
  "payload": {
    "output": {
      "sentence": {
        "begin_time": 170,
        "end_time": 920,
        "text": "好,我知道了",
        "sentence_end": true,
        "words": [
          { "begin_time": 170, "end_time": 295, "text": "好", "punctuation": "," },
          { "begin_time": 295, "end_time": 503, "text": "我", "punctuation": "" },
          { "begin_time": 503, "end_time": 711, "text": "知道", "punctuation": "" },
          { "begin_time": 711, "end_time": 920, "text": "了", "punctuation": "" }
        ]
      }
    }
  }
}
以上字段名以 WebSocket JSON 路径为准。不同 SDK 暴露上述字段的命名习惯不同(如字典 key、对象属性、getter 方法等),完整字段对照请参见各 SDK 的 API 参考。 完整字段定义请参见API参考。

情感识别

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

敏感词过滤

敏感词过滤可对识别结果中的敏感词执行替换或移除,适用于客服质检、内容合规、字幕审核等场景。 支持范围:仅Qwen-Audio-ASR-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-ASR-Message
  • Qwen-Audio-ASR-Streaming / Fun-ASR-Realtime
  • Qwen3-ASR-Flash-Realtime
  • Paraformer
使用 qwen-audio-3.1-asr-flash-message,示例请参见 WebSocket API。默认返回最终结果。如需边说边显示,设置 intermediate_result_enabled=true;如需去除语气词、重复和自我纠正并整理文本,设置 disfluency_removal_enabled=true。两项默认值均为 false。客户端事件 · 服务端事件

应用于生产环境

连接复用(WebSocket)

Qwen-Audio-ASR-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 install 或 mvn 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核8GiB1005002000
8核16GiB2005002000
16核32GiB4005002000

资源管理与异常处理

  • 任务成功:必须调用 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-ASR-Message:qwen-audio-3.1-asr-flash-message
  • Qwen-Audio-ASR-Streaming:qwen-audio-3.1-asr-flash-streaming、qwen-audio-3.0-asr-flash-streaming
  • Fun-ASR-Realtime:
    • fun-asr-realtime(稳定版)、fun-asr-realtime-2026-02-28(最新快照版)
    • fun-asr-flash-8k-realtime(稳定版,当前等同fun-asr-flash-8k-realtime-2026-01-28)、fun-asr-flash-8k-realtime-2026-01-28
  • Qwen-ASR-Realtime:qwen3-asr-flash-realtime(稳定版)
  • Paraformer:paraformer-realtime-v2、paraformer-realtime-8k-v2

API参考

通过 AOQ 接入的流程和示例,请参见AOQ 接入。支持的模型及版本请参见模型与协议支持范围。

常见问题

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

qwen-audio-3.1-asr-flash-message 支持 pcm、wav、mp3、opus、speex、aac、amr 格式,sample_rate 设置为 16000。编码及封装要求请参见 客户端事件。 Qwen-Audio-ASR-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
模型体验
模型调优
模型压缩
用量统计与性能监控
资产中心
服务支持