From 2b0f1609b12909d3f8c4fb9d2ee56ecc1b62b5a3 Mon Sep 17 00:00:00 2001 From: sh123 Date: Mon, 1 Aug 2022 22:22:00 +0300 Subject: [PATCH] Demodulator bug fixing --- .../com/radio/codec2talkie/protocol/Hdlc.java | 49 +++++++++++++ .../radio/codec2talkie/tools/BitTools.java | 69 +++++++++++++++++++ .../codec2talkie/transport/SoundModem.java | 52 +++++++++++++- libcodec2-android/src/main/cpp/Codec2JNI.cpp | 11 ++- .../java/com/ustadmobile/codec2/Codec2.java | 1 + 5 files changed, 180 insertions(+), 2 deletions(-) diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/Hdlc.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/Hdlc.java index 59a1e1a..af09747 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/Hdlc.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/Hdlc.java @@ -2,30 +2,47 @@ package com.radio.codec2talkie.protocol; import android.content.Context; import android.content.SharedPreferences; +import android.os.Debug; +import android.util.Log; import com.radio.codec2talkie.protocol.message.TextMessage; import com.radio.codec2talkie.protocol.position.Position; import com.radio.codec2talkie.settings.PreferenceKeys; import com.radio.codec2talkie.tools.BitTools; import com.radio.codec2talkie.tools.ChecksumTools; +import com.radio.codec2talkie.tools.DebugTools; +import com.radio.codec2talkie.transport.SoundModem; import com.radio.codec2talkie.transport.Transport; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; public class Hdlc implements Protocol { + private static final String TAG = Hdlc.class.getSimpleName(); + + private static final int RX_BUFFER_SIZE = 8192; protected Transport _transport; private ProtocolCallback _parentProtocolCallback; + protected final byte[] _rxDataBuffer; + protected final ByteBuffer _currentFrameBuffer; + private final int _prefixSymCount; + private int _readByte = 0; + private int _prevHdlc = 0; + public Hdlc(SharedPreferences sharedPreferences) { double preambleLenSec = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.PORTS_SOUND_MODEM_PREAMBLE, "200")) / 1000.0; String modemType = sharedPreferences.getString(PreferenceKeys.PORTS_SOUND_MODEM_TYPE, "1200"); // FIXME if more modulation schemes int modemSpeed = modemType.equals("300") ? 300 : 1200; _prefixSymCount = (int) (preambleLenSec * modemSpeed / 8); + + _rxDataBuffer = new byte[RX_BUFFER_SIZE]; + _currentFrameBuffer = ByteBuffer.allocate(RX_BUFFER_SIZE); } @Override @@ -61,6 +78,38 @@ public class Hdlc implements Protocol { @Override public boolean receive() throws IOException { + int bitsRead = _transport.read(_rxDataBuffer); + if (bitsRead > 0) { + byte[] data = Arrays.copyOf(_rxDataBuffer, bitsRead); + for (byte bit : data) { + _readByte <<= 1; + _readByte |= bit; + _readByte &= 0xff; + if (_readByte == 0x7e) { + Log.i(TAG, "HDLC " + _prevHdlc/8); + int pos = _currentFrameBuffer.position(); + if (pos >= 7) { + _currentFrameBuffer.position(_currentFrameBuffer.position() - 7); + } else { + _currentFrameBuffer.position(0); + } + _currentFrameBuffer.flip(); + byte[] packetBits = new byte[_currentFrameBuffer.remaining()]; + _currentFrameBuffer.get(packetBits); + byte[] packetBytes = BitTools.convertFromHDLCBitArray(packetBits); + if (packetBytes != null) { + Log.i(TAG, DebugTools.byteBitsToString(packetBits)); + Log.i(TAG, DebugTools.bytesToHex(packetBytes)); + } + _currentFrameBuffer.clear(); + _readByte = 0; + _prevHdlc = 0; + } else { + _currentFrameBuffer.put(bit); + _prevHdlc++; + } + } + } return false; } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/tools/BitTools.java b/codec2talkie/src/main/java/com/radio/codec2talkie/tools/BitTools.java index bb4dfd5..89f0a53 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/tools/BitTools.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/tools/BitTools.java @@ -1,5 +1,7 @@ package com.radio.codec2talkie.tools; +import android.util.Log; + import java.nio.ByteBuffer; public class BitTools { @@ -19,6 +21,25 @@ public class BitTools { return r; } + public static byte[] convertFromNRZI(byte[] bitsAsBytes, byte prevLastBit) { + ByteBuffer buffer = ByteBuffer.allocate(bitsAsBytes.length); + byte last = prevLastBit; + for (byte bitAsByte : bitsAsBytes) { + // no transition -> 1 + if (last == bitAsByte) { + buffer.put((byte) 1); + // transition -> 0 + } else { + buffer.put((byte) 0); + } + last = bitAsByte; + } + buffer.flip(); + byte[] r = new byte[buffer.remaining()]; + buffer.get(r); + return r; + } + public static byte[] convertToHDLCBitArray(byte[] data, boolean shouldBitStuff) { ByteBuffer bitBuffer = ByteBuffer.allocate(2 * data.length * 8); @@ -40,9 +61,57 @@ public class BitTools { cntOnes = 0; } } + // return bitBuffer.flip(); byte[] r = new byte[bitBuffer.remaining()]; bitBuffer.get(r); return r; } + + public static byte[] convertFromHDLCBitArray(byte[] dataBitsAsBytes) { + ByteBuffer byteBuffer = ByteBuffer.allocate(dataBitsAsBytes.length / 8); + + int currentByte = 0; + int cntOnes = 0; + int bitStuffCnt = 5; + boolean skipNext = false; + StringBuffer s = new StringBuffer(); + int cntBits = 0; + for (int i = 0; i < dataBitsAsBytes.length; i++) { + byte currentBit = dataBitsAsBytes[i]; + if (skipNext) { + // cannot have 6 consecutive 1, non-HDLC data + if (currentBit == 1) return null; + s.append(String.format("[%d]", currentBit)); + skipNext = false; + continue; + } + currentByte >>= 1; + s.append(String.format("%d", currentBit)); + if (currentBit == 1) { + currentByte |= (1 << 7); + cntOnes++; + } else { + cntOnes = 0; + } + if (i % 8 == 7) { + s.append(' '); + byteBuffer.put((byte)(currentByte & 0xff)); + currentByte = 0; + } + // 5 ones, skip next + if (cntOnes == bitStuffCnt) { + skipNext = true; + cntOnes = 0; + } + cntBits++; + } + //if (cntBits % 8 != 0) return null; + Log.i("----", s.toString()); + // return + byteBuffer.flip(); + byte[] r = new byte[byteBuffer.remaining()]; + byteBuffer.get(r); + return r; + } } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java b/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java index c30aa58..5fbab58 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java @@ -7,6 +7,7 @@ import android.media.AudioFormat; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder; +import android.os.Debug; import android.util.Log; import androidx.preference.PreferenceManager; @@ -17,9 +18,11 @@ import com.radio.codec2talkie.tools.DebugTools; import com.ustadmobile.codec2.Codec2; import java.io.IOException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; import java.util.Arrays; -public class SoundModem implements Transport { +public class SoundModem implements Transport, Runnable { private static final String TAG = SoundModem.class.getSimpleName(); @@ -36,10 +39,13 @@ public class SoundModem implements Transport { private final short[] _playbackAudioBuffer; private final byte[] _playbackBitBuffer; private final int _samplesPerSymbol; + private final ByteBuffer _bitBuffer; private final Context _context; private final SharedPreferences _sharedPreferences; + private boolean _isRunning = true; + private final long _fskModem; public SoundModem(Context context) { @@ -59,8 +65,11 @@ public class SoundModem implements Transport { _playbackAudioBuffer = new short[Codec2.fskModSamplesBufSize(_fskModem)]; _playbackBitBuffer = new byte[Codec2.fskModBitsBufSize(_fskModem)]; _samplesPerSymbol = Codec2.fskSamplesPerSymbol(_fskModem); + _bitBuffer = ByteBuffer.allocate(10 * _recordBitBuffer.length); constructSystemAudioDevices(); + + new Thread(this).start(); } private void constructSystemAudioDevices() { @@ -81,6 +90,7 @@ public class SoundModem implements Transport { AUDIO_SAMPLE_SIZE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); + _systemAudioRecorder.startRecording(); int usage = AudioAttributes.USAGE_MEDIA; _systemAudioPlayer = new AudioTrack.Builder() @@ -107,6 +117,16 @@ public class SoundModem implements Transport { @Override public int read(byte[] data) throws IOException { + synchronized (_bitBuffer) { + if (_bitBuffer.position() > 0) { + _bitBuffer.flip(); + int len = _bitBuffer.remaining(); + _bitBuffer.get(data, 0, len); + //Log.i(TAG, "-- " + DebugTools.byteBitsToString(data)); + _bitBuffer.compact(); + return len; + } + } return 0; } @@ -133,6 +153,36 @@ public class SoundModem implements Transport { @Override public void close() throws IOException { + Log.i(TAG, "close()"); + _isRunning = false; + _systemAudioRecorder.stop(); + _systemAudioPlayer.stop(); + _systemAudioRecorder.release(); + _systemAudioPlayer.release(); Codec2.fskDestroy(_fskModem); } + + @Override + public void run() { + byte prevLastBit = 0; + while (_isRunning) { + // TODO, take readCnt into account, do not read if playback is active + int readCnt = _systemAudioRecorder.read(_recordAudioBuffer, 0, Codec2.fskNin(_fskModem)); + //Log.i(TAG, DebugTools.shortsToHex(_recordAudioBuffer)); + //Log.i(TAG, readCnt + " " + _recordAudioBuffer.length + " " + Codec2.fskNin(_fskModem)); + Codec2.fskDemodulate(_fskModem, _recordAudioBuffer, _recordBitBuffer); + + //Log.i(TAG, "-- " + DebugTools.byteBitsToString(_recordBitBuffer)); + //Log.i(TAG, "== " + DebugTools.byteBitsToString(BitTools.convertFromNRZI(_recordBitBuffer, prevLastBit))); + synchronized (_bitBuffer) { + try { + _bitBuffer.put(BitTools.convertFromNRZI(_recordBitBuffer, prevLastBit)); + prevLastBit = _recordBitBuffer[_recordBitBuffer.length - 1]; + } catch (BufferOverflowException e) { + e.printStackTrace(); + _bitBuffer.clear(); + } + } + } + } } diff --git a/libcodec2-android/src/main/cpp/Codec2JNI.cpp b/libcodec2-android/src/main/cpp/Codec2JNI.cpp index 809dd89..d1d2b60 100644 --- a/libcodec2-android/src/main/cpp/Codec2JNI.cpp +++ b/libcodec2-android/src/main/cpp/Codec2JNI.cpp @@ -74,6 +74,9 @@ namespace Java_com_ustadmobile_codec2_Codec2 { conFsk->demodBits = (uint8_t*)malloc(sizeof(uint8_t) * fsk->Nbits); conFsk->demodBuf = (int16_t*)malloc(sizeof(short) * (fsk->N + 2 * fsk->Ts)); + fsk_set_freq_est_limits(fsk, -sampleFrequency / 2, sampleFrequency / 2); + fsk_set_freq_est_alg(fsk, 1); + auto pv = (unsigned long) conFsk; return pv; } @@ -113,6 +116,11 @@ namespace Java_com_ustadmobile_codec2_Codec2 { return conFsk->Ts; } + static jint fskNin(JNIEnv * env, jclass clazz, jlong n) { + ContextFsk *conFsk = getContextFsk(n); + return fsk_nin(conFsk->fsk); + } + static jint destroy(JNIEnv *env, jclass clazz, jlong n) { Context *con = getContext(n); codec2_destroy(con->c2); @@ -201,7 +209,8 @@ namespace Java_com_ustadmobile_codec2_Codec2 { {"fskModSamplesBufSize","(J)I", (void *) fskModSamplesBufSize}, {"fskDemodSamplesBufSize","(J)I", (void *) fskDemodSamplesBufSize}, {"fskModBitsBufSize", "(J)I", (void *) fskModBitsBufSize}, - {"fskSamplesPerSymbol","(J)I", (void *) fskSamplesPerSymbol} + {"fskSamplesPerSymbol","(J)I", (void *) fskSamplesPerSymbol}, + {"fskNin", "(J)I", (void *) fskNin} }; } diff --git a/libcodec2-android/src/main/java/com/ustadmobile/codec2/Codec2.java b/libcodec2-android/src/main/java/com/ustadmobile/codec2/Codec2.java index 9d45622..4c8a58d 100644 --- a/libcodec2-android/src/main/java/com/ustadmobile/codec2/Codec2.java +++ b/libcodec2-android/src/main/java/com/ustadmobile/codec2/Codec2.java @@ -43,6 +43,7 @@ public class Codec2 { public native static int fskDemodSamplesBufSize(long conFsk); public native static int fskModBitsBufSize(long conFsk); public native static int fskSamplesPerSymbol(long conFsk); + public native static int fskNin(long conFsk); public native static long fskModulate(long conFsk, short[] outputSamples, byte[] inputBits); public native static long fskDemodulate(long conFsk, short[] inputSamples, byte[] outputBits);