WaveOutWrite回调会创建不连贯的音频

时间:2012-10-05 22:33:47

标签: c++ audio winmm

我有四个缓冲区用于合成器中的音频播放。我最初提交了两个缓冲区,然后在回调例程中,我将数据写入下一个缓冲区,然后提交该缓冲区。

当我生成每个缓冲区时,我只是将一个正弦波放入其中,其周期是缓冲区长度的倍数。

当我执行时,我听到每个缓冲区之间的短暂暂停。我已经将缓冲区大小增加到4400 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: %d\n", 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: %d\n", 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个样本很清楚。调试和发布版本都会发生这种情况。

1 个答案:

答案 0 :(得分:0)

1024个样本只有大约23毫秒的音频数据。从Windows Vista开始,wav是相当高级的API。如果您想要低延迟音频播放,则应使用CoreAudio。您可以在共享模式下将延迟降至10 ms,在独占模式下降至3 ms。此外,音频取决于系统上当前运行的进程。换句话说,它取决于您的音频线程可以运行以获取数据的频率。您还应该查看MultiMedia Class Scheduler ServiceAvSetMmThreadCharacteristics函数。