如何从实时流中播放音频

时间:2009-08-19 20:48:19

标签: c# multimedia

我有一个程序可以产生应该同时播放的音频信号。为此,我在每100 ms周期内播放100 ms的音频流间隔。但是我在每个100毫秒音频流的开始和结束时都有不需要的信号(因为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
            }
        }
     }
}

3 个答案:

答案 0 :(得分:4)

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

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

顺便说一句 - 我想你知道.Net的Managed Direct X库已不再开发,对于这种音频开发来说实际上是一个死胡同吗?

答案 1 :(得分:1)

此代码存在许多问题。我猜测当你运行这段代码时,你会听到每100毫秒点击或弹出声音。这是因为在while(true)循环内调用了Thread.Sleep(100)。基本上,你的应用程序正在等待100毫秒(给予或花费一小段时间),然后调用Play(),它会进行一些处理,然后将阵列排队等待播放。结果,每个100毫秒数组的播放之间存在一点时间间隔,这产生了点击。

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

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

此外,即使您每隔100毫秒摆脱一次点击,您仍然无法听到扬声器发出的任何声音,因为您的代码将每个样本值设置为1000.如果随时间改变样本值。顺便说一下,你听到点击的唯一原因是因为这个样本值被设置为1000,并且在块之间的那些小时间间隔内,回放值变回0.如果你将每个样本值设置为0,你将永远不会听到任何声音。

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

答案 2 :(得分:0)

如果通过“不良信号”表示两端都有轻微的砰砰声,则可能是信封问题,在Csound中可能会受到“亚麻”操作码或类似操作的控制。这个想法是你需要提高前端的振幅,然后稍微向后踩下后端,以避免扬声器的咔哒声在中波突然停止输出,可以这么说。几毫秒就足够了 - 试试它,直到你摆脱弹出而没有注意到幅度调制。

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

如果您试图通过以规则的间隔顺序连接相同的波形来产生无缝信号,那么您将始终听到这种砰砰声,因为一个波形的末端不与下一个波形的开头对齐。让波形准确排列非常困难,这并不是一个好的策略。一个更好的策略是使用一个包络(如上所述)并重叠波形(称为dove-tailing),以便旧关节的衰减与新关节的上升同时发生。

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