You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 05:27:51