ComfyUI工作流集成SenseVoice-Small:可视化语音识别节点开发
ComfyUI工作流集成SenseVoice-Small:可视化语音识别节点开发
最近在折腾ComfyUI,想给它加点新能力。我发现很多工作流都在处理图片和文字,但音频这块好像缺了点好用的工具。正好手头有个不错的语音识别模型SenseVoice-Small,就想试试能不能把它做成一个ComfyUI节点,让音频处理也能像搭积木一样简单直观。
SenseVoice-Small这个模型挺有意思的,识别准确率不错,而且对硬件要求不算太高,很适合集成到本地工作流里。今天我就来分享一下,怎么从零开始开发一个ComfyUI自定义节点,把语音识别能力无缝接入你的可视化AI流程中。整个过程其实没有想象中那么复杂,跟着步骤走,你也能做出自己的专属节点。
1. 为什么要在ComfyUI里做语音识别?
你可能要问,语音识别工具那么多,为什么非要集成到ComfyUI里?我最初也是这么想的,但实际用下来发现,集成后的体验完全不一样。
最直接的好处就是流程可视化。以前处理音频,我得在命令行、代码编辑器、播放器之间来回切换。现在所有步骤都在一个画布里完成,从加载音频文件,到识别转文字,再到后续的文字处理,一目了然。哪个环节出问题,一眼就能看出来。
另一个优势是工作流可复用。一旦你把语音识别节点做好了,就可以把它保存成一个完整的工作流模板。下次再处理类似的音频任务,直接加载模板就行,不用重新配置。如果你经常需要处理会议录音、视频字幕生成或者语音笔记整理,这个功能能省下大量重复劳动。
还有就是能力扩展。ComfyUI本身是个很开放的平台,你可以把语音识别节点和其他节点自由组合。比如,识别出的文字可以直接喂给大语言模型做摘要,或者生成对应的图片、视频。这种跨模态的联动,在传统工具里很难实现得这么流畅。
2. 开发前的准备工作
在动手写代码之前,我们需要先把环境搭好,把必要的工具和模型准备好。这部分工作做扎实了,后面的开发会顺利很多。
2.1 环境与依赖安装
首先确保你的ComfyUI是最新版本。如果你是从源码安装的,最好先拉取一下最新代码。然后我们需要安装一些Python依赖包。
打开终端,进入你的ComfyUI安装目录,激活虚拟环境(如果你用了的话),然后安装以下包:
pip install torchaudio
pip install transformers
pip install soundfile
torchaudio是PyTorch的音频处理库,我们会用它来加载和处理音频文件。transformers是Hugging Face的模型库,SenseVoice-Small模型就是通过它来加载和使用的。soundfile是一个轻量级的音频文件读写库,兼容性很好。
如果你之前已经安装过这些包,可以跳过这一步。不过建议检查一下版本,太旧的版本可能会有兼容性问题。
2.2 获取SenseVoice-Small模型
SenseVoice-Small模型可以在Hugging Face上找到。我们不需要手动下载,代码运行时会自动从Hugging Face Hub下载。但为了开发时测试方便,我建议你先在本地准备好模型文件。
你可以通过以下Python代码快速测试模型是否能正常加载:
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor
model_name = "SenseVoice/SenseVoice-Small"
model = AutoModelForSpeechSeq2Seq.from_pretrained(model_name)
processor = AutoProcessor.from_pretrained(model_name)
print("模型加载成功!")
如果这段代码能正常运行,说明你的网络环境和依赖都没问题。第一次运行会下载模型文件,可能需要一些时间,取决于你的网速。模型大小在1-2GB左右,请确保有足够的磁盘空间。
3. 创建自定义节点:SenseVoice识别节点
现在进入核心部分,我们来创建ComfyUI的自定义节点。ComfyUI的节点开发其实有一套固定的模式,理解了这套模式,开发起来就很快了。
3.1 节点类的基本结构
在ComfyUI的custom_nodes目录下,新建一个文件夹,比如叫sensevoice_node。然后在这个文件夹里创建__init__.py和nodes.py两个文件。
__init__.py文件很简单,主要用来导出节点类:
from .nodes import SenseVoiceNode
NODE_CLASS_MAPPINGS = {
"SenseVoiceNode": SenseVoiceNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SenseVoiceNode": "SenseVoice语音识别"
}
重点是nodes.py文件,这里定义了我们的节点类。先来看基本框架:
import torch
import torchaudio
import numpy as np
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor
import folder_paths
import comfy.utils
class SenseVoiceNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"audio": ("AUDIO",),
},
"optional": {
"language": (["auto", "zh", "en", "ja", "ko"], {"default": "auto"}),
"task": (["transcribe", "translate"], {"default": "transcribe"}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "recognize"
CATEGORY = "audio"
DESCRIPTION = "使用SenseVoice-Small模型进行语音识别"
我来解释一下这几个关键部分:
INPUT_TYPES: 定义了节点的输入参数。required是必填项,这里我们只需要一个音频输入。optional是可选项,比如语言选择和任务类型。RETURN_TYPES: 定义输出类型,这里我们输出字符串类型的文本。RETURN_NAMES: 输出参数的显示名称,方便在界面上识别。FUNCTION: 节点执行时调用的方法名。CATEGORY: 节点在ComfyUI界面中的分类,我把它放在"audio"类别下。DESCRIPTION: 节点的描述信息,鼠标悬停时会显示。
3.2 音频输入的处理
你可能注意到了,输入类型里我用了("AUDIO",)。但ComfyUI默认可能没有AUDIO这个类型,所以我们需要先创建一个音频加载节点,或者使用现有的音频处理节点。
在实际开发中,我建议先创建一个简单的音频加载节点,它可以把音频文件加载成Tensor格式,供后续节点使用。这里给出一个简化的音频加载节点示例:
class AudioLoadNode:
@classmethod
def INPUT_TYPES(cls):
input_dir = folder_paths.get_input_directory()
files = folder_paths.get_filename_list("audio")
return {
"required": {
"audio_file": (files, {"audio_upload": True}),
},
}
RETURN_TYPES = ("AUDIO",)
FUNCTION = "load_audio"
CATEGORY = "audio/load"
def load_audio(self, audio_file):
# 获取音频文件完整路径
input_dir = folder_paths.get_input_directory()
audio_path = folder_paths.get_annotated_filepath(audio_file)
# 使用torchaudio加载音频
waveform, sample_rate = torchaudio.load(audio_path)
# 统一采样率(如果需要)
if sample_rate != 16000: # SenseVoice通常需要16kHz
resampler = torchaudio.transforms.Resample(sample_rate, 16000)
waveform = resampler(waveform)
sample_rate = 16000
return (waveform, sample_rate)
这个节点会把音频文件加载成波形数据(waveform)和采样率(sample_rate),然后打包成一个元组输出。其他节点可以通过AUDIO类型接收这个数据。
3.3 核心识别功能的实现
现在回到SenseVoice节点,我们来实现核心的recognize方法:
def recognize(self, audio, language="auto", task="transcribe"):
# 解包音频数据
waveform, sample_rate = audio
# 确保音频是单声道
if waveform.shape[0] > 1:
waveform = torch.mean(waveform, dim=0, keepdim=True)
# 转换为numpy数组并归一化
audio_array = waveform.numpy().squeeze()
audio_array = audio_array / np.max(np.abs(audio_array))
# 加载模型和处理器(带缓存)
model_name = "SenseVoice/SenseVoice-Small"
model = AutoModelForSpeechSeq2Seq.from_pretrained(
model_name,
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
low_cpu_mem_usage=True,
use_safetensors=True
)
processor = AutoProcessor.from_pretrained(model_name)
# 将模型移到GPU(如果可用)
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
# 准备输入
inputs = processor(
audio_array,
sampling_rate=sample_rate,
return_tensors="pt",
padding=True
)
# 移到相同设备
inputs = {k: v.to(device) for k, v in inputs.items()}
# 生成识别结果
with torch.no_grad():
generated_ids = model.generate(
**inputs,
max_length=448,
language=language if language != "auto" else None,
task=task
)
# 解码文本
text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
# 清理GPU内存
if torch.cuda.is_available():
torch.cuda.empty_cache()
return (text,)
这段代码做了几件重要的事情:
- 音频预处理:确保音频是单声道,并做归一化处理,这样模型识别效果更好。
- 模型加载:使用
from_pretrained加载SenseVoice-Small模型。我加了torch_dtype参数,如果GPU可用就用半精度浮点数,节省显存。 - 设备管理:自动检测是否有GPU,有的话就把模型和输入数据都移到GPU上,加快推理速度。
- 识别生成:调用模型的
generate方法进行语音识别。这里可以指定语言和任务类型。 - 内存清理:识别完成后清理GPU缓存,避免内存泄漏。
3.4 性能优化与缓存
上面的代码每次调用都会重新加载模型,这显然效率太低。在实际使用中,我们应该添加模型缓存机制:
class SenseVoiceNode:
_model = None
_processor = None
@classmethod
def get_model(cls):
if cls._model is None:
print("正在加载SenseVoice模型...")
model_name = "SenseVoice/SenseVoice-Small"
cls._model = AutoModelForSpeechSeq2Seq.from_pretrained(
model_name,
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
low_cpu_mem_usage=True,
use_safetensors=True
)
cls._model.to("cuda" if torch.cuda.is_available() else "cpu")
cls._model.eval()
return cls._model
@classmethod
def get_processor(cls):
if cls._processor is None:
model_name = "SenseVoice/SenseVoice-Small"
cls._processor = AutoProcessor.from_pretrained(model_name)
return cls._processor
def recognize(self, audio, language="auto", task="transcribe"):
# 获取缓存的模型和处理器
model = self.get_model()
processor = self.get_processor()
# ... 其余处理逻辑不变 ...
这样修改后,模型只会在第一次调用时加载,后续调用都使用缓存好的模型,大大提升了效率。
4. 构建完整的工作流
节点开发好了,现在我们来搭建一个完整的语音识别工作流。这个工作流会包含三个主要节点:音频加载、语音识别、文本输出。
4.1 工作流节点连接
在ComfyUI界面中,你可以这样连接节点:
- AudioLoadNode(音频加载节点):选择你要识别的音频文件,输出
AUDIO类型数据。 - SenseVoiceNode(语音识别节点):接收上一步的音频数据,设置识别参数(语言、任务类型),输出识别文本。
- TextOutputNode(文本输出节点):接收识别结果,显示或保存文本。
如果你想让工作流更完整,还可以在后面接上:
- LLM处理节点:把识别出的文本送给大语言模型做摘要、翻译或分析。
- 文件保存节点:把结果保存到本地文件。
4.2 实际使用示例
我最近用这个工作流处理了一个小时的会议录音。整个过程完全在ComfyUI里完成:
- 第一步:用AudioLoadNode加载MP3文件
- 第二步:SenseVoiceNode识别,设置语言为中文,任务为转写
- 第三步:识别出的文本有3万多字,我接了一个LLM节点,让它生成会议纪要
- 第四步:最后用SaveTextNode把纪要保存为Markdown文件
整个流程跑了大概10分钟(包括模型加载时间),识别准确率我觉得能有90%以上。关键是全程可视化,哪个环节出问题一目了然。
4.3 处理长音频的技巧
如果你要处理很长的音频(比如超过10分钟),直接喂给模型可能会遇到内存问题。这时候可以添加一个音频分割的预处理节点:
class AudioSplitNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"audio": ("AUDIO",),
"segment_length": ("INT", {"default": 30, "min": 10, "max": 300, "step": 5}),
"overlap": ("INT", {"default": 5, "min": 0, "max": 30, "step": 1}),
}
}
RETURN_TYPES = ("AUDIO_LIST",)
FUNCTION = "split_audio"
CATEGORY = "audio/process"
def split_audio(self, audio, segment_length=30, overlap=5):
waveform, sample_rate = audio
# 计算采样点数
segment_samples = int(segment_length * sample_rate)
overlap_samples = int(overlap * sample_rate)
step_samples = segment_samples - overlap_samples
# 分割音频
segments = []
total_samples = waveform.shape[1]
for start in range(0, total_samples, step_samples):
end = min(start + segment_samples, total_samples)
segment = waveform[:, start:end]
# 如果片段太短,跳过或特殊处理
if segment.shape[1] < sample_rate * 5: # 少于5秒
continue
segments.append((segment, sample_rate))
return (segments,)
然后在SenseVoice节点里,你可以添加对音频列表的支持,批量处理这些片段,最后再把结果拼接起来。
5. 常见问题与调试技巧
开发过程中我遇到了一些坑,这里分享出来,希望能帮你少走弯路。
5.1 音频格式兼容性问题
不同的音频文件格式可能需要不同的处理方式。我发现SenseVoice对WAV格式的支持最好,MP3次之。如果你遇到识别效果差的情况,可以尝试先把音频转换成WAV格式,采样率设为16000Hz,单声道。
可以在音频加载节点里添加格式转换逻辑:
# 如果是MP3格式,先解码
if audio_path.endswith('.mp3'):
import tempfile
import subprocess
# 使用ffmpeg转换为WAV
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp:
cmd = ['ffmpeg', '-i', audio_path, '-ar', '16000', '-ac', '1', tmp.name]
subprocess.run(cmd, capture_output=True)
audio_path = tmp.name
5.2 显存不足的处理
SenseVoice-Small虽然算小模型,但在长音频上还是会占用不少显存。如果遇到CUDA out of memory错误,可以尝试:
- 使用音频分割,分批处理
- 降低模型精度:
torch_dtype=torch.float32而不是float16 - 启用CPU模式:强制在CPU上运行(虽然慢,但不会爆显存)
5.3 识别效果优化
如果识别结果不理想,可以调整这些参数:
- 语言设置:如果知道音频的语言,明确指定比用"auto"效果更好
- 音频质量:确保音频清晰,背景噪音少
- 采样率:统一为16000Hz,这是大多数语音模型的最佳采样率
6. 扩展思路与应用场景
这个语音识别节点开发完成后,我发现了更多可以扩展的方向。
6.1 实时语音识别
现在的节点是处理文件,但完全可以扩展成实时识别。你可以添加一个麦克风输入节点,实时采集音频流,然后分段送给SenseVoice识别。这对于做实时字幕生成、语音助手之类的应用很有用。
6.2 多语言混合识别
SenseVoice支持多种语言,但一次只能指定一种。你可以开发一个语言检测节点,先检测音频的主要语言,然后动态切换识别语言。甚至可以实现中英文混合识别,这在很多实际场景中很实用。
6.3 与其他AI能力结合
语音识别只是第一步,识别出的文字可以:
- 送给大语言模型做内容分析
- 用于生成对应的图像或视频(文字转视觉)
- 作为语音克隆的输入,用不同音色重新合成
- 翻译成其他语言,再做语音合成
在ComfyUI里,这些都可以通过节点连接轻松实现。我最近就在做一个工作流:语音识别 → 中文摘要 → 英文翻译 → 语音合成,全程自动化。
7. 总结
开发这个SenseVoice-Small节点的过程,让我对ComfyUI的扩展能力有了更深的理解。虽然开始可能会觉得有点复杂,但一旦掌握了节点开发的基本模式,其实挺有意思的。
用下来感觉,SenseVoice-Small的识别效果对日常使用来说足够了,而且集成到可视化工作流后,整个处理流程变得特别清晰。你可以清楚地看到音频怎么变成文字,文字又怎么被后续节点处理。
如果你也想扩展ComfyUI的能力,我建议从这种实用的小功能开始。先解决一个具体问题,把流程跑通,然后再慢慢添加更多功能。开发过程中多测试,多调试,遇到问题就去ComfyUI的社区看看,通常都能找到解决方案。
最后,这个节点代码我已经整理好了,你可以在我的GitHub仓库找到完整版本。当然,我更建议你根据自己的需求修改和扩展,做出更适合自己工作流的版本。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐
所有评论(0)