您的位置:首页 > 移动开发 > Android开发

Speex语音压缩与解压在Android上的实现

2015-09-06 17:48 351 查看
由于项目需要做语音降噪处理,最近做了这方面的研究。但结果却没有达到,反而却学得了基于Speex的语音压缩和解压,也算没有白白浪费时间(300K的语音文件经过Speex压缩后文件大小变为了30K左右,对于网络传输非常好)。希望通过与大家分享和交流,解决共同遇到的难题。

关于Speex:http://www.speex.org/

Speex主要提供的技术:

1.窄带(8kHz),宽带(16kHz)和超宽带(32kHz)压缩于同一位流

2.强化立体编码
3.数据包丢失隐蔽
4.可变比特率(VBR)
5.语音捕捉(VAD)
6.非连续传输(DTX)
7.定点运算
8.感官回声消除(AEC)
9.噪音屏蔽

首先参考了一位大神的博客 speex编解码在android上实现,由于博客比较久了,现在按照上面的步骤去做,结果却出现些小小的Bug。其中主要的是在Android.mk文件中的LOCAL_SRC_FILES所对应的.c文件,最新的Speex版本,已经去掉了些libspeex文件夹下的.c文件,参照Speex中的libspeex文件进行对比,删除多余.c文件。以下为去掉之后需要的.c文件(加上自己的speex.cpp)。

LOCAL_SRC_FILES := \

libspeex/bits.c \

libspeex/cb_search.c \

libspeex/exc_10_16_table.c \

libspeex/exc_10_32_table.c \

libspeex/exc_20_32_table.c \

libspeex/exc_5_256_table.c \

libspeex/exc_5_64_table.c \

libspeex/exc_8_128_table.c \

libspeex/filters.c \

libspeex/gain_table.c \

libspeex/gain_table_lbr.c \

libspeex/hexc_10_32_table.c \

libspeex/hexc_table.c \

libspeex/high_lsp_tables.c \

libspeex/kiss_fft.c \

libspeex/kiss_fftr.c \

libspeex/lpc.c \

libspeex/lsp.c \

libspeex/lsp_tables_nb.c \

libspeex/ltp.c \

libspeex/modes.c \

libspeex/modes_wb.c \

libspeex/nb_celp.c \

libspeex/quant_lsp.c \

libspeex/sb_celp.c \

libspeex/smallft.c \

libspeex/speex.c \

libspeex/speex_callbacks.c \

libspeex/speex_header.c \

libspeex/stereo.c \

libspeex/vbr.c \

libspeex/vq.c \

libspeex/window.c

配置好Android.mk文件后,其次主要是如何调用Speex所提供的方法。对于语音的压缩和解压,Speex主要提供了open(),getFrameSize(),decode(),encode(),close()方法。其中open()方法中需要传一个压缩质量(quality是一个0~10(包含10)范围内的整数)的参数;getFrameSize()方法获得帧字节的大小;encode()方法中有short
lin[], int offset, byte encoded[], int size四个参数,其中short lin[]表示录音得到的short型数据,int offset为跳过的字节数,byte encoded[]为压缩后的byte型数据,int
size为数据的长度;decode()方法中有byte encoded[], short lin[], int size三个参数,其中byte encoded[]表示压缩后的byte型数据,
short lin[]为解压后的short型数据,int size为数据大小;close()方法退出语音压缩和解压。

Sppex.cpp文件的代码主要为:

#include <jni.h>

#include <string.h>
#include <unistd.h>

#include <include/speex/speex.h>

static int codec_open = 0;

static int dec_frame_size;
static int enc_frame_size;

static SpeexBits ebits, dbits;
void *enc_state;
void *dec_state;

static JavaVM *gJavaVM;

extern "C" JNIEXPORT jint JNICALL Java_com_example_speex_Speex_open(JNIEnv *env,
		jobject obj, jint compression) {
	int tmp;

	if (codec_open++ != 0)
		return (jint) 0;

	speex_bits_init(&ebits);
	speex_bits_init(&dbits);

	enc_state = speex_encoder_init(&speex_nb_mode);
	dec_state = speex_decoder_init(&speex_nb_mode);
	tmp = compression;
	speex_encoder_ctl(enc_state, SPEEX_SET_QUALITY, &tmp);
	speex_encoder_ctl(enc_state, SPEEX_GET_FRAME_SIZE, &enc_frame_size);
	speex_decoder_ctl(dec_state, SPEEX_GET_FRAME_SIZE, &dec_frame_size);

	return (jint) 0;
}

extern "C" JNIEXPORT jint Java_com_example_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;
}

extern "C" JNIEXPORT jint JNICALL Java_com_example_speex_Speex_decode(
		JNIEnv *env, jobject obj, jbyteArray encoded, jshortArray lin,
		jint size) {

	jbyte buffer[dec_frame_size];
	jshort output_buffer[dec_frame_size];
	jsize encoded_length = size;

	if (!codec_open)
		return 0;

	env->GetByteArrayRegion(encoded, 0, encoded_length, buffer);
	speex_bits_read_from(&dbits, (char *) buffer, encoded_length);
	speex_decode_int(dec_state, &dbits, output_buffer);
	env->SetShortArrayRegion(lin, 0, dec_frame_size, output_buffer);

	return (jint) dec_frame_size;
}

extern "C" JNIEXPORT jint JNICALL Java_com_example_speex_Speex_getFrameSize(
		JNIEnv *env, jobject obj) {

	if (!codec_open)
		return 0;
	return (jint) enc_frame_size;

}

extern "C" JNIEXPORT void JNICALL Java_com_example_speex_Speex_close(
		JNIEnv *env, jobject obj) {

	if (--codec_open != 0)
		return;

	speex_bits_destroy(&ebits);
	speex_bits_destroy(&dbits);
	speex_decoder_destroy(dec_state);
	speex_encoder_destroy(enc_state);
} 


编写好Sppex.cpp后,接下来是在Java中调用这些方法,下面为录音和播放:

1.录音

private List<Data> mDatas = new ArrayList<Data>();

private static final class Data {

private int mSize;//记录每次编码之后的大小,用做解码时设置每次读取的字节大小

private byte[] mBuffer;//记录每次编码之后的字节数组,可以用做边录边播放

}

Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);//设置线程的优先级,音频优先级最高

new Thread(new Runnable() {

public void run() {

try {

mAudioRecord.startRecording();

DataOutputStream dos = new DataOutputStream(

new BufferedOutputStream(

new FileOutputStream(mAudioFile)));

int sizeInShorts = speex.getFrameSize();

short[] audioData = new short[sizeInShorts];

int sizeInBytes = speex.getFrameSize();

//开始记录数据

while (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {

int number = mAudioRecord.read(audioData, 0,

sizeInShorts);

short[] dst = new short[sizeInBytes];

System.arraycopy(audioData, 0, dst, 0, number);

byte[] encoded = new byte[sizeInBytes];

int count = speex.encode(dst, 0, encoded,

number);

if (count > 0) {

//记录每次录音得到的数据,与播放录音的时候对应

Data data = new Data();

data.mSize = count;

data.mBuffer = encoded;

mDatas.add(data);

dos.write(encoded, 0, count);

}

}

dos.flush();

dos.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}).start();





2.播放

new Thread(new Runnable() {

public void run() {

try {

DataInputStream dis = new DataInputStream(

new BufferedInputStream(

new FileInputStream(mAudioFile)));

int len = 0;

//根据每次录音得到的数据,进行播放

for (Data data : mDatas) {

byte[] encoded = new byte[data.mSize];

len = dis.read(encoded, 0, data.mSize);

if (len != -1) {

short[] lin = new short[speex

.getFrameSize()];

int size = speex.decode(encoded, lin,

encoded.length);

if (size > 0) {

mAudioTrack.write(lin, 0, size);

mAudioTrack.play();

}

}

}

//直接使用存放录音时的数据播放,如果不存入文件,可边录边放

// short[] lin = new short[speex.getFrameSize()];

// for (Data data : mDatas) {

// int size = speex.decode(data.mBuffer, lin,

// data.mSize);

// if (size > 0) {

// mAudioTrack.write(lin, 0, size);

// mAudioTrack.play();

// }

// }

dis.close();

speex.close();

mAudioTrack.stop();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}).start();

到这儿,基于Speex的语音压缩和解压基本实现。 参考了网上很多的资料,有的实现了,但却经过了OGG封装(较复杂),有点不懂为什么需要OGG封装,下来再研究下。另外,Speex也提供了Java语言的实现,请参考JSpeex

Speex Demo下载地址:基于Speex的语音压缩和解压在Android中的实现

在后来继续研究发现,对于语音的压缩、解压、噪音抑制、回声消除,及时通信等,WebRTC技术完全能够满足,并且特别强大,该技术被Google收购了,当然Android中也提供了实现,但是是在Android4.1版本之后,而且并不是所有的Android手机都支持,所以最好还是和Speex一样用JNI调用C/C++代码。之后将写博客仔细剖解。可参考例子:Android
4.1回声消除(AcousticEchoCanceler)和噪声抑制 (NoiseSuppressor)Demo

有什么疑问或者问题,希望留言和大家一起讨论。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: