From 52f2e3bdca474d76d147a8772c05603dd656f5e7 Mon Sep 17 00:00:00 2001 From: "dennis.sheirer" Date: Sun, 1 Mar 2015 15:40:25 +0000 Subject: [PATCH] P25 audio work in progress - started work on exposing 88-bit (11-byte) IMBE voice frames from LDU messages. Added deinterleave, derandomize and the Golay(23,12,7) error detection and correction to LDU voice messages. Hamming edac still remains to be implemented. Plan to create a separate project to produce an imbe FormatConversionProvider that will convert 88-bit imbe frames to PCM audio that is then in a standard format for audio playback, recording, or conversion to another format like shoutcast. --- src/bits/BinaryMessage.java | 35 +- src/decode/p25/P25Decoder.java | 13 +- src/decode/p25/P25Interleave.java | 39 +- src/decode/p25/message/ldu/LDU1Message.java | 2 + src/decode/p25/message/ldu/LDUMessage.java | 398 ++++++++++++++++++-- src/decode/p25/reference/DataUnitID.java | 2 +- src/dsp/gain/DirectGainControl.java | 3 - src/dsp/psk/CQPSKDemodulator.java | 119 +++++- src/edac/Galois24.java | 13 +- 9 files changed, 544 insertions(+), 80 deletions(-) diff --git a/src/bits/BinaryMessage.java b/src/bits/BinaryMessage.java index 17c5ce39..585cf6c7 100644 --- a/src/bits/BinaryMessage.java +++ b/src/bits/BinaryMessage.java @@ -17,6 +17,7 @@ ******************************************************************************/ package bits; +import java.util.Arrays; import java.util.BitSet; import org.slf4j.Logger; @@ -359,7 +360,39 @@ public class BinaryMessage extends BitSet } /** - * Returns the integer value represented by the bit array + * Returns the byte value represented by the bit array + * @param bits - an array of bit positions that will be treated as if they + * were contiguous bits, with index 0 being the MSB and index + * length - 1 being the LSB + * @return - byte value of the bit array + */ + public byte getByte( int[] bits ) + { + if( bits.length != 8 ) + { + throw new IllegalArgumentException( "Invalid - there must be 8" + + "indexes to form a proper byte" ); + } + + int value = 0; + + for( int index: bits ) + { + value = Integer.rotateLeft( value, 1 ); + + if( get( index ) ) + { + value++; + } + } + + mLog.debug( "Value is:" + value ); + + return (byte)( value & 0xFF ); + } + + /** + * Returns the long value represented by the bit array * @param bits - an array of bit positions that will be treated as if they * were contiguous bits, with index 0 being the MSB and index * length - 1 being the LSB diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java index a93a18d9..94be8982 100644 --- a/src/decode/p25/P25Decoder.java +++ b/src/decode/p25/P25Decoder.java @@ -322,10 +322,10 @@ public class P25Decoder extends Decoder mC4FMSlicer.removeListener( slicerTap ); } - if( mCQPSKSlicer != null ) - { - mCQPSKSlicer.removeListener( slicerTap ); - } +// if( mCQPSKSlicer != null ) +// { +// mCQPSKSlicer.removeListener( slicerTap ); +// } break; } } @@ -348,6 +348,11 @@ public class P25Decoder extends Decoder { mSymbolFilter.addListener( listener ); } + + if( mCQPSKDemodulator != null ) + { + mCQPSKDemodulator.addListener( listener ); + } } @Override diff --git a/src/decode/p25/P25Interleave.java b/src/decode/p25/P25Interleave.java index 8e3713ad..60da95eb 100644 --- a/src/decode/p25/P25Interleave.java +++ b/src/decode/p25/P25Interleave.java @@ -39,7 +39,7 @@ public class P25Interleave 111,124,125,126,127,140,141,142,143,156,157,158,159,172,173,174,175,188, 189,190,191 }; - private static int[] VOICE_DEINTERLEAVE = new int[] { 0,24,48,72,96,120,25, + private static int[] VOICE_INTERLEAVE = new int[] { 0,24,48,72,96,120,25, 1,73,49,121,97,2,26,50,74,98,122,27,3,75,51,123,99,4,28,52,76,100,124, 29,5,77,53,125,101,6,30,54,78,102,126,31,7,79,55,127,103,8,32,56,80,104, 128,33,9,81,57,129,105,10,34,58,82,106,130,35,11,83,59,131,107,12,36,60, @@ -48,13 +48,13 @@ public class P25Interleave 67,139,115,20,44,68,92,116,140,45,21,93,69,141,117,22,46,70,94,119,142, 47,23,95,71,143,118 }; - private static int[] VOICE_INTERLEAVE = new int[] { 0,7,12,19,24,31,36,43, + private static int[] VOICE_DEINTERLEAVE = new int[] { 0,7,12,19,24,31,36,43, 48,55,60,67,72,79,84,91,96,103,108,115,120,127,132,139,1,6,13,18,25,30, 37,42,49,54,61,66,73,78,85,90,97,102,109,114,121,126,133,138,2,9,14,21, 26,33,38,45,50,57,62,69,74,81,86,93,98,105,110,117,122,129,134,141,3,8, 15,20,27,32,39,44,51,56,63,68,75,80,87,92,99,104,111,116,123,128,135, 140,4,11,16,23,28,35,40,47,52,59,64,71,76,83,88,95,100,107,112,119,124, - 131,143,136,5,10,17,22,29,34,41,46,53,58,65,70,77,82,89,94,101,106,113, + 131,136,143,5,10,17,22,29,34,41,46,53,58,65,70,77,82,89,94,101,106,113, 118,125,130,137,142 }; /** * Deinterleaves the 196-bit block in message, identified by start and end @@ -140,31 +140,22 @@ public class P25Interleave public static void main( String[] args ) { - Trellis_1_2_Rate halfrate = new Trellis_1_2_Rate(); - int start = 0; - int end = 196; - String interleaved = "0010010100000000000000000000000000000000000010100011001011111010101010101010101010101010010111101010100011010000000000000000000000000000110001011010011010101010101010101010101010101010100101100101"; - - BinaryMessage b = new BinaryMessage( end ); + int end = 144; + int set = 50; - for( int x = 0; x < end; x++ ) - { - if( interleaved.substring( x, x + 1 ).contentEquals( "1" ) ) - { - b.set( x ); - } - } + String interleaved = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + BinaryMessage b = BinaryMessage.load( interleaved ); - mLog.debug( "Interleaved:" + interleaved ); - mLog.debug( " Buffer:" + b.toString() ); + b.set( set ); + + mLog.debug( "INT:" + b.toString() ); - P25Interleave.deinterleaveData( b, start, end ); - - mLog.debug( "Deinterleav:" + b.toString() ); + P25Interleave.deinterleaveVoice( b, start, end ); + + mLog.debug( "DEI:" + b.toString() ); -// halfrate.decode( b, start, end ); -// -// mLog.debug( "Trellis Off:" + b.toString() ); + mLog.debug( "In: " + set + " Out:" + b.nextSetBit( 0 ) ); } } diff --git a/src/decode/p25/message/ldu/LDU1Message.java b/src/decode/p25/message/ldu/LDU1Message.java index 8a7cb1c7..ee498a6e 100644 --- a/src/decode/p25/message/ldu/LDU1Message.java +++ b/src/decode/p25/message/ldu/LDU1Message.java @@ -36,6 +36,8 @@ public class LDU1Message extends LDUMessage mCRC = new CRC[ 2 ]; mCRC[ 0 ] = CRC.PASSED; + //TODO: edac on link control octets + checkCRC(); } diff --git a/src/decode/p25/message/ldu/LDUMessage.java b/src/decode/p25/message/ldu/LDUMessage.java index 96554a97..c55d48c4 100644 --- a/src/decode/p25/message/ldu/LDUMessage.java +++ b/src/decode/p25/message/ldu/LDUMessage.java @@ -6,18 +6,145 @@ import decode.p25.P25Interleave; import decode.p25.message.P25Message; import decode.p25.reference.DataUnitID; import edac.CRC; +import edac.Galois24; public class LDUMessage extends P25Message { - public static final int IMBE_1 = 64; - public static final int IMBE_2 = 208; - public static final int IMBE_3 = 392; - public static final int IMBE_4 = 576; - public static final int IMBE_5 = 760; - public static final int IMBE_6 = 944; - public static final int IMBE_7 = 1128; - public static final int IMBE_8 = 1312; - public static final int IMBE_9 = 1488; + public static final int IMBE_FRAME_1 = 64; + public static final int IMBE_FRAME_2 = 208; + public static final int IMBE_FRAME_3 = 392; + public static final int IMBE_FRAME_4 = 576; + public static final int IMBE_FRAME_5 = 760; + public static final int IMBE_FRAME_6 = 944; + public static final int IMBE_FRAME_7 = 1128; + public static final int IMBE_FRAME_8 = 1312; + public static final int IMBE_FRAME_9 = 1488; + + public static final int[] PN_SEED_1 = { 64,65,66,67,68,69,70,71,72,73,74,75 }; + public static final int[] PN_SEED_2 = { 208,209,210,211,212,213,214,215,216, + 217,218,219 }; + public static final int[] PN_SEED_3 = { 392,393,394,395,396,397,398,399,400, + 401,402,403 }; + public static final int[] PN_SEED_4 = { 576,577,578,579,580,581,582,583,584, + 585,586,587 }; + public static final int[] PN_SEED_5 = { 760,761,762,763,764,765,766,767,768, + 769,770,771 }; + public static final int[] PN_SEED_6 = { 944,945,946,947,948,949,950,951,952, + 953,954,955 }; + public static final int[] PN_SEED_7 = { 1128,1129,1130,1131,1132,1133,1134, + 1135,1136,1137,1138,1139 }; + public static final int[] PN_SEED_8 = { 1312,1313,1314,1315,1316,1317,1318, + 1319,1320,1321,1322,1323 }; + public static final int[] PN_SEED_9 = { 1488,1489,1490,1491,1492,1493,1494, + 1495,1496,1497,1498,1499 }; + + public static final int[] FRAME1_1= { 64,65,66,67,68,69,70,71 }; + public static final int[] FRAME1_2= { 72,73,74,75,87,88,89,90 }; + public static final int[] FRAME1_3= { 91,92,93,94,95,96,97,98 }; + public static final int[] FRAME1_4= { 110,111,112,113,114,115,116,117 }; + public static final int[] FRAME1_5= { 118,119,120,121,133,134,135,136 }; + public static final int[] FRAME1_6= { 137,138,139,140,141,142,143,144 }; + public static final int[] FRAME1_7= { 156,157,158,159,160,161,162,163 }; + public static final int[] FRAME1_8= { 164,165,166,171,172,173,174,175 }; + public static final int[] FRAME1_9= { 176,177,178,179,180,181,186,187 }; + public static final int[] FRAME1_10= { 188,189,190,191,192,193,194,195 }; + public static final int[] FRAME1_11= { 196,201,202,203,204,205,206,207 }; + + public static final int[] FRAME2_1= { 208,209,210,211,212,213,214,215 }; + public static final int[] FRAME2_2= { 216,217,218,219,231,232,233,234 }; + public static final int[] FRAME2_3= { 235,236,237,238,239,240,241,242 }; + public static final int[] FRAME2_4= { 254,255,256,257,258,259,260,261 }; + public static final int[] FRAME2_5= { 262,263,264,265,277,278,279,280 }; + public static final int[] FRAME2_6= { 281,282,283,284,285,286,287,288 }; + public static final int[] FRAME2_7= { 300,301,302,303,304,305,306,307 }; + public static final int[] FRAME2_8= { 308,309,310,315,316,317,318,319 }; + public static final int[] FRAME2_9= { 320,321,322,323,324,325,330,331 }; + public static final int[] FRAME2_10= { 332,333,334,335,336,337,338,339 }; + public static final int[] FRAME2_11= { 340,345,346,347,348,349,350,351 }; + + public static final int[] FRAME3_1= { 392,393,394,395,396,397,398,399 }; + public static final int[] FRAME3_2= { 400,401,402,403,415,416,417,418 }; + public static final int[] FRAME3_3= { 419,420,421,422,423,424,425,426 }; + public static final int[] FRAME3_4= { 438,439,440,441,442,443,444,445 }; + public static final int[] FRAME3_5= { 446,447,448,449,461,462,463,464 }; + public static final int[] FRAME3_6= { 465,466,467,468,469,470,471,472 }; + public static final int[] FRAME3_7= { 484,485,486,487,488,489,490,491 }; + public static final int[] FRAME3_8= { 492,493,494,499,500,501,502,503 }; + public static final int[] FRAME3_9= { 504,505,506,507,508,509,514,515 }; + public static final int[] FRAME3_10= { 516,517,518,519,520,521,522,523 }; + public static final int[] FRAME3_11= { 524,529,530,531,532,533,534,535 }; + + public static final int[] FRAME4_1= { 576,577,578,579,580,581,582,583 }; + public static final int[] FRAME4_2= { 584,585,586,587,599,600,601,602 }; + public static final int[] FRAME4_3= { 603,604,605,606,607,608,609,610 }; + public static final int[] FRAME4_4= { 622,623,624,625,626,627,628,629 }; + public static final int[] FRAME4_5= { 630,631,632,633,645,646,647,648 }; + public static final int[] FRAME4_6= { 649,650,651,652,653,654,655,656 }; + public static final int[] FRAME4_7= { 668,669,670,671,672,673,674,675 }; + public static final int[] FRAME4_8= { 676,677,678,683,684,685,686,687 }; + public static final int[] FRAME4_9= { 688,689,690,691,692,693,698,699 }; + public static final int[] FRAME4_10= { 700,701,702,703,704,705,706,707 }; + public static final int[] FRAME4_11= { 708,713,714,715,716,717,718,719 }; + + public static final int[] FRAME5_1= { 760,761,762,763,764,765,766,767 }; + public static final int[] FRAME5_2= { 768,769,770,771,783,784,785,786 }; + public static final int[] FRAME5_3= { 787,788,789,790,791,792,793,794 }; + public static final int[] FRAME5_4= { 806,807,808,809,810,811,812,813 }; + public static final int[] FRAME5_5= { 814,815,816,817,829,830,831,832 }; + public static final int[] FRAME5_6= { 833,834,835,836,837,838,839,840 }; + public static final int[] FRAME5_7= { 852,853,854,855,856,857,858,859 }; + public static final int[] FRAME5_8= { 860,861,862,867,868,869,870,871 }; + public static final int[] FRAME5_9= { 872,873,874,875,876,877,882,883 }; + public static final int[] FRAME5_10= { 884,885,886,887,888,889,890,891 }; + public static final int[] FRAME5_11= { 892,897,898,899,900,901,902,903 }; + + public static final int[] FRAME6_1= { 944,945,946,947,948,949,950,951 }; + public static final int[] FRAME6_2= { 952,953,954,955,967,968,969,970 }; + public static final int[] FRAME6_3= { 971,972,973,974,975,976,977,978 }; + public static final int[] FRAME6_4= { 990,991,992,993,994,995,996,997 }; + public static final int[] FRAME6_5= { 998,999,1000,1001,1013,1014,1015,1016 }; + public static final int[] FRAME6_6= { 1017,1018,1019,1020,1021,1022,1023,1024 }; + public static final int[] FRAME6_7= { 1036,1037,1038,1039,1040,1041,1042,1043 }; + public static final int[] FRAME6_8= { 1044,1045,1046,1051,1052,1053,1054,1055 }; + public static final int[] FRAME6_9= { 1056,1057,1058,1059,1060,1061,1066,1067 }; + public static final int[] FRAME6_10= { 1068,1069,1070,1071,1072,1073,1074,1075 }; + public static final int[] FRAME6_11= { 1076,1081,1082,1083,1084,1085,1086,1087 }; + + public static final int[] FRAME7_1= { 1128,1129,1130,1131,1132,1133,1134,1135 }; + public static final int[] FRAME7_2= { 1136,1137,1138,1139,1151,1152,1153,1154 }; + public static final int[] FRAME7_3= { 1155,1156,1157,1158,1159,1160,1161,1162 }; + public static final int[] FRAME7_4= { 1174,1175,1176,1177,1178,1179,1180,1181 }; + public static final int[] FRAME7_5= { 1182,1183,1184,1185,1197,1198,1199,1200 }; + public static final int[] FRAME7_6= { 1201,1202,1203,1204,1205,1206,1207,1208 }; + public static final int[] FRAME7_7= { 1220,1221,1222,1223,1224,1225,1226,1227 }; + public static final int[] FRAME7_8= { 1228,1229,1230,1235,1236,1237,1238,1239 }; + public static final int[] FRAME7_9= { 1240,1241,1242,1243,1244,1245,1250,1251 }; + public static final int[] FRAME7_10= { 1252,1253,1254,1255,1256,1257,1258,1259 }; + public static final int[] FRAME7_11= { 1260,1265,1266,1267,1268,1269,1270,1271 }; + + public static final int[] FRAME8_1= { 1312,1313,1314,1315,1316,1317,1318,1319 }; + public static final int[] FRAME8_2= { 1320,1321,1322,1323,1335,1336,1337,1338 }; + public static final int[] FRAME8_3= { 1339,1340,1341,1342,1343,1344,1345,1346 }; + public static final int[] FRAME8_4= { 1358,1359,1360,1361,1362,1363,1364,1365 }; + public static final int[] FRAME8_5= { 1366,1367,1368,1369,1381,1382,1383,1384 }; + public static final int[] FRAME8_6= { 1385,1386,1387,1388,1389,1390,1391,1392 }; + public static final int[] FRAME8_7= { 1404,1405,1406,1407,1408,1409,1410,1411 }; + public static final int[] FRAME8_8= { 1412,1413,1414,1419,1420,1421,1422,1423 }; + public static final int[] FRAME8_9= { 1424,1425,1426,1427,1428,1429,1434,1435 }; + public static final int[] FRAME8_10= { 1436,1437,1438,1439,1440,1441,1442,1443 }; + public static final int[] FRAME8_11= { 1444,1449,1450,1451,1452,1453,1454,1455 }; + + public static final int[] FRAME9_1= { 1488,1489,1490,1491,1492,1493,1494,1495 }; + public static final int[] FRAME9_2= { 1496,1497,1498,1499,1511,1512,1513,1514 }; + public static final int[] FRAME9_3= { 1515,1516,1517,1518,1519,1520,1521,1522 }; + public static final int[] FRAME9_4= { 1534,1535,1536,1537,1538,1539,1540,1541 }; + public static final int[] FRAME9_5= { 1542,1543,1544,1545,1557,1558,1559,1560 }; + public static final int[] FRAME9_6= { 1561,1562,1563,1564,1565,1566,1567,1568 }; + public static final int[] FRAME9_7= { 1580,1581,1582,1583,1584,1585,1586,1587 }; + public static final int[] FRAME9_8= { 1588,1589,1590,1595,1596,1597,1598,1599 }; + public static final int[] FRAME9_9= { 1600,1601,1602,1603,1604,1605,1610,1611 }; + public static final int[] FRAME9_10= { 1612,1613,1614,1615,1616,1617,1618,1619 }; + public static final int[] FRAME9_11= { 1620,1625,1626,1627,1628,1629,1630,1631 }; public static final int[] LOW_SPEED_DATA = { 1456,1457,1458,1459,1460,1461, 1462,1463,1472,1473,1474,1475,1476,1477,1478,1479 }; @@ -27,10 +154,21 @@ public class LDUMessage extends P25Message { super( message, duid, aliasList ); - deinterleaveVoiceFrames(); + deinterleave(); + + /* Perform error detection & correction on coset zero of each frame */ + errorCorrectCosetZero(); + + /* Remove randomizer from coset words c1 - c6 */ + derandomize(); + + /* Perform edac against remaining info vectors */ + errorCorrectInformationVectors(); /* NID CRC is checked in the message framer, thus a constructed message - * means it passed the CRC */ + * means it passed the CRC */ + +//TODO: update crc status for voice frames mCRC = new CRC[ 2 ]; mCRC[ 0 ] = CRC.PASSED; } @@ -61,22 +199,240 @@ public class LDUMessage extends P25Message return mMessage.getHex( LOW_SPEED_DATA, 4 ); } - private void deinterleaveVoiceFrames() + /** + * Deinterleaves 144 message bits of each of the 9 imbe voice frames + */ + private void deinterleave() { - P25Interleave.deinterleaveVoice( mMessage, IMBE_1, IMBE_1 + 144 ); - P25Interleave.deinterleaveVoice( mMessage, IMBE_2, IMBE_2 + 144 ); - P25Interleave.deinterleaveVoice( mMessage, IMBE_3, IMBE_3 + 144 ); - P25Interleave.deinterleaveVoice( mMessage, IMBE_4, IMBE_4 + 144 ); - P25Interleave.deinterleaveVoice( mMessage, IMBE_5, IMBE_5 + 144 ); - P25Interleave.deinterleaveVoice( mMessage, IMBE_6, IMBE_6 + 144 ); - P25Interleave.deinterleaveVoice( mMessage, IMBE_7, IMBE_7 + 144 ); - P25Interleave.deinterleaveVoice( mMessage, IMBE_8, IMBE_8 + 144 ); - P25Interleave.deinterleaveVoice( mMessage, IMBE_9, IMBE_9 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_1, IMBE_FRAME_1 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_2, IMBE_FRAME_2 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_3, IMBE_FRAME_3 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_4, IMBE_FRAME_4 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_5, IMBE_FRAME_5 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_6, IMBE_FRAME_6 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_7, IMBE_FRAME_7 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_8, IMBE_FRAME_8 + 144 ); + P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_9, IMBE_FRAME_9 + 144 ); } + + + /** + * Performs error detection and correction on coset work zero of each of the + * 9 imbe voice frames. + */ + private void errorCorrectCosetZero() + { + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_1 ); + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_2 ); + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_3 ); + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_4 ); + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_5 ); + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_6 ); + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_7 ); + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_8 ); + Galois24.checkAndCorrect( mMessage, IMBE_FRAME_9 ); + } + + /** + * Generates a pseudo-random noise randomizer mask and applies (xor) the + * mask against coset words c1 through c6 of each of the 9 imbe voice frames + */ + private void derandomize() + { + derandomize( PN_SEED_1 ); + derandomize( PN_SEED_2 ); + derandomize( PN_SEED_3 ); + derandomize( PN_SEED_4 ); + derandomize( PN_SEED_5 ); + derandomize( PN_SEED_6 ); + derandomize( PN_SEED_7 ); + derandomize( PN_SEED_8 ); + derandomize( PN_SEED_9 ); + } + + /** + * Perform error detection and correction against information vectors 1 - 6 + */ + private void errorCorrectInformationVectors() + { + errorCorrectInformationVectors( IMBE_FRAME_1 ); + errorCorrectInformationVectors( IMBE_FRAME_2 ); + errorCorrectInformationVectors( IMBE_FRAME_3 ); + errorCorrectInformationVectors( IMBE_FRAME_4 ); + errorCorrectInformationVectors( IMBE_FRAME_5 ); + errorCorrectInformationVectors( IMBE_FRAME_6 ); + errorCorrectInformationVectors( IMBE_FRAME_7 ); + errorCorrectInformationVectors( IMBE_FRAME_8 ); + errorCorrectInformationVectors( IMBE_FRAME_9 ); + } + + /** + * Performs Golay(23,12,7) error detection and correction against information + * vectors 1 - 3 and Hamming(15,11,3) against vectors 4 - 6. + * + * @param frameStart - bit position of the imbe frame start + */ + private void errorCorrectInformationVectors( int frameStart ) + { + Galois24.checkAndCorrect( mMessage, frameStart + 23 ); + Galois24.checkAndCorrect( mMessage, frameStart + 46 ); + Galois24.checkAndCorrect( mMessage, frameStart + 69 ); + + //TODO: hamming edac for iv 4,5,6 ... + } + /* Temporary override */ public boolean isValid() { return true; } + + /** + * Removes randomizer by generating a pseudo-random noise sequence from the + * first 12 bits of coset word c0 and applies (xor) that sequence against + * message coset words c1 through c6. + * + * @param seedBits - first 12 bit indexes of coset word c0 + */ + public void derandomize( int[] seedBits ) + { + /* Set the offset to the first seed bit plus 23 to point to coset c1 */ + int offset = seedBits[ 0 ] + 23; + + /* Get seed value from first 12 bits of coset c0 */ + int seed = mMessage.getInt( seedBits ); + + /* Left shift 4 places (multiply by 16) */ + seed <<= 4; + + for( int x = 0; x < 114; x++ ) + { + seed = ( ( 173 * seed ) + 13849 ) & 65536; + + /* If seed bit 15 is set, toggle the corresponding message bit */ + if( ( seed & 32768 ) == 32768 ) + { + mMessage.flip( x + offset ); + } + } + } + + /** + * Extracts 9 IMBE voice frames of 11-bytes (88-bit) each into a 99 byte array + */ + public byte[] getIMBEFrameData() + { + byte[] data = new byte[ 99 ]; + + data[ 0 ] = mMessage.getByte( FRAME1_1 ); + data[ 1 ] = mMessage.getByte( FRAME1_2 ); + data[ 2 ] = mMessage.getByte( FRAME1_3 ); + data[ 3 ] = mMessage.getByte( FRAME1_4 ); + data[ 4 ] = mMessage.getByte( FRAME1_5 ); + data[ 5 ] = mMessage.getByte( FRAME1_6 ); + data[ 6 ] = mMessage.getByte( FRAME1_7 ); + data[ 7 ] = mMessage.getByte( FRAME1_8 ); + data[ 8 ] = mMessage.getByte( FRAME1_9 ); + data[ 9 ] = mMessage.getByte( FRAME1_10 ); + data[ 10 ] = mMessage.getByte( FRAME1_11 ); + + data[ 11 ] = mMessage.getByte( FRAME2_1 ); + data[ 12 ] = mMessage.getByte( FRAME2_2 ); + data[ 13 ] = mMessage.getByte( FRAME2_3 ); + data[ 14 ] = mMessage.getByte( FRAME2_4 ); + data[ 15 ] = mMessage.getByte( FRAME2_5 ); + data[ 16 ] = mMessage.getByte( FRAME2_6 ); + data[ 17 ] = mMessage.getByte( FRAME2_7 ); + data[ 18 ] = mMessage.getByte( FRAME2_8 ); + data[ 19 ] = mMessage.getByte( FRAME2_9 ); + data[ 20 ] = mMessage.getByte( FRAME2_10 ); + data[ 21 ] = mMessage.getByte( FRAME2_11 ); + + data[ 22 ] = mMessage.getByte( FRAME3_1 ); + data[ 23 ] = mMessage.getByte( FRAME3_2 ); + data[ 24 ] = mMessage.getByte( FRAME3_3 ); + data[ 25 ] = mMessage.getByte( FRAME3_4 ); + data[ 26 ] = mMessage.getByte( FRAME3_5 ); + data[ 27 ] = mMessage.getByte( FRAME3_6 ); + data[ 28 ] = mMessage.getByte( FRAME3_7 ); + data[ 29 ] = mMessage.getByte( FRAME3_8 ); + data[ 30 ] = mMessage.getByte( FRAME3_9 ); + data[ 31 ] = mMessage.getByte( FRAME3_10 ); + data[ 32 ] = mMessage.getByte( FRAME3_11 ); + + data[ 33 ] = mMessage.getByte( FRAME4_1 ); + data[ 34 ] = mMessage.getByte( FRAME4_2 ); + data[ 35 ] = mMessage.getByte( FRAME4_3 ); + data[ 36 ] = mMessage.getByte( FRAME4_4 ); + data[ 37 ] = mMessage.getByte( FRAME4_5 ); + data[ 38 ] = mMessage.getByte( FRAME4_6 ); + data[ 39 ] = mMessage.getByte( FRAME4_7 ); + data[ 40 ] = mMessage.getByte( FRAME4_8 ); + data[ 41 ] = mMessage.getByte( FRAME4_9 ); + data[ 42 ] = mMessage.getByte( FRAME4_10 ); + data[ 43 ] = mMessage.getByte( FRAME4_11 ); + + data[ 44 ] = mMessage.getByte( FRAME5_1 ); + data[ 45 ] = mMessage.getByte( FRAME5_2 ); + data[ 46 ] = mMessage.getByte( FRAME5_3 ); + data[ 47 ] = mMessage.getByte( FRAME5_4 ); + data[ 48 ] = mMessage.getByte( FRAME5_5 ); + data[ 49 ] = mMessage.getByte( FRAME5_6 ); + data[ 50 ] = mMessage.getByte( FRAME5_7 ); + data[ 51 ] = mMessage.getByte( FRAME5_8 ); + data[ 52 ] = mMessage.getByte( FRAME5_9 ); + data[ 53 ] = mMessage.getByte( FRAME5_10 ); + data[ 54 ] = mMessage.getByte( FRAME5_11 ); + + data[ 55 ] = mMessage.getByte( FRAME6_1 ); + data[ 56 ] = mMessage.getByte( FRAME6_2 ); + data[ 57 ] = mMessage.getByte( FRAME6_3 ); + data[ 58 ] = mMessage.getByte( FRAME6_4 ); + data[ 59 ] = mMessage.getByte( FRAME6_5 ); + data[ 60 ] = mMessage.getByte( FRAME6_6 ); + data[ 61 ] = mMessage.getByte( FRAME6_7 ); + data[ 62 ] = mMessage.getByte( FRAME6_8 ); + data[ 63 ] = mMessage.getByte( FRAME6_9 ); + data[ 64 ] = mMessage.getByte( FRAME6_10 ); + data[ 65 ] = mMessage.getByte( FRAME6_11 ); + + data[ 66 ] = mMessage.getByte( FRAME7_1 ); + data[ 67 ] = mMessage.getByte( FRAME7_2 ); + data[ 68 ] = mMessage.getByte( FRAME7_3 ); + data[ 69 ] = mMessage.getByte( FRAME7_4 ); + data[ 70 ] = mMessage.getByte( FRAME7_5 ); + data[ 71 ] = mMessage.getByte( FRAME7_6 ); + data[ 72 ] = mMessage.getByte( FRAME7_7 ); + data[ 73 ] = mMessage.getByte( FRAME7_8 ); + data[ 74 ] = mMessage.getByte( FRAME7_9 ); + data[ 75 ] = mMessage.getByte( FRAME7_10 ); + data[ 76 ] = mMessage.getByte( FRAME7_11 ); + + data[ 77 ] = mMessage.getByte( FRAME8_1 ); + data[ 78 ] = mMessage.getByte( FRAME8_2 ); + data[ 79 ] = mMessage.getByte( FRAME8_3 ); + data[ 80 ] = mMessage.getByte( FRAME8_4 ); + data[ 81 ] = mMessage.getByte( FRAME8_5 ); + data[ 82 ] = mMessage.getByte( FRAME8_6 ); + data[ 83 ] = mMessage.getByte( FRAME8_7 ); + data[ 84 ] = mMessage.getByte( FRAME8_8 ); + data[ 85 ] = mMessage.getByte( FRAME8_9 ); + data[ 86 ] = mMessage.getByte( FRAME8_10 ); + data[ 87 ] = mMessage.getByte( FRAME8_11 ); + + data[ 88 ] = mMessage.getByte( FRAME9_1 ); + data[ 89 ] = mMessage.getByte( FRAME9_2 ); + data[ 90 ] = mMessage.getByte( FRAME9_3 ); + data[ 91 ] = mMessage.getByte( FRAME9_4 ); + data[ 92 ] = mMessage.getByte( FRAME9_5 ); + data[ 93 ] = mMessage.getByte( FRAME9_6 ); + data[ 94 ] = mMessage.getByte( FRAME9_7 ); + data[ 95 ] = mMessage.getByte( FRAME9_8 ); + data[ 96 ] = mMessage.getByte( FRAME9_9 ); + data[ 97 ] = mMessage.getByte( FRAME9_10 ); + data[ 98 ] = mMessage.getByte( FRAME9_11 ); + + return data; + } } diff --git a/src/decode/p25/reference/DataUnitID.java b/src/decode/p25/reference/DataUnitID.java index 4e318b2e..31e8bcd2 100644 --- a/src/decode/p25/reference/DataUnitID.java +++ b/src/decode/p25/reference/DataUnitID.java @@ -9,7 +9,7 @@ public enum DataUnitID NID ( -1, 64, false, "NID ", "Network and Data Unit ID" ), HDU ( 0, 712, false, "HDU ", "Header Data Unit" ), TDU ( 3, 64, false, "TDU ", "Simple Terminator Data Unit" ), - LDU1 ( 5, 1630, true, "LDU1 ", "Logical Link Data Unit 1" ), + LDU1 ( 5, 1632, true, "LDU1 ", "Logical Link Data Unit 1" ), TSBK1( 7, 260, false, "TSBK1", "Trunking Signaling Block" ), TSBK2( 7, 260, false, "TSBK2", "Trunking Signaling Block" ), TSBK3( 7, 260, false, "TSBK3", "Trunking Signaling Block" ), diff --git a/src/dsp/gain/DirectGainControl.java b/src/dsp/gain/DirectGainControl.java index a83523bc..78223427 100644 --- a/src/dsp/gain/DirectGainControl.java +++ b/src/dsp/gain/DirectGainControl.java @@ -71,8 +71,6 @@ public class DirectGainControl implements GainController, RealSampleListener { mGain = mMaximum; } - - mLog.debug( "Gain Increase: " + mGain ); } @Override @@ -84,7 +82,6 @@ public class DirectGainControl implements GainController, RealSampleListener { mGain = mMinimum; } - mLog.debug( "Gain Decrease: " + mGain ); } public void setListener( RealSampleListener listener ) diff --git a/src/dsp/psk/CQPSKDemodulator.java b/src/dsp/psk/CQPSKDemodulator.java index 042cb160..05105c24 100644 --- a/src/dsp/psk/CQPSKDemodulator.java +++ b/src/dsp/psk/CQPSKDemodulator.java @@ -36,6 +36,10 @@ import org.slf4j.LoggerFactory; import sample.Listener; import sample.Provider; import sample.complex.ComplexSample; +import source.tuner.FrequencyChangeEvent; +import source.tuner.FrequencyChangeEvent.Attribute; +import source.tuner.FrequencyChangeListener; +import buffer.FloatAveragingBuffer; import dsp.filter.interpolator.QPSKInterpolator; /** @@ -51,11 +55,13 @@ public class CQPSKDemodulator implements Listener, { private final static Logger mLog = LoggerFactory.getLogger( CQPSKDemodulator.class ); - + private Listener mListener; private GardnerDetector mGardnerDetector = new GardnerDetector(); + private FrequencyChangeListener mFrequencyChangeListener; + public CQPSKDemodulator() { } @@ -82,6 +88,75 @@ public class CQPSKDemodulator implements Listener, return value; } + + public void addListener( FrequencyChangeListener listener ) + { + mFrequencyChangeListener = listener; + } + + public void removeListener( FrequencyChangeListener listener ) + { + mFrequencyChangeListener = null; + } + + /** + * Processes the current loop frequency of the costas loop and broadcasts + * tuner frequency offset adjustments 10 times a second as needed to keep + * the frequency offset below half of the maximum frequency + */ + public class FrequencyControl + { + /* Set the trigger threshold at half of the maximum costas loop control + * frequency */ + private static final float CORRECTION_THRESHOLD_FREQUENCY = + ( 2.0f * (float)Math.PI * 600.0f ) / 48000.0f; + + private FloatAveragingBuffer mBuffer = new FloatAveragingBuffer( 40 ); + + private long mFrequencyError = 0; + + private int mCounter = 0; + + public void receive( float frequency ) + { +// mCounter++; +// +// if( mCounter >= 480 ) +// { +// float average = mBuffer.get( frequency ); +// mLog.debug( "Avg:" + average + " Threshold:" + CORRECTION_THRESHOLD_FREQUENCY); +// +// boolean correctionNeeded = false; +// +// if( average > CORRECTION_THRESHOLD_FREQUENCY ) +// { +// mFrequencyError--; +// +// correctionNeeded = true; +// } +// else if( average < -CORRECTION_THRESHOLD_FREQUENCY ) +// { +// mFrequencyError++; +// +// correctionNeeded = true; +// } +// +// if( correctionNeeded && mFrequencyChangeListener != null ) +// { +// mLog.debug( "Issuing Frequency Correction: " + mFrequencyError ); +// mFrequencyChangeListener.frequencyChanged( +// new FrequencyChangeEvent( Attribute.FREQUENCY_ERROR, +// mFrequencyError ) ); +// } +// +// mCounter = 0; +// } +// else +// { +// mBuffer.get( frequency ); +// } + } + } /** * Gardner symbol timing detector @@ -209,10 +284,15 @@ public class CQPSKDemodulator implements Listener, } /** - * Costas Loop - phase locked loop synchronized to the incoming signal. + * Costas Loop - phase locked loop synchronized to the frequency offset of + * the incoming signal to enable the signal to be mixed down to zero offset + * for proper synchronization. The mFrequency value indicates the detected + * frequency offset. We attempt to keep that value close to zero by issuing + * frequency adjustments to the tuner channel source. * - * Ported from gnuradio/control_loop and gnuradio/mpsk_receiver_cc - * using initialization values from KA1RBI's OP25/cqpsk.py + * Most of the costas loop code was ported from gnuradio/control_loop and + * gnuradio/mpsk_receiver_cc using initialization values from KA1RBI's + * OP25/cqpsk.py */ public class CostasLoop implements Listener { @@ -221,6 +301,9 @@ public class CQPSKDemodulator implements Listener, /* 45 degree rotation angle */ public static final float THETA = (float)( Math.PI / 4.0d ); + private static final float MAXIMUM_FREQUENCY = ( 2.0f * (float)Math.PI * + 1200.0f ) / 48000.0f; + /* http://www.trondeau.com/blog/2011/8/13/control-loop-gain-values.html */ private float mDamping = (float)Math.sqrt( 2.0 ) / 2.0f; @@ -238,9 +321,8 @@ public class CQPSKDemodulator implements Listener, private float mLoopPhase = 0.0f; private float mLoopFrequency = 0.0f; - - private float mMaximumFrequency = ( 2.0f * (float)Math.PI * 1200.0f ) / - 48000.0f; + + private FrequencyControl mFrequencyControl = new FrequencyControl(); public CostasLoop() { @@ -289,12 +371,6 @@ public class CQPSKDemodulator implements Listener, /* Adjust for phase error */ adjust( phaseError ); - - /* Maintain phase between +/- 2 * PI */ - unwrapPhase(); - - /* Limit frequency to +/- maximum frequency */ - limitFrequency(); } /** @@ -307,18 +383,27 @@ public class CQPSKDemodulator implements Listener, { mLoopFrequency += mBetaGain * phase_error; mLoopPhase += mLoopFrequency + mAlphaGain * phase_error; + + /* Maintain phase between +/- 2 * PI */ + unwrapPhase(); + + /* Limit frequency to +/- maximum frequency */ + limitFrequency(); } private void limitFrequency() { - if( mLoopFrequency > mMaximumFrequency ) + /* Check for and issue tuner offset correction */ + mFrequencyControl.receive( mLoopFrequency ); + + if( mLoopFrequency > MAXIMUM_FREQUENCY ) { - mLoopFrequency = mMaximumFrequency; + mLoopFrequency = MAXIMUM_FREQUENCY; } - if( mLoopFrequency < -mMaximumFrequency ) + if( mLoopFrequency < -MAXIMUM_FREQUENCY ) { - mLoopFrequency = -mMaximumFrequency; + mLoopFrequency = -MAXIMUM_FREQUENCY; } } diff --git a/src/edac/Galois24.java b/src/edac/Galois24.java index fc2f62ce..04d60b02 100644 --- a/src/edac/Galois24.java +++ b/src/edac/Galois24.java @@ -161,21 +161,16 @@ public class Galois24 public static void main( String[] args ) { - /* NID + TDULC message */ - String msg = "0010011000001111100100110011000100111010000111010001000000000000000000000000000000000000000000000100101010010111000000000000000000000000000010010111101111111111000000000000000000000000000000000000000000000000000110001011100001111101010110111001001011010010101111010010000010110011101111111100100000010001111100011010010100001101111001111001100000111000"; + String msg = "01001111011100100011011"; BinaryMessage message = BinaryMessage.load( msg ); mLog.debug( "MSG: " + message.toString() ); - int x = 64; + int x = 0; - while( x < msg.length() ) - { - message = checkAndCorrect( message, x ); + message = checkAndCorrect( message, x ); - mLog.debug( "X:" + x + " Pass:" + ( message.getCRC()== CRC.PASSED ) ); - x += 24; - } + mLog.debug( "MSG: " + message.toString() + " Pass:" + ( message.getCRC()== CRC.PASSED ) ); } }