在最近的專案中,需要完成這樣一個功能,要求app能與硬體進行語音交流,不需要實時,僅僅實現類似微信的按住說話的功能就可以。為了減少網路流量,考慮將音訊檔案壓縮傳送。成熟的壓縮方式有speex、opus。目前speex已不再維護。考慮到本次需要的功能簡單以及網上關於speex的文件較多的情況下,我們決定使用speex完成語音壓縮功能。 我們通過node呼叫c++模組的方法,來實現speex的壓縮(僅實現壓縮,關於音訊格式轉換日後再碼)。
ps: 先水上他一篇 :)
下載speex
我們選擇speex的最新穩定版本進行下載:speex-1.2.0.tar.gz
具體步驟
1、將下載的speex檔案放到專案中
- 專案根目錄下新建deps資料夾,並在deps中新建檔案,建好目錄如下:
- 圖中的speex-1.2.0為下載並解壓好的speex。
- 在x64資料夾中新加config.h檔案 內容如下
- building.gyp中的內容如下:(此部分主要用於選擇性編譯speex中的檔案並載入到專案中)
{
'variables': { 'target_arch%': 'x64' },
'target_defaults': {
'default_configuration': 'Debug',
'configuration': {
'Debug': {
'defines': [ 'DEBUG', '_DEBUG' ],
'msvs_settings': {
'VSSLCompilerTool': {
'RuntimeLibrary': 1, #static debug
},
},
},
'Release': {
'defines': [ 'NODEBUG' ],
'msvs_settings': {
'VSSLCompilerTool': {
'RuntimeLibrary': 0, #static release
},
},
},
},
'msvs_settings': {
'VCLinkerTool': {
'GenerateDebugInformation': 'true',
},
},
},
'targets': [
{
'target_name': 'libspeexdsp',
'type': 'static_library',
'sources': [
'speex-1.2.0/libspeex/bits.c',
'speex-1.2.0/libspeex/cb_search.c',
'speex-1.2.0/libspeex/exc_5_64_table.c',
'speex-1.2.0/libspeex/exc_5_256_table.c',
'speex-1.2.0/libspeex/exc_8_128_table.c',
'speex-1.2.0/libspeex/exc_10_16_table.c',
'speex-1.2.0/libspeex/exc_10_32_table.c',
'speex-1.2.0/libspeex/exc_20_32_table.c',
'speex-1.2.0/libspeex/filters.c',
'speex-1.2.0/libspeex/gain_table.c',
'speex-1.2.0/libspeex/gain_table_lbr.c',
'speex-1.2.0/libspeex/hexc_10_32_table.c',
'speex-1.2.0/libspeex/hexc_table.c',
'speex-1.2.0/libspeex/high_lsp_tables.c',
'speex-1.2.0/libspeex/kiss_fft.c',
'speex-1.2.0/libspeex/kiss_fftr.c',
'speex-1.2.0/libspeex/lpc.c',
'speex-1.2.0/libspeex/lsp.c',
'speex-1.2.0/libspeex/lsp_tables_nb.c',
'speex-1.2.0/libspeex/ltp.c',
'speex-1.2.0/libspeex/modes.c',
'speex-1.2.0/libspeex/modes_wb.c',
'speex-1.2.0/libspeex/nb_celp.c',
'speex-1.2.0/libspeex/quant_lsp.c',
'speex-1.2.0/libspeex/smallft.c',
'speex-1.2.0/libspeex/speex.c',
'speex-1.2.0/libspeex/speex_callbacks.c',
'speex-1.2.0/libspeex/speex_header.c',
'speex-1.2.0/libspeex/stereo.c',
'speex-1.2.0/libspeex/vbr.c',
'speex-1.2.0/libspeex/vq.c',
'speex-1.2.0/libspeex/window.c'
],
'cflags': [
'-fvisibility=hidden',
'-W',
'-Wstrict-prototypes',
'-Wno-parentheses',
'-Wno-unused-parameter',
'-Wno-sign-compare',
'-Wno-unused-variable',
],
'include_dirs': [
'config/speex-1.2.0/<(OS)/<(target_arch)',
'speex-1.2.0/include',
],
'defines': [
'PIC',
'HAVE_CONFIG_H',
'_USE_MATH_DEFINES',
]
}
]
}
複製程式碼
2 新加c++目錄,如下圖所示
此目錄為我們要自己動手實現的c++程式碼,其中hello.cc檔案內容如下:(由於編碼格式錯誤,暫時分多段顯示程式碼,一下程式碼全為hello.cc)#include <node.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
複製程式碼
#ifdef __cplusplus
extern "C"
{
#endif
#include <speex/speex.h>
#include <speex/speex_callbacks.h>
#ifdef __cplusplus
}
#endif
複製程式碼
#ifdef FIXED_DEBUG
extern long long spx_mips;
#endif
複製程式碼
#define FRAME_SIZE 160
#ifdef FIXED_DEBUG
extern long long spx_mips;
#endif
複製程式碼
using namespace v8;
複製程式碼
void Add(const FunctionCallbackInfo<Value>& args) {
複製程式碼
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
void *st;
FILE *fin,*fout;
short in[FRAME_SIZE];
float input[FRAME_SIZE];
char cbits[200];
int nbBytes;
SpeexBits bits;
int i,tmp;
st = speex_encoder_init(&speex_nb_mode);
tmp = 8;
speex_encoder_ctl(st,SPEEX_SET_QUALITY,&tmp);
Local<Value> in_addr = args[0];
Local<String> ss = in_addr->ToString();
String::Utf8Value value(ss);
char* c = *value;
fin = fopen(c,"r");
複製程式碼
Local<Value> out_addr = args[1];
Local<String> out_addr_str = out_addr->ToString();
String::Utf8Value value1(out_addr_str);
char* o = *value1;
fout = fopen(o,"w");
speex_bits_init(&bits);
while(1){
fread(in,sizeof(short),FRAME_SIZE,fin);
if(feof(fin))
break;
for(i = 0;i<FRAME_SIZE;i++){
input[i] = in[i];
}
speex_bits_reset(&bits);
speex_encode(st,input,&bits);
nbBytes = speex_bits_write(&bits,cbits,200);
fwrite(&nbBytes, sizeof(int), 1, stdout);
fwrite(cbits, 1, nbBytes, stdout);
fwrite(input,sizeof(short),FRAME_SIZE,fout);
}
複製程式碼
speex_encoder_destroy(st);
/*Destroy the bit-packing struct*/
speex_bits_destroy(&bits);
fclose(fin);
}
void Init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "add", Add);
}
NODE_MODULE(hello, Init)
複製程式碼
3 根目錄下新建binding.gyp
binding.gyp內容如下:
{
"targets": [
{
"target_name": "hello",
"sources": ["./c++/hello.cc", ],
"dependencies":[
"deps/binding.gyp:libspeexdsp"
],
'cflags': [
'-pthread',
'-fno-exceptions',
'-fno-strict-aliasing',
'-Wall',
'-Wno-unused-parameter',
'-Wno-missing-field-initializers',
'-Wextra',
'-pipe',
'-fno-ident',
'-fdata-sections',
'-ffunction-sections',
'-fPIC'
],
'defines': [
'LARGEFILE_SOURCE',
'_FILE_OFFSET_BITS=64',
'WEBRTC_TARGET_PC',
'WEBRTC_LINUX',
'WEBRTC_THREAD_RR',
'EXPAT_RELATIVE_PATH',
'GTEST_RELATIVE_PATH',
'JSONCPP_RELATIVE_PATH',
'WEBRTC_RELATIVE_PATH',
'POSIX',
'__STDC_FORMAT_MACROS',
'DYNAMIC_ANNOTATIONS_ENABLED=0'
],
'include_dirs': [
'deps/speex-1.2.0/include',
'deps/config/speex-1.2.0/<(OS)/<(target_arch)',
],
}
]
}
複製程式碼
4 編譯
在專案根目錄下執行命令
node-gyp configure
node-gyp build
複製程式碼
如未報錯並且根目錄下生成build目錄:
說明編譯成功。若為成功,請詳細檢查兩個binding.gyp中的目錄是否對應正確,注:使用node-gyp需要python27環境,必須為此版本5 測試:
新建hello.js 檔案內容如下
const hello = require('./build/Release/hello');
hello.add('male.wav', "male.test");
複製程式碼
檔案執行後會將male.wav檔案壓縮,並生成male.test檔案 效果如下:
最終檔案目錄如下