1. ALSA驱动框架初探
任务目标
编写一个最小的虚拟声卡驱动,向 ALSA 核心注册 snd_card,并在该声卡上创建一个 PCM 播放设备。驱动加载后,系统应出现 /dev/snd/pcmC0D0p 设备节点,且可以通过 ALSA 工具或应用程序打开该设备进行基本的播放操作。
环境与依赖
- Linux 内核源码树(本案例基于 5.15 版本验证,其他版本接口基本一致)
- 内核头文件与编译工具链(gcc、make)
- 目标系统已启用 ALSA 框架(
CONFIG_SND) - 需要 root 权限加载/卸载模块
实现原理
ALSA 驱动核心对象是 snd_card,每个声卡对应一个 card 实例。PCM 设备通过 snd_pcm_new() 创建并挂载到 card 上。本驱动不涉及真实硬件,仅分配内存作为 DMA 缓冲区,并实现 playback 方向的 open/close/hw_params/prepare/trigger 回调,使设备能够被打开并完成基本的数据流操作。
关键完整代码
// minimal_alsa_card.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#define DRV_NAME "minialsa"
static struct snd_card *card;
static struct snd_pcm *pcm;
static void *dma_area;
static int snd_mini_pcm_open(struct snd_pcm_substream *substream)
{
return 0;
}
static int snd_mini_pcm_close(struct snd_pcm_substream *substream)
{
return 0;
}
static int snd_mini_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}
static int snd_mini_pcm_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
static int snd_mini_pcm_prepare(struct snd_pcm_substream *substream)
{
return 0;
}
static int snd_mini_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
return 0;
}
static snd_pcm_uframes_t snd_mini_pcm_pointer(struct snd_pcm_substream *substream)
{
return 0;
}
static const struct snd_pcm_ops snd_mini_pcm_ops = {
.open = snd_mini_pcm_open,
.close = snd_mini_pcm_close,
.hw_params = snd_mini_pcm_hw_params,
.hw_free = snd_mini_pcm_hw_free,
.prepare = snd_mini_pcm_prepare,
.trigger = snd_mini_pcm_trigger,
.pointer = snd_mini_pcm_pointer,
};
static int snd_mini_probe(struct platform_device *pdev)
{
int err;
err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card);
if (err < 0)
return err;
err = snd_pcm_new(card, "mini PCM", 0, 1, 0, &pcm);
if (err < 0)
goto fail_pcm;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_mini_pcm_ops);
pcm->dma_device = &pdev->dev;
err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
&pdev->dev, 64 * 1024, 64 * 1024);
if (err < 0)
goto fail_pcm;
strcpy(card->driver, DRV_NAME);
strcpy(card->shortname, "Mini ALSA Card");
strcpy(card->longname, "Mini Virtual ALSA Card");
err = snd_card_register(card);
if (err < 0)
goto fail_register;
platform_set_drvdata(pdev, card);
return 0;
fail_register:
snd_pcm_lib_preallocate_free_for_all(pcm);
fail_pcm:
snd_card_free(card);
return err;
}
static int snd_mini_remove(struct platform_device *pdev)
{
card = platform_get_drvdata(pdev);
snd_card_free(card);
return 0;
}
static struct platform_driver mini_driver = {
.driver = {
.name = DRV_NAME,
},
.probe = snd_mini_probe,
.remove = snd_mini_remove,
};
static struct platform_device *mini_device;
static int __init mini_init(void)
{
int err;
err = platform_driver_register(&mini_driver);
if (err < 0)
return err;
mini_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0);
if (IS_ERR(mini_device)) {
platform_driver_unregister(&mini_driver);
return PTR_ERR(mini_device);
}
return 0;
}
static void __exit mini_exit(void)
{
platform_device_unregister(mini_device);
platform_driver_unregister(&mini_driver);
}
module_init(mini_init);
module_exit(mini_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALSA Course");
MODULE_DESCRIPTION("Minimal virtual ALSA sound card");
编译与加载
# 在内核源码树外编译(需已配置内核头文件)
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载模块
sudo insmod minimal_alsa_card.ko
# 验证设备节点
ls -l /dev/snd/pcmC0D0p
# 查看声卡注册信息
cat /proc/asound/cards
验证方法
- 加载模块后执行
ls /dev/snd/pcmC0D0p,若存在该节点则说明 PCM 设备已成功注册。 - 执行
cat /proc/asound/cards,应能看到编号为 0 的声卡,名称包含 "Mini ALSA Card"。 - 使用
aplay -D hw:0,0 /usr/share/sounds/alsa/Front_Center.wav播放测试音频(若系统无此文件,可用speaker-test -D hw:0,0 -c 2 -t wav替代)。命令能正常执行且无 I/O 错误,即表示设备可被 ALSA 应用层打开并完成数据提交。
常见问题
- 设备节点未出现:检查
dmesg是否有错误输出,确认snd_card_register是否成功;确认系统声卡编号是否已被其他声卡占用(若 card 编号不为 0,节点名会相应变化)。 - aplay 报 no such device:确认 PCM 设备方向正确(本驱动只创建了 playback 方向),使用
aplay -D hw:0,0而非arecord。 - 编译报错:确认内核头文件版本与运行内核一致,且
CONFIG_SND已启用。