2008-10-12 114 views
13

虽然研究这个问题,我发现以下情形多提到网上,总是作为编程论坛上悬而未决的问题。我希望在这里张贴这至少将用于记录我的发现。为什么waveOutWrite()会在调试堆中导致异常?

一,症状:

[email protected]() 
[email protected]() + 0x28 bytes  
[email protected]() + 0x113 bytes 
[email protected]() + 0x96 bytes 
[email protected]() + 0x32743 bytes  
[email protected]() + 0x3a bytes 
[email protected]() + 0x40 bytes 
[email protected]() + 0x9c bytes 
[email protected]() + 0x37 bytes  

虽然明显的嫌疑人将是一个堆损坏:在运行使用waveOutWrite()输出PCM音频非常标准的代码,我有时会在调试器下运行时,得到这个在代码的其他地方,我发现情况并非如此。此外,我能够重现使用下面的代码这个问题(这是一个基于对话框的MFC应用程序的一部分:)

void CwaveoutDlg::OnBnClickedButton1() 
{ 
    WAVEFORMATEX wfx; 
    wfx.nSamplesPerSec = 44100; /* sample rate */ 
    wfx.wBitsPerSample = 16; /* sample size */ 
    wfx.nChannels = 2; 
    wfx.cbSize = 0; /* size of _extra_ info */ 
    wfx.wFormatTag = WAVE_FORMAT_PCM; 
    wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels; 
    wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec; 

    waveOutOpen(&hWaveOut, 
       WAVE_MAPPER, 
       &wfx, 
       (DWORD_PTR)m_hWnd, 
       0, 
       CALLBACK_WINDOW); 

    ZeroMemory(&header, sizeof(header)); 
    header.dwBufferLength = 4608; 
    header.lpData = (LPSTR)GlobalLock(GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, 4608)); 

    waveOutPrepareHeader(hWaveOut, &header, sizeof(header)); 
    waveOutWrite(hWaveOut, &header, sizeof(header)); 
} 

afx_msg LRESULT CwaveoutDlg::OnWOMDone(WPARAM wParam, LPARAM lParam) 
{ 
    HWAVEOUT dev = (HWAVEOUT)wParam; 
    WAVEHDR *hdr = (WAVEHDR*)lParam; 
    waveOutUnprepareHeader(dev, hdr, sizeof(WAVEHDR)); 
    GlobalFree(GlobalHandle(hdr->lpData)); 
    ZeroMemory(hdr, sizeof(*hdr)); 
    hdr->dwBufferLength = 4608; 
    hdr->lpData = (LPSTR)GlobalLock(GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, 4608)); 
    waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR)); 
    waveOutWrite(hWaveOut, hdr, sizeof(WAVEHDR)); 
    return 0; 
} 

在任何人对这个评论,是 - 示例代码播放未初始化的内存。不要试图将您的扬声器翻过来。

某些调试揭示了以下信息:waveOutPrepareHeader()使用指向至少包含两个指针作为其前两个成员的结构的指针填充header.reserved。第一个指针设置为NULL。在调用waveOutWrite()之后,该指针被设置为在全局堆上分配的指针。在伪代码,这将是这个样子:

struct Undocumented { void *p1, *p2; } /* This might have more members */ 

MMRESULT waveOutPrepareHeader(handle, LPWAVEHDR hdr, ...) { 
    hdr->reserved = (Undocumented*)calloc(sizeof(Undocumented)); 
    /* Do more stuff... */ 
} 

MMRESULT waveOutWrite(handle, LPWAVEHDR hdr, ...) { 

    /* The following assignment fails rarely, causing the problem: */ 
    hdr->reserved->p1 = malloc(/* chunk of private data */); 
    /* Probably more code to initiate playback */ 
} 

通常情况下,报头由waveCompleteHeader返回给应用程序(),内部功能到wdmaud.dll。 waveCompleteHeader()尝试通过调用GlobalHandle()/ GlobalUnlock()和朋友解除分配)的waveOutWrite(分配的指针。有时候,GlobalHandle()炸弹,如上所示。

现在,GlobalHandle()炸弹不是由于堆腐败造成的,正如我最初怀疑的 - 这是因为waveOutWrite()返回时没有将内部结构中的第一个指针设置为有效指针。我怀疑它在返回之前释放了该指针指向的内存,但我还没有反汇编它。

这只在波形回放系统在缓冲区低时才会出现,这就是为什么我使用单个标题来重现这一点。

在这一点上我对这个是我的应用程序中的错误相当不错的情况 - 毕竟,我的应用程序甚至没有运行。有没有人见过这个?

我在Windows XP SP2看到这一点。声卡来自SigmaTel,驱动程序版本为5.10.0.4995。

注:

为了防止将来出现混乱,我想指出的是,答案提示问题出在使用malloc(的)/免费()来管理正在播放的缓冲区简直是错的。你会注意到我改变了上面的代码来反映这个建议,以防止更多的人犯同样的错误 - 这没有什么不同。 waveCompleteHeader()释放的缓冲区不是包含PCM数据的缓冲区,释放PCM缓冲区的责任在于应用程序,并且不要求以任何特定方式分配它。

另外,我要确保我没有使用waveout的API调用的失败。

我目前假设,这是无论是在Windows中的漏洞,或在音频驱动程序。反对意见总是受欢迎的。

+1

我依稀记得见过类似的东西在Windows CE应用程序。 Deleaker报告说,一些波***功能有内存泄漏。最后,一切都很好,关闭时,我只需要释放资源。虽然我没有这里的代码。 – OregonGhost 2008-10-12 17:11:52

回答

0

不确定这个问题,但你有没有考虑过使用更高级别的跨平台音频库? Windows音频编程有很多怪癖,这些库可以为您节省很多麻烦。

示例包括PortAudioRtAudioSDL

+0

我需要低级别的访问权限,而且Windows音频编程非常简单 - waveOut API永远存在,我非常了解它(使用它13年的经验,使用它编写多个声音引擎,从Windows 3.1开始)代码以上几乎是需要的。而已。 – 2008-10-14 04:21:12

+1

我在这里因为QAudioOutput和PortAudio因此而崩溃:D – 0xbaadf00d 2012-10-02 05:35:33

0

我要做的第一件事就是检查waveOutX函数的返回值。如果它们中的任何一个都失败 - 考虑到你描述的情况,这不是不合理的 - 并且不管怎么说,事情开始出错都不足为奇。我的猜测是waveOutWrite会在某个时候返回MMSYSERR_NOMEM。

+0

在我的产品代码中,每个waveOutXXX函数都有一个assert()函数,确保紧接着它的返回值是MMSYSERR_NOERROR。没有任何通话失败。 – 2008-10-17 14:54:30

+0

另外,您应该注意,失败发生在缓冲区成功播放之后,并且在它返回到应用程序之前发生,所以waveOutWrite()认为它成功了。 – 2008-10-17 14:56:25

+0

这可能是由驱动程序故障引起的。您是否尝试在使用不同的音频硬件时重现相同的问题?你目前使用什么设备以及哪个驱动程序版本? – 2008-10-17 16:11:05

1

我看到了同样的问题,也做了一些分析自己:

waveOutWrite()分配(即的GlobalAlloc)一个指向354个字节的堆区,并正确地将其存储在数据区域指向header.reserved。

但是,当这个堆区被再次释放时(根据你的分析,在waveCompleteHeader()中,我没有wdmaud.drv的符号),指针的最低有效字节被设置为零,从而使指针无效(而堆没有被破坏)。换句话说,什么情况是这样的:

  • (BYTE *)(header.reserved)= 0

于是我在一个点上与你的陈述不同意:waveOutWrite()存储一个有效的指针第一;该指针只会在后来从另一个线程变坏。 也许这是相同的线程(mxdmessage),后来试图释放这个堆区域,但我还没有找到存储零字节的位置。

这不会经常发生,并且相同的堆区(相同的地址)已经成功地被分配和释放之前。 我相当确信这是系统代码中的某个错误。

+0

Johannes, 如果内存为我服务,我看到的指针损坏在整个指针被设置为0和被设置为各种垃圾值之间变化。显然,你看到别的东西。我真的很想听到更多关于您的调试工作的信息。 – 2009-01-16 20:17:13

+0

另外,出于好奇:您使用哪个音频驱动程序来重现此? – 2009-01-16 20:18:13

0

使用Application Verifier来弄清楚发生了什么,如果你做了一些可疑的事情,它会很早就捕获它。

0

它可能有助于看看source code for Wine,虽然它可能是酒有固定的任何错误存在,而且它也可能葡萄酒在其他错误它。相关文件是dlls/winmm/winmm.c,dlls/winmm/lolvldrv.c以及其他可能的文件。祝你好运!

3

现在,GlobalHandle() 炸弹是不是由于堆损坏, 正如我在第一个怀疑的理由 - 这是因为 waveOutWrite()返回无 设置在 内部结构的第一个指针一个有效的指针。 我怀疑它释放 返回前指针指向的内存 ,但我还没有反汇编 呢。

我可以用你的代码在我的系统上重现这一点。我看到类似约翰内斯报道的东西。在调用WaveOutWrite之后,hdr-> reserved通常会保存一个指向已分配内存的指针(其中包含unicode中的wave out设备名称)。

但是,有时从WaveOutWrite()返回后,hdr->reserved指向的字节被设置为0.这通常是该指针的最低有效字节。其余的hdr->reserved字节都没问题,并且它通常指向的内存块仍然被分配并且没有损坏。

它可能正在被另一个线程破坏 - 我可以在调用WaveOutWrite()之后立即用条件断点捕获更改。系统调试断点发生在另一个线程中,而不是消息处理程序。

但是,如果我使用回调函数而不是Windows消息泵,则不会导致系统调试断点发生。 (fdwOpen = CALLBACK_FUNCTION in WaveOutOpen()) 当我这样做时,我的OnWOMDone处理程序被另一个线程调用 - 可能是另一个负责该损坏的线程。

所以我认为有一个错误,无论是在Windows或驱动程序,但我认为你可以通过处理WOM_DONE回调函数而不是Windows消息泵。

0

那么你不允许从回调中调用winmm函数呢? MSDN没有提及有关窗口消息的限制,但窗口消息的使用与回调函数类似。可能内部它是作为驱动程序的回调函数实现的,并且该回调函数执行SendMessage。 在内部,waveout必须维护使用waveOutWrite写入的标题链表;所以,我猜测:

hdr->reserved = (Undocumented*)calloc(sizeof(Undocumented)); 

设置链接列表的上一个/下一个指针或类似的东西。如果你写更多的缓冲区,那么如果你检查指针,并且它们中的任何一个指向另一个,那么我的猜测很可能是正确的。

网络上的多个来源提到您不需要反复准备/准备相同的标题。如果您在原始示例中注释掉Prepare/unprepare标题,那么它看起来工作正常,没有任何问题。

0

我通过查询解决了这个问题的声音回放和延误:

WAVEHDR header = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 }; 
waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR)); 
waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR)); 
/* 
* wait a while for the block to play then start trying 
* to unprepare the header. this will fail until the block has 
* played. 
*/ 
while (waveOutUnprepareHeader(hWaveOut,&header,sizeof(WAVEHDR)) == WAVERR_STILLPLAYING) 
Sleep(100); 
waveOutClose(hWaveOut); 

Playing Audio in Windows using waveOut Interface

相关问题