当前位置: 首页 > 工具软件 > Speex > 使用案例 >

java speex转码_微信Speex转wav,Speex to wav

南宫森
2023-12-01

前言

微信公众号开发,因为需要在页面发送语音和播放,由于公众号页面中录音必须要调用微信js录音,录音完成由前端上传到微信临时素材,再由后端下载到服务器,然后给前端播放,但是因为从微信下载下来的语音智能是speex格式(高清语音)和amr格式,然而这2种格式都是无法直接在HTML中播放的,所以需要对语音进行转码,由于speex格式清晰度较高,所以我选择了下载speex格式的语音进行转码,本文就是记录如果一步一步调用speex官方源码和微信提供部分C代码进行转码,注:本文所有环境和命令是基于Linux的

下载并安装speex

环境:

Linux Centos

Gcc

JDK 1.8

speex 1.2.0

步骤:

首先下载speex最新的源码,下载地址,解压然后进入源码目录,执行命令

sudo ./configure

验证环境是否有误,如果有问题,则根据具体提示自行安装和配置,如果没有异常,则可以执行命令进行编译安装了

sudo make;sudo make install

如果没有出问题,则会在/usr/local/lib文件夹下面产生libspeex.so等文件,如果有问题,则根据具体提示解决,因为我这里没有遇到任何问题,所以也无法提供常见的问题了

编写Java调用C语言代码

在Java中调用C或者C++的代码技术叫做JNI,是Java原生支持的,首先我们要定义好原生方法的定义,代码如下

package wang.raye.speex;

import java.lang.reflect.Field;

/**

* Speex 转码工具类

* @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语言的方法,而static部分是加载C语言代码的动态库,有2种方法可以加载动态链接库

System.load

System.loadLibrary

其中System.load 参数必须为库文件的绝对路径,可以是任意路径,System.loadLibrary 参数为库文件名,不包含库文件的扩展名,但是库路径必须是在JVM属性java.library.path所指向的路径中,这里我是获取的绝对路径,就是项目目录,因为用的spring boot直接打包的jar运行的,类写好之后生成class文件,然后用Javah命令生成C语言的.h文件,在class文件执行命令

javah -classpath . wang.raye.speex.SpeexUtil

会生成

wang_raye_speex_SpeexUtil.h文件,这就是C语言的头文件,里面定义了我们SpeexUtil定义的decode,当然是没有实现的,具体实现代码需要我们自己实现

wang_raye_speex_SpeexUtil.h 内容:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include

/* 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

具体方法实现可以调用微信的demo,首先下载微信的demo,下载地址

由于微信demo里面是main方法的,所以需要进行修改,原本的SpeexDecode.c代码

#include

#include

#include

#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,其次修改参数,因为Java调用传递的是2个字符串参数,speex的路径和转码后的wav的路径,所以需要先将原来的参数argc删除,并删除

if(argc <3)

{

printf("Usage SpeexDecode InputspxFile OutputWavFile\n");

return 1;

}

同时删除argv参数,添加两个参数char* in ,char* out,分别对应speex的路径和转码后的wav的路径,然后修改代码中的

fpInput = fopen(argv[1],"rb");

fpInput = fopen(in,"rb");

修改

fpOutput = fopen(argv[2],"wb");

fpOutput = fopen(out,"wb");

修改后的代码为

#include

#include

#include

#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

实现原生方法

微信demo修改后,就可以实现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);

}

这里就是实现了一个调用中转,在这个方法中调用刚刚修改的SpeexDecode.h的方法

打包so

代码写完后,需要把wangrayespeexSpeexUtil.h和wang_raye_speex_SpeexUtil.c以及修改过的微信demo的代码进行打包成so文件。注:Windows环境下就是DLL文件

首先创建打包文件makefile-linux,编写一下内容

#共享库文件名,lib*.so

TARGET := libjspeex.so

#compile and lib parameter

#编译参数

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

#下面的基本上不需要做任何改动了

#source file

#源文件,自动找所有.c和.cpp文件,并将目标定义为同名.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

其中libjspeex是动态库的名字,保存后执行命令

sudo make -f makefile-linux

如果没有异常则执行命令

sudo make -f makefile-linux install

完成后会在/usr/local/lib文件夹中生成libjspeex.so文件,如果编译时出现

relocation R_X86_64_32 against `.rodata' can not be used when making a shared object

是由于系统是AMD64位的,所以需要在编译的时候添加 -fPIC 选项,需要修改makefile-linux的CC := gcc行为CC := gcc -fPIC

再重新执行命令即可

使用

将生成的libjspeex.so放到项目根目录即可使用,如果使用时提示

speex.xxxx --cannot open shared object file: No such file or directory,

则是因为/usr/local/lib并没有在系统的环境变量里面,可以修改/etc/ld.so.conf,然后刷新

vi /etc/ld.so.conf

增加一行 include /usr/local/lib

sudo ldconfig

结尾

本文只是记录了我在使用过程中遇到的一些问题,有些没有遇到的欢迎补充,另外如果知道Java如果加载jar中so文件也麻烦告知一下,现在就在头疼这个问题

 类似资料: