Speex是一套主要针对语音的开源免费,无专利保护的音频压缩格式。
官网:https://www.speex.org/
下载最新版:https://www.speex.org/downloads/
解压后,得到 include 、 libspeex 两个文件夹,里面是h和c源码文件。
1.创建安卓工程并集成cpp
首先 将上面include 、 libspeex 两个文件夹放到cpp目录,并创建CMakeLists.txt编译入口文件和UtilSpeex.cpp工具类文件。
然后 配置gradle集成cpp。
修改 include/speex/speex_config_types.h.in 文件去除“.in”后缀并更正代码。
1 2 3 4 5 6 7 8 9 |
#ifndef __SPEEX_TYPES_H__ #define __SPEEX_TYPES_H__ typedef short spx_int16_t; typedef unsigned short spx_uint16_t; typedef int spx_int32_t; typedef unsigned int spx_uint32_t; #endif |
修改 libspeex 文件夹中的头文件,正确引用include中头文件。
2.录音机和播放器准备
伪代码。采样率要一致。
1 2 3 4 5 6 7 8 |
private int audioSource = MediaRecorder.AudioSource.MIC; // 声源 private int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 采样精度,一个采样点16比特,相当于2个字节。 private int channelConfig = AudioFormat.CHANNEL_IN_MONO; // CHANNEL_IN_STEREO双声道,CHANNEL_IN_MONO单声道 private int sampleRateInHz = 16000; // 采样率。 private int bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); private AudioRecord audioRecord; audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSize); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 采样精度,一个采样点16比特,相当于2个字节。 private int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // CHANNEL_IN_STEREO双声道,CHANNEL_IN_MONO单声道 private int sampleRateInHz = 16000; // 采样率。 private int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); private AudioTrack audioTrack; audioTrack = new AudioTrack( // AudioManager.STREAM_MUSIC, // sampleRateInHz, // channelConfig, // audioFormat, // bufferSize * 2, // AudioTrack.MODE_STREAM); |
3.Speex入口和cmake入口
CMakeLists.txt文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# 需要的一个ndk相关配置文件。这里默认和此CMakeLists.txt文件同一目录。非必须。 include(ndk-stl-config.cmake) # 执行编译的cmake的最小版本限制 cmake_minimum_required(VERSION 3.4.1) # 定义xxx目录。可以有下一级目录。CMAKE_SOURCE_DIR指的是CMakeLists.txt所在目录 set(include_DIR ${CMAKE_SOURCE_DIR}/include/speex) set(lib_DIR ${CMAKE_SOURCE_DIR}/libspeex) # 这个flag避免编译时各种找不到的错误 set(CMAKE_CXX_FLAGS -stdlib=libc++) # 我们开发的c++源代码文件。【UtilSpeex】即编译出的so库名称,文件名即libVigilesUtil.so # java中调用时即 System.loadLibrary("UtilSpeex"); # UtilSpeex.cpp文件默认和此CMakeLists.txt文件同一目录。 add_library(UtilSpeex SHARED # 以下是speex的代码 libspeex/bits.c libspeex/cb_search.c libspeex/exc_5_64_table.c libspeex/exc_5_256_table.c libspeex/exc_8_128_table.c libspeex/exc_10_16_table.c libspeex/exc_10_32_table.c libspeex/exc_20_32_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/testenc.c libspeex/testenc_uwb.c libspeex/testenc_wb.c libspeex/vbr.c libspeex/vorbis_psy.c libspeex/vq.c libspeex/window.c # 这个 UtilSpeex.cpp 即cpp的入口,我们自己对speex的封装 UtilSpeex.cpp) # 指定so库包含的头文件路径,即UtilSpeex.cpp文件引用的h文件目录 target_include_directories(UtilSpeex PRIVATE ${include_DIR}/include/speex ${lib_DIR}/libspeex) # 将so库和须要用到的资源连接。android和log直接加入即可 target_link_libraries(UtilSpeex android log) |
speex的工具类入口UtilSpeex.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
#include <cstring> #include <jni.h> #include <cinttypes> #include <android/log.h> #include <string.h> #include <unistd.h> #include "./include/speex/speex.h" #define LOG_TAG "UtilSpeex" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 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; extern "C" JNIEXPORT jint // 【!】注意函数名前缀 Java_com_cuiweiyou_testspeex_UtilSpeex_ 对应java中的类 com.cuiweiyou.testspeex.UtilSpeex JNICALL Java_com_cuiweiyou_testspeex_UtilSpeex_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); LOGE("---> 打开了"); return (jint)0; } extern "C" JNIEXPORT jint JNICALL Java_com_cuiweiyou_testspeex_UtilSpeex_encode(JNIEnv *env, jobject obj, jshortArray src, jint offset, jint size, jbyteArray dst) { 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 (jint)0; speex_bits_reset(&ebits); for ( i = 0; i<nsamples; i++) { env->GetShortArrayRegion(src, offset+i *enc_frame_size, enc_frame_size, buffer); speex_encode_int(enc_state, buffer, &ebits); } //env->GetShortArrayRegion(src, 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(dst, 0, tot_bytes, output_buffer); LOGE("---> 编码了"); return (jint)tot_bytes; } extern "C" JNIEXPORT jint JNICALL Java_com_cuiweiyou_testspeex_UtilSpeex_decode(JNIEnv * env, jobject obj, jbyteArray src, jint size, jshortArray dst) { jbyte buffer[dec_frame_size]; jshort output_buffer[dec_frame_size]; jsize encoded_length = size; if (!codec_open) return (jint)0; env->GetByteArrayRegion(src, 0, encoded_length, buffer); speex_bits_read_from(&dbits, (char *)buffer, encoded_length); speex_decode_int(dec_state, &dbits, output_buffer); env->SetShortArrayRegion(dst, 0, dec_frame_size, output_buffer); LOGE("---> 解码了"); return (jint)dec_frame_size; } extern "C" JNIEXPORT jint JNICALL Java_com_cuiweiyou_testspeex_UtilSpeex_getFrameSize(JNIEnv * env, jobject obj) { if (!codec_open) return (jint)0; LOGE("---> 音频大小"); return (jint)enc_frame_size; } extern "C" JNIEXPORT void JNICALL Java_com_cuiweiyou_testspeex_UtilSpeex_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); LOGE("---> 关闭了"); } |
4.录音压缩和解压播放
首先speex的工具类入口UtilSpeex.cpp对应的java工具类UtilSpeex.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
package com.cuiweiyou.testspeex; import android.util.Log; public class UtilSpeex { /* 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) // 压缩到最大体积 */ public static final int COMPRESSION_1 = 1; public static final int COMPRESSION_2 = 2; public static final int COMPRESSION_4 = 4; public static final int COMPRESSION_6 = 6; public static final int COMPRESSION_8 = 8; static { try { System.loadLibrary("UtilSpeex"); Log.e("ard", "加载库成功"); } catch (Throwable e) { e.printStackTrace(); } } /** * 初始化speex * * @param compression 压缩级别 * @return 0-已经初始化了 */ public native int open(int compression); public native int getFrameSize(); /** * 压缩原始音频数据 * * @param src AudioRecord录制的原始音频数据 * @param offset 原始音频开始压缩位置偏移。通常是0-开头. * @param size 原始音频结束压缩位置 * @param dest 压缩后的数据的容器 * * @return 压缩后的数据长度。受初始化时compression的影响 */ public native int encode(short[] src, int offset, int size, byte[] dest); /** * 解码压缩过的数据 * @param src speex压缩过的数据 * @param size 固定160。speex解压数据长度,须和 audioRecord.read(audioData, offsetInShorts, sizeInShorts); 第三个参数相同 * @param dest 解压后的数据的容器-可供AudioTrack拿来直接播放 * @return */ public native int decode(byte[] src, int size, short[] dest); // 关闭speex public native void close(); } |
然后初始化speex:
1 2 |
utilSpeex = new UtilSpeex(); utilSpeex.open(UtilSpeex.COMPRESSION_1); |
录音压缩:
1 2 3 4 5 6 7 8 |
int packageSize = 160; // 【"音节"固定长度160,和speex默认解码长度一致】 short[] buffer = new short[packageSize]; int srcSize = audioRecord.read(buffer, 0, packageSize); byte[] out = new byte[buffer.length]; // 压缩后的数据。保存之 int encodeSize = utilSpeex.encode(buffer, 0, srcSize, out); // 压缩 // Log.e("ard", "源数据长度:" + buffer.length + ",压缩后长度:" + encodeSize); |
解压播放:
1 2 3 4 5 6 7 |
byte[] buffer = out; // 上一步压缩后的数据 int len = 160; // 【固定160】 short[] decoded = new short[len]; int decodeSize = util.decode(buffer, len, decoded);// 非160时:java.lang.ArrayIndexOutOfBoundsException: short[] offset=0 length=160 dst.length=xx audioTrack.write(decoded, 0, decoded.length); audioTrack.play(); |
- end
声明
本文由崔维友 威格灵 cuiweiyou vigiles cuiweiyou 原创,转载请注明出处:http://www.gaohaiyan.com/4362.html
承接App定制、企业web站点、办公系统软件 设计开发,外包项目,毕设