使用C# System.Media库开发键盘钢琴的重复播放及自定义声音问题
问题一:按键播放声音重复的解决办法
你的代码里用同一个SoundPlayer实例在KeyDown事件中重复调用Play(),而KeyDown会在按键按住时持续触发,同时SoundPlayer.Play()是异步播放,这就导致多次触发播放请求,声音重叠重复。可以通过以下几种方式解决:
避免按键重复触发:添加一个播放状态标志,确保同一时间只播放一次:
System.Media.SoundPlayer media = new System.Media.SoundPlayer("C.wav"); private bool _isPlaying = false; private void Form1_KeyDown(object sender, EventArgs e) { if (!_isPlaying) { _isPlaying = true; media.Play(); // 播放完成后重置标志并移除事件绑定 media.PlayCompleted += (s, args) => { _isPlaying = false; ((SoundPlayer)s).PlayCompleted -= (s, args); }; } }每次播放前停止当前声音:如果需要快速切换按键声音,每次播放前停止之前的播放:
System.Media.SoundPlayer media = new System.Media.SoundPlayer("C.wav"); private void Form1_KeyDown(object sender, EventArgs e) { media.Stop(); media.Play(); }每次创建新的SoundPlayer实例:避免复用实例导致的播放冲突:
private void Form1_KeyDown(object sender, EventArgs e) { using (var media = new System.Media.SoundPlayer("C.wav")) { media.Play(); } }
问题二:BinaryWriter编写自定义WAV文件无法播放的解决办法
WAV文件有严格的格式规范,必须包含RIFF头、fmt子块和data子块,缺少任何部分或格式错误都会导致无法播放。以下是符合标准的PCM WAV文件编写示例:
using System; using System.IO; using System.Text; // 生成可播放的自定义WAV文件 void CreateCustomWavFile(string filePath, double frequency, int durationMs = 1000) { using (var fs = new FileStream(filePath, FileMode.Create)) using (var writer = new BinaryWriter(fs)) { // 1. RIFF 头部 writer.Write(Encoding.ASCII.GetBytes("RIFF")); var riffSizePos = fs.Position; writer.Write(0); // 先占位,最后填充总大小 writer.Write(Encoding.ASCII.GetBytes("WAVE")); // 2. fmt 子块(PCM格式) writer.Write(Encoding.ASCII.GetBytes("fmt ")); writer.Write(16); // PCM格式的子块大小固定为16 writer.Write((short)1); // 音频格式:PCM=1 writer.Write((short)1); // 声道数:单声道 int sampleRate = 44100; writer.Write(sampleRate); // 采样率 int bitDepth = 16; int byteRate = sampleRate * 1 * bitDepth / 8; writer.Write(byteRate); // 字节率 short blockAlign = (short)(1 * bitDepth / 8); writer.Write(blockAlign); // 块对齐 writer.Write((short)bitDepth); // 位深度 // 3. data 子块 writer.Write(Encoding.ASCII.GetBytes("data")); var dataSizePos = fs.Position; writer.Write(0); // 先占位,最后填充数据大小 // 生成音频采样数据(以440Hz正弦波为例) int sampleCount = (int)(sampleRate * durationMs / 1000); for (int i = 0; i < sampleCount; i++) { double angle = 2 * Math.PI * frequency * i / sampleRate; short sample = (short)(Math.Sin(angle) * short.MaxValue); writer.Write(sample); // 写入16位采样数据 } // 填充RIFF总大小:文件总长度 - 8字节(RIFF标识和大小本身的长度) fs.Position = riffSizePos; writer.Write((int)(fs.Length - 8)); // 填充data子块大小:文件总长度 - data子块起始位置 -4字节(data标识和大小的长度) fs.Position = dataSizePos; writer.Write((int)(fs.Length - dataSizePos - 4)); } } // 调用示例:生成1秒的440Hz音频 CreateCustomWavFile("custom.wav", 440);
需要注意的关键点:
- 所有数值必须使用小端字节序,
BinaryWriter默认符合要求 - 必须准确计算RIFF块大小、data块大小,这是最容易出错的地方
- 音频数据的格式要和fmt子块中声明的声道数、位深度一致,比如16位单声道每个采样是2字节的短整型
内容的提问来源于stack exchange,提问作者Echrem
相关产品推荐
相关产品推荐

