< 目次

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

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

第3章「オーディオ・システム・リソースへのアクセス」で簡単に説明したように、Java 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を作成します。この固定長(バイト単位)が、フレームの整数値と等しいことを確認します。これは、読込みがフレームの一部またはひどい時にはサンプルの一部に対して行われただけで終了してしまわないようにするためです。このバイト配列は、ストリームから読み込んだオーディオ・データのチャンクを一時的に保持するためのバッファになります。非常に短いサウンド・ファイルだけを読み込むことがわかっている場合は、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節で行われており、以下を含んでいます。

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 © 1993, 2020, Oracle and/or its affiliates. All rights reserved.