TOP > 技術メモ > Java > 音声管理

音声管理

Javaでサウンド(WAVE)を再生する

Java標準APIを使用する場合、以下の再生方法があるようだ

SourceDataLine
バッファを読み込みながら再生するため、長い音声や、合成をかけながら再生する場合に使用する。
Clip
Clipは短い音声に適しており、データをメモリ上に読み込んでおいてから再生する。
*注意点
Windows環境へリモート接続中にアプリケーション起動した状態で「切断」(ログオフではない)を行うと、
AudioSystem.getLineメソッドでIllegalArugmentExceptionが発生する場合があった。
上記リモート接続時に、「リモートコンピュータサウンド」設定を「このコンピュータで聞く」に設定していたため、
「切断」ではアプリケーションが起動したままセッションが残ってしまうため、対応する音声ラインが見つからずIllegalArugmentExceptionとなっているようだ。
というわけで、検証するために、n秒後に音声を再生するアプリ作成・実行後、即「切断」し、再接続すると期待通りIllegalArugmentExceptionが発生していた。
「リモートコンピュータサウンド」設定を「再生しない」にした場合も上記と同様のIllegalArugmentExceptionが発生していた。
リモート上でも「動作保障」し、常に起動&自動音声再生するアプリケーションを作成する場合は、注意が必要そうだ。

SourceDataLineから再生するパターン
					package fomsan.ne.jp.example.sound;
	
					import java.io.File;
					
					import javax.sound.sampled.AudioFormat;
					import javax.sound.sampled.AudioInputStream;
					import javax.sound.sampled.AudioSystem;
					import javax.sound.sampled.LineEvent;
					import javax.sound.sampled.LineListener;
					import javax.sound.sampled.SourceDataLine;
					
					public class Play {
						public static void main(final String[] args) {
							class MyLineListener implements LineListener {
								@Override
								public void update(final LineEvent le) {
									final LineEvent.Type type = le.getType();
									System.out.println(type);
								}
							}
					
							try {
								final AudioInputStream fis =
										AudioSystem.getAudioInputStream(new File("Sound.wav"));
								System.out.println("File AudioFormat: " + fis.getFormat());
								final AudioInputStream ais = AudioSystem.getAudioInputStream(
										AudioFormat.Encoding.PCM_SIGNED, fis);
								final AudioFormat af = ais.getFormat();
								System.out.println("AudioFormat: " + af.toString());
					
								final int frameRate = (int) af.getFrameRate();
								System.out.println("Frame Rate: " + frameRate);
								final int frameSize = af.getFrameSize();
								System.out.println("Frame Size: " + frameSize);
					
								final SourceDataLine line = AudioSystem.getSourceDataLine(af);
								line.addLineListener(new MyLineListener());
					
								line.open(af);
								final int bufSize = line.getBufferSize();
								System.out.println("Buffer Size: " + bufSize);
					
								line.start();
					
								final byte[] data = new byte[bufSize];
								int bytesRead;
					
								while ((bytesRead = ais.read(data, 0, data.length)) != -1) {
									line.write(data, 0, bytesRead);
								}
					
								line.drain();
								line.stop();
								line.close();
							} catch (final Exception e) {
								System.out.println(e);
							}
						}
					}
					
Clipから再生するパターン
					package fomsan.ne.jp.example.sound;
	
					import java.io.File;
					
					import javax.sound.sampled.AudioInputStream;
					import javax.sound.sampled.AudioSystem;
					import javax.sound.sampled.Clip;
					
					public class PlayClip {
						public static void main(final String[] args) {
							try {
								System.out.println("Sound Start");
								final AudioInputStream ais =
										AudioSystem.getAudioInputStream(new File("Sound.wav"));
								ais.getFormat();
								final Clip line = AudioSystem.getClip();
								line.open(ais);
								line.start();
								System.out.println("Playing...");
								Thread.sleep(1);
								line.drain();
								line.stop();
								line.close();
								System.out.println("Sound End");
							} catch (final Exception e) {
								System.out.println(e);
							}
						}
					}
					

強制終了(Application hang)対策

SourceDataLine drain() でハングする場合
Windows環境で音声再生時にアプリケーションがハングする問題が発生した。
原因は、EventDispatchスレッドのデッドロック(Java既知バグ)。
解決方法は、Javaを最新へ(1.6.45または1.7)へアップデートするしかないため、
drain()等のEventDipatchスレッド排他処理を呼ばないようにする。
					// ライン開始
					line.start();

					final int avail = line.available();

					int bytesRead = 0;
					while (bytesRead != -1)
					{
						bytesRead = ais.read(bufData, 0, bufData.length);
						if (bytesRead != -1)
						{
							line.write(bufData, 0, bytesRead);
						}
					}
					
					// line.drain()を使用しない(ブロッキングが発生する)
					while (line.available() < avail)
					{
						Thread.sleep(WAIT_TIME);
					}
					
					// ラインを閉じる
					line.close();
					

▲ページの先頭へ