如何产生精确定时的音调和沉默?

时间:2010-04-27 21:33:01

标签: c# .net audio directx directsound

我有一个C#项目,它为RSS源播放摩尔斯电码。我使用Managed DirectX编写它,却发现Managed DirectX已经过时并且已弃用。我的任务是播放纯正的正弦波突发,其间穿插着静音期(代码),它们的持续时间是精确的。我需要能够调用一个播放纯音多少毫秒的函数,然后Thread.Sleep()然后播放另一个,等等。最快,音调和空格可以短至40ms。

它在Managed DirectX中运行良好。为了获得精确定时的音调,我创造了1秒。将正弦波放入辅助缓冲器,然后播放一定持续时间的音调,我在缓冲结束的x毫秒内向前寻找然后播放。

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

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

DirectSoundOut运行良好一段时间,但它的问题是,对于每个音调突发Play()它会旋转一个单独的线程。最终(5分钟左右)它就停止工作了。在VS2008 IDE中运行项目时,可以在“输出”窗口中退出线程后查看线程之后的线程。我不会在播放期间创建新对象,我只是Seek()音色流然后反复调用Play(),所以我认为这不是孤立缓冲区的问题/无论堆积到什么时候都会被阻塞。

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

4 个答案:

答案 0 :(得分:8)

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

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

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

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

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

我还实现了一个频率属性,它重新生成样本并使用Seek / BinaryWriter技巧覆盖RIFF / WAV MemoryStream中的旧数据,但样本数相同但频率不同,并再次执行相同操作振幅属性的东西。

This project is on SourceForge。您可以从this page in the SVN browser获取SPTones.CS中此hack的C#代码。感谢所有提供相关信息的人,包括@arke,他的想法与我的关系很接近。我很感激。

答案 1 :(得分:3)

最好只生成正弦波并一起沉默到你玩的缓冲区中。也就是说,总是玩一些东西,但是把你需要的东西写到那个缓冲区里。

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

uint numSamples = timeWantedInSeconds * sampleRate;

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

答案 2 :(得分:1)

尝试使用XNA。

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

由于XNA是针对游戏制作的,因此40毫秒的延迟完全没有问题。

答案 3 :(得分:1)

从ManagedDX转换为SlimDX ...

应该很容易

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