注:这部分主要是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循环,这里也就是写数据、判断是否成功及改变标志位 ---文章来源:https://www.toymoban.com/news/detail-402537.html
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()里面的循环部分:文章来源地址https://www.toymoban.com/news/detail-402537.html
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;
}
到了这里,关于pcm设备相关代码解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!