3. PCM硬件参数配置
任务说明
在ALSA驱动中,hw_params回调负责验证并采纳用户空间传入的PCM硬件参数(采样率、通道数、采样格式)。本案例实现一个完整的hw_params回调,包含参数合法性检查与错误返回,并提供一个最小测试入口验证各分支逻辑。
核心实现
#include <linux/module.h>
#include <linux/soundcard.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
/* 支持的参数范围 */
#define SUPPORTED_RATE_MIN 8000
#define SUPPORTED_RATE_MAX 192000
#define SUPPORTED_CH_MIN 1
#define SUPPORTED_CH_MAX 2
/* 设备私有数据结构 */
struct my_pcm_device {
unsigned int rate;
unsigned int channels;
snd_pcm_format_t format;
};
/* 检查采样率是否合法 */
static int check_rate(unsigned int rate)
{
if (rate < SUPPORTED_RATE_MIN || rate > SUPPORTED_RATE_MAX)
return -EINVAL;
/* 仅支持常见整数倍率 */
if (rate % 8000 != 0 && rate % 11025 != 0)
return -EINVAL;
return 0;
}
/* 检查通道数是否合法 */
static int check_channels(unsigned int channels)
{
if (channels < SUPPORTED_CH_MIN || channels > SUPPORTED_CH_MAX)
return -EINVAL;
return 0;
}
/* 检查采样格式是否合法 */
static int check_format(snd_pcm_format_t format)
{
switch (format) {
case SNDRV_PCM_FORMAT_S16_LE:
case SNDRV_PCM_FORMAT_S24_LE:
case SNDRV_PCM_FORMAT_S32_LE:
return 0;
default:
return -EINVAL;
}
}
/* hw_params 回调:验证并保存硬件参数 */
static int my_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct my_pcm_device *dev = snd_pcm_substream_chip(substream);
unsigned int rate, channels;
snd_pcm_format_t format;
int err;
/* 从params中提取用户空间请求的参数 */
rate = params_rate(params);
channels = params_channels(params);
format = params_format(params);
/* 参数合法性检查 */
err = check_rate(rate);
if (err < 0) {
dev_err(substream->pcm->device, "unsupported rate: %u\n", rate);
return err;
}
err = check_channels(channels);
if (err < 0) {
dev_err(substream->pcm->device, "unsupported channels: %u\n", channels);
return err;
}
err = check_format(format);
if (err < 0) {
dev_err(substream->pcm->device, "unsupported format: %d\n", format);
return err;
}
/* 保存参数到设备结构 */
dev->rate = rate;
dev->channels = channels;
dev->format = format;
/* 通知ALSA内核参数已确定 */
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
return 0;
}
/* 将回调绑定到pcm ops */
static struct snd_pcm_ops my_pcm_ops = {
.hw_params = my_pcm_hw_params,
/* 其他回调如open/close/prepare等按需填充 */
};
/* 测试入口:模拟参数设置流程 */
static int __init test_hw_params_init(void)
{
struct my_pcm_device test_dev = {0};
struct snd_pcm_substream fake_substream = {0};
struct snd_pcm_hw_params fake_params;
int ret;
/* 绑定设备私有数据 */
fake_substream.private_data = &test_dev;
/* 测试用例1:合法参数 (48000Hz, 2ch, S16_LE) */
memset(&fake_params, 0, sizeof(fake_params));
fake_params.rate_num = 48000;
fake_params.rate_den = 1;
fake_params.channels = 2;
fake_params.format = SNDRV_PCM_FORMAT_S16_LE;
ret = my_pcm_hw_params(&fake_substream, &fake_params);
pr_info("Test1 (valid): ret=%d, rate=%u, ch=%u, fmt=%d\n",
ret, test_dev.rate, test_dev.channels, test_dev.format);
/* 测试用例2:非法采样率 (100000Hz) */
memset(&fake_params, 0, sizeof(fake_params));
fake_params.rate_num = 100000;
fake_params.rate_den = 1;
fake_params.channels = 2;
fake_params.format = SNDRV_PCM_FORMAT_S16_LE;
ret = my_pcm_hw_params(&fake_substream, &fake_params);
pr_info("Test2 (bad rate): ret=%d\n", ret);
/* 测试用例3:非法格式 (U8) */
memset(&fake_params, 0, sizeof(fake_params));
fake_params.rate_num = 48000;
fake_params.rate_den = 1;
fake_params.channels = 2;
fake_params.format = SNDRV_PCM_FORMAT_U8;
ret = my_pcm_hw_params(&fake_substream, &fake_params);
pr_info("Test3 (bad format): ret=%d\n", ret);
return 0;
}
static void __exit test_hw_params_exit(void)
{
pr_info("hw_params test module unloaded\n");
}
module_init(test_hw_params_init);
module_exit(test_hw_params_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ALSA hw_params callback example");
编译与加载
# 内核源码树内编译
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载模块(触发测试入口)
sudo insmod hw_params_test.ko
# 查看测试输出
dmesg | tail -20
# 卸载模块
sudo rmmod hw_params_test
验证方法
加载模块后,通过dmesg观察输出,预期看到以下结果:
- Test1:返回
ret=0,且设备结构保存了rate=48000, ch=2, fmt=2(S16_LE的枚举值),证明合法参数被正确采纳并存储。 - Test2:返回
ret=-22(-EINVAL),证明非法采样率被拒绝。 - Test3:返回
ret=-22,证明不支持的格式被拒绝。
若三个测试用例的返回值与上述一致,且Test1中保存的参数值正确,则hw_params回调的参数校验与保存逻辑验证通过。在实际驱动集成中,该回调会被ALSA框架在snd_pcm_hw_params系统调用路径上自动调用,无需手动触发。
关键说明
- 测试入口使用伪造的
substream和params结构,仅用于验证回调逻辑,不涉及真实硬件操作。 - 实际驱动中需在
snd_pcm_ops中完整填充open、close、prepare等回调,本案例聚焦hw_params本身。 - 参数范围(采样率、通道数、格式)可根据具体硬件能力调整,检查逻辑保持不变。