Kotlin录制的WAV文件无法被scipy识别的问题求助
解决Android录制WAV文件无法被scipy.wavfile.read识别的问题
问题根源
你代码里的MediaRecorder配置存在核心错误:混淆了输出格式和音频编码格式,导致生成的文件并非标准WAV格式:
- 先设置了
AMR_WB输出格式(这是压缩音频格式,不是WAV) - 又错误地用
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() ) }
验证方法
- 录制完成后,用记事本打开WAV文件,开头应能看到
RIFF字符串 - 上传到Flask服务器后,
scipy.wavfile.read可正常读取采样率和音频数据
额外注意事项
- 上传代码中设置的
audio/wavContent-Type是正确的,无需修改 - 服务器端直接保存上传文件的逻辑没问题,不要修改文件原始内容
内容的提问来源于stack exchange,提问作者Luis Cáceres
相关产品推荐
相关产品推荐

