使用Java SDK接入Paraformer实时语音识别服务,支持非流式调用、基于回调的双向流式调用和基于Flowable的双向流式调用。
前提条件
- 开通服务:实时语音识别服务需要单独开通,详见实时语音识别概述。
- 获取API Key:在使用前,您需要获取API Key并配置环境变量
DASHSCOPE_API_KEY。如需使用临时Token,请参考临时Token。 - 安装Java SDK:参考安装SDK完成Maven依赖配置。
模型列表
| paraformer-realtime-v2(推荐) | paraformer-realtime-8k-v2(推荐) | paraformer-realtime-v1 | paraformer-realtime-8k-v1 | |
|---|---|---|---|---|
| 适用场景 | 直播、会议 | 电话客服/8kHz | 直播、会议 | 电话客服/8kHz |
| 采样率 | 任意 | 8kHz | 16kHz | 8kHz |
| 语种 | 中文+英/日/韩/德/法/俄 | 中文 | 中文 | 中文 |
| 标点符号预测 | 默认支持 | 默认支持 | 默认支持 | 默认支持 |
| ITN | 默认支持 | 默认支持 | 默认支持 | 默认支持 |
| 定制热词 | 支持(vocabularyId) | 支持(vocabularyId) | 支持(phraseId) | 支持(phraseId) |
| 指定待识别语种 | language_hints | 不支持 | 不支持 | 不支持 |
| 情感识别 | 不支持 | 支持(仅isSentenceEnd=true时) | 不支持 | 不支持 |
快速开始
安装DashScope Java SDK后,设置环境变量:复制
export DASHSCOPE_API_KEY="your-api-key"
RecognitionParam.builder() 构建参数,通过 Recognition 类发起识别任务。根据使用场景选择调用方式:
- 非流式调用:适用于本地音频文件,调用阻塞直到文件读取完毕。
- 双向流式调用(基于回调):适用于麦克风等实时音频流,通过
ResultCallback异步接收结果。 - 双向流式调用(基于Flowable):响应式编程风格,适用于RxJava生态。
无论使用哪种调用方式,任务结束后必须调用
recognizer.getDuplexApi().close(1000, "bye") 关闭WebSocket连接,否则连接将无法正常释放。非流式调用
调用recognizer.call(param, File) 方法会阻塞线程,直到音频文件读取完毕并返回完整识别结果字符串。
复制
import com.alibaba.dashscope.audio.asr.recognition.Recognition;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionParam;
import java.io.File;
public class Main {
public static void main(String[] args) {
Recognition recognizer = new Recognition();
RecognitionParam param =
RecognitionParam.builder()
// .apiKey("yourApikey")
.model("paraformer-realtime-v2")
.format("wav")
.sampleRate(16000)
// "language_hints"只支持paraformer-realtime-v2模型
.parameter("language_hints", new String[]{"zh", "en"})
.build();
try {
System.out.println("识别结果:" + recognizer.call(param, new File("asr_example.wav")));
} catch (Exception e) {
e.printStackTrace();
} finally {
recognizer.getDuplexApi().close(1000, "bye");
}
System.out.println(
"[Metric] requestId: "
+ recognizer.getLastRequestId()
+ ", first package delay ms: "
+ recognizer.getFirstPackageDelay()
+ ", last package delay ms: "
+ recognizer.getLastPackageDelay());
System.exit(0);
}
}
实时
Paraformer 实时语音识别 WebSocket API
通过 WebSocket 连接使用 Paraformer 实时语音识别服务的完整 API 参考。
Paraformer 实时语音识别通过 WebSocket 全双工协议实现流式语音输入与实时识别结果输出,适用于直播字幕、实时会议转写、电话客服等场景。
。
前提条件
- 已获取 API Key(参见获取 API Key)并配置到环境变量(参见配置 API Key 到环境变量)
- 如需临时访问,可使用临时鉴权 Token
模型列表
| 特性 | paraformer-realtime-v2 | paraformer-realtime-8k-v2 | paraformer-realtime-v1 | paraformer-realtime-8k-v1 |
|---|---|---|---|---|
| 适用场景 | 直播、会议 | 电话客服、语音信箱(8kHz) | 直播、会议 | 电话客服、语音信箱(8kHz) |
| 采样率 | 任意 | 8kHz | 16kHz | 8kHz |
| 语种 | 中文(含方言)、英、日、韩、德、法、俄 | 中文 | 中文 | 中文 |
| 标点 | 默认支持 | 默认支持 | 默认支持 | 默认支持 |
| ITN | 默认支持 | 默认支持 | 默认支持 | 默认支持 |
| 定制热词 | 定制热词(v2) | 定制热词(v2) | Paraformer 热词定制与管理 | Paraformer 热词定制与管理 |
| 指定语种 | ✓ language_hints | ✗ | ✗ | ✗ |
| 情感识别 | ✗ | ✓(有约束条件) | ✗ | ✗ |
交互流程
与 Paraformer 实时语音识别服务交互须遵循以下步骤:- 建立 WebSocket 连接 — 使用 WebSocket 客户端库,向服务地址发起连接,并在请求头中携带 Authorization 鉴权信息。
-
监听服务端消息(开启独立线程) — 在独立于主线程的线程中异步监听服务端消息,并根据事件类型分别处理:
- 收到
task-started事件后,开始向服务端发送音频。 - 收到
result-generated事件后,处理识别结果。 - 收到
task-finished事件时,表示任务已完成,可关闭连接。 - 收到
task-failed事件时,表示任务失败,需关闭连接并根据错误信息排查。
- 收到
-
向服务端发送指令和二进制音频(请务必注意时序) — 在主线程(或与监听线程不同的线程)中发送指令和音频,发送时序如下:
- 发送
run-task指令,启动识别任务 - 收到
task-started事件后,持续发送二进制音频(单声道) - 音频发送完毕后,发送
finish-task指令结束任务
- 发送
-
关闭 WebSocket 连接 — 在程序正常结束、出现异常或收到
task-finished/task-failed事件后,调用客户端库的close方法关闭连接。
连接地址
双向流式调用:基于回调
通过
ResultCallback 接口接收识别结果,call(param, callback) 不阻塞线程。调用后通过 sendAudioFrame(ByteBuffer) 持续推送音频帧,调用 stop() 等待识别完成。
以下示例从麦克风采集音频并进行实时识别:
复制
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 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 {
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()
// .apiKey("yourApikey")
.model("paraformer-realtime-v2")
.format("wav")
.sampleRate(16000)
.parameter("language_hints", new String[]{"zh", "en"})
.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();
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);
Thread.sleep(20);
}
}
recognizer.stop();
} catch (Exception e) {
e.printStackTrace();
} finally {
recognizer.getDuplexApi().close(1000, "bye");
}
System.out.println(
"[Metric] requestId: "
+ recognizer.getLastRequestId()
+ ", first package delay ms: "
+ recognizer.getFirstPackageDelay()
+ ", last package delay ms: "
+ recognizer.getLastPackageDelay());
}
}
双向流式调用:基于Flowable
通过streamCall(param, Flowable<ByteBuffer>) 方法以响应式编程风格发起识别,返回 Flowable<RecognitionResult>。以下示例从麦克风采集音频,通过 blockingForEach 同步处理识别结果:
复制
import com.alibaba.dashscope.audio.asr.recognition.Recognition;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionParam;
import com.alibaba.dashscope.exception.NoApiKeyException;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.TargetDataLine;
import java.nio.ByteBuffer;
public class Main {
public static void main(String[] args) throws NoApiKeyException {
Flowable<ByteBuffer> audioSource = Flowable.create(
emitter -> {
new Thread(() -> {
try {
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();
while (System.currentTimeMillis() - start < 50000) {
int read = targetDataLine.read(buffer.array(), 0, buffer.capacity());
if (read > 0) {
buffer.limit(read);
emitter.onNext(buffer);
buffer = ByteBuffer.allocate(1024);
Thread.sleep(20);
}
}
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}).start();
},
BackpressureStrategy.BUFFER);
Recognition recognizer = new Recognition();
RecognitionParam param = RecognitionParam.builder()
// .apiKey("yourApikey")
.model("paraformer-realtime-v2")
.format("pcm")
.sampleRate(16000)
.parameter("language_hints", new String[]{"zh", "en"})
.build();
recognizer.streamCall(param, audioSource).blockingForEach(result -> {
if (result.isSentenceEnd()) {
System.out.println("Final Result: " + result.getSentence().getText());
} else {
System.out.println("Intermediate Result: " + result.getSentence().getText());
}
});
recognizer.getDuplexApi().close(1000, "bye");
System.out.println(
"[Metric] requestId: "
+ recognizer.getLastRequestId()
+ ", first package delay ms: "
+ recognizer.getFirstPackageDelay()
+ ", last package delay ms: "
+ recognizer.getLastPackageDelay());
System.exit(0);
}
}
高并发调用
高并发场景下,DashScope Java SDK内部使用OkHttp3连接池管理WebSocket连接。详细最佳实践请参考高并发调用。请求参数
通过RecognitionParam.builder() 构建请求参数:
| 参数 | 类型 | 默认值 | 必须 | 说明 |
|---|---|---|---|---|
| model | String | - | 是 | 用于实时语音识别的模型 |
| sampleRate | Integer | - | 是 | 采样率(Hz);v2任意,v1=16kHz,8k版=8kHz |
| format | String | - | 是 | pcm/wav/mp3/opus/speex/aac/amr;opus/speex需Ogg封装;wav需PCM编码;amr仅AMR-NB |
| vocabularyId | String | - | 否 | 热词ID(v2及以上);详见自定义热词和热词管理 |
| phraseId | String | - | 否 | 热词ID(v1系列) |
| disfluencyRemovalEnabled | boolean | false | 否 | 是否过滤语气词 |
| language_hints | String[] | ["zh","en"] | 否 | 指定语言代码;仅v2支持;通过parameter()或parameters()设置 |
| semantic_punctuation_enabled | boolean | false | 否 | 开启语义断句(关闭VAD);v2+;通过parameter()/parameters()设置 |
| max_sentence_silence | Integer | 800 | 否 | VAD断句静音阈值(ms),200-6000;v2+,VAD模式 |
| multi_threshold_mode_enabled | boolean | false | 否 | 防止VAD断句切割过长;v2+,VAD模式 |
| punctuation_prediction_enabled | boolean | true | 否 | 自动添加标点;v2+ |
| heartbeat | boolean | false | 否 | 长连接保持;SDK>=2.19.1;v2+ |
| inverse_text_normalization_enabled | boolean | true | 否 | ITN(中文数字→阿拉伯数字);v2+ |
| apiKey | String | - | 否 | 用户API Key |
关键接口
Recognition
| 方法 | 参数 | 返回值 | 描述 |
|---|---|---|---|
call(RecognitionParam, ResultCallback<RecognitionResult>) | param, callback | 无 | 回调形式流式识别,不阻塞线程 |
call(RecognitionParam, File) | param, file | String | 非流式调用,阻塞直到读完文件 |
streamCall(RecognitionParam, Flowable<ByteBuffer>) | param, audioFrame | Flowable<RecognitionResult> | Flowable流式识别 |
sendAudioFrame(ByteBuffer) | audioFrame | 无 | 推送音频帧,建议每包100ms,1KB-16KB |
stop() | 无 | 无 | 停止识别,阻塞直到onComplete/onError触发 |
getDuplexApi().close(int, String) | code, reason | true | 关闭WebSocket连接,任务结束后必须调用 |
getLastRequestId() | 无 | requestId | 获取requestId(v2.18.0+) |
getFirstPackageDelay() | 无 | 首包延迟 | 任务完成后使用(v2.18.0+) |
getLastPackageDelay() | 无 | 尾包延迟 | 任务完成后使用(v2.18.0+) |
回调接口(ResultCallback)
| 方法 | 参数 | 返回值 | 描述 |
|---|---|---|---|
onEvent(RecognitionResult) | result | 无 | 服务有回复时触发 |
onComplete() | 无 | 无 | 任务完成后触发 |
onError(Exception) | e | 无 | 发生异常时触发 |
响应结果
RecognitionResult
| 方法 | 返回值 | 描述 |
|---|---|---|
getRequestId() | requestId | 获取requestId |
isSentenceEnd() | boolean | 判断句子是否结束 |
getSentence() | Sentence | 获取单句信息 |
实时识别结果(Sentence)
| 方法 | 返回值 | 描述 |
|---|---|---|
getBeginTime() | long(ms) | 句子开始时间 |
getEndTime() | long(ms) | 句子结束时间 |
getText() | String | 识别文本 |
getWords() | List<Word> | 字时间戳信息列表 |
getEmoTag() | String | 当前句子情感(positive/negative/neutral);仅paraformer-realtime-8k-v2,isSentenceEnd=true时 |
getEmoConfidence() | Double[0.0,1.0] | 情感置信度;仅paraformer-realtime-8k-v2 |
字时间戳信息(Word)
| 方法 | 返回值 | 描述 |
|---|---|---|
getBeginTime() | long(ms) | 字开始时间 |
getEndTime() | long(ms) | 字结束时间 |
getText() | String | 识别的字 |
getPunctuation() | String | 标点 |
错误码
请参考错误码文档了解错误码的含义和处理方法。更多示例
更多调用示例请参考GitHub仓库中的示例代码常见问题
长时间静默如何保持长连接?
长时间静默如何保持长连接?
设置
heartbeat=true 参数(需SDK>=2.19.1,仅v2+支持),同时持续发送静默音频帧,保持WebSocket连接活跃。如何将音频格式转换?
如何将音频格式转换?
推荐使用FFmpeg进行音频格式转换:查看音频文件信息:
复制
# 基础转换命令(万能模板)
ffmpeg -i input_audio.ext -c:a 编码器名 -b:a 比特率 -ar 采样率 -ac 声道数 output.ext
# WAV → MP3
ffmpeg -i input.wav -c:a libmp3lame -q:a 0 output.mp3
# MP3 → WAV (16bit PCM)
ffmpeg -i input.mp3 -c:a pcm_s16le -ar 44100 -ac 2 output.wav
# M4A → AAC
ffmpeg -i input.m4a -c:a copy output.aac
ffmpeg -i input.m4a -c:a aac -b:a 256k output.aac
# FLAC → Opus
ffmpeg -i input.flac -c:a libopus -b:a 128k -vbr on output.opus
复制
ffprobe -v error -show_entries format=format_name -show_entries stream=codec_name,sample_rate,channels -of default=noprint_wrappers=1 input.xxx
是否支持查看每句话时间范围?
是否支持查看每句话时间范围?
支持。识别结果中每个句子(Sentence)都包含开始时间(
getBeginTime())和结束时间(getEndTime()),单位为毫秒。如何识别本地文件?
如何识别本地文件?
有两种方式:
- 非流式调用:直接传入文件路径,调用
call(param, new File("path/to/file.wav"))方法。 - 流式调用:将文件读取为二进制流,通过
sendAudioFrame(ByteBuffer)分帧发送,或使用streamCall(param, audioSource)方式传入Flowable<ByteBuffer>。
无法识别语音是什么原因?
无法识别语音是什么原因?
请检查以下几点:
- 确认
format和sampleRate参数与实际音频文件一致 - 如设置了
language_hints,确认指定的语种与音频实际语种匹配 - 如识别效果不佳,可使用自定义热词提升特定词汇的识别准确率
上一页
Paraformer实时语音识别Python SDK
本文介绍Paraformer实时语音识别Python SDK的参数和接口细节。
下一页
3D 生成 API
wss://dashscope.aliyuncs.com/api-ws/v1/inference/
请求头
| 参数 | 是否必填 | 说明 |
|---|---|---|
Authorization | 必填 | 鉴权信息,格式:Bearer $DASHSCOPE_API_KEY |
user-agent | 可选 | 用户代理标识,用于版本追踪 |
X-DashScope-WorkSpace | 可选 | 业务空间 ID,用于指定请求所属业务空间 |
X-DashScope-DataInspection | 可选 | 是否启用数据合规检测功能。默认不传或设为 enable。如非必要,请勿启用该参数。 |
客户端指令
run-task 指令
用于启动语音识别任务。示例如下:复制
{
"header": {
"action": "run-task",
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"streaming": "duplex"
},
"payload": {
"task_group": "audio",
"task": "asr",
"function": "recognition",
"model": "paraformer-realtime-v2",
"parameters": {
"format": "pcm",
"sample_rate": 16000,
"disfluency_removal_enabled": false,
"language_hints": ["en"]
},
"input": {}
}
}
header 参数
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
action | String | 必填 | 固定值 "run-task" |
task_id | String | 必填 | 任务 ID,UUID 格式,每次任务须使用不同的 ID |
streaming | String | 必填 | 固定值 "duplex" |
payload 参数
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
task_group | String | 必填 | 固定值 "audio" |
task | String | 必填 | 固定值 "asr" |
function | String | 必填 | 固定值 "recognition" |
model | String | 必填 | 模型名称,见模型列表 |
parameters | Object | 必填 | 识别参数,见下表 |
input | Object | 必填 | 固定为空对象 {} |
resources | Array | 可选 | 热词资源列表,仅 v1 模型支持,见下表 |
payload.parameters 参数
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
format | String | 必填 | 音频格式,支持 pcm、wav、mp3、opus、speex、aac、amr。pcm 为无头 PCM 裸音频。约束:opus/speex 必须使用 Ogg 封装;wav 必须为 PCM 编码;amr 仅支持 AMR-NB 类型。 |
sample_rate | Integer | 必填 | 采样率(Hz),如 16000。使用 paraformer-realtime-8k-v2 时必须为 8000。 |
vocabulary_id | String | 可选 | 热词 ID,仅 v2 模型支持,从定制热词(v2)中获取。 |
disfluency_removal_enabled | Boolean | 可选 | 是否去除语气词(如"嗯""啊"),默认 false。 |
language_hints | Array[String] | 可选 | 语言提示,仅 paraformer-realtime-v2 支持,如 ["zh", "en"]。 |
semantic_punctuation_enabled | Boolean | 可选 | 是否启用语义断句,仅 v2 模型支持,默认 false。设为 true 开启语义断句(准确性更高,适合会议转写),设为 false 使用 VAD 断句(延迟更低,适合交互场景)。 |
max_sentence_silence | Integer | 可选 | 句末静音时长(毫秒),范围 200–6000,默认 1300,仅 v2 模型 VAD 模式支持。仅在 semantic_punctuation_enabled 为 false 时生效。 |
multi_threshold_mode_enabled | Boolean | 可选 | 是否启用多阈值 VAD 模式,启用后可防止 VAD 断句切割过长。仅 v2 模型支持,默认 false。仅在 semantic_punctuation_enabled 为 false 时生效。 |
punctuation_prediction_enabled | Boolean | 可选 | 是否启用标点预测,仅 v2 模型支持,默认 true。 |
heartbeat | Boolean | 可选 | 是否启用心跳包,设为 true 后服务端定期发送心跳,仅 v2 模型支持。 |
inverse_text_normalization_enabled | Boolean | 可选 | 是否启用数字、日期等书面语转换(ITN),仅 v2 模型支持,默认 true。 |
payload.resources 参数(v1 热词)
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
resource_id | String | 可选 | 热词 ID,从 Paraformer 热词定制与管理中获取 |
resource_type | String | 必填 | 固定值 "asr_phrase" |
二进制音频
建立 WebSocket 连接并收到task-started 事件后,通过 WebSocket 二进制帧持续发送待识别音频:
- 音频须为单声道,格式和采样率与
payload.parameters中声明一致。 - 建议每次发送约 100ms 的音频数据,模拟麦克风实时流。
- 必须在收到
task-started事件后再发送音频,否则任务失败。
finish-task 指令
音频发送完毕后,发送此指令通知服务端结束识别任务:复制
{
"header": {
"action": "finish-task",
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"streaming": "duplex"
},
"payload": {
"input": {}
}
}
header 参数
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
action | String | 必填 | 固定值 "finish-task" |
task_id | String | 必填 | 与 run-task 指令中的 task_id 保持一致 |
streaming | String | 必填 | 固定值 "duplex" |
payload 参数
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
input | Object | 必填 | 固定为空对象 {} |
服务端事件
task-started 事件
服务端确认任务启动后返回此事件,收到后即可开始发送音频。复制
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-started",
"attributes": {}
},
"payload": {}
}
result-generated 事件
服务端识别出语音片段后返回此事件。复制
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "result-generated",
"attributes": {}
},
"payload": {
"output": {
"sentence": {
"begin_time": 170,
"end_time": null,
"text": "好,我知道了",
"heartbeat": false,
"sentence_end": true,
"emo_tag": "neutral",
"emo_confidence": 0.914,
"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": ""}
]
}
},
"usage": {"duration": 3}
}
}
payload.output.sentence 参数
| 参数 | 类型 | 说明 |
|---|---|---|
begin_time | Integer | 句子起始时间(毫秒) |
end_time | Integer | null | 句子结束时间(毫秒),句子未结束时为 null |
text | String | 识别文本 |
words | Array | 词级时间戳,见下表 |
heartbeat | Boolean | null | 是否为心跳包,启用 heartbeat 参数时出现 |
sentence_end | Boolean | 是否为句子终止帧 |
emo_tag | String | 情感标签,取值 positive / negative / neutral,仅 paraformer-realtime-8k-v2 且 sentence_end=true 时返回 |
emo_confidence | Number | 情感置信度,取值范围 [0.0, 1.0],与 emo_tag 同条件返回 |
payload.output.sentence.words 参数
| 参数 | 类型 | 说明 |
|---|---|---|
begin_time | Integer | 词起始时间(毫秒) |
end_time | Integer | 词结束时间(毫秒) |
text | String | 词文本 |
punctuation | String | 词后标点,无标点时为空字符串 |
payload.usage 参数
| 参数 | 类型 | 说明 |
|---|---|---|
duration | Integer | 本次计费时长(秒) |
task-finished 事件
服务端完成所有识别后返回此事件,收到后可关闭 WebSocket 连接。复制
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-finished",
"attributes": {}
},
"payload": {
"output": {},
"usage": null
}
}
task-failed 事件
任务失败时服务端返回此事件,收到后需关闭连接并根据错误信息排查。复制
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-failed",
"error_code": "CLIENT_ERROR",
"error_message": "request timeout after 23 seconds.",
"attributes": {}
},
"payload": {}
}
复用连接
同一 WebSocket 连接可复用于多个识别任务,但须注意:- 每次新任务必须使用不同的
task_id。 - 若某次任务失败(收到
task-failed),连接将被关闭,不可复用。 - 任务结束后连接保持空闲超过 60 秒,服务端将主动关闭连接。
示例代码
以下示例使用音频文件 asr_example.wav- Go
- C#
- PHP
- Node.js
复制
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
const (
wsURL = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/" // WebSocket服务器地址
audioFile = "asr_example.wav" // 替换为您的音频文件路径
)
var dialer = websocket.DefaultDialer
func main() {
// 若没有将API Key配置到环境变量,可将下行替换为:apiKey := "your_api_key"。不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。
apiKey := os.Getenv("DASHSCOPE_API_KEY")
// 连接WebSocket服务
conn, err := connectWebSocket(apiKey)
if err != nil {
log.Fatal("连接WebSocket失败:", err)
}
defer closeConnection(conn)
// 启动一个goroutine来接收结果
taskStarted := make(chan bool)
taskDone := make(chan bool)
startResultReceiver(conn, taskStarted, taskDone)
// 发送run-task指令
taskID, err := sendRunTaskCmd(conn)
if err != nil {
log.Fatal("发送run-task指令失败:", err)
}
// 等待task-started事件
waitForTaskStarted(taskStarted)
// 发送待识别音频文件流
if err := sendAudioData(conn); err != nil {
log.Fatal("发送音频失败:", err)
}
// 发送finish-task指令
if err := sendFinishTaskCmd(conn, taskID); err != nil {
log.Fatal("发送finish-task指令失败:", err)
}
// 等待任务完成或失败
<-taskDone
}
// 定义结构体来表示JSON数据
type Header struct {
Action string `json:"action"`
TaskID string `json:"task_id"`
Streaming string `json:"streaming"`
Event string `json:"event"`
ErrorCode string `json:"error_code,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
Attributes map[string]interface{} `json:"attributes"`
}
type Output struct {
Sentence struct {
BeginTime int64 `json:"begin_time"`
EndTime *int64 `json:"end_time"`
Text string `json:"text"`
Words []struct {
BeginTime int64 `json:"begin_time"`
EndTime *int64 `json:"end_time"`
Text string `json:"text"`
Punctuation string `json:"punctuation"`
} `json:"words"`
} `json:"sentence"`
}
type Payload struct {
TaskGroup string `json:"task_group"`
Task string `json:"task"`
Function string `json:"function"`
Model string `json:"model"`
Parameters Params `json:"parameters"`
Input Input `json:"input"`
Output Output `json:"output,omitempty"`
Usage *struct {
Duration int `json:"duration"`
} `json:"usage,omitempty"`
}
type Params struct {
Format string `json:"format"`
SampleRate int `json:"sample_rate"`
DisfluencyRemovalEnabled bool `json:"disfluency_removal_enabled"`
LanguageHints []string `json:"language_hints"`
}
type Input struct {
}
type Event struct {
Header Header `json:"header"`
Payload Payload `json:"payload"`
}
// 连接WebSocket服务
func connectWebSocket(apiKey string) (*websocket.Conn, error) {
header := make(http.Header)
header.Add("Authorization", fmt.Sprintf("Bearer %s", apiKey))
conn, _, err := dialer.Dial(wsURL, header)
return conn, err
}
// 启动一个goroutine异步接收WebSocket消息
func startResultReceiver(conn *websocket.Conn, taskStarted chan<- bool, taskDone chan<- bool) {
go func() {
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("解析服务器消息失败:", err)
return
}
var event Event
err = json.Unmarshal(message, &event)
if err != nil {
log.Println("解析事件失败:", err)
continue
}
if handleEvent(conn, event, taskStarted, taskDone) {
return
}
}
}()
}
// 发送run-task指令
func sendRunTaskCmd(conn *websocket.Conn) (string, error) {
runTaskCmd, taskID, err := generateRunTaskCmd()
if err != nil {
return "", err
}
err = conn.WriteMessage(websocket.TextMessage, []byte(runTaskCmd))
return taskID, err
}
// 生成run-task指令
func generateRunTaskCmd() (string, string, error) {
taskID := uuid.New().String()
runTaskCmd := Event{
Header: Header{
Action: "run-task",
TaskID: taskID,
Streaming: "duplex",
},
Payload: Payload{
TaskGroup: "audio",
Task: "asr",
Function: "recognition",
Model: "paraformer-realtime-v2",
Parameters: Params{
Format: "wav",
SampleRate: 16000,
},
Input: Input{},
},
}
runTaskCmdJSON, err := json.Marshal(runTaskCmd)
return string(runTaskCmdJSON), taskID, err
}
// 等待task-started事件
func waitForTaskStarted(taskStarted chan bool) {
select {
case <-taskStarted:
fmt.Println("任务开启成功")
case <-time.After(10 * time.Second):
log.Fatal("等待task-started超时,任务开启失败")
}
}
// 发送音频数据
func sendAudioData(conn *websocket.Conn) error {
file, err := os.Open(audioFile)
if err != nil {
return err
}
defer file.Close()
buf := make([]byte, 1024) // 假设100ms的音频数据大约为1024字节
for {
n, err := file.Read(buf)
if n == 0 {
break
}
if err != nil && err != io.EOF {
return err
}
err = conn.WriteMessage(websocket.BinaryMessage, buf[:n])
if err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
// 发送finish-task指令
func sendFinishTaskCmd(conn *websocket.Conn, taskID string) error {
finishTaskCmd, err := generateFinishTaskCmd(taskID)
if err != nil {
return err
}
err = conn.WriteMessage(websocket.TextMessage, []byte(finishTaskCmd))
return err
}
// 生成finish-task指令
func generateFinishTaskCmd(taskID string) (string, error) {
finishTaskCmd := Event{
Header: Header{
Action: "finish-task",
TaskID: taskID,
Streaming: "duplex",
},
Payload: Payload{
Input: Input{},
},
}
finishTaskCmdJSON, err := json.Marshal(finishTaskCmd)
return string(finishTaskCmdJSON), err
}
// 处理事件
func handleEvent(conn *websocket.Conn, event Event, taskStarted chan<- bool, taskDone chan<- bool) bool {
switch event.Header.Event {
case "task-started":
fmt.Println("收到task-started事件")
taskStarted <- true
case "result-generated":
if event.Payload.Output.Sentence.Text != "" {
fmt.Println("识别结果:", event.Payload.Output.Sentence.Text)
}
if event.Payload.Usage != nil {
fmt.Println("任务计费时长(秒):", event.Payload.Usage.Duration)
}
case "task-finished":
fmt.Println("任务完成")
taskDone <- true
return true
case "task-failed":
handleTaskFailed(event, conn)
taskDone <- true
return true
default:
log.Printf("预料之外的事件:%v", event)
}
return false
}
// 处理任务失败事件
func handleTaskFailed(event Event, conn *websocket.Conn) {
if event.Header.ErrorMessage != "" {
log.Fatalf("任务失败:%s", event.Header.ErrorMessage)
} else {
log.Fatal("未知原因导致任务失败")
}
}
// 关闭连接
func closeConnection(conn *websocket.Conn) {
if conn != nil {
conn.Close()
}
}
示例代码如下:DashScope 平台 API 工具包与框架
复制
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
class Program {
private static ClientWebSocket _webSocket = new ClientWebSocket();
private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private static bool _taskStartedReceived = false;
private static bool _taskFinishedReceived = false;
// 若没有将API Key配置到环境变量,可将下行替换为:private const string ApiKey="your_api_key"。不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。
private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");
// WebSocket服务器地址
private const string WebSocketUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/";
// 替换为您的音频文件路径
private const string AudioFilePath = "asr_example.wav";
static async Task Main(string[] args) {
// 建立WebSocket连接,配置headers进行鉴权
_webSocket.Options.SetRequestHeader("Authorization", $"Bearer {ApiKey}");
await _webSocket.ConnectAsync(new Uri(WebSocketUrl), _cancellationTokenSource.Token);
// 启动线程异步接收WebSocket消息
var receiveTask = ReceiveMessagesAsync();
// 发送run-task指令
string _taskId = Guid.NewGuid().ToString("N"); // 生成32位随机ID
var runTaskJson = GenerateRunTaskJson(_taskId);
await SendAsync(runTaskJson);
// 等待task-started事件
while (!_taskStartedReceived) {
await Task.Delay(100, _cancellationTokenSource.Token);
}
// 读取本地文件,向服务器发送待识别音频流
await SendAudioStreamAsync(AudioFilePath);
// 发送finish-task指令结束任务
var finishTaskJson = GenerateFinishTaskJson(_taskId);
await SendAsync(finishTaskJson);
// 等待task-finished事件
while (!_taskFinishedReceived && !_cancellationTokenSource.IsCancellationRequested) {
try {
await Task.Delay(100, _cancellationTokenSource.Token);
} catch (OperationCanceledException) {
// 任务已被取消,退出循环
break;
}
}
// 关闭连接
if (!_cancellationTokenSource.IsCancellationRequested) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token);
}
_cancellationTokenSource.Cancel();
try {
await receiveTask;
} catch (OperationCanceledException) {
// 忽略操作取消异常
}
}
private static async Task ReceiveMessagesAsync() {
try {
while (_webSocket.State == WebSocketState.Open && !_cancellationTokenSource.IsCancellationRequested) {
var message = await ReceiveMessageAsync(_cancellationTokenSource.Token);
if (message != null) {
var eventValue = message["header"]?["event"]?.GetValue<string>();
switch (eventValue) {
case "task-started":
Console.WriteLine("任务开启成功");
_taskStartedReceived = true;
break;
case "result-generated":
Console.WriteLine($"识别结果:{message["payload"]?["output"]?["sentence"]?["text"]?.GetValue<string>()}");
if (message["payload"]?["usage"] != null && message["payload"]?["usage"]?["duration"] != null) {
Console.WriteLine($"任务计费时长(秒):{message["payload"]?["usage"]?["duration"]?.GetValue<int>()}");
}
break;
case "task-finished":
Console.WriteLine("任务完成");
_taskFinishedReceived = true;
_cancellationTokenSource.Cancel();
break;
case "task-failed":
Console.WriteLine($"任务失败:{message["header"]?["error_message"]?.GetValue<string>()}");
_cancellationTokenSource.Cancel();
break;
}
}
}
} catch (OperationCanceledException) {
// 忽略操作取消异常
}
}
private static async Task<JsonNode?> ReceiveMessageAsync(CancellationToken cancellationToken) {
var buffer = new byte[1024 * 4];
var segment = new ArraySegment<byte>(buffer);
var result = await _webSocket.ReceiveAsync(segment, cancellationToken);
if (result.MessageType == WebSocketMessageType.Close) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
return null;
}
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
return JsonNode.Parse(message);
}
private static async Task SendAsync(string message) {
var buffer = Encoding.UTF8.GetBytes(message);
var segment = new ArraySegment<byte>(buffer);
await _webSocket.SendAsync(segment, WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
}
private static async Task SendAudioStreamAsync(string filePath) {
using (var audioStream = File.OpenRead(filePath)) {
var buffer = new byte[1024]; // 每次发送100ms的音频数据
int bytesRead;
while ((bytesRead = await audioStream.ReadAsync(buffer, 0, buffer.Length)) > 0) {
var segment = new ArraySegment<byte>(buffer, 0, bytesRead);
await _webSocket.SendAsync(segment, WebSocketMessageType.Binary, true, _cancellationTokenSource.Token);
await Task.Delay(100); // 间隔100ms
}
}
}
private6px] px-3 py-1 text-[8px] font-medium tracking-wide bg-supporting-blue text-supporting-blue-tint" aria-hidden="true">POST
