From cddc47ed85e7435af8c2587e860d228bb7a67176 Mon Sep 17 00:00:00 2001 From: sh123 Date: Thu, 28 Jul 2022 21:14:45 +0300 Subject: [PATCH] Sound modem --- .../com/radio/codec2talkie/MainActivity.java | 4 +- .../com/radio/codec2talkie/app/AppWorker.java | 2 +- .../protocol/AudioFrameAggregator.java | 6 +- .../codec2talkie/settings/PreferenceKeys.java | 2 +- .../radio/codec2talkie/transport/Audio.java | 31 ----- .../codec2talkie/transport/SoundModem.java | 109 ++++++++++++++++++ .../transport/TransportFactory.java | 10 +- codec2talkie/src/main/res/values/strings.xml | 4 +- codec2talkie/src/main/res/xml/preferences.xml | 6 +- libcodec2-android/src/main/cpp/Codec2JNI.cpp | 16 ++- .../java/com/ustadmobile/codec2/Codec2.java | 2 + 11 files changed, 144 insertions(+), 48 deletions(-) delete mode 100644 codec2talkie/src/main/java/com/radio/codec2talkie/transport/Audio.java create mode 100644 codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/MainActivity.java b/codec2talkie/src/main/java/com/radio/codec2talkie/MainActivity.java index 40f98a1..8754848 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/MainActivity.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/MainActivity.java @@ -255,9 +255,9 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection _textConnInfo.setText(R.string.main_status_loopback_test); startAppService(TransportFactory.TransportType.LOOPBACK); } else if (requestPermissions()) { - if (_sharedPreferences.getBoolean(PreferenceKeys.PORTS_AUDIO_ENABLED, false)) { + if (_sharedPreferences.getBoolean(PreferenceKeys.PORTS_SOUND_MODEM_ENABLED, false)) { _textConnInfo.setText(R.string.main_status_sound_modem); - startAppService(TransportFactory.TransportType.AUDIO); + startAppService(TransportFactory.TransportType.SOUND_MODEM); } else if (_sharedPreferences.getBoolean(PreferenceKeys.PORTS_TCP_IP_ENABLED, false)) { startTcpIpConnectActivity(); } else { 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 08ec9d5..9ae9424 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java @@ -87,7 +87,7 @@ public class AppWorker extends Thread { String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, _context.getResources().getStringArray(R.array.codec2_modes)[0]); _codec2Mode = AudioTools.extractCodec2ModeId(codec2ModeName); - _transport = TransportFactory.create(transportType); + _transport = TransportFactory.create(transportType, context); _protocol = ProtocolFactory.create(_codec2Mode, context); _processPeriodicTimer = new Timer(); diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/AudioFrameAggregator.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/AudioFrameAggregator.java index 716c264..6783bb3 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/AudioFrameAggregator.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/AudioFrameAggregator.java @@ -66,11 +66,11 @@ public class AudioFrameAggregator implements Protocol { public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException { if ( _outputBufferPos + frame.length >= _outputBufferSize) { _childProtocol.sendCompressedAudio(src, dst, codec2Mode, Arrays.copyOf(_outputBuffer, _outputBufferPos)); - _lastSrc = src; - _lastDst = dst; - _lastCodec2Mode = codec2Mode; _outputBufferPos = 0; } + _lastSrc = src; + _lastDst = dst; + _lastCodec2Mode = codec2Mode; System.arraycopy(frame, 0, _outputBuffer, _outputBufferPos, frame.length); _outputBufferPos += frame.length; } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/settings/PreferenceKeys.java b/codec2talkie/src/main/java/com/radio/codec2talkie/settings/PreferenceKeys.java index b4d8c6a..9d0e613 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/settings/PreferenceKeys.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/settings/PreferenceKeys.java @@ -17,7 +17,7 @@ public final class PreferenceKeys { public static String PORTS_TCP_IP_RETRY_COUNT = "ports_tcp_ip_retry_count"; public static String PORTS_TCP_IP_RETRY_DELAY = "ports_tcp_ip_retry_delay"; - public static String PORTS_AUDIO_ENABLED = "ports_audio_enabled"; + public static String PORTS_SOUND_MODEM_ENABLED = "ports_sound_modem_enable"; public static String CODEC2_MODE = "codec2_mode"; public static String CODEC2_TEST_MODE = "codec2_test_mode"; diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/transport/Audio.java b/codec2talkie/src/main/java/com/radio/codec2talkie/transport/Audio.java deleted file mode 100644 index ee4b8bf..0000000 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/transport/Audio.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.radio.codec2talkie.transport; - -import java.io.IOException; - -public class Audio implements Transport { - - private final String _name; - - public Audio(String name) { - _name = name; - } - - @Override - public String name() { - return _name; - } - - @Override - public int read(byte[] data) throws IOException { - return 0; - } - - @Override - public int write(byte[] data) throws IOException { - return 0; - } - - @Override - public void close() throws IOException { - } -} diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java b/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java new file mode 100644 index 0000000..983d0ca --- /dev/null +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/transport/SoundModem.java @@ -0,0 +1,109 @@ +package com.radio.codec2talkie.transport; + +import android.content.Context; +import android.content.SharedPreferences; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.AudioTrack; +import android.media.MediaRecorder; +import android.provider.MediaStore; + +import androidx.preference.PreferenceManager; + +import com.radio.codec2talkie.settings.PreferenceKeys; +import com.ustadmobile.codec2.Codec2; + +import java.io.IOException; + +public class SoundModem implements Transport { + + private static final int AUDIO_SAMPLE_SIZE = 8000; + + private final String _name; + + private AudioTrack _systemAudioPlayer; + private AudioRecord _systemAudioRecorder; + + private final short[] _recordAudioBuffer; + private final byte[] _recordBitBuffer; + private final short[] _playbackAudioBuffer; + private final byte[] _playbackBitBuffer; + + private final Context _context; + private final SharedPreferences _sharedPreferences; + + private final long _fskModem; + + public SoundModem(String name, Context context) { + _name = name; + + _context = context; + _sharedPreferences = PreferenceManager.getDefaultSharedPreferences(_context); + + _fskModem = Codec2.fskCreate(AUDIO_SAMPLE_SIZE, 300, 1600, 200); + + _recordAudioBuffer = new short[Codec2.fskDemodSamplesBufSize(_fskModem)]; + _recordBitBuffer = new byte[Codec2.fskDemodBitsBufSize(_fskModem)]; + _playbackAudioBuffer = new short[Codec2.fskModSamplesBufSize(_fskModem)]; + _playbackBitBuffer = new byte[Codec2.fskModBitsBufSize(_fskModem)]; + + constructSystemAudioDevices(); + } + + private void constructSystemAudioDevices() { + 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, + 10 * _audioRecorderMinBufferSize); + + int _audioPlayerMinBufferSize = AudioTrack.getMinBufferSize( + AUDIO_SAMPLE_SIZE, + AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT); + + int usage = AudioAttributes.USAGE_MEDIA; + _systemAudioPlayer = new AudioTrack.Builder() + .setAudioAttributes(new AudioAttributes.Builder() + .setUsage(usage) + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .build()) + .setAudioFormat(new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setSampleRate(AUDIO_SAMPLE_SIZE) + .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) + .build()) + .setTransferMode(AudioTrack.MODE_STREAM) + .setBufferSizeInBytes(10 * _audioPlayerMinBufferSize) + .build(); + } + + @Override + public String name() { + return _name; + } + + @Override + public int read(byte[] data) throws IOException { + return 0; + } + + @Override + public int write(byte[] data) throws IOException { + Codec2.fskModulate(_fskModem, _playbackAudioBuffer, _playbackBitBuffer); + _systemAudioPlayer.write(_playbackAudioBuffer, 0, _playbackAudioBuffer.length); + return 0; + } + + @Override + public void close() throws IOException { + Codec2.fskDestroy(_fskModem); + } +} diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/transport/TransportFactory.java b/codec2talkie/src/main/java/com/radio/codec2talkie/transport/TransportFactory.java index 94b9607..5d023d1 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/transport/TransportFactory.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/transport/TransportFactory.java @@ -1,5 +1,7 @@ package com.radio.codec2talkie.transport; +import android.content.Context; + import com.radio.codec2talkie.connect.BleHandler; import com.radio.codec2talkie.connect.BluetoothSocketHandler; import com.radio.codec2talkie.connect.TcpIpSocketHandler; @@ -15,10 +17,10 @@ public class TransportFactory { LOOPBACK, TCP_IP, BLE, - AUDIO + SOUND_MODEM }; - public static Transport create(TransportType transportType) throws IOException { + public static Transport create(TransportType transportType, Context context) throws IOException { switch (transportType) { case USB: return new UsbSerial(UsbPortHandler.getPort(), UsbPortHandler.getName()); @@ -28,8 +30,8 @@ public class TransportFactory { return new TcpIp(TcpIpSocketHandler.getSocket(), TcpIpSocketHandler.getName()); case BLE: return new Ble(BleHandler.getGatt(), BleHandler.getName()); - case AUDIO: - return new Audio("SoundModem"); + case SOUND_MODEM: + return new SoundModem("SoundModem", context); case LOOPBACK: default: return new Loopback(); diff --git a/codec2talkie/src/main/res/values/strings.xml b/codec2talkie/src/main/res/values/strings.xml index 11017c3..09872fb 100644 --- a/codec2talkie/src/main/res/values/strings.xml +++ b/codec2talkie/src/main/res/values/strings.xml @@ -291,7 +291,7 @@ \? km APRS icon APRS symbols - Enable sound modem - Send data and receive data through sound modem by using phone speaker and mic + Enable sound modem + Send data and receive data through sound modem by using phone speaker and mic SoundModem \ No newline at end of file diff --git a/codec2talkie/src/main/res/xml/preferences.xml b/codec2talkie/src/main/res/xml/preferences.xml index e15ba97..55e0ef1 100644 --- a/codec2talkie/src/main/res/xml/preferences.xml +++ b/codec2talkie/src/main/res/xml/preferences.xml @@ -142,9 +142,9 @@ diff --git a/libcodec2-android/src/main/cpp/Codec2JNI.cpp b/libcodec2-android/src/main/cpp/Codec2JNI.cpp index f840083..e288462 100644 --- a/libcodec2-android/src/main/cpp/Codec2JNI.cpp +++ b/libcodec2-android/src/main/cpp/Codec2JNI.cpp @@ -24,6 +24,7 @@ namespace Java_com_ustadmobile_codec2_Codec2 { unsigned char *demodBits; int Nbits; int N; + int Ts; }; static Context *getContext(jlong jp) { @@ -64,6 +65,7 @@ namespace Java_com_ustadmobile_codec2_Codec2 { conFsk->Nbits = fsk->Nbits; conFsk->N = fsk->N; + conFsk->Ts = fsk->Ts; conFsk->modBuf = (float*)malloc(conFsk->N); conFsk->modBits = (uint8_t*)malloc(conFsk->Nbits); @@ -86,6 +88,11 @@ namespace Java_com_ustadmobile_codec2_Codec2 { return con->nbyte; } + static jint fskDemodSamplesBufSize(JNIEnv * env, jclass clazz, jlong n) { + ContextFsk *conFsk = getContextFsk(n); + return sizeof(short) * (conFsk->N + 2 * conFsk->Ts); + } + static jint fskDemodBitsBufSize(JNIEnv * env, jclass clazz, jlong n) { ContextFsk *conFsk = getContextFsk(n); return sizeof(uint8_t) * conFsk->Nbits; @@ -96,6 +103,11 @@ namespace Java_com_ustadmobile_codec2_Codec2 { return conFsk->N; } + static jint fskModBitsBufSize(JNIEnv * env, jclass clazz, jlong n) { + ContextFsk *conFsk = getContextFsk(n); + return conFsk->Nbits; + } + static jint destroy(JNIEnv *env, jclass clazz, jlong n) { Context *con = getContext(n); codec2_destroy(con->c2); @@ -193,7 +205,9 @@ namespace Java_com_ustadmobile_codec2_Codec2 { {"fskModulate", "(J[S[B)J", (void *) fskModulate}, {"fskDemodulate", "(J[S[B)J", (void *) fskDemodulate}, {"fskDemodBitsBufSize","(J)I", (void *) fskDemodBitsBufSize}, - {"fskModSamplesBufSize","(J)I", (void *) fskModSamplesBufSize} + {"fskModSamplesBufSize","(J)I", (void *) fskModSamplesBufSize}, + {"fskDemodSamplesBufSize","(J)I", (void *) fskDemodSamplesBufSize}, + {"fskModBitsBufSize", "(J)I", (void *) fskModBitsBufSize} }; } 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 5c124b2..ab8f4cd 100644 --- a/libcodec2-android/src/main/java/com/ustadmobile/codec2/Codec2.java +++ b/libcodec2-android/src/main/java/com/ustadmobile/codec2/Codec2.java @@ -40,6 +40,8 @@ public class Codec2 { public native static int fskDemodBitsBufSize(long conFsk); public native static int fskModSamplesBufSize(long conFsk); + public native static int fskDemodSamplesBufSize(long conFsk); + public native static int fskModBitsBufSize(long conFsk); public native static long fskModulate(long conFsk, short[] outputSamples, byte[] inputBits); public native static long fskDemodulate(long conFsk, short[] inputSamples, byte[] outputBits);