You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter音频流重连:iOS锁屏下AVFAudio错误561145187问题求助

iOS锁屏下音频流重连失败(错误码561145187)解决方案

问题概况

  • 触发场景:音频流因第二设备关闭重启中断后执行重连逻辑,iPhone锁屏状态下启动重连必现错误
  • 异常表现:前几次重连全部失败,热重启应用后重试可正常恢复通话
  • 错误码对应:AVAudioSession.ErrorCode.cannotStartRecording

错误日志

[aurioc] AURemoteIO.cpp:1668 AUIOClient_StartIO failed (561145187)
[avae] AVAEInternal.h:109 [AVAudioEngineGraph.mm:1545:Start: (err = PerformCommand(*ioNode, kAUStartIO, NULL, 0)): error 561145187
Flutter : PlatformException(record, The operation couldn’t be completed. (com.apple.coreaudio.avfaudio error 561145187.), null, null)

已尝试无效的配置

重连前已调用AudioSession配置,但问题依旧:

Future<AudioSession> configureAudioSession() async {
    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration(
      avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
      avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.defaultToSpeaker | 
                                     AVAudioSessionCategoryOptions.mixWithOthers | 
                                     AVAudioSessionCategoryOptions.allowBluetooth | 
                                     AVAudioSessionCategoryOptions.interruptSpokenAudioAndMixWithOthers,
      avAudioSessionMode: AVAudioSessionMode.voiceChat,
      avAudioSessionRouteSharingPolicy: AVAudioSessionRouteSharingPolicy.defaultPolicy,
      avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.notifyOthersOnDeactivation,
      androidAudioAttributes: const AndroidAudioAttributes(
        contentType: AndroidAudioContentType.speech,
        flags: AndroidAudioFlags.none,
        usage: AndroidAudioUsage.voiceCommunication,
      ),
      androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
      androidWillPauseWhenDucked: true,
    ));
    return session;
  }

启动音频流代码:

final stream = await audioRecorder.startStream(const RecordConfig(
    encoder: AudioEncoder.pcm16bits,
    bitRate: audioRecorderChannelCount * BabyMonitorCallService.sampleRate * 16,
    sampleRate: BabyMonitorCallService.sampleRate,
    numChannels: audioRecorderChannelCount,
    autoGain: true,
    noiseSuppress: true));

final streamSub = await _subscribeToPcm16Stream(stream, onFrame);

可行解决方案建议

1. 彻底重置AudioSession状态

锁屏下错误大概率是旧会话状态残留导致,重连前先强制停用再重新激活:

Future<AudioSession> reconfigureAudioSession() async {
  final session = await AudioSession.instance;
  // 先尝试停用旧会话,给系统释放资源的时间
  try {
    await session.setActive(false, options: AVAudioSessionSetActiveOptions.notifyOthersOnDeactivation);
    await Future.delayed(const Duration(milliseconds: 100));
  } catch (e) {
    // 忽略停用失败的情况,继续下一步
  }
  // 重新配置,移除锁屏下可能干扰权限的选项
  await session.configure(AudioSessionConfiguration(
    avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
    // 只保留必要选项:默认扬声器+允许蓝牙,mixWithOthers会降低锁屏录音优先级
    avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.defaultToSpeaker | 
                                   AVAudioSessionCategoryOptions.allowBluetooth,
    avAudioSessionMode: AVAudioSessionMode.voiceChat,
    avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
    // Android配置保持不变
    androidAudioAttributes: const AndroidAudioAttributes(
      contentType: AndroidAudioContentType.speech,
      flags: AndroidAudioFlags.none,
      usage: AndroidAudioUsage.voiceCommunication,
    ),
    androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
    androidWillPauseWhenDucked: true,
  ));
  // 主动激活会话
  await session.setActive(true);
  return session;
}

2. 增加重连重试机制

针对561145187错误做指数退避重试,每次重试前重置会话:

Future<void> attemptReconnect(int retryCount) async {
  if (retryCount >= 3) return; // 限制重试次数,避免无限循环
  try {
    await reconfigureAudioSession();
    final stream = await audioRecorder.startStream(const RecordConfig(
      encoder: AudioEncoder.pcm16bits,
      bitRate: audioRecorderChannelCount * BabyMonitorCallService.sampleRate * 16,
      sampleRate: BabyMonitorCallService.sampleRate,
      numChannels: audioRecorderChannelCount,
      autoGain: true,
      noiseSuppress: true
    ));
    final streamSub = await _subscribeToPcm16Stream(stream, onFrame);
  } on PlatformException catch (e) {
    if (e.code == 'record' && e.message?.contains('561145187') == true) {
      // 指数退避延迟:200ms、400ms、600ms
      await Future.delayed(Duration(milliseconds: 200 * (retryCount + 1)));
      await attemptReconnect(retryCount + 1);
    }
  }
}

3. 检查后台模式配置

确保Xcode中已开启Audio, AirPlay, and Picture in Picture后台权限,锁屏下应用需要该权限才能正常操作音频资源。

4. 监听路由变化并主动切换

重连前确认音频路由可用,避免设备断开后的路由残留问题:

// 提前监听路由变化
final session = await AudioSession.instance;
session.onRouteChanged.listen((event) {
  if (event.reason == AVAudioSessionRouteChangeReason.oldDeviceUnavailable) {
    // 标记需要重新检查路由
    _needRouteReset = true;
  }
});

// 重连前主动切换到内置设备
if (_needRouteReset) {
  await session.setPreferredInputType(AVAudioSessionPortBuiltInMic);
  await session.setPreferredOutputType(AVAudioSessionPortBuiltInSpeaker);
  _needRouteReset = false;
}

核心原因分析

锁屏下iOS对录音权限的限制更严格,mixWithOthers等混音选项会降低系统授予录音权限的优先级;同时旧AudioSession状态未彻底释放,导致新会话无法获取资源。热重启能解决问题,也验证了是会话状态残留的问题。

内容的提问来源于stack exchange,提问作者William

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 03:24:53