第 7 章: ファイルコンバータおよび形式コンバータの使用


 

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

第 3 章「オーディオシステムリソースへのアクセス」で簡単に説明したように、JavaTM Sound API はアプリケーション開発者にファイルの入出力および形式変換用のさまざまな機能を提供します。アプリケーションプログラムで、さまざまなサウンドファイル形式およびオーディオデータ形式の読み込み、書き込み、およびこれらの相互変換が可能です。

第 2 章「Sampled パッケージの概要」では、サウンドファイル形式およびオーディオデータ形式に関連する主なクラスを紹介しました。以下に要約を示します。

Java Sound API の実装により、オーディオの読み込み、書き込み、およびオーディオをほかのデータ形式およびファイル形式に変換する総合的な機能が必ずしも提供されているわけではありません。サポートされるのは、もっとも一般的なデータ形式とファイル形式だけという場合もあります。ただし、第 14 章「サンプリングオーディオサービスの提供」に示すように、サービスプロバイダは、このセットを継承した変換サービスの開発および配布が可能です。AudioSystem クラスが提供するメソッドを使用することにより、利用可能な変換の種類をアプリケーションプログラムから識別可能になります。詳細は、この章の「ファイル形式およびデータ形式の変換」で後述します。

サウンドファイルの読み込み

AudioSystem クラスは、次の 2 種類のファイル読み込みサービスを提供します。

上の 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 つの手順を実行します。

  1. ファイルから AudioInputStream オブジェクトを取得する
  2. ファイルから取得した連続するデータのチャンクを格納するバイト配列を作成する
  3. オーディオ入力ストリームから、配列にバイトを読み込む作業を繰り返す。反復のたびに、配列内のバイトを使って有用な作業 (再生、フィルタ処理、分析、表示、別のファイルへの書き込みなど) を行う

上記の手順を実行するコード例を次に示します。

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 を作成します。この固定長 (バイト単位) が、フレームの整数値と等しいことを確認します。これは、読み込みがフレームの一部またはひどい時にはサンプルの一部に対して行われただけで終了してしまわないようにするためです。このバイト配列は、ストリームから読み込んだオーディオデータのチャンクを一時的に保持するためのバッファになります。非常に短いサウンドファイルだけを読み込むことがわかっている場合は、AudioInputStreamgetFrameLength メソッドが返すフレーム長から得たバイト長を使って、この配列をファイル内のデータと同じ長さに設定できます。実際には、Clip オブジェクトを代わりに使うことが多いようです。ただし、一般的なケースで、メモリ不足状態での実行を避けるため、ファイルをブロックに分けて一度に 1 つのバッファを読み込みます。

内側の try 節に含まれる while ループで、AudioInputStream からオーディオデータをバイト配列に読み込みます。このループ内にコードを追加し、プログラムの要件に適した方法でこの配列内のオーディオデータを処理する必要があります。データに対してなんらかの信号処理を行う場合は、さらに AudioInputStreamAudioFormat にも問い合わせて、サンプルあたりのビット数などを確認する必要があります。

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 オブジェクトからファイルタイプを取得します。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 節で行われており、以下を含んでいます。

3 つのうちの 2 番目のメソッド 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 を使って、入力ファイルから作成した AudioInputStreaminFileAIS を変換する方法を示します。

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 を使って形式を変換する際のエラーの発生を防ぐのに役立ちます。