Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
java wav转pcm_WAV格式转换成MP3,希望能够帮助你!!!。
之前写过了如何将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 packagecom.sixin.speex;2
3 importjava.io.File;4 importjava.io.FileOutputStream;5 importjava.io.IOException;6 importjava.io.RandomAccessFile;7 importjava.util.ArrayList;8 importjava.util.List;9
10 importandroid.media.AudioFormat;11 importandroid.media.AudioManager;12 importandroid.media.AudioTrack;13 importandroid.os.RecoverySystem.ProgressListener;14 importandroid.util.Log;15
16 /**
17 * 采用Jspeex方案,首先解包,从ogg里面接出来,然后使用speex decode将speex转为wav数据并进行播放18 *19 *@authorHonghe20 */
21 public classSpeexFileDecoder {22
23 protectedSpeex speexDecoder;24 private String errmsg = null;25 private List listenerList = new ArrayList();26 privateFile srcPath;27 privateFile dstPath;28
29 public SpeexFileDecoder(File srcPath, File dstPath) throwsException {30 this.srcPath =srcPath;31 this.dstPath =dstPath;32 }33
34 private void initializeAndroidAudio(int sampleRate) throwsException {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 voidaddOnMetadataListener(ProgressListener l) {43 listenerList.add(l);44 }45
46 publicString getErrmsg() {47 returnerrmsg;48 }49
50 public void decode() throwsException {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 = newSpeex();64 speexDecoder.init();65 //open the input stream
66 RandomAccessFile dis = new RandomAccessFile(srcPath, "r");67 FileOutputStream fos = newFileOutputStream(dstPath);68
69 intorigchksum;70 intchksum;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 *
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: * 60 - 63: vbr166 * 64 - 67: frames_per_packet167 * 68 - 71: extra_headers: 0168 * 72 - 75: reserved1169 * 76 - 79: reserved2170 *
171 *172 *@parampacket173 *@paramoffset174 *@parambytes175 *@return
176 *@throwsException177 */
178 private boolean readSpeexHeader(final byte[] packet, final int offset, final int bytes, boolean init) throwsException {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 intoffset) {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 intoffset) {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 intoffset) {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 packagecom.sixin.speex;2
3 public classShortAndByte {4 /**
5 * @功能 短整型与字节的转换6 *@param短整型7 *@return两位的字节数组8 */
9 public static byte[] shortToByte(shortnumber) {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 returnb;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 returns;31 }32
33 /**
34 * @说明 主要是为解析静态数据包,将一个字节数组转换为short数组35 *@paramb36 */
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
53 /**
54 * @说明 主要是为解析静态数据包,将一个short数组反转为字节数组55 *@paramb56 */
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 returnrebt;68 }69 }
读出来的原数据我们放入到了dstPath的文件,再来看看是怎么操作的呢?其中我还是修改了gauss的speexplayer方法。
我们复制speexPlayer方法,改名为SpeexFileDecoderHelper,按照如下方法修改
1 /**
2 *3 */
4 packagecom.sixin.speex;5
6 importjava.io.File;7
8 importandroid.os.Handler;9
10 /**
11 *@authorhonghe12 *13 */
14 public classSpeexFileDecoderHelper {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 = newHandler() {23 public voidhandleMessage(android.os.Message msg) {24 int what =msg.what;25 switch(what) {26 casespeexdecode_completion:27 if (speexListener != null) {28 speexListener.onCompletion(speexdec);29 } else{30 System.out.println("司信---------null===speexListener");31 }32 break;33 casespeexdecode_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 publicSpeexFileDecoderHelper(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 voidstartDecode() {64 RecordDecodeThread rpt = newRecordDecodeThread();65 Thread th = newThread(rpt);66 th.start();67 }68
69 public boolean isDecoding = false;70
71 class RecordDecodeThread extendsThread {72
73 public voidrun() {74 try{75 if (speexdec != null) {76 isDecoding = true;77 speexdec.decode();78 if (null !=speexdec.getErrmsg()) {79 throw newException(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 voidstopDecode() {100 isDecoding = false;101 }102
103 publicString getSpxFileName() {104 return this.srcName;105 };106 }
这个方法是开启了一个现成去解码,然后解码完成后会发送handler,调用回调方法,通知解码失败还是成功。OnSpeexFileCompletionListener这个很简单,我需要贴吗?还是贴上吧,省的被骂娘。
1 packagecom.sixin.speex;2
3 /**
4 * Speex音频解码完成监听5 *@authorhonghe6 *7 */
8 public interfaceOnSpeexFileCompletionListener {9 voidonCompletion(SpeexFileDecoder speexdecoder);10 voidonError(Exception ex);11 }
到此代码都贴出来了。什么?!还不会用?哦,我还没写怎么加wav头呢,那再写个方法吧
1 /**
2 * 语音转换3 *4 *@paramname5 *@paramsrcFileName spx文件名6 *@paramdstFileName 转换后得到文件的文件名7 */
8 public static void decodeSpx(Context context, String srcFileName, finalString 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, newOnSpeexFileCompletionListener() {18
19 @Override20 public voidonError(Exception ex) {21 System.out.println("转换错误");22 }23
24 @Override25 public voidonCompletion(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
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。