ALSA Audio API 使用指南(译文)

时间:2022-01-26 16:00:20
罗索实验室:http://www.rosoo.net/a/201010/10359.html   此文档旨在提供一个对ALSA Audio API的介绍。它并非是一个API的完全参考手册,它也没有涉及许多特定的方面,很多复杂的软件会涉及那些特定的方面。然而它试着给一位合理的熟练的程序员提供足够多的相关知识和信息,而并非给那些使用API来编写简单程序的不熟悉ALSA的新手 TAG: ALSA  Linux音频  

         工作需要,最近研究ALSA,写了个简单的音频接口并移植到板子上了,使用API的过程中发现一些知识对理解还是十分重要的,于是把API使用指南给粗略的翻译了一遍,e文水平有限,希望有人可以指出我的错误,哈哈,第一次翻译技术文档.原文:http://equalarea.com/paul/alsa-audio.html#interruptex

⊙﹏⊙b汗!原来还有字数限制,把代码删掉了,看原文。

ALSA Audio API 使用指南此文档旨在提供一个对ALSA Audio API的介绍。它并非是一个API的完全参考手册,它也没有涉及许多特定的方面,很多复杂的软件会涉及那些特定的方面。然而它试着给一位合理的熟练的程序员提供足够多的相关知识和信息,而并非给那些使用API来编写简单程序的不熟悉ALSA的新手。文档中所有的代码都遵循GNU Public License。如果你试图在其它的准则下使用ALSA来编写程序,那么我将建议你寻找其他的文档。内容介绍         。理解音频接口         。一个典型的音频应用程序         。一个最简单的回放程序         。一个最简单的捕获数据程序         。一个最简单的驱动中断程序         。一个最简单的全双工程序         。如何使用API                   。打开设备                   。设置参数                   。接收和传输数据         。为何你会要去忘记这里所讲述的一、理解音频接口我们先来了解下音频接口的基本设计。对于一位应用程序开发者,你不需要担心硬件水平如何操作,它们全部由设备驱动来搞定(这些驱动是由ALSA来提供的一些组件)。但是,如果你想写出高效和简洁的软件你必须要对它们有个概念上的了解。

音频接口是一种设备,该设备可以让电脑 对外接收和向外发送音频数据。在电脑那一端,音频数据用一串比特流来表示,就像其它种类的数据一样。虽然如此,音频接口发送和接收音频数据即可以用模拟信 号(时刻变化的电压)也可以用数字信号(一些比特流)。无论用什么来接收发送数据,电脑用来表示一段音频的比特流在往外传输前都要被转化为模拟信号;同样 的,被声卡接收的外部信号在被电脑使用前也需要被转化为数字信号。这两方面的转化也是音频接口的用途所在。【注:raison d'etre为法语,意为:存在的目的或理由】

声卡内部有一个硬件缓存区域。当声卡接 收到外界声音信号时,通过计算机它将信号转化为可用的比特流并存储在用来发送数据给计算机的硬件缓存中。当缓存存储了足够多的数据时,声卡中断计算机告知 它数据已经准备好了。一个相似的过程将会发生在将数据从计算机传送到外界时。此过程中声卡中断计算机并通知它缓存有空间,计算机将会往里面存储数据。声卡 接着将那些数据转化成所要求的任何格式并将其传送到外界,并且传输。有一点是十分重要的,那就是声卡循环使用这块缓存。也就是说,当到了缓存的末端时,会 将开始的数据擦除用来存数据。

为了保证这个过程的正确执行,有一些变量需要设置。它们包括:。当在计算机使用的比特流和外界使用的模拟信号之间转化时声卡要用什么样的格式?。声卡的采样率是多少?。当有多少数据时声卡才中断计算机?。硬件缓存要设置为多大?头两个问题是控制音频数据质量的基础。后两个问题影响着音频信号的延迟。这个术语涉及到以下两个方面的延迟1.       数据从外界到达声卡与转化成计算机中可用的数据之间的延迟(称为:内部延迟)2.       数据被计算机传输,并传输到外界的延迟(称为:外部延迟)虽然一些程序不需要关心这两方面的东西,但对很多音频相关软件它们都十分重要。二、一个典型的音频应用程序下面是一个典型的音频应用程序的大致结构:
      
      
  1. open_the_device(); 
  2. set_the_parameters_of_the_device(); 
  3. while (!done) { 
  4.      /* one or both of these */ 
  5.      receive_audio_data_from_the_device(); 
  6.      deliver_audio_data_to_the_device(); 
  7. close the device 

        

三、个最简单的回放程序下面这段程序开启声卡回放,设置成双声道,16bit(采样位数),44.1KHZ,交叉存储,常规的读写(不用mmap)。然后传送一些随即的数据给声卡,退出。它也许反映了最简单的ALSA Audio API的用法,但它并不是一个真正可运行的程序。四、一个最简单的捕获数据程序这段程序打开声卡捕获数据,设置为双声道,16bit,44.1KHZ,交叉存储,常规的读写。然后读一些数据,退出。它并非实际运行程序。
五、一个最简单的驱动中断程序这段程序打开声卡回放,配置成双声道,16bit,44.1KHZ,交叉存储,常规读写。它将等待声卡直到声卡准备好回放数据,同时传输一些数据给它。这种设计可以使你的程序很容易的通过一种回调驱动机制来和系统绑定,这样的软件比如:JACK,LADSPA,CoreAudio,VST等 六、一个最简单的全双工程序

全 双工可以通过上面提到的回放和捕获设计来合并实现。虽然很多现有的音频程序使用了这种设计,但这篇文章中认为这是有很大缺陷的。上面的中断驱动样例在多数 情况下是一个很基本也很好的设计模式。然而,它在扩展成全双工时也是十分复杂的。这正是我为何要建议你忘记我前面所说的。

 
名词术语:Capture         从外界获取音频数据(和“录音”不同,录音意味着将数据存储起来,它并不是ALSA库的API)。Playback         音频播放出来,使能被听见,这也许并不是必须的。Duplex         捕获和回放在同一声卡同一时刻同时发生的情况Xrun一旦声卡开始运行,便一直持续直到被要求停止。它将收集数据给计算机使用并且(或者)发送计算机中的数据给外界。但有很多种情况你的程序并非可以控制声卡正常的进行。在回放的时候,当声卡需要从计算机中收到数据时,而此时并没有,xrun便可以强迫它使用在硬件缓存中留有的旧数据。这种情况叫做“下溢”。在捕获的时候,声卡也许要传送数据给计算机,但此时计算机没有空间来存储,因此,声卡会重写部分硬件缓存中没有及时送出的数据。这种情况叫做“上溢”。简单的说,我们用词“xrun”来概括其中的一个或几个。Pcm         Pulse Code Modulation(脉冲编码调制)。这个词(还有缩写)描述了一种用数字化形式表示模拟信号的方法。这种方法几乎被所有的计算机音卡所使用,它在ALSA API中用“audio”来简称。ChannelFrame         一个采样是描述某一时刻的单通道下一个信号点的声音信号的振幅的信号值。当我们谈及数字音频时,我们经常谈到的是代表所有通道的一个信号点的数据。它是单个通道采样的一个集合,叫做"帧"。当我们依据帧来表示一段时间时,它粗略的等于人们用一组采样来衡量,但前者更精确(???);更重要的是,当我们在讨论代表一个时刻所有通道的数据量时,帧是唯一能引起人们感官感受的标准。几乎所有的ALSA Audio API使用帧作为衡量音频质量的标准。Interleaved         一种将同一时刻所有通道音频数据依次存取的交叉存储形式。参看:不交叉存储。Non-interleaved         一种将每个通道的数据按顺序存储的存储形式,不同的通道的数据存储在另一个缓存中或者同一个缓存的其他地方。和交叉存储对比。Sample clock

         时 钟源,用来决定采样的传送,接收时刻。一些音卡允许你使用外部时钟,比如“全球时钟信号”(经常在一些工作室中使用),还有使用来自数字数据的时钟信号的 “自动同步信号”。所有的声卡内部都必须至少有一个采样时钟源,典型的是一种小的晶体时钟。一些声卡不允许改变时钟频率,一些则有不是很精准的时钟(比如,44.1khz)(???)。没有两个时钟能够以同一个时钟频率运行,如果你需要两个采样时钟进行同步,他们必须要和同一个采样时钟进行同步。(?晕了)

七、如何使用API1.打开设备         ALSA将捕获和回放的用API分开。。。(估计是说用同一个api来实现了吧,没写了)2.设置参数         上面提到了要使声卡工作需要设置很多的参数。然而,由于你的程序将不会实际的直接的去和硬件打交道,所以我们使用了两个不同的设置参数的方式来替带用设备驱动来控制硬件:硬件参数下面的参数可以直接影响声卡的硬件采样率         如果声卡有模拟i/o的情况下,当声卡准备好了A/D或D/A转换时,这个可以控制声卡的速率。对于全数字的声卡,它控制用于移动数字数据进出外部的时钟的速度。在一些声卡中,其它的硬件指定设置也许意味着你的程序不能控制这个值(比如,当声卡用一个外部时钟源来决定采样率时)。 采样格式         这个控制了用于从声卡传输数据的采样格式。它也许和硬件直接支持的格式不符。 通道数         这个完全可以自定义。 数据接入和布局         数据接入控制了程序从声卡接收传送数据的方式。有两个参数来设置4种情况。一个参数是是否使用“读写”模块,该模块中有相应的用于传输数据的函数调用。另一个选项是用“映射模式”,该模式通过拷贝内存区域的数据来传输数据,这个API仅仅需要在开始和结束的时候注明一下即可。         数据布局是指数据的存储是交叉存储还是非交叉存储。 中断间隔         这个决定了读或写完整个硬件缓存需要发生多少次中断。它能根据一些指定的时间段来设置,也可以根据一段时间的长短来设置。设置决定了在声卡向计算机发出中断之前积累的空间中或数据中的帧数。它控制着延迟。 缓存大小         这决定了硬件缓存的大小。它可以用时间长短或帧数来指定。 软件参数这些参数控制着硬件驱动的操作而不是硬件本身。很多使用ALSA Audio API的程序什么参数都不需要设置,一些只需要设置其中的一部分。 什么时候打开设备         当你打开声卡设备的时候,ALSA确认它并没有在被使用——没有数据正在传输。假如,此时,你想要开始数据传输,有几个选项需要设置。         此处的控制关键是开端,开端定义了自动开启设备所需要达到的空间/数据包含的帧数。如果为回放设置了一个非0值,那在设备开始运作之前就要先填充硬件缓存。如果设置成0,那么最开始写入数据给设备(或者试图开始捕获流数据)将会开启设备。你可以用snd_pcm_start来明确的打开设备,但如果是回放的话要求缓存中预先填充数据。如果你没有预填数据而想开始回放,函数则会返回-EPIPE,表明现在没有数据在向硬件缓存中传。 Xruns有什么作用         如果xrun发生了,如果被请求了,设备将通过几个步骤来处理它。选项包括停止设备,用于回放的硬件缓存的全部或部分静音。                  停止极限                   如果可用的数据/空间的帧数达到或超过了这个值,设备将停止声卡。         静音极限如果用于回放流的可用的空间帧数达到或超过了这个值,设备将往硬件缓存中填充静音数据。静音大小         当静音极限到达时,这个值决定了往硬件缓存中写入多少静音数据。 唤醒所需的最小可用空间/数据。         使用poll(2)或者select(2)来控制音频数据的传输的程序可以设置这个值来控制在什么时候唤醒程序,这些时候与硬件缓存的状态相关。 传输块大小         这个决定了传入/出数据给硬件缓存时所用的帧数。 还有一些其他的软件参数此处没有提及。 接收和传输数据没有写 八、为何你会要去忘记这里所讲述的。。。 

使用ALSA编写自己的音频程序

Alsa是Linux高级音频接口。面对众多的音频设备,Alsa为Linux音频开发人员提供了一套标准的访问机制,使得音频开发变得十分容易。不信?下面我们就利用它编写一个简单的录音/播音程序,不过这需要你有一定的计算机语言基础。TAG: ALSA  Linux音频  

作者:北京中科红旗软件技术有限公司 孔伟 

Alsa是Linux高级音频接口。面对众多的音频设备,Alsa为Linux音频开发人员提供了一套标准的访问机制,使得音频开发变得十分容易。不信?下面我们就利用它编写一个简单的录音/播音程序,不过这需要你有一定的计算机语言基础。

一个典型的音频程序应该具有以下结构:

打开音频设备

为设备设置读写参数

向音频设备读/写音频数据

关闭设备

Alsa库为我们实现这些操作提供了丰富的接口。

首先让我们封装一个打开音频设备的函数:

             
             
  1. snd_pcm_t *pcm_handle; 
  2.  
  3. bool device_open(int mode){ 
  4. if (snd_pcm_open (&pcm_handle, “default” , mode , 0) < 0) 
  5. return false
  6. return true

snd_pcm_open是Alsa库提供的打开设备调用函数,这里我们指定打开缺省的音频设备,并根据参数mode将设备置为录音或是播放状态,如果设备打开成功,pcm_handle便指向该设备句柄,我们用全局变量保存起来,方便以后使用。

第二步是设置参数,参数设置不当将会导致音频设备无法正常工作。在设置参数前,我们需要了解一下各个参数的含义以及一些基本概念。

样本长度(sample):样本是记录音频数据最基本的单位,常见的有8位和16位。

通道数(channel):该参数为1表示单声道,2则是立体声。

桢(frame):桢记录了一个声音单元,其长度为样本长度与通道数的乘积。

采样率(rate):每秒钟采样次数,该次数是针对桢而言。

周期(period):音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。

交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。不过多数情况下,我们只需要使用交错模式就可以了。

明白了各参数含义及关系后,我们开始设置参数:

             
             
  1. int bit_per_sample; //样本长度(bit) 
  2. int period_size; //周期长度(桢数) 
  3. int chunk_byte; //周期长度(字节数) 
  4. snd_pcm_hw_params_t *params; //定义参数变量 
  5.  
  6. bool device_setparams() 
  7. snd_pcm_hw_params_t *hw_params; 
  8. snd_pcm_hw_params_malloc (&hw_params); //为参数变量分配空间 
  9. snd_pcm_hw_params_malloc (&params); 
  10. snd_pcm_hw_params_any ( pcm_handle, hw_params ); //参数初始化 
  11. snd_pcm_hw_params_set_access ( pcm_handle, hw_params,
  12.  SND_PCM_ACCESS_RW_INTERLEAVED); //设置为交错模式 
  13. snd_pcm_hw_params_set_format( pcm_handle, hw_params,
  14.  SND_FORMAT_S16_LE); //使用用16位样本 
  15. snd_pcm_hw_params_set_rate_near( pcm_handle, hw_params,
  16.  44100, 0); //设置采样率为44.1KHz 
  17. snd_pcm_hw_params_set_channels( pcm_handle, hw_params, 2); //设置为立体声 
  18. snd_pcm_hw_params_get_period_size( hw_params, &period_size); //获取周期长度 
  19. bit_per_sample = snd_pcm_hw_format_physical_width( hw_params.format );
  20.  //获取样本长度 
  21. chunk_byte = period_size * bit_per_sample * hw_params.channels / 8;
  22. //计算周期长度(字节数(bytes) = 每周期的桢数 * 样本长度(bit) * 通道数 / 8 ) 
  23. snd_pcm_hw_params( pcm_handle, hw_params); //设置参数 
  24. params = hw_params; //保存参数,方便以后使用 
  25. snd_pcm_hw_params_free( hw_params); //释放参数变量空间 
  26. return true
  27.  

这里先使用了Alsa提供的一系列snd_pcm_hw_params_set_函数为参数变量赋值。最后才通过snd_pcm_hw_params将参数传递给设备。需要说明的是正式的开发中需要处理参数设置失败的情况,这里仅做为示例程序而未作考虑。

设置好参数后便可以开始录音了。录音过程实际上就是从音频设备中读取数据信息并保存。

             
             
  1. char *wave_buf; 
  2. int wave_buf_len; 
  3. bool device_capture( int dtime /*录音长度(单位:秒)*/){ 
  4.  wave_buf_len = dtime * params.rate * bit_per_sample * params.channels / 8 ;
  5.  //计算音频数据长度(秒数 * 采样率 * 桢长度) 
  6.  char *data = wave_buf = (char*)malloc( wave_buf_len ); //分配空间 
  7.  
  8. int r = 0; 
  9. while ( data ?C wave_buf <= wave_buf_len ?C chunk_size ){ 
  10. r = snd_pcm_readi( pcm_handle, data , chunk_size); 
  11. if ( r>0 ) data += r * chunk_byte; 
  12. else 
  13. return false 
  14. return true

形参dtime用来确定录音时间,根据录音时间分配数据空间,再调用snd_pcm_readi从音频设备读取音频数据,存放到wave_buf中。

同样的原理,我们再添加一个播放函数,向音频设备写入数据:

             
             
  1. bool device_play(){ 
  2. char *data = wave_buf; 
  3. int r = 0; 
  4. while ( data ?C wave_buf <= wave_buf_len ?C chunk_size ){ 
  5. r = snd_pcm_writei( pcm_handle, data , chunk_size); 
  6. if ( r>0 ) data += r * chunk_byte; 
  7. else 
  8. return false 
  9. return true

最后我们给这个示例程序加上main函数

             
             
  1. #include <alsa/asoundlib.h> 
  2.  
  3. bool device_open( int mode); 
  4. bool device_setparams(); 
  5. bool device_capture( int dtime ); 
  6. bool device_play(); 
  7. char *wave_buf; 
  8. int wave_buf_len; 
  9. int bit_per_sample; 
  10. int period_size; 
  11. int chunk_byte; 
  12. int chunk_size; 
  13. snd_pcm_hw_params_t *params; 
  14.  
  15. int main( int , char** ){ 
  16. //录音 
  17. if (!device_open(SND_PCM_STREAM_CAPTURE ) return 1; 
  18. if (!device_setparams()) return 2; 
  19. if (!device_capture( 3 )) return 3; //录制3秒 
  20. snd_pcm_close( pcm_handle ); 
  21.  
  22. //播放 
  23. if (!device_open( SND_PCM_STREAM_PLAYBACK ) return 4; 
  24. if (!device_setparams()) return 5; 
  25. if (!device_play()) return 6; 
  26. snd_pcm_close( pcm_handle ); 
  27.  
  28. return 0; 

这样,我们便完成了一个具有录音,播音的功能的音频程序,因为使用了alsa库,如果你使用的是gcc编译器,最后链接时记得要带上参数——lasound 。

限于篇幅,Alsa接口提供的强大功能不仅于此,有兴趣的读者可以参阅ALSA HOWTO,那上面你一定能够发现Alsa的强大之处。

使用ALSA架构的驱动程序,在实际开发使用过程中,比较常见的错误有-EPIPE,也就是-32?为什么会出现呢?肯定是系统内部不和谐了!EPIPE的错误在播放时出现就是因为驱动buffer没有数据可以丢给codec所致,通俗一点就是上层给下面喂数据的速度慢了,下面饿晕了,所以抱怨你上层慢啊,给你一个-EPIPE错误出来,自己去找原因。在录音的时候,出现EPIPE也是有原因的,ALSA的驱动也有一块专门用来存储录音数据的buffer,上层从该buffer搬运数据再存储起来就能得到我们需要的录音文件。一旦驱动的buffer满了,就会出现EPIPE的错误,因为你上层读录音buffer数据的速度慢了,这就不能抱怨下层不给面子了。通过分析出现原因后,我们得找找对策,说起来容易,做的可能因为系统的原因并不是想的那么容易。

 

    在播放的时候,如果会出现这种-EPIPE的错误,请调整下发数据的数据,加快一点点!录音的时候出现这种错误的时候,请读得更快一点!可以提高任务的优先级来处理,也可以把驱动buffer扩大一下,给系统更多一点的缓冲时间!都退一步,和谐社会,和谐系统!