[先頭の項目] [前の項目] [次の項目] [最後の項目]

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



[先頭の項目] [前の項目] [次の項目] [最後の項目]

Copyright © 2000, Sun Microsystems Inc. All rights reserved.