asr 语音识别
自动语音识别(ASR)集成,支持 DashScope 和火山引擎
概述
@mono/asr 提供统一的语音识别接口,支持多个服务提供商。无论是离线音频文件识别还是实时流式识别,都能轻松实现。
核心功能
OpenAI 兼容 API
支持 DashScope(阿里云通义千问)等 OpenAI 兼容的 ASR 服务。
基本使用
import { createOpenAICompatibleAsrClient } from "@mono/asr";
const client = createOpenAICompatibleAsrClient({
apiKey: process.env.DASHSCOPE_API_KEY,
model: "qwen3-asr-flash",
language: "zh",
enableItn: true,
});
const result = await client.transcribeAudioUrl(
"https://example.com/audio.mp3"
);
console.log(result.transcript); // "你好世界"自定义配置
const client = createOpenAICompatibleAsrClient({
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
model: "qwen3-asr-flash",
language: "zh",
enableItn: true,
timeoutMs: 30000,
});火山引擎流式识别
适用于实时语音识别场景。
import { transcribeAudioPcmByVolcengine } from "@mono/asr";
// 音频要求:PCM 格式,16kHz, 16bit, 单声道
const audioPcm = Buffer.from(/* PCM 数据 */);
const transcript = await transcribeAudioPcmByVolcengine(audioPcm);
console.log(transcript);实战示例
示例 1:音频文件转文字
构建一个音频转文字服务。
import { createOpenAICompatibleAsrClient } from "@mono/asr";
const client = createOpenAICompatibleAsrClient();
export async function transcribeAudioFile(audioUrl: string) {
try {
const result = await client.transcribeAudioUrl(audioUrl);
return {
success: true,
text: result.transcript,
raw: result.raw,
};
} catch (error) {
console.error("识别失败:", error);
return {
success: false,
error: error instanceof Error ? error.message : "未知错误",
};
}
}
// 使用
const result = await transcribeAudioFile(
"https://example.com/meeting.mp3"
);
if (result.success) {
console.log("会议记录:", result.text);
}示例 2:多语言识别
支持中英文等多种语言。
import { createOpenAICompatibleAsrClient } from "@mono/asr";
const client = createOpenAICompatibleAsrClient();
export async function transcribeMultiLanguage(
audioUrl: string,
language: "zh" | "en" | "ja"
) {
const result = await client.transcribeAudioUrl(audioUrl, {
language,
enableItn: true,
});
return {
language,
text: result.transcript,
timestamp: new Date().toISOString(),
};
}
// 识别中文音频
const zhResult = await transcribeMultiLanguage(audioUrl, "zh");
// 识别英文音频
const enResult = await transcribeMultiLanguage(audioUrl, "en");示例 3:批量音频处理
并发处理多个音频文件。
import { createOpenAICompatibleAsrClient } from "@mono/asr";
const client = createOpenAICompatibleAsrClient();
export async function batchTranscribe(audioUrls: string[]) {
console.log(`开始批量识别 ${audioUrls.length} 个音频文件`);
const results = await Promise.allSettled(
audioUrls.map(async (url, index) => {
console.log(`[${index + 1}/${audioUrls.length}] 识别: ${url}`);
const result = await client.transcribeAudioUrl(url);
return {
url,
transcript: result.transcript,
};
})
);
return results.map((result, index) => ({
url: audioUrls[index],
success: result.status === "fulfilled",
transcript: result.status === "fulfilled" ? result.value.transcript : null,
error: result.status === "rejected" ? result.reason.message : null,
}));
}
// 使用
const audioUrls = [
"https://example.com/audio1.mp3",
"https://example.com/audio2.mp3",
"https://example.com/audio3.mp3",
];
const results = await batchTranscribe(audioUrls);
results.forEach((result, index) => {
if (result.success) {
console.log(`✓ [${index + 1}] ${result.transcript}`);
} else {
console.error(`✗ [${index + 1}] ${result.error}`);
}
});示例 4:实时语音识别
使用火山引擎进行实时识别。
import { transcribeAudioPcmByVolcengine } from "@mono/asr";
import { readFileSync } from "fs";
export async function realtimeTranscribe(pcmFilePath: string) {
try {
// 读取 PCM 音频文件
const audioPcm = readFileSync(pcmFilePath);
console.log(`音频大小: ${audioPcm.length} 字节`);
console.log("开始实时识别...");
const startTime = Date.now();
const transcript = await transcribeAudioPcmByVolcengine(audioPcm);
const duration = Date.now() - startTime;
console.log(`识别完成 (${duration}ms)`);
return {
transcript,
duration,
audioSize: audioPcm.length,
};
} catch (error) {
console.error("实时识别失败:", error);
throw error;
}
}
// 使用
const result = await realtimeTranscribe("./audio.pcm");
console.log("识别结果:", result.transcript);示例 5:带重试的识别
增加错误重试机制。
import { createOpenAICompatibleAsrClient } from "@mono/asr";
const client = createOpenAICompatibleAsrClient();
export async function transcribeWithRetry(
audioUrl: string,
maxRetries = 3,
retryDelay = 1000
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`尝试 ${attempt}/${maxRetries}: ${audioUrl}`);
const result = await client.transcribeAudioUrl(audioUrl);
console.log(`✓ 识别成功 (尝试 ${attempt})`);
return result.transcript;
} catch (error) {
console.error(`✗ 尝试 ${attempt} 失败:`, error);
if (attempt === maxRetries) {
throw new Error(`识别失败,已重试 ${maxRetries} 次`);
}
// 指数退避
const delay = retryDelay * attempt;
console.log(`等待 ${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error("识别失败");
}
// 使用
try {
const transcript = await transcribeWithRetry(audioUrl);
console.log("最终结果:", transcript);
} catch (error) {
console.error("所有重试均失败:", error);
}示例 6:音频转字幕
将音频转换为字幕格式。
import { createOpenAICompatibleAsrClient } from "@mono/asr";
const client = createOpenAICompatibleAsrClient();
interface SubtitleSegment {
index: number;
startTime: string;
endTime: string;
text: string;
}
export async function generateSubtitles(
audioUrl: string,
segmentDuration = 5
): Promise<SubtitleSegment[]> {
const result = await client.transcribeAudioUrl(audioUrl);
const text = result.transcript;
// 简单分段(实际应用中可能需要更复杂的逻辑)
const words = text.split(/\s+/);
const wordsPerSegment = Math.ceil(words.length / segmentDuration);
const segments: SubtitleSegment[] = [];
for (let i = 0; i < words.length; i += wordsPerSegment) {
const segmentWords = words.slice(i, i + wordsPerSegment);
const index = Math.floor(i / wordsPerSegment) + 1;
const startSeconds = (index - 1) * segmentDuration;
const endSeconds = index * segmentDuration;
segments.push({
index,
startTime: formatTime(startSeconds),
endTime: formatTime(endSeconds),
text: segmentWords.join(" "),
});
}
return segments;
}
function formatTime(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${pad(hours)}:${pad(minutes)}:${pad(secs)}`;
}
function pad(num: number): string {
return num.toString().padStart(2, "0");
}
// 使用
const subtitles = await generateSubtitles(audioUrl);
// 输出 SRT 格式
subtitles.forEach(segment => {
console.log(segment.index);
console.log(`${segment.startTime} --> ${segment.endTime}`);
console.log(segment.text);
console.log();
});配置指南
环境变量配置
创建 .env.local 文件:
# DashScope(阿里云通义千问)
DASHSCOPE_API_KEY=your-dashscope-api-key
OPENAI_ASR_MODEL=qwen3-asr-flash
OPENAI_ASR_LANGUAGE=zh
OPENAI_ASR_ENABLE_ITN=true
# 火山引擎
VOLCENGINE_APP_ID=your-app-id
VOLCENGINE_ACCESS_TOKEN=your-access-token
VOLCENGINE_ENABLE_ITN=true
VOLCENGINE_ENABLE_PUNC=true提供商选择
DashScope(推荐)
优势:
- 支持多种音频格式
- 高准确率
- 简单易用
- OpenAI 兼容接口
适用场景:
- 离线音频文件识别
- 批量处理
- 需要高准确率
const client = createOpenAICompatibleAsrClient({
apiKey: process.env.DASHSCOPE_API_KEY,
model: "qwen3-asr-flash", // 快速模型
// model: "qwen3-asr-pro", // 高精度模型
});火山引擎
优势:
- 实时流式识别
- 低延迟
- 支持长音频
适用场景:
- 实时语音识别
- 语音对话系统
- 需要低延迟
// 通过环境变量配置
const transcript = await transcribeAudioPcmByVolcengine(audioPcm);错误处理
常见错误
import { createOpenAICompatibleAsrClient } from "@mono/asr";
const client = createOpenAICompatibleAsrClient();
try {
const result = await client.transcribeAudioUrl(audioUrl);
console.log(result.transcript);
} catch (error) {
if (error instanceof Error) {
// API 密钥错误
if (error.message.includes("缺少")) {
console.error("请配置 DASHSCOPE_API_KEY 环境变量");
}
// 请求失败
else if (error.message.includes("请求失败")) {
console.error("API 请求失败,请检查网络连接");
}
// 识别结果为空
else if (error.message.includes("结果为空")) {
console.error("音频无法识别,请检查音频质量");
}
// 其他错误
else {
console.error("未知错误:", error.message);
}
}
}火山引擎错误处理
import { transcribeAudioPcmByVolcengine } from "@mono/asr";
try {
const transcript = await transcribeAudioPcmByVolcengine(audioPcm);
console.log(transcript);
} catch (error) {
if (error instanceof Error) {
// 凭证未配置
if (error.message.includes("未配置")) {
console.error("请配置 VOLCENGINE_APP_ID 和 VOLCENGINE_ACCESS_TOKEN");
}
// 音频数据无效
else if (error.message.includes("音频数据为空")) {
console.error("音频数据无效,请检查 PCM 格式");
}
// 连接失败
else if (error.message.includes("连接失败")) {
console.error("WebSocket 连接失败,请检查网络");
}
// 其他错误
else {
console.error("识别错误:", error.message);
}
}
}性能优化
1. 选择合适的模型
// 快速模型(低延迟,适合实时场景)
const fastClient = createOpenAICompatibleAsrClient({
model: "qwen3-asr-flash",
});
// 高精度模型(高准确率,适合离线处理)
const accurateClient = createOpenAICompatibleAsrClient({
model: "qwen3-asr-pro",
});2. 并发控制
import pLimit from "p-limit";
const limit = pLimit(5); // 最多 5 个并发请求
const results = await Promise.all(
audioUrls.map(url =>
limit(() => client.transcribeAudioUrl(url))
)
);3. 超时控制
const client = createOpenAICompatibleAsrClient({
timeoutMs: 30000, // 30 秒超时
});4. 缓存结果
import { createMemoryCache } from "@mono/cache";
const cache = createMemoryCache();
async function transcribeWithCache(audioUrl: string) {
const cached = cache.get<string>(audioUrl);
if (cached) {
console.log("使用缓存结果");
return cached;
}
const result = await client.transcribeAudioUrl(audioUrl);
cache.set(audioUrl, result.transcript, 3600); // 缓存 1 小时
return result.transcript;
}最佳实践
1. 音频预处理
确保音频格式正确:
# 使用 FFmpeg 转换音频
ffmpeg -i input.mp3 -ar 16000 -ac 1 -f s16le output.pcm2. 结果验证
function validateTranscript(transcript: string): boolean {
// 检查是否为空
if (!transcript || transcript.trim().length === 0) {
return false;
}
// 检查最小长度
if (transcript.length < 2) {
return false;
}
return true;
}
const result = await client.transcribeAudioUrl(audioUrl);
if (!validateTranscript(result.transcript)) {
throw new Error("识别结果无效");
}3. 日志记录
async function transcribeWithLogging(audioUrl: string) {
const startTime = Date.now();
console.log(`[ASR] 开始识别: ${audioUrl}`);
try {
const result = await client.transcribeAudioUrl(audioUrl);
const duration = Date.now() - startTime;
console.log(`[ASR] 识别成功 (${duration}ms)`);
console.log(`[ASR] 文本长度: ${result.transcript.length}`);
return result;
} catch (error) {
const duration = Date.now() - startTime;
console.error(`[ASR] 识别失败 (${duration}ms):`, error);
throw error;
}
}4. 错误监控
import { createOpenAICompatibleAsrClient } from "@mono/asr";
const client = createOpenAICompatibleAsrClient();
let successCount = 0;
let errorCount = 0;
export async function transcribeWithMetrics(audioUrl: string) {
try {
const result = await client.transcribeAudioUrl(audioUrl);
successCount++;
return result;
} catch (error) {
errorCount++;
console.error(`错误率: ${(errorCount / (successCount + errorCount) * 100).toFixed(2)}%`);
throw error;
}
}故障排除
问题 1:API 密钥错误
症状: 缺少 OPENAI_API_KEY / OPENAI_ASR_API_KEY(或 DASHSCOPE_API_KEY)配置
解决方案:
- 检查
.env.local文件是否存在 - 确认环境变量名称正确
- 重启开发服务器
# 检查环境变量
echo $DASHSCOPE_API_KEY问题 2:识别结果为空
症状: 语音识别结果为空
解决方案:
- 检查音频文件是否有效
- 确认音频格式是否支持
- 尝试使用不同的模型
- 检查音频质量
问题 3:火山引擎连接失败
症状: 火山引擎连接失败
解决方案:
- 检查
VOLCENGINE_APP_ID和VOLCENGINE_ACCESS_TOKEN - 确认网络连接正常
- 检查防火墙设置
- 验证凭证是否过期
问题 4:音频格式不支持
症状: 识别失败或结果不准确
解决方案:
使用 FFmpeg 转换音频格式:
# 转换为 PCM(火山引擎)
ffmpeg -i input.mp3 -ar 16000 -ac 1 -f s16le output.pcm
# 转换为 MP3(DashScope)
ffmpeg -i input.wav -ar 16000 -ac 1 output.mp3问题 5:超时错误
症状: 请求超时
解决方案:
增加超时时间:
const client = createOpenAICompatibleAsrClient({
timeoutMs: 120000, // 增加到 120 秒
});相关资源
下一步
- 探索 @mono/ai - AI 集成包
- 了解 @mono/storage - 文件存储包
- 查看 @mono/cache - 缓存管理包