# 前言
我們在Android應用做語音識別的時候,一般是用戶喚醒之後開始說話。當用戶超過一定的時候沒有說話,就停止錄音,並把錄音發送到語音識別服務器,獲取語音識別結果。本教程就是解決如何檢測用戶是否停止說話,我們使用的是WebRTC架構的源代碼中的vad代碼實現的。
VAD算法全稱是Voice Activity Detection,該算法的作用是檢測是否是人的語音,使用範圍極廣,降噪,語音識別等領域都需要有vad檢測。webrtc的vad檢測原理是根據人聲的頻譜範圍,把輸入的頻譜分成六個子帶:80Hz——250Hz,250Hz——500Hz,500Hz——1K,1K——2K,2K——3K,3K——4K。分別計算這六個子帶的能量。然後使用高斯模型的概率密度函數做運算,得出一個對數似然比函數。對數似然比分爲全局和局部,全局是六個子帶之加權之和,而局部是指每一個子帶則是局部,所以語音判決會先判斷子帶,子帶判斷沒有時會判斷全局,只要有一個通過認爲是語音。
創建Android項目¶
現在我們就來使用webrtc的vad源碼開發檢測是否有語音的Android項目。
首先我們創建一個Android項目,修改local.properties中的配置信息,添加NDK的路徑,例如筆者的如下:
ndk.dir=D\:\\Android\\android-ndk-r15c
sdk.dir=D\:\\Android\\sdk
接着在app目錄下創建CMakeLists.txt文件,並添加以下代碼:
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic")
aux_source_directory(src/main/cpp/vad_src/ DIR_LIB_SRCS)
add_definitions(-DWEBRTC_POSIX)
add_definitions(-DWEBRTC_ANDROID)
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp
${DIR_LIB_SRCS})
include_directories(src/main/cpp/vad_src/)
find_library( log-lib
log )
target_link_libraries( native-lib
${log-lib} )
然後修改app目錄下的build.gradle文件,修改如下:
# 在defaultConfig添加
externalNativeBuild {
cmake {
arguments = ['-DANDROID_STL=c++_static']
cppFlags ""
}
}
# 在android下添加
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
使用webrtc¶
接下來就開始克隆webrtc源碼
git clone https://android.googlesource.com/platform/external/webrtc
我們所需的源碼主要存放webrtc/webrtc/common_audio/vad目錄中,我們把裏面的源碼文件都複製到我們的Android項目main/cpp/vad_src目錄下,主要: 有很多的依賴代碼並不在這個目錄中,我們需要更加每個文件的導入庫查看依賴庫所在的位置,並吧他們都複製到main/cpp/vad_src目錄下。在鼻子提供的源碼中,已經提取好了,可以下載:
在main/cpp目錄下創建native-lib.cpp文件,爲Java調用vad提供接口,代碼如下:
#include <jni.h>
#include <string>
#include <malloc.h>
#include "vad_src/webrtc_vad.h"
#include "vad_src/vad_core.h"
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_yeyupiaoling_testvad_MainActivity_webRtcVad_1Process(JNIEnv *env, jobject instance,
jshortArray audioData_,
jint offsetInshort,
jint readSize) {
VadInst *handle = WebRtcVad_Create();
WebRtcVad_Init(handle);
WebRtcVad_set_mode(handle, 2);
int index = readSize / 160;
jshort *pcm_data = env->GetShortArrayElements(audioData_, JNI_FALSE);
bool b = JNI_FALSE;
for (int i = 0; i < index; ++i) {
int vad = WebRtcVad_Process(handle, 16000, pcm_data + offsetInshort + i * 160, 160);
if (vad == 1) {
b = JNI_TRUE;
} else{
b=JNI_FALSE;
}
}
env->ReleaseShortArrayElements(audioData_, pcm_data, JNI_ABORT);
WebRtcVad_Free(handle);
return static_cast<jboolean>(b);
}
其對應的Java方法如下:
public native boolean webRtcVad_Process(short[] audioData, int offsetInshort, int readSize);
最後在我們的Android這樣子調用,可以檢測到用戶是否在說話。
int mMinBufferSize = AudioRecord.getMinBufferSize(16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
AudioRecord mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, mMinBufferSize * 2);
mMinBufferSize = 320;
short[] audioData = new short[mMinBufferSize];
if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) {
stopRecord();
return;
}
mRecorder.startRecording();
while (mIsRecording) {
if (null != mRecorder) {
readSize = mRecorder.read(audioData, 0, mMinBufferSize);
if (readSize == AudioRecord.ERROR_INVALID_OPERATION || readSize == AudioRecord.ERROR_BAD_VALUE) {
continue;
}
if (readSize != 0 && readSize != -1) {
// 語音活動檢測
mSpeaking = webRtcVad_Process(audioData, 0, readSize);
if (mSpeaking) {
Log.d(TAG, ">>>>>正在講話");
} else {
Log.d(TAG, "=====當前無聲音");
}
} else {
break;
}
}
}
最後別忘了添加錄音權限RECORD_AUDIO,如果是Android 6.0以上的,還需要動態申請權限。
if (!hasPermission()){
requestPermission();
}
// check had permission
private boolean hasPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
} else {
return true;
}
}
// request permission
private void requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 1);
}
}
