2007年4月10日火曜日

JavaでWAVファイルを再生する

javax.sound.sampledというパッケージを使って、とりあえずWAVファイル(AIFFなども可能)をサウンドデバイスに出力する。とりあえずということで、前提と条件は以下の通り

  • デフォルトのデバイスに出力する
  • 制御は、開始、一時停止、再開だけ

とりあえず、いきなりソースコード。

public class RenderAudioFile implements LineListener, Runnable {
 protected AudioInputStream fileStream = null;
 protected SourceDataLine sourceDataLine = null;
 private byte sampleBuffer[];//データ受け渡し用のバッファ

 public void openFile(File file) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
  //出力するファイルを受け取るメソッド
  fileStream = AudioSystem.getAudioInputStream(file);
  AudioFormat format = fileStream.getFormat();
  sourceDataLine = AudioSystem.getSourceDataLine(format);
  sourceDataLine.addLineListener(this);
  sourceDataLine.open(format);
  sampleBuffer = new byte[sourceDataLine.getBufferSize()];
 }
 public void play() {
  // 再生開始、再開用メソッド
  if(sourceDataLine == null) {
   return;
  }
  sourceDataLine.start();
  new Thread(this).start();
 }
 public void pause() {
  // 一時停止メソッド
  if(sourceDataLine == null) {
   return;
  }
  sourceDataLine.stop();
 }
 public void update(LineEvent ev) {
  // LineEvent用ハンドラ UI制御用に使うとよい
  LineEvent.Type type = ev.getType();
  if( type == LineEvent.Type.OPEN) {
  }
  else if( type == LineEvent.Type.START) {
  }
  else if( type == LineEvent.Type.STOP) {
  }
 }
 public void run() {
  //再生用のスレッド用メソッド
  int readSize = 0;
  do {
   try {
    readSize = fileStream.read(sampleBuffer);
   } catch (IOException e) {
    sourceDataLine.stop();
    break;
   }
   if (readSize < 0) {
    sourceDataLine.stop();
    break;
   }
   }
   sourceDataLine.write(sampleBuffer, 0, readSize);
  } while (sourceDataLine.isRunning());
 }
}

とりあえず、こんな感じです。大したコードでなくて申し訳ないですが、いくつかポイントがあるので、説明させていただきますと。

public void update(LineEvent ev);ですが、いらないときはメソッドごと消してもらって、implementsからLineListenerを消して、、sourceDataLine.addLineListener(this);も消してください。あと、このイベントハンドラーで「START」がくるタイミングですが、sourceDataLine.start();を呼び出した時には呼ばれずに、sourceDataLine.write();でデーターを書き込んだときに呼ばれるようです。UIの制御にこれを使ったほうがよいかというと、startやstopの処理が完了するとこれらのイベントが呼ばれてくるので、連打されたりしたときにおかしなことにならないようにこのタイミングで制御したほうがよいかと思われるからです。

あと、とりあえずで手を抜いてしまってあるところがあるので、白状しておきますが。sourceDataLine.write();はstopが呼ばれると書き込みのブロッキングを解除して復帰してくるのですが、このときの戻り値に書き込みされたデーターのサイズとなります。この戻り値が渡したバッファーのサイズより小さいときには、その差分のデーターは書き込みされなかったわけで、本当なら再開されたときにその分のデーターを書き込んでから、ファイルから読み込むという処理をしないと、そのデーターはスキップされて再生されるということになります。

これを回避するには、この戻り値をメンバー変数にとっておいて、再度スレッドが呼ばれたときにファイルから読み込まず、残りのデーターを渡すという処理を入れればよいでしょう。

0 件のコメント: