前言
微信公眾號(hào)開發(fā),因?yàn)樾枰陧?yè)面發(fā)送語(yǔ)音和播放,由于公眾號(hào)頁(yè)面中錄音必須要調(diào)用微信js錄音,錄音完成由前端上傳到微信臨時(shí)素材,再由后端下載到服務(wù)器,然后給前端播放,但是因?yàn)閺奈⑿畔螺d下來的語(yǔ)音智能是speex格式(高清語(yǔ)音)和amr格式,然而這2種格式都是無法直接在HTML中播放的,所以需要對(duì)語(yǔ)音進(jìn)行轉(zhuǎn)碼,由于speex格式清晰度較高,所以我選擇了下載speex格式的語(yǔ)音進(jìn)行轉(zhuǎn)碼,本文就是記錄如果一步一步調(diào)用speex官方源碼和微信提供部分C代碼進(jìn)行轉(zhuǎn)碼,注:本文所有環(huán)境和命令是基于Linux的
下載并安裝speex
環(huán)境:
Linux Centos
Gcc
JDK 1.8
speex 1.2.0
步驟:
首先下載speex最新的源碼,下載地址,解壓然后進(jìn)入源碼目錄,執(zhí)行命令
sudo ./configure
驗(yàn)證環(huán)境是否有誤,如果有問題,則根據(jù)具體提示自行安裝和配置,如果沒有異常,則可以執(zhí)行命令進(jìn)行編譯安裝了
sudo make;sudo make install
如果沒有出問題,則會(huì)在/usr/local/lib文件夾下面產(chǎn)生libspeex.so等文件,如果有問題,則根據(jù)具體提示解決,因?yàn)槲疫@里沒有遇到任何問題,所以也無法提供常見的問題了
編寫Java調(diào)用C語(yǔ)言代碼
在Java中調(diào)用C或者C++的代碼技術(shù)叫做JNI,是Java原生支持的,首先我們要定義好原生方法的定義,代碼如下
package wang.raye.speex;
import java.lang.reflect.Field;
/**
* Speex 轉(zhuǎn)碼工具類
* @author Raye
* @since 2017年10月19日17:04:47
*/
public class SpeexUtil {
/**
* .speex to .wav
* @param in .speex文件路徑
* @param out .wav文件路徑
* @return
*/
public static native boolean decode(String in, String out);
static {
try{
System.load(System.getProperty("user.dir")+java.io.File.separator+"libjspeex.so");
} catch (Exception e) {
e.printStackTrace();
}
}
}
其中
public static native boolean decode(String in, String out);
是定義的原生方法,也就是C語(yǔ)言的方法,而static部分是加載C語(yǔ)言代碼的動(dòng)態(tài)庫(kù),有2種方法可以加載動(dòng)態(tài)鏈接庫(kù)
System.load
和
System.loadLibrary
其中System.load 參數(shù)必須為庫(kù)文件的絕對(duì)路徑,可以是任意路徑,System.loadLibrary 參數(shù)為庫(kù)文件名,不包含庫(kù)文件的擴(kuò)展名,但是庫(kù)路徑必須是在JVM屬性java.library.path所指向的路徑中,這里我是獲取的絕對(duì)路徑,就是項(xiàng)目目錄,因?yàn)橛玫膕pring boot直接打包的jar運(yùn)行的,類寫好之后生成class文件,然后用Javah命令生成C語(yǔ)言的.h文件,在class文件執(zhí)行命令
javah -classpath . wang.raye.speex.SpeexUtil
會(huì)生成
wang_raye_speex_SpeexUtil.h文件,這就是C語(yǔ)言的頭文件,里面定義了我們SpeexUtil定義的decode,當(dāng)然是沒有實(shí)現(xiàn)的,具體實(shí)現(xiàn)代碼需要我們自己實(shí)現(xiàn)
wang_raye_speex_SpeexUtil.h 內(nèi)容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class wang_raye_speex_SpeexUtil */
#ifndef _Included_wang_raye_speex_SpeexUtil
#define _Included_wang_raye_speex_SpeexUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: wang_raye_speex_SpeexUtil
* Method: decode
* Signature: (Ljava/lang/String;Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_wang_raye_speex_SpeexUtil_decode
(JNIEnv *, jclass, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
修改微信demo
具體方法實(shí)現(xiàn)可以調(diào)用微信的demo,首先下載微信的demo,下載地址
由于微信demo里面是main方法的,所以需要進(jìn)行修改,原本的SpeexDecode.c代碼
#include <memory.h>
#include <stdio.h>
#include <malloc.h>
#include "TRSpeex.h"
int main(int argc, char* argv[])
{
FILE *fpInput;
FILE *fpOutput;
char aInputBuffer[MAX_FRAME_SIZE*10];
char aOutputBuffer[MAX_FRAME_SIZE*10];
int ret;
int buffer_size;
int nOutSize;
int nPackNo;
TRSpeexDecodeContex SpeexDecode;
int nTotalLen;
char buf[44];
if(argc <3)
{
printf("Usage SpeexDecode InputspxFile OutputWavFile\n");
return 1;
}
memset(aInputBuffer,0,sizeof(char)*MAX_FRAME_SIZE*10);
memset(buf,0,44);
buf[0] = 'R';
buf[1] = 'I';
buf[2] = 'F';
buf[3] = 'F';
buf[8] = 'W';
buf[9] = 'A';
buf[10] = 'V';
buf[11] = 'E';
buf[12] = 'f';
buf[13] = 'm';
buf[14] = 't';
buf[15] = 0x20;
buf[16] = 0x10;
buf[20] = 0x01;
buf[22] = 0x01;
buf[24] = 0x80;
buf[25] = 0x3E;
buf[29]= 0x7D;
buf[32] = 0x02;
buf[34] = 0x10;
buf[36] = 'd';
buf[37] = 'a';
buf[38] = 't';
buf[39] = 'a';
TRSpeexDecodeInit(&SpeexDecode);
fpInput = fopen(argv[1],"rb");
if(fpInput == NULL)
{
printf("can't open input spx file");
return 0;
}
fpOutput = fopen(argv[2],"wb");
if(fpOutput == NULL)
{
printf("can't open output file");
return 0;
}
fwrite(buf,1,44,fpOutput);
nTotalLen = 0;
buffer_size = 6;
ret = fread(aInputBuffer, 1,buffer_size,fpInput);
while(1)
{
TRSpeexDecode(&SpeexDecode,aInputBuffer,buffer_size,aOutputBuffer, &nOutSize);
ret = fread(aInputBuffer, 1,buffer_size, fpInput);
if(ret != buffer_size)
break;
fwrite(aOutputBuffer,1, nOutSize,fpOutput);
nTotalLen += nOutSize;
}
TRSpeexDecodeRelease(&SpeexDecode);
fseek(fpOutput,40,SEEK_SET);
fwrite(&nTotalLen,1,4,fpOutput);
fseek(fpOutput,4,SEEK_SET);
nTotalLen += 36;
fwrite(&nTotalLen,1,4,fpOutput);
fclose(fpOutput);
fclose(fpInput);
return 0;
}
首先需要把方法名稱由main方法改為自己想要的名字,這里我改成了decode,其次修改參數(shù),因?yàn)镴ava調(diào)用傳遞的是2個(gè)字符串參數(shù),speex的路徑和轉(zhuǎn)碼后的wav的路徑,所以需要先將原來的參數(shù)argc刪除,并刪除
if(argc <3)
{
printf("Usage SpeexDecode InputspxFile OutputWavFile\n");
return 1;
}
同時(shí)刪除argv參數(shù),添加兩個(gè)參數(shù)char* in ,char* out,分別對(duì)應(yīng)speex的路徑和轉(zhuǎn)碼后的wav的路徑,然后修改代碼中的
fpInput = fopen(argv[1],"rb");
為
fpInput = fopen(in,"rb");
修改
fpOutput = fopen(argv[2],"wb");
為
fpOutput = fopen(out,"wb");
修改后的代碼為
#include <memory.h>
#include <stdio.h>
#include <malloc.h>
#include "TRSpeex.h"
int decode(char* in,char* out)
{
FILE *fpInput;
FILE *fpOutput;
char aInputBuffer[MAX_FRAME_SIZE*10];
char aOutputBuffer[MAX_FRAME_SIZE*10];
int ret;
int buffer_size;
int nOutSize;
int nPackNo;
TRSpeexDecodeContex SpeexDecode;
int nTotalLen;
char buf[44];
memset(aInputBuffer,0,sizeof(char)*MAX_FRAME_SIZE*10);
memset(buf,0,44);
buf[0] = 'R';
buf[1] = 'I';
buf[2] = 'F';
buf[3] = 'F';
buf[8] = 'W';
buf[9] = 'A';
buf[10] = 'V';
buf[11] = 'E';
buf[12] = 'f';
buf[13] = 'm';
buf[14] = 't';
buf[15] = 0x20;
buf[16] = 0x10;
buf[20] = 0x01;
buf[22] = 0x01;
buf[24] = 0x80;
buf[25] = 0x3E;
buf[29]= 0x7D;
buf[32] = 0x02;
buf[34] = 0x10;
buf[36] = 'd';
buf[37] = 'a';
buf[38] = 't';
buf[39] = 'a';
TRSpeexDecodeInit(&SpeexDecode);
fpInput = fopen(in,"rb");
if(fpInput == NULL)
{
printf("can't open input spx file");
return 0;
}
fpOutput = fopen(out,"wb");
if(fpOutput == NULL)
{
printf("can't open output file");
return 0;
}
fwrite(buf,1,44,fpOutput);
nTotalLen = 0;
buffer_size = 6;
ret = fread(aInputBuffer, 1,buffer_size,fpInput);
while(1)
{
TRSpeexDecode(&SpeexDecode,aInputBuffer,buffer_size,aOutputBuffer, &nOutSize);
ret = fread(aInputBuffer, 1,buffer_size, fpInput);
if(ret != buffer_size)
break;
fwrite(aOutputBuffer,1, nOutSize,fpOutput);
nTotalLen += nOutSize;
}
TRSpeexDecodeRelease(&SpeexDecode);
fseek(fpOutput,40,SEEK_SET);
fwrite(&nTotalLen,1,4,fpOutput);
fseek(fpOutput,4,SEEK_SET);
nTotalLen += 36;
fwrite(&nTotalLen,1,4,fpOutput);
fclose(fpOutput);
fclose(fpInput);
return 0;
}
修改完成后為了方便引用改文件后綴c為h
實(shí)現(xiàn)原生方法
微信demo修改后,就可以實(shí)現(xiàn)wang_raye_speex_SpeexUtil.h的方法了,新建wang_raye_speex_SpeexUtil.c,編寫如下代碼
#include "wang_raye_speex_SpeexUtil.h"
#include "SpeexDecode.h"
JNIEXPORT jboolean JNICALL Java_wang_raye_speex_SpeexUtil_decode
(JNIEnv * env, jclass p2, jstring p3, jstring p4)
{
const char *str3 = (*env)->GetStringUTFChars(env, p3, 0);
const char *str4 = (*env)->GetStringUTFChars(env, p4, 0);
return 0==decode(str3,str4);
}
這里就是實(shí)現(xiàn)了一個(gè)調(diào)用中轉(zhuǎn),在這個(gè)方法中調(diào)用剛剛修改的SpeexDecode.h的方法
打包so
代碼寫完后,需要把wangrayespeexSpeexUtil.h和wang_raye_speex_SpeexUtil.c以及修改過的微信demo的代碼進(jìn)行打包成so文件。注:Windows環(huán)境下就是DLL文件
首先創(chuàng)建打包文件makefile-linux,編寫一下內(nèi)容
#共享庫(kù)文件名,lib*.so
TARGET := libjspeex.so
#compile and lib parameter
#編譯參數(shù)
CC := gcc
LIBS :=-lspeex
LDFLAGS :=
DEFINES :=
INCLUDE := -I. -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux
CFLAGS := -g -Wall -O3 $(DEFINES) $(INCLUDE)
CXXFLAGS:= $(CFLAGS) -DHAVE_CONFIG_H
SHARE := -fPIC -shared -o
#i think you should do anything here
#下面的基本上不需要做任何改動(dòng)了
#source file
#源文件,自動(dòng)找所有.c和.cpp文件,并將目標(biāo)定義為同名.o文件
SOURCE := $(wildcard *.c) $(wildcard *.cpp)
OBJS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE)))
.PHONY : everything objs clean veryclean rebuild
everything : $(TARGET)
all : $(TARGET)
objs : $(OBJS)
rebuild: veryclean everything
clean :
rm -fr *.o
rm -rf *.so
veryclean : clean
rm -fr $(TARGET)
$(TARGET) : $(OBJS)
$(CC) $(CXXFLAGS) $(SHARE) $@ $(OBJS) $(LDFLAGS) $(LIBS)
rm -rf *.o
install:
rm -rf /usr/local/lib/$(TARGET)
cp $(TARGET) /usr/local/lib
其中l(wèi)ibjspeex是動(dòng)態(tài)庫(kù)的名字,保存后執(zhí)行命令
sudo make -f makefile-linux
如果沒有異常則執(zhí)行命令
sudo make -f makefile-linux install
完成后會(huì)在/usr/local/lib文件夾中生成libjspeex.so文件,如果編譯時(shí)出現(xiàn)
relocation R_X86_64_32 against `.rodata' can not be used when making a shared object
是由于系統(tǒng)是AMD64位的,所以需要在編譯的時(shí)候添加 -fPIC 選項(xiàng),需要修改makefile-linux的CC := gcc行為CC := gcc -fPIC
再重新執(zhí)行命令即可
使用
將生成的libjspeex.so放到項(xiàng)目根目錄即可使用,如果使用時(shí)提示
speex.xxxx --cannot open shared object file: No such file or directory,
則是因?yàn)?usr/local/lib并沒有在系統(tǒng)的環(huán)境變量里面,可以修改/etc/ld.so.conf,然后刷新
vi /etc/ld.so.conf
增加一行 include /usr/local/lib
sudo ldconfig
結(jié)尾
本文只是記錄了我在使用過程中遇到的一些問題,有些沒有遇到的歡迎補(bǔ)充,另外如果知道Java如果加載jar中so文件也麻煩告知一下,現(xiàn)在就在頭疼這個(gè)問題