博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(原创)speex与wav格式音频文件的互相转换(二)
阅读量:4659 次
发布时间:2019-06-09

本文共 16860 字,大约阅读时间需要 56 分钟。

之前写过了如何将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
listenerList = new ArrayList
(); 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 packet129 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 *
155      *  0 -  7: speex_string: "Speex   "156      *  8 - 27: speex_version: "speex-1.0"157      * 28 - 31: speex_version_id: 1158      * 32 - 35: header_size: 80159      * 36 - 39: rate160      * 40 - 43: mode: 0=narrowband, 1=wb, 2=uwb161      * 44 - 47: mode_bitstream_version: 4162      * 48 - 51: nb_channels163      * 52 - 55: bitrate: -1164      * 56 - 59: frame_size: 160165      * 60 - 63: vbr166      * 64 - 67: frames_per_packet167      * 68 - 71: extra_headers: 0168      * 72 - 75: reserved1169      * 76 - 79: reserved2170      * 
171 * 172 * @param packet173 * @param offset174 * @param bytes175 * @return176 * @throws Exception177 */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=" + channels191 // + "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 sign205 */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 sign212 */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 sign220 */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                         @Override20                         public void onError(Exception ex) {21                             System.out.println("转换错误");22                         }23 24                         @Override25                         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

转载于:https://www.cnblogs.com/dongweiq/p/4521655.html

你可能感兴趣的文章
数据集
查看>>
[Leetcode] unique paths ii 独特路径
查看>>
HDU 1217 Arbitrage (Floyd + SPFA判环)
查看>>
IntelliJ idea学习资源
查看>>
Django Rest Framework -解析器
查看>>
ExtJs 分组表格控件----监听
查看>>
Hibernate二级缓存配置
查看>>
LoadRunner常用术语
查看>>
关于jedis2.4以上版本的连接池配置,及工具类
查看>>
记忆讲师石伟华微信公众号2017所有文章汇总(待更新)
查看>>
FactoryBean
查看>>
Coolite动态加载CheckboxGroup,无法在后台中获取
查看>>
C3P0连接池工具类使用
查看>>
SVN常用命令备注
查看>>
孩子教育
查看>>
解决Cacti监控图像断断续续问题
查看>>
结构体的传参理解成员的存储方式
查看>>
python 进程与线程(理论部分)
查看>>
什么是API
查看>>
强名称程序集(strong name assembly)——为程序集赋予强名称
查看>>