6

我有一个程序可以产生应该同时播放的音频信号。为此,我在每 100 毫秒的周期内播放 100 毫秒的音频流。但是我在每个 100 ms 音频流的开始和结束时都有不想要的信号(因为 DC),所以即使信号值相同,输出声音也不平滑。我的代码附在下面。请帮助我应该怎么做才能获得正确的实时音频。

using System;
using System.Windows.Forms;
using Microsoft.DirectX.DirectSound;
using System.IO;

namespace TestSound
{
    class CSound : Form
    {
        const int HEADER_SIZE = 44;
        const bool FLAG_STEREO = true;
        const short BITS_PER_SAMPLE = 16;
        const int SAMPLE_RATE = 44100;

        int numberOfSamples;
        MemoryStream stream;
        BinaryWriter writer;
        Device ApplicationDevice = null;
        SecondaryBuffer buffer = null;
        BufferDescription description;

        public CSound()
        {
            try
            {
                ApplicationDevice = new Device();
            }
            catch
            {
                MessageBox.Show("Unable to create sound device.");
                ApplicationDevice = null;
                return;
            }
            ApplicationDevice.SetCooperativeLevel(this, CooperativeLevel.Priority);
            description = new BufferDescription();
            description.ControlEffects = false;
            stream = new MemoryStream();
            writer = new BinaryWriter(stream);
        }

        private void AddHeader()
        {
            stream.Position = 0;

            writer.Write(0x46464952); // "RIFF" in ASCII
            writer.Write((int)(HEADER_SIZE + (numberOfSamples * BITS_PER_SAMPLE * (FLAG_STEREO ? 2 : 1) / 8)) - 8);
            writer.Write(0x45564157); // "WAVE" in ASCII
            writer.Write(0x20746d66); // "fmt " in ASCII
            writer.Write(16);
            writer.Write((short)1);
            writer.Write((short)(FLAG_STEREO ? 2 : 1));
            writer.Write(SAMPLE_RATE);
            writer.Write(SAMPLE_RATE * (FLAG_STEREO ? 2 : 1) * BITS_PER_SAMPLE / 8);
            writer.Write((short)((FLAG_STEREO ? 2 : 1) * BITS_PER_SAMPLE / 8));
            writer.Write(BITS_PER_SAMPLE);
            writer.Write(0x61746164); // "data" in ASCII
            writer.Write((int)(numberOfSamples * BITS_PER_SAMPLE * (FLAG_STEREO ? 2 : 1) / 8));
        }

        public void Play(short[] samples)
        {
            if (ApplicationDevice == null)
                return;

            stream.Position = HEADER_SIZE;
            numberOfSamples = samples.Length;
            for (int i = 0; i < numberOfSamples; i++)
            {
                writer.Write(samples[i]);
                if (FLAG_STEREO)
                    writer.Write(samples[i]);
            }
            AddHeader();
            stream.Position = 0;

            try
            {
                if (buffer != null)
                {
                    buffer.Dispose();
                    buffer = null;
                }
                buffer = new SecondaryBuffer(stream, description, ApplicationDevice);
                buffer.Play(0, BufferPlayFlags.Default);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }

        static short[] samples = new short[4410]; // 100 ms
        static CSound sound;

        static void Main()
        {
            Form form = new Form();
            form.Show();

            sound = new CSound();
            Random random = new Random();
            for (int i = 0; i < samples.Length; i++)
                samples[i] = 1000; // constant value

            while (true)
            {
                sound.Play(samples);
                System.Threading.Thread.Sleep(100); // 100 ms
            }
        }
     }
}
4

3 回答 3

4

如果您正在寻找一种通过定义的流播放音频的方法,您是否考虑过 NAudio http://naudio.codeplex.com/

您可以从文件或其他位置(即内存)定义流,然后使用要播放的数据填充流。只要您能够在读取指针到达缓冲区末尾之前继续向流提供音频数据,您就不会在生成的音频中听到这些伪影。

顺便说一句 - 我假设您知道 .Net 的托管 Direct X 库不再被开发,并且实际上是这种音频开发的死胡同?

于 2009-08-19T22:21:06.117 回答
1

这段代码有很多问题。我猜当您运行此代码时,您会每 100 毫秒听到一次咔嗒声或爆裂声。这是因为在 while(true) 循环中调用了 Thread.Sleep(100)。基本上,您的应用程序等待 100 毫秒(给予或花费一小段时间)然后调用 Play(),后者会进行一些处理,然后将数组排队等待播放。因此,每个 100 ms 数组的播放之间会有一点时间间隔,这会产生咔嗒声。

但是,如果您刚刚注释掉 Thread.Sleep(100) 行,您的应用程序将进入无限循环,在 100 ms 数组之后,它会继续排队 100 ms 数组,直到内存不足。但至少播放不会每 100 毫秒就有一次伪影。

如果您将行更改为 Thread.Sleep(80),它会工作得更好一些,因为您需要更长的时间才能耗尽内存,但这仍然会发生,因为您仍然会将缓冲区转储到音频中播放系统的速度比系统播放它们的速度要快。

此外,即使您消除了每 100 毫秒的点击声,您仍然不会从扬声器中听到任何声音,因为您的代码将每个样本值设置为 1000。只有改变随时间变化的样本值。顺便说一句,您听到咔嗒声的唯一原因是因为该样本值设置为 1000,并且在块之间的这些小时间间隔内,播放值会回到 0。如果您将每个样本值设置为 0,您将永远不会什么都听。

我可以进一步帮助您,但我需要更好地了解您正在尝试做什么。您是否尝试以特定频率播放连续音调?

于 2009-08-19T21:33:22.580 回答
0

如果“不良信号”是指两端有轻微的爆裂声,则可能是包络问题,在 Csound 中可以通过“亚麻”操作码或类似的东西来控制。这个想法是你需要增加前端的幅度,然后稍微降低后端的幅度,以避免扬声器的咔嗒声突然停止在中波输出,可以这么说。几毫秒就足够了——用它做实验,直到你在没有注意到幅度调制的情况下摆脱爆音。

看这里:http ://www.csounds.com/journal/issue11/csoundEnvelopes.html

如果您试图通过以固定间隔顺序连接相同波形来产生无缝信号,那么您将始终听到这种爆裂声,因为一个波形的结尾与下一个波形的开头不对齐。让波形精确排列非常困难,这并不是一个好的策略。更好的策略是使用包络(如上所述)并重叠波形(称为鸠尾拖尾),以便旧发音的衰减与新发音的上升同时发生。

然而,这种策略不会产生完全纯净的声音,因为两个稍微相同的波形异步重叠的存在会相互抵消一点,并导致幅度在波形的每个接合点下降。

于 2011-09-07T14:43:16.200 回答