サウンドを扱うほとんどのアプリケーションプログラムで、オーディオファイルやオーディオストリームの読み込みが必要になります。プログラムが読み込みの次に何をするか (再生、ミキシング、処理など) に関係なく、読み込みという機能はプログラムに共通する機能です。同様に、サウンドファイルやサウンドストリームの作成も必要になります。場合によっては、読み込んだデータやこれから書き込むデータの形式を変換する必要も生じます。
第 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 を使って形式を変換する際のエラーの発生を防ぐのに役立ちます。