前回は、P/ECEでストリーム再生を行う場合に発生する、問題の再現方法について説明しました。それは、PCMデータのサンプル数が64の倍数でないと、ストリーム再生が途中で止まったり、ハングアップしてしまう、というものでした。この問題の原因を調べるために、P/ECEカーネルのサウンドルーチンの動作を詳しく追いかけてみることにします。
P/ECEカーネルのサウンドルーチンは、四つのサウンドチャネルそれぞれについて、カーネル内部に管理用のワークエリアを持っています。ワークエリアの構造を示します。
四つのチャネルのワークエリアは同じ構造をしていて、サウンドルーチンの動作も四つのチャネルに対して同じ処理を四回繰り返すだけです。そこで、以下では一つ分のチャネルについてだけ説明することにします。
さてこれから、pceWaveDataOut() APIを使ってサウンド再生を開始したとき、カーネル内部のワークエリアがどのように使用されるのかを追って行こうと思います。まずアプリケーションプログラムが、自身の管理するメモリ上にPCEWAVEINFO構造体とPCMデータを用意し、その内容を初期化します。
サウンドチャネル番号と、初期化したPCEWAVEINFO構造体へのポインタを引数として、pceWaveDataOut() APIを呼び出します。
pceWaveDataOut(0, &wi[0]); /* チャネル0で鳴らす */呼び出されたカーネルは、指定されたチャネルが発声中か停止中かを調べます。チャネルが発声中か停止中かは、WAVEPLAY構造体のpwiフィールドがNULLかどうかで判断します。
上の図ではpwiフィールドがNULLなので、指定されたチャネルは停止中です。指定されたチャネルが停止中ならば、カーネルは、ワークエリアを次の図のように設定します。
ちょっとややこしいですね。言葉で説明すると、次の通りです。
pceWaveDataOut(0, &wi[1]); /* チャネル0で鳴らす */呼び出されたカーネルは、指定されたチャネルが発声中か停止中かを調べます。先程は、WAVEPLAY構造体のpwiフィールドがNULLだったので停止中でしたが、今度はpwiフィールドがNULLでないので発声中です。指定されたチャネルが発声中ならば、カーネルは、ワークエリアを次の図のように設定します。
言葉で説明すると、次の通りです。
二回目のpceWaveDataOut() API呼び出しでは、本物のPCEWAVEINFO構造体のnextフィールドは書き換えられず、カーネル内に複製されたPCEWAVEINFO構造体のnextフィールドだけが書き換えられました。それに対して、二回目のpceWaveDataOut() API呼び出しでは、本物のPCEWAVEINFO構造体のnextフィールドが書き換えられています。このように、アプリケーションプログラムが初期化したPCEWAVEINFO構造体のnextフィールドは、API呼び出しのタイミングによって、書き換えられたり書き換えられなかったりするので、ちょっと注意が必要です。
今回は、pceWaveDataOut() APIの動作を説明しました。複製されたPCEWAVEINFO構造体と本物のPCEWAVEINFO構造体、両方へのポインタがあったりして、ややこしいですね。なぜPCEWAVEINFO構造体を複製しているのかというと、発声処理の中でpDataとlenフィールドを書き換えるためです。たぶん、本物のPCEWAVEINFO構造体のpDataやlenフィールドを破壊しないための仕組みだと思うのですけれど、一方、nextフィールドは書き換えていたりして、一貫していない感じです。想像ですけれど、P/ECEカーネルの開発において、サウンドまわりの仕様はかなり終盤まで試行錯誤があったりして、ちょっと無理やりっぽい実装になってしまったのかなあ、と思います。
pceWaveDataOut() APIは、発声開始を要求するためのAPIで、実際の発声処理は割り込み処理の中で行われています。次回は、割り込み処理の中の発声処理の動作を追いかけてみようと思います。