文章目录

  • 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, &params)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(二):概览相关推荐

  1. faster rcnn源码理解(二)之AnchorTargetLayer(网络中的rpn_data)

    转载自:faster rcnn源码理解(二)之AnchorTargetLayer(网络中的rpn_data) - 野孩子的专栏 - 博客频道 - CSDN.NET http://blog.csdn.n ...

  2. AQS理解之二,自己设计一个锁

    AQS理解之二,自己设计一个锁 一,实现锁的条件 首先我们想一想,如果我们自己实现一个类似于java中的锁,我们可能需要哪些必要的东西: 1,记录是哪个线程持有了锁. 2,如果有一个变量代表加锁,A线 ...

  3. 设计模式理解(二)创建型——单例、原型

    设计模式理解(二)单例(Singleton)与原型(Prototype) 为什么一起写,因为懒.... 单例,就是用了面向对象语言的一些奇技淫巧,把构造函数私有了,然后用一个自身类型的静态指针作为全局 ...

  4. Docker的一些理解(二)

    Docker的一些理解(二) 百度百科 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上 ...

  5. 本质安全设备标准(IEC60079-11)的理解(二)

    本质安全设备标准(IEC60079-11)的理解(二) 十,本安设备的测试 我们知道如何测试本安设备以及一些基本概念后, 现在需要进一步说明: (1),本安设备的测试和一般软件,硬件的测试是完全不同的 ...

  6. SVPWM算法理解(二)——关于非零基本矢量幅值和线电压幅值的解释

    SVPWM算法理解(二)--关于非零基本矢量幅值和线电压幅值的解释 1 引言 2 非零基本矢量的幅值 3 线电压的幅值 4 电压空间矢量图中的图形含义 5 如何保证逆变器的输出电压不失真 1 引言   ...

  7. DFT - 对芯片测试的理解(二) 详解

    DFT - 对芯片测试的理解(二) 详解 参考: https://www.docin.com/p-2014360649.html The basic view of DFT scan chain 这图 ...

  8. 深入理解GBDT二分类算法

    我的个人微信公众号: Microstrong 微信公众号ID: MicrostrongAI 微信公众号介绍: Microstrong(小强)同学主要研究机器学习.深度学习.计算机视觉.智能对话系统相关 ...

  9. 通信系统中对眼图的理解(二)

    中文名称: 眼图 英文名称: eye diagram;eye pattern 定义: 示波器屏幕上所显示的数字通信符号,由许多波形部分重叠形成,其形状类似"眼"的图形." ...

最新文章

  1. android 九宫格绘制,Android draw9patch.bat 九宫格绘制工具使用
  2. 使用Yeoman定制前端脚手架
  3. Redis 主从复制(replication)
  4. 大数据(生于2006,卒于2019)已死!
  5. Java知识整理——基础知识
  6. 软件设计文档国家标准_GB8567--88
  7. android 怎么刷机,android系统怎么刷机
  8. 如何关闭mac烦人的更新升级提醒
  9. IBM发布JumpGate 连接OpenStack和公有云
  10. 推荐两款超实用的 gRPC 客户端调试工具
  11. .NET Framework 4.5的C#中的对话框消息
  12. 试论《华严经》来历的可信
  13. Python动态页面抓取超级指南
  14. SAP 配额协议及策略解析
  15. java图像压缩文件大小通过 thumbnailator-0.4.2-all.jar 进行压缩调用简单 jpg格式
  16. 【风马一族_Android】 图能
  17. CRM 学习笔记(一)
  18. 中国大学MOOC-陈越、何钦铭-数据结构-2020春期末考试【个人完整题解记录-判断选择部分】
  19. 微信小程序云开发定时推送订阅消息
  20. 概率论笔记1.5伯努利模型(二项分布)

热门文章

  1. As I Began to Love Myself
  2. 2021秋季开学必备数码产品!学生党的超实用好物清单
  3. 计算机操作系统期末复习,《计算机操作系统》期末复习课稿.docx
  4. OCH1660全极高灵敏低功耗霍尔开关
  5. 破解ChatGPT机遇与挑战,中国AIGC产业峰会给出最强答案
  6. redis 穿透、雪崩、击穿
  7. 算法与数据结构基础<一>----线性查找法
  8. 这样整理关键词,竞价推广更高效
  9. 《Effective Java》读书笔记
  10. Swift Literal Convertibles