You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin录制的WAV文件无法被scipy识别的问题求助

解决Android录制WAV文件无法被scipy.wavfile.read识别的问题

问题根源

你代码里的MediaRecorder配置存在核心错误:混淆了输出格式和音频编码格式,导致生成的文件并非标准WAV格式:

  1. 先设置了AMR_WB输出格式(这是压缩音频格式,不是WAV)
  2. 又错误地用AudioFormat.ENCODING_PCM_16BIT覆盖输出格式(这是编码格式常量,不属于MediaRecorder.OutputFormat枚举)

最终生成的文件要么是AMR压缩格式,要么是裸PCM音频数据——这两种都缺少WAV必须的RIFF文件头,所以scipy.wavfile.read会报错。

修复方案(API 26+)

直接使用Android原生支持的WAV输出格式,自动生成标准RIFF头:

val tmpRecordfile: File = 
    File.createTempFile("birdrec${System.currentTimeMillis()}", ".wav", requireContext().cacheDir)
if (tmpRecordfile.exists()) recordingFilePath = tmpRecordfile.path.toString()

recorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    MediaRecorder(requireContext())
} else {
    MediaRecorder()
}

recorder!!.apply {
    setAudioSource(MediaRecorder.AudioSource.MIC)
    // 设置标准WAV输出格式(API 26+支持)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        setOutputFormat(MediaRecorder.OutputFormat.WAVE)
    }
    setAudioSamplingRate(44100)
    setAudioChannels(1)
    // 配置PCM 16位编码(WAV标准编码)
    setAudioEncoder(MediaRecorder.AudioEncoder.PCM_16BIT)
    setOutputFile(recordingFilePath)
    prepare()
    start()
}

关键修正点:

  • 用MediaRecorder.OutputFormat.WAVE替代错误的输出格式配置,这会自动生成带RIFF头的标准WAV文件
  • 用MediaRecorder.AudioEncoder.PCM_16BIT设置音频编码,与WAV格式匹配

低版本兼容方案(API < 26)

Android 8.0以下不直接支持WAV输出格式,需要录制裸PCM数据后手动添加RIFF头:

// 1. 用AudioRecord录制PCM裸数据
val audioFormat = AudioFormat.Builder()
    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
    .setSampleRate(44100)
    .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
    .build()

val minBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)
val audioRecord = AudioRecord(
    MediaRecorder.AudioSource.MIC,
    44100,
    AudioFormat.CHANNEL_IN_MONO,
    AudioFormat.ENCODING_PCM_16BIT,
    minBufferSize * 2
)

val pcmFile = File(requireContext().cacheDir, "temp.pcm")
val outputStream = FileOutputStream(pcmFile)
val buffer = ByteArray(minBufferSize)

audioRecord.startRecording()
while (isRecording) {
    val read = audioRecord.read(buffer, 0, buffer.size)
    if (read > 0) outputStream.write(buffer, 0, read)
}
audioRecord.stop()
outputStream.close()

// 2. 将PCM转换为带RIFF头的WAV文件
convertPcmToWav(pcmFile, tmpRecordfile, 44100, 1, 16)
pcmFile.delete()

// 转换工具函数
fun convertPcmToWav(pcmFile: File, wavFile: File, sampleRate: Int, channels: Int, bitsPerSample: Int) {
    val pcmSize = pcmFile.length().toInt()
    val wavOut = FileOutputStream(wavFile)

    // 写入RIFF头
    wavOut.write(byteArrayOf('R'.code.toByte(), 'I'.code.toByte(), 'F'.code.toByte(), 'F'.code.toByte()))
    wavOut.write((36 + pcmSize).toLittleEndianBytes())
    wavOut.write(byteArrayOf('W'.code.toByte(), 'A'.code.toByte(), 'V'.code.toByte(), 'E'.code.toByte()))

    // 写入fmt子块
    wavOut.write(byteArrayOf('f'.code.toByte(), 'm'.code.toByte(), 't'.code.toByte(), ' '.code.toByte()))
    wavOut.write(16.toLittleEndianBytes()) // PCM格式块大小
    wavOut.write(1.toLittleEndianBytes()) // PCM编码标识
    wavOut.write(channels.toLittleEndianBytes())
    wavOut.write(sampleRate.toLittleEndianBytes())
    val byteRate = sampleRate * channels * bitsPerSample / 8
    wavOut.write(byteRate.toLittleEndianBytes())
    val blockAlign = channels * bitsPerSample / 8
    wavOut.write(blockAlign.toLittleEndianBytes())
    wavOut.write(bitsPerSample.toLittleEndianBytes())

    // 写入data子块
    wavOut.write(byteArrayOf('d'.code.toByte(), 'a'.code.toByte(), 't'.code.toByte(), 'a'.code.toByte()))
    wavOut.write(pcmSize.toLittleEndianBytes())

    // 写入PCM数据
    val pcmIn = FileInputStream(pcmFile)
    val buffer = ByteArray(1024)
    var read: Int
    while (pcmIn.read(buffer).also { read = it } != -1) {
        wavOut.write(buffer, 0, read)
    }

    pcmIn.close()
    wavOut.close()
}

// 扩展函数:Int转小端字节数组
fun Int.toLittleEndianBytes(): ByteArray {
    return byteArrayOf(
        (this and 0xFF).toByte(),
        ((this shr 8) and 0xFF).toByte(),
        ((this shr 16) and 0xFF).toByte(),
        ((this shr 24) and 0xFF).toByte()
    )
}

验证方法

  1. 录制完成后,用记事本打开WAV文件,开头应能看到RIFF字符串
  2. 上传到Flask服务器后,scipy.wavfile.read可正常读取采样率和音频数据

额外注意事项

  • 上传代码中设置的audio/wav Content-Type是正确的,无需修改
  • 服务器端直接保存上传文件的逻辑没问题,不要修改文件原始内容

内容的提问来源于stack exchange,提问作者Luis Cáceres

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 21:55:25