当前位置: 首页 > ai >正文

Paraformer实时语音识别中的碎碎念

前言

近期在做一些语音识别相关的工作,使用Paraformer实时语音识别作为后端服务

在开发过程中,碰到的一些诡异的问题,在此做个记录

背景

问题:短音频(2-3秒)识别,在尾部音频没有被正确识别。

原始音频内容:”上号“

模型输出:”上“

”号“ 字没有被正确识别

整个服务由websocket进行通信,输入是音频数据流,输出是流式识别结果

”上号“ 出现在音频 1.3s 左右,整个音频2.2s左右

分析过程

1、通过客户端补静音空包:

模型此时识别出 ”上号“,正确

2、为了降低客户端使用难度,我在服务端改成了每次处理的包大小都固定,并且在最后一段不足设置的固定包大小时,补空包

模型此时只识别了 ”上“,漏掉了 ”号“

3、在2 的基础之上,我咋子服务端处理最后一段音频时,拼接了一段0.12s左右的空包

模型此时能够将 ”上号“ 都识别出来

前面有提到,”上号“出现在音频开始到1.3s左右,后续还有0.9s的静音

直接补空包,好像对结果不会有影响才对,带着这个问题,开始分析。

先看代码:

    async def ws_serve(cls, websocket: WebSocket):# 确保卸载检查任务已启动await cls.start_unload_check_task()websocket.ping_interval = 10.0  # 30秒发送一次pingwebsocket.ping_timeout = 300.0  # 300秒未收到pong则断开frames = []frames_asr_online = []cls.websocket_users.add(websocket)# 添加固定包大小的缓冲区websocket.audio_buffer = b""websocket.packet_count = 0websocket.status_dict_asr = {}websocket.status_dict_asr_online = {"cache": {}, "is_final": False, "chunk_size": [5,10,5]}websocket.status_dict_vad = {"cache": {}, "is_final": False}websocket.status_dict_punc = {"cache": {}}websocket.chunk_interval = 15websocket.status_dict_asr_online["encoder_chunk_look_back"] = 4websocket.status_dict_asr_online["decoder_chunk_look_back"] = 0# 握手阶段try:await websocket.accept()# 获取URL参数language = websocket.query_params.get("lang")use_speaker_verification = websocket.query_params.get("use_speaker", False)if use_speaker_verification == "true":use_speaker_verification = Trueelse:use_speaker_verification = Falseif language is not None:if language not in ["cantonese", "english", "mandarin", "uyghur", "dialect", "other"]:response = {"action": "error","code": "10110","data": "","desc": "不支持的语言类型","sid": "rta0000000a@ch312c0e3f63609f0900"}await websocket.send_json(response)cls.websocket_users.remove(websocket)await cls.ws_reset(websocket)websocket.language = languagelogger.info(f"连接参数: language={language}")response = {"action": "started","code": "0","data": "","desc": "success","sid": "rta0000000a@ch312c0e3f63609f0900"}await websocket.send_json(response)# 初始化模型if language in ["cantonese", "english", "mandarin"]:if cls.cantonese_model is None and not use_speaker_verification:logger.info("初始化粤语模型")cls.cantonese_model = init_cantonese_model(config.CANTONESE_ASR_MODEL_PATH, config.DEVICE)# 更新模型最后访问时间cls.model_last_access_time["cantonese_model"] = time.time()if cls.mandrian_spk_model is None and use_speaker_verification:logger.info("初始化普通话模型")cls.mandrian_spk_model = init_mandrian_spk_model(config.MANDRIAN_ASR_MODEL_PATH, config.MANDRIAN_VAD_MODEL_PATH, config.MANDRIAN_PUNC_MODEL_PATH, config.MANDRIAN_SPK_MODEL_PATH, config.DEVICE)# 更新模型最后访问时间cls.model_last_access_time["mandrian_spk_model"] = time.time()else:logger.error(f"不支持的语言类型: {language}")response = {"action": "error","code": "10110","data": "","desc": f"不支持的语言类型:{language}, 所有支持的模型: cantonese, english, mandarin, uyghur, dialect, other","sid": "rta0000000a@ch312c0e3f63609f0900"}await websocket.send_json(response)cls.websocket_users.remove(websocket)await cls.ws_reset(websocket)except Exception as e:error_response = {"action": "error","code": "10110","data": "","desc": "invalid authorization|illegal signature","sid": "rta0000000b@ch312c0e3f65f09f0900"}traceback.print_exc()await websocket.send_json(error_response)await cls.ws_reset(websocket)try:while True:message = await websocket.receive_bytes()# message_size = len(message)# print(f"收到 {message_size} 字节的数据")decoded_message = message.decode('utf-8', errors='ignore')websocket.status_dict_asr_online["is_final"] = Falseif decoded_message == '{"end": true}':logger.info("结束标志")websocket.status_dict_asr_online["is_final"] = True# 处理最后的缓冲数据,如果不足固定包大小则补足if len(websocket.audio_buffer) > 0:if len(websocket.audio_buffer) < cls.FIXED_PACKET_SIZE:# 用零字节补足到固定包大小padding_size = cls.FIXED_PACKET_SIZE - len(websocket.audio_buffer)websocket.audio_buffer += b'\x00' * padding_sizelogger.info(f"最后一包数据不足{cls.FIXED_PACKET_SIZE}字节,已补足{padding_size}字节")frames.append(websocket.audio_buffer)frames_asr_online.append(websocket.audio_buffer)websocket.audio_buffer = b""websocket.packet_count += 1# 添加额外的空音频包来确保最后的音频被正确识别# 添加空音频包来帮助模型完成final解码,触发完整识别结果输出# 根据encoder_chunk_look_back=4,建议至少4个包来确保充分的上下文extra_silence_packets = 4  # 约0.24秒静音,确保模型有足够时间完成解码for i in range(extra_silence_packets):silence_packet = b'\x00' * cls.FIXED_PACKET_SIZEframes.append(silence_packet)frames_asr_online.append(silence_packet)websocket.packet_count += 1logger.info(f"添加了{extra_silence_packets}个空音频包以改善结尾识别")else:# 将数据添加到缓冲区websocket.audio_buffer += message# 检查是否达到固定包大小while len(websocket.audio_buffer) >= cls.FIXED_PACKET_SIZE:# 提取固定大小的数据包packet = websocket.audio_buffer[:cls.FIXED_PACKET_SIZE]websocket.audio_buffer = websocket.audio_buffer[cls.FIXED_PACKET_SIZE:]frames.append(packet)frames_asr_online.append(packet)websocket.packet_count += 1# print(f"处理第{websocket.packet_count}个固定包,大小: {len(packet)} 字节,缓冲区剩余: {len(websocket.audio_buffer)} 字节")websocket.status_dict_vad["chunk_size"] = int(websocket.status_dict_asr_online["chunk_size"][1]* 60/ websocket.chunk_interval)# 检查是否有新的固定包需要处理,或者是否需要处理结束逻辑if len(frames_asr_online) > 0 or websocket.status_dict_asr_online["is_final"]:# print("------"*10)# print(f"当前有{len(frames_asr_online)}个固定包待处理")# print(f"chunk_interval: {websocket.chunk_interval}")# print(f"处理条件检查: {len(frames_asr_online) % websocket.chunk_interval}")# print(f"is_final: {websocket.status_dict_asr_online['is_final']}")# print("------"*10)if websocket.language in ["cantonese", "english", "mandarin"] and not use_speaker_verification:# 每收集到chunk_interval个固定包就处理一次,或者收到结束标志if (len(frames_asr_online) % websocket.chunk_interval == 0or websocket.status_dict_asr_online["is_final"]):if websocket.status_dict_asr_online["is_final"]:logger.info("is_final")if len(frames_asr_online) > 0:audio_in = b"".join(frames_asr_online)logger.info(f"处理音频流,包含{len(frames_asr_online)}个固定包,总大小: {len(audio_in)} 字节")# print(f"处理音频流,包含{len(frames_asr_online)}个固定包,总大小: {len(audio_in)} 字节")try:await cls.async_online_asr_cantonese(websocket, audio_in)except Exception as e:# logger.error(#     f"error in asr streaming, {websocket.status_dict_asr_online}"# )traceback.print_exc()logger.error(f"音频流识别错误,ERROR: {e}")frames_asr_online = []# 确保在处理结束时发送结束消息if websocket.status_dict_asr_online["is_final"]:logger.info("发送处理结束消息给客户端")message = {"end": True,}await websocket.send_json(message)except WebSocketDisconnect:logger.error("WebSocket disconnected...")cls.websocket_users.remove(websocket)await cls.ws_reset(websocket)except Exception as e:logger.error("Exception:", e)traceback.print_exc()

代码只包含核心逻辑部分,其中:

encoder_chunk_look_back = 4:表示编码器需要回看四个chunk的信息

decoder_chunk_look_back = 0:表示解码器不回看
chunk_interval = 10 : 表示每10个包识别一次

音频参数:
采样率:16,000 Hz
位深:16位(2字节/采样点)
声道:单声道
固定包大小:1,920字节
每秒音频数据量 = 16,000 × 2字节 × 1声道 = 32,000字节/秒
每个包时长 = 1,920 ÷ 32,000 = 0.06秒
10个包总时长 = 0.06 × 10 = 0.6秒

所以 19,200字节大约是0.6秒的音频。
原始音频大约2.2s,大概被分成了四个chunk,其中 ”上号“ 在前1.3s左右,后续0.9秒无内容。
通过调整encoder_chunk_look_back为3,即编码器回看3个chunk,即1.8s,模型能识别出 ”哦哦“(模型效果问题)
通过调整chunk_interval为100,即 10个包总时长 为 6 秒,模型能够识别出 ”上号“

由此得出结论,不添加0.12s空包,模型只能识别出 ”上“ 而无法识别出 ”号“ 是因为 这个音频 ”号“ 字刚好被截断,导致识别效果不好,被当作是空字符。

http://www.xdnf.cn/news/18173.html

相关文章:

  • 将SSL配置迁移到Nacos的步骤
  • HarmonyOS 中的 setInterval的基本使用
  • 分布式机器学习之流水线并行GPipe:借助数据并行来实现模型并行计算
  • 矿物分类系统开发笔记(二):模型训练[删除空缺行]
  • ZooKeeper 一致性模型解析:线性一致性与顺序一致性的平衡
  • VScode ROS文件相关配置
  • 【habitat学习一】Habitat-Lab 配置键文档详解(CONFIG_KEYS.md)
  • 嵌入式开发学习———Linux环境下网络编程学习(三)
  • RAG 面试题(实时更新补充)
  • 学习笔记分享——基于STM32的平衡车项目
  • Ubuntu 和麒麟系统创建新用户 webapp、配置密码、赋予 sudo 权限并禁用 root 的 SSH 登录的详细
  • 【PyTorch】单对象分割项目
  • 八大排序简介
  • Java 11中的Collections类详解
  • 数据结构:二叉树oj练习
  • Kubernetes Pod 控制器
  • 【PyTorch项目实战】OpenNMT本地机器翻译框架 —— 支持本地部署和自定义训练
  • 学习strandsagents的http_request tool
  • 接口性能测试工具 - JMeter
  • IMX6ULL-KERNEL源代码
  • KMM跨平台叛逃实录:SwiftUI与Compose Multiplatform共享ViewModel的混合开发框架(代码复用率85%)
  • RPC高频问题与底层原理剖析
  • 【树莓派】【嵌入式】远程树莓派,解决ping不通问题
  • 应用缓存不止是Redis!——亿级流量系统架构设计系列
  • Windows/Centos 7下搭建Apache服务器
  • Nacos-7--扩展一下:0-RTT和1-RTT怎么理解?
  • Spring Boot 全局异常处理
  • 重置iPhone会删除所有内容吗? 详细回答
  • 基于WebSocket和SpringBoot聊天项目ChatterBox测试报告
  • 如何用Prometheus和FastAPI打造任务监控的“火眼金睛”?