再生は、プレゼンテーションまたはレンダリングと呼ばれる場合があります。 これらは、サウンド以外の種類のメディアにも使用される一般的な用語です。 基本的な機能は、連続するデータを最終的にユーザーが知覚できる場所に配信することです。 サウンドのように、データが時間ベースの場合は、正しい速度で配信されなければなりません。 サウンド再生が中断されると大きなクリック・ノイズやもどかしい歪みが生じることが多いので、サウンドではデータ・フローの速度を維持することが、ビデオの場合よりも重要です。 Java Sound APIは、非常に長いサウンドの場合にも、アプリケーション・プログラムがサウンドを円滑に再生するために役立つよう設計されています。
前章では、オーディオ・システムまたはミキサーからラインを取得する方法について説明しました。 この章では、ラインを介してサウンドを再生する方法について説明します。
サウンドの再生に使用できるラインには、ClipとSourceDataLineの2種類があります。 この2つのインタフェースについては、第2章「Sampledパッケージの概要」の「Lineインタフェースの階層」に簡単な説明があります。 2つの主な相違点は、Clipでは再生の前に一度にすべてのサウンド・データを指定するのに対し、SourceDataLineでは再生中にデータをバッファに継続的に書き込むことです。 多くの場合、ClipとSourceDataLineのどちらも使用できますが、特定の状況でどちらのラインの方が適しているのかを判断するには、次の条件を参考にしてください。
Clipを使用します。
たとえば、短いサウンド・ファイルはクリップに読み込みます。 サウンドを2回以上再生する場合、特に、ループ(サウンドの一部または全体を繰り返すこと)して再生する場合は、SourceDataLineよりもClipの方が便利です。 サウンドの任意の位置から再生を開始する必要がある場合は、Clipインタフェースに用意されたメソッドを使うと簡単です。 さらに、Clipからの再生は、通常、バッファを使うSourceDataLineからの再生よりも待ち時間が短くなります。 つまり、サウンドはクリップにあらかじめロードされているので、バッファがいっぱいになるまで待つことなくただちに再生を開始できます。
SourceDataLineを使用します。
後者の例として、サウンド入力のモニタリング、つまりサウンドを取り込みながら再生する場合を考えます。 入力オーディオを出力ポートに直接送り返せるミキサーがない場合は、取り込んだデータをアプリケーション・プログラムで処理してからオーディオ出力ミキサーに送る必要があります。 この場合は、ClipよりもSourceDataLineの方が適しています。 前もって知ることのできないサウンドの例として、ユーザーの入力に応答してサウンド・データを対話形式で合成または操作する場合があります。 たとえば、ユーザーがマウスを動かすのに伴ってサウンドが別のサウンドに「モーフィング」することにより聴覚的なフィードバックを与えるゲームを考えます。 サウンド変形の動的な性質により、アプリケーション・プログラムは再生が開始する前にすべてのサウンド・データを提供するのではなく、再生中にサウンド・データを継続的に更新する必要があります。
Clipは、第3章「オーディオ・システム・リソースへのアクセス」の「目的の種類のラインの取得」で説明した方法で取得します。最初の引数をClip.classとしてDataLine.Infoオブジェクトを構築し、AudioSystemまたはMixerのgetLineメソッドに引数としてこのDataLine.Infoを渡します。
ラインを取得することは、単にそのラインを参照するための方法を得ることであり、getLineがそのラインを取得者のために実際に予約するわけではありません。 ミキサーで利用できる、目的の種類のライン数は限られているので、getLineを呼び出してクリップを取得したあと、再生を始める前に、ほかのアプリケーション・プログラムが飛入りし、クリップを横取りする可能性があります。 実際にクリップを使用するには、次のClipメソッドのいずれかを呼び出すことにより、自分のプログラムが排他的に使用するためにそのクリップを予約する必要があります。
void open(AudioInputStream stream) void open(AudioFormat format, byte[] data, int offset, int bufferSize)上記の2番目の
openメソッドにはbufferSize引数がありますが、SourceDataLineとは異なり、Clipには新しいデータをバッファに書き込むメソッドがありません。 ここでは、bufferSize引数は、クリップにロードするバイト配列のバイト数を指定するためのものです。 SourceDataLineのバッファのような、さらに続けてデータをロードできるバッファではありません。
クリップをオープンしたら、Clipの setFramePositionまたはsetMicroSecondPositionメソッドを使って、データ内のどの位置から再生を開始するかを指定できます。 指定しない場合は、先頭から再生が開始します。 setLoopPointsメソッドを使って、再生が循環するように設定することもできます。
再生の準備ができたら、startメソッドを呼び出します。 クリップを停止または一時停止するにはstopメソッドを呼び出し、再生を再開するにはふたたびstartを呼び出します。 クリップは再生を停止したメディア位置を記憶しているので、一時停止と再開の明示的なメソッドは必要ありません。 停止した位置からの再開を行わない場合は、すでに説明したフレームまたはマイクロ秒の位置決めメソッドを使って、クリップを先頭または希望する位置まで「巻き戻す」ことができます。
Clipのボリューム・レベルと活動ステータスがアクティブかアクティブでないかは、DataLineのgetLevelメソッド、isActiveメソッドをそれぞれ呼び出すことによりモニターできます。 アクティブなClipとは、現在サウンドを再生しているクリップです。
SourceDataLineを取得することは、Clipを取得することに似ています。 第3章「オーディオ・システム・リソースへのアクセス」の「目的の種類のラインの取得」を参照してください。
SourceDataLineをオープンする目的は、Clipの場合と同様に、ラインを予約するためです。 ただし、DataLineから継承した別のメソッドを使用します。
void open(AudioFormat format)
Clipの場合とは異なり、SourceDataLineをオープンするときは、そのラインにサウンド・データを関連付けません。 その代わり、再生するオーディオ・データの形式を指定します。 デフォルトのバッファ長がシステムにより選択されます。
また、次の変数を使って特定のバッファ長をバイト単位で指定することもできます。
void open(AudioFormat format, int bufferSize)
類似メソッドとの整合性を維持するために、buffersize引数はバイト数で表しますが、フレームの整数値に対応していなければなりません。
バッファ・サイズを決める方法について考えます。 バッファ・サイズはプログラムの条件により異なります。
まず、バッファ・サイズは小さい方が待ち時間が少なくなります。 新しいデータを送ったときに、より早く再生されます。 高度に対話的なプログラムなど、アプリケーション・プログラムによっては、このような応答の速さが重視されます。 たとえば、ゲームでは再生の開始は視覚的なイベントと正確に同期する必要があります。 このようなプログラムでは、待ち時間を0.1秒未満にすることが要求されます。 別の例として、会議アプリケーションでは再生と取込みの両方で遅れを防ぐ必要があります。 ただし、多くのアプリケーション・プログラムでは、サウンドがいつ再生を開始するかはあまり問題にならないので、ユーザーが困惑するほどの遅れでないかぎり、1秒程度の遅れがあっても構いません。 このことは、1秒分のバッファを使って大きなオーディオ・ファイルをストリーミングするアプリケーション・プログラムにも当てはまります。 サウンドそのものは長時間途切れずに再生され、高度に対話的な操作を伴わないため、再生の開始にわずかな時間がかかってもユーザーは気にしないでしょう。
反面、バッファ・サイズが小さい場合は、バッファに十分な速度でデータを書き込めず、書込みに失敗する可能性が大きくなります。 書込みに失敗するとオーディオ・データに不連続部が発生します。不連続部は、クリック・ノイズや付随音を生じます。 また、バッファ・サイズが小さい場合は、バッファを常に満たしておくためにプログラムの動作が激しくなり、CPUを集中的に使用することになります。 このため、他のプログラムはもとより、そのプログラムの他のスレッドの実行も遅くなります。
したがって、最適なバッファ・サイズとは、待ち時間がアプリケーション・プログラムで許容できる程度に短く、かつ、バッファのアンダーフローの危険が少なく、CPUリソースの不必要な消費が避けられるようなサイズです。 会議アプリケーションのようなプログラムでは、音の再現性の低さよりも遅れの方が問題視されるので、バッファ・サイズは小さい方が適しています。 音楽のストリーミングでは、最初の遅れは許容されますが付随音は許容されません。 このため、音楽のストリーミングでは1秒程度の大きなバッファ・サイズが望まれます。 ただし、サンプリング・レートが高い場合は、DataLine APIのバッファ・サイズの計量単位であるバイト数に換算すると、バッファ・サイズは大きくなります。
上記のopenメソッドを使う代わりに、Lineの open()メソッドを使って、引数を指定しないでSourceDataLineをオープンすることもできます。 この場合、ラインはデフォルトのオーディオ形式とバッファ・サイズでオープンされます。 ただし、このオーディオ形式とバッファ・サイズはあとから変更できません。 ラインのデフォルトのオーディオ形式とバッファ・サイズは、ラインがオープンされていなくても、DataLineの getFormatメソッドとgetBufferSizeメソッドを呼び出して知ることができます。
SourceDataLineをオープンすると、サウンドの再生を開始できます。 再生を行うには、DataLineの startメソッドを呼び出してから、ラインの再生バッファにデータを繰返し書き込みます。
startメソッドにより、ラインはバッファにデータが入り次第、ただちに再生を開始できるようになります。 バッファには、次のメソッドを使ってデータを書き込みます。
int write(byte[] b, int offset, int length)
配列に対するオフセットは配列の長さと同様に、バイト単位で表されます。
ラインは、ミキサーへのデータ送信を可能なかぎり早期に開始します。 ミキサー自体がターゲットにデータを送るとき、SourceDataLineがSTARTイベントを生成します。 (Java Sound APIの一般的な実装では、ソース・ラインがミキサーにデータを送るまぎわとミキサーがターゲットにデータを送るまぎわとの間の遅延はサンプル1個の長さよりはるかに短いので、無視できる。) このSTARTイベントは、この後の「ラインのステータスのモニタリング」で説明するように、そのラインのリスナー・オブジェクトに送られます。 この時点でこのラインはアクティブ状態とみなされるので、DataLineのisActiveメソッドはtrueを返します。 これらはすべて、バッファに再生データが入ってはじめて行われる動作であり、startメソッドが呼び出された直後に行われるとは限りません。 新しいSourceDataLineに対してstartメソッドを呼び出してもバッファにデータを書き込まなければ、そのラインはアクティブにならず、STARTイベントも送られません。 ただし、この場合、DataLineのisRunningメソッドはtrueを返します。
ここで、バッファに書き込むデータの量と2回目のバッチ・データを送る時期を決める方法を考えます。 さいわい、2回目の書込みの呼出し時期を記録して最初のバッファの終わりと同期させる必要はなくなりました。 その代わりに、writeメソッドのブロックを利用することができるようになりました。
DataLineの availableメソッドが返す値で制限することができます。
SourceDataLineに書き込んで再生します。
// read chunks from a stream and write them to a source data
line
line.start();
while (total < totalToRead && !stopped)}
numBytesRead = stream.read(myData, 0, numBytesToRead);
if (numBytesRead == -1) break;
total += numBytesRead;
line.write(myData, 0, numBytesRead);
}
writeメソッドがブロックされないようにするには、まずavailableメソッドをループ内で呼び出して、ブロックされずに書き込めるバイト数を調べ、numBytesToRead変数をその数値に制限してから、ストリームからの読込みを行います。 この例では、writeメソッドがループの中で呼び出されており、ループの最後の繰返し時に、最後のバッファが書き込まれるまでループは終了しないので、ブロックは問題になりません。 ブロッキング技法を使うか使わないかにかかわらず、長いサウンドの再生中にプログラムがフリーズしているように見えることを防ぐために、独立したスレッドの再生ループをアプリケーション・プログラムのほかの部分から呼び出す方法があります。 ループの繰り返しごとに、ユーザーが再生の停止を要求したかどうかを調べることができます。 停止が要求されたら、上のコードではstoppedブール変数をtrueに設定する必要があります。
writeはすべてのデータの再生が終了する前に戻るので、再生が実際に終わった時期を知る方法が必要です。 1つの方法は、最後の1バッファ分のデータを書き込んだ後にDataLineのdrainメソッドを呼び出すことです。 このメソッドは、すべてのデータの再生が終わるまでブロックされます。 プログラムに制御が戻った時点で、必要に応じてラインを解放できます。その際にオーディオ・サンプルの再生が途中で打ち切られる心配はありません。
line.write(b, offset, numBytesToWrite); //this is the final invocation of write line.drain(); line.stop(); line.close(); line = null;もちろん、意図的に再生を途中で停止させることもできます。 たとえば、アプリケーション・プログラムにユーザー用の「停止」ボタンがある場合があります。 バッファの途中であっても再生をただちに停止するには
DataLineのstopメソッドを呼び出します。 未再生のデータはそのままバッファに残るので、続いてstartを呼び出すと、停止した位置から再生が再開されます。 再生を再開しない場合は、flushを呼び出して、バッファ内に残っているデータを破棄します。
データの流れが停止すると、SourceDataLineは常にSTOPイベントを生成します。これは、データの流れがdrain、stop、flushのいずれかのメソッドにより停止した場合にも、アプリケーション・プログラムが次のデータを書き込むためにwriteを再び呼び出す前に再生バッファの最後に到達したためにデータの流れが停止した場合にも当てはまります。 STOPイベントが生成されても、必ずしもstopメソッドが呼び出されたとは限らず、続いてisRunningを呼び出してもfalseが返されるとは限りません。 ただし、isActiveメソッドはfalseを返します。 (startメソッドがすでに呼び出されている場合は、STOPイベントが生成されてもisRunningメソッドはtrueを返し、stopメソッドが呼び出されたところで初めてfalseを返すことになります。) 重要なことは、STARTイベントとSTOPイベントは、isRunningではなくisActiveに対応しているということです。
サウンドの再生を開始したら、それがいつ終了するかを知る手段が必要になります。 1つの方法として、最後のバッファのデータを書き込んでからdrainメソッドを呼び出す方法をすでに説明しましたが、これは、SourceDataLineにしか使用できません。 SourceDataLinesとClipsの両方に使用できるもう1つの方法は、ラインがステータスを変更したときにそのラインから通知を受けるように登録することです。 これらの通知はLineEventオブジェクトの形で生成され、OPEN、CLOSE、START、STOPの4種類があります。
LineListenerインタフェースを実装するプログラムのどのオブジェクトも、この通知を受け取るように登録することができます。 LineListenerインタフェースの実装に必要なものは、LineEventオブジェクトを引数に取るupdateメソッドだけです。 このオブジェクトをそのラインのリスナーの1つとして登録するには、次のLineメソッドを呼び出します。
public void addLineListener(LineListener listener)
ラインのオープン時、クローズ時、開始時、および停止時に、必ずすべてのリスナーにupdateメッセージが送信されます。 オブジェクトは、自分が受け取るLineEventを問い合わせることができます。 まずLineEvent.getLineを呼び出して、停止したラインが該当するラインであることを確認します。 このケースではサウンドが終了したかどうかを知りたいので、LineEventの種類がSTOPかどうかを調べます。 STOPの場合は、最後まで到達したのか、それとも別の理由(ユーザーが「停止」ボタンをクリックしたなど)で停止されたのかを調べるために、LineEventオブジェクトに保存されているサウンドの現在位置を調べ、サウンドの長さ(わかっている場合)と比べます。ただし、停止の理由はプログラムの他の部分で調べることもできます。
同じラインについて、ラインがオープン、クローズ、または開始された時期を知る必要があるときも、同じメカニズムを使用します。 LineEventsは、ClipsとSourceDataLines以外の種類のラインによっても生成されます。 しかし、Portの場合は、イベントによってラインのオープンまたはクローズ状態を知ることはできません。 たとえば、Portは作成された当初からオープンされているので、プログラムはopenメソッドを呼び出さず、PortがOPENイベントを生成することはありません。 (第3章「オーディオ・システム・リソースへのアクセス」の「入出力ポートの選択」を参照。)
オーディオの複数トラックを同時に再生する場合は、正確に同じ時間にすべてのトラックの開始と停止を行うことが望まれます。 一部のミキサーにはsynchronizeメソッドによりこの機能が備わり、データ・ラインのグループに対するopen、close、start、stopなどの操作を単一のコマンドで行うことができるので、各ラインを個別に制御する必要はありません。 さらに、ラインに対する操作の精度を調節することができます。
特定のミキサーが、指定されたグループ・データ・ラインに対してこの機能を使用できるかどうかを調べるには、MixerインタフェースのisSynchronizationSupportedメソッドを呼び出します。
boolean isSynchronizationSupported(Line[] lines, boolean maintainSync)最初のパラメータは特定のデータ・ラインのグループを指定し、2番目のパラメータは同期を維持すべき精度を示します。 2番目のパラメータが
trueの場合、クエリーは、そのミキサーが指定されたラインを制御するときにサンプルどおりの精度を常に維持する機能があるかどうかを問い合わせます。そうでない場合、正確な同期は開始と停止の操作時にのみ必要で、再生全体を通じては必要ありません。
ソース・データ・ラインには、ゲイン、パン、リバーブ、およびサンプリング・レートのコントロールのような信号処理コントロールを備えているものがあります。 同様な機能、特にゲイン・コントロールが出力ポートにも備わっていることがあります。 ラインがそのようなコントロールを備えているかどうかを調べる方法、およびそれらの使用方法の詳細は、第6章「コントロールを使ったオーディオ処理」を参照してください。