diff --git a/CMakeLists.txt b/CMakeLists.txt index 057fc7c..45b11e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/cmake/FindCODEC2.cmake b/cmake/FindCODEC2.cmake new file mode 100644 index 0000000..728d208 --- /dev/null +++ b/cmake/FindCODEC2.cmake @@ -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) \ No newline at end of file diff --git a/download-and-install.sh b/download-and-install.sh index 1554815..f6c9927 100644 --- a/download-and-install.sh +++ b/download-and-install.sh @@ -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. diff --git a/examples/Install_Notes.md b/examples/Install_Notes.md index 776b2ce..037708f 100644 --- a/examples/Install_Notes.md +++ b/examples/Install_Notes.md @@ -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!! diff --git a/include/dsd.h b/include/dsd.h index d7d9803..e64d457 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -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 +#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); diff --git a/src/dsd_dibit.c b/src/dsd_dibit.c index e696474..c4e92dd 100644 --- a/src/dsd_dibit.c +++ b/src/dsd_dibit.c @@ -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 diff --git a/src/dsd_filters.c b/src/dsd_filters.c index 2a26c77..c78fc6f 100644 --- a/src/dsd_filters.c +++ b/src/dsd_filters.c @@ -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; } diff --git a/src/dsd_frame.c b/src/dsd_frame.c index 16fb3f8..87ff2f1 100644 --- a/src/dsd_frame.c +++ b/src/dsd_frame.c @@ -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 diff --git a/src/dsd_frame_sync.c b/src/dsd_frame_sync.c index 333f745..ac1efec 100644 --- a/src/dsd_frame_sync.c +++ b/src/dsd_frame_sync.c @@ -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"); } diff --git a/src/dsd_main.c b/src/dsd_main.c index c273f58..26ef9f3 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -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; diff --git a/src/dsd_symbol.c b/src/dsd_symbol.c index 4d65c4d..4f3065e 100644 --- a/src/dsd_symbol.c +++ b/src/dsd_symbol.c @@ -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) { diff --git a/src/m17.c b/src/m17.c new file mode 100644 index 0000000..88d0410 --- /dev/null +++ b/src/m17.c @@ -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; im17_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