3. 车载音频PCM播放通路驱动实现
任务目标
在ALSA SOC驱动中实现 hw_params 回调,完成PCM硬件参数(采样率、采样格式、通道数)的校验与配置,确保用户空间申请的硬件参数与Codec/DAI实际能力匹配。
核心实现
#include <sound/soc.h>
#include <linux/module.h>
/* 硬件能力定义:采样率、格式、通道数 */
static const struct snd_pcm_hw_constraint_list hw_constraints_sample_rates = {
.count = 4,
.list = (const unsigned int[]){ 8000, 16000, 44100, 48000 },
.mask = 0,
};
static int codec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_pcm_get_substream(substream);
struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int sample_rate = params_rate(params);
snd_pcm_format_t format = params_format(params);
unsigned int channels = params_channels(params);
int ret;
/* 1. 采样率校验 */
switch (sample_rate) {
case 8000:
case 16000:
case 44100:
case 48000:
break;
default:
dev_err(codec_dai->dev, "Unsupported sample rate: %u\n", sample_rate);
return -EINVAL;
}
/* 2. 采样格式校验(仅支持S16_LE和S24_LE) */
switch (format) {
case SNDRV_PCM_FORMAT_S16_LE:
case SNDRV_PCM_FORMAT_S24_LE:
break;
default:
dev_err(codec_dai->dev, "Unsupported format: %d\n", format);
return -EINVAL;
}
/* 3. 通道数校验(仅支持单声道/双声道) */
if (channels < 1 || channels > 2) {
dev_err(codec_dai->dev, "Unsupported channels: %u\n", channels);
return -EINVAL;
}
/* 4. 将参数下发到Codec硬件寄存器(示例:通过I2C写入) */
ret = snd_soc_component_update_bits(codec_dai->component,
CODEC_SAMPLE_RATE_REG,
SAMPLE_RATE_MASK,
sample_rate << SAMPLE_RATE_SHIFT);
if (ret < 0)
return ret;
ret = snd_soc_component_update_bits(codec_dai->component,
CODEC_FORMAT_REG,
FORMAT_MASK,
(format == SNDRV_PCM_FORMAT_S24_LE) ?
FORMAT_S24_LE : FORMAT_S16_LE);
if (ret < 0)
return ret;
ret = snd_soc_component_update_bits(codec_dai->component,
CODEC_CHANNEL_REG,
CHANNEL_MASK,
channels - 1);
if (ret < 0)
return ret;
return 0;
}
/* DAI驱动结构 */
static const struct snd_soc_dai_ops codec_dai_ops = {
.hw_params = codec_hw_params,
};
static struct snd_soc_dai_driver codec_dai_drv = {
.name = "codec-dai",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
},
.ops = &codec_dai_ops,
};
/* 注册Codec组件 */
static struct snd_soc_component_driver codec_component_drv = {
.name = "codec-component",
.dai_drv = &codec_dai_drv,
.num_dai = 1,
};
static int codec_probe(struct platform_device *pdev)
{
return devm_snd_soc_register_component(&pdev->dev,
&codec_component_drv,
&codec_dai_drv, 1);
}
static struct platform_driver codec_driver = {
.probe = codec_probe,
.driver = {
.name = "codec-driver",
.owner = THIS_MODULE,
},
};
module_platform_driver(codec_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ALSA Codec hw_params example");
关键说明
- 能力声明:
codec_dai_drv中的rates、formats、channels_min/max是PCM核心用于参数协商的依据,必须与hw_params回调中的校验逻辑一致。 - 寄存器地址:代码中的
CODEC_SAMPLE_RATE_REG、CODEC_FORMAT_REG、CODEC_CHANNEL_REG及掩码/移位宏需根据实际Codec数据手册定义,此处为示例占位。 - 参数下发:通过
snd_soc_component_update_bits将参数写入Codec寄存器,该函数保证读-改-写操作的原子性。
编译与验证
# 编译为内核模块
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载模块
insmod codec_hw_params.ko
# 使用arecord/aplay验证(以播放为例)
aplay -D hw:0,0 -f S16_LE -c 2 -r 44100 test.wav
# 预期行为:
# 1. 参数匹配时,播放正常无错误
# 2. 使用不支持的参数(如 -r 96000)时,返回错误:
# "Unsupported sample rate: 96000"
验证判定标准:dmesg 中无 Unsupported 错误日志,且 aplay 正常完成播放;使用非法参数时驱动返回 -EINVAL,用户空间工具报错退出。