Linux 中的流音频

作者:Siome Goldenstein

随着多媒体的普及和硬件成本的不断降低,开发者在新应用程序中使用声音变得越来越有吸引力。声音可以为游戏增添色彩,帮助改进文字处理器的用户界面等等,使计算机的使用更有趣和/或更有效。

在常见的声卡中,声音生成有两种不同的形式。第一种主要用于音乐,通过 MIDI 接口和 FM 发生器。其思想是合成(人工合成或通过先前录制的样本)不同乐器在不同时间的发生。第二种方法是使用数字音频信号,它可以表示任何声音,从鸟鸣到人声,甚至是音乐信号。

声音与感知

人类听觉系统感知到达它的空气压力的差异。例如,当我们拨动吉他弦时,它开始振动。这种运动以压缩和解压缩的波浪形式在空气中传播,就像我们向湖面扔一块小石头时可以看到湖面上的波浪一样。

Streaming Audio in Linux

图 1. 人类听觉系统

图 1 显示了人类听觉系统的详细表示,它在将信息发送到大脑之前处理和分解传入的声音。关于处理人类语音有很多文献(参见参考文献 1 和 2),但是,我将只介绍一些基本知识,而不是过多地介绍生物医学方面的考虑。

我们将空气的快速振荡“理解”为尖锐的声音,将缓慢的振荡理解为低音。当不同的振荡混合在一起时,我们可能会失去这种“音乐”内涵。枪声或玻璃破碎的声音很难分类。然而,有一些数学工具能够将任意声音分解为一组简单的振荡(例如,傅里叶分析),但这些工具超出了本文的范围(参见参考文献 3)。

表示声音现象的一种简单的数学方法是使用描述它的函数。此函数负责关联一段时间内空气压力的强度。这种方法通常称为时域表示,当使用电子传感器(如麦克风)时也可以获得,电子传感器将电电压而不是气压随时间变化进行映射。在电子处理结束时,例如录音或放大,另一个传感器(例如扬声器)负责从电强度中获取气压,以便我们可以听到它们。

对于模拟电子设备来说,这个模型已经足够好了;但是,对于现代数字计算机或 DSP(用于信号处理的专用设备)的使用来说,存在一个问题——如何在有限的内存中保存和处理即使在一个很小的函数区间内也包含的无限信息。

我们必须处理数字音频,它是通过连续数学函数模型获得的,通过两个步骤:采样和量化。采样步骤仅用有限数量的点来表示函数的域。这通常通过均匀点采样来完成;换句话说,我们只保留函数在某些点的值,这些点彼此等距。很明显,如果函数在两个相邻样本之间变化太大,则可能会发生一些信息丢失(参见图 2)。

Streaming Audio in Linux

图 2. 数字音频函数

每秒钟采集的样本数,即采样率,应谨慎选择。香农定理指出,如果采样速度是信号“最快振荡”的两倍,则可以恢复原始声音;这称为奈奎斯特频率采样率。

了解您正在处理的信号类型非常重要,以便选择合适的采样率。较高的采样率可以提供更好的质量,但也需要更多的内存和计算机能力来处理和存储。

量化步骤设置每个离散样本处函数的值。一种简单的方法是使用 float,因此每个样本需要四个字节。一般来说,这远远超出了所需的质量,人们通常使用一个字节(无符号字符)或两个字节的样本(有符号字)。

让我们比较两个具体的例子,看看这些选择如何显着改变保存和处理声音所需的内存量。在光盘上,音频是立体声的(两个不同的音轨,左声道和右声道),采样率为每秒 44,100 个样本,使用两个字节来保存每个样本。一分钟的音频将需要:2 X (60 X 44,100) X 2 = 10.5MB 的存储空间。

对于语音邮件或基于电话的系统,如果控制计算机需要向用户播放声音,则这些数字会大相径庭。不需要那么多样本,因为电话线路有严重的质量限制,通常这些系统每秒采集 8000 个样本,仅使用一个通道(单声道)并且每个样本仅保存一个字节。同样,一分钟的音频我们将需要:1 X (60 X 8000) X 1,大约 480KB 的信息。

声卡

由于使您的声音设备完全正常工作非常重要,因此应检查许多配置细节,例如内核中的声音支持(您可以编译为内核模块)。请查看 Jeff Tranter (Jeff_Tranter@mitel.com) 撰写的 SOUND-HOWTO,网址为 http://confused.ume.maine.edu/mdw/HOWTO/Sound-HOWTO.html —这是一个很好的参考资料。

声音文件

一旦我们掌握了数字音频信号,对其进行编码就很重要。有许多类型的声音文件(au、wav、aiff、mpeg),每种文件都有其优缺点。其中一些文件只是将样本一个字节接一个字节地堆叠在向量中,而另一些文件则尝试通过转换,有时甚至是繁重的计算来压缩信号。

幸运的是,在 sunsite (http://sunsite.unc.edu/) 上,您可以找到 Peter Kabal (kabal@tsp.ee.mcgill.ca) 提供的 AFsp,这是一个为您读取和写入这些文件的库。我没有使用它,因为当我找到它时,我已经编写了一些代码来执行相同的操作。AFsp 的文档非常完善。

简单的播放操作

在 Web 上有很多关于如何在 Linux 中编程声音的输入和输出的良好信息来源。您应该首先查看的是 http://www.4front-tech.com/pguide/index.html 上的OSS 程序员指南。它包含您控制和操作音频硬件不同方面所需的所有信息,例如 MIDI、混音功能,当然还有数字音频。

在 Linux 中,声音硬件通常通过设备(通常是 /dev/audio)进行控制。您必须通过调用 open 激活它,并使用 ioctl (I/O 控制) 设置一些参数(采样率、量化方法以及单声道或立体声)。上述指南的“基本音频”部分很好地说明了播放或录音的这些基本步骤。

播放是通过对设备上的样本向量执行 write 操作来完成的,而录音是通过 read 完成的。为了简单起见,这里省略了一些细微的细节,例如执行这些操作的顺序。

流音频和交互式应用程序

如果您计划创建需要实时交互的应用程序(例如,游戏引擎)并且必须连续流式传输音频序列,则需要采取一些重要措施来确保音频缓冲区既不会溢出也不会下溢。

第一种情况,溢出,可以通过更多地了解 OSS 实现来解决(查看OSS 程序员指南的“使音频复杂化”页面)。缓冲区被划分为多个相等的部分,您可以在其他部分排队等待播放时填充其中一个部分。一些 ioctl 调用将为您提供有关缓冲区中总可用空间的信息,以便您可以避免阻塞。您还可以使用 IPC(进程间通信)技术并创建一个不同的进程,专门负责缓冲区操作。

当您以比设备播放速度慢的速度将音频发送到输出时,缓冲区会在您发送更多数据之前变空,从而导致下溢。由此产生的效果令人不安,有时很难诊断为下溢问题。一种可能的解决方案是以较慢的速度输出音频,从而为计算机提供更多时间来处理数据。

可用软件

了解其他人如何编写他们的程序有助于理解实现问题的内在困难。因此,我在网上搜索了可用的软件,并且对我找到的软件的数量和质量都印象深刻。

一个好的、简单的“音效服务器”示例是 Terry Evans (tevans@cs.utah.edu) 提供的 sfxserver,它可以在 sunsite 上找到。它控制音频设备并接收命令(目前仅来自 stdin),例如“加载新效果”,开始播放加载的效果等。在同一位置,您还可以找到 sfxclient,这是一个程序客户端的示例。

通用的网络音频系统采用了将高层应用程序开发与硬件和设备操作分离的方法。网络音频系统 (nas) 是这种范例的一种实现,它具有与 X Windows 系统相同的思想和框架。它运行在许多架构上,例如 Sun、SGI、HPUX 和 Linux。通过它,您可以编写利用网络声音的应用程序,而无需担心您实际在哪里工作——网络层会为您处理一切。nas 附带文档和许多客户端示例。您可以从 sunsite 下载它以及预编译的 rpm 包。一些游戏,例如 XBoing 和 xpilot,已经支持它。

另一种网络传输实现是 netaudio (http://www.bitgate.com/netaudio/)。它不打算像 nas 那样作为应用程序和设备之间的中间层工作,而仅负责沿网络实时传输数据,从而允许一些有趣的属性,例如重广播。我看到的巨大优势是它的紧凑性:gzip 压缩的 tar 文件约为 6 KB。基本思想是在管道状结构中使用另一个程序,以便在接收后(或录制以进行传输)启用播放。README 文件提供了如何使用其他程序压缩音频以减少所需带宽的示例,从而成为 Web 的免费、实时音频替代方案。

一个类似的软件包,使用 LPC 压缩方法(仅为语音设计)是 John Walker (http://www.fourmilab.ch/) 提供的 Speak Freely for Unix

另一个有趣的音频流应用程序示例是 mpeg 音频播放。这种方法实现的压缩令人难以置信,使得通过 Internet 按需提供高质量音频成为可能。不幸的是,实时播放需要一台快速的机器。

同样,查看您的 sunsite 镜像,您会找到一些实现。其中一个快速、有趣且引人注目的是 Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) 和 Oliver Fromme (oliver.fromme@heim3.tu-clausthal.de) 提供的 mpg123。官方网页位于 http://www.sfs.nphil.uni-tuebingen.de/~hipp/mpg123.html。

结论

在应用程序的实现过程中,头脑中清楚地了解基本的数字音频概念非常重要,否则可能会出现误解或难以定位的微小错误。

网上有大量可用的程序表明,这种资源很有吸引力,它们还为您提供了一个开始编程的基础,因此您不必从头开始。

我故意将实现细节排除在本文之外,因为要使其既简洁又准确,又要有帮助是不可能的。再次,我建议那些对内部细节感兴趣的人访问提到的链接并下载一些现有的应用程序,以了解它们是如何编码的。

参考文献

Siome Klein Goldenstein 是一名电子工程师,并拥有计算机科学(图形学)硕士学位。在印刷时,他将开始在宾夕法尼亚大学攻读计算机科学博士学位。

加载 Disqus 评论