UEditor 微信小程序 element OpenCV4 Gradle highcharts import fonts vcpkg routing rss seo angularjs版本 vue绑定点击事件 npm安装vue bootstrap后台管理系统模板 android富文本框架 matlab颜色代码 bootstrap文本框 python的range python入门教程 python获取时间戳 java访问数据库 java开发语言 linux密码忘记 python网站开发实例 js删除数组指定元素 丁丁下载 字幕提取 msn格式 php四舍五入 go程序设计语言 安卓adb 发射爱心的图片 cinema4d下载 pp安卓助手 寂静城 android计算器 ps颜色查找 cf兑换券
当前位置: 首页 > 学习教程  > 

Linux音频驱动之三:pcm接口的调用流程

2020/10/16 17:47:44 文章标签: pcm接口

本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记 一.pcm设备的open 由“Linux音频驱动之一:音频驱动注册流程” 这篇文章可知,pcm device调用的是snd_pcm_dev_register函数注册的。 snd_pcm_dev_register err snd_register_device_f…

本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记

一.pcm设备的open

由“Linux音频驱动之一:音频驱动注册流程” 这篇文章可知,pcm device调用的是snd_pcm_dev_register函数注册的。

snd_pcm_dev_register
err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,
						  &snd_pcm_f_ops[cidx],pcm, str, dev);

当注册播放pcm设备时,fops是snd_pcm_f_ops[0];
当注册录制pcm设备时,fops是snd_pcm_f_ops[1];
现在以播放pcm设备为例,看一下snd_pcm_f_ops[0]。

const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.aio_write =		snd_pcm_aio_write,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	dummy_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.aio_read =		snd_pcm_aio_read,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	dummy_get_unmapped_area,
	}
};

当打开pcm device设备时,就会调用上面的snd_pcm_playback_open函数。
进入snd_pcm_playback_open函数分析。

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
	struct snd_pcm *pcm;

	pcm = snd_lookup_minor_data(iminor(inode),
				    SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
	return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
}
  • 获得snd_pcm数据。
    iminor(inode) = MINOR(inode->i_rdev)
    inode->i_rdev是文件对应设备的设备号,MINOR(inode->i_rdev)是次设备号。
    我们在pcm device这个设备注册的时候,在snd_minors数组里面寻找一个没有使用的元素,把fops,snd_pcm,type等信息保存。把该元素的下标minor作为次设备号。
    主设备号是116
#define CONFIG_SND_MAJOR	116

dev_t = 116 << 20 | minor
最后把dev_t注册进了内核。
现在open时根据次设备号找到snd_minors[minor],得到snd_pcm数据。

  • 调用snd_pcm_open函数
    1.调用snd_card_file_add函数。
    这个函数将打开的文件保存,放入了snd_card的files_list链表。作用是用于跟踪连接状态,避免热插拔释放繁忙资源。
err = snd_card_file_add(pcm->card, file);
  1. 调用snd_pcm_open_file函数。
err = snd_pcm_open_file(file, pcm, stream, &pcm_file);
  • snd_pcm_open_file函数
    1.调用snd_pcm_open_substream函数获取snd_pcm_substream数据。
err = snd_pcm_open_substream(pcm, stream, file, &substream);

2.申请一个snd_pcm_file结构体,把获取到的snd_pcm_substream数据保存到snd_pcm_file.substream。
然后把snd_pcm_file赋值给file->private_data。
在这里插入图片描述

二. snd_pcm_open_substream函数分析
  • 调用函数snd_pcm_attach_substream获取snd_pcm_substream。
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
    pstr = &pcm->streams[stream];
    for (substream = pstr->substream; substream; substream = substream->next)
		if (!SUBSTREAM_BUSY(substream))
			break;

根据snd_pcm.streams[0]得到播放的snd_pcm_str。
循环查找snd_pcm_str的substream,找到substream->ref_count == 0 的那个substream。这个substream就是我们我们上面想要的snd_pcm_substream数据。

runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
runtime->status = snd_malloc_pages(size, GFP_KERNEL);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
runtime->control = snd_malloc_pages(size, GFP_KERNEL);

申请一个snd_pcm_runtime类型的数据runtime,然后给runtime->status和runtime->control分配空间。
填充substream数据。

substream->runtime = runtime;
substream->private_data = pcm->private_data;
substream->ref_count = 1;
substream->f_flags = file->f_flags;
pstr->substream_opened++;

最终得到的数据如下:
在这里插入图片描述

  • 调用snd_pcm_hw_constraints_init函数
    snd_pcm_hw_constraints_init函数里面涉及到几个重要的结构体。
struct snd_pcm_hw_constraints {
	struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - 
			 SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
	struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
			     SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};

SNDRV_PCM_HW_PARAM_FIRST_MASK = 0,SNDRV_PCM_HW_PARAM_LAST_MASK = 2;
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL = 8, SNDRV_PCM_HW_PARAM_LAST_INTERVAL = 19
因此:

struct snd_pcm_hw_constraints {
	struct snd_mask masks[3];
	struct snd_interval intervals[12];
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};
#define SNDRV_MASK_MAX	256
struct snd_mask 
{
	__u32 bits[(SNDRV_MASK_MAX+31)/32];
};
struct snd_interval {
    unsigned int min, 
    unsigned int max,
    unsigned int openmin:1,
    openmax:1,
    integer:1,
    empty:1;
};

因此:

struct snd_pcm_hw_constraints 
{
    struct snd_mask masks[3] =
    {
        int bits[8];     //设置Access type
        int bits[8];     //设置Format
        int bits[8];    //设置Subformat
    };
	struct snd_interval intervals[12]=
    {
        //设置采样数据位数
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置每帧数据位数
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置通道
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置采样率
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置中断之间的时间间隔
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置中断之间的数据帧(不知道是个啥意思)
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置终端之间的bytes(不知道是个啥意思)
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置中断个数
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置缓冲时间
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置缓冲大小
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置缓冲大小
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置Approx tick duration
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },       
    }
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};

masks元素的前两个设置成0xff

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

设置后:

struct snd_mask masks[3] =
{
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;   //设置Access type
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;     //设置Format
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;    //设置Subformat
};

初始化intervals[12]的所有元素,元素里面的变量全部设置为0。

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

static inline void snd_interval_any(struct snd_interval *i)
{
	i->min = 0;
	i->openmin = 0;
	i->max = UINT_MAX;
	i->openmax = 0;
	i->integer = 0;
	i->empty = 0;
}

初始化intervals[12]的整数标记,就是intervals[12]中的元素哪几个要设置成整数的。

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));

通道,缓冲大小,采样位数,帧位数等要设置成整数。

添加rule规则,就是给snd_pcm_hw_constraints.rules赋值。

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);

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;
	unsigned int k;
	va_list args;
	va_start(args, dep);
	if (constrs->rules_num >= constrs->rules_all) {
		struct snd_pcm_hw_rule *new;
		unsigned int new_rules = constrs->rules_all + 16;
		new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL);
		if (!new)
			return -ENOMEM;
		if (constrs->rules) {
			memcpy(new, constrs->rules,
			       constrs->rules_num * sizeof(*c));
			kfree(constrs->rules);
		}
		constrs->rules = new;
		constrs->rules_all = new_rules;
	}
	c = &constrs->rules[constrs->rules_num];
	c->cond = cond;
	c->func = func;
	c->var = var;
	c->private = private;
	k = 0;
	while (1) {
		if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps)))
			return -EINVAL;
		c->deps[k++] = dep;
		if (dep < 0)
			break;
		dep = va_arg(args, int);
	}
	constrs->rules_num++;
	va_end(args);
	return 0;
}

其中va_start,va_arg,va_end是传入可变参数用的。
va_start(args, dep):也就是让args指针指向dep变量所在的地址;
va_arg(args, int):让args指针偏移到下一个地址,同时返回里面的值,int类型。偏移结束的条件是取出来的值小于0。
va_end(args):args = 0
上面的代码把args指针里面的取出来的值保存在c->deps数组中。把上面的snd_pcm_hw_rule_add函数翻译一下得到:

constrs.rules[0] = 
{
    cond = 0;
    func = snd_pcm_hw_rule_format;
    var = SNDRV_PCM_HW_PARAM_FORMAT
    deps[0] = SNDRV_PCM_HW_PARAM_SAMPLE_BITS = 8;
    private = NULL;
}

下面添加了很多的规则,就不一一介绍了。

  • 调用substream->ops->open函数
    根据Linux 音频驱动这一章节中的内容:
    给snd_pcm.streams[i].substream[i].ops赋值,mmap,pointer,ioctl等几个函数使用s3c24xx_pcm_ops的,其他的使用soc_pcm_ops自己的。soc_pcm_ops如下:
soc_pcm_ops.open		= soc_pcm_open,
soc_pcm_ops.close		= soc_codec_close,
soc_pcm_ops.hw_params	= soc_pcm_hw_params,
soc_pcm_ops.hw_free	= soc_pcm_hw_free,
soc_pcm_ops.prepare	= soc_pcm_prepare,
soc_pcm_ops.trigger	= soc_pcm_trigger,
soc_pcm_ops.mmap = platform->pcm_ops->mmap;
soc_pcm_ops.pointer = platform->pcm_ops->pointer;
soc_pcm_ops.ioctl = platform->pcm_ops->ioctl;
soc_pcm_ops.copy = platform->pcm_ops->copy;
soc_pcm_ops.silence = platform->pcm_ops->silence;
soc_pcm_ops.ack = platform->pcm_ops->ack;
soc_pcm_ops.page = platform->pcm_ops->page;
if (playback)
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);

if (capture)
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);

可以知道substream->ops->open = soc_pcm_open函数。
这一步就是调用soc_pcm_open函数。

  1. 调用cpu_dai->ops->startup函数。
if (cpu_dai->ops->startup) 
{
    ret = cpu_dai->ops->startup(substream, cpu_dai);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open interface %s\n",
            cpu_dai->name);
        goto out;
    }
}

cpu_dai = s3c24xx_i2s_dai,s3c24xx_i2s_dai->ops->open = NULL,这里不执行。

2.调用platform->pcm_ops->open

if (platform->pcm_ops->open) 
{
    ret = platform->pcm_ops->open(substream);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
        goto platform_err;
    }
}

platform = s3c24xx_soc_platform,s3c24xx_soc_platform->pcm_ops->open = s3c24xx_pcm_open
进入s3c24xx_pcm_open函数。

static int s3c24xx_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct s3c24xx_runtime_data *prtd;
	pr_debug("Entered %s\n", __func__);
	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
	snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware);
	prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
	if (prtd == NULL)
		return -ENOMEM;

	spin_lock_init(&prtd->lock);
	runtime->private_data = prtd;
	return 0;
}

设置SNDRV_PCM_HW_PARAM_PERIODS项时,要用整形数据。

snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);

对snd_pcm_runtime.hw赋值。
runtime->hw.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;
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE |SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |SNDRV_PCM_FMTBIT_S8;
runtime->hw.period_bytes_min = 4Kb;
runtime->hw.period_bytes_max = 8Kb;
runtime->hw.periods_min = 2;
runtime->hw.periods_max =128 ;
runtime->hw.buffer_bytes_max =128Kb ;
runtime->hw.fifo_size = 32;

3.调用codec_dai->ops->startup函数。

if (codec_dai->ops->startup) 
{
    ret = codec_dai->ops->startup(substream, codec_dai);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open codec %s\n",
            codec_dai->name);
        goto codec_dai_err;
    }
}

codec_dai = uda134x_dai,codec_dai->ops->startup = uda134x_startup,分析一下uda134x_startup函数。

static int uda134x_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_device *socdev = rtd->socdev;
	struct snd_soc_codec *codec = socdev->card->codec;
	struct uda134x_priv *uda134x = codec->private_data;
	struct snd_pcm_runtime *master_runtime;

	if (uda134x->master_substream) {
		master_runtime = uda134x->master_substream->runtime;

		pr_debug("%s constraining to %d bits at %d\n", __func__,
			 master_runtime->sample_bits,
			 master_runtime->rate);

		snd_pcm_hw_constraint_minmax(substream->runtime,
					     SNDRV_PCM_HW_PARAM_RATE,
					     master_runtime->rate,
					     master_runtime->rate);

		snd_pcm_hw_constraint_minmax(substream->runtime,
					     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
					     master_runtime->sample_bits,
					     master_runtime->sample_bits);

		uda134x->slave_substream = substream;
	} else
		uda134x->master_substream = substream;

	//
	uda134x_write(codec, 2, 2|(5U<<2));
	return 0;
}

设置采样率

snd_pcm_hw_constraint_minmax(substream->runtime,SNDRV_PCM_HW_PARAM_RATE,
 master_runtime->rate,master_runtime->rate);

设置采样位数,8位或者16位或者其他

snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,master_runtime->sample_bits,
master_runtime->sample_bits);

设置mic的灵敏度,以及录制通道

uda134x_write(codec, 2, 2|(5U<<2));

在这里插入图片描述
灵敏度设置的是21,录制通道是2

4.调用machine->ops->startup函数
machine = s3c24xx_uda134x_dai_link,machine->ops->startup = s3c24xx_uda134x_startup
这个函数的作用是通过pclk和xtal得到采样率。

for (i = 0; i < 2; i++) 
{
    int fs = i ? 256 : 384;

    rates[i*33] = clk_get_rate(xtal) / fs;
    for (j = 1; j < 33; j++)
        rates[i*33 + j] = clk_get_rate(pclk) / (j * fs);
}

5.根据codec_dai和cpu_dai重新计算runtime->hw的一些值,需要同时满足codec_dai和cpu_dai

if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 
{
    runtime->hw.rate_min =max(codec_dai->playback.rate_min,cpu_dai->playback.rate_min);
    runtime->hw.rate_max =min(codec_dai->playback.rate_max,cpu_dai->playback.rate_max);
    runtime->hw.channels_min =max(codec_dai->playback.channels_min,cpu_dai->playback.channels_min);
    runtime->hw.channels_max =min(codec_dai->playback.channels_max,cpu_dai->playback.channels_max);
    runtime->hw.formats =codec_dai->playback.formats & cpu_dai->playback.formats;
    runtime->hw.rates =codec_dai->playback.rates & cpu_dai->playback.rates;
}
  • 调用snd_pcm_hw_constraints_complete函数
    重新设置SNDRV_PCM_HW_PARAM_ACCESS,SNDRV_PCM_HW_PARAM_FORMAT,SNDRV_PCM_HW_PARAM_CHANNELS等参数,设置到runtime.hw_constraints.rules里面去。

本文链接: http://www.dtmao.cc/news_show_300131.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?