Android(Kotlin):使用AudioRecord转WAV格式(无需存储文件)
问题需求
我使用AudioRecord录制音频,需要将录制内容以可播放的WAV格式保存为内存数组/变量,不写入文件。由于录制同时要运行语音识别,无法使用MediaRecorder。现有方案依赖文件存储,求全程内存操作的实现,最终得到音频数组传递给Flutter。
解决方案
核心思路:录制时将PCM数据缓存到内存流,录制结束后直接在内存中生成WAV头并与PCM数据合并,全程无文件IO操作。
1. 内存缓存版录制函数
替换文件输出流为ByteArrayOutputStream,直接在内存中缓存录制的PCM数据:
private val pcmDataStream = ByteArrayOutputStream() private val stopFlag = AtomicBoolean(false) private fun startRecording() { val sampleRate = 16000 val frameLength = 512 val minBufferSize = AudioRecord.getMinBufferSize( sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ) val bufferSize = (sampleRate / 2).coerceAtLeast(minBufferSize) var audioRecord: AudioRecord? = null val buffer = ShortArray(frameLength) try { audioRecord = AudioRecord( MediaRecorder.AudioSource.MIC, sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize ) audioRecord.startRecording() pcmDataStream.reset() // 重置缓存流,清理旧数据 while (!stopFlag.get()) { val readLength = audioRecord.read(buffer, 0, buffer.size) if (readLength > 0) { // 仅转换并写入实际读取到的有效数据 val bufferByte = shortToByte(buffer.copyOf(readLength)) pcmDataStream.write(bufferByte) } } } catch (e: Exception) { e.printStackTrace() } finally { audioRecord?.stop() audioRecord?.release() } }
2. 内存版WAV头生成函数
直接基于内存中的PCM数据生成完整的WAV格式字节数组,无需文件操作:
// 将内存中的PCM数据转换为WAV格式字节数组 private fun pcmToWaveInMemory(pcmData: ByteArray, sampleRate: Int): ByteArray { val wavStream = ByteArrayOutputStream() val dataSize = pcmData.size // 写入WAV头信息 wavStream.write("RIFF".toByteArray()) writeIntToStream(wavStream, 36 + dataSize) // ChunkSize wavStream.write("WAVE".toByteArray()) wavStream.write("fmt ".toByteArray()) writeIntToStream(wavStream, 16) // Subchunk1Size(PCM固定为16) writeShortToStream(wavStream, 1.toShort()) // AudioFormat(PCM为1) writeShortToStream(wavStream, 1.toShort()) // 声道数(单声道) writeIntToStream(wavStream, sampleRate) // 采样率 writeIntToStream(wavStream, sampleRate * 2) // 字节率(采样率*声道*位深/8) writeShortToStream(wavStream, 2.toShort()) // 块对齐(声道*位深/8) writeShortToStream(wavStream, 16.toShort()) // 位深(16BIT) wavStream.write("data".toByteArray()) writeIntToStream(wavStream, dataSize) // 数据块大小 // 写入PCM原始数据 wavStream.write(pcmData) return wavStream.toByteArray() } // 辅助函数:以小端序写入Int值 private fun writeIntToStream(stream: ByteArrayOutputStream, value: Int) { stream.write(value shr 0) stream.write(value shr 8) stream.write(value shr 16) stream.write(value shr 24) } // 辅助函数:以小端序写入Short值 private fun writeShortToStream(stream: ByteArrayOutputStream, value: Short) { val intValue = value.toInt() stream.write(intValue shr 0) stream.write(intValue shr 8) } // 复用原有Short数组转ByteArray函数 fun shortToByte(shortArray: ShortArray): ByteArray { val buffer = ByteBuffer.allocate(shortArray.size * 2) buffer.order(ByteOrder.LITTLE_ENDIAN) buffer.asShortBuffer().put(shortArray) return buffer.array() }
3. 获取WAV数组并传递给Flutter
停止录制后生成WAV数组,通过MethodChannel传递给Flutter:
private fun stopRecordingAndGetWav(): ByteArray { stopFlag.set(true) // 若录制在异步线程执行,需等待线程结束(可根据实际逻辑调整) // 示例:使用CountDownLatch或协程等待 val pcmData = pcmDataStream.toByteArray() return pcmToWaveInMemory(pcmData, 16000) } // 通过MethodChannel将WAV数组传递给Flutter private fun sendWavToFlutter(wavData: ByteArray) { // 假设已初始化MethodChannel methodChannel.invokeMethod("receiveAudioData", wavData) }
关键注意事项
- 录制时长较长时需关注内存占用,可添加最大录制时长限制或分段处理逻辑
- 确保只写入实际读取到的有效音频数据(
buffer.copyOf(readLength)),避免无效空数据 - 若录制在子线程执行,需处理线程同步问题,确保缓存流数据完整
内容的提问来源于stack exchange,提问作者kemmel
相关产品推荐
相关产品推荐

