(原创)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变量,因为我们要得到的是解码数据,跟播放无关。
修改后代码如下
- 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格式音频文件的互相转换(二)的更多相关文章
- (原创)speex与wav格式音频文件的互相转换
我们的司信项目又有了新的需求,就是要做会议室.然而需求却很纠结,要继续按照原来发语音消息那样的形式来实现这个会议的功能,还要实现语音播放的计时,暂停,语音的拼接,还要绘制频谱图等等. 如果是wav,m ...
- [原创]使用python对视频/音频文件进行详细信息采集,并进行去重操作
[原创]使用python对视频/音频文件进行详细信息采集,并进行去重操作 转载请注明出处 一.关于为什么用pymediainfo以及pymediainfo的安装 使用python对视频/音频文件进行详 ...
- c#使用SoundPlayer播放wav格式音频
1.引用System.Media名称空间下的类SoundPlayer SoundPlayer player = new SoundPlayer(); 2.方法调用Play(); public vo ...
- 调用CImg库显示WAV格式音频波形
最近在做傅里叶变换和小波变换时经常要通过显示波形来检验算法,但通过visual studio之类显示波形又显得麻烦,而且不能跨平台. CImg是一个跨平台的C++的图像处理库,提供的图像处理等功能十分 ...
- ffmpeg 合并aac格式音频文件
1:连接到一起 'ffmpeg - i "concat:D:\learn\audio\1.aac|D:\learn\audio\2.aac" - acodec copy D:\le ...
- JAVA版-微信高清语音.speex转.wav格式
功能介绍: PC端将.speex文件解码为*.wav文件 使用场景: 在MAC/Linux下Java JNI 调用C Speex,后端Java处理微信服务器下载下来的微信高清语音.speex解码为.w ...
- S3C2416裸机开发系列19_Fatfs播放录像wav音频文件
S3C2416裸机开发系列19 Fatfs播放录像wav音频文件 国际象棋男孩 1048272975 多媒体资源,一般都是以文件的形式存储在固化存储器中.Fatfs所支持的fat32为windo ...
- WAV格式文件无损合并&帧头数据体解析(python)(原创)
一,百度百科 WAV为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频 ...
- 解析WAV音频文件----》生成WAV音频文件头
前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i WAV音频文件介绍: WAV文件是在PC机平台上很常见的.最经典的多媒体音频文件,最早于1991年8月出现在Windows3.1操作系统 ...
随机推荐
- poj3280Cheapest Palindrome(记忆化)
链接 真的1A了.. 一开始想复杂了 想着补全再删 没想好 后来想到递归 大的回文串是由小的推过来的 一直递归下去 对于当前的i,j可以选择保留或者删除 选个最小的 #include <iost ...
- GML、SVG、VML的比较
转自:http://www.cnblogs.com/3echo/archive/2009/02/19/1394399.html GML.SVG和VML都是基于XML的可用来描述矢量图形的标记语言,都是 ...
- [转]ASP.NET MVC 入门9、Action Filter 与 内置的Filter实现(介绍)
有时候你想在调用action方法之前或者action方法之后处理一些逻辑,为了支持这个,ASP.NET MVC允许你创建action过滤器.Action过滤器是自定义的Attributes,用来标记添 ...
- [转]ASP.NET MVC 入门7、Hellper与数据的提交与绑定
ASP.NET MVC提供了很多Hellper的方法,Hellper就是一些生成HTML代码的方法,方便我们书写HTML代码(有一部分的朋友更喜欢直接写HTML代码).我们也可以利用.NET 3.5的 ...
- linux 已有目录挂载磁盘
1.查看当前硬盘使用状况: [root@gluster_node1 ~]# df -h Filesystem Size Used Avail Use% Mounted on / ...
- java 异常架构图
cu 红色为检查异常,就是eclipse要提示你是try catch 还是throws. 非检查异常,就是/0,nullpointexception,数据越界访问indexOfOutBounds 异常 ...
- NOIP2015 子串 (DP+优化)
子串 (substring.cpp/c/pas) [问题描述] 有两个仅包含小写英文字母的字符串 A 和 B.现在要从字符串 A 中取出 k 个 互不重 叠 的非空子串,然后把这 k 个子串按照其在字 ...
- TCA9546A
The TCA9546A is a 4-channel, bidirectional translating switch for I 2 C buses that supports Standard ...
- Storm系列(九)架构分析之Supervisor-同步Nimbus的事件线程
Supervisor由三个线程组成,一个计时器线程和两个事件线程. 计时器线程负责维持心跳已经更新Zookeeper中的状态,还负责每隔一定的时间将事件线程需要执行的事件添加到其对应的队列中. 两个事 ...
- 安卓问题http://blog.csdn.net/xb12369/article/details/50510302