[英]Playing sinus through XAudio2
我正在使用XAudio2制作音頻播放器。 我們以640Hz的數據包流式傳輸數據,采樣率為8000Hz,采樣深度為16個字節。 我們正在使用SlimDX來訪問XAudio2。
但是在播放聲音時,我們注意到聲音質量很差。 例如,這是用Audacity捕獲的3KHz正弦曲線。
我已經將音頻播放器壓縮為基本內容,但是音頻質量仍然很差。 這是XAudio2,SlimDX或我的代碼中的錯誤,還是僅僅是當人們從8KHz變為44.1KHz時發生的偽像? 最后一個似乎不合理,因為我們還會生成PCM wav文件,這些文件可以由Windows Media Player完美播放。
以下是基本的實現,它將生成損壞的Sine。
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的上變頻,但是它們的執行方式很重要,而且一個庫運行良好的事實不能證明上變頻不是另一種中的錯誤源。
自從我處理聲音和頻率已經有一段時間了,但是我記得這里:您的采樣率為8000Hz,想要的正弦頻率為3000Hz。 因此,在1秒鍾內,您有8000個樣本,而在這一秒內,您希望正弦振動3000次。 該頻率低於奈奎斯特頻率(一半采樣率),但幾乎沒有(請參閱奈奎斯特-香農采樣定理 )。 因此,在這里我不會指望優質的產品。
實際上:逐步執行GenerateSine
方法,您會看到s[0]
將包含值0、5792,-8191、5792、0,-5792、8191,-5792、0、5792 ...
但這並不能解釋您錄制的奇數正弦波,而且我不確定人耳需要多少個樣本才能聽到“良好”的正弦波。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.