本文首发于微信公众号——世界上有意思的事,搬运转载请注明出处,否则将追究版权责任。交流qq群:859640274
DeepSpeech是一个通过深度学习将语音转换成文字的引擎, 详见百度的paper 。本次我们研究的是 github 上 DeepSpeech 分类中 star 最多的 Mozilla 实现的多平台多语言开源框架。
点击查看DeepSpeech在Android上的实现视频
# 获取 Ubuntu 上使用的深度学习模型,下面是英文的
wget https://github.com/mozilla/DeepSpeech/releases/download/v0.9.3/deepspeech-0.9.3-models.pbmm
wget https://github.com/mozilla/DeepSpeech/releases/download/v0.9.3/deepspeech-0.9.3-models.scorer
# 当然你也可以获取中文的模型
wget https://github.com/mozilla/DeepSpeech/releases/download/v0.9.3/deepspeech-0.9.3-models-zh-CN.pbmm
wget https://github.com/mozilla/DeepSpeech/releases/download/v0.9.3/deepspeech-0.9.3-models-zh-CN.scorer
# 使用 pip3 安装 deepspeech,注意 DeepSpeech 只支持 python3
pip3 install deepspeech
pip3 install --upgrade deepspeech
# 识别 a.aac 中的语音,并输出文字
deepspeech --model deepspeech-0.9.3-models.pbmm --scorer deepspeech-0.9.3-models.scorer --audio a.aac
DeepSpeech的使用,分为模型训练与模型使用这两部分。
模型训练:主要就是用 python 通过深度学习来生成模型,这一块接触不多,暂且不表。
模型使用:训练好的模型可以通过各个语言的封装的客户端在不同的平台上面使用。
DeepSpeech支持哪些语言:
c/c++:这个是 DeepSpeech 的核心语言,其他所有语言应该都是基于 c/c++ 进行封装的。
java:通过 jni 调用到 c/c++,可以到 Android 平台上
python
NodeJs
DeepSpeech 的客户端是围绕着 tensorflow 来实现的。例如
移动端有 tensorflow-lite 来实现低功耗设备上的深度学习
PC、Linux、Mac 则是满血版 tensorflow
使用不同的 tensorflow 版本用到的模型文件也不同
tensorflow-lite 使用的是 xxx.tflite 文件
tensorlow 使用的是 xxx.pbmm 和 .scorer 文件
在使用Android版本的客户端时,我发现如果训练的模型是中文模型时会抛异常。所以我们接下来通过解决这个异常来走一遍 DeepSpeech 的编译流程。
如果你使用了中文的 tflite 模型,然后在 Android 上面运行时。会发现识别出来的中文是乱码,且会发生崩溃。中文识别乱码崩溃issue,这个问题 github 上面的 issue 已经提出来了。但是目前 DeepSpeech 的管理者们并没有修复这个 issue 的计划。所以只能我们自己去修复了。注意这里的问题只有使用 java client 的时候才会有问题
乱码和崩溃的问题原因很简单,主要是因为 java 的 String 使用的 utf-8 编码,而 c++ 的编码使用的是 GBK。所以在从 c++ 到java 的传递过程中就乱码了。
同时崩溃的原因则是因为JNIEnv调用NewStringUTF(charStr)时,会校验charStr是否为标准的UTF-8格式字符串。调用顺序:NewStringUTF() -> checkUtfString() -> checkUtfBytes()。
所以要解决这个原因,我们需要修改 String org.deepspeech.libdeepspeech.implJNI.SpeechToText(long, short[], long) 方法的返回值,因为这个接口是 JNI 的,所以我们需要修改代码之后重新编译 DeepSpeech 的 java client,拿到新的依赖库。
3.解决办法
(1).代码修改
在修复代码前,我们先需要知道一个事情:因为 DeepSpeech 的核心代码都是 c/c++ 所以为了非常方便的为各种语言提供调用 c/c++ api 的接口,DeepSpeech 中使用了 SWIG(Simplified Wrapper and Interface Generator )。
SWIG是什么?
这是SWIG的tutorial。简单来说,通过 SWIG 我们可以将某个 c/c++ 头文件中的api,自动转换成各种语言的接口,省去你写各种模板代码的时间。一个简单的例子是:如果你想让 java 调用 c/c++ 那么,你需要写 JNI 接口,通过 JNI 调用 c/c++ 的对应接口。而有了 SWIG 之后,这些代码可以直接生成。
下面的代码块,是 Linux 安装 SWIG 的 shell 代码。
sudo apt-get install libpcre3 libpcre3-dev
wget http://prdownloads.sourceforge.net/swig/swig-4.0.2.tar.gz
tar -zxvf swig-4.0.2.tar.gz
cd swig-4.0.2
./configure
sudo make install
vi /etc/profile
export SWIG_HOME=/root/swig-4.0.2
source /etc/profile
这里我默认读者已经将 DeepSpeech 项目 clone 到了本地。接下来我们来对代码进行修复:
首先 cd DeepSpeech/native_client/java
,此时 jni/deepspeech.i 就是 SWIG 的配置代码。
其次 swig -java -c++ jni/deepspeech.i
,此时在 jni 目录下,就会生成一系列 java 代码。
然后我们找到 String org.deepspeech.libdeepspeech.implJNI.SpeechToText()
接口将其返回值改成 byte[]
。
同时 DeepSpeech/native_client/java/jni/deepspeech_wrap.cxx/Java_implJNI_SpeechToText
方法的返回值也得改成 jbytearray
具体的代码很简单就不赘述了。
这两个地方修改了之后,我们使用的地方也得改,因为返回到 java 层的数据是 byte[] 了,而且其编码方式是 GBK,所以我们在 java 层需要转成 UTF-8 的 String,才能正常展示。
至此,对于这个问题已经修改完毕了,这个只是简单的修改方式。大家可以想想有没有更好的解决办法,想到后甚至可以提 mr 到 DeepSpeech 项目中,我想短时间他们应该没有人力解决这个问题。
(2).编译
这一小节,我会带大家验证编译一次 DeepSpeech java client 验证这个修改是否有用。这里强烈推荐大家使用一个比较环境干净的 Linux 服务器或者是 Docker 进行编译。我前期使用 Mac 和我家中的 Ubuntu,因为环境不干净导致踩了非常多的坑,浪费了很多时间。最终我租了一个阿里云服务器才完成了编译。(注意:编译需要性能比较高,推荐服务器最少能有 8g 内存,4核Cpu)
先 clone 项目,然后同步 tensorflow。
git clone https://github.com/mozilla/DeepSpeech.git
git submodule sync tensorflow/
git submodule update --init tensorflow/
因为 tensorflow 的编译使用的是 Bazel,又 Bazel 依赖 java,所以我们需要先安装 java:
wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.tar.gz
tar -zxvf jdk-8u131-linux-x64.tar.gz
cd jdk-8u131-linux-x64
vim /etc/profile
export JAVA_HOME=~/jdk1.8.0_131
export JRE_HOME=~/jdk1.8.0_131/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$MYSQL_HOME/bin:$JAVA_HOME/bin:$JRE_HOME/bin:$JAVA_HOME:$PATH
source /etc/profile
java -version
然后安装 Bazel:
echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
curl https://storage.googleapis.com/bazel-apt/doc/apt-key.pub.gpg | sudo apt-key add -
sudo apt-get update && sudo apt-get install bazel
bazel version
## 安装 Bazel 3.1.0
sudo apt update && sudo apt install bazel-3.1.0
同时 tensorflow 还依赖 python3 和其 c/c++ 的头文件:
sudo apt-get install python3-dev # for python3.x installs
因为我们需要在 Android 手机上验证 DeepSpeech java client 是否修复,所以还需要用命令行安装 Android tool、sdk、ndk。
先安装 Android sdk tool
wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
vi /etc/profile
export ANDROID_TOOLS_HOME=/root/Android/tools
export PATH=$ANDROID_TOOLS_HOME:$ANDROID_TOOLS_HOME/bin:$PATH
source /etc/profile
再通过 sdkmanager 安装 Android SDK、Android SDK build tools、Android platform tools、Android NDK
sdkmanager "platform-tools" "platforms;android-30" "platforms;android-29" "platforms;android-28" "platforms;android-27" "platforms;android-26" "platforms;android-25" "platforms;android-24" "platforms;android-23" "platforms;android-22" "platforms;android-21" "platforms;android-20" "platforms;android-19" "platforms;android-18" "platforms;android-17" "platforms;android-16" "platforms;android-15" "platforms;android-14" "platforms;android-13" "platforms;android-12" "platforms;android-11" "platforms;android-10"
sdkmanager "build-tools;30.0.3" "build-tools;30.0.2" "build-tools;30.0.1" "build-tools;30.0.0" "build-tools;29.0.3" "build-tools;29.0.2" "build-tools;29.0.1" "build-tools;29.0.0" "build-tools;28.0.3" "build-tools;28.0.2" "build-tools;28.0.1" "build-tools;28.0.0" "build-tools;27.0.3" "build-tools;27.0.2" "build-tools;27.0.1" "build-tools;27.0.0" "build-tools;26.0.3" "build-tools;26.0.2" "build-tools;26.0.1" "build-tools;26.0.0" "build-tools;25.0.3" "build-tools;25.0.2" "build-tools;25.0.1" "build-tools;25.0.0" "build-tools;24.0.3" "build-tools;24.0.2" "build-tools;24.0.1" "build-tools;24.0.0" "build-tools;23.0.3" "build-tools;23.0.2" "build-tools;23.0.1" "build-tools;22.0.1" "build-tools;21.1.2" "build-tools;20.0.0" "build-tools;19.1.0"
sdkmanager "ndk;22.1.7171670" "ndk;21.4.7075529" "ndk;18.1.5063045"
## Python3 已经下载好了,直接按回车
You have bazel 3.1.0 installed.
Please specify the location of python. [Default is /usr/bin/python3]:
## 直接回车
Found possible Python library paths:
/usr/local/lib/python3.6/dist-packages
/usr/lib/python3/dist-packages
Please input the desired Python library path to use. Default is [/usr/local/lib/python3.6/dist-packages]
## 我们不需要这个,按 n
Do you wish to build TensorFlow with OpenCL SYCL support? [y/N]:
## 我们不需要这个,按 n
Do you wish to build TensorFlow with ROCm support? [y/N]:
## 我们不需要这个,按 n
Do you wish to build TensorFlow with CUDA support? [y/N]:
## 我们不需要这个,按 n
Do you wish to download a fresh release of clang? (Experimental) [y/N]:
## 回车
Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -march=native -Wno-sign-compare]:
## 我们需要对 Android 进行编译,按 y
Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]:
## 输入前面下载的 Android NDK 路径,因为我们前面下载了好几个版本的 Android NDK,所以路径上需要有具体的版本,例如:
## /root/Android/ndk/18.1.5063045,然后回车
Please specify the home path of the Android NDK to use. [Default is /root/Android/Sdk/ndk-bundle]:
## 输入18
Please specify the (min) Android NDK API level to use. [Available levels: ['16', '17', '18', '19', '21', '22', '23', '24', '26', '27', '28']] [Default is 21]:
##输入前面下载的 Android sdk 的路径,例如 /root/Android,然后回车
Please specify the home path of the Android SDK to use. [Default is /root/Android/Sdk]:
## 直接回车
Please specify the Android SDK API level to use. [Available levels: ['10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30']] [Default is 30]:
## 直接回车
Please specify an Android build tools version to use. [Available versions: ['19.1.0', '20.0.0', '21.1.2', '22.0.1', '23.0.1', '23.0.2', '23.0.3', '24.0.0', '24.0.1', '24.0.2', '24.0.3', '25.0.0', '25.0.1', '25.0.2', '25.0.3', '26.0.0', '26.0.1', '26.0.2', '26.0.3', '27.0.0', '27.0.1', '27.0.2', '27.0.3', '28.0.0', '28.0.1', '28.0.2', '28.0.3', '29.0.0', '29.0.1', '29.0.2', '29.0.3', '30.0.0', '30.0.1', '30.0.2', '30.0.3']] [Default is 30.0.3]:
至此我们将编译选项配置好了,还需要将 native_client 目录的代码 link 到 tensorflow 目录下:ln -s ../native_client
接下来我们就可以开始编译 DeepSpeech 在 Android 平台的 .so 文件了。目前有三种 CPU 平台可供选择:armeabi-v7a、arm64-v8a、x86_64
## 编译 armeabi-v7a,先在 DeepSpeech/tensorflow 目录下执行
bazel build --workspace_status_command="bash native_client/bazel_workspace_status_cmd.sh" --config=monolithic --config=android --config=android_arm --define=runtime=tflite --action_env ANDROID_NDK_API_LEVEL=21 --cxxopt=-std=c++14 --copt=-D_GLIBCXX_USE_C99 //native_client:libdeepspeech.so
## 然后在 DeepSpeech/native_client 目录下执行,记住 Android NDK 路径换成自己的
/root/Android/ndk/21.4.7075529/ndk-build APP_PLATFORM=android-21 APP_BUILD_SCRIPT=$(pwd)/Android.mk NDK_PROJECT_PATH=$(pwd) APP_STL=c++_shared TFDIR=$(pwd)/../tensorflow/ TARGET_ARCH_ABI=armeabi-v7a
## 编译 android_arm64,先在 DeepSpeech/tensorflow 目录下执行
bazel build --workspace_status_command="bash native_client/bazel_workspace_status_cmd.sh" --config=monolithic --config=android --config=android_arm64 --define=runtime=tflite --action_env ANDROID_NDK_API_LEVEL=21 --cxxopt=-std=c++14 --copt=-D_GLIBCXX_USE_C99 //native_client:libdeepspeech.so
## 然后在 DeepSpeech/native_client 目录下执行,记住 Android NDK 路径换成自己的
/root/Android/ndk/21.4.7075529/ndk-build APP_PLATFORM=android-21 APP_BUILD_SCRIPT=$(pwd)/Android.mk NDK_PROJECT_PATH=$(pwd) APP_STL=c++_shared TFDIR=$(pwd)/../tensorflow/ TARGET_ARCH_ABI=arm64-v8a
## 编译 x86_64,先在 DeepSpeech/tensorflow 目录下执行
bazel build --workspace_status_command="bash native_client/bazel_workspace_status_cmd.sh" --config=monolithic --config=android --config=android_x86 --define=runtime=tflite --action_env ANDROID_NDK_API_LEVEL=21 --cxxopt=-std=c++14 --copt=-D_GLIBCXX_USE_C99 //native_client:libdeepspeech.so
## 然后在 DeepSpeech/native_client 目录下执行,记住 Android NDK 路径换成自己的
/root/Android/ndk/21.4.7075529/ndk-build APP_PLATFORM=android-21 APP_BUILD_SCRIPT=$(pwd)/Android.mk NDK_PROJECT_PATH=$(pwd) APP_STL=c++_shared TFDIR=$(pwd)/../tensorflow/ TARGET_ARCH_ABI=x86_64
至此我们已经有了 Android 平台上的 so 文件了,我们可以在 DeepSpeech/native_client/java 目录下运行 ./gradlew libdeepspeech:build
以此编译出一个 libdeepspeech.aar,这个 aar 就是我们一直心心念念的 DeepSpeech java client 了,我们可以将这个 aar 替换 DeepSpeech Demo 中的 implementation 'deepspeech.mozilla.org:libdeepspeech:VERSION@aar' 来验证我们的改动是否正确。
本篇文章,我们了解了如何使用 DeepSpeech,同时通过一个 DeepSpeech 的已知问题走了一遍 DeepSpeech 的代码修改与编译流程,给大家留了一个给 github 大项目提 mr 的机会。如果你喜欢这篇文章的话,那么就请点赞、转发、收藏吧,你的支持是我最大的动力。>_<