再生は、プレゼンテーションまたはレンダリングと呼ばれる場合があります。これらは、サウンド以外の種類のメディアにも使用される一般的な用語です。基本的な機能は、連続するデータを最終的にユーザーが知覚できる場所に配信することです。サウンドのように、データが時間ベースの場合は、正しい速度で配信されなければなりません。サウンド再生が中断されると大きなクリック・ノイズやもどかしい歪みが生じることが多いので、サウンドではデータ・フローの速度を維持することが、ビデオの場合よりも重要です。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章「コントロールを使ったオーディオ処理」を参照してください。