¿Por qué waveOutWrite () causa una excepción en el montón de depuración?

Al investigar este problema, encontré múltiples 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.

Primero, el síntoma: mientras ejecuto un código bastante estándar que usa waveOutWrite () para emitir audio PCM, a veces lo obtengo cuando ejecuto bajo el depurador:

 ntdll.dll!_DbgBreakPoint@0()   
 ntdll.dll!_RtlpBreakPointHeap@4()  + 0x28 bytes    
 ntdll.dll!_RtlpValidateHeapEntry@12()  + 0x113 bytes   
 ntdll.dll!_RtlDebugGetUserInfoHeap@20()  + 0x96 bytes  
 ntdll.dll!_RtlGetUserInfoHeap@20()  + 0x32743 bytes    
 kernel32.dll!_GlobalHandle@4()  + 0x3a bytes   
 wdmaud.drv!_waveCompleteHeader@4()  + 0x40 bytes   
 wdmaud.drv!_waveThread@4()  + 0x9c bytes   
 kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes    

Si bien el sospechoso obvio sería un montón de corrupción en otro lugar del código, descubrí que ese no es el caso. Además, pude reproducir este problema utilizando el siguiente código (esto es parte de una aplicación MFC basada en cuadros de 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 comente sobre esto, sí, el código de muestra reproduce la memoria sin inicializar. No intente esto con los altavoces girados completamente hacia arriba.

Algunos errores en la depuración revelaron 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 el pseudo código, se vería algo así:

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, el encabezado se devuelve a la aplicación por waveCompleteHeader (), una función interna de wdmaud.dll. waveCompleteHeader () intenta desasignar el puntero asignado por waveOutWrite () llamando a GlobalHandle () / GlobalUnlock () y amigos. A veces, GlobalHandle () bombea, como se muestra arriba.

Ahora, la razón por la que GlobalHandle () bombardea no se debe a una corrupción del montón, como sospeché al principio, es porque waveOutWrite () regresó 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 volver, pero todavía no la he desmontado.

Parece que esto solo sucede cuando el sistema de reproducción de onda tiene pocos buffers, por lo que estoy usando un solo encabezado para reproducir esto.

En este punto tengo un caso bastante bueno en contra de que se trate de 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 administrar los búferes que se reproducen es simplemente errónea. Notarás que cambié el código anterior para reflejar la sugerencia, para evitar que más personas cometan el mismo error, no hay diferencia. El búfer 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 hay ningún requisito de que se asigne de ninguna manera específica.

Además, me aseguro de que ninguna de las llamadas de API waveOut que utilizo falle.

Actualmente estoy asumiendo que esto es un error en Windows o en el controlador de audio. Las opiniones disidentes siempre son bienvenidas.

Respuestas a la pregunta(9)

Su respuesta a la pregunta