理解ALSA(二):概览
文章目录
- 1 ALSA概览图
- 2 流传输时的ALSA栈
- 2.1 一个最小的录音程序和一个最小的播放程序
- 2.2 pcm_open()栈
- 2.3 pcm_read()栈
- 2.4 pcm_start()栈
- 2.5 pcm_close()栈
- 3 播放时的数据传输
- 3.1 正常情况
- 3.2 start_threshold
- 3.3 stop_threshold
- 3.4 xrun的情况
- 4 录音时的数据传输
- 4.1 正常情况
- 4.2 start_threshold
- 4.3 stop_threshold
- 4.4 xrun的情况
- 5 xrun发生的时候,应该要做什么呢?
- 6 有用的调试信息
1 ALSA概览图
2 流传输时的ALSA栈
2.1 一个最小的录音程序和一个最小的播放程序
为方便说明,让我们用tinyalsalib写一个最简单的录音程序来录5秒的音频(PCM数据)并输出到stdout,然后写一个最简单的音频播放程序来播放来自stdin的PCM数据。
— sample1.c —
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tinyalsa/asoundlib.h>int main(void)
{unsigned int card = 0;unsigned int device = 0;struct pcm *pcm = NULL;struct pcm_config config;unsigned int size = 0;char *buffer = NULL;int duration_secs = 5;int n = 0;memset(&config, 0, sizeof(config));config.channels = 2;config.rate = 48000;config.period_size = 1024;config.period_count = 4;config.format = PCM_FORMAT_S16_LE;pcm = pcm_open(card, device, PCM_IN, &config);if (!pcm || !pcm_is_ready(pcm)) {fprintf(stderr, "Unable to open PCM device (%s)\n", pcm_get_error(pcm));goto err;}size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));buffer = malloc(size);if (!buffer) {fprintf(stderr, "Unable to allocate %u bytes\n", size);goto err;}n = pcm_frames_to_bytes(pcm, duration_secs * config.rate) / size;while (n-- && !pcm_read(pcm, buffer, size)) {fwrite(buffer, 1, size, stdout);}err:free(buffer);pcm_close(pcm);return 0;
}
编译:
gcc sample1.c -ltinyalsa -ldl
运行:
./a.out > test.pcm
录到的声音被保存成test.pcm
文件。
现在让我们写一个最简单的程序去播放这个文件。
— sample2.c —
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tinyalsa/asoundlib.h>int main(void)
{unsigned int card = 0;unsigned int device = 0;struct pcm_config config;struct pcm *pcm = NULL;char *buffer = NULL;unsigned int size;int num_read;memset(&config, 0, sizeof(config));config.channels = 2;config.rate = 48000;config.period_size = 1024;config.period_count = 4;config.format = PCM_FORMAT_S16_LE;pcm = pcm_open(card, device, PCM_OUT, &config);if (!pcm || !pcm_is_ready(pcm)) {fprintf(stderr, "Unable to open PCM device %u (%s)\n",device, pcm_get_error(pcm));goto err;}size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));buffer = malloc(size);if (!buffer) {fprintf(stderr, "Unable to allocate %d bytes\n", size);goto err;}while ((num_read = fread(buffer, 1, size, stdin)) > 0) {if (pcm_write(pcm, buffer, num_read)) {fprintf(stderr, "Error playing sample\n");break;}}err:free(buffer);pcm_close(pcm);return 0;
}
编译:
gcc sample2.c -ltinyalsa -ldl
运行:
./a.out < test.pcm
我们可以听到刚刚录的5秒声音(test.pcm)从喇叭里播了出来!
注:这里写的两个程序,我们假设card=0,device=0,并且这个设备支持48k 16bit 2ch录音和播放。如果你的card 0 device 0不支持这些参数,这个两个程序会不工作,需要调整合适的参数。
接下来,让我们看看每一个alsa函数的调用栈是什么样的。
2.2 pcm_open()栈
用户的pcm_open()
相当于先对ASoC各个驱动模块startup()
,再做hw_params()
。
pcm_open()pcm->fd = open("/dev/snd/pcmC0D0c")snd_pcm_capture_open()snd_pcm_open(SNDRV_PCM_STREAM_CAPTURE)snd_pcm_open_file()snd_pcm_open_substream()substream->ops->open()soc_pcm_open()cpu_dai->driver->ops->startup()platform->driver->ops->open()codec_dai->driver->ops->startup()rtd->dai_link->ops->startup()ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)snd_pcm_hw_params_user()snd_pcm_hw_params()substream->ops->hw_params()soc_pcm_hw_params()rtd->dai_link->ops->hw_params()dai->driver->ops->hw_params()platform->driver->ops->hw_params()ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)
2.3 pcm_read()栈
用户的pcm_read()
相当于做从内核缓冲区到用户缓冲区的copy_to_user()
。即把硬件写到内核缓冲区的数据拷贝到用户缓冲区。(mmap模式例外,其没有数据拷贝的动作,性能更好。)
pcm_read()if (!pcm->running)pcm_start()pcm->running = 1ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)snd_pcm_lib_read()snd_pcm_lib_read1(transfer)transfer(substream, appl_ofs, data, offset, frames)snd_pcm_lib_read_transfer()substream->ops->copy() *or* copy_to_user()
2.4 pcm_start()栈
用户的pcm_start()
相当于对ASoC的各个驱动模块做prepare()
和trigger(START)
动作。
pcm_start()ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)snd_pcm_prepare()snd_pcm_do_prepare()substream->ops->prepare()soc_pcm_prepare()rtd->dai_link->ops->prepare()platform->driver->ops->prepare()codec_dai->driver->ops->prepare()cpu_dai->driver->ops->prepare()ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)snd_pcm_action_lock_irq()snd_pcm_do_start()substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START)soc_pcm_trigger()codec_dai->driver->ops->trigger()platform->driver->ops->trigger()cpu_dai->driver->ops->trigger()rtd->dai_link->ops->trigger()
2.5 pcm_close()栈
用户的pcm_close()
相当于对ASoC的各个驱动模块做trigger(STOP)
, hw_free()
和shutdown()
动作。
pcm_close()close(pcm->fd)snd_pcm_release()snd_pcm_release_substream()snd_pcm_drop(substream)snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP)snd_pcm_do_stop()substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP)soc_pcm_trigger()codec_dai->driver->ops->trigger()platform->driver->ops->trigger()cpu_dai->driver->ops->trigger()rtd->dai_link->ops->trigger()if (substream->hw_opened)if (substream->ops->hw_free != NULL)substream->ops->hw_free(substream)substream->ops->close(substream)soc_pcm_close()cpu_dai->driver->ops->shutdown()codec_dai->driver->ops->shutdown()rtd->dai_link->ops->shutdown()platform->driver->ops->close()substream->hw_opened = 0
3 播放时的数据传输
ALSA内核缓冲区是一个环状缓冲区。播放的时候,appl_ptr
为写指针,hw_ptr
为读指针。
3.1 正常情况
appl_ptrv|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|vhw_ptr
hw_ptr
以采样率的速度移动,也就是说硬件以采样率的速度消耗数据,并且每读走一个period
数据,发一次中断给内核。appl_ptr
应该要能够移动地足够快,这样才可以保证缓冲区里有足够的数据供硬件消耗。当缓冲区数据满了的时候,appl_ptr
会被hw_ptr
阻塞。然而当缓冲区数据空的时候,hw_ptr
一般来说不会被appl_ptr
阻塞。
3.2 start_threshold
appl_ptrv|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|----------------|----------------|vhw_ptr
start_threshold
决定了硬件什么时候开始消耗数据(开始输出)。通常它的值被设为内核缓冲区的一半或者整个缓冲区,这样的话起播的时候就不容易发生underrun。但是start_threshold
越大的话,延时就会越大。对于延时敏感的应用,这个值要仔细考虑。
3.3 stop_threshold
appl_ptrv|----------------|XXXXXXXXXXXXXXXX|----------------|----------------|vhw_ptr
stop_threshold
决定了硬件什么时候停止消耗数据(停止输出)。当缓冲区里空位置的数量超过了这个值,播放就自动停止。如果这个值被为整个缓冲区,那么播放会到所有数据都消耗光了才停止。这里有个风险,如果hw_ptr
停止不及时,一些垃圾数据会被读走播放出来产生杂音。所有stop_threshold
经常被设为比整个缓冲区稍微小一点,这样能尽量保证没有杂音输出,然而最后几帧也会相应丢失。
3.4 xrun的情况
appl_ptrv|----------------|----------------|----------------|----------------|vhw_ptr
当缓冲区里所有的数据被消耗光了,而应用又不写入新的数据,underrun就会发生,同时alsa core会进入xrun状态。应用会得到-EPIPE
的错误,需要做一些动作来将xrun状态恢复成正常状态。
4 录音时的数据传输
ALSA内核缓冲区是一个环状缓冲区。录音的时候,appl_ptr
为读指针,hw_ptr
为写指针。
4.1 正常情况
appl_ptr^|----------------|XXXXXXXXXXXXXXXX|----------------|----------------|^hw_ptr
hw_ptr
以采样率的速度移动,也就是说硬件以采用率的速度向缓冲区里写数据,并且每写入一个period数据,发一次中断给内核。appl_ptr
应该能够移动地足够快,缓冲区才不会被复写。当缓冲区数据空的时候,appl_ptr
会被hw_ptr
阻塞;然而当缓冲区数据满的时候,hw_ptr
一般来说不会被appl_ptr
阻塞。
4.2 start_threshold
appl_ptr^|XXXXXXXXXXXXXXXX|----------------|----------------|----------------|^hw_ptr
start_threshold
决定了应用开始读取数据的时间点。通常它被设为1帧,意思是只要缓冲区里有1帧,应用就可以把他读走。
4.3 stop_threshold
appl_ptr^|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|----------------|^hw_ptr
stop_threshold
决定了硬件什么时候停止填数据。当缓冲区里的数据量超过了这个值时,录音就自动停止。当这个值被设为整个缓冲区,那么意味着当数据填满整个缓冲区时,hw_ptr
才停止。
4.4 xrun的情况
appl_ptr^|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|^hw_ptr
当缓冲区里的数据满了,应用又不读走,overrun就会发生,并且alsa core会进入xrun状态。应用会得到-EPIPE
的错误,需要做一些动作来将xrun状态恢复成正常状态。
5 xrun发生的时候,应该要做什么呢?
无论对于录音还是播放,ALSA处理xrun的流程都是一样的。首先内核的硬件中断处理函数是第一个发现xrun的人。从调用栈能看出,当avail超过一个阈值时,立刻做xrun处理,其中就包含第一时间做snd_pcm_stop()。
irq_handler()snd_pcm_period_elapsed()snd_pcm_update_hw_ptr0()snd_pcm_update_state()if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)avail = snd_pcm_playback_avail(runtime)elseavail = snd_pcm_capture_avail(runtime)if (avail >= runtime->stop_threshold)xrun(substream)snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN)
用户程序随即发现ioctl(SNDRV_PCM_IOCTL_READI_FRAMES)
失败,并且errno == EPIPE
。在下次循环中,重新做pcm_start()
,以使xrun状态恢复。
pcm_read()
{for (;;) {if (!pcm->running) {if (pcm_start(pcm) < 0) {fprintf(stderr, "start error");return -errno;}}if (ioctl(pcm->fd, 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;}
}
6 有用的调试信息
$ tinycap test.wav
Capturing sample: 1 ch, 48000 hz, 16 bit
$ ls /proc/asound/card0/pcm0c/sub0
hw_params
info
status
sw_params
$ cat /proc/asound/card0/pcm0c/sub0/info
card: 0
device: 0
subdevice: 0
stream: CAPTURE
id: MultiMedia1 (*)
name:
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 0
$ cat /proc/asound/card0/pcm0c/sub0/hw_params
access: RW_INTERLEAVED
format: S16_LE
subformat: STD
channels: 1
rate: 48000 (48000/1)
period_size: 1024
buffer_size: 4096
$ cat /proc/asound/card0/pcm0c/sub0/sw_params
tstamp_mode: ENABLE
period_step: 1
avail_min: 1
start_threshold: 1
stop_threshold: 40960
silence_threshold: 0
silence_size: 0
boundary: 1073741824
$ cat /proc/asound/card0/pcm0c/sub0/status
state: RUNNING
owner_pid : 6004
trigger_time: 1197453.884234423
tstamp : 1197760.259512913
delay : 0
avail : 0
avail_max : 1024
-----
hw_ptr : 14685184
appl_ptr : 14685184
理解ALSA(二):概览相关推荐
- faster rcnn源码理解(二)之AnchorTargetLayer(网络中的rpn_data)
转载自:faster rcnn源码理解(二)之AnchorTargetLayer(网络中的rpn_data) - 野孩子的专栏 - 博客频道 - CSDN.NET http://blog.csdn.n ...
- AQS理解之二,自己设计一个锁
AQS理解之二,自己设计一个锁 一,实现锁的条件 首先我们想一想,如果我们自己实现一个类似于java中的锁,我们可能需要哪些必要的东西: 1,记录是哪个线程持有了锁. 2,如果有一个变量代表加锁,A线 ...
- 设计模式理解(二)创建型——单例、原型
设计模式理解(二)单例(Singleton)与原型(Prototype) 为什么一起写,因为懒.... 单例,就是用了面向对象语言的一些奇技淫巧,把构造函数私有了,然后用一个自身类型的静态指针作为全局 ...
- Docker的一些理解(二)
Docker的一些理解(二) 百度百科 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上 ...
- 本质安全设备标准(IEC60079-11)的理解(二)
本质安全设备标准(IEC60079-11)的理解(二) 十,本安设备的测试 我们知道如何测试本安设备以及一些基本概念后, 现在需要进一步说明: (1),本安设备的测试和一般软件,硬件的测试是完全不同的 ...
- SVPWM算法理解(二)——关于非零基本矢量幅值和线电压幅值的解释
SVPWM算法理解(二)--关于非零基本矢量幅值和线电压幅值的解释 1 引言 2 非零基本矢量的幅值 3 线电压的幅值 4 电压空间矢量图中的图形含义 5 如何保证逆变器的输出电压不失真 1 引言 ...
- DFT - 对芯片测试的理解(二) 详解
DFT - 对芯片测试的理解(二) 详解 参考: https://www.docin.com/p-2014360649.html The basic view of DFT scan chain 这图 ...
- 深入理解GBDT二分类算法
我的个人微信公众号: Microstrong 微信公众号ID: MicrostrongAI 微信公众号介绍: Microstrong(小强)同学主要研究机器学习.深度学习.计算机视觉.智能对话系统相关 ...
- 通信系统中对眼图的理解(二)
中文名称: 眼图 英文名称: eye diagram;eye pattern 定义: 示波器屏幕上所显示的数字通信符号,由许多波形部分重叠形成,其形状类似"眼"的图形." ...
最新文章
- android 九宫格绘制,Android draw9patch.bat 九宫格绘制工具使用
- 使用Yeoman定制前端脚手架
- Redis 主从复制(replication)
- 大数据(生于2006,卒于2019)已死!
- Java知识整理——基础知识
- 软件设计文档国家标准_GB8567--88
- android 怎么刷机,android系统怎么刷机
- 如何关闭mac烦人的更新升级提醒
- IBM发布JumpGate 连接OpenStack和公有云
- 推荐两款超实用的 gRPC 客户端调试工具
- .NET Framework 4.5的C#中的对话框消息
- 试论《华严经》来历的可信
- Python动态页面抓取超级指南
- SAP 配额协议及策略解析
- java图像压缩文件大小通过 thumbnailator-0.4.2-all.jar 进行压缩调用简单 jpg格式
- 【风马一族_Android】 图能
- CRM 学习笔记(一)
- 中国大学MOOC-陈越、何钦铭-数据结构-2020春期末考试【个人完整题解记录-判断选择部分】
- 微信小程序云开发定时推送订阅消息
- 概率论笔记1.5伯努利模型(二项分布)