ひでっぷの技術メモ

はてなダイアリーから移行しました

Clipで再生した音声ファイルが途中で途切れる

Javaで作成したあるシステムで待合呼出のためにwavファイルを鳴らしています。
複数の呼出があるのでlistにつっこんで順次再生しています。
最近お客さんから「呼出音声のおしりが途切れることがある。」という報告をうけました。
wavファイルの再生にはClipを使用しています。

音声ファイルの再生のために以下のようなクラスを作成。


#####トラブルを起こしていたソースです#####

/**
* 指定された音を鳴らすクラス */
public class Sounder(){
/**オーディオストリーム*/
private AudioInputSteam audioInputStream;
/**音声クリップ*/
private Clip clip = null;

/**
* 音声ファイルからClipを作成する
*/
public void init(File file) throws IOException,
UnsupportedAudioFileException,
LineUnavailableException, Exception {
// オーディオ入力ストリームを取得します
audioInputStream =
AudioSystem.getAudioInputStream(soundFile);
// オーディオ形式を取得します
AudioFormat audioFormat =
audioInputStream.getFormat();
// データラインの情報オブジェクトを生成します
DataLine.Info info =
new DataLine.Info(Clip.class, audioFormat);
// 指定されたデータライン情報に一致するラインを取得します
clip = (Clip) AudioSystem.getLine(info);
clip.open(audioInputStream);
}

/**
* 音声を鳴らす
*/
public void start() {
  clip.setFramePosition(0);
clip.start();
}

/**
* 音声を停止する
*/
 public void stop() {
if(clip != null){
clip.stop();
clip.close();
}
}

/**
* 音声再生中かを返す
*/
public boolean isPlaying(){
if(clip == null){
return false;
}
return clip.isRunning();
}
}


このSounderクラスで音声再生、音声再生後はThreadでisPlaying()を監視しfalseになったら次の音声を再生・・・という処理を行っていました。
わかる方ならばつっこみどころ満載のソースかもしれません(汗

現象を再現させるために、ボタンを押すと音声を再生するテストプログラムを作成しました。
以下一部抜粋です。


/**
* ボタンのアクションイベント
*/
public void jButton1_actionPerformed(ActionEvent e) {
try {
Sounder sounder = new Sounder();
File file = new File("音声ファイル名");
sound.init(file);
sound.play();
while(sound.isPlaying()){
System.out.println("waiting");
try {
Thread.sleep(100l);
}
catch (InterruptedException ex1) {
}
}
System.out.println("finish");
sound.stop();
}
catch (LineUnavailableException ex) {
ex.printStackTrace();
}
catch (UnsupportedAudioFileException ex) {
ex.printStackTrace();
}
catch (IOException ex) {
}
catch (Exception ex) {
}
}
}


これでisPlaying() == true (再生中)の間は抜けずに、音声ファイルを再生し終わったら抜けるはずです。
この状態でボタンを連打してみると・・・
音声切れまくり
おしりどころか頭も切れる始末・・・。
isPlaying()がすぐに音声再生終わってないのに抜けたりタイミングがばらばらです。
Clipの継承元DataLineのJavaDocには

ラインが実行中かどうかを示します。デフォルトは false です。開かれたラインは、start メソッドの呼び出しに応じて最初のデータが提示されると実行が開始され、stop メソッドの呼び出しに応じて、あるいは再生が完了して、表示されなくなるまで継続して実行されます。

とあるのでこれでいいのかと思っていましたが、どうやら違うみたいです。

これじゃあだめだということで音声再生の終了がわかる方法はないかとサイトを調べてみると、Javaでゲーム作りますが何か?に的確な情報がありました。
ClipのイベントリスナーとしてLineListenerというインターフェースがあり、このインターフェースのupdate(LineEvent event)メソッドevent.getType()で今の再生状態がわかるようです。
以下の情報をふまえてSoudnerクラスを以下のように書き換えました。


#####改良後のソースです#####

/**
* 指定された音を鳴らすクラス
*/
public class Sounder()
implements LineListener{
/**オーディオストリーム*/
private AudioInputSteam audioInputStream;
/**音声クリップ*/
private Clip clip = null;
/**音声再生中フラグ*/
private boolean running = false;

/**
* 音声ファイルからClipを作成する
*/
public void init(File file) throws IOException,
UnsupportedAudioFileException,
LineUnavailableException, Exception {
// オーディオ入力ストリームを取得します
audioInputStream =
AudioSystem.getAudioInputStream(soundFile);
// オーディオ形式を取得します
AudioFormat audioFormat =
audioInputStream.getFormat();
// データラインの情報オブジェクトを生成します
DataLine.Info info =
new DataLine.Info(Clip.class, audioFormat);
// 指定されたデータライン情報に一致するラインを取得します
clip = (Clip) AudioSystem.getLine(info);
clip.open(audioInputStrem);
audioInputStream.close();*1
}

/**
* 音声を鳴らす
*/
public void start() {
clip.addLineListener(this);*2
clip.start();
}

/**
* 音声を停止する
*/
 public void stop() {
if(clip != null){
clip.stop();
clip.close();*3
}
}

/**
* 音声再生中かを返す
*/
public boolean isPlaying() {
return running;
}

public update(LineEvent event) {
if (event.getType() == LineEvent.Type.STOP) {*4
clip.stop();
clip.close();*5
clip.setFramePosition(0);*6
running = false;
clip.removeLineListener(this);*7
}
}

}

  1. 音声再生が終了していないのに音声再生をstopしていたため音声のおしりが途切れていた。
  2. 再生ポジションが先頭に戻っていないのに音声を再生していたため頭が切れていた。

というのが原因だったようです。
しっかり調べることとしっかりテストすることは大切だなあと身にしみました・・・。

参考サイト:Javaでゲーム作りますが何か?--ClipでWAVE再生*8

*1:Clipに読ませてしまえばaudioInputStreamはclose()してもいいようです。

*2:再生するClipのlistenerに自分を追加する

*3:2009.02.26ここで閉じてしまうと次の再生ができない

*4:LineEventのTypeがSTOPならば音声再生が終了している

*5:2009.02.26ここで閉じてしまうと次の再生ができない

*6:次の再生に備えて再生ポジションを先頭に戻しておく

*7:listenerの削除忘れを防ぐため音声再生が終了したらClipのlistenerから削除しておく

*8:2009.02.26サイトアドレスが変わっていたので変更