mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
fix(dmr): recover handheld voice sync
This commit is contained in:
+10
-1
@@ -71,7 +71,7 @@ const
|
||||
{$ENDIF}
|
||||
var
|
||||
i: Integer;
|
||||
LocalName: string;
|
||||
LocalName, BuildName: string;
|
||||
begin
|
||||
if GLib <> dynlibs.NilHandle then Exit(True);
|
||||
GLastError := '';
|
||||
@@ -79,6 +79,15 @@ begin
|
||||
begin
|
||||
LocalName := ExtractFilePath(ParamStr(0)) + Names[i];
|
||||
GLib := LoadLibrary(LocalName);
|
||||
// Lazarus debug binaries live in bin/<target>/ while the documented
|
||||
// optional adapter build lives in build/dmr. Make an in-tree build
|
||||
// immediately usable without a system-wide cmake --install.
|
||||
if GLib = dynlibs.NilHandle then
|
||||
begin
|
||||
BuildName := ExpandFileName(ExtractFilePath(ParamStr(0)) +
|
||||
'../../build/dmr/' + Names[i]);
|
||||
GLib := LoadLibrary(BuildName);
|
||||
end;
|
||||
if GLib = dynlibs.NilHandle then GLib := LoadLibrary(Names[i]);
|
||||
if GLib <> dynlibs.NilHandle then Break;
|
||||
end;
|
||||
|
||||
+42
-6
@@ -139,6 +139,8 @@ type
|
||||
FInverted: Boolean;
|
||||
FSyncCount: QWord;
|
||||
FLastSyncTick: QWord;
|
||||
FLastSyncSample: QWord;
|
||||
FMobileActiveBurst: Boolean;
|
||||
|
||||
function PopSamples(var Buf: array of Single): Integer;
|
||||
procedure ProcessSample(S: Single);
|
||||
@@ -170,6 +172,8 @@ implementation
|
||||
const
|
||||
DMR_SYNC_MASK = LongWord($00FFFFFF);
|
||||
DMR_SYNC_HOLD_MS = 500;
|
||||
DMR_SYNC_MAX_ERRORS = 2;
|
||||
DMR_SUPERFRAME_SAMPLES = DMR_INPUT_RATE * 360 div 1000;
|
||||
|
||||
// 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.
|
||||
@@ -332,6 +336,8 @@ begin
|
||||
FInverted := False;
|
||||
FSyncCount := 0;
|
||||
FLastSyncTick := 0;
|
||||
FLastSyncSample := 0;
|
||||
FMobileActiveBurst := True;
|
||||
finally
|
||||
FStatusLock.Leave;
|
||||
end;
|
||||
@@ -426,6 +432,8 @@ var
|
||||
Pat: LongWord;
|
||||
Dibit: Byte;
|
||||
V: Double;
|
||||
DeltaSamples, CycleOffset: QWord;
|
||||
CandidateAllowed: Boolean;
|
||||
begin
|
||||
V := Symbol;
|
||||
if FInputInverted then V := -V;
|
||||
@@ -466,11 +474,29 @@ begin
|
||||
FBestSyncPhase := Phase;
|
||||
FBestCandidateKind := SYNC_KIND[i];
|
||||
end;
|
||||
end;
|
||||
if FSyncShift[Phase] = Pat then
|
||||
begin
|
||||
NoteSync(Phase, SYNC_KIND[i], FInputInverted);
|
||||
Exit;
|
||||
// Real on-air sync commonly carries one or two sign errors. DSD-FME
|
||||
// likewise uses a bounded sync tolerance; requiring an exact 24-symbol
|
||||
// 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
|
||||
// superframe after one or more missed sync words.
|
||||
if CandidateAllowed and (FLastSyncSample <> 0) and
|
||||
(FSyncKind in [dskMSData, dskMSVoice]) and
|
||||
(SYNC_KIND[i] = FSyncKind) and (DeltaSamples > 20) then
|
||||
begin
|
||||
CycleOffset := DeltaSamples mod DMR_SUPERFRAME_SAMPLES;
|
||||
CandidateAllowed := (CycleOffset <= 20) or
|
||||
(CycleOffset >= DMR_SUPERFRAME_SAMPLES - 20);
|
||||
end;
|
||||
if (Distance <= DMR_SYNC_MAX_ERRORS) and CandidateAllowed then
|
||||
begin
|
||||
NoteSync(Phase, SYNC_KIND[i], FInputInverted);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -542,6 +568,8 @@ begin
|
||||
end;
|
||||
end;
|
||||
DecodeVoiceBurst;
|
||||
if FSyncKind in [dskMSData, dskMSVoice] then
|
||||
FMobileActiveBurst := not FMobileActiveBurst;
|
||||
Inc(FBurstCount);
|
||||
FCurrentBurstPos := 0;
|
||||
end;
|
||||
@@ -558,6 +586,10 @@ begin
|
||||
(GetTickCount64 - FLastSyncTick > DMR_SYNC_HOLD_MS) then Exit;
|
||||
if not (FSyncKind in [dskBSVoice, dskMSVoice, dskDirectTS1Voice,
|
||||
dskDirectTS2Voice]) then Exit;
|
||||
// Mobile simplex occupies one 30 ms half-slot and leaves the alternating
|
||||
// half-slot idle. A 144-dibit continuous assembler sees both; only every
|
||||
// other record contains the three AMBE frames.
|
||||
if (FSyncKind = dskMSVoice) and not FMobileActiveBurst then Exit;
|
||||
|
||||
if FSyncKind in [dskBSVoice] then
|
||||
begin
|
||||
@@ -566,6 +598,7 @@ begin
|
||||
end
|
||||
else if FSyncKind = dskDirectTS2Voice then Slot := 1
|
||||
else Slot := 0;
|
||||
if FSyncKind = dskMSVoice then FSelectedSlot := 0;
|
||||
NowTick := GetTickCount64;
|
||||
if (FSelectedSlot < 0) or
|
||||
((Slot <> FSelectedSlot) and (FLastSelectedVoiceTick <> 0) and
|
||||
@@ -608,7 +641,8 @@ begin
|
||||
// Adjacent timing hypotheses recognize the same physical burst within one
|
||||
// sample period. Keep the phase with the largest 24-symbol eye opening,
|
||||
// not merely the first phase that happened to match the sign pattern.
|
||||
if (FLastSyncTick <> 0) and (NowTick - FLastSyncTick < 5) then
|
||||
if (FLastSyncSample <> 0) and
|
||||
(FSampleNo - FLastSyncSample <= DMR_SAMPLES_PER_SYM * 2) then
|
||||
begin
|
||||
if FMagSum[Phase] > FBestSyncQuality then
|
||||
begin
|
||||
@@ -623,7 +657,9 @@ begin
|
||||
FInverted := Inverted;
|
||||
Inc(FSyncCount);
|
||||
FLastSyncTick := NowTick;
|
||||
FLastSyncSample := FSampleNo;
|
||||
FBestSyncQuality := FMagSum[Phase];
|
||||
if Kind in [dskMSData, dskMSVoice] then FMobileActiveBurst := True;
|
||||
StartBurstTracking(Phase);
|
||||
finally
|
||||
FStatusLock.Leave;
|
||||
|
||||
@@ -859,6 +859,10 @@ begin
|
||||
// DMR front-end owns a worker thread. It stays idle until MODE_DMR is active.
|
||||
FDMRDiag := TDMRDiagnosticCapture.Create;
|
||||
FDMRDec := TDMRDecoder.Create;
|
||||
// WDSP's FM discriminator polarity on the EWSDR RX path is opposite to the
|
||||
// DMR dibit convention: captured handheld MS voice sync appears as MS data
|
||||
// without this inversion (the two sync words are complements).
|
||||
FDMRDec.SetInverted(True);
|
||||
FDMRDec.OnAudio := OnDMRAudioReady;
|
||||
FDMRDec.OnBurst := OnDMRBurstReady;
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ begin
|
||||
WriteLn('last_sync=', TDMRDecoder.SyncKindName(Status.SyncKind));
|
||||
WriteLn('bursts=', Counter.Count);
|
||||
WriteLn('dropped_samples=', Status.DroppedSamples);
|
||||
WriteLn('selected_slot=', Status.SelectedSlot + 1);
|
||||
WriteLn('voice_frames=', Status.VoiceFrameCount);
|
||||
WriteLn('voice_errors=', Status.VoiceErrorCount);
|
||||
WriteLn('vocoder_available=', Status.VocoderAvailable);
|
||||
if Status.CACHValid then WriteLn('timeslot=', Status.Slot + 1);
|
||||
if Status.SlotTypeValid then
|
||||
WriteLn('color_code=', Status.ColorCode, ' data_type=', Status.DataType);
|
||||
|
||||
Reference in New Issue
Block a user