第13章「サービス・プロバイダ・インタフェースの概要」に詳しい説明がありますが、Java Sound APIには、javax.sound.sampled.spiとjavax.sound.midi.spiという2つのパッケージが入っています。これらのパッケージは、サウンド・サービスの開発者が使用する抽象クラス群を定義しています。 サービス・プロバイダは、これらの抽象クラス群の中の1つのクラスのサブクラスを実装およびインストールすることにより新しいサービスを登録して、実行システムの機能性を拡張します。 この章では、サンプリング・オーディオを処理する新しいサービスを提供するために、実際にjavax.sound.sampled.spiパッケージを使用する方法について説明していきます。
既存のオーディオ・サービスだけをアプリケーション・プログラムで使用するプログラマは、この章を省略しても差し支えありません。 インストール済のオーディオ・サービスをアプリケーション・プログラムで使用する方法については、このプログラマーズ・ガイドの第I部「サンプリング・オーディオ」を参照してください。 この章では、インストール済みのオーディオ・サービスにアクセスするためにアプリケーション・プログラムが呼び出すJava Sound APIメソッドについての知識があることを前提とします。
javax.sound.sampled.spiパッケージには次の4つの抽象クラスが存在し、サンプリング・オーディオ・システムを提供する4種類のサービスを表しています。
AudioFileWriterは、サウンド・ファイル書込みサービスを提供します。 これらのサービスにより、アプリケーション・プログラムはオーディオ・データのストリームを特定のタイプのファイルに書き込むことができます。 AudioFileReaderは、ファイル読込みサービスを提供します。 これらのサービスにより、アプリケーション・プログラムはサウンド・ファイルの特性を確かめ、ストリームを取得して、そこからファイルのオーディオ・データを読み込むことができます。 FormatConversionProviderは、オーディオ・データ形式の変換サービスを提供します。 これらのサービスにより、アプリケーション・プログラムはオーディオ・ストリームのデータ形式をほかの形式に変換することができます。 MixerProviderは、特定の種類のミキサー管理を提供します。 このメカニズムにより、アプリケーション・プログラムは特定の種類のミキサーの情報を取得し、そのミキサーのインスタンスにアクセスすることができます。
サービスのインスタンスは、本質的に、アプリケーションの開発者から二重に隔離されています。 アプリケーション・プログラムが、ミキサーや形式コンバータなどのサービス・オブジェクトのオーディオ処理タスクに必要なインスタンスを直接作成することはありません。 また、これらのオブジェクトを管理するSPIクラスから直接オブジェクトを要求することもありません。 アプリケーション・プログラムはjavax.sound.sampledパッケージ内のAudioSystemオブジェクトに対して要求を行い、AudioSystemはSPIオブジェクトを使ってこれらのクエリーとサービス・リクエストを処理します。
新しいオーディオ・サービスの存在は、ユーザーとアプリケーション・プログラマに対しては完全に透過的です。 アプリケーション参照はすべてjavax.sound.sampledパッケージの標準オブジェクト、主にAudioSystemによって行われ、新しいサービスによって提供される特殊処理は完全に隠されます。
この章でも、前章と同様に、新しいSPIサブクラスをAcmeMixer、AcmeMixerProviderなどの名前で呼びます。
最初に、比較的簡単なSPIクラスの1つであるAudioFileWriterについて説明します。
AudioFileWriterのメソッドを実装しているサブクラスは、クラスでサポートされるファイル形式やファイル・タイプのクエリーを処理するために、一連のメソッドの実装を提供しなければなりません。また、サブクラスは、提供オーディオ・データ・ストリームを実際にFileまたはOutputStreamに書き出すメソッドも提供する必要があります。
AudioFileWriterには、基底クラスに固定実装を持つ次の2つのメソッドが含まれています。
boolean isFileTypeSupported(AudioFileFormat.Type fileType)
boolean isFileTypeSupported(AudioFileFormat.Type fileType,
AudioInputStream stream)
1つ目のメソッドは、このファイル・ライターが指定された種類のサウンド・ファイルを書き込むことができるかどうかを呼出し側に通知するメソッドです。 このメソッドは汎用クエリーです。ファイル・ライターに適切なオーディオ・データが渡されることを前提として、そのファイル・ライターがその種類のファイルを書き込める場合は、trueを返します。 ただし、ファイルを書き込めるかどうかは、ファイル・ライターに渡される特定のオーディオ・データ形式に依存することがあります。 1つのファイル・ライターですべてのオーディオ・データ形式をサポートするとは限らず、また、ファイル形式自体による制約もあります。 すべての種類のオーディオ・データをすべての種類のサウンド・ファイルに書き込めるとは限りません。 そのため、2つ目のメソッドはさらに細かい指定が可能で、特定のAudioInputStreamを特定のタイプのファイルに書き込めるかどうかを問い合わせます。
通常は、これらの2つの具象メソッドをオーバーライドする必要はありません。 それぞれのメソッドは単なるラッパーであり、2つのクエリー・メソッドの一方を呼び出し、返された結果の繰返しを行います。 これらの2つのクエリー・メソッドは抽象メソッドなので、サブクラスに実装する必要があります。
abstract AudioFileFormat.Type[] getAudioFileTypes()
abstract AudioFileFormat.Type[]
getAudioFileTypes(AudioInputStream stream)
これらのメソッドは、上記の2つのメソッドに直接対応します。 各メソッドは、サポートされるすべてのファイル・タイプの配列を返します。すべてのファイル・タイプとは、1つ目のメソッドの場合は、一般にサポートされているすべてのファイル・タイプを意味し、2つ目のメソッドの場合は、特定のオーディオ・ストリームでサポートされるすべてのファイル・タイプを意味します。 前者のメソッドの典型的な実装では、単にそのファイル・ライターのコンストラクタが初期化する配列を返します。 後者のメソッドの実装では、ストリームのAudioFormatオブジェクトを検査して、要求されたファイル・タイプがサポートしているデータ形式かどうかを確かめます。
AudioFileWriterの最後の2つのメソッドは、実際のファイル書込み作業を行います。
abstract int write(AudioInputStream stream,
AudioFileFormat.Type fileType, java.io.File out)
abstract int write(AudioInputStream stream,
AudioFileFormat.Type fileType, java.io.OutputStream out)
これらのメソッドは、オーディオ・データを表すバイトのストリームを、3番目の引数で指定するストリームまたはファイルに書き込みます。 これが実際にどのように行われるかは、指定されたファイル・タイプの構造により異なります。 writeメソッドは、この形式のサウンド・ファイルに指定されている方法(標準形式のサウンド・ファイルまたは新しい独自の形式)で、ファイルのヘッダーとオーディオ・データを書き込まなければなりません。
AudioFileReaderクラスは、サブクラスに実装する必要のある6つの抽象クラスと、2種類のオーバーロード・メソッドで構成されています。このオーバーロード・メソッドはそれぞれ、File、URL、InputStreamのいずれか1つの引数を取ることができます。 1つ目のオーバーロード・メソッドは、指定されたファイルのファイル形式に関するクエリーを受け取ります。
abstract AudioFileFormat getAudioFileFormat(
java.io.File file)
abstract AudioFileFormat getAudioFileFormat(
java.io.InputStream stream)
abstract AudioFileFormat getAudioFileFormat(
java.net.URL url)
getAudioFileFormatメソッドの一般的な実装は、サウンド・ファイルのヘッダーを読み取って構文解析し、ファイル形式を確かめます。 ヘッダーのどのフィールドを読み込むかについては、AudioFileFormatクラスの説明を参照してください。また、ヘッダーの構文解析方法を理解するには、そのファイル・タイプの仕様を参照してください。
このメソッドにストリームを引数として提供する呼出し側は、ストリームはメソッドにより変更されないものとしているので、ファイル・リーダーは通常、最初にストリームにマークを付ける必要があります。 ヘッダーの最後まで読み込んだら、ファイル・リーダーはストリームを元の位置に戻さなければなりません。
2つ目のオーバーロード・メソッドAudioFileReaderは、AudioInputStreamを返すことによりファイルの読込みサービスを提供します。このストリームからファイルのオーディオ・データを読み込むことができます。
abstract AudioInputStream getAudioInputStream(
java.io.File file)
abstract AudioInputStream getAudioInputStream(
java.io.InputStream stream)
abstract AudioInputStream getAudioInputStream(
java.net.URL url)
一般に、getAudioInputStreamの実装はAudioInputStreamをファイルのデータ・チャンクの先頭(ヘッダーの後ろ)の位置に戻して、読込みの用意をします。 ただし、ファイル・リーダーが返すAudioInputStreamのオーディオ形式が、ファイルに含まれているデータをなんらかの方法でデコードしたデータのストリームを表すことがあります。 重要なのは、そのメソッドが返すストリームが、ファイルに含まれるオーディオ・データを読み込める形式になっているということです。 返されたAudioInputStreamオブジェクトにカプセル化されたAudioFormatは、呼出し側にそのストリームのデータ形式を通知します。この形式は、通常はファイル自体のデータ形式と同じですが、必ずしも同じであるとは限りません。
一般に、返されるストリームはAudioInputStreamのインスタンスです。AudioInputStreamをサブクラス化することが必要になることはほとんどありません。
FormatConversionProviderサブクラスは、あるオーディオ・データ形式を持つAudioInputStreamを別の形式のストリームに変換します。 前者(入力)のストリームをソース・ストリーム、後者(出力)のストリームをターゲット・ストリームと呼びます。 第2章「Sampledパッケージの概要」で説明したように、AudioInputStreamにはAudioFormatが含まれ、AudioFormatにはAudioFormat.Encodingオブジェクトで表される、特定の種類のデータ・エンコーディングが含まれています。 ソース・ストリーム内の形式とエンコーディングをそれぞれ「ソース形式」と「ソース・エンコーディング」と呼び、ターゲット・ストリーム内のそれらを同様に、「ターゲット形式」と「ターゲット・エンコーディング」と呼びます。
変換の作業は、FormatConversionProviderのオーバーロードされた抽象メソッド(getAudioInputStream)の中で行われます。 このクラスにはまた、ターゲットとソースのサポートされるすべての形式とエンコーディングの抽象クエリー・メソッドがあります。 特定の変換に関する問い合わせのためには、具象ラッパー・メソッドがあります。
次に、2種類のgetAudioInputStreamの形式を示します。
abstract AudioInputStream getAudioInputStream(
AudioFormat.Encoding targetEncoding,
AudioInputStream sourceStream)
および
abstract AudioInputStream getAudioInputStream(
AudioFormat targetFormat,
AudioInputStream sourceStream)
この2つの形式は、呼出し側が完全なターゲット形式を指定しているか、形式のエンコーディングのみを指定しているかによって、第1引数が異なります。
getAudioInputStreamの一般的な実装は、元の(ソース) AudioInputStreamをラップ・アラウンドするAudioInputStreamの新しいサブクラスを返し、readメソッドが呼び出されたときにそのデータに対してデータ形式変換を適用することにより動作します。 たとえば、AcmeCodecという新しいFormatConversionProviderサブクラスがAcmeCodecStreamという新しいAudioInputStreamサブクラスとともに動作する場合を考えます。
AcmeCodecの 2番目のgetAudioInputStreamメソッドの実装は次のようになります。
public AudioInputStream getAudioInputStream
(AudioFormat outputFormat, AudioInputStream stream) {
AudioInputStream cs = null;
AudioFormat inputFormat = stream.getFormat();
if (inputFormat.matches(outputFormat) ) {
cs = stream;
} else {
cs = (AudioInputStream)
(new AcmeCodecStream(stream, outputFormat));
tempBuffer = new byte[tempBufferSize];
}
return cs;
}
実際の形式変換は、AudioInputStreamのサブクラスである、返されたAcmeCodecStreamの、新しいreadメソッドの中で行われます。 返されたAcmeCodecStreamにアクセスするアプリケーション・プログラムは、単にこれをAudioInputStreamとして処理をするため、実装についての詳細な知識は必要ありません。
FormatConversionProviderの他のすべてのメソッドでは、そのオブジェクトがサポートする入出力のエンコーディングと形式に関する問合せが可能です。 次の4つのメソッドは、抽象メソッドです。実装する必要があります。
abstract AudioFormat.Encoding[] getSourceEncodings()
abstract AudioFormat.Encoding[] getTargetEncodings()
abstract AudioFormat.Encoding[] getTargetEncodings(
AudioFormat sourceFormat)
abstract AudioFormat[] getTargetFormats(
AudioFormat.Encoding targetEncoding,
AudioFormat sourceFormat)
すでに説明したAudioFileReaderクラスのクエリー・メソッドの場合と同様に、これらのクエリーは、オブジェクトのプライベート・データをチェックし、後の2つのメソッドについては引数と比較することにより処理されます。
残りの4つのFormatConversionProviderメソッドは具象メソッドです。通常はオーバーライドする必要はありません。
boolean isConversionSupported(
AudioFormat.Encoding targetEncoding,
AudioFormat sourceFormat)
boolean isConversionSupported(AudioFormat targetFormat,
AudioFormat sourceFormat)
boolean isSourceEncodingSupported(
AudioFormat.Encoding sourceEncoding)
boolean isTargetEncodingSupported(
AudioFormat.Encoding targetEncoding)
AudioFileWriter.isFileTypeSupported()の場合と同様に、これらの各メソッドのデフォルトの実装は、本質的にはほかのクエリー・メソッドの1つを呼び出し、返された結果を繰返し実行するラッパーです。
MixerProviderは名前が示すように、ミキサーのインスタンスを提供します。 各具象MixerProviderサブクラスは、アプリケーション・プログラムが使用するMixerオブジェクトのファクトリの役目をします。 もちろん、新しいMixerProviderの定義は、Mixerインタフェースの新しい実装を定義する場合にのみ必要です。 上のFormatConversionProviderの例でgetAudioInputStreamメソッドが返すAudioInputStreamのサブクラスが変換を行ったのと同様に、新しいAcmeMixerProviderクラスにはgetMixerメソッドがあり、Mixerインタフェースを実装している別の新しいクラスのインスタンスを返します。 この新しいクラスの名前をAcmeMixerとします。 ミキサーがハードウェアに実装されている場合は、プロバイダは要求されたデバイスのstaticインスタンスを1つしかサポートしないことがあります。 その場合は、getMixerの呼出しに対して、毎回このstaticインスタンスを返します。
AcmeMixerはMixerインタフェースをサポートするので、アプリケーション・プログラムがこのインタフェースの基本機能にアクセスするために、その他の情報は必要ありません。 ただし、AcmeMixerがMixerインタフェースに定義されていない機能をサポートしており、この拡張された機能をアプリケーション・プログラムからアクセスできるようにする場合は、当然、ミキサーを、追加の文書で十分実証されたpublicメソッドとともにpublicクラスとして定義する必要があります。定義すると、この拡張された機能を利用しようとするプログラムがAcmeMixerをインポートして、getMixerから返されるオブジェクトをこの種類にキャストできるようになります。
次に、MixerProviderのほかの2つのメソッドを示します。
abstract Mixer.Info[] getMixerInfo()および
boolean isMixerSupported(Mixer.Info info)これらのメソッドにより、オーディオ・システムは、アプリケーション・プログラムが必要とするデバイスをこの特定のプロバイダ・クラスが生成できるかどうかを判断できます。 つまり、
AudioSystemオブジェクトはインストールされているすべてのMixerProvidersを繰返し調べて、アプリケーション・プログラムがAudioSystemに要求したデバイスを供給できるものがあるかどうかを確認します。 第3章「オーディオ・システム・リソースへのアクセス」の「ミキサーの取得」を参照してください。 getMixerInfoメソッドは、このプロバイダ・オブジェクトから提供できるミキサーの種類に関する情報を含むオブジェクトの配列を返します。 システムはこれらの情報オブジェクトを、ほかのプロバイダからの情報とともにアプリケーション・プログラムに渡します。
1つのMixerProviderは複数の種類のミキサーを提供できます。 システムはMixerProviderのgetMixerInfoメソッドを呼び出すとき、このプロバイダがサポートするさまざまな種類のミキサーを識別する情報オブジェクトのリストを取得します。 次にシステムは、MixerProvider.getMixer(Mixer.Info)を呼び出して、目的のミキサーをそれぞれ取得することができます。
サブクラスには、その抽象メソッドとしてgetMixerInfoを実装する必要があります。 isMixerSupportedメソッドは具象であり、通常はオーバーライドする必要はありません。 デフォルト実装では、単に提供されるMixer.Infoと、getMixerInfoから返される配列内の各Mixer.Infoとを比較します。