微信Speex轉(zhuǎn)wav,Speex to wav

前言

微信公眾號(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è)問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Ubuntu的發(fā)音 Ubuntu,源于非洲祖魯人和科薩人的語(yǔ)言,發(fā)作 oo-boon-too 的音。了解發(fā)音是有意...
    螢火蟲de夢(mèng)閱讀 100,847評(píng)論 9 468
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,366評(píng)論 25 708
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 7,391評(píng)論 0 17
  • 最近正忙于寫博士論文,期間也被師弟各種問如何才能逃離延期的魔咒,看看這些九零后無辜的面孔決定把這幾年的一些想法做一...
    YAO瑤閱讀 646評(píng)論 0 0
  • 愛情不在于說多少次我愛你,而在于怎么樣去證明你說的是真的。 別人只會(huì)夸你,溫柔懂事脾氣好。真正在乎你的人,才會(huì)關(guān)心...
    星影520閱讀 273評(píng)論 0 1

友情鏈接更多精彩內(nèi)容