Кодирование и декодирование аудио потока с Speex в Android
В настоящее время я занимаюсь разработкой приложения для живого чата, которое похоже на рацию. Это приложение передает аудиобайты через сокеты TCP между телефонами Android и удаленным сервером. Чтобы сохранить стоимость данных мобильного интернета для моих пользователей, я должен кодировать аудиобайты, считанные из AudioRecord, прежде чем отправлять их на сервер. В результате мне также нужно декодировать то, что я получил с сервера, конечно. Кодек, который я выбрал для использования, - speex. Хотя speex написан на C++, который я почти не использую и плохо понимаю его грамматику и правила, я нашел несколько примеров использования speex в android, включая файл jni, как скомпилировать его в.so файлы с помощью ndk build, как вызвать его метод через JNI. Спасибо за вклад этих ребят, наконец-то я заставил speex работать в моем приложении. Я сжал битрейт до 2 кбит / с с 16 кбит / с, что действительно является значительным улучшением. Однако реализация не идеальна. Небольшой недостаток заключается в том, что метод кодирования не кодирует полный массив байтов, считанный из AudioRecord. Например, speex.encode(readShortBuffer,0, буфер,readShortBuffer.length). ReadShortBuffer - это короткий массив 320*12, буфер - это байтовый массив, его длина - 80*12, степень сжатия - 8(2 байта равны короткому). Предполагается, что приведенная выше команда заполняет буфер 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);
Мой пост слишком длинный, чтобы его прочитать. Спасибо за терпение, любая помощь очень ценится!