docs/scripts(kaisa): add doctor verify mode and HDMI readiness checks
Add a more detailed --verify flow to capture Jack/ELD/subdevices, route per sink, and collect kernel error windows. Improve --fix with readiness gating, retries, and connected-only selection; document single-monitor pcm mapping behavior and ignore local logs/artifacts. Made-with: Cursor
This commit is contained in:
10
.gitignore
vendored
10
.gitignore
vendored
@@ -8,6 +8,16 @@ chromiumos_kernel_v5.15_git_backup.tar.xz
|
||||
# 本地基线(含 topology / tar,体积与隐私不宜入库)
|
||||
audio_topology/baseline-stash/
|
||||
|
||||
# 运行日志与一次性采集物(勿提交)
|
||||
/_logs/
|
||||
/scripts/_logs/
|
||||
/report.txt
|
||||
/audio_topology/*.txt
|
||||
/audio_topology/new_reboot/
|
||||
|
||||
# 本地个人命令记录(勿提交)
|
||||
/常用命令
|
||||
|
||||
# 本机 sound/soc 等对照 diff(可选放入 reference/chromeos-ubuntu-sound-diffs/)
|
||||
reference/chromeos-ubuntu-sound-diffs/diff-u_*.txt
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ workflowType: epics
|
||||
### Story 3.1:OPERATION 覆盖 pro-audio / EBUSY / IEC958 / 全自动
|
||||
|
||||
作为一名 **Kaisa 用户**,
|
||||
我希望 **OPERATION_PipeWire_Kaisa_UCM_HiFi.md** 说明 **UCM2 / HiFi**、**`speaker-test -D pulse`**、**IEC958',N**、以及安装器脚本(disable + install),
|
||||
我希望 **OPERATION_PipeWire_Kaisa_UCM_HiFi.md** 说明 **UCM2 / HiFi**、**`speaker-test -D pipewire`**、**IEC958',N**、以及安装器脚本(disable + install),
|
||||
以便 **排障不靠口口相传**(**FR5**)。
|
||||
|
||||
**验收标准:**
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
| 文档 | 说明 |
|
||||
| ---- | ---- |
|
||||
| [linux-hdmi/OPERATION_PipeWire_Kaisa_UCM_HiFi.md](linux-hdmi/OPERATION_PipeWire_Kaisa_UCM_HiFi.md) | **主路线(交付)**:UCM2、HiFi、`60-kaisa-ucm.lua`、Jack-driven 端口、IEC958、安装 / 验收 / 排障 |
|
||||
| [linux-hdmi/DEBUG_Reboot_No_Sound_and_Fix.md](linux-hdmi/DEBUG_Reboot_No_Sound_and_Fix.md) | 排障记录:重启后无声的根因链路(WirePlumber 状态恢复 / `set_hw_params`)与 `--fix` 一键恢复 |
|
||||
| [linux-hdmi/LOCAL_Root_Cause_and_Fix.md](linux-hdmi/LOCAL_Root_Cause_and_Fix.md) | **本机判因(替代 Live)**:`kaisa-audio-doctor.sh` 流程、证据表、安装顺序、与 DEBUG 文档衔接 |
|
||||
| [linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md](linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md) | **根因深读**:`default-routes` 极低 HDMI 音量(机制 A 实测)与 `set_hw_params`(机制 B)分工 |
|
||||
| [README.md](README.md) | 本目录说明 |
|
||||
|
||||
**脚本(仓库根 `scripts/`)**:禁用 pro-audio 片段 — [`disable-kaisa-pro-audio-wireplumber.sh`](../scripts/disable-kaisa-pro-audio-wireplumber.sh);安装 UCM overlay — [`install-kaisa-ucm-overlay.sh`](../scripts/install-kaisa-ucm-overlay.sh)。
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
| 文档 | 内容 |
|
||||
| ---- | ---- |
|
||||
| [linux-hdmi/OPERATION_PipeWire_Kaisa_UCM_HiFi.md](linux-hdmi/OPERATION_PipeWire_Kaisa_UCM_HiFi.md) | **主路线(交付)**:UCM2、HiFi、Jack-driven 端口、IEC958、安装/验收/排障 |
|
||||
| [linux-hdmi/DEBUG_Reboot_No_Sound_and_Fix.md](linux-hdmi/DEBUG_Reboot_No_Sound_and_Fix.md) | 排障记录:重启后无声与 `--fix` 恢复的证据与判因路径 |
|
||||
| [linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md](linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md) | 根因深读:`default-routes` 极低 HDMI 音量 vs `set_hw_params` |
|
||||
|
||||
附属文件:`linux-hdmi/wireplumber/50-kaisa-sof-rt5682-hdmi.conf`、`linux-hdmi/systemd-user/kaisa-hdmi-iec958-pipewire.service`。
|
||||
附属文件:`linux-hdmi/wireplumber/50-kaisa-sof-rt5682-hdmi.conf`、`linux-hdmi/systemd-user/kaisa-hdmi-iec958-pipewire.service`(旧 pro-audio 示例)、**`linux-hdmi/systemd-user/kaisa-ucm-hifi-boot-fix.service`(重启必现无声时登录自动 `--fix`)**。
|
||||
|
||||
仓库根 [README.md](../README.md) · 路径表 [REPO_INDEX.md](../REPO_INDEX.md)。
|
||||
|
||||
97
docs/linux-hdmi/DEBUG_Reboot_No_Sound_and_Fix.md
Normal file
97
docs/linux-hdmi/DEBUG_Reboot_No_Sound_and_Fix.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 排障记录:重启后无声(sof-rt5682 / UCM HiFi)与 `--fix` 恢复
|
||||
|
||||
本文把一次已复现的问题“落盘”:**系统重启后没有声音**,但运行仓库脚本的 **`--fix` 模式后恢复有声**。目标是让后续排障有统一的证据与判因路径。
|
||||
|
||||
适用:Ubuntu + PipeWire + WirePlumber,声卡为 `sof-rt5682`,并已按主交付文档启用 **UCM2 + HiFi**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 现象
|
||||
|
||||
- **重启后无声**:桌面看起来仍是 HiFi/HDMI,但测试音无声。
|
||||
- **运行修复工具后恢复**:执行 `./scripts/kaisa-audio-doctor.sh --fix` 后恢复有声。
|
||||
|
||||
---
|
||||
|
||||
## 2. 关键结论(根因链路)
|
||||
|
||||
该问题至少包含两类机制:
|
||||
|
||||
- **机制 A(确定)**:WirePlumber 会在重启后按持久化状态恢复默认路由/默认音量(`~/.local/state/wireplumber/default-nodes`、`default-routes`)。如果恢复到“错误的默认输出”或“极小音量”,就会表现为“无声”。
|
||||
- **本机已量化的一例**:`default-routes` 里 HDMI1 端口的 `channelVolumes` 曾被持久化为约 **0.063**(6% 线性增益),冷启动后等价于「几乎无声」;Port1 仍为 1.0。详见 [ROOTCAUSE_Reboot_Silent_Analysis.md](ROOTCAUSE_Reboot_Silent_Analysis.md)。
|
||||
- **机制 B(不稳定点)**:HDMI1(常见为 `pcm=2`)在某些时刻会出现驱动层打开失败(PipeWire 日志出现 `set_hw_params: 输入/输出错误`),即使 **Jack=on / ELD 有效** 也可能无声。
|
||||
|
||||
`--fix` 模式之所以能“立刻恢复”,本质是把上述两类状态做了一次“强制收敛”:
|
||||
|
||||
- 重启用户音频栈(清理 error 节点与链路)
|
||||
- 强制 `HiFi` profile
|
||||
- 按 **Jack/ELD** 选择输出(只尝试 Jack=on 的 HDMI)
|
||||
- 取消静音、把音量拉到 1.0
|
||||
- 打开对应 `IEC958',N`
|
||||
- `pw-play` 做最短试播闭环
|
||||
|
||||
---
|
||||
|
||||
## 3. 如何采集证据(重启后第一时间)
|
||||
|
||||
重启后,先不要手工乱改设置,直接跑一次诊断:
|
||||
|
||||
```bash
|
||||
cd "/home/jack/文档/chromebox_10th_audio_driver"
|
||||
./scripts/kaisa-audio-doctor.sh
|
||||
```
|
||||
|
||||
重点关注这几段输出:
|
||||
|
||||
- **WirePlumber state**:
|
||||
- `~/.local/state/wireplumber/default-nodes`
|
||||
- `~/.local/state/wireplumber/default-routes`
|
||||
- **HDMI Jack states + ELD controls**:
|
||||
- `HDMI/DP,pcm=2/3/4 Jack` 是否为 on/off
|
||||
- `ELD device=2/3/4` 是否有 bytes(为空通常代表该路未就绪)
|
||||
- **PipeWire 日志**:
|
||||
- 是否出现 `set_hw_params: 输入/输出错误`
|
||||
|
||||
---
|
||||
|
||||
## 4. 一键恢复(可重复执行)
|
||||
|
||||
```bash
|
||||
cd "/home/jack/文档/chromebox_10th_audio_driver"
|
||||
./scripts/kaisa-audio-doctor.sh --fix
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `--fix` 会尽量只对“确实插线”的 HDMI(Jack=on)尝试输出;若所有 HDMI Jack 都是 off,会回退到 Port1(模拟)。
|
||||
- `--fix` 会把音量拉到 1.0 并取消静音,避免“看似路由正确但音量太小”的假无声。
|
||||
|
||||
---
|
||||
|
||||
## 5. 重启后**必现**无声:自动收敛(可选)
|
||||
|
||||
若每次冷启动进桌面后都要手跑 `--fix` 才有声,多半是 **机制 A**(WirePlumber 把默认 sink / 路由恢复到不合适的状态)或 **机制 B**(错误节点需重启栈清空)。可以用登录后 **user systemd oneshot** 自动执行与 `--fix` 相同的逻辑,无需改内核。
|
||||
|
||||
1. 仓库已提供:
|
||||
- `scripts/kaisa-audio-boot-fix.sh`(等待 `pactl` 就绪后调用 doctor)
|
||||
- `scripts/kaisa-audio-doctor.sh --fix-only`(只做修复、不写完整诊断报告,适合开机跑)
|
||||
2. 单元模板:[systemd-user/kaisa-ucm-hifi-boot-fix.service](systemd-user/kaisa-ucm-hifi-boot-fix.service)(内附 `sed` 安装说明)。
|
||||
|
||||
**副作用**:每次登录会 **重启用户 PipeWire 栈一次** 并 **把默认 sink 音量拉到 1.0**(与手动 `--fix` 相同)。若你希望保留「仅诊断、不重启」,不要启用该单元,继续手跑 `./scripts/kaisa-audio-doctor.sh --fix`。
|
||||
|
||||
---
|
||||
|
||||
## 6. LiveCD 验证如何使用本工具
|
||||
|
||||
在 Ubuntu Live 环境中(临时安装 UCM overlay 后),同样可以运行:
|
||||
|
||||
```bash
|
||||
./scripts/kaisa-audio-doctor.sh
|
||||
./scripts/kaisa-audio-doctor.sh --fix
|
||||
```
|
||||
|
||||
用于判断问题属于:
|
||||
|
||||
- **Live 原生就存在**(偏内核/硬件/显示链路/线材),还是
|
||||
- **仅安装后出现**(偏 WirePlumber 状态恢复、策略或配置)。
|
||||
|
||||
135
docs/linux-hdmi/LOCAL_Root_Cause_and_Fix.md
Normal file
135
docs/linux-hdmi/LOCAL_Root_Cause_and_Fix.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 本机定位根因与修复方案(替代 LiveCD)
|
||||
|
||||
LiveCD 未通过或不便使用时,在**已安装的 Ubuntu + PipeWire** 本机上用仓库脚本与下表即可判因;本机可长期保留安装与 WirePlumber 持久化状态,证据比 Live 更完整。
|
||||
|
||||
**必读**:主交付步骤见 [OPERATION_PipeWire_Kaisa_UCM_HiFi.md](OPERATION_PipeWire_Kaisa_UCM_HiFi.md);历史与 IEC958 对照见 [HISTORY.md](../../HISTORY.md)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 前置条件
|
||||
|
||||
- 在**桌面会话**、**普通用户**下操作(**不要** `sudo ./scripts/kaisa-audio-doctor.sh`)。若以 root 运行,`systemctl --user` 会离线,`pactl` 可能无法连接,日志会误报「无声音」。
|
||||
- 仓库根目录执行:`./scripts/kaisa-audio-doctor.sh`(可加 `-o ./_logs/my.log` 指定报告路径)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 判因流程(与计划一致)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
start[本机桌面用户运行 doctor]
|
||||
checkFiles{系统路径 UCM+Lua 存在?}
|
||||
checkUcm{alsaucm 列出 HiFi?}
|
||||
checkPactl{pactl 有 HiFi profile?}
|
||||
branchInstall[安装: disable pro-audio + install overlay]
|
||||
branchUcm[修 UCM: sof-rt5682.conf / HiFi.conf]
|
||||
branchWp[修 WirePlumber: 60-kaisa-ucm.lua 与互斥片段]
|
||||
branchRuntime[运行时: doctor --fix / 查 state / journal]
|
||||
start --> checkFiles
|
||||
checkFiles -->|否| branchInstall
|
||||
checkFiles -->|是| checkUcm
|
||||
checkUcm -->|否| branchUcm
|
||||
checkUcm -->|是| checkPactl
|
||||
checkPactl -->|否| branchWp
|
||||
checkPactl -->|是| branchRuntime
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 证据与修复对照表
|
||||
|
||||
| 本机证据 | 根因归纳 | 修复方向 |
|
||||
|----------|----------|----------|
|
||||
| `/usr/share/alsa/ucm2/conf.d/sof-rt5682/sof-rt5682.conf`、`GoogleKaisa/.../HiFi.conf`、`/usr/share/wireplumber/main.lua.d/60-kaisa-ucm.lua` **缺失** | 未安装 overlay(与 Live 仅跑一键未 `--install` 同类) | 见下文 **§4 安装顺序** |
|
||||
| `alsaucm -c sof-rt5682` **import/parse 失败** | UCM 语法或入口未指向 `GoogleKaisa/.../HiFi.conf` | 对照仓库 [`reference/ucm2/`](../../reference/ucm2/README.md);[HISTORY.md](../../HISTORY.md) §3.2:`SectionVerb` + `TQ "HiFi"`、避免 `disdevall` 等导致 PipeWire 不注册 profile |
|
||||
| `alsaucm` **有 HiFi**,`pactl list cards` **无 `HiFi:`** | ACP 未走 UCM,或与强制 pro-audio 片段互斥 | [`scripts/disable-kaisa-pro-audio-wireplumber.sh`](../../scripts/disable-kaisa-pro-audio-wireplumber.sh)(必要时 `--system`);重装 [`scripts/install-kaisa-ucm-overlay.sh`](../../scripts/install-kaisa-ucm-overlay.sh);`systemctl --user restart wireplumber pipewire pipewire-pulse` |
|
||||
| **`HiFi:` 已存在**,但无声 / 切换异常 / journal 有 `set_hw_params: 输入/输出错误` | WirePlumber 恢复的默认路由错误,或某路 HDMI PCM 打开失败 | [DEBUG_Reboot_No_Sound_and_Fix.md](DEBUG_Reboot_No_Sound_and_Fix.md);**`./scripts/kaisa-audio-doctor.sh --fix`**;仅插一根 HDMI 试 pcm=3/4 再切默认 sink |
|
||||
|
||||
---
|
||||
|
||||
## 4. 未安装时的推荐安装顺序(本机)
|
||||
|
||||
仅在 §3 第一行命中时执行:
|
||||
|
||||
```bash
|
||||
cd "/path/to/chromebox_10th_audio_driver"
|
||||
./scripts/disable-kaisa-pro-audio-wireplumber.sh
|
||||
# 若曾在 /etc/wireplumber 装过 kaisa 片段:
|
||||
# ./scripts/disable-kaisa-pro-audio-wireplumber.sh --system
|
||||
|
||||
./scripts/install-kaisa-ucm-overlay.sh
|
||||
# 脚本会在可用时重启用户 PipeWire 栈;否则登录会话后手动:
|
||||
# systemctl --user restart wireplumber pipewire pipewire-pulse
|
||||
|
||||
./scripts/kaisa-audio-doctor.sh
|
||||
```
|
||||
|
||||
然后按 [OPERATION](OPERATION_PipeWire_Kaisa_UCM_HiFi.md) §3 验收 `alsaucm` / `pactl list cards`。
|
||||
|
||||
---
|
||||
|
||||
## 5. HiFi 已生效时的「重启后无声 / HDMI 打开失败」
|
||||
|
||||
1. 先读 [DEBUG_Reboot_No_Sound_and_Fix.md](DEBUG_Reboot_No_Sound_and_Fix.md)(机制 A:持久化路由;机制 B:`set_hw_params`),以及 [ROOTCAUSE_Reboot_Silent_Analysis.md](ROOTCAUSE_Reboot_Silent_Analysis.md)(**机制 A 实测**:`default-routes` 里 HDMI 端口 `channelVolumes≈0.06` 导致假无声)。
|
||||
2. 查看 `~/.local/state/wireplumber/default-nodes`、`default-routes`(重点 **`grep channelVolumes`**)与 `journalctl --user -u pipewire -b`。
|
||||
3. **首选一键恢复**:`./scripts/kaisa-audio-doctor.sh --fix` 在本机实测中**执行后即可恢复正常出声与路由**(重启栈 + 强制 HiFi + 按 Jack 选路 + IEC958 + 试播);若问题复现,可**重复执行**,不必先改 UCM。
|
||||
|
||||
```bash
|
||||
./scripts/kaisa-audio-doctor.sh --fix
|
||||
```
|
||||
|
||||
**重启必现、不想每次手跑**:见 [DEBUG_Reboot_No_Sound_and_Fix.md §5](DEBUG_Reboot_No_Sound_and_Fix.md) — 使用 `scripts/kaisa-audio-boot-fix.sh` + [systemd-user/kaisa-ucm-hifi-boot-fix.service](systemd-user/kaisa-ucm-hifi-boot-fix.service) 在登录后自动执行与 `--fix` 等价的收敛(有音量拉满、重启音频栈等副作用,与手动 `--fix` 相同)。
|
||||
|
||||
### 5.1 重要澄清:物理 HDMI 口 ≠ 固定的 ALSA pcm 编号
|
||||
|
||||
在 **jack-Kaisa** 上观察到一个容易误判的现象:
|
||||
|
||||
- 仅连接 **一个** 显示器时,即使把同一根 HDMI 线插到不同物理口,系统也可能始终表现为 **`pcm=2 Jack=on`**,并在 `pactl list cards` 中显示 **`HDMI/DP,pcm=2` available**。
|
||||
- 只有在连接 **两个** 显示器时,才更容易同时出现 `pcm=2` 与 `pcm=3` 两路都 `Jack=on/available`(从而可对比两路的稳定性差异)。
|
||||
|
||||
因此,做 A/B 验证时不要用「插 HDMI1 / 插 HDMI2」直接类比为「测试 `pcm=2` / `pcm=3`」;请以 **Jack/ELD 实测**为准:
|
||||
|
||||
```bash
|
||||
amixer -c0 cget "iface=CARD,name='HDMI/DP,pcm=2 Jack'"
|
||||
amixer -c0 cget "iface=CARD,name='HDMI/DP,pcm=3 Jack'"
|
||||
amixer -c0 cget "iface=CARD,name='HDMI/DP,pcm=4 Jack'"
|
||||
```
|
||||
|
||||
(可选)再看 ELD 是否非空:
|
||||
|
||||
```bash
|
||||
amixer -c0 cget "iface=PCM,name='ELD',device=2"
|
||||
amixer -c0 cget "iface=PCM,name='ELD',device=3"
|
||||
amixer -c0 cget "iface=PCM,name='ELD',device=4"
|
||||
```
|
||||
|
||||
`values=on` + `ELD values>0` 的 pcm,才是当前实际连着显示器的输出链路。
|
||||
|
||||
---
|
||||
|
||||
## 6. 交付物(便于他人复核)
|
||||
|
||||
1. `./_logs/kaisa-audio-doctor_*.log`(修复前后各一份更佳)。
|
||||
2. 注明命中 §3 中哪一行,以及是否已执行 §4 / §5。
|
||||
|
||||
---
|
||||
|
||||
## 7. 本仓库所在机器一次实测摘要(2026-04-09)
|
||||
|
||||
在 **jack-Kaisa** 上以用户 `jack` 运行 `kaisa-audio-doctor.sh` 的报告见:
|
||||
|
||||
- `_logs/kaisa-audio-doctor_local-plan-jack_20260409_130048.log`
|
||||
|
||||
**归类**:§3 **第四行**(运行时 / 驱动打开失败),**不是**「未安装」分支。
|
||||
|
||||
**依据**:
|
||||
|
||||
- 三处系统文件均存在;`alsaucm` 列出 `HiFi`;`pactl` **活动配置为 HiFi**,多路 `HiFi__hw_sofrt5682_*__sink` 已注册。
|
||||
- `journalctl` 中 **`HiFi__hw_sofrt5682_2__sink`**(HDMI1 / pcm=2)反复出现 **`set_hw_params: 输入/输出错误`**;默认 sink 曾指向该节点时易表现为无声或节点 `error`。
|
||||
- `~/.config/wireplumber/.../50-kaisa-sof-rt5682-hdmi.conf.disabled` 已存在,pro-audio 强制片段已禁用。
|
||||
|
||||
**修复结果**:随后执行 **`kaisa-audio-doctor.sh --fix`** 后,**本机实际使用已恢复正常**(与 DEBUG 文档中「`--fix` 可立刻恢复有声」的结论一致)。说明当时问题主要是 **栈内错误节点 / 默认 sink 与 Jack 路由未收敛**,而不是「必须改 UCM 才能好」。
|
||||
|
||||
**若日后再次无声**:可再跑 `--fix`;若 journal 仍反复出现某一路 `set_hw_params: 输入/输出错误`,再按 DEBUG 文档做单 HDMI、换口、对照 [HISTORY.md](../../HISTORY.md) IEC958。
|
||||
|
||||
`--fix` 运行记录:`_logs/kaisa-audio-doctor_local-plan-jack-fix_20260409_130131.log`。
|
||||
@@ -80,7 +80,7 @@ pactl set-card-profile "$(pactl list cards short | awk '/cml_rt5682/ {print $2;
|
||||
### 3.4 出声测试(走 PipeWire)
|
||||
|
||||
```bash
|
||||
speaker-test -D pulse -c2 -t sine -f 440 -l 3
|
||||
speaker-test -D pipewire -c2 -t sine -f 440 -l 3
|
||||
```
|
||||
|
||||
若你想强制把测试音打到某一路(避免默认 sink 干扰),先取出 sink 名:
|
||||
@@ -177,6 +177,13 @@ speaker-test -D hw:0,3 -c2 -r48000 -t sine
|
||||
systemctl --user start pipewire pipewire-pulse wireplumber
|
||||
```
|
||||
|
||||
### 5.4 每次重启进桌面后都要手跑 `--fix` 才有声
|
||||
|
||||
多为 WirePlumber 持久化默认路由不当或 PipeWire 残留 error 节点;证据与判因见 [DEBUG_Reboot_No_Sound_and_Fix.md](DEBUG_Reboot_No_Sound_and_Fix.md)。
|
||||
|
||||
- **临时**:`./scripts/kaisa-audio-doctor.sh --fix`
|
||||
- **自动(接受与 `--fix` 相同副作用:重启用户音频栈、默认 sink 音量拉满)**:按 [DEBUG §5](DEBUG_Reboot_No_Sound_and_Fix.md) 安装 `kaisa-ucm-hifi-boot-fix.service`,使用 [`scripts/kaisa-audio-boot-fix.sh`](../../scripts/kaisa-audio-boot-fix.sh)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 卸载 / 回退 UCM overlay
|
||||
@@ -198,5 +205,5 @@ systemctl --user restart wireplumber pipewire pipewire-pulse
|
||||
./scripts/disable-kaisa-pro-audio-wireplumber.sh
|
||||
```
|
||||
|
||||
仓库仍保留相关 pro-audio 脚本仅供对照与临时排障;仓库主交付以本文为准。
|
||||
本仓库已收敛为 UCM/HiFi 主线;pro-audio 仅作为概念与历史背景出现在 `HISTORY.md`。
|
||||
|
||||
|
||||
67
docs/linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md
Normal file
67
docs/linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# 根因分析:重启后「无声」与本机状态对照
|
||||
|
||||
本文在已启用 **UCM HiFi** 的前提下,把「重启必现无声 / 手跑 `--fix` 又好」拆成**可验证**的两条链路,并给出比「整栈重启」更轻的验证手段。
|
||||
|
||||
---
|
||||
|
||||
## 1. 机制 A(本机已证实):WirePlumber 持久化了**极低的路由音量**
|
||||
|
||||
WirePlumber 把每张声卡、每个**输出端口**的 `channelVolumes` 写在:
|
||||
|
||||
`~/.local/state/wireplumber/default-routes`
|
||||
|
||||
在本机(jack-Kaisa)与历史日志 `kaisa-audio-doctor_local-plan-jack_20260409_130048.log` 中,同一文件里出现:
|
||||
|
||||
- **HDMI1**(UCM 端口名在文件里常编码为 `\oOut\c\sHDMI1`):`channelVolumes=0.062991671264172;0.062991671264172`(约 **6.3%** 线性增益,主观上接近无声)
|
||||
- **Port1**:`channelVolumes=1.0;1.0`(正常)
|
||||
|
||||
冷启动后 WirePlumber **按该文件恢复**各端口的音量;若当前默认输出或某条路由落在 HDMI1 上,就会表现为 **「路由看起来对、就是没声」**。这与 [DEBUG_Reboot_No_Sound_and_Fix.md](DEBUG_Reboot_No_Sound_and_Fix.md) 中的**机制 A**一致,并把「极小音量」落实为**可 grep 的数字**。
|
||||
|
||||
**`--fix` 为何能立刻好**:`kaisa-audio-doctor.sh` 的 `apply_fix` 会对当前默认 sink 执行 `wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0`,相当于把**当前输出**从持久化的低音量里拉出来;若再配合切到 Jack=on 的 HDMI / 重启栈,就会把「错路由 + 低音量 + error 节点」一并收敛。
|
||||
|
||||
**更轻的验证(不必先重启栈)**:
|
||||
|
||||
```bash
|
||||
grep -n channelVolumes ~/.local/state/wireplumber/default-routes
|
||||
wpctl get-volume @DEFAULT_AUDIO_SINK@
|
||||
```
|
||||
|
||||
若 `default-routes` 里某 HDMI 行 `< 0.2` 而你觉得应该能听见,可先:
|
||||
|
||||
```bash
|
||||
wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0
|
||||
# 或在声音设置里把对应输出滑条拉高,观察 default-routes 是否被写回合理值
|
||||
```
|
||||
|
||||
**根因追问(仍开放)**:为何 HDMI1 会被写成 ~0.06?可能是 GNOME/滑条与 WirePlumber 路由音量的交互、某次误触、或显示刻度与存储线性值不一致。**产品侧**可在 deb 文档中建议用户检查该文件;**工程侧**可评估是否在登录后只做「路由音量下限裁剪」而非整栈重启(需单独实现与测试)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 机制 B(日志已见):某路 HDMI PCM `set_hw_params` 打开失败
|
||||
|
||||
`journalctl --user -u pipewire` 中出现:
|
||||
|
||||
- `spa.alsa: set_hw_params: 输入/输出错误`
|
||||
- `alsa_output....HiFi__hw_sofrt5682_2__sink`(pcm=2 / HDMI1)`suspended -> error`
|
||||
|
||||
即 **驱动/时序层** 在打开 `hw:0,2` 时失败。脚本里已优先尝试 pcm=3/4 再 2(见 `detect_available_pcm` 注释)。这与 DEBUG 中的**机制 B**一致。
|
||||
|
||||
**根因追问**:需内核/SOF/HDMI 音频路径上进一步对照(单线插拔、EDID、HWE 小版本),超出用户态 deb 能「根治」的范围。
|
||||
|
||||
---
|
||||
|
||||
## 3. `default-nodes` 在说什么(辅助理解)
|
||||
|
||||
`~/.local/state/wireplumber/default-nodes` 里常见多行 `default.configured.audio.sink.N=...`,表示曾配置过的默认 sink 候选;**当前真正生效的默认 sink** 仍以 `pactl info` / `wpctl status` 为准。机制 A 的「假无声」可以发生在 **default-nodes 指向 Port1,但某应用或路由仍走 HDMI1 且音量为 0.06** 的组合下,因此排查时 **务必看 `default-routes` 里各端口的 channelVolumes**。
|
||||
|
||||
---
|
||||
|
||||
## 4. 与交付策略的关系
|
||||
|
||||
| 手段 | 针对 |
|
||||
|------|------|
|
||||
| 查 / 清 / 修正 `default-routes` 中的低 `channelVolumes` | **机制 A**(用户态、可文档化) |
|
||||
| `--fix` / 登录 oneshot | **A + B** 一并收敛(重、但稳) |
|
||||
| 内核/驱动跟进 | **机制 B** 的根本缩小 |
|
||||
|
||||
继续「找根本原因」时,建议优先在本机复现一次重启后、无声当下执行 **§1 的 grep**,确认是否仍为 HDMI 某行 `channelVolumes≈0.06`;若是,则**首要根因已落在 WirePlumber 持久化音量**,再决定是否做轻量修复脚本或上游 issue(WirePlumber/GNOME 音量模型)。
|
||||
24
docs/linux-hdmi/systemd-user/kaisa-ucm-hifi-boot-fix.service
Normal file
24
docs/linux-hdmi/systemd-user/kaisa-ucm-hifi-boot-fix.service
Normal file
@@ -0,0 +1,24 @@
|
||||
# 重启后若「必现无声」,可启用本 user 单元:登录后自动执行与
|
||||
# kaisa-audio-doctor.sh --fix 相同的收敛(实际调用 scripts/kaisa-audio-boot-fix.sh)。
|
||||
#
|
||||
# 安装(把下面 @REPO@ 换成仓库绝对路径,或复制后手工改 ExecStart 一行):
|
||||
# mkdir -p ~/.config/systemd/user
|
||||
# sed "s|@REPO@|$HOME/文档/chromebox_10th_audio_driver|g" kaisa-ucm-hifi-boot-fix.service > ~/.config/systemd/user/kaisa-ucm-hifi-boot-fix.service
|
||||
# chmod +x .../scripts/kaisa-audio-boot-fix.sh .../scripts/kaisa-audio-doctor.sh
|
||||
# systemctl --user daemon-reload
|
||||
# systemctl --user enable --now kaisa-ucm-hifi-boot-fix.service
|
||||
#
|
||||
# 日志:~/.local/state/kaisa-audio/last-boot-fix.log(或环境变量 KAISA_BOOT_FIX_LOG)
|
||||
#
|
||||
[Unit]
|
||||
Description=Kaisa sof-rt5682: post-login audio convergence (UCM HiFi, same as doctor --fix)
|
||||
After=wireplumber.service pipewire-pulse.service pipewire.service
|
||||
Wants=wireplumber.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=@REPO@/scripts/kaisa-audio-boot-fix.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
84
next.md
84
next.md
@@ -1,83 +1,25 @@
|
||||
## LiveCD 验证:UCM(HiFi / Jack-driven)方案
|
||||
# 下一步(建议以 docs 主线为准)
|
||||
|
||||
目标:在 Ubuntu Live 环境里验证 **本仓库的 UCM2 + HiFi** 是否能实现:
|
||||
本仓库当前主交付已收敛到 **UCM/HiFi(Jack-driven HDMI)** 主线;操作与验收请优先阅读:
|
||||
|
||||
- HDMI **插入才显示为输出**
|
||||
- HDMI **测试音正常**、切换正常
|
||||
- `docs/linux-hdmi/OPERATION_PipeWire_Kaisa_UCM_HiFi.md`
|
||||
|
||||
### 1) Live 原生基线(不装任何东西)
|
||||
排障与根因分析:
|
||||
|
||||
目的:确认 Live 的内核/驱动本身没把 HDMI 搞死(排除“纯驱动级”问题)。
|
||||
- `docs/linux-hdmi/DEBUG_Reboot_No_Sound_and_Fix.md`
|
||||
- `docs/linux-hdmi/LOCAL_Root_Cause_and_Fix.md`
|
||||
- `docs/linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md`
|
||||
|
||||
建议的一键诊断(在桌面会话、普通用户下运行):
|
||||
|
||||
```bash
|
||||
uname -a
|
||||
aplay -l
|
||||
amixer -c0 sset 'IEC958',0 on
|
||||
amixer -c0 sset 'IEC958',1 on
|
||||
amixer -c0 sset 'IEC958',2 on
|
||||
speaker-test -D pulse -c2 -t sine -f 440 -l 2
|
||||
./scripts/kaisa-audio-doctor.sh --verify
|
||||
./scripts/kaisa-audio-doctor.sh --fix --verify
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 若 `-D pulse` 不通,再用 ALSA 直连(例如 `speaker-test -D hw:0,3 ...`),但**不要在 PipeWire 运行时**直连 `hw:0,N`,否则会遇到 `EBUSY` 或把状态搅乱。
|
||||
|
||||
### 2) 在 Live 里临时安装本仓库 UCM 方案
|
||||
|
||||
有两种方式,选你方便的:
|
||||
|
||||
- **方式 A(推荐)**:把仓库放到另一只 U 盘/同一只启动盘的可写分区里,Live 启动后挂载再执行脚本
|
||||
- **方式 B**:Live 里 `git clone` 仓库(需要网络)
|
||||
|
||||
在仓库根目录执行:
|
||||
单显示器环境下建议使用“只测已连接输出”的验证,避免误测未连接的 HDMI PCM:
|
||||
|
||||
```bash
|
||||
./scripts/install-kaisa-ucm-overlay.sh
|
||||
systemctl --user restart wireplumber pipewire pipewire-pulse
|
||||
./scripts/kaisa-audio-doctor.sh --fix --verify --only-connected --retries 10
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- Live 环境通常**没有**你的 pro-audio 方案,所以一般**不需要**跑 `disable-kaisa-pro-audio-wireplumber.sh`(跑了也多半是空操作)。
|
||||
|
||||
### 3) 验收 UCM 是否真的生效(关键)
|
||||
|
||||
```bash
|
||||
alsaucm -c sof-rt5682 list _verbs
|
||||
pactl list cards
|
||||
```
|
||||
|
||||
预期:
|
||||
|
||||
- `alsaucm` 应列出 `HiFi`
|
||||
- `pactl list cards` 的 **配置文件** 里应出现 `HiFi:`
|
||||
|
||||
若 `HiFi:` 已出现但活动配置不是 HiFi,手动切一次:
|
||||
|
||||
```bash
|
||||
pactl set-card-profile "$(pactl list cards short | awk '/cml_rt5682/ {print $2; exit}')" HiFi
|
||||
```
|
||||
|
||||
### 4) 验收“插线才出现 HDMI” + 出声
|
||||
|
||||
- **拔掉 HDMI**:GNOME 输出列表应只剩模拟(或 HDMI 变不可用)
|
||||
- **插上 HDMI**:应出现 HDMI1/HDMI2(以及显示器名),并可用测试音验证
|
||||
|
||||
终端验收(可选):
|
||||
|
||||
```bash
|
||||
pactl list short sinks
|
||||
speaker-test -D pulse -c2 -t sine -f 440 -l 3
|
||||
```
|
||||
|
||||
### 5) 常见坑(Live 特有)
|
||||
|
||||
- **user systemd 不完整**:`systemctl --user restart ...` 失败时,注销/重登 Live 会话再试一次
|
||||
- **误用 ALSA 直连**:PipeWire 运行时对 `hw:0,N` 直连会 `EBUSY`;优先 `-D pulse`
|
||||
|
||||
---
|
||||
|
||||
如果需要我远程判定“Live 是否真正验证了 UCM 方案”,请贴:
|
||||
|
||||
- `pactl list cards`(包含“配置文件”那段)
|
||||
- 插拔 HDMI 前后 `pactl list short sinks` 的变化
|
||||
27
scripts/kaisa-audio-boot-fix.sh
Executable file
27
scripts/kaisa-audio-boot-fix.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# 登录后跑一次与 kaisa-audio-doctor --fix 相同的收敛,缓解「重启后 WirePlumber 恢复错误默认路由 /
|
||||
# PipeWire 残留 error 节点」导致的必现无声。供 systemd --user 调用。
|
||||
#
|
||||
# 环境变量:
|
||||
# KAISA_REPO 仓库根目录(未设置则从本脚本位置推断)
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="${KAISA_REPO:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/kaisa-audio"
|
||||
LOG="${KAISA_BOOT_FIX_LOG:-${STATE_DIR}/last-boot-fix.log}"
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
|
||||
if [[ "${EUID}" -eq 0 ]]; then
|
||||
echo "kaisa-audio-boot-fix: refuse to run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 等 PipeWire/Pulse 套接字就绪(冷启动桌面可能较慢)
|
||||
for _ in $(seq 1 45); do
|
||||
if [[ -n "${XDG_RUNTIME_DIR:-}" && -S "${XDG_RUNTIME_DIR}/pulse/native" ]] && pactl info &>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
exec "${REPO_ROOT}/scripts/kaisa-audio-doctor.sh" --fix-only -o "$LOG"
|
||||
800
scripts/kaisa-audio-doctor.sh
Executable file
800
scripts/kaisa-audio-doctor.sh
Executable file
@@ -0,0 +1,800 @@
|
||||
#!/usr/bin/env bash
|
||||
# Kaisa (sof-rt5682) audio one-shot diagnostics (UCM/HiFi mainline).
|
||||
# Collects high-signal state across ALSA/UCM2, PipeWire, WirePlumber, and policy/state files.
|
||||
set -euo pipefail
|
||||
|
||||
ts="$(date +%Y%m%d_%H%M%S)"
|
||||
out=""
|
||||
fix=0
|
||||
fix_only=0
|
||||
verify=0
|
||||
only_pcm=""
|
||||
retries=1
|
||||
only_connected=0
|
||||
log_dir="${KAISA_LOG_DIR:-./_logs}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/kaisa-audio-doctor.sh [--fix] [--fix-only] [--verify] [--only-pcm N] [--only-connected] [--retries N] [-o|--out /path/to/log]
|
||||
|
||||
Modes:
|
||||
default Diagnostics only (no changes)
|
||||
--fix Best-effort recovery, then full diagnostics report
|
||||
--fix-only Same recovery as --fix, then exit (for login/boot automation; use with -o for a short log)
|
||||
--verify More detailed verification (route/ports/playback + kernel error window), best used right after boot
|
||||
|
||||
Options:
|
||||
--only-pcm N Restrict fix/verify to a single PCM (0/2/3/4). Useful for single-monitor A/B tests.
|
||||
--only-connected Restrict fix/verify to HDMI PCMs that are currently connected (Jack=on + ELD non-empty).
|
||||
--retries N Playback retries per PCM (default 1). On failure, restart user audio services then retry.
|
||||
|
||||
Recovery steps:
|
||||
- restart user PipeWire/WirePlumber
|
||||
- set card profile to HiFi
|
||||
- choose an "available" HDMI port (prefer pcm=3/4, then pcm=2), else fallback to Analog (pcm=0)
|
||||
- enable matching IEC958 switch (pcm=2->IEC958,0; pcm=3->IEC958,1; pcm=4->IEC958,2)
|
||||
- quick playback test (timeout)
|
||||
|
||||
Notes:
|
||||
- Run as your desktop user (NOT via sudo).
|
||||
- This script assumes card name contains "cml_rt5682_def".
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--fix-only) fix=1; fix_only=1; shift ;;
|
||||
--fix) fix=1; shift ;;
|
||||
--verify) verify=1; shift ;;
|
||||
--only-pcm) only_pcm="${2:-}"; shift 2 ;;
|
||||
--only-connected) only_connected=1; shift ;;
|
||||
--retries) retries="${2:-}"; shift 2 ;;
|
||||
-o|--out) out="${2:-}"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--) shift; break ;;
|
||||
-*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
*)
|
||||
# Backward compatible: treat the first positional argument as output path.
|
||||
if [[ -z "${out}" ]]; then
|
||||
out="$1"
|
||||
shift
|
||||
else
|
||||
echo "Unexpected argument: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
out="${out:-${log_dir}/kaisa-audio-doctor_${ts}.log}"
|
||||
mkdir -p "$(dirname "$out")"
|
||||
|
||||
exec > >(tee "$out") 2>&1
|
||||
|
||||
hr() { printf '\n%s\n' "================================================================================"; }
|
||||
sec() { hr; printf '%s\n' "$1"; hr; }
|
||||
cmd() { printf '\n$ %s\n' "$*"; "$@"; }
|
||||
maybe() { printf '\n$ %s\n' "$*"; "$@" || true; }
|
||||
note() { printf '\n[NOTE] %s\n' "$*"; }
|
||||
warn() { printf '\n[WARN] %s\n' "$*"; }
|
||||
|
||||
is_int() { [[ "${1:-}" =~ ^[0-9]+$ ]]; }
|
||||
|
||||
get_kaisa_card_name() {
|
||||
pactl list cards short 2>/dev/null | awk '/cml_rt5682_def/ {print $2; exit}'
|
||||
}
|
||||
|
||||
amixer_find_numid() {
|
||||
# Find numid by matching a single control line from `amixer -c0 controls`.
|
||||
# Usage:
|
||||
# amixer_find_numid "iface=PCM" "name='ELD'" "device=2"
|
||||
# amixer_find_numid "iface=CARD" "name='HDMI/DP,pcm=2 Jack'"
|
||||
local want_iface="${1:-}"
|
||||
local want_name="${2:-}"
|
||||
local want_device="${3:-}"
|
||||
amixer -c0 controls 2>/dev/null | awk -v wi="$want_iface" -v wn="$want_name" -v wd="$want_device" '
|
||||
{
|
||||
ok=1
|
||||
if (wi!="" && $0 !~ wi) ok=0
|
||||
if (wn!="" && $0 !~ wn) ok=0
|
||||
if (wd!="" && $0 !~ wd) ok=0
|
||||
if (ok) {
|
||||
# line begins with: numid=XX,iface=...
|
||||
if (match($0, /numid=[0-9]+/)) {
|
||||
s=substr($0, RSTART, RLENGTH)
|
||||
sub(/^numid=/, "", s)
|
||||
print s
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
amixer_cget_by_control() {
|
||||
# Best-effort cget using stable identifiers; falls back to numid lookup.
|
||||
# For Jack: iface=CARD + exact name
|
||||
# For ELD: iface=PCM + name='ELD' + device=N
|
||||
local iface="${1:?iface}"
|
||||
local name="${2:?name}"
|
||||
local device="${3:-}"
|
||||
|
||||
local numid
|
||||
if [[ -n "$device" ]]; then
|
||||
numid="$(amixer_find_numid "iface=${iface}" "name='${name}'" "device=${device}" || true)"
|
||||
else
|
||||
numid="$(amixer_find_numid "iface=${iface}" "name='${name}'" "" || true)"
|
||||
fi
|
||||
if [[ -n "$numid" ]]; then
|
||||
maybe amixer -c0 cget "numid=${numid}"
|
||||
else
|
||||
warn "Could not find numid for iface=${iface} name='${name}' device=${device:-<none>}"
|
||||
fi
|
||||
}
|
||||
|
||||
read_jack_on_for_pcm() {
|
||||
# Returns 0 if Jack is on, 1 otherwise/unknown.
|
||||
# Uses stable name->numid resolution.
|
||||
local pcm="${1:?pcm}"
|
||||
local name="HDMI/DP,pcm=${pcm} Jack"
|
||||
local numid
|
||||
numid="$(amixer_find_numid "iface=CARD" "name='${name}'" "" || true)"
|
||||
[[ -n "$numid" ]] || return 1
|
||||
# Example output contains ": values=on/off"
|
||||
amixer -c0 cget "numid=${numid}" 2>/dev/null | awk '
|
||||
BEGIN { ok=0; seen=0 }
|
||||
$1==":" && $2 ~ /^values=/ {
|
||||
seen=1
|
||||
sub(/^values=/,"",$2)
|
||||
if ($2=="on" || $2=="1") ok=1
|
||||
else ok=0
|
||||
exit
|
||||
}
|
||||
END {
|
||||
if (seen && ok) exit 0
|
||||
exit 1
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
eld_bytes_len_for_pcm() {
|
||||
# Return byte count for ELD device=N (0 if empty/unknown).
|
||||
local pcm="${1:?pcm}"
|
||||
local numid
|
||||
numid="$(amixer_find_numid "iface=PCM" "name='ELD'" "device=${pcm}" || true)"
|
||||
[[ -n "$numid" ]] || { echo 0; return 0; }
|
||||
amixer -c0 cget "numid=${numid}" 2>/dev/null | awk '
|
||||
BEGIN { printed=0 }
|
||||
/values=/ {
|
||||
# Example: "; type=BYTES,...,values=36"
|
||||
if (match($0, /values=[0-9]+/)) {
|
||||
s=substr($0, RSTART, RLENGTH)
|
||||
sub(/^values=/,"",s)
|
||||
print s
|
||||
printed=1
|
||||
exit
|
||||
}
|
||||
}
|
||||
END { if (!printed) print 0 }
|
||||
' || true
|
||||
}
|
||||
|
||||
wait_for_hdmi_ready_pcm() {
|
||||
# Best-effort wait until HDMI pcm looks "ready enough":
|
||||
# - Jack ON
|
||||
# - ELD has bytes
|
||||
# - ALSA subdevices avail > 0 (if we can parse)
|
||||
local pcm="${1:?pcm}"
|
||||
local timeout_s="${2:-6}"
|
||||
local i
|
||||
for i in $(seq 1 "$timeout_s"); do
|
||||
if read_jack_on_for_pcm "$pcm"; then
|
||||
local eld
|
||||
eld="$(eld_bytes_len_for_pcm "$pcm" || echo 0)"
|
||||
local avail total
|
||||
avail="$(alsa_pcm_subdevices_available "$pcm" || true)"
|
||||
total="$(alsa_pcm_subdevices_total "$pcm" || true)"
|
||||
if [[ "$eld" -gt 0 ]]; then
|
||||
if [[ -n "$avail" && -n "$total" && "$total" -gt 0 ]]; then
|
||||
if [[ "$avail" -gt 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
# If we can't parse subdevices, treat Jack+ELD as enough.
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
connected_hdmi_pcms() {
|
||||
# Return space-separated HDMI PCMs that appear connected: Jack=on AND ELD has bytes.
|
||||
# Ordered by preference (3/4/2).
|
||||
local out=()
|
||||
local pcm
|
||||
for pcm in 3 4 2; do
|
||||
if read_jack_on_for_pcm "$pcm"; then
|
||||
local eld
|
||||
eld="$(eld_bytes_len_for_pcm "$pcm" || echo 0)"
|
||||
if [[ "$eld" -gt 0 ]]; then
|
||||
out+=("$pcm")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
echo "${out[*]-}"
|
||||
}
|
||||
|
||||
detect_available_pcm() {
|
||||
# Prefer HDMI2/HDMI3 over HDMI1 because pcm=2 is known flaky on some setups.
|
||||
# Returns one of: 3 4 2 0 (fallback to analog).
|
||||
local card_section
|
||||
card_section="$(pactl list cards 2>/dev/null | sed -n '/cml_rt5682_def/,+220p' || true)"
|
||||
if echo "$card_section" | awk 'BEGIN{ok=1} /pcm=3, available/ {ok=0} END{exit ok}'; then echo 3; return 0; fi
|
||||
if echo "$card_section" | awk 'BEGIN{ok=1} /pcm=4, available/ {ok=0} END{exit ok}'; then echo 4; return 0; fi
|
||||
if echo "$card_section" | awk 'BEGIN{ok=1} /pcm=2, available/ {ok=0} END{exit ok}'; then echo 2; return 0; fi
|
||||
echo 0
|
||||
}
|
||||
|
||||
alsa_pcm_subdevices_total() {
|
||||
# Return total subdevices for a given DEV from `aplay -l` (expects "子设备: X/Y" or "Subdevices: X/Y").
|
||||
local dev="${1:?dev}"
|
||||
aplay -l 2>/dev/null | awk -v dev="$dev" '
|
||||
$0 ~ ("device " dev ":") {inblk=1}
|
||||
inblk && ($0 ~ /子设备:/ || $0 ~ /Subdevices:/) {
|
||||
# formats: "子设备: 0/1" or "Subdevices: 0/1"
|
||||
gsub(/[^0-9\/]/,"",$0)
|
||||
split($0,a,"/")
|
||||
print a[2]
|
||||
exit 0
|
||||
}
|
||||
inblk && $0 ~ /^card [0-9]+:/ {inblk=0}
|
||||
'
|
||||
}
|
||||
|
||||
alsa_pcm_subdevices_available() {
|
||||
# Return available subdevices count X from X/Y for a given DEV.
|
||||
local dev="${1:?dev}"
|
||||
aplay -l 2>/dev/null | awk -v dev="$dev" '
|
||||
$0 ~ ("device " dev ":") {inblk=1}
|
||||
inblk && ($0 ~ /子设备:/ || $0 ~ /Subdevices:/) {
|
||||
gsub(/[^0-9\/]/,"",$0)
|
||||
split($0,a,"/")
|
||||
print a[1]
|
||||
exit 0
|
||||
}
|
||||
inblk && $0 ~ /^card [0-9]+:/ {inblk=0}
|
||||
'
|
||||
}
|
||||
|
||||
iec958_index_for_pcm() {
|
||||
case "${1:-}" in
|
||||
2) echo 0 ;;
|
||||
3) echo 1 ;;
|
||||
4) echo 2 ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
sink_name_for_pcm() {
|
||||
local pcm="${1:?pcm}"
|
||||
echo "alsa_output.pci-0000_00_1f.3-platform-cml_rt5682_def.HiFi__hw_sofrt5682_${pcm}__sink"
|
||||
}
|
||||
|
||||
sink_exists() {
|
||||
local sink="${1:?sink}"
|
||||
pactl list short sinks 2>/dev/null | awk -v s="$sink" '$2==s {found=1} END{exit !found}'
|
||||
}
|
||||
|
||||
kernel_tail_since_ts() {
|
||||
# Print kernel SOF/ASoC HDMI related lines since given timestamp.
|
||||
# ts format: "YYYY-MM-DD HH:MM:SS"
|
||||
local since_ts="${1:?since_ts}"
|
||||
journalctl -k -b --since "$since_ts" --no-pager 2>/dev/null | \
|
||||
grep -nE 'sof-audio|sof_ipc3_pcm_hw_params|ipc tx error|STREAM_PCM_PARAMS|ASoC error|set_hw_params|HDMI[0-9]|pcm[0-9]+' 2>/dev/null | \
|
||||
tail -n 200 || true
|
||||
}
|
||||
|
||||
restart_user_audio_services() {
|
||||
maybe systemctl --user restart pipewire pipewire-pulse wireplumber
|
||||
maybe sleep 2
|
||||
}
|
||||
|
||||
verify_one_pcm() {
|
||||
local pcm="${1:?pcm}"
|
||||
local since_ts="${2:?since_ts}"
|
||||
local sink
|
||||
sink="$(sink_name_for_pcm "$pcm")"
|
||||
|
||||
echo
|
||||
echo "=== VERIFY: pcm=$pcm ==="
|
||||
|
||||
if [[ "$pcm" -ne 0 ]]; then
|
||||
local jack="off"
|
||||
if read_jack_on_for_pcm "$pcm"; then jack="on"; fi
|
||||
local eld
|
||||
eld="$(eld_bytes_len_for_pcm "$pcm" || echo 0)"
|
||||
local avail total
|
||||
avail="$(alsa_pcm_subdevices_available "$pcm" || true)"
|
||||
total="$(alsa_pcm_subdevices_total "$pcm" || true)"
|
||||
echo "Jack=$jack ELD_bytes=$eld Subdevices=${avail:-?}/${total:-?}"
|
||||
fi
|
||||
|
||||
if ! sink_exists "$sink"; then
|
||||
warn "Sink missing in PipeWire: $sink"
|
||||
note "pactl list short sinks (for reference):"
|
||||
maybe pactl list short sinks
|
||||
return 2
|
||||
fi
|
||||
|
||||
note "Routing to sink: $sink"
|
||||
maybe pactl set-default-sink "$sink"
|
||||
maybe pactl info | sed -n '/默认音频入口/p'
|
||||
maybe wpctl set-mute @DEFAULT_AUDIO_SINK@ 0
|
||||
maybe wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0
|
||||
|
||||
local iec
|
||||
iec="$(iec958_index_for_pcm "$pcm")"
|
||||
if [[ -n "$iec" ]]; then
|
||||
maybe amixer -c0 sset "IEC958,${iec}" on
|
||||
fi
|
||||
|
||||
note "pw-play (attempt, 5s timeout)"
|
||||
local attempt=1
|
||||
local max_attempts="${retries}"
|
||||
if ! is_int "$max_attempts" || [[ "$max_attempts" -lt 1 ]]; then
|
||||
max_attempts=1
|
||||
fi
|
||||
local ok=0
|
||||
while [[ "$attempt" -le "$max_attempts" ]]; do
|
||||
note "pw-play attempt ${attempt}/${max_attempts}"
|
||||
if timeout -k 1s 5s pw-play /usr/share/sounds/alsa/Front_Center.wav; then
|
||||
note "pw-play: OK (exit 0)"
|
||||
ok=1
|
||||
break
|
||||
fi
|
||||
warn "pw-play: FAILED (non-zero exit)"
|
||||
if [[ "$attempt" -lt "$max_attempts" ]]; then
|
||||
note "Restarting user audio services before retry"
|
||||
restart_user_audio_services
|
||||
fi
|
||||
attempt=$((attempt+1))
|
||||
done
|
||||
|
||||
note "Kernel error window since $since_ts (tail)"
|
||||
local k
|
||||
k="$(kernel_tail_since_ts "$since_ts")"
|
||||
if [[ -n "$k" ]]; then
|
||||
warn "Kernel shows possible SOF/ASoC/HDMI issues in this window:"
|
||||
printf '%s\n' "$k"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$ok" -eq 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 3
|
||||
}
|
||||
|
||||
verify_audio() {
|
||||
sec "VERIFY mode (more detailed validation)"
|
||||
note "This runs active route + playback attempts and captures kernel error windows."
|
||||
note "Best run right after boot and BEFORE any manual --fix, to catch first-open races."
|
||||
|
||||
local since_ts
|
||||
since_ts="$(date '+%F %T')"
|
||||
echo "verify_start_ts=$since_ts"
|
||||
|
||||
sec "VERIFY snapshot (user services / routing / objects)"
|
||||
maybe systemctl --user status pipewire pipewire-pulse wireplumber --no-pager
|
||||
maybe pactl info
|
||||
maybe pactl list short cards
|
||||
maybe pactl list cards | sed -n '/cml_rt5682_def/,+220p'
|
||||
maybe pactl list short sinks
|
||||
maybe wpctl status
|
||||
|
||||
sec "VERIFY WirePlumber state files (default-*)"
|
||||
maybe ls -la ~/.local/state/wireplumber
|
||||
maybe sed -n '1,240p' ~/.local/state/wireplumber/default-profile
|
||||
maybe sed -n '1,240p' ~/.local/state/wireplumber/default-nodes
|
||||
maybe sed -n '1,240p' ~/.local/state/wireplumber/default-routes
|
||||
|
||||
sec "VERIFY ALSA readiness (Jack/ELD/subdevices)"
|
||||
maybe aplay -l
|
||||
echo
|
||||
echo "HDMI/DP Jack + ELD + Subdevices:"
|
||||
local pcm
|
||||
for pcm in 2 3 4; do
|
||||
local jack="off"
|
||||
if read_jack_on_for_pcm "$pcm"; then jack="on"; fi
|
||||
local eld
|
||||
eld="$(eld_bytes_len_for_pcm "$pcm" || echo 0)"
|
||||
local avail total
|
||||
avail="$(alsa_pcm_subdevices_available "$pcm" || true)"
|
||||
total="$(alsa_pcm_subdevices_total "$pcm" || true)"
|
||||
echo "pcm=$pcm Jack=$jack ELD_bytes=$eld Subdevices=${avail:-?}/${total:-?}"
|
||||
done
|
||||
|
||||
sec "VERIFY per-sink playback attempts (with kernel windows)"
|
||||
local preferred
|
||||
preferred="$(detect_available_pcm)"
|
||||
note "Order preference: detected=$preferred then 3,4,2 then 0"
|
||||
local pcms=()
|
||||
if [[ -n "$only_pcm" ]]; then
|
||||
pcms+=("$only_pcm")
|
||||
elif [[ "$only_connected" -eq 1 ]]; then
|
||||
local connected
|
||||
connected="$(connected_hdmi_pcms)"
|
||||
if [[ -n "$connected" ]]; then
|
||||
note "Restricting VERIFY targets due to --only-connected: ${connected}"
|
||||
# shellcheck disable=SC2206
|
||||
pcms+=($connected)
|
||||
else
|
||||
warn "--only-connected set but no connected HDMI PCMs detected; falling back to pcm=0"
|
||||
pcms+=(0)
|
||||
fi
|
||||
else
|
||||
pcms+=("$preferred")
|
||||
for pcm in 3 4 2 0; do
|
||||
[[ "$pcm" == "$preferred" ]] || pcms+=("$pcm")
|
||||
done
|
||||
fi
|
||||
|
||||
local ok_any=0
|
||||
local ok_pcm=""
|
||||
local ret
|
||||
for pcm in "${pcms[@]}"; do
|
||||
if [[ "$pcm" -ne 0 ]]; then
|
||||
if ! wait_for_hdmi_ready_pcm "$pcm" 6; then
|
||||
note "pcm=$pcm not ready (Jack/ELD/subdevices); skipping verify attempt"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
verify_one_pcm "$pcm" "$since_ts" || ret=$?
|
||||
ret="${ret:-0}"
|
||||
if [[ "$ret" -eq 0 ]]; then
|
||||
ok_any=1
|
||||
ok_pcm="$pcm"
|
||||
note "VERIFY succeeded on pcm=$pcm; stopping further attempts to avoid triggering flaky paths."
|
||||
break
|
||||
fi
|
||||
ret=""
|
||||
# Move the window forward so each attempt isolates new kernel lines.
|
||||
since_ts="$(date '+%F %T')"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
sec "VERIFY summary"
|
||||
if [[ "$ok_any" -eq 1 ]]; then
|
||||
note "At least one sink attempt completed with no kernel error lines in its window (pcm=${ok_pcm})."
|
||||
note "Default sink is kept at this successful pcm to preserve working audio."
|
||||
note "If you still have silence but no kernel errors: focus on routing/monitor input/volume persistence."
|
||||
else
|
||||
warn "No sink attempt was clean. If windows show ipc tx error -5 / hw_params failures, this points to kernel/SOF."
|
||||
fi
|
||||
}
|
||||
|
||||
apply_fix() {
|
||||
sec "FIX mode (best-effort recovery)"
|
||||
|
||||
if [[ "${EUID}" -eq 0 ]]; then
|
||||
warn "You are running as root. Fix mode must run as your desktop user (no sudo)."
|
||||
warn "Abort fix to avoid writing state into the wrong user session."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "${XDG_RUNTIME_DIR-}" || ! -d "${XDG_RUNTIME_DIR-}" ]]; then
|
||||
warn "XDG_RUNTIME_DIR is missing; likely not in a desktop session. Abort fix."
|
||||
return 1
|
||||
fi
|
||||
|
||||
note "Restarting user audio services"
|
||||
restart_user_audio_services
|
||||
|
||||
note "Post-restart quick state (forensics)"
|
||||
maybe pactl info | sed -n '/默认音频入口/p'
|
||||
maybe pactl list cards | sed -n '/cml_rt5682_def/,+120p'
|
||||
maybe aplay -l
|
||||
|
||||
local card
|
||||
card="$(get_kaisa_card_name)"
|
||||
if [[ -z "$card" ]]; then
|
||||
warn "Could not find card name (expected match: cml_rt5682_def). Abort fix."
|
||||
return 1
|
||||
fi
|
||||
|
||||
note "Forcing profile to HiFi on card: $card"
|
||||
maybe pactl set-card-profile "$card" HiFi
|
||||
maybe sleep 1
|
||||
|
||||
local preferred_pcm
|
||||
preferred_pcm="$(detect_available_pcm)"
|
||||
note "Detected available pcm (from port availability): $preferred_pcm (preference: 3/4/2, fallback 0)"
|
||||
|
||||
# Try a small ordered set. Start with "available" one, but if ALSA reports 0/1 subdevices or playback fails,
|
||||
# fall back to other HDMI PCMs (3/4/2) then analog (0).
|
||||
local candidates=()
|
||||
if [[ -n "$only_pcm" ]]; then
|
||||
candidates+=("$only_pcm")
|
||||
note "Restricting FIX candidates due to --only-pcm: $only_pcm"
|
||||
elif [[ "$only_connected" -eq 1 ]]; then
|
||||
local connected
|
||||
connected="$(connected_hdmi_pcms)"
|
||||
if [[ -n "$connected" ]]; then
|
||||
note "Restricting FIX candidates due to --only-connected: ${connected}"
|
||||
# shellcheck disable=SC2206
|
||||
candidates+=($connected)
|
||||
# Always keep analog fallback last, in case HDMI opens but remains silent.
|
||||
candidates+=(0)
|
||||
else
|
||||
warn "--only-connected set but no connected HDMI PCMs detected; falling back to pcm=0"
|
||||
candidates+=(0)
|
||||
fi
|
||||
else
|
||||
candidates+=("$preferred_pcm")
|
||||
for p in 3 4 2 0; do
|
||||
[[ "$p" == "$preferred_pcm" ]] || candidates+=("$p")
|
||||
done
|
||||
fi
|
||||
|
||||
local pcm sink iec avail total
|
||||
for pcm in "${candidates[@]}"; do
|
||||
# For HDMI outputs, only try PCMs whose Jack is currently ON.
|
||||
if [[ "$pcm" -ne 0 ]]; then
|
||||
if ! wait_for_hdmi_ready_pcm "$pcm" 6; then
|
||||
note "pcm=$pcm not ready (Jack/ELD/subdevices); skipping"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Skip clearly broken HDMI devices (subdevices available == 0).
|
||||
if [[ "$pcm" -ne 0 ]]; then
|
||||
avail="$(alsa_pcm_subdevices_available "$pcm" || true)"
|
||||
total="$(alsa_pcm_subdevices_total "$pcm" || true)"
|
||||
if [[ -n "$avail" && -n "$total" && "$total" -gt 0 && "$avail" -eq 0 ]]; then
|
||||
warn "pcm=$pcm appears unavailable at ALSA level (subdevices ${avail}/${total}); skipping"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
sink="alsa_output.pci-0000_00_1f.3-platform-cml_rt5682_def.HiFi__hw_sofrt5682_${pcm}__sink"
|
||||
if [[ "$pcm" -eq 0 ]]; then
|
||||
note "Trying fallback Analog (Port1) sink: $sink"
|
||||
else
|
||||
note "Trying HDMI pcm=$pcm sink: $sink"
|
||||
fi
|
||||
|
||||
maybe pactl set-default-sink "$sink"
|
||||
maybe pactl info | sed -n '/默认音频入口/p'
|
||||
maybe wpctl set-mute @DEFAULT_AUDIO_SINK@ 0
|
||||
maybe wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0
|
||||
|
||||
iec="$(iec958_index_for_pcm "$pcm")"
|
||||
if [[ -n "$iec" ]]; then
|
||||
note "Enabling IEC958 for pcm=$pcm -> IEC958,$iec"
|
||||
maybe amixer -c0 sset "IEC958,${iec}" on
|
||||
fi
|
||||
|
||||
note "Quick playback test on pcm=$pcm (timeout -k 1s 5s), retries=${retries}"
|
||||
local attempt=1
|
||||
local max_attempts="${retries}"
|
||||
if ! is_int "$max_attempts" || [[ "$max_attempts" -lt 1 ]]; then
|
||||
max_attempts=1
|
||||
fi
|
||||
local since_ts
|
||||
since_ts="$(date '+%F %T')"
|
||||
while [[ "$attempt" -le "$max_attempts" ]]; do
|
||||
note "pw-play attempt ${attempt}/${max_attempts} (pcm=$pcm)"
|
||||
if timeout -k 1s 5s pw-play /usr/share/sounds/alsa/Front_Center.wav; then
|
||||
note "Playback command returned success on pcm=$pcm"
|
||||
note "Post-play quick checks (kernel/user logs)"
|
||||
maybe journalctl --user -u pipewire -b --no-pager | tail -n 40
|
||||
local k
|
||||
k="$(kernel_tail_since_ts "$since_ts")"
|
||||
if [[ -n "$k" ]]; then
|
||||
warn "Kernel window since $since_ts shows possible SOF/ASoC issues:"
|
||||
printf '%s\n' "$k"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
warn "Playback test failed on pcm=$pcm (attempt ${attempt}/${max_attempts})"
|
||||
local k
|
||||
k="$(kernel_tail_since_ts "$since_ts")"
|
||||
if [[ -n "$k" ]]; then
|
||||
warn "Kernel window since $since_ts shows possible SOF/ASoC issues:"
|
||||
printf '%s\n' "$k"
|
||||
fi
|
||||
if [[ "$attempt" -lt "$max_attempts" ]]; then
|
||||
note "Restarting user audio services before retry"
|
||||
restart_user_audio_services
|
||||
since_ts="$(date '+%F %T')"
|
||||
fi
|
||||
attempt=$((attempt+1))
|
||||
done
|
||||
warn "Playback test failed on pcm=$pcm; trying next candidate"
|
||||
done
|
||||
|
||||
warn "Fix mode could not find a working sink automatically. Check cables/monitor input and re-run with only one HDMI plugged."
|
||||
return 1
|
||||
}
|
||||
|
||||
sec "Kaisa audio doctor (sof-rt5682) — report: $out"
|
||||
maybe uname -a
|
||||
maybe date
|
||||
maybe id
|
||||
|
||||
sec "Session sanity (THIS OFTEN EXPLAINS 'no sound')"
|
||||
echo
|
||||
echo "If you run this as root / without a logged-in desktop session:"
|
||||
echo "- systemctl --user will be offline"
|
||||
echo "- /run/user/\$UID may not exist"
|
||||
echo "- PipeWire/WirePlumber won't be running"
|
||||
echo "- ALSA may show 'no soundcards found'"
|
||||
echo
|
||||
echo "Current:"
|
||||
maybe bash -lc 'echo "USER=$USER UID=$UID HOME=$HOME XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR-}"'
|
||||
maybe bash -lc 'if [ -n "${XDG_RUNTIME_DIR-}" ]; then ls -ld "${XDG_RUNTIME_DIR}" 2>/dev/null || true; else echo "XDG_RUNTIME_DIR is empty"; fi'
|
||||
maybe bash -lc 'test -S "${XDG_RUNTIME_DIR-}/pipewire-0" && echo "pipewire socket: OK" || echo "pipewire socket: MISSING"'
|
||||
maybe bash -lc 'test -S "${XDG_RUNTIME_DIR-}/pulse/native" && echo "pulse native socket: OK" || echo "pulse native socket: MISSING"'
|
||||
echo
|
||||
if [[ "${EUID}" -eq 0 ]]; then
|
||||
echo "WARNING: running as root (EUID=0). For best results, run as your desktop user WITHOUT sudo:"
|
||||
echo " ./scripts/kaisa-audio-doctor.sh"
|
||||
fi
|
||||
|
||||
if [[ "$fix" -eq 1 ]]; then
|
||||
apply_fix || true
|
||||
fi
|
||||
|
||||
if [[ "$fix_only" -eq 1 ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$verify" -eq 1 ]]; then
|
||||
verify_audio || true
|
||||
fi
|
||||
|
||||
sec "Versions (PipeWire / WirePlumber / ALSA utils)"
|
||||
maybe pipewire --version
|
||||
maybe wireplumber --version
|
||||
maybe wpctl --version
|
||||
maybe pactl --version
|
||||
maybe pw-play --version
|
||||
maybe speaker-test --version
|
||||
maybe alsaucm --version
|
||||
maybe amixer --version
|
||||
maybe aplay --version
|
||||
|
||||
sec "User services status"
|
||||
maybe systemctl --user is-system-running
|
||||
maybe systemctl --user status pipewire pipewire-pulse wireplumber --no-pager
|
||||
maybe systemctl --user status pipewire.socket wireplumber.socket --no-pager
|
||||
|
||||
sec "ALSA enumeration"
|
||||
maybe aplay -l
|
||||
sec "ALSA PCM list (check pipewire/default/pulse)"
|
||||
maybe aplay -L
|
||||
|
||||
sec "UCM sanity"
|
||||
maybe alsaucm -c sof-rt5682 list _verbs
|
||||
maybe alsaucm -c sof-rt5682 list _devices
|
||||
|
||||
sec "IEC958 switches (all 0/1/2)"
|
||||
maybe amixer -c0 sget 'IEC958',0
|
||||
maybe amixer -c0 sget 'IEC958',1
|
||||
maybe amixer -c0 sget 'IEC958',2
|
||||
|
||||
sec "HDMI Jack states (on/off) + ELD controls"
|
||||
amixer_cget_by_control CARD "HDMI/DP,pcm=2 Jack"
|
||||
amixer_cget_by_control CARD "HDMI/DP,pcm=3 Jack"
|
||||
amixer_cget_by_control CARD "HDMI/DP,pcm=4 Jack"
|
||||
amixer_cget_by_control PCM "ELD" 2
|
||||
amixer_cget_by_control PCM "ELD" 3
|
||||
amixer_cget_by_control PCM "ELD" 4
|
||||
|
||||
sec "Installed files (system paths)"
|
||||
maybe ls -l /usr/share/alsa/ucm2/conf.d/sof-rt5682/sof-rt5682.conf
|
||||
maybe ls -l /usr/share/alsa/ucm2/GoogleKaisa/sof-rt5682/HiFi.conf
|
||||
maybe ls -l /usr/share/wireplumber/main.lua.d/60-kaisa-ucm.lua
|
||||
maybe ls -l /usr/share/wireplumber/main.lua.d/60-kaisa-ucm.lua.disabled
|
||||
|
||||
sec "Potential conflicting WirePlumber snippets (user/system)"
|
||||
maybe ls -la ~/.config/wireplumber/wireplumber.conf.d
|
||||
maybe ls -la ~/.config/wireplumber/wireplumber.conf.d/*kaisa* 2>/dev/null
|
||||
maybe ls -la /etc/wireplumber/wireplumber.conf.d 2>/dev/null
|
||||
maybe ls -la /etc/wireplumber/wireplumber.conf.d/*kaisa* 2>/dev/null
|
||||
|
||||
sec "WirePlumber state (profile / nodes / routes)"
|
||||
maybe ls -la ~/.local/state/wireplumber
|
||||
maybe sed -n '1,200p' ~/.local/state/wireplumber/default-profile
|
||||
maybe sed -n '1,200p' ~/.local/state/wireplumber/default-nodes
|
||||
maybe sed -n '1,200p' ~/.local/state/wireplumber/default-routes
|
||||
|
||||
sec "WirePlumber default-routes: persisted volume sanity"
|
||||
routes="${HOME}/.local/state/wireplumber/default-routes"
|
||||
if [[ -f "$routes" ]]; then
|
||||
low_lines="$(awk -F'channelVolumes=' '
|
||||
NF >= 2 {
|
||||
rest = $2
|
||||
sub(/;.*/, "", rest)
|
||||
v = rest + 0.0
|
||||
if (v > 0 && v < 0.25) print $0
|
||||
}
|
||||
' "$routes" 2>/dev/null || true)"
|
||||
if [[ -n "$low_lines" ]]; then
|
||||
warn "Persisted channelVolumes below ~0.25 in default-routes (can sound like silence after reboot):"
|
||||
printf '%s\n' "$low_lines"
|
||||
note "See docs/linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md"
|
||||
else
|
||||
note "No suspiciously low channelVolumes lines in default-routes (threshold 0.25)."
|
||||
fi
|
||||
else
|
||||
note "No default-routes file (yet)."
|
||||
fi
|
||||
|
||||
sec "PipeWire card / profile / ports (focus: cml_rt5682_def)"
|
||||
maybe pactl list cards short
|
||||
maybe pactl list cards | sed -n '/cml_rt5682_def/,+220p'
|
||||
|
||||
sec "Sinks (PipeWire) + default sink"
|
||||
maybe pactl info
|
||||
maybe pactl list short sinks
|
||||
maybe wpctl status
|
||||
|
||||
sec "Kernel hints (SOF/HDMI hw_params IPC errors)"
|
||||
echo
|
||||
echo "Note: if you see PipeWire 'set_hw_params: Input/output error', the real cause is often in kernel logs."
|
||||
echo " This section tries to summarize SOF/ASoC HDMI failures (e.g. ipc tx error -5 for pcm2 HDMI1)."
|
||||
|
||||
kernel_snip="$(
|
||||
journalctl -k -b --no-pager 2>/dev/null | \
|
||||
grep -nE 'sof-audio|sof_ipc3_pcm_hw_params|ipc tx error|STREAM_PCM_PARAMS|ASoC error|HDMI1|pcm2' 2>/dev/null | \
|
||||
tail -n 120 || true
|
||||
)"
|
||||
if [[ -n "${kernel_snip}" ]]; then
|
||||
warn "Kernel log shows SOF/ASoC HDMI failures (tail):"
|
||||
printf '%s\n' "${kernel_snip}"
|
||||
note "If it mentions: sof_ipc3_pcm_hw_params: pcm2 (HDMI1) ... ipc failed ... -5"
|
||||
note "then this is a kernel/SOF issue (not UCM/WirePlumber). Capture full: journalctl -k -b | grep -nE 'sof-audio|ipc tx error|pcm2|HDMI1'"
|
||||
else
|
||||
note "No matching kernel SOF/HDMI error lines found (or insufficient permission to read kernel journal)."
|
||||
fi
|
||||
|
||||
sec "Quick playback tests (non-destructive)"
|
||||
echo
|
||||
echo "Note: if ALSA 'pulse' PCM is missing, do NOT use: speaker-test -D pulse"
|
||||
echo "Try these instead (they use PipeWire):"
|
||||
echo
|
||||
echo "Tip: these are wrapped with a short timeout to avoid hanging."
|
||||
echo " (uses: timeout -k 1s 5s ... -> TERM then KILL)"
|
||||
maybe timeout -k 1s 5s speaker-test -D pipewire -c2 -t sine -f 440 -l 1
|
||||
maybe timeout -k 1s 5s speaker-test -D default -c2 -t sine -f 440 -l 1
|
||||
maybe timeout -k 1s 5s pw-play /usr/share/sounds/alsa/Front_Center.wav
|
||||
maybe timeout -k 1s 5s paplay /usr/share/sounds/alsa/Front_Center.wav
|
||||
|
||||
sec "Recent logs (journalctl --user, current boot)"
|
||||
maybe journalctl --user -u wireplumber -b --no-pager -n 200
|
||||
maybe journalctl --user -u pipewire -b --no-pager -n 200
|
||||
maybe journalctl --user -u pipewire-pulse -b --no-pager -n 200
|
||||
|
||||
sec "Hints"
|
||||
cat <<'EOF'
|
||||
- If HDMI ports show "not available", verify cable/monitor input/EDID and re-plug.
|
||||
- If profile keeps reverting after reboot, compare:
|
||||
- ~/.local/state/wireplumber/default-profile
|
||||
- /usr/share/wireplumber/main.lua.d/60-kaisa-ucm.lua (device.profile)
|
||||
- any *kaisa* snippets under ~/.config/wireplumber/ or /etc/wireplumber/
|
||||
- If set_hw_params errors appear in logs for a given pcm (2/3/4), test only ONE HDMI at a time and switch sink accordingly.
|
||||
EOF
|
||||
|
||||
hr
|
||||
echo "Done. Report saved to: $out"
|
||||
213
scripts/kaisa-hdmi-stability-probe.sh
Executable file
213
scripts/kaisa-hdmi-stability-probe.sh
Executable file
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env bash
|
||||
# Kaisa (sof-rt5682) HDMI stability probe.
|
||||
# Goal: one script to reproduce/quantify "reboot -> silent -> later OK" and capture
|
||||
# kernel SOF IPC(-5)/ASoC errors with monotonic timestamps.
|
||||
#
|
||||
# What it does (non-destructive, no service stop by default):
|
||||
# - Repeatedly tries playback on HiFi HDMI sinks (pcm=2/3/4 as requested)
|
||||
# - After each attempt, scrapes *new* kernel lines for SOF/ASoC HDMI hw_params failures
|
||||
# - Records ALSA subdevices availability, Jack/ELD states, default sink, and sinks list
|
||||
#
|
||||
# Logs:
|
||||
# ./_logs/kaisa-hdmi-stability-probe_<ts>.log (or $KAISA_LOG_DIR)
|
||||
#
|
||||
# Notes:
|
||||
# - Run as desktop user (no sudo). Needs access to kernel journal for best results.
|
||||
# - If journalctl -k is restricted, the script will still log PipeWire errors and ALSA state.
|
||||
set -euo pipefail
|
||||
|
||||
ts="$(date +%Y%m%d_%H%M%S)"
|
||||
log_dir="${KAISA_LOG_DIR:-./_logs}"
|
||||
out="${log_dir}/kaisa-hdmi-stability-probe_${ts}.log"
|
||||
|
||||
duration_s="${KAISA_PROBE_DURATION_S:-120}"
|
||||
interval_s="${KAISA_PROBE_INTERVAL_S:-2}"
|
||||
pcms="${KAISA_PROBE_PCMS:-2,3}" # comma-separated list: 2,3,4
|
||||
audio_file="${KAISA_PROBE_AUDIO_FILE:-/usr/share/sounds/alsa/Front_Center.wav}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/kaisa-hdmi-stability-probe.sh [--duration S] [--interval S] [--pcms 2,3,4]
|
||||
|
||||
Env overrides:
|
||||
KAISA_LOG_DIR
|
||||
KAISA_PROBE_DURATION_S
|
||||
KAISA_PROBE_INTERVAL_S
|
||||
KAISA_PROBE_PCMS
|
||||
KAISA_PROBE_AUDIO_FILE
|
||||
|
||||
Example:
|
||||
KAISA_PROBE_DURATION_S=180 KAISA_PROBE_PCMS=2,3 ./scripts/kaisa-hdmi-stability-probe.sh
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--duration) duration_s="${2:?seconds}"; shift 2 ;;
|
||||
--interval) interval_s="${2:?seconds}"; shift 2 ;;
|
||||
--pcms) pcms="${2:?list}"; shift 2 ;;
|
||||
*) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "$log_dir"
|
||||
exec > >(tee "$out") 2>&1
|
||||
|
||||
hr() { printf '\n%s\n' "================================================================================"; }
|
||||
sec() { hr; printf '%s\n' "$1"; hr; }
|
||||
note() { printf '\n[NOTE] %s\n' "$*"; }
|
||||
warn() { printf '\n[WARN] %s\n' "$*"; }
|
||||
|
||||
kernel_grep_re='sof-audio|sof_ipc3_pcm_hw_params|ipc tx error|STREAM_PCM_PARAMS|ASoC error|HDMI1|HDMI2|HDMI3|pcm2|pcm3|pcm4'
|
||||
pipewire_grep_re='set_hw_params|suspended -> error|Start error|hw_sofrt5682_[234]__sink'
|
||||
|
||||
get_default_sink() {
|
||||
pactl info 2>/dev/null | awk -F': ' '/^默认音频入口:|^Default Sink:/{print $2; exit}'
|
||||
}
|
||||
|
||||
list_sinks_short() {
|
||||
pactl list short sinks 2>/dev/null || true
|
||||
}
|
||||
|
||||
alsa_dump_subdevices() {
|
||||
aplay -l 2>/dev/null | awk '
|
||||
BEGIN { dev=""; }
|
||||
$0 ~ /^card [0-9]+: / { next }
|
||||
$0 ~ /, device [0-9]+: / {
|
||||
match($0, /device [0-9]+:/)
|
||||
dev=substr($0, RSTART+7, RLENGTH-8)
|
||||
gsub(/[^0-9]/,"",dev)
|
||||
print $0
|
||||
next
|
||||
}
|
||||
$0 ~ /(子设备:|Subdevices:)/ {
|
||||
print " " $0
|
||||
next
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
amixer_dump_jack_eld() {
|
||||
# Stable numids for this platform are often: Jack 10/16/22; ELD 15/21/27.
|
||||
# If they differ, doctor script can resolve dynamically; here we best-effort.
|
||||
for n in 10 16 22 15 21 27; do
|
||||
echo
|
||||
echo "\$ amixer -c0 cget numid=$n"
|
||||
amixer -c0 cget "numid=$n" 2>/dev/null || true
|
||||
done
|
||||
}
|
||||
|
||||
kernel_tail_since() {
|
||||
# Print kernel lines since a monotonic timestamp (string like "[ 78.312861]")
|
||||
# We do not rely on exact parse; we simply filter journalctl -k -b -o short-monotonic output
|
||||
# by lexicographic comparison on the bracketed numeric value.
|
||||
local since="${1:-}"
|
||||
if ! journalctl -k -b -o short-monotonic --no-pager &>/dev/null; then
|
||||
note "Cannot read kernel journal (journalctl -k)."
|
||||
return 0
|
||||
fi
|
||||
if [[ -z "$since" ]]; then
|
||||
journalctl -k -b -o short-monotonic --no-pager | grep -E "$kernel_grep_re" | tail -n 80 || true
|
||||
return 0
|
||||
fi
|
||||
journalctl -k -b -o short-monotonic --no-pager | awk -v since="$since" '
|
||||
# since is like "[ 78.312861]"
|
||||
function num(s, x) {
|
||||
x=s
|
||||
gsub(/[\[\]]/,"",x)
|
||||
gsub(/^[[:space:]]+/,"",x)
|
||||
return x+0.0
|
||||
}
|
||||
{
|
||||
tag=""
|
||||
if (match($0, /^\[[^]]+\]/)) {
|
||||
tag=substr($0, RSTART, RLENGTH)
|
||||
}
|
||||
if (tag != "" && num(tag) >= num(since)) print $0
|
||||
}
|
||||
' | grep -E "$kernel_grep_re" | tail -n 120 || true
|
||||
}
|
||||
|
||||
now_monotonic() {
|
||||
# Current kernel journal last monotonic tag, best-effort.
|
||||
# journalctl -o short-monotonic lines start with: "[ 3.588851] ..."
|
||||
# which may tokenize as "[" "3.588851]" ... so we must extract up to the first "]".
|
||||
journalctl -k -b -o short-monotonic --no-pager 2>/dev/null | tail -n 1 | \
|
||||
awk '{ if (match($0, /^\[[^]]+\]/)) print substr($0, RSTART, RLENGTH); }' || true
|
||||
}
|
||||
|
||||
play_on_pcm() {
|
||||
local pcm="${1:?pcm}"
|
||||
local sink="alsa_output.pci-0000_00_1f.3-platform-cml_rt5682_def.HiFi__hw_sofrt5682_${pcm}__sink"
|
||||
echo
|
||||
echo "\$ pactl set-default-sink $sink"
|
||||
pactl set-default-sink "$sink" 2>/dev/null || true
|
||||
echo "\$ pw-play $audio_file"
|
||||
timeout -k 1s 5s pw-play "$audio_file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
sec "Kaisa HDMI stability probe — report: $out"
|
||||
echo "\$ date"; date
|
||||
echo "\$ uname -a"; uname -a
|
||||
echo "\$ id"; id
|
||||
echo
|
||||
note "duration_s=$duration_s interval_s=$interval_s pcms=$pcms audio_file=$audio_file"
|
||||
note "Tip: run right after reboot, before any manual audio tweaks."
|
||||
|
||||
sec "Baseline: ALSA + Jack/ELD + sinks"
|
||||
echo "\$ aplay -l"; aplay -l || true
|
||||
echo
|
||||
echo "\$ aplay -l (subdevices summary)"; alsa_dump_subdevices || true
|
||||
sec "Jack/ELD (best-effort numid dump)"
|
||||
amixer_dump_jack_eld
|
||||
sec "PipeWire sinks"
|
||||
echo "\$ pactl info"; pactl info || true
|
||||
echo "\$ pactl list short sinks"; list_sinks_short
|
||||
|
||||
start_epoch="$(date +%s)"
|
||||
end_epoch="$((start_epoch + duration_s))"
|
||||
iter=0
|
||||
|
||||
last_mon="$(now_monotonic)"
|
||||
note "Kernel monotonic cursor (initial): ${last_mon:-<none>}"
|
||||
|
||||
sec "Loop"
|
||||
while [[ "$(date +%s)" -lt "$end_epoch" ]]; do
|
||||
iter="$((iter+1))"
|
||||
echo
|
||||
hr
|
||||
echo "[ITER $iter] time=$(date) default_sink=$(get_default_sink || true)"
|
||||
|
||||
# Try play on each requested pcm
|
||||
IFS=',' read -r -a pcm_arr <<< "$pcms"
|
||||
for pcm in "${pcm_arr[@]}"; do
|
||||
pcm="$(echo "$pcm" | tr -d '[:space:]')"
|
||||
[[ -n "$pcm" ]] || continue
|
||||
play_on_pcm "$pcm"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "[ITER $iter] sinks:"
|
||||
list_sinks_short
|
||||
|
||||
echo
|
||||
echo "[ITER $iter] new kernel hints since ${last_mon:-<none>}:"
|
||||
new_kernel="$(kernel_tail_since "${last_mon:-}")"
|
||||
if [[ -n "${new_kernel}" ]]; then
|
||||
warn "Kernel SOF/ASoC lines (tail):"
|
||||
printf '%s\n' "${new_kernel}"
|
||||
else
|
||||
echo "(none)"
|
||||
fi
|
||||
|
||||
# Advance cursor
|
||||
last_mon="$(now_monotonic)"
|
||||
|
||||
sleep "$interval_s"
|
||||
done
|
||||
|
||||
sec "Done"
|
||||
note "Saved: $out"
|
||||
note "If you hit failures, also capture: journalctl -k -b -o short-monotonic | grep -nE '$kernel_grep_re' | tail -n 200"
|
||||
@@ -8,7 +8,10 @@
|
||||
-- WirePlumber 无法匹配 device.profile,最终会把活动配置留在 off,且 pactl set-card-profile HiFi
|
||||
-- 报「无此实体」。此时请保持 "pro-audio" 先恢复输出;待 install-kaisa-ucm-overlay + 重启后 pactl 已出现
|
||||
-- HiFi: 时,再把本变量改为 "HiFi" 并重启用户 pipewire 栈。
|
||||
local KAISA_WP_DEVICE_PROFILE = "pro-audio" -- 或 "HiFi"
|
||||
--
|
||||
-- 当前仓库主交付为 UCM/HiFi;当你已验证 `pactl list cards` 的「配置文件」里出现 `HiFi:`,
|
||||
-- 就应默认固定为 "HiFi",避免重启后回退到 stereo-fallback(只剩 3.5mm)。
|
||||
local KAISA_WP_DEVICE_PROFILE = "HiFi" -- 或 "pro-audio"(仅用于临时排障对照)
|
||||
|
||||
table.insert(alsa_monitor.rules, {
|
||||
matches = {
|
||||
|
||||
Reference in New Issue
Block a user