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

C# 如何产生精确定时的音调和静音?,c#,.net,audio,directx,directsound,C#,.net,Audio,Directx,Directsound,我有一个C#项目,为RSS提要播放摩尔斯电码。我使用ManagedDirectX编写它,却发现ManagedDirectX已经过时了。我的任务是播放纯粹的正弦波爆发,其间穿插着静默期(代码),这些静默期与持续时间精确同步。我需要能够调用一个函数,该函数可以播放一个纯音达数毫秒,然后Thread.Sleep()再播放另一个,等等。在最快的速度下,音调和空格可以短至40毫秒 它在托管DirectX中运行得相当好。为了获得准确的定时音调,我创建了1秒。将正弦波输入辅助缓冲区,然后播放某个持续时间的音调

我有一个C#项目,为RSS提要播放摩尔斯电码。我使用ManagedDirectX编写它,却发现ManagedDirectX已经过时了。我的任务是播放纯粹的正弦波爆发,其间穿插着静默期(代码),这些静默期与持续时间精确同步。我需要能够调用一个函数,该函数可以播放一个纯音达数毫秒,然后Thread.Sleep()再播放另一个,等等。在最快的速度下,音调和空格可以短至40毫秒

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

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

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

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

在这一点上我没有耐心,所以我问的是希望这里有人遇到过类似的要求,并能用可能的解决方案引导我前进。

尝试使用XNA

您必须提供一个文件,或一个静态音调流,您可以循环。然后你可以改变音调和音量


由于XNA是为游戏而设计的,因此延迟40毫秒完全没有问题。

最好只生成正弦波,并将其静音到一个缓冲区中,然后播放。也就是说,总是播放一些东西,但接下来将需要的任何东西写入缓冲区

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

uint numSamples = timeWantedInSeconds * sampleRate;

这是生成正弦波或静音所需的样本量,以两者中的任何一个为准。然后根据需要填充缓冲区。这样,您就可以获得尽可能精确的计时。

从ManagedDX转换到


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

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

我使用MemoryStream和BinaryWriter生成了一个WAV文件,并完成了RIFF头和分块。不需要“事实”块,这是44100Hz下的16位采样。现在我有了一个内存流,里面有1000毫秒的样本,并用二进制阅读器包装

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

当我尝试这样做时,我无法让SoundPlayer看到流的更改,即使我设置了它的stream属性。我被迫创建一个新的声音播放器。。。每40毫秒???没有

好吧,我今天想回到那个代码,开始看SoundPlayer的成员。我看了《声音定位》并读了一遍。据说设置SoundLocation的一个副作用是使Stream属性为null,反之亦然。因此,我添加了一行代码,将SOundLocation属性设置为伪造的“x”,然后将Stream属性设置为我的(刚刚修改的)MemoryStream。该死的,如果它没有按我要求的那样播放一个音调。似乎没有任何疯狂的副作用,比如事后死亡时间或增加记忆,或者???调整WAV流,然后加载/启动播放器确实需要1-2毫秒,但它非常小,而且价格合理

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

。你可以从SPTones.CS中找到此黑客的C#代码。感谢所有提供这方面信息的人,我