注:这部分主要是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; ... }
- 判断config存在 — if (!config)
- 为pcm分配空间 — calloc()
- 拿到pcm设备的节点 — pcm->snd_node = snd_utils_get_dev_node()
- 拿到pcm类型 — pcm_type = snd_utils_get_node_type()
- 打开设备 — pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node)
- 拿到相关信息 — pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)
- 初始化参数信息 — param_init(¶ms)
- 设置参数 — param_set_mask() param_set_int()
- 写入参数 — pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)
- 读出设置好后的参数,拿到映射的内存地址 —
/* 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);
- 根据config的值设置sparam以及pcm->config里的相关参数
- 写入参数 — pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)
- 检测映射状态 — rc = pcm_hw_mmap_status(pcm);
- 设置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; ... }
- 为pcm_hw_data分配内存 — calloc()
- 拼凑出节点名称并打开 — snprintf(fn, …) open(fn, …)
- 设置该文件读写方式为阻塞方式 — fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK)
- 为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; };
- 判断标志位 — if (pcm->flags & PCM_IN)
- 为x赋值 —
x.buf = (void*)data; x.frames = count / (pcm->config.channels * pcm_format_to_bits(pcm->config.format) / 8);
- 进入无限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; }
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232873.html原文链接:https://javaforall.net