Java實現音訊格式轉換 WAV---mp3,可使音訊壓縮
阿新 • • 發佈:2019-01-22
最近做的一個小專案中,師兄安排的任務,要實現錄音然後儲存檔案,實現網路傳輸,然後我用初學的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(); } } } }