Repository navigation
Conversation
…formance # Conflicts: # openless-all/ios/scripts/test-audio.sh
原来的「保留原始录音(调试)」只会落一份处理后的 WAV,没法判断某个识别问题是 麦克风原始信号差,还是降噪/增益/AGC 处理坏了。 Core: - `UserPreferences` 新增 `debug_archive_processed` / `debug_archive_raw`,两个独立 开关,默认都关;旧 preferences.json 走 serde default,不迁移。 - `RecordingPlan` 增加 `archive_processed` / `archive_raw`,由上面两个开关推出。 quick note / undecided 这类功能必需档案始终 `archive_processed = true`,不受开关 影响;调试录音两个开关都关时 `archive_successful_recording = false`(不落盘)。 - 新增 `openless_core::pcm_f32_to_i16_le`,让宿主把「处理前」的交织 f32 帧也量化 成同格式 PCM,与处理器内部规则一致。 - 偏好注册表补两条(Portable),云同步与契约测试同步跟上。 linux-egui: - 主档案 `<session>.wav` 按 `archive_processed` 决定写处理后还是原始 PCM;只有两份 都要时才额外创建 `<session>.raw.wav`。 - 音频处理线程在处理前就拿到原始帧,因此不做常态额外分配:`raw` 只在开关打开时 才量化。 - 设置页在「保留原始录音(调试)」下面新增两个开关:保存处理后的音频 / 另存一份 处理前的音频;打开主开关且两项都关时自动勾上「处理后」,避免主开关看起来没效果。 五语文案补在 i18n 目录里。 测试:`dictation_context` 新增用例覆盖四种开关组合与 quick note 例外; `openless-core --lib` 1391 passed,`openless-linux-egui --lib` 266 passed, `clippy -D warnings` 干净。
MicYou 的 DSP 参数是 8 项:增益 / 声学回声消除 / 降噪 / 去混响 / 自动增益 / 人声活动检测 / 处理链路 / 缓冲区大小。Core 的 `AudioDspConfig` 一直缺 AEC 与 缓冲区,而且因为 `processing_order` 只有 6 个阶段、`settings()` 里用 `..Default::default()` 兜底,MicYou 的 `processing_chain` 里根本没有 "AEC" 这一级 —— 即使把 `aec_enabled` 打开也不会执行。 Core: - `AudioDspStage` 新增 `AcousticEchoCancellation`(链名 "AEC"), `processing_order` 由 6 级变 7 级,默认顺序与 MicYou 一致(AEC 在最前)。 - `AudioDspConfig` 新增 `aec_enabled` 与 `output_buffer_ms`(100..=1200,默认 300), `normalized()` 夹紧范围,`settings()` 映射到 micyou 的 `AudioDspSettings`。 - `processing_order` 反序列化兼容老配置:6 级数组把 AEC 前置补齐,其余顺序原样 保留(既符合 AEC 必须在降噪之前,也不会制造重复项触发「重复即重置」)。 - 测试:老 6 级配置反序列化保留自定义顺序 + AEC 前置;AEC/缓冲区能到达 DSP 设置;越界缓冲夹到 1200。 linux-egui: - 设置 → 录音 → 语音增强设置新增「回声消除与缓冲」段:AEC 开关(附「需要扬声器 参考信号」说明)+ 输出缓冲区 100–1200 ms 滑杆(附延迟代价说明)。 - 处理链路列表把 AEC 作为一级显示,未开启时置灰;五语文案补齐。 这样 MicYou 的 8 项参数在 egui 前端全部可达。
Core 侧已经支持 `aec_enabled` / `output_buffer_ms`(并真正传给 MicYou 的 `processing_chain`),Tauri 的设置页还停在 6 级链路: - `AudioDspStage` 类型加 `acousticEchoCancellation`,`AudioDspConfig` 加 `aecEnabled` / `outputBufferMs`,`defaultAudioDsp` 同步成 7 级链路(AEC 在最前)。 - `AudioDspSettings.tsx` 新增「回声消除」开关(附「需要扬声器参考信号」说明)与 输出缓冲 100–1200 ms 滑杆;处理顺序列表自动多出 AEC 一级。 - 八语文案补齐(en / zh-CN / zh-TW / ja / ko / de / fr / es),`UserPreferences` 类型与 mock 数据跟上 Core 新增的两个调试录音字段。 验证:`tsc --noEmit` 干净、`vite build` 成功、`frontend-test-runner` 全过。
AEC 打开却一直没效果:MicYou 需要「扬声器此刻在放什么」当参考,而宿主从来没有 把输出信号喂进来。这次把整条链补上。 Core: - 新增 `FarEndQueue`:宿主在独立采集线程里 push(任意采样率/通道),DSP 线程在 每帧处理前 drain。队列按 96k 样本(约 2 秒 @48k)限长,丢最旧的;非有限值与 空帧直接丢弃。 - `AudioProcessor::attach_far_end(queue)` + `process()` 里把参考重采样到 48 kHz 单声道后交给 `micyou set_far_end_audio`(MicYou 用最近历史做自适应滤波,所以 参考必须先于本帧到达)。 - `RecordingPlan` 增加 `aec_reference_device_name`,来自新增偏好 `aec_reference_device_name`(空 = 自动,`"off"` = 不采集;注册表记为 Excluded/local_hardware,不同步到云端)。 linux-egui: - `is_loopback_device_name()` 识别 PulseAudio/PipeWire 的 `<sink>.monitor`、 `Monitor of …` 与 ALSA `snd-aloop`;带单测确认普通麦克风不会被误判。 - `list_loopback_devices()` 按 `pipewire → pulseaudio → alsa` 顺序枚举去重。 - `start_reference_capture()`:另起一条 cpal 采集流抓回环,带自己的启动握手与 停止标志;同样按三后端顺序尝试,ALSA 无回环时只记一条 warn —— 录音本身照常。 - 录音侧:AEC 打开且参考未被显式关闭时创建队列、挂到处理器、启动参考采集; `LinuxActiveRecording` 持有它并在 Drop 时**先**停参考流再拆主线程。 - 设置页:语音增强里 AEC 打开后出现「扬声器参考源」下拉(自动 / 不采集 / 具名 监听设备),五语文案补齐。 验证(MicYou micyou-dsp 已含 AEC7 模型,beta 上无冲突改动): ``` openless-core --lib 1399 passed openless-linux-egui --lib 268 passed localization_contract 5 passed clippy -D warnings clean deb/rpm verify PASS(2.0.2-Beta.2-146) ```
之前 `process()` 在 `enhancement_enabled == false` 时直接走归一化,AEC 永远不执行 —— 但「外放听写、只想去回声、不想动音色」正是最需要 AEC 的场景。 现在只要 AEC 打开且本次录音接了参考队列,就仍然走 MicYou 链路(其余阶段由各自 开关控制,不会顺带改音色)。 顺带记录实测:单核上 aec7 模型约 2.0x 实时(≈50% 单核),NLMS 类方案约 31x (≈3%),差 16 倍 —— 弱机上要用轻量参考抵消,别默认开模型。
「能不能两者都留、让用户选」——可以,而且实测下来两者正好互补。
- 新增 `AecMode { Off, Light, Model }` 取代原来的布尔 `aec_enabled`;
老 preferences 里的 `aecEnabled: true/false` 由自定义反序列化映射成
`Light`/`Off`,不会丢配置。
- `Light`:把 Open-Less#1228 的有界归一化 LMS(1024 抽头、双讲时暂停自适应)移植进来,
在 Core 里对麦克风原始样本做抵消,再进降噪/增益链。
- `Model`:仍走 MicYou 的 aec7 神经网络(经 `FarEndQueue` → `set_far_end_audio`)。
- 两种模式共用同一套参考采集(linux-egui 的 `<sink>.monitor` 与扬声器选择)。
关键修正:早先轻量路径把参考重采样到 16 kHz 却拿 48 kHz 的麦克风样本做抵消,
采样率不一致导致 ERLE ≈ 0(等于没做)。现在参考按麦克风采样率重采样。
实测(单核,120 s 音频,单讲段,回声延迟 15 ms、衰减 0.45):
| 模式 | ERLE | 实时倍率 | 单核占用 |
|---|---:|---:|---:|
| 关闭 | 0 dB | 5719x | ~0% |
| 轻量 NLMS | **22.9 dB** | 6.2x | **~16%** |
| aec7 模型 | **26.9 dB** | 1.9x | **~53%** |
模型只多 4 dB,却贵 3.3 倍 —— 所以默认关、弱机推轻量、强机可选模型。
UI:egui 与 Tauri 的语音增强里都改成三选一(关闭 / 轻量(省 CPU)/ 模型(更强)),
选模型时提示 CPU 代价;八语 + 五语文案补齐。
对照 MicYou 的实现逐个核对后,界面上三处名不副实: 1. **均衡器**:用户不需要,整块从 egui 设置里移除(`equalizer_*` 配置字段保留, 旧 preferences 仍能反序列化;处理链路里的 Equalizer 阶段默认不启用)。 2. **去混响**:MicYou 的 `apply_dereverb` 其实是 `x[n] - mix·x[n-10ms]` —— 一个 固定延迟的**梳状滤波**,不是去混响。界面改名为「梳状滤波(简易去混响)」, 提示里写清它会给音色染金属感、真正去混响需要频谱或模型方法(当前没做)。 3. **人声检测**:MicYou 的 `apply_vad` 是 RMS 门限 + 淡入淡出**噪声门**,不输出 语音/非语音判定。界面改名为「噪声门限(不是人声检测)」,并说明低于门限只是 衰减、不会停止录音。 结果:设置页现在只暴露真实存在且自解释的开关,不再有「看起来有、其实没做」的项。
把 egui 侧已经跑通的两件事在 Tauri 侧补齐(Linux 上 Tauri crate 被
`compile_error!` 拦住,只能靠 CI 的 macOS/Windows job 真编,这里做了 rustfmt
语法校验 + 前端 tsc/vite/144 个前端测试)。
一、AEC 的扬声器参考(`start_reference_stream`)
- 与 egui 同样的思路:拿宿主**默认输出设备**开一条 capture 流 —— Windows 的
WASAPI 允许在 render 设备上做 loopback,Linux 的 PulseAudio/PipeWire 把输出
映射到 monitor 源,macOS 走 CoreAudio 同一设备。
- 参考帧按设备原生采样率/通道推入 `openless_core::FarEndQueue`,由 Core 重采样
后交给 AEC(轻量 NLMS 与 aec7 模型都吃这一路)。
- 没有回环源时只记一条 warn,录音照常进行;移动端 stub 直接返回不可用。
- 参考流挂在 `Recorder` 上,跟录音同生命周期,Drop 即停。
二、处理前/处理后档案
- 新增 `ArchiveTargets { processed, raw, write_processed }` 与
`Recorder::start_with_archive_targets`。
- 关键约束:原始 PCM 只存在于 DSP 工作线程里,而 staged 路径的档案是
`commit` 时才创建的。所以引入 `RawTap`:`CaptureSink::promote` 把 tap 写进
共享槽,DSP 线程每帧读一次槽。
- 两份都要 → 主档案写处理后,另存 `<session>.raw.wav` 写原始;
- 只要处理前 → 主档案直接写原始 PCM(回退/播放功能不变,且不多出文件);
- 都不要 → 不落盘。
- `ForwardTarget.archive` 在 `write_processed == false` 时为 `None`,避免同一
段 PCM 被写两次。
三、Tauri 设置页
- 调试页在「保留原始录音(调试)」下面恢复两个开关(保存处理后的音频 / 另存
一份处理前的音频),打开主开关且两项都空时自动勾上「处理后」。
Open-Less#1275 与 Open-Less#1258 改的是同一套东西(`micyou-dsp`、`audio_dsp.rs`、 `AudioDspSettings.tsx`、`linux-egui/.../audio_dsp.rs`),两个 PR 并存只会互相踩。 Open-Less#1275 已关闭,内容并到这里。 冲突处理(16 个文件,19 处): - `AudioDspConfig`:保留 Open-Less#1258 的 Android AudioRecord effects(`android_*`), 叠加 7 级链路(AEC 在最前)、`aec_mode` 三选一与 `output_buffer_ms`; 均衡器档位夹取沿用 Open-Less#1258 的 ±15 dB。 - `AudioProcessor`:保留 Open-Less#1258 的 `EnhancedStream` 重构(`mono`/`working`/ `push_into` 免分配重采样),叠加 far-end 参考队列、轻量 NLMS 抵消器、 参考重采样到 48 kHz。 - `dsp_tests.rs`:两侧测试都留下。 - egui DSP 页:取我方(去掉十段均衡器、AEC 三选一、缓冲滑杆)。 - Tauri DSP 页:AEC 三选一 + 缓冲滑杆 + Android 系统效果入口全都保留。 - i18n 8 个语种:Android 效果的 `aec` 与链路的 `aec` 同键不同义,保留 Android 那个键名(其调用点未变),链路文案用 `aecHint`/`aecOff`/… 。 - `recorder.rs`:Android 监控发布 + 原始 PCM 落盘两侧都留。 同时修掉 Open-Less#1275 CI 暴露的、只有 macOS/Windows 才编得到的问题: - `Recorder` 里存 `cpal::Stream` 让整个 `Recorder` 变成 `!Send`,把 `MicrophoneMonitorState` 一起拖下水。参考流改为在音频线程内创建/释放, 与麦克风流一致。 - `parking_lot::Mutex` 用了 `std` 的 `unwrap_or_else(|poisoned| …)` 写法。 - `WarmCaptureSink`/`WarmCaptureHub::promote` 补上 `raw_tap` 参数,热麦克风 路径也能落原始 PCM;热流启动时把自己的 tap 槽交给 DSP 线程。 - `cpal::SampleRate` 是 newtype、`cpal::Device` 不实现 `Display`。 - `far_end` 在借用前被 move。 以及一个真正的行为 bug:调试录音总开关打开、两个子开关都没开时(老版本只有 总开关的配置),`archive_successful_recording` 变成 false,升级后原本在录调试 音频的用户会一段都录不到。现在「两个子开关都关」按「保存处理后」处理, `less_successful_asr_discards_only_non_debug_audio` 重新绿。
Collaborator
Author
|
把握手时提到的那批音频工作并进来了(原 #1275 已关闭)。#1275 和本 PR 改的是同一套文件 —— 并入的内容
冲突处理
顺带修的两个真问题
本地验证仍需 CI 兜底:Tauri 的 Windows/macOS 专属路径(WASAPI loopback / CoreAudio)只能在 CI 编译验证。若在 macOS 上 |
scripts/test-recorder-module.py 会生成一个只导出少量 core 符号的临时 crate, / / 没加进去,Windows 的 `openless-recorder-tests` 编译因此失败(7 个 E0425/E0433)。本地 `--check` 与完整跑 60 个测试均通过。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem and behavior
Microphone enhancement was repeatedly allocating temporary buffers and calculating sinc kernels per output sample. This increases processing cost, especially alongside local ASR on mobile devices.
Reuse shared processing buffers and cache bounded rational resampling phases. Preserve the original filter, including at native 48 kHz, to keep the denoiser input unchanged. Add baseline x86-64 SSE and ARM64 NEON projection kernels with separate multiply/add and unchanged accumulation order. Software DSP stays at 48 kHz; ASR still receives 16 kHz mono PCM16. Android requests 48 kHz capture and retains a native-format CPAL fallback. Both egui and Tauri expose all six EQ presets and the expanded parameter ranges.
Complete MicYou audio-settings parity with six EQ presets, expanded parameter ranges, optional Android session-bound system AEC/NS/AGC, and opt-in raw/processed FFT monitoring. Monitoring is throttled and expires when the UI stops renewing it. Capture queues are not enlarged. AEC7 remains unexposed without a synchronized speaker reference; playback-buffer tuning is not applied to ASR. Preserve the pinned upstream importer and refresh its reviewed adapter patch.
Validation
audio_performanceexample is included.Additional platform UI and numerical comparison
Add native iOS audio settings for the enhancement toggle, all software denoisers, gain/AGC, gate, comb filter, ten-band EQ/preamp, six EQ presets and processing order. Connect these persisted settings to the native Rust recorder with session-frozen processing and a stop/drain flush; default enhancement remains off. Writes are blocked while recording. Tauri (Windows/macOS/Android), Harmony's shared settings, and Linux egui retain their applicable settings and presets. Android system effects remain platform-specific.
Reuse input conversion scratch in the external PCM recorder and bounded, exact-capacity output PCM scratch in the native iOS Swift actor. Add a frozen Swift converter oracle and byte-for-byte tests over up to 32 format/ownership/packetization combinations and flush. CI exposed pre-existing frame loss in the original variable-packet converter (15040 instead of 16000 samples for 44.1 kHz); honor requested input frame counts with a cursor and drain available output to avoid dropping packet suffixes. Both fixed and variable new packetizations must match the complete, original fixed-100ms waveform, with the duration assertion retained. These Apple SDK tests require CI execution, not claimed as locally passed.
Independently built the same
audio_equivalenceprobe against baselinef0744e10and the optimized Rust sources. On Linux x86-64 all 48 synthetic cases produced byte-identical PCM: 0 differing cases, 0 LSB maximum error, identical lengths. Cases cover 16/44.1/48 kHz, mono/stereo, all four denoiser modes, combined effects, silence/noise/tones/impulses, irregular chunks and a partial final frame. Per-case hashes and diagnostic timings are inscripts/fixtures/audio-equivalence-2026-10-24.json. The comparator's negative controls reject a one-LSB mutation and truncation. External recorder regressions and strict Core Clippy passed locally.No real-device test, listening study, speech-recognition quality evaluation, or battery measurement was performed. Android system-effect quality remains OEM-dependent. Native Apple changes require Apple CI validation. Existing CI workflows are unchanged.