LocalAI 音频变换(Audio Transform)功能深度指南:批量与流式 AEC、降噪与语音转换实战
【免费下载链接】LocalAILocalAI is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required.项目地址: https://gitcode.com/GitHub_Trending/lo/LocalAI
音频变换(Audio Transform)是 LocalAI 中一类「音频进、音频出」的通用后端能力:以主输入音频为处理对象,可选叠加一路参考音频作为条件信号,最终输出一路增强或转换后的音频。本文围绕 docs/content/features/audio-transform.md 展开,结合仓库内的 HTTP 路由、gRPC 后端与 Go 实现源码,系统讲解 LocalVQE 回声消除、噪声抑制、解混响以及基于 audio.cpp 的源分离(stems)与语音转换(speech-to-speech)的完整用法,覆盖批量 REST 端点、多输出 stems、双向 WebSocket 流式端点与逐参数调优,读完即可直接在本地部署并调用localvqe-v1.3-4.8m完成一次真实的实时通话语音增强。
audio-transform-io.png
端点类别概述:一类端点,多种变换
audio-transform 端点接收audio in并产出audio out,可以可选地以第二路参考音频信号作为条件。该类别在设计上是通用的——具体操作由所选后端决定,包括:
- 联合回声消除(AEC)+ 噪声抑制 + 解混响:由 LocalVQE 提供,首个正式上线的后端;
- 语音转换(voice conversion):此时
reference为目标说话人,由 audio.cpp 后端的seed_vc、vevo2、miocodec家族提供; - 音高变换、音频超分辨率等其它派生操作。
从仓库路由注册(core/http/routes/localai.go)可以看到该类别实际暴露为三个入口,后文逐一讲解:
router.POST("/audio/transformations", audioTransformHandler, ...) router.POST("/audio/transform", audioTransformHandler, ...) // 别名 router.GET("/audio/transformations/stream", localai.AudioTransformStreamEndpoint(app), ...)首个实现后端:LocalVQE
LocalVQE 是一个约 1.3 M 参数的、基于 GGML 的模型,在 16 kHz 单声道语音上进行 AEC + 噪声抑制 + 解混响联合处理,在桌面 CPU 上可达约 9.6× 实时。它派生自 Microsoft DeepVQE 论文的研究成果。仓库画廊(gallery/index.yaml)中对其描述为 "DeepVQE-style architecture with an S4D bottleneck and an in-graph DCT-II filterbank",模型文件约 5 MB(F32),license 为 Apache-2.0,并通过huggingface://LocalAI-io/LocalVQE/...分发。
源分离与语音转换则由 audio.cpp 后端承担:其htdemucs与mel_band_roformer系列生成下文所述的命名 stems;seed_vc、vevo2、miocodec系列则以参考说话人为条件做语音转换。
核心心智模型:三个请求字段
每个 audio-transform 请求都携带:
audio—— 主输入文件(必填)。reference—— 辅助信号,其语义由后端决定(可选):- 回声消除场景:扬声器播放的环回 / 远端(far-end)信号;
- 语音转换场景:目标说话人的参考片段;
- 音高 / 风格迁移场景:音调或风格参考;
- 省略时后端将其视为静音并优雅降级——例如 LocalVQE 在
reference为空时只做降噪 + 解混响。
params—— 一个泛化的key=value映射,原样转发给后端(LocalVQE 的键为noise_gate=true|false与noise_gate_threshold_dbfs=<float>)。
这种"近端/远端双通道"的形状在业界有清晰先例:它对应 WebRTC APM API 的ProcessStream(near)/ProcessReverseStream(far)、NVIDIA Maxine 的NvAFX_Run配对流签名,以及 ICASSP AEC Challenge 的双通道 WAV 约定。仓库中的 Go 后端实现(backend/go/localvqe/golocalvqe.go)印证了这一约定:参考通道缺省时零填充,且当参考短于主输入时零填充补齐、长于主输入时截断到与 mic 等长,保证逐样本对齐。
批量端点:/audio/transformations
POST /audio/transformations(别名POST /audio/transform)使用 multipart/form-data,返回音频字节。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
model | string | 是 | audio-transform 模型 id(如localvqe-v1.3-4.8m) |
audio | file | 是 | 主输入音频 |
reference | file | 否 | 可选辅助信号 |
response_format | string | 否 | wav(默认)、mp3、ogg、flac |
sample_rate | int | 否 | 期望输出采样率(Hz)。省略则使用后端自身采样率;否则必须在 8000~192000 之间,越界将被拒绝并返回 400 |
params[<key>] | string | 否 | 可重复出现;转发给后端 |
params[stem] | string | 否 | 仅多输出变换使用;指定响应体携带哪个命名输出 |
params[text] | string | 否 | 仅文本条件变换使用;被重合成的文本行 |
先通过画廊安装一个 audio-transform 模型(示例使用localvqe-v1.3-4.8m):
local-ai run localvqe-v1.3-4.8mLocalVQE 示例(回声消除 + 降噪 + 残差门控):
curl -X POST http://localhost:8080/audio/transformations \ -F model=localvqe-v1.3-4.8m \ -F audio=@mic.wav \ -F reference=@loopback.wav \ -F 'params[noise_gate]=true' \ -F 'params[noise_gate_threshold_dbfs]=-50' \ -o enhanced.wav当省略reference时,LocalVQE 将参考通道零填充,操作退化为噪声抑制 + 解混响。
源码视角:请求解析与表单参数收集
AudioTransformRequest(core/schema/audio_transform.go)是一个仅 multipart 的 schema,model字段实际上不通过 binder 绑定,而是由中间件按名称从c.FormValue("model")取到(见同文件注释);response_format与sample_rate两个 snake_case 字段上的form标签是承载逻辑的——echo binder 只会绑定带显式标签的字段,缺少标签曾导致这两个字段被静默忽略。
collectParamsFromForm(core/http/endpoints/localai/audio_transform.go)负责从表单中收集所有形如params[<key>]的键值,重复键取最后一个值;此外还提供了noise_gate与noise_gate_threshold_dbfs的裸字段快捷方式(表单字段优先级更高,params[*]仍然优先生效)。schema 中的参数常量AudioTransformParamNoiseGate/AudioTransformParamNoiseThreshold与 Go 后端(backend/go/localvqe/golocalvqe.go)中的字符串保持同步。
sample_rate 边界是资源约束,而非审美判断
validateAudioTransformSampleRate(core/http/endpoints/localai/audio_transform.go)在触碰磁盘与模型之前就拒绝越界的采样率,返回 HTTP 400:
const minAudioTransformSampleRate = 8000 const maxAudioTransformSampleRate = 192000源码注释解释了缘由:sample_rate会被直接插值进 ffmpeg 的-ar,而 ffmpeg 会毫无抱怨地接受离谱速率——-ar 999999999写一秒钟的音频会产出 3.9 GB 的 WAV 并以退出码 0 结束,且GeneratedContentDir无人清扫;反过来-ar 1会写出零字节文件同样返回 0,随后被当作变换结果发给调用者。8000 Hz 是电话编解码器的最低速率,192000 Hz 已是消费级音频硬件与 WAV 容器的常规上限(且是画廊中所有模型 48 kHz 产出的 4 倍)。sample_rate缺省(0)表示"保持后端自身采样率",跳过校验。
后端见到上传之前,LocalAI 做了什么?
默认情况下,什么都不做:文件以其原始采样率与声道数到达后端。已经是纯 16-bit PCM 的 WAV 会逐字节透传;任何其它容器或编码会被转码为 16-bit PCM WAV,保持原有速率与声道布局。
唯一的例外是声明需要固定输入形态的后端。LocalVQE 正是如此:其回声消除针对 16 kHz 单声道训练,并要求主输入与参考同形,因此针对它的上传会用 ffmpeg 折叠为 16 kHz mono s16。该声明是BackendCapability.AudioTransformInputMono16k(位于core/config,对应 core/config/backend_capabilities.go 中的AudioTransformRequiresMono16kInput),而localvqe是目前唯一设置它的后端。
这一点对非语音增强类任务至关重要:源分离模型只接受其 checkpoint 自身的采样率(已发布的 htdemucs 与 mel_band_roformer checkpoint 均为 44.1 kHz),并依赖立体声像来区分居中人声与宽阔混音——16 kHz 单声道降混会同时毁掉它们接受的格式与依赖的线索。
在端点实现中(core/http/endpoints/localai/audio_transform.go),折叠与否由后端声明config.AudioTransformRequiresMono16kInput(cfg.Backend)决定:为真走utils.AudioToWav(折叠到 16 kHz mono),为假走utils.AudioToWavPreservingShape(保持原始形态;已是 PCM16 的 WAV 以硬链接方式透传而非重编码)。主输入与参考按同一标准折叠,保证 AEC 逐样本对齐。上传文件先落盘为audio-raw-*/reference-raw-*,再规整为audio.wav/reference.wav,前缀命名可避免同一 basename 双上传互相截断覆盖的竞态。
多输出变换:源分离 stems
部分变换单次运行会产出多个命名输出:htdemucs 一次即可得到drums、bass、other、vocals。由于响应体只能携带一个文件,LocalAI 采用如下协议:
- 后端只运行一次,并把每个 stem 写在主输出旁;
params[stem]=<name>选择响应体携带哪一个;缺省为vocals(若模型有该 stem),否则取模型的第一个输出。未知 stem 名会被拒绝并返回错误、列出真实可用的名字,绝不会静默替换;- 每个 stem(包括响应体内的那个)都会出现在
X-Audio-Stems响应头中,这是一个紧凑的 JSON 数组:
X-Audio-Stems: [{"name":"drums","url":"/generated-audio/transform.drums.wav"}, {"name":"bass","url":"/generated-audio/transform.bass.wav"}, {"name":"other","url":"/generated-audio/transform.other.wav"}, {"name":"vocals","url":"/generated-audio/transform.vocals.wav"}]抓取任意上述 URL 即可在不重复付费做第二次分离的前提下取得其它 stem。sample_rate与response_format会同样应用于 stems 与响应体,因此整组输出保持所请求的形态。该响应头已列入Access-Control-Expose-Headers,浏览器客户端可以读取。
单输出变换(回声消除、语音转换)完全不设置该响应头;对这类模型传params[stem]会被拒绝而不是被忽略。
# 只取人声(默认 stem),然后看其它 stem 落到了哪里 curl -sS -D headers.txt -X POST http://localhost:8080/audio/transformations \ -F model=htdemucs -F audio=@song.wav -o vocals.wav grep -i '^x-audio-stems' headers.txt # 或者让响应体直接携带某个指定 stem curl -sS -X POST http://localhost:8080/audio/transformations \ -F model=htdemucs -F audio=@song.wav -F 'params[stem]=drums' -o drums.wavstems 存放在生成内容目录中主输出旁的兄弟文件,由/generated-audio/提供访问;与其它生成产物一样,不会被自动清扫。
源码视角:一次推理产出全部 stems
后端ModelAudioTransform(core/backend/audio_transform.go)把输出文件写入GeneratedContentDir/audio下的唯一文件名,同时将上传的输入拷贝持久化(persistAudioInput),以便 React UI 历史记录回放。collectStems(core/backend/audio_transform.go)把后端上报的 stems 收拢为调用方可读列表,且做了安全校验:每个路径必须是该请求交给后端的生成目录的直接子文件,杜绝恶意后端进程返回指向/etc或他人文件的路径再经 HTTP 层原样回吐;校验失败即丢弃该 stem 而非致命报错。
在 HTTP 层,stemsHeader(core/http/endpoints/localai/audio_transform.go)将 stems 渲染为单行 JSON 数组——之所以用 JSON 而非name=url平铺格式,是因为 stem 名是模型的字符串(来自 checkpoint 配置),一个含逗号或等号的 stem 名会悄悄破坏手拼的分隔符格式;URL 经url.PathEscape转义,防止#、?、%截断或污染 URL。convertStems会对每个 stem 应用请求的sample_rate与response_format,转换失败的 stem 直接从列表丢弃(日志记录)而不是以原速率/原格式广告——"广告的 URL 文件不是调用者请求的形态"正是这套设计要避免的静默错误答案。/generated-audio/前缀取自生成目录,与 TTS API 同一套逻辑。CORS 暴露清单见exposedAudioTransformHeaders = "X-Audio-Input-Url, X-Audio-Reference-Url, X-Audio-Stems"。
文本条件变换:语音到语音(speech-to-speech)
大多数变换是"音频进、音频出",少数不是:speech-to-speech 模型需要知道要重合成的文本行内容,才能用另一种声音复述。由于/audio/transform没有文本字段,文本通过params[text]传递:
curl -sS -X POST http://localhost:8080/audio/transformations \ -F model=audio-cpp-vevo2-speech-to-speech \ -F audio=@source.wav \ -F reference=@target-speaker.wav \ -F 'params[text]=The quick brown fox jumps over the lazy dog.' \ -o converted.wav对于这类模型,文本是必需项而非提示:缺少文本时运行会被直接拒绝。params[target_text]被接受为同义字段;params[language]在文本非英语时设定语言。不接受文本的模型会忽略以上三个字段,因此普通分离或降噪请求完全不受影响。
流式端点:/audio/transformations/stream
GET /audio/transformations/stream是一个双向 WebSocket 端点。客户端第一条消息为 JSON 信封;其后客户端消息为二进制 PCM 帧;服务器以相同节奏回发二进制 PCM 帧。
该端点只接受带audio_transformuse case 的模型,不接受带realtime_audiouse case 的 any-to-any 模型。例如liquid-audio应改用 OpenAI Realtime API。这一校验在端点源码中显式实现(validateAudioTransformStreamModel,core/http/endpoints/localai/audio_transform.go),会对 any-to-any 模型返回 "use the OpenAI Realtime API instead" 的明确错误。
线上格式
客户端 → 服务器(文本帧,第一条):
{ "type": "session.update", "model": "localvqe-v1.3-4.8m", "sample_format": "S16_LE", "sample_rate": 16000, "frame_samples": 256, "params": { "noise_gate": "true" } }sample_format为S16_LE(16-bit 有符号小端)或F32_LE(32-bit 浮点小端,取值 [-1, 1])。frame_samples缺省为后端首选 hop 长度(LocalVQE 为 256 = 16 ms)。控制信封的完整字段结构见AudioTransformStreamControl(core/schema/audio_transform.go),包含type、model、sample_format、sample_rate、frame_samples、params、reset与error。
客户端 → 服务器(二进制帧,后续消息):交错立体声 PCM,通道 0 = audio(mic),通道 1 = reference。帧大小:frame_samples × 2 channels × sample_size。S16_LE256 样本时为每帧 1024 字节;F32_LE时为 2048 字节。参考通道无信号时发送全零。端点在读到二进制帧后会调用splitStereoFrameInto(core/http/endpoints/localai/audio_transform.go)原位去交错,利用每连接复用的缓冲区避免 16 ms 节奏下每次入站帧两次分配。
服务器 → 客户端(二进制帧):同格式的单声道 PCM,frame_samples × sample_size字节(S16_LE为 512,F32_LE为 1024)。
流中控制(文本帧):再次发送session.update且其reset字段为 true 时重置流状态;session.close文本帧干净地结束会话。WebSocket 连接的每消息读取上限为 1 MiB(audioTransformWSReadLimit),以容纳 hop 更长的后端在更大frame_samples下的帧。Go 后端侧(backend/go/localvqe/golocalvqe.go)要求首条消息必须是Config,且若frame_samples非零则必须等于 hop_length;流状态是每上下文(per-context)的,因此调用经SingleThread串行化,避免并发流破坏 overlap-add 缓冲区。
延迟
LocalVQE 具有 16 ms 算法延迟(一个 hop)。运行时的每帧 CPU 开销取决于模型:紧凑的 1.3 M 参数模型(v1.1/v1.2,约 9.7× 实时)约 1.6 ms/帧,更宽的 v1.3 4.8 M 模型(约 4.7× 实时)约 3.3 ms/帧——测量环境为 4 线程的现代桌面,剩余预算留给网络与下游播放。
值得补充的是:Go 后端的线程数策略(backend/go/localvqe/golocalvqe.go)会把GGML_NTHREADS封顶到 4——LocalVQE 仅 1.3 M 参数,上游基准扫描显示 1–4 线程是最优区间,超过约 4 线程后每帧预算被同步开销主导、p99 延迟恶化。该上限同时避免为 70B LLM 全局调优的LOCALAI_THREADS=N意外拖累音频处理。
后端专项调优(LocalVQE)
params[<key>] | 类型 | 默认值 | 作用 |
|---|---|---|---|
noise_gate | bool | false | 启用 OLA 后置的基于 RMS 的残差回波门 |
noise_gate_threshold_dbfs | float | -45.0 | 门限值(dBFS);低于该值的帧被置零 |
该门在远端单讲 / 近端静音的时间段里最有用——此时模型的残差听起来像缓冲噪声或被放大的底噪。一个合理的起始值是-50dBFS。
Go 后端会按调用应用参数(applyParams,backend/go/localvqe/golocalvqe.go):先以Options[]中的静态配置为底(Load 时经parseOptions解析),请求级params中的值逐次覆盖并通过CppSetNoiseGate下发到 C 侧,结果缓存回内存避免每请求重复 CGo 往返。两个键的字符串常量与 HTTP 层、schema 层保持同步。
配置一个模型
LocalVQE 在画廊中发布多个权重版本:localvqe-v1.3-4.8m(当前默认,质量最佳)、localvqe-v1.2-1.3m与localvqe-v1.1-1.3m(紧凑型,每 hop 开销约为前者的 1/4,适合低核或功耗受限主机)。它们共享同一后端与请求 API,仅有model文件名不同。画廊条目(gallery/index.yaml)以overrides.backend: localvqe绑定后端,并携带audio-transform、aec、noise-suppression、dereverberation等标签用于筛选。
在模型配置中可以通过Options[]设置后端级默认值;每次请求的params[*]表单字段会覆盖它们:
name: localvqe backend: localvqe parameters: model: localvqe-v1.3-4.8M-f32.gguf # Backend-specific defaults can be set in Options[]; per-request # params[*] form fields override. # # `backend` and `device` route through the upstream localvqe options # builder so you can force a non-default GGML backend (e.g. `Vulkan`) or # pin to a specific GPU index. Leave both unset to keep the CPU default. options: - noise_gate=true - noise_gate_threshold_dbfs=-50 # - backend=Vulkan # - device=0源码层面(backend/go/localvqe/golocalvqe.go)确认了Options[]的解析规则:支持key=value与key:value两种分隔形式;backend与device经由上游 options builder(CppOptionsNew+ setter +CppNewWithOptions)构造上下文,从而可强制非默认 GGML 后端或钉住某块 GPU;其余键(noise_gate、noise_gate_threshold_dbfs)在本地消费。加载时会断言模型采样率恰为 16000 Hz、hop 与 FFT 尺寸为正,否则拒绝加载并给出清晰错误,而不是放 C 侧返回垃圾数据。
端到端调用链小结
一次典型的批量增强请求在仓库中走如下链路,可作为排障时的参考:
- 路由层:
router.POST("/audio/transformations")命中AudioTransformEndpoint(core/http/routes/localai.go); - 中间件:
setModelNameFromRequest从表单取model并解析模型配置(model字段不依赖 echo binder); - 端点校验:先验
sample_rate边界(400 拒绝),再接收audio文件,可选接收reference,按后端声明决定是否折叠到 16 kHz mono(saveMultipartFileAsWAV); - 参数收集:
collectParamsFromForm汇总params[*]并与 schema body 参数合并(表单优先); - 后端执行:
backend.ModelAudioTransform持久化输入、获取全局后端槽位(AcquireGlobalBackendSlot)、下发proto.AudioTransformRequest并收集 stems;LocalVQE 侧经 purego 绑定调用 C 库的localvqe_process_f32,缺参考则零填充; - 产物处理:按需
AudioResample+AudioConvert,设置X-Audio-Input-Url/X-Audio-Reference-Url/X-Audio-Stems头与 CORS 暴露清单,最后以附件形式返回音频字节。
流式路径则在AudioTransformStreamEndpoint中完成 WebSocket 升级(lockedConn串行化写入、避免 Gorilla 单并发写者约束下的数据竞争)、首帧session.update校验、后端双向 gRPC 流桥接与立体声帧去交错,整个会话以session.close干净收尾。
See also
- 音频变换的官方功能文档(本文依据的原始文档,含线格式与参数表的权威表述)
- 音频变换端点实现(批量与流式端点、采样率边界、stems 头与 CORS)
- 音频变换后端服务层(gRPC 调用、输入持久化与 stems 路径安全)
- LocalVQE Go 后端实现(参数解析、16 kHz 断言、流式帧处理与线程策略)
- audio-transform 请求/流控制 schema(字段标签、协议常量)
- LocalVQE 画廊条目(v1.1 / v1.2 / v1.3 各权重版本的下载地址与 sha256)
【免费下载链接】LocalAILocalAI is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required.项目地址: https://gitcode.com/GitHub_Trending/lo/LocalAI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考