From 5834914e91f7b2cdaaff24508c9a5b0b27b06808 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sun, 6 Mar 2022 18:30:29 -0500 Subject: [PATCH 01/12] Staging for DMR Improvements Staging for DMR Improvements --- include/dpmr_const.h | 76 +++ include/dsd.h | 262 +++++++- include/fec.h | 1448 ++++++++++++++++++++++++++++++++++++++++++ src/bptc.c | 560 ++++++++++++++++ src/dmr_data.c | 513 ++++++++++----- src/dmr_encryption.c | 115 ++++ src/dmr_sync.c | 839 ++++++++++++++++++++++++ src/dmr_voice.c | 880 ++++++++++++++++--------- src/dpmr_data.c | 278 ++++++++ src/dpmr_voice.c | 919 +++++++++++++++++++++++++++ src/dsd_file.c | 47 +- src/dsd_frame.c | 44 ++ src/dsd_frame_sync.c | 743 +++++++++++++++++++++- src/dsd_main.c | 59 +- src/dsd_ncurses.c | 103 ++- src/fec.c | 1448 ++++++++++++++++++++++++++++++++++++++++++ src/rs-12-9.c | 316 +++++++++ 17 files changed, 8149 insertions(+), 501 deletions(-) create mode 100644 include/dpmr_const.h create mode 100644 include/fec.h create mode 100644 src/bptc.c create mode 100644 src/dmr_encryption.c create mode 100644 src/dmr_sync.c create mode 100644 src/dpmr_data.c create mode 100644 src/dpmr_voice.c create mode 100644 src/fec.c create mode 100644 src/rs-12-9.c diff --git a/include/dpmr_const.h b/include/dpmr_const.h new file mode 100644 index 0000000..cac7039 --- /dev/null +++ b/include/dpmr_const.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010 DSD Author + * GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6 F630 FAA2 635D 3F1D 7FD0) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + + +//#ifdef __cplusplus +//extern "C" { +//#endif + + +#ifndef _MAIN +extern const int dPmrW[36]; +extern const int dPmrX[36]; +extern const int dPmrY[36]; +extern const int dPmrZ[36]; + +//#else +/* + * dPMR AMBE interleave schedule + */ +// bit 1 +const int dPmrW[36] = { + 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 2, + 0, 2, 0, 2, 0, 2, + 0, 2, 0, 2, 0, 2 +}; + +const int dPmrX[36] = { + 23, 10, 22, 9, 21, 8, + 20, 7, 19, 6, 18, 5, + 17, 4, 16, 3, 15, 2, + 14, 1, 13, 0, 12, 10, + 11, 9, 10, 8, 9, 7, + 8, 6, 7, 5, 6, 4 +}; + +// bit 0 +const int dPmrY[36] = { + 0, 2, 0, 2, 0, 2, + 0, 2, 0, 3, 0, 3, + 1, 3, 1, 3, 1, 3, + 1, 3, 1, 3, 1, 3, + 1, 3, 1, 3, 1, 3, + 1, 3, 1, 3, 1, 3 +}; + +const int dPmrZ[36] = { + 5, 3, 4, 2, 3, 1, + 2, 0, 1, 13, 0, 12, + 22, 11, 21, 10, 20, 9, + 19, 8, 18, 7, 17, 6, + 16, 5, 15, 4, 14, 3, + 13, 2, 12, 1, 11, 0 +}; + +#endif + +//#ifdef __cplusplus +//} +//#endif diff --git a/include/dsd.h b/include/dsd.h index 9f423b3..e703fb3 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -147,9 +147,116 @@ typedef struct } NxdnElementsContent_t; +//LEH DMR + +enum +{ + MODE_UNENCRYPTED, + MODE_BASIC_PRIVACY, + MODE_ENHANCED_PRIVACY_ARC4, + MODE_ENHANCED_PRIVACY_AES256, + MODE_HYTERA_BASIC_40_BIT, + MODE_HYTERA_BASIC_128_BIT, + MODE_HYTERA_BASIC_256_BIT +}; + +//Read Solomon (12,9) constant +#define RS_12_9_DATASIZE 9 +#define RS_12_9_CHECKSUMSIZE 3 + +//Read Solomon (12,9) struct +typedef struct +{ + uint8_t data[RS_12_9_DATASIZE+RS_12_9_CHECKSUMSIZE]; +} rs_12_9_codeword_t; + +// Maximum degree of various polynomials. +#define RS_12_9_POLY_MAXDEG (RS_12_9_CHECKSUMSIZE*2) + +typedef struct +{ + uint8_t data[RS_12_9_POLY_MAXDEG]; +} rs_12_9_poly_t; + +#define RS_12_9_CORRECT_ERRORS_RESULT_NO_ERRORS_FOUND 0 +#define RS_12_9_CORRECT_ERRORS_RESULT_ERRORS_CORRECTED 1 +#define RS_12_9_CORRECT_ERRORS_RESULT_ERRORS_CANT_BE_CORRECTED 2 +typedef uint8_t rs_12_9_correct_errors_result_t; + +typedef struct +{ + uint8_t bytes[3]; +} rs_12_9_checksum_t; + +typedef struct +{ + char VoiceFrame[3][72]; /* 3 x 72 bit of voice per time slot frame */ + char Sync[48]; /* 48 bit of data or SYNC per time slot frame */ +}TimeSlotRawVoiceFrame_t; + + +typedef struct +{ + char DeInterleavedVoiceSample[3][4][24]; /* 3 x (4 x 24) deinterleaved bit of voice per time slot frame */ +}TimeSlotDeinterleavedVoiceFrame_t; + + +typedef struct +{ + int errs1[3]; /* 3 x errors #1 computed when demodulate the AMBE voice bit of the frame */ + int errs2[3]; /* 3 x errors #2 computed when demodulate the AMBE voice bit of the frame */ + char AmbeBit[3][49]; /* 3 x 49 bit of AMBE voice of the frame */ +}TimeSlotAmbeVoiceFrame_t; + +typedef struct +{ + unsigned int ProtectFlag; + unsigned int Reserved; + unsigned int FullLinkControlOpcode; + unsigned int FeatureSetID; + unsigned int ServiceOptions; + unsigned int GroupAddress; /* Destination ID or TG (in Air Interface format) */ + unsigned int SourceAddress; /* Source ID (in Air Interface format) */ + unsigned int DataValidity; /* 0 = All Full LC data are incorrect ; 1 = Full LC data are correct (CRC checked OK) */ +}FullLinkControlPDU_t; + +typedef struct +{ + TimeSlotRawVoiceFrame_t TimeSlotRawVoiceFrame[6]; /* A voice superframe contains 6 timeslot voice frame */ + TimeSlotDeinterleavedVoiceFrame_t TimeSlotDeinterleavedVoiceFrame[6]; + TimeSlotAmbeVoiceFrame_t TimeSlotAmbeVoiceFrame[6]; + FullLinkControlPDU_t FullLC; +} TimeSlotVoiceSuperFrame_t; //end borrow from LEH +//LEH dPMR +/* Could only be 2 or 4 */ +#define NB_OF_DPMR_VOICE_FRAME_TO_DECODE 2 +typedef struct +{ + unsigned char RawVoiceBit[NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4][72]; + unsigned int errs1[NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4]; /* 8 x errors #1 computed when demodulate the AMBE voice bit of the frame */ + unsigned int errs2[NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4]; /* 8 x errors #2 computed when demodulate the AMBE voice bit of the frame */ + unsigned char AmbeBit[NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4][49]; /* 8 x 49 bit of AMBE voice of the frame */ + unsigned char CCHData[NB_OF_DPMR_VOICE_FRAME_TO_DECODE][48]; + unsigned int CCHDataHammingOk[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned char CCHDataCRC[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned int CCHDataCrcOk[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned char CalledID[8]; + unsigned int CalledIDOk; + unsigned char CallingID[8]; + unsigned int CallingIDOk; + unsigned int FrameNumbering[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned int CommunicationMode[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned int Version[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned int CommsFormat[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned int EmergencyPriority[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned int Reserved[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned char SlowData[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + unsigned int ColorCode[NB_OF_DPMR_VOICE_FRAME_TO_DECODE / 2]; +} dPMRVoiceFS2Frame_t; +// typedef struct { int onesymbol; @@ -241,6 +348,15 @@ typedef struct int reset_state; int payload; + //LEH DMR Stuff + int EncryptionMode; + + //LEH dPMR stuff + unsigned int dPMR_curr_frame_is_encrypted; + int dPMR_next_part_of_superframe; + int inverted_dpmr; + int frame_dpmr; + } dsd_opts; typedef struct @@ -295,7 +411,7 @@ typedef struct int numtdulc; int firstframe; char slot0light[8]; - char slot1light[8]; + //char slot1light[8]; float aout_gain; float aout_max_buf[200]; float *aout_max_buf_p; @@ -356,9 +472,47 @@ typedef struct char ambe_ciphered[49]; char ambe_deciphered[49]; //end borrow + //LEH DMR struff + unsigned int color_code; + unsigned int color_code_ok; + unsigned int PI; + unsigned int PI_ok; + unsigned int LCSS; + unsigned int LCSS_ok; + unsigned int dmr_fid; + unsigned int dmr_so; + char slot1light[8]; + char slot2light[8]; + int directmode; + TimeSlotVoiceSuperFrame_t TS1SuperFrame; + TimeSlotVoiceSuperFrame_t TS2SuperFrame; + //LEH dPMR stuff + dPMRVoiceFS2Frame_t dPMRVoiceFS2Frame; + + //These flags are used to known the DMR frame + //printing format (hex or binary) + int printDMRRawVoiceFrameHex; + int printDMRRawVoiceFrameBin; + int printDMRRawDataFrameHex; + int printDMRRawDataFrameBin; + int printDMRAmbeVoiceSampleHex; + int printDMRAmbeVoiceSampleBin; + + //These flags are used to known the dPMR frame + //orinting format (hex or binary) + int printdPMRRawVoiceFrameHex; + int printdPMRRawVoiceFrameBin; + int printdPMRRawDataFrameHex; + int printdPMRRawDataFrameBin; + int printdPMRAmbeVoiceSampleHex; + int printdPMRAmbeVoiceSampleBin; + + unsigned char * dpmr_caller_id; + unsigned char * dpmr_target_id; + int dpmr_color_code; #ifdef TRACE_DSD char debug_prefix; @@ -421,11 +575,40 @@ typedef struct #define INV_PROVOICE_EA_SYNC "13313133113113333311313133133311" #define PROVOICE_EA_SYNC "31131311331331111133131311311133" +//LEH dPMR +#define DPMR_FRAME_SYNC_1 "111333331133131131111313" + +/* dPMR Frame Sync 2 - 24 bits sequence + * HEX : 5F F7 7D + * Binary : 0101 1111 1111 0111 0111 1101 + * Dibit : 1 1 3 3 3 3 1 3 1 3 3 1 */ +#define DPMR_FRAME_SYNC_2 "113333131331" + +/* dPMR Frame Sync 3 - 24 bits sequence + * HEX : 7D DF F5 + * Binary : 0111 1101 1101 1111 1111 0101 + * Dibit : 1 3 3 1 3 1 3 3 3 3 1 1 */ +#define DPMR_FRAME_SYNC_3 "133131333311" + +/* dPMR Frame Sync 4 - 48 bits sequence + * HEX : FD 55 F5 DF 7F DD + * Binary : 1111 1101 0101 0101 1111 0101 1101 1111 0111 1111 1101 1101 + * Dibit : 3 3 3 1 1 1 1 1 3 3 1 1 3 1 3 3 1 3 3 3 3 1 3 1 */ +#define DPMR_FRAME_SYNC_4 "333111113311313313333131" + +/* dPMR Frame Sync 1 to 4 - Inverted */ +#define INV_DPMR_FRAME_SYNC_1 "333111113311313313333131" +#define INV_DPMR_FRAME_SYNC_2 "331111313113" +#define INV_DPMR_FRAME_SYNC_3 "311313111133" +#define INV_DPMR_FRAME_SYNC_4 "111333331133131131111313" + +// /* * function prototypes */ void processDMRdata (dsd_opts * opts, dsd_state * state); void processDMRvoice (dsd_opts * opts, dsd_state * state); +void processdPMRvoice (dsd_opts * opts, dsd_state * state); void processAudio (dsd_opts * opts, dsd_state * state); void playRawAudio (dsd_opts * opts, dsd_state * state); //added this one HERE HERE void openPulseInput (dsd_opts * opts); //not sure if we need to just pass opts, or opts and state yet @@ -538,6 +721,83 @@ void ScrambledNXDNVoiceBit(int * LfsrValue, char * BufferIn, char * BufferOut, i void NxdnEncryptionStreamGeneration (dsd_opts* opts, dsd_state* state, uint8_t KeyStream[1664]); void processMbeFrameEncrypted (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char ambe_fr[4][24], char imbe7100_fr[7][24], char ambe_keystream[49], char imbe_keystream[88]); + +//LEH dPMR +void dPMRVoiceFrameProcess(dsd_opts * opts, dsd_state * state); +void printdPMRAmbeVoiceSample(dsd_opts * opts, dsd_state * state); +void printdPMRRawVoiceFrame (dsd_opts * opts, dsd_state * state); +//dPMR functions +void ScrambledPMRBit(uint32_t * LfsrValue, uint8_t * BufferIn, uint8_t * BufferOut, uint32_t NbOfBitToScramble); +void DeInterleave6x12DPmrBit(uint8_t * BufferIn, uint8_t * BufferOut); +uint8_t CRC7BitdPMR(uint8_t * BufferIn, uint32_t BitLength); +uint8_t CRC8BitdPMR(uint8_t * BufferIn, uint32_t BitLength); +void ConvertAirInterfaceID(uint32_t AI_ID, uint8_t ID[8]); +int32_t GetdPmrColorCode(uint8_t ChannelCodeBit[24]); +//LEH DMR +void ProcessDMREncryption (dsd_opts * opts, dsd_state * state); //consider if this is needed +void DMRDataFrameProcess(dsd_opts * opts, dsd_state * state); +void DMRVoiceFrameProcess(dsd_opts * opts, dsd_state * state); +//BPTC (Block Product Turbo Code) functions +void BPTCDeInterleaveDMRData(uint8_t * Input, uint8_t * Output); +uint32_t BPTC_196x96_Extract_Data(uint8_t InputDeInteleavedData[196], uint8_t DMRDataExtracted[96], uint8_t R[3]); +uint32_t BPTC_128x77_Extract_Data(uint8_t InputDataMatrix[8][16], uint8_t DMRDataExtracted[77]); +uint32_t BPTC_16x2_Extract_Data(uint8_t InputInterleavedData[32], uint8_t DMRDataExtracted[32]); + +//Read Solomon (12,9) functions +void rs_12_9_calc_syndrome(rs_12_9_codeword_t *codeword, rs_12_9_poly_t *syndrome); +uint8_t rs_12_9_check_syndrome(rs_12_9_poly_t *syndrome); +rs_12_9_correct_errors_result_t rs_12_9_correct_errors(rs_12_9_codeword_t *codeword, rs_12_9_poly_t *syndrome, uint8_t *errors_found); +rs_12_9_checksum_t *rs_12_9_calc_checksum(rs_12_9_codeword_t *codeword); + +//SYNC DMR data extraction functions +void ProcessDmrVoiceLcHeader(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t syncdata[48], uint8_t SlotType[20]); +void ProcessDmrTerminaisonLC(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t syncdata[48], uint8_t SlotType[20]); +void ProcessVoiceBurstSync(dsd_opts * opts, dsd_state * state); +uint16_t ComputeCrcCCITT(uint8_t * DMRData); +uint32_t ComputeAndCorrectFullLinkControlCrc(uint8_t * FullLinkControlDataBytes, uint32_t * CRCComputed, uint32_t CRCMask); +uint8_t ComputeCrc5Bit(uint8_t * DMRData); +uint8_t * DmrAlgIdToStr(uint8_t AlgID); +uint8_t * DmrAlgPrivacyModeToStr(uint32_t PrivacyMode); + + +void Hamming_7_4_init(); +void Hamming_7_4_encode(unsigned char *origBits, unsigned char *encodedBits); +bool Hamming_7_4_decode(unsigned char *rxBits); + +void Hamming_12_8_init(); +void Hamming_12_8_encode(unsigned char *origBits, unsigned char *encodedBits); +bool Hamming_12_8_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords); + +void Hamming_13_9_init(); +void Hamming_13_9_encode(unsigned char *origBits, unsigned char *encodedBits); +bool Hamming_13_9_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords); + +void Hamming_15_11_init(); +void Hamming_15_11_encode(unsigned char *origBits, unsigned char *encodedBits); +bool Hamming_15_11_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords); + +void Hamming_16_11_4_init(); +void Hamming_16_11_4_encode(unsigned char *origBits, unsigned char *encodedBits); +bool Hamming_16_11_4_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords); + +void Golay_20_8_init(); +void Golay_20_8_encode(unsigned char *origBits, unsigned char *encodedBits); +bool Golay_20_8_decode(unsigned char *rxBits); + +void Golay_23_12_init(); +void Golay_23_12_encode(unsigned char *origBits, unsigned char *encodedBits); +bool Golay_23_12_decode(unsigned char *rxBits); + +void Golay_24_12_init(); +void Golay_24_12_encode(unsigned char *origBits, unsigned char *encodedBits); +bool Golay_24_12_decode(unsigned char *rxBits); + +void QR_16_7_6_init(); +void QR_16_7_6_encode(unsigned char *origBits, unsigned char *encodedBits); +bool QR_16_7_6_decode(unsigned char *rxBits); + +void InitAllFecFunction(void); + //end borrow from LEH void resetState (dsd_state * state); diff --git a/include/fec.h b/include/fec.h new file mode 100644 index 0000000..6180f2d --- /dev/null +++ b/include/fec.h @@ -0,0 +1,1448 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB. // +// // +// 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 as version 3 of the License, or // +// // +// 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 V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsd.h" + +unsigned char Hamming_7_4_m_corr[8]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_7_4_m_G[7*4] = { + 1, 0, 0, 0, 1, 0, 1, + 0, 1, 0, 0, 1, 1, 1, + 0, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 1, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_7_4_m_H[7*3] = { + 1, 1, 1, 0, 1, 0, 0, + 0, 1, 1, 1, 0, 1, 0, + 1, 1, 0, 1, 0, 0, 1 +// 0 1 2 3 <- correctable bit positions +}; + + +// ======================================================================================== + + +unsigned char Hamming_12_8_m_corr[16]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_12_8_m_G[12*8] = { + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_12_8_m_H[12*4] = { + 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, + 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 +// 0 1 2 3 4 5 6 7 <- correctable bit positions +}; + + +// ======================================================================================== + + +unsigned char Hamming_13_9_m_corr[16]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_13_9_m_G[13*9] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1 +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_13_9_m_H[13*4] = { + 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, +// 0 1 2 3 4 5 6 7 8 <- correctable bit positions +}; + + +// ======================================================================================== + + +unsigned char Hamming_15_11_m_corr[16]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_15_11_m_G[15*11] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_15_11_m_H[15*4] = { + 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, + 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, +// 0 1 2 3 4 5 6 7 8 9 10 <- correctable bit positions +}; + + +// ======================================================================================== + + +unsigned char Hamming_16_11_4_m_corr[32]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_16_11_4_m_G[16*11] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1 +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_16_11_4_m_H[16*5] = { + 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, + 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1 +}; + + +// ======================================================================================== + + +unsigned char Golay_20_8_m_corr[4096][3]; //!< up to 3 bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Golay_20_8_m_G[20*8] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Golay_20_8_m_H[20*12] = { + 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + + +// ======================================================================================== + + +unsigned char Golay_23_12_m_corr[2048][3]; //!< up to 3 bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Golay_23_12_m_G[23*12] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Golay_23_12_m_H[23*11] = { + 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + + +// ======================================================================================== + + +unsigned char Golay_24_12_m_corr[4096][3]; //!< up to 3 bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Golay_24_12_m_G[24*12] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Golay_24_12_m_H[24*12] = { + 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + + +// ======================================================================================== + + +unsigned char QR_16_7_6_m_corr[512][2]; //!< up to 2 bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char QR_16_7_6_m_G[16*7] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, + 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char QR_16_7_6_m_H[16*9] = { + 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + + +// ======================================================================================== + + +void Hamming_7_4_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_7_4_m_corr, 0xFF, 8); // initialize with all invalid positions + Hamming_7_4_m_corr[0b101] = 0; + Hamming_7_4_m_corr[0b111] = 1; + Hamming_7_4_m_corr[0b110] = 2; + Hamming_7_4_m_corr[0b011] = 3; + Hamming_7_4_m_corr[0b100] = 4; + Hamming_7_4_m_corr[0b010] = 5; + Hamming_7_4_m_corr[0b001] = 6; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_7_4_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 7); + + for (i = 0; i < 4; i++) + { + for (j = 0; j < 7; j++) + { + encodedBits[j] += origBits[i] * Hamming_7_4_m_G[7*i + j]; + } + } + + for (i = 0; i < 7; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_7_4_decode(unsigned char *rxBits) // corrects in place +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + + for (is = 0; is < 3; is++) + { + syndromeI += (((rxBits[0] * Hamming_7_4_m_H[7*is + 0]) + + (rxBits[1] * Hamming_7_4_m_H[7*is + 1]) + + (rxBits[2] * Hamming_7_4_m_H[7*is + 2]) + + (rxBits[3] * Hamming_7_4_m_H[7*is + 3]) + + (rxBits[4] * Hamming_7_4_m_H[7*is + 4]) + + (rxBits[5] * Hamming_7_4_m_H[7*is + 5]) + + (rxBits[6] * Hamming_7_4_m_H[7*is + 6])) % 2) << (2-is); + } + + if (syndromeI > 0) + { + if (Hamming_7_4_m_corr[syndromeI] == 0xFF) + { + return false; + } + else + { + rxBits[Hamming_7_4_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + return true; +} + + +// ======================================================================================== + + +void Hamming_12_8_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_12_8_m_corr, 0xFF, 16); // initialize with all invalid positions + Hamming_12_8_m_corr[0b1110] = 0; + Hamming_12_8_m_corr[0b0111] = 1; + Hamming_12_8_m_corr[0b1010] = 2; + Hamming_12_8_m_corr[0b0101] = 3; + Hamming_12_8_m_corr[0b1011] = 4; + Hamming_12_8_m_corr[0b1100] = 5; + Hamming_12_8_m_corr[0b0110] = 6; + Hamming_12_8_m_corr[0b0011] = 7; + Hamming_12_8_m_corr[0b1000] = 8; + Hamming_12_8_m_corr[0b0100] = 9; + Hamming_12_8_m_corr[0b0010] = 10; + Hamming_12_8_m_corr[0b0001] = 11; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_12_8_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 12); + + for (i = 0; i < 8; i++) + { + for (j = 0; j < 12; j++) + { + encodedBits[j] += origBits[i] * Hamming_12_8_m_G[12*i + j]; + } + } + + for (i = 0; i < 12; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_12_8_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords) +{ + bool correctable = true; + int ic = 0; + int is = 0; + int syndromeI = 0; // syndrome index + + for (ic = 0; ic < nbCodewords; ic++) + { + // calculate syndrome + + syndromeI = 0; // syndrome index + + for (is = 0; is < 4; is++) + { + syndromeI += (((rxBits[12*ic + 0] * Hamming_12_8_m_H[12*is + 0]) + + (rxBits[12*ic + 1] * Hamming_12_8_m_H[12*is + 1]) + + (rxBits[12*ic + 2] * Hamming_12_8_m_H[12*is + 2]) + + (rxBits[12*ic + 3] * Hamming_12_8_m_H[12*is + 3]) + + (rxBits[12*ic + 4] * Hamming_12_8_m_H[12*is + 4]) + + (rxBits[12*ic + 5] * Hamming_12_8_m_H[12*is + 5]) + + (rxBits[12*ic + 6] * Hamming_12_8_m_H[12*is + 6]) + + (rxBits[12*ic + 7] * Hamming_12_8_m_H[12*is + 7]) + + (rxBits[12*ic + 8] * Hamming_12_8_m_H[12*is + 8]) + + (rxBits[12*ic + 9] * Hamming_12_8_m_H[12*is + 9]) + + (rxBits[12*ic + 10] * Hamming_12_8_m_H[12*is + 10]) + + (rxBits[12*ic + 11] * Hamming_12_8_m_H[12*is + 11])) % 2) << (3-is); + } + + // correct bit + + if (syndromeI > 0) // single bit error correction + { + if (Hamming_12_8_m_corr[syndromeI] == 0xFF) // uncorrectable error + { + correctable = false; + } + else + { + rxBits[Hamming_12_8_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + // move information bits + memcpy(&decodedBits[8*ic], &rxBits[12*ic], 8); + } + + return correctable; +} + + +// ======================================================================================== + + +void Hamming_13_9_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_13_9_m_corr, 0xFF, 16); // initialize with all invalid positions + Hamming_13_9_m_corr[0b1111] = 0; + Hamming_13_9_m_corr[0b1110] = 1; + Hamming_13_9_m_corr[0b0111] = 2; + Hamming_13_9_m_corr[0b1010] = 3; + Hamming_13_9_m_corr[0b0101] = 4; + Hamming_13_9_m_corr[0b1011] = 5; + Hamming_13_9_m_corr[0b1100] = 6; + Hamming_13_9_m_corr[0b0110] = 7; + Hamming_13_9_m_corr[0b0011] = 8; + Hamming_13_9_m_corr[0b1000] = 9; + Hamming_13_9_m_corr[0b0100] = 10; + Hamming_13_9_m_corr[0b0010] = 11; + Hamming_13_9_m_corr[0b0001] = 12; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_13_9_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + memset(encodedBits, 0, 13); + + for (i = 0; i < 9; i++) + { + for (j = 0; j < 13; j++) + { + encodedBits[j] += origBits[i] * Hamming_13_9_m_G[13*i + j]; + } + } + + for (i = 0; i < 13; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_13_9_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords) +{ + bool correctable = true; + int ic = 0, is = 0; + int syndromeI = 0; // syndrome index + + for (ic = 0; ic < nbCodewords; ic++) + { + // calculate syndrome + + syndromeI = 0; // syndrome index + + for (is = 0; is < 4; is++) + { + syndromeI += (((rxBits[13*ic + 0] * Hamming_13_9_m_H[13*is + 0]) + + (rxBits[13*ic + 1] * Hamming_13_9_m_H[13*is + 1]) + + (rxBits[13*ic + 2] * Hamming_13_9_m_H[13*is + 2]) + + (rxBits[13*ic + 3] * Hamming_13_9_m_H[13*is + 3]) + + (rxBits[13*ic + 4] * Hamming_13_9_m_H[13*is + 4]) + + (rxBits[13*ic + 5] * Hamming_13_9_m_H[13*is + 5]) + + (rxBits[13*ic + 6] * Hamming_13_9_m_H[13*is + 6]) + + (rxBits[13*ic + 7] * Hamming_13_9_m_H[13*is + 7]) + + (rxBits[13*ic + 8] * Hamming_13_9_m_H[13*is + 8]) + + (rxBits[13*ic + 9] * Hamming_13_9_m_H[13*is + 9]) + + (rxBits[13*ic + 10] * Hamming_13_9_m_H[13*is + 10]) + + (rxBits[13*ic + 11] * Hamming_13_9_m_H[13*is + 11]) + + (rxBits[13*ic + 12] * Hamming_13_9_m_H[13*is + 12])) % 2) << (3-is); + } + + // correct bit + + if (syndromeI > 0) // single bit error correction + { + if (Hamming_13_9_m_corr[syndromeI] == 0xFF) // uncorrectable error + { + correctable = false; + break; + } + else + { + rxBits[Hamming_13_9_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + // move information bits + if (decodedBits) + { + memcpy(&decodedBits[9*ic], &rxBits[13*ic], 9); + } + } + + return correctable; +} + + +// ======================================================================================== + + +void Hamming_15_11_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_15_11_m_corr, 0xFF, 16); // initialize with all invalid positions + Hamming_15_11_m_corr[0b1001] = 0; + Hamming_15_11_m_corr[0b1101] = 1; + Hamming_15_11_m_corr[0b1111] = 2; + Hamming_15_11_m_corr[0b1110] = 3; + Hamming_15_11_m_corr[0b0111] = 4; + Hamming_15_11_m_corr[0b1010] = 5; + Hamming_15_11_m_corr[0b0101] = 6; + Hamming_15_11_m_corr[0b1011] = 7; + Hamming_15_11_m_corr[0b1100] = 8; + Hamming_15_11_m_corr[0b0110] = 9; + Hamming_15_11_m_corr[0b0011] = 10; + Hamming_15_11_m_corr[0b1000] = 11; + Hamming_15_11_m_corr[0b0100] = 12; + Hamming_15_11_m_corr[0b0010] = 13; + Hamming_15_11_m_corr[0b0001] = 14; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_15_11_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + memset(encodedBits, 0, 15); + + for (i = 0; i < 11; i++) + { + for (j = 0; j < 15; j++) + { + encodedBits[j] += origBits[i] * Hamming_15_11_m_G[15*i + j]; + } + } + + for (i = 0; i < 15; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_15_11_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords) +{ + bool correctable = true; + int ic = 0, is = 0; + int syndromeI = 0; // syndrome index + + for (ic = 0; ic < nbCodewords; ic++) + { + // calculate syndrome + + syndromeI = 0; // syndrome index + + for (is = 0; is < 4; is++) + { + syndromeI += (((rxBits[15*ic + 0] * Hamming_15_11_m_H[15*is + 0]) + + (rxBits[15*ic + 1] * Hamming_15_11_m_H[15*is + 1]) + + (rxBits[15*ic + 2] * Hamming_15_11_m_H[15*is + 2]) + + (rxBits[15*ic + 3] * Hamming_15_11_m_H[15*is + 3]) + + (rxBits[15*ic + 4] * Hamming_15_11_m_H[15*is + 4]) + + (rxBits[15*ic + 5] * Hamming_15_11_m_H[15*is + 5]) + + (rxBits[15*ic + 6] * Hamming_15_11_m_H[15*is + 6]) + + (rxBits[15*ic + 7] * Hamming_15_11_m_H[15*is + 7]) + + (rxBits[15*ic + 8] * Hamming_15_11_m_H[15*is + 8]) + + (rxBits[15*ic + 9] * Hamming_15_11_m_H[15*is + 9]) + + (rxBits[15*ic + 10] * Hamming_15_11_m_H[15*is + 10]) + + (rxBits[15*ic + 11] * Hamming_15_11_m_H[15*is + 11]) + + (rxBits[15*ic + 12] * Hamming_15_11_m_H[15*is + 12]) + + (rxBits[15*ic + 13] * Hamming_15_11_m_H[15*is + 13]) + + (rxBits[15*ic + 14] * Hamming_15_11_m_H[15*is + 14])) % 2) << (3-is); + } + + // correct bit + + if (syndromeI > 0) // single bit error correction + { + if (Hamming_15_11_m_corr[syndromeI] == 0xFF) // uncorrectable error + { + correctable = false; + break; + } + else + { + rxBits[Hamming_15_11_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + // move information bits + if (decodedBits) + { + memcpy(&decodedBits[11*ic], &rxBits[15*ic], 11); + } + } + + return correctable; +} + + +// ======================================================================================== + + +void Hamming_16_11_4_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_16_11_4_m_corr, 0xFF, 32); // initialize with all invalid positions + Hamming_16_11_4_m_corr[0b10011] = 0; + Hamming_16_11_4_m_corr[0b11010] = 1; + Hamming_16_11_4_m_corr[0b11111] = 2; + Hamming_16_11_4_m_corr[0b11100] = 3; + Hamming_16_11_4_m_corr[0b01110] = 4; + Hamming_16_11_4_m_corr[0b10101] = 5; + Hamming_16_11_4_m_corr[0b01011] = 6; + Hamming_16_11_4_m_corr[0b10110] = 7; + Hamming_16_11_4_m_corr[0b11001] = 8; + Hamming_16_11_4_m_corr[0b01101] = 9; + Hamming_16_11_4_m_corr[0b00111] = 10; + Hamming_16_11_4_m_corr[0b10000] = 11; + Hamming_16_11_4_m_corr[0b01000] = 12; + Hamming_16_11_4_m_corr[0b00100] = 13; + Hamming_16_11_4_m_corr[0b00010] = 14; + Hamming_16_11_4_m_corr[0b00001] = 15; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_16_11_4_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + memset(encodedBits, 0, 16); + + for (i = 0; i < 11; i++) + { + for (j = 0; j < 16; j++) + { + encodedBits[j] += origBits[i] * Hamming_16_11_4_m_G[16*i + j]; + } + } + + for (i = 0; i < 16; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_16_11_4_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords) +{ + bool correctable = true; + int ic = 0, is = 0; + int syndromeI = 0; // syndrome index + + for (ic = 0; ic < nbCodewords; ic++) + { + // calculate syndrome + syndromeI = 0; // syndrome index + for (is = 0; is < 5; is++) + { + syndromeI += (((rxBits[16*ic + 0] * Hamming_16_11_4_m_H[16*is + 0]) + + (rxBits[16*ic + 1] * Hamming_16_11_4_m_H[16*is + 1]) + + (rxBits[16*ic + 2] * Hamming_16_11_4_m_H[16*is + 2]) + + (rxBits[16*ic + 3] * Hamming_16_11_4_m_H[16*is + 3]) + + (rxBits[16*ic + 4] * Hamming_16_11_4_m_H[16*is + 4]) + + (rxBits[16*ic + 5] * Hamming_16_11_4_m_H[16*is + 5]) + + (rxBits[16*ic + 6] * Hamming_16_11_4_m_H[16*is + 6]) + + (rxBits[16*ic + 7] * Hamming_16_11_4_m_H[16*is + 7]) + + (rxBits[16*ic + 8] * Hamming_16_11_4_m_H[16*is + 8]) + + (rxBits[16*ic + 9] * Hamming_16_11_4_m_H[16*is + 9]) + + (rxBits[16*ic + 10] * Hamming_16_11_4_m_H[16*is + 10]) + + (rxBits[16*ic + 11] * Hamming_16_11_4_m_H[16*is + 11]) + + (rxBits[16*ic + 12] * Hamming_16_11_4_m_H[16*is + 12]) + + (rxBits[16*ic + 13] * Hamming_16_11_4_m_H[16*is + 13]) + + (rxBits[16*ic + 14] * Hamming_16_11_4_m_H[16*is + 14]) + + (rxBits[16*ic + 15] * Hamming_16_11_4_m_H[16*is + 15])) % 2) << (4-is); + } + + // correct bit + + if (syndromeI > 0) // single bit error correction + { + if (Hamming_16_11_4_m_corr[syndromeI] == 0xFF) // uncorrectable error + { + correctable = false; + break; + } + else + { + rxBits[Hamming_16_11_4_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + // move information bits + if (decodedBits) + { + memcpy(&decodedBits[11*ic], &rxBits[16*ic], 11); + } + } + + return correctable; +} + + +// ======================================================================================== + + +void Golay_20_8_init() +{ + int i1 = 0, i2 = 0, i3 = 0, ir = 0, ip = 0; + int syndromeI = 0, syndromeIP = 0; + int ip1 = 0, ip2 = 0, ip3 = 0; + int syndromeIP1 = 0, syndromeIP2 = 0, syndromeIP3 = 0; + + memset(Golay_20_8_m_corr, 0xFF, 3*4096); + + for (i1 = 0; i1 < 8; i1++) + { + for (i2 = i1+1; i2 < 8; i2++) + { + for (i3 = i2+1; i3 < 8; i3++) + { + // 3 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += ((Golay_20_8_m_H[20*ir + i1] + Golay_20_8_m_H[20*ir + i2] + Golay_20_8_m_H[20*ir + i3]) % 2) << (11-ir); + } + + Golay_20_8_m_corr[syndromeI][0] = i1; + Golay_20_8_m_corr[syndromeI][1] = i2; + Golay_20_8_m_corr[syndromeI][2] = i3; + } + + // 2 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += ((Golay_20_8_m_H[20*ir + i1] + Golay_20_8_m_H[20*ir + i2]) % 2) << (11-ir); + } + + Golay_20_8_m_corr[syndromeI][0] = i1; + Golay_20_8_m_corr[syndromeI][1] = i2; + + // 1 possible bit flip left in the parity part + for (ip = 0; ip < 12; ip++) + { + syndromeIP = syndromeI ^ (1 << (11-ip)); + Golay_20_8_m_corr[syndromeIP][0] = i1; + Golay_20_8_m_corr[syndromeIP][1] = i2; + Golay_20_8_m_corr[syndromeIP][2] = 12 + ip; + } + } + + // single bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += Golay_20_8_m_H[20*ir + i1] << (11-ir); + } + + Golay_20_8_m_corr[syndromeI][0] = i1; + + for (ip1 = 0; ip1 < 12; ip1++) // 1 more bit flip in parity + { + syndromeIP1 = syndromeI ^ (1 << (11-ip1)); + Golay_20_8_m_corr[syndromeIP1][0] = i1; + Golay_20_8_m_corr[syndromeIP1][1] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); + Golay_20_8_m_corr[syndromeIP2][0] = i1; + Golay_20_8_m_corr[syndromeIP2][1] = 12 + ip1; + Golay_20_8_m_corr[syndromeIP2][2] = 12 + ip2; + } + } + } + + // no bit patterns (in message) -> all in parity + for (ip1 = 0; ip1 < 12; ip1++) // 1 bit flip in parity + { + syndromeIP1 = (1 << (11-ip1)); + Golay_20_8_m_corr[syndromeIP1][0] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); + Golay_20_8_m_corr[syndromeIP2][0] = 12 + ip1; + Golay_20_8_m_corr[syndromeIP2][1] = 12 + ip2; + + for (ip3 = ip2+1; ip3 < 12; ip3++) // 1 more bit flip in parity + { + syndromeIP3 = syndromeIP2 ^ (1 << (11-ip3)); + Golay_20_8_m_corr[syndromeIP3][0] = 12 + ip1; + Golay_20_8_m_corr[syndromeIP3][1] = 12 + ip2; + Golay_20_8_m_corr[syndromeIP3][2] = 12 + ip3; + } + } + } +} + +// Not very efficient but encode is used for unit testing only +void Golay_20_8_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 20); + + for (i = 0; i < 8; i++) + { + for (j = 0; j < 20; j++) + { + encodedBits[j] += origBits[i] * Golay_20_8_m_G[20*i + j]; + } + } + + for (i = 0; i < 20; i++) + { + encodedBits[i] %= 2; + } +} + +bool Golay_20_8_decode(unsigned char *rxBits) +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + int i = 0; + + for (is = 0; is < 12; is++) + { + syndromeI += (((rxBits[0] * Golay_20_8_m_H[20*is + 0]) + + (rxBits[1] * Golay_20_8_m_H[20*is + 1]) + + (rxBits[2] * Golay_20_8_m_H[20*is + 2]) + + (rxBits[3] * Golay_20_8_m_H[20*is + 3]) + + (rxBits[4] * Golay_20_8_m_H[20*is + 4]) + + (rxBits[5] * Golay_20_8_m_H[20*is + 5]) + + (rxBits[6] * Golay_20_8_m_H[20*is + 6]) + + (rxBits[7] * Golay_20_8_m_H[20*is + 7]) + + (rxBits[8] * Golay_20_8_m_H[20*is + 8]) + + (rxBits[9] * Golay_20_8_m_H[20*is + 9]) + + (rxBits[10] * Golay_20_8_m_H[20*is + 10]) + + (rxBits[11] * Golay_20_8_m_H[20*is + 11]) + + (rxBits[12] * Golay_20_8_m_H[20*is + 12]) + + (rxBits[13] * Golay_20_8_m_H[20*is + 13]) + + (rxBits[14] * Golay_20_8_m_H[20*is + 14]) + + (rxBits[15] * Golay_20_8_m_H[20*is + 15]) + + (rxBits[16] * Golay_20_8_m_H[20*is + 16]) + + (rxBits[17] * Golay_20_8_m_H[20*is + 17]) + + (rxBits[18] * Golay_20_8_m_H[20*is + 18]) + + (rxBits[19] * Golay_20_8_m_H[20*is + 19])) % 2) << (11-is); + } + + if (syndromeI > 0) + { + i = 0; + + for (; i < 3; i++) + { + if (Golay_20_8_m_corr[syndromeI][i] == 0xFF) + { + break; + } + else + { + rxBits[Golay_20_8_m_corr[syndromeI][i]] ^= 1; // flip bit + } + } + + if (i == 0) + { + return false; + } + } + + return true; +} + + +// ======================================================================================== + + +void Golay_23_12_init() +{ + int i1 = 0, i2 = 0, i3 = 0, ir = 0, ip = 0; + int syndromeI = 0, syndromeIP = 0; + int ip1 = 0, ip2 = 0, ip3 = 0; + int syndromeIP1 = 0, syndromeIP2 = 0, syndromeIP3 = 0; + + memset(Golay_23_12_m_corr, 0xFF, 3*2048); + + for (i1 = 0; i1 < 12; i1++) + { + for (i2 = i1+1; i2 < 12; i2++) + { + for (i3 = i2+1; i3 < 12; i3++) + { + // 3 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 11; ir++) + { + syndromeI += ((Golay_23_12_m_H[23*ir + i1] + Golay_23_12_m_H[23*ir + i2] + Golay_23_12_m_H[23*ir + i3]) % 2) << (10-ir); + } + + Golay_23_12_m_corr[syndromeI][0] = i1; + Golay_23_12_m_corr[syndromeI][1] = i2; + Golay_23_12_m_corr[syndromeI][2] = i3; + } + + // 2 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 11; ir++) + { + syndromeI += ((Golay_23_12_m_H[23*ir + i1] + Golay_23_12_m_H[23*ir + i2]) % 2) << (10-ir); + } + + Golay_23_12_m_corr[syndromeI][0] = i1; + Golay_23_12_m_corr[syndromeI][1] = i2; + + // 1 possible bit flip left in the parity part + for (ip = 0; ip < 11; ip++) + { + syndromeIP = syndromeI ^ (1 << (10-ip)); + Golay_23_12_m_corr[syndromeIP][0] = i1; + Golay_23_12_m_corr[syndromeIP][1] = i2; + Golay_23_12_m_corr[syndromeIP][2] = 12 + ip; + } + } + + // single bit patterns + syndromeI = 0; + + for (ir = 0; ir < 11; ir++) + { + syndromeI += Golay_23_12_m_H[23*ir + i1] << (10-ir); + } + + Golay_23_12_m_corr[syndromeI][0] = i1; + + for (ip1 = 0; ip1 < 11; ip1++) // 1 more bit flip in parity + { + syndromeIP1 = syndromeI ^ (1 << (10-ip1)); + Golay_23_12_m_corr[syndromeIP1][0] = i1; + Golay_23_12_m_corr[syndromeIP1][1] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 11; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (10-ip2)); + Golay_23_12_m_corr[syndromeIP2][0] = i1; + Golay_23_12_m_corr[syndromeIP2][1] = 12 + ip1; + Golay_23_12_m_corr[syndromeIP2][2] = 12 + ip2; + } + } + } + + // no bit patterns (in message) -> all in parity + for (ip1 = 0; ip1 < 11; ip1++) // 1 bit flip in parity + { + syndromeIP1 = (1 << (10-ip1)); + Golay_23_12_m_corr[syndromeIP1][0] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 11; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (10-ip2)); + Golay_23_12_m_corr[syndromeIP2][0] = 12 + ip1; + Golay_23_12_m_corr[syndromeIP2][1] = 12 + ip2; + + for (ip3 = ip2+1; ip3 < 11; ip3++) // 1 more bit flip in parity + { + syndromeIP3 = syndromeIP2 ^ (1 << (10-ip3)); + Golay_23_12_m_corr[syndromeIP3][0] = 12 + ip1; + Golay_23_12_m_corr[syndromeIP3][1] = 12 + ip2; + Golay_23_12_m_corr[syndromeIP3][2] = 12 + ip3; + } + } + } +} + +// Not very efficient but encode is used for unit testing only +void Golay_23_12_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 23); + + for (i = 0; i < 12; i++) // orig bits + { + for (j = 0; j < 23; j++) // codeword bits + { + encodedBits[j] += origBits[i] * Golay_23_12_m_G[23*i + j]; + } + } + + for (i = 0; i < 23; i++) + { + encodedBits[i] %= 2; + } +} + +bool Golay_23_12_decode(unsigned char *rxBits) +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + int i = 0; + + for (is = 0; is < 11; is++) + { + syndromeI += (((rxBits[0] * Golay_23_12_m_H[23*is + 0]) + + (rxBits[1] * Golay_23_12_m_H[23*is + 1]) + + (rxBits[2] * Golay_23_12_m_H[23*is + 2]) + + (rxBits[3] * Golay_23_12_m_H[23*is + 3]) + + (rxBits[4] * Golay_23_12_m_H[23*is + 4]) + + (rxBits[5] * Golay_23_12_m_H[23*is + 5]) + + (rxBits[6] * Golay_23_12_m_H[23*is + 6]) + + (rxBits[7] * Golay_23_12_m_H[23*is + 7]) + + (rxBits[8] * Golay_23_12_m_H[23*is + 8]) + + (rxBits[9] * Golay_23_12_m_H[23*is + 9]) + + (rxBits[10] * Golay_23_12_m_H[23*is + 10]) + + (rxBits[11] * Golay_23_12_m_H[23*is + 11]) + + (rxBits[12] * Golay_23_12_m_H[23*is + 12]) + + (rxBits[13] * Golay_23_12_m_H[23*is + 13]) + + (rxBits[14] * Golay_23_12_m_H[23*is + 14]) + + (rxBits[15] * Golay_23_12_m_H[23*is + 15]) + + (rxBits[16] * Golay_23_12_m_H[23*is + 16]) + + (rxBits[17] * Golay_23_12_m_H[23*is + 17]) + + (rxBits[18] * Golay_23_12_m_H[23*is + 18]) + + (rxBits[19] * Golay_23_12_m_H[23*is + 19]) + + (rxBits[20] * Golay_23_12_m_H[23*is + 20]) + + (rxBits[21] * Golay_23_12_m_H[23*is + 21]) + + (rxBits[22] * Golay_23_12_m_H[23*is + 22])) % 2) << (10-is); + } + + if (syndromeI > 0) + { + i = 0; + + for (; i < 3; i++) + { + if (Golay_23_12_m_corr[syndromeI][i] == 0xFF) + { + break; + } + else + { + rxBits[Golay_23_12_m_corr[syndromeI][i]] ^= 1; // flip bit + } + } + + if (i == 0) + { + return false; + } + } + + return true; +} + + +// ======================================================================================== + + +void Golay_24_12_init() +{ + int i1 = 0, i2 = 0, i3 = 0, ir = 0, ip = 0; + int syndromeI = 0, syndromeIP = 0; + int ip1 = 0, ip2 = 0, ip3 = 0; + int syndromeIP1 = 0, syndromeIP2 = 0, syndromeIP3 = 0; + + memset (Golay_24_12_m_corr, 0xFF, 3*4096); + + for (i1 = 0; i1 < 12; i1++) + { + for (i2 = i1+1; i2 < 12; i2++) + { + for (i3 = i2+1; i3 < 12; i3++) + { + // 3 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += ((Golay_24_12_m_H[24*ir + i1] + Golay_24_12_m_H[24*ir + i2] + Golay_24_12_m_H[24*ir + i3]) % 2) << (11-ir); + } + + Golay_24_12_m_corr[syndromeI][0] = i1; + Golay_24_12_m_corr[syndromeI][1] = i2; + Golay_24_12_m_corr[syndromeI][2] = i3; + } + + // 2 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += ((Golay_24_12_m_H[24*ir + i1] + Golay_24_12_m_H[24*ir + i2]) % 2) << (11-ir); + } + + Golay_24_12_m_corr[syndromeI][0] = i1; + Golay_24_12_m_corr[syndromeI][1] = i2; + + // 1 possible bit flip left in the parity part + for (ip = 0; ip < 12; ip++) + { + syndromeIP = syndromeI ^ (1 << (11-ip)); + Golay_24_12_m_corr[syndromeIP][0] = i1; + Golay_24_12_m_corr[syndromeIP][1] = i2; + Golay_24_12_m_corr[syndromeIP][2] = 12 + ip; + } + } + + // single bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += Golay_24_12_m_H[24*ir + i1] << (11-ir); + } + + Golay_24_12_m_corr[syndromeI][0] = i1; + + for (ip1 = 0; ip1 < 12; ip1++) // 1 more bit flip in parity + { + syndromeIP1 = syndromeI ^ (1 << (11-ip1)); + Golay_24_12_m_corr[syndromeIP1][0] = i1; + Golay_24_12_m_corr[syndromeIP1][1] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); + Golay_24_12_m_corr[syndromeIP2][0] = i1; + Golay_24_12_m_corr[syndromeIP2][1] = 12 + ip1; + Golay_24_12_m_corr[syndromeIP2][2] = 12 + ip2; + } + } + } + + // no bit patterns (in message) -> all in parity + for (ip1 = 0; ip1 < 12; ip1++) // 1 bit flip in parity + { + syndromeIP1 = (1 << (11-ip1)); + Golay_24_12_m_corr[syndromeIP1][0] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); + Golay_24_12_m_corr[syndromeIP2][0] = 12 + ip1; + Golay_24_12_m_corr[syndromeIP2][1] = 12 + ip2; + + for (ip3 = ip2+1; ip3 < 12; ip3++) // 1 more bit flip in parity + { + syndromeIP3 = syndromeIP2 ^ (1 << (11-ip3)); + Golay_24_12_m_corr[syndromeIP3][0] = 12 + ip1; + Golay_24_12_m_corr[syndromeIP3][1] = 12 + ip2; + Golay_24_12_m_corr[syndromeIP3][2] = 12 + ip3; + } + } + } +} + +// Not very efficient but encode is used for unit testing only +void Golay_24_12_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 24); + + for (i = 0; i < 12; i++) + { + for (j = 0; j < 24; j++) + { + encodedBits[j] += origBits[i] * Golay_24_12_m_G[24*i + j]; + } + } + + for (i = 0; i < 24; i++) + { + encodedBits[i] %= 2; + } +} + +bool Golay_24_12_decode(unsigned char *rxBits) +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + int i = 0; + + for (is = 0; is < 12; is++) + { + syndromeI += (((rxBits[0] * Golay_24_12_m_H[24*is + 0]) + + (rxBits[1] * Golay_24_12_m_H[24*is + 1]) + + (rxBits[2] * Golay_24_12_m_H[24*is + 2]) + + (rxBits[3] * Golay_24_12_m_H[24*is + 3]) + + (rxBits[4] * Golay_24_12_m_H[24*is + 4]) + + (rxBits[5] * Golay_24_12_m_H[24*is + 5]) + + (rxBits[6] * Golay_24_12_m_H[24*is + 6]) + + (rxBits[7] * Golay_24_12_m_H[24*is + 7]) + + (rxBits[8] * Golay_24_12_m_H[24*is + 8]) + + (rxBits[9] * Golay_24_12_m_H[24*is + 9]) + + (rxBits[10] * Golay_24_12_m_H[24*is + 10]) + + (rxBits[11] * Golay_24_12_m_H[24*is + 11]) + + (rxBits[12] * Golay_24_12_m_H[24*is + 12]) + + (rxBits[13] * Golay_24_12_m_H[24*is + 13]) + + (rxBits[14] * Golay_24_12_m_H[24*is + 14]) + + (rxBits[15] * Golay_24_12_m_H[24*is + 15]) + + (rxBits[16] * Golay_24_12_m_H[24*is + 16]) + + (rxBits[17] * Golay_24_12_m_H[24*is + 17]) + + (rxBits[18] * Golay_24_12_m_H[24*is + 18]) + + (rxBits[19] * Golay_24_12_m_H[24*is + 19]) + + (rxBits[20] * Golay_24_12_m_H[24*is + 20]) + + (rxBits[21] * Golay_24_12_m_H[24*is + 21]) + + (rxBits[22] * Golay_24_12_m_H[24*is + 22]) + + (rxBits[23] * Golay_24_12_m_H[24*is + 23])) % 2) << (11-is); + } + + if (syndromeI > 0) + { + i = 0; + + for (; i < 3; i++) + { + if (Golay_24_12_m_corr[syndromeI][i] == 0xFF) + { + break; + } + else + { + rxBits[Golay_24_12_m_corr[syndromeI][i]] ^= 1; // flip bit + } + } + + if (i == 0) + { + return false; + } + } + + return true; +} + + +// ======================================================================================== + + +void QR_16_7_6_init() +{ + int i1 = 0, i2 = 0, ir = 0, ip = 0; + int syndromeI = 0, syndromeIP = 0; + int ip1 = 0, ip2 = 0; + int syndromeIP1 = 0, syndromeIP2 = 0; + + memset(QR_16_7_6_m_corr, 0xFF, 2*512); + + for (i1 = 0; i1 < 7; i1++) + { + for (i2 = i1+1; i2 < 7; i2++) + { + // 2 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 9; ir++) + { + syndromeI += ((QR_16_7_6_m_H[16*ir + i1] + QR_16_7_6_m_H[16*ir + i2]) % 2) << (8-ir); + } + + QR_16_7_6_m_corr[syndromeI][0] = i1; + QR_16_7_6_m_corr[syndromeI][1] = i2; + } + + // single bit patterns + syndromeI = 0; + + for (ir = 0; ir < 9; ir++) + { + syndromeI += QR_16_7_6_m_H[16*ir + i1] << (8-ir); + } + + QR_16_7_6_m_corr[syndromeI][0] = i1; + + // 1 possible bit flip left in the parity part + for (ip = 0; ip < 9; ip++) + { + syndromeIP = syndromeI ^ (1 << (8-ip)); + QR_16_7_6_m_corr[syndromeIP][0] = i1; + QR_16_7_6_m_corr[syndromeIP][1] = 7 + ip; + } + } + + // no bit patterns (in message) -> all in parity + for (ip1 = 0; ip1 < 9; ip1++) // 1 bit flip in parity + { + syndromeIP1 = (1 << (8-ip1)); + QR_16_7_6_m_corr[syndromeIP1][0] = 7 + ip1; + + for (ip2 = ip1+1; ip2 < 9; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (8-ip2)); + QR_16_7_6_m_corr[syndromeIP2][0] = 7 + ip1; + QR_16_7_6_m_corr[syndromeIP2][1] = 7 + ip2; + } + } +} + +// Not very efficient but encode is used for unit testing only +void QR_16_7_6_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 16); + + for (i = 0; i < 7; i++) + { + for (j = 0; j < 16; j++) + { + encodedBits[j] += origBits[i] * QR_16_7_6_m_G[16*i + j]; + } + } + + for (i = 0; i < 16; i++) + { + encodedBits[i] %= 2; + } +} + +bool QR_16_7_6_decode(unsigned char *rxBits) +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + int i = 0; + + for (is = 0; is < 9; is++) + { + syndromeI += (((rxBits[0] * QR_16_7_6_m_H[16*is + 0]) + + (rxBits[1] * QR_16_7_6_m_H[16*is + 1]) + + (rxBits[2] * QR_16_7_6_m_H[16*is + 2]) + + (rxBits[3] * QR_16_7_6_m_H[16*is + 3]) + + (rxBits[4] * QR_16_7_6_m_H[16*is + 4]) + + (rxBits[5] * QR_16_7_6_m_H[16*is + 5]) + + (rxBits[6] * QR_16_7_6_m_H[16*is + 6]) + + (rxBits[7] * QR_16_7_6_m_H[16*is + 7]) + + (rxBits[8] * QR_16_7_6_m_H[16*is + 8]) + + (rxBits[9] * QR_16_7_6_m_H[16*is + 9]) + + (rxBits[10] * QR_16_7_6_m_H[16*is + 10]) + + (rxBits[11] * QR_16_7_6_m_H[16*is + 11]) + + (rxBits[12] * QR_16_7_6_m_H[16*is + 12]) + + (rxBits[13] * QR_16_7_6_m_H[16*is + 13]) + + (rxBits[14] * QR_16_7_6_m_H[16*is + 14]) + + (rxBits[15] * QR_16_7_6_m_H[16*is + 15])) % 2) << (8-is); + } + + if (syndromeI > 0) + { + i = 0; + + for (; i < 2; i++) + { + if (QR_16_7_6_m_corr[syndromeI][i] == 0xFF) + { + break; + } + else + { + rxBits[QR_16_7_6_m_corr[syndromeI][i]] ^= 1; // flip bit + } + } + + if (i == 0) + { + return false; + } + } + + return true; +} + + +// ======================================================================================== + + +/* This function init all FEC functions + * it must be called once at startup */ +void InitAllFecFunction(void) +{ + Hamming_7_4_init(); + Hamming_12_8_init(); + Hamming_13_9_init(); + Hamming_15_11_init(); + Hamming_16_11_4_init(); + Golay_20_8_init(); + Golay_23_12_init(); + Golay_24_12_init(); + QR_16_7_6_init(); +} /* End InitAllFEC() */ + + +/* End of file */ diff --git a/src/bptc.c b/src/bptc.c new file mode 100644 index 0000000..7f6ddff --- /dev/null +++ b/src/bptc.c @@ -0,0 +1,560 @@ +/* + ============================================================================ + Name : bptc.c + Author : MotoTRBO_Encrypted_DSD_Author + Version : 1.0 + Date : 2017 - December - 11 + Copyright : None + Description : BPTC library source + ============================================================================ + */ + + +/* Include ------------------------------------------------------------------*/ +#include "dsd.h" + + +/* Define -------------------------------------------------------------------*/ + + +/* Global variables ---------------------------------------------------------*/ +const uint8_t BPTCInterleavingIndex[196] = +{ + 0, 181, 166, 151, + 136, 121, 106, 91, + 76, 61, 46, 31, + 16, 1, 182, 167, + 152, 137, 122, 107, + 92, 77, 62, 47, + 32, 17, 2, 183, + 168, 153, 138, 123, + 108, 93, 78, 63, + 48, 33, 18, 3, + 184, 169, 154, 139, + 124, 109, 94, 79, + 64, 49, 34, 19, + 4, 185, 170, 155, + 140, 125, 110, 95, + 80, 65, 50, 35, + 20, 5, 186, 171, + 156, 141, 126, 111, + 96, 81, 66, 51, + 36, 21, 6, 187, + 172, 157, 142, 127, + 112, 97, 82, 67, + 52, 37, 22, 7, + 188, 173, 158, 143, + 128, 113, 98, 83, + 68, 53, 38, 23, + 8, 189, 174, 159, + 144, 129, 114, 99, + 84, 69, 54, 39, + 24, 9, 190, 175, + 160, 145, 130, 115, + 100, 85, 70, 55, + 40, 25, 10, 191, + 176, 161, 146, 131, + 116, 101, 86, 71, + 56, 41, 26, 11, + 192, 177, 162, 147, + 132, 117, 102, 87, + 72, 57, 42, 27, + 12, 193, 178, 163, + 148, 133, 118, 103, + 88, 73, 58, 43, + 28, 13, 194, 179, + 164, 149, 134, 119, + 104, 89, 74, 59, + 44, 29, 14, 195, + 180, 165, 150, 135, + 120, 105, 90, 75, + 60, 45, 30, 15 +}; + + +const uint8_t BPTCDeInterleavingIndex[196] = +{ + 0, 13, 26, 39, + 52, 65, 78, 91, + 104, 117, 130, 143, + 156, 169, 182, 195, + 12, 25, 38, 51, + 64, 77, 90, 103, + 116, 129, 142, 155, + 168, 181, 194, 11, + 24, 37, 50, 63, + 76, 89, 102, 115, + 128, 141, 154, 167, + 180, 193, 10, 23, + 36, 49, 62, 75, + 88, 101, 114, 127, + 140, 153, 166, 179, + 192, 9, 22, 35, + 48, 61, 74, 87, + 100, 113, 126, 139, + 152, 165, 178, 191, + 8, 21, 34, 47, + 60, 73, 86, 99, + 112, 125, 138, 151, + 164, 177, 190, 7, + 20, 33, 46, 59, + 72, 85, 98, 111, + 124, 137, 150, 163, + 176, 189, 6, 19, + 32, 45, 58, 71, + 84, 97, 110, 123, + 136, 149, 162, 175, + 188, 5, 18, 31, + 44, 57, 70, 83, + 96, 109, 122, 135, + 148, 161, 174, 187, + 4, 17, 30, 43, + 56, 69, 82, 95, + 108, 121, 134, 147, + 160, 173, 186, 3, + 16, 29, 42, 55, + 68, 81, 94, 107, + 120, 133, 146, 159, + 172, 185, 2, 15, + 28, 41, 54, 67, + 80, 93, 106, 119, + 132, 145, 158, 171, + 184, 1, 14, 27, + 40, 53, 66, 79, + 92, 105, 118, 131, + 144, 157, 170, 183 +}; + + +const uint8_t DeInterleaveReverseChannelBptc[32] = +{ + 0, 17, 2, 19, 4, 21, 6, 23, + 8, 25, 10, 27, 12, 29, 14, 31, + 16, 1, 18, 3, 20, 5, 22, 7, + 24, 9, 26, 11, 28, 13, 30, 15 +}; + + +/* Functions ----------------------------------------------------------------*/ + + +/* + * @brief : This function deinterleave the DMR data by using BPTC (196,96) + * + * @param Input : Pointer of DMR input data interleaved (196 bytes) + * + * @param Output : Pointer where DMR deinterleaved data will be written (196 bytes) + * + * @return None + */ +void BPTCDeInterleaveDMRData(uint8_t * Input, uint8_t * Output) +{ + uint32_t i, DeInterleaveIndex; + + for(i = 0; i < 196; i++) + { + DeInterleaveIndex = BPTCDeInterleavingIndex[i]; + Output[DeInterleaveIndex] = (Input[i] & 1); + } +} /* End BPTCDeInterleaveDMRData() */ + + +/* + * @brief : This function extract the 96 bits of a deinteleaved 196 bits + * buffer using BPTC (196,96) + * + * @param InputDeInteleavedData : Pointer of DMR input data deinterleaved (196 bytes) + * + * @param DMRDataExtracted : Pointer where the DMR data will be written (96 bytes) + * + * @return The total number of irrecoverable Hamming check errors + */ +uint32_t BPTC_196x96_Extract_Data(uint8_t InputDeInteleavedData[196], uint8_t DMRDataExtracted[96], uint8_t R[3]) +{ + uint32_t i, j, k; + uint8_t DataMatrix[13][15]; + uint8_t LineUncorrected[15]; + uint8_t LineCorrected[11]; + uint8_t ColumnUncorrected[13]; + uint8_t ColumnCorrected[9]; + uint32_t HammingIrrecoverableErrorNb = 0; + + /* First step : Reconstitute the BPTC 15x11 matrix + * Note : Input data shall be deinterleaved */ + k = 1; /* Discard R(3) bit - See DMR standard chapter B1.1 BPTC (196,96) */ + for(i = 0; i < 13; i++) + { + for(j = 0; j < 15; j++) + { + /* Only the LSBit of the byte is stored */ + DataMatrix[i][j] = (InputDeInteleavedData[k] & 1); + k++; + } + } + + /* R(0) to R(2) may be used to transport some + * Restricted Access System (RAS) information, + * So save these three bytes before reset all of them + * See patent US 2013/0288643 A1 */ + R[0] = DataMatrix[0][2]; /* Save R(0) */ + R[1] = DataMatrix[0][1]; /* Save R(1) */ + R[2] = DataMatrix[0][0]; /* Save R(2) */ + + /* Set to 0 R(0) to R(2) - See DMR standard chapter B1.1 BPTC (196,96) */ + DataMatrix[0][0] = 0; /* R(2) */ + DataMatrix[0][1] = 0; /* R(1) */ + DataMatrix[0][2] = 0; /* R(0) */ + + /* Init the Hamming (15,11,3) library + * Not needed because it has already been done + * in the "InitAllFecFunction()" function */ + //Hamming_15_11_init(); + + /* Init the Hamming (13,9,3) library + * Not needed because it has already been done + * in the "InitAllFecFunction()" function */ + //Hamming_13_9_init(); + + /* Init the Hamming error counter */ + HammingIrrecoverableErrorNb = 0; + + /* Process the the Hamming (15,11,3) code + * check on each line (first time). + * Do not check the 4th last lines */ + for(i = 0; i < 9; i++) + { + /* Get a full line */ + for(j = 0; j < 15; j++) + { + LineUncorrected[j] = DataMatrix[i][j]; + } + + /* Apply Hamming (15,11,3) code correction */ + if(Hamming_15_11_decode(LineUncorrected, LineCorrected, 1) == false) + { + HammingIrrecoverableErrorNb++; + } + + /* Re-inject the line in the matrix (only the util data [11 bit], + * not the Hamming part [4 bit]) */ + for(j = 0; j < 11; j++) + { + DataMatrix[i][j] = LineCorrected[j]; + } + } + + + /* Process the the Hamming (15,11,3) code + * check on each column/row (first time) */ + for(i = 0; i < 15; i++) + { + /* Get a full column */ + for(j = 0; j < 13; j++) + { + ColumnUncorrected[j] = DataMatrix[j][i]; + } + + /* Apply Hamming (15,11,3) code correction */ + if(Hamming_13_9_decode(ColumnUncorrected, ColumnCorrected, 1) == false) + { + HammingIrrecoverableErrorNb++; + } + + /* Re-inject the column in the matrix (only the util data [11 bit], + * not the Hamming part [4 bit]) */ + for(j = 0; j < 9; j++) + { + DataMatrix[j][i] = ColumnCorrected[j]; + } + } + + /* The first Hamming code check has maybe corrected bit + * witch can be use to correct other bit, so make the + * same operation twice */ + + /* Reset the number of irrecoverable errors */ + HammingIrrecoverableErrorNb = 0; + + + /* Process the the Hamming (15,11,3) code + * check on each line (second time). + * Do not check the 4th last lines */ + for(i = 0; i < 9; i++) + { + /* Get a full line */ + for(j = 0; j < 15; j++) + { + LineUncorrected[j] = DataMatrix[i][j]; + } + + /* Apply Hamming (15,11,3) code correction */ + if(Hamming_15_11_decode(LineUncorrected, LineCorrected, 1) == false) + { + /* Row/Line contains irrecoverable errors */ + HammingIrrecoverableErrorNb++; + } + else + { + /* Row/Line contains correct data */ + } + + /* Re-inject the line in the matrix (only the util data [11 bit], + * not the Hamming part [4 bit]) */ + for(j = 0; j < 11; j++) + { + DataMatrix[i][j] = LineCorrected[j]; + } + } + + + /* Process the the Hamming (15,11,3) code + * check on each column/row (second time) */ + for(i = 0; i < 15; i++) + { + /* Get a full column */ + for(j = 0; j < 13; j++) + { + ColumnUncorrected[j] = DataMatrix[j][i]; + } + + /* Apply Hamming (15,11,3) code correction */ + if(Hamming_13_9_decode(ColumnUncorrected, ColumnCorrected, 1) == false) + { + /* Row/Line contains irrecoverable errors */ + HammingIrrecoverableErrorNb++; + } + else + { + /* Row/Line contains correct data */ + } + + /* Re-inject the column in the matrix (only the util data [11 bit], + * not the Hamming part [4 bit]) */ + for(j = 0; j < 9; j++) + { + DataMatrix[j][i] = ColumnCorrected[j]; + } + } + + /* Extract the DMR data (96 bit) from the matrix */ + /* First line : Do not take R(2), R(1) and R(0) */ + k = 0; + + for(i = 3; i < 11; i++) + { + DMRDataExtracted[k] = DataMatrix[0][i]; + k++; + } + + /* Next lines */ + for(i = 1; i < 9; i++) + { + for(j = 0; j < 11; j++) + { + DMRDataExtracted[k] = DataMatrix[i][j]; + k++; + } + } + + /* Return the number of irrecoverable Hamming errors */ + return HammingIrrecoverableErrorNb; +} /* End BPTC_196x96_Extract_Data() */ + + +/* + * @brief : This function extract the 77 bits of a deinteleaved 128 bits + * buffer using BPTC (128,77). + * + * @note : See DMR standard ETSI TS 102 361-1 chapter B.2.1 for details + * + * @param InputDeInteleavedData : Pointer of DMR input data deinterleaved (128 bytes + act as a 16 x 8 byte data matrix) + * + * @param DMRDataExtracted : Pointer where the DMR data will be written (77 bytes) + * DMRDataExtracted[0..71] = Util data (hamming corrected) + * DMRDataExtracted[72..76] = 5 bit of CRC extracted (to be checked) + * + * @return 0 = All is OK + * Other = Error - Irrecoverable Hamming parity check error number + */ +uint32_t BPTC_128x77_Extract_Data(uint8_t InputDataMatrix[8][16], uint8_t DMRDataExtracted[77]) +{ + uint32_t i, j, k; + uint8_t DataMatrix[8][16]; + uint8_t LineUncorrected[16]; + uint8_t LineCorrected[11]; + uint32_t HammingIrrecoverableErrorNb = 0; + uint32_t ParityCheckErrorNb = 0; + uint32_t NbOfOne; + + /* First step : Reconstitute the BPTC 16x8 matrix */ + for(i = 0; i < 8; i++) + { + for(j = 0; j < 16; j++) + { + /* Only the LSBit of the byte is stored */ + DataMatrix[i][j] = (InputDataMatrix[i][j] & 1); + } + } + + /* Init the Hamming (16,11,4) library + * Not needed because it has already been done + * in the "InitAllFecFunction()" function */ + //Hamming_16_11_4_init(); + + /* Process the the Hamming (16,11,4) code + * check on each line. + * Do not check the last line (line nb 8) */ + for(i = 0; i < 7; i++) + { + /* Get a full line */ + for(j = 0; j < 16; j++) + { + LineUncorrected[j] = DataMatrix[i][j]; + } + + /* Apply Hamming (16,11,4) code correction */ + if(Hamming_16_11_4_decode(LineUncorrected, LineCorrected, 1) == false) + { + HammingIrrecoverableErrorNb++; + } + + /* Re-inject the line in the matrix (only the util data [11 bit], + * not the Hamming part [4 bit]) */ + for(j = 0; j < 11; j++) + { + DataMatrix[i][j] = LineCorrected[j]; + } + } + + /* Extract the DMR data (77 bit) from the matrix */ + k = 0; + + /* 2 first lines */ + for(i = 0; i < 2; i++) + { + for(j = 0; j < 11; j++) + { + DMRDataExtracted[k] = DataMatrix[i][j]; + k++; + } + } + + /* 5 Next lines */ + for(i = 2; i < 7; i++) + { + for(j = 0; j < 10; j++) + { + DMRDataExtracted[k] = DataMatrix[i][j]; + k++; + } + } + + /* 5 bit of CRC */ + for(i = 2; i < 7; i++) + { + DMRDataExtracted[k] = DataMatrix[i][10]; + k++; + } + + /* Verify the data integrity by checking + * all column parity bit */ + ParityCheckErrorNb = 0; + for(i = 0; i < 16; i++) + { + NbOfOne = 0; + + /* Get a full column */ + for(j = 0; j < 7; j++) + { + NbOfOne += DataMatrix[j][i]; + } + + /* Check the parity bit (number of "1" + * must be even) */ + if((NbOfOne % 2) != DataMatrix[7][i]) + { + ParityCheckErrorNb++; + } + } + + /* Return the number of irrecoverable Hamming errors + + * the number of parity check error */ + return (HammingIrrecoverableErrorNb + ParityCheckErrorNb); +} /* End BPTC_128x77_Extract_Data() */ + + +/* + * @brief : This function extract the 32 bits of a deinteleaved 32 bits + * buffer using Reverse Channel Single Burst BPTC. + * + * @note : See DMR standard ETSI TS 102 361-1 chapter B.2.2.2 for details + * + * @param InputInterleavedData : Pointer of DMR input data interleaved (32 bytes + act as a 16 x 2 bits data matrix) + * + * @param DMRDataExtracted : Pointer where the DMR data will be written (77 bytes) + * DMRDataExtracted[0..10] = Util data (Hamming corrected) + * DMRDataExtracted[11..15] = Hamming data + * DMRDataExtracted[16..31] = 16 bits of odd parity (to be checked) + * + * @return 0 = All is OK + * Other = Error - Irrecoverable Hamming parity check error number + */ +uint32_t BPTC_16x2_Extract_Data(uint8_t InputInterleavedData[32], uint8_t DMRDataExtracted[32]) +{ + uint32_t i; + uint8_t DataMatrix[32]; + uint8_t LineUncorrected[16]; + uint8_t LineCorrected[11]; + uint32_t HammingIrrecoverableErrorNb = 0; + uint32_t ParityCheckErrorNb = 0; + + for(i = 0; i < 32; i++) + { + DataMatrix[DeInterleaveReverseChannelBptc[i]] = InputInterleavedData[i] & 1; + } + + /* Copy the entire input buffer to the output buffer */ + for(i = 0; i < 32; i++) + { + DMRDataExtracted[i] = DataMatrix[i]; + } + + /* Prepare the line to be corrected */ + for(i = 0; i < 16; i++) + { + LineUncorrected[i] = DataMatrix[i]; + } + + /* Apply Hamming (16,11,4) code correction */ + if(Hamming_16_11_4_decode(LineUncorrected, LineCorrected, 1) == false) + { + HammingIrrecoverableErrorNb++; + } + + /* Copy the corrected content to the output buffer */ + for(i = 0; i < 11; i++) + { + DMRDataExtracted[i] = LineCorrected[i]; + } + + /* Check Parity bits */ + for(i = 0; i < 16; i++) + { + /* Odd parity ==> If data = 1 then parity = 0 + * If data = 0 then parity = 1 */ + if(DMRDataExtracted[i] == DMRDataExtracted[i + 16]) ParityCheckErrorNb++; + } + + //fprintf(stderr, "Hamming ERR=%u ; Parity ERR=%u", HammingIrrecoverableErrorNb, ParityCheckErrorNb); + + /* Return the number of irrecoverable Hamming errors + + * the number of parity check error */ + return (HammingIrrecoverableErrorNb + ParityCheckErrorNb); + +} /* End BPTC_16x2_Extract_Data() */ + + + +/* End of file */ diff --git a/src/dmr_data.c b/src/dmr_data.c index 8d61c0f..1eddd5f 100644 --- a/src/dmr_data.c +++ b/src/dmr_data.c @@ -24,240 +24,407 @@ processDMRdata (dsd_opts * opts, dsd_state * state) int i, dibit; int *dibit_p; char sync[25]; - char syncdata[25]; - char cachdata[13]; - char cc[5]; + char syncdata[48]; + char cachdata[13] = {0}; + char cc[5] = {0}; + char ccAscii[5] = {0}; char bursttype[5]; - int dcc_buf; + unsigned int burst; + char info[196]; + char SlotType[20]; + unsigned int SlotTypeOk; + + /* Remove warning compiler */ + //UNUSED_VARIABLE(cachdata[0]); + //UNUSED_VARIABLE(cc[0]); + //UNUSED_VARIABLE(ccAscii[0]); #ifdef DMR_DUMP - int k; char syncbits[49]; char cachbits[25]; + int k; #endif - cc[0]=0; - cc[1]=0; - cc[2]=0; - cc[3]=0; //initialize with zeroes to prevent segfault down below - state->dmr_color_code = 0; //reset - cc[4] = 0; + + ccAscii[4] = 0; bursttype[4] = 0; dibit_p = state->dibit_buf_p - 90; // CACH for (i = 0; i < 12; i++) + { + dibit = *dibit_p; + dibit_p++; + if (opts->inverted_dmr == 1) { - dibit = *dibit_p; - dibit_p++; - if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - cachdata[i] = dibit; - if (i == 2) - { - state->currentslot = (1 & (dibit >> 1)); // bit 1 - if (state->currentslot == 0) - { - state->slot0light[0] = '['; - state->slot0light[6] = ']'; - state->slot1light[0] = ' '; - state->slot1light[6] = ' '; - } - else - { - state->slot1light[0] = '['; - state->slot1light[6] = ']'; - state->slot0light[0] = ' '; - state->slot0light[6] = ' '; - } - } + dibit = (dibit ^ 2); } + cachdata[i] = dibit; + if (i == 2) + { + /* Change the current slot only if we are in relayed mode (not in direct mode) */ + if(state->directmode == 0) state->currentslot = (1 & (dibit >> 1)); // bit 1 + + if (state->currentslot == 0) + { + state->slot1light[0] = '['; + state->slot1light[6] = ']'; + state->slot2light[0] = ' '; + state->slot2light[6] = ' '; + } + else + { + state->slot2light[0] = '['; + state->slot2light[6] = ']'; + state->slot1light[0] = ' '; + state->slot1light[6] = ' '; + } + } + } cachdata[12] = 0; #ifdef DMR_DUMP k = 0; for (i = 0; i < 12; i++) - { - dibit = cachdata[i]; - cachbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 - k++; - cachbits[k] = (1 & dibit) + 48; // bit 0 - k++; - } + { + dibit = cachdata[i]; + cachbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 + k++; + cachbits[k] = (1 & dibit) + 48; // bit 0 + k++; + } cachbits[24] = 0; - fprintf (stderr, "%s ", cachbits); + fprintf(stderr, "%s ", cachbits); #endif - // current slot - dibit_p += 49; + // Current slot - First half - Data Payload - 1st part + for (i = 0; i < 49; i++) + { + dibit = *dibit_p; + dibit_p++; + if (opts->inverted_dmr == 1) + { + dibit = (dibit ^ 2); + } + info[2*i] = (1 & (dibit >> 1)); // bit 1 + info[(2*i)+1] = (1 & dibit); // bit 0 + } // slot type dibit = *dibit_p; dibit_p++; if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - //c[0] = (1 & (dibit >> 1)) + 48; // bit 1 - //cc[1] = (1 & dibit) + 48; // bit 0 - cc[0] = (1 & (dibit >> 1)); // bit 1 - cc[1] = (1 & dibit); // bit 0 - dcc_buf = 0; - //dcc_buf = (1 & (dibit >> 1)) | (1 & dibit); - dcc_buf = ((1 & (dibit >> 1)) + 48) | ((1 & dibit) + 48); - + { + dibit = (dibit ^ 2); + } + cc[0] = (1 & (dibit >> 1)); // bit 1 + cc[1] = (1 & dibit); // bit 0 + SlotType[0] = (1 & (dibit >> 1)); // bit 1 + SlotType[1] = (1 & dibit); // bit 0 dibit = *dibit_p; dibit_p++; if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - //cc[2] = (1 & (dibit >> 1)) + 48; // bit 1 - //cc[3] = (1 & dibit) + 48; // bit 0 - cc[2] = (1 & (dibit >> 1)); // bit 1 - cc[3] = (1 & dibit); // bit 0 - - dcc_buf = dcc_buf << 2; - //dcc_buf = (1 & (dibit >> 1)) | (1 & dibit); - dcc_buf = ((1 & (dibit >> 1)) + 48) | ((1 & dibit) + 48); - - //state->dmr_color_code = dcc_buf; - state->dmr_color_code = (unsigned int)((cc[0] << 3) + (cc[1] << 2) + (cc[2] << 1) + cc[3]); - //cc[2] = (1 & (dibit >> 1)); // bit 1 - //cc[3] = (1 & dibit); // bit 0 + { + dibit = (dibit ^ 2); + } + cc[2] = (1 & (dibit >> 1)); // bit 1 + cc[3] = (1 & dibit); // bit 0 + SlotType[2] = (1 & (dibit >> 1)); // bit 1 + SlotType[3] = (1 & dibit); // bit 0 + ccAscii[0] = cc[0] + '0'; + ccAscii[1] = cc[1] + '0'; + ccAscii[2] = cc[2] + '0'; + ccAscii[3] = cc[3] + '0'; + ccAscii[4] = cc[4] + '\0'; dibit = *dibit_p; dibit_p++; if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - bursttype[0] = (1 & (dibit >> 1)) + 48; // bit 1 - bursttype[1] = (1 & dibit) + 48; // bit 0 + { + dibit = (dibit ^ 2); + } +// burst = (unsigned int)((1 & (dibit >> 1)) << 3); +// burst |= (unsigned int)((1 & (dibit >> 0)) << 2); +// bursttype[0] = (1 & (dibit >> 1)) + 48; // bit 1 +// bursttype[1] = (1 & dibit) + 48; // bit 0 + SlotType[4] = (1 & (dibit >> 1)); // bit 1 + SlotType[5] = (1 & dibit); // bit 0 dibit = *dibit_p; dibit_p++; if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - bursttype[2] = (1 & (dibit >> 1)) + 48; // bit 1 - bursttype[3] = (1 & dibit) + 48; // bit 0 + { + dibit = (dibit ^ 2); + } +// burst |= (unsigned int)((1 & (dibit >> 1)) << 1); +// burst |= (unsigned int)((1 & (dibit >> 0)) << 0); +// bursttype[2] = (1 & (dibit >> 1)) + 48; // bit 1 +// bursttype[3] = (1 & dibit) + 48; // bit 0 + SlotType[6] = (1 & (dibit >> 1)); // bit 1 + SlotType[7] = (1 & dibit); // bit 0 - // parity bit + // Parity bit + dibit = *dibit_p; dibit_p++; - - if (strcmp (bursttype, "0000") == 0) - { - sprintf (state->fsubtype, " PI Header "); - } - else if (strcmp (bursttype, "0001") == 0) - { - sprintf (state->fsubtype, " VOICE Header "); - } - else if (strcmp (bursttype, "0010") == 0) - { - sprintf (state->fsubtype, " TLC "); - } - else if (strcmp (bursttype, "0011") == 0) - { - sprintf (state->fsubtype, " CSBK "); - } - else if (strcmp (bursttype, "0100") == 0) - { - sprintf (state->fsubtype, " MBC Header "); - } - else if (strcmp (bursttype, "0101") == 0) - { - sprintf (state->fsubtype, " MBC "); - } - else if (strcmp (bursttype, "0110") == 0) - { - sprintf (state->fsubtype, " DATA Header "); - } - else if (strcmp (bursttype, "0111") == 0) - { - sprintf (state->fsubtype, " RATE 1/2 DATA"); - } - else if (strcmp (bursttype, "1000") == 0) - { - sprintf (state->fsubtype, " RATE 3/4 DATA"); - } - else if (strcmp (bursttype, "1001") == 0) - { - sprintf (state->fsubtype, " Slot idle "); - } - else if (strcmp (bursttype, "1010") == 0) - { - sprintf (state->fsubtype, " Rate 1 DATA "); - } - else - { - sprintf (state->fsubtype, " "); - } + if (opts->inverted_dmr == 1) + { + dibit = (dibit ^ 2); + } + SlotType[8] = (1 & (dibit >> 1)); // bit 1 + SlotType[9] = (1 & dibit); // bit 0 // signaling data or sync for (i = 0; i < 24; i++) + { + dibit = *dibit_p; + dibit_p++; + if (opts->inverted_dmr == 1) { - dibit = *dibit_p; - dibit_p++; - if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - syncdata[i] = dibit; - sync[i] = (dibit | 1) + 48; + dibit = (dibit ^ 2); } + syncdata[2*i] = (1 & (dibit >> 1)); // bit 1 + syncdata[(2*i)+1] = (1 & dibit); // bit 0 + sync[i] = (dibit | 1) + 48; + } sync[24] = 0; - syncdata[24] = 0; #ifdef DMR_DUMP k = 0; for (i = 0; i < 24; i++) - { - dibit = syncdata[i]; - syncbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 - k++; - syncbits[k] = (1 & dibit) + 48; // bit 0 - k++; - } + { + syncbits[k] = syncdata[(k*2)] + 48; // bit 1 + k++; + syncbits[k] = syncdata[(k*2)+1] + 48; // bit 0 + k++; + } syncbits[48] = 0; - fprintf (stderr, "%s ", syncbits); + fprintf(stderr, "%s ", syncbits); #endif - if ((strcmp (sync, DMR_BS_DATA_SYNC) == 0) || (strcmp (sync, DMR_MS_DATA_SYNC) == 0)) + if((strcmp (sync, DMR_BS_DATA_SYNC) == 0) || (strcmp (sync, DMR_MS_DATA_SYNC) == 0)) + { + if (state->currentslot == 0) { - if (state->currentslot == 0) - { - sprintf (state->slot0light, "[slot0]"); - } - else - { - sprintf (state->slot1light, "[slot1]"); - } + sprintf(state->slot1light, "[slot1]"); } + else + { + sprintf(state->slot2light, "[slot2]"); + } + } + else if(strcmp (sync, DMR_DIRECT_MODE_TS1_DATA_SYNC) == 0) + { + state->currentslot = 0; + sprintf(state->slot1light, "[sLoT1]"); + } + + else if(strcmp (sync, DMR_DIRECT_MODE_TS2_DATA_SYNC) == 0) + { + state->currentslot = 1; + sprintf(state->slot2light, "[sLoT2]"); + } if (opts->errorbars == 1) - { - fprintf (stderr, "%s %s ", state->slot0light, state->slot1light); - } + { + fprintf(stderr, "%s %s ", state->slot1light, state->slot2light); + } - // current slot second half, cach, next slot 1st half - skipDibit (opts, state, 120); + // Slot type - Second part - Parity bit + for (i = 0; i < 5; i++) + { + dibit = getDibit(opts, state); + if (opts->inverted_dmr == 1) + { + dibit = (dibit ^ 2); + } + SlotType[(i*2) + 10] = (1 & (dibit >> 1)); // bit 1 + SlotType[(i*2) + 11] = (1 & dibit); // bit 0 + } + + /* Check and correct the SlotType (apply Golay(20,8) FEC check) */ + if(Golay_20_8_decode((unsigned char *)SlotType)) SlotTypeOk = 1; + else SlotTypeOk = 0; + + /* Slot Type parity checked => Fill the color code */ + state->color_code = (unsigned int)((cc[0] << 3) + (cc[1] << 2) + (cc[2] << 1) + cc[3]); + state->color_code_ok = SlotTypeOk; + + /* Reconstitute the burst type */ + burst = (unsigned int)((SlotType[4] << 3) + (SlotType[5] << 2) + (SlotType[6] << 1) + SlotType[7]); + + /* Reconstitute the burst type */ + bursttype[0] = SlotType[4] + '0'; + bursttype[1] = SlotType[5] + '0'; + bursttype[2] = SlotType[6] + '0'; + bursttype[3] = SlotType[7] + '0'; + bursttype[4] = '\0'; + + if (strcmp (bursttype, "0000") == 0) + { + sprintf(state->fsubtype, " PI Header "); + } + else if (strcmp (bursttype, "0001") == 0) + { + sprintf(state->fsubtype, " VOICE Header "); + } + else if (strcmp (bursttype, "0010") == 0) + { + sprintf(state->fsubtype, " TLC "); + } + else if (strcmp (bursttype, "0011") == 0) + { + sprintf(state->fsubtype, " CSBK "); + } + else if (strcmp (bursttype, "0100") == 0) + { + sprintf(state->fsubtype, " MBC Header "); + } + else if (strcmp (bursttype, "0101") == 0) + { + sprintf(state->fsubtype, " MBC "); + } + else if (strcmp (bursttype, "0110") == 0) + { + sprintf(state->fsubtype, " DATA Header "); + } + else if (strcmp (bursttype, "0111") == 0) + { + sprintf(state->fsubtype, " RATE 1/2 DATA"); + } + else if (strcmp (bursttype, "1000") == 0) + { + sprintf(state->fsubtype, " RATE 3/4 DATA"); + } + else if (strcmp (bursttype, "1001") == 0) + { + sprintf(state->fsubtype, " Slot idle "); + } + else if (strcmp (bursttype, "1010") == 0) + { + sprintf(state->fsubtype, " Rate 1 DATA "); + } + else + { + sprintf(state->fsubtype, " "); + } + + // Current slot - First half - Data Payload - 1st part + for (i = 0; i < 49; i++) + { + dibit = getDibit(opts, state); + if (opts->inverted_dmr == 1) + { + dibit = (dibit ^ 2); + } + info[(2*i) + 98] = (1 & (dibit >> 1)); // bit 1 + info[(2*i) + 99] = (1 & dibit); // bit 0 + } + + // Skip cach (24 bit = 12 dibit) and next slot 1st half (98 + 10 bit = 49 + 5 dibit) + skipDibit (opts, state, 12 + 49 + 5); if (opts->errorbars == 1) + { + /* Print the color code */ + fprintf(stderr, "| Color Code=%02d ", (int)state->color_code); + + if(state->color_code_ok) fprintf(stderr, "(OK) |"); + else fprintf(stderr, "(CRC ERR) |"); + + if (strcmp (state->fsubtype, " ") == 0) { - if (strcmp (state->fsubtype, " ") == 0) - { - fprintf (stderr, " Unknown burst type: %s\n", bursttype); - } - else - { - fprintf (stderr, "%s\n", state->fsubtype); - } + fprintf(stderr, " Unknown burst type: %s", bursttype); } -} + else + { + fprintf(stderr, "%s", state->fsubtype); + } + } + + switch(burst) + { + /* Burst = PI header */ + case 0b0000: + { + break; + } + + /* Burst = Voice LC header */ + case 0b0001: + { + /* Extract data from Voice LC Header */ + ProcessDmrVoiceLcHeader(opts, state, (uint8_t *)info, (uint8_t *)syncdata, (uint8_t *)SlotType); + break; + } + + /* Burst = TLC */ + case 0b0010: + { + ProcessDmrTerminaisonLC(opts, state, (uint8_t *)info, (uint8_t *)syncdata, (uint8_t *)SlotType); + break; + } + + /* Burst = CSBK */ + case 0b0011: + { + break; + } + + /* Burst = MBC Header */ + case 0b0100: + { + break; + } + + /* Burst = MBC */ + case 0b0101: + { + break; + } + + /* Burst = DATA Header */ + case 0b0110: + { + break; + } + + /* Burst = RATE 1/2 DATA */ + case 0b0111: + { + break; + } + + /* Burst = RATE 3/4 DATA */ + case 0b1000: + { + break; + } + + /* Burst = Slot idle */ + case 0b1001: + { + break; + } + + /* Burst = Rate 1 DATA */ + case 0b1010: + { + break; + } + + /* Default */ + default: + { + /* Nothing to do */ + break; + } + } /* End switch(burst) */ + + if (opts->errorbars == 1) + { + fprintf(stderr, "\n"); + } + +} /* End processDMRdata() */ diff --git a/src/dmr_encryption.c b/src/dmr_encryption.c new file mode 100644 index 0000000..afb8e5a --- /dev/null +++ b/src/dmr_encryption.c @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010 DSD Author + * GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6 F630 FAA2 635D 3F1D 7FD0) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "dsd.h" +#include "dmr_const.h" +#include "p25p1_check_hdu.h" + + +void ProcessDMREncryption (dsd_opts * opts, dsd_state * state) +{ + uint32_t i, j; + uint32_t Frame; + TimeSlotVoiceSuperFrame_t * TSVoiceSupFrame = NULL; + int *errs; + int *errs2; + + /* + * Currently encryption is not supported in this public version... + */ + + /* Check the current time slot */ + if(state->currentslot == 0) + { + TSVoiceSupFrame = &state->TS1SuperFrame; + } + else + { + TSVoiceSupFrame = &state->TS2SuperFrame; + } + + /* Apply encryption here + * + * A DMR superframe = 6 frames x 3 AMBE voice sample of 49 bits each + * uint8_t KeyStream[6][3][49]; + * + * 1�) Initialize the "KeyStream" buffer (total 882 bits) with correct bits (depending of + * encryption mode used, MotoTRBO BP, Hytera BP, MotoTRBO EP, MotoTRBO AES... + * + * 2�) Apply a XOR between "KeyStream" and "TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i][j]" + * like code : + * for(Frame = 0; Frame < 6; Frame++) + * { + * for(i = 0; i < 3; i++) + * { + * for(j = 0; j < 49; j++) + * { + * TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i] ^= KeyStream[Frame][i][j]; + * } + * } + * } + * + * 3�) Play all AMBE decoded sample (see the "for" loop below) + * + * */ + + /* + * Play all AMBE voice samples + * 1 DMR voice superframe = 6 DMR frames */ + for(Frame = 0; Frame < 6; Frame++) + { + /* 1 DMR frame contains 3 AMBE voice samples */ + for(i = 0; i < 3; i++) + { + errs = (int*)&(TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].errs1[i]); + errs2 = (int*)&(TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].errs2[i]); + state->errs = TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].errs1[i]; //correct placement + state->errs2 = TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].errs2[i]; //correct placement + + mbe_processAmbe2450Dataf (state->audio_out_temp_buf, errs, errs2, state->err_str, + TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i], + state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); + + if (opts->mbe_out_f != NULL) + { + saveAmbe2450Data (opts, state, TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i]); + } + if (opts->errorbars == 1) + { + fprintf(stderr, "%s", state->err_str); + } + + state->debug_audio_errors += *errs2; + + processAudio(opts, state); + + if (opts->wav_out_f != NULL) + { + writeSynthesizedVoice (opts, state); + } + + if (opts->audio_out == 1 && opts->p25enc != 1) //play only if not equal to enc value, going to set p25enc to 1 if enc detected?? + { + playSynthesizedVoice (opts, state); + } + } /* End for(i = 0; i < 3; i++) */ + } /* End for(Frame = 0; Frame < 6; Frame++) */ +} /* End ProcessDMREncryption() */ + + + +/* End of file */ diff --git a/src/dmr_sync.c b/src/dmr_sync.c new file mode 100644 index 0000000..857276e --- /dev/null +++ b/src/dmr_sync.c @@ -0,0 +1,839 @@ +/* + * Copyright (C) 2010 DSD Author + * GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6 F630 FAA2 635D 3F1D 7FD0) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "dsd.h" + +//#define PRINT_PI_HEADER_BYTES +//#define PRINT_VOICE_LC_HEADER_BYTES +//#define PRINT_TERMINAISON_LC_BYTES +//#define PRINT_VOICE_BURST_BYTES + + +void ProcessDmrVoiceLcHeader(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t syncdata[48], uint8_t SlotType[20]) +{ + uint32_t i, j, k; + uint32_t CRCExtracted = 0; + uint32_t CRCComputed = 0; + uint32_t CRCCorrect = 0; + uint32_t IrrecoverableErrors = 0; + uint8_t DeInteleavedData[196]; + uint8_t DmrDataBit[96]; + uint8_t DmrDataByte[12]; + TimeSlotVoiceSuperFrame_t * TSVoiceSupFrame = NULL; + uint8_t R[3]; + uint8_t BPTCReservedBits = 0; + + /* Remove warning compiler */ + //UNUSED_VARIABLE(syncdata[0]); + //UNUSED_VARIABLE(SlotType[0]); + //UNUSED_VARIABLE(BPTCReservedBits); + + /* Check the current time slot */ + if(state->currentslot == 0) + { + TSVoiceSupFrame = &state->TS1SuperFrame; + } + else + { + TSVoiceSupFrame = &state->TS2SuperFrame; + } + + CRCExtracted = 0; + CRCComputed = 0; + IrrecoverableErrors = 0; + + /* Deinterleave DMR data */ + BPTCDeInterleaveDMRData(info, DeInteleavedData); + + /* Extract the BPTC 196,96 DMR data */ + IrrecoverableErrors = BPTC_196x96_Extract_Data(DeInteleavedData, DmrDataBit, R); + + /* Fill the reserved bit (R(0)-R(2) of the BPTC(196,96) block) */ + BPTCReservedBits = (R[0] & 0x01) | ((R[1] << 1) & 0x02) | ((R[2] << 2) & 0x08); + + /* Convert the 96 bit of voice LC Header data into 12 bytes */ + k = 0; + for(i = 0; i < 12; i++) + { + DmrDataByte[i] = 0; + for(j = 0; j < 8; j++) + { + DmrDataByte[i] = DmrDataByte[i] << 1; + DmrDataByte[i] = DmrDataByte[i] | (DmrDataBit[k] & 0x01); + k++; + } + } + + /* Fill the CRC extracted (before Reed-Solomon (12,9) FEC correction) */ + CRCExtracted = 0; + for(i = 0; i < 24; i++) + { + CRCExtracted = CRCExtracted << 1; + CRCExtracted = CRCExtracted | (uint32_t)(DmrDataBit[i + 72] & 1); + } + + /* Apply the CRC mask (see DMR standard B.3.12 Data Type CRC Mask) */ + CRCExtracted = CRCExtracted ^ 0x969696; + + /* Check/correct the full link control data and compute the Reed-Solomon (12,9) CRC */ + CRCCorrect = ComputeAndCorrectFullLinkControlCrc(DmrDataByte, &CRCComputed, 0x969696); + + /* Convert corrected 12 bytes into 96 bits */ + for(i = 0, j = 0; i < 12; i++, j+=8) + { + DmrDataBit[j + 0] = (DmrDataByte[i] >> 7) & 0x01; + DmrDataBit[j + 1] = (DmrDataByte[i] >> 6) & 0x01; + DmrDataBit[j + 2] = (DmrDataByte[i] >> 5) & 0x01; + DmrDataBit[j + 3] = (DmrDataByte[i] >> 4) & 0x01; + DmrDataBit[j + 4] = (DmrDataByte[i] >> 3) & 0x01; + DmrDataBit[j + 5] = (DmrDataByte[i] >> 2) & 0x01; + DmrDataBit[j + 6] = (DmrDataByte[i] >> 1) & 0x01; + DmrDataBit[j + 7] = (DmrDataByte[i] >> 0) & 0x01; + } + + /* Store the Protect Flag (PF) bit */ + TSVoiceSupFrame->FullLC.ProtectFlag = (unsigned int)(DmrDataBit[0]); + + /* Store the Reserved bit */ + TSVoiceSupFrame->FullLC.Reserved = (unsigned int)(DmrDataBit[1]); + + /* Store the Full Link Control Opcode (FLCO) */ + TSVoiceSupFrame->FullLC.FullLinkControlOpcode = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[2], 6); + + /* Store the Feature set ID (FID) */ + TSVoiceSupFrame->FullLC.FeatureSetID = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[8], 8); + //state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + + /* Store the Service Options */ + TSVoiceSupFrame->FullLC.ServiceOptions = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[16], 8); + + /* Store the Group address (Talk Group) */ + TSVoiceSupFrame->FullLC.GroupAddress = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[24], 24); + //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + /* Store the Source address */ + TSVoiceSupFrame->FullLC.SourceAddress = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[48], 24); + //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + + if((IrrecoverableErrors == 0) && CRCCorrect) + { + /* CRC is correct so consider the Full LC data as correct/valid */ + TSVoiceSupFrame->FullLC.DataValidity = 1; + } + else + { + /* CRC checking error, so consider the Full LC data as invalid */ + TSVoiceSupFrame->FullLC.DataValidity = 0; + } + + /* Print the destination ID (TG) and the source ID */ + fprintf(stderr, "| TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); + fprintf(stderr, "FID=0x%02X ", TSVoiceSupFrame->FullLC.FeatureSetID); + //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + //state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + + //HERE HERE do some work to get this Emergency and BP modes to display in ncurses + //state->dmr_so = TSVoiceSupFrame->FullLC.ServiceOptions; + + if(TSVoiceSupFrame->FullLC.ServiceOptions & 0x80) fprintf(stderr, "Emergency "); + if(TSVoiceSupFrame->FullLC.ServiceOptions & 0x40) + { + /* By default select the basic privacy (BP), if the encryption mode is EP ARC4 or AES256 + * a PI Header will be sent with the encryption mode and DSD will upgrade automatically + * the new encryption mode */ + opts->EncryptionMode = MODE_BASIC_PRIVACY; + + fprintf(stderr, "Encrypted "); + } + else + { + opts->EncryptionMode = MODE_UNENCRYPTED; + fprintf(stderr, "Clear/Unencrypted "); + } + + /* Check the "Reserved" bits */ + if(TSVoiceSupFrame->FullLC.ServiceOptions & 0x30) + { + /* Experimentally determined with DSD+, when the "Reserved" bit field + * is equal to 0x2, this is a TXI call */ + if((TSVoiceSupFrame->FullLC.ServiceOptions & 0x30) == 0x20) fprintf(stderr, "TXI "); + else fprintf(stderr, "Reserved=%d ", (TSVoiceSupFrame->FullLC.ServiceOptions & 0x30) >> 4); + } + if(TSVoiceSupFrame->FullLC.ServiceOptions & 0x08) fprintf(stderr, "Broadcast "); + if(TSVoiceSupFrame->FullLC.ServiceOptions & 0x04) fprintf(stderr, "OVCM "); + if(TSVoiceSupFrame->FullLC.ServiceOptions & 0x03) + { + if((TSVoiceSupFrame->FullLC.ServiceOptions & 0x03) == 0x01) fprintf(stderr, "Priority 1 "); + else if((TSVoiceSupFrame->FullLC.ServiceOptions & 0x03) == 0x02) fprintf(stderr, "Priority 2 "); + else if((TSVoiceSupFrame->FullLC.ServiceOptions & 0x03) == 0x03) fprintf(stderr, "Priority 3 "); + else fprintf(stderr, "No Priority "); /* We should never go here */ + } + fprintf(stderr, "Call "); + + if(TSVoiceSupFrame->FullLC.DataValidity) fprintf(stderr, "(OK) "); + else if(IrrecoverableErrors == 0) fprintf(stderr, "RAS (FEC OK/CRC ERR)"); + else fprintf(stderr, "(FEC FAIL/CRC ERR)"); + +#ifdef PRINT_VOICE_LC_HEADER_BYTES + fprintf(stderr, "\n"); + fprintf(stderr, "VOICE LC HEADER : "); + for(i = 0; i < 12; i++) + { + fprintf(stderr, "0x%02X", DmrDataByte[i]); + if(i != 11) fprintf(stderr, " - "); + } + + fprintf(stderr, "\n"); + + fprintf(stderr, "BPTC(196,96) Reserved bit R(0)-R(2) = 0x%02X\n", BPTCReservedBits); + + fprintf(stderr, "CRC extracted = 0x%04X - CRC computed = 0x%04X - ", CRCExtracted, CRCComputed); + + if((IrrecoverableErrors == 0) && CRCCorrect) + { + fprintf(stderr, "CRCs are equal + FEC OK !\n"); + } + else if(IrrecoverableErrors == 0) + { + else fprintf(stderr, "FEC correctly corrected but CRCs are incorrect\n"); + } + else + { + fprintf(stderr, "ERROR !!! CRCs are different and FEC Failed !\n"); + } + + fprintf(stderr, "Hamming Irrecoverable Errors = %u\n", IrrecoverableErrors); +#endif /* PRINT_VOICE_LC_HEADER_BYTES */ +} /* End ProcessDmrVoiceLcHeader() */ + + +void ProcessDmrTerminaisonLC(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t syncdata[48], uint8_t SlotType[20]) +{ + uint32_t i, j, k; + uint32_t CRCExtracted = 0; + uint32_t CRCComputed = 0; + uint32_t CRCCorrect = 0; + uint32_t IrrecoverableErrors = 0; + uint8_t DeInteleavedData[196]; + uint8_t DmrDataBit[96]; + uint8_t DmrDataByte[12]; + TimeSlotVoiceSuperFrame_t * TSVoiceSupFrame = NULL; + uint8_t R[3]; + uint8_t BPTCReservedBits = 0; + + /* Remove warning compiler */ + //UNUSED_VARIABLE(opts); + //UNUSED_VARIABLE(syncdata[0]); + //UNUSED_VARIABLE(SlotType[0]); + //UNUSED_VARIABLE(BPTCReservedBits); + + /* Check the current time slot */ + if(state->currentslot == 0) + { + TSVoiceSupFrame = &state->TS1SuperFrame; + } + else + { + TSVoiceSupFrame = &state->TS2SuperFrame; + } + + CRCExtracted = 0; + CRCComputed = 0; + IrrecoverableErrors = 0; + + /* Deinterleave DMR data */ + BPTCDeInterleaveDMRData(info, DeInteleavedData); + + /* Extract the BPTC 196,96 DMR data */ + IrrecoverableErrors = BPTC_196x96_Extract_Data(DeInteleavedData, DmrDataBit, R); + + /* Fill the reserved bit (R(0)-R(2) of the BPTC(196,96) block) */ + BPTCReservedBits = (R[0] & 0x01) | ((R[1] << 1) & 0x02) | ((R[2] << 2) & 0x08); + + /* Convert the 96 bit of voice LC Header data into 12 bytes */ + k = 0; + for(i = 0; i < 12; i++) + { + DmrDataByte[i] = 0; + for(j = 0; j < 8; j++) + { + DmrDataByte[i] = DmrDataByte[i] << 1; + DmrDataByte[i] = DmrDataByte[i] | (DmrDataBit[k] & 0x01); + k++; + } + } + + /* Fill the CRC extracted (before Reed-Solomon (12,9) FEC correction) */ + CRCExtracted = 0; + for(i = 0; i < 24; i++) + { + CRCExtracted = CRCExtracted << 1; + CRCExtracted = CRCExtracted | (uint32_t)(DmrDataBit[i + 72] & 1); + } + + /* Apply the CRC mask (see DMR standard B.3.12 Data Type CRC Mask) */ + CRCExtracted = CRCExtracted ^ 0x999999; + + /* Check/correct the full link control data and compute the Reed-Solomon (12,9) CRC */ + CRCCorrect = ComputeAndCorrectFullLinkControlCrc(DmrDataByte, &CRCComputed, 0x999999); + + /* Convert corrected 12 bytes into 96 bits */ + for(i = 0, j = 0; i < 12; i++, j+=8) + { + DmrDataBit[j + 0] = (DmrDataByte[i] >> 7) & 0x01; + DmrDataBit[j + 1] = (DmrDataByte[i] >> 6) & 0x01; + DmrDataBit[j + 2] = (DmrDataByte[i] >> 5) & 0x01; + DmrDataBit[j + 3] = (DmrDataByte[i] >> 4) & 0x01; + DmrDataBit[j + 4] = (DmrDataByte[i] >> 3) & 0x01; + DmrDataBit[j + 5] = (DmrDataByte[i] >> 2) & 0x01; + DmrDataBit[j + 6] = (DmrDataByte[i] >> 1) & 0x01; + DmrDataBit[j + 7] = (DmrDataByte[i] >> 0) & 0x01; + } + + /* Store the Protect Flag (PF) bit */ + TSVoiceSupFrame->FullLC.ProtectFlag = (unsigned int)(DmrDataBit[0]); + + /* Store the Reserved bit */ + TSVoiceSupFrame->FullLC.Reserved = (unsigned int)(DmrDataBit[1]); + + /* Store the Full Link Control Opcode (FLCO) */ + TSVoiceSupFrame->FullLC.FullLinkControlOpcode = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[2], 6); + + /* Store the Feature set ID (FID) */ + TSVoiceSupFrame->FullLC.FeatureSetID = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[8], 8); + //state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + + /* Store the Service Options */ + TSVoiceSupFrame->FullLC.ServiceOptions = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[16], 8); + + /* Store the Group address (Talk Group) */ + TSVoiceSupFrame->FullLC.GroupAddress = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[24], 24); + //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + /* Store the Source address */ + TSVoiceSupFrame->FullLC.SourceAddress = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[48], 24); + //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + if((IrrecoverableErrors == 0))// && CRCCorrect) + { + /* CRC is correct so consider the Full LC data as correct/valid */ + TSVoiceSupFrame->FullLC.DataValidity = 1; + } + else + { + /* CRC checking error, so consider the Full LC data as invalid */ + TSVoiceSupFrame->FullLC.DataValidity = 0; + } + + /* Print the destination ID (TG) and the source ID */ + fprintf(stderr, "| TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); + fprintf(stderr, "FID=0x%02X ", TSVoiceSupFrame->FullLC.FeatureSetID); + //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + //state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + + + if((IrrecoverableErrors == 0) && CRCCorrect) + { + fprintf(stderr, "(OK) "); + //only set on good CRC value or corrected values + state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + state->dmr_so = TSVoiceSupFrame->FullLC.ServiceOptions; + state->dmr_color_code = state->color_code; + } + else if(IrrecoverableErrors == 0) + { + fprintf(stderr, "RAS (FEC OK/CRC ERR)"); + //only set on good CRC value or corrected values + state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + state->dmr_so = TSVoiceSupFrame->FullLC.ServiceOptions; + state->dmr_color_code = state->color_code; + } + else fprintf(stderr, "(FEC FAIL/CRC ERR)"); + +#ifdef PRINT_TERMINAISON_LC_BYTES + fprintf(stderr, "\n"); + fprintf(stderr, "TERMINAISON LINK CONTROL (TLC) : "); + for(i = 0; i < 12; i++) + { + fprintf(stderr, "0x%02X", DmrDataByte[i]); + if(i != 11) fprintf(stderr, " - "); + } + + fprintf(stderr, "\n"); + + fprintf(stderr, "BPTC(196,96) Reserved bit R(0)-R(2) = 0x%02X\n", BPTCReservedBits); + + fprintf(stderr, "CRC extracted = 0x%04X - CRC computed = 0x%04X - ", CRCExtracted, CRCComputed); + + if((IrrecoverableErrors == 0) && CRCCorrect) + { + fprintf(stderr, "CRCs are equal + FEC OK !\n"); + } + else if(IrrecoverableErrors == 0) + { + else fprintf(stderr, "FEC correctly corrected but CRCs are incorrect\n"); + } + else + { + fprintf(stderr, "ERROR !!! CRCs are different and FEC Failed !\n"); + } + + fprintf(stderr, "Hamming Irrecoverable Errors = %u\n", IrrecoverableErrors); +#endif /* PRINT_TERMINAISON_LC_BYTES */ +} /* End ProcessDmrTerminaisonLC() */ + + +/* Extract the Link Control (LC) embedded in the SYNC + * of a DMR voice superframe */ +void ProcessVoiceBurstSync(dsd_opts * opts, dsd_state * state) +{ + uint32_t i, j, k; + uint32_t Burst; + uint8_t BptcDataMatrix[8][16]; + uint8_t LC_DataBit[77]; + uint8_t LC_DataBytes[10]; + TimeSlotVoiceSuperFrame_t * TSVoiceSupFrame = NULL; + uint32_t IrrecoverableErrors; + uint8_t CRCExtracted; + uint8_t CRCComputed; + uint32_t CRCCorrect = 0; + + /* Remove warning compiler */ + //UNUSED_VARIABLE(opts); + + /* Check the current time slot */ + if(state->currentslot == 0) + { + TSVoiceSupFrame = &state->TS1SuperFrame; + } + else + { + TSVoiceSupFrame = &state->TS2SuperFrame; + } + + /* First step : Reconstitute the BPTC 16x8 matrix */ + Burst = 1; /* Burst B to E contains embedded signaling data */ + k = 0; + for(i = 0; i < 16; i++) + { + for(j = 0; j < 8; j++) + { + /* Only the LSBit of the byte is stored */ + BptcDataMatrix[j][i] = TSVoiceSupFrame->TimeSlotRawVoiceFrame[Burst].Sync[k + 8]; + k++; + + /* Go on to the next burst once 32 bit + * of the SNYC have been stored */ + if(k >= 32) + { + k = 0; + Burst++; + } + } /* End for(j = 0; j < 8; j++) */ + } /* End for(i = 0; i < 16; i++) */ + + /* Extract the 72 LC bit (+ 5 CRC bit) of the matrix */ + IrrecoverableErrors = BPTC_128x77_Extract_Data(BptcDataMatrix, LC_DataBit); + + /* Convert the 77 bit of voice LC Header data into 9 bytes */ + k = 0; + for(i = 0; i < 10; i++) + { + LC_DataBytes[i] = 0; + for(j = 0; j < 8; j++) + { + LC_DataBytes[i] = LC_DataBytes[i] << 1; + LC_DataBytes[i] = LC_DataBytes[i] | (LC_DataBit[k] & 0x01); + k++; + if(k >= 76) break; + } + } + + /* Reconstitute the 5 bit CRC */ + CRCExtracted = (LC_DataBit[72] & 1) << 4; + CRCExtracted |= (LC_DataBit[73] & 1) << 3; + CRCExtracted |= (LC_DataBit[74] & 1) << 2; + CRCExtracted |= (LC_DataBit[75] & 1) << 1; + CRCExtracted |= (LC_DataBit[76] & 1) << 0; + + /* Compute the 5 bit CRC */ + CRCComputed = ComputeCrc5Bit(LC_DataBit); + + if(CRCExtracted == CRCComputed) CRCCorrect = 1; + else CRCCorrect = 0; + + /* Store the Protect Flag (PF) bit */ + TSVoiceSupFrame->FullLC.ProtectFlag = (unsigned int)(LC_DataBit[0]); + + /* Store the Reserved bit */ + TSVoiceSupFrame->FullLC.Reserved = (unsigned int)(LC_DataBit[1]); + + /* Store the Full Link Control Opcode (FLCO) */ + TSVoiceSupFrame->FullLC.FullLinkControlOpcode = (unsigned int)ConvertBitIntoBytes(&LC_DataBit[2], 6); + + /* Store the Feature set ID (FID) */ + TSVoiceSupFrame->FullLC.FeatureSetID = (unsigned int)ConvertBitIntoBytes(&LC_DataBit[8], 8); + //state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + + /* Store the Service Options */ + TSVoiceSupFrame->FullLC.ServiceOptions = (unsigned int)ConvertBitIntoBytes(&LC_DataBit[16], 8); + + /* Store the Group address (Talk Group) */ + TSVoiceSupFrame->FullLC.GroupAddress = (unsigned int)ConvertBitIntoBytes(&LC_DataBit[24], 24); + //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + /* Store the Source address */ + TSVoiceSupFrame->FullLC.SourceAddress = (unsigned int)ConvertBitIntoBytes(&LC_DataBit[48], 24); + //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + + /* Check the CRC values */ + if((IrrecoverableErrors == 0))// && CRCCorrect) + { + /* Data ara correct */ + //fprintf(stderr, "\nLink Control (LC) Data CRCs are correct !!! Number of error = %u\n", NbOfError); + + /* CRC is correct so consider the Full LC data as correct/valid */ + TSVoiceSupFrame->FullLC.DataValidity = 1; + } + else + { + /* CRC checking error, so consider the Full LC data as invalid */ + TSVoiceSupFrame->FullLC.DataValidity = 0; + } + + /* Print the destination ID (TG) and the source ID */ + fprintf(stderr, "| TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); + fprintf(stderr, "FID=0x%02X ", TSVoiceSupFrame->FullLC.FeatureSetID); + //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + //state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + + if((IrrecoverableErrors == 0) && CRCCorrect) + { + fprintf(stderr, "(OK)"); + //only set on good CRC? + state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + state->dmr_so = TSVoiceSupFrame->FullLC.ServiceOptions; + state->dmr_color_code = state->color_code; + } + else if(IrrecoverableErrors == 0) + { + fprintf(stderr, "RAS (FEC OK/CRC ERR)"); + //or set if was corrected + state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; + state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + state->dmr_fid = TSVoiceSupFrame->FullLC.FeatureSetID; + state->dmr_so = TSVoiceSupFrame->FullLC.ServiceOptions; + state->dmr_color_code = state->color_code; + } + else fprintf(stderr, "(FEC FAIL/CRC ERR)"); + +#ifdef PRINT_VOICE_BURST_BYTES + fprintf(stderr, "\n"); + fprintf(stderr, "VOICE BURST BYTES : "); + for(i = 0; i < 10; i++) + { + fprintf(stderr, "0x%02X", LC_DataBytes[i]); + if(i != 9) fprintf(stderr, " - "); + } + + fprintf(stderr, "\n"); + + fprintf(stderr, "CRC extracted = 0x%04X - CRC computed = 0x%04X - ", CRCExtracted, CRCComputed); + + if((IrrecoverableErrors == 0) && CRCCorrect) + { + fprintf(stderr, "CRCs are equal + FEC OK !\n"); + } + else if(IrrecoverableErrors == 0) + { + else fprintf(stderr, "FEC correctly corrected but CRCs are incorrect\n"); + } + else + { + fprintf(stderr, "ERROR !!! CRCs are different and FEC Failed !\n"); + } + + fprintf(stderr, "Hamming Irrecoverable Errors = %u\n", IrrecoverableErrors); +#endif /* PRINT_VOICE_BURST_BYTES */ + +} /* End ProcessVoiceBurstSync() */ + + +/* + * @brief : This function compute the CRC-CCITT of the DMR data + * by using the polynomial x^16 + x^12 + x^5 + 1 + * + * @param Input : A buffer pointer of the DMR data (80 bytes) + * + * @return The 16 bit CRC + */ +uint16_t ComputeCrcCCITT(uint8_t * DMRData) +{ + uint32_t i; + uint16_t CRC = 0x0000; /* Initialization value = 0x0000 */ + /* Polynomial x^16 + x^12 + x^5 + 1 + * Normal = 0x1021 + * Reciprocal = 0x0811 + * Reversed = 0x8408 + * Reversed reciprocal = 0x8810 */ + uint16_t Polynome = 0x1021; + for(i = 0; i < 80; i++) + { + if(((CRC >> 15) & 1) ^ (DMRData[i] & 1)) + { + CRC = (CRC << 1) ^ Polynome; + } + else + { + CRC <<= 1; + } + } + + /* Invert the CRC */ + CRC ^= 0xFFFF; + + /* Return the CRC */ + return CRC; +} /* End ComputeCrcCCITT() */ + + +/* + * @brief : This function compute the CRC-24 bit of the full + * link control by using the Reed-Solomon(12,9) FEC + * + * @param FullLinkControlDataBytes : A buffer pointer of the DMR data bytes (12 bytes) + * + * @param CRCComputed : A 32 bit pointer where the computed CRC 24-bit will be stored + * + * @param CRCMask : The 24 bit CRC mask to apply + * + * @return 0 = CRC error + * 1 = CRC is correct + */ +uint32_t ComputeAndCorrectFullLinkControlCrc(uint8_t * FullLinkControlDataBytes, uint32_t * CRCComputed, uint32_t CRCMask) +{ + uint32_t i; + rs_12_9_codeword_t VoiceLCHeaderStr; + rs_12_9_poly_t syndrome; + uint8_t errors_found = 0; + rs_12_9_correct_errors_result_t result = RS_12_9_CORRECT_ERRORS_RESULT_NO_ERRORS_FOUND; + uint32_t CrcIsCorrect = 0; + + for(i = 0; i < 12; i++) + { + VoiceLCHeaderStr.data[i] = FullLinkControlDataBytes[i]; + + /* Apply CRC mask on each 3 last bytes + * of the full link control */ + if(i == 9) + { + VoiceLCHeaderStr.data[i] ^= (uint8_t)(CRCMask >> 16); + } + else if(i == 10) + { + VoiceLCHeaderStr.data[i] ^= (uint8_t)(CRCMask >> 8); + } + else if(i == 11) + { + VoiceLCHeaderStr.data[i] ^= (uint8_t)(CRCMask); + } + else + { + /* Nothing to do */ + } + } + + /* Check and correct the full link LC control with Reed Solomon (12,9) FEC */ + rs_12_9_calc_syndrome(&VoiceLCHeaderStr, &syndrome); + if(rs_12_9_check_syndrome(&syndrome) != 0) result = rs_12_9_correct_errors(&VoiceLCHeaderStr, &syndrome, &errors_found); + + /* Reconstitue the CRC */ + *CRCComputed = (uint32_t)((VoiceLCHeaderStr.data[9] << 16) & 0xFF0000); + *CRCComputed |= (uint32_t)((VoiceLCHeaderStr.data[10] << 8) & 0x00FF00); + *CRCComputed |= (uint32_t)((VoiceLCHeaderStr.data[11] << 0) & 0x0000FF); + + if((result == RS_12_9_CORRECT_ERRORS_RESULT_NO_ERRORS_FOUND) || + (result == RS_12_9_CORRECT_ERRORS_RESULT_ERRORS_CORRECTED)) + { + //fprintf(stderr, "CRC OK : 0x%06X\n", *CRCComputed); + CrcIsCorrect = 1; + + /* Reconstitue full link control data after FEC correction */ + for(i = 0; i < 12; i++) + { + FullLinkControlDataBytes[i] = VoiceLCHeaderStr.data[i]; + + /* Apply CRC mask on each 3 last bytes + * of the full link control */ + if(i == 9) + { + FullLinkControlDataBytes[i] ^= (uint8_t)(CRCMask >> 16); + } + else if(i == 10) + { + FullLinkControlDataBytes[i] ^= (uint8_t)(CRCMask >> 8); + } + else if(i == 11) + { + FullLinkControlDataBytes[i] ^= (uint8_t)(CRCMask); + } + else + { + /* Nothing to do */ + } + } + } + else + { + //fprintf(stderr, "CRC ERROR : 0x%06X\n", *CRCComputed); + CrcIsCorrect = 0; + } + + /* Return the CRC status */ + return CrcIsCorrect; +} /* End ComputeAndCorrectFullLinkControlCrc() */ + + +/* + * @brief : This function compute the 5 bit CRC of the DMR voice burst data + * See ETSI TS 102 361-1 chapter B.3.11 + * + * @param Input : A buffer pointer of the DMR data (72 bytes) + * + * @return The 5 bit CRC + */ +uint8_t ComputeCrc5Bit(uint8_t * DMRData) +{ + uint32_t i, j, k; + uint8_t Buffer[9]; + uint32_t Sum; + uint8_t CRC = 0; + + /* Convert the 72 bit into 9 bytes */ + k = 0; + for(i = 0; i < 9; i++) + { + Buffer[i] = 0; + for(j = 0; j < 8; j++) + { + Buffer[i] = Buffer[i] << 1; + Buffer[i] = Buffer[i] | DMRData[k++]; + } + } + + /* Add all 9 bytes */ + Sum = 0; + for(i = 0; i < 9; i++) + { + Sum += (uint32_t)Buffer[i]; + } + + /* Sum MOD 31 = CRC */ + CRC = (uint8_t)(Sum % 31); + + /* Return the CRC */ + return CRC; +} /* End ComputeCrc5Bit() */ + + +/* + * @brief : This function returns the Algorithm ID into an explicit string + * + * @param AlgID : The algorithm ID + * @arg : 0x21 for ARC4 + * @arg : 0x22 for DES + * @arg : 0x25 for AES256 + * + * @return A constant string pointer that explain the Alg ID used + */ +uint8_t * DmrAlgIdToStr(uint8_t AlgID) +{ + if(AlgID == 0x21) return (uint8_t *)"ARC4"; + else if(AlgID == 0x25) return (uint8_t *)"AES256"; + else return (uint8_t *)"UNKNOWN"; + //state->payload_algid = AlgID; +} /* End DmrAlgIdToStr */ + +/* + * @brief : This function returns the encryption mode into an explicit string + * + * @param PrivacyMode : The algorithm ID + * @arg : MODE_UNENCRYPTED + * @arg : MODE_BASIC_PRIVACY + * @arg : MODE_ENHANCED_PRIVACY_ARC4 + * @arg : MODE_ENHANCED_PRIVACY_DES + * @arg : MODE_ENHANCED_PRIVACY_AES256 + * @arg : MODE_HYTERA_BASIC_40_BIT + * @arg : MODE_HYTERA_BASIC_128_BIT + * @arg : MODE_HYTERA_BASIC_256_BIT + * + * @return A constant string pointer that explain the encryption mode used + */ +uint8_t * DmrAlgPrivacyModeToStr(uint32_t PrivacyMode) +{ + switch(PrivacyMode) + { + case MODE_UNENCRYPTED: + { + return (uint8_t *)"NOT ENC"; + break; + } + case MODE_BASIC_PRIVACY: + { + return (uint8_t *)"BP"; + break; + } + case MODE_ENHANCED_PRIVACY_ARC4: + { + return (uint8_t *)"EP ARC4"; + break; + } + case MODE_ENHANCED_PRIVACY_AES256: + { + return (uint8_t *)"EP AES256"; + break; + } + case MODE_HYTERA_BASIC_40_BIT: + { + return (uint8_t *)"HYTERA BASIC 40 BIT"; + break; + } + case MODE_HYTERA_BASIC_128_BIT: + { + return (uint8_t *)"HYTERA BASIC 128 BIT"; + break; + } + case MODE_HYTERA_BASIC_256_BIT: + { + return (uint8_t *)"HYTERA BASIC 256 BIT"; + break; + } + default: + { + return (uint8_t *)"UNKNOWN"; + break; + } + } /* End switch(PrivacyMode) */ +} /* End DmrAlgPrivacyModeToStr() */ + + +/* End of file */ diff --git a/src/dmr_voice.c b/src/dmr_voice.c index b7a76f7..5522a76 100644 --- a/src/dmr_voice.c +++ b/src/dmr_voice.c @@ -18,365 +18,633 @@ #include "dsd.h" #include "dmr_const.h" -void -processDMRvoice (dsd_opts * opts, dsd_state * state) +void processDMRvoice (dsd_opts * opts, dsd_state * state) { // extracts AMBE frames from DMR frame - int i, j, dibit; + int i, j, k, l, dibit; int *dibit_p; - char ambe_fr[4][24]; - char ambe_fr2[4][24]; - char ambe_fr3[4][24]; + char ambe_fr[4][24] = {0}; + char ambe_fr2[4][24] = {0}; + char ambe_fr3[4][24] = {0}; const int *w, *x, *y, *z; char sync[25]; - char syncdata[25]; - char cachdata[13]; + char syncdata[48]; + char cachdata[13] = {0}; int mutecurrentslot; int msMode; + char cc[4]; + unsigned char EmbeddedSignalling[16]; + unsigned int EmbeddedSignallingOk; + + /* Remove warning compiler */ + //UNUSED_VARIABLE(ambe_fr[0][0]); + //UNUSED_VARIABLE(ambe_fr2[0][0]); + //UNUSED_VARIABLE(ambe_fr3[0][0]); + //UNUSED_VARIABLE(cachdata[0]); #ifdef DMR_DUMP int k; char syncbits[49]; - char cachbits[25]; + char cachbits[25];12 #endif + /* Init the superframe buffers */ + memset(&state->TS1SuperFrame, 0, sizeof(TimeSlotVoiceSuperFrame_t)); + memset(&state->TS2SuperFrame, 0, sizeof(TimeSlotVoiceSuperFrame_t)); + + /* Init the color code status */ + state->color_code_ok = 0; + mutecurrentslot = 0; msMode = 0; dibit_p = state->dibit_buf_p - 144; - for (j = 0; j < 6; j++) + for(j = 0; j < 6; j++) + { + // 2nd half of previous slot + for(i = 0; i < 54; i++) { - // 2nd half of previous slot - for (i = 0; i < 54; i++) + if(j > 0) + { + dibit = getDibit(opts, state); + } + else + { + dibit = *dibit_p; + dibit_p++; + if(opts->inverted_dmr == 1) { - if (j > 0) - { - dibit = getDibit (opts, state); - } - else - { - dibit = *dibit_p; - dibit_p++; - if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - } + dibit = (dibit ^ 2); } + } + } - // CACH - for (i = 0; i < 12; i++) + // CACH + for(i = 0; i < 12; i++) + { + if(j > 0) + { + dibit = getDibit(opts, state); + } + else + { + dibit = *dibit_p; + dibit_p++; + if(opts->inverted_dmr == 1) { - if (j > 0) - { - dibit = getDibit (opts, state); - } - else - { - dibit = *dibit_p; - dibit_p++; - if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - } - cachdata[i] = dibit; - if (i == 2) - { - state->currentslot = (1 & (dibit >> 1)); // bit 1 - if (state->currentslot == 0) - { - state->slot0light[0] = '['; - state->slot0light[6] = ']'; - state->slot1light[0] = ' '; - state->slot1light[6] = ' '; - } - else - { - state->slot1light[0] = '['; - state->slot1light[6] = ']'; - state->slot0light[0] = ' '; - state->slot0light[6] = ' '; - } - } + dibit = (dibit ^ 2); } - cachdata[12] = 0; + } + cachdata[i] = dibit; + if(i == 2) + { + /* Change the current slot only if we are in relayed mode (not in direct mode) */ + if(state->directmode == 0) state->currentslot = (1 & (dibit >> 1)); // bit 1 + + if(state->currentslot == 0) + { + state->slot1light[0] = '['; + state->slot1light[6] = ']'; + state->slot2light[0] = ' '; + state->slot2light[6] = ' '; + } + else + { + state->slot2light[0] = '['; + state->slot2light[6] = ']'; + state->slot1light[0] = ' '; + state->slot1light[6] = ' '; + } + } + } + cachdata[12] = 0; #ifdef DMR_DUMP - k = 0; - for (i = 0; i < 12; i++) - { - dibit = cachdata[i]; - cachbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 - k++; - cachbits[k] = (1 & dibit) + 48; // bit 0 - k++; - } - cachbits[24] = 0; - fprintf (stderr, "%s ", cachbits); + k = 0; + for(i = 0; i < 12; i++) + { + dibit = cachdata[i]; + cachbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 + k++; + cachbits[k] = (1 & dibit) + 48; // bit 0 + k++; + } + cachbits[24] = 0; + fprintf(stderr, "%s ", cachbits); #endif - // current slot frame 1 - w = rW; - x = rX; - y = rY; - z = rZ; - for (i = 0; i < 36; i++) + // current slot frame 1 + w = rW; + x = rX; + y = rY; + z = rZ; + k = 0; + for(i = 0; i < 36; i++) + { + if(j > 0) + { + dibit = getDibit(opts, state); + } + else + { + dibit = *dibit_p; + dibit_p++; + if(opts->inverted_dmr == 1) { - if (j > 0) - { - dibit = getDibit (opts, state); - } - else - { - dibit = *dibit_p; - dibit_p++; - if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - } - ambe_fr[*w][*x] = (1 & (dibit >> 1)); // bit 1 - ambe_fr[*y][*z] = (1 & dibit); // bit 0 - w++; - x++; - y++; - z++; + dibit = (dibit ^ 2); } + } + ambe_fr[*w][*x] = (1 & (dibit >> 1)); // bit 1 + ambe_fr[*y][*z] = (1 & dibit); // bit 0 - // current slot frame 2 first half - w = rW; - x = rX; - y = rY; - z = rZ; - for (i = 0; i < 18; i++) - { - if (j > 0) - { - dibit = getDibit (opts, state); - } - else - { - dibit = *dibit_p; - dibit_p++; - if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - } - ambe_fr2[*w][*x] = (1 & (dibit >> 1)); // bit 1 - ambe_fr2[*y][*z] = (1 & dibit); // bit 0 - w++; - x++; - y++; - z++; - } + /* Save the current frame in the appropriate superframe buffer */ + if(state->currentslot == 0) + { + /* Time slot 1 superframe buffer filling => RAW voice bit */ + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[0][k++] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[0][k++] = (1 & dibit); // bit 0 - // signaling data or sync - for (i = 0; i < 24; i++) - { - if (j > 0) - { - dibit = getDibit (opts, state); - } - else - { - dibit = *dibit_p; - dibit_p++; - if (opts->inverted_dmr == 1) - { - dibit = (dibit ^ 2); - } - } - syncdata[i] = dibit; - sync[i] = (dibit | 1) + 48; - } - sync[24] = 0; - syncdata[24] = 0; + /* Time slot 1 superframe buffer filling => Deinterleaved voice bit */ + state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[0][*w][*x] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[0][*y][*z] = (1 & dibit); // bit 0 + } + else + { + /* Time slot 2 superframe buffer filling => RAW voice bit */ + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[0][k++] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[0][k++] = (1 & dibit); // bit 0 - if ((strcmp (sync, DMR_BS_DATA_SYNC) == 0) || (strcmp (sync, DMR_MS_DATA_SYNC) == 0)) - { - mutecurrentslot = 1; - if (state->currentslot == 0) - { - sprintf (state->slot0light, "[slot0]"); - } - else - { - sprintf (state->slot1light, "[slot1]"); - } - } - else if ((strcmp (sync, DMR_BS_VOICE_SYNC) == 0) || (strcmp (sync, DMR_MS_VOICE_SYNC) == 0)) - { - mutecurrentslot = 0; - if (state->currentslot == 0) - { - sprintf (state->slot0light, "[SLOT0]"); - } - else - { - sprintf (state->slot1light, "[SLOT1]"); - } - } - if ((strcmp (sync, DMR_MS_VOICE_SYNC) == 0) || (strcmp (sync, DMR_MS_DATA_SYNC) == 0)) - { - msMode = 1; - } + /* Time slot 2 superframe buffer filling => Deinterleaved voice bit */ + state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[0][*w][*x] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[0][*y][*z] = (1 & dibit); // bit 0 + } - if ((j == 0) && (opts->errorbars == 1)) + w++; + x++; + y++; + z++; + } + + // current slot frame 2 first half + w = rW; + x = rX; + y = rY; + z = rZ; + k = 0; + for(i = 0; i < 18; i++) + { + if(j > 0) + { + dibit = getDibit(opts, state); + } + else + { + dibit = *dibit_p; + dibit_p++; + if(opts->inverted_dmr == 1) { - fprintf (stderr, "%s %s VOICE e:", state->slot0light, state->slot1light); + dibit = (dibit ^ 2); } + } + ambe_fr2[*w][*x] = (1 & (dibit >> 1)); // bit 1 + ambe_fr2[*y][*z] = (1 & dibit); // bit 0 + + /* Save the current frame in the appropriate superframe buffer */ + if(state->currentslot == 0) + { + /* Time slot 1 superframe buffer filling => RAW voice bit */ + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[1][k++] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[1][k++] = (1 & dibit); // bit 0 + + /* Time slot 1 superframe buffer filling => Deinterleaved voice bit */ + state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[1][*w][*x] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[1][*y][*z] = (1 & dibit); // bit 0 + } + else + { + /* Time slot 2 superframe buffer filling => RAW voice bit */ + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[1][k++] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[1][k++] = (1 & dibit); // bit 0 + + /* Time slot 2 superframe buffer filling => Deinterleaved voice bit */ + state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[1][*w][*x] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[1][*y][*z] = (1 & dibit); // bit 0 + } + + w++; + x++; + y++; + z++; + } + + l = 0; + // signaling data or sync + for(i = 0; i < 24; i++) + { + if(j > 0) + { + dibit = getDibit(opts, state); + } + else + { + dibit = *dibit_p; + dibit_p++; + if(opts->inverted_dmr == 1) + { + dibit = (dibit ^ 2); + } + } + syncdata[(2*i)] = (1 & (dibit >> 1)); // bit 1 + syncdata[(2*i)+1] = (1 & dibit); // bit 0 + sync[i] = (dibit | 1) + 48; + + /* Save the current frame in the appropriate superframe buffer */ + if(state->currentslot == 0) + { + /* Time slot 1 superframe buffer filling => SYNC data */ + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].Sync[l++] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].Sync[l++] = (1 & dibit); // bit 0 + } + else + { + /* Time slot 2 superframe buffer filling => SYNC data */ + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].Sync[l++] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].Sync[l++] = (1 & dibit); // bit 0 + } + } + sync[24] = 0; + + /* Copy the Embedded signaling (1st part) */ + for(i = 0; i < 8; i++) + { + EmbeddedSignalling[i] = syncdata[i]; + } + /* Copy the Embedded signaling (2nd part) */ + for(i = 0; i < 8; i++) + { + EmbeddedSignalling[i + 8] = syncdata[i + 40]; + } + + /* Check and correct the SlotType (apply Golay(20,8) FEC check) */ + if(QR_16_7_6_decode(EmbeddedSignalling)) EmbeddedSignallingOk = 1; + else EmbeddedSignallingOk = 0; + + /* Get the color code */ + cc[0] = EmbeddedSignalling[0]; + cc[1] = EmbeddedSignalling[1]; + cc[2] = EmbeddedSignalling[2]; + cc[3] = EmbeddedSignalling[3]; + + /* The color code is normally the same for all + * 6 received voice frame, so in case of error + * do not store it except if the last SYNC frame + * has been received. */ + if(EmbeddedSignallingOk || ((state->color_code_ok == 0) && (j == 5))) + { + /* Save the color code */ + state->color_code = (unsigned int)((cc[0] << 3) + (cc[1] << 2) + (cc[2] << 1) + cc[3]); + state->color_code_ok = EmbeddedSignallingOk; + } + + /* Save the Power Control (PI) indicator */ + state->PI = (unsigned int)EmbeddedSignalling[4]; + state->PI_ok = EmbeddedSignallingOk; + + /* Save the Link Control Start Stop (LCSS) indicator */ + state->LCSS = (unsigned int)((EmbeddedSignalling[5] << 1) + EmbeddedSignalling[6]); + state->LCSS_ok = EmbeddedSignallingOk; + + if((strcmp (sync, DMR_BS_DATA_SYNC) == 0) || (strcmp (sync, DMR_MS_DATA_SYNC) == 0)) + { + mutecurrentslot = 1; + state->directmode = 0; + if(state->currentslot == 0) + { + sprintf(state->slot1light, "[slot1]"); + } + else + { + sprintf(state->slot2light, "[slot2]"); + } + } + else if(strcmp (sync, DMR_DIRECT_MODE_TS1_DATA_SYNC) == 0) + { + mutecurrentslot = 1; + state->currentslot = 0; + state->directmode = 1; /* Direct mode */ + sprintf(state->slot1light, "[sLoT1]"); + } + else if(strcmp (sync, DMR_DIRECT_MODE_TS2_DATA_SYNC) == 0) + { + mutecurrentslot = 1; + state->currentslot = 1; + state->directmode = 1; /* Direct mode */ + sprintf(state->slot1light, "[sLoT2]"); + } + else if((strcmp (sync, DMR_BS_VOICE_SYNC) == 0) || (strcmp (sync, DMR_MS_VOICE_SYNC) == 0)) + { + mutecurrentslot = 0; + state->directmode = 0; + if(state->currentslot == 0) + { + sprintf(state->slot1light, "[SLOT1]"); + } + else + { + sprintf(state->slot2light, "[SLOT2]"); + } + } + else if(strcmp (sync, DMR_DIRECT_MODE_TS1_VOICE_SYNC) == 0) + { + mutecurrentslot = 0; + state->currentslot = 0; + state->directmode = 1; /* Direct mode */ + sprintf(state->slot1light, "[sLoT1]"); + } + else if(strcmp (sync, DMR_DIRECT_MODE_TS2_VOICE_SYNC) == 0) + { + mutecurrentslot = 0; + state->currentslot = 1; + state->directmode = 1; /* Direct mode */ + sprintf(state->slot2light, "[sLoT2]"); + } + + if((strcmp (sync, DMR_MS_VOICE_SYNC) == 0) || (strcmp (sync, DMR_MS_DATA_SYNC) == 0)) + { + msMode = 1; + } + +// TODO : To be removed +// if((j == 0) && (opts->errorbars == 1)) +// { +// fprintf(stderr, "%s %s VOICE e:", state->slot1light, state->slot2light); +// } #ifdef DMR_DUMP - k = 0; - for (i = 0; i < 24; i++) - { - dibit = syncdata[i]; - syncbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 - k++; - syncbits[k] = (1 & dibit) + 48; // bit 0 - k++; - } - syncbits[48] = 0; - fprintf (stderr, "%s ", syncbits); + k = 0; + for(i = 0; i < 24; i++) + { + dibit = syncdata[i]; + syncbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 + k++; + syncbits[k] = (1 & dibit) + 48; // bit 0 + k++; + } + syncbits[48] = 0; + fprintf(stderr, "%s ", syncbits); #endif - // current slot frame 2 second half - for (i = 0; i < 18; i++) - { - dibit = getDibit (opts, state); - ambe_fr2[*w][*x] = (1 & (dibit >> 1)); // bit 1 - ambe_fr2[*y][*z] = (1 & dibit); // bit 0 - w++; - x++; - y++; - z++; - } + // current slot frame 2 second half + for(i = 0; i < 18; i++) + { + dibit = getDibit(opts, state); + ambe_fr2[*w][*x] = (1 & (dibit >> 1)); // bit 1 + ambe_fr2[*y][*z] = (1 & dibit); // bit 0 - if (mutecurrentslot == 0) - { - if (state->firstframe == 1) - { // we don't know if anything received before the first sync after no carrier is valid - state->firstframe = 0; - } - else - { - processMbeFrame (opts, state, NULL, ambe_fr, NULL); - processMbeFrame (opts, state, NULL, ambe_fr2, NULL); - } - } + /* Save the current frame in the appropriate superframe buffer */ + if(state->currentslot == 0) + { + /* Time slot 1 superframe buffer filling => RAW voice bit */ + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[1][k++] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[1][k++] = (1 & dibit); // bit 0 - // current slot frame 3 - w = rW; - x = rX; - y = rY; - z = rZ; - for (i = 0; i < 36; i++) - { - dibit = getDibit (opts, state); - ambe_fr3[*w][*x] = (1 & (dibit >> 1)); // bit 1 - ambe_fr3[*y][*z] = (1 & dibit); // bit 0 - w++; - x++; - y++; - z++; - } - if (mutecurrentslot == 0) - { - processMbeFrame (opts, state, NULL, ambe_fr3, NULL); - } + /* Time slot 1 superframe buffer filling => Deinterleaved voice bit */ + state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[1][*w][*x] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[1][*y][*z] = (1 & dibit); // bit 0 + } + else + { + /* Time slot 2 superframe buffer filling */ + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[1][k++] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[1][k++] = (1 & dibit); // bit 0 - // CACH - for (i = 0; i < 12; i++) - { - dibit = getDibit (opts, state); - cachdata[i] = dibit; - } - cachdata[12] = 0; + /* Time slot 2 superframe buffer filling => Deinterleaved voice bit */ + state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[1][*w][*x] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[1][*y][*z] = (1 & dibit); // bit 0 + } + + w++; + x++; + y++; + z++; + } + + if(mutecurrentslot == 0) + { + if(state->firstframe == 1) + { // we don't know if anything received before the first sync after no carrier is valid + state->firstframe = 0; + } + else + { + //processMbeFrame (opts, state, NULL, ambe_fr, NULL); + //processMbeFrame (opts, state, NULL, ambe_fr2, NULL); + } + } + + // current slot frame 3 + w = rW; + x = rX; + y = rY; + z = rZ; + k = 0; + for(i = 0; i < 36; i++) + { + dibit = getDibit(opts, state); + ambe_fr3[*w][*x] = (1 & (dibit >> 1)); // bit 1 + ambe_fr3[*y][*z] = (1 & dibit); // bit 0 + + /* Save the current frame in the appropriate superframe buffer */ + if(state->currentslot == 0) + { + /* Time slot 1 superframe buffer filling => RAW voice bit */ + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[2][k++] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[2][k++] = (1 & dibit); // bit 0 + + /* Time slot 1 superframe buffer filling => Deinterleaved voice bit */ + state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[2][*w][*x] = (1 & (dibit >> 1)); // bit 1 + state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[2][*y][*z] = (1 & dibit); // bit 0 + } + else + { + /* Time slot 2 superframe buffer filling => RAW voice bit */ + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[2][k++] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotRawVoiceFrame[j].VoiceFrame[2][k++] = (1 & dibit); // bit 0 + + /* Time slot 2 superframe buffer filling => Deinterleaved voice bit */ + state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[2][*w][*x] = (1 & (dibit >> 1)); // bit 1 + state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[2][*y][*z] = (1 & dibit); // bit 0 + } + + w++; + x++; + y++; + z++; + } + if(mutecurrentslot == 0) + { + //processMbeFrame (opts, state, NULL, ambe_fr3, NULL); + } + + // CACH + for(i = 0; i < 12; i++) + { + dibit = getDibit(opts, state); + cachdata[i] = dibit; + } + cachdata[12] = 0; #ifdef DMR_DUMP - k = 0; - for (i = 0; i < 12; i++) - { - dibit = cachdata[i]; - cachbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 - k++; - cachbits[k] = (1 & dibit) + 48; // bit 0 - k++; - } - cachbits[24] = 0; - fprintf (stderr, "%s ", cachbits); + k = 0; + for(i = 0; i < 12; i++) + { + dibit = cachdata[i]; + cachbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 + k++; + cachbits[k] = (1 & dibit) + 48; // bit 0 + k++; + } + cachbits[24] = 0; + fprintf(stderr, "%s ", cachbits); #endif - // next slot + // next slot + skipDibit (opts, state, 54); + + // signaling data or sync + for(i = 0; i < 24; i++) + { + dibit = getDibit(opts, state); + syncdata[i] = dibit; + sync[i] = (dibit | 1) + 48; + } + sync[24] = 0; + syncdata[24] = 0; + + if((strcmp (sync, DMR_BS_DATA_SYNC) == 0) || (msMode == 1)) + { + state->directmode = 0; + if(state->currentslot == 0) + { + sprintf(state->slot2light, " slot2 "); + } + else + { + sprintf(state->slot1light, " slot1 "); + } + } + else if(strcmp (sync, DMR_BS_VOICE_SYNC) == 0) + { + state->directmode = 0; + if(state->currentslot == 0) + { + sprintf(state->slot2light, " SLOT2 "); + } + else + { + sprintf(state->slot1light, " SLOT1 "); + } + } + else if((strcmp (sync, DMR_DIRECT_MODE_TS1_VOICE_SYNC) == 0) || (strcmp (sync, DMR_DIRECT_MODE_TS1_DATA_SYNC) == 0)) + { + state->currentslot = 0; + state->directmode = 1; /* Direct mode */ + sprintf(state->slot1light, " sLoT1 "); + } + else if((strcmp (sync, DMR_DIRECT_MODE_TS2_VOICE_SYNC) == 0) || (strcmp (sync, DMR_DIRECT_MODE_TS2_DATA_SYNC) == 0)) + { + state->currentslot = 1; + state->directmode = 1; /* Direct mode */ + sprintf(state->slot1light, " sLoT2 "); + } + +#ifdef DMR_DUMP + k = 0; + for(i = 0; i < 24; i++) + { + dibit = syncdata[i]; + syncbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 + k++; + syncbits[k] = (1 & dibit) + 48; // bit 0 + k++; + } + syncbits[48] = 0; + fprintf(stderr, "%s ", syncbits); +#endif + + if(j == 5) + { + // 2nd half next slot skipDibit (opts, state, 54); - // signaling data or sync - for (i = 0; i < 24; i++) - { - dibit = getDibit (opts, state); - syncdata[i] = dibit; - sync[i] = (dibit | 1) + 48; - } - sync[24] = 0; - syncdata[24] = 0; + // CACH + skipDibit (opts, state, 12); - if ((strcmp (sync, DMR_BS_DATA_SYNC) == 0) || (msMode == 1)) - { - if (state->currentslot == 0) - { - sprintf (state->slot1light, " slot1 "); - } - else - { - sprintf (state->slot0light, " slot0 "); - } - } - else if (strcmp (sync, DMR_BS_VOICE_SYNC) == 0) - { - if (state->currentslot == 0) - { - sprintf (state->slot1light, " SLOT1 "); - } - else - { - sprintf (state->slot0light, " SLOT0 "); - } - } - -#ifdef DMR_DUMP - k = 0; - for (i = 0; i < 24; i++) - { - dibit = syncdata[i]; - syncbits[k] = (1 & (dibit >> 1)) + 48; // bit 1 - k++; - syncbits[k] = (1 & dibit) + 48; // bit 0 - k++; - } - syncbits[48] = 0; - fprintf (stderr, "%s ", syncbits); -#endif - - if (j == 5) - { - // 2nd half next slot - skipDibit (opts, state, 54); - - // CACH - skipDibit (opts, state, 12); - - // first half current slot - skipDibit (opts, state, 54); - } + // first half current slot + skipDibit (opts, state, 54); } - if (opts->errorbars == 1) + /* Extract the 49 bits AMBE of the voice frame */ + if(state->currentslot == 0) { - fprintf (stderr, "\n"); + /* 3 x 49 bit of AMBE sample per voice frame => Slot 1buffer here */ + for(i = 0; i < 3; i++) + { + state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i] = mbe_eccAmbe3600x2450C0(state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i]); + //state->errs = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; //correct placement + state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i] = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; + //state->errs2 = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i]; //correct placement + mbe_demodulateAmbe3600x2450Data(state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i]); + state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i] += mbe_eccAmbe3600x2450Data(state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i], + &(state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].AmbeBit[i][0])); + } + //state->errs = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; //correct placement + //state->errs2 = (int)state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i]; //correct placement } + else + { + /* 3 x 49 bit of AMBE sample per voice frame => Slot 2 buffer here */ + for(i = 0; i < 3; i++) + { + state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i] = mbe_eccAmbe3600x2450C0(state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i]); + //state->errs = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; //correct placement + state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i] = state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; + //state->errs2 = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i]; //correct placement + mbe_demodulateAmbe3600x2450Data(state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i]); + state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i] += mbe_eccAmbe3600x2450Data(state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i], + &(state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].AmbeBit[i][0])); + } + //state->errs = state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; //correct placement + //state->errs2 = (int)state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i]; //correct placement + } + } /* End for(j = 0; j < 6; j++) */ -} + if(opts->errorbars == 1) + { + fprintf(stderr, "%s %s ", state->slot1light, state->slot2light); + + /* Print the color code */ + fprintf(stderr, "| Color Code=%02d ", (int)state->color_code); + + if(state->color_code_ok) fprintf(stderr, "(OK) |"); + else fprintf(stderr, "(CRC ERR) |"); + + fprintf(stderr, " VOICE e:"); + } + + /* Perform the SYNC DMR data embedded decoding */ + ProcessVoiceBurstSync(opts, state); + + /* Perform the DMR encryption decoding */ + ProcessDMREncryption(opts, state); + + /* Print DMR frame (if needed) */ + //DMRVoiceFrameProcess(opts, state); //HERE HERE, was enabled, disabled so I could build + + if(opts->errorbars == 1) + { + fprintf(stderr, "\n"); + } +} /* End processDMRvoice() */ diff --git a/src/dpmr_data.c b/src/dpmr_data.c new file mode 100644 index 0000000..a356ea9 --- /dev/null +++ b/src/dpmr_data.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2010 DSD Author + * GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6 F630 FAA2 635D 3F1D 7FD0) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "dsd.h" + + +/* Convert the Channel code (24 bit) into a valid + * dPMR color code [0..63] + * Return -1 on error */ +int32_t GetdPmrColorCode(uint8_t ChannelCodeBit[24]) +{ + uint32_t ChannelCodeByte; + int32_t ColorCode; + uint32_t i; + + /* Reconstitute the 24 bit channel code into a 3 bytes integer */ + ChannelCodeByte = 0; + for(i = 0; i < 24; i++) + { + ChannelCodeByte <<= 1; + ChannelCodeByte |= (ChannelCodeBit[i] & 1); + } + + /* By analyzing all channel code on the + * dPMR standard, I observe that if we decompose the + * 24 bit into 12 dibit, all dibit's LSB are always + * equal to "1", so apply a mask to correct + * flipped LSB dibit. + * + * For more details see ETSI TS 102 658 chapter + * 6.1.5.2.2 "Channel Code Determined by Frequency + * and System Identity Code". + * + * Apply the mask 0x555555 + * 0x555555 = 0b010101010101010101010101 => All dibit's LSB + * are equal to "1". + */ + ChannelCodeByte |= 0x555555; + + + /* Switch to the right color code */ + switch(ChannelCodeByte & 0xFF0000) + { + case 0x570000: + { + switch(ChannelCodeByte) + { + case 0x575F77: {ColorCode = 0; break;} + case 0x577577: {ColorCode = 1; break;} + case 0x57DD75: {ColorCode = 2; break;} + case 0x57F775: {ColorCode = 3; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0x570000 */ + + case 0x550000: + { + switch(ChannelCodeByte) + { + case 0x55577D: {ColorCode = 4; break;} + case 0x557D7D: {ColorCode = 5; break;} + case 0x55D57F: {ColorCode = 6; break;} + case 0x55FF7F: {ColorCode = 7; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0x550000 */ + + case 0x5F0000: + { + switch(ChannelCodeByte) + { + case 0x5F555F: {ColorCode = 8; break;} + case 0x5F7F5F: {ColorCode = 9; break;} + case 0x5FD75D: {ColorCode = 10; break;} + case 0x5FFD5D: {ColorCode = 11; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0x5F0000 */ + + case 0x5D0000: + { + switch(ChannelCodeByte) + { + case 0x5D5D55: {ColorCode = 12; break;} + case 0x5D7755: {ColorCode = 13; break;} + case 0x5DDF57: {ColorCode = 14; break;} + case 0x5DF557: {ColorCode = 15; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0x5D0000 */ + + case 0x770000: + { + switch(ChannelCodeByte) + { + case 0x775DD7: {ColorCode = 16; break;} + case 0x7777D7: {ColorCode = 17; break;} + case 0x77DFD5: {ColorCode = 18; break;} + case 0x77F5D5: {ColorCode = 19; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0x770000 */ + + case 0x750000: + { + switch(ChannelCodeByte) + { + case 0x7555DD: {ColorCode = 20; break;} + case 0x757FDD: {ColorCode = 21; break;} + case 0x75D7DF: {ColorCode = 22; break;} + case 0x75FDDF: {ColorCode = 23; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0x750000 */ + + case 0x7F0000: + { + switch(ChannelCodeByte) + { + case 0x7F57FF: {ColorCode = 24; break;} + case 0x7F7DFF: {ColorCode = 25; break;} + case 0x7FD5FD: {ColorCode = 26; break;} + case 0x7FFFFD: {ColorCode = 27; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0x7F0000 */ + + case 0x7D0000: + { + switch(ChannelCodeByte) + { + case 0x7D5FF5: {ColorCode = 28; break;} + case 0x7D75F5: {ColorCode = 29; break;} + case 0x7DDDF7: {ColorCode = 30; break;} + case 0x7DF7F7: {ColorCode = 31; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0x7D0000 */ + + case 0xD70000: + { + switch(ChannelCodeByte) + { + case 0xD755F7: {ColorCode = 32; break;} + case 0xD77FF7: {ColorCode = 33; break;} + case 0xD7D7F5: {ColorCode = 34; break;} + case 0xD7FDF5: {ColorCode = 35; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0xD70000 */ + + case 0xD50000: + { + switch(ChannelCodeByte) + { + case 0xD55DFD: {ColorCode = 36; break;} + case 0xD577FD: {ColorCode = 37; break;} + case 0xD5DFFF: {ColorCode = 38; break;} + case 0xD5F5FF: {ColorCode = 39; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0xD50000 */ + + case 0xDF0000: + { + switch(ChannelCodeByte) + { + case 0xDF5FDF: {ColorCode = 40; break;} + case 0xDF75DF: {ColorCode = 41; break;} + case 0xDFDDDD: {ColorCode = 42; break;} + case 0xDFF7DD: {ColorCode = 43; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0xDF0000 */ + + case 0xDD0000: + { + switch(ChannelCodeByte) + { + case 0xDD57D5: {ColorCode = 44; break;} + case 0xDD7DD5: {ColorCode = 45; break;} + case 0xDDD5D7: {ColorCode = 46; break;} + case 0xDDFFD7: {ColorCode = 47; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0xDD0000 */ + + case 0xF70000: + { + switch(ChannelCodeByte) + { + case 0xF75757: {ColorCode = 48; break;} + case 0xF77D57: {ColorCode = 49; break;} + case 0xF7D555: {ColorCode = 50; break;} + case 0xF7FF55: {ColorCode = 51; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0xF70000 */ + + case 0xF50000: + { + switch(ChannelCodeByte) + { + case 0xF55F5D: {ColorCode = 52; break;} + case 0xF5755D: {ColorCode = 53; break;} + case 0xF5DD5F: {ColorCode = 54; break;} + case 0xF5F75F: {ColorCode = 55; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0xF50000 */ + + case 0xFF0000: + { + switch(ChannelCodeByte) + { + case 0xFF5D7F: {ColorCode = 56; break;} + case 0xFF777F: {ColorCode = 57; break;} + case 0xFFDF7D: {ColorCode = 58; break;} + case 0xFFF57D: {ColorCode = 59; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0xFF0000 */ + + case 0xFD0000: + { + switch(ChannelCodeByte) + { + case 0xFD5575: {ColorCode = 60; break;} + case 0xFD7F75: {ColorCode = 61; break;} + case 0xFDD777: {ColorCode = 62; break;} + case 0xFDFD77: {ColorCode = 63; break;} + default: {ColorCode = -1; break;} + } /* End switch(ChannelCodeByte) */ + break; + } /* End case 0xFD0000 */ + + default: + { + ColorCode = -1; + break; + } + } /* End switch(ChannelCodeByte & 0xFF0000) */ + + /* Return the colour code */ + return ColorCode; +} + + +/* End of file */ diff --git a/src/dpmr_voice.c b/src/dpmr_voice.c new file mode 100644 index 0000000..c7e4ae8 --- /dev/null +++ b/src/dpmr_voice.c @@ -0,0 +1,919 @@ +/* + * Copyright (C) 2010 DSD Author + * GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6 F630 FAA2 635D 3F1D 7FD0) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "dsd.h" +#include "dpmr_const.h" + +/* Uncomment this define to display the dPMR debug info */ +//#define dPMR_PRINT_DEBUG_INFO + + +void processdPMRvoice (dsd_opts * opts, dsd_state * state) +{ + // extracts AMBE frames from DMR frame + uint32_t i, j, k, dibit; + uint8_t CCH[NB_OF_DPMR_VOICE_FRAME_TO_DECODE][72] = {0}; + uint8_t CCHDescrambled[NB_OF_DPMR_VOICE_FRAME_TO_DECODE][72] = {0}; + uint8_t CCHDeInterleaved[NB_OF_DPMR_VOICE_FRAME_TO_DECODE][72] = {0}; + uint8_t CCHDataHammingCorrected[NB_OF_DPMR_VOICE_FRAME_TO_DECODE][48] = {0}; + uint8_t CCHDataCRC[NB_OF_DPMR_VOICE_FRAME_TO_DECODE] = {0}; + uint8_t CCHDataCRCComputed[NB_OF_DPMR_VOICE_FRAME_TO_DECODE] = {0}; + //uint8_t TCH[NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4][72] = {0}; + uint8_t CC[NB_OF_DPMR_VOICE_FRAME_TO_DECODE / 2][24] = {0}; + char ambe_fr[NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4][4][24] = {0}; + char ambe_fr2[4][24]; + const int *w, *x, *y, *z; + uint32_t ScramblerLFSR = 0; + bool correctable = true; + bool HammingCorrectable[NB_OF_DPMR_VOICE_FRAME_TO_DECODE][6] = {0}; + uint32_t CrcOk[NB_OF_DPMR_VOICE_FRAME_TO_DECODE] = {0}; + uint32_t HammingOk[NB_OF_DPMR_VOICE_FRAME_TO_DECODE] = {0}; + uint32_t Temp = 0; + /* CCH (Control CHannel) data */ + uint32_t CCH_FrameNumber[NB_OF_DPMR_VOICE_FRAME_TO_DECODE] = {0}; + uint32_t CCH_CalledID = 0; + uint32_t CCH_CallingID = 0; + uint32_t CCH_CommunicationMode[NB_OF_DPMR_VOICE_FRAME_TO_DECODE] = {0}; + uint32_t CCH_Version[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + uint32_t CCH_CommsFormat[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + uint32_t CCH_EmergencyPriority[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + uint32_t CCH_Reserved[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + uint32_t CCH_SlowData[NB_OF_DPMR_VOICE_FRAME_TO_DECODE]; + uint32_t *errs; + uint32_t *errs2; + uint8_t AmbeBitDescrambled[49] = {0}; + uint32_t ScramblerKey = 0; + uint32_t PartOfSuperFrame = 0; + uint32_t VoiceFrameFlag = 1; /* We consider the current frame as a voice frame */ + uint32_t AttachedDataFlag = 0; + uint8_t CalledID[8] = {0}; + uint8_t CallingID[8] = {0}; + + /* Remove compiler warning */ + //UNUSED_VARIABLE(AmbeBitDescrambled[0]); + //UNUSED_VARIABLE(ScramblerKey); + //UNUSED_VARIABLE(PartOfSuperFrame); + + if (opts->errorbars == 1) + { + fprintf(stderr, "VOICE e:"); + } + + /* First CCH (Control CHannel) - 72 bit */ + k = 0; + for (i = 0; i < 36; i++) + { + dibit = getDibit (opts, state); + + if (opts->inverted_dpmr == 1) + { + dibit = (dibit ^ 2); + } + + CCH[0][(i * 2)] = (1 & (dibit >> 1)); // bit 1 + CCH[0][(i * 2) + 1] = (1 & dibit); // bit 0 + +#ifdef DPMR_DUMP + fprintf(stderr, "%c", dibit + 48); +#endif + } +#ifdef DPMR_DUMP + fprintf(stderr, " "); +#endif + + /* 4 TCH (Traffic CHannel) = 4 x 72 bit voice playload */ + k = 0; + for (j = 0; j < 4; j++) + { + w = dPmrW; + x = dPmrX; + y = dPmrY; + z = dPmrZ; + k = 0; + for (i = 0; i < 36; i++) + { + dibit = getDibit (opts, state); + + if (opts->inverted_dpmr == 1) + { + dibit = (dibit ^ 2); + } + +#ifdef DPMR_DUMP + fprintf(stderr, "%c", dibit + 48); +#endif + ambe_fr[j][*w][*x] = (1 & (dibit >> 1)); // bit 1 + ambe_fr[j][*y][*z] = (1 & dibit); // bit 0 + ambe_fr2[*w][*x] = (1 & (dibit >> 1)); // bit 1 + ambe_fr2[*y][*z] = (1 & dibit); // bit 0 + //TCH[j][k] = (1 & (dibit >> 1)); // bit 1 + //TCH[j][k + 1] = (1 & dibit); // bit 0 + state->dPMRVoiceFS2Frame.RawVoiceBit[j][k] = (1 & (dibit >> 1)); // bit 1 + state->dPMRVoiceFS2Frame.RawVoiceBit[j][k + 1] = (1 & dibit); // bit 0 + k = k + 2; + w++; + x++; + y++; + z++; + } + //processMbeFrame (opts, state, NULL, ambe_fr[j], NULL); //HERE HERE +#ifdef DPMR_DUMP + fprintf(stderr, " "); +#endif + } + + /* First CC (Channel Code) - 24 bit */ + k = 0; + for (i = 0; i < 12; i++) + { + dibit = getDibit (opts, state); + + if (opts->inverted_dpmr == 1) + { + dibit = (dibit ^ 2); + } + + CC[0][k++] = (1 & (dibit >> 1)); // bit 1 + CC[0][k++] = (1 & dibit); // bit 0 + +#ifdef DPMR_DUMP + fprintf(stderr, "%c", dibit + 48); +#endif + } +#ifdef DPMR_DUMP + fprintf(stderr, " "); +#endif + + /* Get the color code */ + state->dPMRVoiceFS2Frame.ColorCode[0] = (unsigned int)GetdPmrColorCode(CC[0]); + + /* Second CCH (Control CHannel) - 72 bit */ + k = 0; + for (i = 0; i < 36; i++) + { + dibit = getDibit (opts, state); + + if (opts->inverted_dpmr == 1) + { + dibit = (dibit ^ 2); + } + + CCH[1][k++] = (1 & (dibit >> 1)); // bit 1 + CCH[1][k++] = (1 & dibit); // bit 0 + +#ifdef DPMR_DUMP + fprintf(stderr, "%c", dibit + 48); +#endif + } + +#ifdef DPMR_DUMP + fprintf(stderr, " "); +#endif + + /* 4 TCH (Traffic CHannel) = 4 x 72 bit voice playload */ + k = 0; + for (j = 0; j < 4; j++) + { + w = dPmrW; + x = dPmrX; + y = dPmrY; + z = dPmrZ; + k = 0; + for (i = 0; i < 36; i++) + { + dibit = getDibit (opts, state); + + if (opts->inverted_dpmr == 1) + { + dibit = (dibit ^ 2); + } + +#ifdef DPMR_DUMP + fprintf(stderr, "%c", dibit + 48); +#endif + ambe_fr[j + 4][*w][*x] = (1 & (dibit >> 1)); // bit 1 + ambe_fr[j + 4][*y][*z] = (1 & dibit); // bit 0 + //TCH[j + 4][k] = (1 & (dibit >> 1)); // bit 1 + //TCH[j + 4][k + 1] = (1 & dibit); // bit 0 + state->dPMRVoiceFS2Frame.RawVoiceBit[j + 4][k] = (1 & (dibit >> 1)); // bit 1 + state->dPMRVoiceFS2Frame.RawVoiceBit[j + 4][k + 1] = (1 & dibit); // bit 0 + k = k + 2; + w++; + x++; + y++; + z++; + } + +#ifdef DPMR_DUMP + fprintf(stderr, " "); +#endif + } + + +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + /* Initialize the Hamming (12,8) error code detection + * Not needed because it has already been done + * in the "InitAllFecFunction()" function */ + //Hamming_12_8_init(); + + /* Decoding all CCH (Control CHannel) */ + for(i = 0; i < NB_OF_DPMR_VOICE_FRAME_TO_DECODE; i++) + { + /* Init LFSR value - All bit to '1' */ + ScramblerLFSR = 0x1FF; + + /* Descramble data - Input 72 bit => Output 72 bit */ + ScrambledPMRBit(&ScramblerLFSR, CCH[i], CCHDescrambled[i], 72); + + /* Deinterleave data - Input 72 bit => Output 72 bit */ + DeInterleave6x12DPmrBit(CCHDescrambled[i], CCHDeInterleaved[i]); + + correctable = true; + for(j = 0; j < 6; j++) + { + /* Apply the Hamming(12,8) correction - Input 72 bit => Output 48 bit */ + HammingCorrectable[i][j] = Hamming_12_8_decode(&CCHDeInterleaved[i][j*12], &CCHDataHammingCorrected[i][j*8], 1); + if(HammingCorrectable[i][j] == false) correctable = false; + } + + if(correctable) + { +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "CCH data corrected successfully\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + HammingOk[i] = 1; + } + else + { +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "ERROR !!! CCH data contains uncorrectable bit\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + HammingOk[i] = 0; + } + + /* Reconstitute the 7 bit CRC */ + CCHDataCRC[i] = 0; + CCHDataCRC[i] |= (CCHDataHammingCorrected[i][41]) << 6; + CCHDataCRC[i] |= (CCHDataHammingCorrected[i][42]) << 5; + CCHDataCRC[i] |= (CCHDataHammingCorrected[i][43]) << 4; + CCHDataCRC[i] |= (CCHDataHammingCorrected[i][44]) << 3; + CCHDataCRC[i] |= (CCHDataHammingCorrected[i][45]) << 2; + CCHDataCRC[i] |= (CCHDataHammingCorrected[i][46]) << 1; + CCHDataCRC[i] |= (CCHDataHammingCorrected[i][47]) << 0; + + /* Compute the 7 bit CRC */ + CCHDataCRCComputed[i] = CRC7BitdPMR(CCHDataHammingCorrected[i], 41); + + if(CCHDataCRC[i] == CCHDataCRCComputed[i]) + { +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "Good CCH CRCs ! CRC received = 0x%02X, CRC computed = 0x%02X\n", + CCHDataCRC[i], CCHDataCRCComputed[i]); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + CrcOk[i] = 1; + } + else + { +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "ERROR !!! CCH CRCs are different ! CRC received = 0x%02X, CRC computed = 0x%02X\n", + CCHDataCRC[i], CCHDataCRCComputed[i]); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + CrcOk[i] = 0; + } + + /* Fill the frame number */ + CCH_FrameNumber[i] = ConvertBitIntoBytes(&CCHDataHammingCorrected[i][0], 2); + + /* Fill the communication mode */ + CCH_CommunicationMode[i] = ConvertBitIntoBytes(&CCHDataHammingCorrected[i][14], 3); + + /* Fill the version */ + CCH_Version[i] = ConvertBitIntoBytes(&CCHDataHammingCorrected[i][17], 2); + + /* Fill the comm format */ + CCH_CommsFormat[i] = ConvertBitIntoBytes(&CCHDataHammingCorrected[i][19], 2); + + /* Fill the emergency priority bit */ + CCH_EmergencyPriority[i] = (uint32_t)CCHDataHammingCorrected[i][21]; + + /* Fill the reserved bit */ + CCH_Reserved[i] = (uint32_t)CCHDataHammingCorrected[i][22]; + + /* Fill the slow data */ + CCH_SlowData[i] = ConvertBitIntoBytes(&CCHDataHammingCorrected[i][23], 18); + + /* Copy the CCH data into the structure */ + memcpy(state->dPMRVoiceFS2Frame.CCHData[i], CCHDataHammingCorrected[i], 48); + state->dPMRVoiceFS2Frame.CCHDataHammingOk[i] = HammingOk[i]; + state->dPMRVoiceFS2Frame.CCHDataCRC[i] = CCHDataCRC[i]; + state->dPMRVoiceFS2Frame.CCHDataCrcOk[i] = CrcOk[i]; + state->dPMRVoiceFS2Frame.FrameNumbering[i] = CCH_FrameNumber[i]; + state->dPMRVoiceFS2Frame.CommunicationMode[i] = CCH_CommunicationMode[i]; + state->dPMRVoiceFS2Frame.Version[i] = CCH_Version[i]; + state->dPMRVoiceFS2Frame.CommsFormat[i] = CCH_CommsFormat[i]; + state->dPMRVoiceFS2Frame.EmergencyPriority[i] = CCH_EmergencyPriority[i]; + state->dPMRVoiceFS2Frame.Reserved[i] = CCH_Reserved[i]; + state->dPMRVoiceFS2Frame.SlowData[i] = CCH_SlowData[i]; + } /* End for(i = 0; i < NB_OF_DPMR_VOICE_FRAME_TO_DECODE; i++) */ + + /* Get the last TG */ + strcpy((char *)CalledID, (char *)state->dPMRVoiceFS2Frame.CalledID); + CalledID[7] = '\0'; + + /* Get the last source ID */ + strcpy((char *)CallingID, (char *)state->dPMRVoiceFS2Frame.CallingID); + CallingID[7] = '\0'; + +#ifdef dPMR_PRINT_DEBUG_INFO + /* Print Hamming debug info */ + fprintf(stderr, " | "); + for(i = 0; i < NB_OF_DPMR_VOICE_FRAME_TO_DECODE; i++) + { + fprintf(stderr, "Hamming[%u]=", i); + for(j = 0; j < 6; j++) + { + if(HammingCorrectable[i][j]) fprintf(stderr, "1"); + else fprintf(stderr, "0"); + } + fprintf(stderr, " HammingOk[%u]=%u", i, HammingOk[i]); + fprintf(stderr, " CrcOk[%u]=%u", i, CrcOk[i]); + //fprintf(stderr, " Bit[%u]=", i); + //for(j = 0; j < 7; j++) + //{ + // if(CCHDataHammingCorrected[i][j + 41]) fprintf(stderr, "1"); + // else fprintf(stderr, "0"); + //} + fprintf(stderr, " CRCrec=0x%02X CRCcomp=0x%02X", CCHDataCRC[i], CCHDataCRCComputed[i]); + if(i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE - 1)) fprintf(stderr, " - "); + } +#endif /* dPMR_PRINT_DEBUG_INFO */ + + +#if (NB_OF_DPMR_VOICE_FRAME_TO_DECODE == 2) + /* To determine the part of the payload frame, we need to check the + * Hamming code result, the CRC and the frame number */ +// if( (HammingOk[0] && CrcOk[0] && (CCH_FrameNumber[0] == 0)) || +// (HammingOk[1] && CrcOk[1] && (CCH_FrameNumber[1] == 1))) + if(((CrcOk[0] || HammingCorrectable[0][0]) && (CCH_FrameNumber[0] == 0)) || + ((CrcOk[1] || HammingCorrectable[1][0]) && (CCH_FrameNumber[1] == 1))) + { + /* First part of the super frame */ + PartOfSuperFrame = 1; + +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "First part of the Super Frame\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + /* The next part will normally be the second part */ + opts->dPMR_next_part_of_superframe = 2; + + /* Fill the called ID (talk group - TG) */ + Temp = ConvertBitIntoBytes(&CCHDataHammingCorrected[0][2], 12); + CCH_CalledID = ((Temp << 12) & 0x00FFF000); /* 12 MSBit */ + Temp = ConvertBitIntoBytes(&CCHDataHammingCorrected[1][2], 12); + CCH_CalledID |= (Temp & 0x00000FFF); /* 12 LSBit */ + ConvertAirInterfaceID(CCH_CalledID, CalledID); + CalledID[7] = '\0'; + + /* Determine if the called TG ID only is correct */ + //if(HammingOk[0] && CrcOk[0] && HammingOk[1] && CrcOk[1]) + if((CrcOk[0] || (HammingCorrectable[0][0] && HammingCorrectable[0][1])) && + (CrcOk[1] || (HammingCorrectable[1][0] && HammingCorrectable[1][1]))) + { + /* Save CCH data parameters */ + state->dPMRVoiceFS2Frame.CalledIDOk = 1; + } + else + { + state->dPMRVoiceFS2Frame.CalledIDOk = 0; + } + + /* BUGFIX : Copy in all case the TG ID */ + strcpy((char *)state->dPMRVoiceFS2Frame.CalledID, (char *)CalledID); + } + /* To determine the part of the payload frame, we need to check the + * Hamming code result, the CRC and the frame number */ +// else if( (HammingOk[0] && CrcOk[0] && (CCH_FrameNumber[0] == 2)) || +// (HammingOk[1] && CrcOk[1] && (CCH_FrameNumber[1] == 3))) + else if(((CrcOk[0] || HammingCorrectable[0][0]) && (CCH_FrameNumber[0] == 2)) || + ((CrcOk[1] || HammingCorrectable[1][0]) && (CCH_FrameNumber[1] == 3))) + { + /* Second part of the super frame */ + PartOfSuperFrame = 2; + +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "Second part of the Super Frame\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + /* The next part will normally be the first part */ + opts->dPMR_next_part_of_superframe = 1; + + /* Fill the calling ID (source) */ + Temp = ConvertBitIntoBytes(&CCHDataHammingCorrected[0][2], 12); + CCH_CallingID = ((Temp << 12) & 0x00FFF000); /* 12 MSBit */ + Temp = ConvertBitIntoBytes(&CCHDataHammingCorrected[1][2], 12); + CCH_CallingID |= (Temp & 0x00000FFF); /* 12 LSBit */ + ConvertAirInterfaceID(CCH_CallingID, CallingID); + CallingID[7] = '\0'; + + /* Determine if the calling SRC ID is correct */ + //if(HammingOk[0] && CrcOk[0] && HammingOk[1] && CrcOk[1]) + if((CrcOk[0] || (HammingCorrectable[0][0] && HammingCorrectable[0][1])) && + (CrcOk[1] || (HammingCorrectable[1][0] && HammingCorrectable[1][1]))) + { + /* Save CCH data parameters */ + state->dPMRVoiceFS2Frame.CallingIDOk = 1; + } + else + { + state->dPMRVoiceFS2Frame.CallingIDOk = 0; + } + + /* BUGFIX : Copy in all case the SRC ID */ + strcpy((char *)state->dPMRVoiceFS2Frame.CallingID, (char *)CallingID); + } + else + { + /* The dPMR source and destination ID are now invalid */ + state->dPMRVoiceFS2Frame.CalledIDOk = 0; + state->dPMRVoiceFS2Frame.CallingIDOk = 0; + + /* Unknown part of superframe => We suppose + * it is the next part of the previous frame received */ + PartOfSuperFrame = opts->dPMR_next_part_of_superframe; + + /* Set the next part of the superframe to receive */ + if(opts->dPMR_next_part_of_superframe == 1) + { + +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "Supposed first part of the Super Frame\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + /* Set the next part as the second part */ + opts->dPMR_next_part_of_superframe = 2; + } + else if(opts->dPMR_next_part_of_superframe == 2) + { + +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "Supposed second part of the Super Frame\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + /* Set the next part as the first part */ + opts->dPMR_next_part_of_superframe = 1; + } + else + { + +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "Unknown part of the Super Frame\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + + /* Unknown current part, set to 0 */ + opts->dPMR_next_part_of_superframe = 0; + } + } + + if(state->dPMRVoiceFS2Frame.ColorCode[0] != (unsigned int)(-1)) + { + /* Display the color code */ + //fprintf(stderr, " | Color Code=%02d ", (int)state->dPMRVoiceFS2Frame.ColorCode[0]); + } + else + { + /* Unknown color code, display "??" */ + //fprintf(stderr, " | Color Code=?? "); + } + + /* Display the destination ID (talk group - TG) */ + //fprintf(stderr, "| TG=%s", CalledID); + if(state->dPMRVoiceFS2Frame.CalledIDOk) + { + fprintf(stderr, "| TG=%s", CalledID); + //strcpy (state->dpmr_target_id, CalledID); //HERE HERE + state->dpmr_target_id = CalledID; + //fprintf(stderr, " (CRC OK) "); + if(state->dPMRVoiceFS2Frame.ColorCode[0] != (unsigned int)(-1)) + { + fprintf(stderr, " | Color Code=%02d ", (int)state->dPMRVoiceFS2Frame.ColorCode[0]); + state->dpmr_color_code = (int)state->dPMRVoiceFS2Frame.ColorCode[0]; + + } + + } + else + { + //fprintf(stderr, " (CRC ERR) "); + } + + /* Display the source ID */ + //fprintf(stderr, "| Src=%s", CallingID); + if(state->dPMRVoiceFS2Frame.CallingIDOk) + { + fprintf(stderr, "| Src=%s", CallingID); + //strcpy (state->dpmr_caller_id, CallingID); + state->dpmr_caller_id = CallingID; + + //fprintf(stderr, " (CRC OK) "); + } + else + { + fprintf(stderr, " (CRC ERR) "); + } + +#endif /* (NB_OF_DPMR_VOICE_FRAME_TO_DECODE == 2) */ + + /* Test if the current frame is encrypted or not */ + for(i = 0; i < NB_OF_DPMR_VOICE_FRAME_TO_DECODE; i++) + { + //if(HammingOk[i] && CrcOk[i]) + if(HammingCorrectable[i][2] && CrcOk[i]) + { + /* Unencrypted voice frame => Version = 0 (0b00) */ + if(state->dPMRVoiceFS2Frame.Version[i] == 0x00) + { + opts->dPMR_curr_frame_is_encrypted = 0; + } + /* Encrypted voice frame => Version = 3 (0b11) */ + else if(state->dPMRVoiceFS2Frame.Version[i] == 0x03) + { + opts->dPMR_curr_frame_is_encrypted = 1; + } + else + { + /* Unknown clear or encrypted voice frame status */ + } + + /* Valid encryption parameter found, + * exit the "for" loop */ + break; + } /* End if(HammingCorrectable[i][2] && CrcOk[i]) */ + } + + if(opts->dPMR_curr_frame_is_encrypted) fprintf(stderr, "| Scrambler "); + + /* Determine if the received frame is a voice frame, a voice frame + data + * or a data frame. + * + * See ETSI TS 102 658 chapter 5.0.5 "Communication Mode [BT]" + * for details + */ + VoiceFrameFlag = 0; + AttachedDataFlag = 0; + for(i = 0; i < NB_OF_DPMR_VOICE_FRAME_TO_DECODE; i++) + { + /* Search for voice frame + * Note : Due to error decoding, we parse all CCH + * Communication Mode parameters, if a voice parameter is + * found, the entire payload is considered as voice */ + if((state->dPMRVoiceFS2Frame.CommunicationMode[i] == 0) || + (state->dPMRVoiceFS2Frame.CommunicationMode[i] == 1) || + (state->dPMRVoiceFS2Frame.CommunicationMode[i] == 5)) + { + /* Voice frame detected */ + VoiceFrameFlag = 1; + + /* Check if the voice frame contains attached data */ + if((state->dPMRVoiceFS2Frame.CommunicationMode[i] == 5) && state->dPMRVoiceFS2Frame.CCHDataCrcOk[i]) + { + AttachedDataFlag = 1; + } + } + } + + if(VoiceFrameFlag) + { + if(AttachedDataFlag) fprintf(stderr, "| Voice+Data "); + else fprintf(stderr, "| Voice "); + } + else + { + fprintf(stderr, "| Data "); + } + + /* Demodulate and store AMBE sample only if the current superframe + * is a voice frame */ + if(VoiceFrameFlag) + { + /* There is 4 AMBE voice sample per voice frame (= 8 per superframe) */ + for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) + { + /* Apply ECC 1 to the AMBE frames */ + state->dPMRVoiceFS2Frame.errs1[i] = (unsigned int)mbe_eccAmbe3600x2450C0(ambe_fr[i]); + state->dPMRVoiceFS2Frame.errs2[i] = state->dPMRVoiceFS2Frame.errs1[i]; + + /* Demodulate the AMBE frames */ + mbe_demodulateAmbe3600x2450Data(ambe_fr[i]); + + /* Apply ECC 2 to the AMBE frames and get the 49 bit of the voice sample */ + state->dPMRVoiceFS2Frame.errs2[i] += (unsigned int)mbe_eccAmbe3600x2450Data(ambe_fr[i], (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); + } + } /* End if(VoiceFrameFlag && !AttachedDataFlag) */ + + /* Check if the voice frame to play is encrypted */ + if(opts->dPMR_curr_frame_is_encrypted) + { + /* Encryption is not supported in the public version */ + ScramblerKey = 0; + } + else + { + /* The frame is unencrypted, + * so set the scrambler key to 0 */ + ScramblerKey = 0; + } + + + /* Play only voice frame */ + if(VoiceFrameFlag) + { + +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "Voice frame to play\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + /* There is 4 AMBE voice sample per voice frame (so 2 x 4 = 8 per superframe) */ + for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) + { + errs = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs1[i]); + errs2 = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs2[i]); + state->errs = state->dPMRVoiceFS2Frame.errs1[i]; + state->errs2 = state->dPMRVoiceFS2Frame.errs2[i]; + + //sounds like choppy shit, but .amb file playback is acceptable, so investigate why this doesn't work properly + mbe_processAmbe2450Dataf (state->audio_out_temp_buf, (int *)errs, (int *)errs2, state->err_str, + (char *)state->dPMRVoiceFS2Frame.AmbeBit[i], + state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); + + //mbe_processAmbe2450Data ((short *) state->audio_out_temp_buf_p, (int *)errs, (int *)errs2, state->err_str, + // (char *)state->dPMRVoiceFS2Frame.AmbeBit[i], + // state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); + if (opts->mbe_out_f != NULL) + { + saveAmbe2450Data (opts, state, (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); + } + if (opts->errorbars == 1) + { + fprintf(stderr, "%s", state->err_str); + } + + state->debug_audio_errors += *errs2; + + processAudio(opts, state); + + if (opts->wav_out_f != NULL) + { + writeSynthesizedVoice (opts, state); + } + + if (opts->audio_out == 1) + { + /* Play the AMBE Frames */ + playSynthesizedVoice (opts, state); + } + } /* End for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) */ + } /* End if(VoiceFrameFlag) */ + else + { +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "Data frame, don't play it !\n"); +#endif /* dPMR_PRINT_DEBUG_INFO */ + } + + if (opts->errorbars == 1) + { + fprintf(stderr, "\n"); + } + + +#ifdef dPMR_PRINT_DEBUG_INFO + /* Display the CCH content */ + for(i = 0; i < NB_OF_DPMR_VOICE_FRAME_TO_DECODE; i++) + { + fprintf(stderr, "i = %u - ", i); + fprintf(stderr, "Comm Mode = %01u - ", state->dPMRVoiceFS2Frame.CommunicationMode[i]); + fprintf(stderr, "Version = %01u - ", state->dPMRVoiceFS2Frame.Version[i]); + fprintf(stderr, "Comms Format = %01u - ", state->dPMRVoiceFS2Frame.CommsFormat[i]); + fprintf(stderr, "Emergency = %01u - ", state->dPMRVoiceFS2Frame.EmergencyPriority[i]); + fprintf(stderr, "Reserved = %01u - ", state->dPMRVoiceFS2Frame.Reserved[i]); + fprintf(stderr, "Slow Data = 0x%05X - ", state->dPMRVoiceFS2Frame.SlowData[i]); + if(HammingOk[i] && CrcOk[i]) fprintf(stderr, "Valid"); + else fprintf(stderr, "CRC ERROR"); + fprintf(stderr, "\n"); + } +#endif /* dPMR_PRINT_DEBUG_INFO */ + + /* Print sPMR frame (if needed) */ + //dPMRVoiceFrameProcess(opts, state); //HERE HERE disabled to build +} /* End processdPMRvoice() */ + + +/* Scrambler used for dPMR transmission (different of the + * voice encryption scrambler), see ETSI TS 102 658 chapter + * 7.3 for the polynomial description. + * It is a X^9 + X^5 + 1 polynomial. */ +void ScrambledPMRBit(uint32_t * LfsrValue, uint8_t * BufferIn, uint8_t * BufferOut, uint32_t NbOfBitToScramble) +{ + uint8_t S[9] = {0}; + uint32_t i; + uint8_t Temp; + uint32_t LFSRValue; + + LFSRValue = *LfsrValue; + + /* Load the initial LFSR value */ + for(i = 0; i < 9; i++) + { + S[i] = LFSRValue & 1; + LFSRValue >>= 1; + } + + /* There are 72 bit to encrypt for voice and 288 bit for data */ + for(i = 0; i < NbOfBitToScramble; i++) + { + BufferOut[i] = (BufferIn[i] ^ S[0]) & 0x01; + + /* Shift registers */ + Temp = S[4] ^ S[0]; + S[0] = S[1]; + S[1] = S[2]; + S[2] = S[3]; + S[3] = S[4]; + S[4] = S[5]; + S[5] = S[6]; + S[6] = S[7]; + S[7] = S[8]; + S[8] = Temp; + } + + /* Save the final LFSR value */ + LFSRValue = 0; + for(i = 9; i > 0; i--) + { + LFSRValue <<= 1; + LFSRValue |= S[i - 1] & 1; + } + + *LfsrValue = LFSRValue; +} /* End ScrambleDPmrBit() */ + + +/* Scrambler used for dPMR encryption / decryption, + * see ETSI TS 102 658 chapter 7.3 for the + * polynomial description. + * It is a X^9 + X^5 + 1 polynomial. */ +void DeInterleave6x12DPmrBit(uint8_t * BufferIn, uint8_t * BufferOut) +{ + uint8_t Matrix[12][6] = {0}; + uint32_t i, j, k; + + /* Step 1 : Filling the 12 x 6 bit matrix */ + k = 0; + for(i = 0; i < 12; i++) + { + for(j = 0; j < 6; j++) + { + Matrix[i][j] = BufferIn[k++]; + } + } + + /* Step 2 : Filling the output buffer with deinterleaved data */ + k = 0; + for(j = 0; j < 6; j++) + { + for(i = 0; i < 12; i++) + { + BufferOut[k++] = Matrix[i][j]; + } + } +} /* End DeInterleave6x12DPmrBit() */ + + +/* CRC 7 bit computation with the following + * polynomial : X^7 + X^3 + 1 */ +uint8_t CRC7BitdPMR(uint8_t * BufferIn, uint32_t BitLength) +{ + uint8_t ShiftRegister = 0x00; /* All bit to '0' (7 LSBit only used) */ + uint8_t Polynome = 0x09; /* X^7 + X^3 + 1 */ + uint32_t i; + + for(i = 0; i < BitLength; i++) + { + if(((ShiftRegister >> 6) & 1) ^ BufferIn[i]) + { + ShiftRegister = ((ShiftRegister << 1) ^ Polynome) & 0x7F; + } + else + { + ShiftRegister = (ShiftRegister << 1) & 0x7F; + } + } + + return ShiftRegister; +} /* End CRC7BitdPMR() */ + + +/* CRC 8 bit computation with the following + * polynomial : X^8 + X^2 + X + 1 */ +uint8_t CRC8BitdPMR(uint8_t * BufferIn, uint32_t BitLength) +{ + uint8_t ShiftRegister = 0xFF; /* All bit to '1' */ + uint8_t Polynome = 0x07; /* X^7 + X^3 + 1 */ + uint32_t i; + + for(i = 0; i < BitLength; i++) + { + if(((ShiftRegister >> 7) & 1) ^ BufferIn[i]) + { + ShiftRegister = ((ShiftRegister << 1) ^ Polynome) & 0xFF; + } + else + { + ShiftRegister = (ShiftRegister << 1) & 0xFF; + } + } + + return ShiftRegister; +} /* End CRC8BitdPMR() */ + + +/* Convert an air interface identifier (AI ID) into + * a 7 ASCII digit string. + * + * See dPMR standard chapter A.1.2.1.1.6 + * "Mapping of dialled strings to the AI address space" */ +void ConvertAirInterfaceID(uint32_t AI_ID, uint8_t ID[8]) +{ + uint32_t AI_ID_Temp = AI_ID; + uint32_t Digit; + + /* 1st digit */ + Digit = AI_ID_Temp / 1464100; + AI_ID_Temp = AI_ID_Temp % 1464100; + if(Digit == 10) ID[0] = '*'; + else ID[0] = Digit + '0'; + + /* 2nd digit */ + Digit = AI_ID_Temp / 146410; + AI_ID_Temp = AI_ID_Temp % 146410; + if(Digit == 10) ID[1] = '*'; + else ID[1] = Digit + '0'; + + /* 3rd digit */ + Digit = AI_ID_Temp / 14641; + AI_ID_Temp = AI_ID_Temp % 14641; + if(Digit == 10) ID[2] = '*'; + else ID[2] = Digit + '0'; + + /* 4th digit */ + Digit = AI_ID_Temp / 1331; + AI_ID_Temp = AI_ID_Temp % 1331; + if(Digit == 10) ID[3] = '*'; + else ID[3] = Digit + '0'; + + /* 5th digit */ + Digit = AI_ID_Temp / 121; + AI_ID_Temp = AI_ID_Temp % 121; + if(Digit == 10) ID[4] = '*'; + else ID[4] = Digit + '0'; + + /* 6th digit */ + Digit = AI_ID_Temp / 11; + AI_ID_Temp = AI_ID_Temp % 11; + if(Digit == 10) ID[5] = '*'; + else ID[5] = Digit + '0'; + + /* 7th digit */ + Digit = AI_ID_Temp; + if(Digit == 10) ID[6] = '*'; + else ID[6] = Digit + '0'; + + /* Add the "end of string" */ + ID[7] = '\0'; + +} /* End convertAirInterfaceID() */ + + + +/* End of file */ diff --git a/src/dsd_file.c b/src/dsd_file.c index a4ec9ca..6eb4dfc 100644 --- a/src/dsd_file.c +++ b/src/dsd_file.c @@ -72,7 +72,8 @@ saveAmbe2450Data (dsd_opts * opts, dsd_state * state, char *ambe_d) { fprintf(stderr, "\n"); } - for (i = 0; i < 6; i++) + //for (i = 0; i < 6; i++) + for (i = 0; i < 7; i++) { b = 0; @@ -103,12 +104,17 @@ readImbe4400Data (dsd_opts * opts, dsd_state * state, char *imbe_d) { int i, j, k; - unsigned char b; + unsigned char b, x; state->errs2 = fgetc (opts->mbe_in_f); state->errs = state->errs2; + k = 0; + if (opts->payload == 1) //make opt variable later on to toggle this + { + fprintf(stderr, "\n"); + } for (i = 0; i < 11; i++) { b = fgetc (opts->mbe_in_f); @@ -119,10 +125,24 @@ readImbe4400Data (dsd_opts * opts, dsd_state * state, char *imbe_d) for (j = 0; j < 8; j++) { imbe_d[k] = (b & 128) >> 7; + + x = x << 1; + x |= ((b & 0x80) >> 7); + b = b << 1; b = b & 255; k++; } + //new printout on reading MBE files back in + if (opts->payload == 1) //make opt variable later on to toggle this + { + fprintf (stderr, "[%02X] ", x); + } + // + } + if (opts->payload == 1) + { + fprintf(stderr, " err = [%X] [%X] ", state->errs, state->errs2); //not sure that errs here are legit values } return (0); } @@ -132,13 +152,18 @@ readAmbe2450Data (dsd_opts * opts, dsd_state * state, char *ambe_d) { int i, j, k; - unsigned char b; + unsigned char b, x; state->errs2 = fgetc (opts->mbe_in_f); state->errs = state->errs2; k = 0; - for (i = 0; i < 6; i++) + if (opts->payload == 1) //make opt variable later on to toggle this + { + fprintf(stderr, "\n"); + } + //for (i = 0; i < 6; i++) + for (i = 0; i < 7; i++) { b = fgetc (opts->mbe_in_f); if (feof (opts->mbe_in_f)) @@ -148,10 +173,22 @@ readAmbe2450Data (dsd_opts * opts, dsd_state * state, char *ambe_d) for (j = 0; j < 8; j++) { ambe_d[k] = (b & 128) >> 7; + + x = x << 1; + x |= ((b & 0x80) >> 7); + b = b << 1; b = b & 255; k++; } + if (opts->payload == 1) //make opt variable later on to toggle this + { + fprintf (stderr, "[%02X] ", x); + } + } + if (opts->payload == 1) + { + fprintf(stderr, " err = [%X] [%X] ", state->errs, state->errs2); } b = fgetc (opts->mbe_in_f); ambe_d[48] = (b & 1); @@ -247,7 +284,7 @@ closeMbeOutFile (dsd_opts * opts, dsd_state * state) { sprintf (newfilename, "%s-nac%X-tg%i%s", datestr, state->nac, talkgroup, ext); } - + sprintf (new_path, "%s%s", opts->mbe_out_dir, newfilename); #ifdef _WIN32 sprintf (shell, "move %s %s", opts->mbe_out_path, new_path); diff --git a/src/dsd_frame.c b/src/dsd_frame.c index 6044f5b..5b1c47a 100644 --- a/src/dsd_frame.c +++ b/src/dsd_frame.c @@ -244,6 +244,50 @@ processFrame (dsd_opts * opts, dsd_state * state) processProVoice (opts, state); return; } + //LEH dPMR + else if ((state->synctype == 20) || (state->synctype == 24)) + { + /* dPMR Frame Sync 1 */ + fprintf(stderr, "dPMR Frame sync 1 "); + } + else if ((state->synctype == 21) || (state->synctype == 25)) + { + /* dPMR Frame Sync 2 */ + fprintf(stderr, "dPMR Frame sync 2 "); + { + //state->rf_mod = GFSK_MODE; + state->nac = 0; + state->lastsrc = 0; + state->lasttg = 0; + if (opts->errorbars == 1) + { + if (opts->verbose > 0) + { + level = (int) state->max / 164; + fprintf(stderr, "inlvl: %2i%% ", level); + } + } + state->nac = 0; + if ((opts->mbe_out_dir[0] != 0) && (opts->mbe_out_f == NULL)) + { + openMbeOutFile (opts, state); + } + sprintf(state->fsubtype, " VOICE "); + processdPMRvoice (opts, state); + return; + } + } + else if ((state->synctype == 22) || (state->synctype == 26)) + { + /* dPMR Frame Sync 3 */ + fprintf(stderr, "dPMR Frame sync 3 "); + } + else if ((state->synctype == 23) || (state->synctype == 27)) + { + /* dPMR Frame Sync 4 */ + fprintf(stderr, "dPMR Frame sync 4 "); + } + //LEH dPMR else { // Read the NAC, 12 bits diff --git a/src/dsd_frame_sync.c b/src/dsd_frame_sync.c index e2a7417..44c6fd1 100644 --- a/src/dsd_frame_sync.c +++ b/src/dsd_frame_sync.c @@ -143,11 +143,22 @@ getFrameSync (dsd_opts * opts, dsd_state * state) * 17 = -NXDN (inverted data frame) * 18 = +D-STAR_HD * 19 = -D-STAR_HD + * 20 = +dPMR Frame Sync 1 + * 21 = +dPMR Frame Sync 2 + * 22 = +dPMR Frame Sync 3 + * 23 = +dPMR Frame Sync 4 + * 24 = -dPMR Frame Sync 1 + * 25 = -dPMR Frame Sync 2 + * 26 = -dPMR Frame Sync 3 + * 27 = -dPMR Frame Sync 4 + * 28 = +NXDN (sync only) + * 29 = -NXDN (sync only */ int i, j, t, o, dibit, sync, symbol, synctest_pos, lastt; char synctest[25]; + char synctest12[13]; //LEH DPMR char synctest18[19]; char synctest32[33]; char modulation[8]; @@ -195,7 +206,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) playRawAudio(opts, state); //this is on line 21 in dsd_audio.c } */ - + /* if (opts->reset_state == 1 && state->carrier == 0) { @@ -512,6 +523,8 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } } } + //old style for DMR + /* if (opts->frame_dmr == 1) { if ((strcmp (synctest, DMR_MS_DATA_SYNC) == 0) || (strcmp (synctest, DMR_BS_DATA_SYNC) == 0) )//try this @@ -586,6 +599,734 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } } } + */ + //LEH dPMR + if(opts->frame_dpmr == 1) + { + strncpy(synctest, (synctest_p - 23), 24); + strncpy(synctest12, (synctest_p - 11), 12); + + if(strcmp(synctest, DPMR_FRAME_SYNC_1) == 0) + { + if (opts->inverted_dpmr == 0) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_1\n"); // TODO : To be removed + + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + } + + /* The next part of the superframe will normally be the first part */ + //opts->dPMR_next_part_of_superframe = 1; + + state->lastsynctype = 20; + return (20); + } + else + { + state->lastsynctype = 20; + } + } /* End if (opts->inverted_dpmr == 0) */ + } + else if(strcmp(synctest12, DPMR_FRAME_SYNC_2) == 0) + { + if (opts->inverted_dpmr == 0) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_2\n"); // TODO : To be removed + + // TODO : Modif 2019-01-04 + //if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + // (state->lastsynctype == 22) || (state->lastsynctype == 23)) + //{ + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + } + + state->lastsynctype = 21; + return (21); + // TODO : Modif 2019-01-04 + //} + //else + //{ + // state->lastsynctype = 21; + //} + } + } + else if(strcmp(synctest12, DPMR_FRAME_SYNC_3) == 0) + { + if (opts->inverted_dpmr == 0) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_3\n"); // TODO : To be removed + + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + } + + /* The next part of the superframe will normally be the first part */ + //opts->dPMR_next_part_of_superframe = 1; + + state->lastsynctype = 22; + return (22); + } + else + { + state->lastsynctype = 22; + } + } + } + if(strcmp(synctest, DPMR_FRAME_SYNC_4) == 0) + { + if (opts->inverted_dpmr == 0) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_4\n"); // TODO : To be removed + + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + } + + /* The next part of the superframe will normally be the first part */ + //opts->dPMR_next_part_of_superframe = 1; + + state->lastsynctype = 23; + return (23); + } + else + { + state->lastsynctype = 23; + } + } + } + else if(strcmp(synctest, INV_DPMR_FRAME_SYNC_1) == 0) + { + if (opts->inverted_dpmr) + { + //fprintf(stderr, "INV_DPMR_FRAME_SYNC_1\n"); // TODO : To be removed + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 24; + return (24); + } + else + { + state->lastsynctype = 24; + } + } + } + else if(strcmp(synctest12, INV_DPMR_FRAME_SYNC_2) == 0) + { + if (opts->inverted_dpmr) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_2\n"); // TODO : To be removed + + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 25; + return (25); + } + else + { + state->lastsynctype = 25; + } + } + } + else if(strcmp(synctest12, INV_DPMR_FRAME_SYNC_3) == 0) + { + if (opts->inverted_dpmr) + { + //fprintf(stderr, "INV_DPMR_FRAME_SYNC_3\n"); // TODO : To be removed + + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 26; + return (26); + } + else + { + state->lastsynctype = 26; + } + } + } + if(strcmp(synctest, INV_DPMR_FRAME_SYNC_4) == 0) + { + if (opts->inverted_dpmr) + { + //fprintf(stderr, "INV_DPMR_FRAME_SYNC_4\n"); // TODO : To be removed + + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 27; + return (27); + } + else + { + state->lastsynctype = 27; + } + } + } + else + { + /* No dPMR frame sync detected */ + } + } /* End if(opts->frame_dpmr == 1) */ + + // + //LEH dPMR + /* //testing frame syncs here for better performance + if(opts->frame_dpmr == 1) + { + strncpy(synctest, (synctest_p - 23), 24); + strncpy(synctest12, (synctest_p - 11), 12); + + + + //if(strcmp(synctest, DPMR_FRAME_SYNC_1) == 0) + if (strncmperr (synctest, DPMR_FRAME_SYNC_1, 24, 1) == 0 ) //24 bits on frame 1 and 4, 12 on 2 and 3? + { + if (opts->inverted_dpmr == 0) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_1\n"); // TODO : To be removed + + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + } + + //The next part of the superframe will normally be the first part + //opts->dPMR_next_part_of_superframe = 1; + + state->lastsynctype = 20; + return (20); + } + else + { + state->lastsynctype = 20; + } + } //End if (opts->inverted_dpmr == 0) + } + + + + //else if(strcmp(synctest12, DPMR_FRAME_SYNC_2) == 0) + if (strncmperr (synctest12, DPMR_FRAME_SYNC_2, 12, 1) == 0 ) //char synctest12[13] ?? + { + if (opts->inverted_dpmr == 0) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_2\n"); // TODO : To be removed + + // TODO : Modif 2019-01-04 + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + } + + state->lastsynctype = 21; + return (21); + // TODO : Modif 2019-01-04 + } + else + { + state->lastsynctype = 21; + } + } + } + + + + //else if(strcmp(synctest12, DPMR_FRAME_SYNC_3) == 0) + if (strncmperr (synctest12, DPMR_FRAME_SYNC_3, 12, 1) == 0 ) //char synctest12[13] ?? + { + if (opts->inverted_dpmr == 0) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_3\n"); // TODO : To be removed + + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + } + + //The next part of the superframe will normally be the first part// + //opts->dPMR_next_part_of_superframe = 1; + + state->lastsynctype = 22; + return (22); + } + else + { + state->lastsynctype = 22; + } + } + } + //if(strcmp(synctest, DPMR_FRAME_SYNC_4) == 0) + if (strncmperr (synctest, DPMR_FRAME_SYNC_4, 24, 1) == 0 ) //if, or else if? original is else if + { + if (opts->inverted_dpmr == 0) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_4\n"); // TODO : To be removed + + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + } + + //The next part of the superframe will normally be the first part + //opts->dPMR_next_part_of_superframe = 1; + + state->lastsynctype = 23; + return (23); + } + else + { + state->lastsynctype = 23; + } + } + } + //else if(strcmp(synctest, INV_DPMR_FRAME_SYNC_1) == 0) + //else if (strncmperr (synctest12, INV_DPMR_FRAME_SYNC_1, 12, 1) == 0 ) + if (strncmperr (synctest, INV_DPMR_FRAME_SYNC_1, 24, 1) == 0 ) //if, or else if + { + if (opts->inverted_dpmr) + { + //fprintf(stderr, "INV_DPMR_FRAME_SYNC_1\n"); // TODO : To be removed + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 24; + return (24); + } + else + { + state->lastsynctype = 24; + } + } + } + + + + //else if(strcmp(synctest12, INV_DPMR_FRAME_SYNC_2) == 0) + if (strncmperr (synctest12, INV_DPMR_FRAME_SYNC_2, 12, 1) == 0 ) + { + if (opts->inverted_dpmr) + { + //fprintf(stderr, "DPMR_FRAME_SYNC_2\n"); // TODO : To be removed + + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { + + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 25; + return (25); + } + else + { + state->lastsynctype = 25; + } + } + } + //else if(strcmp(synctest12, INV_DPMR_FRAME_SYNC_3) == 0) + + + + if (strncmperr (synctest12, INV_DPMR_FRAME_SYNC_3, 12, 1) == 0 ) + { + if (opts->inverted_dpmr) + { + //fprintf(stderr, "INV_DPMR_FRAME_SYNC_3\n"); // TODO : To be removed + + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 26; + return (26); + } + else + { + state->lastsynctype = 26; + } + } + } + //if(strcmp(synctest, INV_DPMR_FRAME_SYNC_4) == 0) //if, or else if + if (strncmperr (synctest, INV_DPMR_FRAME_SYNC_4, 24, 1) == 0 ) + { + if (opts->inverted_dpmr) + { + //fprintf(stderr, "INV_DPMR_FRAME_SYNC_4\n"); // TODO : To be removed + + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf(state->ftype, " dPMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 27; + return (27); + } + else + { + state->lastsynctype = 27; + } + } + } + else + { + //No dPMR frame sync detected + } + + } //End if(opts->frame_dpmr == 1) + */ + //LEH DMR Sync + if (opts->frame_dmr == 1) + { + if ((strcmp (synctest, DMR_MS_DATA_SYNC) == 0) || (strcmp (synctest, DMR_BS_DATA_SYNC) == 0)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + (lmax)) / 2; + state->min = ((state->min) + (lmin)) / 2; + state->directmode = 0; + if (opts->inverted_dmr == 0) + { + // data frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 10; + return (10); + } + else + { + // inverted voice frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + } + if (state->lastsynctype != 11) + { + state->firstframe = 1; + } + state->lastsynctype = 11; + return (11); + } + } + if(strcmp (synctest, DMR_DIRECT_MODE_TS1_DATA_SYNC) == 0) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + (lmax)) / 2; + state->min = ((state->min) + (lmin)) / 2; + state->currentslot = 0; + state->directmode = 1; //Direct mode + if (opts->inverted_dmr == 0) + { + // data frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 10; + return (10); + } + else + { + // inverted voice frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + } + if (state->lastsynctype != 11) + { + state->firstframe = 1; + } + state->lastsynctype = 11; + return (11); + } + } /* End if(strcmp (synctest, DMR_DIRECT_MODE_TS1_DATA_SYNC) == 0) */ + if(strcmp (synctest, DMR_DIRECT_MODE_TS2_DATA_SYNC) == 0) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + (lmax)) / 2; + state->min = ((state->min) + (lmin)) / 2; + state->currentslot = 1; + state->directmode = 1; //Direct mode + if (opts->inverted_dmr == 0) + { + // data frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 10; + return (10); + } + else + { + // inverted voice frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + } + if (state->lastsynctype != 11) + { + state->firstframe = 1; + } + state->lastsynctype = 11; + return (11); + } + } /* End if(strcmp (synctest, DMR_DIRECT_MODE_TS2_DATA_SYNC) == 0) */ + if((strcmp (synctest, DMR_MS_VOICE_SYNC) == 0) || (strcmp (synctest, DMR_BS_VOICE_SYNC) == 0)) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + state->directmode = 0; + if (opts->inverted_dmr == 0) + { + // voice frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + } + if (state->lastsynctype != 12) + { + state->firstframe = 1; + } + state->lastsynctype = 12; + return (12); + } + else + { + // inverted data frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 13; + return (13); + } + } + if(strcmp (synctest, DMR_DIRECT_MODE_TS1_VOICE_SYNC) == 0) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + state->currentslot = 0; + state->directmode = 1; //Direct mode + if (opts->inverted_dmr == 0) + { + // voice frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + } + if (state->lastsynctype != 12) + { + state->firstframe = 1; + } + state->lastsynctype = 12; + return (12); + } + else + { + // inverted data frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 13; + return (13); + } + } /* End if(strcmp (synctest, DMR_DIRECT_MODE_TS1_VOICE_SYNC) == 0) */ + if(strcmp (synctest, DMR_DIRECT_MODE_TS2_VOICE_SYNC) == 0) + { + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + state->currentslot = 1; + state->directmode = 1; //Direct mode + if (opts->inverted_dmr == 0) + { + // voice frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + } + if (state->lastsynctype != 12) + { + state->firstframe = 1; + } + state->lastsynctype = 12; + return (12); + } + else + { + // inverted data frame + sprintf(state->ftype, " DMR "); + if (opts->errorbars == 1) + { + printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + } + state->lastsynctype = 13; + return (13); + } + } //End if(strcmp (synctest, DMR_DIRECT_MODE_TS2_VOICE_SYNC) == 0) + } //End if (opts->frame_dmr == 1) + + //end LEH DMR Sync if (opts->frame_provoice == 1) { strncpy (synctest32, (synctest_p - 31), 32); diff --git a/src/dsd_main.c b/src/dsd_main.c index 2d3c38f..7dbe392 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -168,6 +168,7 @@ initOpts (dsd_opts * opts) opts->frame_nxdn48 = 0; opts->frame_nxdn96 = 0; opts->frame_dmr = 1; + opts->frame_dpmr = 0; //borrow from LEH opts->frame_provoice = 0; opts->mod_c4fm = 1; opts->mod_qpsk = 1; @@ -190,16 +191,19 @@ initOpts (dsd_opts * opts) opts->rtl_bandwidth = 12; //changed recommended default to 12, 24 for ProVoice opts->pulse_raw_rate_in = 48000; opts->pulse_raw_rate_out = 48000; //doing tests with 2 channels at 24000 for 48000 audio default in pulse - opts->pulse_digi_rate_in = 24000; + opts->pulse_digi_rate_in = 48000; opts->pulse_digi_rate_out = 24000; //need to copy this to rtl type in and change rate out to 8000 opts->pulse_raw_in_channels = 1; opts->pulse_raw_out_channels = 1; - opts->pulse_digi_in_channels = 2; + opts->pulse_digi_in_channels = 1; //2 opts->pulse_digi_out_channels = 2; opts->pulse_flush = 1; //set 0 to flush, 1 for flushed opts->use_ncurses_terminal = 0; //ncurses terminal disabled by default, call with -N opts->payload = 0; //initialize with 0 so can pass == 0 arguments + + opts->EncryptionMode = MODE_UNENCRYPTED; + opts->inverted_dpmr = 0; } void @@ -297,6 +301,10 @@ initState (dsd_state * state) state->nxdn_last_ran = -1; state->nxdn_last_rid = 0; + state->dpmr_color_code = -1; + state->dpmr_caller_id = 0; + state->dpmr_target_id = 0; + #ifdef TRACE_DSD state->debug_sample_index = 0; state->debug_label_file = NULL; @@ -357,11 +365,12 @@ usage () fprintf (stderr," -fa Auto-detect frame type (default)\n"); fprintf (stderr," -f1 Decode only P25 Phase 1\n"); fprintf (stderr," -fd Decode only D-STAR\n"); + fprintf (stderr," -fr Decode only DMR/MOTOTRBO\n"); + fprintf (stderr," -fx Decode only X2-TDMA\n"); fprintf (stderr," -fi Decode only NXDN48* (6.25 kHz) / IDAS*\n"); fprintf (stderr," -fn Decode only NXDN96* (12.5 kHz)\n"); fprintf (stderr," -fp Decode only ProVoice*\n"); - fprintf (stderr," -fr Decode only DMR/MOTOTRBO\n"); - fprintf (stderr," -fx Decode only X2-TDMA\n"); + fprintf (stderr," -fm Decode only dPMR*\n"); fprintf (stderr," -l Disable DMR/MOTOTRBO and NXDN input filtering\n"); fprintf (stderr," -ma Auto-select modulation optimizations (default)\n"); fprintf (stderr," -mc Use only C4FM modulation optimizations\n"); @@ -371,6 +380,7 @@ usage () fprintf (stderr," -u Unvoiced speech quality (default=3)\n"); fprintf (stderr," -xx Expect non-inverted X2-TDMA signal\n"); fprintf (stderr," -xr Expect inverted DMR/MOTOTRBO signal\n"); + fprintf (stderr," -xd Expect inverted ICOM dPMR signal\n"); //still need to do this fprintf (stderr,"\n"); fprintf (stderr," * denotes frame types that cannot be auto-detected.\n"); fprintf (stderr,"\n"); @@ -619,6 +629,8 @@ main (int argc, char **argv) initOpts (&opts); initState (&state); + InitAllFecFunction(); //HERE + exitflag = 0; signal (SIGINT, sigfun); @@ -807,6 +819,7 @@ main (int argc, char **argv) opts.frame_nxdn48 = 0; //turn it on, doesn't work due to symbol rate difference opts.frame_nxdn96 = 0; //causes false positives, leave on 0 opts.frame_dmr = 1; + opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; //turn it on, doesn't work due to symbol rate difference } else if (optarg[0] == 'd') @@ -817,6 +830,7 @@ main (int argc, char **argv) opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; + opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; fprintf (stderr,"Decoding only D-STAR frames.\n"); } @@ -828,6 +842,7 @@ main (int argc, char **argv) opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; + opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; fprintf (stderr,"Decoding only X2-TDMA frames.\n"); } @@ -839,6 +854,7 @@ main (int argc, char **argv) opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; + opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 1; state.samplesPerSymbol = 5; state.symbolCenter = 2; @@ -858,6 +874,7 @@ main (int argc, char **argv) opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; + opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; opts.mod_gfsk = 0; opts.mod_c4fm = 1; //Phase 1 only uses C4FM, right? @@ -873,6 +890,7 @@ main (int argc, char **argv) opts.frame_nxdn48 = 1; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; + opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; //I think I've got it to decode on proper modulation now c4fm, not really sure what the difference is in DSD, seems to work either way for some reason state.samplesPerSymbol = 20; @@ -894,6 +912,7 @@ main (int argc, char **argv) opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 1; opts.frame_dmr = 0; + opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; opts.mod_c4fm = 1; //was 0 opts.mod_qpsk = 0; @@ -910,6 +929,7 @@ main (int argc, char **argv) opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 1; + opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; opts.mod_c4fm = 1; //just a test opts.mod_qpsk = 0; @@ -917,6 +937,32 @@ main (int argc, char **argv) state.rf_mod = 0; // fprintf (stderr,"Decoding only DMR/MOTOTRBO frames.\n"); } + else if (optarg[0] == 'm') + { + opts.frame_dstar = 0; + opts.frame_x2tdma = 0; + opts.frame_p25p1 = 0; + opts.frame_nxdn48 = 0; + opts.frame_nxdn96 = 0; + opts.frame_dmr = 0; + opts.frame_provoice = 0; + opts.frame_dpmr = 1; + state.samplesPerSymbol = 20; //same as NXDN48 - 20 + state.symbolCenter = 10; //same as NXDN48 - 10 + opts.mod_c4fm = 1; + opts.mod_qpsk = 0; + opts.mod_gfsk = 0; + state.rf_mod = 0; + //opts.pulse_digi_rate_in = 48000; + //opts.pulse_digi_rate_out = 48000; //need to copy this to rtl type in and change rate out to 8000 + //opts.pulse_digi_in_channels = 1; + //opts.pulse_digi_out_channels = 1; + //opts.inverted_dpmr = 1; + fprintf(stderr, "Setting symbol rate to 2400 / second\n"); + //fprintf(stderr, "Enabling only GFSK modulation optimizations.\n"); + fprintf(stderr, "Enabling only C4FM modulation optimizations.\n"); + fprintf(stderr, "Decoding only dPMR (4800 baud frames).\n"); + } break; case 'm': if (optarg[0] == 'a') @@ -974,6 +1020,11 @@ main (int argc, char **argv) opts.inverted_dmr = 1; fprintf (stderr,"Expecting inverted DMR/MOTOTRBO signals.\n"); } + else if (optarg[0] == 'd') + { + opts.inverted_dpmr = 1; + fprintf (stderr, "Expecting inverted ICOM dPMR signals.\n"); + } break; case 'A': sscanf (optarg, "%i", &opts.mod_threshold); diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index 92e8e39..173e5a2 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -36,7 +36,7 @@ int rn; //last ran int nc; //nac int src; //last rid? double? int lls = -1; //last last sync type -int dcc = 0; //initialize with 0 to prevent core dump +int dcc = -1; //initialize with 0 to prevent core dump int i = 0; char * s0last = " "; char * s1last = " "; @@ -53,7 +53,7 @@ char * FM_bannerN[9] = { "https://github.com/lwvmobile/dsd-fme/tree/pulseaudio " }; -char * SyncTypes[20] = { +char * SyncTypes[30] = { "+P25P1", "-P25P1", "+X2TDMA DATA", @@ -73,7 +73,15 @@ char * SyncTypes[20] = { "+NXDN DATA", //16 "-NXDN DATA", //17 "+DSTAR HD", - "-DSTAR HD" + "-DSTAR HD", + "+dPMR", + "+dPMR", + "+dPMR", + "+dPMR", + "-dPMR", + "-dPMR", + "-dPMR", + "-dPMR" }; @@ -187,7 +195,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) //if (state->dmr_color_code != dcc && (lls == 11 || lls == 13 || lls == 10 || lls == 12) ) //DMR Voice + last two is data //if ( (call_matrix[9][4] != dcc || call_matrix[9][2] != rd) && (lls == 10 || lls == 11 || lls == 12 || lls == 13) ) //DMR - if ( (call_matrix[9][4] != dcc || call_matrix[9][2] != rd) && (lls == 12 || lls == 13) ) //DMR + if ( (call_matrix[9][4] != dcc || call_matrix[9][2] != rd) && (lls == 12 || lls == 13) ) //DMR, needs work { //dcc = state->dmr_color_code; for (short int k = 0; k < 9; k++) @@ -302,19 +310,25 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) rn = state->nxdn_last_ran; } - if (state->dmr_color_code > 0 && (lls == 10 || lls == 11 || lls == 12 || lls == 13) ) //DMR, DCC only carried on Data? + //if (state->dmr_color_code > 0 && (lls == 10 || lls == 11 || lls == 12 || lls == 13) ) //DMR, DCC only carried on Data? + //if (state->color_code > -1 && (lls == 10 || lls == 11 || lls == 12 || lls == 13) ) //DMR, DCC only carried on Data? + if (state->color_code_ok && (lls == 12 || lls == 13) ) //DMR, needs work + { dcc = state->dmr_color_code; + //dcc = state->color_code; } - if (state->lastsrc > 0 && (lls == 12 || lls == 13)) + if (state->lastsrc > 0 && (lls == 12 || lls == 13 || lls == 0 || lls == 1)) //DMR Voice and P25P1 { rd = state->lastsrc; + opts->p25enc = 0; } - if (state->lasttg > 0 && (lls == 12 || lls == 13)) + if (state->lasttg > 0 && (lls == 12 || lls == 13 || lls == 0 || lls == 1)) //DMR Voice and P25P1 { tg = state->lasttg; + opts->p25enc = 0; } //if (state->lastsynctype == 8 || state->lastsynctype == 9 || state->lastsynctype == 16 || state->lastsynctype == 17) //change this to NXDN syncs later on @@ -326,12 +340,13 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } //printw ("Error?: [%i] [%i] \n", state->errs, state->errs2); //what are these? - + /* //wasn't we already doing this twice if (state->lasttg > 0 && state->lastsrc > 0) { tg = state->lasttg; rd = state->lastsrc; } + */ if (state->nac > 0) { nc = state->nac; @@ -358,7 +373,35 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) //if (state->lastsynctype == 12 || state->lastsynctype == 13) //DMR Voice Types if (lls == 12 || lls == 13) //DMR Voice Types { - printw ("| DCC: [%i]\n", dcc); + //printw ("| DCC: [%i] FID: [%02X]\n", dcc, state->dmr_fid); + printw ("| DCC: [%i] FID: [%02X] SOP: [%X] \n", dcc, state->dmr_fid, state->dmr_so); + printw ("| TID: [%i] RID: [%i]", tg, rd); + if(state->dmr_so & 0x80) //& apparently is same as == + { + attron(COLOR_PAIR(2)); + printw (" **Emergency** "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + + if(state->dmr_so & 0x40) + { + attron(COLOR_PAIR(2)); + printw (" **ENC** "); + //printw ("0x%X", state->payload_algid); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + //opts->p25enc = 1; //just testing for now + } + + if(state->dmr_so & 0x30) + { + attron(COLOR_PAIR(2)); + printw (" **Private Call** "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + printw("\n"); //printw ("%s ", state->slot0light); printw ("| SLOT 0 "); if (state->currentslot == 0) //find out how to tell when slot0 has voice @@ -380,9 +423,39 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) //if (state->lastsynctype == 10 || state->lastsynctype == 11) //DMR Data Types - if (lls == 10 || lls == 11) //DMR Data Types + if (lls == 10 || lls == 11 ) //DMR Data Types { - printw ("| DCC: [%i]\n", dcc); + //printw ("| DCC: [%i]\n", dcc); + printw ("| DCC: [%i] FID: [%02X] SOP: [%X] \n", dcc, state->dmr_fid, state->dmr_so); + printw ("| TID: [%i] RID: [%i]", tg, rd); + //does this need to be in DATA type? + /* + if(state->dmr_so & 0x80) + { + attron(COLOR_PAIR(2)); + printw (" **Emergency** "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + + if(state->dmr_so & 0x40) + { + attron(COLOR_PAIR(2)); + printw (" **ENC** "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + + if(state->dmr_so & 0x30) + { + attron(COLOR_PAIR(2)); + printw (" **Private Call** "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + */ + printw("\n"); + //printw ("%s ", state->slot0light); printw ("| SLOT 0 "); if (state->currentslot == 0) //find out how to tell when slot0 has voice @@ -406,6 +479,14 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } printw ("%s \n", s1last); } + + //dPMR + if (lls == 20 || lls == 21 || lls == 22 || lls == 23 ||lls == 24 || lls == 25 || lls == 26 || lls == 27) + { + printw ("| DCC: [%i] ", state->dpmr_color_code); + printw ("TID: [%s] RID: [%s] \n", state->dpmr_target_id, state->dpmr_caller_id); + } + if (lls != -1) //is there a synctype 0? { printw ("| %s \n", SyncTypes[lls]); diff --git a/src/fec.c b/src/fec.c new file mode 100644 index 0000000..6180f2d --- /dev/null +++ b/src/fec.c @@ -0,0 +1,1448 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB. // +// // +// 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 as version 3 of the License, or // +// // +// 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 V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsd.h" + +unsigned char Hamming_7_4_m_corr[8]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_7_4_m_G[7*4] = { + 1, 0, 0, 0, 1, 0, 1, + 0, 1, 0, 0, 1, 1, 1, + 0, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 1, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_7_4_m_H[7*3] = { + 1, 1, 1, 0, 1, 0, 0, + 0, 1, 1, 1, 0, 1, 0, + 1, 1, 0, 1, 0, 0, 1 +// 0 1 2 3 <- correctable bit positions +}; + + +// ======================================================================================== + + +unsigned char Hamming_12_8_m_corr[16]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_12_8_m_G[12*8] = { + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_12_8_m_H[12*4] = { + 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, + 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 +// 0 1 2 3 4 5 6 7 <- correctable bit positions +}; + + +// ======================================================================================== + + +unsigned char Hamming_13_9_m_corr[16]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_13_9_m_G[13*9] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1 +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_13_9_m_H[13*4] = { + 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, +// 0 1 2 3 4 5 6 7 8 <- correctable bit positions +}; + + +// ======================================================================================== + + +unsigned char Hamming_15_11_m_corr[16]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_15_11_m_G[15*11] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_15_11_m_H[15*4] = { + 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, + 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, +// 0 1 2 3 4 5 6 7 8 9 10 <- correctable bit positions +}; + + +// ======================================================================================== + + +unsigned char Hamming_16_11_4_m_corr[32]; //!< single bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Hamming_16_11_4_m_G[16*11] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1 +}; + +//!< Parity check matrix of bits +const unsigned char Hamming_16_11_4_m_H[16*5] = { + 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, + 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1 +}; + + +// ======================================================================================== + + +unsigned char Golay_20_8_m_corr[4096][3]; //!< up to 3 bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Golay_20_8_m_G[20*8] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Golay_20_8_m_H[20*12] = { + 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + + +// ======================================================================================== + + +unsigned char Golay_23_12_m_corr[2048][3]; //!< up to 3 bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Golay_23_12_m_G[23*12] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Golay_23_12_m_H[23*11] = { + 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + + +// ======================================================================================== + + +unsigned char Golay_24_12_m_corr[4096][3]; //!< up to 3 bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char Golay_24_12_m_G[24*12] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char Golay_24_12_m_H[24*12] = { + 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + + +// ======================================================================================== + + +unsigned char QR_16_7_6_m_corr[512][2]; //!< up to 2 bit error correction by syndrome index + +//!< Generator matrix of bits +const unsigned char QR_16_7_6_m_G[16*7] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, + 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, +}; + +//!< Parity check matrix of bits +const unsigned char QR_16_7_6_m_H[16*9] = { + 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, +}; + + +// ======================================================================================== + + +void Hamming_7_4_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_7_4_m_corr, 0xFF, 8); // initialize with all invalid positions + Hamming_7_4_m_corr[0b101] = 0; + Hamming_7_4_m_corr[0b111] = 1; + Hamming_7_4_m_corr[0b110] = 2; + Hamming_7_4_m_corr[0b011] = 3; + Hamming_7_4_m_corr[0b100] = 4; + Hamming_7_4_m_corr[0b010] = 5; + Hamming_7_4_m_corr[0b001] = 6; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_7_4_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 7); + + for (i = 0; i < 4; i++) + { + for (j = 0; j < 7; j++) + { + encodedBits[j] += origBits[i] * Hamming_7_4_m_G[7*i + j]; + } + } + + for (i = 0; i < 7; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_7_4_decode(unsigned char *rxBits) // corrects in place +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + + for (is = 0; is < 3; is++) + { + syndromeI += (((rxBits[0] * Hamming_7_4_m_H[7*is + 0]) + + (rxBits[1] * Hamming_7_4_m_H[7*is + 1]) + + (rxBits[2] * Hamming_7_4_m_H[7*is + 2]) + + (rxBits[3] * Hamming_7_4_m_H[7*is + 3]) + + (rxBits[4] * Hamming_7_4_m_H[7*is + 4]) + + (rxBits[5] * Hamming_7_4_m_H[7*is + 5]) + + (rxBits[6] * Hamming_7_4_m_H[7*is + 6])) % 2) << (2-is); + } + + if (syndromeI > 0) + { + if (Hamming_7_4_m_corr[syndromeI] == 0xFF) + { + return false; + } + else + { + rxBits[Hamming_7_4_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + return true; +} + + +// ======================================================================================== + + +void Hamming_12_8_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_12_8_m_corr, 0xFF, 16); // initialize with all invalid positions + Hamming_12_8_m_corr[0b1110] = 0; + Hamming_12_8_m_corr[0b0111] = 1; + Hamming_12_8_m_corr[0b1010] = 2; + Hamming_12_8_m_corr[0b0101] = 3; + Hamming_12_8_m_corr[0b1011] = 4; + Hamming_12_8_m_corr[0b1100] = 5; + Hamming_12_8_m_corr[0b0110] = 6; + Hamming_12_8_m_corr[0b0011] = 7; + Hamming_12_8_m_corr[0b1000] = 8; + Hamming_12_8_m_corr[0b0100] = 9; + Hamming_12_8_m_corr[0b0010] = 10; + Hamming_12_8_m_corr[0b0001] = 11; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_12_8_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 12); + + for (i = 0; i < 8; i++) + { + for (j = 0; j < 12; j++) + { + encodedBits[j] += origBits[i] * Hamming_12_8_m_G[12*i + j]; + } + } + + for (i = 0; i < 12; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_12_8_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords) +{ + bool correctable = true; + int ic = 0; + int is = 0; + int syndromeI = 0; // syndrome index + + for (ic = 0; ic < nbCodewords; ic++) + { + // calculate syndrome + + syndromeI = 0; // syndrome index + + for (is = 0; is < 4; is++) + { + syndromeI += (((rxBits[12*ic + 0] * Hamming_12_8_m_H[12*is + 0]) + + (rxBits[12*ic + 1] * Hamming_12_8_m_H[12*is + 1]) + + (rxBits[12*ic + 2] * Hamming_12_8_m_H[12*is + 2]) + + (rxBits[12*ic + 3] * Hamming_12_8_m_H[12*is + 3]) + + (rxBits[12*ic + 4] * Hamming_12_8_m_H[12*is + 4]) + + (rxBits[12*ic + 5] * Hamming_12_8_m_H[12*is + 5]) + + (rxBits[12*ic + 6] * Hamming_12_8_m_H[12*is + 6]) + + (rxBits[12*ic + 7] * Hamming_12_8_m_H[12*is + 7]) + + (rxBits[12*ic + 8] * Hamming_12_8_m_H[12*is + 8]) + + (rxBits[12*ic + 9] * Hamming_12_8_m_H[12*is + 9]) + + (rxBits[12*ic + 10] * Hamming_12_8_m_H[12*is + 10]) + + (rxBits[12*ic + 11] * Hamming_12_8_m_H[12*is + 11])) % 2) << (3-is); + } + + // correct bit + + if (syndromeI > 0) // single bit error correction + { + if (Hamming_12_8_m_corr[syndromeI] == 0xFF) // uncorrectable error + { + correctable = false; + } + else + { + rxBits[Hamming_12_8_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + // move information bits + memcpy(&decodedBits[8*ic], &rxBits[12*ic], 8); + } + + return correctable; +} + + +// ======================================================================================== + + +void Hamming_13_9_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_13_9_m_corr, 0xFF, 16); // initialize with all invalid positions + Hamming_13_9_m_corr[0b1111] = 0; + Hamming_13_9_m_corr[0b1110] = 1; + Hamming_13_9_m_corr[0b0111] = 2; + Hamming_13_9_m_corr[0b1010] = 3; + Hamming_13_9_m_corr[0b0101] = 4; + Hamming_13_9_m_corr[0b1011] = 5; + Hamming_13_9_m_corr[0b1100] = 6; + Hamming_13_9_m_corr[0b0110] = 7; + Hamming_13_9_m_corr[0b0011] = 8; + Hamming_13_9_m_corr[0b1000] = 9; + Hamming_13_9_m_corr[0b0100] = 10; + Hamming_13_9_m_corr[0b0010] = 11; + Hamming_13_9_m_corr[0b0001] = 12; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_13_9_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + memset(encodedBits, 0, 13); + + for (i = 0; i < 9; i++) + { + for (j = 0; j < 13; j++) + { + encodedBits[j] += origBits[i] * Hamming_13_9_m_G[13*i + j]; + } + } + + for (i = 0; i < 13; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_13_9_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords) +{ + bool correctable = true; + int ic = 0, is = 0; + int syndromeI = 0; // syndrome index + + for (ic = 0; ic < nbCodewords; ic++) + { + // calculate syndrome + + syndromeI = 0; // syndrome index + + for (is = 0; is < 4; is++) + { + syndromeI += (((rxBits[13*ic + 0] * Hamming_13_9_m_H[13*is + 0]) + + (rxBits[13*ic + 1] * Hamming_13_9_m_H[13*is + 1]) + + (rxBits[13*ic + 2] * Hamming_13_9_m_H[13*is + 2]) + + (rxBits[13*ic + 3] * Hamming_13_9_m_H[13*is + 3]) + + (rxBits[13*ic + 4] * Hamming_13_9_m_H[13*is + 4]) + + (rxBits[13*ic + 5] * Hamming_13_9_m_H[13*is + 5]) + + (rxBits[13*ic + 6] * Hamming_13_9_m_H[13*is + 6]) + + (rxBits[13*ic + 7] * Hamming_13_9_m_H[13*is + 7]) + + (rxBits[13*ic + 8] * Hamming_13_9_m_H[13*is + 8]) + + (rxBits[13*ic + 9] * Hamming_13_9_m_H[13*is + 9]) + + (rxBits[13*ic + 10] * Hamming_13_9_m_H[13*is + 10]) + + (rxBits[13*ic + 11] * Hamming_13_9_m_H[13*is + 11]) + + (rxBits[13*ic + 12] * Hamming_13_9_m_H[13*is + 12])) % 2) << (3-is); + } + + // correct bit + + if (syndromeI > 0) // single bit error correction + { + if (Hamming_13_9_m_corr[syndromeI] == 0xFF) // uncorrectable error + { + correctable = false; + break; + } + else + { + rxBits[Hamming_13_9_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + // move information bits + if (decodedBits) + { + memcpy(&decodedBits[9*ic], &rxBits[13*ic], 9); + } + } + + return correctable; +} + + +// ======================================================================================== + + +void Hamming_15_11_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_15_11_m_corr, 0xFF, 16); // initialize with all invalid positions + Hamming_15_11_m_corr[0b1001] = 0; + Hamming_15_11_m_corr[0b1101] = 1; + Hamming_15_11_m_corr[0b1111] = 2; + Hamming_15_11_m_corr[0b1110] = 3; + Hamming_15_11_m_corr[0b0111] = 4; + Hamming_15_11_m_corr[0b1010] = 5; + Hamming_15_11_m_corr[0b0101] = 6; + Hamming_15_11_m_corr[0b1011] = 7; + Hamming_15_11_m_corr[0b1100] = 8; + Hamming_15_11_m_corr[0b0110] = 9; + Hamming_15_11_m_corr[0b0011] = 10; + Hamming_15_11_m_corr[0b1000] = 11; + Hamming_15_11_m_corr[0b0100] = 12; + Hamming_15_11_m_corr[0b0010] = 13; + Hamming_15_11_m_corr[0b0001] = 14; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_15_11_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + memset(encodedBits, 0, 15); + + for (i = 0; i < 11; i++) + { + for (j = 0; j < 15; j++) + { + encodedBits[j] += origBits[i] * Hamming_15_11_m_G[15*i + j]; + } + } + + for (i = 0; i < 15; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_15_11_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords) +{ + bool correctable = true; + int ic = 0, is = 0; + int syndromeI = 0; // syndrome index + + for (ic = 0; ic < nbCodewords; ic++) + { + // calculate syndrome + + syndromeI = 0; // syndrome index + + for (is = 0; is < 4; is++) + { + syndromeI += (((rxBits[15*ic + 0] * Hamming_15_11_m_H[15*is + 0]) + + (rxBits[15*ic + 1] * Hamming_15_11_m_H[15*is + 1]) + + (rxBits[15*ic + 2] * Hamming_15_11_m_H[15*is + 2]) + + (rxBits[15*ic + 3] * Hamming_15_11_m_H[15*is + 3]) + + (rxBits[15*ic + 4] * Hamming_15_11_m_H[15*is + 4]) + + (rxBits[15*ic + 5] * Hamming_15_11_m_H[15*is + 5]) + + (rxBits[15*ic + 6] * Hamming_15_11_m_H[15*is + 6]) + + (rxBits[15*ic + 7] * Hamming_15_11_m_H[15*is + 7]) + + (rxBits[15*ic + 8] * Hamming_15_11_m_H[15*is + 8]) + + (rxBits[15*ic + 9] * Hamming_15_11_m_H[15*is + 9]) + + (rxBits[15*ic + 10] * Hamming_15_11_m_H[15*is + 10]) + + (rxBits[15*ic + 11] * Hamming_15_11_m_H[15*is + 11]) + + (rxBits[15*ic + 12] * Hamming_15_11_m_H[15*is + 12]) + + (rxBits[15*ic + 13] * Hamming_15_11_m_H[15*is + 13]) + + (rxBits[15*ic + 14] * Hamming_15_11_m_H[15*is + 14])) % 2) << (3-is); + } + + // correct bit + + if (syndromeI > 0) // single bit error correction + { + if (Hamming_15_11_m_corr[syndromeI] == 0xFF) // uncorrectable error + { + correctable = false; + break; + } + else + { + rxBits[Hamming_15_11_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + // move information bits + if (decodedBits) + { + memcpy(&decodedBits[11*ic], &rxBits[15*ic], 11); + } + } + + return correctable; +} + + +// ======================================================================================== + + +void Hamming_16_11_4_init() +{ + // correctable bit positions given syndrome bits as index (see above) + memset(Hamming_16_11_4_m_corr, 0xFF, 32); // initialize with all invalid positions + Hamming_16_11_4_m_corr[0b10011] = 0; + Hamming_16_11_4_m_corr[0b11010] = 1; + Hamming_16_11_4_m_corr[0b11111] = 2; + Hamming_16_11_4_m_corr[0b11100] = 3; + Hamming_16_11_4_m_corr[0b01110] = 4; + Hamming_16_11_4_m_corr[0b10101] = 5; + Hamming_16_11_4_m_corr[0b01011] = 6; + Hamming_16_11_4_m_corr[0b10110] = 7; + Hamming_16_11_4_m_corr[0b11001] = 8; + Hamming_16_11_4_m_corr[0b01101] = 9; + Hamming_16_11_4_m_corr[0b00111] = 10; + Hamming_16_11_4_m_corr[0b10000] = 11; + Hamming_16_11_4_m_corr[0b01000] = 12; + Hamming_16_11_4_m_corr[0b00100] = 13; + Hamming_16_11_4_m_corr[0b00010] = 14; + Hamming_16_11_4_m_corr[0b00001] = 15; +} + +// Not very efficient but encode is used for unit testing only +void Hamming_16_11_4_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + memset(encodedBits, 0, 16); + + for (i = 0; i < 11; i++) + { + for (j = 0; j < 16; j++) + { + encodedBits[j] += origBits[i] * Hamming_16_11_4_m_G[16*i + j]; + } + } + + for (i = 0; i < 16; i++) + { + encodedBits[i] %= 2; + } +} + +bool Hamming_16_11_4_decode(unsigned char *rxBits, unsigned char *decodedBits, int nbCodewords) +{ + bool correctable = true; + int ic = 0, is = 0; + int syndromeI = 0; // syndrome index + + for (ic = 0; ic < nbCodewords; ic++) + { + // calculate syndrome + syndromeI = 0; // syndrome index + for (is = 0; is < 5; is++) + { + syndromeI += (((rxBits[16*ic + 0] * Hamming_16_11_4_m_H[16*is + 0]) + + (rxBits[16*ic + 1] * Hamming_16_11_4_m_H[16*is + 1]) + + (rxBits[16*ic + 2] * Hamming_16_11_4_m_H[16*is + 2]) + + (rxBits[16*ic + 3] * Hamming_16_11_4_m_H[16*is + 3]) + + (rxBits[16*ic + 4] * Hamming_16_11_4_m_H[16*is + 4]) + + (rxBits[16*ic + 5] * Hamming_16_11_4_m_H[16*is + 5]) + + (rxBits[16*ic + 6] * Hamming_16_11_4_m_H[16*is + 6]) + + (rxBits[16*ic + 7] * Hamming_16_11_4_m_H[16*is + 7]) + + (rxBits[16*ic + 8] * Hamming_16_11_4_m_H[16*is + 8]) + + (rxBits[16*ic + 9] * Hamming_16_11_4_m_H[16*is + 9]) + + (rxBits[16*ic + 10] * Hamming_16_11_4_m_H[16*is + 10]) + + (rxBits[16*ic + 11] * Hamming_16_11_4_m_H[16*is + 11]) + + (rxBits[16*ic + 12] * Hamming_16_11_4_m_H[16*is + 12]) + + (rxBits[16*ic + 13] * Hamming_16_11_4_m_H[16*is + 13]) + + (rxBits[16*ic + 14] * Hamming_16_11_4_m_H[16*is + 14]) + + (rxBits[16*ic + 15] * Hamming_16_11_4_m_H[16*is + 15])) % 2) << (4-is); + } + + // correct bit + + if (syndromeI > 0) // single bit error correction + { + if (Hamming_16_11_4_m_corr[syndromeI] == 0xFF) // uncorrectable error + { + correctable = false; + break; + } + else + { + rxBits[Hamming_16_11_4_m_corr[syndromeI]] ^= 1; // flip bit + } + } + + // move information bits + if (decodedBits) + { + memcpy(&decodedBits[11*ic], &rxBits[16*ic], 11); + } + } + + return correctable; +} + + +// ======================================================================================== + + +void Golay_20_8_init() +{ + int i1 = 0, i2 = 0, i3 = 0, ir = 0, ip = 0; + int syndromeI = 0, syndromeIP = 0; + int ip1 = 0, ip2 = 0, ip3 = 0; + int syndromeIP1 = 0, syndromeIP2 = 0, syndromeIP3 = 0; + + memset(Golay_20_8_m_corr, 0xFF, 3*4096); + + for (i1 = 0; i1 < 8; i1++) + { + for (i2 = i1+1; i2 < 8; i2++) + { + for (i3 = i2+1; i3 < 8; i3++) + { + // 3 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += ((Golay_20_8_m_H[20*ir + i1] + Golay_20_8_m_H[20*ir + i2] + Golay_20_8_m_H[20*ir + i3]) % 2) << (11-ir); + } + + Golay_20_8_m_corr[syndromeI][0] = i1; + Golay_20_8_m_corr[syndromeI][1] = i2; + Golay_20_8_m_corr[syndromeI][2] = i3; + } + + // 2 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += ((Golay_20_8_m_H[20*ir + i1] + Golay_20_8_m_H[20*ir + i2]) % 2) << (11-ir); + } + + Golay_20_8_m_corr[syndromeI][0] = i1; + Golay_20_8_m_corr[syndromeI][1] = i2; + + // 1 possible bit flip left in the parity part + for (ip = 0; ip < 12; ip++) + { + syndromeIP = syndromeI ^ (1 << (11-ip)); + Golay_20_8_m_corr[syndromeIP][0] = i1; + Golay_20_8_m_corr[syndromeIP][1] = i2; + Golay_20_8_m_corr[syndromeIP][2] = 12 + ip; + } + } + + // single bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += Golay_20_8_m_H[20*ir + i1] << (11-ir); + } + + Golay_20_8_m_corr[syndromeI][0] = i1; + + for (ip1 = 0; ip1 < 12; ip1++) // 1 more bit flip in parity + { + syndromeIP1 = syndromeI ^ (1 << (11-ip1)); + Golay_20_8_m_corr[syndromeIP1][0] = i1; + Golay_20_8_m_corr[syndromeIP1][1] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); + Golay_20_8_m_corr[syndromeIP2][0] = i1; + Golay_20_8_m_corr[syndromeIP2][1] = 12 + ip1; + Golay_20_8_m_corr[syndromeIP2][2] = 12 + ip2; + } + } + } + + // no bit patterns (in message) -> all in parity + for (ip1 = 0; ip1 < 12; ip1++) // 1 bit flip in parity + { + syndromeIP1 = (1 << (11-ip1)); + Golay_20_8_m_corr[syndromeIP1][0] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); + Golay_20_8_m_corr[syndromeIP2][0] = 12 + ip1; + Golay_20_8_m_corr[syndromeIP2][1] = 12 + ip2; + + for (ip3 = ip2+1; ip3 < 12; ip3++) // 1 more bit flip in parity + { + syndromeIP3 = syndromeIP2 ^ (1 << (11-ip3)); + Golay_20_8_m_corr[syndromeIP3][0] = 12 + ip1; + Golay_20_8_m_corr[syndromeIP3][1] = 12 + ip2; + Golay_20_8_m_corr[syndromeIP3][2] = 12 + ip3; + } + } + } +} + +// Not very efficient but encode is used for unit testing only +void Golay_20_8_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 20); + + for (i = 0; i < 8; i++) + { + for (j = 0; j < 20; j++) + { + encodedBits[j] += origBits[i] * Golay_20_8_m_G[20*i + j]; + } + } + + for (i = 0; i < 20; i++) + { + encodedBits[i] %= 2; + } +} + +bool Golay_20_8_decode(unsigned char *rxBits) +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + int i = 0; + + for (is = 0; is < 12; is++) + { + syndromeI += (((rxBits[0] * Golay_20_8_m_H[20*is + 0]) + + (rxBits[1] * Golay_20_8_m_H[20*is + 1]) + + (rxBits[2] * Golay_20_8_m_H[20*is + 2]) + + (rxBits[3] * Golay_20_8_m_H[20*is + 3]) + + (rxBits[4] * Golay_20_8_m_H[20*is + 4]) + + (rxBits[5] * Golay_20_8_m_H[20*is + 5]) + + (rxBits[6] * Golay_20_8_m_H[20*is + 6]) + + (rxBits[7] * Golay_20_8_m_H[20*is + 7]) + + (rxBits[8] * Golay_20_8_m_H[20*is + 8]) + + (rxBits[9] * Golay_20_8_m_H[20*is + 9]) + + (rxBits[10] * Golay_20_8_m_H[20*is + 10]) + + (rxBits[11] * Golay_20_8_m_H[20*is + 11]) + + (rxBits[12] * Golay_20_8_m_H[20*is + 12]) + + (rxBits[13] * Golay_20_8_m_H[20*is + 13]) + + (rxBits[14] * Golay_20_8_m_H[20*is + 14]) + + (rxBits[15] * Golay_20_8_m_H[20*is + 15]) + + (rxBits[16] * Golay_20_8_m_H[20*is + 16]) + + (rxBits[17] * Golay_20_8_m_H[20*is + 17]) + + (rxBits[18] * Golay_20_8_m_H[20*is + 18]) + + (rxBits[19] * Golay_20_8_m_H[20*is + 19])) % 2) << (11-is); + } + + if (syndromeI > 0) + { + i = 0; + + for (; i < 3; i++) + { + if (Golay_20_8_m_corr[syndromeI][i] == 0xFF) + { + break; + } + else + { + rxBits[Golay_20_8_m_corr[syndromeI][i]] ^= 1; // flip bit + } + } + + if (i == 0) + { + return false; + } + } + + return true; +} + + +// ======================================================================================== + + +void Golay_23_12_init() +{ + int i1 = 0, i2 = 0, i3 = 0, ir = 0, ip = 0; + int syndromeI = 0, syndromeIP = 0; + int ip1 = 0, ip2 = 0, ip3 = 0; + int syndromeIP1 = 0, syndromeIP2 = 0, syndromeIP3 = 0; + + memset(Golay_23_12_m_corr, 0xFF, 3*2048); + + for (i1 = 0; i1 < 12; i1++) + { + for (i2 = i1+1; i2 < 12; i2++) + { + for (i3 = i2+1; i3 < 12; i3++) + { + // 3 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 11; ir++) + { + syndromeI += ((Golay_23_12_m_H[23*ir + i1] + Golay_23_12_m_H[23*ir + i2] + Golay_23_12_m_H[23*ir + i3]) % 2) << (10-ir); + } + + Golay_23_12_m_corr[syndromeI][0] = i1; + Golay_23_12_m_corr[syndromeI][1] = i2; + Golay_23_12_m_corr[syndromeI][2] = i3; + } + + // 2 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 11; ir++) + { + syndromeI += ((Golay_23_12_m_H[23*ir + i1] + Golay_23_12_m_H[23*ir + i2]) % 2) << (10-ir); + } + + Golay_23_12_m_corr[syndromeI][0] = i1; + Golay_23_12_m_corr[syndromeI][1] = i2; + + // 1 possible bit flip left in the parity part + for (ip = 0; ip < 11; ip++) + { + syndromeIP = syndromeI ^ (1 << (10-ip)); + Golay_23_12_m_corr[syndromeIP][0] = i1; + Golay_23_12_m_corr[syndromeIP][1] = i2; + Golay_23_12_m_corr[syndromeIP][2] = 12 + ip; + } + } + + // single bit patterns + syndromeI = 0; + + for (ir = 0; ir < 11; ir++) + { + syndromeI += Golay_23_12_m_H[23*ir + i1] << (10-ir); + } + + Golay_23_12_m_corr[syndromeI][0] = i1; + + for (ip1 = 0; ip1 < 11; ip1++) // 1 more bit flip in parity + { + syndromeIP1 = syndromeI ^ (1 << (10-ip1)); + Golay_23_12_m_corr[syndromeIP1][0] = i1; + Golay_23_12_m_corr[syndromeIP1][1] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 11; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (10-ip2)); + Golay_23_12_m_corr[syndromeIP2][0] = i1; + Golay_23_12_m_corr[syndromeIP2][1] = 12 + ip1; + Golay_23_12_m_corr[syndromeIP2][2] = 12 + ip2; + } + } + } + + // no bit patterns (in message) -> all in parity + for (ip1 = 0; ip1 < 11; ip1++) // 1 bit flip in parity + { + syndromeIP1 = (1 << (10-ip1)); + Golay_23_12_m_corr[syndromeIP1][0] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 11; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (10-ip2)); + Golay_23_12_m_corr[syndromeIP2][0] = 12 + ip1; + Golay_23_12_m_corr[syndromeIP2][1] = 12 + ip2; + + for (ip3 = ip2+1; ip3 < 11; ip3++) // 1 more bit flip in parity + { + syndromeIP3 = syndromeIP2 ^ (1 << (10-ip3)); + Golay_23_12_m_corr[syndromeIP3][0] = 12 + ip1; + Golay_23_12_m_corr[syndromeIP3][1] = 12 + ip2; + Golay_23_12_m_corr[syndromeIP3][2] = 12 + ip3; + } + } + } +} + +// Not very efficient but encode is used for unit testing only +void Golay_23_12_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 23); + + for (i = 0; i < 12; i++) // orig bits + { + for (j = 0; j < 23; j++) // codeword bits + { + encodedBits[j] += origBits[i] * Golay_23_12_m_G[23*i + j]; + } + } + + for (i = 0; i < 23; i++) + { + encodedBits[i] %= 2; + } +} + +bool Golay_23_12_decode(unsigned char *rxBits) +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + int i = 0; + + for (is = 0; is < 11; is++) + { + syndromeI += (((rxBits[0] * Golay_23_12_m_H[23*is + 0]) + + (rxBits[1] * Golay_23_12_m_H[23*is + 1]) + + (rxBits[2] * Golay_23_12_m_H[23*is + 2]) + + (rxBits[3] * Golay_23_12_m_H[23*is + 3]) + + (rxBits[4] * Golay_23_12_m_H[23*is + 4]) + + (rxBits[5] * Golay_23_12_m_H[23*is + 5]) + + (rxBits[6] * Golay_23_12_m_H[23*is + 6]) + + (rxBits[7] * Golay_23_12_m_H[23*is + 7]) + + (rxBits[8] * Golay_23_12_m_H[23*is + 8]) + + (rxBits[9] * Golay_23_12_m_H[23*is + 9]) + + (rxBits[10] * Golay_23_12_m_H[23*is + 10]) + + (rxBits[11] * Golay_23_12_m_H[23*is + 11]) + + (rxBits[12] * Golay_23_12_m_H[23*is + 12]) + + (rxBits[13] * Golay_23_12_m_H[23*is + 13]) + + (rxBits[14] * Golay_23_12_m_H[23*is + 14]) + + (rxBits[15] * Golay_23_12_m_H[23*is + 15]) + + (rxBits[16] * Golay_23_12_m_H[23*is + 16]) + + (rxBits[17] * Golay_23_12_m_H[23*is + 17]) + + (rxBits[18] * Golay_23_12_m_H[23*is + 18]) + + (rxBits[19] * Golay_23_12_m_H[23*is + 19]) + + (rxBits[20] * Golay_23_12_m_H[23*is + 20]) + + (rxBits[21] * Golay_23_12_m_H[23*is + 21]) + + (rxBits[22] * Golay_23_12_m_H[23*is + 22])) % 2) << (10-is); + } + + if (syndromeI > 0) + { + i = 0; + + for (; i < 3; i++) + { + if (Golay_23_12_m_corr[syndromeI][i] == 0xFF) + { + break; + } + else + { + rxBits[Golay_23_12_m_corr[syndromeI][i]] ^= 1; // flip bit + } + } + + if (i == 0) + { + return false; + } + } + + return true; +} + + +// ======================================================================================== + + +void Golay_24_12_init() +{ + int i1 = 0, i2 = 0, i3 = 0, ir = 0, ip = 0; + int syndromeI = 0, syndromeIP = 0; + int ip1 = 0, ip2 = 0, ip3 = 0; + int syndromeIP1 = 0, syndromeIP2 = 0, syndromeIP3 = 0; + + memset (Golay_24_12_m_corr, 0xFF, 3*4096); + + for (i1 = 0; i1 < 12; i1++) + { + for (i2 = i1+1; i2 < 12; i2++) + { + for (i3 = i2+1; i3 < 12; i3++) + { + // 3 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += ((Golay_24_12_m_H[24*ir + i1] + Golay_24_12_m_H[24*ir + i2] + Golay_24_12_m_H[24*ir + i3]) % 2) << (11-ir); + } + + Golay_24_12_m_corr[syndromeI][0] = i1; + Golay_24_12_m_corr[syndromeI][1] = i2; + Golay_24_12_m_corr[syndromeI][2] = i3; + } + + // 2 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += ((Golay_24_12_m_H[24*ir + i1] + Golay_24_12_m_H[24*ir + i2]) % 2) << (11-ir); + } + + Golay_24_12_m_corr[syndromeI][0] = i1; + Golay_24_12_m_corr[syndromeI][1] = i2; + + // 1 possible bit flip left in the parity part + for (ip = 0; ip < 12; ip++) + { + syndromeIP = syndromeI ^ (1 << (11-ip)); + Golay_24_12_m_corr[syndromeIP][0] = i1; + Golay_24_12_m_corr[syndromeIP][1] = i2; + Golay_24_12_m_corr[syndromeIP][2] = 12 + ip; + } + } + + // single bit patterns + syndromeI = 0; + + for (ir = 0; ir < 12; ir++) + { + syndromeI += Golay_24_12_m_H[24*ir + i1] << (11-ir); + } + + Golay_24_12_m_corr[syndromeI][0] = i1; + + for (ip1 = 0; ip1 < 12; ip1++) // 1 more bit flip in parity + { + syndromeIP1 = syndromeI ^ (1 << (11-ip1)); + Golay_24_12_m_corr[syndromeIP1][0] = i1; + Golay_24_12_m_corr[syndromeIP1][1] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); + Golay_24_12_m_corr[syndromeIP2][0] = i1; + Golay_24_12_m_corr[syndromeIP2][1] = 12 + ip1; + Golay_24_12_m_corr[syndromeIP2][2] = 12 + ip2; + } + } + } + + // no bit patterns (in message) -> all in parity + for (ip1 = 0; ip1 < 12; ip1++) // 1 bit flip in parity + { + syndromeIP1 = (1 << (11-ip1)); + Golay_24_12_m_corr[syndromeIP1][0] = 12 + ip1; + + for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); + Golay_24_12_m_corr[syndromeIP2][0] = 12 + ip1; + Golay_24_12_m_corr[syndromeIP2][1] = 12 + ip2; + + for (ip3 = ip2+1; ip3 < 12; ip3++) // 1 more bit flip in parity + { + syndromeIP3 = syndromeIP2 ^ (1 << (11-ip3)); + Golay_24_12_m_corr[syndromeIP3][0] = 12 + ip1; + Golay_24_12_m_corr[syndromeIP3][1] = 12 + ip2; + Golay_24_12_m_corr[syndromeIP3][2] = 12 + ip3; + } + } + } +} + +// Not very efficient but encode is used for unit testing only +void Golay_24_12_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 24); + + for (i = 0; i < 12; i++) + { + for (j = 0; j < 24; j++) + { + encodedBits[j] += origBits[i] * Golay_24_12_m_G[24*i + j]; + } + } + + for (i = 0; i < 24; i++) + { + encodedBits[i] %= 2; + } +} + +bool Golay_24_12_decode(unsigned char *rxBits) +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + int i = 0; + + for (is = 0; is < 12; is++) + { + syndromeI += (((rxBits[0] * Golay_24_12_m_H[24*is + 0]) + + (rxBits[1] * Golay_24_12_m_H[24*is + 1]) + + (rxBits[2] * Golay_24_12_m_H[24*is + 2]) + + (rxBits[3] * Golay_24_12_m_H[24*is + 3]) + + (rxBits[4] * Golay_24_12_m_H[24*is + 4]) + + (rxBits[5] * Golay_24_12_m_H[24*is + 5]) + + (rxBits[6] * Golay_24_12_m_H[24*is + 6]) + + (rxBits[7] * Golay_24_12_m_H[24*is + 7]) + + (rxBits[8] * Golay_24_12_m_H[24*is + 8]) + + (rxBits[9] * Golay_24_12_m_H[24*is + 9]) + + (rxBits[10] * Golay_24_12_m_H[24*is + 10]) + + (rxBits[11] * Golay_24_12_m_H[24*is + 11]) + + (rxBits[12] * Golay_24_12_m_H[24*is + 12]) + + (rxBits[13] * Golay_24_12_m_H[24*is + 13]) + + (rxBits[14] * Golay_24_12_m_H[24*is + 14]) + + (rxBits[15] * Golay_24_12_m_H[24*is + 15]) + + (rxBits[16] * Golay_24_12_m_H[24*is + 16]) + + (rxBits[17] * Golay_24_12_m_H[24*is + 17]) + + (rxBits[18] * Golay_24_12_m_H[24*is + 18]) + + (rxBits[19] * Golay_24_12_m_H[24*is + 19]) + + (rxBits[20] * Golay_24_12_m_H[24*is + 20]) + + (rxBits[21] * Golay_24_12_m_H[24*is + 21]) + + (rxBits[22] * Golay_24_12_m_H[24*is + 22]) + + (rxBits[23] * Golay_24_12_m_H[24*is + 23])) % 2) << (11-is); + } + + if (syndromeI > 0) + { + i = 0; + + for (; i < 3; i++) + { + if (Golay_24_12_m_corr[syndromeI][i] == 0xFF) + { + break; + } + else + { + rxBits[Golay_24_12_m_corr[syndromeI][i]] ^= 1; // flip bit + } + } + + if (i == 0) + { + return false; + } + } + + return true; +} + + +// ======================================================================================== + + +void QR_16_7_6_init() +{ + int i1 = 0, i2 = 0, ir = 0, ip = 0; + int syndromeI = 0, syndromeIP = 0; + int ip1 = 0, ip2 = 0; + int syndromeIP1 = 0, syndromeIP2 = 0; + + memset(QR_16_7_6_m_corr, 0xFF, 2*512); + + for (i1 = 0; i1 < 7; i1++) + { + for (i2 = i1+1; i2 < 7; i2++) + { + // 2 bit patterns + syndromeI = 0; + + for (ir = 0; ir < 9; ir++) + { + syndromeI += ((QR_16_7_6_m_H[16*ir + i1] + QR_16_7_6_m_H[16*ir + i2]) % 2) << (8-ir); + } + + QR_16_7_6_m_corr[syndromeI][0] = i1; + QR_16_7_6_m_corr[syndromeI][1] = i2; + } + + // single bit patterns + syndromeI = 0; + + for (ir = 0; ir < 9; ir++) + { + syndromeI += QR_16_7_6_m_H[16*ir + i1] << (8-ir); + } + + QR_16_7_6_m_corr[syndromeI][0] = i1; + + // 1 possible bit flip left in the parity part + for (ip = 0; ip < 9; ip++) + { + syndromeIP = syndromeI ^ (1 << (8-ip)); + QR_16_7_6_m_corr[syndromeIP][0] = i1; + QR_16_7_6_m_corr[syndromeIP][1] = 7 + ip; + } + } + + // no bit patterns (in message) -> all in parity + for (ip1 = 0; ip1 < 9; ip1++) // 1 bit flip in parity + { + syndromeIP1 = (1 << (8-ip1)); + QR_16_7_6_m_corr[syndromeIP1][0] = 7 + ip1; + + for (ip2 = ip1+1; ip2 < 9; ip2++) // 1 more bit flip in parity + { + syndromeIP2 = syndromeIP1 ^ (1 << (8-ip2)); + QR_16_7_6_m_corr[syndromeIP2][0] = 7 + ip1; + QR_16_7_6_m_corr[syndromeIP2][1] = 7 + ip2; + } + } +} + +// Not very efficient but encode is used for unit testing only +void QR_16_7_6_encode(unsigned char *origBits, unsigned char *encodedBits) +{ + int i = 0, j = 0; + + memset(encodedBits, 0, 16); + + for (i = 0; i < 7; i++) + { + for (j = 0; j < 16; j++) + { + encodedBits[j] += origBits[i] * QR_16_7_6_m_G[16*i + j]; + } + } + + for (i = 0; i < 16; i++) + { + encodedBits[i] %= 2; + } +} + +bool QR_16_7_6_decode(unsigned char *rxBits) +{ + unsigned int syndromeI = 0; // syndrome index + int is = 0; + int i = 0; + + for (is = 0; is < 9; is++) + { + syndromeI += (((rxBits[0] * QR_16_7_6_m_H[16*is + 0]) + + (rxBits[1] * QR_16_7_6_m_H[16*is + 1]) + + (rxBits[2] * QR_16_7_6_m_H[16*is + 2]) + + (rxBits[3] * QR_16_7_6_m_H[16*is + 3]) + + (rxBits[4] * QR_16_7_6_m_H[16*is + 4]) + + (rxBits[5] * QR_16_7_6_m_H[16*is + 5]) + + (rxBits[6] * QR_16_7_6_m_H[16*is + 6]) + + (rxBits[7] * QR_16_7_6_m_H[16*is + 7]) + + (rxBits[8] * QR_16_7_6_m_H[16*is + 8]) + + (rxBits[9] * QR_16_7_6_m_H[16*is + 9]) + + (rxBits[10] * QR_16_7_6_m_H[16*is + 10]) + + (rxBits[11] * QR_16_7_6_m_H[16*is + 11]) + + (rxBits[12] * QR_16_7_6_m_H[16*is + 12]) + + (rxBits[13] * QR_16_7_6_m_H[16*is + 13]) + + (rxBits[14] * QR_16_7_6_m_H[16*is + 14]) + + (rxBits[15] * QR_16_7_6_m_H[16*is + 15])) % 2) << (8-is); + } + + if (syndromeI > 0) + { + i = 0; + + for (; i < 2; i++) + { + if (QR_16_7_6_m_corr[syndromeI][i] == 0xFF) + { + break; + } + else + { + rxBits[QR_16_7_6_m_corr[syndromeI][i]] ^= 1; // flip bit + } + } + + if (i == 0) + { + return false; + } + } + + return true; +} + + +// ======================================================================================== + + +/* This function init all FEC functions + * it must be called once at startup */ +void InitAllFecFunction(void) +{ + Hamming_7_4_init(); + Hamming_12_8_init(); + Hamming_13_9_init(); + Hamming_15_11_init(); + Hamming_16_11_4_init(); + Golay_20_8_init(); + Golay_23_12_init(); + Golay_24_12_init(); + QR_16_7_6_init(); +} /* End InitAllFEC() */ + + +/* End of file */ diff --git a/src/rs-12-9.c b/src/rs-12-9.c new file mode 100644 index 0000000..f64ac2d --- /dev/null +++ b/src/rs-12-9.c @@ -0,0 +1,316 @@ +/* + * This file is part of dmrshark. + * + * dmrshark 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 of the License, or + * (at your option) any later version. + * + * dmrshark 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 dmrshark. If not, see . +**/ + +// Reed-Solomon (12,9) error-correction coding. +// +// As there are 3 checksum bytes, we can only correct 1 erroneous byte. +// +// Based on the code by Henry Minsky (hqm@alum.mit.edu), http://rscode.sourceforge.net +// Berlekamp-Peterson and Berlekamp-Massey Algorithms for error-location are +// from Cain, Clark, "Error-Correction Coding For Digital Communications", pp. 205. + +//#include DEFAULTCONFIG + +#include "dsd.h" + +//#include + +#include +#include + +typedef struct { + uint8_t error_locations[256]; + uint8_t errors_num; +} rs_12_9_roots_t; + +// See DMR AI. spec. page 138. +static uint8_t rs_12_9_galois_exp_table[256] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1D, 0x3A, 0x74, 0xE8, 0xCD, 0x87, 0x13, 0x26, + 0x4C, 0x98, 0x2D, 0x5A, 0xB4, 0x75, 0xEA, 0xC9, 0x8F, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, + 0x9D, 0x27, 0x4E, 0x9C, 0x25, 0x4A, 0x94, 0x35, 0x6A, 0xD4, 0xB5, 0x77, 0xEE, 0xC1, 0x9F, 0x23, + 0x46, 0x8C, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x5D, 0xBA, 0x69, 0xD2, 0xB9, 0x6F, 0xDE, 0xA1, + 0x5F, 0xBE, 0x61, 0xC2, 0x99, 0x2F, 0x5E, 0xBC, 0x65, 0xCA, 0x89, 0x0F, 0x1E, 0x3C, 0x78, 0xF0, + 0xFD, 0xE7, 0xD3, 0xBB, 0x6B, 0xD6, 0xB1, 0x7F, 0xFE, 0xE1, 0xDF, 0xA3, 0x5B, 0xB6, 0x71, 0xE2, + 0xD9, 0xAF, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0D, 0x1A, 0x34, 0x68, 0xD0, 0xBD, 0x67, 0xCE, + 0x81, 0x1F, 0x3E, 0x7C, 0xF8, 0xED, 0xC7, 0x93, 0x3B, 0x76, 0xEC, 0xC5, 0x97, 0x33, 0x66, 0xCC, + 0x85, 0x17, 0x2E, 0x5C, 0xB8, 0x6D, 0xDA, 0xA9, 0x4F, 0x9E, 0x21, 0x42, 0x84, 0x15, 0x2A, 0x54, + 0xA8, 0x4D, 0x9A, 0x29, 0x52, 0xA4, 0x55, 0xAA, 0x49, 0x92, 0x39, 0x72, 0xE4, 0xD5, 0xB7, 0x73, + 0xE6, 0xD1, 0xBF, 0x63, 0xC6, 0x91, 0x3F, 0x7E, 0xFC, 0xE5, 0xD7, 0xB3, 0x7B, 0xF6, 0xF1, 0xFF, + 0xE3, 0xDB, 0xAB, 0x4B, 0x96, 0x31, 0x62, 0xC4, 0x95, 0x37, 0x6E, 0xDC, 0xA5, 0x57, 0xAE, 0x41, + 0x82, 0x19, 0x32, 0x64, 0xC8, 0x8D, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xDD, 0xA7, 0x53, 0xA6, + 0x51, 0xA2, 0x59, 0xB2, 0x79, 0xF2, 0xF9, 0xEF, 0xC3, 0x9B, 0x2B, 0x56, 0xAC, 0x45, 0x8A, 0x09, + 0x12, 0x24, 0x48, 0x90, 0x3D, 0x7A, 0xF4, 0xF5, 0xF7, 0xF3, 0xFB, 0xEB, 0xCB, 0x8B, 0x0B, 0x16, + 0x2C, 0x58, 0xB0, 0x7D, 0xFA, 0xE9, 0xCF, 0x83, 0x1B, 0x36, 0x6C, 0xD8, 0xAD, 0x47, 0x8E, 0x01, +}; + +// See DMR AI. spec. page 138. +static uint8_t rs_12_9_galois_log_table[256] = { + 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, + 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, + 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, + 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, + 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, + 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, + 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, + 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, + 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, + 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, + 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, + 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, + 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, + 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, + 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, + 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 +}; + +static uint8_t rs_12_9_galois_exp_table_get(uint8_t pos) { + return rs_12_9_galois_exp_table[pos]; +} + +static uint8_t rs_12_9_galois_multiplication(uint8_t a, uint8_t b) { + if (a == 0 || b == 0) + return 0; + + return rs_12_9_galois_exp_table[(rs_12_9_galois_log_table[a] + rs_12_9_galois_log_table[b]) % 255]; +} + +static uint8_t rs_12_9_galois_inv(uint8_t elt) { + return rs_12_9_galois_exp_table[255-rs_12_9_galois_log_table[elt]]; +} + +// Multiply by z (shift right by 1). +static void rs_12_9_multiply_poly_z(rs_12_9_poly_t *poly) { + uint8_t i; + + for (i = RS_12_9_POLY_MAXDEG-1; i > 0; i--) + poly->data[i] = poly->data[i-1]; + poly->data[0] = 0; +} + +static void rs_12_9_multiplicate_polys(uint8_t dst[], rs_12_9_poly_t *p1, rs_12_9_poly_t *p2) { + uint8_t i; + int8_t j; + int8_t tmp1[RS_12_9_POLY_MAXDEG*2]; + + for (i = 0; i < RS_12_9_POLY_MAXDEG*2; i++) + dst[i] = 0; + + for (i = 0; i < RS_12_9_POLY_MAXDEG; i++) { + for(j = RS_12_9_POLY_MAXDEG; j < (RS_12_9_POLY_MAXDEG*2); j++) + tmp1[j] = 0; + + // Scale tmp1 by p1[i] + for(j = 0; j < RS_12_9_POLY_MAXDEG; j++) + tmp1[j] = rs_12_9_galois_multiplication(p2->data[j], p1->data[i]); + + // Shift (multiply) tmp1 right by i + for (j = (RS_12_9_POLY_MAXDEG*2)-1; j >= i; j--) + tmp1[j] = tmp1[j-i]; + for (j = 0; j < i; j++) + tmp1[j] = 0; + + // Add into partial product. + for (j = 0; j < (RS_12_9_POLY_MAXDEG*2); j++) + dst[j] ^= tmp1[j]; + } +} + +// Computes the combined erasure/error evaluator polynomial (error_locator_poly*syndrome mod z^4) +static void rs_12_9_calc_error_evaluator_poly(rs_12_9_poly_t *error_locator_poly, rs_12_9_poly_t *syndrome, rs_12_9_poly_t *error_evaluator_poly) { + uint8_t i; + uint8_t product[RS_12_9_POLY_MAXDEG*2]; + + rs_12_9_multiplicate_polys(product, error_locator_poly, syndrome); + for (i = 0; i < RS_12_9_CHECKSUMSIZE; i++) + error_evaluator_poly->data[i] = product[i]; + for (; i < RS_12_9_POLY_MAXDEG; i++) + error_evaluator_poly->data[i] = 0; +} + +static uint8_t rs_12_9_compute_discrepancy(rs_12_9_poly_t *error_locator_poly, rs_12_9_poly_t *syndrome, uint8_t L, uint8_t n) { + uint8_t i; + uint8_t sum = 0; + + for (i = 0; i <= L; i++) + sum ^= rs_12_9_galois_multiplication(error_locator_poly->data[i], syndrome->data[n-i]); + + return sum; +} + +// This finds the coefficients of the error locator polynomial, and then calculates +// the error evaluator polynomial using the Berlekamp-Massey algorithm. +// From Cain, Clark, "Error-Correction Coding For Digital Communications", pp. 216. +static void rs_12_9_calculate(rs_12_9_poly_t *syndrome, rs_12_9_poly_t *error_locator_poly, rs_12_9_poly_t *error_evaluator_poly) { + uint8_t n; + uint8_t L = 0; + uint8_t L2; + int8_t k; + uint8_t d; + uint8_t i; + uint8_t psi2[RS_12_9_POLY_MAXDEG]; + rs_12_9_poly_t D = { .data = { 0, 1, 0, } }; + + k = -1; + memset(error_locator_poly, 0, sizeof(rs_12_9_poly_t)); + error_locator_poly->data[0] = 1; + + for (n = 0; n < RS_12_9_CHECKSUMSIZE; n++) { + d = rs_12_9_compute_discrepancy(error_locator_poly, syndrome, L, n); + + if (d != 0) { + // psi2 = error_locator_poly - d*D + for (i = 0; i < RS_12_9_POLY_MAXDEG; i++) + psi2[i] = error_locator_poly->data[i] ^ rs_12_9_galois_multiplication(d, D.data[i]); + + if (L < (n-k)) { + L2 = n-k; + k = n-L; + + for (i = 0; i < RS_12_9_POLY_MAXDEG; i++) + D.data[i] = rs_12_9_galois_multiplication(error_locator_poly->data[i], rs_12_9_galois_inv(d)); + + L = L2; + } + + // error_locator_poly = psi2 + for (i = 0; i < RS_12_9_POLY_MAXDEG; i++) + error_locator_poly->data[i] = psi2[i]; + } + + rs_12_9_multiply_poly_z(&D); + } + + rs_12_9_calc_error_evaluator_poly(error_locator_poly, syndrome, error_evaluator_poly); +} + +// The error-locator polynomial's roots are found by looking for the values of a^n where +// evaluating the polynomial yields zero (evaluating rs_12_9_error_locator_poly at +// successive values of alpha (Chien's search)). +static rs_12_9_roots_t *rs_12_9_find_roots(rs_12_9_poly_t *error_locator_poly) { + static rs_12_9_roots_t roots; + uint8_t sum; + uint16_t r; + uint8_t k; + + memset(&roots, 0, sizeof(rs_12_9_roots_t)); + + for (r = 1; r < 256; r++) { + sum = 0; + // Evaluate rs_12_9_error_locator_poly at r + for (k = 0; k < RS_12_9_CHECKSUMSIZE+1; k++) + sum ^= rs_12_9_galois_multiplication(rs_12_9_galois_exp_table_get((k*r) % 255), error_locator_poly->data[k]); + + if (sum == 0) + roots.error_locations[roots.errors_num++] = (255-r); + } + return &roots; +} + +void rs_12_9_calc_syndrome(rs_12_9_codeword_t *codeword, rs_12_9_poly_t *syndrome) { + uint8_t i, j; + + syndrome->data[0] = syndrome->data[1] = syndrome->data[2] = 0; + + for (j = 0; j < 3; j++) { + for (i = 0; i < sizeof(rs_12_9_codeword_t); i++) + syndrome->data[j] = codeword->data[i] ^ rs_12_9_galois_multiplication(rs_12_9_galois_exp_table_get(j+1), syndrome->data[j]); + } +} + +// Returns 1 if syndrome differs from all zeroes. +uint8_t rs_12_9_check_syndrome(rs_12_9_poly_t *syndrome) { + uint8_t i; + + for (i = 0; i < 3; i++) { + if (syndrome->data[i] != 0) + return 1; + } + + return 0; +} +// Returns 1 if errors have been found and corrected, returns 0 if +// no errors found or errors can't be corrected. +rs_12_9_correct_errors_result_t rs_12_9_correct_errors(rs_12_9_codeword_t *codeword, rs_12_9_poly_t *syndrome, uint8_t *errors_found) { + uint8_t r; + uint8_t i; + uint8_t j; + uint8_t err; + rs_12_9_poly_t error_locator_poly; + rs_12_9_poly_t error_evaluator_poly; + rs_12_9_roots_t *roots; + uint8_t num, denom; + + rs_12_9_calculate(syndrome, &error_locator_poly, &error_evaluator_poly); + roots = rs_12_9_find_roots(&error_locator_poly); + *errors_found = roots->errors_num; + + if (roots->errors_num == 0) + return RS_12_9_CORRECT_ERRORS_RESULT_NO_ERRORS_FOUND; + + // Error correction is done using the error-evaluator equation on pp 207. + if (roots->errors_num > 0 && roots->errors_num <= RS_12_9_CHECKSUMSIZE) { + // First check for illegal error locations. + for (r = 0; r < roots->errors_num; r++) { + if (roots->error_locations[r] >= RS_12_9_DATASIZE+RS_12_9_CHECKSUMSIZE) + return RS_12_9_CORRECT_ERRORS_RESULT_ERRORS_CANT_BE_CORRECTED; + } + + // Evaluates rs_12_9_error_evaluator_poly/rs_12_9_error_locator_poly' at the roots + // alpha^(-i) for error locs i. + for (r = 0; r < roots->errors_num; r++) { + i = roots->error_locations[r]; + + // Evaluate rs_12_9_error_evaluator_poly at alpha^(-i) + num = 0; + for (j = 0; j < RS_12_9_POLY_MAXDEG; j++) + num ^= rs_12_9_galois_multiplication(error_evaluator_poly.data[j], rs_12_9_galois_exp_table_get(((255-i)*j) % 255)); + + // Evaluate rs_12_9_error_evaluator_poly' (derivative) at alpha^(-i). All odd powers disappear. + denom = 0; + for (j = 1; j < RS_12_9_POLY_MAXDEG; j += 2) + denom ^= rs_12_9_galois_multiplication(error_locator_poly.data[j], rs_12_9_galois_exp_table_get(((255-i)*(j-1)) % 255)); + + err = rs_12_9_galois_multiplication(num, rs_12_9_galois_inv(denom)); + //console_log(LOGLEVEL_CODING LOGLEVEL_DEBUG " rs (12,9): error magnitude %#x at byte loc. %u\n", err, sizeof(rs_12_9_codeword_t)-i); + + codeword->data[sizeof(rs_12_9_codeword_t)-i-1] ^= err; + } + return RS_12_9_CORRECT_ERRORS_RESULT_ERRORS_CORRECTED; + } + + return RS_12_9_CORRECT_ERRORS_RESULT_ERRORS_CANT_BE_CORRECTED; +} + +// Simulates an LFSR with the generator polynomial and calculates checksum bytes for the given data. +rs_12_9_checksum_t *rs_12_9_calc_checksum(rs_12_9_codeword_t *codeword) { + // See DMR AI. spec. page 136 for these coefficients. + static uint8_t genpoly[] = { 0x40, 0x38, 0x0e, 0x01 }; + static rs_12_9_checksum_t rs_12_9_checksum; + uint8_t i; + uint8_t feedback; + + rs_12_9_checksum.bytes[0] = rs_12_9_checksum.bytes[1] = rs_12_9_checksum.bytes[2] = 0; + + for (i = 0; i < 9; i++) { + feedback = codeword->data[i] ^ rs_12_9_checksum.bytes[0]; + + rs_12_9_checksum.bytes[0] = rs_12_9_checksum.bytes[1] ^ rs_12_9_galois_multiplication(genpoly[2], feedback); + rs_12_9_checksum.bytes[1] = rs_12_9_checksum.bytes[2] ^ rs_12_9_galois_multiplication(genpoly[1], feedback); + rs_12_9_checksum.bytes[2] = rs_12_9_galois_multiplication(genpoly[0], feedback); + } + return &rs_12_9_checksum; +} From c994f00e5df41f981b7cd1139b7e0d949bb37959 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sun, 6 Mar 2022 18:42:50 -0500 Subject: [PATCH 02/12] Fix issue on amb file playback Fix issue on amb file playback -switching from 7 back to 6, otherwise older files may break when playing back -still needs testing on 6 to 6, and 7 to 6 reads --- src/dsd_file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dsd_file.c b/src/dsd_file.c index 6eb4dfc..9acc3e5 100644 --- a/src/dsd_file.c +++ b/src/dsd_file.c @@ -162,8 +162,8 @@ readAmbe2450Data (dsd_opts * opts, dsd_state * state, char *ambe_d) { fprintf(stderr, "\n"); } - //for (i = 0; i < 6; i++) - for (i = 0; i < 7; i++) + for (i = 0; i < 6; i++) + //for (i = 0; i < 7; i++) //this will really break backwards compatability { b = fgetc (opts->mbe_in_f); if (feof (opts->mbe_in_f)) From f053e53e7657fc214170cfe37e6793f2bce1fd25 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sun, 6 Mar 2022 22:38:29 -0500 Subject: [PATCH 03/12] More DMR Staging, Fix MI to Hex in P25 More DMR Staging, Fix MI to Hex in P25 -switch back to 7 octets on AMB payloads --will break compatibility with 6 octets -Print MI values as Hex now, not as binary strings --- include/dsd.h | 1 + src/dmr_sync.c | 8 +- src/dsd_file.c | 6 +- src/dsd_ncurses.c | 10 +-- src/p25p1_hdu.c | 13 ++-- src/p25p1_ldu2.c | 192 +++------------------------------------------- 6 files changed, 31 insertions(+), 199 deletions(-) diff --git a/include/dsd.h b/include/dsd.h index e703fb3..8f9a744 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -218,6 +218,7 @@ typedef struct unsigned int GroupAddress; /* Destination ID or TG (in Air Interface format) */ unsigned int SourceAddress; /* Source ID (in Air Interface format) */ unsigned int DataValidity; /* 0 = All Full LC data are incorrect ; 1 = Full LC data are correct (CRC checked OK) */ + unsigned int LeftOvers; //what is the 8 bits for that isn't used }FullLinkControlPDU_t; typedef struct diff --git a/src/dmr_sync.c b/src/dmr_sync.c index 857276e..007b5ef 100644 --- a/src/dmr_sync.c +++ b/src/dmr_sync.c @@ -104,7 +104,7 @@ void ProcessDmrVoiceLcHeader(dsd_opts * opts, dsd_state * state, uint8_t info[19 DmrDataBit[j + 6] = (DmrDataByte[i] >> 1) & 0x01; DmrDataBit[j + 7] = (DmrDataByte[i] >> 0) & 0x01; } - + //fprintf (stderr, "\nDDB = 0x%X \n", DmrDataBit); /* Store the Protect Flag (PF) bit */ TSVoiceSupFrame->FullLC.ProtectFlag = (unsigned int)(DmrDataBit[0]); @@ -303,7 +303,7 @@ void ProcessDmrTerminaisonLC(dsd_opts * opts, dsd_state * state, uint8_t info[19 DmrDataBit[j + 6] = (DmrDataByte[i] >> 1) & 0x01; DmrDataBit[j + 7] = (DmrDataByte[i] >> 0) & 0x01; } - + //fprintf (stderr, "\nDDB = 0x%X \n", DmrDataBit); /* Store the Protect Flag (PF) bit */ TSVoiceSupFrame->FullLC.ProtectFlag = (unsigned int)(DmrDataBit[0]); @@ -326,6 +326,8 @@ void ProcessDmrTerminaisonLC(dsd_opts * opts, dsd_state * state, uint8_t info[19 /* Store the Source address */ TSVoiceSupFrame->FullLC.SourceAddress = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[48], 24); //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; + //TSVoiceSupFrame->FullLC.LeftOvers = (unsigned int)ConvertBitIntoBytes(&DmrDataBit[64], 8); + //fprintf (stderr, "\nBPTC Left Overs = %02X \n", TSVoiceSupFrame->FullLC.LeftOvers); if((IrrecoverableErrors == 0))// && CRCCorrect) { /* CRC is correct so consider the Full LC data as correct/valid */ @@ -472,7 +474,7 @@ void ProcessVoiceBurstSync(dsd_opts * opts, dsd_state * state) CRCExtracted |= (LC_DataBit[74] & 1) << 2; CRCExtracted |= (LC_DataBit[75] & 1) << 1; CRCExtracted |= (LC_DataBit[76] & 1) << 0; - + //fprintf (stderr, "\nLCDB = 0x%X \n", LC_DataBit); /* Compute the 5 bit CRC */ CRCComputed = ComputeCrc5Bit(LC_DataBit); diff --git a/src/dsd_file.c b/src/dsd_file.c index 9acc3e5..a05e960 100644 --- a/src/dsd_file.c +++ b/src/dsd_file.c @@ -73,7 +73,7 @@ saveAmbe2450Data (dsd_opts * opts, dsd_state * state, char *ambe_d) fprintf(stderr, "\n"); } //for (i = 0; i < 6; i++) - for (i = 0; i < 7; i++) + for (i = 0; i < 7; i++) //using 7 seems to break older amb files where it was 6, need to test 7 on 7 some more { b = 0; @@ -162,8 +162,8 @@ readAmbe2450Data (dsd_opts * opts, dsd_state * state, char *ambe_d) { fprintf(stderr, "\n"); } - for (i = 0; i < 6; i++) - //for (i = 0; i < 7; i++) //this will really break backwards compatability + //for (i = 0; i < 6; i++) + for (i = 0; i < 7; i++) //breaks backwards compatablilty with 6 files { b = fgetc (opts->mbe_in_f); if (feof (opts->mbe_in_f)) diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index 173e5a2..052a7e0 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -374,9 +374,9 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (lls == 12 || lls == 13) //DMR Voice Types { //printw ("| DCC: [%i] FID: [%02X]\n", dcc, state->dmr_fid); - printw ("| DCC: [%i] FID: [%02X] SOP: [%X] \n", dcc, state->dmr_fid, state->dmr_so); + printw ("| DCC: [%i] MFID: [%02X] SOP: [%X] \n", dcc, state->dmr_fid, state->dmr_so); printw ("| TID: [%i] RID: [%i]", tg, rd); - if(state->dmr_so & 0x80) //& apparently is same as == + if(state->dmr_so & 0x80) //1000 0000 { attron(COLOR_PAIR(2)); printw (" **Emergency** "); @@ -384,17 +384,17 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) attron(COLOR_PAIR(3)); } - if(state->dmr_so & 0x40) + if(state->dmr_so & 0x40) //0100 0000 { attron(COLOR_PAIR(2)); printw (" **ENC** "); //printw ("0x%X", state->payload_algid); attroff(COLOR_PAIR(2)); attron(COLOR_PAIR(3)); - //opts->p25enc = 1; //just testing for now + opts->p25enc = 1; //just testing for now } - if(state->dmr_so & 0x30) + if(state->dmr_so & 0x30) //0010 0000 { attron(COLOR_PAIR(2)); printw (" **Private Call** "); diff --git a/src/p25p1_hdu.c b/src/p25p1_hdu.c index 8d7e9f7..eaf49a8 100644 --- a/src/p25p1_hdu.c +++ b/src/p25p1_hdu.c @@ -222,7 +222,7 @@ processHDU(dsd_opts* opts, dsd_state* state) char hex[6]; int status_count; int status; - + unsigned long long int mihex1, mihex2, mihex3; char hex_data[20][6]; // Data in hex-words (6 bit words). A total of 20 hex words. char hex_parity[16][6]; // Parity of the data, again in hex-word format. A total of 16 parity hex words. @@ -461,14 +461,17 @@ processHDU(dsd_opts* opts, dsd_state* state) state->payload_algid = algidhex; state->payload_keyid = kidhex; + + mihex1 = (unsigned long long int)ConvertBitIntoBytes(&mi[0], 32); + mihex2 = (unsigned long long int)ConvertBitIntoBytes(&mi[32], 32); + mihex3 = (unsigned long long int)ConvertBitIntoBytes(&mi[64], 8); } - //if (opts->payload == 1) + //if (opts->payload == 0) if (opts->payload == 1 && state->errs == 0) { - //fprintf (stderr, "ALG ID: 0x%X KEY ID: 0x%X\n", algidhex, kidhex); - fprintf (stderr, "HDU ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); - //fprintf (stderr, "ALG ID: 0x%X KEY ID: 0x%X MI: 0x%016X%02X\n", algidhex, kidhex, mihex1, mihex2); + //fprintf (stderr, "HDU ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); + fprintf (stderr, "HDU ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08X%08X%02X\n", algidhex, kidhex, mihex1, mihex2, mihex3); } if (opts->p25enc == 1 && opts->payload == 0) diff --git a/src/p25p1_ldu2.c b/src/p25p1_ldu2.c index b431385..097baca 100644 --- a/src/p25p1_ldu2.c +++ b/src/p25p1_ldu2.c @@ -22,8 +22,6 @@ #include "p25p1_check_ldu.h" #include "p25p1_hdu.h" -long long int mihex1 = 0; -long long int mihex2 = 0; void processLDU2 (dsd_opts * opts, dsd_state * state) @@ -33,7 +31,7 @@ processLDU2 (dsd_opts * opts, dsd_state * state) char mi[73], algid[9], kid[17]; char lsd1[9], lsd2[9]; int algidhex, kidhex; - + unsigned long long int mihex1, mihex2, mihex3; int status_count; char hex_data[16][6]; // Data in hex-words (6 bit words). A total of 16 hex words. @@ -249,165 +247,6 @@ processLDU2 (dsd_opts * opts, dsd_state * state) lsd1[8] = 0; lsd2[8] = 0; - mihex1 = 0; - mihex2 = 0; - /* - mihex1 |= atoi(&hex_data[15][0]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[15][1]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[15][2]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[15][3]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[15][4]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[15][5]); - mihex1 << 1; - //fprintf(stderr, "\n%X\n", mihex1); - mihex1 |= atoi(&hex_data[14][0]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[14][1]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[14][2]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[14][3]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[14][4]); - mihex1 << 1; - mihex1 |= atoi(&hex_data[14][5]); - mihex1 << 1; - - mihex1 |= hex_data[13][0]; - mihex1 << 1; - mihex1 |= hex_data[13][1]; - mihex1 << 1; - mihex1 |= hex_data[13][2]; - mihex1 << 1; - mihex1 |= hex_data[13][3]; - mihex1 << 1; - mihex1 |= hex_data[13][4]; - mihex1 << 1; - mihex1 |= hex_data[13][5]; - mihex1 << 1; - - mihex1 |= hex_data[12][0]; - mihex1 << 1; - mihex1 |= hex_data[12][1]; - mihex1 << 1; - mihex1 |= hex_data[12][2]; - mihex1 << 1; - mihex1 |= hex_data[12][3]; - mihex1 << 1; - mihex1 |= hex_data[12][4]; - mihex1 << 1; - mihex1 |= hex_data[12][5]; - mihex1 << 1; - - mihex1 |= hex_data[113][0]; - mihex1 << 1; - mihex1 |= hex_data[11][1]; - mihex1 << 1; - mihex1 |= hex_data[11][2]; - mihex1 << 1; - mihex1 |= hex_data[11][3]; - mihex1 << 1; - mihex1 |= hex_data[11][4]; - mihex1 << 1; - mihex1 |= hex_data[11][5]; - mihex1 << 1; - - mihex1 |= hex_data[10][0]; - mihex1 << 1; - mihex1 |= hex_data[10][1]; - mihex1 << 1; - mihex1 |= hex_data[10][2]; - mihex1 << 1; - mihex1 |= hex_data[10][3]; - mihex1 << 1; - mihex1 |= hex_data[10][4]; - mihex1 << 1; - mihex1 |= hex_data[10][5]; - mihex1 << 1; - - mihex1 |= hex_data[9][0]; - mihex1 << 1; - mihex1 |= hex_data[9][1]; - mihex1 << 1; - mihex1 |= hex_data[9][2]; - mihex1 << 1; - mihex1 |= hex_data[9][3]; - mihex1 << 1; - mihex1 |= hex_data[9][4]; - mihex1 << 1; - mihex1 |= hex_data[9][5]; - mihex1 << 1; - - mihex1 |= hex_data[8][0]; - mihex1 << 1; - mihex1 |= hex_data[8][1]; - mihex1 << 1; - mihex1 |= hex_data[8][2]; - mihex1 << 1; - mihex1 |= hex_data[8][3]; - mihex1 << 1; - mihex1 |= hex_data[8][4]; - mihex1 << 1; - mihex1 |= hex_data[8][5]; - mihex1 << 1; - - mihex1 |= hex_data[7][0]; - mihex1 << 1; - mihex1 |= hex_data[7][1]; - mihex1 << 1; - mihex1 |= hex_data[7][2]; - mihex1 << 1; - mihex1 = hex_data[7][3]; - mihex1 << 1; - mihex1 |= hex_data[7][4]; - mihex1 << 1; - mihex1 |= hex_data[7][5]; - mihex1 << 1; - - mihex1 |= hex_data[6][0]; - mihex1 << 1; - mihex1 |= hex_data[6][1]; - mihex1 << 1; - mihex1 |= hex_data[6][2]; - mihex1 << 1; - mihex1 |= hex_data[6][3]; - mihex1 << 1; - mihex1 |= hex_data[6][4]; - mihex1 << 1; - mihex1 |= hex_data[6][5]; - mihex1 << 1; - - mihex1 |= hex_data[5][0]; - mihex1 << 1; - mihex1 |= hex_data[5][1]; - mihex1 << 1; - mihex1 |= hex_data[5][2]; - mihex1 << 1; - mihex1 |= hex_data[5][3]; - //mihex1 << 1; - - mihex2 |= hex_data[5][4]; - mihex2 << 1; - mihex2 |= hex_data[5][5]; - mihex2 << 1; - mihex2 |= hex_data[4][0]; - mihex2 << 1; - mihex2 |= hex_data[4][1]; - mihex2 << 1; - mihex2 |= hex_data[4][2]; - mihex2 << 1; - mihex2 |= hex_data[4][3]; - mihex2 << 1; - mihex2 |= hex_data[4][4]; - mihex2 << 1; - mihex2 |= hex_data[4][5]; - //mihex2 << 1; - */ mi[ 0] = hex_data[15][0] + '0'; mi[ 1] = hex_data[15][1] + '0'; mi[ 2] = hex_data[15][2] + '0'; @@ -520,40 +359,27 @@ processLDU2 (dsd_opts * opts, dsd_state * state) kid[14] = hex_data[ 0][4] + '0'; kid[15] = hex_data[ 0][5] + '0'; - //if (state->carrier == 1 && state->errs2 == 0) //only update when carrier is present, and no errors?? + if (state->carrier == 1 && state->errs == 0) //only update when carrier is present, and no errors?? //if (state->carrier == 1) { - /* - for (short i = 0; i < 64; i++) - { - mihex1 << 1; - //mihex1 |= strtoll(&mi[i], NULL, 2); - mihex1 |= atoi(&mi[i]); - //fprintf (stderr, "%c", mi[i]); - } - for (short i = 0; i < 8; i++) - { - mihex2 << 1; - //mihex2 |= strtoll(&mi[64+i], NULL, 2); - mihex2 |= atoi(&mi[i+64]); - //fprintf (stderr, "%c", mi[i+64]); - } - */ + algidhex = strtol (algid, NULL, 2); kidhex = strtol (kid, NULL, 2); state->payload_algid = algidhex; state->payload_keyid = kidhex; + mihex1 = (unsigned long long int)ConvertBitIntoBytes(&mi[0], 32); + mihex2 = (unsigned long long int)ConvertBitIntoBytes(&mi[32], 32); + mihex3 = (unsigned long long int)ConvertBitIntoBytes(&mi[64], 8); } - //if (opts->payload == 1) + //if (opts->payload == 0) if (opts->payload == 1 && state->errs == 0) { - //fprintf (stderr, "ALG ID: 0x%X KEY ID: 0x%X\n", algidhex, kidhex); - fprintf (stderr, "LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); - //fprintf (stderr, "ALG ID: 0x%X KEY ID: 0x%X MI: 0x%016X%02X\n", algidhex, kidhex, mihex1, mihex2); + //fprintf (stderr, "LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); + fprintf (stderr, "LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08X%08X%02X\n", algidhex, kidhex, mihex1, mihex2, mihex3); } //if (opts->p25enc == 1 && opts->payload == 0) if (opts->payload == 0) From beb96f73db11fb19cd3fbbfe8dc9b04a2ee96182 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sun, 6 Mar 2022 22:48:44 -0500 Subject: [PATCH 04/12] Fix MI to llX Fix MI to llX --- src/p25p1_hdu.c | 2 +- src/p25p1_ldu2.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p25p1_hdu.c b/src/p25p1_hdu.c index eaf49a8..a535456 100644 --- a/src/p25p1_hdu.c +++ b/src/p25p1_hdu.c @@ -471,7 +471,7 @@ processHDU(dsd_opts* opts, dsd_state* state) if (opts->payload == 1 && state->errs == 0) { //fprintf (stderr, "HDU ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); - fprintf (stderr, "HDU ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08X%08X%02X\n", algidhex, kidhex, mihex1, mihex2, mihex3); + fprintf (stderr, "HDU ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08llX%08llX%02llX\n", algidhex, kidhex, mihex1, mihex2, mihex3); } if (opts->p25enc == 1 && opts->payload == 0) diff --git a/src/p25p1_ldu2.c b/src/p25p1_ldu2.c index 097baca..55bdb53 100644 --- a/src/p25p1_ldu2.c +++ b/src/p25p1_ldu2.c @@ -379,7 +379,7 @@ processLDU2 (dsd_opts * opts, dsd_state * state) if (opts->payload == 1 && state->errs == 0) { //fprintf (stderr, "LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); - fprintf (stderr, "LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08X%08X%02X\n", algidhex, kidhex, mihex1, mihex2, mihex3); + fprintf (stderr, "LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08llX%08llX%02llX\n", algidhex, kidhex, mihex1, mihex2, mihex3); } //if (opts->p25enc == 1 && opts->payload == 0) if (opts->payload == 0) From d756ab6574178a7453ca05d658cadf13e9e3689d Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Tue, 8 Mar 2022 16:57:26 -0500 Subject: [PATCH 05/12] DMR, dPMR, Pulse Audio Tinkering DMR, dPMR, Pulse Audio Tinkering --- include/dsd.h | 17 ++++- src/dmr_encryption.c | 28 +++++++++ src/dpmr_voice.c | 143 ++++++++++++++++++++++++++++++++----------- src/dsd_audio.c | 39 ++++++++++-- src/dsd_frame_sync.c | 112 ++++++++++++++++----------------- src/dsd_main.c | 33 ++++++++-- src/dsd_ncurses.c | 10 +-- 7 files changed, 271 insertions(+), 111 deletions(-) diff --git a/include/dsd.h b/include/dsd.h index 8f9a744..e7423e7 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -42,8 +42,9 @@ #include "p25p1_heuristics.h" -#include //PULSE AUDIO -#include //PULSE AUDIO +#include //PULSE AUDIO +#include //PULSE AUDIO +//#include //PUlSE AUDIO #define SAMPLE_RATE_IN 48000 //48000 #define SAMPLE_RATE_OUT 8000 //8000, @@ -348,7 +349,7 @@ typedef struct int use_ncurses_terminal; int reset_state; int payload; - + char output_name[1024]; //LEH DMR Stuff int EncryptionMode; @@ -367,12 +368,20 @@ typedef struct int repeat; short *audio_out_buf; short *audio_out_buf_p; + short *audio_out_bufR; + short *audio_out_buf_pR; float *audio_out_float_buf; float *audio_out_float_buf_p; + float *audio_out_float_bufR; + float *audio_out_float_buf_pR; float audio_out_temp_buf[160]; float *audio_out_temp_buf_p; + float audio_out_temp_bufR[160]; + float *audio_out_temp_buf_pR; int audio_out_idx; int audio_out_idx2; + int audio_out_idxR; + int audio_out_idx2R; //int wav_out_bytes; int center; int jitter; @@ -391,6 +400,8 @@ typedef struct int midx; char err_str[64]; //actual error errorbars char err_buf[64]; //make copy of err_str for comparison, only have string print when strcmp is different?? + char err_strR[64]; + char err_bufR[64]; char fsubtype[16]; char ftype[16]; int symbolcnt; diff --git a/src/dmr_encryption.c b/src/dmr_encryption.c index afb8e5a..21122df 100644 --- a/src/dmr_encryption.c +++ b/src/dmr_encryption.c @@ -25,8 +25,12 @@ void ProcessDMREncryption (dsd_opts * opts, dsd_state * state) uint32_t i, j; uint32_t Frame; TimeSlotVoiceSuperFrame_t * TSVoiceSupFrame = NULL; + TimeSlotVoiceSuperFrame_t * TSVoiceSupFrameL = NULL; + TimeSlotVoiceSuperFrame_t * TSVoiceSupFrameR = NULL; int *errs; int *errs2; + int *errsR; + int *errs2R; /* * Currently encryption is not supported in this public version... @@ -42,6 +46,11 @@ void ProcessDMREncryption (dsd_opts * opts, dsd_state * state) TSVoiceSupFrame = &state->TS2SuperFrame; } + TSVoiceSupFrameL = &state->TS1SuperFrame; + TSVoiceSupFrameR = &state->TS2SuperFrame; + + + /* Apply encryption here * * A DMR superframe = 6 frames x 3 AMBE voice sample of 49 bits each @@ -84,6 +93,24 @@ void ProcessDMREncryption (dsd_opts * opts, dsd_state * state) TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i], state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); + //errs = (int*)&(TSVoiceSupFrameL->TimeSlotAmbeVoiceFrame[Frame].errs1[i]); + //errs2 = (int*)&(TSVoiceSupFrameL->TimeSlotAmbeVoiceFrame[Frame].errs2[i]); + //state->errs = TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].errs1[i]; //correct placement + //state->errs2 = TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].errs2[i]; //correct placement + + //errsR = (int*)&(TSVoiceSupFrameR->TimeSlotAmbeVoiceFrame[Frame].errs1[i]); + //errs2R = (int*)&(TSVoiceSupFrameR->TimeSlotAmbeVoiceFrame[Frame].errs2[i]); + //state->errs = TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].errs1[i]; //correct placement + //state->errs2 = TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].errs2[i]; //correct placement + //state->audio_out_temp_bufR = state->audio_out_temp_buf; + //mbe_processAmbe2450Dataf (state->audio_out_temp_buf, errs, errs2, state->err_str, + // TSVoiceSupFrameL->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i], + // state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); + + //mbe_processAmbe2450Dataf (state->audio_out_temp_bufR, errsR, errs2R, state->err_strR, + // TSVoiceSupFrameR->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i], + // state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); + if (opts->mbe_out_f != NULL) { saveAmbe2450Data (opts, state, TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i]); @@ -94,6 +121,7 @@ void ProcessDMREncryption (dsd_opts * opts, dsd_state * state) } state->debug_audio_errors += *errs2; + //state->debug_audio_errors += *errs2R; processAudio(opts, state); diff --git a/src/dpmr_voice.c b/src/dpmr_voice.c index c7e4ae8..eaf935f 100644 --- a/src/dpmr_voice.c +++ b/src/dpmr_voice.c @@ -129,12 +129,18 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) x++; y++; z++; + } - //processMbeFrame (opts, state, NULL, ambe_fr[j], NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, ambe_fr2, NULL); //HERE HERE + #ifdef DPMR_DUMP fprintf(stderr, " "); #endif } + //processMbeFrame (opts, state, NULL, ambe_fr[0], NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, ambe_fr[1], NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, ambe_fr[2], NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, ambe_fr[3], NULL); //HERE HERE /* First CC (Channel Code) - 24 bit */ k = 0; @@ -207,6 +213,8 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) #endif ambe_fr[j + 4][*w][*x] = (1 & (dibit >> 1)); // bit 1 ambe_fr[j + 4][*y][*z] = (1 & dibit); // bit 0 + //ambe_fr2[*w][*x] = (1 & (dibit >> 1)); // bit 1 + //ambe_fr2[*y][*z] = (1 & dibit); // bit 0 //TCH[j + 4][k] = (1 & (dibit >> 1)); // bit 1 //TCH[j + 4][k + 1] = (1 & dibit); // bit 0 state->dPMRVoiceFS2Frame.RawVoiceBit[j + 4][k] = (1 & (dibit >> 1)); // bit 1 @@ -217,12 +225,16 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) y++; z++; } + //processMbeFrame (opts, state, NULL, ambe_fr2, NULL); #ifdef DPMR_DUMP fprintf(stderr, " "); #endif } - + //processMbeFrame (opts, state, NULL, ambe_fr[4], NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, ambe_fr[5], NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, ambe_fr[6], NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, ambe_fr[7], NULL); //HERE HERE #ifdef dPMR_PRINT_DEBUG_INFO fprintf(stderr, "\n"); @@ -595,6 +607,7 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) /* Voice frame detected */ VoiceFrameFlag = 1; + /* Check if the voice frame contains attached data */ if((state->dPMRVoiceFS2Frame.CommunicationMode[i] == 5) && state->dPMRVoiceFS2Frame.CCHDataCrcOk[i]) { @@ -618,7 +631,8 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) if(VoiceFrameFlag) { /* There is 4 AMBE voice sample per voice frame (= 8 per superframe) */ - for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) + //for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) + for(i = 0; i < (8); i++) { /* Apply ECC 1 to the AMBE frames */ state->dPMRVoiceFS2Frame.errs1[i] = (unsigned int)mbe_eccAmbe3600x2450C0(ambe_fr[i]); @@ -626,47 +640,100 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) /* Demodulate the AMBE frames */ mbe_demodulateAmbe3600x2450Data(ambe_fr[i]); + //mbe_demodulateAmbe3600x2400Data(ambe_fr[i]); /* Apply ECC 2 to the AMBE frames and get the 49 bit of the voice sample */ state->dPMRVoiceFS2Frame.errs2[i] += (unsigned int)mbe_eccAmbe3600x2450Data(ambe_fr[i], (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); + //processMbeFrame (opts, state, NULL, ambe_fr[i], NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, state->dPMRVoiceFS2Frame.AmbeBit[i], NULL); //HERE HERE } } /* End if(VoiceFrameFlag && !AttachedDataFlag) */ - /* Check if the voice frame to play is encrypted */ - if(opts->dPMR_curr_frame_is_encrypted) + //for 1 to 8 and then VoiceFrameFlag + /* + for(i = 0; i < (8); i++) { - /* Encryption is not supported in the public version */ - ScramblerKey = 0; - } - else - { - /* The frame is unencrypted, - * so set the scrambler key to 0 */ - ScramblerKey = 0; - } - - - /* Play only voice frame */ - if(VoiceFrameFlag) - { - -#ifdef dPMR_PRINT_DEBUG_INFO - fprintf(stderr, "Voice frame to play\n"); -#endif /* dPMR_PRINT_DEBUG_INFO */ - /* There is 4 AMBE voice sample per voice frame (so 2 x 4 = 8 per superframe) */ - for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) + if(VoiceFrameFlag) { + //processMbeFrame (opts, state, NULL, ambe_fr[i], NULL); //HERE HERE + // errs = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs1[i]); errs2 = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs2[i]); state->errs = state->dPMRVoiceFS2Frame.errs1[i]; state->errs2 = state->dPMRVoiceFS2Frame.errs2[i]; - //sounds like choppy shit, but .amb file playback is acceptable, so investigate why this doesn't work properly - mbe_processAmbe2450Dataf (state->audio_out_temp_buf, (int *)errs, (int *)errs2, state->err_str, - (char *)state->dPMRVoiceFS2Frame.AmbeBit[i], + //processMbeFrame (opts, state, NULL, (char *)state->dPMRVoiceFS2Frame.AmbeBit[i], NULL); //HERE HERE + + mbe_processAmbe2450Dataf (state->audio_out_temp_buf, errs, errs2, state->err_str, + state->dPMRVoiceFS2Frame.AmbeBit[i], state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); - //mbe_processAmbe2450Data ((short *) state->audio_out_temp_buf_p, (int *)errs, (int *)errs2, state->err_str, + if (opts->mbe_out_f != NULL) + { + //saveAmbe2450Data (opts, state, (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); + } + if (opts->errorbars == 1) + { + fprintf(stderr, "%s", state->err_str); + } + + state->debug_audio_errors += *errs2; + + processAudio(opts, state); + + if (opts->wav_out_f != NULL) + { + writeSynthesizedVoice (opts, state); + } + + if (opts->audio_out == 1) + { + //Play the AMBE Frames + playSynthesizedVoice (opts, state); + } + } + } + */ + + //Check if the voice frame to play is encrypted + if(opts->dPMR_curr_frame_is_encrypted) + { + //Encryption is not supported in the public version + ScramblerKey = 0; + } + else + { + //The frame is unencrypted, + //so set the scrambler key to 0 + ScramblerKey = 0; + } + + + //Play only voice frame + + if(VoiceFrameFlag) + { + +#ifdef dPMR_PRINT_DEBUG_INFO + fprintf(stderr, "Voice frame to play\n"); +#endif //dPMR_PRINT_DEBUG_INFO + //There is 4 AMBE voice sample per voice frame (so 2 x 4 = 8 per superframe) + for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) //is this backwards, shouldn't we run this first, and then voice frame after? + { + + errs = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs1[i]); + errs2 = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs2[i]); + state->errs = state->dPMRVoiceFS2Frame.errs1[i]; + state->errs2 = state->dPMRVoiceFS2Frame.errs2[i]; + // + //processMbeFrame (opts, state, NULL, ambe_fr2, NULL); + + //sounds like choppy shit, but .amb file playback is acceptable, so investigate why this doesn't work properly + mbe_processAmbe2450Dataf (state->audio_out_temp_buf, errs, errs2, state->err_str, + state->dPMRVoiceFS2Frame.AmbeBit[i], + state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); + + //mbe_processAmbe2450Dataf (state->audio_out_temp_buf_p, (int *)errs, (int *)errs2, state->err_str, // (char *)state->dPMRVoiceFS2Frame.AmbeBit[i], // state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); if (opts->mbe_out_f != NULL) @@ -689,16 +756,16 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) if (opts->audio_out == 1) { - /* Play the AMBE Frames */ + //Play the AMBE Frames playSynthesizedVoice (opts, state); } - } /* End for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) */ - } /* End if(VoiceFrameFlag) */ + } //End for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) + } //End if(VoiceFrameFlag) else { #ifdef dPMR_PRINT_DEBUG_INFO fprintf(stderr, "Data frame, don't play it !\n"); -#endif /* dPMR_PRINT_DEBUG_INFO */ +#endif //dPMR_PRINT_DEBUG_INFO } if (opts->errorbars == 1) @@ -708,7 +775,7 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) #ifdef dPMR_PRINT_DEBUG_INFO - /* Display the CCH content */ + //Display the CCH content for(i = 0; i < NB_OF_DPMR_VOICE_FRAME_TO_DECODE; i++) { fprintf(stderr, "i = %u - ", i); @@ -722,17 +789,19 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) else fprintf(stderr, "CRC ERROR"); fprintf(stderr, "\n"); } -#endif /* dPMR_PRINT_DEBUG_INFO */ +#endif //dPMR_PRINT_DEBUG_INFO - /* Print sPMR frame (if needed) */ + //Print sPMR frame (if needed) //dPMRVoiceFrameProcess(opts, state); //HERE HERE disabled to build -} /* End processdPMRvoice() */ + +} //End processdPMRvoice() /* Scrambler used for dPMR transmission (different of the * voice encryption scrambler), see ETSI TS 102 658 chapter * 7.3 for the polynomial description. * It is a X^9 + X^5 + 1 polynomial. */ + void ScrambledPMRBit(uint32_t * LfsrValue, uint8_t * BufferIn, uint8_t * BufferOut, uint32_t NbOfBitToScramble) { uint8_t S[9] = {0}; diff --git a/src/dsd_audio.c b/src/dsd_audio.c index 9712f70..5b203bc 100644 --- a/src/dsd_audio.c +++ b/src/dsd_audio.c @@ -21,6 +21,8 @@ pa_sample_spec ss; pa_sample_spec tt; pa_sample_spec zz; pa_sample_spec cc; +//pa_channel_map channel_map; + void openPulseOutput(dsd_opts * opts) { @@ -33,7 +35,7 @@ void openPulseOutput(dsd_opts * opts) tt.channels = opts->pulse_digi_out_channels; //doing tests with 2 channels at 22050 for 44100 audio default in pulse tt.rate = opts->pulse_digi_rate_out; //48000, switches to 8000 when using RTL dongle //fprintf (stderr,"digi rate out = %d\n", opts->pulse_digi_rate_out); - + //pa_channel_map_init_stereo(&channel_map); //ss if (opts->monitor_input_audio == 1) { @@ -41,8 +43,13 @@ void openPulseOutput(dsd_opts * opts) } //tt - opts->pulse_digi_dev_out = pa_simple_new(NULL, "DSD FME", PA_STREAM_PLAYBACK, NULL, "Digi Audio Out", &tt, NULL, NULL, NULL); - + pa_channel_map* left = 0; //NULL and 0 are same in this context + pa_channel_map* right = 0; //NULL and 0 are same in this context + //pa_channel_map* right; + //pa_channel_map_init(right); + opts->pulse_digi_dev_out = pa_simple_new(NULL, "DSD-FME", PA_STREAM_PLAYBACK, NULL, opts->output_name, &tt, left, NULL, NULL); + //opts->pulse_digi_dev_out = pa_simple_new(NULL, "DSD FME", PA_STREAM_PLAYBACK, NULL, "Digi Audio Out LEFT", &tt, left, NULL, NULL); + //opts->pulse_raw_dev_out = pa_simple_new(NULL, "DSD-FME", PA_STREAM_PLAYBACK, NULL, "Digi Audio Out RIGHT", &tt, right, NULL, NULL); } void openPulseInput(dsd_opts * opts) @@ -62,7 +69,7 @@ void openPulseInput(dsd_opts * opts) //opts->pulse_raw_dev_in = pa_simple_new(NULL, "DSD FME", PA_STREAM_RECORD, NULL, "Raw Audio In", &zz, NULL, NULL, NULL); } //cc - opts->pulse_digi_dev_in = pa_simple_new(NULL, "DSD FME", PA_STREAM_RECORD, NULL, "Audio In", &cc, NULL, NULL, NULL); + opts->pulse_digi_dev_in = pa_simple_new(NULL, "DSD-FME", PA_STREAM_RECORD, NULL, opts->output_name, &cc, NULL, NULL, NULL); } @@ -161,6 +168,7 @@ processAudio (dsd_opts * opts, dsd_state * state) // detect max level max = 0; state->audio_out_temp_buf_p = state->audio_out_temp_buf; + //state->audio_out_temp_buf_pR = state->audio_out_temp_bufR; for (n = 0; n < 160; n++) { aout_abs = fabsf (*state->audio_out_temp_buf_p); @@ -169,15 +177,24 @@ processAudio (dsd_opts * opts, dsd_state * state) max = aout_abs; } state->audio_out_temp_buf_p++; + //state->audio_out_temp_buf_pR++; } *state->aout_max_buf_p = max; + //*state->aout_max_buf_pR = max; state->aout_max_buf_p++; + //state->aout_max_buf_pR++; state->aout_max_buf_idx++; + //state->aout_max_buf_idxR++; if (state->aout_max_buf_idx > 24) { state->aout_max_buf_idx = 0; state->aout_max_buf_p = state->aout_max_buf; } + //if (state->aout_max_buf_idxR > 24) + // { + // state->aout_max_buf_idxR = 0; + // state->aout_max_buf_pR = state->aout_max_bufR; + // } // lookup max history for (i = 0; i < 25; i++) @@ -412,6 +429,20 @@ playSynthesizedVoice (dsd_opts * opts, dsd_state * state) //pa_simple_write(opts->pulse_raw_dev_out, (void*)&state->pulse_raw_out_buffer, 2, NULL); } + + //two slot audio testing, still need to seperate channels first internally, but this will play them out on left and RIGHT + /* + if(state->currentslot == 0) + { + pa_simple_write(opts->pulse_digi_dev_out, (state->audio_out_buf_p - state->audio_out_idx), (state->audio_out_idx * 2), NULL); //Yay! It works. + } + if(state->currentslot == 1) + { + pa_simple_write(opts->pulse_raw_dev_out, (state->audio_out_buf_p - state->audio_out_idx), (state->audio_out_idx * 2), NULL); //Yay! It works. + //pa_simple_write(opts->pulse_raw_dev_out, (state->audio_out_buf_pR - state->audio_out_idxR), (state->audio_out_idxR * 2), NULL); //Yay! It works. + } + */ + pa_simple_write(opts->pulse_digi_dev_out, (state->audio_out_buf_p - state->audio_out_idx), (state->audio_out_idx * 2), NULL); //Yay! It works. state->audio_out_idx = 0; } diff --git a/src/dsd_frame_sync.c b/src/dsd_frame_sync.c index 44c6fd1..7bfba83 100644 --- a/src/dsd_frame_sync.c +++ b/src/dsd_frame_sync.c @@ -612,9 +612,9 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "DPMR_FRAME_SYNC_1\n"); // TODO : To be removed - if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || - (state->lastsynctype == 22) || (state->lastsynctype == 23)) - { + //if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + // (state->lastsynctype == 22) || (state->lastsynctype == 23)) + //{ state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; @@ -630,11 +630,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->lastsynctype = 20; return (20); - } - else - { - state->lastsynctype = 20; - } + //} + //else + //{ + // state->lastsynctype = 20; + //} } /* End if (opts->inverted_dpmr == 0) */ } else if(strcmp(synctest12, DPMR_FRAME_SYNC_2) == 0) @@ -674,9 +674,9 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "DPMR_FRAME_SYNC_3\n"); // TODO : To be removed - if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || - (state->lastsynctype == 22) || (state->lastsynctype == 23)) - { + //if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + // (state->lastsynctype == 22) || (state->lastsynctype == 23)) + //{ state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; @@ -692,11 +692,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->lastsynctype = 22; return (22); - } - else - { - state->lastsynctype = 22; - } + //} + //else + //{ + // state->lastsynctype = 22; + //} } } if(strcmp(synctest, DPMR_FRAME_SYNC_4) == 0) @@ -705,9 +705,9 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "DPMR_FRAME_SYNC_4\n"); // TODO : To be removed - if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || - (state->lastsynctype == 22) || (state->lastsynctype == 23)) - { + //if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + // (state->lastsynctype == 22) || (state->lastsynctype == 23)) + //{ state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; @@ -723,11 +723,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->lastsynctype = 23; return (23); - } - else - { - state->lastsynctype = 23; - } + //} + //else + //{ + // state->lastsynctype = 23; + //} } } else if(strcmp(synctest, INV_DPMR_FRAME_SYNC_1) == 0) @@ -735,9 +735,9 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (opts->inverted_dpmr) { //fprintf(stderr, "INV_DPMR_FRAME_SYNC_1\n"); // TODO : To be removed - if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || - (state->lastsynctype == 26) || (state->lastsynctype == 27)) - { + //if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + // (state->lastsynctype == 26) || (state->lastsynctype == 27)) + //{ state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; @@ -749,11 +749,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } state->lastsynctype = 24; return (24); - } - else - { - state->lastsynctype = 24; - } + //} + //else + //{ + // state->lastsynctype = 24; + //} } } else if(strcmp(synctest12, INV_DPMR_FRAME_SYNC_2) == 0) @@ -762,9 +762,9 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "DPMR_FRAME_SYNC_2\n"); // TODO : To be removed - if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || - (state->lastsynctype == 26) || (state->lastsynctype == 27)) - { + //if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + // (state->lastsynctype == 26) || (state->lastsynctype == 27)) + //{ state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; @@ -776,11 +776,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } state->lastsynctype = 25; return (25); - } - else - { - state->lastsynctype = 25; - } + //} + //else + //{ + // state->lastsynctype = 25; + //} } } else if(strcmp(synctest12, INV_DPMR_FRAME_SYNC_3) == 0) @@ -789,9 +789,9 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "INV_DPMR_FRAME_SYNC_3\n"); // TODO : To be removed - if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || - (state->lastsynctype == 26) || (state->lastsynctype == 27)) - { + //if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + // (state->lastsynctype == 26) || (state->lastsynctype == 27)) + //{ state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; @@ -803,11 +803,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } state->lastsynctype = 26; return (26); - } - else - { - state->lastsynctype = 26; - } + //} + //else + //{ + // state->lastsynctype = 26; + //} } } if(strcmp(synctest, INV_DPMR_FRAME_SYNC_4) == 0) @@ -816,9 +816,9 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "INV_DPMR_FRAME_SYNC_4\n"); // TODO : To be removed - if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || - (state->lastsynctype == 26) || (state->lastsynctype == 27)) - { + //if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + // (state->lastsynctype == 26) || (state->lastsynctype == 27)) + //{ state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; @@ -830,11 +830,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } state->lastsynctype = 27; return (27); - } - else - { - state->lastsynctype = 27; - } + //} + //else + //{ + // state->lastsynctype = 27; + //} } } else diff --git a/src/dsd_main.c b/src/dsd_main.c index 7dbe392..3f2c82f 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -95,6 +95,7 @@ noCarrier (dsd_opts * opts, dsd_state * state) state->min = -15000; state->center = 0; state->err_str[0] = 0; + state->err_strR[0] = 0; sprintf (state->fsubtype, " "); sprintf (state->ftype, " "); state->errs = 0; @@ -190,14 +191,15 @@ initOpts (dsd_opts * opts) opts->rtl_udp_port = 6020; //set UDP port for RTL remote opts->rtl_bandwidth = 12; //changed recommended default to 12, 24 for ProVoice opts->pulse_raw_rate_in = 48000; - opts->pulse_raw_rate_out = 48000; //doing tests with 2 channels at 24000 for 48000 audio default in pulse + opts->pulse_raw_rate_out = 24000; //doing tests with 2 channels at 24000 for 48000 audio default in pulse opts->pulse_digi_rate_in = 48000; - opts->pulse_digi_rate_out = 24000; //need to copy this to rtl type in and change rate out to 8000 + opts->pulse_digi_rate_out = 48000; //need to copy this to rtl type in and change rate out to 8000 opts->pulse_raw_in_channels = 1; - opts->pulse_raw_out_channels = 1; + opts->pulse_raw_out_channels = 2; opts->pulse_digi_in_channels = 1; //2 - opts->pulse_digi_out_channels = 2; - + opts->pulse_digi_out_channels = 1; //2 + //opts->output_name = "DSD-FME"; + sprintf (opts->output_name, " "); opts->pulse_flush = 1; //set 0 to flush, 1 for flushed opts->use_ncurses_terminal = 0; //ncurses terminal disabled by default, call with -N opts->payload = 0; //initialize with 0 so can pass == 0 arguments @@ -217,14 +219,23 @@ initState (dsd_state * state) memset (state->dibit_buf, 0, sizeof (int) * 200); state->repeat = 0; state->audio_out_buf = malloc (sizeof (short) * 1000000); + state->audio_out_bufR = malloc (sizeof (short) * 1000000); memset (state->audio_out_buf, 0, 100 * sizeof (short)); + memset (state->audio_out_bufR, 0, 100 * sizeof (short)); state->audio_out_buf_p = state->audio_out_buf + 100; + state->audio_out_buf_pR = state->audio_out_buf + 100; state->audio_out_float_buf = malloc (sizeof (float) * 1000000); + state->audio_out_float_bufR = malloc (sizeof (float) * 1000000); memset (state->audio_out_float_buf, 0, 100 * sizeof (float)); + memset (state->audio_out_float_bufR, 0, 100 * sizeof (float)); state->audio_out_float_buf_p = state->audio_out_float_buf + 100; + state->audio_out_float_buf_pR = state->audio_out_float_bufR + 100; state->audio_out_idx = 0; state->audio_out_idx2 = 0; + state->audio_out_idxR = 0; + state->audio_out_idx2R = 0; state->audio_out_temp_buf_p = state->audio_out_temp_buf; + state->audio_out_temp_buf_pR = state->audio_out_temp_bufR; //state->wav_out_bytes = 0; state->center = 0; state->jitter = -1; @@ -251,6 +262,7 @@ initState (dsd_state * state) } state->midx = 0; state->err_str[0] = 0; + state->err_strR[0] = 0; sprintf (state->fsubtype, " "); sprintf (state->ftype, " "); state->symbolcnt = 0; @@ -821,6 +833,7 @@ main (int argc, char **argv) opts.frame_dmr = 1; opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; //turn it on, doesn't work due to symbol rate difference + sprintf (opts.output_name, "Auto Detect"); } else if (optarg[0] == 'd') { @@ -832,6 +845,7 @@ main (int argc, char **argv) opts.frame_dmr = 0; opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; + sprintf (opts.output_name, "D-STAR"); fprintf (stderr,"Decoding only D-STAR frames.\n"); } else if (optarg[0] == 'x') @@ -844,6 +858,7 @@ main (int argc, char **argv) opts.frame_dmr = 0; opts.frame_dpmr = 0; //borrow from LEH opts.frame_provoice = 0; + sprintf (opts.output_name, "X2-TDMA"); fprintf (stderr,"Decoding only X2-TDMA frames.\n"); } else if (optarg[0] == 'p') @@ -862,6 +877,7 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 1; state.rf_mod = 2; + sprintf (opts.output_name, "ProVoice"); fprintf (stderr,"Setting symbol rate to 9600 / second\n"); fprintf (stderr,"Enabling only GFSK modulation optimizations.\n"); fprintf (stderr,"Decoding only ProVoice frames.\n"); @@ -880,6 +896,7 @@ main (int argc, char **argv) opts.mod_c4fm = 1; //Phase 1 only uses C4FM, right? opts.mod_qpsk = 0; state.rf_mod = 0; // + sprintf (opts.output_name, "P25P1"); fprintf (stderr,"Decoding only P25 Phase 1 frames.\n"); } else if (optarg[0] == 'i') @@ -899,6 +916,7 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 0; //was 1 with others on zero state.rf_mod = 0; //was 2 + sprintf (opts.output_name, "NXDN48"); //opts.symboltiming = 2400; //NXDN48 uses 2400 symbol rate fprintf (stderr,"Setting symbol rate to 2400 / second\n"); fprintf (stderr,"Enabling only C4FM modulation optimizations.\n"); @@ -918,6 +936,7 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 0; //was 1 state.rf_mod = 0; //was 2 + sprintf (opts.output_name, "NXDN96"); fprintf (stderr,"Enabling only C4FM modulation optimizations.\n"); fprintf (stderr,"Decoding only NXDN 9600 baud frames.\n"); } @@ -935,6 +954,7 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 0; // state.rf_mod = 0; // + sprintf (opts.output_name, "DMR/MOTOTRBO"); fprintf (stderr,"Decoding only DMR/MOTOTRBO frames.\n"); } else if (optarg[0] == 'm') @@ -952,7 +972,8 @@ main (int argc, char **argv) opts.mod_c4fm = 1; opts.mod_qpsk = 0; opts.mod_gfsk = 0; - state.rf_mod = 0; + state.rf_mod = 0; //garbage value to keep jitter in check? + sprintf (opts.output_name, "dPMR"); //opts.pulse_digi_rate_in = 48000; //opts.pulse_digi_rate_out = 48000; //need to copy this to rtl type in and change rate out to 8000 //opts.pulse_digi_in_channels = 1; diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index 052a7e0..fb4904f 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -374,7 +374,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (lls == 12 || lls == 13) //DMR Voice Types { //printw ("| DCC: [%i] FID: [%02X]\n", dcc, state->dmr_fid); - printw ("| DCC: [%i] MFID: [%02X] SOP: [%X] \n", dcc, state->dmr_fid, state->dmr_so); + printw ("| DCC: [%i] FID: [%02X] SOP: [%X] \n", dcc, state->dmr_fid, state->dmr_so); printw ("| TID: [%i] RID: [%i]", tg, rd); if(state->dmr_so & 0x80) //1000 0000 { @@ -403,7 +403,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } printw("\n"); //printw ("%s ", state->slot0light); - printw ("| SLOT 0 "); + printw ("| SLOT 1 "); if (state->currentslot == 0) //find out how to tell when slot0 has voice { s0last = "Voice"; @@ -412,7 +412,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("%s ", s0last); //printw ("%s ", state->slot1light); - printw ("SLOT 1 "); + printw ("SLOT 2 "); if (state->currentslot == 1) //find out how to tell when slot1 has voice { s1last = "Voice"; @@ -457,7 +457,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw("\n"); //printw ("%s ", state->slot0light); - printw ("| SLOT 0 "); + printw ("| SLOT 1 "); if (state->currentslot == 0) //find out how to tell when slot0 has voice { s0last = "Data "; @@ -468,7 +468,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } printw ("%s ", s0last); //printw ("%s ", state->slot1light); - printw ("SLOT 1 "); + printw ("SLOT 2 "); if (state->currentslot == 1) //find out how to tell when slot1 has voice { s1last = "Data "; From 9f330e79028e7c756fd2986d5b15907f259d2890 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Wed, 9 Mar 2022 18:10:32 -0500 Subject: [PATCH 06/12] NXDN Ncurses Improvements NXDN Ncurses Improvements -show TG, call types, alg, keys, etc on NXDN systems --- include/dsd.h | 4 +++ include/nxdn_const.h | 2 +- src/dsd_audio.c | 4 +-- src/dsd_main.c | 9 ++++-- src/dsd_ncurses.c | 66 ++++++++++++++++++++++++++++++++++++++++---- src/nxdn_lib.c | 25 +++++++++++++++-- 6 files changed, 97 insertions(+), 13 deletions(-) diff --git a/include/dsd.h b/include/dsd.h index e7423e7..03f0eda 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -472,6 +472,10 @@ typedef struct unsigned int dmr_color_code; unsigned int nxdn_last_ran; unsigned int nxdn_last_rid; + unsigned int nxdn_last_tg; + unsigned int nxdn_cipher_type; + unsigned int nxdn_key; + char nxdn_call_type[1024]; //borrow from LEH NxdnSacchRawPart_t NxdnSacchRawPart[4]; NxdnFacch1RawPart_t NxdnFacch1RawPart[2]; diff --git a/include/nxdn_const.h b/include/nxdn_const.h index 9bfa907..8b34ccf 100644 --- a/include/nxdn_const.h +++ b/include/nxdn_const.h @@ -65,7 +65,7 @@ extern const int DSDNXDN_UDCH_m_PunctureList[58]; //NXDN_DCALL_REC_RESP = 0b001010, /* NXDN_DCALL_REC_REQ = NXDN_DCALL_REC_RESP */ #define NXDN_DCALL_ASSGN = 0b001110 #define NXDN_DCALL_ASSGN_DUP = 0b001101 -#define NXDN_IDLE = 0b010000 +#define NXDN_IDLE 0b010000 #define NXDN_DISC_REQ = 0b010001 /* NXDN_DISC_REQ = NXDN_DISC */ #define NXDN_DISC = 0b010001 /* NXDN_DISC_REQ = NXDN_DISC */ diff --git a/src/dsd_audio.c b/src/dsd_audio.c index 5b203bc..10b0e9b 100644 --- a/src/dsd_audio.c +++ b/src/dsd_audio.c @@ -430,7 +430,7 @@ playSynthesizedVoice (dsd_opts * opts, dsd_state * state) } - //two slot audio testing, still need to seperate channels first internally, but this will play them out on left and RIGHT + //two slot audio testing, still need to seperate channels first internally, but this will play them out of different streams /* if(state->currentslot == 0) { @@ -442,7 +442,7 @@ playSynthesizedVoice (dsd_opts * opts, dsd_state * state) //pa_simple_write(opts->pulse_raw_dev_out, (state->audio_out_buf_pR - state->audio_out_idxR), (state->audio_out_idxR * 2), NULL); //Yay! It works. } */ - + pa_simple_write(opts->pulse_digi_dev_out, (state->audio_out_buf_p - state->audio_out_idx), (state->audio_out_idx * 2), NULL); //Yay! It works. state->audio_out_idx = 0; } diff --git a/src/dsd_main.c b/src/dsd_main.c index 3f2c82f..45a4090 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -193,11 +193,11 @@ initOpts (dsd_opts * opts) opts->pulse_raw_rate_in = 48000; opts->pulse_raw_rate_out = 24000; //doing tests with 2 channels at 24000 for 48000 audio default in pulse opts->pulse_digi_rate_in = 48000; - opts->pulse_digi_rate_out = 48000; //need to copy this to rtl type in and change rate out to 8000 + opts->pulse_digi_rate_out = 24000; //need to copy this to rtl type in and change rate out to 8000 opts->pulse_raw_in_channels = 1; opts->pulse_raw_out_channels = 2; opts->pulse_digi_in_channels = 1; //2 - opts->pulse_digi_out_channels = 1; //2 + opts->pulse_digi_out_channels = 2; //2 //opts->output_name = "DSD-FME"; sprintf (opts->output_name, " "); opts->pulse_flush = 1; //set 0 to flush, 1 for flushed @@ -312,6 +312,10 @@ initState (dsd_state * state) state->nxdn_last_ran = -1; state->nxdn_last_rid = 0; + state->nxdn_last_tg = 0; + state->nxdn_cipher_type = 0; + state->nxdn_key = 0; + sprintf (state->nxdn_call_type, " "); state->dpmr_color_code = -1; state->dpmr_caller_id = 0; @@ -1079,6 +1083,7 @@ main (int argc, char **argv) opts.playfiles = 1; opts.errorbars = 0; opts.datascope = 0; + sprintf (opts.output_name, "MBE Playback"); state.optind = optind; break; case 'l': diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index fb4904f..cd59917 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -188,7 +188,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) call_matrix[9][1] = rn; call_matrix[9][2] = src; call_matrix[9][3] = 0; - call_matrix[9][4] = 0; + call_matrix[9][4] = tg; call_matrix[9][5] = time(NULL); } @@ -295,6 +295,16 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("--Call Info-------------------------------------------------------------------\n"); + //NXDN Ciper Types + //switch(CipherType) + //{ + // case 0: Ptr = ""; break; /* Non-ciphered mode / clear call */ + // case 1: Ptr = "Scrambler"; break; + // case 2: Ptr = "DES"; break; + // case 3: Ptr = "AES"; break; + // default: Ptr = "Unknown Cipher Type"; break; + //} + if (state->lastsynctype != -1) //not sure if this will be okay { lls = state->lastsynctype; @@ -302,13 +312,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (state->nxdn_last_rid > 0 && state->nxdn_last_rid != src); { src = state->nxdn_last_rid; - //rn = state->nxdn_last_ran; } if (state->nxdn_last_ran > -1 && state->nxdn_last_ran != rn); { - //src = state->nxdn_last_rid; rn = state->nxdn_last_ran; } + if (state->nxdn_last_tg > 0 && state->nxdn_last_tg != tg); + { + tg = state->nxdn_last_tg; + } //if (state->dmr_color_code > 0 && (lls == 10 || lls == 11 || lls == 12 || lls == 13) ) //DMR, DCC only carried on Data? //if (state->color_code > -1 && (lls == 10 || lls == 11 || lls == 12 || lls == 13) ) //DMR, DCC only carried on Data? @@ -334,9 +346,50 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) //if (state->lastsynctype == 8 || state->lastsynctype == 9 || state->lastsynctype == 16 || state->lastsynctype == 17) //change this to NXDN syncs later on if (lls == 8 || lls == 9 || lls == 16 || lls == 17) { - //printw ("| RAN: [%02d] \n", state->nxdn_last_ran); - printw ("| RAN: [%02d] \n", rn); - printw ("| RID: [%d] \n", src); //maybe change to nxdn_last_rid + printw ("| RAN: [%02d] ", rn); + printw ("TID: [%d] ", tg); + //printw ("| RID: [%d] \n", src); + printw ("RID: [%d] \n| ALG: [0x%02X] KEY [0x%02X] ", src, state->nxdn_cipher_type, state->nxdn_key); + if (state->carrier == 1) + { + printw("%s ", state->nxdn_call_type); + //attron(COLOR_PAIR(4)); + //printw ("No Encryption "); + //attroff(COLOR_PAIR(4)); + //attron(COLOR_PAIR(3)); + } + if (state->nxdn_cipher_type == 0x1 && state->carrier == 1) + { + attron(COLOR_PAIR(2)); + printw ("Scrambler Encryption "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + if (state->nxdn_cipher_type == 0x2 && state->carrier == 1) + { + attron(COLOR_PAIR(2)); + printw ("DES Encryption "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + if (state->nxdn_cipher_type == 0x3 && state->carrier == 1) + { + attron(COLOR_PAIR(2)); + printw ("AES Encryption "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + if (state->nxdn_cipher_type > 0x3 && state->carrier == 1) + { + attron(COLOR_PAIR(2)); + printw ("Unknown Encryption "); + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(3)); + } + //printw("%s ", state->nxdn_call_type); + printw("\n"); + + } //printw ("Error?: [%i] [%i] \n", state->errs, state->errs2); //what are these? @@ -515,6 +568,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (lls == 8 || lls == 9 || lls == 16 || lls == 17) { printw ("RAN [%2d] ", call_matrix[9-j][1]); + printw ("TG [%d] ", call_matrix[9-j][4]); } if (lls == 0 || lls == 1 || lls == 12 || lls == 13 || lls == 10 || lls == 11 ) //P25 P1 and DMR { diff --git a/src/nxdn_lib.c b/src/nxdn_lib.c index c3cef9b..6ed5f45 100644 --- a/src/nxdn_lib.c +++ b/src/nxdn_lib.c @@ -574,6 +574,14 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, /* Decode the right "Message Type" */ switch(MessageType) { + //Idle + case NXDN_IDLE: + { + //fprintf(stderr, "NXDN IDLE\n"); + sprintf (state->nxdn_call_type, "NXDN IDLE "); + } + break; + /* VCALL */ case NXDN_VCALL: { @@ -728,19 +736,32 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) /* Print the "Call Type" */ //fprintf(stderr, "%s - ", NXDN_Call_Type_To_Str(CallType)); fprintf(stderr, "\n\t %s - ", NXDN_Call_Type_To_Str(CallType)); //line break 1 tab, 2 spaces start this string + sprintf (state->nxdn_call_type, NXDN_Call_Type_To_Str(CallType)); + //state->nxdn_call_type = NXDN_Call_Type_To_Str(CallType); /* Print the "Voice Call Option" */ NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode); fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode); + state->nxdn_key = (KeyID & 0xFF); + state->nxdn_cipher_type = CipherType; /* Print the "Cipher Type" */ - if(CipherType != 0) fprintf(stderr, "%s - ", NXDN_Cipher_Type_To_Str(CipherType)); + if(CipherType != 0) + { + fprintf(stderr, "%s - ", NXDN_Cipher_Type_To_Str(CipherType)); + //state->nxdn_cipher_type = CipherType; + } /* Print the Key ID */ - if(CipherType != 0) fprintf(stderr, "Key ID %u - ", KeyID & 0xFF); + if(CipherType != 0) + { + fprintf(stderr, "Key ID %u - ", KeyID & 0xFF); + //state->nxdn_key = (KeyID & 0xFF); + } /* Print Source ID and Destination ID (Talk Group or Unit ID) */ fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF); + state->nxdn_last_tg = (DestinationID & 0xFFFF); if(state->NxdnElementsContent.VCallCrcIsGood) { From 0fc9380b0014dfd317435c41382d6c7c9ad55183 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sat, 12 Mar 2022 00:03:21 -0500 Subject: [PATCH 07/12] DMR PI Header, NCurses Tweaks, Fun Stuff DMR PI Header, NCurses Tweaks, Fun Stuff --Add PI Header handler, print DMR ALG, KEY, and MI values --NCurses updates to reflect new DMR and NXDN info available --Other Fun Stuff a.k.a. Stuff I might have broken or can't remember what I did now --- include/dsd.h | 4 +- src/dmr_data.c | 8 +-- src/dmr_encryption.c | 42 +++------------- src/dmr_sync.c | 113 ++++++++++++++++++++++++++++++++++++++++--- src/dmr_voice.c | 22 ++++----- src/dsd_audio.c | 10 ++-- src/dsd_dibit.c | 3 ++ src/dsd_frame.c | 16 +++--- src/dsd_frame_sync.c | 68 +++++++++++++------------- src/dsd_main.c | 35 +++++++++++--- src/dsd_ncurses.c | 77 ++++++++++++++++++++--------- src/dsd_symbol.c | 1 + src/nxdn_lib.c | 15 ++++-- 13 files changed, 274 insertions(+), 140 deletions(-) diff --git a/include/dsd.h b/include/dsd.h index 03f0eda..31ca97c 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -440,6 +440,8 @@ typedef struct int payload_algid; int payload_keyid; int payload_mfid; + int payload_mi; + unsigned long long int K; unsigned int debug_audio_errors; unsigned int debug_header_errors; @@ -774,7 +776,7 @@ uint32_t ComputeAndCorrectFullLinkControlCrc(uint8_t * FullLinkControlDataBytes, uint8_t ComputeCrc5Bit(uint8_t * DMRData); uint8_t * DmrAlgIdToStr(uint8_t AlgID); uint8_t * DmrAlgPrivacyModeToStr(uint32_t PrivacyMode); - +void ProcessDmrPIHeader(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t syncdata[48], uint8_t SlotType[20]); void Hamming_7_4_init(); void Hamming_7_4_encode(unsigned char *origBits, unsigned char *encodedBits); diff --git a/src/dmr_data.c b/src/dmr_data.c index 1eddd5f..f2301f9 100644 --- a/src/dmr_data.c +++ b/src/dmr_data.c @@ -330,9 +330,10 @@ processDMRdata (dsd_opts * opts, dsd_state * state) /* Print the color code */ fprintf(stderr, "| Color Code=%02d ", (int)state->color_code); - if(state->color_code_ok) fprintf(stderr, "(OK) |"); - else fprintf(stderr, "(CRC ERR) |"); - + //if(state->color_code_ok) fprintf(stderr, "(OK) |"); + if(state->color_code_ok) fprintf(stderr, "(CRC OK ) |"); //add line break + else fprintf(stderr, "(CRC ERR) |"); //add line break + //fprintf (stderr, "\n"); //print line break if (strcmp (state->fsubtype, " ") == 0) { fprintf(stderr, " Unknown burst type: %s", bursttype); @@ -348,6 +349,7 @@ processDMRdata (dsd_opts * opts, dsd_state * state) /* Burst = PI header */ case 0b0000: { + ProcessDmrPIHeader(opts, state, (uint8_t *)info, (uint8_t *)syncdata, (uint8_t *)SlotType); break; } diff --git a/src/dmr_encryption.c b/src/dmr_encryption.c index 21122df..5355286 100644 --- a/src/dmr_encryption.c +++ b/src/dmr_encryption.c @@ -27,16 +27,14 @@ void ProcessDMREncryption (dsd_opts * opts, dsd_state * state) TimeSlotVoiceSuperFrame_t * TSVoiceSupFrame = NULL; TimeSlotVoiceSuperFrame_t * TSVoiceSupFrameL = NULL; TimeSlotVoiceSuperFrame_t * TSVoiceSupFrameR = NULL; + int *errs; int *errs2; int *errsR; int *errs2R; + unsigned long long int k; + k = 0; - /* - * Currently encryption is not supported in this public version... - */ - - /* Check the current time slot */ if(state->currentslot == 0) { TSVoiceSupFrame = &state->TS1SuperFrame; @@ -48,37 +46,9 @@ void ProcessDMREncryption (dsd_opts * opts, dsd_state * state) TSVoiceSupFrameL = &state->TS1SuperFrame; TSVoiceSupFrameR = &state->TS2SuperFrame; - - - - /* Apply encryption here - * - * A DMR superframe = 6 frames x 3 AMBE voice sample of 49 bits each - * uint8_t KeyStream[6][3][49]; - * - * 1�) Initialize the "KeyStream" buffer (total 882 bits) with correct bits (depending of - * encryption mode used, MotoTRBO BP, Hytera BP, MotoTRBO EP, MotoTRBO AES... - * - * 2�) Apply a XOR between "KeyStream" and "TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i][j]" - * like code : - * for(Frame = 0; Frame < 6; Frame++) - * { - * for(i = 0; i < 3; i++) - * { - * for(j = 0; j < 49; j++) - * { - * TSVoiceSupFrame->TimeSlotAmbeVoiceFrame[Frame].AmbeBit[i] ^= KeyStream[Frame][i][j]; - * } - * } - * } - * - * 3�) Play all AMBE decoded sample (see the "for" loop below) - * - * */ - - /* - * Play all AMBE voice samples - * 1 DMR voice superframe = 6 DMR frames */ +// +// +// for(Frame = 0; Frame < 6; Frame++) { /* 1 DMR frame contains 3 AMBE voice samples */ diff --git a/src/dmr_sync.c b/src/dmr_sync.c index 007b5ef..1f7b338 100644 --- a/src/dmr_sync.c +++ b/src/dmr_sync.c @@ -21,7 +21,108 @@ //#define PRINT_VOICE_LC_HEADER_BYTES //#define PRINT_TERMINAISON_LC_BYTES //#define PRINT_VOICE_BURST_BYTES +void ProcessDmrPIHeader(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t syncdata[48], uint8_t SlotType[20]) +{ + //Placeholder + uint32_t i, j, k; + uint32_t CRCExtracted = 0; + uint32_t CRCComputed = 0; + uint32_t CRCCorrect = 0; + uint32_t IrrecoverableErrors = 0; + uint8_t DeInteleavedData[196]; + uint8_t DmrDataBit[96]; + uint8_t DmrDataByte[12]; + TimeSlotVoiceSuperFrame_t * TSVoiceSupFrame = NULL; + uint8_t R[3]; + uint8_t BPTCReservedBits = 0; + /* Remove warning compiler */ + //UNUSED_VARIABLE(syncdata[0]); + //UNUSED_VARIABLE(SlotType[0]); + //UNUSED_VARIABLE(BPTCReservedBits); + + /* Check the current time slot */ + if(state->currentslot == 0) + { + TSVoiceSupFrame = &state->TS1SuperFrame; + } + else + { + TSVoiceSupFrame = &state->TS2SuperFrame; + } + + CRCExtracted = 0; + CRCComputed = 0; + IrrecoverableErrors = 0; + + /* Deinterleave DMR data */ + BPTCDeInterleaveDMRData(info, DeInteleavedData); + + /* Extract the BPTC 196,96 DMR data */ + IrrecoverableErrors = BPTC_196x96_Extract_Data(DeInteleavedData, DmrDataBit, R); + + /* Fill the reserved bit (R(0)-R(2) of the BPTC(196,96) block) */ + BPTCReservedBits = (R[0] & 0x01) | ((R[1] << 1) & 0x02) | ((R[2] << 2) & 0x08); + + /* Convert the 96 bit of voice LC Header data into 12 bytes */ + k = 0; + for(i = 0; i < 12; i++) + { + DmrDataByte[i] = 0; + for(j = 0; j < 8; j++) + { + DmrDataByte[i] = DmrDataByte[i] << 1; + DmrDataByte[i] = DmrDataByte[i] | (DmrDataBit[k] & 0x01); + k++; + } + } + + /* Fill the CRC extracted (before Reed-Solomon (12,9) FEC correction) */ + CRCExtracted = 0; + //for(i = 0; i < 24; i++) + for(i = 0; i < 16; i++) + { + CRCExtracted = CRCExtracted << 1; + //CRCExtracted = CRCExtracted | (uint32_t)(DmrDataBit[i + 72] & 1); + CRCExtracted = CRCExtracted | (uint32_t)(DmrDataBit[i + 80] & 1); //80-96 for PI header + } + + /* Apply the CRC mask (see DMR standard B.3.12 Data Type CRC Mask) */ + //CRCExtracted = CRCExtracted ^ 0x969696; //does this mask get applied here though for PI? + //CRCExtracted = CRCExtracted ^ 0x6969; + + /* Check/correct the full link control data and compute the Reed-Solomon (12,9) CRC */ + //CRCCorrect = ComputeAndCorrectFullLinkControlCrc(DmrDataByte, &CRCComputed, 0x969696); + //CRCCorrect = ComputeAndCorrectFullLinkControlCrc(DmrDataByte, &CRCComputed, 0x6969); + + /* Convert corrected 12 bytes into 96 bits */ + for(i = 0, j = 0; i < 12; i++, j+=8) + { + DmrDataBit[j + 0] = (DmrDataByte[i] >> 7) & 0x01; + DmrDataBit[j + 1] = (DmrDataByte[i] >> 6) & 0x01; + DmrDataBit[j + 2] = (DmrDataByte[i] >> 5) & 0x01; + DmrDataBit[j + 3] = (DmrDataByte[i] >> 4) & 0x01; + DmrDataBit[j + 4] = (DmrDataByte[i] >> 3) & 0x01; + DmrDataBit[j + 5] = (DmrDataByte[i] >> 2) & 0x01; + DmrDataBit[j + 6] = (DmrDataByte[i] >> 1) & 0x01; + DmrDataBit[j + 7] = (DmrDataByte[i] >> 0) & 0x01; + } + //Placeholder, figure out which areas to grab + //state->payload_algid = DmrDataBit[1]; + state->payload_algid = DmrDataByte[0]; //not really sure, just guessing on observation? + //state->payload_keyid = DmrDataBit[2]; + state->payload_keyid = DmrDataByte[2]; + //state->payload_mi = ( (DmrDataBit[3] << 3) + (DmrDataBit[4] << 2) + (DmrDataBit[5] << 1) + DmrDataBit[6] ); + state->payload_mi = ( ((DmrDataByte[3]) << 24) + ((DmrDataByte[4]) << 16) + ((DmrDataByte[5]) << 8) + (DmrDataByte[6]) ); + //fprintf(stderr, "%s Slot(%d), CC(%x), PI HEADER: ALGID(%02x), KEYID(%02x), MI(%08x), DSTADDR(%06x)\n", + fprintf (stderr, "\n DMR PI Header ALG ID: 0x%02X KEY ID: 0x%02X MI: 0x%08X \n", state->payload_algid, state->payload_keyid, state->payload_mi); + fprintf (stderr, " Full PI Header Payload in Hex\n"); + for(i = 0, j = 0; i < 12; i++) + { + //fprintf (stderr, " Byte [%02d] [%02X] [%02X]\n", i+1, DmrDataByte[i], DmrDataByte[i] ^ 0x69); //0x6969 PI header mask? + } + //end Placeholder +} void ProcessDmrVoiceLcHeader(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t syncdata[48], uint8_t SlotType[20]) { @@ -140,7 +241,7 @@ void ProcessDmrVoiceLcHeader(dsd_opts * opts, dsd_state * state, uint8_t info[19 } /* Print the destination ID (TG) and the source ID */ - fprintf(stderr, "| TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); + fprintf(stderr, "\n TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); fprintf(stderr, "FID=0x%02X ", TSVoiceSupFrame->FullLC.FeatureSetID); //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; @@ -184,7 +285,7 @@ void ProcessDmrVoiceLcHeader(dsd_opts * opts, dsd_state * state, uint8_t info[19 } fprintf(stderr, "Call "); - if(TSVoiceSupFrame->FullLC.DataValidity) fprintf(stderr, "(OK) "); + if(TSVoiceSupFrame->FullLC.DataValidity) fprintf(stderr, "(CRC OK ) "); else if(IrrecoverableErrors == 0) fprintf(stderr, "RAS (FEC OK/CRC ERR)"); else fprintf(stderr, "(FEC FAIL/CRC ERR)"); @@ -340,7 +441,7 @@ void ProcessDmrTerminaisonLC(dsd_opts * opts, dsd_state * state, uint8_t info[19 } /* Print the destination ID (TG) and the source ID */ - fprintf(stderr, "| TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); + fprintf(stderr, "\n TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); fprintf(stderr, "FID=0x%02X ", TSVoiceSupFrame->FullLC.FeatureSetID); //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; @@ -349,7 +450,7 @@ void ProcessDmrTerminaisonLC(dsd_opts * opts, dsd_state * state, uint8_t info[19 if((IrrecoverableErrors == 0) && CRCCorrect) { - fprintf(stderr, "(OK) "); + fprintf(stderr, "(CRC OK ) "); //only set on good CRC value or corrected values state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; @@ -520,7 +621,7 @@ void ProcessVoiceBurstSync(dsd_opts * opts, dsd_state * state) } /* Print the destination ID (TG) and the source ID */ - fprintf(stderr, "| TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); + fprintf(stderr, " TG=%u Src=%u ", TSVoiceSupFrame->FullLC.GroupAddress, TSVoiceSupFrame->FullLC.SourceAddress); fprintf(stderr, "FID=0x%02X ", TSVoiceSupFrame->FullLC.FeatureSetID); //state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; //state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; @@ -528,7 +629,7 @@ void ProcessVoiceBurstSync(dsd_opts * opts, dsd_state * state) if((IrrecoverableErrors == 0) && CRCCorrect) { - fprintf(stderr, "(OK)"); + fprintf(stderr, "(CRC OK ) "); //only set on good CRC? state->lasttg = TSVoiceSupFrame->FullLC.GroupAddress; state->lastsrc = TSVoiceSupFrame->FullLC.SourceAddress; diff --git a/src/dmr_voice.c b/src/dmr_voice.c index 5522a76..fb0cf5c 100644 --- a/src/dmr_voice.c +++ b/src/dmr_voice.c @@ -593,15 +593,14 @@ void processDMRvoice (dsd_opts * opts, dsd_state * state) for(i = 0; i < 3; i++) { state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i] = mbe_eccAmbe3600x2450C0(state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i]); - //state->errs = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; //correct placement + state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i] = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; - //state->errs2 = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i]; //correct placement + mbe_demodulateAmbe3600x2450Data(state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i]); state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i] += mbe_eccAmbe3600x2450Data(state->TS1SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i], &(state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].AmbeBit[i][0])); } - //state->errs = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; //correct placement - //state->errs2 = (int)state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i]; //correct placement + } else { @@ -609,15 +608,14 @@ void processDMRvoice (dsd_opts * opts, dsd_state * state) for(i = 0; i < 3; i++) { state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i] = mbe_eccAmbe3600x2450C0(state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i]); - //state->errs = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; //correct placement + state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i] = state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; - //state->errs2 = state->TS1SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i]; //correct placement + mbe_demodulateAmbe3600x2450Data(state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i]); state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i] += mbe_eccAmbe3600x2450Data(state->TS2SuperFrame.TimeSlotDeinterleavedVoiceFrame[j].DeInterleavedVoiceSample[i], &(state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].AmbeBit[i][0])); } - //state->errs = state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs1[i]; //correct placement - //state->errs2 = (int)state->TS2SuperFrame.TimeSlotAmbeVoiceFrame[j].errs2[i]; //correct placement + } } /* End for(j = 0; j < 6; j++) */ @@ -628,10 +626,12 @@ void processDMRvoice (dsd_opts * opts, dsd_state * state) /* Print the color code */ fprintf(stderr, "| Color Code=%02d ", (int)state->color_code); - if(state->color_code_ok) fprintf(stderr, "(OK) |"); - else fprintf(stderr, "(CRC ERR) |"); + //if(state->color_code_ok) fprintf(stderr, "(OK) |"); + if(state->color_code_ok) fprintf(stderr, "(CRC OK ) |"); //add line break + else fprintf(stderr, "(CRC ERR) |"); //add line break - fprintf(stderr, " VOICE e:"); + //fprintf(stderr, " VOICE e: \n"); + fprintf(stderr, " VOICE \n"); } /* Perform the SYNC DMR data embedded decoding */ diff --git a/src/dsd_audio.c b/src/dsd_audio.c index 10b0e9b..3a28e9e 100644 --- a/src/dsd_audio.c +++ b/src/dsd_audio.c @@ -27,9 +27,9 @@ pa_sample_spec cc; void openPulseOutput(dsd_opts * opts) { - //ss.format = PA_SAMPLE_S16NE; - //ss.channels = opts->pulse_raw_out_channels; //doing tests with 2 channels at 22050 for 44100 audio default in pulse - //ss.rate = opts->pulse_raw_rate_out; //48000 + ss.format = PA_SAMPLE_S16NE; + ss.channels = opts->pulse_raw_out_channels; //doing tests with 2 channels at 22050 for 44100 audio default in pulse + ss.rate = opts->pulse_raw_rate_out; //48000 tt.format = PA_SAMPLE_S16NE; tt.channels = opts->pulse_digi_out_channels; //doing tests with 2 channels at 22050 for 44100 audio default in pulse @@ -39,7 +39,7 @@ void openPulseOutput(dsd_opts * opts) //ss if (opts->monitor_input_audio == 1) { - //opts->pulse_raw_dev_out = pa_simple_new(NULL, "DSD FME", PA_STREAM_PLAYBACK, NULL, "Raw Audio Out", &ss, NULL, NULL, NULL); + opts->pulse_raw_dev_out = pa_simple_new(NULL, "DSD FME", PA_STREAM_PLAYBACK, NULL, "Raw Audio Out", &ss, NULL, NULL, NULL); } //tt @@ -64,7 +64,7 @@ void openPulseInput(dsd_opts * opts) cc.rate = opts->pulse_digi_rate_in; //48000 //zz - if (opts->monitor_input_audio == 2) + if (opts->monitor_input_audio == 1) { //opts->pulse_raw_dev_in = pa_simple_new(NULL, "DSD FME", PA_STREAM_RECORD, NULL, "Raw Audio In", &zz, NULL, NULL, NULL); } diff --git a/src/dsd_dibit.c b/src/dsd_dibit.c index 7502861..872c030 100644 --- a/src/dsd_dibit.c +++ b/src/dsd_dibit.c @@ -444,6 +444,9 @@ get_dibit_and_analog_signal (dsd_opts* opts, dsd_state* state, int* out_analog_s // state->sbuf[state->sidx] = symbol; + // + //state->pulse_raw_out_buffer = symbol; + // if (out_analog_signal != NULL) { *out_analog_signal = symbol; diff --git a/src/dsd_frame.c b/src/dsd_frame.c index 5b1c47a..f33eb97 100644 --- a/src/dsd_frame.c +++ b/src/dsd_frame.c @@ -31,7 +31,7 @@ printFrameInfo (dsd_opts * opts, dsd_state * state) level = (int) state->max / 164; if (opts->verbose > 0) { - fprintf (stderr,"inlvl: %2i%% ", level); + //fprintf (stderr,"inlvl: %2i%% ", level); } if (state->nac != 0) { @@ -89,7 +89,7 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->verbose > 0) { level = (int) state->max / 164; - fprintf (stderr, "inlvl: %2i%% ", level); + //fprintf (stderr, "inlvl: %2i%% ", level); } } state->nac = 0; @@ -112,7 +112,7 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->verbose > 0) { level = (int) state->max / 164; - fprintf (stderr, "inlvl: %2i%% ", level); + //fprintf (stderr, "inlvl: %2i%% ", level); } } state->nac = 0; @@ -134,7 +134,7 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->verbose > 0) { level = (int) state->max / 164; - fprintf (stderr,"inlvl: %2i%% ", level); + //fprintf (stderr,"inlvl: %2i%% ", level); } } state->nac = 0; @@ -156,7 +156,7 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->verbose > 0) { level = (int) state->max / 164; - fprintf (stderr,"inlvl: %2i%% ", level); + //fprintf (stderr,"inlvl: %2i%% ", level); } } state->nac = 0; @@ -179,7 +179,7 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->verbose > 0) { level = (int) state->max / 164; - fprintf (stderr,"inlvl: %2i%% ", level); + //fprintf (stderr,"inlvl: %2i%% ", level); } } if ((state->synctype == 11) || (state->synctype == 12)) @@ -233,7 +233,7 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->verbose > 0) { level = (int) state->max / 164; - fprintf (stderr,"inlvl: %2i%% ", level); + //fprintf (stderr,"inlvl: %2i%% ", level); } } if ((opts->mbe_out_dir[0] != 0) && (opts->mbe_out_f == NULL)) @@ -264,7 +264,7 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->verbose > 0) { level = (int) state->max / 164; - fprintf(stderr, "inlvl: %2i%% ", level); + //fprintf(stderr, "inlvl: %2i%% ", level); } } state->nac = 0; diff --git a/src/dsd_frame_sync.c b/src/dsd_frame_sync.c index 7bfba83..6a88c55 100644 --- a/src/dsd_frame_sync.c +++ b/src/dsd_frame_sync.c @@ -108,7 +108,7 @@ printFrameSync (dsd_opts * opts, dsd_state * state, char *frametype, int offset, } if (opts->verbose > 1) { - fprintf (stderr,"mod: %s ", modulation); + //fprintf (stderr,"mod: %s ", modulation); //disabled, don't like looking at incorrect mod types anyways, and it eats up space on the console //printw("mod: %s ", modulation); } if (opts->verbose > 2) @@ -200,12 +200,6 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { ncursesPrinter(opts, state); } - /* - if ( opts->monitor_input_audio == 1 && (time(NULL) - now) > 1 ) //okay, still something going on, still doing the read part for some reason - { - playRawAudio(opts, state); //this is on line 21 in dsd_audio.c - } - */ /* if (opts->reset_state == 1 && state->carrier == 0) @@ -1126,10 +1120,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (opts->inverted_dmr == 0) { // data frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+DMR ", synctest_pos + 1, modulation); } state->lastsynctype = 10; return (10); @@ -1137,10 +1131,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) else { // inverted voice frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-DMR ", synctest_pos + 1, modulation); } if (state->lastsynctype != 11) { @@ -1161,10 +1155,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (opts->inverted_dmr == 0) { // data frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+DMR ", synctest_pos + 1, modulation); } state->lastsynctype = 10; return (10); @@ -1172,10 +1166,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) else { // inverted voice frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-DMR ", synctest_pos + 1, modulation); } if (state->lastsynctype != 11) { @@ -1196,10 +1190,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (opts->inverted_dmr == 0) { // data frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+DMR ", synctest_pos + 1, modulation); } state->lastsynctype = 10; return (10); @@ -1207,10 +1201,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) else { // inverted voice frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-DMR ", synctest_pos + 1, modulation); } if (state->lastsynctype != 11) { @@ -1230,10 +1224,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (opts->inverted_dmr == 0) { // voice frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+DMR ", synctest_pos + 1, modulation); } if (state->lastsynctype != 12) { @@ -1245,10 +1239,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) else { // inverted data frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-DMR ", synctest_pos + 1, modulation); } state->lastsynctype = 13; return (13); @@ -1265,10 +1259,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (opts->inverted_dmr == 0) { // voice frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+DMR ", synctest_pos + 1, modulation); } if (state->lastsynctype != 12) { @@ -1280,10 +1274,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) else { // inverted data frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-DMR ", synctest_pos + 1, modulation); } state->lastsynctype = 13; return (13); @@ -1300,10 +1294,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (opts->inverted_dmr == 0) { // voice frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+DMR ", synctest_pos + 1, modulation); } if (state->lastsynctype != 12) { @@ -1315,10 +1309,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) else { // inverted data frame - sprintf(state->ftype, " DMR "); + sprintf(state->ftype, "DMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -DMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-DMR ", synctest_pos + 1, modulation); } state->lastsynctype = 13; return (13); @@ -1711,7 +1705,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) sprintf (state->ftype, "(DMR)"); if (opts->errorbars == 1) { - printFrameSync (opts, state, "(-DMR) ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "(-DMR)", synctest_pos + 1, modulation); } state->lastsynctype = -1; if (opts->use_ncurses_terminal == 1) @@ -1726,10 +1720,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "(DMR) "); + sprintf (state->ftype, "(DMR) "); if (opts->errorbars == 1) { - printFrameSync (opts, state, "(+DMR) ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "(+DMR)", synctest_pos + 1, modulation); } state->lastsynctype = -1; if (opts->use_ncurses_terminal == 1) @@ -1778,7 +1772,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) return (-1); } } - + if ( opts->monitor_input_audio == 1 && (time(NULL) - now) > 1 ) //okay, still something going on, still doing the read part for some reason + //if ( opts->monitor_input_audio == 1) + { + playRawAudio(opts, state); //this is on line 21 in dsd_audio.c + } } return (-1); diff --git a/src/dsd_main.c b/src/dsd_main.c index 45a4090..f11fc00 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -191,11 +191,11 @@ initOpts (dsd_opts * opts) opts->rtl_udp_port = 6020; //set UDP port for RTL remote opts->rtl_bandwidth = 12; //changed recommended default to 12, 24 for ProVoice opts->pulse_raw_rate_in = 48000; - opts->pulse_raw_rate_out = 24000; //doing tests with 2 channels at 24000 for 48000 audio default in pulse + opts->pulse_raw_rate_out = 48000; //doing tests with 2 channels at 24000 for 48000 audio default in pulse opts->pulse_digi_rate_in = 48000; opts->pulse_digi_rate_out = 24000; //need to copy this to rtl type in and change rate out to 8000 opts->pulse_raw_in_channels = 1; - opts->pulse_raw_out_channels = 2; + opts->pulse_raw_out_channels = 1; opts->pulse_digi_in_channels = 1; //2 opts->pulse_digi_out_channels = 2; //2 //opts->output_name = "DSD-FME"; @@ -321,6 +321,13 @@ initState (dsd_state * state) state->dpmr_caller_id = 0; state->dpmr_target_id = 0; + state->payload_mi = 0; + state->payload_mfid = 0; + state->payload_algid = 0; + state->payload_keyid = 0; + + state->K = 0; + #ifdef TRACE_DSD state->debug_sample_index = 0; state->debug_label_file = NULL; @@ -650,7 +657,7 @@ main (int argc, char **argv) exitflag = 0; signal (SIGINT, sigfun); - while ((c = getopt (argc, argv, "haep:P:qstv:z:i:o:d:c:g:nw:B:C:R:f:m:u:x:A:S:M:G:D:L:V:U:Y:NWrlZ")) != -1) + while ((c = getopt (argc, argv, "haep:P:qstv:z:i:o:d:c:g:nw:B:C:R:f:m:u:x:A:S:M:G:D:L:V:U:Y:K:NWrlZ")) != -1) { opterr = 0; switch (c) @@ -713,6 +720,11 @@ main (int argc, char **argv) sscanf (optarg, "%d", &opts.verbose); break; + case 'K': + sscanf (optarg, "%d", &state.K); + state.K = ( ((state.K & 0xFF0F) << 32 ) + (state.K << 16) + state.K ); + break; + case 'G': //Set rtl device gain sscanf (optarg, "%d", &opts.rtl_gain_value); //multiple value by ten to make it consitent with the way rtl_fm really works break; @@ -738,10 +750,19 @@ main (int argc, char **argv) break; case 'W': //monitor_input_audio if no sync - opts.monitor_input_audio = 0; - //fprintf (stderr,"Monitor Source Audio Enabled (WIP!) Sounds bad.\n"); - //fprintf (stderr,"Monitor Source Audio may cause decoding issues.\n"); - fprintf (stderr,"Monitor Source Audio currently disabled in Pulse Audio Builds.\n"); + opts.monitor_input_audio = 1; + fprintf (stderr,"Monitor Source Audio Enabled.\n"); + //opts.pulse_raw_rate_in = 48000; + //opts.pulse_raw_rate_out = 48000; //doing tests with 2 channels at 24000 for 48000 audio default in pulse + //opts.pulse_digi_rate_in = 48000; + //opts.pulse_digi_rate_out = 48000; //need to copy this to rtl type in and change rate out to 8000 + //opts.pulse_raw_in_channels = 1; + //opts.pulse_raw_out_channels = 1; + //opts.pulse_digi_in_channels = 1; //2 + //opts.pulse_digi_out_channels = 1; //2 + fprintf (stderr," Monitor Source Audio may cause decoding issues.\n"); + fprintf (stderr," Don't Run Monitor Source Audio with Ncurses Terminal.\n"); + opts.use_ncurses_terminal = 0; //may not set depends on when -N and -W are first and second break; case 'N': diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index cd59917..f860a3f 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -31,6 +31,7 @@ int reset = 0; char c; //getch key int tg; //last tg +int tgn; int rd; //last rid int rn; //last ran int nc; //nac @@ -188,7 +189,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) call_matrix[9][1] = rn; call_matrix[9][2] = src; call_matrix[9][3] = 0; - call_matrix[9][4] = tg; + call_matrix[9][4] = tgn; call_matrix[9][5] = time(NULL); } @@ -278,12 +279,12 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (state->carrier == 1){ //figure out method that will tell me when is active and when not active, maybe carrier but this doesn't print anyways unless activity attron(COLOR_PAIR(3)); level = (int) state->max / 164; //only update on carrier present - reset = 1; + //reset = 1; } if (state->carrier == 0 && opts->reset_state == 1 && reset == 1) { - resetState (state); - reset = 0; + //resetState (state); + //reset = 0; } @@ -317,9 +318,9 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { rn = state->nxdn_last_ran; } - if (state->nxdn_last_tg > 0 && state->nxdn_last_tg != tg); + if (state->nxdn_last_tg > 0 && state->nxdn_last_tg != tgn); { - tg = state->nxdn_last_tg; + tgn = state->nxdn_last_tg; } //if (state->dmr_color_code > 0 && (lls == 10 || lls == 11 || lls == 12 || lls == 13) ) //DMR, DCC only carried on Data? @@ -334,22 +335,22 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (state->lastsrc > 0 && (lls == 12 || lls == 13 || lls == 0 || lls == 1)) //DMR Voice and P25P1 { rd = state->lastsrc; - opts->p25enc = 0; + //opts->p25enc = 0; } if (state->lasttg > 0 && (lls == 12 || lls == 13 || lls == 0 || lls == 1)) //DMR Voice and P25P1 { tg = state->lasttg; - opts->p25enc = 0; + //opts->p25enc = 0; } //if (state->lastsynctype == 8 || state->lastsynctype == 9 || state->lastsynctype == 16 || state->lastsynctype == 17) //change this to NXDN syncs later on if (lls == 8 || lls == 9 || lls == 16 || lls == 17) { printw ("| RAN: [%02d] ", rn); - printw ("TID: [%d] ", tg); + printw ("TID: [%04d] ", tgn); //printw ("| RID: [%d] \n", src); - printw ("RID: [%d] \n| ALG: [0x%02X] KEY [0x%02X] ", src, state->nxdn_cipher_type, state->nxdn_key); + printw ("RID: [%04d] \n| ALG: 0x[%02X] KEY 0x[%02X] ", src, state->nxdn_cipher_type, state->nxdn_key); if (state->carrier == 1) { printw("%s ", state->nxdn_call_type); @@ -408,8 +409,8 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { //printw("| TID:[%i] | RID:[%i] \n", tg, rd); //printw("| NAC: [0x%X] \n", nc); - printw("| TID:[%i] RID:[%i] ", tg, rd); - printw("NAC: [0x%X] \n", nc); + printw("| TID:[%08i] RID:[%08i] ", tg, rd); + printw("NAC: [0x%03X] \n", nc); printw("| ALG: [0x%02X] ", state->payload_algid); printw("KEY: [0x%04X] ", state->payload_keyid); //printw("MFG: [0x%X] ", state->payload_mfid); //no way of knowing if this is accurate info yet @@ -427,8 +428,34 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (lls == 12 || lls == 13) //DMR Voice Types { //printw ("| DCC: [%i] FID: [%02X]\n", dcc, state->dmr_fid); - printw ("| DCC: [%i] FID: [%02X] SOP: [%X] \n", dcc, state->dmr_fid, state->dmr_so); - printw ("| TID: [%i] RID: [%i]", tg, rd); + //attron(COLOR_PAIR(3)); + printw ("| DCC: [%02i] FID: [%02X] SOP: [%02X] ", dcc, state->dmr_fid, state->dmr_so); + if(state->payload_mi == 0 && state->dmr_so & 0x40) + { + attron(COLOR_PAIR(5)); + printw ("**BP** "); + //printw ("0x%X", state->payload_algid); + attroff(COLOR_PAIR(5)); + attron(COLOR_PAIR(3)); + } + if(state->payload_keyid > 0 && state->dmr_so & 0x40) + { + attron(COLOR_PAIR(5)); + printw (" ALG: [0x%02X] KEY [0x%02X] MI 0x[%08X]", state->payload_algid, state->payload_keyid, state->payload_mi); + //printw ("0x%X", state->payload_algid); + attroff(COLOR_PAIR(5)); + attron(COLOR_PAIR(3)); + } + if(state->K > 0 && state->dmr_so & 0x40) + { + attron(COLOR_PAIR(5)); + //printw ("K 0x[%12llX]", state->K); //seems to get reset in ncurses for some reason? some bigger issue perhaps? other strange issues occur too with provoice + attroff(COLOR_PAIR(5)); + attron(COLOR_PAIR(3)); + } + + printw("\n"); + printw ("| TID: [%08i] RID: [%08i]", tg, rd); if(state->dmr_so & 0x80) //1000 0000 { attron(COLOR_PAIR(2)); @@ -444,7 +471,11 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) //printw ("0x%X", state->payload_algid); attroff(COLOR_PAIR(2)); attron(COLOR_PAIR(3)); - opts->p25enc = 1; //just testing for now + //if (state->K = 0) + //{ + //opts->p25enc = 1; //just testing for now + //} + } if(state->dmr_so & 0x30) //0010 0000 @@ -479,8 +510,8 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (lls == 10 || lls == 11 ) //DMR Data Types { //printw ("| DCC: [%i]\n", dcc); - printw ("| DCC: [%i] FID: [%02X] SOP: [%X] \n", dcc, state->dmr_fid, state->dmr_so); - printw ("| TID: [%i] RID: [%i]", tg, rd); + printw ("| DCC: [%02i] FID: [%02X] SOP: [%02X] \n", dcc, state->dmr_fid, state->dmr_so); + printw ("| TID: [%08i] RID: [%08i]", tg, rd); //does this need to be in DATA type? /* if(state->dmr_so & 0x80) @@ -567,23 +598,23 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("| #%d %s ", j, SyncTypes[call_matrix[9-j][0]]); if (lls == 8 || lls == 9 || lls == 16 || lls == 17) { - printw ("RAN [%2d] ", call_matrix[9-j][1]); - printw ("TG [%d] ", call_matrix[9-j][4]); + printw ("RAN [%02d] ", call_matrix[9-j][1]); + printw ("TG [%04d] ", call_matrix[9-j][4]); } if (lls == 0 || lls == 1 || lls == 12 || lls == 13 || lls == 10 || lls == 11 ) //P25 P1 and DMR { - printw ("TID [%2d] ", call_matrix[9-j][1]); + printw ("TID [%08d] ", call_matrix[9-j][1]); } - printw ("RID [%4d] ", call_matrix[9-j][2]); + printw ("RID [%08d] ", call_matrix[9-j][2]); //printw ("S %d - ", call_matrix[j][3]); if (call_matrix[9-j][0] == 0 || call_matrix[9-j][0] == 1) //P25P1 Voice { - printw ("NAC [0x%X] ", call_matrix[9-j][4]); + printw ("NAC [0x%03X] ", call_matrix[9-j][4]); } if (call_matrix[9-j][0] == 12 || call_matrix[9-j][0] == 13 || call_matrix[9-j][0] == 10 || call_matrix[9-j][0] == 11 ) //DMR Voice Types { - printw ("DCC [%d] ", call_matrix[9-j][4]); + printw ("DCC [%02d] ", call_matrix[9-j][4]); } printw ("%d secs ago\n", time(NULL) - call_matrix[9-j][5]); } diff --git a/src/dsd_symbol.c b/src/dsd_symbol.c index 131cf2f..e132ce5 100644 --- a/src/dsd_symbol.c +++ b/src/dsd_symbol.c @@ -111,6 +111,7 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync) #ifdef USE_RTLSDR // TODO: need to read demodulated stream here get_rtlsdr_sample(&sample); + //state->pulse_raw_out_buffer = sample; #endif } diff --git a/src/nxdn_lib.c b/src/nxdn_lib.c index 6ed5f45..f3a1e1c 100644 --- a/src/nxdn_lib.c +++ b/src/nxdn_lib.c @@ -736,15 +736,17 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) /* Print the "Call Type" */ //fprintf(stderr, "%s - ", NXDN_Call_Type_To_Str(CallType)); fprintf(stderr, "\n\t %s - ", NXDN_Call_Type_To_Str(CallType)); //line break 1 tab, 2 spaces start this string - sprintf (state->nxdn_call_type, NXDN_Call_Type_To_Str(CallType)); + sprintf (state->nxdn_call_type, NXDN_Call_Type_To_Str(CallType)); //fix warning below + //warning: format not a string literal and no format arguments [-Wformat-security] + //state->nxdn_call_type = NXDN_Call_Type_To_Str(CallType); /* Print the "Voice Call Option" */ NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode); fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode); - state->nxdn_key = (KeyID & 0xFF); - state->nxdn_cipher_type = CipherType; + //state->nxdn_key = (KeyID & 0xFF); + //state->nxdn_cipher_type = CipherType; /* Print the "Cipher Type" */ if(CipherType != 0) { @@ -761,13 +763,16 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) /* Print Source ID and Destination ID (Talk Group or Unit ID) */ fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF); - state->nxdn_last_tg = (DestinationID & 0xFFFF); + if(state->NxdnElementsContent.VCallCrcIsGood) { if ( (SourceUnitID & 0xFFFF) > 0 ) { - state->nxdn_last_rid = SourceUnitID & 0xFFFF; //only grab if CRC is okay + state->nxdn_last_rid = SourceUnitID & 0xFFFF; //only grab if CRC is okay + state->nxdn_last_tg = (DestinationID & 0xFFFF); + state->nxdn_key = (KeyID & 0xFF); + state->nxdn_cipher_type = CipherType; } fprintf(stderr, " (OK) - "); } From 184b8b6a39be19a4029652af4549a53807f065e6 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sun, 13 Mar 2022 19:13:09 -0400 Subject: [PATCH 08/12] Enable D-Star, Pulse Tweaks, NCurses Tweaks, Misc Enable D-Star, Pulse Tweaks, NCurses Tweaks, Misc --- src/dmr_sync.c | 18 ++-- src/dpmr_voice.c | 85 ++++----------- src/dsd_frame.c | 3 +- src/dsd_frame_sync.c | 240 +++++++++++++++++++++---------------------- src/dsd_main.c | 18 ++-- src/dsd_ncurses.c | 36 ++++--- src/dstar_header.c | 1 + src/nxdn_lib.c | 2 +- src/p25p1_hdu.c | 6 +- src/p25p1_ldu2.c | 6 +- 10 files changed, 187 insertions(+), 228 deletions(-) diff --git a/src/dmr_sync.c b/src/dmr_sync.c index 1f7b338..570581e 100644 --- a/src/dmr_sync.c +++ b/src/dmr_sync.c @@ -87,6 +87,8 @@ void ProcessDmrPIHeader(dsd_opts * opts, dsd_state * state, uint8_t info[196], u CRCExtracted = CRCExtracted | (uint32_t)(DmrDataBit[i + 80] & 1); //80-96 for PI header } + //Look into whether or not we need to run these CRC checks for this header information + //and see if its applied the same or differently /* Apply the CRC mask (see DMR standard B.3.12 Data Type CRC Mask) */ //CRCExtracted = CRCExtracted ^ 0x969696; //does this mask get applied here though for PI? //CRCExtracted = CRCExtracted ^ 0x6969; @@ -107,21 +109,15 @@ void ProcessDmrPIHeader(dsd_opts * opts, dsd_state * state, uint8_t info[196], u DmrDataBit[j + 6] = (DmrDataByte[i] >> 1) & 0x01; DmrDataBit[j + 7] = (DmrDataByte[i] >> 0) & 0x01; } - //Placeholder, figure out which areas to grab - //state->payload_algid = DmrDataBit[1]; - state->payload_algid = DmrDataByte[0]; //not really sure, just guessing on observation? - //state->payload_keyid = DmrDataBit[2]; + + + state->payload_algid = DmrDataByte[0]; state->payload_keyid = DmrDataByte[2]; - //state->payload_mi = ( (DmrDataBit[3] << 3) + (DmrDataBit[4] << 2) + (DmrDataBit[5] << 1) + DmrDataBit[6] ); state->payload_mi = ( ((DmrDataByte[3]) << 24) + ((DmrDataByte[4]) << 16) + ((DmrDataByte[5]) << 8) + (DmrDataByte[6]) ); - //fprintf(stderr, "%s Slot(%d), CC(%x), PI HEADER: ALGID(%02x), KEYID(%02x), MI(%08x), DSTADDR(%06x)\n", - fprintf (stderr, "\n DMR PI Header ALG ID: 0x%02X KEY ID: 0x%02X MI: 0x%08X \n", state->payload_algid, state->payload_keyid, state->payload_mi); - fprintf (stderr, " Full PI Header Payload in Hex\n"); - for(i = 0, j = 0; i < 12; i++) + if (opts->payload == 1) { - //fprintf (stderr, " Byte [%02d] [%02X] [%02X]\n", i+1, DmrDataByte[i], DmrDataByte[i] ^ 0x69); //0x6969 PI header mask? + fprintf (stderr, "\n DMR PI Header ALG ID: 0x%02X KEY ID: 0x%02X MI: 0x%08X", state->payload_algid, state->payload_keyid, state->payload_mi); } - //end Placeholder } void ProcessDmrVoiceLcHeader(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t syncdata[48], uint8_t SlotType[20]) diff --git a/src/dpmr_voice.c b/src/dpmr_voice.c index eaf935f..11d76a5 100644 --- a/src/dpmr_voice.c +++ b/src/dpmr_voice.c @@ -70,7 +70,7 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) if (opts->errorbars == 1) { - fprintf(stderr, "VOICE e:"); + //fprintf(stderr, "VOICE e:"); } /* First CCH (Control CHannel) - 72 bit */ @@ -131,7 +131,7 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) z++; } - //processMbeFrame (opts, state, NULL, ambe_fr2, NULL); //HERE HERE + //processMbeFrame (opts, state, NULL, ambe_fr[j], NULL); //HERE HERE #ifdef DPMR_DUMP fprintf(stderr, " "); @@ -225,7 +225,7 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) y++; z++; } - //processMbeFrame (opts, state, NULL, ambe_fr2, NULL); + //processMbeFrame (opts, state, NULL, ambe_fr[j], NULL); #ifdef DPMR_DUMP fprintf(stderr, " "); @@ -523,7 +523,7 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) //fprintf(stderr, "| TG=%s", CalledID); if(state->dPMRVoiceFS2Frame.CalledIDOk) { - fprintf(stderr, "| TG=%s", CalledID); + fprintf(stderr, "\n| TG=%s", CalledID); //strcpy (state->dpmr_target_id, CalledID); //HERE HERE state->dpmr_target_id = CalledID; //fprintf(stderr, " (CRC OK) "); @@ -631,70 +631,25 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) if(VoiceFrameFlag) { /* There is 4 AMBE voice sample per voice frame (= 8 per superframe) */ - //for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) - for(i = 0; i < (8); i++) + for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) + //for(i = 0; i < (8); i++) { /* Apply ECC 1 to the AMBE frames */ state->dPMRVoiceFS2Frame.errs1[i] = (unsigned int)mbe_eccAmbe3600x2450C0(ambe_fr[i]); state->dPMRVoiceFS2Frame.errs2[i] = state->dPMRVoiceFS2Frame.errs1[i]; /* Demodulate the AMBE frames */ - mbe_demodulateAmbe3600x2450Data(ambe_fr[i]); - //mbe_demodulateAmbe3600x2400Data(ambe_fr[i]); + //mbe_demodulateAmbe3600x2450Data(ambe_fr[i]); + mbe_demodulateAmbe3600x2400Data(ambe_fr[i]); /* Apply ECC 2 to the AMBE frames and get the 49 bit of the voice sample */ - state->dPMRVoiceFS2Frame.errs2[i] += (unsigned int)mbe_eccAmbe3600x2450Data(ambe_fr[i], (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); + //state->dPMRVoiceFS2Frame.errs2[i] += (unsigned int)mbe_eccAmbe3600x2450Data(ambe_fr[i], (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); + state->dPMRVoiceFS2Frame.errs2[i] += (unsigned int)mbe_eccAmbe3600x2400Data(ambe_fr[i], (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); //processMbeFrame (opts, state, NULL, ambe_fr[i], NULL); //HERE HERE //processMbeFrame (opts, state, NULL, state->dPMRVoiceFS2Frame.AmbeBit[i], NULL); //HERE HERE } } /* End if(VoiceFrameFlag && !AttachedDataFlag) */ - //for 1 to 8 and then VoiceFrameFlag - /* - for(i = 0; i < (8); i++) - { - if(VoiceFrameFlag) - { - //processMbeFrame (opts, state, NULL, ambe_fr[i], NULL); //HERE HERE - // - errs = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs1[i]); - errs2 = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs2[i]); - state->errs = state->dPMRVoiceFS2Frame.errs1[i]; - state->errs2 = state->dPMRVoiceFS2Frame.errs2[i]; - - //processMbeFrame (opts, state, NULL, (char *)state->dPMRVoiceFS2Frame.AmbeBit[i], NULL); //HERE HERE - - mbe_processAmbe2450Dataf (state->audio_out_temp_buf, errs, errs2, state->err_str, - state->dPMRVoiceFS2Frame.AmbeBit[i], - state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); - - if (opts->mbe_out_f != NULL) - { - //saveAmbe2450Data (opts, state, (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); - } - if (opts->errorbars == 1) - { - fprintf(stderr, "%s", state->err_str); - } - - state->debug_audio_errors += *errs2; - - processAudio(opts, state); - - if (opts->wav_out_f != NULL) - { - writeSynthesizedVoice (opts, state); - } - - if (opts->audio_out == 1) - { - //Play the AMBE Frames - playSynthesizedVoice (opts, state); - } - } - } - */ - //Check if the voice frame to play is encrypted if(opts->dPMR_curr_frame_is_encrypted) { @@ -721,31 +676,29 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) //is this backwards, shouldn't we run this first, and then voice frame after? { - errs = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs1[i]); + //errs = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs1[i]); errs2 = (uint32_t*)&(state->dPMRVoiceFS2Frame.errs2[i]); + errs = 0; + //errs2 = 0; state->errs = state->dPMRVoiceFS2Frame.errs1[i]; state->errs2 = state->dPMRVoiceFS2Frame.errs2[i]; - // - //processMbeFrame (opts, state, NULL, ambe_fr2, NULL); //sounds like choppy shit, but .amb file playback is acceptable, so investigate why this doesn't work properly - mbe_processAmbe2450Dataf (state->audio_out_temp_buf, errs, errs2, state->err_str, - state->dPMRVoiceFS2Frame.AmbeBit[i], + //could be related to err states not properly done or something? + mbe_processAmbe2450Dataf (state->audio_out_temp_buf, (int *)errs, (int *)errs2, state->err_str, + (char *)state->dPMRVoiceFS2Frame.AmbeBit[i], state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); - //mbe_processAmbe2450Dataf (state->audio_out_temp_buf_p, (int *)errs, (int *)errs2, state->err_str, - // (char *)state->dPMRVoiceFS2Frame.AmbeBit[i], - // state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); if (opts->mbe_out_f != NULL) { saveAmbe2450Data (opts, state, (char *)state->dPMRVoiceFS2Frame.AmbeBit[i]); } if (opts->errorbars == 1) { - fprintf(stderr, "%s", state->err_str); + //fprintf(stderr, "%s", state->err_str); } - state->debug_audio_errors += *errs2; + //state->debug_audio_errors += *errs2; processAudio(opts, state); @@ -756,7 +709,7 @@ void processdPMRvoice (dsd_opts * opts, dsd_state * state) if (opts->audio_out == 1) { - //Play the AMBE Frames + //Play the AMBE Frames playSynthesizedVoice (opts, state); } } //End for(i = 0; i < (NB_OF_DPMR_VOICE_FRAME_TO_DECODE * 4); i++) diff --git a/src/dsd_frame.c b/src/dsd_frame.c index f33eb97..da7b8a2 100644 --- a/src/dsd_frame.c +++ b/src/dsd_frame.c @@ -609,7 +609,8 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->errorbars == 1) { printFrameInfo (opts, state); - fprintf (stderr," duid:%s *Unknown DUID*\n", duid); + //fprintf (stderr," duid:%s *Unknown DUID*\n", duid); //prints on dPMR frame 3 + fprintf (stderr, "\n"); //prints on dPMR frame 3 } } } diff --git a/src/dsd_frame_sync.c b/src/dsd_frame_sync.c index 6a88c55..250d602 100644 --- a/src/dsd_frame_sync.c +++ b/src/dsd_frame_sync.c @@ -425,7 +425,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) sprintf (state->ftype, "P25 Phase 1"); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +P25p1 ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+P25p1 ", synctest_pos + 1, modulation); } state->lastsynctype = 0; return (0); @@ -440,7 +440,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) sprintf (state->ftype, "P25 Phase 1"); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -P25p1 ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-P25p1 ", synctest_pos + 1, modulation); } state->lastsynctype = 1; return (1); @@ -461,7 +461,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) sprintf (state->ftype, "X2-TDMA"); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +X2-TDMA ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+X2-TDMA ", synctest_pos + 1, modulation); } state->lastsynctype = 2; return (2); @@ -472,7 +472,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) sprintf (state->ftype, "X2-TDMA"); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -X2-TDMA ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-X2-TDMA ", synctest_pos + 1, modulation); } if (state->lastsynctype != 3) { @@ -495,7 +495,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) sprintf (state->ftype, "X2-TDMA"); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +X2-TDMA ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+X2-TDMA ", synctest_pos + 1, modulation); } if (state->lastsynctype != 4) { @@ -510,7 +510,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) sprintf (state->ftype, "X2-TDMA"); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -X2-TDMA ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-X2-TDMA ", synctest_pos + 1, modulation); } state->lastsynctype = 5; return (5); @@ -606,17 +606,17 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "DPMR_FRAME_SYNC_1\n"); // TODO : To be removed - //if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || - // (state->lastsynctype == 22) || (state->lastsynctype == 23)) - //{ + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf(state->ftype, " dPMR "); + sprintf(state->ftype, "dPMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+dPMR ", synctest_pos + 1, modulation); } /* The next part of the superframe will normally be the first part */ @@ -624,11 +624,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->lastsynctype = 20; return (20); - //} - //else - //{ - // state->lastsynctype = 20; - //} + } + else + { + state->lastsynctype = 20; + } } /* End if (opts->inverted_dpmr == 0) */ } else if(strcmp(synctest12, DPMR_FRAME_SYNC_2) == 0) @@ -638,28 +638,28 @@ getFrameSync (dsd_opts * opts, dsd_state * state) //fprintf(stderr, "DPMR_FRAME_SYNC_2\n"); // TODO : To be removed // TODO : Modif 2019-01-04 - //if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || - // (state->lastsynctype == 22) || (state->lastsynctype == 23)) - //{ + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf(state->ftype, " dPMR "); + sprintf(state->ftype, "dPMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+dPMR ", synctest_pos + 1, modulation); } state->lastsynctype = 21; return (21); // TODO : Modif 2019-01-04 - //} - //else - //{ - // state->lastsynctype = 21; - //} + } + else + { + state->lastsynctype = 21; + } } } else if(strcmp(synctest12, DPMR_FRAME_SYNC_3) == 0) @@ -668,17 +668,17 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "DPMR_FRAME_SYNC_3\n"); // TODO : To be removed - //if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || - // (state->lastsynctype == 22) || (state->lastsynctype == 23)) - //{ + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf(state->ftype, " dPMR "); + sprintf(state->ftype, "dPMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+dPMR ", synctest_pos + 1, modulation); } /* The next part of the superframe will normally be the first part */ @@ -686,11 +686,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->lastsynctype = 22; return (22); - //} - //else - //{ - // state->lastsynctype = 22; - //} + } + else + { + state->lastsynctype = 22; + } } } if(strcmp(synctest, DPMR_FRAME_SYNC_4) == 0) @@ -699,17 +699,17 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "DPMR_FRAME_SYNC_4\n"); // TODO : To be removed - //if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || - // (state->lastsynctype == 22) || (state->lastsynctype == 23)) - //{ + if ((state->lastsynctype == 20) || (state->lastsynctype == 21) || + (state->lastsynctype == 22) || (state->lastsynctype == 23)) + { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf(state->ftype, " dPMR "); + sprintf(state->ftype, "dPMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +dPMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+dPMR ", synctest_pos + 1, modulation); } /* The next part of the superframe will normally be the first part */ @@ -717,11 +717,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->lastsynctype = 23; return (23); - //} - //else - //{ - // state->lastsynctype = 23; - //} + } + else + { + state->lastsynctype = 23; + } } } else if(strcmp(synctest, INV_DPMR_FRAME_SYNC_1) == 0) @@ -729,25 +729,25 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (opts->inverted_dpmr) { //fprintf(stderr, "INV_DPMR_FRAME_SYNC_1\n"); // TODO : To be removed - //if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || - // (state->lastsynctype == 26) || (state->lastsynctype == 27)) - //{ + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf(state->ftype, " dPMR "); + sprintf(state->ftype, "dPMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-dPMR ", synctest_pos + 1, modulation); } state->lastsynctype = 24; return (24); - //} - //else - //{ - // state->lastsynctype = 24; - //} + } + else + { + state->lastsynctype = 24; + } } } else if(strcmp(synctest12, INV_DPMR_FRAME_SYNC_2) == 0) @@ -756,25 +756,25 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "DPMR_FRAME_SYNC_2\n"); // TODO : To be removed - //if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || - // (state->lastsynctype == 26) || (state->lastsynctype == 27)) - //{ + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf(state->ftype, " dPMR "); + sprintf(state->ftype, "dPMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-dPMR ", synctest_pos + 1, modulation); } state->lastsynctype = 25; return (25); - //} - //else - //{ - // state->lastsynctype = 25; - //} + } + else + { + state->lastsynctype = 25; + } } } else if(strcmp(synctest12, INV_DPMR_FRAME_SYNC_3) == 0) @@ -783,25 +783,25 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "INV_DPMR_FRAME_SYNC_3\n"); // TODO : To be removed - //if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || - // (state->lastsynctype == 26) || (state->lastsynctype == 27)) - //{ + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf(state->ftype, " dPMR "); + sprintf(state->ftype, "dPMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-dPMR ", synctest_pos + 1, modulation); } state->lastsynctype = 26; return (26); - //} - //else - //{ - // state->lastsynctype = 26; - //} + } + else + { + state->lastsynctype = 26; + } } } if(strcmp(synctest, INV_DPMR_FRAME_SYNC_4) == 0) @@ -810,25 +810,25 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { //fprintf(stderr, "INV_DPMR_FRAME_SYNC_4\n"); // TODO : To be removed - //if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || - // (state->lastsynctype == 26) || (state->lastsynctype == 27)) - //{ + if ((state->lastsynctype == 24) || (state->lastsynctype == 25) || + (state->lastsynctype == 26) || (state->lastsynctype == 27)) + { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf(state->ftype, " dPMR "); + sprintf(state->ftype, "dPMR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -dPMR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-dPMR ", synctest_pos + 1, modulation); } state->lastsynctype = 27; return (27); - //} - //else - //{ - // state->lastsynctype = 27; - //} + } + else + { + state->lastsynctype = 27; + } } } else @@ -1331,11 +1331,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "ProVoice"); + sprintf (state->ftype, "ProVoice "); if (opts->errorbars == 1) //if (opts->errorbars == 1 && (time(NULL) - now) > 2 ) { - printFrameSync (opts, state, " -ProVoice ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-ProVoice ", synctest_pos + 1, modulation); } state->lastsynctype = 14; if (opts->use_ncurses_terminal == 1) @@ -1351,10 +1351,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "ProVoice"); + sprintf (state->ftype, "ProVoice "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -ProVoice ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-ProVoice ", synctest_pos + 1, modulation); } state->lastsynctype = 15; if (opts->use_ncurses_terminal == 1) @@ -1384,11 +1384,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (state->samplesPerSymbol == 20) { //sprintf (state->ftype, " NXDN48 "); - sprintf (state->ftype, "NXDN48"); //get rid of spaces + sprintf (state->ftype, "NXDN48 "); //get rid of spaces if (opts->errorbars == 1) { //printFrameSync (opts, state, " +NXDN48 ", synctest_pos + 1, modulation); - printFrameSync (opts, state, " +NXDN48", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+NXDN48 ", synctest_pos + 1, modulation); } } else @@ -1427,7 +1427,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (state->samplesPerSymbol == 20) { //sprintf (state->ftype, " NXDN48 "); - sprintf (state->ftype, "NXDN48"); + sprintf (state->ftype, "NXDN48 "); if (opts->errorbars == 1) { //printFrameSync (opts, state, " -NXDN48 ", synctest_pos + 1, modulation); @@ -1436,10 +1436,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } else { - sprintf (state->ftype, "NXDN96"); + sprintf (state->ftype, "NXDN96 "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -NXDN96 ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-NXDN96 ", synctest_pos + 1, modulation); } } state->lastsynctype = 9; @@ -1468,19 +1468,19 @@ getFrameSync (dsd_opts * opts, dsd_state * state) if (state->samplesPerSymbol == 20) { //sprintf (state->ftype, " NXDN48 "); - sprintf (state->ftype, "NXDN48"); + sprintf (state->ftype, " NXDN48"); if (opts->errorbars == 1) { //printFrameSync (opts, state, " +NXDN48 ", synctest_pos + 1, modulation); - printFrameSync (opts, state, " +NXDN48", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+NXDN48 ", synctest_pos + 1, modulation); } } else { - sprintf (state->ftype, "NXDN96"); + sprintf (state->ftype, "NXDN96 "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +NXDN96 ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+NXDN96 ", synctest_pos + 1, modulation); } } state->lastsynctype = 16; @@ -1507,22 +1507,22 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; //sprintf (state->ftype, " NXDN "); - sprintf (state->ftype, "NXDN"); + sprintf (state->ftype, "NXDN "); if (state->samplesPerSymbol == 20) { - sprintf (state->ftype, "NXDN48"); + sprintf (state->ftype, "NXDN48 "); //sprintf (state->ftype, "NXDN48"); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -NXDN48 ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-NXDN48 ", synctest_pos + 1, modulation); } } else { - sprintf (state->ftype, "NXDN96"); + sprintf (state->ftype, "NXDN96 "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -NXDN96 ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-NXDN96 ", synctest_pos + 1, modulation); } } state->lastsynctype = 17; @@ -1549,10 +1549,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "D-STAR"); + sprintf (state->ftype, "D-STAR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +D-STAR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+D-STAR ", synctest_pos + 1, modulation); } state->lastsynctype = 6; if (opts->use_ncurses_terminal == 1) @@ -1568,10 +1568,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "D-STAR"); + sprintf (state->ftype, "D-STAR "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -D-STAR ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-D-STAR ", synctest_pos + 1, modulation); } state->lastsynctype = 7; if (opts->use_ncurses_terminal == 1) @@ -1587,10 +1587,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "D-STAR_HD"); + sprintf (state->ftype, "D-STAR_HD "); if (opts->errorbars == 1) { - printFrameSync (opts, state, " +D-STAR_HD ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "+D-STAR_HD ", synctest_pos + 1, modulation); } state->lastsynctype = 18; if (opts->use_ncurses_terminal == 1) @@ -1606,10 +1606,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "D-STAR_HD"); + sprintf (state->ftype, " D-STAR_HD"); if (opts->errorbars == 1) { - printFrameSync (opts, state, " -D-STAR_HD ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "-D-STAR_HD ", synctest_pos + 1, modulation); } state->lastsynctype = 19; if (opts->use_ncurses_terminal == 1) @@ -1629,10 +1629,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + (lmax)) / 2; state->min = ((state->min) + (lmin)) / 2; - sprintf (state->ftype, "(P25 Phase 1)"); + sprintf (state->ftype, "(P25 Phase 1) "); if (opts->errorbars == 1) { - printFrameSync (opts, state, "(+P25p1) ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "(+P25p1) ", synctest_pos + 1, modulation); } state->lastsynctype = -1; //do we want ncursesPrinter here?? @@ -1648,10 +1648,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "(P25 Phase 1)"); + sprintf (state->ftype, "(P25 Phase 1) "); if (opts->errorbars == 1) { - printFrameSync (opts, state, "(-P25p1) ", synctest_pos + 1, modulation); + printFrameSync (opts, state, "(-P25p1) ", synctest_pos + 1, modulation); } state->lastsynctype = -1; if (opts->use_ncurses_terminal == 1) @@ -1666,7 +1666,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "(X2-TDMA) "); + sprintf (state->ftype, "(X2-TDMA) "); if (opts->errorbars == 1) { printFrameSync (opts, state, "(-X2-TDMA) ", synctest_pos + 1, modulation); @@ -1702,10 +1702,10 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "(DMR)"); + sprintf (state->ftype, "(DMR) "); if (opts->errorbars == 1) { - printFrameSync (opts, state, "(-DMR)", synctest_pos + 1, modulation); + printFrameSync (opts, state, "(-DMR) ", synctest_pos + 1, modulation); } state->lastsynctype = -1; if (opts->use_ncurses_terminal == 1) @@ -1723,7 +1723,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) sprintf (state->ftype, "(DMR) "); if (opts->errorbars == 1) { - printFrameSync (opts, state, "(+DMR)", synctest_pos + 1, modulation); + printFrameSync (opts, state, "(+DMR) ", synctest_pos + 1, modulation); } state->lastsynctype = -1; if (opts->use_ncurses_terminal == 1) diff --git a/src/dsd_main.c b/src/dsd_main.c index f11fc00..6bc63fc 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -163,7 +163,7 @@ initOpts (dsd_opts * opts) opts->serial_baud = 115200; sprintf (opts->serial_dev, "/dev/ttyUSB0"); opts->resume = 0; - opts->frame_dstar = 0; + opts->frame_dstar = 1; //was disabled for some reason, make sure there are no false positives opts->frame_x2tdma = 1; opts->frame_p25p1 = 1; opts->frame_nxdn48 = 0; @@ -193,16 +193,16 @@ initOpts (dsd_opts * opts) opts->pulse_raw_rate_in = 48000; opts->pulse_raw_rate_out = 48000; //doing tests with 2 channels at 24000 for 48000 audio default in pulse opts->pulse_digi_rate_in = 48000; - opts->pulse_digi_rate_out = 24000; //need to copy this to rtl type in and change rate out to 8000 + opts->pulse_digi_rate_out = 48000; //need to copy this to rtl type in and change rate out to 8000 opts->pulse_raw_in_channels = 1; opts->pulse_raw_out_channels = 1; opts->pulse_digi_in_channels = 1; //2 - opts->pulse_digi_out_channels = 2; //2 - //opts->output_name = "DSD-FME"; - sprintf (opts->output_name, " "); + opts->pulse_digi_out_channels = 1; //2 + + sprintf (opts->output_name, "Auto Detect"); opts->pulse_flush = 1; //set 0 to flush, 1 for flushed - opts->use_ncurses_terminal = 0; //ncurses terminal disabled by default, call with -N - opts->payload = 0; //initialize with 0 so can pass == 0 arguments + opts->use_ncurses_terminal = 0; + opts->payload = 0; opts->EncryptionMode = MODE_UNENCRYPTED; opts->inverted_dpmr = 0; @@ -720,8 +720,8 @@ main (int argc, char **argv) sscanf (optarg, "%d", &opts.verbose); break; - case 'K': - sscanf (optarg, "%d", &state.K); + case 'K': + sscanf (optarg, "%lld", &state.K); state.K = ( ((state.K & 0xFF0F) << 32 ) + (state.K << 16) + state.K ); break; diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index f860a3f..63ff114 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -253,7 +253,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } if (opts->audio_in_type == 3) { - printw ("| RTL2838 Device #[%d]", opts->rtl_dev_index); + printw ("| RTL2838UHIDIR Device #[%d]", opts->rtl_dev_index); printw (" Gain [%i] dB -", opts->rtl_gain_value); printw (" Squelch [%i]", opts->rtl_squelch_level); printw (" VFO [%i] kHz\n", opts->rtl_bandwidth); @@ -409,8 +409,8 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { //printw("| TID:[%i] | RID:[%i] \n", tg, rd); //printw("| NAC: [0x%X] \n", nc); - printw("| TID:[%08i] RID:[%08i] ", tg, rd); - printw("NAC: [0x%03X] \n", nc); + printw("| TID:[%8i] RID:[%8i] ", tg, rd); + printw("NAC: [0x%3X] \n", nc); printw("| ALG: [0x%02X] ", state->payload_algid); printw("KEY: [0x%04X] ", state->payload_keyid); //printw("MFG: [0x%X] ", state->payload_mfid); //no way of knowing if this is accurate info yet @@ -429,7 +429,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { //printw ("| DCC: [%i] FID: [%02X]\n", dcc, state->dmr_fid); //attron(COLOR_PAIR(3)); - printw ("| DCC: [%02i] FID: [%02X] SOP: [%02X] ", dcc, state->dmr_fid, state->dmr_so); + printw ("| DCC: [%2i] FID: [%02X] SOP: [%02X] ", dcc, state->dmr_fid, state->dmr_so); if(state->payload_mi == 0 && state->dmr_so & 0x40) { attron(COLOR_PAIR(5)); @@ -441,7 +441,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if(state->payload_keyid > 0 && state->dmr_so & 0x40) { attron(COLOR_PAIR(5)); - printw (" ALG: [0x%02X] KEY [0x%02X] MI 0x[%08X]", state->payload_algid, state->payload_keyid, state->payload_mi); + printw (" ALG: [0x%02X] KEY [0x%02X] MI [0x%08X]", state->payload_algid, state->payload_keyid, state->payload_mi); //printw ("0x%X", state->payload_algid); attroff(COLOR_PAIR(5)); attron(COLOR_PAIR(3)); @@ -455,7 +455,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } printw("\n"); - printw ("| TID: [%08i] RID: [%08i]", tg, rd); + printw ("| TID: [%8i] RID: [%8i]", tg, rd); if(state->dmr_so & 0x80) //1000 0000 { attron(COLOR_PAIR(2)); @@ -510,8 +510,8 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (lls == 10 || lls == 11 ) //DMR Data Types { //printw ("| DCC: [%i]\n", dcc); - printw ("| DCC: [%02i] FID: [%02X] SOP: [%02X] \n", dcc, state->dmr_fid, state->dmr_so); - printw ("| TID: [%08i] RID: [%08i]", tg, rd); + printw ("| DCC: [%2i] FID: [%02X] SOP: [%02X] \n", dcc, state->dmr_fid, state->dmr_so); + printw ("| TID: [%8i] RID: [%8i]", tg, rd); //does this need to be in DATA type? /* if(state->dmr_so & 0x80) @@ -598,23 +598,31 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("| #%d %s ", j, SyncTypes[call_matrix[9-j][0]]); if (lls == 8 || lls == 9 || lls == 16 || lls == 17) { - printw ("RAN [%02d] ", call_matrix[9-j][1]); - printw ("TG [%04d] ", call_matrix[9-j][4]); + printw ("RAN [%2d] ", call_matrix[9-j][1]); + printw ("TG [%4d] ", call_matrix[9-j][4]); + printw ("RID [%4d] ", call_matrix[9-j][2]); } if (lls == 0 || lls == 1 || lls == 12 || lls == 13 || lls == 10 || lls == 11 ) //P25 P1 and DMR { - printw ("TID [%08d] ", call_matrix[9-j][1]); + printw ("TID [%8d] ", call_matrix[9-j][1]); + printw ("RID [%8d] ", call_matrix[9-j][2]); + } + if (lls == 20 || lls == 21 || lls == 22 || lls == 23 ||lls == 24 || lls == 25 || lls == 26 || lls == 27) //dPMR + { + printw ("TID [%8d] ", call_matrix[9-j][1]); + printw ("RID [%8d] ", call_matrix[9-j][2]); + printw ("DCC [%2d] ", call_matrix[9-j][4]); } - printw ("RID [%08d] ", call_matrix[9-j][2]); + //printw ("RID [%08d] ", call_matrix[9-j][2]); //printw ("S %d - ", call_matrix[j][3]); if (call_matrix[9-j][0] == 0 || call_matrix[9-j][0] == 1) //P25P1 Voice { - printw ("NAC [0x%03X] ", call_matrix[9-j][4]); + printw ("NAC [0x%3X] ", call_matrix[9-j][4]); } if (call_matrix[9-j][0] == 12 || call_matrix[9-j][0] == 13 || call_matrix[9-j][0] == 10 || call_matrix[9-j][0] == 11 ) //DMR Voice Types { - printw ("DCC [%02d] ", call_matrix[9-j][4]); + printw ("DCC [%2d] ", call_matrix[9-j][4]); } printw ("%d secs ago\n", time(NULL) - call_matrix[9-j][5]); } diff --git a/src/dstar_header.c b/src/dstar_header.c index 77ad1bd..204ee7b 100644 --- a/src/dstar_header.c +++ b/src/dstar_header.c @@ -65,6 +65,7 @@ void dstar_header_decode(int radioheaderbuffer[660]) { radioheader[28], radioheader[29], radioheader[30], radioheader[31], radioheader[32], radioheader[33], radioheader[34], radioheader[35], radioheader[36], radioheader[37], radioheader[38]); + //FCSinheader = ((radioheader[39] << 8) | radioheader[40]) & 0xFFFF; //FCScalculated = calc_fcs((unsigned char*) radioheader, 39); //fprintf (stderr, "Check sum = %04X ", FCSinheader); diff --git a/src/nxdn_lib.c b/src/nxdn_lib.c index f3a1e1c..990a0aa 100644 --- a/src/nxdn_lib.c +++ b/src/nxdn_lib.c @@ -735,7 +735,7 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) /* Print the "Call Type" */ //fprintf(stderr, "%s - ", NXDN_Call_Type_To_Str(CallType)); - fprintf(stderr, "\n\t %s - ", NXDN_Call_Type_To_Str(CallType)); //line break 1 tab, 2 spaces start this string + fprintf(stderr, "\n %s - ", NXDN_Call_Type_To_Str(CallType)); //line break and 1 space sprintf (state->nxdn_call_type, NXDN_Call_Type_To_Str(CallType)); //fix warning below //warning: format not a string literal and no format arguments [-Wformat-security] diff --git a/src/p25p1_hdu.c b/src/p25p1_hdu.c index a535456..60345a5 100644 --- a/src/p25p1_hdu.c +++ b/src/p25p1_hdu.c @@ -467,11 +467,11 @@ processHDU(dsd_opts* opts, dsd_state* state) mihex3 = (unsigned long long int)ConvertBitIntoBytes(&mi[64], 8); } - //if (opts->payload == 0) + //if (state->errs == 0 && algidhex != 0x80) if (opts->payload == 1 && state->errs == 0) { - //fprintf (stderr, "HDU ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); - fprintf (stderr, "HDU ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08llX%08llX%02llX\n", algidhex, kidhex, mihex1, mihex2, mihex3); + //fprintf (stderr, " HDU ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); + fprintf (stderr, " HDU ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08llX%08llX%02llX\n", algidhex, kidhex, mihex1, mihex2, mihex3); } if (opts->p25enc == 1 && opts->payload == 0) diff --git a/src/p25p1_ldu2.c b/src/p25p1_ldu2.c index 55bdb53..b9013ec 100644 --- a/src/p25p1_ldu2.c +++ b/src/p25p1_ldu2.c @@ -375,11 +375,11 @@ processLDU2 (dsd_opts * opts, dsd_state * state) mihex3 = (unsigned long long int)ConvertBitIntoBytes(&mi[64], 8); } - //if (opts->payload == 0) + //if (state->errs == 0 && algidhex != 0x80) if (opts->payload == 1 && state->errs == 0) { - //fprintf (stderr, "LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); - fprintf (stderr, "LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08llX%08llX%02llX\n", algidhex, kidhex, mihex1, mihex2, mihex3); + //fprintf (stderr, " LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); + fprintf (stderr, " LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08llX%08llX%02llX\n", algidhex, kidhex, mihex1, mihex2, mihex3); } //if (opts->p25enc == 1 && opts->payload == 0) if (opts->payload == 0) From ea1f0bdf2ff6edecc2a4b18d70ffaa1e2086155e Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sun, 13 Mar 2022 19:49:13 -0400 Subject: [PATCH 09/12] Update Screenshot Update Screenshot --- dsd-fme.png | Bin 93503 -> 81073 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dsd-fme.png b/dsd-fme.png index 76a9b494f14a770ded1cf464a78d7eddad1c1b95..f770f2dabf4a8611f7e72f28e695414c475d7210 100644 GIT binary patch literal 81073 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

EYqm7DJ9iT>_yGti40g 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 Date: Sun, 13 Mar 2022 20:30:29 -0400 Subject: [PATCH 10/12] Update Information for README Update Information for README --- README.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index de79b2a..96f0e83 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ This version of DSD is a flavor blend of [szechyjs](https://github.com/szechyjs/ ![DSD-FME](https://github.com/lwvmobile/dsd-fme/blob/pulseaudio/dsd-fme.png) ## Example Usage - NCurses Terminal and Log Console to log file -`./dsd -fa -N 2> voice.log` +`./dsd -N 2> voice.log` in second terminal, same folder, run: `tail -n 40 -f voice.log` ## Example Usage - RTL -`dsd -fi -i rtl -o pulse -c 154.9875M -P -2 -D 1 -G 36 -L 70 -U 6021 -Y 12` +`dsd -fi -i rtl -c 154.9875M -P -2 -D 1 -G 36 -L 70 -U 6021 -Y 12` ``` -i rtl to use rtl_fm (default is -i pulse for pulse audio) @@ -34,7 +34,6 @@ in second terminal, same folder, run: -Y 12 set rtl VFO bandwidth in kHz, (default = 48)(6, 8, 12, 16, 24, 48) --W Monitor Source Audio (WIP!) (Currently disabled on PulseAudio branch) ``` ### Note @@ -47,29 +46,34 @@ in second terminal, same folder, run: ``` Yes, it really is this simple now --fa Auto-detect frame type +-fa Auto-detect frame type, is default -sans NXDN or Provoice, need to specify -fp, -fi, or -fn -pulse audio are set as default input and output methods +sans NXDN, dPMR, or Provoice, need to specify -fp, -fd, -fi, or -fn respectively +pulse audio is set as default input and output methods ``` ## Example STDIN UDP from GQRX or SDR++, output to Pulse Audio, and save wav files `socat stdio udp-listen:7355 | dsd -fi -i - -o pulse -w nxdn.wav` +## Example Usage - Extra Information for Academic Study and Logging +`dsd -d ./MBE/ -Z -pu 2>> voice.log` + +in second terminal, same folder, run: + +`tail -n 40 -f voice.log` + ## Roadmap The Current list of objectives include: 1. ~~Random Tinkering~~ More Random Tinkering -2. Implemented Pulse Audio and ~~Remove PortAudio and~~ Remove OSS, including SOLARIS/APPLE/BSD methods, and Retain PortAudio as Optional (need to re-enable in CMakeLists.txt file) +2. ~~Implemented Pulse Audio~~ Remove PortAudio, improve Pulse Audio for stereo output and channel/slot separation. -3. Improve NXDN and DMR support +3. ~~Improve NXDN and DMR support~~ Continue to improve ~~NXDN and DMR~~ all data and voice decoding. -4. More Concise Printouts - Ncurses +4. ~~More Concise Printouts - Ncurses~~ -5. ~~Improve Monitor Source Audio (if #2 on list is up and working)~~ Not currently playing well with Pulse Audio, need to re-evaluate - -6. ~~Make simple to use installer script.~~ Working on script now, also have full build and install guide down below +5. ~~Make simple to use installer script.~~ ## How to clone, check out, and build this branch @@ -84,7 +88,7 @@ chmod +x install.sh ``` ### Ubuntu 18.04/LM19/Buster Note: -Or you can elect to manually follow the steps down below. Do NOT Manually build and install ITPP 4.3.1 on older systems, it is currently not wanting to build on Ubuntu 18.04 and Linux Mint 19. Install it from the repository instead. +The above install.sh should now function on older system types. You can elect to manually follow the steps down below. Do NOT Manually build and install ITPP 4.3.1 on older systems, it is currently not wanting to build on Ubuntu 18.04 and Linux Mint 19. Install it from the repository instead. ## Manual Install From 5ee669aa18633208d6e7525d9dcb037bc401dd30 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sun, 13 Mar 2022 20:32:32 -0400 Subject: [PATCH 11/12] Fix Compiler Warning, minor tweaks Fix Compiler Warning, minor tweaks to NCurses view and information logging --- src/dsd_ncurses.c | 2 +- src/nxdn_lib.c | 6 ++---- src/p25p1_hdu.c | 2 +- src/p25p1_ldu2.c | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index 63ff114..7839f7f 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -350,7 +350,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("| RAN: [%02d] ", rn); printw ("TID: [%04d] ", tgn); //printw ("| RID: [%d] \n", src); - printw ("RID: [%04d] \n| ALG: 0x[%02X] KEY 0x[%02X] ", src, state->nxdn_cipher_type, state->nxdn_key); + printw ("RID: [%04d] \n| ALG: [0x%02X] KEY [0x%02X] ", src, state->nxdn_cipher_type, state->nxdn_key); if (state->carrier == 1) { printw("%s ", state->nxdn_call_type); diff --git a/src/nxdn_lib.c b/src/nxdn_lib.c index 990a0aa..d8060a8 100644 --- a/src/nxdn_lib.c +++ b/src/nxdn_lib.c @@ -734,12 +734,10 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) } /* Print the "Call Type" */ - //fprintf(stderr, "%s - ", NXDN_Call_Type_To_Str(CallType)); fprintf(stderr, "\n %s - ", NXDN_Call_Type_To_Str(CallType)); //line break and 1 space - sprintf (state->nxdn_call_type, NXDN_Call_Type_To_Str(CallType)); //fix warning below + //sprintf (state->nxdn_call_type, NXDN_Call_Type_To_Str(CallType)); //fix warning below //warning: format not a string literal and no format arguments [-Wformat-security] - - //state->nxdn_call_type = NXDN_Call_Type_To_Str(CallType); + sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType)); //fix warning below /* Print the "Voice Call Option" */ NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode); diff --git a/src/p25p1_hdu.c b/src/p25p1_hdu.c index 60345a5..26ad960 100644 --- a/src/p25p1_hdu.c +++ b/src/p25p1_hdu.c @@ -468,7 +468,7 @@ processHDU(dsd_opts* opts, dsd_state* state) } //if (state->errs == 0 && algidhex != 0x80) - if (opts->payload == 1 && state->errs == 0) + if (opts->payload == 1) { //fprintf (stderr, " HDU ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); fprintf (stderr, " HDU ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08llX%08llX%02llX\n", algidhex, kidhex, mihex1, mihex2, mihex3); diff --git a/src/p25p1_ldu2.c b/src/p25p1_ldu2.c index b9013ec..72aae00 100644 --- a/src/p25p1_ldu2.c +++ b/src/p25p1_ldu2.c @@ -376,7 +376,7 @@ processLDU2 (dsd_opts * opts, dsd_state * state) } //if (state->errs == 0 && algidhex != 0x80) - if (opts->payload == 1 && state->errs == 0) + if (opts->payload == 1) { //fprintf (stderr, " LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: %s \n", algidhex, kidhex, mi); fprintf (stderr, " LDU2 ALG ID: 0x%X KEY ID: 0x%X MI: 0x%08llX%08llX%02llX\n", algidhex, kidhex, mihex1, mihex2, mihex3); From 4af78289b2182fc3d317dd606c3be0968768ff83 Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Sun, 13 Mar 2022 20:44:48 -0400 Subject: [PATCH 12/12] Last Minute NCURSES Tweak Last Minute NCURSES Tweak --- src/dsd_ncurses.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index 7839f7f..6e2d0c2 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -347,10 +347,10 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) //if (state->lastsynctype == 8 || state->lastsynctype == 9 || state->lastsynctype == 16 || state->lastsynctype == 17) //change this to NXDN syncs later on if (lls == 8 || lls == 9 || lls == 16 || lls == 17) { - printw ("| RAN: [%02d] ", rn); - printw ("TID: [%04d] ", tgn); + printw ("| RAN: [%2d] ", rn); + printw ("TID: [%4d] ", tgn); //printw ("| RID: [%d] \n", src); - printw ("RID: [%04d] \n| ALG: [0x%02X] KEY [0x%02X] ", src, state->nxdn_cipher_type, state->nxdn_key); + printw ("RID: [%4d] \n| ALG: [0x%02X] KEY [0x%02X] ", src, state->nxdn_cipher_type, state->nxdn_key); if (state->carrier == 1) { printw("%s ", state->nxdn_call_type); @@ -456,7 +456,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw("\n"); printw ("| TID: [%8i] RID: [%8i]", tg, rd); - if(state->dmr_so & 0x80) //1000 0000 + if(state->dmr_so & 0x80) //1000 0000 //prints emergency on some amateru DMR repeaters with 0x8E SOP code, need to investigate { attron(COLOR_PAIR(2)); printw (" **Emergency** ");