.NET 7 MAUI安卓端AudioRecord重启录制无音频流问题
.NET 7 MAUI Android AudioRecord停止后无法重启录制问题
问题场景
在.NET 7 MAUI应用中,通过依赖注入将封装Android AudioRecord的AndroidAudioRecordService注入ViewModel,需实现:
- 点击启动按钮开始麦克风音频捕获
- 点击停止按钮暂停录制
- 再次点击启动按钮恢复录制
问题现象:首次启动录制可正常获取音频流,但停止后再次启动,无法捕获任何音频。
已尝试方案
- 将服务注册为Transient、Scoped、Singleton三种生命周期
- 在构造函数和每次点击启动按钮时初始化AudioRecord
- 定位到问题与
audioRecord.Stop()方法相关:注释该方法后功能正常;但直接在ViewModel中实现AudioRecord逻辑时,调用.Stop()可正常工作
问题代码
#if ANDROID using Android.Media; namespace Neuron.Nexus.Services; public interface IAndroidAudioRecordService : IDisposable { void StartRecording(); void StopRecording(); Task<(byte[] buffer, int bytesRead)> GetAudioStream(); bool IsRecording { get; } } public class AndroidAudioRecordService : IAndroidAudioRecordService { private AudioRecord audioRecord; private readonly int sampleRate = 44100; private readonly int bufferSize = 1024; private readonly ChannelIn channelConfig = ChannelIn.Mono; private readonly Encoding audioFormat = Encoding.Pcm16bit; private readonly byte[] audioBuffer; public bool IsRecording { get; private set; } public AndroidAudioRecordService() { audioBuffer = new byte[bufferSize]; } public void StartRecording() { if (audioRecord != null) { audioRecord.Stop(); audioRecord.Release(); audioRecord.Dispose(); audioRecord = null; } audioRecord = new AudioRecord(AudioSource.Mic, sampleRate, channelConfig, audioFormat, bufferSize); if (audioRecord != null && audioRecord.State == State.Initialized) { audioRecord.StartRecording(); IsRecording = true; } } public void StopRecording() { if (audioRecord != null && audioRecord.RecordingState == RecordState.Recording) { audioRecord.Stop(); IsRecording = false; } } public async Task<(byte[] buffer, int bytesRead)> GetAudioStream() { if (audioRecord == null) { throw new InvalidOperationException("AudioRecord is not initialized or not in recording state."); } int bytesRead = await audioRecord.ReadAsync(audioBuffer, 0, audioBuffer.Length); return (audioBuffer, bytesRead); } public void Dispose() { if (audioRecord != null) { if (IsRecording) { audioRecord.Stop(); } audioRecord.Release(); audioRecord.Dispose(); audioRecord = null; IsRecording = false; } } } #endif
解决方案
问题分析
当前代码每次启动录制时都会销毁并重建AudioRecord实例,结合Stop()方法调用,可能导致Android系统麦克风资源释放不及时,或残留未完成的异步读取任务干扰新实例工作。此外,硬编码的缓冲区大小可能不符合系统要求,引发录制异常。
修改后的代码
#if ANDROID using Android.Media; using System.Threading; namespace Neuron.Nexus.Services; public interface IAndroidAudioRecordService : IDisposable { void StartRecording(); void StopRecording(); Task<(byte[] buffer, int bytesRead)> GetAudioStream(); bool IsRecording { get; } } public class AndroidAudioRecordService : IAndroidAudioRecordService { private AudioRecord audioRecord; private readonly int sampleRate = 44100; private readonly ChannelIn channelConfig = ChannelIn.Mono; private readonly Encoding audioFormat = Encoding.Pcm16bit; private readonly byte[] audioBuffer; private CancellationTokenSource recordingCts; public bool IsRecording { get; private set; } public AndroidAudioRecordService() { // 使用系统推荐的最小缓冲区大小,避免硬编码兼容性问题 int minBufferSize = AudioRecord.GetMinBufferSize(sampleRate, channelConfig, audioFormat); // 确保缓冲区大小为合理值 int bufferSize = minBufferSize > 1024 ? minBufferSize : 1024; audioBuffer = new byte[bufferSize]; } public void StartRecording() { // 取消上一次录制的异步任务,避免资源泄漏 recordingCts?.Cancel(); recordingCts?.Dispose(); recordingCts = new CancellationTokenSource(); // 复用已初始化的AudioRecord实例,减少资源频繁申请释放 if (audioRecord == null) { int minBufferSize = AudioRecord.GetMinBufferSize(sampleRate, channelConfig, audioFormat); audioRecord = new AudioRecord(AudioSource.Mic, sampleRate, channelConfig, audioFormat, minBufferSize); } if (audioRecord != null && audioRecord.State == State.Initialized) { if (audioRecord.RecordingState != RecordState.Recording) { audioRecord.StartRecording(); IsRecording = true; } } } public void StopRecording() { if (audioRecord != null && audioRecord.RecordingState == RecordState.Recording) { audioRecord.Stop(); IsRecording = false; } // 取消异步读取任务,防止后续读取旧实例数据 recordingCts?.Cancel(); } public async Task<(byte[] buffer, int bytesRead)> GetAudioStream() { if (audioRecord == null || !IsRecording) { throw new InvalidOperationException("AudioRecord is not initialized or not in recording state."); } // 使用取消令牌,确保停止录制时能终止读取任务 int bytesRead = await audioRecord.ReadAsync(audioBuffer, 0, audioBuffer.Length, recordingCts.Token); return (audioBuffer, bytesRead); } public void Dispose() { recordingCts?.Cancel(); recordingCts?.Dispose(); if (audioRecord != null) { if (IsRecording) { audioRecord.Stop(); } audioRecord.Release(); audioRecord.Dispose(); audioRecord = null; IsRecording = false; } } } #endif
关键修改点
- 复用AudioRecord实例:不再每次启动都销毁重建,而是复用已初始化的实例,减少系统资源的频繁申请释放
- 使用系统推荐缓冲区大小:通过
AudioRecord.GetMinBufferSize()获取符合系统要求的缓冲区大小,避免硬编码导致的兼容性问题 - 添加异步任务取消机制:使用
CancellationTokenSource管理录制期间的异步读取任务,停止录制时取消未完成的任务,避免资源泄漏 - 优化状态判断:启动录制前检查当前录制状态,避免重复调用
StartRecording()引发异常
内容的提问来源于stack exchange,提问作者AllramEst
相关产品推荐
相关产品推荐

