C#中实现真正低级别的声音生成?

11

有没有合理的方法可以在C#中创建任意声波并从扬声器播放?

多年来,这个问题一直不断地出现,我总是在找不到解决方案后失败了很多次。

我想要做的就像是一个反向可视化工具,也就是说,我不想从声音中生成“数字”,我希望从数字中生成声音。

比如说,我提供一个函数,它需要采样率、采样大小和声音数据(例如一个整数数组),然后它会根据这些信息生成相应的wav文件(实时声音回放最理想,但这样也完全可以)。

我知道wav文件规格已经普及全网,并尝试创建上述函数,对于低频率的成功了一些,但一旦开始涉及位数等参数,就变得非常混乱和难以控制。

这种情况不是已经有别的方法实现了吗? 我无所谓它使用什么方法,只要有.NET托管包装器(我能够在最新版本的VS中访问)就行。 XNA不支持以这种方式进行底层音频处理。我也找到了几个声称能够实现类似功能的示例,但它们要么根本不起作用,要么就是做了完全不同的事情。

谢谢。

3个回答

8
这看起来很有趣,所以我制作了一个简单的应用程序,它可以:
  • 创建纯音(440Hz A)的两秒样本。
  • 将它们转换为WAV文件格式的字节数组。
  • 通过将字节数组传递给PlaySound API播放声音。
  • 还包括保存WAV数据到WAV文件的代码。
您可以轻松更改采样率、音调频率和采样持续时间。代码非常丑陋且空间效率低下,但它能正常工作。以下是完整的命令行应用程序:
使用System;
使用System.Diagnostics;
使用System.IO;
使用System.Runtime.InteropServices;
命名空间playwav { 类Program { [DllImport("winmm.dll", EntryPoint = "PlaySound", SetLastError = true)] 私有的外部静态int PlaySound(byte [] wavData,IntPtr hModule,PlaySoundFlags flags);
//#define SND_SYNC 0x0000 / *同步播放(默认值)* / //#define SND_ASYNC 0x0001 / *异步播放* / //#define SND_NODEFAULT 0x0002 / *如果找不到声音,则保持沉默(!默认值)* / //#define SND_MEMORY 0x0004 / * pszSound指向内存文件* / //#define SND_LOOP 0x0008 / *循环声音,直到下一个sndPlaySound* / //#define SND_NOSTOP 0x0010 / *不要停止任何当前正在播放的声音* /
//#define SND_NOWAIT 0x00002000L / *如果驱动程序忙,则不要等待* / //#define SND_ALIAS 0x00010000L / *名称是注册表别名* / //#define SND_ALIAS_ID 0x00110000L / *别名是预定义的ID* / //#define SND_FILENAME 0x00020000L / *名称是文件名* / //#define SND_RESOURCE 0x00040004L / *名称是资源名称或原子* /
枚举PlaySoundFlags { SND_SYNC = 0x0000, SND_ASYNC = 0x0001, SND_MEMORY = 0x0004 }
//播放出现在字节数组中的wav文件 静态void PlayWav(byte [] wav) { PlaySound(wav,System.IntPtr.Zero,PlaySoundFlags.SND_MEMORY | PlaySoundFlags.SND_SYNC); }
静态byte [] ConvertSamplesToWavFileFormat(short [] left,short [] right,int sampleRate) { Debug.Assert(left.Length == right.Length);
const int channelCount = 2; int sampleSize = sizeof(short)* channelCount * left.Length; int totalSize = 12 + 24 + 8 + sampleSize;
byte [] wav = new byte [totalSize]; int b = 0;
// RIFF头 wav [b ++] =(byte)'R'; wav [b ++] =(byte)'I'; wav [b ++] =(byte)'F'; wav [b ++] =(byte)'F'; int chunkSize = totalSize-8; wav [b ++] =(byte)(chunkSize& 0xff); wav [b ++] =(byte)((chunkSize>> 8)& 0xff); wav [b ++] =(byte)((chunkSize>> 16)& 0xff); wav [b ++] =(byte)((chunkSize>> 24)& 0xff); wav [b ++] =(byte)'W'; wav [b ++] =(byte)'A'; wav [b ++] =(byte)'V'; wav [b ++] =(byte)'E';
//格式头 wav [b ++] =(byte)'f'; wav [b ++] =(byte)'m'; wav [b ++] =(byte)'t'; wav [b ++] =(byte)' '; wav [b ++] = 16; wav [b ++] = 0; wav [b ++] = 0; wav [b ++] = 0; //块大小 wav [b ++] = 1; wav [b ++] = 0; //压缩代码 wav [b ++] = channelCount; wav [b ++] = 0; //通道数 wav [b ++] =(byte)(sampleRate& 0xff); wav [b ++] =(byte)((sampleRate>> 8)& 0xff); wav [b ++] =(byte)((sampleRate

这看起来真的很棒,我感到非常惭愧,但是还没有时间真正去尝试它。只有一个问题:将其设置为每个样本4字节是否容易? - jssyjrm
你可以将每个样本设置为4个字节,但我不知道Windows是否能播放。也许可以,但我不确定。无论如何,如果你想做这个改变,需要将所有对sizeof(short)的引用更改为sizeof(int),将样本类型更改为int,将缩放因子(short.MaxValue)更改为int.MaxValue,并修复填充字节数组的循环以添加每个样本四个字节。但是,如果你能听出区别,我会感到惊讶。 - arx
非常感谢您的提问。在这里,我该如何添加停止(和可能的暂停)功能呢?我猜我需要一个后台工作线程,以便GUI的其余部分可以用于输入。一个“停止声音”的代码会是什么样子呢? - Dan W
@DanW: PlaySound是一个非常简单的API,它只播放声音。如果您想要更复杂的控制,例如暂停和停止,您需要不同的API。有几个win32 API可以完成这项工作,但它们没有内置的.Net包装器。NAudio是一个开源的.Net音频库。我听说过它很好用,但我从未使用过它。 - arx

2
在下方有一个名为“如何从数组中播放”的链接。
    PlayerEx pl = new PlayerEx();

    private static void PlayArray(PlayerEx pl)
    {
        double fs = 8000; // sample freq
        double freq = 1000; // desired tone
        short[] mySound = new short[4000];
        for (int i = 0; i < 4000; i++)
        {
            double t = (double)i / fs; // current time
            mySound[i] = (short)(Math.Cos(t * freq) * (short.MaxValue));
        }
        IntPtr format = AudioCompressionManager.GetPcmFormat(1, 16, (int)fs);
        pl.OpenPlayer(format);
        byte[] mySoundByte = new byte[mySound.Length * 2];
        Buffer.BlockCopy(mySound, 0, mySoundByte, 0, mySoundByte.Length);
        pl.AddData(mySoundByte);
        pl.StartPlay();
    }

2

FMOD可以从内存中加载样本,并且有一个C#包装器。


好的,我这段时间遇到了很多事情,所以还没有进行太多的实验,我很抱歉。FMOD绝对有能力做到这一点,但它的自动生成托管包装器非常糟糕。有一个具体的例子可以使用特定设置来完成此操作,但更改这些设置非常麻烦,并且它迫使开发人员在任何地方都使用不安全的代码。感谢您指出这一点,当我有更多时间时,我会问他们为什么我不能使用每个样本超过2个字节的设置。 - jssyjrm

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