1

我正在编写同时播放数字音频(合成音乐)和 MIDI 音乐(使用 RtMidi 库)的 C++ 代码。数字化的音乐将在计算机的音频设备中播放,但 MIDI 音乐可以在外部合成器。我想播放一首同时使用数字化乐器和 MIDI 乐器的歌曲,但我不确定同步这两个音频流的最佳方式:

  • 无法使用像 Sleep() 这样的函数,因为延迟时间既不均匀又太长,无法满足我的需要(大约为 1 毫秒)。如果 Sleep() 在仅请求 1ms 时定期等待 5ms,生成的歌曲速度将关闭,除非每次调用时都准确,否则速度将不均匀。
  • 计算放入音频缓冲区的样本数量可为数字音频的音符之间提供超精确的时序(一个样本的最小延迟 - 48kHz 时为 0.02 毫秒),但此时序不能用于 MIDI。因为音频是缓冲的,所以音符会以突发方式合成(一次填充一个音频缓冲区,尽可能快),所以这会导致每次数字音频缓冲区需要播放一堆 MIDI 音符时,它们之间没有延迟被重新填充。
  • 播放实时 MIDI 数据没有时间信息,因此一旦发送音符就会播放。因此,一个音符不能安排在以后播放,所以我需要自己在正确的时间准确地发送 MIDI 事件。

目前我正在使用 nanosleep() - 它仅适用于 Linux,而不适用于 Windows - 等待音符之间的正确时间。这允许数字音频和 MIDI 数据保持同步,但是 nanosleep() 不是很一致,因此产生的速度非常不均匀。

谁能想到一种方法来保持数字音频和 MIDI 数据的音符之间的准确时序?

4

3 回答 3

2

如果你愿意使用 Boost,它有CPU-precision timers。如果没有,在 Windows 上有功能QueryPerformanceCounterQueryPerformanceFrequency,可用于基于 CPU 的计时,这肯定会满足您的所有需求。网络上有很多 Timer 类实现,其中一些在 windows 和 *ix 系统上都可以使用。

于 2012-06-10T14:30:40.093 回答
1

第一个问题是您需要知道有多少音频通过了音频设备。如果您的延迟足够低,您可能会根据您推送的数据量来猜测,但是这与播放之间的延迟是一个移动目标,因此您应该尝试从音频中获取该信息硬件。该信息是可用的,因此请使用它,因为您将从延迟测量中的错误中获得的“抖动”会以音乐上明显的方式影响同步。

如果必须使用 sleep 进行计时,有两个问题会使其睡眠时间更长:1. 优先级(如果另一个进程/线程具有更高的优先级,则如果计时器已用完,它将运行)和 2. 系统延迟(如果系统需要 5 毫秒来交换进程/线程,它可能会将其添加到您请求的延迟时间中)。这些延迟在音乐上是相关的。大多数 midi API 都有一个“sequencer” API,可以让您提前对数据进行排队,以避免使用系统计时器。

即使您没有将 portaudio 用于音频 I/O,您也可能会发现此文档很有用。

http://www.portaudio.com/docs/portaudio_sync_acmc2003.pdf

于 2012-06-11T14:51:21.707 回答
0

这个问题的答案不在于小缓冲区,而在于大缓冲区。

让我们以一首 3 分钟的歌曲为例。

首先渲染数字部分,并用 MIDI 音符“标记”它。然后开始播放并在时间到时触发 MIDI 音符,可能使用 std::vector 来保存有序列表。可以使用整体时间偏移来更改同步:

可怕的不完整但希望是关于该主题的示范性伪代码:

start_digital_playing_thread();
int midi_time_sync = 10; // ms
if (time >= (midi_note[50]->time + midi_time_sync)) // play note
于 2012-06-10T14:42:23.607 回答