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等函数。