站长手记 - 站长手记
打印】【收藏此页
Linux 音频 API 指南
作者:本站 苏信东翻译  来源:WaveCN.com 世纪音频  发布日期:2008-11-10  最后修改日期:2008-11-12  

Linux Sound API由于历史的原因,在 Linux 环境下有多种API系统可用于声音编程。但没有合适的指引就难以找到合乎自己需要的体系。这里是一个指引,是由Lennart Poettering <mzoybt (at) 0pointer (dot) net>所写(参见:Homepage of Lennart), 目的就是带你走出这个密林。

最简单的指引莫过于问自己:“我想(用 Linux Sound API)做些什么?”。下面就是答案:
我想写一个类似于媒体播放器的软件!
那就用 GStreamer。除非你只想基于KDE来编程,那么,你可以选择使用Phonon。
我想在自己的应用程序中加入事件声音!
使用 libcanberra,并根据XDG Sound Theming/Naming 规范安装声音文件。如果你只基于KDE,则可以使用KNotify,但它的着重点有所不同。
我想做专业的音频编程,包括硬盘录音、音乐合成、MIDI界面等!
使用 JACK 或者完整的ALSA 界面。
我想基础的PCM音频播放和捕捉!
使用 safe ALSA 子集。
我想在游戏中加入声音!
如果是全屏幕的大型游戏,使用SDL的声音API。如果是简单的游戏,只用到基础的用户界面例如Gtk+的,使用 libcanberra
我想写一个混音器/音量控制软件!
这要根据你想在哪一个层次提供直接的支持:如果你想支持增强的桌面软件混音器,使用 PulseAudio 音量控制 API。如果你想支持硬件混音器,使用ALSA混音器API。
我想写一个支持plumbing layer的软件!
使用 完整的 ALSA体系。
我想写一个嵌入式的音频软件!
从技术使用性角度而言safe ALSA子集是一个好的选择,但基于你实际的情况也可以使用其它API。
你想了解更多关于这些API的不同之处吗?
GStreamer  
GStreamer LogoGStreamer 是一个Linux桌面媒体流系统的事实标准。它支持音视频流的编解码。可以基于该API实现从简单的音频文件播放到网络流媒体配置等大范围的应用。 GStreamer支持多种编解码器和音频后端。GStreamer显然不适用于基本的PCM音频播放或者低延时/实时应用场合。GStreamer是可 移植的,并不限于仅能在Linux上使用。它支持的音频后端包括ALSA、OSS、PulseAudio等。
[API Reference]
libcanberra   
GNomelibcanberra 是一个抽象的事件声音API。它是XDG 声音主题和命名规范的实现。虽然libcanberra是GNOME的一部分,但它并不依赖于GNOME/Gtk/GLib,因此可以用于其它桌 面环境。它除了提供简单的界面用于播放声音文件之外,还提供了缓冲机制(适用于基于网络的瘦客户机)和允许传递多种元数据到底层的声音系统进行控制从而增 强用户体验(比如定位事件声音)和改善易操作性。libcanberra支持多种后端,并且是可移植的。目前支持的后端包括ALSA、OSS、 PulseAudio、GStreamer等。
[API Reference]
JACK  
JACK LogoJACK 是一个用于连接专业音频应用程序和硬件输入输出的声音系统。该系统专注于低延时和应用程序之间的互联接,因此,该系统对于普通的桌面应用或者嵌入式应用都 不适合。如果你想做的只是简单的PCM音频播放,这个系统也不是很有用。JACK支持多种后端,其中支持得最好的是ALSA。JACK也是可移植的。
[API Reference]
Full ALSA  
ALSA LogoALSA 是专门面向PCM音频播放和录音的Linux API。ALSA专注于硬件设备,但也支持其他一些后端。ALSA这个名字既表示了Linux内核的音频驱动程序,也表示了围绕着驱动程序而建立的用户空间函数库。ALSA 函数库是全面且可移植的。完整的ALSA API可以是非常复杂和巨大的,但它能几乎能支持所有的音频设备。不过ALSA API的某些功能实际取决于Linux核心和对应的Linux驱动程序对硬件的支持。比如基于软件的声音服务器或者在用户空间实现的音频驱动程序(蓝牙和火线音频支持)。
[API Reference]
Safe ALSA  
ALSA Logo在全部的ALSA API中,只有一个子集是可以在所有ALSA支持的后端上工作的。因此,如果编写使用了ALSA的软件,并且需要软件是可移植、向后兼容和与各种音频服务器、蓝牙音频和火线音频都兼容的话,你应该只使用这个被称为Safe ALSA的安全API子集。该子集是基础的、可移植的PCM音频播放和录音功能的实现,而且除了支持ALSA核心本身所支持的设备之外,还支持OSS、PulseAudio、JACK等。
PhononKNotify  
PhononPhonon 是高层次的媒体流系统的抽象实现,比如GStreamer便是一种。但Phonon比GStreamer更深入。KNotify是一个用于“提示”的系 统,支持的不仅仅是事件声音。但目前还不支持 XDG Sound Theming/Naming Specifications。而且并不支持缓冲或传递事件元数据到更下层的音频系统。KNotify支持多种音频后端来通过Phonon进行播放。这两者的API都是仅面向KDE/Qt体系的,所以不能在该体系之外的应用程序中使用。
[Phonon API Reference] [KNotify API Reference]
SDL  
SDL LogoSDL 是一种跨平台、可移植的API,主要用于全屏幕的游戏开发。SDL包括很多东西,其中的一部分是一个可移植的音频界面。SDL也支持OSS、PulseAudio和ALSA作为后端。
[API Reference]
PulseAudio  
Pulse Audio LogoPulseAudio 是一种适用于Linux桌面和嵌入式环 境的音频系统。PulseAudio可以在用户空间运行,并且通常在ALSA之上运行。PulseAudio支持网络透明化、每应用程序的音量调节、空间事件声音、可以即时在设备之间切换音频流、策略判断以及其他许多高层次的操作。PulseAudio在Linux音频栈上加入了无缝播放glitch-free模 式。在专业音频制作环境下PulseAudio并不是很有用。PulseAudio在Linux之间是可移植的。PulseAudio具有原生的API可供调用,并支持ALSA的安全子集Safe ALSA。PulseAudio支持基于LD_PRELOAD的OSS兼容。PulseAudio支持与 JACK连接。
[API Reference]
OSS  
OSS LogoOpen Sound System 是一种低层的PCM音频API,支持多种Unix,包括Linux。一开始的时候它是被作为标准的Linux音频系统并在目前的Linux核心中实现对API版本3支持,并称为OSS3。但是,OSS3 已经被认为是过时的,并已经被ALSA完整地替代。OSS3的后继即OSS4已经可用,但在Linux上没有获得支持,包括标准核心以及众多的发行版。 OSS API是非常低层的,是基于使用ioctl()函数的直接核心界面通信而实现的。因此,OSS3 并不易用,而且并不能在非核心级别的音频系统比如象PulseAudio一类的声音服务器上实现,也不能在用户空间级别的音频驱动(比如蓝牙或火线音频)上实现。OSS3的时序模型完全无法正确地投射到软件音频服务器上,并在非PCI硬件诸如USB音频上也是存在问题的。还有就是,OSS不支持采样类型转换、remapping或重采样。这意味着对于支持OSS3的软件来说,它必须包括完整的转换器/remapper/resampler,以应对硬件本 身不支持用户需要的音频参数时需要用软件搭救的情况。而现在常见的音频设备很多就是只支持采样率48KHz的S32LE(有符号32位低位在前)音频。如果OSS应用软件假设它总是能播放S16LE(有符号16位低位在前)采样率为44.1KHz的音频就会因此而失败。OSS3可移植到其他Unix类的操作系统,但不同的系统之间存在差异。还有就是,OSS不支持环绕声和其他现在的声音系统所提供的功能。因此,综上所述,不应该再使用OSS。
[Programming Guide]

上面列出的所有音频系统和API一般在当前的各种发行版中均获得支持,除了libcanberra可能需要到开发中的发行版里面才有支持。

你想了解在什么情况下你必须使用某种特定的音频API吗?

GStreamer
GStreamer 适用于于非常高层的应用。比如你想播放音频或视频流,但不想理会任何的技术细节,诸如PCM播放控制或解码器调用等。
libcanberra
libcanberra 对于那些需要在用户界面中加入声音反馈的应用是最适合的。另外,它也可用作播放简单的声音以达到向用户给出提示一类的用途。
JACK
JACK 最适合于专业音频制作,以及应用程序之间的互联。
Full ALSA
完整的 ALSA 界面最适合于那些在“plumbing layer”的软件,或你基于某些专业音频制作的目的而想使用某种特定硬件的特殊功能时。
Safe ALSA
safe ALSA 界面最适合于通过硬件或其他软件声音系统播放或录制PCM音频数据的软件。
Phonon and KNotify
Phonon 和 KNotify 只能在 KDE/Qt 应用程序中使用,并只适用于高层的媒体播放用途,比如简单的声音提示等。
SDL
SDL 最适合于在全屏幕游戏中使用。
PulseAudio
目前,PulseAudio API 只应在那些想控制声音系统特定的功能(比如混音器)的应用程序,又或者,应用程序本身已具备PCM输出抽象层,想为PulseAudio增加额外的后端来保持一个最小化的音频栈。
OSS
OSS 不适合于在任何新的应用程序中使用。

你想了解多些有关safe ALSA 子集吗?

这里是一个“应”和“否”的列表,对应着ALSA API。如果你想你的软件能向后兼容,并且能在各种非硬件的后端或运行在用户空间的后端(比如蓝牙或火线音频)上运行,你应仔细阅读这个列表。其中一些建议也适用于使用完整ALSA API的软件开发者。如果你的软件不遵循这个列表,除非你有很好的理由,否则,可以认为你的软件是失败的。

“否”:

  • 不要使用“异步句柄(async handlers)”。比如通过 snd_async_add_pcm_handler() 及相关的函数。异步句柄Asynchronous handlers 是通过POSIX信号实现的,但在函数库和插件中使用该技术是有疑问的。即使你不想把自己限制在安全ALSA子集中,也不建议使用该功能。 这篇文章解释了为何信号对于音频输入输出操作是恶魔。
  • 不要自行分解ALSA配置文件,也不要使用任何ALSA配置功能函数,比如snd_config_xxx()那些。如果你需要枚举音频设备,则使用snd_device_name_hint()及相关的函数。它是唯一一个能同时支持枚举非硬件音频设备和用户空间音频设备的 API。
  • 不要分解/proc/asound/下面的任何文件。那些文件只包含有关于核心音频驱动的信息,用户空间插件并没有在那里列出。同时,核心设备的集合可能会与在用户空间所表现的不尽相同。比如,子设备会以其他名字(面向最终用户的名字,比如surround51)的方式被映射到用户空间。
  • 不要依赖于ALSA给出的固定设备索引。设备是依赖于系统引导过程驱动程序的初始化顺序,因此并不是固定的。
  • 不要使用snd_card_xxx() API。如果要进行枚举,可使用snd_device_name_hint()以 及相关的函数。snd_card_xxx()已经过时了。它只能列出核心的硬件设备,不能列出用户空间设备。尤其是snd_card_load()已经完全淘汰。
  • 不要硬编码设备名字符串,尤其是不要使用“hw:0”或“plughw:0”或“dmix”。这些设备没有定义通道映射,而且本身是直接映射到原始核心设备的。建议只使用“default”作为设备名字符串。如果需要特定的通道映射,对于立体声来说应使用“front”,4声道立体声应使用“surround40”,以及“surround41”、“surround51”等。可惜的是在目前,ALSA并没有为非核心设备的通道映射定义标准 名称。也即只有“default”是对于单声道或立体声音频流的唯一安全选择。你还应该在你的设备字符串前加上“plug:”前缀以使ALSA能透明地重新格式/映射/重采样你的PCM音频流,以便应对硬件或后端并不能原生地支持你的采样参数时的情况。
  • 不要假设任何特定的采样样本类别都能被支持,除了以下这些:U8, S16_LE, S16_BE, S32_LE, S32_BE, FLOAT_LE, FLOAT_BE, MU_LAW, A_LAW。
  • 不要使用snd_pcm_avail_update()来进行同步控制用途。当前这个函数只应用来查询还剩下多少字节需要读或者写。不要使用 snd_pcm_delay() 来查询播放缓冲区的填充情况。它只能用来作同步用途。务必完全理解这两个函数的区别,并且注意这两个函数的返回值并不需要有任何直接联系。
  • 不要假设混音器总是能理解dB信息。
  • 不要假设所有的设备都支持MMAP类型的缓冲区访问。
  • 不要假设播放缓冲区内的硬件指针就是DAC内部的实际播放位置。因为还存在着外部延时。
  • 不要试图用自己的代码来从ALSA错误状态中恢复。一个例子是缓冲区欠载。应使用snd_pcm_recover()来实 现。
  • 不要修改缓冲/时段衡量单位,除非需要实现特定的时延要求。程序应该有防护性,能正确处理当后端不能支持你的缓冲时段要求的情况。还要注意,在大多数的情况下播放缓冲区的缓冲时段设置只能间接地影响整体的时延。比如,把缓冲区的大小设置为某个固定数值反而有可能导致更高的实际时延。
  • 不要假设snd_pcm_rewind()是一定可用的,或者能在某种程度上可用的。
  • 不要假设一个PCM流何时能收到新的数据是严格依赖于采样频率和缓冲区设置,从而形成一个所谓的“平均数据流量”的概念。必须在设备要求提供数据时总是能有新数据写入到设备,捕获数据也是一样。
  • 不要使用“简单的”界面snd_spcm_xxx()
  • 不要使用任何已经标记为“过时”的函数。
  • 不要使用定时器、midi、rawmidi以及硬件依赖的任何子系统。定时器和midi不推荐使用的原因是他们不受libasound的插件支持。

“应”:

  • 使用snd_device_name_hint()来枚举音频设备。
  • 使用snd_smixer_xxx()取代原始的snd_ctl_xxx()
  • 如果需要同步,使用snd_pcm_delay()
  • 如果需要检查缓冲区的播放/捕获填充情况,使用snd_pcm_update_avail()
  • 使用snd_pcm_recover()来从任何ALSA函数库返回的错误状态中恢复。
  • 如果可能,使用设备所能支持的最大尺寸的缓冲区。这样可以最大限度地节约能源消耗和防止意外崩溃。如果需要快速地对用户输入进行响应的话,使用snd_pcm_rewind()

FAQ

ESD 和 NAS呢?
ESD 和 NAS 已经过时,无论是作为API抑或作为守护进程来使用。不要再基于他们来做开发。
ALSA 并不是可移植的!
这并不正确!实际上,用户空间函数库是相对可移植的,它甚至包括了一个专门支持OSS声音设备的后端。因而,没有理由不能在其他类Unix系统上使用ALSA库。
可移植性对我来说很重要!我能做些什么?
很不幸的是,截至目前为止,我没有能推荐的真正全面可移植的(比如,移植到WIN32)PCM API。虽然上面介绍的各种系统都或多或少地在类Unix系统之间可移植,但这并不表示这些系统在任何平台上都有可用的受支持的后端。如果你关心能否移植到Win32平台和MacOS,你只能在上面的建议之外寻找一个解决方案,或针对以上的方案提交你的可移植性或后端支持的修补。以上介绍的系统除了OSS之外没有一个是与Linux或类Unix系统的核心绑定的。
对PortAudio你的看法是?
我不认为对于类Unix系统来说,PortAudio是一个好的API。因此我不建议使用,但这是你的选择。
为何你这么讨厌OSS4?
我并不讨厌任何人或任何事情。我只是不认为OSS4是一个严肃的选择,特别是在Linux上。而且,对于Linux来说,由于有了ALSA,OSS已经完全是一个冗余。
我知道有些新的项目具备非常好的音频/媒体抽象层设计!
这并不足够。我只列出已知是有足够的相关性和维护得很好的那些。

后记

当然,以上这些建议都是非常基础的,只能作为一个方向性的指引。对于每一个具体的用例,实际上都有不同的细节上的需求,但我这里没有给予考虑。你应该考虑对于你的应用软件,我这些建议究竟有多少是实际适用的。

这个汇总只包括被认为是稳定的和在此时相对通用的那些系统。在未来,我希望能出现相对safe ALAS子集来说更适合以及更具备可移植性的替代品。

(作者的其他一些感语忽略不译了)

译者语

评论指出,作者还漏了一些其他的音频API没有描述。尤其是:OpenAL。OpenAl 是唯一一种支持多扬声器配置的音频函数库,并在那些需要使用到三维定位音效的游戏中被大量使用。比如Doom3这样的游戏都是使用OpenAL的。对于平常的多媒体用途,可能通过OpenAL来播放DVD上的多声道音频是一种比较常见的用途。OpenAL还有一个特点是,它已经是一个可以横跨Windows/Linux/OSX三大平台的系统,而且并不是只能作为游戏的后端使用。

另外,作者这篇发表在个人Blog上的文章引起了KDE粉丝的不满,认为作者有偏颇。而作者表示虽然自己是GNOME基础成员之一,但以上的一切只是个人看法。在滔滔的评论中,有一段是值得在这里列出的:

如果你的应用软件已经使用了GTK+,可以通过GST或libcanberra来加入声音功能;
如果你的应用软件使用的是Qt或Qt/KDE函数库,那么Phonon是一个比较自然的选择;
如果你打算写一个全新的应用软件,而且还没有与任何工具平台绑定,可以考虑Phonon所带来的后端抽象化的优点,与之对比的是直接使用GST或Xine所得到的可以进行细节控制的功能。