FastAPI+FunASR实现局域网WebSocket实时语音识别 | 解决麦克风权限、10054连接重置、PyAudio硬件报错全记录


1. 项目背景与整体架构

项目需求

搭建局域网实时流式语音字幕服务。最初方案后端通过PyAudio调用本机麦克风录音,但是部署到无声卡、无音频硬件的台式机时频繁报错。
最终改造方案:把麦克风采集迁移到浏览器前端,后端只通过WebSocket接收二进制音频流,不再依赖任何声卡硬件。

整体技术链路

  1. 前端网页:浏览器调用麦克风,生成 16000Hz 单声道16位PCM音频;
  2. WebSocket长连接:二进制音频上行,识别文字实时下发;
  3. 后端服务:FunASR paraformer流式模型做语音识别;
  4. 增强逻辑:静音停顿0.6秒自动清空字幕,并重置模型缓存,避免上下文串话。

2. 环境依赖安装

pip install funasr

FunASR官方文档

注意: 如果只是跑语音识别,建议降到 Python3.9 ~ Python3.12,funasr 兼容性最好
使用Python3.13 就会报下面这个错误:

使用  pip install funasr 安装时报错:
File "C:\Users\Administrator\AppData\Local\Temp\pip-build-env-fr3svpmz\overlay\Lib\site-packages\pdm\backend\hooks\setuptools.py", line 92, in _build_lib
          raise BuildError(f"Error occurs when running {build_args}:\n{e}")
      pdm.backend.exceptions.BuildError: Error occurs when running ['C:\\Python313\\python.exe', 'C:\\Users\\Administrator\\AppData\\Local\\Temp\\pip-install-ozs44hc3\\editdistance_ecb96c8585a54a4485377adc1e68862b\\setup.py', 'build', '-b', 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\pdm-build-7bg3n_hl']:
      Command '['C:\\Python313\\python.exe', 'C:\\Users\\Administrator\\AppData\\Local\\Temp\\pip-install-ozs44hc3\\editdistance_ecb96c8585a54a4485377adc1e68862b\\setup.py', 'build', '-b', 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\pdm-build-7bg3n_hl']' returned non-zero exit status 1.
      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for editdistance
Failed to build editdistance
error: failed-wheel-build-for-install

× Failed to build installable wheels for some pyproject.toml based projects
╰─> editdistance

3. 完整前后端源码

后端 asr_api.py

# -*- coding: utf-8 -*-
"""
@Created on : 2026/6/25 16:12
@creator : er_nao
@File :asr_api.py
@Description :
"""
from fastapi import FastAPI, HTTPException, WebSocket
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from funasr import AutoModel
import pyaudio
import asyncio
import threading
import os
import sys
import time

# 屏蔽进度条
os.environ["TQDM_DISABLE"] = "1"
devnull = open(os.devnull, 'w')
sys.stdout = devnull
sys.stderr = devnull

app = FastAPI()

# 挂载当前目录为静态资源目录
app.mount("/static", StaticFiles(directory="."), name="static")

# 访问根路径自动打开 index.html
@app.get("/")
async def index():
    return FileResponse("index.html")

# ---------------------- 加载模型 ----------------------
# 音频url转文字模型
model = AutoModel(model='paraformer-zh', vad_model='fsmn-vad', punc_model='ct-punc')

# 加载实时流式模型
asr_model = AutoModel(
    model="paraformer-zh-streaming",
    disable_update=True
)
CHUNK = 960
RATE = 16000
chunk_size = [0, 10, 5]

# 消息队列 + 客户端连接池
# text_queue = asyncio.Queue()
# client_set = set()
# main_loop: asyncio.AbstractEventLoop = None

# 定义请求体格式:只接收音频url
class AudioUrlRequest(BaseModel):
    audio_url: str


"""
音频链接转文字
"""

@app.post("/asr")

async def speech_to_text(req: AudioUrlRequest):
    try:
        # 直接把网络地址传给model.generate
        res = model.generate(input=req.audio_url)
        return {
            "code": 200,
            "text": res[0]["text"]
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


"""
实时语音转文字,WebSocket接口
"""

@app.websocket("/ws")
async def handle_audio_stream(websocket: WebSocket):
    await websocket.accept()
    print("客户端已连接")
    cache = {}
    sentence_buffer = ""
    last_voice_time = time.time()
    silence_timeout = 1

    try:
        while True:
            try:
                pcm_data = await websocket.receive_bytes()
                print(f"收到音频字节:{len(pcm_data)}")  # 打印收到的数据长度
            except Exception:
                print("连接断开")
                break

            now = time.time()
            res = asr_model.generate(
                input=pcm_data,
                cache=cache,
                is_final=False,
                chunk_size=[0,10,5]
            )
            print("识别结果:", res)  # 打印模型输出

            if res and res[0]["text"].strip():
                text = res[0]["text"].strip()
                sentence_buffer += text
                last_voice_time = now
                await websocket.send_text(sentence_buffer)

            if sentence_buffer and (now - last_voice_time > silence_timeout):
                sentence_buffer = ""
                cache.clear()
                try:
                    await websocket.send_text("")
                except:
                    pass

    finally:
        cache.clear()


if __name__ == "__main__":
    import uvicorn
    # 开启https,局域网IP直接变成安全网站
    uvicorn.run(
        "asr_api:app",
        host="0.0.0.0",
        port=8088,
        ssl_keyfile="./key.pem",
        ssl_certfile="./cert.pem"
    )

前端 index.html(自动适配 http/ws、https/wss)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>实时字幕</title>
</head>
<body>
<div style="font-size:48px;padding:20px;" id="subtitle"></div>

<script>
const ws = new WebSocket(`wss://${location.host}/ws`);
const subtitleDom = document.getElementById("subtitle");

ws.onmessage = e => {
    subtitleDom.innerText = e.data;
};

async function startRecord() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
        audio: { sampleRate: 16000, channelCount: 1 }
    });
    const ctx = new AudioContext({ sampleRate: 16000 });
    const src = ctx.createMediaStreamSource(stream);
    const processor = ctx.createScriptProcessor(1024, 1, 1);

    src.connect(processor);
    processor.connect(ctx.destination);

    processor.onaudioprocess = evt => {
        const input = evt.inputBuffer.getChannelData(0);
        const buffer = new Int16Array(input.length);
        for (let i = 0; i < input.length; i++) {
            let s = Math.max(-1, Math.min(1, input[i]));
            buffer[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
        }
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(buffer.buffer);
        }
    };
  } catch (err) {
    console.error("麦克风权限失败:", err);
  }
}

startRecord();
</script>
</body>
</html>

4. 启动运行方式

把两个文件放在同一文件夹,打开PowerShell执行命令:

uvicorn asr_api:app --host 0.0.0.0 --port 8088

在这里插入图片描述

局域网访问方案(推荐首选)

  1. 查询服务端本机局域网IP:ipconfig
  2. 配置Windows hosts实现内网安全域名,解决麦克风权限拦截,下文详细说明。

5. 部署踩坑全记录(核心章节)

问题1:PyAudio OSError: [Errno -9999] Unanticipated host error

报错原因

后端使用PyAudio读取本机麦克风,服务器无声卡、无音频输入设备,直接打开流失败。

最终解决方案

彻底删除PyAudio录音代码,改为前端浏览器采集音频,后端只接收网络二进制流。
后端不再依赖任何音频硬件,彻底根除该异常。


问题2:WebSocket连接成功,浏览器不弹出麦克风授权,完全无法收音

根本原因

浏览器安全策略:http://192.168.x.x 内网IP属于非安全上下文,Chrome直接禁用麦克风API,不会弹出授权窗口。

方案A(局域网最优,零证书、零配置,强烈推荐)

使用.local内网域名绕过安全限制:

  1. 用管理员权限打开记事本,编辑hosts文件:
    C:\Windows\System32\drivers\etc\hosts
  2. 在文件末尾添加一行:
192.168.0.110  asr.local
  1. 浏览器访问地址:
http://asr.local:8088

浏览器自动把.local域名判定为内网安全域,HTTP环境下正常弹出麦克风权限。

方案B:mkcert生成自签名HTTPS证书
  1. 下载mkcert.exe,执行命令安装根证书:
.\mkcert.exe -install
  1. 为内网IP生成证书:
.\mkcert.exe xxx.xxx.x.xxx (你的电脑ip地址 ,cmd弹出黑框 输入ipconfig就能看到)

执行完毕,文件夹会多出 2 个文件:

  • xxx.xxx.x.xxx-key.pem 私钥
  • xxx.xxx.x.xxx.pem 证书文件

把两个文件改名为:

  • key.pem
  • cert.pem
    复制到你的 asr_api.py 同一个文件夹。
  1. 带证书启动HTTPS服务:
uvicorn asr_api:app --host 0.0.0.0 --port 8088 --ssl-keyfile key.pem --ssl-certfile cert.pem

在这里插入图片描述
在这里插入图片描述

重要:HTTPS网页中,WebSocket必须使用wss://,继续使用ws://会被浏览器拦截二进制数据流。


问题3:浏览器访问https提示:Invalid HTTP request received.

原因

服务没有加载SSL证书,仍然是普通HTTP服务,浏览器发起HTTPS握手,协议不匹配。
只有启动命令带上--ssl-keyfile--ssl-certfile,服务才是HTTPS服务。


问题4:WinError 10054 远程主机强迫关闭了一个现有的连接

现象

页面刷新、麦克风暂停后,控制台抛出连接重置异常。

原因

浏览器主动断开套接字,Windows系统强制关闭TCP连接,asyncio抛出警告。

解决办法
  1. receive_bytes()增加内层异常捕获,优雅退出循环;
  2. 全局屏蔽asyncio警告日志:
logging.getLogger("asyncio").setLevel(logging.CRITICAL)

问题5:WebSocket显示[accepted],说话但是页面没有任何文字

排查步骤
  1. HTTPS页面必须使用wss://ws://会被浏览器静默拦截二进制音频;
  2. 浏览器禁止页面自动调用麦克风,录音逻辑必须写在按钮点击事件内;
  3. 音频参数必须严格为:16000Hz、单声道、Int16 PCM;
  4. 在后端打印接收到的字节长度,确认前端音频流正常传输。

6. 功能扩展:语音识别对接大模型问答

FunASR只负责语音转文字,本身不具备对话问答能力。有两种扩展方案:

  1. FunASR一体化语音对话模型:LLMASRQwen/Qwen3-ASR-0.6B,输入音频直接返回回答文本;
  2. 模块化架构:Paraformer识别语音文本 → 调用本地轻量化Qwen小模型生成回答 → 把答案通过WebSocket推送回前端页面。

原有WebSocket接口完全不用改动,可直接升级为语音对话机器人。

演示视频:
在这里插入图片描述


7. 总结

  1. 架构优化:前端拾音+后端纯服务,彻底消灭PyAudio声卡硬件报错;
  2. 权限最优解:Hosts配置.local内网域名,不用给所有访客安装证书、修改浏览器配置;
  3. 协议严格区分:http对应ws,https对应wss,避免二进制音频被浏览器拦截;
  4. 完善异常捕获,解决Windows套接字10054强制断开警告;
  5. 增加静音自动清空字幕逻辑,大幅提升流式识别体验。

摘要 针对便利购超市传统库存管理中人工操作效率低、数据同步滞后、权限边界模糊、流程不规范等问题,为实现库存管理的数字化、规范化与智能化,提升多角色协同效率,本文设计并实现了一套适配中小型超市实际业务的库存信息管理平台。研究以问题为导向,遵循调研分析 - 设计开发 - 测试优化的软件开发流程,先通过文献研究与实地调研梳理核心技术要点与业务需求,明确管理员、库管、一线员工三类角色的功能边界;再基于 Vue+Spring Boot+MyBatis 技术栈搭建前后端分离架构,结合 RBAC 角色权限模型与数据库第三范式完成系统整体设计,涵盖需求分析、架构设计、功能模块设计、接口与权限控制设计、界面原型设计等环节;随后完成平台前后端开发实现实现商品及类别管理、库存预警、出入库与报损管理、全局库存管控等九大核心功能,同时针对开发中的权限控制、数据一致性、接口交互等问题提出针对性解决策略;最后通过功能、性能、兼容性多维度测试验证系统有效性。测试结果表明,该平台实现了库存管理全流程的线上化,可实现多角色权限的精细化管控、库存数据的实时同步与预警信息的即时推送,有效解决了传统库存管理的痛点,提升了超市库存管理的效率与精准度。系统兼具良好的稳定性、易用性与可扩展性,可为中小型零售超市的库存数字化管理提供技术支撑与实践参考,后续可进一步拓展数据分析、智能补货等功能,提升平台的智能化水平。 关键词:库存预警;超市;MyBatis
内容概要:本文研究了改进深度优先搜索算法与二进制粒子群优化算法相结合在配电网故障恢复重构中的应用,旨在提升故障后网络重构的效率与供电可靠性。通过引入改进的深度优先搜索算法高效生成满足辐射状约束的可行拓扑结构,并结合二进制粒子群算法进行全局优化,实现对开关操作序列的智能决策。文中系统阐述了两种算法的协同机制、适应度函数构建、配电网约束处理(如潮流平衡、电压限值、容量限制)以及孤岛与环网的规避策略,提出了一套完整的故障恢复重构流程。基于Matlab平台的仿真验证表明,该方法能在较短时间内找到高质量的恢复方案,有效恢复失电负荷,避免不合理的网络结构,具有较强的实用性和鲁棒性。; 适合人群:具备电力系统分析基础和Matlab编程能力,从事智能电网、配电自动化、故障诊断与恢复、电力系统优化等方向的科研人员及工程技术人员。; 使用场景及目标:①应对配电网突发故障,快速制定最优网络重构方案以最大化恢复供电范围;②优化故障后开关操作策略,降低停电损失和运行风险;③为配电管理系统(DMS)和自愈控制系统提供高效的算法支撑;④研究启发式算法与图搜索算法在复杂电力网络优化中的融合应用; 阅读建议:建议读者结合Matlab代码深入理解算法实现细节,重点关注深度优先搜索在拓扑可行性校验中的作用以及粒子群算法在离散空间优化中的编码与更新策略,可通过调整网络模型、故障场景和算法参数进行对比实验,以全面掌握其性能特点与适用边界。
内容概要:本文围绕“自适应最优控制在系统动力学完全未知的连续时间线性系统中的应用”展开,基于动态规划理论,提出了一种无需先验系统模型的数据驱动型自适应最优控制方法,并通过Matlab代码实现完成算法验证。文中系统阐述了在缺乏精确系统动态方程的前提下,如何融合强化学习中的策略迭代与值迭代思想,利用在线采集的状态数据逐步逼近哈密尔顿-雅克比-贝尔曼(HJB)方程的最优解,从而实现对无限时域线性二次调节器(LQR)问题的有效求解。该方法突破了传统最优控制对精确数学模型的依赖,具备良好的鲁棒性与工程适用性,特别适用于智能电网、机器人控制、飞行器导航等建模困难或存在模型不确定性的复杂系统。文档不仅包含详尽的理论推导与算法流程,还提供了完整的Matlab仿真实现代码及丰富的拓展科研资源,涵盖智能优化、机器学习、信号处理等多个交叉领域,强调“借力科研工具”以提升研究效率与创新能力。; 适合人群:具备现代控制理论基础和Matlab编程能力,从事自动化、控制工程、人工智能或相关方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究数据驱动的自适应动态规划(ADP)与最优控制算法的设计与实现;②应用于系统建模困难或参数时变的实际控制系统中,解决模型不确定性带来的控制难题;③复现高水平SCI论文中的先进控制策略,提升科研创新能力与算法实践水平。; 阅读建议:此资源以Matlab代码实现为核心,强调理论分析与仿真实践深度融合,建议读者按照文档目录循序渐进地学习,重点关注算法原理推导、代码实现细节与参数调优过程,并充分利用所提供的网盘资源进行动手复现与拓展研究,以深化对自适应最优控制机制的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HappyAcmen

非常感谢大佬的鼓励!感谢感谢!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值