通过XAudio2播放窦

我正在使用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(sourceVoice_BufferStart); sourceVoice.BufferEnd += new EventHandler(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,但这并没有改变任何结果。

看起来好像没有适当的抗锯齿(重建)滤波器就完成了上采样。 截止频率太高(高于原始奈奎斯特频率),因此许多别名被保留,导致输出类似于以8000Hz采样的分段线性插值。

虽然你所有不同的选择都是从8kHz到44.1kHz进行上变频,但是它们的方式很重要,而且一个库做得好的事实并不能certificate上变换不是另一个变换的误差来源。

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

事实上:逐步通过GenerateSine -method你会看到s[0]将包含值0,5792,-8191,5792,0,-5792,8191,-5792,0,5792 ……

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