From f3703e024171b7cc134110538321f23e1f4d3ce2 Mon Sep 17 00:00:00 2001 From: Joe Taylor <k1jt@arrl.org> Date: Mon, 18 Jul 2016 12:42:10 +0000 Subject: [PATCH] Nico's additions for new AP decoding modes and improved control of AP decoding. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@6926 ab8295b8-cf94-4d9e-aec4-7959e3be5d79 --- lib/qra/qra64/main.c | 107 +++++++++----- lib/qra/qra64/qra64.c | 277 +++++++++++++++++++++++++++--------- lib/qra/qra64/qra64.h | 94 +++++++++--- lib/qra/qra64/qra64_subs.c | 37 ++++- lib/qra/qracodes/main.c | 60 +++++++- lib/qra/qracodes/qracodes.c | 8 +- lib/qra/qracodes/qracodes.h | 8 +- 7 files changed, 455 insertions(+), 136 deletions(-) diff --git a/lib/qra/qra64/main.c b/lib/qra/qra64/main.c index 85b57ff28..c4ea87f4f 100644 --- a/lib/qra/qra64/main.c +++ b/lib/qra/qra64/main.c @@ -89,6 +89,9 @@ unsigned GetTickCount(void) { #define CHANNEL_AWGN 0 #define CHANNEL_RAYLEIGH 1 +#define JT65_SNR_EBNO_OFFSET 29.1f // with the synch used in JT65 +#define QRA64_SNR_EBNO_OFFSET 31.0f // with the costas array synch + void printwordd(char *msg, int *x, int size) { int k; @@ -171,13 +174,21 @@ symbol. #define GRID_JN66 0x3AE4 // JN66 #define GRID_73 0x7ED0 // 73 -char decode_type[6][32] = { +char decode_type[9][32] = { "[? ? ?] AP0", "[CQ ? ?] AP27", "[CQ ? ] AP42", "[CALL ? ?] AP29", "[CALL ? ] AP44", - "[CALL CALL ?] AP57" + "[CALL CALL ?] AP57", + "[? CALL ?] AP29", + "[? CALL ] AP44", + "[CALL CALL G] AP72" +}; +char apmode_type[3][32] = { + "NO AP", + "AUTO AP", + "USER AP" }; int test_proc_1(int channel_type, float EbNodB, int mode) @@ -229,10 +240,10 @@ be decoded float *rx; int rc; -// Each simulated station must use its own codec, since it might work with +// Each simulated station must use its own codec since it might work with // different a-priori information. - qra64codec *codec_iv3nwv = qra64_init(mode,CALL_IV3NWV); // codec for IV3NWV - qra64codec *codec_k1jt = qra64_init(mode,CALL_K1JT); // codec for K1JT + qra64codec *codec_iv3nwv = qra64_init(mode); // codec for IV3NWV + qra64codec *codec_k1jt = qra64_init(mode); // codec for K1JT // Step 1a: IV3NWV makes a CQ call (with no grid) printf("IV3NWV tx: CQ IV3NWV\n"); @@ -241,7 +252,7 @@ be decoded rx = mfskchannel(y,channel_type,EbNodB); // Step 1b: K1JT attempts to decode [? ? ?], [CQ/QRZ ? ?] or [CQ/QRZ ?] - rc = qra64_decode(codec_k1jt, xdec,rx); + rc = qra64_decode(codec_k1jt, 0, xdec,rx); if (rc>=0) { // decoded printf("K1JT rx: received with apcode=%d %s\n",rc, decode_type[rc]); @@ -252,7 +263,7 @@ be decoded rx = mfskchannel(y,channel_type,EbNodB); // Step 2b: IV3NWV attempts to decode [? ? ?], [IV3NWV ? ?] or [IV3NWV ?] - rc = qra64_decode(codec_iv3nwv, xdec,rx); + rc = qra64_decode(codec_iv3nwv, 0, xdec,rx); if (rc>=0) { // decoded printf("IV3NWV rx: received with apcode=%d %s\n",rc, decode_type[rc]); @@ -263,7 +274,7 @@ be decoded rx = mfskchannel(y,channel_type,EbNodB); // Step 3b: K1JT attempts to decode [? ? ?] or [K1JT IV3NWV ?] - rc = qra64_decode(codec_k1jt, xdec,rx); + rc = qra64_decode(codec_k1jt, 0, xdec,rx); if (rc>=0) { // decoded printf("K1JT rx: received with apcode=%d %s\n",rc, decode_type[rc]); @@ -274,7 +285,7 @@ be decoded rx = mfskchannel(y,channel_type,EbNodB); // Step 4b: IV3NWV attempts to decode [? ? ?], [IV3NWV ? ?], or [IV3NWV ?] - rc = qra64_decode(codec_iv3nwv, xdec,rx); + rc = qra64_decode(codec_iv3nwv, 0, xdec,rx); if (rc>=0) { // decoded printf("IV3NWV rx: received with apcode=%d %s\n",rc, decode_type[rc]); return 0; @@ -282,7 +293,7 @@ be decoded } } } - printf("the other party did not decode\n"); + printf("no decode\n"); return -1; } @@ -307,6 +318,9 @@ message according to this table: rc=3 [CALL ? ?] AP29 rc=4 [CALL ? ] AP44 rc=5 [CALL CALL ?] AP57 + rc=6 [? CALL ?] AP29 + rc=7 [? CALL ] AP44 + rc=8 [CALL CALL GRID ] AP72 The return code is <0 when decoding is unsuccessful @@ -317,42 +331,67 @@ a particular type decode among the above 6 cases succeded. int x[QRA64_K], xdec[QRA64_K]; int y[QRA64_N]; float *rx; + float ebnodbest, ebnodbavg=0; int rc,k; - int ndecok[6] = { 0, 0, 0, 0, 0, 0}; + int ndecok[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int nundet = 0; int ntx = 100,ndec=0; - qra64codec *codec_iv3nwv = qra64_init(mode,CALL_IV3NWV); // codec for IV3NWV - qra64codec *codec_k1jt = qra64_init(mode,CALL_K1JT); // codec for K1JT + qra64codec *codec_iv3nwv = qra64_init(mode); // codec for IV3NWV + qra64codec *codec_k1jt = qra64_init(mode); // codec for K1JT -// This will enable K1JT's decoder to look for IV3NWV calls - encodemsg_jt65(x,CALL_IV3NWV,CALL_K1JT,GRID_BLANK); - qra64_encode(codec_k1jt, y, x); - printf("K1JT tx: IV3NWV K1JT\n"); + printf("\nQRA64 Test #2 - Decoding with AP knowledge (SNR-Eb/No offset = %.1f dB)\n\n", + QRA64_SNR_EBNO_OFFSET); + +// This will enable K1JT's decoder to look for calls directed to him [K1JT ? ?/b] + printf("K1JT decoder enabled for [K1JT ? ?/blank]\n"); + qra64_apset(codec_k1jt, CALL_K1JT,0,0,APTYPE_MYCALL); + +// This will enable K1JT's decoder to look for IV3NWV calls directed to him [K1JT IV3NWV ?/b] + printf("K1JT decoder enabled for [K1JT IV3NWV ?]\n"); + qra64_apset(codec_k1jt, CALL_K1JT,CALL_IV3NWV,0,APTYPE_BOTHCALLS); + +// This will enable K1JT's decoder to look for msges sent by IV3NWV [? IV3NWV ?] + printf("K1JT decoder enabled for [? IV3NWV ?/blank]\n"); + qra64_apset(codec_k1jt, 0,CALL_IV3NWV,GRID_BLANK,APTYPE_HISCALL); + +// This will enable K1JT's decoder to look for full-knowledge [K1JT IV3NWV JN66] msgs + printf("K1JT decoder enabled for [K1JT IV3NWV JN66]\n"); + qra64_apset(codec_k1jt, CALL_K1JT,CALL_IV3NWV,GRID_JN66,APTYPE_FULL); // IV3NWV reply to K1JT - printf("IV3NWV tx: K1JT IV3NWV JN66\n"); + printf("\nIV3NWV encoder sends msg: [K1JT IV3NWV JN66]\n\n"); encodemsg_jt65(x,CALL_K1JT,CALL_IV3NWV,GRID_JN66); qra64_encode(codec_iv3nwv, y, x); - printf("Simulating decodes by K1JT up to AP56 ..."); + printf("Simulating K1JT decoder up to AP72\n"); for (k=0;k<ntx;k++) { printf("."); rx = mfskchannel(y,channel_type,EbNodB); - rc = qra64_decode(codec_k1jt, xdec,rx); - if (rc>=0) - ndecok[rc]++; + rc = qra64_decode(codec_k1jt, &ebnodbest, xdec,rx); + if (rc>=0) { + ebnodbavg +=ebnodbest; + if (memcmp(xdec,x,12*sizeof(int))==0) + ndecok[rc]++; + else + nundet++; + } } - printf("\n"); + printf("\n\n"); - printf("Transimtted:%d - Decoded:\n",ntx); - for (k=0;k<6;k++) { + + printf("Transimtted msgs:%d\nDecoded msgs:\n\n",ntx); + for (k=0;k<9;k++) { printf("%3d with %s\n",ndecok[k],decode_type[k]); ndec += ndecok[k]; } - printf("Total: %d/%d\n",ndec,ntx); - printf("\n"); + printf("\nTotal: %d/%d (%d undetected errors)\n\n",ndec,ntx,nundet); + printf(""); + + ebnodbavg/=(ndec+nundet); + printf("Estimated SNR (average in dB) = %.2f dB\n\n",ebnodbavg-QRA64_SNR_EBNO_OFFSET); return 0; } @@ -366,7 +405,7 @@ void syntax(void) printf("Options: \n"); printf(" -s<snrdb> : set simulation SNR in 2500 Hz BW (default:-27.5 dB)\n"); printf(" -c<channel> : set channel type 0=AWGN (default) 1=Rayleigh\n"); - printf(" -a<ap-type> : set decode type 0=NO_AP 1=AUTO_AP (default)\n"); + printf(" -a<ap-type> : set decode type 0=NOAP 1=AUTOAP (default) 2=USERAP\n"); printf(" -t<testtype>: 0=simulate seq of msgs between IV3NWV and K1JT (default)\n"); printf(" 1=simulate K1JT receiving K1JT IV3NWV JN66\n"); printf(" -h: this help\n"); @@ -391,7 +430,7 @@ int main(int argc, char* argv[]) } else { if (strncmp(*argv,"-a",2)==0) { mode = ( int)atoi((*argv)+2); - if (mode>1) { + if (mode>2) { printf("Invalid decoding mode\n"); syntax(); return -1; @@ -399,8 +438,8 @@ int main(int argc, char* argv[]) } else { if (strncmp(*argv,"-s",2)==0) { SNRdB = (float)atof((*argv)+2); - if (SNRdB>0 || SNRdB<-40) { - printf("SNR should be in the range [-40..0]\n"); + if (SNRdB>20 || SNRdB<-40) { + printf("SNR should be in the range [-40..20]\n"); syntax(); return -1; } @@ -431,7 +470,7 @@ int main(int argc, char* argv[]) } } - EbNodB = SNRdB+29.1f; + EbNodB = SNRdB+QRA64_SNR_EBNO_OFFSET; #if defined(__linux__) || defined(__unix__) srand48(GetTickCount()); @@ -449,10 +488,10 @@ int main(int argc, char* argv[]) test_proc_2(channel, EbNodB, mode); } - printf("SNR = %.1fdB channel=%s ap-mode=%s\n\n", + printf("Input SNR = %.1fdB channel=%s ap-mode=%s\n\n", SNRdB, channel==CHANNEL_AWGN?"AWGN":"RAYLEIGH", - mode==QRA_NOAP?"NO_AP":"AUTO_AP" + apmode_type[mode] ); return 0; } diff --git a/lib/qra/qra64/qra64.c b/lib/qra/qra64/qra64.c index 89ed5a15f..2f35a651a 100644 --- a/lib/qra/qra64/qra64.c +++ b/lib/qra/qra64/qra64.c @@ -35,6 +35,7 @@ resulting code is a (12,63) code #include <stdlib.h> #include <math.h> +#include <string.h> #include "qra64.h" #include "../qracodes/qracodes.h" @@ -64,7 +65,7 @@ static int qra64_do_decode(int *x, const float *pix, const int *ap_mask, #define MASK_GRIDBIT 0x8000 // b[15] is 1 for free text, 0 otherwise // ---------------------------------------------------------------------------- -qra64codec *qra64_init(int flags, const int mycall) +qra64codec *qra64_init(int flags) { // Eb/No value for which we optimize the decoder metric @@ -80,39 +81,100 @@ qra64codec *qra64_init(int flags, const int mycall) pcodec->decEsNoMetric = 1.0f*QRA64_m*R*EbNoMetric; pcodec->apflags = flags; - if (flags!=QRA_AUTOAP) + memset(pcodec->apmsg_set,0,APTYPE_SIZE*sizeof(int)); + + if (flags==QRA_NOAP) return pcodec; - // initialize messages and mask for decoding with a-priori information + // for QRA_USERAP and QRA_AUTOAP modes we always enable [CQ/QRZ ? ?] mgs look-up. + // encode CQ/QRZ AP messages + // NOTE: Here we handle only CQ and QRZ msgs. + // 'CQ nnn', 'CQ DX' and 'DE' msgs will be handled by the decoder + // as messages with no a-priori knowledge + qra64_apset(pcodec, CALL_CQ, 0, GRID_BLANK, APTYPE_CQQRZ); - pcodec->apmycall = mycall; - pcodec->apsrccall = 0; + // initialize masks for decoding with a-priori information + encodemsg_jt65(pcodec->apmask_cqqrz, MASK_CQQRZ, 0, MASK_GRIDBIT); + encodemsg_jt65(pcodec->apmask_cqqrz_ooo, MASK_CQQRZ, 0, MASK_GRIDFULL); + encodemsg_jt65(pcodec->apmask_call1, MASK_CALL1, 0, MASK_GRIDBIT); + encodemsg_jt65(pcodec->apmask_call1_ooo, MASK_CALL1, 0, MASK_GRIDFULL); + encodemsg_jt65(pcodec->apmask_call2, 0, MASK_CALL2, MASK_GRIDBIT); + encodemsg_jt65(pcodec->apmask_call2_ooo, 0, MASK_CALL2, MASK_GRIDFULL); + encodemsg_jt65(pcodec->apmask_call1_call2, MASK_CALL1,MASK_CALL2, MASK_GRIDBIT); + encodemsg_jt65(pcodec->apmask_call1_call2_grid,MASK_CALL1,MASK_CALL2, MASK_GRIDFULL); - // encode CQ/QRZ messages and masks - // NOTE: Here we handle only CQ and QRZ msgs - // 'CQ nnn', 'CQ DX' and 'DE' msgs - // will be handled by the decoder as messages with no a-priori knowledge - encodemsg_jt65(pcodec->apmsg_cqqrz, CALL_CQ, 0, GRID_BLANK); - encodemsg_jt65(pcodec->apmask_cqqrz, MASK_CQQRZ,0, MASK_GRIDBIT); // AP27 - encodemsg_jt65(pcodec->apmask_cqqrz_ooo, MASK_CQQRZ,0, MASK_GRIDFULL);// AP42 - - // encode [mycall ? x] messages and set masks - encodemsg_jt65(pcodec->apmsg_call1, mycall, 0, GRID_BLANK); - encodemsg_jt65(pcodec->apmask_call1, MASK_CALL1, 0, MASK_GRIDBIT); // AP29 - encodemsg_jt65(pcodec->apmask_call1_ooo, MASK_CALL1,0, MASK_GRIDFULL);// AP44 - - // set mask for [mycall srccall ?] messages - encodemsg_jt65(pcodec->apmask_call1_call2,MASK_CALL1,MASK_CALL2, - MASK_GRIDBIT); // AP56 return pcodec; } +void qra64_close(qra64codec *pcodec) +{ + free(pcodec); +} + +int qra64_apset(qra64codec *pcodec, const int mycall, const int hiscall, const int grid, const int aptype) +{ +// Set decoder a-priori knowledge accordingly to the type of the message to look up for +// arguments: +// pcodec = pointer to a qra64codec data structure as returned by qra64_init +// mycall = mycall to look for +// hiscall = hiscall to look for +// grid = grid to look for +// aptype = define and masks the type of AP to be set accordingly to the following: +// APTYPE_CQQRZ set [cq/qrz ? ?/blank] +// APTYPE_MYCALL set [mycall ? ?/blank] +// APTYPE_HISCALL set [? hiscall ?/blank] +// APTYPE_BOTHCALLS set [mycall hiscall ?] +// APTYPE_FULL set [mycall hiscall grid] +// returns: +// 0 on success +// -1 when qra64_init was called with the QRA_NOAP flag +// -2 invalid apytpe + + if (pcodec->apflags==QRA_NOAP) + return -1; + + switch (aptype) { + case APTYPE_CQQRZ: + encodemsg_jt65(pcodec->apmsg_cqqrz, CALL_CQ, 0, GRID_BLANK); + break; + case APTYPE_MYCALL: + encodemsg_jt65(pcodec->apmsg_call1, mycall, 0, GRID_BLANK); + break; + case APTYPE_HISCALL: + encodemsg_jt65(pcodec->apmsg_call2, 0, hiscall, GRID_BLANK); + break; + case APTYPE_BOTHCALLS: + encodemsg_jt65(pcodec->apmsg_call1_call2, mycall, hiscall, GRID_BLANK); + break; + case APTYPE_FULL: + encodemsg_jt65(pcodec->apmsg_call1_call2_grid, mycall, hiscall, grid); + break; + default: + return -2; // invalid ap type + } + + pcodec->apmsg_set[aptype]=1; // signal the decoder to look-up for the specified type + + + return 0; +} +void qra64_apdisable(qra64codec *pcodec, const int aptype) +{ + if (pcodec->apflags==QRA_NOAP) + return; + + if (aptype<APTYPE_CQQRZ || aptype>APTYPE_FULL) + return; + + pcodec->apmsg_set[aptype] = 0; // signal the decoder not to look-up to the specified type +} + void qra64_encode(qra64codec *pcodec, int *y, const int *x) { int encx[QRA64_KC]; // encoder input buffer int ency[QRA64_NC]; // encoder output buffer - int call1,call2,grid; + int hiscall,mycall,grid; memcpy(encx,x,QRA64_K*sizeof(int)); // Copy input to encoder buffer encx[QRA64_K]=calc_crc6(encx,QRA64_K); // Compute and add crc symbol @@ -125,42 +187,56 @@ void qra64_encode(qra64codec *pcodec, int *y, const int *x) if (pcodec->apflags!=QRA_AUTOAP) return; + // Here we handle the QRA_AUTOAP mode -------------------------------------------- + + // When a [hiscall mycall ?] msg is detected we instruct the decoder + // to look for [mycall hiscall ?] msgs + // otherwise when a [cq mycall ?] msg is sent we reset the APTYPE_BOTHCALLS + // look if the msg sent is a std type message (bit15 of grid field = 0) if ((x[9]&0x80)==1) - return; // no, it's a text message + return; // no, it's a text message, nothing to do - // It's a [call1 call2 grid] message + // It's a [hiscall mycall grid] message - // We assume that call2 is our call (but we don't check it) - // call1 the station callsign we are calling or indicates a general call (CQ/QRZ/etc..) - decodemsg_jt65(&call1,&call2,&grid,x); - - if ((call1>=CALL_CQ && call1<=CALL_CQ999) || call1==CALL_CQDX || - call1==CALL_DE) { - // We are making a general call; don't know who might reply (srccall) - // Reset apsrccall to 0 so decoder won't look for [mycall srccall ?] msgs - pcodec->apsrccall = 0; + // We assume that mycall is our call (but we don't check it) + // hiscall the station we are calling or a general call (CQ/QRZ/etc..) + decodemsg_jt65(&hiscall,&mycall,&grid,x); + + + if ((hiscall>=CALL_CQ && hiscall<=CALL_CQ999) || hiscall==CALL_CQDX || + hiscall==CALL_DE) { + // tell the decoder to look for msgs directed to us + qra64_apset(pcodec,mycall,0,0,APTYPE_MYCALL); + // We are making a general call and don't know who might reply + // Reset APTYPE_BOTHCALLS so decoder won't look for [mycall hiscall ?] msgs + qra64_apdisable(pcodec,APTYPE_BOTHCALLS); } else { - // We are replying to someone named call1 - // Set apmsg_call1_call2 so decoder will try for [mycall call1 ?] msgs - pcodec->apsrccall = call1; - encodemsg_jt65(pcodec->apmsg_call1_call2, pcodec->apmycall, - pcodec->apsrccall, 0); + // We are replying to someone named hiscall + // Set APTYPE_BOTHCALLS so decoder will try for [mycall hiscall ?] msgs + qra64_apset(pcodec,mycall, hiscall, GRID_BLANK, APTYPE_BOTHCALLS); } + } -int qra64_decode(qra64codec *pcodec, int *x, const float *rxen) +#define EBNO_MIN -10.0f // minimum Eb/No value returned by the decoder (in dB) +int qra64_decode(qra64codec *pcodec, float *ebno, int *x, const float *rxen) { int k; float *srctmp, *dsttmp; float ix[QRA64_NC*QRA64_M]; // (depunctured) intrisic information + int xdec[QRA64_KC]; // decoded message (with crc) + int ydec[QRA64_NC]; // re-encoded message (for snr calculations) + float noisestd; // estimated noise variance + float msge; // estimated message energy + float ebnoval; // estimated Eb/No int rc; if (QRA64_NMSG!=QRA64_CODE.NMSG) // sanity check return -16; // QRA64_NMSG define is wrong // compute symbols intrinsic probabilities from received energy observations - qra_mfskbesselmetric(ix, rxen, QRA64_m, QRA64_N,pcodec->decEsNoMetric); + noisestd = qra_mfskbesselmetric(ix, rxen, QRA64_m, QRA64_N,pcodec->decEsNoMetric); // de-puncture observations adding a uniform distribution for the crc symbol @@ -176,34 +252,109 @@ int qra64_decode(qra64codec *pcodec, int *x, const float *rxen) pd_init(dsttmp,pd_uniform(QRA64_m),QRA64_M); // Attempt to decode without a-priori info -------------------------------- - rc = qra64_do_decode(x, ix, NULL, NULL); - if (rc>=0) return 0; // successfull decode with AP0 + rc = qra64_do_decode(xdec, ix, NULL, NULL); + if (rc>=0) { + rc = 0; // successfull decode with AP0 + goto decode_end; + } + else + if (pcodec->apflags==QRA_NOAP) + // nothing more to do + return rc; // rc<0 = unsuccessful decode - if (pcodec->apflags!=QRA_AUTOAP) return rc; // rc<0 = unsuccessful decode + // Here we handle decoding with AP knowledge // Attempt to decode CQ calls - rc = qra64_do_decode(x,ix,pcodec->apmask_cqqrz, pcodec->apmsg_cqqrz); // AP27 - if (rc>=0) return 1; // decoded [cq/qrz ? ?] + rc = qra64_do_decode(xdec,ix,pcodec->apmask_cqqrz, pcodec->apmsg_cqqrz); + if (rc>=0) { rc = 1; goto decode_end; }; // decoded [cq/qrz ? ?] - rc = qra64_do_decode(x, ix, pcodec->apmask_cqqrz_ooo, - pcodec->apmsg_cqqrz); // AP42 - if (rc>=0) return 2; // decoded [cq ? ooo] + rc = qra64_do_decode(xdec, ix, pcodec->apmask_cqqrz_ooo, + pcodec->apmsg_cqqrz); + if (rc>=0) { rc = 2; goto decode_end; }; // decoded [cq ? ooo] - // attempt to decode calls directed to us (mycall) - rc = qra64_do_decode(x, ix, pcodec->apmask_call1, - pcodec->apmsg_call1); // AP29 - if (rc>=0) return 3; // decoded [mycall ? ?] + // attempt to decode calls directed to us + if (pcodec->apmsg_set[APTYPE_MYCALL]) { + rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1, + pcodec->apmsg_call1); + if (rc>=0) { rc = 3; goto decode_end; }; // decoded [mycall ? ?] + rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_ooo, + pcodec->apmsg_call1); + if (rc>=0) { rc = 4; goto decode_end; }; // decoded [mycall ? ooo] + } - rc = qra64_do_decode(x, ix, pcodec->apmask_call1_ooo, - pcodec->apmsg_call1); // AP44 - if (rc>=0) return 4; // decoded [mycall ? ooo] + // attempt to decode [mycall srccall ?] msgs + if (pcodec->apmsg_set[APTYPE_BOTHCALLS]) { + rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_call2, + pcodec->apmsg_call1_call2); + if (rc>=0) { rc = 5; goto decode_end; }; // decoded [mycall srccall ?] + } - // if apsrccall is set attempt to decode [mycall srccall ?] msgs - if (pcodec->apsrccall==0) return rc; // nothing more to do + // attempt to decode [? hiscall ?] msgs + if (pcodec->apmsg_set[APTYPE_HISCALL]) { + rc = qra64_do_decode(xdec, ix, pcodec->apmask_call2, + pcodec->apmsg_call2); + if (rc>=0) { rc = 6; goto decode_end; }; // decoded [? hiscall ?] + rc = qra64_do_decode(xdec, ix, pcodec->apmask_call2_ooo, + pcodec->apmsg_call2); + if (rc>=0) { rc = 7; goto decode_end; }; // decoded [? hiscall ooo] + } - rc = qra64_do_decode(x, ix, pcodec->apmask_call1_call2, - pcodec->apmsg_call1_call2); // AP57 - if (rc>=0) return 5; // decoded [mycall srccall ?] + if (pcodec->apmsg_set[APTYPE_FULL]) { + rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_call2_grid, + pcodec->apmsg_call1_call2_grid); + if (rc>=0) { rc = 8; goto decode_end; }; // decoded [mycall hiscall grid] + } + + // all decoding attempts failed + return rc; + +decode_end: // successfull decode + + // copy decoded message (without crc) to output buffer + memcpy(x,xdec,QRA64_K*sizeof(int)); + + if (ebno==0) // null pointer indicates we are not interested in the Eb/No estimate + return rc; + + // reencode message and estimate Eb/No + qra_encode(&QRA64_CODE, ydec, xdec); + // puncture crc + memmove(ydec+QRA64_K,ydec+QRA64_KC,QRA64_C*sizeof(int)); + // compute total power of decoded message + msge = 0; + for (k=0;k<QRA64_N;k++) { + msge +=rxen[ydec[k]]; // add energy of current symbol + rxen+=QRA64_M; // ptr to next symbol + } + + // NOTE: + // To make a more accurate Eb/No estimation we should compute the noise variance + // on all the rxen values but the transmitted symbols. + // Noisestd is compute by qra_mfskbesselmetric assuming that + // the signal power is much less than the total noise power in the QRA64_M tones + // but this is true only if the Eb/No is low. + // Here, in order to improve accuracy, we linearize the estimated Eb/No value empirically + // (it gets compressed when it is very high as in this case the noise variance + // is overestimated) + + // this would be the exact value if the noisestd were not overestimated at high Eb/No + ebnoval = (0.5f/(QRA64_K*QRA64_m))*msge/(noisestd*noisestd)-1.0f; + + // Empirical linearization (to remove the noise variance overestimation) + // the resulting SNR is accurate up to +20 dB (51 dB Eb/No) + if (ebnoval>57.004f) + ebnoval=57.004f; + ebnoval = ebnoval*57.03f/(57.03f-ebnoval); + + // compute value in dB + if (ebnoval<=0) + ebnoval = EBNO_MIN; // assume a minimum, positive value + else + ebnoval = 10.0f*(float)log10(ebnoval); + if (ebnoval<EBNO_MIN) + ebnoval = EBNO_MIN; + + *ebno = ebnoval; return rc; } @@ -211,7 +362,7 @@ int qra64_decode(qra64codec *pcodec, int *x, const float *rxen) // Static functions definitions ---------------------------------------------- // Decode with given a-priori information -static int qra64_do_decode(int *x, const float *pix, const int *ap_mask, +static int qra64_do_decode(int *xdec, const float *pix, const int *ap_mask, const int *ap_x) { int rc; @@ -221,7 +372,6 @@ static int qra64_do_decode(int *x, const float *pix, const int *ap_mask, float v2cmsg[QRA64_NMSG*QRA64_M]; // buffers for the decoder messages float c2vmsg[QRA64_NMSG*QRA64_M]; - int xdec[QRA64_KC]; if (ap_mask==NULL) { // no a-priori information ixsrc = pix; // intrinsic source is what passed as argument @@ -244,9 +394,6 @@ static int qra64_do_decode(int *x, const float *pix, const int *ap_mask, if (calc_crc6(xdec,QRA64_K)!=xdec[QRA64_K]) // crc doesn't match (detected error) return -2; // decoding was succesfull but crc doesn't match - // success. copy decoded message to output buffer - memcpy(x,xdec,QRA64_K*sizeof(int)); - return 0; } // crc functions -------------------------------------------------------------- diff --git a/lib/qra/qra64/qra64.h b/lib/qra/qra64/qra64.h index 8fd7cc51e..456a2c6f7 100644 --- a/lib/qra/qra64/qra64.h +++ b/lib/qra/qra64/qra64.h @@ -23,8 +23,9 @@ #define _qra64_h_ // qra64_init(...) initialization flags -#define QRA_NOAP 0 // don't use a-priori knowledge -#define QRA_AUTOAP 1 // use auto a-priori knowledge +#define QRA_NOAP 0 // don't use a-priori knowledge +#define QRA_AUTOAP 1 // use auto a-priori knowledge +#define QRA_USERAP 2 // a-priori knowledge messages provided by the user // QRA code parameters #define QRA64_K 12 // information symbols @@ -42,33 +43,48 @@ #define CALL_DE 0xFF641D1 #define GRID_BLANK 0x7E91 +// Types of a-priori knowledge messages +#define APTYPE_CQQRZ 0 // [cq/qrz ? ?/blank] +#define APTYPE_MYCALL 1 // [mycall ? ?/blank] +#define APTYPE_HISCALL 2 // [? hiscall ?/blank] +#define APTYPE_BOTHCALLS 3 // [mycall hiscall ?] +#define APTYPE_FULL 4 // [mycall hiscall grid] +#define APTYPE_SIZE (APTYPE_FULL+1) + typedef struct { float decEsNoMetric; int apflags; - int apmycall; - int apsrccall; - int apmsg_cqqrz[12]; // [cq/qrz ? blank] - int apmsg_call1[12]; // [mycall ? blank] - int apmsg_call1_call2[12]; // [mycall srccall ?] + int apmsg_set[APTYPE_SIZE]; // indicate which ap type knowledge has been set by the user + // ap messages buffers + int apmsg_cqqrz[12]; // [cq/qrz ? ?/blank] + int apmsg_call1[12]; // [mycall ? ?/blank] + int apmsg_call2[12]; // [? hiscall ?/blank] + int apmsg_call1_call2[12]; // [mycall hiscall ?] + int apmsg_call1_call2_grid[12]; // [mycall hiscall grid] + // ap messages masks int apmask_cqqrz[12]; int apmask_cqqrz_ooo[12]; int apmask_call1[12]; int apmask_call1_ooo[12]; + int apmask_call2[12]; + int apmask_call2_ooo[12]; int apmask_call1_call2[12]; + int apmask_call1_call2_grid[12]; } qra64codec; #ifdef __cplusplus extern "C" { #endif -qra64codec *qra64_init(int flags, const int mycall); +qra64codec *qra64_init(int flags); // QRA64 mode initialization function // arguments: // flags: set the decoder mode // When flags = QRA_NOAP no a-priori information will be used by the decoder // When flags = QRA_AUTOAP the decoder will attempt to decode with the amount // of available a-priori information -// mycall: 28-bit packed callsign of the user (as computed by JT65) +// When flags = QRA_USERAP the decoder will attempt to decode with the amount +// of a-priori information as provided by the caller with the function qra64_apset(...) // returns: // Pointer to the qra64codec data structure allocated and inizialized by the function // this handle should be passed to the encoding/decoding functions @@ -86,10 +102,12 @@ void qra64_encode(qra64codec *pcodec, int *y, const int *x); // y must point to an array of integers of lenght 63 (i.e. defined as int y[63]) // ------------------------------------------------------------------------------------------- -int qra64_decode(qra64codec *pcodec, int *x, const float *r); +int qra64_decode(qra64codec *pcodec, float *ebno, int *x, const float *r); // QRA64 mode decoder // arguments: // pcodec = pointer to a qra64codec data structure as returned by qra64_init +// ebno = pointer to a float number where the avg Eb/No (in dB) will be stored +// in case of successfull decoding (pass a null pointer if not interested) // x = pointer to the array of integers where the decoded message will be stored // x must point to an array of integers (i.e. defined as int x[12]) // r = pointer to the received symbols energies (squared amplitudes) @@ -102,14 +120,54 @@ int qra64_decode(qra64codec *pcodec, int *x, const float *r); // The return code is <0 when decoding is unsuccessful // -16 indicates that the definition of QRA64_NMSG does not match what required by the code // If the decoding process is successfull the return code is accordingly to the following table -// rc=0 [? ? ?] AP0 (decoding with no a-priori) -// rc=1 [CQ ? ?] AP27 -// rc=2 [CQ ? ] AP44 -// rc=3 [CALL ? ?] AP29 -// rc=4 [CALL ? ] AP45 -// rc=5 [CALL CALL ?] AP57 -// return codes in the range 1-5 indicate the amount of a-priori information which was required -// to decode the received message and are possible only when the QRA_AUTOAP mode has been enabled. +// rc=0 [? ? ?] AP0 (decoding with no a-priori) +// rc=1 [CQ ? ?] AP27 +// rc=2 [CQ ? ] AP44 +// rc=3 [CALL ? ?] AP29 +// rc=4 [CALL ? ] AP45 +// rc=5 [CALL CALL ?] AP57 +// rc=6 [? CALL ?] AP29 +// rc=7 [? CALL ] AP45 +// rc=8 [CALL CALL GRID] AP72 +// return codes in the range 1-8 indicate the amount and the type of a-priori information +// which was required to decode the received message + +int qra64_apset(qra64codec *pcodec, const int mycall, const int hiscall, const int grid, const int aptype); +// Set decoder a-priori knowledge accordingly to the type of the message to look up for +// arguments: +// pcodec = pointer to a qra64codec data structure as returned by qra64_init +// mycall = mycall to look for +// hiscall = hiscall to look for +// grid = grid to look for +// aptype = define the type of AP to be set accordingly to the following table: +// APTYPE_CQQRZ set [cq/qrz ? ?/blank] +// APTYPE_MYCALL set [mycall ? ?/blank] +// APTYPE_HISCALL set [? hiscall ?/blank] +// APTYPE_BOTHCALLS set [mycall hiscall ?] +// APTYPE_FULL set [mycall hiscall grid] +// returns: +// 0 on success +// -1 when qra64_init was called with the QRA_NOAP flag +// -2 invalid apytpe (aptype must be in the range [APTYPE_MYCALL..APTYPE_FULL] +// (APTYPE_CQQRZ [cq/qrz ? ?] is set during the initialization function and +// doesn't need to be set by the user + +void qra64_apdisable(qra64codec *pcodec, const int aptype); +// disable specific AP type +// arguments: +// pcodec = pointer to a qra64codec data structure as returned by qra64_init +// aptype = define the type of AP to be disabled +// APTYPE_CQQRZ disable [cq/qrz ? ?/blank] +// APTYPE_MYCALL disable [mycall ? ?/blank] +// APTYPE_HISCALL disable [? hiscall ?/blank] +// APTYPE_BOTHCALLS disable [mycall hiscall ?] +// APTYPE_FULL disable [mycall hiscall grid] + +void qra64_close(qra64codec *pcodec); +// Free memory allocated by qra64_init +// arguments: +// pcodec = pointer to a qra64codec data structure as returned by qra64_init + // ------------------------------------------------------------------------------------------- // encode/decode std msgs in 12 symbols as done in jt65 diff --git a/lib/qra/qra64/qra64_subs.c b/lib/qra/qra64/qra64_subs.c index 933035b3f..d857dc8af 100644 --- a/lib/qra/qra64/qra64_subs.c +++ b/lib/qra/qra64/qra64_subs.c @@ -4,11 +4,16 @@ #include "qra64.h" #include <stdio.h> +#define NICO_WANTS_SNR_DUMP + +static qra64codec *pqra64codec = NULL; + void qra64_enc_(int x[], int y[]) { - int ncall=0xf70c238; //K1ABC - qra64codec *codec = qra64_init(0,ncall); //codec for ncall - qra64_encode(codec, y, x); + if (pqra64codec==NULL) + pqra64codec = qra64_init(QRA_AUTOAP); + + qra64_encode(pqra64codec, y, x); } void qra64_dec_(float r[], int* nmycall, int xdec[], int* rc) @@ -23,14 +28,34 @@ void qra64_dec_(float r[], int* nmycall, int xdec[], int* rc) // rc=3 [CALL ? ?] AP29 // rc=4 [CALL ? ] AP44 // rc=5 [CALL CALL ?] AP57 +// rc=6 [? CALL ?] AP29 +// rc=7 [? CALL ] AP44 +// rc=8 [CALL CALL G] AP72 static ncall0=-1; int ncall=*nmycall; - static qra64codec *codec; + float EbNodBEstimated; +#ifdef NICO_WANTS_SNR_DUMP + FILE *fout; +#endif + if(ncall!=ncall0) { - codec = qra64_init(1,ncall); //codec for ncall + if (pqra64codec!=NULL) + qra64_close(pqra64codec); + pqra64codec = qra64_init(QRA_AUTOAP); + // the following apset call is not strictly necessary + // It enables AP decoding of messages directed to our call + // also in the case we have never made a CQ + qra64_apset(pqra64codec,ncall,0,0,APTYPE_MYCALL); ncall0=ncall; } - *rc = qra64_decode(codec,xdec,r); + *rc = qra64_decode(pqra64codec,&EbNodBEstimated,xdec,r); + +#ifdef NICO_WANTS_SNR_DUMP + fout = fopen("C:\\JTSDK\\snrdump.txt","a+"); + if ((*rc)>=0) + fprintf(fout,"rc=%d snr=%.2f dB\n",*rc,EbNodBEstimated-31.0f); + fclose(fout); +#endif } diff --git a/lib/qra/qracodes/main.c b/lib/qra/qracodes/main.c index 3fe7d44f3..5f9f067d4 100644 --- a/lib/qra/qracodes/main.c +++ b/lib/qra/qracodes/main.c @@ -56,18 +56,38 @@ #include <process.h> // _beginthread #endif -#if __linux__ -#include <unistd.h> +#if defined(__linux__) + +// remove unwanted macros +#define __cdecl + +// implements Windows API #include <time.h> - GetTickCount(void) { + unsigned int GetTickCount(void) { struct timespec ts; - theTick = 0U; + unsigned int theTick = 0U; clock_gettime( CLOCK_REALTIME, &ts ); theTick = ts.tv_nsec / 1000000; theTick += ts.tv_sec * 1000; return theTick; } + +// Convert Windows millisecond sleep +// +// VOID WINAPI Sleep(_In_ DWORD dwMilliseconds); +// +// to Posix usleep (in microseconds) +// +// int usleep(useconds_t usec); +// +#include <unistd.h> +#define Sleep(x) usleep(x*1000) + +#endif + +#if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) ) +#include <pthread.h> #endif #if __APPLE__ @@ -86,7 +106,7 @@ // ----------------------------------------------------------------------------------- -#define NTHREADS_MAX 24 +#define NTHREADS_MAX 160 // channel types #define CHANNEL_AWGN 0 @@ -367,9 +387,21 @@ void wer_test_thread(wer_test_ds *pdata) pdata->done=1; + #if _WIN32 _endthread(); + #endif } +#if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) ) + +void *wer_test_pthread(void *p) +{ + wer_test_thread ((wer_test_ds *)p); + return 0; +} + +#endif + void ix_mask(const qracode *pcode, float *r, const int *mask, const int *x) { // mask intrinsic information (channel observations) with a priori knowledge @@ -454,7 +486,14 @@ int wer_test_proc(const qracode *pcode, int nthreads, int chtype, int ap_index, wt[j].nerrsu=0; wt[j].done = 0; wt[j].stop = 0; + #if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) ) + if (pthread_create (&wt[j].thread, 0, wer_test_pthread, &wt[j])) { + perror ("Creating thread: "); + exit (255); + } + #else _beginthread((void*)(void*)wer_test_thread,0,&wt[j]); + #endif } nd = 0; @@ -487,9 +526,19 @@ int wer_test_proc(const qracode *pcode, int nthreads, int chtype, int ap_index, // wait for the working threads to exit for (j=0;j<nthreads;j++) + #if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) ) + { + void *rc; + if (pthread_join (wt[j].thread, &rc)) { + perror ("Waiting working threads to exit"); + exit (255); + } + } + #else while(wt[j].done==0) Sleep(1); + #endif printf("\n"); fflush (stdout); @@ -601,6 +650,7 @@ int main(int argc, char* argv[]) else if (strncmp(*argv,"-t",2)==0) { nthreads = (int)atoi((*argv)+2); + printf("nthreads = %d\n",nthreads); if (nthreads>NTHREADS_MAX) { printf("Invalid number of threads\n"); syntax(); diff --git a/lib/qra/qracodes/qracodes.c b/lib/qra/qracodes/qracodes.c index 7af0945bf..748a9c9cd 100644 --- a/lib/qra/qracodes/qracodes.c +++ b/lib/qra/qracodes/qracodes.c @@ -27,8 +27,6 @@ #include "qracodes.h" -#define QRA_DEBUG - int qra_encode(const qracode *pcode, int *y, const int *x) { int k,j,kk,jj; @@ -144,7 +142,7 @@ static void qra_ioapprox(float *src, float C, int nitems) } -void qra_mfskbesselmetric(float *pix, const float *rsq, const int m, const int N, float EsNoMetric) +float qra_mfskbesselmetric(float *pix, const float *rsq, const int m, const int N, float EsNoMetric) { // Computes the codeword symbols intrinsic probabilities // given the square of the received input amplitudes. @@ -167,6 +165,8 @@ void qra_mfskbesselmetric(float *pix, const float *rsq, const int m, const int N // nevertheless it is usually better than a generic parameter-free metric which // makes no assumptions on the input Es/No. + // returns the estimated noise standard deviation + int k; float rsum = 0.f; float sigmaest, cmetric; @@ -196,7 +196,7 @@ void qra_mfskbesselmetric(float *pix, const float *rsq, const int m, const int N pd_norm(PD_ROWADDR(pix,M,k),m); } - return; + return sigmaest; } diff --git a/lib/qra/qracodes/qracodes.h b/lib/qra/qracodes/qracodes.h index e4bac50aa..97542c118 100644 --- a/lib/qra/qracodes/qracodes.h +++ b/lib/qra/qracodes/qracodes.h @@ -67,10 +67,10 @@ typedef struct { extern "C" { #endif -int qra_encode(const qracode *pcode, int *y, const int *x); -void qra_mfskbesselmetric(float *pix, const float *rsq, const int m, const int N, float EsNoMetric); -int qra_extrinsic(const qracode *pcode, float *pex, const float *pix, int maxiter,float *qra_v2cmsg,float *qra_c2vmsg); -void qra_mapdecode(const qracode *pcode, int *xdec, float *pex, const float *pix); +int qra_encode(const qracode *pcode, int *y, const int *x); +float qra_mfskbesselmetric(float *pix, const float *rsq, const int m, const int N, float EsNoMetric); +int qra_extrinsic(const qracode *pcode, float *pex, const float *pix, int maxiter,float *qra_v2cmsg,float *qra_c2vmsg); +void qra_mapdecode(const qracode *pcode, int *xdec, float *pex, const float *pix); #ifdef __cplusplus }