2008-10-12 6 views
13

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.

+1

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

Respuesta

2
+0

Es agradable ver que el problema se informó en 2005 y que se ha cerrado. –

+0

Está cerrado como "Externo" lo que significa que es una falla dentro de un controlador que no es de MS, u otra parte componente que no está desarrollada en mi MS. Supongo que es un error del controlador: no hay muchos componentes no controladores en Windows que no sean de MS. – Stefan

+0

Gotcha. Hubiera sido bueno obtener más información al respecto, como qué controlador es el que está causando esto. –

0

No estoy seguro sobre este problema en particular, pero ¿ha considerado utilizar una biblioteca de audio multiplataforma de nivel superior? Hay muchas peculiaridades con la programación de audio de Windows, y estas bibliotecas pueden ahorrarte muchos dolores de cabeza.

Los ejemplos incluyen PortAudio, RtAudio y SDL.

+0

Necesito acceso de bajo nivel, y la programación de audio de Windows es absolutamente simple: la API de WaveOut ha estado ahí para siempre, lo sé extremadamente bien (13 años de experiencia utilizándolo, escribiendo múltiples motores de sonido usándolo, comenzando con Windows 3.1) El código arriba es más o menos lo que se necesita. Eso es. –

+1

Llegué aquí porque QAudioOutput y PortAudio se bloquean debido a esto: D – 0xbaadf00d

0

Lo primero que haría sería verificar los valores de retorno de las funciones de waveOutX. Si alguno de ellos falla, lo que no es irrazonable dado el escenario que describes, y sigues adelante independientemente, entonces no es sorprendente que las cosas comiencen a ir mal. Supongo que waveOutWrite devolverá MMSYSERR_NOMEM en algún momento.

+0

En mi código de producción, cada función waveOutXXX tiene un assert() asegurándose de que el valor devuelto sea MMSYSERR_NOERROR inmediatamente después. Ninguna de las llamadas falla. –

+0

Además, debe tener en cuenta que la falla ocurre después de que el búfer se jugó con éxito, y justo antes de que se devuelva a la aplicación, por lo que waveOutWrite() cree que tuvo éxito. –

+0

Es posible que esto se deba a un error del controlador. ¿Has intentado reproducir el mismo problema al usar diferentes hardware de audio?¿Qué dispositivo está utilizando en este momento y qué versión de controlador? –

1

que estoy viendo el mismo problema y lo han hecho algunos análisis a mí mismo:

waveOutWrite() asigna (es decir, GlobalAlloc) un puntero a un área montón de 354 bytes y lo almacena correctamente en el área de datos a la que apunta header.reserved

Pero cuando este área del montón se libere nuevamente (en waveCompleteHeader(), de acuerdo con su análisis, no tengo los símbolos para wdmaud.drv), el byte menos significativo del puntero se ha configurado para cero, lo que invalida el puntero (mientras que el montón no está dañado aún). En otras palabras, lo que sucede es algo así como:

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

Así que no están de acuerdo con sus estados de cuenta en un punto: waveOutWrite() almacena un puntero válido primero; el puntero solo se corrompe después de otro subproceso. Probablemente ese es el mismo hilo (mxdmessage) que luego trata de liberar esta área de montón, pero todavía no encontré el punto donde se almacena el byte cero.

Esto no ocurre muy a menudo, y la misma área de montón (misma dirección) ha sido asignada y desasignada con éxito anteriormente. Estoy bastante convencido de que este es un error en algún lugar del código del sistema.

+0

Johannes, Si me sirve la memoria, la corrupción del puntero que he visto varía entre el puntero completo que se establece en 0 y se establece en varios valores de basura. Obviamente, estás viendo algo más. Estaría realmente interesado en saber más acerca de sus esfuerzos de depuración. –

+0

Además, por curiosidad: ¿Qué controlador de audio está utilizando para reproducir esto? –

+0

La corrupción del puntero de bajo byte aquí corresponde al enlace MS Connect publicado por Stefan sobre – Roddy

0

Use Application Verifier para descubrir qué sucede, si hace algo sospechoso, lo detectará mucho antes.

+0

Enlace? El enlace de http://technet.microsoft.com/en-us/library/bb457063.aspx está roto. –

+0

Hmmm, raro: lo informaré a MSDN –

0

Puede ser útil para mirar el source code for Wine, aunque es posible que el vino ha fijado lo bug que hay, y también es posible vino tiene otros errores en eso. Los archivos relevantes son dlls/winmm/winmm.c, dlls/winmm/lolvldrv.c, y posiblemente otros. ¡Buena suerte!

+0

Normalmente lo hago, de hecho, contribuí con Wine en el pasado, y me encanta tenerlo como una forma de documentación. Dudo que Wine tenga el mismo error en este caso, sin embargo, la compatibilidad de errores en Wine solo va tan lejos. –

3

Ahora, la razón por la que GlobalHandle() bombas no se debe a un daño de montón, como sospechaba en un principio - es porque waveOutWrite() devuelve sin estableciendo el primer puntero en la estructura interna a un puntero válido Sospecho que libera la memoria señalada por ese puntero antes de que regresa, pero aún no lo he desmontado .

Puedo reproducir esto con su código en mi sistema. Veo algo similar a lo que informó Johannes.Después de la llamada a WaveOutWrite, hdr-> reserved normalmente contiene un puntero a la memoria asignada (que parece contener el nombre del dispositivo de salida de onda en Unicode, entre otras cosas).

Pero ocasionalmente, después de regresar de WaveOutWrite(), el byte al que apunta hdr->reserved se establece en 0. Este es normalmente el byte menos significativo de ese puntero. El resto de los bytes en hdr->reserved son correctos, y el bloque de memoria al que normalmente apunta sigue asignado y sin corromper.

Probablemente esté siendo golpeado por otro hilo - Puedo detectar el cambio con un punto de corte condicional inmediatamente después de la llamada a WaveOutWrite(). Y el punto de corte de depuración del sistema está ocurriendo en otro hilo, no en el controlador de mensajes.

Sin embargo, no puedo provocar que se produzca el punto de interrupción de depuración del sistema si utilizo una función de devolución de llamada en lugar de la bomba de mensajes de Windows. (fdwOpen = CALLBACK_FUNCTION en WaveOutOpen()) Cuando lo hago de esta manera, mi manejador OnWOMDone es llamado por un hilo diferente, posiblemente el responsable de la corrupción.

Así que creo que hay un error, ya sea en Windows o en el controlador, pero creo que puede evitar manipular WOM_DONE con una función de devolución de llamada en lugar de la bomba de mensajes de Windows.

+0

Echa un vistazo a la respuesta de Stefan que apunta a un informe de error que p proporciona más información sobre esto. Tiene razón, ya sea en Windows o en el controlador, y su análisis es casi idéntico al del informe de errores. Última información que no puedo obtener de nadie aquí: ¿Qué controlador de audio usas? –

+0

Estoy usando SoundMAX Integrated Digital Audio de Analog Devices 5.12.1.4060. Solo leí el informe de error después de hacer la mayor parte de este trabajo. Publiqué de todos modos porque pensé que podría encontrar la solución útil. – AShelly

+0

Gracias. Estoy usando un controlador diferente, entonces o ambos controladores tienen el mismo error, o esto está en Windows, después de todo. Francamente, no veo cómo puede ser algo más que un error de Windows, según la descripción del informe de errores. –

0

¿Qué pasa con el hecho de que no puede llamar a las funciones winmm desde la devolución de llamada? MSDN no menciona tales restricciones sobre los mensajes de ventana, pero el uso de los mensajes de ventana es similar a la función de devolución de llamada. Posiblemente, internamente se implementa como una función de devolución de llamada desde el controlador y esa devolución de llamada hace SendMessage. Internamente, Waveout debe mantener la lista de encabezados enlazados que se escribieron usando waveOutWrite; Entonces, supongo que:

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

establece los punteros anteriores/siguientes de la lista vinculada o algo como esto. Si escribe más búferes, entonces si comprueba los punteros y si alguno de ellos apuntan el uno al otro, entonces mi conjetura es probablemente correcta.

Las fuentes múltiples en la web mencionan que no es necesario preparar/preparar los mismos encabezados repetidamente. Si comenta el encabezado Prepare/unprepare en el ejemplo original, entonces parece funcionar bien sin ningún problema.

+0

El uso de mensajes de ventana es diferente de usar una función de devolución de llamada en un aspecto importante: no se invocan en el momento de la interrupción, y puede usar casi cualquier API de ellos. –

+0

En cualquier caso, incluso si trata el mensaje de ventana como una función de devolución de llamada, seguirá generando la misma excepción. La eliminación del encabezado repetetive prepera/unprepare parece ser la única forma de solucionar el problema. – pps

+0

Tendré que revisar la documentación, pero por lo que puedo recordar, las llamadas prepararse/no prepararse son necesarias. –

0

He resuelto el problema mediante el sondeo de la reproducción de sonido y los retrasos:

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

Cuestiones relacionadas