关于SPEEX和语音的研究(转载的基础上加原创)

容寒
2023-12-01

前言

项目需要,抛弃掉原有的音频,统一使用speex(虽然这个解决方案也比较老,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 。有什么问题请邮箱联系 dongweiqmail@gmail.com 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

下边文章接着写

 类似资料: