feat(dmr): decode and route AMBE voice

This commit is contained in:
2026-07-14 18:55:28 +03:00
parent e0e74b5337
commit 28c434d9a7
7 changed files with 231 additions and 10 deletions
+58
View File
@@ -27,8 +27,11 @@ const
DMR_RING_SIZE = 65536; // >1.3 s at 48 kHz, power of two
DMR_BURST_DIBITS = 144;
DMR_SYNC_END_DIBIT = 89; // sync occupies burst dibits 66..89
DMR_VOICE_FRAME_SAMPLES = DMR_PCM_SAMPLES; // 20 ms at 8 kHz
type
TDMRAudioEvent = procedure(const PCM: array of Single; Count: Integer) of object;
TDMRSyncKind = (
dskNone,
dskBSData, dskBSVoice,
@@ -62,6 +65,9 @@ type
ServiceOptions: Integer;
TargetID: LongWord;
SourceID: LongWord;
SelectedSlot: Integer;
VoiceFrameCount: QWord;
VoiceErrorCount: QWord;
end;
TDMRDecoder = class;
@@ -111,6 +117,9 @@ type
FLastLCSlot: Integer;
FColorCodeValid: Boolean;
FColorCode, FLastDataType, FSlotTypeCorrectedBits: Integer;
FSelectedSlot: Integer;
FVoiceFrameCount, FVoiceErrorCount: QWord;
FOnAudio: TDMRAudioEvent;
FBurstCount: QWord;
FRMSSq: Double;
@@ -127,6 +136,7 @@ type
function Digitize(Phase: Integer; Symbol: Double): Byte;
procedure StartBurstTracking(Phase: Integer);
procedure CompleteBurst;
procedure DecodeVoiceBurst;
procedure ResetDSP;
public
constructor Create;
@@ -140,6 +150,7 @@ type
class function SyncKindName(Kind: TDMRSyncKind): string; static;
property Enabled: Boolean read FEnabled;
property OnAudio: TDMRAudioEvent read FOnAudio write FOnAudio;
end;
implementation
@@ -275,6 +286,10 @@ begin
FColorCode := 0;
FLastDataType := 0;
FSlotTypeCorrectedBits := 0;
FSelectedSlot := -1;
FVoiceFrameCount := 0;
FVoiceErrorCount := 0;
DMRMbeReset(FMbe);
FTrackPhase := -1;
FCurrentBurstPos := 0;
FBurstCount := 0;
@@ -480,11 +495,51 @@ begin
FLastLCSlot := 1
else
FLastLCSlot := 0;
if FSelectedSlot < 0 then FSelectedSlot := FLastLCSlot;
end;
DecodeVoiceBurst;
Inc(FBurstCount);
FCurrentBurstPos := 0;
end;
procedure TDMRDecoder.DecodeVoiceBurst;
var
Frames: TDMRAMBEFrames;
PCM: array[0..DMR_PCM_SAMPLES - 1] of Single;
MbeResult: TDMRMbeResult;
Slot, FrameIndex, RC: Integer;
begin
if (FMbe = nil) or (FLastSyncTick = 0) or
(GetTickCount64 - FLastSyncTick > DMR_SYNC_HOLD_MS) then Exit;
if not (FSyncKind in [dskBSVoice, dskMSVoice, dskDirectTS1Voice,
dskDirectTS2Voice]) then Exit;
if FSyncKind in [dskBSVoice] then
begin
if not FLastBurstInfo.CACH.Valid then Exit;
Slot := FLastBurstInfo.CACH.Slot;
end
else if FSyncKind = dskDirectTS2Voice then Slot := 1
else Slot := 0;
if FSelectedSlot < 0 then FSelectedSlot := Slot;
if Slot <> FSelectedSlot then Exit;
DMRExtractAMBEFrames(FLastBurst, Frames);
for FrameIndex := 0 to 2 do
begin
FillChar(MbeResult, SizeOf(MbeResult), 0);
RC := DMRMbeDecode(FMbe, @Frames[FrameIndex][0], @PCM[0], @MbeResult);
if RC <> 0 then
begin
Inc(FVoiceErrorCount);
Continue;
end;
Inc(FVoiceFrameCount);
if MbeResult.ErrorsTotal > 0 then Inc(FVoiceErrorCount, MbeResult.ErrorsTotal);
if Assigned(FOnAudio) then FOnAudio(PCM, Length(PCM));
end;
end;
procedure TDMRDecoder.NoteSync(Phase: Integer; Kind: TDMRSyncKind;
Inverted: Boolean);
var
@@ -559,6 +614,9 @@ begin
S.ServiceOptions := FLastLinkControl.ServiceOptions;
S.TargetID := FLastLinkControl.TargetID;
S.SourceID := FLastLinkControl.SourceID;
S.SelectedSlot := FSelectedSlot;
S.VoiceFrameCount := FVoiceFrameCount;
S.VoiceErrorCount := FVoiceErrorCount;
finally
FStatusLock.Leave;
end;