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

安卓Kotlin:麦克风录音转WAV及Socket传输与采样率修改问题

解决方案

问题分析

你当前的代码指定了MediaRecorder.OutputFormat.AMR_NB和MediaRecorder.AudioEncoder.AMR_NB参数,所以录制的是AMR格式的压缩音频,服务器自然收到AMR数据。要输出WAV格式,需要录制未压缩的PCM音频并添加标准WAV文件头;同时要设置48000采样率,需调整录制参数并确保设备支持该采样率。

基于MediaRecorder的修改方案(生成完整WAV文件)

步骤1:调整MediaRecorder配置

将输出格式改为原始PCM,同时设置48000采样率、声道数和位深:

val sampleRate = 48000
val channels = 1 // 单声道,可改为2实现立体声
val bitDepth = 16

val descriptors = ParcelFileDescriptor.createPipe()
val parcelRead = ParcelFileDescriptor(descriptors[0])
val parcelWrite = ParcelFileDescriptor(descriptors[1])

val inputStream: InputStream = ParcelFileDescriptor.AutoCloseInputStream(parcelRead)

val recorder = MediaRecorder()
recorder.setAudioSource(MediaRecorder.AudioSource.MIC)
recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_FORMAT) // 输出原始PCM数据
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) // 对应PCM编码
recorder.setAudioSamplingRate(sampleRate)
recorder.setAudioChannels(channels)
recorder.setAudioEncodingBitRate(bitDepth * sampleRate * channels) // 计算比特率
recorder.setOutputFile(parcelWrite.fileDescriptor)
recorder.prepare()

步骤2:生成WAV文件头

WAV格式由44字节的标准文件头+PCM数据组成,定义生成头的工具方法:

fun generateWavHeader(sampleRate: Int, channels: Int, bitDepth: Int, pcmSize: Int): ByteArray {
    val byteRate = sampleRate * channels * bitDepth / 8
    val header = ByteArray(44)

    // RIFF标识
    header[0] = 'R'.code.toByte()
    header[1] = 'I'.code.toByte()
    header[2] = 'F'.code.toByte()
    header[3] = 'F'.code.toByte()
    // 文件总大小:44字节头 + PCM数据大小
    val totalSize = 36 + pcmSize
    header[4] = (totalSize and 0xff).toByte()
    header[5] = (totalSize shr 8 and 0xff).toByte()
    header[6] = (totalSize shr 16 and 0xff).toByte()
    header[7] = (totalSize shr 24 and 0xff).toByte()
    // WAVE标识
    header[8] = 'W'.code.toByte()
    header[9] = 'A'.code.toByte()
    header[10] = 'V'.code.toByte()
    header[11] = 'E'.code.toByte()
    // fmt子块
    header[12] = 'f'.code.toByte()
    header[13] = 'm'.code.toByte()
    header[14] = 't'.code.toByte()
    header[15] = ' '.code.toByte()
    header[16] = 16 // PCM格式子块大小
    header[17] = 0
    header[18] = 0
    header[19] = 0
    header[20] = 1 // PCM音频格式标识
    header[21] = 0
    // 声道数
    header[22] = channels.toByte()
    header[23] = 0
    // 采样率
    header[24] = (sampleRate and 0xff).toByte()
    header[25] = (sampleRate shr 8 and 0xff).toByte()
    header[26] = (sampleRate shr 16 and 0xff).toByte()
    header[27] = (sampleRate shr 24 and 0xff).toByte()
    // 字节率
    header[28] = (byteRate and 0xff).toByte()
    header[29] = (byteRate shr 8 and 0xff).toByte()
    header[30] = (byteRate shr 16 and 0xff).toByte()
    header[31] = (byteRate shr 24 and 0xff).toByte()
    // 块对齐
    val blockAlign = channels * bitDepth / 8
    header[32] = blockAlign.toByte()
    header[33] = 0
    // 位深度
    header[34] = bitDepth.toByte()
    header[35] = 0
    // data子块
    header[36] = 'd'.code.toByte()
    header[37] = 'a'.code.toByte()
    header[38] = 't'.code.toByte()
    header[39] = 'a'.code.toByte()
    // PCM数据大小
    header[40] = (pcmSize and 0xff).toByte()
    header[41] = (pcmSize shr 8 and 0xff).toByte()
    header[42] = (pcmSize shr 16 and 0xff).toByte()
    header[43] = (pcmSize shr 24 and 0xff).toByte()

    return header
}

步骤3:录制PCM并封装为WAV

recorder.start()

// 先读取PCM数据到临时流
val pcmOutputStream = ByteArrayOutputStream()
var read: Int
val data = ByteArray(16384)
while ((inputStream.read(data, 0, data.size).also { read = it } != -1)) {
    pcmOutputStream.write(data, 0, read)
}
pcmOutputStream.flush()

// 将PCM数据封装为WAV格式
val pcmData = pcmOutputStream.toByteArray()
val wavHeader = generateWavHeader(sampleRate, channels, bitDepth, pcmData.size)
val byteArrayOutputStream = ByteArrayOutputStream()
byteArrayOutputStream.write(wavHeader)
byteArrayOutputStream.write(pcmData)
byteArrayOutputStream.flush()

// 停止录制并释放资源
recorder.stop()
recorder.release()

实时传输优化方案(持续录制+Socket发送)

如果需要持续录制并实时传输音频,上述方案不适用(因为WAV头需要提前知道总数据大小),推荐以下两种实用方式:

方式1:实时传输PCM,服务器端封装WAV

先将音频参数(采样率、声道数、位深)发送给服务器,再实时传输PCM数据,服务器收到后自行生成WAV头并封装:

val sampleRate = 48000
val channels = AudioFormat.CHANNEL_IN_MONO
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
val bufferSize = AudioRecord.getMinBufferSize(sampleRate, channels, audioFormat) * 2

// 初始化AudioRecord
val audioRecord = AudioRecord(
    MediaRecorder.AudioSource.MIC,
    sampleRate,
    channels,
    audioFormat,
    bufferSize
)

// 连接Socket并发送音频参数
val socket = Socket("服务器地址", 端口号)
val outputStream = socket.getOutputStream()
// 示例:简单发送参数,实际可自定义协议
outputStream.write(sampleRate.toByteArray())
outputStream.write(if (channels == AudioFormat.CHANNEL_IN_MONO) 1 else 2)
outputStream.write(16)

// 开始实时录制并发送数据
audioRecord.startRecording()
val data = ByteArray(bufferSize)
var isRecording = true
while (isRecording) {
    val read = audioRecord.read(data, 0, data.size)
    if (read > 0) {
        outputStream.write(data, 0, read)
        outputStream.flush()
    }
}

// 停止录制并关闭资源
audioRecord.stop()
audioRecord.release()
socket.close()

方式2:传输压缩音频(如AAC),服务器转WAV

如果担心PCM数据量过大,可录制AAC格式实时传输,服务器接收后转码为WAV。修改MediaRecorder配置为:

recorder.setAudioSource(MediaRecorder.AudioSource.MIC)
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
recorder.setAudioSamplingRate(48000)
recorder.setAudioChannels(1)
recorder.setAudioEncodingBitRate(128000) // 设置AAC比特率

注意事项

  • 必须申请RECORD_AUDIO权限,在AndroidManifest.xml中添加:<uses-permission android:name="android.permission.RECORD_AUDIO"/>,并在运行时动态请求权限。
  • 48000采样率需设备支持,可通过AudioRecord.getMinBufferSize(sampleRate, channels, audioFormat)验证,返回值不为AudioRecord.ERROR_BAD_VALUE则支持。
  • Socket传输时需处理网络异常,避免程序崩溃。

内容的提问来源于stack exchange,提问作者Karson Parami

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 16:10:33