Android 14音频录制异常:录制时长与播放速度问题排查
解决Android 14音频录制速度异常问题
核心问题分析
你遇到的录制时长缩短、播放倍速问题,大概率是以下几个原因导致:
- 硬编码的AudioRecord缓冲区大小不符合Android 14的要求,导致音频采集节奏异常;
- MediaCodec编码时未传入正确的时间戳,编码器无法正确计算音频时长;
- AudioRecord实际使用的采样率与配置值不一致,Android 14可能自动降级采样率,但编码仍用原配置,导致播放速度错误。
具体修复步骤
1. 使用系统推荐的最小缓冲区大小初始化AudioRecord
硬编码bufferSize * 10可能在Android 14中导致缓冲区过小,引发采集速度异常。替换为AudioRecord.getMinBufferSize()计算正确的缓冲区:
private AudioRecord createAudioRecord() { int minBufferSize = AudioRecord.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ); if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) { throw new RuntimeException("Unsupported audio parameters"); } // 额外扩大缓冲区避免丢帧 int bufferSize = minBufferSize * 2; AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize ); // 原有的NoiseSuppressor、AutomaticGainControl逻辑保持不变 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (android.media.audiofx.NoiseSuppressor.isAvailable()) { android.media.audiofx.NoiseSuppressor noiseSuppressor = android.media.audiofx.NoiseSuppressor .create(audioRecord.getAudioSessionId()); if (noiseSuppressor != null) { noiseSuppressor.setEnabled(true); } } if (android.media.audiofx.AutomaticGainControl.isAvailable()) { android.media.audiofx.AutomaticGainControl automaticGainControl = android.media.audiofx.AutomaticGainControl .create(audioRecord.getAudioSessionId()); if (automaticGainControl != null) { automaticGainControl.setEnabled(true); } } } return audioRecord; }
2. 给MediaCodec输入数据时附加正确的时间戳
在run()方法的循环读取PCM数据环节,必须计算并传入基于采样数的时间戳,否则编码器会错误生成时长信息。示例代码:
@Override public void run() { long presentationTimeUs = 0; byte[] pcmBuffer = new byte[audioRecord.getBufferSizeInFrames() * 2]; // 16bit单声道,每个帧占2字节 while (!isInterrupted()) { int readSize = audioRecord.read(pcmBuffer, 0, pcmBuffer.length); if (readSize <= 0) { break; } // 计算当前读取的采样数:16bit采样,每个采样占2字节 int sampleCount = readSize / 2; // 计算时间戳:(采样数 * 1000000) / 采样率 presentationTimeUs += (sampleCount * 1000000L) / SAMPLE_RATE; // 将PCM数据喂给MediaCodec int inputBufferId = mediaCodec.dequeueInputBuffer(-1); if (inputBufferId >= 0) { ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferId); inputBuffer.clear(); inputBuffer.put(pcmBuffer, 0, readSize); mediaCodec.queueInputBuffer( inputBufferId, 0, readSize, presentationTimeUs, 0 ); } // 处理MediaCodec输出数据,写入文件 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, -1); while (outputBufferId >= 0) { ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId); if (outputBuffer != null && bufferInfo.size > 0) { // 写入ADTS头 byte[] adtsHeader = createAdtsHeader(bufferInfo.size); outputStream.write(adtsHeader); // 写入编码后的数据 byte[] data = new byte[bufferInfo.size]; outputBuffer.get(data); outputStream.write(data); outputBuffer.clear(); } mediaCodec.releaseOutputBuffer(outputBufferId, false); outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, -1); } } // 结束录制,发送结束标志 int inputBufferId = mediaCodec.dequeueInputBuffer(-1); if (inputBufferId >= 0) { mediaCodec.queueInputBuffer( inputBufferId, 0, 0, presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM ); } // 处理剩余输出数据 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, -1); while (outputBufferId >= 0) { if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { break; } ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId); if (outputBuffer != null && bufferInfo.size > 0) { byte[] adtsHeader = createAdtsHeader(bufferInfo.size); outputStream.write(adtsHeader); byte[] data = new byte[bufferInfo.size]; outputBuffer.get(data); outputStream.write(data); outputBuffer.clear(); } mediaCodec.releaseOutputBuffer(outputBufferId, false); outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, -1); } // 释放资源 mediaCodec.stop(); mediaCodec.release(); audioRecord.stop(); audioRecord.release(); outputStream.close(); }
3. 验证并同步AudioRecord实际采样率到MediaCodec
Android 14可能会自动调整不支持的采样率,需确认AudioRecord实际使用的采样率,并同步到MediaCodec配置:
private AudioRecord createAudioRecord() { int minBufferSize = AudioRecord.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ); if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) { throw new RuntimeException("Unsupported audio parameters"); } int bufferSize = minBufferSize * 2; AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize ); // 检查实际采样率 int actualSampleRate = audioRecord.getSampleRate(); if (actualSampleRate != SAMPLE_RATE) { Log.w(TAG, "Sample rate fallback to: " + actualSampleRate); SAMPLE_RATE = actualSampleRate; SAMPLE_RATE_INDEX = getSampleRateIndex(actualSampleRate); } // 原有的音频效果逻辑保持不变 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (android.media.audiofx.NoiseSuppressor.isAvailable()) { android.media.audiofx.NoiseSuppressor noiseSuppressor = android.media.audiofx.NoiseSuppressor .create(audioRecord.getAudioSessionId()); if (noiseSuppressor != null) { noiseSuppressor.setEnabled(true); } } if (android.media.audiofx.AutomaticGainControl.isAvailable()) { android.media.audiofx.AutomaticGainControl automaticGainControl = android.media.audiofx.AutomaticGainControl .create(audioRecord.getAudioSessionId()); if (automaticGainControl != null) { automaticGainControl.setEnabled(true); } } } return audioRecord; } // 根据采样率获取对应的AAC索引 private int getSampleRateIndex(int sampleRate) { switch (sampleRate) { case 96000: return 0; case 88200: return 1; case 64000: return 2; case 48000: return 3; case 44100: return 4; case 32000: return 5; case 24000: return 6; case 22050: return 7; case 16000: return 8; case 12000: return 9; case 11025: return 10; case 8000: return 11; default: return 4; // 默认44100 } }
然后在createMediaCodec()方法中,使用更新后的SAMPLE_RATE配置MediaFormat:
private MediaCodec createMediaCodec() throws IOException { MediaCodec mediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm"); MediaFormat mediaFormat = new MediaFormat(); mediaFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm"); mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, audioRecord.getBufferSizeInFrames() * 2); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); try { mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); } catch (Exception e) { e.printStackTrace(); mediaCodec.release(); throw new IOException(e); } return mediaCodec; }
额外注意事项
- 确保已申请
RECORD_AUDIO权限,并在运行时动态授权; - 录制线程需在后台线程运行,避免阻塞主线程;
- 释放资源时需按顺序:先停止编码器,再停止AudioRecord,最后关闭文件输出流。
内容的提问来源于stack exchange,提问作者iOS Developer
相关产品推荐
相关产品推荐

