PCM ALSA的一些基础知识

PCM
1、设备命名

API 库使用逻辑设备名而不是设备文件。设备名字可以是真实的硬件名字也可以是插件名字。硬件名字使用 hw:i,j 这样的格式。其中i是卡号,j是这块声卡上的设备号。第一个声音设备是hw:0,0.这 别名默认引用第一块声音设备并且在本文示例中一直会被用到。插件使用另外的唯一名字。比如plughw:, 表示一个插件,这个插件不提供对硬件设备的访问,而是提供像采样率转换这样的软件特性,硬件本身并不支持这样的特性。

2、声音缓存和数据传输
每个声卡都有一个硬件缓存区来保存记录下来的样本。当缓存区足够满时,声卡将产生一个中断。内核声卡驱动然后使用直接内存(DMA)访问通道将样本传送到内存中的应用程序缓存区。类似地,对于playback,任何应用程序使用DMA将自己的缓存区数据传送到声卡的硬件缓存区中。该硬件缓存区是一个 ring buffer 。也就是,说当数据到达缓存区末尾时将重新回到缓存区的起始位置。ALSA维护一个指针来指向硬件缓存以及应用程序缓存区中数据操作的当前位置。

从内核外部看,我们只对应用程序的缓存区感兴趣,所以这里只讨论应用程序缓存区。应用程序缓存区的大小可以通过ALSA库函数调用来控制。缓存区可以很大,一次传输操作可能会导致不可接受的延迟,我们把它称为延时(latency)。为了解决这个问题,ALSA将缓存区拆分成一系列周期 (period).

ALSA以period为单元来传送数据。一个周期(period) 存储一些帧(frames)。每一帧包含时间上一个点所抓取的样本。对于立体声设备,一个帧会包含两个信道上的样本。当左右信道信息被交替地存储在一个帧内,这称为交错 (interleaved)模式。在非交错模式中,一个信道的所有样本数据存储在另外一个信道的数据之后。

period(周期): 硬件中中断间的间隔时间,它表示输入延时。 声卡接口中有一个指针来指示声卡硬件缓存区中当前的读写位置。只要接口在运行,这个指针将循环地指向缓存区中的某个位置。

frame size = sizeof(one sample) * nChannels

alsa中配置的缓存(buffer)和周期(size)大小在runtime中是以帧(frames)形式存储的。

period_bytes = frames_to_bytes(runtime, runtime->period_size);

bytes_to_frames() :The period and buffer sizes are not dependent on the sample format because they are measured in frames; you do not need to change them.

3、Over and Under Run
当一个声卡活动时,数据总是连续地在硬件缓存区和应用程序缓存区间传输。但是也有例外。在录音例子中, 如果应用程序读取数据不够快,循环缓存区将会被新的数据覆盖,这种数据的丢失被称为overrun.

在播放例子中,如果应用程序写入数据到缓存区中的速度不够快,缓存区将会"饿死",这样的错误被称为underrun。 在ALSA文档中,有时将这两种情形统称为"XRUN"。适当地设计应用程序可以最小化XRUN并且可以从中恢复过来。

4、参数设置

  1. PCM设备的句柄;2) 指定同时可供回放或截获的PCM流的方向;3) 提供一些关于我们想要使用的设置选项的信息,比如缓冲区大小,采样率,PCM数据格式等;4) 检查硬件是否支持设置选项.

    4.1) 初始化PCM变量

    4.2) 分配hwparams结构

    4.3) 打开PCM设备

    4.4) 以声卡的全部设置选项空间来初始化hwparams结构

    4.5) 指定访问类型,采样格式,采样率,声道号码,周期数目以及周期大小

     a) 访问类型 : 指定了哪一个多声道数据储存在缓冲区的方法.  对于交错访问,缓冲区里的每一个帧为声道容纳连续的采样数据.  对于非交错访问,每一个周期为第一个声道容纳所有采样数据接着是第二个声道的采样数据       
    
     b) 缓冲区尺寸的单元依赖于函数.一些时候是字节,一些时候是必须指定的帧的数目.   一个帧是对所有声道的采样数据数组.对于16位立体声数据,一个帧的长度是4个字节. 如果你的硬件不支持2的N次方的缓冲区大小,你可以使用 snd_pcm_hw_params_set_buffer_size_near函数.这个函数工作起来与snd_pcm_hw_params_set_rate_near相似.
    
  2. 为PCM设备申请由pcm_handle指向的设置选项

ALSA
1、 alsa展现的三层结构:
(1)application:这个就是你写的程序,你开辟一个buffer,比如playback,就交给alsa来play。

(2)computer:指的是计算机的内核和驱动(驱动由alsa提供),当audio interfacce引发中断,内核会捕捉到,再把处理移交alsa。

(3)audio interface:就是声卡,它含有hardware buffer,注意,这个hardware buffer是在声卡里面,不是内存。

在上面的框架下,流程如下: (1)playback:application开辟一个buffer,填上数据,调用alsa接口,alsa把buffer数据复制到 其驱动的空间,再把数据交给hardware buffer。 (2)record:同playback,相似的。

2、 细节:
按照上面的流程,其中有许多细节我们可以加以控制,这里仅仅指出应用程序需要关心的:

2.1 操作的设备:
在alsa驱动这一层,目前为止,抽象出了4层设备,一是如hw:0,0,二是plughw:0,0,三是default:0, 四是default。至于一是清楚了,二和二以上可以做数据转换,以支持一个动态的范围,比如你要播放7000hz 的东西,那么就可以用二和二以上的。而你用7000hz作为参数,去设置一,就会报错。三和四,支持软件混音。 我觉得default:0表示对第一个声卡软件混音,default表示对整个系统软件混音。

这里提出两点:(1)一般为了让所有的程序都可以发音,为使用更多的默认策略,我们选用三和四,这样少一些控制权,多一些方便。 (2)对不同的层次的设备,相同的函数,结果可能是不一样的。 比如,设置Hardware Parameters里的period和buffer size,这个是对硬件的设置,所以,default和 default:0这两种设备是不能设置的。 比如,如果直接操作hw:0,0,那么snd_pcm_write只能写如8的倍数的frame,比如16,24,否则就会剩下 一点不写入而退回,而default,就可以想写多少就写多少,我们也不必要关心里面具体的策略。

2.2 Hardware Parameters
之所以叫做Hardware Parameters,是因为alsa这一层api是较为底层的,它允许用户对上面提到的三层结构的audio interface和computer两层都做设置。其中对computer,也就是alsa驱动这一层的设置, 叫做Software Parameters,而对audio interface(声卡)的设置叫做Hardware Parameters。 当然,要设置hardware parameters,也肯定是通过alsa驱动来完成,只不过哪些参数是指导硬件的, 哪些是指导alsa驱动的,分开设置了。(

(1)Sample rate: 不用说了(这些,对于default设备也能设的,上面已经说了)

(2)Sample format: 不用说了

(3)Number of channels: 不用说

(4)Data access and layout:简单点,就是说,在一个period以内,数据是按照channel1排完了再排channel2呢,还是一个frame一个frame的来排(frame在alsa里指的是一次采样时间内,两个channel的数据放一块儿就是一个frame)。默认是第二种。

(5)Interrupt interval:中断间隔,就是靠periods决定的,有函数来设置periods,也就是说这个 hardware buffer在一次遍历之内,要中断多少次,来通知内核(最终是到alsa驱动)来写入或读走数据。比如buffer是8192个frame大,而period设为4个frame大,那么比如playback,则每当有4个frame大的hardware buffer空间空出,就会中断,通知内核(alsa驱动)来写如数据。这个是影响实时效果的关键!!!但是,我观察的,我的电脑的默认period就是4个frame,按16字节,双通道来算 的话,也就是16个字节!所以,默认就很实时了!!一般的实时程序已经够用了!!一般不用调整。

(6)Buffer size:就是hardware buffer的大小,如果alsa整套体系主要靠这个来做缓冲,那么这个的大小,将影响缓冲效果,但是一般也不调整。

2.3 Software Parameters:
(1)snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096) 这个仅用在interrupt-driven模式。这个模式是alsa驱动层的,不是硬件的interrupt。它的意思是, 用户使用snd_pcm_wait()时,这个实际封装的是系统的poll调用,表示用户在等待,那么在等待什么呢? 对于playback来讲,就是等待下面的声卡的hardware buffer里有一定数量的空间,可以放入新的数据 了,对于record来讲,就是等待下面声卡新采集的数据达到了一定数量了。这个一定数量,就是用 snd_pcm_sw_params_set_avail_min来设置。单位是frame。实际运作,没读驱动代码,不是很清楚, 可能是alsa驱动根据用户设的这个参数,来设置Hardware Parameters里面的period,也可能是不改 变硬件的period,每次硬件中断还是copy到自己的空间,然后数据积累到一定数量再interrupt应用程 序,使之从wait()出来。我不知道,也不必深究。 这种模式的使用,需要用户在snd_pcm_wait()出来以后,调用一个平常的wirtei或readi函数,来写入 或读取那个“一定数量”的数据。如果用户不用interrupt-driven模式,那么这个函数不必使用。

(2)snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U) 这个函数指导什么时候开启audio interface的AD/DA,就是什么时候启动声卡。 对于playback,假设第三个参数设为320,那么就是说,当用户调用writei,写入的数据,将暂时存在alsa 驱动空间里,当这个数据量达到320帧时,alsa驱动才开始将数据写入hardware buffer,并启动DA转换。 对于record,当用户调用readi,这个数据量达到320帧时,alsa驱动才开始启动AD转换,捕捉数据。我一般 把它设为0,我没试过非0,如果是非0, 我想第一次的writei和readi一定得够数量才行,否则设备不启动。 这个对实时效果是需要的,将第三个参数设置为0,保证声卡的立即启动。

(3)what to do about xruns: xrun指的是,声卡period一过,引发一个中断,告诉alsa驱动,要填入数据,或读走数据,但是,问题在于, alsa的读取和写入操作,好象是必须用户调用writei和readi才会发生的,它不会去缓存数据!!!,所以如 果上层没有用户调用writei和readi,那么就会产生overrun(录制时,数据都满了,还没被alsa驱动读走) 和underrun(需要数据来播放,alsa驱动却不写入数据),统称为xrun。 我对它的理解是,不是一个period 引发的中断就叫做xrun,而是当整个hardware buffer都被写满了(record时)或空了(play时),这个时 候的中断下的情况才指的是xrun。这个东西,需要用一些函数来设置,比如snd_pcm_sw_params_set_silence_threshold(),是针对 playback的,就是设置当xxx的情况下,就用silence来写入hardware buffer。

猜你喜欢

转载自blog.csdn.net/qq_38350702/article/details/111996436
今日推荐