如何将 SoundEffectInstances 组合成一个新的声音文件 /mp3 或 wav

Posted

技术标签:

【中文标题】如何将 SoundEffectInstances 组合成一个新的声音文件 /mp3 或 wav【英文标题】:How to combine SoundEffectInstances into a new Sound File /mp3 or wav 【发布时间】:2011-12-04 12:02:43 【问题描述】:

我正在开发新的 WindowsPhone 平台。我有几个 SoundEffectInstance 实例,我想将它们组合成一个新的单一声音文件(SoundEffectInstance、SoundEffect 或 MediaElement,没关系。)然后我想将该文件作为 mp3 保存到手机中。

我该怎么做?通常,我会尝试将所有文​​件发送到字节数组,但我不确定这是否是正确的方法,或者如何将字节数组转换为 MP3 格式的声音。

例如,我有 SoundEffectInstance soudBackground,从 0 到 5 秒播放。然后我让 SoundEffectInstance 铃声从 3 到 4 秒播放,SoundEffectInstance 前景从 3.5 到 7 秒播放。我想将所有这些组合成一个持续 7 秒的 mp3 文件。

【问题讨论】:

【参考方案1】:

您在这里尝试完成两项任务:

将多个声音文件合并为一个声音文件 将生成的文件另存为 MP3。

就我目前所发现的而言,您将在第 2 项上遇到很多挑战。迄今为止,我还没有找到纯 .Net MP3 编码器。我发现的所有代码都依赖于 P/Invokes 到本机代码(这当然不适用于手机)。

至于合并文件,您不想将它们视为 SoundEffectInstance。该类仅用于播放,它抽象了声音文件的大部分细节。相反,您需要将声音文件视为整数数组。我将假设所有三个声音文件的采样率完全相同,并且这些都是 16 位录音。我还将假设这些波形文件是以单声道录制的。我现在保持场景简单。在您掌握了这个更简单的场景之后,您可以使用立体声和各种采样率对其进行扩展。

波形文件的前 48 个字节只是标题。跳过它(暂时)并将波形文件的内容读入它们自己的数组。一旦它们都被读取,我们就可以开始将它们混合在一起。如果我们想开始生成一个样本,它是所有三个的组合结果,则忽略开始播放这些声音的时间差异,我们可以通过将声音文件数组中的值相加并将其写入数组来实现保持我们的结果。但是有一个问题。 16 位数字最多只能达到 32,767(下降到 -32,768)。如果所有三种声音的组合值超出这些限制,您将得到非常糟糕的失真。处理这个问题的最简单(尽管不一定是最好的)方法是考虑将播放的同时声音的最大数量并相应地缩小值。从 3.5 秒到 4 秒,您将播放所有三种声音。所以我们将通过除以三来缩放。另一种方法是使用可以超出此范围的数据类型对声音样本求和,然后在将它们混合在一起后将值归一化回此范围。

让我们定义一些参数。

int SamplesPerSecond = 22000;
int ResultRecordingLength = 7;
short[] Sound01;
short[] Sound02;
short[] Sound03;
int[] ResultantSoundBuffer;
short[] ProcessedResultSoundBuffer;

//Insert code to populate sound array's here. 
// Sound01.Length will equal 5.0*SamplesPerSecond
// Sound02.Length will equal 1.0*SamplesPerSecond
// Sound03.Length will equal 3.5*SamplesPerSecond

ResultantSound = new int[ResultRecordingLength*SamplesPerSecond];

一旦您读取了声音文件并准备好接收结果文件的数组,您就可以开始渲染了。我们有几种方法可以解决这个问题。这是一个:

void InitResultArray(int[] resultArray)

   for(int i=0;i<resultArray.Length;++i)
   
      resultArray[i]=0;
   


void RenderSound(short[] sourceSound, int[] resultArray, double timeOffset)

   int startIndex = (int)(timeOffset*SamplesPerSecond);
   int readIndex = 0;
   for(int readIndex=0;((readIndex<sourceSound.Length)&&(readIndex+sourceSound<resultArray.Length;++readIndex)
   
      resultArray[readIndex+startIndex] += (int)sourceSound[readIndex];
   


 RangeAdjust(int[] resultArray)

   int max = int.MinimumValue;
   int min = int.MaximumValue;
   for(int i=0;i<resultArray;++i)
   
     max = Math.Max(max, resultArray[i]);
     min = Math.Min(min, resultArray[i]);
   
   //I want the range normalized to [-32,768..32,768]
   //you may want to normalize differently.
   double scale = 65536d/(double)(max-min);
   double offset = 32767-(max*scale);
   for(int i=0;i<resultArray.Length;++i)
   
      resultArray[i]= (scale*resultArray[i])+offset;
   

您将调用 InitResultAttay 以确保结果数组填充为零(我相信这是默认情况下,但我仍然更喜欢将其显式设置为零),然后为您想要的每个声音调用 RenderSound() 结果.渲染声音后,调用 RangeAdjust 来标准化声音。剩下的就是将其写入文件。您需要从整数转换回短裤。

short[] writeBuffer = new short[ResultantSound.Length];
for(int i=0;i<writeBuffer.Length;++i)
   writeBuffer[i]=(short)ResultantSound[i];

现在混合的声音已经准备好写入文件了。只缺少一件事,您需要在写入文件之前写入 48 字节的波头。我在这里写了如何做到这一点的代码:http://www.codeproject.com/KB/windows-phone-7/WpVoiceMemo.aspx

【讨论】:

谢谢 Joel... 这一切都可以在手机上完成吗?我开始相信这是不可能的。如何从声音文件中获取位数组?还是必须从网上下载才能做到这一点? Wave 文件的来源并不重要。它可以来自网络,与您的项目一起部署,或者只要您的代码可以访问它们。是的,这一切都可以在手机上实现。但是天已经晚了,我必须跳上我的车与当地的交通进行战斗。本周将尝试写一篇更详细的文章。 如何从声音文件中获取位数组?等待更多细节!请 @arsenium - 通常你会首先读取文件的前 48 个字节。它包含的信息包括文件的长度,样本是 8 位还是 16 位,以及声音具有的声道数(通常为 1 或 2,但对于环绕声可以更多)。紧随这 48 个字节的是示例数据。 @Shafiq Abbas - 我认为您需要分享更多细节才能调用补救措施。如果你有一个解决方案,如果你也分享它会对社区有所帮助。

以上是关于如何将 SoundEffectInstances 组合成一个新的声音文件 /mp3 或 wav的主要内容,如果未能解决你的问题,请参考以下文章

如何将CString转换成wstring

如何将Ios文件上传到

Qt如何将文字变成图片?

如何将Bitmap保存为本地图片文件?

在MATLAB中如何将图导出

ASP如何将SQLSERVER数据导出到DBF(VF)