限时福利领取


在移动端实现高质量的语音识别一直是个技术难点,尤其是需要离线使用的场景。最近在项目中尝试了Whisper.cpp+VAD的方案,效果出乎意料的好,下面把整个实践过程记录下来,希望对有类似需求的同学有所帮助。

1. 为什么选择Whisper.cpp+VAD?

在Android上做语音识别,常见方案有:

  • Google Speech-to-Text API:在线方案,延迟和隐私是硬伤
  • TensorFlow Lite ASR:模型优化复杂,实时性差
  • 传统DNN方案:准确率不足

Whisper.cpp作为开源方案有几个独特优势:

  1. 纯C++实现,无第三方依赖
  2. 支持量化模型(GGML格式)
  3. 内存占用可低至~100MB(tiny模型)
  4. 单次推理延迟<200ms(骁龙865测试)

加上VAD(Voice Activity Detection)后,可以在用户不说话时停止识别,节省30%以上的CPU资源。

语音识别流程对比

2. 编译部署全流程

2.1 NDK交叉编译

首先需要为Android编译Whisper.cpp:

git clone --recursive https://github.com/ggerganov/whisper.cpp
cd whisper.cpp

然后创建NDK编译脚本android_build.sh

#!/bin/bash
ANDROID_NDK=~/Android/Sdk/ndk/25.1.8937393
TOOLCHAIN=$ANDROID_NDK/build/cmake/android.toolchain.cmake

BUILD_DIR=android-arm64
mkdir -p $BUILD_DIR
cd $BUILD_DIR

cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN \
    -DANDROID_ABI=arm64-v8a \
    -DANDROID_PLATFORM=android-23 \
    -DWHISPER_NO_AVX=ON \
    -DWHISPER_NO_AVX2=ON \
    -DWHISPER_NO_FMA=ON

make -j4

关键参数说明:

  • 禁用AVX/AVX2:移动端CPU不支持这些指令集
  • 推荐使用arm64-v8a:兼容99%的现代设备
  • Android 23+:覆盖绝大多数用户

2.2 模型量化

原始模型较大,建议使用量化版本:

# 下载基础模型
./models/download-ggml-model.sh tiny.en

# 量化模型(Q4_0格式)
./quantize ./models/ggml-tiny.en.bin ./models/ggml-tiny.en-q4_0.bin q4_0

量化后模型大小对比:

| 模型类型 | 原始大小 | Q4_0量化后 | |----------|---------|-----------| | tiny | 75MB | 25MB | | small | 400MB | 130MB | | base | 500MB | 160MB |

3. 核心实现细节

3.1 JNI接口封装

Java层通过JNI调用Native代码:

// native-lib.cpp
extern "C" JNIEXPORT jlong JNICALL
Java_com_example_whisper_WhisperLib_initContext(
    JNIEnv *env, jobject thiz, jstring model_path) {
    const char *path = env->GetStringUTFChars(model_path, nullptr);

    struct whisper_context *ctx = whisper_init_from_file(path);
    env->ReleaseStringUTFChars(model_path, path);

    return reinterpret_cast<jlong>(ctx);
}

3.2 VAD集成方案

使用WebRTC的VAD模块进行语音检测:

// AudioProcessor.java
private boolean detectVoice(short[] audioBuffer) {
    VadInst vad = WebRtcVad.create(1); // Aggressiveness mode
    return WebRtcVad.process(vad, SAMPLE_RATE, audioBuffer, audioBuffer.length);
}

音频处理时序:

  1. AudioRecord采集16kHz PCM数据
  2. VAD检测语音活动
  3. 有语音时送入Whisper.cpp
  4. 无语音超300ms后触发识别结束

音频处理流程

4. 性能优化实战

在Redmi Note 10 Pro(骁龙732G)上测试结果:

| 模型 | 内存占用 | CPU占用 | 实时因子(RTF) | |--------|----------|---------|--------------| | tiny | 110MB | 15% | 0.8 | | small | 320MB | 35% | 1.5 | | base | 450MB | 55% | 2.3 |

优化建议:

  • 低端设备使用tiny模型
  • 增加2秒的音频缓存队列
  • 设置WHISPER_SINGLE_SEGMENT减少分段

5. 遇到的坑与解决方案

问题1:Android录音采样率不匹配

  • 现象:识别结果乱码
  • 原因:部分设备只支持44100Hz采样
  • 解决:强制重采样到16000Hz
// 使用libswresample重采样
swr_convert(swr_ctx, &resampled, out_samples, 
            (const uint8_t**)&audio, in_samples);

问题2:JNI引用泄漏

  • 现象:内存缓慢增长
  • 解决:使用FindClass后必须调用DeleteLocalRef
jclass clazz = env->FindClass("java/util/ArrayList");
// ...使用clazz...
env->DeleteLocalRef(clazz);

6. 延伸思考

当前方案已经能实现不错的识别效果,但还有优化空间:

  1. 能否结合微型LLM做实时语义理解?
  2. 如何利用NPU加速推理?
  3. 多语言混合识别怎么处理?

欢迎在评论区分享你的想法和实践经验。完整项目代码已开源在GitHub(链接见评论区)。

Logo

音视频技术社区,一个全球开发者共同探讨、分享、学习音视频技术的平台,加入我们,与全球开发者一起创造更加优秀的音视频产品!

更多推荐