22

我有一个为 RSS 提要播放摩尔斯电码的 C# 项目。我使用托管 DirectX 编写它,但发现托管 DirectX 已过时且已弃用。我的任务是播放纯正弦波阵发,其间穿插着静默期(代码),它们的持续时间精确计时。我需要能够调用一个在这么多毫秒内播放纯音的函数,然后是 Thread.Sleep() 然后播放另一个,等等。在最快的情况下,音调和空格可以短至 40 毫秒。

它在托管 DirectX 中运行良好。为了获得精确定时的音调,我创建了 1 秒。正弦波进入辅助缓冲区,然后播放一定持续时间的音调,我在缓冲区结束后的 x 毫秒内向前搜索,然后播放。

我试过 System.Media.SoundPlayer。这是一个失败者[编辑 - 请参阅下面的答案],因为您必须 Play()、Sleep(),然后是 Stop() 才能获得任意音调长度。结果是音调太长,因 CPU 负载而异。实际停止音调需要不确定的时间。

然后我开始了漫长的尝试使用NAudio 1.3。我最终得到了一个提供音调数据的内存驻留流,然后再次向前寻找,将所需的音调长度留在流中,然后播放。这在 DirectSoundOut 类上工作了一段时间(见下文),但 WaveOut 类很快就死了,内部断言说尽管 PlayerStopped = true,但缓冲区仍在队列中。这很奇怪,因为我玩到最后,然后在音调结束和下一个音调开始之间等待相同的持续时间。您会认为在开始播放 40 毫秒音调后的 80 毫秒,它不会在队列中有缓冲区。

DirectSoundOut 有一段时间运行良好,但它的问题是,对于每个音调突发 Play(),它都会分离一个单独的线程。最终(5分钟左右)它只是停止工作。在 VS2008 IDE 中运行项目时,您可以在输出窗口中看到一个又一个线程退出的线程。我在播放过程中不创建新对象,我只是 Seek() 音流然后一遍又一遍地调用 Play(),所以我认为孤立缓冲区/任何堆积直到它被阻塞都不是问题。

我对此没有耐心,所以我希望这里有人面临类似的要求,并且可以引导我朝着可能的解决方案前进。

4

4 回答 4

8

我不敢相信......我回到 System.Media.SoundPlayer 并让它做我想做的事......没有巨大的依赖库,95% 未使用的代码和/或怪癖等待被发现:-) . 此外,它可以在 Mono (2.6) 下的 MacOSX 上运行!!! [错误 - 没有声音,将提出单独的问题]

我使用 MemoryStream 和 BinaryWriter 编写 WAV 文件,并带有 RIFF 标头和分块。不需要“事实”块,这是 44100Hz 的 16 位样本。所以现在我有一个包含 1000 毫秒样本的 MemoryStream,并由 BinaryReader 包装。

在 RIFF 文件中,有两个 4 字节/32 位长度,“总”长度是流中的 4 个字节(ASCII 中的“RIFF”之后),以及样本数据字节之前的“数据”长度. 我的策略是在流中寻找并使用 BinaryWriter 更改两个长度以欺骗 SoundPlayer 认为音频流只是我想要的长度/持续时间,然后 Play() 它。下一次,持续时间不同,所以再次用 BinaryWriter 覆盖 MemoryStream 中的长度,Flush() 它并再次调用 Play()。

当我尝试这个时,即使我设置了它的 Stream 属性,我也无法让 SoundPlayer 看到对流的更改。我被迫创建一个新的 SoundPlayer……每 40 毫秒???不。

好吧,我今天想回到那个代码并开始查看 SoundPlayer 成员。我看到了“SoundLocation”并阅读了它。那里说设置 SoundLocation 的副作用是使 Stream 属性为空,反之亦然。所以我添加了一行代码将 SOundLocation 属性设置为虚假的“x”,然后将 Stream 属性设置为我的(刚刚修改的)MemoryStream。该死的,如果它没有按照我的要求准确地播放一个音调。似乎没有任何疯狂的副作用,比如之后的死时间或增加记忆,或者???调整 WAV 流然后加载/启动播放器确实需要 1-2 毫秒,但它非常小而且价格合适!

我还实现了一个 Frequency 属性,它重新生成样本并使用 Seek/BinaryWriter 技巧以相同数量的样本覆盖 RIFF/WAV MemoryStream 中的旧数据,但频率不同,并再次为幅度属性。

这个项目在 SourceForge 上您可以从SVN 浏览器中的此页面获取 SPTones.CS 中此 hack 的 C# 代码。感谢所有提供这方面信息的人,包括与我的想法接近的@arke。我很感激。

于 2010-04-28T03:46:35.267 回答
3

最好将正弦波和静音一起生成到您播放的缓冲区中。也就是说,总是播放一些东西,但将接下来需要的任何内容写入该缓冲区。

您知道采样率,并且给定采样率,您可以计算出您需要编写的样本量。

uint numSamples = timeWantedInSeconds * sampleRate;

这就是生成正弦波或静音所需的样本量,无论哪个。然后根据需要填充缓冲区。这样,您可以获得最准确的时间。

于 2010-04-27T21:44:58.150 回答
1

尝试使用 XNA。

您必须提供一个文件,或一个静态音调的流,您可以循环播放。然后,您可以更改该音调的音高和音量。

由于 XNA 是为游戏而生的,所以 40 毫秒的延迟完全没有问题。

于 2010-04-27T21:37:16.310 回答
1

从 ManagedDX 转换为SlimDX应该很容易......

编辑:是什么阻止你,顺便说一句,只是预先生成'n'个正弦波样本?(其中 n 最接近您想要的毫秒数)。生成数据真的不需要那么长时间。除此之外,如果您有一个 22Khz 缓冲区并且您想要最后的 100 个样本,为什么不提交“缓冲区 + 21950”并将缓冲区长度设置为 100 个样本?

于 2010-04-27T22:08:43.857 回答