1. 程式人生 > >MT6737 Android N 平臺 Audio系統學習----TinyAlsa

MT6737 Android N 平臺 Audio系統學習----TinyAlsa

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 = c