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

Flutter实时音频播放崩溃:Fatal Signal 6(SIGABRT)问题排查

问题描述

我正在开发一款帮助听障人士的Flutter应用,核心功能是将麦克风采集的音频实时播放到扬声器。实现方式是通过录音器采集音频流,再用播放器实时播放,但应用运行一段时间后会崩溃,崩溃日志如下:

崩溃日志

F/libc (16368): Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 16693 (Thread-21), pid 16368 (corise.hearmate)
error is App crash:
pid: 16368, tid: 16693, name: Thread-21 >>> com.corise.hearmate <<<
uid: 10270
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'releaseBuffer: mUnreleased out of range, !(stepCount:78 <= mUnreleased:0 <= mFrameCount:4096), BufferSizeInFrames:4096'

已实现的核心代码

启动录音与流播放的函数代码如下:

Future<void> startRecording(BuildContext context) async {
  checkBluetoothPermission();
  log("Start recording");
  getMacAccountDate();
  allowed();
  if (isRecording) return;

  final String connectedDevice =
  await MethodChannel('com.example.settings/bluetoothState')
        .invokeMethod('isHeadsetConnected');

  log(connectedDevice);
  if (connectedDevice == "none") {
    showDialog(
      context: context,
      builder: (context) => CustomDialog(
        headline: "No Headset",
        red: () {},
        green: () {
          Navigator.pop(context);
        },
        redButton: "",
        greenButton: "Try Again",
        animation: "assets/animations/inf.json",
        subline:
            "To use listening feature of this app you must connect some headset with the device for best results "),
    );
  } else {
    try {
      _audioStreamController = StreamController<Uint8List>();
      log("BitRate: " + bitrate.toString());
      // Start recording with initial settings
      await _recorder!.startRecorder(
        codec: Codec.pcm16WAV,
        bitRate: bitrate.toInt(),
        toStream: _audioStreamController!.sink,
        numChannels: 1,
        enableVoiceProcessing: true,
        sampleRate: frequency.toInt(), // Initial frequency (from slider)
      );

      // Listen for audio data and handle it
      _audioStreamController!.stream.listen(
        (audioData) async {
          try {
            // Apply noise cancellation if enabled

            if (_player != null && !_player!.isPlaying) {
              await _player!.startPlayerFromStream(
                codec: Codec.pcm16,
                numChannels: 1,
                sampleRate: frequency.toInt() // Apply frequency to player
              );
            }

            if (_player != null && _player!.isPlaying) {
              await _player!.feedFromStream(audioData);
            }
          } catch (e) {
            log("Error during feed: $e");
          }
        },
        onError: (error) {
          log("Stream error: $error");
        },
        onDone: () {
          log("Stream closed");
        },
      );
      final prefs = await SharedPreferences.getInstance();
      final isEnabled = prefs.getBool('notification_toggle') ?? false;
      if (isEnabled) {
        showNotification("HearMate", "Hear Mate app is running . . . ");
      }
      isRecording = true;
      notifyListeners();
    } catch (e) {
      log("Error starting recording: $e");
      isRecording = false;
      notifyListeners();
    }
  }
}

复现步骤

  • 启动应用并开启实时音频录播功能;
  • 让应用运行一段时间(几秒到几分钟不等);
  • 应用出现上述崩溃。

咨询问题

  1. 崩溃日志中的releaseBuffer: mUnreleased out of range是什么含义?
  2. 当前的录音/播放器实现是否存在缓冲区相关问题导致崩溃?
  3. 是否需要调整Flutter或Android原生设置(如缓冲区大小、采样率)来解决该问题?

解答

1. releaseBuffer: mUnreleased out of range的含义

这是Android底层音频系统的断言错误,表明音频缓冲区的释放逻辑异常:

  • mUnreleased是当前未被释放的缓冲区帧数,此处为0;
  • stepCount是尝试释放的帧数(78);
  • mFrameCount是缓冲区总帧数(4096);
    错误本质是:尝试释放的帧数超过了实际未被释放的帧数,触发底层音频缓冲区管理的断言检查,直接导致应用崩溃。

2. 当前实现的缓冲区问题

从代码看,存在多个触发缓冲区异常的风险点:

  • 编码格式不匹配:录音使用Codec.pcm16WAV(带WAV文件头),但播放器使用Codec.pcm16(纯PCM数据),WAV头会被当作音频数据喂给播放器,导致解析错误,破坏缓冲区状态;
  • 播放器重复启动:流回调中每次都检查_player!.isPlaying并尝试启动播放器,可能触发多次startPlayerFromStream调用,造成播放器内部缓冲区状态混乱;
  • 异步竞态堆积:流回调是异步函数,await _player!.feedFromStream(audioData)会阻塞回调执行,导致音频数据在流中堆积,播放器缓冲区无法及时处理,最终引发释放逻辑错误。

3. 解决方案与调整建议

代码层面修复

  • 统一编码格式:将录音的Codec.pcm16WAV改为Codec.pcm16,避免WAV头干扰播放器解析:
    await _recorder!.startRecorder(
      codec: Codec.pcm16, // 替换为纯PCM编码
      bitRate: bitrate.toInt(),
      toStream: _audioStreamController!.sink,
      numChannels: 1,
      enableVoiceProcessing: true,
      sampleRate: frequency.toInt(),
    );
    
  • 固定播放器启动时机:将播放器启动逻辑移到录音启动之后,而非流回调中,确保只启动一次:
    // 启动录音后立即初始化播放器
    await _player!.startPlayerFromStream(
      codec: Codec.pcm16,
      numChannels: 1,
      sampleRate: frequency.toInt()
    );
    // 监听流并喂数据,去掉await避免阻塞
    _audioStreamController!.stream.listen(
      (audioData) {
        if (_player != null && _player!.isPlaying) {
          _player!.feedFromStream(audioData);
        }
      },
      onError: (error) => log("Stream error: $error"),
      onDone: () => log("Stream closed"),
    );
    
  • 避免异步阻塞:流回调中不要使用await,防止音频数据堆积;可添加状态标记确保播放器操作的原子性。

系统与配置调整

  • 调整缓冲区大小:如果使用record或audioplayers插件,尝试设置更小的缓冲区帧数(如从4096改为2048),减少数据堆积概率;
  • 固定采样率:避免运行中动态修改frequency(滑块调整),若需调整需先停止录音/播放器,重启时同步新参数;
  • Android原生优化:在AndroidManifest.xml中添加低延迟音频配置:
    <uses-feature android:name="android.hardware.audio.low_latency" />
    
    并在音频初始化时启用低延迟模式,降低缓冲区异常风险。

资源管理优化

  • 及时释放资源:停止录音时,务必关闭_audioStreamController、停止播放器并释放资源,避免缓冲区残留数据干扰后续启动;
  • 添加错误恢复:捕获播放器缓冲区异常,出现错误时重启录音/播放器流程,避免直接崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:47:12