目前我正在开发一个类似对讲机的音频实时聊天应用程序。此应用程序通过Android手机和远程服务器之间的tcp套接字传输音频字节。为了节省用户的移动互联网数据成本,我必须先编码从AudioRecord读取的音频字节,然后再将其发送到服务器。因此,我还需要解码从服务器收到的内容。我选择使用的编解码器是speex。虽然speex是用c ++编写的,我几乎没用,也很难理解它的语法和规则,但我找到了一些关于在android中使用speex的例子,包括jni文件,如何通过ndk build将它编译成.so文件,如何调用它的方法通过jni。感谢这些家伙的贡献,最后我在我的应用程序中使用了speex。我将比特率从16kb / s压缩到2kb / s,这确实是一项重大改进。但是,实施并不完美。小缺陷是编码方法不编码从AudioRecord读取的完整字节数组。例如, speex.encode(readShortBuffer,0,缓冲液,readShortBuffer.length)。 readShortBuffer是一个320 * 12的短数组,缓冲区是一个字节数组,其长度为80 * 12,压缩比为8(2个字节等于short)。上面的命令应该用960(80 * 12)字节编码数据填充缓冲区。但是,我只有320字节的数据。其余的都是0.所以我必须使readShortBuffer和缓冲区更短来制作缓冲区完全填充。然后将每个小缓冲区组装成一个较长的缓冲区来表示AudioRecord读取的内容。但它效率很低。我要找的是找到一种在任意长度编码短数组的方法。以下是代码:
speex.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_eu_ratikal_helloc_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_wb_mode);
dec_state = speex_decoder_init(&speex_wb_mode);
tmp = 1;
speex_decoder_ctl(dec_state, SPEEX_SET_ENH, &tmp);
speex_encoder_ctl(enc_state, SPEEX_SET_ENH, &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_eu_ratikal_helloc_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_eu_ratikal_helloc_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_eu_ratikal_helloc_Speex_getFrameSize(
JNIEnv *env, jobject obj) {
if (!codec_open)
return 0;
return (jint) enc_frame_size;
}
Speex.java
public class Speex {
/* quality
* 1 : 4kbps (very noticeable artifacts, usually intelligible)
* 2 : 6kbps (very noticeable artifacts, good intelligibility)
* 4 : 8kbps (noticeable artifacts sometimes)
* 6 : 11kpbs (artifacts usually only noticeable with headphones)
* 8 : 15kbps (artifacts not usually noticeable)
*/
private static final int DEFAULT_COMPRESSION = 1;
public Speex() {
}
public int init() {
load();
return open(DEFAULT_COMPRESSION);
}
private void load() {
try {
System.loadLibrary("speex");
} catch (Throwable e) {
e.printStackTrace();
}
}
public native int open(int compression);
public native int getFrameSize();
public native int decode(byte encoded[], short lin[], int size);
public native int encode(short lin[], int offset, byte encoded[], int size);
public native void close();
}
MainActivity.java
recorder.read(readShortBuffer, 0, readShortBuffer.length);
int r = speex.encode(readShortBuffer,0,buffer,readShortBuffer.length);
os.write(buffer, 0, buffer.length);
我的帖子太长了,无法通读。谢谢你们耐心等待,非常感谢任何帮助!