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)。支持图片(JPG/ARW/HEIF 等)和视频(MP4 + 配套 XML 元数据)的筛选删除。

相关说明:

  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. 支持的扩展名:.jpg .jpeg .png .arw .heic .heif .hif .dng .mp4 .xml
  6. 索尼视频的 XML 元数据文件会自动匹配对应的 MP4 主文件(如 C0054M01.XML 跟随 C0054.MP4 同进退),无需在 keep.txt 中单独列出

照片工作流

索尼 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_media.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_media.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

视频工作流

索尼 A7C2 录制视频后,存储卡上的目录结构为 PRIVATE/M4ROOT/CLIP/。与照片不同,视频不按日期分子目录,所有日期的视频文件都平铺在同一个 CLIP/ 目录下,因此 keep.txt 会较长。不过首次维护好之后,每次拍摄后只需要在末尾追加新增的 clip 号即可。

每个视频片段会生成:

  • C0054.MP4 — 视频主文件
  • C0054M01.XML — 配套元数据文件(自动跟随 MP4 同进退)

cd /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/

ls
C0054.MP4  C0054M01.XML  C0055.MP4  C0055M01.XML  C0056.MP4  C0056M01.XML  C0057.MP4  C0057M01.XML
C0061.MP4  C0061M01.XML  C0062.MP4  C0062M01.XML  C0063.MP4  C0063M01.XML  C0068.MP4  C0068M01.XML
C0070.MP4  C0070M01.XML  C0071.MP4  C0071M01.XML  C0074.MP4  C0074M01.XML  C0076.MP4  C0076M01.XML
C0077.MP4  C0077M01.XML  C0079.MP4  C0079M01.XML  C0080.MP4  C0080M01.XML  C0083.MP4  C0083M01.XML
C0084.MP4  C0084M01.XML  C0085.MP4  C0085M01.XML  C0086.MP4  C0086M01.XML  C0087.MP4  C0087M01.XML
C0088.MP4  C0088M01.XML  C0089.MP4  C0089M01.XML  C0090.MP4  C0090M01.XML  C0091.MP4  C0091M01.XML
C0092.MP4  C0092M01.XML  C0093.MP4  C0093M01.XML  C0094.MP4  C0094M01.XML  C0095.MP4  C0095M01.XML
C0096.MP4  C0096M01.XML

keep.txt 内容(只写 stem,不包含扩展名):

$cat keep.txt 
# start to record keep.txt list
C0054
C0055
C0056
C0057
C0061
C0062
C0063
C0068
C0070
C0071
C0074
C0076
C0077
C0080
C0083
C0086
C0092
C0094
C0095
C0096
# 2026-07-04

先模拟运行确认预期:

$python3 ~/Downloads/myapps/delete_unwanted_media.py ./keep.txt --dry-run

========== 执行模式 ==========
DRY RUN
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0079M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0079.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0084M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0084.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0085M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0085.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0087M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0087.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0088M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0088.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0089M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0089.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0090M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0090.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0091M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0091.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0093M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0093.MP4

========== 保留文件 ==========
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0054M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0054.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0055M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0055.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0056M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0056.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0057M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0057.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0061M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0061.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0062M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0062.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0063M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0063.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0068M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0068.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0071M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0071.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0070M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0070.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0074M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0074.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0077M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0077.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0076M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0076.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0080M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0080.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0083M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0083.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0086M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0086.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0092M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0092.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0094M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0094.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0095M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0095.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0096M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0096.MP4

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

========== 删除列表 ==========
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0079M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0079.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0084M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0084.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0085M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0085.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0087M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0087.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0088M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0088.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0089M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0089.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0090M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0090.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0091M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0091.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0093M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0093.MP4

========== 保留列表 ==========
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0054M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0054.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0055M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0055.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0056M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0056.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0057M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0057.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0061M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0061.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0062M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0062.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0063M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0063.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0068M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0068.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0071M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0071.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0070M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0070.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0074M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0074.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0077M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0077.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0076M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0076.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0080M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0080.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0083M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0083.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0086M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0086.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0092M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0092.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0094M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0094.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0095M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0095.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0096M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0096.MP4

确认无误后去掉 --dry-run 执行实际删除。


脚本内容

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

TARGET_EXTS = {".jpg", ".jpeg", ".png", ".arw", ".heic", ".heif", ".hif", ".dng", ".mp4", ".xml"}

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 or raw.startswith("#"):
                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 get_effective_stem(filename, mp4_stems):
    stem, ext = os.path.splitext(filename)
    if ext.lower() == ".xml" and mp4_stems:
        match = ""
        for mp4_stem in mp4_stems:
            if len(mp4_stem) > len(match) and stem.startswith(mp4_stem):
                match = mp4_stem
        if match:
            return match
    return stem

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

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 delete_unwanted_media.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):
        mp4_stems = [os.path.splitext(f)[0] for f in files if f.lower().endswith(".mp4")]
        for file in files:
            if not is_target_file(file):
                continue

            full_path = os.path.join(root, file)
            stem = get_effective_stem(file, mp4_stems)

            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

keep.txt 内容示例(视频,支持 # 注释行):

$cat keep.txt
# start to record keep.txt list
C0054
C0055
C0056
C0057
C0061
C0062
C0063
C0068
C0070
C0071
C0074
C0076
C0077
C0080
C0083
C0086
C0092
C0094
C0095
C0096
# 2026-07-04

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