(原创)speex与wav格式音频文件的互相转换(二)
2015-05-22 10:26
218 查看
之前写过了如何将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变量,因为我们要得到的是解码数据,跟播放无关。
修改后代码如下
注意上面因为speex解码出来的是160个short类型的数组,而java写文件要求写入的是byte数组,所以我们还是用到了short转byte数组的方法shortArray2ByteArray,我封装了一个类。也贴在下边
读出来的原数据我们放入到了dstPath的文件,再来看看是怎么操作的呢?其中我还是修改了gauss的speexplayer方法。
我们复制speexPlayer方法,改名为SpeexFileDecoderHelper,按照如下方法修改
这个方法是开启了一个现成去解码,然后解码完成后会发送handler,调用回调方法,通知解码失败还是成功。OnSpeexFileCompletionListener这个很简单,我需要贴吗?还是贴上吧,省的被骂娘。
到此代码都贴出来了。什么?!还不会用?哦,我还没写怎么加wav头呢,那再写个方法吧
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
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变量,因为我们要得到的是解码数据,跟播放无关。
修改后代码如下
package com.sixin.speex; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.List; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.os.RecoverySystem.ProgressListener; import android.util.Log; /** * 采用Jspeex方案,首先解包,从ogg里面接出来,然后使用speex decode将speex转为wav数据并进行播放 * * @author Honghe */ public class SpeexFileDecoder { protected Speex speexDecoder; private String errmsg = null; private List<ProgressListener> listenerList = new ArrayList<ProgressListener>(); private File srcPath; private File dstPath; public SpeexFileDecoder(File srcPath, File dstPath) throws Exception { this.srcPath = srcPath; this.dstPath = dstPath; } private void initializeAndroidAudio(int sampleRate) throws Exception { int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); if (minBufferSize < 0) { throw new Exception("Failed to get minimum buffer size: " + Integer.toString(minBufferSize)); } } public void addOnMetadataListener(ProgressListener l) { listenerList.add(l); } public String getErrmsg() { return errmsg; } public void decode() throws Exception { errmsg = null; byte[] header = new byte[2048]; byte[] payload = new byte[65536]; final int OGG_HEADERSIZE = 27; final int OGG_SEGOFFSET = 26; final String OGGID = "OggS"; int segments = 0; int curseg = 0; int bodybytes = 0; int decsize = 0; int packetNo = 0; // construct a new decoder speexDecoder = new Speex(); speexDecoder.init(); // open the input stream RandomAccessFile dis = new RandomAccessFile(srcPath, "r"); FileOutputStream fos = new FileOutputStream(dstPath); int origchksum; int chksum; try { // read until we get to EOF while (true) { if (Thread.interrupted()) { dis.close(); return; } // read the OGG header dis.readFully(header, 0, OGG_HEADERSIZE); origchksum = readInt(header, 22); readLong(header, 6); header[22] = 0; header[23] = 0; header[24] = 0; header[25] = 0; chksum = OggCrc.checksum(0, header, 0, OGG_HEADERSIZE); // make sure its a OGG header if (!OGGID.equals(new String(header, 0, 4))) { System.err.println("missing ogg id!"); errmsg = "missing ogg id!"; return; } /* how many segments are there? */ segments = header[OGG_SEGOFFSET] & 0xFF; dis.readFully(header, OGG_HEADERSIZE, segments); chksum = OggCrc.checksum(chksum, header, OGG_HEADERSIZE, segments); /* decode each segment, writing output to wav */ for (curseg = 0; curseg < segments; curseg++) { if (Thread.interrupted()) { dis.close(); return; } /* get the number of bytes in the segment */ bodybytes = header[OGG_HEADERSIZE + curseg] & 0xFF; if (bodybytes == 255) { System.err.println("sorry, don't handle 255 sizes!"); return; } dis.readFully(payload, 0, bodybytes); chksum = OggCrc.checksum(chksum, payload, 0, bodybytes); /* decode the segment */ /* if first packet, read the Speex header */ if (packetNo == 0) { if (readSpeexHeader(payload, 0, bodybytes, true)) { packetNo++; } else { packetNo = 0; } } else if (packetNo == 1) { // Ogg Comment packet packetNo++; } else { /* get the amount of decoded data */ short[] decoded = new short[160]; if ((decsize = speexDecoder.decode(payload, decoded, 160)) > 0) { //把边解边播改为写文件 fos.write(ShortAndByte.shortArray2ByteArray(decoded), 0, decsize * 2); } packetNo++; } } if (chksum != origchksum) throw new IOException("Ogg CheckSums do not match"); } } catch (Exception e) { e.printStackTrace(); } fos.close(); dis.close(); } /** * Reads the header packet. * * <pre> * 0 - 7: speex_string: "Speex " * 8 - 27: speex_version: "speex-1.0" * 28 - 31: speex_version_id: 1 * 32 - 35: header_size: 80 * 36 - 39: rate * 40 - 43: mode: 0=narrowband, 1=wb, 2=uwb * 44 - 47: mode_bitstream_version: 4 * 48 - 51: nb_channels * 52 - 55: bitrate: -1 * 56 - 59: frame_size: 160 * 60 - 63: vbr * 64 - 67: frames_per_packet * 68 - 71: extra_headers: 0 * 72 - 75: reserved1 * 76 - 79: reserved2 * </pre> * * @param packet * @param offset * @param bytes * @return * @throws Exception */ private boolean readSpeexHeader(final byte[] packet, final int offset, final int bytes, boolean init) throws Exception { if (bytes != 80) { return false; } if (!"Speex ".equals(new String(packet, offset, 8))) { return false; } // int mode = packet[40 + offset] & 0xFF; int sampleRate = readInt(packet, offset + 36); // int channels = readInt(packet, offset + 48); // int nframes = readInt(packet, offset + 64); // int frameSize = readInt(packet, offset + 56); // RongXinLog.SystemOut("mode=" + mode + " sampleRate==" + sampleRate + " channels=" + channels // + "nframes=" + nframes + "framesize=" + frameSize); initializeAndroidAudio(sampleRate); if (init) { // return speexDecoder.init(mode, sampleRate, channels, enhanced); return true; } else { return true; } } protected static int readInt(final byte[] data, final int offset) { /* * no 0xff on the last one to keep the sign */ return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | ((data[offset + 2] & 0xff) << 16) | (data[offset + 3] << 24); } protected static long readLong(final byte[] data, final int offset) { /* * no 0xff on the last one to keep the sign */ return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | ((data[offset + 2] & 0xff) << 16) | ((data[offset + 3] & 0xff) << 24) | ((data[offset + 4] & 0xff) << 32) | ((data[offset + 5] & 0xff) << 40) | ((data[offset + 6] & 0xff) << 48) | (data[offset + 7] << 56); } protected static int readShort(final byte[] data, final int offset) { /* * no 0xff on the last one to keep the sign */ return (data[offset] & 0xff) | (data[offset + 1] << 8); } }
注意上面因为speex解码出来的是160个short类型的数组,而java写文件要求写入的是byte数组,所以我们还是用到了short转byte数组的方法shortArray2ByteArray,我封装了一个类。也贴在下边
package com.sixin.speex; public class ShortAndByte { /** * @功能 短整型与字节的转换 * @param 短整型 * @return 两位的字节数组 */ public static byte[] shortToByte(short number) { int temp = number; byte[] b = new byte[2]; for (int i = 0; i < b.length; i++) { b[i] = new Integer(temp & 0xff).byteValue();// 将最低位保存在最低位 temp = temp >> 8; // 向右移8位 } return b; } /** * @功能 字节的转换与短整型 * @param 两位的字节数组 * @return 短整型 */ public static short byteToShort(byte[] b) { short s = 0; short s0 = (short) (b[0] & 0xff);// 最低位 short s1 = (short) (b[1] & 0xff); s1 <<= 8; s = (short) (s0 | s1); return s; } /** * @说明 主要是为解析静态数据包,将一个字节数组转换为short数组 * @param b */ public static short[] byteArray2ShortArray(byte[] b) { int len = b.length / 2; int index = 0; short[] re = new short[len]; byte[] buf = new byte[2]; for (int i = 0; i < b.length;) { buf[0] = b[i]; buf[1] = b[i + 1]; short st = byteToShort(buf); re[index] = st; index++; i += 2; } return re; } /** * @说明 主要是为解析静态数据包,将一个short数组反转为字节数组 * @param b */ public static byte[] shortArray2ByteArray(short[] b) { byte[] rebt = new byte[b.length * 2]; int index = 0; for (int i = 0; i < b.length; i++) { short st = b[i]; byte[] bt = shortToByte(st); rebt[index] = bt[0]; rebt[index + 1] = bt[1]; index += 2; } return rebt; } }
读出来的原数据我们放入到了dstPath的文件,再来看看是怎么操作的呢?其中我还是修改了gauss的speexplayer方法。
我们复制speexPlayer方法,改名为SpeexFileDecoderHelper,按照如下方法修改
/** * */ package com.sixin.speex; import java.io.File; import android.os.Handler; /** * @author honghe * */ public class SpeexFileDecoderHelper { private String srcName = null; private String dstName = null; private SpeexFileDecoder speexdec = null; private OnSpeexFileCompletionListener speexListener = null; private static final int speexdecode_completion = 1001; private static final int speexdecode_error = 1002; public Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { int what = msg.what; switch (what) { case speexdecode_completion: if (speexListener != null) { speexListener.onCompletion(speexdec); } else { System.out.println("司信---------null===speexListener"); } break; case speexdecode_error: if (speexListener != null) { File file = new File(SpeexFileDecoderHelper.this.srcName); if (null != file && file.exists()) { file.delete(); } speexListener.onError(null); } break; default: break; } }; }; public SpeexFileDecoderHelper(String fileName,String dstName, OnSpeexFileCompletionListener splistener) { this.speexListener = splistener; this.srcName = fileName; this.dstName = dstName; try { speexdec = new SpeexFileDecoder(new File(this.srcName),new File(this.dstName)); } catch (Exception e) { e.printStackTrace(); File file = new File(SpeexFileDecoderHelper.this.srcName); if (null != file && file.exists()) { file.delete(); } } } public void startDecode() { RecordDecodeThread rpt = new RecordDecodeThread(); Thread th = new Thread(rpt); th.start(); } public boolean isDecoding = false; class RecordDecodeThread extends Thread { public void run() { try { if (speexdec != null) { isDecoding = true; speexdec.decode(); if (null != speexdec.getErrmsg()) { throw new Exception(speexdec.getErrmsg()); } } System.out.println("RecordPlayThread 文件转换完成"); if (isDecoding) { handler.sendEmptyMessage(speexdecode_completion); } isDecoding = false; } catch (Exception t) { t.printStackTrace(); System.out.println("RecordPlayThread 文件转换出错"); handler.sendEmptyMessage(speexdecode_error); isDecoding = false; } } } /** * 结束播放 */ public void stopDecode() { isDecoding = false; } public String getSpxFileName() { return this.srcName; }; }
这个方法是开启了一个现成去解码,然后解码完成后会发送handler,调用回调方法,通知解码失败还是成功。OnSpeexFileCompletionListener这个很简单,我需要贴吗?还是贴上吧,省的被骂娘。
package com.sixin.speex; /** * Speex音频解码完成监听 * @author honghe * */ public interface OnSpeexFileCompletionListener { void onCompletion(SpeexFileDecoder speexdecoder); void onError(Exception ex); }
到此代码都贴出来了。什么?!还不会用?哦,我还没写怎么加wav头呢,那再写个方法吧
/** * 语音转换 * * @param name * @param srcFileName spx文件名 * @param dstFileName 转换后得到文件的文件名 */ public static void decodeSpx(Context context, String srcFileName, final String dstFileName) { final String temppath = AudioFileFunc.getFilePathByName("temp.raw"); try { // 如果是speex录音 if (srcFileName != null && srcFileName.endsWith(".spx")) { if (mSpeexFileDecoderHelper != null && mSpeexFileDecoderHelper.isDecoding) { stopMusic(context); } else { muteAudioFocus(context, true); mSpeexFileDecoderHelper = new SpeexFileDecoderHelper(srcFileName, temppath, new OnSpeexFileCompletionListener() { @Override public void onError(Exception ex) { System.out.println("转换错误"); } @Override public void onCompletion(SpeexFileDecoder speexdecoder) { System.out.println("转换完成"); WaveJoin.copyWaveFile(temppath, dstFileName); } }); mSpeexFileDecoderHelper.startDecode(); } } else { System.out.println("音频文件格式不正确"); } } catch (Exception e) { e.printStackTrace(); } }
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
相关文章推荐
- (原创)speex与wav格式音频文件的互相转换
- vs2010音频文件压缩 调用lame_enc.dll将WAV格式转换成MP3
- XML文件与实体类之间的互相转换
- 语言文件 po 和 mo 的互相转换
- [转]关于ffmpeg 的总结(一个linux 下 集 屏幕录像录音,音频视频转换,合并音频视频文件,格式转换于一身的命令)
- 关于ffmpeg 的总结(一个linux下集屏幕录像录音,音频视频转换,合并音频视频文件,格式转换于一身的命令)
- 关于ffmpeg 的总结(一个linux 下 集 屏幕录像录音,音频视频转换,合并音频视频文件,格式转换于一身的命令)
- XML文件与实体类的互相转换
- java 中文和unicode字符串互相转换(文件需要保存为gbk2312或者asci格式)
- Fmpeg 对各音频文件之前的转换
- C#制作文本转换为声音的demo,保存音频文件到本地
- 在Oracle中如何将varchar2与nvarchar2进行互相转换?[原创]
- java实现silk音频文件转换成mp3
- Android 手机录制wav格式音频文件实现
- 图片文件与字符串互相转换
- 一个在线互相转换文件的网站
- WAV格式音频文件头文件格式以及C++读取
- Android音频: 怎样使用AudioTrack播放一个WAV格式文件?
- 文件内容在Js(Jquery)中,字符串与JSON格式互相转换的示例(直接运行例子)
- wav格式音频转换成flv格式音频(一)