1. 程式人生 > >Java實現音訊格式轉換 WAV---mp3,可使音訊壓縮

Java實現音訊格式轉換 WAV---mp3,可使音訊壓縮

最近做的一個小專案中,師兄安排的任務,要實現錄音然後儲存檔案,實現網路傳輸,然後我用初學的java實現了一個錄音機的功能(見前面的部落格),但是windows錄音預設儲存的格式是WAVE,字尾是WAV,經過大量測試,錄製一分鐘要1M大小,這樣不便於網路的傳輸,於是下面的任務就是實現音訊壓縮了,搜了幾天,找到了一個不錯的軟體Monkey Audio(實現說明,我們的專案得全是程式碼實現的),這個軟體的開源的,但是在官網下載的原始碼經過我們一個下午的測試,竟然跟最新軟體的壓縮差的很遠,於是我們便嘗試走其他的路,比如:音訊格式轉換也可以達到音訊壓縮的目的,於是便搜類似的東西,於是便找到了Lame解碼器。

百度百科普及:

LAME 是最好的MP3編碼器,編碼高品質MP3的最好也是唯一的選擇。LAME本身是DOS下的檔案,需要加外殼程式才比較容易使用,也可以在別的軟體(比如EAC)中間呼叫。是一款出色的MP3壓縮程式,它使用了獨創的人體聽音心理學模型和聲學模型,改變了人們對MP3高音發啞、低音發破的音質的印象。

然後我就通過程式碼在程式呼叫這個解碼器,實現了最後的音訊格式轉換,當然也達到的壓縮的目地,並且經過大量的測試,效果非常不錯,並沒有損壞音訊,同時壓縮比很高。

事先說明:Lame.exe檔案必須得有,並且在程式呼叫的時候,如果沒在java的src同一個資料夾下的話,必須在程式中使用絕對路徑,否則報錯。

貼出完整的錄音程式碼(這次與上次博文中程式碼添加了音訊格式轉換的程式碼)

/*
 * 實現錄音機的功能
 */
package com.liuyun.MyRecord1;

import java.awt.*;

import javax.swing.*;
import java.awt.event.*;
import java.io.*;
import javax.sound.sampled.*;
import java.lang.*;

public class MyRecord extends JFrame implements ActionListener{

	//定義錄音格式
	AudioFormat af = null;
	//定義目標資料行,可以從中讀取音訊資料,該 TargetDataLine 介面提供從目標資料行的緩衝區讀取所捕獲資料的方法。
	TargetDataLine td = null;
	//定義源資料行,源資料行是可以寫入資料的資料行。它充當其混頻器的源。應用程式將音訊位元組寫入源資料行,這樣可處理位元組緩衝並將它們傳遞給混頻器。
	SourceDataLine sd = null;
	//定義位元組陣列輸入輸出流
	ByteArrayInputStream bais = null;
	ByteArrayOutputStream baos = null;
	//定義音訊輸入流
	AudioInputStream ais = null;
	//定義停止錄音的標誌,來控制錄音執行緒的執行
	Boolean stopflag = false;
	//記錄開始錄音的時間
	long startPlay;
	
	//定義所需要的元件
	JPanel jp1,jp2,jp3;
	JLabel jl1=null;
	JButton captureBtn,stopBtn,playBtn,saveBtn;
	public static void main(String[] args) {
		
		//創造一個例項
		MyRecord mr = new MyRecord();

	}
	//建構函式
	public MyRecord()
	{
		//元件初始化
		jp1 = new JPanel();
		jp2 = new JPanel();
		jp3 = new JPanel();
		
		//定義字型
		Font myFont = new Font("華文新魏",Font.BOLD,30);
		jl1 = new JLabel("錄音機功能的實現");
		jl1.setFont(myFont);
		jp1.add(jl1);
		
		captureBtn = new JButton("開始錄音");
		//對開始錄音按鈕進行註冊監聽
		captureBtn.addActionListener(this);
		captureBtn.setActionCommand("captureBtn");
		//對停止錄音進行註冊監聽
		stopBtn = new JButton("停止錄音");
		stopBtn.addActionListener(this);
		stopBtn.setActionCommand("stopBtn");
		//對播放錄音進行註冊監聽
		playBtn = new JButton("播放錄音");
		playBtn.addActionListener(this);
		playBtn.setActionCommand("playBtn");
		//對保存錄音進行註冊監聽
		saveBtn = new JButton("保存錄音");
		saveBtn.addActionListener(this);
		saveBtn.setActionCommand("saveBtn");
		
		
		this.add(jp1,BorderLayout.NORTH);
		this.add(jp2,BorderLayout.CENTER);
		this.add(jp3,BorderLayout.SOUTH);
		jp3.setLayout(null);
		jp3.setLayout(new GridLayout(1, 4,10,10));
		jp3.add(captureBtn);
		jp3.add(stopBtn);
		jp3.add(playBtn);
		jp3.add(saveBtn);
		//設定按鈕的屬性
		captureBtn.setEnabled(true);
        stopBtn.setEnabled(false);
        playBtn.setEnabled(false);
        saveBtn.setEnabled(false);
		//設定視窗的屬性
		this.setSize(400,300);
		this.setTitle("錄音機");
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setLocationRelativeTo(null);
		this.setVisible(true);
		
		
	}
	
	public void actionPerformed(ActionEvent e) {
		
		if(e.getActionCommand().equals("captureBtn"))
		{
			//點選開始錄音按鈕後的動作
			//停止按鈕可以啟動
			captureBtn.setEnabled(false);
            stopBtn.setEnabled(true);
            playBtn.setEnabled(false);
            saveBtn.setEnabled(false);
            
            //呼叫錄音的方法
            capture();
            //記錄開始錄音的時間
            startPlay = System.currentTimeMillis();
		}else if (e.getActionCommand().equals("stopBtn")) {
			//點選停止錄音按鈕的動作
			captureBtn.setEnabled(true);
            stopBtn.setEnabled(false);
            playBtn.setEnabled(true);
            saveBtn.setEnabled(true);
            //呼叫停止錄音的方法     
            stop();
			//記錄停止錄音的時間
            long stopPlay = System.currentTimeMillis();
            //輸出錄音的時間
            System.out.println("Play continues " + (stopPlay-startPlay));
		}else if(e.getActionCommand().equals("playBtn"))
		{
			//呼叫播放錄音的方法
			play();
		}else if(e.getActionCommand().equals("saveBtn"))
		{
			//呼叫保存錄音的方法
			save();
		}
	}
	//開始錄音
	public void capture()
	{
		try {
			//af為AudioFormat也就是音訊格式
			af = getAudioFormat();
			DataLine.Info info = new DataLine.Info(TargetDataLine.class,af);
			td = (TargetDataLine)(AudioSystem.getLine(info));
			//開啟具有指定格式的行,這樣可使行獲得所有所需的系統資源並變得可操作。
			td.open(af);
			//允許某一資料行執行資料 I/O
			td.start();
			
			//建立播放錄音的執行緒
			Record record = new Record();
			Thread t1 = new Thread(record);
			t1.start();
			
		} catch (LineUnavailableException ex) {
			ex.printStackTrace();
			return;
		}
	}
	//停止錄音
	public void stop()
	{
		stopflag = true;			
	}
	//播放錄音
	public void play()
	{
		//將baos中的資料轉換為位元組資料
		byte audioData[] = baos.toByteArray();
		//轉換為輸入流
		bais = new ByteArrayInputStream(audioData);
		af = getAudioFormat();
		ais = new AudioInputStream(bais, af, audioData.length/af.getFrameSize());
		
		try {
			DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, af);
            sd = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
            sd.open(af);
            sd.start();
            //建立播放程序
            Play py = new Play();
            Thread t2 = new Thread(py);
            t2.start();           
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			try {
				//關閉流
				if(ais != null)
				{
					ais.close();
				}
				if(bais != null)
				{
					bais.close();
				}
				if(baos != null)
				{
					baos.close();
				}
				
			} catch (Exception e) {		
				e.printStackTrace();
			}
		}
		
	}
	//保存錄音
	public void save()
	{
		 //取得錄音輸入流
        af = getAudioFormat();

        byte audioData[] = baos.toByteArray();
        bais = new ByteArrayInputStream(audioData);
        ais = new AudioInputStream(bais,af, audioData.length / af.getFrameSize());
        //定義最終儲存的檔名
        File file = null;
        //寫入檔案
        try {	
        	//以當前的時間命名錄音的名字
        	//將錄音的檔案存放到F盤下語音資料夾下
        	File filePath = new File("F:/語音檔案");
        	if(!filePath.exists())
        	{//如果檔案不存在,則建立該目錄
        		filePath.mkdir();
        	}
        	long time = System.currentTimeMillis();
        	file = new File(filePath+"/"+time+".wav");      
            AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file);
            //將錄音產生的wav檔案轉換為容量較小的mp3格式
            //定義產生後文件名
            String tarFileName = time+".mp3";
            Runtime run = null;
            
            try {
				run = Runtime.getRuntime();
				long start=System.currentTimeMillis();
				//呼叫解碼器來將wav檔案轉換為mp3檔案
				Process p=run.exec(filePath+"/"+"lame -b 16 "+filePath+"/"+file.getName()+" "+filePath+"/"+tarFileName); //16為位元速率,可自行修改
				//釋放程序
				p.getOutputStream().close();
				p.getInputStream().close();
				p.getErrorStream().close();
				p.waitFor();
				long end=System.currentTimeMillis();
				System.out.println("convert need costs:"+(end-start)+"ms");
				//刪除無用的wav檔案
				if(file.exists())
				{
					file.delete();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
				//最後都要執行的語句
				//run呼叫lame解碼器最後釋放記憶體
				run.freeMemory();
			}
            
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
        	//關閉流
        	try {
        		
        		if(bais != null)
        		{
        			bais.close();
        		} 
        		if(ais != null)
        		{
        			ais.close();		
        		}
			} catch (Exception e) {
				e.printStackTrace();
			}   	
        }
	}
	//設定AudioFormat的引數
	public AudioFormat getAudioFormat() 
	{
		//下面註釋部分是另外一種音訊格式,兩者都可以
		AudioFormat.Encoding encoding = AudioFormat.Encoding.
        PCM_SIGNED ;
		float rate = 8000f;
		int sampleSize = 16;
		String signedString = "signed";
		boolean bigEndian = true;
		int channels = 1;
		return new AudioFormat(encoding, rate, sampleSize, channels,
				(sampleSize / 8) * channels, rate, bigEndian);
//		//取樣率是每秒播放和錄製的樣本數
//		float sampleRate = 16000.0F;
//		// 取樣率8000,11025,16000,22050,44100
//		//sampleSizeInBits表示每個具有此格式的聲音樣本中的位數
//		int sampleSizeInBits = 16;
//		// 8,16
//		int channels = 1;
//		// 單聲道為1,立體聲為2
//		boolean signed = true;
//		// true,false
//		boolean bigEndian = true;
//		// true,false
//		return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed,bigEndian);
	}
	//錄音類,因為要用到MyRecord類中的變數,所以將其做成內部類
	class Record implements Runnable
	{
		//定義存放錄音的位元組陣列,作為緩衝區
		byte bts[] = new byte[10000];
		//將位元組陣列包裝到流裡,最終存入到baos中
		//重寫run函式
		public void run() {	
			baos = new ByteArrayOutputStream();		
			try {
				stopflag = false;
				while(stopflag != true)
				{
					//當停止錄音沒按下時,該執行緒一直執行	
					//從資料行的輸入緩衝區讀取音訊資料。
					//要讀取bts.length長度的位元組,cnt 是實際讀取的位元組數
					int cnt = td.read(bts, 0, bts.length);
					if(cnt > 0)
					{
						baos.write(bts, 0, cnt);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
				try {
					//關閉開啟的位元組陣列流
					if(baos != null)
					{
						baos.close();
					}	
				} catch (IOException e) {
					e.printStackTrace();
				}finally{
					td.drain();
					td.close();
				}
			}
		}
		
	}
	//播放類,同樣也做成內部類
	class Play implements Runnable
	{
		//播放baos中的資料即可
		public void run() {
			byte bts[] = new byte[10000];
			try {
				int cnt;
	            //讀取資料到快取資料
	            while ((cnt = ais.read(bts, 0, bts.length)) != -1) 
	            {
	                if (cnt > 0) 
	                {
	                    //寫入快取資料
	                    //將音訊資料寫入到混頻器
	                    sd.write(bts, 0, cnt);
	                }
	            }
	           
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
				 sd.drain();
		         sd.close();
			}
			
			
		}		
	}	
}