因爲官方的網站已經沒法訪問,能夠到這裏下載github.com/zly394/Silk…java
下載後解壓,目錄結構以下:linux
根據不一樣的 CPU 分了不一樣文件夾,我這裏使用的是 SILK_SDK_SRC_ARM_v1.0.9。android
省略 ndk 環境配置過程git
進入 SILK_SDK_SRC_ARM_v1.0.9 目錄github
在該目錄下建立配置腳本:bash
build.sh微信
# ndk 目錄根據你的安裝目錄
ANDROID_NDK=/Users/zhuleiyue/Library/Android/sdk/ndk-bundle
# 指定 CPU 架構
CPU=armeabi-v7a
# 最低支持的 Android 版本
ANDROID_API=android-18
# CPU 架構
ARCH=arch-arm
# 工具鏈版本
TOOLCHAIN_VERSION=4.9
# 指定工具鏈 CPU 架構
TOOLCHAIN_CPU=arm-linux-androideabi
# 指定編譯工具 CPU 架構
CROSS_CPU=arm-linux-androideabi
# 優化參數
ADDED_CFLAGS="-fpic -pipe "
case $CPU in
armeabi-v7a)
ARCH=arch-arm
TOOLCHAIN_CPU=arm-linux-androideabi
CROSS_CPU=arm-linux-androideabi
TARGET_ARCH=armv7-a
ADDED_CFLAGS+="-DNO_ASM"
;;
arm64-v8a)
ARCH=arch-arm64
ANDROID_API=android-21
TOOLCHAIN_CPU=aarch64-linux-android
CROSS_CPU=aarch64-linux-android
TARGET_ARCH=armv8-a
ADDED_CFLAGS+="-D__ARMEL__"
;;
*)
echo "不支持的架構 $CPU";
exit 1
;;
esac
# 設置編譯針對的平臺
# 最低支持的 android 版本,CPU 架構
SYSROOT=$ANDROID_NDK/platforms/$ANDROID_API/$ARCH
# 設置編譯工具前綴
export TOOLCHAIN_PREFIX=$ANDROID_NDK/toolchains/$TOOLCHAIN_CPU-$TOOLCHAIN_VERSION/prebuilt/darwin-x86_64/bin/$CROSS_CPU-
# 設置編譯工具後綴
export TOOLCHAIN_SUFFIX=" --sysroot=$SYSROOT"
# 設置 CPU 架構
export TARGET_ARCH
# 設置優化參數
export ADDED_CFLAGS
make clean all複製代碼
對於 armeabi-v7a 的 CPU 架構須要設置 NO_ASM 來禁用 asm,對於 arm64-v8a 架構,須要設置 ARMEL 支持 big endian。架構
給 build.sh 賦予可執行權限:app
chmod +x build.sh複製代碼
而後運行編譯腳本進行編譯:ide
./build.sh複製代碼
編譯完成後會在當前目錄生成靜態庫 libSKP_SILK_SDK.a。
建立支持 C/C++ 的項目
在 app 的 build.gradle 文件中 defaultConfig 標籤下添加以下配置:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags ""
}
}
// 指定 ABI
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
...
}複製代碼
在 app/src/main 目錄下新建 jniLibs 文件夾,在 jniLibs 根據支持的 CPU 架構新建 armeabi-v7a 和 arm64-v8a 文件夾。將編譯好的不一樣 CPU 架構的 libSKP_SILK_SDK.a 靜態庫文件分別添加進去。以下所示:
將 SILK_SDK_SRC_ARM_v1.0.9 目錄下的 interface 文件夾添加到 app/src/cpp 目錄下:
在 CMakelist.txt 文件中添加以下配置:
...
# 添加庫到項目中
# STATIC 表示爲靜態庫文件
# 由於庫已經預先構建,您須要使用 IMPORTED 標誌告知 CMake 只但願將庫導入到項目中
add_library( silk
STATIC
IMPORTED )
# 使用 set_target_properties() 命令指定庫的路徑
# 要向 CMake 構建腳本中添加庫的多個 ABI 版本,而沒必要爲庫的每一個版本編寫多個命令,能夠使用 ANDROID_ABI 路徑變量。
set_target_properties( silk
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libSKP_SILK_SDK.a )
# 指定頭文件路徑
include_directories( src/main/cpp/interface )
...
# 將預構建庫關聯到本身的原生庫
target_link_libraries( # Specifies the target library.
native-lib
silk
# Links the target library to the log library
# included in the NDK.
${log-lib} )複製代碼
在項目中添加預構建庫須要如下 4 步:
使用 add_library( name SHARED IMPORTED ) 命令將庫添加進來。第一個參數爲添加進來的庫指定名稱;SHARED 表示添加的是動態庫,若是是靜態庫則是 STATIC ;由於是預先構建的庫,使用 IMPORTED 標誌表示只將庫導入到項目中。
使用 set_target_properties() 命令指定庫的路徑。庫的名稱,要和 add_library 中的一致;使用 ANDROID_ABI 路徑變量添加庫的多個 ABI 版本。
使用 include_directories() 命令指定頭文件的路徑。
使用target_link_libraries() 將預構建庫關聯到本身的原生庫
配置好 CMakeLists.txt 後同步代碼。
這樣就把 libSKP_SILK_SDK.a 引入到項目中了。
在 Activity 中添加測試代碼,以下所示:
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(getSilkVersion());
}
/** * 獲取 Silk_SDK 的版本號 */
public native String getSilkVersion();
}複製代碼
在 native-lib.cpp 中實現 native 方法:
#include <jni.h>
#include <string>
extern "C" {
#include <SKP_Silk_SDK_API.h>
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_zly_silkdecoder_MainActivity_getSilkVersion(JNIEnv *env, jobject instance) {
const char *version = SKP_Silk_SDK_get_version();
return env->NewStringUTF(version);
}複製代碼
查看運行結果
解碼 silk 格式的音頻的步驟以下:
打開輸入文件
驗證文件 header
讀取有效數據大小
讀取有效數據,調用 SKP_Silk_SDK_Decode() 方法解碼
處理解碼出來的 PCM 數據,保存爲 PCM 文件
#define LOG_I(TAG, ...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOG_E(TAG, ...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define TAG "SILK"
#define ERROR_BAD_VALUE -2
#define MAX_BYTES_PER_FRAME 1024
#define MAX_INPUT_FRAMES 5
#define FRAME_LENGTH_MS 20
#define MAX_API_FS_KHZ 48
unsigned long GetHighResolutionTime() /* O: time in usec*/ {
struct timeval tv;
gettimeofday(&tv, 0);
return (unsigned long) ((tv.tv_sec * 1000000) + (tv.tv_usec));
}
JNIEXPORT jstring JNICALL Java_com_zly_silkdecoder_SilkDecoder_nativeTranscode2PCM(JNIEnv *env, jclass type, jstring inputPath_, jint sampleRate, jstring outputPath_) {
const char *inputPath = (*env)->GetStringUTFChars(env, inputPath_, 0);
const char *outputPath = (*env)->GetStringUTFChars(env, outputPath_, 0);
unsigned long totTime, startTime;
double fileLength;
size_t counter;
SKP_int32 ret, tot_len, totPackets;
SKP_int32 decSizeBytes, frames, packetSize_ms = 0;
SKP_int16 nBytes, len;
SKP_uint8 payload[MAX_BYTES_PER_FRAME * MAX_INPUT_FRAMES], *payloadToDec = NULL;
SKP_int16 out[((FRAME_LENGTH_MS * MAX_API_FS_KHZ) << 1) * MAX_INPUT_FRAMES], *outPtr;
void *psDec;
FILE *inFile, *outFile;
SKP_SILK_SDK_DecControlStruct DecControl;
LOG_I(TAG, "********** Silk Decoder (Fixed Point) v %s ********************",
SKP_Silk_SDK_get_version());
LOG_I(TAG, "********** Compiled for %d bit cpu *******************************",
(int) sizeof(void *) * 8);
LOG_I(TAG, "Input: %s", inputPath);
LOG_I(TAG, "Output: %s", outputPath);
// 打開輸入文件
inFile = fopen(inputPath, "rb");
if (inFile == NULL) {
LOG_E(TAG, "Error: could not open input file %s", inputPath);
return NULL;
}
// 驗證文件頭
{
char header_buf[50];
fread(header_buf, sizeof(char), strlen("#!SILK_V3"), inFile);
header_buf[strlen("#!SILK_V3")] = '\0';
if (strcmp(header_buf, "#!SILK_V3") != 0) {
LOG_E(TAG, "Error: Wrong Header %s", header_buf);
return NULL;
}
LOG_I(TAG, "Header is \"%s\"", header_buf);
}
// 打開輸出文件
outFile = fopen(outputPath, "wb");
if (outFile == NULL) {
LOG_E(TAG, "Error: could not open output file %s", outputPath);
return NULL;
}
// 設置採樣率
if (sampleRate == 0) {
DecControl.API_sampleRate = 24000;
} else {
DecControl.API_sampleRate = sampleRate;
}
// 獲取 Silk 解碼器狀態的字節大小
ret = SKP_Silk_SDK_Get_Decoder_Size(&decSizeBytes);
if (ret) {
LOG_E(TAG, "SKP_Silk_SDK_Get_Decoder_Size returned %d", ret);
}
psDec = malloc((size_t) decSizeBytes);
// 初始化或充值解碼器
ret = SKP_Silk_SDK_InitDecoder(psDec);
if (ret) {
LOG_E(TAG, "SKP_Silk_SDK_InitDecoder returned %d", ret);
}
totPackets = 0;
totTime = 0;
while (1) {
// 讀取有效數據大小
counter = fread(&nBytes, sizeof(SKP_int16), 1, inFile);
if (nBytes < 0 || counter < 1) {
break;
}
// 讀取有效數據
counter = fread(payload, sizeof(SKP_uint8), (size_t) nBytes, inFile);
if ((SKP_int16) counter < nBytes) {
break;
}
payloadToDec = payload;
outPtr = out;
tot_len = 0;
startTime = GetHighResolutionTime();
frames = 0;
do {
// 解碼
ret = SKP_Silk_SDK_Decode(psDec, &DecControl, 0, payloadToDec, nBytes, outPtr, &len);
if (ret) {
LOG_E(TAG, "SKP_Silk_SDK_Decode returned %d", ret);
}
frames++;
outPtr += len;
tot_len += len;
if (frames > MAX_INPUT_FRAMES) {
outPtr = out;
tot_len = 0;
frames = 0;
}
} while (DecControl.moreInternalDecoderFrames);
packetSize_ms = tot_len / (DecControl.API_sampleRate / 1000);
totTime += GetHighResolutionTime() - startTime;
totPackets++;
// 將解碼後的數據保存到文件
fwrite(out, sizeof(SKP_int16), (size_t) tot_len, outFile);
}
LOG_I(TAG, "Packets decoded: %d", totPackets);
LOG_I(TAG, "Decoding Finished");
free(psDec);
fclose(outFile);
fclose(inFile);
fileLength = totPackets * 1e-3 * packetSize_ms;
LOG_I(TAG, "File length: %.3f s", fileLength);
LOG_I(TAG, "Time for decoding: %.3f s (%.3f%% of realTime)", 1e-6 * totTime,
1e-4 * totTime / fileLength);
(*env)->ReleaseStringUTFChars(env, inputPath_, inputPath);
(*env)->ReleaseStringUTFChars(env, outputPath_, outputPath);
return (*env)->NewStringUTF(env, outputPath);
}複製代碼
在解碼前須要驗證文件頭是否爲 "#!SILK_V3",可是若是是微信裏的語音的話,須要把文件的第一個字節去掉,而後纔是 "#!SILK_V3" 的文件頭。