pcm设备相关代码解析

注:这部分主要是tinyplay和tinycap涉及到

结构体

先把涉及到的数据结构列出:

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;
    unsigned int subdevice;

    struct pcm_ops *ops;
    void *data;
    void *snd_node;
};

/* 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;
};

/* pcm parameters */
enum pcm_param
{
    /* mask parameters */
    PCM_PARAM_ACCESS,
    PCM_PARAM_FORMAT,
    PCM_PARAM_SUBFORMAT,
    /* interval parameters */
    PCM_PARAM_SAMPLE_BITS,
    PCM_PARAM_FRAME_BITS,
    PCM_PARAM_CHANNELS,
    PCM_PARAM_RATE,
    PCM_PARAM_PERIOD_TIME,
    PCM_PARAM_PERIOD_SIZE,
    PCM_PARAM_PERIOD_BYTES,
    PCM_PARAM_PERIODS,
    PCM_PARAM_BUFFER_TIME,
    PCM_PARAM_BUFFER_SIZE,
    PCM_PARAM_BUFFER_BYTES,
    PCM_PARAM_TICK_TIME,
};

pcm_open()

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;
    int rc, pcm_type;

	...
}
  1. 判断config存在 --- if (!config)

  2. 为pcm分配空间 --- calloc()

  3. 拿到pcm设备的节点 --- pcm->snd_node = snd_utils_get_dev_node()

  4. 拿到pcm类型 --- pcm_type = snd_utils_get_node_type()

  5. 打开设备 --- pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node)

  6. 拿到相关信息 --- pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)

  7. 初始化参数信息 --- param_init(¶ms)

  8. 设置参数 --- param_set_mask() param_set_int()

  9. 写入参数 --- pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)

  10. 读出设置好后的参数,拿到映射的内存地址 ---

    /* get our refined hw_params */
    config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
    config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS);
    pcm->buffer_size = config->period_count * config->period_size;
    ...
    pcm->mmap_buffer = pcm->ops->mmap(pcm->data, NULL,
    			pcm_frames_to_bytes(pcm, pcm->buffer_size),
    			PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, 0);
  11. 根据config的值设置sparam以及pcm->config里的相关参数

  12. 写入参数 --- pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)

  13. 检测映射状态 --- rc = pcm_hw_mmap_status(pcm);

  14. 设置pcm状态并返回 --- pcm->underruns = 0; return pcm;

注:补充一下第3和4点,其实3里面涉及到了一个动态库"libsndcardparser.so",但是这个库一般默认不存在,它属于一个插件框架,所以3返回为NULL,这也导致了4返回SND_NODE_TYPE_HW,最后导致ops = &hw_ops;

下面再解析一下ops的相关内容

struct pcm_ops hw_ops = {
    .open = pcm_hw_open,
    .close = pcm_hw_close,
    .ioctl = pcm_hw_ioctl,
    .mmap = pcm_hw_mmap,
    .munmap = pcm_hw_munmap,
    .poll = pcm_hw_poll,
};
struct pcm_hw_data {
    unsigned int card;
    unsigned int device;
    unsigned int fd;
    void *snd_node;
};

来看看pcm_ops_open()

static int pcm_hw_open(unsigned int card, unsigned int device,
                unsigned int flags, void **data,
                __attribute__((unused)) void *node)
{
    struct pcm_hw_data *hw_data;
    char fn[256];
    int fd;
    ...
} 
  1. 为pcm_hw_data分配内存 --- calloc()

  2. 拼凑出节点名称并打开 --- snprintf(fn, ...) open(fn, ...)

  3. 设置该文件读写方式为阻塞方式 --- fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK)

  4. 为hw_data赋值之后返回

hw_data->snd_node = node;
hw_data->card = card;
hw_data->device = device;
hw_data->fd = fd;
*data = hw_data;
return fd;

pcm_hw_ioctl()则是通过调用ioctl()完成相关操作,不展开讨论

pcm_write()

int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{
	struct snd_xferi x;				
	...
}

struct snd_xferi {
        snd_pcm_sframes_t result;
        void __user *buf;
        snd_pcm_uframes_t frames;
};
  1. 判断标志位 --- if (pcm->flags & PCM_IN)

  2. 为x赋值 ---

    x.buf = (void*)data;
    x.frames = count / (pcm->config.channels *
                        pcm_format_to_bits(pcm->config.format) / 8);
  3. 进入无限for循环,这里也就是写数据、判断是否成功及改变标志位 ---

    for (;;) {
        if (!pcm->running) {
            int prepare_error = pcm_prepare(pcm);
            if (prepare_error)
                return prepare_error;
            if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
                return oops(pcm, errno, "cannot write initial data");
            pcm->running = 1;
            return 0;
        }
        if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
            pcm->prepared = 0;
            pcm->running = 0;
            if (errno == EPIPE) {
                /* we failed to make our window -- try to restart if we are
                 * allowed to do so.  Otherwise, simply allow the EPIPE error to
                 * propagate up to the app level */
                pcm->underruns++;
                if (pcm->flags & PCM_NORESTART)
                    return -EPIPE;
                continue;
            }
            return oops(pcm, errno, "cannot write stream data");
        }
        return 0;
    }

pcm_read()

pcm_read()和read_write()十分类似,直接列出pcm_read()里面的循环部分:

for (;;) {
    if (!pcm->running) {
        if (pcm_start(pcm) < 0) {
            fprintf(stderr, "start error");
            return -errno;
        }
    }
    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
        pcm->prepared = 0;
        pcm->running = 0;
        if (errno == EPIPE) {
                /* we failed to make our window -- try to restart */
            pcm->underruns++;
            continue;
        }
        return oops(pcm, errno, "cannot read stream data");
    }
    return 0;
}

你可能感兴趣的:(tinyalsa相关,音频,alsa,c语言,驱动开发,linux)