WaveOutWrite 回调创建断断续续的音频

Posted

技术标签:

【中文标题】WaveOutWrite 回调创建断断续续的音频【英文标题】:WaveOutWrite callback creates choppy audio 【发布时间】:2012-10-05 22:33:46 【问题描述】:

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

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

当我执行时,我听到每个缓冲区之间有短暂的停顿。我已将缓冲区大小增加到 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: %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 个样本中很明显。 Debug 和 Release 版本都会发生这种情况。

【问题讨论】:

您听到停顿声还是听到尖锐的咔哒声?邮政编码。 大多数音频 API 至少有两个缓冲区,一个正在播放,另一个正在写入。缓冲区之间丢失的最常见原因是库的用户执行了某种阻塞(如互斥锁)。这可能会有所帮助:blog.bjornroche.com/2011/11/… 这里有更多信息可能会有所帮助:waveOutWrite( ) stuttering Hans:这显然是一个停顿,而不是尖锐的点击。听起来不像是缓冲区在彼此之上播放并截断波形导致咔嗒声。我也尝试过使用白噪声,这是一个暂停,而不是时钟。我已经用代码更新了上面的内容。 1024 个样本只有 20 毫秒。您可以在这种低延迟下使用的唯一 API 是 WASAPI,而您使用的 API 是其上的一层,因此会产生一定的开销。 【参考方案1】:

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

【讨论】:

以上是关于WaveOutWrite 回调创建断断续续的音频的主要内容,如果未能解决你的问题,请参考以下文章

waveOutWrite 和 waveOutGetPosition 死锁的问题

AUGraph 播放 aac->pcm 转换的音频流在 iPhone7plus 上听起来断断续续,但不是 iPhone6

使用导致内存泄漏的音频片段

NAudio - 通过终端服务器连接播放断断续续的音频

混合音频通道背后的基本数学

使用 AudioQueue 播放断断续续的音频