前言

项目需要,抛弃掉原有的音频,统一使用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 decoderspeexDecoder = new Speex();speexDecoder.init();// open the input streamRandomAccessFile dis = new RandomAccessFile(srcPath, "r");FileOutputStream fos = new FileOutputStream(dstPath);int origchksum;int chksum;try {// read until we get to EOFwhile (true) {if (Thread.interrupted()) {dis.close();return;}// read the OGG headerdis.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 headerif (!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 packetpacketNo++;} 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() {@Overridepublic void onError(Exception ex) {System.out.println("转换错误");}@Overridepublic 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

下边文章接着写

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

  1. Android开源项目大合集(转载的基础上添加了项目地址)

    WeChat高仿微信 项目地址:https://github.com/motianhuo/wechat 高仿微信,实现功能有: 好友之间文字聊天,表情,视频通话,语音,语音电话,发送文件等. 知乎专栏 ...

  2. Matlab神经网络语音增强,基于BP神经网络的语音增强研究

    曰髯? 分类号: 论文编号:2丛坦丝旦生丛 密级:公开 贵州大学 2009届硕士研究生学位论文 基于即神经网络的语音增强研究 学科专业:电路与系统 研究方向:模式识别 导师:刘宇红教授 研究生:周元芬 ...

  3. DFSS项目选择方法研究(转载)

    DFSS项目选择方法研究 http://www.quality-world.cn/guanli/2396.html 摘要:六西格玛设计(DFSS)在六西格玛领域受到的关注越来越多.对于项目组合的DFS ...

  4. 【转载】史上最全:TensorFlow 好玩的技术、应用和你不知道的黑科技

    [导读]TensorFlow 在 2015 年年底一出现就受到了极大的关注,经过一年多的发展,已经成为了在机器学习.深度学习项目中最受欢迎的框架之一.自发布以来,TensorFlow 不断在完善并增加 ...

  5. 转载知乎上的一篇:“ 面向对象编程的弊端是什么?”

    2019独角兽企业重金招聘Python工程师标准>>> 弊端是,没有人还记得面向对象原本要解决的问题是什么. 1.面向对象原本要解决什么(或者说有什么优良特性) 似乎很简单,但实际又 ...

  6. 转载 从算法上解读自动驾驶是如何实现的?

    科技新闻小鹏汽车2016-03-28 10:42 [摘要]车辆路径规划问题中路网模型.路径规划算法和交通信息的智能预测为关键点. 由于驾驶员的驾驶工作繁重,同时随着汽车拥有量的增加,非职业驾驶员的数量 ...

  7. 浅谈用户研究那些事(上)定性研究

    什么是用户研究 从"用户研究"这四个字来看: 一是用户:所谓的用户,就是我们产品定位中涉及到的目标用户,是产品的使用人群.所以在用户研究时首要任务就是要明确产品的目标用户.目标用户 ...

  8. 【无标题】音频蓝牙语音芯片,WT2605C-32N实时录音上传技术方案介绍

    基于WT2605C-32N音频蓝牙语音芯片的实时录音上传技术方案介绍 现代社会,信息技术的发展尤其迅速,信息和数据的重要性和安全性日渐深刻,在某些特殊场合,因为工作上的需要,或者日常生活的需求,往往需 ...

  9. 免费报名 | 微软全双工语音对话以及在智能硬件上的应用

    微软小冰第六代发布会上正式宣布上线全新的共感模型,同时也开始公测一种融合了文本.全双工语音与实时视觉的新感官.这项新技术可以实时预测人类即将说出的内容,实时生成回应,并控制对话节奏,从而使长程语音交互 ...

最新文章

  1. java中小数的乘法_javascript的小数点乘法除法实例
  2. Websockets与Spring 4
  3. Android百度云推送接入,附完整代码
  4. VMware虚拟化云平台-最新版本vSphere 6.7
  5. 逻辑回归(Logistic Regression)学习笔记
  6. excel方格子插件_转载 | 18个Word/Excel/PPT插件整理
  7. 现代通信原理10.1:带宽无限信道下采用低通滤波器(LPF)接收时的误码性能分析
  8. TomCat7安装与配置
  9. 城市供水调度平台(Axure高保真原型)
  10. android studio 文件名颜色 灰色,绿色,红色,蓝色,白色的含义
  11. axure 折线图部件_Axure教程:折线图
  12. 删除文件夹时,报错“错误ox80070091:目录不是空的”,该如何解决?
  13. 如何设计一个C++的类?
  14. 【英语学习】【WOTD】hamartia 释义/词源/示例
  15. 2020第四届全球程序员节观后感
  16. cmd 下删除mysql 服务命令
  17. 一花一树一城,走进三维重建的绚丽世界|专访权龙
  18. 医药领域知识图谱快速及医药问答项目--项目探究
  19. oracle内存体系结构
  20. 基于富芮坤的FR801 BLE芯片开发

热门文章

  1. 设置屏幕常亮 switch
  2. Calendar类获取当前时间上一个月,下一个月,当月的最后一天等的处理方法
  3. MATLAB 基础与通信系统仿真
  4. client elapsed: 0 ms, server elapsed: 1022 ms, timeout: 1000 ms,
  5. 如何使用在线客服转接功能
  6. freemarker 解析对象的某元素_FreeMarker标签介绍及各种数据类型处理
  7. 西部数据蓝盘 绿盘和黑盘 到底有什么区别?
  8. 清华软院保研机试总结
  9. Vue2 面试题总结1(笔记自用)
  10. React二级路由的实现