MoreRSS

site iconAllen Hua修改

Java工程师,常驻南京。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Allen Hua的 RSS 预览

机械革命泰钽 plus linux Debian Wayland 独显直连调优全记录

2026-06-24 10:21:00

机械革命泰钽 Plus 游戏本在 Linux Debian GNOME Wayland 下流畅使用诊断艰辛历程

一、软硬件配置

硬件

项目 规格
型号 MECHREVO Taitan Series GM7TG0M
CPU 11th Gen Intel Core i7-11800H × 16 (8C/16T, Tiger Lake-H)
独显 NVIDIA GeForce RTX 3060 Laptop GPU (6GB GDDR6)
核显 Intel UHD Graphics (Tiger Lake-H GT1, 集成于 i7-11800H)
内存 64 GiB DDR4
磁盘 1 CT240BX500SSD1 240GB SATA SSD
磁盘 2 Fanxiang S500PRO 1TB NVMe SSD
磁盘 3 IM2P33F8-512GD 512GB NVMe SSD
固件 N.1.07MRO06
BIOS 显卡模式 dGPU Only (独显直连) / MSHybrid (双显卡混合输出)

软件

项目 版本
操作系统 Debian GNU/Linux 13 (trixie)
架构 x86_64
桌面环境 GNOME 48.7
窗口系统 Wayland
内核 Linux 6.12.90+deb13.1-amd64
NVIDIA 驱动 595.80 (私有驱动)
NVIDIA VA-API nvidia-vaapi-driver 0.0.13 (NVDEC 后端)
PipeWire 1.4.2
腾讯会议 v3.26.10.401 (deb 包安装于 /opt/wemeet)

二、双显卡 vs 独显直连:两难抉择

2.1 混合输出模式 (MSHybrid) 的问题

BIOS 中设为双显卡混合输出时,系统主 GPU 是 Intel 核显,桌面合成器(Mutter)由核显驱动。但笔者的两台外接显示器(一台 4K 27 寸、一台 1080P 24 寸)通过 HDMI 和 USB-C 直连到了 RTX 3060 独显,这意味着:外接屏幕的最终画面输出必须经过 NVIDIA 独显。每一帧渲染流程为 Intel 核显合成→经 PCIe 总线拷贝到 NVIDIA 显存→NVIDIA 输出到屏幕,这个跨 GPU 的 PRIME 缓冲区同步过程引入了显著的延迟和掉帧;同时 Mutter 同时管理两个 GPU 的 framebuffer,帧时序难以对齐,进一步加剧了卡顿感。

为什么 Windows 没这个问题? Windows 下 NVIDIA Optimus 的混合输出走的是一个更高效的路径:NVIDIA 驱动内置硬件拷贝引擎(cross-adapter copy engine),独显渲染的画面通过 PCIe 直接写进核显的 framebuffer 然后输出到屏幕,不需要等待双向同步和帧对齐。而 Linux 的 PRIME 方案目前依赖通用 DMA-BUF 机制进行跨 GPU 缓冲区共享,多了一层软件调度开销,在 4K 高分辨率下带宽压力更大,卡顿感就非常明显。本质上不是硬件不行,是 Linux 图形栈在双 GPU 调度上还没做到 Windows 的成熟度。NVIDIA 在 Windows 下的图形驱动已经打磨了超过 25 年(自 1999 年 GeForce 256 发布算起),Optimus 双显卡切换技术也已在 Windows 上迭代了 15 年以上;而 Linux 下的 PRIME 多 GPU 同步方案直到 2016 年前后才逐渐可用,Wayland 下的双 GPU 混合渲染就更加年轻。十几年的工程积累差距,不是短期内能抹平的。

  • Waydroid 正常 — Android 容器通过 Mesa 调用 Intel iGPU 做 GPU 加速
  • 腾讯会议各项功能正常 — 屏幕共享打 patch 后也能正常创建分享,别人能看到
  • 桌面流畅度不佳 — GNOME 动画卡顿,窗口拖拽/切换时有可感知的延迟

补充:混合模式下若改用 X11 显示协议,流畅度问题同样可以得到缓解——X11 的 GPU 调度路径更成熟,PRIME 同步开销较低。但笔者不选择此方案,因为 Wayland 是 Linux 桌面显示的明确未来方向,且大部分日常软件(包括通过 XWayland 兼容层运行的)在 Wayland 下已足够稳定。放弃 Wayland 换 X11 是走回头路。

2.2 为什么不能只用 Intel 核显 (iGPU Only)

最理想的情况似乎是 BIOS 设为 iGPU Only,只用 Intel 核显驱动内置屏幕,简单省电。但这是不可能的,原因在于本游戏本的硬件设计:

HDMI 和 USB-C(DP Alt Mode)视频输出接口在物理上直连到了 RTX 3060 独显,而非 Intel 核显。

这意味着:

  • 若 BIOS 禁用 NVIDIA 独显(iGPU Only),HDMI 和 USB-C 接口完全无信号输出
  • 笔者日常外接两台显示器(一台 HDMI + 一台 USB-C),这两个接口离开独显就无法使用
  • 所以 iGPU Only 模式从一开始就被硬件设计排除了

因此实际可选的模式只有两种:MSHybrid(双显卡混合输出)dGPU Only(独显直连)

2.3 独显直连模式 (dGPU Only) 的问题

BIOS 切换为独显直连后,所有渲染由 RTX 3060 承担,桌面流畅度大幅提升。但带来三个问题:

  1. 腾讯会议 webcam 黑屏 — 摄像头画面无法显示
  2. 腾讯会议屏幕共享失败 — 无法创建分享,别人也看不到(混合模式下打 patch 后已修复的部分,在独显下仍不行)
  3. Waydroid 不可用 — 无解(详见下文 2.3)

2.4 Waydroid 在 NVIDIA 独显下不可用的根因

参见 waydroid/waydroid#1619

Waydroid 维护者 electrikjesus 于 2025 年 12 月明确表态:

Android uses only Mesa drivers. No way to get around it.

Android 图形栈的 GPU 加速只能通过 Mesa 体系内的驱动实现。NVIDIA 私有驱动(包括 nvidia-open 内核模块)不属于 Mesa 生态,waydroid 无法调用它为 Android 容器渲染界面。

NVIDIA 支持的唯一例外是更换为 nouveau 开源驱动,但 nouveau 在 RTX 3060 上的性能极差,没有实用价值。


三、最终方案:放弃 Waydroid,全面转向 dGPU Only

既然 Waydroid 使用频率不高,而桌面流畅度与日常体验息息相关,最终决定:

BIOS 设为 dGPU Only(独显直连),放弃 Waydroid。

3.1 受影响的软件

软件 状态 说明
Waydroid 不可用 硬伤,无解
腾讯会议 webcam 已修复 见第四章
腾讯会议屏幕共享 已修复 见第四章
kooha 已卸载 VA-API 编码依赖,已用 GNOME Screenshot + ffmpeg NVENC 替代
音频输入输出 正常 之前测试时为空是因为 systemctl --user stop pipewire.socket 关掉了 PipeWire,与独显直连无关
GNOME 桌面 流畅 RTX 3060 渲染 + nvidia-clock-unlock 锁频,体验显著改善
其他软件 正常 Chrome、VS Code、飞书、LibreOffice、GIMP、Krita、VLC、Docker、Flatpak 等均不依赖 Intel GPU

3.2 NVIDIA GPU 锁频服务(提升桌面流畅度)

独显直连能改善流畅度,但 NVIDIA 私有驱动在默认情况下会根据负载动态调整 GPU 频率。桌面合成器(Mutter)的渲染负载波动较大,GPU 在低频↔高频之间频繁切换,导致 GNOME 动画仍有偶尔的掉帧。

为此创建了 nvidia-clock-unlock.service 系统服务,在启动时:

  1. 启用 NVIDIA 持久化模式(nvidia-smi -pm 1),防止 GPU 在空闲时进入深度休眠
  2. 锁定 GPU 最低频率为 1200 MHz(nvidia-smi -lgc 1200,2100),确保合成器始终获得足够的渲染算力
# /etc/systemd/system/nvidia-clock-unlock.service
[Unit]
Description=Unlock NVIDIA GPU clock floor for smooth Wayland compositing
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/usr/bin/nvidia-smi -pm 1
ExecStart=/usr/bin/nvidia-smi -lgc 1200,2100
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

启用方式:

sudo systemctl daemon-reload
sudo systemctl enable --now nvidia-clock-unlock.service

代价:GPU 无法完全进入最低功耗状态,闲置功耗略高(对本游戏本影响不大,日常插电使用)。

3.3 死重量但保留的包(为日后切回混合模式留余地)

i965-va-driver           # Intel 老 GPU VA-API 驱动
intel-media-va-driver    # Intel 新 GPU VA-API 驱动
firmware-intel-graphics  # Intel GPU 固件
libdrm-intel1            # Intel DRM 用户态库
libigdgmm12              # Intel 显存管理库

以上包在不加载 i915 模块时不生效,占空间极小,保留以免日后切回混合模式时需重装。

3.4 内核参数清理

# /etc/default/grub 中以下 i915 参数可删除(独显直连下 i915 模块不加载):
i915.enable_psr=0 i915.enable_fbc=1 i915.enable_guc=3

四、腾讯会议在 dGPU Only 下的完整修复方案

4.1 Webcam 黑屏修复

现象:启动腾讯会议后,webcam 预览画面黑屏,无法显示视频。

根因:xcast 日志中 eglCreateWindowSurface 返回 error:3005EGL_BAD_CONFIG)。wemeet 以 X11(XCB) 后端运行在 XWayland 下,NVIDIA EGL 无法将 XWayland 的 X11 窗口句柄转换为 EGL Window Surface。

关键线索:AUR wemeet-bin 评论区 pinned 评论(by anlorsp, 2025-03-08):

对于 N 卡视频黑屏的问题,可以尝试为腾讯会议单独设置 __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json 环境变量(不要全局设置该变量!)。在双显卡环境下可以修复黑屏问题,在单 NVIDIA 显卡环境下未测试。

本机验证:dGPU Only 模式(即单 NVIDIA)下,设置此变量后 webcam 加载成功。原理:强制 wemeet 走 Mesa 的 libEGL_mesa.so.0,走 llvmpipe 软件渲染路径,虽然性能不如硬件加速,但可以正常创建 EGL Window Surface 并显示摄像头画面。

修复步骤:编辑 /opt/wemeet/wemeetapp.sh,在第 53 行添加:

export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json

注意:此处必须有 export,仅写变量赋值不导出的话子进程 wemeetapp 继承不到,变量无效。

当前完整脚本内容见文末 附录 B

4.2 屏幕共享修复

现象:点击"共享屏幕"无反应,或能发起共享但别人看到白屏。

根因(来自 wemeet-screenshare-patch 技术细节):

  • Hunk 0libscreen_share_module.so 中 DBus 异步调用顺序错误 — Start 调用放在了 SelectSources 之后而未等待其完成,依赖竞态条件。xdg-desktop-portal-gnome 后端将 SelectSources 立即返回、Start 弹窗等待用户选择显示器,掩盖了此 bug;但在 xdg-desktop-portal-wlr 后端则会必然失败。
  • Hunk 1a/1b:PipeWire 颜色格式协商硬编码了 BGRx 格式,部分 portal 后端不支持,导致对方看到白屏。补丁中的 libhook.so 劫持缓冲区路径做了 BGRx↔RGBx 转换。

本机修复过程

  1. 按 patch README 执行 Step 1 (运行 python3 patch.py) 和 Step 2 (添加 LD_PRELOAD),能发起共享但别人看到白屏(颜色格式问题)。
  2. 参考 Issue #2,创建并执行 reverse_colors.py 撤销 Hunk 1a/1b 的颜色格式劫持,同时删除 LD_PRELOAD 行。
  3. 最终效果:只保留 Hunk 0(DBus 异步调用修复),去掉颜色格式转换,屏幕共享恢复正常,别人能看到且颜色正确。

reverse_colors.py 代码(保存为 .py 文件后用 sudo python3 执行):

#!/usr/bin/env python3
"""Reverse Hunk 1a/1b (color format patches) while keeping Hunk 0 (DBus fix)."""
import mmap
import os
import sys

REVERSE_PATCHES = {
    "/opt/wemeet/bin/modules/screen_share/libscreen_share_module.so": [
        (0x450fb6, bytes.fromhex("08")),
        (0x4566e4, bytes.fromhex("8b45c08b4808")),
        (0x6b1951, bytes.fromhex("4889e54883ec1048897df8488b7df8e88b01")),
        (0x6b1964, bytes.fromhex("004889c7e8b30100004883c4105dc3662e0f1f84")),
    ],
    "/opt/wemeet/lib/libxcast.so": [
        (0xfedbfa, bytes.fromhex("660f1f44")),
        (0xfedc3c, bytes.fromhex("23")),
        (0xfedc4b, bytes.fromhex("4883")),
        (0xfedc4f, bytes.fromhex("5b5d415c415d415e415fc3660f1f440000")),
    ],
}

def apply(path, patches):
    print(f"Patching: {path}")
    with open(path, "r+b") as fh:
        with mmap.mmap(fh.fileno(), 0, access=mmap.ACCESS_WRITE) as mm:
            for offset, new_bytes in patches:
                current = mm[offset:offset+len(new_bytes)]
                print(f"  0x{offset:08x}: {current.hex()} => {new_bytes.hex()}")
                mm.seek(offset)
                mm.write(new_bytes)
            mm.flush()
    print("  OK\n")

for f, patches in REVERSE_PATCHES.items():
    apply(f, patches)

print("Done. Hunks 1a/1b reversed. Hunk 0 (DBus fix) retained.")

执行后删除启动脚本中的 LD_PRELOAD 行:

sudo sed -i '/^export LD_PRELOAD.*libhook\.so/d' /opt/wemeet/wemeetapp.sh

4.3 修复后腾讯会议功能状态

功能 状态 备注
Webcam 预览 正常 50_mesa.json → llvmpipe 软件渲染
观看别人共享屏幕 正常 软解 + 软渲染,稍慢但可用
创建屏幕共享给别人看 正常 Hunk 0 DBus 修复
共享画面颜色 正常 已撤销 Hunk 1a/1b
音频输入/输出 正常 PipeWire 正常启动即可
视频硬件编解码 关闭 (hwdec:0, hwenc:0) 走 Mesa llvmpipe 软件路径

五、总结

维度 混合输出 (MSHybrid) 独显直连 (dGPU Only)
桌面流畅度
Waydroid 可用 不可用
腾讯会议 webcam 正常 已修复(Mesa llvmpipe 兜底)
腾讯会议屏幕共享 打 patch 后正常 打 patch 后正常
NVIDIA VA-API 硬解 NVDEC + Intel 都可用 仅 NVDEC
NVIDIA 硬编 NVENC 正常 NVENC 正常

核心妥协:放弃 Waydroid,换取桌面流畅度。腾讯会议通过 __EGL_VENDOR_LIBRARY_FILENAMES + wemeet-screenshare-patch(仅 Hunk 0)组合方案完全可用。


附录 A:相关链接

附录 B:/opt/wemeet/wemeetapp.sh 当前内容

#!/bin/bash

os_release="/etc/os-release"
if [[ -e ${os_release} ]];then
  source /etc/os-release
  main=`echo ${VERSION_ID} | awk -F . '{print $1}'`
  case $ID in
  ubuntu)
    if [[ ${main} -le "16" ]];then
      zenity --info --title="腾讯会议" --text="腾讯会议检测到您操作系统版本过低,请升级系统到ubuntu18.04或以上版本!" --width=350 --height=100
      exit 1
    fi

    if [[ ${main} -le "18" ]];then
      if [ $XMODIFIERS ];then
        im_module=$XMODIFIERS
        echo 'use XMODIFIERS'
        export QT_IM_MODULE=${im_module#*=}
      elif [ $GTK_IM_MODULE ];then
        echo 'use GTK_IM_MODULE'
        export QT_IM_MODULE=${GTK_IM_MODULE}
      fi
      echo ${QT_IM_MODULE}
    fi
    ;;
  *)
    ;;
  esac
fi

if [ "$XDG_SESSION_TYPE" = "wayland" ];then
  if [ -f "/opt/x11-wayland/x11-ext.sh" ];then
    source /opt/x11-wayland/x11-ext.sh
  else
    export QT_QPA_PLATFORM=xcb
    export XDG_SESSION_TYPE=x11
    unset WAYLAND_DISPLAY
    export WEMEET_XWAYLAND=1
  fi
fi

SELF=$(readlink -f "$0")
HERE=${SELF%/*}

export LC_ALL=zh_CN.UTF-8
export PATH="${HERE}/bin${PATH:+:$PATH}"
export LD_LIBRARY_PATH="${HERE}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export QT_PLUGIN_PATH="${HERE}/plugins"
export TZ=Asia/Shanghai

export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json

exec wemeetapp $*

MECHREVO WUJIE14 PRO on Linux 声卡完整修复历程

2026-06-18 16:46:00

为了解决我的机械革命无界 14 pro 笔记本在 linux debian testing(forky) 下的自带麦克风、自带扬声器、插入 3.5mm 耳机这三个场景下,对应硬件都能正常工作的问题,遂有了此文。

前景提要:机械革命无界14Pro笔记本debian forky成功驱动内置扬声器和麦克风

系统信息

项目
机型 MECHREVO WUJIE14 PRO
CPU 12th Gen Intel Core i7-12650H
声卡 Intel Alder Lake PCH-P HD Audio (8086:51c8)
Codec Realtek ALC256 (10ec:0256)
子系统 ID 1d05:170b
固件 SOF (Sound Open Firmware) 签名固件 2025.05.1
OS Debian forky/sid, GNOME 50, Wayland
内核 7.0.10+deb14-amd64
PipeWire 1.6.6 + WirePlumber 1.6.6
ALSA UCM2 alsa-ucm-conf 1.2.15.3-1

目标

确保以下三个场景全部正常工作:

场景 状态
内置数字麦克风(DMIC) 始终正常
内置扬声器 始终正常
3.5mm 耳机插入后 修复前无声,修复后正常

问题

插入 3.5mm 耳机后声音仍然从笔记本内置扬声器输出,耳机无声音。系统设置输入设备显示正常(内置数字麦克风可用)。

根因分析

三重故障叠加

┌─────────────────────────────────────────────────────────┐
│  问题 1:硬件插孔检测不工作                                │
│  ALSA 控制 "Headphone Jack" 始终 = off                    │
│  即使耳机物理插入,Codec (Node 0x21) 的 pin sense 未触发   │
│  可能原因:SOF 驱动与 ALC256 pin sense 事件处理有兼容问题    │
├─────────────────────────────────────────────────────────┤
│  问题 2:UCM2 启动时禁用 Auto-Mute                         │
│  /usr/share/alsa/ucm2/HDA/init.conf                      │
│  → cset "name='Auto-Mute Mode' off"                      │
│  设计意图:让 PipeWire 用户态管理切换,但 UCM2 配置有缺陷    │
├─────────────────────────────────────────────────────────┤
│  问题 3:Speaker 和 Headphones 在不同 HiFi Profile 中      │
│  两者共用 PlaybackPCM "hw:0",ACP 拆分成了两个互斥 Profile  │
│  Headphones 依赖 JackControl,端口永远 "not available"     │
│  导致 PipeWire 无法激活耳机 DSP 通路                       │
└─────────────────────────────────────────────────────────┘

为什么 model= 参数无效

  • 本机使用 SOF/avs 驱动栈snd_soc_skl_hda_dsp),声卡名 sof-hda-dsp
  • snd-hda-intel 模块虽然被加载,但依赖计数为 0,不参与实际驱动
  • /etc/modprobe.d/alsa-alc256.conf 中配置的 options snd-hda-intel model=xxx 在 SOF 下不生效
  • 修复后该文件应当为空(或直接删除),避免误导
  • 强制使用传统 HDA(snd-intel-dspcfg.dsp_driver=1)会丢失内置 DMIC

音频拓扑(ALC256 Codec 节点路由)

                    ┌──────────┐
    hw:0 PCM ──────┤ DAC 0x02 │──┬── Pin 0x14 (Speaker) ── 扬声器
                    │ (Speaker)│  │
                    └──────────┘  │
                                  ├── Pin 0x1b (unused)
                                  └── Pin 0x21 (HP β) ── 耳机(辅)
                    ┌──────────┐
    hw:0 PCM ──────┤ DAC 0x03 │───── Pin 0x21* (HP α) ── 耳机(主)
                    │(Headphone)│
                    └──────────┘

                    * 表示活动连接

关键:两个 DAC 接收同一路 PCM 音频流,SOF DSP 固件根据激活的 Profile 控制哪个 DAC 通路工作。

修复历程(失败记录)

尝试 方法 结果 原因
1 model=laptop-amicheadset-mic 无效 SOF 驱动忽略 model= 参数
2 方案A:加 dsp_driver=1 切传统 HDA + headset-mic 耳机无效、麦消失 传统 HDA 不支持 DMIC;headset-mic 合并插孔类型不匹配
3 SOF 下手动切 Headphones Profile 端口 not available 无法路由 JackControl 依赖失败检测
4 Speaker Profile 下手动 amixer 开耳机 无声音 DSP 未激活耳机通路,DAC 0x03 无 stream
5 amixer toggle 脚本(操作 Playback Switch) 调音量时扬声器被激活 WirePlumber EnableSequence 每回路由激活都重开 Speaker

最终修复方案

核心思路

绕过硬件插孔检测,通过切换 Profile 来切换耳机/扬声器,让 WirePlumber 独立管理每个 Profile 的音量。

步骤

1. 修改 UCM2 配置,移除耳机 JackControl

# 备份
sudo cp /usr/share/alsa/ucm2/HDA/HiFi-analog.conf \
        /usr/share/alsa/ucm2/HDA/HiFi-analog.conf.bak

# 注释 JackControl 行(保留缩进)
sudo sed -i 's/^\([[:space:]]*\)JackControl "${var:hpjack}"/\1#JackControl "${var:hpjack}"/' \
    /usr/share/alsa/ucm2/HDA/HiFi-analog.conf

修改位置:/usr/share/alsa/ucm2/HDA/HiFi-analog.conf 第 156 行

         PlaybackSwitch "${var:hpvol} Playback Switch"
-        JackControl "${var:hpjack}"
+        #JackControl "${var:hpjack}"

效果:Headphones 端口不再依赖 Headphone Jack ALSA 控制,始终显示为可用(如同 Speaker 的 availability unknown)。

2. 创建快捷键切换脚本

~/.local/bin/toggle-audio.sh

#!/bin/bash
# 在扬声器和耳机之间切换

CARD="alsa_card.pci-0000_00_1f.3-platform-skl_hda_dsp_generic"
SPK_PROFILE="HiFi (HDMI1, HDMI2, HDMI3, Mic1, Mic2, Speaker)"
HP_PROFILE="HiFi (HDMI1, HDMI2, HDMI3, Headphones, Mic1, Mic2)"
SPK_SINK="alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Speaker__sink"
HP_SINK="alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Headphones__sink"

current=$(pactl list cards 2>/dev/null | grep "Active Profile" | grep -o "Speaker")

if [ -n "$current" ]; then
    pactl set-card-profile "$CARD" "$HP_PROFILE"
    sleep 0.3
    pactl set-default-sink "$HP_SINK"
    for i in $(pactl list sink-inputs short 2>/dev/null | awk '{print $1}'); do
        pactl move-sink-input "$i" "$HP_SINK" 2>/dev/null
    done
    notify-send -a "音频切换" "已切换到耳机" -i audio-headphones
else
    pactl set-card-profile "$CARD" "$SPK_PROFILE"
    sleep 0.3
    pactl set-default-sink "$SPK_SINK"
    for i in $(pactl list sink-inputs short 2>/dev/null | awk '{print $1}'); do
        pactl move-sink-input "$i" "$SPK_SINK" 2>/dev/null
    done
    notify-send -a "音频切换" "已切换到扬声器" -i audio-speakers
fi
chmod +x ~/.local/bin/toggle-audio.sh

3. 绑定快捷键

GNOME 设置 → 键盘 → 查看及自定义快捷键 → 自定义快捷键 → 添加:

  • 命令:/home/dk/.local/bin/toggle-audio.sh
  • 快捷键:Super+Alt+S

4. 重启音频服务

systemctl --user restart wireplumber pipewire pipewire-pulse

注意事项

  • alsa-ucm-conf 包更新后会覆盖修改的配置文件,需重新执行 sed 命令
  • 备份文件位置:/usr/share/alsa/ucm2/HDA/HiFi-analog.conf.bak
  • 恢复:sudo cp /usr/share/alsa/ucm2/HDA/HiFi-analog.conf.bak /usr/share/alsa/ucm2/HDA/HiFi-analog.conf

涉及知识点

ALSA 驱动栈

层级 组件 说明
用户态 PipeWire / WirePlumber 音频路由、流管理、音量控制
用户态 ACP (ALSA Card Profile) 解析 UCM2 配置,生成 Profile/Port
用户态 ALSA UCM2 声卡用例配置(Volume、Switch、Jack、PCM)
内核态 SOF 固件 DSP 音频处理管线
内核态 snd_soc_skl_hda_dsp ASoC machine driver
内核态 snd_hda_codec_alc269 ALC256 Codec 驱动
硬件 ALC256 Codec DAC/ADC/Pin Complex 寄存器

UCM2 关键文件

文件 作用
/usr/share/alsa/ucm2/HDA/init.conf 初始化序列(含 Auto-Mute off)
/usr/share/alsa/ucm2/HDA/HiFi-analog.conf 模拟设备定义(Speaker/Headphones/Mic)
/usr/share/alsa/ucm2/Intel/sof-hda-dsp/HiFi.conf SOF HiFi 顶层用例
/usr/share/alsa/ucm2/Intel/sof-hda-dsp/HiFi-sof.conf SOF 特定覆盖(DRC/EQ/固件)

诊断用命令

# 查看声卡
cat /proc/asound/cards
aplay -l

# 查看 Codec 寄存器(Pin Sense、DAC 音量、静音状态)
cat /proc/asound/card0/codec#0

# 查看 ALSA 控制和插孔状态
amixer -c0 contents | grep -E "Jack|Auto-Mute|name="

# 查看 PipeWire 状态
pactl info | grep "Default Sink"
pactl list cards | grep -A2 "Active Profile"
pactl list sinks short

# 查看 snd_hda_intel 是否被使用
ls /sys/module/snd_hda_intel/holders/
cat /sys/module/snd_hda_intel/refcnt

# 查看内核参数
cat /proc/cmdline

# 手动控制 Codec
amixer -c0 cset name='Headphone Playback Switch' on,on
amixer -c0 cset name='Auto-Mute Mode' Enabled

记录一下我删除相机 SD 卡当中废片的工作流

2026-06-14 10:05:00

采用 python 脚本实现(python3)。

相关说明:

  1. Linux/mac 电脑,如果你是 windows 电脑那么脚本需要稍加改造
  2. 相机当中开启了拍摄的照片按照日期文件夹存储,这种功能,否则可能一个文件夹下的照片数量特别多,你需要维护一个特别长的 keep.txt
  3. keep.txt 当中的文件名可以是完整文件名,也可以不包含扩展名。
    a. 如果是完整文件名,那脚本就根据完整文件名匹配去保留/删除照片
    b. 如果只包含文件名 stem,没有扩展名,将匹配所有包含 stem 的文件名,进而执行保留/删除逻辑。比如 keep.txt 当中有这样一行 HDK09432 ,那这些文件 HDK09432.JPG, HDK09432.ARW, HDK09432.HEIF 都会被保留,这尤其适用于你开启了 JPEG/RAW 拍摄,那么一张图片会有两个文件
  4. 支持模拟运行,只需要在最后添加 --dry-run
  5. 索尼 JPEG 照片扩展名是 JPG,RAW 拍摄文件扩展名是 ARW,HEIF 拍摄扩展名是 HIF (Apple 的 HEIF 照片扩展名是 HEIC)

cd /run/media/dk/708D-3AF3/DCIM/15660613/

ls
HDK09429.JPG  HDK09432.JPG  HDK09435.JPG  HDK09438.JPG  HDK09441.JPG  HDK09444.JPG  HDK09447.JPG
HDK09430.JPG  HDK09433.JPG  HDK09436.JPG  HDK09439.JPG  HDK09442.JPG  HDK09445.JPG  HDK09448.JPG
HDK09431.JPG  HDK09434.JPG  HDK09437.JPG  HDK09440.JPG  HDK09443.JPG  HDK09446.JPG  HDK09449.JPG
$python3 ~/Downloads/myapps/delete_unwanted_images.py ./keep.txt --dry-run

========== 执行模式 ==========
DRY RUN
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09429.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09430.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09431.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09433.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09434.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09435.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09436.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09437.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09439.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09443.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09444.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09445.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09446.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09447.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09448.JPG

========== 保留文件 ==========
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09432.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09438.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09440.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09441.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09442.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09449.JPG

========== 统计 ==========
模式: DRY-RUN
计划删除文件数: 15
保留文件数: 6

========== 删除列表 ==========
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09429.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09430.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09431.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09433.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09434.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09435.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09436.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09437.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09439.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09443.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09444.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09445.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09446.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09447.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09448.JPG

========== 保留列表 ==========
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09432.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09438.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09440.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09441.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09442.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09449.JPG
$python3 ~/Downloads/myapps/delete_unwanted_images.py ./keep.txt

========== 执行模式 ==========
REAL DELETE
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09429.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09430.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09431.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09433.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09434.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09435.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09436.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09437.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09439.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09443.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09444.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09445.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09446.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09447.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09448.JPG

========== 保留文件 ==========
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09432.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09438.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09440.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09441.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09442.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09449.JPG

========== 统计 ==========
模式: DELETE
计划删除文件数: 15
保留文件数: 6
实际删除成功数: 15

========== 删除列表 ==========
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09429.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09430.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09431.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09433.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09434.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09435.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09436.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09437.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09439.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09443.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09444.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09445.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09446.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09447.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09448.JPG

========== 保留列表 ==========
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09432.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09438.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09440.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09441.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09442.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09449.JPG
ls
HDK09432.JPG  HDK09438.JPG  HDK09440.JPG  HDK09441.JPG  HDK09442.JPG  HDK09449.JPG  keep.txt

脚本内容:

$cat ~/Downloads/myapps/delete_unwanted_images.py 
#!/usr/bin/env python3
import os
import sys

IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".arw", ".heic", ".heif", ".hif", ".dng"}

def load_keep_list(path):
    exact_keep = set()
    stem_keep = set()

    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            raw = line.strip()
            if not raw:
                continue

            stem, ext = os.path.splitext(raw)

            if ext:  # 有扩展名 → 精确匹配
                exact_keep.add(raw)
            else:    # 无扩展名 → stem 匹配
                stem_keep.add(stem)

    return exact_keep, stem_keep

def is_image_file(filename):
    ext = os.path.splitext(filename)[1].lower()
    return ext in IMAGE_EXTS

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 delete_unwanted_images.py ./keep.txt [--dry-run]")
        sys.exit(1)

    keep_path = sys.argv[1]
    dry_run = "--dry-run" in sys.argv[2:]

    root_dir = os.getcwd()

    if not os.path.exists(keep_path):
        print(f"[ERROR] keep.txt not found: {keep_path}")
        sys.exit(1)

    exact_keep, stem_keep = load_keep_list(keep_path)

    to_delete = []
    to_keep = []

    # 扫描文件
    for root, dirs, files in os.walk(root_dir):
        for file in files:
            if not is_image_file(file):
                continue

            full_path = os.path.join(root, file)
            stem = os.path.splitext(file)[0]

            if (file in exact_keep) or (stem in stem_keep):
                to_keep.append(full_path)
            else:
                to_delete.append(full_path)

    deleted_count = 0

    print("\n========== 执行模式 ==========")
    print("DRY RUN" if dry_run else "REAL DELETE")

    # 删除逻辑
    for path in to_delete:
        if dry_run:
            print(f"[DRY-RUN] 将删除: {path}")
        else:
            try:
                os.remove(path)
                print(f"已删除: {path}")
                deleted_count += 1
            except Exception as e:
                print(f"[ERROR] 删除失败 {path}: {e}")

    # 保留输出
    print("\n========== 保留文件 ==========")
    for path in to_keep:
        print(f"保留: {path}")

    # 统计
    print("\n========== 统计 ==========")
    print(f"模式: {'DRY-RUN' if dry_run else 'DELETE'}")
    print(f"计划删除文件数: {len(to_delete)}")
    print(f"保留文件数: {len(to_keep)}")

    if not dry_run:
        print(f"实际删除成功数: {deleted_count}")

    # 列表输出
    print("\n========== 删除列表 ==========")
    for path in to_delete:
        print(path)

    print("\n========== 保留列表 ==========")
    for path in to_keep:
        print(path)

if __name__ == "__main__":
    main()

keep.txt 内容示例

$cat keep.txt 
HDK09432
HDK09438
HDK09440
HDK09441
HDK09442
HDK09449

Linux Desktop 使用 NVIDIA 独显驱动 Google Earth Pro 和 WeChat

2026-06-12 16:09:00

更新说明

2026年6月17日 09:00

gnome 桌面自带这样的功能,右键就能“使用独立显卡启动”,看下图

gnome直接右键就可以使用独显打开.jpg


原理

NVIDIA 官方提供了 PRIME Render Offload 机制,通过环境变量让指定程序走独显渲染:

  • __NV_PRIME_RENDER_OFFLOAD=1 — 告诉 NVIDIA 驱动将 GL/Vulkan 请求路由到独显
  • __GLX_VENDOR_LIBRARY_NAME=nvidia — 强制 GLX 使用 NVIDIA 的 vendor 库

适用条件

  • 双显卡(Intel 核显 + NVIDIA 独显)笔记本
  • 已安装 NVIDIA 闭源驱动(nvidia-driver)
  • 桌面环境不限(GNOME / KDE / XFCE 等均可)

操作步骤

1. 修改 .desktop 文件

将系统目录下的 .desktop 文件拷贝到用户目录(避免被系统更新覆盖):

cp /usr/share/applications/google-earth-pro.desktop ~/.local/share/applications/
cp /usr/share/applications/wechat.desktop ~/.local/share/applications/

修改 Exec 行,在程序路径前加上环境变量:

sed -i 's|^Exec=/opt/google/earth/pro/google-earth-pro %f|Exec=env __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia /opt/google/earth/pro/google-earth-pro %f|' \
  ~/.local/share/applications/google-earth-pro.desktop

sed -i 's|^Exec=/usr/bin/wechat %U|Exec=env __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia /usr/bin/wechat %U|' \
  ~/.local/share/applications/wechat.desktop

2. 注销并重新登录

让 GNOME Shell 重新加载 .desktop 文件缓存。

3. 验证

nvidia-smi

启动 Google Earth Pro 和 WeChat 后,应看到类似输出:

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|    0   N/A  N/A           2327      G   /usr/bin/gnome-shell                    130MiB |
|    0   N/A  N/A          25233      G   ...gle/earth/pro/googleearth-bin        112MiB |
|    0   N/A  N/A          25399      G   ...ns-seed-version --log-level=2         72MiB |
+-----------------------------------------------------------------------------------------+

googleearth-bin 为 Google Earth Pro,...ns-seed-version 为 WeChat 的 GPU 子进程。

说明

  • Chrome 未纳入:日常浏览网页、看视频等场景 Intel 核显已足够,独显无感知收益且徒增功耗发热。如有 3D/WebGL 重负载需求,可自行添加 --use-gl=egl 参数。
  • 环境变量直接写在 Exec 行:避免了对 shell alias 或外部脚本的依赖,桌面菜单点击即可生效。
  • 恢复默认:删除 ~/.local/share/applications/ 下对应的 .desktop 文件即可回到系统默认配置。
  • 为什么不使用独显直连?虽然改成独显直连,桌面都会变流畅,大部分程序都会默认走英伟达独显,比如 telegram-desktop,但是对我来说有一个致命性问题,腾讯会议的视频功能无法使用了,摄像头也无法调用,无法分享桌面,别人分享桌面我这边查看也是黑屏,这个问题暂时无法解决,只能依赖腾讯会议那边针对性开发:在英伟达独显上正常工作

实现了一套Linux下快速查词/翻译句子的工作流-linux-quick-translate

2026-06-01 10:25:05

GitHub 地址:https://github.com/hellodk34/linux-quick-translate

在 Linux 桌面环境快速查词/翻译句子,绑定系统全局快捷键。支持各种 Linux 发行版和不同的 DE(桌面环境)。详细食用方法请看 GitHub

选中文本 → 复制( Ctrl+C )→ 按 Super+C 弹出结果。

  • 单个英文单词:本地 sdcv 词典查询
  • 句子/段落:在线翻译 API

这样在浏览器或者系统中的任何地方阅读英文文献/文章都能快速翻译,复制文章的全部文本也能翻译整篇文章。

截图展示:
presentation.webp

欢迎使用!

开发了一个基于和风天气 API 的 Android 天气应用

2026-06-01 10:06:31

使用截图

screenshot1.jpg
screenshot2.jpg

介绍请看 GitHub,这里懒得再写一遍了。地址 https://github.com/hellodk34/checkitout_weather_android