一. alsa展现的三层结构:
(1)audio interface:
audio interface就是声卡,它含有hardware buffer,注意,这个hardware buffer是在声卡里面,不是内存。
(2)computer:
这个指的是计算机的内核和驱动(驱动由alsa提供),当(1)的audio interfacce引发中断,内核会捕捉到,再把处理移交alsa。
(3)application:
这个就是你写的程序,你开辟一个buffer,比如playback,就交给alsa来play。
在上面的框架下,流程如下:
(1)playback:
application开辟一个buffer,填上数据,调用alsa接口,alsa把buffer数据复制到其驱动的空间,再把数据交给 hardware buffer。
(2)record:
同playback,相似的。
二. 细节:
按照上面的流程,其中有许多细节我们可以加以控制,这里仅仅指出应用程序需要关心的:
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_writei只能写如8的倍数的frame,比如16,24,否则就会剩下一点不写入而退回,而 default,就可以想写多少就写多少,我们也不必要关心里面具体的策略。
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整套体系主要靠这个来做缓冲,那么这个的大小,将影响缓冲效果,但是一般也不调整。
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。至于xxx情况,以及写入多少silence,我都不是很清楚,还有,比如xrun到什么情况下,可以停止这个设备等等函数。这个(3)的 涉及的参数,我都没试过,一般情况下,就用alsa驱动的默认的xrun处理策略吧,等以后出了错误再说,而且例子里也没有提到。
但是,关于xrun,在编程时,最好这样写:
while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
snd_pcm_prepare(pcm_handle);
fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>\n");
}
就是说,如果这次读/写距离上次读/写,时间可能过长,那么这次去读/写的时候,device已经xrun了,在不知道alsa驱动对xrun的默认策略 的情况下,最好调用snd_pcm_prepare()来重新准备好设备,然后再开始下一次读写。我想,prepare()意思,可能相当于复位,不很清 楚。我想,windows下的那套API下的驱动,一定是已经有了一套对xrun的处理策略,用户根本没有接口可以调整它,我想它的策略比如 playback遇到underrun,也就是填入silence罢了。
(4)transfer chunk size:
这个应该是用不上的,我没找到文档里有用这个的。
三. 编码模式:
TODO LIST:以后把下面两点补上:
(1)一般读写模式:
(2)interrupt-driven模式:作者推荐这个模式,我决定用这个模式来做吧。
我推测的原因:它非常清晰的告诉了用户,你需要读取数据或写入数据了! 这样允许用户即时的作出操作:比如现在让用户从wait()出来,用户知道需要比如写入数据了,它可以决定写入真实的数据,或者写如silence,或者 其他。而用一般读写模式,你不会即时知道下层的需求!!所以,相当于你只能在你下一次读/写的时候,判断有没有xrun,其他你什么都做不了:) (并且在实时性要求不高的情况下,设置一个较大的interrupt间隔,真个alsa的效率会高一点呵呵。而且这种interrupt模式,可以使得代 码简单改下就可以用到其他采用interrupt的系统上,作者这米说的)
TODO: 再好好想想这个模式,其实我不确定实际使用上有什么好?
四. 和windows API的比较:
windows audio接口的那些需要加入的buffer,属于应用程序的buffer,windows靠用户添加buffer queue来设置缓冲,而alsa并不提供接口让用户设置应用层的缓冲区,缓冲区的作用,就是减弱或消除数据流或用户操作偶尔过快或过慢造成的影响,所以 alsa是一定要有缓冲区的,我不知道是否,hardware buffer独自承担着alsa里面的buffer角色?
alsa的writei和readi好象可以改成
TODO: 根据实际的编程结果,从效果看看,这样做是否效果也很好。
五. 其他:
对声卡的编程就是对两个设备进行指导的过程, 下面分3点叙述:
1. 关于Mixer编程.
我了解到的声卡,有三个主要部分:
(1)mixer
(2)dsp(ad,da)
(3)buffer
可以认为它们按上面(1)(2)(3)的顺序联接起来, 而buffer和计算机总线间接相连.
目前认为的原理是这样的:
(1)图1表现了一个mixer, 图2是很多个mixer, 但实际上, 声卡里至少有两个mixer, 一个叫做input mixer, 一个叫做output mixer. input mixer接收外来模拟信号, output mixer接收dsp给它的模拟信号. 需要知道的是, mixer的输入和输出都是模拟信号, 输入和输出的线路也叫做混音通道, 我觉得这是物理线路的范畴, 这也就是为什么一个程序调整mixer, 会影响另一个程序的原因, 因为我觉得它调整的是物理参数. 图中蓝色的方框就是可调节增益的地方(总之, 我认为它们是可供软件调节的硬件线路参数)
(2)一个mixer的输出可以作为另一个mixer的输入. 比如考虑这样一种情景, 怎样混合CD和自己的歌声? 我估计就是CD的数字信号, 先到buffer, 经dsp的D/A转换, 输入"输出混音器"的一个混音通道(source line), 再从destination line出来后, 不送往speaker, 而是送往"输入混音器"混音通道, 这样和人声(从"输入混音器"的microphone混音通道进入)一起混音, 再从destination line出来, 送往dsp做A/D转换, 最后送往buffer.
而调节各个source line/destination line的增益, 以及route souce line和destination line就是mixer做的, 也是软件可以调节的.
至于系统mixer面板上的master和pcm的区别, 我想pcm是dsp输出的模拟信号, 也就是outmixer的source line的增益吧, 而master也许是outmixer的destination line的增益吧.这个其实找不到具体的声卡结构图是很难搞清楚的.
2. 关于dsp编程.
这个最好理解为是上述mixer体系中的一条默认的流程.对于用户来讲,dsp设备就是完成基本的播放和录音功能.
而用户当然应该指导dsp完成工作, 就象指导mxier一样. 用户给dsp的指导参数主要是format(frame/s和bits/frame)和channel数, 要不然dsp怎样能做D/A或A/D转换呢.
3. 关于buffer.
涉及到的buffer有三个, 一是声卡的buffer, 二是驱动的kernel buffer, 三是用户指定的user buffer. 数据就是这样一个接一个复制上来的,或一个一个复制下去的, 复制的信号是声卡的interrupt信号. 当buffer满了(读)或buffer空了(写)时, 就会引发中断.
来源:CSDN
作者:小田
链接:https://blog.csdn.net/tianshuai11/article/details/8652699