我有四个缓冲区,用于合成器中的音频播放。我最初提交两个缓冲区,然后在回调例程中将数据写入下一个缓冲区中,然后提交该缓冲区。
当我生成每个缓冲区时,我只是在其中放入一个正弦波,其周期是缓冲区长度的倍数。
当我执行时,我听到每个缓冲区之间有短暂的停顿。我已经将缓冲区大小增加到44100 Hz的16K样本,这样我就可以清楚地听到整个缓冲区正在播放,但每个缓冲区之间都有一个中断。
我认为发生的情况是,只有当所有已写入的缓冲区都完成时,才会调用回调函数。我需要合成保持在播放之前,所以当每个缓冲区完成时,我需要回调。
人们通常是如何解决这个问题的?
更新:我被要求添加代码。这是我的:
首先我连接到WaveOut设备:
// Always grab the mapped wav device.
UINT deviceId = WAVE_MAPPER;
// This is an excelent tutorial:
// http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=4422&lngWId=3
WAVEFORMATEX wfx;
wfx.nSamplesPerSec = 44100;
wfx.wBitsPerSample = 16;
wfx.nChannels = 1;
wfx.cbSize = 0;
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
_waveChangeEventHandle = CreateMutex(NULL,false,NULL);
MMRESULT res;
res = waveOutOpen(&_wo, deviceId, &wfx, (DWORD_PTR)WavCallback,
(DWORD_PTR)this, CALLBACK_FUNCTION);
我初始化了将要使用的四个帧:
for (int i=0; i<_numFrames; ++i)
{
WAVEHDR *header = _outputFrames+i;
ZeroMemory(header, sizeof(WAVEHDR));
// Block size is in bytes. We have 2 bytes per sample.
header->dwBufferLength = _codeSpec->OutputNumSamples*2;
header->lpData = (LPSTR)malloc(2 * _codeSpec->OutputNumSamples);
ZeroMemory(header->lpData, 2*_codeSpec->OutputNumSamples);
res = waveOutPrepareHeader(_wo, header, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR)
{
printf("Error preparing header: %dn", res - MMSYSERR_BASE);
}
}
SubmitBuffer();
SubmitBuffer();
这是SubmitBuffer代码:
void Vodec::SubmitBuffer()
{
WAVEHDR *header = _outputFrames+_curFrame;
MMRESULT res;
res = waveOutWrite(_wo, header, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR)
{
if (res = WAVERR_STILLPLAYING)
{
printf("Cannot write when still playing.");
}
else
{
printf("Error calling waveOutWrite: %dn", res-WAVERR_BASE);
}
}
_curFrame = (_curFrame+1)&0x3;
if (_pointQueue != NULL)
{
RenderQueue();
_nextFrame = (_nextFrame + 1) & 0x3;
}
}
这是我的回调代码:
void CALLBACK Vodec::WavCallback(HWAVEOUT hWaveOut,
UINT uMsg,
DWORD dwInstance,
DWORD dwParam1,
DWORD dwParam2 )
{
// Only listen for end of block messages.
if(uMsg != WOM_DONE) return;
Vodec *instance = (Vodec *)dwInstance;
instance->SubmitBuffer();
}
RenderQueue代码非常简单——只需将一块模板缓冲区复制到输出缓冲区:
void Vodec::RenderQueue()
{
double white = _pointQueue->White;
white = 10.0; // For now just override with a constant value
int numSamples = _codeSpec->OutputNumSamples;
signed short int *data = (signed short int *)_outputFrames[_nextFrame].lpData;
for (int i=0; i<numSamples; ++i)
{
Sample x = white * _noise->Samples[i];
data[i] = (signed short int)(x);
}
_sampleOffset += numSamples;
if (_sampleOffset >= _pointQueue->DurationInSamples)
{
_sampleOffset = 0;
_pointQueue = _pointQueue->next;
}
}
更新:基本解决了问题。我需要增加_nextFrame和_curFrame(不是有条件的)。播放缓冲区超过了写入缓冲区。
然而,当我将播放缓冲区减少到1024个样本时,它再次变得不稳定。在2048个样本中,它是清晰的。调试和发布版本都会发生这种情况。
1024个样本大约是23ms的音频数据。wav是从Windows Vista开始相当高级别的API。如果您想要低延迟音频播放,您应该使用CoreAudio。在共享模式下,延迟可以降到10毫秒,在独占模式下,可以降到3毫秒。此外,音频取决于系统上当前运行的进程。换句话说,这取决于音频线程获取数据的频率。您还应该了解多媒体类调度程序服务和AvSetMmThreadCharacteristics函数。