转载

(原创)speex与wav格式音频文件的互相转换(二)

之前写过了如何将speex与wav格式的音频互相转换,如果没有看过的请看一下连接

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

虽然自己实现了相关的压缩算法,但是发现还是与gauss的压缩比例差了一些,一部分是参数设置的问题,另外一部分是没有使用ogg的问题。

本来想研究一下gauss的ogg算法,然后将他录制的音频转为wav格式,再继续进行后面的频谱绘制之类的。

在后续的研究gauss的解码过程,他是先解了ogg的格式,然后分段,然后去掉speex的头,然后把一段段的speex数据再解码成pcm的原数据,最后使用audiotrack一段段播放出来了。audiotrack播放的时候是阻塞的,所以播了多久就解了多久。

既然他得到了一段段pcm的原数据,那我就可以去将这一段段的原数据拼起来,最后得到解码完成的整个的pcm数据,最后加上wav的头不就可以直接转换成wav格式的音频了么???

前天的时候想到这里,立马就去改了。

SpeexDecoder是gauss的demo里主要的解码类,我们复制一份,改名为SpeexFileDecoder

去掉里面跟播放相关的audiotrack变量,因为我们要得到的是解码数据,跟播放无关。

修改后代码如下

  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

正文到此结束
Loading...