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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • phpstrom2021激活码【注册码】

    phpstrom2021激活码【注册码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月18日
    43
  • python获取窗口句柄

    python获取窗口句柄importwin32guiclassHwnd():defget_hwnd_dic(self,hwnd,hwnd_title):if(win32gui.IsWindow(hwnd)andwin32gui.IsWindowEnabled(hwnd)andwin32gui.IsWindowVisible(hwnd)andwin32gui.GetWindowTe

    2022年7月21日
    26
  • 数据结构课程设计–实验室设备管理系统(c语言)[通俗易懂]

    数据结构课程设计–实验室设备管理系统(c语言)[通俗易懂]#include<stdio.h>//标准输入输出函数库#include<stdlib.h>//标准函数库#include<string.h>//字符串函数库#include<conio.h>//屏幕操作函数库#defineHEADER1″——————————-实验室…

    2022年10月10日
    4
  • Android数据库加密

    Android数据库加密Android数据库加密一、简介SQLite是一个轻量的、跨平台的、开源的数据库引擎,它的读写效率、资源消耗总量、延迟时间和整体简单性上具有的优越性,使其成为移动平台数据库的最佳解决方案(如Android、iOS)。Android系统内置了SQLite数据库,并且提供了一整套的API用于对数据库进行增删改查操作,具体就不详细说明了。然而,Android平台自带的SQLite有一个致命的缺陷:…

    2022年5月13日
    44
  • C++函数指针详解

    C++函数指针详解1.定义每一个函数都占用一段内存单元,它们有一个起始地址,指向函数入口地址的指针称为函数指针。2.语法指向函数的指针变量的一般定义形式为:数据类型(*指针变量名)(参数表);3.说明1)函数指针的定义形式中的数据类型是指函

    2022年6月22日
    28
  • 硬核!高频Linux命令大总结,建议收藏~

    硬核!高频Linux命令大总结,建议收藏~前言记得不久前跟大家大分享了一波个人在平时日常工作、学习、开发、写文字、做视频等过程中,一些好用高效的在线工具和网站,并且把自己的浏览器收藏夹书签离线文件都导出给大家了。很多小伙伴后台反馈还不错,说书签一导入后,很多工具确实挺好用,主要省了很多找资源和整理的时间。今天继续分享,最近花了不少时间把平时开发过程中常用的一些Linux系统命令给做了一个大整理,形成一个常用高频Linux速查备忘录。有了它,还怕Linux操作系统常用操作和命令记不住么?接下来直接上菜吧。注:本文GitHubhtt

    2022年5月8日
    39

发表回复

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

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