2. 案例2

任务目标

排查音频通路中设备节点缺失问题:确认节点是否存在、定位缺失原因、完成修复并验证通路恢复。本案例聚焦于Linux ALSA体系下/dev/snd/设备节点缺失的典型场景。

问题现象与定位思路

音频应用报错cannot open device或No such file or directory,通常对应设备节点缺失。按以下顺序定位:

  1. 检查/dev/snd/目录是否存在及内容;
  2. 确认声卡驱动是否已加载(cat /proc/asound/cards);
  3. 若驱动正常但节点缺失,判定为udev规则未生效或节点被误删;
  4. 手动触发节点创建或重启udev服务。

环境依赖

Linux系统(本案例基于Ubuntu/Debian验证),需具备alsa-utils(提供aplay/arecord)及root权限。内核已加载snd_soc_xxx或snd_hda_intel等声卡驱动。

关键完整代码

以下脚本实现节点缺失检测、原因判断与自动修复:

#!/bin/bash
# audio_node_repair.sh - 检测并修复音频设备节点缺失

SND_DIR="/dev/snd"
CARDS_FILE="/proc/asound/cards"

echo "=== [1/4] 检查声卡驱动状态 ==="
if [ ! -f "$CARDS_FILE" ] || [ ! -s "$CARDS_FILE" ]; then
    echo "FAIL: 未检测到声卡驱动,请先加载声卡驱动模块"
    exit 1
fi
echo "OK: 声卡驱动已加载"
cat "$CARDS_FILE"

echo "=== [2/4] 检查设备节点目录 ==="
if [ ! -d "$SND_DIR" ]; then
    echo "INFO: $SND_DIR 不存在,尝试创建"
    mkdir -p "$SND_DIR"
fi

echo "=== [3/4] 检查具体设备节点 ==="
# 从 /proc/asound 获取实际存在的设备编号
EXPECTED_NODES=""
for card_dir in /proc/asound/card*; do
    [ -d "$card_dir" ] || continue
    card_id=$(basename "$card_dir" | sed 's/card//')
    # 检查pcm设备
    for pcm_dir in "$card_dir"/pcm*; do
        [ -d "$pcm_dir" ] || continue
        pcm_id=$(basename "$pcm_dir" | sed 's/pcm//')
        # pcm_id 格式如 0p (播放) / 0c (录音)
        dir_suffix=""
        case "$pcm_id" in
            *p) dir_suffix="p" ;;
            *c) dir_suffix="c" ;;
        esac
        dev_num="${pcm_id%[pc]}"
        # 期望节点: controlC0, pcmC0D0p, pcmC0D0c, timer
        EXPECTED_NODES="$EXPECTED_NODES controlC$card_id"
        EXPECTED_NODES="$EXPECTED_NODES pcmC${card_id}D${dev_num}${dir_suffix}"
    done
done
EXPECTED_NODES="$EXPECTED_NODES timer"

# 去重并检查
MISSING=0
for node in $(echo "$EXPECTED_NODES" | tr ' ' '\n' | sort -u); do
    if [ ! -e "$SND_DIR/$node" ]; then
        echo "MISSING: $SND_DIR/$node"
        MISSING=1
    fi
done

if [ $MISSING -eq 0 ]; then
    echo "OK: 所有预期设备节点均存在"
    ls -l "$SND_DIR"
    exit 0
fi

echo "=== [4/4] 尝试修复 ==="
# 方法1: 触发udev重新生成节点
echo "尝试触发udev重新生成节点..."
udevadm trigger --subsystem-match=snd
sleep 1

# 方法2: 若仍缺失,手动创建节点(需从 /proc/asound 获取主次设备号)
REMAIN_MISSING=0
for node in $(echo "$EXPECTED_NODES" | tr ' ' '\n' | sort -u); do
    if [ ! -e "$SND_DIR/$node" ]; then
        # 从 /proc/asound 解析设备号
        case "$node" in
            controlC*)
                cid=$(echo "$node" | sed 's/controlC//')
                dev_line=$(grep -E "^ *${cid} \[" /proc/asound/cards)
                if [ -n "$dev_line" ]; then
                    # 主设备号通常为116 (ALSA), 次设备号 = card_id * 16
                    major=116
                    minor=$((cid * 16))
                    mknod "$SND_DIR/$node" c $major $minor
                    echo "CREATED: $SND_DIR/$node (c $major $minor)"
                fi
                ;;
            pcmC*)
                # 格式: pcmC0D0p -> card=0, device=0, dir=p
                cid=$(echo "$node" | sed -E 's/pcmC([0-9]+)D.*/\1/')
                did=$(echo "$node" | sed -E 's/pcmC[0-9]+D([0-9]+).*/\1/')
                dir_suffix=$(echo "$node" | grep -o '[pc]$')
                # 次设备号: card*32 + device*2 + (0=playback, 1=capture)
                if [ "$dir_suffix" = "p" ]; then
                    minor=$((cid * 32 + did * 2))
                else
                    minor=$((cid * 32 + did * 2 + 1))
                fi
                mknod "$SND_DIR/$node" c 116 $minor
                echo "CREATED: $SND_DIR/$node (c 116 $minor)"
                ;;
            timer)
                mknod "$SND_DIR/$node" c 116 33
                echo "CREATED: $SND_DIR/$node (c 116 33)"
                ;;
        esac
        REMAIN_MISSING=1
    fi
done

# 设置权限
chown root:audio "$SND_DIR"/* 2>/dev/null
chmod 660 "$SND_DIR"/* 2>/dev/null

echo "=== 修复完成,当前节点状态 ==="
ls -l "$SND_DIR"

运行与验证

执行修复脚本:

sudo bash audio_node_repair.sh

验证通路恢复:

# 1. 确认节点已存在
ls -l /dev/snd/

# 2. 用aplay测试播放通路(需有音频文件或使用默认测试音)
aplay -D default /usr/share/sounds/alsa/Front_Center.wav

# 3. 若aplay可用但无实际音频输出,可仅验证设备打开成功:
timeout 2 aplay -D default /dev/zero || true
# 预期输出包含 "Playing raw data" 且无 "cannot open" 错误

判定通过标准:

  • 脚本输出显示所有预期节点均存在(无MISSING标记);
  • ls /dev/snd/能看到controlC0、pcmC0D0p、pcmC0D0c、timer等节点;
  • aplay命令能成功打开设备,不报cannot open device错误。

常见问题

  • 驱动未加载:脚本第一步即报错退出,需先modprobe snd_soc_xxx或检查内核配置;
  • 手动mknod后仍无法访问:确认当前用户属于audio组(sudo usermod -aG audio $USER),或节点权限为660 root:audio;
  • 重启后节点再次丢失:说明udev规则异常,检查/etc/udev/rules.d/中是否有覆盖ALSA默认规则的配置,必要时删除自定义规则并执行udevadm control --reload。