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