Ответ 1
Вы не одиноки с этой проблемой: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100589
Во время исследования этой проблемы я нашел несколько упоминаний о следующем сценарии онлайн, неизменно в качестве неотвеченных вопросов на форумах программирования. Я надеюсь, что публикация этого здесь будет, по крайней мере, служить для документирования моих результатов.
Во-первых, симптом: во время запуска довольно стандартного кода, который использует 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.
Примечания:
Чтобы предотвратить путаницу в будущем, я хотел бы указать, что ответ, предполагающий, что проблема заключается в использовании функции malloc()/free() для управления воспроизводимыми буферами, просто неверна. Вы заметите, что я изменил код выше, чтобы отразить это предложение, чтобы предотвратить больше людей от такой же ошибки - это не имеет значения. Буфер, освобождаемый waveCompleteHeader(), не является тем, который содержит данные PCM, ответственность за освобождение буфера PCM лежит в приложении, и нет необходимости выделять его каким-либо определенным образом.
Кроме того, я уверен, что ни один из вызовов API waveOut, которые я использую, не работает.
В настоящее время я предполагаю, что это либо ошибка в Windows, либо в звуковой драйвер. Особые мнения всегда приветствуются.
Вы не одиноки с этой проблемой: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100589
Теперь причина, по которой GlobalHandle() бомбы не из-за кучи коррупции, как я подозревал вначале - это потому, что waveOutWrite() возвращается без установка первого указателя в внутренней структуры к действительному указателю. Я подозреваю, что он освобождает память указанный указателем до вернувшись, но я не разобрался он еще.
Я могу воспроизвести это с вашим кодом в моей системе. Я вижу нечто похожее на то, что сообщил Йоханнес. После вызова WaveOutWrite, hdr- > reserved обычно содержит указатель на выделенную память (которая, как представляется, содержит, среди прочего, имя устройства выхода из системы в юникоде).
Но иногда, возвращаясь из WaveOutWrite(), байт, на который указывает hdr->reserved
, устанавливается в 0. Это обычно младший значащий байт этого указателя. Остальные байты в hdr->reserved
в порядке, и блок памяти, который он обычно указывает, все еще распределен и не поврежден.
Вероятно, он сбивается другим потоком - я могу поймать изменение с условной точкой останова сразу после вызова WaveOutWrite(). И контрольная точка отладки системы встречается в другом потоке, а не в обработчике сообщений.
Тем не менее, я не могу заставить контрольную точку отладки системы возникать, если я использую функцию обратного вызова вместо насоса messsage. (fdwOpen = CALLBACK_FUNCTION
в WaveOutOpen())
Когда я делаю это так, мой обработчик OnWOMDone вызывается другим потоком - возможно, тем, кто иначе отвечает за коррупцию.
Итак, я думаю, что есть ошибка, либо в окнах, либо в драйвере, но я думаю, что вы можете обойтись, обратившись к WOM_DONE с функцией обратного вызова вместо насоса сообщений Windows.
Я вижу ту же проблему и сам сделал анализ:
waveOutWrite() выделяет (то есть GlobalAlloc) указатель на область кучи 354 байта и правильно сохраняет его в области данных, на которую указывает header.reserved.
Но когда эта область кучи должна быть снова освобождена (в waveCompleteHeader(), согласно вашему анализу, у меня нет символов для wdmaud.drv), младший значащий байт указателя был установлен на ноль, тем самым аннулируя указатель (пока куча еще не повреждена). Другими словами, что-то вроде:
Поэтому я не согласен с вашими утверждениями в один момент: waveOutWrite() сначала сохраняет действительный указатель; указатель только потом испортится из другого потока. Вероятно, тот же самый поток (mxdmessage), который позже пытается освободить эту область кучи, но я еще не нашел точку, в которой хранится нулевой байт.
Это происходит не очень часто, и одна и та же область кучи (тот же адрес) успешно была выделена и освобождена ранее. Я вполне уверен, что это ошибка в системном коде.
Не уверен в этой конкретной проблеме, но считаете ли вы, что используете более совершенную кросс-платформенную аудио библиотеку? Есть много причуд с аудиопрограммами Windows, и эти библиотеки могут сэкономить вам много головных болей.
Первое, что я сделал бы, это проверить возвращаемые значения из функций waveOutX. Если какой-либо из них терпит неудачу, что не является необоснованным, учитывая описанный вами сценарий, и вы продолжаете, несмотря на это, неудивительно, что все начинает идти не так. Я предполагаю, что waveOutWrite в какой-то момент возвращает MMSYSERR_NOMEM.
Используйте Application Verifier, чтобы выяснить, что происходит, если вы делаете что-то подозрительное, оно будет ловить его намного раньше.
Может быть полезно посмотреть исходный код для Wine, хотя возможно, что Wine исправил всю ошибку, и это также возможно У Вина есть другие ошибки. Соответствующими файлами являются dlls/winmm/winmm.c, dlls/winmm/lolvldrv.c и, возможно, другие. Удачи!
Как насчет того, что вам не позволено вызывать функции winmm из-за обратного вызова? MSDN не упоминает такие ограничения в отношении оконных сообщений, но использование оконных сообщений аналогично функции обратного вызова. Возможно, внутренне он реализован как функция обратного вызова от драйвера, и этот обратный вызов делает SendMessage. Внутри, всплеск должен поддерживать связанный список заголовков, которые были написаны с использованием waveOutWrite; Итак, я предполагаю, что:
hdr->reserved = (Undocumented*)calloc(sizeof(Undocumented));
устанавливает предыдущие/следующие указатели связанного списка или что-то вроде этого. Если вы пишете больше буферов, то, если вы проверите указатели, и если какой-либо из них указывает друг на друга, то, скорее всего, это будет правильно.
Несколько источников в Интернете упоминают, что вам не нужно повторно создавать/готовить те же заголовки. Если вы закомментируете заголовок Prepare/unprepare в исходном примере, то он работает нормально, без каких-либо проблем.
Я решил проблему, опросив звук воспроизведения и задержки:
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);
Воспроизведение аудио в Windows с использованием интерфейса waveOut