13

この問題を調査しているときに、オンラインで次のシナリオについて複数の言及を見つけましたが、プログラミング フォーラムでは常に未回答の質問として扱われていました。これをここに投稿することが、少なくとも私の調査結果を文書化するのに役立つことを願っています.

まず、症状: 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 に、最初の 2 つのメンバーとして少なくとも 2 つのポインターを含む構造体のように見えるものへのポインターを設定します。最初のポインターは 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 */
}

通常、ヘッダーは、wdmaud.dll の内部関数である waveCompleteHeader() によってアプリケーションに返されます。waveCompleteHeader() は、GlobalHandle()/GlobalUnlock() などを呼び出して、waveOutWrite() によって割り当てられたポインターの割り当てを解除しようとします。上記のように、GlobalHandle() が爆発することがあります。

さて、最初に疑ったように、GlobalHandle() が爆発する理由は、ヒープの破損によるものではありません。内部構造の最初のポインターを有効なポインターに設定せずに waveOutWrite() が返されたためです。戻る前に、そのポインタが指すメモリを解放すると思われますが、まだ逆アセンブルしていません。

これは、Wave 再生システムのバッファーが不足している場合にのみ発生するようです。そのため、これを再現するために単一のヘッダーを使用しています。

この時点で、これが私のアプリケーションのバグであるということに対してかなり良いケースがあります。結局のところ、私のアプリケーションは実行さえしていません。誰もこれを見たことがありますか?

これは Windows XP SP2 で見られます。オーディオ カードは SigmaTel 製で、ドライバーのバージョンは 5.10.0.4995 です。

ノート:

将来の混乱を防ぐために、再生中のバッファを管理するために malloc()/free() を使用することに問題があることを示唆する答えは単に間違っていることを指摘したいと思います。より多くの人が同じ間違いを犯すのを防ぐために、提案を反映するために上記のコードを変更したことに注意してください。違いはありません。waveCompleteHeader() によって解放されるバッファーは、PCM データを含むものではなく、PCM バッファーを解放する責任はアプリケーションにあり、特定の方法で割り当てる必要はありません。

また、使用する waveOut API 呼び出しがどれも失敗しないようにしています。

現在のところ、これは Windows のバグか、オーディオ ドライバのバグであると推測しています。反対意見はいつでも大歓迎です。

4

9 に答える 9

3

さて、GlobalHandle()が爆弾を投下する理由は、最初に推測したように、ヒープの破損によるものではありません。これは、waveOutWrite()が、内部構造の最初のポインターを有効なポインターに設定せずに戻ったためです。戻る前に、そのポインタが指すメモリを解放しているのではないかと思いますが、まだ分解していません。

私のシステムであなたのコードを使ってこれを再現することができます。ヨハネスが報告したものに似たものが見えます。WaveOutWriteの呼び出し後、hdr-> reservedは通常、割り当てられたメモリへのポインタを保持します(特に、UnicodeでWave Outデバイス名が含まれているように見えます)。

ただし、WaveOutWrite()から戻った後、が指すバイトhdr->reservedが0に設定される場合があります。これは通常、そのポインターの最下位バイトです。の残りのバイトはhdr->reserved正常であり、通常それが指すメモリのブロックはまだ割り当てられており、破損していません。

おそらく別のスレッドによって破壊されています-WaveOutWrite()を呼び出した直後に、条件付きブレークポイントで変更をキャッチできます。また、システムデバッグブレークポイントは、メッセージハンドラではなく、別のスレッドで発生しています。

ただし、Windowsメッセージポンプの代わりにコールバック関数を使用すると、システムデバッグブレークポイントを発生させることができません。(fdwOpen = CALLBACK_FUNCTIONWaveOutOpen()内)このようにすると、OnWOMDoneハンドラーが別のスレッド(おそらく破損の原因となるスレッド)によって呼び出されます。

したがって、Windowsまたはドライバーのいずれかにバグがあると思いますが、Windowsメッセージポンプの代わりにコールバック関数を使用してWOM_DONEを処理することで、回避できると思います。

于 2009-02-05T02:37:38.107 に答える
2

この問題はあなただけではありません: http ://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID = 100589

于 2009-01-31T15:06:29.067 に答える
1

私は同じ問題を抱えており、自分でいくつかの分析を行いました:

waveOutWrite() は、354 バイトのヒープ領域へのポインターを割り当て (つまり、GlobalAlloc)、header.reserved が指すデータ領域に正しく格納します。

しかし、このヒープ領域が再び解放されるとき (あなたの分析によると、waveCompleteHeader() で; 私自身は wdmaud.drv のシンボルを持っていません)、ポインターの最下位バイトがゼロに設定されているため、無効になりますポインター (ヒープがまだ破損していない間)。つまり、次のようなことが起こります。

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

だから私はあなたの声明に一点同意しません.waveOutWrite()は最初に有効なポインタを格納します。ポインターは、後で別のスレッドから破損するだけです。おそらくそれは、後でこのヒープ領域を解放しようとするのと同じスレッド (mxdmessage) ですが、ゼロバイトが格納されているポイントをまだ見つけていません。

これはあまり頻繁には発生せず、同じヒープ領域 (同じアドレス) が以前に正常に割り当てられ、割り当て解除されています。これはシステム コードのどこかにバグがあると確信しています。

于 2009-01-16T15:28:35.820 に答える
0

コールバック内からwinmm関数を呼び出すことが許可されていないという事実はどうですか?MSDNは、ウィンドウメッセージに関するそのような制限については言及していませんが、ウィンドウメッセージの使用法は、コールバック関数に似ています。おそらく、内部的にはドライバーからのコールバック関数として実装され、そのコールバックはSendMessageを実行します。内部的には、waveoutはwaveOutWriteを使用して書き込まれたヘッダーのリンクリストを維持する必要があります。だから、私はそれを推測します:

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

リンクリストなどの前/次のポインタを設定します。さらに多くのバッファーを作成する場合、ポインターをチェックし、それらのいずれかが相互にポイントしている場合、私の推測はおそらく正しいでしょう。

Web上の複数のソースでは、同じヘッダーを繰り返し準備解除/準備する必要はないと述べています。元の例でPrepare/unprepareヘッダーをコメントアウトすると、問題なく正常に機能しているように見えます。

于 2009-08-20T02:47:25.853 に答える
0

Wineのソースコードを確認すると役立つ場合がありますが、Wineがバグを修正した可能性があり、Wineに他のバグが含まれている可能性もあります。関連するファイルは、dlls / winmm / winmm.c、dlls / winmm / lolvldrv.c、および場合によっては他のファイルです。幸運を!

于 2009-01-31T15:45:19.627 に答える
0

Application Verifier を使用して、何が起こっているかを把握します。疑わしいことをすると、はるかに早くそれを検出できます。

于 2009-01-30T16:32:26.663 に答える
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);

waveOut インターフェイスを使用して Windows でオーディオを再生する

于 2016-03-18T10:42:33.813 に答える
0

最初に行うことは、waveOutX 関数からの戻り値を確認することです。それらのいずれかが失敗した場合 (これは、説明したシナリオを考えると不合理ではありません)、それでも続行した場合、問題が発生し始めることは驚くべきことではありません。私の推測では、ある時点で waveOutWrite が MMSYSERR_NOMEM を返していると思います。

于 2008-10-17T14:48:49.450 に答える
0

この特定の問題についてはよくわかりませんが、高レベルのクロスプラットフォーム オーディオ ライブラリの使用を検討しましたか? Windows のオーディオ プログラミングには多くの癖がありますが、これらのライブラリを使用すると頭痛の種を大幅に減らすことができます。

例には、PortAudioRtAudio、およびSDLが含まれます。

于 2008-10-14T01:56:04.800 に答える