安卓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
相关产品推荐
相关产品推荐

