技術系備忘録

プログラミング、人工知能、議論などの技術系の設定・使い方などの方法の置き場です.いつも助けてもらっているので誰かの助けになれば

Javaでmp3を再生する(コード編)

<2016年追記>

以下の記事はmp3のライブラリをプロジェクトにimportする必要があります

Javaでmp3を再生する(準備編) - 技術系備忘録

-----------------------

また,現在下記のコードを実行すると,デフォルトでは音量がmuteになるようです.

cipepser.hatenablog.com

3年前はそんなことなかったんですが,未検証です.情報ありがとうございます.

そのうち追記します.

-----------------------

<2015年追記>ソース汚いので書き直したい

-----------------------

 

まずは予備知識から。

Javaでmp3を再生するには次の2つの方法があります。

  • Clip
  • Source Data Line

Clipは再生前にすべてのサウンドデータを指定します。Source Data Line は再生中にデータを出力ラインのバッファに書き込みます。この違いは次のように出てきます。

Clipは再生前にすべてのデータをロードします。このため、クリップに登録できるデータの大きさには制限がありますし、再生前にデータを登録する時間が必要です。反面、再生する際、出力ラインのバッファがたまるのを待つ必要が無く、短い時間で再生が始められます。また、再生位置を戻したりすることが容易です。

Source Data Line はデータをオンタイムに出力ラインに書き込みます。このため入力→出力が可能で、データの容量に制限がなくなります。また、このような考えからオンタイムの入力を出力へ書き出すことができます。反面、再生までに出力ラインのバッファが一杯になる必要があり、やや時間がかかる場合があります。

 

今回はSource Data Line を用います。

まず、データのストリームを開き、再生する型を定義します。


import java.io.File;
import java.io.IOException;

import javax.sound.sampled.*;

public class PCMFilePlayer implements Runnable { 
    private File file;
	private AudioInputStream in;
	private SourceDataLine line;
	private int frameSize;
	private byte[] buffer = new byte [32 * 1024]; // 32k is arbitrary
	private Thread playThread;
	private boolean playing;
	private boolean notYetEOF;
	int readPoint = 0;
	int bytesRead = 0;
	private AudioInputStream din = null;
	AudioFormat decodedFormat;

	public PCMFilePlayer (File f) throws IOException,
						UnsupportedAudioFileException,
						LineUnavailableException {
		file = f; // openするファイル
		in = AudioSystem.getAudioInputStream (f);
		AudioFormat format = in.getFormat();
		decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                				format.getSampleRate(),
                				16,
                				format.getChannels(),
                				format.getChannels()*2,
                				format.getSampleRate(),
                				false); // PCMフォーマットを指定
		// 指定された形式へ変換
		din = AudioSystem.getAudioInputStream(decodedFormat, in); 
		format = din.getFormat();
		frameSize = format.getFrameSize(); 
		DataLine.Info info = new DataLine.Info (SourceDataLine.class,
							decodedFormat); 
		// ラインを取得
		line = (SourceDataLine) AudioSystem.getLine (info); 
		line.open(); 
		playThread = new Thread (this); 
		playing = false; 
		notYetEOF = true;        
		playThread.start();
	}
	public void run() {
		readPoint = 0;
		bytesRead = 0;
		while(true){
			try {
				while (notYetEOF) {
					if (playing) {
						bytesRead = din.read (buffer, 
	                                                       readPoint, 
							       buffer.length - readPoint);
	                                        if (bytesRead == -1) { 
	                	   	                notYetEOF = false; 
	                	   	                break;
						}
						int leftover = bytesRead % frameSize;
						line.write (buffer, readPoint, bytesRead-leftover);
						System.arraycopy (buffer, bytesRead,
								  buffer, 0, 
								  leftover);
		                                readPoint = leftover;
					} else { 
						try { Thread.sleep (10);} 
						catch (InterruptedException ie) {}
					}
				}
				line.drain();
				line.stop();
				playing=false;
				notYetEOF=true;
				lineReset();
				// スレッドを休止
				suspend();
			} catch (IOException ioe) {
				//ioe.printStackTrace();
			} catch (InterruptedException e){
				
			} finally {
				//line.close();
			}
		}
	} // run

	public void start() {
		playing = true;
		line.start();
		try {
			// スレッド復帰
			resume();
		} catch (InterruptedException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
	}
	
	public void contStart(){
    	if(playing){
			line.stop();
			line.flush();
			lineReset();
			line.start();
    	}else{
    		start();
    	}
	}

	public void stop(){
		playing = false;
		line.stop();
		line.flush();
		lineReset();
	}
   
	public SourceDataLine getLine() {
		return line;
	}

	public File getFile() {		
		return file; 
	}
	
    /**
     * Lineの音量を調整する
     * @param linearScalar 0-1までの小数
     */
	public void volume(double linearScalar){
    	try{
    		FloatControl control = 
    				(FloatControl)line.getControl(FloatControl.Type.MASTER_GAIN);
    		control.setValue((float)Math.log10(linearScalar) * 20);
    	}catch(IllegalArgumentException e){
    		e.printStackTrace();
    	}
	}
	
    /**
     * Lineをミュートする
     * @param state true or false
     */
	public void mute(boolean state){
    	BooleanControl control = 
    			(BooleanControl)line.getControl(BooleanControl.Type.MUTE);
    	if(control.getValue()!=state){
    		control.setValue(state);
    	}
	}
	
	public boolean isUsed(){
		return playing;
	}
	
    /**
     * Lineを再取得する。
     * 
     */
	public void lineReset(){
		try {
			in = AudioSystem.getAudioInputStream (file);
			din = AudioSystem.getAudioInputStream(decodedFormat, in);
		} catch (UnsupportedAudioFileException e) {
		} catch (IOException e) {
		}
	}
	
    /**
     * スレッドを休止。
     * 
     */
	public synchronized void suspend() throws InterruptedException {
	    this.wait();
	}
	
    /**
     * スレッドを復帰。
     * 
     */
	public synchronized void resume() throws InterruptedException {
	    this.notify();
	}
	
	public void exit(){
		line.stop();
		line.close();
	}
}

このような型を定義します。この型では以下のことができます。

  • 再生
  • 停止
  • 音量の変更
  • ミュート

サンプルとして再生のみを実行する型を次のように定義します。


import java.io.File;
import java.io.IOException;

import javax.sound.sampled.*;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

public class StreamingLineSound extends Object 
       implements LineListener {
	File soundFile;
	JDialog playingDialog;
	PCMFilePlayer player;
	
	public static void main (String[] args) {
		JFileChooser chooser = new JFileChooser();
		chooser.showOpenDialog(null);
		File f = chooser.getSelectedFile();
	try {
		StreamingLineSound s = new StreamingLineSound (f);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

public StreamingLineSound (File f)
	throws LineUnavailableException, IOException,
		   UnsupportedAudioFileException { 
	soundFile = f; 

	JOptionPane pane = new JOptionPane ("Playing " + f.getName(),
					   JOptionPane.PLAIN_MESSAGE); 
    playingDialog = pane.createDialog (null, "Streaming Sound");
    playingDialog.pack();

	player = new PCMFilePlayer (soundFile);
	player.getLine().addLineListener (this);
	player.start();

}
// LineListener
public void update (LineEvent le) {
	LineEvent.Type type = le.getType();
	if (type == LineEvent.Type.OPEN) {
		System.out.println ("OPEN");
	} else if (type == LineEvent.Type.CLOSE) {
		System.out.println ("CLOSE");
		System.exit (0);
	} else if (type == LineEvent.Type.START) {
		System.out.println ("START");
		playingDialog.setVisible(true);
	} else if (type == LineEvent.Type.STOP) {
		System.out.println ("STOP");
		playingDialog.setVisible(false);
		player.exit();

	} 
  } 
}

実行できたでしょうか。エラーが出た方は準備編を読み直してください。

このサンプルには java でサウンドを扱う際に用いる機能が数多く入っています。これとHash,Clipを組み合わせることで多彩な機能が実現できます。

 

 

参考にさせていただいたサイト

CodeIdol  様
http://codeidol.com/java/swing/Audio/Play-Non-Trivial-Audio/