處理記憶體占用、手機發熱與 API 呼叫的成本控制
建立 lib/features/live_translate/data/services/silence_filter_utils.dart
import 'dart:typed_data';
class SilenceFilterUtils {
/// 計算 RMS (Root Mean Square) 音量振幅,判定是否為有效語音
static bool isSpeech(Uint8List pcmBytes, {double thresholdDb = -40.0}) {
if (pcmBytes.isEmpty) return false;
// 將 PCM 16-bit 轉為 Sample 數值
int sampleCount = pcmBytes.length ~/ 2;
double sumSquares = 0.0;
final buffer = pcmBytes.buffer.asByteData(pcmBytes.offsetInBytes, pcmBytes.lengthInBytes);
for (int i = 0; i < pcmBytes.length - 1; i += 2) {
int sample = buffer.getInt16(i, Endian.little);
double normalized = sample / 32768.0; // 正規化至 [-1.0, 1.0]
sumSquares += normalized * normalized;
}
double rms = (sampleCount > 0) ? (sumSquares / sampleCount) : 0.0;
// 避免 log10(0)
if (rms < 0.00001) return false;
// 轉換為分貝 (dBFS)
double db = 20 * (rms > 0 ? (rms) : 0.00001);
// 高於門檻視為有聲音(通常 -40dB 到 -35dB 是良好的背景噪聲切分點)
return db > thresholdDb;
}
}
更新 multi_lang_page.dart 中的字幕更新邏輯
class AudioRecorderService {
StreamSubscription<Uint8List>? _audioStreamSubscription;
/// 停止錄音並安全釋放記憶體與 Stream
Future<void> stopAndCleanUp() async {
// 1. 取消 Stream 監聽,避免背景繼續跑
await _audioStreamSubscription?.cancel();
_audioStreamSubscription = null;
// 2. 停止錄音元件
await _audioRecorder.stop();
await _audioRecorder.dispose();
}
}
在 audio_recorder_service.dart 與頁面中實作標準的 dispose 規範
static const int _maxSubtitleHistory = 80; // 記憶體最多保留 80 句字幕
void _addSubtitleSafely(SubtitleItem newSubtitle) {
setState(() {
_subtitleList.add(newSubtitle);
// 防記憶體爆:超過上限時,淘汰最舊的字幕,釋放記憶體
if (_subtitleList.length > _maxSubtitleHistory) {
_subtitleList.removeAt(0);
}
});
}
