之前写过了如何将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. Ember.js demo8

    <!DOCTYPE html> <html> <head> <meta name="description" content=" ...

  2. libvlc 双击,鼠标事件消息响应

    基于vlc 2.1 动态库实现接收双击消息的接收,使双击vlc播放画面可以全屏显示. 需要其他版本的vlc可以与我联系(有偿进行修改) 下载地址:http://download.csdn.net/de ...

  3. Android开发UI之Navigation Drawer

    http://blog.csdn.net/xyz_lmn/article/details/12523895

  4. Memcached‘process_bin_delete’函数安全漏洞

    漏洞名称: Memcached‘process_bin_delete’函数安全漏洞 CNNVD编号: CNNVD-201401-174 发布时间: 2014-01-15 更新时间: 2014-01-1 ...

  5. 基于DDD的现代ASP.NET开发框架--ABP系列之3、ABP分层架构

    基于DDD的现代ASP.NET开发框架--ABP系列之3.ABP分层架构 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:ht ...

  6. Linux中ifreq 结构体分析和使用

    结构原型: struct ifreq{#define IFHWADDRLEN 6 union {  char ifrn_name[IFNAMSIZ];   } ifr_ifrn;  union {   ...

  7. Zookeeper的一致性协议:Zab(转)

    Zookeeper使用了一种称为Zab(Zookeeper Atomic Broadcast)的协议作为其一致性复制的核心,据其作者说这是一种新发算法,其特点是充分考虑了Yahoo的具体情况:高吞吐量 ...

  8. [转]ASP.NET MVC 入门6、TempData

    ASP.NET MVC的TempData用于传输一些临时的数据,例如在各个控制器Action间传递临时的数据或者给View传递一些临时的数据,相信大家都看过“在ASP.NET页面间传值的方法有哪几种” ...

  9. 【原】泛型-Java

    泛型是Java SE5.0中新增的新特性,通过泛型使得在代码编译的时候就能检查类型安全,并且所有的强制类型转换都是自动和隐式的,从而提高代码的重用率. Note:在JavaSE7+以后的版本中,构造函 ...

  10. HDU 1907

    博弈入门题吧. 把尼姆博弈推广到n堆,都是用异或运算.还有个总结的地方是,只要先手面对的是奇异局势,则胜负都掌握在后手.本题,题目要求是最后拿完的输,尼姆博弈是最后拿完的赢.但实际上优先权都掌握在后手 ...