AudioRecord与AudioTrack 32位编码无法正常工作问题求助
我来帮你拆解一下问题的核心,并给出针对性的解决方案——你的问题主要出在Android AudioTrack的API使用错误和跨平台格式兼容性细节上,咱们一步步来解决:
核心问题分析
AudioTrack.write()返回ERROR_INVALID_OPERATION的直接原因:
当你使用AudioFormat.ENCODING_PCM_FLOAT(32位浮点PCM)时,调用write(byte[])方法是完全错误的!这个重载方法仅适用于8位/16位整数PCM编码,浮点PCM必须使用write(float[])的重载版本,否则系统会直接返回无效操作错误。跨平台互播的格式一致性问题:
浏览器和Android的32位PCM都是浮点格式,但要确保两端的字节序一致(你已经用了小端序,这点没问题),以及通道交错逻辑统一(双声道必须是左-右-左-右的顺序)。另外,Android 5.0(API 21)才开始支持PCM_FLOAT,低版本设备需要降级处理。
具体解决方案
1. 修复Android端AudioTrack的写入逻辑
把原来直接写入字节数组的代码,改成根据编码格式选择对应的写入方法:
// 假设buffer是从WebSocket接收的字节数组 int result; if (metadata.getEncoding() == AudioFormat.ENCODING_PCM_FLOAT) { // 32位浮点PCM:将字节数组转换为float数组 float[] floatBuffer = new float[buffer.length / 4]; // 每个float占4字节 ByteBuffer.wrap(buffer) .order(ByteOrder.LITTLE_ENDIAN) .asFloatBuffer() .get(floatBuffer); result = audioTrack.write(floatBuffer, 0, floatBuffer.length, AudioTrack.WRITE_BLOCKING); } else { // 8/16位整数PCM:直接写入字节数组 result = audioTrack.write(buffer, 0, buffer.length, AudioTrack.WRITE_BLOCKING); } // 错误判断逻辑保持不变 if(result == AudioTrack.ERROR_BAD_VALUE){ System.out.println("ERROR: bad value"); }else if (result == AudioTrack.ERROR_DEAD_OBJECT){ System.out.println("ERROR: dead object"); }else if (result == AudioTrack.ERROR_INVALID_OPERATION){ System.out.println("ERROR: invalid operation"); }else if (result == AudioTrack.ERROR){ System.out.println("ERROR: ??"); }else{ System.out.println("Successfully written to buffer!"); }
2. 添加API版本兼容性检查
在初始化AudioTrack前,先判断设备版本,避免在Android 5.0以下设备使用PCM_FLOAT:
private void initAudioTrack() { // 检查版本,不支持PCM_FLOAT则降级到16位 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && metadata.getEncoding() == AudioFormat.ENCODING_PCM_FLOAT) { Log.w("AudioStream", "32位浮点PCM不支持当前设备,自动降级到16位PCM"); metadata = new AudioStreamMetadata( metadata.getSampleRate(), metadata.getBufferSize(), metadata.getChannels(), 16 // 降级到16位 ); } int channelConfig = metadata.getChannels(false); // false表示输出通道 int audioFormat = metadata.getEncoding(); int sampleRate = metadata.getSampleRate(); int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, minBufferSize, AudioTrack.MODE_STREAM ); audioTrack.play(); }
3. 确保浏览器端的格式匹配
浏览器发送32位浮点PCM时,要保证是小端字节序的交错双声道数据:
// 从Web Audio API的AudioBuffer获取双声道数据并交错 function interleaveChannels(leftChannel, rightChannel) { const interleaved = new Float32Array(leftChannel.length * 2); for (let i = 0; i < leftChannel.length; i++) { interleaved[i * 2] = leftChannel[i]; interleaved[i * 2 + 1] = rightChannel[i]; } return interleaved; } // 发送到WebSocket const audioBuffer = await audioContext.decodeAudioData(blob); const left = audioBuffer.getChannelData(0); const right = audioBuffer.getChannelData(1); const interleavedData = interleaveChannels(left, right); socket.send(new Uint8Array(interleavedData.buffer));
4. 验证Android录制端的逻辑
你当前的录制代码是正确的,读取float数组后转换成小端字节序发送,保持这个逻辑即可:
float[] data = new float[metadata.getBufferSize()]; recorder.read(data, 0, data.length, AudioRecord.READ_BLOCKING); byte[] output = new byte[data.length * metadata.getBytesPerSample()]; ByteBuffer.wrap(output).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().put(data); socket.send(ByteString.of(output));
补充说明
关于你提到的Android能播放32位WAV文件但不确定是否降采样:系统的MediaPlayer会自动处理格式适配(比如降采样到设备支持的格式),但AudioTrack是直接播放原始PCM数据,必须严格匹配参数,这就是为什么WAV能播但WebSocket流不行的原因——MediaPlayer帮你做了格式转换,而AudioTrack没有。
按照上面的修改,你应该能在Android 5.0+设备上实现32位PCM的低延迟互播,同时兼容低版本设备降级到16位。
内容的提问来源于stack exchange,提问作者Merve Sahin

