
人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手【免费下载链接】airi 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器目标是接近 Neuro-sama 的高度支持实时语音聊天、Minecraft 和 Factorio 游玩支持 Web / macOS / Windows。项目地址https://gitcode.com/moeru-ai/airi点击查看免费下载proj-airi/audio是 Moeru AI AIRI 项目自托管 AI 伴侣与赛博生命灵魂容器中负责浏览器音频资源与格式转换的基础包。它不关心对话轮次、角色路由或 Agent 通知只解决三件事把麦克风、借用 MediaStream 或音频文件变成 PCM 流把 PCM 编码为 WAV 或提供商输入格式把文件或流式 PCM 通过 Web Audio 播出来。读完本文你将掌握 AIRI 音频管线的完整工作流——从 getUserMedia 到转录提供商从 TTS 音频入队到基于音频时钟的静音回执以及对应的测试与类型检查方法。包职责边界谁拥有音频资源谁拥有编排AIRI 将音频拆成两层职责划分非常清晰proj-airi/audio本包拥有浏览器音频资源和格式转换麦克风/MediaStream/文件的 PCM 源、WAV 编码、Web Audio 播放、toMediaStream轨道桥接。相关实现位于 packages/audio/src/browser 与 packages/audio/src/encoding。proj-airi/pipelines-audio拥有采集区间capture intervals、观察窗口observation windows和播放组playback groups它不依赖任何 UI也不接触浏览器音频 API。相关实现位于 packages/pipelines-audio/src。一个容易混淆的点是本包导出的入口只有proj-airi/audio/encoding见 packages/audio/src/index.ts 的export * from ./encoding而浏览器能力统一从proj-airi/audio/browser子路径导入这在 packages/audio/package.json 的exports字段中有明确声明./browser、.、./encoding三个子路径各自指向编译产物。音频编排能力AudioInput、capture、Playback则从proj-airi/pipelines-audio导入。用它做什么根据 README 的定位本包用于打开麦克风、借用的MediaStream或音频文件作为 PCM 源把 PCM 编码为 WAV或转换成提供商输入格式通过 Web Audio 播放文件或流式 PCM在上报静音silence之前对自有播放节点做淡出处理。明确不做什么同样重要的边界约束README 的 Do not use it for 一节不做角色路由、聊天持久化、Agent 通知不做 VAD 策略、唤醒词匹配、记忆检索、转录改写不做 Vue 状态或跨窗口命令。这些职责分属 core-agent、stage-ui 等其他模块音频包保持纯粹的可复用性。音频源AudioSource三种来源一个接口一个音频源就是一个AudioSource。调用它的open(signal)会返回一个 PCM 流中止signal会释放该连接占用的资源。接口定义在 packages/pipelines-audio/src/audio-input.tsexport interface AudioSource { open: (signal: AbortSignal) ReadableStreamPcmBlock }PcmBlock是贯穿全链路的核心数据结构它包含半开区间rangesourceIdstartFrame/endFrame、sampleRate与按平面planar排列的channels且约定消费者不得修改共享的源采样见 packages/pipelines-audio/src/audio-input.ts。sourceId让帧坐标始终归属某一次源连接设备切换时不会跨连接混用坐标。三种内置源的对比README 原表附实现要点源模块是否实时Live轨道/数据的属主microphoneSource(constraints, options)proj-airi/audio/browser是源自身。停止轨道并关闭它自己的 AudioContextmediaStreamSource(stream, context)proj-airi/audio/browser是调用方。源只断开它自己的节点fileSource(blob)proj-airi/audio/encoding否调用方。每次连接由 Mediabunny 重新解码文件麦克风源同步 getUserMedia 保住用户手势microphoneSource的实现packages/audio/src/browser/sources.ts有一个容易被忽视的关键点open会同步调用getUserMedia并resume()自己专属的AudioContext因此可以放在 click handler 里以保留用户手势。它对外暴露stream与echoCancellation两个只读属性供回声策略和设备标签查询。连接关闭时依次停止所有轨道、关闭该源的 AudioContext。借用 MediaStream只动自己的节点mediaStreamSourcepackages/audio/src/browser/sources.ts把调用方传入的流接入一个AudioWorkletNode(airi-capture)。它只读取采样图中用一个值为 0 的GainNode静音清理时只断开自己的节点绝不停止调用方拥有的轨道。如果流里没有 live 的音频轨道open会直接抛错。采集内核AudioWorklet 在渲染线程外复制采样airi-capture处理器定义在 packages/audio/src/browser/capture.worklet.ts在每个渲染量子render quantum里把输入平面slice()出来连同startFrame与sampleRate一起通过postMessage转移出去使用转移列表避免拷贝。这样编解码和语音策略都运行在渲染线程之外且每个AudioContext只会addModule一次见sources.ts里的workletsWeakMap 缓存。文件源每次连接重新解码fileSourcepackages/audio/src/encoding/media-file.ts 附近的实现基于 Mediabunny 的BlobSource解码每个消费者打开自己的流、拥有独立的帧坐标天然保留背压——文件的读取者决定解码速度。README 给出的转录用法如下const events transcriber.transcribe({ audio: fileSource(recording).open(signal), signal })解码器在文件结束、解码失败、流被取消或signal中止时统一释放因为 abort 可能发生在没有任何 read 挂起时释放逻辑专门在start/cancel阶段注册监听而不能只依赖下一次 pull。实时源不能等待读者用 AudioInput 共享实时源麦克风无法等待慢读者。要把一个实时源分享给多个消费者例如同时喂给录音、转录和 VAD 检测器需要把它包进AudioInput。每个消费者用各自的signal订阅第一个订阅者打开源最后一个离开的订阅者停止轨道连接关闭后如果权限结果迟到返回其轨道会被立即停止该行为有专门测试覆盖见 packages/audio/src/browser.browser.test.ts 中的 stops a late permission result 用例。录音 WAV 编码的完整示例README 给出的经典组合用法如下capture来自proj-airi/pipelines-audioimport { microphoneSource } from proj-airi/audio/browser import { encodeWav } from proj-airi/audio/encoding import { AudioInput, capture } from proj-airi/pipelines-audio const input new AudioInput(microphoneSource({ audio: { echoCancellation: true } }), { historyMs: 360 }) // Start this in the click handler. getUserMedia starts before the first await. const recording capture(input) const wav encodeWav(recording.stream, { sampleRate: 16000, channels: 1 }) // Call finish when the control is released. Call cancel to discard the interval. await recording.finish() showPreview(await wav)关键点capture(input)立即订阅recording.stream是一个区间内的连续 PCM 流encodeWav(stream, { sampleRate, channels })边到达边编码为 16-bit WAVsampleRate/channels由WavOptions声明packages/audio/src/encoding/media-file.ts提供商通常期望 16 kHz 单声道recording.finish()密封已接受的音频并关闭流recording.cancel(reason)则丢弃该区间并对流报错两者都只影响本次 capture输入的其他订阅者不受影响capture还提供started: Promiseboolean首个块到达时 resolve true无音频结束时 resolve false与done: PromiseOutcomeAudioRange | undefinedOutcome取值为finished/cancelled/failed三态。capture 的连续性与背压保护从 packages/pipelines-audio/src/capture.ts 的pump可以看到capture内部以from位置重放保留历史让检测触发前就已开始的语音也能进入区间并做两项强约束——区间内出现sourceId变化或帧号不连续就判为 Audio source has a gap 而失败当stream未读音频超过maxBufferedMs默认 60 秒时该 capture 单独失败而不是无限增长内存。这与AudioInput.subscribe的maxBufferedMs背压策略packages/pipelines-audio/src/audio-input.ts一脉相承实时源不能等慢读者慢的那个读者独自失败源和其他读者继续。桥接只认 MediaStream 的 APItoMediaStream很多浏览器 API如 Web Speech recognition只接受MediaStream而不接受 PCM 流。此时用toMediaStream(frames, context, signal)packages/audio/src/browser/media-stream.ts把 PCM 灌进新建轨道const { media, done } toMediaStream(pcmStream, audioContext, signal)done在 PCM 流结束且音频实际播放完成后 resolve中止signal立即停止输出返回的轨道属于本次调用settle 时会自动停止若播放期间AudioContext被关闭输出会失败并释放轨道——测试 fails native output and releases its tracks when the audio context closes 验证了这一点packages/audio/src/browser.browser.test.ts。播放Playback 组 BrowserPlayback 驱动播放层的分工同样是“编排 驱动”Playback来自proj-airi/pipelines-audio管理独立队列组BrowserPlayback来自proj-airi/audio/browser实现PlaybackDriver用audioContext.currentTime作为播放时钟packages/audio/src/browser/playback.ts。README 给出的用法import { BrowserPlayback } from proj-airi/audio/browser import { Playback } from proj-airi/pipelines-audio const playback new Playback(new BrowserPlayback(audioContext)) const group playback.openGroup(answer) void group.enqueue({ id: first, audio: audioBlob }) const receipt await group.stop({ fadeMs: 100 })三个值得强调的语义README 原意 源码印证stop 回执跟随音频时钟与源完成事件不依赖 UI 定时器。AudioOutputpackages/audio/src/browser/audio-output.ts按上下文时钟逐个背靠背调度AudioBufferSourceNodeonended事件驱动的渲染区间、以及淡出用的linearRampToValueAtTime(0, end)都锚定在context.currentTime上浏览器挂起或关闭上下文时会走contextChanged路径让待决播放和静音回执失败而不是报出假静音。startAtMs与回执interval都基于该时钟单位毫秒。PlayedAudio.interval给出首末渲染采样的时钟时刻调用方可以据此安排后续输出例如“在上一条结尾 2 秒后开始下一条”。调用方拥有audioContext并在消费者结束之后关闭它。BrowserPlayback只借用人家的上下文不负责关闭。此外播放组OutputGroup见 packages/pipelines-audio/src/playback.ts还具备finish()排空队列stop({ fadeMs })关闭入场、丢弃排队中的 clip 并等待真实静音单个 clip 的中止只取消它自己的音频不牵连同组其余队列组间互不混流、不互相调度声道PlaybackReceipt汇总每个实际起播 clip 的throughMs与interval。PCM 与 WAV格式转换的底层细节Float32 ↔ PCM16 互转packages/audio/src/encoding/wav.ts 提供两个纯函数toPCM16FromFloat32(samples)把归一化 Float32 采样转成小端有符号 PCM16 字节越界值会被 clamp 到 [-1, 1]负半轴乘0x8000正半轴乘0x7FFFtoFloat32FromPCM16(pcmBytes)反向转换并要求输入必须包含完整的 16-bit 样本否则抛TypeError。对应的边界行为都有 Vitest 用例锁定packages/audio/src/encoding/wav.test.ts越界输入如 ±2会被钳制到 ±32768/±32767非完整样本输入会被拒绝。WAV 封装与流式编码toWav/toWavFromPCM16手工构造标准 44 字节 RIFF/WAVE 头fmtdata16-bit PCM、单/双声道可选并把 PCM16 数据填入toWAVBase64则直接返回 base64。而面向实时录音的encodeWav走的是边到达边编码的流式路径它用 Mediabunny 的AudioSampleSource对每个PcmBlock做重采样与混音transform: { sampleRate, numberOfChannels }平面数据先经过planar()展平为 interleaved Float32最后产出Blob { type: audio/wav }见 packages/audio/src/encoding/media-file.ts 的encodeWav。面向提供商的流式 PCM16Pcm16Encoder如果提供商要的是裸 PCM16 字节而非 WAV 容器可用Pcm16Encoderpackages/audio/src/encoding/pcm-stream.ts由 packages/audio/src/encoding/index.ts 导出。它在每次 reader pull 时推进一次转换绝不整段收集必要时用alexanderolsen/libsamplerate-js做 SRC_SINC_FASTEST 单声道重采样逐块混音、转 PCM16 并enqueue。实现里有一处值得注意的细节由于该库的 wrapper 没有暴露end_of_input标志文件结束时用零扩展zero extension冲刷重采样滤波器的尾延迟同时保持“接受的时长不超发”。测试与类型检查ChecksREADME 明确给出质量门槛按以下命令执行pnpm -F proj-airi/audio typecheck pnpm -F proj-airi/audio test:run其中typecheck对应tsc --noEmit见 packages/audio/package.json 的 scripts。测试分层明确Node 测试覆盖 WAV 编码与文件解码如 packages/audio/src/encoding/wav.test.ts、packages/audio/src/encoding/media-file.test.ts浏览器测试覆盖 Web Audio、麦克风共享、迟到权限、轨道清理集中在 packages/audio/src/browser.browser.test.ts 与 packages/audio/src/encoding/pcm-stream.browser.test.ts编排层的公开测试则覆盖 capture 独立性、延迟历史回放、源缺口、文件终结、取消与播放回执见 packages/pipelines-audio/src 下的audio-input.test.ts、capture相关测试与playback.test.ts。一个典型用例可以直观展示“最后订阅者离开才停麦”的行为packages/audio/src/browser.browser.test.ts两个订阅者先后abort第一个离开时轨道仍为live第二个离开后轨道才变为ended且microphone.stream回到undefined。小结proj-airi/audio用清晰的接口切分了 AIRI 的音频落地AudioSource统一“能产生 PCM 的东西”AudioInputcapture解决实时源的多消费者共享与区间采集BrowserPlaybackPlayback把播放编排锚定在音频时钟上encodeWav/Pcm16Encoder则完成面向文件与提供商的格式转换。配合proj-airi/pipelines-audio的观察窗口、播放组与流控maxBufferedMs背压这套设计既能支撑实时语音聊天也能服务于离线文件转录而所有音频之外的对话状态都交给上层 Agent 管线处理。赞分享人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手【免费下载链接】airi 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器目标是接近 Neuro-sama 的高度支持实时语音聊天、Minecraft 和 Factorio 游玩支持 Web / macOS / Windows。项目地址https://gitcode.com/moeru-ai/airi点击查看免费下载相关推荐paseo 双向音频实战指南基于 expo-two-way-audio 的 PCM 实时语音采集与播放paseo 双向音频实战指南基于 expo two way audio 的 PCM 实时语音采集与播放 导读 本文围绕 paseo 仓库中 packagesSuper Productivity Web Audio音频处理与播放Super Productivity Web Audio音频处理与播放 引言为什么生产力工具需要音频反馈 在现代生产力应用中音频反馈Audio Fee前端桌面应用移动开发NAudio RawSourceWaveStream 实战指南裸 PCM 与压缩音频的播放、WAV 封装与格式转换NAudio RawSourceWaveStream 实战指南裸 PCM 与压缩音频的播放、WAV 封装与格式转换 RawSourceWaveStream 是音视频音频处理上一篇VTracer完全指南5分钟掌握开源图像矢量化神器下一篇efinance 实战手册用 Python 一行代码获取股票、基金、债券、期货全市场行情数据创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考