fix(dmr): use flat discriminator and reject noise sync

This commit is contained in:
2026-07-14 19:30:43 +03:00
parent 300bc91a46
commit a19c82a214
3 changed files with 118 additions and 10 deletions
+77 -3
View File
@@ -140,12 +140,15 @@ type
FSyncCount: QWord;
FLastSyncTick: QWord;
FLastSyncSample: QWord;
FPendingSyncSample: array[TDMRSyncKind] of QWord;
FPendingSyncCount: array[TDMRSyncKind] of Byte;
FMobileActiveBurst: Boolean;
function PopSamples(var Buf: array of Single): Integer;
procedure ProcessSample(S: Single);
procedure ProcessSymbol(Phase: Integer; Symbol: Double);
procedure NoteSync(Phase: Integer; Kind: TDMRSyncKind; Inverted: Boolean);
procedure LoseSync;
function Digitize(Phase: Integer; Symbol: Double): Byte;
procedure StartBurstTracking(Phase: Integer);
procedure CompleteBurst;
@@ -174,6 +177,7 @@ const
DMR_SYNC_HOLD_MS = 500;
DMR_SYNC_MAX_ERRORS = 2;
DMR_SUPERFRAME_SAMPLES = DMR_INPUT_RATE * 360 div 1000;
DMR_SYNC_CADENCE_TOLERANCE = DMR_SAMPLES_PER_SYM * 3;
// One bit per DSD-FME sync character: '1'=0, '3'=1. These are the eight
// ETSI DMR 48-bit sync patterns after collapsing each dibit to its sign.
@@ -337,6 +341,8 @@ begin
FSyncCount := 0;
FLastSyncTick := 0;
FLastSyncSample := 0;
FillChar(FPendingSyncSample, SizeOf(FPendingSyncSample), 0);
FillChar(FPendingSyncCount, SizeOf(FPendingSyncCount), 0);
FMobileActiveBurst := True;
finally
FStatusLock.Leave;
@@ -432,11 +438,19 @@ var
Pat: LongWord;
Dibit: Byte;
V: Double;
DeltaSamples, CycleOffset: QWord;
DeltaSamples, CycleOffset, PendingSample: QWord;
CandidateAllowed: Boolean;
begin
V := Symbol;
if FInputInverted then V := -V;
// Stop assembling/decoding an endless stream of noise after the carrier
// vanishes. Two superframes allow one damaged sync word without losing a
// valid call, while still closing the decoder in about 720 ms.
if (FLastSyncSample <> 0) and
(FSampleNo - FLastSyncSample > 2 * DMR_SUPERFRAME_SAMPLES +
DMR_SYNC_CADENCE_TOLERANCE) then
LoseSync;
Dibit := Digitize(Phase, V);
// Keep the 90 dibits ending at sync. Once the timing phase is selected this
@@ -479,7 +493,6 @@ begin
// match lost almost every superframe in recorded handheld-radio IQ.
DeltaSamples := FSampleNo - FLastSyncSample;
CandidateAllowed := (FLastSyncSample = 0) or
(DeltaSamples > DMR_INPUT_RATE * 2) or
(SYNC_KIND[i] = FSyncKind);
// MS voice/data sync repeats every 360 ms. Cadence gating rejects
// chance <=2-bit matches inside AMBE payload while still accepting a
@@ -494,13 +507,74 @@ begin
end;
if (Distance <= DMR_SYNC_MAX_ERRORS) and CandidateAllowed then
begin
NoteSync(Phase, SYNC_KIND[i], FInputInverted);
// Fuzzy 24-symbol matches occur often enough in open-channel noise to
// make mbelib emit occasional garbage. Real voice sync repeats every
// 360 ms, so require three words of the same kind on that cadence
// before opening burst and vocoder processing.
if FLastSyncSample = 0 then
begin
PendingSample := FPendingSyncSample[SYNC_KIND[i]];
if PendingSample <> 0 then
begin
DeltaSamples := FSampleNo - PendingSample;
CycleOffset := DeltaSamples mod DMR_SUPERFRAME_SAMPLES;
if (DeltaSamples >= DMR_SUPERFRAME_SAMPLES -
DMR_SYNC_CADENCE_TOLERANCE) and
((CycleOffset <= DMR_SYNC_CADENCE_TOLERANCE) or
(CycleOffset >= DMR_SUPERFRAME_SAMPLES -
DMR_SYNC_CADENCE_TOLERANCE)) then
begin
Inc(FPendingSyncCount[SYNC_KIND[i]]);
FPendingSyncSample[SYNC_KIND[i]] := FSampleNo;
// Three consecutive superframe syncs make accidental capture
// in random discriminator noise vanishingly unlikely.
if FPendingSyncCount[SYNC_KIND[i]] >= 3 then
NoteSync(Phase, SYNC_KIND[i], FInputInverted);
end
else if DeltaSamples > DMR_SUPERFRAME_SAMPLES +
DMR_SYNC_CADENCE_TOLERANCE then
begin
FPendingSyncSample[SYNC_KIND[i]] := FSampleNo;
FPendingSyncCount[SYNC_KIND[i]] := 1;
end;
end;
if FLastSyncSample = 0 then
begin
if PendingSample = 0 then
begin
FPendingSyncSample[SYNC_KIND[i]] := FSampleNo;
FPendingSyncCount[SYNC_KIND[i]] := 1;
end;
Exit;
end;
end
else
NoteSync(Phase, SYNC_KIND[i], FInputInverted);
Exit;
end;
end;
end;
end;
procedure TDMRDecoder.LoseSync;
begin
FStatusLock.Enter;
try
FSyncKind := dskNone;
FInverted := False;
FLastSyncTick := 0;
FLastSyncSample := 0;
FillChar(FPendingSyncSample, SizeOf(FPendingSyncSample), 0);
FillChar(FPendingSyncCount, SizeOf(FPendingSyncCount), 0);
finally
FStatusLock.Leave;
end;
FTrackPhase := -1;
FCurrentBurstPos := 0;
FMobileActiveBurst := True;
DMRMbeReset(FMbe);
end;
function TDMRDecoder.Digitize(Phase: Integer; Symbol: Double): Byte;
var
A, Threshold: Double;