M17 Support (Partially Working Now);

This commit is contained in:
lwvmobile 2023-07-06 21:04:28 -04:00
parent b6d41260e7
commit 92a2217e80
12 changed files with 554 additions and 67 deletions

View File

@ -68,6 +68,7 @@ find_package(ITPP REQUIRED)
find_package(RTLSDR)
find_package(Curses REQUIRED)
find_package(PulseAudio REQUIRED)
find_package(CODEC2)
include_directories(SYSTEM ${LIBSNDFILE_INCLUDE_DIR} ${MBE_INCLUDE_DIR} ${ITPP_INCLUDE_DIR} ${PULSEAUDIO_INCLUDE_DIRS} ${CURSES_INCLUDE_DIR})
set(LIBS ${MBE_LIBRARY} ${LIBSNDFILE_LIBRARY} ${ITPP_LIBRARY} ${PULSEAUDIO_SIMPLE_LIBRARY} ${CURSES_LIBRARY})
@ -79,6 +80,12 @@ if(RTLSDR_FOUND)
add_definitions(-DUSE_RTLSDR)
endif(RTLSDR_FOUND)
if(CODEC2_FOUND)
include_directories(SYSTEM ${CODEC2_INCLUDE_DIRS})
list(APPEND LIBS ${CODEC2_LIBRARIES})
add_definitions(-DUSE_CODEC2)
endif(CODEC2_FOUND)
FILE(GLOB SRCS src/*.c src/*.cpp)
FILE(GLOB HEADERS include/*.h include/*.hpp)

27
cmake/FindCODEC2.cmake Normal file
View File

@ -0,0 +1,27 @@
# - Try to find CODEC2
# Once done this will define
#seems to work now, will want to test on multiple platforms
#I just copied RTLSDR and modified it
if (NOT CODEC2_FOUND)
find_package(PkgConfig)
pkg_check_modules(CODEC2_PKG codec2)
set(CODEC2_DEFINITIONS ${PC_CODEC2_CFLAGS_OTHER})
find_path(CODEC2_INCLUDE_DIR
NAMES codec2.h
HINTS ${CODEC2_PKG_INCLUDE_DIRS})
find_library(CODEC2_LIBRARY
NAMES codec2
HINTS ${CODEC2_PKG_LIBRARY_DIRS})
set(CODEC2_LIBRARIES ${CODEC2_LIBRARY})
set(CODEC2_INCLUDE_DIRS ${CODEC2_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CODEC2 DEFAULT_MSG CODEC2_LIBRARY CODEC2_INCLUDE_DIR)
mark_as_advanced(CODEC2_INCLUDE_DIR CODEC2_LIBRARY)
endif (NOT CODEC2_FOUND)

View File

@ -13,7 +13,7 @@ Y='y'
if [[ $Y == $ANSWER ]]; then
sudo apt update
sudo apt install libpulse-dev pavucontrol libsndfile1-dev libfftw3-dev liblapack-dev socat libusb-1.0-0-dev libncurses5 libncurses5-dev rtl-sdr librtlsdr-dev libusb-1.0-0-dev cmake git wget make build-essential libitpp-dev libncursesw5-dev
sudo apt install libpulse-dev pavucontrol libsndfile1-dev libfftw3-dev liblapack-dev socat libusb-1.0-0-dev libncurses5 libncurses5-dev rtl-sdr librtlsdr-dev libusb-1.0-0-dev cmake git wget make build-essential libitpp-dev libncursesw5-dev libcodec2-dev
echo ITPP Manual Build and Install has been temporarily removed from this script due to issues with older versions of Ubuntu.
echo Please check to see that libitpp-dev has successfully been downloaded and installed from the repo.

View File

@ -28,20 +28,20 @@ Debian/Mint/Ubuntu/Pi
```
sudo apt update
sudo apt install libpulse-dev pavucontrol libsndfile1-dev libfftw3-dev liblapack-dev socat libusb-1.0-0-dev libncurses5 libncurses5-dev rtl-sdr librtlsdr-dev libusb-1.0-0-dev cmake git wget make build-essential libitpp-dev libncursesw5-dev
sudo apt install libpulse-dev pavucontrol libsndfile1-dev libfftw3-dev liblapack-dev socat libusb-1.0-0-dev libncurses5 libncurses5-dev rtl-sdr librtlsdr-dev libusb-1.0-0-dev cmake git wget make build-essential libitpp-dev libncursesw5-dev libcodec2-dev
```
Fedora 36/37 -- from https://github.com/lwvmobile/dsd-fme/issues/99
Fedora (Recent Releases) -- from https://github.com/lwvmobile/dsd-fme/issues/99
```
sudo dnf install libsndfile-devel fftw-devel lapack-devel rtl-sdr-devel pulseaudio-libs-devel libusb-devel cmake git ncurses ncurses-devel gcc wget pavucontrol gcc-c++
sudo dnf install libsndfile-devel fftw-devel lapack-devel rtl-sdr-devel pulseaudio-libs-devel libusb-devel cmake git ncurses ncurses-devel gcc wget pavucontrol gcc-c++ codec2-devel
```
## Headless Ubuntu Server/Pi
If running headless, swap out pavucontrol for pulsemixer, and also install pulseaudio as well. Attempting to install pavucontrol in a headless environment may attempt to install a minimal desktop environment. Note: Default behavior of pulseaudio in a headless environment may be to be muted, so check by opening pulsemixer and unmuting and routing audio appropriately.
```
sudo apt install libpulse-dev libsndfile1-dev libfftw3-dev liblapack-dev socat libusb-1.0-0-dev libncurses5 libncurses5-dev rtl-sdr librtlsdr-dev libusb-1.0-0-dev cmake git wget make build-essential libitpp-dev libncursesw5-dev pulsemixer pulseaudio
sudo apt install libpulse-dev libsndfile1-dev libfftw3-dev liblapack-dev socat libusb-1.0-0-dev libncurses5 libncurses5-dev rtl-sdr librtlsdr-dev libusb-1.0-0-dev cmake git wget make build-essential libitpp-dev libncursesw5-dev pulsemixer pulseaudio libcodec2-dev
```
### Build and Install ITPP - ONLY IF NOT IN REPO!!

View File

@ -80,6 +80,10 @@
#define UNUSED4(x1, x2, x3, x4) (UNUSED(x1), UNUSED(x2), UNUSED(x3), UNUSED(x4))
#define UNUSED5(x1, x2, x3, x4, x5) (UNUSED(x1), UNUSED(x2), UNUSED(x3), UNUSED(x4), UNUSED(x5))
#ifdef USE_CODEC2
#include <codec2/codec2.h>
#endif
extern volatile uint8_t exitflag; //fix for issue #136
//group csv import struct
@ -729,8 +733,14 @@ typedef struct
char ysf_txt[21][21]; //text storage blocks
//M17 Storage
uint8_t m17_lsf_err[6]; //golay segment FEC validity
uint8_t m17_lsf[280];
uint8_t m17_lsf[360];
uint8_t m17_str_dt; //stream contents
//Codec2
#ifdef USE_CODEC2
struct CODEC2 *codec2_3200; //M17 fullrate
struct CODEC2 *codec2_1600; //M17 halfrate
#endif
} dsd_state;
@ -739,9 +749,8 @@ typedef struct
*/
//The inverse of LSF also seems to be Stream Frame Type, only going to work on Stream mode for now
// #define M17_LSF_PRE "1311113313"
#define M17_LSF "11113313" //"11113313"
#define M17_STR "33331131" //"33331131"
#define M17_LSF "11113313"
#define M17_STR "33331131"
// #define M17_BRT "31331111"
// #define M17_PKT "13113333"
@ -918,6 +927,7 @@ void processMPDU(dsd_opts * opts, dsd_state * state); //P25 Multi Block PDU (SAP
short dmr_filter(short sample);
short nxdn_filter(short sample);
short dpmr_filter(short sample);
short m17_filter(short sample);
int strncmperr(const char *s1, const char *s2, size_t size, int MaxErr);
uint64_t ConvertBitIntoBytes(uint8_t * BufferIn, uint32_t BitLength);

View File

@ -266,8 +266,9 @@ static int digitize (dsd_opts* opts, dsd_state* state, int symbol)
}
else if ((state->synctype == 1) || (state->synctype == 3) || (state->synctype == 5) ||
(state->synctype == 31) || (state->synctype == 11) || (state->synctype == 13) ||
(state->synctype == 17) || (state->synctype == 29) || (state->synctype == 36) || (state->synctype == 16) )
(state->synctype == 9) || (state->synctype == 11) || (state->synctype == 13) ||
(state->synctype == 17) || (state->synctype == 29) || (state->synctype == 31) ||
(state->synctype == 36) )
{
// 1 -P25p1

View File

@ -1,8 +1,36 @@
//M17 Filter -- RRC Alpha = 0.5 S/s 48000 with 81 taps
#define M17ZEROS 80 //ONLY SEE 79 THOUGH
float m17gain = 10.0f;
static float m17xv[M17ZEROS+1];
float m17coeffs[81] =
{
-0.009265784007800534, -0.006136551625729697, -0.001125978562075172, 0.004891777252042491,
0.01071805138282269, 0.01505751553351295, 0.01679337935001369, 0.015256245142156299,
0.01042830577908502, 0.003031522725559901, -0.0055333532968188165, -0.013403099825723372,
-0.018598682349642525, -0.01944761739590459, -0.015005271935951746, -0.0053887880354343935,
0.008056525910253532, 0.022816244158307273, 0.035513467692208076, 0.04244131815783876,
0.04025481153629372, 0.02671818654865632, 0.0013810216516704976, -0.03394615682795165,
-0.07502635967975885, -0.11540977897637611, -0.14703962203941534, -0.16119995609538576,
-0.14969512896336504, -0.10610329539459686, -0.026921412469634916, 0.08757875030779196,
0.23293327870303457, 0.4006012210123992, 0.5786324696325503, 0.7528286479934068,
0.908262741447522, 1.0309661131633199, 1.1095611856548013, 1.1366197723675815,
1.1095611856548013, 1.0309661131633199, 0.908262741447522, 0.7528286479934068,
0.5786324696325503, 0.4006012210123992, 0.23293327870303457, 0.08757875030779196,
-0.026921412469634916, -0.10610329539459686, -0.14969512896336504, -0.16119995609538576,
-0.14703962203941534, -0.11540977897637611, -0.07502635967975885, -0.03394615682795165,
0.0013810216516704976, 0.02671818654865632, 0.04025481153629372, 0.04244131815783876,
0.035513467692208076, 0.022816244158307273, 0.008056525910253532, -0.0053887880354343935,
-0.015005271935951746, -0.01944761739590459, -0.018598682349642525, -0.013403099825723372,
-0.0055333532968188165, 0.003031522725559901, 0.01042830577908502, 0.015256245142156299,
0.01679337935001369, 0.01505751553351295, 0.01071805138282269, 0.004891777252042491,
-0.001125978562075172, -0.006136551625729697, -0.009265784007800534
};
// Original DMR filter
#define NZEROS 60
float ngain = 7.423339364f;
static float xv[NZEROS+1];
float xcoeffs[] =
float xcoeffs[61] =
{ -0.0083649323f, -0.0265444850f, -0.0428141462f, -0.0537571943f,
-0.0564141052f, -0.0489161045f, -0.0310068662f, -0.0043393881f,
+0.0275375106f, +0.0595423283f, +0.0857543325f, +0.1003565948f,
@ -27,7 +55,7 @@ float xcoeffs[] =
float nxgain = 15.95930463f;
static float nxv[NXZEROS+1];
float nxcoeffs[] =
float nxcoeffs[135] =
{ +0.031462429f, +0.031747267f, +0.030401148f, +0.027362877f,
+0.022653298f, +0.016379869f, +0.008737200f, +0.000003302f,
-0.009468531f, -0.019262057f, -0.028914291f, -0.037935027f,
@ -74,7 +102,7 @@ const float dmrgain = 6.82973073748f;
static float dxv[DMRNZEROS+1];
// DMR filter F4EXB - root raised cosine alpha=0.7 Ts = 6650 S/s Fc = 48kHz
const float dmrcoeffs[] =
const float dmrcoeffs[61] =
{0.0301506278, 0.0269200615, 0.0159662432, -0.0013114705, -0.0216605133,
-0.0404938748, -0.0528141756, -0.0543747957, -0.0428325003, -0.0186176083,
0.0147202645, 0.0508418571, 0.0816392577, 0.0988113688, 0.0957187780,
@ -98,7 +126,7 @@ static float dpmrxv[NXZEROS+1];
const float dpmrgain = 14.6083498224f;
// dPMR filter - root raised cosine alpha=0.2 Ts = 3325 S/s Fc = 48 kHz - zero at boundaries appears to be slightly better
const float dpmrcoeffs[] =
const float dpmrcoeffs[135] =
{-0.0000983004, 0.0058388841, 0.0119748846, 0.0179185547, 0.0232592816,
0.0275919612, 0.0305433586, 0.0317982965, 0.0311240307,
0.0283911865, 0.0235897433, 0.0168387650, 0.0083888763,
@ -156,6 +184,11 @@ dpmr_filter(short sample)
return dsd_input_filter(sample, 4);
}
short m17_filter (short sample)
{
return dsd_input_filter(sample, 5);
}
short
dsd_input_filter(short sample, int mode)
@ -189,6 +222,14 @@ dsd_input_filter(short sample, int mode)
coeffs = (float *)dpmrcoeffs;
zeros = DPMRNZEROS;
break;
case 5:
gain = m17gain;
v = m17xv;
coeffs = (float *)m17coeffs;
zeros = M17ZEROS;
break;
default:
return sample;
}

View File

@ -299,9 +299,9 @@ processFrame (dsd_opts * opts, dsd_state * state)
//M17
else if ((state->synctype == 16) || (state->synctype == 9) || (state->synctype == 17) || (state->synctype == 8) )
{
// processM17(opts, state); //disabled until more work can be done
skipDibit(opts, state, 184);
fprintf (stderr, "\n");
if (state->synctype == 16 || state->synctype == 17)
processM17(opts, state);
else skipDibit(opts, state, 184); //8 and 9 will need seperate handling for LSF
return;
}
//P25 P2

View File

@ -236,7 +236,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
}
else if (opts->frame_m17 == 1)
{
t_max = 8; //pfffffft....why (test with multiples of 8?) (might be an idea to do t_max *= 5 or something...for tests)
t_max = 8;
}
else if (state->lastsynctype == 30 || state->lastsynctype == 31 )
{
@ -613,8 +613,6 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
strncpy(synctest8, (synctest_p - 7), 8);
if(opts->frame_m17 == 1)
{
//may need to try the contents of this frame much like NXDN, only
//printFrameSync after good LICH validation etc
if (strcmp(synctest8, M17_STR) == 0)
{
if (opts->inverted_m17 == 0)
@ -625,7 +623,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
state->max = ((state->max) + lmax) / 2;
state->min = ((state->min) + lmin) / 2;
if (state->lastsynctype == 16)
return (16); //reusing some of those old NXDN returns
return (16);
state->lastsynctype = 16;
fprintf (stderr, "\n");
}
@ -637,7 +635,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
state->max = ((state->max) + lmax) / 2;
state->min = ((state->min) + lmin) / 2;
if (state->lastsynctype == 9)
return (9); //reusing some of those old NXDN returns
return (9);
state->lastsynctype = 9;
fprintf (stderr, "\n");
}
@ -652,7 +650,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
state->max = ((state->max) + lmax) / 2;
state->min = ((state->min) + lmin) / 2;
if (state->lastsynctype == 17)
return (17); //reusing some of those old NXDN returns
return (17);
state->lastsynctype = 17;
fprintf (stderr, "\n");
}
@ -664,7 +662,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
state->max = ((state->max) + lmax) / 2;
state->min = ((state->min) + lmin) / 2;
if (state->lastsynctype == 8)
return (8); //reusing some of those old NXDN returns
return (8);
state->lastsynctype = 8;
fprintf (stderr, "\n");
}

View File

@ -471,9 +471,9 @@ noCarrier (dsd_opts * opts, dsd_state * state)
state->ysf_cm = 9;
//M17 Storage
memset (state->m17_lsf, 0, sizeof(state->m17_lsf));
memset (state->m17_lsf_err, 0, sizeof(state->m17_lsf_err));
memset (state->m17_lsf, 1, sizeof(state->m17_lsf));
state->m17_str_dt = 9;
} //nocarrier
void
@ -556,7 +556,7 @@ initOpts (dsd_opts * opts)
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_m17 = 0;
opts->inverted_m17 = 0; //samples from M17_Education seem to all be positive polarity (same from m17-tools programs)
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
@ -1056,8 +1056,13 @@ initState (dsd_state * state)
state->ysf_cm = 9;
//M17 Storage
memset (state->m17_lsf, 0, sizeof(state->m17_lsf));
memset (state->m17_lsf_err, 0, sizeof(state->m17_lsf_err));
memset (state->m17_lsf, 1, sizeof(state->m17_lsf));
state->m17_str_dt = 9;
#ifdef USE_CODEC2
state->codec2_3200 = codec2_create(CODEC2_MODE_3200);
state->codec2_1600 = codec2_create(CODEC2_MODE_1600);
#endif
} //init_state
@ -1144,7 +1149,7 @@ usage ()
printf (" -fd Decode only D-STAR\n");
printf (" -fx Decode only X2-TDMA\n");
printf (" -fy Decode only YSF\n");
printf (" -fz Decode only M17*(not working)\n");
printf (" -fz Decode only M17*\n");
printf (" -fi Decode only NXDN48* (6.25 kHz) / IDAS*\n");
printf (" -fn Decode only NXDN96* (12.5 kHz)\n");
printf (" -fp Decode only EDACS/ProVoice*\n");
@ -1349,6 +1354,11 @@ cleanupAndExit (dsd_opts * opts, dsd_state * state)
{
// Signal that everything should shutdown.
exitflag = 1;
#ifdef USE_CODEC2
codec2_destroy(state->codec2_1600);
codec2_destroy(state->codec2_3200);
#endif
noCarrier (opts, state);
if (opts->wav_out_f != NULL)
@ -2157,7 +2167,7 @@ main (int argc, char **argv)
state.dmr_stereo = 0;
sprintf (opts.output_name, "M17");
fprintf(stderr, "Notice: M17 cannot autodetect polarity. \n Use -xz option if Inverted Signal expected.\n");
fprintf(stderr, "Decoding only M17 frames.(Not Working)\n");
fprintf(stderr, "Decoding only M17 frames.\n");
}
break;
//don't mess with the modulations unless you really need to
@ -2260,7 +2270,7 @@ main (int argc, char **argv)
}
else if (optarg[0] == 'z')
{
opts.inverted_m17 = 1;
opts.inverted_m17 = 0;
fprintf (stderr, "Expecting inverted M17 signals.\n");
}
break;

View File

@ -111,7 +111,7 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync)
{
sf_close(opts->audio_in_file);
fprintf (stderr, "\n\nEnd of .wav file.\n");
fprintf (stderr, "\nEnd of %s\n", opts->audio_in_dev);
//open pulse input if we are pulse output AND using ncurses terminal
if (opts->audio_out_type == 0 && opts->use_ncurses_terminal == 1)
{
@ -248,34 +248,6 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync)
}
//tcp socket input from SDR++ -- old method to open pulse or quit right away
// else if (opts->audio_in_type == 8)
// {
// result = sf_read_short(opts->tcp_file_in, &sample, 1);
// if(result == 0) {
// fprintf (stderr, "Connection to TCP Server Disconnected.\n");
// //open pulse input if we are pulse output AND using ncurses terminal
// if (opts->audio_out_type == 0 && opts->use_ncurses_terminal == 1)
// {
// opts->audio_in_type = 0; //set input type
// openPulseInput(opts); //open pulse input
// }
// //else cleanup and exit
// else cleanupAndExit(opts, state);
// }
// }
//UDP Socket input...not working correct. Reads samples, but no sync
// else if (opts->audio_in_type == 6)
// {
//I think this doesn't get the entire dgram when we run sf_read_short on the udp dgram
// result = sf_read_short(opts->udp_file_in, &sample, 1);
// if (sample != 0)
// fprintf (stderr, "Result = %d Sample = %d \n", result, sample);
// }
if (opts->use_cosine_filter)
{
if ( (state->lastsynctype >= 10 && state->lastsynctype <= 13) || state->lastsynctype == 32 || state->lastsynctype == 33
@ -284,8 +256,12 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync)
sample = dmr_filter(sample);
}
else if (state->lastsynctype == 8 || state->lastsynctype == 9 ||
state->lastsynctype == 16 || state->lastsynctype == 17 ||
else if (state->lastsynctype == 8 || state->lastsynctype == 9 ||state->lastsynctype == 16 || state->lastsynctype == 17)
{
sample = m17_filter(sample);
}
else if (
state->lastsynctype == 20 || state->lastsynctype == 21 ||
state->lastsynctype == 22 || state->lastsynctype == 23 ||
state->lastsynctype == 24 || state->lastsynctype == 25 ||
@ -524,7 +500,7 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync)
{
// opts->audio_in_type = 0; //switch to pulse after playback, ncurses terminal can initiate replay if wanted
fclose(opts->symbolfile);
fprintf (stderr, "\n\nEnd of .bin file\n");
fprintf (stderr, "\nEnd of %s\n", opts->audio_in_dev);
//open pulse input if we are pulse output AND using ncurses terminal
if (opts->audio_out_type == 0 && opts->use_ncurses_terminal == 1)
{

417
src/m17.c Normal file
View File

@ -0,0 +1,417 @@
/*-------------------------------------------------------------------------------
* m17.c
* M17 Decoder (WIP)
*
* M17_SCRAMBLER (m17_scramble) Bit Array from SDR++
* Base40 CharMap from m17-tools
*
* LWVMOBILE
* 2023-07 DSD-FME Florida Man Edition
*-----------------------------------------------------------------------------*/
#include "dsd.h"
//try to find a fancy lfsr or calculation for this and not an array if possible
uint8_t m17_scramble[369] = {
1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1,
1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0,
1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1,
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1,
1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1,
1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1,
0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0,
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0,
1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1,
1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1,
1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1
};
char b40[41] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/.";
/*
2.5.4
LSF CRC
M17 uses a non-standard version of 16-bit CRC with polynomial x16 + x14 + x12 + x11 + x8 +
x5 + x4 + x2 + 1 or 0×5935 and initial value of 0×FFFF. This polynomial allows for detecting all
errors up to hamming distance of 5 with payloads up to 241 bits, which is less than the amount
of data in each frame.
*/
uint16_t crc16m17(const uint8_t buf[], int len)
{
uint16_t poly = 0x5935;
uint16_t crc = 0xFFFF;
for(int i=0; i<len; i++)
{
uint8_t bit = buf[i] & 1;
crc = ((crc << 1) | bit) & 0x1ffff;
if (crc & 0x10000) crc = (crc & 0xffff) ^ poly;
}
return crc & 0xffff;
}
void M17decodeLSF(dsd_state * state, dsd_opts * opts)
{
int i;
unsigned long long int lsf_dst = (unsigned long long int)ConvertBitIntoBytes(&state->m17_lsf[0], 48);
unsigned long long int lsf_src = (unsigned long long int)ConvertBitIntoBytes(&state->m17_lsf[48], 48);
uint16_t lsf_type = (uint16_t)ConvertBitIntoBytes(&state->m17_lsf[96], 16);
//this is the way the manual/code expects you to read these bits
uint8_t lsf_ps = (lsf_type >> 0) & 0x1;
uint8_t lsf_dt = (lsf_type >> 1) & 0x3;
uint8_t lsf_et = (lsf_type >> 3) & 0x3;
uint8_t lsf_es = (lsf_type >> 5) & 0x3;
uint8_t lsf_cn = (lsf_type >> 7) & 0xF;
uint8_t lsf_rs = (lsf_type >> 11) & 0x1F;
//store this so we can reference it for playing voice and/or decoding data
state->m17_str_dt = lsf_dt;
fprintf (stderr, "\n");
//debug
// fprintf (stderr, " LSF TYPE: %04X", lsf_type);
// if (lsf_ps == 0) fprintf (stderr, " PACKET - ");
// if (lsf_ps == 1) fprintf (stderr, " STREAM - ");
//TODO: Base40 Callsigns
fprintf (stderr, " CAN: %d", lsf_cn);
fprintf (stderr, " DST: %012llX SRC: %012llX", lsf_dst, lsf_src);
if (lsf_dt == 0) fprintf (stderr, " Reserved");
if (lsf_dt == 1) fprintf (stderr, " Data");
if (lsf_dt == 2) fprintf (stderr, " Voice (3200bps)");
if (lsf_dt == 3) fprintf (stderr, " Voice + Data");
if (lsf_et != 0) fprintf (stderr, " ENC");
if (lsf_et == 1)
fprintf (stderr, " Scrambler - %d", lsf_es);
if (lsf_et == 2) fprintf (stderr, " AES-CTR");
}
int M17processLICH(dsd_state * state, dsd_opts * opts, uint8_t lich_bits[96])
{
int i, j, err;
err = 0;
uint8_t lich[4][24];
uint8_t lich_decoded[48];
uint8_t temp[96];
bool g[4];
uint16_t crc_cmp = 0;
uint16_t crc_ext = 0;
uint8_t crc_err = 0;
memset(lich, 0, sizeof(lich));
memset(lich_decoded, 0, sizeof(lich_decoded));
memset(temp, 0, sizeof(temp));
//execute golay 24,12 or 4 24-bit chunks and reorder into 4 12-bit chunks
for (i = 0; i < 4; i++)
{
g[i] = TRUE;
for (j = 0; j < 24; j++)
lich[i][j] = lich_bits[(i*24)+j];
g[i] = Golay_24_12_decode(lich[i]);
if(g[i] == FALSE) err = -1;
for (j = 0; j < 12; j++)
lich_decoded[i*12+j] = lich[i][j];
}
uint8_t lich_counter = (uint8_t)ConvertBitIntoBytes(&lich_decoded[40], 3); //lich_cunt
uint8_t lich_reserve = (uint8_t)ConvertBitIntoBytes(&lich_decoded[43], 5); //lich_reserved
if (err == 0)
fprintf (stderr, "LC: %d/6 RES: %02X ", lich_counter+1, lich_reserve); //do the same thing we did on fusion and nxdn
else fprintf (stderr, "LICH G24 ERR");
//this is what 2.rrc is supposed to be -- according to m17-demod -- code fixed now and reflects this
//SRC: AB2CDE, DEST: BROADCAST, STR:V/V, ENC:NONE, CAN:10, NONCE: 0000000000000000000000000000, CRC: 99af
//transfer to storage
for (i = 0; i < 40; i++)
state->m17_lsf[lich_counter*40+i] = lich_decoded[i];
if (opts->payload == 1)
{
fprintf (stderr, " LICH: ");
for (i = 0; i < 6; i++)
fprintf (stderr, "[%02X]", (uint8_t)ConvertBitIntoBytes(&lich_decoded[i*8], 8));
}
if (lich_counter == 5)
{
crc_cmp = crc16m17 (state->m17_lsf, 240);
crc_ext = (uint16_t)ConvertBitIntoBytes(&state->m17_lsf[224], 16);
if (crc_cmp != crc_ext) crc_err = 1;
if (crc_err == 0)
M17decodeLSF(state, opts);
if (opts->payload == 1)
{
fprintf (stderr, "\n LSF: ");
for (i = 0; i < 30; i++)
{
if (i == 15) fprintf (stderr, "\n ");
fprintf (stderr, "[%02X]", (uint8_t)ConvertBitIntoBytes(&state->m17_lsf[i*8], 8));
}
}
// if (crc_err != 0)
// fprintf (stderr, " %04X - %04X (CRC ERR)", crc_cmp, crc_ext);
if (crc_err == 1) fprintf (stderr, " LSF CRC ERR");
}
return err;
}
void M17processCodec2(dsd_opts * opts, dsd_state * state, uint8_t payload[128])
{
int i;
unsigned char bytes[16];
unsigned char bytes1[8];
unsigned char bytes2[8];
for (i = 0; i < 8; i++)
{
bytes[i+0] = (unsigned char)ConvertBitIntoBytes(&payload[i*8*1], 8);
bytes[i+8] = (unsigned char)ConvertBitIntoBytes(&payload[i*8*2], 8);
bytes1[i] = (unsigned char)ConvertBitIntoBytes(&payload[i*8*1], 8);
bytes2[i] = (unsigned char)ConvertBitIntoBytes(&payload[i*8*2], 8);
}
//TODO: A switch to flip whether or not fullrate or halfrate
//halfrate format not available yet in manual, but assuming
//it'll be either byte 1 or byte 2 when passed this far
if (opts->payload == 1)
{
fprintf (stderr, "\n CODEC2: ");
for (i = 0; i < 16; i++)
fprintf (stderr, "%02X", bytes[i]);
fprintf (stderr, " (3200)");
}
//TODO: Fix Speech Output speed/rate issues and also find/figure out potential memory overflow here
#ifdef USE_CODEC2
size_t nsam;
nsam = codec2_samples_per_frame(state->codec2_3200);
//use pointer and allocate memory -- increasing allocation doesn't appear to cause any issues of note just yet
short * speech = malloc (sizeof (short) * (nsam*2)); //*2
//original -- working but speech rate in .rrc files is too fast, but sounds really clean
//may need to collect enough samples first for smooth playback, or upsample them
codec2_decode(state->codec2_3200, speech, bytes);
pa_simple_write(opts->pulse_digi_dev_out, speech, nsam*2, NULL);
//testing area
// codec2_decode(state->codec2_3200, speech, bytes1);
// codec2_decode(state->codec2_3200, speech, bytes2);
// pa_simple_write(opts->pulse_digi_dev_out, speech, nsam, NULL);
// pa_simple_write(opts->pulse_digi_dev_out, speech, nsam, NULL);
//testing area
// codec2_decode(state->codec2_3200, speech, bytes1);
// pa_simple_write(opts->pulse_digi_dev_out, speech, nsam, NULL);
// codec2_decode(state->codec2_3200, speech, bytes2);
// pa_simple_write(opts->pulse_digi_dev_out, speech, nsam, NULL);
free(speech); //ba dum tiss
#endif
}
void M17prepareStream(dsd_opts * opts, dsd_state * state, uint8_t m17_bits[368])
{
//this function will prepare Stream frames
// Scheme P2 is for frames (excluding LICH chunks, which are coded differently). This takes 296
// encoded bits and selects 272 of them. Every 12th bit is being punctured out, leaving 272 bits.
// The full matrix shall have 12 entries with 11 being ones.
// P2 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
int i, j, k, x;
uint8_t m17_punc[310]; //25 * 11 = 275
memset (m17_punc, 0, sizeof(m17_punc));
for (i = 0; i < 272; i++)
m17_punc[i] = m17_bits[i+96];
//depuncture the bits
uint8_t m17_depunc[310]; //25 * 12 = 300
memset (m17_depunc, 0, sizeof(m17_depunc));
k = 0; x = 0;
for (i = 0; i < 25; i++)
{
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = m17_punc[x++];
m17_depunc[k++] = 0;
}
//setup the convolutional decoder
uint8_t temp[300];
uint8_t s0;
uint8_t s1;
uint8_t m_data[28];
uint8_t trellis_buf[400];
memset (trellis_buf, 0, sizeof(trellis_buf));
memset (temp, 0, sizeof (temp));
memset (m_data, 0, sizeof (m_data));
for (i = 0; i < 296; i++)
temp[i] = m17_depunc[i] << 1;
CNXDNConvolution_start();
for (i = 0; i < 148; i++)
{
s0 = temp[(2*i)];
s1 = temp[(2*i)+1];
CNXDNConvolution_decode(s0, s1);
}
CNXDNConvolution_chainback(m_data, 144);
//144/8 = 18, last 4 (144-148) are trailing zeroes
for(i = 0; i < 18; 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;
}
//load m_data into bits for either data packets or voice packets
uint8_t payload[128];
uint8_t end = 9;
uint16_t fn = 0;
memset (payload, 0, sizeof(payload));
end = trellis_buf[0];
fn = (uint16_t)ConvertBitIntoBytes(&trellis_buf[1], 15);
if (opts->payload == 1)
fprintf (stderr, " FSN: %05d", fn);
if (end == 1)
fprintf (stderr, " END;");
for (i = 0; i < 128; i++)
payload[i] = trellis_buf[i+16];
if (state->m17_str_dt == 2)
M17processCodec2(opts, state, payload);
else if (state->m17_str_dt == 3) fprintf (stderr, " V+D;"); //format not available yet in manual
else if (state->m17_str_dt == 1) fprintf (stderr, " DATA;");
else if (state->m17_str_dt == 0) fprintf (stderr, " RES;");
else fprintf (stderr, " UNK;");
if (opts->payload == 1)
{
fprintf (stderr, "\n STREAM: ");
for (i = 0; i < 18; i++)
fprintf (stderr, "[%02X]", (uint8_t)ConvertBitIntoBytes(&trellis_buf[i*8], 8));
}
}
void processM17(dsd_opts * opts, dsd_state * state)
{
int i, x;
uint8_t dbuf[184]; //384-bit frame - 16-bit (8 symbol) sync pattern
uint8_t m17_rnd_bits[368]; //368 bits that are still scrambled (randomized)
uint8_t m17_int_bits[368]; //368 bits that are still interleaved
uint8_t m17_bits[368]; //368 bits that have been de-interleaved and de-scramble
uint8_t lich_bits[96];
int lich_err = -1;
memset (dbuf, 0, sizeof(dbuf));
memset (m17_rnd_bits, 0, sizeof(m17_rnd_bits));
memset (m17_int_bits, 0, sizeof(m17_int_bits));
memset (m17_bits, 0, sizeof(m17_bits));
memset (lich_bits, 0, sizeof(lich_bits));
//this is a shim to allot extra memory until I can find the overflow issue
//when decoding the codec2 data
uint8_t big_chungus[0xFFFF];
memset (big_chungus, 0, sizeof(big_chungus));
/*
Within the Physical Layer, the 368 Type 4 bits are randomized and combined with the 16-bit
Stream Sync Burst, which results in a complete frame of 384 bits (384 bits / 9600bps = 40 ms).
*/
//load dibits into dibit buffer
for (i = 0; i < 184; i++)
dbuf[i] = getDibit(opts, state);
//convert dbuf into a bit array
for (i = 0; i < 184; i++)
{
m17_rnd_bits[i*2+0] = (dbuf[i] >> 1) & 1;
m17_rnd_bits[i*2+1] = (dbuf[i] >> 0) & 1;
}
//descramble the frame
for (i = 0; i < 368; i++)
m17_int_bits[i] = (m17_rnd_bits[i] ^ m17_scramble[i]) & 1;
//deinterleave the bit array using Quadratic Permutation Polynomial function π(x) = (45x + 92x2 ) mod 368
for (i = 0; i < 368; i++)
{
x = ((45*i)+(92*i*i)) % 368;
m17_bits[i] = m17_int_bits[x];
}
for (i = 0; i < 96; i++)
lich_bits[i] = m17_bits[i];
//check lich first, and handle LSF chunk and completed LSF
lich_err = M17processLICH(state, opts, lich_bits);
if (lich_err == 0)
M17prepareStream(opts, state, m17_bits);
if (lich_err != 0) state->lastsynctype = -1;
//ending linebreak
fprintf (stderr, "\n");
} //end processM17