如果我将线路或 USB 麦克风插入 PC,我可以以 44.1kHz 的频率录制音频。也就是说,大约每 23 微秒录制一次音频。音频波必须精确采样和播放,否则声音会失真。非实时操作系统如何管理如此高采样率的音频录制和播放这一对时间极为敏感的过程?无论是通过主板音频播放/录制音频,还是通过 USB 扬声器/麦克风播放/录制音频,这个过程有什么不同吗?
答案1
如果我将线路或 USB 麦克风插入 PC,我可以以 44.1kHz 的频率录制音频。也就是说,大约每 23 微秒录制一次音频。音频波必须精确采样和播放,否则声音会失真。非实时操作系统如何管理如此高采样率的音频录制和播放这一对时间极为敏感的过程?无论是通过主板音频播放/录制音频,还是通过 USB 扬声器/麦克风播放/录制音频,这个过程有什么不同吗?
归根结底缓冲。
44.1 kHz 并不意味着模拟信号在从声卡(USB、PCIe、板载,无所谓)发送到 CPU 时必须完美定时。事实上,信号数字的以 44100 赫兹发送数字脉冲编码调制比特流意味着,对于每一秒的音频,信号中将有 44,100 个“数据点”。信号是量化,这意味着它基本上是一个数字序列。这些数字的大小(8 位、16 位等)由样本格式。
现在,考虑一下,一旦音频被量化,它被捕获的速度有多快或多慢都无关紧要:因为它只是音频的一堆数据点(“样本”),如果你每微秒传输 1 个样本,每秒 1 个样本,每年 1 个样本等,系统都可以同样好地捕获它 - 只要你的计算机知道故意的采样率是,它可以以任何速度收集所有数据点,然后将它们存储在磁盘上(或 RAM 中,或任何其他地方),以便稍后以 100% 的保真度进行再现。这就是数字信号处理的魅力所在。唯一的最低要求是,如果您从麦克风“实时”捕获音频数据,您必须能够将样本流写入 CPU 并对其进行处理至少和样品到达的速度一样快。但您不必将每个单独的样本作为单独的传输进行处理。
虽然音频工作做需要相当精确的时间,以便人耳无法察觉音频中的“滞后”,这是绝不数字音频源(如声卡或 USB 麦克风)传输每个样本当它们被接收时,直接发送到 CPU,一次一个。问题是高架每次从音频源到 CPU 的传输中。
PCI Express 的开销比 USB 低得多(事实上,USB 2.0 每 1 毫秒只能发送和接收一次数据包 - 这对于单独发送 44,100 个样本来说太粗粒度了),但无论哪种情况,您都无法在一秒钟内单独发送 44100 个单独的样本。
为了解决这个问题,我们使用缓冲。虽然缓冲确实会带来延迟,但目标是拥有一个足够小的缓冲区,以便用户无论使用哪种情况都可以容忍它,同时又足够高,以便非 RTOS 的抢占式多任务内核调度程序将“切断”任何其他占用 CPU 的任务,并让您的音频堆栈有机会处理已堆叠的样本。
缓冲的基本思想是,您拥有一系列内存位置,其中代表一定数量样本(通常是 44,100 个样本中的几千个)的位排队。一旦缓冲区已满或接近已满,声源就会发送打断发送给内核,告诉 CPU 缓冲区已准备好读取。然后,当内核有时间时,它会安排一项任务,通过直接内存访问 (DMA) 将这些样本传输到系统内存。从那里,执行声音捕获的程序可以处理这些样本。音频播放的过程类似,但略有相反。
因此,如果您的缓冲区为 50 毫秒(1/20 秒),这并不罕见,则每个缓冲区中将有 44100 / 20 = 2205 个样本。因此,CPU 每秒仅接收 20 个中断,而不是每秒接收 44100 个中断(这肯定会使 CPU 因接收和处理这些中断而超载!)。好多了。
以下是一个示例的“实际操作”演练:
- 计算机用户请求程序开始从所连接的 USB 麦克风录制音频。
- 在几层软件向声卡发送正确的命令后,麦克风音频芯片组内的数字信号处理器 (DSP) 会启动。基本上,这个 DSP 会接收模拟麦克风捕获的模拟音频信号,并以每秒许多小点的频率对音频电平进行非常频繁、小的“采样”——例如,每秒 44100 次,或 44.1 kHz。想象一下,将一张纸放在一个非常精确加工的圆盘(如飞盘)的顶部,然后开始在纸上沿着圆的周长放置点。圆的每个象限上的 4 个点看起来不太像纸上的圆,但如果将点数增加到几百个,它就会开始成形。如果取数千个点,它看起来就很像一个圆。这就是量化的作用,只不过它需要一大堆点,这样最终“绘制”到纸上的结果看起来非常非常接近模拟波的原始“形状”。
- DSP 生成的每个样本都会被捕获到位于声卡音频芯片组内的小型样本缓冲区中(我使用术语“声卡”来指连接到数字计算机的任何数字音频接口 - 因此,USB,PCI,PCIe,在这方面都是相对相同的。)
- 一旦有“足够”的样本,声卡就会让计算机知道它可以从缓冲区中取出样本。如果计算机没有及时取样,同一个缓冲区最终会被下一组样本覆盖,这被称为“丢失”(音频中会发出可听见的“砰”声)。如果 CPU 非常繁忙,就会发生这种情况。
- 计算机的硬件驱动程序将样本从缓冲区复制到 CPU,然后将其从 CPU 传输到系统内存。从那里,应用程序可以对它们进行任何操作,包括将它们写入磁盘、将它们编码为 MP3、通过网络发送它们、将它们堆叠在 RAM 中等。
由于我们假设操作系统是不是实时,总有可能 CPU 会有太多任务争夺 CPU 时间,以至于在声卡继续移动到下一组样本之前,它根本没有时间及时读取或写入音频数据。通过拥有一个可预测的调度程序操作系统内核中的实现,例如将任务划分为时间片,可以尽量减少(但不能 100% 消除)丢失的可能性。例如,如果 HypotheticalOS 有一个抢占式内核调度程序,并为任何请求 CPU 时间的任务分配 1 毫秒的时间片,并在竞争任务之间公平分配这些时间片,那么处理音频 I/O 的任务可能会获得:
- 如果有 1,000 个任务争夺时间,则每秒 CPU 时间为 1 毫秒(每秒 1 次中断)
- 如果有 500 个任务争夺时间(每秒 2 次中断),则每秒的 CPU 时间为 2 毫秒
- ...
- 如果有 2 个任务争夺时间,则 CPU 时间每秒为 500 毫秒(每秒 500 次中断)
(以上假设一个单处理器和一个完全饱和的 CPU;SMP 会使事情变得复杂。)
所以你可以看到,100 毫秒的缓冲区意味着内核调度程序必须至少给你的音频 I/O 堆栈(通常包括内核和用户空间组件)10音频的每一秒都有多个时间片来完成工作。如果音频管道需要多个时间片来完成工作,那么它每秒就需要更多时间片。
希望这些时间片是均匀分布的:如果你在给定秒的前 50 毫秒内获得了 50 个时间片,然后零剩余的 950 毫秒的时间片,加上 100 毫秒的缓冲,用户将体验到将近一秒钟的掉线!真恶心。
稍微偏离一下我应该指出,还有其他方法可以进行音频 I/O,但这是在典型桌面上最传统的方式。在 GNU/Linux 上,有一个名为 PulseAudio 的声音服务器,它会告诉您的声卡停止发送中断,并根据所使用的缓冲区大小(可以随时间变化以适应更高的 CPU 需求或应用程序请求的更低延迟!)在需要时从声音缓冲区中获取数据。这种技术称为基于时间的调度,并且它依赖于Linux内核中非常好的调度程序实现。