【Java】WAVファイルの再生

2018年12月26日

本稿はJavaでwavファイルを再生するClipの使用方法について説明します。

ゲーム制作においてBGM、効果音はそこそこ重要なものになると思います。

Clipを使用することで、wavファイルの再生、ループ再生、停止、一時停止、再生位置の指定、音量設定などができます。

今回のサンプルソースではこちらの音声ファイルを使用しています。



Clipオブジェクトの取得

まずはClipオブジェクトの取得します。

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class Test {
	public static void main(String[] args) {
		Clip clip = createClip(new File("sample.wav"));
		//ここで再生メソッドの呼び出し
	}

	public static Clip createClip(File path) {
		//指定されたURLのオーディオ入力ストリームを取得
		try (AudioInputStream ais = AudioSystem.getAudioInputStream(path)){
			
			//ファイルの形式取得
			AudioFormat af = ais.getFormat();
			
			//単一のオーディオ形式を含む指定した情報からデータラインの情報オブジェクトを構築
			DataLine.Info dataLine = new DataLine.Info(Clip.class,af);
			
			//指定された Line.Info オブジェクトの記述に一致するラインを取得
			Clip c = (Clip)AudioSystem.getLine(dataLine);
			
			//再生準備完了
			c.open(ais);
			
			return c;
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (UnsupportedAudioFileException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (LineUnavailableException e) {
			e.printStackTrace();
		}
		return null;
	}
}

AudioSystem.getAudioInputStream(url)メソッドでオーディオデータを読み込むストリームを取得します。こいつからバイナリデータを読み込めます。

ただしClipの利用では自力でデータを読み込む処理は不要です。

AudioFormat af = ais.getFormat()でオーディオ形式を読み込みます。ステレオとか周波数とかです。まああまり深く考える必要はありません。

DataLine.Info dataLine = new DataLine.Info(Clip.class,af)で指定されたオーディオフォーマットを再生するための情報を取得します。

最後にAudioSystem.getLineメソッドを呼び出すと再生操作を行うためのオブジェクトを取得できます。

openメソッドを呼び出して再生可能状態にしておきましょう。

再生

再生は簡単でstartメソッドを呼び出すだけです。

メインメソッドのみ書き換えたサンプルです。

	public static void main(String[] args) throws Exception{
		Clip clip = createClip(new File("sample.wav"));
		clip.start();
		Thread.sleep(3000);
	}
実行結果

mainメソッドが終了すると再生が止まるので、スレッドを3秒停止させています。

3秒くらいBGMが流れれば成功です。

ループ再生

音声を指定回数繰り返して再生させることが可能です。

loop(繰り返し回数)と指定しましょう。

Clip.LOOP_CONTINUOUSLYを指定すると無限ループになります。

	public static void main(String[] args) throws Exception{
		Clip clip = createClip(new File("sample.wav"));
		clip.loop(Clip.LOOP_CONTINUOUSLY);
		Thread.sleep(9000);
	}
実行結果

音声が流れ終わって、もう一度最初から流せました。

指定区間ループ再生

setLoopPointsメソッドを使用するとループ区間を指定できます。

	public static void main(String[] args) throws Exception{
		Clip clip = createClip(new File("sample.wav"));
		clip.setLoopPoints(130000,190000);
		clip.loop(Clip.LOOP_CONTINUOUSLY);
		Thread.sleep(9000);
	}
実行結果

最初から再生し、途中の区間を繰り返して再生できました。

一般ゲームでもイントロがあるけど途中の区間を繰り返して再生されていて途切れずに流れ続けるようなものがありますが、それを再現させたいときに使えそうです。

再生位置の指定

setFramePositionで再生位置の指定が可能です。

	public static void main(String[] args) throws Exception{
		Clip clip = createClip(new File("sample.wav"));
		clip.setFramePosition(80000);
		clip.start();
		Thread.sleep(3000);
	}
実行結果

途中から再生されました。

一時停止

stopメソッドを使用します。

	public static void main(String[] args) throws Exception{
		Clip clip = createClip(new File("sample.wav"));
		clip.start();
		Thread.sleep(1000);
		clip.stop();
		Thread.sleep(1000);
		clip.start();
		Thread.sleep(1000);
		clip.stop();
	}
実行結果

サンプルではわかりやすくするために1秒ごとに再生、一時停止を繰り返しました。

停止

Clipには音声停止用のメソッドはないため、stopメソッド、flushメソッド、setFramePositionメソッドを利用します。

stopメソッドで一時停止、flushメソッドで音声再生の内部バッファを削除、setFramePositionで再生位置を最初に戻します。

flushメソッドですが、stopメソッドの直前に既に読み込まれた内部バッファが残ってしまうと次回再生時にその残りを再生してしまうため、呼び出しています。

	public static void main(String[] args) throws Exception{
		Clip clip = createClip(new File("sample.wav"));
		clip.start();
		Thread.sleep(1000);
		clip.stop();
		clip.flush();
		clip.setFramePosition(0);
		Thread.sleep(1000);
		clip.start();
		Thread.sleep(6000);
	}
実行結果

音量設定

音量設定はgetControlメソッドで取得できるオブジェクトを操作すると設定できます。

実際に音量設定を使用してフェードアウトさせてみました。

	public static void main(String[] args) throws Exception{
		Clip clip = createClip(new File("sample.wav"));
		clip.start();
		FloatControl ctrl = (FloatControl)clip.getControl(FloatControl.Type.MASTER_GAIN);
		for ( int i = 100;i >= 0;i-- ) {
			Thread.sleep(50);
			ctrl.setValue((float)Math.log10((float)i / 100)*20);
		}
	}
実行結果

リソースの破棄

wavファイルは容量がでかいのにもかかわらず、データをすべてメモリに展開した状態になっているため、複数読み込むと簡単にOutOfMemoryErrorを引き起こします。

最近のPCではある程度までは行けそうですが(^^;)

そんなものを破棄せずに持ったままにしているとメモリの無駄遣いです。

使用後は必ずcloseメソッドで破棄してください。

そういう意味では上記のサンプルはcloseをしていないので悪いサンプルと言えますね。

	public static void main(String[] args) throws Exception{
		Clip clip = createClip(new File("sample.wav"));
		clip.start();
		Thread.sleep(9000);
		clip.close();
	}

当然try-with-resource構文でのcloseも可能です。