4. PCM操作集实现
任务目标
在ALSA驱动中,struct snd_pcm_ops是PCM设备与内核ALSA框架之间的核心接口。本案例实现一个最小但完整的PCM操作集,包含open、close、hw_params、hw_free、prepare、trigger回调,并通过ALSA框架的标准调用流程验证各回调按预期顺序被触发。
核心实现
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#define DRV_NAME "demo-pcm-ops"
static int demo_pcm_open(struct snd_pcm_substream *substream)
{
pr_info("%s: open called\n", DRV_NAME);
return 0;
}
static int demo_pcm_close(struct snd_pcm_substream *substream)
{
pr_info("%s: close called\n", DRV_NAME);
return 0;
}
static int demo_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
pr_info("%s: hw_params called, rate=%d, channels=%d\n",
DRV_NAME, params_rate(params), params_channels(params));
return 0;
}
static int demo_pcm_hw_free(struct snd_pcm_substream *substream)
{
pr_info("%s: hw_free called\n", DRV_NAME);
return 0;
}
static int demo_pcm_prepare(struct snd_pcm_substream *substream)
{
pr_info("%s: prepare called\n", DRV_NAME);
return 0;
}
static int demo_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
pr_info("%s: trigger called, cmd=%d\n", DRV_NAME, cmd);
return 0;
}
static struct snd_pcm_ops demo_pcm_ops = {
.open = demo_pcm_open,
.close = demo_pcm_close,
.hw_params = demo_pcm_hw_params,
.hw_free = demo_pcm_hw_free,
.prepare = demo_pcm_prepare,
.trigger = demo_pcm_trigger,
};
static int demo_pcm_new(struct snd_card *card, struct snd_device *device)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(card, "demo-pcm", 0, 0, 1, &pcm);
if (err < 0)
return err;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &demo_pcm_ops);
return 0;
}
static int demo_probe(struct platform_device *pdev)
{
struct snd_card *card;
int err;
err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card);
if (err < 0)
return err;
err = demo_pcm_new(card, NULL);
if (err < 0)
goto fail;
strcpy(card->driver, DRV_NAME);
strcpy(card->shortname, "Demo PCM Ops");
strcpy(card->longname, "Demo PCM Ops Driver");
err = snd_card_register(card);
if (err < 0)
goto fail;
platform_set_drvdata(pdev, card);
return 0;
fail:
snd_card_free(card);
return err;
}
static int demo_remove(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
snd_card_free(card);
return 0;
}
static struct platform_driver demo_driver = {
.probe = demo_probe,
.remove = demo_remove,
.driver = {
.name = DRV_NAME,
},
};
module_platform_driver(demo_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALSA Course");
MODULE_DESCRIPTION("Demo PCM ops implementation");
编译与加载
# Makefile
obj-m += demo_pcm_ops.o
# 编译
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载
sudo insmod demo_pcm_ops.ko
# 确认设备节点生成
cat /proc/asound/cards
# 应看到类似: 0 [Demo ]: Demo - Demo PCM Ops
验证调用流程
# 使用arecord触发PCM操作(播放方向用aplay)
arecord -D hw:0,0 -f S16_LE -r 44100 -c 2 -d 2 /dev/null
# 观察内核日志
dmesg | tail -20
预期日志顺序(与代码逻辑严格对应):
demo-pcm-ops: open called
demo-pcm-ops: hw_params called, rate=44100, channels=2
demo-pcm-ops: prepare called
demo-pcm-ops: trigger called, cmd=1 # SNDRV_PCM_TRIGGER_START
demo-pcm-ops: trigger called, cmd=0 # SNDRV_PCM_TRIGGER_STOP
demo-pcm-ops: hw_free called
demo-pcm-ops: close called
判定标准
- 加载模块后
/proc/asound/cards出现新声卡,无错误信息; - 执行
arecord后,dmesg中按上述顺序出现全部7条回调日志; - 回调参数正确:
hw_params中的rate和channels与arecord参数一致; trigger收到start(1)和stop(0)两个命令。
常见问题
- 无设备节点:确认
snd_pcm_new的playback参数为1(最后一个参数),且snd_pcm_set_ops设置了PLAYBACK方向; - hw_params未被调用:arecord必须指定格式参数(如
-f S16_LE),否则ALSA可能跳过参数协商; - trigger只出现一次:若使用
-d 2指定时长,停止时才会触发stop命令;直接Ctrl+C可能不触发stop。