mirror of
https://github.com/lwvmobile/dsd-fme.git
synced 2026-11-13 01:07:24 -05:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69d31151dd | ||
|
|
6fd7176659 | ||
|
|
c960f9932c | ||
|
|
e877e7b9de | ||
|
|
43d63b20dc | ||
|
|
e6d2dd7934 | ||
|
|
8c3ae61b1a | ||
|
|
f443063d3b | ||
|
|
ed1d1d630c | ||
|
|
56f6f6d78e | ||
|
|
a6bdfa913f | ||
|
|
ad77f266c4 | ||
|
|
f72eb31b30 | ||
|
|
441ba31d1b | ||
|
|
7d94abd76a | ||
|
|
7b89174bb1 | ||
|
|
f6f5f80361 | ||
|
|
b7cd501cc1 | ||
|
|
9f03ad123d | ||
|
|
d3303aea28 | ||
|
|
615f67536f | ||
|
|
12012a9eee | ||
|
|
53a256bee8 | ||
|
|
d7ce5e4cae | ||
|
|
5af38d3563 | ||
|
|
034855069a | ||
|
|
3ee5acef0a | ||
|
|
b5bd9a7238 | ||
|
|
56b491ed3e | ||
|
|
a26a680fd6 | ||
|
|
4ec2387140 | ||
|
|
385a9a1747 | ||
|
|
2267f5d007 | ||
|
|
99f422898a |
@@ -39,6 +39,62 @@ if (LZ)
|
||||
add_definitions(-DLIMAZULUTWEAKS)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DASCII=ON to enable ASCII only Alias Support in NXDN
|
||||
option(ASCII
|
||||
"Build with ASCII only Alias Support in NXDN Enabled" ON)
|
||||
if (ASCII)
|
||||
add_definitions(-DASCII_DECODE)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DASCII=OFF -DBIG5=ON to enable ASCII and BIG5 Alias Support in NXDN
|
||||
option(BIG5
|
||||
"Build with BIG5 and ASCII Alias Support in NXDN Enabled" OFF)
|
||||
if (BIG5)
|
||||
add_definitions(-DBIG5_DECODE)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DASCII=OFF -DSJIS=ON to enable ASCII and SJIS Alias Support in NXDN
|
||||
option(SJIS
|
||||
"Build with SJIS and ASCII Alias Support in NXDN Enabled" OFF)
|
||||
if (SJIS)
|
||||
add_definitions(-DSJIS_DECODE)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DASCII=OFF -DUTF16=ON to enable ASCII and UTF16BE Alias Support in NXDN
|
||||
option(UTF16
|
||||
"Build with UTF16BE and ASCII Alias Support in NXDN Enabled" OFF)
|
||||
if (UTF16)
|
||||
add_definitions(-DUTF16_DECODE)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DTYPEC=OFF to disable NXDN Type-C and Conventional Decoding (enabled by default)
|
||||
option(TYPEC
|
||||
"Build with NXDN Type-C and Conventional Decoding Enabled" ON)
|
||||
if (TYPEC)
|
||||
add_definitions(-DTYPE_C_DECODE)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DTYPED=OFF to disable NXDN Type-D / IDAS Decoding (enabled by default)
|
||||
option(TYPED
|
||||
"Build with NXDN Type-D / IDAS Decoding Enabled" ON)
|
||||
if (TYPED)
|
||||
add_definitions(-DTYPE_D_DECODE)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DDCR=OFF to disable Japanese DCR (NXDN Variant) Decoding (enabled by default)
|
||||
option(DCR
|
||||
"Build with Japanese DCR (NXDN Variant) Decoding Enabled" ON)
|
||||
if (DCR)
|
||||
add_definitions(-DJPNDCR_DECODE)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DNOS=ON to enable older NXDN sync w/ pos and inv polarity, disable newer seperated (disabled by default)
|
||||
option(NOS
|
||||
"Build with Old NXDN Sync (pos and inv polarity) Enabled" OFF)
|
||||
if (NOS)
|
||||
add_definitions(-DNXDN_OLD_SYNC)
|
||||
endif ()
|
||||
|
||||
#use cmake option -DSID=ON to enable P25p1 Soft ID decoding -- Experimental
|
||||
option(SID
|
||||
"Build with P25p1 LSD/Soft ID Enabled" OFF)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-f2 P25P2 (may need to specify wacn/sys/nac manually if traffic channel)
|
||||
-fx X2-TDMA
|
||||
-fy YSF
|
||||
-fz M17 Audio
|
||||
-fz M17 Stream
|
||||
```
|
||||
|
||||
## Conventional Frequency Scanner
|
||||
@@ -190,7 +190,7 @@ y - toggle scanner (needs either rtl input, or rigctl connection)
|
||||
1 - Toggle Voice Synthesis in TDMA Slot 1 or FDMA Conventional Voice
|
||||
2 - Toggle Voice Synthesis in TDMA Slot 2
|
||||
w - Toggle Trunking/Playback White List (Allow A Groups Only) / Black List (Block B or DE groups only) Mode
|
||||
g - Toggle Trunking Tuning to Group Calls (DMR T3, Con+, Cap+, P25, NXDN)
|
||||
T - Toggle Trunking Tuning to Group Calls (DMR T3, Con+, Cap+, P25, NXDN)
|
||||
u - Toggle Trunking Tuning to Private Calls (DMR T3, Cap+, P25)
|
||||
d - Toggle Trunking Tuning to Data Calls (DMR T3, NXDN)
|
||||
e - Toggle Trunking Tuning to Encrypted Calls (P25)
|
||||
@@ -222,6 +222,12 @@ N - Toggle Passband Filter on Analog Voice or Raw Signal Monitoring
|
||||
|
||||
v - Cycle RTL Input Volume Multiplier 1X, 2X, 3X (lower v)
|
||||
|
||||
g - Manually decrease RTL gain
|
||||
G - Manually increase RTL gain
|
||||
|
||||
{ - Manually decrease PPM
|
||||
} - Manually increase PPM
|
||||
|
||||
```
|
||||
|
||||
## Sending Audio to a Icecast 2 Server via FFmpeg (Windows)
|
||||
@@ -311,70 +317,3 @@ On the remote side where dsd-fme is, use the TCP input:
|
||||
`dsd-fme -i tcp:192.168.7.8:1234 -N`
|
||||
|
||||
Note: As always, when using rtl_fm, play with `-l` and `-g` to find a good squelch and gain values for your environment - you will know if the squelch is the right value when `input.wav` does not increase in size in the absence of useful signal on the monitored frequency.
|
||||
|
||||
## Using the Experimental M17 Encoder (and Decoder)
|
||||
|
||||
You can use the experimental M17 Voice Stream and Packet encoder with the following examples.
|
||||
|
||||
Encode/Decode Local Feedback Voice Stream (Single Session):
|
||||
|
||||
`dsd-fme -fZ -i pulse -o pulse -M M17:3:SP5WWP:BROADCAST:48000`
|
||||
|
||||
Using the `-8` option (raw audio monitor) will instead send encoded raw audio to output instead of decoded audio. For example,
|
||||
|
||||
To send a voice stream out of stdout into SOCAT TCP to a second/remote DSD-FME session to decode:
|
||||
|
||||
Encode (Upper Z):
|
||||
|
||||
`dsd-fme -fZ -8 -i pulse -M M17:9:LWVMOBILE:DSD-FME:48000 -o - | socat stdio tcp-listen:7355,forever,reuseaddr`
|
||||
|
||||
Decode (Lower z):
|
||||
|
||||
`dsd-fme -fz -i tcp:192.168.7.8:7355`
|
||||
|
||||
NOTE: When using tcp input for M17 decoding, DSD-FME will keep the TCP connection alive with constant retries, so no need to restart the decoding session on connection failure.
|
||||
|
||||
SMS Packet Encoder (Text Only):
|
||||
|
||||
`dsd-fme -fP -8 -S "This is a sample message sent over M17 Packet Data using DSD-FME to encode it."`
|
||||
|
||||
SMS Packet Encoder (over SOCAT TCP):
|
||||
|
||||
`dsd-fme -fP -8 -S "This is a sample message sent over M17 Packet Data using DSD-FME to encode it and SOCAT to send it over TCP." -o - | socat stdio tcp-listen:7355,forever,reuseaddr`
|
||||
|
||||
Many combinations of input and output can be used as well, and using features like raw audio saving with `-6 m17str.wav` will save a wav file of your encoded audio to be replayed later with `-i m17str.wav`
|
||||
Also note, features function similarly, like -Z for payload, and input options include pulse, oss, stdin, rtl, tcp, etc, using those as the mic in for voice encoding. With the right setup of input/output/input using SOCAT TCP pipes, it is conceivable to use DSD-FME to convert analog audio from NFM into M17 audio, or even change another digital format supported by DSD-FME into M17 audio, if desired (purely for research and fun purposes). Ncurses terminal will function with internal encoder/decoder and wav file creation, with toggle encode/tx with `\` key, but will not function with stdout properly.
|
||||
|
||||
The `-M` switch is used to specify user M17 variables, the string is in the format of M17:CAN:SRC:DST:INPUT_RATE.
|
||||
CAN 1-15; SRC and DST have to be no more than 9 UPPER base40 characters.
|
||||
BASE40: ' ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/.'
|
||||
Input Rate Default is 48000; Use Multiples of 8000 up to 48000.
|
||||
Values not entered into the M17: string are set to default values. M17:7:DSD-FME:DSD-FME:48000
|
||||
|
||||
Fun Example Transmuting OTA Analog Things into M17:
|
||||
|
||||
Start up in sequence.
|
||||
|
||||
Terminal #1 (RTL Input):
|
||||
`dsd-fme -fZ -8 -M M17:1:ANALOG:M17:48000 -i rtl:0:162.425M -o - | socat stdio tcp-listen:7310,forever,reuseaddr`
|
||||
|
||||
Terminal #2:
|
||||
`dsd-fme -fz -i tcp:127.0.0.1:7310`
|
||||
|
||||
Fun Example Transmuting OTA Digital Things into M17:
|
||||
|
||||
Start up all three of these in sequence.
|
||||
|
||||
Terminal #1 (connecting to SDR++):
|
||||
`dsd-fme -fi -i tcp:127.0.0.1:7355 -o - | socat stdio tcp-listen:7300,forever,reuseaddr`
|
||||
|
||||
Terminal #2:
|
||||
`dsd-fme -fZ -8 -M M17:2:NXDN:M17:8000 -i tcp:127.0.0.1:7300 -o - | socat stdio tcp-listen:7301,forever,reuseaddr`
|
||||
|
||||
Terminal #3:
|
||||
`dsd-fme -fz -i tcp:127.0.0.1:7301`
|
||||
|
||||
Note: Transmuting only carries over voice when audio is output, and will not carry any ID values, or any other data payloads.
|
||||
This mode is only meant for educational or fun purposes, please do not retransmit stuff that doesn't belong to you.
|
||||
|
||||
Information on this section will be updated as features are added or modified, etc. If you need help with a particular function and can't find a suitable answer, always open a new issue here, or make contact on the radio reference forum for additional examples, guidance, ideas, help, etc.
|
||||
@@ -1,4 +1,4 @@
|
||||
Build Version: AW 2025-209-g14edf32
|
||||
Build Version: AW 2026-30-g43d63b2
|
||||
MBElib Version: 1.3.4
|
||||
CODEC2 Support Enabled
|
||||
|
||||
@@ -50,6 +50,8 @@ Input/Output options:
|
||||
-w <file> Output synthesized speech to a single static .wav file. (Do not use with -P Per Call Switch)
|
||||
-P Enable Per Call WAV file saving. (Do not use with -w filename.wav single wav file switch)
|
||||
(Per Call works with everything now and doesn't require ncurses terminal!)
|
||||
-j <string> Custom Tag(s), colon delimited, to append to Per Call decoded .wav files.
|
||||
-j TGT_LAPD:TAG_FIRE:FREQ_851987500 (Total 3 Tags, Maximum Letter Length = 100)
|
||||
-a Enable Call Alert Beep
|
||||
(Warning! Might be annoying.)
|
||||
-J <file> Specify Filename for Event Log Output.
|
||||
@@ -103,6 +105,7 @@ Decoder options:
|
||||
-xx Expect non-inverted X2-TDMA signal
|
||||
-xr Expect inverted DMR signal
|
||||
-xd Expect inverted ICOM dPMR signal
|
||||
-xn Expect inverted NXDN/DCR/IDAS signal
|
||||
|
||||
* denotes frame types that cannot be auto-detected.
|
||||
** Phase 2 Single Frequency may require user to manually set WACN/SYSID/CC parameters if MAC_SIGNAL not present.
|
||||
@@ -220,5 +223,4 @@ Advanced Decoder options:
|
||||
-t <secs> Set Trunking or Scan Speed VC/sync loss hangtime in seconds. (default = 1 second)
|
||||
|
||||
Trunking Example TCP: dsd-fme -fs -i tcp -U 4532 -T -C dmr_t3_chan.csv -G group.csv -N 2> log.txt
|
||||
Trunking Example RTL: dsd-fme -fs -i rtl:0:450M:26:-2:8 -T -C connect_plus_chan.csv -G group.csv -N 2> log.txt
|
||||
|
||||
Trunking Example RTL: dsd-fme -fs -i rtl:0:450M:26:-2:8 -T -C connect_plus_chan.csv -G group.csv -N 2> log.txt
|
||||
@@ -173,8 +173,8 @@ typedef struct pa_devicelist
|
||||
typedef struct
|
||||
{
|
||||
unsigned long int groupNumber;
|
||||
char groupMode[8]; //char *?
|
||||
char groupName[50];
|
||||
char groupMode[3];
|
||||
char groupName[100];
|
||||
} groupinfo;
|
||||
|
||||
|
||||
@@ -324,6 +324,7 @@ typedef struct
|
||||
char wav_out_file[1024];
|
||||
char wav_out_fileR[1024];
|
||||
char wav_out_file_raw[1024];
|
||||
char wav_custom_tag[100];
|
||||
char symbol_out_file[1024];
|
||||
char lrrp_out_file[1024];
|
||||
char event_out_file[1024];
|
||||
@@ -354,6 +355,7 @@ typedef struct
|
||||
int uvquality;
|
||||
int inverted_x2tdma;
|
||||
int inverted_dmr;
|
||||
int inverted_nxdn;
|
||||
int mod_threshold;
|
||||
int ssize;
|
||||
int msize;
|
||||
@@ -363,6 +365,7 @@ typedef struct
|
||||
int unmute_encrypted_p25;
|
||||
int rtl_dev_index;
|
||||
int rtl_gain_value;
|
||||
int rtl_gain_actual;
|
||||
int rtl_squelch_level;
|
||||
int rtl_volume_multiplier;
|
||||
int rtl_udp_port;
|
||||
@@ -869,7 +872,7 @@ typedef struct
|
||||
//trunking group and lcn freq list
|
||||
long int trunk_lcn_freq[26]; //max number on an EDACS system, should be enough on DMR too hopefully
|
||||
long int trunk_chan_map[0xFFFF]; //NXDN - 10 bit; P25 - 16 bit; DMR up to 12 bit (standard TIII)
|
||||
groupinfo group_array[0x3FF]; //max supported by Cygwin is 3FFF, I hope nobody actually tries to import this many groups
|
||||
groupinfo * group_array;
|
||||
unsigned int group_tally; //tally number of groups imported from CSV file for referencing later
|
||||
int lcn_freq_count;
|
||||
int lcn_freq_roll; //number we have 'rolled' to in search of the CC
|
||||
@@ -1183,7 +1186,7 @@ void openWavOutFileLR (dsd_opts * opts, dsd_state * state); //stereo wav file fo
|
||||
void openWavOutFileRaw (dsd_opts * opts, dsd_state * state);
|
||||
SNDFILE * open_wav_file (char * dir, char * temp_filename, uint16_t sample_rate, uint8_t ext);
|
||||
SNDFILE * close_wav_file(SNDFILE * wav_file);
|
||||
SNDFILE * close_and_rename_wav_file(SNDFILE * wav_file, char * wav_out_filename, char * dir, Event_History_I * event_struct);
|
||||
SNDFILE * close_and_rename_wav_file(SNDFILE * wav_file, char * wav_out_filename, char * dir, char * custom_tag, Event_History_I * event_struct);
|
||||
SNDFILE * close_and_delete_wav_file(SNDFILE * wav_file, char * wav_out_filename);
|
||||
void openSymbolOutFile (dsd_opts * opts, dsd_state * state);
|
||||
void closeSymbolOutFile (dsd_opts * opts, dsd_state * state);
|
||||
@@ -1262,25 +1265,25 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state);
|
||||
uint8_t ncurses_input_handler(dsd_opts * opts, dsd_state * state, int c);
|
||||
void ncursesClose ();
|
||||
|
||||
//new NXDN Functions start here!
|
||||
//NXDN Functions Start Here
|
||||
void nxdn_frame (dsd_opts * opts, dsd_state * state);
|
||||
void nxdn_pn95_dibit_scrambler(dsd_state * state, uint8_t * dibits, int len);
|
||||
//nxdn deinterleaving/depuncturing functions
|
||||
void nxdn_deperm_facch (dsd_opts * opts, dsd_state * state, uint8_t bits[144]);
|
||||
void nxdn_deperm_sacch (dsd_opts * opts, dsd_state * state, uint8_t bits[60]);
|
||||
void nxdn_deperm_cac (dsd_opts * opts, dsd_state * state, uint8_t bits[300]);
|
||||
void nxdn_deperm_facch2_udch (dsd_opts * opts, dsd_state * state, uint8_t bits[348], uint8_t type);
|
||||
//type-d 'idas' deinterleaving/depuncturing functions
|
||||
void nxdn_deperm_scch(dsd_opts * opts, dsd_state * state, uint8_t bits[60], uint8_t direction);
|
||||
void nxdn_deperm_facch3_udch2(dsd_opts * opts, dsd_state * state, uint8_t bits[288], uint8_t type);
|
||||
//DCR Mode
|
||||
void nxdn_deperm_sacch2(dsd_opts * opts, dsd_state * state, uint8_t bits[60]);
|
||||
void nxdn_deperm_pich_tch(dsd_opts * opts, dsd_state * state, uint8_t bits[144], uint8_t lich);
|
||||
//MT and Voice
|
||||
void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType);
|
||||
void nxdn_voice (dsd_opts * opts, dsd_state * state, int voice, uint8_t dbuf[182]);
|
||||
//Osmocom OP25 12 Rate Trellis Decoder (for NXDN, M17, YSF, etc)
|
||||
void trellis_decode(uint8_t result[], const uint8_t source[], int result_len);
|
||||
void nxdn_voice (dsd_opts * opts, dsd_state * state, int voice, uint8_t * dbuf);
|
||||
|
||||
//All-in-One NXDN Soft Decision Viterbi Function based on libM17
|
||||
uint32_t nxdn_soft_decision_viterbi(uint8_t * bits, const uint16_t * interleave, uint8_t * puncture, int d_len, int p_len, int num_bytes, int offset, uint8_t * viterbi_bits, uint8_t * viterbi_bytes);
|
||||
//NXDN Conventional and Type-C
|
||||
void nxdn_facch1(dsd_opts * opts, dsd_state * state, uint8_t * bits, uint8_t frame);
|
||||
void nxdn_sacch(dsd_opts * opts, dsd_state * state, uint8_t * bits);
|
||||
void nxdn_cac(dsd_opts * opts, dsd_state * state, uint8_t * bits);
|
||||
void nxdn_facch2_udch(dsd_opts * opts, dsd_state * state, uint8_t * bits, uint8_t type);
|
||||
//type-d 'idas'
|
||||
void idas_scch(dsd_opts * opts, dsd_state * state, uint8_t * bits, uint8_t direction);
|
||||
void idas_facch3_udch2(dsd_opts * opts, dsd_state * state, uint8_t * bits, uint8_t type);
|
||||
//Japanese DCR
|
||||
void dcr_sacch(dsd_opts * opts, dsd_state * state, uint8_t * bits);
|
||||
void dcr_pich_tch(dsd_opts * opts, dsd_state * state, uint8_t * bits, uint8_t lich);
|
||||
|
||||
//OP25 NXDN CRC functions
|
||||
int load_i(const uint8_t val[], int len);
|
||||
@@ -1289,13 +1292,10 @@ uint16_t crc12f(const uint8_t buf[], int len);
|
||||
uint16_t crc15(const uint8_t buf[], int len);
|
||||
uint16_t crc16cac(const uint8_t buf[], int len);
|
||||
uint8_t crc7_scch(uint8_t bits[], int len); //converted from op25 crc6
|
||||
uint32_t nxdn_message_crc32(uint8_t * input, int len);
|
||||
|
||||
/* NXDN Convolution functions */
|
||||
void CNXDNConvolution_start(void);
|
||||
void CNXDNConvolution_decode(uint8_t s0, uint8_t s1);
|
||||
void CNXDNConvolution_chainback(unsigned char* out, unsigned int nBits);
|
||||
void CNXDNConvolution_encode(const unsigned char* in, unsigned char* out, unsigned int nBits);
|
||||
void CNXDNConvolution_init();
|
||||
//YSF Soft Decision Viterbi
|
||||
uint32_t ysf_soft_decision_viterbi(uint8_t * dbuf, int d_len, int num_bytes, int offset, uint8_t * viterbi_bits, uint8_t * viterbi_bytes);
|
||||
|
||||
//libM17 viterbi decoder
|
||||
|
||||
@@ -1311,6 +1311,9 @@ uint32_t viterbi_chainback(uint8_t* out, size_t pos, uint16_t len);
|
||||
void viterbi_reset(void);
|
||||
uint16_t q_abs_diff(const uint16_t v1, const uint16_t v2);
|
||||
|
||||
//libm17 based soft decision viterbi for variable len and new punctures
|
||||
void slice_symbols_to_len(uint16_t * out, const float * inp, int len);
|
||||
|
||||
//keeping these
|
||||
void NXDN_SACCH_Full_decode(dsd_opts * opts, dsd_state * state);
|
||||
void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state,
|
||||
@@ -1319,19 +1322,28 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_VCALL_IV(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
char * NXDN_Call_Type_To_Str(uint8_t CallType);
|
||||
void NXDN_Voice_Call_Option_To_Str(uint8_t VoiceCallOption, uint8_t * Duplex, uint8_t * TransmissionMode);
|
||||
void NXDN_Data_Call_Option_To_Str(uint8_t DataCallOption, uint8_t * Duplex, uint8_t * TransmissionMode);
|
||||
char * NXDN_Cipher_Type_To_Str(uint8_t CipherType);
|
||||
//added these
|
||||
void NXDN_decode_Prop(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_Alias(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_Alias16(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_cch_info(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_srv_info(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_site_info(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void nxdn_decode_dst_info(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_adj_site(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void nxdn_sdcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void nxdn_sdcall_iv(dsd_opts * opts, dsd_state * state, int type, uint8_t * Message);
|
||||
void nxdn_dcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Message);
|
||||
//Type-D SCCH Message Decoder
|
||||
void NXDN_decode_scch(dsd_opts * opts, dsd_state * state, uint8_t * Message, uint8_t direction);
|
||||
void NXDN_decode_VCALL_ARIB(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
void NXDN_decode_ALIAS_ARIB(dsd_opts * opts, dsd_state * state, uint8_t * Message);
|
||||
uint32_t sjis_char_to_unicode(uint16_t sjis);
|
||||
uint32_t big5_char_to_unicode(uint16_t big5);
|
||||
int utf8_encode(int c, char *buf);
|
||||
|
||||
void dPMRVoiceFrameProcess(dsd_opts * opts, dsd_state * state);
|
||||
@@ -1372,7 +1384,7 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C
|
||||
void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8_t cs_pdu[], uint32_t CRCCorrect, uint32_t IrrecoverableErrors);
|
||||
void dmr_slco (dsd_opts * opts, dsd_state * state, uint8_t slco_bits[]);
|
||||
uint8_t dmr_cach (dsd_opts * opts, dsd_state * state, uint8_t cach_bits[25]);
|
||||
uint32_t dmr_34(uint8_t * input, uint8_t treturn[18]); //simplier trellis decoder
|
||||
uint32_t viterbi_r34 (uint8_t * input, uint8_t * output);
|
||||
void beeper (dsd_opts * opts, dsd_state * state, int lr, int id, int ad, int len);
|
||||
void dmr_gateway_identifier (uint32_t source, uint32_t target); //translate special addresses
|
||||
|
||||
@@ -1394,10 +1406,11 @@ void dmr_embedded_gps (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[]);
|
||||
void apx_embedded_gps (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[]);
|
||||
void lip_protocol_decoder (dsd_opts * opts, dsd_state * state, uint8_t * input);
|
||||
void nmea_iec_61162_1 (dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src, int type);
|
||||
void nmea_harris (dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src, int slot);
|
||||
void harris_gps(dsd_opts * opts, dsd_state * state, int slot, uint8_t * input);
|
||||
void harris_lptt (dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src, int slot, int phase);
|
||||
void nxdn_gps_report(dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src);
|
||||
void utf16_to_text (dsd_state * state, uint8_t wr, uint16_t len, uint8_t * input);
|
||||
void utf8_to_text (dsd_state * state, uint8_t wr, uint16_t len, uint8_t * input);
|
||||
uint8_t nmea_sentence_checker(dsd_opts * opts, dsd_state * state, uint8_t * input, uint8_t slot, int len);
|
||||
|
||||
//"DMR STEREO"
|
||||
void dmrBSBootstrap (dsd_opts * opts, dsd_state * state);
|
||||
@@ -1606,6 +1619,9 @@ int udp_socket_connectA(dsd_opts * opts, dsd_state * state);
|
||||
void udp_socket_blaster(dsd_opts * opts, dsd_state * state, size_t nsam, void * data);
|
||||
void udp_socket_blasterA(dsd_opts * opts, dsd_state * state, size_t nsam, void * data);
|
||||
|
||||
//15-bit Scrambler
|
||||
void pdu_scrambler_keystream_creation(uint8_t * ks, int lfsr, int len);
|
||||
|
||||
//RC4 function prototypes
|
||||
void rc4_voice_decrypt (int drop, uint8_t keylength, uint8_t messagelength, uint8_t key[], uint8_t cipher[], uint8_t plain[]);
|
||||
void rc4_block_output (int drop, int keylen, int meslen, uint8_t * key, uint8_t * output_blocks);
|
||||
@@ -1648,6 +1664,7 @@ void lfsr_64_to_128(uint8_t * iv);
|
||||
void LFSR128(dsd_state * state);
|
||||
void LFSR128n(dsd_state * state);
|
||||
void LFSR128d(dsd_state * state);
|
||||
void LFSR128npdu(dsd_state * state);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
// 02110-1301, USA.
|
||||
|
||||
static const uint8_t PERM_12_5[] = {
|
||||
static const uint16_t PERM_12_5[] = {
|
||||
0,12,24,36,48,
|
||||
1,13,25,37,49,
|
||||
2,14,26,38,50,
|
||||
@@ -31,7 +31,7 @@ static const uint8_t PERM_12_5[] = {
|
||||
11,23,35,47,59
|
||||
};
|
||||
|
||||
static const uint8_t PERM_16_9[] = {
|
||||
static const uint16_t PERM_16_9[] = {
|
||||
0, 16, 32, 48, 64, 80, 96, 112, 128,
|
||||
1, 17, 33, 49, 65, 81, 97, 113, 129,
|
||||
2, 18, 34, 50, 66, 82, 98, 114, 130,
|
||||
|
||||
476
src/dmr_34.c
476
src/dmr_34.c
@@ -1,12 +1,35 @@
|
||||
/*-------------------------------------------------------------------------------
|
||||
* dmr_34.c
|
||||
* DMR (and P25) 3/4 Rate Simple Trellis Decoder
|
||||
* DMR (and P25) 3/4 Rate Viterbi Trellis Decoder (Improved)
|
||||
*
|
||||
* Code Sourced From: Unknown
|
||||
* No License Provided
|
||||
*
|
||||
* LWVMOBILE
|
||||
* 2023-12 DSD-FME Florida Man Edition
|
||||
* 2026-02 DSD-FME Florida Man Edition
|
||||
*-----------------------------------------------------------------------------*/
|
||||
|
||||
#define INRANGE 8 // Serial input is grouped in 3 bits, each input value ranges 0~7
|
||||
#define INSTATIC 8 // Number of possible states in the state machine, range 0~7
|
||||
#define INDATA 49 // Input 144 bits, grouped into 3-bit units → outputs 48 values (0~15) + one final 0 to determine end state
|
||||
#define INSYMBOL 49 // Number of input symbols
|
||||
#define PATH 64 // For the i-th (48≥i≥2) input, there are 64 branch metrics → 64 possible paths
|
||||
#define SELECTPATH 8 // Among the 64 paths, select the 8 with smallest metrics, then choose the smallest one as final output
|
||||
#define BITELEN 9 //
|
||||
|
||||
/******************************************************************************
|
||||
* Header file includes
|
||||
*******************************************************************************/
|
||||
#include "dsd.h"
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/******************************************************************************
|
||||
* Global variable definitions
|
||||
*******************************************************************************/
|
||||
|
||||
uint8_t interleave[98] = {
|
||||
0, 1, 8, 9, 16, 17, 24, 25, 32, 33, 40, 41, 48, 49, 56, 57, 64, 65, 72, 73, 80, 81, 88, 89, 96, 97,
|
||||
@@ -14,111 +37,288 @@ uint8_t interleave[98] = {
|
||||
4, 5, 12, 13, 20, 21, 28, 29, 36, 37, 44, 45, 52, 53, 60, 61, 68, 69, 76, 77, 84, 85, 92, 93,
|
||||
6, 7, 14, 15, 22, 23, 30, 31, 38, 39, 46, 47, 54, 55, 62, 63, 70, 71, 78, 79, 86, 87, 94, 95};
|
||||
|
||||
//this is a convertion table for converting the dibit pairs into constellation points
|
||||
uint8_t constellation_map[16] = {
|
||||
11, 12, 0, 7, 14, 9, 5, 2, 10, 13, 1, 6, 15, 8, 4, 3
|
||||
static int16_t staticX[INSTATIC][INRANGE] =
|
||||
{
|
||||
{ 1,-3,-3,1,3,-1,-1,3 },
|
||||
{ -3,1,3,-1,-1,3,1,-3 },
|
||||
{ -1,3,3,-1,-3,1,1,-3 },
|
||||
{ 3,-1,-3,1,1,-3,-1,3 },
|
||||
{ -3,1,1,-3,-1,3,3,-1 },
|
||||
{ 1,-3,-1,3,3,-1,-3,1 },
|
||||
{ 3,-1,-1,3,1,-3,-3,1 },
|
||||
{ -1,3,1,-3,-3,1,3,-1 }
|
||||
|
||||
};
|
||||
|
||||
//digitized dibit to OTA symbol conversion for reference
|
||||
//0 = +1; 1 = +3;
|
||||
//2 = -1; 3 = -3;
|
||||
|
||||
//finite state machine values
|
||||
uint8_t fsm[64] = {
|
||||
0, 8, 4, 12, 2, 10, 6, 14,
|
||||
4, 12, 2, 10, 6, 14, 0, 8,
|
||||
1, 9, 5, 13, 3, 11, 7, 15,
|
||||
5, 13, 3, 11, 7, 15, 1, 9,
|
||||
3, 11, 7, 15, 1, 9, 5, 13,
|
||||
7, 15, 1, 9, 5, 13, 3, 11,
|
||||
2, 10, 6, 14, 0, 8, 4, 12,
|
||||
6, 14, 0, 8, 4, 12, 2, 10};
|
||||
|
||||
//attempt to find the surviving path, or the 'best' path available (most positions gained)
|
||||
uint8_t fix_34(uint8_t * p, uint8_t state, int position)
|
||||
static int16_t staticY[INSTATIC][INRANGE] =
|
||||
{
|
||||
int i, j, k, counter, best_p, best_v, survivors;
|
||||
uint8_t temp_s, tri, t;
|
||||
{ -1,3,-1,3,-3,1,-3,1 },
|
||||
{ -1,3,-3,1,-3,1,-1,3 },
|
||||
{ -1,3,-1,3,-3,1,-3,1 },
|
||||
{ -1,3,-3,1,-3,1,-1,3 },
|
||||
{ -3,1,-3,1,-1,3,-1,3 },
|
||||
{ -3,1,-1,3,-1,3,-3,1 },
|
||||
{ -3,1,-3,1,-1,3,-1,3 },
|
||||
{ -3,1,-1,3,-1,3,-3,1 }
|
||||
|
||||
//status of surviving paths -- debug
|
||||
int s[8];
|
||||
memset (s, 0, 8*sizeof(int));
|
||||
};
|
||||
|
||||
//assign all potentially correct points to temporary storage
|
||||
uint8_t temp_p[8];
|
||||
temp_p[0] = (p[position] ^ 1) & 0xF;
|
||||
temp_p[1] = (p[position] ^ 3) & 0xF;
|
||||
temp_p[2] = (p[position] ^ 5) & 0xF;
|
||||
temp_p[3] = (p[position] ^ 7) & 0xF;
|
||||
temp_p[4] = (p[position] ^ 9) & 0xF;
|
||||
temp_p[5] = (p[position] ^ 11) & 0xF;
|
||||
temp_p[6] = (p[position] ^ 13) & 0xF;
|
||||
temp_p[7] = (p[position] ^ 15) & 0xF;
|
||||
|
||||
best_p = 0; //best position
|
||||
best_v = 0; //best path value
|
||||
|
||||
for (k = 0; k < 8; k++)
|
||||
{
|
||||
temp_s = state;
|
||||
counter = 0;
|
||||
tri = 0;
|
||||
for (i = position; i < 49; i++)
|
||||
{
|
||||
//assign temp t either as our temp_p, or next point of i
|
||||
if (i == position) t = temp_p[k];
|
||||
else t = p[i];
|
||||
|
||||
if (tri != 0xFF) //while the path survives
|
||||
{
|
||||
tri = 0xFF;
|
||||
for (j = 0; j < 8; j++)
|
||||
{
|
||||
if ( fsm[(temp_s*8)+j] == t )
|
||||
{
|
||||
//return our tribit value and state for the next point
|
||||
tri = temp_s = (uint8_t)j;
|
||||
counter++;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (counter > best_p)
|
||||
{
|
||||
best_p = counter;
|
||||
best_v = k; //if we make it further on current path, assign as best path
|
||||
}
|
||||
|
||||
// surviving path -- made it to 49
|
||||
if (i == 48) s[k] = 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//NOTE: If there are no surviving paths, then that means there is another bit/point err
|
||||
//Ideally, at the end of a single or multiple fixes, there should only be one survivor, but in practice,
|
||||
//I've seen up to 4 surviving paths and somehow, get a good CRC and a good LRRP or LOCN decode
|
||||
//multiple survivors seems common when the starting err position is very close to 49
|
||||
|
||||
//tally number of surviving paths
|
||||
survivors = 0;
|
||||
for (k = 0; k < 8; k++)
|
||||
{
|
||||
if (s[k] == 1) survivors++;
|
||||
}
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, "START: %d; BEST_P: %d; BEST_V: %d; Survivors: %d; Point: %d; ", position, best_p, best_v, survivors, temp_p[best_v]);
|
||||
|
||||
return temp_p[best_v]; //return the point value of the best path's starting point value
|
||||
/**
|
||||
* @brief Convert 0~7 symbols into 3-bit (0/1) bitstream
|
||||
*
|
||||
* @Param [in] psymbol Input decimal symbol values
|
||||
* @Param [in] num Number of input symbols
|
||||
* @Param [out] pbit Output 3-bit stream
|
||||
*
|
||||
*/
|
||||
void TriBit(uint16_t *psymbol, uint16_t num, uint16_t *pbit)
|
||||
{
|
||||
int16_t temp, i;
|
||||
for (i = 0; i < num; i++)
|
||||
{
|
||||
temp = psymbol[i];
|
||||
*pbit++ = (temp >> 2) & 0x01;
|
||||
*pbit++ = (temp >> 1) & 0x01;
|
||||
*pbit++ = temp & 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t dmr_34(uint8_t * input, uint8_t treturn[18])
|
||||
/**
|
||||
* @brief Organize the decoded 3-bit stream into 16-bit word format for output
|
||||
*
|
||||
* @Param [in] ptriBitDecRslt Input 3-bit decoded bitstream
|
||||
* @Param [out] poutdata Output packed 16-bit words
|
||||
*
|
||||
*/
|
||||
void TriBitToShort(uint16_t *ptriBitDecRslt, uint16_t *poutdata)
|
||||
{
|
||||
int i, j;
|
||||
int16_t i, j, k = 0;
|
||||
uint16_t bitArray[144] = { 0 };
|
||||
memset(poutdata, 0, sizeof(uint16_t) * 9);
|
||||
|
||||
TriBit(ptriBitDecRslt, 48, bitArray);
|
||||
for (i = 0; i < 9; i++)
|
||||
{
|
||||
for (j = 15; j >= 0; j--)
|
||||
{
|
||||
poutdata[i] += bitArray[k++] << j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Among 8 paths, select the one with the smallest metric and return its index
|
||||
*
|
||||
* @Param [in] pdata Array of 8 path metrics
|
||||
* @return Index of the path with minimum metric
|
||||
*
|
||||
*/
|
||||
uint16_t MinPath(uint16_t *pdata)
|
||||
{
|
||||
uint16_t i, j = 0;
|
||||
uint16_t min = pdata[0]; // Take first value as initial minimum
|
||||
|
||||
for (i = 0; i < SELECTPATH; i++)
|
||||
{
|
||||
if (pdata[i] < min) // If current value is smaller
|
||||
{
|
||||
min = pdata[i]; // Update minimum
|
||||
j = i;
|
||||
}
|
||||
}
|
||||
return j; // Return index of minimum path
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief From 64 paths, select the 8 paths with smallest metrics,
|
||||
* and output corresponding state and input values
|
||||
*
|
||||
* @Param [in] parrary Array of 64 branch metrics
|
||||
* @Param [out] pipath_ed Sorted 8 smallest path metrics
|
||||
* @Param [out] pstate States corresponding to the 8 best paths
|
||||
* @Param [out] pdata Input values corresponding to the 8 best paths
|
||||
*
|
||||
*/
|
||||
void PathFunc(uint16_t *parrary, uint16_t *pipath_ed, uint16_t *pstate, uint16_t *pdata)
|
||||
{
|
||||
int16_t i, j, locT = 0, ValT = 0;
|
||||
uint16_t dataT[PATH] = { 0 };
|
||||
|
||||
memcpy(dataT, parrary, sizeof(uint16_t)*PATH);
|
||||
|
||||
for (i = 0; i < INRANGE; i++)
|
||||
{
|
||||
ValT = dataT[0];
|
||||
locT = 0;
|
||||
for (j = 1; j < PATH; j++)
|
||||
{
|
||||
if (dataT[j] < ValT)
|
||||
{
|
||||
ValT = dataT[j];
|
||||
locT = j;
|
||||
}
|
||||
}
|
||||
dataT[locT] = 10000;
|
||||
pipath_ed[i] = ValT;
|
||||
pstate[i] = locT / 8; // quotient → state
|
||||
pdata[i] = locT % 8; // remainder → input symbol
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Calculate path metric (Manhattan distance)
|
||||
*
|
||||
* @Param [in] dataAX X-coordinate of received constellation point A
|
||||
* @Param [in] dataBX X-coordinate of reference constellation point B
|
||||
* @Param [in] dataAY Y-coordinate of received constellation point A
|
||||
* @Param [in] dataBY Y-coordinate of reference constellation point B
|
||||
* @return Path metric result
|
||||
*
|
||||
*/
|
||||
uint16_t DistanceCal(int16_t dataAX, int16_t dataBX, int16_t dataAY, int16_t dataBY)
|
||||
{
|
||||
uint16_t DistanceRslt = 0;
|
||||
DistanceRslt = abs(dataAX - dataBX) + abs(dataAY - dataBY);
|
||||
//DistanceRslt = sqrt((dataAX - dataBX)*(dataAX - dataBX) + (dataAY - dataBY)*(dataAY - dataBY));
|
||||
return DistanceRslt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert 2-bit value to constellation symbol (±1, ±3)
|
||||
*
|
||||
* @Param [in] InBit Input 2-bit value (0~3)
|
||||
* @return Constellation symbol value
|
||||
*
|
||||
*/
|
||||
int16_t BitToSymbol(int16_t InBit)
|
||||
{
|
||||
switch (InBit)
|
||||
{
|
||||
case 0:
|
||||
return 1;
|
||||
case 1:
|
||||
return 3;
|
||||
case 2:
|
||||
return -1;
|
||||
case 3:
|
||||
return -3;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Map input symbols (0~15) to constellation points (±1, ±3) in I/Q
|
||||
*
|
||||
* @Param [in] pinstar Input symbol array (0~15)
|
||||
* @Param [in] num Number of symbols
|
||||
* @Param [out] pdataX Output I-channel (X) coordinates
|
||||
* @Param [out] pdataY Output Q-channel (Y) coordinates
|
||||
*
|
||||
*/
|
||||
void MapStar(uint16_t *pinstar, uint16_t num, int16_t *pdataX, int16_t *pdataY)
|
||||
{
|
||||
int16_t i, itemp;
|
||||
|
||||
for (i = 0; i < num; i++)
|
||||
{
|
||||
itemp = (pinstar[i] >> 2) & 0x03;
|
||||
*pdataX++ = BitToSymbol(itemp);
|
||||
itemp = pinstar[i] & 0x03;
|
||||
*pdataY++ = BitToSymbol(itemp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Viterbi decoding function — selects the most likely sequence
|
||||
*
|
||||
* @Param [in] pindata Input symbol sequence (values 0~15)
|
||||
* @Param [out] outdata Decoded output (packed 16-bit words)
|
||||
*
|
||||
*/
|
||||
void Virterbi(uint16_t *pindata, uint16_t outdata[])
|
||||
{
|
||||
int16_t i, p, j = 1;
|
||||
uint16_t lastDistnc[INRANGE] = { 0 }; // Branch metrics for final input (8 values)
|
||||
uint16_t crntDistnc[64] = { 0 }; // All 64 possible branch metrics for current step
|
||||
uint16_t DecodeOut[INDATA][INRANGE] = { {0 }}; // Possible input sequences up to current step (8 survivors)
|
||||
uint16_t DecodeOutEnd[INDATA][INRANGE] = { {0 }}; // Temporary buffer for path history
|
||||
uint16_t exInDistnc[INRANGE] = { 0 }; // 8 survivor path metrics
|
||||
uint16_t triBitDecRslt[INDATA] = { 0 }; // Final decoded 3-bit symbols
|
||||
int16_t starX[INDATA] = { 0 }, starY[INDATA] = { 0 }; // Constellation point coordinates (I/Q)
|
||||
|
||||
uint16_t minDistnc = 0;
|
||||
uint16_t ConstelPData[INRANGE] = { 0 }; // Input symbols leading to next states
|
||||
uint16_t ConstelPState[INSTATIC] = { 0 }; // Survivor states
|
||||
|
||||
MapStar(pindata, INDATA, starX, starY); // Map input symbols to constellation points
|
||||
|
||||
// Branch metrics for the first input symbol (starting from state 0)
|
||||
for (i = 0; i < INRANGE; i++)
|
||||
{
|
||||
ConstelPData[i] = i; // Next state value
|
||||
DecodeOut[0][i] = i; // Possible input symbols from state 0
|
||||
exInDistnc[i] = DistanceCal(starX[0], staticX[0][i], starY[0], staticY[0][i]);
|
||||
}
|
||||
|
||||
// Compute branch metrics for each subsequent input symbol
|
||||
for (j = 1; j < INDATA - 1; j++) // for each input symbol
|
||||
{
|
||||
for (p = 0; p < INSTATIC; p++) // for each current survivor state
|
||||
{
|
||||
for (i = 0; i < INRANGE; i++) // for each possible input
|
||||
{
|
||||
crntDistnc[p * 8 + i] = DistanceCal(starX[j], staticX[ConstelPData[p]][i], starY[j], staticY[ConstelPData[p]][i]) + exInDistnc[p];
|
||||
}
|
||||
}
|
||||
|
||||
PathFunc(crntDistnc, exInDistnc, ConstelPState, ConstelPData); // Select 8 best paths
|
||||
|
||||
for (p = 0; p <= j; p++)
|
||||
{
|
||||
for (i = 0; i < INRANGE; i++)
|
||||
{
|
||||
if (p < j)
|
||||
{
|
||||
DecodeOutEnd[p][i] = DecodeOut[p][ConstelPState[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeOut[p][i] = ConstelPData[i];
|
||||
DecodeOutEnd[p][i] = DecodeOut[p][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
memcpy(DecodeOut, DecodeOutEnd, j * 8 * sizeof(int16_t));
|
||||
}
|
||||
|
||||
// Final step — force ending in state 0
|
||||
for (i = 0; i < INRANGE; i++)
|
||||
{
|
||||
lastDistnc[i] = DistanceCal(starX[INDATA - 1], staticX[DecodeOut[INDATA - 2][i]][0], starY[INDATA - 1], staticY[DecodeOut[INDATA - 2][i]][0]) + exInDistnc[i];
|
||||
}
|
||||
|
||||
minDistnc = MinPath(lastDistnc); // Find best final path
|
||||
|
||||
for (i = 0; i < INDATA - 1; i++)
|
||||
{
|
||||
triBitDecRslt[i] = DecodeOutEnd[i][minDistnc];
|
||||
}
|
||||
|
||||
TriBitToShort(triBitDecRslt, outdata);
|
||||
}
|
||||
|
||||
uint32_t viterbi_r34 (uint8_t * input, uint8_t * output)
|
||||
{
|
||||
|
||||
int i = 0;
|
||||
uint32_t irr_err = 0; //irrecoverable errors
|
||||
|
||||
uint8_t deinterleaved_dibits[98];
|
||||
@@ -135,80 +335,20 @@ uint32_t dmr_34(uint8_t * input, uint8_t treturn[18])
|
||||
for (i = 0; i < 49; i++)
|
||||
nibs[i] = (deinterleaved_dibits[i*2+0] << 2) | (deinterleaved_dibits[i*2+1] << 0);
|
||||
|
||||
//convert our dibit pairs into constellation point values
|
||||
uint8_t point[49];
|
||||
memset (point, 0xFF, sizeof(point));
|
||||
uint16_t pindata[49]; memset (pindata, 0, sizeof(pindata));
|
||||
uint16_t outdata[18]; memset (outdata, 0, sizeof(outdata));
|
||||
|
||||
for (i = 0; i < 49; i++)
|
||||
point[i] = constellation_map[nibs[i]];
|
||||
for (int i = 0; i < 49; i++)
|
||||
pindata[i] = (uint16_t)nibs[i];
|
||||
|
||||
//debug view points
|
||||
// fprintf (stderr, "\n P =");
|
||||
// for (i = 0; i < 49; i++)
|
||||
// fprintf (stderr, " %02d", point[i]);
|
||||
Virterbi(pindata, outdata);
|
||||
|
||||
//convert constellation points into tribit values using the FSM
|
||||
uint8_t state = 0;
|
||||
uint32_t tribits[49];
|
||||
memset (tribits, 0xF, sizeof(tribits));
|
||||
|
||||
for (i = 0; i < 49; i++)
|
||||
int k = 0;
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
|
||||
for (j = 0; j < 8; j++)
|
||||
{
|
||||
if ( fsm[(state*8)+j] == point[i] )
|
||||
{
|
||||
//return our tribit value and state for the next point
|
||||
tribits[i] = state = (uint8_t)j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if tribit value is greater than 7, then we have a decoding error
|
||||
if (tribits[i] > 7)
|
||||
{
|
||||
irr_err++; //tally number of errors
|
||||
|
||||
//debug point, position of error, and state value
|
||||
// fprintf (stderr, "\n P: %d, %d:%d; ", point[i], i, state);
|
||||
|
||||
//attempt to fix point by looking for the best path from here
|
||||
point[i] = fix_34(point, state, i);
|
||||
|
||||
//Make a hard decision and flip point to fit in current state
|
||||
// point[i] ^= 7; //lucky number 7 (0111) //fallback
|
||||
|
||||
//decrement one and resume decoding
|
||||
i--;
|
||||
|
||||
}
|
||||
|
||||
output[k++] = (outdata[i] >> 8) & 0xFF;
|
||||
output[k++] = (outdata[i] >> 0) & 0xFF;
|
||||
}
|
||||
|
||||
//debug view tribits/states
|
||||
// fprintf (stderr, "\n T =");
|
||||
// for (i = 0; i < 49; i++)
|
||||
// fprintf (stderr, " %02d", tribits[i]);
|
||||
|
||||
//convert tribits into a return payload
|
||||
uint32_t temp = 0;
|
||||
|
||||
//break into chunks of 24 bit values and shuffle into 8-bit (byte) treturn values
|
||||
for (i = 0; i < 6; i++)
|
||||
{
|
||||
temp = (tribits[(i*8)+0] << 21) + (tribits[(i*8)+1] << 18) + (tribits[(i*8)+2] << 15) + (tribits[(i*8)+3] << 12) +
|
||||
(tribits[(i*8)+4] << 9) + (tribits[(i*8)+5] << 6) + (tribits[(i*8)+6] << 3) + (tribits[(i*8)+7] << 0);
|
||||
|
||||
treturn[(i*3)+0] = (temp >> 16) & 0xFF;
|
||||
treturn[(i*3)+1] = (temp >> 8) & 0xFF;
|
||||
treturn[(i*3)+2] = (temp >> 0) & 0xFF;
|
||||
}
|
||||
|
||||
//trellis point/state err tally
|
||||
// if (irr_err != 0)
|
||||
// fprintf (stderr, " P_ERR = %d", irr_err);
|
||||
|
||||
return (0);
|
||||
return irr_err; //TODO: Need to pull viterbi metrics from upper functions
|
||||
}
|
||||
|
||||
|
||||
@@ -270,13 +270,12 @@ void dmr_dheader (dsd_opts * opts, dsd_state * state, uint8_t dheader[], uint8_t
|
||||
{
|
||||
strcat (rsp_string, "NACK - ");
|
||||
if (r_type == 0) strcat (rsp_string, "Illegal Format");
|
||||
if (r_type == 1) strcat (rsp_string, "Illegal Format");
|
||||
if (r_type == 2) strcat (rsp_string, "Packet CRC ERR");
|
||||
if (r_type == 3) strcat (rsp_string, "Memory Full");
|
||||
if (r_type == 4) strcat (rsp_string, "FSN Out of Seq");
|
||||
if (r_type == 5) strcat (rsp_string, "Undeliverable");
|
||||
if (r_type == 6) strcat (rsp_string, "PKT Out of Seq");
|
||||
if (r_type == 7) strcat (rsp_string, "Invalid User");
|
||||
if (r_type == 1) strcat (rsp_string, "Packet CRC ERR");
|
||||
if (r_type == 2) strcat (rsp_string, "Memory Full");
|
||||
if (r_type == 3) strcat (rsp_string, "FSN Out of Seq");
|
||||
if (r_type == 4) strcat (rsp_string, "Undeliverable");
|
||||
if (r_type == 5) strcat (rsp_string, "PKT Out of Seq");
|
||||
if (r_type == 6) strcat (rsp_string, "Invalid User");
|
||||
}
|
||||
if (r_class == 2) strcat (rsp_string, "SACK - Retry");
|
||||
// if (r_status) strcat (rsp_string, " - %d", r_status);
|
||||
@@ -293,8 +292,19 @@ void dmr_dheader (dsd_opts * opts, dsd_state * state, uint8_t dheader[], uint8_t
|
||||
//Confirmed or Unconfirmed Data Packets Header
|
||||
if (dpf == 2 || dpf == 3)
|
||||
{
|
||||
if (dpf == 2) fprintf (stderr, "\n SAP %02d [%s] - FMF %d - BLOCKS %02d - PAD %02d - FSN %d", sap, sap_string, f, bf, poc, fsn);
|
||||
if (dpf == 3) fprintf (stderr, "\n SAP %02d [%s] - FMF %d - BLOCKS %02d - PAD %02d - S %d - NS %d - FSN %d", sap, sap_string, f, bf, poc, s, ns, fsn);
|
||||
|
||||
uint8_t fsn_number = (fsn & 7) + 1;
|
||||
|
||||
char fsn_string[50];
|
||||
memset(fsn_string, 0, sizeof(fsn_string));
|
||||
|
||||
if (fsn == 0) sprintf(fsn_string, "%s", "Unconfirmed data single fragment");
|
||||
else if (fsn == 8) sprintf(fsn_string, "%s", "Confirmed data single fragment");
|
||||
else if (fsn >= 9) sprintf(fsn_string, "%s", "Last confirmed data fragment with number");
|
||||
else sprintf(fsn_string, "%s", "Subsequent confirmed data fragment with number");
|
||||
|
||||
if (dpf == 2) fprintf (stderr, "\n SAP %02d [%s] - FMF %d - BLOCKS %02d - PAD %02d - FSN: [%X] - %s %d; ", sap, sap_string, f, bf, poc, fsn, fsn_string, fsn_number);
|
||||
if (dpf == 3) fprintf (stderr, "\n SAP %02d [%s] - FMF %d - BLOCKS %02d - PAD %02d - S %d - NS %d - FSN: [%X] - %s %d; ", sap, sap_string, f, bf, poc, s, ns, fsn, fsn_string, fsn_number);
|
||||
state->data_header_blocks[slot] = bf;
|
||||
if (dpf == 3) state->data_conf_data[slot] = 1; //set confirmed data delivery flag for additional CRC checks, block assembly, etc.
|
||||
|
||||
@@ -1028,9 +1038,9 @@ void dmr_block_assembler (dsd_opts * opts, dsd_state * state, uint8_t block_byte
|
||||
R = state->rkey_array[state->payload_keyidR];
|
||||
|
||||
//loader for aes keys
|
||||
uint8_t kaes[32];
|
||||
uint8_t empt[32];
|
||||
uint8_t maes[16];
|
||||
uint8_t kaes[32]; memset(kaes, 0, sizeof(kaes));
|
||||
uint8_t empt[32]; memset(empt, 0, sizeof(empt));
|
||||
uint8_t maes[16]; memset(maes, 0, sizeof(maes));
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
kaes[i+0] = ((state->rkey_array[kid+0x000]) >> (56-(i*8))) & 0xFF;
|
||||
|
||||
@@ -702,8 +702,8 @@ void dmrBS (dsd_opts * opts, dsd_state * state)
|
||||
}
|
||||
else if (state->payload_algid == 0x02)
|
||||
{
|
||||
hytera_enhanced_alg_refresh(state);
|
||||
state->currentslot = 0;
|
||||
hytera_enhanced_alg_refresh(state);
|
||||
dmr_alg_refresh (opts, state);
|
||||
}
|
||||
if (state->payload_algidR >= 0x21)
|
||||
@@ -713,8 +713,8 @@ void dmrBS (dsd_opts * opts, dsd_state * state)
|
||||
}
|
||||
else if (state->payload_algidR == 0x02)
|
||||
{
|
||||
hytera_enhanced_alg_refresh(state);
|
||||
state->currentslot = 1;
|
||||
hytera_enhanced_alg_refresh(state);
|
||||
dmr_alg_refresh (opts, state);
|
||||
}
|
||||
|
||||
@@ -1069,10 +1069,10 @@ void dmrBSBootstrap (dsd_opts * opts, dsd_state * state)
|
||||
state->currentslot = 1;
|
||||
dmr_alg_refresh (opts, state);
|
||||
}
|
||||
else if (state->payload_algid == 0x02)
|
||||
else if (state->payload_algidR == 0x02)
|
||||
{
|
||||
hytera_enhanced_alg_refresh(state);
|
||||
state->currentslot = 1;
|
||||
hytera_enhanced_alg_refresh(state);
|
||||
dmr_alg_refresh (opts, state);
|
||||
}
|
||||
|
||||
|
||||
@@ -438,7 +438,7 @@ void dmr_data_burst_handler(dsd_opts * opts, dsd_state * state, uint8_t info[196
|
||||
|
||||
uint8_t TrellisReturn[18];
|
||||
memset (TrellisReturn, 0, sizeof(TrellisReturn));
|
||||
IrrecoverableErrors = dmr_34(tdibits, TrellisReturn);
|
||||
IrrecoverableErrors = viterbi_r34(tdibits, TrellisReturn);
|
||||
|
||||
//NOTE: IrrecoverableErrors in this context are a tally of errors from trellis
|
||||
//they may have been successfully corrected, the CRC will reveal as much
|
||||
|
||||
@@ -560,7 +560,7 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C
|
||||
if (opts->p25_trunk == 1 && opts->trunk_tune_enc_calls == 0) //&& type != 2
|
||||
{
|
||||
int i, lo = 0;
|
||||
uint32_t t = 0; char gm[8]; char gn[50];
|
||||
uint32_t t = 0; char gm[3]; char gn[100];
|
||||
|
||||
//check to see if this group already exists, or has already been locked out, or is allowed
|
||||
for (i = 0; i <= state->group_tally; i++)
|
||||
|
||||
@@ -521,6 +521,12 @@ void decode_ip_pdu (dsd_opts * opts, dsd_state * state, uint16_t len, uint8_t *
|
||||
lip_protocol_decoder(opts, state, bits);
|
||||
}
|
||||
//known P25 Ports
|
||||
else if (port1 == 9361 && port2 == 9361)
|
||||
{
|
||||
sprintf (state->dmr_lrrp_gps[slot], "P25 Atlas SRC(IP): %d.%d.%d.%d; DST(IP): %d.%d.%d.%d; ",
|
||||
input[12], input[13], input[14], input[15], input[16], input[17], input[18], input[19]);
|
||||
fprintf (stderr, "Atlas Data Registration Server;"); //EF Johnson Atlas
|
||||
}
|
||||
else if (port1 == 49198 && port2 == 49198)
|
||||
{
|
||||
sprintf (state->dmr_lrrp_gps[slot], "P25 Tier 2 LOCN SRC(IP): %d.%d.%d.%d; DST(IP): %d.%d.%d.%d; ",
|
||||
@@ -995,8 +1001,9 @@ void dmr_locn (dsd_opts * opts, dsd_state * state, uint16_t len, uint8_t * DMR_P
|
||||
//write to LRRP file
|
||||
if (opts->lrrp_file_output == 1)
|
||||
{
|
||||
char * timestr = getTimeC();
|
||||
|
||||
char * datestr = getDateS();
|
||||
char * timestr = getTimeC();
|
||||
|
||||
//open file by name that is supplied in the ncurses terminal, or cli
|
||||
FILE * pFile; //file pointer
|
||||
|
||||
@@ -581,11 +581,15 @@ void tait_iso7_embedded_alias_decode (dsd_opts * opts, dsd_state * state, uint8_
|
||||
for (uint8_t i = 0; i < len; i++)
|
||||
{
|
||||
alias[i] = (uint8_t)ConvertBitIntoBytes(&input[16+(i*7)], 7);
|
||||
fprintf (stderr, "%c", alias[i]);
|
||||
|
||||
// fprintf (stderr, "%X,", alias[i]); //debug
|
||||
|
||||
if (alias[i] == 0x2C) //change a comma to a dot
|
||||
alias[i] = 0x2E;
|
||||
else if (alias[i] < 0x20) //change any garble / control chars to a space
|
||||
alias[i] = 0x20;
|
||||
|
||||
fprintf (stderr, "%c", alias[i]);
|
||||
}
|
||||
|
||||
//flag to indicate this already exists in import or group struct
|
||||
|
||||
@@ -2194,6 +2194,8 @@ void agsm (dsd_opts * opts, dsd_state * state, short * input, int len)
|
||||
|
||||
state->aout_gainA = coeff; //store for internal use
|
||||
|
||||
UNUSED(avg);
|
||||
|
||||
}
|
||||
|
||||
//until analog agc is fixed, going to use a manual gain control on this
|
||||
|
||||
@@ -218,13 +218,13 @@ void watchdog_event_history (dsd_opts * opts, dsd_state * state, uint8_t slot)
|
||||
|
||||
if (slot == 0 && opts->wav_out_f != NULL)
|
||||
{
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, event_struct);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, event_struct);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 8000, 0);
|
||||
}
|
||||
|
||||
else if (slot == 1 && opts->wav_out_fR != NULL)
|
||||
{
|
||||
opts->wav_out_fR = close_and_rename_wav_file(opts->wav_out_fR, opts->wav_out_fileR, opts->wav_out_dir, event_struct);
|
||||
opts->wav_out_fR = close_and_rename_wav_file(opts->wav_out_fR, opts->wav_out_fileR, opts->wav_out_dir, opts->wav_custom_tag, event_struct);
|
||||
opts->wav_out_fR = open_wav_file(opts->wav_out_dir, opts->wav_out_fileR, 8000, 0);
|
||||
}
|
||||
|
||||
|
||||
238
src/dsd_file.c
238
src/dsd_file.c
@@ -502,7 +502,7 @@ SNDFILE * close_wav_file(SNDFILE * wav_file)
|
||||
return wav_file;
|
||||
}
|
||||
|
||||
SNDFILE * close_and_rename_wav_file(SNDFILE * wav_file, char * wav_out_filename, char * dir, Event_History_I * event_struct)
|
||||
SNDFILE * close_and_rename_wav_file(SNDFILE * wav_file, char * wav_out_filename, char * dir, char * custom_tag, Event_History_I * event_struct)
|
||||
{
|
||||
sf_close(wav_file);
|
||||
|
||||
@@ -538,11 +538,46 @@ SNDFILE * close_and_rename_wav_file(SNDFILE * wav_file, char * wav_out_filename,
|
||||
char new_filename[2000];
|
||||
memset (new_filename, 0, sizeof(new_filename));
|
||||
|
||||
//parse any possible custom tags or aliasing (up to 3)
|
||||
char custom_string_temp[100];
|
||||
memset(custom_string_temp, 0, sizeof(custom_string_temp));
|
||||
memcpy(custom_string_temp, custom_tag, sizeof(custom_string_temp));
|
||||
char * token = strtok(custom_string_temp, ":");
|
||||
char tag[3][25];
|
||||
memset(tag, 0, sizeof(tag));
|
||||
sprintf(tag[0], "%s", "");
|
||||
sprintf(tag[1], "%s", "");
|
||||
sprintf(tag[2], "%s", "");
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
|
||||
if (token != NULL)
|
||||
{
|
||||
tag[i][0] = '_';
|
||||
strncpy(tag[i]+1, token, 23);
|
||||
tag[i][24] = '\0';
|
||||
token = strtok(NULL, ":");
|
||||
|
||||
//look for _TGT_, convert to strings
|
||||
if (strncmp("_TGT_", tag[i], 5) == 0)
|
||||
{
|
||||
sprintf (tgt_str, "%s", tag[i]+5);
|
||||
sprintf (src_str, "%d", source_id);
|
||||
is_string = 1;
|
||||
memset(tag[i], 0, sizeof(tag[i]));
|
||||
sprintf(tag[i], "%s", "");
|
||||
}
|
||||
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
//check for String based TGT and SRC values (M17, YSF, DSTAR)
|
||||
if (is_string == 1)
|
||||
sprintf (new_filename, "%s/%s_%s_%05d_%s_%s_TGT_%s_SRC_%s.wav", dir, datestr, timestr, random_number, sys_str, gi_str, tgt_str, src_str);
|
||||
sprintf (new_filename, "%s/%s_%s_%05d_%s_%s%s%s%s_TGT_%s_SRC_%s.wav", dir, datestr, timestr, random_number, sys_str, gi_str, tag[0], tag[1], tag[2], tgt_str, src_str);
|
||||
else //is a numerical value
|
||||
sprintf (new_filename, "%s/%s_%s_%05d_%s_%s_TGT_%d_SRC_%d.wav", dir, datestr, timestr, random_number, sys_str, gi_str, target_id, source_id);
|
||||
sprintf (new_filename, "%s/%s_%s_%05d_%s_%s%s%s%s_TGT_%d_SRC_%d.wav", dir, datestr, timestr, random_number, sys_str, gi_str, tag[0], tag[1], tag[2], target_id, source_id);
|
||||
|
||||
if (timestr != NULL)
|
||||
{
|
||||
@@ -1345,6 +1380,18 @@ void read_sdrtrunk_json_format (dsd_opts * opts, dsd_state * state)
|
||||
state->lastsynctype = 10;
|
||||
}
|
||||
|
||||
if (strncmp ("NXDN", str_buffer, 4) == 0)
|
||||
{
|
||||
//set AMBE+2 protocol here
|
||||
protocol = 3;
|
||||
|
||||
//disable dmra (+7 on ks_idx)
|
||||
is_dmra = 0;
|
||||
|
||||
state->synctype = 28;
|
||||
state->lastsynctype = 28;
|
||||
}
|
||||
|
||||
//open .imb or .amb file, if desired, but only after setting a synctype
|
||||
if (state->synctype != -1)
|
||||
{
|
||||
@@ -1355,6 +1402,57 @@ void read_sdrtrunk_json_format (dsd_opts * opts, dsd_state * state)
|
||||
|
||||
}
|
||||
|
||||
if (strncmp ("tag", str_buffer, 3) == 0)
|
||||
{
|
||||
|
||||
str_buffer = strtok(NULL, "\n"); //copies to End of Line
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, " TAG: %s", str_buffer);
|
||||
|
||||
//NOTE: Nothing coded for SDRTrunk MBE files provides information regarding
|
||||
//possible FACCH1 steals on voice frames, so keystrem index position here can
|
||||
//become unsynced on some voice frames if trunking or other does FACCH1 steals
|
||||
//when the system is really busy, or radio drops out. Writing code to read ahead
|
||||
//would still not tell us if the FACCH1 steal was in vocoder socket 1 or 2
|
||||
|
||||
//NOTE: Above may become even more noticeable on DES or AES as those keystreams
|
||||
//are generated for two voice superframes, and the decryption may become unsynced
|
||||
//until the next VCALL_IV is read in from the MBE file.
|
||||
|
||||
//Check NXDN tag value, set the ks_idx to proper position
|
||||
//by looking for SACCH_1, SACCH_2, SACCH_3, or SACCH_4 for scrambler
|
||||
if (alg_id == 1 && protocol == 3)
|
||||
{
|
||||
if (strncmp ("SACCH 1", str_buffer+4, 7) == 0)
|
||||
{
|
||||
ks_idx = 0;
|
||||
if (opts->payload == 1)
|
||||
fprintf (stderr, "\n PF 1/4");
|
||||
}
|
||||
else if (strncmp ("SACCH 2", str_buffer+4, 7) == 0)
|
||||
{
|
||||
ks_idx = 49*4;
|
||||
if (opts->payload == 1)
|
||||
fprintf (stderr, "\n PF 2/4");
|
||||
}
|
||||
else if (strncmp ("SACCH 3", str_buffer+4, 7) == 0)
|
||||
{
|
||||
ks_idx = 49*8;
|
||||
if (opts->payload == 1)
|
||||
fprintf (stderr, "\n PF 3/4");
|
||||
}
|
||||
else if (strncmp ("SACCH 4", str_buffer+4, 7) == 0)
|
||||
{
|
||||
ks_idx = 49*12;
|
||||
if (opts->payload == 1)
|
||||
fprintf (stderr, "\n PF 4/4");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (strncmp ("call_type", str_buffer, 9) == 0)
|
||||
{
|
||||
str_buffer = strtok(NULL, " : \""); //next value after any : "" string
|
||||
@@ -1388,6 +1486,29 @@ void read_sdrtrunk_json_format (dsd_opts * opts, dsd_state * state)
|
||||
key_id = 0;
|
||||
iv_hex = 0;
|
||||
|
||||
//NXDN Forced Scrambler
|
||||
if (is_enc == 1 && state->forced_alg_id == 1 && protocol == 3)
|
||||
{
|
||||
|
||||
alg_id = state->forced_alg_id;
|
||||
|
||||
//Key Load Forced Scrambler Key by TGT or SRC value
|
||||
if (state->forced_alg_id == 1 && state->keyloader == 1)
|
||||
{
|
||||
if (state->rkey_array[target] != 0)
|
||||
state->R = state->rkey_array[target];
|
||||
else if (state->rkey_array[source] != 0)
|
||||
state->R = state->rkey_array[source];
|
||||
}
|
||||
|
||||
if (alg_id == 1 && state->R != 0)
|
||||
{
|
||||
pdu_scrambler_keystream_creation(ks, state->R, 16*49);
|
||||
ks_available = 1;
|
||||
}
|
||||
//DES and AES still require the IV for KS creation
|
||||
}
|
||||
|
||||
//debug set value
|
||||
// fprintf (stderr, " ENC: %d;", is_enc);
|
||||
|
||||
@@ -1461,10 +1582,6 @@ void read_sdrtrunk_json_format (dsd_opts * opts, dsd_state * state)
|
||||
{
|
||||
//Hytera BP
|
||||
}
|
||||
else if (state->R != 0)
|
||||
{
|
||||
//NXDN Scrambler
|
||||
}
|
||||
}
|
||||
|
||||
if (strncmp ("to", str_buffer, 2) == 0)
|
||||
@@ -1523,6 +1640,36 @@ void read_sdrtrunk_json_format (dsd_opts * opts, dsd_state * state)
|
||||
|
||||
key_id = strtol (str_buffer, NULL, 10);
|
||||
|
||||
//NXDN Scrambler Setup
|
||||
if (alg_id == 1 && protocol == 3)
|
||||
{
|
||||
|
||||
state->payload_keyid = key_id;
|
||||
|
||||
//Scrambler Key Loading
|
||||
if (state->keyloader == 1)
|
||||
keyring(opts, state);
|
||||
|
||||
//If Scrambler Key still not available on KID
|
||||
if (state->R == 0)
|
||||
{
|
||||
//Key Load Scrambler Key by TGT or SRC value
|
||||
if (state->forced_alg_id == 1 && state->keyloader == 1)
|
||||
{
|
||||
if (state->rkey_array[target] != 0)
|
||||
state->R = state->rkey_array[target];
|
||||
else if (state->rkey_array[source] != 0)
|
||||
state->R = state->rkey_array[source];
|
||||
}
|
||||
}
|
||||
|
||||
if (state->R != 0)
|
||||
{
|
||||
pdu_scrambler_keystream_creation(ks, state->R, 16*49);
|
||||
ks_available = 1;
|
||||
}
|
||||
}
|
||||
|
||||
//debug set value
|
||||
if (opts->payload == 1)
|
||||
fprintf (stderr, "\n Key ID: %04X;", key_id);
|
||||
@@ -1708,6 +1855,50 @@ void read_sdrtrunk_json_format (dsd_opts * opts, dsd_state * state)
|
||||
|
||||
}
|
||||
|
||||
//WIP: Add handling for NXDN DES and AES256
|
||||
//NOTE: This hinges on where SDRTrunk places the IV for the call,
|
||||
//ideally needs to be after the SACCH4 voice frames
|
||||
//NOTE: This will NOT WORK on Type-D with its 22-bit IV
|
||||
//I doubt anybody will ever test that theory out here either
|
||||
else if (protocol == 3 && alg_id == 2 && state->R != 0)
|
||||
{
|
||||
des_multi_keystream_output(iv_hex, state->R, ks_bytes, 1, 32);
|
||||
|
||||
unpack_byte_array_into_bit_array(ks_bytes+8, ks, 256-8);
|
||||
|
||||
ambe2_counter = 0;
|
||||
ks_idx = 0;
|
||||
|
||||
ks_available = 1;
|
||||
}
|
||||
else if (protocol == 3 && alg_id == 3 && state->aes_key_loaded[0] == 1)
|
||||
{
|
||||
|
||||
uint8_t aes_key[32];
|
||||
memset (aes_key, 0, sizeof(aes_key));
|
||||
|
||||
//Load key from A1 - A4
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
aes_key[i+0] = (state->A1[0] >> (56-(i*8))) & 0xFF;
|
||||
aes_key[i+8] = (state->A2[0] >> (56-(i*8))) & 0xFF;
|
||||
aes_key[i+16] = (state->A3[0] >> (56-(i*8))) & 0xFF;
|
||||
aes_key[i+24] = (state->A4[0] >> (56-(i*8))) & 0xFF;
|
||||
}
|
||||
|
||||
//Generate 128-bit IV for AES
|
||||
LFSR128npdu(state); //npdu variant uses payload_mi here, and loads to aes_ivR
|
||||
|
||||
aes_ofb_keystream_output (state->aes_ivR, aes_key, ks_bytes, 2, 16);
|
||||
|
||||
unpack_byte_array_into_bit_array(ks_bytes+16, ks, 256-16);
|
||||
|
||||
ambe2_counter = 0;
|
||||
ks_idx = 0;
|
||||
|
||||
ks_available = 1;
|
||||
}
|
||||
|
||||
//Pull request: https://github.com/DSheirer/sdrtrunk/pull/2273
|
||||
//Merged into SDRTrunk nightly so should be able to handle this better now
|
||||
|
||||
@@ -1776,7 +1967,7 @@ void read_sdrtrunk_json_format (dsd_opts * opts, dsd_state * state)
|
||||
// fprintf (stderr, " Enc Play;");
|
||||
|
||||
}
|
||||
else if (protocol == 2) //P25p2 AMBE
|
||||
else if (protocol == 2) //P25p2 or DMR AMBE+2
|
||||
{
|
||||
//debug print current str_buffer
|
||||
// fprintf (stderr, "\n AMBE HEX: %s", str_buffer);
|
||||
@@ -1801,6 +1992,37 @@ void read_sdrtrunk_json_format (dsd_opts * opts, dsd_state * state)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else if (protocol == 3) //NXDN AMBE+2 (EHR)
|
||||
{
|
||||
//debug print current str_buffer
|
||||
// fprintf (stderr, "\n AMBE HEX: %s", str_buffer);
|
||||
|
||||
//18 hex characters on 'hex' which is the AMBE interleaved C codewords
|
||||
ks_idx = ambe2_str_to_decode(opts, state, str_buffer, ks, ks_idx, is_dmra, is_enc, ks_available);
|
||||
|
||||
//debug
|
||||
// if (is_enc == 1 && ks_available == 0)
|
||||
// fprintf (stderr, " Enc Mute;");
|
||||
// else if (is_enc == 1 && ks_available == 1)
|
||||
// fprintf (stderr, " Enc Play;");
|
||||
|
||||
//increment AMBE+2 counter
|
||||
ambe2_counter++;
|
||||
|
||||
//Really actually need the Structure Field (PF values here)
|
||||
//reset if over for NXDN 16 AMBE+2 frames for Scrambler
|
||||
if (alg_id == 1 && ambe2_counter == 16)
|
||||
{
|
||||
ambe2_counter = 0;
|
||||
ks_idx = 0;
|
||||
}
|
||||
else if (alg_id > 1 && ambe2_counter == 32)
|
||||
{
|
||||
ambe2_counter = 0;
|
||||
ks_idx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strncmp ("time", str_buffer, 4) == 0)
|
||||
|
||||
@@ -187,7 +187,7 @@ dmr_filter(short sample)
|
||||
short
|
||||
nxdn_filter(short sample)
|
||||
{
|
||||
return dsd_input_filter(sample, 4);
|
||||
return dsd_input_filter(sample, 2);
|
||||
}
|
||||
|
||||
short
|
||||
|
||||
@@ -379,8 +379,19 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
|
||||
lbuf2[i] = lbuf[i];
|
||||
}
|
||||
qsort (lbuf2, t_max, sizeof (int), comp);
|
||||
lmin = (lbuf2[1] + lbuf2[2] + lbuf2[3]) / 3;
|
||||
lmax = (lbuf2[t_max - 3] + lbuf2[t_max - 2] + lbuf2[t_max - 1]) / 3;
|
||||
|
||||
//on shorter sync patterns, we need to look at the edges since we don't have many to pull from
|
||||
if (t_max < 13)
|
||||
{
|
||||
lmin = (lbuf2[0] + lbuf2[1] + lbuf2[2]) / 3;
|
||||
lmax = (lbuf2[t_max - 3] + lbuf2[t_max - 2] + lbuf2[t_max - 1]) / 3;
|
||||
}
|
||||
//on longer sync patterns, we can get more towards the middle and eliminate outlier values
|
||||
else
|
||||
{
|
||||
lmin = (lbuf2[2] + lbuf2[3] + lbuf2[4]) / 3;
|
||||
lmax = (lbuf2[t_max - 5] + lbuf2[t_max - 4] + lbuf2[t_max - 3]) / 3;
|
||||
}
|
||||
|
||||
if (state->rf_mod == 1)
|
||||
{
|
||||
@@ -1140,8 +1151,8 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//NXDN
|
||||
#ifdef NXDN_OLD_SYNC
|
||||
//NXDN (previous sync, now a cmake option -DNOS=ON ..)
|
||||
else if ((opts->frame_nxdn96 == 1) || (opts->frame_nxdn48 == 1))
|
||||
{
|
||||
strncpy (synctest10, (synctest_p - 9), 10); //FSW only
|
||||
@@ -1155,11 +1166,14 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
|
||||
)
|
||||
{
|
||||
|
||||
state->offset = synctest_pos;
|
||||
state->max = ((state->max) + lmax) / 2;
|
||||
state->min = ((state->min) + lmin) / 2;
|
||||
//debug
|
||||
// fprintf (stderr, " %s ", synctest10);
|
||||
|
||||
if (state->lastsynctype == 28)
|
||||
{
|
||||
state->offset = synctest_pos;
|
||||
state->max = ((state->max) + lmax) / 2;
|
||||
state->min = ((state->min) + lmin) / 2;
|
||||
state->last_cc_sync_time = time(NULL);
|
||||
return (28);
|
||||
}
|
||||
@@ -1177,9 +1191,72 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
|
||||
)
|
||||
{
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, " %s ", synctest10);
|
||||
|
||||
if (state->lastsynctype == 29)
|
||||
{
|
||||
state->offset = synctest_pos;
|
||||
state->max = ((state->max) + lmax) / 2;
|
||||
state->min = ((state->min) + lmin) / 2;
|
||||
state->last_cc_sync_time = time(NULL);
|
||||
return (29);
|
||||
}
|
||||
state->lastsynctype = 29;
|
||||
}
|
||||
}
|
||||
|
||||
#else //new sync
|
||||
//NXDN (positive sync only)
|
||||
else if (opts->inverted_nxdn == 0 && ((opts->frame_nxdn96 == 1) || (opts->frame_nxdn48 == 1)) )
|
||||
{
|
||||
strncpy (synctest10, (synctest_p - 9), 10); //FSW only
|
||||
if (
|
||||
(strcmp (synctest10, "3131331131") == 0 )
|
||||
|| (strcmp (synctest10, "3331331131") == 0 )
|
||||
|| (strcmp (synctest10, "3131331111") == 0 )
|
||||
// || (strcmp (synctest10, "3331331111") == 0 )
|
||||
// || (strcmp (synctest10, "3131311131") == 0 )
|
||||
)
|
||||
{
|
||||
|
||||
state->offset = synctest_pos;
|
||||
state->max = ((state->max) + lmax) / 2;
|
||||
state->min = ((state->min) + lmin) / 2;
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, " %s ", synctest10);
|
||||
|
||||
if (state->lastsynctype == 28)
|
||||
{
|
||||
state->last_cc_sync_time = time(NULL);
|
||||
return (28);
|
||||
}
|
||||
state->lastsynctype = 28;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//NXDN (inverted sync only)
|
||||
else if (opts->inverted_nxdn == 1 && ((opts->frame_nxdn96 == 1) || (opts->frame_nxdn48 == 1)) )
|
||||
{
|
||||
strncpy (synctest10, (synctest_p - 9), 10); //FSW only
|
||||
if (
|
||||
(strcmp (synctest10, "1313113313") == 0 )
|
||||
|| (strcmp (synctest10, "1113113313") == 0 )
|
||||
|| (strcmp (synctest10, "1313113333") == 0 )
|
||||
// || (strcmp (synctest10, "1113113333") == 0 )
|
||||
// || (strcmp (synctest10, "1313133313") == 0 )
|
||||
)
|
||||
{
|
||||
|
||||
state->offset = synctest_pos;
|
||||
state->max = ((state->max) + lmax) / 2;
|
||||
state->min = ((state->min) + lmin) / 2;
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, " %s ", synctest10);
|
||||
|
||||
if (state->lastsynctype == 29)
|
||||
{
|
||||
state->last_cc_sync_time = time(NULL);
|
||||
@@ -1187,7 +1264,9 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
|
||||
}
|
||||
state->lastsynctype = 29;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
//Provoice Conventional -- Some False Positives due to shortened frame sync pattern, so use squelch if possible
|
||||
#ifdef PVCONVENTIONAL
|
||||
|
||||
593
src/dsd_gps.c
593
src/dsd_gps.c
@@ -129,8 +129,8 @@ void lip_protocol_decoder (dsd_opts * opts, dsd_state * state, uint8_t * input)
|
||||
if (opts->lrrp_file_output == 1)
|
||||
{
|
||||
|
||||
char * datestr = getDate();
|
||||
char * timestr = getTime();
|
||||
char * datestr = getDateS();
|
||||
char * timestr = getTimeC();
|
||||
|
||||
//open file by name that is supplied in the ncurses terminal, or cli
|
||||
pFile = fopen (opts->lrrp_out_file, "a");
|
||||
@@ -247,8 +247,8 @@ void nmea_iec_61162_1 (dsd_opts * opts, dsd_state * state, uint8_t * input, uint
|
||||
if (opts->lrrp_file_output == 1)
|
||||
{
|
||||
|
||||
char * datestr = getDate();
|
||||
char * timestr = getTime();
|
||||
char * datestr = getDateS();
|
||||
char * timestr = getTimeC();
|
||||
|
||||
int s = (int)fkph; //rounded interger format for the log report
|
||||
int a = 0;
|
||||
@@ -281,269 +281,146 @@ void nmea_iec_61162_1 (dsd_opts * opts, dsd_state * state, uint8_t * input, uint
|
||||
|
||||
}
|
||||
|
||||
//restructured the Harris GPS to flow more like the DMR UDT NMEA Format when possible
|
||||
void nmea_harris (dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src, int slot)
|
||||
//harris location on PTT (MAC) 3.100
|
||||
void harris_lptt (dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src, int slot, int phase)
|
||||
{
|
||||
|
||||
//GPS working, Time Working (old method), COG and Speed are logical on due west sample
|
||||
//TODO: Fiddle/Test various ns and ew bits for proper signs in case its located elsewhere
|
||||
//UPDATE: Found two contiguous bits that always work on all my accumulated samples
|
||||
//the bits also place it with the 'lone' flagged bit observed prior
|
||||
//I really wonder about the engineers who laid out this message
|
||||
|
||||
uint8_t nmea_ns = input[88]; //north/south (lat sign) //72 previously used bit
|
||||
uint8_t nmea_ew = input[89]; //east/west (lon sign) //88 previously used bit
|
||||
uint8_t nmea_q = input[147]; //Quality Indicator (last fix or current fix)
|
||||
// uint8_t nmea_speed = (uint8_t)ConvertBitIntoBytes(&input[129], 7); //129,7
|
||||
uint8_t nmea_speed = (uint8_t)ConvertBitIntoBytes(&input[129], 6); //this is one bit shorter than called for, but I believe it works
|
||||
//NOTE: I have only seen the MAC variation of this message description, I cannot 100% confirm
|
||||
//the two block LCW format is identical, but it does carry the same amount of relevant bytes
|
||||
//observation shows the GPS coordinates are accurate on LCW format, but I cannot
|
||||
//vouch for the COG,SPEED,TIME, EMG and GPS Quality elements when in LCW format.
|
||||
|
||||
//Latitude Bits
|
||||
uint8_t nmea_ndeg = (uint8_t)ConvertBitIntoBytes(&input[65], 7); //Latitude Degrees
|
||||
uint8_t nmea_nmin = (uint8_t)ConvertBitIntoBytes(&input[58], 6); //Latitude Minutes
|
||||
uint16_t nmea_nminf = (uint16_t)ConvertBitIntoBytes(&input[42], 14); //Latitude Fractions of Minutes
|
||||
//starting at relevant information, bytes 0,1,2,3
|
||||
uint16_t lat_min_dec = (uint16_t)ConvertBitIntoBytes(input+0, 16);
|
||||
uint16_t lat_ns = input[16]; //0 == N, 1 == S
|
||||
uint16_t lat_min_int = (uint16_t)ConvertBitIntoBytes(input+17, 7);
|
||||
uint16_t lat_deg_int = (uint16_t)ConvertBitIntoBytes(input+24, 8);
|
||||
|
||||
//Longitude Bits
|
||||
uint8_t nmea_edeg = (uint8_t)ConvertBitIntoBytes(&input[96], 8); //Longitude Degrees
|
||||
uint8_t nmea_emin = (uint8_t)ConvertBitIntoBytes(&input[90], 6); //Longitude Minutes
|
||||
uint16_t nmea_eminf = (uint16_t)ConvertBitIntoBytes(&input[74], 14); //Longitude Fractions of Minutes
|
||||
//starting at relevant information, bytes 4,5,6,7
|
||||
uint16_t lon_min_dec = (uint16_t)ConvertBitIntoBytes(input+32, 16);
|
||||
uint16_t lon_ew = input[48]; //0 == N, 1 == S
|
||||
uint16_t lon_min_int = (uint16_t)ConvertBitIntoBytes(input+49, 7);
|
||||
uint16_t lon_deg_int = (uint16_t)ConvertBitIntoBytes(input+56, 8);
|
||||
|
||||
//NOTE: Removed UTC hh.mm.ss6 format found in DMR UDT, could not make it work
|
||||
//with bits already established for time and it works extremely well as is
|
||||
//starting at relevant information, bytes 8,9
|
||||
uint32_t time_secs_low = (uint32_t)ConvertBitIntoBytes(input+64, 16);
|
||||
|
||||
//Course Over Ground in Degrees (0-360)
|
||||
uint16_t nmea_cog = (uint16_t)ConvertBitIntoBytes(&input[135], 9) % 360; //course over ground in degrees //working on due west sample without %360
|
||||
//starting at relevant information, bytes 10,11,12,13
|
||||
uint16_t course_low = (uint16_t)ConvertBitIntoBytes(input+80, 8);
|
||||
uint16_t speed_low = (uint16_t)ConvertBitIntoBytes(input+88, 8);
|
||||
uint16_t course_high = (uint16_t)ConvertBitIntoBytes(input+96+4, 4); //says 3,2,1,0,
|
||||
uint16_t speed_high = (uint16_t)ConvertBitIntoBytes(input+96+0, 4); //says 7,6,5,4
|
||||
|
||||
//timestamp (16-bit value w/ appended 17th bit to MSB)
|
||||
uint32_t rtime = (uint32_t)ConvertBitIntoBytes(&input[104], 16);
|
||||
rtime |= input[144] << 16; //this bit is flagged on after 0xFFFF rollover in the late afternoon
|
||||
uint32_t thour = rtime / 3600;
|
||||
uint32_t tmin = (rtime % 3600) / 60;
|
||||
uint32_t tsec = (rtime % 3600) % 60;
|
||||
//starting at relevant information, byte 14
|
||||
uint32_t time_secs_high = input[104];
|
||||
uint8_t status_emergency = input[105];
|
||||
uint8_t gps_quality = (uint16_t)ConvertBitIntoBytes(input+106, 2);
|
||||
uint8_t gps_sat_num = (uint16_t)ConvertBitIntoBytes(input+108, 4);
|
||||
|
||||
//lat and lon conversion
|
||||
char deg_glyph[4];
|
||||
sprintf (deg_glyph, "%s", "°");
|
||||
float latitude = 0.0f;
|
||||
float longitude = 0.0f;
|
||||
float m_unit = 1.0f / 60.0f;
|
||||
float mm_unit = 1.0f / 600000.0f;
|
||||
//start calculating things
|
||||
float lat_DDMMmm = (float)lat_deg_int + ((float)lat_min_int / 60.0f) + ((float)lat_min_dec / 600000.0f);
|
||||
float lon_DDMMmm = (float)lon_deg_int + ((float)lon_min_int / 60.0f) + ((float)lon_min_dec / 600000.0f);
|
||||
|
||||
if (lat_ns)
|
||||
lat_DDMMmm *= -1.0f;
|
||||
if (lon_ew)
|
||||
lon_DDMMmm *= -1.0f;
|
||||
|
||||
//course conversion
|
||||
uint32_t course = (course_high << 4) | course_low; //tenths of degrees
|
||||
float course_deg = (float)course / 10.0f; //actual value
|
||||
|
||||
//speed conversion
|
||||
uint32_t speed_knots = (speed_high << 4) | speed_low; //tenths of degrees
|
||||
float fmps, fmph, fkph = 0.0f; //conversion of knots to mps, kph, and mph values
|
||||
fmps = (float)nmea_speed * 0.514444; UNUSED(fmps);
|
||||
fmph = (float)nmea_speed * 1.15078f; UNUSED(fmph);
|
||||
fkph = (float)nmea_speed * 1.852f; UNUSED(fkph);
|
||||
fmps = ((float)speed_knots / 10.0f) * 0.514444; UNUSED(fmps);
|
||||
fmph = ((float)speed_knots / 10.0f) * 1.15078f; UNUSED(fmph);
|
||||
fkph = ((float)speed_knots / 10.0f) * 1.852f; UNUSED(fkph);
|
||||
|
||||
//calculate decimal representation of latidude and longitude (need some samples to test)
|
||||
latitude = ( (float)nmea_ndeg + ((float)nmea_nmin*m_unit) + ((float)nmea_nminf*mm_unit) );
|
||||
longitude = ( (float)nmea_edeg + ((float)nmea_emin*m_unit) + ((float)nmea_eminf*mm_unit) );
|
||||
//seconds since midnight as a 17-bit representation and calculation to HHMMSS format
|
||||
uint32_t sec_m = (time_secs_high << 16) | time_secs_low;
|
||||
uint32_t thour = sec_m / 3600;
|
||||
uint32_t tmin = (sec_m % 3600) / 60;
|
||||
uint32_t tsec = (sec_m % 3600) % 60;
|
||||
|
||||
//This is opposite (seemingly) compared to the version specified by DMR UDT NMEA, could still be different bits involved (to be tested)
|
||||
// if (nmea_ns) latitude *= -1.0f; //1 is South, 0 is North
|
||||
// if (nmea_ew) longitude *= -1.0f; //1 is West, 0 is East
|
||||
|
||||
//DMR UDT NMEA original version (working now with new bit selection 88 and 89 like how DMR UDT NMEA format specifies)
|
||||
if (!nmea_ns) latitude *= -1.0f; //0 is South, 1 is North
|
||||
if (!nmea_ew) longitude *= -1.0f; //0 is West, 1 is East
|
||||
|
||||
//is this from LCW, or MAC?
|
||||
uint16_t header = (uint16_t)ConvertBitIntoBytes(&input[0], 16);
|
||||
char deg_glyph[4];
|
||||
sprintf (deg_glyph, "%s", "°");
|
||||
|
||||
//print results
|
||||
fprintf (stderr, "\n");
|
||||
if (header == 0x2AA4)
|
||||
fprintf (stderr, " SRC: %08d;", src);
|
||||
else
|
||||
fprintf (stderr, " VCH: %d - SRC: %08d;", slot, src);
|
||||
fprintf (stderr, " GPS: %f%s, %f%s;", latitude, deg_glyph, longitude, deg_glyph);
|
||||
|
||||
//Speed in Knots (assuming in knots, and not in mps, or kph)
|
||||
if (header != 0x2AA4) //can't yet verify this is present or accurate on LCW
|
||||
if (phase == 2)
|
||||
{
|
||||
if (nmea_speed > 126)
|
||||
fprintf (stderr, " SPD > 126 knots or %f MPH;", fmph); //using MPH here, its Harris, they're in the U.S.
|
||||
fprintf (stderr, " LCH: %d;", slot);
|
||||
if (src != 0)
|
||||
fprintf (stderr, " SRC: %08d;", src);
|
||||
else
|
||||
fprintf (stderr, " SPD: %d knots; %f MPH;", nmea_speed, fmph);
|
||||
fprintf (stderr, " SRC: UNK;");
|
||||
|
||||
if (gps_quality < 3) //GPS hardware present
|
||||
{
|
||||
fprintf (stderr, " GPS: %f%s, %f%s;", lat_DDMMmm, deg_glyph, lon_DDMMmm, deg_glyph);
|
||||
if (gps_quality == 0)
|
||||
fprintf (stderr, " Last Fix;"); //No fix still holds last values
|
||||
else if (gps_quality == 1)
|
||||
fprintf (stderr, " Curr Fix;");
|
||||
else if (gps_quality == 2)
|
||||
fprintf (stderr, " Diff Fix;"); //presumably either lat or lon only changed, not both?
|
||||
|
||||
fprintf (stderr, " #Sats: %d;", gps_sat_num);
|
||||
}
|
||||
else if (gps_quality == 3)
|
||||
fprintf (stderr, " GPS: Not Enabled / No Hardware Present;");
|
||||
|
||||
fprintf (stderr, " COG: %f%s;", course_deg, deg_glyph);
|
||||
fprintf (stderr, " SPD: %f k/h;", fkph);
|
||||
|
||||
if (sec_m != 0x1FFFF) // && sec_m != 0 (so says the doc, but midnight is a valid time)
|
||||
fprintf (stderr, " T: %02d:%02d:%02d;", thour, tmin, tsec);
|
||||
|
||||
if (status_emergency)
|
||||
fprintf (stderr, " Emergency;");
|
||||
}
|
||||
else if (phase == 1)
|
||||
{
|
||||
if (src != 0)
|
||||
fprintf (stderr, " SRC: %08d;", src);
|
||||
else
|
||||
fprintf (stderr, " SRC: UNK;");
|
||||
fprintf (stderr, " GPS: %f%s, %f%s;", lat_DDMMmm, deg_glyph, lon_DDMMmm, deg_glyph);
|
||||
|
||||
//again, can't vouch 100%, so just going to dump the GPS Coordinates and zero out other values for LRRP file
|
||||
fkph = 0.0f;
|
||||
course_deg = 0.0f;
|
||||
}
|
||||
|
||||
//Course Over Ground (COG)
|
||||
fprintf (stderr, " COG: %03d%s;", nmea_cog, deg_glyph); //%360
|
||||
|
||||
//debug speed
|
||||
// fprintf (stderr, " RSPD: %02X;", nmea_speed);
|
||||
|
||||
//debug cog
|
||||
// fprintf (stderr, " RCOG: %03X;", nmea_cog >> 0);
|
||||
|
||||
//UTC Time
|
||||
fprintf (stderr, " T: %02d:%02d:%02d UTC;", thour, tmin, tsec);
|
||||
|
||||
//Fix Indicator
|
||||
if (nmea_q) fprintf (stderr, " Current Fix;");
|
||||
else fprintf (stderr, " Last Fix;");
|
||||
|
||||
//save to ncurses string
|
||||
sprintf (state->dmr_embedded_gps[slot], "(%f%s, %f%s)", latitude, deg_glyph, longitude, deg_glyph);
|
||||
sprintf (state->dmr_embedded_gps[slot], "(%f%s, %f%s)", lat_DDMMmm, deg_glyph, lon_DDMMmm, deg_glyph);
|
||||
|
||||
//save to event history string
|
||||
if (state->event_history_s[slot].Event_History_Items[0].source_id == src && src != 0)
|
||||
sprintf (state->event_history_s[slot].Event_History_Items[0].gps_s, "%s", state->dmr_embedded_gps[slot]);
|
||||
|
||||
//save to LRRP report for mapping/logging
|
||||
FILE * pFile; //file pointer
|
||||
if (opts->lrrp_file_output == 1)
|
||||
if (opts->lrrp_file_output == 1 && src != 0 && gps_quality != 3)
|
||||
{
|
||||
|
||||
char * datestr = getDate();
|
||||
char * timestr = getTime();
|
||||
char * datestr = getDateS();
|
||||
char * timestr = getTimeC();
|
||||
|
||||
//rounded interger formats for the log report
|
||||
int s = (int)fkph;
|
||||
int a = nmea_cog;
|
||||
|
||||
//open file by name that is supplied in the ncurses terminal, or cli
|
||||
pFile = fopen (opts->lrrp_out_file, "a");
|
||||
fprintf (pFile, "%s\t", datestr );
|
||||
fprintf (pFile, "%s\t", timestr ); //could switch to UTC time on PDU if desired
|
||||
fprintf (pFile, "%08d\t", src);
|
||||
fprintf (pFile, "%.6lf\t", latitude);
|
||||
fprintf (pFile, "%.6lf\t", longitude);
|
||||
fprintf (pFile, "%d\t ", s);
|
||||
fprintf (pFile, "%d\t ", a);
|
||||
fprintf (pFile, "\n");
|
||||
fclose (pFile);
|
||||
|
||||
if (timestr != NULL)
|
||||
{
|
||||
free (timestr);
|
||||
timestr = NULL;
|
||||
}
|
||||
if (datestr != NULL)
|
||||
{
|
||||
free (datestr);
|
||||
datestr = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//NOTE: There seems to be a few more octets left undecoded in this PDU
|
||||
//not including the CRC obviously, unsure of their meaning, could relate
|
||||
//to other GPS functions like precision, etc, unknown.
|
||||
|
||||
//NOTE2: All these values are just best effort based on observation and
|
||||
//making map points match using logic and distance over time for speed and course
|
||||
//without any documentation, any of these values may still be wrong
|
||||
|
||||
}
|
||||
|
||||
//fallback version (if desired/required)
|
||||
void harris_gps(dsd_opts * opts, dsd_state * state, int slot, uint8_t * input)
|
||||
{
|
||||
|
||||
uint8_t lat_sign, lat_deg, lat_min = 0;
|
||||
uint8_t lon_sign, lon_deg, lon_min = 0;
|
||||
uint16_t lat_mmin = 0;
|
||||
uint16_t lon_mmin = 0;
|
||||
|
||||
//potentially in this PDU, but unverifiable without documentation or reasonable
|
||||
//mathematical proof vs map points and direction of travel / distance over time (assuming the radio is facing that direction)
|
||||
uint16_t rspeed = (uint16_t)ConvertBitIntoBytes(&input[136], 8); //MSB //136
|
||||
rspeed = (rspeed << 8) + (uint16_t)ConvertBitIntoBytes(&input[128], 8); //LSB //128
|
||||
|
||||
//this works okay for example of driver driving due west at the speed limit of the road (fluke?)
|
||||
uint16_t rangle = (uint16_t)ConvertBitIntoBytes(&input[120], 8); //120,8
|
||||
float fspeed = (float)rspeed;
|
||||
float fangle = (float)rangle;
|
||||
|
||||
fspeed /= 255.0f; //unit value of 1 bit
|
||||
fspeed *= 1.56f; //this is based on an observation of a driver moving approx 210 meters in about 8 seconds and this makes it just under the speed limit on the road there
|
||||
// fangle *= 360.0f/255.0f; //unit value of 1 bit
|
||||
fangle *= 2.0f; //may exceed 360 degrees as is (use %)
|
||||
|
||||
int s, a = 0;
|
||||
s = (int)fspeed * 3.6;
|
||||
a = (int)fangle % 360;
|
||||
|
||||
//fix and quality indicators?
|
||||
uint8_t fix = input[147]; UNUSED(fix);
|
||||
uint8_t quality = (uint16_t)ConvertBitIntoBytes(&input[148], 3); UNUSED(quality);
|
||||
|
||||
//timestamp (16-bit value w/ appended 17th bit to MSB)
|
||||
uint32_t rtime = (uint32_t)ConvertBitIntoBytes(&input[104], 16); //seconds since midnight since last GPS fix (whatever the radio has as internal time)
|
||||
rtime |= input[144] << 16; //test appending this as bit 17 (observed this bit set after the afternoon 0xFFFF rollover)
|
||||
uint32_t thour = rtime / 3600;
|
||||
uint32_t tmin = (rtime % 3600) / 60;
|
||||
uint32_t tsec = (rtime % 3600) % 60;
|
||||
|
||||
float lat_dec = 0.0f;
|
||||
float lon_dec = 0.0f;
|
||||
|
||||
char deg_glyph[4];
|
||||
sprintf (deg_glyph, "%s", "°");
|
||||
|
||||
//This appears to be similar to the NMEA GPGGA format (DDmm.mm) but
|
||||
//octets are ordered in least significant to most significant value
|
||||
lat_mmin = (uint16_t)ConvertBitIntoBytes(&input[40], 16); //? bits required, but grabbing two octets
|
||||
lat_min = (uint8_t)ConvertBitIntoBytes(&input[58], 6); //6 bits required to get 60
|
||||
lat_deg = (uint8_t)ConvertBitIntoBytes(&input[65], 7); //7 bits required to get 90
|
||||
lat_sign = input[72]; //64
|
||||
|
||||
lon_mmin = (uint16_t)ConvertBitIntoBytes(&input[72], 16); //? bits required, but grabbing two octets
|
||||
lon_min = (uint8_t)ConvertBitIntoBytes(&input[90], 6); //6 bits required to get 60
|
||||
lon_deg = (uint8_t)ConvertBitIntoBytes(&input[96], 8); //8 bits required to get 180
|
||||
lon_sign = input[88]; //88, unsure of a correct location, but on the sample with 0 minutes, this lonely bit was flagged on
|
||||
|
||||
int src = 0;
|
||||
if (slot == 0) src = state->lastsrc;
|
||||
if (slot == 1) src = state->lastsrcR;
|
||||
|
||||
//calculate decimal representation (was a pain to figure out the sub minute values)
|
||||
lat_dec = ( (float)lat_deg + ((float)lat_min/60.0f) + ((float)lat_mmin/600000.0f) );
|
||||
lon_dec = ( (float)lon_deg + ((float)lon_min/60.0f) + ((float)lon_mmin/600000.0f) );
|
||||
|
||||
if (lat_sign)
|
||||
lat_dec *= -1.0f;
|
||||
|
||||
if (lon_sign)
|
||||
lon_dec *= -1.0f;
|
||||
|
||||
//line break
|
||||
fprintf (stderr, "\n");
|
||||
fprintf (stderr, " GPS: %f%s, %f%s;", lat_dec, deg_glyph, lon_dec, deg_glyph);
|
||||
|
||||
//gps fix time
|
||||
// fprintf (stderr, " RT: %05X;", rtime); //debug
|
||||
fprintf (stderr, " LTS: %02d:%02d:%02d UTC;", thour, tmin, tsec); //last time synced to GPS
|
||||
|
||||
//speed and direction
|
||||
// fprintf (stderr, " RSPD: %04X;", rspeed); //debug
|
||||
// fprintf (stderr, " RANG: %02X;", rangle); //debug
|
||||
// fprintf (stderr, " MPS: %06.02f;", fspeed);
|
||||
// fprintf (stderr, " KPH: %06.02f;", fspeed * 3.6f);
|
||||
// fprintf (stderr, " MPH: %06.02f;", fspeed * 2.2369f);
|
||||
fprintf (stderr, " DIR: %03d%s;", a, deg_glyph);
|
||||
|
||||
// fprintf (stderr, " FIX: %d", fix);
|
||||
// fprintf (stderr, " QLT: %02d", quality);
|
||||
|
||||
// fprintf (stderr, " SRC: %08d;", src);
|
||||
|
||||
//save to array for ncurses
|
||||
sprintf (state->dmr_embedded_gps[slot], "GPS: (%f%s, %f%s)", lat_dec, deg_glyph, lon_dec, deg_glyph);
|
||||
|
||||
//save to LRRP report for mapping/logging
|
||||
FILE * pFile; //file pointer
|
||||
if (opts->lrrp_file_output == 1)
|
||||
{
|
||||
|
||||
char * datestr = getDate();
|
||||
char * timestr = getTime();
|
||||
int a = (int)course_deg;
|
||||
|
||||
//open file by name that is supplied in the ncurses terminal, or cli
|
||||
FILE * pFile; //file pointer
|
||||
pFile = fopen (opts->lrrp_out_file, "a");
|
||||
fprintf (pFile, "%s\t", datestr );
|
||||
fprintf (pFile, "%s\t", timestr );
|
||||
fprintf (pFile, "%08d\t", src);
|
||||
fprintf (pFile, "%.6lf\t", lat_dec);
|
||||
fprintf (pFile, "%.6lf\t", lon_dec);
|
||||
fprintf (pFile, "%.6lf\t", lat_DDMMmm);
|
||||
fprintf (pFile, "%.6lf\t", lon_DDMMmm);
|
||||
fprintf (pFile, "%d\t ", s);
|
||||
fprintf (pFile, "%d\t ", a);
|
||||
fprintf (pFile, "\n");
|
||||
@@ -562,9 +439,14 @@ void harris_gps(dsd_opts * opts, dsd_state * state, int slot, uint8_t * input)
|
||||
|
||||
}
|
||||
|
||||
//NOTE: Thanks to DSheirer (SDRTrunk) for helping me work out a few of the things in here
|
||||
//not entirely convinced on some of these calcs (speed, angle, and TS) but sure these bits
|
||||
//are actual speed/direction/timestamps judging from what the coordinates show on a map
|
||||
//debugging dump of this message
|
||||
// if (opts->payload == 1)
|
||||
// {
|
||||
// fprintf (stderr, "\n ");
|
||||
// for (int i = 0; i < 14; i++)
|
||||
// fprintf (stderr, "%02X ", (uint8_t)ConvertBitIntoBytes(input+(i*8), 8));
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
//externalize embedded GPS - Confirmed working now on NE, NW, SE, and SW coordinates
|
||||
@@ -654,8 +536,8 @@ void dmr_embedded_gps (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[])
|
||||
if (opts->lrrp_file_output == 1)
|
||||
{
|
||||
|
||||
char * datestr = getDate();
|
||||
char * timestr = getTime();
|
||||
char * datestr = getDateS();
|
||||
char * timestr = getTimeC();
|
||||
|
||||
//open file by name that is supplied in the ncurses terminal, or cli
|
||||
pFile = fopen (opts->lrrp_out_file, "a");
|
||||
@@ -762,8 +644,9 @@ void apx_embedded_gps (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[])
|
||||
FILE * pFile; //file pointer
|
||||
if (opts->lrrp_file_output == 1)
|
||||
{
|
||||
char * datestr = getDate();
|
||||
char * timestr = getTime();
|
||||
|
||||
char * datestr = getDateS();
|
||||
char * timestr = getTimeC();
|
||||
|
||||
//open file by name that is supplied in the ncurses terminal, or cli
|
||||
pFile = fopen (opts->lrrp_out_file, "a");
|
||||
@@ -827,6 +710,7 @@ void decode_cellocator(dsd_opts * opts, dsd_state * state, uint8_t * input, int
|
||||
//will need to establish a len value for data and contents
|
||||
|
||||
}
|
||||
|
||||
void decode_ars(dsd_opts * opts, dsd_state * state, uint8_t * input, int len)
|
||||
{
|
||||
//TODO: This
|
||||
@@ -835,3 +719,240 @@ void decode_ars(dsd_opts * opts, dsd_state * state, uint8_t * input, int len)
|
||||
UNUSED(input);
|
||||
UNUSED(len);
|
||||
}
|
||||
|
||||
//WIP: Based on Denny's always brilliant reverse-engineering of GPS reports
|
||||
void nxdn_gps_report(dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src)
|
||||
{
|
||||
|
||||
//Elevation
|
||||
int16_t elevation = (int16_t)convert_bits_into_output(input+56, 16);
|
||||
|
||||
//Speed & Heading (in tenths)
|
||||
uint16_t speed_raw = (uint16_t)convert_bits_into_output(input+74, 14);
|
||||
uint16_t heading_raw = (uint16_t)convert_bits_into_output(input+92, 12);
|
||||
double speed = speed_raw / 10.0; //knots or k/h
|
||||
double heading = heading_raw / 10.0;
|
||||
|
||||
//Date
|
||||
uint16_t year = (uint16_t)(convert_bits_into_output(input+136,7) + 2000);
|
||||
uint8_t month = (uint8_t) convert_bits_into_output(input+143,4);
|
||||
uint8_t day = (uint8_t)(convert_bits_into_output(input+147,5) + 1);
|
||||
|
||||
//Longitude (DDMM.mmmm format)
|
||||
uint16_t lon_degmin = (uint16_t)convert_bits_into_output(input+152, 16);
|
||||
uint16_t lon_frac = (uint16_t)convert_bits_into_output(input+16, 15);
|
||||
uint8_t lon_hem = (uint8_t) convert_bits_into_output(input+183, 1);
|
||||
double lon_minutes = (lon_degmin % 100) + (lon_frac / 10000.0);
|
||||
double lon_decimal = (lon_degmin / 100) + (lon_minutes / 60.0);
|
||||
double longitude = (lon_hem == 0) ? lon_decimal : -lon_decimal;
|
||||
|
||||
//Latitude (DDMM.mmmm format)
|
||||
uint16_t lat_degmin = (uint16_t)convert_bits_into_output(input+184,16);
|
||||
uint16_t lat_frac = (uint16_t)convert_bits_into_output(input+200,15);
|
||||
uint8_t lat_hem = (uint8_t) convert_bits_into_output(input+215,1);
|
||||
double lat_minutes = (lat_degmin % 100) + (lat_frac / 10000.0);
|
||||
double lat_decimal = (lat_degmin / 100) + (lat_minutes / 60.0);
|
||||
double latitude = (lat_hem == 0) ? lat_decimal : -lat_decimal;
|
||||
|
||||
//Time
|
||||
uint8_t hour = (uint8_t) convert_bits_into_output(input+247,5);
|
||||
uint8_t minute = (uint8_t) convert_bits_into_output(input+252,6);
|
||||
|
||||
char deg_glyph[4];
|
||||
sprintf (deg_glyph, "%s", "°");
|
||||
|
||||
//Report
|
||||
fprintf (stderr, "\n GPS: (%f%s, %f%s) ", latitude, deg_glyph, longitude, deg_glyph);
|
||||
fprintf (stderr, "Speed: %lf k/h; ", speed);
|
||||
fprintf (stderr, "COG: %lf; ", heading);
|
||||
fprintf (stderr, "Elevation: %i; ", elevation);
|
||||
fprintf (stderr, "Date: %04d/%02d/%02d ", year, month, day);
|
||||
fprintf (stderr, "Time: %02d:%02d ", hour, minute);
|
||||
|
||||
//dump to event history
|
||||
sprintf (state->event_history_s[0].Event_History_Items[0].gps_s, "(%f%s, %f%s)", latitude, deg_glyph, longitude, deg_glyph);
|
||||
uint32_t source = state->dmr_lrrp_source[0];
|
||||
uint32_t target = state->dmr_lrrp_target[0];
|
||||
char comp_string[500]; memset (comp_string, 0, sizeof(comp_string));
|
||||
sprintf (comp_string, "GPS SRC: %d; TGT: %d;", source, target);
|
||||
watchdog_event_datacall (opts, state, source, target, comp_string, 0);
|
||||
|
||||
//save to LRRP report for mapping/logging
|
||||
if (opts->lrrp_file_output == 1 && src != 0)
|
||||
{
|
||||
|
||||
char * datestr = getDateS();
|
||||
char * timestr = getTimeC();
|
||||
|
||||
//rounded interger formats for the log report
|
||||
int s = (int)speed;
|
||||
int a = (int)heading;
|
||||
|
||||
//open file by name that is supplied in the ncurses terminal, or cli
|
||||
FILE * pFile; //file pointer
|
||||
pFile = fopen (opts->lrrp_out_file, "a");
|
||||
fprintf (pFile, "%s\t", datestr );
|
||||
fprintf (pFile, "%s\t", timestr );
|
||||
fprintf (pFile, "%08d\t", src);
|
||||
fprintf (pFile, "%.6lf\t", latitude);
|
||||
fprintf (pFile, "%.6lf\t", longitude);
|
||||
fprintf (pFile, "%d\t ", s);
|
||||
fprintf (pFile, "%d\t ", a);
|
||||
fprintf (pFile, "\n");
|
||||
fclose (pFile);
|
||||
|
||||
if (timestr != NULL)
|
||||
{
|
||||
free (timestr);
|
||||
timestr = NULL;
|
||||
}
|
||||
if (datestr != NULL)
|
||||
{
|
||||
free (datestr);
|
||||
datestr = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//reset source and target from PDU storage
|
||||
state->dmr_lrrp_source[0] = 0;
|
||||
state->dmr_lrrp_target[0] = 0;
|
||||
}
|
||||
|
||||
uint8_t nmea_sentence_checker(dsd_opts * opts, dsd_state * state, uint8_t * input, uint8_t slot, int len)
|
||||
{
|
||||
|
||||
//Check Start and End, and Validity w/ Checksum
|
||||
int i;
|
||||
uint8_t start_value = (uint8_t)ConvertBitIntoBytes(input, 8);
|
||||
uint8_t end_value = 0;
|
||||
uint8_t checksum_calc = 0;
|
||||
uint8_t checksum_ext = 0;
|
||||
char checksum_str[3];
|
||||
memset(checksum_str, 0, sizeof(checksum_str));
|
||||
uint8_t valid = 0;
|
||||
|
||||
if (start_value == '$' || start_value == '!')
|
||||
{
|
||||
//debug
|
||||
// fprintf (stderr, "Start Okay! ");
|
||||
|
||||
for (i = 1; i < len; i++)
|
||||
{
|
||||
uint8_t value = (uint8_t)ConvertBitIntoBytes(input+(i*8), 8);
|
||||
if (value == '*')
|
||||
{
|
||||
end_value = value;
|
||||
checksum_str[0] = (uint8_t)ConvertBitIntoBytes(input+(i*8)+8, 8);
|
||||
checksum_str[1] = (uint8_t)ConvertBitIntoBytes(input+(i*8)+16, 8);
|
||||
checksum_str[2] = 0;
|
||||
sscanf (checksum_str, "%hhX", &checksum_ext);
|
||||
break;
|
||||
}
|
||||
else if (value >= 0x20 && value < 0x7F)
|
||||
{
|
||||
checksum_calc ^= value;
|
||||
continue;
|
||||
}
|
||||
else break; //anything else that isn't ascii range values
|
||||
}
|
||||
|
||||
//debug
|
||||
// if (end_value == '*')
|
||||
// fprintf (stderr, "Checksum Present! ");
|
||||
|
||||
if (checksum_ext == checksum_calc)
|
||||
{
|
||||
// fprintf (stderr, "Checksum Okay! (%02X / %02X)", checksum_calc, checksum_ext);
|
||||
valid = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// fprintf (stderr, "Checksum Err! (%02X / %02X) ", checksum_calc, checksum_ext);
|
||||
valid = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//dump, if valid
|
||||
memset(state->event_history_s[slot].Event_History_Items[0].text_message, 0, sizeof(state->event_history_s[slot].Event_History_Items[0].text_message));
|
||||
if (valid)
|
||||
{
|
||||
uint8_t ascii = 0;
|
||||
uint16_t crlf = 0;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
ascii = (uint8_t)ConvertBitIntoBytes(input+(i*8), 8);
|
||||
crlf = (uint16_t)ConvertBitIntoBytes(input+(i*8), 16);
|
||||
if (ascii >= 0x20 && ascii < 0x7F)
|
||||
{
|
||||
fprintf (stderr, "%c", ascii);
|
||||
state->event_history_s[slot].Event_History_Items[0].text_message[i] = ascii;
|
||||
}
|
||||
else if (crlf == 0x0D0A) //NMEA Sentences end with 0x0D 0x0A (CR/LF)
|
||||
{
|
||||
fprintf (stderr, "%s", " ");
|
||||
state->event_history_s[slot].Event_History_Items[0].text_message[i+0] = 0x20; //Space
|
||||
state->event_history_s[slot].Event_History_Items[0].text_message[i+1] = 0x20; //Space
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
else break; //anything else that isn't ascii range values
|
||||
}
|
||||
|
||||
//check for supplimentary string i.e., $PP25
|
||||
ascii = (uint8_t)ConvertBitIntoBytes(input+(i*8), 8);
|
||||
if (crlf == 0x0D0A && ascii == '$')
|
||||
{
|
||||
for (i = i; i < len; i++)
|
||||
{
|
||||
ascii = (uint8_t)ConvertBitIntoBytes(input+(i*8), 8);
|
||||
crlf = (uint16_t)ConvertBitIntoBytes(input+(i*8), 16);
|
||||
|
||||
if (ascii >= 0x20 && ascii < 0x7F)
|
||||
{
|
||||
fprintf (stderr, "%c", ascii);
|
||||
state->event_history_s[slot].Event_History_Items[0].text_message[i] = ascii;
|
||||
}
|
||||
else if (crlf == 0x0D0A) //Supplimentary Sentences end with 0x0D 0x0A (CR/LF)
|
||||
{
|
||||
state->event_history_s[slot].Event_History_Items[0].text_message[i] = 0x20;
|
||||
break;
|
||||
}
|
||||
else break; //anything else that isn't ascii range values
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//debug
|
||||
// fprintf(stderr, "\n %s", state->event_history_s[slot].Event_History_Items[0].text_message);
|
||||
|
||||
//dump to event history
|
||||
state->event_history_s[slot].Event_History_Items[0].text_message[i] = '\0';
|
||||
uint32_t source = state->dmr_lrrp_source[slot];
|
||||
uint32_t target = state->dmr_lrrp_target[slot];
|
||||
char comp_string[500]; memset (comp_string, 0, sizeof(comp_string));
|
||||
sprintf (comp_string, "NMEA SRC: %d; TGT: %d;", source, target);
|
||||
watchdog_event_datacall (opts, state, source, target, comp_string, slot);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (start_value != '$' && start_value != '!')
|
||||
fprintf (stderr, " Not an NMEA Sentence Structure;");
|
||||
else if ( (start_value == '$' || start_value == '!') && end_value != '*')
|
||||
fprintf (stderr, " Possible NMEA Sentence, Missing Ending *;");
|
||||
else if ( start_value != '$' && start_value != '!' && end_value == '*')
|
||||
fprintf (stderr, " Possible NMEA Sentence, Missing Starting $ or !;");
|
||||
else if (checksum_calc != checksum_ext)
|
||||
fprintf (stderr, " NMEA Checksum Error (%02X / %02X);", checksum_calc, checksum_ext);
|
||||
}
|
||||
|
||||
//TODO: Convert to LRRP data and report / GPS on Event History
|
||||
|
||||
state->dmr_lrrp_source[slot] = 0;
|
||||
state->dmr_lrrp_target[slot] = 0;
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@@ -7,26 +7,31 @@ int csvGroupImport(dsd_opts * opts, dsd_state * state)
|
||||
{
|
||||
char filename[1024] = "filename.csv";
|
||||
sprintf (filename, "%s", opts->group_in_file);
|
||||
//filename[1023] = '\0'; //necessary?
|
||||
|
||||
char buffer[BSIZE];
|
||||
FILE * fp;
|
||||
fp = fopen(filename, "r");
|
||||
if (fp == NULL) {
|
||||
|
||||
if (fp == NULL)
|
||||
{
|
||||
printf("Unable to open group file '%s'\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int row_count = 0;
|
||||
int field_count = 0;
|
||||
long int group_number = 0; //local group number for array index value
|
||||
UNUSED(group_number);
|
||||
int i = 0;
|
||||
while (fgets(buffer, BSIZE, fp)) {
|
||||
|
||||
unsigned int i = 0;
|
||||
while (fgets(buffer, BSIZE, fp))
|
||||
{
|
||||
|
||||
field_count = 0;
|
||||
row_count++;
|
||||
if (row_count == 1)
|
||||
continue; //don't want labels
|
||||
char * field = strtok(buffer, ","); //seperate by comma
|
||||
while (field) {
|
||||
while (field)
|
||||
{
|
||||
|
||||
if (field_count == 0)
|
||||
{
|
||||
@@ -34,23 +39,38 @@ int csvGroupImport(dsd_opts * opts, dsd_state * state)
|
||||
state->group_array[i].groupNumber = atol(field);
|
||||
fprintf (stderr, "%ld, ", state->group_array[i].groupNumber);
|
||||
}
|
||||
if (field_count == 1)
|
||||
else if (field_count == 1)
|
||||
{
|
||||
strcpy(state->group_array[i].groupMode, field);
|
||||
// strcpy(state->group_array[i].groupMode, field);
|
||||
strncpy(state->group_array[i].groupMode, field, 2); //only copy two letters max A, B, D, DE
|
||||
fprintf (stderr, "%s, ", state->group_array[i].groupMode);
|
||||
}
|
||||
if (field_count == 2)
|
||||
else if (field_count == 2)
|
||||
{
|
||||
strcpy(state->group_array[i].groupName, field);
|
||||
// strcpy(state->group_array[i].groupName, field);
|
||||
strncpy(state->group_array[i].groupName, field, 98); //this should also fix potential issues observed in past when there is no comma after the name
|
||||
|
||||
uint16_t len = strlen((const char*)state->group_array[i].groupName);
|
||||
|
||||
//remove any potential linebreaks at this point
|
||||
if (state->group_array[i].groupName[len-1] == '\n')
|
||||
{
|
||||
// fprintf(stderr, " ** LEN: %d; ** ", len); //debug
|
||||
state->group_array[i].groupName[len-1] = '\0';
|
||||
}
|
||||
|
||||
fprintf (stderr, "%s ", state->group_array[i].groupName);
|
||||
}
|
||||
|
||||
field = strtok(NULL, ",");
|
||||
field_count++;
|
||||
}
|
||||
|
||||
fprintf (stderr, "\n");
|
||||
|
||||
i++;
|
||||
state->group_tally++;
|
||||
|
||||
}
|
||||
fclose(fp);
|
||||
return 0;
|
||||
|
||||
@@ -234,7 +234,7 @@
|
||||
state->dibit_buf_p = state->dibit_buf + 200;
|
||||
memset (state->dibit_buf, 0, sizeof (int) * 200);
|
||||
//dmr buffer
|
||||
state->dmr_payload_p = state->dibit_buf + 200;
|
||||
state->dmr_payload_p = state->dmr_payload_buf + 200;
|
||||
memset (state->dmr_payload_buf, 0, sizeof (int) * 200);
|
||||
memset (state->dmr_stereo_payload, 1, sizeof(int) * 144);
|
||||
//dmr buffer end
|
||||
@@ -417,7 +417,7 @@
|
||||
// state->p25_vc_freq[1] = 0;
|
||||
|
||||
//new nxdn stuff
|
||||
state->nxdn_part_of_frame = 0;
|
||||
state->nxdn_part_of_frame = 3;
|
||||
state->nxdn_ran = 0;
|
||||
state->nxdn_sf = 0;
|
||||
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); //init on 1, bad CRC all
|
||||
@@ -636,6 +636,7 @@
|
||||
opts->wav_out_file[0] = 0;
|
||||
opts->wav_out_fileR[0] = 0;
|
||||
opts->wav_out_file_raw[0] = 0;
|
||||
opts->wav_custom_tag[0] = 0;
|
||||
opts->symbol_out_file[0] = 0;
|
||||
opts->lrrp_out_file[0] = 0;
|
||||
opts->event_out_file[0] = 0;
|
||||
@@ -678,6 +679,7 @@
|
||||
opts->uvquality = 3;
|
||||
opts->inverted_x2tdma = 1; // most transmitter + scanner + sound card combinations show inverted signals for this
|
||||
opts->inverted_dmr = 0; // most transmitter + scanner + sound card combinations show non-inverted signals for this
|
||||
opts->inverted_nxdn = 0; //only seek pos by default to reduce false sync pattern detections
|
||||
opts->mod_threshold = 26;
|
||||
opts->ssize = 128; //36 default, max is 128, much cleaner data decodes on Phase 2 cqpsk at max
|
||||
opts->msize = 1024; //15 default, max is 1024, much cleaner data decodes on Phase 2 cqpsk at max
|
||||
@@ -688,6 +690,7 @@
|
||||
//all RTL user options -- enabled AGC by default due to weak signal related issues
|
||||
opts->rtl_dev_index = 0; //choose which device we want by index number
|
||||
opts->rtl_gain_value = 0; //mid value, 0 - AGC - 0 to 49 acceptable values
|
||||
opts->rtl_gain_actual = -100; //mirror dongle.gain actual values -100 is AUTO_GAIN, else nearest_gain(dongle.dev, dongle.gain);
|
||||
opts->rtl_squelch_level = 100; //100 by default, but only affects NXDN and dPMR during framesync test, compared to RMS value
|
||||
opts->rtl_volume_multiplier = 2; //sample multiplier; This multiplies the sample value to produce a higher 'inlvl' for the demodulator
|
||||
opts->rtl_udp_port = 0; //set UDP port for RTL remote -- 0 by default, will be making this optional for some external/legacy use cases (edacs-fm, etc)
|
||||
@@ -1185,7 +1188,9 @@
|
||||
//trunking
|
||||
memset (state->trunk_lcn_freq, 0, sizeof(state->trunk_lcn_freq));
|
||||
memset (state->trunk_chan_map, 0, sizeof(state->trunk_chan_map));
|
||||
state->group_tally = 0;
|
||||
//allocate max 24-bit address w/ memory with zero fill (termination) for end of strings, import will only copy 98, leaving the terminator intact
|
||||
state->group_array = calloc(16778000, sizeof(groupinfo));
|
||||
state->group_tally = 0; //is an unsigned int
|
||||
state->lcn_freq_count = 0; //number of frequncies imported as an enumerated lcn list
|
||||
state->lcn_freq_roll = 0; //needs reset if sync is found?
|
||||
state->last_cc_sync_time = time(NULL);
|
||||
@@ -1201,7 +1206,7 @@
|
||||
state->tg_hold = 0;
|
||||
|
||||
//new nxdn stuff
|
||||
state->nxdn_part_of_frame = 0;
|
||||
state->nxdn_part_of_frame = 3;
|
||||
state->nxdn_ran = 0;
|
||||
state->nxdn_sf = 0;
|
||||
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); //init on 1, bad CRC all
|
||||
@@ -1432,6 +1437,8 @@
|
||||
printf (" -w <file> Output synthesized speech to a single static .wav file. (Do not use with -P Per Call Switch)\n");
|
||||
printf (" -P Enable Per Call WAV file saving. (Do not use with -w filename.wav single wav file switch)\n");
|
||||
printf (" (Per Call works with everything now and doesn't require ncurses terminal!)\n");
|
||||
printf (" -j <string> Custom Tag(s), colon delimited, to append to Per Call decoded .wav files.\n");
|
||||
printf (" -j TGT_LAPD:TAG_FIRE:FREQ_851987500 (Total 3 Tags, Maximum Letter Length = 100)\n");
|
||||
printf (" -a Enable Call Alert Beep\n");
|
||||
printf (" (Warning! Might be annoying.)\n");
|
||||
printf (" -J <file> Specify Filename for Event Log Output.\n");
|
||||
@@ -1489,6 +1496,11 @@
|
||||
printf (" -xx Expect non-inverted X2-TDMA signal\n");
|
||||
printf (" -xr Expect inverted DMR signal\n");
|
||||
printf (" -xd Expect inverted ICOM dPMR signal\n");
|
||||
#ifdef NXDN_OLD_SYNC
|
||||
//
|
||||
#else
|
||||
printf (" -xn Expect inverted NXDN/DCR/IDAS signal\n");
|
||||
#endif
|
||||
printf ("\n");
|
||||
printf (" * denotes frame types that cannot be auto-detected.\n");
|
||||
printf (" ** Phase 2 Single Frequency may require user to manually set WACN/SYSID/CC parameters if MAC_SIGNAL not present.\n");
|
||||
@@ -1767,10 +1779,10 @@
|
||||
if (opts->static_wav_file == 0)
|
||||
{
|
||||
if (opts->wav_out_f != NULL)
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
|
||||
if (opts->wav_out_fR != NULL)
|
||||
opts->wav_out_fR = close_and_rename_wav_file(opts->wav_out_fR, opts->wav_out_fileR, opts->wav_out_dir, &state->event_history_s[1]);
|
||||
opts->wav_out_fR = close_and_rename_wav_file(opts->wav_out_fR, opts->wav_out_fileR, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[1]);
|
||||
}
|
||||
|
||||
else if (opts->static_wav_file == 1)
|
||||
@@ -1882,11 +1894,11 @@
|
||||
init_audio_filters(&state); //audio filters
|
||||
init_rrc_filter_memory(); //initialize input filtering
|
||||
InitAllFecFunction();
|
||||
CNXDNConvolution_init();
|
||||
|
||||
|
||||
exitflag = 0;
|
||||
|
||||
while ((c = getopt (argc, argv, "~yhaepPqs:t:v:z:i:o:d:c:g:n:w:B:C:R:f:m:u:x:A:S:G:D:L:V:U:YK:b:H:X:M:NQ:WrlZTF@:!:01:2:345:6:^:7:8_:9:Ek:I:J:O+:")) != -1)
|
||||
while ((c = getopt (argc, argv, "~yhaepPqs:t:v:z:i:o:d:c:g:n:w:B:C:R:f:m:u:x:A:S:G:D:L:V:U:YK:b:H:X:M:NQ:WrlZTF@:!:01:2:345:6:^:7:8_:9:Ek:I:J:O+:j:")) != -1)
|
||||
{
|
||||
|
||||
switch (c)
|
||||
@@ -1900,7 +1912,7 @@
|
||||
opts.call_alert = 1;
|
||||
break;
|
||||
|
||||
//Free'd up switches include: j, M,
|
||||
|
||||
//NOTE: Don't use -*, found it exhibits unusual behavior, was setting
|
||||
//itself to -3 for some reason, may be reading contents of directory
|
||||
|
||||
@@ -1909,6 +1921,12 @@
|
||||
|
||||
//NOTE: The 'K' option for single BP key has been swapped to 'b'
|
||||
//'K' is now used for hexidecimal key.csv imports
|
||||
|
||||
case 'j': //custom wav tags appended to per-call wav files
|
||||
strncpy(opts.wav_custom_tag, optarg, 99);
|
||||
opts.wav_custom_tag[99] = '\0';
|
||||
fprintf(stderr, "Appending Tags %s to per-call wav files. \n", opts.wav_custom_tag);
|
||||
break;
|
||||
|
||||
//this is a debug option hidden from users, but use it to replay .bin files on loop
|
||||
case '~':
|
||||
@@ -1928,10 +1946,10 @@
|
||||
fprintf (stderr, "TG Hold set to %d \n", state.tg_hold);
|
||||
break;
|
||||
|
||||
//experimental audio monitoring
|
||||
//raw audio monitoring
|
||||
case '8':
|
||||
opts.monitor_input_audio = 1;
|
||||
fprintf (stderr,"Experimental Raw Analog Source Monitoring Enabled (Pulse Audio Only!)\n");
|
||||
fprintf (stderr,"Raw Analog Source Monitoring Enabled.\n");
|
||||
break;
|
||||
|
||||
//rc4 enforcement on DMR (due to missing the PI header)
|
||||
@@ -2757,6 +2775,11 @@
|
||||
sprintf (opts.output_name, "NXDN48");
|
||||
fprintf (stderr,"Setting symbol rate to 2400 / second\n");
|
||||
fprintf (stderr,"Decoding only NXDN 4800 baud frames.\n");
|
||||
#ifdef NXDN_OLD_SYNC
|
||||
//
|
||||
#else
|
||||
fprintf(stderr, "Notice: NXDN autodetect polarity disabled. \n Use -xn option if Inverted Signal expected.\n");
|
||||
#endif
|
||||
}
|
||||
else if (optarg[0] == 'y')
|
||||
{
|
||||
@@ -2892,6 +2915,7 @@
|
||||
// opts.setmod_bw = 12000; //causing issues
|
||||
sprintf (opts.output_name, "NXDN96");
|
||||
fprintf (stderr,"Decoding only NXDN 9600 baud frames.\n");
|
||||
fprintf(stderr, "Notice: NXDN autodetect polarity disabled. \n Use -xn option if Inverted Signal expected.\n");
|
||||
}
|
||||
else if (optarg[0] == 'r')
|
||||
{
|
||||
@@ -3077,6 +3101,15 @@
|
||||
opts.inverted_dpmr = 1;
|
||||
fprintf (stderr, "Expecting inverted ICOM dPMR signals.\n");
|
||||
}
|
||||
else if (optarg[0] == 'n')
|
||||
{
|
||||
opts.inverted_nxdn = 1;
|
||||
#ifdef NXDN_OLD_SYNC
|
||||
//
|
||||
#else
|
||||
fprintf (stderr, "Expecting inverted NXDN/DCR/IDAS signals.\n");
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
@@ -3088,7 +3121,6 @@
|
||||
opts.dmr_stereo = 0;
|
||||
state.dmr_stereo = 0;
|
||||
sprintf (opts.output_name, "MBE Playback");
|
||||
state.optind = optind;
|
||||
break;
|
||||
case 'l':
|
||||
opts.use_cosine_filter = 0;
|
||||
@@ -3098,6 +3130,10 @@
|
||||
exit (0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set optind after getopt completes so -r works regardless of argument order
|
||||
if (opts.playfiles == 1)
|
||||
state.optind = optind;
|
||||
|
||||
if (opts.resume > 0)
|
||||
{
|
||||
@@ -3532,5 +3568,5 @@
|
||||
return (0);
|
||||
}
|
||||
|
||||
//Tag: 2025
|
||||
//Tag: 2026
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ void playMbeFiles (dsd_opts * opts, dsd_state * state, int argc, char **argv)
|
||||
//static wav file only, handled by playSynthesizedVoiceMS
|
||||
//NOTE: if using -o null, playSynthesizedVoiceMS will not write to static wav file
|
||||
//Per call will work, but will end up with a single file with no meta info
|
||||
if (opts->wav_out_f != NULL && opts->dmr_stereo_wav == 1)
|
||||
if (opts->wav_out_f != NULL && (opts->dmr_stereo_wav == 1 || opts->static_wav_file == 1))
|
||||
{
|
||||
writeSynthesizedVoice (opts, state);
|
||||
}
|
||||
@@ -141,7 +141,7 @@ void playMbeFiles (dsd_opts * opts, dsd_state * state, int argc, char **argv)
|
||||
//static wav file only, handled by playSynthesizedVoiceMS
|
||||
//NOTE: if using -o null, playSynthesizedVoiceMS will not write to static wav file
|
||||
//Per call will work, but will end up with a single file with no meta info
|
||||
if (opts->wav_out_f != NULL && opts->dmr_stereo_wav == 1)
|
||||
if (opts->wav_out_f != NULL && (opts->dmr_stereo_wav == 1 || opts->static_wav_file == 1))
|
||||
{
|
||||
writeSynthesizedVoice (opts, state);
|
||||
}
|
||||
@@ -535,7 +535,7 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a
|
||||
state->bit_counterL = 0;
|
||||
|
||||
//unpack the octets into a bit-wise keystream
|
||||
unpack_byte_array_into_bit_array(state->ks_octetL+8, state->ks_bitstreamL, 15*8);
|
||||
unpack_byte_array_into_bit_array(state->ks_octetL+16, state->ks_bitstreamL, 14*16);
|
||||
|
||||
//reset flag to 0
|
||||
state->nxdn_new_iv = 0;
|
||||
@@ -546,7 +546,7 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a
|
||||
if (state->bit_counterL > (1568-49))
|
||||
state->bit_counterL = (1568-49);
|
||||
|
||||
//Keystream creation is currently inside of the NXDN_decode_VCALL_IV function
|
||||
//Keystream Application (no +7 discard like DMRA)
|
||||
for (i = 0; i < 49; i++)
|
||||
ambe_d[i] ^= state->ks_bitstreamL[state->bit_counterL++];
|
||||
|
||||
@@ -674,10 +674,15 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a
|
||||
}
|
||||
|
||||
pos = state->DMRvcL * 49;
|
||||
for(i = 0; i < 49; i++)
|
||||
if (memcmp(ambe_d, ambe_silence, 49) == 0)
|
||||
pos += 49;
|
||||
else
|
||||
{
|
||||
ambe_d[i] ^= pN[pos];
|
||||
pos++;
|
||||
for(i = 0; i < 49; i++)
|
||||
{
|
||||
ambe_d[i] ^= pN[pos];
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
state->DMRvcL++;
|
||||
}
|
||||
@@ -1136,10 +1141,15 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a
|
||||
}
|
||||
|
||||
pos = state->DMRvcR * 49;
|
||||
for(i = 0; i < 49; i++)
|
||||
if (memcmp(ambe_d, ambe_silence, 49) == 0)
|
||||
pos += 49;
|
||||
else
|
||||
{
|
||||
ambe_d[i] ^= pN[pos];
|
||||
pos++;
|
||||
for(i = 0; i < 49; i++)
|
||||
{
|
||||
ambe_d[i] ^= pN[pos];
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
state->DMRvcR++;
|
||||
}
|
||||
|
||||
446
src/dsd_misc.c
446
src/dsd_misc.c
@@ -8,322 +8,6 @@
|
||||
|
||||
#include "dsd.h"
|
||||
|
||||
static const int PARITY[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1};
|
||||
|
||||
// trellis_1_2 encode: source is in bits, result in bits
|
||||
void trellis_encode(uint8_t result[], const uint8_t source[], int result_len, int reg)
|
||||
{
|
||||
for (int i=0; i<result_len; i+=2) {
|
||||
reg = (reg << 1) | source[i>>1];
|
||||
result[i] = PARITY[reg & 0x19];
|
||||
result[i+1] = PARITY[reg & 0x17];
|
||||
}
|
||||
}
|
||||
|
||||
// simplified trellis 2:1 decode; source and result in bits
|
||||
// assumes that encoding was done with NTEST trailing zero bits
|
||||
// result_len should be set to the actual number of data bits
|
||||
// in the original unencoded message (excl. these trailing bits)
|
||||
void trellis_decode(uint8_t result[], const uint8_t source[], int result_len)
|
||||
{
|
||||
int reg = 0;
|
||||
int min_d;
|
||||
int min_bt;
|
||||
static const int NTEST = 4;
|
||||
static const int NTESTC = 1 << NTEST;
|
||||
uint8_t bt[NTEST];
|
||||
uint8_t tt[NTEST*2];
|
||||
int dstats[4];
|
||||
int sum;
|
||||
for (int p=0; p < 4; p++)
|
||||
dstats[p] = 0;
|
||||
for (int p=0; p < result_len; p++) {
|
||||
for (int i=0; i<NTESTC; i++) {
|
||||
bt[0] = (i&8)>>3;
|
||||
bt[1] = (i&4)>>2;
|
||||
bt[2] = (i&2)>>1;
|
||||
bt[3] = (i&1);
|
||||
trellis_encode(tt, bt, NTEST*2, reg);
|
||||
sum=0;
|
||||
for (int j=0; j<NTEST*2; j++) {
|
||||
sum += tt[j] ^ source[p*2+j];
|
||||
}
|
||||
if (i == 0 || sum < min_d) {
|
||||
min_d = sum;
|
||||
min_bt = bt[0];
|
||||
}
|
||||
}
|
||||
result[p] = min_bt;
|
||||
reg = (reg << 1) | min_bt;
|
||||
dstats[(min_d > 3) ? 3 : min_d] += 1;
|
||||
}
|
||||
|
||||
//debug output
|
||||
// fprintf (stderr, "\n stats\t%d %d %d %d\n", dstats[0], dstats[1], dstats[2], dstats[3]);
|
||||
}
|
||||
|
||||
//Original Copyright/License
|
||||
|
||||
/* -*- c++ -*- */
|
||||
/*
|
||||
* NXDN Encoder/Decoder (C) Copyright 2019 Max H. Parke KA1RBI
|
||||
*
|
||||
* This file is part of OP25
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
//Ripped from libM17
|
||||
#define K 5 //constraint length
|
||||
#define NUM_STATES (1 << (K - 1)) //number of states
|
||||
|
||||
static uint32_t prevMetrics[NUM_STATES];
|
||||
static uint32_t currMetrics[NUM_STATES];
|
||||
static uint32_t prevMetricsData[NUM_STATES];
|
||||
static uint32_t currMetricsData[NUM_STATES];
|
||||
static uint16_t viterbi_history[244];
|
||||
|
||||
/**
|
||||
* @brief Decode unpunctured convolutionally encoded data.
|
||||
*
|
||||
* @param out Destination array where decoded data is written.
|
||||
* @param in Input data.
|
||||
* @param len Input length in bits.
|
||||
* @return Number of bit errors corrected.
|
||||
*/
|
||||
uint32_t viterbi_decode(uint8_t* out, const uint16_t* in, const uint16_t len)
|
||||
{
|
||||
if(len > 244*2)
|
||||
fprintf(stderr, "Input size exceeds max history\n");
|
||||
|
||||
viterbi_reset();
|
||||
|
||||
size_t pos = 0;
|
||||
for(size_t i = 0; i < len; i += 2)
|
||||
{
|
||||
uint16_t s0 = in[i];
|
||||
uint16_t s1 = in[i + 1];
|
||||
|
||||
viterbi_decode_bit(s0, s1, pos);
|
||||
pos++;
|
||||
}
|
||||
uint32_t err = viterbi_chainback(out, pos, len/2);
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, "\n vcb: \n");
|
||||
// for (size_t i = 0; i < len; i++)
|
||||
// fprintf (stderr, "%02X", out[i]);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode punctured convolutionally encoded data.
|
||||
*
|
||||
* @param out Destination array where decoded data is written.
|
||||
* @param in Input data.
|
||||
* @param punct Puncturing matrix.
|
||||
* @param in_len Input data length.
|
||||
* @param p_len Puncturing matrix length (entries).
|
||||
* @return Number of bit errors corrected.
|
||||
*/
|
||||
uint32_t viterbi_decode_punctured(uint8_t* out, const uint16_t* in, const uint8_t* punct, const uint16_t in_len, const uint16_t p_len)
|
||||
{
|
||||
if(in_len > 244*2)
|
||||
fprintf(stderr, "Input size exceeds max history\n");
|
||||
|
||||
uint16_t umsg[244*2]; //unpunctured message
|
||||
uint8_t p=0; //puncturer matrix entry
|
||||
uint16_t u=0; //bits count - unpunctured message
|
||||
uint16_t i=0; //bits read from the input message
|
||||
|
||||
while(i<in_len)
|
||||
{
|
||||
if(punct[p])
|
||||
{
|
||||
umsg[u]=in[i];
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
umsg[u]=0x7FFF;
|
||||
}
|
||||
|
||||
u++;
|
||||
p++;
|
||||
p%=p_len;
|
||||
}
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, " p: %d, u: %d; p_len: %d; len: %d;", p, u, p_len, (u-in_len)*0x7FFF);
|
||||
|
||||
return viterbi_decode(out, umsg, u) - (u-in_len)*0x7FFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode one bit and update trellis.
|
||||
*
|
||||
* @param s0 Cost of the first symbol.
|
||||
* @param s1 Cost of the second symbol.
|
||||
* @param pos Bit position in history.
|
||||
*/
|
||||
void viterbi_decode_bit(uint16_t s0, uint16_t s1, const size_t pos)
|
||||
{
|
||||
static const uint16_t COST_TABLE_0[] = {0, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
static const uint16_t COST_TABLE_1[] = {0, 0xFFFF, 0xFFFF, 0, 0, 0xFFFF, 0xFFFF, 0};
|
||||
|
||||
for(uint8_t i = 0; i < NUM_STATES/2; i++)
|
||||
{
|
||||
uint32_t metric = q_abs_diff(COST_TABLE_0[i], s0)
|
||||
+ q_abs_diff(COST_TABLE_1[i], s1);
|
||||
|
||||
|
||||
uint32_t m0 = prevMetrics[i] + metric;
|
||||
uint32_t m1 = prevMetrics[i + NUM_STATES/2] + (0x1FFFE - metric);
|
||||
|
||||
uint32_t m2 = prevMetrics[i] + (0x1FFFE - metric);
|
||||
uint32_t m3 = prevMetrics[i + NUM_STATES/2] + metric;
|
||||
|
||||
uint8_t i0 = 2 * i;
|
||||
uint8_t i1 = i0 + 1;
|
||||
|
||||
if(m0 >= m1)
|
||||
{
|
||||
viterbi_history[pos]|=(1<<i0);
|
||||
currMetrics[i0] = m1;
|
||||
}
|
||||
else
|
||||
{
|
||||
viterbi_history[pos]&=~(1<<i0);
|
||||
currMetrics[i0] = m0;
|
||||
}
|
||||
|
||||
if(m2 >= m3)
|
||||
{
|
||||
viterbi_history[pos]|=(1<<i1);
|
||||
currMetrics[i1] = m3;
|
||||
}
|
||||
else
|
||||
{
|
||||
viterbi_history[pos]&=~(1<<i1);
|
||||
currMetrics[i1] = m2;
|
||||
}
|
||||
}
|
||||
|
||||
//swap
|
||||
uint32_t tmp[NUM_STATES];
|
||||
for(uint8_t i=0; i<NUM_STATES; i++)
|
||||
{
|
||||
tmp[i]=currMetrics[i];
|
||||
}
|
||||
for(uint8_t i=0; i<NUM_STATES; i++)
|
||||
{
|
||||
currMetrics[i]=prevMetrics[i];
|
||||
prevMetrics[i]=tmp[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief History chainback to obtain final byte array.
|
||||
*
|
||||
* @param out Destination byte array for decoded data.
|
||||
* @param pos Starting position for the chainback.
|
||||
* @param len Length of the output in bits.
|
||||
* @return Minimum Viterbi cost at the end of the decode sequence.
|
||||
*/
|
||||
uint32_t viterbi_chainback(uint8_t* out, size_t pos, uint16_t len)
|
||||
{
|
||||
uint8_t state = 0;
|
||||
size_t bitPos = len+4;
|
||||
|
||||
memset(out, 0, (len-1)/8+1);
|
||||
|
||||
while(pos > 0)
|
||||
{
|
||||
bitPos--;
|
||||
pos--;
|
||||
uint16_t bit = viterbi_history[pos]&((1<<(state>>4)));
|
||||
state >>= 1;
|
||||
if(bit)
|
||||
{
|
||||
state |= 0x80;
|
||||
out[bitPos/8]|=1<<(7-(bitPos%8));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cost = prevMetrics[0];
|
||||
|
||||
for(size_t i = 0; i < NUM_STATES; i++)
|
||||
{
|
||||
uint32_t m = prevMetrics[i];
|
||||
if(m < cost) cost = m;
|
||||
}
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, "\n cb: \n");
|
||||
// for (size_t i = 0; i < len; i++)
|
||||
// fprintf (stderr, "%02X", out[i]);
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the decoder state. No args.
|
||||
*
|
||||
*/
|
||||
void viterbi_reset(void)
|
||||
{
|
||||
memset((uint8_t*)viterbi_history, 0, 2*244);
|
||||
memset((uint8_t*)currMetrics, 0, 4*NUM_STATES);
|
||||
memset((uint8_t*)prevMetrics, 0, 4*NUM_STATES);
|
||||
memset((uint8_t*)currMetricsData, 0, 4*NUM_STATES);
|
||||
memset((uint8_t*)prevMetricsData, 0, 4*NUM_STATES);
|
||||
}
|
||||
|
||||
uint16_t q_abs_diff(const uint16_t v1, const uint16_t v2)
|
||||
{
|
||||
if(v2 > v1) return v2 - v1;
|
||||
return v1 - v2;
|
||||
}
|
||||
|
||||
//original sources
|
||||
//--------------------------------------------------------------------
|
||||
// M17 C library - decode/viterbi.c
|
||||
//
|
||||
// This file contains:
|
||||
// - the Viterbi decoder
|
||||
//
|
||||
// Wojciech Kaczmarski, SP5WWP
|
||||
// M17 Project, 29 December 2023
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// M17 C library - math/math.c
|
||||
//
|
||||
// This file contains:
|
||||
// - absolute difference value
|
||||
// - Euclidean norm (L2) calculation for n-dimensional vectors (float)
|
||||
// - soft-valued arrays to integer conversion (and vice-versa)
|
||||
// - fixed-valued multiplication and division
|
||||
//
|
||||
// Wojciech Kaczmarski, SP5WWP
|
||||
// M17 Project, 29 December 2023
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
|
||||
//audio filter stuff sourced from: https://github.com/NedSimao/FilteringLibrary
|
||||
//no license / information provided in source code
|
||||
#define PI 3.141592653
|
||||
@@ -337,7 +21,6 @@ uint16_t q_abs_diff(const uint16_t v1, const uint16_t v2)
|
||||
// #define HFC 50
|
||||
// #define LFC 100
|
||||
|
||||
|
||||
void LPFilter_Init(LPFilter *filter, float cutoffFreqHz, float sampleTimeS)
|
||||
{
|
||||
|
||||
@@ -351,7 +34,6 @@ void LPFilter_Init(LPFilter *filter, float cutoffFreqHz, float sampleTimeS)
|
||||
|
||||
}
|
||||
|
||||
|
||||
float LPFilter_Update(LPFilter *filter, float v_in)
|
||||
{
|
||||
|
||||
@@ -382,7 +64,6 @@ void HPFilter_Init(HPFilter *filter, float cutoffFreqHz, float sampleTimeS)
|
||||
|
||||
}
|
||||
|
||||
|
||||
float HPFilter_Update(HPFilter *filter, float v_in)
|
||||
{
|
||||
|
||||
@@ -592,130 +273,3 @@ long int raw_rms(int16_t *samples, int len, int step) //use samplespersymbol as
|
||||
if (rms < 0) rms = 150;
|
||||
return rms;
|
||||
}
|
||||
|
||||
//same as symbol levels in m17.h (m17-fme version)
|
||||
const int8_t symbol_list[4]={-3, -1, +1, +3};
|
||||
|
||||
/**
|
||||
* @brief Slice payload symbols into soft dibits.
|
||||
* Input (RRC filtered baseband sampled at symbol centers)
|
||||
* should be already normalized to {-3, -1, +1 +3}.
|
||||
* @param out Soft valued dibits (type-4).
|
||||
* @param inp Array of 184 floats (1 sample per symbol).
|
||||
*/
|
||||
void slice_symbols(uint16_t out[2*SYM_PER_PLD], const float inp[SYM_PER_PLD])
|
||||
{
|
||||
|
||||
for(uint8_t i=0; i<SYM_PER_PLD; i++)
|
||||
{
|
||||
//bit 0
|
||||
if(inp[i]>=symbol_list[3])
|
||||
{
|
||||
out[i*2+1]=0xFFFF;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[2])
|
||||
{
|
||||
out[i*2+1]=-(float)0xFFFF/(symbol_list[3]-symbol_list[2])*symbol_list[2]+inp[i]*(float)0xFFFF/(symbol_list[3]-symbol_list[2]);
|
||||
}
|
||||
else if(inp[i]>=symbol_list[1])
|
||||
{
|
||||
out[i*2+1]=0x0000;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[0])
|
||||
{
|
||||
out[i*2+1]=(float)0xFFFF/(symbol_list[1]-symbol_list[0])*symbol_list[1]-inp[i]*(float)0xFFFF/(symbol_list[1]-symbol_list[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
out[i*2+1]=0xFFFF;
|
||||
}
|
||||
|
||||
//bit 1
|
||||
if(inp[i]>=symbol_list[2])
|
||||
{
|
||||
out[i*2]=0x0000;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[1])
|
||||
{
|
||||
out[i*2]=0x7FFF-inp[i]*(float)0xFFFF/(symbol_list[2]-symbol_list[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
out[i*2]=0xFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Soft logic NOT.
|
||||
*
|
||||
* @param a Input A.
|
||||
* @return uint16_t Output = not A.
|
||||
*/
|
||||
uint16_t soft_bit_NOT(const uint16_t a)
|
||||
{
|
||||
return 0xFFFFU-a;
|
||||
}
|
||||
|
||||
//randomizing pattern
|
||||
const uint8_t rand_seq[46]=
|
||||
{
|
||||
0xD6, 0xB5, 0xE2, 0x30, 0x82, 0xFF, 0x84, 0x62, 0xBA, 0x4E, 0x96, 0x90, 0xD8, 0x98, 0xDD, 0x5D, 0x0C, 0xC8, 0x52, 0x43, 0x91, 0x1D, 0xF8,
|
||||
0x6E, 0x68, 0x2F, 0x35, 0xDA, 0x14, 0xEA, 0xCD, 0x76, 0x19, 0x8D, 0xD5, 0x80, 0xD1, 0x33, 0x87, 0x13, 0x57, 0x18, 0x2D, 0x29, 0x78, 0xC3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Randomize type-4 soft bits.
|
||||
*
|
||||
* @param inp Input 368 soft type-4 bits.
|
||||
*/
|
||||
void randomize_soft_bits(uint16_t inp[SYM_PER_PLD*2])
|
||||
{
|
||||
for(uint16_t i=0; i<SYM_PER_PLD*2; i++)
|
||||
{
|
||||
if((rand_seq[i/8]>>(7-(i%8)))&1) //flip bit if '1'
|
||||
{
|
||||
inp[i]=soft_bit_NOT(inp[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//interleaver pattern
|
||||
const uint16_t intrl_seq[SYM_PER_PLD*2]=
|
||||
{
|
||||
0, 137, 90, 227, 180, 317, 270, 39, 360, 129, 82, 219, 172, 309, 262, 31,
|
||||
352, 121, 74, 211, 164, 301, 254, 23, 344, 113, 66, 203, 156, 293, 246, 15,
|
||||
336, 105, 58, 195, 148, 285, 238, 7, 328, 97, 50, 187, 140, 277, 230, 367,
|
||||
320, 89, 42, 179, 132, 269, 222, 359, 312, 81, 34, 171, 124, 261, 214, 351,
|
||||
304, 73, 26, 163, 116, 253, 206, 343, 296, 65, 18, 155, 108, 245, 198, 335,
|
||||
288, 57, 10, 147, 100, 237, 190, 327, 280, 49, 2, 139, 92, 229, 182, 319,
|
||||
272, 41, 362, 131, 84, 221, 174, 311, 264, 33, 354, 123, 76, 213, 166, 303,
|
||||
256, 25, 346, 115, 68, 205, 158, 295, 248, 17, 338, 107, 60, 197, 150, 287,
|
||||
240, 9, 330, 99, 52, 189, 142, 279, 232, 1, 322, 91, 44, 181, 134, 271,
|
||||
224, 361, 314, 83, 36, 173, 126, 263, 216, 353, 306, 75, 28, 165, 118, 255,
|
||||
208, 345, 298, 67, 20, 157, 110, 247, 200, 337, 290, 59, 12, 149, 102, 239,
|
||||
192, 329, 282, 51, 4, 141, 94, 231, 184, 321, 274, 43, 364, 133, 86, 223,
|
||||
176, 313, 266, 35, 356, 125, 78, 215, 168, 305, 258, 27, 348, 117, 70, 207,
|
||||
160, 297, 250, 19, 340, 109, 62, 199, 152, 289, 242, 11, 332, 101, 54, 191,
|
||||
144, 281, 234, 3, 324, 93, 46, 183, 136, 273, 226, 363, 316, 85, 38, 175,
|
||||
128, 265, 218, 355, 308, 77, 30, 167, 120, 257, 210, 347, 300, 69, 22, 159,
|
||||
112, 249, 202, 339, 292, 61, 14, 151, 104, 241, 194, 331, 284, 53, 6, 143,
|
||||
96, 233, 186, 323, 276, 45, 366, 135, 88, 225, 178, 315, 268, 37, 358, 127,
|
||||
80, 217, 170, 307, 260, 29, 350, 119, 72, 209, 162, 299, 252, 21, 342, 111,
|
||||
64, 201, 154, 291, 244, 13, 334, 103, 56, 193, 146, 283, 236, 5, 326, 95,
|
||||
48, 185, 138, 275, 228, 365, 318, 87, 40, 177, 130, 267, 220, 357, 310, 79,
|
||||
32, 169, 122, 259, 212, 349, 302, 71, 24, 161, 114, 251, 204, 341, 294, 63,
|
||||
16, 153, 106, 243, 196, 333, 286, 55, 8, 145, 98, 235, 188, 325, 278, 47
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Reorder (interleave) 368 soft bits.
|
||||
*
|
||||
* @param outp Reordered soft bits.
|
||||
* @param inp Input soft bits.
|
||||
*/
|
||||
void reorder_soft_bits(uint16_t outp[SYM_PER_PLD*2], const uint16_t inp[SYM_PER_PLD*2])
|
||||
{
|
||||
for(uint16_t i=0; i<SYM_PER_PLD*2; i++)
|
||||
outp[i]=inp[intrl_seq[i]];
|
||||
}
|
||||
|
||||
@@ -230,6 +230,7 @@ uint8_t ncurses_input_handler(dsd_opts * opts, dsd_state * state, int c)
|
||||
opts->inverted_dpmr = 1;
|
||||
opts->inverted_x2tdma = 1;
|
||||
opts->inverted_ysf = 1;
|
||||
opts->inverted_nxdn = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -237,6 +238,7 @@ uint8_t ncurses_input_handler(dsd_opts * opts, dsd_state * state, int c)
|
||||
opts->inverted_dpmr = 0;
|
||||
opts->inverted_x2tdma = 0;
|
||||
opts->inverted_ysf = 0;
|
||||
opts->inverted_nxdn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,8 +379,8 @@ uint8_t ncurses_input_handler(dsd_opts * opts, dsd_state * state, int c)
|
||||
if (c == 112) //'p' key - stop all per call wav files //TODO: Fix
|
||||
{
|
||||
//TODO: Add Closing of RAW files as well?
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_fR = close_and_rename_wav_file(opts->wav_out_fR, opts->wav_out_fileR, opts->wav_out_dir, &state->event_history_s[1]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_fR = close_and_rename_wav_file(opts->wav_out_fR, opts->wav_out_fileR, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[1]);
|
||||
opts->wav_out_file[0] = 0; //Bugfix for decoded wav file display after disabling
|
||||
opts->wav_out_fileR[0] = 0;
|
||||
opts->dmr_stereo_wav = 0;
|
||||
@@ -592,7 +594,7 @@ uint8_t ncurses_input_handler(dsd_opts * opts, dsd_state * state, int c)
|
||||
else opts->trunk_tune_enc_calls = 1;
|
||||
}
|
||||
|
||||
if (opts->p25_trunk == 1 && c == 103) //'g' key, toggle tune group calls
|
||||
if (opts->p25_trunk == 1 && c == 84) //'T' key, toggle tune group calls
|
||||
{
|
||||
if (opts->trunk_tune_group_calls == 1) opts->trunk_tune_group_calls = 0;
|
||||
else opts->trunk_tune_group_calls = 1;
|
||||
@@ -916,6 +918,26 @@ uint8_t ncurses_input_handler(dsd_opts * opts, dsd_state * state, int c)
|
||||
if (c == 123)
|
||||
opts->rtlsdr_ppm_error--;
|
||||
|
||||
//RTL Gain Manual Adjustment
|
||||
if (c == 103)
|
||||
{
|
||||
if (opts->rtl_gain_value > 0)
|
||||
{
|
||||
opts->rtl_gain_value--;
|
||||
opts->rtl_gain_actual = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == 71)
|
||||
{
|
||||
if (opts->rtl_gain_value < 49)
|
||||
{
|
||||
opts->rtl_gain_value++;
|
||||
opts->rtl_gain_actual = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//anything with an entry box will need the inputs and outputs stopped first
|
||||
//so probably just write a function to handle c input, and when c = certain values
|
||||
//needing an entry box, then stop all of those
|
||||
|
||||
@@ -694,8 +694,8 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state)
|
||||
if (choicec == 12)
|
||||
{
|
||||
//TODO: Add Closing of RAW files as well?
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_fR = close_and_rename_wav_file(opts->wav_out_fR, opts->wav_out_fileR, opts->wav_out_dir, &state->event_history_s[1]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_fR = close_and_rename_wav_file(opts->wav_out_fR, opts->wav_out_fileR, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[1]);
|
||||
opts->wav_out_file[0] = 0; //Bugfix for decoded wav file display after disabling
|
||||
opts->wav_out_fileR[0] = 0;
|
||||
opts->dmr_stereo_wav = 0;
|
||||
|
||||
@@ -245,10 +245,10 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state)
|
||||
if (opts->audio_in_type == 3)
|
||||
{
|
||||
printw ("| RTL: %d;", opts->rtl_dev_index);
|
||||
if (opts->rtl_gain_value == 0)
|
||||
if (opts->rtl_gain_actual == -100)
|
||||
printw (" G: AGC;");
|
||||
else
|
||||
printw (" G: %idB;", opts->rtl_gain_value);
|
||||
printw (" G: %i/%i;", opts->rtl_gain_value, opts->rtl_gain_actual);
|
||||
printw (" V: %iX;", opts->rtl_volume_multiplier);
|
||||
printw (" PPM: %i;", opts->rtlsdr_ppm_error); //Adjust manually now with { and }
|
||||
printw (" SQ: %i;", opts->rtl_squelch_level);
|
||||
@@ -363,7 +363,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state)
|
||||
{
|
||||
printw ("| Trunking -");
|
||||
if (opts->trunk_tune_group_calls == 0) attron(COLOR_PAIR(2));
|
||||
printw (" Group(g)");
|
||||
printw (" Group(T)");
|
||||
attron(COLOR_PAIR(4));
|
||||
if (opts->trunk_tune_private_calls == 0) attron(COLOR_PAIR(2));
|
||||
printw (" Private(u)");
|
||||
@@ -382,8 +382,8 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state)
|
||||
if (opts->p25_trunk == 1 && (opts->use_rigctl == 1 || opts->audio_in_type == 3) )
|
||||
{
|
||||
printw ("| Trunking -");
|
||||
if (opts->trunk_tune_group_calls == 0) printw (" group(g)");
|
||||
else printw (" GROUP(g)");
|
||||
if (opts->trunk_tune_group_calls == 0) printw (" group(T)");
|
||||
else printw (" GROUP(T)");
|
||||
if (opts->trunk_tune_private_calls == 0) printw (" private(u)");
|
||||
else printw (" PRIVATE(u)");
|
||||
if (opts->trunk_tune_data_calls == 0) printw (" data(d)");
|
||||
|
||||
@@ -1098,7 +1098,7 @@ void edacs(dsd_opts * opts, dsd_state * state)
|
||||
if (is_digital == 1) {} //just write to already open temp file to be renamed later
|
||||
else //close the temp 8k wav file and open as 48k
|
||||
{
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 48000, 0);
|
||||
}
|
||||
}
|
||||
@@ -1214,7 +1214,7 @@ void edacs(dsd_opts * opts, dsd_state * state)
|
||||
if (is_digital == 1) {} //just write to already open temp file to be renamed later
|
||||
else //close the temp 8k wav file and open as 48k
|
||||
{
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 48000, 0);
|
||||
}
|
||||
}
|
||||
@@ -1321,7 +1321,7 @@ void edacs(dsd_opts * opts, dsd_state * state)
|
||||
if (is_digital == 1) {} //just write to already open temp file to be renamed later
|
||||
else //close the temp 8k wav file and open as 48k
|
||||
{
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 48000, 0);
|
||||
}
|
||||
}
|
||||
@@ -1501,7 +1501,7 @@ void edacs(dsd_opts * opts, dsd_state * state)
|
||||
if (is_digital == 1) {} //just write to already open temp file to be renamed later
|
||||
else //close the temp 8k wav file and open as 48k
|
||||
{
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 48000, 0);
|
||||
}
|
||||
}
|
||||
@@ -1757,7 +1757,7 @@ void edacs(dsd_opts * opts, dsd_state * state)
|
||||
if (is_digital == 1) {} //just write to already open temp file to be renamed later
|
||||
else //close the temp 8k wav file and open as 48k
|
||||
{
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 48000, 0);
|
||||
}
|
||||
}
|
||||
@@ -1870,7 +1870,7 @@ void edacs(dsd_opts * opts, dsd_state * state)
|
||||
if (is_digital == 1) {} //just write to already open temp file to be renamed later
|
||||
else //close the temp 8k wav file and open as 48k
|
||||
{
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 48000, 0);
|
||||
}
|
||||
}
|
||||
@@ -2166,7 +2166,7 @@ void edacs(dsd_opts * opts, dsd_state * state)
|
||||
if (is_digital == 1) {} //just write to already open temp file to be renamed later
|
||||
else //close the temp 8k wav file and open as 48k
|
||||
{
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 48000, 0);
|
||||
}
|
||||
}
|
||||
@@ -2312,7 +2312,7 @@ void eot_cc(dsd_opts * opts, dsd_state * state)
|
||||
if (opts->dmr_stereo_wav == 1)
|
||||
{
|
||||
if (opts->wav_out_f != NULL)
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, &state->event_history_s[0]);
|
||||
opts->wav_out_f = close_and_rename_wav_file(opts->wav_out_f, opts->wav_out_file, opts->wav_out_dir, opts->wav_custom_tag, &state->event_history_s[0]);
|
||||
opts->wav_out_f = open_wav_file(opts->wav_out_dir, opts->wav_out_file, 8000, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1479,7 +1479,7 @@ bool QR_16_7_6_decode(unsigned char *rxBits)
|
||||
unsigned int syndromeI = 0; // syndrome index
|
||||
int is = 0;
|
||||
int i = 0;
|
||||
int corrections = 0;
|
||||
int corrections = 0; UNUSED(corrections);
|
||||
for (is = 0; is < 9; is++)
|
||||
{
|
||||
syndromeI += (((rxBits[0] * QR_16_7_6_m_H[16*is + 0])
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2016,2018 by Jonathan Naylor G4KLX
|
||||
*
|
||||
* Copyright (C) 2018 by Edouard Griffiths F4EXB:
|
||||
* - Cosmetic changes to integrate with DSDcc
|
||||
*
|
||||
* Copyright (C) 2018 by Louis HERVE F4HUZ:
|
||||
* - Transform C++ lib into C lib to integrate with DSD
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
|
||||
/* Include ------------------------------------------------------------------*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/* Define -------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/* Global variables ---------------------------------------------------------*/
|
||||
static const uint8_t CNXDNConvolution_BIT_MASK_TABLE[8] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U};
|
||||
|
||||
#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | CNXDNConvolution_BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~CNXDNConvolution_BIT_MASK_TABLE[(i)&7])
|
||||
#define READ_BIT1(p,i) (p[(i)>>3] & CNXDNConvolution_BIT_MASK_TABLE[(i)&7])
|
||||
|
||||
static const uint8_t CNXDNConvolution_BRANCH_TABLE1[8] = {0U, 0U, 0U, 0U, 2U, 2U, 2U, 2U};
|
||||
static const uint8_t CNXDNConvolution_BRANCH_TABLE2[8] = {0U, 2U, 2U, 0U, 0U, 2U, 2U, 0U};
|
||||
|
||||
static const unsigned int CNXDNConvolution_NUM_OF_STATES_D2 = 8U;
|
||||
static const uint32_t CNXDNConvolution_M = 4U;
|
||||
static const unsigned int CNXDNConvolution_K = 5U;
|
||||
|
||||
//NOTE:
|
||||
uint16_t m_metrics1[16];
|
||||
uint16_t m_metrics2[16];
|
||||
uint64_t m_decisions[8*300];
|
||||
uint16_t * m_oldMetrics = NULL;
|
||||
uint16_t * m_newMetrics = NULL;
|
||||
uint64_t * m_dp = NULL;
|
||||
|
||||
/* Functions ----------------------------------------------------------------*/
|
||||
|
||||
void CNXDNConvolution_decode(uint8_t s0, uint8_t s1)
|
||||
{
|
||||
uint8_t i = 0;
|
||||
uint8_t j = 0;
|
||||
uint8_t decision0 = 0;
|
||||
uint8_t decision1 = 0;
|
||||
uint16_t metric = 0;
|
||||
uint16_t m0 = 0;
|
||||
uint16_t m1 = 0;
|
||||
uint16_t * tmp = NULL;
|
||||
|
||||
*m_dp = 0U;
|
||||
|
||||
for (i = 0U; i < CNXDNConvolution_NUM_OF_STATES_D2; i++)
|
||||
{
|
||||
j = i * 2U;
|
||||
|
||||
metric = abs(CNXDNConvolution_BRANCH_TABLE1[i] - s0) + abs(CNXDNConvolution_BRANCH_TABLE2[i] - s1);
|
||||
|
||||
m0 = m_oldMetrics[i] + metric;
|
||||
m1 = m_oldMetrics[i + CNXDNConvolution_NUM_OF_STATES_D2] + (CNXDNConvolution_M - metric);
|
||||
decision0 = (m0 >= m1) ? 1U : 0U;
|
||||
m_newMetrics[j + 0U] = decision0 != 0U ? m1 : m0;
|
||||
|
||||
m0 = m_oldMetrics[i] + (CNXDNConvolution_M - metric);
|
||||
m1 = m_oldMetrics[i + CNXDNConvolution_NUM_OF_STATES_D2] + metric;
|
||||
decision1 = (m0 >= m1) ? 1U : 0U;
|
||||
m_newMetrics[j + 1U] = decision1 != 0U ? m1 : m0;
|
||||
|
||||
*m_dp |= ((uint64_t)(decision1) << (j + 1U)) | ((uint64_t)(decision0) << (j + 0U));
|
||||
}
|
||||
|
||||
++m_dp;
|
||||
|
||||
//assert((m_dp - m_decisions) <= 300);
|
||||
|
||||
tmp = m_oldMetrics;
|
||||
m_oldMetrics = m_newMetrics;
|
||||
m_newMetrics = tmp;
|
||||
}
|
||||
|
||||
void CNXDNConvolution_chainback(unsigned char* out, unsigned int nBits)
|
||||
{
|
||||
//assert(out != 0);
|
||||
|
||||
uint32_t state = 0U;
|
||||
uint32_t i = 0;
|
||||
uint8_t bit = 0;
|
||||
|
||||
while (nBits-- > 0)
|
||||
{
|
||||
--m_dp;
|
||||
|
||||
i = state >> (9 - CNXDNConvolution_K);
|
||||
bit = (uint8_t)(*m_dp >> i) & 1;
|
||||
state = (bit << 7) | (state >> 1);
|
||||
|
||||
WRITE_BIT1(out, nBits, bit != 0U);
|
||||
}
|
||||
}
|
||||
|
||||
void CNXDNConvolution_encode(const unsigned char* in, unsigned char* out, unsigned int nBits)
|
||||
{
|
||||
//assert(in != 0);
|
||||
//assert(out != 0);
|
||||
//assert(nBits > 0U);
|
||||
|
||||
uint8_t g1 = 0U;
|
||||
uint8_t g2 = 0U;
|
||||
uint8_t d = 0U;
|
||||
uint8_t d1 = 0U;
|
||||
uint8_t d2 = 0U;
|
||||
uint8_t d3 = 0U;
|
||||
uint8_t d4 = 0U;
|
||||
uint32_t k = 0U;
|
||||
unsigned int i = 0U;
|
||||
|
||||
for (i = 0U; i < nBits; i++)
|
||||
{
|
||||
d = READ_BIT1(in, i) ? 1U : 0U;
|
||||
|
||||
g1 = (d + d3 + d4) & 1;
|
||||
g2 = (d + d1 + d2 + d4) & 1;
|
||||
|
||||
d4 = d3;
|
||||
d3 = d2;
|
||||
d2 = d1;
|
||||
d1 = d;
|
||||
|
||||
WRITE_BIT1(out, k, g1 != 0U);
|
||||
k++;
|
||||
|
||||
WRITE_BIT1(out, k, g2 != 0U);
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
void CNXDNConvolution_start(void)
|
||||
{
|
||||
|
||||
m_oldMetrics = m_metrics1;
|
||||
m_newMetrics = m_metrics2;
|
||||
m_dp = m_decisions;
|
||||
|
||||
}
|
||||
|
||||
void CNXDNConvolution_init()
|
||||
{
|
||||
memset(m_metrics1, 0x0, 16*sizeof(uint16_t));
|
||||
memset(m_metrics2, 0x0, 16*sizeof(uint16_t));
|
||||
memset(m_decisions,0x0, 8*300*sizeof(uint64_t));
|
||||
}
|
||||
|
||||
2816
src/nxdn_deperm.c
2816
src/nxdn_deperm.c
File diff suppressed because it is too large
Load Diff
1175
src/nxdn_element.c
1175
src/nxdn_element.c
File diff suppressed because it is too large
Load Diff
436
src/nxdn_frame.c
436
src/nxdn_frame.c
@@ -1,40 +1,32 @@
|
||||
//NXDN frame handler
|
||||
//Reworked portions from Osmocom OP25 rx_sync.cc
|
||||
|
||||
/* -*- c++ -*- */
|
||||
/*
|
||||
/*-------------------------------------------------------------------------------
|
||||
* nxdn_frame.c
|
||||
* NXDN Frame Handling
|
||||
*
|
||||
* Reworked portions from Osmocom OP25 rx_sync.cc
|
||||
* NXDN Encoder/Decoder (C) Copyright 2019 Max H. Parke KA1RBI
|
||||
*
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
* LWVMOBILE
|
||||
* 2026-03 DSD-FME Florida Man Edition
|
||||
*-----------------------------------------------------------------------------*/
|
||||
|
||||
#include "dsd.h"
|
||||
#include "nxdn_const.h"
|
||||
|
||||
// #define NXDN_DEBUG_LICH //print LICH debug info on err on payload == 1
|
||||
#define NXDN_LICH_OFFBITS //use the offbits to help determine sync status (disable if bad signal / bad sample)
|
||||
#define LICH_ERR_THRESHOLD 7 //Threshold for lich dibit "dividing" errors (8 is perfect, 7 is 1 bit error)
|
||||
// #define NXDN_DEBUG_LICH //print LICH debug info on err on payload == 1
|
||||
// #define NXDN_LICH_OFFBITS //use the offbits to help determine sync status (disable if bad signal / bad sample)
|
||||
|
||||
//NXDN Type Enablement (handled by cmake -DTYPEC=OFF , -DTYPED=OFF, -DDCR=OFF)
|
||||
// #define JPNDCR_DECODE //Include Japanese DCR Decoding
|
||||
// #define TYPE_D_DECODE //Include Type-D Decoding
|
||||
// #define TYPE_C_DECODE //Include Type-C and Conventional Decoding (Basically the "normal" NXDN stuff)
|
||||
|
||||
void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
{
|
||||
// length is implicitly 192, with frame sync in first 10 dibits
|
||||
uint8_t dbuf[182];
|
||||
uint8_t lich;
|
||||
uint8_t answer[32];
|
||||
uint8_t sacch_answer[32];
|
||||
uint8_t lich = 0;
|
||||
int lich_parity_received;
|
||||
int lich_parity_computed;
|
||||
int voice = 0;
|
||||
@@ -71,12 +63,21 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
|
||||
//nxdn bit buffer, for easy assignment handling
|
||||
int nxdn_bit_buffer[364];
|
||||
int nxdn_dibit_buffer[182];
|
||||
|
||||
#ifdef NXDN_DEBUG_LICH
|
||||
/*
|
||||
//this is how many bad syncs with polarity flipping occurred when allowed both pos and inv polarity on sync test
|
||||
Sync: no sync
|
||||
[+] [+] [-] [+] [-] [+] [-] [-] [-] [+] [+] [-] [+] (+) 17:36:02 Sync: NXDN48 RDCH Voice RAN 01 PF X/4
|
||||
*/
|
||||
//debug polarity (purely illustative, shows how frequently false sync can be attributed to wrong polarity)
|
||||
if (state->lastsynctype == 28)
|
||||
fprintf (stderr, "[+] ");
|
||||
else fprintf (stderr, "[-] ");
|
||||
#endif
|
||||
|
||||
//init all arrays
|
||||
memset (dbuf, 0, sizeof(dbuf));
|
||||
memset (answer, 0, sizeof(answer));
|
||||
memset (sacch_answer, 0, sizeof(sacch_answer));
|
||||
memset (lich_dibits, 0, sizeof(lich_dibits));
|
||||
memset (sacch_bits, 0, sizeof(sacch_bits));
|
||||
memset (facch_bits_b, 0, sizeof(facch_bits_b));
|
||||
@@ -85,15 +86,18 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
memset (facch2_bits, 0, sizeof(facch2_bits));
|
||||
|
||||
memset (nxdn_bit_buffer, 0, sizeof(nxdn_bit_buffer));
|
||||
memset (nxdn_dibit_buffer, 0, sizeof(nxdn_dibit_buffer));
|
||||
|
||||
//collect lich bits first, if they are good, then we can collect the rest of them
|
||||
for (int i = 0; i < 8; i++) lich_dibits[i] = dbuf[i] = getDibit(opts, state);
|
||||
|
||||
nxdn_pn95_dibit_scrambler (state, lich_dibits, 8);
|
||||
|
||||
lich = 0;
|
||||
for (int i=0; i<8; i++) lich |= (lich_dibits[i] >> 1) << (7-i);
|
||||
//divide lich_dibits to get the lich code w/ parity bit on LSB
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
lich <<= 1;
|
||||
lich |= (lich_dibits[i] >> 1) & 1;
|
||||
}
|
||||
|
||||
//debug lich as a 16-bit value (with encoding "dividing")
|
||||
uint8_t lich_bits[16]; memset(lich_bits, 0, sizeof(lich_bits));
|
||||
@@ -107,18 +111,17 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
|
||||
//debug look at the "off bits" of the encoded lich, should be all 1's (8)
|
||||
//disble this code if sync issues arise, this may not be ideal of marginal signal
|
||||
uint8_t lich_off_hex = 0;
|
||||
uint8_t lich_off_hex = 0; UNUSED(lich_off_hex);
|
||||
for (int i=0; i<8; i++)
|
||||
lich_off_hex += lich_bits[(i*2)+1];
|
||||
#ifdef NXDN_LICH_OFFBITS
|
||||
if (lich_off_hex < 7) //allow up to 1 bit error
|
||||
if (lich_off_hex < LICH_ERR_THRESHOLD)
|
||||
{
|
||||
#ifdef NXDN_DEBUG_LICH
|
||||
if (opts->payload == 1)
|
||||
fprintf(stderr, " Lich Off Bit Fill Error: %d / 8; \n", lich_off_hex);
|
||||
#endif
|
||||
state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
|
||||
goto END;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -127,9 +130,11 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
lich_parity_computed = ((lich_full >> 7) + (lich_full >> 6) + (lich_full >> 5) + (lich_full >> 4)) & 1;
|
||||
lich = lich_full >> 1;
|
||||
|
||||
#ifdef JPNDCR_DECODE
|
||||
//special cases on DCR where parity is computed over 7 bits, and not 4 bits
|
||||
if (lich == 0x08 || lich == 0x4A || lich == 0x48 || lich == 0x46)
|
||||
lich_parity_computed = ((lich_full >> 7) + (lich_full >> 6) + (lich_full >> 5) + (lich_full >> 4) + (lich_full >> 3) + (lich_full >> 2) + (lich_full >> 1)) & 1;
|
||||
#endif
|
||||
|
||||
if (lich_parity_received != lich_parity_computed)
|
||||
{
|
||||
@@ -137,8 +142,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
if (opts->payload == 1)
|
||||
fprintf(stderr, " Lich Parity Error %02X / %04X\n", lich_full, lich_bits_hex);
|
||||
#endif
|
||||
state->lastsynctype = -1;
|
||||
goto END;
|
||||
state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
|
||||
}
|
||||
|
||||
voice = 0;
|
||||
@@ -156,154 +160,179 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
fprintf(stderr, " Simplex/Inbound NXDN lich on trunking system - type 0x%02X\n", lich);
|
||||
#endif
|
||||
state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
|
||||
goto END;
|
||||
}
|
||||
|
||||
switch(lich) { //cases without breaks continue to flow downwards until they hit the break
|
||||
case 0x01: // CAC types
|
||||
case 0x05:
|
||||
cac = 1;
|
||||
break;
|
||||
case 0x28: //facch2 types
|
||||
case 0x29:
|
||||
// case 0x48: //removing from here, moving to DCR as pich_tch
|
||||
case 0x49:
|
||||
facch2 = 1;
|
||||
break;
|
||||
case 0x2e: //udch types
|
||||
case 0x2f:
|
||||
case 0x4e:
|
||||
case 0x4f:
|
||||
udch = 1;
|
||||
break;
|
||||
case 0x32: //facch in 1, vch in 2
|
||||
case 0x33:
|
||||
case 0x52:
|
||||
case 0x53:
|
||||
voice = 2;
|
||||
facch = 1;
|
||||
sacch = 1;
|
||||
break;
|
||||
case 0x34: //vch in 1, facch in 2
|
||||
case 0x35:
|
||||
case 0x54:
|
||||
case 0x55: //disabled for testing, IDAS system randomly triggers this one, probably due to poor signal
|
||||
voice = 1;
|
||||
facch = 2;
|
||||
sacch = 1;
|
||||
break;
|
||||
case 0x36: //vch in both
|
||||
case 0x37:
|
||||
case 0x56:
|
||||
case 0x57:
|
||||
voice = 3;
|
||||
facch = 0;
|
||||
sacch = 1;
|
||||
break;
|
||||
case 0x20: //facch in both
|
||||
case 0x21:
|
||||
case 0x30:
|
||||
case 0x31:
|
||||
case 0x40:
|
||||
case 0x41:
|
||||
case 0x50:
|
||||
case 0x51:
|
||||
voice = 0;
|
||||
facch = 3;
|
||||
sacch = 1;
|
||||
break;
|
||||
case 0x38: //sacch only (NULL?)
|
||||
case 0x39:
|
||||
sacch = 1;
|
||||
break;
|
||||
switch(lich)
|
||||
{
|
||||
|
||||
//DCR Voice
|
||||
case 0x46:
|
||||
voice = 3;
|
||||
sacch2 = 1;
|
||||
break;
|
||||
#ifdef TYPE_C_DECODE
|
||||
case 0x01: // CAC types
|
||||
case 0x05:
|
||||
cac = 1;
|
||||
break;
|
||||
case 0x28: //facch2 types
|
||||
case 0x29:
|
||||
// case 0x48: //removing from here, moving to DCR as pich_tch
|
||||
case 0x49:
|
||||
facch2 = 1;
|
||||
break;
|
||||
case 0x2e: //udch types
|
||||
case 0x2f:
|
||||
case 0x4e:
|
||||
case 0x4f:
|
||||
udch = 1;
|
||||
break;
|
||||
case 0x32: //facch in 1, vch in 2
|
||||
case 0x33:
|
||||
case 0x52:
|
||||
case 0x53:
|
||||
voice = 2;
|
||||
facch = 1;
|
||||
sacch = 1;
|
||||
break;
|
||||
case 0x34: //vch in 1, facch in 2
|
||||
case 0x35:
|
||||
case 0x54:
|
||||
case 0x55:
|
||||
voice = 1;
|
||||
facch = 2;
|
||||
sacch = 1;
|
||||
break;
|
||||
case 0x36: //vch in both
|
||||
case 0x37:
|
||||
case 0x56:
|
||||
case 0x57:
|
||||
voice = 3;
|
||||
facch = 0;
|
||||
sacch = 1;
|
||||
break;
|
||||
case 0x20: //facch in both
|
||||
case 0x21:
|
||||
case 0x30:
|
||||
case 0x31:
|
||||
case 0x40:
|
||||
case 0x41:
|
||||
case 0x50:
|
||||
case 0x51:
|
||||
voice = 0;
|
||||
facch = 3;
|
||||
sacch = 1;
|
||||
break;
|
||||
case 0x38: //sacch only (NULL?)
|
||||
case 0x39:
|
||||
sacch = 1;
|
||||
break;
|
||||
#endif
|
||||
|
||||
//DCR SB0, Data or End Frame
|
||||
case 0x08: //SB0 w/ CSM 9 digit BCD found here
|
||||
sacch2 = 1;
|
||||
pich_tch = 1; //observed 2nd PICH is zero fill
|
||||
break;
|
||||
case 0x48:
|
||||
pich_tch = 3; //may be 1, or 2 TCH (observed possibly both)
|
||||
case 0x4A:
|
||||
sacch2 = 1;
|
||||
break;
|
||||
#ifdef JPNDCR_DECODE
|
||||
//DCR Voice
|
||||
case 0x46:
|
||||
voice = 3;
|
||||
sacch2 = 1;
|
||||
break;
|
||||
|
||||
//NXDN "Type-D" or "IDAS" Specific Lich Codes
|
||||
case 0x76: //normal vch voice (in one and two)
|
||||
case 0x77:
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
voice = 3;
|
||||
break;
|
||||
// case 0x74: //False Positive on DCR, keep disabled, or revert this line
|
||||
case 0x75: //vch in 1, facch1 in 2 (facch 2 steal)
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
voice = 1;
|
||||
facch = 2;
|
||||
break;
|
||||
case 0x72: //facch in 1, vch in 2 (facch 1 steal)
|
||||
case 0x73:
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
voice = 2;
|
||||
facch = 1;
|
||||
break;
|
||||
case 0x70: //facch steal in vch 1 and vch 2 (during voice only)
|
||||
case 0x71:
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
facch = 3;
|
||||
break;
|
||||
case 0x6E: //udch2
|
||||
case 0x6F:
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
udch2 = 1;
|
||||
break;
|
||||
case 0x68:
|
||||
case 0x69: //facch3
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
facch3 = 1;
|
||||
break;
|
||||
case 0x62:
|
||||
case 0x63: //facch1 in 1, null data and post field in 2
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
facch = 1;
|
||||
break;
|
||||
case 0x60:
|
||||
case 0x61: //facch1 in both (non vch)
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
facch = 3;
|
||||
break;
|
||||
//DCR SB0, Data or End Frame
|
||||
case 0x08: //SB0 w/ CSM 9 digit BCD found here
|
||||
sacch2 = 1;
|
||||
pich_tch = 1; //observed 2nd PICH is zero fill
|
||||
break;
|
||||
case 0x48:
|
||||
pich_tch = 3; //may be 1, or 2 TCH (observed possibly both)
|
||||
case 0x4A:
|
||||
sacch2 = 1;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
#ifdef NXDN_DEBUG_LICH
|
||||
if (opts->payload == 1)
|
||||
fprintf(stderr, " false sync or unsupported NXDN lich type L: %02X / LH: %04X\n", lich, lich_bits_hex);
|
||||
#endif
|
||||
//reset the sacch field, we probably got a false sync and need to wipe or give a bad crc
|
||||
memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
|
||||
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
|
||||
state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
|
||||
voice = 0;
|
||||
goto END;
|
||||
break;
|
||||
#ifdef TYPE_D_DECODE
|
||||
//NXDN "Type-D" or "IDAS" Specific Lich Codes
|
||||
case 0x76: //normal vch voice (in one and two)
|
||||
case 0x77:
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
voice = 3;
|
||||
break;
|
||||
// case 0x74: //False Positive on DCR, keep disabled, or revert this line
|
||||
case 0x75: //vch in 1, facch1 in 2 (facch 2 steal)
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
voice = 1;
|
||||
facch = 2;
|
||||
break;
|
||||
case 0x72: //facch in 1, vch in 2 (facch 1 steal)
|
||||
case 0x73:
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
voice = 2;
|
||||
facch = 1;
|
||||
break;
|
||||
case 0x70: //facch steal in vch 1 and vch 2 (during voice only)
|
||||
case 0x71:
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
facch = 3;
|
||||
break;
|
||||
case 0x6E: //udch2
|
||||
case 0x6F:
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
udch2 = 1;
|
||||
break;
|
||||
case 0x68:
|
||||
case 0x69: //facch3
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
facch3 = 1;
|
||||
break;
|
||||
case 0x62:
|
||||
case 0x63: //facch1 in 1, null data and post field in 2
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
facch = 1;
|
||||
break;
|
||||
case 0x60:
|
||||
case 0x61: //facch1 in both (non vch)
|
||||
idas = 1;
|
||||
scch = 1;
|
||||
facch = 3;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
#ifdef NXDN_DEBUG_LICH
|
||||
if (opts->payload == 1)
|
||||
fprintf(stderr, " false sync or unsupported NXDN lich type L: %02X / LH: %04X\n", lich, lich_bits_hex);
|
||||
#endif
|
||||
//reset the sacch field, we probably got a false sync and need to wipe or give a bad crc
|
||||
memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
|
||||
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
|
||||
state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
|
||||
voice = 0;
|
||||
break;
|
||||
} // end of switch(lich)
|
||||
|
||||
// //collect remaining dibits at this point (before bad frame skip)
|
||||
// for (int i = 0; i < 174; i++) //192total-10FSW-8lich = 174
|
||||
// dbuf[i+8] = getDibit(opts, state);
|
||||
|
||||
//go to end if bad returns from earlier
|
||||
if (state->lastsynctype == -1)
|
||||
goto END;
|
||||
|
||||
//collect remaining dibits at this point (after bad frame skip)
|
||||
for (int i = 0; i < 174; i++) //192total-10FSW-8lich = 174
|
||||
dbuf[i+8] = getDibit(opts, state);
|
||||
|
||||
//enable these after good lich parity and known lich value
|
||||
state->carrier = 1;
|
||||
state->last_cc_sync_time = time(NULL);
|
||||
|
||||
//debug polarity (purely illustative, shows how frequently false sync can be attributed to wrong polarity)
|
||||
#ifdef NXDN_DEBUG_LICH
|
||||
if (state->lastsynctype == 28)
|
||||
fprintf (stderr, "(+) ");
|
||||
else fprintf (stderr, "(-) ");
|
||||
#endif
|
||||
|
||||
//printframesync after determining we have a good lich and it has something in it
|
||||
if (idas)
|
||||
{
|
||||
@@ -340,13 +369,6 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
#endif
|
||||
}
|
||||
|
||||
//now that we have a good LICH, we can collect all of our dibits
|
||||
//and push them to the proper places for decoding (if LICH calls for that type)
|
||||
for (int i = 0; i < 174; i++) //192total-10FSW-8lich = 174
|
||||
{
|
||||
dbuf[i+8] = getDibit(opts, state);
|
||||
}
|
||||
|
||||
nxdn_pn95_dibit_scrambler (state, dbuf, 182);
|
||||
|
||||
//seperate our dbuf (dibit_buffer) into individual bit array
|
||||
@@ -358,9 +380,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
|
||||
//sacch or scch bits
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
sacch_bits[i] = nxdn_bit_buffer[i+16];
|
||||
}
|
||||
|
||||
//facch
|
||||
for (int i = 0; i < 144; i++)
|
||||
@@ -371,21 +391,15 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
|
||||
//cac
|
||||
for (int i = 0; i < 300; i++)
|
||||
{
|
||||
cac_bits[i] = nxdn_bit_buffer[i+16];
|
||||
}
|
||||
|
||||
//udch or facch2
|
||||
for (int i = 0; i < 348; i++)
|
||||
{
|
||||
facch2_bits[i] = nxdn_bit_buffer[i+16];
|
||||
}
|
||||
|
||||
//udch2 or facch3
|
||||
for (int i = 0; i < 288; i++)
|
||||
{
|
||||
facch3_bits[i] = nxdn_bit_buffer[i+16+60];
|
||||
}
|
||||
|
||||
//vch frames stay inside dbuf, easier to assign that to ambe_fr frames
|
||||
//sacch needs extra handling depending on superframe or non-superframe variety
|
||||
@@ -551,32 +565,38 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
else state->nxdn_sacch_non_superframe = FALSE;
|
||||
|
||||
//TODO Later: Add Direction and/or LICH to all decoding functions
|
||||
if (scch) nxdn_deperm_scch (opts, state, sacch_bits, direction);
|
||||
|
||||
if (udch2) nxdn_deperm_facch3_udch2(opts, state, facch3_bits, 0);
|
||||
if (facch3) nxdn_deperm_facch3_udch2(opts, state, facch3_bits, 1);
|
||||
//Type-D "IDAS"
|
||||
if (scch) idas_scch(opts, state, sacch_bits, direction);
|
||||
if (udch2) idas_facch3_udch2(opts, state, facch3_bits, 0);
|
||||
if (facch3) idas_facch3_udch2(opts, state, facch3_bits, 1);
|
||||
|
||||
if (sacch) nxdn_deperm_sacch(opts, state, sacch_bits);
|
||||
if (cac) nxdn_deperm_cac(opts, state, cac_bits);
|
||||
//NXDN Conventional and Type-C
|
||||
if (sacch) nxdn_sacch(opts, state, sacch_bits);
|
||||
if (cac) nxdn_cac(opts, state, cac_bits);
|
||||
|
||||
//Seperated UDCH user data from facch2 data
|
||||
if (udch) nxdn_deperm_facch2_udch(opts, state, facch2_bits, 0);
|
||||
if (facch2) nxdn_deperm_facch2_udch(opts, state, facch2_bits, 1);
|
||||
|
||||
//DCR
|
||||
if (sacch2) nxdn_deperm_sacch2(opts, state, sacch_bits);
|
||||
if (pich_tch & 1) nxdn_deperm_pich_tch(opts, state, facch_bits_a, lich);
|
||||
if (pich_tch & 2) nxdn_deperm_pich_tch(opts, state, facch_bits_b, lich);
|
||||
|
||||
//only run facch in second slot if its not equal to the first one
|
||||
//ideally, this would work better AFTER decoding/FEC
|
||||
if (facch & 1) nxdn_deperm_facch(opts, state, facch_bits_a);
|
||||
if (facch & 2)
|
||||
//Duplicate facch check inside decoder. store and compare for facch message, depending on passed frame value
|
||||
if (facch == 3)
|
||||
{
|
||||
if (memcmp (facch_bits_a, facch_bits_b, 144) != 0)
|
||||
nxdn_deperm_facch(opts, state, facch_bits_b);
|
||||
if (facch & 1) nxdn_facch1(opts, state, facch_bits_a, 1);
|
||||
if (facch & 2) nxdn_facch1(opts, state, facch_bits_b, 2);
|
||||
}
|
||||
else //facch1 steals, or single facch only messages
|
||||
{
|
||||
if (facch & 1) nxdn_facch1(opts, state, facch_bits_a, 0);
|
||||
if (facch & 2) nxdn_facch1(opts, state, facch_bits_b, 0);
|
||||
}
|
||||
|
||||
//Seperated UDCH user data from facch2 data
|
||||
if (udch) nxdn_facch2_udch(opts, state, facch2_bits, 0);
|
||||
if (facch2) nxdn_facch2_udch(opts, state, facch2_bits, 1);
|
||||
|
||||
//Japanese DCR
|
||||
if (sacch2) dcr_sacch(opts, state, sacch_bits);
|
||||
if (pich_tch & 1) dcr_pich_tch(opts, state, facch_bits_a, lich);
|
||||
if (pich_tch & 2) dcr_pich_tch(opts, state, facch_bits_b, lich);
|
||||
|
||||
//EHR only (TODO: Make an EFR mode? never observed)
|
||||
if (voice)
|
||||
{
|
||||
//restore MBE file open here
|
||||
@@ -632,7 +652,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
|
||||
//if rejected sync, reset carrier and synctype as well
|
||||
if (state->lastsynctype == -1)
|
||||
{
|
||||
state->carrier = 0;
|
||||
state->carrier = 0; //this is why the "Sync: no sync" doesn't show in terminal if we have a bad sync (not an issue, just not consistent)
|
||||
state->synctype = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ const int nZ[36] = { 5, 3, 4, 2, 3, 1,
|
||||
13, 2, 12, 1, 11, 0
|
||||
};
|
||||
|
||||
void nxdn_voice (dsd_opts * opts, dsd_state * state, int voice, uint8_t dbuf[182])
|
||||
void nxdn_voice (dsd_opts * opts, dsd_state * state, int voice, uint8_t * dbuf)
|
||||
{
|
||||
int i;
|
||||
int start, stop;
|
||||
|
||||
186
src/p25_12.c
186
src/p25_12.c
@@ -1,22 +1,24 @@
|
||||
/*-------------------------------------------------------------------------------
|
||||
* p25_12.c
|
||||
* P25p1 1/2 Rate Simple Trellis Decoder
|
||||
* P25p1 1/2 Rate Viterbi Trellis Decoder (Improved)
|
||||
*
|
||||
* LWVMOBILE
|
||||
* 2023-10 DSD-FME Florida Man Edition
|
||||
* 2026-02 DSD-FME Florida Man Edition
|
||||
*-----------------------------------------------------------------------------*/
|
||||
|
||||
#include "dsd.h"
|
||||
#include <limits.h> // For UINT8_MAX as infinity proxy
|
||||
|
||||
uint8_t p25_interleave[98] = {
|
||||
0, 1, 8, 9, 16, 17, 24, 25, 32, 33, 40, 41, 48, 49, 56, 57, 64, 65, 72, 73, 80, 81, 88, 89, 96, 97,
|
||||
2, 3, 10, 11, 18, 19, 26, 27, 34, 35, 42, 43, 50, 51, 58, 59, 66, 67, 74, 75, 82, 83, 90, 91,
|
||||
4, 5, 12, 13, 20, 21, 28, 29, 36, 37, 44, 45, 52, 53, 60, 61, 68, 69, 76, 77, 84, 85, 92, 93,
|
||||
6, 7, 14, 15, 22, 23, 30, 31, 38, 39, 46, 47, 54, 55, 62, 63, 70, 71, 78, 79, 86, 87, 94, 95};
|
||||
0, 1, 8, 9, 16, 17, 24, 25, 32, 33, 40, 41, 48, 49, 56, 57, 64, 65, 72, 73, 80, 81, 88, 89, 96, 97,
|
||||
2, 3, 10, 11, 18, 19, 26, 27, 34, 35, 42, 43, 50, 51, 58, 59, 66, 67, 74, 75, 82, 83, 90, 91,
|
||||
4, 5, 12, 13, 20, 21, 28, 29, 36, 37, 44, 45, 52, 53, 60, 61, 68, 69, 76, 77, 84, 85, 92, 93,
|
||||
6, 7, 14, 15, 22, 23, 30, 31, 38, 39, 46, 47, 54, 55, 62, 63, 70, 71, 78, 79, 86, 87, 94, 95
|
||||
};
|
||||
|
||||
//this is a convertion table for converting the dibit pairs into constellation points
|
||||
uint8_t p25_constellation_map[16] = {
|
||||
11, 12, 0, 7, 14, 9, 5, 2, 10, 13, 1, 6, 15, 8, 4, 3
|
||||
11, 12, 0, 7, 14, 9, 5, 2, 10, 13, 1, 6, 15, 8, 4, 3
|
||||
};
|
||||
|
||||
//digitized dibit to OTA symbol conversion for reference
|
||||
@@ -25,19 +27,21 @@ uint8_t p25_constellation_map[16] = {
|
||||
|
||||
//finite state machine values
|
||||
uint8_t p25_fsm[16] = {
|
||||
0,15,12,3,
|
||||
4,11,8,7,
|
||||
13,2,1,14,
|
||||
9,6,5,10 };
|
||||
0,15,12,3,
|
||||
4,11,8,7,
|
||||
13,2,1,14,
|
||||
9,6,5,10
|
||||
};
|
||||
|
||||
//this is a dibit-pair to trellis dibit transition matrix (SDRTrunk and Ossmann)
|
||||
//when evaluating hamming distance, we want to use this xor the dibit-pair nib,
|
||||
//and not the fsm xor the constellation point
|
||||
uint8_t p25_dtm[16] = {
|
||||
2,12,1,15,
|
||||
14,0,13,3,
|
||||
9,7,10,4,
|
||||
5,11,6,8 };
|
||||
2,12,1,15,
|
||||
14,0,13,3,
|
||||
9,7,10,4,
|
||||
5,11,6,8
|
||||
};
|
||||
|
||||
int count_bits(uint8_t b, int slen)
|
||||
{
|
||||
@@ -50,37 +54,13 @@ int count_bits(uint8_t b, int slen)
|
||||
return i;
|
||||
}
|
||||
|
||||
uint8_t find_min(uint8_t list[4], int len)
|
||||
{
|
||||
int min = list[0];
|
||||
uint8_t index = 0;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < len; i++)
|
||||
{
|
||||
if (list[i] < min)
|
||||
{
|
||||
min = list[i];
|
||||
index = (uint8_t)i;
|
||||
}
|
||||
|
||||
//NOTE: Disqualifying result on uniqueness can impact decoding
|
||||
//let the CRC determine if the result is good or bad
|
||||
//its not uncommon for two values of the same min
|
||||
//distance to emerge, so its 50/50 each time
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
int p25_12(uint8_t * input, uint8_t treturn[12])
|
||||
{
|
||||
int i, j;
|
||||
int irr_err = 0;
|
||||
int i, t;
|
||||
int total_err = 0; UNUSED(total_err);
|
||||
|
||||
uint8_t deinterleaved_dibits[98];
|
||||
memset (deinterleaved_dibits, 0, sizeof(deinterleaved_dibits));
|
||||
memset(deinterleaved_dibits, 0, sizeof(deinterleaved_dibits));
|
||||
|
||||
//deinterleave our input dibits
|
||||
for (i = 0; i < 98; i++)
|
||||
@@ -88,80 +68,86 @@ int p25_12(uint8_t * input, uint8_t treturn[12])
|
||||
|
||||
//pack the input into nibbles (dibit pairs)
|
||||
uint8_t nibs[49];
|
||||
memset (nibs, 0, sizeof(nibs));
|
||||
memset(nibs, 0, sizeof(nibs));
|
||||
|
||||
for (i = 0; i < 49; i++)
|
||||
nibs[i] = (deinterleaved_dibits[i*2+0] << 2) | (deinterleaved_dibits[i*2+1] << 0);
|
||||
|
||||
//convert our dibit pairs into constellation point values
|
||||
uint8_t point[49];
|
||||
memset (point, 0xFF, sizeof(point));
|
||||
memset(point, 0xFF, sizeof(point));
|
||||
|
||||
for (i = 0; i < 49; i++)
|
||||
point[i] = p25_constellation_map[nibs[i]];
|
||||
|
||||
//debug view points
|
||||
// fprintf (stderr, "\n P =");
|
||||
// for (i = 0; i < 49; i++)
|
||||
// fprintf (stderr, " %02d", point[i]);
|
||||
// Viterbi setup: 4 states, 49 stages
|
||||
// Use UINT8_MAX / 2 as proxy for infinity (max possible errors per path ~196)
|
||||
const uint8_t INF = UINT8_MAX / 2;
|
||||
uint8_t metric[2][4]; // Ping-pong: [0] prev, [1] curr
|
||||
uint8_t path[49][4]; // path[t][s] = best prev_state to reach state s at stage t
|
||||
uint8_t tdibits[49]; // Decoded trellis dibits (states along best path)
|
||||
|
||||
//convert constellation points into tdibit values using the FSM
|
||||
uint8_t state = 0;
|
||||
uint8_t tdibits[49]; //trellis dibits 1/2 rate
|
||||
memset (tdibits, 0xF, sizeof(tdibits));
|
||||
uint8_t hd[4]; //array of the four fsm words hamming distance
|
||||
memset (hd, 0, sizeof(hd));
|
||||
uint8_t min = 0;
|
||||
|
||||
for (i = 0; i < 49; i++)
|
||||
{
|
||||
|
||||
for (j = 0; j < 4; j++)
|
||||
{
|
||||
if ( p25_fsm[(state*4)+j] == point[i] )
|
||||
{
|
||||
//return our tdibit value and state for the next point
|
||||
tdibits[i] = state = (uint8_t)j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if tdibit value is greater than 3, then we have a decoding error
|
||||
if (tdibits[i] == 0xF)
|
||||
{
|
||||
irr_err++; //increment number of errors
|
||||
|
||||
//debug position of error and state value
|
||||
// fprintf (stderr, " %d:%d;", i, state);
|
||||
|
||||
//this method only seems to be reliable up to 1-2 bit errors...sometimes
|
||||
for (j = 0; j < 4; j++)
|
||||
hd[j] = count_bits ( ((nibs[i] ^ p25_dtm[(state*4)+j]) & 0xF ), 4 );
|
||||
min = find_min (hd, 4);
|
||||
tdibits[i] = state = min;
|
||||
|
||||
memset (hd, 0, sizeof(hd));
|
||||
min = 0;
|
||||
|
||||
}
|
||||
// Initialize: Start from state 0 before first symbol, metric 0
|
||||
int curr = 0, prev = 1;
|
||||
memset(metric, 0xFF, sizeof(metric)); // Set to high value
|
||||
memset(path, 0, sizeof(path)); // Zero-init paths
|
||||
|
||||
// Stage 0 (first symbol): From initial state 0 to each possible next_s
|
||||
memset(metric[curr], INF, sizeof(metric[0]));
|
||||
for (int next_s = 0; next_s < 4; next_s++) {
|
||||
int branch = count_bits(nibs[0] ^ p25_dtm[0 * 4 + next_s], 4);
|
||||
metric[curr][next_s] = (uint8_t)branch;
|
||||
path[0][next_s] = 0; // Prev state is initial 0
|
||||
total_err += branch; // Accumulate for return (total path errors)
|
||||
}
|
||||
|
||||
//debug view tdibits/states
|
||||
// fprintf (stderr, "\n T =");
|
||||
// for (i = 0; i < 49; i++)
|
||||
// fprintf (stderr, " %02d", tdibits[i]);
|
||||
// Stages 1 to 48
|
||||
for (t = 1; t < 49; t++) {
|
||||
// Swap buffers
|
||||
prev = curr;
|
||||
curr = 1 - curr;
|
||||
memset(metric[curr], INF, sizeof(metric[0]));
|
||||
|
||||
//pack tdibits into return payload bytes
|
||||
for (int next_s = 0; next_s < 4; next_s++) {
|
||||
uint8_t min_dist = INF;
|
||||
uint8_t best_prev = 0;
|
||||
for (int prev_s = 0; prev_s < 4; prev_s++) {
|
||||
if (metric[prev][prev_s] == INF) continue;
|
||||
int branch = count_bits(nibs[t] ^ p25_dtm[prev_s * 4 + next_s], 4);
|
||||
uint8_t cand = (uint8_t)(metric[prev][prev_s] + branch);
|
||||
if (cand < min_dist) {
|
||||
min_dist = cand;
|
||||
best_prev = (uint8_t)prev_s;
|
||||
}
|
||||
}
|
||||
metric[curr][next_s] = min_dist;
|
||||
path[t][next_s] = best_prev;
|
||||
}
|
||||
}
|
||||
|
||||
// Find final state with minimum metric
|
||||
uint8_t min_final = 0;
|
||||
uint8_t min_metric = metric[curr][0];
|
||||
for (int s = 1; s < 4; s++) {
|
||||
if (metric[curr][s] < min_metric) {
|
||||
min_metric = metric[curr][s];
|
||||
min_final = (uint8_t)s;
|
||||
}
|
||||
}
|
||||
|
||||
// Traceback: Reconstruct state sequence (tdibits[t] = state after symbol t)
|
||||
tdibits[48] = min_final;
|
||||
for (t = 48; t >= 1; t--) {
|
||||
tdibits[t - 1] = path[t][tdibits[t]];
|
||||
}
|
||||
|
||||
//pack tdibits into return payload bytes (use first 48 as original)
|
||||
for (i = 0; i < 12; i++)
|
||||
treturn[i] = (tdibits[(i*4)+0] << 6) | (tdibits[(i*4)+1] << 4) | (tdibits[(i*4)+2] << 2) | tdibits[(i*4)+3];
|
||||
|
||||
//trellis point/state err tally
|
||||
// if (irr_err != 0)
|
||||
// fprintf (stderr, " P_ERR = %d", irr_err);
|
||||
|
||||
return (irr_err);
|
||||
}
|
||||
|
||||
|
||||
//debug what was the cost metric on this
|
||||
// fprintf (stderr, " R12 Min Metric = %d; ", min_metric);
|
||||
|
||||
// Return total Hamming errors in best path
|
||||
return (int)min_metric;
|
||||
}
|
||||
@@ -506,16 +506,16 @@ void p25_lcw (dsd_opts * opts, dsd_state * state, uint8_t LCW_bits[], uint8_t ir
|
||||
fprintf (stderr, " MFIDA4 (Harris) GPS Block 1");
|
||||
memset(state->dmr_pdu_sf[0], 0, sizeof(state->dmr_pdu_sf[0]));
|
||||
memcpy(state->dmr_pdu_sf[0], LCW_bits, 16*sizeof(uint8_t)); //opcode and mfid for check
|
||||
memcpy(state->dmr_pdu_sf[0]+40, LCW_bits+16, 56*sizeof(uint8_t)); //+40 offset to match the vPDU decoder
|
||||
memcpy(state->dmr_pdu_sf[0]+16, LCW_bits+16, 56*sizeof(uint8_t)); //contents of message in this block
|
||||
}
|
||||
|
||||
else if (lc_mfid == 0xA4 && lc_opcode == 0x2B)
|
||||
{
|
||||
fprintf (stderr, " MFIDA4 (Harris) GPS Block 2");
|
||||
memcpy(state->dmr_pdu_sf[0]+40+56, LCW_bits+16, 56*sizeof(uint8_t)); //+40 +56 to offset first block
|
||||
memcpy(state->dmr_pdu_sf[0]+16+56, LCW_bits+16, 56*sizeof(uint8_t)); //+16 +56 to offset first block
|
||||
uint16_t check = (uint16_t)ConvertBitIntoBytes(&state->dmr_pdu_sf[0][0], 16);
|
||||
if (check == 0x2AA4)
|
||||
nmea_harris(opts, state, state->dmr_pdu_sf[0], (uint32_t)state->lastsrc, 0);
|
||||
harris_lptt(opts, state, state->dmr_pdu_sf[0]+16, (uint32_t)state->lastsrc, 0, 1);
|
||||
else fprintf (stderr, " Missing GPS Block 1");
|
||||
memset(state->dmr_pdu_sf[0], 0, sizeof(state->dmr_pdu_sf[0]));
|
||||
}
|
||||
@@ -538,6 +538,15 @@ void p25_lcw (dsd_opts * opts, dsd_state * state, uint8_t LCW_bits[], uint8_t ir
|
||||
tait_iso7_embedded_alias_decode(opts, state, 0, 8, LCW_bits);
|
||||
}
|
||||
|
||||
//observed alongside Tait Talker Alias ID: 10T
|
||||
else if (lc_mfid == 0xD8 && lc_format == 0x01)
|
||||
{
|
||||
uint32_t wacn = (uint32_t)ConvertBitIntoBytes(&LCW_bits[16], 20);
|
||||
uint32_t sys = (uint32_t)ConvertBitIntoBytes(&LCW_bits[36], 12);
|
||||
uint32_t rid = (uint32_t)ConvertBitIntoBytes(&LCW_bits[48], 24);
|
||||
fprintf (stderr, " MFIDD8 (Tait) Subscriber FQ-SUID: %05X.%03X.%d", wacn, sys, rid);
|
||||
}
|
||||
|
||||
//not a duplicate, this one will print if not MFID 0 or 1
|
||||
else
|
||||
{
|
||||
|
||||
@@ -143,7 +143,7 @@ void processMPDU(dsd_opts * opts, dsd_state * state)
|
||||
//debug
|
||||
// fprintf (stderr, " J:%d;", j); //use this with the P_ERR inside of 34 rate decoder to see where the failures occur
|
||||
|
||||
ec[j] = dmr_34 (tsbk_dibit, r34byte_b);
|
||||
ec[j] = viterbi_r34 (tsbk_dibit, r34byte_b);
|
||||
|
||||
//shuffle 34 rate data into array
|
||||
if (j != 0) //should never happen, but just in case
|
||||
|
||||
@@ -457,8 +457,18 @@ void p25_decode_pdu_data(dsd_opts * opts, dsd_state * state, uint8_t * input, in
|
||||
if (sap == 0 || sap == 4) //User Data or Packet Data (both are UDP typically, same format dmr UDP/IP data)
|
||||
decode_ip_pdu (opts, state, len+1, input+ptr);
|
||||
|
||||
else if (sap == 48) //Tier 1 Location Service (or does it depend on the io bit?)
|
||||
utf8_to_text(state, 1, len-ptr+1, input+ptr); //TODO, read initial string, i.e., $GPRMC and properly decode
|
||||
//NMEA Tier 1 Location Reporting w/ Supplimentary $PP25 string
|
||||
else if (sap == 48)
|
||||
{
|
||||
len += 12; //not sure how we ended up off by up to 12 on this, above negation too much?
|
||||
state->dmr_lrrp_source[state->currentslot] = state->lastsrc;
|
||||
state->dmr_lrrp_source[state->currentslot] = state->lasttg;
|
||||
uint8_t input_bits[(len+1)*8];
|
||||
memset(input_bits, 0, sizeof(input_bits));
|
||||
unpack_byte_array_into_bit_array(input, input_bits, len);
|
||||
fprintf (stderr, "\n ");
|
||||
nmea_sentence_checker(opts, state, input_bits+ptr*8, state->currentslot, len*8);
|
||||
}
|
||||
|
||||
// else //default catch all (debug only)
|
||||
// {
|
||||
|
||||
@@ -489,11 +489,53 @@ void processTSBK(dsd_opts * opts, dsd_state * state)
|
||||
{
|
||||
fprintf (stderr, "\n");
|
||||
fprintf (stderr, "%s",KYEL);
|
||||
fprintf (stderr, " MFID 90 (Moto) Acknoledge Response: ");
|
||||
fprintf (stderr, " MFID 90 (Moto) Acknowledge Response: ");
|
||||
for (i = 2; i < 10; i++)
|
||||
fprintf (stderr, "%02X", tsbk_byte[i]);
|
||||
}
|
||||
|
||||
else if ( (tsbk_byte[0] & 0x3F) == 0x09 )
|
||||
{
|
||||
fprintf (stderr, "\n");
|
||||
fprintf (stderr, " MFID 90 (Moto) Scan Marker: ");
|
||||
for (i = 2; i < 10; i++)
|
||||
fprintf (stderr, "%02X", tsbk_byte[i]);
|
||||
}
|
||||
|
||||
else if ( (tsbk_byte[0] & 0x3F) == 0x0B )
|
||||
{
|
||||
|
||||
uint16_t channel = (tsbk_byte[8] << 8) | tsbk_byte[9];
|
||||
fprintf (stderr, "\n");
|
||||
fprintf (stderr, " MFID 90 (Moto) ID Channel: %04X;", channel);
|
||||
char sid_string[10];
|
||||
memset(sid_string, 0, sizeof(sid_string));
|
||||
int k = 0;
|
||||
|
||||
if (tsbk_byte[2] != 0)
|
||||
{
|
||||
fprintf (stderr, " Station ID: ");
|
||||
uint8_t sid_bits[42];
|
||||
memset(sid_bits, 0, sizeof(sid_bits));
|
||||
unpack_byte_array_into_bit_array(tsbk_byte+2, sid_bits, 6);
|
||||
for (i = 0; i < 42; i+=6)
|
||||
{
|
||||
uint8_t bsi_char = convert_bits_into_output(sid_bits+i, 6) + 43;
|
||||
if (bsi_char > 0x1F && bsi_char < 0x7F)
|
||||
{
|
||||
fprintf (stderr, "%c", bsi_char);
|
||||
sid_string[k++] = bsi_char;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
}
|
||||
|
||||
//debug, may assign this string to something else later on
|
||||
// fprintf (stderr, " - Str: %s;", sid_string);
|
||||
|
||||
process_channel_to_freq(opts, state, channel);
|
||||
}
|
||||
|
||||
|
||||
//Some of these Opcodes that aren't found in any TIA manual come from SDRTrunk (or other sources),
|
||||
//but can't verify the accuracy of their meaning/context
|
||||
@@ -505,22 +547,6 @@ void processTSBK(dsd_opts * opts, dsd_state * state)
|
||||
// fprintf (stderr, "%02X", tsbk_byte[i]);
|
||||
// }
|
||||
|
||||
// else if ( (tsbk_byte[0] & 0x3F) == 0x09 )
|
||||
// {
|
||||
// fprintf (stderr, "\n");
|
||||
// fprintf (stderr, " MFID 90 (Moto) Channel Loading: "); //don't understand the context for this one, waiting on units to arrive on channel?
|
||||
// for (i = 2; i < 10; i++)
|
||||
// fprintf (stderr, "%02X", tsbk_byte[i]);
|
||||
// }
|
||||
|
||||
// else if ( (tsbk_byte[0] & 0x3F) == 0x0B )
|
||||
// {
|
||||
// fprintf (stderr, "\n");
|
||||
// fprintf (stderr, " MFID 90 (Moto) Control Channel: "); //this appears to echo the 16-bit channel number for the main control channel/RFSS
|
||||
// for (i = 2; i < 10; i++)
|
||||
// fprintf (stderr, "%02X", tsbk_byte[i]);
|
||||
// }
|
||||
|
||||
// else if ( (tsbk_byte[0] & 0x3F) == 0x0E )
|
||||
// {
|
||||
// fprintf (stderr, "\n");
|
||||
@@ -537,14 +563,6 @@ void processTSBK(dsd_opts * opts, dsd_state * state)
|
||||
// fprintf (stderr, "%02X", tsbk_byte[i]);
|
||||
// }
|
||||
|
||||
// else if ( (tsbk_byte[0] & 0x3F) == 0x15 ) //noted on RR, but not observed as of yet, this may actualy be a LCW and not a TSBK?
|
||||
// {
|
||||
// fprintf (stderr, "\n");
|
||||
// fprintf (stderr, " MFID 90 (Moto) Talker Alias: ");
|
||||
// for (i = 2; i < 10; i++)
|
||||
// fprintf (stderr, "%02X", tsbk_byte[i]);
|
||||
// }
|
||||
|
||||
// else if ( (tsbk_byte[0] & 0x3F) == 0x16 )
|
||||
// {
|
||||
// fprintf (stderr, "\n");
|
||||
|
||||
@@ -1628,10 +1628,79 @@ void process_MAC_VPDU(dsd_opts * opts, dsd_state * state, int type, unsigned lon
|
||||
|
||||
}
|
||||
|
||||
//Extended Function Command - Abbreviated (Generic non MFID variant)
|
||||
if (MAC[1+len_a] == 0x64)
|
||||
{
|
||||
|
||||
uint8_t class = MAC[2+len_a];
|
||||
uint8_t operand = MAC[3+len_a];
|
||||
uint32_t argument = (MAC[4+len_a] << 16) | (MAC[5+len_a] << 8) | MAC[6+len_a];
|
||||
uint32_t target = (MAC[7+len_a] << 16) | (MAC[8+len_a] << 8) | MAC[9+len_a];
|
||||
fprintf (stderr, "\n Extended Function Command: Class: %02X; Operand: %02X; Arg/Src: %06X; Target: %d; ", class, operand, argument, target);
|
||||
//WIP: Further Decode
|
||||
if (class == 0)
|
||||
{
|
||||
if ((operand & 0x7F) == 0x00) fprintf (stderr, "Radio Check; ");
|
||||
else if ((operand & 0x7F) == 0x7D) fprintf (stderr, "Radio Detach; ");
|
||||
else if ((operand & 0x7F) == 0x7E) fprintf (stderr, "Radio Uninhibit; ");
|
||||
else if ((operand & 0x7F) == 0x7F) fprintf (stderr, "Radio Inhibit; ");
|
||||
else fprintf (stderr, "Reserved; ");
|
||||
|
||||
//MSB is an ack of the above command
|
||||
if ((operand >> 7) == 1) fprintf (stderr, "Ack; ");
|
||||
}
|
||||
else fprintf (stderr, "Other Command; ");
|
||||
|
||||
}
|
||||
|
||||
//MFID90 Group Regroup Add Command
|
||||
if (MAC[1+len_a] == 0x81 && MAC[2+len_a] == 0x90) //needs MAC message len update, may work same as explicit enc regroup?
|
||||
if (MAC[1+len_a] == 0x81 && MAC[2+len_a] == 0x90)
|
||||
{
|
||||
fprintf (stderr, "\n MFID90 Group Regroup Add Command ");
|
||||
uint8_t len = MAC[3+len_a];
|
||||
len_b = len;
|
||||
}
|
||||
|
||||
//MFID90 Unknown Message <--was causing an issue on ABQ system w/ false GPS, SCCB and RFSS_STS_BCST MAC messages
|
||||
if (MAC[1+len_a] == 0x8F && MAC[2+len_a] == 0x90)
|
||||
{
|
||||
|
||||
uint8_t len = MAC[3+len_a]; //observed len values are 0x04, 0x08, and 0x0B (12)
|
||||
|
||||
//constraints on message len
|
||||
if (len > 12)
|
||||
len = 12;
|
||||
// else if (len < 4)
|
||||
// len = 4;
|
||||
|
||||
fprintf (stderr, "%s",KCYN);
|
||||
fprintf (stderr, "\n MFID 90 (Moto); Opcode: %02llX; ", MAC[1+len_a] & 0x3F); //mask, or not?
|
||||
for (int i = 4; i < len+1; i++)
|
||||
fprintf (stderr, "%02llX", MAC[i+len_a]);
|
||||
fprintf (stderr, " %s",KNRM);
|
||||
|
||||
len_b = len;
|
||||
}
|
||||
|
||||
//MFID90 Unknown Message 2 (usually in second message, unknown, maybe just filler?)
|
||||
if (MAC[1+len_a] == 0xBF && MAC[2+len_a] == 0x90)
|
||||
{
|
||||
|
||||
uint8_t len = MAC[3+len_a]; //observed len values are 0x03
|
||||
|
||||
//constraints on message len
|
||||
if (len > 3)
|
||||
len = 3;
|
||||
else if (len < 3)
|
||||
len = 3;
|
||||
|
||||
fprintf (stderr, "%s",KCYN);
|
||||
fprintf (stderr, "\n MFID 90 (Moto); Opcode: %02llX; ", MAC[1+len_a] & 0x3F); //mask, or not?
|
||||
for (int i = 4; i < len+1; i++)
|
||||
fprintf (stderr, "%02llX", MAC[i+len_a]);
|
||||
fprintf (stderr, " %s",KNRM);
|
||||
|
||||
len_b = len;
|
||||
}
|
||||
|
||||
//the len on these indicate they are always a single messages, foregoing the +len_a index pointer
|
||||
@@ -1772,9 +1841,15 @@ void process_MAC_VPDU(dsd_opts * opts, dsd_state * state, int type, unsigned lon
|
||||
if (MAC[len_a+1] == 0x80 && MAC[len_a+2] != 0xA4 && MAC[len_a+2] != 0x90)
|
||||
{
|
||||
int unk1 = MAC[len_a+1]; //assuming this is the octet set for the 'manufacturer specific' message, may only be the MSBit
|
||||
int unk2 = MAC[len_a+2]; //This field is observed as 0xAA, unknown if this is an opcode, or other MFID
|
||||
int unk2 = MAC[len_a+2]; //This field is observed as 0xAA, its possible its a derived message of 0x2A from P25p1 LCW format
|
||||
int mfid = MAC[len_a+3]; //This is where the 0xA4 (Harris) Identifier is found in this message, as opposed to +2
|
||||
int len = MAC[len_a+4] & 0x3F;; //0x11 or 17 dec sounds reasonable, but cannot verify
|
||||
int len = MAC[len_a+4] & 0x3F; //len of this message (should always be 17)
|
||||
|
||||
//bugfix observed on random errant second MAC message on Phase 2
|
||||
//confirmed this MAC message is always 17 (0x11)
|
||||
if (len != 0x11)
|
||||
goto END_PDU;
|
||||
|
||||
fprintf (stderr, "\n MFID %02X (Harris); Len: %d; Opcode: %02X/%02X;", mfid, len, unk1, unk2);
|
||||
|
||||
//convert bytes to bits, may move this up top
|
||||
@@ -1791,8 +1866,7 @@ void process_MAC_VPDU(dsd_opts * opts, dsd_state * state, int type, unsigned lon
|
||||
if (slot == 0 && state->lastsrc != 0) tsrc = state->lastsrc;
|
||||
if (slot == 1 && state->lastsrcR != 0) tsrc = state->lastsrcR;
|
||||
|
||||
// harris_gps (opts, state, slot, mac_bits); //fallback
|
||||
nmea_harris (opts, state, mac_bits+0, tsrc, slot); //new
|
||||
harris_lptt (opts, state, mac_bits+40, tsrc, slot, 2);
|
||||
|
||||
//debug - just dump payload
|
||||
// for (i = 0; i < 24; i++)
|
||||
|
||||
@@ -465,27 +465,27 @@ void dc_block_filter(struct demod_state *fm)
|
||||
}
|
||||
|
||||
long int rms(int16_t *samples, int len, int step)
|
||||
/* largely lifted from rtl_power */
|
||||
{
|
||||
int i;
|
||||
long int rms;
|
||||
long p, t, s;
|
||||
double dc, err;
|
||||
long int rms_val;
|
||||
int64_t p = 0, t = 0;
|
||||
int64_t s;
|
||||
|
||||
p = t = 0L;
|
||||
for (i=0; i<len; i+=step) {
|
||||
s = (long)samples[i];
|
||||
for (i = 0; i < len; i += step) {
|
||||
s = samples[i];
|
||||
t += s;
|
||||
p += s * s;
|
||||
}
|
||||
/* correct for dc offset in squares */
|
||||
dc = (double)(t*step) / (double)len;
|
||||
err = t * 2 * dc - dc * dc * len;
|
||||
|
||||
rms = (long int)sqrt((p-err) / len);
|
||||
//going with a value that's easy to figure out when its done the thing
|
||||
if (rms < 0){ rms = 999; }
|
||||
return rms;
|
||||
double dc = (double)(t * (int64_t)step) / (double)len;
|
||||
double err = (double)t * 2.0 * dc - dc * dc * (double)len;
|
||||
|
||||
double val = ((double)p - err) / (double)len;
|
||||
if (val < 0.0) val = 0.0;
|
||||
|
||||
rms_val = (long int)sqrt(val);
|
||||
if (rms_val < 0) rms_val = 999;
|
||||
return rms_val;
|
||||
}
|
||||
|
||||
void full_demod(struct demod_state *d)
|
||||
@@ -1031,6 +1031,7 @@ void open_rtlsdr_stream(dsd_opts *opts)
|
||||
|
||||
//this needs to be initted first, then we set the parameters
|
||||
dongle_init(&dongle);
|
||||
|
||||
//init with low pass if decoding P25 or EDACS/Provoice
|
||||
if (opts->frame_p25p1 == 1 || opts->frame_p25p2 == 1 || opts->frame_provoice == 1)
|
||||
demod_init_ro2(&demod);
|
||||
@@ -1089,7 +1090,7 @@ void open_rtlsdr_stream(dsd_opts *opts)
|
||||
verbose_auto_gain(dongle.dev);
|
||||
fprintf (stderr, "Setting RTL Autogain. \n");
|
||||
} else {
|
||||
dongle.gain = nearest_gain(dongle.dev, dongle.gain);
|
||||
opts->rtl_gain_actual = dongle.gain = nearest_gain(dongle.dev, dongle.gain);
|
||||
verbose_gain_set(dongle.dev, dongle.gain);
|
||||
// fprintf (stderr, "Setting RTL Nearest Gain to %d. \n", dongle.gain); //seems to be working now
|
||||
}
|
||||
@@ -1151,6 +1152,26 @@ int get_rtlsdr_sample(int16_t *sample, dsd_opts * opts, dsd_state * state)
|
||||
verbose_ppm_set(dongle.dev, dongle.ppm_error);
|
||||
}
|
||||
|
||||
//if Gain or AGC Value is Manually Changed, Change it here now
|
||||
if (opts->rtl_gain_actual != dongle.gain && opts->rtl_gain_value != 0)
|
||||
{
|
||||
dongle.gain = (opts->rtl_gain_value * 10);
|
||||
opts->rtl_gain_actual = dongle.gain = nearest_gain(dongle.dev, dongle.gain);
|
||||
verbose_gain_set(dongle.dev, dongle.gain);
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, "\n Dongle Gain Set: %d; \n", dongle.gain);
|
||||
}
|
||||
|
||||
else if (opts->rtl_gain_value == 0 && dongle.gain != AUTO_GAIN)
|
||||
{
|
||||
opts->rtl_gain_actual = dongle.gain = AUTO_GAIN;
|
||||
verbose_auto_gain(dongle.dev);
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, "\n AGC Set; \n");
|
||||
}
|
||||
|
||||
while (output.queue.empty())
|
||||
{
|
||||
struct timespec ts;
|
||||
@@ -1193,20 +1214,18 @@ void rtl_dev_tune(dsd_opts * opts, long int frequency)
|
||||
|
||||
}
|
||||
|
||||
//return RMS value (root means square) power level -- used as soft squelch inside of framesync
|
||||
static long int prev_rms = 0;
|
||||
|
||||
long int rtl_return_rms()
|
||||
{
|
||||
long int sr = 0;
|
||||
// #ifdef __arm__
|
||||
// sr = 100;
|
||||
// #else
|
||||
//debug -- on main machine, lp_len is around 6420, so this probably contributes to very high CPU usage
|
||||
// fprintf (stderr, "LP_LEN: %d \n", demod.lp_len);
|
||||
//I've found that just using a sample size of 160 will give us a good approximation without killing the CPU
|
||||
// sr = rms(demod.lowpassed, demod.lp_len, 1);
|
||||
sr = rms(demod.lowpassed, 160, 1); //I wonder what a reasonable value would be for #2 (input len) there
|
||||
// #endif
|
||||
return (sr);
|
||||
|
||||
long int sr = rms(demod.lowpassed, 512, 1); //change 512 to 160 if using too much CPU
|
||||
|
||||
// simple 4-block IIR average for stability
|
||||
sr = (prev_rms * 3 + sr) / 4;
|
||||
prev_rms = sr;
|
||||
|
||||
return sr;
|
||||
}
|
||||
|
||||
//simple function to clear the rtl sample queue when tuning and during other events (ncurses menu open/close)
|
||||
|
||||
3415
src/sjis_unicode.c
3415
src/sjis_unicode.c
File diff suppressed because it is too large
Load Diff
541
src/soft_viterbi_k5.c
Normal file
541
src/soft_viterbi_k5.c
Normal file
@@ -0,0 +1,541 @@
|
||||
/*-------------------------------------------------------------------------------
|
||||
* soft_viterbi_k5.c
|
||||
* Soft Decision Viterbi K5 Contraint for M17, NXDN, and YSF Fusion
|
||||
*
|
||||
* Viterbi Code from or Modified from libM17 (see bottom)
|
||||
* Wojciech Kaczmarski, SP5WWP
|
||||
*
|
||||
* LWVMOBILE
|
||||
* 2026-01 DSD-FME Florida Man Edition
|
||||
*-----------------------------------------------------------------------------*/
|
||||
|
||||
#include "dsd.h"
|
||||
|
||||
//Ripped from libM17
|
||||
#define K 5 //constraint length
|
||||
#define NUM_STATES (1 << (K - 1)) //number of states
|
||||
|
||||
static uint32_t prevMetrics[NUM_STATES];
|
||||
static uint32_t currMetrics[NUM_STATES];
|
||||
static uint32_t prevMetricsData[NUM_STATES];
|
||||
static uint32_t currMetricsData[NUM_STATES];
|
||||
static uint16_t viterbi_history[600*2];
|
||||
|
||||
/**
|
||||
* @brief Decode unpunctured convolutionally encoded data.
|
||||
*
|
||||
* @param out Destination array where decoded data is written.
|
||||
* @param in Input data.
|
||||
* @param len Input length in bits.
|
||||
* @return Number of bit errors corrected.
|
||||
*/
|
||||
uint32_t viterbi_decode(uint8_t* out, const uint16_t* in, const uint16_t len)
|
||||
{
|
||||
|
||||
viterbi_reset();
|
||||
|
||||
size_t pos = 0;
|
||||
for(size_t i = 0; i < len; i += 2)
|
||||
{
|
||||
uint16_t s0 = in[i];
|
||||
uint16_t s1 = in[i + 1];
|
||||
|
||||
viterbi_decode_bit(s0, s1, pos);
|
||||
pos++;
|
||||
}
|
||||
uint32_t err = viterbi_chainback(out, pos, len/2);
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, "\n vcb: \n");
|
||||
// for (size_t i = 0; i < len; i++)
|
||||
// fprintf (stderr, "%02X", out[i]);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode punctured convolutionally encoded data.
|
||||
*
|
||||
* @param out Destination array where decoded data is written.
|
||||
* @param in Input data.
|
||||
* @param punct Puncturing matrix.
|
||||
* @param in_len Input data length.
|
||||
* @param p_len Puncturing matrix length (entries).
|
||||
* @return Number of bit errors corrected.
|
||||
*/
|
||||
uint32_t viterbi_decode_punctured(uint8_t* out, const uint16_t* in, const uint8_t* punct, const uint16_t in_len, const uint16_t p_len)
|
||||
{
|
||||
|
||||
uint16_t umsg[600*2]; //unpunctured message
|
||||
uint16_t p=0; //puncturer matrix entry
|
||||
uint16_t u=0; //bits count - unpunctured message
|
||||
uint16_t i=0; //bits read from the input message
|
||||
|
||||
while(i<in_len)
|
||||
{
|
||||
if(punct[p])
|
||||
{
|
||||
umsg[u]=in[i];
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
umsg[u]=0x7FFF;
|
||||
}
|
||||
|
||||
u++;
|
||||
p++;
|
||||
p%=p_len;
|
||||
}
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, " p: %d, u: %d; p_len: %d; ", p, u, p_len); //
|
||||
|
||||
return viterbi_decode(out, umsg, u) - (u-in_len)*0x7FFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode one bit and update trellis.
|
||||
*
|
||||
* @param s0 Cost of the first symbol.
|
||||
* @param s1 Cost of the second symbol.
|
||||
* @param pos Bit position in history.
|
||||
*/
|
||||
void viterbi_decode_bit(uint16_t s0, uint16_t s1, const size_t pos)
|
||||
{
|
||||
static const uint16_t COST_TABLE_0[] = {0, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
static const uint16_t COST_TABLE_1[] = {0, 0xFFFF, 0xFFFF, 0, 0, 0xFFFF, 0xFFFF, 0};
|
||||
|
||||
for(uint8_t i = 0; i < NUM_STATES/2; i++)
|
||||
{
|
||||
uint32_t metric = q_abs_diff(COST_TABLE_0[i], s0)
|
||||
+ q_abs_diff(COST_TABLE_1[i], s1);
|
||||
|
||||
|
||||
uint32_t m0 = prevMetrics[i] + metric;
|
||||
uint32_t m1 = prevMetrics[i + NUM_STATES/2] + (0x1FFFE - metric);
|
||||
|
||||
uint32_t m2 = prevMetrics[i] + (0x1FFFE - metric);
|
||||
uint32_t m3 = prevMetrics[i + NUM_STATES/2] + metric;
|
||||
|
||||
uint8_t i0 = 2 * i;
|
||||
uint8_t i1 = i0 + 1;
|
||||
|
||||
if(m0 >= m1)
|
||||
{
|
||||
viterbi_history[pos]|=(1<<i0);
|
||||
currMetrics[i0] = m1;
|
||||
}
|
||||
else
|
||||
{
|
||||
viterbi_history[pos]&=~(1<<i0);
|
||||
currMetrics[i0] = m0;
|
||||
}
|
||||
|
||||
if(m2 >= m3)
|
||||
{
|
||||
viterbi_history[pos]|=(1<<i1);
|
||||
currMetrics[i1] = m3;
|
||||
}
|
||||
else
|
||||
{
|
||||
viterbi_history[pos]&=~(1<<i1);
|
||||
currMetrics[i1] = m2;
|
||||
}
|
||||
}
|
||||
|
||||
//swap
|
||||
uint32_t tmp[NUM_STATES];
|
||||
for(uint8_t i=0; i<NUM_STATES; i++)
|
||||
{
|
||||
tmp[i]=currMetrics[i];
|
||||
}
|
||||
for(uint8_t i=0; i<NUM_STATES; i++)
|
||||
{
|
||||
currMetrics[i]=prevMetrics[i];
|
||||
prevMetrics[i]=tmp[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief History chainback to obtain final byte array.
|
||||
*
|
||||
* @param out Destination byte array for decoded data.
|
||||
* @param pos Starting position for the chainback.
|
||||
* @param len Length of the output in bits.
|
||||
* @return Minimum Viterbi cost at the end of the decode sequence.
|
||||
*/
|
||||
uint32_t viterbi_chainback(uint8_t* out, size_t pos, uint16_t len)
|
||||
{
|
||||
|
||||
uint8_t state = 0;
|
||||
size_t bitPos = len+4;
|
||||
|
||||
while(pos > 0)
|
||||
{
|
||||
bitPos--;
|
||||
pos--;
|
||||
uint16_t bit = viterbi_history[pos]&((1<<(state>>4)));
|
||||
state >>= 1;
|
||||
if(bit)
|
||||
{
|
||||
state |= 0x80;
|
||||
out[bitPos/8]|=1<<(7-(bitPos%8));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cost = prevMetrics[0];
|
||||
|
||||
for(size_t i = 0; i < NUM_STATES; i++)
|
||||
{
|
||||
uint32_t m = prevMetrics[i];
|
||||
if(m < cost) cost = m;
|
||||
}
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, "\n cb: \n");
|
||||
// for (size_t i = 0; i < len; i++)
|
||||
// fprintf (stderr, "%02X", out[i]);
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the decoder state. No args.
|
||||
*
|
||||
*/
|
||||
void viterbi_reset(void)
|
||||
{
|
||||
memset(viterbi_history, 0, sizeof(viterbi_history));
|
||||
memset(currMetrics, 0, sizeof(currMetrics));
|
||||
memset(prevMetrics, 0, sizeof(prevMetrics));
|
||||
memset(currMetricsData, 0, sizeof(currMetricsData));
|
||||
memset(prevMetricsData, 0, sizeof(prevMetricsData));
|
||||
}
|
||||
|
||||
uint16_t q_abs_diff(const uint16_t v1, const uint16_t v2)
|
||||
{
|
||||
if(v2 > v1) return v2 - v1;
|
||||
return v1 - v2;
|
||||
}
|
||||
|
||||
//same as symbol levels in m17.h (m17-fme version)
|
||||
const int8_t symbol_list[4]={-3, -1, +1, +3};
|
||||
|
||||
/**
|
||||
* @brief Slice payload symbols into soft dibits.
|
||||
* Input (RRC filtered baseband sampled at symbol centers)
|
||||
* should be already normalized to {-3, -1, +1 +3}.
|
||||
* @param out Soft valued dibits (type-4).
|
||||
* @param inp Array of 184 floats (1 sample per symbol).
|
||||
*/
|
||||
void slice_symbols(uint16_t out[2*SYM_PER_PLD], const float inp[SYM_PER_PLD])
|
||||
{
|
||||
|
||||
for(uint8_t i=0; i<SYM_PER_PLD; i++)
|
||||
{
|
||||
//bit 0
|
||||
if(inp[i]>=symbol_list[3])
|
||||
{
|
||||
out[i*2+1]=0xFFFF;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[2])
|
||||
{
|
||||
out[i*2+1]=-(float)0xFFFF/(symbol_list[3]-symbol_list[2])*symbol_list[2]+inp[i]*(float)0xFFFF/(symbol_list[3]-symbol_list[2]);
|
||||
}
|
||||
else if(inp[i]>=symbol_list[1])
|
||||
{
|
||||
out[i*2+1]=0x0000;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[0])
|
||||
{
|
||||
out[i*2+1]=(float)0xFFFF/(symbol_list[1]-symbol_list[0])*symbol_list[1]-inp[i]*(float)0xFFFF/(symbol_list[1]-symbol_list[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
out[i*2+1]=0xFFFF;
|
||||
}
|
||||
|
||||
//bit 1
|
||||
if(inp[i]>=symbol_list[2])
|
||||
{
|
||||
out[i*2]=0x0000;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[1])
|
||||
{
|
||||
out[i*2]=0x7FFF-inp[i]*(float)0xFFFF/(symbol_list[2]-symbol_list[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
out[i*2]=0xFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Slice payload symbols into soft dibits.
|
||||
* Input (RRC filtered baseband sampled at symbol centers)
|
||||
* should be already normalized to {-3, -1, +1 +3}.
|
||||
* @param out Soft valued dibits (type-4).
|
||||
* @param inp Array of len floats (1 sample per symbol).
|
||||
* @param len legnth value to run this at
|
||||
*/
|
||||
void slice_symbols_to_len(uint16_t * out, const float * inp, int len)
|
||||
{
|
||||
|
||||
for(int i=0; i<len; i++)
|
||||
{
|
||||
//bit 0
|
||||
if(inp[i]>=symbol_list[3])
|
||||
{
|
||||
out[i*2+1]=0xFFFF;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[2])
|
||||
{
|
||||
out[i*2+1]=-(float)0xFFFF/(symbol_list[3]-symbol_list[2])*symbol_list[2]+inp[i]*(float)0xFFFF/(symbol_list[3]-symbol_list[2]);
|
||||
}
|
||||
else if(inp[i]>=symbol_list[1])
|
||||
{
|
||||
out[i*2+1]=0x0000;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[0])
|
||||
{
|
||||
out[i*2+1]=(float)0xFFFF/(symbol_list[1]-symbol_list[0])*symbol_list[1]-inp[i]*(float)0xFFFF/(symbol_list[1]-symbol_list[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
out[i*2+1]=0xFFFF;
|
||||
}
|
||||
|
||||
//bit 1
|
||||
if(inp[i]>=symbol_list[2])
|
||||
{
|
||||
out[i*2]=0x0000;
|
||||
}
|
||||
else if(inp[i]>=symbol_list[1])
|
||||
{
|
||||
out[i*2]=0x7FFF-inp[i]*(float)0xFFFF/(symbol_list[2]-symbol_list[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
out[i*2]=0xFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Soft logic NOT.
|
||||
*
|
||||
* @param a Input A.
|
||||
* @return uint16_t Output = not A.
|
||||
*/
|
||||
uint16_t soft_bit_NOT(const uint16_t a)
|
||||
{
|
||||
return 0xFFFFU-a;
|
||||
}
|
||||
|
||||
//randomizing pattern for M17
|
||||
const uint8_t rand_seq[46]=
|
||||
{
|
||||
0xD6, 0xB5, 0xE2, 0x30, 0x82, 0xFF, 0x84, 0x62, 0xBA, 0x4E, 0x96, 0x90, 0xD8, 0x98, 0xDD, 0x5D, 0x0C, 0xC8, 0x52, 0x43, 0x91, 0x1D, 0xF8,
|
||||
0x6E, 0x68, 0x2F, 0x35, 0xDA, 0x14, 0xEA, 0xCD, 0x76, 0x19, 0x8D, 0xD5, 0x80, 0xD1, 0x33, 0x87, 0x13, 0x57, 0x18, 0x2D, 0x29, 0x78, 0xC3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Randomize type-4 soft bits.
|
||||
*
|
||||
* @param inp Input 368 soft type-4 bits.
|
||||
*/
|
||||
void randomize_soft_bits(uint16_t inp[SYM_PER_PLD*2])
|
||||
{
|
||||
for(uint16_t i=0; i<SYM_PER_PLD*2; i++)
|
||||
{
|
||||
if((rand_seq[i/8]>>(7-(i%8)))&1) //flip bit if '1'
|
||||
{
|
||||
inp[i]=soft_bit_NOT(inp[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//interleaver pattern for M17
|
||||
const uint16_t intrl_seq[SYM_PER_PLD*2]=
|
||||
{
|
||||
0, 137, 90, 227, 180, 317, 270, 39, 360, 129, 82, 219, 172, 309, 262, 31,
|
||||
352, 121, 74, 211, 164, 301, 254, 23, 344, 113, 66, 203, 156, 293, 246, 15,
|
||||
336, 105, 58, 195, 148, 285, 238, 7, 328, 97, 50, 187, 140, 277, 230, 367,
|
||||
320, 89, 42, 179, 132, 269, 222, 359, 312, 81, 34, 171, 124, 261, 214, 351,
|
||||
304, 73, 26, 163, 116, 253, 206, 343, 296, 65, 18, 155, 108, 245, 198, 335,
|
||||
288, 57, 10, 147, 100, 237, 190, 327, 280, 49, 2, 139, 92, 229, 182, 319,
|
||||
272, 41, 362, 131, 84, 221, 174, 311, 264, 33, 354, 123, 76, 213, 166, 303,
|
||||
256, 25, 346, 115, 68, 205, 158, 295, 248, 17, 338, 107, 60, 197, 150, 287,
|
||||
240, 9, 330, 99, 52, 189, 142, 279, 232, 1, 322, 91, 44, 181, 134, 271,
|
||||
224, 361, 314, 83, 36, 173, 126, 263, 216, 353, 306, 75, 28, 165, 118, 255,
|
||||
208, 345, 298, 67, 20, 157, 110, 247, 200, 337, 290, 59, 12, 149, 102, 239,
|
||||
192, 329, 282, 51, 4, 141, 94, 231, 184, 321, 274, 43, 364, 133, 86, 223,
|
||||
176, 313, 266, 35, 356, 125, 78, 215, 168, 305, 258, 27, 348, 117, 70, 207,
|
||||
160, 297, 250, 19, 340, 109, 62, 199, 152, 289, 242, 11, 332, 101, 54, 191,
|
||||
144, 281, 234, 3, 324, 93, 46, 183, 136, 273, 226, 363, 316, 85, 38, 175,
|
||||
128, 265, 218, 355, 308, 77, 30, 167, 120, 257, 210, 347, 300, 69, 22, 159,
|
||||
112, 249, 202, 339, 292, 61, 14, 151, 104, 241, 194, 331, 284, 53, 6, 143,
|
||||
96, 233, 186, 323, 276, 45, 366, 135, 88, 225, 178, 315, 268, 37, 358, 127,
|
||||
80, 217, 170, 307, 260, 29, 350, 119, 72, 209, 162, 299, 252, 21, 342, 111,
|
||||
64, 201, 154, 291, 244, 13, 334, 103, 56, 193, 146, 283, 236, 5, 326, 95,
|
||||
48, 185, 138, 275, 228, 365, 318, 87, 40, 177, 130, 267, 220, 357, 310, 79,
|
||||
32, 169, 122, 259, 212, 349, 302, 71, 24, 161, 114, 251, 204, 341, 294, 63,
|
||||
16, 153, 106, 243, 196, 333, 286, 55, 8, 145, 98, 235, 188, 325, 278, 47
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Reorder (interleave) 368 soft bits.
|
||||
*
|
||||
* @param outp Reordered soft bits.
|
||||
* @param inp Input soft bits.
|
||||
*/
|
||||
void reorder_soft_bits(uint16_t outp[SYM_PER_PLD*2], const uint16_t inp[SYM_PER_PLD*2])
|
||||
{
|
||||
for(uint16_t i=0; i<SYM_PER_PLD*2; i++)
|
||||
outp[i]=inp[intrl_seq[i]];
|
||||
}
|
||||
|
||||
//NXDN all-in-one function to go from punctured bits to valid output, return viterbi error
|
||||
uint32_t nxdn_soft_decision_viterbi(uint8_t * bits, const uint16_t * interleave, uint8_t * puncture, int d_len, int p_len, int num_bytes, int offset, uint8_t * viterbi_bits, uint8_t * viterbi_bytes)
|
||||
{
|
||||
uint32_t error = 0;
|
||||
uint16_t soft_bit[1000];
|
||||
uint8_t temp_bits[500];
|
||||
memset(temp_bits, 0, sizeof(temp_bits));
|
||||
memset(soft_bit, 0, sizeof(soft_bit));
|
||||
|
||||
//deinterleaved bits
|
||||
uint8_t deinterleaved_bits[500];
|
||||
memset(deinterleaved_bits, 0, sizeof(deinterleaved_bits));
|
||||
|
||||
//deinterleaving the bits
|
||||
for (int i = 0; i < d_len; i++)
|
||||
deinterleaved_bits[interleave[i]] = bits[i];
|
||||
|
||||
//converting back to dibits
|
||||
uint8_t dbuf[500];
|
||||
memset(dbuf, 0, sizeof(dbuf));
|
||||
for (int i = 0; i < d_len/2; i++)
|
||||
dbuf[i] = (deinterleaved_bits[(i*2)+0] << 1) | deinterleaved_bits[(i*2)+1];
|
||||
|
||||
float sbuf[500];
|
||||
memset(sbuf, 0.0f, sizeof(sbuf));
|
||||
|
||||
//convert dbuf into a symbol array
|
||||
for (int i = 0; i < d_len/2; i++)
|
||||
{
|
||||
if (dbuf[i] == 0) sbuf[i] = +1.0f;
|
||||
else if (dbuf[i] == 1) sbuf[i] = +3.0f;
|
||||
else if (dbuf[i] == 2) sbuf[i] = -1.0f;
|
||||
else if (dbuf[i] == 3) sbuf[i] = -3.0f;
|
||||
else sbuf[i] = +0.0f;
|
||||
}
|
||||
|
||||
//slice symbols to soft dibits
|
||||
slice_symbols_to_len(soft_bit, sbuf, d_len/2);
|
||||
|
||||
//viterbi
|
||||
error = viterbi_decode_punctured(viterbi_bytes, soft_bit, puncture, d_len, p_len);
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, " Ve: %1.1f; ", (float)error/(float)0xFFFF);
|
||||
|
||||
//debug
|
||||
// for (int i = 0; i < num_bytes+1; i++)
|
||||
// fprintf(stderr, "%02X", viterbi_bytes[i]);
|
||||
|
||||
//load viterbi_bytes into bits
|
||||
unpack_byte_array_into_bit_array(viterbi_bytes, temp_bits, num_bytes+1);
|
||||
|
||||
//rearrange bits to get correct offset
|
||||
for (int i = 0; i < num_bytes*8; i++)
|
||||
viterbi_bits[i] = temp_bits[i+offset];
|
||||
|
||||
//zero out viterbi_bytes to reload packed appropriately
|
||||
for (int i = 0; i < num_bytes; i++)
|
||||
viterbi_bytes[i] = 0;
|
||||
|
||||
pack_bit_array_into_byte_array(viterbi_bits, viterbi_bytes, num_bytes);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
//ysf fake puncture
|
||||
uint8_t ysf_puncture[4] = {1,1,1,1};
|
||||
|
||||
//YSF all-in-one function to go from dibits to valid output, return viterbi error
|
||||
uint32_t ysf_soft_decision_viterbi(uint8_t * dbuf, int d_len, int num_bytes, int offset, uint8_t * viterbi_bits, uint8_t * viterbi_bytes)
|
||||
{
|
||||
uint32_t error = 0;
|
||||
uint16_t soft_bit[1000];
|
||||
uint8_t temp_bits[500];
|
||||
memset(temp_bits, 0, sizeof(temp_bits));
|
||||
memset(soft_bit, 0, sizeof(soft_bit));
|
||||
|
||||
float sbuf[500];
|
||||
memset(sbuf, 0.0f, sizeof(sbuf));
|
||||
|
||||
//convert dbuf into a symbol array
|
||||
for (int i = 0; i < d_len/2; i++)
|
||||
{
|
||||
if (dbuf[i] == 0) sbuf[i] = +1.0f;
|
||||
else if (dbuf[i] == 1) sbuf[i] = +3.0f;
|
||||
else if (dbuf[i] == 2) sbuf[i] = -1.0f;
|
||||
else if (dbuf[i] == 3) sbuf[i] = -3.0f;
|
||||
else sbuf[i] = +0.0f;
|
||||
}
|
||||
|
||||
//slice symbols to soft dibits
|
||||
slice_symbols_to_len(soft_bit, sbuf, d_len/2);
|
||||
|
||||
//viterbi
|
||||
error = viterbi_decode_punctured(viterbi_bytes, soft_bit, ysf_puncture, d_len, 4);
|
||||
|
||||
//debug
|
||||
// fprintf (stderr, " Ve: %1.1f; ", (float)error/(float)0xFFFF);
|
||||
|
||||
//debug
|
||||
// for (int i = 0; i < num_bytes+1; i++)
|
||||
// fprintf(stderr, "%02X", viterbi_bytes[i]);
|
||||
|
||||
//load viterbi_bytes into bits
|
||||
unpack_byte_array_into_bit_array(viterbi_bytes, temp_bits, num_bytes+1);
|
||||
|
||||
//rearrange bits to get correct offset
|
||||
for (int i = 0; i < num_bytes*8; i++)
|
||||
viterbi_bits[i] = temp_bits[i+offset];
|
||||
|
||||
//zero out viterbi_bytes to reload packed appropriately
|
||||
for (int i = 0; i < num_bytes; i++)
|
||||
viterbi_bytes[i] = 0;
|
||||
|
||||
pack_bit_array_into_byte_array(viterbi_bits, viterbi_bytes, num_bytes);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
//original sources for viterbi code and misc math related
|
||||
//--------------------------------------------------------------------
|
||||
// M17 C library - decode/viterbi.c
|
||||
//
|
||||
// This file contains:
|
||||
// - the Viterbi decoder
|
||||
//
|
||||
// Wojciech Kaczmarski, SP5WWP
|
||||
// M17 Project, 29 December 2023
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// M17 C library - math/math.c
|
||||
//
|
||||
// This file contains:
|
||||
// - absolute difference value
|
||||
// - Euclidean norm (L2) calculation for n-dimensional vectors (float)
|
||||
// - soft-valued arrays to integer conversion (and vice-versa)
|
||||
// - fixed-valued multiplication and division
|
||||
//
|
||||
// Wojciech Kaczmarski, SP5WWP
|
||||
// M17 Project, 29 December 2023
|
||||
//--------------------------------------------------------------------
|
||||
205
src/ysf.c
205
src/ysf.c
@@ -228,7 +228,12 @@ void ysf_dch_decode (dsd_state * state, uint8_t bn, uint8_t bt, uint8_t fn, uint
|
||||
}
|
||||
|
||||
if (fn == ft) //last frame
|
||||
fprintf (stderr, " %s", state->event_history_s[0].Event_History_Items[0].text_message);
|
||||
{
|
||||
char text_string[15]; memset (text_string, 0, sizeof(text_string));
|
||||
sprintf (text_string, "%s", "BUMBLEBEETUNA");
|
||||
if (strncmp(text_string, state->event_history_s[0].Event_History_Items[0].event_string, 13) != 0)
|
||||
fprintf (stderr, " %s", state->event_history_s[0].Event_History_Items[0].text_message);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -377,15 +382,14 @@ static inline uint16_t crc16ysf(const uint8_t buf[], int len)
|
||||
int ysf_conv_dch2 (dsd_opts * opts, dsd_state * state, uint8_t bn, uint8_t bt, uint8_t fn, uint8_t ft, uint8_t cm, uint8_t input[])
|
||||
{
|
||||
|
||||
int i, j, k, err;
|
||||
uint8_t s0, s1;
|
||||
uint8_t trellis_buf[100];
|
||||
int i, j, err;
|
||||
uint8_t viterbi_bits[100];
|
||||
uint8_t temp[210];
|
||||
uint8_t m_data[100];
|
||||
uint8_t viterbi_bytes[100];
|
||||
uint8_t bits[210];
|
||||
memset (trellis_buf, 0, sizeof(trellis_buf));
|
||||
memset (viterbi_bits, 0, sizeof(viterbi_bits));
|
||||
memset (temp, 0, sizeof (temp));
|
||||
memset (m_data, 0, sizeof (m_data));
|
||||
memset (viterbi_bytes, 0, sizeof (viterbi_bytes));
|
||||
memset (bits, 0, sizeof(bits));
|
||||
err = 0;
|
||||
|
||||
@@ -398,56 +402,27 @@ int ysf_conv_dch2 (dsd_opts * opts, dsd_state * state, uint8_t bn, uint8_t bt, u
|
||||
}
|
||||
}
|
||||
|
||||
k = 0;
|
||||
//convert dibits to bits
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
bits[k++] = (buf[i] >> 1) & 1;
|
||||
bits[k++] = (buf[i] >> 0) & 1;
|
||||
}
|
||||
uint32_t v_error =
|
||||
ysf_soft_decision_viterbi(buf, 200, 13, 8, viterbi_bits, viterbi_bytes);
|
||||
|
||||
//setup for the convolutional decoder
|
||||
for (i = 0; i < 200; i++)
|
||||
temp[i] = bits[i] << 1;
|
||||
//debug
|
||||
if (opts->payload == 1)
|
||||
fprintf (stderr, " D2-Ve: %1.1f; ", (float)v_error/(float)0xFFFF);
|
||||
|
||||
CNXDNConvolution_start();
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
s0 = temp[(2*i)+0];
|
||||
s1 = temp[(2*i)+1];
|
||||
|
||||
CNXDNConvolution_decode(s0, s1);
|
||||
}
|
||||
|
||||
CNXDNConvolution_chainback(m_data, 96);
|
||||
|
||||
//96/8 = 12, last 4 (96-100) are trailing zeroes
|
||||
for(i = 0; i < 12; i++)
|
||||
{
|
||||
trellis_buf[(i*8)+0] = (m_data[i] >> 7) & 1;
|
||||
trellis_buf[(i*8)+1] = (m_data[i] >> 6) & 1;
|
||||
trellis_buf[(i*8)+2] = (m_data[i] >> 5) & 1;
|
||||
trellis_buf[(i*8)+3] = (m_data[i] >> 4) & 1;
|
||||
trellis_buf[(i*8)+4] = (m_data[i] >> 3) & 1;
|
||||
trellis_buf[(i*8)+5] = (m_data[i] >> 2) & 1;
|
||||
trellis_buf[(i*8)+6] = (m_data[i] >> 1) & 1;
|
||||
trellis_buf[(i*8)+7] = (m_data[i] >> 0) & 1;
|
||||
}
|
||||
|
||||
uint16_t crc = crc16ysf(trellis_buf, 96);
|
||||
uint16_t crc = crc16ysf(viterbi_bits, 96);
|
||||
if (crc != 0) err = -2; // crc failure
|
||||
|
||||
for (i = 0; i < 80; i++)
|
||||
trellis_buf[i] = trellis_buf[i] ^ pn95[i];
|
||||
viterbi_bits[i] = viterbi_bits[i] ^ pn95[i];
|
||||
|
||||
//reload after de-whitening
|
||||
memset (m_data, 0, sizeof (m_data));
|
||||
memset (viterbi_bytes, 0, sizeof (viterbi_bytes));
|
||||
for (i = 0; i < 12; i++)
|
||||
m_data[i] = (uint8_t)ConvertBitIntoBytes(&trellis_buf[i*8], 8);
|
||||
viterbi_bytes[i] = (uint8_t)ConvertBitIntoBytes(&viterbi_bits[i*8], 8);
|
||||
|
||||
//decode the callsign, etc, found in the DCH when no errors
|
||||
if (err == 0)
|
||||
ysf_dch_decode2 (state, bn, bt, fn, ft, cm, trellis_buf);
|
||||
ysf_dch_decode2 (state, bn, bt, fn, ft, cm, viterbi_bits);
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "%s", KRED);
|
||||
@@ -460,25 +435,24 @@ int ysf_conv_dch2 (dsd_opts * opts, dsd_state * state, uint8_t bn, uint8_t bt, u
|
||||
fprintf (stderr, "\n ");
|
||||
fprintf (stderr, "DCH2: ");
|
||||
for (i = 0; i < 12; i++)
|
||||
fprintf (stderr, "[%02X]", m_data[i]);
|
||||
fprintf (stderr, "[%02X]", viterbi_bytes[i]);
|
||||
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
//modified version of nxdn_deperm_facch1 -- this one for Full Rate, Type 1 CC, Headers and Terminators DCH (180 dibit version)
|
||||
//Soft Decision Full Rate, Type 1 CC, Headers and Terminators DCH (180 dibit version)
|
||||
int ysf_conv_dch (dsd_opts * opts, dsd_state * state, uint8_t bn, uint8_t bt, uint8_t fn, uint8_t ft, uint8_t cm, uint8_t input[])
|
||||
{
|
||||
int i, j, k, err;
|
||||
uint8_t s0, s1;
|
||||
uint8_t trellis_buf[190];
|
||||
int i, j, err;
|
||||
uint8_t viterbi_bits[190];
|
||||
uint8_t temp[370];
|
||||
uint8_t m_data[100];
|
||||
uint8_t viterbi_bytes[100];
|
||||
uint8_t bits[370];
|
||||
memset (trellis_buf, 0, sizeof(trellis_buf));
|
||||
memset (viterbi_bits, 0, sizeof(viterbi_bits));
|
||||
memset (temp, 0, sizeof (temp));
|
||||
memset (m_data, 0, sizeof (m_data));
|
||||
memset (viterbi_bytes, 0, sizeof (viterbi_bytes));
|
||||
memset (bits, 0, sizeof(bits));
|
||||
err = 0;
|
||||
|
||||
@@ -491,56 +465,27 @@ int ysf_conv_dch (dsd_opts * opts, dsd_state * state, uint8_t bn, uint8_t bt, ui
|
||||
}
|
||||
}
|
||||
|
||||
k = 0;
|
||||
//convert dibits to bits
|
||||
for (i = 0; i < 180; i++)
|
||||
{
|
||||
bits[k++] = (buf[i] >> 1) & 1;
|
||||
bits[k++] = (buf[i] >> 0) & 1;
|
||||
}
|
||||
uint32_t v_error =
|
||||
ysf_soft_decision_viterbi(buf, 360, 23, 8, viterbi_bits, viterbi_bytes);
|
||||
|
||||
//setup for the convolutional decoder
|
||||
for (i = 0; i < 360; i++)
|
||||
temp[i] = bits[i] << 1;
|
||||
//debug
|
||||
if (opts->payload == 1)
|
||||
fprintf (stderr, " D1-Ve: %1.1f; ", (float)v_error/(float)0xFFFF);
|
||||
|
||||
CNXDNConvolution_start();
|
||||
for (i = 0; i < 180; i++)
|
||||
{
|
||||
s0 = temp[(2*i)+0];
|
||||
s1 = temp[(2*i)+1];
|
||||
|
||||
CNXDNConvolution_decode(s0, s1);
|
||||
}
|
||||
|
||||
CNXDNConvolution_chainback(m_data, 176);
|
||||
|
||||
//176/8 = 22, last 4 (176-180) are trailing zeroes
|
||||
for(i = 0; i < 22; i++)
|
||||
{
|
||||
trellis_buf[(i*8)+0] = (m_data[i] >> 7) & 1;
|
||||
trellis_buf[(i*8)+1] = (m_data[i] >> 6) & 1;
|
||||
trellis_buf[(i*8)+2] = (m_data[i] >> 5) & 1;
|
||||
trellis_buf[(i*8)+3] = (m_data[i] >> 4) & 1;
|
||||
trellis_buf[(i*8)+4] = (m_data[i] >> 3) & 1;
|
||||
trellis_buf[(i*8)+5] = (m_data[i] >> 2) & 1;
|
||||
trellis_buf[(i*8)+6] = (m_data[i] >> 1) & 1;
|
||||
trellis_buf[(i*8)+7] = (m_data[i] >> 0) & 1;
|
||||
}
|
||||
|
||||
uint16_t crc = crc16ysf(trellis_buf, 176);
|
||||
uint16_t crc = crc16ysf(viterbi_bits, 176);
|
||||
if (crc != 0) err = -2; // crc failure
|
||||
|
||||
for (i = 0; i < 160; i++)
|
||||
trellis_buf[i] = trellis_buf[i] ^ pn95[i];
|
||||
viterbi_bits[i] = viterbi_bits[i] ^ pn95[i];
|
||||
|
||||
//reload after de-whitening
|
||||
memset (m_data, 0, sizeof (m_data));
|
||||
memset (viterbi_bytes, 0, sizeof (viterbi_bytes));
|
||||
for (i = 0; i < 22; i++)
|
||||
m_data[i] = (uint8_t)ConvertBitIntoBytes(&trellis_buf[i*8], 8);
|
||||
viterbi_bytes[i] = (uint8_t)ConvertBitIntoBytes(&viterbi_bits[i*8], 8);
|
||||
|
||||
//decode the callsign, etc, found in the DCH when no errors
|
||||
if (err == 0)
|
||||
ysf_dch_decode (state, bn, bt, fn, ft, cm, trellis_buf);
|
||||
ysf_dch_decode (state, bn, bt, fn, ft, cm, viterbi_bits);
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "%s", KRED);
|
||||
@@ -553,25 +498,24 @@ int ysf_conv_dch (dsd_opts * opts, dsd_state * state, uint8_t bn, uint8_t bt, ui
|
||||
fprintf (stderr, "\n ");
|
||||
fprintf (stderr, "DCH1: ");
|
||||
for (i = 0; i < 22; i++)
|
||||
fprintf (stderr, "[%02X]", m_data[i]);
|
||||
fprintf (stderr, "[%02X]", viterbi_bytes[i]);
|
||||
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
//modified version of nxdn_deperm_facch1
|
||||
int ysf_conv_fich (uint8_t input[], uint8_t dest[32])
|
||||
//FICH Soft Decision Viterbi
|
||||
int ysf_conv_fich (dsd_opts * opts, uint8_t input[], uint8_t dest[32])
|
||||
{
|
||||
int i, j, k, err;
|
||||
uint8_t s0, s1;
|
||||
uint8_t trellis_buf[100];
|
||||
int i, j, err;
|
||||
uint8_t viterbi_bits[100];
|
||||
uint8_t temp[210];
|
||||
uint8_t m_data[100];
|
||||
uint8_t viterbi_bytes[100];
|
||||
uint8_t bits[210];
|
||||
memset (trellis_buf, 0, sizeof(trellis_buf));
|
||||
memset (viterbi_bits, 0, sizeof(viterbi_bits));
|
||||
memset (temp, 0, sizeof (temp));
|
||||
memset (m_data, 0, sizeof (m_data));
|
||||
memset (viterbi_bytes, 0, sizeof (viterbi_bytes));
|
||||
memset (bits, 0, sizeof(bits));
|
||||
err = 0;
|
||||
|
||||
@@ -584,41 +528,12 @@ int ysf_conv_fich (uint8_t input[], uint8_t dest[32])
|
||||
}
|
||||
}
|
||||
|
||||
k = 0;
|
||||
//convert dibits to bits
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
bits[k++] = (buf[i] >> 1) & 1;
|
||||
bits[k++] = (buf[i] >> 0) & 1;
|
||||
}
|
||||
uint32_t v_error =
|
||||
ysf_soft_decision_viterbi(buf, 200, 13, 8, viterbi_bits, viterbi_bytes);
|
||||
|
||||
//setup for the convolutional decoder
|
||||
for (i = 0; i < 200; i++) //192
|
||||
temp[i] = bits[i] << 1;
|
||||
|
||||
CNXDNConvolution_start();
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
s0 = temp[(2*i)+0];
|
||||
s1 = temp[(2*i)+1];
|
||||
|
||||
CNXDNConvolution_decode(s0, s1);
|
||||
}
|
||||
|
||||
CNXDNConvolution_chainback(m_data, 96);
|
||||
|
||||
//96/8 = 12, last 4 (96-100) are trailing zeroes
|
||||
for(i = 0; i < 12; i++)
|
||||
{
|
||||
trellis_buf[(i*8)+0] = (m_data[i] >> 7) & 1;
|
||||
trellis_buf[(i*8)+1] = (m_data[i] >> 6) & 1;
|
||||
trellis_buf[(i*8)+2] = (m_data[i] >> 5) & 1;
|
||||
trellis_buf[(i*8)+3] = (m_data[i] >> 4) & 1;
|
||||
trellis_buf[(i*8)+4] = (m_data[i] >> 3) & 1;
|
||||
trellis_buf[(i*8)+5] = (m_data[i] >> 2) & 1;
|
||||
trellis_buf[(i*8)+6] = (m_data[i] >> 1) & 1;
|
||||
trellis_buf[(i*8)+7] = (m_data[i] >> 0) & 1;
|
||||
}
|
||||
//debug
|
||||
if (opts->payload == 1)
|
||||
fprintf (stderr, " F-Ve: %1.1f; ", (float)v_error/(float)0xFFFF);
|
||||
|
||||
uint8_t fich_bits[12*4];
|
||||
uint8_t temp_b[24];
|
||||
@@ -631,31 +546,27 @@ int ysf_conv_fich (uint8_t input[], uint8_t dest[32])
|
||||
g[i] = FALSE;
|
||||
|
||||
for (j = 0; j < 24; j++)
|
||||
temp_b[j] = (char) trellis_buf[(i*24)+j];
|
||||
temp_b[j] = (char) viterbi_bits[(i*24)+j];
|
||||
|
||||
g[i] = Golay_24_12_decode(temp_b);
|
||||
if(g[i] == FALSE) err = -1;
|
||||
|
||||
for (j = 0; j < 24; j++)
|
||||
trellis_buf[(i*24)+j] = (uint8_t) temp_b[j];
|
||||
viterbi_bits[(i*24)+j] = (uint8_t) temp_b[j];
|
||||
}
|
||||
|
||||
//load corrected bits
|
||||
for (i = 0; i < 12; i++)
|
||||
{
|
||||
fich_bits[(12*0)+i] = trellis_buf[i+0];
|
||||
fich_bits[(12*1)+i] = trellis_buf[i+24];
|
||||
fich_bits[(12*2)+i] = trellis_buf[i+48];
|
||||
fich_bits[(12*3)+i] = trellis_buf[i+72];
|
||||
fich_bits[(12*0)+i] = viterbi_bits[i+0];
|
||||
fich_bits[(12*1)+i] = viterbi_bits[i+24];
|
||||
fich_bits[(12*2)+i] = viterbi_bits[i+48];
|
||||
fich_bits[(12*3)+i] = viterbi_bits[i+72];
|
||||
}
|
||||
|
||||
uint16_t crc = crc16ysf(fich_bits, 48);
|
||||
if (crc != 0) err = -2; // crc failure
|
||||
|
||||
memset (m_data, 0, sizeof (m_data));
|
||||
for (i = 0; i < 12; i++)
|
||||
m_data[i] = (uint8_t)ConvertBitIntoBytes(&trellis_buf[i*8], 8);
|
||||
|
||||
memcpy(dest, fich_bits, 32); //copy minus the crc16
|
||||
return err;
|
||||
}
|
||||
@@ -808,7 +719,7 @@ void processYSF(dsd_opts * opts, dsd_state * state)
|
||||
fichrawdibits[i] = getDibit(opts, state);
|
||||
|
||||
//from nxdn_deperm_facch1 w/ nxdn convolutional decoder
|
||||
err = ysf_conv_fich (fichrawdibits, fich_decode);
|
||||
err = ysf_conv_fich (opts, fichrawdibits, fich_decode);
|
||||
|
||||
//if errors decoding fich, then just treat it like the last frame that came in
|
||||
if (err == 0)
|
||||
|
||||
Reference in New Issue
Block a user