サウンドを扱うほとんどのアプリケーションプログラムで、サウンドファイルやオーディオストリームの読み込みが必要になります。プログラムが読み込んだデータで次に何をするか (再生、ミキシング、処理など) に関係なく、読み込みという機能はプログラムに共通する機能です。同様に、多くのプログラムでサウンドファイルやサウンドストリームの書き込みも必要になります。場合によっては、読み込んだデータやこれから書き込むデータの形式を変換する必要も生じます。
第 3 章「オーディオシステムリソースへのアクセス」で簡単に説明したように、Java Sound API はアプリケーション開発者にファイルの入/出力および形式変換用のさまざまな機能を提供します。アプリケーションプログラムで、さまざまなサウンドファイル形式およびオーディオデータ形式の読み込み、書き込み、およびこれらの相互変換が可能です。
第 2 章「Sampled パッケージの概要」では、サウンドファイル形式およびオーディオデータ形式に関連する主なクラスを紹介しました。次に要約を示します。
AudioInputStream
オブジェクトで表されます。(AudioInputStream
は、java.io.InputStream
を継承しています。) AudioFormat
オブジェクトによって表されます。
このオーディオデータ形式は、オーディオサンプル自体の配置方法を指定するものであり、サンプルが格納されるファイルの構造を指定するものではありません。つまり、AudioFormat
は、システムがプログラムに渡す「raw」オーディオデータ (マイクロフォン入力から取り込んだり、サウンドファイルから解析したりしたオーディオデータ) を記述するものです。AudioFormat
には、エンコーディング、バイト順、チャネル数、サンプリングレート、およびサンプルあたりのビット数などの情報が含まれます。
AudioFileFormat
オブジェクトにより表現されます。AudioFileFormat
には、ファイルに格納されるオーディオデータの形式を記述する AudioFormat
オブジェクトが含まれます。また、ファイルタイプやファイル内のデータ長などの情報も含まれます。 AudioSystem
クラスが提供するメソッドには、(1) AudioInputStream
から取得したオーディオデータストリームを特定タイプのオーディオファイルに格納する (ファイルの書き込み)、(2) オーディオファイルからオーディオバイトのストリーム (AudioInputStream
) を抽出する (ファイルの読み込み)、(3) ある形式のオーディオデータを別の形式に変換するなどの機能があります。この章は 3 つのセクションに分かれており、各セクションでこれら 3 つの機能について説明します。
AudioSystem
クラスが提供するメソッドを使用することにより、利用可能な変換の種類をアプリケーションプログラムから識別可能になります。詳細は、この章の「ファイル形式およびデータ形式の変換」で後述します。
AudioSystem
クラスは、次の 2 種類のファイル読み込みサービスを提供します。
getAudioFileFormat
の 3 つの形式により実行されます。
static AudioFileFormat getAudioFileFormat (java.io.File file)
static AudioFileFormat getAudioFileFormat(java.io.InputStream stream)
static AudioFileFormat getAudioFileFormat (java.net.URL url)
すでに説明したように、返される AudioFileFormat
オブジェクトから、ファイルの種類、ファイル内のデータ長、エンコーディング、バイト順、チャネル数、サンプリングレート、およびサンプルあたりのビット数などを知ることができます。
2 つのうちの 2 番目のファイル読み込み機能は、次の AudioSystem
メソッドにより利用可能になります。
static AudioInputStream getAudioInputStream (java.io.File file)
static AudioInputStream getAudioInputStream (java.net.URL url)
static AudioInputStream getAudioInputStream (java.io.InputStream stream)
これらのメソッドが提供する AudioInputStream
オブジェクトを使用することにより、AudioInputStream
のいずれかの読み込みメソッドを使って、ファイル内のオーディオデータの読み込みが可能になります。1 つの例を考えてみましょう。
サウンド編集用のアプリケーションを作成しているとします。ユーザーはこのアプリケーションを使って、ファイルからのサウンドデータのロード、対応する波形やスペクトログラムの表示、サウンドの編集、編集したデータの再生、編集結果の新規ファイルへの保存を行うことができます。また、そのプログラムを使って、ファイルに格納されたデータを読み込み、なんらかの信号処理 (ピッチを変えずにサウンドのペースを落とすアルゴリズムの適用など) を行い、処理後のオーディオを再生することも考えられます。どちらの場合にも、オーディオファイル内のデータにアクセスする必要があります。このプログラムが、ユーザーに入力サウンドファイルの選択または指定を行うためのなんらかの手段を提供しているとしましょう。ファイルからオーディオデータを読み込むには、次の 3 つの手順を実行します。
AudioInputStream
オブジェクトを取得する。 int totalFramesRead = 0; File fileIn = new File(somePathName); // somePathName is a pre-existing string whose value was // based on a user selection. try { AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(fileIn); int bytesPerFrame = audioInputStream.getFormat().getFrameSize(); // Set an arbitrary buffer size of 1024 frames. int numBytes = 1024 * bytesPerFrame; byte[] audioBytes = new byte[numBytes]; try { int numBytesRead = 0; int numFramesRead = 0; // Try to read numBytes bytes from the file. while ((numBytesRead = audioInputStream.read(audioBytes)) != -1) { // Calculate the number of frames actually read. numFramesRead = numBytesRead / bytesPerFrame; totalFramesRead += numFramesRead; // Here, do something useful with the audio data that's // now in the audioBytes array... } } catch (Exception ex) { // Handle the error... } } catch (Exception e) { // Handle the error... }上記のコード例で、何が行われているのかを見てみましょう。まず、外側の try 節では、
AudioSystem.getAudioInputStream(File)
メソッドを呼び出して AudioInputStream
オブジェクトのインスタンスを生成しています。このメソッドは、指定されたファイルが実際に Java Sound API がサポートするタイプのサウンドファイルかどうかを判定するために、必要なすべてのテストを透過的に実行します。検査対象のファイル (この例では fileIn
) がサウンドファイルではない場合、またはサポートされていないタイプのサウンドファイルの場合は、例外 UnsupportedAudioFileException
がスローされます。この動作により、アプリケーションプログラマは検査ファイルの属性に頭を悩ませたりファイルの命名規則にしばられることがなくなります。入力ファイルの妥当性検査に必要な低レベルの解析と検証は、getAudioInputStream
メソッドが行います。
外側の try
節は、次に任意の固定長バイト配列 audioBytes
を作成します。この固定長 (バイト単位) が、フレームの整数値と等しいことを確認します。これは、読み込みがフレームの一部またはひどい時にはサンプルの一部に対して行われただけで終了してしまわないようにするためです。このバイト配列は、ストリームから読み込んだオーディオデータのチャンクを一時的に保持するためのバッファーになります。非常に短いサウンドファイルだけを読み込むことがわかっている場合は、AudioInputStream の getFrameLength
メソッドが返すフレーム長から得たバイト長を使って、この配列をファイル内のデータと同じ長さに設定できます。実際には、Clip
オブジェクトを代わりに使うことが多いようです。ただし、一般的なケースで、メモリー不足状態での実行を避けるため、ファイルをブロックに分けて一度に 1 つのバッファーを読み込みます。
内側の try
節に含まれる while
ループで、AudioInputStream
からオーディオデータをバイト配列に読み込みます。このループ内にコードを追加し、プログラムの要件に適した方法でこの配列内のオーディオデータを処理する必要があります。データに対してなんらかの信号処理を行う場合は、さらに AudioInputStream の AudioFormat
にも問い合わせて、サンプルあたりのビット数などを確認する必要があります。
AudioInputStream.read(byte[])
メソッドは、サンプルまたはフレームの数を返すのではなく、読み取ったバイト数を返すことに留意してください。読み取るデータがなくなると、このメソッドは -1 を返します。-1 が返されると、while
ループから抜けます。
前のセクションでは、AudioSystem
および AudioInputStream
クラスの特定のメソッドを使ってサウンドファイルの読み込みに関する基本事項を説明しました。このセクションでは、オーディオデータを新規ファイルに書き出す方法を説明します。
次の AudioSystem
のメソッドは、指定されたファイルタイプのディスクファイルを作成します。ファイルには、指定された AudioInputStream
内のオーディオデータが格納されます。
static int write(AudioInputStream in, AudioFileFormat.Type fileType, File out)2 番目の引数は、システムがサポートするファイルタイプ (AU、AIFF、WAV など) のいずれかでなければなりません。サポート外の場合、
write
メソッドは IllegalArgumentException
をスローします。この例外のスローを避けるために、以下の AudioSystem
のメソッドを呼び出して、特定の AudioInputStream
に特定のファイルタイプで書き込むことができるかどうかを検査します。
static boolean isFileTypeSupported (AudioFileFormat.Type fileType, AudioInputStream stream)上記のコードは、特定の組み合わせがサポートされている場合にのみ
true
を返します。
一般に、次の AudioSystem
メソッドのいずれかを呼び出すことにより、システムが書き込み可能なファイルタイプを知ることができます。
static AudioFileFormat.Type[] getAudioFileTypes() static AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream)最初のメソッドは、システムが書き込み可能なすべてのファイルタイプを返します。2 番目のメソッドは、システムが指定されたオーディオ入力ストリームから書き込むことのできるファイルタイプだけを返します。
次の引用コードは、上記の write
メソッドを使って、AudioInputStream
から出力ファイルを作成する方法を示します。
File fileOut = new File(someNewPathName); AudioFileFormat.Type fileType = fileFormat.getType(); if (AudioSystem.isFileTypeSupported(fileType, audioInputStream)) { AudioSystem.write(audioInputStream, fileType, fileOut); }最初の文では、ユーザーまたはプログラムにより指定されたパス名を使って、新規の
File
オブジェクト、fileOut
を作成します。2 番目の文では、fileFormat
と呼ばれる既存の AudioFileFormat
オブジェクトからファイルタイプを取得します。これには、ほかのサウンドファイル (この章の「サウンドファイルの読み込み」の中で読み取ったサウンドファイルなど) から取得したオブジェクトを指定できます。ほかの場所からファイルタイプを取得する代わりに、サポートされる任意のファイルタイプを指定することもできます。たとえば、上記の 2 番目の文の代わりに、AudioFileFormat.Type.WAVE
を使って 2 つの fileType
を記述することも可能です。
3 番目の文は、指定されたタイプのファイルで所定の AudioInputStream
に書き込むことが可能かどうかを検査します。ファイル形式の場合と同様、このストリームもすでに読み取ったサウンドファイルから導き出されている可能性があります。このような場合は、データをなんらかの方法で処理または変更してしまっています。そうでなければ、ファイルを単にコピーするためのより簡単な方法があるからです。また、ストリームには、マイクロフォン入力から取り込んだばかりのバイトが含まれている場合もあります。
最後に、ストリーム、ファイルタイプ、および出力ファイルが AudioSystem
.write
メソッドに渡されて、ファイルの書き込みが完成します。
第 2 章「Sampled パッケージの概要」の「整形済みオーディオデータとは」では、Java Sound API がオーディオファイル形式とオーディオデータ形式を区別することを説明しました。両者は、ほぼ独立したものです。大まかに言って、データ形式はコンピュータが各 raw データポイント (サンプル) を表現する方法を指し、ファイル形式はサウンドファイルをディスクに格納する際の編成を指します。各サウンドファイル形式には特定の構造があります。その構造内で、たとえばファイルのヘッダーに格納される情報が定義されます。場合によっては、ファイル形式には、「raw」オーディオサンプルのほかに、メタデータのいくつかの形式を含む構造もあります。この章の後半では、さまざまなファイル形式の変換とデータ形式の変換を可能にする Java Sound API のメソッドについて考察します。
このセクションでは、Java Sound API でオーディオファイルタイプを変換する上での基本事項を取り上げます。ここでもう一度、仮説のプログラムを示します。今回は、任意の入力ファイルからオーディオデータを読み込み、AIFF 形式のファイルに書き込みを実行します。もちろん、入力ファイルのタイプはシステムが読み取ることのできるものでなければなりません。また、出力ファイルのタイプはシステムが書き込むことのできるものでなければなりません。この例では、システムが AIFF ファイルに書き込むことができるものとします。サンプルプログラムは、どのようなデータ形式の変換も行いません。入力ファイルのデータ形式を AIFF ファイルで表現できない場合、プログラムはユーザーにその問題を通知するだけです。一方、入力サウンドファイルが AIFF ファイルになっている場合、プログラムはユーザーに変換の必要がないことを通知します。
public void ConvertFileToAIFF(String inputPath, String outputPath) { AudioFileFormat inFileFormat; File inFile; File outFile; try { inFile = new File(inputPath); outFile = new File(outputPath); } catch (NullPointerException ex) { System.out.println("Error: one of the ConvertFileToAIFF" +" parameters is null!"); return; } try { // query file type inFileFormat = AudioSystem.getAudioFileFormat(inFile); if (inFileFormat.getType() != AudioFileFormat.Type.AIFF) { // inFile is not AIFF, so let's try to convert it. AudioInputStream inFileAIS = AudioSystem.getAudioInputStream(inFile); inFileAIS.reset(); // rewind if (AudioSystem.isFileTypeSupported( AudioFileFormat.Type.AIFF, inFileAIS)) { // inFileAIS can be converted to AIFF. // so write the AudioInputStream to the // output file. AudioSystem.write(inFileAIS, AudioFileFormat.Type.AIFF, outFile); System.out.println("Successfully made AIFF file, " + outFile.getPath() + ", from " + inFileFormat.getType() + " file, " + inFile.getPath() + "."); inFileAIS.close(); return; // All done now } else System.out.println("Warning: AIFF conversion of " + inFile.getPath() + " is not currently supported by AudioSystem."); } else System.out.println("Input file " + inFile.getPath() + " is AIFF." + " Conversion is unnecessary."); } catch (UnsupportedAudioFileException e) { System.out.println("Error: " + inFile.getPath() + " is not a supported audio file type!"); return; } catch (IOException e) { System.out.println("Error: failure attempting to read " + inFile.getPath() + "!"); return; } }
すでに説明したとおり、このサンプル関数 ConvertFileToAIFF
の目的は、入力ファイルへの問い合わせを行って AIFF サウンドファイルかどうかを判断し、AIFF でない場合は AIFF への変換を試みて、2 番目の引数で指定されたパス名を持つ新たなコピーを作成することです。練習として、この関数をより汎用化して、常に AIFF へ変換する代わりに、新しい関数の引数で指定されたファイルタイプにその関数が変換するように変えてみるのもよいでしょう。作成されたコピー (新規ファイル) のオーディオデータ形式は、元の入力ファイルのオーディオデータ形式の模造であることに留意してください。
関数の大部分については特に説明しません。また、Java Sound API に固有ではありません。ただし、サウンドファイルタイプの変換に重要な役割を果たすルーチンが使用する Java Sound API メソッドがいくつか存在します。これらのメソッド呼び出しは、上記の 2 つ目の try
節で行われており、以下を含んでいます。
AudioSystem.getAudioFileFormat
: 入力ファイルがすでに AIFF タイプであるかどうかを判別します。AIFF の場合、関数はすぐに値を返します。AIFF でない場合は変換が試みられます。 AudioSystem.isFileTypeSupported
: 指定された AudioInputStream.
から取得したオーディオデータを含む指定されたタイプのファイルを、システムが書き込めるかどうかを示します。この例では、指定されたオーディオ入力ファイルを AIFF オーディオファイル形式に変換できる場合、このメソッドは true
を返します。AudioFileFormat.Type.AIFF
がサポートされていない場合、ConvertFileToAIFF
は、入力ファイルを変換できないという警告を発行してから戻ります。 AudioSystem.write
: AudioInputStream inFileAIS
から取得したオーディオデータを出力ファイル outFile
に書き込むために使用されます。
isFileTypeSupported
は、書き込みに先立ち、特定の入力サウンドファイルを特定の出力サウンドファイルタイプに変換できるかどうかを判定します。次のセクションでは、サンプルルーチン ConvertFileToAIFF
に多少の変更を加えることにより、オーディオデータ形式およびサウンドファイルタイプの変換が可能になることを示します。
前のセクションでは、Java Sound API を使って、あるファイル形式のファイル (特定のタイプのサウンドファイル) を別のファイル形式に変換する方法を説明しました。このセクションでは、オーディオデータ形式の変換を可能にするいくつかのメソッドを説明します。
前のセクションでは、任意のタイプのファイルからデータを読み込み、それを AIFF ファイルに保存しました。そこでは、データの格納に使用するファイルタイプは変更しましたが、オーディオデータ自体の形式は変更しませんでした。AIFF などの、もっとも一般的なオーディオファイルタイプには、さまざまな形式のオーディオデータを含めることができます。このため、元のファイルに CD 音質のオーディオデータ (サンプルサイズ 16 ビット、サンプリングレート 44.1-kHz、2 チャネル) が格納されている場合、出力される AIFF ファイルも CD 音質になります。
ここでは、出力ファイルのファイルタイプとともにデータ形式も指定する場合を考えてみましょう。たとえば、インターネット上の公開目的でサイズの大きなファイルを多数保存しており、これらのファイルが占めるディスク容量およびファイルのダウンロードにかかる時間が気にかかっているとします。これには、低音質のデータを含む、サイズのより小さな AIFF ファイル (サンプルサイズ 8 ビット、サンプリングレート 8 kHz、1 チャネルのデータなど) を作成します。
前述のような細かいコード化には立ち入らず、データ形式の変換に使用するメソッドのいくつかを調べ、ConvertFileToAIFF
関数を変更して目的を達成する方法を考えましょう。
オーディオデータ変換に利用する主要なメソッドは、前にも記したとおり、AudioSystem
クラス内に存在します。このメソッドは、getAudioInputStream
の形式で、次のようになります。
AudioInputStream getAudioInputStream(AudioFormat format, AudioInputStream stream)この関数は、指定された
AudioFormat
である format
を使って、AudioInputStream
である stream
の変換結果である AudioInputStream
を返します。AudioSystem
がその変換をサポートしない場合、この関数は IllegalArgumentException
をスローします。
この例外を避けるために、まずこの AudioSystem
メソッドを呼び出して、システムが必要な変換を実行可能かどうかをチェックします。
boolean isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat)この場合、2 番目の引数として
stream.getFormat()
を渡します。
指定の AudioFormat
オブジェクトを作成するために、次に示す 2 つの AudioFormat
コンストラクタのどちらかを使用します。
AudioFormat(float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian)このコンストラクタは、線形 PCM エンコーディングと指定されたパラメータを使って
AudioFormat
を構成します。
AudioFormat(AudioFormat.Encoding encoding, float sampleRate, int sampleSizeInBits, int channels, int frameSize, float frameRate, boolean bigEndian)このコンストラクタも
AudioFormat
を構成しますが、ほかのパラメータに加え、エンコーディング、フレームサイズ、フレームレートの指定も可能です。
これで上記のメソッドが利用可能になったため、ConvertFileToAIFF
関数を継承して「低音質」のオーディオデータ形式への変換を行います。まず、目的の出力オーディオデータ形式について記述した AudioFormat
オブジェクトを構成します。次の文で十分なので関数の先頭に挿入します。
AudioFormat outDataFormat = new AudioFormat((float) 8000.0, (int) 8, (int) 1, true, false);上記の
AudioFormat
コンストラクタは、サンプル形式を 8 ビットとしているため、コンストラクタに渡す最後のパラメータ (サンプルがビッグエンディアンか、それともリトルエンディアンかを指定) は無視されます。ビッグエンディアンか、リトルエンディアンかは、サンプルサイズが 1 バイト以上の場合にのみ問題となります。
次の例は、この新規 AudioFormat
を使って、入力ファイルから作成した AudioInputStream
、inFileAIS
を変換する方法を示します。
AudioInputStream lowResAIS; if (AudioSystem.isConversionSupported(outDataFormat, inFileAIS.getFormat())) { lowResAIS = AudioSystem.getAudioInputStream (outDataFormat, inFileAIS); }このコードを挿入する位置は、
inFileAIS
の構成後であれば特に問題になりません。isConversionSupported
テストを行わない場合、要求された変換がサポートされていないと、呼び出しは失敗し、IllegalArgumentException
がスローされます。この場合、制御は、関数内の適切な catch
節に移ります。
このため、この処理段階で、元の入力ファイル (AudioInputStream
形式) を outDataFormat
で定義された低音質のオーディオデータ形式に変換した結果として、新規 AudioInputStream
を構成できます。
目的の低音質 AIFF サウンドファイルを作成する最後の手順は、AudioSystem.write
への呼び出し内の AudioInputStream
パラメータ (最初のパラメータ) を、変換後のストリーム lowResAIS
と置き換えることです。その方法を次に示します。
AudioSystem.write(lowResAIS, AudioFileFormat.Type.AIFF, outFile);このように、前出の関数にほんの少しの変更を加えることにより、指定された入力ファイルのオーディオデータ形式とファイル形式の両方を変換できます。ただし、システムがその変換をサポートすることが前提条件です。
いくつかの AudioSystem
メソッドは、パラメータをチェックして、システムが特定のデータ形式の変換またはファイルの書き込み操作をサポートするかどうかを判断します。一般に、各メソッドは、データ変換またはファイルの書き込みを実行する別のメソッドとペアになっています。サンプル関数 ConvertFileToAIFF
では、システムがオーディオデータを AIFF ファイルに書き込むことができるかどうかを判断するために、これらのクエリーメソッドの 1 つである AudioSystem.isFileTypeSupported
が使用されています。関連する AudioSystem
メソッドである getAudioFileTypes(AudioInputStream)
は、指定されたストリームでサポートされるファイルタイプの完全なリストを、AudioFileFormat.Type
インスタンスの配列として返します。また、次のメソッドに注目してください。
boolean isConversionSupported(AudioFormat.Encoding encoding,上記は、指定されたエンコーディングでのオーディオ入力ストリームを、指定されたオーディオ形式のオーディオ入力ストリームから取得可能かどうかを判定する場合に使用されます。次のメソッドも同様です。
AudioFormat format)
boolean isConversionSupported(AudioFormat newFormat, AudioFormat oldFormat)
このメソッドは、指定されたオーディオ形式 newFormat
を持つ AudioInputStream
が、オーディオ形式 oldFormat
を持つ AudioInputStream
を変換することにより取得可能かどうかを判定します。(前のセクションのコードの引用例では、このメソッドは、低音質のオーディオ入力ストリーム lowResAIS
を作成するコードから呼び出されました。)
オーディオ形式に関連したこれらのクエリーは、Java Sound API を使って形式を変換する際のエラーの発生を防ぐのに役立ちます。