c# - 通过 XAudio2 播放正弦

标签 c# audio slimdx xaudio2

我正在使用 XAudio2 制作音频播放器。我们以 8000Hz 的采样率和 16 字节的采样深度以 640 字节的数据包流式传输数据。我们正在使用 SlimDX 访问 XAudio2。

但是在播放声音的时候,我们发现音质不好。例如,这是一个 3KHz 正弦曲线,用 Audacity 捕获。 3KHz sinus curve

我已经将音频播放器压缩到最基本的水平,但音频质量仍然很差。这是 XAudio2、SlimDX 或我的代码中的错误,还是这只是一个从 8KHz 到 44.1KHz 时出现的伪像?最后一个似乎不合理,因为我们还生成了 Windows Media Player 完美播放的 PCM wav 文件。

下面是基本的实现,它生成了 splinter 的正弦。

public partial class MainWindow : Window
{
    private XAudio2 device = new XAudio2();
    private WaveFormatExtensible format = new WaveFormatExtensible();
    private SourceVoice sourceVoice = null;
    private MasteringVoice masteringVoice = null;
    private Guid KSDATAFORMAT_SUBTYPE_PCM = new Guid("00000001-0000-0010-8000-00aa00389b71");
    private AutoResetEvent BufferReady = new AutoResetEvent(false);

    private PlayBufferPool PlayBuffers = new PlayBufferPool();

    public MainWindow()
    {
        InitializeComponent();

        Closing += OnClosing;

        format.Channels = 1;
        format.BitsPerSample = 16;
        format.FormatTag = WaveFormatTag.Extensible;
        format.BlockAlignment = (short)(format.Channels * (format.BitsPerSample / 8));
        format.SamplesPerSecond = 8000;
        format.AverageBytesPerSecond = format.SamplesPerSecond * format.BlockAlignment;
        format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
    }

    private void OnClosing(object sender, CancelEventArgs cancelEventArgs)
    {
        sourceVoice.Stop();
        sourceVoice.Dispose();
        masteringVoice.Dispose();

        PlayBuffers.Dispose();
    }

    private void button_Click(object sender, RoutedEventArgs e)
    {
        masteringVoice = new MasteringVoice(device);

        PlayBuffer buffer = PlayBuffers.NextBuffer();

        GenerateSine(buffer.Buffer);
        buffer.AudioBuffer.AudioBytes = 640;

        sourceVoice = new SourceVoice(device, format, VoiceFlags.None, 8);
        sourceVoice.BufferStart += new EventHandler<ContextEventArgs>(sourceVoice_BufferStart);
        sourceVoice.BufferEnd += new EventHandler<ContextEventArgs>(sourceVoice_BufferEnd);

        sourceVoice.SubmitSourceBuffer(buffer.AudioBuffer);

        sourceVoice.Start();
    }

    private void sourceVoice_BufferEnd(object sender, ContextEventArgs e)
    {
        BufferReady.Set();
    }

    private void sourceVoice_BufferStart(object sender, ContextEventArgs e)
    {
        BufferReady.WaitOne(1000);

        PlayBuffer nextBuffer = PlayBuffers.NextBuffer();
        nextBuffer.DataStream.Position = 0;
        nextBuffer.AudioBuffer.AudioBytes = 640;
        GenerateSine(nextBuffer.Buffer);

        Result r = sourceVoice.SubmitSourceBuffer(nextBuffer.AudioBuffer);
    }

    private void GenerateSine(byte[] buffer)
    {
        double sampleRate = 8000.0;
        double amplitude = 0.25 * short.MaxValue;
        double frequency = 3000.0;
        for (int n = 0; n < buffer.Length / 2; n++)
        {
            short[] s = { (short)(amplitude * Math.Sin((2 * Math.PI * n * frequency) / sampleRate)) };
            Buffer.BlockCopy(s, 0, buffer, n * 2, 2);
        }
    }
}

public class PlayBuffer : IDisposable
{
    #region Private variables
    private IntPtr BufferPtr;
    private GCHandle BufferHandle;
    #endregion

    #region Constructors
    public PlayBuffer()
    {
        Index = 0;
        Buffer = new byte[640 * 4]; // 640 = 30ms
        BufferHandle = GCHandle.Alloc(this.Buffer, GCHandleType.Pinned);
        BufferPtr = new IntPtr(BufferHandle.AddrOfPinnedObject().ToInt32());

        DataStream = new DataStream(BufferPtr, 640 * 4, true, false);
        AudioBuffer = new AudioBuffer();
        AudioBuffer.AudioData = DataStream;
    }

    public PlayBuffer(int index)
        : this()
    {
        Index = index;
    }
    #endregion

    #region Destructor
    ~PlayBuffer()
    {
        Dispose();
    }
    #endregion

    #region Properties
    protected int Index { get; private set; }
    public byte[] Buffer { get; private set; }
    public DataStream DataStream { get; private set; }
    public AudioBuffer AudioBuffer { get; private set; }
    #endregion

    #region Public functions
    public void Dispose()
    {
        if (AudioBuffer != null)
        {
            AudioBuffer.Dispose();
            AudioBuffer = null;
        }

        if (DataStream != null)
        {
            DataStream.Dispose();
            DataStream = null;
        }
    }
    #endregion
}

public class PlayBufferPool : IDisposable
{
    #region Private variables
    private int _currentIndex = -1;
    private PlayBuffer[] _buffers = new PlayBuffer[2];
    #endregion

    #region Constructors
    public PlayBufferPool()
    {
        for (int i = 0; i < 2; i++)
            Buffers[i] = new PlayBuffer(i);
    }
    #endregion

    #region Desctructor
    ~PlayBufferPool()
    {
        Dispose();
    }
    #endregion

    #region Properties
    protected int CurrentIndex
    {
        get { return _currentIndex; }
        set { _currentIndex = value; }
    }

    protected PlayBuffer[] Buffers
    {
        get { return _buffers; }
        set { _buffers = value; }
    }
    #endregion

    #region Public functions
    public void Dispose()
    {
        for (int i = 0; i < Buffers.Length; i++)
        {
            if (Buffers[i] == null)
                continue;

            Buffers[i].Dispose();
            Buffers[i] = null;
        }
    }

    public PlayBuffer NextBuffer()
    {
        CurrentIndex = (CurrentIndex + 1) % Buffers.Length;
        return Buffers[CurrentIndex];
    }
    #endregion
}

一些额外的细节:

这用于重放使用各种压缩(例如 ALAW、µLAW 或 TrueSpeech)录制的语音。数据以小数据包的形式发送、解码并发送到该播放器。这就是为什么我们使用如此低的采样率和如此小的缓冲区的原因。 然而,我们的数据没有问题,因为生成一个包含数据的 WAV 文件可以通过 WMP 或 VLC 完美重​​放。

编辑:我们现在已经通过重写 NAudio 中的播放器“解决”了这个问题。 我仍然对任何关于这里发生的事情的意见感兴趣。是我们在 PlayBuffers 中的方法,还是仅仅是 DirectX 或包装器中的错误/限制?我尝试使用 SharpDX 而不是 SlimDX,但这并没有改变任何结果。

最佳答案

看起来上采样是在没有适当的抗锯齿(重建)滤波器的情况下完成的。截止频率太高(高于原始奈奎斯特频率),因此保留了很多混叠,导致输出类似于在 8000 Hz 采集的样本之间的分段线性插值。

尽管所有不同的选项都在进行从 8kHz 到 44.1kHz 的上变频,但它们执行此操作的方式很重要,而且一个库做得很好的事实并不能证明上变频不是错误的来源另一个。

关于c# - 通过 XAudio2 播放正弦,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12258495/

相关文章:

c# - 将两个 double[] 数组组合成 double[,]

javascript - 如何仅从模态中删除背景

java - 如何创建振幅上下限的正弦波

audio - 关于环境声音分类的分类方法和机器学习模型的建议

directx - SlimDX/DirectX9/C# - 如何访问纹理中的像素数据

c# - TaskContinuationOptions.OnlyOnFaulted 不考虑使用异常的流量控制?

c# - 等待 DownloadFileAsync 完成下载,然后执行某些操作

javascript - "useRefs"变量是 div 引用的初始值?

c# - NV_STEREO_IMAGE_SIGNATURE 和 DirectX 10/11(nVidia 3D Vision)

visual-studio - SlimDX 装配引用