这部分内容比较繁琐,,先从初始化开始了解里面参数意义,以period size
这个参数为线索来追终代码流程.
int snd_pcm_hw_constraints_init(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
//ASOC对应hw params有参数大小限制,一定要在它的范围内,否则设置无效.
//相关信息都保存在hw_constraints中.
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
int k, err;
......
//每个hw参数规则都会被添加进去来作追踪.
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
snd_pcm_hw_rule_mulkdiv, (void*) 8,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
......
}
struct snd_pcm_hw_constraints {
//mask用来表示后面的intervals是否能访问,没有设置是不行的.
struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK -
SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
//保存所有hw param值.
struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
//目前已有规则总数
unsigned int rules_num;
//存规则势必要分配空间,当rules_num达到rules_all的时候,
//会每次分配16个存储空间,而不是一次分配一个,这样可以提高效率,
//因此rules_all总是>=rules_num.
unsigned int rules_all;
//所有规则用此结构表示.
struct snd_pcm_hw_rule *rules;
};
snd_pcm_hw_rule_add():
int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,
int var,
snd_pcm_hw_rule_func_t func, void *private,
int dep, ...)
{
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
struct snd_pcm_hw_rule *c;
......
//每添加一个interval,constrs->rules_num就会随着增加,
//当超过constrs->rules_all,就需要分配新空间用于存储interval.
if (constrs->rules_num >= constrs->rules_all) {
struct snd_pcm_hw_rule *new;
//基于效率,每次直接分配16个.
unsigned int new_rules = constrs->rules_all + 16;
//分配的结构是struct snd_pcm_hw_rule
new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL);
......
}
//填充rules,下面的值会以snd_pcm_hw_constraints_init()调用中的实际值为例.
c = &constrs->rules[constrs->rules_num];
//为0
c->cond = cond;
//后面会回调snd_pcm_hw_rule_mulkdiv()来计算period size.
c->func = func;
//SNDRV_PCM_HW_PARAM_PERIOD_SIZE
c->var = var;
//为8,后面计算period size会当系数用.
c->private = private;
k = 0;
while (1) {
......
//SNDRV_PCM_HW_PARAM_PERIOD_BYTES和SNDRV_PCM_HW_PARAM_FRAME_BITS
//存到deps数组中,后面通过这两个来重计算period size.
c->deps[k++] = dep;
.....
constrs->rules_num++;
......
}
有些参数如period count, channel等,一开始它的值会根据硬件限制定好大范围了,如rk_pcm.c
static const struct snd_pcm_hardware rockchip_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S16_LE,
//最少双通道,最大八通道.
.channels_min = 2,
.channels_max = 8,
.buffer_bytes_max = 2*1024*1024,/*128*1024,*/
.period_bytes_min = 64,
.period_bytes_max = 512*1024,/*32*1024,//2048*4,///PAGE_SIZE*2,*/
.periods_min = 3,
.periods_max = 128,
.fifo_size = 16,
};
然后会被保存到hw interval中:
int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream)
{
......
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE,
hw->rate_min, hw->rate_max);
......
}
int snd_pcm_hw_constraint_minmax(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
unsigned int min, unsigned int max)
{
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
struct snd_interval t;
t.min = min;
t.max = max;
t.openmin = t.openmax = 0;
t.integer = 0;
//新的配置更新到旧的interval中.
return snd_interval_refine(constrs_interval(constrs, var), &t);
}
这样默认就有个范围值了,这样,当用户空间设置时,就不能超过这个硬件设置范围,否则就会出错了!
struct pcm_config pcm_config_in = {
.channels = 2,
.rate = 44100,
.period_size = 16,
.period_count = 32,
.format = PCM_FORMAT_S16_LE,
};
然后会被传到pcm_open()中
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config)
{
......
//config->period_size被传到了这里.
//函数用来设置period size的最小值
//然后会通过下面的ioctl传给kernel看是否超出范围了.
param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
......
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
oops(pcm, errno, "cannot set hw params");
goto fail_close;
}
//返回设置可以设置的值.
config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
......
}
param_set_min():
static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val)
{
//判断当前SNDRV_PCM_HW_PARAM_PERIOD_SIZE是否在interval列表范围内,肯定成立.
if (param_is_interval(n)) {
//取出对应的interval
struct snd_interval *i = param_to_interval(p, n);
//赋值最大最小都是config->period_size
i->min = val;
i->max = val;
i->integer = 1;
}
}
看ioctl进入kernel
int snd_pcm_hw_refine(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
unsigned int k;
struct snd_pcm_hardware *hw;
struct snd_interval *i = NULL;
struct snd_mask *m = NULL;
struct snd_pcm_hw_constraints *constrs = &substream->runtime->hw_constraints;
unsigned int rstamps[constrs->rules_num];
unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1];
......
//先对mask做处理,这里不太明白mask和后面提到的interval有什么联系?
for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
//取出对应的mask
m = hw_param_mask(params, k);
//每个mask使用了两个字节总共64位
//如果一个都没设置,那就不处理参数了,当前正常流程不会发生.
if (snd_mask_empty(m))
return -EINVAL;
//rmask用于存每个interval对应的位,如果没设置就不能往下走.
if (!(params->rmask & (1 << k)))
continue;
//调试信息.
#ifdef RULES_DEBUG
printk(KERN_DEBUG "%s = ", snd_pcm_hw_param_names[k]);
printk("%04x%04x%04x%04x -> ", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
#endif
//更新旧的mask信息
changed = snd_mask_refine(m, constrs_mask(constrs, k));
#ifdef RULES_DEBUG
printk("%04x%04x%04x%04x\n", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
#endif
//如果前后有变化,则标记.
if (changed)
params->cmask |= 1 << k;
if (changed < 0)
return changed;
}
//依次处理每个interval
for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
//取出
i = hw_param_interval(params, k);
......
//替换旧的interval
changed = snd_interval_refine(i, constrs_interval(constrs, k));
......
}
......
do {
again = 0;
for (k = 0; k < constrs->rules_num; k++) {
struct snd_pcm_hw_rule *r = &constrs->rules[k];
......
//执行之前注册的回调函数,这里是snd_pcm_hw_rule_mulkdiv
changed = r->func(params, r);
......
}
} while (again);
......
}
snd_pcm_hw_rule_mulkdiv():
static int snd_pcm_hw_rule_mulkdiv(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval t;
//参数一对应SNDRV_PCM_HW_PARAM_PERIOD_BYTES的配置
//参数二的是值是8
//参数三对应SNDRV_PCM_HW_PARAM_FRAME_BITS的配置
snd_interval_mulkdiv(hw_param_interval_c(params, rule->deps[0]),
(unsigned long) rule->private,
hw_param_interval_c(params, rule->deps[1]), &t);
//更新配置
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
}
SNDRV_PCM_HW_PARAM_FRAME_BITS()的设置在snd_pcm_hw_constraints_init()中
snd_pcm_hw_constraints_init() {
......
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));
......
}
而具体的值是在HAL层设置的:
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config)
{
......
//对应pcm_config_in的配置,为16*2 = 32.
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS,
pcm_format_to_bits(config->format) * config->channels);
......
}
而SNDRV_PCM_HW_PARAM_PERIOD_BYTES的值是在
int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream)
{
......
//也就是rockchip_pcm_hardware变量中的值.
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
hw->period_bytes_min, hw->period_bytes_max);
......
}
snd_interval_mulkdiv():
void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k,
const struct snd_interval *b, struct snd_interval *c)
{
......
//c->min = a->min * k / b->max;
c->min = muldiv32(a->min, k, b->max, &r);
c->openmin = (r || a->openmin || b->openmax);
if (b->min > 0) {
c->max = muldiv32(a->max, k, b->min, &r);
if (r) {
c->max++;
c->openmax = 1;
} else
c->openmax = (a->openmax || b->openmin);
} else {
c->max = UINT_MAX;
c->openmax = 0;
}
c->integer = 0;
}