4. PCM硬件参数约束设置

任务目标

在ALSA驱动中,PCM设备需要向用户空间声明自身能力范围。本案例实现一个虚拟PCM驱动,通过snd_pcm_hw_constraint_*系列函数将采样率硬性限制为8000 Hz,同时约束通道数为单声道、格式为S16_LE,使应用层无法配置超出硬件能力之外的参数。

核心实现

#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>

static struct snd_soc_card test_card;
static struct snd_soc_dai_link test_dai_link;

static const struct snd_pcm_hw_constraint_list rate_constraint = {
    .count = 1,
    .list = (const unsigned int[]) { 8000 },
};

static const struct snd_pcm_hw_constraint_list channels_constraint = {
    .count = 1,
    .list = (const unsigned int[]) { 1 },
};

static int test_pcm_open(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    int ret;

    /* 限制采样率为8000 Hz */
    ret = snd_pcm_hw_constraint_list(runtime, 0,
            SNDRV_PCM_HW_PARAM_RATE, &rate_constraint);
    if (ret < 0)
        return ret;

    /* 限制通道数为1 */
    ret = snd_pcm_hw_constraint_list(runtime, 0,
            SNDRV_PCM_HW_PARAM_CHANNELS, &channels_constraint);
    if (ret < 0)
        return ret;

    /* 限制格式为S16_LE */
    ret = snd_pcm_hw_constraint_single(runtime,
            SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_FORMAT_S16_LE);
    if (ret < 0)
        return ret;

    return 0;
}

static const struct snd_pcm_ops test_pcm_ops = {
    .open = test_pcm_open,
    .hw_params = snd_pcm_hw_params_noop,
    .prepare = snd_pcm_prepare_noop,
    .trigger = snd_pcm_trigger_noop,
};

static int test_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
    struct snd_pcm *pcm = rtd->pcm;
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &test_pcm_ops);
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &test_pcm_ops);
    return 0;
}

static struct snd_soc_dai_driver test_dai = {
    .name = "test-dai",
    .playback = {
        .channels_min = 1,
        .channels_max = 1,
        .rates = SNDRV_PCM_RATE_8000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE,
    },
    .capture = {
        .channels_min = 1,
        .channels_max = 1,
        .rates = SNDRV_PCM_RATE_8000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE,
    },
};

static struct snd_soc_dai_link test_dai_link = {
    .name = "test-link",
    .stream_name = "test-stream",
    .cpus = &(struct snd_soc_dai_link_component) { .dai_name = "test-dai" },
    .num_cpus = 1,
    .codecs = &(struct snd_soc_dai_link_component) { .name = "snd-soc-dummy" },
    .num_codecs = 1,
    .platforms = &(struct snd_soc_dai_link_component) { .name = "snd-soc-dummy" },
    .num_platforms = 1,
    .init = test_pcm_new,
};

static struct snd_soc_card test_card = {
    .name = "test-card",
    .dai_link = &test_dai_link,
    .num_links = 1,
};

static int test_platform_probe(struct platform_device *pdev)
{
    test_card.dev = &pdev->dev;
    return snd_soc_register_card(&test_card);
}

static int test_platform_remove(struct platform_device *pdev)
{
    return snd_soc_unregister_card(&test_card);
}

static struct platform_driver test_pdrv = {
    .probe = test_platform_probe,
    .remove = test_platform_remove,
    .driver = {
        .name = "test-pcm-constraint",
    },
};

static struct platform_device *test_pdev;

static int __init test_init(void)
{
    int ret;

    ret = platform_driver_register(&test_pdrv);
    if (ret)
        return ret;

    test_pdev = platform_device_register_simple("test-pcm-constraint", -1, NULL, 0);
    if (IS_ERR(test_pdev)) {
        platform_driver_unregister(&test_pdrv);
        return PTR_ERR(test_pdev);
    }
    return 0;
}

static void __exit test_exit(void)
{
    platform_device_unregister(test_pdev);
    platform_driver_unregister(&test_pdrv);
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

编译与加载

# 编译(需内核源码树)
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

# 加载
insmod pcm_constraint.ko

# 确认声卡注册
cat /proc/asound/cards | grep test-card

验证方法

使用aplay尝试以不同参数播放,观察约束是否生效:

# 应成功:8000 Hz / 单声道 / S16_LE
aplay -D hw:0,0 -r 8000 -c 1 -f S16_LE test.wav

# 应失败:尝试48000 Hz
aplay -D hw:0,0 -r 48000 -c 1 -f S16_LE test.wav
# 预期输出:aplay: set_params:1239: Unable to install hw params

# 应失败:尝试双声道
aplay -D hw:0,0 -r 8000 -c 2 -f S16_LE test.wav
# 预期输出:aplay: set_params:1239: Unable to install hw params

通过判定标准:aplay使用8000 Hz/1ch/S16_LE时正常播放;使用其他采样率或通道数时报参数错误,说明snd_pcm_hw_constraint_*约束已生效。

关键说明

  • snd_pcm_hw_constraint_list用于限制参数为列表中的离散值,本案例列表仅含8000和1,等效于固定值;
  • snd_pcm_hw_constraint_single直接将格式锁定为S16_LE;
  • DAI定义中的rates/channels_min/formats是静态能力声明,而open回调中的约束是运行时动态限制,两者共同作用确保参数合法;
  • 若需允许范围而非固定值,可改用snd_pcm_hw_constraint_minmax或snd_pcm_hw_constraint_step等函数。