皆さんこんにちは、高橋と申します。
いつもお世話になっております。
現在、Winamp の DSP プラグインを制作中なのですが、
どうしてもわからないことがあり、開発が中断していて困っております。
また、内容として純粋なVC++の内容ではないのでここに書き込んでいいものか
迷ったのですが、他に質問できる場所がわからず書き込んだしだいです。
開発環境は、 OS:Windows XP, コンパイラ VC++ .NET, SDKのみで DLL を製作してま
す。
問題は、Winampのメニュー 「オプション」->「設定」->「プラグイン-DSP」
と進んでいくと、DSPプラグインの選択画面になると思います。
そこで、プラグインを 「なし」->「今回作ったプラグイン」と交互に変更(かなり激し
く)
した場合、Winamp がアクセス違反で不正終了してしまうというものです。
(0 番地をリードしたとか、訳のわからない番地だったりします)
発生するタイミングは絶対というわけではなく、再生時に切り替えると
どうやら飛んでしまうようです。(再生していない場合は不正終了しません)
(長文になりますので、次にわけて書き込ませて頂きます。)
再現性のチェックのために書いたプログラムは、以下のものです。
私なりに調査してみましたので、その内容も書き添えます。
・・・省略
float* buf0 = NULL;
float buf1[100];
// プラグイン初期化時に呼ばれる
int init(struct winampDSPModule* this_mod)
{
buf0 = new float[100];
return 0;
}
// プラグイン終了時に呼ばれる
void quit(struct winampDSPModule* this_mod)
{
if(buf0 != NULL){
delete[] buf0;
buf0 = NULL;
}
}
// init が実行された後、繰り返し実行される
int DSPProc(struct winampDSPModule* this_mod, short int* samples, int
numsamples, int bps, int nch, int srate)
{
float buf2[100];
// *1
for(int i = 0 ; i < 100 - 1 ; i ++){
buf0[i] = 0;
for(int q = 0 ; q < numsamples * 2 - 1 ; q ++){
buf0[i] += samples[q] / 2;
}
}
// *2
for(int i = 0 ; i < 100 ; i ++){
buf1[i] = 0;
for(int q = 0 ; q < numsamples * 2 - 1 ; q ++){
buf1[i] += samples[q] / 2;
}
}
// *3
for(int i = 0 ; i < 100 ; i ++){
buf2[i] = 0;
for(int q = 0 ; q < numsamples * 2 - 1 ; q ++){
buf2[i] += samples[q] / 2;
}
}
// *4
return numsamples;
}
* winampDSPGetHeader2 など他のプラグインの必須関数は、多分問題ないと思うので
省かさせて頂きました。
(さらに続く・・・すいません)
DSPProc が、実際の音声データを修正するためにWinamp から呼ばれる関数です。
再生時のみの問題なので、多分 DSPProc がおかしいのではないかとにらみ。調査してみ
ました。
(1) *4 のみを残して他をコメントアウト
何もせずに終了するというプログラムです。
この場合まったく問題ありませんでした。
(2) *1 のみを残して他をコメントアウト
初期化時に、動的にメモリを確保してそのバッファに対して操作を行うプログラムで
す。
(プログラムの内容は意味がなくただ単にCPU負荷を稼いでいるだけです)
この場合が、一番飛びやすいです。
(3) *2 のみを残して他をコメントアウト
グローバルなバッファに対して操作を行うプログラムです。
これも飛んでしまいます。
(4) *3 のみを残して他をコメントアウト
ローカルなバッファに対して操作を行うプログラムです。
これも飛んでしまいますが、 (2), (3) に比べると、頻度が低いです。
こうやって調査した結果、まともに動くのは (1) のみで、他のパターンは不安定になっ
てしまいます。
また、 DSPProc 内で特に重い処理を実行した場合は確実に不正終了してしまうようで
す。
(処理落ちで音がとぎれるくらいの処理や、Sleep(1) などとしてブロックした場合)
ここまででわかったこととして、
再生時にDSPプラグインを変更した場合、少しでも処理を入れると不正終了で落ちる可能
性がある。
ということです。
さらに、私以外の作ったDSPはどうなのかなと思い同じように、何度も変更していると
落ちてしまうプラグインもあったりしました。
Winamp の プラグインを作る際に何か特別に気をつけなくてはいけないこと
または、絶対してはならない等の重要事項があるのでしょうか?
それとも、これは仕様いう名のWinampのバグなのでしょうか?
(私が悪いんだと思いますが・・・)
どうぞ皆様よろしくお願い致します。
※ かなり長文になってしまいすいませんでした。
また、この掲示板にふさわしくない内容でしたら削除して頂いて結構です。
いろいろ考えたのですが、こういう事ではないかなということで
自己解決です・・・(ごまかしなのかもしれませんが)
1. 上記の結果からメモリの解放タイミングという件は考えられないので、
(ローカルのメモリ参照でも落ちているようなので)
解放済みのメモリを参照してとかのエラーではなさそう。
2. 他のDSPプラグイン(ネット上で広く配布されているもの)も同様の条件で不正終了を
起こす。
(これに関しては、そのDSPがバグを内包しているのかもしれませんが)
で、問題はWinamp側にあるのではないのかなと・・・
プラグインということなので LoadLibrary() で、問題のDSPをロードし
そのDLL内の問題の関数
init
quit
DSPProc
へのポインタを取得してその関数へアクセスを試みるはずです
(1) DSP が選択されたのでロードして、まずは初期化
init();
(2) 音声データを DSPProc の引数に設定して連続的に呼び出し
DSPProc
(3) 他のDSPが選択されたので quit() を呼び出して DSP 側に終了を通知し、DLL を フ
リー
ここで (2) が、他と別スレッドで実行されているならば問題が起こりえるのかなと
私なりのとりあえずの結論として・・・
スレッドの協調がうまく行われていなく (3) で問答無用に DLL を終了したため、
(2)のスレッドが DSPProc を呼び出してアクセス違反をおこした。
DSPProc の処理が重い場合、この危険性は高まるのではないかなと推測しました。
そこで、多少強引ですが DLL がデタッチされる際に DSP終了待ちをいれて
DLL 終了処理をブロックしてみました。
BOOL bBusy = FALSE;
BOOL APIENTRY DllMain(HINSTANCE hInstance, DWORD ul_reason_for_call, LPVOID
lpReserved)
{
switch(ul_reason_for_call){
case DLL_PROCESS_ATTACH :
break;
case DLL_THREAD_ATTACH :
break;
case DLL_THREAD_DETACH :
break;
case DLL_PROCESS_DETACH :
while(bBusy) { ::Sleep(1); } // DSP 処理中は デッタチ
出来ないようにする
// バッファの解放等の終了処理
break;
}
return TRUE;
}
int DSPProc(struct winampDSPModule* this_mod, short int* samples, int
numsamples, int bps, int nch, int srate)
{
bBusy = TRUE; // DSP 処理中
// DSP 処理
bBusy = FALSE; // DSP 処理終了
return numsamples;
}
結果、なんか異常終了しなくなりました・・・
これでいいのかはわからないのですが(怪しいです(^_^;)
ひとまずよしとして解決とします。
お騒がせしました。