标题
这是个很好的问题!让我先回答你的两个核心问题,然后设计方案。
解答你的两个问题
问题1:如何获取面试官语音并转文字?
方案:系统音频捕获 + 麦克风捕获
在 macOS 上,需要同时捕获两个音频源:
- 麦克风:你自己(面试者)说话
- 系统音频(Loopback audio):面试官通过 Zoom/Teams/Meet 传来的声音
macOS 不允许直接录制系统音频,需要借助虚拟音频驱动,常见方案:
- BlackHole(免费,推荐):虚拟声卡,将系统输出路由到可录制的输入
- Loopback(付费):更强大的音频路由工具
- SoundFlower(老牌,已停止维护)
语音转文字方案:
- Apple Speech Recognition(本地,免费,支持中文/英文,低延迟)
- Whisper(OpenAI,本地运行精度高,但有延迟)
- AssemblyAI / Deepgram(云端,实时流式转写,准确率高)
问题2:如何区分面试官和面试者?
方案对比:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 双音频流分离 ⭐推荐 | 麦克风=你,系统音频=对方 | 天然区分,最准确 | 需要配置 BlackHole |
| 说话人识别(Speaker Diarization) | AI分析声纹区分多人 | 无需额外配置 | 需要云端API,有延迟 |
| 手动标记 | 用户按键切换"谁在说话" | 最简单 | 需要手动操作 |
| 声音方向/设备 | 外放=对方,耳麦=自己 | 简单 | 不稳定 |
推荐方案:双音频流分离(最可靠)
系统音频(BlackHole) → 转文字 → "面试官" → 发给 Claude AI
麦克风音频 → 转文字 → "面试者" → 显示但不处理
推荐技术栈:Tauri + Rust + React
现在我来设计这个 app 的架构图和 UI 原型:这是一个很有价值的架构设计问题。让我先画出完整的系统架构图,然后给出具体的技术方案:---
完整技术方案
核心原理:天然双流分离
麦克风 (CoreAudio) ──────► Stream A → STT → "面试者" → 仅显示
BlackHole虚拟声卡 ─────► Stream B → STT → "面试官" → 发给 Claude AI
Stream B 就是面试官的声音,天然分离,不需要 AI 做说话人识别。
技术栈选择
Tauri 2.0 + React(前端)+ Rust(音频/STT 后端)
| 模块 | 方案 | 说明 |
|---|---|---|
| 系统音频捕获 | BlackHole 虚拟声卡 | 免费,需用户一次性安装 |
| 麦克风捕获 | cpal(Rust crate) | 跨平台音频捕获 |
| 语音转文字 | Apple SFSpeechRecognizer(via Objective-C bridge) | 本地、实时、支持中英文,无网络延迟 |
| AI 回答 | Claude API(流式输出) | 边生成边显示 |
| UI 框架 | Tauri 2.0 + React | 原生 macOS 窗口,可置顶悬浮 |
关键代码思路
Rust 端:同时打开两个音频流
// 伪代码
let mic_stream = open_device("Built-in Microphone"); // 面试者
let sys_stream = open_device("BlackHole 2ch"); // 面试官
// 分别送入各自的 STT pipeline
mic_stream -> stt_pipeline_A -> emit("interviewee_text", text);
sys_stream -> stt_pipeline_B -> emit("interviewer_text", text);
// ↑ 只有这条会发给 Claude
前端路由逻辑
// 面试官的问题 → 发给 Claude
listen("interviewer_text", async ({ payload }) => {
const answer = await callClaude(payload.text);
setAiResponse(answer);
});
// 面试者的话 → 只显示字幕,不处理
listen("interviewee_text", ({ payload }) => {
setSubtitle(payload.text);
});
用户安装流程(一次性)
- 用户下载安装你的 App
- App 引导用户安装 BlackHole 2ch(一个 pkg 安装包)
- 引导用户在 macOS 音频 MIDI 设置 中创建「多输出设备」,同时输出到扬声器 + BlackHole
- 配置完成后,面试官声音会同时出现在:扬声器(能听到)+ BlackHole(App 可录制)
可选:备选方案(不需要 BlackHole)
如果你不想要用户安装额外驱动,可以用 AssemblyAI 的 Speaker Diarization:
所有音频 → AssemblyAI 实时 API
↓
返回: [Speaker A: "请介绍一下你自己"] ← 识别为面试官
[Speaker B: "我是..."] ← 识别为面试者
缺点是有网络延迟(约 1-2s),且需要付费 API。
推荐路径
- 第一阶段:先用 BlackHole 双流方案,准确率高、无延迟
- 第二阶段:加入手动标记功能("当前说话人是谁"的切换按钮),作为备用
- 长远:如果想支持纯线下面试(面对面),再考虑 Whisper + Speaker Diarization
需要我帮你搭建具体的 Tauri 项目结构或核心代码吗?