6

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

但是在播放声音时,我们注意到音质很差。例如,这是一条 3KHz 正弦曲线,使用 Audacity 捕获。 3KHz 正弦曲线

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

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

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,但这并没有改变任何结果。

4

2 回答 2

2

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

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

于 2012-09-26T13:29:22.127 回答
0

自从我处理声音和频率以来已经有一段时间了,但这是我记得的:你有一个 8000Hz 的采样率并且想要一个 3000Hz 的正弦频率。因此,在 1 秒内,您有 8000 个样本,而在那一秒内,您希望正弦波振荡 3000 次。这低于奈奎斯特频率(采样率的一半),但几乎没有(参见奈奎斯特-香农采样定理)。所以我不希望这里有好的质量。

事实上:单步执行GenerateSine- 方法,您会看到s[0]它将包含值 0、5792、-8191、5792、0、-5792、8191、-5792、0、5792...

尽管如此,这并不能解释您记录回来的奇怪正弦波,而且我不确定人耳需要多少样本才能听到“好”的正弦波。

于 2012-09-26T10:05:01.160 回答