限时福利领取


在Android端实现高质量的实时语音转文字(ASR)一直是个技术难点,尤其是平衡延迟、功耗和准确性这三要素。最近用Whisper模型做了个实践项目,踩了不少坑也积累了些经验,分享给有同样需求的开发者。

语音转文字示意图

一、为什么选择Whisper

对比过几个方案后发现:

  • TensorFlow Lite:生态完善但模型转换麻烦,动态形状支持差
  • MLKit:开箱即用但定制性差,且必须联网
  • Whisper:支持纯离线、多语言,50ms级延迟(tiny模型)

特别适合需要离线支持且对体积敏感的场景,比如海外出行翻译工具。

二、核心实现步骤

1. 音频流处理

用MediaCodec提取PCM数据时要注意采样率匹配,建议这样配置:

val format = MediaFormat().apply {
    setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_RAW)
    setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000) // Whisper标准输入
    setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1)   // 单声道
}

2. JNI层关键代码

Native侧用双缓冲避免阻塞音频线程:

// NOTE: 必须加锁防止读写冲突
std::mutex buf_mutex;
std::vector<float> input_buffer;

JNIEXPORT void JNICALL
Java_com_example_asr_AudioProcessor_pushPCM(JNIEnv* env, jclass clazz, jfloatArray arr) {
    jsize len = env->GetArrayLength(arr);
    std::unique_lock<std::mutex> lock(buf_mutex);
    input_buffer.insert(input_buffer.end(), arr_data, arr_data + len);
}

3. 模型量化实践

使用4-bit量化后模型体积从150MB降到45MB:

# 转换命令示例
python convert.py --model tiny --quantize q4_0 --output_dir android/app/src/main/assets

模型量化对比

三、性能优化技巧

  1. 内存管理
  2. 预分配Tensor内存避免运行时开销
  3. 使用AHardwareBuffer共享GPU内存

  4. 线程模型

  5. 音频采集:高优先级线程
  6. 模型推理:绑定到大核
  7. 结果回调:独立HandlerThread

  8. 功耗控制

    // 检测用户是否在说话再启动推理
    val vad = VoiceActivityDetector()
    if (vad.detect(buffer)) {
        executor.execute { whisper.process(buffer) }
    }

四、避坑指南

  • Android 12后台限制:需要添加FOREGROUND_SERVICE_MICROPHONE权限
  • TFLite动态形状:在build.gradle添加:
    android.defaultConfig.externalNativeBuild.cmake {
        arguments "-DANDROID_STL=c++_shared"
    }

实测在Pixel 6上能达到: - 平均延迟:230ms(含VAD) - 内存占用:峰值85MB - 持续运行功耗:≤5%/小时

完整代码已整理成Gist:点击查看(包含JNI绑定和模型加载示例)

最后建议:如果对延迟要求极高,可以考虑把VAD放在Native层做,能再减少20ms左右的跨语言调用开销。

Logo

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

更多推荐