pcm设备相关代码解析

pcm设备相关代码解析介绍并解析了 tinyalsa 中与 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(&params)
  8. 设置参数 — param_set_mask() param_set_int()
  9. 写入参数 — pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, &params)
  10. 读出设置好后的参数,拿到映射的内存地址 —
    /* 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; ... 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; }
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232873.html原文链接:https://javaforall.net

(0)
上一篇 2025年9月4日 上午10:01
下一篇 2025年9月4日 上午10:22


相关推荐

  • RC专题:积分电路理解

    RC专题:积分电路理解积分电路 微分电路我们对 Markdown 编辑器进行了一些功能拓展与语法支持 除了标准的 Markdown 编辑器功能 我们增加了如下几点新功能 帮助你用它写博客 全新的界面设计 将会带来全新的写作体验 在创作中心设置你喜爱的代码高亮样式 Markdown 将代码片显示选择的高亮样式进行展示 增加了图片拖拽功能 你可以将本地的图片直接拖拽到编辑区域直接展示 全新的 KaTeX 数学公式语法 增加了支持甘特图的 mermaid 语法 1 功能 增加了多屏幕编辑 Markdown 文章功能 增加了

    2025年10月16日
    7
  • idea查看自己的激活码_在线激活「建议收藏」

    (idea查看自己的激活码)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html83…

    2022年3月27日
    214
  • 计算机网络原理(谢希仁第八版)第四章课后习题答案

    计算机网络原理(谢希仁第八版)第四章课后习题答案第四章 1 网络层向上提供的服务有哪两种 是比较其优缺点 答 网络层向运输层提供 面向连接 虚电路 VirtualCircu 服务或 无连接 数据报服务前者预约了双方通信所需的一切网络资源 优点是能提供服务质量的承诺 即所传送的分组不出错 丢失 重复和失序 不按序列到达终点 也保证分组传送的时限 缺点是路由器复杂 网络成本高 后者无网络资源障碍 尽力而为 优缺点与前者互易 2 网络互连有何实际意义 进行网络互连时 有哪些共同的问题需要解决 答 网络互联可扩大用户共享资源范围和更大的通信区

    2026年3月17日
    2
  • idea后端开发必备插件:MybatisCodeHelperPro[通俗易懂]

    idea后端开发必备插件:MybatisCodeHelperPro[通俗易懂]官方文档在这:https://gejun123456.github.io/MyBatisCodeHelper-Pro/#/

    2026年2月8日
    11
  • ER图(实体-联系图)「建议收藏」

    ER图(实体-联系图)「建议收藏」概念E-R图也称实体-联系图(EntityRelationshipDiagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。构成ER图有实体(entity)、属性(attribute)、关系(relationship)三部分。用“矩形框”表示实体型,矩形框内写明实体名称;用“椭圆框”表示实体的属性,将属性名记入框中;用”菱形框“表示实体型之间的关系,在菱形…

    2026年2月17日
    5
  • PhpSpreadsheet_php标准输入流stdin

    PhpSpreadsheet_php标准输入流stdin实例化对象composer导包”phpoffice/phpspreadsheet”:”^1.11″,$spreadsheet=newSpreadsheet();$sheet=$spreadsheet->getActiveSheet();合并单元格$sheet->mergeCells(‘A1:J1’);设置单元格的内容$spreadsheet->getActiveSheet()->setCellValue(‘A2′,’序号’)-&

    2025年12月13日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号