MT6737 Android N 平台 Audio系统学习----TinyAlsa

来源:互联网 发布:电脑上怎么发淘宝链接 编辑:程序博客网 时间:2024/06/06 11:26

TinyAlsa(精简版alsa)是 Android 默认的 alsalib, 封装了内核 ALSA 的接口,用于简化用户空 间的 ALSA 编程。
tinyalsa位于Android源码的external/tinyalsa位置。
关于tinyalsa,tinyalsa是Google在Android 4.0之后推的基于alsa内核的用户层音频接口。在Android 4.0之前还一直是使用这alsa-lib接口。Google之所以推出tinyalsa,我认为有可能是因为alsa使用了GPL许可证的缘故,也有可能是因为alsa-lib的库过于复杂繁琐大部分功能在Android平台没有实际实用意义却依然占用屈指可数的内存空间。
关于alsa在Android中,在Android 4.0及之后只要你愿意还是可以使用原版alsa的,因为内核中依然是使用alsa的驱动,只需要把alsa的用户层接口alsa-lib移植到源码中即可。
下面进行分析TinyAlsa如何与alsa驱动交互。主要分析两个方面。一、如何打开PCM设备。二、如何打开control设备。

1、如何调用pcm设备

如何去调用一个pcm设备?/dev/snd/pcmC0D*p 是主设备号116,名字为“ALSA”的字符设备的次设备。 所以通过系统调用 open打开pcmC0D*p 实际系统调用进入kernel space以后,通过字符设备层往下执行。
external/tinyalsa/include/tinyalsa/pcm.c
这里写图片描述

1.1、pcm_open

struct pcm {    int fd;    unsigned int flags;    int running:1;    int prepared:1;    int underruns;    unsigned int buffer_size;    unsigned int boundary;    char error[PCM_ERROR_MAX];    struct pcm_config config;    struct snd_pcm_mmap_status *mmap_status;    struct snd_pcm_mmap_control *mmap_control;    struct snd_pcm_sync_ptr *sync_ptr;    void *mmap_buffer;    unsigned int noirq_frames_per_msec;    int wait_for_avail_min;};
/* Configuration for a stream */struct pcm_config {    unsigned int channels;    unsigned int rate;    unsigned int period_size;    unsigned int period_count;    enum pcm_format format;    /* Values to use for the ALSA start, stop and silence thresholds, and     * silence size.  Setting any one of these values to 0 will cause the     * default tinyalsa values to be used instead.     * Tinyalsa defaults are as follows.     *     * start_threshold   : period_count * period_size     * stop_threshold    : period_count * period_size     * silence_threshold : 0     * silence_size      : 0     */    unsigned int start_threshold;    unsigned int stop_threshold;    unsigned int silence_threshold;    unsigned int silence_size;    /* Minimum number of frames available before pcm_mmap_write() will actually     * write into the kernel buffer. Only used if the stream is opened in mmap mode     * (pcm_open() called with PCM_MMAP flag set).   Use 0 for default.     */    int avail_min;};
struct snd_pcm_hw_params {    unsigned int flags;    struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK -                    SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];    struct snd_mask mres[5];    /* reserved masks */    struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -                        SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];    struct snd_interval ires[9];    /* reserved intervals */    unsigned int rmask;     /* W: requested masks */    unsigned int cmask;     /* R: changed masks */    unsigned int info;      /* R: Info flags for returned setup */    unsigned int msbits;        /* R: used most significant bits */    unsigned int rate_num;      /* R: rate numerator */    unsigned int rate_den;      /* R: rate denominator */    snd_pcm_uframes_t fifo_size;    /* R: chip FIFO size in frames */    unsigned char reserved[64]; /* reserved for future */};
struct snd_pcm_info {    unsigned int device;        /* RO/WR (control): device number */    unsigned int subdevice;     /* RO/WR (control): subdevice number */    int stream;         /* RO/WR (control): stream direction */    int card;           /* R: card number */    unsigned char id[64];       /* ID (user selectable) */    unsigned char name[80];     /* name of this device */    unsigned char subname[32];  /* subdevice name */    int dev_class;          /* SNDRV_PCM_CLASS_* */    int dev_subclass;       /* SNDRV_PCM_SUBCLASS_* */    unsigned int subdevices_count;    unsigned int subdevices_avail;    union snd_pcm_sync_id sync; /* hardware synchronization ID */    unsigned char reserved[64]; /* reserved for future... */};
struct snd_pcm_sw_params {    int tstamp_mode;            /* timestamp mode */    unsigned int period_step;    unsigned int sleep_min;         /* min ticks to sleep */    snd_pcm_uframes_t avail_min;        /* min avail frames for wakeup */    snd_pcm_uframes_t xfer_align;       /* obsolete: xfer size need to be a multiple */    snd_pcm_uframes_t start_threshold;  /* min hw_avail frames for automatic start */    snd_pcm_uframes_t stop_threshold;   /* min avail frames for automatic stop */    snd_pcm_uframes_t silence_threshold;    /* min distance from noise for silence filling */    snd_pcm_uframes_t silence_size;     /* silence block size */    snd_pcm_uframes_t boundary;     /* pointers wrap point */    unsigned int proto;         /* protocol version */    unsigned int tstamp_type;       /* timestamp type (req. proto >= 2.0.12) */    unsigned char reserved[56];     /* reserved for future */};
struct pcm *pcm_open(unsigned int card, unsigned int device,                     unsigned int flags, struct pcm_config *config){    struct pcm *pcm;    struct snd_pcm_info info;    struct snd_pcm_hw_params params;    struct snd_pcm_sw_params sparams;    char fn[256];    int rc;    pcm = calloc(1, sizeof(struct pcm));    if (!pcm || !config)        return &bad_pcm; /* TODO: could support default config here */    pcm->config = *config;    snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,             flags & PCM_IN ? 'c' : 'p');//将 "/dev/snd/pcmC%uD%u%c"(声卡设备), card(声卡), device,flags &PCM_IN?'c':'p'以字符串形式复制给fn。             //flags & PCM_IN值为真时,代表capture设备节点,反之为playback设备节点    pcm->flags = flags;    //系统调用open调用,通过之前注册的cdev设备和pcmC0D*p/c的信息,在kernel会创建两个重要的数据结构分别为struct inode *inode和struct file *file。由于open的设备是主设备号116,次设备号为minor的pcmC0D*p /c,所以会调用 “alsa”字符设备的操作函数    pcm->fd = open(fn, O_RDWR);//打开指定声卡设备,权限为可读可写    if (pcm->fd < 0) {        oops(pcm, errno, "cannot open device '%s'", fn);        return pcm;    }/*ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,int ioctl(int fd, int cmd,…);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl命令号是这个函数中最重要的参数,它描述的ioctl要处理的命令。Linux中使用一个32位的数据来编码ioctl命令,它包含四个部分:dir:type:nr:size。dir:代表数据传输的方向,占2位,可以是_IOC_NONE(无数据传输,0U),_IOC_WRITE(向设备写数据,1U)或_IOC_READ(从设备读数据,2U)或他们的逻辑或组合,当然只有_IOC_WRITE和_IOC_READ的逻辑或才有意义。type:描述了ioctl命令的类型,8位。每种设备或系统都可以指定自己的一个类型号,ioctl用这个类型来表示ioctl命令所属的设备或驱动。一般用ASCII码字符来表示,如 'a'。nr:ioctl命令序号,一般8位。对于一个指定的设备驱动,可以对它的ioctl命令做一个顺序编码,一般从零开始,这个编码就是ioctl命令的序号。size:octl命令的参数大小,一般14位。ioctl命令号的这个数据成员不是强制使用的,你可以不使用它,但是我们建议你指定这个数据成员,通过它我们可以检查用户空间数据的大小以避免错误的数据操作,也可以实现兼容旧版本的ioctl命令。我们可以自己来直接指定一个ioctl命令号,它可能仅仅是一个整数集,但Linux中的ioctl命令号都是有特定含义的,因此通常我们不推荐这么做。其实Linux内核已经提供了相应的宏来自动生成ioctl命令号:_IO(type,nr)_IOR(type,nr,size)_IOW(type,nr,size)_IOWR(type,nr,size)宏_IO用于无数据传输,宏_IOR用于从设备读数据,宏_IOW用于向设备写数据,宏_IOWR用于同时有读写数据的IOCTL命令。*///#define SNDRV_PCM_IOCTL_INFO      _IOR('A', 0x01, struct snd_pcm_info)     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {        oops(pcm, errno, "cannot get info");        goto fail_close;    }    param_init(&params);    param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,                   pcm_format_to_alsa(config->format));    param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,                   SNDRV_PCM_SUBFORMAT_STD);    param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);    param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,                  pcm_format_to_bits(config->format));    param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,                  pcm_format_to_bits(config->format) * config->channels);    param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,                  config->channels);    param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);    param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);    if (flags & PCM_NOIRQ) {        if (!(flags & PCM_MMAP)) {            oops(pcm, -EINVAL, "noirq only currently supported with mmap().");            goto fail;        }        params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;        pcm->noirq_frames_per_msec = config->rate / 1000;    }    if (flags & PCM_MMAP)        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,                       SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);    else        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,                       SNDRV_PCM_ACCESS_RW_INTERLEAVED);    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {        oops(pcm, errno, "cannot set hw params");        goto fail_close;    }    /* get our refined hw_params */    config->period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);    config->period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);    pcm->buffer_size = config->period_count * config->period_size;    if (flags & PCM_MMAP) {        pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),                                PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);        if (pcm->mmap_buffer == MAP_FAILED) {            oops(pcm, -errno, "failed to mmap buffer %d bytes\n",                 pcm_frames_to_bytes(pcm, pcm->buffer_size));            goto fail_close;        }    }    memset(&sparams, 0, sizeof(sparams));    sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;    sparams.period_step = 1;    if (!config->start_threshold) {//播放        if (pcm->flags & PCM_IN)            pcm->config.start_threshold = sparams.start_threshold = 1;        else            pcm->config.start_threshold = sparams.start_threshold =                config->period_count * config->period_size / 2;    } else        sparams.start_threshold = config->start_threshold;    /* pick a high stop threshold - todo: does this need further tuning */    if (!config->stop_threshold) {//停止        if (pcm->flags & PCM_IN)            pcm->config.stop_threshold = sparams.stop_threshold =                config->period_count * config->period_size * 10;        else            pcm->config.stop_threshold = sparams.stop_threshold =                config->period_count * config->period_size;    }    else        sparams.stop_threshold = config->stop_threshold;    if (!pcm->config.avail_min) {        if (pcm->flags & PCM_MMAP)            pcm->config.avail_min = sparams.avail_min = pcm->config.period_size;        else            pcm->config.avail_min = sparams.avail_min = 1;    } else        sparams.avail_min = config->avail_min;    sparams.xfer_align = config->period_size / 2; /* needed for old kernels */    sparams.silence_threshold = config->silence_threshold;    sparams.silence_size = config->silence_size;    pcm->boundary = sparams.boundary = pcm->buffer_size;    while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)        pcm->boundary *= 2;    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {        oops(pcm, errno, "cannot set sw params");        goto fail;    }    rc = pcm_hw_mmap_status(pcm);    if (rc < 0) {        oops(pcm, rc, "mmap status failed");        goto fail;    }#ifdef SNDRV_PCM_IOCTL_TTSTAMP    if (pcm->flags & PCM_MONOTONIC) {        int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;        rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg);        if (rc < 0) {            oops(pcm, rc, "cannot set timestamp type");            goto fail;        }    }#endif    pcm->underruns = 0;    return pcm;fail:    if (flags & PCM_MMAP)        munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));fail_close:    close(pcm->fd);    pcm->fd = -1;    return pcm;}

pcm->fd = open(fn, O_RDWR)来打开pcm设备文件,此设备节点对应有一个设备号,这是我们识别驱动和设备的桥梁。
根据设备号,在cdev链表中找到cdev这个结构体,cdev里面包含了file_operation结构体snd_fops,有设备的各种操作,打开时就调用里面的.open 函数snd_open。在这里要完成几件事:
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
(1)inode节点,每一个文件都对应有一个inode节点,inode结构体里.i_fop由cdev的file_operation填充,i_dev由cdev的设备号填充
(2)file结构体中的file_operation也同样由cdev中对应项填充,还有一项fd,对应于打开文件的文件描述符,fd和file一一对应,文件每打开一次,就有一个file结构体。所以file里面的.private就很重要。还有一个问题,那就是多个相同的设备,会公用同一个驱动,所以要把每一个设备的私有数据封装起来,构成一个私有数据结构体。对设备的每一次读写,都通过操作设备的私有数据结构体中的资源来完成。也就是说,驱动在加载的时候,会申请多个设备私有资源结构体,每个结构体中包含了设备的所有私有资源,虽然公用一个驱动,可是通过设备号找到此设备号对应设备的私有资源,说的有点拗口。这可以通过file结构体的.private来指向。
struct snd_minor {
int type; /* SNDRV_DEVICE_TYPE_XXX */
int card; /* card number */
int device; /* device number */
const struct file_operations f_ops; / file operations */
void private_data; / private data for f_ops->open */
struct device dev; / device for sysfs */
struct snd_card card_ptr; / assigned card instance */
};
前面应经提到inode中的i_cdev会指向cdev结构,所以可以由Container宏来得到snd_minor的地址。
所以,在驱动的open函数中有两个参数,inode和file
int open(structc inode *inode,struct file *file){
struct snd_minor *p =container(inode->i_cdev,hello_struct,cdev)

file->private=p;

}
这样file中就包含了设备的私有数据。
驱动read函数中:
ssize_t read(fd,char __user *buf,count)
fd和file一一对应,每打开一次设备,虽然有不同的fd,但他们的file.private是一样的。
下面看看snd_open函数。

1.2、snd_open

进入snd_open函数,它首先从inode中取出此设备号,然后以次设备号为索引,从snd_minors全局数组中取出当初注册pcm设备时填充的snd_minor结构,然后从snd_minor结构中取出pcm设备的f_ops,并且把file->f_op替换为pcm设备的f_ops,紧接着直接调用pcm设备的f_ops->open(),然后返回。因为file->f_op已经被替换,以后,应用程序的所有read/write/ioctl调用都会进入pcm设备自己的回调函数中,也就是前面分析提到的snd_pcm_f_ops结构中定义的回调。

static struct snd_minor *snd_minors[256];

struct snd_minor {
int type; /* SNDRV_DEVICE_TYPE_XXX SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE */
int card; /* card number card的编号 */
int device; /* device number pcm实例的编号,大多数情况为0*/
const struct file_operations f_ops; / file operations snd_pcm_f_ops*/
void private_data; / private data for f_ops->open 指向该pcm的实例*/
struct device dev; / device for sysfs */
struct snd_card card_ptr; / assigned card instance */
};

static int snd_open(struct inode *inode, struct file *file){/*作为一个鼓励更可移植编程的方法, 内核开发者已经增加了 2 个宏, 可用来从一个 inode 中获取主次编号:unsigned int iminor(struct inode *inode);//次设备号unsigned int imajor(struct inode *inode);//主设备号*/    unsigned int minor = iminor(inode);//获取次设备号    struct snd_minor *mptr = NULL;    const struct file_operations *new_fops;    int err = 0;    if (minor >= ARRAY_SIZE(snd_minors))//看获取到的次设备号是否大于256        return -ENODEV;    mutex_lock(&sound_mutex);    mptr = snd_minors[minor];//将对应设备(minor=次设备号)的snd_minor结构体地址给mptr    if (mptr == NULL) {        mptr = autoload_device(minor);        if (!mptr) {            mutex_unlock(&sound_mutex);            return -ENODEV;        }    }    new_fops = fops_get(mptr->f_ops);//fops_get获取snd_minor结构体成员file_operations *f_ops    mutex_unlock(&sound_mutex);    if (!new_fops)        return -ENODEV;    replace_fops(file, new_fops);//把file->f_op替换为pcm设备的f_ops    if (file->f_op->open)        err = file->f_op->open(inode, file);//调用结构体f_op的open函数    return err;}

file->f_op->open(inode, file);//调用结构体f_op的open函数。
前面分析alsa driver驱动时提到过pcm设备注册的结构体,是在snd_pcm_dev_register函数中注册的。
snd_register_device_for_dev(devtype, pcm->card,pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);

static int snd_pcm_dev_register(struct snd_device *device){    ........    err = snd_pcm_add(pcm);    if (err) {        mutex_unlock(&register_mutex);        return err;    }    for (cidx = 0; cidx < 2; cidx++) {        int devtype = -1;        if (pcm->streams[cidx].substream == NULL || pcm->internal)            continue;        switch (cidx) {        case SNDRV_PCM_STREAM_PLAYBACK:            sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);            devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;            break;        case SNDRV_PCM_STREAM_CAPTURE:            sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);            devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;            break;        }    .........        /* register pcm */        err = snd_register_device_for_dev(devtype, pcm->card,                          pcm->device,                          &snd_pcm_f_ops[cidx],                          pcm, str, dev);        dev = snd_get_device(devtype, pcm->card, pcm->device);    ..........}

下面进入snd_pcm_f_ops结构体。
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,
.llseek = no_llseek,
.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 = snd_pcm_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,
.llseek = no_llseek,
.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 = snd_pcm_get_unmapped_area,
}
};
加入是播放,那么下面进入被调用的snd_pcm_playback_open函数。

1.3、snd_pcm_playback_open

snd_pcm_playback_open调用了snd_pcm_open函数。

static int snd_pcm_playback_open(struct inode *inode, struct file *file){    struct snd_pcm *pcm;    int err = nonseekable_open(inode, file);    if (err < 0)        return err;    pcm = snd_lookup_minor_data(iminor(inode),                    SNDRV_DEVICE_TYPE_PCM_PLAYBACK);    err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);//调用snd_pcm_open函数    if (pcm)        snd_card_unref(pcm->card);    return err;}

1.4、snd_pcm_open

snd_pcm_open调用了snd_pcm_open_file函数

static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream){    int err;    wait_queue_t wait;    if (pcm == NULL) {        err = -ENODEV;        goto __error1;    }    err = snd_card_file_add(pcm->card, file);//将结构体file加入声卡结构体snd_card成员结构体files_list中    if (err < 0)        goto __error1;    if (!try_module_get(pcm->card->module)) {        err = -EFAULT;        goto __error2;    }    init_waitqueue_entry(&wait, current);    add_wait_queue(&pcm->open_wait, &wait);    mutex_lock(&pcm->open_mutex);    while (1) {        err = snd_pcm_open_file(file, pcm, stream);//调用snd_pcm_open_file        if (err >= 0)            break;        if (err == -EAGAIN) {            if (file->f_flags & O_NONBLOCK) {                err = -EBUSY;                break;            }        } else            break;        set_current_state(TASK_INTERRUPTIBLE);        mutex_unlock(&pcm->open_mutex);        schedule();        mutex_lock(&pcm->open_mutex);        if (pcm->card->shutdown) {            err = -ENODEV;            break;        }        if (signal_pending(current)) {            err = -ERESTARTSYS;            break;        }    }    remove_wait_queue(&pcm->open_wait, &wait);    mutex_unlock(&pcm->open_mutex);    if (err < 0)        goto __error;    return err;      __error:    module_put(pcm->card->module);      __error2:        snd_card_file_remove(pcm->card, file);      __error1:        return err;}

1.5、snd_pcm_open_substream

snd_pcm_open_file调用了snd_pcm_open_substream函数

static int snd_pcm_open_file(struct file *file,                 struct snd_pcm *pcm,                 int stream){    struct snd_pcm_file *pcm_file;    struct snd_pcm_substream *substream;    int err;    err = snd_pcm_open_substream(pcm, stream, file, &substream);    if (err < 0)        return err;    pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL);    if (pcm_file == NULL) {        snd_pcm_release_substream(substream);        return -ENOMEM;    }    pcm_file->substream = substream;    if (substream->ref_count == 1) {        substream->file = pcm_file;        substream->pcm_release = pcm_release_private;    }    file->private_data = pcm_file;    return 0;}

1.6、snd_pcm_open_substream

substream->ops->open(substream)中open(substream)原型为函数指针int (*open)(struct snd_pcm_substream *substream),substream->ops->open会调用到ASOC上pcm注册的(假如是录音)mtk_afe_capture_ops结构体中的.open = mtk_capture_pcm_open函数。
static int mtk_capture_pcm_open(struct snd_pcm_substream *substream)
到此,就真正打开了pcm设备。

int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,               struct file *file,               struct snd_pcm_substream **rsubstream){    struct snd_pcm_substream *substream;    int err;    err = snd_pcm_attach_substream(pcm, stream, file, &substream);    if (err < 0)        return err;    if (substream->ref_count > 1) {        *rsubstream = substream;        return 0;    }    err = snd_pcm_hw_constraints_init(substream);    if (err < 0) {        pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");        goto error;    }/*    int (*open)(struct snd_pcm_substream *substream);    substream->ops->open会调用到ASOC上pcm注册的(假如是录音)mtk_afe_capture_ops结构体中的.open =  mtk_capture_pcm_open函数。    static int mtk_capture_pcm_open(struct snd_pcm_substream *substream)*/    if ((err = substream->ops->open(substream)) < 0)        goto error;    substream->hw_opened = 1;    err = snd_pcm_hw_constraints_complete(substream);    if (err < 0) {        pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");        goto error;    }    *rsubstream = substream;    return 0; error:    snd_pcm_release_substream(substream);    return err;}

2、如何去调用control设备

external/tinyalsa/include/tinyalsa/mixer.c

image
一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。定义一个kcontrol主要就是定义一个snd_kcontrol_new结构,我们知道,对于每个控件,我们需要定义一个和它对应的snd_kcontrol_new结构,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态。snd_kcontrol_new结构中,几个主要的字段是get,put,private_value,get回调函数用于获取该控件当前的状态值,而put回调函数则用于设置控件的状态值,而private_value字段则根据不同的控件类型有不同的意义,比如对于普通的控件,private_value字段可以用来定义该控件所对应的寄存器的地址以及对应的控制位在寄存器中的位置信息。

2.1、mixer_open

打开/dev/snd/controlC0节点。
通过call系统调用open,会通过之前注册的cdev设备和controlC0的信息,在kernel会创建两个重要的数据结构分别为struct inode *inode和struct file *file。
由于open的设备是主设备号116,次设备号为minor的controlC0,所以会操作“alsa”字符设备的操作函数。

struct mixer *mixer_open(unsigned int card){    struct snd_ctl_elem_list elist;    struct snd_ctl_elem_info tmp;    struct snd_ctl_elem_id *eid = NULL;    struct mixer *mixer = NULL;    unsigned int n, m;    int fd;    char fn[256];    snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);    fd = open(fn, O_RDWR);//打开control设备    if (fd < 0)        return 0;    ...........}

fd = open(fn, O_RDWR);打开control设备,open的设备是主设备号116,次设备号为minor的controlC0,所以会操作“alsa”字符设备的操作函数,调用snd_open。

static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};

static int snd_open(struct inode *inode, struct file *file){    unsigned int minor = iminor(inode);    struct snd_minor *mptr = NULL;    const struct file_operations *new_fops;    int err = 0;    if (minor >= ARRAY_SIZE(snd_minors))        return -ENODEV;    mutex_lock(&sound_mutex);    mptr = snd_minors[minor];    if (mptr == NULL) {        mptr = autoload_device(minor);        if (!mptr) {            mutex_unlock(&sound_mutex);            return -ENODEV;        }    }    new_fops = fops_get(mptr->f_ops);    mutex_unlock(&sound_mutex);    if (!new_fops)        return -ENODEV;    replace_fops(file, new_fops);    if (file->f_op->open)        err = file->f_op->open(inode, file);    return err;}

file->f_op->open(inode, file)会调用结构体snd_ctl_f_ops的.open = snd_ctl_open函数
static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};

static int snd_ctl_open(struct inode *inode, struct file *file){    unsigned long flags;    struct snd_card *card;    struct snd_ctl_file *ctl;    int err;    err = nonseekable_open(inode,file);//这个调用标识了给定的file为不可移位的;内核就不会允许一个lseek调用在这样一个文件上成功.通过用这样的方式标识这个文件,你可确定不会有通过pread和pwrite系统调用的方式来试图移位这个文件.    if (err < 0)        return err;//////////////////////////////////////////////////////////struct snd_minor {    int type;           /* SNDRV_DEVICE_TYPE_XXX */    int card;           /* card number */    int device;         /* device number */    const struct file_operations *f_ops;    /* file operations */    void *private_data;     /* private data for f_ops->open */    struct device *dev;     /* device for sysfs */    struct snd_card *card_ptr;  /* assigned card instance */};//////////////////////////////////////////////////////////    card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL);//调用snd_lookup_minor_data拿到snd_minors保存的私有数据    if (!card) {        err = -ENODEV;        goto __error1;    }    err = snd_card_file_add(card, file);// This function adds the file to the file linked-list of the card    if (err < 0) {        err = -ENODEV;        goto __error1;    }    if (!try_module_get(card->module)) {//如果模块已经插入内核,则递增该模块引用计数;如果该模块还没有插入内核,则返回0表示出错        err = -EFAULT;        goto __error2;    }    ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);    if (ctl == NULL) {        err = -ENOMEM;        goto __error;    }    INIT_LIST_HEAD(&ctl->events);    init_waitqueue_head(&ctl->change_sleep);//该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。    spin_lock_init(&ctl->read_lock);//初始化自旋锁lock    ctl->card = card;//最终目的得到这个数据结构snd_card    ctl->prefer_pcm_subdevice = -1;    ctl->prefer_rawmidi_subdevice = -1;    ctl->pid = get_pid(task_pid(current));    file->private_data = ctl;    write_lock_irqsave(&card->ctl_files_rwlock, flags);    list_add_tail(&ctl->list, &card->ctl_files);    write_unlock_irqrestore(&card->ctl_files_rwlock, flags);//释放锁,同时使能cpu中断,恢复cpu的标识    //#define snd_card_unref(card)  put_device(&(card)->card_dev)    减少设备对象的引用计数    snd_card_unref(card);    return 0;      __error:    module_put(card->module);      __error2:    snd_card_file_remove(card, file);      __error1:    if (card)        snd_card_unref(card);        return err;}

在 ctl->card = card获取到数据结构snd_card后,下面看看mixer_open后面做的事,将kernel里面所有kcontrol的有用信息复制到user空间。

struct mixer *mixer_open(unsigned int card){    struct snd_ctl_elem_list elist;    struct snd_ctl_elem_info tmp;    struct snd_ctl_elem_id *eid = NULL;    struct mixer *mixer = NULL;    unsigned int n, m;    int fd;    char fn[256];    snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);    fd = open(fn, O_RDWR);    if (fd < 0)        return 0;    memset(&elist, 0, sizeof(elist));/////////////////////////////////////////////////////struct snd_ctl_elem_list {    unsigned int offset;        /* W: first element ID to get */    unsigned int space;     /* W: count of element IDs to get */    unsigned int used;      /* R: count of element IDs set */    unsigned int count;     /* R: count of all elements */    struct snd_ctl_elem_id __user *pids; /* R: IDs */    unsigned char reserved[50];};/////////////////////////////////////////////////////    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)//通过elist.count获得 kernel Kcontrol的数量        goto fail;    mixer = calloc(1, sizeof(*mixer));//user空间用于存储kernel kcontros信息的结构体    if (!mixer)        goto fail;    mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl));//每一个kernel kcontrol对应一个mixer->ctl[n]    mixer->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));    if (!mixer->ctl || !mixer->elem_info)        goto fail;    if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0)//取出snd_card的信息        goto fail;    eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id));//临时存储空间分配空间    if (!eid)        goto fail;    mixer->count = elist.count;    mixer->fd = fd;    elist.space = mixer->count;    elist.pids = eid;    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)        goto fail;    for (n = 0; n < mixer->count; n++) {        struct snd_ctl_elem_info *ei = mixer->elem_info + n;        ei->id.numid = eid[n].numid;        if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0))//取出kcontrol的id存入ei            goto fail;        mixer->ctl[n].info = ei;        mixer->ctl[n].mixer = mixer;        if (ei->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {            char **enames = calloc(ei->value.enumerated.items, sizeof(char*));            if (!enames)                goto fail;            mixer->ctl[n].ename = enames;            for (m = 0; m < ei->value.enumerated.items; m++) {                memset(&tmp, 0, sizeof(tmp));                tmp.id.numid = ei->id.numid;                tmp.value.enumerated.item = m;                if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)                    goto fail;                enames[m] = strdup(tmp.value.enumerated.name);                if (!enames[m])                    goto fail;            }        }    }    free(eid);    return mixer;fail:    /* TODO: verify frees in failure case */    if (eid)        free(eid);    if (mixer)        mixer_close(mixer);    else if (fd >= 0)        close(fd);    return 0;}

if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0))通过SNDRV_CTL_IOCTL_ELEM_INFO会传入snd_ctl_f_ops中snd_ctl_ioctl函数。

static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};

static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg){    struct snd_ctl_file *ctl;    struct snd_card *card;    struct snd_kctl_ioctl *p;    void __user *argp = (void __user *)arg;    int __user *ip = argp;    int err;    ctl = file->private_data;    card = ctl->card;    if (snd_BUG_ON(!card))        return -ENXIO;    switch (cmd) {    case SNDRV_CTL_IOCTL_PVERSION:        return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;    case SNDRV_CTL_IOCTL_CARD_INFO:        return snd_ctl_card_info(card, ctl, cmd, argp);    case SNDRV_CTL_IOCTL_ELEM_LIST:        return snd_ctl_elem_list(card, argp);    case SNDRV_CTL_IOCTL_ELEM_INFO://会走到这里,然后调用snd_ctl_elem_info_user(ctl, argp)        return snd_ctl_elem_info_user(ctl, argp);    case SNDRV_CTL_IOCTL_ELEM_READ:        return snd_ctl_elem_read_user(card, argp);    case SNDRV_CTL_IOCTL_ELEM_WRITE:        return snd_ctl_elem_write_user(ctl, argp);    case SNDRV_CTL_IOCTL_ELEM_LOCK:        return snd_ctl_elem_lock(ctl, argp);    case SNDRV_CTL_IOCTL_ELEM_UNLOCK:        return snd_ctl_elem_unlock(ctl, argp);    case SNDRV_CTL_IOCTL_ELEM_ADD:        return snd_ctl_elem_add_user(ctl, argp, 0);    case SNDRV_CTL_IOCTL_ELEM_REPLACE:        return snd_ctl_elem_add_user(ctl, argp, 1);    case SNDRV_CTL_IOCTL_ELEM_REMOVE:        return snd_ctl_elem_remove(ctl, argp);    case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:        return snd_ctl_subscribe_events(ctl, ip);    case SNDRV_CTL_IOCTL_TLV_READ:        return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_READ);    case SNDRV_CTL_IOCTL_TLV_WRITE:        return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE);    case SNDRV_CTL_IOCTL_TLV_COMMAND:        return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD);    case SNDRV_CTL_IOCTL_POWER:        return -ENOPROTOOPT;    case SNDRV_CTL_IOCTL_POWER_STATE:#ifdef CONFIG_PM        return put_user(card->power_state, ip) ? -EFAULT : 0;#else        return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0;#endif    }    down_read(&snd_ioctl_rwsem);    list_for_each_entry(p, &snd_control_ioctls, list) {        err = p->fioctl(card, ctl, cmd, arg);        if (err != -ENOIOCTLCMD) {            up_read(&snd_ioctl_rwsem);            return err;        }    }    up_read(&snd_ioctl_rwsem);    dev_dbg(card->dev, "unknown ioctl = 0x%x\n", cmd);    return -ENOTTY;}

接着调用snd_ctl_elem_info_user函数

static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl,                  struct snd_ctl_elem_info __user *_info){    struct snd_ctl_elem_info info;    int result;    if (copy_from_user(&info, _info, sizeof(info)))//将user _info数据拷贝到kernel info        return -EFAULT;    snd_power_lock(ctl->card);    result = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0);    if (result >= 0)        result = snd_ctl_elem_info(ctl, &info);//调用snd_ctl_elem_info    snd_power_unlock(ctl->card);    if (result >= 0)        if (copy_to_user(_info, &info, sizeof(info)))            return -EFAULT;    return result;}

然后调用snd_ctl_elem_info函数。

static int snd_ctl_elem_info(struct snd_ctl_file *ctl,                 struct snd_ctl_elem_info *info){    struct snd_card *card = ctl->card;    struct snd_kcontrol *kctl;    struct snd_kcontrol_volatile *vd;    unsigned int index_offset;    int result;    down_read(&card->controls_rwsem);    kctl = snd_ctl_find_id(card, &info->id);    if (kctl == NULL) {        up_read(&card->controls_rwsem);        return -ENOENT;    }#ifdef CONFIG_SND_DEBUG    info->access = 0;#endif//////////////////////////////////////////////////////////////struct snd_kcontrol *kctl;struct snd_kcontrol {    struct list_head list;      /* list of controls */    struct snd_ctl_elem_id id;    unsigned int count;     /* count of same elements */    snd_kcontrol_info_t *info;    snd_kcontrol_get_t *get;    snd_kcontrol_put_t *put;    union {        snd_kcontrol_tlv_rw_t *c;        const unsigned int *p;    } tlv;    unsigned long private_value;    void *private_data;    void (*private_free)(struct snd_kcontrol *kcontrol);    struct snd_kcontrol_volatile vd[0]; /* volatile data */};snd_kcontrol_info_t *info;typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo);//////////////////////////////////////////////////////////////////    result = kctl->info(kctl, info);//会调到snd_soc_info_enum_double    if (result >= 0) {        snd_BUG_ON(info->access);        index_offset = snd_ctl_get_ioff(kctl, &info->id);        vd = &kctl->vd[index_offset];        snd_ctl_build_ioff(&info->id, kctl, index_offset);        info->access = vd->access;        if (vd->owner) {            info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK;            if (vd->owner == ctl)                info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER;            info->owner = pid_vnr(vd->owner->pid);        } else {            info->owner = -1;        }    }    up_read(&card->controls_rwsem);    return result;}

先介绍一些知识。
control的最主要用途是mixer(混音器),所有mixer元素基于control内核API实现,创建一个新的control之前至少需要实现snd_kcontrol_new中的info(),get()和put()这3个成员
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char name; / ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};

typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo);
typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);

struct snd_ctl_elem_info 结构体定义如下:

struct snd_ctl_elem_info { struct snd_ctl_elem_id id; //元素ID* W: element ID */ snd_ctl_elem_type_t type; //值类型* R: value type - SNDRV_CTL_ELEM_TYPE_* */ unsigned int access;  //访问权限* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */ unsigned int count;  //值的计算* count of values */ pid_t owner;    union {  struct {   long min;  //最小值* R: minimum value */   long max;  //最大值* R: maximum value */   long step;    } integer;  struct {   long long min;  /   long long max;  /   long long step;  /  } integer64;  struct {   unsigned int items; /   unsigned int item; /   char name[64];  /  } enumerated;  unsigned char reserved[128]; } value; union {  unsigned short d[4];  /  unsigned short *d_ptr;  / } dimen; unsigned char reserved[64-4*sizeof(unsigned short)];};

and_ctl_elem_info结构体的type字段定于了control的类型,包括BOOLEAN、INTEGER、ENUMERATED、BYTES、IEC958和 INTEGER64。count字段定义了这个control中包含的元素的数量,例如1个立体声音量control的count = 2。value是1个联合体,其所存储的值的具体类型依赖于type。

info()函数用于获得control的详细信息。
get()函数用于得到control的目前值并返回用户空间。
put()函数从用户空间写入值,如果值改变,该函数返回1,否则返回0。

snd_kcontrol_new结构体中的info()函数用于获得control的详细信息,该函数必须填充给它的第二个参数snd_ctl_elem_info结构体。

这里就可以联系到我们alsa driver中添加的那么多codec control了。比如:

snd_soc_add_codec_controls(codec, mt6331_snd_controls, ARRAY_SIZE(mt6331_snd_controls))static const struct snd_kcontrol_new mt6331_snd_controls[] = {    SOC_ENUM_EXT("Audio_Amp_R_Switch", Audio_DL_Enum[0], Audio_AmpR_Get, Audio_AmpR_Set),    SOC_ENUM_EXT("Audio_Amp_L_Switch", Audio_DL_Enum[1], Audio_AmpL_Get, Audio_AmpL_Set),    SOC_ENUM_EXT("Voice_Amp_Switch", Audio_DL_Enum[2], Voice_Amp_Get, Voice_Amp_Set),    SOC_ENUM_EXT("Speaker_Amp_Switch", Audio_DL_Enum[3], Speaker_Amp_Get, Speaker_Amp_Set),    SOC_ENUM_EXT("Headset_Speaker_Amp_Switch", Audio_DL_Enum[4], Headset_Speaker_Amp_Get,             Headset_Speaker_Amp_Set),    SOC_ENUM_EXT("Headset_PGAL_GAIN", Audio_DL_Enum[5], Headset_PGAL_Get, Headset_PGAL_Set),    SOC_ENUM_EXT("Headset_PGAR_GAIN", Audio_DL_Enum[6], Headset_PGAR_Get, Headset_PGAR_Set),    SOC_ENUM_EXT("Handset_PGA_GAIN", Audio_DL_Enum[7], Handset_PGA_Get, Handset_PGA_Set),    SOC_ENUM_EXT("Lineout_PGAR_GAIN", Audio_DL_Enum[8], Lineout_PGAR_Get, Lineout_PGAR_Set),    SOC_ENUM_EXT("Lineout_PGAL_GAIN", Audio_DL_Enum[9], Lineout_PGAL_Get, Lineout_PGAL_Set),    SOC_ENUM_EXT("AUD_CLK_BUF_Switch", Audio_DL_Enum[10], Aud_Clk_Buf_Get, Aud_Clk_Buf_Set),    SOC_ENUM_EXT("Ext_Speaker_Amp_Switch", Audio_DL_Enum[11], Ext_Speaker_Amp_Get,             Ext_Speaker_Amp_Set),    SOC_ENUM_EXT("Receiver_Speaker_Switch", Audio_DL_Enum[11], Receiver_Speaker_Switch_Get,             Receiver_Speaker_Switch_Set),    SOC_SINGLE_EXT("Audio HP Impedance", SND_SOC_NOPM, 0, 512, 0, Audio_Hp_Impedance_Get,               Audio_Hp_Impedance_Set),};#define SOC_ENUM_EXT(xname, xenum, xhandler_get, xhandler_put) \{   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \    .info = snd_soc_info_enum_double, \    .get = xhandler_get, .put = xhandler_put, \    .private_value = (unsigned long)&xenum }
int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,    struct snd_ctl_elem_info *uinfo){    struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;//获取snd_kcontrol结构体中private_value的数据    uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;    uinfo->count = e->shift_l == e->shift_r ? 1 : 2;    uinfo->value.enumerated.items = e->items;    if (uinfo->value.enumerated.item >= e->items)        uinfo->value.enumerated.item = e->items - 1;    strlcpy(uinfo->value.enumerated.name,        e->texts[uinfo->value.enumerated.item],        sizeof(uinfo->value.enumerated.name));    return 0;}

上面函数中就是为了获取snd_kcontrol结构体中private_value的数据,然后将获取数据填充给结构体snd_ctl_elem_info *uinfo。

而.get = xhandler_get, .put = xhandler_put就会调用codec中创建的mt6331_snd_controls[]中的get和set函数。
到此调用control设备就分析完了。

3、HAL层如何使用kernel的kcontrol

vendor/mediatek/proprirtary/hardware/audio/common/V3/aud_drv/AudioALSACodecDeviceOutEarphonePMIC.cpp

3.1、HAL层对耳机通路的控制

vendor/mediatek/proprirtary/hardware/audio/common/V3/aud_drv/AudioALSACodecDeviceOutEarphonePMIC.cpp

status_t AudioALSACodecDeviceOutEarphonePMIC::open(){    ALOGD("+%s(), mClientCount = %d", __FUNCTION__, mClientCount);    if (mClientCount == 0)    {        if (mixer_ctl_set_enum_by_string(mixer_get_ctl_by_name(mMixer, "Audio_Amp_R_Switch"), "On"))        {            ALOGE("Error: Audio_Amp_R_Switch invalid value");        }        if (mixer_ctl_set_enum_by_string(mixer_get_ctl_by_name(mMixer, "Audio_Amp_L_Switch"), "On"))        {            ALOGE("Error: Audio_Amp_L_Switch invalid value");        }    }    mClientCount++;    ALOGD("-%s(), mClientCount = %d", __FUNCTION__, mClientCount);    return NO_ERROR;}

AudioALSACodecDeviceOutEarphonePMIC::open()调用external/tinyalsa/include/tinyalsa/mixer.c 里的函数mixer_ctl_set_enum_by_string(mixer_get_ctl_by_name(mMixer, “Audio_Amp_R_Switch”), “On”)

/*kernel中添加的controlSOC_ENUM_EXT("Audio_Amp_R_Switch", Audio_DL_Enum[0], Audio_AmpR_Get, Audio_AmpR_Set),SOC_ENUM_EXT("Audio_Amp_L_Switch", Audio_DL_Enum[1], Audio_AmpL_Get, Audio_AmpL_Set),mixer_get_ctl_by_name(mMixer, "Audio_Amp_R_Switch")struct mixer_ctl {    struct mixer *mixer;    struct snd_ctl_elem_info *info;    char **ename;};*/struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name){    unsigned int n;    if (!mixer)        return NULL;    for (n = 0; n < mixer->count; n++)        if (!strcmp(name, (char*) mixer->elem_info[n].id.name))//与从kernel取出的名字比较            return mixer->ctl + n;    return NULL;}int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string){    unsigned int i, num_enums;    struct snd_ctl_elem_value ev;    int ret;    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED))        return -EINVAL;    num_enums = ctl->info->value.enumerated.items;    for (i = 0; i < num_enums; i++) {        if (!strcmp(string, ctl->ename[i])) {//寻找字符串“on”            memset(&ev, 0, sizeof(ev));            ev.value.enumerated.item[0] = i;            ev.id.numid = ctl->info->id.numid;//“Audio_Amp_R_Switch”的kcontrol单元的数字标识号            ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);            if (ret < 0)                return ret;            return 0;        }    }    return -EINVAL;}

ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev)通过传cmd调用kernel-3.18/sound/core/control.c中static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)的snd_ctl_elem_write_user(struct snd_ctl_file *file,struct snd_ctl_elem_value __user *_control)

static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg){    struct snd_ctl_file *ctl;    struct snd_card *card;    struct snd_kctl_ioctl *p;    void __user *argp = (void __user *)arg;    int __user *ip = argp;    int err;    ctl = file->private_data;    card = ctl->card;    if (snd_BUG_ON(!card))        return -ENXIO;    switch (cmd) {    case SNDRV_CTL_IOCTL_PVERSION:        return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;    case SNDRV_CTL_IOCTL_CARD_INFO:        return snd_ctl_card_info(card, ctl, cmd, argp);    case SNDRV_CTL_IOCTL_ELEM_LIST:        return snd_ctl_elem_list(card, argp);    case SNDRV_CTL_IOCTL_ELEM_INFO:        return snd_ctl_elem_info_user(ctl, argp);    case SNDRV_CTL_IOCTL_ELEM_READ:        return snd_ctl_elem_read_user(card, argp);    case SNDRV_CTL_IOCTL_ELEM_WRITE:        return snd_ctl_elem_write_user(ctl, argp);    case SNDRV_CTL_IOCTL_ELEM_LOCK:        return snd_ctl_elem_lock(ctl, argp);    case SNDRV_CTL_IOCTL_ELEM_UNLOCK:        return snd_ctl_elem_unlock(ctl, argp);    case SNDRV_CTL_IOCTL_ELEM_ADD:        return snd_ctl_elem_add_user(ctl, argp, 0);    case SNDRV_CTL_IOCTL_ELEM_REPLACE:        return snd_ctl_elem_add_user(ctl, argp, 1);    case SNDRV_CTL_IOCTL_ELEM_REMOVE:        return snd_ctl_elem_remove(ctl, argp);    case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:        return snd_ctl_subscribe_events(ctl, ip);    case SNDRV_CTL_IOCTL_TLV_READ:        return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_READ);    case SNDRV_CTL_IOCTL_TLV_WRITE:        return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE);    case SNDRV_CTL_IOCTL_TLV_COMMAND:        return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD);    case SNDRV_CTL_IOCTL_POWER:        return -ENOPROTOOPT;    case SNDRV_CTL_IOCTL_POWER_STATE:#ifdef CONFIG_PM        return put_user(card->power_state, ip) ? -EFAULT : 0;#else        return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0;#endif    }    down_read(&snd_ioctl_rwsem);    list_for_each_entry(p, &snd_control_ioctls, list) {        err = p->fioctl(card, ctl, cmd, arg);        if (err != -ENOIOCTLCMD) {            up_read(&snd_ioctl_rwsem);            return err;        }    }    up_read(&snd_ioctl_rwsem);    dev_dbg(card->dev, "unknown ioctl = 0x%x\n", cmd);    return -ENOTTY;}
static int snd_ctl_elem_write_user(struct snd_ctl_file *file,                   struct snd_ctl_elem_value __user *_control){    struct snd_ctl_elem_value *control;    struct snd_card *card;    int result;    control = memdup_user(_control, sizeof(*control));    if (IS_ERR(control))        return PTR_ERR(control);    card = file->card;    snd_power_lock(card);    result = snd_power_wait(card, SNDRV_CTL_POWER_D0);    if (result >= 0)        result = snd_ctl_elem_write(card, file, control);//调用snd_ctl_elem_write    snd_power_unlock(card);    if (result >= 0)        if (copy_to_user(_control, control, sizeof(*control)))            result = -EFAULT;    kfree(control);    return result;}
static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,                  struct snd_ctl_elem_value *control){    struct snd_kcontrol *kctl;    struct snd_kcontrol_volatile *vd;    unsigned int index_offset;    int result;    down_read(&card->controls_rwsem);    kctl = snd_ctl_find_id(card, &control->id);    if (kctl == NULL) {        result = -ENOENT;    } else {        index_offset = snd_ctl_get_ioff(kctl, &control->id);        vd = &kctl->vd[index_offset];        if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) ||            kctl->put == NULL ||            (file && vd->owner && vd->owner != file)) {            result = -EPERM;        } else {            snd_ctl_build_ioff(&control->id, kctl, index_offset);            result = kctl->put(kctl, control);        }        if (result > 0) {            struct snd_ctl_elem_id id = control->id;            up_read(&card->controls_rwsem);            snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id);            return 0;        }    }    up_read(&card->controls_rwsem);    return result;}

通过前面分析可知这里的kctl->put(kctl, control)会调用SOC_ENUM_EXT(“Audio_Amp_R_Switch”, Audio_DL_Enum[0], Audio_AmpR_Get, Audio_AmpR_Set)中的Audio_AmpR_Set函数。

4、Device Config Manager

AudioALSADeviceConfigManager::AudioALSADeviceConfigManager():    mMixer(NULL),    mConfigsupport(false),    mInit(false){    ALOGD("%s()", __FUNCTION__);    int ret = LoadAudioConfig(AUDIO_DEVICE_EXT_CONFIG_FILE);    if (ret != NO_ERROR)    {        mConfigsupport = false;    }    else    {        mConfigsupport = true;    }    if (mMixer == NULL)    {        mMixer = AudioALSADriverUtility::getInstance()->getMixer();        ASSERT(mMixer != NULL);    }    mInit = true;    dump();}

#define AUDIO_DEVICE_EXT_CONFIG_FILE “/vendor/etc/audio_device.xml”
audio_device.xml是一个path管理文件,可以对相应的control进行turn on,turn off,setting。
快速修改audio_device.xml?
1)adb pull system/vendor/etc/audio_device.xml
2)edit
3)adb push system/vendor/etc/audio_device.xml

<?xml version="1.0" encoding="UTF-8" ?> <mixercontrol>    <versioncontrol value="1.01">    </versioncontrol>    <!-- These are the initial mixer settings -->    <kctl name="Audio_Speaker_class_Switch" value="CLASSAB" />    <!--headphone output-->        <path name="headphone_output" value="turnon">        <kctl name="Audio_Amp_R_Switch" value="On" />        <kctl name="Audio_Amp_L_Switch" value="On" />    </path>    <path name="headphone_output" value="turnoff">          <kctl name="Audio_Amp_R_Switch" value="Off" />        <kctl name="Audio_Amp_L_Switch" value="Off" />          </path>    <!--receiver output-->        <path name="receiver_output" value="turnon">        <kctl name="Voice_Amp_Switch" value="On" />    </path>    <path name="receiver_output" value="turnoff">        <kctl name="Voice_Amp_Switch" value="Off" />    </path>   <!-- 2-in-1 speaker output-->            <path name="two_in_one_speaker_output" value="turnon">        <kctl name="Audio_Speaker_class_Switch" value="RECEIVER" />        <kctl name="Speaker_Amp_Switch" value="On" />    </path>    <path name="two_in_one_speaker_output" value="turnoff">        <kctl name="Speaker_Amp_Switch" value="Off" />        <kctl name="Audio_Speaker_class_Switch" value="CALSSD" />    </path>   <!--speaker output-->    <path name="speaker_output" value="turnon">        <kctl name="Speaker_Amp_Switch" value="On" />    </path>    <path name="speaker_output" value="turnoff">        <kctl name="Speaker_Amp_Switch" value="Off" />    </path>     <!--headhpone_speaker output-->        <path name="headphoneSpeaker_output" value="turnon">        <kctl name="Headset_Speaker_Amp_Switch" value="On" />    </path>    <path name="headphoneSpeaker_output" value="turnoff">        <kctl name="Headset_Speaker_Amp_Switch" value="Off" />    </path>    <!--external_speaker output-->        <path name="ext_speaker_output" value="turnon">        <kctl name="Ext_Speaker_Amp_Switch" value="On" />    </path>    <path name="ext_speaker_output" value="turnoff">        <kctl name="Ext_Speaker_Amp_Switch" value="Off" />    </path>        <!--mic setting-->     <path name="builtin_Mic_Mic1" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC1" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_ADC_2_Switch" value="On" />        <kctl name="Audio_Preamp1_Switch" value="IN_ADC1" />          <kctl name="Audio_Preamp2_Switch" value="IN_ADC1" />                            </path>    <path name="builtin_Mic_Mic1" value="turnoff">           <kctl name="Audio_Preamp1_Switch" value="OPEN" />        <kctl name="Audio_Preamp2_Switch" value="OPEN" />               <kctl name="Audio_ADC_1_Switch" value="Off" />        <kctl name="Audio_ADC_2_Switch" value="Off" />    </path>    <path name="builtin_Mic_Mic1_Inverse" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC1" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_ADC_2_Switch" value="On" />        <kctl name="Audio_Preamp1_Switch" value="IN_ADC3" />          <kctl name="Audio_Preamp2_Switch" value="IN_ADC3" />                            </path>    <path name="builtin_Mic_Mic1_Inverse" value="turnoff">           <kctl name="Audio_Preamp1_Switch" value="OPEN" />        <kctl name="Audio_Preamp2_Switch" value="OPEN" />               <kctl name="Audio_ADC_1_Switch" value="Off" />        <kctl name="Audio_ADC_2_Switch" value="Off" />    </path>    <path name="builtin_Mic_Mic2" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC1" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_ADC_2_Switch" value="On" />        <kctl name="Audio_Preamp1_Switch" value="IN_ADC3" />          <kctl name="Audio_Preamp2_Switch" value="IN_ADC3" />                            </path>    <path name="builtin_Mic_Mic2" value="turnoff">           <kctl name="Audio_Preamp1_Switch" value="OPEN" />         <kctl name="Audio_Preamp2_Switch" value="OPEN" />               <kctl name="Audio_ADC_1_Switch" value="Off" />        <kctl name="Audio_ADC_2_Switch" value="Off" />    </path>    <path name="builtin_Mic_Mic2_Inverse" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC1" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_ADC_2_Switch" value="On" />        <kctl name="Audio_Preamp1_Switch" value="IN_ADC1" />          <kctl name="Audio_Preamp2_Switch" value="IN_ADC1" />                            </path>    <path name="builtin_Mic_Mic2_Inverse" value="turnoff">           <kctl name="Audio_Preamp1_Switch" value="OPEN" />         <kctl name="Audio_Preamp2_Switch" value="OPEN" />               <kctl name="Audio_ADC_1_Switch" value="Off" />        <kctl name="Audio_ADC_2_Switch" value="Off" />    </path>    <path name="builtin_Mic_SingleMic" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC1" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_Preamp1_Switch" value="IN_ADC1" />          <kctl name="Audio_Preamp2_Switch" value="IN_ADC1" />               </path>    <path name="builtin_Mic_SingleMic" value="turnoff">        <kctl name="Audio_Preamp1_Switch" value="OPEN" />        <kctl name="Audio_Preamp2_Switch" value="OPEN" />        <kctl name="Audio_ADC_1_Switch" value="Off" />    </path>    <path name="builtin_Mic_DualMic" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC1" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_ADC_2_Switch" value="On" />         <kctl name="Audio_Preamp1_Switch" value="IN_ADC1" />                <kctl name="Audio_Preamp2_Switch" value="IN_ADC3" />    </path>    <path name="builtin_Mic_DualMic" value="turnoff">        <kctl name="Audio_Preamp1_Switch" value="OPEN" />        <kctl name="Audio_Preamp2_Switch" value="OPEN" />               <kctl name="Audio_ADC_1_Switch" value="Off" />        <kctl name="Audio_ADC_2_Switch" value="Off" />            </path>    <path name="builtin_Mic_BackMic" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC1" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_ADC_2_Switch" value="On" />        <kctl name="Audio_Preamp1_Switch" value="IN_ADC3" />        <kctl name="Audio_Preamp2_Switch" value="IN_ADC3" />            </path>    <path name="builtin_Mic_BackMic" value="turnoff">        <kctl name="Audio_Preamp1_Switch" value="OPEN" />        <kctl name="Audio_Preamp2_Switch" value="OPEN" />               <kctl name="Audio_ADC_1_Switch" value="Off" />        <kctl name="Audio_ADC_2_Switch" value="Off" />    </path>    <path name="builtin_Mic_BackMic_Inverse" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC1" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_ADC_2_Switch" value="On" />        <kctl name="Audio_Preamp1_Switch" value="IN_ADC1" />        <kctl name="Audio_Preamp2_Switch" value="IN_ADC1" />            </path>    <path name="builtin_Mic_BackMic_Inverse" value="turnoff">        <kctl name="Audio_Preamp1_Switch" value="OPEN" />        <kctl name="Audio_Preamp2_Switch" value="OPEN" />               <kctl name="Audio_ADC_1_Switch" value="Off" />        <kctl name="Audio_ADC_2_Switch" value="Off" />    </path>     <path name="headset_mic_input" value="turnon">        <kctl name="Audio_MicSource1_Setting" value="ADC2" />        <kctl name="Audio_ADC_1_Switch" value="On" />        <kctl name="Audio_ADC_2_Switch" value="On" />        <kctl name="Audio_Preamp1_Switch" value="IN_ADC2" />        <kctl name="Audio_Preamp2_Switch" value="IN_ADC2" />            </path>    <path name="headset_mic_input" value="turnoff">        <kctl name="Audio_Preamp1_Switch" value="OPEN" />        <kctl name="Audio_Preamp2_Switch" value="OPEN" />                   <kctl name="Audio_ADC_1_Switch" value="Off" />        <kctl name="Audio_ADC_2_Switch" value="Off" />    </path>    <path name="sidetone_switch" value="turnon">        <kctl name="Audio_Sidetone_Switch" value="On" />    </path>    <path name="sidetone_switch" value="turnoff">        <kctl name="Audio_Sidetone_Switch" value="Off" />    </path>           <!--mic1 type setting-->        <path name="Mic1TypeACCMode" value="setting">        <kctl name="Audio_MIC1_Mode_Select" value="ACCMODE" />>    </path>    <path name="Mic1TypeDCCMode" value="setting">        <kctl name="Audio_MIC1_Mode_Select" value="DCCMODE" />>    </path>    <path name="Mic1TypeDMICMode" value="setting">        <kctl name="Audio_MIC1_Mode_Select" value="DMIC" />>    </path>             <path name="Mic1TypeDCCECMDIFFMode" value="setting">        <kctl name="Audio_MIC1_Mode_Select" value="DCCECMDIFFMODE" />>    </path>    <path name="Mic1TypeDCCECMSINGLEMode" value="setting">        <kctl name="Audio_MIC1_Mode_Select" value="DCCECMSINGLEMODE" />>    </path>   <!--mic2 type setting-->        <path name="Mic2TypeACCMode" value="setting">        <kctl name="Audio_MIC2_Mode_Select" value="ACCMODE" />>    </path>    <path name="Mic2TypeDCCMode" value="setting">        <kctl name="Audio_MIC2_Mode_Select" value="DCCMODE" />>    </path>    <path name="Mic2TypeDMICMode" value="setting">        <kctl name="Audio_MIC2_Mode_Select" value="DMIC" />>    </path>             <path name="Mic2TypeDCCECMDIFFMode" value="setting">        <kctl name="Audio_MIC2_Mode_Select" value="DCCECMDIFFMODE" />>    </path>    <path name="Mic2TypeDCCECMSINGLEMode" value="setting">        <kctl name="Audio_MIC2_Mode_Select" value="DCCECMSINGLEMODE" />>    </path>   <!--mic3 type setting-->     <path name="Mic3TypeACCMode" value="setting">        <kctl name="Audio_MIC3_Mode_Select" value="ACCMODE" />>    </path>    <path name="Mic3TypeDCCMode" value="setting">        <kctl name="Audio_MIC3_Mode_Select" value="DCCMODE" />>    </path>    <path name="Mic3TypeDMICMode" value="setting">        <kctl name="Audio_MIC3_Mode_Select" value="DMIC" />>    </path>             <path name="Mic3TypeDCCECMDIFFMode" value="setting">        <kctl name="Audio_MIC3_Mode_Select" value="DCCECMDIFFMODE" />>    </path>    <path name="Mic3TypeDCCECMSINGLEMode" value="setting">        <kctl name="Audio_MIC3_Mode_Select" value="DCCECMSINGLEMODE" />>    </path>   <!--mic4 type setting-->        <path name="Mic4TypeACCMode" value="setting">        <kctl name="Audio_MIC4_Mode_Select" value="ACCMODE" />>    </path>    <path name="Mic4TypeDCCMode" value="setting">        <kctl name="Audio_MIC4_Mode_Select" value="DCCMODE" />>    </path>    <path name="Mic4TypeDMICMode" value="setting">        <kctl name="Audio_MIC4_Mode_Select" value="DMIC" />>    </path>             <path name="Mic4TypeDCCECMDIFFMode" value="setting">        <kctl name="Audio_MIC4_Mode_Select" value="DCCECMDIFFMODE" />>    </path>    <path name="Mic4TypeDCCECMSINGLEMode" value="setting">        <kctl name="Audio_MIC4_Mode_Select" value="DCCECMSINGLEMODE" />>    </path>    <path name="Mic_Setting_Inverse" value="setting">        <kctl name="Audio_Preamp1_Switch" value="IN_ADC3" />>        <kctl name="Audio_Preamp2_Switch" value="IN_ADC1" />>            </path>    <path name="Mic_Setting_NoInverse" value="setting">        <kctl name="Audio_Preamp1_Switch" value="IN_ADC1" />>        <kctl name="Audio_Preamp2_Switch" value="IN_ADC3" />>            </path>            </mixercontrol>

5、Tinyalsa工具使用

mtk默认是没有编译tinyalsa工具的,需要进行模块化编译。下面介绍如何使用tinyalsa工具。

(1)编译:mmm external/tinyalsa
(2)将out/target/product/nicklaus/system/bin/路径下生成文件tinymix、tinyplay、tinycap、tinypcminfo 四个文件adb push到system/bin/下
(3)运行tinymix,查看配置混音器。通过tinymix ctl On/Off可以进行修改对应控件的状态。
这里写图片描述
这里写图片描述
(4)播放音乐:tinyplay
先进行用tinymix修改 Ext_Speaker_Amp_Switch、Audio_Amp_R_Switch、Audio_Amp_L_Switch状态为On,打开这些通路,再运行 tinyplay sdcard/xxx.wav即可播放音乐。(注:只能播放大于等于16bit wav格式音乐)
这里写图片描述
(5)录音:tinycap
(6)查看声卡信息 tinypcminfo -D card -d device
cat /proc/asound/cards
这里写图片描述

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 苹果刷机出现的问题怎么办 小米手机解不开图案锁怎么办 小米6无限重启怎么办 小米5一直显示mi怎么办 小米手机电池进入休眠状态怎么办 小米3s开不开机怎么办 小米n充电关机开机不了怎么办? 关机后强制刷机怎么办 红米手机开机画面怎么办 红米note1无法清理数据怎么办 红米2a密码忘记怎么办 线刷也不成功该怎么办 红米2开不了机怎么办 魅族无限重启怎么办 坚果pro无法双清怎么办 usb外置网卡网速慢怎么办? 无线路由器被改密码怎么办 电脑打不开flv格式的视频怎么办 电脑打不开pdf格式的文件怎么办 pdf格式在电脑上打不开怎么办 zip压缩的时候空间不足怎么办 电子发票填抬头错了怎么办 发票写错一个字怎么办 普票税率开错了怎么办 税率开错为17了怎么办 电子发票抬头错了怎么办 发票抬头错了一个字怎么办 5月税率开错了怎么办 如果发票是假的怎么办 发票收款人名字写错了怎么办 医院发票名字写错了怎么办 购买方发票联丢失怎么办 市中区超市办理发票怎么办 发票购买薄丢了怎么办 发票领用簿丢了怎么办 摩托车证扣12分怎么办 初中孩孑想扩展单词量怎么办 恒安保险倒闭了怎么办 小麦收割机卸粮筒总是转怎么办 非牛顿体结块了怎么办 非牛顿流体硬了怎么办