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

22

我有一个C#项目,用于播放RSS订阅的莫尔斯电码。我使用了Managed DirectX编写它,但发现Managed DirectX已经过时并被废弃。我的任务是播放与沉默周期(代码)精确定时其持续时间的纯正正弦波间隔。我需要能够调用一个函数,播放纯音调一段时间,然后Thread.Sleep(),然后再播放另一个,依此类推。在最快的情况下,音调和空格可以短至40毫秒。

在Managed 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个投票了?哇。 - Bob Denny
这是一个有趣的应用程序,你提出了一些好问题。所以,是的,点赞。 - Erik Funkenbusch
如果您已经在托管DirectX (MDX)中使其工作,为什么它不再受Microsoft支持是个问题呢?仅仅因为它不再得到支持并不意味着您不能使用它。任何已安装.NET 2和DirectX 9的计算机都应该能够继续运行MDX应用程序,没有任何问题。 - Ash
这可能是我在这个项目上采取的路线。这是一个免费的“玩具”应用程序,而且我在可以花费时间调试它方面受到了限制 :-) - Bob Denny
4个回答

8
我简直不敢相信...我回到了System.Media.SoundPlayer,并让它做了我想要的一切...没有庞大的依赖库,其中95%的代码未使用和/或等待被发现的怪癖 :-). 此外,它在Mono(2.6)下运行在MacOSX上!!! [错误-没有声音,请提一个单独的问题] 我使用MemoryStream和BinaryWriter来抄录WAV文件,包括RIFF头和分块。不需要“fact”块,这是44100Hz的16位样本。所以现在我有一个带有1000毫秒样本的MemoryStream,由BinaryReader包装。
在RIFF文件中,有两个4字节/32位长度,即“总体”长度,在流中距离4个字节(ASCII中的“RIFF”之后),以及在采样数据字节之前的“数据”长度。我的策略是在流中寻找,并使用BinaryWriter更改两个长度,以欺骗SoundPlayer认为音频流只是我想要的长度/持续时间,然后Play()它。下一次,持续时间不同,因此再次使用BinaryWriter覆盖MemoryStream中的长度,Flush()它并再次调用Play()。
当我尝试这个时,即使我设置了其Stream属性,SoundPlayer也无法看到流的更改。我被迫每40毫秒创建一个新的SoundPlayer?不。
今天我回到那段代码并开始查看SoundPlayer成员。我看到“SoundLocation”并阅读了它。它说,设置SoundLocation的副作用将是将Stream属性设置为null,反之亦然。因此,我添加了一行代码来将SOundLocation属性设置为某些虚假的东西,“x”,然后将Stream属性设置为我的(刚修改过的)MemoryStream。该死的,如果它没有挑选出来并播放一个与我要求的时间长度完全相同的音调。似乎没有任何疯狂的副作用,如死时间后或增加内存等。它需要1-2毫秒来调整WAV流并加载/启动播放器,但很小而且价格合适!
我还实现了一个频率属性,它重新生成样本并使用Seek / BinaryWriter技巧在RIFF / WAV MemoryStream中叠加旧数据,但样本数不同但频率不同,然后对Amplitude属性执行相同操作。

这个项目在SourceForge上。你可以从SVN浏览器中的这个页面获取SPTones.CS中的C#代码。感谢所有提供信息的人,包括思路与我相近的@arke。我非常感激。


1
即使您不需要更多的帮助,回顾并描述您所做的事情也是值得赞赏的。您可以在stackoverflow上将自己的答案标记为正确的。如果您愿意的话。 - CaptainCasey
Casey - 我明天会做的... StackOverflow说你必须等待2天才能将自己的答案标记为正确答案...这并不奇怪 :-) - Bob Denny

3
最好将正弦波和静音一起生成到一个缓冲区中并播放。也就是说,始终播放某些内容,但将下一个所需内容写入该缓冲区。
您知道采样率,并且根据采样率可以计算需要编写的样本数量。
uint numSamples = timeWantedInSeconds * sampleRate;

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

再次感谢,Arke!这可能是正确的方法。但它会很复杂,因为美国莫尔斯电码在时间上有“细微差别”。我已经在代码中解决了所有这些问题,提供了一系列毫秒值来表示“标记”和“空格”。这就是为什么我正在寻找一种产生任意音调和间隔长度的方法。现在我正在使用Managed DirectX完成这个任务,效果几乎完美。 - Bob Denny
我现在正在查看ASIO.NET http://www.codeproject.com/KB/audio-video/Asio_Net.aspx - Bob Denny

1

尝试使用XNA。

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

由于XNA是为游戏而设计的,因此它完全不会受到40毫秒延迟的影响。


XNA可以从C#中使用吗?我不知道这一点。使用它需要我的用户安装某个庞大的库吗?在等待您的答复时,我会去查找。谢谢! - Bob Denny
是的,是的,是的!http://weblogs.asp.net/scottgu/archive/2006/12/19/building-killer-games-using-net-and-xna-game-studio-express.aspx - James Westgate
XNA会有同样的问题。特别是在Windows XP上,但也很可能在Vista及以上版本上出现,这取决于驱动程序。 - Chris Walton
似乎在XNA中只能从文件播放,但是“缓冲”将在即将推出的版本中实现?感谢arke - 我负担不起另一个失败的实验。最后一条建议(“使用NAudio”)让我花了2天时间,包括浏览库源代码以发现基本限制。 - Bob Denny
正如我在答案中所说,您只能播放静态文件,但是您可以完全控制音高、音量、播放和停止。它非常快,因为它是为游戏而设计的。Windows Media库会加载然后播放文件,因此性能较差。XNA允许您预加载文件,然后根据需要应用效果并播放它。 - Coincoin
由于您只是试图玩摩尔斯电码,这将足够了。 - Coincoin

1

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

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


这就是我基本上正在做的事情,只是将secondaryBuffer的起始位置设置为在结束时间的“n”毫秒内,然后播放。在托管DirectX中,这非常有效。我现在正在研究ASIO。使用ASIO4ALL看起来非常轻量级,并且使用WDM设备进行输出。 - Bob Denny
哦,我看了看SlimDX,可能会再看看。它需要太多的粘合剂,而且它是一个商业产品,具有免费许可证。我不需要那么多的绘图/3D东西。但我没有好好尝试过。我的时间有限,SlimDX的重新分发限制似乎是一个劣势。 - Bob Denny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接