Android Capacitor插件音频录制崩溃求助:releaseBuffer越界问题
问题描述
我不擅长Android开发,目前正在开发一个Capacitor插件,通过socket.io流式传输麦克风录制的音频。功能整体正常,但偶尔会收到崩溃报告,报错信息如下:
releaseBuffer: mUnreleased out of range, !(stepCount:480 <= mUnreleased:128 <= mFrameCount:1440), BufferSizeInFrames:1440
我理解报错含义,但不知道怎么解决,恳请帮忙。触发错误的函数代码如下:
@PluginMethod() public void startRecording(PluginCall call) { try { if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_RECORD_REQUEST_CODE); call.reject("Permission is required for recording audio."); return; } vibratePhone(); sendUniqueId = generateRandomString(10); JSObject ret = new JSObject(); ret.put("companyId", companyId); ret.put("name", name); ret.put("userId", userId); ret.put("uid", sendUniqueId); socket.emit("speaking", ret); int channelConfig = AudioFormat.CHANNEL_IN_MONO; int audioFormat = AudioFormat.ENCODING_PCM_16BIT; int minBufferSize = AudioRecord.getMinBufferSize(pcmSampleRate, channelConfig, audioFormat); audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, pcmSampleRate, channelConfig, audioFormat, minBufferSize); if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { call.reject("Failed to initialize audio recording."); return; } new Thread(() -> { audioRecord.startRecording(); isRecording = true; byte[] buffer = new byte[minBufferSize]; int chunksToSkip = 6; // Number of initial chunks to skip int chunksProcessed = 0; // Counter for processed chunks long startTime = System.currentTimeMillis(); long totalDataSent = 0; while (isRecording && audioRecord != null && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { int bytesRead = audioRecord.read(buffer, 0, buffer.length); if (bytesRead > 0) { chunksProcessed++; // Increment the chunks processed counter if (chunksProcessed > chunksToSkip) { byte[] actualData = Arrays.copyOf(buffer, bytesRead); try { if (actualData.length > 0) { Constants.FrameSize frameSize2 = Constants.FrameSize.Companion.fromValue(buffer.length / 2); byte[] encoded = codec.encode(actualData, frameSize2); totalDataSent += encoded.length; if (encoded != null) { JSObject ret2 = new JSObject(); ret2.put("data", encoded); ret2.put("frameSize", buffer.length / 2); ret2.put("companyId", companyId); ret2.put("name", name); ret2.put("userId", userId); socket.emit("send-chat-data", ret2); } else { JSObject errorLog = new JSObject(); errorLog.put("error", "CODEC.Encoded is null"); errorLog.put("type", "startRecording"); socket.emit("ptt-error-logging", errorLog); } long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) >= 60000) { // 60,000 milliseconds = 1 minute Log.d("DataSent", "Total data sent in last minute: " + (totalDataSent / 1024) + " KB"); // Reset counters startTime = currentTime; totalDataSent = 0; } } } catch (Exception e) { JSObject errorLog = new JSObject(); errorLog.put("error", e); errorLog.put("type", "startRecording"); socket.emit("ptt-error-logging", errorLog); stopRecordingFromSocket(); } } } } }).start(); call.success(); } catch (Exception e) { JSObject errorLog = new JSObject(); errorLog.put("error", e); errorLog.put("type", "startRecording-whole"); socket.emit("ptt-error-logging", errorLog); stopRecordingFromSocket(); } }
我尝试过调整缓冲区大小,但似乎毫无效果,感谢各位的帮助!
解决方案
这个错误是Android AudioRecord内部缓冲区管理异常,通常由读取/释放不同步、缓冲区设置不合理或线程阻塞导致,以下是针对性修复方案:
1. 合理设置AudioRecord缓冲区大小
getMinBufferSize返回的是底层要求的最小缓冲区,实际使用时建议用其整数倍(比如2倍),避免底层缓冲区溢出:
int minBufferSize = AudioRecord.getMinBufferSize(pcmSampleRate, channelConfig, audioFormat); // 使用最小缓冲区的2倍作为实际缓冲区,提升容错性 int bufferSize = minBufferSize * 2; audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, pcmSampleRate, channelConfig, audioFormat, bufferSize); // 同时更新录音线程内的缓冲区 byte[] buffer = new byte[bufferSize];
2. 修复帧大小计算逻辑
当前用buffer.length / 2作为帧大小,但实际读取的字节数是bytesRead,应该用bytesRead / 2(16位PCM每个样本占2字节),避免传递错误参数给编解码器:
// 替换原frameSize相关代码 Constants.FrameSize frameSize2 = Constants.FrameSize.Companion.fromValue(bytesRead / 2); ret2.put("frameSize", bytesRead / 2);
3. 保证线程安全的录制停止逻辑
- 给
isRecording添加volatile修饰,确保线程间可见性:
private volatile boolean isRecording = false;
- 完善
stopRecordingFromSocket方法,确保正确停止并释放AudioRecord:
public void stopRecordingFromSocket() { isRecording = false; if (audioRecord != null) { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } audioRecord.release(); audioRecord = null; } }
4. 避免录音线程被阻塞
当前录音线程中直接执行编码和socket发送操作,这些操作可能阻塞线程,导致AudioRecord读取不及时。建议将编码和发送逻辑放到单独的线程池:
// 提前初始化单线程池(在类中定义) private ExecutorService codecExecutor = Executors.newSingleThreadExecutor(); // 替换录音线程内的编码发送代码 if (chunksProcessed > chunksToSkip) { byte[] actualData = Arrays.copyOf(buffer, bytesRead); // 提交到线程池处理 codecExecutor.execute(() -> { try { if (actualData.length > 0) { Constants.FrameSize frameSize2 = Constants.FrameSize.Companion.fromValue(bytesRead / 2); byte[] encoded = codec.encode(actualData, frameSize2); totalDataSent += encoded.length; if (encoded != null) { JSObject ret2 = new JSObject(); ret2.put("data", encoded); ret2.put("frameSize", bytesRead / 2); ret2.put("companyId", companyId); ret2.put("name", name); ret2.put("userId", userId); socket.emit("send-chat-data", ret2); } else { JSObject errorLog = new JSObject(); errorLog.put("error", "CODEC.Encoded is null"); errorLog.put("type", "startRecording"); socket.emit("ptt-error-logging", errorLog); } long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) >= 60000) { Log.d("DataSent", "Total data sent in last minute: " + (totalDataSent / 1024) + " KB"); startTime = currentTime; totalDataSent = 0; } } } catch (Exception e) { JSObject errorLog = new JSObject(); errorLog.put("error", e); errorLog.put("type", "startRecording"); socket.emit("ptt-error-logging", errorLog); stopRecordingFromSocket(); } }); }
5. 处理AudioRecord.read的异常返回值
read方法可能返回错误码,需要及时处理避免异常扩散:
int bytesRead = audioRecord.read(buffer, 0, buffer.length); if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION || bytesRead == AudioRecord.ERROR_BAD_VALUE) { stopRecordingFromSocket(); break; } else if (bytesRead > 0) { // 正常处理逻辑 }
内容的提问来源于stack exchange,提问作者Prawnster
相关产品推荐
相关产品推荐

