寻找一个简单的 Unix 音频接口
为了好玩,我正在通过移植一个旧游戏来学习 x86 汇编语言。最棘手的部分是找到一种播放简单声音(主要是方波)的方法。该游戏目前预计能够直接写入扬声器。如何在当前的 Unix 系统中最轻松地播放音频?
标准
更准确地说,这就是我正在寻找的(首先是最重要的标准):
- 可以播放波形。 (8 位、8000Hz 即可)。
- 足够简单,可以从汇编语言调用。
- 适用于现代 Debian GNU/Linux 系统,无需 root 即可进行简单的软件包安装。
- 用户无需安装任何库或其他依赖项。
- 可跨尽可能多的 UNIX 系统移植。 (尤其是 {Net,Free,Open}BSD。我预计 Solaris、MacOS 和 WSL 可能有自己的处理方式。)
优先
我正在移植的代码是 32 位 x86 汇编语言,目前我不想将其转换为 64 位。另外,代码不需要与 libc 链接,如果可能的话,最好保持这种方式。
研究
以下是我研究过的一些途径:
/dev/audio
这就是我所希望的:我只是打开
/dev/audio
作为一个文件并向其中写入字节。不幸的是,默认情况下,Linux 内核似乎不再附带该设备,因此人们必须以 root 身份安装内核模块 (snd-pcm-oss)。 (此外,虽然这是一个较小的问题,但 Linux 使用旧的 SunOS 语义并且不允许多路复用)。直接扬声器访问 (
/dev/tty0
,/dev/input/by-path/platform-pcspkr-event-spkr
)这就是游戏最初编写的目的。然而,直接写入PC的内置扬声器需要root配置和Linux下的内核模块(pcspkr)。此外,它不适用于笔记本电脑和其他甚至没有连接 PC 扬声器的机器。
阿尔萨斯
Linux 确实附带了一个无需内核模块即可使用的声音系统,称为高级 Linux 声音架构 (ALSA)。其他Unix系统可以模仿它,虽然我不确定有多好。它需要更多的设置,虽然看起来它可以直接通过 ioctl 进行控制,但通常它需要一个外部库(alsa-lib)。该界面在纯汇编中很难看,但可以用 C 语言完成。在 Linux 上,ALSA 本质上是动态链接的,并且还使用 dlopen 在运行时根据配置加载更多库(pulse、pipewire)。这是一个问题,因为我正在处理的汇编代码是 32 位的,但大多数 64 位机器不会安装 32 位版本的 libalsa、pulse 和 pipelinewire。
管道到 fork/exec'd 程序
系统调用
pipe(2)
可用于创建文件描述符,以便游戏可以将原始数据发送到程序,例如 sox 的玩命令,在 fork 中执行。虽然这具有将可移植性问题推到其他程序上的良好特性,但我宁愿避免对外部程序的运行时依赖。
答案1
足够简单,可以从汇编语言调用。
UNIX 系统都基于这样的思想:C 调用约定允许您调用功能。这是一个非常基本的原则! C 和 UNIX 的历史交织在一起是有原因的。
可跨尽可能多的 UNIX 系统移植。 (尤其是 {Net,Free,Open}BSD。我预计 Solaris、MacOS 和 WSL 可能有自己的处理方式。)
您可以从列表中删除它。如果您留在汇编中,那么您正在编写特定的 ABI。对此你没有什么可以改变的。由于将汇编移植到其他操作系统需要付出巨大的努力,因此发明了 C。你已经落后了50年!
这是一个问题,因为我正在处理的汇编代码是 32 位的,但大多数 64 位机器不会安装 32 位版本的 libalsa、pulse 和 pipelinewire。
幸运的是,对于较大的桌面发行版来说情况并非如此,它们仍然提供 32 位的兼容层。 (所以,32 位库)
但老实说,当您支持一个在大多数机器上 20 年来本质上一直是遗留模式的平台时,这是您必须跳过的一个障碍。您至少需要 32 位环境。
纯汇编的界面很难看,但可以用 C 语言完成。
因此,是的,您必须按照 x86-Linux ABI C 调用约定的要求设置寄存器,并调用 C 功能。 (无论如何,我怀疑你也必须对图形、键盘 IO 和其他东西这样做。)
不过,不建议直接调用 libalsa,即使这会增加另一个依赖项,但使用portaudio
可以减少必须执行的复杂函数调用数量的 slim 库。
因此,老实说,通常这种将程序集移植到现代计算机上的现代操作系统的移植工作或多或少都是用 C 或其他语言重写的。如果您可以忍受 32 位 x86 的限制,那么最可能最明智的方法是识别游戏中的功能核心单元,例如“这是主循环”、“这是用于更新帧中图像的子例程” buffer”,“这是调用获取键盘状态的子例程”,并将其外部结构转换为 C,并且有内联组装在这些C骨架中。然后,您可以在程序查询键盘控制器内存地址的地方直接从 C 调用您的输入库。在需要将样本转移到声音系统的地方,您将获取程序集填充样本的缓冲区,并将地址传递给正确的 portaudio 函数,依此类推。
您会发现,在托管平台上学习汇编采取的形式是用与平台范例兼容的运行时填充编程语言,而不是尝试从本质上不兼容的汇编调用平台。