Mientras investigaba este tema, encontré varias menciones del siguiente escenario en línea, invariablemente como preguntas sin respuesta en los foros de programación. Espero que publicar esto aquí sirva al menos para documentar mis hallazgos.¿Por qué waveOutWrite() causaría una excepción en el montón de depuración?
En primer lugar, el síntoma: Durante la ejecución de código bastante estándar que utiliza waveOutWrite() para audio PCM de salida, a veces me sale esto cuando se ejecuta en el depurador:
[email protected]()
[email protected]() + 0x28 bytes
[email protected]() + 0x113 bytes
[email protected]() + 0x96 bytes
[email protected]() + 0x32743 bytes
[email protected]() + 0x3a bytes
wdmaud.drv!_waveCompl[email protected]() + 0x40 bytes
[email protected]() + 0x9c bytes
[email protected]() + 0x37 bytes
Mientras que el sospechoso obvio sería un montón resulte dañado en otro lugar del código, descubrí que ese no es el caso. Por otra parte, yo era capaz de reproducir este problema utilizando el siguiente código (esto es parte de una aplicación MFC basada diálogo :)
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;
}
Antes de que alguien comenta sobre esto, sí - el código de ejemplo reproduce los memoria sin inicializar. No intente esto con sus parlantes girados hacia arriba.
Algunos depuración reveló la siguiente información: waveOutPrepareHeader() rellena header.reserved con un puntero a lo que parece ser una estructura que contiene al menos dos punteros como sus dos primeros miembros. El primer puntero se establece en NULL. Después de llamar a waveOutWrite(), este puntero se establece en un puntero asignado en el montón global. En pseudo código, que sería algo como esto:
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 */
}
Normalmente, la cabecera se devuelve a la aplicación por waveCompleteHeader(), una función interna para wdmaud.dll. waveCompleteHeader() intenta desasignar el puntero asignado por waveOutWrite() llamando a GlobalHandle()/GlobalUnlock() y amigos. A veces, las bombas GlobalHandle(), como se muestra arriba.
Ahora, la razón por la que las bombas GlobalHandle() no se deben a un daño en el montón, como sospechaba al principio, es porque waveOutWrite() devolvió sin establecer el primer puntero en la estructura interna a un puntero válido. Sospecho que libera la memoria apuntada por ese puntero antes de regresar, pero aún no lo he desmontado.
Esto solo parece suceder cuando el sistema de reproducción de onda tiene pocos búfers, por lo que estoy usando un único encabezado para reproducir esto.
En este punto, tengo un muy buen caso en contra de que esto sea un error en mi aplicación; después de todo, mi aplicación ni siquiera se está ejecutando. ¿Alguien ha visto esto antes?
Estoy viendo esto en Windows XP SP2. La tarjeta de audio es de SigmaTel y la versión del controlador es 5.10.0.4995.
Notas:
Para evitar confusiones en el futuro, me gustaría señalar que la respuesta que sugiere que el problema radica en el uso de malloc()/free() para gestionar los buffers se está reproduciendo se simplemente equivocado Notarás que cambié el código anterior para reflejar la sugerencia, para evitar que más personas cometan el mismo error: no hace la diferencia. El buffer liberado por waveCompleteHeader() no es el que contiene los datos PCM, la responsabilidad de liberar el búfer PCM recae en la aplicación, y no es necesario que se asigne de manera específica.
Además, me aseguro de que ninguna de las llamadas a la API waveOut que uso falle.
Actualmente estoy asumiendo que esto es un error en Windows o en el controlador de audio. Las opiniones disidentes son siempre bienvenidas.
vagamente recuerdo haber visto algo así en una aplicación de Windows CE. Deleaker informó que alguna función de onda *** tenía una pérdida de memoria. Al final, todo estaba bien y cuando cerré, solo tuve que liberar un recurso. Aunque no tengo el código aquí. – OregonGhost