From fae6937533565157d5363c353c2bcbd75e59f689 Mon Sep 17 00:00:00 2001 From: sh123 Date: Fri, 29 Jul 2022 17:54:24 +0300 Subject: [PATCH] Sound modem experiments --- .../com/radio/codec2talkie/app/AppWorker.java | 11 +- .../protocol/ProtocolFactory.java | 20 ++-- .../protocol/ax25/AX25Packet.java | 1 + .../codec2talkie/tools/ChecksumTools.java | 52 +++++++++ .../codec2talkie/transport/SoundModem.java | 104 ++++++++++++++---- libcodec2-android/src/main/cpp/Codec2JNI.cpp | 6 +- 6 files changed, 155 insertions(+), 39 deletions(-) create mode 100644 codec2talkie/src/main/java/com/radio/codec2talkie/tools/ChecksumTools.java diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java b/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java index 67597cb..2762c35 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java @@ -28,7 +28,6 @@ import com.radio.codec2talkie.protocol.Protocol; import com.radio.codec2talkie.protocol.ProtocolFactory; import com.radio.codec2talkie.protocol.position.Position; import com.radio.codec2talkie.settings.PreferenceKeys; -import com.radio.codec2talkie.storage.message.MessageItem; import com.radio.codec2talkie.storage.message.MessageItemRepository; import com.radio.codec2talkie.storage.position.PositionItemRepository; import com.radio.codec2talkie.tools.AudioTools; @@ -87,16 +86,16 @@ public class AppWorker extends Thread { String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, _context.getResources().getStringArray(R.array.codec2_modes)[0]); _codec2Mode = AudioTools.extractCodec2ModeId(codec2ModeName); + _logItemRepository = new LogItemRepository((Application)context); + _messageItemRepository = new MessageItemRepository((Application)context); + _positionItemRepository = new PositionItemRepository((Application)context); + _transport = TransportFactory.create(transportType, context); _protocol = ProtocolFactory.create(_codec2Mode, context); _processPeriodicTimer = new Timer(); _recordAudioBuffer = new short[_protocol.getPcmAudioBufferSize()]; - _logItemRepository = new LogItemRepository((Application)context); - _messageItemRepository = new MessageItemRepository((Application)context); - _positionItemRepository = new PositionItemRepository((Application)context); - constructSystemAudioDevices(); } @@ -493,7 +492,7 @@ public class AppWorker extends Thread { public void run() { Log.i(TAG, "Starting message loop"); try { - sleep(1000); + sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ProtocolFactory.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ProtocolFactory.java index 05b2e0f..9206a06 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ProtocolFactory.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ProtocolFactory.java @@ -76,22 +76,20 @@ public class ProtocolFactory { break; } - if (protocolType != ProtocolType.RAW) { - if (scramblingEnabled) { - proto = new Scrambler(proto, scramblingKey); - } - if (aprsEnabled) { - proto = new Ax25(proto); - } - if (recordingEnabled) { - proto = new Recorder(proto, codec2ModeId); - } + if (scramblingEnabled) { + proto = new Scrambler(proto, scramblingKey); + } + if (aprsEnabled) { + proto = new Ax25(proto); + } + if (recordingEnabled) { + proto = new Recorder(proto, codec2ModeId); } proto = new AudioFrameAggregator(proto, codec2ModeId); proto = new AudioCodec2(proto, codec2ModeId); - if (aprsEnabled && protocolType != ProtocolType.RAW) { + if (aprsEnabled) { // && protocolType != ProtocolType.RAW) { proto = new Aprs(proto); } return proto; diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ax25/AX25Packet.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ax25/AX25Packet.java index c40bc83..601245d 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ax25/AX25Packet.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ax25/AX25Packet.java @@ -118,6 +118,7 @@ public class AX25Packet { } // data buffer.put(rawData); + // return buffer.flip(); byte[] b = new byte[buffer.remaining()]; diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/tools/ChecksumTools.java b/codec2talkie/src/main/java/com/radio/codec2talkie/tools/ChecksumTools.java new file mode 100644 index 0000000..f917873 --- /dev/null +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/tools/ChecksumTools.java @@ -0,0 +1,52 @@ +package com.radio.codec2talkie.tools; + +public class ChecksumTools { + + private static final int[] _ccitTable = new int[] { + + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 + }; + + public static int calculateFcs(byte[] data) + { + int crc = 0xffff; + + for (int j = 0; j < data.length; j++) { + crc = (crc >> 8) ^ _ccitTable[(crc ^ (int)data[j]) & 0xff]; + crc &= 0xffff; + } + + return (crc ^ 0xffff); + } +} 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 09bb74f..cff7e1a 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java @@ -11,17 +11,18 @@ import android.util.Log; import androidx.preference.PreferenceManager; +import com.radio.codec2talkie.tools.ChecksumTools; import com.ustadmobile.codec2.Codec2; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.Arrays; -import java.util.BitSet; public class SoundModem implements Transport { private static final String TAG = SoundModem.class.getSimpleName(); - private static final int AUDIO_SAMPLE_SIZE = 12000; + private static final int AUDIO_SAMPLE_SIZE = 48000; private final String _name; @@ -44,7 +45,8 @@ public class SoundModem implements Transport { _context = context; _sharedPreferences = PreferenceManager.getDefaultSharedPreferences(_context); - _fskModem = Codec2.fskCreate(AUDIO_SAMPLE_SIZE, 300, 1600, 200); + //_fskModem = Codec2.fskCreate(AUDIO_SAMPLE_SIZE, 300, 1600, 200); + _fskModem = Codec2.fskCreate(AUDIO_SAMPLE_SIZE, 1200, 1200, 1000); _recordAudioBuffer = new short[Codec2.fskDemodSamplesBufSize(_fskModem)]; _recordBitBuffer = new byte[Codec2.fskDemodBitsBufSize(_fskModem)]; @@ -55,19 +57,20 @@ public class SoundModem implements Transport { } private void constructSystemAudioDevices() { - int _audioRecorderMinBufferSize = AudioRecord.getMinBufferSize( + int audioRecorderMinBufferSize = AudioRecord.getMinBufferSize( AUDIO_SAMPLE_SIZE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); + int audioSource = MediaRecorder.AudioSource.MIC; _systemAudioRecorder = new AudioRecord( audioSource, AUDIO_SAMPLE_SIZE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, - _audioRecorderMinBufferSize); + audioRecorderMinBufferSize); - int _audioPlayerMinBufferSize = AudioTrack.getMinBufferSize( + int audioPlayerMinBufferSize = AudioTrack.getMinBufferSize( AUDIO_SAMPLE_SIZE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); @@ -76,7 +79,7 @@ public class SoundModem implements Transport { _systemAudioPlayer = new AudioTrack.Builder() .setAudioAttributes(new AudioAttributes.Builder() .setUsage(usage) - .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()) .setAudioFormat(new AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_PCM_16BIT) @@ -84,7 +87,7 @@ public class SoundModem implements Transport { .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) .build()) .setTransferMode(AudioTrack.MODE_STREAM) - .setBufferSizeInBytes(_audioPlayerMinBufferSize) + .setBufferSizeInBytes(audioPlayerMinBufferSize) .build(); } @@ -98,32 +101,93 @@ public class SoundModem implements Transport { return 0; } - public static byte[] toByteBitArray(BitSet bits) { - byte[] bytes = new byte[bits.length()]; - for (int i=0; i 0) { + bitBuffer.put((byte)1); + s.append(1); + if (shouldBitStuff) + cntOnes += 1; + } else { + bitBuffer.put((byte)0); + s.append(0); + } + if (shouldBitStuff && cntOnes == 5) { + bitBuffer.put((byte)0); + s.append(0); + cntOnes = 0; + } } - return bytes; + + Log.i(TAG, s.toString()); + + bitBuffer.flip(); + byte[] r = new byte[bitBuffer.remaining()]; + bitBuffer.get(r); + return r; + } + + public byte[] genPreamble(int count) { + byte[] preamble = new byte[count]; + for (int i = 0; i < count; i++) + preamble[i] = (byte)0x7e; + return toHdlcByteBitArray(preamble, false); + } + + public byte[] hdlcEncode(byte[] dataSrc) { + ByteBuffer buffer = ByteBuffer.allocate(512); + + buffer.put(dataSrc); + int fcs = ChecksumTools.calculateFcs(dataSrc); + buffer.put((byte)((fcs >> 8) & 0xff)); + buffer.put((byte)(fcs & 0xff)); + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + + Log.i(TAG, String.format("checksum: %x", fcs)); + Log.i(TAG, "" + Arrays.toString(data)); + + byte[] dataBytesAsBits = toHdlcByteBitArray(data, true); + Log.i(TAG, "write() " + data.length + " " + 8 * data.length + " " + dataBytesAsBits.length + " " + _playbackBitBuffer.length); + + ByteBuffer hdlcBitBuffer = ByteBuffer.allocate(512*8); + hdlcBitBuffer.put(genPreamble(30)); + hdlcBitBuffer.put(dataBytesAsBits); + hdlcBitBuffer.put(genPreamble(5)); + + hdlcBitBuffer.flip(); + byte[] r = new byte[hdlcBitBuffer.remaining()]; + hdlcBitBuffer.get(r); + return r; } @Override - public int write(byte[] data) throws IOException { - _systemAudioPlayer.play(); - byte[] dataBits = toByteBitArray(BitSet.valueOf(data)); - Log.i(TAG, "write() " + data.length + " " + dataBits.length + " " + _playbackBitBuffer.length); + public int write(byte[] dataSrc) throws IOException { + byte[] dataBytesAsBits = hdlcEncode(dataSrc); + int j = 0; - for (int i = 0; i < dataBits.length; i++, j++) { + for (int i = 0; i < dataBytesAsBits.length; i++, j++) { if (j >= _playbackBitBuffer.length) { Log.i(TAG, "-- " + i + " " + j); Codec2.fskModulate(_fskModem, _playbackAudioBuffer, _playbackBitBuffer); _systemAudioPlayer.write(_playbackAudioBuffer, 0, _playbackAudioBuffer.length); + _systemAudioPlayer.play(); j = 0; } - _playbackBitBuffer[j] = dataBits[i]; + _playbackBitBuffer[j] = dataBytesAsBits[i]; } Log.i(TAG, "-- " + j); - Codec2.fskModulate(_fskModem, _playbackAudioBuffer, _playbackBitBuffer); + Codec2.fskModulate(_fskModem, _playbackAudioBuffer, Arrays.copyOf(_playbackBitBuffer, j)); _systemAudioPlayer.write(_playbackAudioBuffer, 0, _playbackAudioBuffer.length); + _systemAudioPlayer.play(); + return 0; } diff --git a/libcodec2-android/src/main/cpp/Codec2JNI.cpp b/libcodec2-android/src/main/cpp/Codec2JNI.cpp index d546f2f..bcd829a 100644 --- a/libcodec2-android/src/main/cpp/Codec2JNI.cpp +++ b/libcodec2-android/src/main/cpp/Codec2JNI.cpp @@ -153,14 +153,16 @@ namespace Java_com_ustadmobile_codec2_Codec2 { static jlong fskModulate(JNIEnv *env, jclass clazz, jlong n, jshortArray outputSamples, jbyteArray inputBits) { ContextFsk *conFsk = getContextFsk(n); jbyte *jbuf = env->GetByteArrayElements(inputBits, nullptr); - for (int i = 0; i < conFsk->Nbits; i++) { + //for (int i = 0; i < conFsk->Nbits; i++) { + int inputBitsSize = env->GetArrayLength(inputBits); + for (int i = 0; i < inputBitsSize; i++) { auto v = (unsigned char) jbuf[i]; conFsk->modBits[i] = v; } env->ReleaseByteArrayElements(inputBits, jbuf, 0); //env->DeleteLocalRef(inputBits); - fsk_mod(conFsk->fsk, conFsk->modBuf, conFsk->modBits, conFsk->Nbits); + fsk_mod(conFsk->fsk, conFsk->modBuf, conFsk->modBits, inputBitsSize); jshort *jOutBuf = env->GetShortArrayElements(outputSamples, nullptr); for (int i = 0; i < conFsk->N; i++) {