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


相关推荐

  • C# List集合转Json字符串示例代码

    C# List集合转Json字符串示例代码将list集合转换为Json字符串简单实现代码:publicstaticstringGetJosn(List<CalendarInfo>list){stringjsonStr=”[“;if(list!=null&&list.Count>0)…

    2022年9月25日
    2
  • 树莓派连接wifi教程[通俗易懂]

    树莓派连接wifi教程[通俗易懂]第一种方法:如果你已经连接了VNC图形界面,就像手机电脑一样点击wifi的图标找到你的wifi输入密码就行第二种方法:如果登录了putty1.输入sudonano/etc/wpa_supplicant/wpa_supplicant.conf2.在尾部添加network={ssid=""psk=""}引号内容SSID是你的无线名称PSK是你的无线密码无线名称不能是中…

    2022年4月28日
    356
  • EwebEditor漏洞[通俗易懂]

    EwebEditor漏洞[通俗易懂]一、后台上马漏洞各位站长在使用eWebEditor的时候是否发现,eWebEditor配置不当会使其成为网站中的隐形炸弹呢?第一次发现这漏洞源于去年的一次入侵,在山穷水尽的时候发现了eWebEdito

    2022年7月1日
    32
  • mysql修改用户密码命令_linux下mysql修改用户密码命令

    mysql修改用户密码命令_linux下mysql修改用户密码命令Linux下有时候想要修改用户密码用什么命令呢?下面由学习啦小编为大家整理了linux下mysql修改用户密码命令的相关知识,希望对大家有帮助!linux的mysql修改用户密码命令详解修改的用户都以root为列。分为两种情况:linux修改mysql用户密码情况一、拥有原来的myql的root的密码方法一:在mysql系统外,使用mysqladmin#mysqladmin-uroot-p…

    2022年6月17日
    35
  • “儿子,千万别帮老婆做家务!”爸爸写的这封信火了

    文 | 十二朵女王 · 主播 | 维维 儿子,明天就是你的婚礼了。 我必须提醒你,一旦结了婚,你就有了自己的新家,我和你妈不用你操心,你只用竭尽全力照顾好你自己…

    2021年6月21日
    81
  • 易驱线主控芯片对比(电动三轮电机90O瓦世纪通达)

    前几篇介绍了ODrive在Windows下的使用环境搭建,驱动3508/5008无刷电机、TLE5012B、AS5047P的ABI编码器配置、AS5047P-SPI绝对值编码器配置。ODrive踩坑(一)windows下使用环境的搭建,odrivetool及USB驱动的安装ODrive踩坑(二)电机和编码器参数配置、校准、位置闭环模式转动电机(TLE5012B-ABI)ODrive踩坑(三)ODrive配置使用AS5047P磁编码器的ABI接口ODrive踩坑(四)ODrive配置AS504

    2022年4月15日
    249

发表回复

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

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