(原創)speex與wav格式音頻文件的互相轉換(二)

以前寫過了如何將speex與wav格式的音頻互相轉換,若是沒有看過的請看一下鏈接html

http://www.cnblogs.com/dongweiq/p/4515186.htmljava

雖然本身實現了相關的壓縮算法,可是發現仍是與gauss的壓縮比例差了一些,一部分是參數設置的問題,另一部分是沒有使用ogg的問題。android

原本想研究一下gauss的ogg算法,而後將他錄製的音頻轉爲wav格式,再繼續進行後面的頻譜繪製之類的。git

在後續的研究gauss的解碼過程,他是先解了ogg的格式,而後分段,而後去掉speex的頭,而後把一段段的speex數據再解碼成pcm的原數據,最後使用audiotrack一段段播放出來了。audiotrack播放的時候是阻塞的,因此播了多久就解了多久。github

既然他獲得了一段段pcm的原數據,那我就能夠去將這一段段的原數據拼起來,最後獲得解碼完成的整個的pcm數據,最後加上wav的頭不就能夠直接轉換成wav格式的音頻了麼???算法

前天的時候想到這裏,立馬就去改了。數組

SpeexDecoder是gauss的demo裏主要的解碼類,咱們複製一份,更名爲SpeexFileDecoderdom

去掉裏面跟播放相關的audiotrack變量,由於咱們要獲得的是解碼數據,跟播放無關。ide

修改後代碼以下this

  1 package com.sixin.speex;
  2 
  3 import java.io.File;
  4 import java.io.FileOutputStream;
  5 import java.io.IOException;
  6 import java.io.RandomAccessFile;
  7 import java.util.ArrayList;
  8 import java.util.List;
  9 
 10 import android.media.AudioFormat;
 11 import android.media.AudioManager;
 12 import android.media.AudioTrack;
 13 import android.os.RecoverySystem.ProgressListener;
 14 import android.util.Log;
 15 
 16 /**
 17  * 採用Jspeex方案,首先解包,從ogg裏面接出來,而後使用speex decode將speex轉爲wav數據並進行播放
 18  * 
 19  * @author Honghe
 20  */
 21 public class SpeexFileDecoder {
 22 
 23     protected Speex speexDecoder;
 24     private String errmsg = null;
 25     private List<ProgressListener> listenerList = new ArrayList<ProgressListener>();
 26     private File srcPath;
 27     private File dstPath;
 28 
 29     public SpeexFileDecoder(File srcPath, File dstPath) throws Exception {
 30         this.srcPath = srcPath;
 31         this.dstPath = dstPath;
 32     }
 33 
 34     private void initializeAndroidAudio(int sampleRate) throws Exception {
 35         int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
 36 
 37         if (minBufferSize < 0) {
 38             throw new Exception("Failed to get minimum buffer size: " + Integer.toString(minBufferSize));
 39         }
 40     }
 41 
 42     public void addOnMetadataListener(ProgressListener l) {
 43         listenerList.add(l);
 44     }
 45 
 46     public String getErrmsg() {
 47         return errmsg;
 48     }
 49 
 50     public void decode() throws Exception {
 51         errmsg = null;
 52         byte[] header = new byte[2048];
 53         byte[] payload = new byte[65536];
 54         final int OGG_HEADERSIZE = 27;
 55         final int OGG_SEGOFFSET = 26;
 56         final String OGGID = "OggS";
 57         int segments = 0;
 58         int curseg = 0;
 59         int bodybytes = 0;
 60         int decsize = 0;
 61         int packetNo = 0;
 62         // construct a new decoder
 63         speexDecoder = new Speex();
 64         speexDecoder.init();
 65         // open the input stream
 66         RandomAccessFile dis = new RandomAccessFile(srcPath, "r");
 67         FileOutputStream fos = new FileOutputStream(dstPath);
 68 
 69         int origchksum;
 70         int chksum;
 71         try {
 72 
 73             // read until we get to EOF
 74             while (true) {
 75                 if (Thread.interrupted()) {
 76                     dis.close();
 77                     return;
 78                 }
 79 
 80                 // read the OGG header
 81                 dis.readFully(header, 0, OGG_HEADERSIZE);
 82                 origchksum = readInt(header, 22);
 83                 readLong(header, 6);
 84                 header[22] = 0;
 85                 header[23] = 0;
 86                 header[24] = 0;
 87                 header[25] = 0;
 88                 chksum = OggCrc.checksum(0, header, 0, OGG_HEADERSIZE);
 89 
 90                 // make sure its a OGG header
 91                 if (!OGGID.equals(new String(header, 0, 4))) {
 92                     System.err.println("missing ogg id!");
 93                     errmsg = "missing ogg id!";
 94                     return;
 95                 }
 96 
 97                 /* how many segments are there? */
 98                 segments = header[OGG_SEGOFFSET] & 0xFF;
 99                 dis.readFully(header, OGG_HEADERSIZE, segments);
100                 chksum = OggCrc.checksum(chksum, header, OGG_HEADERSIZE, segments);
101 
102                 /* decode each segment, writing output to wav */
103                 for (curseg = 0; curseg < segments; curseg++) {
104 
105                     if (Thread.interrupted()) {
106                         dis.close();
107                         return;
108                     }
109 
110                     /* get the number of bytes in the segment */
111                     bodybytes = header[OGG_HEADERSIZE + curseg] & 0xFF;
112                     if (bodybytes == 255) {
113                         System.err.println("sorry, don't handle 255 sizes!");
114                         return;
115                     }
116                     dis.readFully(payload, 0, bodybytes);
117                     chksum = OggCrc.checksum(chksum, payload, 0, bodybytes);
118 
119                     /* decode the segment */
120                     /* if first packet, read the Speex header */
121                     if (packetNo == 0) {
122                         if (readSpeexHeader(payload, 0, bodybytes, true)) {
123 
124                             packetNo++;
125                         } else {
126                             packetNo = 0;
127                         }
128                     } else if (packetNo == 1) { // Ogg Comment packet
129                         packetNo++;
130                     } else {
131 
132                         /* get the amount of decoded data */
133                         short[] decoded = new short[160];
134                         if ((decsize = speexDecoder.decode(payload, decoded, 160)) > 0) {
135                             //把邊解邊播改成寫文件
136                             fos.write(ShortAndByte.shortArray2ByteArray(decoded), 0, decsize * 2);
137                         }
138                         packetNo++;
139                     }
140                 }
141                 if (chksum != origchksum)
142                     throw new IOException("Ogg CheckSums do not match");
143             }
144         } catch (Exception e) {
145             e.printStackTrace();
146         }
147         fos.close();
148         dis.close();
149     }
150 
151     /**
152      * Reads the header packet.
153      * 
154      * <pre>
155      *  0 -  7: speex_string: "Speex   "
156      *  8 - 27: speex_version: "speex-1.0"
157      * 28 - 31: speex_version_id: 1
158      * 32 - 35: header_size: 80
159      * 36 - 39: rate
160      * 40 - 43: mode: 0=narrowband, 1=wb, 2=uwb
161      * 44 - 47: mode_bitstream_version: 4
162      * 48 - 51: nb_channels
163      * 52 - 55: bitrate: -1
164      * 56 - 59: frame_size: 160
165      * 60 - 63: vbr
166      * 64 - 67: frames_per_packet
167      * 68 - 71: extra_headers: 0
168      * 72 - 75: reserved1
169      * 76 - 79: reserved2
170      * </pre>
171      * 
172      * @param packet
173      * @param offset
174      * @param bytes
175      * @return
176      * @throws Exception
177      */
178     private boolean readSpeexHeader(final byte[] packet, final int offset, final int bytes, boolean init) throws Exception {
179         if (bytes != 80) {
180             return false;
181         }
182         if (!"Speex   ".equals(new String(packet, offset, 8))) {
183             return false;
184         }
185         //        int mode = packet[40 + offset] & 0xFF;
186         int sampleRate = readInt(packet, offset + 36);
187         //        int channels = readInt(packet, offset + 48);
188         //        int nframes = readInt(packet, offset + 64);
189         //        int frameSize = readInt(packet, offset + 56);
190         //        RongXinLog.SystemOut("mode=" + mode + " sampleRate==" + sampleRate + " channels=" + channels
191         //                + "nframes=" + nframes + "framesize=" + frameSize);
192         initializeAndroidAudio(sampleRate);
193 
194         if (init) {
195             // return speexDecoder.init(mode, sampleRate, channels, enhanced);
196             return true;
197         } else {
198             return true;
199         }
200     }
201 
202     protected static int readInt(final byte[] data, final int offset) {
203         /*
204          * no 0xff on the last one to keep the sign
205          */
206         return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | ((data[offset + 2] & 0xff) << 16) | (data[offset + 3] << 24);
207     }
208 
209     protected static long readLong(final byte[] data, final int offset) {
210         /*
211          * no 0xff on the last one to keep the sign
212          */
213         return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | ((data[offset + 2] & 0xff) << 16) | ((data[offset + 3] & 0xff) << 24) | ((data[offset + 4] & 0xff) << 32)
214                 | ((data[offset + 5] & 0xff) << 40) | ((data[offset + 6] & 0xff) << 48) | (data[offset + 7] << 56);
215     }
216 
217     protected static int readShort(final byte[] data, final int offset) {
218         /*
219          * no 0xff on the last one to keep the sign
220          */
221         return (data[offset] & 0xff) | (data[offset + 1] << 8);
222     }
223 
224 }

注意上面由於speex解碼出來的是160個short類型的數組,而java寫文件要求寫入的是byte數組,因此咱們仍是用到了short轉byte數組的方法shortArray2ByteArray,我封裝了一個類。也貼在下邊

 1 package com.sixin.speex;
 2 
 3 public class ShortAndByte {
 4     /** 
 5     * @功能 短整型與字節的轉換 
 6     * @param 短整型 
 7     * @return 兩位的字節數組 
 8     */
 9     public static byte[] shortToByte(short number) {
10         int temp = number;
11         byte[] b = new byte[2];
12         for (int i = 0; i < b.length; i++) {
13             b[i] = new Integer(temp & 0xff).byteValue();// 將最低位保存在最低位  
14             temp = temp >> 8; // 向右移8位  
15         }
16         return b;
17     }
18 
19     /** 
20      * @功能 字節的轉換與短整型 
21      * @param 兩位的字節數組 
22      * @return 短整型 
23      */
24     public static short byteToShort(byte[] b) {
25         short s = 0;
26         short s0 = (short) (b[0] & 0xff);// 最低位  
27         short s1 = (short) (b[1] & 0xff);
28         s1 <<= 8;
29         s = (short) (s0 | s1);
30         return s;
31     }
32 
33     /** 
34      * @說明 主要是爲解析靜態數據包,將一個字節數組轉換爲short數組 
35      * @param b 
36      */
37     public static short[] byteArray2ShortArray(byte[] b) {
38         int len = b.length / 2;
39         int index = 0;
40         short[] re = new short[len];
41         byte[] buf = new byte[2];
42         for (int i = 0; i < b.length;) {
43             buf[0] = b[i];
44             buf[1] = b[i + 1];
45             short st = byteToShort(buf);
46             re[index] = st;
47             index++;
48             i += 2;
49         }
50         return re;
51     }
52 
53     /** 
54      * @說明 主要是爲解析靜態數據包,將一個short數組反轉爲字節數組 
55      * @param b 
56      */
57     public static byte[] shortArray2ByteArray(short[] b) {
58         byte[] rebt = new byte[b.length * 2];
59         int index = 0;
60         for (int i = 0; i < b.length; i++) {
61             short st = b[i];
62             byte[] bt = shortToByte(st);
63             rebt[index] = bt[0];
64             rebt[index + 1] = bt[1];
65             index += 2;
66         }
67         return rebt;
68     }
69 }

讀出來的原數據咱們放入到了dstPath的文件,再來看看是怎麼操做的呢?其中我仍是修改了gauss的speexplayer方法。

咱們複製speexPlayer方法,更名爲SpeexFileDecoderHelper,按照以下方法修改

  1 /**
  2  * 
  3  */
  4 package com.sixin.speex;
  5 
  6 import java.io.File;
  7 
  8 import android.os.Handler;
  9 
 10 /**
 11  * @author honghe
 12  * 
 13  */
 14 public class SpeexFileDecoderHelper {
 15     private String srcName = null;
 16     private String dstName = null;
 17     private SpeexFileDecoder speexdec = null;
 18     private OnSpeexFileCompletionListener speexListener = null;
 19     private static final int speexdecode_completion = 1001;
 20     private static final int speexdecode_error = 1002;
 21 
 22     public Handler handler = new Handler() {
 23         public void handleMessage(android.os.Message msg) {
 24             int what = msg.what;
 25             switch (what) {
 26             case speexdecode_completion:
 27                 if (speexListener != null) {
 28                     speexListener.onCompletion(speexdec);
 29                 } else {
 30                     System.out.println("司信---------null===speexListener");
 31                 }
 32                 break;
 33             case speexdecode_error:
 34                 if (speexListener != null) {
 35                     File file = new File(SpeexFileDecoderHelper.this.srcName);
 36                     if (null != file && file.exists()) {
 37                         file.delete();
 38                     }
 39                     speexListener.onError(null);
 40                 }
 41                 break;
 42             default:
 43                 break;
 44             }
 45         };
 46     };
 47 
 48     public SpeexFileDecoderHelper(String fileName,String dstName, OnSpeexFileCompletionListener splistener) {
 49         this.speexListener = splistener;
 50         this.srcName = fileName;
 51         this.dstName = dstName;
 52         try {
 53             speexdec = new SpeexFileDecoder(new File(this.srcName),new File(this.dstName));
 54         } catch (Exception e) {
 55             e.printStackTrace();
 56             File file = new File(SpeexFileDecoderHelper.this.srcName);
 57             if (null != file && file.exists()) {
 58                 file.delete();
 59             }
 60         }
 61     }
 62 
 63     public void startDecode() {
 64         RecordDecodeThread rpt = new RecordDecodeThread();
 65         Thread th = new Thread(rpt);
 66         th.start();
 67     }
 68 
 69     public boolean isDecoding = false;
 70 
 71     class RecordDecodeThread extends Thread {
 72 
 73         public void run() {
 74             try {
 75                 if (speexdec != null) {
 76                     isDecoding = true;
 77                     speexdec.decode();
 78                     if (null != speexdec.getErrmsg()) {
 79                         throw new Exception(speexdec.getErrmsg());
 80                     }
 81                 }
 82                 System.out.println("RecordPlayThread   文件轉換完成");
 83                 if (isDecoding) {
 84                     handler.sendEmptyMessage(speexdecode_completion);
 85                 } 
 86                 isDecoding = false;
 87             } catch (Exception t) {
 88                 t.printStackTrace();
 89                 System.out.println("RecordPlayThread   文件轉換出錯");
 90                 handler.sendEmptyMessage(speexdecode_error);
 91                 isDecoding = false;
 92             }
 93         }
 94     }
 95 
 96     /**
 97      * 結束播放
 98      */
 99     public void stopDecode() {
100         isDecoding = false;
101     }
102 
103     public String getSpxFileName() {
104         return this.srcName;
105     };
106 }

這個方法是開啓了一個現成去解碼,而後解碼完成後會發送handler,調用回調方法,通知解碼失敗仍是成功。OnSpeexFileCompletionListener這個很簡單,我須要貼嗎?仍是貼上吧,省的被罵娘。

 1 package com.sixin.speex;
 2 
 3 /**
 4  * Speex音頻解碼完成監聽
 5  * @author honghe
 6  *
 7  */
 8 public interface OnSpeexFileCompletionListener {
 9     void onCompletion(SpeexFileDecoder speexdecoder);
10     void onError(Exception ex);
11 }

到此代碼都貼出來了。什麼?!還不會用?哦,我還沒寫怎麼加wav頭呢,那再寫個方法吧

 1 /**
 2      * 語音轉換
 3      * 
 4      * @param name
 5      * @param srcFileName spx文件名
 6      * @param dstFileName 轉換後獲得文件的文件名
 7      */
 8     public static void decodeSpx(Context context, String srcFileName, final String dstFileName) {
 9         final String temppath = AudioFileFunc.getFilePathByName("temp.raw");
10         try {
11             // 若是是speex錄音
12             if (srcFileName != null && srcFileName.endsWith(".spx")) {
13                 if (mSpeexFileDecoderHelper != null && mSpeexFileDecoderHelper.isDecoding) {
14                     stopMusic(context);
15                 } else {
16                     muteAudioFocus(context, true);
17                     mSpeexFileDecoderHelper = new SpeexFileDecoderHelper(srcFileName, temppath, new OnSpeexFileCompletionListener() {
18 
19                         @Override
20                         public void onError(Exception ex) {
21                             System.out.println("轉換錯誤");
22                         }
23 
24                         @Override
25                         public void onCompletion(SpeexFileDecoder speexdecoder) {
26                             System.out.println("轉換完成");
27                             WaveJoin.copyWaveFile(temppath, dstFileName);
28                         }
29                     });
30                     mSpeexFileDecoderHelper.startDecode();
31                 }
32             } else {
33                 System.out.println("音頻文件格式不正確");
34             }
35 
36         } catch (Exception e) {
37             e.printStackTrace();
38         }
39     }


copyWaveFile這個方法在哪裏?去看開頭的那個連接吧,上面有。不過在加wav頭的時候要注意,加的頭要和你錄製音頻的時候設置的參數一致,好比samplerate,聲道數,framesize這些,我就設置錯了一次,gauss錄製音頻的時候使用的是單聲道,我加入的wav頭的channel設置成了2,結果放出來的聲音老搞笑了,就跟快放同樣。有興趣你能夠試試。

代碼已更新

代碼連接以下:

https://github.com/dongweiq/study/tree/master/Record

個人github地址:https://github.com/dongweiq/study

歡迎關注,歡迎star o(∩_∩)o 。有什麼問題請郵箱聯繫 dongweiqmail@gmail.com qq714094450

相關文章
相關標籤/搜索