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本身。
  • 参数范围(采样率、通道数、格式)可根据具体硬件能力调整,检查逻辑保持不变。