ALSA subsystem (thirteen) ------snd_pcm_hw_refine hardware parameter redefinition

Hello! Here is Kite's blog,

Welcome to communicate with me.


hw parameter indicates the hardware parameters used by the current audio stream, such as the format used by the current audio stream, the number of channels, which audio output port is used, and so on.
Because a codec or a dai can support multiple formats, for example, a dai can transmit 16bit pcm and 24bit pcm, so which format is used now, this question is specified by the upper layer application.
For example, if the upper-layer application needs to play a 16-bit pcm, it will open a pcm logical device, and notify the audio driver of the parameters of the currently playing audio stream through ioctrl, and the audio driver will perform corresponding operations according to these configurations, but the hardware supports 16bit is not supported, the hardware parameters need to be re-standardized, and the snd_pcm_hw_refine function will be called.

struct snd_mask {
    
    
        __u32 bits[(SNDRV_MASK_MAX+31)/32];
};

struct snd_interval {
    
    
		unsigned int min, max;
		//openmin和openmax代表开集:
		//openmin和openmax都为1时,代表开区间,range范围为(min,max)
		//openmin=1,openmax=0时,range范围为(min,max] 即开区间和闭区间
		unsigned int openmin:1,
					 openmax:1,
					 //integer等于1,表示it不是一个范围区间,而是一个固定的interger整型值
					 integer:1,
					 empty:1;
}

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];
        unsigned int stamp = 2;
        int changed, again;

        params->info = 0;
        params->fifo_size = 0;
        //requested masks
        if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_SAMPLE_BITS))
                params->msbits = 0;
        if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_RATE)) {
    
    
                params->rate_num = 0;
                params->rate_den = 0;
        }
		//分别是access type、format、subformat
        for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
    
    
        		//用mask来表示,mask[]数组下标代表类型,以k=1为例,mask[1]就是format,mask[]->bits[]代表支持当格式
        		//mask[]->bits[]数组中每个元素的bits代表一个格式
                m = hw_param_mask(params, k);
                if (snd_mask_empty(m))
                        return -EINVAL;
                if (!(params->rmask & (1 << k)))
                        continue;
                //mask重定义,mask取自上层params,初始时mask[]->bits[]是0xff,这里需要与硬件判断支持哪些参数
                //mask->bits[i] &= constrs->bits[i];
                //constrs->bits[]也就是硬件支持的参数,只有constrs支持的相应bit才能设置有效
                changed = snd_mask_refine(m, constrs_mask(constrs, k));
                //changed masks,如果成功重定义了参数,cmask记录下是哪个参数被改变
                if (changed)
                        params->cmask |= 1 << k;
                if (changed < 0)
                        return changed;
        }
		//分别是bits per sample、bits per frams、channels、rate、period size等
        for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
    
    
        		//不同于format等,像rate是一个范围值,不能用每个bits表示支持的参数,所以用intervals
        		//intervals->min ~ intervals->max 代表范围
                i = hw_param_interval(params, k);
                if (snd_interval_empty(i))
                        return -EINVAL;
                if (!(params->rmask & (1 << k)))
                        continue;
                //interval重定义,interval取自上层params,这里和constrs对比,也就是和硬件hw参数对比
                //取interval、constrs中最大的min,最小的max进行赋值
                changed = snd_interval_refine(i, constrs_interval(constrs, k));
                //如果成功重定义了参数,cmask记录下是哪个参数被改变
                if (changed)
                        params->cmask |= 1 << k;
                if (changed < 0)
                        return changed;
        }

        for (k = 0; k < constrs->rules_num; k++)
                rstamps[k] = 0;
        //快速建立有变动的数组表
        for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++)
                vstamps[k] = (params->rmask & (1 << k)) ? 1 : 0;
        do {
    
    
                again = 0;
                //rule规则检查遍历,硬件参数hw约束规则rule
                for (k = 0; k < constrs->rules_num; k++) {
    
    
                        struct snd_pcm_hw_rule *r = &constrs->rules[k];
                        unsigned int d;
                        int doit = 0;
                        //rule的约束cond,上层在配置param时设置同样的flags,可以跳过这条规则
                        if (r->cond && !(r->cond & params->flags))
                                continue;
                        //dependence,依赖
                        for (d = 0; r->deps[d] >= 0; d++) {
    
    
                                if (vstamps[r->deps[d]] > rstamps[k]) {
    
    
                                        doit = 1;
                                        break;
                                }
                        }
                        if (!doit)
                                continue;
                        //规则检查,对params施加约束,snd_pcm_hw_constraints_init函数里添加了一些简单基础当规则
                        //各个pcm逻辑设备open时也可以通过snd_pcm_hw_rule_add添加自己的rule
                        changed = r->func(params, r);
                        //rstamps记录当前rule被最后一次更新对应的时间戳
                        rstamps[k] = stamp;
                        //如果rule规则检查成功改写了params参数
                        //r->var实际上就是参数标识,标识该rule是针对的哪一个参数
                        if (changed && r->var >= 0) {
    
    
                                params->cmask |= (1 << r->var);
                                //vstamps记录当前var(参数标识,例如SNDRV_PCM_HW_PARAM_FORMAT)被最后一次更新对应的时间戳
                                vstamps[r->var] = stamp;
                                //again置位
                                again = 1;
                        }
                        if (changed < 0)
                                return changed;
                        //时间戳,每次都会执行stamp++递增时间戳,让"时间"跳动
                        stamp++;
                }
        //当多条rule约束同一参数时,如果参数被rule给修改了,则表示该参数被此rule约束了
        //一旦一个rule修改了该参数的值,那么所有其他的rule都必须重新对该参数调用一次约束函数
        //直到所有约束该参数的rule都不再会修改该参数的值为止
        } while (again);
        //设置msbits
        if (!params->msbits) {
    
    
                i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
                if (snd_interval_single(i))
                        params->msbits = snd_interval_value(i);
        }
		//rate denominator
        if (!params->rate_den) {
    
    
                i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
                if (snd_interval_single(i)) {
    
    
                        params->rate_num = snd_interval_value(i);
                        params->rate_den = 1;
                }
        }

        hw = &substream->runtime->hw;
        if (!params->info) {
    
    
                params->info = hw->info & ~(SNDRV_PCM_INFO_FIFO_IN_FRAMES |
                                            SNDRV_PCM_INFO_DRAIN_TRIGGER);
                if (!hw_support_mmap(substream))
                        params->info &= ~(SNDRV_PCM_INFO_MMAP |
                                          SNDRV_PCM_INFO_MMAP_VALID);
        }
        //设置fifo size
        if (!params->fifo_size) {
    
    
                m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
                i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
                if (snd_mask_min(m) == snd_mask_max(m) &&
                    snd_interval_min(i) == snd_interval_max(i)) {
    
    
                        changed = substream->ops->ioctl(substream,
                                        SNDRV_PCM_IOCTL1_FIFO_SIZE, params);
                        if (changed < 0)
                                return changed;
                }
        }
        params->rmask = 0;
        return 0;
}

Code details are shown in comments.

There is a very important parameter in the snd_pcm_hw_refine function: constrs = &substream->runtime->hw_constraints
hw_constraints is the hardware constraint. Where is the value assigned here?
In fact, it is in the snd_pcm_hw_constraints_complete function:

snd_pcm_open_substream
	snd_pcm_hw_constraints_init
	substream->ops->open
	snd_pcm_hw_constraints_complete
		snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask)
		snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats)
		snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_SUBFORMAT, 1 << SNDRV_PCM_SUBFORMAT_STD)
		snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, hw->channels_min, hw->channels_max)
		snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, hw->rate_min, hw->rate_max)
		//等等......	

For example, for SNDRV_PCM_HW_PARAM_FORMAT, the supported format is hw->formats, which comes from the hardware.
For SNDRV_PCM_HW_PARAM_CHANNELS, the channel number range is [hw->channels_min, hw->channels_max], determined by the hardware.
For SNDRV_PCM_HW_PARAM_RATE, the rate is [hw->rate_min, hw->rate_max], determined by the hardware.
If the parameters passed down from the upper params are not within these ranges, they will be redefined.

Regarding the rule check, there is another place to pay attention to:

The general execution process of processing multiple rules to constrain the same parameter:
when multiple rules constrain the same parameter, each rule will call the constraint function to process the parameter. If the
parameter is modified by the rule, it means that the parameter is constrained by the rule Once a rule modifies
the value of this parameter, all other rules must re-call the constraint function on this parameter until
all the rules that constrain this parameter will no longer modify the value of this parameter.
rstamps, vstamps, stamp, again these variables are transferred to this service

Where is the rule inside add added?
In fact, it is also in the snd_pcm_hw_constraints_init function:

int snd_pcm_hw_constraints_init(struct snd_pcm_substream *substream)
{
    
    
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
        int k, err;

        for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
    
    
                snd_mask_any(constrs_mask(constrs, k));
        }

        for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
    
    
                snd_interval_any(constrs_interval(constrs, k));
        }

        snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
        snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
        snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
        snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
        snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));

        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
                                   snd_pcm_hw_rule_format, NULL,
                                   SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
                                  snd_pcm_hw_rule_sample_bits, NULL,
                                  SNDRV_PCM_HW_PARAM_FORMAT,
                                  SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
                                  snd_pcm_hw_rule_div, NULL,
                                  SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
                                  snd_pcm_hw_rule_mul, NULL,
                                  SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
                                  snd_pcm_hw_rule_mulkdiv, (void*) 8,
                                  SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
                                  snd_pcm_hw_rule_mulkdiv, (void*) 8,
                                  SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
                                  snd_pcm_hw_rule_div, NULL,
                                  SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
                                  snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
                                  SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_TIME, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
                                  snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
                                  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_BUFFER_TIME, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS,
                                  snd_pcm_hw_rule_div, NULL,
                                  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
                                  snd_pcm_hw_rule_div, NULL,
                                  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1);
        if (err < 0)
                return err;
        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);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
                                  snd_pcm_hw_rule_muldivk, (void*) 1000000,
                                  SNDRV_PCM_HW_PARAM_PERIOD_TIME, SNDRV_PCM_HW_PARAM_RATE, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
                                  snd_pcm_hw_rule_mul, NULL,
                                  SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
                                  snd_pcm_hw_rule_mulkdiv, (void*) 8,
                                  SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
                                  snd_pcm_hw_rule_muldivk, (void*) 1000000,
                                  SNDRV_PCM_HW_PARAM_BUFFER_TIME, SNDRV_PCM_HW_PARAM_RATE, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
                                  snd_pcm_hw_rule_muldivk, (void*) 8,
                                  SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
                                  snd_pcm_hw_rule_muldivk, (void*) 8,
                                  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
                                  snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
                                  SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1);
        if (err < 0)
                return err;
        err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
                                  snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
                                  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1);
        if (err < 0)
                return err;
        return 0;
}

Let's dig a hole here~ I will have time to chew on the calculation process between the rules later.


Reference:
Analysis of alsa sound card driver snd_pcm_hw_refine function
ASOC audio path analysis of Qualcomm msm8996 platform (based on androidN and linux3.1x)

Guess you like

Origin blog.csdn.net/Guet_Kite/article/details/114314003