第 14 章: サンプリングオーディオサービスの提供


 

第 13 章「サービスプロバイダインタフェースの概要」に詳しい説明がありますが、JavaTM Sound API には、javax.sound.sampled.spijavax.sound.midi.spi という 2 つのパッケージが入っています。これらのパッケージは、サウンドサービスの開発者が使用する抽象クラス群を定義しています。サービスプロバイダは、これらの抽象クラス群の中の 1 つのクラスのサブクラスを実装およびインストールすることにより新しいサービスを登録して、実行システムの機能性を拡張します。この章では、サンプリングオーディオの処理という新しいサービスを提供するために、実際に javax.sound.sampled.spi パッケージを使用する方法について説明していきます。

既存のオーディオサービスだけをアプリケーションプログラムで使用するプログラマは、この章を読まなくても差し支えありません。インストール済みのオーディオサービスをアプリケーションプログラムで使う方法については、このマニュアル(『Java Sound API プログラマーズガイド』) の第 I 部「サンプリングオーディオ」を参照してください。この章では、インストール済みのオーディオサービスにアクセスするためにアプリケーションプログラムが呼び出す JavaTM Sound API メソッドについての知識があることを前提とします。

はじめに

javax.sound.sampled.spi パッケージには次の 4 つの抽象クラスが存在し、サンプリングオーディオシステムを提供する 4 種類のサービスを表しています。

第 13 章で説明したように、サービスプロバイダは実行システムの機能性を拡張することができます。一般的な SPI クラスには、次の 2 種類のメソッドがあります。1 つは、特定のプロバイダから提供されるサービスの種類のクエリーに対するメソッドで、もう 1 つは、新しいサービスを直接実行するか、またはそのサービスを実際に提供するオブジェクトのインスタンスを返すメソッドです。実行環境のサービスプロバイダ機構は、インストールされるサービスとオーディオシステムの登録、および新しいサービスプロバイダクラスの管理を行います。

サービスのインスタンスは、本質的に、アプリケーションの開発者からニ重に隔離されています。アプリケーションプログラムが、ミキサーや形式コンバータなどのサービスプログラムのオーディオ処理タスクに必要なインスタンスを直接作成することはありません。また、これらのオブジェクトを管理する SPI クラスから直接オブジェクトを要求することもありません。アプリケーションプログラムは javax.sound.sampled パッケージ内の AudioSystem オブジェクトに対して要求を行い、AudioSystem は SPI オブジェクトを使ってこれらのクエリーとサービス要求を処理します。

新しいオーディオサービスの存在は、ユーザとアプリケーションプログラマに対しては完全に透過的です。アプリケーション参照はすべて javax.sound.sampled パッケージの標準オブジェクト、主に AudioSystem によって行われ、新しいサービスによって提供される特殊処理は完全に隠されます。

この章でも、前章と同様に、新しい SPI サブクラスを AcmeMixerAcmeMixerProvider などの名前で呼びます。

オーディオファイル書き込みサービスの提供

最初に、比較的簡単な SPI クラスの 1 つである AudioFileWriter について説明します。

AudioFileWriter のメソッドを実装しているサブクラスは、クラスでサポートされるファイル形式やファイルタイプのクエリーを処理するために、一連のメソッドの実装を提供しなければなりません。また、サブクラスは、提供オーディオデータストリームを実際に File または OutputStream に書き出すメソッドも提供する必要があります。

AudioFileWriter には、基底クラスに固定実装を持つ次の 2 つのメソッドが含まれています。

boolean isFileTypeSupported(AudioFileFormat.Type fileType)
boolean isFileTypeSupported(AudioFileFormat.Type fileType,
AudioInputStream stream)
1 つ目のメソッドは、このファイルライターが指定された種類のサウンドファイルを書き込むことができるかどうかを呼び出し側に通知するメソッドです。このメソッドは汎用クエリーです。ファイルライターに適切なオーディオデータが渡されることを前提として、そのファイルライターがその種類のファイルを書き込める場合は、true を返します。ただし、ファイルを書き込めるかどうかは、ファイルライターに渡される特定のオーディオデータ形式に依存することがあります。1 つのファイルライターですべてのオーディオデータ形式をサポートするとは限らず、また、ファイル形式自体による制約もあります。すべての種類のオーディオデータをすべての種類のサウンドファイルに書き込めるとは限りません。そのため、後者のメソッドはさらに細かい指定が可能で、特定の 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 種類のオーバーロードメソッドにより構成されます。このオーバーロードメソッドはそれぞれ、FileURLInputStream のいずれか 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)
	
および
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;
    }
実際の形式変換は、返された AcmeCodecStreamread メソッドの中で行われます。AcmeCodecStreamAudioInputStream のサブクラスです。返された 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 インスタンスを返します。

AcmeMixerMixer インタフェースをサポートするので、アプリケーションプログラムはミキサーの基本機能にアクセスするための情報はこれ以上は必要ありません。ただし、AcmeMixer は、Mixer インタフェースに定義されていない機能をサポートしています。この拡張された機能をアプリケーションプログラムからアクセスできるようにする場合は、当然、ミキサーを、追加の文書で十分実証された public メソッドとともに public クラスとして定義する必要があります。定義すると、この拡張された機能を利用しようとするプログラムが AcmeMixer をインポートして、getMixer から返されるオブジェクトをこの種類にキャストできるようになります。

次に、MixerProvider のほかの 2 つのメソッドを示します。

abstract Mixer.Info[] getMixerInfo()
および
boolean isMixerSupported(Mixer.Info info)
これらのメソッドにより、オーディオシステムは、アプリケーションプログラムが必要とするデバイスをこの特定のプロバイダクラスが生成できるかどうかを判断できます。つまり、AudioSystem オブジェクトはインストールされているすべての MixerProviders を繰り返し調べて、アプリケーションプログラムが AudioSystem に要求したデバイスを供給できるものがあるかどうかを確認します。第 3 章「オーディオシステムリソースへのアクセス」「ミキサーの取得」を参照してください。getMixerInfo メソッドは、このプロバイダオブジェクトから提供できるミキサーの種類に関する情報を含むオブジェクトの配列を返します。システムはこれらの情報オブジェクトを、ほかのプロバイダからの情報とともにアプリケーションプログラムに渡します。

1 つの MixerProvider は複数の種類のミキサーを提供できます。システムは MixerProvidergetMixerInfo メソッドを呼び出すとき、このプロバイダがサポートするさまざまな種類のミキサーを識別する情報オブジェクトのリストを取得します。次にシステムは、MixerProvider.getMixer(Mixer.Info) を呼び出して、目的のミキサーをそれぞれ取得することができます。

サブクラスには、その抽象メソッドとして getMixerInfo を実装する必要があります。isMixerSupported メソッドは具象であり、通常はオーバーライドする必要はありません。デフォルト実装では、単に提供される Mixer.Info と、getMixerInfo から返される配列内の各 Mixer.Info とを比較します。