3. PCM设备创建与销毁
任务说明
在Linux ALSA声卡驱动中,PCM设备是音频数据传输的核心接口。本案例目标是在驱动中创建PCM播放(playback)和捕获(capture)设备,并实现对应的释放函数,确保模块卸载时能够正确清理所有ALSA资源,避免内存泄漏或设备节点残留。
核心实现
#include <linux/module.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#define DRIVER_NAME "demo_pcm"
static struct snd_card *card;
static struct snd_pcm *pcm_playback;
static struct snd_pcm *pcm_capture;
/* PCM播放操作集 */
static const struct snd_pcm_ops playback_ops = {
.open = NULL, /* 本案例仅演示创建/销毁,不实现IO */
.close = NULL,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = NULL,
.hw_free = NULL,
.prepare = NULL,
.trigger = NULL,
.pointer = NULL,
};
/* PCM捕获操作集 */
static const struct snd_pcm_ops capture_ops = {
.open = NULL,
.close = NULL,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = NULL,
.hw_free = NULL,
.prepare = NULL,
.trigger = NULL,
.pointer = NULL,
};
static int __init demo_pcm_init(void)
{
int err;
/* 1. 创建声卡 */
err = snd_card_new(&platform_device, -1, DRIVER_NAME,
THIS_MODULE, 0, &card);
if (err < 0) {
pr_err("snd_card_new failed: %d\n", err);
return err;
}
/* 2. 创建播放PCM设备 */
err = snd_pcm_new(card, "demo-playback", 0, 1, 0, &pcm_playback);
if (err < 0) {
pr_err("snd_pcm_new playback failed: %d\n", err);
goto err_card;
}
snd_pcm_set_ops(pcm_playback, SNDRV_PCM_STREAM_PLAYBACK,
&playback_ops);
strcpy(pcm_playback->name, "Demo PCM Playback");
/* 3. 创建捕获PCM设备 */
err = snd_pcm_new(card, "demo-capture", 1, 0, 1, &pcm_capture);
if (err < 0) {
pr_err("snd_pcm_new capture failed: %d\n", err);
goto err_pcm_playback;
}
snd_pcm_set_ops(pcm_capture, SNDRV_PCM_STREAM_CAPTURE,
&capture_ops);
strcpy(pcm_capture->name, "Demo PCM Capture");
/* 4. 注册声卡 */
err = snd_card_register(card);
if (err < 0) {
pr_err("snd_card_register failed: %d\n", err);
goto err_pcm_capture;
}
pr_info("Demo PCM card registered\n");
return 0;
err_pcm_capture:
/* 释放函数:snd_pcm_free会清理PCM及其子设备 */
snd_pcm_free(pcm_capture);
err_pcm_playback:
snd_pcm_free(pcm_playback);
err_card:
snd_card_free(card);
return err;
}
static void __exit demo_pcm_exit(void)
{
/* 卸载时先释放PCM设备,再释放声卡 */
if (pcm_capture)
snd_pcm_free(pcm_capture);
if (pcm_playback)
snd_pcm_free(pcm_playback);
if (card)
snd_card_free(card);
pr_info("Demo PCM module unloaded\n");
}
module_init(demo_pcm_init);
module_exit(demo_pcm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ALSA PCM device create/destroy demo");
编译与加载
# Makefile(与源码同目录)
obj-m := demo_pcm.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# 编译并加载
make
sudo insmod demo_pcm.ko
# 查看设备节点
cat /proc/asound/cards
# 预期输出包含 "Demo PCM" 声卡条目
ls -l /dev/snd/
# 预期出现 pcmCxXx p 和 pcmCxXx c 设备节点
# 例如:pcmC1D0p(播放)、pcmC1D0c(捕获)
# 卸载模块
sudo rmmod demo_pcm
# 验证清理
cat /proc/asound/cards # Demo PCM条目消失
ls -l /dev/snd/ # 对应设备节点被移除
验证要点
- 加载成功:
dmesg | tail应显示Demo PCM card registered,无错误信息。 - 设备节点存在:
/dev/snd/下出现播放(p后缀)和捕获(c后缀)两个PCM设备节点。 - 卸载干净:
rmmod后,声卡条目和PCM设备节点全部消失,dmesg无资源泄漏警告。
关键说明
snd_pcm_new的第4、5个参数分别指定播放和捕获子流数量,本案例各创建1个。- 释放顺序必须为:先释放PCM设备(
snd_pcm_free),再释放声卡(snd_card_free),否则可能导致悬空指针。 - 操作集中的
ioctl使用snd_pcm_lib_ioctl提供默认处理,其余回调置NULL仅用于演示创建/销毁流程;实际驱动需实现完整操作集。 - 本案例使用
platform_device作为父设备,实际驱动中应替换为具体总线设备。