mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Fix voice quality regression
This commit is contained in:
@@ -91,6 +91,12 @@ class AppProvider with ChangeNotifier {
|
||||
bool get isVoiceCompressorEnabled => _isVoiceCompressorEnabled;
|
||||
bool _isVoiceLimiterEnabled = true;
|
||||
bool get isVoiceLimiterEnabled => _isVoiceLimiterEnabled;
|
||||
bool _isVoiceAutoGainEnabled = false;
|
||||
bool get isVoiceAutoGainEnabled => _isVoiceAutoGainEnabled;
|
||||
bool _isVoiceEchoCancellationEnabled = false;
|
||||
bool get isVoiceEchoCancellationEnabled => _isVoiceEchoCancellationEnabled;
|
||||
bool _isVoiceNoiseSuppressionEnabled = false;
|
||||
bool get isVoiceNoiseSuppressionEnabled => _isVoiceNoiseSuppressionEnabled;
|
||||
double _messageFontScale = 1.0;
|
||||
double get messageFontScale => _messageFontScale;
|
||||
bool _autoAddDiscoveredContacts = false;
|
||||
@@ -142,6 +148,8 @@ class AppProvider with ChangeNotifier {
|
||||
required this.imageProvider,
|
||||
}) {
|
||||
_setupCallbacks();
|
||||
connectionProvider.canStartAutomaticMessageSyncCallback =
|
||||
_canStartAutomaticMessageSync;
|
||||
_wasDeviceConnected = connectionProvider.deviceInfo.isConnected;
|
||||
_initializeLocationTracking();
|
||||
_loadMapEnabled();
|
||||
@@ -151,6 +159,9 @@ class AppProvider with ChangeNotifier {
|
||||
_loadVoiceBandPassFilterEnabled();
|
||||
_loadVoiceCompressorEnabled();
|
||||
_loadVoiceLimiterEnabled();
|
||||
_loadVoiceAutoGainEnabled();
|
||||
_loadVoiceEchoCancellationEnabled();
|
||||
_loadVoiceNoiseSuppressionEnabled();
|
||||
_loadMessageFontScale();
|
||||
_loadAutoAddDiscoveredContacts();
|
||||
_loadMessagingRouteSettings();
|
||||
@@ -439,6 +450,72 @@ class AppProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadVoiceAutoGainEnabled() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_isVoiceAutoGainEnabled =
|
||||
prefs.getBool('voice_auto_gain_enabled') ?? false;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error loading voice auto gain setting: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleVoiceAutoGainEnabled(bool enabled) async {
|
||||
try {
|
||||
_isVoiceAutoGainEnabled = enabled;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('voice_auto_gain_enabled', enabled);
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error saving voice auto gain setting: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadVoiceEchoCancellationEnabled() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_isVoiceEchoCancellationEnabled =
|
||||
prefs.getBool('voice_echo_cancellation_enabled') ?? false;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error loading voice echo cancellation setting: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleVoiceEchoCancellationEnabled(bool enabled) async {
|
||||
try {
|
||||
_isVoiceEchoCancellationEnabled = enabled;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('voice_echo_cancellation_enabled', enabled);
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error saving voice echo cancellation setting: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadVoiceNoiseSuppressionEnabled() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_isVoiceNoiseSuppressionEnabled =
|
||||
prefs.getBool('voice_noise_suppression_enabled') ?? false;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error loading voice noise suppression setting: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleVoiceNoiseSuppressionEnabled(bool enabled) async {
|
||||
try {
|
||||
_isVoiceNoiseSuppressionEnabled = enabled;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('voice_noise_suppression_enabled', enabled);
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error saving voice noise suppression setting: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadMessageFontScale() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -1642,6 +1719,8 @@ class AppProvider with ChangeNotifier {
|
||||
if (!connectionProvider.deviceInfo.isConnected) return;
|
||||
|
||||
try {
|
||||
_isReconnectSyncInProgress = true;
|
||||
_hasCompletedConnectionBootstrap = false;
|
||||
// Initialize contacts provider with device public key to exclude self
|
||||
// If already initialized (from early load), this will just filter out self-contact
|
||||
// This must happen before getContacts to ensure proper filtering
|
||||
@@ -1693,7 +1772,9 @@ class AppProvider with ChangeNotifier {
|
||||
debugPrint(
|
||||
'🔄 [AppProvider] Performing initial message sync (fallback for missed pushes)',
|
||||
);
|
||||
final initialMessageCount = await connectionProvider.syncAllMessages();
|
||||
final initialMessageCount = await connectionProvider.syncAllMessages(
|
||||
force: true,
|
||||
);
|
||||
debugPrint(
|
||||
'📥 [AppProvider] Initial sync retrieved $initialMessageCount message(s)',
|
||||
);
|
||||
@@ -1715,19 +1796,26 @@ class AppProvider with ChangeNotifier {
|
||||
|
||||
_hasCompletedConnectionBootstrap = true;
|
||||
_wasDeviceConnected = connectionProvider.deviceInfo.isConnected;
|
||||
await _flushDeferredAutomaticMessageSync();
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Initialization error: $e');
|
||||
} finally {
|
||||
_isReconnectSyncInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _syncAfterReconnect() async {
|
||||
if (_isReconnectSyncInProgress ||
|
||||
!connectionProvider.deviceInfo.isConnected) {
|
||||
Future<void> _syncAfterReconnect({bool started = false}) async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isReconnectSyncInProgress = true;
|
||||
if (!started) {
|
||||
if (_isReconnectSyncInProgress) {
|
||||
return;
|
||||
}
|
||||
_isReconnectSyncInProgress = true;
|
||||
}
|
||||
try {
|
||||
debugPrint(
|
||||
'🔄 [AppProvider] Device reconnected - syncing contacts and missed messages',
|
||||
@@ -1738,14 +1826,19 @@ class AppProvider with ChangeNotifier {
|
||||
);
|
||||
await connectionProvider.getContacts();
|
||||
|
||||
final messageCount = await connectionProvider.syncAllMessages();
|
||||
final messageCount = await connectionProvider.syncAllMessages(
|
||||
force: true,
|
||||
);
|
||||
debugPrint(
|
||||
'📥 [AppProvider] Reconnect sync retrieved $messageCount message(s)',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [AppProvider] Reconnect sync error: $e');
|
||||
} finally {
|
||||
_hasCompletedConnectionBootstrap =
|
||||
connectionProvider.deviceInfo.isConnected;
|
||||
_isReconnectSyncInProgress = false;
|
||||
await _flushDeferredAutomaticMessageSync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2737,7 +2830,9 @@ class AppProvider with ChangeNotifier {
|
||||
debugPrint(
|
||||
'🔄 [AppProvider] Manual message sync requested (user initiated)',
|
||||
);
|
||||
final messageCount = await connectionProvider.syncAllMessages();
|
||||
final messageCount = await connectionProvider.syncAllMessages(
|
||||
force: true,
|
||||
);
|
||||
debugPrint(
|
||||
'✅ [AppProvider] Manual sync completed: $messageCount messages',
|
||||
);
|
||||
@@ -2766,9 +2861,32 @@ class AppProvider with ChangeNotifier {
|
||||
_stopLocationTracking();
|
||||
}
|
||||
|
||||
if (isConnected && !wasConnected && _hasCompletedConnectionBootstrap) {
|
||||
unawaited(_syncAfterReconnect());
|
||||
if (!isConnected) {
|
||||
connectionProvider.clearPendingAutomaticMessageSync();
|
||||
}
|
||||
|
||||
if (isConnected && !wasConnected && _hasCompletedConnectionBootstrap) {
|
||||
_isReconnectSyncInProgress = true;
|
||||
unawaited(_syncAfterReconnect(started: true));
|
||||
}
|
||||
}
|
||||
|
||||
bool _canStartAutomaticMessageSync() {
|
||||
return connectionProvider.deviceInfo.isConnected &&
|
||||
_hasCompletedConnectionBootstrap &&
|
||||
!_isReconnectSyncInProgress;
|
||||
}
|
||||
|
||||
Future<void> _flushDeferredAutomaticMessageSync() async {
|
||||
if (!connectionProvider.hasPendingAutomaticMessageSync ||
|
||||
!_canStartAutomaticMessageSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
'🔄 [AppProvider] Running deferred automatic message sync after bootstrap',
|
||||
);
|
||||
await connectionProvider.syncAllMessages(force: true);
|
||||
}
|
||||
|
||||
/// Start location tracking
|
||||
|
||||
@@ -186,9 +186,11 @@ class ConnectionProvider with ChangeNotifier {
|
||||
Function(Uint8List publicKeyPrefix, Uint8List statusData)? onStatusResponse;
|
||||
Function(Uint8List payload, int snrRaw, int rssiDbm)? onRawDataReceived;
|
||||
Contact? Function(Uint8List contactPublicKey)? resolveContactForDmCallback;
|
||||
bool Function()? canStartAutomaticMessageSyncCallback;
|
||||
|
||||
// Track pending send operations for auto-recovery
|
||||
final Map<String, _PendingSendOperation> _pendingSendOperations = {};
|
||||
bool _pendingAutomaticMessageSync = false;
|
||||
|
||||
ConnectionProvider() {
|
||||
_wireServiceCallbacks(_bleService);
|
||||
@@ -333,6 +335,13 @@ class ConnectionProvider with ChangeNotifier {
|
||||
debugPrint('📥 [Provider] MSG_WAITING ignored during spectrum scan');
|
||||
return;
|
||||
}
|
||||
if (!(canStartAutomaticMessageSyncCallback?.call() ?? true)) {
|
||||
_pendingAutomaticMessageSync = true;
|
||||
debugPrint(
|
||||
'📥 [Provider] MSG_WAITING deferred until connection bootstrap completes',
|
||||
);
|
||||
return;
|
||||
}
|
||||
debugPrint('📥 [Provider] MSG_WAITING - auto-syncing');
|
||||
if (_isSyncingMessages) {
|
||||
_syncRequestedWhileBusy = true;
|
||||
@@ -1894,11 +1903,18 @@ class ConnectionProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Sync all waiting messages from device
|
||||
Future<int> syncAllMessages() async {
|
||||
Future<int> syncAllMessages({bool force = false}) async {
|
||||
if (_isSpectrumScanActive) {
|
||||
debugPrint('⏸️ [Provider] Message sync skipped during spectrum scan');
|
||||
return 0;
|
||||
}
|
||||
if (!force && !(canStartAutomaticMessageSyncCallback?.call() ?? true)) {
|
||||
_pendingAutomaticMessageSync = true;
|
||||
debugPrint(
|
||||
'⏸️ [Provider] Message sync deferred until connection bootstrap completes',
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
if (_isSyncingMessages) {
|
||||
// Already syncing; avoid overlapping loops
|
||||
_syncRequestedWhileBusy = true;
|
||||
@@ -1914,6 +1930,7 @@ class ConnectionProvider with ChangeNotifier {
|
||||
int totalCount = 0;
|
||||
|
||||
try {
|
||||
_pendingAutomaticMessageSync = false;
|
||||
_isSyncingMessages = true;
|
||||
do {
|
||||
_syncRequestedWhileBusy = false;
|
||||
@@ -2005,6 +2022,12 @@ class ConnectionProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
bool get hasPendingAutomaticMessageSync => _pendingAutomaticMessageSync;
|
||||
|
||||
void clearPendingAutomaticMessageSync() {
|
||||
_pendingAutomaticMessageSync = false;
|
||||
}
|
||||
|
||||
/// Login to a room or repeater
|
||||
///
|
||||
/// Sends login request with password. Results will be delivered via
|
||||
|
||||
@@ -416,7 +416,7 @@ class ContactsProvider with ChangeNotifier {
|
||||
final existingAdvertLocation = existingContact.advertLocation;
|
||||
|
||||
var updatedContact = incomingContact.copyWith(
|
||||
isNew: existingContact.isNew,
|
||||
isNew: false,
|
||||
advertHistory: existingContact.advertHistory,
|
||||
telemetry: mergedTelemetry,
|
||||
outPathLen:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/contact.dart';
|
||||
@@ -299,7 +300,11 @@ class VoiceProvider with ChangeNotifier {
|
||||
);
|
||||
|
||||
try {
|
||||
final pcm = await _codec.decodePackets(session.packets, session.mode);
|
||||
final decodedPcm = await _codec.decodePackets(
|
||||
session.packets,
|
||||
session.mode,
|
||||
);
|
||||
final pcm = _preparePlaybackPcm(decodedPcm, session.mode);
|
||||
debugPrint('🎙️ [VoiceProvider] decoded ${pcm.length} PCM samples');
|
||||
_playingSessionId = sessionId;
|
||||
notifyListeners();
|
||||
@@ -319,6 +324,28 @@ class VoiceProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Int16List _preparePlaybackPcm(Int16List pcm, VoicePacketMode mode) {
|
||||
if (pcm.isEmpty) {
|
||||
return pcm;
|
||||
}
|
||||
|
||||
if (mode.codec != VoiceCodecKind.lpcnet) {
|
||||
return pcm;
|
||||
}
|
||||
|
||||
final output = Int16List(pcm.length);
|
||||
var dc = 0.0;
|
||||
const dcAlpha = 0.995;
|
||||
|
||||
for (var i = 0; i < pcm.length; i++) {
|
||||
final sample = pcm[i].toDouble();
|
||||
dc = (dcAlpha * dc) + ((1.0 - dcAlpha) * sample);
|
||||
final filtered = sample - dc;
|
||||
output[i] = filtered.clamp(-32768.0, 32767.0).round();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
Future<void> clearStoredVoiceData() async {
|
||||
_sessions.clear();
|
||||
_outgoingSessions.clear();
|
||||
|
||||
Reference in New Issue
Block a user