ALSA中PCM的使用

一、预备知识

1、声音分分类 
  (0)极低频: 20-40Hz
  (1)低频: 40-80Hz  
  (2)中低频: 80-160Hz
  (3)中频: 160Hz-1280Hz这个频段之间横跨的幅度是最宽的,几乎把所有的 乐器
          及人声都包含进去了,所以是最重要的频段
  (4)中高频: 1280-2560Hz
(5)高频: 2560-5120Hz
(6)极高频: 5120Hz-20000Hz
   人耳理论上的可听域为20-20KHz, 在家用优质前置放大器中,低音调节点一般设置在80Hz,
   中音调节点一般设置在1Kz,高音调节点一般有3种设置法:8KHz、10KHz、12KHz

2、2.1声道2.0声道的区别
  一般的2.0的音箱分为两个分频,高频和中低频,如果你打开音箱可以看到两个扬声器(俗称喇叭),
  上面小的就是输出高频信号,下面大的扬声器为中低频。2.1音箱也是二分频的,小音箱是高频为
  主,低音炮为低音为主,所以大家就可以比较出来了,2.1的音箱中音是远不如2.0的,因为它没有
  单独的中音单元(负责中频的扬声器),但是2.1的好处是,它的低音是单独的一个大直径的扬声器,
  所以它的下潜较深,也导致2.1音响比较容易出较果。


二、PCM

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

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

应用程序缓存区的大小可以通过ALSA库函数调用来控制。缓存区可以很大,一次传输操作可能会导致不
可接受的延迟,我们把它称为延时(latency)。为了解决这个问题,ALSA将缓存区拆分成一系列周期
(period)(OSS/Free中叫片断fragments).ALSA以period为单元来传送数据。一个周期(period)
存储一些帧(frames)。每一帧包含时间上一个点所抓取的样本。对于立体声设备,一个帧会包含两个信
道上的样本。


图1展示了分解过程:一个缓存区分解成周期,然后是帧,然后是样本。图中包含一些假定的数值。图中左右
信道信息被交替地存储在一个帧内,这称为交错 (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相似.
5) 为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。至于xxx情况,以及写入
多少silence,我都不是很清楚,还有,比如xrun到什么情况下,可以停止这个设备等等函数。这个(3)的涉及
的参数,我都没试过,一般情况下,就用alsa驱动的默认的xrun处理策略吧,等以后出了错误再说,而且例子里
也没有提到。
但是,关于xrun,在编程时,最好这样写:
<span style="font-size:18px;"><span style="font-size:24px;">  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罢了。


2.4 对声卡的编程就是对两个设备进行指导的过程, 下面分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空了(写)时, 就会引发中断.</span></span>






  
"alsa lib pcm.c:8512"是一个错误信息,它是ALSA(Advanced Linux Sound Architecture)库的一个错误代码。 ALSA是用于在Linux操作系统上处理音频和声音的软件框架。pcm.c是该库PCM(Pulse-code Modulation)音频数据处理相关的文件。当出现"alsa lib pcm.c:8512"错误时,表示在执行PCM音频数据读取或写入操作时发生了错误,具体是在pcm.c文件的第8512行。 要解决这个错误,可以考虑以下几个步骤: 1. 检查设备配置:确保使用正确的音频设备进行读取或写入操作。可以使用命令"aplay -l"和"arecord -l"来列出系统可用的音频设备。 2. 检查权限:确保当前用户有足够的权限来访问音频设备。可以使用命令"ls -l /dev/snd/"来查看设备文件的权限。 3. 检查设置参数:检查PCM音频处理的设置参数是否正确。可以参考ALSA文档和相应的程序代码来确认参数设置的正确性。 4. 检查驱动和库版本:确保ALSA库和音频驱动程序的版本是兼容的。更新驱动程序或升级ALSA库可能有助于解决一些已知的问题。 5. 查找其他错误信息:在"alsa lib pcm.c:8512"错误之前或之后的错误信息,可能会提供更多有关问题原因的线索。对这些错误信息进行分析,可能会帮助定位和解决问题。 6. 检查其他系统资源:确保系统上的其他资源,如内存、磁盘空间等,充足且没有其他问题。 如果以上步骤都无法解决问题,建议查看ALSA论坛或咨询相关的技术支持团队,寻求更详细的帮助和指导。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值