サウンドを扱うほとんどのアプリケーションプログラムで、オーディオファイルやオーディオストリームの読み込みが必要になります。プログラムが読み込みの次に何をするか (再生、ミキシング、処理など) に関係なく、読み込みという機能はプログラムに共通する機能です。同様に、サウンドファイルやサウンドストリームの作成も必要になります。場合によっては、読み込んだデータやこれから書き込むデータの形式を変換する必要も生じます。
第 3 章「オーディオシステムリソースへのアクセス」で簡単に説明したように、JavaTM Sound API はアプリケーション開発者にファイルの入出力および形式変換用のさまざまな機能を提供します。アプリケーションプログラムで、さまざまなサウンドファイル形式およびオーディオデータ形式の読み込み、書き込み、およびこれらの相互変換が可能です。
第 2 章「Sampled パッケージの概要」では、サウンドファイル形式およびオーディオデータ形式に関連する主なクラスを紹介しました。以下に要約を示します。
AudioInputStream
オブジェクトで表されます。(AudioInputStream
は、java.io.InputStream
を継承しています。
AudioFormat
オブジェクトによって表されます。
このオーディオデータ形式は、オーディオサンプル自体の配置方法を指定するものであり、サンプルが格納されるファイルの構造を指定するものではありません。つまり、AudioFormat
は、システムがプログラムに渡す「raw」オーディオデータ (マイクロフォン入力から取り込んだり、サウンドファイルから解析したりしたオーディオデータ) を記述するものです。AudioFormat
には、エンコーディング、バイト順、チャネル数、サンプリングレート、およびサンプルあたりのビット数などの情報が含まれます。
AudioFileFormat
オブジェクトにより表現されます。AudioFileFormat
には、ファイルに格納されるオーディオデータの形式を記述する AudioFormat
オブジェクトが含まれます。また、ファイルタイプやファイル内のデータ長などの情報も含まれます。
AudioSystem
クラスが提供するメソッドには、(1) AudioInputStream
から取得したオーディオデータストリームを特定タイプのオーディオファイルに格納する (ファイルの書き込み)、(2) オーディオファイルからオーディオバイトのストリーム (AudioInputStream
) を抽出する (ファイルの読み込み)、(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
オブジェクトを取得する
上記のコード例で、何が行われているのかを見てみましょう。まず、外側の try 節では、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... }
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
内のオーディオデータが格納されます。
2 番目の引数は、システムがサポートするファイルタイプ (AU、AIFF、WAV など) のいずれかでなければなりません。サポート外の場合、static int write(AudioInputStream in, AudioFileFormat.Type fileType, File out)
write
メソッドは IllegalArgumentException
をスローします。この例外のスローを避けるために、以下の AudioSystem
のメソッドを呼び出して、特定の AudioInputStream
に特定のファイルタイプで書き込むことができるかどうかを検査します。
上記のコードは、特定の組み合わせがサポートされている場合にのみstatic boolean isFileTypeSupported (AudioFileFormat.Type fileType, AudioInputStream stream)
true
を返します。
一般に、次の AudioSystem
メソッドのいずれかを呼び出すことにより、システムが書き込み可能なファイルタイプを知ることができます。
最初のメソッドは、システムが書き込み可能なすべてのファイルタイプを返します。2 番目のメソッドは、システムが指定されたオーディオ入力ストリームから書き込むことのできるファイルタイプだけを返します。static AudioFileFormat.Type[] getAudioFileTypes() static AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream)
次の引用コードは、上記の 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
オブジェクトからファイルタイプを取得します。fileFormat
には、ほかのサウンドファイル (この章の「サウンドファイルの読み込み」の中で読み取ったサウンドファイルなど) から取得したオブジェクトを指定できます。ほかの場所からファイルタイプを取得する代わりに、サポートされる任意のファイルタイプを指定することもできます。たとえば、上記の 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
メソッドを呼び出して、システムが必要な変換を実行可能かどうかをチェックします。
この場合、2 番目の引数としてboolean isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat)
stream.getFormat()
を渡します。
指定の AudioFormat
オブジェクトを作成するために、次に示す 2 つの AudioFormat
コンストラクタのどちらかを使用します。
このコンストラクタは、線形 PCM エンコーディングと指定されたパラメータを使ってAudioFormat(float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian)
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 を使って形式を変換する際のエラーの発生を防ぐのに役立ちます。