第 13 章「サービスプロバイダインタフェースの概要」に詳しい説明がありますが、JavaTM Sound API には、javax.sound.sampled.spi
と javax.sound.midi.spi
という 2 つのパッケージが入っています。これらのパッケージは、サウンドサービスの開発者が使用する抽象クラス群を定義しています。サービスプロバイダは、これらの抽象クラス群の中の 1 つのクラスのサブクラスを実装およびインストールすることにより新しいサービスを登録して、実行システムの機能性を拡張します。この章では、サンプリングオーディオの処理という新しいサービスを提供するために、実際に javax.sound.sampled.spi
パッケージを使用する方法について説明していきます。
既存のオーディオサービスだけをアプリケーションプログラムで使用するプログラマは、この章を読まなくても差し支えありません。インストール済みのオーディオサービスをアプリケーションプログラムで使う方法については、このマニュアル(『Java Sound API プログラマーズガイド』) の第 I 部「サンプリングオーディオ」を参照してください。この章では、インストール済みのオーディオサービスにアクセスするためにアプリケーションプログラムが呼び出す JavaTM 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 つのメソッドが含まれています。
1 つ目のメソッドは、このファイルライターが指定された種類のサウンドファイルを書き込むことができるかどうかを呼び出し側に通知するメソッドです。このメソッドは汎用クエリーです。ファイルライターに適切なオーディオデータが渡されることを前提として、そのファイルライターがその種類のファイルを書き込める場合は、boolean isFileTypeSupported(AudioFileFormat.Type fileType) boolean isFileTypeSupported(AudioFileFormat.Type fileType, AudioInputStream stream)
true
を返します。ただし、ファイルを書き込めるかどうかは、ファイルライターに渡される特定のオーディオデータ形式に依存することがあります。1 つのファイルライターですべてのオーディオデータ形式をサポートするとは限らず、また、ファイル形式自体による制約もあります。すべての種類のオーディオデータをすべての種類のサウンドファイルに書き込めるとは限りません。そのため、後者のメソッドはさらに細かい指定が可能で、特定の AudioInputStream
を特定のタイプのファイルに書き込めるかどうかを問い合わせます。
通常は、これらの 2 つの具象メソッドをオーバーライドする必要はありません。それぞれのメソッドは単なるラッパーであり、2 つのクエリーメソッドの一方を呼び出し、返された結果の繰り返しを行います。これらの 2 つのクエリーメソッドは抽象メソッドなので、サブクラスに実装する必要があります。
これらのメソッドは、上記の 2 つのメソッドに直接対応します。各メソッドは、サポートされるすべてのファイルタイプの配列を返します。すべてのファイルタイプとは、1 つ目のメソッドの場合は、一般にサポートされているすべてのファイルタイプを意味し、2 つ目のメソッドの場合は、特定のオーディオストリームでサポートされるすべてのファイルタイプを意味します。前者のメソッドの典型的な実装では、単にそのファイルライターのコンストラクタが初期化する配列を返します。後者のメソッドの実装では、ストリームのabstract AudioFileFormat.Type[] getAudioFileTypes() abstract AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream)
AudioFormat
オブジェクトを検査して、要求されたファイルタイプがサポートしているデータ形式かどうかを確かめます。
AudioFileWriter
の最後の 2 つのメソッドは、実際のファイル書き込み作業を行います。
これらのメソッドは、オーディオデータを表すバイトのストリームを、3 番目の引数で指定するストリームまたはファイルに書き込みます。これが実際にどのように行われるかは、指定されたファイルタイプの構造により異なります。abstract int write(AudioInputStream stream, AudioFileFormat.Type fileType, java.io.File out) abstract int write(AudioInputStream stream, AudioFileFormat.Type fileType, java.io.OutputStream out)
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
メソッドの一般的な実装は、サウンドファイルのヘッダを読んで構文解析し、ファイル形式を確かめます。ヘッダのどのフィールドを読み取るかについては、AudioFIle Format クラスの説明を参照してください。また、ヘッダの構文解析方法を理解するには、そのファイルタイプの仕様を参照してください。
このメソッドにストリームを引数として提供する呼び出し側は、ストリームはメソッドにより変更されないものとしているので、ファイルリーダは通常、最初にストリームにマークを付ける必要があります。ヘッダの最後まで読み込んだら、ファイルリーダはストリームを元の位置に戻さなければなりません。
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)
この 2 つの形式は、呼び出し側が完全なターゲット形式を指定しているか、形式のエンコーディングのみを指定しているかによって、第 1 引数が異なります。abstract AudioInputStream getAudioInputStream( AudioFormat targetFormat, AudioInputStream sourceStream)
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; }
AcmeCodecStream
の read
メソッドの中で行われます。AcmeCodecStream
は AudioInputStream
のサブクラスです。返された 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
とを比較します。