mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix(dmr): recover handheld voice sync
This commit is contained in:
+10
-1
@@ -71,7 +71,7 @@ const
|
|||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
LocalName: string;
|
LocalName, BuildName: string;
|
||||||
begin
|
begin
|
||||||
if GLib <> dynlibs.NilHandle then Exit(True);
|
if GLib <> dynlibs.NilHandle then Exit(True);
|
||||||
GLastError := '';
|
GLastError := '';
|
||||||
@@ -79,6 +79,15 @@ begin
|
|||||||
begin
|
begin
|
||||||
LocalName := ExtractFilePath(ParamStr(0)) + Names[i];
|
LocalName := ExtractFilePath(ParamStr(0)) + Names[i];
|
||||||
GLib := LoadLibrary(LocalName);
|
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 GLib := LoadLibrary(Names[i]);
|
||||||
if GLib <> dynlibs.NilHandle then Break;
|
if GLib <> dynlibs.NilHandle then Break;
|
||||||
end;
|
end;
|
||||||
|
|||||||
+38
-2
@@ -139,6 +139,8 @@ type
|
|||||||
FInverted: Boolean;
|
FInverted: Boolean;
|
||||||
FSyncCount: QWord;
|
FSyncCount: QWord;
|
||||||
FLastSyncTick: QWord;
|
FLastSyncTick: QWord;
|
||||||
|
FLastSyncSample: QWord;
|
||||||
|
FMobileActiveBurst: Boolean;
|
||||||
|
|
||||||
function PopSamples(var Buf: array of Single): Integer;
|
function PopSamples(var Buf: array of Single): Integer;
|
||||||
procedure ProcessSample(S: Single);
|
procedure ProcessSample(S: Single);
|
||||||
@@ -170,6 +172,8 @@ implementation
|
|||||||
const
|
const
|
||||||
DMR_SYNC_MASK = LongWord($00FFFFFF);
|
DMR_SYNC_MASK = LongWord($00FFFFFF);
|
||||||
DMR_SYNC_HOLD_MS = 500;
|
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
|
// 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.
|
// ETSI DMR 48-bit sync patterns after collapsing each dibit to its sign.
|
||||||
@@ -332,6 +336,8 @@ begin
|
|||||||
FInverted := False;
|
FInverted := False;
|
||||||
FSyncCount := 0;
|
FSyncCount := 0;
|
||||||
FLastSyncTick := 0;
|
FLastSyncTick := 0;
|
||||||
|
FLastSyncSample := 0;
|
||||||
|
FMobileActiveBurst := True;
|
||||||
finally
|
finally
|
||||||
FStatusLock.Leave;
|
FStatusLock.Leave;
|
||||||
end;
|
end;
|
||||||
@@ -426,6 +432,8 @@ var
|
|||||||
Pat: LongWord;
|
Pat: LongWord;
|
||||||
Dibit: Byte;
|
Dibit: Byte;
|
||||||
V: Double;
|
V: Double;
|
||||||
|
DeltaSamples, CycleOffset: QWord;
|
||||||
|
CandidateAllowed: Boolean;
|
||||||
begin
|
begin
|
||||||
V := Symbol;
|
V := Symbol;
|
||||||
if FInputInverted then V := -V;
|
if FInputInverted then V := -V;
|
||||||
@@ -466,14 +474,32 @@ begin
|
|||||||
FBestSyncPhase := Phase;
|
FBestSyncPhase := Phase;
|
||||||
FBestCandidateKind := SYNC_KIND[i];
|
FBestCandidateKind := SYNC_KIND[i];
|
||||||
end;
|
end;
|
||||||
|
// 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;
|
end;
|
||||||
if FSyncShift[Phase] = Pat then
|
if (Distance <= DMR_SYNC_MAX_ERRORS) and CandidateAllowed then
|
||||||
begin
|
begin
|
||||||
NoteSync(Phase, SYNC_KIND[i], FInputInverted);
|
NoteSync(Phase, SYNC_KIND[i], FInputInverted);
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function TDMRDecoder.Digitize(Phase: Integer; Symbol: Double): Byte;
|
function TDMRDecoder.Digitize(Phase: Integer; Symbol: Double): Byte;
|
||||||
var
|
var
|
||||||
@@ -542,6 +568,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
DecodeVoiceBurst;
|
DecodeVoiceBurst;
|
||||||
|
if FSyncKind in [dskMSData, dskMSVoice] then
|
||||||
|
FMobileActiveBurst := not FMobileActiveBurst;
|
||||||
Inc(FBurstCount);
|
Inc(FBurstCount);
|
||||||
FCurrentBurstPos := 0;
|
FCurrentBurstPos := 0;
|
||||||
end;
|
end;
|
||||||
@@ -558,6 +586,10 @@ begin
|
|||||||
(GetTickCount64 - FLastSyncTick > DMR_SYNC_HOLD_MS) then Exit;
|
(GetTickCount64 - FLastSyncTick > DMR_SYNC_HOLD_MS) then Exit;
|
||||||
if not (FSyncKind in [dskBSVoice, dskMSVoice, dskDirectTS1Voice,
|
if not (FSyncKind in [dskBSVoice, dskMSVoice, dskDirectTS1Voice,
|
||||||
dskDirectTS2Voice]) then Exit;
|
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
|
if FSyncKind in [dskBSVoice] then
|
||||||
begin
|
begin
|
||||||
@@ -566,6 +598,7 @@ begin
|
|||||||
end
|
end
|
||||||
else if FSyncKind = dskDirectTS2Voice then Slot := 1
|
else if FSyncKind = dskDirectTS2Voice then Slot := 1
|
||||||
else Slot := 0;
|
else Slot := 0;
|
||||||
|
if FSyncKind = dskMSVoice then FSelectedSlot := 0;
|
||||||
NowTick := GetTickCount64;
|
NowTick := GetTickCount64;
|
||||||
if (FSelectedSlot < 0) or
|
if (FSelectedSlot < 0) or
|
||||||
((Slot <> FSelectedSlot) and (FLastSelectedVoiceTick <> 0) and
|
((Slot <> FSelectedSlot) and (FLastSelectedVoiceTick <> 0) and
|
||||||
@@ -608,7 +641,8 @@ begin
|
|||||||
// Adjacent timing hypotheses recognize the same physical burst within one
|
// Adjacent timing hypotheses recognize the same physical burst within one
|
||||||
// sample period. Keep the phase with the largest 24-symbol eye opening,
|
// sample period. Keep the phase with the largest 24-symbol eye opening,
|
||||||
// not merely the first phase that happened to match the sign pattern.
|
// 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
|
begin
|
||||||
if FMagSum[Phase] > FBestSyncQuality then
|
if FMagSum[Phase] > FBestSyncQuality then
|
||||||
begin
|
begin
|
||||||
@@ -623,7 +657,9 @@ begin
|
|||||||
FInverted := Inverted;
|
FInverted := Inverted;
|
||||||
Inc(FSyncCount);
|
Inc(FSyncCount);
|
||||||
FLastSyncTick := NowTick;
|
FLastSyncTick := NowTick;
|
||||||
|
FLastSyncSample := FSampleNo;
|
||||||
FBestSyncQuality := FMagSum[Phase];
|
FBestSyncQuality := FMagSum[Phase];
|
||||||
|
if Kind in [dskMSData, dskMSVoice] then FMobileActiveBurst := True;
|
||||||
StartBurstTracking(Phase);
|
StartBurstTracking(Phase);
|
||||||
finally
|
finally
|
||||||
FStatusLock.Leave;
|
FStatusLock.Leave;
|
||||||
|
|||||||
@@ -859,6 +859,10 @@ begin
|
|||||||
// DMR front-end owns a worker thread. It stays idle until MODE_DMR is active.
|
// DMR front-end owns a worker thread. It stays idle until MODE_DMR is active.
|
||||||
FDMRDiag := TDMRDiagnosticCapture.Create;
|
FDMRDiag := TDMRDiagnosticCapture.Create;
|
||||||
FDMRDec := TDMRDecoder.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.OnAudio := OnDMRAudioReady;
|
||||||
FDMRDec.OnBurst := OnDMRBurstReady;
|
FDMRDec.OnBurst := OnDMRBurstReady;
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ begin
|
|||||||
WriteLn('last_sync=', TDMRDecoder.SyncKindName(Status.SyncKind));
|
WriteLn('last_sync=', TDMRDecoder.SyncKindName(Status.SyncKind));
|
||||||
WriteLn('bursts=', Counter.Count);
|
WriteLn('bursts=', Counter.Count);
|
||||||
WriteLn('dropped_samples=', Status.DroppedSamples);
|
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.CACHValid then WriteLn('timeslot=', Status.Slot + 1);
|
||||||
if Status.SlotTypeValid then
|
if Status.SlotTypeValid then
|
||||||
WriteLn('color_code=', Status.ColorCode, ' data_type=', Status.DataType);
|
WriteLn('color_code=', Status.ColorCode, ' data_type=', Status.DataType);
|
||||||
|
|||||||
Reference in New Issue
Block a user