在C#中创建一个.wav文件

16
作为学习C#的借口,我一直在尝试编写一个简单的项目:创建音频文件。首先,我想确保我能够编写符合WAVE格式的文件。我已经在线上研究了该格式(例如here),但每当我尝试播放文件时,它都无法正确打开。以下是我的代码。是否有什么遗漏或不正确的地方?
uint numsamples = 44100;
ushort numchannels = 1;
ushort samplelength = 1; // in bytes
uint samplerate = 22050;

FileStream f = new FileStream("a.wav", FileMode.Create);
BinaryWriter wr = new BinaryWriter(f);

wr.Write("RIFF");
wr.Write(36 + numsamples * numchannels * samplelength);
wr.Write("WAVEfmt ");
wr.Write(16);
wr.Write((ushort)1);
wr.Write(numchannels);
wr.Write(samplerate);
wr.Write(samplerate * samplelength * numchannels);
wr.Write(samplelength * numchannels);
wr.Write((ushort)(8 * samplelength));
wr.Write("data");
wr.Write(numsamples * samplelength);

// for now, just a square wave
Waveform a = new Waveform(440, 50);

double t = 0.0;
for (int i = 0; i < numsamples; i++, t += 1.0 / samplerate)
{
    wr.Write((byte)((a.sample(t) + (samplelength == 1 ? 128 : 0)) & 0xff));
}

你说的“无法正确打开”是什么意思?请提供具体的错误或异常信息。 - Mohammad Dehghan
当我尝试播放上述代码时,它会产生以下错误信息:“Windows Media Player 无法播放该文件。播放器可能不支持该文件类型或可能不支持用于压缩文件的编解码器。”但是,当我将文本输出更改为逐个编写每个字符时(我猜它们被视为字符串,它们将空终止符放入其中,这会破坏对齐),进行了这些更改后,当我尝试播放它时,会弹出另一条错误消息:“Windows Media Player 在播放文件时遇到问题。” - user2034752
这其实是我的第二次尝试。我的第一次尝试使用了更精确的代码(我使用了一个字节数组并逐个分配每个字节,跟踪字节序),然后将数组内容写入文件。但是这次尝试出现了我提到的第一个错误消息。 - user2034752
你在尝试播放文件之前,是否正确关闭了流? - Igal Tabachnik
WaveForm是什么?来自哪个库? - Matthieu Charbonnier
显示剩余2条评论
4个回答

13

主要问题是:

BinaryWriter.Write(string) 方法会在写入字符串时在前面添加字符串的长度,以便于 BinaryReader 在读取时使用。这个方法并不适用于您的情况。您需要直接写入字节而非使用BinaryWriter.Write(string)

应该怎么做:

将字符串转换成字节然后直接写入字节即可。

byte[] data = System.Text.Encoding.ASCII.GetBytes("RIFF");
binaryWriter.Write(data);

或将其变成一行:

binaryWriter.Write(System.Text.Encoding.ASCII.GetBytes("RIFF"));

还可能存在其他问题,例如你编写的整数可能与所需大小不同。你应该仔细检查它们。

至于字节序,你提供的链接说明数据是小端字节序,并且BinaryWriter使用小端字节序,因此这不应该是一个问题。



我修复了。我觉得我需要用ASCII编码。如我在评论中提到的,我最初尝试使用字节数组并逐个处理每个字节(就像在C中工作一样)。就是在这个设置中,我明确地处理了字节序(通过移位和隔离一个字节并将其存储在正确的索引中)。无论如何,谢谢你。 - user2034752

4

最简单的方法就是直接更改:

wr.Write("RIFF");

to:

wr.Write("RIFF".ToArray());

将字符串写入二进制文件时,它将包含字符串的长度,以便稍后可以将其反序列化回字符串。在这种情况下,您只希望四个字节被写入为四个字节,并将其转换为字符数组即可实现。


3

我没有合适的 WAV 数据,但是请尝试使用以下代码替换你生成头文件的部分(请适当更改):

wr.Write(Encoding.ASCII.GetBytes("RIFF"));
wr.Write(0);
wr.Write(Encoding.ASCII.GetBytes("WAVE"));
wr.Write(Encoding.ASCII.GetBytes("fmt "));
wr.Write(18 + (int)(numsamples * samplelength));
wr.Write((short)1); // Encoding
wr.Write((short)numchannels); // Channels
wr.Write((int)(samplerate)); // Sample rate
wr.Write((int)(samplerate * samplelength * numchannels)); // Average bytes per second
wr.Write((short)(samplelength * numchannels)); // block align
wr.Write((short)(8 * samplelength)); // bits per sample
wr.Write((short)(numsamples * samplelength)); // Extra size
wr.Write("data");

我得到了一个错误(就是我上面提到的第二个错误)。可能是我的数据有问题,但我很确定正确数量的字节被写入文件,所以最坏的情况也只会是产生噪音。我甚至将最后一行更改为与其他字符串相同的格式。 - user2034752
我重新排列了一些东西(例如,我不明白你的块大小,所以我使用了我的),结果实际上起作用了!但我不知道为什么。也许C#或Visual Studio的默认值不是ASCII?不管怎样,谢谢。 - user2034752
是的,我试图制作自己的录音机用于谷歌语音识别(就像一种桌面助手)。我花了几个小时来研究WAV格式的具体细节。很高兴你已经解决了它。 - Jason

1

@Alvin-wong的答案完美无缺。我想再提出另一个建议,尽管需要多写几行:

binaryWriter.Write('R');
binaryWriter.Write('I');
binaryWriter.Write('F');
binaryWriter.Write('F'); 

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