From bcd168b6ad326f29d045257f7ea7cd100faadbec Mon Sep 17 00:00:00 2001 From: sh123 Date: Thu, 28 Jul 2022 14:46:58 +0300 Subject: [PATCH] Wrapper for FSK modulator/demodulator --- libcodec2-android/src/main/cpp/Codec2JNI.cpp | 117 +++++++-- libcodec2-android/src/main/cpp/codec2/fsk.h | 222 ++++++++++++++++++ .../src/main/cpp/codec2/kiss_fftr.h | 46 ++++ .../java/com/ustadmobile/codec2/Codec2.java | 25 +- 4 files changed, 373 insertions(+), 37 deletions(-) create mode 100644 libcodec2-android/src/main/cpp/codec2/fsk.h create mode 100644 libcodec2-android/src/main/cpp/codec2/kiss_fftr.h diff --git a/libcodec2-android/src/main/cpp/Codec2JNI.cpp b/libcodec2-android/src/main/cpp/Codec2JNI.cpp index 7729f2a..bf44988 100644 --- a/libcodec2-android/src/main/cpp/Codec2JNI.cpp +++ b/libcodec2-android/src/main/cpp/Codec2JNI.cpp @@ -2,6 +2,7 @@ #include #include "codec2/codec2_fdmdv.h" #include "codec2/codec2.h" +#include "codec2/fsk.h" namespace Java_com_ustadmobile_codec2_Codec2 { @@ -14,13 +15,29 @@ namespace Java_com_ustadmobile_codec2_Codec2 { int nbyte;//size of one frame of codec2 data }; + struct ContextFsk { + struct FSK *fsk; + float *buf; + unsigned char *bits; + COMP * modBuf; + int Nbits; + int N; + }; + static Context *getContext(jlong jp) { - unsigned long p = (unsigned long) jp; + auto p = (unsigned long) jp; Context *con; con = (Context *) p; return con; } + static ContextFsk *getContextFsk(jlong jp) { + auto p = (unsigned long) jp; + ContextFsk *conFsk; + conFsk = (ContextFsk *) p; + return conFsk; + } + static jlong create(JNIEnv *env, jclass clazz, int mode) { struct Context *con; con = (struct Context *) malloc(sizeof(struct Context)); @@ -32,8 +49,22 @@ namespace Java_com_ustadmobile_codec2_Codec2 { con->buf = (short*)malloc(con->nsam*sizeof(short)); con->nbyte = (con->nbit + 7) / 8; con->bits = (unsigned char*)malloc(con->nbyte*sizeof(char)); - unsigned long pv = (unsigned long) con; + auto pv = (unsigned long) con; + return pv; + } + static jlong fskCreate(JNIEnv *env, jclass clazz, int sampleFrequency, int symbolRate, int toneFreq, int toneSpacing) { + struct ContextFsk *conFsk; + conFsk = (struct ContextFsk *) malloc(sizeof(struct ContextFsk)); + struct FSK *fsk; + fsk = fsk_create(sampleFrequency, symbolRate, MODE_2FSK, toneFreq, toneSpacing); + conFsk->fsk = fsk; + conFsk->Nbits = fsk->Nbits; + conFsk->N = fsk->N; + conFsk->buf = (float*)malloc(conFsk->N); + conFsk->modBuf = (COMP*)malloc(sizeof(COMP)*(fsk->N+fsk->Ts*2)); + conFsk->bits = (unsigned char*)malloc(conFsk->Nbits); + auto pv = (unsigned long) conFsk; return pv; } @@ -56,20 +87,30 @@ namespace Java_com_ustadmobile_codec2_Codec2 { return 0; } - static jlong encode(JNIEnv *env, jclass clazz, jlong n, - jshortArray inputBuffer, - jcharArray outputBits) { + static jint fskDestroy(JNIEnv *env, jclass clazz, jlong n) { + ContextFsk *conFsk = getContextFsk(n); + fsk_destroy(conFsk->fsk); + free(conFsk->bits); + free(conFsk->modBuf); + free(conFsk->buf); + free(conFsk); + return 0; + } + + static jlong encode(JNIEnv *env, jclass clazz, jlong n, jshortArray inputBuffer, jcharArray outputBits) { Context *con = getContext(n); int i; - jshort *jbuf = env->GetShortArrayElements(inputBuffer, 0); + jshort *jbuf = env->GetShortArrayElements(inputBuffer, nullptr); for (i = 0; i < con->nsam; i++) { - short v = (short) jbuf[i]; + auto v = (short) jbuf[i]; con->buf[i] = v; } env->ReleaseShortArrayElements(inputBuffer, jbuf, 0); //env->DeleteLocalRef(inputBuffer); + codec2_encode(con->c2, con->bits, con->buf); - jchar *jbits = env->GetCharArrayElements(outputBits, 0); + + jchar *jbits = env->GetCharArrayElements(outputBits, nullptr); for (i = 0; i < con->nbyte; i++) { jbits[i] = con->bits[i]; } @@ -78,26 +119,60 @@ namespace Java_com_ustadmobile_codec2_Codec2 { return 0; } - static jlong decode(JNIEnv *env, jclass clazz, jlong n, jshortArray outBuffer, jbyteArray inBuffer) { + static jlong fskModulate(JNIEnv *env, jclass clazz, jlong n, jshortArray outputSamples, jbyteArray inputBits) { + ContextFsk *conFsk = getContextFsk(n); + int i; + jbyte *jbuf = env->GetByteArrayElements(inputBits, nullptr); + for (i = 0; i < conFsk->Nbits; i++) { + auto v = (unsigned char) jbuf[i]; + conFsk->bits[i] = v; + } + env->ReleaseByteArrayElements(inputBits, jbuf, 0); + //env->DeleteLocalRef(inputBits); + + fsk_mod(conFsk->fsk, conFsk->buf, conFsk->bits, conFsk->Nbits); + + jshort *jOutBuf = env->GetShortArrayElements(outputSamples, nullptr); + for (i = 0; i < conFsk->N; i++) { + jOutBuf[i] = (int16_t)(conFsk->buf[i] * FDMDV_SCALE); + } + env->ReleaseShortArrayElements(outputSamples, jOutBuf, 0); + //env->DeleteLocalRef(outputSamples); + return 0; + } + + static jlong decode(JNIEnv *env, jclass clazz, jlong n, jshortArray outputSamples, jbyteArray inputBits) { Context *con = getContext(n); - - env->GetByteArrayRegion (inBuffer, 0, con->nbyte, reinterpret_cast(con->bits)); - + env->GetByteArrayRegion(inputBits, 0, con->nbyte, reinterpret_cast(con->bits)); codec2_decode_ber(con->c2, con->buf, con->bits, 0.0); + env->SetShortArrayRegion(outputSamples, 0, con->nsam, con->buf); + return 0; + } - env->SetShortArrayRegion(outBuffer, 0, con->nsam, con->buf); - + static jlong fskDemodulate(JNIEnv * env, jclass clazz, jlong n, jshortArray inputSamples, jbyteArray outputBits) { + ContextFsk *conFsk = getContextFsk(n); + env->GetShortArrayRegion(inputSamples, 0, conFsk->N, reinterpret_cast(conFsk->buf)); + for(int i = 0; i < fsk_nin(conFsk->fsk); i++){ + conFsk->modBuf[i].real = ((float)conFsk->buf[i])/FDMDV_SCALE; + conFsk->modBuf[i].imag = 0.0; + } + fsk_demod(conFsk->fsk, conFsk->bits, conFsk->modBuf); + env->SetByteArrayRegion(outputBits, 0, conFsk->Nbits, reinterpret_cast(conFsk->bits)); return 0; } static JNINativeMethod method_table[] = { - {"create", "(I)J", (void *) create}, - {"getSamplesPerFrame", "(J)I", (void *) c2spf}, - {"getBitsSize", "(J)I", (void *) c2bits}, - {"destroy", "(J)I", (void *) destroy}, - {"encode", "(J[S[C)J", (void *) encode}, - {"decode", "(J[S[B)J", (void *) decode}}; - + {"create", "(I)J", (void *) create}, + {"getSamplesPerFrame", "(J)I", (void *) c2spf}, + {"getBitsSize", "(J)I", (void *) c2bits}, + {"destroy", "(J)I", (void *) destroy}, + {"encode", "(J[S[C)J", (void *) encode}, + {"decode", "(J[S[B)J", (void *) decode}, + {"fskCreate", "(IIII)J", (void *) fskCreate}, + {"fskDestroy", "(J)I", (void *) fskDestroy}, + {"fskModulate", "(J[S[C)J", (void *) fskModulate}, + {"fskDemodulate", "(J[S[S)J", (void *) fskDemodulate} + }; } using namespace Java_com_ustadmobile_codec2_Codec2; diff --git a/libcodec2-android/src/main/cpp/codec2/fsk.h b/libcodec2-android/src/main/cpp/codec2/fsk.h new file mode 100644 index 0000000..7a548a5 --- /dev/null +++ b/libcodec2-android/src/main/cpp/codec2/fsk.h @@ -0,0 +1,222 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: fsk.h + AUTHOR......: Brady O'Brien + DATE CREATED: 6 January 2016 + + C Implementation of 2FSK/4FSK modulator/demodulator, based on octave/fsk_horus.m + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2016 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. This program is + distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, see . +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __C2FSK_H +#define __C2FSK_H + +#include +#include "comp.h" +#include "kiss_fftr.h" +#include "modem_stats.h" + +#define MODE_2FSK 2 +#define MODE_4FSK 4 + +#define MODE_M_MAX 4 + +#define FSK_SCALE 16383 + +/* default internal parameters */ +#define FSK_DEFAULT_P 8 /* Number of timing offsets we have to choose from, try to keep P >= 8 */ +#define FSK_DEFAULT_NSYM 50 /* See Nsym below */ +#define FSK_NONE -1 /* unused parameter */ + +struct FSK { + /* Static parameters set up by fsk_init */ + int Ndft; /* freq offset est fft */ + int Fs; /* sample freq */ + int N; /* processing buffer size */ + int Rs; /* symbol rate */ + int Ts; /* samples per symbol */ + int Nmem; /* size of extra mem for timing adj */ + int P; /* oversample rate for timing est/adj */ + int Nsym; /* Number of symbols processed by demodulator in each call, also the timing estimator window */ + int Nbits; /* Number of bits spat out in a processing frame */ + int f1_tx; /* f1 for modulator */ + int tone_spacing; /* Space between TX freqs for modulator (and option mask freq estimator) */ + int mode; /* 2FSK or 4FSK */ + float tc; /* time constant for smoothing FFTs */ + int est_min; /* Minimum frequency for freq. estimator */ + int est_max; /* Maximum frequency for freq. estimator */ + int est_space; /* Minimum frequency spacing for freq. estimator */ + float* hann_table; /* Precomputed or runtime computed hann window table */ + + /* Parameters used by demod */ + float* Sf; /* Average of magnitude spectrum */ + COMP phi_c[MODE_M_MAX]; /* phase of each demod local oscillator */ + COMP *f_dc; /* down converted samples */ + + kiss_fft_cfg fft_cfg; /* Config for KISS FFT, used in freq est */ + float norm_rx_timing; /* Normalized RX timing */ + + /* Parameters used by mod */ + COMP tx_phase_c; /* TX phase, but complex */ + + /* Statistics generated by demod */ + float EbNodB; /* Estimated EbNo in dB */ + float f_est[MODE_M_MAX]; /* Estimated frequencies (peak method) */ + float f2_est[MODE_M_MAX];/* Estimated frequencies (mask method) */ + int freq_est_type; /* which estimator to use */ + float ppm; /* Estimated PPM clock offset */ + float SNRest; /* used for LLRs */ + float v_est; /* used for LLRs */ + + /* Parameters used by mod/demod and driving code */ + int nin; /* Number of samples to feed the next demod cycle */ + int burst_mode; /* enables/disables 'burst' mode */ + int lock_nin; /* locks nin during testing */ + + /* modem statistics struct */ + struct MODEM_STATS *stats; + int normalise_eye; /* enables/disables normalisation of eye diagram */ +}; + +/* + * Create a FSK modem + * + * int Fs - Sample frequency + * int Rs - Symbol rate + * int M - 2 for 2FSK, 4 for 4FSK + * int f1_tx - first tone frequency + * int tone_spacing - frequency spacing (for modulator and optional "mask" freq estimator) + */ +struct FSK * fsk_create(int Fs, int Rs, int M, int f1_tx, int tone_spacing); + +/* + * Create a FSK modem - advanced version + * + * int Fs - Sample frequency + * int Rs - Symbol rate + * int M - 2 for 2FSK, 4 for 4FSK + * int P - number of timing offsets to choose from (suggest >= 8) + * int Nsym - windows size for timing estimator + * int f1_tx - first tone frequency + * int tone_spacing - frequency spacing (for modulator and optional "mask" freq estimator) + */ +struct FSK * fsk_create_hbr(int Fs, int Rs, int M, int P, int Nsym, int f1_tx, int tone_spacing); + +/* + * Set the minimum and maximum frequencies at which the freq. estimator can find tones + */ +void fsk_set_freq_est_limits(struct FSK *fsk,int fmin, int fmax); + +/* + * Clear the estimator states + */ +void fsk_clear_estimators(struct FSK *fsk); + +/* + * Fills MODEM_STATS struct with demod statistics + */ +void fsk_get_demod_stats(struct FSK *fsk, struct MODEM_STATS *stats); + +/* + * Destroy an FSK state struct and free it's memory + * + * struct FSK *fsk - FSK config/state struct to be destroyed + */ +void fsk_destroy(struct FSK *fsk); + +/* + * Modulates Nsym bits into N samples + * + * struct FSK *fsk - FSK config/state struct, set up by fsk_create + * float fsk_out[] - Buffer for samples of modulated FSK, fsk->Ts*(Nbits/(M>>1)) in length + * uint8_t tx_bits[] - Buffer containing Nbits unpacked bits + * int Nbits - number of bits to transmit + */ +void fsk_mod(struct FSK *fsk, float fsk_out[], uint8_t tx_bits[], int nbits); + +/* + * Modulates Nsym bits into N samples + * + * struct FSK *fsk - FSK config/state struct, set up by fsk_create + * float fsk_out[] - Buffer for samples of "voltage" used to modulate an external VCO + * - fsk->Ts*(Nbits/(M>>1)) in length + * uint8_t tx_bits[] - Buffer containing Nbits unpacked bits + * int Nbits - number of bits to transmit + */ +void fsk_mod_ext_vco(struct FSK *fsk, float vco_out[], uint8_t tx_bits[], int nbits); + +/* + * Modulates Nsym bits into N complex samples + * + * struct FSK *fsk - FSK config/state struct, set up by fsk_create + * comp fsk_out[] - Buffer for samples of modulated FSK, fsk->Ts*(Nbits/(M>>1)) in length + * uint8_t tx_bits[] - Buffer containing Nbits unpacked bits + * int Nbits - number of bits to transmit + */ +void fsk_mod_c(struct FSK *fsk, COMP fsk_out[], uint8_t tx_bits[], int nbits); + +/* + * Returns the number of samples needed for the next fsk_demod() cycle + * + * struct FSK *fsk - FSK config/state struct, set up by fsk_create + * returns - number of samples to be fed into fsk_demod next cycle + */ +uint32_t fsk_nin(struct FSK *fsk); + + +/* + * Demodulate some number of FSK samples. The number of samples to be + * demodulated can be found by calling fsk_nin(). + * + * struct FSK *fsk - FSK config/state struct, set up by fsk_create + * uint8_t rx_bits[] - Buffer for fsk->Nbits unpacked bits to be written + * float fsk_in[] - nin samples of modulated FSK + */ +void fsk_demod(struct FSK *fsk, uint8_t rx_bits[], COMP fsk_in[]); + +/* + * Soft decision demodulation + * + * struct FSK *fsk - FSK config/state struct, set up by fsk_create + * float rx_flit[] - M x Nsym array of filtermagnitude outputs + * float fsk_in[] - nin samples of modualted FSK + */ +void fsk_demod_sd(struct FSK *fsk, float rx_filt[], COMP fsk_in[]); + +/* enables/disables normalisation of eye diagram samples */ + +void fsk_stats_normalise_eye(struct FSK *fsk, int normalise_enable); + +/* Set the FSK modem into burst demod mode */ + +void fsk_enable_burst_mode(struct FSK *fsk); + +/* Set freq est algorithm 0: peak 1:mask */ +void fsk_set_freq_est_alg(struct FSK *fsk, int est_type); + +#endif + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/libcodec2-android/src/main/cpp/codec2/kiss_fftr.h b/libcodec2-android/src/main/cpp/codec2/kiss_fftr.h new file mode 100644 index 0000000..72e5a57 --- /dev/null +++ b/libcodec2-android/src/main/cpp/codec2/kiss_fftr.h @@ -0,0 +1,46 @@ +#ifndef KISS_FTR_H +#define KISS_FTR_H + +#include "kiss_fft.h" +#ifdef __cplusplus +extern "C" { +#endif + + +/* + + Real optimized version can save about 45% cpu time vs. complex fft of a real seq. + + + + */ + +typedef struct kiss_fftr_state *kiss_fftr_cfg; + + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); +/* + nfft must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); +/* + input timedata has nfft scalar points + output freqdata has nfft/2+1 complex points +*/ + +void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); +/* + input freqdata has nfft/2+1 complex points + output timedata has nfft scalar points +*/ + +#define kiss_fftr_free free + +#ifdef __cplusplus +} +#endif +#endif 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 c132c0d..94feb8a 100644 --- a/libcodec2-android/src/main/java/com/ustadmobile/codec2/Codec2.java +++ b/libcodec2-android/src/main/java/com/ustadmobile/codec2/Codec2.java @@ -27,24 +27,17 @@ public class Codec2 { public static final int CODEC2_FILE_HEADER_SIZE = 7; public native static long create(int mode); - - public native static int getSamplesPerFrame(long con); - - public native static int getBitsSize(long con); - public native static int destroy(long con); - public native static long encode(long con, short[] buf, char[] bits); + public native static int getSamplesPerFrame(long con); + public native static int getBitsSize(long con); - /** - * Decode one frame from codec2. - * - * @param con pointer long, as from the create method - * @param outputBuffer buffer which will be filled with raw PCM audio decoded - * @param bits input buffer containing one frame of audio - * - * @return 0 on successful completion - */ - public native static long decode(long con, short[] outputBuffer, byte[] bits); + public native static long encode(long con, short[] inputSamples, char[] outputBits); + public native static long decode(long con, short[] outputSamples, byte[] inputsBits); + public native static long fskCreate(int sampleFrequency, int symbolRate, int toneFreq, int toneSpacing); + public native static int fskDestroy(long conFsk); + + public native static long fskModulate(long conFsk, short[] outputSamples, char[] inputBits); + public native static long fskDemodulate(long conFsk, short[] inputSamples, short[] outputBits); } \ No newline at end of file