之前写过了如何将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格式音频文件的互相转换(二)的更多相关文章

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

    我们的司信项目又有了新的需求,就是要做会议室.然而需求却很纠结,要继续按照原来发语音消息那样的形式来实现这个会议的功能,还要实现语音播放的计时,暂停,语音的拼接,还要绘制频谱图等等. 如果是wav,m ...

  2. [原创]使用python对视频/音频文件进行详细信息采集,并进行去重操作

    [原创]使用python对视频/音频文件进行详细信息采集,并进行去重操作 转载请注明出处 一.关于为什么用pymediainfo以及pymediainfo的安装 使用python对视频/音频文件进行详 ...

  3. c#使用SoundPlayer播放wav格式音频

    1.引用System.Media名称空间下的类SoundPlayer   SoundPlayer player = new SoundPlayer(); 2.方法调用Play(); public vo ...

  4. 调用CImg库显示WAV格式音频波形

    最近在做傅里叶变换和小波变换时经常要通过显示波形来检验算法,但通过visual studio之类显示波形又显得麻烦,而且不能跨平台. CImg是一个跨平台的C++的图像处理库,提供的图像处理等功能十分 ...

  5. ffmpeg 合并aac格式音频文件

    1:连接到一起 'ffmpeg - i "concat:D:\learn\audio\1.aac|D:\learn\audio\2.aac" - acodec copy D:\le ...

  6. JAVA版-微信高清语音.speex转.wav格式

    功能介绍: PC端将.speex文件解码为*.wav文件 使用场景: 在MAC/Linux下Java JNI 调用C Speex,后端Java处理微信服务器下载下来的微信高清语音.speex解码为.w ...

  7. S3C2416裸机开发系列19_Fatfs播放录像wav音频文件

    S3C2416裸机开发系列19 Fatfs播放录像wav音频文件 国际象棋男孩    1048272975 多媒体资源,一般都是以文件的形式存储在固化存储器中.Fatfs所支持的fat32为windo ...

  8. WAV格式文件无损合并&帧头数据体解析(python)(原创)

    一,百度百科 WAV为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频 ...

  9. 解析WAV音频文件----》生成WAV音频文件头

    前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i WAV音频文件介绍: WAV文件是在PC机平台上很常见的.最经典的多媒体音频文件,最早于1991年8月出现在Windows3.1操作系统 ...

随机推荐

  1. 手势识别官方教程(2)识别常见手势用GestureDetector+手势回调接口/手势抽象类

    简介 GestureDetector识别手势. GestureDetector.OnGestureListener是识别手势后的回调接口.GestureDetector.SimpleOnGesture ...

  2. Pizza pieces

    Pizza pieces Description In her trip to Italy, Elizabeth Gilbert made it her duty to eat perfect piz ...

  3. Visual Studio中一个解决方案设置多个启动项目

    在解决方案上右键,选择属性. 这样设置之后,点击开始运行之后,会同时启动2个项目. 适合一个项目既包含客户端也包含服务端,方便调试

  4. Good vs Evil

    Good vs Evil Description Middle Earth is about to go to war. The forces of good will have many battl ...

  5. Android开发之bindService()侦听service内部状态

    在Android开发之bindService()通信的基础上,实现bindService()方法侦听service内部状态. 实现侦听service内部状态,使用的是回调机制 1.首先实现一个接口 p ...

  6. 为什么你的 phpinfo() 无法显示

    一.问题描述 编写了一个php文件test.php,代码如下: <?php echo phpinfo(); ?> 浏览器访问了一下,却返回了 NULL. 二.问题定位及解决 网上查了下,大 ...

  7. POJ 3083 Children of the Candy Corn 解题报告

    最短用BFS即可.关于左手走和右手走也很容易理解,走的顺序是左上右下. 值得注意的是,从起点到终点的右手走法和从终点到起点的左手走法步数是一样. 所以写一个左手走法就好了.贴代码,0MS #inclu ...

  8. bootm命令中地址参数,内核加载地址以及内核入口地址

    bootm命令只能用来引导经过mkimage构建了镜像头的内核镜像文件以及根文件镜像,对于没有用mkimage对内核进行处理的话,那直接把内核下载到连接脚本中指定的加载地址0x30008000再运行就 ...

  9. 录制屏幕(gif)便于在博客中嵌入软件licecap

  10. 使用Visual Studio 2013编写可维护的本地可视化(natvis)

    在Visual Studio 2012中,我们介绍了创建可视化使用原生类型的能力natvis文件. Visual Studio 2013中包含了一些改进,使其更容易编写可视化的类,在内部利用收集来存储 ...