在 C# 中播放动态创建的简单声音,无需外部库

Posted

技术标签:

【中文标题】在 C# 中播放动态创建的简单声音,无需外部库【英文标题】:Play dynamically-created simple sounds in C# without external libraries 【发布时间】:2012-08-01 22:50:57 【问题描述】:

我需要能够使用 C# 动态生成波形并播放它,无需任何外部库,也无需将声音文件存储在硬盘上。延迟不是问题;声音会在应用程序需要它们之前生成。

实际上,如果不是因为 Microsoft 表示 64 位版本的 Windows 不支持它,Console.Beep() 方法可能会满足我的需求。

如果我动态地生成自己的声音,我可以得到比简单的哔声更花哨的声音。例如,我可以从频率从 2 KHz 增加到 4 KHz 的三角波制作波形,同时音量衰减。我不需要花哨的 16 位立体声,只需 8 位单声道就可以了。我不需要对音量和音高进行动态控制,基本上只是在内存中生成一个声音文件并播放它而不存储它。

上一次我需要生成声音是在很多年前,在 Apple II、HP 工作站和我的旧 Amiga 电脑上。从那以后就不需要这样做了,似乎我描述的一些简单的事情变得更加复杂了。我很难相信如此简单的事情似乎如此困难。我看到的大多数答案都是指 NAudio 或类似的库,这不是该项目的选择(除了拉入整个库只是为了播放音调似乎是一种浪费)。

【问题讨论】:

【参考方案1】:

根据我收到的答案中的一个链接,以及我发现的有关 .wav 标头格式的其他一些页面,这是我的一个小类的工作代码,它生成一个 8 位“叮!”具有用户指定的频率和持续时间的声音。它基本上是在指定的持续时间内振幅线性衰减到零的哔哔声。

public class AlertDing 
    private SoundPlayer player = null;
    private BinaryWriter writer = null;

    /// <summary>
    /// Dynamically generate a "ding" sound and save it to a memory stream
    /// </summary>
    /// <param name="freq">Frequency in Hertz, e.g. 880</param>
    /// <param name="tenthseconds">Duration in multiple of 1/10 second</param>
    public AlertDing(double freq, uint tenthseconds) 

        string header_GroupID = "RIFF";  // RIFF
        uint header_FileLength = 0;      // total file length minus 8, which is taken up by RIFF
        string header_RiffType = "WAVE"; // always WAVE

        string fmt_ChunkID = "fmt "; // Four bytes: "fmt "
        uint fmt_ChunkSize = 16;     // Length of header in bytes
        ushort fmt_FormatTag = 1;        // 1 for PCM
        ushort fmt_Channels = 1;         // Number of channels, 2=stereo
        uint fmt_SamplesPerSec = 14000;  // sample rate, e.g. CD=44100
        ushort fmt_BitsPerSample = 8;   // bits per sample
        ushort fmt_BlockAlign =
            (ushort)(fmt_Channels * (fmt_BitsPerSample / 8)); // sample frame size, in bytes
        uint fmt_AvgBytesPerSec =
            fmt_SamplesPerSec * fmt_BlockAlign; // for estimating RAM allocation

        string data_ChunkID = "data";  // "data"
        uint data_ChunkSize;           // Length of header in bytes
        byte [] data_ByteArray;

        // Fill the data array with sample data

        // Number of samples = sample rate * channels * bytes per sample * duration in seconds
        uint numSamples = fmt_SamplesPerSec * fmt_Channels * tenthseconds / 10;
        data_ByteArray = new byte[numSamples];

        //int amplitude = 32760, offset=0; // for 16-bit audio
        int amplitude = 127, offset = 128; // for 8-audio
        double period = (2.0*Math.PI*freq) / (fmt_SamplesPerSec * fmt_Channels);
        double amp;
        for (uint i = 0; i < numSamples - 1; i += fmt_Channels) 
            amp = amplitude * (double)(numSamples - i) / numSamples; // amplitude decay
            // Fill with a waveform on each channel with amplitude decay
            for (int channel = 0; channel < fmt_Channels; channel++) 
                data_ByteArray[i+channel] = Convert.ToByte(amp * Math.Sin(i*period) + offset);
            
        

        // Calculate file and data chunk size in bytes
        data_ChunkSize = (uint)(data_ByteArray.Length * (fmt_BitsPerSample / 8));
        header_FileLength = 4 + (8 + fmt_ChunkSize) + (8 + data_ChunkSize);

        // write data to a MemoryStream with BinaryWriter
        MemoryStream audiostream = new MemoryStream();
        BinaryWriter writer = new BinaryWriter(audioStream);

        // Write the header
        writer.Write(header_GroupID.ToCharArray());
        writer.Write(header_FileLength);
        writer.Write(header_RiffType.ToCharArray());

        // Write the format chunk
        writer.Write(fmt_ChunkID.ToCharArray());
        writer.Write(fmt_ChunkSize);
        writer.Write(fmt_FormatTag);
        writer.Write(fmt_Channels);
        writer.Write(fmt_SamplesPerSec);
        writer.Write(fmt_AvgBytesPerSec);
        writer.Write(fmt_BlockAlign);
        writer.Write(fmt_BitsPerSample);

        // Write the data chunk
        writer.Write(data_ChunkID.ToCharArray());
        writer.Write(data_ChunkSize);
        foreach (byte dataPoint in data_ByteArray) 
            writer.Write(dataPoint);
        
        player = new SoundPlayer(audioStream);
    

    /// <summary>
    /// Call this to clean up when program is done using this sound
    /// </summary>
    public void Dispose() 
        if (writer != null) writer.Close();
        if (player != null) player.Dispose();
        writer = null;
        player = null;
    

    /// <summary>
    /// Play "ding" sound
    /// </summary>
    public void Play() 
        if (player != null) 
            player.Stream.Seek(0, SeekOrigin.Begin); // rewind stream
            player.Play();
        
    

希望这可以帮助其他尝试动态生成简单警报声音而不需要声音文件的人。

【讨论】:

除非它不会编译。 data_ByteArray 在使用前未初始化,以及其他问题。 对不起,data_ShortArray 应该到处都是 data_ByteArray。当我在处理它时发布一些东西时会发生这种情况。我修复了上面的代码。对我来说效果很好。 如果我使用您的新代码启用 16 位音频,我会收到 OverflowException。不过它正在到达那里。 如果启用16位音频,则需要将byte [] data_ByteArray更改为short [] data_ShortArray,将其分配为短数组,并将代码中所有出现的data_ByteArray替换为data_ShortArray。我的目的是产生一个简单的波形声音,为此不需要 16 位音频的动态范围; 8位绰绰有余。如果我在 C 中执行此操作,我只需分配一个适当大小的字符数组,然后将数组指针转换为适当的字节或短。我对 C# 不够熟悉,不知道我是否可以在这里做到这一点。【参考方案2】:

这可能会有所帮助:http://channel9.msdn.com/coding4fun/articles/Generating-Sound-Waves-with-C-Wave-Oscillators

【讨论】:

作者要求不要使用任何外部库 @RamanZhylich:该代码中涉及的唯一外部库是 DirectSound,它已经存在于每个 Windows 安装中(可能除了一些较新的服务器版本,但我认为你不会无论如何都想在那里播放声音)。它实际上只是对已经存在的 DLL 的几次调用。 DirectSound 并不是真正的外部设备,因为它是随 Windows 一起安装的。 谢谢,我在搜索中也看到了该页面,但不确定 DirectSound 库是否保证可用。 某些 Windows 2000 服务包默认包含 DX3。很确定至少 DX7 将安装在 WinXP 及更高版本上【参考方案3】:

以下文章解释了如何使用 SoundPlayer 生成和播放 *.wav 文件。请注意,SoundPlayer 可以将流作为参数,因此您可以在 MemoryStream 中生成 wav 文件内容并避免保存到文件中。

http://blogs.msdn.com/b/dawate/archive/2009/06/24/intro-to-audio-programming-part-3-synthesizing-simple-wave-audio-using-c.aspx

【讨论】:

嗯。我之前看到过,当我看到它需要将 wav 数据保存到文件时忽略了它。我忘记了 SoundPlayer 可以首先接受流。但是,天哪,这肯定是一个复杂的解决方案来播放简单的哔哔声。不过我可以接受。【参考方案4】:

我试用了从 Anachronist (2012-10) 截取的代码 - 它对我有用。

对我来说最大的障碍: 摆脱“AlertDing”wav 结尾系统性的“咔哒声”。

这是由被截断的代码中的“软错误”引起的:

for (uint i = 0; i &lt; numSamples - 1; i += fmt_Channels)

需要改成

for (uint i = 0; i &lt; numSamples; i += fmt_Channels)

如果不更改,每次“播放”结束时会产生一个系统的“零”,从而产生尖锐的点击噪音。 (= 幅度跳跃 0->min->0)

原来的问题当然意味着“没有点击噪音”:)

【讨论】:

如果 fmt_Channels 始终 == 1,这将起作用。如果您正在制作立体声,则 fmt_Channels = 2,您的更改将导致数组边界错误。循环测试结束应该是 i

以上是关于在 C# 中播放动态创建的简单声音,无需外部库的主要内容,如果未能解决你的问题,请参考以下文章

音频设备测试无法播放声音

无需使用外部库即可处理声音

在没有外部模块的情况下在 Python 中播放声音?

【c++】怎样才能使程序播放文件中的声音?

如何在python中动态显示文本时在后台播放声音?

C#中如何调用动态链接库DLL