编辑:今天我发现我只有在使用带电源线的耳机时才会遇到这个问题。问题不在于耳机,因为同样的耳机也可以无线使用,然后问题就消失了,就像我使用其他无线耳机一样。不过,我更喜欢使用电源线,因为它的延迟较小。所以我希望有人能在提供了这些额外信息后帮助我解开这个谜团。
我有这个代码来播放正弦波声音。当我尝试播放这个时,我经常听到咔嗒声。我敢肯定,这是因为缓冲区的播放并不完美,因为当您将l
的值更改为更大的值(例如44100(时,单击会相距更远。我想我已经尽可能准确地遵循了关于如何使用微软网站上的回调的解释。我创建了三个轮流播放的源音:一个在播放,另一个准备好了,然后一个正在制作。我使用总时间(tt
(来放入sin()
函数,因此下一个缓冲区的第一个字节应该与当前缓冲区的最后一个字节完全对齐。
有人知道出了什么问题吗?
附言:许多类似的问题并没有回答我的问题。简而言之:我没有修改播放缓冲区(至少我不这么认为(;一个缓冲区与另一个缓冲区时的边界不应出现间断;在播放过程中,我也没有调整频率。所以我不认为这是重复的。
#include <xaudio2.h>
#include <iostream>
#define PI 3.14159265358979323846f
#define l 4410 //0.1 seconds
IXAudio2MasteringVoice* pMasterVoice;
IXAudio2* pXAudio2;
IXAudio2SourceVoice* pSourceVoice[3];
XAUDIO2_BUFFER buffer;
WAVEFORMATEX wfx;
XAUDIO2_VOICE_STATE state;
BYTE pDataBuffer[2*l];
BYTE bytw[2];
int pow16[2];
float w[l];
int i, p;
float tt, ampl;
class VoiceCallback : public IXAudio2VoiceCallback {
public:
HANDLE hBufferEndEvent;
VoiceCallback() : hBufferEndEvent(CreateEvent(NULL, FALSE, FALSE, NULL)) {}
~VoiceCallback() { CloseHandle(hBufferEndEvent); }
//Called when the voice has just finished playing a contiguous audio stream.
void STDMETHODCALLTYPE OnStreamEnd() {SetEvent(hBufferEndEvent);}
//Unused methods are stubs
void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() {}
void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32 SamplesRequired) {}
void STDMETHODCALLTYPE OnBufferEnd(void * pBufferContext) {}
void STDMETHODCALLTYPE OnBufferStart(void * pBufferContext) {}
void STDMETHODCALLTYPE OnLoopEnd(void * pBufferContext) {}
void STDMETHODCALLTYPE OnVoiceError(void * pBufferContext, HRESULT Error) {}
};
VoiceCallback voiceCallback[3];
int main() {
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
pXAudio2 = nullptr;
XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);
pMasterVoice = nullptr;
pXAudio2->CreateMasteringVoice(&pMasterVoice);
tt = 0, p = 660, ampl = 2000;
pow16[0] = 16;
pow16[1] = 4096;
wfx = {0};
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = (WORD)1; //mono
wfx.nSamplesPerSec = (DWORD)44100; //samplerate
wfx.wBitsPerSample = (WORD)16; //16 bit (signed)
wfx.nBlockAlign = (WORD)2; //2 bytes per sample
wfx.nAvgBytesPerSec = (DWORD)88200; //samplerate*blockalign
wfx.cbSize = (WORD)0;
i = 0;
while (true) {
for (int t = 0; t < l; t++) {
tt = (float)(t + i*l); //total time
w[t] = sin(2.f*PI*tt/p)*ampl;
int intw = (int)w[t];
if (intw < 0) {
intw += 65535;
}
bytw[0] = 0; bytw[1] = 0;
for (int k = 1; k >= 0; k--) {
//turn integer into a little endian byte array
bytw[k] += (BYTE)(16*(intw/pow16[k]));
intw -= bytw[k]*(pow16[k]/16);
bytw[k] += (BYTE)(intw/(pow16[k]/16));
intw -= (intw/(pow16[k]/16))*pow16[k]/16;
}
pDataBuffer[2*t] = bytw[0];
pDataBuffer[2*t + 1] = bytw[1];
}
buffer.AudioBytes = 2*l; //number of bytes per buffer
buffer.pAudioData = pDataBuffer;
buffer.Flags = XAUDIO2_END_OF_STREAM;
if (i > 2) {
pSourceVoice[i%3]->DestroyVoice();
}
pSourceVoice[i%3] = nullptr;
pXAudio2->CreateSourceVoice(&pSourceVoice[i%3], &wfx, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &voiceCallback[i%3], NULL, NULL);
pSourceVoice[i%3]->SubmitSourceBuffer(&buffer);
if (i > 1) {
//wait until the current one is done playing
while (pSourceVoice[(i - 2)%3]->GetState(&state), state.BuffersQueued > 0) {
WaitForSingleObjectEx(voiceCallback[(i - 2)%3].hBufferEndEvent, INFINITE, TRUE);
}
}
if (i > 0) {
//play the next one while you're writing the one after that in the next iteration
pSourceVoice[(i - 1)%3]->Start(0);
}
i++;
}
}
如果您希望声音"循环",则将多个数据包提交给同一源语音,或者设置循环值,使其自动重新启动现有音频包。如果你允许源语音耗尽数据,那么你会听到介于两者之间的中断,这取决于你的音频输出系统的延迟。
此外,创建和销毁源语音是一项相对昂贵的操作,因此在这样的循环中进行操作并不是特别有效。
有关XAudio2使用的完整示例,以及GitHub上旧版DirectX SDK中XAudio2示例的最新版本,请参阅DirectX音频工具包。