2008-10-12 6 views
13

При изучении этой проблемы я нашел несколько упоминаний о следующем сценарии онлайн, неизменно в качестве неотвеченных вопросов на форумах программирования. Надеюсь, что публикация этого здесь будет, по крайней мере, служить для документирования моих результатов.Почему waveOutWrite() вызывает исключение в куче отладки?

Во-первых, симптом: Пока работает довольно стандартный код, который использует waveOutWrite() аудио выход PCM, я иногда получаю это когда работает под управлением отладчика:

[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  

Хотя очевидно подозреваемый будет куча коррупции где-то еще в коде, я узнал, что это не так. Кроме того, я был в состоянии воспроизвести эту проблему, используя следующий код (это часть приложения 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() пытается освободить указатель, выделенный waveOutWrite(), вызывая GlobalHandle()/GlobalUnlock() и друзей. Иногда, GlobalHandle() бомбы, как показано выше.

Теперь, причина, по которой бомбы GlobalHandle() не вызваны повреждением кучи, как я подозревал вначале - это потому, что waveOutWrite() возвращен без установки первого указателя во внутренней структуре на действительный указатель. Я подозреваю, что он освобождает память, указанную этим указателем, перед возвратом, но я еще не разобрал ее.

Это происходит только тогда, когда система воспроизведения волн на буферах низкая, поэтому я использую один заголовок, чтобы воспроизвести это.

На данный момент у меня довольно хороший аргумент в пользу того, что это ошибка в моем приложении. В конце концов, мое приложение даже не работает. Кто-нибудь видел это раньше?

Я вижу это на Windows XP SP2. Звуковая карта от SigmaTel, а версия драйвера - 5.10.0.4995.

Примечания:

Чтобы избежать путаницы в будущем я хотел бы отметить, что ответ свидетельствует о том, что проблема заключается в использовании таНос()/Free() для управления буферами воспроизводимого просто неправильно. Вы заметите, что я изменил код выше, чтобы отразить это предложение, чтобы предотвратить больше людей от такой же ошибки - это не имеет значения. Буфер, освобождаемый waveCompleteHeader(), не является тем, который содержит данные PCM, ответственность за освобождение буфера PCM лежит на приложении, и нет необходимости выделять его каким-либо определенным образом.

Кроме того, я уверен, что ни один из вызовов API waveOut, которые я использую, не работает.

В настоящее время я предполагаю, что это либо ошибка в Windows, либо в звуковом драйвере. Особые мнения всегда приветствуются.

+1

Я смутно помню, что видел что-то подобное в приложении Windows CE. Deleaker сообщил, что какая-то волновая функция имела утечку памяти. В конце концов, все было в порядке, и когда я закрыл, мне просто пришлось выпустить ресурс. У меня здесь нет кода. – OregonGhost 2008-10-12 17:11:52

ответ

0

Не уверен в этой конкретной проблеме, но вы считаете, что используете более совершенную межплатформенную аудио библиотеку? Есть много причуд с аудиопрограммами Windows, и эти библиотеки могут сэкономить вам много головных болей.

Примеры включают PortAudio, RtAudio и SDL.

+0

Мне нужен низкоуровневый доступ, а аудио-программирование на Windows мертво - API-интерфейс waveOut существует навсегда, я знаю его очень хорошо (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), наименее значимый байт указателя был установлен на 0, таким образом, недействительным указатель (пока куча еще не повреждена). Другими словами, что происходит что-то вроде:

  • (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 обычно содержит указатель на выделенную память (которая, как представляется, содержит, среди прочего, имя устройства выхода из системы в юникоде).

Но иногда, после возвращения из WaveOutWrite(), байт, на который указывает hdr->reserved, установлен в 0. Это обычно младший значащий байт этого указателя. Остальные байты в hdr->reserved в порядке, и блок памяти, на который он обычно указывает, по-прежнему выделяется и не прерывается.

Возможно, он сбивается другим потоком - я могу поймать изменение с условной точкой останова сразу после вызова WaveOutWrite(). И контрольная точка отладки системы встречается в другом потоке, а не в обработчике сообщений.

Однако я не могу заставить точку останова отладки системы произойти, если я использую функцию обратного вызова вместо насоса messsage для Windows. (fdwOpen = CALLBACK_FUNCTION in WaveOutOpen()) Когда я это делаю, мой обработчик OnWOMDone вызывается другой веткой - возможно, той, которая иначе отвечает за коррупцию.

Таким образом, я думаю, что есть ошибка, либо в окнах, либо в драйвере, но я думаю, что вы можете обойти WOM_DONE с помощью функции обратного вызова вместо насоса сообщений Windows.

0

Как насчет того факта, что вы не можете вызывать функции winmm из-за обратного вызова? MSDN не упоминает такие ограничения в отношении оконных сообщений, но использование оконных сообщений аналогично функции обратного вызова. Возможно, внутренне он реализован как функция обратного вызова от драйвера, и этот обратный вызов делает SendMessage. Внутри, всплеск должен поддерживать связанный список заголовков, которые были написаны с использованием 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

 Смежные вопросы

  • Нет связанных вопросов^_^