在Windows Phone 7上使用DynamicSoundEffectInstance在运行时创建声音(译文)
By S.F.
本文链接 https://www.kyfws.com/news/using-dynamicsoundeffectinstance-to-create-sounds/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 9 分钟阅读 - 4075 个词 阅读量 0在Windows Phone 7上使用DynamicSoundEffectInstance在运行时创建声音(译文)
原文地址:https://www.codeproject.com/Articles/116087/Using-DynamicSoundEffectInstance-to-Create-Sounds
原文作者:Joel Ivory Johnson
译文由本站翻译
前言
Windows Phone 7的DynamicSoundEffectInstance类的简要介绍,以及有关如何与该类通信和控制音调的演示.
下载代码93.1 KB 在Atlanta Silverlight用户的会议之后,我和其他几个MVP一起吃饭,我们正在谈论我们当时的情况我想提到的是,我想直接访问XNA中使用的声音缓冲区.詹姆斯`阿什利(James Ashley)立即回应" DynamicSoundEffectInstance !!".当时,James从未使用过它,而我才发现它,所以我需要获得有关它如何工作的更多信息.所以那天晚上,我比平时晚了一点,以便弄清楚它是如何工作的.由于该方法的文档仍处于早期形式,因此我并没有找到我想知道的所有内容,但能够找到答案. 在撰写本文时,我将假设您对声音和扬声器的工作原理有所了解.如果没有,您将要阅读数字到模拟转换器上的Wikipedia文章. 在这篇文章中,我只是想达到能够播放音调并控制其频率的目的.从高层次来看,这是我们需要做的: 现在将这些步骤转换为更具体的步骤.让我们从分配缓冲区开始.
填充缓冲区
您选择的缓冲区大小将主要取决于您希望声音所具有的延迟类型以及所生成声音的期望质量.通常,低延迟比较好,因为从程序生成声音到用户听到声音之间的时间差较小.如果您编写了一个模拟钢琴的程序,则需要低延迟,以便用户只要按一下屏幕上的键,就可以感觉到设备正在播放声音.当然,您也将需要高质量.但是,在您追求更高的质量和更低的延迟时需要进行权衡,就像在追求低质量和高延迟的时候需要进行权衡.
为了产生更高质量的声音,您将需要更高的采样率.如果提高用于播放声音的采样率,则可能需要增加缓冲区的大小(以便消耗更多的内存),或者需要更频繁地填充和提供较小的缓冲区(因此需要更多的CPU时间)正在消耗).较低的质量会占用更少的内存和更少的CPU时间,但负面影响显而易见.您的程序听起来不那么好.如果您希望降低等待时间,则需要使用较小的缓冲区,但这也意味着DynamicSoundEffectInstance
会更频繁地请求新缓冲区(再次增加CPU时间).我对声音质量的建议是针对足够好的东西.不要以48KHz的采样率开始.而是从22KHz或更低的频率开始,然后看看它对您的效果如何.至于使用XNA程序的延迟,请争取由游戏FPS确定的延迟.如果您的游戏以每秒30帧的速度运行,则应使缓冲区足够大以播放1/30秒的声音.声音也可以是立体声或单声道.毋庸置疑,以立体声产生声音比单声道需要两倍的内存.
现在让我们假设我们正在创建一个" DynamicSoundEffectInstance",其单声道采样率为22KHz.我们可以用以下方法实例化一个:
var dynamicSoundEffectInstance = new DynamicSoundEffectInstance(22000,AudioChannels.Mono);
我们可以通过以下两种方法之一来计算缓冲区的大小. DynamicSoundEffectInstance
总是播放16位声音样本(2个字节).如果我希望能够以22KHz的采样率播放1/30秒的声音,则此缓冲区所需的字节数将为22000 (1/30) 2 * 1 =1466.等式(2 * 1)是样本中的字节数乘以要播放的通道数.如果我正在播放立体声样本,第二个数字将是2而不是1.我本来可以要求DynamicSoundEffectInstance
计算所需缓冲区的大小.
22000*(1d/30d)*dynamicSoundEffectInstance.GetSampleSizeInBytes
(TimeSpan.FromSeconds(1d/30d))
填充缓冲区
放入缓冲区的数据来自您正在播放的声音.如果您一直在精读,那么您可能已经注意到我说过DynamicSoundEffectInstance
消耗字节数组(8位),但是音频必须由16位样本组成.在C ++中,可能只是将一个数组传递给保存数据的任何对象.即使这样做没有任何意义,它也会让您做到这一点.在C#语言中,也可以通过将其代码包装在"不安全"块中来实现.但是许多人认为包装在"不安全"块中的代码可能不安全(我想知道为什么). Silverlight不会让您这样做.因此,有必要使用其他方法将您的16位数据转换为字节数据.有一种方法可以执行此操作,但我还将介绍如何手动执行此操作.
一个16位(两个字节)的数字有一个高位字节和一个低位字节.高阶和低阶也可以被认为是较高和较低的.在十进制数字39中,三个数字比九个数字更重要;它对最终价值的影响更大.相同的概念转换为由字节组成的数字.我们的字节需要具有小的字节序排序.需要将低位字节放置在数组中高位字节之前.可以使用位掩码选择低位字节.带移位的高位字节.
byte lowOrder = (byte)(SomeNumber & 0xFF);
byte highOrder = (byte)(SomeNumber >> 0x08);
既然您知道需要做什么,那么这里是实用程序方法,该方法实际上将执行相同的操作.
Buffer.BlockCopy(
sourceBuffer
, sourceStartIndex
, destinationBuffer
, destinationStartIndex
, ByteCount)
在这种情况下," sourceBuffer"元素将是16位整数的数组. " destinationBuffer"将是目标字节缓冲区.有两件事要注意.首先,目标缓冲区必须具有两倍于源缓冲区的元素数(因为字节是短整数大小的一半).其次,最后一个参数是要复制的字节数,而不是元素数.如果您弄错了,您将得到IndexOutOfRange
异常或听起来很糟糕的东西.
开始播放声音
一旦" DynamicSoundEffectInstance"具有缓冲区,我将调用"播放"来使事情滚动.
提交缓冲区到DynamicSoundEffectInstance
" DynamicSoundEffectInstance"具有一个名为" BufferNeeded"的事件,当对象准备播放更多声音数据时将调用该事件.如果要编写XNA程序,则可能要避免该对象到达需要调用它的地步.您可以通过以消耗类数据的速率来提供类数据,从而减少开销.可以通过使缓冲区足够大以播放尽可能多的声音(在游戏循环的一个周期中播放)来轻松实现此目的.如果要创建Silverlight应用程序,则将利用此事件.从我发现的结果来看,DynamicSoundEffectInstance
类最多可以容纳两个缓冲区.从一个播放,然后在另一个播放.因此,我更喜欢制作三个缓冲区,以便在第三个缓冲区中可以渲染下一个声音块.当调用BufferNeeded事件时,它会填充缓冲区并通过SubmitBuffer方法传递缓冲区.我以循环方式使用相同的缓冲区.
FrameworkDispatcher.Update()
仅当您在Silverlight中使用该类时才需要.在播放声音之前,至少需要一次调用FrameworkDispatcher.Update,并且必须继续定期进行调用. Windows Phone文档已经逐步完成了将要执行此操作的类.看一下这篇文章,看看这个类是如何工作的.
我的声音功能和频率控制
虽然传递给DynamicSoundEffectInstance
的声音数据必须为16位整数有符号,但我想使声音生成功能与该约束保持解耦,并且与正在播放的特定频率保持解耦.我在创建的名为" SoundManager"的类中实现了这些目标.尽管" SoundManager"包含生成正弦波的代码,但实际使用的声音功能已分配给属性" SoundFunction".只需为该属性分配其他功能即可产生不同的声音.
为了使函数与数据格式脱钩,我创建了程序,以便它希望声音函数将其数据作为" double"返回.声音功能返回的值范围应在[-1..1]范围内.我不会进行范围检查来避免开销(因此,如果您使用我的代码,则要确保代码的行为由您决定).该函数使用两个参数:代表时间的double值和代表通道的整数值.对于左通道,该通道大概为" 0",对于右通道,该通道可能为" 1".为了产生单声道声音,可以忽略此参数. “时间"参数指示正在请求声波周期的哪一部分.声音函数从0到1''返回的值将持续一个声音周期.从
1'‘到2
将是声音的第二个值,依此类推.由于"时间"参数用于表示一个周期内的位置,而不是实际的"时间”,因此声音功能与生成的实际频率是隔离的.我可以通过增大或减小所传递的"时间"值之间的间隔来更改播放声音的频率.间隔越短,频率越低.间隔越大,频率越高.请注意,您可以创建的最高频率将不高于采样率的一半.因此,使用22
KHz采样率,您将只能生成频率分量高达1'‘1'‘KHz的声音.鉴于我们听到的大多数声音是声音成分的复杂混合,请记住,某些频率成分可能会比公认的主导频率高.因此,以较高的频率播放此类声音可能会导致某些高频成分被剥离.您可以在奈奎斯特速率主题下找到有关此概念的更多信息.
方法" FillBuffer"将为需要填充下一个缓冲区的每个样本调用此函数.
double MySin(double time, int channel) { return Math.Sin(time*Math.PI*2.0d); }
填充声音缓冲区的代码如下:
void FillBuffer()
{
if (SoundFunction == null)
throw new NullReferenceException("SoundFunction");
byte[] destinationBuffer = _audioBufferList[CurrentFillBufferIndex];
if (++CurrentFillBufferIndex >= _audioBufferList.Length)
CurrentFillBufferIndex = 0;
short result;
int currentBufferIndex = 0;
int deltaBufferIndex = ChannelCount * BytesPerSample;
for (int i = 0; i < destinationBuffer.Length / (ChannelCount * BytesPerSample); ++i)
{
int baseIndex = ChannelCount * BytesPerSample * i;
//currentBufferIndex = 0;
for (int c = 0; c < ChannelCount; ++c)
{
result = (short)(MaxWaveMagnitude * SoundFunction(_Time, c));
#if(MANUAL_COPY)
destinationBuffer[baseIndex + currentBufferIndex] = (byte)(0xFF & result);
destinationBuffer[baseIndex + currentBufferIndex] =
(byte)(0xFF & (result >> 0x8));
currentBufferIndex += deltaBufferIndex;
#else
_renderingBuffer[i * ChannelCount + c] = result;
#endif
}
_Time += _deltaTime;
}
#if(!MANUAL_COPY)
Buffer.BlockCopy(_renderingBuffer, 0, destinationBuffer,
0, _renderingBuffer.Length*sizeof(short));
#endif
OnPropertyChanged("Time");
OnPropertyChanged("PendingBufferCount");
}
如果部署此条目所附的代码,您将拥有一个可以播放正弦波的程序.很无聊,我知道.但是我想保持我在第一次提到" DynamicSoundEffectInstance"时所播放的声音很简单.下次我提到它时,我想谈谈生成更复杂的声音,并且可能不会在引用此条目之外使用该类本身. CodeProject
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# Windows-Phone-7 新闻 翻译