@@ -81,7 +81,6 @@ kv4p HT (see http://kv4p.com)
8181
8282import org .apache .commons .lang3 .ArrayUtils ;
8383
84- import java .io .ByteArrayOutputStream ;
8584import java .io .UnsupportedEncodingException ;
8685import java .nio .ByteBuffer ;
8786import java .nio .charset .StandardCharsets ;
@@ -151,11 +150,10 @@ public class RadioAudioService extends Service {
151150 private LiveData <List <ChannelMemory >> channelMemoriesLiveData = null ;
152151
153152 // Delimiter must match ESP32 code
154- private static final byte [] COMMAND_DELIMITER = new byte [] {(byte )0xFF , (byte )0x00 , (byte )0xFF , (byte )0x00 , (byte )0xFF , (byte )0x00 , (byte )0xFF , (byte )0x00 };
153+ static final byte [] COMMAND_DELIMITER = new byte [] {(byte )0xDE , (byte )0xAD , (byte )0xBE , (byte )0xEF , (byte )0xDE , (byte )0xAD , (byte )0xBE , (byte )0xEF };
155154 private static final byte COMMAND_SMETER_REPORT = 0x53 ; // Ascii "S"
156155
157- // This buffer holds leftover data that wasn’t fully parsed yet (from ESP32 audio stream)
158- private final ByteArrayOutputStream leftoverBuffer = new ByteArrayOutputStream ();
156+ private final RxStreamParser rxStreamParser = new RxStreamParser (this ::handleParsedCommand );
159157
160158 // AFSK modem
161159 private Afsk1200Modulator afskModulator = null ;
@@ -1333,7 +1331,7 @@ private void handleESP32Data(byte[] data) {
13331331
13341332 if (mode == MODE_RX || mode == MODE_SCAN ) {
13351333 // Handle and remove any commands (e.g. S-meter updates) embedded in the audio.
1336- data = extractAudioAndHandleCommands (data );
1334+ data = rxStreamParser . extractAudioAndHandleCommands (data );
13371335
13381336 if (prebufferComplete && audioTrack != null ) {
13391337 synchronized (audioTrack ) {
@@ -1403,145 +1401,6 @@ private void handleESP32Data(byte[] data) {
14031401 }
14041402 }
14051403
1406- private synchronized byte [] extractAudioAndHandleCommands (byte [] newData ) {
1407- // 1. Append the new data to leftover.
1408- leftoverBuffer .write (newData , 0 , newData .length );
1409- byte [] buffer = leftoverBuffer .toByteArray ();
1410-
1411- ByteArrayOutputStream audioOut = new ByteArrayOutputStream ();
1412- int parsePos = 0 ;
1413-
1414- while (true ) {
1415- int startDelim = indexOf (buffer , COMMAND_DELIMITER , parsePos );
1416- if (startDelim == -1 ) {
1417- // -- NO FULL DELIMITER FOUND IN [buffer] STARTING AT parsePos --
1418-
1419- // We might have a *partial* delimiter at the tail of [buffer].
1420- // Figure out how many trailing bytes might match the start of the next command.
1421- int partialLen = findPartialDelimiterTail (buffer , parsePos , buffer .length );
1422-
1423- // "pureAudioEnd" is where pure audio stops and partial leftover begins.
1424- int pureAudioEnd = buffer .length - partialLen ;
1425-
1426- // Write the "definitely audio" portion to our output.
1427- if (pureAudioEnd > parsePos ) {
1428- audioOut .write (buffer , parsePos , pureAudioEnd - parsePos );
1429- }
1430-
1431- // Store ONLY the partial leftover so we can complete the delimiter/command next time.
1432- leftoverBuffer .reset ();
1433- if (partialLen > 0 ) {
1434- leftoverBuffer .write (buffer , pureAudioEnd , partialLen );
1435- }
1436-
1437- // Return everything we've decoded as audio so far.
1438- return audioOut .toByteArray ();
1439- }
1440-
1441- // -- FOUND A DELIMITER --
1442- // Everything from parsePos..(startDelim) is audio
1443- if (startDelim > parsePos ) {
1444- audioOut .write (buffer , parsePos , startDelim - parsePos );
1445- }
1446-
1447- // Check if we have enough bytes for "delimiter + cmd + paramLen"
1448- int neededBeforeParams = COMMAND_DELIMITER .length + 2 ;
1449- // (1 for cmd byte, 1 for paramLen byte)
1450- if (startDelim + neededBeforeParams > buffer .length ) {
1451- // Not enough data => partial command leftover
1452- storeTailForNextTime (buffer , startDelim );
1453- return audioOut .toByteArray ();
1454- }
1455-
1456- int cmdPos = startDelim + COMMAND_DELIMITER .length ;
1457- byte cmd = buffer [cmdPos ];
1458- int paramLen = (buffer [cmdPos + 1 ] & 0xFF );
1459- int paramStart = cmdPos + 2 ;
1460- int paramEnd = paramStart + paramLen ; // one past the last param byte
1461-
1462- if (paramEnd > buffer .length ) {
1463- // Again, partial command leftover
1464- storeTailForNextTime (buffer , startDelim );
1465- return audioOut .toByteArray ();
1466- }
1467-
1468- // We have a full command => handle it
1469- byte [] param = Arrays .copyOfRange (buffer , paramStart , paramEnd );
1470- handleParsedCommand (cmd , param );
1471-
1472- // Advance parsePos beyond this entire command block
1473- parsePos = paramEnd ;
1474- }
1475- }
1476-
1477- /**
1478- * Stores the tail of 'buffer' from 'startIndex' to end into leftoverBuffer,
1479- * for the next invocation of extractAudioAndHandleCommands().
1480- */
1481- private void storeTailForNextTime (byte [] buffer , int startIndex ) {
1482- leftoverBuffer .reset ();
1483- leftoverBuffer .write (buffer , startIndex , buffer .length - startIndex );
1484- }
1485-
1486- /**
1487- * Finds the first occurrence of 'pattern' in 'data' at or after 'start'.
1488- * Returns -1 if not found.
1489- */
1490- private int indexOf (byte [] data , byte [] pattern , int start ) {
1491- if (pattern .length == 0 || start >= data .length ) {
1492- return -1 ;
1493- }
1494- for (int i = start ; i <= data .length - pattern .length ; i ++) {
1495- boolean found = true ;
1496- for (int j = 0 ; j < pattern .length ; j ++) {
1497- if (data [i + j ] != pattern [j ]) {
1498- found = false ;
1499- break ;
1500- }
1501- }
1502- if (found ) {
1503- return i ;
1504- }
1505- }
1506- return -1 ;
1507- }
1508-
1509- /**
1510- * Checks how many trailing bytes in [data, from parsePos..end) might match the
1511- * *start* of our delimiter (or partial command).
1512- *
1513- * For example, if COMMAND_DELIMITER = { (byte)0xFF, (byte)0x00, (byte)0xFF, (byte)0x00 },
1514- * we see if the tail ends with 1, 2, or 3 bytes that match the first 1, 2, or 3 bytes
1515- * of COMMAND_DELIMITER.
1516- *
1517- * Return value: the number of trailing bytes that match
1518- * (range 0..COMMAND_DELIMITER.length - 1).
1519- */
1520- private int findPartialDelimiterTail (byte [] data , int start , int end ) {
1521- final int dataLen = end - start ;
1522- // We'll check from the largest possible partial (delimiter.length - 1) down to 1
1523- // because if a bigger partial matches, that's our answer.
1524- for (int checkSize = COMMAND_DELIMITER .length - 1 ; checkSize >= 1 ; checkSize --) {
1525- if (checkSize > dataLen ) {
1526- continue ; // can't match if leftover is too small
1527- }
1528- boolean match = true ;
1529- // Compare data[end-checkSize .. end-1] to delimiter[0..checkSize-1]
1530- for (int j = 0 ; j < checkSize ; j ++) {
1531- if (data [end - checkSize + j ] != COMMAND_DELIMITER [j ]) {
1532- match = false ;
1533- break ;
1534- }
1535- }
1536- if (match ) {
1537- // We found the largest partial match
1538- return checkSize ;
1539- }
1540- }
1541- // If no partial match, return 0
1542- return 0 ;
1543- }
1544-
15451404 private void handleParsedCommand (byte cmd , byte [] param ) {
15461405 if (cmd == COMMAND_SMETER_REPORT ) {
15471406 if (param .length >= 1 ) {
0 commit comments