如何从实时流中播放音频

6

我有一个程序可以生成音频信号,这些信号应该同时播放。为此,我在每个100毫秒周期中播放100毫秒的音频流间隔。但是,在每个100毫秒音频流的开头和结尾都会出现不期望的信号(因为直流偏置),导致输出声音不平滑,即使信号值相同也是如此。我的代码附在下面。请帮我找出正确实时音频所需的解决方法。

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个回答

4
如果你正在寻找一种通过定义流播放音频的方法,那么你是否考虑过NAudio http://naudio.codeplex.com/呢?
你可以定义一个流,从文件或其他位置(例如内存)中读取数据,并将所需的数据填充到流中以便播放。只要你能够在读取指针到达缓冲区末尾之前继续向流提供音频数据,就不会听到这些生成音频中的杂音。
顺便说一下 - 我假设你知道为.Net开发的托管DirectX库已经不再维护,对于这种音频开发来说是一个死胡同了?

1
这段代码有很多问题。我猜测当你运行这段代码时,你会听到每100毫秒一次的点击或爆裂声。这是因为在while(true)循环内调用了Thread.Sleep(100)方法。基本上,你的应用程序会等待100毫秒(再加上一点时间),然后调用Play()方法,它会进行一些处理,然后将数组排队进行播放。因此,在每个100毫秒数组的播放之间会有一个小的时间间隔,这就产生了点击声。
然而,如果你注释掉Thread.Sleep(100)这一行,你的应用程序将进入一个无限循环,不断地在100毫秒数组之后队列中排队,直到你的内存用完。但至少,播放不会每100毫秒出现伪影。
如果你将这一行改为Thread.Sleep(80),它会稍微好一点,因为你需要更长时间才能用完内存,但仍然会发生这种情况,因为你仍然会比系统播放它们的速度更快地向音频播放系统倾倒缓冲区。
此外,即使您每100毫秒消除了点击声,您仍然听不到扬声器中的任何声音,因为您的代码将每个采样值设置为1000。只有在随时间变化时才会听到任何声音。顺便说一句,您听到点击声的唯一原因是因为该采样值设置为1000,并且在块之间的那些小时间间隔内,播放值返回到0。如果将每个采样值设置为0,则根本听不到任何声音。
我可以帮助您进一步解决此问题,但我需要更好地了解您究竟要做什么。您是否试图以某个频率播放连续音调?

0
如果你所说的“不良信号”是指两端出现轻微的爆裂声,那么可能是一个包络问题,在Csound中可以通过“linen”操作码或类似的东西来控制。其思想是你需要在前端逐渐增加振幅,在后端略微降低振幅,以避免扬声器突然停止输出中波浪的点击声,试着用几毫秒就可以了,直到你消除了爆裂声而没有注意到振幅调制。
看这里:http://www.csounds.com/journal/issue11/csoundEnvelopes.html 如果你试图通过在规律间隔时间内顺序地表达相同的波形来产生无缝信号,那么你总会听到这种爆裂声,因为一个波形的末尾与下一个波形的开头不对齐。让波形精确对齐非常困难,这并不是一个好策略。更好的策略是使用包络(如上所述)和重叠波形(称为鸽尾),使旧的表达的衰减与新的表达的上升同时发生。

然而,这种策略不会产生完全纯净的声音,因为两个相似的波形异步重叠会在每个波形的交汇处相互抵消一些,并导致振幅降低。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接