Flutter Web麦克风实时音频播放正常,Android设备运行秒退问题求助
Flutter Web麦克风实时音频播放正常,Android设备运行秒退问题求助
我现在在做一个用tAudio库实现麦克风录音实时回放到扬声器的功能,在Flutter Web上测试完全正常,但一放到Android设备上运行,录音大概1秒不到App就直接崩了,实在找不到问题出在哪,麻烦大家帮我看看我的代码有没有问题?
以下是我的完整代码:
/* * Copyright 2018, 2019, 2020, 2021 Dooboolab. * * This file is part of Flutter-Sound. * * Flutter-Sound is free software: you can redistribute it and/or modify * it under the terms of the Mozilla Public License version 2 (MPL2.0), * as published by the Mozilla organization. * * Flutter-Sound is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MPL General Public License for more details. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import 'dart:async'; import 'package:flutter/foundation.dart' show kIsWeb, Uint8List; import 'package:flutter/material.dart'; import 'package:taudio/taudio.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:audio_session/audio_session.dart'; /* This is a very simple example for Flutter Sound beginners, hat shows how to record, and then playback a file. This example is really basic. */ void main() { runApp(const MaterialApp( home: PlayFromMic(), )); } typedef _Fn = void Function(); const theSource = AudioSource.microphone; // Example app. class PlayFromMic extends StatefulWidget { const PlayFromMic({super.key}); @override State<PlayFromMic> createState() => _PlayFromMic(); } class _PlayFromMic extends State<PlayFromMic> { /// Our player final FlutterSoundPlayer _mPlayer = FlutterSoundPlayer(); /// Our recorder final FlutterSoundRecorder _mRecorder = FlutterSoundRecorder(); var recordingDataControllerUint8 = StreamController<Uint8List>(); Future<void> init() async { final session = await AudioSession.instance; await session.configure(AudioSessionConfiguration( avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.allowBluetooth | AVAudioSessionCategoryOptions.defaultToSpeaker, avAudioSessionMode: AVAudioSessionMode.spokenAudio, avAudioSessionRouteSharingPolicy: AVAudioSessionRouteSharingPolicy.defaultPolicy, avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, androidAudioAttributes: const AndroidAudioAttributes( contentType: AndroidAudioContentType.speech, flags: AndroidAudioFlags.none, usage: AndroidAudioUsage.voiceCommunication, ), androidAudioFocusGainType: AndroidAudioFocusGainType.gain, androidWillPauseWhenDucked: true, )); _mPlayer.openPlayer().then((value) { setState(() { _mPlayerIsInited = true; }); }); openTheRecorder().then((value) { setState(() { _mRecorderIsInited = true; }); }); } @override void initState() { super.initState(); init(); } @override void dispose() { _mPlayer.closePlayer(); _mRecorder.closeRecorder(); recordingDataControllerUint8.close(); // 补充了StreamController的关闭,避免内存泄漏 super.dispose(); } // ------------------------------ This is the recorder stuff ----------------------------- static const Codec _codec = Codec.pcm16; static const int _sampleRate = 48000; bool _mRecorderIsInited = false; double _dbLevel = 0.0; StreamSubscription? _recorderSubscription; bool bNoiseSuppression = false; bool bEchoCancellation = false; bool _mPlayerIsInited = false; // 补全了原代码中缺失的变量定义 /// Request permission to record something and open the recorder Future<void> openTheRecorder() async { if (!kIsWeb) { var status = await Permission.microphone.request(); if (status != PermissionStatus.granted) { throw RecordingPermissionException('Microphone permission not granted'); } // 补充请求存储权限?不过实时播放可能不需要,但Android 13+可能需要POST_NOTIFICATIONS? var notificationStatus = await Permission.notification.request(); if (notificationStatus != PermissionStatus.granted) { print('Notification permission not granted'); } } await _mRecorder.openRecorder(); _recorderSubscription = _mRecorder.onProgress!.listen((e) { setState(() { _dbLevel = e.decibels as double; }); }); await _mRecorder.setSubscriptionDuration( const Duration(milliseconds: 100)); _mRecorderIsInited = true; } /// Begin to record. /// This is our main function. /// We ask Flutter Sound to record to a File. void record() async { try { assert(_mPlayerIsInited && _mRecorder.isStopped && _mPlayer.isStopped); // 先将录音流直接对接播放器的sink,避免中间的StreamController可能的问题 await _mPlayer.startPlayerFromStream( codec: _codec, sampleRate: _sampleRate, interleaved: true, bufferSize: 1024, numChannels: 2, ); await _mRecorder.startRecorder( codec: _codec, audioSource: theSource, toStream: _mPlayer.sink, // 直接将录音输出到播放器的输入流,去掉中间的自定义StreamController sampleRate: _sampleRate, numChannels: 2, enableNoiseSuppression: bNoiseSuppression, enableEchoCancellation: bEchoCancellation, ); } catch (err) { print('录音/播放启动错误: $err'); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('启动失败: $err'))); } } // 补充停止功能 void stop() async { await _mRecorder.stopRecorder(); await _mPlayer.stopPlayer(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('实时麦克风播放')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_mRecorderIsInited && _mPlayerIsInited) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: _mRecorder.isRecording ? null : record, child: const Text('开始实时播放'), ), const SizedBox(width: 20), ElevatedButton( onPressed: _mRecorder.isRecording ? stop : null, child: const Text('停止'), ), ], ), const SizedBox(height: 20), Text('当前分贝值: ${_dbLevel.toStringAsFixed(1)}'), ], ), ), ); } }
我自己排查的时候发现原代码里有几个可能的问题点:
- 自定义的
recordingDataControllerUint8没有对接播放器的输入流,相当于录音数据没被消费,可能导致Android端的缓冲区溢出崩溃 - 原代码缺失
_mPlayerIsInited变量的定义,虽然Web上可能不影响,但Android端严格类型检查可能出问题 - 没有关闭自定义的
StreamController,可能导致内存泄漏
我在代码里做了一些修改尝试修复,但还是不确定是不是这些问题导致的崩溃,有没有大佬能帮忙看看Android端秒退的具体原因?
内容来源于stack exchange
相关产品推荐
相关产品推荐

