mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Add LPCNet voice mode support
This commit is contained in:
@@ -18,8 +18,8 @@ import '../services/location_tracking_service.dart';
|
||||
import '../services/locale_preferences.dart';
|
||||
import '../services/mesh_map_nodes_service.dart';
|
||||
import '../services/update_checker_service.dart';
|
||||
import '../services/voice_codec_service.dart';
|
||||
import '../services/voice_bitrate_preferences.dart';
|
||||
import '../services/voice_codec_preferences.dart';
|
||||
import '../services/image_preferences.dart';
|
||||
import '../services/route_hash_preferences.dart';
|
||||
import '../services/image_codec_service.dart';
|
||||
@@ -60,6 +60,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
bool _showRxTxIndicators = true;
|
||||
bool _isCheckingForUpdates = false;
|
||||
int _voiceBitrate = VoiceBitratePreferences.defaultBitrate;
|
||||
VoiceCodecKind _voiceCodec = VoiceCodecPreferences.defaultCodec;
|
||||
int _routeHashSize = RouteHashPreferences.defaultHashSize;
|
||||
int _imageMaxSize = ImagePreferences.defaultMaxSize;
|
||||
int _imageCompression = ImagePreferences.defaultQuality;
|
||||
@@ -88,7 +89,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_loadPackageInfo();
|
||||
_initializeLocationService();
|
||||
_loadRxTxPreference();
|
||||
_loadVoiceBitratePreference();
|
||||
_loadVoicePreferences();
|
||||
_loadRouteHashSizePreference();
|
||||
_loadImagePreferences();
|
||||
_loadFastLocationSettings();
|
||||
@@ -178,11 +179,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
await prefs.setBool('show_rx_tx_indicators', value);
|
||||
}
|
||||
|
||||
Future<void> _loadVoiceBitratePreference() async {
|
||||
Future<void> _loadVoicePreferences() async {
|
||||
final value = await VoiceBitratePreferences.getBitrate();
|
||||
final codec = await VoiceCodecPreferences.getCodec();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_voiceBitrate = value;
|
||||
_voiceCodec = codec;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -194,10 +197,27 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveVoiceCodecPreference(VoiceCodecKind value) async {
|
||||
await VoiceCodecPreferences.setCodec(value);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_voiceCodec = value;
|
||||
});
|
||||
}
|
||||
|
||||
String _voiceBitrateSubtitle(int bitrate) {
|
||||
if (_voiceCodec == VoiceCodecKind.lpcnet) {
|
||||
return 'Fixed 1600 bps';
|
||||
}
|
||||
return '$bitrate bps';
|
||||
}
|
||||
|
||||
String _voiceCodecSubtitle() {
|
||||
return _voiceCodec == VoiceCodecKind.codec2
|
||||
? 'Selectable low-bitrate modes'
|
||||
: 'Fixed 16 kHz / 1.6 kbps mode';
|
||||
}
|
||||
|
||||
Future<void> _loadRouteHashSizePreference() async {
|
||||
final value = await RouteHashPreferences.getHashSize();
|
||||
if (!mounted) return;
|
||||
@@ -1159,6 +1179,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Consumer2<AppProvider, ConnectionProvider>(
|
||||
builder: (context, appProvider, connectionProvider, child) =>
|
||||
_buildVoiceStatsCard(
|
||||
codec: _voiceCodec,
|
||||
bitrate: _voiceBitrate,
|
||||
connectionProvider: connectionProvider,
|
||||
bandPassEnabled: appProvider.isVoiceBandPassFilterEnabled,
|
||||
@@ -1168,12 +1189,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
),
|
||||
_buildSettingsCard([
|
||||
ListTile(
|
||||
leading: const Icon(Icons.record_voice_over),
|
||||
title: const Text('Voice codec'),
|
||||
subtitle: Text(_voiceCodecSubtitle()),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: _showVoiceCodecDialog,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.graphic_eq),
|
||||
title: const Text('Voice bitrate'),
|
||||
subtitle: Text(_voiceBitrateSubtitle(_voiceBitrate)),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: _showVoiceBitrateDialog,
|
||||
enabled: _voiceCodec == VoiceCodecKind.codec2,
|
||||
onTap: _voiceCodec == VoiceCodecKind.codec2
|
||||
? _showVoiceBitrateDialog
|
||||
: null,
|
||||
),
|
||||
Consumer<AppProvider>(
|
||||
builder: (context, appProvider, child) => SwitchListTile(
|
||||
@@ -1724,6 +1755,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
}
|
||||
|
||||
Widget _buildVoiceStatsCard({
|
||||
required VoiceCodecKind codec,
|
||||
required int bitrate,
|
||||
required ConnectionProvider connectionProvider,
|
||||
required bool bandPassEnabled,
|
||||
@@ -1746,9 +1778,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
final radioSf = connectionProvider.deviceInfo.radioSf;
|
||||
final radioCr = connectionProvider.deviceInfo.radioCr;
|
||||
final bwHz = _resolveBandwidthHz(radioBw);
|
||||
final voiceMode = VoiceBitratePreferences.toVoiceMode(bitrate);
|
||||
final voiceMode = codec == VoiceCodecKind.lpcnet
|
||||
? VoicePacketMode.lpcnet1600
|
||||
: VoiceBitratePreferences.toVoiceMode(bitrate);
|
||||
const voicePreviewMs = 10000; // 10-second reference clip
|
||||
final packetDurationMs = codec2ModeFor(voiceMode).packetDurationMs;
|
||||
final packetDurationMs = voiceMode.packetDurationMs;
|
||||
final voicePacketCount =
|
||||
(voicePreviewMs + packetDurationMs - 1) ~/ packetDurationMs;
|
||||
final voiceDirect = estimateVoiceTransmitDuration(
|
||||
@@ -1783,7 +1817,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Bitrate: $bitrate bps',
|
||||
'Codec: ${voiceMode.label}${codec == VoiceCodecKind.codec2 ? ' · $bitrate bps' : ' · 1600 bps'}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -2112,6 +2146,47 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showVoiceCodecDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Voice codec'),
|
||||
content: SingleChildScrollView(
|
||||
child: RadioGroup<VoiceCodecKind>(
|
||||
groupValue: _voiceCodec,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
_saveVoiceCodecPreference(value);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
RadioListTile<VoiceCodecKind>(
|
||||
value: VoiceCodecKind.codec2,
|
||||
title: const Text('Codec2'),
|
||||
subtitle: const Text('Selectable ultra-low bitrate modes'),
|
||||
),
|
||||
RadioListTile<VoiceCodecKind>(
|
||||
value: VoiceCodecKind.lpcnet,
|
||||
title: const Text('LPCNet'),
|
||||
subtitle: const Text('Fixed 16 kHz / 1.6 kbps neural mode'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAboutDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
||||
Reference in New Issue
Block a user