项目需要,抛弃掉原有的音频,统一使用speex(虽然这个解决方案也比较老,speex项目已经停止,但是资料最全,能快速满足项目需求的speex最优)
speex官网
自行了解speex是什么,重要概念不要混淆,speex只负责压缩和反压缩,不要涉及到音频了就把播放什么的都跟他联系。切记这点,对理解很有帮助。
下面转载:
安卓录音的时候是使用AudioRecord来进行录制的(当然mediarecord也可以,mediarecord强大一些),录制后的数据称为pcm,这就是raw(原始)数据,这些数据是没有任何文件头的,存成文件后用播放器是播放不出来的,需要加入一个44字节的头,就可以转变为wav格式,这样就可以用播放器进行播放了。
怎么加头,代码在下边:
// 这里得到可播放的音频文件
private void copyWaveFile(String inFilename, String outFilename) {
FileInputStream in = null;
FileOutputStream out = null;
long totalAudioLen = 0;
long totalDataLen = totalAudioLen + 36;
long longSampleRate = AudioFileFunc.AUDIO_SAMPLE_RATE;
int channels = 2;
long byteRate = 16 * AudioFileFunc.AUDIO_SAMPLE_RATE * channels / 8;
byte[] data = new byte[bufferSizeInBytes];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/** * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。 * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有 * 自己特有的头文件。 */
private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = 16; // bits per sample
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
得到了wav文件,那我们如何转化成speex文件呢?由于之前的项目采用的是googlecode上gauss的代码,没有经过太多改动,也没有仔细研究过。这里我先请教了公司的技术达人,他说就把wav去掉header,然后把pcm数据放入的speex的encode方法里编码就可以了,得到的数据就是speex的文件。
代码写好了,一运行就崩溃,擦,为什么呢,再运行还崩溃,错误提示是:
1 JNI WARNING: JNI function SetByteArrayRegion called with exception pending
2 in Lcom/sixin/speex/Speex;.encode:([SI[BI)I (SetByteArrayRegion)
数组越界,天啊为什么?!
于是我仔细去找了speex的源码:
extern "C"
JNIEXPORT jint JNICALL Java_com_sixin_speex_Speex_encode
(JNIEnv *env, jobject obj, jshortArray lin, jint offset, jbyteArray encoded, jint size) {
jshort buffer[enc_frame_size];
jbyte output_buffer[enc_frame_size];
int nsamples = (size-1)/enc_frame_size + 1;
int i, tot_bytes = 0;
if (!codec_open)
return 0;
speex_bits_reset(&ebits);
for (i = 0; i < nsamples; i++) {
env->GetShortArrayRegion(lin, offset + i*enc_frame_size, enc_frame_size, buffer);
speex_encode_int(enc_state, buffer, &ebits);
}
//env->GetShortArrayRegion(lin, offset, enc_frame_size, buffer);
//speex_encode_int(enc_state, buffer, &ebits);
tot_bytes = speex_bits_write(&ebits, (char *)output_buffer,
enc_frame_size);
env->SetByteArrayRegion(encoded, 0, tot_bytes,
output_buffer);
return (jint)tot_bytes;
}
发现了enc_frame_size 有一个恒定的值:160
然后仔细研究发现这个encode方法每次也就只能编码160个short类型的音频原数据,擦,大牛给我留了一个坑啊。
没事,这也好办,既然你只接受160的short,那我就一点一点的读,一点一点的编码不行么。
方法在下:
public void raw2spx(String inFileName, String outFileName) {
FileInputStream rawFileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
rawFileInputStream = new FileInputStream(inFileName);
fileOutputStream = new FileOutputStream(outFileName);
byte[] rawbyte = new byte[320];
byte[] encoded = new byte[160];
//将原数据转换成spx压缩的文件,speex只能编码160字节的数据,需要使用一个循环
int readedtotal = 0;
int size = 0;
int encodedtotal = 0;
while ((size = rawFileInputStream.read(rawbyte, 0, 320)) != -1) {
readedtotal = readedtotal + size;
short[] rawdata = byteArray2ShortArray(rawbyte);
int encodesize = speex.encode(rawdata, 0, encoded, rawdata.length);
fileOutputStream.write(encoded, 0, encodesize);
encodedtotal = encodedtotal + encodesize;
Log.e("test", "readedtotal " + readedtotal + "\n size" + size + "\n encodesize" + encodesize + "\n encodedtotal" + encodedtotal);
}
fileOutputStream.close();
rawFileInputStream.close();
} catch (Exception e) {
Log.e("test", e.toString());
}
}
注意speex.encode方法的第一个参数是short类型的,这里需要160大小的short数组,所以我们要从文件里每次读取出320个byte(一个short等于两个byte这不用再解释了吧)。转化成short数组之后在编码。
经过转化发现speex的编码能力好强大,1.30M的文件,直接编码到了80k,好腻害呦。
这样在传输的过程中可以大大的减少流量,只能说speex技术真的很牛x。听说后来又升级了opus,不知道会不会更腻害呢。
编码过程实现了,接下来就是如何解码了,后来测试又发现speex的编码也是每次只能解码出来160个short,要不怎么说坑呢。
那个方法是这样子的
1 decsize = speex.decode(inbyte, decoded, readsize);
既然每次都必须解码出160个short来,那我放进去的inbyte是多少个byte呢,你妹的也不告诉我啊???
不告诉我,我也有办法,之前不是每次编码160个short吗?看看你编完之后是多少个byte不就行了?
经过测试,得到160个short编完了是20个byte,也就是320个byte压缩成了20个byte,数据缩小到了原来的1/16啊,果然牛x。
既然知道了是20,那么每次从压缩后的speex文件里读出20个byte来解码,这样就应该可以还原数据了。
public void spx2raw(String inFileName, String outFileName) {
FileInputStream inAccessFile = null;
FileOutputStream fileOutputStream = null;
try {
inAccessFile = new FileInputStream(inFileName);
fileOutputStream = new FileOutputStream(outFileName);
byte[] inbyte = new byte[20];
short[] decoded = new short[160];
int readsize = 0;
int readedtotal = 0;
int decsize = 0;
int decodetotal = 0;
while ((readsize = inAccessFile.read(inbyte, 0, 20)) != -1) {
readedtotal = readedtotal + readsize;
decsize = speex.decode(inbyte, decoded, readsize);
fileOutputStream.write(shortArray2ByteArray(decoded), 0, decsize*2);
decodetotal = decodetotal + decsize;
Log.e("test", "readsize " + readsize + "\n readedtotal" + readedtotal + "\n decsize" + decsize + "\n decodetotal" + decodetotal);
}
fileOutputStream.close();
inAccessFile.close();
} catch (Exception e) {
Log.e("test", e.toString());
}
}
当然解码出来的文件是pcm的原数据,要想播放必须加44个字节的wav的文件头,上面已经说过了,有兴趣的可以自己试试。
ps:wav文件去头转成spx然后再转回wav播放出来的文件,虽然时长没有变,但是声音变小了,貌似还有了点点的噪音。因此我怀疑speex压缩式有损压缩,不过如果只是语音的话,还是可以听清楚的,里面的具体算法我不清楚,如果大家有时间可以自己研究研究。
昨天晚上又经过了一轮测试,发现直接压缩wav的原数据到speex这个压缩效率只是压缩为原来数据大小的1/16,而我用gauss的算法录出来的spx文件压缩效率要高很多,比如用原始音频录了7s,wav数据是1.21M,而gauss算法得到的speex文件只有8k,采用我的方法直接压缩后的speex文件为77k。而用安卓的mediarecord录音得到的amr格式的文件只有13k,如果使用我提供的方法录音那还不如使用安卓自带的api录制amr格式的音频呢,还费这么大劲搞这玩意儿干啥?大牛还是有些东西没有告诉我们,这还需要我们自己去研究。
差距为什么这么大呢?我又去看了gauss的方法,他生成speex文件的流程经过了ogg编码,过程如下:
1.首先它录音的过程与我们录音的过程都是一样的,都是先录制pcm的原数据
2.录制完成后他也是用了speex先压缩
3.speex压缩后的数据存储的时候,他封装了speexwriter的一个类,speexwriter又调用了speexwriterClient的一个类
,而在speexwriterClient里又发现了oggspeexwriter的类。也就是说,他在把speex压缩后的20个byte放入到文件的时候又进行了一次ogg编码
这样我们就找到原因了,但是对于ogg的编码我不熟悉,还有待研究。如果有啥成果了,就请期待我下一篇博客吧。
更正:之所以我录制出来的wav音频大,以及编码成的speex文件比gauss的文件大的原因不只有ogg编码的问题,还有另外一个更重要的原因:设置的采样率不同,gauss的demo里设置的采样率额为8000,而我设置的是标准的44100的采样率额,因此采集到的数据本来就大很多
然后我又将采样率改成了8000,然后7s的原始录音大小由1M多减小到200k多一点了,然后直接转成speex后为13k大小,跟amr可以说不相上下。请原谅我的错误。(T_T)
代码链接如下:
https://github.com/dongweiq/study/tree/master/Record
我的github地址:https://github.com/dongweiq/study
欢迎关注,欢迎star o(∩_∩)o 。有什么问题请邮箱联系 [email protected] qq714094450
之前写过了如何将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();
}
}
数,framesize这些,我就设置错了一次,gauss录制音频的时候使用的是单声道,我加入的wav头的channel设置成了2,结果放出来的声音老搞笑了,就跟快放一样。有兴趣你可以试试。
代码已更新
代码链接如下:
https://github.com/dongweiq/study/tree/master/Record
我的github地址:https://github.com/dongweiq/study